Begin implementing better DB layer
This commit is contained in:
parent
0878e830e4
commit
2766286a7e
3 changed files with 173 additions and 30 deletions
|
@ -1,28 +1,141 @@
|
||||||
|
import enum
|
||||||
import sqlite3
|
import sqlite3
|
||||||
|
|
||||||
from nntp.tiny.message import Message
|
class DatabaseOrder(enum.Enum):
|
||||||
|
DEFAULT = 0
|
||||||
|
ASC = 1
|
||||||
|
DESC = 2
|
||||||
|
|
||||||
|
class DatabaseTable():
|
||||||
|
pass
|
||||||
|
|
||||||
|
class DatabaseTableCursor():
|
||||||
|
__slots__ = 'cr', 'table',
|
||||||
|
|
||||||
|
def __init__(self, table, cr):
|
||||||
|
self.cr = cr
|
||||||
|
self.table = table
|
||||||
|
|
||||||
|
def __getattr__(self, name):
|
||||||
|
return getattr(self.cr, name)
|
||||||
|
|
||||||
|
def __map__(self, row):
|
||||||
|
obj = self.table()
|
||||||
|
|
||||||
|
for name in self.table.columns:
|
||||||
|
try:
|
||||||
|
setattr(obj, name, row[name])
|
||||||
|
except IndexError:
|
||||||
|
setattr(obj, name, None)
|
||||||
|
|
||||||
|
return obj
|
||||||
|
|
||||||
|
def fetchone(self):
|
||||||
|
return self.__map__(self.cr.fetchone())
|
||||||
|
|
||||||
|
def fetchall(self):
|
||||||
|
return map(self.__map__, self.cr.fetchall())
|
||||||
|
|
||||||
class Database():
|
class Database():
|
||||||
__slots__ = 'db',
|
__slots__ = 'db',
|
||||||
|
|
||||||
def __init__(self, path: str):
|
def __init__(self, db):
|
||||||
self.db = sqlite3.connect(path)
|
self.db = db
|
||||||
|
|
||||||
def message_add(self, newsgroup_id: int, message: Message):
|
def __getattr__(self, name):
|
||||||
sql = """
|
return getattr(self.db, name)
|
||||||
insert into
|
|
||||||
newsgroup_message
|
|
||||||
(newsgroup_id, message_id, created_on, sender, subject, content) values (?, ?, ?, ?, ?, ?)
|
|
||||||
"""
|
|
||||||
|
|
||||||
self.db.execute(sql, (
|
@staticmethod
|
||||||
newsgroup_id,
|
def connect(path):
|
||||||
message.id(),
|
db = sqlite3.connect(path)
|
||||||
message.date().isoformat(),
|
db.row_factory = sqlite3.Row
|
||||||
message.sender(),
|
|
||||||
message.subject(),
|
|
||||||
message.content
|
|
||||||
))
|
|
||||||
|
|
||||||
def commit(self):
|
return Database(db)
|
||||||
self.db.commit()
|
|
||||||
|
def add(self, obj):
|
||||||
|
table = type(obj)
|
||||||
|
sql = f"insert into {table.name} ("
|
||||||
|
|
||||||
|
first = True
|
||||||
|
|
||||||
|
for column in table.columns:
|
||||||
|
if column == table.key:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if first:
|
||||||
|
sql += column
|
||||||
|
first = False
|
||||||
|
else:
|
||||||
|
sql += ', ' + column
|
||||||
|
|
||||||
|
sql += ') values ('
|
||||||
|
|
||||||
|
first = True
|
||||||
|
|
||||||
|
for column in table.columns:
|
||||||
|
if column == table.key:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if first:
|
||||||
|
first = False
|
||||||
|
else:
|
||||||
|
sql += ', '
|
||||||
|
|
||||||
|
sql += '?'
|
||||||
|
|
||||||
|
sql += ')'
|
||||||
|
|
||||||
|
fn = getattr(obj, '__values__')
|
||||||
|
|
||||||
|
if fn is not None:
|
||||||
|
values = fn()
|
||||||
|
else:
|
||||||
|
values = list()
|
||||||
|
|
||||||
|
for column in table.columns:
|
||||||
|
if column != table.key:
|
||||||
|
values.append(getattr(obj, column))
|
||||||
|
|
||||||
|
self.db.execute(sql, values)
|
||||||
|
|
||||||
|
def query(self, table, values=dict(), order_by=list()):
|
||||||
|
sql = f"select * from {table.name}"
|
||||||
|
|
||||||
|
params = list()
|
||||||
|
first = True
|
||||||
|
|
||||||
|
for key in values.keys():
|
||||||
|
if first:
|
||||||
|
sql += f" where {table.name}.{key} = ?"
|
||||||
|
|
||||||
|
first = False
|
||||||
|
else:
|
||||||
|
sql += f" and {table.name}.{key} = ?"
|
||||||
|
|
||||||
|
params.append(values[key])
|
||||||
|
|
||||||
|
first = True
|
||||||
|
|
||||||
|
if len(order_by) > 0:
|
||||||
|
sql += " order by"
|
||||||
|
|
||||||
|
for column, order in order_by:
|
||||||
|
if first:
|
||||||
|
first = False
|
||||||
|
else:
|
||||||
|
sql += ", "
|
||||||
|
|
||||||
|
if order is None or order is DatabaseOrder.DEFAULT:
|
||||||
|
sql += f" {column}"
|
||||||
|
elif order is DatabaseOrder.ASC:
|
||||||
|
sql += f" {column} asc"
|
||||||
|
elif order is DatabaseOrder.DESC:
|
||||||
|
sql += f" {column} desc"
|
||||||
|
|
||||||
|
cr = DatabaseTableCursor(table, self.db.cursor())
|
||||||
|
cr.execute(sql, params)
|
||||||
|
|
||||||
|
return cr
|
||||||
|
|
||||||
|
def get(self, table, values=dict()):
|
||||||
|
return self.query(table, values).fetchone()
|
||||||
|
|
|
@ -5,6 +5,8 @@ import datetime
|
||||||
from dateparser.search import search_dates
|
from dateparser.search import search_dates
|
||||||
from email.header import decode_header
|
from email.header import decode_header
|
||||||
|
|
||||||
|
from nntp.tiny.db import DatabaseTable
|
||||||
|
|
||||||
def decode(text: str):
|
def decode(text: str):
|
||||||
decoded = decode_header(text)[0]
|
decoded = decode_header(text)[0]
|
||||||
|
|
||||||
|
@ -21,19 +23,41 @@ class MessageState(enum.Enum):
|
||||||
HEADER = 1
|
HEADER = 1
|
||||||
BODY = 2
|
BODY = 2
|
||||||
|
|
||||||
class Message():
|
class Message(DatabaseTable):
|
||||||
__slots__ = 'state', 'headers', 'line', 'content', 'body', 'key',
|
__slots__ = 'newsgroup_id', 'state', 'headers', 'line', 'content', 'body', '_key',
|
||||||
|
|
||||||
|
name = 'newsgroup_message'
|
||||||
|
key = 'id'
|
||||||
|
columns = (
|
||||||
|
'newsgroup_id',
|
||||||
|
'message_id',
|
||||||
|
'created_on',
|
||||||
|
'sender',
|
||||||
|
'subject',
|
||||||
|
'content'
|
||||||
|
)
|
||||||
|
|
||||||
RE_HEADER = re.compile(r'^([A-Za-z0-9\-]+): (.*)$')
|
RE_HEADER = re.compile(r'^([A-Za-z0-9\-]+): (.*)$')
|
||||||
RE_MESSAGE_ID = re.compile(r'^<([^<>]+)>$')
|
RE_MESSAGE_ID = re.compile(r'^<([^<>]+)>$')
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.state = MessageState.EMPTY
|
self.newsgroup_id = None
|
||||||
self.headers = dict()
|
self.state = MessageState.EMPTY
|
||||||
self.line = None
|
self.headers = dict()
|
||||||
self.content = ''
|
self.line = None
|
||||||
self.body = None
|
self.content = ''
|
||||||
self.key = None
|
self.body = None
|
||||||
|
self._key = None
|
||||||
|
|
||||||
|
def __values__(self):
|
||||||
|
return (
|
||||||
|
self.newsgroup_id,
|
||||||
|
self.id(),
|
||||||
|
self.date(),
|
||||||
|
self.sender(),
|
||||||
|
self.subject(),
|
||||||
|
self.content
|
||||||
|
)
|
||||||
|
|
||||||
def add(self, line: str):
|
def add(self, line: str):
|
||||||
if self.line is not None:
|
if self.line is not None:
|
||||||
|
@ -46,14 +70,14 @@ class Message():
|
||||||
if line == '\n' or line == '\r\n':
|
if line == '\n' or line == '\r\n':
|
||||||
self.state = MessageState.BODY
|
self.state = MessageState.BODY
|
||||||
elif line[0] == ' ' or line[0] == '\t':
|
elif line[0] == ' ' or line[0] == '\t':
|
||||||
self.headers[self.key] += ' ' + decode(line.strip())
|
self.headers[self._key] += ' ' + decode(line.strip())
|
||||||
else:
|
else:
|
||||||
match = self.RE_HEADER.match(line)
|
match = self.RE_HEADER.match(line)
|
||||||
|
|
||||||
if match:
|
if match:
|
||||||
self.key = match[1].lower()
|
self._key = match[1].lower()
|
||||||
|
|
||||||
self.headers[self.key] = decode(match[2].rstrip())
|
self.headers[self._key] = decode(match[2].rstrip())
|
||||||
elif self.state is MessageState.BODY:
|
elif self.state is MessageState.BODY:
|
||||||
if self.body is None:
|
if self.body is None:
|
||||||
self.body = ''
|
self.body = ''
|
||||||
|
|
6
lib/nntp/tiny/newsgroup.py
Normal file
6
lib/nntp/tiny/newsgroup.py
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
from nntp.tiny.db import DatabaseTable
|
||||||
|
|
||||||
|
class Newsgroup(DatabaseTable):
|
||||||
|
name = 'newsgroup'
|
||||||
|
key = 'id'
|
||||||
|
columns = 'id', 'name', 'description',
|
Loading…
Add table
Reference in a new issue