Skip to content

Commit

Permalink
Persistent Storage (#13)
Browse files Browse the repository at this point in the history
  • Loading branch information
super-cooper committed Feb 22, 2021
1 parent 10da978 commit e571b65
Show file tree
Hide file tree
Showing 14 changed files with 156 additions and 21 deletions.
8 changes: 8 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
**/__pycache__/
.dockerignore
.git/
.github/
.gitignore
data/
docker/
install.bash
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -104,5 +104,6 @@ venv.bak/

# project specific
client_token
data/
twitter_api_tokens.json
.vscode/
16 changes: 0 additions & 16 deletions Dockerfile

This file was deleted.

2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,5 @@ Current commands that can be used in Discord:
!role - Self-contained role management

## Docker
Memebot has a straightforward Docker image that can be build based on the [Dockerfile](./Dockerfile) in this
Memebot has a straightforward Docker image that can be build based on the [Dockerfile](./docker/Dockerfile) in this
repository. This image can be used for both deployment and testing purposes.
29 changes: 29 additions & 0 deletions docker/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
FROM python:3.8.1-slim

ARG CLIENT_TOKEN_FILE
ARG TWITTER_API_TOKENS_FILE

ARG DATABASE_URI
ENV DBURI $DATABASE_URI

# pull in required files
WORKDIR /home/memebot/
COPY src src
COPY requirements.txt requirements.txt
COPY $CLIENT_TOKEN_FILE client_token
COPY $TWITTER_API_TOKENS_FILE twitter_api_tokens.json

# set up virtual environment
ENV VIRTUAL_ENV "/venv"
ENV PATH "$VIRTUAL_ENV/bin:$PATH"

RUN \
apt update -y && \
# gcc is required to build package aiohttp (https://docs.aiohttp.org/en/stable/) required by discord.py
apt install -y gcc && \
python -m venv $VIRTUAL_ENV && \
# install dependencies
python -m pip install -r requirements.txt

# run memebot
CMD python3 src/main.py --database-uri ${DBURI}
35 changes: 35 additions & 0 deletions docker/docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
version: '3.1'

services:
bot:
container_name: memebot-bot
build:
dockerfile: docker/Dockerfile
context: ..
args:
CLIENT_TOKEN_FILE: client_token
TWITTER_API_TOKENS_FILE: twitter_api_tokens.json
DATABASE_URI: "mongodb://db:27017"
restart: always
depends_on:
- db
environment:
PYTHONUNBUFFERED: 1
networks:
default:
db:
container_name: memebot-db
image: mongo:4.4.4-bionic
restart: always
volumes:
- ../data/db:/data/db
- ../src/config/mongod.yaml:/etc/mongo/mongod.yaml:ro
networks:
default:

networks:
default:
driver: bridge
ipam:
config:
- subnet: 172.19.0.0/24
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
discord.py
pymongo
python-twitter
10 changes: 6 additions & 4 deletions src/commands/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import os
from typing import List

import config
from . import registry, execution
from .command import Command, CommandOutput

Expand All @@ -14,8 +15,9 @@ def dynamically_register_commands() -> None:
def find_and_register_subcommands(top_level_command: Command, cmd_path: List[str]) -> None:
# Do a depth-first search to register subcommands
for subcommand in top_level_command.subcommands:
registry.register_subcommand(cmd_path, subcommand)
find_and_register_subcommands(subcommand, cmd_path + [subcommand.name])
if not (subcommand.requires_database and not config.database_enabled):
registry.register_subcommand(cmd_path, subcommand)
find_and_register_subcommands(subcommand, cmd_path + [subcommand.name])

# Get all the packages located in the command package
top_level_packages = [f.path for f in os.scandir(os.path.dirname(os.path.realpath(__file__))) if
Expand All @@ -31,8 +33,8 @@ def find_and_register_subcommands(top_level_command: Command, cmd_path: List[str
if issubclass(cmd_class, Command):
instance = cmd_class()
# Registration machinery:
# If the command is a top-level command
if type(cmd_class.parent) is Command:
# If the command is a top-level command and meets configuration requirements
if type(cmd_class.parent) is Command and not (instance.requires_database and not config.database_enabled):
registry.register_top_level_command(instance)
find_and_register_subcommands(instance, [instance.name])

Expand Down
1 change: 1 addition & 0 deletions src/commands/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ def __init__(self, name: str = None, description: str = None, example_args: str
raise ValueError(f"Every command needs to have a description! ({type(self).__name__})")
self.description: str = description
self.example_args: str = example_args
self.requires_database: bool = False

def __repr__(self) -> str:
return f"Command: {type(self).__name__}(name={self.name} description={self.description})"
Expand Down
29 changes: 29 additions & 0 deletions src/config/__init__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
import argparse
import pathlib
import urllib.parse

# Path to the file containing the Discord API token
discord_api_token: pathlib.Path
# Path to the JSON file containing the Twitter API tokens
twitter_api_tokens: pathlib.Path

# Flag which tells if a database connection is enabled
database_enabled: bool
# MongoDB URI
database_uri: urllib.parse.ParseResult


def populate_config_from_command_line():
parser = argparse.ArgumentParser()
Expand All @@ -18,12 +26,33 @@ def populate_config_from_command_line():
default="twitter_api_tokens.json",
type=pathlib.Path)

# Database Configuration
parser.add_argument("--database-enabled",
help="Enable the database connection, and all features which require it.",
dest='database_enabled',
action='store_true')
parser.add_argument("--database-disabled",
help="Disable the database connection, and all features which require it.",
dest="database_enabled",
action="store_false")
parser.set_defaults(database_enabled=True)

parser.add_argument("--database-uri",
help="URI of the MongoDB database server",
default=urllib.parse.urlparse("mongodb://127.0.0.1:27017"),
type=urllib.parse.urlparse)

args = parser.parse_args()

global discord_api_token
global twitter_api_tokens
discord_api_token = args.discord_api_token
twitter_api_tokens = args.twitter_api_tokens

global database_enabled
global database_uri
database_enabled = args.database_enabled
database_uri = args.database_uri


populate_config_from_command_line()
Empty file added src/config/mongod.yaml
Empty file.
17 changes: 17 additions & 0 deletions src/db/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import config
from .internals import DatabaseInternals

db_internals = DatabaseInternals()

if config.database_enabled:
db_internals.connect()


def test() -> bool:
"""
Functions as a "ping" to the databse to ensure that there is an available connection
:return: True if the test succeeds
"""
test_db = db_internals.get_db("test")
test_db.list_collection_names()
return True
25 changes: 25 additions & 0 deletions src/db/internals.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from typing import Optional

import pymongo as mongo
import pymongo.database

import config


class DatabaseInternals:
"""
Class for managing all database internals that do not need to be exposed to the command programmer.
"""

def __init__(self):
self.client: Optional[mongo.MongoClient] = None

def connect(self) -> None:
"""
Create a client connection to a MongoDB database
"""
if self.client is None:
self.client = mongo.MongoClient(config.database_uri.geturl())

def get_db(self, db_name: str) -> mongo.database.Database:
return self.client[db_name]
3 changes: 3 additions & 0 deletions src/memebot.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import commands
import config
import db
from lib import constants


Expand Down Expand Up @@ -35,6 +36,8 @@ def __init__(self, **args):

self.twitter_url_pattern = re.compile(r'https:/{2}twitter\.com/([0-9a-zA-Z_]+|i/web)/status/[0-9]+(\?s=\d+)?')

db.test()

async def on_ready(self) -> None:
"""
Determines what the bot does as soon as it is logged into discord
Expand Down

0 comments on commit e571b65

Please sign in to comment.