-# Copyright (C) 2013, 2014, 2015, 2018, 2020, 2021 The Meme Factory, Inc.
+# Copyright (C) 2013, 2014, 2015, 2018, 2020, 2021, 2024 The Meme Factory, Inc.
# http://www.karlpinc.com/
# This file is part of PGWUI_Core.
PasswordField,
FileField)
-import psycopg2
-import psycopg2.extensions
+import psycopg
+import psycopg.errors
from pgwui_core.constants import (
CHECKED,
def format_exception(ex):
'''Return an exception formatted as suffix text for a log message.'''
- if isinstance(ex, psycopg2.DatabaseError):
+ if isinstance(ex, psycopg.DatabaseError):
diag = ex.diag
msg = diag.message_primary
if hasattr(diag, 'message_detail'):
An SQL command that returns nothing
Attributes:
- stmt The statement, formatted for psycopg2 substitution
+ stmt The statement, formatted for psycopg3 substitution
args Tuple of arguments used to substitute when executed.
'''
def __init__(self, stmt, args, ec=None):
'''
- stmt The statement, formatted for psycopg2 substitution
+ stmt The statement, formatted for psycopg3 substitution
args Tuple of arguments used to substitute when executed.
ec(ex) Produces the exception to raise an instance of on failure
Input:
- ex The exception raised by psycopg2
+ ex The exception raised by psycopg3
'''
super(SQLCommand, self).__init__()
self.stmt = stmt
Execute the sql statement.
Input:
- cur A psycopg2 cursor
+ cur A psycopg3 cursor
Side effects:
Does something in the db.
- Can raise a psycopg2 error
+ Can raise a psycopg3 error
'''
try:
cur.execute(self.stmt, self.args)
- except psycopg2.DatabaseError as ex:
+ except psycopg.DatabaseError as ex:
if self.ec is None:
raise ex
else:
def __init__(self, stmt, args, ec=None,
log_success=None, log_failure=None):
'''
- stmt The statement, formatted for psycopg2 substitution
+ stmt The statement, formatted for psycopg3 substitution
args Tuple of arguments used to substitute when executed.
ec(ex) Produces the exception to raise an instance of on failure
Input:
- ex The exception raised by psycopg2
+ ex The exception raised by psycopg3
'''
super(LogSQLCommand, self).__init__(stmt, args, ec)
self.log_success = log_success
Execute the sql statement.
Input:
- cur A psycopg2 cursor
+ cur A psycopg3 cursor
Side effects:
Does something in the db.
- Can raise a psycopg2 error
+ Can raise a psycopg3 error
'''
try:
super(LogSQLCommand, self).execute(cur)
- except (core_ex.UploadError, psycopg2.DatabaseError) as ex:
+ except (core_ex.UploadError, psycopg.DatabaseError) as ex:
if self.log_failure:
self.log_failure(ex)
raise
def mapper(st):
st = do_trim(st)
- # psycopg2 maps None to NULL
+ # psycopg3 maps None to NULL
return None if st == null_rep else st
self._mapper = mapper
else:
@attr.s
class ParameterExecutor():
- '''Execute a parameterized pscopg2 statement
+ '''Execute a parameterized psycopg3 statement
Must be mixed in with a DataLineProcessor.
'''
def param_execute(self, insert_stmt, udl):
udl.lineno,
'Line has too few columns',
'Fewer columns than column headings',
- f'The IndexError from psycopg2 is: ({exp})',
+ f'The IndexError from psycopg3 is: ({exp})',
data=udl.raw)
except UnicodeEncodeError as exp:
- self.raise_encoding_error(exp, udl)
+ self.raise_encoding_error(
+ exp, udl, self.cur.connection.encoding, False)
+ except psycopg.errors.UntranslateableCharacter as exp:
+ self.raise_encoding_error(
+ exp, udl, self.ue.server_encoding(), True)
- def raise_encoding_error(self, exp, udl):
+ def raise_encoding_error(self, exp, udl, encoding, server_side):
errors = []
cnt = 1
- enc = psycopg2.extensions.encodings[self.cur.connection.encoding]
+ if server_side:
+ description = ("Data cannot be represented in the"
+ " character encoding of the database")
+ else:
+ description = ("Data cannot be represented in the database"
+ " connection's client-side character encoding")
for col in udl.tuples:
try:
- col.encode(encoding=enc)
- except UnicodeEncodeError as detailed_exp:
+ col.encode(encoding=encoding)
+ except UnicodeEncodeError as col_exp:
+ if server_side:
+ reported_error = str(exp)
+ else:
+ reported_error = str(col_exp)
errors.append(core_ex.EncodingError(
udl.lineno,
- ("Data cannot be represented in the database's character"
- " encoding"),
+ description,
(f'The data ({col}) in column'
- f' {cnt} contains an un-representable bit sequence;'
+ f' {cnt} contains the bit sequence'
+ f' ({col[col_exp.start:col_exp.end]}), in the bits'
+ f' numbered {col_exp.start + 1} through {col_exp.end},'
+ ' that are not able to'
+ f' be represented in the (Python) {encoding} character'
+ ' encoding;'
' the reported error is:'),
- str(detailed_exp),
+ reported_error,
data=udl.raw))
cnt += 1
if errors:
raise core_ex.MultiDataLineError(errors)
raise core_ex.EncodingError(
udl.lineno,
- ("Data cannot be represented in the database's character"
- " encoding"),
- ('Cannot discover which column contains an un-representable'
- ' bit sequence, the reported error is:'),
+ description,
+ ('Cannot discover which column contains a'
+ ' bit sequence that cannot be represented in the (Python)'
+ f' {encoding} character encoding; the reported error is:'),
str(exp),
data=udl.raw)
Attributes:
ue UploadEngine instance
uh UploadHandler instance
- cur psycopg2 cursor
+ cur psycopg3 cursor
Methods:
eat(udl) Given an UploadDataLine instance put the line in the db.
'''
ue UploadEngine instance
uh UploadHandler instance
- cur psycopg2 cursor
+ cur psycopg3 cursor
'''
super(NoOpProcessor, self).__init__(ue, uh)
'''
ue UploadEngine instance
uh UploadHandler instance
- cur psycopg2 cursor
+ cur psycopg3 cursor
'''
super(ExecuteSQL, self).__init__(ue, uh)
Attributes:
uh An UploadHandler instance.
- cur A psycopg2 cursor instance
+ cur A psycopg3 cursor instance
db Name of db to connect to
user User to connect to db
password Password to connect to db
Methods:
run() Get a DataLineProcessor instance from the upload handler's
factory and feed it by iterating over data.
+ server_encoding()
+ Return the python standard encoding of the server_encoding
+ used in the database.
'''
def __init__(self, uh):
# Configuration and response management.
self.uh = uh
+ self._server_encoding = None
def call_alter_db(self, conn):
'''
errors.extend(ex.errors)
except core_ex.PGWUIError as ex:
errors.append(ex)
- except psycopg2.DatabaseError as ex:
+ except psycopg.DatabaseError as ex:
errors.append(core_ex.DBSetupError(ex))
else:
try:
self.cur.close()
return errors
+ def _get_client_encoding(self, conn):
+ '''Return the client-side encoding as a Python standard encoding
+
+ Input: conn A psycopg connection
+ '''
+ encoding = conn.info.encoding
+ conn.close()
+ return encoding
+
def call_with_connection(self, func):
'''
Validate input, connect to the db, and do something with
Side effects:
Raises errors, calls func(conn)
'''
+ return self._call_with_encoded_connection(func, None)
+
+ def _call_with_encoded_connection(self, func, client_encoding):
+
+ '''Validate input, connect to the db with a specific client
+ encoding, and do something with the connection. See
+ call_with_connection().
+ '''
errors = []
havecreds = False
response = {}
if not errors:
registry = self.uh.request.registry
try:
- conn = psycopg2.connect(
- database=self.db,
+ conn = psycopg.connect(
+ dbname=self.db,
user=self.user,
password=self.password,
host=registry.settings['pgwui'].get('pg_host'),
- port=registry.settings['pgwui'].get('pg_port'))
- except psycopg2.OperationalError:
+ except psycopg.OperationalError:
errors = [self.authfailerror_factory()]
havecreds = False
else:
self.uh.session.update({'havecreds': havecreds})
return (errors, response)
+ def server_encoding(self):
+ '''Return the server-side encoding, as a Python standard encoding.
+ '''
+ # This does the lame and easy thing and gets the encoding
+ # from a new connection; by supplying '' as the client
+ # encoding, the server sets the client encoding to the server encoding.
+ if self._server_encoding is None:
+ encoding = self._call_with_encoded_connection(
+ self._get_client_encoding, '')
+ self._server_encoding = encoding
+ return self._server_encoding
+
def read_uh(self):
'''Read data into the upload handler.'''
self.uh.read()
Attributes:
uh An UploadHandler instance.
data An UploadData instance of the uploaded data
- cur A psycopg2 cursor instance
+ cur A psycopg3 cursor instance
db Name of db to connect to
user User to connect to db
password Password to connect to db
Methods:
run() Get a DataLineProcessor instance from the upload handler's
factory and feed it by iterating over data.
+ server_encoding()
+ Return the python standard encoding of the server_encoding
+ used in the database.
'''
def __init__(self, uh):
'''
else:
try:
processor.eat(udl)
- except psycopg2.DatabaseError as ex:
+ except psycopg.DatabaseError as ex:
errors.append(core_ex.DBDataLineError(udl, ex))
except (core_ex.DataLineError, core_ex.DBError) as ex:
errors.append(ex)
Attributes:
uh An UploadHandler instance.
data An UploadData instance of the uploaded data
- cur A psycopg2 cursor instance
+ cur A psycopg3 cursor instance
db Name of db to connect to
user User to connect to db
password Password to connect to db
Methods:
run() Get a DataLineProcessor instance from the upload handler's
factory and feed it by iterating over data.
+ server_encoding()
+ Return the python standard encoding of the server_encoding
+ used in the database.
eat_old_line(udl, thunk)
Trap errors raised by the db while running thunk.
Report any errors as due to the udl UploadDataLine
else:
try:
conn.commit()
- except psycopg2.DatabaseError as ex:
+ except psycopg.DatabaseError as ex:
errors.append(core_ex.DBCommitError(ex))
conn.close()
return errors
'''
try:
result = thunk()
- except psycopg2.DatabaseError as ex:
+ except psycopg.DatabaseError as ex:
raise core_ex.DBDataLineError(udl, ex)
else:
return result
'SAVEPOINT line_savepoint;')
try:
processor.eat(udl)
- except psycopg2.DatabaseError as ex:
+ except psycopg.DatabaseError as ex:
self.cur.execute(
'ROLLBACK TO line_savepoint;')
errors.append(core_ex.DBDataLineError(udl, ex))
Attributes:
uh An UploadHandler instance.
- cur A psycopg2 cursor instance
+ cur A psycopg3 cursor instance
db Name of db to connect to
user User to connect to db
password Password to connect to db
Methods:
run() Get a DataLineProcessor instance from the upload handler's
factory and feed it by iterating over data.
+ server_encoding()
+ Return the python standard encoding of the server_encoding
+ used in the database.
'''
def __init__(self, uh):