kapsikkum-unmanic – Blame information for rev 1

Subversion Repositories:
Rev:
Rev Author Line No. Line
1 office 1 #!/usr/bin/env python3
2 # -*- coding: utf-8 -*-
3  
4 """
5 unmanic.database.py
6  
7 Written by: Josh.5 <jsunnex@gmail.com>
8 Date: 14 Aug 2021, (12:03 PM)
9  
10 Copyright:
11 Copyright (C) Josh Sunnex - All Rights Reserved
12  
13 Permission is hereby granted, free of charge, to any person obtaining a copy
14 of this software and associated documentation files (the "Software"), to deal
15 in the Software without restriction, including without limitation the rights
16 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 copies of the Software, and to permit persons to whom the Software is
18 furnished to do so, subject to the following conditions:
19  
20 The above copyright notice and this permission notice shall be included in all
21 copies or substantial portions of the Software.
22  
23 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
26 IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
27 DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
28 OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
29 OR OTHER DEALINGS IN THE SOFTWARE.
30  
31 """
32 import importlib
33 import inspect
34 import os
35 import sys
36  
37 from peewee import Model, SqliteDatabase, Field
38 from peewee_migrate import Migrator, Router
39  
40 from unmanic.libs import unlogger
41 from unmanic.libs.unmodels.lib import BaseModel
42  
43  
44 class Migrations(object):
45 """
46 Migrations
47  
48 Handle all migrations during application start.
49 """
50  
51 logger = None
52 database = None
53  
54 def __init__(self, config):
55 unmanic_logging = unlogger.UnmanicLogger.__call__()
56 self.logger = unmanic_logging.get_logger(__class__.__name__)
57  
58 # Based on configuration, select database to connect to.
59 if config['TYPE'] == 'SQLITE':
60 # Create SQLite directory if not exists
61 db_file_directory = os.path.dirname(config['FILE'])
62 if not os.path.exists(db_file_directory):
63 os.makedirs(db_file_directory)
64 self.database = SqliteDatabase(config['FILE'])
65  
66 self.router = Router(database=self.database,
67 migrate_table='migratehistory_{}'.format(config.get('MIGRATIONS_HISTORY_VERSION')),
68 migrate_dir=config.get('MIGRATIONS_DIR'),
69 logger=self.logger)
70  
71 self.migrator = Migrator(self.database)
72  
73 def __log(self, message, level='info'):
74 if self.logger:
75 getattr(self.logger, level)(message)
76 else:
77 print(message)
78  
79 def __run_all_migrations(self):
80 """
81 Run all new migrations.
82 Migrations that have already been run will be ignored.
83  
84 :return:
85 """
86 self.router.run()
87  
88 def update_schema(self):
89 """
90 Updates the Unmanic database schema.
91  
92 Newly added tables/models and columns/fields will be automatically generated by this function.
93 This way we do not need to create a migration script unless we:
94 - rename a column/field
95 - delete a column/field
96 - delete a table/model
97  
98 :return:
99 """
100 # Fetch all model classes
101 discovered_models = inspect.getmembers(sys.modules["unmanic.libs.unmodels"], inspect.isclass)
102 all_models = [tup[1] for tup in discovered_models]
103  
104 # Start by creating all models
105 self.__log("Initialising database tables")
106 try:
107 with self.database.transaction():
108 for model in all_models:
109 self.migrator.create_table(model)
110 self.migrator.run()
111 except Exception:
112 self.database.rollback()
113 self.__log("Initialising tables failed", level='exception')
114 raise
115  
116 # Migrations will only be used for removing obsolete columns
117 self.__run_all_migrations()
118  
119 # Newly added fields can be auto added with this function... no need for a migration script
120 # Ensure all files are also present for each of the model classes
121 self.__log("Updating database fields")
122 for model in all_models:
123 if issubclass(model, BaseModel):
124 # Fetch all peewee fields for the model class
125 # https://stackoverflow.com/questions/22573558/peewee-determining-meta-data-about-model-at-run-time
126 fields = model._meta.fields
127 # loop over the fields and ensure each on exists in the table
128 field_keys = [f for f in fields]
129 for fk in field_keys:
130 field = fields.get(fk)
131 if isinstance(field, Field):
132 if not any(f for f in self.database.get_columns(model._meta.name) if f.name == field.name):
133 # Field does not exist in DB table
134 self.__log("Adding missing column")
135 try:
136 with self.database.transaction():
137 self.migrator.add_columns(model, **{field.name: field})
138 self.migrator.run()
139 except Exception:
140 self.database.rollback()
141 self.__log("Update failed", level='exception')
142 raise