-
Notifications
You must be signed in to change notification settings - Fork 28
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
Upgrade script #452
Merged
Merged
Upgrade script #452
Changes from 21 commits
Commits
Show all changes
22 commits
Select commit
Hold shift + click to select a range
5d356c0
Remove header property setters
achilleas-k 7aaac7e
Cleaner file and header creation logic
achilleas-k c3b02b2
Set file ID on creation
achilleas-k 7ea2a6d
Set file format version 1.2.0
achilleas-k d85eae4
Check for ID in header when opening existing files
achilleas-k 6bc5596
Revert "Delete unused debugging function in validator test"
achilleas-k 5d62d49
Update tests for new file header and methods
achilleas-k 572789d
[validator] Check for file ID when appropriate
achilleas-k 6e89d9a
File tests for missing ID and validation
achilleas-k b574853
README for the scripts directory
achilleas-k 8581457
Remove scripts/ from release archives
achilleas-k c79de80
New script: nixio-upgrade
achilleas-k 00cd38e
[property] Simplify version check for old style props
achilleas-k 56c23a8
[property] Simplify old style value reading
achilleas-k 83b7ff2
File metadata H5Group attribute should be private
achilleas-k 30e9a01
Upgrade script: Add file ID and update file version
achilleas-k 05133f3
Upgrade script: Update properties to new format
achilleas-k ecc5121
Use argparse in validate script
achilleas-k 8523ed1
Upgrade script: Small docstring fix
achilleas-k a686f67
Upgrade script: Recheck property before fixing
achilleas-k 5bba39f
Upgrade script: Conditionally add and run tasks
achilleas-k 781c25a
Upgrade script: Copy over Property definition and unit
achilleas-k File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,3 @@ | ||
include LICENSE README.rst | ||
recursive-include docs * | ||
recursive-include scripts * | ||
include nixio/info.json |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +0,0 @@ | ||
from .validate import main as validatemain | ||
|
||
__all__ = ["validatemain"] | ||
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,219 @@ | ||
import argparse | ||
import nixio as nix | ||
import h5py | ||
|
||
|
||
def get_file_version(fname): | ||
with h5py.File(fname, mode="r") as hfile: | ||
return tuple(hfile.attrs["version"]) | ||
|
||
|
||
def has_valid_file_id(fname): | ||
with h5py.File(fname, mode="r") as hfile: | ||
fileid = hfile.attrs.get("id") | ||
if fileid and nix.util.is_uuid(fileid): | ||
return True | ||
return False | ||
|
||
|
||
def add_file_id(fname): | ||
""" | ||
Returns a closure that binds the filename if a file ID is required. When | ||
the return value is called, it adds a UUID to the file header. | ||
""" | ||
if has_valid_file_id(fname): | ||
return None | ||
|
||
def add_id(): | ||
"Add a UUID to the file header" | ||
with h5py.File(fname, mode="a") as hfile: | ||
if has_valid_file_id(fname): | ||
return | ||
print("Adding file id") | ||
hfile.attrs["id"] = nix.util.create_id() | ||
return add_id | ||
|
||
|
||
def update_property_values(fname): | ||
""" | ||
Returns a closure that binds the filename if at least one Property update | ||
is required. When the return value is called, it rewrites all the metadata | ||
Property objects to the new format. | ||
""" | ||
props = list() | ||
|
||
with h5py.File(fname, mode="r") as hfile: | ||
sections = hfile["metadata"] | ||
|
||
def find_props(name, group): | ||
if isinstance(group, h5py.Dataset) and len(group.dtype): | ||
# structured/compound dtypes have non-zero length | ||
props.append(group.name) | ||
|
||
sections.visititems(find_props) | ||
|
||
if not props: | ||
return None | ||
|
||
def update_props(): | ||
for propname in props: | ||
with h5py.File(fname, mode="a") as hfile: | ||
prop = hfile[propname] | ||
if not (isinstance(prop, h5py.Dataset) and len(prop.dtype)): | ||
# File was possibly changed since the tasks were | ||
# collected. File may have been submitted twice or | ||
# multiple instances of the script could be running. | ||
# skip this prop | ||
continue | ||
|
||
print(f"Fixing {propname}") | ||
# pull out the old extra attributes | ||
uncertainty = prop["uncertainty"] | ||
reference = prop["reference"] | ||
filename = prop["filename"] | ||
encoder = prop["encoder"] | ||
checksum = prop["checksum"] | ||
|
||
# replace base prop | ||
values = prop["value"] | ||
dt = values.dtype | ||
del hfile[propname] | ||
newprop = create_property(hfile, propname, | ||
dtype=dt, data=values) | ||
|
||
# Create properties for any extra attrs that are set | ||
if len(set(uncertainty)) > 1: | ||
# multiple values, make new prop | ||
create_property(hfile, propname + ".uncertainty", | ||
dtype=float, data=uncertainty) | ||
elif any(uncertainty): | ||
# single, unique, non-zero value; add to main prop attr | ||
newprop.attrs["uncertainty"] = uncertainty[0] | ||
|
||
if any(reference): | ||
create_property(hfile, propname + ".reference", | ||
dtype=nix.util.vlen_str_dtype, | ||
data=reference) | ||
|
||
if any(filename): | ||
create_property(hfile, propname + ".filename", | ||
dtype=nix.util.vlen_str_dtype, | ||
data=filename) | ||
|
||
if any(encoder): | ||
create_property(hfile, propname + ".encoder", | ||
dtype=nix.util.vlen_str_dtype, | ||
data=encoder) | ||
|
||
if any(checksum): | ||
create_property(hfile, propname + ".checksum", | ||
dtype=nix.util.vlen_str_dtype, | ||
data=checksum) | ||
|
||
update_props.__doc__ = "Update {} properties".format(len(props)) | ||
return update_props | ||
|
||
|
||
def create_property(hfile, name, dtype, data): | ||
prop = hfile.create_dataset(name, dtype=dtype, data=data) | ||
prop.attrs["name"] = name.split("/")[-1] | ||
prop.attrs["entity_id"] = nix.util.create_id() | ||
prop.attrs["created_at"] = nix.util.time_to_str(nix.util.now_int()) | ||
prop.attrs["updated_at"] = nix.util.time_to_str(nix.util.now_int()) | ||
return prop | ||
|
||
|
||
def update_format_version(fname): | ||
""" | ||
Returns a closure that binds the filename. When the return value is | ||
called, it updates the version in the header to the version in the library. | ||
""" | ||
def update_ver(): | ||
with h5py.File(fname, mode="a") as hfile: | ||
hfile.attrs["version"] = nix.file.HDF_FF_VERSION | ||
lib_verstr = ".".join(str(v) for v in nix.file.HDF_FF_VERSION) | ||
update_ver.__doc__ = f"Update the file format version to {lib_verstr}" | ||
return update_ver | ||
|
||
|
||
def collect_tasks(fname): | ||
file_ver = get_file_version(fname) | ||
file_verstr = ".".join(str(v) for v in file_ver) | ||
lib_verstr = ".".join(str(v) for v in nix.file.HDF_FF_VERSION) | ||
if file_ver >= nix.file.HDF_FF_VERSION: | ||
print(f"{fname}: Up to date ({file_verstr})") | ||
return | ||
|
||
# even if the version string indicates the file is old, check format | ||
# details before scheduling tasks | ||
tasks = list() | ||
id_task = add_file_id(fname) | ||
if id_task: | ||
tasks.append(id_task) | ||
|
||
props_task = update_property_values(fname) | ||
if props_task: | ||
tasks.append(props_task) | ||
|
||
# always update the format last | ||
tasks.append(update_format_version(fname)) | ||
|
||
# print task list | ||
print(f"{fname}: {file_verstr} -> {lib_verstr}") | ||
print(" - " + "\n - ".join(t.__doc__ for t in tasks) + "\n") | ||
|
||
return tasks | ||
|
||
|
||
def main(): | ||
parser = argparse.ArgumentParser( | ||
description="Upgrade NIX files to newest version" | ||
) | ||
parser.add_argument("-f", "--force", action="store_true", | ||
help="overwrite existing files without prompting") | ||
parser.add_argument("file", type=str, nargs="+", | ||
help="path to file to upgrade (at least one)") | ||
args = parser.parse_args() | ||
filenames = args.file | ||
|
||
tasks = dict() | ||
for fname in filenames: | ||
tasklist = collect_tasks(fname) | ||
if not tasklist: | ||
continue | ||
|
||
tasks[fname] = tasklist | ||
|
||
if not tasks: | ||
return | ||
|
||
force = args.force | ||
if not force: | ||
print(""" | ||
PLEASE READ CAREFULLY | ||
|
||
If you choose to continue, the changes listed above will be applied to the | ||
respective files. This will make the files unreadable by older NIX library | ||
versions. Although this procedure is generally fast and safe, interrupting it | ||
may leave files in a corrupted state. | ||
|
||
MAKE SURE YOUR FILES AND DATA ARE BACKED UP BEFORE CONTINUING. | ||
""") | ||
conf = None | ||
|
||
while conf not in ("y", "n", "yes", "no"): | ||
conf = input("Continue with changes? [yes/no] ") | ||
conf = conf.lower() | ||
else: | ||
conf = "yes" | ||
|
||
if conf in ("y", "yes"): | ||
for fname, tasklist in tasks.items(): | ||
print(f"Processing {fname} ", end="", flush=True) | ||
for task in tasklist: | ||
task() | ||
print("done") | ||
|
||
|
||
if __name__ == "__main__": | ||
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am afraid the unit got lost during upgrading
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh dear. Good catch!
Same for definition.