bear-utils 0.8.21__py3-none-any.whl → 0.8.22__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,9 +1,26 @@
1
1
  import asyncio
2
- from asyncio import AbstractEventLoop
2
+ from asyncio import AbstractEventLoop, Task
3
3
  from collections.abc import Callable
4
4
  from contextlib import suppress
5
5
  import inspect
6
6
 
7
+ from pydantic import BaseModel, Field
8
+
9
+
10
+ class AsyncResponseModel(BaseModel):
11
+ """A model to handle asynchronous operations with a function and its arguments."""
12
+
13
+ loop: AbstractEventLoop | None = Field(default=None, description="The event loop to run the function in.")
14
+ task: Task | None = Field(default=None, description="The task created for the asynchronous function.")
15
+ before_loop: bool = Field(default=False, description="If the function was called from a running loop.")
16
+
17
+ model_config = {"arbitrary_types_allowed": True}
18
+
19
+ def conditional_run(self) -> None:
20
+ """Run the event loop until the task is complete if not in a running loop."""
21
+ if self.loop and self.task and not self.before_loop:
22
+ self.loop.run_until_complete(self.task)
23
+
7
24
 
8
25
  def is_async_function(func: Callable) -> bool:
9
26
  """Check if a function is asynchronous.
@@ -36,3 +53,15 @@ def gimmie_async_loop() -> AbstractEventLoop:
36
53
  loop: AbstractEventLoop = asyncio.new_event_loop()
37
54
  asyncio.set_event_loop(loop)
38
55
  return loop
56
+
57
+
58
+ def create_async_task(
59
+ func: Callable,
60
+ *args,
61
+ **kwargs,
62
+ ) -> AsyncResponseModel:
63
+ """Create an asyncio task for a given function."""
64
+ before_loop: bool = in_async_loop()
65
+ loop: AbstractEventLoop = gimmie_async_loop()
66
+ task = loop.create_task(func(*args, **kwargs))
67
+ return AsyncResponseModel(loop=loop, task=task, before_loop=before_loop)
@@ -2,23 +2,24 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from collections.abc import Callable
6
5
  import json
7
6
  from subprocess import CompletedProcess
8
7
  from types import SimpleNamespace as Namespace
9
- from typing import Any, Literal, Self, overload
8
+ from typing import TYPE_CHECKING, Any, Literal, Self, overload
10
9
 
11
10
  from pydantic import BaseModel, Field, field_validator
12
11
 
13
- from bear_utils.extras._async_helpers import AbstractEventLoop, gimmie_async_loop, in_async_loop, is_async_function
14
- from bear_utils.logger_manager import (
15
- AsyncLoggerProtocol,
16
- LoggerProtocol,
17
- )
12
+ from bear_utils.extras._async_helpers import AsyncResponseModel, create_async_task, is_async_function
13
+
14
+ # Pydantic will yell if we put this into a TYPE_CHECKING block.
15
+ from bear_utils.logger_manager import AsyncLoggerProtocol, LoggerProtocol # noqa: TC001
18
16
 
19
17
  SUCCESS: list[str] = ["name", "success"]
20
18
  FAILURE: list[str] = ["name"]
21
19
 
20
+ if TYPE_CHECKING:
21
+ from collections.abc import Callable
22
+
22
23
 
23
24
  class FunctionResponse(BaseModel):
24
25
  """A class to represent the response of a function call, including success status, content, and error messages."""
@@ -240,6 +241,19 @@ class FunctionResponse(BaseModel):
240
241
  self._add_error(error=result.stderr.strip() if result.stderr else "")
241
242
  self.returncode = result.returncode
242
243
 
244
+ def _handle_content(self, content: list[str] | str | FunctionResponse | CompletedProcess | Any) -> None:
245
+ """Handle different types of content and update the FunctionResponse."""
246
+ if isinstance(content, FunctionResponse):
247
+ self._handle_function_response(func_response=content)
248
+ elif isinstance(content, CompletedProcess):
249
+ self._handle_completed_process(result=content)
250
+ elif isinstance(content, (str | list)):
251
+ self._add_content(content=content)
252
+ else:
253
+ return
254
+ self.number_of_tasks += 1
255
+
256
+ @overload
243
257
  def add(
244
258
  self,
245
259
  content: list[str] | str | FunctionResponse | CompletedProcess | None = None,
@@ -247,19 +261,36 @@ class FunctionResponse(BaseModel):
247
261
  returncode: int | None = None,
248
262
  log_output: bool = False,
249
263
  extra: dict[str, Any] | None = None,
250
- ) -> Self:
264
+ *,
265
+ to_dict: Literal[True],
266
+ ) -> dict[str, Any]: ...
267
+
268
+ @overload
269
+ def add(
270
+ self,
271
+ content: list[str] | str | FunctionResponse | CompletedProcess | None = None,
272
+ error: str | list[str] | None = None,
273
+ returncode: int | None = None,
274
+ log_output: bool = False,
275
+ extra: dict[str, Any] | None = None,
276
+ *,
277
+ to_dict: Literal[False] = False,
278
+ ) -> Self: ...
279
+
280
+ def add(
281
+ self,
282
+ content: list[str] | str | FunctionResponse | CompletedProcess | None = None,
283
+ error: str | list[str] | None = None,
284
+ returncode: int | None = None,
285
+ log_output: bool = False,
286
+ extra: dict[str, Any] | None = None,
287
+ *,
288
+ to_dict: bool = False,
289
+ ) -> Self | dict[str, Any]:
251
290
  """Append additional content to the existing content."""
252
291
  try:
253
292
  if content is not None:
254
- if isinstance(content, FunctionResponse):
255
- self._handle_function_response(func_response=content)
256
- self.number_of_tasks += 1
257
- elif isinstance(content, CompletedProcess):
258
- self._handle_completed_process(result=content)
259
- self.number_of_tasks += 1
260
- elif isinstance(content, (str | list)) and content:
261
- self._add_content(content=content)
262
- self.number_of_tasks += 1
293
+ self._handle_content(content=content)
263
294
  if error is not None and isinstance(error, (str | list)):
264
295
  self._add_error(error=error)
265
296
  if isinstance(returncode, int):
@@ -270,6 +301,8 @@ class FunctionResponse(BaseModel):
270
301
  self._log_handling(content=content, error=error, logger=self.logger)
271
302
  except Exception as e:
272
303
  raise ValueError(f"Failed to add content: {e!s}") from e
304
+ if to_dict:
305
+ return self.done(to_dict=True)
273
306
  return self
274
307
 
275
308
  def _log_handling(
@@ -308,18 +341,14 @@ class FunctionResponse(BaseModel):
308
341
  if not content and not error:
309
342
  return
310
343
 
311
- before_loop: bool = in_async_loop()
312
- loop: AbstractEventLoop = gimmie_async_loop()
313
- task = loop.create_task(
314
- _log_messages(
315
- content=content,
316
- error=error,
317
- info_func=logger.info,
318
- error_func=logger.error,
319
- )
344
+ res: AsyncResponseModel = create_async_task(
345
+ _log_messages,
346
+ content=content,
347
+ error=error,
348
+ info_func=logger.info,
349
+ error_func=logger.error,
320
350
  )
321
- if task is not None and loop is not None and not before_loop:
322
- loop.run_until_complete(task)
351
+ res.conditional_run()
323
352
 
324
353
  @overload
325
354
  def done(self, to_dict: Literal[True], suppress: list[str] | None = None) -> dict[str, Any]: ...
@@ -352,7 +381,7 @@ class FunctionResponse(BaseModel):
352
381
  add("name", self.name, bool(self.name))
353
382
  add("success", self.success)
354
383
  add("returncode", self.returncode, self.returncode > 0)
355
- add("number_of_tasks", self.number_of_tasks, self.number_of_tasks > 0)
384
+ add("number_of_tasks", self.number_of_tasks, (self.number_of_tasks > 0 and not self.success))
356
385
  add("content", self.content, bool(self.content))
357
386
  add("error", self.error, bool(self.error))
358
387
  result.update(self.extra)
@@ -36,7 +36,7 @@ class QTApplication(QObject):
36
36
  self.app.setOrganizationDomain(org_domain)
37
37
  else:
38
38
  self.app = QApplication.instance()
39
- self.console = ConsoleLogger.get_instance(init=True, name=app_name, level=VERBOSE)
39
+ self.console: ConsoleLogger = ConsoleLogger.get_instance(init=True, name=app_name, level=VERBOSE)
40
40
  atexit.register(self.cleanup)
41
41
 
42
42
  def _default_exit_shortcuts(self) -> None:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bear-utils
3
- Version: 0.8.21
3
+ Version: 0.8.22
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
@@ -24,7 +24,7 @@ Provides-Extra: gui
24
24
  Requires-Dist: pyqt6>=6.9.0; extra == 'gui'
25
25
  Description-Content-Type: text/markdown
26
26
 
27
- # Bear Utils v# Bear Utils v0.8.21
27
+ # Bear Utils v# Bear Utils v0.8.22
28
28
 
29
29
  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.
30
30
 
@@ -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=DkQsDZ5WzM1MH1Msg7mRny40a17Jl31CXbpw4-pICxc,2349
35
35
  bear_utils/extras/__init__.py,sha256=szflSapj7aGFc2j5sTitQFccXu-6_UdGG-eYuv6zVJI,607
36
- bear_utils/extras/_async_helpers.py,sha256=JNzUferzvTf98ocGyHE1jTzeI0_PzskRXwwYsx743Pw,1132
36
+ bear_utils/extras/_async_helpers.py,sha256=4buULZZkR0n2z13Q8_FK59dMchj0Mup03YBaqSCbveQ,2291
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=yzZCBAIvjJbq1jBTU9X31FTc5xD_dIBSE0kuFa5YKq4,15333
40
+ bear_utils/extras/responses/function_response.py,sha256=VFhYGoSXbqdkgeBWYdmHcHcJ2Z0BFg_Y8EmdkaF442Y,16220
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
@@ -57,7 +57,7 @@ bear_utils/gui/__init__.py,sha256=z_rOZ-nN3E6Hd_nGD6SdDVqE61VUk2OOogI6U89nkkA,34
57
57
  bear_utils/gui/gui_tools/__init__.py,sha256=cD6cKxU1cmKDVaBRT8KsqsCbulf6TUNAmVr50XGPpo8,446
58
58
  bear_utils/gui/gui_tools/_settings.py,sha256=xSQ7I-axAifZNvEw_28mnFBFYIJd4xFuDpycFFQLib0,1201
59
59
  bear_utils/gui/gui_tools/_types.py,sha256=krguJ-ccALKeUHz9auh_iyOCzeAuerOYcuhWW8jjJQ0,248
60
- bear_utils/gui/gui_tools/qt_app.py,sha256=hjZtHNJkCcpfT_KkUV35aa9y92ejEhpAV3AFgd27IbY,5865
60
+ bear_utils/gui/gui_tools/qt_app.py,sha256=sxBLcPgy2r9coFqjN3wnCoFKdyvCWvHIJfb64NHtJOs,5880
61
61
  bear_utils/gui/gui_tools/qt_color_picker.py,sha256=5NtLiBHk5r9Goma_oiymriH49D_JGIk844p4Hsi51io,4744
62
62
  bear_utils/gui/gui_tools/qt_file_handler.py,sha256=FgWdS-9WnjVuyGIC8V30ByDCBeJGZKGc8KRTy34SFfI,4404
63
63
  bear_utils/gui/gui_tools/qt_input_dialog.py,sha256=5KaCM9q8kmoy-Fd0j1FbXIVrLlE7W47NEGdhsWtvKwQ,9281
@@ -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.21.dist-info/METADATA,sha256=40Y3kA8qpdI_LU3UFuHntvEdmuXCySQ99s1Y6IYDig8,8763
90
- bear_utils-0.8.21.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
91
- bear_utils-0.8.21.dist-info/RECORD,,
89
+ bear_utils-0.8.22.dist-info/METADATA,sha256=bI6HOJaPlDs8ml0B_n-if7qmczI67TKOllT-6JkXXhY,8763
90
+ bear_utils-0.8.22.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
91
+ bear_utils-0.8.22.dist-info/RECORD,,