half-orm-dev 0.16.0a1__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 (38) hide show
  1. half_orm_dev/__init__.py +0 -0
  2. half_orm_dev/changelog.py +117 -0
  3. half_orm_dev/cli_extension.py +171 -0
  4. half_orm_dev/database.py +127 -0
  5. half_orm_dev/db_conn.py +134 -0
  6. half_orm_dev/hgit.py +202 -0
  7. half_orm_dev/hop.py +167 -0
  8. half_orm_dev/manifest.py +43 -0
  9. half_orm_dev/modules.py +357 -0
  10. half_orm_dev/patch.py +348 -0
  11. half_orm_dev/patches/0/1/0/00_half_orm_meta.database.sql +34 -0
  12. half_orm_dev/patches/0/1/0/01_alter_half_orm_meta.hop_release.sql +2 -0
  13. half_orm_dev/patches/0/1/0/02_half_orm_meta.view.hop_penultimate_release.sql +3 -0
  14. half_orm_dev/patches/log +2 -0
  15. half_orm_dev/patches/sql/half_orm_meta.sql +208 -0
  16. half_orm_dev/repo.py +266 -0
  17. half_orm_dev/templates/.gitignore +14 -0
  18. half_orm_dev/templates/MANIFEST.in +1 -0
  19. half_orm_dev/templates/Pipfile +13 -0
  20. half_orm_dev/templates/README +25 -0
  21. half_orm_dev/templates/base_test +26 -0
  22. half_orm_dev/templates/init_module_template +6 -0
  23. half_orm_dev/templates/module_template_1 +12 -0
  24. half_orm_dev/templates/module_template_2 +5 -0
  25. half_orm_dev/templates/module_template_3 +3 -0
  26. half_orm_dev/templates/relation_test +19 -0
  27. half_orm_dev/templates/setup.py +81 -0
  28. half_orm_dev/templates/sql_adapter +9 -0
  29. half_orm_dev/templates/warning +12 -0
  30. half_orm_dev/utils.py +12 -0
  31. half_orm_dev/version.txt +1 -0
  32. half_orm_dev-0.16.0a1.dist-info/METADATA +314 -0
  33. half_orm_dev-0.16.0a1.dist-info/RECORD +38 -0
  34. half_orm_dev-0.16.0a1.dist-info/WHEEL +5 -0
  35. half_orm_dev-0.16.0a1.dist-info/entry_points.txt +2 -0
  36. half_orm_dev-0.16.0a1.dist-info/licenses/AUTHORS +3 -0
  37. half_orm_dev-0.16.0a1.dist-info/licenses/LICENSE +14 -0
  38. half_orm_dev-0.16.0a1.dist-info/top_level.txt +1 -0
half_orm_dev/hgit.py ADDED
@@ -0,0 +1,202 @@
1
+ "Provides the HGit class"
2
+
3
+ import os
4
+ import sys
5
+ import subprocess
6
+ import git
7
+ from git.exc import GitCommandError
8
+
9
+ from half_orm import utils
10
+ from half_orm_dev.manifest import Manifest
11
+
12
+ class HGit:
13
+ "Manages the git operations on the repo."
14
+ def __init__(self, repo=None):
15
+ self.__origin = None
16
+ self.__repo = repo
17
+ self.__base_dir = None
18
+ self.__git_repo: git.Repo = None
19
+ if repo:
20
+ self.__origin = repo.git_origin
21
+ self.__base_dir = repo.base_dir
22
+ self.__post_init()
23
+
24
+ def __post_init(self):
25
+ self.__git_repo = git.Repo(self.__base_dir)
26
+ origin = None
27
+ try:
28
+ origin = self.__git_repo.git.remote('get-url', 'origin')
29
+ except Exception as err:
30
+ utils.warning(utils.Color.red(f"No origin\n{err}\n"))
31
+ if self.__origin == '' and origin:
32
+ self.__repo.git_origin = origin
33
+ self.add(os.path.join('.hop', 'config'))
34
+ self.commit("-m", f"[hop] Set remote for origin: {origin}.")
35
+ self.__git_repo.git.push('-u', 'origin', 'hop_main')
36
+ self.__origin = origin
37
+ elif origin and self.__origin != origin:
38
+ utils.error(f'Git remote origin should be {self.__origin}. Got {origin}\n', 1)
39
+ self.__current_branch = self.branch
40
+
41
+ def __str__(self):
42
+ res = ['[Git]']
43
+ res.append(f'- origin: {self.__origin or utils.Color.red("No origin")}')
44
+ res.append(f'- current branch: {self.__current_branch}')
45
+ clean = self.repos_is_clean()
46
+ clean = utils.Color.green(clean) \
47
+ if clean else utils.Color.red(clean)
48
+ res.append(f'- repo is clean: {clean}')
49
+ res.append(f'- last commit: {self.last_commit()}')
50
+ return '\n'.join(res)
51
+
52
+ def init(self, base_dir, release='0.0.0'):
53
+ "Initiazes the git repo."
54
+ cur_dir = os.path.abspath(os.path.curdir)
55
+ self.__base_dir = base_dir
56
+ try:
57
+ git.Repo.init(base_dir)
58
+ self.__git_repo = git.Repo(base_dir)
59
+ os.chdir(base_dir)
60
+ self.__git_repo.git.add('.')
61
+ self.__git_repo.git.commit(m=f'[{release}] hop new {os.path.basename(base_dir)}')
62
+ self.__git_repo.git.checkout('-b', 'hop_main')
63
+ os.chdir(cur_dir)
64
+ self.__post_init()
65
+ except GitCommandError as err:
66
+ utils.error(
67
+ f'Something went wrong initializing git repo in {base_dir}\n{err}\n', exit_code=1)
68
+ return self
69
+
70
+ @property
71
+ def branch(self):
72
+ "Returns the active branch"
73
+ return str(self.__git_repo.active_branch)
74
+
75
+ @property
76
+ def current_release(self):
77
+ "Returns the current branch name without 'hop_'"
78
+ return self.branch.replace('hop_', '')
79
+
80
+ @property
81
+ def is_hop_patch_branch(self):
82
+ "Returns True if we are on a hop patch branch hop_X.Y.Z."
83
+ try:
84
+ major, minor, patch = self.current_release.split('.')
85
+ return bool(1 + int(major) + int(minor) + int(patch))
86
+ except ValueError:
87
+ return False
88
+
89
+ def repos_is_clean(self):
90
+ "Returns True if the git repository is clean, False otherwise."
91
+ return not self.__git_repo.is_dirty(untracked_files=True)
92
+
93
+ def last_commit(self):
94
+ """Returns the last commit
95
+ """
96
+ commit = str(list(self.__git_repo.iter_commits(self.branch, max_count=1))[0])[0:8]
97
+ assert self.__git_repo.head.commit.hexsha[0:8] == commit
98
+ return commit
99
+
100
+ def branch_exists(self, branch):
101
+ "Returns True if branch is in branches"
102
+ return branch in self.__git_repo.heads
103
+
104
+ def set_branch(self, release_s):
105
+ """Checks the branch
106
+
107
+ Either hop_main or hop_<release>.
108
+ """
109
+ rel_branch = f'hop_{release_s}'
110
+ self.add(self.__repo.changelog.file)
111
+ if str(self.branch) == 'hop_main' and rel_branch != 'hop_main':
112
+ # creates the new branch
113
+ self.__git_repo.git.commit('-m', f'[hop][main] Add {release_s} to Changelog')
114
+ self.__git_repo.create_head(rel_branch)
115
+ self.__git_repo.git.checkout(rel_branch)
116
+ self.__git_repo.git.add('Patches')
117
+ self.__git_repo.git.commit('-m', f'[hop][{release_s}] Patch skeleton')
118
+ self.cherry_pick_changelog(release_s)
119
+ print(f'NEW branch {rel_branch}')
120
+ elif str(self.branch) == rel_branch:
121
+ print(f'On branch {rel_branch}')
122
+
123
+ def cherry_pick_changelog(self, release_s):
124
+ "Sync CHANGELOG on all hop_x.y.z branches in devel different from release_s"
125
+ branch = self.__git_repo.active_branch
126
+ self.__git_repo.git.checkout('hop_main')
127
+ commit_sha = self.__git_repo.head.commit.hexsha[0:8]
128
+ for release in self.__repo.changelog.releases_in_dev:
129
+ if release != release_s:
130
+ self.__git_repo.git.checkout(f'hop_{release}')
131
+ self.__git_repo.git.cherry_pick(commit_sha)
132
+ # self.__git_repo.git.commit('--amend', '-m', f'[hop][{release_s}] CHANGELOG')
133
+ self.__git_repo.git.checkout(branch)
134
+
135
+ def rebase_devel_branches(self, release_s):
136
+ "Rebase all hop_x.y.z branches in devel different from release_s on hop_main:HEAD"
137
+ for release in self.__repo.changelog.releases_in_dev:
138
+ if release != release_s:
139
+ self.__git_repo.git.checkout(f'hop_{release}')
140
+ self.__git_repo.git.rebase('hop_main')
141
+
142
+ def check_rebase_hop_main(self, current_branch):
143
+ git = self.__git_repo.git
144
+ try:
145
+ git.branch("-D", "hop_temp")
146
+ except GitCommandError:
147
+ pass
148
+ for release in self.__repo.changelog.releases_in_dev:
149
+ git.checkout(f'hop_{release}')
150
+ git.checkout("HEAD", b="hop_temp")
151
+ try:
152
+ git.rebase('hop_main')
153
+ except GitCommandError as exc:
154
+ git.rebase('--abort')
155
+ git.checkout(current_branch)
156
+ utils.error(f"Can't rebase {release} on hop_main.\n{exc}\n", exit_code=1)
157
+ git.checkout(current_branch)
158
+ git.branch("-D", "hop_temp")
159
+
160
+ def rebase_to_hop_main(self, push=False):
161
+ "Rebase a hop_X.Y.Z branch to hop_main"
162
+ release = self.current_release
163
+ if push and not self.__repo.git_origin:
164
+ utils.error("Git: No remote specified for \"origin\". Can't push!\n", 1)
165
+ try:
166
+ if self.__origin:
167
+ self.__git_repo.git.pull('origin', 'hop_main')
168
+ hop_main_last_commit = self.__git_repo.commit('hop_main').hexsha[0:8]
169
+ self.__git_repo.git.rebase('hop_main')
170
+ self.__git_repo.git.checkout('hop_main')
171
+ self.__git_repo.git.rebase(f'hop_{release}')
172
+ self.__repo.changelog.update_release(
173
+ self.__repo.database.last_release_s,
174
+ self.__repo.hgit.last_commit(),
175
+ hop_main_last_commit)
176
+ patch_dir = os.path.join(self.__base_dir, 'Patches', *release.split('.'))
177
+ manifest = Manifest(patch_dir)
178
+ message = f'[{release}] {manifest.changelog_msg}'
179
+ self.__git_repo.git.commit('-m', message)
180
+ self.__git_repo.git.tag(release, '-m', release)
181
+ self.cherry_pick_changelog(release)
182
+ if push:
183
+ self.__git_repo.git.push()
184
+ self.__git_repo.git.push('-uf', 'origin', release)
185
+ except GitCommandError as err:
186
+ utils.error(f'Something went wrong rebasing hop_main\n{err}\n', exit_code=1)
187
+
188
+ def add(self, *args, **kwargs):
189
+ "Proxy to git.add method"
190
+ return self.__git_repo.git.add(*args, **kwargs)
191
+
192
+ def commit(self, *args, **kwargs):
193
+ "Proxy to git.commit method"
194
+ return self.__git_repo.git.commit(*args, **kwargs)
195
+
196
+ def rebase(self, *args, **kwargs):
197
+ "Proxy to git.commit method"
198
+ return self.__git_repo.git.rebase(*args, **kwargs)
199
+
200
+ def checkout_to_hop_main(self):
201
+ "Checkout to hop_main branch"
202
+ self.__git_repo.git.checkout('hop_main')
half_orm_dev/hop.py ADDED
@@ -0,0 +1,167 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+
4
+ """
5
+ Generates/Patches/Synchronizes a hop Python package with a PostgreSQL database
6
+ using the `hop` command.
7
+
8
+ Initiate a new project and repository with the `hop new <project_name>` command.
9
+ The <project_name> directory should not exist when using this command.
10
+
11
+ In the <project name> directory generated, the hop command helps you patch your
12
+ model, keep your Python synced with the PostgreSQL model, test your Python code and
13
+ deal with CI.
14
+
15
+ TODO:
16
+ On the 'devel' or any private branch hop applies patches if any, runs tests.
17
+ On the 'main' or 'master' branch, hop checks that your git repo is in sync with
18
+ the remote origin, synchronizes with devel branch if needed and tags your git
19
+ history with the last release applied.
20
+ """
21
+
22
+ import sys
23
+
24
+ import click
25
+
26
+ from half_orm_dev.repo import Repo
27
+ from half_orm import utils
28
+
29
+ class Hop:
30
+ "Sets the options available to the hop command"
31
+ __available_cmds = []
32
+ __command = None
33
+ def __init__(self):
34
+ self.__repo: Repo = Repo()
35
+ if not self.repo_checked:
36
+ Hop.__available_cmds = ['new']
37
+ else:
38
+ if not self.__repo.devel:
39
+ # Sync-only mode
40
+ Hop.__available_cmds = ['sync-package']
41
+ else:
42
+ # Full mode - check environment
43
+ if self.__repo.production:
44
+ Hop.__available_cmds = ['upgrade', 'restore']
45
+ else:
46
+ Hop.__available_cmds = ['prepare', 'apply', 'release', 'undo']
47
+ @property
48
+ def repo_checked(self):
49
+ "Returns wether we are in a repo or not."
50
+ return self.__repo.checked
51
+
52
+ @property
53
+ def model(self):
54
+ "Returns the model (half_orm.model.Model) associated to the repo."
55
+ return self.__repo.model
56
+
57
+ @property
58
+ def state(self):
59
+ "Returns the state of the repo."
60
+ return self.__repo.state
61
+
62
+ @property
63
+ def command(self):
64
+ "The command invoked (click)"
65
+ return self.__command
66
+
67
+ def add_commands(self, click_main):
68
+ "Adds the commands to the main click group."
69
+ @click.command()
70
+ @click.argument('package_name')
71
+ @click.option('-d', '--devel', is_flag=True, help="Development mode")
72
+ def new(package_name, devel=False):
73
+ """ Creates a new hop project named <package_name>.
74
+ """
75
+ self.__repo.init(package_name, devel)
76
+
77
+
78
+ @click.command()
79
+ @click.option(
80
+ '-l', '--level',
81
+ type=click.Choice(['patch', 'minor', 'major']), help="Release level.")
82
+ @click.option('-m', '--message', type=str, help="The git commit message")
83
+ def prepare(level, message=None):
84
+ """ Prepares the next release.
85
+ """
86
+ self.__command = 'prepare'
87
+ self.__repo.prepare_release(level, message)
88
+ sys.exit()
89
+
90
+ @click.command()
91
+ def apply():
92
+ """Apply the current release.
93
+ """
94
+ self.__command = 'apply'
95
+ self.__repo.apply_release()
96
+
97
+ @click.command()
98
+ @click.option(
99
+ '-d', '--database-only', is_flag=True,
100
+ help='Restore the database to the previous release.')
101
+ def undo(database_only):
102
+ """Undo the last release.
103
+ """
104
+ self.__command = 'undo'
105
+ self.__repo.undo_release(database_only)
106
+
107
+ @click.command()
108
+ # @click.option('-d', '--dry-run', is_flag=True, help='Do nothing')
109
+ # @click.option('-l', '--loop', is_flag=True, help='Run every patches to apply')
110
+ def upgrade():
111
+ """Apply one or many patches.
112
+
113
+ switches to hop_main, pulls should check the tags
114
+ """
115
+ self.__command = 'upgrade_prod'
116
+ self.__repo.upgrade_prod()
117
+
118
+ @click.command()
119
+ @click.argument('release')
120
+ def restore(release):
121
+ "Restore to release"
122
+ self.__repo.restore(release)
123
+
124
+ @click.command()
125
+ @click.option('-p', '--push', is_flag=True, help='Push git repo to origin')
126
+ def release(push=False):
127
+ self.__repo.commit_release(push)
128
+
129
+ @click.command()
130
+ def sync_package():
131
+ self.__repo.sync_package()
132
+
133
+ cmds = {
134
+ 'new': new,
135
+ 'prepare': prepare,
136
+ 'apply': apply,
137
+ 'undo': undo,
138
+ 'release': release,
139
+ 'sync-package': sync_package,
140
+ 'upgrade': upgrade,
141
+ 'restore': restore
142
+ }
143
+
144
+ for cmd in self.__available_cmds:
145
+ click_main.add_command(cmds[cmd])
146
+
147
+
148
+ hop = Hop()
149
+
150
+ @click.group(invoke_without_command=True)
151
+ @click.pass_context
152
+ def main(ctx):
153
+ """
154
+ Generates/Synchronises/Patches a python package from a PostgreSQL database
155
+ """
156
+ if hop.repo_checked and ctx.invoked_subcommand is None:
157
+ click.echo(hop.state)
158
+ elif not hop.repo_checked and ctx.invoked_subcommand != 'new':
159
+ click.echo(hop.state)
160
+ print(
161
+ "\nNot in a hop repository.\n"
162
+ f"Try {utils.Color.bold('hop new [--devel] <package name>')} or change directory.\n")
163
+
164
+ hop.add_commands(main)
165
+
166
+ if __name__ == '__main__':
167
+ main({})
@@ -0,0 +1,43 @@
1
+ """Manages the MANIFEST.json
2
+ """
3
+
4
+ import json
5
+ import os
6
+
7
+ from half_orm import utils
8
+
9
+ class Manifest:
10
+ "Manages the manifest of a release"
11
+ def __init__(self, path):
12
+ self.__hop_version = None
13
+ self.__changelog_msg = None
14
+ self.__file = os.path.join(path, 'MANIFEST.json')
15
+ if os.path.exists(self.__file):
16
+ manifest = utils.read(self.__file)
17
+ data = json.loads(manifest)
18
+ self.__hop_version = data['hop_version']
19
+ self.__changelog_msg = data['changelog_msg']
20
+
21
+ @property
22
+ def changelog_msg(self):
23
+ "Returns the changelog msg"
24
+ return self.__changelog_msg
25
+ @changelog_msg.setter
26
+ def changelog_msg(self, msg):
27
+ self.__changelog_msg = msg
28
+
29
+ @property
30
+ def hop_version(self):
31
+ "Returns the version of hop used to create this release"
32
+ return self.__hop_version
33
+ @hop_version.setter
34
+ def hop_version(self, release):
35
+ self.__hop_version = release
36
+
37
+ def write(self):
38
+ "Writes the manifest"
39
+ with open(self.__file, 'w', encoding='utf-8') as manifest:
40
+ manifest.write(json.dumps({
41
+ 'hop_version': self.__hop_version,
42
+ 'changelog_msg': self.__changelog_msg
43
+ }))