Skip to content

Commit f05ed9e

Browse files
committed
feat(firestore): add BSONInt32 support
1 parent 173e2e0 commit f05ed9e

7 files changed

Lines changed: 143 additions & 2 deletions

File tree

.librarian/generator-input/client-post-processing/firestore-integration.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ replacements:
7070
from google.cloud.firestore_v1.base_query import And, FieldFilter, Or
7171
from google.cloud.firestore_v1.batch import WriteBatch
7272
from google.cloud.firestore_v1.bson import (
73+
BSONInt32,
7374
BSONMaxKey,
7475
BSONMinKey,
7576
BSONObjectId,
@@ -175,6 +176,7 @@ replacements:
175176
"async_transactional",
176177
"AsyncTransaction",
177178
"AsyncWriteBatch",
179+
"BSONInt32",
178180
"BSONMaxKey",
179181
"BSONMinKey",
180182
"BSONObjectId",
@@ -250,6 +252,7 @@ replacements:
250252
AsyncQuery,
251253
AsyncTransaction,
252254
AsyncWriteBatch,
255+
BSONInt32,
253256
BSONMaxKey,
254257
BSONMinKey,
255258
BSONObjectId,
@@ -310,6 +313,7 @@ replacements:
310313
"async_transactional",
311314
"AsyncTransaction",
312315
"AsyncWriteBatch",
316+
"BSONInt32",
313317
"BSONMaxKey",
314318
"BSONMinKey",
315319
"BSONObjectId",

packages/google-cloud-firestore/google/cloud/firestore/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
AsyncQuery,
3636
AsyncTransaction,
3737
AsyncWriteBatch,
38+
BSONInt32,
3839
BSONMaxKey,
3940
BSONMinKey,
4041
BSONObjectId,
@@ -95,6 +96,7 @@
9596
"async_transactional",
9697
"AsyncTransaction",
9798
"AsyncWriteBatch",
99+
"BSONInt32",
98100
"BSONMaxKey",
99101
"BSONMinKey",
100102
"BSONObjectId",

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
from google.cloud.firestore_v1.base_query import And, FieldFilter, Or
4848
from google.cloud.firestore_v1.batch import WriteBatch
4949
from google.cloud.firestore_v1.bson import (
50+
BSONInt32,
5051
BSONMaxKey,
5152
BSONMinKey,
5253
BSONObjectId,
@@ -152,6 +153,7 @@
152153
"async_transactional",
153154
"AsyncTransaction",
154155
"AsyncWriteBatch",
156+
"BSONInt32",
155157
"BSONMaxKey",
156158
"BSONMinKey",
157159
"BSONObjectId",

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

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
"BSONObjectId",
3333
"BSONMinKey",
3434
"BSONMaxKey",
35+
"BSONInt32",
3536
]
3637

3738
_OBJECT_ID_BYTES_LEN = 12
@@ -171,3 +172,60 @@ def __eq__(self, other: Any) -> bool:
171172

172173
def __hash__(self) -> int:
173174
return hash("BSONMaxKey")
175+
176+
177+
class BSONInt32(_BSONType):
178+
"""Represents a 32-bit signed integer value container for Firestore BSON.
179+
180+
Args:
181+
value (int): A 32-bit signed integer value.
182+
183+
Raises:
184+
TypeError: If value is not an integer or is a boolean.
185+
ValueError: If value is outside the 32-bit signed range (-2147483648 to 2147483647).
186+
187+
Example:
188+
>>> int_val = BSONInt32(42)
189+
>>> int_val.value
190+
42
191+
"""
192+
193+
__slots__ = ("_value",)
194+
195+
_MIN_VALUE: int = -(1 << 31)
196+
_MAX_VALUE: int = (1 << 31) - 1
197+
198+
def __init__(self, value: int):
199+
if isinstance(value, bool) or not isinstance(value, int):
200+
raise TypeError("BSONInt32 requires an int.")
201+
if not (self._MIN_VALUE <= value <= self._MAX_VALUE):
202+
raise ValueError(
203+
f"BSONInt32 value must be between {self._MIN_VALUE} and {self._MAX_VALUE}."
204+
)
205+
self._value: int = value
206+
207+
@property
208+
def value(self) -> int:
209+
"""int: The 32-bit signed integer value."""
210+
return self._value
211+
212+
def _to_map_value(self) -> Dict[str, int]:
213+
"""Returns map dictionary representation for wire serialization."""
214+
return {"__int__": self._value}
215+
216+
def __repr__(self) -> str:
217+
return f"BSONInt32({self._value})"
218+
219+
def __str__(self) -> str:
220+
return str(self._value)
221+
222+
def __int__(self) -> int:
223+
return self._value
224+
225+
def __eq__(self, other: Any) -> bool:
226+
if isinstance(other, BSONInt32):
227+
return self._value == other._value
228+
return NotImplemented
229+
230+
def __hash__(self) -> int:
231+
return hash(self._value)

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,12 @@
4848
from google.cloud import firestore_v1 as firestore
4949
from google.cloud.firestore_v1.base_query import And, FieldFilter, Or
5050
from google.cloud.firestore_v1.base_vector_query import DistanceMeasure
51-
from google.cloud.firestore_v1.bson import BSONMaxKey, BSONMinKey, BSONObjectId
51+
from google.cloud.firestore_v1.bson import (
52+
BSONInt32,
53+
BSONMaxKey,
54+
BSONMinKey,
55+
BSONObjectId,
56+
)
5257
from google.cloud.firestore_v1.vector import Vector
5358

5459

@@ -1286,6 +1291,7 @@ def test_bson_document_writes(client, cleanup, database):
12861291
"user_id": BSONObjectId("507f191e810c19729de860ea"),
12871292
"min_key": BSONMinKey(),
12881293
"max_key": BSONMaxKey(),
1294+
"int32_val": BSONInt32(42),
12891295
}
12901296

12911297
doc_ref.set(bson_payload)
@@ -1296,6 +1302,7 @@ def test_bson_document_writes(client, cleanup, database):
12961302
"user_id": {"__oid__": "507f191e810c19729de860ea"},
12971303
"min_key": {"__min__": None},
12981304
"max_key": {"__max__": None},
1305+
"int32_val": {"__int__": 42},
12991306
}
13001307

13011308

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,12 @@
5151
from google.cloud import firestore_v1 as firestore
5252
from google.cloud.firestore_v1.base_query import And, FieldFilter, Or
5353
from google.cloud.firestore_v1.base_vector_query import DistanceMeasure
54-
from google.cloud.firestore_v1.bson import BSONMaxKey, BSONMinKey, BSONObjectId
54+
from google.cloud.firestore_v1.bson import (
55+
BSONInt32,
56+
BSONMaxKey,
57+
BSONMinKey,
58+
BSONObjectId,
59+
)
5560
from google.cloud.firestore_v1.query_profile import (
5661
ExecutionStats,
5762
ExplainMetrics,
@@ -1259,6 +1264,7 @@ async def test_async_bson_document_writes(client, cleanup, database):
12591264
"user_id": BSONObjectId("507f191e810c19729de860ea"),
12601265
"min_key": BSONMinKey(),
12611266
"max_key": BSONMaxKey(),
1267+
"int32_val": BSONInt32(42),
12621268
}
12631269

12641270
await doc_ref.set(bson_payload)
@@ -1269,6 +1275,7 @@ async def test_async_bson_document_writes(client, cleanup, database):
12691275
"user_id": {"__oid__": "507f191e810c19729de860ea"},
12701276
"min_key": {"__min__": None},
12711277
"max_key": {"__max__": None},
1278+
"int32_val": {"__int__": 42},
12721279
}
12731280

12741281

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

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import pytest
2222

2323
from google.cloud.firestore_v1.bson import (
24+
BSONInt32,
2425
BSONMaxKey,
2526
BSONMinKey,
2627
BSONObjectId,
@@ -195,3 +196,63 @@ def test_bson_maxkey_copy():
195196
def test_bson_maxkey_pickle():
196197
key = BSONMaxKey()
197198
assert pickle.loads(pickle.dumps(key)) == key
199+
200+
201+
def test_bson_int32_valid():
202+
val = BSONInt32(42)
203+
assert val.value == 42
204+
assert int(val) == 42
205+
assert str(val) == "42"
206+
assert repr(val) == "BSONInt32(42)"
207+
assert val._to_map_value() == {"__int__": 42}
208+
209+
210+
def test_bson_int32_boundaries():
211+
min_val = BSONInt32(-2147483648)
212+
max_val = BSONInt32(2147483647)
213+
assert min_val.value == -2147483648
214+
assert max_val.value == 2147483647
215+
216+
217+
@pytest.mark.parametrize(
218+
"invalid_input, exc_type, match_msg",
219+
[
220+
(2147483648, ValueError, "must be between"),
221+
(-2147483649, ValueError, "must be between"),
222+
(True, TypeError, "requires an int"),
223+
(False, TypeError, "requires an int"),
224+
("42", TypeError, "requires an int"),
225+
(42.0, TypeError, "requires an int"),
226+
(None, TypeError, "requires an int"),
227+
],
228+
)
229+
def test_bson_int32_invalid_inputs(invalid_input, exc_type, match_msg):
230+
with pytest.raises(exc_type, match=match_msg):
231+
BSONInt32(invalid_input)
232+
233+
234+
def test_bson_int32_equality():
235+
val1 = BSONInt32(42)
236+
val2 = BSONInt32(42)
237+
val3 = BSONInt32(100)
238+
assert val1 == val2
239+
assert val1 != val3
240+
assert val1 != 42
241+
242+
243+
def test_bson_int32_hash_and_dict_key():
244+
val1 = BSONInt32(42)
245+
val2 = BSONInt32(42)
246+
assert hash(val1) == hash(val2)
247+
assert len({val1, val2}) == 1
248+
249+
250+
def test_bson_int32_copy():
251+
val = BSONInt32(42)
252+
assert copy.copy(val) == val
253+
assert copy.deepcopy(val) == val
254+
255+
256+
def test_bson_int32_pickle():
257+
val = BSONInt32(42)
258+
assert pickle.loads(pickle.dumps(val)) == val

0 commit comments

Comments
 (0)