Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions firestore/google/cloud/firestore_v1beta1/_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,70 @@ def __ne__(self, other):
return not equality_val


class FieldPath(object):
""" Field Path object for client use.

Args:
parts: (one or more strings)
Indicating path of the key to be used.
"""
simple_field_name = re.compile(r'[A-Za-z_][A-Za-z_0-9]*')

def __init__(self, *parts):
for part in parts:
if not isinstance(part, six.string_types) or not part:
error = 'One or more components is not a string or is empty.'
raise ValueError(error)
self.parts = tuple(parts)

@staticmethod
def from_string(string):
""" Creates a FieldPath from a unicode string representation.

Args:
:type string: str
:param string: A unicode string which cannot contain
`~*/[]` characters, cannot exceed 1500 bytes,
and cannot be empty.

Returns:
A :class: `FieldPath` instance with the string split on "."
as arguments to `FieldPath`.
"""
invalid_characters = '~*/[]'
for invalid_character in invalid_characters:
if invalid_character in string:
raise ValueError('Invalid characters in string.')
string = string.split('.')
return FieldPath(*string)

def to_api_repr(self):
""" Returns quoted string representation of the FieldPath

Returns: :rtype: str
Quoted string representation of the path stored
within this FieldPath conforming to the Firestore API
specification
"""
ans = []
for part in self.parts:
match = re.match(self.simple_field_name, part)
if match:
ans.append(part)
else:
replaced = part.replace('\\', '\\\\').replace('`', '\\`')
ans.append('`' + replaced + '`')
return '.'.join(ans)

def __hash__(self):
return hash(self.to_api_repr())

def __eq__(self, other):

This comment was marked as spam.

if isinstance(other, FieldPath):
return self.parts == other.parts
return NotImplemented


class FieldPathHelper(object):
"""Helper to convert field names and paths for usage in a request.

Expand Down
155 changes: 155 additions & 0 deletions firestore/tests/unit/test__helpers.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# Copyright 2017 Google LLC All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
Expand Down Expand Up @@ -86,6 +87,160 @@ def test___ne__type_differ(self):
self.assertIs(geo_pt1.__ne__(geo_pt2), NotImplemented)


class TestFieldPath(unittest.TestCase):

@staticmethod
def _get_target_class():
from google.cloud.firestore_v1beta1._helpers import FieldPath
return FieldPath

def _make_one(self, *args, **kwargs):
klass = self._get_target_class()
return klass(*args, **kwargs)

def test_none_fails(self):
with self.assertRaises(ValueError):
self._make_one('a', None, 'b')

def test_empty_string_in_part_fails(self):
with self.assertRaises(ValueError):
self._make_one('a', '', 'b')

def test_integer_fails(self):
with self.assertRaises(ValueError):
self._make_one('a', 3, 'b')

def test_iterable_fails(self):
with self.assertRaises(ValueError):
self._make_one('a', ['a'], 'b')

def test_invalid_chars_in_constructor(self):
parts = '~*/[].'
for part in parts:
field_path = self._make_one(part)
self.assertEqual(field_path.parts, (part, ))

def test_component(self):
field_path = self._make_one('a..b')
self.assertEquals(field_path.parts, ('a..b',))

def test_constructor_iterable(self):
field_path = self._make_one('a', 'b', 'c')
self.assertEqual(field_path.parts, ('a', 'b', 'c'))

def test_unicode(self):
field_path = self._make_one('一', '二', '三')
self.assertEqual(field_path.parts, ('一', '二', '三'))

def test_to_api_repr_a(self):
parts = 'a'
field_path = self._make_one(parts)
self.assertEqual('a', field_path.to_api_repr())

def test_to_api_repr_backtick(self):
parts = '`'
field_path = self._make_one(parts)
self.assertEqual('`\``', field_path.to_api_repr())

def test_to_api_repr_slash(self):
parts = '\\'
field_path = self._make_one(parts)
self.assertEqual(field_path.to_api_repr(), r'`\\`')

def test_to_api_repr_double_slash(self):
parts = r'\\'
field_path = self._make_one(parts)
self.assertEqual(field_path.to_api_repr(), r'`\\\\`')

def test_to_api_repr_underscore(self):
parts = '_33132'
field_path = self._make_one(parts)
self.assertEqual(field_path.to_api_repr(), '_33132')

def test_to_api_repr_unicode_non_simple(self):
parts = '一'
field_path = self._make_one(parts)
self.assertEqual(field_path.to_api_repr(), '`一`')

def test_to_api_repr_number_non_simple(self):
parts = '03'
field_path = self._make_one(parts)
self.assertEqual(field_path.to_api_repr(), '`03`')

def test_to_api_repr_simple(self):
parts = 'a0332432'
field_path = self._make_one(parts)
self.assertEqual(field_path.to_api_repr(), 'a0332432')

def test_to_api_repr_chain(self):
parts = 'a', '`', '\\', '_3', '03', 'a03', '\\\\', 'a0332432', '一'
field_path = self._make_one(*parts)
self.assertEqual(field_path.to_api_repr(),
r'a.`\``.`\\`._3.`03`.a03.`\\\\`.a0332432.`一`')

def test_from_string(self):
field_path = self._get_target_class().from_string('a.b.c')
self.assertEqual(field_path.parts, ('a', 'b', 'c'))

def test_list_splat(self):
parts = ['a', 'b', 'c']
field_path = self._make_one(*parts)
self.assertEqual(field_path.parts, ('a', 'b', 'c'))

def test_tuple_splat(self):
parts = ('a', 'b', 'c')
field_path = self._make_one(*parts)
self.assertEqual(field_path.parts, ('a', 'b', 'c'))

def test_invalid_chars_from_string_fails(self):
parts = '~*/[].'
for part in parts:
with self.assertRaises(ValueError):
self._get_target_class().from_string(part)

def test_empty_string_fails(self):
parts = ''
with self.assertRaises(ValueError):
self._get_target_class().from_string(parts)

def test_list_fails(self):
parts = ['a', 'b', 'c']
with self.assertRaises(ValueError):
self._make_one(parts)

def test_tuple_fails(self):
parts = ('a', 'b', 'c')
with self.assertRaises(ValueError):
self._make_one(parts)

def test_equality(self):
field_path = self._make_one('a', 'b')
string_path = self._get_target_class().from_string('a.b')
self.assertEqual(field_path, string_path)

def test_non_equal_types(self):
import mock
mock = mock.Mock()
mock.parts = 'a', 'b'
field_path = self._make_one('a', 'b')
self.assertNotEqual(field_path, mock)

def test_key(self):
field_path = self._make_one('a321', 'b456')
field_path_same = self._get_target_class().from_string('a321.b456')
field_path_different = self._make_one('a321', 'b457')
keys = {
field_path: '',
field_path_same: '',
field_path_different: ''
}
for key in keys:
if key == field_path_different:
self.assertNotEqual(key, field_path)
else:
self.assertEqual(key, field_path)


class TestFieldPathHelper(unittest.TestCase):

@staticmethod
Expand Down