Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: create new abstract model for nested dict objects #421

Merged
merged 5 commits into from
Sep 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
70 changes: 70 additions & 0 deletions xrpl/models/nested_model.py
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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