diff --git a/bigquery/google/cloud/bigquery/dbapi/__init__.py b/bigquery/google/cloud/bigquery/dbapi/__init__.py index cbc90ea7be5f1..4e9c9a810da48 100644 --- a/bigquery/google/cloud/bigquery/dbapi/__init__.py +++ b/bigquery/google/cloud/bigquery/dbapi/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2016 Google Inc. +# 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. @@ -53,12 +53,12 @@ from google.cloud.bigquery.dbapi.types import STRING -apilevel = "2.0" +apilevel = '2.0' # Threads may share the module, but not connections. threadsafety = 1 -paramstyle = "pyformat" +paramstyle = 'pyformat' __all__ = [ 'apilevel', 'threadsafety', 'paramstyle', 'connect', 'Connection', diff --git a/bigquery/google/cloud/bigquery/dbapi/_helpers.py b/bigquery/google/cloud/bigquery/dbapi/_helpers.py index 86946d83512ac..1a9a02fd7cc7d 100644 --- a/bigquery/google/cloud/bigquery/dbapi/_helpers.py +++ b/bigquery/google/cloud/bigquery/dbapi/_helpers.py @@ -1,4 +1,4 @@ -# Copyright 2016 Google Inc. +# 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. @@ -15,9 +15,10 @@ import collections import datetime import numbers -import six import time +import six + from google.cloud import bigquery from google.cloud.bigquery.dbapi import exceptions @@ -25,10 +26,13 @@ def wait_for_job(job): """Waits for a job to complete by polling until the state is `DONE`. - Raises a DatabaseError if the job fails. + Sleeps 1 second between calls to the BigQuery API. - :type: :class:`~google.cloud.bigquery.job._AsyncJob` + :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() @@ -42,21 +46,18 @@ def wait_for_job(job): def scalar_to_query_parameter(value, name=None): """Convert a scalar value into a query parameter. - Note: You must use the unicode type for string parameters in Python 2. - - Raises a :class:`~ google.cloud.bigquery.dbapi.exceptions.ProgrammingError` - if the type cannot be determined. - - For more information about BigQuery data types, see: - https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types - - :type: any + :type value: any :param value: A scalar value to convert into a query parameter. - :type: str - :param name: Optional name of the 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 @@ -71,7 +72,7 @@ def scalar_to_query_parameter(value, name=None): elif isinstance(value, six.binary_type): parameter_type = 'BYTES' elif isinstance(value, datetime.datetime): - parameter_type = 'TIMESTAMP' if value.tzinfo else 'DATETIME' + parameter_type = 'DATETIME' if value.tzinfo is None else 'TIMESTAMP' elif isinstance(value, datetime.date): parameter_type = 'DATE' elif isinstance(value, datetime.time): @@ -84,13 +85,13 @@ def scalar_to_query_parameter(value, name=None): def to_query_parameters_list(parameters): - """Converts a list of parameter values into query parameters. + """Converts a sequence of parameter values into query parameters. - :type: Sequence[Any] + :type parameters: Sequence[Any] :param parameters: Sequence of query parameter values. - :rtype: - list of :class:`~google.cloud.bigquery._helpers.AbstractQueryParameter` + :rtype: List[google.cloud.bigquery._helpers.AbstractQueryParameter] + :returns: A list of query parameters. """ return [scalar_to_query_parameter(value) for value in parameters] @@ -98,11 +99,11 @@ def to_query_parameters_list(parameters): def to_query_parameters_dict(parameters): """Converts a dictionary of parameter values into query parameters. - :type: Mapping[str, Any] + :type parameters: Mapping[str, Any] :param parameters: Dictionary of query parameter values. - :rtype: - list of :class:`~google.cloud.bigquery._helpers.AbstractQueryParameter` + :rtype: List[google.cloud.bigquery._helpers.AbstractQueryParameter] + :returns: A list of named query parameters. """ return [ scalar_to_query_parameter(value, name=name) @@ -113,14 +114,11 @@ def to_query_parameters_dict(parameters): def to_query_parameters(parameters): """Converts DB-API parameter values into query parameters. - STRUCT/RECORD and REPEATED type parameters are not yet supported. - https://github.com/GoogleCloudPlatform/google-cloud-python/issues/3524 - - :type: Mapping[str, Any] or Sequence[Any] + :type parameters: Mapping[str, Any] or Sequence[Any] :param parameters: A dictionary or sequence of query parameter values. - :rtype: - list of :class:`~google.cloud.bigquery._helpers.AbstractQueryParameter` + :rtype: List[google.cloud.bigquery._helpers.AbstractQueryParameter] + :returns: A list of query parameters. """ if parameters is None: return [] diff --git a/bigquery/google/cloud/bigquery/dbapi/connection.py b/bigquery/google/cloud/bigquery/dbapi/connection.py index 074938dba3d70..66aa0929b97e8 100644 --- a/bigquery/google/cloud/bigquery/dbapi/connection.py +++ b/bigquery/google/cloud/bigquery/dbapi/connection.py @@ -1,4 +1,4 @@ -# Copyright 2016 Google Inc. +# 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. @@ -21,7 +21,7 @@ class Connection(object): """DB-API Connection to Google BigQuery. - :type: :class:`~google.cloud.bigquery.Client` + :type client: :class:`~google.cloud.bigquery.Client` :param client: A client used to connect to BigQuery. """ def __init__(self, client): @@ -37,6 +37,7 @@ 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) @@ -44,12 +45,13 @@ def cursor(self): def connect(client=None): """Construct a DB-API connection to Google BigQuery. - :type: :class:`~google.cloud.bigquery.Client` + :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() diff --git a/bigquery/google/cloud/bigquery/dbapi/cursor.py b/bigquery/google/cloud/bigquery/dbapi/cursor.py index ebafdce620c60..4398eec20b88c 100644 --- a/bigquery/google/cloud/bigquery/dbapi/cursor.py +++ b/bigquery/google/cloud/bigquery/dbapi/cursor.py @@ -1,4 +1,4 @@ -# Copyright 2016 Google Inc. +# 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. @@ -38,7 +38,7 @@ class Cursor(object): """DB-API Cursor to Google BigQuery. - :type: :class:`~google.cloud.bigquery.dbapi.Connection` + :type connection: :class:`~google.cloud.bigquery.dbapi.Connection` :param connection: A DB-API connection to Google BigQuery. """ def __init__(self, connection): @@ -61,7 +61,7 @@ def close(self): def _set_description(self, schema): """Set description from schema. - :type: Sequence[:class:`~google.cloud.bigquery.schema.SchemaField`] + :type schema: Sequence[google.cloud.bigquery.schema.SchemaField] :param schema: A description of fields in the schema. """ if schema is None: @@ -86,7 +86,8 @@ def _set_rowcount(self, query_results): query, but if it was a DML statement, it sets rowcount to the number of modified rows. - :type: :class:`~google.cloud.bigquery.query.QueryResults` + :type query_results: + :class:`~google.cloud.bigquery.query.QueryResults` :param query_results: results of a query """ total_rows = 0 @@ -99,74 +100,34 @@ def _set_rowcount(self, query_results): total_rows = num_dml_affected_rows self.rowcount = total_rows - def _format_operation_list(self, operation, parameters): - """Formats parameters in operation in the way BigQuery expects. - - The input operation will be a query like ``SELECT %s`` and the output - will be a query like ``SELECT ?``. - - :type: str - :param operation: A Google BigQuery query string. - - :type: Sequence[Any] - :param parameters: Sequence of parameter values. - """ - formatted_params = ['?' for _ in parameters] - - try: - return operation % tuple(formatted_params) - except TypeError as ex: - raise exceptions.ProgrammingError(ex) - - def _format_operation_dict(self, operation, parameters): - """Formats parameters in operation in the way BigQuery expects. - - The input operation will be a query like ``SELECT %(namedparam)s`` and - the output will be a query like ``SELECT @namedparam``. - - :type: str - :param operation: A Google BigQuery query string. - - :type: Mapping[str, Any] - :param parameters: Dictionary of parameter values. - """ - formatted_params = {} - for name in parameters: - formatted_params[name] = '@{}'.format(name) - - try: - return operation % formatted_params - except KeyError as ex: - raise exceptions.ProgrammingError(ex) - - def _format_operation(self, operation, parameters=None): - """Formats parameters in operation in way BigQuery expects. - - Raises a ProgrammingError if a parameter used in the operation is not - found in the parameters dictionary. - - :type: str - :param operation: A Google BigQuery query string. + def execute(self, operation, parameters=None): + """Prepare and execute a database operation. - :type: Mapping[str, Any] or Sequence[Any] - :param parameters: Optional parameter values. - """ - if parameters is None: - return operation + .. note:: + When setting query parameters, values which are "text" + (``unicode`` in Python2, ``str`` in Python3) will use + the 'STRING' BigQuery type. Values which are "bytes" (``str`` in + Python2, ``bytes`` in Python3), will use using the 'BYTES' type. - if isinstance(parameters, collections.Mapping): - return self._format_operation_dict(operation, parameters) + A `~datetime.datetime` parameter without timezone information uses + the 'DATETIME' BigQuery type (example: Global Pi Day Celebration + March 14, 2017 at 1:59pm). A `~datetime.datetime` parameter with + timezone information uses the 'TIMESTAMP' BigQuery type (example: + a wedding on April 29, 2011 at 11am, British Summer Time). - return self._format_operation_list(operation, parameters) + For more information about BigQuery data types, see: + https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types - def execute(self, operation, parameters=None): - """Prepare and execute a database operation. + ``STRUCT``/``RECORD`` and ``REPEATED`` query parameters are not + yet supported. See: + https://github.com/GoogleCloudPlatform/google-cloud-python/issues/3524 - :type: str + :type operation: str :param operation: A Google BigQuery query string. - :type: Mapping[str, Any] or Sequence[Any] - :param parameters: Optional dictionary or sequence of parameter values. + :type parameters: Mapping[str, Any] or Sequence[Any] + :param parameters: + (Optional) dictionary or sequence of parameter values. """ self._query_results = None self._page_token = None @@ -178,7 +139,7 @@ def execute(self, operation, parameters=None): # query parameters was not one of the standard options. Convert both # the query and the parameters to the format expected by the client # libraries. - formatted_operation = self._format_operation( + formatted_operation = _format_operation( operation, parameters=parameters) query_parameters = _helpers.to_query_parameters(parameters) @@ -208,10 +169,10 @@ def execute(self, operation, parameters=None): def executemany(self, operation, seq_of_parameters): """Prepare and execute a database operation multiple times. - :type: str + :type operation: str :param operation: A Google BigQuery query string. - :type: Sequence[Mapping[str, Any] or Sequence[Any]] + :type seq_of_parameters: Sequence[Mapping[str, Any] or Sequence[Any]] :param parameters: Sequence of many sets of parameter values. """ for parameters in seq_of_parameters: @@ -224,6 +185,8 @@ def fetchone(self): :returns: A tuple representing a row or ``None`` if no more data is available. + :raises: :class:`~google.cloud.bigquery.dbapi.InterfaceError` + if called before ``execute()``. """ if self._query_data is None: raise exceptions.InterfaceError( @@ -237,17 +200,20 @@ def fetchone(self): def fetchmany(self, size=None): """Fetch multiple results from the last ``execute*()`` call. - Note that the size parameter is not used for the request/response size. - Set the ``arraysize`` attribute before calling ``execute()`` to set the - batch size. + .. note:: + The size parameter is not used for the request/response size. + Set the ``arraysize`` attribute before calling ``execute()`` to + set the batch size. - :type: int + :type size: int :param size: (Optional) Maximum number of rows to return. Defaults to the ``arraysize`` property value. - :rtype: Sequence[tuple] + :rtype: List[tuple] :returns: A list of rows. + :raises: :class:`~google.cloud.bigquery.dbapi.InterfaceError` + if called before ``execute()``. """ if self._query_data is None: raise exceptions.InterfaceError( @@ -265,8 +231,10 @@ def fetchmany(self, size=None): def fetchall(self): """Fetch all remaining results from the last ``execute*()`` call. - :rtype: Sequence[tuple] + :rtype: List[tuple] :returns: A list of all the rows in the results. + :raises: :class:`~google.cloud.bigquery.dbapi.InterfaceError` + if called before ``execute()``. """ if self._query_data is None: raise exceptions.InterfaceError( @@ -278,3 +246,82 @@ def setinputsizes(self, sizes): def setoutputsize(self, size, column=None): """No-op.""" + + +def _format_operation_list(operation, parameters): + """Formats parameters in operation in the way BigQuery expects. + + The input operation will be a query like ``SELECT %s`` and the output + will be a query like ``SELECT ?``. + + :type operation: str + :param operation: A Google BigQuery query string. + + :type parameters: Sequence[Any] + :param parameters: Sequence of parameter values. + + :rtype: str + :returns: A formatted query string. + :raises: :class:`~google.cloud.bigquery.dbapi.ProgrammingError` + if a parameter used in the operation is not found in the + ``parameters`` argument. + """ + formatted_params = ['?' for _ in parameters] + + try: + return operation % tuple(formatted_params) + except TypeError as exc: + raise exceptions.ProgrammingError(exc) + + +def _format_operation_dict(operation, parameters): + """Formats parameters in operation in the way BigQuery expects. + + The input operation will be a query like ``SELECT %(namedparam)s`` and + the output will be a query like ``SELECT @namedparam``. + + :type operation: str + :param operation: A Google BigQuery query string. + + :type parameters: Mapping[str, Any] + :param parameters: Dictionary of parameter values. + + :rtype: str + :returns: A formatted query string. + :raises: :class:`~google.cloud.bigquery.dbapi.ProgrammingError` + if a parameter used in the operation is not found in the + ``parameters`` argument. + """ + formatted_params = {} + for name in parameters: + escaped_name = name.replace('`', r'\`') + formatted_params[name] = '@`{}`'.format(escaped_name) + + try: + return operation % formatted_params + except KeyError as exc: + raise exceptions.ProgrammingError(exc) + + +def _format_operation(operation, parameters=None): + """Formats parameters in operation in way BigQuery expects. + + :type: str + :param operation: A Google BigQuery query string. + + :type: Mapping[str, Any] or Sequence[Any] + :param parameters: Optional parameter values. + + :rtype: str + :returns: A formatted query string. + :raises: :class:`~google.cloud.bigquery.dbapi.ProgrammingError` + if a parameter used in the operation is not found in the + ``parameters`` argument. + """ + if parameters is None: + return operation + + if isinstance(parameters, collections.Mapping): + return _format_operation_dict(operation, parameters) + + return _format_operation_list(operation, parameters) diff --git a/bigquery/google/cloud/bigquery/dbapi/exceptions.py b/bigquery/google/cloud/bigquery/dbapi/exceptions.py index 769fd09bd485a..77494e5ff1e13 100644 --- a/bigquery/google/cloud/bigquery/dbapi/exceptions.py +++ b/bigquery/google/cloud/bigquery/dbapi/exceptions.py @@ -1,4 +1,4 @@ -# Copyright 2016 Google Inc. +# 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. diff --git a/bigquery/google/cloud/bigquery/dbapi/types.py b/bigquery/google/cloud/bigquery/dbapi/types.py index 12980a4d5daf1..2d06f260e360c 100644 --- a/bigquery/google/cloud/bigquery/dbapi/types.py +++ b/bigquery/google/cloud/bigquery/dbapi/types.py @@ -14,23 +14,32 @@ """Types used in the Google BigQuery DB-API. -See `PEP 249`_ for details. +See `PEP-249`_ for details. -.. _`PEP 249`: +.. _PEP-249: https://www.python.org/dev/peps/pep-0249/#type-objects-and-constructors """ import datetime -import six - Date = datetime.date Time = datetime.time Timestamp = datetime.datetime DateFromTicks = datetime.date.fromtimestamp TimestampFromTicks = datetime.datetime.fromtimestamp -Binary = six.binary_type + + +def Binary(string): + """Contruct a DB-API binary value. + + :type string: str + :param string: A string to encode as a binary value. + + :rtype: bytes + :returns: The UTF-8 encoded bytes representing the string. + """ + return string.encode('utf-8') def TimeFromTicks(ticks, tz=None): @@ -54,9 +63,9 @@ def TimeFromTicks(ticks, tz=None): class _DBAPITypeObject(object): """DB-API type object which compares equal to many different strings. - See `PEP 249`_ for details. + See `PEP-249`_ for details. - .. _`PEP 249`: + .. _PEP-249: https://www.python.org/dev/peps/pep-0249/#implementation-hints-for-module-authors """ @@ -64,9 +73,7 @@ def __init__(self, *values): self.values = values def __eq__(self, other): - if other in self.values: - return True - return False + return other in self.values STRING = 'STRING' diff --git a/bigquery/tests/system.py b/bigquery/tests/system.py index e2d3be3485a8c..3391ec2bd2d86 100644 --- a/bigquery/tests/system.py +++ b/bigquery/tests/system.py @@ -76,7 +76,7 @@ class Config(object): def setUpModule(): Config.CLIENT = bigquery.Client() - Config.CURSOR = dbapi.connect().cursor() + Config.CURSOR = dbapi.connect(Config.CLIENT).cursor() class TestBigQuery(unittest.TestCase): @@ -379,9 +379,6 @@ def test_load_table_from_local_file_then_dump_table(self): write_disposition='WRITE_EMPTY', ) - def _job_done(instance): - return instance.state.lower() == 'done' - # Retry until done. retry = RetryInstanceState(_job_done, max_tries=8) retry(job.reload)() @@ -420,9 +417,6 @@ def test_load_table_from_local_avro_file_then_dump_table(self): write_disposition='WRITE_TRUNCATE' ) - def _job_done(instance): - return instance.state.lower() == 'done' - # Retry until done. retry = RetryInstanceState(_job_done, max_tries=8) retry(job.reload)() @@ -495,9 +489,6 @@ def test_load_table_from_storage_then_dump_table(self): job.begin() - def _job_done(instance): - return instance.state in ('DONE', 'done') - # Allow for 90 seconds of "warm up" before rows visible. See # https://cloud.google.com/bigquery/streaming-data-into-bigquery#dataavailability # 8 tries -> 1 + 2 + 4 + 8 + 16 + 32 + 64 = 127 seconds @@ -531,9 +522,6 @@ def test_job_cancel(self): job.begin() job.cancel() - def _job_done(instance): - return instance.state in ('DONE', 'done') - retry = RetryInstanceState(_job_done, max_tries=8) retry(job.reload)() @@ -701,7 +689,7 @@ def _load_table_for_dml(self, rows, dataset_name, table_name): with _NamedTemporaryFile() as temp: with open(temp.name, 'w') as csv_write: writer = csv.writer(csv_write) - writer.writerow(('Greeting')) + writer.writerow(('Greeting',)) writer.writerows(rows) with open(temp.name, 'rb') as csv_read: @@ -713,9 +701,6 @@ def _load_table_for_dml(self, rows, dataset_name, table_name): write_disposition='WRITE_EMPTY', ) - def _job_done(instance): - return instance.state.lower() == 'done' - # Retry until done. retry = RetryInstanceState(_job_done, max_tries=8) retry(job.reload)() @@ -725,12 +710,13 @@ def test_sync_query_w_dml(self): dataset_name = _make_dataset_name('dml_tests') table_name = 'test_table' self._load_table_for_dml([('Hello World',)], dataset_name, table_name) + query_template = """UPDATE {}.{} + SET greeting = 'Guten Tag' + WHERE greeting = 'Hello World' + """ query = Config.CLIENT.run_sync_query( - 'UPDATE {}.{} ' - 'SET greeting = \'Guten Tag\' ' - 'WHERE greeting = \'Hello World\''.format( - dataset_name, table_name)) + query_template.format(dataset_name, table_name)) query.use_legacy_sql = False query.run() @@ -740,12 +726,12 @@ def test_dbapi_w_dml(self): dataset_name = _make_dataset_name('dml_tests') table_name = 'test_table' self._load_table_for_dml([('Hello World',)], dataset_name, table_name) + query_template = """UPDATE {}.{} + SET greeting = 'Guten Tag' + WHERE greeting = 'Hello World' + """ - Config.CURSOR.execute( - 'UPDATE {}.{} ' - 'SET greeting = \'Guten Tag\' ' - 'WHERE greeting = \'Hello World\''.format( - dataset_name, table_name)) + Config.CURSOR.execute(query_template.format(dataset_name, table_name)) self.assertEqual(Config.CURSOR.rowcount, 1) self.assertIsNone(Config.CURSOR.fetchone()) @@ -910,6 +896,20 @@ def test_dbapi_w_query_parameters(self): 'boolval': True, }, }, + { + 'sql': 'SELECT %(a "very" weird `name`)s', + 'expected': True, + 'query_parameters': { + 'a "very" weird `name`': True, + }, + }, + { + 'sql': 'SELECT %(select)s', + 'expected': True, + 'query_parameters': { + 'select': True, # this name is a keyword + }, + }, { 'sql': 'SELECT %s', 'expected': False, @@ -977,11 +977,7 @@ def test_dbapi_w_query_parameters(self): msg = 'sql: {} query_parameters: {}'.format( example['sql'], example['query_parameters']) - try: - Config.CURSOR.execute( - example['sql'], example['query_parameters']) - except dbapi.DatabaseError as ex: - raise dbapi.DatabaseError('{} {}'.format(ex, msg)) + Config.CURSOR.execute(example['sql'], example['query_parameters']) self.assertEqual(Config.CURSOR.rowcount, 1, msg=msg) row = Config.CURSOR.fetchone() @@ -1121,3 +1117,7 @@ def test_create_table_insert_fetch_nested_schema(self): parts = time.strptime(expected[7], '%Y-%m-%dT%H:%M:%S') e_favtime = datetime.datetime(*parts[0:6]) self.assertEqual(found[7], e_favtime) # FavoriteTime + + +def _job_done(instance): + return instance.state.lower() == 'done' diff --git a/bigquery/tests/unit/test_dbapi__helpers.py b/bigquery/tests/unit/test_dbapi__helpers.py index 8a16548c6cb4c..e030ed49df0c4 100644 --- a/bigquery/tests/unit/test_dbapi__helpers.py +++ b/bigquery/tests/unit/test_dbapi__helpers.py @@ -1,4 +1,4 @@ -# Copyright 2016 Google Inc. +# 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. diff --git a/bigquery/tests/unit/test_dbapi_connection.py b/bigquery/tests/unit/test_dbapi_connection.py index d708c4954c989..d30852377852a 100644 --- a/bigquery/tests/unit/test_dbapi_connection.py +++ b/bigquery/tests/unit/test_dbapi_connection.py @@ -1,4 +1,4 @@ -# Copyright 2016 Google Inc. +# 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. diff --git a/bigquery/tests/unit/test_dbapi_cursor.py b/bigquery/tests/unit/test_dbapi_cursor.py index 73d74f0f559ae..901d2f176785f 100644 --- a/bigquery/tests/unit/test_dbapi_cursor.py +++ b/bigquery/tests/unit/test_dbapi_cursor.py @@ -1,4 +1,4 @@ -# Copyright 2016 Google Inc. +# 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. @@ -231,22 +231,19 @@ def test_executemany_w_dml(self): self.assertEquals(cursor.rowcount, 12) def test__format_operation_w_dict(self): - from google.cloud.bigquery import dbapi - connection = dbapi.connect(self._mock_client()) - cursor = connection.cursor() + from google.cloud.bigquery.dbapi import cursor formatted_operation = cursor._format_operation( - 'SELECT %(somevalue)s, %(othervalue)s;', + 'SELECT %(somevalue)s, %(a `weird` one)s;', { 'somevalue': 'hi', - 'othervalue': 'world', + 'a `weird` one': 'world', }) self.assertEquals( - formatted_operation, 'SELECT @somevalue, @othervalue;') + formatted_operation, 'SELECT @`somevalue`, @`a \\`weird\\` one`;') def test__format_operation_w_wrong_dict(self): from google.cloud.bigquery import dbapi - connection = dbapi.connect(self._mock_client()) - cursor = connection.cursor() + from google.cloud.bigquery.dbapi import cursor self.assertRaises( dbapi.ProgrammingError, cursor._format_operation, @@ -257,17 +254,14 @@ def test__format_operation_w_wrong_dict(self): }) def test__format_operation_w_sequence(self): - from google.cloud.bigquery import dbapi - connection = dbapi.connect(self._mock_client()) - cursor = connection.cursor() + from google.cloud.bigquery.dbapi import cursor formatted_operation = cursor._format_operation( 'SELECT %s, %s;', ('hello', 'world')) self.assertEquals(formatted_operation, 'SELECT ?, ?;') def test__format_operation_w_too_short_sequence(self): from google.cloud.bigquery import dbapi - connection = dbapi.connect(self._mock_client()) - cursor = connection.cursor() + from google.cloud.bigquery.dbapi import cursor self.assertRaises( dbapi.ProgrammingError, cursor._format_operation, diff --git a/bigquery/tests/unit/test_dbapi_types.py b/bigquery/tests/unit/test_dbapi_types.py index 3120537e801ec..afd45b259263e 100644 --- a/bigquery/tests/unit/test_dbapi_types.py +++ b/bigquery/tests/unit/test_dbapi_types.py @@ -26,6 +26,10 @@ def test_binary_type(self): self.assertEqual('STRUCT', types.BINARY) self.assertNotEqual('STRING', types.BINARY) + def test_binary_constructor(self): + self.assertEqual(types.Binary(u'hello'), b'hello') + self.assertEqual(types.Binary(u'\u1f60'), u'\u1f60'.encode('utf-8')) + def test_timefromticks(self): somedatetime = datetime.datetime( 2017, 2, 18, 12, 47, 26, tzinfo=google.cloud._helpers.UTC)