Skip to content

Commit

Permalink
refactor: create new abstract model for nested dict objects (#421)
Browse files Browse the repository at this point in the history
* create NestedModel

* try with Memo

* replace Signer

* add SignerEntry

* infer nested_name from class name
  • Loading branch information
mvadari committed Sep 1, 2022
1 parent 2270270 commit 3511d1c
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 131 deletions.
70 changes: 70 additions & 0 deletions xrpl/models/nested_model.py
@@ -0,0 +1,70 @@
"""The base class for models that involve a nested dictionary e.g. memos."""

from __future__ import annotations

from typing import Any, Dict, Type, Union

from xrpl.models.base_model import BaseModel, _key_to_json


def _get_nested_name(cls: Union[NestedModel, Type[NestedModel]]) -> str:
if isinstance(cls, NestedModel):
name = cls.__class__.__name__
else:
name = cls.__name__
return _key_to_json(name)


class NestedModel(BaseModel):
"""The base class for models that involve a nested dictionary e.g. memos."""

@classmethod
def is_dict_of_model(cls: Type[NestedModel], dictionary: Any) -> bool:
"""
Returns True if the input dictionary was derived by the `to_dict`
method of an instance of this class. In other words, True if this is
a dictionary representation of an instance of this class.
NOTE: does not account for model inheritance, IE will only return True
if dictionary represents an instance of this class, but not if
dictionary represents an instance of a subclass of this class.
Args:
dictionary: The dictionary to check.
Returns:
True if dictionary is a dict representation of an instance of this
class.
"""
return (
isinstance(dictionary, dict)
and _get_nested_name(cls) in dictionary
and super().is_dict_of_model(dictionary[_get_nested_name(cls)])
)

@classmethod
def from_dict(cls: Type[NestedModel], value: Dict[str, Any]) -> NestedModel:
"""
Construct a new NestedModel from a dictionary of parameters.
Args:
value: The value to construct the NestedModel from.
Returns:
A new NestedModel object, constructed using the given parameters.
Raises:
XRPLModelException: If the dictionary provided is invalid.
"""
if _get_nested_name(cls) not in value:
return super(NestedModel, cls).from_dict(value)
return super(NestedModel, cls).from_dict(value[_get_nested_name(cls)])

def to_dict(self: NestedModel) -> Dict[str, Any]:
"""
Returns the dictionary representation of a NestedModel.
Returns:
The dictionary representation of a NestedModel.
"""
return {_get_nested_name(self): super().to_dict()}
54 changes: 3 additions & 51 deletions xrpl/models/transactions/signer_list_set.py
Expand Up @@ -2,9 +2,9 @@
from __future__ import annotations

from dataclasses import dataclass, field
from typing import Any, Dict, List, Optional, Type
from typing import Dict, List, Optional

from xrpl.models.base_model import BaseModel
from xrpl.models.nested_model import NestedModel
from xrpl.models.required import REQUIRED
from xrpl.models.transactions.transaction import Transaction
from xrpl.models.transactions.types import TransactionType
Expand All @@ -13,7 +13,7 @@

@require_kwargs_on_init
@dataclass(frozen=True)
class SignerEntry(BaseModel):
class SignerEntry(NestedModel):
"""Represents one entry in a list of multi-signers authorized to an account."""

account: str = REQUIRED # type: ignore
Expand All @@ -30,54 +30,6 @@ class SignerEntry(BaseModel):
:meta hide-value:
"""

@classmethod
def is_dict_of_model(cls: Type[SignerEntry], dictionary: Dict[str, Any]) -> bool:
"""
Returns True if the input dictionary was derived by the `to_dict`
method of an instance of this class. In other words, True if this is
a dictionary representation of an instance of this class.
NOTE: does not account for model inheritance, IE will only return True
if dictionary represents an instance of this class, but not if
dictionary represents an instance of a subclass of this class.
Args:
dictionary: The dictionary to check.
Returns:
True if dictionary is a dict representation of an instance of this
class.
"""
return (
isinstance(dictionary, dict)
and "signer_entry" in dictionary
and super().is_dict_of_model(dictionary["signer_entry"])
)

@classmethod
def from_dict(cls: Type[SignerEntry], value: Dict[str, Any]) -> SignerEntry:
"""
Construct a new SignerEntry from a dictionary of parameters.
Args:
value: The value to construct the SignerEntry from.
Returns:
A new SignerEntry object, constructed using the given parameters.
"""
if len(value) == 1 and "signer_entry" in value:
return super(SignerEntry, cls).from_dict(value["signer_entry"])
return super(SignerEntry, cls).from_dict(value)

def to_dict(self: SignerEntry) -> Dict[str, Any]:
"""
Returns the dictionary representation of a SignerEntry.
Returns:
The dictionary representation of a SignerEntry.
"""
return {"signer_entry": super().to_dict()}


@require_kwargs_on_init
@dataclass(frozen=True)
Expand Down
83 changes: 3 additions & 80 deletions xrpl/models/transactions/transaction.py
Expand Up @@ -12,6 +12,7 @@
from xrpl.models.base_model import BaseModel
from xrpl.models.exceptions import XRPLModelException
from xrpl.models.flags import check_false_flag_definition, interface_to_flag_list
from xrpl.models.nested_model import NestedModel
from xrpl.models.requests import PathStep
from xrpl.models.required import REQUIRED
from xrpl.models.transactions.types import PseudoTransactionType, TransactionType
Expand Down Expand Up @@ -79,7 +80,7 @@ def _value_to_tx_json(value: XRPL_VALUE_TYPE) -> XRPL_VALUE_TYPE:

@require_kwargs_on_init
@dataclass(frozen=True)
class Memo(BaseModel):
class Memo(NestedModel):
"""
An arbitrary piece of data attached to a transaction. A transaction can
have multiple Memo objects as an array in the Memos field.
Expand Down Expand Up @@ -121,37 +122,10 @@ def _get_errors(self: Memo) -> Dict[str, str]:
errors["Memo"] = "Memo must contain at least one field"
return errors

@classmethod
def from_dict(cls: Type[Memo], value: Dict[str, Any]) -> Memo:
"""
Construct a new Memo from a dictionary of parameters.
Args:
value: The value to construct the Memo from.
Returns:
A new Memo object, constructed using the given parameters.
Raises:
XRPLModelException: If the dictionary provided is invalid.
"""
if "memo" not in value:
return super(Memo, cls).from_dict(value)
return super(Memo, cls).from_dict(value["memo"])

def to_dict(self: Memo) -> Dict[str, Any]:
"""
Returns the dictionary representation of a Memo.
Returns:
The dictionary representation of a Memo.
"""
return {"memo": super().to_dict()}


@require_kwargs_on_init
@dataclass(frozen=True)
class Signer(BaseModel):
class Signer(NestedModel):
"""
One Signer in a multi-signature. A multi-signed transaction can have an
array of up to 8 Signers, each contributing a signature, in the Signers
Expand Down Expand Up @@ -183,57 +157,6 @@ class Signer(BaseModel):
:meta hide-value:
"""

@classmethod
def is_dict_of_model(cls: Type[Signer], dictionary: Any) -> bool:
"""
Returns True if the input dictionary was derived by the `to_dict`
method of an instance of this class. In other words, True if this is
a dictionary representation of an instance of this class.
NOTE: does not account for model inheritance, IE will only return True
if dictionary represents an instance of this class, but not if
dictionary represents an instance of a subclass of this class.
Args:
dictionary: The dictionary to check.
Returns:
True if dictionary is a dict representation of an instance of this
class.
"""
return (
isinstance(dictionary, dict)
and "signer" in dictionary
and super().is_dict_of_model(dictionary["signer"])
)

@classmethod
def from_dict(cls: Type[Signer], value: Dict[str, Any]) -> Signer:
"""
Construct a new Signer from a dictionary of parameters.
Args:
value: The value to construct the Signer from.
Returns:
A new Signer object, constructed using the given parameters.
Raises:
XRPLModelException: If the dictionary provided is invalid.
"""
if "signer" not in value:
return super(Signer, cls).from_dict(value)
return super(Signer, cls).from_dict(value["signer"])

def to_dict(self: Signer) -> Dict[str, Any]:
"""
Returns the dictionary representation of a Signer.
Returns:
The dictionary representation of a Signer.
"""
return {"signer": super().to_dict()}


T = TypeVar("T", bound="Transaction") # any type inherited from Transaction

Expand Down

0 comments on commit 3511d1c

Please sign in to comment.