Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions .github/workflows/reviewer.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: Automated Review

on: [push]

jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.10"]
steps:
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v3
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install flake8 pylint
- name: Checking the code using Flake8
run: |
flake8 $(git ls-files '*.py')
- name: Reviewing the code using Pylint
run: |
pylint $(git ls-files '*.py')
157 changes: 157 additions & 0 deletions .pylintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
[MAIN]

# Specify a configuration file.
#rcfile=

# Python code to execute, usually for sys.path manipulation such as
# pygtk.require().
#init-hook=

# Files or directories to be skipped. They should be base names, not
# paths.
ignore=
__pycache__,
.venv

# Pickle collected data for later comparisons.
persistent=yes

# List of plugins (as comma separated values of python modules names) to load,
# usually to register additional checkers.
load-plugins=
pylint.extensions.check_elif,
pylint.extensions.bad_builtin,
pylint.extensions.docparams,
pylint.extensions.for_any_all,
pylint.extensions.set_membership,
pylint.extensions.code_style,
pylint.extensions.overlapping_exceptions,
pylint.extensions.typing,
pylint.extensions.redefined_variable_type,
pylint.extensions.comparison_placement,
pylint.extensions.mccabe,

# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the
# number of processors available to use.
jobs=5

# When enabled, pylint would attempt to guess common misconfiguration and emit
# user-friendly hints instead of false-positive error messages.
suggestion-mode=yes

# Allow loading of arbitrary C extensions. Extensions are imported into the
# active Python interpreter and may run arbitrary code.
unsafe-load-any-extension=no

# A comma-separated list of package or module names from where C extensions may
# be loaded. Extensions are loading into the active Python interpreter and may
# run arbitrary code
extension-pkg-allow-list=

# Minimum supported python version
py-version = 3.10

# Specify a score threshold to be exceeded before program exits with error.
fail-under=9.0

# Return non-zero exit code if any of these messages/categories are detected,
# even if score is above --fail-under value. Syntax same as enable. Messages
# specified are enabled, while categories only check already-enabled messages.
fail-on=


[MESSAGES CONTROL]

# Only show warnings with the listed confidence levels. Leave empty to show
# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED
# confidence=

# Disable the message, report, category or checker with the given id(s). You
# can either give multiple identifiers separated by comma (,) or put this
# option multiple times (only on the command line, not in the configuration
# file where it should appear only once).You can also use "--disable=all" to
# disable everything first and then re-enable specific checks. For example, if
# you want to run only the similarities checker, you can use "--disable=all
# --enable=similarities". If you want to run only the classes checker, but have
# no Warning level messages displayed, use"--disable=all --enable=classes
# --disable=W"

disable=
too-few-public-methods,
arguments-differ,
import-error,
too-many-locals

[REPORTS]

# Set the output format. Available formats are text, parseable, colorized, msvs
# (visual studio) and html. You can also give a reporter class, eg
# mypackage.mymodule.MyReporterClass.
output-format=text

# Tells whether to display a full report or only the messages
reports=no

# Python expression which should return a note less than 10 (10 is the highest
# note). You have access to the variables 'fatal', 'error', 'warning', 'refactor', 'convention'
# and 'info', which contain the number of messages in each category, as
# well as 'statement', which is the total number of statements analyzed. This
# score is used by the global evaluation report (RP0004).
evaluation=max(0, 0 if fatal else 10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10))

# Template used to display messages. This is a python new-style format string
# used to format the message information. See doc for all details
#msg-template=

# Activate the evaluation score.
score=yes


[LOGGING]

# Logging modules to check that the string format arguments are in logging
# function parameter format
logging-modules=logging

# The type of string formatting that logging methods do. `old` means using %
# formatting, `new` is for `{}` formatting.
logging-format-style=old


[MISCELLANEOUS]

# List of note tags to take in consideration, separated by a comma.
notes=FIXME,XXX,TODO

# Regular expression of note tags to take in consideration.
#notes-rgx=


[SIMILARITIES]

# Ignore comments when computing similarities.
ignore-comments=yes

# Ignore docstrings when computing similarities.
ignore-docstrings=yes

# Ignore imports when computing similarities.
ignore-imports=yes

# Signatures are removed from the similarity computation
ignore-signatures=yes


[VARIABLES]

# Tells whether we should check for unused import in __init__ files.
init-import=no


[FORMAT]

# Maximum number of characters on a single line.
max-line-length=100

# Regexp for a line that is allowed to be longer than the limit.
ignore-long-lines=^\s*(# )?<?https?://\S+>?$
8 changes: 4 additions & 4 deletions core/observers/observer/hss_observer.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,19 +27,19 @@ def update(self, subject: BaseSubject) -> None:
"""This method is called when the observer is updated."""
if isinstance(subject, WiFiSubject):
self.wifi_state = subject.get_state()
logger.debug("WiFi state: " + str(self.wifi_state.name))
logger.debug("WiFi state: %s", str(self.wifi_state.name))

if isinstance(subject, EyeSubject):
self.eye_state = subject.get_state()
logger.debug("Eye state: " + str(self.eye_state.name))
logger.debug("Eye state: %s", str(self.eye_state.name))

if self.wifi_state == WiFiStates.DISCONNECTED and self.eye_state == EyeStates.DETECTED:
logger.info("There is an intruder!")
fileio_link = upload_to_fileio(
read_latest_file("~/.home-security-system/images")
)
self._notifier.notify_all(f"There is an intruder! Here is the image: {fileio_link}.")

def set_notifier(self, notifier: BaseNotifierStrategy) -> None:
"""This method is called when the observer is updated."""
self._notifier = notifier
9 changes: 5 additions & 4 deletions core/observers/subject/base_subject.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
The base subject for observer pattern.
This observer is used to create subjects.
"""
from abc import ABCMeta, abstractstaticmethod, abstractmethod
from abc import ABCMeta, abstractmethod
from core.utils.datatypes import ObserverStates
from core.observers.observer.base_observer import BaseObserver

Expand All @@ -19,7 +19,7 @@ def __init__(self):
def attach(self, observer: BaseObserver) -> None:
"""This method is called when the observer is updated."""
self._observers.append(observer)

def detach(self, observer: BaseObserver) -> None:
"""This method is called when the observer is updated."""
self._observers.remove(observer)
Expand All @@ -32,13 +32,14 @@ def notify(self) -> None:
def get_state(self) -> ObserverStates:
"""This method is called when the observer is updated."""
return self._current_state

def set_state(self, state: ObserverStates) -> None:
"""This method is called when the observer is updated."""
self._current_state = state
self.notify()

@abstractstaticmethod
@abstractmethod
@staticmethod
def get_default_state() -> ObserverStates:
"""This method is called when the observer is updated."""
raise NotImplementedError
Expand Down
21 changes: 8 additions & 13 deletions core/observers/subject/eye_subject.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,14 @@
Concretes a subject for Eye/Camera features.
"""
import os
import logging
from datetime import datetime
from time import sleep
from threading import Thread, Lock
from typing import Optional

import cv2

from core.utils.logger import get_logger
from core.utils.datatypes import EyeStates, EyeStrategyResult
from core.utils.fileio_adaptor import upload_to_fileio
from core.observers.subject.base_subject import BaseSubject
from core.strategies.eye.base_eye_strategy import BaseEyeStrategy

Expand All @@ -37,7 +34,7 @@ def __init__(self, image_path: str = DEFAULT_IMAGE_LOCATIONS):
if '~' not in image_path
else os.path.expanduser(image_path)
)

# Create the default image directory if not exists.
os.makedirs(self._image_path, exist_ok=True)

Expand All @@ -48,17 +45,16 @@ def get_default_state() -> EyeStates:

def run(self,
eye_strategy: BaseEyeStrategy,
wifi_lock: Optional[Lock] = None
wifi_lock: Lock | None = None
) -> None:
"""This method is called when the observer is updated."""
thread = Thread(target=self._run_in_loop, args=(self, eye_strategy, wifi_lock))
thread.start()
logger.debug("EyeSubject is running...")

@staticmethod
def _run_in_loop(self,
eye_strategy: BaseEyeStrategy,
wifi_lock: Optional[Lock] = None
wifi_lock: Lock | None = None
) -> None:
"""This method is called when the observer is updated."""
sleep_interval = EyeSubject.DEFAULT_SLEEP_INTERVAL
Expand All @@ -72,7 +68,7 @@ def _run_in_loop(self,
# Check if any intruders detected.
if not wifi_lock.locked():
result = eye_strategy.check_if_detected()
logger.debug("EyeStrategyResult: " + str(result.result))
logger.debug("EyeStrategyResult: %s", str(result.result))

if result.result:
logger.debug("Changing state to DETECTED...")
Expand All @@ -83,21 +79,20 @@ def _run_in_loop(self,
logger.debug("Changing state to NOT_DETECTED...")
self.set_state(EyeStates.NOT_DETECTED)
sleep_interval = EyeSubject.DEFAULT_SLEEP_INTERVAL
# If the WiFi subject does not give rights,

#  If the WiFi subject does not give rights,
# aka: "There is protectors around the house."
else:
logger.debug("Changing state to UNREACHABLE...")
self.set_state(EyeStates.UNREACHABLE)
sleep_interval = EyeSubject.DEFAULT_SLEEP_INTERVAL

sleep(sleep_interval)

def _save_image(self, result: EyeStrategyResult) -> None:
"""This method is called when the observer is updated."""
logger.debug("Saving image to the disk...")
time_now = datetime.now().strftime("%d-%m-%Y_%H-%M-%S")
file_location = f"{self._image_path}/intruder_{time_now}.jpg"
cv2.imwrite(file_location, result.image)
logger.debug("Image saved to the disk with name: " + f"intruder_{time_now}.jpg")

logger.debug("Image saved to the disk with name: intruder_%s.jpg", time_now)
11 changes: 5 additions & 6 deletions core/observers/subject/wifi_subject.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,10 @@
This class inherits from IBaseSubject.
Concretes a subject WiFi features.
"""
from core.utils.logger import get_logger
from threading import Thread, Lock
from time import sleep
from typing import Optional

from core.utils.logger import get_logger
from core.utils.datatypes import WiFiStates
from core.observers.subject.base_subject import BaseSubject
from core.strategies.wifi.base_wifi_strategy import BaseWiFiStrategy
Expand All @@ -20,9 +19,9 @@ class WiFiSubject(BaseSubject):
This class inherits from IBaseSubject.
Concretes a subject for WiFiS features.
"""
SINGLETON_LOCK: Optional[Lock] = None
SINGLETON_LOCK: Lock | None = None
CHECK_INTERVAL: int = 5

@staticmethod
def get_default_state() -> WiFiStates:
"""This method is called when the observer is updated."""
Expand All @@ -33,7 +32,7 @@ def run(self, wifi_strategy: BaseWiFiStrategy) -> None:
thread = Thread(target=self._run_in_loop, args=(wifi_strategy,))
thread.start()
logger.debug("WiFiSubject is running...")

@classmethod
def get_protector_lock(cls) -> Lock:
"""This method returns a Lock object where it can be
Expand All @@ -50,7 +49,7 @@ def _run_in_loop(self, wifi_strategy: BaseWiFiStrategy) -> None:

while True:
protectors = wifi_strategy.check_protectors()
logger.debug("Protectors: " + str(protectors.result) + " " + str(protectors.protector))
logger.debug("Protectors: %s %s", str(protectors.result), str(protectors.protector))

if protectors.result:
self.set_state(WiFiStates.CONNECTED)
Expand Down
Loading