Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1#!/usr/bin/env python 

2 

3""" 

4camcops_server/cc_modules/cc_alembic.py 

5 

6=============================================================================== 

7 

8 Copyright (C) 2012-2020 Rudolf Cardinal (rudolf@pobox.com). 

9 

10 This file is part of CamCOPS. 

11 

12 CamCOPS is free software: you can redistribute it and/or modify 

13 it under the terms of the GNU General Public License as published by 

14 the Free Software Foundation, either version 3 of the License, or 

15 (at your option) any later version. 

16 

17 CamCOPS is distributed in the hope that it will be useful, 

18 but WITHOUT ANY WARRANTY; without even the implied warranty of 

19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

20 GNU General Public License for more details. 

21 

22 You should have received a copy of the GNU General Public License 

23 along with CamCOPS. If not, see <https://www.gnu.org/licenses/>. 

24 

25=============================================================================== 

26 

27**Functions to talk to Alembic; specifically, those functions that may be used 

28by users/administrators, such as to upgrade a database.** 

29 

30If you're a developer and want to create a new database migration, see 

31``tools/create_database_migration.py`` instead. 

32 

33""" 

34 

35import logging 

36from typing import TYPE_CHECKING 

37import os 

38 

39# from alembic import command 

40from alembic.config import Config 

41from cardinal_pythonlib.fileops import preserve_cwd 

42from cardinal_pythonlib.logs import BraceStyleAdapter 

43from cardinal_pythonlib.sqlalchemy.alembic_func import ( 

44 # get_current_and_head_revision, 

45 downgrade_database, 

46 upgrade_database, 

47 stamp_allowing_unusual_version_table, 

48) 

49 

50from camcops_server.cc_modules.cc_baseconstants import ( 

51 ALEMBIC_BASE_DIR, 

52 ALEMBIC_CONFIG_FILENAME, 

53 ALEMBIC_VERSION_TABLE, 

54) 

55from camcops_server.cc_modules.cc_sqlalchemy import Base 

56 

57if TYPE_CHECKING: 

58 from sqlalchemy.sql.schema import MetaData 

59 from camcops_server.cc_modules.cc_config import CamcopsConfig 

60 

61log = BraceStyleAdapter(logging.getLogger(__name__)) 

62 

63 

64def import_all_models(): 

65 """ 

66 Imports all SQLAlchemy models. (This has side effects including setting up 

67 the SQLAlchemy metadata properly.) 

68 """ 

69 # noinspection PyUnresolvedReferences 

70 import camcops_server.cc_modules.cc_all_models # delayed import # import side effects (ensure all models registered) # noqa 

71 

72 

73def upgrade_database_to_head(show_sql_only: bool = False) -> None: 

74 """ 

75 The primary upgrade method. Modifies the database structure from where it 

76 is, stepwise through revisions, to the head revision. 

77 

78 Args: 

79 show_sql_only: just show the SQL; don't execute it 

80 """ 

81 upgrade_database_to_revision(revision="head", show_sql_only=show_sql_only) 

82 

83 

84def upgrade_database_to_revision(revision: str, 

85 show_sql_only: bool = False) -> None: 

86 """ 

87 Upgrades the database to a specific revision. Modifies the database 

88 structure from where it is, stepwise through revisions, to the specified 

89 revision. 

90 

91 Args: 

92 revision: destination revision 

93 show_sql_only: just show the SQL; don't execute it 

94 """ 

95 import_all_models() # delayed, for command-line interfaces 

96 upgrade_database(alembic_base_dir=ALEMBIC_BASE_DIR, 

97 alembic_config_filename=ALEMBIC_CONFIG_FILENAME, 

98 destination_revision=revision, 

99 version_table=ALEMBIC_VERSION_TABLE, 

100 as_sql=show_sql_only) 

101 # ... will get its config information from the OS environment; see 

102 # run_alembic() in alembic/env.py 

103 

104 

105def downgrade_database_to_revision(revision: str, 

106 show_sql_only: bool = False, 

107 confirm_downgrade_db: bool = False) -> None: 

108 """ 

109 Developer option. Takes the database to a specific revision. 

110 

111 Args: 

112 revision: destination revision 

113 show_sql_only: just show the SQL; don't execute it 

114 confirm_downgrade_db: has the user confirmed? Necessary for the 

115 (destructive) database operation. 

116 """ 

117 if not show_sql_only and not confirm_downgrade_db: 

118 log.critical("Destructive action not confirmed! Refusing.") 

119 return 

120 if show_sql_only: 

121 log.warning("Current Alembic v1.0.0 bug in downgrading with " 

122 "as_sql=True; may fail") 

123 import_all_models() # delayed, for command-line interfaces 

124 downgrade_database(alembic_base_dir=ALEMBIC_BASE_DIR, 

125 alembic_config_filename=ALEMBIC_CONFIG_FILENAME, 

126 destination_revision=revision, 

127 version_table=ALEMBIC_VERSION_TABLE, 

128 as_sql=show_sql_only) 

129 # ... will get its config information from the OS environment; see 

130 # run_alembic() in alembic/env.py 

131 

132 

133@preserve_cwd 

134def create_database_from_scratch(cfg: "CamcopsConfig") -> None: 

135 """ 

136 Takes the database from nothing to the "head" revision in one step, by 

137 bypassing Alembic's revisions and taking the state directly from the 

138 SQLAlchemy ORM metadata. 

139 

140 See 

141 https://alembic.zzzcomputing.com/en/latest/cookbook.html#building-an-up-to-date-database-from-scratch 

142 

143 This function ASSUMES that the head revision "frozen" into the latest 

144 ``alembic/version/XXX.py`` file MATCHES THE STATE OF THE SQLALCHEMY ORM 

145 METADATA as judged by ``Base.metadata``. If that's not the case, things 

146 will go awry later! (Alembic will think the database is at the state of its 

147 "head" revision, but it won't be.) 

148 

149 It also ASSUMES (as many things do) that importing ``.cc_all_models`` 

150 imports all the models (or ``Base.metadata`` will be incomplete). 

151 """ # noqa 

152 import_all_models() # delayed, for command-line interfaces 

153 

154 log.warning("Performing one-step database creation.") 

155 metadata = Base.metadata # type: MetaData 

156 engine = cfg.get_sqla_engine() 

157 metadata.create_all(engine) 

158 

159 alembic_cfg = Config(ALEMBIC_CONFIG_FILENAME) 

160 os.chdir(ALEMBIC_BASE_DIR) 

161 # command.stamp(alembic_cfg, "head") 

162 stamp_allowing_unusual_version_table(alembic_cfg, "head", 

163 version_table=ALEMBIC_VERSION_TABLE) 

164 log.info("One-step database creation complete.")