django-spire 0.23.8__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.
Files changed (25) hide show
  1. django_spire/consts.py +1 -1
  2. django_spire/contrib/progress/__init__.py +7 -13
  3. django_spire/contrib/progress/enums.py +2 -1
  4. django_spire/contrib/progress/session.py +283 -0
  5. django_spire/contrib/progress/static/django_spire/js/contrib/progress/progress.js +35 -57
  6. django_spire/contrib/progress/tasks.py +67 -0
  7. django_spire/contrib/progress/templates/django_spire/contrib/progress/card/card.html +25 -24
  8. django_spire/contrib/progress/templates/django_spire/contrib/progress/modal/content.html +58 -67
  9. django_spire/core/management/commands/spire_startapp_pkg/template/app/apps.py.template +2 -0
  10. django_spire/core/management/commands/spire_startapp_pkg/template/app/urls/__init__.py.template +2 -0
  11. django_spire/core/management/commands/spire_startapp_pkg/template/app/urls/form_urls.py.template +2 -0
  12. django_spire/core/management/commands/spire_startapp_pkg/template/app/urls/page_urls.py.template +2 -0
  13. django_spire/core/templates/django_spire/card/card.html +1 -1
  14. django_spire/core/templates/django_spire/card/title_card.html +7 -4
  15. django_spire/core/templates/django_spire/table/base.html +4 -4
  16. {django_spire-0.23.8.dist-info → django_spire-0.23.10.dist-info}/METADATA +2 -2
  17. {django_spire-0.23.8.dist-info → django_spire-0.23.10.dist-info}/RECORD +20 -23
  18. {django_spire-0.23.8.dist-info → django_spire-0.23.10.dist-info}/licenses/LICENSE.md +1 -1
  19. django_spire/contrib/progress/mixins.py +0 -36
  20. django_spire/contrib/progress/runner.py +0 -140
  21. django_spire/contrib/progress/states.py +0 -64
  22. django_spire/contrib/progress/task.py +0 -40
  23. django_spire/contrib/progress/tracker.py +0 -245
  24. {django_spire-0.23.8.dist-info → django_spire-0.23.10.dist-info}/WHEEL +0 -0
  25. {django_spire-0.23.8.dist-info → django_spire-0.23.10.dist-info}/top_level.txt +0 -0
@@ -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
@@ -1,245 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import logging
4
- import threading
5
- import time
6
-
7
- from typing import TYPE_CHECKING
8
-
9
- from django.core.cache import cache
10
-
11
- from django_spire.contrib.progress.enums import ProgressStatus
12
- from django_spire.contrib.progress.runner import TaskRunner
13
- from django_spire.contrib.progress.states import TaskState, TrackerState
14
- from django_spire.contrib.progress.task import Task, TaskResult
15
-
16
- if TYPE_CHECKING:
17
- from typing import Any, Callable
18
-
19
-
20
- log = logging.getLogger(__name__)
21
-
22
-
23
- class ProgressTracker:
24
- def __init__(self, key: str, timeout: int = 300) -> None:
25
- self._key = f'progress_tracker_{key}'
26
- self._lock = threading.Lock()
27
- self._tasks: list[Task] = []
28
- self._timeout = timeout
29
-
30
- def clear(self) -> None:
31
- cache.delete(self._key)
32
-
33
- def complete(self, message: str = 'Complete!') -> None:
34
- with self._lock:
35
- state = self._get_state()
36
- state.message = message
37
- state.progress = 100
38
- state.status = ProgressStatus.COMPLETE
39
- self._save_state(state)
40
-
41
- def error(self, message: str) -> None:
42
- with self._lock:
43
- state = self._get_state()
44
- state.message = message
45
- state.progress = 0
46
- state.status = ProgressStatus.ERROR
47
- self._save_state(state)
48
-
49
- def execute(self) -> dict[str, Any] | None:
50
- self._create_state()
51
-
52
- error = False
53
- results: dict[str, TaskResult] = {}
54
-
55
- try:
56
- error = self._execute_parallel_tasks(results)
57
-
58
- if not error:
59
- error = self._execute_sequential_tasks(results)
60
-
61
- if not error:
62
- self.complete()
63
- time.sleep(1)
64
- except BaseException:
65
- log.exception('Progress tracker failed')
66
- error = True
67
-
68
- if error:
69
- self.error('An unexpected error occurred')
70
- time.sleep(2)
71
-
72
- self.clear()
73
-
74
- if error:
75
- return None
76
-
77
- return {name: result.value for name, result in results.items()}
78
-
79
- def get(self) -> dict[str, Any] | None:
80
- return cache.get(self._key)
81
-
82
- def parallel(
83
- self,
84
- name: str,
85
- func: Callable[[], Any],
86
- label: str | None = None
87
- ) -> ProgressTracker:
88
- task = Task(
89
- func=func,
90
- label=label or self._generate_label(name),
91
- name=name,
92
- parallel=True,
93
- )
94
-
95
- self._tasks.append(task)
96
-
97
- return self
98
-
99
- def sequential(
100
- self,
101
- name: str,
102
- func: Callable[[dict[str, Any]], Any],
103
- label: str | None = None
104
- ) -> ProgressTracker:
105
- task = Task(
106
- func=func,
107
- label=label or self._generate_label(name),
108
- name=name,
109
- parallel=False,
110
- )
111
-
112
- self._tasks.append(task)
113
-
114
- return self
115
-
116
- def start(self) -> None:
117
- state = TrackerState()
118
- cache.set(self._key, state.to_dict(), timeout=self._timeout)
119
-
120
- def update(
121
- self,
122
- status: ProgressStatus,
123
- message: str,
124
- progress: int,
125
- **kwargs: Any
126
- ) -> None:
127
- with self._lock:
128
- state = self._get_state()
129
- state.message = message
130
- state.progress = progress
131
- state.status = status
132
- self._save_state(state, **kwargs)
133
-
134
- def _calculate_overall_progress(self, state: TrackerState) -> int:
135
- if not state.tasks:
136
- return 0
137
-
138
- total = sum(
139
- task.progress
140
- for task in state.tasks.values()
141
- )
142
-
143
- return int(total / len(state.tasks))
144
-
145
- def _create_state(self) -> None:
146
- task_names = [task.name for task in self._tasks]
147
-
148
- state = TrackerState(
149
- task_order=task_names,
150
- tasks={name: TaskState() for name in task_names}
151
- )
152
-
153
- cache.set(self._key, state.to_dict(), timeout=self._timeout)
154
-
155
- def _execute_parallel_tasks(self, results: dict[str, TaskResult]) -> bool:
156
- tasks = [
157
- task
158
- for task in self._tasks if task.parallel
159
- ]
160
-
161
- if not tasks:
162
- return False
163
-
164
- runners: list[TaskRunner] = []
165
- threads: list[threading.Thread] = []
166
-
167
- for task in tasks:
168
- runner = TaskRunner(self, task)
169
- runners.append(runner)
170
-
171
- thread = threading.Thread(target=runner.run_parallel, args=(results,))
172
- threads.append(thread)
173
- thread.start()
174
-
175
- for thread in threads:
176
- thread.join(timeout=self._timeout)
177
-
178
- if thread.is_alive():
179
- for runner in runners:
180
- runner.stop()
181
-
182
- return True
183
-
184
- return any(
185
- results.get(task.name, TaskResult()).failed
186
- for task in tasks
187
- )
188
-
189
- def _execute_sequential_tasks(self, results: dict[str, TaskResult]) -> bool:
190
- tasks = [
191
- task
192
- for task in self._tasks
193
- if not task.parallel
194
- ]
195
-
196
- for task in tasks:
197
- runner = TaskRunner(self, task)
198
- runner.run_sequential(results)
199
-
200
- if results.get(task.name, TaskResult()).failed:
201
- return True
202
-
203
- return False
204
-
205
- def _generate_label(self, name: str) -> str:
206
- return name.replace('_', ' ').title()
207
-
208
- def _get_state(self) -> TrackerState:
209
- task_names = [task.name for task in self._tasks]
210
- data = cache.get(self._key)
211
-
212
- if data is None:
213
- return TrackerState(
214
- task_order=task_names,
215
- tasks={name: TaskState() for name in task_names}
216
- )
217
-
218
- return TrackerState.from_dict(data)
219
-
220
- def _save_state(self, state: TrackerState, **kwargs: Any) -> None:
221
- data = state.to_dict()
222
- data.update(kwargs)
223
- cache.set(self._key, data, timeout=self._timeout)
224
-
225
- def _update_task(
226
- self,
227
- name: str,
228
- status: ProgressStatus,
229
- message: str,
230
- progress: int
231
- ) -> None:
232
- with self._lock:
233
- state = self._get_state()
234
-
235
- state.tasks[name] = TaskState(
236
- message=message,
237
- progress=progress,
238
- status=status,
239
- )
240
-
241
- state.message = message
242
- state.progress = self._calculate_overall_progress(state)
243
- state.status = ProgressStatus.PROCESSING
244
-
245
- self._save_state(state)