text: # Copyright 2015 Google LLC
  #
  # 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."""
  
  from __future__ import absolute_import
  
  import six
  import copy
  
  import google.cloud._helpers
  from google.cloud.bigquery import _helpers
  from google.cloud.bigquery.table import TableReference
  
  
  class AccessEntry(object):
      """Represents grant of an access role to an entity.
  
      An entry must have exactly one of the allowed :attr:`ENTITY_TYPES`. If
      anything but ``view`` is set, a ``role`` is also required. ``role`` is
      omitted for a ``view``, because ``view`` s are always read-only.
  
      See https://cloud.google.com/bigquery/docs/reference/rest/v2/datasets.
  
      Attributes:
          role (str):
              Role granted to the entity. The following string values are
              supported: `'READER'`, `'WRITER'`, `'OWNER'`. It may also be
              :data:`None` if the ``entity_type`` is ``view``.
  
          entity_type (str):
              Type of entity being granted the role. One of :attr:`ENTITY_TYPES`.
  
          entity_id (Union[str, Dict[str, str]]):
              If the ``entity_type`` is not 'view', the ``entity_id`` is the
              ``str`` ID of the entity being granted the role. If the
              ``entity_type`` is 'view', the ``entity_id`` is a ``dict``
              representing the view from a different dataset to grant access to
              in the following format::
  
                  {
                      'projectId': string,
                      'datasetId': string,
                      'tableId': string
                  }
  
      Raises:
          ValueError:
              If the ``entity_type`` is not among :attr:`ENTITY_TYPES`, or if a
              ``view`` has ``role`` set, or a non ``view`` **does not** have a
              ``role`` set.
  
      Examples:
          >>> entry = AccessEntry('OWNER', 'userByEmail', 'user@example.com')
  
          >>> view = {
          ...     'projectId': 'my-project',
          ...     'datasetId': 'my_dataset',
          ...     'tableId': 'my_table'
          ... }
          >>> entry = AccessEntry(None, 'view', view)
      """
  
      ENTITY_TYPES = frozenset(['userByEmail', 'groupByEmail', 'domain',
                                'specialGroup', 'view'])
      """Allowed entity types."""
  
      def __init__(self, role, entity_type, entity_id):
          if entity_type not in self.ENTITY_TYPES:
              message = 'Entity type %r not among: %s' % (
                  entity_type, ', '.join(self.ENTITY_TYPES))
              raise ValueError(message)
          if entity_type == 'view':
              if role is not None:
                  raise ValueError('Role must be None for a view. Received '
                                   'role: %r' % (role,))
          else:
              if role is None:
                  raise ValueError('Role must be set for entity '
                                   'type %r' % (entity_type,))
  
          self.role = role
          self.entity_type = entity_type
          self.entity_id = entity_id
  
      def __eq__(self, other):
          if not isinstance(other, AccessEntry):
              return NotImplemented
          return (
              self.role == other.role and
              self.entity_type == other.entity_type and
              self.entity_id == other.entity_id)
  
      def __ne__(self, other):
          return not self == other
  
      def __repr__(self):
          return '<AccessEntry: role=%s, %s=%s>' % (
              self.role, self.entity_type, self.entity_id)
  
      def to_api_repr(self):
          """Construct the API resource representation of this access entry
  
          Returns:
              Dict[str, object]: Access entry represented as an API resource
          """
          resource = {self.entity_type: self.entity_id}
          if self.role is not None:
              resource['role'] = self.role
          return resource
  
      @classmethod
      def from_api_repr(cls, resource):
          """Factory: construct an access entry given its API representation
  
          Args:
              resource (Dict[str, object]):
                  Access entry resource representation returned from the API
  
          Returns:
              google.cloud.bigquery.dataset.AccessEntry:
                  Access entry parsed from ``resource``.
  
          Raises:
              ValueError:
                  If the resource has more keys than ``role`` and one additional
                  key.
          """
          entry = resource.copy()
          role = entry.pop('role', None)
          entity_type, entity_id = entry.popitem()
          if len(entry) != 0:
              raise ValueError('Entry has unexpected keys remaining.', entry)
          return cls(role, entity_type, entity_id)
  
  
  class DatasetReference(object):
      """DatasetReferences are pointers to datasets.
  
      See
      https://cloud.google.com/bigquery/docs/reference/rest/v2/datasets
  
      Args:
          project (str): The ID of the project
          dataset_id (str): The ID of the dataset
  
      Raises:
          ValueError: If either argument is not of type ``str``.
      """
  
      def __init__(self, project, dataset_id):
          if not isinstance(project, six.string_types):
              raise ValueError("Pass a string for project")
          if not isinstance(dataset_id, six.string_types):
              raise ValueError("Pass a string for dataset_id")
          self._project = project
          self._dataset_id = dataset_id
  
      @property
      def project(self):
          """str: Project ID of the dataset."""
          return self._project
  
      @property
      def dataset_id(self):
          """str: Dataset ID."""
          return self._dataset_id
  
      @property
      def path(self):
          """str: URL path for the dataset based on project and dataset ID."""
          return '/projects/%s/datasets/%s' % (self.project, self.dataset_id)
  
      def table(self, table_id):
          """Constructs a TableReference.
  
          Args:
              table_id (str): The ID of the table.
  
          Returns:
              google.cloud.bigquery.table.TableReference:
                  A table reference for a table in this dataset.
          """
          return TableReference(self, table_id)
  
      @classmethod
      def from_api_repr(cls, resource):
          """Factory: construct a dataset reference given its API representation
  
          Args:
              resource (Dict[str, str]):
                  Dataset reference resource representation returned from the API
  
          Returns:
              google.cloud.bigquery.dataset.DatasetReference:
                  Dataset reference parsed from ``resource``.
          """
          project = resource['projectId']
          dataset_id = resource['datasetId']
          return cls(project, dataset_id)
  
      @classmethod
      def from_string(cls, full_dataset_id):
          """Construct a dataset reference from fully-qualified dataset ID.
  
          Args:
              full_dataset_id (str):
                  A fully-qualified dataset ID in standard SQL format. Must
                  included both the project ID and the dataset ID, separated by
                  ``.``.
  
          Returns:
              DatasetReference:
                  Dataset reference parsed from ``full_dataset_id``.
  
          Examples:
              >>> DatasetReference.from_string('my-project-id.some_dataset')
              DatasetReference('my-project-id', 'some_dataset')
  
          Raises:
              ValueError:
                  If ``full_dataset_id`` is not a fully-qualified dataset ID in
                  standard SQL format.
          """
          parts = full_dataset_id.split('.')
          if len(parts) != 2:
              raise ValueError(
                  'full_dataset_id must be a fully-qualified dataset ID in '
                  'standard SQL format. e.g. "project.dataset_id", got '
                  '{}'.format(full_dataset_id))
          return cls(*parts)
  
      def to_api_repr(self):
          """Construct the API resource representation of this dataset reference
  
          Returns:
              Dict[str, str]: dataset reference represented as an API resource
          """
          return {
              'projectId': self._project,
              'datasetId': self._dataset_id,
          }
  
      def _key(self):
          """A tuple key that uniquely describes this field.
  
          Used to compute this instance's hashcode and evaluate equality.
  
          Returns:
              Tuple[str]: The contents of this :class:`.DatasetReference`.
          """
          return (
              self._project,
              self._dataset_id,
          )
  
      def __eq__(self, other):
          if not isinstance(other, DatasetReference):
              return NotImplemented
          return self._key() == other._key()
  
      def __ne__(self, other):
          return not self == other
  
      def __hash__(self):
          return hash(self._key())
  
      def __repr__(self):
          return 'DatasetReference{}'.format(self._key())
  
  
  class Dataset(object):
      """Datasets are containers for tables.
  
      See
      https://cloud.google.com/bigquery/docs/reference/rest/v2/datasets
  
      Args:
          dataset_ref (google.cloud.bigquery.dataset.DatasetReference):
              a pointer to a dataset
      """
  
      _PROPERTY_TO_API_FIELD = {
          'access_entries': 'access',
          'created': 'creationTime',
          'default_table_expiration_ms': 'defaultTableExpirationMs',
          'friendly_name': 'friendlyName',
      }
  
      def __init__(self, dataset_ref):
          self._properties = {
              'datasetReference': dataset_ref.to_api_repr(),
              'labels': {},
          }
  
      @property
      def project(self):
          """str: Project ID of the project bound to the dataset."""
          return self._properties['datasetReference']['projectId']
  
      @property
      def path(self):
          """str: URL path for the dataset based on project and dataset ID."""
          return '/projects/%s/datasets/%s' % (self.project, self.dataset_id)
  
      @property
      def access_entries(self):
          """List[google.cloud.bigquery.dataset.AccessEntry]: Dataset's access
          entries.
  
          ``role`` augments the entity type and must be present **unless** the
          entity type is ``view``.
  
          Raises:
              TypeError: If 'value' is not a sequence
              ValueError:
                  If any item in the sequence is not an
                  :class:`~google.cloud.bigquery.dataset.AccessEntry`.
          """
          entries = self._properties.get('access', [])
          return [AccessEntry.from_api_repr(entry) for entry in entries]
  
      @access_entries.setter
      def access_entries(self, value):
          if not all(isinstance(field, AccessEntry) for field in value):
              raise ValueError('Values must be AccessEntry instances')
          entries = [entry.to_api_repr() for entry in value]
          self._properties['access'] = entries
  
      @property
      def created(self):
          """Union[datetime.datetime, None]: Datetime at which the dataset was
          created (:data:`None` until set from the server).
          """
          creation_time = self._properties.get('creationTime')
          if creation_time is not None:
              # creation_time will be in milliseconds.
              return google.cloud._helpers._datetime_from_microseconds(
                  1000.0 * float(creation_time))
  
      @property
      def dataset_id(self):
          """str: Dataset ID."""
          return self._properties['datasetReference']['datasetId']
  
      @property
      def full_dataset_id(self):
          """Union[str, None]: ID for the dataset resource (:data:`None` until
          set from the server)
  
          In the format ``project_id:dataset_id``.
          """
          return self._properties.get('id')
  
      @property
      def reference(self):
          """google.cloud.bigquery.dataset.DatasetReference: A reference to this
          dataset.
          """
          return DatasetReference(self.project, self.dataset_id)
  
      @property
      def etag(self):
          """Union[str, None]: ETag for the dataset resource (:data:`None` until
          set from the server).
          """
          return self._properties.get('etag')
  
      @property
      def modified(self):
          """Union[datetime.datetime, None]: Datetime at which the dataset was
          last modified (:data:`None` until set from the server).
          """
          modified_time = self._properties.get('lastModifiedTime')
          if modified_time is not None:
              # modified_time will be in milliseconds.
              return google.cloud._helpers._datetime_from_microseconds(
                  1000.0 * float(modified_time))
  
      @property
      def self_link(self):
          """Union[str, None]: URL for the dataset resource (:data:`None` until
          set from the server).
          """
          return self._properties.get('selfLink')
  
      @property
      def default_table_expiration_ms(self):
          """Union[int, None]: Default expiration time for tables in the dataset
          (defaults to :data:`None`).
  
          Raises:
              ValueError: For invalid value types.
          """
          return _helpers._int_or_none(
              self._properties.get('defaultTableExpirationMs'))
  
      @default_table_expiration_ms.setter
      def default_table_expiration_ms(self, value):
          if not isinstance(value, six.integer_types) and value is not None:
              raise ValueError("Pass an integer, or None")
          self._properties['defaultTableExpirationMs'] = _helpers._str_or_none(
              value)
  
      @property
      def description(self):
          """Union[str, None]: Description of the dataset as set by the user
          (defaults to :data:`None`).
  
          Raises:
              ValueError: for invalid value types.
          """
          return self._properties.get('description')
  
      @description.setter
      def description(self, value):
          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 friendly_name(self):
          """Union[str, None]: Title of the dataset as set by the user
          (defaults to :data:`None`).
  
          Raises:
              ValueError: for invalid value types.
          """
          return self._properties.get('friendlyName')
  
      @friendly_name.setter
      def friendly_name(self, value):
          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):
          """Union[str, None]: Location in which the dataset is hosted as set by
          the user (defaults to :data:`None`).
  
          Raises:
              ValueError: for invalid value types.
          """
          return self._properties.get('location')
  
      @location.setter
      def location(self, value):
          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 labels(self):
          """Dict[str, str]: Labels for the dataset.
  
          This method always returns a dict. To change a dataset's labels,
          modify the dict, then call
          :meth:`google.cloud.bigquery.client.Client.update_dataset`. To delete
          a label, set its value to :data:`None` before updating.
  
          Raises:
              ValueError: for invalid value types.
          """
          return self._properties.setdefault('labels', {})
  
      @labels.setter
      def labels(self, value):
          if not isinstance(value, dict):
              raise ValueError("Pass a dict")
          self._properties['labels'] = value
  
      @classmethod
      def from_string(cls, full_dataset_id):
          """Construct a dataset from fully-qualified dataset ID.
  
          Args:
              full_dataset_id (str):
                  A fully-qualified dataset ID in standard SQL format. Must
                  included both the project ID and the dataset ID, separated by
                  ``.``.
  
          Returns:
              Dataset: Dataset parsed from ``full_dataset_id``.
  
          Examples:
              >>> Dataset.from_string('my-project-id.some_dataset')
              Dataset(DatasetReference('my-project-id', 'some_dataset'))
  
          Raises:
              ValueError:
                  If ``full_dataset_id`` is not a fully-qualified dataset ID in
                  standard SQL format.
          """
          return cls(DatasetReference.from_string(full_dataset_id))
  
      @classmethod
      def from_api_repr(cls, resource):
          """Factory: construct a dataset given its API representation
  
          Args:
              resource (Dict[str: object]):
                  Dataset resource representation returned from the API
  
          Returns:
              google.cloud.bigquery.dataset.Dataset:
                  Dataset parsed from ``resource``.
          """
          if ('datasetReference' not in resource or
                  'datasetId' not in resource['datasetReference']):
              raise KeyError('Resource lacks required identity information:'
                             '["datasetReference"]["datasetId"]')
          project_id = resource['datasetReference']['projectId']
          dataset_id = resource['datasetReference']['datasetId']
          dataset = cls(DatasetReference(project_id, dataset_id))
          dataset._properties = copy.deepcopy(resource)
          return dataset
  
      def to_api_repr(self):
          """Construct the API resource representation of this dataset
  
          Returns:
              Dict[str, object]: The dataset represented as an API resource
          """
          return copy.deepcopy(self._properties)
  
      def _build_resource(self, filter_fields):
          """Generate a resource for ``update``."""
          partial = {}
          for filter_field in filter_fields:
              api_field = self._PROPERTY_TO_API_FIELD.get(filter_field)
              if api_field is None and filter_field not in self._properties:
                  raise ValueError('No Dataset property %s' % filter_field)
              elif api_field is not None:
                  partial[api_field] = self._properties.get(api_field)
              else:
                  # allows properties that are not defined in the library
                  # and properties that have the same name as API resource key
                  partial[filter_field] = self._properties[filter_field]
  
          return partial
  
      def table(self, table_id):
          """Constructs a TableReference.
  
          Args:
              table_id (str): the ID of the table.
  
          Returns:
              google.cloud.bigquery.table.TableReference:
                  A TableReference for a table in this dataset.
          """
          return TableReference(self.reference, table_id)
  
      def __repr__(self):
          return 'Dataset({})'.format(repr(self.reference))
  
  
  class DatasetListItem(object):
      """A read-only dataset resource from a list operation.
  
      For performance reasons, the BigQuery API only includes some of the
      dataset properties when listing datasets. Notably,
      :attr:`~google.cloud.bigquery.dataset.Dataset.access_entries` is missing.
  
      For a full list of the properties that the BigQuery API returns, see the
      `REST documentation for datasets.list
      <https://cloud.google.com/bigquery/docs/reference/rest/v2/datasets/list>`_.
  
  
      Args:
          resource (Dict[str, str]):
              A dataset-like resource object from a dataset list response. A
              ``datasetReference`` property is required.
  
      Raises:
          ValueError:
              If ``datasetReference`` or one of its required members is missing
              from ``resource``.
      """
  
      def __init__(self, resource):
          if 'datasetReference' not in resource:
              raise ValueError('resource must contain a datasetReference value')
          if 'projectId' not in resource['datasetReference']:
              raise ValueError(
                  "resource['datasetReference'] must contain a projectId value")
          if 'datasetId' not in resource['datasetReference']:
              raise ValueError(
                  "resource['datasetReference'] must contain a datasetId value")
          self._properties = resource
  
      @property
      def project(self):
          """str: Project bound to the dataset."""
          return self._properties['datasetReference']['projectId']
  
      @property
      def dataset_id(self):
          """str: Dataset ID."""
          return self._properties['datasetReference']['datasetId']
  
      @property
      def full_dataset_id(self):
          """Union[str, None]: ID for the dataset resource (:data:`None` until
          set from the server)
  
          In the format ``project_id:dataset_id``.
          """
          return self._properties.get('id')
  
      @property
      def friendly_name(self):
          """Union[str, None]: Title of the dataset as set by the user
          (defaults to :data:`None`).
          """
          return self._properties.get('friendlyName')
  
      @property
      def labels(self):
          """Dict[str, str]: Labels for the dataset."""
          return self._properties.setdefault('labels', {})
  
      @property
      def reference(self):
          """google.cloud.bigquery.dataset.DatasetReference: A reference to this
          dataset.
          """
          return DatasetReference(self.project, self.dataset_id)
  
      def table(self, table_id):
          """Constructs a TableReference.
  
          Args:
              table_id (str): the ID of the table.
  
          Returns:
              google.cloud.bigquery.table.TableReference:
                  A TableReference for a table in this dataset.
          """
          return TableReference(self.reference, table_id)
  