diff --git a/bigquery/google/cloud/bigquery/client.py b/bigquery/google/cloud/bigquery/client.py index 2ae577a51708..5ab8ff820764 100644 --- a/bigquery/google/cloud/bigquery/client.py +++ b/bigquery/google/cloud/bigquery/client.py @@ -18,6 +18,7 @@ from google.cloud.client import ClientWithProject from google.cloud.bigquery._http import Connection from google.cloud.bigquery.dataset import Dataset +from google.cloud.bigquery.dataset import DatasetReference from google.cloud.bigquery.job import CopyJob from google.cloud.bigquery.job import ExtractJob from google.cloud.bigquery.job import LoadJob @@ -148,7 +149,7 @@ def list_datasets(self, include_all=False, max_results=None, extra_params=extra_params) def dataset(self, dataset_name, project=None): - """Construct a dataset bound to this client. + """Construct a reference to a dataset. :type dataset_name: str :param dataset_name: Name of the dataset. @@ -157,10 +158,13 @@ def dataset(self, dataset_name, project=None): :param project: (Optional) project ID for the dataset (defaults to the project of the client). - :rtype: :class:`google.cloud.bigquery.dataset.Dataset` - :returns: a new ``Dataset`` instance + :rtype: :class:`google.cloud.bigquery.dataset.DatasetReference` + :returns: a new ``DatasetReference`` instance """ - return Dataset(dataset_name, client=self, project=project) + if project is None: + project = self.project + + return DatasetReference(project, dataset_name) def _get_query_results(self, job_id, project=None, timeout_ms=None): """Get the query results object for a query job. diff --git a/bigquery/google/cloud/bigquery/dataset.py b/bigquery/google/cloud/bigquery/dataset.py index f4e6fd519e38..e31b4a2a93b1 100644 --- a/bigquery/google/cloud/bigquery/dataset.py +++ b/bigquery/google/cloud/bigquery/dataset.py @@ -19,6 +19,7 @@ from google.cloud._helpers import _datetime_from_microseconds from google.cloud.exceptions import NotFound from google.cloud.bigquery.table import Table +from google.cloud.bigquery.table import TableReference class AccessEntry(object): @@ -126,6 +127,14 @@ def dataset_id(self): """ return self._dataset_id + def table(self, table_id): + """Constructs a TableReference. + + :rtype: :class:`google.cloud.bigquery.table.TableReference` + :returns: a TableReference for a table in this dataset. + """ + return TableReference(self, table_id) + class Dataset(object): """Datasets are containers for tables. diff --git a/bigquery/google/cloud/bigquery/job.py b/bigquery/google/cloud/bigquery/job.py index f060ba1bc2f9..6f5c2c294a0c 100644 --- a/bigquery/google/cloud/bigquery/job.py +++ b/bigquery/google/cloud/bigquery/job.py @@ -1362,8 +1362,8 @@ def _copy_configuration_properties(self, configuration): dest_local = self._destination_table_resource() if dest_remote != dest_local: project = dest_remote['projectId'] - dataset = self._client.dataset( - dest_remote['datasetId'], project=project) + dataset = Dataset( + dest_remote['datasetId'], self._client, project=project) self.destination = dataset.table(dest_remote['tableId']) def_ds = configuration.get('defaultDataset') @@ -1372,7 +1372,7 @@ def _copy_configuration_properties(self, configuration): del self.default_dataset else: project = def_ds['projectId'] - self.default_dataset = self._client.dataset(def_ds['datasetId']) + self.default_dataset = Dataset(def_ds['datasetId'], self._client) udf_resources = [] for udf_mapping in configuration.get(self._UDF_KEY, ()): @@ -1528,7 +1528,7 @@ def referenced_tables(self): ds_name = table['datasetId'] t_dataset = datasets_by_project_name.get((t_project, ds_name)) if t_dataset is None: - t_dataset = client.dataset(ds_name, project=t_project) + t_dataset = Dataset(ds_name, client, project=t_project) datasets_by_project_name[(t_project, ds_name)] = t_dataset t_name = table['tableId'] diff --git a/bigquery/google/cloud/bigquery/table.py b/bigquery/google/cloud/bigquery/table.py index 2a3dfd50059a..3a66db152faa 100644 --- a/bigquery/google/cloud/bigquery/table.py +++ b/bigquery/google/cloud/bigquery/table.py @@ -48,6 +48,42 @@ _DEFAULT_NUM_RETRIES = 6 +class TableReference(object): + """TableReferences are pointers to tables. + + See + https://cloud.google.com/bigquery/docs/reference/rest/v2/tables + + :type dataset_ref: :class:`google.cloud.bigquery.dataset.DatasetReference` + :param dataset_ref: a pointer to the dataset + + :type table_id: str + :param table_id: the ID of the table + """ + + def __init__(self, dataset_ref, table_id): + self._dataset_ref = dataset_ref + self._table_id = table_id + + @property + def dataset_ref(self): + """Pointer to the dataset. + + :rtype: :class:`google.cloud.bigquery.dataset.DatasetReference` + :returns: a pointer to the dataset. + """ + return self._dataset_ref + + @property + def table_id(self): + """Table ID. + + :rtype: str + :returns: the table ID. + """ + return self._table_id + + class Table(object): """Tables represent a set of rows whose values correspond to a schema. diff --git a/bigquery/tests/system.py b/bigquery/tests/system.py index 1974418461ce..392f1d04af2d 100644 --- a/bigquery/tests/system.py +++ b/bigquery/tests/system.py @@ -25,6 +25,7 @@ import six from google.cloud import bigquery +from google.cloud.bigquery.dataset import Dataset from google.cloud._helpers import UTC from google.cloud.bigquery import dbapi from google.cloud.exceptions import Forbidden @@ -111,7 +112,7 @@ def _still_in_use(bad_request): def test_create_dataset(self): DATASET_NAME = _make_dataset_name('create_dataset') - dataset = Config.CLIENT.dataset(DATASET_NAME) + dataset = Dataset(DATASET_NAME, Config.CLIENT) self.assertFalse(dataset.exists()) retry_403(dataset.create)() @@ -122,20 +123,20 @@ def test_create_dataset(self): def test_reload_dataset(self): DATASET_NAME = _make_dataset_name('reload_dataset') - dataset = Config.CLIENT.dataset(DATASET_NAME) + dataset = Dataset(DATASET_NAME, Config.CLIENT) dataset.friendly_name = 'Friendly' dataset.description = 'Description' retry_403(dataset.create)() self.to_delete.append(dataset) - other = Config.CLIENT.dataset(DATASET_NAME) + other = Dataset(DATASET_NAME, Config.CLIENT) other.reload() self.assertEqual(other.friendly_name, 'Friendly') self.assertEqual(other.description, 'Description') def test_patch_dataset(self): - dataset = Config.CLIENT.dataset(_make_dataset_name('patch_dataset')) + dataset = Dataset(_make_dataset_name('patch_dataset'), Config.CLIENT) self.assertFalse(dataset.exists()) retry_403(dataset.create)() @@ -149,7 +150,7 @@ def test_patch_dataset(self): self.assertEqual(dataset.description, 'Description') def test_update_dataset(self): - dataset = Config.CLIENT.dataset(_make_dataset_name('update_dataset')) + dataset = Dataset(_make_dataset_name('update_dataset'), Config.CLIENT) self.assertFalse(dataset.exists()) retry_403(dataset.create)() @@ -175,7 +176,7 @@ def test_list_datasets(self): 'newest' + unique_resource_id(), ] for dataset_name in datasets_to_create: - created_dataset = Config.CLIENT.dataset(dataset_name) + created_dataset = Dataset(dataset_name, Config.CLIENT) retry_403(created_dataset.create)() self.to_delete.append(created_dataset) @@ -189,7 +190,7 @@ def test_list_datasets(self): self.assertEqual(len(created), len(datasets_to_create)) def test_create_table(self): - dataset = Config.CLIENT.dataset(_make_dataset_name('create_table')) + dataset = Dataset(_make_dataset_name('create_table'), Config.CLIENT) self.assertFalse(dataset.exists()) retry_403(dataset.create)() @@ -208,7 +209,7 @@ def test_create_table(self): def test_list_tables(self): DATASET_NAME = _make_dataset_name('list_tables') - dataset = Config.CLIENT.dataset(DATASET_NAME) + dataset = Dataset(DATASET_NAME, Config.CLIENT) self.assertFalse(dataset.exists()) retry_403(dataset.create)() @@ -244,7 +245,7 @@ def test_list_tables(self): self.assertEqual(len(created), len(tables_to_create)) def test_patch_table(self): - dataset = Config.CLIENT.dataset(_make_dataset_name('patch_table')) + dataset = Dataset(_make_dataset_name('patch_table'), Config.CLIENT) self.assertFalse(dataset.exists()) retry_403(dataset.create)() @@ -266,7 +267,7 @@ def test_patch_table(self): self.assertEqual(table.description, 'Description') def test_update_table(self): - dataset = Config.CLIENT.dataset(_make_dataset_name('update_table')) + dataset = Dataset(_make_dataset_name('update_table'), Config.CLIENT) self.assertFalse(dataset.exists()) retry_403(dataset.create)() @@ -309,8 +310,8 @@ def test_insert_data_then_dump_table(self): ('Bhettye Rhubble', 27, None), ] ROW_IDS = range(len(ROWS)) - dataset = Config.CLIENT.dataset( - _make_dataset_name('insert_data_then_dump')) + dataset = Dataset( + _make_dataset_name('insert_data_then_dump'), Config.CLIENT) self.assertFalse(dataset.exists()) retry_403(dataset.create)() @@ -353,8 +354,8 @@ def test_load_table_from_local_file_then_dump_table(self): ] TABLE_NAME = 'test_table' - dataset = Config.CLIENT.dataset( - _make_dataset_name('load_local_then_dump')) + dataset = Dataset( + _make_dataset_name('load_local_then_dump'), Config.CLIENT) retry_403(dataset.create)() self.to_delete.append(dataset) @@ -402,8 +403,8 @@ def test_load_table_from_local_avro_file_then_dump_table(self): ("orange", 590), ("red", 650)] - dataset = Config.CLIENT.dataset( - _make_dataset_name('load_local_then_dump')) + dataset = Dataset( + _make_dataset_name('load_local_then_dump'), Config.CLIENT) retry_403(dataset.create)() self.to_delete.append(dataset) @@ -466,8 +467,8 @@ def test_load_table_from_storage_then_dump_table(self): self.to_delete.insert(0, blob) - dataset = Config.CLIENT.dataset( - _make_dataset_name('load_gcs_then_dump')) + dataset = Dataset( + _make_dataset_name('load_gcs_then_dump'), Config.CLIENT) retry_403(dataset.create)() self.to_delete.append(dataset) @@ -536,8 +537,8 @@ def test_load_table_from_storage_w_autodetect_schema(self): self.to_delete.insert(0, blob) - dataset = Config.CLIENT.dataset( - _make_dataset_name('load_gcs_then_dump')) + dataset = Dataset( + _make_dataset_name('load_gcs_then_dump'), Config.CLIENT) retry_403(dataset.create)() self.to_delete.append(dataset) @@ -574,7 +575,7 @@ def test_job_cancel(self): TABLE_NAME = 'test_table' QUERY = 'SELECT * FROM %s.%s' % (DATASET_NAME, TABLE_NAME) - dataset = Config.CLIENT.dataset(DATASET_NAME) + dataset = Dataset(DATASET_NAME, Config.CLIENT) retry_403(dataset.create)() self.to_delete.append(dataset) @@ -765,7 +766,7 @@ def test_dbapi_fetchall(self): def _load_table_for_dml(self, rows, dataset_name, table_name): from google.cloud._testing import _NamedTemporaryFile - dataset = Config.CLIENT.dataset(dataset_name) + dataset = Dataset(dataset_name, Config.CLIENT) retry_403(dataset.create)() self.to_delete.append(dataset) @@ -1081,7 +1082,7 @@ def test_dump_table_w_public_data(self): DATASET_NAME = 'samples' TABLE_NAME = 'natality' - dataset = Config.CLIENT.dataset(DATASET_NAME, project=PUBLIC) + dataset = Dataset(DATASET_NAME, Config.CLIENT, project=PUBLIC) table = dataset.table(TABLE_NAME) # Reload table to get the schema before fetching the rows. table.reload() @@ -1134,8 +1135,8 @@ def test_insert_nested_nested(self): ('Some value', record) ] table_name = 'test_table' - dataset = Config.CLIENT.dataset( - _make_dataset_name('issue_2951')) + dataset = Dataset( + _make_dataset_name('issue_2951'), Config.CLIENT) retry_403(dataset.create)() self.to_delete.append(dataset) @@ -1154,8 +1155,8 @@ def test_insert_nested_nested(self): def test_create_table_insert_fetch_nested_schema(self): table_name = 'test_table' - dataset = Config.CLIENT.dataset( - _make_dataset_name('create_table_nested_schema')) + dataset = Dataset( + _make_dataset_name('create_table_nested_schema'), Config.CLIENT) self.assertFalse(dataset.exists()) retry_403(dataset.create)() diff --git a/bigquery/tests/unit/test_client.py b/bigquery/tests/unit/test_client.py index 3cd4a24ceb43..70e1f1eea7c7 100644 --- a/bigquery/tests/unit/test_client.py +++ b/bigquery/tests/unit/test_client.py @@ -246,8 +246,21 @@ def test_list_datasets_explicit_response_missing_datasets_key(self): self.assertEqual(req['query_params'], {'all': True, 'maxResults': 3, 'pageToken': TOKEN}) - def test_dataset(self): - from google.cloud.bigquery.dataset import Dataset + def test_dataset_with_specified_project(self): + from google.cloud.bigquery.dataset import DatasetReference + + PROJECT = 'PROJECT' + DATASET = 'dataset_name' + creds = _make_credentials() + http = object() + client = self._make_one(project=PROJECT, credentials=creds, _http=http) + dataset = client.dataset(DATASET, PROJECT) + self.assertIsInstance(dataset, DatasetReference) + self.assertEqual(dataset.dataset_id, DATASET) + self.assertEqual(dataset.project_id, PROJECT) + + def test_dataset_with_default_project(self): + from google.cloud.bigquery.dataset import DatasetReference PROJECT = 'PROJECT' DATASET = 'dataset_name' @@ -255,9 +268,9 @@ def test_dataset(self): http = object() client = self._make_one(project=PROJECT, credentials=creds, _http=http) dataset = client.dataset(DATASET) - self.assertIsInstance(dataset, Dataset) - self.assertEqual(dataset.name, DATASET) - self.assertIs(dataset._client, client) + self.assertIsInstance(dataset, DatasetReference) + self.assertEqual(dataset.dataset_id, DATASET) + self.assertEqual(dataset.project_id, PROJECT) def test_job_from_resource_unknown_type(self): PROJECT = 'PROJECT' diff --git a/bigquery/tests/unit/test_dataset.py b/bigquery/tests/unit/test_dataset.py index 4da2ada5de66..c509be6838a1 100644 --- a/bigquery/tests/unit/test_dataset.py +++ b/bigquery/tests/unit/test_dataset.py @@ -101,6 +101,12 @@ def test_ctor_defaults(self): self.assertEqual(dataset_ref.project_id, 'some-project-1') self.assertEqual(dataset_ref.dataset_id, 'dataset_1') + def test_table(self): + dataset_ref = self._make_one('some-project-1', 'dataset_1') + table_ref = dataset_ref.table('table_1') + self.assertIs(table_ref.dataset_ref, dataset_ref) + self.assertEqual(table_ref.table_id, 'table_1') + class TestDataset(unittest.TestCase): PROJECT = 'project' diff --git a/bigquery/tests/unit/test_job.py b/bigquery/tests/unit/test_job.py index 23fb95eea123..a4b96470c2e7 100644 --- a/bigquery/tests/unit/test_job.py +++ b/bigquery/tests/unit/test_job.py @@ -2697,11 +2697,6 @@ def __init__(self, project='project', connection=None): self.project = project self._connection = connection - def dataset(self, name, project=None): - from google.cloud.bigquery.dataset import Dataset - - return Dataset(name, client=self, project=project) - def _get_query_results(self, job_id): from google.cloud.bigquery.query import QueryResults diff --git a/bigquery/tests/unit/test_table.py b/bigquery/tests/unit/test_table.py index aa9e00670655..2629f824e0b2 100644 --- a/bigquery/tests/unit/test_table.py +++ b/bigquery/tests/unit/test_table.py @@ -37,6 +37,26 @@ def _verifySchema(self, schema, resource): self._verify_field(field, r_field) +class TestTableReference(unittest.TestCase): + + @staticmethod + def _get_target_class(): + from google.cloud.bigquery.table import TableReference + + return TableReference + + def _make_one(self, *args, **kw): + return self._get_target_class()(*args, **kw) + + def test_ctor_defaults(self): + from google.cloud.bigquery.dataset import DatasetReference + dataset_ref = DatasetReference('project_1', 'dataset_1') + + table_ref = self._make_one(dataset_ref, 'table_1') + self.assertIs(table_ref.dataset_ref, dataset_ref) + self.assertEqual(table_ref.table_id, 'table_1') + + class TestTable(unittest.TestCase, _SchemaBase): PROJECT = 'prahj-ekt'