Merged in feature/HTTPASTE-10/backend/mysql (pull request #10)

feat(backend/mysql): initialize mysql backend
This commit is contained in:
Tiara Rodney 2022-04-02 22:34:12 +00:00
commit 5a6c6431e9
7 changed files with 384 additions and 0 deletions

View file

@ -14,3 +14,9 @@ Filesystem
.. autoclass:: httpaste.backend.file.Parameters
:members:
MySQL
-----
.. autoclass:: httpaste.backend.mysql.Parameters
:members:

View file

@ -11,9 +11,13 @@ from .sqlite import Parameters as SqliteParameters
from .sqlite import User as SqliteUser
from .sqlite import Paste as SqlitePaste
from .sqlite import get_connection as get_sqlite_connection
from .mysql import get_connection as get_mysql_connection
from .file import Parameters as FileParameters
from .file import User as FileUser
from .file import Paste as FilePaste
from .mysql import Parameters as MySQLParameters
from .mysql import User as MySQLUser
from .mysql import Paste as MySQLPaste
class SQLite(Backend):
@ -46,6 +50,22 @@ class File(Backend):
self.paste = FilePaste(parameters, Paste, PasteDataSchema)
class MySQL(Backend):
"""MySQL backend interface
"""
parameter_class = MySQLParameters
user: MySQLUser
paste: MySQLPaste
def __init__(self, parameters: MySQLParameters):
parameters = MySQLParameters(*parameters[1:], get_mysql_connection(parameters))
self.user = MySQLUser(parameters, User)
self.paste = MySQLPaste(parameters, Paste)
def get_backend_map() -> Dict[str, Tuple[type, type]]:
"""get a map of backend ids and their classes
"""

View file

@ -0,0 +1,120 @@
"""MySQL backend
"""
try:
from mysql.connector import connect
from mysql.connector.connection import MySQLConnection
except ImportError as e:
raise ImportError(' '.join((
'\'mysql-connector-python\' is not installed.',
'Install it by running',
'\'python3 -m pip install mysql-connector-python\'.'
))) from e
from typing import NamedTuple, Optional
from . import user
from . import paste
class Parameters(NamedTuple):
"""MySQL parameters
"""
#: user name
user: str
#: user password
password: str
#: hostname or IP address
host: str
#: database identifier
database: str
#: a mysql.connection.MySQLConnection object (does not apply to config)
connection: Optional[MySQLConnection] = None
class User(object):
"""MySQL user model backend
"""
connection: MySQLConnection
def __init__(self, parameters: Parameters, model_class: type) -> None:
self.model_class = model_class
self.connection = get_connection(parameters)
def load(self, proto: object) -> object:
return user.load(proto, self.connection, self.model_class)
def dump(self, model: object) -> None:
return user.dump(model, self.connection)
def delete(self, proto: object) -> None:
return user.delete(proto, self.connection)
def init(self) -> None:
return user.init(self.connection)
def sanitize(self) -> None:
return user.sanitize(self.connection, self.model_class)
class Paste(object):
"""MySQL paste model backend
"""
connection: MySQLConnection
def __init__(self, parameters: Parameters, model_class: type) -> None:
self.model_class = model_class
self.connection = get_connection(parameters)
def load(self, proto: object) -> object:
return paste.load(proto, self.connection, self.model_class)
def dump(self, model: object) -> None:
return paste.dump(model, self.connection)
def delete(self, proto: object) -> None:
return paste.delete(proto, self.connection)
def init(self) -> None:
return paste.init(self.connection)
def sanitize(self) -> None:
return paste.sanitize(self.connection, self.model_class)
def get_connection(parameters: Parameters) -> MySQLConnection:
"""get a mysql.connection.MySQLConnection object
"""
if parameters.connection is not None:
return parameters.connection
connection = connect(user=parameters.user, password=parameters.password,
host=parameters.host,
database=parameters.database)
return connection
__all__ = [
Parameters,
User,
Paste
]

View file

@ -0,0 +1,122 @@
from os import path
from time import time
try:
from mysql.connector.connection import MySQLConnection
except ImportError as e:
raise ImportError(' '.join((
'\'mysql-connector-python\' is not installed.',
'Install it by running',
'\'python3 -m pip install mysql-connector-python\'.'
))) from e
def load(proto:object, connection: MySQLConnection, model_class: type):
"""load a paste model
:param model: model prototype
:param connection: mysql connector connection object
:param model_class: model class
"""
cursor = connection.cursor(dictionary=True)
statement = '''SELECT pid, data, data_hash, sub, expiration, encoding
FROM httpaste_pastes
WHERE pid=%s'''
cursor.execute(statement, (proto.pid,))
row = cursor.fetchone()
if row is not None:
return model_class(
row['pid'],
row['sub'],
row['data'],
row['data_hash'],
row['expiration'],
row['encoding'])
return None
def dump(model:object, connection: MySQLConnection):
"""dump a paste model
:param model: model object
:param connection: mysql connector connection object
:param model_class: model class
"""
cursor = connection.cursor()
statement = '''REPLACE INTO httpaste_pastes
(pid, data, data_hash, sub, expiration, encoding)
VALUES (%s, %s, %s, %s, %s, %s)'''
cursor.execute(statement, (model.pid, model.data, model.data_hash,
model.sub, model.expiration, model.encoding))
connection.commit()
return None
def delete(proto: object, connection: MySQLConnection):
"""delete a paste model
:param model: model prototype
:param connection: mysql connector connection object
:param model_class: model class
"""
cursor = connection.cursor()
statement = '''DELETE FROM httpaste_pastes
WHERE pid=%s'''
cursor.execute(statement, (proto.pid,))
connection.commit()
return None
def init(connection: MySQLConnection):
"""initialize paste model table
:param connection: mysql connector connection object
"""
cursor = connection.cursor()
with open(path.join(path.dirname(__file__), 'paste.sql'), 'r') as fh:
cursor.execute(fh.read())
connection.commit()
return None
def sanitize(connection: MySQLConnection, model_class:type):
"""sanitize paste model table
:param connection: mysql connector connection object
"""
cursor = connection.cursor(dictionary=True)
statement = '''SELECT pid
FROM httpaste_pastes
WHERE expiration < %s AND expiration > 0'''
cursor.execute(statement, (time(),))
for row in cursor.fetchall():
delete(model_class(row['pid']))
return None

View file

@ -0,0 +1,9 @@
CREATE TABLE `httpaste_pastes` (
`pid` blob NOT NULL,
`data` longblob NOT NULL,
`data_hash` blob NOT NULL,
`sub` blob DEFAULT NULL,
`expiration` int(16) NOT NULL,
`encoding` tinytext DEFAULT NULL,
PRIMARY KEY (`pid`(128))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

View file

@ -0,0 +1,101 @@
from os import path
try:
from mysql.connector.connection import MySQLConnection
except ImportError as e:
raise ImportError(' '.join((
'\'mysql-connector-python\' is not installed.',
'Install it by running',
'\'python3 -m pip install mysql-connector-python\'.'
))) from e
def load(proto:object, connection: MySQLConnection, model_class: type):
"""load a user model
:param model: model prototype
:param connection: mysql connector connection object
:param model_class: model class
"""
cursor = connection.cursor(dictionary=True)
statement = '''SELECT sub, key_hash, paste_index
FROM httpaste_users
WHERE sub=%s'''
cursor.execute(statement, (proto.sub,))
row = cursor.fetchone()
if row is not None:
return model_class(row['sub'], row['key_hash'], row['paste_index'])
return None
def dump(model:object, connection: MySQLConnection):
"""dump a user model
:param model: model object
:param connection: mysql connector connection object
:param model_class: model class
"""
cursor = connection.cursor()
statement = '''REPLACE INTO httpaste_users
(sub, key_hash, paste_index)
VALUES (%s, %s, %s)'''
cursor.execute(statement, (model.sub, model.key_hash, model.index))
connection.commit()
return None
def delete(proto: object, connection: MySQLConnection):
"""delete a user model
:param model: model prototype
:param connection: mysql connector connection object
:param model_class: model class
"""
cursor = connection.cursor()
statement = '''DELETE FROM httpaste_users
WHERE sub=%s'''
cursor.execute(statement, (proto.sub,))
connection.commit()
return None
def init(connection: MySQLConnection):
"""initialize user model table
:param connection: mysql connector connection object
"""
cursor = connection.cursor()
with open(path.join(path.dirname(__file__), 'user.sql'), 'r') as fh:
cursor.execute(fh.read())
connection.commit()
return None
def sanitize(connection: MySQLConnection, model_class: type):
"""sanitize user model table
:param connection: mysql connector connection object
"""
return None

View file

@ -0,0 +1,6 @@
CREATE TABLE `httpaste_users` (
`sub` blob NOT NULL,
`key_hash` blob NOT NULL,
`paste_index` blob NOT NULL,
PRIMARY KEY (`sub`(128))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;