Skip to content

Commit e926301

Browse files
authored
All non-simple field names are converted into unicode (googleapis#4859)
* Firestore: Fix for quoting simple field names with integer beginnings * Added comments * Fix bug for multiple field names in field paths * Escape all non simple paths * Review Changes
1 parent 186f882 commit e926301

4 files changed

Lines changed: 81 additions & 1 deletion

File tree

firestore/google/cloud/firestore_v1beta1/_helpers.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import collections
1919
import contextlib
2020
import datetime
21+
import re
2122
import sys
2223

2324
import google.gax
@@ -67,6 +68,7 @@
6768
grpc.StatusCode.ALREADY_EXISTS: exceptions.Conflict,
6869
grpc.StatusCode.NOT_FOUND: exceptions.NotFound,
6970
}
71+
_UNESCAPED_FIELD_NAME_RE = re.compile('^[_a-zA-Z][_a-zA-Z0-9]*$')
7072

7173

7274
class GeoPoint(object):
@@ -837,6 +839,38 @@ def pbs_for_set(document_path, document_data, option):
837839
return write_pbs
838840

839841

842+
def canonicalize_field_paths(field_paths):
843+
"""Converts simple field path with integer beginnings to quoted field path
844+
845+
Args:
846+
field_paths (Sequence[str]): A list of field paths
847+
848+
Returns:
849+
Sequence[str]:
850+
The same list of field paths except non-simple field names
851+
in the `.` delimited field path have been converted
852+
into quoted unicode field paths. Simple field paths match
853+
the regex ^[_a-zA-Z][_a-zA-Z0-9]*$. See `Document`_ page for
854+
more information.
855+
856+
.. _Document: https://cloud.google.com/firestore/docs/reference/rpc/google.firestore.v1beta1#google.firestore.v1beta1.Document # NOQA
857+
"""
858+
canonical_strings = []
859+
for field_path in field_paths:
860+
escaped_names = []
861+
field_names = field_path.split('.')
862+
for field_name in field_names:
863+
if re.match(_UNESCAPED_FIELD_NAME_RE, field_name):
864+
escaped_name = field_name
865+
else:
866+
escaped_name = u"`{}`".format(
867+
field_name.replace('\\', '\\\\').replace('`', '``'))
868+
escaped_names.append(escaped_name)
869+
new_field_path = '.'.join(escaped_names)
870+
canonical_strings.append(new_field_path)
871+
return canonical_strings
872+
873+
840874
def pbs_for_update(client, document_path, field_updates, option):
841875
"""Make ``Write`` protobufs for ``update()`` methods.
842876
@@ -860,6 +894,7 @@ def pbs_for_update(client, document_path, field_updates, option):
860894

861895
transform_paths, actual_updates = remove_server_timestamp(field_updates)
862896
update_values, field_paths = FieldPathHelper.to_field_paths(actual_updates)
897+
field_paths = canonicalize_field_paths(field_paths)
863898

864899
update_pb = write_pb2.Write(
865900
update=document_pb2.Document(

firestore/google/cloud/firestore_v1beta1/client.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -434,7 +434,6 @@ class CreateIfMissingOption(WriteOption):
434434
create_if_missing (bool): Indicates if the document should be created
435435
if it doesn't already exist.
436436
"""
437-
438437
def __init__(self, create_if_missing):
439438
self._create_if_missing = create_if_missing
440439

firestore/tests/system.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,38 @@ def test_document_set(client, cleanup):
194194
assert exc_to_code(exc_info.value.cause) == StatusCode.FAILED_PRECONDITION
195195

196196

197+
def test_document_integer_field(client, cleanup):
198+
document_id = 'for-set' + unique_resource_id('-')
199+
document = client.document('i-did-it', document_id)
200+
# Add to clean-up before API request (in case ``set()`` fails).
201+
cleanup(document)
202+
203+
data1 = {
204+
'1a': {
205+
'2b': '3c',
206+
'ab': '5e'},
207+
'6f': {
208+
'7g': '8h',
209+
'cd': '0j'}
210+
}
211+
option1 = client.write_option(exists=False)
212+
document.set(data1, option=option1)
213+
214+
data2 = {'1a.ab': '4d', '6f.7g': '9h'}
215+
option2 = client.write_option(create_if_missing=True)
216+
document.update(data2, option=option2)
217+
snapshot = document.get()
218+
expected = {
219+
'1a': {
220+
'2b': '3c',
221+
'ab': '4d'},
222+
'6f': {
223+
'7g': '9h',
224+
'cd': '0j'}
225+
}
226+
assert snapshot.to_dict() == expected
227+
228+
197229
def test_update_document(client, cleanup):
198230
document_id = 'for-update' + unique_resource_id('-')
199231
document = client.document('made', document_id)

firestore/tests/unit/test__helpers.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1186,6 +1186,20 @@ def test_update_and_transform(self):
11861186
self._helper(do_transform=True)
11871187

11881188

1189+
class Test_canonicalize_field_paths(unittest.TestCase):
1190+
1191+
def test_canonicalize_field_paths(self):
1192+
from google.cloud.firestore_v1beta1 import _helpers
1193+
field_paths = ['0abc.deq', 'abc.654', '321.0deq._321',
1194+
u'0abc.deq', u'abc.654', u'321.0deq._321']
1195+
convert = _helpers.canonicalize_field_paths(field_paths)
1196+
self.assertListEqual(
1197+
convert,
1198+
['`0abc`.deq', 'abc.`654`', '`321`.`0deq`._321',
1199+
'`0abc`.deq', 'abc.`654`', '`321`.`0deq`._321']
1200+
)
1201+
1202+
11891203
class Test_pbs_for_update(unittest.TestCase):
11901204

11911205
@staticmethod

0 commit comments

Comments
 (0)