-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
detached moods into a specialised class
- Loading branch information
1 parent
0a9b469
commit 31d06e8
Showing
5 changed files
with
339 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,157 @@ | ||
import logging | ||
from unittest import TestCase | ||
|
||
from entry.mood import Moodverse, MoodGroup, Mood, MoodNotFoundError | ||
|
||
|
||
# noinspection SpellCheckingInspection | ||
class TestMoodverse(TestCase): | ||
def test_default_moodverse_no_customisation(self): | ||
my_default_moodverse = Moodverse() | ||
self.assertTrue(isinstance(my_default_moodverse["rad"], MoodGroup)) | ||
self.assertTrue(isinstance(my_default_moodverse["good"], MoodGroup)) | ||
self.assertTrue(isinstance(my_default_moodverse["neutral"], MoodGroup)) | ||
self.assertTrue(isinstance(my_default_moodverse["bad"], MoodGroup)) | ||
self.assertTrue(isinstance(my_default_moodverse["awful"], MoodGroup)) | ||
|
||
self.assertEqual(my_default_moodverse["rad"], ["rad"]) | ||
self.assertEqual(my_default_moodverse["good"], ["good"]) | ||
self.assertEqual(my_default_moodverse["neutral"], ["neutral"]) | ||
self.assertEqual(my_default_moodverse["bad"], ["bad"]) | ||
self.assertEqual(my_default_moodverse["awful"], ["awful"]) | ||
|
||
# this is just so I can test whether my __eq__ function overload correctly skips this | ||
self.assertNotEqual(my_default_moodverse["awful"], MoodGroup("awful")) | ||
|
||
# These comparisons should be falsy because the array has more moods than the default mood set initialised | ||
self.assertNotEqual(my_default_moodverse["rad"], ["rad", "amazing"]) | ||
self.assertNotEqual(my_default_moodverse["awful"], ["awful", "miserable"]) | ||
|
||
# This comparison should be falsy because it does not contain the default mood set initialised | ||
# └── known moods of 'neutral' group | ||
# └── neutral <-- from standard | ||
# And we're basically saying, "In neutral group there should only be a 'meh' mood" | ||
self.assertNotEqual(my_default_moodverse["neutral"], ["meh"]) | ||
|
||
def test_loading_valid_moods_into_moodverse(self): | ||
# These moods are self-sufficient, because even if standard mood set didn't exist, they satisfy all requirements | ||
ok_moods_loaded_from_json = { | ||
"rad": | ||
["rad", "amazing"], | ||
"good": | ||
["good", "nice"], | ||
"neutral": | ||
["neutral", "ok", "fine"], | ||
"bad": | ||
["bad"], | ||
"awful": | ||
["awful", "miserable"] | ||
} | ||
my_moodverse = Moodverse(ok_moods_loaded_from_json) | ||
self.assertTrue(isinstance(my_moodverse["rad"], MoodGroup)) | ||
self.assertTrue(isinstance(my_moodverse["good"], MoodGroup)) | ||
self.assertTrue(isinstance(my_moodverse["neutral"], MoodGroup)) | ||
self.assertTrue(isinstance(my_moodverse["bad"], MoodGroup)) | ||
self.assertTrue(isinstance(my_moodverse["awful"], MoodGroup)) | ||
|
||
self.assertEqual(my_moodverse["rad"], ["rad", "amazing"]) | ||
self.assertEqual(my_moodverse["good"], ["good", "nice"]) | ||
self.assertEqual(my_moodverse["neutral"], ["neutral", "ok", "fine"]) | ||
self.assertEqual(my_moodverse["bad"], ["bad"]) | ||
self.assertEqual(my_moodverse["awful"], ["awful", "miserable"]) | ||
|
||
def test_loading_semi_valid_moods_into_moodverse(self): | ||
# This mood set isn't self-sufficient, but still valid, because it has all the required "groups". | ||
# Standard mood set is used here to cover for missing moods | ||
# so, when: | ||
semi_ok_moods_loaded_from_json = { | ||
"rad": | ||
["amazing"], # lacks rad | ||
"good": | ||
["nice"], # lacks good | ||
"neutral": | ||
["ok", "fine"], # lacks neutral | ||
"bad": | ||
["bad"], # OK | ||
"awful": | ||
["miserable"] # lacks awful | ||
} | ||
|
||
# then I can still use my moodverse, because standard set filled the blanks like so: | ||
# . | ||
# ├── known moods of 'rad' group | ||
# │ └── rad <-- from standard | ||
# │ └── amazing | ||
# ├── known moods of 'great' group | ||
# │ └── great <-- from standard | ||
# │ └── nice | ||
# ├── known moods of 'neutral' group | ||
# │ └── neutral <-- from standard | ||
# │ └── ok | ||
# │ └── fine | ||
# ├── known moods of 'bad' group | ||
# │ └── bad | ||
# └── known moods of 'awful' group | ||
# └── awful <0 from standard | ||
# └── miserable | ||
|
||
my_moodverse = Moodverse(semi_ok_moods_loaded_from_json) | ||
self.assertTrue(isinstance(my_moodverse["rad"], MoodGroup)) | ||
self.assertTrue(isinstance(my_moodverse["good"], MoodGroup)) | ||
self.assertTrue(isinstance(my_moodverse["neutral"], MoodGroup)) | ||
self.assertTrue(isinstance(my_moodverse["bad"], MoodGroup)) | ||
self.assertTrue(isinstance(my_moodverse["awful"], MoodGroup)) | ||
|
||
# responses should be identical to the ones in previous test, because standard mood filled the blanks | ||
self.assertEqual(my_moodverse["rad"], ["rad", "amazing"]) | ||
self.assertEqual(my_moodverse["good"], ["good", "nice"]) | ||
self.assertEqual(my_moodverse["neutral"], ["neutral", "ok", "fine"]) | ||
self.assertEqual(my_moodverse["bad"], ["bad"]) | ||
self.assertEqual(my_moodverse["awful"], ["awful", "miserable"]) | ||
|
||
def test_loading_invalid_moods_into_moodverse(self): | ||
bad_moods_loaded_from_json = { | ||
"rad": | ||
[""], # lacks rad | ||
"good": | ||
[""], # lacks good | ||
"neutral": | ||
[""], # lacks neutral | ||
"bad": | ||
[""], # lacks bad | ||
"awful": | ||
[""] # lacks awful | ||
} | ||
|
||
with self.assertLogs(logging.getLogger(), level=logging.WARNING): | ||
Moodverse(bad_moods_loaded_from_json) | ||
|
||
bad_moods_loaded_from_json = { | ||
"rad": | ||
["rad"], | ||
"good": | ||
["good"], | ||
"neutral": | ||
["neutral"], | ||
"bad": | ||
["bed"], # lacks bad | ||
"awful": | ||
[""] # lacks awful | ||
} | ||
|
||
with self.assertLogs(logging.getLogger(), level=logging.WARNING): | ||
Moodverse(bad_moods_loaded_from_json) | ||
|
||
bad_moods_loaded_from_json = { | ||
"rad": | ||
["rad"], | ||
"good": | ||
["good"], | ||
"neutral": | ||
["neutral"] | ||
# lacks bed | ||
# lacks awful | ||
} | ||
|
||
with self.assertLogs(logging.getLogger(), level=logging.WARNING): | ||
Moodverse(bad_moods_loaded_from_json) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,163 @@ | ||
import logging | ||
from typing import List | ||
|
||
import errors | ||
import utils | ||
|
||
|
||
class ErrorMsg(errors.ErrorMsgBase): | ||
MOOD_GROUP_NOT_FOUND = "Expected to find {} mood group, but the key is missing from the dictionary." | ||
STANDARD_MOODS_USED = "Problem parsing custom moods file. Standard mood set - i.e. {} - will be used." | ||
SKIPPED_INVALID_MOOD = "Skipping \'{}\' as it is not a valid mood." | ||
|
||
|
||
class EmptyValue(utils.CustomException): | ||
"""Attribute cannot be set to empty.""" | ||
|
||
|
||
class MoodNotFoundError(utils.CustomException): | ||
"""The mood could not be found neither in the standard mood set nor the custom one.""" | ||
|
||
|
||
# noinspection SpellCheckingInspection | ||
class Moodverse: | ||
def __init__(self, moods_to_process: dict[str, List[str]] = None): | ||
self.__logger = logging.getLogger(self.__class__.__name__) | ||
# DEFAULT PART OF INIT | ||
# -------------------- | ||
# Build a minimal-viable mood set with these five mood groups | ||
# . | ||
# ├── known moods of 'rad' group | ||
# │ └── rad | ||
# ├── known moods of 'great' group | ||
# │ └── great | ||
# ├── known moods of 'neutral' group | ||
# │ └── neutral | ||
# ├── known moods of 'bad' group | ||
# │ └── bad | ||
# └── known moods of 'awful' group | ||
# └── awful | ||
|
||
self.__mood_set: dict[str, MoodGroup] = {} | ||
|
||
for group_name in ["rad", "good", "neutral", "bad", "awful"]: | ||
# Add the new group to the dict | ||
self.__mood_set[group_name] = MoodGroup(group_name) | ||
# Ask it to create its main mood (e.g. 'rad' for 'rad'-group) | ||
self.__mood_set[group_name].create_mood() | ||
|
||
# We can stop here and be content with our "default" / "standard" mood-set if user did not pass a custom one | ||
# CUSTOM PART OF INIT | ||
# -------------------- | ||
if moods_to_process is not None: | ||
try: | ||
self.__create_moods(moods_to_process) | ||
except MoodNotFoundError: | ||
msg = ErrorMsg.print(ErrorMsg.STANDARD_MOODS_USED, ', '.join(self.__mood_set.keys())) | ||
self.__logger.warning(msg) | ||
|
||
def __create_moods(self, moods_to_process: dict[str, List[str]]): | ||
# Then expand the minimal-viable mood set from Moodverse __init__ | ||
# . | ||
# ├── known moods of 'rad' group | ||
# │ └── rad | ||
# │ └── (add more...) | ||
# ├── known moods of 'great' group | ||
# │ └── great | ||
# │ └── (add more...) | ||
# ├── known moods of 'neutral' group | ||
# │ └── neutral | ||
# │ └── (add more...) | ||
# ├── known moods of 'bad' group | ||
# │ └── bad | ||
# │ └── (add more...) | ||
# └── known moods of 'awful' group | ||
# └── awful | ||
# └── (add more...) | ||
|
||
# Use keys from standard_mood_set to navigate through the passed dictionary and add their unique moods over | ||
# The side effect is that keys in the passed dictionary which do not appear in standard mood set are skipped | ||
|
||
# e.g. I'm expecting a "rad" group to be in the dict | ||
for expected_mood_group in self.__mood_set.keys(): | ||
expected_mood_group: str | ||
try: | ||
# Leap of faith - there must be a "rad" group, otherwise there's no point in continuing | ||
mood_group_to_transfer = moods_to_process[expected_mood_group] | ||
except KeyError: | ||
msg = ErrorMsg.print(ErrorMsg.MOOD_GROUP_NOT_FOUND, expected_mood_group) | ||
self.__logger.warning(msg) | ||
raise MoodNotFoundError(msg) | ||
# Go through each mood in this mood group - e.g. rad - and transfer them in the form of Mood objects | ||
else: | ||
for mood in mood_group_to_transfer: | ||
self.__mood_set[expected_mood_group].create_mood(mood) | ||
|
||
def __getitem__(self, item: str): | ||
return self.__mood_set[item] | ||
|
||
@property | ||
def known_mood_groups(self): | ||
return self.__mood_set | ||
|
||
|
||
class AbstractMood: | ||
def __init__(self, value): | ||
if not value: | ||
raise EmptyValue | ||
elif isinstance(value, str) is False: | ||
raise utils.WrongValueError | ||
else: | ||
self.__name = value | ||
|
||
@property | ||
def name(self): | ||
return self.__name | ||
|
||
def __str__(self): | ||
return self.__name | ||
|
||
|
||
class MoodGroup(AbstractMood): | ||
def __init__(self, name_of_the_mood_group: str): | ||
self.__logger = logging.getLogger(self.__class__.__name__) | ||
self.__known_moods: dict[str, Mood] = {} | ||
|
||
super().__init__(name_of_the_mood_group) | ||
|
||
def create_mood(self, name: str = None): | ||
# e.g. if no argument is passed, MoodGroup "rad" will create a Mood "rad" | ||
# it's just a shorthand so that you don't have to write MoodGroup("rad").create_mood("rad") | ||
# └── known moods of 'rad' group | ||
# └── rad | ||
final_name = self.name if name is None else name | ||
try: | ||
ref = Mood(final_name) | ||
except (EmptyValue, utils.WrongValueError): | ||
self.__logger.warning(ErrorMsg.print(ErrorMsg.SKIPPED_INVALID_MOOD, final_name)) | ||
# all went ok | ||
else: | ||
self.__known_moods[final_name] = ref | ||
return ref | ||
|
||
# TODO: possibly could do funky stuff with multiple inheritance - this method could come from Moodverse | ||
@property | ||
def known_moods(self): | ||
return self.__known_moods | ||
|
||
# TODO: possibly could do funky stuff with multiple inheritance - this method could come from Moodverse | ||
def __getitem__(self, item: str): | ||
return self.__known_moods[item] | ||
|
||
def __eq__(self, other): | ||
# Used to compare instance of this class to ["mood", "mood", "mood"] and check if they contain the same moods | ||
if isinstance(other, list) and all(isinstance(item, str) for item in other): | ||
return [str(obj) for obj in self.known_moods] == other | ||
else: | ||
# Call the superclass' __eq__ for any other comparison | ||
return super().__eq__(other) | ||
|
||
|
||
class Mood(AbstractMood): | ||
def __init__(self, value: str): | ||
super().__init__(value) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters