from __future__ import division
from csv import reader as csv_reader
-from cgi import escape as cgi_escape
import collections.abc
import ast
import markupsafe
import hashlib
+
import io
+from . import exceptions as core_ex
+
# We are not really using wtforms. We use it to (barely)
# interact with the html and post request but really
# we define our own classes to handle working memory
msg += ', detail={0}'.format(escape_eol(diag.message_detail))
if hasattr(diag, 'message_hint'):
msg += ', hint={0}'.format(escape_eol(diag.message_hint))
- elif isinstance(ex, UploadError):
+ elif isinstance(ex, core_ex.UploadError):
msg = ex.e
if ex.descr != '':
msg += ' {0}'.format(escape_eol(ex.descr))
return msg
-# Error handling
-
-class UploadError(Exception):
- '''
- Module exceptions are derived from this class.
-
- lineno Line number to which error pertains, if any
- e The error message
- descr More description of the error
- detail Extra HTML describing the error
- data Line of data causing problem, if any
-
- UploadError
- * Error
- * NoHeadersError
- * NoDataError
- * DBError
- * DBCommitError
- * DBDataLineError
- * DataLineError
- * TooManyColsError
- '''
- def __init__(self, e, lineno='', descr='', detail='', data=''):
- super(UploadError, self).__init__()
- self.lineno = lineno
- self.e = e
- self.descr = descr
- self.detail = detail
- self.data = data
-
- def __str__(self):
- out = 'error ({0})'.format(self.e)
- if self.lineno != '':
- out = '{0}: lineno ({1})'.format(out, self.lineno)
- if self.descr != '':
- out = '{0}: descr ({1})'.format(out, self.descr)
- if self.detail != '':
- out = '{0}: detail ({1})'.format(out, self.detail)
- if self.data != '':
- out = '{0}: data ({1})'.format(out, self.data)
- return out
-
-
-class Error(UploadError):
- '''
- Module exceptions rasied while setting up to read data lines
- are derived from this class.
-
- e The error message
- descr More description of the error
- detail Extra HTML describing the error
- '''
- def __init__(self, e, descr='', detail=''):
- super(Error, self).__init__(e=e, descr=descr, detail=detail)
-
-
-class NoFileError(Error):
- '''No file uploaded'''
- def __init__(self, e, descr='', detail=''):
- super(NoFileError, self).__init__(e, descr, detail)
-
-
-class NoDBError(Error):
- '''No database name given'''
- def __init__(self, e, descr='', detail=''):
- super(NoDBError, self).__init__(e, descr, detail)
-
-
-class NoUserError(Error):
- '''No user name supplied'''
- def __init__(self, e, descr='', detail=''):
- super(NoUserError, self).__init__(e, descr, detail)
-
-
-class AuthFailError(Error):
- '''Unable to connect to the db'''
- def __init__(self, e, descr='', detail=''):
- super(AuthFailError, self).__init__(e, descr, detail)
-
-
-class DryRunError(Error):
- '''Rollback due to dry_run config option'''
- def __init__(self, e, descr='', detail=''):
- super(DryRunError, self).__init__(e, descr, detail)
-
-
-class CSRFError(Error):
- '''Invalid CSRF token'''
- def __init__(self, e, descr='', detail=''):
- super(CSRFError, self).__init__(e, descr, detail)
-
-
-class NoHeadersError(Error):
- '''No column headings found'''
- def __init__(self, e, descr='', detail=''):
- super(NoHeadersError, self).__init__(e, descr, detail)
-
-
-class NoDataError(Error):
- '''No data uploaded'''
- def __init__(self, e, descr='', detail=''):
- super(NoDataError, self).__init__(e, descr, detail)
-
-
-class DuplicateUploadError(Error):
- '''The same filename updated twice into the same db'''
- def __init__(self, e, descr='', detail=''):
- super(DuplicateUploadError, self).__init__(e, descr, detail)
-
-
-class DataInconsistencyError(Error):
- def __init__(self, e, descr='', detail=''):
- super(DataInconsistencyError, self).__init__(e, descr, detail)
-
-
-class DBError(Error):
- '''psycopg2 raised an error'''
- def __init__(self, pgexc, e='process your request'):
- '''
- pgexc The psycopg2 exception object
- e Description of what PG was doing
- '''
- super(DBError, self).__init__(
- 'PostgreSQL is unable to ' + e + ':',
- 'It reports:',
- self.html_blockquote(pgexc))
-
- def html_blockquote(self, ex):
- '''
- Produce an html formatted message from a psycopg2 DatabaseError
- exception.
- '''
- primary = cgi_escape(ex.diag.message_primary)
-
- if ex.diag.message_detail is None:
- detail = ''
- else:
- detail = '<br />DETAIL: ' + cgi_escape(ex.diag.message_detail)
-
- if ex.diag.message_hint is None:
- hint = ''
- else:
- hint = '<br />HINT: ' + cgi_escape(ex.diag.message_hint)
-
- return '<blockquote><p>{0}: {1}{2}{3}</p></blockquote>'.format(
- ex.diag.severity,
- primary,
- detail,
- hint)
-
-
-class DBCommitError(DBError):
- def __init__(self, pgexc):
- super(DBCommitError, self).__init__(pgexc)
-
-
-class DBDataLineError(DBError):
- '''Database generated an error while the processor was running.'''
-
- def __init__(self, udl, pgexc):
- '''
- udl An UploadDataLine instance
- pgexc The psycopg2 exception object
- '''
- super(DBDataLineError, self).__init__(pgexc)
- self.lineno = udl.lineno
- self.data = udl.raw
-
-
-class DataLineError(UploadError):
- '''
- Module exceptions rasied while line-by-line processing the uploaded
- data are derived from this class.
-
- lineno The line number
- e The error message
- descr More description of the error
- detail Extra HTML describing the error
- data The uploaded data
- '''
- def __init__(self, lineno, e, descr='', detail='', data=''):
- super(DataLineError, self).__init__(e, lineno, descr, detail, data)
-
-
-class TooManyColsError(DataLineError):
- def __init__(self, lineno, e, descr='', detail='', data=''):
- super(TooManyColsError, self).__init__(lineno, e, descr, detail, data)
-
-
# Upload processing
class SQLCommand(object):
'''
try:
super(LogSQLCommand, self).execute(cur)
- except (UploadError, psycopg2.DatabaseError) as ex:
+ except (core_ex.UploadError, psycopg2.DatabaseError) as ex:
if self.log_failure:
self.log_failure(ex)
raise
def __init__(self, line, stol, mapper):
if mapper(line) == '':
- raise NoHeadersError('No column headings found on first line',
- 'The first line is ({0})'.format(line))
+ raise core_ex.NoHeadersError(
+ 'No column headings found on first line',
+ 'The first line is ({0})'.format(line))
super(UploadHeaders, self).__init__(line, stol, mapper)
self.sql = ', '.join(['"' + doublequote(st) + '"'
try:
line = next(self._fileo)
except StopIteration:
- raise NoDataError('Uploaded file contains no data')
+ raise core_ex.NoDataError('Uploaded file contains no data')
else:
self.lineno += 1
# Intuit the eol sequence
If there's too many elements, raise an error.
'''
if len(seq) > self.cols:
- raise TooManyColsError(self.lineno,
- 'Line has too many columns',
- 'More columns than column headings',
- data=line)
+ raise core_ex.TooManyColsError(self.lineno,
+ 'Line has too many columns',
+ 'More columns than column headings',
+ data=line)
return seq + ['' for i in range(len(seq) + 1, self.cols)]
udl An UploadDataLine instance
'''
- raise NotImplementedError
+ raise core_ex.NotImplementedError
class NoOpProcessor(DataLineProcessor):
Return an instantiation of the upload form needed
by the upload handler.
'''
- raise NotImplementedError
+ raise core_ex.NotImplementedError
def get_data(self):
'''
Put something that will go into the db into the 'data' attribute.
'''
- raise NotImplementedError
+ raise core_ex.NotImplementedError
def val_input(self):
'''
Returns:
Dict pyramid will use to render the resulting form
Reserved keys:
- errors A list of UploadError exceptions.
+ errors A list of core_ex.UploadError exceptions.
'''
return self.uf.write(result, errors)
Returns:
Dict pyramid will use to render the resulting form
Reserved keys:
- errors A list of UploadError exceptions.
+ errors A list of core_ex.UploadError exceptions.
csrf_token Token for detecting CSRF.
'''
response = super(SessionDBHandler, self).write(result, errors)
errors = super(UploadHandler, self).val_input()
if uf['filename'] == '':
- errors.append(NoFileError('No file supplied'))
+ errors.append(core_ex.NoFileError('No file supplied'))
return errors
'''
uf = self.uf
if self.make_double_key() == uf['last_key']:
- errors.append(DuplicateUploadError(
+ errors.append(core_ex.DuplicateUploadError(
'File just uploaded to this db',
('File named ({0}) just uploaded'
.format(markupsafe.escape(uf['filename']))),
Returns:
Dict pyramid will use to render the resulting form
Reserved keys:
- errors A list of UploadError exceptions.
+ errors A list of core_ex.UploadError exceptions.
csrf_token Token for detecting CSRF.
e_cnt Number of errors.
db_changed Boolean. Whether the db was changed.
'''Finish after processing all lines.'''
lines = self.ue.data.lineno
if lines == 1:
- raise DataLineError(
+ raise core_ex.DataLineError(
1,
'File contains no data',
('No lines found after '
return {'havecreds': False}
def nodberror_factory(self):
- return NoDBError('No database name supplied')
+ return core_ex.NoDBError('No database name supplied')
def nousererror_factory(self):
- return NoUserError('No user name supplied as login credentials')
+ return core_ex.NoUserError(
+ 'No user name supplied as login credentials')
def authfailerror_factory(self):
- return AuthFailError('Unable to login',
- 'Is the database, user, and password correct?')
+ return core_ex.AuthFailError(
+ 'Unable to login',
+ 'Is the database, user, and password correct?')
def dryrunerror_factory(self):
- return DryRunError('Configured for "dry_run":'
- ' Transaction deliberately rolled back')
+ return core_ex.DryRunError('Configured for "dry_run":'
+ ' Transaction deliberately rolled back')
def upload_data(self, data, errors):
'''Put a DBData object into the db.
# (Cannot call uh until after self is fully
# initalized, including self.cur.)
processor = self.uh.factory(self)
- except Error as ex:
+ except core_ex.Error as ex:
errors.append(ex)
else:
try:
# Let upload handler finish
try:
self.uh.cleanup()
- except UploadError as ex:
+ except core_ex.UploadError as ex:
errors.append(ex)
finally:
self.cur.close()
the connection.
func(conn) Call this function with the connection.
- func(conn) must return a list of Error instances
+ func(conn) must return a list of core_ex.Error instances
Returns:
(errors, response)
- errors List of Error instances
+ errors List of core_ex.Error instances
response Dict pyramid will use to render the resulting form.
The dict returned by func(conn) plus reserved keys.
Reserved keys:
Returns:
(errors, response)
- errors List of Error instantiations
+ errors List of core_ex.Error instantiations
response Dict containing connection result info
Side effects:
a transaction.
func(conn) Call this function with the connection.
- func(conn) must return a list of Error instances
+ func(conn) must return a list of core_ex.Error instances
Returns:
- errors List of Error instances
+ errors List of core_ex.Error instances
Side effects:
Calls func(conn)
'''
for thunk in data:
try:
udl = thunk()
- except DataLineError as ex:
+ except core_ex.DataLineError as ex:
errors.append(ex)
else:
try:
processor.eat(udl)
except psycopg2.DatabaseError as ex:
- errors.append(DBDataLineError(udl, ex))
- except DataLineError as ex:
+ errors.append(core_ex.DBDataLineError(udl, ex))
+ except core_ex.DataLineError as ex:
errors.append(ex)
- except DBError as ex:
+ except core_ex.DBError as ex:
errors.append(ex)
Call a database modification function with a connection.
func(conn) Call this function with the connection.
- func(conn) must return a list of Error instances
+ func(conn) must return a list of core_ex.Error instances
Returns:
- errors List of Error instances
+ errors List of core_ex.Error instances
Side effects:
Calls func(conn)
'''
try:
conn.commit()
except psycopg2.DatabaseError as ex:
- errors.append(DBCommitError(ex))
+ errors.append(core_ex.DBCommitError(ex))
conn.close()
return errors
try:
result = thunk()
except psycopg2.DatabaseError as ex:
- raise DBDataLineError(udl, ex)
+ raise core_ex.DBDataLineError(udl, ex)
else:
return result
for thunk in data:
try:
udl = thunk()
- except DataLineError as ex:
+ except core_ex.DataLineError as ex:
errors.append(ex)
else:
self.cur.execute(
except psycopg2.DatabaseError as ex:
self.cur.execute(
'ROLLBACK TO line_savepoint;')
- errors.append(DBDataLineError(udl, ex))
- except DataLineError as ex:
+ errors.append(core_ex.DBDataLineError(udl, ex))
+ except core_ex.DataLineError as ex:
self.cur.execute(
'ROLLBACK TO line_savepoint;')
errors.append(ex)
- except DBError as ex:
+ except core_ex.DBError as ex:
self.cur.execute(
'ROLLBACK TO line_savepoint;')
errors.append(ex)
super(UploadEngine, self).__init__(uh)
def csrferror_factory(self):
- return CSRFError(
+ return core_ex.CSRFError(
'Your request failed and you are now logged out',
('This is a security measure. '
'Some possible causes are:'),
func(conn) Call this function with the connection.
f(conn) must return a (errors, dict) tuple result,
- errors list of Error instances
+ errors list of core_ex.Error instances
dict other results
Returns:
(errors, response)
- errors List of Error instances
+ errors List of core_ex.Error instances
response Dict pyramid will use to render the resulting form.
The dict returned by func(conn) plus reserved keys.
Reserved keys:
--- /dev/null
+# Copyright (C) 2013, 2014, 2015, 2018, 2020 The Meme Factory, Inc.
+# http://www.karlpinc.com/
+
+# This file is part of PGWUI_Core.
+#
+# This program is free software: you can redistribute it and/or
+# modify it under the terms of the GNU Affero General Public License
+# as published by the Free Software Foundation, either version 3 of
+# the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public
+# License along with this program. If not, see
+# <http://www.gnu.org/licenses/>.
+#
+
+# Karl O. Pinc <kop@karlpinc.com>
+
+'''Exceptions
+'''
+
+from cgi import escape as cgi_escape
+
+
+class PGWUIError(Exception):
+ pass
+
+
+class UploadError(PGWUIError):
+ '''
+ Module exceptions are derived from this class.
+
+ lineno Line number to which error pertains, if any
+ e The error message
+ descr More description of the error
+ detail Extra HTML describing the error
+ data Line of data causing problem, if any
+
+ UploadError
+ * Error
+ * NoHeadersError
+ * NoDataError
+ * DBError
+ * DBCommitError
+ * DBDataLineError
+ * DataLineError
+ * TooManyColsError
+ '''
+ def __init__(self, e, lineno='', descr='', detail='', data=''):
+ super(UploadError, self).__init__()
+ self.lineno = lineno
+ self.e = e
+ self.descr = descr
+ self.detail = detail
+ self.data = data
+
+ def __str__(self):
+ out = 'error ({0})'.format(self.e)
+ if self.lineno != '':
+ out = '{0}: lineno ({1})'.format(out, self.lineno)
+ if self.descr != '':
+ out = '{0}: descr ({1})'.format(out, self.descr)
+ if self.detail != '':
+ out = '{0}: detail ({1})'.format(out, self.detail)
+ if self.data != '':
+ out = '{0}: data ({1})'.format(out, self.data)
+ return out
+
+
+class Error(UploadError):
+ '''
+ Module exceptions rasied while setting up to read data lines
+ are derived from this class.
+
+ e The error message
+ descr More description of the error
+ detail Extra HTML describing the error
+ '''
+ def __init__(self, e, descr='', detail=''):
+ super(Error, self).__init__(e=e, descr=descr, detail=detail)
+
+
+class NoFileError(Error):
+ '''No file uploaded'''
+ def __init__(self, e, descr='', detail=''):
+ super(NoFileError, self).__init__(e, descr, detail)
+
+
+class NoDBError(Error):
+ '''No database name given'''
+ def __init__(self, e, descr='', detail=''):
+ super(NoDBError, self).__init__(e, descr, detail)
+
+
+class NoUserError(Error):
+ '''No user name supplied'''
+ def __init__(self, e, descr='', detail=''):
+ super(NoUserError, self).__init__(e, descr, detail)
+
+
+class AuthFailError(Error):
+ '''Unable to connect to the db'''
+ def __init__(self, e, descr='', detail=''):
+ super(AuthFailError, self).__init__(e, descr, detail)
+
+
+class DryRunError(Error):
+ '''Rollback due to dry_run config option'''
+ def __init__(self, e, descr='', detail=''):
+ super(DryRunError, self).__init__(e, descr, detail)
+
+
+class CSRFError(Error):
+ '''Invalid CSRF token'''
+ def __init__(self, e, descr='', detail=''):
+ super(CSRFError, self).__init__(e, descr, detail)
+
+
+class NoHeadersError(Error):
+ '''No column headings found'''
+ def __init__(self, e, descr='', detail=''):
+ super(NoHeadersError, self).__init__(e, descr, detail)
+
+
+class NoDataError(Error):
+ '''No data uploaded'''
+ def __init__(self, e, descr='', detail=''):
+ super(NoDataError, self).__init__(e, descr, detail)
+
+
+class DuplicateUploadError(Error):
+ '''The same filename updated twice into the same db'''
+ def __init__(self, e, descr='', detail=''):
+ super(DuplicateUploadError, self).__init__(e, descr, detail)
+
+
+class DataInconsistencyError(Error):
+ def __init__(self, e, descr='', detail=''):
+ super(DataInconsistencyError, self).__init__(e, descr, detail)
+
+
+class DBError(Error):
+ '''psycopg2 raised an error'''
+ def __init__(self, pgexc, e='process your request'):
+ '''
+ pgexc The psycopg2 exception object
+ e Description of what PG was doing
+ '''
+ super(DBError, self).__init__(
+ 'PostgreSQL is unable to ' + e + ':',
+ 'It reports:',
+ self.html_blockquote(pgexc))
+
+ def html_blockquote(self, ex):
+ '''
+ Produce an html formatted message from a psycopg2 DatabaseError
+ exception.
+ '''
+ primary = cgi_escape(ex.diag.message_primary)
+
+ if ex.diag.message_detail is None:
+ detail = ''
+ else:
+ detail = '<br />DETAIL: ' + cgi_escape(ex.diag.message_detail)
+
+ if ex.diag.message_hint is None:
+ hint = ''
+ else:
+ hint = '<br />HINT: ' + cgi_escape(ex.diag.message_hint)
+
+ return '<blockquote><p>{0}: {1}{2}{3}</p></blockquote>'.format(
+ ex.diag.severity,
+ primary,
+ detail,
+ hint)
+
+
+class DBCommitError(DBError):
+ def __init__(self, pgexc):
+ super(DBCommitError, self).__init__(pgexc)
+
+
+class DBDataLineError(DBError):
+ '''Database generated an error while the processor was running.'''
+
+ def __init__(self, udl, pgexc):
+ '''
+ udl An UploadDataLine instance
+ pgexc The psycopg2 exception object
+ '''
+ super(DBDataLineError, self).__init__(pgexc)
+ self.lineno = udl.lineno
+ self.data = udl.raw
+
+
+class DataLineError(UploadError):
+ '''
+ Module exceptions rasied while line-by-line processing the uploaded
+ data are derived from this class.
+
+ lineno The line number
+ e The error message
+ descr More description of the error
+ detail Extra HTML describing the error
+ data The uploaded data
+ '''
+ def __init__(self, lineno, e, descr='', detail='', data=''):
+ super(DataLineError, self).__init__(e, lineno, descr, detail, data)
+
+
+class TooManyColsError(DataLineError):
+ def __init__(self, lineno, e, descr='', detail='', data=''):
+ super(TooManyColsError, self).__init__(lineno, e, descr, detail, data)