Skip to content

Commit

Permalink
Implementation of DB-API for BigQuery. (#2921)
Browse files Browse the repository at this point in the history
The `google.cloud.bigquery.dbapi` package covers all of the required implementation
details in the PEP-249 DB-API specification.
  • Loading branch information
tswast authored Jul 12, 2017
1 parent 569e739 commit 68720f6
Show file tree
Hide file tree
Showing 11 changed files with 1,432 additions and 19 deletions.
70 changes: 70 additions & 0 deletions bigquery/google/cloud/bigquery/dbapi/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Copyright 2017 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Google BigQuery implementation of the Database API Specification v2.0.
This module implements the `Python Database API Specification v2.0 (DB-API)`_
for Google BigQuery.
.. _Python Database API Specification v2.0 (DB-API):
https://www.python.org/dev/peps/pep-0249/
.. warning::
The ``dbapi`` module is **alpha**. The implementation is not complete. It
might be changed in backward-incompatible ways and is not subject to any SLA
or deprecation policy.
"""

from google.cloud.bigquery.dbapi.connection import connect
from google.cloud.bigquery.dbapi.connection import Connection
from google.cloud.bigquery.dbapi.cursor import Cursor
from google.cloud.bigquery.dbapi.exceptions import Warning
from google.cloud.bigquery.dbapi.exceptions import Error
from google.cloud.bigquery.dbapi.exceptions import InterfaceError
from google.cloud.bigquery.dbapi.exceptions import DatabaseError
from google.cloud.bigquery.dbapi.exceptions import DataError
from google.cloud.bigquery.dbapi.exceptions import OperationalError
from google.cloud.bigquery.dbapi.exceptions import IntegrityError
from google.cloud.bigquery.dbapi.exceptions import InternalError
from google.cloud.bigquery.dbapi.exceptions import ProgrammingError
from google.cloud.bigquery.dbapi.exceptions import NotSupportedError
from google.cloud.bigquery.dbapi.types import Binary
from google.cloud.bigquery.dbapi.types import Date
from google.cloud.bigquery.dbapi.types import DateFromTicks
from google.cloud.bigquery.dbapi.types import Time
from google.cloud.bigquery.dbapi.types import TimeFromTicks
from google.cloud.bigquery.dbapi.types import Timestamp
from google.cloud.bigquery.dbapi.types import TimestampFromTicks
from google.cloud.bigquery.dbapi.types import BINARY
from google.cloud.bigquery.dbapi.types import DATETIME
from google.cloud.bigquery.dbapi.types import NUMBER
from google.cloud.bigquery.dbapi.types import ROWID
from google.cloud.bigquery.dbapi.types import STRING


apilevel = '2.0'

# Threads may share the module, but not connections.
threadsafety = 1

paramstyle = 'pyformat'

__all__ = [
'apilevel', 'threadsafety', 'paramstyle', 'connect', 'Connection',
'Cursor', 'Warning', 'Error', 'InterfaceError', 'DatabaseError',
'DataError', 'OperationalError', 'IntegrityError', 'InternalError',
'ProgrammingError', 'NotSupportedError', 'Binary', 'Date', 'DateFromTicks',
'Time', 'TimeFromTicks', 'Timestamp', 'TimestampFromTicks', 'BINARY',
'DATETIME', 'NUMBER', 'ROWID', 'STRING',
]
129 changes: 129 additions & 0 deletions bigquery/google/cloud/bigquery/dbapi/_helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
# Copyright 2017 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import collections
import datetime
import numbers
import time

import six

from google.cloud import bigquery
from google.cloud.bigquery.dbapi import exceptions


def wait_for_job(job):
"""Waits for a job to complete by polling until the state is `DONE`.
Sleeps 1 second between calls to the BigQuery API.
:type job: :class:`~google.cloud.bigquery.job._AsyncJob`
:param job: Wait for this job to finish.
:raises: :class:`~google.cloud.bigquery.dbapi.exceptions.DatabaseError`
if the job fails.
"""
while True:
job.reload()
if job.state == 'DONE':
if job.error_result:
raise exceptions.DatabaseError(job.errors)
return
time.sleep(1)


def scalar_to_query_parameter(value, name=None):
"""Convert a scalar value into a query parameter.
:type value: any
:param value: A scalar value to convert into a query parameter.
:type name: str
:param name: (Optional) Name of the query parameter.
:rtype: :class:`~google.cloud.bigquery.ScalarQueryParameter`
:returns:
A query parameter corresponding with the type and value of the plain
Python object.
:raises: :class:`~google.cloud.bigquery.dbapi.exceptions.ProgrammingError`
if the type cannot be determined.
"""
parameter_type = None

if isinstance(value, bool):
parameter_type = 'BOOL'
elif isinstance(value, numbers.Integral):
parameter_type = 'INT64'
elif isinstance(value, numbers.Real):
parameter_type = 'FLOAT64'
elif isinstance(value, six.text_type):
parameter_type = 'STRING'
elif isinstance(value, six.binary_type):
parameter_type = 'BYTES'
elif isinstance(value, datetime.datetime):
parameter_type = 'DATETIME' if value.tzinfo is None else 'TIMESTAMP'
elif isinstance(value, datetime.date):
parameter_type = 'DATE'
elif isinstance(value, datetime.time):
parameter_type = 'TIME'
else:
raise exceptions.ProgrammingError(
'encountered parameter {} with value {} of unexpected type'.format(
name, value))
return bigquery.ScalarQueryParameter(name, parameter_type, value)


def to_query_parameters_list(parameters):
"""Converts a sequence of parameter values into query parameters.
:type parameters: Sequence[Any]
:param parameters: Sequence of query parameter values.
:rtype: List[google.cloud.bigquery._helpers.AbstractQueryParameter]
:returns: A list of query parameters.
"""
return [scalar_to_query_parameter(value) for value in parameters]


def to_query_parameters_dict(parameters):
"""Converts a dictionary of parameter values into query parameters.
:type parameters: Mapping[str, Any]
:param parameters: Dictionary of query parameter values.
:rtype: List[google.cloud.bigquery._helpers.AbstractQueryParameter]
:returns: A list of named query parameters.
"""
return [
scalar_to_query_parameter(value, name=name)
for name, value
in six.iteritems(parameters)]


def to_query_parameters(parameters):
"""Converts DB-API parameter values into query parameters.
:type parameters: Mapping[str, Any] or Sequence[Any]
:param parameters: A dictionary or sequence of query parameter values.
:rtype: List[google.cloud.bigquery._helpers.AbstractQueryParameter]
:returns: A list of query parameters.
"""
if parameters is None:
return []

if isinstance(parameters, collections.Mapping):
return to_query_parameters_dict(parameters)

return to_query_parameters_list(parameters)
58 changes: 58 additions & 0 deletions bigquery/google/cloud/bigquery/dbapi/connection.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Copyright 2017 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Connection for the Google BigQuery DB-API."""

from google.cloud import bigquery
from google.cloud.bigquery.dbapi import cursor


class Connection(object):
"""DB-API Connection to Google BigQuery.
:type client: :class:`~google.cloud.bigquery.Client`
:param client: A client used to connect to BigQuery.
"""
def __init__(self, client):
self._client = client

def close(self):
"""No-op."""

def commit(self):
"""No-op."""

def cursor(self):
"""Return a new cursor object.
:rtype: :class:`~google.cloud.bigquery.dbapi.Cursor`
:returns: A DB-API cursor that uses this connection.
"""
return cursor.Cursor(self)


def connect(client=None):
"""Construct a DB-API connection to Google BigQuery.
:type client: :class:`~google.cloud.bigquery.Client`
:param client:
(Optional) A client used to connect to BigQuery. If not passed, a
client is created using default options inferred from the environment.
:rtype: :class:`~google.cloud.bigquery.dbapi.Connection`
:returns: A new DB-API connection to BigQuery.
"""
if client is None:
client = bigquery.Client()
return Connection(client)
Loading

0 comments on commit 68720f6

Please sign in to comment.