Skip to content

Commit e253501

Browse files
committed
RDBC-1029: fix AttributeError in BulkInsert._get_exception_from_operation
result['$type'].starts_with(...) is not a Python method; the correct name is str.startswith(). The typo swallowed all server-side bulk insert errors with an AttributeError, hiding the real cause. Regression test: test_bulk_insert_error_handling.py verifies that a server-side abort surfaces as BulkInsertAbortedException with the server's error detail rather than the generic "Failed to execute bulk insert" message.
1 parent 4cd66c4 commit e253501

2 files changed

Lines changed: 70 additions & 1 deletion

File tree

ravendb/documents/bulk_insert_operation.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -355,7 +355,7 @@ def _get_exception_from_operation(self) -> Optional[BulkInsertAbortedException]:
355355

356356
result = state_request.result["Result"]
357357

358-
if result["$type"].starts_with("Raven.Client.Documents.Operations.OperationExceptionResult"):
358+
if result["$type"].startswith("Raven.Client.Documents.Operations.OperationExceptionResult"):
359359
return BulkInsertAbortedException(result["Error"])
360360

361361
return None
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
"""
2+
BulkInsert: server errors surface as BulkInsertAbortedException with the
3+
server's error detail; metadata round-trips correctly.
4+
5+
C# reference: FastTests/Client/BulkInsert/BulkInserts.cs
6+
CanModifyMetadataWithBulkInsert
7+
"""
8+
9+
import datetime
10+
11+
from ravendb.json.metadata_as_dictionary import MetadataAsDictionary
12+
from ravendb.primitives.constants import Documents
13+
from ravendb.exceptions.documents.bulkinsert import BulkInsertAbortedException
14+
from ravendb.tests.test_base import TestBase
15+
16+
17+
class FooBar:
18+
def __init__(self, name: str = ""):
19+
self.name = name
20+
21+
22+
class TestRavenDBBulkInsert(TestBase):
23+
def setUp(self):
24+
super().setUp()
25+
26+
def test_bulk_insert_server_error_raises_aborted_exception(self):
27+
"""
28+
When the server rejects a bulk insert operation,
29+
_get_exception_from_operation returns BulkInsertAbortedException
30+
carrying the server's error text.
31+
32+
C# ref: BulkInserts.CanModifyMetadataWithBulkInsert (error path)
33+
"""
34+
# A date-only string triggers a server-side error (@expires requires a full datetime).
35+
date_only_expiry = (datetime.date.today() + datetime.timedelta(days=365)).isoformat()
36+
meta = MetadataAsDictionary()
37+
meta[Documents.Metadata.EXPIRES] = date_only_expiry
38+
39+
with self.assertRaises(BulkInsertAbortedException) as cm:
40+
with self.store.bulk_insert() as bi:
41+
bi.store(FooBar(name="Jon Snow"), metadata=meta)
42+
43+
error_message = str(cm.exception)
44+
self.assertNotIn(
45+
"Failed to execute bulk insert",
46+
error_message,
47+
"exception message should contain server error detail, not just the generic fallback",
48+
)
49+
50+
def test_bulk_insert_with_full_datetime_expiry_works(self):
51+
"""
52+
C# spec: BulkInserts.CanModifyMetadataWithBulkInsert — stores a document
53+
with @expires set to a full datetime string; the server accepts it and
54+
persists the expiry in metadata.
55+
"""
56+
expiry = (datetime.datetime.utcnow() + datetime.timedelta(days=365)).isoformat()
57+
meta = MetadataAsDictionary()
58+
meta[Documents.Metadata.EXPIRES] = expiry
59+
60+
with self.store.bulk_insert() as bi:
61+
bi.store(FooBar(name="Jon Snow"), metadata=meta)
62+
63+
with self.store.open_session() as session:
64+
entity = session.load("FooBars/1-A", FooBar)
65+
self.assertIsNotNone(entity, "Document should have been stored")
66+
meta_out = session.advanced.get_metadata_for(entity)
67+
stored_expiry = meta_out.get(Documents.Metadata.EXPIRES)
68+
self.assertIsNotNone(stored_expiry, "@expires should be persisted in metadata")
69+
self.assertEqual(expiry, stored_expiry, "@expires value should round-trip exactly")

0 commit comments

Comments
 (0)