Skip to content

Commit

Permalink
Merge pull request #368 from aiven/van_ess0_custom_files_api_support
Browse files Browse the repository at this point in the history
Add custom files API support
  • Loading branch information
mteiste authored Dec 19, 2023
2 parents 1f64f18 + 15c4ad1 commit dce793a
Show file tree
Hide file tree
Showing 7 changed files with 475 additions and 7 deletions.
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ build-dep-fedora:
python3-mypy \
python3-pytest \
python3-requests \
python3-requests-toolbelt \
python3-types-requests \
python3-setuptools_scm \
rpmdevtools \
Expand Down
16 changes: 16 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -343,9 +343,25 @@ Delete a service integration::

$ avn service integration-delete --project <project> <integration_id>

Custom Files
--------------------

Listing files::

$ anv service custom-file list --project <project> <service_name>

Reading file::

$ anv service custom-file get --project <project> --file_id <file_id> [--target_filepath <file_path>] [--stdout_write] <service_name>


Uploading new files::

$ avn service custom-file upload --project <project> --file_type <file_type> --file_path <file_path> --file_name <file_name> <service_name>

Updating existing files::

$ avn service custom-file update --project <project> --file_path <file_path> --file_id <file_id> <service_name>
.. _teams:

Working with Teams
Expand Down
71 changes: 71 additions & 0 deletions aiven/client/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -5546,6 +5546,77 @@ def service__flink__cancel_application_deployments(self) -> None:
),
)

@arg.project
@arg.service_name
def service__custom_file__list(self) -> None:
"""List all the custom files for the specified service"""
self.print_response(
self.client.custom_file_list(
project=self.args.project,
service=self.args.service_name,
),
)

@arg.project
@arg.service_name
@arg("--file_id", help="A file ID, usually an uuid4 string", required=True)
@arg("--target_filepath", help="Where to save the file downloaded")
@arg("--stdout_write", help="Write to the stdout instead of file", action="store_true")
def service__custom_file__get(self) -> None:
"""
Download the custom file for the specified service and ID
Requires at least `--target_filepath` or `--stdout_write`, but able to handle both
"""
if not (self.args.target_filepath or self.args.stdout_write):
raise argx.UserError("You need to specify `--target_filepath` or `--stdout_write` or both")

result = self.client.custom_file_get(
project=self.args.project,
service=self.args.service_name,
file_id=self.args.file_id,
)

if self.args.target_filepath:
with open(self.args.target_filepath, "wb") as f:
f.write(result)

if self.args.stdout_write:
print(result.decode("utf-8"))

@arg.project
@arg.service_name
@arg("--file_path", help="A path to the file", required=True)
@arg("--file_type", choices=["synonyms", "stopwords", "wordnet"], required=True)
@arg("--file_name", help="A name for the file", required=True)
def service__custom_file__upload(self) -> None:
"""Upload custom file for the specified service"""
with open(self.args.file_path, "rb") as f:
self.print_response(
self.client.custom_file_upload(
project=self.args.project,
service=self.args.service_name,
file_type=self.args.file_type,
file_name=self.args.file_name,
file_object=f,
),
)

@arg.project
@arg.service_name
@arg("--file_path", help="A path to the file", required=True)
@arg("--file_id", help="A file ID, usually an uuid4 string", required=True)
def service__custom_file__update(self) -> None:
"""Update custom file for the specified service and ID"""
with open(self.args.file_path, "rb") as f:
self.print_response(
self.client.custom_file_update(
project=self.args.project,
service=self.args.service_name,
file_id=self.args.file_id,
file_object=f,
),
)

@arg.json
@arg("name", help="Name of the organization to create")
@arg.force
Expand Down
76 changes: 71 additions & 5 deletions aiven/client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
from .session import get_requests_session
from http import HTTPStatus
from requests import Response
from typing import Any, Callable, Collection, Mapping, Sequence, TypedDict
from requests_toolbelt import MultipartEncoder # type: ignore
from typing import Any, BinaryIO, Callable, Collection, Mapping, Sequence, TypedDict
from urllib.parse import quote

import json
Expand Down Expand Up @@ -79,6 +80,10 @@ def _execute(self, func: Callable, method: str, path: str, body: Any, params: An
headers["content-type"] = "application/json"
data = json.dumps(body)
log_data = json.dumps(body, sort_keys=True, indent=4)
elif isinstance(body, MultipartEncoder):
headers["content-type"] = body.content_type
data = body
log_data = data
else:
headers["content-type"] = "application/octet-stream"
data = body
Expand Down Expand Up @@ -174,11 +179,28 @@ def verify(
)
time.sleep(0.2)

return self._process_response(response=response, op=op, path=path, result_key=result_key)

@staticmethod
def build_path(*parts: str) -> str:
return "/" + "/".join(quote(part, safe="") for part in parts)

def _process_response(
self,
response: Response,
op: Callable[..., Response],
path: str,
result_key: str | None = None,
) -> Mapping | bytes:
# Check API is actually returning data or not
if response.status_code == HTTPStatus.NO_CONTENT or len(response.content) == 0:
return {}

if response.headers.get("Content-Type") == "application/octet-stream":
return response.content

result = response.json()

if result.get("error"):
raise ResponseError(
"server returned error: {op} {base_url}{path} {result}".format(
Expand All @@ -190,10 +212,6 @@ def verify(
return result[result_key]
return result

@staticmethod
def build_path(*parts: str) -> str:
return "/" + "/".join(quote(part, safe="") for part in parts)


class AivenClient(AivenClientBase):
"""Aiven Client with high-level operations"""
Expand Down Expand Up @@ -2138,6 +2156,54 @@ def clickhouse_database_list(self, project: str, service: str) -> Mapping:
path = self.build_path("project", project, "service", service, "clickhouse", "db")
return self.verify(self.get, path, result_key="databases")

def custom_file_list(self, project: str, service: str) -> Mapping:
path = self.build_path("project", project, "service", service, "file")
return self.verify(self.get, path)

def custom_file_get(self, project: str, service: str, file_id: str) -> bytes:
path = self.build_path("project", project, "service", service, "file", file_id)
return self.verify(self.get, path)

def custom_file_upload(
self,
project: str,
service: str,
file_type: str,
file_object: BinaryIO,
file_name: str,
update: bool = False,
) -> Mapping:
path = self.build_path("project", project, "service", service, "file")
return self.verify(
self.post,
path,
body=MultipartEncoder(
fields={
"file": (file_name, file_object, "application/octet-stream"),
"filetype": file_type,
"filename": file_name,
}
),
)

def custom_file_update(
self,
project: str,
service: str,
file_object: BinaryIO,
file_id: str,
) -> Mapping:
path = self.build_path("project", project, "service", service, "file", file_id)
return self.verify(
self.put,
path,
body=MultipartEncoder(
fields={
"file": (file_id, file_object, "application/octet-stream"),
}
),
)

def flink_list_applications(
self,
*,
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ dynamic = ["version"]
dependencies = [
'requests>=2.2.1; sys_platform == "linux"',
'requests>=2.9.1; sys_platform != "linux"',
"requests-toolbelt>=0.9.0",
"certifi>=2015.11.20.1",
]
[project.optional-dependencies]
Expand Down
Loading

0 comments on commit dce793a

Please sign in to comment.