From c4a576d415dce676499dfd75beadb85c617651a6 Mon Sep 17 00:00:00 2001 From: Rusty Bower Date: Sun, 13 Jan 2019 20:26:39 -0600 Subject: [PATCH] db: minor code cleanup --- sopel/config/core_section.py | 28 ++++++++++++---- sopel/db.py | 64 ++++++++++++++++++++++-------------- 2 files changed, 60 insertions(+), 32 deletions(-) diff --git a/sopel/config/core_section.py b/sopel/config/core_section.py index 5a62a14fc2..d8249b244c 100644 --- a/sopel/config/core_section.py +++ b/sopel/config/core_section.py @@ -96,23 +96,37 @@ class CoreSection(StaticSection): channels = ListAttribute('channels') """List of channels for the bot to join when it connects""" - db_type = ValidatedAttribute('db_type') - """The type of database to use for Sopel's database. (SQLite or MySQL)""" + db_type = ChoiceAttribute('db_type', choices=[ + 'sqlite', 'mysql', 'postgres', 'mssql', 'oracle', 'firebird', 'sybase'], default='sqlite') + """The type of database to use for Sopel's database. + + mysql - pip install mysql-python (Python 2) or pip install mysqlclient (Python 3) + postgres - pip install psycopg2 + + See https://docs.sqlalchemy.org/en/latest/dialects/ for a full list of dialects""" db_filename = ValidatedAttribute('db_filename') """The filename for Sopel's database. (SQLite only)""" - db_user = ValidatedAttribute('db_username') - """The user for Sopel's database. (MySQL only)""" + db_driver = ValidatedAttribute('db_driver') + """The driver for Sopel's database. + This is optional, but can be specified if user wants to use a non-default driver + https://docs.sqlalchemy.org/en/latest/core/engines.html""" + + db_user = ValidatedAttribute('db_user') + """The user for Sopel's database.""" db_pass = ValidatedAttribute('db_pass') - """The password for Sopel's database. (MySQL only)""" + """The password for Sopel's database.""" db_host = ValidatedAttribute('db_host') - """The host for Sopel's database. (MySQL only)""" + """The host for Sopel's database.""" + + db_port = ValidatedAttribute('db_port') + """The port for Sopel's database.""" db_database = ValidatedAttribute('db_database') - """The database for Sopel's database. (MySQL only)""" + """The database for Sopel's database.""" default_time_format = ValidatedAttribute('default_time_format', default='%Y-%m-%d - %T%Z') diff --git a/sopel/db.py b/sopel/db.py index 878a948952..ecc3805105 100644 --- a/sopel/db.py +++ b/sopel/db.py @@ -8,6 +8,7 @@ from sopel.tools import Identifier from sqlalchemy import create_engine, Column, ForeignKey, Integer, String +from sqlalchemy.engine.url import URL from sqlalchemy.exc import OperationalError, SQLAlchemyError from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import scoped_session, sessionmaker @@ -48,7 +49,7 @@ class Nicknames(BASE): Nicknames SQLAlchemy Class """ __tablename__ = 'nicknames' - nick_id = Column(Integer, ForeignKey('nicknames.nick_id'), primary_key=True) + nick_id = Column(Integer, ForeignKey('nick_ids.nick_id'), primary_key=True) slug = Column(String, primary_key=True) canonical = Column(String) @@ -58,7 +59,7 @@ class NickValues(BASE): NickValues SQLAlchemy Class """ __tablename__ = 'nick_values' - nick_id = Column(Integer, ForeignKey('nicknames.nick_id'), primary_key=True) + nick_id = Column(Integer, ForeignKey('nick_ids.nick_id'), primary_key=True) key = Column(String, primary_key=True) value = Column(String) @@ -88,22 +89,8 @@ def __init__(self, config): # SQLite - sqlite:////home/sopel/.sopel/default.db db_type = config.core.db_type - if db_type and db_type == 'mysql': - db_user = config.core.db_user - db_pass = config.core.db_pass - db_host = config.core.db_host - db_database = config.core.db_database - - # Ensure we have all our variables defined - if db_user is None or db_pass is None \ - or db_host is None or db_database is None: - raise Exception('Please make sure the following core ' - 'configuration values are defined: ' - 'db_user, db_pass, db_host, db_database') - self.engine = create_engine('mysql://%s:%s@%s/%s' % - (db_user, db_pass, db_host, db_database)) - # Otherwise, assume sqlite - else: + # Handle SQLite explicitly as a default + if not db_type or db_type == 'sqlite': path = config.core.db_filename config_dir, config_file = os.path.split(config.filename) config_name, _ = os.path.splitext(config_file) @@ -113,9 +100,41 @@ def __init__(self, config): if not os.path.isabs(path): path = os.path.normpath(os.path.join(config_dir, path)) self.filename = path - self.engine = create_engine('sqlite:///%s' % path) + self.url = 'sqlite:///%s' % path + # Otherwise, handle all other database engines + else: + if db_type == 'mysql': + drivername = config.core.db_driver or 'mysql' + elif db_type == 'postgres': + drivername = config.core.db_driver or 'postgresql' + elif db_type == 'oracle': + drivername = config.core.db_driver or 'oracle' + elif db_type == 'mssql': + drivername = config.core.db_driver or 'mssql+pyodbc' + elif db_type == 'firebird': + drivername = config.core.db_driver or 'firebird+fdb' + elif db_type == 'sybase': + drivername = config.core.db_driver or 'sybase+pysybase' + else: + raise Exception('Unknown db_type') - # Catch any errors connecting to MySQL + db_user = config.core.db_user + db_pass = config.core.db_pass + db_host = config.core.db_host + db_port = config.core.db_port # Optional + db_database = config.core.db_database # Optional, depending on DB + + # Ensure we have all our variables defined + if db_user is None or db_pass is None or db_host is None: + raise Exception('Please make sure the following core ' + 'configuration values are defined: ' + 'db_user, db_pass, db_host') + self.url = URL(drivername=drivername, username=db_user, password=db_pass, + host=db_host, port=db_port, database=db_database) + + self.engine = create_engine(self.url) + + # Catch any errors connecting to database try: self.engine.connect() except OperationalError: @@ -138,11 +157,6 @@ def execute(self, *args, **kwargs): called per PEP 249.""" with self.connect() as conn: return conn.execute(*args, **kwargs) - #cur = conn.cursor() - #return cur.execute(*args, **kwargs) - - def _create(self): - BASE.metadata.create_all(self.engine) def get_uri(self): """Returns a URL for the database, usable to connect with SQLAlchemy."""