Skip to content

Commit 7c7622e

Browse files
committed
refactor(firestore): streamline BSON classes with strict target constructors in PR 1A
1 parent 638eebd commit 7c7622e

3 files changed

Lines changed: 28 additions & 69 deletions

File tree

packages/google-cloud-firestore/google/cloud/firestore_v1/bson.py

Lines changed: 23 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -38,23 +38,17 @@ class BSONObjectID:
3838

3939
__slots__ = ("_value",)
4040

41-
def __init__(self, value: Any):
42-
if hasattr(value, "binary"):
43-
value = getattr(value, "binary")
44-
elif hasattr(value, "value"):
45-
value = getattr(value, "value")
46-
41+
def __init__(self, value: Union[str, bytes, bytearray]):
4742
if isinstance(value, str):
4843
if not _HEX_24_REGEX.match(value):
4944
raise ValueError("BSONObjectID string must be a 24-character hex string.")
5045
self._value: str = value.lower()
51-
elif isinstance(value, (bytes, bytearray, memoryview)):
52-
raw_bytes = bytes(value)
53-
if len(raw_bytes) != 12:
46+
elif isinstance(value, (bytes, bytearray)):
47+
if len(value) != 12:
5448
raise ValueError("BSONObjectID bytes input must be 12 raw bytes.")
55-
self._value = raw_bytes.hex()
49+
self._value: str = bytes(value).hex()
5650
else:
57-
raise TypeError("BSONObjectID requires str, bytes, bytearray, memoryview, or BSONObjectID instance.")
51+
raise TypeError("BSONObjectID requires str or bytes.")
5852

5953
@property
6054
def value(self) -> str:
@@ -81,11 +75,9 @@ class BSONDecimal128:
8175

8276
__slots__ = ("_value",)
8377

84-
def __init__(self, value: Any):
78+
def __init__(self, value: Union[str, decimal.Decimal]):
8579
if isinstance(value, bool):
8680
raise TypeError("BSONDecimal128 value cannot be bool.")
87-
if hasattr(value, "to_decimal") and callable(getattr(value, "to_decimal")):
88-
value = getattr(value, "to_decimal")()
8981

9082
if isinstance(value, decimal.Decimal):
9183
self._value: str = str(value)
@@ -96,13 +88,14 @@ def __init__(self, value: Any):
9688
raise ValueError(f"Invalid Decimal128 string format: {value!r}") from exc
9789
self._value = value
9890
else:
99-
raise TypeError("BSONDecimal128 requires str, decimal.Decimal, or Decimal128-like object.")
91+
raise TypeError("BSONDecimal128 requires str or decimal.Decimal.")
10092

10193
@property
10294
def value(self) -> str:
10395
return self._value
10496

10597
def to_decimal(self) -> decimal.Decimal:
98+
"""Converts to a native Python decimal.Decimal object."""
10699
return decimal.Decimal(self._value)
107100

108101
def to_map_value(self) -> Dict[str, str]:
@@ -140,7 +133,12 @@ class BSONTimestamp:
140133
__slots__ = ("_seconds", "_increment")
141134

142135
def __init__(self, seconds: int, increment: int):
143-
if type(seconds) is not int or type(increment) is not int:
136+
if (
137+
type(seconds) is not int
138+
or type(increment) is not int
139+
or isinstance(seconds, bool)
140+
or isinstance(increment, bool)
141+
):
144142
raise TypeError("seconds and increment must be ints.")
145143
if not (0 <= seconds <= 4294967295) or not (0 <= increment <= 4294967295):
146144
raise ValueError("seconds and increment must be uint32 (0 to 4294967295).")
@@ -176,11 +174,7 @@ class BSONRegex:
176174

177175
__slots__ = ("_pattern", "_options")
178176

179-
def __init__(self, pattern: Any, options: str = ""):
180-
if hasattr(pattern, "pattern") and not isinstance(pattern, str):
181-
flags = getattr(pattern, "flags", "")
182-
options = flags if isinstance(flags, str) else options
183-
pattern = getattr(pattern, "pattern")
177+
def __init__(self, pattern: str, options: str = ""):
184178
if not isinstance(pattern, str) or not isinstance(options, str):
185179
raise TypeError("pattern and options must be strings.")
186180
self._pattern: str = pattern
@@ -215,15 +209,14 @@ class BSONBinary:
215209

216210
__slots__ = ("_subtype", "_data")
217211

218-
def __init__(self, data: Union[bytes, bytearray, memoryview], subtype: int = 0):
212+
def __init__(self, data: Union[bytes, bytearray], subtype: int = 0):
219213
if isinstance(subtype, bool) or type(subtype) is not int:
220214
raise TypeError("subtype must be an integer.")
221215
if not (0 <= subtype <= 255):
222216
raise ValueError("subtype must be in range 0..255.")
223-
try:
224-
self._data: bytes = bytes(data)
225-
except TypeError as exc:
226-
raise TypeError("data must be bytes-like.") from exc
217+
if not isinstance(data, (bytes, bytearray)):
218+
raise TypeError("data must be bytes or bytearray.")
219+
self._data: bytes = bytes(data)
227220
self._subtype: int = subtype
228221

229222
@property
@@ -255,17 +248,12 @@ class BSONInt32:
255248

256249
__slots__ = ("_value",)
257250

258-
def __init__(self, value: Any):
259-
if isinstance(value, bool):
260-
raise TypeError("BSONInt32 value cannot be bool.")
261-
if hasattr(value, "value") and type(getattr(value, "value")) is int:
262-
value = getattr(value, "value")
263-
if type(value) is not int and not isinstance(value, (int, BSONInt32)):
251+
def __init__(self, value: int):
252+
if isinstance(value, bool) or type(value) is not int:
264253
raise TypeError("BSONInt32 value must be an integer.")
265-
int_val = int(value)
266-
if not (-2147483648 <= int_val <= 2147483647):
254+
if not (-2147483648 <= value <= 2147483647):
267255
raise ValueError("BSONInt32 out of range [-2147483648, 2147483647].")
268-
self._value: int = int_val
256+
self._value: int = value
269257

270258
@property
271259
def value(self) -> int:

packages/google-cloud-firestore/tests/system/test_bson.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
# limitations under the License.
1515

1616
import pytest
17-
from google.cloud import firestore
1817
from google.cloud.firestore import (
1918
ArrayUnion,
2019
BSONBinary,

packages/google-cloud-firestore/tests/unit/v1/test_bson.py

Lines changed: 5 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -49,39 +49,11 @@ def test_bson_object_id_constructors_and_properties():
4949
BSONObjectID(b"invalid_bytes_len")
5050

5151

52-
def test_bson_constructors_duck_typed_objects():
53-
class DummyPyMongoObjectId:
54-
def __init__(self, raw: bytes):
55-
self.binary = raw
56-
57-
class DummyPyMongoDecimal128:
58-
def __init__(self, d: decimal.Decimal):
59-
self._d = d
60-
def to_decimal(self):
61-
return self._d
62-
63-
class DummyPyMongoRegex:
64-
def __init__(self, pat: str, flags: str):
65-
self.pattern = pat
66-
self.flags = flags
67-
68-
dummy_oid = DummyPyMongoObjectId(bytes.fromhex("507f1f77bcf86cd799439011"))
69-
oid = BSONObjectID(dummy_oid)
70-
assert oid.value == "507f1f77bcf86cd799439011"
71-
72-
dummy_dec = DummyPyMongoDecimal128(decimal.Decimal("123.45"))
73-
dec = BSONDecimal128(dummy_dec)
74-
assert dec.value == "123.45"
75-
76-
dummy_reg = DummyPyMongoRegex("^[a-z]+$", "i")
77-
reg = BSONRegex(dummy_reg)
78-
assert reg.pattern == "^[a-z]+$"
79-
assert reg.options == "i"
80-
81-
# Copy constructor
82-
target_oid = BSONObjectID("507f191e810c19729de860ea")
83-
oid4 = BSONObjectID(target_oid)
84-
assert oid4.value == "507f191e810c19729de860ea"
52+
def test_bson_constructors_validation():
53+
# bytearray
54+
raw_bytes = bytes.fromhex("507f1f77bcf86cd799439011")
55+
oid_ba = BSONObjectID(bytearray(raw_bytes))
56+
assert oid_ba.value == "507f1f77bcf86cd799439011"
8557

8658
# Invalid constructors
8759
with pytest.raises(ValueError):

0 commit comments

Comments
 (0)