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/__init__.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"""The changelog module
|
|
2
|
+
|
|
3
|
+
Manages the CHANGELOG file. The file contains the log of the patches (released) and in preparation.
|
|
4
|
+
|
|
5
|
+
A line is of the form:
|
|
6
|
+
<hop version>\t<release number>\t<commit>\t<previous commit>
|
|
7
|
+
|
|
8
|
+
* hop version allows to check that the good hop version is used to apply the patch in production
|
|
9
|
+
* release number is an ordered list of release number
|
|
10
|
+
* commit is the git sha1 corresponding to the release of the patch. If empty, the patch is in
|
|
11
|
+
preparation.
|
|
12
|
+
* previous commit is the last commit on hop_main before the rebase of hop_<release>
|
|
13
|
+
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import os
|
|
17
|
+
|
|
18
|
+
from half_orm import utils
|
|
19
|
+
from .utils import hop_version
|
|
20
|
+
|
|
21
|
+
class Changelog:
|
|
22
|
+
"The Changelog class..."
|
|
23
|
+
__log_list = []
|
|
24
|
+
__log_dict = {}
|
|
25
|
+
__releases = []
|
|
26
|
+
def __init__(self, repo):
|
|
27
|
+
self.__repo = repo
|
|
28
|
+
self.__file = os.path.join(self.__repo.base_dir, '.hop', 'CHANGELOG')
|
|
29
|
+
if not os.path.exists(self.__file):
|
|
30
|
+
utils.write(
|
|
31
|
+
self.__file,
|
|
32
|
+
f'{hop_version()}\t{self.__repo.database.last_release_s}\tInitial\t\n')
|
|
33
|
+
self.__repo.hgit.add(self.__file)
|
|
34
|
+
self.__repo.hgit.commit('-m', '[hop] Initial CHANGELOG')
|
|
35
|
+
self.__seq()
|
|
36
|
+
|
|
37
|
+
def __seq(self):
|
|
38
|
+
self.__log_list = [elt.strip('\n').split('\t') for elt in utils.readlines(self.__file)]
|
|
39
|
+
self.__log_dict = {elt[1]: elt for elt in self.__log_list}
|
|
40
|
+
self.__releases = list(self.__log_dict.keys())
|
|
41
|
+
|
|
42
|
+
@staticmethod
|
|
43
|
+
def _sort_releases(releases):
|
|
44
|
+
"Sort the releases"
|
|
45
|
+
int_releases = list([tuple(int(elt) for elt in release.split('.'))
|
|
46
|
+
for release in releases])
|
|
47
|
+
int_releases.sort()
|
|
48
|
+
releases = list([".".join(str(elt) for elt in release)
|
|
49
|
+
for release in int_releases])
|
|
50
|
+
return releases
|
|
51
|
+
|
|
52
|
+
def new_release(self, release):
|
|
53
|
+
"""Update with the release the .hop/CHANGELOG file"""
|
|
54
|
+
releases = self.__releases
|
|
55
|
+
releases.append(release)
|
|
56
|
+
releases = Changelog._sort_releases(releases)
|
|
57
|
+
utils.write(self.__file, '')
|
|
58
|
+
for elt in releases:
|
|
59
|
+
rel = self.__log_dict.get(elt)
|
|
60
|
+
if rel:
|
|
61
|
+
utils.write(self.__file, f'{rel[0]}\t{rel[1]}\t{rel[2]}\t{rel[3]}\n', mode='a+')
|
|
62
|
+
else:
|
|
63
|
+
utils.write(self.__file, f'{hop_version()}\t{release}\t\t\n', mode='a+')
|
|
64
|
+
self.__seq()
|
|
65
|
+
|
|
66
|
+
def update_release(self, release, commit, previous_commit):
|
|
67
|
+
"Add the commit sha1 to the release in the .hop/CHANGELOG file"
|
|
68
|
+
out = []
|
|
69
|
+
previous = self.previous(release, 1)
|
|
70
|
+
for line in utils.readlines(self.__file):
|
|
71
|
+
if line and line.split()[1] not in {release, previous}:
|
|
72
|
+
out.append(line)
|
|
73
|
+
elif line.split()[1] == previous:
|
|
74
|
+
elt = line.split()
|
|
75
|
+
out.append(f'{elt[0]}\t{elt[1]}\t{elt[2]}\t{previous_commit}\n')
|
|
76
|
+
else:
|
|
77
|
+
out.append(f'{hop_version()}\t{release}\t{commit}\t\n')
|
|
78
|
+
utils.write(self.__file, ''.join(out))
|
|
79
|
+
self.__repo.hgit.add(self.__file)
|
|
80
|
+
# self.__repo.hgit.commit('-m', f'[hop][{release}] CHANGELOG')
|
|
81
|
+
self.__seq()
|
|
82
|
+
|
|
83
|
+
def previous(self, release, index):
|
|
84
|
+
"Return previous release of release."
|
|
85
|
+
index_of_release = self.__releases.index(release)
|
|
86
|
+
return self.__releases[index_of_release - index]
|
|
87
|
+
|
|
88
|
+
@property
|
|
89
|
+
def file(self):
|
|
90
|
+
"Return the name of the changelog file"
|
|
91
|
+
return self.__file
|
|
92
|
+
|
|
93
|
+
@property
|
|
94
|
+
def last_release(self):
|
|
95
|
+
"Return the sequence"
|
|
96
|
+
releases = [elt[1] for elt in self.__log_list if elt[2]]
|
|
97
|
+
return releases[-1]
|
|
98
|
+
|
|
99
|
+
@property
|
|
100
|
+
def releases_in_dev(self):
|
|
101
|
+
"Returns the list of patches in dev (not released)"
|
|
102
|
+
return [elt[1] for elt in self.__log_list if not elt[2]]
|
|
103
|
+
|
|
104
|
+
@property
|
|
105
|
+
def releases_to_apply_in_prod(self):
|
|
106
|
+
"Returns the list of releases to apply in production"
|
|
107
|
+
current = self.__repo.database.last_release_s
|
|
108
|
+
releases_to_apply = []
|
|
109
|
+
to_apply = False
|
|
110
|
+
for elt in self.__log_list:
|
|
111
|
+
if elt[1] == current:
|
|
112
|
+
# we're here
|
|
113
|
+
to_apply = True
|
|
114
|
+
continue
|
|
115
|
+
if to_apply and elt[2]:
|
|
116
|
+
releases_to_apply.append(elt[1])
|
|
117
|
+
return releases_to_apply
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
CLI extension integration for half-orm-dev
|
|
6
|
+
|
|
7
|
+
Provides the halfORM development tools through the unified half_orm CLI interface.
|
|
8
|
+
Generates/Patches/Synchronizes a hop Python package with a PostgreSQL database.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import sys
|
|
12
|
+
import click
|
|
13
|
+
from half_orm.cli_utils import create_and_register_extension
|
|
14
|
+
|
|
15
|
+
# Import existing halfORM_dev functionality
|
|
16
|
+
from half_orm_dev.repo import Repo
|
|
17
|
+
from half_orm import utils
|
|
18
|
+
|
|
19
|
+
class Hop:
|
|
20
|
+
"""Sets the options available to the hop command"""
|
|
21
|
+
__available_cmds = []
|
|
22
|
+
__command = None
|
|
23
|
+
|
|
24
|
+
def __init__(self):
|
|
25
|
+
self.__repo: Repo = Repo()
|
|
26
|
+
if not self.repo_checked:
|
|
27
|
+
Hop.__available_cmds = ['new']
|
|
28
|
+
else:
|
|
29
|
+
if not self.__repo.devel:
|
|
30
|
+
# Sync-only mode
|
|
31
|
+
Hop.__available_cmds = ['sync-package']
|
|
32
|
+
else:
|
|
33
|
+
# Full mode - check environment
|
|
34
|
+
if self.__repo.production:
|
|
35
|
+
Hop.__available_cmds = ['upgrade', 'restore']
|
|
36
|
+
else:
|
|
37
|
+
Hop.__available_cmds = ['prepare', 'apply', 'release', 'undo']
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def repo_checked(self):
|
|
41
|
+
"""Returns whether we are in a repo or not."""
|
|
42
|
+
return self.__repo.checked
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def model(self):
|
|
46
|
+
"""Returns the model (half_orm.model.Model) associated to the repo."""
|
|
47
|
+
return self.__repo.model
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def state(self):
|
|
51
|
+
"""Returns the state of the repo."""
|
|
52
|
+
return self.__repo.state
|
|
53
|
+
|
|
54
|
+
@property
|
|
55
|
+
def command(self):
|
|
56
|
+
"""The command invoked (click)"""
|
|
57
|
+
return self.__command
|
|
58
|
+
|
|
59
|
+
def add_commands(main_group):
|
|
60
|
+
"""
|
|
61
|
+
Required entry point for halfORM extensions.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
main_group: The main Click group for the half_orm command
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
# Create hop instance to determine available commands
|
|
68
|
+
hop = Hop()
|
|
69
|
+
|
|
70
|
+
@create_and_register_extension(main_group, sys.modules[__name__])
|
|
71
|
+
def dev():
|
|
72
|
+
"""halfORM development tools - project management, patches, and database synchronization"""
|
|
73
|
+
pass
|
|
74
|
+
|
|
75
|
+
# Define all possible commands
|
|
76
|
+
@click.command()
|
|
77
|
+
@click.argument('package_name')
|
|
78
|
+
@click.option('-d', '--devel', is_flag=True, help="Development mode")
|
|
79
|
+
def new(package_name, devel=False):
|
|
80
|
+
"""Creates a new hop project named <package_name>."""
|
|
81
|
+
hop._Hop__repo.init(package_name, devel)
|
|
82
|
+
|
|
83
|
+
@click.command()
|
|
84
|
+
@click.option(
|
|
85
|
+
'-l', '--level',
|
|
86
|
+
type=click.Choice(['patch', 'minor', 'major']), help="Release level.")
|
|
87
|
+
@click.option('-m', '--message', type=str, help="The git commit message")
|
|
88
|
+
def prepare(level, message=None):
|
|
89
|
+
"""Prepares the next release."""
|
|
90
|
+
hop._Hop__command = 'prepare'
|
|
91
|
+
hop._Hop__repo.prepare_release(level, message)
|
|
92
|
+
sys.exit()
|
|
93
|
+
|
|
94
|
+
@click.command()
|
|
95
|
+
def apply():
|
|
96
|
+
"""Apply the current release."""
|
|
97
|
+
hop._Hop__command = 'apply'
|
|
98
|
+
hop._Hop__repo.apply_release()
|
|
99
|
+
|
|
100
|
+
@click.command()
|
|
101
|
+
@click.option(
|
|
102
|
+
'-d', '--database-only', is_flag=True,
|
|
103
|
+
help='Restore the database to the previous release.')
|
|
104
|
+
def undo(database_only):
|
|
105
|
+
"""Undo the last release."""
|
|
106
|
+
hop._Hop__command = 'undo'
|
|
107
|
+
hop._Hop__repo.undo_release(database_only)
|
|
108
|
+
|
|
109
|
+
@click.command()
|
|
110
|
+
def upgrade():
|
|
111
|
+
"""Apply one or many patches.
|
|
112
|
+
|
|
113
|
+
Switches to hop_main, pulls should check the tags.
|
|
114
|
+
"""
|
|
115
|
+
hop._Hop__command = 'upgrade_prod'
|
|
116
|
+
hop._Hop__repo.upgrade_prod()
|
|
117
|
+
|
|
118
|
+
@click.command()
|
|
119
|
+
@click.argument('release')
|
|
120
|
+
def restore(release):
|
|
121
|
+
"""Restore to release."""
|
|
122
|
+
hop._Hop__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
|
+
"""Commit and optionally push the current release."""
|
|
128
|
+
hop._Hop__repo.commit_release(push)
|
|
129
|
+
|
|
130
|
+
@click.command()
|
|
131
|
+
def sync_package():
|
|
132
|
+
"""Synchronize the Python package with the database model."""
|
|
133
|
+
hop._Hop__repo.sync_package()
|
|
134
|
+
|
|
135
|
+
# Map command names to command functions
|
|
136
|
+
all_commands = {
|
|
137
|
+
'new': new,
|
|
138
|
+
'prepare': prepare,
|
|
139
|
+
'apply': apply,
|
|
140
|
+
'undo': undo,
|
|
141
|
+
'release': release,
|
|
142
|
+
'sync-package': sync_package,
|
|
143
|
+
'upgrade': upgrade,
|
|
144
|
+
'restore': restore
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
# 🎯 COMPORTEMENT ADAPTATIF RESTAURÉ
|
|
148
|
+
# Only add commands that are available in the current context
|
|
149
|
+
for cmd_name in hop._Hop__available_cmds:
|
|
150
|
+
if cmd_name in all_commands:
|
|
151
|
+
dev.add_command(all_commands[cmd_name])
|
|
152
|
+
|
|
153
|
+
# Add callback to show state when no subcommand (like original hop)
|
|
154
|
+
original_callback = dev.callback
|
|
155
|
+
|
|
156
|
+
@click.pass_context
|
|
157
|
+
def enhanced_callback(ctx, *args, **kwargs):
|
|
158
|
+
if ctx.invoked_subcommand is None:
|
|
159
|
+
# Show repo state when no subcommand is provided
|
|
160
|
+
if hop.repo_checked:
|
|
161
|
+
click.echo(hop.state)
|
|
162
|
+
else:
|
|
163
|
+
click.echo(hop.state)
|
|
164
|
+
click.echo("\nNot in a hop repository.")
|
|
165
|
+
click.echo(f"Try {utils.Color.bold('half_orm dev new [--devel] <package_name>')} or change directory.\n")
|
|
166
|
+
else:
|
|
167
|
+
# Call original callback if there is one
|
|
168
|
+
if original_callback:
|
|
169
|
+
return original_callback(*args, **kwargs)
|
|
170
|
+
|
|
171
|
+
dev.callback = enhanced_callback
|
half_orm_dev/database.py
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"""Provides the Database class
|
|
2
|
+
"""
|
|
3
|
+
|
|
4
|
+
import os
|
|
5
|
+
import subprocess
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
from psycopg2 import OperationalError
|
|
9
|
+
from half_orm.model import Model
|
|
10
|
+
from half_orm.model_errors import UnknownRelation
|
|
11
|
+
from half_orm import utils
|
|
12
|
+
from half_orm_dev.db_conn import DbConn
|
|
13
|
+
from .utils import HOP_PATH
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Database:
|
|
17
|
+
"""Reads and writes the halfORM connection file
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(self, repo, get_release=True):
|
|
21
|
+
self.__repo = repo
|
|
22
|
+
self.__model = None
|
|
23
|
+
self.__last_release = None
|
|
24
|
+
self.__connection_params: DbConn = DbConn(self.__repo.name)
|
|
25
|
+
if self.__repo.name:
|
|
26
|
+
try:
|
|
27
|
+
self.__model = Model(self.__repo.name)
|
|
28
|
+
self.__init(self.__repo.name, get_release)
|
|
29
|
+
except OperationalError as err:
|
|
30
|
+
if not self.__repo.new:
|
|
31
|
+
utils.error(err, 1)
|
|
32
|
+
|
|
33
|
+
def __call__(self, name):
|
|
34
|
+
return self.__class__(self.__repo)
|
|
35
|
+
|
|
36
|
+
def __init(self, name, get_release=True):
|
|
37
|
+
self.__name = name
|
|
38
|
+
self.__connection_params = DbConn(name)
|
|
39
|
+
if get_release and self.__repo.devel:
|
|
40
|
+
self.__last_release = self.last_release
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
def last_release(self):
|
|
44
|
+
"Returns the last release"
|
|
45
|
+
self.__last_release = next(
|
|
46
|
+
self.__model.get_relation_class('half_orm_meta.view.hop_last_release')().ho_select())
|
|
47
|
+
return self.__last_release
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def last_release_s(self):
|
|
51
|
+
"Returns the string representation of the last release X.Y.Z"
|
|
52
|
+
return '{major}.{minor}.{patch}'.format(**self.last_release)
|
|
53
|
+
|
|
54
|
+
@property
|
|
55
|
+
def model(self):
|
|
56
|
+
"The model (halfORM) of the database"
|
|
57
|
+
return self.__model
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def state(self):
|
|
61
|
+
"The state (str) of the database"
|
|
62
|
+
res = ['[Database]']
|
|
63
|
+
res.append(f'- name: {self.__name}')
|
|
64
|
+
res.append(f'- user: {self.__connection_params.user}')
|
|
65
|
+
res.append(f'- host: {self.__connection_params.host}')
|
|
66
|
+
res.append(f'- port: {self.__connection_params.port}')
|
|
67
|
+
prod = utils.Color.blue(
|
|
68
|
+
True) if self.__connection_params.production else False
|
|
69
|
+
res.append(f'- production: {prod}')
|
|
70
|
+
if self.__repo.devel:
|
|
71
|
+
res.append(f'- last release: {self.last_release_s}')
|
|
72
|
+
return '\n'.join(res)
|
|
73
|
+
|
|
74
|
+
@property
|
|
75
|
+
def production(self):
|
|
76
|
+
"Returns wether the database is tagged in production or not."
|
|
77
|
+
return self.__connection_params.production
|
|
78
|
+
|
|
79
|
+
def init(self, name):
|
|
80
|
+
"""Called when creating a new repo.
|
|
81
|
+
Tries to read the connection parameters and then connect to
|
|
82
|
+
the database.
|
|
83
|
+
"""
|
|
84
|
+
try:
|
|
85
|
+
self.__init(name, get_release=False)
|
|
86
|
+
except FileNotFoundError:
|
|
87
|
+
pass
|
|
88
|
+
return self.__init_db()
|
|
89
|
+
|
|
90
|
+
def __init_db(self):
|
|
91
|
+
"""Tries to connect to the database. If unsuccessful, creates the
|
|
92
|
+
database end initializes it with half_orm_meta.
|
|
93
|
+
"""
|
|
94
|
+
try:
|
|
95
|
+
self.__model = Model(self.__name)
|
|
96
|
+
except OperationalError:
|
|
97
|
+
sys.stderr.write(f"The database '{self.__name}' does not exist.\n")
|
|
98
|
+
create = input('Do you want to create it (Y/n): ') or "y"
|
|
99
|
+
if create.upper() == 'Y':
|
|
100
|
+
self.execute_pg_command('createdb')
|
|
101
|
+
else:
|
|
102
|
+
utils.error(
|
|
103
|
+
f'Aborting! Please remove {self.__name} directory.\n', exit_code=1)
|
|
104
|
+
self.__model = Model(self.__name)
|
|
105
|
+
if self.__repo.devel:
|
|
106
|
+
try:
|
|
107
|
+
self.__model.get_relation_class('half_orm_meta.hop_release')
|
|
108
|
+
except UnknownRelation:
|
|
109
|
+
hop_init_sql_file = os.path.join(
|
|
110
|
+
HOP_PATH, 'patches', 'sql', 'half_orm_meta.sql')
|
|
111
|
+
self.execute_pg_command(
|
|
112
|
+
'psql', '-f', hop_init_sql_file, stdout=subprocess.DEVNULL)
|
|
113
|
+
self.__model.reconnect(reload=True)
|
|
114
|
+
self.__last_release = self.register_release(
|
|
115
|
+
major=0, minor=0, patch=0, changelog='Initial release')
|
|
116
|
+
return self(self.__name)
|
|
117
|
+
|
|
118
|
+
@property
|
|
119
|
+
def execute_pg_command(self):
|
|
120
|
+
"Helper: execute a postgresql command"
|
|
121
|
+
return self.__connection_params.execute_pg_command
|
|
122
|
+
|
|
123
|
+
def register_release(self, major, minor, patch, changelog):
|
|
124
|
+
"Register the release into half_orm_meta.hop_release"
|
|
125
|
+
return self.__model.get_relation_class('half_orm_meta.hop_release')(
|
|
126
|
+
major=major, minor=minor, patch=patch, changelog=changelog
|
|
127
|
+
).ho_insert()
|
half_orm_dev/db_conn.py
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"""Provides the DbConn class.
|
|
2
|
+
"""
|
|
3
|
+
|
|
4
|
+
import os
|
|
5
|
+
import subprocess
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
from getpass import getpass
|
|
9
|
+
from configparser import ConfigParser
|
|
10
|
+
|
|
11
|
+
from half_orm.model import CONF_DIR
|
|
12
|
+
from half_orm import utils
|
|
13
|
+
|
|
14
|
+
CONF_NOT_FOUND = '''
|
|
15
|
+
The configuration file {} is missing.
|
|
16
|
+
You must create it before proceeding.
|
|
17
|
+
|
|
18
|
+
'''
|
|
19
|
+
|
|
20
|
+
class DbConn:
|
|
21
|
+
"""Handles the connection parameters to the database.
|
|
22
|
+
Provides the execute_pg_command."""
|
|
23
|
+
__conf_dir = CONF_DIR # HALFORM_CONF_DIR
|
|
24
|
+
def __init__(self, name):
|
|
25
|
+
self.__name = name
|
|
26
|
+
self.__user = None
|
|
27
|
+
self.__password = None
|
|
28
|
+
self.__host = None
|
|
29
|
+
self.__port = None
|
|
30
|
+
self.__production = None
|
|
31
|
+
if name:
|
|
32
|
+
self.__connection_file = os.path.join(self.__conf_dir, self.__name)
|
|
33
|
+
if os.path.exists(self.__connection_file):
|
|
34
|
+
self.__init()
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def production(self):
|
|
38
|
+
"prod"
|
|
39
|
+
return self.__production
|
|
40
|
+
|
|
41
|
+
def __init(self):
|
|
42
|
+
"Reads the config file and sets the connection parameters"
|
|
43
|
+
config = ConfigParser()
|
|
44
|
+
config.read([self.__connection_file])
|
|
45
|
+
|
|
46
|
+
self.__name = config.get('database', 'name')
|
|
47
|
+
|
|
48
|
+
self.__user = config.get('database', 'user', fallback=None)
|
|
49
|
+
self.__password = config.get('database', 'password', fallback=None)
|
|
50
|
+
self.__host = config.get('database', 'host', fallback=None)
|
|
51
|
+
self.__port = config.get('database', 'port', fallback=None)
|
|
52
|
+
|
|
53
|
+
self.__production = config.getboolean('database', 'production', fallback=False)
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def host(self):
|
|
57
|
+
"Returns host name"
|
|
58
|
+
return self.__host
|
|
59
|
+
@property
|
|
60
|
+
def port(self):
|
|
61
|
+
"Returns port"
|
|
62
|
+
return self.__port
|
|
63
|
+
@property
|
|
64
|
+
def user(self):
|
|
65
|
+
"Returns user"
|
|
66
|
+
return self.__user
|
|
67
|
+
|
|
68
|
+
def set_params(self, name):
|
|
69
|
+
"""Asks for the connection parameters.
|
|
70
|
+
"""
|
|
71
|
+
if not os.access(self.__conf_dir, os.W_OK):
|
|
72
|
+
sys.stderr.write(f"You don't have write access to {self.__conf_dir}.\n")
|
|
73
|
+
if self.__conf_dir == '/etc/half_orm': # only on linux
|
|
74
|
+
utils.error(
|
|
75
|
+
"Set the HALFORM_CONF_DIR environment variable if you want to use a\n"
|
|
76
|
+
"different directory.\n", exit_code=1)
|
|
77
|
+
print(f'Connection parameters to the database {self.__name}:')
|
|
78
|
+
self.__user = os.environ['USER']
|
|
79
|
+
self.__user = input(f'. user ({self.__user}): ') or self.__user
|
|
80
|
+
self.__password = getpass('. password: ')
|
|
81
|
+
if self.__password == '' and \
|
|
82
|
+
(input(
|
|
83
|
+
'. is it an ident login with a local account? [Y/n] ') or 'Y').upper() == 'Y':
|
|
84
|
+
self.__host = self.__port = ''
|
|
85
|
+
else:
|
|
86
|
+
self.__host = input('. host (localhost): ') or 'localhost'
|
|
87
|
+
self.__port = input('. port (5432): ') or 5432
|
|
88
|
+
|
|
89
|
+
self.__production = input('Production (False): ') or False
|
|
90
|
+
|
|
91
|
+
self.__write_config()
|
|
92
|
+
|
|
93
|
+
return self
|
|
94
|
+
|
|
95
|
+
def __write_config(self):
|
|
96
|
+
"Helper: write file in utf8"
|
|
97
|
+
self.__connection_file = os.path.join(self.__conf_dir, self.__name)
|
|
98
|
+
config = ConfigParser()
|
|
99
|
+
config['database'] = {
|
|
100
|
+
'name': self.__name,
|
|
101
|
+
'user': self.__user,
|
|
102
|
+
'password': self.__password,
|
|
103
|
+
'host': self.__host,
|
|
104
|
+
'port': self.__port,
|
|
105
|
+
'production': self.__production
|
|
106
|
+
}
|
|
107
|
+
with open(self.__connection_file, 'w', encoding='utf-8') as configfile:
|
|
108
|
+
config.write(configfile)
|
|
109
|
+
|
|
110
|
+
def execute_pg_command(self, cmd, *args, **kwargs):
|
|
111
|
+
"""Helper. Executes a postgresql
|
|
112
|
+
"""
|
|
113
|
+
if not kwargs.get('stdout'):
|
|
114
|
+
kwargs['stdout'] = subprocess.DEVNULL
|
|
115
|
+
cmd_list = [cmd]
|
|
116
|
+
env = os.environ.copy()
|
|
117
|
+
password = self.__password
|
|
118
|
+
if password:
|
|
119
|
+
env['PGPASSWORD'] = password
|
|
120
|
+
if self.__user:
|
|
121
|
+
cmd_list += ['-U', self.__user]
|
|
122
|
+
if self.__port:
|
|
123
|
+
cmd_list += ['-p', self.__port]
|
|
124
|
+
if self.__host:
|
|
125
|
+
cmd_list += ['-h', self.__host]
|
|
126
|
+
cmd_list.append(self.__name)
|
|
127
|
+
if args:
|
|
128
|
+
cmd_list += args
|
|
129
|
+
try:
|
|
130
|
+
subprocess.run(
|
|
131
|
+
cmd_list, env=env, shell=False, check=True,
|
|
132
|
+
**kwargs)
|
|
133
|
+
except subprocess.CalledProcessError as err:
|
|
134
|
+
utils.error(f'{err}\ndatabase: {self.__name} with user: {self.__user}, host: {self.__host}, port: {self.__port}\n', exit_code=err.returncode)
|