Merged in feature/HTTPASTE-10/backend/mysql (pull request #10)
feat(backend/mysql): initialize mysql backend
This commit is contained in:
commit
5a6c6431e9
7 changed files with 384 additions and 0 deletions
|
|
@ -13,4 +13,10 @@ Filesystem
|
||||||
----------
|
----------
|
||||||
|
|
||||||
.. autoclass:: httpaste.backend.file.Parameters
|
.. autoclass:: httpaste.backend.file.Parameters
|
||||||
|
:members:
|
||||||
|
|
||||||
|
MySQL
|
||||||
|
-----
|
||||||
|
|
||||||
|
.. autoclass:: httpaste.backend.mysql.Parameters
|
||||||
:members:
|
:members:
|
||||||
|
|
@ -11,9 +11,13 @@ from .sqlite import Parameters as SqliteParameters
|
||||||
from .sqlite import User as SqliteUser
|
from .sqlite import User as SqliteUser
|
||||||
from .sqlite import Paste as SqlitePaste
|
from .sqlite import Paste as SqlitePaste
|
||||||
from .sqlite import get_connection as get_sqlite_connection
|
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 Parameters as FileParameters
|
||||||
from .file import User as FileUser
|
from .file import User as FileUser
|
||||||
from .file import Paste as FilePaste
|
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):
|
class SQLite(Backend):
|
||||||
|
|
@ -46,6 +50,22 @@ class File(Backend):
|
||||||
self.paste = FilePaste(parameters, Paste, PasteDataSchema)
|
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]]:
|
def get_backend_map() -> Dict[str, Tuple[type, type]]:
|
||||||
"""get a map of backend ids and their classes
|
"""get a map of backend ids and their classes
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
120
src/httpaste/backend/mysql/__init__.py
Normal file
120
src/httpaste/backend/mysql/__init__.py
Normal 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
|
||||||
|
]
|
||||||
122
src/httpaste/backend/mysql/paste.py
Normal file
122
src/httpaste/backend/mysql/paste.py
Normal 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
|
||||||
9
src/httpaste/backend/mysql/paste.sql
Normal file
9
src/httpaste/backend/mysql/paste.sql
Normal 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;
|
||||||
101
src/httpaste/backend/mysql/user.py
Normal file
101
src/httpaste/backend/mysql/user.py
Normal 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
|
||||||
6
src/httpaste/backend/mysql/user.sql
Normal file
6
src/httpaste/backend/mysql/user.sql
Normal 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;
|
||||||
Loading…
Add table
Add a link
Reference in a new issue