diff --git a/.gitignore b/.gitignore index 3101d2b..ab3e8ce 100644 --- a/.gitignore +++ b/.gitignore @@ -1,15 +1,4 @@ # ---> Python -# ignore IDE files -.vscode -.idea -venv.* -.repo -.git -.git_shadow - -# ignore folders -protos - # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] diff --git a/frostfs_api/__init__.py b/frostfs_api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/frostfs_sdk/cryptography/__init__.py b/frostfs_api/cryptography/__init__.py similarity index 100% rename from frostfs_sdk/cryptography/__init__.py rename to frostfs_api/cryptography/__init__.py diff --git a/frostfs_api/cryptography/key_extension.py b/frostfs_api/cryptography/key_extension.py new file mode 100644 index 0000000..5844e89 --- /dev/null +++ b/frostfs_api/cryptography/key_extension.py @@ -0,0 +1,44 @@ +import base58 +import ecdsa + + +class KeyExtension: + def get_private_key_from_wif(self, wif: str) -> bytes: + """ + Converts a WIF private key to a byte array. + + :param wif: WIF private key in string format. + :return: Private key in byte format (32 bytes). + :raises ValueError: If the WIF key is incorrect. + """ + assert not self.is_empty(wif) + + decoded = base58.b58decode_check(wif) + if len(decoded) != 34 or decoded[0] != 0x80 or decoded[-1] != 0x01: + raise ValueError("Incorrect WIF private key") + + private_key = decoded[1:-1] + return private_key + + def get_public_key(self, private_key: bytes) -> bytes: + """ + Extract public key from Private key + + :param private_key: Private key in byte format (32 bytes). + :return: compressed public key in byte format (33 bytes). + :raises ValueError: If the private_key key is empty or null. + """ + assert not self.is_empty(private_key) + + if len(private_key) != 32: + raise ValueError(f"Incorrect len of private key, Expected: 32, Actual: {len(private_key)}") + + public_key = ecdsa.SigningKey.from_string(private_key, curve=ecdsa.NIST256p).get_verifying_key() + compressed_public_key = public_key.to_string("compressed") + return compressed_public_key + + @staticmethod + def is_empty(sequence_symbols: bytes | str): + if len(sequence_symbols) == 0 or sequence_symbols is None: + raise ValueError(f"Empty sequence symbols of key: {sequence_symbols}") + return False diff --git a/frostfs_api/cryptography/signer.py b/frostfs_api/cryptography/signer.py new file mode 100644 index 0000000..f8c7ef6 --- /dev/null +++ b/frostfs_api/cryptography/signer.py @@ -0,0 +1,26 @@ +import ecdsa +from hashlib import sha256, sha512 + + +class Signer: + def sign_rfc6979(self, private_key: bytes, message: bytes) -> bytes: + if len(private_key) == 0 or private_key is None: + raise ValueError(f"Incorrect private_key: {private_key}") + + sk = ecdsa.SigningKey.from_string(private_key, curve=ecdsa.NIST256p, hashfunc=sha256) + + signature = sk.sign_deterministic(message) + + return signature + + def sign(self, private_key: bytes, message: bytes) -> bytes: + if len(private_key) == 0 or private_key is None: + raise ValueError(f"Incorrect private key: {private_key}") + + sk = ecdsa.SigningKey.from_string(private_key, curve=ecdsa.NIST256p, hashfunc=sha512) + signature = sk.sign(message) + + # the first byte indicates the node version marker + signature_with_marker = bytes([0x04]) + signature + + return signature_with_marker diff --git a/frostfs_sdk/cryptography/verifier.py b/frostfs_api/cryptography/verifier.py similarity index 100% rename from frostfs_sdk/cryptography/verifier.py rename to frostfs_api/cryptography/verifier.py diff --git a/frostfs_sdk/__init__.py b/frostfs_sdk/__init__.py deleted file mode 100644 index 4934e5f..0000000 --- a/frostfs_sdk/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from frostfs_sdk.client import * -from frostfs_sdk.models import * diff --git a/frostfs_sdk/client/__init__.py b/frostfs_sdk/client/__init__.py deleted file mode 100644 index f9d922d..0000000 --- a/frostfs_sdk/client/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -from frostfs_sdk.client.frostfs_client import FrostfsClient - -from frostfs_sdk.client.models.client_environment import ClientEnvironment -from frostfs_sdk.client.models.client_settings import ClientSettings -from frostfs_sdk.client.models.ecdsa_model import ECDSA - -from frostfs_sdk.client.parameters.container_param import ContainerCreateParam -from frostfs_sdk.client.parameters.wait_param import WaitParam - -from frostfs_sdk.client.services.container import ContainerClient diff --git a/frostfs_sdk/client/frostfs_client.py b/frostfs_sdk/client/frostfs_client.py deleted file mode 100644 index 5113587..0000000 --- a/frostfs_sdk/client/frostfs_client.py +++ /dev/null @@ -1,33 +0,0 @@ -# Create channel and Stubs -import grpc - -from frostfs_sdk.client.utils.session_cache import SessionCache -from frostfs_sdk.client.models.client_environment import ClientEnvironment -from frostfs_sdk.client.models.client_settings import ClientSettings -from frostfs_sdk.client.models.ecdsa_model import ECDSA -from frostfs_sdk.client.services.container import ContainerClient -from frostfs_sdk.models.dto.version import Version - - -class FrostfsClient: - def __init__(self, client_settings: ClientSettings): - self.channel = grpc.insecure_channel(client_settings.address) - self.ecdsa: ECDSA = ECDSA(wif=client_settings.wif) - - client_environment = ClientEnvironment(self.ecdsa, self.channel, client_settings.address, Version(), SessionCache(0)) - self.container = ContainerClient(client_environment) - - def close(self): - self.channel.close() - - - -""" -import frostfs_sdk - -WIF = "L5XNVUzPnma6m4mPrWEN6CcTscJERcfX3yvb1cdffdxe1iriAshU" -address = "10.78.128.25:8080" -client = frostfs_sdk.FrostfsClient(ClientSettings(WIF, address)) -params = frostfs_sdk.models.PrmsCreateContainer(name="1234") -client.container.create(params) -""" diff --git a/frostfs_sdk/client/models/client_environment.py b/frostfs_sdk/client/models/client_environment.py deleted file mode 100644 index 29631a6..0000000 --- a/frostfs_sdk/client/models/client_environment.py +++ /dev/null @@ -1,22 +0,0 @@ -import grpc -from frostfs_sdk.cryptography.key_extension import KeyExtension -from frostfs_sdk.client.models.ecdsa_model import ECDSA -from frostfs_sdk.models.dto.version import Version -from frostfs_sdk.models.dto.owner_id import OwnerId -from frostfs_sdk.client.utils.session_cache import SessionCache - - -class ClientEnvironment: - def __init__(self, ecdsa: ECDSA, channel: grpc.Channel, address: str, version: Version, session_cache: SessionCache): - self.ecdsa = ecdsa - self.channel = channel - self.version = version - self.owner_id = OwnerId(KeyExtension().get_owner_id_by_public_key(ecdsa.public_key)) - self.session_cache = session_cache - self.address = address - self._session_key = None - - def get_session_key(self): - if not self._session_key: - self._session_key = SessionCache.form_cache_key(self.address, KeyExtension.get_hex_string(self.ecdsa.public_key)) - return self._session_key diff --git a/frostfs_sdk/client/models/client_settings.py b/frostfs_sdk/client/models/client_settings.py deleted file mode 100644 index b7bd1df..0000000 --- a/frostfs_sdk/client/models/client_settings.py +++ /dev/null @@ -1,19 +0,0 @@ -class ClientSettings: - def __init__(self, wif: str = None, address: str = None): - """ - Initializes client settings with validation. - - Args: - wif: Wallet import format string - address: FrostFS node host address - """ - self.wif = wif - self.address = address - - # Perform validation after initialization - self.validate() - - def validate(self): - """Performs runtime validation of the settings""" - if not (self.address and self.wif): - raise ValueError("The value must be specified ADDRESS and WIF") diff --git a/frostfs_sdk/client/models/ecdsa_model.py b/frostfs_sdk/client/models/ecdsa_model.py deleted file mode 100644 index c04d116..0000000 --- a/frostfs_sdk/client/models/ecdsa_model.py +++ /dev/null @@ -1,8 +0,0 @@ -from frostfs_sdk.cryptography.key_extension import KeyExtension - - -class ECDSA: - def __init__(self, wif: str): - self.wif = wif - self.private_key: bytes = KeyExtension().get_private_key_from_wif(wif) - self.public_key: bytes = KeyExtension().get_public_key(self.private_key) diff --git a/frostfs_sdk/client/parameters/call_context_param.py b/frostfs_sdk/client/parameters/call_context_param.py deleted file mode 100644 index 4b15546..0000000 --- a/frostfs_sdk/client/parameters/call_context_param.py +++ /dev/null @@ -1,21 +0,0 @@ -from dataclasses import dataclass, field -from enum import Enum -from typing import Optional - - -DEFAULT_GRPC_TIMEOUT = 5 - - -class TimeUnit(Enum): - MINUTES = "MINUTES" - SECONDS = "SECONDS" - MILLISECONDS = "MILLISECONDS" - -@dataclass -class CallContextParam: - timeout: int = DEFAULT_GRPC_TIMEOUT - time_unit: TimeUnit = TimeUnit.SECONDS - - @classmethod - def default(cls): - return cls() diff --git a/frostfs_sdk/client/parameters/container_param.py b/frostfs_sdk/client/parameters/container_param.py deleted file mode 100644 index 464c070..0000000 --- a/frostfs_sdk/client/parameters/container_param.py +++ /dev/null @@ -1,18 +0,0 @@ -from dataclasses import dataclass, field -from typing import Optional, Dict - -from frostfs_sdk.models.dto.container import Container -from frostfs_sdk.client.utils.session_cache import SessionToken -from frostfs_sdk.client.parameters.wait_param import WaitParam - - -@dataclass(frozen=True) -class ContainerCreateParam: - container: Container - wait_params: Optional[WaitParam] = None - session_token: Optional[SessionToken] = None - x_headers: Dict[str, str] = field(default_factory=dict) - - def __post_init__(self): - if self.wait_params is None: - object.__setattr__(self, 'wait_params', WaitParam()) diff --git a/frostfs_sdk/client/parameters/create_session_param.py b/frostfs_sdk/client/parameters/create_session_param.py deleted file mode 100644 index a694974..0000000 --- a/frostfs_sdk/client/parameters/create_session_param.py +++ /dev/null @@ -1,14 +0,0 @@ -from dataclasses import dataclass, field -from typing import Dict, Optional - -@dataclass -class CreateSessionParam: - """ - Represents parameters for creating a session. - """ - expiration: int # -1 indicates maximum expiration - x_headers: Optional[Dict[str, str]] = field(default_factory=dict) - - def __init__(self, expiration: int, x_headers: Optional[Dict[str, str]] = None): - self.expiration = expiration - self.x_headers = x_headers if x_headers is not None else {} diff --git a/frostfs_sdk/client/parameters/wait_param.py b/frostfs_sdk/client/parameters/wait_param.py deleted file mode 100644 index e05fcb7..0000000 --- a/frostfs_sdk/client/parameters/wait_param.py +++ /dev/null @@ -1,21 +0,0 @@ -from dataclasses import dataclass, field -from datetime import datetime, timedelta -from typing import Optional - - -@dataclass(frozen=True) -class WaitParam: - DEFAULT_TIMEOUT: timedelta = field(default=timedelta(seconds=120), init=False) - DEFAULT_POLL_INTERVAL: timedelta = field(default=timedelta(seconds=5), init=False) - - timeout: timedelta = DEFAULT_TIMEOUT - poll_interval: timedelta = DEFAULT_POLL_INTERVAL - - def __post_init__(self): - if self.timeout is None: - object.__setattr__(self, 'timeout', self.DEFAULT_TIMEOUT) - if self.poll_interval is None: - object.__setattr__(self, 'poll_interval', self.DEFAULT_POLL_INTERVAL) - - def get_deadline(self) -> datetime: - return datetime.now() + self.timeout diff --git a/frostfs_sdk/client/services/container.py b/frostfs_sdk/client/services/container.py deleted file mode 100644 index 4d7beca..0000000 --- a/frostfs_sdk/client/services/container.py +++ /dev/null @@ -1,64 +0,0 @@ -# implementation Conainer methods -from frostfs_sdk.client.models.client_environment import ClientEnvironment -from frostfs_sdk.client.services.context_accessor import ContextAccessor -from frostfs_sdk.client.services.session import SessionClient -from frostfs_sdk.client.parameters.container_param import ContainerCreateParam -from frostfs_sdk.client.parameters.call_context_param import CallContextParam -from frostfs_sdk.client.utils.session_cache import SessionToken -from frostfs_sdk.client.utils.request_constructor import RequestConstructor -from frostfs_sdk.cryptography.signer import Signer -from frostfs_sdk.models.dto.container import ContainerId, Container -from frostfs_sdk.models.mappers.container_mapper import ContainerMapper -from frostfs_sdk.models.mappers.owner_id_mapper import OwnerIdMapper -from frostfs_sdk.models.mappers.version_mapper import VersionMapper -from frostfs_sdk.client.parameters.create_session_param import CreateSessionParam - -from frostfs_sdk.protos.models.container import service_pb2 as service_pb2_container -from frostfs_sdk.protos.models.container import service_pb2_grpc as service_pb2_grpc_container - - -class ContainerClient(ContextAccessor): - def __init__(self, client_environment: ClientEnvironment): - super().__init__(client_environment) - self.container_stub = service_pb2_grpc_container.ContainerServiceStub(client_environment.channel) - self.ecdsa = client_environment.ecdsa - - def create_container(self, container_create_param: ContainerCreateParam, ctx: CallContextParam) -> ContainerId: - request = self._create_put_request(container_create_param, ctx) - response: service_pb2_container.PutResponse = self.container_stub.Put(request) - return ContainerId(value=response.body.container_id.value) - - def _create_put_request(self, param: ContainerCreateParam, ctx: CallContextParam) -> service_pb2_container.PutRequest: - """ - Creates a PUT request for creating a container. - """ - - grpc_container=ContainerMapper().to_grpc_message(param.container, self.get_context) - - body = service_pb2_container.PutRequest.Body( - container=grpc_container, - signature=Signer.sign_message_rfc_6979(self.get_context.ecdsa, grpc_container) - ) - - request = service_pb2_container.PutRequest(body=body) - session_token = self.get_or_create_session(param.session_token, ctx) - proto_token = session_token - RequestConstructor.add_meta_header(request, param.x_headers, proto_token) - - signed_request = Signer.sign(self.ecdsa.private_key, request) - return signed_request - - def get_or_create_session(self, session_ctx: SessionToken, ctx: CallContextParam) -> bytes: - if session_ctx: - return session_ctx.token - - session_token_from_cache = self.get_context.session_cache.try_get_value(self.get_context.get_session_key()) - if session_token_from_cache: - return session_token_from_cache.token - - new_session_token = SessionClient(self.get_context).create_session(CreateSessionParam(expiration=-1), ctx) - if new_session_token: - self.get_context.session_cache.set_value(self.get_context.get_session_key(), new_session_token) - return new_session_token.token - - raise ValueError("cannot create session") diff --git a/frostfs_sdk/client/services/context_accessor.py b/frostfs_sdk/client/services/context_accessor.py deleted file mode 100644 index 913bcaa..0000000 --- a/frostfs_sdk/client/services/context_accessor.py +++ /dev/null @@ -1,12 +0,0 @@ -from frostfs_sdk.client.models.client_environment import ClientEnvironment - -class ContextAccessor: - def __init__(self, context: ClientEnvironment): - """ - Initializes a ContextAccessor with a given ClientEnvironment. - """ - self.context: ClientEnvironment = context - - @property - def get_context(self) -> ClientEnvironment: - return self.context diff --git a/frostfs_sdk/client/services/session.py b/frostfs_sdk/client/services/session.py deleted file mode 100644 index 5d191fd..0000000 --- a/frostfs_sdk/client/services/session.py +++ /dev/null @@ -1,38 +0,0 @@ -from typing import Optional -from dataclasses import dataclass - -from frostfs_sdk.client.utils.session_cache import SessionToken -from frostfs_sdk.cryptography.signer import Signer -from frostfs_sdk.models.mappers.session_mapper import SessionMapper -from frostfs_sdk.models.mappers.owner_id_mapper import OwnerIdMapper -from frostfs_sdk.client.models.client_environment import ClientEnvironment -from frostfs_sdk.client.services.context_accessor import ContextAccessor -from frostfs_sdk.client.utils.request_constructor import RequestConstructor -from frostfs_sdk.client.parameters.call_context_param import CallContextParam -from frostfs_sdk.client.parameters.create_session_param import CreateSessionParam - -from frostfs_sdk.protos.models.session import service_pb2_grpc as service_pb2_grpc_session -from frostfs_sdk.protos.models.session import service_pb2 as service_pb2_session -from frostfs_sdk.protos.models.session import types_pb2 as types_pb2_session - - -class SessionClient(ContextAccessor): - def __init__(self, client_environment: ClientEnvironment): - super().__init__(client_environment) - self.session_stub = service_pb2_grpc_session.SessionServiceStub(client_environment.channel) - - def create_session(self, param: CreateSessionParam, ctx: CallContextParam) -> SessionToken: - body = service_pb2_session.CreateRequest.Body( - owner_id=OwnerIdMapper.to_grpc_message(self.get_context.owner_id), - expiration=param.expiration - ) - request = service_pb2_session.CreateRequest( - body=body - ) - RequestConstructor.add_meta_header(request, None, None) - signed_request = Signer.sign_message(self.get_context.ecdsa.private_key, request) - response: service_pb2_session.CreateResponse = self.session_stub.Create(request) - - session_token_grpc = types_pb2_session.SessionToken(response.body) - token = SessionMapper.serialize(session_token_grpc) - return SessionToken(token=token) diff --git a/frostfs_sdk/client/utils/message_helper.py b/frostfs_sdk/client/utils/message_helper.py deleted file mode 100644 index 2806109..0000000 --- a/frostfs_sdk/client/utils/message_helper.py +++ /dev/null @@ -1,30 +0,0 @@ -from typing import Any -from google.protobuf.message import Message - -class MessageHelper: - @staticmethod - def get_field(message: Message, field_name: str): - """ - Retrieves the value of a field from a Protobuf message. - - :param message: A Protobuf Message object. - :param field_name: The name of the field to retrieve. - :return: The value of the specified field. - :raises ValueError: If the input parameters are invalid. - """ - if not message or not field_name or not isinstance(field_name, str) or not field_name.strip(): - raise ValueError("Some parameter is missing") - - descriptor = message.DESCRIPTOR - field_descriptor = descriptor.fields[field_name] - if not field_descriptor: - raise ValueError(f"Field '{field_name}' not found in message descriptor") - - return getattr(field_descriptor, field_name) - - @staticmethod - def set_field(message: Message, field_name: str, value: Any) -> None: - if message is None or not field_name.strip() or value is None: - raise ValueError("Some parameter is missing") - - setattr(message, message.DESCRIPTOR.fields[field_name], value) diff --git a/frostfs_sdk/client/utils/request_constructor.py b/frostfs_sdk/client/utils/request_constructor.py deleted file mode 100644 index ab3174b..0000000 --- a/frostfs_sdk/client/utils/request_constructor.py +++ /dev/null @@ -1,47 +0,0 @@ -from google.protobuf.message import Message -from typing import Dict, Optional - -from frostfs_sdk.models.mappers.meta_header_mapper import MetaHeaderMapper -from frostfs_sdk.models.mappers.session_mapper import SessionMapper -from frostfs_sdk.models.dto.meta_header import MetaHeader -from frostfs_sdk.protos.models.session import types_pb2 as types_pb2_session - - -META_HEADER_FIELD_NAME = "meta_header" - - -class RequestConstructor: - @staticmethod - def add_meta_header(request: Message, x_headers: Optional[Dict[str, str]] = None, session_token: types_pb2_session.SessionToken = None): - """ - Adds a meta header to the request. - - :param request: A Protobuf Message.Builder object. - :param x_headers: Optional dictionary of custom headers. - :param session_token: Optional session token. - :raises ValueError: If the request or required fields are missing. - """ - if request is None: - return - - descriptor = request.DESCRIPTOR - if getattr(descriptor, META_HEADER_FIELD_NAME) is None: - raise ValueError(f"Required Protobuf field is missing: {META_HEADER_FIELD_NAME}") - - meta_header = getattr(request, META_HEADER_FIELD_NAME) - if meta_header.ByteSize() > 0: - return - - meta_header_builder = MetaHeaderMapper.to_grpc_message(MetaHeader()) - - if session_token and session_token.ByteSize() > 0: - meta_header_builder.session_token = session_token - - if x_headers: - grpc_x_headers = [ - types_pb2_session.XHeader(key=key, value=value) - for key, value in x_headers.items() - ] - meta_header_builder.x_headers.extend(grpc_x_headers) - - setattr(request, META_HEADER_FIELD_NAME, meta_header_builder.build()) diff --git a/frostfs_sdk/client/utils/session_cache.py b/frostfs_sdk/client/utils/session_cache.py deleted file mode 100644 index f77b671..0000000 --- a/frostfs_sdk/client/utils/session_cache.py +++ /dev/null @@ -1,39 +0,0 @@ - -from dataclasses import dataclass -from typing import Optional - - -@dataclass(frozen=True) -class SessionToken: - token: bytes - - -class SessionCache: - def __init__(self, session_expiration_duration): - self.cache = {} - self.token_duration = session_expiration_duration - self.current_epoch = 0 - - - def contains(self, key: str): - return key in self.cache - - def try_get_value(self, key: str) -> Optional[SessionToken]: - if not key: - return None - return self.cache.get(key) - - - def set_value(self, key: str, value: SessionToken): - if key is not None: - self.cache[key] = value - - def delete_by_prefix(self, prefix: str): - # Collect keys to avoid modifying dictionary during iteration - keys_to_delete = [key for key in self.cache if key.startswith(prefix)] - for key in keys_to_delete: - del self.cache[key] - - @staticmethod - def form_cache_key(address: str, key: str): - return address + key diff --git a/frostfs_sdk/cryptography/key_extension.py b/frostfs_sdk/cryptography/key_extension.py deleted file mode 100644 index cd8b07f..0000000 --- a/frostfs_sdk/cryptography/key_extension.py +++ /dev/null @@ -1,100 +0,0 @@ -import base58 -import ecdsa -import hashlib -from struct import pack, unpack -from Crypto.Hash import RIPEMD160 - - -COMPRESSED_PUBLIC_KEY_LENGTH = 33 -NEO_ADDRESS_VERSION = 0x35 -UNCOMPRESSED_PUBLIC_KEY_LENGTH = 65 -DECODE_ADDRESS_LENGTH = 21 -PS_IN_HASH160 = 0x0C -CHECK_SIG_DESCRIPTOR = int.from_bytes( - hashlib.sha256("System.Crypto.CheckSig".encode('ascii')).digest()[:4], byteorder='little' - ) - - -class KeyExtension: - def get_private_key_from_wif(self, wif: str) -> bytes: - """ - Converts a WIF private key to a byte array. - - :param wif: WIF private key in string format. - :return: Private key in byte format (32 bytes). - :raises ValueError: If the WIF key is incorrect. - """ - assert not self.is_empty(wif) - - decoded = base58.b58decode_check(wif) - if len(decoded) != 34 or decoded[0] != 0x80 or decoded[-1] != 0x01: - raise ValueError("Incorrect WIF private key") - - private_key = decoded[1:-1] - return private_key - - def get_public_key(self, private_key: bytes) -> bytes: - """ - Extract public key from Private key - - :param private_key: Private key in byte format (32 bytes). - :return: compressed public key in byte format (33 bytes). - :raises ValueError: If the private_key key is empty or null. - """ - assert not self.is_empty(private_key) - - if len(private_key) != 32: - raise ValueError(f"Incorrect len of private key, Expected: 32, Actual: {len(private_key)}") - - public_key = ecdsa.SigningKey.from_string(private_key, curve=ecdsa.NIST256p).get_verifying_key() - compressed_public_key = public_key.to_string("compressed") - return compressed_public_key - - def get_owner_id_by_public_key(self, public_key: bytes) -> str: - if len(public_key) != COMPRESSED_PUBLIC_KEY_LENGTH: - raise ValueError(f"Encoded compressed public key has wrong length. Expected {COMPRESSED_PUBLIC_KEY_LENGTH}, got {len(public_key)}") - - script_hash = self.get_script_hash(public_key) - data = bytearray(DECODE_ADDRESS_LENGTH) - data[0] = NEO_ADDRESS_VERSION - data[1:] = script_hash - return base58.b58encode_check(data).decode('utf-8') - - def get_script_hash(self, public_key: bytes): - script = self.create_signature_redeem_script(public_key) - sha256_hash = hashlib.sha256(script).digest() - return self.get_ripemd160(sha256_hash) - - @staticmethod - def create_signature_redeem_script(public_key: bytes): - if len(public_key) != COMPRESSED_PUBLIC_KEY_LENGTH: - raise ValueError(f"Encoded compressed public key has wrong length. Expected {COMPRESSED_PUBLIC_KEY_LENGTH}, got {len(public_key)}") - - script = bytearray([PS_IN_HASH160, COMPRESSED_PUBLIC_KEY_LENGTH]) - script.extend(public_key) - script.append(UNCOMPRESSED_PUBLIC_KEY_LENGTH) - script.extend(pack(" SignatureRFC6979: - return SignatureRFC6979( - key=key.public_key, - sign=Signer.sign_rfc6979(key.private_key, message.SerializeToString()) - ) - - @staticmethod - def sign_rfc6979(private_key: bytes, message: bytes) -> bytes: - if len(private_key) == 0 or private_key is None: - raise ValueError(f"Incorrect private_key: {private_key}") - - sk = ecdsa.SigningKey.from_string(private_key, curve=ecdsa.NIST256p, hashfunc=sha256) - - signature = sk.sign_deterministic(message) - - return signature - - @staticmethod - def sign_message(key: ECDSA, message: Message) -> SignatureRFC6979: - return SignatureRFC6979( - key=key.public_key, - sign=Signer.sign(key.private_key, message.SerializeToString()) - ) - - @staticmethod - def sign(private_key: bytes, message: bytes) -> bytes: - if len(private_key) == 0 or private_key is None: - raise ValueError(f"Incorrect private key: {private_key}") - - sk = ecdsa.SigningKey.from_string(private_key, curve=ecdsa.NIST256p, hashfunc=sha512) - signature = sk.sign(message) - - # the first byte indicates the node version marker - signature_with_marker = bytes([0x04]) + signature - - return signature_with_marker - - @staticmethod - def _sign_message_part(key: ECDSA, data: Message) -> Signature: - return Signature( - key=key.public_key, - sign=Signature.sign(key.private_key, data.SerializeToString()) - ) diff --git a/frostfs_sdk/models/__init__.py b/frostfs_sdk/models/__init__.py deleted file mode 100644 index aa4cd69..0000000 --- a/frostfs_sdk/models/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -from frostfs_sdk.models.dto.container import Container, ContainerId -from frostfs_sdk.models.dto.filter import Filter -from frostfs_sdk.models.dto.placement_policy import PlacementPolicy -from frostfs_sdk.models.dto.replica import Replica -from frostfs_sdk.models.dto.selector import Selector - -from frostfs_sdk.models.enums.basic_acl import BasicAcl -from frostfs_sdk.models.enums.filter_operation import FilterOperation -from frostfs_sdk.models.enums.selector_clause import SelectorClause diff --git a/frostfs_sdk/models/dto/container.py b/frostfs_sdk/models/dto/container.py deleted file mode 100644 index e369b35..0000000 --- a/frostfs_sdk/models/dto/container.py +++ /dev/null @@ -1,29 +0,0 @@ -from dataclasses import dataclass, field -from typing import Dict, Optional -import uuid - -from frostfs_sdk.models.dto.owner_id import OwnerId -from frostfs_sdk.models.dto.version import Version -from frostfs_sdk.models.enums.basic_acl import BasicAcl -from frostfs_sdk.models.dto.placement_policy import PlacementPolicy - - - -@dataclass -class Container: - # basicAcl: BasicAcl # TODO: will remove it? - placementPolicy: PlacementPolicy - nonce: uuid.UUID = field(default_factory=uuid.uuid4) - version: Optional[Version] = None - owner_id: Optional[OwnerId] = None - attributes: Dict[str, str] = field(default_factory=dict) - - def __init__(self, placementPolicy: PlacementPolicy): - self.nonce = uuid.uuid4() - self.placementPolicy = placementPolicy - self.attributes = {} - - -@dataclass -class ContainerId: - value: str diff --git a/frostfs_sdk/models/dto/filter.py b/frostfs_sdk/models/dto/filter.py deleted file mode 100644 index 0819648..0000000 --- a/frostfs_sdk/models/dto/filter.py +++ /dev/null @@ -1,14 +0,0 @@ -from dataclasses import dataclass - -from frostfs_sdk.models.enums.filter_operation import FilterOperation - - -@dataclass(frozen=True) -class Filter: - """ - Data Transfer Object for Filter configuration - """ - name: str - key: str - operation: FilterOperation - value: str diff --git a/frostfs_sdk/models/dto/meta_header.py b/frostfs_sdk/models/dto/meta_header.py deleted file mode 100644 index ea9572e..0000000 --- a/frostfs_sdk/models/dto/meta_header.py +++ /dev/null @@ -1,47 +0,0 @@ -from dataclasses import dataclass -from typing import Optional - -from frostfs_sdk.models.dto.version import Version, DEFAULT_MAJOR_VERSION, DEFAULT_MINOR_VERSION - - -class MetaHeader: - def __init__(self, version=None, epoch: int = 0, ttl: int = 2): - """ - Initializes a MetaHeader object. - - :param version: A Version object representing the version. - :param epoch: An integer representing the epoch (default: 0). - :param ttl: An integer representing the time-to-live (default: 2). - """ - self._version = None - self._epoch = None - self._ttl = None - - # Set default values if not provided - self.set_version(version or Version(DEFAULT_MAJOR_VERSION, DEFAULT_MINOR_VERSION)) - self.set_epoch(epoch) - self.set_ttl(ttl) - - def get_version(self): - return self._version - - def get_epoch(self): - return self._epoch - - def get_ttl(self): - return self._ttl - - def set_version(self, version): - if version is None: - raise ValueError(f"Input parameter is missing: {Version.__name__}") - self._version = version - - def set_epoch(self, epoch: int): - if epoch < 0: - raise ValueError("Epoch must be greater than or equal to zero") - self._epoch = epoch - - def set_ttl(self, ttl: int): - if ttl <= 0: - raise ValueError("TTL must be greater than zero") - self._ttl = ttl diff --git a/frostfs_sdk/models/dto/owner_id.py b/frostfs_sdk/models/dto/owner_id.py deleted file mode 100644 index cccb7f3..0000000 --- a/frostfs_sdk/models/dto/owner_id.py +++ /dev/null @@ -1,21 +0,0 @@ -from base58 import b58decode -from dataclasses import dataclass - - -@dataclass(frozen=True) -class OwnerId: - value: str - - def __post_init__(self): - if not self.value or self.value.strip() == "": - raise ValueError(f"{self.__class__.__name__} value is not present") - - def to_hash(self) -> bytes: - """Decodes the Base58-encoded value into a byte array.""" - try: - return b58decode(self.value) - except Exception as e: - raise ValueError(f"Failed to decode Base58 value: {self.value}") from e - - def __str__(self) -> str: - return self.value diff --git a/frostfs_sdk/models/dto/placement_policy.py b/frostfs_sdk/models/dto/placement_policy.py deleted file mode 100644 index f3c95f0..0000000 --- a/frostfs_sdk/models/dto/placement_policy.py +++ /dev/null @@ -1,14 +0,0 @@ -from dataclasses import dataclass -from typing import List - -from frostfs_sdk.models.dto.replica import Replica -from frostfs_sdk.models.dto.selector import Selector -from frostfs_sdk.models.dto.filter import Filter - -@dataclass -class PlacementPolicy: - replicas: List[Replica] - unique: bool - backup_factory: int - filters: List[Filter] = None - selectors: List[Selector] = None diff --git a/frostfs_sdk/models/dto/replica.py b/frostfs_sdk/models/dto/replica.py deleted file mode 100644 index f3946c0..0000000 --- a/frostfs_sdk/models/dto/replica.py +++ /dev/null @@ -1,12 +0,0 @@ -from dataclasses import dataclass, field - - -EMPTY_STRING = "" - -@dataclass -class Replica: - count: int - selector: str = field(default=EMPTY_STRING) - - def __post_init__(self): - self.selector = self.selector if self.selector else EMPTY_STRING diff --git a/frostfs_sdk/models/dto/selector.py b/frostfs_sdk/models/dto/selector.py deleted file mode 100644 index a03f885..0000000 --- a/frostfs_sdk/models/dto/selector.py +++ /dev/null @@ -1,15 +0,0 @@ -from dataclasses import dataclass - -from frostfs_sdk.models.enums.selector_clause import SelectorClause - - -@dataclass(frozen=True) -class Selector: - """ - Data Transfer Object for Selector configuration - """ - name: str - count: int - clause: SelectorClause - attribute: str - filter: str diff --git a/frostfs_sdk/models/dto/version.py b/frostfs_sdk/models/dto/version.py deleted file mode 100644 index b492109..0000000 --- a/frostfs_sdk/models/dto/version.py +++ /dev/null @@ -1,19 +0,0 @@ -from dataclasses import dataclass - -DEFAULT_MAJOR_VERSION = 2 -DEFAULT_MINOR_VERSION = 13 - - -@dataclass(frozen=True) -class Version: - major: int = DEFAULT_MAJOR_VERSION - minor: int = DEFAULT_MINOR_VERSION - - def __str__(self) -> str: - return f"v{self.major}.{self.minor}" - - def is_supported(self, other): - if not isinstance(other, Version): - return False - return self.major == other.major - diff --git a/frostfs_sdk/models/enums/basic_acl.py b/frostfs_sdk/models/enums/basic_acl.py deleted file mode 100644 index 0058a25..0000000 --- a/frostfs_sdk/models/enums/basic_acl.py +++ /dev/null @@ -1,7 +0,0 @@ -from enum import Enum - -class BasicAcl(Enum): - PRIVATE = 0x1C8C8CCC - PUBLIC_RO = 0x1FBF8CFF - PUBLIC_RW = 0x1FBFBFFF - PUBLIC_APPEND = 0x1FBF9FFF diff --git a/frostfs_sdk/models/enums/filter_operation.py b/frostfs_sdk/models/enums/filter_operation.py deleted file mode 100644 index e447962..0000000 --- a/frostfs_sdk/models/enums/filter_operation.py +++ /dev/null @@ -1,42 +0,0 @@ -class FilterOperation: - """ - Enum for filter operations with integer value mapping - """ - OPERATION_UNSPECIFIED = 0 - EQ = 1 - NE = 2 - GT = 3 - GE = 4 - LT = 5 - LE = 6 - OR = 7 - AND = 8 - NOT = 9 - LIKE = 10 - - _value_map = { - 0: OPERATION_UNSPECIFIED, - 1: EQ, - 2: NE, - 3: GT, - 4: GE, - 5: LT, - 6: LE, - 7: OR, - 8: AND, - 9: NOT, - 10: LIKE - } - - @classmethod - def get(cls, value: int) -> 'FilterOperation': - """ - Get enum instance by integer value - - Args: - value: Integer value of the operation - - Returns: - Corresponding FilterOperation instance - """ - return cls._value_map.get(value) diff --git a/frostfs_sdk/models/enums/selector_clause.py b/frostfs_sdk/models/enums/selector_clause.py deleted file mode 100644 index 880a0f5..0000000 --- a/frostfs_sdk/models/enums/selector_clause.py +++ /dev/null @@ -1,14 +0,0 @@ -from enum import Enum, unique - -@unique -class SelectorClause(Enum): - CLAUSE_UNSPECIFIED = 0 - SAME = 1 - DISTINCT = 2 - - @classmethod - def get(cls, value: int): - try: - return cls(value) - except ValueError: - raise KeyError(f"Unknown enum value: {value}") diff --git a/frostfs_sdk/models/mappers/container_mapper.py b/frostfs_sdk/models/mappers/container_mapper.py deleted file mode 100644 index c1913aa..0000000 --- a/frostfs_sdk/models/mappers/container_mapper.py +++ /dev/null @@ -1,77 +0,0 @@ -from typing import Optional - -import grpc - -from frostfs_sdk.client.models.client_environment import ClientEnvironment -from frostfs_sdk.client.services.context_accessor import ContextAccessor -from frostfs_sdk.models.mappers.placement_policy_mapper import PlacementPolicyMapper -from frostfs_sdk.models.mappers.owner_id_mapper import OwnerIdMapper -from frostfs_sdk.models.mappers.version_mapper import VersionMapper -from frostfs_sdk.models.mappers.uuid_extension import UuidExtension -from frostfs_sdk.models.dto.container import Container -from frostfs_sdk.protos.models.container import types_pb2 as types_pb2_container - - -class ContainerMapper: - @staticmethod - def to_grpc_message(container: Container, client_context: ClientEnvironment) -> Optional[types_pb2_container.Container]: - """ - Converts Container DTO to gRPC message - - Args: - container: Container DTO object - - Returns: - gRPC Container message builder - """ - if not container: - return None - - attributes = [ - types_pb2_container.Container.Attribute(key=k, value=v) - for k, v in container.attributes.items() - ] - - if container.owner_id: - owner_id = OwnerIdMapper.to_grpc_message(container.owner_id) - else: - owner_id = OwnerIdMapper.to_grpc_message(client_context.owner_id) - - if container.version: - version = VersionMapper.to_grpc_message(container.version) - else: - version = VersionMapper.to_grpc_message(client_context.version) - - grpc_container = types_pb2_container.Container( - nonce=container.nonce.bytes, - placement_policy=PlacementPolicyMapper.to_grpc_message(container.placementPolicy), - owner_id=owner_id, - version=version, - attributes=attributes - ) - - return grpc_container - - @staticmethod - def to_model(container_grpc: types_pb2_container.Container) -> Optional[Container]: - """ - Converts gRPC message to Container DTO - - Args: - container_grpc: gRPC Container message - - Returns: - Container DTO object - """ - if not container_grpc or container_grpc.ByteSize() == 0: - return None - - attributes = {attr.key: attr.value for attr in container_grpc.attributes} - - return Container( - nonce=UuidExtension.to_uuid(container_grpc.nonce), - placement_policy=PlacementPolicyMapper.to_model(container_grpc.placement_policy), - version=VersionMapper.to_model(container_grpc.version), - owner_id=OwnerIdMapper.to_model(container_grpc.owner_id), - attributes=attributes - ) diff --git a/frostfs_sdk/models/mappers/filter_mapper.py b/frostfs_sdk/models/mappers/filter_mapper.py deleted file mode 100644 index a3f2f11..0000000 --- a/frostfs_sdk/models/mappers/filter_mapper.py +++ /dev/null @@ -1,60 +0,0 @@ -from typing import List, Optional - -from frostfs_sdk.models.enums.filter_operation import FilterOperation -from frostfs_sdk.models.dto.filter import Filter -from frostfs_sdk.protos.models.netmap import types_pb2 as types_pb2_netmap - - -class FilterMapper: - @staticmethod - def to_grpc_messages(filters: List[Filter]) -> List[types_pb2_netmap.Filter]: - """ - Converts list of Filter DTOs to gRPC messages with nested conversion - """ - if not filters: - return [] - - return [FilterMapper.to_grpc_message(f) for f in filters] - - @staticmethod - def to_grpc_message(filter_dto: Filter) -> types_pb2_netmap.Filter: - """ - Converts Filter DTO to gRPC message with nested filters - """ - - operation = types_pb2_netmap.Filter.Operation.Value(filter_dto.operation.value) - return types_pb2_netmap.Filter( - name=filter_dto.name, - key=filter_dto.key, - op=operation, - value=filter_dto.value, - filters=FilterMapper.to_grpc_messages(filter_dto.filters) - ) - - @staticmethod - def to_models(filters_grpc: List[types_pb2_netmap.Filter]) -> Optional[List[Filter]]: - """ - Converts gRPC messages to Filter DTOs with nested conversion - """ - if not filters_grpc: - return None - - return [FilterMapper.to_model(f) for f in filters_grpc] - - @staticmethod - def to_model(filter_grpc: types_pb2_netmap.Filter) -> Optional[Filter]: - """ - Converts gRPC message to Filter DTO with nested filters - """ - if not filter_grpc or filter_grpc.ByteSize() == 0: - return None - - operation = FilterOperation(filter_grpc.op) - - return Filter( - name=filter_grpc.name, - key=filter_grpc.key, - operation=operation, - value=filter_grpc.value, - filters=FilterMapper.to_models(filter_grpc.filters) - ) diff --git a/frostfs_sdk/models/mappers/meta_header_mapper.py b/frostfs_sdk/models/mappers/meta_header_mapper.py deleted file mode 100644 index 340a19f..0000000 --- a/frostfs_sdk/models/mappers/meta_header_mapper.py +++ /dev/null @@ -1,26 +0,0 @@ -from frostfs_sdk.models.mappers.version_mapper import VersionMapper -from frostfs_sdk.models.dto.meta_header import MetaHeader -from frostfs_sdk.protos.models.session import types_pb2 as types_pb2_session - - -class MetaHeaderMapper: - """ - Maps a MetaHeader object to a Protobuf RequestMetaHeader object. - """ - @staticmethod - def to_grpc_message(meta_header: MetaHeader): - """ - Converts a MetaHeader object to a Protobuf RequestMetaHeader object. - - :param meta_header: A MetaHeader object. - :return: A Protobuf RequestMetaHeader object. - :raises ValueError: If the input meta_header is None. - """ - if meta_header is None: - raise ValueError(f"Input parameter is missing: {MetaHeader.__name__}") - - return types_pb2_session.RequestMetaHeader( - version=VersionMapper.to_grpc_message(meta_header.get_version()), - epoch=meta_header.get_epoch(), - ttl=meta_header.get_ttl() - ) diff --git a/frostfs_sdk/models/mappers/owner_id_mapper.py b/frostfs_sdk/models/mappers/owner_id_mapper.py deleted file mode 100644 index 8606673..0000000 --- a/frostfs_sdk/models/mappers/owner_id_mapper.py +++ /dev/null @@ -1,32 +0,0 @@ -from base58 import b58encode - -from frostfs_sdk.models.dto.owner_id import OwnerId -from frostfs_sdk.protos.models.refs import types_pb2 as types_pb2_refs - -class OwnerIdMapper: - @staticmethod - def to_grpc_message(owner_id: OwnerId) -> types_pb2_refs.OwnerID: - """ - Converts OwnerId DTO to gRPC message - """ - if not owner_id: - return None - - return types_pb2_refs.OwnerID( - value=owner_id.to_hash(), - ) - - @staticmethod - def to_model(owner_id_grpc: types_pb2_refs.OwnerID) -> OwnerId: - """ - Converts gRPC message to OwnerId DTO - """ - if not owner_id_grpc or owner_id_grpc.ByteSize() == 0: - return None - - try: - return OwnerId( - value=b58encode(owner_id_grpc.value).decode('utf-8') - ) - except Exception as e: - raise ValueError(f"Failed to encode Base58 value: {owner_id_grpc.value}") diff --git a/frostfs_sdk/models/mappers/placement_policy_mapper.py b/frostfs_sdk/models/mappers/placement_policy_mapper.py deleted file mode 100644 index 66a6d2d..0000000 --- a/frostfs_sdk/models/mappers/placement_policy_mapper.py +++ /dev/null @@ -1,53 +0,0 @@ -from typing import Optional - -from frostfs_sdk.models.mappers.filter_mapper import FilterMapper -from frostfs_sdk.models.mappers.selector_mapper import SelectorMapper -from frostfs_sdk.models.mappers.replica_mapper import ReplicaMapper -from frostfs_sdk.models.dto.placement_policy import PlacementPolicy -from frostfs_sdk.protos.models.netmap import types_pb2 as types_pb2_netmap - - -class PlacementPolicyMapper: - @staticmethod - def to_grpc_message(policy: PlacementPolicy) -> Optional[types_pb2_netmap.PlacementPolicy]: - """ - Converts PlacementPolicy DTO to gRPC message - - Args: - policy: PlacementPolicy DTO object - - Returns: - gRPC PlacementPolicy message - """ - if not policy: - return None - - return types_pb2_netmap.PlacementPolicy( - unique=policy.unique, - container_backup_factor=policy.backup_factory, - filters=FilterMapper.to_grpc_messages(policy.filters), - selectors=SelectorMapper.to_grpc_messages(policy.selectors), - replicas=ReplicaMapper.to_grpc_messages(policy.replicas) - ) - - @staticmethod - def to_model(policy_grpc: types_pb2_netmap.PlacementPolicy) -> Optional[PlacementPolicy]: - """ - Converts gRPC message to PlacementPolicy DTO - - Args: - policy_grpc: gRPC PlacementPolicy message - - Returns: - PlacementPolicy DTO object - """ - if not policy_grpc or policy_grpc.ByteSize() == 0: - return None - - return PlacementPolicy( - replicas=ReplicaMapper.to_models(policy_grpc.replicas), - unique=policy_grpc.unique, - backup_factory=policy_grpc.container_backup_factor, - filters=FilterMapper.to_models(policy_grpc.filters), - selectors=SelectorMapper.to_models(policy_grpc.selectors) - ) diff --git a/frostfs_sdk/models/mappers/replica_mapper.py b/frostfs_sdk/models/mappers/replica_mapper.py deleted file mode 100644 index 8823ca0..0000000 --- a/frostfs_sdk/models/mappers/replica_mapper.py +++ /dev/null @@ -1,56 +0,0 @@ -from typing import List, Optional - -from frostfs_sdk.models.dto.replica import Replica -from frostfs_sdk.protos.models.netmap import types_pb2 as types_pb2_netmap - - -class ReplicaMapper: - @staticmethod - def to_grpc_messages(replicas: List[Replica]) -> Optional[List[types_pb2_netmap.Replica]]: - if not replicas: - return None - return [ReplicaMapper.to_grpc_message(selector) for selector in replicas] - - @staticmethod - def to_grpc_message(replica: Replica) -> Optional[types_pb2_netmap.Replica]: - """ - Converts Replica DTO to gRPC message - - Args: - replice: Replica DTO object - - Returns: - gRPC Replica message - """ - if not replica: - return None - - return types_pb2_netmap.Replica( - count=replica.count, - selector=replica.selector - ) - - @staticmethod - def to_models(grpc_replicas: List[types_pb2_netmap.Replica]) -> Optional[List[Replica]]: - if not grpc_replicas: - return None - return [ReplicaMapper.to_model(grpc_replica) for grpc_replica in grpc_replicas] - - @staticmethod - def to_model(grpc_replica: types_pb2_netmap.Replica) -> Optional[Replica]: - """ - Converts gRPC message to Replica DTO - - Args: - grpc_replica: gRPC Replica message - - Returns: - Replica DTO object - """ - if not grpc_replica or grpc_replica.ByteSize() == 0: - return None - - return Replica( - count=grpc_replica.count, - selectors=grpc_replica.selector - ) diff --git a/frostfs_sdk/models/mappers/selector_mapper.py b/frostfs_sdk/models/mappers/selector_mapper.py deleted file mode 100644 index d536f3c..0000000 --- a/frostfs_sdk/models/mappers/selector_mapper.py +++ /dev/null @@ -1,73 +0,0 @@ -from typing import List, Optional - -from frostfs_sdk.models.dto.selector import Selector -from frostfs_sdk.models.enums.selector_clause import SelectorClause -from frostfs_sdk.protos.models.netmap import types_pb2 as types_pb2_netmap - - -class SelectorMapper: - @staticmethod - def to_grpc_messages(selectors: List[Selector]) -> Optional[List[types_pb2_netmap.Selector]]: - if not selectors: - return None - return [SelectorMapper.to_grpc_message(selector) for selector in selectors] - - @staticmethod - def to_grpc_message(selector: Selector) -> Optional[types_pb2_netmap.Selector]: - """ - Converts Selector DTO to gRPC message - - Args: - selector: Selector DTO object - - Returns: - gRPC Selector message - """ - if not selector: - return None - - clause_grpc = types_pb2_netmap.Clause(selector.clause.value) - if clause_grpc is None: - raise ValueError(f"Unknown enum value: {selector.clause.name} for {types_pb2_netmap.Clause.__name__}") - - - return types_pb2_netmap.Selector( - name=selector.name, - count=selector.count, - clause=clause_grpc, - attribute=selector.attribute, - filter=selector.filter - ) - - @staticmethod - def to_models(grpc_selectors: List[types_pb2_netmap.Selector]) -> Optional[List[Selector]]: - if not grpc_selectors: - return None - return [SelectorMapper.to_model(grpc_selector) for grpc_selector in grpc_selectors] - - @staticmethod - def to_model(selector_grpc: types_pb2_netmap.Selector) -> Optional[Selector]: - """ - Converts gRPC message to Selector DTO - - Args: - selector_grpc: gRPC Selector message - - Returns: - Selector DTO object - """ - if not selector_grpc or selector_grpc.ByteSize() == 0: - return None - - clause = SelectorClause.get(selector_grpc.clause) - if clause is None: - raise ValueError(f"Unknown enum value: {selector_grpc.clause} for {SelectorClause.__name__}") - - - return Selector( - name=selector_grpc.name, - count=selector_grpc.count, - clause=clause, - attribute=selector_grpc.attribute, - filter=selector_grpc.filter - ) diff --git a/frostfs_sdk/models/mappers/session_mapper.py b/frostfs_sdk/models/mappers/session_mapper.py deleted file mode 100644 index 4006771..0000000 --- a/frostfs_sdk/models/mappers/session_mapper.py +++ /dev/null @@ -1,44 +0,0 @@ -from google.protobuf.message import DecodeError -from frostfs_sdk.protos.models.session import types_pb2 as types_pb2_session - - -class SessionMapper: - @staticmethod - def serialize(token: types_pb2_session.SessionToken) -> bytes: - """ - Serializes a SessionToken object into a byte array. - - :param token: A SessionToken Protobuf object. - :return: A byte array representing the serialized SessionToken. - :raises ValueError: If the input token is None. - :raises Exception: If serialization fails. - """ - if token is None: - raise ValueError(f"Input parameter is missing: {types_pb2_session.SessionToken.__name__}") - - try: - # Serialize the token to bytes - return token.SerializeToString() - except Exception as e: - raise Exception(f"Serialization failed: {str(e)}") - - @staticmethod - def deserialize_session_token(bytes_data: bytes) -> types_pb2_session.SessionToken: - """ - Deserializes a byte array into a SessionToken object. - - :param bytes_data: A byte array representing the serialized SessionToken. - :return: A SessionToken Protobuf object. - :raises ValueError: If the input byte array is None or empty. - :raises Exception: If deserialization fails. - """ - if not bytes_data: - raise ValueError(f"Input parameter is missing: {types_pb2_session.SessionToken.__name__}") - - try: - # Deserialize the byte array into a SessionToken object - session_token = types_pb2_session.SessionToken() - session_token.ParseFromString(bytes_data) - return session_token - except DecodeError as e: - raise Exception(f"Deserialization failed: {str(e)}") diff --git a/frostfs_sdk/models/mappers/uuid_extension.py b/frostfs_sdk/models/mappers/uuid_extension.py deleted file mode 100644 index e1bdbdf..0000000 --- a/frostfs_sdk/models/mappers/uuid_extension.py +++ /dev/null @@ -1,31 +0,0 @@ -import uuid -from typing import Optional - -UUID_BYTE_ARRAY_LENGTH = 16 - - -class UuidExtension: - @staticmethod - def to_uuid(bytes_: Optional[bytes]) -> uuid.UUID: - """ - Converts a byte array into a UUID object. - """ - if bytes_ is None or len(bytes_) != UUID_BYTE_ARRAY_LENGTH: - raise ValueError(f"Wrong UUID size: expected {UUID_BYTE_ARRAY_LENGTH} bytes, got {len(bytes_)}") - - # Unpack the byte array into two 64-bit integers - most_sig_bits = int.from_bytes(bytes_[:8], byteorder='big', signed=False) - least_sig_bits = int.from_bytes(bytes_[8:], byteorder='big', signed=False) - - return uuid.UUID(int=(most_sig_bits << 64) | least_sig_bits) - - @staticmethod - def to_bytes(uuid_: Optional[uuid.UUID]) -> bytes: - """ - Converts a UUID object into a byte array. - """ - if uuid_ is None: - raise ValueError(f"Input parameter is missing: {uuid.UUID.__name__}") - - # Pack the UUID into a 16-byte array - return (uuid_.int >> 64).to_bytes(8, byteorder='big') + (uuid_.int & 0xFFFFFFFFFFFFFFFF).to_bytes(8, byteorder='big') diff --git a/frostfs_sdk/models/mappers/version_mapper.py b/frostfs_sdk/models/mappers/version_mapper.py deleted file mode 100644 index 6eedcda..0000000 --- a/frostfs_sdk/models/mappers/version_mapper.py +++ /dev/null @@ -1,31 +0,0 @@ -from typing import Optional -from frostfs_sdk.models.dto.version import Version -from frostfs_sdk.protos.models.refs import types_pb2 as types_pb2_refs - - -class VersionMapper: - @staticmethod - def to_grpc_message(version: Optional[Version]) -> Optional[types_pb2_refs.Version]: - """ - Converts a Version object to a gRPC Version message. - """ - if version is None: - return None - - return types_pb2_refs.Version( - major=version.major, - minor=version.minor - ) - - @staticmethod - def to_model(grpc_version: Optional[types_pb2_refs.Version]) -> Optional[Version]: - """ - Converts a gRPC Version message to a Version object. - """ - if grpc_version is None or grpc_version.ByteSize() == 0: - return None - - return Version( - major=grpc_version.major, - minor=grpc_version.minor - ) diff --git a/generate_proto.sh b/generate_proto.sh deleted file mode 100755 index 6695de4..0000000 --- a/generate_proto.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/bin/bash - -# TODO: add to MakeFile -# chmod +x generate_proto.sh -# ./generate_proto.sh - - -# Define directories -PROTOS_DIR="./frostfs_sdk/protos" -SOURCE_DIR="${PROTOS_DIR}/source" -MODELS_DIR="${PROTOS_DIR}/models" -REPO_FROSTFS_API_PROTOS="https://git.frostfs.info/TrueCloudLab/frostfs-api.git" - -rm -rf "$PROTOS_DIR" - -echo "1. Create folder ./protos" -mkdir -p "$PROTOS_DIR" - -echo "2. Cloning repository into ./protos/source..." -mkdir -p "$SOURCE_DIR" -git clone "$REPO_FROSTFS_API_PROTOS" "$SOURCE_DIR" - -echo "3. Generating Python code from .proto files to $MODELS_DIR" -mkdir -p "$MODELS_DIR" -find "$SOURCE_DIR" -name "*.proto" | while read -r proto_file; do - python -m grpc_tools.protoc -I "$SOURCE_DIR" --python_out="$MODELS_DIR" --pyi_out="$MODELS_DIR" --grpc_python_out="$MODELS_DIR" "$proto_file" -done - -echo "4. Making ./protos/models a Python package..." -touch "$MODELS_DIR/__init__.py" - -echo "All steps completed successfully." \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 726ddf6..2d8c473 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,4 @@ base58==2.1.1 ecdsa==0.19.0 -grpcio==1.70.0 -grpcio-tools==1.70.0 -pycryptodome==3.22.0 pytest==8.3.4 diff --git a/tests/client/test_create_container.py b/tests/client/test_create_container.py deleted file mode 100644 index 0876a38..0000000 --- a/tests/client/test_create_container.py +++ /dev/null @@ -1,15 +0,0 @@ -import pytest - -from frostfs_sdk import FrostfsClient, ContainerCreateParam, ContainerId -from frostfs_sdk.client.parameters.call_context_param import CallContextParam, TimeUnit -from tests.helpers.models import Helpers - - -@pytest.mark.container -class TestContainer: - def test_create_container(self, default_frostfs_client: FrostfsClient, helpers: Helpers): - call_context = CallContextParam(timeout=1, time_unit=TimeUnit.MINUTES) - req_body: ContainerCreateParam = helpers.container.create_params_container_create() - - cid: ContainerId = default_frostfs_client.container.create_container(req_body, call_context) - print(cid.value) diff --git a/tests/conftest.py b/tests/conftest.py index de465cd..3e26bbe 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,23 +1,8 @@ import pytest -from frostfs_sdk import ClientSettings, FrostfsClient -from tests.helpers.models import ClientCryptograpy, Helpers +from tests.helpers.models import ClientCryptograpy @pytest.fixture(scope="session") def client_cryptography(): return ClientCryptograpy() - - -@pytest.fixture(scope="session") -def default_frostfs_client(): - client_settings = ClientSettings( - wif="KxnEZ7FsPgKMdL9PYt9vsDkXiSw6qP9J8dpR4eVMsDpJyJxcYpve", - address="localhost:8080" - ) - return FrostfsClient(client_settings) - - -@pytest.fixture(scope="session") -def helpers(): - return Helpers() diff --git a/tests/helpers/models.py b/tests/helpers/models.py index 641710c..56071ad 100644 --- a/tests/helpers/models.py +++ b/tests/helpers/models.py @@ -1,7 +1,6 @@ -from frostfs_sdk.cryptography.key_extension import KeyExtension -from frostfs_sdk.cryptography.verifier import Verifier -from frostfs_sdk.cryptography.signer import Signer -from tests.helpers.params_container import ParamsContainerHelper +from frostfs_api.cryptography.key_extension import KeyExtension +from frostfs_api.cryptography.verifier import Verifier +from frostfs_api.cryptography.signer import Signer class ClientCryptograpy: @@ -9,8 +8,3 @@ class ClientCryptograpy: self.key_extension = KeyExtension() self.signer = Signer() self.verifier = Verifier() - - -class Helpers: - def __init__(self): - self.container = ParamsContainerHelper() diff --git a/tests/helpers/params_container.py b/tests/helpers/params_container.py deleted file mode 100644 index 93651d9..0000000 --- a/tests/helpers/params_container.py +++ /dev/null @@ -1,16 +0,0 @@ -from frostfs_sdk import ContainerCreateParam, WaitParam, Container, PlacementPolicy, Replica, BasicAcl - - -class ParamsContainerHelper: - def create_params_container_create(self): - req_container_create = ContainerCreateParam( - container=Container( - placementPolicy=PlacementPolicy( - replicas=[Replica(count=1)], - unique=True, - backup_factory=0 - ) - ), - wait_params=WaitParam() - ) - return req_container_create