remote.py 19 KB

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