OmniSciDB  340b00dbf6
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
connection.py
Go to the documentation of this file.
1 """
2 
3 Connect to an OmniSci database.
4 """
5 from collections import namedtuple
6 import base64
7 from sqlalchemy.engine.url import make_url
8 from thrift.protocol import TBinaryProtocol, TJSONProtocol
9 from thrift.transport import TSocket, TSSLSocket, THttpClient, TTransport
10 from thrift.transport.TSocket import TTransportException
11 from omnisci.thrift.OmniSci import Client
12 from omnisci.thrift.ttypes import TOmniSciException
13 
14 from .cursor import Cursor
15 from .exceptions import _translate_exception, OperationalError
16 
17 from ._parsers import _extract_column_details
18 
19 from ._samlutils import get_saml_response
20 
21 from packaging.version import Version
22 
23 
24 ConnectionInfo = namedtuple(
25  "ConnectionInfo",
26  [
27  'user',
28  'password',
29  'host',
30  'port',
31  'dbname',
32  'protocol',
33  'bin_cert_validate',
34  'bin_ca_certs',
35  ],
36 )
37 
38 
39 def connect(
40  uri=None,
41  user=None,
42  password=None,
43  host=None,
44  port=6274,
45  dbname=None,
46  protocol='binary',
47  sessionid=None,
48  bin_cert_validate=None,
49  bin_ca_certs=None,
50  idpurl=None,
51  idpformusernamefield='username',
52  idpformpasswordfield='password',
53  idpsslverify=True,
54 ):
55  """
56  Create a new Connection.
57 
58  Parameters
59  ----------
60  uri: str
61  user: str
62  password: str
63  host: str
64  port: int
65  dbname: str
66  protocol: {'binary', 'http', 'https'}
67  sessionid: str
68  bin_cert_validate: bool, optional, binary encrypted connection only
69  Whether to continue if there is any certificate error
70  bin_ca_certs: str, optional, binary encrypted connection only
71  Path to the CA certificate file
72  idpurl : str
73  EXPERIMENTAL Enable SAML authentication by providing
74  the logon page of the SAML Identity Provider.
75  idpformusernamefield: str
76  The HTML form ID for the username, defaults to 'username'.
77  idpformpasswordfield: str
78  The HTML form ID for the password, defaults to 'password'.
79  idpsslverify: str
80  Enable / disable certificate checking, defaults to True.
81 
82  Returns
83  -------
84  conn: Connection
85 
86  Examples
87  --------
88  You can either pass a string ``uri``, all the individual components,
89  or an existing sessionid excluding user, password, and database
90 
91  >>> connect('mapd://admin:HyperInteractive@localhost:6274/omnisci?'
92  ... 'protocol=binary')
93  Connection(mapd://mapd:***@localhost:6274/mapd?protocol=binary)
94 
95  >>> connect(user='admin', password='HyperInteractive', host='localhost',
96  ... port=6274, dbname='omnisci')
97 
98  >>> connect(user='admin', password='HyperInteractive', host='localhost',
99  ... port=443, idpurl='https://sso.localhost/logon',
100  protocol='https')
101 
102  >>> connect(sessionid='XihlkjhdasfsadSDoasdllMweieisdpo', host='localhost',
103  ... port=6273, protocol='http')
104 
105  """
106  return Connection(
107  uri=uri,
108  user=user,
109  password=password,
110  host=host,
111  port=port,
112  dbname=dbname,
113  protocol=protocol,
114  sessionid=sessionid,
115  bin_cert_validate=bin_cert_validate,
116  bin_ca_certs=bin_ca_certs,
117  idpurl=idpurl,
118  idpformusernamefield=idpformusernamefield,
119  idpformpasswordfield=idpformpasswordfield,
120  idpsslverify=idpsslverify,
121  )
122 
123 
124 def _parse_uri(uri):
125  """
126  Parse connection string
127 
128  Parameters
129  ----------
130  uri: str
131  a URI containing connection information
132 
133  Returns
134  -------
135  info: ConnectionInfo
136 
137  Notes
138  ------
139  The URI may include information on
140 
141  - user
142  - password
143  - host
144  - port
145  - dbname
146  - protocol
147  - bin_cert_validate
148  - bin_ca_certs
149  """
150  url = make_url(uri)
151  user = url.username
152  password = url.password
153  host = url.host
154  port = url.port
155  dbname = url.database
156  protocol = url.query.get('protocol', 'binary')
157  bin_cert_validate = url.query.get('bin_cert_validate', None)
158  bin_ca_certs = url.query.get('bin_ca_certs', None)
159 
160  return ConnectionInfo(
161  user,
162  password,
163  host,
164  port,
165  dbname,
166  protocol,
167  bin_cert_validate,
168  bin_ca_certs,
169  )
170 
171 
173  """Connect to your OmniSci database."""
174 
175  def __init__(
176  self,
177  uri=None,
178  user=None,
179  password=None,
180  host=None,
181  port=6274,
182  dbname=None,
183  protocol='binary',
184  sessionid=None,
185  bin_cert_validate=None,
186  bin_ca_certs=None,
187  idpurl=None,
188  idpformusernamefield='username',
189  idpformpasswordfield='password',
190  idpsslverify=True,
191  ):
192 
193  self.sessionid = None
194  self._closed = 0
195  if sessionid is not None:
196  if any([user, password, uri, dbname, idpurl]):
197  raise TypeError(
198  "Cannot specify sessionid with user, password,"
199  " dbname, uri, or idpurl"
200  )
201  if uri is not None:
202  if not all(
203  [
204  user is None,
205  password is None,
206  host is None,
207  port == 6274,
208  dbname is None,
209  protocol == 'binary',
210  bin_cert_validate is None,
211  bin_ca_certs is None,
212  idpurl is None,
213  ]
214  ):
215  raise TypeError("Cannot specify both URI and other arguments")
216  (
217  user,
218  password,
219  host,
220  port,
221  dbname,
222  protocol,
223  bin_cert_validate,
224  bin_ca_certs,
225  ) = _parse_uri(uri)
226  if host is None:
227  raise TypeError("`host` parameter is required.")
228  if protocol != 'binary' and not all(
229  [bin_cert_validate is None, bin_ca_certs is None]
230  ):
231  raise TypeError(
232  "Cannot specify bin_cert_validate or bin_ca_certs,"
233  " without binary protocol"
234  )
235  if protocol in ("http", "https"):
236  if not host.startswith(protocol):
237  # the THttpClient expects http[s]://localhost
238  host = '{0}://{1}'.format(protocol, host)
239  transport = THttpClient.THttpClient("{}:{}".format(host, port))
240  proto = TJSONProtocol.TJSONProtocol(transport)
241  socket = None
242  elif protocol == "binary":
243  if any([bin_cert_validate is not None, bin_ca_certs]):
244  socket = TSSLSocket.TSSLSocket(
245  host,
246  port,
247  validate=(bin_cert_validate),
248  ca_certs=bin_ca_certs,
249  )
250  else:
251  socket = TSocket.TSocket(host, port)
252  transport = TTransport.TBufferedTransport(socket)
253  proto = TBinaryProtocol.TBinaryProtocolAccelerated(transport)
254  else:
255  raise ValueError(
256  "`protocol` should be one of"
257  " ['http', 'https', 'binary'],"
258  " got {} instead".format(protocol),
259  )
260  self._user = user
261  self._password = password
262  self._host = host
263  self._port = port
264  self._dbname = dbname
265  self._transport = transport
266  self._protocol = protocol
267  self._socket = socket
268  self._tdf = None
269  self._rbc = None
270  try:
271  self._transport.open()
272  except TTransportException as e:
273  if e.NOT_OPEN:
274  err = OperationalError("Could not connect to database")
275  raise err from e
276  else:
277  raise
278  self._client = Client(proto)
279  try:
280  # If a sessionid was passed, we should validate it
281  if sessionid:
282  self._session = sessionid
283  self._client.get_tables(self._session)
284  self.sessionid = sessionid
285  else:
286  if idpurl:
287  self._user = ''
289  username=user,
290  password=password,
291  idpurl=idpurl,
292  userformfield=idpformusernamefield,
293  passwordformfield=idpformpasswordfield,
294  sslverify=idpsslverify,
295  )
296  self._dbname = ''
297  self._idpsslverify = idpsslverify
298  user = self._user
299  password = self._password
300  dbname = self._dbname
301 
302  self._session = self._client.connect(user, password, dbname)
303  except TOmniSciException as e:
304  raise _translate_exception(e) from e
305  except TTransportException:
306  raise ValueError(
307  f"Connection failed with port {port} and "
308  f"protocol '{protocol}'. Try port 6274 for "
309  "protocol == binary or 6273, 6278 or 443 for "
310  "http[s]"
311  )
312 
313  # if OmniSci version <4.6, raise RuntimeError, as data import can be
314  # incorrect for columnar date loads
315  # Caused by https://github.com/omnisci/pymapd/pull/188
316  semver = self._client.get_version()
317  if Version(semver.split("-")[0]) < Version("4.6"):
318  raise RuntimeError(
319  f"Version {semver} of OmniSci detected. "
320  "Please use pymapd <0.11. See release notes "
321  "for more details."
322  )
323 
324  def __repr__(self):
325  tpl = (
326  'Connection(omnisci://{user}:***@{host}:{port}/{dbname}?'
327  'protocol={protocol})'
328  )
329  return tpl.format(
330  user=self._user,
331  host=self._host,
332  port=self._port,
333  dbname=self._dbname,
334  protocol=self._protocol,
335  )
336 
337  def __del__(self):
338  self.close()
339 
340  def __enter__(self):
341  return self
342 
343  def __exit__(self, exc_type, exc_val, exc_tb):
344  self.close()
345 
346  @property
347  def closed(self):
348  return self._closed
349 
350  def close(self):
351  """Disconnect from the database unless created with sessionid"""
352  if not self.sessionid and not self._closed:
353  try:
354  self._client.disconnect(self._session)
355  except (TOmniSciException, AttributeError, TypeError):
356  pass
357  self._closed = 1
358  self._rbc = None
359 
360  def commit(self):
361  """This is a noop, as OmniSci does not provide transactions.
362 
363  Implemented to comply with the DBI specification.
364  """
365  return None
366 
367  def execute(self, operation, parameters=None):
368  """Execute a SQL statement
369 
370  Parameters
371  ----------
372  operation: str
373  A SQL statement to exucute
374 
375  Returns
376  -------
377  c: Cursor
378  """
379  c = Cursor(self)
380  return c.execute(operation, parameters=parameters)
381 
382  def cursor(self):
383  """Create a new :class:`Cursor` object attached to this connection."""
384  return Cursor(self)
385 
386  def __call__(self, *args, **kwargs):
387  """Runtime UDF decorator.
388 
389  The connection object can be applied to a Python function as
390  decorator that will add the function to bending registration
391  list.
392  """
393  try:
394  from rbc.omniscidb import RemoteOmnisci
395  except ImportError:
396  raise ImportError("The 'rbc' package is required for `__call__`")
397  if self._rbc is None:
398  self._rbc = RemoteOmnisci(
399  user=self._user,
400  password=self._password,
401  host=self._host,
402  port=self._port,
403  dbname=self._dbname,
404  )
405  self._rbc._session_id = self.sessionid
406  return self._rbc(*args, **kwargs)
407 
409  """Register any bending Runtime UDF functions in OmniSci server.
410 
411  If no Runtime UDFs have been defined, the call to this method
412  is noop.
413  """
414  if self._rbc is not None:
415  self._rbc.register()