remote.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537
  1. """adodbapi.remote - A python DB API 2.0 (PEP 249) interface to Microsoft ADO
  2. Copyright (C) 2002 Henrik Ekelund, version 2.1 by Vernon Cole
  3. * http://sourceforge.net/projects/pywin32
  4. * http://sourceforge.net/projects/adodbapi
  5. This library is free software; you can redistribute it and/or
  6. modify it under the terms of the GNU Lesser General Public
  7. License as published by the Free Software Foundation; either
  8. version 2.1 of the License, or (at your option) any later version.
  9. This library is distributed in the hope that it will be useful,
  10. but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  12. Lesser General Public License for more details.
  13. You should have received a copy of the GNU Lesser General Public
  14. License along with this library; if not, write to the Free Software
  15. Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  16. django adaptations and refactoring thanks to Adam Vandenberg
  17. DB-API 2.0 specification: http://www.python.org/dev/peps/pep-0249/
  18. This module source should run correctly in CPython versions 2.5 and later,
  19. or IronPython version 2.7 and later,
  20. or, after running through 2to3.py, CPython 3.0 or later.
  21. """
  22. from __future__ import absolute_import
  23. __version__ = '2.6.0.4'
  24. version = 'adodbapi.remote v' + __version__
  25. import os
  26. import sys
  27. import array
  28. import time
  29. import datetime
  30. # Pyro4 is required for server and remote operation --> https://pypi.python.org/pypi/Pyro4/
  31. try:
  32. import Pyro4
  33. except ImportError:
  34. print('* * * Sorry, server operation requires Pyro4. Please "pip import" it.')
  35. exit(11)
  36. import adodbapi
  37. import adodbapi.apibase as api
  38. import adodbapi.process_connect_string
  39. from adodbapi.apibase import ProgrammingError
  40. _BaseException = api._BaseException
  41. sys.excepthook = Pyro4.util.excepthook
  42. Pyro4.config.PREFER_IP_VERSION = 0 # allow system to prefer IPv6
  43. Pyro4.config.COMMTIMEOUT = 40.0 # a bit longer than the default SQL server Gtimeout
  44. Pyro4.config.SERIALIZER = 'pickle'
  45. try:
  46. verbose = int(os.environ['ADODBAPI_VERBOSE'])
  47. except:
  48. verbose = False
  49. if verbose:
  50. print(version)
  51. # --- define objects to smooth out Python3 <-> Python 2.x differences
  52. unicodeType = unicode #this line will be altered by 2to3.py to '= str'
  53. longType = long #this line will be altered by 2to3.py to '= int'
  54. if sys.version[0] >= '3': #python 3.x
  55. StringTypes = str
  56. makeByteBuffer = bytes
  57. memoryViewType = memoryview
  58. else: #python 2.x
  59. memoryViewType = type(buffer(''))
  60. def makeByteBuffer(x): # special for remote to be pickle-able
  61. return bytes(x)
  62. try: #jdhardy -- handle bytes under IronPython
  63. bytes
  64. except NameError:
  65. bytes = str
  66. StringTypes = (str,unicode) # will be messed up by 2to3 but never used
  67. # -----------------------------------------------------------
  68. # conversion functions mandated by PEP 249
  69. Binary = makeByteBuffer # override the function from apibase.py
  70. def Date(year,month,day):
  71. return datetime.date(year,month,day) #dateconverter.Date(year,month,day)
  72. def Time(hour,minute,second):
  73. return datetime.time(hour, minute, second) # dateconverter.Time(hour,minute,second)
  74. def Timestamp(year,month,day,hour,minute,second):
  75. return datetime.datetime(year,month,day,hour,minute,second)
  76. def DateFromTicks(ticks):
  77. return Date(*time.gmtime(ticks)[:3])
  78. def TimeFromTicks(ticks):
  79. return Time(*time.gmtime(ticks)[3:6])
  80. def TimestampFromTicks(ticks):
  81. return Timestamp(*time.gmtime(ticks)[:6])
  82. def connect(*args, **kwargs): # --> a remote db-api connection object
  83. """Create and open a remote db-api database connection object"""
  84. # process the argument list the programmer gave us
  85. kwargs = adodbapi.process_connect_string.process(args, kwargs)
  86. # the "proxy_xxx" keys tell us where to find the PyRO proxy server
  87. kwargs.setdefault('pyro_connection', 'PYRO:ado.connection@%(proxy_host)s:%(proxy_port)s')
  88. if not 'proxy_port' in kwargs:
  89. try:
  90. pport = os.environ['PROXY_PORT']
  91. except KeyError:
  92. pport = 9099
  93. kwargs['proxy_port'] = pport
  94. if not 'proxy_host' in kwargs or not kwargs['proxy_host']:
  95. try:
  96. phost = os.environ['PROXY_HOST']
  97. except KeyError:
  98. phost = '[::1]' # '127.0.0.1'
  99. kwargs['proxy_host'] = phost
  100. ado_uri = kwargs['pyro_connection'] % kwargs
  101. # ask PyRO make us a remote connection object
  102. auto_retry = 3
  103. while auto_retry:
  104. try:
  105. dispatcher = Pyro4.Proxy(ado_uri)
  106. if 'comm_timeout' in kwargs:
  107. dispatcher._pyroTimeout = float(kwargs['comm_timeout'])
  108. uri = dispatcher.make_connection()
  109. break
  110. except Pyro4.core.errors.PyroError:
  111. auto_retry -= 1
  112. if auto_retry:
  113. time.sleep(1)
  114. else:
  115. raise api.DatabaseError ('Cannot create connection to=%s' % ado_uri)
  116. conn_uri = fix_uri(uri, kwargs) # get a host connection from the proxy server
  117. while auto_retry:
  118. try:
  119. host_conn = Pyro4.Proxy(conn_uri) # bring up an exclusive Pyro connection for my ADO connection
  120. break
  121. except Pyro4.core.errors.PyroError:
  122. auto_retry -= 1
  123. if auto_retry:
  124. time.sleep(1)
  125. else:
  126. raise api.DatabaseError ('Cannot create ADO connection object using=%s' % conn_uri)
  127. if 'comm_timeout' in kwargs:
  128. host_conn._pyroTimeout = float(kwargs['comm_timeout'])
  129. # make a local clone
  130. myConn = Connection()
  131. while auto_retry:
  132. try:
  133. myConn.connect(kwargs, host_conn) # call my connect method -- hand him the host connection
  134. break
  135. except Pyro4.core.errors.PyroError:
  136. auto_retry -= 1
  137. if auto_retry:
  138. time.sleep(1)
  139. else:
  140. raise api.DatabaseError ('Pyro error creating connection to/thru=%s' % repr(kwargs))
  141. except _BaseException, e:
  142. raise api.DatabaseError('Error creating remote connection to=%s, e=%s, %s' % (repr(kwargs), repr(e),sys.exc_info()[2]))
  143. return myConn
  144. def fix_uri(uri, kwargs):
  145. """convert a generic pyro uri with '0.0.0.0' into the address we actually called"""
  146. u = uri.asString()
  147. s = u.split('[::0]') # IPv6 generic address
  148. if len(s) == 1: # did not find one
  149. s = u.split('0.0.0.0') # IPv4 generic address
  150. if len(s) > 1: # found a generic
  151. return kwargs['proxy_host'].join(s) # fill in our address for the host
  152. return uri
  153. # # # # # ----- the Class that defines a connection ----- # # # # #
  154. class Connection(object):
  155. # include connection attributes required by api definition.
  156. Warning = api.Warning
  157. Error = api.Error
  158. InterfaceError = api.InterfaceError
  159. DataError = api.DataError
  160. DatabaseError = api.DatabaseError
  161. OperationalError = api.OperationalError
  162. IntegrityError = api.IntegrityError
  163. InternalError = api.InternalError
  164. NotSupportedError = api.NotSupportedError
  165. ProgrammingError = api.ProgrammingError
  166. # set up some class attributes
  167. paramstyle = api.paramstyle
  168. @property
  169. def dbapi(self): # a proposed db-api version 3 extension.
  170. "Return a reference to the DBAPI module for this Connection."
  171. return api
  172. def __init__(self):
  173. self.proxy = None
  174. self.kwargs = {}
  175. self.errorhandler = None
  176. self.supportsTransactions = False
  177. self.paramstyle = api.paramstyle
  178. self.timeout = 30
  179. self.cursors = {}
  180. def connect(self, kwargs, connection_maker):
  181. self.kwargs = kwargs
  182. if verbose:
  183. print '%s attempting: "%s"' % (version, repr(kwargs))
  184. self.proxy = connection_maker
  185. ##try:
  186. ret = self.proxy.connect(kwargs) # ask the server to hook us up
  187. ##except ImportError, e: # Pyro is trying to import pywinTypes.comerrer
  188. ## self._raiseConnectionError(api.DatabaseError, 'Proxy cannot connect using=%s' % repr(kwargs))
  189. if ret is not True:
  190. self._raiseConnectionError(api.OperationalError, 'Proxy returns error message=%s'%repr(ret))
  191. self.supportsTransactions = self.getIndexedValue('supportsTransactions')
  192. self.paramstyle = self.getIndexedValue('paramstyle')
  193. self.timeout = self.getIndexedValue('timeout')
  194. if verbose:
  195. print 'adodbapi.remote New connection at %X' % id(self)
  196. def _raiseConnectionError(self, errorclass, errorvalue):
  197. eh = self.errorhandler
  198. if eh is None:
  199. eh = api.standardErrorHandler
  200. eh(self, None, errorclass, errorvalue)
  201. def close(self):
  202. """Close the connection now (rather than whenever __del__ is called).
  203. The connection will be unusable from this point forward;
  204. an Error (or subclass) exception will be raised if any operation is attempted with the connection.
  205. The same applies to all cursor objects trying to use the connection.
  206. """
  207. for crsr in self.cursors.values()[:]: # copy the list, then close each one
  208. crsr.close()
  209. try:
  210. """close the underlying remote Connection object"""
  211. self.proxy.close()
  212. if verbose:
  213. print 'adodbapi.remote Closed connection at %X' % id(self)
  214. object.__delattr__(self, 'proxy') # future attempts to use closed cursor will be caught by __getattr__
  215. except Exception:
  216. pass
  217. def __del__(self):
  218. try:
  219. self.proxy.close()
  220. except:
  221. pass
  222. def commit(self):
  223. """Commit any pending transaction to the database.
  224. Note that if the database supports an auto-commit feature,
  225. this must be initially off. An interface method may be provided to turn it back on.
  226. Database modules that do not support transactions should implement this method with void functionality.
  227. """
  228. if not self.supportsTransactions:
  229. return
  230. result = self.proxy.commit()
  231. if result:
  232. self._raiseConnectionError(api.OperationalError, 'Error during commit: %s' % result)
  233. def _rollback(self):
  234. """In case a database does provide transactions this method causes the the database to roll back to
  235. the start of any pending transaction. Closing a connection without committing the changes first will
  236. cause an implicit rollback to be performed.
  237. """
  238. result = self.proxy.rollback()
  239. if result:
  240. self._raiseConnectionError(api.OperationalError, 'Error during rollback: %s' % result)
  241. def __setattr__(self, name, value):
  242. if name in ('paramstyle', 'timeout', 'autocommit'):
  243. if self.proxy:
  244. self.proxy.send_attribute_to_host(name, value)
  245. object.__setattr__(self, name, value) # store attribute locally (too)
  246. def __getattr__(self, item):
  247. if item == 'rollback': # the rollback method only appears if the database supports transactions
  248. if self.supportsTransactions:
  249. return self._rollback # return the rollback method so the caller can execute it.
  250. else:
  251. raise self.ProgrammingError ('this data provider does not support Rollback')
  252. elif item in ('dbms_name', 'dbms_version', 'connection_string', 'autocommit'): # 'messages' ):
  253. return self.getIndexedValue(item)
  254. elif item == 'proxy':
  255. raise self.ProgrammingError('Attempting to use closed connection')
  256. else:
  257. raise self.ProgrammingError('No remote access for attribute="%s"' % item)
  258. def getIndexedValue(self, index):
  259. r = self.proxy.get_attribute_for_remote(index)
  260. return r
  261. def cursor(self):
  262. "Return a new Cursor Object using the connection."
  263. myCursor = Cursor(self)
  264. return myCursor
  265. def _i_am_here(self, crsr):
  266. "message from a new cursor proclaiming its existence"
  267. self.cursors[crsr.id] = crsr
  268. def _i_am_closing(self, crsr):
  269. "message from a cursor giving connection a chance to clean up"
  270. try:
  271. del self.cursors[crsr.id]
  272. except:
  273. pass
  274. def __enter__(self): # Connections are context managers
  275. return(self)
  276. def __exit__(self, exc_type, exc_val, exc_tb):
  277. if exc_type:
  278. self._rollback() #automatic rollback on errors
  279. else:
  280. self.commit()
  281. def get_table_names(self):
  282. return self.proxy.get_table_names()
  283. def fixpickle(x):
  284. """pickle barfs on buffer(x) so we pass as array.array(x) then restore to original form for .execute()"""
  285. if x is None:
  286. return None
  287. if isinstance(x, dict):
  288. # for 'named' paramstyle user will pass a mapping
  289. newargs = {}
  290. for arg,val in x.items():
  291. if isinstance(val, memoryViewType):
  292. newval = array.array('B')
  293. newval.fromstring(val)
  294. newargs[arg] = newval
  295. else:
  296. newargs[arg] = val
  297. return newargs
  298. # if not a mapping, then a sequence
  299. newargs = []
  300. for arg in x:
  301. if isinstance(arg, memoryViewType):
  302. newarg = array.array('B')
  303. newarg.fromstring(arg)
  304. newargs.append(newarg)
  305. else:
  306. newargs.append(arg)
  307. return newargs
  308. class Cursor(object):
  309. def __init__(self, connection):
  310. self.command = None
  311. self.errorhandler = None ## was: connection.errorhandler
  312. self.connection = connection
  313. self.proxy = self.connection.proxy
  314. self.rs = None # the fetchable data for this cursor
  315. self.converters = NotImplemented
  316. self.id = connection.proxy.build_cursor()
  317. connection._i_am_here(self)
  318. self.recordset_format = api.RS_REMOTE
  319. if verbose:
  320. print '%s New cursor at %X on conn %X' % (version, id(self), id(self.connection))
  321. def prepare(self, operation):
  322. self.command = operation
  323. try: del self.description
  324. except AttributeError: pass
  325. self.proxy.crsr_prepare(self.id, operation)
  326. def __iter__(self): # [2.1 Zamarev]
  327. return iter(self.fetchone, None) # [2.1 Zamarev]
  328. def next(self):
  329. r = self.fetchone()
  330. if r:
  331. return r
  332. raise StopIteration
  333. def __enter__(self):
  334. "Allow database cursors to be used with context managers."
  335. return self
  336. def __exit__(self, exc_type, exc_val, exc_tb):
  337. "Allow database cursors to be used with context managers."
  338. self.close()
  339. def __getattr__(self, key):
  340. if key == 'numberOfColumns':
  341. try:
  342. return len(self.rs[0])
  343. except:
  344. return 0
  345. if key == 'description':
  346. try:
  347. self.description = self.proxy.crsr_get_description(self.id)[:]
  348. return self.description
  349. except TypeError:
  350. return None
  351. if key == 'columnNames':
  352. try:
  353. r = dict(self.proxy.crsr_get_columnNames(self.id)) # copy the remote columns
  354. except TypeError:
  355. r = {}
  356. self.columnNames = r
  357. return r
  358. if key == 'remote_cursor':
  359. raise api.OperationalError
  360. try:
  361. return self.proxy.crsr_get_attribute_for_remote(self.id, key)
  362. except AttributeError:
  363. raise api.InternalError ('Failure getting attribute "%s" from proxy cursor.' % key)
  364. def __setattr__(self, key, value):
  365. if key == 'arraysize':
  366. self.proxy.crsr_set_arraysize(self.id, value)
  367. if key == 'paramstyle':
  368. if value in api.accepted_paramstyles:
  369. self.proxy.crsr_set_paramstyle(self.id, value)
  370. else:
  371. self._raiseCursorError(api.ProgrammingError, 'invalid paramstyle ="%s"' % value)
  372. object.__setattr__(self, key, value)
  373. def _raiseCursorError(self, errorclass, errorvalue):
  374. eh = self.errorhandler
  375. if eh is None:
  376. eh = api.standardErrorHandler
  377. eh(self.connection, self, errorclass, errorvalue)
  378. def execute(self, operation, parameters=None):
  379. if self.connection is None:
  380. self._raiseCursorError(ProgrammingError, 'Attempted operation on closed cursor')
  381. self.command = operation
  382. try: del self.description
  383. except AttributeError: pass
  384. try: del self.columnNames
  385. except AttributeError: pass
  386. fp = fixpickle(parameters)
  387. if verbose > 2:
  388. print('%s executing "%s" with params=%s' % (version, operation, repr(parameters)))
  389. result = self.proxy.crsr_execute(self.id, operation, fp)
  390. if result: # an exception was triggered
  391. self._raiseCursorError(result[0], result[1])
  392. def executemany(self, operation, seq_of_parameters):
  393. if self.connection is None:
  394. self._raiseCursorError(ProgrammingError, 'Attempted operation on closed cursor')
  395. self.command = operation
  396. try: del self.description
  397. except AttributeError: pass
  398. try: del self.columnNames
  399. except AttributeError: pass
  400. sq = [fixpickle(x) for x in seq_of_parameters]
  401. if verbose > 2:
  402. print('%s executemany "%s" with params=%s' % (version, operation, repr(seq_of_parameters)))
  403. self.proxy.crsr_executemany(self.id, operation, sq)
  404. def nextset(self):
  405. try: del self.description
  406. except AttributeError: pass
  407. try: del self.columnNames
  408. except AttributeError: pass
  409. if verbose > 2:
  410. print('%s nextset' % version)
  411. return self.proxy.crsr_nextset(self.id)
  412. def callproc(self, procname, parameters=None):
  413. if self.connection is None:
  414. self._raiseCursorError(ProgrammingError, 'Attempted operation on closed cursor')
  415. self.command = procname
  416. try: del self.description
  417. except AttributeError: pass
  418. try: del self.columnNames
  419. except AttributeError: pass
  420. fp = fixpickle(parameters)
  421. if verbose > 2:
  422. print('%s callproc "%s" with params=%s' % (version, procname, repr(parameters)))
  423. return self.proxy.crsr_callproc(self.id, procname, fp)
  424. def fetchone(self):
  425. try:
  426. f1 = self.proxy.crsr_fetchone(self.id)
  427. except _BaseException, e:
  428. self._raiseCursorError(api.DatabaseError, e)
  429. else:
  430. if f1 is None:
  431. return None
  432. self.rs = [f1]
  433. return api.SQLrows(self.rs, 1, self)[0] # new object to hold the results of the fetch
  434. def fetchmany(self, size=None):
  435. try:
  436. self.rs = self.proxy.crsr_fetchmany(self.id, size)
  437. if not self.rs:
  438. return []
  439. r = api.SQLrows(self.rs, len(self.rs), self)
  440. return r
  441. except Exception, e:
  442. self._raiseCursorError(api.DatabaseError, e)
  443. def fetchall(self):
  444. try:
  445. self.rs = self.proxy.crsr_fetchall(self.id)
  446. if not self.rs:
  447. return []
  448. return api.SQLrows(self.rs, len(self.rs), self)
  449. except Exception, e:
  450. self._raiseCursorError(api.DatabaseError, e)
  451. def close(self):
  452. if self.connection is None:
  453. return
  454. self.connection._i_am_closing(self) # take me off the connection's cursors list
  455. try:
  456. self.proxy.crsr_close(self.id)
  457. except: pass
  458. try: del self.description
  459. except: pass
  460. try: del self.rs # let go of the recordset
  461. except: pass
  462. self.connection = None #this will make all future method calls on me throw an exception
  463. self.proxy = None
  464. if verbose:
  465. print 'adodbapi.remote Closed cursor at %X' % id(self)
  466. def __del__(self):
  467. try:
  468. self.close()
  469. except:
  470. pass
  471. def setinputsizes(self,sizes):
  472. pass
  473. def setoutputsize(self, size, column=None):
  474. pass