py.lib.aw_db_tools

Yohn Y. 2024-02-27 Parent:4b0d10bfa023 Child:fd7d3b38860e

1:e90eb3d2fd01 Go to Latest

py.lib.aw_db_tools/src/aw_db_tools/migrator.py

.

History
1 # coding: utf-8
2 """\
3 Инструмент миграции схемы БД.
5 """
7 from os.path import exists, join as p_join, isdir
8 from os import listdir
10 from . import Error
13 class MigrateError(Error):
14 """\
15 Общий класс ошибок миграции
16 """
19 class MigrateManager(object):
20 """\
21 Менеджер миграции
22 """
23 def __init__(self, control_table: str, migrate_env: str):
24 """
25 :param control_table: Имя таблицы, хранящей метаданные миграции
26 :param migrate_env: Директория, хранящая SQL-скрипты миграции
27 """
28 self.control_table = control_table
30 if not exists(migrate_env):
31 raise MigrateError('Migrate enviroment not found')
33 self.schema = p_join(migrate_env, 'schema.sql')
34 if not exists(self.schema):
35 raise MigrateError(f'Schema file not found: {self.schema}')
37 self.patch_dir = p_join(migrate_env, 'patch')
38 if not isdir(self.patch_dir):
39 raise MigrateError(f'Patch dir not found or not directory: {self.patch_dir}')
41 def get_patch_files(self, ver: int):
42 """\
43 Получение из директории файлов миграции списка применяемых к данному экземпляру БД
44 """
45 res = {}
46 for f in listdir(self.patch_dir):
47 if not f.lower().endswith('.sql'):
48 continue
50 _f = f.strip().split('.')
52 try:
53 _ver = int(_f[0])
55 except (TypeError, ValueError) as e:
56 raise MigrateError(f'Error on parse version "{_f[0]}" of file "{f}": {e}')
58 except IndexError:
59 raise MigrateError(f'Error on get version from filename: {f}')
61 if _ver in res:
62 raise MigrateError(f'Version duplicates on parse file: {f}')
64 res[_ver] = p_join(self.patch_dir, f)
66 for i in sorted(res.keys()):
67 if i > ver:
68 yield i, res[i]
70 @staticmethod
71 def get_commands(file: str):
72 """\
73 Получение из файлов серий команд, которые необходимо применять на БД
74 """
75 buf = []
76 with open(file) as IN:
77 for l in IN:
78 if l.lstrip().startswith('--'):
79 if buf:
80 yield '\n'.join(buf)
81 buf[:] = []
83 else:
84 buf.append(l)
86 if buf:
87 yield '\n'.join(buf)
89 def init_db(self, db):
90 """\
91 Инициализация БД.
93 :param db: Объект-подключения, представляющий нужную БД и при этом поддерживающий DB API Python
94 """
95 cursor = db.cursor()
96 for c in self.get_commands(self.schema):
97 cursor.execute(c)
98 db.commit()
100 db.commit()
102 def check(self, db):
103 """\
104 Проверка БД на соответствие.
106 :param db: Объект-подключения, представляющий нужную БД и при этом поддерживающий DB API Python
107 """
108 cursor = db.cursor()
109 cursor.execute(f"SELECT version FROM {self.control_table}")
110 q = cursor.fetchone()
111 del cursor
113 if q is None:
114 ver = -1
115 else:
116 ver = int(q[0])
118 new_ver = ver
119 cursor = db.cursor()
120 for up_ver, patch_file in self.get_patch_files(ver):
121 new_ver = up_ver
122 for cmd in self.get_commands(patch_file):
123 cursor.execute(cmd)
124 db.commit()
126 cursor.execute(f"DELETE FROM {self.control_table}")
128 cursor.execute(f"""
129 INSERT INTO {self.control_table} (version)
130 VALUES ({new_ver})
131 """)
132 db.commit()
134 @staticmethod
135 def get_conn_from_my_obj(obj: object):
136 """\
137 Получиение объекта соединения из обёрток, которые я сам себе пишу для работы с DB-API
139 :param obj: Получение объекта-подключения из объектов БД своего стиля оформления.
140 :return:
141 """
142 if hasattr(obj, '_conn'):
143 return obj._conn
144 elif hasattr(obj, '_con'):
145 return obj._con
146 else:
147 raise TypeError('No known connection object in given database object found')