bear-utils 0.8.21__py3-none-any.whl → 0.8.23__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.
- bear_utils/database/_db_manager.py +9 -16
- bear_utils/extras/_async_helpers.py +30 -1
- bear_utils/extras/responses/function_response.py +61 -33
- bear_utils/gui/gui_tools/qt_app.py +1 -1
- {bear_utils-0.8.21.dist-info → bear_utils-0.8.23.dist-info}/METADATA +2 -2
- {bear_utils-0.8.21.dist-info → bear_utils-0.8.23.dist-info}/RECORD +7 -7
- {bear_utils-0.8.21.dist-info → bear_utils-0.8.23.dist-info}/WHEEL +0 -0
@@ -32,42 +32,35 @@ class DatabaseManager:
|
|
32
32
|
raise ValueError("Base class is not set, failed to set base.")
|
33
33
|
return cls._base
|
34
34
|
|
35
|
-
def __init__(self, db_url: str | Path | None = None):
|
35
|
+
def __init__(self, db_url: str | Path | None = None, default_schema: str = "sqlite:///"):
|
36
36
|
if db_url is None or db_url == "":
|
37
37
|
raise ValueError("Database URL cannot be None or empty.")
|
38
|
-
if isinstance(db_url, str) and not db_url.startswith(
|
39
|
-
db_url = f"
|
38
|
+
if isinstance(db_url, str) and not db_url.startswith(default_schema):
|
39
|
+
db_url = f"{default_schema}{db_url}"
|
40
40
|
self.db_url: str = str(db_url)
|
41
41
|
self.engine: Engine = create_engine(self.db_url, echo=False)
|
42
42
|
base: DeclarativeMeta = DatabaseManager.get_base()
|
43
43
|
self.metadata: MetaData = base.metadata
|
44
|
-
self.SessionFactory = sessionmaker(bind=self.engine)
|
45
|
-
self.session = scoped_session(self.SessionFactory)
|
44
|
+
self.SessionFactory: sessionmaker[Session] = sessionmaker(bind=self.engine)
|
45
|
+
self.session: scoped_session[Session] = scoped_session(self.SessionFactory)
|
46
46
|
atexit.register(self.close_all)
|
47
47
|
self.create_tables()
|
48
48
|
|
49
49
|
def get_all_records(self, table_obj: type[TableType]) -> list[TableType]:
|
50
50
|
"""Get all records from a table."""
|
51
|
-
|
52
|
-
return session.query(table_obj).all()
|
51
|
+
return self.session().query(table_obj).all()
|
53
52
|
|
54
53
|
def count_records(self, table_obj: type[TableType]) -> int:
|
55
54
|
"""Count the number of records in a table."""
|
56
|
-
|
57
|
-
count: int = session.query(table_obj).count()
|
58
|
-
return count
|
55
|
+
return self.session().query(table_obj).count()
|
59
56
|
|
60
57
|
def get_records_by_var(self, table_obj: type[TableType], variable: str, value: str) -> list[TableType]:
|
61
58
|
"""Get records from a table by a specific variable."""
|
62
|
-
|
63
|
-
records: list[TableType] = session.query(table_obj).filter(getattr(table_obj, variable) == value).all()
|
64
|
-
return records
|
59
|
+
return self.session().query(table_obj).filter(getattr(table_obj, variable) == value).all()
|
65
60
|
|
66
61
|
def count_records_by_var(self, table_obj: type[TableType], variable: str, value: str) -> int:
|
67
62
|
"""Count the number of records in a table by a specific variable."""
|
68
|
-
|
69
|
-
count: int = session.query(table_obj).filter(getattr(table_obj, variable) == value).count()
|
70
|
-
return count
|
63
|
+
return self.session().query(table_obj).filter(getattr(table_obj, variable) == value).count()
|
71
64
|
|
72
65
|
@contextmanager
|
73
66
|
def open_session(self) -> Generator[Session, Any]:
|
@@ -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,22 +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
|
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
|
18
13
|
|
19
|
-
|
20
|
-
|
14
|
+
# Pydantic will yell if we put this into a TYPE_CHECKING block.
|
15
|
+
from bear_utils.logger_manager import AsyncLoggerProtocol, LoggerProtocol # noqa: TC001
|
16
|
+
|
17
|
+
if TYPE_CHECKING:
|
18
|
+
from collections.abc import Callable
|
19
|
+
|
20
|
+
|
21
|
+
SUCCESS: list[str] = ["name", "success", "number_of_tasks"]
|
22
|
+
FAILURE: list[str] = ["name", "number_of_tasks"]
|
21
23
|
|
22
24
|
|
23
25
|
class FunctionResponse(BaseModel):
|
@@ -240,6 +242,19 @@ class FunctionResponse(BaseModel):
|
|
240
242
|
self._add_error(error=result.stderr.strip() if result.stderr else "")
|
241
243
|
self.returncode = result.returncode
|
242
244
|
|
245
|
+
def _handle_content(self, content: list[str] | str | FunctionResponse | CompletedProcess | Any) -> None:
|
246
|
+
"""Handle different types of content and update the FunctionResponse."""
|
247
|
+
if isinstance(content, FunctionResponse):
|
248
|
+
self._handle_function_response(func_response=content)
|
249
|
+
elif isinstance(content, CompletedProcess):
|
250
|
+
self._handle_completed_process(result=content)
|
251
|
+
elif isinstance(content, (str | list)):
|
252
|
+
self._add_content(content=content)
|
253
|
+
else:
|
254
|
+
return
|
255
|
+
self.number_of_tasks += 1
|
256
|
+
|
257
|
+
@overload
|
243
258
|
def add(
|
244
259
|
self,
|
245
260
|
content: list[str] | str | FunctionResponse | CompletedProcess | None = None,
|
@@ -247,19 +262,36 @@ class FunctionResponse(BaseModel):
|
|
247
262
|
returncode: int | None = None,
|
248
263
|
log_output: bool = False,
|
249
264
|
extra: dict[str, Any] | None = None,
|
250
|
-
|
265
|
+
*,
|
266
|
+
to_dict: Literal[True],
|
267
|
+
) -> dict[str, Any]: ...
|
268
|
+
|
269
|
+
@overload
|
270
|
+
def add(
|
271
|
+
self,
|
272
|
+
content: list[str] | str | FunctionResponse | CompletedProcess | None = None,
|
273
|
+
error: str | list[str] | None = None,
|
274
|
+
returncode: int | None = None,
|
275
|
+
log_output: bool = False,
|
276
|
+
extra: dict[str, Any] | None = None,
|
277
|
+
*,
|
278
|
+
to_dict: Literal[False] = False,
|
279
|
+
) -> Self: ...
|
280
|
+
|
281
|
+
def add(
|
282
|
+
self,
|
283
|
+
content: list[str] | str | FunctionResponse | CompletedProcess | None = None,
|
284
|
+
error: str | list[str] | None = None,
|
285
|
+
returncode: int | None = None,
|
286
|
+
log_output: bool = False,
|
287
|
+
extra: dict[str, Any] | None = None,
|
288
|
+
*,
|
289
|
+
to_dict: bool = False,
|
290
|
+
) -> Self | dict[str, Any]:
|
251
291
|
"""Append additional content to the existing content."""
|
252
292
|
try:
|
253
293
|
if content is not None:
|
254
|
-
|
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
|
294
|
+
self._handle_content(content=content)
|
263
295
|
if error is not None and isinstance(error, (str | list)):
|
264
296
|
self._add_error(error=error)
|
265
297
|
if isinstance(returncode, int):
|
@@ -270,6 +302,8 @@ class FunctionResponse(BaseModel):
|
|
270
302
|
self._log_handling(content=content, error=error, logger=self.logger)
|
271
303
|
except Exception as e:
|
272
304
|
raise ValueError(f"Failed to add content: {e!s}") from e
|
305
|
+
if to_dict:
|
306
|
+
return self.done(to_dict=True)
|
273
307
|
return self
|
274
308
|
|
275
309
|
def _log_handling(
|
@@ -304,22 +338,16 @@ class FunctionResponse(BaseModel):
|
|
304
338
|
content = []
|
305
339
|
if not isinstance(error, (list | str)):
|
306
340
|
error = []
|
307
|
-
|
308
341
|
if not content and not error:
|
309
342
|
return
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
error=error,
|
317
|
-
info_func=logger.info,
|
318
|
-
error_func=logger.error,
|
319
|
-
)
|
343
|
+
res: AsyncResponseModel = create_async_task(
|
344
|
+
_log_messages,
|
345
|
+
content=content,
|
346
|
+
error=error,
|
347
|
+
info_func=logger.info,
|
348
|
+
error_func=logger.error,
|
320
349
|
)
|
321
|
-
|
322
|
-
loop.run_until_complete(task)
|
350
|
+
res.conditional_run()
|
323
351
|
|
324
352
|
@overload
|
325
353
|
def done(self, to_dict: Literal[True], suppress: list[str] | None = None) -> dict[str, Any]: ...
|
@@ -352,7 +380,7 @@ class FunctionResponse(BaseModel):
|
|
352
380
|
add("name", self.name, bool(self.name))
|
353
381
|
add("success", self.success)
|
354
382
|
add("returncode", self.returncode, self.returncode > 0)
|
355
|
-
add("number_of_tasks", self.number_of_tasks, self.number_of_tasks > 0)
|
383
|
+
add("number_of_tasks", self.number_of_tasks, (self.number_of_tasks > 0 and not self.success))
|
356
384
|
add("content", self.content, bool(self.content))
|
357
385
|
add("error", self.error, bool(self.error))
|
358
386
|
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.
|
3
|
+
Version: 0.8.23
|
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.
|
27
|
+
# Bear Utils v# Bear Utils v0.8.23
|
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
|
|
@@ -28,16 +28,16 @@ bear_utils/constants/date_related.py,sha256=vwgPPGamVJ8p12iRFu3IzTksmGfhqYULwrk-
|
|
28
28
|
bear_utils/constants/server.py,sha256=1gWA3h1HH12uk5nqX3r5EFkffPKVDTo3he51teHSDbk,271
|
29
29
|
bear_utils/constants/time_related.py,sha256=Mykb27THqC-K0HFrbbrdkTNJUKhzBEOfmPJ0rx2-L6E,705
|
30
30
|
bear_utils/database/__init__.py,sha256=dS_HHJKESpsI6BFDLVx055o0GCJV9CMYOQVuHs7EfgY,192
|
31
|
-
bear_utils/database/_db_manager.py,sha256=
|
31
|
+
bear_utils/database/_db_manager.py,sha256=bbg5zG06DcOvd37oafLIqc480BwF9ZW9qWF3ldZHR8o,3805
|
32
32
|
bear_utils/events/__init__.py,sha256=EFqmuzhaEYK9kjkGlrM7bjdjPwFEDbKn6RjJKfIBEJY,414
|
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=
|
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=
|
40
|
+
bear_utils/extras/responses/function_response.py,sha256=aGUhxfliaqE2tXKGtVPDeOy5JJRjHN5M9gRgI1IsopY,16257
|
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=
|
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.
|
90
|
-
bear_utils-0.8.
|
91
|
-
bear_utils-0.8.
|
89
|
+
bear_utils-0.8.23.dist-info/METADATA,sha256=paN99eKZqMAyoDD6z25V7EFeHK1IcDaAvRwXBQPjyJ8,8763
|
90
|
+
bear_utils-0.8.23.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
91
|
+
bear_utils-0.8.23.dist-info/RECORD,,
|
File without changes
|