strix

A simple web UI for motion
git clone https://www.brianlane.com/git/strix
Log | Files | Refs | LICENSE

commit c45bf0cdd0865168699997e4f8e493da59fd29e9
parent 7c3d854058c815df87f1db01e94ee5a3f60e8a37
Author: Brian C. Lane <bcl@brianlane.com>
Date:   Sun, 20 Mar 2022 12:21:06 -0700

mypy: Drop attempts to use mypy types

I'd rather focus on unit testing and coverage than fighting with a
bolted-on type system.

This removes the types, removes mypy from tox, and also makes tox
install the test dependencies from setup.cfg

Diffstat:
Msetup.cfg | 5+----
Msrc/strix/__init__.py | 16+++++++---------
Msrc/strix/api.py | 12+++++++-----
Msrc/strix/cmdline.py | 2+-
Msrc/strix/events.py | 22+++++-----------------
Msrc/strix/logger.py | 4++--
Msrc/strix/motion/config.py | 12+++++-------
Msrc/strix/queue.py | 6++----
Mtox.ini | 2+-
9 files changed, 31 insertions(+), 50 deletions(-)

diff --git a/setup.cfg b/setup.cfg @@ -15,10 +15,8 @@ packages = find: install_requires = bottle gevent - mypy - Pillow - rfc3339 structlog + Pillow [options.packages.find] where = src @@ -29,7 +27,6 @@ strix = ui/* [options.extras_require] testing = coverage - mypy nose pylint pytest diff --git a/src/strix/__init__.py b/src/strix/__init__.py @@ -31,16 +31,14 @@ from . import motion ## Start the bottle/API thread ## Wait for a signal to shutdown -from typing import List, Match, Optional, Tuple, Any - -def check_motion_config(config_path: str) -> Tuple[str, List[str]]: +def check_motion_config(config_path): """ Check the config file to make sure the settings match what Strix needs. """ picture_filename = "%Y-%m-%d/%v/%H-%M-%S-%q" on_event_end_re = r".*touch (.*)/queue/Camera%t_%Y-%m-%d_%v" target_dir_re = r"(.*)/Camera\d+" - errors = [] # type: List[str] + errors = [] base_target_dir = "" base_queue_dir = "" found_pf = False @@ -55,7 +53,7 @@ def check_motion_config(config_path: str) -> Tuple[str, List[str]]: on_event_end = c.get("on_event_end", "") if on_event_end: - em = re.match(on_event_end_re, on_event_end) # type: Optional[Match[str]] + em = re.match(on_event_end_re, on_event_end) if not em or not em.groups(): continue # Above errors will be caught by not having base_queue_dir set. @@ -66,7 +64,7 @@ def check_motion_config(config_path: str) -> Tuple[str, List[str]]: target_dir = c.get("target_dir", "") if target_dir: - tm = re.match(target_dir_re, target_dir) # type: Optional[Match[str]] + tm = re.match(target_dir_re, target_dir) if not tm or not tm.groups(): continue # Above errors will be caught by not having base_target_dir set. @@ -87,7 +85,7 @@ def check_motion_config(config_path: str) -> Tuple[str, List[str]]: return (base_target_dir, errors) -def run() -> bool: +def run(): parser = cmdline.parser(queue.max_cores()) opts = parser.parse_args() @@ -97,13 +95,13 @@ def run() -> bool: errors = [str(e)] if errors: - def p_e(e: str) -> None: + def p_e(e): print("ERROR: %s" % e) list(map(p_e, errors)) return False # Start logger thread - logger_queue = mp.JoinableQueue() # type: mp.JoinableQueue[List[Any]] + logger_queue = mp.JoinableQueue() logger_quit = mp.Event() logger_thread = mp.Process(name="logger-thread", target=logger.listener, diff --git a/src/strix/api.py b/src/strix/api.py @@ -30,21 +30,21 @@ from . import logger from .events import camera_events, EventCache TIME_FORMAT = "%Y-%m-%d %H:%M:%S" -def timestr_to_dt(rfc_str: str) -> datetime: +def timestr_to_dt(rfc_str): return datetime.strptime(rfc_str, TIME_FORMAT) -def run_api(logging_queue: mp.Queue, base_dir: str, host: str, port: int, debug: bool) -> None: +def run_api(logging_queue, base_dir, host, port, debug): log = logger.log(logging_queue) log.info("Starting API", base_dir=base_dir, host=host, port=port, debug=debug) EventCache.logger(log) @route('/') @route('/<filename>') - def serve_root(filename: str = "index.html") -> Response: + def serve_root(filename="index.html"): return static_file(filename, root=os.path.dirname(__file__)+"/ui") @route('/motion/<filepath:path>') - def serve_motion(filepath: str) -> Response: + def serve_motion(filepath): return static_file(filepath, root=base_dir) @route('/api/cameras/list') @@ -52,7 +52,9 @@ def run_api(logging_queue: mp.Queue, base_dir: str, host: str, port: int, debug: return {"cameras": sorted([d for d in os.listdir(base_dir) if d != "queue"])} @route('/api/events/<cameras>') - def serve_events(cameras: str) -> Response: + def serve_events(cameras): + # request.query is a bottle.MultiDict which pylint doesn't understand + # pylint: disable=no-member start = timestr_to_dt(request.query.get("start", "1985-10-26 01:22:00")) end = timestr_to_dt(request.query.get("end", datetime.now().strftime(TIME_FORMAT))) offset= int(request.query.get("offset", "0")) diff --git a/src/strix/cmdline.py b/src/strix/cmdline.py @@ -18,7 +18,7 @@ import argparse version = "DEVEL" -def parser(max_cores) -> argparse.ArgumentParser: +def parser(max_cores): """ Return the ArgumentParser""" parser = argparse.ArgumentParser(description="Motion Camera Web Interface") diff --git a/src/strix/events.py b/src/strix/events.py @@ -26,8 +26,6 @@ import threading import structlog -from typing import Dict, List - class EventCacheClass: def __init__(self): self._log = None @@ -44,10 +42,6 @@ class EventCacheClass: return self._cache[key] def set(self, key, value): - # DEBUG - if self._log: - self._log.info(f"EventCache: {key} = {value}") - with self._lock: # Convert start/end to datetime object if "start" in value and type(value["start"]) == type(""): @@ -177,14 +171,14 @@ def preload_cache(log, base_dir): EventCache.force_expire(False) -def path_to_dt(path: str) -> datetime: +def path_to_dt(path): # Use the last 2 elements of the path to construct a Datatime (date, time) = path.split("/")[-2:] time = time.replace("-", ":") dt_str = "{0} {1}".format(date, time) return datetime.strptime(dt_str, "%Y-%m-%d %H:%M:%S") -def image_to_dt(event_date: str, image: str) -> datetime: +def image_to_dt(event_date, image): """ Convert an event date (YYYY-MM-DD) and image HH-MM-SS-FF returns a datetime object @@ -194,9 +188,7 @@ def image_to_dt(event_date: str, image: str) -> datetime: return datetime.strptime(event_date+"/"+image_time, "%Y-%m-%d/%H-%M-%S") -def event_details(log: structlog.BoundLogger, event_path: str) -> Dict: -# log.info("event_details", path=event_path) - +def event_details(log, event_path): # Check the cache for the details try: return EventCache.get(event_path) @@ -257,9 +249,6 @@ def event_details(log: structlog.BoundLogger, event_path: str) -> Dict: is_saved = os.path.exists(event_path+"/.saved") -# log.debug("event_details", thumbnail=thumbnail, start_time=str(start_time), end_time=str(end_time), -# video=video, debug_video=debug_video, saved=is_saved) - details = { "start": start_time, "end": end_time, @@ -281,15 +270,14 @@ def event_details(log: structlog.BoundLogger, event_path: str) -> Dict: return details -def camera_events(log: structlog.BoundLogger, base_dir: str, camera: str, - start: datetime, end: datetime, offset: int, limit: int) -> List[Dict]: +def camera_events(log, base_dir, camera, start, end, offset, limit): # YYYY-MM-DD/HH-MM-SS is the format of the event directories. glob_path="%s/%s/????-??-??/??-??-??" % (base_dir, camera) # Newest to oldest, limited by offset and limit skipped = 0 added = 0 - events = [] # type: List[Dict] + events = [] for event_path in sorted(glob(glob_path), reverse=True): dt = path_to_dt(event_path) if dt < start or dt > end: diff --git a/src/strix/logger.py b/src/strix/logger.py @@ -39,7 +39,7 @@ structlog.configure( cache_logger_on_first_use=True, ) -def listener(queue: mp.Queue, stop_event: mp.Event, log_path: str) -> None: +def listener(queue, stop_event, log_path): handler = RotatingFileHandler(log_path, maxBytes=100*1024**2, backupCount=10) formatter = logging.Formatter('%(message)s') handler.setFormatter(formatter) @@ -60,7 +60,7 @@ def listener(queue: mp.Queue, stop_event: mp.Event, log_path: str) -> None: import sys, traceback traceback.print_exc(file=sys.stderr) -def log(queue: mp.Queue) -> structlog.BoundLogger: +def log(queue): handler = QueueHandler(queue) root = structlog.get_logger() root.addHandler(handler) diff --git a/src/strix/motion/config.py b/src/strix/motion/config.py @@ -16,15 +16,13 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. import os -from typing import Dict, Tuple - class MotionConfig(): """ Parse a motion configuration file into dicts Last key wins. Threads are stored in self.thread["thread-N"] """ - config = {} # type: Dict - thread = {} # type: Dict + config = {} + thread = {} _thread_n = 0 def thread_n(self) -> str: @@ -35,7 +33,7 @@ class MotionConfig(): self._thread_n += 1 return "thread-%d" % self._thread_n - def split(self, s: str) -> Tuple[str, str]: + def split(self, s): """ Split the line into key and optional values. :returns: (k, v) where v may be "" @@ -51,7 +49,7 @@ class MotionConfig(): v = "" return (k, v) - def parse(self, config_path: str) -> Dict: + def parse(self, config_path): """ Parse a motion config file :returns: dict @@ -62,7 +60,7 @@ class MotionConfig(): for line in f.readlines() if line.strip() and not line.startswith("#")]) - def __init__(self, config_path: str) -> None: + def __init__(self, config_path): self.config = self.parse(config_path) for t in filter(lambda k: k.startswith("thread") or k.startswith("camera"), self.config.keys()): thread_path = self.config[t] diff --git a/src/strix/queue.py b/src/strix/queue.py @@ -24,8 +24,6 @@ import time from PIL import Image import structlog -from typing import List - from . import logger THUMBNAIL_SIZE = (640, 480) @@ -98,8 +96,8 @@ def process_event(log: structlog.BoundLogger, base_dir: str, event: str) -> None except Exception as e: log.error("Moving to destination failed", event_path=event_path, exception=str(e)) -def monitor_queue(logging_queue: mp.Queue, base_dir: str, quit: mp.Event, max_threads: int) -> None: - threads = [] # type: List[mp.Process] +def monitor_queue(logging_queue, base_dir, quit, max_threads): + threads = [] log = logger.log(logging_queue) queue_path = os.path.abspath(os.path.join(base_dir, "queue/")) diff --git a/tox.ini b/tox.ini @@ -2,8 +2,8 @@ envlist = py38 [testenv] +deps=.[testing] commands= - mypy --strict --ignore-missing-imports src/strix/ src/bin/strix pylint --rcfile=pylint.rc -E src/strix/ src/bin/strix nosetests --with-coverage [] # substitute with tox' positional arguments # pytest -v