Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .evergreen/scripts/setup-dev-env.sh
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,13 @@ if [ -f $HOME/.visualStudioEnv.sh ]; then
SSH_TTY=1 source $HOME/.visualStudioEnv.sh
set -u
fi
uv sync --frozen
uv sync

echo "Setting up python environment... done."

# Ensure there is a pre-commit hook if there is a git checkout.
if [ -d .git ] && [ ! -f .git/hooks/pre-commit ]; then
uv run --frozen pre-commit install
uv run pre-commit install
fi

popd > /dev/null
33 changes: 33 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<!-- Thanks for contributing! -->
## Summary
<!-- What conceptually is this PR introducing? If context is already provided from the JIRA ticket, still place it in the
Pull Request as you should not make the reviewer do digging for a basic summary. -->

## Changes in this PR
<!-- What changes did you make to the code? What new APIs (public or private) were added, removed, or edited to generate
the desired outcome explained in the above summary? -->

## Test Plan
<!-- How did you test the code? If you added unit tests, you can say that. If you didn’t introduce unit tests, explain why.
All code should be tested in some way – so please list what your validation strategy was. -->

### Screenshots (optional)
<!-- Usually a great supplement to a test plan, especially if this requires local testing. -->

## Checklist
<!-- Do not delete the items provided on this checklist. -->
### Checklist for Author
- [ ] Did you update the changelog (if necessary)?
- [ ] Is the intention of the code captured in relevant tests?
- [ ] If there are new TODOs, has a related JIRA ticket been created?

### Checklist for Reviewer {@primary_reviewer}
- [ ] Does the title of the PR reference a JIRA Ticket?
- [ ] Do you fully understand the implementation? (Would you be comfortable explaining how this code works to someone else?)
- [ ] Have you checked for spelling & grammar errors?
- [ ] Is all relevant documentation (README or docstring) updated?

## Focus Areas for Reviewer
<!-- List any complex portion of code you believe needs additional scrutiny and explain why. -->

<!-- See also: https://docs.google.com/document/d/1Z-z6BDIBJ9G4fn4MBb7Ql5A1NiSY6nsLfEE3KuU_Btw/edit?tab=t.exaie3tsb7gl#heading=h.asd8fqlsyzb6 -->
17 changes: 11 additions & 6 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -105,12 +105,6 @@ repos:
# - test/test_client.py:188: te ==> the, be, we, to
args: ["-L", "fle,fo,infinit,isnt,nin,te,aks"]

- repo: https://github.com/astral-sh/uv-pre-commit
# uv version.
rev: 0.8.17
hooks:
- id: uv-lock

- repo: local
hooks:
- id: executable-shell
Expand All @@ -128,3 +122,14 @@ repos:
language: python
require_serial: true
additional_dependencies: ["shrub.py>=3.10.0", "pyyaml>=6.0.2"]

- id: uv-lock
name: uv-lock
entry: uv lock
language: python
require_serial: true
files: ^(uv\.lock|pyproject\.toml|requirements.txt|requirements/.*\.txt)$
pass_filenames: false
fail_fast: true
additional_dependencies:
- "uv>=0.8.4"
2 changes: 1 addition & 1 deletion bson/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1009,7 +1009,7 @@ def _dict_to_bson(
try:
elements.append(_element_to_bson(key, value, check_keys, opts))
except InvalidDocument as err:
raise InvalidDocument(f"Invalid document {doc} | {err}") from err
raise InvalidDocument(f"Invalid document: {err}", doc) from err
except AttributeError:
raise TypeError(f"encoder expected a mapping type but got: {doc!r}") from None

Expand Down
26 changes: 11 additions & 15 deletions bson/_cbsonmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -1645,11 +1645,11 @@ static int write_raw_doc(buffer_t buffer, PyObject* raw, PyObject* _raw_str) {
}


/* Update Invalid Document error message to include doc.
/* Update Invalid Document error to include doc as a property.
*/
void handle_invalid_doc_error(PyObject* dict) {
PyObject *etype = NULL, *evalue = NULL, *etrace = NULL;
PyObject *msg = NULL, *dict_str = NULL, *new_msg = NULL;
PyObject *msg = NULL, *new_msg = NULL, *new_evalue = NULL;
PyErr_Fetch(&etype, &evalue, &etrace);
PyObject *InvalidDocument = _error("InvalidDocument");
if (InvalidDocument == NULL) {
Expand All @@ -1659,26 +1659,22 @@ void handle_invalid_doc_error(PyObject* dict) {
if (evalue && PyErr_GivenExceptionMatches(etype, InvalidDocument)) {
PyObject *msg = PyObject_Str(evalue);
if (msg) {
// Prepend doc to the existing message
PyObject *dict_str = PyObject_Str(dict);
if (dict_str == NULL) {
goto cleanup;
}
const char * dict_str_utf8 = PyUnicode_AsUTF8(dict_str);
if (dict_str_utf8 == NULL) {
goto cleanup;
}
const char * msg_utf8 = PyUnicode_AsUTF8(msg);
if (msg_utf8 == NULL) {
goto cleanup;
}
PyObject *new_msg = PyUnicode_FromFormat("Invalid document %s | %s", dict_str_utf8, msg_utf8);
PyObject *new_msg = PyUnicode_FromFormat("Invalid document: %s", msg_utf8);
if (new_msg == NULL) {
goto cleanup;
}
// Add doc to the error instance as a property.
PyObject *new_evalue = PyObject_CallFunctionObjArgs(InvalidDocument, new_msg, dict, NULL);
Py_DECREF(evalue);
Py_DECREF(etype);
etype = InvalidDocument;
InvalidDocument = NULL;
if (new_msg) {
evalue = new_msg;
if (new_evalue) {
evalue = new_evalue;
} else {
evalue = msg;
}
Expand All @@ -1689,7 +1685,7 @@ void handle_invalid_doc_error(PyObject* dict) {
PyErr_Restore(etype, evalue, etrace);
Py_XDECREF(msg);
Py_XDECREF(InvalidDocument);
Py_XDECREF(dict_str);
Py_XDECREF(new_evalue);
Py_XDECREF(new_msg);
}

Expand Down
13 changes: 13 additions & 0 deletions bson/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
"""Exceptions raised by the BSON package."""
from __future__ import annotations

from typing import Any, Optional


class BSONError(Exception):
"""Base class for all BSON exceptions."""
Expand All @@ -31,6 +33,17 @@ class InvalidStringData(BSONError):
class InvalidDocument(BSONError):
"""Raised when trying to create a BSON object from an invalid document."""

def __init__(self, message: str, document: Optional[Any] = None) -> None:
super().__init__(message)
self._document = document

@property
def document(self) -> Any:
"""The invalid document that caused the error.

..versionadded:: 4.16"""
return self._document


class InvalidId(BSONError):
"""Raised when trying to create an ObjectId from invalid data."""
9 changes: 9 additions & 0 deletions doc/changelog.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
Changelog
=========

Changes in Version 4.16.0 (XXXX/XX/XX)
--------------------------------------

PyMongo 4.16 brings a number of changes including:

- Removed invalid documents from :class:`bson.errors.InvalidDocument` error messages as
doing so may leak sensitive user data.
Instead, invalid documents are stored in :attr:`bson.errors.InvalidDocument.document`.

Changes in Version 4.15.1 (2025/09/16)
--------------------------------------

Expand Down
4 changes: 1 addition & 3 deletions justfile
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
# See https://just.systems/man/en/ for instructions
set shell := ["bash", "-c"]
# Do not modify the lock file when running justfile commands.
export UV_FROZEN := "1"

# Commonly used command segments.
typing_run := "uv run --group typing --extra aws --extra encryption --extra ocsp --extra snappy --extra test --extra zstd"
Expand All @@ -16,7 +14,7 @@ default:

[private]
resync:
@uv sync --quiet --frozen
@uv sync --quiet

install:
bash .evergreen/scripts/setup-dev-env.sh
Expand Down
15 changes: 11 additions & 4 deletions test/test_bson.py
Original file line number Diff line number Diff line change
Expand Up @@ -1163,7 +1163,7 @@ def __repr__(self):
):
encode({"t": Wrapper(1)})

def test_doc_in_invalid_document_error_message(self):
def test_doc_in_invalid_document_error_as_property(self):
class Wrapper:
def __init__(self, val):
self.val = val
Expand All @@ -1173,10 +1173,11 @@ def __repr__(self):

self.assertEqual("1", repr(Wrapper(1)))
doc = {"t": Wrapper(1)}
with self.assertRaisesRegex(InvalidDocument, f"Invalid document {doc}"):
with self.assertRaisesRegex(InvalidDocument, "Invalid document:") as cm:
encode(doc)
self.assertEqual(cm.exception.document, doc)

def test_doc_in_invalid_document_error_message_mapping(self):
def test_doc_in_invalid_document_error_as_property_mapping(self):
class MyMapping(abc.Mapping):
def keys(self):
return ["t"]
Expand All @@ -1192,6 +1193,11 @@ def __len__(self):
def __iter__(self):
return iter(["t"])

def __eq__(self, other):
if isinstance(other, MyMapping):
return True
return False

class Wrapper:
def __init__(self, val):
self.val = val
Expand All @@ -1201,8 +1207,9 @@ def __repr__(self):

self.assertEqual("1", repr(Wrapper(1)))
doc = MyMapping()
with self.assertRaisesRegex(InvalidDocument, f"Invalid document {doc}"):
with self.assertRaisesRegex(InvalidDocument, "Invalid document:") as cm:
encode(doc)
self.assertEqual(cm.exception.document, doc)


class TestCodecOptions(unittest.TestCase):
Expand Down
Loading