generated from jCloud/repository-template
71 lines
2.4 KiB
Python
71 lines
2.4 KiB
Python
# Copyright 2026 jCloud
|
|
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
import httpx
|
|
from .exceptions import S3InternalServerError, S3ObjectNotFoundError, S3ObjectPermissionError
|
|
from typing import AsyncGenerator, Optional
|
|
from urllib.parse import urljoin
|
|
|
|
__all__ = [
|
|
'S3Backend'
|
|
]
|
|
|
|
class S3Backend:
|
|
'''
|
|
A class representing an S3 compatible backend.
|
|
|
|
:param url: The URL of the backend.
|
|
:type url: str
|
|
'''
|
|
|
|
def __init__(self, url: str) -> None:
|
|
self.url = url
|
|
|
|
async def get_object(self, object_path: str, range: Optional[str] = None) -> AsyncGenerator[bytes]:
|
|
'''
|
|
Returns the bytes of an object.
|
|
|
|
:param object_path: The path of the object. It consists of the
|
|
bucket name and the object key.
|
|
:type object_path: str
|
|
:param range: The bytes range as an RFC 9110-compliant string.
|
|
:type range: Optional[str]
|
|
|
|
:return: An iterator yielding the object bytes.
|
|
:rtype: AsyncGenerator[bytes]
|
|
'''
|
|
|
|
if range is not None:
|
|
headers = {
|
|
'Range': f'bytes={range}'
|
|
}
|
|
else:
|
|
headers = {}
|
|
|
|
async with httpx.AsyncClient() as client:
|
|
async with client.stream(
|
|
'GET',
|
|
urljoin(self.url, object_path),
|
|
headers = headers
|
|
) as response:
|
|
if response.status_code == 404:
|
|
raise S3ObjectNotFoundError('Object not found', object_path = object_path)
|
|
if response.status_code == 403:
|
|
raise S3ObjectPermissionError('Object access forbidden', object_path = object_path)
|
|
if response.status_code // 100 == 5:
|
|
raise S3InternalServerError('Internal server error')
|
|
|
|
async for chunk in response.aiter_bytes():
|
|
yield chunk
|