Skip to content

Commit c436873

Browse files
authored
Add methods for Models API (#7562)
* Generate protos for Models API. The Models API is a component of the bigquery_v2 interface. It is not available as a gRPC API, but it does provide protocol buffers. This commit adds those protocol buffers to the client so that they can be used to avoid much manual work to create resource classes that can be serialized to/from JSON. * Add handwritten model API classes. These classes provide the top-level classes for the Model API. The protocol buffer objects are used for all sub-objects. The pattern for mutable properties follows the same as with Table and Dataset: a `_properties` dictionary contains the property values in the REST API format.
1 parent 33fcc1c commit c436873

47 files changed

Lines changed: 6644 additions & 64 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

bigquery/LICENSE

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
2-
Apache License
1+
Apache License
32
Version 2.0, January 2004
4-
http://www.apache.org/licenses/
3+
https://www.apache.org/licenses/
54

65
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
76

@@ -193,7 +192,7 @@
193192
you may not use this file except in compliance with the License.
194193
You may obtain a copy of the License at
195194

196-
http://www.apache.org/licenses/LICENSE-2.0
195+
https://www.apache.org/licenses/LICENSE-2.0
197196

198197
Unless required by applicable law or agreed to in writing, software
199198
distributed under the License is distributed on an "AS IS" BASIS,

bigquery/MANIFEST.in

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
include README.rst LICENSE
2+
recursive-include google *.json *.proto
23
recursive-include tests *
3-
global-exclude *.pyc __pycache__
4+
global-exclude *.py[co]
5+
global-exclude __pycache__

bigquery/docs/gapic/v2/enums.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
Enums for BigQuery API Client
2+
=============================
3+
4+
.. autoclass:: google.cloud.bigquery_v2.gapic.enums.Model
5+
:members:
6+
7+
.. autoclass:: google.cloud.bigquery_v2.gapic.enums.StandardSqlDataType
8+
:members:

bigquery/docs/gapic/v2/types.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Types for BigQuery API Client
2+
=============================
3+
4+
.. automodule:: google.cloud.bigquery_v2.types
5+
:members:

bigquery/docs/reference.rst

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,14 @@ Table
9292
table.TimePartitioning
9393
table.TimePartitioningType
9494

95+
Model
96+
=====
97+
98+
.. autosummary::
99+
:toctree: generated
100+
101+
model.Model
102+
model.ModelReference
95103

96104
Schema
97105
======
@@ -139,9 +147,20 @@ External Configuration
139147

140148

141149
Magics
142-
======================
150+
======
143151

144152
.. toctree::
145153
:maxdepth: 2
146154

147155
magics
156+
157+
Additional Types
158+
================
159+
160+
Protocol buffer classes for working with the Models API.
161+
162+
.. toctree::
163+
:maxdepth: 2
164+
165+
gapic/v2/enums
166+
gapic/v2/types

bigquery/google/__init__.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1-
# Copyright 2016 Google LLC
1+
# -*- coding: utf-8 -*-
2+
#
3+
# Copyright 2019 Google LLC
24
#
35
# Licensed under the Apache License, Version 2.0 (the "License");
46
# you may not use this file except in compliance with the License.
57
# You may obtain a copy of the License at
68
#
7-
# http://www.apache.org/licenses/LICENSE-2.0
9+
# https://www.apache.org/licenses/LICENSE-2.0
810
#
911
# Unless required by applicable law or agreed to in writing, software
1012
# distributed under the License is distributed on an "AS IS" BASIS,

bigquery/google/cloud/__init__.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1-
# Copyright 2016 Google LLC
1+
# -*- coding: utf-8 -*-
2+
#
3+
# Copyright 2019 Google LLC
24
#
35
# Licensed under the Apache License, Version 2.0 (the "License");
46
# you may not use this file except in compliance with the License.
57
# You may obtain a copy of the License at
68
#
7-
# http://www.apache.org/licenses/LICENSE-2.0
9+
# https://www.apache.org/licenses/LICENSE-2.0
810
#
911
# Unless required by applicable law or agreed to in writing, software
1012
# distributed under the License is distributed on an "AS IS" BASIS,

bigquery/google/cloud/bigquery/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@
6060
from google.cloud.bigquery.job import SourceFormat
6161
from google.cloud.bigquery.job import UnknownJob
6262
from google.cloud.bigquery.job import WriteDisposition
63+
from google.cloud.bigquery.model import Model
64+
from google.cloud.bigquery.model import ModelReference
6365
from google.cloud.bigquery.query import ArrayQueryParameter
6466
from google.cloud.bigquery.query import ScalarQueryParameter
6567
from google.cloud.bigquery.query import StructQueryParameter
@@ -100,6 +102,9 @@
100102
"UnknownJob",
101103
"TimePartitioningType",
102104
"TimePartitioning",
105+
# Models
106+
"Model",
107+
"ModelReference",
103108
# Shared helpers
104109
"SchemaField",
105110
"UDFResource",

bigquery/google/cloud/bigquery/_helpers.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -581,3 +581,52 @@ def _str_or_none(value):
581581
"""Helper: serialize value to JSON string."""
582582
if value is not None:
583583
return str(value)
584+
585+
586+
def _parse_3_part_id(full_id, default_project=None, property_name="table_id"):
587+
output_project_id = default_project
588+
output_dataset_id = None
589+
output_resource_id = None
590+
parts = full_id.split(".")
591+
592+
if len(parts) != 2 and len(parts) != 3:
593+
raise ValueError(
594+
"{property_name} must be a fully-qualified ID in "
595+
'standard SQL format. e.g. "project.dataset.{property_name}", '
596+
"got {}".format(full_id, property_name=property_name)
597+
)
598+
599+
if len(parts) == 2 and not default_project:
600+
raise ValueError(
601+
"When default_project is not set, {property_name} must be a "
602+
"fully-qualified ID in standard SQL format. "
603+
'e.g. "project.dataset_id.{property_name}", got {}'.format(
604+
full_id, property_name=property_name
605+
)
606+
)
607+
608+
if len(parts) == 2:
609+
output_dataset_id, output_resource_id = parts
610+
else:
611+
output_project_id, output_dataset_id, output_resource_id = parts
612+
613+
return output_project_id, output_dataset_id, output_resource_id
614+
615+
616+
def _build_resource_from_properties(obj, filter_fields):
617+
"""Build a resource based on a ``_properties`` dictionary, filtered by
618+
``filter_fields``, which follow the name of the Python object.
619+
"""
620+
partial = {}
621+
for filter_field in filter_fields:
622+
api_field = obj._PROPERTY_TO_API_FIELD.get(filter_field)
623+
if api_field is None and filter_field not in obj._properties:
624+
raise ValueError("No property %s" % filter_field)
625+
elif api_field is not None:
626+
partial[api_field] = obj._properties.get(api_field)
627+
else:
628+
# allows properties that are not defined in the library
629+
# and properties that have the same name as API resource key
630+
partial[filter_field] = obj._properties[filter_field]
631+
632+
return partial

bigquery/google/cloud/bigquery/client.py

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@
4646
from google.cloud.bigquery.dataset import DatasetListItem
4747
from google.cloud.bigquery.dataset import DatasetReference
4848
from google.cloud.bigquery import job
49+
from google.cloud.bigquery.model import Model
50+
from google.cloud.bigquery.model import ModelReference
4951
from google.cloud.bigquery.query import _QueryResults
5052
from google.cloud.bigquery.retry import DEFAULT_RETRY
5153
from google.cloud.bigquery.table import _table_arg_to_table
@@ -428,6 +430,33 @@ def get_dataset(self, dataset_ref, retry=DEFAULT_RETRY):
428430
api_response = self._call_api(retry, method="GET", path=dataset_ref.path)
429431
return Dataset.from_api_repr(api_response)
430432

433+
def get_model(self, model_ref, retry=DEFAULT_RETRY):
434+
"""[Beta] Fetch the model referenced by ``model_ref``.
435+
436+
Args:
437+
model_ref (Union[ \
438+
:class:`~google.cloud.bigquery.model.ModelReference`, \
439+
str, \
440+
]):
441+
A reference to the model to fetch from the BigQuery API.
442+
If a string is passed in, this method attempts to create a
443+
model reference from a string using
444+
:func:`google.cloud.bigquery.model.ModelReference.from_string`.
445+
retry (:class:`google.api_core.retry.Retry`):
446+
(Optional) How to retry the RPC.
447+
448+
Returns:
449+
google.cloud.bigquery.model.Model:
450+
A ``Model`` instance.
451+
"""
452+
if isinstance(model_ref, str):
453+
model_ref = ModelReference.from_string(
454+
model_ref, default_project=self.project
455+
)
456+
457+
api_response = self._call_api(retry, method="GET", path=model_ref.path)
458+
return Model.from_api_repr(api_response)
459+
431460
def get_table(self, table, retry=DEFAULT_RETRY):
432461
"""Fetch the table referenced by ``table``.
433462
@@ -488,6 +517,41 @@ def update_dataset(self, dataset, fields, retry=DEFAULT_RETRY):
488517
)
489518
return Dataset.from_api_repr(api_response)
490519

520+
def update_model(self, model, fields, retry=DEFAULT_RETRY):
521+
"""[Beta] Change some fields of a model.
522+
523+
Use ``fields`` to specify which fields to update. At least one field
524+
must be provided. If a field is listed in ``fields`` and is ``None``
525+
in ``model``, it will be deleted.
526+
527+
If ``model.etag`` is not ``None``, the update will only succeed if
528+
the model on the server has the same ETag. Thus reading a model with
529+
``get_model``, changing its fields, and then passing it to
530+
``update_model`` will ensure that the changes will only be saved if
531+
no modifications to the model occurred since the read.
532+
533+
Args:
534+
model (google.cloud.bigquery.model.Model): The model to update.
535+
fields (Sequence[str]):
536+
The fields of ``model`` to change, spelled as the Model
537+
properties (e.g. "friendly_name").
538+
retry (google.api_core.retry.Retry):
539+
(Optional) A description of how to retry the API call.
540+
541+
Returns:
542+
google.cloud.bigquery.model.Model:
543+
The model resource returned from the API call.
544+
"""
545+
partial = model._build_resource(fields)
546+
if model.etag:
547+
headers = {"If-Match": model.etag}
548+
else:
549+
headers = None
550+
api_response = self._call_api(
551+
retry, method="PATCH", path=model.path, data=partial, headers=headers
552+
)
553+
return Model.from_api_repr(api_response)
554+
491555
def update_table(self, table, fields, retry=DEFAULT_RETRY):
492556
"""Change some fields of a table.
493557
@@ -523,6 +587,64 @@ def update_table(self, table, fields, retry=DEFAULT_RETRY):
523587
)
524588
return Table.from_api_repr(api_response)
525589

590+
def list_models(
591+
self, dataset, max_results=None, page_token=None, retry=DEFAULT_RETRY
592+
):
593+
"""[Beta] List models in the dataset.
594+
595+
See
596+
https://cloud.google.com/bigquery/docs/reference/rest/v2/models/list
597+
598+
Args:
599+
dataset (Union[ \
600+
:class:`~google.cloud.bigquery.dataset.Dataset`, \
601+
:class:`~google.cloud.bigquery.dataset.DatasetReference`, \
602+
str, \
603+
]):
604+
A reference to the dataset whose models to list from the
605+
BigQuery API. If a string is passed in, this method attempts
606+
to create a dataset reference from a string using
607+
:func:`google.cloud.bigquery.dataset.DatasetReference.from_string`.
608+
max_results (int):
609+
(Optional) Maximum number of models to return. If not passed,
610+
defaults to a value set by the API.
611+
page_token (str):
612+
(Optional) Token representing a cursor into the models. If
613+
not passed, the API will return the first page of models. The
614+
token marks the beginning of the iterator to be returned and
615+
the value of the ``page_token`` can be accessed at
616+
``next_page_token`` of the
617+
:class:`~google.api_core.page_iterator.HTTPIterator`.
618+
retry (:class:`google.api_core.retry.Retry`):
619+
(Optional) How to retry the RPC.
620+
621+
Returns:
622+
google.api_core.page_iterator.Iterator:
623+
Iterator of
624+
:class:`~google.cloud.bigquery.model.Model` contained
625+
within the requested dataset.
626+
"""
627+
if isinstance(dataset, str):
628+
dataset = DatasetReference.from_string(
629+
dataset, default_project=self.project
630+
)
631+
632+
if not isinstance(dataset, (Dataset, DatasetReference)):
633+
raise TypeError("dataset must be a Dataset, DatasetReference, or string")
634+
635+
path = "%s/models" % dataset.path
636+
result = page_iterator.HTTPIterator(
637+
client=self,
638+
api_request=functools.partial(self._call_api, retry),
639+
path=path,
640+
item_to_value=_item_to_model,
641+
items_key="models",
642+
page_token=page_token,
643+
max_results=max_results,
644+
)
645+
result.dataset = dataset
646+
return result
647+
526648
def list_tables(
527649
self, dataset, max_results=None, page_token=None, retry=DEFAULT_RETRY
528650
):
@@ -629,6 +751,40 @@ def delete_dataset(
629751
if not not_found_ok:
630752
raise
631753

754+
def delete_model(self, model, retry=DEFAULT_RETRY, not_found_ok=False):
755+
"""[Beta] Delete a model
756+
757+
See
758+
https://cloud.google.com/bigquery/docs/reference/rest/v2/models/delete
759+
760+
Args:
761+
model (Union[ \
762+
:class:`~google.cloud.bigquery.model.Model`, \
763+
:class:`~google.cloud.bigquery.model.ModelReference`, \
764+
str, \
765+
]):
766+
A reference to the model to delete. If a string is passed in,
767+
this method attempts to create a model reference from a
768+
string using
769+
:func:`google.cloud.bigquery.model.ModelReference.from_string`.
770+
retry (:class:`google.api_core.retry.Retry`):
771+
(Optional) How to retry the RPC.
772+
not_found_ok (bool):
773+
Defaults to ``False``. If ``True``, ignore "not found" errors
774+
when deleting the model.
775+
"""
776+
if isinstance(model, str):
777+
model = ModelReference.from_string(model, default_project=self.project)
778+
779+
if not isinstance(model, (Model, ModelReference)):
780+
raise TypeError("model must be a Model or a ModelReference")
781+
782+
try:
783+
self._call_api(retry, method="DELETE", path=model.path)
784+
except google.api_core.exceptions.NotFound:
785+
if not not_found_ok:
786+
raise
787+
632788
def delete_table(self, table, retry=DEFAULT_RETRY, not_found_ok=False):
633789
"""Delete a table
634790
@@ -1823,6 +1979,21 @@ def _item_to_job(iterator, resource):
18231979
return iterator.client.job_from_resource(resource)
18241980

18251981

1982+
def _item_to_model(iterator, resource):
1983+
"""Convert a JSON model to the native object.
1984+
1985+
Args:
1986+
iterator (google.api_core.page_iterator.Iterator):
1987+
The iterator that is currently in use.
1988+
resource (dict):
1989+
An item to be converted to a model.
1990+
1991+
Returns:
1992+
google.cloud.bigquery.model.Model: The next model in the page.
1993+
"""
1994+
return Model.from_api_repr(resource)
1995+
1996+
18261997
def _item_to_table(iterator, resource):
18271998
"""Convert a JSON table to the native object.
18281999

0 commit comments

Comments
 (0)