half-orm-dev 0.16.0a9__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. half_orm_dev/__init__.py +1 -0
  2. half_orm_dev/cli/__init__.py +9 -0
  3. half_orm_dev/cli/commands/__init__.py +56 -0
  4. half_orm_dev/cli/commands/apply.py +13 -0
  5. half_orm_dev/cli/commands/clone.py +102 -0
  6. half_orm_dev/cli/commands/init.py +331 -0
  7. half_orm_dev/cli/commands/new.py +15 -0
  8. half_orm_dev/cli/commands/patch.py +317 -0
  9. half_orm_dev/cli/commands/prepare.py +21 -0
  10. half_orm_dev/cli/commands/prepare_release.py +119 -0
  11. half_orm_dev/cli/commands/promote_to.py +127 -0
  12. half_orm_dev/cli/commands/release.py +344 -0
  13. half_orm_dev/cli/commands/restore.py +14 -0
  14. half_orm_dev/cli/commands/sync.py +13 -0
  15. half_orm_dev/cli/commands/todo.py +73 -0
  16. half_orm_dev/cli/commands/undo.py +17 -0
  17. half_orm_dev/cli/commands/update.py +73 -0
  18. half_orm_dev/cli/commands/upgrade.py +191 -0
  19. half_orm_dev/cli/main.py +103 -0
  20. half_orm_dev/cli_extension.py +38 -0
  21. half_orm_dev/database.py +1389 -0
  22. half_orm_dev/hgit.py +1025 -0
  23. half_orm_dev/hop.py +167 -0
  24. half_orm_dev/manifest.py +43 -0
  25. half_orm_dev/modules.py +456 -0
  26. half_orm_dev/patch.py +281 -0
  27. half_orm_dev/patch_manager.py +1694 -0
  28. half_orm_dev/patch_validator.py +335 -0
  29. half_orm_dev/patches/0/1/0/00_half_orm_meta.database.sql +34 -0
  30. half_orm_dev/patches/0/1/0/01_alter_half_orm_meta.hop_release.sql +2 -0
  31. half_orm_dev/patches/0/1/0/02_half_orm_meta.view.hop_penultimate_release.sql +3 -0
  32. half_orm_dev/patches/log +2 -0
  33. half_orm_dev/patches/sql/half_orm_meta.sql +208 -0
  34. half_orm_dev/release_manager.py +2841 -0
  35. half_orm_dev/repo.py +1562 -0
  36. half_orm_dev/templates/.gitignore +15 -0
  37. half_orm_dev/templates/MANIFEST.in +1 -0
  38. half_orm_dev/templates/Pipfile +13 -0
  39. half_orm_dev/templates/README +25 -0
  40. half_orm_dev/templates/conftest_template +42 -0
  41. half_orm_dev/templates/init_module_template +10 -0
  42. half_orm_dev/templates/module_template_1 +12 -0
  43. half_orm_dev/templates/module_template_2 +6 -0
  44. half_orm_dev/templates/module_template_3 +3 -0
  45. half_orm_dev/templates/relation_test +23 -0
  46. half_orm_dev/templates/setup.py +81 -0
  47. half_orm_dev/templates/sql_adapter +9 -0
  48. half_orm_dev/templates/warning +12 -0
  49. half_orm_dev/utils.py +49 -0
  50. half_orm_dev/version.txt +1 -0
  51. half_orm_dev-0.16.0a9.dist-info/METADATA +935 -0
  52. half_orm_dev-0.16.0a9.dist-info/RECORD +58 -0
  53. half_orm_dev-0.16.0a9.dist-info/WHEEL +5 -0
  54. half_orm_dev-0.16.0a9.dist-info/licenses/AUTHORS +3 -0
  55. half_orm_dev-0.16.0a9.dist-info/licenses/LICENSE +14 -0
  56. half_orm_dev-0.16.0a9.dist-info/top_level.txt +2 -0
  57. tests/__init__.py +0 -0
  58. tests/conftest.py +329 -0
half_orm_dev/patch.py ADDED
@@ -0,0 +1,281 @@
1
+ "The patch module"
2
+
3
+ import json
4
+ import os
5
+ import subprocess
6
+ import sys
7
+
8
+ import psycopg2
9
+
10
+ from half_orm import utils
11
+ from half_orm_dev import modules
12
+
13
+ from .utils import hop_version
14
+
15
+ try:
16
+ PYTEST_OK = True
17
+ import pytest
18
+ except ImportError:
19
+ PYTEST_OK = False
20
+
21
+ class Patch:
22
+ "The Patch class..."
23
+ __levels = ['patch', 'minor', 'major']
24
+
25
+ def __init__(self, repo):
26
+ self.__repo = repo
27
+ self.__patches_base_dir = os.path.join(repo.base_dir, 'Patches')
28
+ if self.__repo.devel:
29
+ self.__changelog = Changelog(repo)
30
+ if not os.path.exists(self.__patches_base_dir):
31
+ os.makedirs(self.__patches_base_dir)
32
+
33
+ @classmethod
34
+ @property
35
+ def levels(cls):
36
+ "Returns the levels"
37
+ return cls.__levels
38
+
39
+ def previous(self, release, index=0):
40
+ "Return .hop/CHANGELOG second to last line."
41
+ return self.__changelog.previous(release, index)
42
+
43
+ @property
44
+ def __next_releases(self):
45
+ db_last_release = self.__repo.database.last_release_s
46
+ ch_last_release = self.__changelog.last_release
47
+ if db_last_release != ch_last_release:
48
+ utils.error(
49
+ f'Last release mismatch between database {db_last_release}'
50
+ f' and CHANGELOG {ch_last_release}!\n', 1)
51
+ current = dict(self.__repo.database.last_release)
52
+ releases_in_dev = self.__changelog.releases_in_dev
53
+ n_rels = {}
54
+ for level in self.__levels:
55
+ n_rel = dict(current)
56
+ n_rel[level] = current[level] + 1
57
+ if level == 'major':
58
+ n_rel['minor'] = n_rel['patch'] = 0
59
+ if level == 'minor':
60
+ n_rel['patch'] = 0
61
+ next_release_s = f"{n_rel['major']}.{n_rel['minor']}.{n_rel['patch']}"
62
+ n_rel['in_dev'] = ''
63
+ if next_release_s in releases_in_dev:
64
+ n_rel['in_dev'] = '(IN DEV)'
65
+ n_rels[level] = n_rel
66
+ return n_rels
67
+
68
+ def __assert_main_branch(self):
69
+ if str(self.__repo.hgit.branch) != 'hop_main':
70
+ utils.error(
71
+ 'ERROR! Wrong branch. Please, switch to the hop_main branch before.\n', exit_code=1)
72
+
73
+ def prep_release(self, release_level, message=None):
74
+ """LEGACY METHOD - No longer supported"""
75
+ raise NotImplementedError(
76
+ "Legacy release preparation removed. "
77
+ "Use new PatchManager via repo.patch_manager"
78
+ )
79
+
80
+ def __check_apply_or_re_apply(self):
81
+ """Return True if it's the first time.
82
+ False otherwise.
83
+ """
84
+ if self.__repo.database.last_release_s == self.__repo.hgit.current_release:
85
+ return 're-apply'
86
+ return 'apply'
87
+
88
+ def __backup_file(self, directory, release, commit=None):
89
+ backup_dir = os.path.join(self.__repo.base_dir, directory)
90
+ if not os.path.isdir(backup_dir):
91
+ os.mkdir(backup_dir)
92
+ file_name = f'{self.__repo.name}-{release}'
93
+ if commit:
94
+ file_name = f'{file_name}-{commit}'
95
+ return os.path.join(backup_dir, f'{file_name}.sql')
96
+
97
+ def __save_db(self, release):
98
+ """Save the database
99
+ """
100
+ commit = None
101
+ if self.__repo.production:
102
+ commit = self.__repo.hgit.last_commit()
103
+ svg_file = self.__backup_file('Backups', release, commit)
104
+ print(f'Saving the database into {svg_file}')
105
+ if os.path.isfile(svg_file):
106
+ utils.error(
107
+ f"Oops! there is already a dump for the {release} release.\n")
108
+ utils.warning("Please remove it if you really want to proceed.\n")
109
+ sys.exit(1)
110
+
111
+ self.__repo.database.execute_pg_command(
112
+ 'pg_dump', '-f', svg_file, stderr=subprocess.PIPE)
113
+
114
+ def __restore_db(self, release):
115
+ """Restore the database to the release_s version.
116
+ """
117
+ backup_file = self.__backup_file('Backups', release)
118
+ if not os.path.exists(backup_file):
119
+ raise FileNotFoundError(backup_file)
120
+ print(f'{utils.Color.green("Restoring the database to")} {utils.Color.bold(release)}')
121
+ self.__repo.model.disconnect()
122
+ self.__repo.database.execute_pg_command('dropdb')
123
+ self.__repo.database.execute_pg_command('createdb')
124
+ self.__repo.database.execute_pg_command(
125
+ 'psql', '-f', backup_file, stdout=subprocess.DEVNULL)
126
+ self.__repo.model.ping()
127
+
128
+ def __restore_previous_release(self):
129
+ db_release = self.__repo.database.last_release_s
130
+ self.__restore_db(db_release)
131
+ os.remove(self.__backup_file('Backups', db_release))
132
+ sys.exit(1)
133
+
134
+ def __execute_sql(self, file_):
135
+ "Execute sql query contained in sql file_"
136
+ query = utils.read(file_.path).replace('%', '%%')
137
+ if len(query) == 0:
138
+ return
139
+ try:
140
+ self.__repo.model.execute_query(query)
141
+ except psycopg2.Error as err:
142
+ utils.error(f'Problem with query in {file_.name}\n{err}\n')
143
+ self.__restore_previous_release()
144
+
145
+ def __execute_script(self, file_):
146
+ try:
147
+ python_path = os.environ.get('PYTHONPATH')
148
+ if python_path:
149
+ python_path = python_path.split(':')
150
+ else:
151
+ python_path = []
152
+ if self.__repo.base_dir:
153
+ os.environ.update({'PYTHONPATH': ':'.join([self.__repo.base_dir] + python_path)})
154
+ subprocess.run(
155
+ ['python', file_.path],
156
+ cwd=self.__repo.base_dir,
157
+ env=os.environ,
158
+ shell=False, check=True)
159
+ except subprocess.CalledProcessError as err:
160
+ utils.error(f'Problem with script {file_}\n{err}\n')
161
+ self.__restore_previous_release()
162
+
163
+ def __apply(self, path):
164
+ """LEGACY METHOD - No longer supported"""
165
+ raise NotImplementedError(
166
+ "Legacy Patches/ system removed in v0.16.0. "
167
+ "Use new patch-centric workflow with half_orm dev commands."
168
+ )
169
+
170
+ def apply(self, release, force=False, save_db=True):
171
+ """Apply the release in 'path'
172
+
173
+ The history is first rebased on hop_main
174
+ """
175
+ if self.__repo.hgit.repos_is_clean():
176
+ self.__repo.hgit.rebase('hop_main')
177
+ db_release = self.__repo.database.last_release_s
178
+ changelog_msg = ''
179
+ if self.__check_apply_or_re_apply() == 'apply' and save_db:
180
+ self.__save_db(db_release)
181
+ elif not self.__repo.production:
182
+ if not force:
183
+ okay = input(f'Do you want to re-apply the release {release} [y/N]?') or 'y'
184
+ if okay.upper() != 'Y':
185
+ sys.exit()
186
+ self.__restore_db(self.previous(db_release, 1))
187
+ app_upg = utils.Color.green('Upgrading to') if self.__repo.production else utils.Color.bold('Applying')
188
+ major, minor, patch = release.split('.')
189
+ print(utils.Color.green("Pre patch:"))
190
+ self.__apply(os.path.join(self.__patches_base_dir, 'pre'))
191
+ print(f'{app_upg} {utils.Color.green(release)}:')
192
+ self.__apply(os.path.join(self.__patches_base_dir, major, minor, patch))
193
+ if not self.__repo.production:
194
+ modules.generate(self.__repo)
195
+ print(utils.Color.green("Post patch:"))
196
+ self.__apply(os.path.join(self.__patches_base_dir, 'post'))
197
+ self.__repo.database.register_release(major, minor, patch, changelog_msg)
198
+
199
+ @property
200
+ def state(self):
201
+ "The state of a patch"
202
+ if not self.__repo.devel:
203
+ return 'This repo is not in developement mode.'
204
+ if not self.__repo.production:
205
+ resp = ['[Releases in development]']
206
+ if len(self.__changelog.releases_in_dev) == 0:
207
+ resp.append("No release in development.\nUse `hop prepare`.")
208
+ for release in self.__changelog.releases_in_dev:
209
+ resp.append(f'- {release} (branch hop_{release})')
210
+ else:
211
+ resp = ['[Releases to apply]']
212
+ if len(self.__changelog.releases_to_apply_in_prod) == 0:
213
+ resp.append("No new release to apply.")
214
+ for release in self.__changelog.releases_to_apply_in_prod:
215
+ resp.append(f'- {release}')
216
+ return '\n'.join(resp)
217
+
218
+
219
+ def undo(self, database_only=False):
220
+ "Undo a patch."
221
+ db_release = self.__repo.database.last_release_s
222
+ previous_release = self.previous(db_release, 1)
223
+ self.__restore_db(previous_release)
224
+ if not database_only:
225
+ modules.generate(self.__repo)
226
+ os.remove(self.__backup_file('Backups', previous_release))
227
+
228
+ def sync_package(self):
229
+ "Synchronise the package with the current database model"
230
+ modules.generate(self.__repo)
231
+
232
+ def release(self, push):
233
+ "Release a patch"
234
+ raise Exception("Deprecated legacy release")
235
+ # We must be on the first branch in devel (see CHANGELOG)
236
+ next_release = self.__repo.changelog.releases_in_dev[0]
237
+ next_branch = f'hop_{next_release}'
238
+ if next_branch != self.__repo.hgit.branch:
239
+ utils.error(f'Next release is {next_release} Please switch to the branch {next_branch}!\n', 1)
240
+ # Git repo must be clean
241
+ if not self.__repo.hgit.repos_is_clean():
242
+ utils.error(
243
+ f'Please `git commit` your changes before releasing {next_release}.\n', exit_code=1)
244
+ # The patch must be applied and the last to apply
245
+ if self.__repo.database.last_release_s != next_release:
246
+ utils.error(f'Please `hop apply` before releasing {next_release}.\n', exit_code=1)
247
+ # If we undo the patch (db only) and re-apply it the repo must still be clear.
248
+ self.undo(database_only=True)
249
+ self.apply(next_release, force=True)
250
+ if not self.__repo.hgit.repos_is_clean():
251
+ utils.error(
252
+ 'Something has changed when re-applying the release. This should not happen.\n',
253
+ exit_code=1)
254
+ # do we have pytest
255
+ if PYTEST_OK:
256
+ try:
257
+ subprocess.run(['pytest', self.__repo.name], check=True)
258
+ except subprocess.CalledProcessError:
259
+ utils.error('Tests must pass in order to release.\n', exit_code=1)
260
+ # So far, so good
261
+ svg_file = self.__backup_file('Releases', next_release)
262
+ print(f'Saving the database into {svg_file}')
263
+ self.__repo.database.execute_pg_command(
264
+ 'pg_dump', '-xO', '-f', svg_file, stderr=subprocess.PIPE)
265
+ self.__repo.hgit.add(svg_file)
266
+ self.__repo.hgit.commit("-m", f"Add sql for release {next_release}")
267
+ self.__repo.hgit.rebase_to_hop_main(push)
268
+ else:
269
+ utils.error('pytest is not installed!\n', 1)
270
+
271
+ def upgrade_prod(self):
272
+ "Upgrade the production"
273
+ self.__assert_main_branch()
274
+ self.__save_db(self.__repo.database.last_release_s)
275
+ for release in self.__repo.changelog.releases_to_apply_in_prod:
276
+ self.apply(release, save_db=False)
277
+
278
+ def restore(self, release):
279
+ "Restore the database and package to a release (in production)"
280
+ self.__restore_db(release)
281
+ # Do we have the backup