bear-utils 0.9.8__py3-none-any.whl → 0.9.9__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.
@@ -1 +1 @@
1
- __version__ = "0.9.8"
1
+ __version__ = "0.9.9"
@@ -15,14 +15,25 @@ Callback = Callable[..., Any]
15
15
 
16
16
 
17
17
  class Events:
18
- """Simple wrapper exposing :mod:`events_module` functionality as methods."""
18
+ """Simple wrapper exposing :mod:`events_module` functionality as methods.
19
19
 
20
- # Method names mirror functions from ``events_module`` for familiarity
20
+ Method names mirror functions from ``events_module`` for familiarity
21
+ """
21
22
 
22
- def event_handler(self, event_name: str, func: Callback | None = None):
23
+ def event_handler(
24
+ self, event_name: str, func: Callback | None = None
25
+ ) -> Callable[[Callable[..., Any]], Callable[..., Any]] | Callable[..., Any]:
23
26
  """Register ``func`` as a handler for ``event_name``.
24
27
 
25
28
  Can be used as a decorator when ``func`` is omitted.
29
+
30
+ Args:
31
+ event_name (str): The name of the event to handle.
32
+ func (Callable, optional): The function to register as a handler. If omitted, returns
33
+ a decorator that can be used to register a handler function.
34
+
35
+ Returns:
36
+ Callable: If ``func`` is provided, returns the function itself. If ``func`` is None, returns a decorator that can be used to register a handler function.
26
37
  """
27
38
  if func is None:
28
39
  return _event_handler(event_name)
@@ -1,6 +1,5 @@
1
1
  """Event handling module for Bear Utils."""
2
2
 
3
- import asyncio
4
3
  from collections import defaultdict
5
4
  from collections.abc import Callable
6
5
  from functools import wraps
@@ -9,7 +8,7 @@ from typing import Any
9
8
  import weakref
10
9
  from weakref import WeakMethod, ref
11
10
 
12
- from bear_utils.extras._async_helpers import is_async_function
11
+ from bear_utils.extras._async_helpers import AsyncResponseModel, create_async_task, is_async_function
13
12
 
14
13
  Callback = Callable[..., Any]
15
14
 
@@ -45,18 +44,20 @@ def set_handler(name: str, func: Callback) -> None:
45
44
  _event_registry[name].add(ref(func, _make_callback(name)))
46
45
 
47
46
 
48
- def dispatch_event(name: str, *args, **kwargs) -> Any | None:
47
+ def dispatch_event(name: str, single: bool = False, *args, **kwargs) -> list[Any]:
49
48
  """Dispatch an event to all registered handlers."""
50
49
  results: list[Any] = []
51
50
  for func in _event_registry.get(name, []):
52
51
  if is_async_function(func):
53
- result: Any = asyncio.run(func(*args, **kwargs)) # FIXME: This will crash if called from an async context
52
+ task: AsyncResponseModel = create_async_task(func(*args, **kwargs))
53
+ result = task.get_conditional_result(timeout=1)
54
+ results.append(result)
54
55
  else:
55
56
  result: Any = func(*args, **kwargs)
56
57
  results.append(result)
57
- if not results:
58
- return None
59
- return results[0] if len(results) == 1 else results
58
+ if single and len(results) == 1:
59
+ return results[0]
60
+ return results
60
61
 
61
62
 
62
63
  def event_handler(event_name: str) -> Callable[[Callback], Callback]:
@@ -3,37 +3,65 @@ from asyncio import AbstractEventLoop, Task
3
3
  from collections.abc import Callable
4
4
  from contextlib import suppress
5
5
  import inspect
6
+ import time
7
+ from typing import Any
6
8
 
7
9
  from pydantic import BaseModel, Field
8
10
 
9
11
 
12
+ def is_async_function(func: Callable) -> bool:
13
+ """Check if a function is asynchronous.
14
+
15
+ Args:
16
+ func (Callable): The function/method to check.
17
+
18
+ Returns:
19
+ bool: True if the function is asynchronous, False otherwise.
20
+ """
21
+ return inspect.iscoroutinefunction(func) or inspect.isasyncgenfunction(func) or inspect.isasyncgen(func)
22
+
23
+
10
24
  class AsyncResponseModel(BaseModel):
11
25
  """A model to handle asynchronous operations with a function and its arguments."""
12
26
 
13
27
  loop: AbstractEventLoop | None = Field(default=None, description="The event loop to run the function in.")
14
28
  task: Task | None = Field(default=None, description="The task created for the asynchronous function.")
15
29
  before_loop: bool = Field(default=False, description="If the function was called from a running loop.")
30
+ result: Any = Field(default=None, description="The result of the completed task.")
16
31
 
17
32
  model_config = {"arbitrary_types_allowed": True}
18
33
 
34
+ @property
35
+ def done(self) -> bool:
36
+ """Check if the task is completed."""
37
+ return self.task is not None and self.task.done()
38
+
39
+ def get_result(self) -> Any:
40
+ """Get the result of the completed task."""
41
+ if not self.task:
42
+ raise ValueError("No task available")
43
+ if not self.task.done():
44
+ raise ValueError("Task not completed yet")
45
+ if self.result is None:
46
+ self.result = self.task.result()
47
+ return self.result
48
+
49
+ def get_conditional_result(self, timeout: float = 10.0) -> Any:
50
+ if self.loop and self.task and not self.before_loop:
51
+ self.loop.run_until_complete(self.task)
52
+ start_time: float = time.monotonic()
53
+ while not self.done:
54
+ if time.monotonic() - start_time > timeout:
55
+ raise TimeoutError("Task timed out")
56
+ time.sleep(0.1)
57
+ return self.get_result()
58
+
19
59
  def conditional_run(self) -> None:
20
60
  """Run the event loop until the task is complete if not in a running loop."""
21
61
  if self.loop and self.task and not self.before_loop:
22
62
  self.loop.run_until_complete(self.task)
23
63
 
24
64
 
25
- def is_async_function(func: Callable) -> bool:
26
- """Check if a function is asynchronous.
27
-
28
- Args:
29
- func (Callable): The function/method to check.
30
-
31
- Returns:
32
- bool: True if the function is asynchronous, False otherwise.
33
- """
34
- return inspect.iscoroutinefunction(func) or inspect.isasyncgenfunction(func) or inspect.isasyncgen(func)
35
-
36
-
37
65
  def in_async_loop() -> bool:
38
66
  """Check if the current context is already in an async loop.
39
67
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bear-utils
3
- Version: 0.9.8
3
+ Version: 0.9.9
4
4
  Summary: Various utilities for Bear programmers, including a rich logging utility, a disk cache, and a SQLite database wrapper amongst other things.
5
5
  Author-email: chaz <bright.lid5647@fastmail.com>
6
6
  Requires-Python: >=3.12
@@ -1,7 +1,7 @@
1
1
  bear_utils/__init__.py,sha256=Cin66XUC7cwuJ7ePZwgfdDnwFFPX_CHXOI3dMBWuyY8,1515
2
2
  bear_utils/__main__.py,sha256=-FlPquBlI1Tg2RoeX6d0Z8jTAiMFnJ0V06ZeRyiq58k,355
3
3
  bear_utils/_internal/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
- bear_utils/_internal/_version.py,sha256=GI3Cr15wC6XhdahtMoSOf62d2gYve5G_PiR_xx_RGKc,22
4
+ bear_utils/_internal/_version.py,sha256=nhsA3KKA-CXSYpbzuChuLyxpDepY_-JffnUNClcYEaU,22
5
5
  bear_utils/_internal/cli.py,sha256=GWVpdNxtb54EwiJPp4lync7M-hjo8hqVoVB7RFItXaA,4457
6
6
  bear_utils/_internal/debug.py,sha256=mpjGkoAHutSmkyPuHk8QA2xCI0ttPkqs8tQtpjFcVqw,5977
7
7
  bear_utils/ai/__init__.py,sha256=Q5P1KpSYS6iMt3vRbmasdWU5Oy5UkXfOGGyDI7Qy3Po,747
@@ -36,10 +36,10 @@ bear_utils/constants/time_related.py,sha256=Mykb27THqC-K0HFrbbrdkTNJUKhzBEOfmPJ0
36
36
  bear_utils/database/__init__.py,sha256=dS_HHJKESpsI6BFDLVx055o0GCJV9CMYOQVuHs7EfgY,192
37
37
  bear_utils/database/_db_manager.py,sha256=bbg5zG06DcOvd37oafLIqc480BwF9ZW9qWF3ldZHR8o,3805
38
38
  bear_utils/events/__init__.py,sha256=EFqmuzhaEYK9kjkGlrM7bjdjPwFEDbKn6RjJKfIBEJY,414
39
- bear_utils/events/events_class.py,sha256=vPDjWrbut8L3TFn7byyYFZpWYM5ADIqtW2Aeh-qtKfQ,1632
40
- bear_utils/events/events_module.py,sha256=DkQsDZ5WzM1MH1Msg7mRny40a17Jl31CXbpw4-pICxc,2349
39
+ bear_utils/events/events_class.py,sha256=Rzvw-TuX8Tpaf_D0A50fwKiV73BLCKpBTH6Dp08eg3I,2161
40
+ bear_utils/events/events_module.py,sha256=zXl3MB5VtITBCvCRHMRwfCrBkx8GkcH8k1aeB2w9Tnc,2436
41
41
  bear_utils/extras/__init__.py,sha256=0mz3ZYOkOcW_tUHSx2pV2mtCfyuXwgPe9SYXEqlGHsg,755
42
- bear_utils/extras/_async_helpers.py,sha256=4buULZZkR0n2z13Q8_FK59dMchj0Mup03YBaqSCbveQ,2291
42
+ bear_utils/extras/_async_helpers.py,sha256=iTLIJlME5Ifeqy6Y_0kNWAihc2oHCN57U6gehov1aDg,3340
43
43
  bear_utils/extras/_tools.py,sha256=cY5XxtUgeJxomeLO_g0X0xX_vEhMrvJkVd4I3-psjaw,6068
44
44
  bear_utils/extras/_zapper.py,sha256=K41SlZzvfjiKr04E6IMPxC94XBsKIkvwOgLRIWfYgGA,15830
45
45
  bear_utils/extras/platform_utils.py,sha256=Ai7ow7S-_cKb5zFwFh8dkC8xmbMJFy-0_-w3NCERdEw,1362
@@ -102,6 +102,6 @@ bear_utils/monitoring/__init__.py,sha256=9DKNIWTp_voLnaWgiP-wJ-o_N0hYixo-MzjUmg8
102
102
  bear_utils/monitoring/_common.py,sha256=LYQFxgTP9fk0cH71IQTuGwBYYPWCqHP_mMRNecoD76M,657
103
103
  bear_utils/monitoring/host_monitor.py,sha256=e0TYRJw9iDj5Ga6y3ck1TBFEeH42Cax5mQYaNU8yams,13241
104
104
  bear_utils/time/__init__.py,sha256=VctjJG17SyEHAFXytI1sZrOrq7zm3hVenIDOJFdaMN0,1424
105
- bear_utils-0.9.8.dist-info/METADATA,sha256=vhZfMgtDoFqM3oebfv3bbDz4ye0_hx_yj6Z0e8tULBc,8800
106
- bear_utils-0.9.8.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
107
- bear_utils-0.9.8.dist-info/RECORD,,
105
+ bear_utils-0.9.9.dist-info/METADATA,sha256=P8iMvX60OVd18V56xwJL7dzZNaqFVxsmfvVIRzLM_30,8800
106
+ bear_utils-0.9.9.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
107
+ bear_utils-0.9.9.dist-info/RECORD,,