Funktionierender Prototyp des Serious Games zur Vermittlung von Wissen zu Software-Engineering-Arbeitsmodellen.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

remote.py 21KB

1 year ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634
  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 array
  25. import datetime
  26. import os
  27. import sys
  28. import time
  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(
  77. "pyro_connection", "PYRO:ado.connection@%(proxy_host)s:%(proxy_port)s"
  78. )
  79. if not "proxy_port" in kwargs:
  80. try:
  81. pport = os.environ["PROXY_PORT"]
  82. except KeyError:
  83. pport = 9099
  84. kwargs["proxy_port"] = pport
  85. if not "proxy_host" in kwargs or not kwargs["proxy_host"]:
  86. try:
  87. phost = os.environ["PROXY_HOST"]
  88. except KeyError:
  89. phost = "[::1]" # '127.0.0.1'
  90. kwargs["proxy_host"] = phost
  91. ado_uri = kwargs["pyro_connection"] % kwargs
  92. # ask PyRO make us a remote connection object
  93. auto_retry = 3
  94. while auto_retry:
  95. try:
  96. dispatcher = Pyro4.Proxy(ado_uri)
  97. if "comm_timeout" in kwargs:
  98. dispatcher._pyroTimeout = float(kwargs["comm_timeout"])
  99. uri = dispatcher.make_connection()
  100. break
  101. except Pyro4.core.errors.PyroError:
  102. auto_retry -= 1
  103. if auto_retry:
  104. time.sleep(1)
  105. else:
  106. raise api.DatabaseError("Cannot create connection to=%s" % ado_uri)
  107. conn_uri = fix_uri(uri, kwargs) # get a host connection from the proxy server
  108. while auto_retry:
  109. try:
  110. host_conn = Pyro4.Proxy(
  111. conn_uri
  112. ) # bring up an exclusive Pyro connection for my ADO connection
  113. break
  114. except Pyro4.core.errors.PyroError:
  115. auto_retry -= 1
  116. if auto_retry:
  117. time.sleep(1)
  118. else:
  119. raise api.DatabaseError(
  120. "Cannot create ADO connection object using=%s" % conn_uri
  121. )
  122. if "comm_timeout" in kwargs:
  123. host_conn._pyroTimeout = float(kwargs["comm_timeout"])
  124. # make a local clone
  125. myConn = Connection()
  126. while auto_retry:
  127. try:
  128. myConn.connect(
  129. kwargs, host_conn
  130. ) # call my connect method -- hand him the host connection
  131. break
  132. except Pyro4.core.errors.PyroError:
  133. auto_retry -= 1
  134. if auto_retry:
  135. time.sleep(1)
  136. else:
  137. raise api.DatabaseError(
  138. "Pyro error creating connection to/thru=%s" % repr(kwargs)
  139. )
  140. except _BaseException as e:
  141. raise api.DatabaseError(
  142. "Error creating remote connection to=%s, e=%s, %s"
  143. % (repr(kwargs), repr(e), sys.exc_info()[2])
  144. )
  145. return myConn
  146. def fix_uri(uri, kwargs):
  147. """convert a generic pyro uri with '0.0.0.0' into the address we actually called"""
  148. u = uri.asString()
  149. s = u.split("[::0]") # IPv6 generic address
  150. if len(s) == 1: # did not find one
  151. s = u.split("0.0.0.0") # IPv4 generic address
  152. if len(s) > 1: # found a generic
  153. return kwargs["proxy_host"].join(s) # fill in our address for the host
  154. return uri
  155. # # # # # ----- the Class that defines a connection ----- # # # # #
  156. class Connection(object):
  157. # include connection attributes required by api definition.
  158. Warning = api.Warning
  159. Error = api.Error
  160. InterfaceError = api.InterfaceError
  161. DataError = api.DataError
  162. DatabaseError = api.DatabaseError
  163. OperationalError = api.OperationalError
  164. IntegrityError = api.IntegrityError
  165. InternalError = api.InternalError
  166. NotSupportedError = api.NotSupportedError
  167. ProgrammingError = api.ProgrammingError
  168. # set up some class attributes
  169. paramstyle = api.paramstyle
  170. @property
  171. def dbapi(self): # a proposed db-api version 3 extension.
  172. "Return a reference to the DBAPI module for this Connection."
  173. return api
  174. def __init__(self):
  175. self.proxy = None
  176. self.kwargs = {}
  177. self.errorhandler = None
  178. self.supportsTransactions = False
  179. self.paramstyle = api.paramstyle
  180. self.timeout = 30
  181. self.cursors = {}
  182. def connect(self, kwargs, connection_maker):
  183. self.kwargs = kwargs
  184. if verbose:
  185. print('%s attempting: "%s"' % (version, repr(kwargs)))
  186. self.proxy = connection_maker
  187. ##try:
  188. ret = self.proxy.connect(kwargs) # ask the server to hook us up
  189. ##except ImportError, e: # Pyro is trying to import pywinTypes.comerrer
  190. ## self._raiseConnectionError(api.DatabaseError, 'Proxy cannot connect using=%s' % repr(kwargs))
  191. if ret is not True:
  192. self._raiseConnectionError(
  193. api.OperationalError, "Proxy returns error message=%s" % repr(ret)
  194. )
  195. self.supportsTransactions = self.getIndexedValue("supportsTransactions")
  196. self.paramstyle = self.getIndexedValue("paramstyle")
  197. self.timeout = self.getIndexedValue("timeout")
  198. if verbose:
  199. print("adodbapi.remote New connection at %X" % id(self))
  200. def _raiseConnectionError(self, errorclass, errorvalue):
  201. eh = self.errorhandler
  202. if eh is None:
  203. eh = api.standardErrorHandler
  204. eh(self, None, errorclass, errorvalue)
  205. def close(self):
  206. """Close the connection now (rather than whenever __del__ is called).
  207. The connection will be unusable from this point forward;
  208. an Error (or subclass) exception will be raised if any operation is attempted with the connection.
  209. The same applies to all cursor objects trying to use the connection.
  210. """
  211. for crsr in list(self.cursors.values())[
  212. :
  213. ]: # copy the list, then close each one
  214. crsr.close()
  215. try:
  216. """close the underlying remote Connection object"""
  217. self.proxy.close()
  218. if verbose:
  219. print("adodbapi.remote Closed connection at %X" % id(self))
  220. object.__delattr__(
  221. self, "proxy"
  222. ) # future attempts to use closed cursor will be caught by __getattr__
  223. except Exception:
  224. pass
  225. def __del__(self):
  226. try:
  227. self.proxy.close()
  228. except:
  229. pass
  230. def commit(self):
  231. """Commit any pending transaction to the database.
  232. Note that if the database supports an auto-commit feature,
  233. this must be initially off. An interface method may be provided to turn it back on.
  234. Database modules that do not support transactions should implement this method with void functionality.
  235. """
  236. if not self.supportsTransactions:
  237. return
  238. result = self.proxy.commit()
  239. if result:
  240. self._raiseConnectionError(
  241. api.OperationalError, "Error during commit: %s" % result
  242. )
  243. def _rollback(self):
  244. """In case a database does provide transactions this method causes the the database to roll back to
  245. the start of any pending transaction. Closing a connection without committing the changes first will
  246. cause an implicit rollback to be performed.
  247. """
  248. result = self.proxy.rollback()
  249. if result:
  250. self._raiseConnectionError(
  251. api.OperationalError, "Error during rollback: %s" % result
  252. )
  253. def __setattr__(self, name, value):
  254. if name in ("paramstyle", "timeout", "autocommit"):
  255. if self.proxy:
  256. self.proxy.send_attribute_to_host(name, value)
  257. object.__setattr__(self, name, value) # store attribute locally (too)
  258. def __getattr__(self, item):
  259. if (
  260. item == "rollback"
  261. ): # the rollback method only appears if the database supports transactions
  262. if self.supportsTransactions:
  263. return (
  264. self._rollback
  265. ) # return the rollback method so the caller can execute it.
  266. else:
  267. raise self.ProgrammingError(
  268. "this data provider does not support Rollback"
  269. )
  270. elif item in (
  271. "dbms_name",
  272. "dbms_version",
  273. "connection_string",
  274. "autocommit",
  275. ): # 'messages' ):
  276. return self.getIndexedValue(item)
  277. elif item == "proxy":
  278. raise self.ProgrammingError("Attempting to use closed connection")
  279. else:
  280. raise self.ProgrammingError('No remote access for attribute="%s"' % item)
  281. def getIndexedValue(self, index):
  282. r = self.proxy.get_attribute_for_remote(index)
  283. return r
  284. def cursor(self):
  285. "Return a new Cursor Object using the connection."
  286. myCursor = Cursor(self)
  287. return myCursor
  288. def _i_am_here(self, crsr):
  289. "message from a new cursor proclaiming its existence"
  290. self.cursors[crsr.id] = crsr
  291. def _i_am_closing(self, crsr):
  292. "message from a cursor giving connection a chance to clean up"
  293. try:
  294. del self.cursors[crsr.id]
  295. except:
  296. pass
  297. def __enter__(self): # Connections are context managers
  298. return self
  299. def __exit__(self, exc_type, exc_val, exc_tb):
  300. if exc_type:
  301. self._rollback() # automatic rollback on errors
  302. else:
  303. self.commit()
  304. def get_table_names(self):
  305. return self.proxy.get_table_names()
  306. def fixpickle(x):
  307. """pickle barfs on buffer(x) so we pass as array.array(x) then restore to original form for .execute()"""
  308. if x is None:
  309. return None
  310. if isinstance(x, dict):
  311. # for 'named' paramstyle user will pass a mapping
  312. newargs = {}
  313. for arg, val in list(x.items()):
  314. if isinstance(val, memoryViewType):
  315. newval = array.array("B")
  316. newval.fromstring(val)
  317. newargs[arg] = newval
  318. else:
  319. newargs[arg] = val
  320. return newargs
  321. # if not a mapping, then a sequence
  322. newargs = []
  323. for arg in x:
  324. if isinstance(arg, memoryViewType):
  325. newarg = array.array("B")
  326. newarg.fromstring(arg)
  327. newargs.append(newarg)
  328. else:
  329. newargs.append(arg)
  330. return newargs
  331. class Cursor(object):
  332. def __init__(self, connection):
  333. self.command = None
  334. self.errorhandler = None ## was: connection.errorhandler
  335. self.connection = connection
  336. self.proxy = self.connection.proxy
  337. self.rs = None # the fetchable data for this cursor
  338. self.converters = NotImplemented
  339. self.id = connection.proxy.build_cursor()
  340. connection._i_am_here(self)
  341. self.recordset_format = api.RS_REMOTE
  342. if verbose:
  343. print(
  344. "%s New cursor at %X on conn %X"
  345. % (version, id(self), id(self.connection))
  346. )
  347. def prepare(self, operation):
  348. self.command = operation
  349. try:
  350. del self.description
  351. except AttributeError:
  352. pass
  353. self.proxy.crsr_prepare(self.id, operation)
  354. def __iter__(self): # [2.1 Zamarev]
  355. return iter(self.fetchone, None) # [2.1 Zamarev]
  356. def __next__(self):
  357. r = self.fetchone()
  358. if r:
  359. return r
  360. raise StopIteration
  361. def __enter__(self):
  362. "Allow database cursors to be used with context managers."
  363. return self
  364. def __exit__(self, exc_type, exc_val, exc_tb):
  365. "Allow database cursors to be used with context managers."
  366. self.close()
  367. def __getattr__(self, key):
  368. if key == "numberOfColumns":
  369. try:
  370. return len(self.rs[0])
  371. except:
  372. return 0
  373. if key == "description":
  374. try:
  375. self.description = self.proxy.crsr_get_description(self.id)[:]
  376. return self.description
  377. except TypeError:
  378. return None
  379. if key == "columnNames":
  380. try:
  381. r = dict(
  382. self.proxy.crsr_get_columnNames(self.id)
  383. ) # copy the remote columns
  384. except TypeError:
  385. r = {}
  386. self.columnNames = r
  387. return r
  388. if key == "remote_cursor":
  389. raise api.OperationalError
  390. try:
  391. return self.proxy.crsr_get_attribute_for_remote(self.id, key)
  392. except AttributeError:
  393. raise api.InternalError(
  394. 'Failure getting attribute "%s" from proxy cursor.' % key
  395. )
  396. def __setattr__(self, key, value):
  397. if key == "arraysize":
  398. self.proxy.crsr_set_arraysize(self.id, value)
  399. if key == "paramstyle":
  400. if value in api.accepted_paramstyles:
  401. self.proxy.crsr_set_paramstyle(self.id, value)
  402. else:
  403. self._raiseCursorError(
  404. api.ProgrammingError, 'invalid paramstyle ="%s"' % value
  405. )
  406. object.__setattr__(self, key, value)
  407. def _raiseCursorError(self, errorclass, errorvalue):
  408. eh = self.errorhandler
  409. if eh is None:
  410. eh = api.standardErrorHandler
  411. eh(self.connection, self, errorclass, errorvalue)
  412. def execute(self, operation, parameters=None):
  413. if self.connection is None:
  414. self._raiseCursorError(
  415. ProgrammingError, "Attempted operation on closed cursor"
  416. )
  417. self.command = operation
  418. try:
  419. del self.description
  420. except AttributeError:
  421. pass
  422. try:
  423. del self.columnNames
  424. except AttributeError:
  425. pass
  426. fp = fixpickle(parameters)
  427. if verbose > 2:
  428. print(
  429. (
  430. '%s executing "%s" with params=%s'
  431. % (version, operation, repr(parameters))
  432. )
  433. )
  434. result = self.proxy.crsr_execute(self.id, operation, fp)
  435. if result: # an exception was triggered
  436. self._raiseCursorError(result[0], result[1])
  437. def executemany(self, operation, seq_of_parameters):
  438. if self.connection is None:
  439. self._raiseCursorError(
  440. ProgrammingError, "Attempted operation on closed cursor"
  441. )
  442. self.command = operation
  443. try:
  444. del self.description
  445. except AttributeError:
  446. pass
  447. try:
  448. del self.columnNames
  449. except AttributeError:
  450. pass
  451. sq = [fixpickle(x) for x in seq_of_parameters]
  452. if verbose > 2:
  453. print(
  454. (
  455. '%s executemany "%s" with params=%s'
  456. % (version, operation, repr(seq_of_parameters))
  457. )
  458. )
  459. self.proxy.crsr_executemany(self.id, operation, sq)
  460. def nextset(self):
  461. try:
  462. del self.description
  463. except AttributeError:
  464. pass
  465. try:
  466. del self.columnNames
  467. except AttributeError:
  468. pass
  469. if verbose > 2:
  470. print(("%s nextset" % version))
  471. return self.proxy.crsr_nextset(self.id)
  472. def callproc(self, procname, parameters=None):
  473. if self.connection is None:
  474. self._raiseCursorError(
  475. ProgrammingError, "Attempted operation on closed cursor"
  476. )
  477. self.command = procname
  478. try:
  479. del self.description
  480. except AttributeError:
  481. pass
  482. try:
  483. del self.columnNames
  484. except AttributeError:
  485. pass
  486. fp = fixpickle(parameters)
  487. if verbose > 2:
  488. print(
  489. (
  490. '%s callproc "%s" with params=%s'
  491. % (version, procname, repr(parameters))
  492. )
  493. )
  494. return self.proxy.crsr_callproc(self.id, procname, fp)
  495. def fetchone(self):
  496. try:
  497. f1 = self.proxy.crsr_fetchone(self.id)
  498. except _BaseException as e:
  499. self._raiseCursorError(api.DatabaseError, e)
  500. else:
  501. if f1 is None:
  502. return None
  503. self.rs = [f1]
  504. return api.SQLrows(self.rs, 1, self)[
  505. 0
  506. ] # new object to hold the results of the fetch
  507. def fetchmany(self, size=None):
  508. try:
  509. self.rs = self.proxy.crsr_fetchmany(self.id, size)
  510. if not self.rs:
  511. return []
  512. r = api.SQLrows(self.rs, len(self.rs), self)
  513. return r
  514. except Exception as e:
  515. self._raiseCursorError(api.DatabaseError, e)
  516. def fetchall(self):
  517. try:
  518. self.rs = self.proxy.crsr_fetchall(self.id)
  519. if not self.rs:
  520. return []
  521. return api.SQLrows(self.rs, len(self.rs), self)
  522. except Exception as e:
  523. self._raiseCursorError(api.DatabaseError, e)
  524. def close(self):
  525. if self.connection is None:
  526. return
  527. self.connection._i_am_closing(self) # take me off the connection's cursors list
  528. try:
  529. self.proxy.crsr_close(self.id)
  530. except:
  531. pass
  532. try:
  533. del self.description
  534. except:
  535. pass
  536. try:
  537. del self.rs # let go of the recordset
  538. except:
  539. pass
  540. self.connection = (
  541. None # this will make all future method calls on me throw an exception
  542. )
  543. self.proxy = None
  544. if verbose:
  545. print("adodbapi.remote Closed cursor at %X" % id(self))
  546. def __del__(self):
  547. try:
  548. self.close()
  549. except:
  550. pass
  551. def setinputsizes(self, sizes):
  552. pass
  553. def setoutputsize(self, size, column=None):
  554. pass