generated from jCloud/repository-template
Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0e1b875b31
|
||
|
|
e758522d80
|
||
|
|
099878ba57
|
||
|
|
1f9789619b
|
||
|
|
447647bdcb
|
+5
-2
@@ -5,11 +5,14 @@ build-backend = "setuptools.build_meta"
|
||||
[project]
|
||||
name = "jcloud-s3fuse"
|
||||
version = "0.1.0"
|
||||
description = "A userspace filesystem for S3 compatible object stores "
|
||||
description = "A userspace filesystem for S3 compatible object stores"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.11"
|
||||
|
||||
dependencies = []
|
||||
dependencies = [
|
||||
"httpx>=0.28.1",
|
||||
"lxml>=6.0.2"
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
dev = []
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
# 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
|
||||
@@ -0,0 +1,56 @@
|
||||
# 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.
|
||||
|
||||
__all__ = [
|
||||
'S3Error',
|
||||
'S3InternalServerError',
|
||||
'S3ObjectError',
|
||||
'S3ObjectNotFoundError',
|
||||
'S3ObjectPermissionError'
|
||||
]
|
||||
|
||||
class S3Error(Exception):
|
||||
'''
|
||||
Base class for all S3 related errors.
|
||||
'''
|
||||
|
||||
class S3InternalServerError(S3Error):
|
||||
'''
|
||||
Internal S3 server error.
|
||||
'''
|
||||
|
||||
class S3ObjectError(S3Error):
|
||||
'''
|
||||
Base class for S3 object related errors.
|
||||
'''
|
||||
|
||||
def __init__(self, *args: object, object_path: str = '') -> None:
|
||||
super().__init__(*args)
|
||||
self.object_path = object_path
|
||||
|
||||
def __str__(self):
|
||||
if self.args:
|
||||
return f'{self.args[0]}{": " if self.object_path and self.args[0] else ""}{self.object_path if self.args[0] else ""}'
|
||||
else:
|
||||
return ''
|
||||
|
||||
class S3ObjectNotFoundError(S3ObjectError):
|
||||
'''
|
||||
S3 object was not found.
|
||||
'''
|
||||
|
||||
class S3ObjectPermissionError(S3ObjectError):
|
||||
'''
|
||||
Bad permissions to retrieve S3 object.
|
||||
'''
|
||||
Reference in New Issue
Block a user