Skip to content

Commit ee5039f

Browse files
committed
Merge pull request #1018 from tseaver/bigquery-dataset
Implement 'bigquery.dataset.Dataset'.
2 parents 605ca26 + 81c05b7 commit ee5039f

5 files changed

Lines changed: 813 additions & 0 deletions

File tree

gcloud/bigquery/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,4 @@
2323

2424
from gcloud.bigquery.client import Client
2525
from gcloud.bigquery.connection import SCOPE
26+
from gcloud.bigquery.dataset import Dataset

gcloud/bigquery/client.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
from gcloud.client import JSONClient
1919
from gcloud.bigquery.connection import Connection
20+
from gcloud.bigquery.dataset import Dataset
2021

2122

2223
class Client(JSONClient):
@@ -41,3 +42,14 @@ class Client(JSONClient):
4142
"""
4243

4344
_connection_class = Connection
45+
46+
def dataset(self, name):
47+
"""Construct a dataset bound to this client.
48+
49+
:type name: string
50+
:param name: Name of the dataset.
51+
52+
:rtype: :class:`gcloud.bigquery.dataset.Dataset`
53+
:returns: a new ``Dataset`` instance
54+
"""
55+
return Dataset(name, client=self)

gcloud/bigquery/dataset.py

Lines changed: 369 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,369 @@
1+
# Copyright 2015 Google Inc. All rights reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""Define API Datasets."""
16+
17+
import datetime
18+
19+
import pytz
20+
import six
21+
22+
from gcloud.exceptions import NotFound
23+
24+
25+
class Dataset(object):
26+
"""Datasets are containers for tables.
27+
28+
See:
29+
https://cloud.google.com/bigquery/docs/reference/v2/datasets
30+
31+
:type name: string
32+
:param name: the name of the dataset
33+
34+
:type client: :class:`gcloud.bigquery.client.Client`
35+
:param client: A client which holds credentials and project configuration
36+
for the dataset (which requires a project).
37+
"""
38+
39+
def __init__(self, name, client):
40+
self.name = name
41+
self._client = client
42+
self._properties = {}
43+
44+
@property
45+
def project(self):
46+
"""Project bound to the dataset.
47+
48+
:rtype: string
49+
:returns: the project (derived from the client).
50+
"""
51+
return self._client.project
52+
53+
@property
54+
def path(self):
55+
"""URL path for the dataset's APIs.
56+
57+
:rtype: string
58+
:returns: the path based on project and dataste name.
59+
"""
60+
return '/projects/%s/datasets/%s' % (self.project, self.name)
61+
62+
@property
63+
def created(self):
64+
"""Datetime at which the dataset was created.
65+
66+
:rtype: ``datetime.datetime``, or ``NoneType``
67+
:returns: the creation time (None until set from the server).
68+
"""
69+
return _datetime_from_prop(self._properties.get('creationTime'))
70+
71+
@property
72+
def dataset_id(self):
73+
"""ID for the dataset resource.
74+
75+
:rtype: string, or ``NoneType``
76+
:returns: the ID (None until set from the server).
77+
"""
78+
return self._properties.get('id')
79+
80+
@property
81+
def etag(self):
82+
"""ETag for the dataset resource.
83+
84+
:rtype: string, or ``NoneType``
85+
:returns: the ETag (None until set from the server).
86+
"""
87+
return self._properties.get('etag')
88+
89+
@property
90+
def modified(self):
91+
"""Datetime at which the dataset was last modified.
92+
93+
:rtype: ``datetime.datetime``, or ``NoneType``
94+
:returns: the modification time (None until set from the server).
95+
"""
96+
return _datetime_from_prop(self._properties.get('lastModifiedTime'))
97+
98+
@property
99+
def self_link(self):
100+
"""URL for the dataset resource.
101+
102+
:rtype: string, or ``NoneType``
103+
:returns: the URL (None until set from the server).
104+
"""
105+
return self._properties.get('selfLink')
106+
107+
@property
108+
def default_table_expiration_ms(self):
109+
"""Default expiration time for tables in the dataset.
110+
111+
:rtype: integer, or ``NoneType``
112+
:returns: The time in milliseconds, or None (the default).
113+
"""
114+
return self._properties.get('defaultTableExpirationMs')
115+
116+
@default_table_expiration_ms.setter
117+
def default_table_expiration_ms(self, value):
118+
"""Update default expiration time for tables in the dataset.
119+
120+
:type value: integer, or ``NoneType``
121+
:param value: new default time, in milliseconds
122+
123+
:raises: ValueError for invalid value types.
124+
"""
125+
if not isinstance(value, six.integer_types) and value is not None:
126+
raise ValueError("Pass an integer, or None")
127+
self._properties['defaultTableExpirationMs'] = value
128+
129+
@property
130+
def description(self):
131+
"""Description of the dataset.
132+
133+
:rtype: string, or ``NoneType``
134+
:returns: The description as set by the user, or None (the default).
135+
"""
136+
return self._properties.get('description')
137+
138+
@description.setter
139+
def description(self, value):
140+
"""Update description of the dataset.
141+
142+
:type value: string, or ``NoneType``
143+
:param value: new description
144+
145+
:raises: ValueError for invalid value types.
146+
"""
147+
if not isinstance(value, six.string_types) and value is not None:
148+
raise ValueError("Pass a string, or None")
149+
self._properties['description'] = value
150+
151+
@property
152+
def friendly_name(self):
153+
"""Title of the dataset.
154+
155+
:rtype: string, or ``NoneType``
156+
:returns: The name as set by the user, or None (the default).
157+
"""
158+
return self._properties.get('friendlyName')
159+
160+
@friendly_name.setter
161+
def friendly_name(self, value):
162+
"""Update title of the dataset.
163+
164+
:type value: string, or ``NoneType``
165+
:param value: new title
166+
167+
:raises: ValueError for invalid value types.
168+
"""
169+
if not isinstance(value, six.string_types) and value is not None:
170+
raise ValueError("Pass a string, or None")
171+
self._properties['friendlyName'] = value
172+
173+
@property
174+
def location(self):
175+
"""Location in which the dataset is hosted.
176+
177+
:rtype: string, or ``NoneType``
178+
:returns: The location as set by the user, or None (the default).
179+
"""
180+
return self._properties.get('location')
181+
182+
@location.setter
183+
def location(self, value):
184+
"""Update location in which the dataset is hosted.
185+
186+
:type value: string, or ``NoneType``
187+
:param value: new location
188+
189+
:raises: ValueError for invalid value types.
190+
"""
191+
if not isinstance(value, six.string_types) and value is not None:
192+
raise ValueError("Pass a string, or None")
193+
self._properties['location'] = value
194+
195+
def _require_client(self, client):
196+
"""Check client or verify over-ride.
197+
198+
:type client: :class:`gcloud.bigquery.client.Client` or ``NoneType``
199+
:param client: the client to use. If not passed, falls back to the
200+
``client`` stored on the current dataset.
201+
202+
:rtype: :class:`gcloud.bigquery.client.Client`
203+
:returns: The client passed in or the currently bound client.
204+
"""
205+
if client is None:
206+
client = self._client
207+
return client
208+
209+
def _set_properties(self, api_response):
210+
"""Update properties from resource in body of ``api_response``
211+
212+
:type api_response: httplib2.Response
213+
:param api_response: response returned from an API call
214+
"""
215+
self._properties.clear()
216+
cleaned = api_response.copy()
217+
cleaned['creationTime'] = float(cleaned['creationTime'])
218+
cleaned['lastModifiedTime'] = float(cleaned['lastModifiedTime'])
219+
self._properties.update(cleaned)
220+
221+
def _build_resource(self):
222+
"""Generate a resource for ``create`` or ``update``."""
223+
resource = {
224+
'datasetReference': {
225+
'projectId': self.project, 'datasetId': self.name},
226+
}
227+
if self.default_table_expiration_ms is not None:
228+
value = self.default_table_expiration_ms
229+
resource['defaultTableExpirationMs'] = value
230+
231+
if self.description is not None:
232+
resource['description'] = self.description
233+
234+
if self.friendly_name is not None:
235+
resource['friendlyName'] = self.friendly_name
236+
237+
if self.location is not None:
238+
resource['location'] = self.location
239+
240+
return resource
241+
242+
def create(self, client=None):
243+
"""API call: create the dataset via a PUT request
244+
245+
See:
246+
https://cloud.google.com/bigquery/reference/rest/v2/tables/insert
247+
248+
:type client: :class:`gcloud.bigquery.client.Client` or ``NoneType``
249+
:param client: the client to use. If not passed, falls back to the
250+
``client`` stored on the current dataset.
251+
"""
252+
client = self._require_client(client)
253+
path = '/projects/%s/datasets' % (self.project,)
254+
api_response = client.connection.api_request(
255+
method='POST', path=path, data=self._build_resource())
256+
self._set_properties(api_response)
257+
258+
def exists(self, client=None):
259+
"""API call: test for the existence of the dataset via a GET request
260+
261+
See
262+
https://cloud.google.com/bigquery/docs/reference/v2/datasets/get
263+
264+
:type client: :class:`gcloud.bigquery.client.Client` or ``NoneType``
265+
:param client: the client to use. If not passed, falls back to the
266+
``client`` stored on the current dataset.
267+
"""
268+
client = self._require_client(client)
269+
270+
try:
271+
client.connection.api_request(method='GET', path=self.path,
272+
query_params={'fields': 'id'})
273+
except NotFound:
274+
return False
275+
else:
276+
return True
277+
278+
def reload(self, client=None):
279+
"""API call: refresh dataset properties via a GET request
280+
281+
See
282+
https://cloud.google.com/bigquery/docs/reference/v2/datasets/get
283+
284+
:type client: :class:`gcloud.bigquery.client.Client` or ``NoneType``
285+
:param client: the client to use. If not passed, falls back to the
286+
``client`` stored on the current dataset.
287+
"""
288+
client = self._require_client(client)
289+
290+
api_response = client.connection.api_request(
291+
method='GET', path=self.path)
292+
self._set_properties(api_response)
293+
294+
def patch(self, client=None, **kw):
295+
"""API call: update individual dataset properties via a PATCH request
296+
297+
See
298+
https://cloud.google.com/bigquery/docs/reference/v2/datasets/patch
299+
300+
:type client: :class:`gcloud.bigquery.client.Client` or ``NoneType``
301+
:param client: the client to use. If not passed, falls back to the
302+
``client`` stored on the current dataset.
303+
304+
:type kw: ``dict``
305+
:param kw: properties to be patched.
306+
307+
:raises: ValueError for invalid value types.
308+
"""
309+
client = self._require_client(client)
310+
311+
partial = {}
312+
313+
if 'default_table_expiration_ms' in kw:
314+
value = kw['default_table_expiration_ms']
315+
if not isinstance(value, six.integer_types) and value is not None:
316+
raise ValueError("Pass an integer, or None")
317+
partial['defaultTableExpirationMs'] = value
318+
319+
if 'description' in kw:
320+
partial['description'] = kw['description']
321+
322+
if 'friendly_name' in kw:
323+
partial['friendlyName'] = kw['friendly_name']
324+
325+
if 'location' in kw:
326+
partial['location'] = kw['location']
327+
328+
api_response = client.connection.api_request(
329+
method='PATCH', path=self.path, data=partial)
330+
self._set_properties(api_response)
331+
332+
def update(self, client=None):
333+
"""API call: update dataset properties via a PUT request
334+
335+
See
336+
https://cloud.google.com/bigquery/docs/reference/v2/datasets/update
337+
338+
:type client: :class:`gcloud.bigquery.client.Client` or ``NoneType``
339+
:param client: the client to use. If not passed, falls back to the
340+
``client`` stored on the current dataset.
341+
"""
342+
client = self._require_client(client)
343+
api_response = client.connection.api_request(
344+
method='PUT', path=self.path, data=self._build_resource())
345+
self._set_properties(api_response)
346+
347+
def delete(self, client=None):
348+
"""API call: delete the dataset via a DELETE request
349+
350+
See:
351+
https://cloud.google.com/bigquery/reference/rest/v2/datasets/delete
352+
353+
:type client: :class:`gcloud.bigquery.client.Client` or ``NoneType``
354+
:param client: the client to use. If not passed, falls back to the
355+
``client`` stored on the current dataset.
356+
"""
357+
client = self._require_client(client)
358+
client.connection.api_request(method='DELETE', path=self.path)
359+
360+
361+
def _datetime_from_prop(value):
362+
"""Convert non-none timestamp to datetime, assuming UTC.
363+
364+
:rtype: ``datetime.datetime``, or ``NoneType``
365+
"""
366+
if value is not None:
367+
# back-end returns timestamps as milliseconds since the epoch
368+
value = datetime.datetime.utcfromtimestamp(value / 1000.0)
369+
return value.replace(tzinfo=pytz.utc)

0 commit comments

Comments
 (0)