asyncz 0.13.0__tar.gz → 0.13.2__tar.gz
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.
- {asyncz-0.13.0 → asyncz-0.13.2}/PKG-INFO +5 -4
- {asyncz-0.13.0 → asyncz-0.13.2}/README.md +2 -2
- asyncz-0.13.2/asyncz/__init__.py +1 -0
- {asyncz-0.13.0 → asyncz-0.13.2}/asyncz/file_locking.py +11 -0
- {asyncz-0.13.0 → asyncz-0.13.2}/asyncz/schedulers/base.py +1 -1
- {asyncz-0.13.0 → asyncz-0.13.2}/asyncz/schedulers/defaults.py +1 -0
- asyncz-0.13.2/asyncz/stores/file.py +178 -0
- {asyncz-0.13.0 → asyncz-0.13.2}/asyncz/stores/sqlalchemy.py +7 -9
- {asyncz-0.13.0 → asyncz-0.13.2}/pyproject.toml +2 -1
- asyncz-0.13.0/asyncz/__init__.py +0 -1
- {asyncz-0.13.0 → asyncz-0.13.2}/.gitignore +0 -0
- {asyncz-0.13.0 → asyncz-0.13.2}/LICENSE +0 -0
- {asyncz-0.13.0 → asyncz-0.13.2}/asyncz/datastructures.py +0 -0
- {asyncz-0.13.0 → asyncz-0.13.2}/asyncz/enums.py +0 -0
- {asyncz-0.13.0 → asyncz-0.13.2}/asyncz/events/__init__.py +0 -0
- {asyncz-0.13.0 → asyncz-0.13.2}/asyncz/events/base.py +0 -0
- {asyncz-0.13.0 → asyncz-0.13.2}/asyncz/events/constants.py +0 -0
- {asyncz-0.13.0 → asyncz-0.13.2}/asyncz/exceptions.py +0 -0
- {asyncz-0.13.0 → asyncz-0.13.2}/asyncz/executors/__init__.py +0 -0
- {asyncz-0.13.0 → asyncz-0.13.2}/asyncz/executors/asyncio.py +0 -0
- {asyncz-0.13.0 → asyncz-0.13.2}/asyncz/executors/base.py +0 -0
- {asyncz-0.13.0 → asyncz-0.13.2}/asyncz/executors/debug.py +0 -0
- {asyncz-0.13.0 → asyncz-0.13.2}/asyncz/executors/pool.py +0 -0
- {asyncz-0.13.0 → asyncz-0.13.2}/asyncz/executors/process_pool.py +0 -0
- {asyncz-0.13.0 → asyncz-0.13.2}/asyncz/executors/types.py +0 -0
- {asyncz-0.13.0 → asyncz-0.13.2}/asyncz/locks.py +0 -0
- {asyncz-0.13.0 → asyncz-0.13.2}/asyncz/protocols.py +0 -0
- {asyncz-0.13.0 → asyncz-0.13.2}/asyncz/py.typed +0 -0
- {asyncz-0.13.0 → asyncz-0.13.2}/asyncz/schedulers/__init__.py +0 -0
- {asyncz-0.13.0 → asyncz-0.13.2}/asyncz/schedulers/asgi.py +0 -0
- {asyncz-0.13.0 → asyncz-0.13.2}/asyncz/schedulers/asyncio.py +0 -0
- {asyncz-0.13.0 → asyncz-0.13.2}/asyncz/schedulers/datastructures.py +0 -0
- {asyncz-0.13.0 → asyncz-0.13.2}/asyncz/schedulers/types.py +0 -0
- {asyncz-0.13.0 → asyncz-0.13.2}/asyncz/schedulers/utils.py +0 -0
- {asyncz-0.13.0 → asyncz-0.13.2}/asyncz/state.py +0 -0
- {asyncz-0.13.0 → asyncz-0.13.2}/asyncz/stores/__init__.py +0 -0
- {asyncz-0.13.0 → asyncz-0.13.2}/asyncz/stores/base.py +0 -0
- {asyncz-0.13.0 → asyncz-0.13.2}/asyncz/stores/memory.py +0 -0
- {asyncz-0.13.0 → asyncz-0.13.2}/asyncz/stores/mongo.py +0 -0
- {asyncz-0.13.0 → asyncz-0.13.2}/asyncz/stores/redis.py +0 -0
- {asyncz-0.13.0 → asyncz-0.13.2}/asyncz/stores/types.py +0 -0
- {asyncz-0.13.0 → asyncz-0.13.2}/asyncz/tasks/__init__.py +0 -0
- {asyncz-0.13.0 → asyncz-0.13.2}/asyncz/tasks/base.py +0 -0
- {asyncz-0.13.0 → asyncz-0.13.2}/asyncz/tasks/types.py +0 -0
- {asyncz-0.13.0 → asyncz-0.13.2}/asyncz/triggers/__init__.py +0 -0
- {asyncz-0.13.0 → asyncz-0.13.2}/asyncz/triggers/base.py +0 -0
- {asyncz-0.13.0 → asyncz-0.13.2}/asyncz/triggers/combination.py +0 -0
- {asyncz-0.13.0 → asyncz-0.13.2}/asyncz/triggers/cron/__init__.py +0 -0
- {asyncz-0.13.0 → asyncz-0.13.2}/asyncz/triggers/cron/constants.py +0 -0
- {asyncz-0.13.0 → asyncz-0.13.2}/asyncz/triggers/cron/expressions.py +0 -0
- {asyncz-0.13.0 → asyncz-0.13.2}/asyncz/triggers/cron/fields.py +0 -0
- {asyncz-0.13.0 → asyncz-0.13.2}/asyncz/triggers/cron/trigger.py +0 -0
- {asyncz-0.13.0 → asyncz-0.13.2}/asyncz/triggers/cron/types.py +0 -0
- {asyncz-0.13.0 → asyncz-0.13.2}/asyncz/triggers/date.py +0 -0
- {asyncz-0.13.0 → asyncz-0.13.2}/asyncz/triggers/interval.py +0 -0
- {asyncz-0.13.0 → asyncz-0.13.2}/asyncz/triggers/shutdown.py +0 -0
- {asyncz-0.13.0 → asyncz-0.13.2}/asyncz/triggers/types.py +0 -0
- {asyncz-0.13.0 → asyncz-0.13.2}/asyncz/typing.py +0 -0
- {asyncz-0.13.0 → asyncz-0.13.2}/asyncz/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: asyncz
|
|
3
|
-
Version: 0.13.
|
|
3
|
+
Version: 0.13.2
|
|
4
4
|
Summary: The scheduler that nobody wants but every application needs.
|
|
5
5
|
Project-URL: Homepage, https://github.com/dymmond/asyncz
|
|
6
6
|
Project-URL: Documentation, https://asyncz.dymmond.com/
|
|
@@ -27,6 +27,7 @@ Classifier: Programming Language :: Python :: 3.9
|
|
|
27
27
|
Classifier: Programming Language :: Python :: 3.10
|
|
28
28
|
Classifier: Programming Language :: Python :: 3.11
|
|
29
29
|
Classifier: Programming Language :: Python :: 3.12
|
|
30
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
30
31
|
Classifier: Topic :: Internet
|
|
31
32
|
Classifier: Topic :: Internet :: WWW/HTTP
|
|
32
33
|
Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers
|
|
@@ -43,7 +44,7 @@ Requires-Dist: tzlocal; extra == 'localtime'
|
|
|
43
44
|
Provides-Extra: loguru
|
|
44
45
|
Requires-Dist: loguru<0.8.0,>=0.7.0; extra == 'loguru'
|
|
45
46
|
Provides-Extra: testing
|
|
46
|
-
Requires-Dist: esmerald; extra == 'testing'
|
|
47
|
+
Requires-Dist: esmerald>=3.7.5; extra == 'testing'
|
|
47
48
|
Requires-Dist: httpx; extra == 'testing'
|
|
48
49
|
Requires-Dist: lilya; extra == 'testing'
|
|
49
50
|
Requires-Dist: loguru<0.8.0,>=0.7.0; extra == 'testing'
|
|
@@ -71,8 +72,8 @@ Description-Content-Type: text/markdown
|
|
|
71
72
|
</p>
|
|
72
73
|
|
|
73
74
|
<p align="center">
|
|
74
|
-
<a href="https://github.com/dymmond/asyncz/workflows/
|
|
75
|
-
<img src="https://github.com/dymmond/asyncz/workflows/
|
|
75
|
+
<a href="https://github.com/dymmond/asyncz/actions/workflows/test-suite.yml/badge.svg?event=push&branch=main" target="_blank">
|
|
76
|
+
<img src="https://github.com/dymmond/asyncz/actions/workflows/test-suite.yml/badge.svg?event=push&branch=main" alt="Test Suite">
|
|
76
77
|
</a>
|
|
77
78
|
|
|
78
79
|
<a href="https://pypi.org/project/asyncz" target="_blank">
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
</p>
|
|
10
10
|
|
|
11
11
|
<p align="center">
|
|
12
|
-
<a href="https://github.com/dymmond/asyncz/workflows/
|
|
13
|
-
<img src="https://github.com/dymmond/asyncz/workflows/
|
|
12
|
+
<a href="https://github.com/dymmond/asyncz/actions/workflows/test-suite.yml/badge.svg?event=push&branch=main" target="_blank">
|
|
13
|
+
<img src="https://github.com/dymmond/asyncz/actions/workflows/test-suite.yml/badge.svg?event=push&branch=main" alt="Test Suite">
|
|
14
14
|
</a>
|
|
15
15
|
|
|
16
16
|
<a href="https://pypi.org/project/asyncz" target="_blank">
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.13.2"
|
|
@@ -17,7 +17,11 @@ Example Usage::
|
|
|
17
17
|
... f.write('Edgy')
|
|
18
18
|
"""
|
|
19
19
|
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
20
22
|
import os
|
|
23
|
+
from collections.abc import Generator
|
|
24
|
+
from contextlib import contextmanager
|
|
21
25
|
from typing import IO, Any
|
|
22
26
|
|
|
23
27
|
__all__ = ("LOCK_EX", "LOCK_SH", "LOCK_NB", "lock", "unlock")
|
|
@@ -114,3 +118,10 @@ else:
|
|
|
114
118
|
def unlock(f: IO) -> bool:
|
|
115
119
|
fcntl.flock(_fd(f), fcntl.LOCK_UN)
|
|
116
120
|
return True
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
@contextmanager
|
|
124
|
+
def with_lock(f: IO, flags: int) -> Generator[IO, None, None]:
|
|
125
|
+
lock(f, flags)
|
|
126
|
+
yield f
|
|
127
|
+
unlock(f)
|
|
@@ -1138,7 +1138,7 @@ class BaseScheduler(SchedulerType):
|
|
|
1138
1138
|
due_tasks: list[TaskType] = store.get_due_tasks(now)
|
|
1139
1139
|
except Exception as e:
|
|
1140
1140
|
self.loggers[self.logger_name].warning(
|
|
1141
|
-
f
|
|
1141
|
+
f'Error getting due tasks from the store "{store_alias}": {e}.'
|
|
1142
1142
|
)
|
|
1143
1143
|
retry_wakeup_time = now + timedelta(seconds=self.store_retry_interval)
|
|
1144
1144
|
if not next_wakeup_time or next_wakeup_time > retry_wakeup_time:
|
|
@@ -19,6 +19,7 @@ executors: dict[str, str] = {
|
|
|
19
19
|
|
|
20
20
|
stores: dict[str, str] = {
|
|
21
21
|
"memory": "asyncz.stores.memory:MemoryStore",
|
|
22
|
+
"file": "asyncz.stores.file:FileStore",
|
|
22
23
|
"mongodb": "asyncz.stores.mongo:MongoDBStore",
|
|
23
24
|
"redis": "asyncz.stores.redis:RedisStore",
|
|
24
25
|
"sqlalchemy": "asyncz.stores.sqlalchemy:SQLAlchemyStore",
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import glob
|
|
4
|
+
import os
|
|
5
|
+
import pickle
|
|
6
|
+
import shutil
|
|
7
|
+
from contextlib import suppress
|
|
8
|
+
from datetime import datetime
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import TYPE_CHECKING, Any, Optional, Union, cast
|
|
11
|
+
|
|
12
|
+
from asyncz.exceptions import ConflictIdError, TaskLookupError
|
|
13
|
+
from asyncz.file_locking import LOCK_EX, LOCK_SH, with_lock
|
|
14
|
+
from asyncz.stores.base import BaseStore
|
|
15
|
+
from asyncz.tasks import Task
|
|
16
|
+
from asyncz.tasks.types import TaskType
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from asyncz.schedulers.types import SchedulerType
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class FileStore(BaseStore):
|
|
23
|
+
"""
|
|
24
|
+
Stores tasks via sqlalchemy in a database.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
directory - The directory to store the tasks. String or Path.
|
|
28
|
+
suffix - The task suffix.
|
|
29
|
+
pickle_protocol - Pickle protocol level to use (for serialization), defaults to the
|
|
30
|
+
highest available.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
forbidden_characters: set[str] = {"/", "\\", "\0", ":"}
|
|
34
|
+
|
|
35
|
+
def __init__(
|
|
36
|
+
self,
|
|
37
|
+
directory: Union[str, os.PathLike],
|
|
38
|
+
suffix: str = ".task",
|
|
39
|
+
mode: int = 0o700,
|
|
40
|
+
cleanup_directory: bool = False,
|
|
41
|
+
pickle_protocol: Optional[int] = pickle.HIGHEST_PROTOCOL,
|
|
42
|
+
**kwargs: Any,
|
|
43
|
+
) -> None:
|
|
44
|
+
super().__init__(**kwargs)
|
|
45
|
+
self.pickle_protocol = pickle_protocol
|
|
46
|
+
self.directory = Path(directory)
|
|
47
|
+
self.mode = mode
|
|
48
|
+
self.cleanup_directory = cleanup_directory
|
|
49
|
+
self.suffix = suffix
|
|
50
|
+
|
|
51
|
+
def check_task_id(self, task_id: str | None) -> None:
|
|
52
|
+
if task_id is None:
|
|
53
|
+
raise RuntimeError("Task id is None")
|
|
54
|
+
if task_id.startswith("."):
|
|
55
|
+
raise RuntimeError(f'Invalid character in task id: "{task_id}".')
|
|
56
|
+
for char in task_id:
|
|
57
|
+
if char in self.forbidden_characters:
|
|
58
|
+
raise RuntimeError(f'Invalid character in task id: "{task_id}".')
|
|
59
|
+
|
|
60
|
+
def start(self, scheduler: Any, alias: str) -> None:
|
|
61
|
+
"""
|
|
62
|
+
When starting omits from the index any documents that lack next_run_time field.
|
|
63
|
+
"""
|
|
64
|
+
super().start(scheduler, alias)
|
|
65
|
+
self.directory.mkdir(self.mode, parents=True, exist_ok=True)
|
|
66
|
+
if not self.directory.is_dir():
|
|
67
|
+
raise RuntimeError("Not a directory.")
|
|
68
|
+
|
|
69
|
+
def shutdown(self) -> None:
|
|
70
|
+
if self.cleanup_directory:
|
|
71
|
+
shutil.rmtree(self.directory, ignore_errors=True)
|
|
72
|
+
super().shutdown()
|
|
73
|
+
|
|
74
|
+
def lookup_task(self, task_id: str) -> Optional[TaskType]:
|
|
75
|
+
self.check_task_id(task_id)
|
|
76
|
+
task_path = self.directory / f"{task_id}{self.suffix}"
|
|
77
|
+
try:
|
|
78
|
+
with open(task_path, "rb") as read_ob, with_lock(read_ob, LOCK_SH):
|
|
79
|
+
task = self.rebuild_task(read_ob.read())
|
|
80
|
+
except Exception:
|
|
81
|
+
task_path.unlink(missing_ok=True)
|
|
82
|
+
task = None
|
|
83
|
+
return task
|
|
84
|
+
|
|
85
|
+
def rebuild_task(self, state: Any) -> TaskType:
|
|
86
|
+
state = pickle.loads(self.conditional_decrypt(state))
|
|
87
|
+
task = Task.__new__(Task)
|
|
88
|
+
task.__setstate__(state)
|
|
89
|
+
task.scheduler = cast("SchedulerType", self.scheduler)
|
|
90
|
+
task.store_alias = self.alias
|
|
91
|
+
return task
|
|
92
|
+
|
|
93
|
+
def get_due_tasks(self, now: datetime) -> list[TaskType]:
|
|
94
|
+
return [
|
|
95
|
+
task
|
|
96
|
+
for task in self.get_all_tasks()
|
|
97
|
+
if task.next_run_time is not None and task.next_run_time <= now
|
|
98
|
+
]
|
|
99
|
+
|
|
100
|
+
def get_tasks(self) -> list[TaskType]:
|
|
101
|
+
tasks: list[tuple[TaskType, os.stat_result]] = []
|
|
102
|
+
with os.scandir(self.directory) as scanner:
|
|
103
|
+
for entry in scanner:
|
|
104
|
+
if not entry.name.endswith(self.suffix) or not entry.is_file():
|
|
105
|
+
continue
|
|
106
|
+
try:
|
|
107
|
+
with open(entry.path, "rb") as read_ob, with_lock(read_ob, LOCK_SH):
|
|
108
|
+
task = self.rebuild_task(read_ob.read())
|
|
109
|
+
tasks.append((task, entry.stat()))
|
|
110
|
+
except Exception:
|
|
111
|
+
with suppress(FileNotFoundError):
|
|
112
|
+
os.unlink(entry.path)
|
|
113
|
+
return [
|
|
114
|
+
task
|
|
115
|
+
for task, _ in sorted(
|
|
116
|
+
tasks,
|
|
117
|
+
key=lambda task_stat: (
|
|
118
|
+
int(task_stat[0].next_run_time is None),
|
|
119
|
+
task_stat[0].next_run_time,
|
|
120
|
+
# sort for task creation not update
|
|
121
|
+
task_stat[1].st_ctime,
|
|
122
|
+
),
|
|
123
|
+
)
|
|
124
|
+
]
|
|
125
|
+
|
|
126
|
+
def get_next_run_time(self) -> Optional[datetime]:
|
|
127
|
+
next_run_time: datetime | None = None
|
|
128
|
+
for task in self.get_all_tasks():
|
|
129
|
+
if task.next_run_time is None:
|
|
130
|
+
break
|
|
131
|
+
if next_run_time is None or next_run_time >= task.next_run_time:
|
|
132
|
+
next_run_time = task.next_run_time
|
|
133
|
+
return next_run_time
|
|
134
|
+
|
|
135
|
+
def get_all_tasks(self) -> list[TaskType]:
|
|
136
|
+
return self.get_tasks()
|
|
137
|
+
|
|
138
|
+
def add_task(self, task: TaskType) -> None:
|
|
139
|
+
self.check_task_id(task.id)
|
|
140
|
+
task_path = self.directory / f"{task.id}{self.suffix}"
|
|
141
|
+
try:
|
|
142
|
+
with task_path.open("xb") as write_ob, with_lock(write_ob, LOCK_EX):
|
|
143
|
+
write_ob.write(
|
|
144
|
+
self.conditional_encrypt(
|
|
145
|
+
pickle.dumps(task.__getstate__(), self.pickle_protocol)
|
|
146
|
+
)
|
|
147
|
+
)
|
|
148
|
+
except FileExistsError:
|
|
149
|
+
raise ConflictIdError(task.id) from None
|
|
150
|
+
|
|
151
|
+
def update_task(self, task: TaskType) -> None:
|
|
152
|
+
self.check_task_id(task.id)
|
|
153
|
+
task_path = self.directory / f"{task.id}{self.suffix}"
|
|
154
|
+
try:
|
|
155
|
+
with task_path.open("r+b") as write_ob, with_lock(write_ob, LOCK_EX):
|
|
156
|
+
write_ob.truncate()
|
|
157
|
+
write_ob.write(
|
|
158
|
+
self.conditional_encrypt(
|
|
159
|
+
pickle.dumps(task.__getstate__(), self.pickle_protocol)
|
|
160
|
+
)
|
|
161
|
+
)
|
|
162
|
+
except FileNotFoundError:
|
|
163
|
+
raise TaskLookupError(task.id) from None
|
|
164
|
+
|
|
165
|
+
def delete_task(self, task_id: str) -> None:
|
|
166
|
+
self.check_task_id(task_id)
|
|
167
|
+
task_path = self.directory / f"{task_id}{self.suffix}"
|
|
168
|
+
try:
|
|
169
|
+
task_path.unlink(missing_ok=False)
|
|
170
|
+
except FileNotFoundError:
|
|
171
|
+
raise TaskLookupError(task_id) from None
|
|
172
|
+
|
|
173
|
+
def remove_all_tasks(self) -> None:
|
|
174
|
+
for task_path in self.directory.glob(f"*{glob.escape(self.suffix)}"):
|
|
175
|
+
task_path.unlink(missing_ok=True)
|
|
176
|
+
|
|
177
|
+
def __repr__(self) -> str:
|
|
178
|
+
return f"<{self.__class__.__name__} (directory={self.directory})>"
|
|
@@ -41,7 +41,7 @@ class SQLAlchemyStore(BaseStore):
|
|
|
41
41
|
super().__init__(**kwargs)
|
|
42
42
|
self.pickle_protocol = pickle_protocol
|
|
43
43
|
if isinstance(database, str):
|
|
44
|
-
database = sqlalchemy.create_engine(database)
|
|
44
|
+
database = sqlalchemy.create_engine(database, **kwargs)
|
|
45
45
|
if not database:
|
|
46
46
|
raise ValueError("database must not be empty or None")
|
|
47
47
|
self.engine: sqlalchemy.Engine = database
|
|
@@ -63,6 +63,10 @@ class SQLAlchemyStore(BaseStore):
|
|
|
63
63
|
super().start(scheduler, alias)
|
|
64
64
|
self.metadata.create_all(self.engine)
|
|
65
65
|
|
|
66
|
+
def shutdown(self) -> None:
|
|
67
|
+
self.engine.dispose()
|
|
68
|
+
super().shutdown()
|
|
69
|
+
|
|
66
70
|
def lookup_task(self, task_id: str) -> Optional[TaskType]:
|
|
67
71
|
tasks = self.get_tasks(self.table.c.id == task_id, limit=1)
|
|
68
72
|
return tasks[0] if tasks else None
|
|
@@ -82,7 +86,7 @@ class SQLAlchemyStore(BaseStore):
|
|
|
82
86
|
def get_tasks(self, conditions: Any = None, limit: int = 0) -> list[TaskType]:
|
|
83
87
|
tasks: list[TaskType] = []
|
|
84
88
|
failed_task_ids = []
|
|
85
|
-
stmt = self.table.select().order_by(self.table.c.next_run_time.asc())
|
|
89
|
+
stmt = self.table.select().order_by(self.table.c.next_run_time.asc().nullslast())
|
|
86
90
|
if conditions is not None:
|
|
87
91
|
stmt = stmt.where(conditions)
|
|
88
92
|
|
|
@@ -119,9 +123,7 @@ class SQLAlchemyStore(BaseStore):
|
|
|
119
123
|
return utc_timestamp_to_datetime(row.next_run_time) if row else None
|
|
120
124
|
|
|
121
125
|
def get_all_tasks(self) -> list[TaskType]:
|
|
122
|
-
|
|
123
|
-
self.fix_paused_tasks(tasks)
|
|
124
|
-
return tasks
|
|
126
|
+
return self.get_tasks()
|
|
125
127
|
|
|
126
128
|
def add_task(self, task: TaskType) -> None:
|
|
127
129
|
data = {
|
|
@@ -168,9 +170,5 @@ class SQLAlchemyStore(BaseStore):
|
|
|
168
170
|
with self.engine.begin() as conn:
|
|
169
171
|
conn.execute(self.table.delete())
|
|
170
172
|
|
|
171
|
-
def shutdown(self) -> None:
|
|
172
|
-
self.engine.dispose()
|
|
173
|
-
super().shutdown()
|
|
174
|
-
|
|
175
173
|
def __repr__(self) -> str:
|
|
176
174
|
return f"<{self.__class__.__name__} (database={self.engine.url})>"
|
|
@@ -34,6 +34,7 @@ classifiers = [
|
|
|
34
34
|
"Programming Language :: Python :: 3.10",
|
|
35
35
|
"Programming Language :: Python :: 3.11",
|
|
36
36
|
"Programming Language :: Python :: 3.12",
|
|
37
|
+
"Programming Language :: Python :: 3.13",
|
|
37
38
|
"Topic :: Internet :: WWW/HTTP :: HTTP Servers",
|
|
38
39
|
"Topic :: Internet :: WWW/HTTP",
|
|
39
40
|
]
|
|
@@ -76,7 +77,7 @@ testing = [
|
|
|
76
77
|
"pytest-loguru>=0.2.0,<1",
|
|
77
78
|
"redis>=4.4.0,<6.0.0",
|
|
78
79
|
"mypy>=0.982,<2.0.0",
|
|
79
|
-
"esmerald",
|
|
80
|
+
"esmerald>=3.7.5",
|
|
80
81
|
"starlette",
|
|
81
82
|
"lilya",
|
|
82
83
|
"sqlalchemy",
|
asyncz-0.13.0/asyncz/__init__.py
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.13.0"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|