Skip to content

Commit

Permalink
Merge pull request #1311 from pyiron/output_files
Browse files Browse the repository at this point in the history
Add output files class
  • Loading branch information
jan-janssen authored Feb 8, 2024
2 parents ee6b794 + 2ff2c78 commit 13f2a30
Show file tree
Hide file tree
Showing 9 changed files with 347 additions and 19 deletions.
1 change: 1 addition & 0 deletions .ci_support/environment-docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ dependencies:
- h5io_browser =0.0.7
- h5py =3.10.0
- jinja2 =3.1.3
- monty =2024.2.2
- numpy =1.26.3
- pandas =2.2.0
- pint =0.23
Expand Down
1 change: 1 addition & 0 deletions .ci_support/environment-old.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ dependencies:
- h5io_browser =0.0.6
- h5py =3.6.0
- jinja2 =2.11.3
- monty =2024.2.2
- numpy =1.23.5
- pandas =2.0.0
- pint =0.18
Expand Down
1 change: 1 addition & 0 deletions .ci_support/environment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ dependencies:
- h5io_browser =0.0.7
- h5py =3.10.0
- jinja2 =3.1.3
- monty =2024.2.2
- numpy =1.26.3
- pandas =2.2.0
- pint =0.23
Expand Down
24 changes: 18 additions & 6 deletions pyiron_base/jobs/job/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import os
import posixpath
import shutil
from typing import List
import warnings

from pyiron_base.interfaces.has_groups import HasGroups
Expand All @@ -26,11 +27,15 @@
_job_is_compressed,
_job_compress,
_job_decompress,
_job_list_files,
_job_read_file,
_job_delete_files,
_job_delete_hdf,
_job_remove_folder,
)
from pyiron_base.state import state
from pyiron_base.utils.deprecate import deprecate
from pyiron_base.jobs.job.extension.files import FileBrowser

__author__ = "Jan Janssen"
__copyright__ = (
Expand Down Expand Up @@ -134,6 +139,12 @@ def __init__(self, project, job_name):
def content(self):
return self._hdf5_content

@property
def files(self):
return FileBrowser(working_directory=self.working_directory)

files.__doc__ = FileBrowser.__doc__

@property
def job_name(self):
"""
Expand Down Expand Up @@ -595,6 +606,7 @@ def get_job_id(self, job_specifier=None):
return response[-1]["id"]
return None

@deprecate("use job.files.list()")
def list_files(self):
"""
List files inside the working directory
Expand All @@ -605,9 +617,7 @@ def list_files(self):
Returns:
list: list of file names
"""
if os.path.isdir(self.working_directory):
return os.listdir(self.working_directory)
return []
return _job_list_files(self)

def list_childs(self):
"""
Expand Down Expand Up @@ -898,9 +908,11 @@ def __getitem__(self, item):
"""

if item in self.list_files():
file_name = posixpath.join(self.working_directory, "{}".format(item))
with open(file_name) as f:
return f.readlines()
warnings.warn(
"Using __getitem__ on a job to access files in deprecated: use job.files instead!",
category=DeprecationWarning,
)
return _job_read_file(self, item)

# first try to access HDF5 directly to make the common case fast
try:
Expand Down
118 changes: 118 additions & 0 deletions pyiron_base/jobs/job/extension/files.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import os
from typing import List
from pyiron_base.jobs.job.util import (
_working_directory_list_files,
_working_directory_read_file,
)


class FileBrowser:
"""
Allows to browse the files in a job directory.
By default this object prints itself as a listing of the job directory and
the files inside.
>>> job.files
/path/to/my/job:
\tpyiron.log
\terror.out
Access to the names of files is provided with :meth:`.list`
>>> job.files.list()
['pyiron.log', 'error.out', 'INCAR']
Access to the contents of files is provided by indexing into this object,
which returns a list of lines in the file
>>> job.files['error.out']
["Oh no\n", "Something went wrong!\n"]
The :meth:`.tail` method prints the last lines of a file to stdout
>>> job.files.tail('error.out', lines=1)
Something went wrong!
For files that have valid python variable names can also be accessed by
attribute notation
>>> job.files.INCAR # doctest: +SKIP
File('INCAR')
"""

__slots__ = ("_working_directory",)

def __init__(self, working_directory):
self._working_directory = working_directory

def _get_file_dict(self):
return {
f.replace(".", "_"): f
for f in _working_directory_list_files(
working_directory=self._working_directory
)
}

def __dir__(self):
return list(self._get_file_dict().keys()) + super().__dir__()

def list(self) -> List[str]:
"""
List all files in the working directory of the job.
"""
return _working_directory_list_files(working_directory=self._working_directory)

def _ipython_display_(self):
path = self._job.working_directory + ":"
files = [
"\t" + f
for f in _working_directory_list_files(
working_directory=self._working_directory
)
]
print(os.linesep.join([path, *files]))

def tail(self, file: str, lines: int = 100):
"""
Print the last lines of a file.
Args:
file (str): filename
lines (int): number of lines to print
Raises:
FileNotFoundError: if the given file does not exist
"""
print(
*_working_directory_read_file(
working_directory=self._working_directory, file_name=file, tail=lines
),
sep="",
)

def __getitem__(self, item):
if item not in _working_directory_list_files(
working_directory=self._working_directory
):
raise KeyError(item)

return File(os.path.join(self._working_directory, item))

def __getattr__(self, item):
try:
return self[self._get_file_dict()[item]]
except KeyError:
raise AttributeError(item) from None


class File(str):
def tail(self, lines: int = 100):
print(
*_working_directory_read_file(
working_directory=os.path.dirname(self),
file_name=os.path.basename(self),
tail=lines,
),
sep="",
)
Loading

0 comments on commit 13f2a30

Please sign in to comment.