Skip to content

feat(firestore): add core BSON document write support - #18370

Draft
ohmayr wants to merge 1 commit into
mainfrom
bson-pr1a-core-writes
Draft

ohmayr wants to merge 1 commit into
mainfrom
bson-pr1a-core-writes

Conversation

@ohmayr

@ohmayr ohmayr commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

Thank you for opening a Pull Request! Before submitting your PR, there are a few things you can do to make sure it goes smoothly:

  • Make sure to open an issue as a bug/issue before writing your code! That way we can discuss the change, evaluate designs, and agree on the general idea
  • Ensure the tests and linter pass
  • Code coverage does not decrease (if any source code was changed)
  • Appropriate docs were updated (if necessary)

Fixes #<issue_number_goes_here> 🦕

@ohmayr
ohmayr added this pull request to stack #18374 September 14, 2026 21:57

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces native BSON data type support (such as BSONObjectID, BSONDecimal128, BSONTimestamp, BSONRegex, BSONBinary, BSONInt32, BSONMinKey, and BSONMaxKey) to the Firestore Python SDK, along with corresponding unit and system tests. The review feedback focuses on improving robustness and PEP 8 compliance. Key recommendations include using isinstance instead of direct type comparisons to support integer subclasses, safely retrieving attributes using getattr and callable rather than hasattr, correctly converting integer regex flags to option characters when a compiled regex is passed, and supporting direct equality comparisons between BSONDecimal128 and standard decimal.Decimal objects.

Comment on lines +199 to +202
if hasattr(pattern, "pattern") and not isinstance(pattern, str):
flags = getattr(pattern, "flags", "")
options = flags if isinstance(flags, str) else options
pattern = getattr(pattern, "pattern")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

When a compiled regex object is passed, its flags attribute is an integer (e.g., re.IGNORECASE). The current implementation silently ignores integer flags, which drops critical regex options like case-insensitivity. Convert integer flags to their corresponding BSON regex option characters.

        pattern_attr = getattr(pattern, "pattern", None)
        if pattern_attr is not None and not isinstance(pattern, str):
            flags = getattr(pattern, "flags", "")
            if isinstance(flags, int):
                opts = []
                if flags & re.IGNORECASE:
                    opts.append("i")
                if flags & re.MULTILINE:
                    opts.append("m")
                if flags & re.DOTALL:
                    opts.append("s")
                if flags & re.VERBOSE:
                    opts.append("x")
                if flags & re.LOCALE:
                    opts.append("l")
                if flags & re.UNICODE:
                    opts.append("u")
                options = "".join(opts)
            elif isinstance(flags, str):
                options = flags
            pattern = pattern_attr

Comment on lines +185 to +186
if hasattr(value, "to_map_value"):
return encode_value(value.to_map_value())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using hasattr to check for to_map_value can be unsafe if the attribute is not callable (e.g., if it is a property or a mock). It is safer and more robust to use callable(getattr(value, 'to_map_value', None)).

Suggested change
if hasattr(value, "to_map_value"):
return encode_value(value.to_map_value())
to_map_value = getattr(value, "to_map_value", None)
if callable(to_map_value):
return encode_value(to_map_value())

Comment on lines +43 to +46
if hasattr(value, "binary") and isinstance(getattr(value, "binary"), (bytes, bytearray, memoryview)):
value = bytes(getattr(value, "binary"))
elif hasattr(value, "value") and isinstance(getattr(value, "value"), str):
value = getattr(value, "value")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Simplify attribute lookups using getattr with a default value. This avoids multiple lookups and redundant hasattr checks, making the initialization cleaner and more efficient.

        binary_attr = getattr(value, "binary", None)
        if isinstance(binary_attr, (bytes, bytearray, memoryview)):
            value = bytes(binary_attr)
        else:
            value_attr = getattr(value, "value", None)
            if isinstance(value_attr, str):
                value = value_attr

Comment on lines +94 to +95
if hasattr(value, "to_decimal") and callable(getattr(value, "to_decimal")):
value = getattr(value, "to_decimal")()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Simplify the check and lookup for to_decimal using getattr with a default value to avoid redundant lookups.

Suggested change
if hasattr(value, "to_decimal") and callable(getattr(value, "to_decimal")):
value = getattr(value, "to_decimal")()
to_decimal_attr = getattr(value, "to_decimal", None)
if callable(to_decimal_attr):
value = to_decimal_attr()

Comment on lines +126 to +135
if isinstance(other, BSONDecimal128):
try:
d1 = self.to_decimal()
d2 = other.to_decimal()
if d1.is_nan() or d2.is_nan():
return False
return d1 == d2
except (decimal.DecimalException, ArithmeticError):
return self._value == other._value
return False

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

For consistency with BSONInt32 (which supports equality with standard int), BSONDecimal128 should support direct equality comparison with decimal.Decimal.

        if isinstance(other, BSONDecimal128):
            try:
                d1 = self.to_decimal()
                d2 = other.to_decimal()
                if d1.is_nan() or d2.is_nan():
                    return False
                return d1 == d2
            except (decimal.DecimalException, ArithmeticError):
                return self._value == other._value
        elif isinstance(other, decimal.Decimal):
            try:
                d1 = self.to_decimal()
                if d1.is_nan() or other.is_nan():
                    return False
                return d1 == other
            except (decimal.DecimalException, ArithmeticError):
                return False
        return False

Comment on lines +156 to +157
if type(seconds) is not int or type(increment) is not int:
raise TypeError("seconds and increment must be ints.")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Comparing types directly using type(seconds) is not int violates PEP 8 guidelines and rejects valid subclasses of int (such as IntEnum). Use isinstance instead, while explicitly rejecting bool.

Suggested change
if type(seconds) is not int or type(increment) is not int:
raise TypeError("seconds and increment must be ints.")
if (not isinstance(seconds, int) or isinstance(seconds, bool) or
not isinstance(increment, int) or isinstance(increment, bool)):
raise TypeError("seconds and increment must be ints.")
References
  1. PEP 8: Object type comparisons should always use isinstance() instead of comparing types directly. (link)

Comment on lines +244 to +245
if isinstance(subtype, bool) or type(subtype) is not int:
raise TypeError("subtype must be an integer.")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Comparing types directly using type(subtype) is not int violates PEP 8 guidelines and rejects valid subclasses of int. Use isinstance instead, while explicitly rejecting bool.

Suggested change
if isinstance(subtype, bool) or type(subtype) is not int:
raise TypeError("subtype must be an integer.")
if not isinstance(subtype, int) or isinstance(subtype, bool):
raise TypeError("subtype must be an integer.")
References
  1. PEP 8: Object type comparisons should always use isinstance() instead of comparing types directly. (link)

Comment on lines +292 to +295
if hasattr(value, "value") and type(getattr(value, "value")) is int:
value = getattr(value, "value")
if type(value) is not int and not isinstance(value, (int, BSONInt32)):
raise TypeError("BSONInt32 value must be an integer.")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Simplify attribute lookups and type checking using getattr and isinstance to conform to PEP 8 guidelines and support subclasses of int.

Suggested change
if hasattr(value, "value") and type(getattr(value, "value")) is int:
value = getattr(value, "value")
if type(value) is not int and not isinstance(value, (int, BSONInt32)):
raise TypeError("BSONInt32 value must be an integer.")
val_attr = getattr(value, "value", None)
if val_attr is not None and isinstance(val_attr, int) and not isinstance(val_attr, bool):
value = val_attr
if not isinstance(value, (int, BSONInt32)):
raise TypeError("BSONInt32 value must be an integer.")
References
  1. PEP 8: Object type comparisons should always use isinstance() instead of comparing types directly. (link)

Comment on lines +321 to +322
if type(other) is int and not isinstance(other, bool):
return self._value == other

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Comparing types directly using type(other) is int violates PEP 8 guidelines and rejects valid subclasses of int (such as IntEnum). Use isinstance instead.

Suggested change
if type(other) is int and not isinstance(other, bool):
return self._value == other
if isinstance(other, int) and not isinstance(other, bool):
return self._value == other
References
  1. PEP 8: Object type comparisons should always use isinstance() instead of comparing types directly. (link)

@ohmayr
ohmayr force-pushed the bson-pr1a-core-writes branch 7 times, most recently from 35f2aab to 09c273b Compare September 14, 2026 23:56
@ohmayr
ohmayr force-pushed the bson-pr1a-core-writes branch from 09c273b to 209f604 Compare September 15, 2026 00:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant