From 5c31d03922a38b1a8ce139e42075e5e99df4769a Mon Sep 17 00:00:00 2001 From: Amy He Date: Thu, 19 Dec 2024 00:29:41 -0800 Subject: [PATCH] delegate subclass-specific _decode_object --- meeko/cli/mk_prepare_receptor.py | 2 + meeko/molsetup.py | 85 ++++++++++---------------------- meeko/polymer.py | 40 ++------------- meeko/utils/jsonutils.py | 17 +++++-- 4 files changed, 46 insertions(+), 98 deletions(-) diff --git a/meeko/cli/mk_prepare_receptor.py b/meeko/cli/mk_prepare_receptor.py index 5fc5fbd0..9a121227 100755 --- a/meeko/cli/mk_prepare_receptor.py +++ b/meeko/cli/mk_prepare_receptor.py @@ -13,6 +13,7 @@ from meeko import PDBQTMolecule from meeko import RDKitMolCreate from meeko import MoleculePreparation +from meeko import MoleculeSetup from meeko import ResidueChemTemplates from meeko import PDBQTWriterLegacy from meeko import Polymer @@ -20,6 +21,7 @@ from meeko import reactive_typer from meeko import get_reactive_config from meeko import gridbox +from meeko import __file__ as pkg_init_path from rdkit import Chem try: diff --git a/meeko/molsetup.py b/meeko/molsetup.py index 7a89498c..03b18f4d 100644 --- a/meeko/molsetup.py +++ b/meeko/molsetup.py @@ -204,7 +204,25 @@ class Atom(BaseJSONParsable): is_dummy: bool = False is_pseudo_atom: bool = False + + # region JSON-interchange functions + @classmethod + def json_encoder(cls, obj: "Atom") -> Optional[dict[str, Any]]: + return { + "index": obj.index, + "pdbinfo": obj.pdbinfo, + "charge": obj.charge, + "coord": obj.coord.tolist(), # converts coord from numpy array to lists + "atomic_num": obj.atomic_num, + "atom_type": obj.atom_type, + "is_ignore": obj.is_ignore, + "graph": obj.graph, + "interaction_vectors": [v.tolist() for v in obj.interaction_vectors], + "is_dummy": obj.is_dummy, + "is_pseudo_atom": obj.is_pseudo_atom, + } + # Keys to check for deserialized JSON expected_json_keys = { "index", @@ -221,13 +239,7 @@ class Atom(BaseJSONParsable): } @classmethod - def json_decoder(cls, obj: dict[str, Any]): - - # avoid using json_decoder as object_hook for nested objects - if not isinstance(obj, dict): - return obj - if set(obj.keys()) != cls.expected_json_keys: - return obj + def _decode_object(cls, obj: dict[str, Any]): # Constructs an atom object from the provided keys. index = obj["index"] @@ -255,23 +267,7 @@ def json_decoder(cls, obj: dict[str, Any]): is_pseudo_atom, ) return output_atom - - @classmethod - def json_encoder(cls, obj: "Atom") -> Optional[dict[str, Any]]: - - return { - "index": obj.index, - "pdbinfo": obj.pdbinfo, - "charge": obj.charge, - "coord": obj.coord.tolist(), # converts coord from numpy array to lists - "atomic_num": obj.atomic_num, - "atom_type": obj.atom_type, - "is_ignore": obj.is_ignore, - "graph": obj.graph, - "interaction_vectors": [v.tolist() for v in obj.interaction_vectors], - "is_dummy": obj.is_dummy, - "is_pseudo_atom": obj.is_pseudo_atom, - } + # endregion @dataclass @@ -318,13 +314,7 @@ def get_bond_id(idx1: int, idx2: int): return idx_min, idx_max @classmethod - def json_decoder(cls, obj: dict[str, Any]): - - # avoid using json_decoder as object_hook for nested objects - if not isinstance(obj, dict): - return obj - if set(obj.keys()) != cls.expected_json_keys: - return obj + def _decode_object(cls, obj: dict[str, Any]): # Constructs a bond object from the provided keys. index1 = obj["index1"] @@ -351,13 +341,7 @@ class Ring(BaseJSONParsable): expected_json_keys = {"ring_id"} @classmethod - def json_decoder(cls, obj: dict[str, Any]): - - # avoid using json_decoder as object_hook for nested objects - if not isinstance(obj, dict): - return obj - if set(obj.keys()) != cls.expected_json_keys: - return obj + def _decode_object(cls, obj: dict[str, Any]): # Constructs a Ring object from the provided keys. ring_id = string_to_tuple(obj["ring_id"], int) @@ -407,13 +391,7 @@ def copy(self): return new_restraint @classmethod - def json_decoder(cls, obj: dict[str, Any]): - - # avoid using json_decoder as object_hook for nested objects - if not isinstance(obj, dict): - return obj - if set(obj.keys()) != cls.expected_json_keys: - return obj + def _decode_object(cls, obj: dict[str, Any]): # Constructs a Restraint object from the provided keys. atom_index = obj["atom_index"] @@ -501,14 +479,7 @@ def __init__(self, name: str = None, is_sidechain: bool = False): self.flexibility_model = None # from flexibility_model - from flexibility.py @classmethod - def json_decoder(cls, obj: dict[str, Any]): - - # avoid using json_decoder as object_hook for nested objects - if not isinstance(obj, dict): - return obj - # allows more keys in RDKitMoleculeSetup - if not cls.expected_json_keys.issubset(obj.keys()): - return obj + def _decode_object(cls, obj: dict[str, Any]): # Constructs a MoleculeSetup object and restores the expected attributes name = obj["name"] @@ -1573,13 +1544,7 @@ def __init__(self, name: str = None, is_sidechain: bool = False, @classmethod - def json_decoder(cls, obj: dict[str, Any]): - - # avoid using json_decoder as object_hook for nested objects - if not isinstance(obj, dict): - return obj - if set(obj.keys()) != cls.expected_json_keys: - return obj + def _decode_object(cls, obj: dict[str, Any]): try: diff --git a/meeko/polymer.py b/meeko/polymer.py index 5f336065..3f8859ae 100644 --- a/meeko/polymer.py +++ b/meeko/polymer.py @@ -594,13 +594,7 @@ def __init__(self, residue_templates, padders, ambiguous): self.ambiguous = ambiguous @classmethod - def json_decoder(cls, obj: dict[str, Any]): - - # avoid using json_decoder as object_hook for nested objects - if not isinstance(obj, dict): - return obj - if set(obj.keys()) != cls.expected_json_keys: - return obj + def _decode_object(cls, obj: dict[str, Any]): # Extracting the constructor args from the json representation and creating a ResidueChemTemplates instance templates = { @@ -973,13 +967,7 @@ def __init__( @classmethod - def json_decoder(cls, obj: dict[str, Any]): - - # avoid using json_decoder as object_hook for nested objects - if not isinstance(obj, dict): - return obj - if set(obj.keys()) != cls.expected_json_keys: - return obj + def _decode_object(cls, obj: dict[str, Any]): # Deserializes ResidueChemTemplates from the dict to use as an input, then constructs a Polymer object # and sets its values using deserialized JSON values. @@ -2135,13 +2123,7 @@ def _invert_mapping(mapping): return inverted @classmethod - def json_decoder(cls, obj: dict[str, Any]): - - # avoid using json_decoder as object_hook for nested objects - if not isinstance(obj, dict): - return obj - if set(obj.keys()) != cls.expected_json_keys: - return obj + def _decode_object(cls, obj: dict[str, Any]): try: raw_rdkit_mol = rdkit_mol_from_json(obj["raw_rdkit_mol"]) @@ -2555,13 +2537,7 @@ def _check_target_mol(self, target_mol: Chem.Mol): return False @classmethod - def json_decoder(cls, obj: dict[str, Any]): - - # avoid using json_decoder as object_hook for nested objects - if not isinstance(obj, dict): - return obj - if set(obj.keys()) != cls.expected_json_keys: - return obj + def _decode_object(cls, obj: dict[str, Any]): return ResiduePadder(obj["rxn_smarts"], obj["adjacent_smarts"], obj["auto_blunt"]) @@ -2678,13 +2654,7 @@ def json_encoder(cls, obj: "ResidueTemplate") -> Optional[dict[str, Any]]: return output_dict @classmethod - def json_decoder(cls, obj: dict[str, Any]): - - # avoid using json_decoder as object_hook for nested objects - if not isinstance(obj, dict): - return obj - if set(obj.keys()) != cls.expected_json_keys: - return obj + def _decode_object(cls, obj: dict[str, Any]): # Converting ResidueTemplate init values that need conversion deserialized_mol = rdkit_mol_from_json(obj["mol"]) diff --git a/meeko/utils/jsonutils.py b/meeko/utils/jsonutils.py index e61dc978..9a6fb606 100644 --- a/meeko/utils/jsonutils.py +++ b/meeko/utils/jsonutils.py @@ -102,12 +102,23 @@ class BaseJSONParsable: @classmethod def json_encoder(cls, obj): raise NotImplementedError("Subclasses must implement json_encoder.") - + @classmethod - def json_decoder(cls, obj): - raise NotImplementedError("Subclasses must implement json_decoder.") + def _decode_object(cls, obj): + raise NotImplementedError("Subclasses must implement _decode_object.") # Inheritable JSON Interchange Functions + @classmethod + def json_decoder(cls, obj: dict[str, Any]): + # Avoid using json_decoder as object_hook for nested objects + if not isinstance(obj, dict): + return obj + if not cls.expected_json_keys.issubset(obj.keys()): + return obj + + # Delegate specific decoding logic to a subclass-defined method + return cls._decode_object(obj) + @classmethod def from_json(cls, json_string: str): try: