Skip to content

Refactoring #9

Merged
merged 3 commits into from Nov 28, 2018
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
@@ -0,0 +1,2 @@
[TYPECHECK]
ignored-classes=ConfigDict,Configuration
@@ -1,3 +1,6 @@
# pylint: disable=C0103
""" Configuration classes and global_config. """

import json
from os import path

@@ -6,7 +9,7 @@ from frozen import FrozenClass

class ConfigDict(FrozenClass):
""" ConfigDict is a dict with attributes as it's contents. """

def __init__(self, parent=None, **kwargs):
""" Takes a dictionary and stores them as attributes. """
# get all items from kwargs and assigned them as attributes
@@ -16,45 +19,44 @@ class ConfigDict(FrozenClass):
setattr(self, k, ConfigDict(parent=self, **v))
else:
setattr(self, k, v)

self._parent = parent
# freeze object
self._freeze()

def __getitem__(self, attr):
""" Makes ConfigDict subscriptable. """
return getattr(self, attr)

def __contains__(self, attr):
return True if attr in self.__dict__.keys() else False

def to_dict(self):
""" Converts ConfigDict to dict. """
return {
k: v
k: v
for k, v in self.__dict__.items()
if not k.startswith("_")}


class Configuration(ConfigDict):
""" Configuration is a ConfigDict with ability to read json files. """

def __init__(self, fp):
""" Takes a file-like or filename fp and stores it as attributes. """
close = False
# if fp is str convert to file-like
if type(fp) is str:
if isinstance(fp, str):
fp = open(fp, "r")
close = True
# call super

# call super
super().__init__(parent=self, **json.load(fp))

if close:
fp.close()


# global_config is the global configuration for the file
global_config = Configuration(
path.join(path.dirname(__file__), "config.json"))


@@ -1,3 +1,5 @@
# pylint: disable=C0414,C0111,W0104

import unittest
import os.path as path

@@ -6,23 +8,22 @@ from config import Configuration


class ConfigurationTestCase(unittest.TestCase):

def setUp(self):
dirname = path.dirname(__file__)
self.config_file = path.join(dirname, "config.example.json")
self.config = Configuration(self.config_file)

def test_mutation(self):
with self.assertRaises(FrozenException):
self.config.discord.token = "hello"

def test_missing_attr(self):
with self.assertRaises(AttributeError):
self.config.not_here

def test_working_attr(self):
self.assertTrue(self.config.discord.token, "TOKEN_HERE")

def test_working_subscript(self):
self.assertTrue(self.config["discord"]["token"], "TOKEN_HERE")

@@ -1,3 +1,5 @@
# pylint: disable=C0103
""" All things redis related. """
import redis

from config import global_config
@@ -20,4 +22,4 @@ def create_datastore(**kwargs):
if "redis" in global_config:
global_datastore = create_datastore(**global_config.redis.to_dict())
else:
global_datastore = create_datastore(**_default_config)
global_datastore = create_datastore(**_default_config)
@@ -1,10 +1,11 @@
# pylint: disable=C0414,C0111,W0104
import unittest

from datastore import global_datastore


class DataStoreTestCase(unittest.TestCase):

def test_set(self):
self.assertTrue(global_datastore.set("hello", "world"))
self.assertEqual(global_datastore.get("hello"), "world")
@@ -1,21 +1,20 @@
""" FrozenClass and related. """

class FrozenException(Exception):

def __init__(self, message):
super().__init__(message)


""" Is raised when a FrozenClass object is mutated. """


class FrozenClass(object):

""" Prevents mutation after initialisation.
Designed to be inherited from. """

_frozen = False

def __setattr__(self, name, value):
if self._frozen:
raise FrozenException("This object is immutable")

super().__setattr__(name, value)

def _freeze(self):
self._frozen = True


@@ -1,19 +1,24 @@
""" DiceService and related. """

import random
import math

from .service import Service


class DiceService(Service):
""" Service that provides dice related functions. """

def roll_dice(self, sides=6):
""" Returns the result of a dice roll. """
accepted = (4, 6, 8, 10, 12, 20, 100, math.inf)
if not isinstance(sides, (int, float)):
raise TypeError("sides must be numeric.")
if sides not in accepted:
raise ValueError(("{} is not a valid number of sides. "
+ "Valid sides are {}").format(sides, accepted))

raise ValueError(
("{} is not a valid number of sides. "
+ "Valid sides are {}").format(sides, accepted))

# if sides is math.inf, return it
if sides == math.inf:
return math.inf
@@ -1,14 +1,16 @@
# pylint: disable=C0414,C0111,C0103,W0104

import unittest
import math

from .dice import DiceService


class DiceServiceTestCase(unittest.TestCase):

def setUp(self):
self.ds = DiceService()

def test_valid_dx_roll(self):
valid = (4, 6, 8, 10, 12, 20, 100)
for d in valid:
@@ -18,13 +20,13 @@ class DiceServiceTestCase(unittest.TestCase):
def test_inf_roll(self):
roll = self.ds.roll_dice(math.inf)
self.assertEqual(roll, math.inf)

def test_invalid_dx_roll(self):
with self.assertRaises(ValueError):
self.ds.roll_dice(9)
self.ds.roll_dice(5)
self.ds.roll_dice(6.0)

def test_type_roll(self):
with self.assertRaises(TypeError):
self.ds.roll_dice("hello world")
@@ -1,10 +1,13 @@
""" JokeService and related. """

import random

from .service import Service


class JokeService(Service):

""" Services that provides jokes and other comedic things. """

_jokes = (
"Why did the chicken cross the road?\nTo get to the other side.",
"Why do keyboards work 24/7?\nBecause they have two shifts.",
@@ -17,6 +20,7 @@ class JokeService(Service):
"What do you call a cow in an earthquake?\nA milkshake.",
"Where do animals go when their tails fall off?\nThe retail store.",
)

def random_joke(self):
""" Returns a random joke. """
return random.choice(self._jokes)
@@ -1,12 +1,14 @@
# pylint: disable=C0414,C0111,C0103,W0104,W0212

import unittest

from .joke import JokeService


class JokeServiceTestCase(unittest.TestCase):

def setUp(self):
self.js = JokeService()

def test_random_joke(self):
self.assertTrue(self.js.random_joke() in self.js._jokes)
@@ -1,4 +1,4 @@
import json
""" Location services provided by LocationService class. """

import requests

@@ -7,13 +7,15 @@ from .service import Service, register_service

@register_service("location")
class LocationService(Service):

""" Service that provides location related functions. """

def __init__(self):
super().__init__()

self._api_endpoint = "https://nominatim.openstreetmap.org/"

def get_location_coords(self, location: str) -> dict:
""" Queries DarkSky and returns the data as a dictionary. """
query = "{}/search?q={}&format=json".format(self._api_endpoint,
location)
r = requests.get(query)
@@ -1,10 +1,12 @@
# pylint: disable=C0414,C0111,C0103,W0104

import unittest

from .location import LocationService


class LocationServiceTestCase(unittest.TestCase):

def setUp(self):
self.ls = LocationService()

@@ -1,3 +1,6 @@
# pylint: disable=C0413,C0103
""" Service base clase and global_services. """

import sys
import os
sys.path.insert(0, os.path.abspath(".."))
@@ -8,7 +11,7 @@ from config import ConfigDict, global_config
global_services = {}

def register_service(name):
global services
""" Registers a service globally. """
def wrap(c):
if name in global_config.services:
# create service using global_config
@@ -23,14 +26,14 @@ def register_service(name):
return c
return wrap


class Service(object):

""" Service base class. """

def __init__(self, config=ConfigDict()):
if isinstance(config, dict):
config = ConfigDict(**config)
elif not isinstance(config, ConfigDict):
raise TypeError("config must be dict or ConfigDict")

self.config = config