Skip to content

Commit 2928bfc

Browse files
committed
Merge pull request #1021 from tseaver/bigquery-table_ctor_properties
BQ Tables: ctor, scalar property definitions
2 parents 25545c8 + 10eaddd commit 2928bfc

File tree

5 files changed

+591
-15
lines changed

5 files changed

+591
-15
lines changed

gcloud/bigquery/_helpers.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
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+
"""BigQuery utility functions."""
16+
17+
18+
import datetime
19+
import sys
20+
21+
import pytz
22+
23+
_EPOCH = datetime.datetime.utcfromtimestamp(0).replace(tzinfo=pytz.utc)
24+
25+
26+
def _millis(when):
27+
"""Convert a zone-aware datetime to integer milliseconds.
28+
29+
:type when: ``datetime.datetime``
30+
:param when: the datetime to convert
31+
32+
:rtype: integer
33+
:returns: milliseconds since epoch for ``when``
34+
"""
35+
return int(_total_seconds(when - _EPOCH) * 1000)
36+
37+
38+
def _datetime_from_prop(value):
39+
"""Convert non-none timestamp to datetime, assuming UTC.
40+
41+
:rtype: ``datetime.datetime``, or ``NoneType``
42+
"""
43+
if value is not None:
44+
# back-end returns timestamps as milliseconds since the epoch
45+
value = datetime.datetime.utcfromtimestamp(value / 1000.0)
46+
return value.replace(tzinfo=pytz.utc)
47+
48+
49+
def _prop_from_datetime(value):
50+
"""Convert non-none datetime to timestamp, assuming UTC.
51+
52+
:type value: ``datetime.datetime``, or None
53+
:param value: the timestamp
54+
55+
:rtype: integer, or ``NoneType``
56+
:returns: the timestamp, in milliseconds, or None
57+
"""
58+
if value is not None:
59+
if value.tzinfo is None:
60+
# Assume UTC
61+
value = value.replace(tzinfo=pytz.utc)
62+
# back-end wants timestamps as milliseconds since the epoch
63+
return _millis(value)
64+
65+
66+
if sys.version_info[:2] < (2, 7):
67+
def _total_seconds(offset): # pragma: NO COVER
68+
"""Backport of timedelta.total_seconds() from python 2.7+."""
69+
seconds = offset.days * 24 * 60 * 60 + offset.seconds
70+
microseconds = seconds * 10**6 + offset.microseconds
71+
return microseconds / (10**6 * 1.0)
72+
else:
73+
def _total_seconds(offset):
74+
return offset.total_seconds()

gcloud/bigquery/dataset.py

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,10 @@
1313
# limitations under the License.
1414

1515
"""Define API Datasets."""
16-
17-
import datetime
18-
19-
import pytz
2016
import six
2117

2218
from gcloud.exceptions import NotFound
19+
from gcloud.bigquery._helpers import _datetime_from_prop
2320

2421

2522
class Dataset(object):
@@ -356,14 +353,3 @@ def delete(self, client=None):
356353
"""
357354
client = self._require_client(client)
358355
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)

gcloud/bigquery/table.py

Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
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 six
20+
21+
from gcloud.bigquery._helpers import _datetime_from_prop
22+
from gcloud.bigquery._helpers import _prop_from_datetime
23+
24+
25+
class Table(object):
26+
"""Tables represent a set of rows whose values correspond to a schema.
27+
28+
See:
29+
https://cloud.google.com/bigquery/docs/reference/v2/tables
30+
31+
:type name: string
32+
:param name: the name of the table
33+
34+
:type dataset: :class:`gcloud.bigquery.dataset.Dataset`
35+
:param dataset: The dataset which contains the table.
36+
"""
37+
38+
def __init__(self, name, dataset):
39+
self.name = name
40+
self._dataset = dataset
41+
self._properties = {}
42+
43+
@property
44+
def path(self):
45+
"""URL path for the table's APIs.
46+
47+
:rtype: string
48+
:returns: the path based on project and dataste name.
49+
"""
50+
return '%s/tables/%s' % (self._dataset.path, self.name)
51+
52+
@property
53+
def created(self):
54+
"""Datetime at which the table was created.
55+
56+
:rtype: ``datetime.datetime``, or ``NoneType``
57+
:returns: the creation time (None until set from the server).
58+
"""
59+
return _datetime_from_prop(self._properties.get('creationTime'))
60+
61+
@property
62+
def etag(self):
63+
"""ETag for the table resource.
64+
65+
:rtype: string, or ``NoneType``
66+
:returns: the ETag (None until set from the server).
67+
"""
68+
return self._properties.get('etag')
69+
70+
@property
71+
def modified(self):
72+
"""Datetime at which the table was last modified.
73+
74+
:rtype: ``datetime.datetime``, or ``NoneType``
75+
:returns: the modification time (None until set from the server).
76+
"""
77+
return _datetime_from_prop(self._properties.get('lastModifiedTime'))
78+
79+
@property
80+
def num_bytes(self):
81+
"""The size of the table in bytes.
82+
83+
:rtype: integer, or ``NoneType``
84+
:returns: the byte count (None until set from the server).
85+
"""
86+
return self._properties.get('numBytes')
87+
88+
@property
89+
def num_rows(self):
90+
"""The number of rows in the table.
91+
92+
:rtype: integer, or ``NoneType``
93+
:returns: the row count (None until set from the server).
94+
"""
95+
return self._properties.get('numRows')
96+
97+
@property
98+
def self_link(self):
99+
"""URL for the table resource.
100+
101+
:rtype: string, or ``NoneType``
102+
:returns: the URL (None until set from the server).
103+
"""
104+
return self._properties.get('selfLink')
105+
106+
@property
107+
def table_id(self):
108+
"""ID for the table resource.
109+
110+
:rtype: string, or ``NoneType``
111+
:returns: the ID (None until set from the server).
112+
"""
113+
return self._properties.get('id')
114+
115+
@property
116+
def table_type(self):
117+
"""The type of the table.
118+
119+
Possible values are "TABLE" or "VIEW".
120+
121+
:rtype: string, or ``NoneType``
122+
:returns: the URL (None until set from the server).
123+
"""
124+
return self._properties.get('type')
125+
126+
@property
127+
def description(self):
128+
"""Description of the table.
129+
130+
:rtype: string, or ``NoneType``
131+
:returns: The description as set by the user, or None (the default).
132+
"""
133+
return self._properties.get('description')
134+
135+
@description.setter
136+
def description(self, value):
137+
"""Update description of the table.
138+
139+
:type value: string, or ``NoneType``
140+
:param value: new description
141+
142+
:raises: ValueError for invalid value types.
143+
"""
144+
if not isinstance(value, six.string_types) and value is not None:
145+
raise ValueError("Pass a string, or None")
146+
self._properties['description'] = value
147+
148+
@property
149+
def expires(self):
150+
"""Datetime at which the table will be removed.
151+
152+
:rtype: ``datetime.datetime``, or ``NoneType``
153+
:returns: the expiration time, or None
154+
"""
155+
return _datetime_from_prop(self._properties.get('expirationTime'))
156+
157+
@expires.setter
158+
def expires(self, value):
159+
"""Update atetime at which the table will be removed.
160+
161+
:type value: ``datetime.datetime``, or ``NoneType``
162+
:param value: the new expiration time, or None
163+
"""
164+
if not isinstance(value, datetime.datetime) and value is not None:
165+
raise ValueError("Pass a datetime, or None")
166+
self._properties['expirationTime'] = _prop_from_datetime(value)
167+
168+
@property
169+
def friendly_name(self):
170+
"""Title of the table.
171+
172+
:rtype: string, or ``NoneType``
173+
:returns: The name as set by the user, or None (the default).
174+
"""
175+
return self._properties.get('friendlyName')
176+
177+
@friendly_name.setter
178+
def friendly_name(self, value):
179+
"""Update title of the table.
180+
181+
:type value: string, or ``NoneType``
182+
:param value: new title
183+
184+
:raises: ValueError for invalid value types.
185+
"""
186+
if not isinstance(value, six.string_types) and value is not None:
187+
raise ValueError("Pass a string, or None")
188+
self._properties['friendlyName'] = value
189+
190+
@property
191+
def location(self):
192+
"""Location in which the table is hosted.
193+
194+
:rtype: string, or ``NoneType``
195+
:returns: The location as set by the user, or None (the default).
196+
"""
197+
return self._properties.get('location')
198+
199+
@location.setter
200+
def location(self, value):
201+
"""Update location in which the table is hosted.
202+
203+
:type value: string, or ``NoneType``
204+
:param value: new location
205+
206+
:raises: ValueError for invalid value types.
207+
"""
208+
if not isinstance(value, six.string_types) and value is not None:
209+
raise ValueError("Pass a string, or None")
210+
self._properties['location'] = value
211+
212+
@property
213+
def view_query(self):
214+
"""SQL query defining the table as a view.
215+
216+
:rtype: string, or ``NoneType``
217+
:returns: The query as set by the user, or None (the default).
218+
"""
219+
view = self._properties.get('view')
220+
if view is not None:
221+
return view.get('query')
222+
223+
@view_query.setter
224+
def view_query(self, value):
225+
"""Update SQL query defining the table as a view.
226+
227+
:type value: string
228+
:param value: new location
229+
230+
:raises: ValueError for invalid value types.
231+
"""
232+
if not isinstance(value, six.string_types):
233+
raise ValueError("Pass a string")
234+
self._properties['view'] = {'query': value}
235+
236+
@view_query.deleter
237+
def view_query(self):
238+
"""Delete SQL query defining the table as a view."""
239+
self._properties.pop('view', None)

0 commit comments

Comments
 (0)