Skip to content

Commit

Permalink
Firestore Path with unit tests.
Browse files Browse the repository at this point in the history
  • Loading branch information
mckoss authored Nov 29, 2016
1 parent 4c137b5 commit 1ce89bf
Show file tree
Hide file tree
Showing 6 changed files with 398 additions and 1 deletion.
5 changes: 5 additions & 0 deletions docs/firestore-path.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Firestore Path
================
.. autoclass:: google.cloud.firestore.Path
:members:
:show-inheritance:
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@

firestore
firestore-client
firestore-path

.. toctree::
:maxdepth: 0
Expand Down
3 changes: 2 additions & 1 deletion firestore/google/cloud/firestore/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"""Firestore database API public classes."""

from google.cloud.firestore.client import Client
from google.cloud.firestore.path import Path


__all__ = ['Client']
__all__ = ['Client', 'Path']
210 changes: 210 additions & 0 deletions firestore/google/cloud/firestore/path.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
# Copyright 2016 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.

"""Firestore Path represents paths to Documents and Collections."""

import six

from google.firestore.v1alpha1.entity_pb2 import Key


class Path(object):
"""An immutable representation of a Firestore path to a Document
or Collection.
To create a Path to a root-level Collection:
.. code-block:: python
Path('documents')
Path.from_string('documents')
To create a Path to a Document:
.. code-block:: python
Path('documents', 'my-document')
Path.from_string('documents/my-document')
To create a Path to a sub-collection:
.. code-block:: python
Path('documents', 'my-document', 'sub-collection')
Path.from_string('documents/my-document/sub-collection')
# From parent document Path
doc = Path.from_string('documents/my-document')
sub_collection = doc.child('sub-collection')
:param parts: Sequence of names of the parts of a path.
:type parts: list of str or int
"""

def __init__(self, *parts):
self._parts = parts

@property
def parts(self):
"""Path components.
:rtype: tuple of str.
:returns: A sequence of the path components.
"""
return self._parts

@property
def id(self):
"""Document identifier.
:rtype: str or None
:returns: The last component of a path for a document (or None if the
Path is a collection).
"""
if self.is_collection:
return None
return self._parts[-1]

def __str__(self):
return '/'.join([str(part) for part in self._parts])

def __eq__(self, other):
if isinstance(other, self.__class__):
return self.parts == other.parts
return False

def __ne__(self, other):
return not self.__eq__(other)

@property
def is_document(self):
"""Path references a :class:`google.cloud.firestore.Document`.
:rtype: bool
:returns: True if the path is a Document reference.
"""
return len(self._parts) % 2 == 0

@property
def is_collection(self):
"""Path references a :class:`google.cloud.firestore.Collection`.
:rtype: bool
:returns: True if the path is a Collection reference.
"""
return len(self._parts) % 2 == 1

@property
def is_empty(self):
"""Path has no components.
:rtype: bool
:returns: True if the path has no components.
"""
return not self._parts

def child(self, *parts):
"""Create a child path beneath the current one.
:type parts: list of str or int.
:param parts: Path components to append.
:rtype: :class:`Path`
:returns: A new Path formed by appending the given components to the
current Path.
"""
return Path(*(self._parts + parts))

def parent(self):
"""Create a Path to the parent Document or Collection.
:rtype: :class:`Path`
:returns: A new Path formed by remove the last Path component.
"""
return Path(*self._parts[:-1])

@classmethod
def from_string(cls, path_string):
"""Parses a path string to return a Path.
:type path_string: str
:param path_string: A '/' separated path string.
:rtype: :class:`Path`
:returns: A Path corresponding to the given path string.
"""
parts = path_string.rstrip('/').split('/')
for i in six.moves.xrange(1, len(parts), 2):
try:
parts[i] = int(parts[i])
except ValueError:
pass

return cls(*parts)

@property
def kind(self):
"""Get the collection name.
:rtype: str
:returns: The collection "kind" of the current Path.
"""
if self.is_collection:
return self._parts[-1]
else:
return self._parts[-2]

def to_key_proto(self):
"""Convert Path to protobuf Firestore Key.
:rtype: :class:`google.firestore.v1alpha1.entity_pb2.Key`
:returns: Protobuf Firestore Key.
"""

results = []
key_pairs = zip(self._parts[::2], self._parts[1::2])
for collection_name, document_id in key_pairs:
if isinstance(document_id, six.integer_types):
part = Key.PathElement(
kind=collection_name, id=document_id)
else:
part = Key.PathElement(kind=collection_name, name=document_id)
results.append(part)

# Last collection not paired by zip() above.
if self.is_collection:
results.append(Key.PathElement(kind=self.kind))

return Key(path=results)

@classmethod
def from_key_proto(cls, key):
"""Decode a protobuf Firestore Key into a Path.
:type key: :class:`google.firestore.v1alpha1.entity_pb2.Key`
:param key: A protobuf Firestore Key.
:rtype: :class:`Path`
:returns: The Path corresponding to a protobuf Firestore Key.
"""

parts = []
for part in key.path:
parts.append(part.kind)
if part.WhichOneof('id_type') == 'name':
parts.append(part.name)
elif part.WhichOneof('id_type') == 'id':
parts.append(part.id)

return cls(*parts)
Loading

0 comments on commit 1ce89bf

Please sign in to comment.