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

feature/add support for tls settings #93

Merged
merged 10 commits into from
Feb 16, 2024
72 changes: 52 additions & 20 deletions exasol/bucketfs/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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``.
Nicoretti marked this conversation as resolved.
Show resolved Hide resolved
"""
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:
Expand All @@ -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,
Nicoretti marked this conversation as resolved.
Show resolved Hide resolved
):
"""
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}>"
Expand All @@ -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:
Expand All @@ -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]
Nicoretti marked this conversation as resolved.
Show resolved Hide resolved
) -> None:
"""
Uploads a file onto this bucket
Expand All @@ -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:
Expand All @@ -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:
Expand All @@ -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:
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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"]
Expand Down Expand Up @@ -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.

Expand Down
5 changes: 4 additions & 1 deletion exasol/bucketfs/version.py
Original file line number Diff line number Diff line change
@@ -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 <path/version.py> --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
Expand Down
Loading
Loading