Skip to content
Permalink
Browse files
Merge pull request #9 from soperd/refactor
Refactoring
  • Loading branch information
soperd committed Nov 28, 2018
2 parents 86f97bc + c9e6267 commit 9a08fb451a9a6faa425eda62964aa277936094a6
Show file tree
Hide file tree
Showing 19 changed files with 153 additions and 111 deletions.
@@ -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

0 comments on commit 9a08fb4

Please sign in to comment.