Source code for fundamentals.daemonise
#!/usr/bin/env python
# encoding: utf-8
"""
*helper class to daemonise you code*
Author
: David Young
Date Created
: May 23, 2023
"""
from fundamentals import tools
from builtins import object
import sys
import os
os.environ["TERM"] = "vt100"
[docs]
class daemonise:
"""
A class to daemonise a python code
**Key Arguments:**
- ``log`` -- logger
- ``name`` -- a name for the daemon (e.g. python package name)
**Usage:**
Add something like this to your command-line usage:
```text
Usage:
myCommand (start|stop|restart|status)
Options:
start start the myCommand daemon
stop stop the myCommand daemon
restart restart the myCommand daemon
status print the staus of the myCommand daemon
```
and then when executing the commands:
```python
from fundamental import daemonise
class myDaemon(daemonise):
def action(
self,
**kwargs):
self.log.info('starting the ``action`` method')
anotherParameter = kwargs["anotherParameter"]
import time
while True:
print(f"OVERRIDE ACTION - {anotherParameter}")
time.sleep(3)
self.log.info('completed the ``action`` method')
return None
d = myDaemon(log=log, name="gocart", anotherParameter=42.)
if a['start']:
d.start()
elif a['stop']:
d.stop()
elif a['restart']:
d.restart()
elif a['status']:
d.status()
```
Replace `**akws` with any keywords you need.
"""
def __init__(self, log, name, **akws):
self.log = log
log.debug("instantiating a new 'daemonise' object")
# MAKE ROOT DIR
from os.path import expanduser
home = expanduser("~")
self.rootDir = home + f"/.config/{name}/"
if not os.path.exists(self.rootDir):
os.makedirs(self.rootDir)
self.name = name
self.errLog = home + f"/.config/{name}/daemon_err.log"
self.outLog = home + f"/.config/{name}/daemon.log"
self.pidFile = home + f"/.config/{name}/daemon.pid"
self.akws = akws
return
[docs]
def start(self):
"""start the daemonise running"""
self.log.info("starting the ``get`` method")
import time
import signal
from daemon import pidfile
import daemon
running = False
if os.path.exists(self.pidFile):
with open(self.pidFile, mode="r") as f:
pid = int(f.read().strip())
try:
os.kill(pid, 0)
except OSError:
os.remove(self.pidFile)
else:
running = True
print(f"{self.name} daemon is already running (PID = {pid}).")
if not running:
print(f"{self.name} daemon has been started.")
print(f"The daemon logs can be found here: {self.rootDir}")
try:
os.remove(self.outLog)
except:
pass
try:
os.remove(self.errLog)
except:
pass
with daemon.DaemonContext(
working_directory=self.rootDir,
umask=0o002,
pidfile=pidfile.TimeoutPIDLockFile(self.pidFile),
stdout=open(self.outLog, "a"),
stderr=open(self.errLog, "a"),
signal_map={signal.SIGTERM: self.cleanup},
) as context:
self.action(**self.akws)
self.log.info("completed the ``get`` method")
return None
[docs]
def cleanup(self, signum, frame):
"""*the code to run when daemon is killed*"""
self.log.debug("starting the ``cleanup`` method")
from datetime import datetime, date, time
now = datetime.now()
now = now.strftime("%Y%m%dt%H%M%S")
print(f"{self.name} daemon stopped at {now}")
sys.exit(0)
self.log.debug("completed the ``cleanup`` method")
return None
[docs]
def action(self, **akws):
"""*the code to execute in daemon mode, this method should be overriden to execute novel code*"""
self.log.debug("starting the ``action`` method")
import time
while True:
print("ACTION")
time.sleep(3)
self.log.debug("completed the ``action`` method")
return None
[docs]
def stop(self):
"""*stop the daemon and cleanup*"""
self.log.debug("starting the ``stop`` method")
import signal
if os.path.exists(self.pidFile):
with open(self.pidFile, mode="r") as f:
pid = f.read().strip()
try:
os.kill(int(pid), signal.SIGTERM)
except:
os.remove(self.pidFile)
print(f"{self.name} daemon has been stopped.")
else:
print(f"{self.name} daemon is not running.")
self.log.debug("completed the ``stop`` method")
return None
[docs]
def status(self):
"""*print the status of the daemon*"""
self.log.debug("starting the ``status`` method")
if os.path.exists(self.pidFile):
with open(self.pidFile, mode="r") as f:
pid = int(f.read().strip())
try:
os.kill(pid, 0)
except OSError:
os.remove(self.pidFile)
print(f"{self.name} daemon is not running.")
else:
print(f"{self.name} daemon is running (PID = {pid}).")
else:
print(f"{self.name} daemon is not running.")
self.log.debug("completed the ``status`` method")
return None
[docs]
def restart(self):
"""*stop and start the daemon*"""
self.log.debug("starting the ``status`` method")
import time
self.stop()
iteration = 0
while os.path.exists(self.pidFile) and iteration < 15:
iteration += 1
time.sleep(2)
self.start()
self.log.debug("completed the ``status`` method")
return None