hikari-arc 1.3.0__py3-none-any.whl → 1.3.1__py3-none-any.whl

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.
arc/internal/about.py CHANGED
@@ -5,7 +5,7 @@ __author_email__: t.Final[str] = "git@hypergonial.com"
5
5
  __maintainer__: t.Final[str] = "hypergonial"
6
6
  __license__: t.Final[str] = "MIT"
7
7
  __url__: t.Final[str] = "https://github.com/hypergonial/hikari-arc"
8
- __version__: t.Final[str] = "1.3.0"
8
+ __version__: t.Final[str] = "1.3.1"
9
9
 
10
10
  # MIT License
11
11
  #
arc/utils/loops.py CHANGED
@@ -25,12 +25,13 @@ class _LoopBase(abc.ABC, t.Generic[P]):
25
25
  - [`CronLoop`][arc.utils.loops.CronLoop]
26
26
  """
27
27
 
28
- __slots__ = ("_coro", "_task", "_failed", "_stop_next")
28
+ __slots__ = ("_coro", "_task", "_failed", "_stop_next", "_run_on_start")
29
29
 
30
- def __init__(self, callback: t.Callable[P, t.Awaitable[None]]) -> None:
30
+ def __init__(self, callback: t.Callable[P, t.Awaitable[None]], *, run_on_start: bool = True) -> None:
31
31
  self._coro = callback
32
32
  self._task: asyncio.Task[None] | None = None
33
33
  self._failed: int = 0
34
+ self._run_on_start: bool = run_on_start
34
35
  self._stop_next: bool = False
35
36
 
36
37
  if not inspect.iscoroutinefunction(self._coro):
@@ -51,23 +52,28 @@ class _LoopBase(abc.ABC, t.Generic[P]):
51
52
  The number of seconds to wait before running the coroutine again.
52
53
  """
53
54
 
55
+ async def _call_callback(self, *args: P.args, **kwargs: P.kwargs) -> None:
56
+ """Call the callback and handle exceptions."""
57
+ try:
58
+ await self._coro(*args, **kwargs)
59
+ except Exception as e:
60
+ traceback_msg = "\n".join(traceback.format_exception(type(e), e, e.__traceback__))
61
+ print(f"Loop encountered exception: {e}", file=sys.stderr)
62
+ print(traceback_msg, file=sys.stderr)
63
+
64
+ if self._failed < 3:
65
+ self._failed += 1
66
+ else:
67
+ raise RuntimeError(f"Loop failed repeatedly, stopping it. Exception: {e}")
68
+
54
69
  async def _loopy_loop(self, *args: P.args, **kwargs: P.kwargs) -> None:
55
70
  """Main loop logic."""
71
+ if self._run_on_start:
72
+ await self._call_callback(*args, **kwargs)
73
+
56
74
  while not self._stop_next:
57
- try:
58
- await self._coro(*args, **kwargs)
59
- except Exception as e:
60
- traceback_msg = "\n".join(traceback.format_exception(type(e), e, e.__traceback__))
61
- print(f"Loop encountered exception: {e}", file=sys.stderr)
62
- print(traceback_msg, file=sys.stderr)
63
-
64
- if self._failed < 3:
65
- self._failed += 1
66
- await asyncio.sleep(self._get_next_run())
67
- else:
68
- raise RuntimeError(f"Loop failed repeatedly, stopping it. Exception: {e}")
69
- else:
70
- await asyncio.sleep(self._get_next_run())
75
+ await asyncio.sleep(self._get_next_run())
76
+ await self._call_callback(*args, **kwargs)
71
77
  self.cancel()
72
78
 
73
79
  def _create_task(self, *args: P.args, **kwargs: P.kwargs) -> asyncio.Task[None]:
@@ -135,6 +141,9 @@ class IntervalLoop(_LoopBase[P]):
135
141
  The number of hours to wait before running the coroutine again.
136
142
  days : float, optional
137
143
  The number of days to wait before running the coroutine again.
144
+ run_on_start : bool, optional
145
+ Whether to run the callback immediately after starting the loop.
146
+ If set to false, the loop will wait for the specified interval before first running the callback.
138
147
 
139
148
  Raises
140
149
  ------
@@ -164,8 +173,9 @@ class IntervalLoop(_LoopBase[P]):
164
173
  minutes: float | None = None,
165
174
  hours: float | None = None,
166
175
  days: float | None = None,
176
+ run_on_start: bool = True,
167
177
  ) -> None:
168
- super().__init__(callback)
178
+ super().__init__(callback, run_on_start=run_on_start)
169
179
  if not seconds and not minutes and not hours and not days:
170
180
  raise ValueError("At least one of 'seconds', 'minutes', 'hours' or 'days' must be not None.")
171
181
  else:
@@ -196,6 +206,8 @@ class CronLoop(_LoopBase[P]):
196
206
  The coroutine to run at the specified interval.
197
207
  cron_format : str
198
208
  The cron format to use. See https://en.wikipedia.org/wiki/Cron for more information.
209
+ timezone : datetime.timezone
210
+ The timezone to use for the cron format. Defaults to UTC.
199
211
 
200
212
  Raises
201
213
  ------
@@ -217,20 +229,30 @@ class CronLoop(_LoopBase[P]):
217
229
  create a [`CronLoop`][arc.utils.loops.CronLoop] from a coroutine function.
218
230
  """
219
231
 
220
- __slots__ = ("_iter",)
232
+ __slots__ = ("_iter", "_tz")
221
233
 
222
- def __init__(self, callback: t.Callable[P, t.Awaitable[None]], cron_format: str) -> None:
223
- super().__init__(callback)
234
+ def __init__(
235
+ self,
236
+ callback: t.Callable[P, t.Awaitable[None]],
237
+ cron_format: str,
238
+ *,
239
+ timezone: datetime.timezone = datetime.timezone.utc,
240
+ ) -> None:
241
+ super().__init__(callback, run_on_start=False)
242
+ self._tz = timezone
224
243
 
225
244
  try:
226
245
  import croniter
227
246
 
228
- self._iter = croniter.croniter(cron_format, datetime.datetime.now())
247
+ self._iter = croniter.croniter(cron_format)
229
248
  except ImportError:
230
249
  raise ImportError("Missing dependency for CronLoop: 'croniter'")
231
250
 
232
251
  def _get_next_run(self) -> float:
233
- return self._iter.get_next(float) - datetime.datetime.now().timestamp()
252
+ return (
253
+ self._iter.get_next(float, start_time=datetime.datetime.now(self._tz))
254
+ - datetime.datetime.now(self._tz).timestamp()
255
+ )
234
256
 
235
257
 
236
258
  def interval_loop(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: hikari-arc
3
- Version: 1.3.0
3
+ Version: 1.3.1
4
4
  Summary: A command handler for hikari with a focus on type-safety and correctness.
5
5
  Home-page: https://github.com/hypergonial/hikari-arc
6
6
  Author: hypergonial
@@ -32,16 +32,16 @@ Requires-Dist: croniter ==2.0.5 ; extra == 'cron'
32
32
  Requires-Dist: types-croniter ==2.0.0.20240423 ; extra == 'cron'
33
33
  Provides-Extra: dev
34
34
  Requires-Dist: ruff ==0.4.4 ; extra == 'dev'
35
- Requires-Dist: pyright ==1.1.362 ; extra == 'dev'
35
+ Requires-Dist: pyright ==1.1.364 ; extra == 'dev'
36
36
  Requires-Dist: nox ==2024.4.15 ; extra == 'dev'
37
37
  Requires-Dist: typing-extensions ==4.11.0 ; extra == 'dev'
38
- Requires-Dist: pytest ==8.2.0 ; extra == 'dev'
39
- Requires-Dist: pytest-asyncio ==0.23.6 ; extra == 'dev'
38
+ Requires-Dist: pytest ==8.2.1 ; extra == 'dev'
39
+ Requires-Dist: pytest-asyncio ==0.23.7 ; extra == 'dev'
40
40
  Requires-Dist: slotscheck ==0.19.0 ; extra == 'dev'
41
41
  Provides-Extra: docs
42
- Requires-Dist: mkdocs-material[imaging] ~=9.5.21 ; extra == 'docs'
42
+ Requires-Dist: mkdocs-material[imaging] ~=9.5.24 ; extra == 'docs'
43
43
  Requires-Dist: mkdocs ~=1.6.0 ; extra == 'docs'
44
- Requires-Dist: mkdocstrings-python ~=1.10.0 ; extra == 'docs'
44
+ Requires-Dist: mkdocstrings-python ~=1.10.2 ; extra == 'docs'
45
45
  Requires-Dist: black ~=24.4.2 ; extra == 'docs'
46
46
  Requires-Dist: griffe-inherited-docstrings ~=1.0.0 ; extra == 'docs'
47
47
  Requires-Dist: mkdocs-glightbox ~=0.4.0 ; extra == 'docs'
@@ -38,7 +38,7 @@ arc/context/autocomplete.py,sha256=YOu6leCKH0mfGVj0vmo4kj1_cWUM6c_vjgooymQ6sYY,3
38
38
  arc/context/base.py,sha256=MUZe8abJUwzFUsnv_WsxHPKDQswI4IUImyozjgnGDs8,39977
39
39
  arc/ext/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
40
40
  arc/internal/__init__.py,sha256=kZKBSFOkaDLe5kBvxiCQ-MWRPUZ_GBZdWwINPMkSbvo,1436
41
- arc/internal/about.py,sha256=yqP06bxBoLWzl0pwQGr-attZc9syZZvAf3uebXTAWKc,1414
41
+ arc/internal/about.py,sha256=AyaKo5EADmNJPkrNqjvdEswZXMy1LZyf67HwBLzL8Bw,1414
42
42
  arc/internal/deprecation.py,sha256=Lnirv1z_oj6QJvbnd38TwHQnhHhFD2rTqqvH96pxiWE,2204
43
43
  arc/internal/options.py,sha256=EODBho9BdHOgjqqOVAEEbwOkZJiVfjFDDpUztGKUdK4,3622
44
44
  arc/internal/sigparse.py,sha256=EsewKxcidtuoY0clEAVh8nVGmTq5hvAHxqokGOAcZPk,13518
@@ -47,13 +47,13 @@ arc/internal/types.py,sha256=NXemzM6cR2pH2vV9CCr6CSZFJZNY_yaovzNifppUkUA,4365
47
47
  arc/internal/version.py,sha256=bZFtIbhehFhsGU2yyTVHb8YIvCYhp9iyueTalCKFtsg,2201
48
48
  arc/utils/__init__.py,sha256=vc8QYVVVOe95_kfWWb5lc8dFkJrs5SnpIJta_t0l3UI,2334
49
49
  arc/utils/concurrency_limiter.py,sha256=7wz7bfzvCna5Ai50EAw1SaY8ZmZy-Bc68NwubhQExxc,12965
50
- arc/utils/loops.py,sha256=Fj8G6QdvqgZa2VNGE1gJGR0kSyUqlGTjoSlGomWkhe0,11222
50
+ arc/utils/loops.py,sha256=nCKGy0tTwpR49gDAZJaVwetMgIBM_WbqIpEyDIqBcjE,12064
51
51
  arc/utils/ratelimiter.py,sha256=YPETOjQOga8RazYoK3Ghueh2TsOdfkH7WM58dr3ybcU,9477
52
52
  arc/utils/hooks/__init__.py,sha256=pXlAQ1zGxQV-bBeeL8sKRkUyO1PmEazT_a_XKtf7GFA,515
53
53
  arc/utils/hooks/basic.py,sha256=e09raCnIcGMpBMd4uvzBCofNij2aGAmXPO2AuC8cXZY,5894
54
54
  arc/utils/hooks/limiters.py,sha256=So6BZxSy3AdexM1UHWt8cILGQ8b-Fp78DYfqqw4yvdM,7496
55
- hikari_arc-1.3.0.dist-info/LICENSE,sha256=q_osUjCCfQVI7zzgteLMZ-RlhXlB4rqQE8I0DGh7ur4,1076
56
- hikari_arc-1.3.0.dist-info/METADATA,sha256=p0QVNaLrtoJvmZa4kA6jzk5cRKu_h_P2G31MtbLdb3Q,5563
57
- hikari_arc-1.3.0.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
58
- hikari_arc-1.3.0.dist-info/top_level.txt,sha256=kTs_REfGfSlIT6Hq_kxH-MtDlOO6LPwFwkOoNdDCnJ4,4
59
- hikari_arc-1.3.0.dist-info/RECORD,,
55
+ hikari_arc-1.3.1.dist-info/LICENSE,sha256=q_osUjCCfQVI7zzgteLMZ-RlhXlB4rqQE8I0DGh7ur4,1076
56
+ hikari_arc-1.3.1.dist-info/METADATA,sha256=juQrw3lZ5xNQINwBHMfq0o0z7yFIWhJS_c6nn-M6ymQ,5563
57
+ hikari_arc-1.3.1.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
58
+ hikari_arc-1.3.1.dist-info/top_level.txt,sha256=kTs_REfGfSlIT6Hq_kxH-MtDlOO6LPwFwkOoNdDCnJ4,4
59
+ hikari_arc-1.3.1.dist-info/RECORD,,