Skip to content

Commit

Permalink
Merge pull request #37 from jrsmth/develop
Browse files Browse the repository at this point in the history
Develop
  • Loading branch information
jrsmth authored Mar 11, 2024
2 parents b36a7f2 + 29cf92a commit d186998
Show file tree
Hide file tree
Showing 10 changed files with 112 additions and 92 deletions.
11 changes: 9 additions & 2 deletions documentation/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,20 @@
# Version History
- `0.0.x` Waffle-Bot POC
- `0.1.x` Multiple Spaces
- `0.2.x` Presentation
- `0.2.x` Data Maturity
- `0.3.x` Scroll Command
- `0.4.x` Reactions
- `0.4.x` Presentation
- `0.5.x` Reactions
- `1.0.0` App Distribution

# Releases
<!-- @LatestFirst -->

## [0.2.0]
[Data Maturity](https://github.com/jrsmth/waffle-bot/milestone/6) (08/03/2024)
- `#30` Formalise the conversion of objects into dictionaries (and vice versa) when moving to and from redis [![user](https://img.shields.io/badge/jrsmth-181717.svg?style=flat&logo=github)](https://github.com/jrsmth) [![user](https://img.shields.io/badge/adamj335-181717.svg?style=flat&logo=github)](https://github.com/adamj335)
- `#35` Player not getting updated (score & streak) [![user](https://img.shields.io/badge/jrsmth-181717.svg?style=flat&logo=github)](https://github.com/jrsmth) [![user](https://img.shields.io/badge/adamj335-181717.svg?style=flat&logo=github)](https://github.com/adamj335)

## [0.1.1]
[Multiple Spaces](https://github.com/jrsmth/waffle-bot/milestone/2) (22/02/2024)
- `#29` Added OS check in Makefile to support differences between Windows and Unix-based OS workflows [![user](https://img.shields.io/badge/haydende-181717.svg?style=flat&logo=github)](https://github.com/haydende)
Expand All @@ -31,3 +37,4 @@
[0.0.0]: https://github.com/jrsmth/waffle-bot/releases/tag/0.0.0
[0.1.0]: https://github.com/jrsmth/waffle-bot/compare/0.0.0...0.1.0
[0.1.1]: https://github.com/jrsmth/waffle-bot/compare/0.1.0...0.1.1
[0.2.0]: https://github.com/jrsmth/waffle-bot/compare/0.1.1...0.2.0
16 changes: 8 additions & 8 deletions src/app/archbishop/archbishop.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def hello():
def group(group_id):
""" Get group information by id """
log.debug(f"[get_group] Retrieving group for id [{group_id}]")
return Response(redis.get_complex(group_id), status=200)
return Response(str(redis.get_complex(group_id, Group)), status=200)

@archbishop.route(config.EVENT_PATH, methods=['POST'])
def event():
Expand Down Expand Up @@ -60,13 +60,14 @@ def handle_waffle(message, say):
def get_group(event):
""" Fetch group object from redis """
group_id = event.team
group = redis.get_complex(group_id)
group = redis.get_complex(group_id, Group)
if group is not None:
log.debug(f"[get_group] Group found with id [{group_id}]")
return Group(group)
return group
else:
log.debug(f"[get_group] Creating new group with id [{group_id}]")
group = Group({"name": group_id})
dummy_king = Player("", -1, 0)
group = Group(group_id, [], dummy_king, [])
redis.set_complex(group_id, group)
return group

Expand All @@ -76,18 +77,17 @@ def get_player(event, group):
try:
result = requests.get(slack_user_url, headers={'Authorization': 'Bearer ' + config.BOT_TOKEN})
user = result.json().get("user").get("real_name").split()[0]
potential_player = [p for p in group.players if p["name"] == user]
potential_player = [p for p in group.players if p.name == user]

if not potential_player:
player = Player()
player.name = user
player = Player(user, 0, 0)
group.players.append(player)
redis.set_complex(group.name, group)
log.debug(f"[get_player] [{user}] added to the system")
return player

else:
return Player(potential_player[0])
return potential_player[0]

except SlackApiError as e:
log.error(f"Error fetching user! [{str(e)}]")
Expand Down
30 changes: 18 additions & 12 deletions src/app/model/base.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@
import json
from abc import ABC, abstractmethod
from dataclasses import dataclass
import jsons as jsons
from munch import Munch


class Base:
@dataclass
class Base(ABC):
""" Model Base Class """

def __init__(self, d=None):
""" Constructor that optionally converts dict to obj """
if d is not None:
for key, value in d.items():
if type(value) is dict:
setattr(self, key, Munch().fromDict(value))
else:
setattr(self, key, value)
@classmethod
@abstractmethod
def from_dict(cls, dic):
""" Convert a dictionary to a python object """
pass

def to_string(self):
@classmethod
def from_json(cls, json_str: str):
""" Convert a json string to a python object """
return cls.from_dict(json.loads(json_str))

@classmethod
def to_string(cls):
""" Converts a complex object into a string """
return str(jsons.dump(self))
return str(jsons.dump(cls))
17 changes: 10 additions & 7 deletions src/app/model/event/event.py
Original file line number Diff line number Diff line change
@@ -1,33 +1,36 @@
import re

from munch import Munch

from src.app.model.base import Base


class Element(Base):
class Element:
type = ""
text = ""
unicode = ""


class OuterElement(Base):
class OuterElement:
type = ""
elements = [Element()]


class Block(Base):
class Block:
elements = [OuterElement()]


class Event(Base):
class Event:
""" Enhanced message event object based on Slack's Event API """
type = ""
user = ""
blocks = [Block()]
team = ""
channel = ""

def __init__(self, d=None):
""" Constructor that optionally converts dict to obj """
if d is not None:
for key, value in d.items():
setattr(self, key, value)

def get_score(self):
block: Block = Munch.fromDict(self.blocks[0])
elements: [Element] = block.elements[0].elements
Expand Down
47 changes: 27 additions & 20 deletions src/app/model/group/group.py
Original file line number Diff line number Diff line change
@@ -1,33 +1,42 @@
import datetime

from dataclasses import dataclass
from munch import Munch

from src.app.model.base import Base
from src.app.model.group.record import Record
from src.app.model.group.player import Player


@dataclass
class Group(Base):
""" Collection of players grouped by slack space """
name = ''
players = []
king = Player()
scroll = [Record(), Record(), Record()]
name: str
players: [Player]
king: Player
scroll: [Record]

@classmethod
def from_dict(cls, dic):
king = Player.from_dict(dic["king"])
players = []
for p in dic["players"]:
players.append(Player.from_dict(p))

return cls(
name=dic["name"],
players=players,
king=king,
scroll=dic["scroll"]
)

def update_player(self, player):
for index, p in enumerate(self.players):
if hasattr(p, "name") and p.name == player.name:
if p.name == player.name:
self.players[index] = player

def update_scroll(self, player):
# Create new record
new_record = Record(
{
"name": player.name,
"streak": player.streak,
"date": datetime.datetime.today().strftime('%d/%m/%Y')
}
)
def update_scroll(self, player): # FixMe
""" Create new record """
timestamp = datetime.datetime.today().strftime('%d/%m/%Y')
new_record = Record(player.name, player.streak, timestamp)
scroll = Munch().fromDict(self.scroll)
scroll.append(new_record)
# Sort and remove tail Record
Expand All @@ -38,10 +47,8 @@ def crown(self, player):
self.king = player

def dethrone(self):
self.king = Player({"streak": -1})

non_zeros = [p for p in self.players if p["streak"] != 0]

self.king = Player("", -1, 0)
non_zeros = [p for p in self.players if p.streak != 0]
if non_zeros is not list:
return
else:
Expand Down
17 changes: 14 additions & 3 deletions src/app/model/group/player.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,19 @@
from dataclasses import dataclass

from src.app.model.base import Base


@dataclass
class Player(Base):
""" Tracked player information """
name = ''
streak = 0
score = 0
name: str
streak: int
score: int

@classmethod
def from_dict(cls, dic):
return cls(
name=dic["name"],
streak=dic["streak"],
score=dic["score"]
)
16 changes: 13 additions & 3 deletions src/app/model/group/record.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,18 @@
from dataclasses import dataclass
from src.app.model.base import Base


@dataclass
class Record(Base):
""" Tracked Record for the Historic Streaks """
name = ''
streak = 0
date = '0/0/0000'
name: str
streak: int
date: str # TODO :: actual date

@classmethod
def from_dict(cls, dic):
return cls(
name=dic["name"],
streak=dic["streak"],
date=dic["date"]
)
1 change: 1 addition & 0 deletions src/app/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ pyjavaproperties~=0.7
from_root
Munch
pytest
jsonpickle
47 changes: 11 additions & 36 deletions src/app/util/redis.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import logging
from json import JSONDecodeError
import jsons
import jsonpickle
from flask_redis import FlaskRedis
from upstash_redis import Redis as UpstashRedis
from src.app.model.base import Base


class RedisClient:
Expand All @@ -18,7 +19,7 @@ def get_client(self):
return self.client

def get(self, key):
""" Get an element by its key and decode in utf-8 format """
""" Get an element by its key """
value = self.client.get(key)
if value is None:
return None
Expand All @@ -31,43 +32,17 @@ def set(self, key, value):

def set_complex(self, key, complex_value):
""" Set a complex key-value element by converting to json string """
json_value = standardise(jsons.dump(complex_value))
json_value = jsonpickle.encode(complex_value, unpicklable=False)
self.log.debug("[set_complex] Successful conversion to JSON, setting value: " + json_value)
return self.client.set(key, json_value)

def get_complex(self, key):
def get_complex(self, key, cls: Base):
""" Get a complex key-value element by converting from json string """
json_value = self.client.get(key)
if json_value is None:
return None
if json_value is not None:
try:
return cls.from_json(json_value)
except JSONDecodeError:
raise Exception("[get_complex] Error parsing retrieved object: " + str(json_value))
else:
json_value = json_value
try:
return jsons.loads(standardise(json_value))
except JSONDecodeError:
raise Exception("[get_complex] Error parsing retrieved object: " + str(json_value))

def clear(self):
""" Remove all entries held in Redis """
for key in self.client.scan_iter():
self.client.delete(key)


def standardise(value): # FixMe :: bit dodgy
""" Standardises a JSON string for conversion into a python dict """

# Convert Python `True/False/None` to JSON-standard `true/false/null`
standardised = str(value) \
.replace("False,", "false,") \
.replace("False}", "false}") \
.replace("True,", "true,") \
.replace("True}", "true}") \
.replace("None,", "null,") \
.replace("None}", "null}")

# Convert single-speech (') marks to the JSON-standard double-speech marks (")
return standardised.replace("{\'", "{\"") \
.replace("\'}", "\"}") \
.replace("\':", "\":") \
.replace(" \'", " \"") \
.replace("\',", "\",")
return None
2 changes: 1 addition & 1 deletion src/version/version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "0.1.2-SNAPSHOT"
__version__ = "0.2.0-SNAPSHOT"

0 comments on commit d186998

Please sign in to comment.