Database migration (#45)
All checks were successful
continuous-integration/drone/push Build is passing

Closes #16

Co-authored-by: mitsuha_s <stmiroslavsckaya@yandex.ru>
Reviewed-on: #45
Reviewed-by: Alexey Skobkin <skobkin-ru@ya.ru>
This commit is contained in:
Miroslavsckaya 2022-08-21 23:43:35 +03:00
parent 90a26dd1c8
commit 31c112a4fa
3 changed files with 42 additions and 22 deletions

View file

@ -2,6 +2,7 @@ from logging import Logger
import psycopg2 import psycopg2
from psycopg2.extensions import connection from psycopg2.extensions import connection
from psycopg2.extras import DictCursor, DictRow from psycopg2.extras import DictCursor, DictRow
from yoyo import get_backend, read_migrations
from exceptions import DisplayableException from exceptions import DisplayableException
from rss import FeedItem from rss import FeedItem
@ -10,12 +11,12 @@ class Database:
"""Implement interaction with the database.""" """Implement interaction with the database."""
def __init__(self, dsn: str, log: Logger) -> None: def __init__(self, dsn: str, log: Logger) -> None:
"""Create a database file if not exists.""" """Initialize the database"""
self.log: Logger = log self.log: Logger = log
self.log.debug('Database.__init__(DSN=\'%s\')', dsn) self.log.debug('Database.__init__(DSN=\'%s\')', dsn)
self.conn: connection = psycopg2.connect(dsn) self.conn: connection = psycopg2.connect(dsn)
self.cur: DictCursor = self.conn.cursor(cursor_factory=DictCursor) self.cur: DictCursor = self.conn.cursor(cursor_factory=DictCursor)
self.__init_schema() self.__migrate(dsn)
def add_user(self, telegram_id: int) -> int: def add_user(self, telegram_id: int) -> int:
"""Add a user's telegram id to the database and return its database id.""" """Add a user's telegram id to the database and return its database id."""
@ -156,26 +157,14 @@ class Database:
'INSERT INTO feeds_last_items (feed_id, url, guid) VALUES (%s, %s, %s)', new_items) 'INSERT INTO feeds_last_items (feed_id, url, guid) VALUES (%s, %s, %s)', new_items)
self.conn.commit() self.conn.commit()
def __init_schema(self) -> None: def __migrate(self, dsn: str) -> None:
self.log.debug('__init_schema()') """Migrate or initialize the database schema"""
self.cur.execute( self.log.debug(f'Database.__migrate(dsn={dsn})')
'CREATE TABLE IF NOT EXISTS users (id SERIAL PRIMARY KEY, telegram_id INTEGER NOT NULL UNIQUE)' backend = get_backend(dsn)
) migrations = read_migrations('./migrations')
self.cur.execute('CREATE TABLE IF NOT EXISTS feeds (id SERIAL PRIMARY KEY, url TEXT NOT NULL UNIQUE)')
self.cur.execute( with backend.lock():
'CREATE TABLE IF NOT EXISTS subscriptions (' backend.apply_migrations(backend.to_apply(migrations))
' user_id INTEGER REFERENCES users,'
' feed_id INTEGER REFERENCES feeds,'
' UNIQUE (user_id, feed_id)'
')'
)
self.cur.execute(
'CREATE TABLE IF NOT EXISTS feeds_last_items ('
' feed_id INTEGER REFERENCES feeds ON DELETE CASCADE,'
' url TEXT NOT NULL,'
' guid TEXT'
')'
)
@staticmethod @staticmethod
def __dictrow_to_dict_list(rows: list[DictRow]) -> list[dict]: def __dictrow_to_dict_list(rows: list[DictRow]) -> list[dict]:

View file

@ -0,0 +1,30 @@
from yoyo import step
steps = [
step(
'CREATE TABLE users ('
' id SERIAL PRIMARY KEY,'
' telegram_id INTEGER NOT NULL UNIQUE'
')'
),
step(
'CREATE TABLE feeds ('
' id SERIAL PRIMARY KEY,'
' url TEXT NOT NULL UNIQUE'
')'
),
step(
'CREATE TABLE subscriptions ('
' user_id INTEGER REFERENCES users,'
' feed_id INTEGER REFERENCES feeds,'
' UNIQUE (user_id, feed_id)'
')'
),
step(
'CREATE TABLE feeds_last_items ('
' feed_id INTEGER REFERENCES feeds ON DELETE CASCADE,'
' url TEXT NOT NULL,'
' guid TEXT'
')'
)
]

View file

@ -13,3 +13,4 @@ six==1.16.0
urllib3==1.26.9 urllib3==1.26.9
validators==0.19.0 validators==0.19.0
webencodings==0.5.1 webencodings==0.5.1
yoyo-migrations==7.3.2