EAScheduler 0.2.1__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.1/src/EAScheduler.egg-info → eascheduler-0.2.2}/PKG-INFO +18 -4
  2. {eascheduler-0.2.1 → eascheduler-0.2.2}/requirements_setup.txt +2 -2
  3. {eascheduler-0.2.1 → eascheduler-0.2.2/src/EAScheduler.egg-info}/PKG-INFO +18 -4
  4. {eascheduler-0.2.1 → 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.1 → eascheduler-0.2.2}/src/eascheduler/builder/filters.py +1 -1
  7. {eascheduler-0.2.1 → eascheduler-0.2.2}/src/eascheduler/builder/helper.py +4 -3
  8. {eascheduler-0.2.1 → eascheduler-0.2.2}/src/eascheduler/builder/triggers.py +11 -10
  9. {eascheduler-0.2.1 → eascheduler-0.2.2}/src/eascheduler/helpers/time_replace.py +10 -1
  10. {eascheduler-0.2.1 → eascheduler-0.2.2}/src/eascheduler/producers/__init__.py +2 -2
  11. {eascheduler-0.2.1 → eascheduler-0.2.2}/src/eascheduler/producers/base.py +27 -4
  12. {eascheduler-0.2.1 → eascheduler-0.2.2}/src/eascheduler/producers/prod_filter.py +22 -0
  13. {eascheduler-0.2.1 → eascheduler-0.2.2}/src/eascheduler/producers/prod_filter_holiday.py +5 -1
  14. {eascheduler-0.2.1 → eascheduler-0.2.2}/src/eascheduler/producers/prod_group.py +7 -0
  15. {eascheduler-0.2.1 → eascheduler-0.2.2}/src/eascheduler/producers/prod_interval.py +6 -1
  16. {eascheduler-0.2.1 → eascheduler-0.2.2}/src/eascheduler/producers/prod_operation.py +22 -2
  17. {eascheduler-0.2.1 → eascheduler-0.2.2}/src/eascheduler/producers/prod_sun.py +33 -4
  18. {eascheduler-0.2.1 → eascheduler-0.2.2}/src/eascheduler/producers/prod_time.py +6 -1
  19. eascheduler-0.2.1/src/eascheduler/__version__.py +0 -1
  20. {eascheduler-0.2.1 → eascheduler-0.2.2}/LICENSE +0 -0
  21. {eascheduler-0.2.1 → eascheduler-0.2.2}/MANIFEST.in +0 -0
  22. {eascheduler-0.2.1 → eascheduler-0.2.2}/setup.cfg +0 -0
  23. {eascheduler-0.2.1 → eascheduler-0.2.2}/setup.py +0 -0
  24. {eascheduler-0.2.1 → eascheduler-0.2.2}/src/EAScheduler.egg-info/SOURCES.txt +0 -0
  25. {eascheduler-0.2.1 → eascheduler-0.2.2}/src/EAScheduler.egg-info/dependency_links.txt +0 -0
  26. {eascheduler-0.2.1 → eascheduler-0.2.2}/src/EAScheduler.egg-info/top_level.txt +0 -0
  27. {eascheduler-0.2.1 → eascheduler-0.2.2}/src/eascheduler/__init__.py +0 -0
  28. {eascheduler-0.2.1 → eascheduler-0.2.2}/src/eascheduler/builder/__init__.py +0 -0
  29. {eascheduler-0.2.1 → eascheduler-0.2.2}/src/eascheduler/builder/jobs.py +0 -0
  30. {eascheduler-0.2.1 → eascheduler-0.2.2}/src/eascheduler/builder/utils.py +0 -0
  31. {eascheduler-0.2.1 → eascheduler-0.2.2}/src/eascheduler/const.py +0 -0
  32. {eascheduler-0.2.1 → eascheduler-0.2.2}/src/eascheduler/default.py +0 -0
  33. {eascheduler-0.2.1 → eascheduler-0.2.2}/src/eascheduler/errors/__init__.py +0 -0
  34. {eascheduler-0.2.1 → eascheduler-0.2.2}/src/eascheduler/errors/errors.py +0 -0
  35. {eascheduler-0.2.1 → eascheduler-0.2.2}/src/eascheduler/errors/handler.py +0 -0
  36. {eascheduler-0.2.1 → eascheduler-0.2.2}/src/eascheduler/executor/__init__.py +0 -0
  37. {eascheduler-0.2.1 → eascheduler-0.2.2}/src/eascheduler/executor/base.py +0 -0
  38. {eascheduler-0.2.1 → eascheduler-0.2.2}/src/eascheduler/helpers/__init__.py +0 -0
  39. {eascheduler-0.2.1 → eascheduler-0.2.2}/src/eascheduler/helpers/dst_param.py +0 -0
  40. {eascheduler-0.2.1 → eascheduler-0.2.2}/src/eascheduler/helpers/helpers.py +0 -0
  41. {eascheduler-0.2.1 → eascheduler-0.2.2}/src/eascheduler/job_control/__init__.py +0 -0
  42. {eascheduler-0.2.1 → eascheduler-0.2.2}/src/eascheduler/job_control/base.py +0 -0
  43. {eascheduler-0.2.1 → eascheduler-0.2.2}/src/eascheduler/job_control/job_countdown.py +0 -0
  44. {eascheduler-0.2.1 → eascheduler-0.2.2}/src/eascheduler/job_control/job_datetime.py +0 -0
  45. {eascheduler-0.2.1 → eascheduler-0.2.2}/src/eascheduler/job_control/job_onetime.py +0 -0
  46. {eascheduler-0.2.1 → eascheduler-0.2.2}/src/eascheduler/job_stores/__init__.py +0 -0
  47. {eascheduler-0.2.1 → eascheduler-0.2.2}/src/eascheduler/job_stores/base.py +0 -0
  48. {eascheduler-0.2.1 → eascheduler-0.2.2}/src/eascheduler/job_stores/memory.py +0 -0
  49. {eascheduler-0.2.1 → eascheduler-0.2.2}/src/eascheduler/jobs/__init__.py +0 -0
  50. {eascheduler-0.2.1 → eascheduler-0.2.2}/src/eascheduler/jobs/base.py +0 -0
  51. {eascheduler-0.2.1 → eascheduler-0.2.2}/src/eascheduler/jobs/event_handler.py +0 -0
  52. {eascheduler-0.2.1 → eascheduler-0.2.2}/src/eascheduler/jobs/job_countdown.py +0 -0
  53. {eascheduler-0.2.1 → eascheduler-0.2.2}/src/eascheduler/jobs/job_datetime.py +0 -0
  54. {eascheduler-0.2.1 → eascheduler-0.2.2}/src/eascheduler/jobs/job_onetime.py +0 -0
  55. {eascheduler-0.2.1 → eascheduler-0.2.2}/src/eascheduler/py.typed +0 -0
  56. {eascheduler-0.2.1 → eascheduler-0.2.2}/src/eascheduler/schedulers/__init__.py +0 -0
  57. {eascheduler-0.2.1 → eascheduler-0.2.2}/src/eascheduler/schedulers/async_scheduler.py +0 -0
  58. {eascheduler-0.2.1 → eascheduler-0.2.2}/src/eascheduler/schedulers/base.py +0 -0
  59. {eascheduler-0.2.1 → eascheduler-0.2.2}/src/eascheduler/task_managers/__init__.py +0 -0
  60. {eascheduler-0.2.1 → eascheduler-0.2.2}/src/eascheduler/task_managers/base.py +0 -0
  61. {eascheduler-0.2.1 → eascheduler-0.2.2}/src/eascheduler/task_managers/parallel.py +0 -0
  62. {eascheduler-0.2.1 → eascheduler-0.2.2}/src/eascheduler/task_managers/sequential.py +0 -0
  63. {eascheduler-0.2.1 → eascheduler-0.2.2}/tests/test_const.py +0 -0
  64. {eascheduler-0.2.1 → 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.1
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.13
22
+ Requires-Dist: whenever==0.6.16
23
23
  Requires-Dist: astral==3.2
24
- Requires-Dist: holidays==0.61
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,10 @@ 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
+
129
143
  #### 0.2.1 (2024-11-20)
130
144
  - Updated dependencies and some type hints
131
145
 
@@ -1,6 +1,6 @@
1
1
  # Requirements to install and use this library
2
- whenever == 0.6.13
2
+ whenever == 0.6.16
3
3
  astral == 3.2
4
- holidays == 0.61
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.1
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.13
22
+ Requires-Dist: whenever==0.6.16
23
23
  Requires-Dist: astral==3.2
24
- Requires-Dist: holidays==0.61
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,10 @@ 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
+
129
143
  #### 0.2.1 (2024-11-20)
130
144
  - Updated dependencies and some type hints
131
145
 
@@ -1,4 +1,4 @@
1
- whenever==0.6.13
1
+ whenever==0.6.16
2
2
  astral==3.2
3
- holidays==0.61
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
@@ -103,7 +103,7 @@ def get_instant(value: HINT_INSTANT) -> Instant:
103
103
  # timedelta
104
104
  try:
105
105
  timedelta = get_timedelta(value)
106
- except ValueError:
106
+ except (ValueError, TypeError):
107
107
  pass
108
108
  else:
109
109
  return Instant.now() + timedelta
@@ -112,7 +112,7 @@ def get_instant(value: HINT_INSTANT) -> Instant:
112
112
  # time
113
113
  try:
114
114
  time = get_time(value)
115
- except ValueError:
115
+ except (ValueError, TypeError):
116
116
  pass
117
117
  else:
118
118
  now = SystemDateTime.now()
@@ -228,7 +228,8 @@ class BuilderTypeValidator(Generic[TYPE_OUT]):
228
228
  msg = f'Expected an instance of {self.type_out.__name__}, got {obj} ({type(obj).__name__})'
229
229
  raise TypeError(msg)
230
230
 
231
- return obj
231
+ # We return a copy so we are sure that there are no side effects
232
+ return obj.copy()
232
233
 
233
234
  def __repr__(self) -> str:
234
235
  return f'{self.__class__.__name__}({self.type_in.__name__}, {self.type_out.__name__}, {self.var_name})'
@@ -4,6 +4,7 @@ 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,
@@ -27,8 +28,8 @@ from eascheduler.producers import (
27
28
  LatestProducerOperation,
28
29
  NoonProducer,
29
30
  OffsetProducerOperation,
30
- SunAzimuthProducer,
31
- SunElevationProducer,
31
+ SunAzimuthProducerCompare,
32
+ SunElevationProducerCompare,
32
33
  SunriseProducer,
33
34
  SunsetProducer,
34
35
  TimeProducer,
@@ -51,7 +52,7 @@ class TriggerObject:
51
52
  :param offset: The offset (positive or negative)
52
53
  """
53
54
  return self.__class__(
54
- OffsetProducerOperation(self._producer, get_timedelta(offset).in_seconds())
55
+ OffsetProducerOperation(_get_producer(self), get_timedelta(offset).in_seconds())
55
56
  )
56
57
 
57
58
  def earliest(self, earliest: HINT_TIME, *,
@@ -65,7 +66,7 @@ class TriggerObject:
65
66
  """
66
67
  return self.__class__(
67
68
  EarliestProducerOperation(
68
- self._producer,
69
+ _get_producer(self),
69
70
  get_time_replacer(earliest, clock_forward=clock_forward, clock_backward=clock_backward)
70
71
  )
71
72
  )
@@ -81,7 +82,7 @@ class TriggerObject:
81
82
  """
82
83
  return self.__class__(
83
84
  LatestProducerOperation(
84
- self._producer,
85
+ _get_producer(self),
85
86
  get_time_replacer(latest, clock_forward=clock_forward, clock_backward=clock_backward)
86
87
  )
87
88
  )
@@ -94,7 +95,7 @@ class TriggerObject:
94
95
  """
95
96
  return self.__class__(
96
97
  JitterProducerOperation(
97
- self._producer,
98
+ _get_producer(self),
98
99
  get_timedelta(low).in_seconds(), get_timedelta(high).in_seconds() if high is not None else None
99
100
  )
100
101
  )
@@ -106,7 +107,7 @@ class TriggerObject:
106
107
  """
107
108
  if self._producer._filter is not None:
108
109
  raise ValueError()
109
- self._producer._filter = filter._filter
110
+ self._producer._filter = _get_producer_filter(filter)
110
111
  return self
111
112
 
112
113
  only_at = only_on
@@ -146,7 +147,7 @@ class TriggerBuilder:
146
147
  :param elevation: Sun elevation in degrees
147
148
  :param direction: rising or falling
148
149
  """
149
- return TriggerObject(SunElevationProducer(elevation, direction))
150
+ return TriggerObject(SunElevationProducerCompare(elevation, direction))
150
151
 
151
152
  @staticmethod
152
153
  def sun_azimuth(azimuth: float) -> TriggerObject:
@@ -154,7 +155,7 @@ class TriggerBuilder:
154
155
 
155
156
  :param azimuth: Sun azimuth in degrees
156
157
  """
157
- return TriggerObject(SunAzimuthProducer(azimuth))
158
+ return TriggerObject(SunAzimuthProducerCompare(azimuth))
158
159
 
159
160
  @staticmethod
160
161
  def group(*builders: TriggerObject) -> TriggerObject:
@@ -200,4 +201,4 @@ class TriggerBuilder:
200
201
  )
201
202
 
202
203
 
203
- _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,6 +115,11 @@ 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:
@@ -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.1'
File without changes
File without changes
File without changes
File without changes