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.
- half_orm_dev/__init__.py +0 -0
- half_orm_dev/changelog.py +117 -0
- half_orm_dev/cli_extension.py +171 -0
- half_orm_dev/database.py +127 -0
- half_orm_dev/db_conn.py +134 -0
- half_orm_dev/hgit.py +202 -0
- half_orm_dev/hop.py +167 -0
- half_orm_dev/manifest.py +43 -0
- half_orm_dev/modules.py +357 -0
- half_orm_dev/patch.py +348 -0
- half_orm_dev/patches/0/1/0/00_half_orm_meta.database.sql +34 -0
- half_orm_dev/patches/0/1/0/01_alter_half_orm_meta.hop_release.sql +2 -0
- half_orm_dev/patches/0/1/0/02_half_orm_meta.view.hop_penultimate_release.sql +3 -0
- half_orm_dev/patches/log +2 -0
- half_orm_dev/patches/sql/half_orm_meta.sql +208 -0
- half_orm_dev/repo.py +266 -0
- half_orm_dev/templates/.gitignore +14 -0
- half_orm_dev/templates/MANIFEST.in +1 -0
- half_orm_dev/templates/Pipfile +13 -0
- half_orm_dev/templates/README +25 -0
- half_orm_dev/templates/base_test +26 -0
- half_orm_dev/templates/init_module_template +6 -0
- half_orm_dev/templates/module_template_1 +12 -0
- half_orm_dev/templates/module_template_2 +5 -0
- half_orm_dev/templates/module_template_3 +3 -0
- half_orm_dev/templates/relation_test +19 -0
- half_orm_dev/templates/setup.py +81 -0
- half_orm_dev/templates/sql_adapter +9 -0
- half_orm_dev/templates/warning +12 -0
- half_orm_dev/utils.py +12 -0
- half_orm_dev/version.txt +1 -0
- half_orm_dev-0.16.0a1.dist-info/METADATA +314 -0
- half_orm_dev-0.16.0a1.dist-info/RECORD +38 -0
- half_orm_dev-0.16.0a1.dist-info/WHEEL +5 -0
- half_orm_dev-0.16.0a1.dist-info/entry_points.txt +2 -0
- half_orm_dev-0.16.0a1.dist-info/licenses/AUTHORS +3 -0
- half_orm_dev-0.16.0a1.dist-info/licenses/LICENSE +14 -0
- 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({})
|
half_orm_dev/manifest.py
ADDED
|
@@ -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
|
+
}))
|