Skip to content

Commit 2e1575a

Browse files
feat: Automated reviewer with Flake8 and Pylint (#7)
* refactor: Apply flake8 corrections * refactor: Apply flake suggestions * feat: Split the runs
1 parent 7e95276 commit 2e1575a

23 files changed

Lines changed: 298 additions & 101 deletions

.github/workflows/reviewer.yaml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
name: Automated Review
2+
3+
on: [push]
4+
5+
jobs:
6+
build:
7+
runs-on: ubuntu-latest
8+
strategy:
9+
matrix:
10+
python-version: ["3.10"]
11+
steps:
12+
- uses: actions/checkout@v3
13+
- name: Set up Python ${{ matrix.python-version }}
14+
uses: actions/setup-python@v3
15+
with:
16+
python-version: ${{ matrix.python-version }}
17+
- name: Install dependencies
18+
run: |
19+
python -m pip install --upgrade pip
20+
pip install flake8 pylint
21+
- name: Checking the code using Flake8
22+
run: |
23+
flake8 $(git ls-files '*.py')
24+
- name: Reviewing the code using Pylint
25+
run: |
26+
pylint $(git ls-files '*.py')

.pylintrc

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
[MAIN]
2+
3+
# Specify a configuration file.
4+
#rcfile=
5+
6+
# Python code to execute, usually for sys.path manipulation such as
7+
# pygtk.require().
8+
#init-hook=
9+
10+
# Files or directories to be skipped. They should be base names, not
11+
# paths.
12+
ignore=
13+
__pycache__,
14+
.venv
15+
16+
# Pickle collected data for later comparisons.
17+
persistent=yes
18+
19+
# List of plugins (as comma separated values of python modules names) to load,
20+
# usually to register additional checkers.
21+
load-plugins=
22+
pylint.extensions.check_elif,
23+
pylint.extensions.bad_builtin,
24+
pylint.extensions.docparams,
25+
pylint.extensions.for_any_all,
26+
pylint.extensions.set_membership,
27+
pylint.extensions.code_style,
28+
pylint.extensions.overlapping_exceptions,
29+
pylint.extensions.typing,
30+
pylint.extensions.redefined_variable_type,
31+
pylint.extensions.comparison_placement,
32+
pylint.extensions.mccabe,
33+
34+
# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the
35+
# number of processors available to use.
36+
jobs=5
37+
38+
# When enabled, pylint would attempt to guess common misconfiguration and emit
39+
# user-friendly hints instead of false-positive error messages.
40+
suggestion-mode=yes
41+
42+
# Allow loading of arbitrary C extensions. Extensions are imported into the
43+
# active Python interpreter and may run arbitrary code.
44+
unsafe-load-any-extension=no
45+
46+
# A comma-separated list of package or module names from where C extensions may
47+
# be loaded. Extensions are loading into the active Python interpreter and may
48+
# run arbitrary code
49+
extension-pkg-allow-list=
50+
51+
# Minimum supported python version
52+
py-version = 3.10
53+
54+
# Specify a score threshold to be exceeded before program exits with error.
55+
fail-under=9.0
56+
57+
# Return non-zero exit code if any of these messages/categories are detected,
58+
# even if score is above --fail-under value. Syntax same as enable. Messages
59+
# specified are enabled, while categories only check already-enabled messages.
60+
fail-on=
61+
62+
63+
[MESSAGES CONTROL]
64+
65+
# Only show warnings with the listed confidence levels. Leave empty to show
66+
# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED
67+
# confidence=
68+
69+
# Disable the message, report, category or checker with the given id(s). You
70+
# can either give multiple identifiers separated by comma (,) or put this
71+
# option multiple times (only on the command line, not in the configuration
72+
# file where it should appear only once).You can also use "--disable=all" to
73+
# disable everything first and then re-enable specific checks. For example, if
74+
# you want to run only the similarities checker, you can use "--disable=all
75+
# --enable=similarities". If you want to run only the classes checker, but have
76+
# no Warning level messages displayed, use"--disable=all --enable=classes
77+
# --disable=W"
78+
79+
disable=
80+
too-few-public-methods,
81+
arguments-differ,
82+
import-error,
83+
too-many-locals
84+
85+
[REPORTS]
86+
87+
# Set the output format. Available formats are text, parseable, colorized, msvs
88+
# (visual studio) and html. You can also give a reporter class, eg
89+
# mypackage.mymodule.MyReporterClass.
90+
output-format=text
91+
92+
# Tells whether to display a full report or only the messages
93+
reports=no
94+
95+
# Python expression which should return a note less than 10 (10 is the highest
96+
# note). You have access to the variables 'fatal', 'error', 'warning', 'refactor', 'convention'
97+
# and 'info', which contain the number of messages in each category, as
98+
# well as 'statement', which is the total number of statements analyzed. This
99+
# score is used by the global evaluation report (RP0004).
100+
evaluation=max(0, 0 if fatal else 10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10))
101+
102+
# Template used to display messages. This is a python new-style format string
103+
# used to format the message information. See doc for all details
104+
#msg-template=
105+
106+
# Activate the evaluation score.
107+
score=yes
108+
109+
110+
[LOGGING]
111+
112+
# Logging modules to check that the string format arguments are in logging
113+
# function parameter format
114+
logging-modules=logging
115+
116+
# The type of string formatting that logging methods do. `old` means using %
117+
# formatting, `new` is for `{}` formatting.
118+
logging-format-style=old
119+
120+
121+
[MISCELLANEOUS]
122+
123+
# List of note tags to take in consideration, separated by a comma.
124+
notes=FIXME,XXX,TODO
125+
126+
# Regular expression of note tags to take in consideration.
127+
#notes-rgx=
128+
129+
130+
[SIMILARITIES]
131+
132+
# Ignore comments when computing similarities.
133+
ignore-comments=yes
134+
135+
# Ignore docstrings when computing similarities.
136+
ignore-docstrings=yes
137+
138+
# Ignore imports when computing similarities.
139+
ignore-imports=yes
140+
141+
# Signatures are removed from the similarity computation
142+
ignore-signatures=yes
143+
144+
145+
[VARIABLES]
146+
147+
# Tells whether we should check for unused import in __init__ files.
148+
init-import=no
149+
150+
151+
[FORMAT]
152+
153+
# Maximum number of characters on a single line.
154+
max-line-length=100
155+
156+
# Regexp for a line that is allowed to be longer than the limit.
157+
ignore-long-lines=^\s*(# )?<?https?://\S+>?$

core/observers/observer/hss_observer.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,19 +27,19 @@ def update(self, subject: BaseSubject) -> None:
2727
"""This method is called when the observer is updated."""
2828
if isinstance(subject, WiFiSubject):
2929
self.wifi_state = subject.get_state()
30-
logger.debug("WiFi state: " + str(self.wifi_state.name))
31-
30+
logger.debug("WiFi state: %s", str(self.wifi_state.name))
31+
3232
if isinstance(subject, EyeSubject):
3333
self.eye_state = subject.get_state()
34-
logger.debug("Eye state: " + str(self.eye_state.name))
34+
logger.debug("Eye state: %s", str(self.eye_state.name))
3535

3636
if self.wifi_state == WiFiStates.DISCONNECTED and self.eye_state == EyeStates.DETECTED:
3737
logger.info("There is an intruder!")
3838
fileio_link = upload_to_fileio(
3939
read_latest_file("~/.home-security-system/images")
4040
)
4141
self._notifier.notify_all(f"There is an intruder! Here is the image: {fileio_link}.")
42-
42+
4343
def set_notifier(self, notifier: BaseNotifierStrategy) -> None:
4444
"""This method is called when the observer is updated."""
4545
self._notifier = notifier

core/observers/subject/base_subject.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
The base subject for observer pattern.
33
This observer is used to create subjects.
44
"""
5-
from abc import ABCMeta, abstractstaticmethod, abstractmethod
5+
from abc import ABCMeta, abstractmethod
66
from core.utils.datatypes import ObserverStates
77
from core.observers.observer.base_observer import BaseObserver
88

@@ -19,7 +19,7 @@ def __init__(self):
1919
def attach(self, observer: BaseObserver) -> None:
2020
"""This method is called when the observer is updated."""
2121
self._observers.append(observer)
22-
22+
2323
def detach(self, observer: BaseObserver) -> None:
2424
"""This method is called when the observer is updated."""
2525
self._observers.remove(observer)
@@ -32,13 +32,14 @@ def notify(self) -> None:
3232
def get_state(self) -> ObserverStates:
3333
"""This method is called when the observer is updated."""
3434
return self._current_state
35-
35+
3636
def set_state(self, state: ObserverStates) -> None:
3737
"""This method is called when the observer is updated."""
3838
self._current_state = state
3939
self.notify()
4040

41-
@abstractstaticmethod
41+
@abstractmethod
42+
@staticmethod
4243
def get_default_state() -> ObserverStates:
4344
"""This method is called when the observer is updated."""
4445
raise NotImplementedError

core/observers/subject/eye_subject.py

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,14 @@
33
Concretes a subject for Eye/Camera features.
44
"""
55
import os
6-
import logging
76
from datetime import datetime
87
from time import sleep
98
from threading import Thread, Lock
10-
from typing import Optional
119

1210
import cv2
1311

1412
from core.utils.logger import get_logger
1513
from core.utils.datatypes import EyeStates, EyeStrategyResult
16-
from core.utils.fileio_adaptor import upload_to_fileio
1714
from core.observers.subject.base_subject import BaseSubject
1815
from core.strategies.eye.base_eye_strategy import BaseEyeStrategy
1916

@@ -37,7 +34,7 @@ def __init__(self, image_path: str = DEFAULT_IMAGE_LOCATIONS):
3734
if '~' not in image_path
3835
else os.path.expanduser(image_path)
3936
)
40-
37+
4138
# Create the default image directory if not exists.
4239
os.makedirs(self._image_path, exist_ok=True)
4340

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

4946
def run(self,
5047
eye_strategy: BaseEyeStrategy,
51-
wifi_lock: Optional[Lock] = None
48+
wifi_lock: Lock | None = None
5249
) -> None:
5350
"""This method is called when the observer is updated."""
5451
thread = Thread(target=self._run_in_loop, args=(self, eye_strategy, wifi_lock))
5552
thread.start()
5653
logger.debug("EyeSubject is running...")
5754

58-
@staticmethod
5955
def _run_in_loop(self,
6056
eye_strategy: BaseEyeStrategy,
61-
wifi_lock: Optional[Lock] = None
57+
wifi_lock: Lock | None = None
6258
) -> None:
6359
"""This method is called when the observer is updated."""
6460
sleep_interval = EyeSubject.DEFAULT_SLEEP_INTERVAL
@@ -72,7 +68,7 @@ def _run_in_loop(self,
7268
# Check if any intruders detected.
7369
if not wifi_lock.locked():
7470
result = eye_strategy.check_if_detected()
75-
logger.debug("EyeStrategyResult: " + str(result.result))
71+
logger.debug("EyeStrategyResult: %s", str(result.result))
7672

7773
if result.result:
7874
logger.debug("Changing state to DETECTED...")
@@ -83,21 +79,20 @@ def _run_in_loop(self,
8379
logger.debug("Changing state to NOT_DETECTED...")
8480
self.set_state(EyeStates.NOT_DETECTED)
8581
sleep_interval = EyeSubject.DEFAULT_SLEEP_INTERVAL
86-
87-
# If the WiFi subject does not give rights,
82+
83+
#  If the WiFi subject does not give rights,
8884
# aka: "There is protectors around the house."
8985
else:
9086
logger.debug("Changing state to UNREACHABLE...")
9187
self.set_state(EyeStates.UNREACHABLE)
9288
sleep_interval = EyeSubject.DEFAULT_SLEEP_INTERVAL
9389

9490
sleep(sleep_interval)
95-
91+
9692
def _save_image(self, result: EyeStrategyResult) -> None:
9793
"""This method is called when the observer is updated."""
9894
logger.debug("Saving image to the disk...")
9995
time_now = datetime.now().strftime("%d-%m-%Y_%H-%M-%S")
10096
file_location = f"{self._image_path}/intruder_{time_now}.jpg"
10197
cv2.imwrite(file_location, result.image)
102-
logger.debug("Image saved to the disk with name: " + f"intruder_{time_now}.jpg")
103-
98+
logger.debug("Image saved to the disk with name: intruder_%s.jpg", time_now)

core/observers/subject/wifi_subject.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,10 @@
22
This class inherits from IBaseSubject.
33
Concretes a subject WiFi features.
44
"""
5-
from core.utils.logger import get_logger
65
from threading import Thread, Lock
76
from time import sleep
8-
from typing import Optional
97

8+
from core.utils.logger import get_logger
109
from core.utils.datatypes import WiFiStates
1110
from core.observers.subject.base_subject import BaseSubject
1211
from core.strategies.wifi.base_wifi_strategy import BaseWiFiStrategy
@@ -20,9 +19,9 @@ class WiFiSubject(BaseSubject):
2019
This class inherits from IBaseSubject.
2120
Concretes a subject for WiFiS features.
2221
"""
23-
SINGLETON_LOCK: Optional[Lock] = None
22+
SINGLETON_LOCK: Lock | None = None
2423
CHECK_INTERVAL: int = 5
25-
24+
2625
@staticmethod
2726
def get_default_state() -> WiFiStates:
2827
"""This method is called when the observer is updated."""
@@ -33,7 +32,7 @@ def run(self, wifi_strategy: BaseWiFiStrategy) -> None:
3332
thread = Thread(target=self._run_in_loop, args=(wifi_strategy,))
3433
thread.start()
3534
logger.debug("WiFiSubject is running...")
36-
35+
3736
@classmethod
3837
def get_protector_lock(cls) -> Lock:
3938
"""This method returns a Lock object where it can be
@@ -50,7 +49,7 @@ def _run_in_loop(self, wifi_strategy: BaseWiFiStrategy) -> None:
5049

5150
while True:
5251
protectors = wifi_strategy.check_protectors()
53-
logger.debug("Protectors: " + str(protectors.result) + " " + str(protectors.protector))
52+
logger.debug("Protectors: %s %s", str(protectors.result), str(protectors.protector))
5453

5554
if protectors.result:
5655
self.set_state(WiFiStates.CONNECTED)

0 commit comments

Comments
 (0)