Skip to content

Commit

Permalink
Merge pull request #1021 from tseaver/bigquery-table_ctor_properties
Browse files Browse the repository at this point in the history
BQ Tables:  ctor, scalar property definitions
  • Loading branch information
tseaver committed Jul 30, 2015
2 parents 25545c8 + 10eaddd commit 2928bfc
Show file tree
Hide file tree
Showing 5 changed files with 591 additions and 15 deletions.
74 changes: 74 additions & 0 deletions gcloud/bigquery/_helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# Copyright 2015 Google Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""BigQuery utility functions."""


import datetime
import sys

import pytz

_EPOCH = datetime.datetime.utcfromtimestamp(0).replace(tzinfo=pytz.utc)


def _millis(when):
"""Convert a zone-aware datetime to integer milliseconds.
:type when: ``datetime.datetime``
:param when: the datetime to convert
:rtype: integer
:returns: milliseconds since epoch for ``when``
"""
return int(_total_seconds(when - _EPOCH) * 1000)


def _datetime_from_prop(value):
"""Convert non-none timestamp to datetime, assuming UTC.
:rtype: ``datetime.datetime``, or ``NoneType``
"""
if value is not None:
# back-end returns timestamps as milliseconds since the epoch
value = datetime.datetime.utcfromtimestamp(value / 1000.0)
return value.replace(tzinfo=pytz.utc)


def _prop_from_datetime(value):
"""Convert non-none datetime to timestamp, assuming UTC.
:type value: ``datetime.datetime``, or None
:param value: the timestamp
:rtype: integer, or ``NoneType``
:returns: the timestamp, in milliseconds, or None
"""
if value is not None:
if value.tzinfo is None:
# Assume UTC
value = value.replace(tzinfo=pytz.utc)
# back-end wants timestamps as milliseconds since the epoch
return _millis(value)


if sys.version_info[:2] < (2, 7):
def _total_seconds(offset): # pragma: NO COVER
"""Backport of timedelta.total_seconds() from python 2.7+."""
seconds = offset.days * 24 * 60 * 60 + offset.seconds
microseconds = seconds * 10**6 + offset.microseconds
return microseconds / (10**6 * 1.0)
else:
def _total_seconds(offset):
return offset.total_seconds()
16 changes: 1 addition & 15 deletions gcloud/bigquery/dataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,10 @@
# limitations under the License.

"""Define API Datasets."""

import datetime

import pytz
import six

from gcloud.exceptions import NotFound
from gcloud.bigquery._helpers import _datetime_from_prop


class Dataset(object):
Expand Down Expand Up @@ -356,14 +353,3 @@ def delete(self, client=None):
"""
client = self._require_client(client)
client.connection.api_request(method='DELETE', path=self.path)


def _datetime_from_prop(value):
"""Convert non-none timestamp to datetime, assuming UTC.
:rtype: ``datetime.datetime``, or ``NoneType``
"""
if value is not None:
# back-end returns timestamps as milliseconds since the epoch
value = datetime.datetime.utcfromtimestamp(value / 1000.0)
return value.replace(tzinfo=pytz.utc)
239 changes: 239 additions & 0 deletions gcloud/bigquery/table.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
# Copyright 2015 Google Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Define API Datasets."""

import datetime

import six

from gcloud.bigquery._helpers import _datetime_from_prop
from gcloud.bigquery._helpers import _prop_from_datetime


class Table(object):
"""Tables represent a set of rows whose values correspond to a schema.
See:
https://cloud.google.com/bigquery/docs/reference/v2/tables
:type name: string
:param name: the name of the table
:type dataset: :class:`gcloud.bigquery.dataset.Dataset`
:param dataset: The dataset which contains the table.
"""

def __init__(self, name, dataset):
self.name = name
self._dataset = dataset
self._properties = {}

@property
def path(self):
"""URL path for the table's APIs.
:rtype: string
:returns: the path based on project and dataste name.
"""
return '%s/tables/%s' % (self._dataset.path, self.name)

@property
def created(self):
"""Datetime at which the table was created.
:rtype: ``datetime.datetime``, or ``NoneType``
:returns: the creation time (None until set from the server).
"""
return _datetime_from_prop(self._properties.get('creationTime'))

@property
def etag(self):
"""ETag for the table resource.
:rtype: string, or ``NoneType``
:returns: the ETag (None until set from the server).
"""
return self._properties.get('etag')

@property
def modified(self):
"""Datetime at which the table was last modified.
:rtype: ``datetime.datetime``, or ``NoneType``
:returns: the modification time (None until set from the server).
"""
return _datetime_from_prop(self._properties.get('lastModifiedTime'))

@property
def num_bytes(self):
"""The size of the table in bytes.
:rtype: integer, or ``NoneType``
:returns: the byte count (None until set from the server).
"""
return self._properties.get('numBytes')

@property
def num_rows(self):
"""The number of rows in the table.
:rtype: integer, or ``NoneType``
:returns: the row count (None until set from the server).
"""
return self._properties.get('numRows')

@property
def self_link(self):
"""URL for the table resource.
:rtype: string, or ``NoneType``
:returns: the URL (None until set from the server).
"""
return self._properties.get('selfLink')

@property
def table_id(self):
"""ID for the table resource.
:rtype: string, or ``NoneType``
:returns: the ID (None until set from the server).
"""
return self._properties.get('id')

@property
def table_type(self):
"""The type of the table.
Possible values are "TABLE" or "VIEW".
:rtype: string, or ``NoneType``
:returns: the URL (None until set from the server).
"""
return self._properties.get('type')

@property
def description(self):
"""Description of the table.
:rtype: string, or ``NoneType``
:returns: The description as set by the user, or None (the default).
"""
return self._properties.get('description')

@description.setter
def description(self, value):
"""Update description of the table.
:type value: string, or ``NoneType``
:param value: new description
:raises: ValueError for invalid value types.
"""
if not isinstance(value, six.string_types) and value is not None:
raise ValueError("Pass a string, or None")
self._properties['description'] = value

@property
def expires(self):
"""Datetime at which the table will be removed.
:rtype: ``datetime.datetime``, or ``NoneType``
:returns: the expiration time, or None
"""
return _datetime_from_prop(self._properties.get('expirationTime'))

@expires.setter
def expires(self, value):
"""Update atetime at which the table will be removed.
:type value: ``datetime.datetime``, or ``NoneType``
:param value: the new expiration time, or None
"""
if not isinstance(value, datetime.datetime) and value is not None:
raise ValueError("Pass a datetime, or None")
self._properties['expirationTime'] = _prop_from_datetime(value)

@property
def friendly_name(self):
"""Title of the table.
:rtype: string, or ``NoneType``
:returns: The name as set by the user, or None (the default).
"""
return self._properties.get('friendlyName')

@friendly_name.setter
def friendly_name(self, value):
"""Update title of the table.
:type value: string, or ``NoneType``
:param value: new title
:raises: ValueError for invalid value types.
"""
if not isinstance(value, six.string_types) and value is not None:
raise ValueError("Pass a string, or None")
self._properties['friendlyName'] = value

@property
def location(self):
"""Location in which the table is hosted.
:rtype: string, or ``NoneType``
:returns: The location as set by the user, or None (the default).
"""
return self._properties.get('location')

@location.setter
def location(self, value):
"""Update location in which the table is hosted.
:type value: string, or ``NoneType``
:param value: new location
:raises: ValueError for invalid value types.
"""
if not isinstance(value, six.string_types) and value is not None:
raise ValueError("Pass a string, or None")
self._properties['location'] = value

@property
def view_query(self):
"""SQL query defining the table as a view.
:rtype: string, or ``NoneType``
:returns: The query as set by the user, or None (the default).
"""
view = self._properties.get('view')
if view is not None:
return view.get('query')

@view_query.setter
def view_query(self, value):
"""Update SQL query defining the table as a view.
:type value: string
:param value: new location
:raises: ValueError for invalid value types.
"""
if not isinstance(value, six.string_types):
raise ValueError("Pass a string")
self._properties['view'] = {'query': value}

@view_query.deleter
def view_query(self):
"""Delete SQL query defining the table as a view."""
self._properties.pop('view', None)
Loading

0 comments on commit 2928bfc

Please sign in to comment.