Skip to content

Commit 9d5e3ea

Browse files
committed
refactor(firestore): address review feedback for BSON read deserialization
- Perform automatic BSON deserialization in decode_dict and DocumentSnapshot.to_dict using _BSONType._from_dict. - Remove decode_bson configuration parameter across Client, AsyncClient, BaseClient, and DocumentSnapshot. - Preserve precise return type annotations in decode_dict and restore docstring Raises section. Towards #18402
1 parent eb43811 commit 9d5e3ea

8 files changed

Lines changed: 18 additions & 62 deletions

File tree

‎packages/google-cloud-firestore/google/cloud/firestore_v1/_helpers.py‎

Lines changed: 13 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -347,18 +347,20 @@ def reference_value_to_document(reference_value, client) -> Any:
347347
return document
348348

349349

350-
def decode_value(value, client=None, decode_bson: Optional[bool] = None) -> Any:
350+
def decode_value(value, client=None) -> Any:
351351
"""Converts a Firestore protobuf ``Value`` to a native Python value.
352352
353353
Args:
354354
value (google.cloud.firestore_v1.types.Value): A
355355
Firestore protobuf to be decoded / parsed / converted.
356356
client (:class:`~google.cloud.firestore_v1.client.Client`):
357357
A client that has a document factory.
358-
decode_bson (Optional[bool]): Whether to decode BSON extended types.
359358
360359
Returns:
361360
Any: A native Python value converted from the ``value``.
361+
362+
Raises:
363+
ValueError: If ``value_type`` is unknown or unsupported.
362364
"""
363365
value_pb = getattr(value, "_pb", value)
364366
value_type = value_pb.WhichOneof("value_type")
@@ -385,32 +387,19 @@ def decode_value(value, client=None, decode_bson: Optional[bool] = None) -> Any:
385387
)
386388
elif value_type == "array_value":
387389
return [
388-
decode_value(element, client, decode_bson=decode_bson)
390+
decode_value(element, client)
389391
for element in value_pb.array_value.values
390392
]
391393
elif value_type == "map_value":
392-
return decode_dict(value_pb.map_value.fields, client, decode_bson=decode_bson)
394+
return decode_dict(value_pb.map_value.fields, client)
393395
else:
394396
raise ValueError("Unknown ``value_type``", value_type)
395397

396398

397-
def _decode_bson_dict(data: dict) -> Optional[_BSONType]:
398-
"""Decode a single-key wire map dictionary if registered."""
399-
if len(data) == 1:
400-
key, val = next(iter(data.items()))
401-
decoder = _BSON_DECODERS.get(key)
402-
if decoder is not None:
403-
try:
404-
return decoder(val)
405-
except Exception:
406-
pass
407-
return None
408-
409-
410399
def _decode_bson_dict_recursive(data: Any) -> Any:
411400
"""Recursively decodes BSON wire map dictionaries."""
412401
if isinstance(data, dict):
413-
decoded = _decode_bson_dict(data)
402+
decoded = _BSONType._from_dict(data)
414403
if decoded is not None:
415404
return decoded
416405
return {k: _decode_bson_dict_recursive(v) for k, v in data.items()}
@@ -422,25 +411,23 @@ def _decode_bson_dict_recursive(data: Any) -> Any:
422411
def decode_dict(
423412
value_fields,
424413
client=None,
425-
decode_bson: Optional[bool] = None,
426-
) -> Union[dict, Vector, _BSONType]:
414+
) -> Union[dict, Vector, _BSONType, bytes]:
427415
"""Converts a protobuf map of Firestore ``Value``-s.
428416
429417
Args:
430418
value_fields (google.protobuf.pyext._message.MessageMapContainer): A
431419
protobuf map of Firestore ``Value``-s.
432420
client (:class:`~google.cloud.firestore_v1.client.Client`):
433421
A client that has a document factory.
434-
decode_bson (Optional[bool]): Whether to decode BSON extended types.
435422
436423
Returns:
437424
Union[dict, ~google.cloud.firestore_v1.vector.Vector, \
438-
~google.cloud.firestore_v1.bson._BSONType]: A dictionary of native \
425+
~google.cloud.firestore_v1.bson._BSONType, bytes]: A dictionary of native \
439426
Python values, Vector, or BSON object converted from ``value_fields``.
440427
"""
441428
value_fields_pb = getattr(value_fields, "_pb", value_fields)
442429
res = {
443-
key: decode_value(value, client, decode_bson=decode_bson)
430+
key: decode_value(value, client)
444431
for key, value in value_fields_pb.items()
445432
}
446433

@@ -450,15 +437,9 @@ def decode_dict(
450437
values = cast(Sequence[float], res["value"])
451438
return Vector(values)
452439

453-
should_decode = (
454-
decode_bson
455-
if decode_bson is not None
456-
else getattr(client, "_decode_bson", False)
457-
)
458-
if should_decode:
459-
decoded = _decode_bson_dict(res)
460-
if decoded is not None:
461-
return decoded
440+
decoded = _BSONType._from_dict(res)
441+
if decoded is not None:
442+
return decoded
462443

463444
return res
464445

‎packages/google-cloud-firestore/google/cloud/firestore_v1/async_client.py‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,15 +105,13 @@ def __init__(
105105
database=None,
106106
client_info=_CLIENT_INFO,
107107
client_options=None,
108-
decode_bson: bool = False,
109108
) -> None:
110109
super(AsyncClient, self).__init__(
111110
project=project,
112111
credentials=credentials,
113112
database=database,
114113
client_info=client_info,
115114
client_options=client_options,
116-
decode_bson=decode_bson,
117115
)
118116

119117
def _to_sync_copy(self):

‎packages/google-cloud-firestore/google/cloud/firestore_v1/base_client.py‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,6 @@ def __init__(
132132
database=None,
133133
client_info=_CLIENT_INFO,
134134
client_options=None,
135-
decode_bson: bool = False,
136135
) -> None:
137136
database = database or DEFAULT_DATABASE
138137
# NOTE: This API has no use for the _http argument, but sending it
@@ -166,7 +165,6 @@ def __init__(
166165
self._client_options = client_options
167166

168167
self._database = database
169-
self._decode_bson: bool = decode_bson
170168

171169
def _firestore_api_helper(self, transport, client_class, client_module) -> Any:
172170
"""Lazy-loading getter GAPIC Firestore API.

‎packages/google-cloud-firestore/google/cloud/firestore_v1/base_document.py‎

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -512,17 +512,12 @@ def get(self, field_path: str) -> Any:
512512
nested_data = field_path_module.get_nested_value(field_path, self._data)
513513
return copy.deepcopy(nested_data)
514514

515-
def to_dict(
516-
self, decode_bson: Optional[bool] = None
517-
) -> Union[Dict[str, Any], None]:
515+
def to_dict(self) -> Union[Dict[str, Any], None]:
518516
"""Retrieve the data contained in this snapshot.
519517
520518
A copy is returned since the data may contain mutable values,
521519
but the data stored in the snapshot must remain immutable.
522520
523-
Args:
524-
decode_bson (Optional[bool]): Whether to decode BSON extended types.
525-
526521
Returns:
527522
Dict[str, Any] or None:
528523
The data in the snapshot. Returns None if reference
@@ -531,15 +526,7 @@ def to_dict(
531526
if not self._exists:
532527
return None
533528
data = copy.deepcopy(self._data)
534-
client = self._reference._client if self._reference is not None else None
535-
should_decode = (
536-
decode_bson
537-
if decode_bson is not None
538-
else getattr(client, "_decode_bson", False)
539-
)
540-
if should_decode:
541-
return _helpers._decode_bson_dict_recursive(data)
542-
return data
529+
return _helpers._decode_bson_dict_recursive(data)
543530

544531
def _to_protobuf(self) -> Optional[Document]:
545532
return _helpers.document_snapshot_to_protobuf(self)

‎packages/google-cloud-firestore/google/cloud/firestore_v1/client.py‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,15 +94,13 @@ def __init__(
9494
database=None,
9595
client_info=_CLIENT_INFO,
9696
client_options=None,
97-
decode_bson: bool = False,
9897
) -> None:
9998
super(Client, self).__init__(
10099
project=project,
101100
credentials=credentials,
102101
database=database,
103102
client_info=client_info,
104103
client_options=client_options,
105-
decode_bson=decode_bson,
106104
)
107105

108106
@property

‎packages/google-cloud-firestore/tests/system/test_system.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1307,7 +1307,7 @@ def test_bson_document_read_and_write(client, cleanup, database):
13071307

13081308
snapshot = doc_ref.get()
13091309
assert snapshot.exists
1310-
assert snapshot.to_dict(decode_bson=True) == bson_payload
1310+
assert snapshot.to_dict() == bson_payload
13111311

13121312

13131313
@pytest.fixture(scope="module")

‎packages/google-cloud-firestore/tests/system/test_system_async.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1280,7 +1280,7 @@ async def test_async_bson_document_read_and_write(client, cleanup, database):
12801280

12811281
snapshot = await doc_ref.get()
12821282
assert snapshot.exists
1283-
assert snapshot.to_dict(decode_bson=True) == bson_payload
1283+
assert snapshot.to_dict() == bson_payload
12841284

12851285

12861286
@pytest_asyncio.fixture(scope="module")

‎packages/google-cloud-firestore/tests/unit/v1/test__helpers.py‎

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -733,13 +733,7 @@ def test_decode_dict_w_bson_types():
733733
}
734734

735735
pb_fields = encode_dict(original_dict)
736-
# Default (decode_bson=False) returns raw dict
737-
raw_decoded = decode_dict(pb_fields, mock.sentinel.client)
738-
assert raw_decoded != original_dict
739-
assert raw_decoded["oid"] == {"__oid__": "507f191e810c19729de860ea"}
740-
741-
# decode_bson=True returns deserialized BSON objects
742-
decoded = decode_dict(pb_fields, mock.sentinel.client, decode_bson=True)
736+
decoded = decode_dict(pb_fields, mock.sentinel.client)
743737
assert decoded == original_dict
744738

745739

0 commit comments

Comments
 (0)