Skip to content

Commit

Permalink
[json-591] Conceptual split of *_json into *_json, *_dict methods on …
Browse files Browse the repository at this point in the history
…Collections

Addresses #591

Pydantic models support direct JSON serialization; we take advantage of
this here to support use of e.g. sets in a model.

The `to_json` and `from_json` methods also technically dealt with dicts,
not JSON strings; this is a distinction that matters since not all dicts
are valid JSON constructs. We resolve that distinction here.

Need explicit roundtripping tests for `*_json` and `*_dict` methods yet.
  • Loading branch information
dotsdl committed Nov 4, 2020
1 parent 85a11e0 commit 5744cb6
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 16 deletions.
76 changes: 64 additions & 12 deletions qcfractal/interface/collections/collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,23 +144,25 @@ def from_server(cls, client: "FractalClient", name: str) -> "Collection":
if tmp_data.meta.n_found == 0:
raise KeyError("Warning! `{}: {}` not found.".format(class_name, name))

return cls.from_json(tmp_data.data[0], client=client)
return cls.from_dict(tmp_data.data[0], client=client)

@classmethod
def from_json(cls, data: Dict[str, Any], client: "FractalClient" = None) -> "Collection":
"""Creates a new class from a JSON blob
def from_dict(cls, data: Dict[str, Any], client: "FractalClient" = None) -> "Collection":
"""Creates a new Collection instance from a dict representation.
Allows roundtrips from `Collection.to_dict`.
Parameters
----------
data : Dict[str, Any]
The JSON blob to create a new class from.
A dict to create a new Collection instance from.
client : FractalClient, optional
A FractalClient connected to a server
A FractalClient connected to a server.
Returns
-------
Collection
A constructed collection.
A Collection instance.
"""
# Check we are building the correct object
Expand All @@ -180,26 +182,76 @@ def from_json(cls, data: Dict[str, Any], client: "FractalClient" = None) -> "Col
ret = cls(name, client=client, **data)
return ret

@classmethod
def from_json(cls, jsondata: Optional[str] = None, filename: Optional[str] = None, client: "FractalClient" = None) -> "Collection":
"""Creates a new Collection instance from a JSON string.
Allows roundtrips from `Collection.to_json`.
One of `jsondata` or `filename` must be provided.
Parameters
----------
jsondata : str, Optional, Default: None
The JSON string to create a new Collection instance from.
filename : str, Optional, Default: None
The filename to read JSON data from.
client : FractalClient, optional
A FractalClient connected to a server.
Returns
-------
Collection
A Collection instance.
"""
if (jsondata is not None) and (filename is not None):
raise ValueError("One of `jsondata` or `filename` must be specified, not both")

if jsondata is not None:
data = json.loads(jsondata)
elif filename is not None:
with open(filename, 'r') as jsonfile:
data = json.load(jsonfile)
else:
raise ValueError("One of `jsondata` or `filename` must be specified")

return cls.from_dict(data, client)

def to_dict(self):
"""
Returns a copy of the current Collection data as a Python dict.
Returns
-------
ret : dict
A Python dict representation of the Collection data.
"""
datadict = self.data.dict()
return copy.deepcopy(datadict)

def to_json(self, filename: Optional[str] = None):
"""
If a filename is provided, dumps the file to disk. Otherwise returns a copy of the current data.
If a filename is provided, dumps the file to disk.
Otherwise returns data as a JSON string.
Parameters
----------
filename : str, Optional, Default: None
The filename to drop the data to.
The filename to write JSON data to.
Returns
-------
ret : dict
A JSON representation of the Collection
If `filename=None`, a JSON representation of the Collection.
Otherwise `None`.
"""
data = self.data.dict()
jsondata = self.json()
if filename is not None:
with open(filename, "w") as open_file:
json.dump(data, open_file)
open_file.write(jsondata)
else:
return copy.deepcopy(data)
return jsondata

@abc.abstractmethod
def _pre_save_prep(self, client: "FractalClient"):
Expand Down
6 changes: 3 additions & 3 deletions qcfractal/interface/collections/collection_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,12 @@ def register_collection(collection: "Collection") -> None:


def collection_factory(data: Dict[str, Any], client: "FractalClient" = None) -> "Collection":
"""Creates a new Collection class from a JSON blob.
"""Creates a new Collection instance from a dict representation.
Parameters
----------
data : Dict[str, Any]
The JSON blob to create a new class from.
A dict to create a new Collection instance from.
client : FractalClient, optional
A FractalClient connected to a server
Expand All @@ -65,7 +65,7 @@ def collection_factory(data: Dict[str, Any], client: "FractalClient" = None) ->
if data["collection"].lower() not in __registered_collections:
raise KeyError("Attempted to create Collection of unknown type '{}'.".format(data["collection"]))

return __registered_collections[data["collection"].lower()].from_json(data, client=client)
return __registered_collections[data["collection"].lower()].from_dict(data, client=client)


def collections_name_map() -> Dict[str, str]:
Expand Down
2 changes: 1 addition & 1 deletion qcfractal/tests/test_collections.py
Original file line number Diff line number Diff line change
Expand Up @@ -857,7 +857,7 @@ def test_compute_reactiondataset_regression(fractal_compute_server):
assert pytest.approx(0.0406367, 1.0e-5) == ds.statistics("MURE", "SCF/sto-3g")
assert pytest.approx(0.002447793, 1.0e-5) == ds.statistics("MURE", "SCF/sto-3g", floor=10)

assert isinstance(ds.to_json(), dict)
assert isinstance(ds.to_dict(), dict)
assert ds.list_records(keywords=None).shape[0] == 1

ds.units = "eV"
Expand Down

0 comments on commit 5744cb6

Please sign in to comment.