[#3] Added generate proto script create container method
Signed-off-by: Ilyas Niyazov <i.niyazov@yadro.com>
This commit is contained in:
parent
fba6eaaa9c
commit
d2e0c80f7c
16 changed files with 348 additions and 51 deletions
|
@ -1,7 +1,7 @@
|
||||||
# Create channel and Stubs
|
# Create channel and Stubs
|
||||||
import grpc
|
import grpc
|
||||||
|
|
||||||
from frostfs_sdk.client.services.session import SessionCache
|
from frostfs_sdk.client.utils.session_cache import SessionCache
|
||||||
from frostfs_sdk.client.models.client_environment import ClientEnvironment
|
from frostfs_sdk.client.models.client_environment import ClientEnvironment
|
||||||
from frostfs_sdk.client.models.client_settings import ClientSettings
|
from frostfs_sdk.client.models.client_settings import ClientSettings
|
||||||
from frostfs_sdk.client.models.ecdsa_model import ECDSA
|
from frostfs_sdk.client.models.ecdsa_model import ECDSA
|
||||||
|
|
|
@ -3,7 +3,7 @@ from frostfs_sdk.cryptography.key_extension import KeyExtension
|
||||||
from frostfs_sdk.client.models.ecdsa_model import ECDSA
|
from frostfs_sdk.client.models.ecdsa_model import ECDSA
|
||||||
from frostfs_sdk.models.dto.version import Version
|
from frostfs_sdk.models.dto.version import Version
|
||||||
from frostfs_sdk.models.dto.owner_id import OwnerId
|
from frostfs_sdk.models.dto.owner_id import OwnerId
|
||||||
from frostfs_sdk.client.services.session import SessionCache
|
from frostfs_sdk.client.utils.session_cache import SessionCache
|
||||||
|
|
||||||
|
|
||||||
class ClientEnvironment:
|
class ClientEnvironment:
|
||||||
|
@ -11,10 +11,10 @@ class ClientEnvironment:
|
||||||
self.ecdsa = ecdsa
|
self.ecdsa = ecdsa
|
||||||
self.channel = channel
|
self.channel = channel
|
||||||
self.version = version
|
self.version = version
|
||||||
# self.owner_id = OwnerId(KeyExtension.get_owner_id_by_wif(ecdsa.wif))
|
self.owner_id = OwnerId(KeyExtension().get_owner_id_by_public_key(ecdsa.public_key))
|
||||||
self.owner_id = "11"
|
|
||||||
self.session_cache = session_cache
|
self.session_cache = session_cache
|
||||||
self.address = address
|
self.address = address
|
||||||
|
self._session_key = None
|
||||||
|
|
||||||
def get_session_key(self):
|
def get_session_key(self):
|
||||||
if not self._session_key:
|
if not self._session_key:
|
||||||
|
|
|
@ -2,7 +2,7 @@ from dataclasses import dataclass, field
|
||||||
from typing import Optional, Dict
|
from typing import Optional, Dict
|
||||||
|
|
||||||
from frostfs_sdk.models.dto.container import Container
|
from frostfs_sdk.models.dto.container import Container
|
||||||
from frostfs_sdk.client.services.session import SessionToken
|
from frostfs_sdk.client.utils.session_cache import SessionToken
|
||||||
from frostfs_sdk.client.parameters.wait_param import WaitParam
|
from frostfs_sdk.client.parameters.wait_param import WaitParam
|
||||||
|
|
||||||
|
|
||||||
|
|
14
frostfs_sdk/client/parameters/create_session_param.py
Normal file
14
frostfs_sdk/client/parameters/create_session_param.py
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
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 {}
|
|
@ -1,13 +1,18 @@
|
||||||
# implementation Conainer methods
|
# implementation Conainer methods
|
||||||
from frostfs_sdk.client.models.client_environment import ClientEnvironment
|
from frostfs_sdk.client.models.client_environment import ClientEnvironment
|
||||||
from frostfs_sdk.client.services.context_accessor import ContextAccessor
|
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.container_param import ContainerCreateParam
|
||||||
from frostfs_sdk.client.parameters.call_context_param import CallContextParam
|
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.cryptography.signer import Signer
|
||||||
from frostfs_sdk.models.dto.container import ContainerId, Container
|
from frostfs_sdk.models.dto.container import ContainerId, Container
|
||||||
from frostfs_sdk.models.mappers.container_mapper import ContainerMapper
|
from frostfs_sdk.models.mappers.container_mapper import ContainerMapper
|
||||||
from frostfs_sdk.models.mappers.owner_id_mapper import OwnerIdMapper
|
from frostfs_sdk.models.mappers.owner_id_mapper import OwnerIdMapper
|
||||||
from frostfs_sdk.models.mappers.version_mapper import VersionMapper
|
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 as service_pb2_container
|
||||||
from frostfs_sdk.protos.models.container import service_pb2_grpc as service_pb2_grpc_container
|
from frostfs_sdk.protos.models.container import service_pb2_grpc as service_pb2_grpc_container
|
||||||
|
|
||||||
|
@ -27,11 +32,8 @@ class ContainerClient(ContextAccessor):
|
||||||
"""
|
"""
|
||||||
Creates a PUT request for creating a container.
|
Creates a PUT request for creating a container.
|
||||||
"""
|
"""
|
||||||
grpc_container=ContainerMapper().to_grpc_message(param.container)
|
|
||||||
if not grpc_container.owner_id:
|
grpc_container=ContainerMapper().to_grpc_message(param.container, self.get_context)
|
||||||
grpc_container.owner_id = OwnerIdMapper.to_grpc_message(self.get_context.owner_id)
|
|
||||||
if not grpc_container.version:
|
|
||||||
grpc_container.version = VersionMapper.to_grpc_message(self.get_context.version)
|
|
||||||
|
|
||||||
body = service_pb2_container.PutRequest.Body(
|
body = service_pb2_container.PutRequest.Body(
|
||||||
container=grpc_container,
|
container=grpc_container,
|
||||||
|
@ -39,5 +41,24 @@ class ContainerClient(ContextAccessor):
|
||||||
)
|
)
|
||||||
|
|
||||||
request = service_pb2_container.PutRequest(body=body)
|
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)
|
signed_request = Signer.sign(self.ecdsa.private_key, request)
|
||||||
return signed_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")
|
||||||
|
|
|
@ -1,37 +1,38 @@
|
||||||
|
from typing import Optional
|
||||||
from dataclasses import dataclass
|
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
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
from frostfs_sdk.protos.models.session import service_pb2_grpc as service_pb2_grpc_session
|
||||||
class SessionToken:
|
from frostfs_sdk.protos.models.session import service_pb2 as service_pb2_session
|
||||||
token: bytes
|
from frostfs_sdk.protos.models.session import types_pb2 as types_pb2_session
|
||||||
|
|
||||||
|
|
||||||
class SessionCache:
|
class SessionClient(ContextAccessor):
|
||||||
def __init__(self, session_expiration_duration):
|
def __init__(self, client_environment: ClientEnvironment):
|
||||||
self.cache = {}
|
super().__init__(client_environment)
|
||||||
self.token_duration = session_expiration_duration
|
self.session_stub = service_pb2_grpc_session.SessionServiceStub(client_environment.channel)
|
||||||
self.current_epoch = 0
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
def contains(self, key):
|
session_token_grpc = types_pb2_session.SessionToken(response.body)
|
||||||
return key in self.cache
|
token = SessionMapper.serialize(session_token_grpc)
|
||||||
|
return SessionToken(token=token)
|
||||||
def try_get_value(self, key):
|
|
||||||
if not key:
|
|
||||||
return None
|
|
||||||
return self.cache.get(key)
|
|
||||||
|
|
||||||
|
|
||||||
def set_value(self, key, value):
|
|
||||||
if key is not None:
|
|
||||||
self.cache[key] = value
|
|
||||||
|
|
||||||
def delete_by_prefix(self, prefix):
|
|
||||||
# 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
|
|
||||||
|
|
|
@ -20,11 +20,11 @@ class MessageHelper:
|
||||||
if not field_descriptor:
|
if not field_descriptor:
|
||||||
raise ValueError(f"Field '{field_name}' not found in message descriptor")
|
raise ValueError(f"Field '{field_name}' not found in message descriptor")
|
||||||
|
|
||||||
return message.GetField(field_descriptor[field_name])
|
return getattr(field_descriptor, field_name)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def set_field(message: Message, field_name: str, value: Any) -> None:
|
def set_field(message: Message, field_name: str, value: Any) -> None:
|
||||||
if message is None or not field_name.strip() or value is None:
|
if message is None or not field_name.strip() or value is None:
|
||||||
raise ValueError("Some parameter is missing")
|
raise ValueError("Some parameter is missing")
|
||||||
|
|
||||||
message.SetField(message.DESCRIPTOR.fields[field_name], value)
|
setattr(message, message.DESCRIPTOR.fields[field_name], value)
|
||||||
|
|
47
frostfs_sdk/client/utils/request_constructor.py
Normal file
47
frostfs_sdk/client/utils/request_constructor.py
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
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())
|
39
frostfs_sdk/client/utils/session_cache.py
Normal file
39
frostfs_sdk/client/utils/session_cache.py
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
|
||||||
|
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
|
|
@ -1,5 +1,18 @@
|
||||||
import base58
|
import base58
|
||||||
import ecdsa
|
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:
|
class KeyExtension:
|
||||||
|
@ -37,12 +50,47 @@ class KeyExtension:
|
||||||
compressed_public_key = public_key.to_string("compressed")
|
compressed_public_key = public_key.to_string("compressed")
|
||||||
return compressed_public_key
|
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("<I", CHECK_SIG_DESCRIPTOR))
|
||||||
|
return bytes(script)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_ripemd160(value):
|
||||||
|
if value is None:
|
||||||
|
raise ValueError("Input parameter is missing")
|
||||||
|
|
||||||
|
digest = RIPEMD160.new()
|
||||||
|
digest.update(value)
|
||||||
|
return digest.digest()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def is_empty(sequence_symbols: bytes | str):
|
def is_empty(sequence_symbols: bytes | str):
|
||||||
if len(sequence_symbols) == 0 or sequence_symbols is None:
|
if len(sequence_symbols) == 0 or sequence_symbols is None:
|
||||||
raise ValueError(f"Empty sequence symbols of key: {sequence_symbols}")
|
raise ValueError(f"Empty sequence symbols of key: {sequence_symbols}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_hex_string(value):
|
def get_hex_string(value):
|
||||||
if value is None or len(value) == 0:
|
if value is None or len(value) == 0:
|
||||||
|
|
47
frostfs_sdk/models/dto/meta_header.py
Normal file
47
frostfs_sdk/models/dto/meta_header.py
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
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
|
|
@ -1,6 +1,7 @@
|
||||||
from base58 import b58decode
|
from base58 import b58decode
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class OwnerId:
|
class OwnerId:
|
||||||
value: str
|
value: str
|
||||||
|
|
|
@ -2,6 +2,8 @@ from typing import Optional
|
||||||
|
|
||||||
import grpc
|
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.placement_policy_mapper import PlacementPolicyMapper
|
||||||
from frostfs_sdk.models.mappers.owner_id_mapper import OwnerIdMapper
|
from frostfs_sdk.models.mappers.owner_id_mapper import OwnerIdMapper
|
||||||
from frostfs_sdk.models.mappers.version_mapper import VersionMapper
|
from frostfs_sdk.models.mappers.version_mapper import VersionMapper
|
||||||
|
@ -12,7 +14,7 @@ from frostfs_sdk.protos.models.container import types_pb2 as types_pb2_container
|
||||||
|
|
||||||
class ContainerMapper:
|
class ContainerMapper:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def to_grpc_message(container: Container) -> Optional[types_pb2_container.Container]:
|
def to_grpc_message(container: Container, client_context: ClientEnvironment) -> Optional[types_pb2_container.Container]:
|
||||||
"""
|
"""
|
||||||
Converts Container DTO to gRPC message
|
Converts Container DTO to gRPC message
|
||||||
|
|
||||||
|
@ -30,18 +32,24 @@ class ContainerMapper:
|
||||||
for k, v in container.attributes.items()
|
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(
|
grpc_container = types_pb2_container.Container(
|
||||||
nonce=container.nonce.bytes,
|
nonce=container.nonce.bytes,
|
||||||
placement_policy=PlacementPolicyMapper.to_grpc_message(container.placementPolicy),
|
placement_policy=PlacementPolicyMapper.to_grpc_message(container.placementPolicy),
|
||||||
|
owner_id=owner_id,
|
||||||
|
version=version,
|
||||||
attributes=attributes
|
attributes=attributes
|
||||||
)
|
)
|
||||||
|
|
||||||
if container.owner_id:
|
|
||||||
grpc_container.owner_id = OwnerIdMapper.to_grpc_message(container.owner_id)
|
|
||||||
|
|
||||||
if container.version:
|
|
||||||
grpc_container.version = VersionMapper.to_grpc_message(container.version)
|
|
||||||
|
|
||||||
return grpc_container
|
return grpc_container
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|
26
frostfs_sdk/models/mappers/meta_header_mapper.py
Normal file
26
frostfs_sdk/models/mappers/meta_header_mapper.py
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
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()
|
||||||
|
)
|
44
frostfs_sdk/models/mappers/session_mapper.py
Normal file
44
frostfs_sdk/models/mappers/session_mapper.py
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
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)}")
|
|
@ -2,5 +2,6 @@ base58==2.1.1
|
||||||
ecdsa==0.19.0
|
ecdsa==0.19.0
|
||||||
grpcio==1.70.0
|
grpcio==1.70.0
|
||||||
grpcio-tools==1.70.0
|
grpcio-tools==1.70.0
|
||||||
|
pycryptodome==3.22.0
|
||||||
|
|
||||||
pytest==8.3.4
|
pytest==8.3.4
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue