codekingpro/portable-devtools
114k
1"""adodbapi.remote - A python DB API 2.0 (PEP 249) interface to Microsoft ADO2 3Copyright (C) 2002 Henrik Ekelund, version 2.1 by Vernon Cole4* http://sourceforge.net/projects/pywin325* http://sourceforge.net/projects/adodbapi6 7 This library is free software; you can redistribute it and/or8 modify it under the terms of the GNU Lesser General Public9 License as published by the Free Software Foundation; either10 version 2.1 of the License, or (at your option) any later version.11 12 This library is distributed in the hope that it will be useful,13 but WITHOUT ANY WARRANTY; without even the implied warranty of14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU15 Lesser General Public License for more details.16 17 You should have received a copy of the GNU Lesser General Public18 License along with this library; if not, write to the Free Software19 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA20 21 django adaptations and refactoring thanks to Adam Vandenberg22 23DB-API 2.0 specification: http://www.python.org/dev/peps/pep-0249/24 25This module source should run correctly in CPython versions 2.5 and later,26or IronPython version 2.7 and later,27or, after running through 2to3.py, CPython 3.0 or later.28"""29 30__version__ = "2.6.0.4"31version = "adodbapi.remote v" + __version__32 33import array34import datetime35import os36import sys37import time38 39# Pyro4 is required for server and remote operation --> https://pypi.python.org/pypi/Pyro4/40try:41 import Pyro442except ImportError:43 print('* * * Sorry, server operation requires Pyro4. Please "pip import" it.')44 exit(11)45 46import adodbapi47import adodbapi.apibase as api48import adodbapi.process_connect_string49from adodbapi.apibase import ProgrammingError50 51_BaseException = api._BaseException52 53sys.excepthook = Pyro4.util.excepthook54Pyro4.config.PREFER_IP_VERSION = 0 # allow system to prefer IPv655Pyro4.config.COMMTIMEOUT = 40.0 # a bit longer than the default SQL server Gtimeout56Pyro4.config.SERIALIZER = "pickle"57 58try:59 verbose = int(os.environ["ADODBAPI_VERBOSE"])60except:61 verbose = False62if verbose:63 print(version)64 65# --- define objects to smooth out Python3 <-> Python 2.x differences66unicodeType = str # this line will be altered by 2to3.py to '= str'67longType = int # this line will be altered by 2to3.py to '= int'68StringTypes = str69makeByteBuffer = bytes70memoryViewType = memoryview71 72# -----------------------------------------------------------73# conversion functions mandated by PEP 24974Binary = makeByteBuffer # override the function from apibase.py75 76 77def Date(year, month, day):78 return datetime.date(year, month, day) # dateconverter.Date(year,month,day)79 80 81def Time(hour, minute, second):82 return datetime.time(hour, minute, second) # dateconverter.Time(hour,minute,second)83 84 85def Timestamp(year, month, day, hour, minute, second):86 return datetime.datetime(year, month, day, hour, minute, second)87 88 89def DateFromTicks(ticks):90 return Date(*time.gmtime(ticks)[:3])91 92 93def TimeFromTicks(ticks):94 return Time(*time.gmtime(ticks)[3:6])95 96 97def TimestampFromTicks(ticks):98 return Timestamp(*time.gmtime(ticks)[:6])99 100 101def connect(*args, **kwargs): # --> a remote db-api connection object102 """Create and open a remote db-api database connection object"""103 # process the argument list the programmer gave us104 kwargs = adodbapi.process_connect_string.process(args, kwargs)105 # the "proxy_xxx" keys tell us where to find the PyRO proxy server106 kwargs.setdefault(107 "pyro_connection", "PYRO:ado.connection@%(proxy_host)s:%(proxy_port)s"108 )109 if not "proxy_port" in kwargs:110 try:111 pport = os.environ["PROXY_PORT"]112 except KeyError:113 pport = 9099114 kwargs["proxy_port"] = pport115 if not "proxy_host" in kwargs or not kwargs["proxy_host"]:116 try:117 phost = os.environ["PROXY_HOST"]118 except KeyError:119 phost = "[::1]" # '127.0.0.1'120 kwargs["proxy_host"] = phost121 ado_uri = kwargs["pyro_connection"] % kwargs122 # ask PyRO make us a remote connection object123 auto_retry = 3124 while auto_retry:125 try:126 dispatcher = Pyro4.Proxy(ado_uri)127 if "comm_timeout" in kwargs:128 dispatcher._pyroTimeout = float(kwargs["comm_timeout"])129 uri = dispatcher.make_connection()130 break131 except Pyro4.core.errors.PyroError:132 auto_retry -= 1133 if auto_retry:134 time.sleep(1)135 else:136 raise api.DatabaseError("Cannot create connection to=%s" % ado_uri)137 138 conn_uri = fix_uri(uri, kwargs) # get a host connection from the proxy server139 while auto_retry:140 try:141 host_conn = Pyro4.Proxy(142 conn_uri143 ) # bring up an exclusive Pyro connection for my ADO connection144 break145 except Pyro4.core.errors.PyroError:146 auto_retry -= 1147 if auto_retry:148 time.sleep(1)149 else:150 raise api.DatabaseError(151 "Cannot create ADO connection object using=%s" % conn_uri152 )153 if "comm_timeout" in kwargs:154 host_conn._pyroTimeout = float(kwargs["comm_timeout"])155 # make a local clone156 myConn = Connection()157 while auto_retry:158 try:159 myConn.connect(160 kwargs, host_conn161 ) # call my connect method -- hand him the host connection162 break163 except Pyro4.core.errors.PyroError:164 auto_retry -= 1165 if auto_retry:166 time.sleep(1)167 else:168 raise api.DatabaseError(169 "Pyro error creating connection to/thru=%s" % repr(kwargs)170 )171 except _BaseException as e:172 raise api.DatabaseError(173 "Error creating remote connection to=%s, e=%s, %s"174 % (repr(kwargs), repr(e), sys.exc_info()[2])175 )176 return myConn177 178 179def fix_uri(uri, kwargs):180 """convert a generic pyro uri with '0.0.0.0' into the address we actually called"""181 u = uri.asString()182 s = u.split("[::0]") # IPv6 generic address183 if len(s) == 1: # did not find one184 s = u.split("0.0.0.0") # IPv4 generic address185 if len(s) > 1: # found a generic186 return kwargs["proxy_host"].join(s) # fill in our address for the host187 return uri188 189 190# # # # # ----- the Class that defines a connection ----- # # # # #191class Connection(object):192 # include connection attributes required by api definition.193 Warning = api.Warning194 Error = api.Error195 InterfaceError = api.InterfaceError196 DataError = api.DataError197 DatabaseError = api.DatabaseError198 OperationalError = api.OperationalError199 IntegrityError = api.IntegrityError200 InternalError = api.InternalError201 NotSupportedError = api.NotSupportedError202 ProgrammingError = api.ProgrammingError203 # set up some class attributes204 paramstyle = api.paramstyle205 206 @property207 def dbapi(self): # a proposed db-api version 3 extension.208 "Return a reference to the DBAPI module for this Connection."209 return api210 211 def __init__(self):212 self.proxy = None213 self.kwargs = {}214 self.errorhandler = None215 self.supportsTransactions = False216 self.paramstyle = api.paramstyle217 self.timeout = 30218 self.cursors = {}219 220 def connect(self, kwargs, connection_maker):221 self.kwargs = kwargs222 if verbose:223 print('%s attempting: "%s"' % (version, repr(kwargs)))224 self.proxy = connection_maker225 ##try:226 ret = self.proxy.connect(kwargs) # ask the server to hook us up227 ##except ImportError, e: # Pyro is trying to import pywinTypes.comerrer228 ## self._raiseConnectionError(api.DatabaseError, 'Proxy cannot connect using=%s' % repr(kwargs))229 if ret is not True:230 self._raiseConnectionError(231 api.OperationalError, "Proxy returns error message=%s" % repr(ret)232 )233 234 self.supportsTransactions = self.getIndexedValue("supportsTransactions")235 self.paramstyle = self.getIndexedValue("paramstyle")236 self.timeout = self.getIndexedValue("timeout")237 if verbose:238 print("adodbapi.remote New connection at %X" % id(self))239 240 def _raiseConnectionError(self, errorclass, errorvalue):241 eh = self.errorhandler242 if eh is None:243 eh = api.standardErrorHandler244 eh(self, None, errorclass, errorvalue)245 246 def close(self):247 """Close the connection now (rather than whenever __del__ is called).248 249 The connection will be unusable from this point forward;250 an Error (or subclass) exception will be raised if any operation is attempted with the connection.251 The same applies to all cursor objects trying to use the connection.252 """253 for crsr in list(self.cursors.values())[254 :255 ]: # copy the list, then close each one256 crsr.close()257 try:258 """close the underlying remote Connection object"""259 self.proxy.close()260 if verbose:261 print("adodbapi.remote Closed connection at %X" % id(self))262 object.__delattr__(263 self, "proxy"264 ) # future attempts to use closed cursor will be caught by __getattr__265 except Exception:266 pass267 268 def __del__(self):269 try:270 self.proxy.close()271 except:272 pass273 274 def commit(self):275 """Commit any pending transaction to the database.276 277 Note that if the database supports an auto-commit feature,278 this must be initially off. An interface method may be provided to turn it back on.279 Database modules that do not support transactions should implement this method with void functionality.280 """281 if not self.supportsTransactions:282 return283 result = self.proxy.commit()284 if result:285 self._raiseConnectionError(286 api.OperationalError, "Error during commit: %s" % result287 )288 289 def _rollback(self):290 """In case a database does provide transactions this method causes the the database to roll back to291 the start of any pending transaction. Closing a connection without committing the changes first will292 cause an implicit rollback to be performed.293 """294 result = self.proxy.rollback()295 if result:296 self._raiseConnectionError(297 api.OperationalError, "Error during rollback: %s" % result298 )299 300 def __setattr__(self, name, value):301 if name in ("paramstyle", "timeout", "autocommit"):302 if self.proxy:303 self.proxy.send_attribute_to_host(name, value)304 object.__setattr__(self, name, value) # store attribute locally (too)305 306 def __getattr__(self, item):307 if (308 item == "rollback"309 ): # the rollback method only appears if the database supports transactions310 if self.supportsTransactions:311 return (312 self._rollback313 ) # return the rollback method so the caller can execute it.314 else:315 raise self.ProgrammingError(316 "this data provider does not support Rollback"317 )318 elif item in (319 "dbms_name",320 "dbms_version",321 "connection_string",322 "autocommit",323 ): # 'messages' ):324 return self.getIndexedValue(item)325 elif item == "proxy":326 raise self.ProgrammingError("Attempting to use closed connection")327 else:328 raise self.ProgrammingError('No remote access for attribute="%s"' % item)329 330 def getIndexedValue(self, index):331 r = self.proxy.get_attribute_for_remote(index)332 return r333 334 def cursor(self):335 "Return a new Cursor Object using the connection."336 myCursor = Cursor(self)337 return myCursor338 339 def _i_am_here(self, crsr):340 "message from a new cursor proclaiming its existence"341 self.cursors[crsr.id] = crsr342 343 def _i_am_closing(self, crsr):344 "message from a cursor giving connection a chance to clean up"345 try:346 del self.cursors[crsr.id]347 except:348 pass349 350 def __enter__(self): # Connections are context managers351 return self352 353 def __exit__(self, exc_type, exc_val, exc_tb):354 if exc_type:355 self._rollback() # automatic rollback on errors356 else:357 self.commit()358 359 def get_table_names(self):360 return self.proxy.get_table_names()361 362 363def fixpickle(x):364 """pickle barfs on buffer(x) so we pass as array.array(x) then restore to original form for .execute()"""365 if x is None:366 return None367 if isinstance(x, dict):368 # for 'named' paramstyle user will pass a mapping369 newargs = {}370 for arg, val in list(x.items()):371 if isinstance(val, memoryViewType):372 newval = array.array("B")373 newval.fromstring(val)374 newargs[arg] = newval375 else:376 newargs[arg] = val377 return newargs378 # if not a mapping, then a sequence379 newargs = []380 for arg in x:381 if isinstance(arg, memoryViewType):382 newarg = array.array("B")383 newarg.fromstring(arg)384 newargs.append(newarg)385 else:386 newargs.append(arg)387 return newargs388 389 390class Cursor(object):391 def __init__(self, connection):392 self.command = None393 self.errorhandler = None ## was: connection.errorhandler394 self.connection = connection395 self.proxy = self.connection.proxy396 self.rs = None # the fetchable data for this cursor397 self.converters = NotImplemented398 self.id = connection.proxy.build_cursor()399 connection._i_am_here(self)400 self.recordset_format = api.RS_REMOTE401 if verbose:402 print(403 "%s New cursor at %X on conn %X"404 % (version, id(self), id(self.connection))405 )406 407 def prepare(self, operation):408 self.command = operation409 try:410 del self.description411 except AttributeError:412 pass413 self.proxy.crsr_prepare(self.id, operation)414 415 def __iter__(self): # [2.1 Zamarev]416 return iter(self.fetchone, None) # [2.1 Zamarev]417 418 def __next__(self):419 r = self.fetchone()420 if r:421 return r422 raise StopIteration423 424 def __enter__(self):425 "Allow database cursors to be used with context managers."426 return self427 428 def __exit__(self, exc_type, exc_val, exc_tb):429 "Allow database cursors to be used with context managers."430 self.close()431 432 def __getattr__(self, key):433 if key == "numberOfColumns":434 try:435 return len(self.rs[0])436 except:437 return 0438 if key == "description":439 try:440 self.description = self.proxy.crsr_get_description(self.id)[:]441 return self.description442 except TypeError:443 return None444 if key == "columnNames":445 try:446 r = dict(447 self.proxy.crsr_get_columnNames(self.id)448 ) # copy the remote columns449 450 except TypeError:451 r = {}452 self.columnNames = r453 return r454 455 if key == "remote_cursor":456 raise api.OperationalError457 try:458 return self.proxy.crsr_get_attribute_for_remote(self.id, key)459 except AttributeError:460 raise api.InternalError(461 'Failure getting attribute "%s" from proxy cursor.' % key462 )463 464 def __setattr__(self, key, value):465 if key == "arraysize":466 self.proxy.crsr_set_arraysize(self.id, value)467 if key == "paramstyle":468 if value in api.accepted_paramstyles:469 self.proxy.crsr_set_paramstyle(self.id, value)470 else:471 self._raiseCursorError(472 api.ProgrammingError, 'invalid paramstyle ="%s"' % value473 )474 object.__setattr__(self, key, value)475 476 def _raiseCursorError(self, errorclass, errorvalue):477 eh = self.errorhandler478 if eh is None:479 eh = api.standardErrorHandler480 eh(self.connection, self, errorclass, errorvalue)481 482 def execute(self, operation, parameters=None):483 if self.connection is None:484 self._raiseCursorError(485 ProgrammingError, "Attempted operation on closed cursor"486 )487 self.command = operation488 try:489 del self.description490 except AttributeError:491 pass492 try:493 del self.columnNames494 except AttributeError:495 pass496 fp = fixpickle(parameters)497 if verbose > 2:498 print(499 (500 '%s executing "%s" with params=%s'501 % (version, operation, repr(parameters))502 )503 )504 result = self.proxy.crsr_execute(self.id, operation, fp)505 if result: # an exception was triggered506 self._raiseCursorError(result[0], result[1])507 508 def executemany(self, operation, seq_of_parameters):509 if self.connection is None:510 self._raiseCursorError(511 ProgrammingError, "Attempted operation on closed cursor"512 )513 self.command = operation514 try:515 del self.description516 except AttributeError:517 pass518 try:519 del self.columnNames520 except AttributeError:521 pass522 sq = [fixpickle(x) for x in seq_of_parameters]523 if verbose > 2:524 print(525 (526 '%s executemany "%s" with params=%s'527 % (version, operation, repr(seq_of_parameters))528 )529 )530 self.proxy.crsr_executemany(self.id, operation, sq)531 532 def nextset(self):533 try:534 del self.description535 except AttributeError:536 pass537 try:538 del self.columnNames539 except AttributeError:540 pass541 if verbose > 2:542 print(("%s nextset" % version))543 return self.proxy.crsr_nextset(self.id)544 545 def callproc(self, procname, parameters=None):546 if self.connection is None:547 self._raiseCursorError(548 ProgrammingError, "Attempted operation on closed cursor"549 )550 self.command = procname551 try:552 del self.description553 except AttributeError:554 pass555 try:556 del self.columnNames557 except AttributeError:558 pass559 fp = fixpickle(parameters)560 if verbose > 2:561 print(562 (563 '%s callproc "%s" with params=%s'564 % (version, procname, repr(parameters))565 )566 )567 return self.proxy.crsr_callproc(self.id, procname, fp)568 569 def fetchone(self):570 try:571 f1 = self.proxy.crsr_fetchone(self.id)572 except _BaseException as e:573 self._raiseCursorError(api.DatabaseError, e)574 else:575 if f1 is None:576 return None577 self.rs = [f1]578 return api.SQLrows(self.rs, 1, self)[579 0580 ] # new object to hold the results of the fetch581 582 def fetchmany(self, size=None):583 try:584 self.rs = self.proxy.crsr_fetchmany(self.id, size)585 if not self.rs:586 return []587 r = api.SQLrows(self.rs, len(self.rs), self)588 return r589 except Exception as e:590 self._raiseCursorError(api.DatabaseError, e)591 592 def fetchall(self):593 try:594 self.rs = self.proxy.crsr_fetchall(self.id)595 if not self.rs:596 return []597 return api.SQLrows(self.rs, len(self.rs), self)598 except Exception as e:599 self._raiseCursorError(api.DatabaseError, e)600 601 def close(self):602 if self.connection is None:603 return604 self.connection._i_am_closing(self) # take me off the connection's cursors list605 try:606 self.proxy.crsr_close(self.id)607 except:608 pass609 try:610 del self.description611 except:612 pass613 try:614 del self.rs # let go of the recordset615 except:616 pass617 self.connection = (618 None # this will make all future method calls on me throw an exception619 )620 self.proxy = None621 if verbose:622 print("adodbapi.remote Closed cursor at %X" % id(self))623 624 def __del__(self):625 try:626 self.close()627 except:628 pass629 630 def setinputsizes(self, sizes):631 pass632 633 def setoutputsize(self, size, column=None):634 pass635 