Switched SQL backend to MySQL

doc_update
Joey Hines 2018-07-21 13:28:31 -05:00
parent c821491cd4
commit 4c0d8d2384
5 changed files with 97 additions and 73 deletions

34
BotConfig.py 100644
View File

@ -0,0 +1,34 @@
import configparser
def read_config():
config = configparser.ConfigParser()
config.read('GeoffreyConfig.ini')
if len(config.sections()) == 0:
create_config(config)
print("GeoffreyConfig.ini generated.")
quit(0)
return config
def create_config(config):
config['Discord'] = {'Token': ''}
config['SQL'] = {'Dialect+Driver': '', 'username': '', 'password':'', 'host': '', 'port': '', 'database':'',
'test_args':''}
with open('GeoffreyConfig.ini', 'w') as configfile:
config.write(configfile)
def get_engine_arg(config):
driver = config['SQL']['Dialect+Driver']
username = config['SQL']['username']
password = config['SQL']['password']
host = config['SQL']['host']
port = config['SQL']['port']
database_name = config['SQL']['database']
engine_args = '{}://{}:{}@{}:{}/{}'
return engine_args.format(driver, username, password, host, port, database_name)

View File

@ -24,4 +24,8 @@ class PlayerNotFound(DataBaseError):
class EntryNameNotUniqueError(DataBaseError): class EntryNameNotUniqueError(DataBaseError):
'''A location by that name is already in the database.''' '''A location by that name is already in the database.'''
class StringTooLong(DataBaseError):
'''Given string is too long.'''

View File

@ -1,13 +1,12 @@
from sqlalchemy import Column, Integer, String, ForeignKey, Enum from sqlalchemy import Column, Integer, String, ForeignKey, Enum, create_engine, exists, MetaData
import enum
from sqlalchemy.ext.declarative import declarative_base
from BotErrors import *
from sqlalchemy import create_engine, exists, literal
from sqlalchemy.orm import sessionmaker, relationship, column_property from sqlalchemy.orm import sessionmaker, relationship, column_property
from sqlalchemy.exc import IntegrityError from sqlalchemy.exc import IntegrityError, DataError
import sqlalchemy from sqlalchemy.ext.declarative import declarative_base
from MinecraftAccountInfoGrabber import * from sqlalchemy.sql import expression
from difflib import SequenceMatcher from difflib import SequenceMatcher
import enum
from BotErrors import *
from MinecraftAccountInfoGrabber import *
SQL_Base = declarative_base() SQL_Base = declarative_base()
@ -184,7 +183,7 @@ class DatabaseInterface:
def delete_location(self, owner, name): def delete_location(self, owner, name):
expr = (Location.owner == owner) & (Location.name == name) expr = (Location.owner == owner) & (Location.name == name)
self.database.delete_entry(Shop, expr) self.database.delete_entry(Location, expr)
class DiscordDatabaseInterface(DatabaseInterface): class DiscordDatabaseInterface(DatabaseInterface):
@ -243,6 +242,7 @@ class GeoffreyDatabase:
self.engine = create_engine(engine_arg, echo=True) self.engine = create_engine(engine_arg, echo=True)
Session = sessionmaker(bind=self.engine) Session = sessionmaker(bind=self.engine)
self.session = Session() self.session = Session()
self.meta = MetaData()
SQL_Base.metadata.create_all(self.engine) SQL_Base.metadata.create_all(self.engine)
def add_object(self, obj): def add_object(self, obj):
@ -252,7 +252,12 @@ class GeoffreyDatabase:
self.session.add(obj) self.session.add(obj)
self.session.commit() self.session.commit()
except IntegrityError: except IntegrityError:
self.session.rollback()
raise EntryNameNotUniqueError raise EntryNameNotUniqueError
except DataError:
self.session.rollback()
raise StringTooLong
def query_by_filter(self, obj_type, * args): def query_by_filter(self, obj_type, * args):
filter_value = self.combine_filter(args) filter_value = self.combine_filter(args)
@ -264,6 +269,7 @@ class GeoffreyDatabase:
if entry.first() is not None: if entry.first() is not None:
entry.delete() entry.delete()
self.session.commit()
else: else:
raise DeleteEntryError raise DeleteEntryError
@ -277,7 +283,7 @@ class GeoffreyDatabase:
return s return s
def combine_filter(self, filter_value): def combine_filter(self, filter_value):
return sqlalchemy.sql.expression.and_(filter_value[0]) return expression.and_(filter_value[0])
class TunnelDirection(enum.Enum): class TunnelDirection(enum.Enum):
@ -321,11 +327,12 @@ class Dimension(enum.Enum):
class Player(SQL_Base): class Player(SQL_Base):
__tablename__ = 'Players' __tablename__ = 'Players'
id = Column(Integer, primary_key=True, autoincrement=True) id = Column(Integer, primary_key=True, autoincrement=True)
mc_uuid = Column(String) mc_uuid = Column(String(128))
discord_uuid = Column(String) discord_uuid = Column(String(128))
name = Column(String) name = Column(String(128))
locations = relationship("Location", back_populates="owner", lazy='dynamic', locations = relationship("Location", back_populates="owner", lazy='dynamic',
cascade="save-update, merge, delete, delete-orphan") cascade="save-update, merge, delete, delete-orphan")
tunnels = relationship("Tunnel", back_populates="owner", lazy='dynamic', tunnels = relationship("Tunnel", back_populates="owner", lazy='dynamic',
cascade="save-update, merge, delete, delete-orphan") cascade="save-update, merge, delete, delete-orphan")
@ -342,8 +349,8 @@ class Tunnel(SQL_Base):
tunnel_direction = Column(Enum(TunnelDirection)) tunnel_direction = Column(Enum(TunnelDirection))
owner_id = Column(Integer, ForeignKey('Players.id')) owner_id = Column(Integer, ForeignKey('Players.id'))
owner = relationship("Player", back_populates="tunnels", cascade="save-update, merge, delete") owner = relationship("Player", back_populates="tunnels", cascade="save-update, merge, delete")
location_id = Column(Integer, ForeignKey('Locations.id')) location_id = Column(Integer, ForeignKey('Locations.id', ondelete='CASCADE'))
location = relationship("Location", back_populates="tunnel", cascade="save-update, merge, delete") location = relationship("Location", back_populates="tunnel")
def __init__(self, owner, tunnel_color, tunnel_number, location=None): def __init__(self, owner, tunnel_color, tunnel_number, location=None):
try: try:
@ -362,18 +369,16 @@ class Location(SQL_Base):
__tablename__ = 'Locations' __tablename__ = 'Locations'
id = Column(Integer, primary_key=True) id = Column(Integer, primary_key=True)
name = Column(String, unique=True) name = Column(String(128), unique=True, )
x = Column(Integer) x = Column(Integer)
y = Column(Integer)
z = Column(Integer) z = Column(Integer)
tunnel = relationship("Tunnel", back_populates="location", uselist=False, tunnel = relationship("Tunnel", uselist=False, cascade="all, delete-orphan")
cascade="save-update, merge, delete, delete-orphan")
dimension = Column(Enum(Dimension)) dimension = Column(Enum(Dimension))
owner_id = Column(Integer, ForeignKey('Players.id')) owner_id = Column(Integer, ForeignKey('Players.id', ondelete='CASCADE'))
owner = relationship("Player", back_populates="locations", cascade="save-update, merge, delete") owner = relationship("Player", back_populates="locations", cascade="all, delete-orphan", single_parent=True)
type = Column(String) type = Column(String(128))
__mapper_args__ = { __mapper_args__ = {
'polymorphic_on': type, 'polymorphic_on': type,
@ -413,15 +418,13 @@ class Location(SQL_Base):
class Shop(Location): class Shop(Location):
__tablename__ = 'Shops' __tablename__ = 'Shops'
shop_id = Column(Integer, ForeignKey('Locations.id'), primary_key=True) shop_id = Column(Integer, ForeignKey('Locations.id', ondelete='CASCADE'), primary_key=True)
name = column_property(Column(String), Location.name) name = column_property(Column(String(128)), Location.name)
inventory = relationship('ItemListing', back_populates='shop', cascade='all, delete-orphan') inventory = relationship('ItemListing', back_populates='shop', cascade='all, delete-orphan')
__mapper_args__ = { __mapper_args__ = {
'polymorphic_identity': 'Shop', 'polymorphic_identity': 'Shop',
} }
column_property()
def inv_to_str(self): def inv_to_str(self):
if len(self.inventory.all()) != 0: if len(self.inventory.all()) != 0:
@ -449,12 +452,12 @@ class ItemListing(SQL_Base):
__tablename__ = 'Items' __tablename__ = 'Items'
id = Column(Integer, primary_key=True, autoincrement=True) id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String) name = Column(String(128))
price = Column(Integer) price = Column(Integer)
amount = Column(Integer) amount = Column(Integer)
shop_id = Column(Integer, ForeignKey('Shops.shop_id')) shop_id = Column(Integer, ForeignKey('Shops.shop_id', ondelete='CASCADE'))
shop = relationship("Shop", back_populates="inventory") shop = relationship("Shop", back_populates="inventory", single_parent=True)
def __init__(self, name, price, amount, shop): def __init__(self, name, price, amount, shop):
self.name = name self.name = name

View File

@ -3,7 +3,8 @@ from DatabaseModels import *
from BotErrors import * from BotErrors import *
from MinecraftAccountInfoGrabber import * from MinecraftAccountInfoGrabber import *
from itertools import zip_longest from itertools import zip_longest
import configparser from BotConfig import *
import shlex import shlex
#from WebInterface import * #from WebInterface import *
@ -313,46 +314,15 @@ def get_args_dict(args):
return {} return {}
def create_config():
config['Discord'] = {'Token': ''}
config['SQL'] = {'Dialect+Driver': 'Test', 'username': '', 'password':'', 'host': '', 'port': '', 'database':''}
with open('GeoffreyConfig.ini', 'w') as configfile:
config.write(configfile)
def get_engine_arg(config):
driver = config['SQL']['Dialect+Driver']
username = config['SQL']['username']
password = config['SQL']['password']
host = config['SQL']['host']
port = config['SQL']['port']
database_name = config['SQL']['database']
engine_args = '{}://{}:{}@{}:{}/{}'
return engine_args.format(driver, username, password, host, port, database_name)
# Bot Startup ****************************************************************** # Bot Startup ******************************************************************
config = read_config()
config = configparser.ConfigParser()
config.read('GeoffreyConfig.ini')
if len(config.sections()) == 0:
create_config()
print("GeoffreyConfig.ini generated.")
quit(0)
else:
TOKEN = config['Discord']['Token'] TOKEN = config['Discord']['Token']
if config['SQL']['dialect+driver'] == 'Test':
engine_arg = 'sqlite:///temp.db'
else:
engine_arg = get_engine_arg(config) engine_arg = get_engine_arg(config)
database_interface = DiscordDatabaseInterface(engine_arg) database_interface = DiscordDatabaseInterface(engine_arg)
#WebInterface('127.0.0.1', 8081, database)
bot.run(TOKEN) bot.run(TOKEN)

View File

@ -2,14 +2,28 @@ from unittest import TestCase
from DatabaseModels import * from DatabaseModels import *
from BotErrors import * from BotErrors import *
from MinecraftAccountInfoGrabber import * from MinecraftAccountInfoGrabber import *
from BotConfig import *
class TestGeoffreyDatabase(TestCase): class TestGeoffreyDatabase(TestCase):
def setUp(self): def setUp(self):
self.interface = DiscordDatabaseInterface('sqlite:///:memory:') config = read_config()
engine_arg = config['SQL']['test_args']
self.interface = DiscordDatabaseInterface(engine_arg)
self.owner = Player('ZeroHD', '143072699567177728') self.owner = Player('ZeroHD', '143072699567177728')
self.loc = Location('test', 1, 3, self.owner, dimension='Nether') self.loc = Location('test', 1, 3, self.owner, dimension='Nether')
self.tunnel = Tunnel(self.owner, 'Green', 105, self.loc) self.tunnel = Tunnel(self.owner, 'Green', 105, self.loc)
def tearDown(self):
self.interface.database.session.query(Tunnel).delete()
self.interface.database.session.query(ItemListing).delete()
self.interface.database.session.query(Shop).delete()
self.interface.database.session.query(Location).delete()
self.interface.database.session.query(Player).delete()
self.interface.database.session.commit()
def test_add_object(self): def test_add_object(self):
self.interface.database.add_object(self.loc) self.interface.database.add_object(self.loc)
self.interface.database.add_object(self.owner) self.interface.database.add_object(self.owner)
@ -184,12 +198,11 @@ class TestGeoffreyDatabase(TestCase):
def test_big_input(self): def test_big_input(self):
owner = self.add_player() owner = self.add_player()
loc = self.interface.add_location('143072699567177728',
'TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT', 0, 0, 0)
loc_list = self.interface.find_location_by_owner(owner) self.assertRaises(StringTooLong, self.interface.add_location, '143072699567177728',
'TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT'
self.assertEqual(loc_list[0].id, loc.id) 'TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT'
'TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT', 0, 0,)
def test_duplicate_name(self): def test_duplicate_name(self):
self.add_player() self.add_player()
@ -207,7 +220,7 @@ class TestGeoffreyDatabase(TestCase):
self.interface.delete_location('143072699567177728', 'test') self.interface.delete_location('143072699567177728', 'test')
shops = self.interface.find_shop_selling_item('dirt') shops = self.interface.find_shop_selling_item('dirt')
self.assertGreater(len(shops), 0) self.assertEqual(len(shops), 0)