Source code for stellar_sdk.sep.contract_meta

from __future__ import annotations

import os
from collections.abc import Iterator
from pathlib import Path

from .. import xdr as stellar_xdr
from ._wasm import CONTRACT_META_SECTION_NAME, get_wasm_custom_sections
from ._xdr_stream import parse_sc_meta_entries, serialize_sc_meta_entries
from .exceptions import InvalidWasmError

__all__ = ["ContractMeta"]


[docs] class ContractMeta: """The :class:`ContractMeta` object, which represents SEP-46 contract metadata. ``entries`` are normalized to a read-only tuple in entry order. :param entries: The raw SEP-46 metadata XDR entries. """ __slots__ = ("_entries",) def __init__(self, entries: tuple[stellar_xdr.SCMetaEntry, ...] = ()) -> None: self._entries = tuple(entries) @property def entries(self) -> tuple[stellar_xdr.SCMetaEntry, ...]: """Returns the raw SEP-46 metadata XDR entries.""" return self._entries
[docs] @classmethod def from_wasm(cls, wasm: bytes) -> ContractMeta: """Creates a :class:`ContractMeta` object from contract Wasm bytes. :param wasm: The contract Wasm bytes. :return: A :class:`ContractMeta` object. :raises InvalidWasmError: If the Wasm module or metadata section cannot be decoded. """ entries: list[stellar_xdr.SCMetaEntry] = [] for section in get_wasm_custom_sections(wasm, CONTRACT_META_SECTION_NAME): entries.extend(parse_sc_meta_entries(section)) return cls(tuple(entries))
[docs] @classmethod def from_wasm_file(cls, path: str | os.PathLike[str]) -> ContractMeta: """Creates a :class:`ContractMeta` object from a contract Wasm file. :param path: The path to the contract Wasm file. :return: A :class:`ContractMeta` object. :raises InvalidWasmError: If the Wasm module or metadata section cannot be decoded. """ return cls.from_wasm(Path(path).read_bytes())
[docs] @classmethod def from_xdr_bytes(cls, data: bytes) -> ContractMeta: """Creates a :class:`ContractMeta` object from SEP-46 XDR stream bytes. :param data: The XDR stream bytes. :return: A :class:`ContractMeta` object. :raises InvalidWasmError: If the XDR stream cannot be decoded. """ return cls(parse_sc_meta_entries(data))
[docs] def to_xdr_bytes(self) -> bytes: """Serializes the metadata entries as SEP-46 XDR stream bytes. :return: The XDR stream bytes. """ return serialize_sc_meta_entries(self.entries)
[docs] def items(self) -> tuple[tuple[str, str], ...]: """Returns ``SC_META_V0`` key/value pairs in entry order. :return: The decoded key/value pairs. :raises InvalidWasmError: If a key or value is not UTF-8. """ items: list[tuple[str, str]] = [] for entry in self.entries: if entry.kind != stellar_xdr.SCMetaKind.SC_META_V0 or entry.v0 is None: continue items.append( (_decode_meta_string(entry.v0.key), _decode_meta_string(entry.v0.val)) ) return tuple(items)
[docs] def get(self, key: str, default: str | None = None) -> str | None: """Returns the first ``SC_META_V0`` value for ``key``. :param key: The metadata key. :param default: The default value returned when the key is not present. :return: The metadata value or ``default``. :raises InvalidWasmError: If a key or value is not UTF-8. """ for item_key, item_value in self.items(): if item_key == key: return item_value return default
[docs] def get_all(self, key: str) -> tuple[str, ...]: """Returns all ``SC_META_V0`` values for ``key`` in entry order. :param key: The metadata key. :return: All metadata values for the key. :raises InvalidWasmError: If a key or value is not UTF-8. """ return tuple( item_value for item_key, item_value in self.items() if item_key == key )
[docs] def supported_seps(self, strict: bool = False) -> tuple[int, ...]: """Returns SEP-47 SEP identifiers from ``sep`` meta entries. Values are returned in first-seen order with duplicates removed. Invalid identifiers are skipped by default and raise ``ValueError`` when ``strict`` is true. :param strict: Whether to reject invalid SEP identifiers. :return: SEP identifiers declared by the contract. :raises ValueError: If ``strict`` is true and an identifier is invalid. :raises InvalidWasmError: If a key or value is not UTF-8. """ seen: set[int] = set() supported: list[int] = [] for value in self.get_all("sep"): for part in value.split(","): sep = part.strip() if not sep: if strict: raise ValueError("Invalid SEP identifier: empty value.") continue if not sep.isdecimal(): if strict: raise ValueError(f"Invalid SEP identifier: {sep!r}.") continue sep_number = int(sep) if sep_number not in seen: seen.add(sep_number) supported.append(sep_number) return tuple(supported)
[docs] def implements_sep(self, sep: int) -> bool: """Returns whether the contract declares support for ``sep`` via SEP-47. :param sep: The SEP number. :return: ``True`` if the contract declares support for the SEP. """ if not isinstance(sep, int): raise TypeError("sep must be int") return sep in self.supported_seps()
def __iter__(self) -> Iterator[stellar_xdr.SCMetaEntry]: return iter(self.entries) def __len__(self) -> int: return len(self.entries) def __contains__(self, key: object) -> bool: """Returns whether an ``SC_META_V0`` entry exists for ``key``.""" if not isinstance(key, str): return False return any(item_key == key for item_key, _ in self.items()) def __eq__(self, other: object) -> bool: if not isinstance(other, ContractMeta): return NotImplemented return self.entries == other.entries def __repr__(self) -> str: return f"<ContractMeta [entries={self.entries}]>"
def _decode_meta_string(value: bytes) -> str: try: return value.decode("utf-8") except UnicodeDecodeError as exc: raise InvalidWasmError("Contract meta contains a non-UTF-8 string.") from exc