diff --git a/doc/changes/unreleased.md b/doc/changes/unreleased.md index 6b9bbdaf..a472952b 100644 --- a/doc/changes/unreleased.md +++ b/doc/changes/unreleased.md @@ -1,5 +1,8 @@ # Unreleased +## Feature +- Added support for configuring `SSL/TLS` validation + ## Documentation - Added overview of bucketfs system diff --git a/doc/conf.py b/doc/conf.py index baf25232..be285acc 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -50,6 +50,8 @@ ".md": "markdown", } +napoleon_include_init_with_doc = True + # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] diff --git a/exasol/bucketfs/__init__.py b/exasol/bucketfs/__init__.py index 3ee4356c..3b344f0b 100644 --- a/exasol/bucketfs/__init__.py +++ b/exasol/bucketfs/__init__.py @@ -43,6 +43,8 @@ CURL: $ curl -u "w:write" --output myfile.txt http://127.0.0.1:6666/default/myfile.txt """ +from __future__ import annotations + import hashlib from collections import defaultdict from pathlib import Path @@ -115,25 +117,37 @@ class Service: buckets: lists all available buckets. """ - def __init__(self, url: str, credentials: Mapping[str, Mapping[str, str]] = None): + def __init__( + self, + url: str, + credentials: Mapping[str, Mapping[str, str]] = None, + verify: bool | str = True, + ): """Create a new Service instance. Args: - url: of the bucketfs service, e.g. `http(s)://127.0.0.1:2580`. - credentials: a mapping containing credentials (username and password) for buckets. + url: + Url of the bucketfs service, e.g. `http(s)://127.0.0.1:2580`. + credentials: + A mapping containing credentials (username and password) for buckets. E.g. {"bucket1": { "username": "foo", "password": "bar" }} + verify: + Either a boolean, in which case it controls whether we verify + the server's TLS certificate, or a string, in which case it must be a path + to a CA bundle to use. Defaults to ``True``. """ self._url = _parse_service_url(url) self._authenticator = defaultdict( lambda: {"username": "r", "password": "read"}, credentials if credentials is not None else {}, ) + self._verify = verify @property - def buckets(self) -> MutableMapping[str, "Bucket"]: + def buckets(self) -> MutableMapping[str, Bucket]: """List all available buckets.""" url = _build_url(service_url=self._url) - response = requests.get(url) + response = requests.get(url, verify=self._verify) try: response.raise_for_status() except HTTPError as ex: @@ -155,28 +169,44 @@ def buckets(self) -> MutableMapping[str, "Bucket"]: def __str__(self) -> str: return f"Service<{self._url}>" - def __iter__(self) -> Iterator["Bucket"]: + def __iter__(self) -> Iterator[Bucket]: yield from self.buckets - def __getitem__(self, item: str) -> "Bucket": + def __getitem__(self, item: str) -> Bucket: return self.buckets[item] class Bucket: - def __init__(self, name: str, service: str, username: str, password: str): + def __init__( + self, + name: str, + service: str, + username: str, + password: str, + verify: bool | str = True, + ): """ Create a new bucket instance. Args: - name: of the bucket. - service: url where this bucket is hosted on. - username: used for authentication. - password: used for authentication. + name: + Name of the bucket. + service: + Url where this bucket is hosted on. + username: + Username used for authentication. + password: + Password used for authentication. + verify: + Either a boolean, in which case it controls whether we verify + the server's TLS certificate, or a string, in which case it must be a path + to a CA bundle to use. Defaults to ``True``. """ self._name = name self._service = _parse_service_url(service) self._username = username self._password = password + self._verify = verify def __str__(self): return f"Bucket<{self.name} | on: {self._service}>" @@ -192,7 +222,7 @@ def _auth(self) -> HTTPBasicAuth: @property def files(self) -> Iterable[str]: url = _build_url(service_url=self._service, bucket=self.name) - response = requests.get(url, auth=self._auth) + response = requests.get(url, auth=self._auth, verify=self._verify) try: response.raise_for_status() except HTTPError as ex: @@ -205,7 +235,7 @@ def __iter__(self) -> Iterator[str]: yield from self.files def upload( - self, path: str, data: Union[ByteString, BinaryIO, Iterable[ByteString]] + self, path: str, data: ByteString | BinaryIO | Iterable[ByteString] ) -> None: """ Uploads a file onto this bucket @@ -215,7 +245,7 @@ def upload( data: raw content of the file. """ url = _build_url(service_url=self._service, bucket=self.name, path=path) - response = requests.put(url, data=data, auth=self._auth) + response = requests.put(url, data=data, auth=self._auth, verify=self._verify) try: response.raise_for_status() except HTTPError as ex: @@ -232,7 +262,7 @@ def delete(self, path) -> None: A BucketFsError if the operation couldn't be executed successfully. """ url = _build_url(service_url=self._service, bucket=self.name, path=path) - response = requests.delete(url, auth=self._auth) + response = requests.delete(url, auth=self._auth, verify=self._verify) try: response.raise_for_status() except HTTPError as ex: @@ -250,7 +280,9 @@ def download(self, path: str, chunk_size: int = 8192) -> Iterable[ByteString]: An iterable of binary chunks representing the downloaded file. """ url = _build_url(service_url=self._service, bucket=self.name, path=path) - with requests.get(url, stream=True, auth=self._auth) as response: + with requests.get( + url, stream=True, auth=self._auth, verify=self._verify + ) as response: try: response.raise_for_status() except HTTPError as ex: @@ -296,7 +328,7 @@ def __iter__(self) -> Iterable[str]: yield from self._bucket.files def __setitem__( - self, key: str, value: Union[ByteString, BinaryIO, Iterable[ByteString]] + self, key: str, value: ByteString | BinaryIO | Iterable[ByteString] ) -> None: """ Uploads a file onto this bucket. @@ -325,7 +357,7 @@ def __str__(self): return f"MappedBucket<{self._bucket}>" -def _chunk_as_bytes(chunk: Union[int, ByteString]) -> ByteString: +def _chunk_as_bytes(chunk: int | ByteString) -> ByteString: """ In some scenarios python converts single bytes to integers: >>> chunks = [type(chunk) for chunk in b"abc"] @@ -373,7 +405,7 @@ def as_string(chunks: Iterable[ByteString], encoding: str = "utf-8") -> str: return _bytes(chunks).decode(encoding) -def as_file(chunks: Iterable[ByteString], filename: Union[str, Path]) -> Path: +def as_file(chunks: Iterable[ByteString], filename: str | Path) -> Path: """ Transforms a set of byte chunks into a string. diff --git a/exasol/bucketfs/version.py b/exasol/bucketfs/version.py index 7ab25474..647d463f 100644 --- a/exasol/bucketfs/version.py +++ b/exasol/bucketfs/version.py @@ -1,5 +1,8 @@ # ATTENTION: -# This file is generated, do not edit it manually! +# This file is generated by exasol/toolbox/pre_commit_hooks/package_version.py when using: +# * either "poetry run nox -s fix" +# * or "poetry run version-check --fix" +# Do not edit this file manually! # If you need to change the version, do so in the project.toml, e.g. by using `poetry version X.Y.Z`. MAJOR = 0 MINOR = 9 diff --git a/poetry.lock b/poetry.lock index 32697558..36d9f4c2 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,10 +1,9 @@ -# This file is automatically @generated by Poetry and should not be changed by hand. +# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. [[package]] name = "alabaster" version = "0.7.13" description = "A configurable sidebar-enabled Sphinx theme" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -16,7 +15,6 @@ files = [ name = "argcomplete" version = "2.1.2" description = "Bash tab completion for argparse" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -32,7 +30,6 @@ test = ["coverage", "flake8", "mypy", "pexpect", "wheel"] name = "astroid" version = "3.0.1" description = "An abstract syntax tree for Python with inference support." -category = "dev" optional = false python-versions = ">=3.8.0" files = [ @@ -47,7 +44,6 @@ typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.11\""} name = "babel" version = "2.13.1" description = "Internationalization utilities" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -66,7 +62,6 @@ dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] name = "beautifulsoup4" version = "4.12.2" description = "Screen-scraping library" -category = "dev" optional = false python-versions = ">=3.6.0" files = [ @@ -85,7 +80,6 @@ lxml = ["lxml"] name = "black" version = "23.11.0" description = "The uncompromising code formatter." -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -128,7 +122,6 @@ uvloop = ["uvloop (>=0.15.2)"] name = "certifi" version = "2023.11.17" description = "Python package for providing Mozilla's CA Bundle." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -140,7 +133,6 @@ files = [ name = "cffi" version = "1.16.0" description = "Foreign Function Interface for Python calling C code." -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -205,7 +197,6 @@ pycparser = "*" name = "cfgv" version = "3.4.0" description = "Validate configuration and produce human readable error messages." -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -217,7 +208,6 @@ files = [ name = "charset-normalizer" version = "3.3.2" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -category = "main" optional = false python-versions = ">=3.7.0" files = [ @@ -317,7 +307,6 @@ files = [ name = "click" version = "8.1.7" description = "Composable command line interface toolkit" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -332,7 +321,6 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""} name = "colorama" version = "0.4.6" description = "Cross-platform colored terminal text." -category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ @@ -344,7 +332,6 @@ files = [ name = "colorlog" version = "6.7.0" description = "Add colours to the output of Python's logging module." -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -362,7 +349,6 @@ development = ["black", "flake8", "mypy", "pytest", "types-colorama"] name = "coverage" version = "7.3.2" description = "Code coverage measurement for Python" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -427,7 +413,6 @@ toml = ["tomli"] name = "cryptography" version = "41.0.5" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -473,7 +458,6 @@ test-randomorder = ["pytest-randomly"] name = "dill" version = "0.3.7" description = "serialize all of Python" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -488,7 +472,6 @@ graph = ["objgraph (>=1.7.2)"] name = "distlib" version = "0.3.7" description = "Distribution utilities" -category = "dev" optional = false python-versions = "*" files = [ @@ -500,7 +483,6 @@ files = [ name = "docutils" version = "0.19" description = "Docutils -- Python Documentation Utilities" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -512,7 +494,6 @@ files = [ name = "exasol-toolbox" version = "0.6.2" description = "" -category = "dev" optional = false python-versions = ">=3.8,<4.0" files = [ @@ -541,7 +522,6 @@ typer = {version = ">=0.7.0", extras = ["all"]} name = "exasol-udf-mock-python" version = "0.1.0" description = "Mocking framework for Exasol Python UDFs" -category = "dev" optional = false python-versions = ">=3.8" files = [] @@ -562,7 +542,6 @@ resolved_reference = "81abae735e2d44f8a3ae5e7559eeb942b327d76a" name = "exceptiongroup" version = "1.1.3" description = "Backport of PEP 654 (exception groups)" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -577,7 +556,6 @@ test = ["pytest (>=6)"] name = "filelock" version = "3.13.1" description = "A platform independent file lock." -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -594,7 +572,6 @@ typing = ["typing-extensions (>=4.8)"] name = "furo" version = "2022.12.7" description = "A clean customisable Sphinx documentation theme." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -612,7 +589,6 @@ sphinx-basic-ng = "*" name = "identify" version = "2.5.32" description = "File identification library for Python" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -627,7 +603,6 @@ license = ["ukkonen"] name = "idna" version = "3.4" description = "Internationalized Domain Names in Applications (IDNA)" -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -639,7 +614,6 @@ files = [ name = "imagesize" version = "1.4.1" description = "Getting image size from png/jpeg/jpeg2000/gif file" -category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -651,7 +625,6 @@ files = [ name = "importlib-metadata" version = "6.8.0" description = "Read metadata from Python packages" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -671,7 +644,6 @@ testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs name = "importlib-resources" version = "6.1.1" description = "Read resources from Python packages" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -690,7 +662,6 @@ testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", name = "iniconfig" version = "2.0.0" description = "brain-dead simple config-ini parsing" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -702,7 +673,6 @@ files = [ name = "isort" version = "5.12.0" description = "A Python utility / library to sort Python imports." -category = "dev" optional = false python-versions = ">=3.8.0" files = [ @@ -720,7 +690,6 @@ requirements-deprecated-finder = ["pip-api", "pipreqs"] name = "jinja2" version = "3.1.2" description = "A very fast and expressive template engine." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -738,7 +707,6 @@ i18n = ["Babel (>=2.7)"] name = "joblib" version = "1.3.2" description = "Lightweight pipelining with Python functions" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -750,7 +718,6 @@ files = [ name = "markdown-it-py" version = "2.2.0" description = "Python port of markdown-it. Markdown parsing, done right!" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -775,7 +742,6 @@ testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] name = "markupsafe" version = "2.1.3" description = "Safely add untrusted strings to HTML/XML markup." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -799,6 +765,16 @@ files = [ {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, @@ -835,7 +811,6 @@ files = [ name = "mccabe" version = "0.7.0" description = "McCabe checker, plugin for flake8" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -847,7 +822,6 @@ files = [ name = "mdit-py-plugins" version = "0.3.5" description = "Collection of plugins for markdown-it-py" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -867,7 +841,6 @@ testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] name = "mdurl" version = "0.1.2" description = "Markdown URL utilities" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -879,7 +852,6 @@ files = [ name = "mypy" version = "1.7.0" description = "Optional static typing for Python" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -927,7 +899,6 @@ reports = ["lxml"] name = "mypy-extensions" version = "1.0.0" description = "Type system extensions for programs checked with the mypy type checker." -category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -939,7 +910,6 @@ files = [ name = "myst-parser" version = "0.18.1" description = "An extended commonmark compliant parser, with bridges to docutils & sphinx." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -966,7 +936,6 @@ testing = ["beautifulsoup4", "coverage[toml]", "pytest (>=6,<7)", "pytest-cov", name = "nodeenv" version = "1.8.0" description = "Node.js virtual environment builder" -category = "dev" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" files = [ @@ -981,7 +950,6 @@ setuptools = "*" name = "nox" version = "2022.11.21" description = "Flexible test automation." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1002,7 +970,6 @@ tox-to-nox = ["jinja2", "tox"] name = "numpy" version = "1.24.4" description = "Fundamental package for array computing in Python" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1040,7 +1007,6 @@ files = [ name = "packaging" version = "23.2" description = "Core utilities for Python packages" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1052,7 +1018,6 @@ files = [ name = "pandas" version = "1.5.3" description = "Powerful data structures for data analysis, time series, and statistics" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1088,8 +1053,8 @@ files = [ [package.dependencies] numpy = [ {version = ">=1.20.3", markers = "python_version < \"3.10\""}, - {version = ">=1.21.0", markers = "python_version >= \"3.10\""}, {version = ">=1.23.2", markers = "python_version >= \"3.11\""}, + {version = ">=1.21.0", markers = "python_version >= \"3.10\" and python_version < \"3.11\""}, ] python-dateutil = ">=2.8.1" pytz = ">=2020.1" @@ -1101,7 +1066,6 @@ test = ["hypothesis (>=5.5.3)", "pytest (>=6.0)", "pytest-xdist (>=1.31)"] name = "pathspec" version = "0.11.2" description = "Utility library for gitignore style pattern matching of file paths." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1113,7 +1077,6 @@ files = [ name = "platformdirs" version = "3.11.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1129,7 +1092,6 @@ test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-co name = "pluggy" version = "1.3.0" description = "plugin and hook calling mechanisms for python" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1145,7 +1107,6 @@ testing = ["pytest", "pytest-benchmark"] name = "pre-commit" version = "3.5.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1164,7 +1125,6 @@ virtualenv = ">=20.10.0" name = "prysk" version = "0.15.2" description = "Functional tests for command line applications" -category = "dev" optional = false python-versions = ">=3.7,<4.0.0" files = [ @@ -1182,7 +1142,6 @@ pytest-plugin = ["pytest (>=7.0.1)"] name = "pyasn1" version = "0.5.0" description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)" -category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" files = [ @@ -1194,7 +1153,6 @@ files = [ name = "pycparser" version = "2.21" description = "C parser in Python" -category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -1206,7 +1164,6 @@ files = [ name = "pyexasol" version = "0.24.0" description = "Exasol python driver with extra features" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -1230,7 +1187,6 @@ ujson = ["ujson"] name = "pygments" version = "2.17.1" description = "Pygments is a syntax highlighting package written in Python." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1246,7 +1202,6 @@ windows-terminal = ["colorama (>=0.4.6)"] name = "pylint" version = "3.0.2" description = "python code static checker" -category = "dev" optional = false python-versions = ">=3.8.0" files = [ @@ -1259,8 +1214,8 @@ astroid = ">=3.0.1,<=3.1.0-dev0" colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} dill = [ {version = ">=0.2", markers = "python_version < \"3.11\""}, - {version = ">=0.3.6", markers = "python_version >= \"3.11\""}, {version = ">=0.3.7", markers = "python_version >= \"3.12\""}, + {version = ">=0.3.6", markers = "python_version >= \"3.11\" and python_version < \"3.12\""}, ] isort = ">=4.2.5,<6" mccabe = ">=0.6,<0.8" @@ -1277,7 +1232,6 @@ testutils = ["gitpython (>3)"] name = "pyopenssl" version = "23.3.0" description = "Python wrapper module around the OpenSSL library" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1296,7 +1250,6 @@ test = ["flaky", "pretend", "pytest (>=3.0.1)"] name = "pytest" version = "7.4.3" description = "pytest: simple powerful testing with Python" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1315,11 +1268,27 @@ tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} [package.extras] testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] +[[package]] +name = "pytest-localserver" +version = "0.8.1" +description = "pytest plugin to test server connections locally." +optional = false +python-versions = ">=3.5" +files = [ + {file = "pytest-localserver-0.8.1.tar.gz", hash = "sha256:66569c34fef31a5750b16effd1cd1288a7a90b59155d005e7f916accd3dee4f1"}, + {file = "pytest_localserver-0.8.1-py3-none-any.whl", hash = "sha256:13bc3d9aca719a60a2d1e535c5f0a58c7c1c2ad749f6e9cf198a358f5345e1d0"}, +] + +[package.dependencies] +werkzeug = ">=0.10" + +[package.extras] +smtp = ["aiosmtpd"] + [[package]] name = "python-dateutil" version = "2.8.2" description = "Extensions to the standard Python datetime module" -category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" files = [ @@ -1334,7 +1303,6 @@ six = ">=1.5" name = "pytz" version = "2023.3.post1" description = "World timezone definitions, modern and historical" -category = "dev" optional = false python-versions = "*" files = [ @@ -1346,7 +1314,6 @@ files = [ name = "pyupgrade" version = "3.8.0" description = "A tool to automatically upgrade syntax for newer versions." -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1361,7 +1328,6 @@ tokenize-rt = ">=3.2.0" name = "pyyaml" version = "6.0.1" description = "YAML parser and emitter for Python" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -1370,6 +1336,7 @@ files = [ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, @@ -1377,8 +1344,15 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, + {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, + {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, + {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, @@ -1395,6 +1369,7 @@ files = [ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, @@ -1402,6 +1377,7 @@ files = [ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, @@ -1411,7 +1387,6 @@ files = [ name = "requests" version = "2.31.0" description = "Python HTTP for Humans." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1433,7 +1408,6 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] name = "rich" version = "13.7.0" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" -category = "dev" optional = false python-versions = ">=3.7.0" files = [ @@ -1453,7 +1427,6 @@ jupyter = ["ipywidgets (>=7.5.1,<9)"] name = "rsa" version = "4.9" description = "Pure-Python RSA implementation" -category = "dev" optional = false python-versions = ">=3.6,<4" files = [ @@ -1468,7 +1441,6 @@ pyasn1 = ">=0.1.3" name = "setuptools" version = "68.2.2" description = "Easily download, build, install, upgrade, and uninstall Python packages" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1485,7 +1457,6 @@ testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jar name = "shellingham" version = "1.5.4" description = "Tool to Detect Surrounding Shell" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1497,7 +1468,6 @@ files = [ name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" -category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -1509,7 +1479,6 @@ files = [ name = "snowballstemmer" version = "2.2.0" description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." -category = "dev" optional = false python-versions = "*" files = [ @@ -1521,7 +1490,6 @@ files = [ name = "soupsieve" version = "2.5" description = "A modern CSS selector implementation for Beautiful Soup." -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1533,7 +1501,6 @@ files = [ name = "sphinx" version = "5.3.0" description = "Python documentation generator" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -1569,7 +1536,6 @@ test = ["cython", "html5lib", "pytest (>=4.6)", "typed_ast"] name = "sphinx-basic-ng" version = "1.0.0b2" description = "A modern skeleton for Sphinx themes." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1587,7 +1553,6 @@ docs = ["furo", "ipython", "myst-parser", "sphinx-copybutton", "sphinx-inline-ta name = "sphinx-copybutton" version = "0.5.2" description = "Add a copy button to each of your code cells." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1606,7 +1571,6 @@ rtd = ["ipython", "myst-nb", "sphinx", "sphinx-book-theme", "sphinx-examples"] name = "sphinxcontrib-applehelp" version = "1.0.4" description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1622,7 +1586,6 @@ test = ["pytest"] name = "sphinxcontrib-devhelp" version = "1.0.2" description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document." -category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -1638,7 +1601,6 @@ test = ["pytest"] name = "sphinxcontrib-htmlhelp" version = "2.0.1" description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1654,7 +1616,6 @@ test = ["html5lib", "pytest"] name = "sphinxcontrib-jsmath" version = "1.0.1" description = "A sphinx extension which renders display math in HTML via JavaScript" -category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -1669,7 +1630,6 @@ test = ["flake8", "mypy", "pytest"] name = "sphinxcontrib-qthelp" version = "1.0.3" description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document." -category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -1685,7 +1645,6 @@ test = ["pytest"] name = "sphinxcontrib-serializinghtml" version = "1.1.5" description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)." -category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -1701,7 +1660,6 @@ test = ["pytest"] name = "tokenize-rt" version = "5.2.0" description = "A wrapper around the stdlib `tokenize` which roundtrips." -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1713,7 +1671,6 @@ files = [ name = "toml" version = "0.10.2" description = "Python Library for Tom's Obvious, Minimal Language" -category = "dev" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -1725,7 +1682,6 @@ files = [ name = "tomli" version = "2.0.1" description = "A lil' TOML parser" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1737,7 +1693,6 @@ files = [ name = "tomlkit" version = "0.12.3" description = "Style preserving TOML library" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1749,7 +1704,6 @@ files = [ name = "typeguard" version = "4.0.0" description = "Run-time type checker for Python" -category = "main" optional = false python-versions = ">=3.7.4" files = [ @@ -1769,7 +1723,6 @@ test = ["mypy (>=1.2.0)", "pytest (>=7)"] name = "typer" version = "0.9.0" description = "Typer, build great CLIs. Easy to code. Based on Python type hints." -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -1794,7 +1747,6 @@ test = ["black (>=22.3.0,<23.0.0)", "coverage (>=6.2,<7.0)", "isort (>=5.0.6,<6. name = "typing-extensions" version = "4.8.0" description = "Backported and Experimental Type Hints for Python 3.8+" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1806,7 +1758,6 @@ files = [ name = "urllib3" version = "2.1.0" description = "HTTP library with thread-safe connection pooling, file post, and more." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1823,7 +1774,6 @@ zstd = ["zstandard (>=0.18.0)"] name = "virtualenv" version = "20.24.6" description = "Virtual Python Environment builder" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1844,7 +1794,6 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess name = "websocket-client" version = "1.6.4" description = "WebSocket client for Python with low level API options" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1857,11 +1806,27 @@ docs = ["Sphinx (>=6.0)", "sphinx-rtd-theme (>=1.1.0)"] optional = ["python-socks", "wsaccel"] test = ["websockets"] +[[package]] +name = "werkzeug" +version = "3.0.1" +description = "The comprehensive WSGI web application library." +optional = false +python-versions = ">=3.8" +files = [ + {file = "werkzeug-3.0.1-py3-none-any.whl", hash = "sha256:90a285dc0e42ad56b34e696398b8122ee4c681833fb35b8334a095d82c56da10"}, + {file = "werkzeug-3.0.1.tar.gz", hash = "sha256:507e811ecea72b18a404947aded4b3390e1db8f826b494d76550ef45bb3b1dcc"}, +] + +[package.dependencies] +MarkupSafe = ">=2.1.1" + +[package.extras] +watchdog = ["watchdog (>=2.3)"] + [[package]] name = "zipp" version = "3.17.0" description = "Backport of pathlib-compatible object wrapper for zip files" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1876,4 +1841,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = ">=3.8,<4.0" -content-hash = "9dbe985d23f2d8a1e31755e3cb3548e72127dbbecc9a9bff9297cee1285ac002" +content-hash = "ad16e5777318a0c9b115013433edeeac93764e97296f11be16343a495e82cfc0" diff --git a/pyproject.toml b/pyproject.toml index 9da4f100..ba938836 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,6 +39,7 @@ toml = ">=0.10.2" sphinx-copybutton = "^0.5.0" myst-parser = "^0.18.1" exasol-toolbox = ">=0.3.0" +pytest-localserver = "^0.8.1" [tool.coverage.run] source = [ diff --git a/test/integration/bucketfs_test.py b/test/integration/bucketfs_test.py index 9e39bd8f..07c44dcb 100644 --- a/test/integration/bucketfs_test.py +++ b/test/integration/bucketfs_test.py @@ -1,5 +1,7 @@ import random import string +from contextlib import contextmanager +from inspect import cleandoc from typing import ( ByteString, Iterable, @@ -8,6 +10,7 @@ ) import pytest +import requests from integration.conftest import ( File, TestConfig, @@ -18,9 +21,22 @@ Bucket, Service, as_bytes, + as_string, ) +@contextmanager +def does_not_raise(exception_type: Exception = Exception): + try: + yield + + except exception_type as ex: + raise AssertionError(f"Raised exception {ex} when it should not!") from ex + + except Exception as ex: + raise AssertionError(f"An unexpected exception {ex} raised.") from ex + + @pytest.mark.parametrize( "expected", [ @@ -125,3 +141,154 @@ def test_list_files_in_bucket( expected = {file.name for file in files} actual = {file for file in bucket} assert expected.issubset(actual) + + +def test_ssl_verification_for_bucketfs_service_fails(httpsserver): + bucketfs_service_response = "Client should not be able to retrieve this!" + httpsserver.serve_content(bucketfs_service_response, 200) + CREDENTIALS = {"default": {"username": "w", "password": "write"}} + service = Service(httpsserver.url, CREDENTIALS) + + with pytest.raises(requests.exceptions.SSLError) as execinfo: + _ = [bucket for bucket in service] + assert "CERTIFICATE_VERIFY_FAILED" in str(execinfo) + + +def test_ssl_verification_for_bucketfs_service_can_be_bypassed(httpsserver): + bucketfs_service_response = cleandoc( + """ + default + demo_foo + demo_bar + """ + ) + httpsserver.serve_content(bucketfs_service_response, 200) + + CREDENTAILS = {"default": {"username": "w", "password": "write"}} + bucketfs = Service(httpsserver.url, CREDENTAILS, verify=False) + + expected = ["default", "demo_foo", "demo_bar"] + actual = [bucket for bucket in bucketfs] + assert expected == actual + + +def test_ssl_verification_for_bucket_files_fails(httpsserver): + response = "Client should not be able to retrieve this!" + httpsserver.serve_content(response, 200) + bucket = Bucket( + name="foo", + service=httpsserver.url, + username="user", + password="pw", + ) + + with pytest.raises(requests.exceptions.SSLError) as execinfo: + _ = {file for file in bucket} + assert "CERTIFICATE_VERIFY_FAILED" in str(execinfo) + + +def test_ssl_verification_for_bucket_files_can_be_bypassed(httpsserver): + response = "Client should not be able to retrieve this!" + httpsserver.serve_content(response, 200) + bucket = Bucket( + name="foo", + service=httpsserver.url, + username="user", + password="pw", + verify=False, + ) + + with does_not_raise(requests.exceptions.SSLError): + _ = {file for file in bucket} + + +def test_ssl_verification_for_bucket_upload_fails(httpsserver): + response = "Client should not be able to retrieve this!" + httpsserver.serve_content(response, 200) + bucket = Bucket( + name="foo", + service=httpsserver.url, + username="user", + password="pw", + ) + + with pytest.raises(requests.exceptions.SSLError) as execinfo: + data = bytes([65, 65, 65, 65]) + bucket.upload("some/other/path/file2.bin", data) + assert "CERTIFICATE_VERIFY_FAILED" in str(execinfo) + + +def test_ssl_verification_for_bucket_upload_can_be_bypassed(httpsserver): + response = "Client should not be able to retrieve this!" + httpsserver.serve_content(response, 200) + bucket = Bucket( + name="foo", + service=httpsserver.url, + username="user", + password="pw", + verify=False, + ) + + with does_not_raise(requests.exceptions.SSLError): + data = bytes([65, 65, 65, 65]) + bucket.upload("some/other/path/file2.bin", data) + + +def test_ssl_verification_for_bucket_delete_fails(httpsserver): + response = "Client should not be able to retrieve this!" + httpsserver.serve_content(response, 200) + bucket = Bucket( + name="foo", + service=httpsserver.url, + username="user", + password="pw", + ) + + with pytest.raises(requests.exceptions.SSLError) as execinfo: + bucket.delete("some/other/path/file2.bin") + assert "CERTIFICATE_VERIFY_FAILED" in str(execinfo) + + +def test_ssl_verification_for_bucket_delete_can_be_bypassed(httpsserver): + response = "Client should not be able to retrieve this!" + httpsserver.serve_content(response, 200) + bucket = Bucket( + name="foo", + service=httpsserver.url, + username="user", + password="pw", + verify=False, + ) + + with does_not_raise(requests.exceptions.SSLError): + bucket.delete("some/other/path/file2.bin") + + +def test_ssl_verification_for_bucket_download_fails(httpsserver): + response = "Client should not be able to retrieve this!" + httpsserver.serve_content(response, 200) + bucket = Bucket( + name="foo", + service=httpsserver.url, + username="user", + password="pw", + ) + + with pytest.raises(requests.exceptions.SSLError) as execinfo: + _ = as_string(bucket.download("some/other/path/file2.bin")) + assert "CERTIFICATE_VERIFY_FAILED" in str(execinfo) + + +def test_ssl_verification_for_bucket_download_can_be_bypassed(httpsserver): + response = "Client should not be able to retrieve this!" + httpsserver.serve_content(response, 200) + bucket = Bucket( + name="foo", + service=httpsserver.url, + username="user", + password="pw", + verify=False, + ) + + with does_not_raise(requests.exceptions.SSLError): + _ = as_string(bucket.download("some/other/path/file2.bin"))