Skip to content

Commit 70ce9f3

Browse files
committed
2. add morebuiltins.funcs.debounce decorator to debounce function calls.
1 parent f5cacc5 commit 70ce9f3

File tree

5 files changed

+122
-34
lines changed

5 files changed

+122
-34
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
### 1.3.5 (2025-10-15)
44
1. fix writer.wait_closed() ConnectionResetError in ipc.py, proxy_checker.py, log_server.py
5-
2.
5+
2. add `morebuiltins.funcs.debounce` decorator to debounce function calls.
66

77
### 1.3.4 (2025-10-15)
88
1. fix `morebuiltins.utils.gen_id` wrong length issue.

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,8 @@ print(morebuiltins.__file__)
178178

179179
3.12 `check_recursion` - Check if a function is recursive by inspecting its AST.
180180

181+
3.13 `debounce` - Debounce a function, delaying its execution until after a specified wait time.
182+
181183

182184
## 4. morebuiltins.ipc
183185

doc.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1796,6 +1796,34 @@ Demo::
17961796
---
17971797

17981798

1799+
1800+
3.13 `debounce` - Debounce a function, delaying its execution until after a specified wait time.
1801+
1802+
1803+
```python
1804+
1805+
Args:
1806+
wait (float): The amount of time to wait before executing the function.
1807+
1808+
Demo::
1809+
1810+
>>> @debounce(0.1)
1811+
... def test():
1812+
... print("Function executed")
1813+
>>> test()
1814+
Function executed
1815+
>>> test()
1816+
>>> time.sleep(0.1)
1817+
>>> test()
1818+
Function executed
1819+
>>> test()
1820+
1821+
```
1822+
1823+
1824+
---
1825+
1826+
17991827
## 4. morebuiltins.ipc
18001828

18011829

morebuiltins/cmd/log_server.py

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,18 @@ def from_dict(cls, **kwargs):
115115
kwargs = {k: v for k, v in kwargs.items() if k in cls.__annotations__}
116116
return cls(**kwargs)
117117

118+
@staticmethod
119+
def level_to_name(level: int):
120+
levelToName = {
121+
logging.CRITICAL: "CRITICAL",
122+
logging.ERROR: "ERROR",
123+
logging.WARNING: "WARNING",
124+
logging.INFO: "INFO",
125+
logging.DEBUG: "DEBUG",
126+
logging.NOTSET: "NOTSET",
127+
}
128+
return levelToName.get(level, f"Level {level}")
129+
118130
def to_dict_with_meta(self) -> dict:
119131
meta: dict = {
120132
"create_time": self.create_time,
@@ -125,9 +137,7 @@ def to_dict_with_meta(self) -> dict:
125137
# base64 formatter
126138
meta["formatter"] = self.pickle_to_base64(self.formatter)
127139
# int to str
128-
meta["level_specs"] = [
129-
logging.getLevelName(level) for level in self.level_specs
130-
]
140+
meta["level_specs"] = [self.level_to_name(level) for level in self.level_specs]
131141
return meta
132142

133143
def __eq__(self, other):
@@ -392,7 +402,7 @@ def send_log(
392402
"args": (),
393403
"msg": msg,
394404
"levelno": level,
395-
"levelname": logging.getLevelName(level),
405+
"levelname": LogSetting.level_to_name(level),
396406
"exc_info": {},
397407
}
398408
if init_setting:
@@ -565,7 +575,9 @@ def write_queue_consumer(self):
565575
if setting.level_specs:
566576
for levelno in setting.level_specs:
567577
levelname = (
568-
logging.getLevelName(levelno).lower().replace(" ", "-")
578+
LogSetting.level_to_name(levelno)
579+
.lower()
580+
.replace(" ", "-")
569581
)
570582
alias_name = f"{name}_{levelname}"
571583
targets.extend(
@@ -903,7 +915,7 @@ def sync_test():
903915

904916

905917
def entrypoint():
906-
# return sync_test()
918+
return sync_test()
907919
return asyncio.run(main())
908920

909921

morebuiltins/funcs.py

Lines changed: 73 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
Optional,
2323
OrderedDict,
2424
Set,
25-
Tuple,
2625
Union,
2726
)
2827
from weakref import WeakSet
@@ -40,6 +39,7 @@
4039
"get_function",
4140
"to_thread",
4241
"check_recursion",
42+
"debounce",
4343
]
4444

4545

@@ -893,32 +893,32 @@ def calculate_and_print_stats(self, func_name: str):
893893
def line_profiler(func: Callable) -> Callable:
894894
"""Decorator to profile a function line-by-line.
895895
896-
Demo usage:
897-
>>> import sys, io
898-
>>> LineProfiler.stdout = io.StringIO() # Redirect stdout to capture print output
899-
>>> @line_profiler
900-
... def example_function():
901-
... result = 0
902-
... for i in range(10):
903-
... result += i # Simulate some work
904-
... return result
905-
>>> example_function()
906-
45
907-
>>> output = LineProfiler.stdout.getvalue()
908-
>>> output.splitlines()[0].startswith("=" * 95) and "`example_function` profiling report:" in output
909-
True
910-
>>> LineProfiler.stdout = sys.stdout # Restore original stdout
911-
912-
===============================================================================================
913-
`example_function` profiling report:
914-
Start at 2025-07-26 17:09:58 | Total: 1.122 ms
915-
===============================================================================================
916-
Line % Total(ms) Count Avg(ms) Source Code
917-
-----------------------------------------------------------------------------------------------
918-
3 73 0.825 1 0.825 -
919-
4 3 0.040 11 0.004 -
920-
5 4 0.050 10 0.005 -
921-
===============================================================================================
896+
Demo usage:
897+
>>> import sys, io
898+
>>> LineProfiler.stdout = io.StringIO() # Redirect stdout to capture print output
899+
>>> @line_profiler
900+
... def example_function():
901+
... result = 0
902+
... for i in range(10):
903+
... result += i # Simulate some work
904+
... return result
905+
>>> example_function()
906+
45
907+
>>> output = LineProfiler.stdout.getvalue()
908+
>>> output.splitlines()[0].startswith("=" * 95) and "`example_function` profiling report:" in output
909+
True
910+
>>> LineProfiler.stdout = sys.stdout # Restore original stdout
911+
912+
===============================================================================================
913+
`example_function` profiling report:
914+
Start at 2025-07-26 17:09:58 | Total: 1.122 ms
915+
===============================================================================================
916+
Line % Total(ms) Count Avg(ms) Source Code
917+
-----------------------------------------------------------------------------------------------
918+
3 73 0.825 1 0.825 -
919+
4 3 0.040 11 0.004 -
920+
5 4 0.050 10 0.005 -
921+
===============================================================================================
922922
"""
923923

924924
@wraps(func)
@@ -946,6 +946,52 @@ def wrapper(*args, **kwargs):
946946
return wrapper
947947

948948

949+
def debounce(wait: float):
950+
"""Debounce a function, delaying its execution until after a specified wait time.
951+
952+
Args:
953+
wait (float): The amount of time to wait before executing the function.
954+
955+
Demo::
956+
957+
>>> @debounce(0.1)
958+
... def test():
959+
... print("Function executed")
960+
>>> test()
961+
Function executed
962+
>>> test()
963+
>>> time.sleep(0.1)
964+
>>> test()
965+
Function executed
966+
>>> test()
967+
"""
968+
def decorator(fn):
969+
last_call = 0
970+
971+
async def async_wrapper(*args, **kwargs):
972+
nonlocal last_call
973+
current_time = time.time()
974+
if current_time - last_call < wait:
975+
return
976+
last_call = current_time
977+
return await fn(*args, **kwargs)
978+
979+
def sync_wrapper(*args, **kwargs):
980+
nonlocal last_call
981+
current_time = time.time()
982+
if current_time - last_call < wait:
983+
return
984+
last_call = current_time
985+
return fn(*args, **kwargs)
986+
987+
if asyncio.iscoroutinefunction(fn):
988+
return async_wrapper
989+
else:
990+
return sync_wrapper
991+
992+
return decorator
993+
994+
949995
def test_bg_task():
950996
async def _test_bg_task():
951997
async def coro():

0 commit comments

Comments
 (0)