From 337ec30b6d5c7ce09904438efaa9442b58765dc1 Mon Sep 17 00:00:00 2001 From: ohmayr Date: Wed, 16 Sep 2026 22:56:59 +0000 Subject: [PATCH] feat(firestore): add PyMongo duck-typing serialization support --- .../google/cloud/firestore_v1/_helpers.py | 26 ++++++++++++++++ .../tests/unit/v1/test__helpers.py | 30 +++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/packages/google-cloud-firestore/google/cloud/firestore_v1/_helpers.py b/packages/google-cloud-firestore/google/cloud/firestore_v1/_helpers.py index 51f288f9a84c..258bba65d5f8 100644 --- a/packages/google-cloud-firestore/google/cloud/firestore_v1/_helpers.py +++ b/packages/google-cloud-firestore/google/cloud/firestore_v1/_helpers.py @@ -214,6 +214,32 @@ def encode_value(value) -> types.document.Value: if isinstance(value, _BSONType): return encode_value(value._to_map_value()) + # Duck-type native PyMongo / third-party BSON objects + if hasattr(value, "__class__"): + cls_name = value.__class__.__name__ + if cls_name == "ObjectId" and hasattr(value, "binary"): + return encode_value({"__oid__": str(value).lower()}) + if cls_name == "Decimal128" and hasattr(value, "to_decimal"): + return encode_value({"__decimal128__": str(value)}) + if cls_name == "Regex" and hasattr(value, "pattern"): + opts = getattr(value, "flags", "") or getattr(value, "options", "") + return encode_value( + {"__regex__": {"pattern": value.pattern, "options": str(opts)}} + ) + if cls_name == "Timestamp" and hasattr(value, "time") and hasattr(value, "inc"): + return encode_value( + { + "__request_timestamp__": { + "seconds": value.time, + "increment": value.inc, + } + } + ) + if cls_name == "MinKey": + return encode_value({"__min__": None}) + if cls_name == "MaxKey": + return encode_value({"__max__": None}) + if isinstance(value, GeoPoint): return document.Value(geo_point_value=value.to_protobuf()) diff --git a/packages/google-cloud-firestore/tests/unit/v1/test__helpers.py b/packages/google-cloud-firestore/tests/unit/v1/test__helpers.py index b0b81eb98e51..88a493711d50 100644 --- a/packages/google-cloud-firestore/tests/unit/v1/test__helpers.py +++ b/packages/google-cloud-firestore/tests/unit/v1/test__helpers.py @@ -46,6 +46,36 @@ def test_geopoint_to_protobuf(): assert result == geo_pt_pb +def test_encode_value_pymongo_duck_typing(): + from google.cloud.firestore_v1._helpers import encode_value + + class ObjectId: + def __init__(self, val): + self.val = val + self.binary = b"12bytes_raw_" + + def __str__(self): + return self.val + + class Decimal128: + def __init__(self, val): + self.val = val + + def to_decimal(self): + return self.val + + def __str__(self): + return self.val + + oid_obj = ObjectId("507f191e810c19729de860ea") + oid_pb = encode_value(oid_obj) + assert oid_pb.map_value.fields["__oid__"].string_value == "507f191e810c19729de860ea" + + dec_obj = Decimal128("123.45") + dec_pb = encode_value(dec_obj) + assert dec_pb.map_value.fields["__decimal128__"].string_value == "123.45" + + def test_geopoint___eq__w_same_value(): lat = 0.015625 lng = 20.03125