asyncz 0.8.1__tar.gz → 0.8.3__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.
Files changed (55) hide show
  1. {asyncz-0.8.1 → asyncz-0.8.3}/PKG-INFO +1 -1
  2. asyncz-0.8.3/asyncz/__init__.py +1 -0
  3. {asyncz-0.8.1 → asyncz-0.8.3}/asyncz/datastructures.py +6 -5
  4. {asyncz-0.8.1 → asyncz-0.8.3}/asyncz/executors/base.py +1 -3
  5. {asyncz-0.8.1 → asyncz-0.8.3}/asyncz/schedulers/base.py +1 -4
  6. {asyncz-0.8.1 → asyncz-0.8.3}/asyncz/stores/memory.py +1 -1
  7. {asyncz-0.8.1 → asyncz-0.8.3}/asyncz/triggers/interval.py +2 -10
  8. {asyncz-0.8.1 → asyncz-0.8.3}/asyncz/utils.py +5 -1
  9. asyncz-0.8.1/asyncz/__init__.py +0 -1
  10. asyncz-0.8.1/asyncz/contrib/esmerald/__init__.py +0 -0
  11. asyncz-0.8.1/asyncz/contrib/esmerald/decorator.py +0 -40
  12. asyncz-0.8.1/asyncz/contrib/esmerald/scheduler.py +0 -217
  13. asyncz-0.8.1/asyncz/stores/__init__.py +0 -0
  14. {asyncz-0.8.1 → asyncz-0.8.3}/.gitignore +0 -0
  15. {asyncz-0.8.1 → asyncz-0.8.3}/LICENSE +0 -0
  16. {asyncz-0.8.1 → asyncz-0.8.3}/README.md +0 -0
  17. {asyncz-0.8.1 → asyncz-0.8.3}/asyncz/_mapping.py +0 -0
  18. {asyncz-0.8.1 → asyncz-0.8.3}/asyncz/enums.py +0 -0
  19. {asyncz-0.8.1 → asyncz-0.8.3}/asyncz/events/__init__.py +0 -0
  20. {asyncz-0.8.1 → asyncz-0.8.3}/asyncz/events/base.py +0 -0
  21. {asyncz-0.8.1 → asyncz-0.8.3}/asyncz/events/constants.py +0 -0
  22. {asyncz-0.8.1 → asyncz-0.8.3}/asyncz/exceptions.py +0 -0
  23. {asyncz-0.8.1 → asyncz-0.8.3}/asyncz/executors/__init__.py +0 -0
  24. {asyncz-0.8.1 → asyncz-0.8.3}/asyncz/executors/asyncio.py +0 -0
  25. {asyncz-0.8.1 → asyncz-0.8.3}/asyncz/executors/debug.py +0 -0
  26. {asyncz-0.8.1 → asyncz-0.8.3}/asyncz/executors/pool.py +0 -0
  27. {asyncz-0.8.1 → asyncz-0.8.3}/asyncz/executors/types.py +0 -0
  28. {asyncz-0.8.1 → asyncz-0.8.3}/asyncz/py.typed +0 -0
  29. {asyncz-0.8.1 → asyncz-0.8.3}/asyncz/schedulers/__init__.py +0 -0
  30. {asyncz-0.8.1 → asyncz-0.8.3}/asyncz/schedulers/asyncio.py +0 -0
  31. {asyncz-0.8.1 → asyncz-0.8.3}/asyncz/schedulers/datastructures.py +0 -0
  32. {asyncz-0.8.1 → asyncz-0.8.3}/asyncz/schedulers/types.py +0 -0
  33. {asyncz-0.8.1 → asyncz-0.8.3}/asyncz/schedulers/utils.py +0 -0
  34. {asyncz-0.8.1 → asyncz-0.8.3}/asyncz/state.py +0 -0
  35. {asyncz-0.8.1/asyncz/contrib → asyncz-0.8.3/asyncz/stores}/__init__.py +0 -0
  36. {asyncz-0.8.1 → asyncz-0.8.3}/asyncz/stores/base.py +0 -0
  37. {asyncz-0.8.1 → asyncz-0.8.3}/asyncz/stores/mongo.py +0 -0
  38. {asyncz-0.8.1 → asyncz-0.8.3}/asyncz/stores/redis.py +0 -0
  39. {asyncz-0.8.1 → asyncz-0.8.3}/asyncz/stores/types.py +0 -0
  40. {asyncz-0.8.1 → asyncz-0.8.3}/asyncz/tasks/__init__.py +0 -0
  41. {asyncz-0.8.1 → asyncz-0.8.3}/asyncz/tasks/base.py +0 -0
  42. {asyncz-0.8.1 → asyncz-0.8.3}/asyncz/tasks/types.py +0 -0
  43. {asyncz-0.8.1 → asyncz-0.8.3}/asyncz/triggers/__init__.py +0 -0
  44. {asyncz-0.8.1 → asyncz-0.8.3}/asyncz/triggers/base.py +0 -0
  45. {asyncz-0.8.1 → asyncz-0.8.3}/asyncz/triggers/combination.py +0 -0
  46. {asyncz-0.8.1 → asyncz-0.8.3}/asyncz/triggers/cron/__init__.py +0 -0
  47. {asyncz-0.8.1 → asyncz-0.8.3}/asyncz/triggers/cron/constants.py +0 -0
  48. {asyncz-0.8.1 → asyncz-0.8.3}/asyncz/triggers/cron/expressions.py +0 -0
  49. {asyncz-0.8.1 → asyncz-0.8.3}/asyncz/triggers/cron/fields.py +0 -0
  50. {asyncz-0.8.1 → asyncz-0.8.3}/asyncz/triggers/cron/trigger.py +0 -0
  51. {asyncz-0.8.1 → asyncz-0.8.3}/asyncz/triggers/cron/types.py +0 -0
  52. {asyncz-0.8.1 → asyncz-0.8.3}/asyncz/triggers/date.py +0 -0
  53. {asyncz-0.8.1 → asyncz-0.8.3}/asyncz/triggers/types.py +0 -0
  54. {asyncz-0.8.1 → asyncz-0.8.3}/asyncz/typing.py +0 -0
  55. {asyncz-0.8.1 → asyncz-0.8.3}/pyproject.toml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: asyncz
3
- Version: 0.8.1
3
+ Version: 0.8.3
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/
@@ -0,0 +1 @@
1
+ __version__ = "0.8.3"
@@ -1,4 +1,5 @@
1
- from datetime import datetime, timedelta, timezone, tzinfo
1
+ from datetime import datetime, timedelta, tzinfo
2
+ from datetime import timezone as dt_timezone
2
3
  from typing import TYPE_CHECKING, Any, Callable, List, Optional, Union
3
4
 
4
5
  from pydantic import BaseModel, ConfigDict
@@ -28,11 +29,11 @@ class IntervalState(BaseDatastructureState):
28
29
  Handles the state for a IntervalTrigger.
29
30
  """
30
31
 
31
- timezone: Union[timezone, str, tzinfo]
32
+ timezone: Union[dt_timezone, str, tzinfo]
32
33
  start_at: datetime
33
34
  end_at: Optional[datetime] = None
34
- interval: Optional[Union[timezone, timedelta]] = None
35
- jitter: int
35
+ interval: Optional[Union[dt_timezone, timedelta]] = None
36
+ jitter: Optional[int] = None
36
37
 
37
38
 
38
39
  class CombinationState(BaseDatastructureState):
@@ -49,7 +50,7 @@ class CronState(BaseDatastructureState):
49
50
  Handles the state of the CronTrigger.
50
51
  """
51
52
 
52
- timezone: Optional[Union[timezone, str, tzinfo]] = None
53
+ timezone: Optional[Union[dt_timezone, str, tzinfo]] = None
53
54
  start_at: Optional[datetime] = None
54
55
  end_at: Optional[datetime] = None
55
56
  fields: Optional[List[Any]] = None
@@ -97,9 +97,7 @@ class BaseExecutor(BaseStateExtra, ABC):
97
97
  if self.instances[task_id] == 0:
98
98
  del self.instances[task_id]
99
99
 
100
- self.logger.opt(exception=True).error(
101
- f"Error running task {task_id}", exc_info=True
102
- )
100
+ self.logger.opt(exception=True).error(f"Error running task {task_id}", exc_info=True)
103
101
 
104
102
 
105
103
  def run_task(
@@ -155,7 +155,6 @@ class BaseScheduler(BaseStateExtra, ABC):
155
155
  raise SchedulerAlreadyRunningError()
156
156
 
157
157
  self.check_uwsgi()
158
-
159
158
  with self.executor_lock:
160
159
  if "default" not in self.executors:
161
160
  self.add_executor(self.create_default_executor(), "default")
@@ -414,7 +413,6 @@ class BaseScheduler(BaseStateExtra, ABC):
414
413
  }
415
414
  task_kwargs = {key: value for key, value in task_struct.items() if value is not undefined}
416
415
  task = Task(self, **task_kwargs)
417
-
418
416
  with self.store_lock:
419
417
  if self.state == SchedulerState.STATE_STOPPED:
420
418
  self.pending_tasks.append((task, store, replace_existing))
@@ -667,7 +665,7 @@ class BaseScheduler(BaseStateExtra, ABC):
667
665
 
668
666
  task_defaults = config.get("task_defaults", {})
669
667
  self.task_defaults = TaskDefaultStruct(
670
- mistrigger_grace_time=to_int(task_defaults.get("mistrigger_grace_time")),
668
+ mistrigger_grace_time=to_int(task_defaults.get("mistrigger_grace_time", 1)),
671
669
  coalesce=to_bool(task_defaults.get("coalesce", True)),
672
670
  max_instances=to_int(task_defaults.get("max_instances", 1)),
673
671
  )
@@ -825,7 +823,6 @@ class BaseScheduler(BaseStateExtra, ABC):
825
823
 
826
824
  # Apply replacements
827
825
  task._update(**replacements)
828
-
829
826
  # Add the task to the given store
830
827
  store = self.lookup_store(store_alias)
831
828
  try:
@@ -11,7 +11,7 @@ if TYPE_CHECKING:
11
11
 
12
12
  class MemoryStore(BaseStore):
13
13
  """
14
- Stores tasks in an arrat in RAM. Provides no persistance support.
14
+ Stores tasks in an array in RAM. Provides no persistance support.
15
15
  """
16
16
 
17
17
  def __init__(self, **kwargs):
@@ -6,13 +6,7 @@ from tzlocal import get_localzone
6
6
 
7
7
  from asyncz.datastructures import IntervalState
8
8
  from asyncz.triggers.base import BaseTrigger
9
- from asyncz.utils import (
10
- datetime_repr,
11
- normalize,
12
- timedelta_seconds,
13
- to_datetime,
14
- to_timezone,
15
- )
9
+ from asyncz.utils import datetime_repr, normalize, timedelta_seconds, to_datetime, to_timezone
16
10
 
17
11
 
18
12
  class IntervalTrigger(BaseTrigger):
@@ -79,9 +73,7 @@ class IntervalTrigger(BaseTrigger):
79
73
  next_trigger_time = self.start_at
80
74
  else:
81
75
  time_difference_seconds = timedelta_seconds(now - self.start_at)
82
- next_interval_number = int(
83
- ceil(time_difference_seconds / self.interval_size)
84
- )
76
+ next_interval_number = int(ceil(time_difference_seconds / self.interval_size))
85
77
  next_trigger_time = self.start_at + self.interval * next_interval_number
86
78
 
87
79
  if self.jitter is not None:
@@ -234,10 +234,14 @@ def ref_to_obj(ref: str) -> Any:
234
234
  raise AsynczLookupError(
235
235
  "Error resolving reference %s: could not import module" % ref
236
236
  ) from None
237
-
238
237
  try:
239
238
  for name in rest.split("."):
240
239
  obj = getattr(obj, name)
240
+
241
+ # It might come from a class containing the fn attribute
242
+ # implementation of the __call__ method
243
+ if hasattr(obj, "fn"):
244
+ obj = obj.fn
241
245
  return obj
242
246
  except Exception:
243
247
  raise AsynczLookupError(
@@ -1 +0,0 @@
1
- __version__ = "0.8.1"
File without changes
@@ -1,40 +0,0 @@
1
- from datetime import datetime
2
- from typing import Any, Dict, Optional
3
-
4
- from asyncz.contrib.esmerald.scheduler import Task
5
- from asyncz.triggers.types import TriggerType
6
-
7
-
8
- class scheduler(Task):
9
- def __init__(
10
- self,
11
- *,
12
- name: Optional[str] = None,
13
- trigger: Optional[TriggerType] = None,
14
- id: Optional[str] = None,
15
- mistrigger_grace_time: Optional[int] = None,
16
- coalesce: Optional[bool] = None,
17
- max_instances: Optional[int] = None,
18
- next_run_time: Optional[datetime] = None,
19
- store: Optional[str] = "default",
20
- executor: Optional[str] = "default",
21
- replace_existing: bool = True,
22
- extra_args: Optional[Any] = None,
23
- extra_kwargs: Optional[Dict[str, Any]] = None,
24
- is_enabled: bool = True,
25
- ) -> None:
26
- super().__init__(
27
- name=name,
28
- trigger=trigger,
29
- id=id,
30
- mistrigger_grace_time=mistrigger_grace_time,
31
- coalesce=coalesce,
32
- max_instances=max_instances,
33
- next_run_time=next_run_time,
34
- store=store,
35
- executor=executor,
36
- replace_existing=replace_existing,
37
- args=extra_args,
38
- kwargs=extra_kwargs,
39
- is_enabled=is_enabled,
40
- )
@@ -1,217 +0,0 @@
1
- import warnings
2
- from datetime import datetime
3
- from datetime import timezone as dtimezone
4
- from typing import TYPE_CHECKING, Any, Dict, Optional, Union
5
-
6
- from asyncz.schedulers.types import SchedulerType
7
- from asyncz.triggers.types import TriggerType
8
- from asyncz.typing import undefined
9
-
10
- try:
11
- from esmerald.conf import settings
12
- from esmerald.exceptions import ImproperlyConfigured
13
- from esmerald.utils.module_loading import import_string
14
- except ImportError:
15
- raise ImportError(
16
- "Esmerald cannot be found. Please install it by running `pip install esmerald`."
17
- ) from None
18
-
19
- if TYPE_CHECKING:
20
- from esmerald.applications import Esmerald
21
-
22
-
23
- class EsmeraldScheduler:
24
- """
25
- Scheduler instance to be used by the Esmerald application instance.
26
-
27
- Args:
28
- app: Esmerald application
29
- scheduler_class: An instance of a SchedulerType
30
- tasks: A dictinary str, str mapping the tasks to be executed.
31
- timezone: The timezone instance.
32
- configurations: A dictionary with extra configurations to be passed to the scheduler.
33
- """
34
-
35
- def __init__(
36
- self,
37
- app: Optional["Esmerald"] = None,
38
- scheduler_class: Optional["SchedulerType"] = None,
39
- tasks: Optional[Dict[str, str]] = None,
40
- timezone: Optional[Union[dtimezone, str]] = None,
41
- configurations: Optional[Dict[str, Dict[str, str]]] = None,
42
- ) -> None:
43
- self.app = app
44
- self.tasks = tasks
45
- self.timezone = timezone
46
- self.scheduler_class = scheduler_class
47
- self.configurations = configurations
48
-
49
- if not self.scheduler_class and self.app.enable_scheduler:
50
- raise ImproperlyConfigured(
51
- "It cannot start the scheduler if there is no scheduler_class declared."
52
- )
53
-
54
- for task, module in self.tasks.items():
55
- if not isinstance(task, str) or not isinstance(module, str):
56
- raise ImproperlyConfigured("The dict of tasks must be Dict[str, str].")
57
-
58
- if not self.tasks:
59
- warnings.warn(
60
- "Esmerald is starting the scheduler, yet there are no tasks declared.",
61
- UserWarning,
62
- stacklevel=2,
63
- )
64
-
65
- # Load the scheduler object
66
- self.handler = self.get_scheduler(
67
- scheduler=self.scheduler_class,
68
- timezone=self.timezone,
69
- configurations=self.configurations,
70
- )
71
-
72
- self.register_events(app=self.app)
73
- self.register_tasks(tasks=self.tasks)
74
-
75
- def register_tasks(self, tasks: Dict[str, str]) -> None:
76
- """
77
- Registers the tasks in the Scheduler
78
- """
79
- for task, _module in tasks.items():
80
- imported_task = f"{_module}.{task}"
81
- scheduled_task: "Task" = import_string(imported_task)
82
-
83
- if not scheduled_task.is_enabled:
84
- continue
85
-
86
- try:
87
- scheduled_task.add_task(self.handler)
88
- except Exception as e:
89
- raise ImproperlyConfigured(str(e)) from e
90
-
91
- def register_events(self, app: "Esmerald") -> None:
92
- """
93
- Registers the scheduler events in the Esmerald application.
94
- """
95
- if app.on_startup is not None:
96
- app.on_startup.append(self.handler.start)
97
- else:
98
- app.on_startup = [self.handler.start]
99
-
100
- if app.on_shutdown is not None:
101
- app.on_shutdown.append(self.handler.shutdown)
102
- else:
103
- app.on_shutdown = [self.handler.shutdown]
104
-
105
- def get_scheduler(
106
- self,
107
- scheduler: "SchedulerType",
108
- timezone: Optional[dtimezone] = None,
109
- configurations: Optional[Dict[str, str]] = None,
110
- ) -> SchedulerType:
111
- """
112
- Initiates the scheduler from the given time.
113
- If no value is provided, it will default to AsyncIOScheduler.
114
-
115
- The value of `scheduler_class` can be overwritten by any esmerald custom settings.
116
-
117
- Args:
118
- scheduler: AsyncIOScheduler.
119
- timezone: The timezone instance.
120
- configurations: A dictionary with extra configurations to be passed to the scheduler.
121
-
122
- Return:
123
- SchedulerType: An instance of a Scheduler.
124
- """
125
- if not timezone:
126
- timezone = settings.timezone
127
-
128
- if not configurations:
129
- return scheduler(timezone=timezone)
130
-
131
- return scheduler(global_config=configurations, timezone=timezone)
132
-
133
-
134
- class Task:
135
- """
136
- Base for the scheduler decorator that will auto discover the
137
- tasks in the application and add them to the internal scheduler.
138
- """
139
-
140
- def __init__(
141
- self,
142
- *,
143
- name: Optional[str] = None,
144
- trigger: Optional[TriggerType] = None,
145
- id: Optional[str] = None,
146
- mistrigger_grace_time: Optional[int] = None,
147
- coalesce: Optional[bool] = None,
148
- max_instances: Optional[int] = None,
149
- next_run_time: Optional[datetime] = None,
150
- store: Optional[str] = "default",
151
- executor: Optional[str] = "default",
152
- replace_existing: bool = False,
153
- args: Optional[Any] = None,
154
- kwargs: Optional[Dict[str, Any]] = None,
155
- is_enabled: bool = True,
156
- ) -> None:
157
- """_summary_
158
-
159
- Args:
160
- name: Textual description of the task.
161
- trigger: An instance of a trigger class.
162
- identifier: Explicit identifier for the task.
163
- mistrigger_grace_time: Seconds after the designated runtime that the task is still allowed to be run (or None to allow the task to run no
164
- matter how late it is).
165
- coalesce: Run once instead of many times if the scheduler determines that the task should be run more than once in succession.
166
- max_instances: Maximum number of concurrently running instances allowed for this task.
167
- next_run_time: When to first run the task, regardless of the trigger (pass None to add the task as paused).
168
- store: Alias of the task store to store the task in.
169
- executor: Alias of the executor to run the task with.
170
- replace_existing: True to replace an existing task with the same id
171
- (but retain the number of runs from the existing one).
172
- args: List of positional arguments to call func with.
173
- kwargs: Dict of keyword arguments to call func with.
174
- is_enabled: True if the the task to be added to the scheduler.
175
- """
176
- self.name = name
177
- self.trigger = trigger
178
- self.id = id
179
- self.mistrigger_grace_time = mistrigger_grace_time or undefined
180
- self.coalesce = coalesce or undefined
181
- self.max_instances = max_instances or undefined
182
- self.next_run_time = next_run_time or undefined
183
- self.store = store
184
- self.executor = executor
185
- self.replace_existing = replace_existing
186
- self.args = args
187
- self.kwargs = kwargs
188
- self.is_enabled = is_enabled
189
- self.fn = None
190
-
191
- def add_task(self, scheduler: "SchedulerType"):
192
- try:
193
- scheduler.add_task(
194
- fn=self.fn,
195
- trigger=self.trigger,
196
- args=self.args,
197
- kwargs=self.kwargs,
198
- id=self.id,
199
- name=self.name,
200
- mistrigger_grace_time=self.mistrigger_grace_time,
201
- coalesce=self.coalesce,
202
- max_instances=self.max_instances,
203
- next_run_time=self.next_run_time,
204
- store=self.store,
205
- executor=self.executor,
206
- replace_existing=self.replace_existing,
207
- )
208
- except Exception as e:
209
- raise ImproperlyConfigured(str(e)) from e
210
-
211
- def __call__(self, fn: Any) -> None:
212
- """
213
- Tricking the object into think it's being instantiated by in reality
214
- is just returning itself.
215
- """
216
- self.fn = fn
217
- return self
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