Skip to content

Commit

Permalink
♻️ REFACTOR: NodeRepositoryMixin -> NodeRepository
Browse files Browse the repository at this point in the history
  • Loading branch information
chrisjsewell authored and sphuber committed Apr 6, 2022
1 parent 85a8ffb commit 5f98acb
Show file tree
Hide file tree
Showing 46 changed files with 429 additions and 409 deletions.
1 change: 1 addition & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ repos:
aiida/orm/nodes/data/jsonable.py|
aiida/orm/nodes/node.py|
aiida/orm/nodes/process/.*py|
aiida/orm/nodes/repository.py|
aiida/orm/utils/links.py|
aiida/plugins/entry_point.py|
aiida/plugins/factories.py|
Expand Down
4 changes: 2 additions & 2 deletions aiida/cmdline/commands/cmd_calcjob.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ def calcjob_inputcat(calcjob, path):

try:
# When we `cat`, it makes sense to directly send the output to stdout as it is
with calcjob.open(path, mode='rb') as fhandle:
with calcjob.base.repository.open(path, mode='rb') as fhandle:
copyfileobj(fhandle, sys.stdout.buffer)
except OSError as exception:
# The sepcial case is breakon pipe error, which is usually OK.
Expand Down Expand Up @@ -163,7 +163,7 @@ def calcjob_outputcat(calcjob, path):

try:
# When we `cat`, it makes sense to directly send the output to stdout as it is
with retrieved.open(path, mode='rb') as fhandle:
with retrieved.base.repository.open(path, mode='rb') as fhandle:
copyfileobj(fhandle, sys.stdout.buffer)
except OSError as exception:
# The sepcial case is breakon pipe error, which is usually OK.
Expand Down
6 changes: 3 additions & 3 deletions aiida/cmdline/commands/cmd_node.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ def repo_cat(node, relative_path):
import sys

try:
with node.open(relative_path, mode='rb') as fhandle:
with node.base.repository.open(relative_path, mode='rb') as fhandle:
copyfileobj(fhandle, sys.stdout.buffer)
except OSError as exception:
# The sepcial case is breakon pipe error, which is usually OK.
Expand Down Expand Up @@ -96,7 +96,7 @@ def _copy_tree(key, output_dir): # pylint: disable=too-many-branches
Recursively copy the content at the ``key`` path in the given node to
the ``output_dir``.
"""
for file in node.list_objects(key):
for file in node.base.repository.list_objects(key):
# Not using os.path.join here, because this is the "path"
# in the AiiDA node, not an actual OS - level path.
file_key = file.name if not key else f'{key}/{file.name}'
Expand All @@ -110,7 +110,7 @@ def _copy_tree(key, output_dir): # pylint: disable=too-many-branches
assert file.file_type == FileType.FILE
out_file_path = output_dir / file.name
assert not out_file_path.exists()
with node.open(file_key, 'rb') as in_file:
with node.base.repository.open(file_key, 'rb') as in_file:
with out_file_path.open('wb') as out_file:
shutil.copyfileobj(in_file, out_file)

Expand Down
2 changes: 1 addition & 1 deletion aiida/cmdline/utils/repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,6 @@ def list_repository_contents(node, path, color):
"""
from aiida.repository import FileType

for entry in node.list_objects(path):
for entry in node.base.repository.list_objects(path):
bold = bool(entry.file_type == FileType.DIRECTORY)
echo.echo(entry.name, bold=bold, fg='blue' if color and entry.file_type == FileType.DIRECTORY else None)
18 changes: 9 additions & 9 deletions aiida/engine/daemon/execmanager.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,12 +175,12 @@ def upload_calculation(
for code in input_codes:
if code.is_local():
# Note: this will possibly overwrite files
for filename in code.list_object_names():
for filename in code.base.repository.list_object_names():
# Note, once #2579 is implemented, use the `node.open` method instead of the named temporary file in
# combination with the new `Transport.put_object_from_filelike`
# Since the content of the node could potentially be binary, we read the raw bytes and pass them on
with NamedTemporaryFile(mode='wb+') as handle:
handle.write(code.get_object_content(filename, mode='rb'))
handle.write(code.base.repository.get_object_content(filename, mode='rb'))
handle.flush()
transport.put(handle.name, filename)
transport.chmod(code.get_local_executable(), 0o755) # rwxr-xr-x
Expand Down Expand Up @@ -212,14 +212,14 @@ def upload_calculation(
filepath_target = pathlib.Path(folder.abspath) / filename_target
filepath_target.parent.mkdir(parents=True, exist_ok=True)

if data_node.get_object(filename_source).file_type == FileType.DIRECTORY:
if data_node.base.repository.get_object(filename_source).file_type == FileType.DIRECTORY:
# If the source object is a directory, we copy its entire contents
data_node.copy_tree(filepath_target, filename_source)
provenance_exclude_list.extend(data_node.list_object_names(filename_source))
data_node.base.repository.copy_tree(filepath_target, filename_source)
provenance_exclude_list.extend(data_node.base.repository.list_object_names(filename_source))
else:
# Otherwise, simply copy the file
with folder.open(target, 'wb') as handle:
with data_node.open(filename, 'rb') as source:
with data_node.base.repository.open(filename, 'rb') as source:
shutil.copyfileobj(source, handle)

provenance_exclude_list.append(target)
Expand Down Expand Up @@ -320,12 +320,12 @@ def upload_calculation(
dirname not in provenance_exclude_list for dirname in dirnames
):
with open(filepath, 'rb') as handle: # type: ignore[assignment]
node._repository.put_object_from_filelike(handle, relpath) # pylint: disable=protected-access
node.base.repository._repository.put_object_from_filelike(handle, relpath) # pylint: disable=protected-access

# Since the node is already stored, we cannot use the normal repository interface since it will raise a
# `ModificationNotAllowed` error. To bypass it, we go straight to the underlying repository instance to store the
# files, however, this means we have to manually update the node's repository metadata.
node._update_repository_metadata() # pylint: disable=protected-access
node.base.repository._update_repository_metadata() # pylint: disable=protected-access

if not dry_run:
# Make sure that attaching the `remote_folder` with a link is the last thing we do. This gives the biggest
Expand Down Expand Up @@ -465,7 +465,7 @@ def retrieve_calculation(calculation: CalcJobNode, transport: Transport, retriev
with SandboxFolder() as folder:
retrieve_files_from_list(calculation, transport, folder.abspath, retrieve_list)
# Here I retrieved everything; now I store them inside the calculation
retrieved_files.put_object_from_tree(folder.abspath)
retrieved_files.base.repository.put_object_from_tree(folder.abspath)

# Retrieve the temporary files in the retrieved_temporary_folder if any files were
# specified in the 'retrieve_temporary_list' key
Expand Down
4 changes: 2 additions & 2 deletions aiida/engine/processes/calcjobs/calcjob.py
Original file line number Diff line number Diff line change
Expand Up @@ -519,7 +519,7 @@ def parse_scheduler_output(self, retrieved: orm.Node) -> Optional[ExitCode]:
self.logger.warning('could not determine `stderr` filename because `scheduler_stderr` option was not set.')
else:
try:
scheduler_stderr = retrieved.get_object_content(filename_stderr)
scheduler_stderr = retrieved.base.repository.get_object_content(filename_stderr)
except FileNotFoundError:
scheduler_stderr = None
self.logger.warning(f'could not parse scheduler output: the `{filename_stderr}` file is missing')
Expand All @@ -528,7 +528,7 @@ def parse_scheduler_output(self, retrieved: orm.Node) -> Optional[ExitCode]:
self.logger.warning('could not determine `stdout` filename because `scheduler_stdout` option was not set.')
else:
try:
scheduler_stdout = retrieved.get_object_content(filename_stdout)
scheduler_stdout = retrieved.base.repository.get_object_content(filename_stdout)
except FileNotFoundError:
scheduler_stdout = None
self.logger.warning(f'could not parse scheduler output: the `{filename_stdout}` file is missing')
Expand Down
2 changes: 1 addition & 1 deletion aiida/orm/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@
'Node',
'NodeEntityLoader',
'NodeLinksManager',
'NodeRepositoryMixin',
'NodeRepository',
'NumericType',
'OrbitalData',
'OrderSpecifier',
Expand Down
2 changes: 1 addition & 1 deletion aiida/orm/implementation/storage_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ class StorageBackend(abc.ABC): # pylint: disable=too-many-public-methods
- Searchable data, which is stored in the database and can be queried using the QueryBuilder
- Non-searchable (binary) data, which is stored in the repository and can be loaded using the RepositoryBackend
The two sources are inter-linked by the ``Node.repository_metadata``.
The two sources are inter-linked by the ``Node.base.repository.metadata``.
Once stored, the leaf values of this dictionary must be valid pointers to object keys in the repository.
The class methods,`version_profile` and `migrate`,
Expand Down
2 changes: 1 addition & 1 deletion aiida/orm/nodes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
'KpointsData',
'List',
'Node',
'NodeRepositoryMixin',
'NodeRepository',
'NumericType',
'OrbitalData',
'ProcessNode',
Expand Down
12 changes: 6 additions & 6 deletions aiida/orm/nodes/data/array/array.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,11 @@ def delete_array(self, name):
:param name: The name of the array to delete from the node.
"""
fname = f'{name}.npy'
if fname not in self.list_object_names():
if fname not in self.base.repository.list_object_names():
raise KeyError(f"Array with name '{name}' not found in node pk= {self.pk}")

# remove both file and attribute
self.delete_object(fname)
self.base.repository.delete_object(fname)
try:
self.delete_attribute(f'{self.array_prefix}{name}')
except (KeyError, AttributeError):
Expand All @@ -71,7 +71,7 @@ def _arraynames_from_files(self):
Return a list of all arrays stored in the node, listing the files (and
not relying on the properties).
"""
return [i[:-4] for i in self.list_object_names() if i.endswith('.npy')]
return [i[:-4] for i in self.base.repository.list_object_names() if i.endswith('.npy')]

def _arraynames_from_properties(self):
"""
Expand Down Expand Up @@ -111,11 +111,11 @@ def get_array_from_file(self, name):
"""Return the array stored in a .npy file"""
filename = f'{name}.npy'

if filename not in self.list_object_names():
if filename not in self.base.repository.list_object_names():
raise KeyError(f'Array with name `{name}` not found in ArrayData<{self.pk}>')

# Open a handle in binary read mode as the arrays are written as binary files as well
with self.open(filename, mode='rb') as handle:
with self.base.repository.open(filename, mode='rb') as handle:
return numpy.load(handle, allow_pickle=False) # pylint: disable=unexpected-keyword-arg

# Return with proper caching if the node is stored, otherwise always re-read from disk
Expand Down Expand Up @@ -171,7 +171,7 @@ def set_array(self, name, array):
handle.seek(0)

# Write the numpy array to the repository, keeping the byte representation
self.put_object_from_filelike(handle, f'{name}.npy')
self.base.repository.put_object_from_filelike(handle, f'{name}.npy')

# Store the array name and shape for querying purposes
self.set_attribute(f'{self.array_prefix}{name}', list(array.shape))
Expand Down
6 changes: 3 additions & 3 deletions aiida/orm/nodes/data/code.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ def set_files(self, files):
for filename in files:
if os.path.isfile(filename):
with open(filename, 'rb') as handle:
self.put_object_from_filelike(handle, os.path.split(filename)[1])
self.base.repository.put_object_from_filelike(handle, os.path.split(filename)[1])

def __str__(self):
local_str = 'Local' if self.is_local() else 'Remote'
Expand Down Expand Up @@ -282,12 +282,12 @@ def _validate(self):
'You have to set which file is the local executable '
'using the set_exec_filename() method'
)
if self.get_local_executable() not in self.list_object_names():
if self.get_local_executable() not in self.base.repository.list_object_names():
raise exceptions.ValidationError(
f"The local executable '{self.get_local_executable()}' is not in the list of files of this code"
)
else:
if self.list_object_names():
if self.base.repository.list_object_names():
raise exceptions.ValidationError('The code is remote but it has files inside')
if not self.get_remote_computer():
raise exceptions.ValidationError('You did not specify a remote computer')
Expand Down
4 changes: 2 additions & 2 deletions aiida/orm/nodes/data/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ def clone(self):

backend_clone = self.backend_entity.clone()
clone = self.__class__.from_backend_entity(backend_clone)
clone.reset_attributes(copy.deepcopy(self.attributes)) # pylint: disable=no-member
clone._repository.clone(self._repository) # pylint: disable=no-member,protected-access
clone.reset_attributes(copy.deepcopy(self.attributes))
clone.base.repository._clone(self.base.repository) # pylint: disable=protected-access

return clone

Expand Down
2 changes: 1 addition & 1 deletion aiida/orm/nodes/data/folder.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,4 @@ def __init__(self, **kwargs):
tree = kwargs.pop('tree', None)
super().__init__(**kwargs)
if tree:
self.put_object_from_tree(tree)
self.base.repository.put_object_from_tree(tree)
12 changes: 6 additions & 6 deletions aiida/orm/nodes/data/singlefile.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def open(self, path=None, mode='r'):
if path is None:
path = self.filename

with super().open(path, mode=mode) as handle:
with self.base.repository.open(path, mode=mode) as handle:
yield handle

def get_content(self):
Expand Down Expand Up @@ -94,7 +94,7 @@ def set_file(self, file, filename=None):

key = filename or key

existing_object_names = self.list_object_names()
existing_object_names = self.base.repository.list_object_names()

try:
# Remove the 'key' from the list of currently existing objects such that it is not deleted after storing
Expand All @@ -103,13 +103,13 @@ def set_file(self, file, filename=None):
pass

if is_filelike:
self.put_object_from_filelike(file, key)
self.base.repository.put_object_from_filelike(file, key)
else:
self.put_object_from_file(file, key)
self.base.repository.put_object_from_file(file, key)

# Delete any other existing objects (minus the current `key` which was already removed from the list)
for existing_key in existing_object_names:
self.delete_object(existing_key)
self.base.repository.delete_object(existing_key)

self.set_attribute('filename', key)

Expand All @@ -122,7 +122,7 @@ def _validate(self):
except AttributeError:
raise exceptions.ValidationError('the `filename` attribute is not set.')

objects = self.list_object_names()
objects = self.base.repository.list_object_names()

if [filename] != objects:
raise exceptions.ValidationError(
Expand Down
Loading

0 comments on commit 5f98acb

Please sign in to comment.