Publish a first version of battery_guard on PyPI
This commit is contained in:
commit
f7d946b4a5
6 files changed
+251
No files matched your search
@@ -0,0 +1,110 @@
|
||||
import functools
|
||||
import logging
|
||||
import time
|
||||
|
||||
try:
|
||||
import psutil
|
||||
except ImportError:
|
||||
psutil = None
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _check_arguments(minimum, resume, wait_interval):
|
||||
if resume is None:
|
||||
resume = minimum
|
||||
if not 0 <= minimum <= 100:
|
||||
raise ValueError(f"minimum ({minimum}) must be between 0 and 100")
|
||||
if not minimum <= resume <= 100:
|
||||
raise ValueError(f"resume ({resume}) must be between minimum ({minimum}) and 100")
|
||||
if wait_interval <= 0:
|
||||
raise ValueError(f"wait_interval ({wait_interval}) must be positive")
|
||||
return resume
|
||||
|
||||
|
||||
def wait_for_battery(minimum=20, resume=None, wait_interval=5):
|
||||
"""
|
||||
Return right away if the battery is at least `minimum` percent.
|
||||
|
||||
If it is below `minimum`, block until it has charged back up to `resume`
|
||||
percent (defaults to `minimum`). While blocking, it checks the battery every
|
||||
`wait_interval` seconds (5 by default).
|
||||
|
||||
This is useful where there is no function to decorate, for example in a notebook cell.
|
||||
See `battery_guard` for the decorator version and advice on choosing `minimum`.
|
||||
"""
|
||||
resume = _check_arguments(minimum, resume, wait_interval)
|
||||
if psutil is None:
|
||||
logger.warning("Missing psutil library, not sleeping because battery percentage is unknown")
|
||||
return
|
||||
waited = False
|
||||
while True:
|
||||
try:
|
||||
battery = psutil.sensors_battery()
|
||||
if battery is None:
|
||||
logger.debug("psutil.sensors_battery() reports no battery, resuming")
|
||||
return
|
||||
threshold = resume if waited else minimum
|
||||
if battery.percent >= threshold: # noqa, None comparison can't be reached because of the return above
|
||||
if waited:
|
||||
logger.info("Battery at %s%%, resuming", battery.percent)
|
||||
return
|
||||
if not waited:
|
||||
logger.info("Battery at %s%%, waiting until it reaches %s%%",
|
||||
battery.percent, resume)
|
||||
waited = True
|
||||
logger.debug("Battery at %s%%, still waiting", battery.percent)
|
||||
time.sleep(wait_interval)
|
||||
except Exception: # noqa: unsupported platform / sensor read failure / who knows -> log it!
|
||||
logger.warning("Could not read battery status, not sleeping", exc_info=True)
|
||||
return
|
||||
|
||||
|
||||
def battery_guard(func=None, *, minimum=20, resume=None, wait_interval=5):
|
||||
"""
|
||||
Call the function right away if the battery is at least `minimum` percent.
|
||||
|
||||
If it is below `minimum`, block until it has charged back up to `resume`
|
||||
percent (defaults to `minimum`), then call the function.
|
||||
By default it will check every 5 seconds (`wait_interval`) before it continues.
|
||||
|
||||
The battery is only checked when the decorated function is called, a call that
|
||||
is already running is never paused. It therefore works best on a function that
|
||||
is called repeatedly, like the body of a loop that processes one item at a time,
|
||||
rather than on one long-running function. To block without decorating anything,
|
||||
use `wait_for_battery`.
|
||||
|
||||
Note that battery charging slows down when it's near a full charge.
|
||||
It also may produce more heat and consume more power.
|
||||
Therefore it's probably better to not set minimum too high.
|
||||
The recommended range for minimum is between 20 and 80 percent.
|
||||
|
||||
There are valid reasons to set minimum higher though, you simply might want
|
||||
to be able to take it with you and expect it to be charged as much as possible.
|
||||
Or maybe you want a heavy background task to pause once you leave the office,
|
||||
and the battery dipping below 95% is the indication that you have left for the train.
|
||||
|
||||
Setting it to 100% might also give unexpected results, because some battery management
|
||||
systems will not start charging until the reported percentage drops below 95% for example.
|
||||
The percentage psutil.sensors_battery() reports also isn't necessarily the same as the
|
||||
percentage displayed in your system tray.
|
||||
|
||||
This method does not consider whether a charger is plugged in. That is on purpose.
|
||||
The reason it exists is to deal with chargers that are too slow; a situation
|
||||
where battery percentage decreases despite having a charger plugged in.
|
||||
|
||||
Usable as:
|
||||
@battery_guard
|
||||
@battery_guard(minimum=20, resume=25, wait_interval=30)
|
||||
battery_guard(some_func, minimum=50)(arg1, arg2)
|
||||
"""
|
||||
resume = _check_arguments(minimum, resume, wait_interval)
|
||||
|
||||
def decorator(f):
|
||||
@functools.wraps(f)
|
||||
def wrapper(*args, **kwargs):
|
||||
wait_for_battery(minimum=minimum, resume=resume, wait_interval=wait_interval)
|
||||
return f(*args, **kwargs)
|
||||
return wrapper
|
||||
|
||||
return decorator if func is None else decorator(func)
|
||||
Reference in new issue
Block a user