Skip to content

Commit

Permalink
feat(STATE): use a monostate for user config
Browse files Browse the repository at this point in the history
  • Loading branch information
niall-byrne committed Nov 19, 2021
1 parent 32c9c84 commit f5ef26c
Show file tree
Hide file tree
Showing 24 changed files with 169 additions and 94 deletions.
1 change: 1 addition & 0 deletions documentation/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
autosummary_mock_imports = [
"pi_portal.tests",
"pi_portal.modules.tests",
"pi_portal.modules.tests.slack.tests",
"pi_portal.modules.tests.slack_cli.tests",
]

Expand Down
6 changes: 0 additions & 6 deletions pi_portal/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +0,0 @@
"""Pi Portal Raspberry Pi door logger."""

from pi_portal.modules import config_file

configuration = config_file.UserConfiguration()
user_config = configuration.load()
3 changes: 3 additions & 0 deletions pi_portal/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ def cli() -> None:
@cli.command("monitor")
def monitor() -> None:
"""Begin monitoring the door."""
modules.state.State().load()
door_monitor = modules.monitor.Monitor()
door_monitor.log = modules.logger.setup_logger(
door_monitor.log, config.LOGFILE_PATH
Expand All @@ -22,6 +23,7 @@ def monitor() -> None:
@cli.command("slack_bot")
def slack_bot() -> None:
"""Connect the interactive Slack bot."""
modules.state.State().load()
slack_client = modules.slack.Client()
slack_client.subscribe()

Expand All @@ -30,6 +32,7 @@ def slack_bot() -> None:
@click.argument('filename', type=click.Path(exists=True))
def upload_video(filename: str) -> None:
"""Upload a video to Slack and S3."""
modules.state.State().load()
slack_client = modules.slack.Client()
slack_client.send_video(filename)

Expand Down
2 changes: 1 addition & 1 deletion pi_portal/modules/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
"""Modules for the pi_portal cli."""

from . import installer, logger, monitor, slack
from . import installer, logger, monitor, slack, state
3 changes: 0 additions & 3 deletions pi_portal/modules/config_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,6 @@ def __init__(self):
def load(self, file_name: str = "config.json") -> Dict:
"""Load the end user's configuration file."""

if "SPHINX" in os.environ:
return self.user_config

with open(file_name, "r", encoding="utf-8") as file_handle:
self.user_config = json.load(file_handle)

Expand Down
5 changes: 3 additions & 2 deletions pi_portal/modules/s3.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
"""S3 Integration class."""

import boto3
import pi_portal
from botocore.exceptions import ClientError
from pi_portal.modules import state


class S3BucketException(Exception):
Expand All @@ -13,7 +13,8 @@ class S3Bucket:
"""S3 integration class."""

def __init__(self):
self.bucket_name = pi_portal.user_config['S3_BUCKET_NAME']
current_state = state.State()
self.bucket_name = current_state.user_config['S3_BUCKET_NAME']
self.boto_client = boto3.client('s3')

def upload(self, file_name: str):
Expand Down
28 changes: 18 additions & 10 deletions pi_portal/modules/slack.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,32 @@
"""Slack Integration."""

import pi_portal
from pi_portal.modules import motion, slack_cli
from pi_portal.modules import motion, slack_cli, state
from pi_portal.modules.logger import LOG_UUID
from slack_sdk import WebClient
from slack_sdk.rtm_v2 import RTMClient


class ClientConfiguration:
"""Configuration for the Slack integration."""

log_uuid = LOG_UUID
interval = 1
upload_file_title = "Motion Upload"


class Client:
"""Client for integrating with Slack."""

retries = 5

def __init__(self):
self.web = WebClient(token=pi_portal.user_config['SLACK_BOT_TOKEN'])
self.rtm = RTMClient(token=pi_portal.user_config["SLACK_BOT_TOKEN"])
self.channel = pi_portal.user_config['SLACK_CHANNEL']
self.channel_id = pi_portal.user_config['SLACK_CHANNEL_ID']
self.log_uuid = LOG_UUID
self.interval = 1
self.upload_file_title = "Motion Upload"
current_state = state.State()
self.web = WebClient(token=current_state.user_config['SLACK_BOT_TOKEN'])
self.rtm = RTMClient(token=current_state.user_config["SLACK_BOT_TOKEN"])
self.channel = current_state.user_config['SLACK_CHANNEL']
self.channel_id = current_state.user_config['SLACK_CHANNEL_ID']
self.motion_client = motion.Motion()
self.config = ClientConfiguration()

def handle_event(self, event: dict):
"""Process a validated event, and call any valid commands."""
Expand Down Expand Up @@ -51,7 +57,9 @@ def send_file(self, file_name: str):
for _ in range(0, self.retries):
try:
response = self.web.files_upload(
channels=self.channel, file=file_name, title=self.upload_file_title
channels=self.channel,
file=file_name,
title=self.config.upload_file_title,
)
return response
finally:
Expand Down
2 changes: 1 addition & 1 deletion pi_portal/modules/slack_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def get_commands(self) -> List[str]:
def command_id(self):
"""Report the logger ID the bot is currently running with."""

self.slack_client.send_message(f"ID: {self.slack_client.log_uuid}")
self.slack_client.send_message(f"ID: {self.slack_client.config.log_uuid}")

def command_arm(self):
"""Arm the security system."""
Expand Down
22 changes: 22 additions & 0 deletions pi_portal/modules/state.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
"""Borg monostate of the current running configuration."""

from typing import Any, Dict

from pi_portal.modules import config_file


class State:
"""Borg monostate of the current running configuration."""

__shared_state: Dict[str, Any] = {}

def __init__(self):
self.__dict__ = self.__shared_state
if not self.__shared_state:
self.user_config = {}

def load(self):
"""Load the user configuration file into the monostate."""

configuration = config_file.UserConfiguration()
self.user_config = configuration.load()
29 changes: 0 additions & 29 deletions pi_portal/modules/tests/fixtures/environment.py

This file was deleted.

30 changes: 30 additions & 0 deletions pi_portal/modules/tests/fixtures/mock_state.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
"""Fixtures for mocking required environment variables."""

from unittest import mock

from pi_portal.modules import state

MOCK_TOKEN = "secretValue"
MOCK_CHANNEL = "mockChannel"
MOCK_CHANNEL_ID = "CHHH111"
MOCK_S3_BUCKET_NAME = 'MOCK_S3_BUCKET_NAME'
MOCK_LOGZ_IO_CODE = "secretCode"


def patch(func):

def patched_function(*args, **kwargs):

with mock.patch(state.__name__ + ".State") as mock_state:

mock_state.return_value.user_config = {
"SLACK_BOT_TOKEN": MOCK_TOKEN,
"SLACK_CHANNEL": MOCK_CHANNEL,
"SLACK_CHANNEL_ID": MOCK_CHANNEL_ID,
"S3_BUCKET_NAME": MOCK_S3_BUCKET_NAME,
"LOGZ_IO_CODE": MOCK_LOGZ_IO_CODE
}

func(*args, **kwargs)

return patched_function
Empty file.
16 changes: 16 additions & 0 deletions pi_portal/modules/tests/slack/test_slack.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
"""Test Slack ClientConfiguration class."""

from unittest import TestCase

from pi_portal.modules import slack
from pi_portal.modules.logger import LOG_UUID


class TestSlackClient(TestCase):
"""Test the ClientConfiguration class."""

def test_initialize(self):
configuration = slack.ClientConfiguration()
self.assertEqual(configuration.log_uuid, LOG_UUID)
self.assertEqual(configuration.interval, 1)
self.assertEqual(configuration.upload_file_title, "Motion Upload")
Original file line number Diff line number Diff line change
@@ -1,31 +1,28 @@
"""Test Slack Client Class."""
"""Test Slack client configuration."""

from unittest import TestCase, mock

from pi_portal.modules import motion, slack
from pi_portal.modules.logger import LOG_UUID
from pi_portal.modules.tests.fixtures import environment
from pi_portal.modules.tests.fixtures import mock_state


class TestSlackClient(TestCase):
"""Test the SlackClient class."""
"""Test the ClientConfiguration class."""

@environment.patch
@mock_state.patch
def setUp(self):
self.slack_client = slack.Client()
self.slack_client.motion_client = mock.MagicMock()

@environment.patch
@mock_state.patch
def test_initialize(self):
client = slack.Client()
self.assertEqual(client.web.token, environment.MOCK_TOKEN)
self.assertEqual(client.rtm.token, environment.MOCK_TOKEN)
self.assertEqual(client.channel, environment.MOCK_CHANNEL)
self.assertEqual(client.channel_id, environment.MOCK_CHANNEL_ID)
self.assertEqual(client.log_uuid, LOG_UUID)
self.assertEqual(client.interval, 1)
self.assertEqual(client.upload_file_title, "Motion Upload")
self.assertEqual(client.web.token, mock_state.MOCK_TOKEN)
self.assertEqual(client.rtm.token, mock_state.MOCK_TOKEN)
self.assertEqual(client.channel, mock_state.MOCK_CHANNEL)
self.assertEqual(client.channel_id, mock_state.MOCK_CHANNEL_ID)
self.assertIsInstance(client.motion_client, motion.Motion)
self.assertIsInstance(client.config, slack.ClientConfiguration)

@mock.patch(slack.__name__ + ".slack_cli.SlackCLI")
def test_handle_event_invalid_command(self, m_slack_cli):
Expand Down Expand Up @@ -94,7 +91,7 @@ def test_send_file(self):
self.slack_client.web.files_upload.assert_called_once_with(
channels=self.slack_client.channel,
file=test_file,
title=self.slack_client.upload_file_title
title=self.slack_client.config.upload_file_title
)

def test_send_video(self):
Expand Down
4 changes: 2 additions & 2 deletions pi_portal/modules/tests/slack_cli/fixtures/harness.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@
from unittest import TestCase, mock

from pi_portal.modules import slack_cli
from pi_portal.modules.tests.fixtures import environment
from pi_portal.modules.tests.fixtures import mock_state


class TestSlackCLIHarness(TestCase):
"""Test harness for the SlackCLI class."""

__test__ = False

@environment.patch
@mock_state.patch
def setUp(self):
self.slack_client = mock.MagicMock()
self.cli = slack_cli.SlackCLI(client=self.slack_client)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def test_get_commands(self):
def test_command_id(self):
self.cli.command_id()
self.cli.slack_client.send_message.assert_called_once_with(
f"ID: {self.cli.slack_client.log_uuid}"
f"ID: {self.cli.slack_client.config.log_uuid}"
)

def test_command_arm_not_running(self):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from unittest import TestCase, mock

from pi_portal.modules import slack_cli, supervisor
from pi_portal.modules.tests.fixtures import environment
from pi_portal.modules.tests.fixtures import mock_state


class TestSlackCLI(TestCase):
Expand All @@ -12,7 +12,7 @@ class TestSlackCLI(TestCase):
def setUp(self):
self.slack_client = mock.Mock()

@environment.patch
@mock_state.patch
def test_initialize(self):
cli = slack_cli.SlackCLI(client=self.slack_client)
self.assertEqual(cli.prefix, "command_")
Expand Down
6 changes: 0 additions & 6 deletions pi_portal/modules/tests/test_config_file.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
"""Test user configuration loader."""

import json
import os
from unittest import TestCase, mock

from pi_portal.modules import config_file
Expand Down Expand Up @@ -38,11 +37,6 @@ def test_load(self):
self.assertEqual(result, MOCK_JSON)
self.configuration.validate.assert_called_once_with()

@mock.patch.dict(os.environ, {"SPHINX": "1"}, clear=True)
def test_load_sphinx(self):
result = self.configuration.load()
self.assertEqual(result, {})


class TestUserConfigurationValidate(TestCase):
"""Test the UserConfiguration class validate method."""
Expand Down
4 changes: 2 additions & 2 deletions pi_portal/modules/tests/test_installer.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

from pi_portal import config
from pi_portal.modules import installer
from pi_portal.modules.tests.fixtures import environment
from pi_portal.modules.tests.fixtures import mock_state

SCRIPT_DIRECTORY = pathlib.Path(
os.path.dirname(__file__)
Expand All @@ -20,7 +20,7 @@
class TestInstaller(TestCase):
"""Test the installation/configuration module."""

@environment.patch
@mock_state.patch
@mock.patch(installer.__name__ + ".os.system")
@mock.patch(installer.__name__ + ".os.chdir")
@mock.patch(installer.__name__ + ".config_file.UserConfiguration")
Expand Down
Loading

0 comments on commit f5ef26c

Please sign in to comment.