1+
2+ from re import match , Match
3+ from os import listdir
4+ from importlib import import_module
5+
6+ from openslides_backend .database .db_connection_handling import (
7+ os_conn_pool
8+ )
9+
10+ # relative path to the migrations
11+ MIGRATIONS_RELATIVE_DIRECTORY_PATH = ""
12+ FIRST_REL_DB_MIGRATION = 0
13+
14+ class MigrationHelper :
15+ '''
16+ Helper class containing static methods for handling the migrations. Reads and executes them.
17+ '''
18+ migrations : dict = {}
19+
20+
21+ @staticmethod
22+ def run_migrations () -> None :
23+ '''
24+ Runs the full migration process.
25+
26+ Returns:
27+ - None
28+ '''
29+ MigrationHelper .load_migrations ()
30+ MigrationHelper .execute_migrations ()
31+
32+
33+ @staticmethod
34+ def load_migrations () -> None :
35+ '''
36+ Checks wether current migration_index is equal to or above the FIRST_REL_DB_MIGRATION and
37+ accesses MIGRATION_DIRECTORY_PATH. Lists every migration file above the migration_index
38+ and stores them in MigrationHelper.migrations for future reference.
39+
40+ Returns:
41+ - None
42+ '''
43+ migrations : list
44+ migration_file : str
45+ migration_index : int
46+ migration_number : int
47+ reMatch : Match
48+
49+ migration_index = MigrationHelper .pull_migration_index_from_db ()
50+
51+ if migration_index >= FIRST_REL_DB_MIGRATION :
52+ migrations = listdir (MIGRATIONS_RELATIVE_DIRECTORY_PATH )
53+
54+ for n , migration in enumerate (migrations [:]):
55+ reMatch = match (r'(?P<migration>\d{4}_.*)\.py' , migration )
56+ # \d{4}_.*\.py : 4 digits, 1 underscore, any characters, [dot]py
57+ if reMatch is not None :
58+ migration_file = reMatch .groupdict ()["migration" ]
59+ migration_number = int (migration_file [:4 ])
60+ if migration_number > migration_index :
61+ MigrationHelper .migrations [migration_number ] = migration
62+
63+ MigrationHelper .migrations = dict (sorted (MigrationHelper .migrations .items ()))
64+
65+
66+ @staticmethod
67+ def pull_migration_index_from_db () -> migration_index :
68+ '''
69+ Reads the current migration_index from the psql database.
70+ 1. MAX(migration_index) of positions while position is used for the index
71+ 2. migration_index from version after the second migration to eliminate the table position
72+
73+ Returns:
74+ - migration_index : integer
75+ '''
76+
77+ with os_conn_pool .connection () as conn :
78+ with conn .cursor () as cur :
79+ cur .execute ("SELECT MAX(migration_index) FROM positions;" )
80+ row = cur .fetchone ()
81+ if row is None :
82+ cur .execute ("SELECT migration_index FROM version;" )
83+ row = cur .fetchone ()
84+
85+ # idx 0 is a wild guess anticipating that (migration_index.value) is delivered by cur.fetchone()
86+ migration_index = row [0 ]
87+
88+ assert type (migration_index ) == int , f'Type { type (migration_index )} of migration_index must be int.'
89+
90+ return migration_index
91+
92+
93+ @staticmethod
94+ def execute_migrations () -> None :
95+ '''
96+ Executes the migrations stored in MigrationHelper.migrations.
97+ Every migration could provide all of the three methods data_definition,
98+ data_manipulation and cleanup.
99+
100+ Returns:
101+ - None
102+ '''
103+ module_path : str
104+ module_name : str
105+
106+ module_path = MIGRATIONS_RELATIVE_DIRECTORY_PATH .replace ("/" ,"." )
107+
108+ for index , migration in MigrationHelper .migrations .items ():
109+ module_name = migration .replace (".py" ,"" )
110+ migration_module = import_module (f"{ module_path } { module_name } " )
111+ if getattr (migration_module , "IN_MEMORY" , False ):
112+ migration_module .in_memory_method ()
113+ else :
114+ # checks wether the methods are available and executes them.
115+ if callable (getattr (migration_module , "data_definition" , None )):
116+ migration_module .data_definition ()
117+ if callable (getattr (migration_module , "data_manipulation" , None )):
118+ migration_module .data_manipulation ()
119+ if callable (getattr (migration_module , "cleanup" , None )):
120+ migration_module .cleanup ()
121+
122+ # TODO In-Memory migration
0 commit comments