EAScheduler 0.2.0__tar.gz → 0.2.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.
- {eascheduler-0.2.0/src/EAScheduler.egg-info → eascheduler-0.2.2}/PKG-INFO +20 -4
- {eascheduler-0.2.0 → eascheduler-0.2.2}/requirements_setup.txt +2 -2
- {eascheduler-0.2.0 → eascheduler-0.2.2/src/EAScheduler.egg-info}/PKG-INFO +20 -4
- {eascheduler-0.2.0 → eascheduler-0.2.2}/src/EAScheduler.egg-info/requires.txt +2 -2
- eascheduler-0.2.2/src/eascheduler/__version__.py +1 -0
- {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/builder/filters.py +1 -1
- {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/builder/helper.py +6 -4
- {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/builder/jobs.py +7 -2
- {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/builder/triggers.py +15 -13
- {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/helpers/time_replace.py +10 -1
- {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/producers/__init__.py +2 -2
- {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/producers/base.py +27 -4
- {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/producers/prod_filter.py +22 -0
- {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/producers/prod_filter_holiday.py +5 -1
- {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/producers/prod_group.py +7 -0
- {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/producers/prod_interval.py +6 -1
- {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/producers/prod_operation.py +23 -3
- {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/producers/prod_sun.py +33 -4
- {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/producers/prod_time.py +6 -1
- eascheduler-0.2.0/src/eascheduler/__version__.py +0 -1
- {eascheduler-0.2.0 → eascheduler-0.2.2}/LICENSE +0 -0
- {eascheduler-0.2.0 → eascheduler-0.2.2}/MANIFEST.in +0 -0
- {eascheduler-0.2.0 → eascheduler-0.2.2}/setup.cfg +0 -0
- {eascheduler-0.2.0 → eascheduler-0.2.2}/setup.py +0 -0
- {eascheduler-0.2.0 → eascheduler-0.2.2}/src/EAScheduler.egg-info/SOURCES.txt +0 -0
- {eascheduler-0.2.0 → eascheduler-0.2.2}/src/EAScheduler.egg-info/dependency_links.txt +0 -0
- {eascheduler-0.2.0 → eascheduler-0.2.2}/src/EAScheduler.egg-info/top_level.txt +0 -0
- {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/__init__.py +0 -0
- {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/builder/__init__.py +0 -0
- {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/builder/utils.py +0 -0
- {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/const.py +0 -0
- {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/default.py +0 -0
- {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/errors/__init__.py +0 -0
- {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/errors/errors.py +0 -0
- {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/errors/handler.py +0 -0
- {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/executor/__init__.py +0 -0
- {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/executor/base.py +0 -0
- {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/helpers/__init__.py +0 -0
- {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/helpers/dst_param.py +0 -0
- {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/helpers/helpers.py +0 -0
- {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/job_control/__init__.py +0 -0
- {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/job_control/base.py +0 -0
- {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/job_control/job_countdown.py +0 -0
- {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/job_control/job_datetime.py +0 -0
- {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/job_control/job_onetime.py +0 -0
- {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/job_stores/__init__.py +0 -0
- {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/job_stores/base.py +0 -0
- {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/job_stores/memory.py +0 -0
- {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/jobs/__init__.py +0 -0
- {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/jobs/base.py +0 -0
- {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/jobs/event_handler.py +0 -0
- {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/jobs/job_countdown.py +0 -0
- {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/jobs/job_datetime.py +0 -0
- {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/jobs/job_onetime.py +0 -0
- {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/py.typed +0 -0
- {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/schedulers/__init__.py +0 -0
- {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/schedulers/async_scheduler.py +0 -0
- {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/schedulers/base.py +0 -0
- {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/task_managers/__init__.py +0 -0
- {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/task_managers/base.py +0 -0
- {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/task_managers/parallel.py +0 -0
- {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/task_managers/sequential.py +0 -0
- {eascheduler-0.2.0 → eascheduler-0.2.2}/tests/test_const.py +0 -0
- {eascheduler-0.2.0 → eascheduler-0.2.2}/tests/test_default.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
2
|
Name: EAScheduler
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: Easy async scheduling with a nice interface
|
|
5
5
|
Home-page: https://github.com/spacemanspiff2007/eascheduler
|
|
6
6
|
Author: spaceman_spiff
|
|
@@ -19,10 +19,20 @@ Classifier: Programming Language :: Python :: 3 :: Only
|
|
|
19
19
|
Requires-Python: >=3.10
|
|
20
20
|
Description-Content-Type: text/markdown
|
|
21
21
|
License-File: LICENSE
|
|
22
|
-
Requires-Dist: whenever==0.6.
|
|
22
|
+
Requires-Dist: whenever==0.6.16
|
|
23
23
|
Requires-Dist: astral==3.2
|
|
24
|
-
Requires-Dist: holidays==0.
|
|
24
|
+
Requires-Dist: holidays==0.64
|
|
25
25
|
Requires-Dist: typing_extensions==4.12.2
|
|
26
|
+
Dynamic: author
|
|
27
|
+
Dynamic: classifier
|
|
28
|
+
Dynamic: description
|
|
29
|
+
Dynamic: description-content-type
|
|
30
|
+
Dynamic: home-page
|
|
31
|
+
Dynamic: keywords
|
|
32
|
+
Dynamic: project-url
|
|
33
|
+
Dynamic: requires-dist
|
|
34
|
+
Dynamic: requires-python
|
|
35
|
+
Dynamic: summary
|
|
26
36
|
|
|
27
37
|
# EAScheduler
|
|
28
38
|
[](https://github.com/spacemanspiff2007/eascheduler/actions)
|
|
@@ -126,6 +136,12 @@ scheduler.at(
|
|
|
126
136
|
````
|
|
127
137
|
|
|
128
138
|
## Changelog
|
|
139
|
+
#### 0.2.2 (2025-01-07)
|
|
140
|
+
- Ensured that all trigger builders are imputable and can be composed without side effects
|
|
141
|
+
- Updated dependencies
|
|
142
|
+
|
|
143
|
+
#### 0.2.1 (2024-11-20)
|
|
144
|
+
- Updated dependencies and some type hints
|
|
129
145
|
|
|
130
146
|
#### 0.2.0 (2024-11-13)
|
|
131
147
|
- Complete rewrite
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
2
|
Name: EAScheduler
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: Easy async scheduling with a nice interface
|
|
5
5
|
Home-page: https://github.com/spacemanspiff2007/eascheduler
|
|
6
6
|
Author: spaceman_spiff
|
|
@@ -19,10 +19,20 @@ Classifier: Programming Language :: Python :: 3 :: Only
|
|
|
19
19
|
Requires-Python: >=3.10
|
|
20
20
|
Description-Content-Type: text/markdown
|
|
21
21
|
License-File: LICENSE
|
|
22
|
-
Requires-Dist: whenever==0.6.
|
|
22
|
+
Requires-Dist: whenever==0.6.16
|
|
23
23
|
Requires-Dist: astral==3.2
|
|
24
|
-
Requires-Dist: holidays==0.
|
|
24
|
+
Requires-Dist: holidays==0.64
|
|
25
25
|
Requires-Dist: typing_extensions==4.12.2
|
|
26
|
+
Dynamic: author
|
|
27
|
+
Dynamic: classifier
|
|
28
|
+
Dynamic: description
|
|
29
|
+
Dynamic: description-content-type
|
|
30
|
+
Dynamic: home-page
|
|
31
|
+
Dynamic: keywords
|
|
32
|
+
Dynamic: project-url
|
|
33
|
+
Dynamic: requires-dist
|
|
34
|
+
Dynamic: requires-python
|
|
35
|
+
Dynamic: summary
|
|
26
36
|
|
|
27
37
|
# EAScheduler
|
|
28
38
|
[](https://github.com/spacemanspiff2007/eascheduler/actions)
|
|
@@ -126,6 +136,12 @@ scheduler.at(
|
|
|
126
136
|
````
|
|
127
137
|
|
|
128
138
|
## Changelog
|
|
139
|
+
#### 0.2.2 (2025-01-07)
|
|
140
|
+
- Ensured that all trigger builders are imputable and can be composed without side effects
|
|
141
|
+
- Updated dependencies
|
|
142
|
+
|
|
143
|
+
#### 0.2.1 (2024-11-20)
|
|
144
|
+
- Updated dependencies and some type hints
|
|
129
145
|
|
|
130
146
|
#### 0.2.0 (2024-11-13)
|
|
131
147
|
- Complete rewrite
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = '0.2.2'
|
|
@@ -35,7 +35,7 @@ class FilterObject:
|
|
|
35
35
|
self._filter: Final = filter
|
|
36
36
|
|
|
37
37
|
|
|
38
|
-
_get_producer_filter = BuilderTypeValidator(FilterObject, ProducerFilterBase, '_filter')
|
|
38
|
+
_get_producer_filter: Final = BuilderTypeValidator(FilterObject, ProducerFilterBase, '_filter')
|
|
39
39
|
|
|
40
40
|
|
|
41
41
|
# noinspection PyProtectedMember,PyShadowingBuiltins
|
|
@@ -15,6 +15,7 @@ from eascheduler.helpers import HINT_CLOCK_BACKWARD, HINT_CLOCK_FORWARD, TimeRep
|
|
|
15
15
|
|
|
16
16
|
HINT_TIME: TypeAlias = dt_time | Time | str
|
|
17
17
|
HINT_TIMEDELTA: TypeAlias = dt_timedelta | TimeDelta | int | float | str
|
|
18
|
+
HINT_POS_TIMEDELTA: TypeAlias = HINT_TIMEDELTA
|
|
18
19
|
HINT_INSTANT: TypeAlias = dt_datetime | None | str | HINT_TIME | HINT_TIMEDELTA | SystemDateTime
|
|
19
20
|
HINT_NAME_OR_NR: TypeAlias = int | str | Iterable[int | str]
|
|
20
21
|
HINT_DATE: TypeAlias = dt_date | dt_datetime | str | None | Date | SystemDateTime | Instant
|
|
@@ -34,7 +35,7 @@ def get_timedelta(value: HINT_TIMEDELTA) -> TimeDelta:
|
|
|
34
35
|
raise TypeError()
|
|
35
36
|
|
|
36
37
|
|
|
37
|
-
def get_pos_timedelta_secs(value:
|
|
38
|
+
def get_pos_timedelta_secs(value: HINT_POS_TIMEDELTA) -> float:
|
|
38
39
|
if (value := get_timedelta(value).in_seconds()) <= 0:
|
|
39
40
|
msg = 'Value must be positive.'
|
|
40
41
|
raise ValueError(msg)
|
|
@@ -102,7 +103,7 @@ def get_instant(value: HINT_INSTANT) -> Instant:
|
|
|
102
103
|
# timedelta
|
|
103
104
|
try:
|
|
104
105
|
timedelta = get_timedelta(value)
|
|
105
|
-
except ValueError:
|
|
106
|
+
except (ValueError, TypeError):
|
|
106
107
|
pass
|
|
107
108
|
else:
|
|
108
109
|
return Instant.now() + timedelta
|
|
@@ -111,7 +112,7 @@ def get_instant(value: HINT_INSTANT) -> Instant:
|
|
|
111
112
|
# time
|
|
112
113
|
try:
|
|
113
114
|
time = get_time(value)
|
|
114
|
-
except ValueError:
|
|
115
|
+
except (ValueError, TypeError):
|
|
115
116
|
pass
|
|
116
117
|
else:
|
|
117
118
|
now = SystemDateTime.now()
|
|
@@ -227,7 +228,8 @@ class BuilderTypeValidator(Generic[TYPE_OUT]):
|
|
|
227
228
|
msg = f'Expected an instance of {self.type_out.__name__}, got {obj} ({type(obj).__name__})'
|
|
228
229
|
raise TypeError(msg)
|
|
229
230
|
|
|
230
|
-
return
|
|
231
|
+
# We return a copy so we are sure that there are no side effects
|
|
232
|
+
return obj.copy()
|
|
231
233
|
|
|
232
234
|
def __repr__(self) -> str:
|
|
233
235
|
return f'{self.__class__.__name__}({self.type_in.__name__}, {self.type_out.__name__}, {self.var_name})'
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
from collections.abc import Awaitable, Callable
|
|
2
2
|
from typing import Any, Final
|
|
3
3
|
|
|
4
|
-
from eascheduler.builder.helper import
|
|
4
|
+
from eascheduler.builder.helper import (
|
|
5
|
+
HINT_INSTANT,
|
|
6
|
+
HINT_POS_TIMEDELTA,
|
|
7
|
+
get_instant,
|
|
8
|
+
get_pos_timedelta_secs,
|
|
9
|
+
)
|
|
5
10
|
from eascheduler.builder.triggers import TriggerObject, _get_producer
|
|
6
11
|
from eascheduler.executor import ExecutorBase
|
|
7
12
|
from eascheduler.job_control import CountdownJobControl, DateTimeJobControl, OneTimeJobControl
|
|
@@ -20,7 +25,7 @@ class JobBuilder:
|
|
|
20
25
|
self._executor: Final = executor
|
|
21
26
|
self._job_store: Final = job_store
|
|
22
27
|
|
|
23
|
-
def countdown(self, secs:
|
|
28
|
+
def countdown(self, secs: HINT_POS_TIMEDELTA, coro_func: Callable[..., Awaitable[Any]],
|
|
24
29
|
*args: Any, job_id: IdType | None = None, **kwargs: Any) -> CountdownJobControl:
|
|
25
30
|
"""Create a job that count town a certain time and then execute.
|
|
26
31
|
|
|
@@ -4,10 +4,12 @@ from typing import TYPE_CHECKING, Final, Literal
|
|
|
4
4
|
|
|
5
5
|
from typing_extensions import Self
|
|
6
6
|
|
|
7
|
+
from eascheduler.builder.filters import _get_producer_filter
|
|
7
8
|
from eascheduler.builder.helper import (
|
|
8
9
|
HINT_CLOCK_BACKWARD,
|
|
9
10
|
HINT_CLOCK_FORWARD,
|
|
10
11
|
HINT_INSTANT,
|
|
12
|
+
HINT_POS_TIMEDELTA,
|
|
11
13
|
HINT_TIME,
|
|
12
14
|
HINT_TIMEDELTA,
|
|
13
15
|
BuilderTypeValidator,
|
|
@@ -26,8 +28,8 @@ from eascheduler.producers import (
|
|
|
26
28
|
LatestProducerOperation,
|
|
27
29
|
NoonProducer,
|
|
28
30
|
OffsetProducerOperation,
|
|
29
|
-
|
|
30
|
-
|
|
31
|
+
SunAzimuthProducerCompare,
|
|
32
|
+
SunElevationProducerCompare,
|
|
31
33
|
SunriseProducer,
|
|
32
34
|
SunsetProducer,
|
|
33
35
|
TimeProducer,
|
|
@@ -50,7 +52,7 @@ class TriggerObject:
|
|
|
50
52
|
:param offset: The offset (positive or negative)
|
|
51
53
|
"""
|
|
52
54
|
return self.__class__(
|
|
53
|
-
OffsetProducerOperation(self
|
|
55
|
+
OffsetProducerOperation(_get_producer(self), get_timedelta(offset).in_seconds())
|
|
54
56
|
)
|
|
55
57
|
|
|
56
58
|
def earliest(self, earliest: HINT_TIME, *,
|
|
@@ -64,7 +66,7 @@ class TriggerObject:
|
|
|
64
66
|
"""
|
|
65
67
|
return self.__class__(
|
|
66
68
|
EarliestProducerOperation(
|
|
67
|
-
self
|
|
69
|
+
_get_producer(self),
|
|
68
70
|
get_time_replacer(earliest, clock_forward=clock_forward, clock_backward=clock_backward)
|
|
69
71
|
)
|
|
70
72
|
)
|
|
@@ -80,12 +82,12 @@ class TriggerObject:
|
|
|
80
82
|
"""
|
|
81
83
|
return self.__class__(
|
|
82
84
|
LatestProducerOperation(
|
|
83
|
-
self
|
|
85
|
+
_get_producer(self),
|
|
84
86
|
get_time_replacer(latest, clock_forward=clock_forward, clock_backward=clock_backward)
|
|
85
87
|
)
|
|
86
88
|
)
|
|
87
89
|
|
|
88
|
-
def jitter(self, low:
|
|
90
|
+
def jitter(self, low: HINT_POS_TIMEDELTA, high: HINT_POS_TIMEDELTA | None = None) -> Self:
|
|
89
91
|
"""Add jitter to the time returned by the trigger.
|
|
90
92
|
|
|
91
93
|
:param low: The lower bound of the jitter
|
|
@@ -93,8 +95,8 @@ class TriggerObject:
|
|
|
93
95
|
"""
|
|
94
96
|
return self.__class__(
|
|
95
97
|
JitterProducerOperation(
|
|
96
|
-
self
|
|
97
|
-
|
|
98
|
+
_get_producer(self),
|
|
99
|
+
get_timedelta(low).in_seconds(), get_timedelta(high).in_seconds() if high is not None else None
|
|
98
100
|
)
|
|
99
101
|
)
|
|
100
102
|
|
|
@@ -105,7 +107,7 @@ class TriggerObject:
|
|
|
105
107
|
"""
|
|
106
108
|
if self._producer._filter is not None:
|
|
107
109
|
raise ValueError()
|
|
108
|
-
self._producer._filter = filter
|
|
110
|
+
self._producer._filter = _get_producer_filter(filter)
|
|
109
111
|
return self
|
|
110
112
|
|
|
111
113
|
only_at = only_on
|
|
@@ -145,7 +147,7 @@ class TriggerBuilder:
|
|
|
145
147
|
:param elevation: Sun elevation in degrees
|
|
146
148
|
:param direction: rising or falling
|
|
147
149
|
"""
|
|
148
|
-
return TriggerObject(
|
|
150
|
+
return TriggerObject(SunElevationProducerCompare(elevation, direction))
|
|
149
151
|
|
|
150
152
|
@staticmethod
|
|
151
153
|
def sun_azimuth(azimuth: float) -> TriggerObject:
|
|
@@ -153,7 +155,7 @@ class TriggerBuilder:
|
|
|
153
155
|
|
|
154
156
|
:param azimuth: Sun azimuth in degrees
|
|
155
157
|
"""
|
|
156
|
-
return TriggerObject(
|
|
158
|
+
return TriggerObject(SunAzimuthProducerCompare(azimuth))
|
|
157
159
|
|
|
158
160
|
@staticmethod
|
|
159
161
|
def group(*builders: TriggerObject) -> TriggerObject:
|
|
@@ -165,7 +167,7 @@ class TriggerBuilder:
|
|
|
165
167
|
return TriggerObject(GroupProducer([_get_producer(b) for b in builders]))
|
|
166
168
|
|
|
167
169
|
@staticmethod
|
|
168
|
-
def interval(start: HINT_INSTANT, interval:
|
|
170
|
+
def interval(start: HINT_INSTANT, interval: HINT_POS_TIMEDELTA) -> TriggerObject:
|
|
169
171
|
"""Triggers at a fixed interval from a given start time.
|
|
170
172
|
|
|
171
173
|
:param start: When this trigger will run for the first time.
|
|
@@ -199,4 +201,4 @@ class TriggerBuilder:
|
|
|
199
201
|
)
|
|
200
202
|
|
|
201
203
|
|
|
202
|
-
_get_producer = BuilderTypeValidator(TriggerObject, DateTimeProducerBase, '_producer')
|
|
204
|
+
_get_producer: Final = BuilderTypeValidator(TriggerObject, DateTimeProducerBase, '_producer')
|
|
@@ -3,6 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
from enum import Enum
|
|
4
4
|
from typing import TYPE_CHECKING, Final, Literal, TypeAlias
|
|
5
5
|
|
|
6
|
+
from typing_extensions import Self
|
|
6
7
|
from whenever import Date, LocalDateTime, RepeatedTime, SkippedTime, SystemDateTime, Time
|
|
7
8
|
|
|
8
9
|
from eascheduler.helpers.helpers import to_enum
|
|
@@ -60,7 +61,7 @@ class TimeTwiceError(Exception):
|
|
|
60
61
|
|
|
61
62
|
|
|
62
63
|
class TimeReplacer:
|
|
63
|
-
__slots__ = ('
|
|
64
|
+
__slots__ = ('_repeated', '_skipped', '_time')
|
|
64
65
|
|
|
65
66
|
def __init__(self, time: Time, if_skipped: HINT_SKIPPED, if_repeated: HINT_REPEATED) -> None:
|
|
66
67
|
super().__init__()
|
|
@@ -68,10 +69,18 @@ class TimeReplacer:
|
|
|
68
69
|
self._skipped: Final[SkippedTimeBehavior] = to_enum(SkippedTimeBehavior, if_skipped)
|
|
69
70
|
self._repeated: Final[RepeatedTimeBehavior] = to_enum(RepeatedTimeBehavior, if_repeated)
|
|
70
71
|
|
|
72
|
+
def __eq__(self, other: object) -> bool:
|
|
73
|
+
if not isinstance(other, TimeReplacer):
|
|
74
|
+
return NotImplemented
|
|
75
|
+
return self._time == other._time and self._skipped == other._skipped and self._repeated == other._repeated
|
|
76
|
+
|
|
71
77
|
def __repr__(self) -> str:
|
|
72
78
|
return (f'<{self.__class__.__name__} {self._time!s}'
|
|
73
79
|
f' if_skipped={self._skipped.value:s} if_repeated={self._repeated.value:s}>')
|
|
74
80
|
|
|
81
|
+
def copy(self) -> Self:
|
|
82
|
+
return self.__class__(self._time, self._skipped, self._repeated)
|
|
83
|
+
|
|
75
84
|
def replace(self, dt: SystemDateTime | Date) -> SystemDateTime: # noqa: C901
|
|
76
85
|
|
|
77
86
|
kwargs = {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from typing import TYPE_CHECKING, NoReturn
|
|
3
|
+
from typing import TYPE_CHECKING, NoReturn, TypeVar
|
|
4
4
|
|
|
5
|
-
from typing_extensions import override
|
|
5
|
+
from typing_extensions import Self, override
|
|
6
6
|
|
|
7
7
|
from eascheduler.errors import InfiniteLoopDetectedError
|
|
8
8
|
|
|
@@ -13,14 +13,30 @@ if TYPE_CHECKING:
|
|
|
13
13
|
from whenever import Instant, SystemDateTime
|
|
14
14
|
|
|
15
15
|
|
|
16
|
-
class
|
|
16
|
+
class CompareEqualityBySlotValues:
|
|
17
|
+
__slots__ = ()
|
|
18
|
+
|
|
19
|
+
# Since all classes use slots we can compare them by comparing their slot values
|
|
20
|
+
def __eq__(self, other: object) -> bool:
|
|
21
|
+
if not isinstance(other, self.__class__):
|
|
22
|
+
raise TypeError()
|
|
23
|
+
return all(getattr(self, s) == getattr(other, s) for s in self.__slots__)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class ProducerFilterBase(CompareEqualityBySlotValues):
|
|
17
27
|
__slots__ = ()
|
|
18
28
|
|
|
19
29
|
def allow(self, dt: SystemDateTime) -> bool:
|
|
20
30
|
raise NotImplementedError()
|
|
21
31
|
|
|
32
|
+
def copy(self) -> Self:
|
|
33
|
+
raise NotImplementedError()
|
|
34
|
+
|
|
22
35
|
|
|
23
|
-
|
|
36
|
+
DTPB_TYPE = TypeVar('DTPB_TYPE', bound='DateTimeProducerBase')
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class DateTimeProducerBase(CompareEqualityBySlotValues):
|
|
24
40
|
__slots__ = ('_filter', )
|
|
25
41
|
|
|
26
42
|
def __init__(self) -> None:
|
|
@@ -31,6 +47,13 @@ class DateTimeProducerBase:
|
|
|
31
47
|
Has to guarantee that the returned date time is after the given date time."""
|
|
32
48
|
raise NotImplementedError()
|
|
33
49
|
|
|
50
|
+
def _copy_filter(self, obj: DTPB_TYPE) -> DTPB_TYPE:
|
|
51
|
+
obj._filter = self._filter.copy() if self._filter is not None else None
|
|
52
|
+
return obj
|
|
53
|
+
|
|
54
|
+
def copy(self) -> Self:
|
|
55
|
+
raise NotImplementedError()
|
|
56
|
+
|
|
34
57
|
|
|
35
58
|
class DateTimeProducerOperationBase(DateTimeProducerBase):
|
|
36
59
|
__slots__ = ('_producer', )
|
|
@@ -24,6 +24,9 @@ class ProducerFilterGroupBase(ProducerFilterBase):
|
|
|
24
24
|
self._filters = (*self._filters, filter)
|
|
25
25
|
return self
|
|
26
26
|
|
|
27
|
+
def copy(self) -> Self:
|
|
28
|
+
return self.__class__(f.copy() for f in self._filters)
|
|
29
|
+
|
|
27
30
|
|
|
28
31
|
class AnyGroupProducerFilter(ProducerFilterGroupBase):
|
|
29
32
|
@override
|
|
@@ -44,6 +47,10 @@ class InvertingProducerFilter(ProducerFilterBase):
|
|
|
44
47
|
def __init__(self, filter: ProducerFilterBase) -> None: # noqa: A002
|
|
45
48
|
self._filter: ProducerFilterBase = filter
|
|
46
49
|
|
|
50
|
+
@override
|
|
51
|
+
def copy(self) -> Self:
|
|
52
|
+
return self.__class__(self._filter.copy())
|
|
53
|
+
|
|
47
54
|
@override
|
|
48
55
|
def allow(self, dt: SystemDateTime) -> bool:
|
|
49
56
|
return not self._filter.allow(dt)
|
|
@@ -57,6 +64,10 @@ class TimeProducerFilter(ProducerFilterBase):
|
|
|
57
64
|
self._lower: Final = lower
|
|
58
65
|
self._upper: Final = upper
|
|
59
66
|
|
|
67
|
+
@override
|
|
68
|
+
def copy(self) -> Self:
|
|
69
|
+
return self.__class__(self._lower, self._upper)
|
|
70
|
+
|
|
60
71
|
@override
|
|
61
72
|
def allow(self, dt: SystemDateTime) -> bool:
|
|
62
73
|
|
|
@@ -77,6 +88,10 @@ class DayOfWeekProducerFilter(ProducerFilterBase):
|
|
|
77
88
|
super().__init__()
|
|
78
89
|
self._weekdays: Final = frozenset(weekdays)
|
|
79
90
|
|
|
91
|
+
@override
|
|
92
|
+
def copy(self) -> Self:
|
|
93
|
+
return self.__class__(self._weekdays)
|
|
94
|
+
|
|
80
95
|
@override
|
|
81
96
|
def allow(self, dt: SystemDateTime) -> bool:
|
|
82
97
|
return dt.py_datetime().isoweekday() in self._weekdays
|
|
@@ -89,6 +104,10 @@ class DayOfMonthProducerFilter(ProducerFilterBase):
|
|
|
89
104
|
super().__init__()
|
|
90
105
|
self._days: Final = frozenset(days)
|
|
91
106
|
|
|
107
|
+
@override
|
|
108
|
+
def copy(self) -> DayOfMonthProducerFilter:
|
|
109
|
+
return self.__class__(self._days)
|
|
110
|
+
|
|
92
111
|
@override
|
|
93
112
|
def allow(self, dt: SystemDateTime) -> bool:
|
|
94
113
|
return dt.day in self._days
|
|
@@ -101,6 +120,9 @@ class MonthOfYearProducerFilter(ProducerFilterBase):
|
|
|
101
120
|
super().__init__()
|
|
102
121
|
self._months: Final = frozenset(months)
|
|
103
122
|
|
|
123
|
+
def copy(self) -> Self:
|
|
124
|
+
return self.__class__(self._months)
|
|
125
|
+
|
|
104
126
|
@override
|
|
105
127
|
def allow(self, dt: SystemDateTime) -> bool:
|
|
106
128
|
return dt.month in self._months
|
|
@@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Any, Final
|
|
|
4
4
|
|
|
5
5
|
from holidays import HolidayBase as _HolidayBase
|
|
6
6
|
from holidays import country_holidays
|
|
7
|
-
from typing_extensions import override
|
|
7
|
+
from typing_extensions import Self, override
|
|
8
8
|
|
|
9
9
|
from eascheduler.errors.errors import HolidaysNotSetUpError
|
|
10
10
|
from eascheduler.producers.base import ProducerFilterBase
|
|
@@ -24,6 +24,10 @@ class HolidayProducerFilterBase(ProducerFilterBase):
|
|
|
24
24
|
super().__init__()
|
|
25
25
|
self._holidays: Final = _get_holiday_obj(holidays)
|
|
26
26
|
|
|
27
|
+
@override
|
|
28
|
+
def copy(self) -> Self:
|
|
29
|
+
return self.__class__(self._holidays)
|
|
30
|
+
|
|
27
31
|
|
|
28
32
|
class HolidayProducerFilter(HolidayProducerFilterBase):
|
|
29
33
|
@override
|
|
@@ -2,6 +2,8 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from typing import TYPE_CHECKING, Final
|
|
4
4
|
|
|
5
|
+
from typing_extensions import Self, override
|
|
6
|
+
|
|
5
7
|
from .base import DateTimeProducerBase, not_infinite_loop
|
|
6
8
|
|
|
7
9
|
|
|
@@ -18,6 +20,11 @@ class GroupProducer(DateTimeProducerBase):
|
|
|
18
20
|
super().__init__()
|
|
19
21
|
self._producers: Final = tuple(producers)
|
|
20
22
|
|
|
23
|
+
@override
|
|
24
|
+
def copy(self) -> Self:
|
|
25
|
+
cls = self.__class__(p.copy() for p in self._producers)
|
|
26
|
+
return self._copy_filter(cls)
|
|
27
|
+
|
|
21
28
|
def get_next(self, dt: Instant) -> Instant: # type: ignore[return]
|
|
22
29
|
|
|
23
30
|
next_dt = dt
|
|
@@ -2,7 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from typing import TYPE_CHECKING, Final
|
|
4
4
|
|
|
5
|
-
from typing_extensions import override
|
|
5
|
+
from typing_extensions import Self, override
|
|
6
6
|
|
|
7
7
|
from .base import DateTimeProducerBase
|
|
8
8
|
|
|
@@ -20,6 +20,11 @@ class IntervalProducer(DateTimeProducerBase):
|
|
|
20
20
|
self._next: Instant | None = start
|
|
21
21
|
self._interval: Final = interval
|
|
22
22
|
|
|
23
|
+
@override
|
|
24
|
+
def copy(self) -> Self:
|
|
25
|
+
cls = self.__class__(start=self._next, interval=self._interval)
|
|
26
|
+
return self._copy_filter(cls)
|
|
27
|
+
|
|
23
28
|
@override
|
|
24
29
|
def get_next(self, dt: Instant) -> Instant:
|
|
25
30
|
interval = self._interval
|
|
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
from random import uniform
|
|
4
4
|
from typing import TYPE_CHECKING, Final
|
|
5
5
|
|
|
6
|
-
from typing_extensions import override
|
|
6
|
+
from typing_extensions import Self, override
|
|
7
7
|
from whenever import LocalDateTime, SkippedTime, SystemDateTime, Time, TimeDelta
|
|
8
8
|
|
|
9
9
|
from eascheduler.helpers import TimeReplacer, TimeSkippedError, TimeTwiceError
|
|
@@ -34,6 +34,11 @@ class OffsetProducerOperation(DateTimeProducerOperationBase):
|
|
|
34
34
|
super().__init__(producer)
|
|
35
35
|
self.offset: Final = offset
|
|
36
36
|
|
|
37
|
+
@override
|
|
38
|
+
def copy(self) -> Self:
|
|
39
|
+
cls = self.__class__(self._producer.copy(), self.offset)
|
|
40
|
+
return self._copy_filter(cls)
|
|
41
|
+
|
|
37
42
|
@override
|
|
38
43
|
def apply_operation(self, next_dt: Instant, dt: Instant) -> Instant:
|
|
39
44
|
return next_dt.add(seconds=self.offset)
|
|
@@ -46,6 +51,11 @@ class EarliestProducerOperation(DateTimeProducerOperationBase):
|
|
|
46
51
|
super().__init__(producer)
|
|
47
52
|
self.earliest: Final = earliest
|
|
48
53
|
|
|
54
|
+
@override
|
|
55
|
+
def copy(self) -> Self:
|
|
56
|
+
cls = self.__class__(self._producer.copy(), self.earliest.copy())
|
|
57
|
+
return self._copy_filter(cls)
|
|
58
|
+
|
|
49
59
|
@override
|
|
50
60
|
def apply_operation(self, next_dt: Instant, dt: Instant) -> Instant:
|
|
51
61
|
try:
|
|
@@ -69,6 +79,11 @@ class LatestProducerOperation(DateTimeProducerOperationBase):
|
|
|
69
79
|
super().__init__(producer)
|
|
70
80
|
self.latest: Final = earliest
|
|
71
81
|
|
|
82
|
+
@override
|
|
83
|
+
def copy(self) -> Self:
|
|
84
|
+
cls = self.__class__(self._producer.copy(), self.latest.copy())
|
|
85
|
+
return self._copy_filter(cls)
|
|
86
|
+
|
|
72
87
|
@override
|
|
73
88
|
def apply_operation(self, next_dt: Instant, dt: Instant) -> Instant:
|
|
74
89
|
try:
|
|
@@ -86,7 +101,7 @@ class LatestProducerOperation(DateTimeProducerOperationBase):
|
|
|
86
101
|
|
|
87
102
|
|
|
88
103
|
class JitterProducerOperation(DateTimeProducerOperationBase):
|
|
89
|
-
__slots__ = ('
|
|
104
|
+
__slots__ = ('high', 'low')
|
|
90
105
|
|
|
91
106
|
def __init__(self, producer: DateTimeProducerBase, low: float, high: float | None = None) -> None:
|
|
92
107
|
super().__init__(producer)
|
|
@@ -100,12 +115,17 @@ class JitterProducerOperation(DateTimeProducerOperationBase):
|
|
|
100
115
|
self.low: Final = low
|
|
101
116
|
self.high: Final = high
|
|
102
117
|
|
|
118
|
+
@override
|
|
119
|
+
def copy(self) -> Self:
|
|
120
|
+
cls = self.__class__(self._producer.copy(), self.low, self.high)
|
|
121
|
+
return self._copy_filter(cls)
|
|
122
|
+
|
|
103
123
|
@override
|
|
104
124
|
def apply_operation(self, next_dt: Instant, dt: Instant) -> Instant:
|
|
105
125
|
if (low := self.low) >= 0:
|
|
106
126
|
return next_dt.add(seconds=uniform(low, self.high))
|
|
107
127
|
|
|
108
|
-
#
|
|
128
|
+
# Check if we can use the whole interval
|
|
109
129
|
lowest = (dt - next_dt).in_seconds()
|
|
110
130
|
if lowest < low:
|
|
111
131
|
return next_dt.add(seconds=uniform(low, self.high))
|
|
@@ -7,7 +7,7 @@ from datetime import timezone as dt_timezone
|
|
|
7
7
|
from typing import TYPE_CHECKING, Final, Literal
|
|
8
8
|
|
|
9
9
|
from astral import Observer, SunDirection, sun
|
|
10
|
-
from typing_extensions import override
|
|
10
|
+
from typing_extensions import Self, override
|
|
11
11
|
from whenever import Instant
|
|
12
12
|
|
|
13
13
|
from eascheduler.errors.errors import LocationNotSetError
|
|
@@ -66,6 +66,11 @@ class SunProducer(DateTimeProducerBase):
|
|
|
66
66
|
|
|
67
67
|
self.func: Final = func
|
|
68
68
|
|
|
69
|
+
@override
|
|
70
|
+
def copy(self) -> Self:
|
|
71
|
+
cls = self.__class__()
|
|
72
|
+
return self._copy_filter(cls)
|
|
73
|
+
|
|
69
74
|
def _cache_key(self) -> tuple[Hashable, ...]:
|
|
70
75
|
return (self.__class__, )
|
|
71
76
|
|
|
@@ -150,16 +155,33 @@ class DuskProducer(SunProducer):
|
|
|
150
155
|
super().__init__(sun.dusk)
|
|
151
156
|
|
|
152
157
|
|
|
153
|
-
class
|
|
158
|
+
class SunFuncIgnoringCompare:
|
|
159
|
+
__slots__ = ()
|
|
160
|
+
|
|
161
|
+
def __eq__(self, other: object) -> bool:
|
|
162
|
+
if not isinstance(other, self.__class__):
|
|
163
|
+
raise TypeError()
|
|
164
|
+
return all(getattr(self, s) == getattr(other, s) for s in self.__slots__ if s != 'func')
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
class SunElevationProducerCompare(SunProducer, SunFuncIgnoringCompare):
|
|
168
|
+
__slots__ = ('direction', 'elevation')
|
|
169
|
+
|
|
154
170
|
def __init__(self, elevation: float, direction: Literal['rising', 'setting']) -> None:
|
|
155
171
|
if elevation < -90 or elevation > 90:
|
|
156
172
|
msg = 'Elevation must be between -90 and 90'
|
|
157
173
|
raise ValueError(msg)
|
|
158
174
|
|
|
159
175
|
self.elevation: Final = elevation
|
|
160
|
-
self.direction: Final = {'rising': SunDirection.RISING, 'setting': SunDirection.SETTING}[direction]
|
|
176
|
+
self.direction: Final = {'rising': SunDirection.RISING, 'setting': SunDirection.SETTING}[direction] \
|
|
177
|
+
if direction is not SunDirection.RISING and direction is not SunDirection.SETTING else direction
|
|
161
178
|
super().__init__(self._sun_func)
|
|
162
179
|
|
|
180
|
+
@override
|
|
181
|
+
def copy(self) -> Self:
|
|
182
|
+
cls = self.__class__(self.elevation, self.direction)
|
|
183
|
+
return self._copy_filter(cls)
|
|
184
|
+
|
|
163
185
|
def _sun_func(self, observer: Observer, date: dt_date) -> datetime:
|
|
164
186
|
return sun.time_at_elevation(observer, self.elevation, date, self.direction)
|
|
165
187
|
|
|
@@ -168,7 +190,9 @@ class SunElevationProducer(SunProducer):
|
|
|
168
190
|
return self.__class__, self.elevation, self.direction
|
|
169
191
|
|
|
170
192
|
|
|
171
|
-
class
|
|
193
|
+
class SunAzimuthProducerCompare(SunProducer, SunFuncIgnoringCompare):
|
|
194
|
+
__slots__ = ('azimuth', )
|
|
195
|
+
|
|
172
196
|
def __init__(self, azimuth: float) -> None:
|
|
173
197
|
if not 0 <= azimuth <= 360:
|
|
174
198
|
msg = 'Azimuth must be between 0° and 360°'
|
|
@@ -177,6 +201,11 @@ class SunAzimuthProducer(SunProducer):
|
|
|
177
201
|
self.azimuth: Final = azimuth
|
|
178
202
|
super().__init__(self._sun_func)
|
|
179
203
|
|
|
204
|
+
@override
|
|
205
|
+
def copy(self) -> Self:
|
|
206
|
+
cls = self.__class__(self.azimuth)
|
|
207
|
+
return self._copy_filter(cls)
|
|
208
|
+
|
|
180
209
|
@override
|
|
181
210
|
def _cache_key(self) -> tuple[Hashable, ...]:
|
|
182
211
|
return self.__class__, self.azimuth
|
|
@@ -2,7 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from typing import TYPE_CHECKING, Final
|
|
4
4
|
|
|
5
|
-
from typing_extensions import override
|
|
5
|
+
from typing_extensions import Self, override
|
|
6
6
|
|
|
7
7
|
from eascheduler.helpers import TimeReplacer, TimeSkippedError, TimeTwiceError
|
|
8
8
|
from eascheduler.producers.base import DateTimeProducerBase, not_infinite_loop
|
|
@@ -19,6 +19,11 @@ class TimeProducer(DateTimeProducerBase):
|
|
|
19
19
|
super().__init__()
|
|
20
20
|
self._time: Final = time
|
|
21
21
|
|
|
22
|
+
@override
|
|
23
|
+
def copy(self) -> Self:
|
|
24
|
+
cls = self.__class__(time=self._time.copy())
|
|
25
|
+
return self._copy_filter(cls)
|
|
26
|
+
|
|
22
27
|
@override
|
|
23
28
|
def get_next(self, dt: Instant) -> Instant: # type: ignore[return]
|
|
24
29
|
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = '0.2.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
|