From d8fc4f5a8777004cc4d4712487e29990f7e7a11b Mon Sep 17 00:00:00 2001 From: Jakub Kucharski Date: Wed, 24 Sep 2025 14:49:32 +0200 Subject: [PATCH 1/3] RDBC-940 Zstd-compression data prevents simple store. --- ravendb/http/raven_command.py | 27 +++++++++++++++++++-------- setup.py | 2 +- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/ravendb/http/raven_command.py b/ravendb/http/raven_command.py index baa9f3a2..a1bc87da 100644 --- a/ravendb/http/raven_command.py +++ b/ravendb/http/raven_command.py @@ -97,14 +97,25 @@ def set_response(self, response: Optional[str], from_cache: bool) -> None: ) def send(self, session: requests.Session, request: requests.Request) -> requests.Response: - return session.request( - request.method, - url=request.url, - data=request.data, - files=request.files, - cert=session.cert, - headers=request.headers, - ) + prepared_request = session.prepare_request(request) + self._remove_zstd_encoding(prepared_request) + return session.send(prepared_request, cert=session.cert) + + # https://issues.hibernatingrhinos.com/issue/RDBC-940 + # If user has installed module 'zstd' or 'zstandard', + # 'requests' module will automatically add 'zstd' to 'Accept-Encoding' header. + # This causes exceptions. Excluding 'zstd' from the header in this workaround, + # while we keep investigating cause of the issue. + @staticmethod + def _remove_zstd_encoding(request: requests.PreparedRequest) -> None: + accept_encoding = request.headers.get("Accept-Encoding") + + if "zstd" in accept_encoding: + encodings = [ + encoding.strip() for encoding in accept_encoding.split(",") if encoding.strip().lower() != "zstd" + ] + new_header_value = ", ".join(encodings) + request.headers["Accept-Encoding"] = new_header_value def set_response_raw(self, response: requests.Response, stream: bytes) -> None: raise RuntimeError( diff --git a/setup.py b/setup.py index 58040024..4e208501 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ setup( name="ravendb", packages=find_packages(exclude=["*.tests.*", "tests", "*.tests", "tests.*"]), - version="7.0.2", + version="7.0.2.post1", long_description_content_type="text/markdown", long_description=open("README_pypi.md").read(), description="Python client for RavenDB NoSQL Database", From f6f8122d2a96ff28f912acf902fa5479b071bbfd Mon Sep 17 00:00:00 2001 From: Jakub Kucharski Date: Mon, 29 Sep 2025 13:58:33 +0200 Subject: [PATCH 2/3] RDBC-940 Import zstandard for the tests --- .github/workflows/RavenClient.yml | 3 +++ ravendb/documents/commands/stream.py | 22 ----------------- ravendb/tests/issue_tests/test_RDBC-940.py | 28 ++++++++++++++++++++++ 3 files changed, 31 insertions(+), 22 deletions(-) create mode 100644 ravendb/tests/issue_tests/test_RDBC-940.py diff --git a/.github/workflows/RavenClient.yml b/.github/workflows/RavenClient.yml index 38ef72c4..8dc54813 100644 --- a/.github/workflows/RavenClient.yml +++ b/.github/workflows/RavenClient.yml @@ -75,6 +75,9 @@ jobs: - name: Install embedded RavenDB run: pip install ravendb-embedded + - name: Install zstandard + run: pip install zstandard + - name: Run certifi script run: python ./.github/workflows/add_ca.py diff --git a/ravendb/documents/commands/stream.py b/ravendb/documents/commands/stream.py index 48c7123d..c6eeeb38 100644 --- a/ravendb/documents/commands/stream.py +++ b/ravendb/documents/commands/stream.py @@ -51,17 +51,6 @@ def process_response(self, cache: HttpCache, response: requests.Response, url) - except Exception as e: raise RuntimeError("Unable to process stream response", e) - def send(self, session: requests.Session, request: requests.Request) -> requests.Response: - return session.request( - request.method, - url=request.url, - data=request.data, - files=request.files, - cert=session.cert, - headers=request.headers, - stream=True, - ) - def is_read_request(self) -> bool: return True @@ -96,16 +85,5 @@ def process_response(self, cache: HttpCache, response: requests.Response, url) - except Exception as e: raise RuntimeError("Unable to process stream response: " + e.args[0], e) - def send(self, session: requests.Session, request: requests.Request) -> requests.Response: - return session.request( - request.method, - url=request.url, - data=request.data, - files=request.files, - cert=session.cert, - headers=request.headers, - stream=True, - ) - def is_read_request(self) -> bool: return True diff --git a/ravendb/tests/issue_tests/test_RDBC-940.py b/ravendb/tests/issue_tests/test_RDBC-940.py new file mode 100644 index 00000000..cd528874 --- /dev/null +++ b/ravendb/tests/issue_tests/test_RDBC-940.py @@ -0,0 +1,28 @@ +from ravendb.tests.test_base import TestBase + + +class TestDocument: + def __init__(self, name: str = None): + self.name = name + + +class TestRDBC940(TestBase): + def setUp(self): + super(TestRDBC940, self).setUp() + + def test_operations_with_zstandard_imported(self): + import zstandard + + document_id = "test-documents/1" + + with self.store.open_session() as session: + test_doc = TestDocument(name="Test Document") + session.store(test_doc, document_id) + session.save_changes() + + with self.store.open_session() as session: + loaded_doc = session.load(document_id, TestDocument) + + with self.store.open_session() as session: + session.delete(document_id) + session.save_changes() From c20ae069d7a180a25f164f482cb07e753c56ded6 Mon Sep 17 00:00:00 2001 From: Jakub Kucharski Date: Tue, 30 Sep 2025 12:31:41 +0200 Subject: [PATCH 3/3] RDBC-940 Add missing stream argument --- ravendb/documents/commands/stream.py | 12 +++++++++++- ravendb/http/raven_command.py | 19 ++----------------- ravendb/util/request_utils.py | 20 ++++++++++++++++++++ 3 files changed, 33 insertions(+), 18 deletions(-) create mode 100644 ravendb/util/request_utils.py diff --git a/ravendb/documents/commands/stream.py b/ravendb/documents/commands/stream.py index c6eeeb38..cc0ee03b 100644 --- a/ravendb/documents/commands/stream.py +++ b/ravendb/documents/commands/stream.py @@ -9,7 +9,7 @@ from ravendb.http.server_node import ServerNode from ravendb.http.raven_command import RavenCommand, RavenCommandResponseType from ravendb.json.metadata_as_dictionary import MetadataAsDictionary - +from ravendb.util.request_utils import RequestUtils _T = TypeVar("_T") @@ -51,6 +51,11 @@ def process_response(self, cache: HttpCache, response: requests.Response, url) - except Exception as e: raise RuntimeError("Unable to process stream response", e) + def send(self, session: requests.Session, request: requests.Request) -> requests.Response: + prepared_request = session.prepare_request(request) + RequestUtils.remove_zstd_encoding(prepared_request) + return session.send(prepared_request, cert=session.cert, stream=True) + def is_read_request(self) -> bool: return True @@ -85,5 +90,10 @@ def process_response(self, cache: HttpCache, response: requests.Response, url) - except Exception as e: raise RuntimeError("Unable to process stream response: " + e.args[0], e) + def send(self, session: requests.Session, request: requests.Request) -> requests.Response: + prepared_request = session.prepare_request(request) + RequestUtils.remove_zstd_encoding(prepared_request) + return session.send(prepared_request, cert=session.cert, stream=True) + def is_read_request(self) -> bool: return True diff --git a/ravendb/http/raven_command.py b/ravendb/http/raven_command.py index a1bc87da..373afbe9 100644 --- a/ravendb/http/raven_command.py +++ b/ravendb/http/raven_command.py @@ -12,6 +12,7 @@ from ravendb.http.http_cache import HttpCache from ravendb.http.misc import ResponseDisposeHandling from ravendb.http.server_node import ServerNode +from ravendb.util.request_utils import RequestUtils class RavenCommandResponseType(Enum): @@ -98,25 +99,9 @@ def set_response(self, response: Optional[str], from_cache: bool) -> None: def send(self, session: requests.Session, request: requests.Request) -> requests.Response: prepared_request = session.prepare_request(request) - self._remove_zstd_encoding(prepared_request) + RequestUtils.remove_zstd_encoding(prepared_request) return session.send(prepared_request, cert=session.cert) - # https://issues.hibernatingrhinos.com/issue/RDBC-940 - # If user has installed module 'zstd' or 'zstandard', - # 'requests' module will automatically add 'zstd' to 'Accept-Encoding' header. - # This causes exceptions. Excluding 'zstd' from the header in this workaround, - # while we keep investigating cause of the issue. - @staticmethod - def _remove_zstd_encoding(request: requests.PreparedRequest) -> None: - accept_encoding = request.headers.get("Accept-Encoding") - - if "zstd" in accept_encoding: - encodings = [ - encoding.strip() for encoding in accept_encoding.split(",") if encoding.strip().lower() != "zstd" - ] - new_header_value = ", ".join(encodings) - request.headers["Accept-Encoding"] = new_header_value - def set_response_raw(self, response: requests.Response, stream: bytes) -> None: raise RuntimeError( f"When {self.response_type} is set to Raw then please override this method to handle the response " diff --git a/ravendb/util/request_utils.py b/ravendb/util/request_utils.py new file mode 100644 index 00000000..aeebffbb --- /dev/null +++ b/ravendb/util/request_utils.py @@ -0,0 +1,20 @@ +import requests + + +class RequestUtils: + + # https://issues.hibernatingrhinos.com/issue/RDBC-940 + # If user has installed module 'zstd' or 'zstandard', + # 'requests' module will automatically add 'zstd' to 'Accept-Encoding' header. + # This causes exceptions. Excluding 'zstd' from the header in this workaround, + # while we keep investigating cause of the issue. + @staticmethod + def remove_zstd_encoding(request: requests.PreparedRequest) -> None: + accept_encoding = request.headers.get("Accept-Encoding") + + if "zstd" in accept_encoding: + encodings = [ + encoding.strip() for encoding in accept_encoding.split(",") if encoding.strip().lower() != "zstd" + ] + new_header_value = ", ".join(encodings) + request.headers["Accept-Encoding"] = new_header_value