how to catch specific pyodbc error message - python

I trid the following code,
import pyodbc
try:
pyodbc.connect('DRIVER={%s};SERVER=%s;DATABASE=%s;UID=%s;PWD=%s' % (driver, server, database, uid, password))
except pyodbc.Error, err:
logging.warn(err)
The error message format i get is
('HY000', "[HY000] [MySQL][ODBC 5.1 Driver]Access denied for user 'root'#'192.168.2.27' (using password: YES) (1045) (SQLDriverConnect)")
I want to receive just the message part of the error i.e.
Access denied for user 'root'#'192.168.2.27'(using password: YES)
I dont know if I can catch errors specifically like, driver not found, host down etc..
I also tried catching errors as:
except pyodbc.OperationalError, err:
logging.warn(err)
except pyodbc.DataError, err:
logging.warn(err)
except pyodbc.IntegrityError, err:
logging.warn(err)
except pyodbc.ProgrammingError, err:
logging.warn(err)
except pyodbc.NotSupportedError, err:
logging.warn(err)
except pyodbc.DatabaseError, err:
logging.warn(err)
except pyodbc.Error, err:
logging.warn(err)
but the last one always catches the error.
Fruthermore i saw the pyodbc.Error.message is always empty.
How can i get just the message in the error.
Thanks

This worked for me.
try:
cnxn = pyodbc.connect(...)
except pyodbc.Error as ex:
sqlstate = ex.args[0]
if sqlstate == '28000':
print("LDAP Connection failed: check password")
There are different SQLSTATES and you can have if-else statements to print out the cause.
Similarly,
try:
cnxn = pyodbc.connect(...)
except pyodbc.Error as ex:
sqlstate = ex.args[1]
print(sqlstate)
will give you the second part of the error with description.
For exampleex.args[0] give you 28000 and ex.args[1] gives [28000] LDAP authentication failed for user 'user' (24) (SQLDriverConnect)
You can then use String manipulation techniques there to just print out what you want. Hope this helps.

pyodbc seems to just wrap the errors/exceptions from the underlying ODBC implementation, so it's unlikely that you will be able to do this.

It's been very long since op asked this question, but here goes a snippet of code to parse out pyodbc error messages into nice Python exceptions that can be used. This is also meant to be extended, I didn't handle every possible sqlserver error code.
import re
from enum import Enum, IntEnum, unique
class PyODBCError(Exception):
"""
Handle errors for PyODBC. Offers a error message parser
to apply specific logic depending on the error raise
ODBC error identifier: 23000
pyodbc_error_message (str) -- message raised by PyODBC
Example:
[23000] [Microsoft][ODBC Driver 17 for SQL Server][SQL Server] \
Cannot insert explicit value for identity column in table \
'building' when IDENTITY_INSERT is set to OFF.
(544) (SQLExecDirectW) \
"""
error_pattern = re.compile(
r"\[(?P<error_id>.*?)\] \[(?P<operator>.*?)\]\[(?P<driver>.*?)\]\[(?P<database_type>.*?)\](?P<error_message>.+?(?= \()) \((?P<sql_server_error_id>\d*?)\) \(SQLExecDirectW\)"
)
sql_error_code_pattern = re.compile(r"\((?P<sql_server_error_code>\d*?)\) \(SQLExecDirectW\)")
column_pattern = re.compile(r"column \'(?P<column_name>.+?)\'")
table_pattern = re.compile(r"table \'(?P<table_name>.+?)\'")
pyodbc_error_code = 'HY000'
def __init__(self, pyodbc_error_message: str) -> None:
self._parse_error_message(pyodbc_error_message)
def __str__(self) -> str:
return self.error_message
def _parse_error_message(self, pyodbc_error_message: str) -> None:
m = re.match(self.error_pattern, pyodbc_error_message)
self.operator = m.group('operator')
self.error_id = m.group('error_id')
self.driver = m.group('driver')
self.database_type = m.group('database_type')
self.error_message = m.group('error_message')
self.sql_server_error_id = m.group('sql_server_error_id')
#classmethod
def get_message(cls, pyodbc_exception: Exception) -> str:
if pyodbc_exception.args[1] == cls.pyodbc_error_code:
return pyodbc_exception.args[0]
else:
return pyodbc_exception.args[1]
#classmethod
def get_pyodbc_code(cls, pyodbc_exception: Exception) -> str:
if pyodbc_exception.args[1] == cls.pyodbc_error_code:
return pyodbc_exception.args[1]
else:
return pyodbc_exception.args[0]
#staticmethod
def get_exception(error_code: int):
return {
515: IdentityInsertNull,
544: IdentityInsertSetToOff,
2627: PrimaryKeyViolation,
8114: FailedTypeConversion,
102: IncorrectSyntax,
32: InvalidNumberParametersSupplied
}.get(error_code, DefaultException)
#classmethod
def get_sql_server_error_code(cls, pyodbc_code: str, message: str) -> int:
"""
Parses error message raised by PyODBC and return SQL Server Error Code
Looks for the following pattern:
(544) (SQLExecDirectW) -> 544
Args:
pyodbc_error_message (str): Error string raised by PyODBC
Returns:
(int) - SQL Server Error Code
"""
if pyodbc_code == cls.pyodbc_error_code:
return 32
else:
m = re.search(cls.sql_error_code_pattern, message)
if m:
return int(m.group('sql_server_error_code'))
else:
raise ValueError(f"Error raised is not from SQL Server: {message}")
#classmethod
def build_pyodbc_exception(cls, pyodbc_exception: Exception):
pyodbc_code = cls.get_pyodbc_code(pyodbc_exception)
error_message = cls.get_message(pyodbc_exception)
error_code = cls.get_sql_server_error_code(pyodbc_code, error_message)
exception = cls.get_exception(error_code)
raise exception(error_message)
class IdentityInsertNull(PyODBCError):
"""
Handle specific PyODBC error related to Null Value Inserted on Identity Column
"""
def __init__(self, pyodbc_error_message):
super().__init__(pyodbc_error_message)
m = re.search(self.table_pattern, self.error_message)
self.table_name = m.group('table_name')
m = re.search(self.column_pattern, self.error_message)
self.column_name = m.group('column_name')
class IdentityInsertSetToOff(PyODBCError):
"""
Handle specific PyODBC error related to Identity Not Set to On/Off
"""
def __init__(self, pyodbc_error_message):
super().__init__(pyodbc_error_message)
m = re.search(self.table_pattern, self.error_message)
self.table_name = m.group('table_name')
class FailedTypeConversion(PyODBCError):
"""
Handle specific PyODBC error related to data type conversion
"""
def __init__(self, pyodbc_error_message):
super().__init__(pyodbc_error_message)
class PrimaryKeyViolation(PyODBCError):
"""
Handle specific PyODBC error related to Primary Key Violation
"""
def __init__(self, pyodbc_error_message):
super().__init__(pyodbc_error_message)
class IncorrectSyntax(PyODBCError):
"""
Handle specific PyODBC error related to incorrect syntax in query
"""
def __init__(self, pyodbc_error_message):
super().__init__(pyodbc_error_message)
class DefaultException(PyODBCError):
"""
Handle default PyODBC errors
"""
def __init__(self, pyodbc_error_message):
super().__init__(pyodbc_error_message)
def __str__(self) -> str:
return f"{self.sql_server_error_id} - {self.error_message}"
class InvalidNumberParametersSupplied(Exception):
def __init__(self, error_message) -> None:
self.message = error_message
def __str__(self) -> str:
return self.message

In pyodbc 3.0.7, it works fine to catch pyodbc.ProgrammingError (and presumably the other error types, although I haven't tried). The contents of the error are still sort of cryptic, though, so it may be hard to do finer-grained handling of errors.

this will give you more clear and readable error message when connecting to mssql using myodbc:
try:
cnxn = pyodbc.connect(...)
except pyodbc.Error as ex:
sqlstate = ex.args[1]
sqlstate = sqlstate.split(".")
print(sqlstate[-3])

Be carreful it looks like an pyodbc exceptions but it may not be:
In my case I read pyodbc.IntegrityError in "(pyodbc.IntegrityError) ('23000', '[23000]..."
In the python console I can read this: "sqlalchemy.exc.IntegrityError: (pyodbc.IntegrityError) ('23000', '[23000]..."
It is actually a sqlalchemy.exc.IntegrityError exception, which wraps an pyodbc.IntegrityError

Related

How to fix dictionary, get is not a known member of None in Pylance language server prompt message

Love to use pylance, but unable to fix this issue in dictionary get, pylance prompt message as shown in the image
def validate_amount():
try:
json_data = request.json
def isfloat(num: str) -> bool:
try:
float(num)
return True
except ValueError:
return False
amount_val = json_data.get('amount','0.0')
amount_in_words = json_data.get('amount2words','')
if isfloat(amount_val):
split_amt = amount_val.split('.')
response = split_amt
except Exception as e:
response = f'An Error Occurred - {e}'
Below is the image for reference

Python\Flask\SQLAlchemy\Marshmallow - How to process a request with duplicate values without failing the request?

This is only my second task (bug I need to fix) in a Python\Flask\SQLAlchemy\Marshmallow system I need to work on. So please try to be easy with me :)
In short: I'd like to approve an apparently invalid request.
In details:
I need to handle a case in which a user might send a request with some json in which he included by mistake a duplicate value in a list.
For example:
{
"ciphers": [
"TLS_AES_256_GCM_SHA384",
"AES256-SHA256"
],
"is_default": true,
"tls_versions": [
"tls10",
"tls10",
"tls11",
]
}
What I need to do is to eliminate one of the duplicated tls1.0 values, but consider the request as valid, update the db with the correct and distinct tls versions, and in the response return the non duplicated json in body.
Current code segments are as follows:
tls Controller:
...
#client_side_tls_bp.route('/<string:tls_profile_id>', methods=['PUT'])
def update_tls_profile_by_id(tls_profile_id):
return update_entity_by_id(TlsProfileOperator, entity_name, tls_profile_id)
...
general entity controller:
...
def update_entity_by_id(operator, entity_name, entity_id):
"""flask route for updating a resource"""
try:
entity_body = request.get_json()
except Exception:
return make_custom_response("Bad Request", HTTPStatus.BAD_REQUEST)
entity_obj = operator.get(g.tenant, entity_id, g.correlation)
if not entity_obj:
response = make_custom_response(http_not_found_message(entity_name, entity_id), HTTPStatus.NOT_FOUND)
else:
updated = operator.update(g.tenant, entity_id, entity_body, g.correlation)
if updated == "accepted":
response = make_custom_response("Accepted", HTTPStatus.ACCEPTED)
else:
response = make_custom_response(updated, HTTPStatus.OK)
return response
...
tls operator:
...
#staticmethod
def get(tenant, name, correlation_id=None):
try:
tls_profile = TlsProfile.get_by_name(tenant, name)
return schema.dump(tls_profile)
except NoResultFound:
return None
except Exception:
apm_logger.error(f"Failed to get {name} TLS profile", tenant=tenant,
consumer=LogConsumer.customer, correlation=correlation_id)
raise
#staticmethod
def update(tenant, name, json_data, correlation_id=None):
schema.load(json_data)
try:
dependant_vs_names = VirtualServiceOperator.get_dependant_vs_names_locked_by_client_side_tls(tenant, name)
# locks virtual services and tls profile table simultaneously
to_update = TlsProfile.get_by_name(tenant, name)
to_update.update(json_data, commit=False)
db.session.flush() # TODO - need to change when 2 phase commit will be implemented
snapshots = VirtualServiceOperator.get_snapshots_dict(tenant, dependant_vs_names)
# update QWE
# TODO handle QWE update atomically!
for snapshot in snapshots:
QWEController.update_abc_services(tenant, correlation_id, snapshot)
db.session.commit()
apm_logger.info(f"Update successfully {len(dependant_vs_names)} virtual services", tenant=tenant,
correlation=correlation_id)
return schema.dump(to_update)
except Exception:
db.session.rollback()
apm_logger.error(f"Failed to update {name} TLS profile", tenant=tenant,
consumer=LogConsumer.customer, correlation=correlation_id)
raise
...
and in the api schema class:
...
#validates('_tls_versions')
def validate_client_side_tls_versions(self, value):
if len(noDuplicatatesList) < 1:
raise ValidationError("At least a single TLS version must be provided")
for tls_version in noDuplicatatesList:
if tls_version not in TlsProfile.allowed_tls_version_values:
raise ValidationError("Not a valid TLS version")
...
I would have prefer to solve the problem in the schema level, so it won't accept the duplication.
So, as easy as it is to remove the duplication from the "value" parameter value, how can I propagate the non duplicates list back in order to use it to update the db and the response?
Thanks.
I didn't test but I think mutating value in the validation function would work.
However, this is not really guaranteed by marshmallow's API.
The proper way to do it would be to add a post_load method to de-duplicate.
#post_load
def deduplicate_tls(self, data, **kwargs):
if "tls_versions" in data:
data["tls_version"] = list(set(data["tls_version"]))
return data
This won't maintain the order, so if the order matters, or for issues related to deduplication itself, see https://stackoverflow.com/a/7961390/4653485.

Keep getting this error not sure why "Can't convert 'set' object to str implicitly"

#admin.register(Book)
class BookAdmin(ImportExportActionModelAdmin):
resource_class = BookResource
def get_import_form(self):
return CustomImportForm
def get_resource_kwargs(self, request, *args, **kwargs):
rk = super().get_resource_kwargs(request, *args, **kwargs)
rk['input_author'] = None
if request.POST:
author = request.POST.get('input_author', None)
if author:
request.session['input_author'] = author
else:
try:
author = request.session['input_author']
except KeyError as e:
raise Exception("Context failure on row import" + {e})
rk['input_author'] = author
return rk
Have this code in django admin page, but getting an error during the export. Can anyone let me know where is the issue?
Your issue is on this line:
raise Exception("Context failure on row import" + {e})
The ‘{e}’ means that you create a set containing the error, and try to join it to the exception message string. You should be able to get rid of that error by replacing ‘{e}’ with just ‘e’.

Trapping a custom error in python

I'm trying to trap the following error in a try/exception block, but as this is a custom module that is generating the error - not generating a standard error such as ValueError for example. What is the correct way to catch such errors?
Here is my code:
try:
obj = IPWhois(ip_address)
except Exception(IPDefinedError):
results = {}
else:
results = obj.lookup()
The most obvious way:
except IPDefinedError:
gives:
NameError: name 'IPDefinedError' is not defined
The error returned that I want to check for is:
ipwhois.exceptions.IPDefinedError
ipwhois.exceptions.IPDefinedError: IPv4 address '127.0.0.1' is already defined as 'Private-Use Networks' via 'RFC 1918'.
The issue here is the import!
I had the import as
from ipwhois import IPWhois
but I also needed
import ipwhois
So the following works:
try:
obj = IPWhois(ip_address)
except ipwhois.exceptions.IPDefinedError:
results = {}
else:
results = obj.lookup()
Here is a quick recap. Yes, the error in your question did look like it was likely related to an import issue (as per my comment ;) ).
from pprint import pprint as pp
class IPDefinedError(Exception):
"""Mock IPDefinedError implementation
"""
pass
class IPWhois(object):
"""Mock IPWhois implementation
"""
def __init__(self, ip_address):
if ip_address == "127.0.0.1":
raise IPDefinedError(
"IPv4 address '127.0.0.1' is already defined as 'Private-Use Networks' via 'RFC 1918'.")
self._ip_address = ip_address
def lookup(self):
return "RESULT"
def lookup(ip_address):
""" calculates IPWhois lookup result or None if unsuccessful
:param ip_address:
:return: IPWhois lookup result or None if unsuccessful
"""
result = None
try:
obj = IPWhois(ip_address)
result = obj.lookup()
except IPDefinedError as e:
msg = str(e)
print("Error received: {}".format(msg)) # do something with msg
return result
if __name__ == '__main__':
results = map(lookup, ["192.168.1.1", "127.0.0.1"])
pp(list(results)) # ['RESULT', None]

how to use mysqldb for local mysql connection

the error is
File "c:\python27\lib\site-packages\MySQLdb\connections.py", line 72, in Connection
db = get_db_connection(db_name)
NameError: name 'get_db_connection' is not defined
so I think the error is at * which is because the def init(self, *args, **kwargs): ends with the colon but I don't know the syntax of the connection.
this is my class Connection code in connection.py
class Connection(_mysql.connection):
"""MySQL Database Connection Object"""
default_cursor = cursors.Cursor
def __init__(self, *args, **kwargs): # *** this is error I think ie the : but what is syntax
def get_db_connection(database_name):
db = MySQLdb.connect('localhost', 'user', 'pswrd', database_name)
cur = db.cursor()
return db, cur
db_name = 'test' # database name
user_name = 'root' # name of a user
db = get_db_connection(db_name)
"""
Create a connection to the database. It is strongly recommended
that you only use keyword parameters. Consult the MySQL C API
documentation for more information.
host
string, host to connect
user
string, user to connect as
passwd
string, password to use
db
string, database to use
port
integer, TCP/IP port to connect to
unix_socket
string, location of unix_socket to use
conv
conversion dictionary, see MySQLdb.converters
connect_timeout
number of seconds to wait before the connection attempt
fails.
compress
if set, compression is enabled
named_pipe
if set, a named pipe is used to connect (Windows only)
init_command
command which is run once the connection is created
read_default_file
file from which default client values are read
read_default_group
configuration group to use from the default file
cursorclass
class object, used to create cursors (keyword only)
use_unicode
If True, text-like columns are returned as unicode objects
using the connection's character set. Otherwise, text-like
columns are returned as strings. columns are returned as
normal strings. Unicode objects will always be encoded to
the connection's character set regardless of this setting.
charset
If supplied, the connection character set will be changed
to this character set (MySQL-4.1 and newer). This implies
use_unicode=True.
sql_mode
If supplied, the session SQL mode will be changed to this
setting (MySQL-4.1 and newer). For more details and legal
values, see the MySQL documentation.
client_flag
integer, flags to use or 0
(see MySQL docs or constants/CLIENTS.py)
ssl
dictionary or mapping, contains SSL connection parameters;
see the MySQL documentation for more details
(mysql_ssl_set()). If this is set, and the client does not
support SSL, NotSupportedError will be raised.
local_infile
integer, non-zero enables LOAD LOCAL INFILE; zero disables
There are a number of undocumented, non-standard methods. See the
documentation for the MySQL C API for some hints on what they do.
"""
from MySQLdb.constants import CLIENT, FIELD_TYPE
from MySQLdb.converters import conversions
from weakref import proxy, WeakValueDictionary
import types
kwargs2 = kwargs.copy()
if 'conv' in kwargs:
conv = kwargs['conv']
else:
conv = conversions
conv2 = {}
for k, v in conv.items():
if isinstance(k, int) and isinstance(v, list):
conv2[k] = v[:]
else:
conv2[k] = v
kwargs2['conv'] = conv2
cursorclass = kwargs2.pop('cursorclass', self.default_cursor)
charset = kwargs2.pop('charset', '')
if charset:
use_unicode = True
else:
use_unicode = False
use_unicode = kwargs2.pop('use_unicode', use_unicode)
sql_mode = kwargs2.pop('sql_mode', '')
client_flag = kwargs.get('client_flag', 0)
client_version = tuple([ numeric_part(n) for n in _mysql.get_client_info().split('.')[:2] ])
if client_version >= (4, 1):
client_flag |= CLIENT.MULTI_STATEMENTS
if client_version >= (5, 0):
client_flag |= CLIENT.MULTI_RESULTS
kwargs2['client_flag'] = client_flag
super(Connection, self).__init__(*args, **kwargs2) #****
self.cursorclass = cursorclass
self.encoders = dict([ (k, v) for k, v in conv.items()
if type(k) is not int ])
self._server_version = tuple([ numeric_part(n) for n in self.get_server_info().split('.')[:2] ])
db = proxy(self)
def _get_string_literal():
def string_literal(obj, dummy=None):
return db.string_literal(obj)
return string_literal
def _get_unicode_literal():
def unicode_literal(u, dummy=None):
return db.literal(u.encode(unicode_literal.charset))
return unicode_literal
def _get_string_decoder():
def string_decoder(s):
return s.decode(string_decoder.charset)
return string_decoder
string_literal = _get_string_literal()
self.unicode_literal = unicode_literal = _get_unicode_literal()
self.string_decoder = string_decoder = _get_string_decoder()
if not charset:
charset = self.character_set_name()
self.set_character_set(charset)
if sql_mode:
self.set_sql_mode(sql_mode)
if use_unicode:
self.converter[FIELD_TYPE.STRING].append((None, string_decoder))
self.converter[FIELD_TYPE.VAR_STRING].append((None, string_decoder))
self.converter[FIELD_TYPE.VARCHAR].append((None, string_decoder))
self.converter[FIELD_TYPE.BLOB].append((None, string_decoder))
self.encoders[types.StringType] = string_literal
self.encoders[types.UnicodeType] = unicode_literal
self._transactional = self.server_capabilities & CLIENT.TRANSACTIONS
if self._transactional:
# PEP-249 requires autocommit to be initially off
self.autocommit(False)
self.messages = []
def cursor(self, cursorclass=None):
"""
Create a cursor on which queries may be performed. The
optional cursorclass parameter is used to create the
Cursor. By default, self.cursorclass=cursors.Cursor is
used.
"""
return (cursorclass or self.cursorclass)(self)
def __enter__(self): return self.cursor()
def __exit__(self, exc, value, tb):
if exc:
self.rollback()
else:
self.commit()
def literal(self, o):
"""
If o is a single object, returns an SQL literal as a string.
If o is a non-string sequence, the items of the sequence are
converted and returned as a sequence.
Non-standard. For internal use; do not use this in your
applications.
"""
return self.escape(o, self.encoders)
def begin(self):
"""Explicitly begin a connection. Non-standard.
DEPRECATED: Will be removed in 1.3.
Use an SQL BEGIN statement instead."""
from warnings import warn
warn("begin() is non-standard and will be removed in 1.3",
DeprecationWarning, 2)
self.query("BEGIN")
if not hasattr(_mysql.connection, 'warning_count'):
def warning_count(self):
"""Return the number of warnings generated from the
last query. This is derived from the info() method."""
from string import atoi
info = self.info()
if info:
return atoi(info.split()[-1])
else:
return 0
def set_character_set(self, charset):
"""Set the connection character set to charset. The character
set can only be changed in MySQL-4.1 and newer. If you try
to change the character set from the current value in an
older version, NotSupportedError will be raised."""
if charset == "utf8mb4":
py_charset = "utf8"
else:
py_charset = charset
if self.character_set_name() != charset:
try:
super(Connection, self).set_character_set(charset)
except AttributeError:
if self._server_version < (4, 1):
raise NotSupportedError("server is too old to set charset")
self.query('SET NAMES %s' % charset)
self.store_result()
self.string_decoder.charset = py_charset
self.unicode_literal.charset = py_charset
def set_sql_mode(self, sql_mode):
"""Set the connection sql_mode. See MySQL documentation for
legal values."""
if self._server_version < (4, 1):
raise NotSupportedError("server is too old to set sql_mode")
self.query("SET SESSION sql_mode='%s'" % sql_mode)
self.store_result()
def show_warnings(self):
"""Return detailed information about warnings as a
sequence of tuples of (Level, Code, Message). This
is only supported in MySQL-4.1 and up. If your server
is an earlier version, an empty sequence is returned."""
if self._server_version < (4,1): return ()
self.query("SHOW WARNINGS")
r = self.store_result()
warnings = r.fetch_row(0)
return warnings
Warning = Warning
Error = Error
InterfaceError = InterfaceError
DatabaseError = DatabaseError
DataError = DataError
OperationalError = OperationalError
IntegrityError = IntegrityError
InternalError = InternalError
ProgrammingError = ProgrammingError
NotSupportedError = NotSupportedError
errorhandler = defaulterrorhandler
and this is the line 187 in connection.py that calls the function
super(Connection, self).__init__(*args, **kwargs2) #****
From the Connection.__init__ docstring - It is strongly recommended that you only use keyword parameters.
Try using something like:
MySQLdb.connect(
host='localhost',
user='user',
passwd='pswrd',
db=database_name
)
Also, post the error you get. It will probably be helpful.

Categories