[med-svn] [Git][med-team/gnumed-server][master] 6 commits: routine-update: New upstream version
Andreas Tille
gitlab at salsa.debian.org
Fri Jul 24 08:17:12 BST 2020
Andreas Tille pushed to branch master at Debian Med / gnumed-server
Commits:
440d986e by Andreas Tille at 2020-07-24T09:03:38+02:00
routine-update: New upstream version
- - - - -
edf8d011 by Andreas Tille at 2020-07-24T09:03:39+02:00
New upstream version 22.13
- - - - -
61b525d1 by Andreas Tille at 2020-07-24T09:04:39+02:00
Update upstream source from tag 'upstream/22.13'
Update to upstream version '22.13'
with Debian dir 41f7eb013092fe5083c236e9ca7ae5a1c5122610
- - - - -
d327a388 by Andreas Tille at 2020-07-24T09:04:39+02:00
routine-update: debhelper-compat 13
- - - - -
4424bf75 by Andreas Tille at 2020-07-24T09:04:44+02:00
routine-update: Rules-Requires-Root: no
- - - - -
3700d973 by Andreas Tille at 2020-07-24T09:06:20+02:00
routine-update: Ready to upload to unstable
- - - - -
20 changed files:
- debian/changelog
- debian/control
- server/bootstrap/bootstrap_gm_db_system.py
- server/doc/schema/gnumed-entire_schema.html
- server/gm-fingerprint_db.py
- server/pycommon/gmBackendListener.py
- server/pycommon/gmBusinessDBObject.py
- server/pycommon/gmCfg2.py
- server/pycommon/gmConnectionPool.py
- server/pycommon/gmDateTime.py
- server/pycommon/gmDispatcher.py
- server/pycommon/gmGuiBroker.py
- server/pycommon/gmI18N.py
- server/pycommon/gmLog2.py
- server/pycommon/gmLoginInfo.py
- server/pycommon/gmMimeLib.py
- server/pycommon/gmNetworkTools.py
- server/pycommon/gmPG2.py
- server/pycommon/gmTools.py
- server/sql/v21-v22/fixups/v22-release_notes-fixup.sql
Changes:
=====================================
debian/changelog
=====================================
@@ -1,3 +1,11 @@
+gnumed-server (22.13-1) unstable; urgency=medium
+
+ * New upstream version
+ * debhelper-compat 13 (routine-update)
+ * Rules-Requires-Root: no (routine-update)
+
+ -- Andreas Tille <tille at debian.org> Fri, 24 Jul 2020 09:04:48 +0200
+
gnumed-server (22.12-1) unstable; urgency=medium
* New upstream version
=====================================
debian/control
=====================================
@@ -4,7 +4,7 @@ Uploaders: Andreas Tille <tille at debian.org>,
Sebastian Hilbert <sebastian.hilbert at gmx.net>
Section: misc
Priority: optional
-Build-Depends: debhelper-compat (= 12),
+Build-Depends: debhelper-compat (= 13),
dh-python,
python3
Build-Depends-Indep: po-debconf
@@ -12,6 +12,7 @@ Standards-Version: 4.5.0
Vcs-Browser: https://salsa.debian.org/med-team/gnumed-server
Vcs-Git: https://salsa.debian.org/med-team/gnumed-server.git
Homepage: https://www.gnumed.de
+Rules-Requires-Root: no
Package: gnumed-server
Architecture: all
=====================================
server/bootstrap/bootstrap_gm_db_system.py
=====================================
@@ -290,7 +290,7 @@ def connect(host, port, db, user, passwd, conn_name=None):
#==================================================================
class user:
- def __init__(self, anAlias = None, aPassword = None):
+ def __init__(self, anAlias = None, aPassword = None, force_interactive=False):
if anAlias is None:
raise ConstructorError("need user alias")
self.alias = anAlias
@@ -314,10 +314,21 @@ class user:
# defined but empty:
# this means to ask the user if interactive
elif self.password == '':
- if _interactive:
+ if _interactive or force_interactive:
+ _log.info('password for [%s] defined as "", asking user', self.name)
print("I need the password for the database user [%s]." % self.name)
self.password = getpass.getpass("Please type the password: ")
+ _log.info('got password')
+ pwd4check = None
+ while pwd4check != self.password:
+ _log.info('asking for confirmation')
+ pwd2 = getpass.getpass("Please retype the password: ")
+ if pwd2 == self.password:
+ break
+ _log.error('password mismatch, asking again')
+ print('Password mismatch. Try again or CTRL-C to abort.')
else:
+ _log.warning('password for [%s] defined as "" (meaning <ask-user>), but running non-interactively, aborting', self.name)
_log.warning('cannot get password for database user [%s]', self.name)
raise ValueError('no password for user %s' % self.name)
@@ -495,7 +506,7 @@ unless it is pre-defined in the configuration file.
Make sure to remember the password for later use !
""") % name)
- _dbowner = user(anAlias = dbowner_alias)
+ _dbowner = user(anAlias = dbowner_alias, force_interactive = True)
cmd = 'create user "%s" with password \'%s\' createdb createrole in group "%s", "gm-logins"' % (_dbowner.name, _dbowner.password, self.auth_group)
try:
@@ -615,6 +626,18 @@ class database:
_log.error(u"Cannot connect to template database.")
return False
+ # fingerprint template db
+ try:
+ gmLog2.log_multiline (
+ logging.INFO,
+ message = 'template database fingerprint',
+ text = gmPG2.get_db_fingerprint(conn = self.conn, eol = '\n')
+ )
+ except Exception:
+ _log.exception('cannot fingerprint template database')
+
+ self.conn.rollback()
+
# make sure db exists
if not self.__create_db():
_log.error(u"Cannot create database.")
@@ -1754,6 +1777,24 @@ def handle_cfg():
if not import_data():
exit_with_msg("Bootstrapping failed: unable to import data")
+ # fingerprint target db
+ # (the ugliest of hacks)
+ db = None
+ for db_key in _bootstrapped_dbs.keys():
+ db = _bootstrapped_dbs[db_key]
+ if db is not None:
+ try:
+ gmLog2.log_multiline (
+ logging.INFO,
+ message = 'target database fingerprint',
+ text = gmPG2.get_db_fingerprint(conn = db.conn, eol = '\n')
+ )
+ except Exception:
+ _log.error('cannot fingerprint database')
+ finally:
+ db.conn.rollback()
+ return True
+
#==================================================================
def main():
=====================================
server/doc/schema/gnumed-entire_schema.html
=====================================
@@ -112,7 +112,7 @@
<body>
<!-- Primary Index -->
- <p><br><br>Dumped on 2020-03-25</p>
+ <p><br><br>Dumped on 2020-07-23</p>
<h1><a name="index">Index of database - gnumed_v22</a></h1>
<ul>
=====================================
server/gm-fingerprint_db.py
=====================================
@@ -12,7 +12,9 @@
import sys
import psycopg2
-import io
+#import io
+
+from Gnumed.pycommon import gmPG2
database = sys.argv[1]
passwd = sys.argv[2]
@@ -40,28 +42,33 @@ queries = [
fname = u'gm_db-%s-fingerprint.log' % database
#==============================================================
-outfile = io.open(fname, mode = 'wt', encoding = 'utf8')
+#outfile = io.open(fname, mode = 'wt', encoding = 'utf8')
-outfile.write(u"Fingerprinting GNUmed database ...\n")
-outfile.write(u"\n")
-outfile.write(u"%20s: %s\n" % (u"Name (DB)", database))
+#outfile.write(u"Fingerprinting GNUmed database ...\n")
+#outfile.write(u"\n")
+#outfile.write(u"%20s: %s\n" % (u"Name (DB)", database))
conn = psycopg2.connect(dsn=dsn)
-curs = conn.cursor()
-
-for cmd, label in queries:
- curs.execute(cmd)
- rows = curs.fetchall()
- outfile.write(u"%20s: %s\n" % (label, rows[0][0]))
-
+#curs = conn.cursor()
+#
+#for cmd, label in queries:
+# curs.execute(cmd)
+# rows = curs.fetchall()
+# outfile.write(u"%20s: %s\n" % (label, rows[0][0]))
+#
if len(sys.argv) > 3:
if sys.argv[3] == '--with-dump':
- curs.execute('SELECT gm.concat_table_structure()')
- rows = curs.fetchall()
- outfile.write(u"\n%s\n" % rows[0][0])
+ with_dump = True
+ else:
+ with_dump = False
+# curs.execute('SELECT gm.concat_table_structure()')
+# rows = curs.fetchall()
+# outfile.write(u"\n%s\n" % rows[0][0])
+
+gmPG2.fingerprint_db(conn = conn, with_dump = with_dump, fname = fname)
-curs.close()
+#curs.close()
conn.rollback()
-outfile.close()
+#outfile.close()
#==============================================================
=====================================
server/pycommon/gmBackendListener.py
=====================================
@@ -393,7 +393,7 @@ if __name__ == "__main__":
except KeyError:
print('==> received signal from client: "%s"' % kwargs['signal'])
del kwargs['signal']
- for key in kwargs.keys():
+ for key in kwargs:
print(' [%s]: %s' % (key, kwargs[key]))
gmDispatcher.connect(receiver = monitoring_callback)
=====================================
server/pycommon/gmBusinessDBObject.py
=====================================
@@ -355,7 +355,7 @@ def manage_xxx()
result = self.refetch_payload(link_obj = link_obj)
if result is True:
self.payload_most_recently_fetched = {}
- for field in self._idx.keys():
+ for field in self._idx:
self.payload_most_recently_fetched[field] = self._payload[self._idx[field]]
return True
@@ -398,7 +398,7 @@ def manage_xxx()
self.pk_obj = row['pk_obj']
self.payload_most_recently_fetched = {}
- for field in self._idx.keys():
+ for field in self._idx:
self.payload_most_recently_fetched[field] = self._payload[self._idx[field]]
#--------------------------------------------------------
@@ -413,7 +413,7 @@ def manage_xxx()
def __str__(self):
lines = []
try:
- for attr in self._idx.keys():
+ for attr in self._idx:
if self._payload[self._idx[attr]] is None:
lines.append('%s: NULL' % attr)
else:
@@ -440,7 +440,7 @@ def manage_xxx()
getter = getattr(self, 'get_%s' % attribute, None)
if not callable(getter):
_log.warning('[%s]: no attribute [%s]' % (self.__class__.__name__, attribute))
- _log.warning('[%s]: valid attributes: %s' % (self.__class__.__name__, str(self._idx.keys())))
+ _log.warning('[%s]: valid attributes: %s', self.__class__.__name__, list(self._idx))
_log.warning('[%s]: no getter method [get_%s]' % (self.__class__.__name__, attribute))
methods = [ m for m in inspect.getmembers(self, inspect.ismethod) if m[0].startswith('get_') ]
_log.warning('[%s]: valid getter methods: %s' % (self.__class__.__name__, str(methods)))
@@ -502,7 +502,7 @@ def manage_xxx()
#--------------------------------------------------------
def get_fields(self):
try:
- return self._idx.keys()
+ return list(self._idx)
except AttributeError:
return 'nascent [%s @ %s], cannot return keys' %(self.__class__.__name__, id(self))
@@ -517,7 +517,7 @@ def manage_xxx()
else:
bools = {True: bool_strings[0], False: bool_strings[1]}
data = {}
- for field in self._idx.keys():
+ for field in self._idx:
# FIXME: harden against BYTEA fields
#if type(self._payload[self._idx[field]]) == ...
# data[field] = _('<%s bytes of binary data>') % len(self._payload[self._idx[field]])
@@ -707,7 +707,7 @@ def manage_xxx()
return (True, None)
args = {}
- for field in self._idx.keys():
+ for field in self._idx:
args[field] = self._payload[self._idx[field]]
self.payload_most_recently_attempted_to_store = args
@@ -763,7 +763,7 @@ def manage_xxx()
# update to new "original" payload
self.payload_most_recently_fetched = {}
- for field in self._idx.keys():
+ for field in self._idx:
self.payload_most_recently_fetched[field] = self._payload[self._idx[field]]
return (True, None)
=====================================
server/pycommon/gmCfg2.py
=====================================
@@ -388,7 +388,7 @@ class gmCfgData(gmBorg.cBorg):
try: source_data = self.__cfg_data[source]
except KeyError:
_log.error('invalid config source [%s]', source)
- _log.debug('currently known sources: %s', self.__cfg_data.keys())
+ _log.debug('currently known sources: %s', list(self.__cfg_data))
continue
try: value = source_data[option_path]
=====================================
server/pycommon/gmConnectionPool.py
=====================================
@@ -12,6 +12,9 @@ __author__ = "karsten.hilbert at gmx.net"
__license__ = "GPL v2 or later (details at http://www.gnu.org)"
+_DISABLE_CONNECTION_POOL = False # set to True to disable the connection pool for debugging (= always return new connection)
+
+
# standard library imports
import os
import sys
@@ -29,7 +32,7 @@ import psycopg2 as dbapi
if not (float(dbapi.apilevel) >= 2.0):
raise ImportError('gmPG2: supported DB-API level too low')
-if not (dbapi.threadsafety > 0):
+if not (dbapi.threadsafety == 2):
raise ImportError('gmPG2: lacking minimum thread safety in psycopg2')
if not (dbapi.paramstyle == 'pyformat'):
@@ -50,6 +53,7 @@ try:
except ValueError:
raise ImportError('gmPG2: lacking v3 backend protocol support in psycopg2')
+
import psycopg2.extensions
import psycopg2.extras
import psycopg2.errorcodes as SQL_error_codes
@@ -84,6 +88,10 @@ _log.info('psycopg2 module version: %s' % dbapi.__version__)
_log.info('PostgreSQL via DB-API module "%s": API level %s, thread safety %s, parameter style "%s"' % (dbapi, dbapi.apilevel, dbapi.threadsafety, dbapi.paramstyle))
_log.info('libpq version (compiled in): %s', psycopg2.__libpq_version__)
_log.info('libpq version (loaded now) : %s', psycopg2.extensions.libpq_version())
+#if '2.8' in dbapi.__version__:
+# _log.info('psycopg2 v2.8 detected, disabling connection pooling for the time being')
+# _DISABLE_CONNECTION_POOL = True
+
postgresql_version = None
@@ -117,54 +125,52 @@ _map_psyco_iso_level2str = {
class cPGCredentials:
def __init__(self) -> None:
- self.__host = None # None: left out of DSN -> defaults to $PGHOST or implicit <localhost>
- self.__port = None # None: left out of DSN -> defaults to $PGPORT or libpq compiled-in default (typically 5432)
+ self.__host = None # None: left out -> defaults to $PGHOST or implicit <localhost>
+ self.__port = None # None: left out -> defaults to $PGPORT or libpq compiled-in default (typically 5432)
self.__database = None # must be set before connecting
self.__user = None # must be set before connecting
- self.__password = None # None: left out of DSN
+ self.__password = None # None: left out
# -> try password-less connect (TRUST/IDENT/PEER)
- # -> try connect with password from DSN:<passfile> or $PGPASSFILE or ~/.pgpass
+ # -> try connect with password from <passfile> parameter or $PGPASSFILE or ~/.pgpass
#--------------------------------------------------
# properties
#--------------------------------------------------
def __format_credentials(self):
- dsn_parts = [
+ cred_parts = [
'dbname=%s' % self.__database,
'host=%s' % self.__host,
'port=%s' % self.__port,
'user=%s' % self.__user
]
- return ' '.join(dsn_parts)
+ return ' '.join(cred_parts)
- formatted_dsn = property(__format_credentials)
+ formatted_credentials = property(__format_credentials)
#--------------------------------------------------
- def generate_dsn(self, connection_name=None):
+ def generate_credentials_kwargs(self, connection_name=None):
assert (self.__database is not None), 'self.__database must be defined'
assert (self.__user is not None), 'self.__user must be defined'
-
- dsn_parts = [
- 'dbname=%s' % self.__database,
- #'port=%s' % gmTools.coalesce(self.__port, '5432'),
- 'user=%s' % self.__user,
- 'application_name=%s' % gmTools.coalesce(connection_name, 'GNUmed'),
- 'fallback_application_name=GNUmed',
- 'sslmode=prefer',
+ kwargs = {
+ 'dbname': self.__database,
+ 'user': self.__user,
+ 'application_name': gmTools.coalesce(connection_name, 'GNUmed'),
+ 'fallback_application_name': 'GNUmed',
+ 'sslmode': 'prefer',
# try to enforce a useful encoding early on so that we
# have a good chance of decoding authentication errors
# containing foreign language characters
- 'client_encoding=UTF8'
- ]
+ 'client_encoding': 'UTF8'
+ }
if self.__host is not None:
- dsn_parts.append('host=%s' % self.__host)
+ kwargs['host'] = self.__host
if self.__port is not None:
- dsn_parts.append('port=%s' % self.__port)
+ kwargs['port'] = self.__port
if self.__password is not None:
- dsn_parts.append('password=%s' % self.__password)
- return ' '.join(dsn_parts)
+ kwargs['password'] = self.__password
+ return kwargs
- validated_dsn = property(generate_dsn)
+ credentials_kwargs = property(generate_credentials_kwargs)
#--------------------------------------------------
def _get_database(self):
@@ -240,7 +246,7 @@ class gmConnectionPool(gmBorg.cBorg):
self.__initialized = True
_log.info('[%s]: first instantiation', self.__class__.__name__)
- self.__ro_conn_pool = {} # keyed by "DSN::thread ID"
+ self.__ro_conn_pool = {} # keyed by "credentials::thread ID"
self.__SQL_set_client_timezone = None
self.__client_timezone = None
self.__creds = None
@@ -250,6 +256,10 @@ class gmConnectionPool(gmBorg.cBorg):
# connection API
#--------------------------------------------------
def get_connection(self, readonly=True, verbose=False, pooled=True, connection_name=None, autocommit=False, credentials=None):
+
+ if _DISABLE_CONNECTION_POOL:
+ pooled = False
+
if credentials is not None:
pooled = False
conn = None
@@ -328,12 +338,12 @@ class gmConnectionPool(gmBorg.cBorg):
creds2use = self.__creds
else:
creds2use = credentials
- dsn = creds2use.generate_dsn(connection_name = connection_name)
+ creds_kwargs = creds2use.generate_credentials_kwargs(connection_name = connection_name)
try:
# DictConnection now _is_ a real dictionary
- conn = dbapi.connect(dsn = dsn, connection_factory = psycopg2.extras.DictConnection)
+ conn = dbapi.connect(connection_factory = psycopg2.extras.DictConnection, **creds_kwargs)
except dbapi.OperationalError as e:
- _log.error('failed to establish connection [%s]', creds2use.formatted_dsn)
+ _log.error('failed to establish connection [%s]', creds2use.formatted_credentials)
t, v, tb = sys.exc_info()
try:
msg = e.args[0]
@@ -343,7 +353,7 @@ class gmConnectionPool(gmBorg.cBorg):
if not self.__is_auth_fail_msg(msg):
raise
- raise cAuthenticationError(creds2use.formatted_dsn, msg).with_traceback(tb)
+ raise cAuthenticationError(creds2use.formatted_credentials, msg).with_traceback(tb)
_log.debug('established connection "%s", backend PID: %s', gmTools.coalesce(connection_name, 'anonymous'), conn.get_backend_pid())
# - inspect server
@@ -580,9 +590,9 @@ class gmConnectionPool(gmBorg.cBorg):
def _set_credentials(self, creds=None):
if self.__creds is not None:
_log.debug('invalidating pooled connections on credentials change')
- curr_dsn = self.__creds.formatted_dsn + '::'
+ curr_creds = self.__creds.formatted_credentials + '::'
for conn_key in self.__ro_conn_pool:
- if not conn_key.startswith(curr_dsn):
+ if not conn_key.startswith(curr_creds):
continue
conn = self.__ro_conn_pool[conn_key]
self.__ro_conn_pool[conn_key] = None
@@ -601,7 +611,7 @@ class gmConnectionPool(gmBorg.cBorg):
#--------------------------------------------------
def _get_pool_key(self):
return '%s::%s' % (
- self.__creds.formatted_dsn,
+ self.__creds.formatted_credentials,
threading.current_thread().ident
)
@@ -868,12 +878,12 @@ def _raise_exception_on_pooled_ro_conn_close():
#========================================================================
class cAuthenticationError(dbapi.OperationalError):
- def __init__(self, dsn=None, prev_val=None):
- self.dsn = dsn
+ def __init__(self, creds=None, prev_val=None):
+ self.creds = creds
self.prev_val = prev_val
def __str__(self):
- return 'PostgreSQL: %sDSN: %s' % (self.prev_val, self.dsn)
+ return 'PostgreSQL: %sDSN: %s' % (self.prev_val, self.creds)
#============================================================
# Python -> PostgreSQL
@@ -920,7 +930,7 @@ if __name__ == "__main__":
print("testing exceptions")
try:
- raise cAuthenticationError('no dsn', 'no previous exception')
+ raise cAuthenticationError('no credentials', 'no previous exception')
except cAuthenticationError:
t, v, tb = sys.exc_info()
print(t)
@@ -1047,6 +1057,22 @@ if __name__ == "__main__":
conn = pool.get_connection()
#--------------------------------------------------------------------
- test_exceptions()
- test_get_connection()
- test_change_creds()
+ def test_credentials():
+ print("testing credentials with spaces")
+ pool = gmConnectionPool()
+ creds = cPGCredentials()
+ creds.database = 'gnumed_v22'
+ creds.user = 'any-doc'
+ creds.password = 'any-doc'
+ pool.credentials = creds
+ conn = pool.get_connection()
+ print(conn)
+ creds.password = 'a - b'
+ pool.credentials = creds
+ conn = pool.get_connection()
+
+ #--------------------------------------------------------------------
+ test_credentials()
+ #test_exceptions()
+ #test_get_connection()
+ #test_change_creds()
=====================================
server/pycommon/gmDateTime.py
=====================================
@@ -2162,7 +2162,7 @@ if __name__ == '__main__':
#-----------------------------------------------------------------------
def test_format_interval():
intv = pyDT.timedelta(minutes=1, seconds=2)
- for acc in _accuracy_strings.keys():
+ for acc in _accuracy_strings:
print ('[%s]: "%s" -> "%s"' % (acc, intv, format_interval(intv, acc)))
return
@@ -2171,7 +2171,7 @@ if __name__ == '__main__':
if intv is None:
print(tmp, '->', intv)
continue
- for acc in _accuracy_strings.keys():
+ for acc in _accuracy_strings:
print ('[%s]: "%s" -> "%s"' % (acc, tmp, format_interval(intv, acc)))
#-----------------------------------------------------------------------
=====================================
server/pycommon/gmDispatcher.py
=====================================
@@ -322,7 +322,7 @@ def _call(receiver, **kwds):
if not (func_code_def.co_flags & 0x08):
# func_code_def does not have a **kwds type parameter,
# therefore remove unacceptable arguments.
- keys = list(kwds.keys())
+ keys = list(kwds)
for arg in keys:
if arg not in acceptable_args:
del kwds[arg]
@@ -337,8 +337,8 @@ def _call(receiver, **kwds):
#---------------------------------------------------------------------
def _removeReceiver(receiver):
"""Remove receiver from connections."""
- for sender_identity in connections.keys():
- for signal in connections[sender_identity].keys():
+ for sender_identity in connections:
+ for signal in connections[sender_identity]:
receivers = connections[sender_identity][signal]
try:
receivers.remove(receiver)
=====================================
server/pycommon/gmGuiBroker.py
=====================================
@@ -70,7 +70,7 @@ class GuiBroker:
def keylist(self):
" returns a list of all keys; see documentation for the dictionary data type"
- return GuiBroker.__objects.keys()
+ return list(GuiBroker.__objects)
=====================================
server/pycommon/gmI18N.py
=====================================
@@ -107,37 +107,33 @@ def __split_locale_into_levels():
#---------------------------------------------------------------------------
def __log_locale_settings(message=None):
_setlocale_categories = {}
- for category in 'LC_ALL LC_CTYPE LC_COLLATE LC_TIME LC_MONETARY LC_MESSAGES LC_NUMERIC'.split():
+ for category in 'LC_ALL LC_CTYPE LC_NUMERIC LC_TIME LC_COLLATE LC_MONETARY LC_MESSAGES LC_PAPER LC_NAME LC_ADDRESS LC_TELEPHONE LC_MEASUREMENT LC_IDENTIFICATION'.split():
try:
_setlocale_categories[category] = getattr(locale, category)
except Exception:
_log.warning('this OS does not have locale.%s', category)
-
_getlocale_categories = {}
- for category in 'LC_CTYPE LC_COLLATE LC_TIME LC_MONETARY LC_MESSAGES LC_NUMERIC'.split():
+ for category in 'LC_CTYPE LC_NUMERIC LC_TIME LC_COLLATE LC_MONETARY LC_MESSAGES LC_PAPER LC_NAME LC_ADDRESS LC_TELEPHONE LC_MEASUREMENT LC_IDENTIFICATION'.split():
try:
_getlocale_categories[category] = getattr(locale, category)
except Exception:
pass
-
if message is not None:
_log.debug(message)
_log.debug('current locale settings:')
_log.debug('locale.getlocale(): %s' % str(locale.getlocale()))
- for category in _getlocale_categories.keys():
+ for category in _getlocale_categories:
_log.debug('locale.getlocale(%s): %s' % (category, locale.getlocale(_getlocale_categories[category])))
-
- for category in _setlocale_categories.keys():
+ for category in _setlocale_categories:
_log.debug('(locale.setlocale(%s): %s)' % (category, locale.setlocale(_setlocale_categories[category])))
-
try:
_log.debug('locale.getdefaultlocale() - default (user) locale: %s' % str(locale.getdefaultlocale()))
except ValueError:
_log.exception('the OS locale setup seems faulty')
_log.debug('encoding sanity check (also check "locale.nl_langinfo(CODESET)" below):')
- pref_loc_enc = locale.getpreferredencoding(do_setlocale=False)
+ pref_loc_enc = locale.getpreferredencoding(do_setlocale = False)
loc_enc = locale.getlocale()[1]
py_str_enc = sys.getdefaultencoding()
sys_fs_enc = sys.getfilesystemencoding()
@@ -171,7 +167,7 @@ def __log_locale_settings(message=None):
_log.debug('database of locale conventions:')
data = locale.localeconv()
- for key in data.keys():
+ for key in data:
if loc_enc is None:
_log.debug('locale.localeconv(%s): %s', key, data[key])
else:
@@ -186,7 +182,7 @@ def __log_locale_settings(message=None):
except Exception:
_log.warning('this OS does not support nl_langinfo category locale.%s' % category)
try:
- for category in _nl_langinfo_categories.keys():
+ for category in _nl_langinfo_categories:
if loc_enc is None:
_log.debug('locale.nl_langinfo(%s): %s' % (category, locale.nl_langinfo(_nl_langinfo_categories[category])))
else:
@@ -231,37 +227,25 @@ def _translate_safely(term):
def activate_locale():
"""Get system locale from environment."""
global system_locale
-
- __log_locale_settings('unmodified startup locale settings (should be [C])')
-
- # activate user-preferred locale
+ __log_locale_settings('unmodified startup locale settings (could be [C])')
loc, enc = None, None
+ # activate user-preferred locale
try:
- # check whether already set
- loc, loc_enc = locale.getlocale()
- if loc is None:
- loc = locale.setlocale(locale.LC_ALL, '')
- _log.debug("activating user-default locale with <locale.setlocale(locale.LC_ALL, '')> returns: [%s]" % loc)
- else:
- _log.info('user-default locale already activated')
- loc, loc_enc = locale.getlocale()
+ loc = locale.setlocale(locale.LC_ALL, '')
+ _log.debug("activating user-default locale with <locale.setlocale(locale.LC_ALL, '')> returns: [%s]" % loc)
except AttributeError:
_log.exception('Windows does not support locale.LC_ALL')
except Exception:
_log.exception('error activating user-default locale')
-
__log_locale_settings('locale settings after activating user-default locale')
-
- # did we find any locale setting ? assume en_EN if not
+ # assume en_EN if we did not find any locale settings
if loc in [None, 'C']:
_log.error('the current system locale is still [None] or [C], assuming [en_EN]')
system_locale = "en_EN"
else:
system_locale = loc
-
# generate system locale levels
__split_locale_into_levels()
-
return True
#---------------------------------------------------------------------------
=====================================
server/pycommon/gmLog2.py
=====================================
@@ -339,6 +339,9 @@ if __name__ == '__main__':
#-----------------------------------------------------------
def test():
logger = logging.getLogger('gmLog2.test')
+
+ logger.error('test %s', [1,2,3])
+
logger.error("I expected to see %s::test()" % __file__)
add_word2hide('super secret passphrase')
logger.debug('credentials: super secret passphrase')
=====================================
server/pycommon/gmLoginInfo.py
=====================================
@@ -11,12 +11,6 @@ _log = logging.getLogger('gm.db')
class LoginInfo:
"""a class to encapsulate Postgres login information to default database"""
- # private variables
-# user = ''
-# password = ''
-# host = ''
-# port = 5432
-# database = ''
#------------------------------------------
def __init__(self, user=None, password=None, host=None, port=5432, database=None):
self.user = user
@@ -33,80 +27,6 @@ class LoginInfo:
port = property(_get_port, _set_port)
#------------------------------------------
- def GetInfo(self):
- return (
- self.GetUser(),
- self.GetPassword(),
- self.GetHost(),
- self.GetPort(),
- self.GetDatabase(),
- self.GetProfile()
- )
- #------------------------------------------
- def GetInfoStr(self):
- # don't hand out passwords just like that
- info = "host:port=%s:%s, db=%s, user=%s, pw=??" % (
- self.GetHost(),
- str(self.GetPort()),
- self.GetDatabase(),
- self.GetUser()
- )
- return info
- #------------------------------------------
- def GetPGDB_DSN(self):
- host = self.GetHost()
- port = str(self.GetPort())
- # for local UNIX domain sockets connections: leave host/port empty
- # IH: *PLEASE* option of local TCP/IP connection must be available
-# if host in ['', 'localhost']:
-# host = ""
- if host == '':
- port = ''
- dsn = "%s:%s:%s:%s" % (
- host,
- self.GetDatabase(),
- self.GetUser(),
- self.GetPassword()
- )
- host_port = "%s:%s" % (host, port)
- return dsn, host_port
- #------------------------------------------
- def get_psycopg2_dsn(self):
- dsn_parts = []
-
- if self.database.strip() != '':
- dsn_parts.append('dbname=%s' % self.database)
-
- if self.host.strip() != '':
- dsn_parts.append('host=%s' % self.host)
-
- dsn_parts.append('port=%s' % self.port)
-
- if self.user.strip() != '':
- dsn_parts.append('user=%s' % self.user)
-
- if self.password.strip() != '':
- dsn_parts.append('password=%s' % self.password)
-
- return ' '.join(dsn_parts)
- #------------------------------------------
- def GetDBAPI_DSN(self):
- host = self.GetHost()
- port = str(self.GetPort())
- # for local UNIX domain sockets connections: leave host/port empty
-# if host in ['', 'localhost']:
-# host = ''
- if host == '':
- port = ''
- dsn = "%s:%s:%s:%s:%s" % (
- host,
- port,
- self.GetDatabase(),
- self.GetUser(),
- self.GetPassword()
- )
- return dsn
- #------------------------------------------
def SetUser(self, user):
self.user = user
#------------------------------------------
=====================================
server/pycommon/gmMimeLib.py
=====================================
@@ -228,7 +228,7 @@ def _get_system_startfile_cmd(filename):
if _system_startfile_cmd is not None:
return True, _system_startfile_cmd % filename
- open_cmd_candidates = open_cmds.keys()
+ open_cmd_candidates = list(open_cmds)
for candidate in open_cmd_candidates:
found, binary = gmShellAPI.detect_external_binary(binary = candidate)
=====================================
server/pycommon/gmNetworkTools.py
=====================================
@@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
-
-
__doc__ = """GNUmed internetworking tools."""
#===========================================================================
@@ -155,10 +153,19 @@ def compare_versions(left_version, right_version):
-1: left < right
1: left > right
"""
+ _log.debug('comparing [%s] with [%s]', left_version, right_version)
if left_version == right_version:
- _log.debug('same version: [%s] = [%s]', left_version, right_version)
+ _log.debug('same version')
return 0
+ if right_version in ['head', 'dev', 'devel']:
+ _log.debug('development code')
+ return -1
+
+ if left_version in ['head', 'dev', 'devel']:
+ _log.debug('development code')
+ return 1
+
left_parts = left_version.split('.')
right_parts = right_version.split('.')
@@ -194,8 +201,12 @@ def check_for_update(url=None, current_branch=None, current_version=None, consid
False: up to date
None: don't know
"""
- if current_version == 'GIT HEAD':
- _log.debug('GIT HEAD always up to date')
+ if current_version is None:
+ _log.debug('<current_version> is None, currency unknown')
+ return (None, None)
+
+ if current_version.lower() in ['git head', 'head', 'tip', 'dev', 'devel']:
+ _log.debug('[%s] always considered up to date', current_version)
return (False, None)
try:
=====================================
server/pycommon/gmPG2.py
=====================================
@@ -300,6 +300,7 @@ def __request_login_params_tui():
gmLog2.add_word2hide(login.password)
login.port = prompted_input(prompt = "port", default = 5432)
except KeyboardInterrupt:
+ del login
_log.warning("user cancelled text mode login dialog")
print("user cancelled text mode login dialog")
raise gmExceptions.ConnectionError(_("Cannot connect to database without login information!"))
@@ -346,21 +347,28 @@ def __request_login_params_gui_wx():
return login, creds
#---------------------------------------------------
-def request_login_params():
+def request_login_params(setup_pool=False):
"""Request login parameters for database connection."""
-
# are we inside X ?
# if we aren't wxGTK will crash hard at the C-level with "can't open Display"
if 'DISPLAY' in os.environ:
# try wxPython GUI
try:
- return __request_login_params_gui_wx()
+ login, creds = __request_login_params_gui_wx()
except Exception:
pass
+ if setup_pool:
+ pool = gmConnectionPool.gmConnectionPool()
+ pool.credentials = creds
+ return login, creds
# well, either we are on the console or
# wxPython does not work, use text mode
- return __request_login_params_tui()
+ login, creds = __request_login_params_tui()
+ if setup_pool:
+ pool = gmConnectionPool.gmConnectionPool()
+ pool.credentials = creds
+ return login, creds
# =======================================================================
# netadata API
@@ -435,6 +443,70 @@ def get_schema_revision_history(link_obj=None):
rows, idx = run_ro_queries(link_obj = link_obj, queries = [{'cmd': cmd}])
return rows
+
+#------------------------------------------------------------------------
+def get_db_fingerprint(conn=None, fname=None, with_dump=False, eol=None):
+ queries = [
+ ("SELECT setting FROM pg_settings WHERE name = 'server_version'", "Version (PG)"),
+ ("SELECT setting FROM pg_settings WHERE name = 'server_encoding'", "Encoding (PG)"),
+ ("SELECT setting FROM pg_settings WHERE name = 'lc_collate'", "LC_COLLATE (PG)"),
+ ("SELECT setting FROM pg_settings WHERE name = 'lc_ctype'", "LC_CTYPE (PG)"),
+ ("SELECT count(1) FROM dem.identity", "Patients"),
+ ("SELECT count(1) FROM clin.encounter", "Contacts"),
+ ("SELECT count(1) FROM clin.episode", "Episodes"),
+ ("SELECT count(1) FROM clin.health_issue", "Issues"),
+ ("SELECT count(1) FROM clin.test_result", "Results"),
+ ("SELECT count(1) FROM clin.vaccination", "Vaccinations"),
+ ("SELECT count(1) FROM blobs.doc_med", "Documents"),
+ ("SELECT count(1) FROM blobs.doc_obj", "Objects"),
+ ("SELECT count(1) FROM dem.org", "Organizations"),
+ ("SELECT count(1) FROM dem.org_unit", "Organizational units"),
+ ("SELECT max(modified_when) FROM audit.audit_fields", "Most recent .modified_when"),
+ ("SELECT max(audit_when) FROM audit.audit_trail", "Most recent .audit_when")
+ ]
+ if conn is None:
+ conn = get_connection(readonly = True)
+ database = conn.get_dsn_parameters()['dbname']
+ lines = [
+ 'Fingerprinting GNUmed database ...',
+ '',
+ '%20s: %s' % ('Name (DB)', database)
+ ]
+ curs = conn.cursor()
+ # get size
+ cmd = "SELECT pg_size_pretty(pg_database_size('%s'))" % database
+ curs.execute(cmd)
+ rows = curs.fetchall()
+ lines.append('%20s: %s' % ('Size (DB)', rows[0][0]))
+ # get hash
+ cmd = "SELECT md5(gm.concat_table_structure())"
+ curs.execute(cmd)
+ rows = curs.fetchall()
+ md5_sum = rows[0][0]
+ try:
+ lines.append('%20s: %s (v%s)' % ('Schema hash', md5_sum, map_schema_hash2version[md5_sum]))
+ except KeyError:
+ lines.append('%20s: %s' % ('Schema hash', md5_sum))
+ for cmd, label in queries:
+ curs.execute(cmd)
+ rows = curs.fetchall()
+ lines.append('%20s: %s' % (label, rows[0][0]))
+ if with_dump:
+ curs.execute('SELECT gm.concat_table_structure()')
+ rows = curs.fetchall()
+ lines.append('')
+ lines.append(rows[0][0])
+ curs.close()
+ if fname is None:
+ if eol is None:
+ return lines
+ return eol.join(lines)
+
+ outfile = open(fname, mode = 'wt', encoding = 'utf8')
+ outfile.write('\n'.join(lines))
+ outfile.close()
+ return fname
+
#------------------------------------------------------------------------
def get_current_user():
rows, idx = run_ro_queries(queries = [{'cmd': 'select CURRENT_USER'}])
@@ -1803,7 +1875,7 @@ def run_insert(link_obj=None, schema=None, table=None, values=None, returning=No
if schema is None:
schema = 'public'
- fields = values.keys() # that way val_snippets and fields really should end up in the same order
+ fields = list(values) # that way val_snippets and fields really should end up in the same order
val_snippets = []
for field in fields:
val_snippets.append('%%(%s)s' % field)
@@ -1841,11 +1913,11 @@ def run_insert(link_obj=None, schema=None, table=None, values=None, returning=No
# =======================================================================
# connection handling API
# -----------------------------------------------------------------------
-def get_raw_connection(dsn=None, verbose=False, readonly=True, connection_name=None, autocommit=False):
+def get_raw_connection(verbose=False, readonly=True, connection_name=None, autocommit=False):
"""Get a raw, unadorned connection.
- this will not set any parameters such as encoding, timezone, datestyle
- - the only requirement is a valid DSN
+ - the only requirement is valid connection parameters having been passed to the connection pool
- hence it can be used for "service" connections
for verifying encodings etc
"""
@@ -1857,12 +1929,13 @@ def get_raw_connection(dsn=None, verbose=False, readonly=True, connection_name=N
)
# =======================================================================
-def get_connection(dsn=None, readonly=True, verbose=False, pooled=True, connection_name=None, autocommit=False):
+def get_connection(readonly=True, verbose=False, pooled=True, connection_name=None, autocommit=False):
return gmConnectionPool.gmConnectionPool().get_connection (
readonly = readonly,
verbose = verbose,
connection_name = connection_name,
- autocommit = autocommit
+ autocommit = autocommit,
+ pooled = pooled
)
#-----------------------------------------------------------------------
@@ -1985,7 +2058,7 @@ def sanity_check_database_settings():
cmd = "SELECT name, setting from pg_settings where name in %(settings)s"
rows, idx = run_ro_queries (
link_obj = conn,
- queries = [{'cmd': cmd, 'args': {'settings': tuple(options2check.keys())}}],
+ queries = [{'cmd': cmd, 'args': {'settings': tuple(options2check)}}],
get_col_idx = False
)
@@ -2513,6 +2586,15 @@ SELECT to_timestamp (foofoo,'YYMMDD.HH24MI') FROM (
conn = get_connection()
gmConnectionPool.log_pg_settings(curs = conn.cursor())
+ #--------------------------------------------------------------------
+ def test_get_db_fingerprint():
+ login, creds = request_login_params()
+ pool = gmConnectionPool.gmConnectionPool()
+ pool.credentials = creds
+ #conn = get_connection()
+ #print(get_db_fingerprint(conn, with_dump = True, eol = '\n'))
+ print(get_db_fingerprint(with_dump = True, eol = '\n'))
+
#--------------------------------------------------------------------
# run tests
@@ -2535,11 +2617,12 @@ SELECT to_timestamp (foofoo,'YYMMDD.HH24MI') FROM (
#test_get_index_name()
#test_set_user_language()
#test_get_schema_revision_history()
- test_run_query()
+ #test_run_query()
#test_schema_exists()
#test_get_foreign_key_names()
#test_row_locks()
#test_faulty_SQL()
- test_log_settings()
+ #test_log_settings()
+ test_get_db_fingerprint()
# ======================================================================
=====================================
server/pycommon/gmTools.py
=====================================
@@ -698,7 +698,7 @@ def old_unicode_csv_reader(unicode_csv_data, dialect=csv.excel, encoding='utf-8'
for row in csv_reader:
# decode ENCODING back to Unicode, cell by cell:
if is_dict_reader:
- for key in row.keys():
+ for key in row:
if key == default_csv_reader_rest_key:
old_data = row[key]
new_data = []
@@ -1436,8 +1436,8 @@ def compare_dict_likes(d1, d2, title1=None, title2=None):
d2 = dict(d2)
except TypeError:
pass
- keys_d1 = frozenset(d1.keys())
- keys_d2 = frozenset(d2.keys())
+ keys_d1 = list(d1)
+ keys_d2 = list(d2)
different = False
if len(keys_d1) != len(keys_d2):
_log.info('different number of keys: %s vs %s', len(keys_d1), len(keys_d2))
@@ -1487,8 +1487,8 @@ def format_dict_likes_comparison(d1, d2, title_left=None, title_right=None, left
except TypeError: pass
try: d2 = dict(d2)
except TypeError: pass
- keys_d1 = d1.keys()
- keys_d2 = d2.keys()
+ keys_d1 = list(d1)
+ keys_d2 = list(d2)
data = {}
for key in keys_d1:
data[key] = [d1[key], ' ']
@@ -1586,7 +1586,7 @@ def format_dict_like(d, relevant_keys=None, template=None, missing_key_template=
return template % d
if relevant_keys is None:
- relevant_keys = list(d.keys())
+ relevant_keys = list(d)
lines = []
if value_delimiters is None:
delim_left = ''
@@ -1636,7 +1636,7 @@ def dicts2table(dict_list, left_margin=0, eol='\n', keys2ignore=None, column_lab
d[col_label_key] = max(column_labels[dict_idx].split('\n'), key = len)
field_lengths = []
# loop over all keys in this dict
- for key in d.keys():
+ for key in d:
# ignore this key
if key in keys2ignore:
continue
=====================================
server/sql/v21-v22/fixups/v22-release_notes-fixup.sql
=====================================
@@ -17,25 +17,28 @@ INSERT INTO dem.message_inbox (
) VALUES (
(select pk from dem.staff where db_user = 'any-doc'),
(select pk_type from dem.v_inbox_item_type where type = 'memo' and category = 'administrative'),
- 'Release Notes for GNUmed 1.8.1 (database v22.12)',
- 'GNUmed 1.8.1 Release Notes:
+ 'Release Notes for GNUmed 1.8.3 (database v22.13)',
+ 'GNUmed 1.8.3 Release Notes:
- 1.8.1
+ 1.8.3
-NEW: tool: read_all_rows_of_table
+NEW: tool: fingerprint_db
-FIX: bills: failure to generate bill PDFs [thanks Marc]
-FIX: bills: failure to edit bill item date [thanks Marc]
-FIX: bills: failure to edit billable [thanks Marc]
-FIX: export area: fails to load when gm-burn.sh not found [thanks Marc]
-FIX: demographics: failure to edit type of address [thanks Marc]
-FIX: forms: failure to archive generated forms [thanks Marc]
-FIX: demographics: faulty display of patient addresses [thanks Marc]
+IMPROVED: forms: log pdflatex version
- 22.12
+FIX: meds: drug data source selection [thanks bganglia892]
+FIX: meds: ADR URL configuration [thanks bganglia892]
+FIX: vaccs: ADR URL configuration [thanks bganglia892]
+FIX: tests: LOINC import fixup [thanks bganglia892]
+FIX: db: whitespace in connection parameters [thanks kikiruz]
+FIX: startup: startup without console fails [thanks Marc]
+FIX: top panel: incorrect age-at-birthday display
+FIX: i18n: locale activation
-IMPROVED: robustify backup script
+ 22.13
+
+IMPROVED: bootstrapper logging
');
-- --------------------------------------------------------------
-select gm.log_script_insertion('v22-release_notes-fixup.sql', '22.12');
+select gm.log_script_insertion('v22-release_notes-fixup.sql', '22.13 at 1.8.3');
View it on GitLab: https://salsa.debian.org/med-team/gnumed-server/-/compare/f3240512fe25d261fd5f7de852611d6ba749f5b3...3700d97352a66ac4ba41a3450fadc5e9935a6c05
--
View it on GitLab: https://salsa.debian.org/med-team/gnumed-server/-/compare/f3240512fe25d261fd5f7de852611d6ba749f5b3...3700d97352a66ac4ba41a3450fadc5e9935a6c05
You're receiving this email because of your account on salsa.debian.org.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/debian-med-commit/attachments/20200724/2beeb7ee/attachment-0001.html>
More information about the debian-med-commit
mailing list