Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/ruff.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install ruff
pip install ruff==0.16.0
- name: Run Ruff
run: |
ruff check .
ruff format --check .
ruff format --check .
21 changes: 12 additions & 9 deletions babs/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ def __init__(self, project_root, container_config=None):
with open(root_config_path) as f:
cfg = yaml.safe_load(f) or {}
if not isinstance(cfg, dict):
raise ValueError(
raise TypeError(
f'container_config_yaml must be a YAML mapping (key: value pairs), '
f'got {type(cfg).__name__}'
)
Expand Down Expand Up @@ -223,11 +223,13 @@ def _validate_pipeline_config(self) -> None:

Raises
------
TypeError
If the pipeline or one of its steps has an invalid type.
ValueError
If the pipeline configuration is invalid.
If the pipeline configuration is otherwise invalid.
"""
if not isinstance(self.pipeline, list):
raise ValueError('Pipeline configuration must be a list of steps')
raise TypeError('Pipeline configuration must be a list of steps')

if len(self.pipeline) == 0:
raise ValueError('Pipeline configuration cannot be empty')
Expand All @@ -236,7 +238,7 @@ def _validate_pipeline_config(self) -> None:

for i, step in enumerate(self.pipeline):
if not isinstance(step, dict):
raise ValueError(f'Pipeline step {i} must be a dictionary')
raise TypeError(f'Pipeline step {i} must be a dictionary')

required_fields = ['container_name']
for field in required_fields:
Expand Down Expand Up @@ -356,8 +358,8 @@ def wtf_key_info(self, flag_output_ria_only=False) -> None:
'output',
],
stdout=subprocess.PIPE,
check=True,
)
proc_output_ria_data_dir.check_returncode()
self.output_ria_data_dir = urlparse(
proc_output_ria_data_dir.stdout.decode('utf-8')
).path.strip()
Expand All @@ -376,9 +378,9 @@ def wtf_key_info(self, flag_output_ria_only=False) -> None:
['datalad', '-f', "'{infos[dataset][id]}'", 'wtf', '-S', 'dataset'],
cwd=self.analysis_path,
stdout=subprocess.PIPE,
check=True,
)
# datalad -f '{infos[dataset][id]}' wtf -S dataset
proc_analysis_dataset_id.check_returncode()
self.analysis_dataset_id = (
proc_analysis_dataset_id.stdout.decode('utf-8').strip().lstrip("'").rstrip("'")
)
Expand Down Expand Up @@ -474,6 +476,7 @@ def ensure_shared_group_git_safe_directories(self) -> None:
['git', 'config', '--global', '--get-all', 'safe.directory'],
capture_output=True,
text=True,
check=False,
)
existing = (
{line.strip() for line in proc_existing.stdout.splitlines() if line.strip()}
Expand All @@ -488,6 +491,7 @@ def ensure_shared_group_git_safe_directories(self) -> None:
['git', 'config', '--global', '--add', 'safe.directory', repo_path],
capture_output=True,
text=True,
check=True,
)
existing.add(repo_path)

Expand Down Expand Up @@ -533,8 +537,7 @@ def datalad_save(
# Create a temporary .gitignore file to exclude specified files
gitignore_path = op.join(self.analysis_path, '.gitignore')
with open(gitignore_path, 'w') as f:
for file in filter_files:
f.write(f'{file}\n')
f.writelines(f'{file}\n' for file in filter_files)

try:
statuses = self.analysis_datalad_handle.save(path=path, message=message)
Expand All @@ -552,7 +555,7 @@ def datalad_save(
if not saved_status.issubset({'ok', 'notneeded'}):
# exists element in `saved_status` that is not "ok" or "notneeded"
# ^^ "notneeded": nothing to save
raise Exception('`datalad save` failed!')
raise RuntimeError('`datalad save` failed!')

def _get_results_branches(self) -> list[str]:
"""Get the results branch names from the output RIA in a list."""
Expand Down
55 changes: 24 additions & 31 deletions babs/bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,29 +150,26 @@ def babs_bootstrap(
# if exists already, remove it:
if op.exists(gitignore_path):
os.remove(gitignore_path)
gitignore_file = open(gitignore_path, 'a') # open in append mode

# not to track input/output RIA stores:
gitignore_file.write('\n' + op.basename(self.input_ria_path))
gitignore_file.write('\n' + op.basename(self.output_ria_path))
# not to track `logs` folder:
if 'logs' not in (no_ignore or []):
gitignore_file.write('\nlogs')
# not to track `.*_datalad_lock`:
gitignore_file.write('\n.*_datalad_lock')
# not to track lock file:
gitignore_file.write('\n' + 'code/babs_proj_config.yaml.lock')
# not to track `job_status.csv`:
gitignore_file.write('\n' + 'code/job_status.csv')
gitignore_file.write('\n' + 'code/job_status.csv.lock')
gitignore_file.write('\n' + 'code/job_submit.csv')
gitignore_file.write('\n' + 'code/job_submit.csv.lock')
# not to track files generated by `babs check-setup`:
gitignore_file.write('\n' + 'code/check_setup/test_job_info.yaml')
gitignore_file.write('\n' + 'code/check_setup/check_env.yaml')
gitignore_file.write('\n')

gitignore_file.close()
with open(gitignore_path, 'a') as gitignore_file: # open in append mode
# not to track input/output RIA stores:
gitignore_file.write('\n' + op.basename(self.input_ria_path))
gitignore_file.write('\n' + op.basename(self.output_ria_path))
# not to track `logs` folder:
if 'logs' not in (no_ignore or []):
gitignore_file.write('\nlogs')
# not to track `.*_datalad_lock`:
gitignore_file.write('\n.*_datalad_lock')
# not to track lock file:
gitignore_file.write('\n' + 'code/babs_proj_config.yaml.lock')
# not to track `job_status.csv`:
gitignore_file.write('\n' + 'code/job_status.csv')
gitignore_file.write('\n' + 'code/job_status.csv.lock')
gitignore_file.write('\n' + 'code/job_submit.csv')
gitignore_file.write('\n' + 'code/job_submit.csv.lock')
# not to track files generated by `babs check-setup`:
gitignore_file.write('\n' + 'code/check_setup/test_job_info.yaml')
gitignore_file.write('\n' + 'code/check_setup/check_env.yaml')
gitignore_file.write('\n')
self.datalad_save(path='.gitignore', message='Save .gitignore file')

# Create `babs_proj_config.yaml` file: ----------------------
Expand Down Expand Up @@ -246,13 +243,12 @@ def babs_bootstrap(
commit_message = f"Register input data dataset '{dataset_name}' as a subdataset"
git_cmd = ['git', 'commit', '--amend', '-m', commit_message]

result = subprocess.run(
subprocess.run(
git_cmd,
cwd=self.analysis_path,
stdout=subprocess.PIPE,
check=True,
)
result.check_returncode()

# Perform checks on the inputs:
self.input_datasets.validate_input_contents()
Expand Down Expand Up @@ -636,12 +632,12 @@ def clean_up(self):

# `git annex dead here`:
print('\nRunning `git annex dead here`...')
proc_git_annex_dead = subprocess.run(
subprocess.run(
['git', 'annex', 'dead', 'here'],
cwd=self.analysis_path,
stdout=subprocess.PIPE,
check=True,
)
proc_git_annex_dead.check_returncode()

# Update input and output RIA:
print('\nUpdating input and output RIA if created...')
Expand All @@ -654,10 +650,7 @@ def clean_up(self):

# Now we can delete this project folder:
print('\nDeleting created BABS project folder...')
proc_rm_project_folder = subprocess.run(
['rm', '-rf', self.project_root], stdout=subprocess.PIPE
)
proc_rm_project_folder.check_returncode()
subprocess.run(['rm', '-rf', self.project_root], stdout=subprocess.PIPE, check=True)

# confirm the BABS project has been removed:
assert not op.exists(self.project_root), (
Expand Down
21 changes: 10 additions & 11 deletions babs/check_setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ def babs_check_setup(self, submit_a_test_job):
}

# statuses should be all "clean", without anything else e.g., "modified":
if not analysis_statuses == {'clean'}:
if analysis_statuses != {'clean'}:
problem_statuses = [
status
for status in self.analysis_datalad_handle.status(eval_subdataset_state='commit')
Expand Down Expand Up @@ -177,9 +177,8 @@ def babs_check_setup(self, submit_a_test_job):
+ " in 'analysis/code' folder in this BABS project!"
)
# check if bash files are executable:
if op.splitext(temp_fn)[1] == '.sh': # extension is '.sh':
if not os.access(temp_fn, os.X_OK):
raise PermissionError('This code file should be executable: ' + temp_fn)
if op.splitext(temp_fn)[1] == '.sh' and not os.access(temp_fn, os.X_OK):
raise PermissionError('This code file should be executable: ' + temp_fn)
print(CHECK_MARK + ' All good!')

# Check input and output RIA: ----------------------
Expand Down Expand Up @@ -209,7 +208,7 @@ def babs_check_setup(self, submit_a_test_job):
analysis_siblings = self.analysis_datalad_handle.siblings(action='query')
has_sibling_input = False
has_sibling_output = False
for i_sibling in range(0, len(analysis_siblings)):
for i_sibling in range(len(analysis_siblings)):
the_sibling = analysis_siblings[i_sibling]
if the_sibling['name'] == 'output': # output ria:
has_sibling_output = True
Expand Down Expand Up @@ -299,7 +298,7 @@ def _submit_test_job(self):
new_job_status = request_all_job_status(self.queue, job_id)

if not job_status.shape[0] == 1:
raise Exception(
raise RuntimeError(
f'Expected 1 job for {step_container_name}, got {job_status.shape[0]}'
)

Expand All @@ -320,14 +319,14 @@ def _submit_test_job(self):
if op.exists(fn_check_env_yaml):
flag_writable, flag_all_installed = print_versions_from_yaml(fn_check_env_yaml)
if not flag_writable:
raise Exception(
raise RuntimeError(
f'The designated workspace is not writable for {step_container_name}!'
' Please change it in the YAML file'
' used in `babs init --container-config`,'
' then rerun `babs init` with updated YAML file.'
)
if not flag_all_installed:
raise Exception(
raise RuntimeError(
f'Some required package(s) were not installed for '
f'{step_container_name} in the designated environment!'
' Please install them in the designated environment,'
Expand All @@ -349,7 +348,7 @@ def _submit_test_job(self):
new_job_status = request_all_job_status(self.queue, job_id)

if not job_status.shape[0] == 1:
raise Exception(f'Expected 1 job, got {job_status.shape[0]}')
raise RuntimeError(f'Expected 1 job, got {job_status.shape[0]}')

test_info = job_status.iloc[0].to_dict()

Expand All @@ -366,7 +365,7 @@ def _submit_test_job(self):
fn_check_env_yaml = op.join(self.analysis_path, 'code/check_setup', 'check_env.yaml')
flag_writable, flag_all_installed = print_versions_from_yaml(fn_check_env_yaml)
if not flag_writable:
raise Exception(
raise RuntimeError(
'The designated workspace is not writable!'
' Please change it in the YAML file'
' used in `babs init --container-config`,'
Expand All @@ -375,7 +374,7 @@ def _submit_test_job(self):
# NOTE: ^^ currently this is not aligned with YAML file sections;
# this will make more sense after adding section of workspace path in YAML file
if not flag_all_installed:
raise Exception(
raise RuntimeError(
'Some required package(s) were not installed'
' in the designated environment!'
' Please install them in the designated environment,'
Expand Down
4 changes: 2 additions & 2 deletions babs/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -229,14 +229,14 @@ def babs_init_main(
shared_group=shared_group,
no_ignore=no_ignore,
)
except Exception as exc:
except Exception:
print('\n`babs init` failed! Below is the error message:')
if not keep_if_failed:
print('\nCleaning up created BABS project...')
babs_proj.clean_up()
else:
print('\n`--keep-if-failed` is requested, so not to clean up created BABS project.')
raise exc
raise


def _parse_check_setup():
Expand Down
8 changes: 5 additions & 3 deletions babs/container.py
Original file line number Diff line number Diff line change
Expand Up @@ -227,9 +227,11 @@ def generate_bash_test_job(self, folder_check_setup, system, shared_group_mode=F
os.chmod(fn_call_test_job, 0o770 if shared_group_mode else 0o700)

# Copy the template file into the check_setup folder
with resources.files('babs').joinpath('template_test_job.py').open('rb') as src:
with open(fn_test_job, 'wb') as dst:
dst.write(src.read())
with (
resources.files('babs').joinpath('template_test_job.py').open('rb') as src,
open(fn_test_job, 'wb') as dst,
):
dst.write(src.read())
os.chmod(fn_test_job, 0o770 if shared_group_mode else 0o700)

def generate_job_submit_template(self, yaml_path, babs, system, test=False):
Expand Down
4 changes: 2 additions & 2 deletions babs/input_dataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ def is_up_to_date(self):
origin_ds = dlapi.Dataset(self.origin_url)
origin_sha = origin_ds.repo.get_hexsha()

if not babs_sha == origin_sha:
if babs_sha != origin_sha:
print(f'Input dataset {self.name} is not up to date.')
print(f'BABS SHA: {babs_sha}')
print(f'Origin SHA: {origin_sha}')
Expand Down Expand Up @@ -287,7 +287,7 @@ def as_dict(self):
def validate_zipped_input_contents(
dataset_abs_path, root_dir_name, processing_level, included_subjects_df=None
):
""" """
"""Validate the contents of a zipped input dataset."""
zip_pattern = (
f'sub-*_ses-*_{root_dir_name}*.zip'
if processing_level == 'session'
Expand Down
2 changes: 1 addition & 1 deletion babs/interaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ def babs_submit(self, count=None, submit_df=None, skip_failed=False, skip_runnin
)
if non_cg_states.any():
if not skip_running_jobs:
raise Exception(
raise RuntimeError(
'There are still jobs running. '
'Please wait for them to finish or cancel them. '
'Current running jobs:\n'
Expand Down
Loading
Loading