bear-utils 0.8.17__py3-none-any.whl → 0.8.19__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,3 +1,5 @@
1
+ import asyncio
2
+ from asyncio import AbstractEventLoop, get_event_loop
1
3
  from collections.abc import Callable
2
4
  import inspect
3
5
 
@@ -12,3 +14,20 @@ def is_async_function(func: Callable) -> bool:
12
14
  bool: True if the function is asynchronous, False otherwise.
13
15
  """
14
16
  return inspect.iscoroutinefunction(func) or inspect.isasyncgenfunction(func) or inspect.isasyncgen(func)
17
+
18
+
19
+ def in_async_loop() -> bool:
20
+ """Check if the current context is already in an async loop."""
21
+ try:
22
+ return get_event_loop().is_running()
23
+ except RuntimeError:
24
+ return False
25
+
26
+
27
+ def gimmie_async_loop() -> AbstractEventLoop:
28
+ """Get the current event loop, creating one if it doesn't exist."""
29
+ if in_async_loop():
30
+ return asyncio.get_event_loop()
31
+ loop: AbstractEventLoop = asyncio.new_event_loop()
32
+ asyncio.set_event_loop(loop)
33
+ return loop
@@ -11,7 +11,7 @@ from typing import Any, Literal, Self, overload
11
11
 
12
12
  from pydantic import BaseModel, Field, field_validator
13
13
 
14
- from bear_utils.extras._async_helpers import is_async_function
14
+ from bear_utils.extras._async_helpers import AbstractEventLoop, gimmie_async_loop, in_async_loop, is_async_function
15
15
  from bear_utils.logger_manager import (
16
16
  AsyncLoggerProtocol,
17
17
  LoggerProtocol,
@@ -204,47 +204,43 @@ class FunctionResponse(BaseModel):
204
204
  self.add(content=content, error=error, returncode=returncode or 1, **kwargs)
205
205
  return self
206
206
 
207
- async def _add_item(self, item: str, target_list: list[str]) -> None:
207
+ def _add_item(self, item: str, target_list: list[str]) -> None:
208
208
  """Append an item to the target list if not empty."""
209
209
  if item != "":
210
210
  target_list.append(item)
211
211
 
212
- async def _add_to_list(self, items: str | list[str], target_list: list[str], name: str | None = None) -> None:
212
+ def _add_to_list(self, items: str | list[str], target_list: list[str], name: str | None = None) -> None:
213
213
  """Append items to the target list with optional name prefix."""
214
214
  try:
215
215
  if isinstance(items, list):
216
216
  for item in items:
217
- await self._add_item(f"{name}: {item}" if name else item, target_list)
217
+ self._add_item(f"{name}: {item}" if name else item, target_list)
218
218
  elif isinstance(items, str):
219
- await self._add_item(f"{name}: {items}" if name else items, target_list)
219
+ self._add_item(f"{name}: {items}" if name else items, target_list)
220
220
  except Exception as e:
221
221
  raise ValueError(f"Failed to add items: {e!s}") from e
222
222
 
223
- async def _add_content(self, content: str | list[str], name: str | None = None) -> None:
223
+ def _add_content(self, content: str | list[str], name: str | None = None) -> None:
224
224
  """Append content to the existing content."""
225
- await self._add_to_list(content, self.content, name)
225
+ self._add_to_list(content, self.content, name)
226
226
 
227
- async def _add_error(self, error: str | list[str], name: str | None = None) -> None:
227
+ def _add_error(self, error: str | list[str], name: str | None = None) -> None:
228
228
  """Append error to the existing error."""
229
- await self._add_to_list(error, self.error, name)
229
+ self._add_to_list(error, self.error, name)
230
230
 
231
- async def _handle_function_response(self, func_response: FunctionResponse) -> None:
231
+ def _handle_function_response(self, func_response: FunctionResponse) -> None:
232
232
  """Handle a FunctionResponse object and update the current response."""
233
233
  if func_response.extra:
234
234
  self.extra.update(func_response.extra)
235
- await self._add_error(error=func_response.error, name=func_response.name)
236
- await self._add_content(content=func_response.content, name=func_response.name)
235
+ self._add_error(error=func_response.error, name=func_response.name)
236
+ self._add_content(content=func_response.content, name=func_response.name)
237
237
 
238
- async def _handle_completed_process(self, result: CompletedProcess[str]) -> None:
238
+ def _handle_completed_process(self, result: CompletedProcess[str]) -> None:
239
239
  """Handle a CompletedProcess object and update the FunctionResponse."""
240
- await self._add_content(content=result.stdout.strip() if result.stdout else "")
241
- await self._add_error(error=result.stderr.strip() if result.stderr else "")
240
+ self._add_content(content=result.stdout.strip() if result.stdout else "")
241
+ self._add_error(error=result.stderr.strip() if result.stderr else "")
242
242
  self.returncode = result.returncode
243
243
 
244
- @staticmethod
245
- async def _run_coroutines(coroutines: list[asyncio._CoroutineLike]) -> None:
246
- await asyncio.gather(*coroutines)
247
-
248
244
  def add(
249
245
  self,
250
246
  content: list[str] | str | FunctionResponse | CompletedProcess | None = None,
@@ -254,33 +250,30 @@ class FunctionResponse(BaseModel):
254
250
  extra: dict[str, Any] | None = None,
255
251
  ) -> Self:
256
252
  """Append additional content to the existing content."""
257
- coroutines: list[asyncio._CoroutineLike] = []
258
253
  try:
259
- match content:
260
- case FunctionResponse():
261
- coroutines.append(self._handle_function_response(func_response=content))
262
- self.number_of_tasks += 1
263
- case CompletedProcess():
264
- coroutines.append(self._handle_completed_process(result=content))
265
- self.number_of_tasks += 1
266
- case str() | list() if content:
267
- coroutines.append(self._add_content(content=content))
268
- self.number_of_tasks += 1
269
- if isinstance(error, (str | list)):
270
- coroutines.append(self._add_error(error=error))
254
+ if isinstance(content, FunctionResponse | CompletedProcess | str | list) and isinstance(
255
+ error, (str | list)
256
+ ):
257
+ match content:
258
+ case FunctionResponse():
259
+ self._handle_function_response(func_response=content)
260
+ case CompletedProcess():
261
+ self._handle_completed_process(result=content)
262
+ case str() | list() if content:
263
+ self._add_content(content=content)
264
+ if isinstance(error, (str | list)):
265
+ self._add_error(error=error)
271
266
  if returncode is not None:
272
267
  self.returncode = returncode
273
268
  if isinstance(extra, dict):
274
269
  self.extra.update(extra)
275
270
  if log_output and self.logger and (content or error):
276
- coroutines.append(self._log_handling(content=content, error=error, logger=self.logger))
277
- if coroutines:
278
- asyncio.run(self._run_coroutines(coroutines))
271
+ self._log_handling(content=content, error=error, logger=self.logger)
279
272
  except Exception as e:
280
273
  raise ValueError(f"Failed to add content: {e!s}") from e
281
274
  return self
282
275
 
283
- async def _log_handling(
276
+ def _log_handling(
284
277
  self,
285
278
  content: list[str] | str | FunctionResponse | CompletedProcess | None,
286
279
  error: str | list[str] | None,
@@ -288,21 +281,44 @@ class FunctionResponse(BaseModel):
288
281
  ) -> None:
289
282
  """Log the content and error messages if they exist."""
290
283
 
291
- async def _log_messages(messages: str | list[str], log_func: Callable) -> None:
292
- if isinstance(messages, str):
293
- messages = [messages]
294
- if isinstance(messages, list):
295
- for msg in messages:
296
- if is_async_function(log_func):
297
- await log_func(f"{self.name}: {msg}" if self.name else msg)
298
- else:
299
- log_func(f"{self.name}: {msg}" if self.name else msg)
300
-
301
- if content and isinstance(content, (str | list)):
302
- await _log_messages(content, logger.info)
284
+ async def loop_logging(messages: list[str], func: Callable) -> None:
285
+ for msg in messages:
286
+ if is_async_function(func):
287
+ await func(f"{self.name}: {msg}" if self.name else msg)
288
+ else:
289
+ func(f"{self.name}: {msg}" if self.name else msg)
290
+
291
+ async def _log_messages(
292
+ content: str | list[str], error: str | list[str], info_func: Callable, error_func: Callable
293
+ ) -> None:
294
+ if isinstance(content, str):
295
+ content = [content]
296
+ if isinstance(error, str):
297
+ error = [error]
298
+
299
+ await loop_logging(messages=content, func=info_func)
300
+ await loop_logging(messages=error, func=error_func)
301
+
302
+ if isinstance(content, (FunctionResponse | CompletedProcess | None)):
303
+ content = []
304
+ if not isinstance(error, (list | str)):
305
+ error = []
306
+
307
+ if not content and not error:
308
+ return
303
309
 
304
- if error and isinstance(error, (str | list)):
305
- await _log_messages(error, logger.error)
310
+ before_loop: bool = in_async_loop()
311
+ loop: AbstractEventLoop = gimmie_async_loop()
312
+ task = loop.create_task(
313
+ _log_messages(
314
+ content=content,
315
+ error=error,
316
+ info_func=logger.info,
317
+ error_func=logger.error,
318
+ )
319
+ )
320
+ if task is not None and loop is not None and not before_loop:
321
+ loop.run_until_complete(task)
306
322
 
307
323
  @overload
308
324
  def done(self, to_dict: Literal[True], suppress: list[str] | None = None) -> dict[str, Any]: ...
@@ -361,17 +377,37 @@ def fail(
361
377
  return FunctionResponse().fail(content=content, error=error, returncode=returncode, **kwargs)
362
378
 
363
379
 
380
+ async def testing_already_in_loop() -> None:
381
+ """Test function to check if already in an event loop."""
382
+ # from rich import inspect
383
+
384
+ response = FunctionResponse(name="example_function", logger=SimpleLogger())
385
+ try:
386
+ response.add(content=["Test content", "test 1234"], error="Test error", log_output=True)
387
+ response.done(to_dict=True)
388
+ except RuntimeError as e:
389
+ print(f"Already in an event loop: {e}")
390
+ else:
391
+ print("Successfully added content and error without issues.")
392
+ # inspect(response)
393
+
394
+
395
+ def testing_not_in_loop() -> None:
396
+ """Test function to check if not in an event loop."""
397
+ response = FunctionResponse(name="example_function", logger=SimpleLogger())
398
+ try:
399
+ response.add(content=["Test content", "test 1234"], error="Test error", log_output=True)
400
+ response.done(to_dict=True)
401
+ except RuntimeError as e:
402
+ print(f"Not in an event loop: {e}")
403
+ else:
404
+ print("Successfully added content and error without issues.")
405
+
406
+
364
407
  if __name__ == "__main__":
365
408
  # Example usage
366
- from rich import inspect
367
409
 
368
410
  from bear_utils.logger_manager import SimpleLogger
369
411
 
370
- response = FunctionResponse(name="example_function", logger=SimpleLogger())
371
-
372
- response.add(content="This is a test content.", error="This is an error message.", log_output=True)
373
-
374
- inspect(response)
375
- print(response)
376
- print(response.done(to_dict=True))
377
- print(response.done(to_dict=False))
412
+ asyncio.run(testing_already_in_loop())
413
+ testing_not_in_loop()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bear-utils
3
- Version: 0.8.17
3
+ Version: 0.8.19
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
@@ -22,7 +22,7 @@ Requires-Dist: toml>=0.10.2
22
22
  Requires-Dist: uvicorn>=0.35.0
23
23
  Description-Content-Type: text/markdown
24
24
 
25
- # Bear Utils v# Bear Utils v0.8.17
25
+ # Bear Utils v# Bear Utils v0.8.19
26
26
 
27
27
  Personal set of tools and utilities for Python projects, focusing on modularity and ease of use. This library includes components for caching, database management, logging, time handling, file operations, CLI prompts, image processing, clipboard interaction, gradient utilities, event systems, and async helpers.
28
28
 
@@ -33,11 +33,11 @@ bear_utils/events/__init__.py,sha256=EFqmuzhaEYK9kjkGlrM7bjdjPwFEDbKn6RjJKfIBEJY
33
33
  bear_utils/events/events_class.py,sha256=vPDjWrbut8L3TFn7byyYFZpWYM5ADIqtW2Aeh-qtKfQ,1632
34
34
  bear_utils/events/events_module.py,sha256=rv9NoCDFOaYY70EilrImG9ug90n_wpDBDz4XvxUYqdE,2291
35
35
  bear_utils/extras/__init__.py,sha256=szflSapj7aGFc2j5sTitQFccXu-6_UdGG-eYuv6zVJI,607
36
- bear_utils/extras/_async_helpers.py,sha256=cxq5d24NHkECmZqTVXEazv6K-XUa7skFnX6KQZb0Ycw,411
36
+ bear_utils/extras/_async_helpers.py,sha256=QBB9k2H8MBqcpkrSQv1sVz1c8M0h_J0RjTShoIJOMls,967
37
37
  bear_utils/extras/_tools.py,sha256=-rOH_-0pRd7_-o_l2QqByYBB2yhbXcTC8fuQf4Sx-eg,7671
38
38
  bear_utils/extras/platform_utils.py,sha256=Ai7ow7S-_cKb5zFwFh8dkC8xmbMJFy-0_-w3NCERdEw,1362
39
39
  bear_utils/extras/responses/__init__.py,sha256=XbE4VKemrKRwx9E5jqy__OiM_AAjA58ebnqQ2hytnT0,225
40
- bear_utils/extras/responses/function_response.py,sha256=ZOJzb3N1NmsQHmX5-9MKPlkwbb4LkljQunE1TK5IhsA,15408
40
+ bear_utils/extras/responses/function_response.py,sha256=eCfw_1oWhld8c_GdHSqLiA35oYr8k8asf-XT_baOohM,16508
41
41
  bear_utils/extras/wrappers/__init__.py,sha256=crh4sKOLvuhNMVX5bJYjCFWtXtH7G47UgNPOHq3HXTk,43
42
42
  bear_utils/extras/wrappers/add_methods.py,sha256=z2XZG2ZoYOB1MaGiLli4NRyyTeRgBy7tuYsiy8mTa9s,4422
43
43
  bear_utils/files/__init__.py,sha256=mIdnFSXoDE64ElM43bN2m6KuafURnN82ki0pdqN8q2o,201
@@ -86,6 +86,6 @@ bear_utils/monitoring/__init__.py,sha256=9DKNIWTp_voLnaWgiP-wJ-o_N0hYixo-MzjUmg8
86
86
  bear_utils/monitoring/_common.py,sha256=LYQFxgTP9fk0cH71IQTuGwBYYPWCqHP_mMRNecoD76M,657
87
87
  bear_utils/monitoring/host_monitor.py,sha256=e0TYRJw9iDj5Ga6y3ck1TBFEeH42Cax5mQYaNU8yams,13241
88
88
  bear_utils/time/__init__.py,sha256=VctjJG17SyEHAFXytI1sZrOrq7zm3hVenIDOJFdaMN0,1424
89
- bear_utils-0.8.17.dist-info/METADATA,sha256=95W22Dt-rATJiRWx_UarSfDWp4R1jxlLq75ks3BQLqE,8699
90
- bear_utils-0.8.17.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
91
- bear_utils-0.8.17.dist-info/RECORD,,
89
+ bear_utils-0.8.19.dist-info/METADATA,sha256=LMfD9XfFOivtdM_om_ozrLP0FSPfAEdOT_Izo7tiafY,8699
90
+ bear_utils-0.8.19.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
91
+ bear_utils-0.8.19.dist-info/RECORD,,