Skip to content

Commit

Permalink
feat(system): Added new method to save the system to a given folder. (#…
Browse files Browse the repository at this point in the history
…23)

List of changes:
- Created a new method `.save` that saves all the content of the system
  (.json + time series) to a desired folder,
- Save method can archive the folder to enable easy sharing of the
  system,
- Added testing to validate that save in fact creates the folder and
  that when we zip it we delete that folder.
- Closed backup connection from `time_series_metadata`

Closes #10
  • Loading branch information
pesap authored Jun 5, 2024
1 parent bd1ab2e commit 3570237
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 0 deletions.
13 changes: 13 additions & 0 deletions docs/how_tos/save_system.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# How to save the system

## Exporting the entire contents of the system

If the user wants to create a folder that contains all the information of the
system `infrasys` provides a simple method `system.save("my_folder")` that
creates a folder (if it does not exist) and save all the contents of the
system including the `system.to_json()` and the time series arrow files and
sqlite.

To archive the system into a zip file, the user can use `system.save("my_folder",
zip=True)`. This will create a zip folder of the contents of `my_folder` and
delete the folder once the archive is done.
69 changes: 69 additions & 0 deletions src/infrasys/system.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Defines a System"""

import json
import shutil
from operator import itemgetter
from collections import defaultdict
from datetime import datetime
Expand Down Expand Up @@ -284,6 +285,74 @@ def from_dict(
logger.info("Deserialized system {}", system.label)
return system

def save(
self,
fpath: Path | str,
filename: str = "system.json",
zip: bool = False,
overwrite: bool = False,
) -> None:
"""Save the contents of a system and the Time series in a single directory.
By default, this method creates the user specified folder using the
`to_json` method. If user sets `zip = True`, we create the folder of
the user (if it does not exists), zip it to the same location specified
and delete the folder.
Parameters
----------
fpath : Path | str
Filepath to write the contents of the system.
zip : bool
Set to True if you want to archive to a zip file.
filename: str
Name of the sytem to serialize. Default value: "system.json".
overwrite: bool
Overwrites the system if it already exist on the fpath.
Raises
------
FileExistsError
Raised if the folder provided exists and the overwrite flag was not provided.
Examples
--------
>>> fpath = Path("folder/subfolder/")
>>> system.save(fpath)
INFO: Wrote system data to folder/subfolder/system.json
INFO: Copied time series data to folder/subfolder/system_time_series
>>> system_fname = "my_system.json"
>>> fpath = Path("folder/subfolder/")
>>> system.save(fpath, filename=system_fname, zip=True)
INFO: Wrote system data to folder/subfolder/my_system.json
INFO: Copied time series data to folder/subfolder/my_system_time_series
INFO: System archived at folder/subfolder/my_system.zip
See Also
--------
to_json: System serialization
"""
if isinstance(fpath, str):
fpath = Path(fpath)

if fpath.exists() and not overwrite:
raise FileExistsError(
f"{fpath} exists already. To overwrite the folder pass `overwrite=True`"
)

fpath.mkdir(parents=True, exist_ok=True)
self.to_json(fpath / filename, overwrite=overwrite)

if zip:
logger.debug("Archiving system and time series into a single zip file at {}", fpath)
_ = shutil.make_archive(str(fpath), "zip", fpath)
logger.debug("Removing {}", fpath)
shutil.rmtree(fpath)
logger.info("System archived at {}", fpath)

return

def add_component(self, component: Component, **kwargs) -> None:
"""Add one component to the system.
Expand Down
2 changes: 2 additions & 0 deletions src/infrasys/time_series_metadata_store.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ def backup(self, directory: Path | str) -> None:
filename = path / self.DB_FILENAME
with sqlite3.connect(filename) as con:
self._con.backup(con)
con.close()
logger.info("Backed up the time series metadata to {}", filename)

def restore(self, directory: Path | str) -> None:
Expand All @@ -144,6 +145,7 @@ def restore(self, directory: Path | str) -> None:
filename = path / self.DB_FILENAME
with sqlite3.connect(filename) as con:
con.backup(self._con)
con.close()
logger.info("Restored the time series metadata to memory")

def get_time_series_counts(self) -> "TimeSeriesCounts":
Expand Down
27 changes: 27 additions & 0 deletions tests/test_serialization.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import json
import random
import os
from datetime import datetime, timedelta

import numpy as np
Expand Down Expand Up @@ -188,3 +189,29 @@ def test_system_with_time_series_normalization(tmp_path, in_memory):
def test_json_schema():
schema = ComponentWithPintQuantity.model_json_schema()
assert isinstance(json.loads(json.dumps(schema)), dict)


def test_system_save(tmp_path, simple_system_with_time_series):
simple_system = simple_system_with_time_series
custom_folder = "my_system"
fpath = tmp_path / custom_folder
fname = "test_system"
simple_system.save(fpath, filename=fname)
assert os.path.exists(fpath), f"Folder {fpath} was not created successfully"
assert os.path.exists(fpath / fname), f"Serialized system {fname} was not created successfully"

fname = "test_system"
with pytest.raises(FileExistsError):
simple_system.save(fpath, filename=fname)

fname = "test_system"
simple_system.save(fpath, filename=fname, overwrite=True)
assert os.path.exists(fpath), f"Folder {fpath} was not created successfully"
assert os.path.exists(fpath / fname), f"Serialized system {fname} was not created successfully"

custom_folder = "my_system_zip"
fpath = tmp_path / custom_folder
simple_system.save(fpath, filename=fname, zip=True)
assert not os.path.exists(fpath), f"Original folder {fpath} was not deleted sucessfully."
zip_fpath = f"{fpath}.zip"
assert os.path.exists(zip_fpath), f"Zip file {zip_fpath} does not exists"

0 comments on commit 3570237

Please sign in to comment.