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.
Files changed (64) hide show
  1. {eascheduler-0.2.0/src/EAScheduler.egg-info → eascheduler-0.2.2}/PKG-INFO +20 -4
  2. {eascheduler-0.2.0 → eascheduler-0.2.2}/requirements_setup.txt +2 -2
  3. {eascheduler-0.2.0 → eascheduler-0.2.2/src/EAScheduler.egg-info}/PKG-INFO +20 -4
  4. {eascheduler-0.2.0 → eascheduler-0.2.2}/src/EAScheduler.egg-info/requires.txt +2 -2
  5. eascheduler-0.2.2/src/eascheduler/__version__.py +1 -0
  6. {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/builder/filters.py +1 -1
  7. {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/builder/helper.py +6 -4
  8. {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/builder/jobs.py +7 -2
  9. {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/builder/triggers.py +15 -13
  10. {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/helpers/time_replace.py +10 -1
  11. {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/producers/__init__.py +2 -2
  12. {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/producers/base.py +27 -4
  13. {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/producers/prod_filter.py +22 -0
  14. {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/producers/prod_filter_holiday.py +5 -1
  15. {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/producers/prod_group.py +7 -0
  16. {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/producers/prod_interval.py +6 -1
  17. {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/producers/prod_operation.py +23 -3
  18. {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/producers/prod_sun.py +33 -4
  19. {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/producers/prod_time.py +6 -1
  20. eascheduler-0.2.0/src/eascheduler/__version__.py +0 -1
  21. {eascheduler-0.2.0 → eascheduler-0.2.2}/LICENSE +0 -0
  22. {eascheduler-0.2.0 → eascheduler-0.2.2}/MANIFEST.in +0 -0
  23. {eascheduler-0.2.0 → eascheduler-0.2.2}/setup.cfg +0 -0
  24. {eascheduler-0.2.0 → eascheduler-0.2.2}/setup.py +0 -0
  25. {eascheduler-0.2.0 → eascheduler-0.2.2}/src/EAScheduler.egg-info/SOURCES.txt +0 -0
  26. {eascheduler-0.2.0 → eascheduler-0.2.2}/src/EAScheduler.egg-info/dependency_links.txt +0 -0
  27. {eascheduler-0.2.0 → eascheduler-0.2.2}/src/EAScheduler.egg-info/top_level.txt +0 -0
  28. {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/__init__.py +0 -0
  29. {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/builder/__init__.py +0 -0
  30. {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/builder/utils.py +0 -0
  31. {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/const.py +0 -0
  32. {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/default.py +0 -0
  33. {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/errors/__init__.py +0 -0
  34. {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/errors/errors.py +0 -0
  35. {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/errors/handler.py +0 -0
  36. {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/executor/__init__.py +0 -0
  37. {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/executor/base.py +0 -0
  38. {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/helpers/__init__.py +0 -0
  39. {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/helpers/dst_param.py +0 -0
  40. {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/helpers/helpers.py +0 -0
  41. {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/job_control/__init__.py +0 -0
  42. {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/job_control/base.py +0 -0
  43. {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/job_control/job_countdown.py +0 -0
  44. {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/job_control/job_datetime.py +0 -0
  45. {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/job_control/job_onetime.py +0 -0
  46. {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/job_stores/__init__.py +0 -0
  47. {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/job_stores/base.py +0 -0
  48. {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/job_stores/memory.py +0 -0
  49. {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/jobs/__init__.py +0 -0
  50. {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/jobs/base.py +0 -0
  51. {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/jobs/event_handler.py +0 -0
  52. {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/jobs/job_countdown.py +0 -0
  53. {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/jobs/job_datetime.py +0 -0
  54. {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/jobs/job_onetime.py +0 -0
  55. {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/py.typed +0 -0
  56. {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/schedulers/__init__.py +0 -0
  57. {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/schedulers/async_scheduler.py +0 -0
  58. {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/schedulers/base.py +0 -0
  59. {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/task_managers/__init__.py +0 -0
  60. {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/task_managers/base.py +0 -0
  61. {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/task_managers/parallel.py +0 -0
  62. {eascheduler-0.2.0 → eascheduler-0.2.2}/src/eascheduler/task_managers/sequential.py +0 -0
  63. {eascheduler-0.2.0 → eascheduler-0.2.2}/tests/test_const.py +0 -0
  64. {eascheduler-0.2.0 → eascheduler-0.2.2}/tests/test_default.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: EAScheduler
3
- Version: 0.2.0
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.12
22
+ Requires-Dist: whenever==0.6.16
23
23
  Requires-Dist: astral==3.2
24
- Requires-Dist: holidays==0.60
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
  [![Tests Status](https://github.com/spacemanspiff2007/eascheduler/workflows/Tests/badge.svg)](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
1
  # Requirements to install and use this library
2
- whenever == 0.6.12
2
+ whenever == 0.6.16
3
3
  astral == 3.2
4
- holidays == 0.60
4
+ holidays == 0.64
5
5
 
6
6
  typing_extensions == 4.12.2
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: EAScheduler
3
- Version: 0.2.0
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.12
22
+ Requires-Dist: whenever==0.6.16
23
23
  Requires-Dist: astral==3.2
24
- Requires-Dist: holidays==0.60
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
  [![Tests Status](https://github.com/spacemanspiff2007/eascheduler/workflows/Tests/badge.svg)](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,4 +1,4 @@
1
- whenever==0.6.12
1
+ whenever==0.6.16
2
2
  astral==3.2
3
- holidays==0.60
3
+ holidays==0.64
4
4
  typing_extensions==4.12.2
@@ -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: HINT_TIMEDELTA) -> float:
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 obj
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 HINT_INSTANT, HINT_TIMEDELTA, get_instant, get_pos_timedelta_secs
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: HINT_TIMEDELTA, coro_func: Callable[..., Awaitable[Any]],
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
- SunAzimuthProducer,
30
- SunElevationProducer,
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._producer, get_timedelta(offset).in_seconds())
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._producer,
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._producer,
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: HINT_TIMEDELTA, high: HINT_TIMEDELTA | None = None) -> Self:
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._producer,
97
- get_pos_timedelta_secs(low), get_pos_timedelta_secs(high) if high is not None else None
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._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(SunElevationProducer(elevation, direction))
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(SunAzimuthProducer(azimuth))
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: HINT_TIMEDELTA) -> TriggerObject:
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__ = ('_time', '_skipped', '_repeated')
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 = {
@@ -21,8 +21,8 @@ from .prod_sun import (
21
21
  DawnProducer,
22
22
  DuskProducer,
23
23
  NoonProducer,
24
- SunAzimuthProducer,
25
- SunElevationProducer,
24
+ SunAzimuthProducerCompare,
25
+ SunElevationProducerCompare,
26
26
  SunriseProducer,
27
27
  SunsetProducer,
28
28
  )
@@ -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 ProducerFilterBase:
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
- class DateTimeProducerBase:
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__ = ('low', 'high')
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
- # We can use the whole interval because we will always
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 SunElevationProducer(SunProducer):
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 SunAzimuthProducer(SunProducer):
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