Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SNOW-1649438] add zstd decompressor in the urllib3 response #2043

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
47 changes: 47 additions & 0 deletions src/snowflake/connector/vendored/urllib3/response.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,23 @@
except ImportError:
brotli = None

try:
import zstandard as zstd
Copy link
Collaborator

@sfc-gh-aling sfc-gh-aling Sep 11, 2024

Choose a reason for hiding this comment

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

I'm interested when the backend sends back zstd compressed data, is there a configuration?

also what error would connector trigger if it receives zstd compressed data but zstd is not installed in the execution env

except (AttributeError, ImportError, ValueError): # Defensive:
HAS_ZSTD = False
else:
# Extract major and minor version numbers
version_parts = zstd.__version__.split(".")
major_version = int(version_parts[0])
minor_version = int(version_parts[1])

# Check if the version is at least 0.18.0
if (major_version, minor_version) < (0, 18): # Defensive:
HAS_ZSTD = False
else:
HAS_ZSTD = True


from . import util
from ._collections import HTTPHeaderDict
from .connection import BaseSSLError, HTTPException
Expand Down Expand Up @@ -126,6 +143,29 @@ def flush(self):
return b""


if HAS_ZSTD:
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm curious when is this zstd feature introduced in the urllib?


class ZstdDecoder(object):
def __init__(self) -> None:
self._obj = zstd.ZstdDecompressor().decompressobj()

def decompress(self, data: bytes) -> bytes:
if not data:
return b""
data_parts = [self._obj.decompress(data)]
while self._obj.eof and self._obj.unused_data:
unused_data = self._obj.unused_data
self._obj = zstd.ZstdDecompressor().decompressobj()
data_parts.append(self._obj.decompress(unused_data))
return b"".join(data_parts)

def flush(self) -> bytes:
ret = self._obj.flush() # note: this is a no-op
if not self._obj.eof:
raise DecodeError("Zstandard data is incomplete")
return ret


class MultiDecoder(object):
"""
From RFC7231:
Expand Down Expand Up @@ -157,6 +197,9 @@ def _get_decoder(mode):
if brotli is not None and mode == "br":
return BrotliDecoder()

if HAS_ZSTD and mode == "zstd":
return ZstdDecoder()

return DeflateDecoder()


Expand Down Expand Up @@ -196,6 +239,8 @@ class is also compatible with the Python standard library's :mod:`io`
CONTENT_DECODERS = ["gzip", "deflate"]
if brotli is not None:
CONTENT_DECODERS += ["br"]
if HAS_ZSTD:
CONTENT_DECODERS += ["zstd"]
REDIRECT_STATUSES = [301, 302, 303, 307, 308]

def __init__(
Expand Down Expand Up @@ -394,6 +439,8 @@ def _init_decoder(self):
DECODER_ERROR_CLASSES = (IOError, zlib.error)
if brotli is not None:
DECODER_ERROR_CLASSES += (brotli.error,)
if HAS_ZSTD:
DECODER_ERROR_CLASSES += (zstd.ZstdError,)

def _decode(self, data, decode_content, flush_decoder):
"""
Expand Down
Loading