Skip to content

Commit

Permalink
detached moods into a specialised class
Browse files Browse the repository at this point in the history
  • Loading branch information
DeutscheGabanna committed Jan 6, 2024
1 parent 0a9b469 commit 31d06e8
Show file tree
Hide file tree
Showing 5 changed files with 339 additions and 15 deletions.
16 changes: 14 additions & 2 deletions _tests/test_dated_entries_group.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from unittest import TestCase

import dated_entries_group
from dated_entries_group import DatedEntriesGroup, InvalidDateError, \
DatedEntryMissingError, TriedCreatingDuplicateDatedEntryError
DatedEntryMissingError, TriedCreatingDuplicateDatedEntryError, \
IncompleteDataRow


class TestDate(TestCase):
Expand Down Expand Up @@ -43,6 +43,7 @@ def test_creating_entries_from_row(self):
"note": ""
}
)
# This would be a duplicate from the one already created
with self.assertRaises(TriedCreatingDuplicateDatedEntryError):
my_date.create_dated_entry_from_row(
{
Expand All @@ -53,6 +54,17 @@ def test_creating_entries_from_row(self):
"note": ""
}
)
# This lacks the minimum required keys - time and mood - to function correctly
with self.assertRaises(IncompleteDataRow):
my_date.create_dated_entry_from_row(
{
"time": "5:00 PM",
"mood": "",
"activities": "",
"note_title": "",
"note": ""
}
)

def test_create_dated_entries_groups(self):
"""
Expand Down
157 changes: 157 additions & 0 deletions _tests/test_mood.py
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)
14 changes: 1 addition & 13 deletions dated_entry.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,19 +182,7 @@ def __init__(self,
except IsNotTimeError:
raise ValueError("Cannot create object without valid Time attribute")

# Mood
if len(mood) == 0 or mood is None:
raise ValueError("Cannot create object without valid Mood attribute")
else:
if known_moods is True:
mood_is_in_dictionary = False
for i, (_, this_group) in enumerate(known_moods.items()):
if mood in this_group:
mood_is_in_dictionary = True
break
if not mood_is_in_dictionary:
self.__logger.warning(ErrorMsg.print(ErrorMsg.INVALID_MOOD, mood))
# Assign it anyway. Warning is enough.

self.__mood = mood

# Processing other, optional properties
Expand Down
163 changes: 163 additions & 0 deletions entry/mood.py
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)
4 changes: 4 additions & 0 deletions utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ def __init__(self, message=None):
self.message = message


class WrongValueError(CustomException):
"""Passed an unexpected value"""


def slugify(text: str, taggify: bool):
# noinspection SpellCheckingInspection
"""
Expand Down

0 comments on commit 31d06e8

Please sign in to comment.