Skip to content

Commit d3cc493

Browse files
committed
Replace raw dict by CategorizedPaths
The current dict is pretty opaque and makes spelling mistakes easy. Avoid by using a typed, named tuple.
1 parent 37d673f commit d3cc493

4 files changed

Lines changed: 56 additions & 54 deletions

File tree

easybuild/framework/easyconfig/tools.py

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
import sys
4747
import tempfile
4848
from collections import OrderedDict
49+
from typing import List
4950

5051
from easybuild.base import fancylogger
5152
from easybuild.framework.easyconfig import EASYCONFIGS_PKG_SUBDIR
@@ -60,7 +61,7 @@
6061
from easybuild.tools.environment import restore_env
6162
from easybuild.tools.filetools import EASYBLOCK_CLASS_PREFIX, get_cwd, find_easyconfigs, is_patch_file
6263
from easybuild.tools.filetools import locate_files, read_file, resolve_path, which, write_file
63-
from easybuild.tools.github import GITHUB_EASYCONFIGS_REPO
64+
from easybuild.tools.github import GITHUB_EASYCONFIGS_REPO, CategorizedPaths
6465
from easybuild.tools.github import det_pr_labels, det_pr_title, download_repo, fetch_easyconfigs_from_commit
6566
from easybuild.tools.github import fetch_easyconfigs_from_pr, fetch_pr_data
6667
from easybuild.tools.github import fetch_files_from_commit, fetch_files_from_pr
@@ -617,37 +618,30 @@ def dump_env_script(easyconfigs):
617618
dump_env_easyblock(app, orig_env=orig_env, ec_path=ec.path, script_path=script_path)
618619

619620

620-
def categorize_files_by_type(paths):
621+
def categorize_files_by_type(paths: List[str]) -> CategorizedPaths:
621622
"""
622623
Splits list of filepaths into a 4 separate lists: easyconfigs, files to delete, patch files and
623624
files with extension .py
624625
"""
625-
res = {
626-
'easyconfigs': [],
627-
'files_to_delete': [],
628-
'patch_files': [],
629-
'py_files': [],
630-
}
626+
res = CategorizedPaths([], [], [], [])
631627

632628
for path in paths:
633629
if path.startswith(':'):
634-
res['files_to_delete'].append(path[1:])
630+
res.files_to_delete.append(path[1:])
635631
elif path.endswith('.py'):
636-
res['py_files'].append(path)
632+
res.py_files.append(path)
637633
# file must exist in order to check whether it's a patch file
638634
elif os.path.isfile(path) and is_patch_file(path):
639-
res['patch_files'].append(path)
635+
res.patch_files.append(path)
640636
elif path.endswith('.patch'):
641637
if not os.path.exists(path):
642638
raise EasyBuildError('File %s does not exist, did you mistype the path?', path)
643-
elif not os.path.isfile(path):
639+
if not os.path.isfile(path):
644640
raise EasyBuildError('File %s is expected to be a regular file, but is a folder instead', path)
645-
else:
646-
raise EasyBuildError('%s is not detected as a valid patch file. Please verify its contents!',
647-
path)
641+
raise EasyBuildError('%s is not detected as a valid patch file. Please verify its contents!', path)
648642
else:
649643
# anything else is considered to be an easyconfig file
650-
res['easyconfigs'].append(path)
644+
res.easyconfigs.append(path)
651645

652646
return res
653647

easybuild/main.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -402,7 +402,7 @@ def process_eb_args(eb_args, eb_go, cfg_settings, modtool, testing, init_session
402402
no_ec_opts = [options.aggregate_regtest, options.regtest, any_pr_option_set, search_query]
403403

404404
# determine paths to easyconfigs
405-
determined_paths = det_easyconfig_paths(categorized_paths['easyconfigs'])
405+
determined_paths = det_easyconfig_paths(categorized_paths.easyconfigs)
406406

407407
# only copy easyconfigs here if we're not using --try-* (that's handled below)
408408
copy_ec = options.copy_ec and not tweaked_ecs_paths

easybuild/tools/github.py

Lines changed: 39 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
from http import HTTPStatus
4949
from http.client import HTTPException
5050
from string import ascii_letters
51+
from typing import List, NamedTuple
5152
from urllib.request import HTTPError, URLError, urlopen
5253

5354
from easybuild.base import fancylogger
@@ -90,6 +91,14 @@
9091
_log.warning("Failed to import 'git' Python module: %s", err)
9192

9293

94+
class CategorizedPaths(NamedTuple):
95+
easyconfigs: List[str]
96+
easyconfigs: List[str]
97+
files_to_delete: List[str]
98+
patch_files: List[str]
99+
py_files: List[str]
100+
101+
93102
GITHUB_URL = 'https://github.com'
94103
GITHUB_API_URL = 'https://api.github.com'
95104
GITHUB_BRANCH_MAIN = 'main'
@@ -1052,7 +1061,8 @@ def setup_repo(git_repo, target_account, target_repo, branch_name, silent=False,
10521061

10531062

10541063
@only_if_module_is_available('git', pkgname='GitPython')
1055-
def _easyconfigs_pr_common(paths, ecs, start_branch=None, pr_branch=None, start_account=None, commit_msg=None):
1064+
def _easyconfigs_pr_common(paths: CategorizedPaths, ecs, start_branch=None, pr_branch=None,
1065+
start_account=None, commit_msg=None):
10561066
"""
10571067
Common code for new_pr and update_pr functions:
10581068
* check whether all supplied paths point to existing files
@@ -1072,8 +1082,8 @@ def _easyconfigs_pr_common(paths, ecs, start_branch=None, pr_branch=None, start_
10721082
# we need files to create the PR with
10731083
non_existing_paths = []
10741084
ec_paths = []
1075-
if paths['easyconfigs'] or paths['py_files']:
1076-
for path in paths['easyconfigs'] + paths['py_files']:
1085+
if paths.easyconfigs or paths.py_files:
1086+
for path in paths.easyconfigs + paths.py_files:
10771087
if not os.path.exists(path):
10781088
non_existing_paths.append(path)
10791089
else:
@@ -1085,7 +1095,7 @@ def _easyconfigs_pr_common(paths, ecs, start_branch=None, pr_branch=None, start_
10851095
exit_code=EasyBuildExit.OPTION_ERROR
10861096
)
10871097

1088-
if not any(paths.values()):
1098+
if not any(paths):
10891099
raise EasyBuildError("No paths specified", exit_code=EasyBuildExit.OPTION_ERROR)
10901100

10911101
pr_target_repo = det_pr_target_repo(paths)
@@ -1139,7 +1149,7 @@ def _easyconfigs_pr_common(paths, ecs, start_branch=None, pr_branch=None, start_
11391149

11401150
# figure out commit message to use
11411151
if commit_msg:
1142-
if (pr_target_repo == GITHUB_EASYCONFIGS_REPO and all(file_info['new']) and not paths['files_to_delete']
1152+
if (pr_target_repo == GITHUB_EASYCONFIGS_REPO and all(file_info['new']) and not paths.files_to_delete
11431153
and is_new_pr): # Only if opening a new PR
11441154
msg = "When only adding new easyconfigs a PR commit msg (--pr-commit-msg) should not be used, as "
11451155
msg += "the PR title will be automatically generated."
@@ -1150,11 +1160,11 @@ def _easyconfigs_pr_common(paths, ecs, start_branch=None, pr_branch=None, start_
11501160
raise EasyBuildError(msg)
11511161
cnt = len(file_info['paths_in_repo'])
11521162
_log.debug("Using specified commit message for all %d new/modified files at once: %s", cnt, commit_msg)
1153-
elif pr_target_repo == GITHUB_EASYCONFIGS_REPO and all(file_info['new']) and not paths['files_to_delete']:
1163+
elif pr_target_repo == GITHUB_EASYCONFIGS_REPO and all(file_info['new']) and not paths.files_to_delete:
11541164
# automagically derive meaningful commit message if all easyconfig files are new
11551165
commit_msg = "adding easyconfigs: %s" % ', '.join(os.path.basename(p) for p in file_info['paths_in_repo'])
1156-
if paths['patch_files']:
1157-
commit_msg += " and patches: %s" % ', '.join(os.path.basename(p) for p in paths['patch_files'])
1166+
if paths.patch_files:
1167+
commit_msg += " and patches: %s" % ', '.join(os.path.basename(p) for p in paths.patch_files)
11581168
elif pr_target_repo == GITHUB_EASYBLOCKS_REPO and all(file_info['new']):
11591169
commit_msg = "adding easyblocks: %s" % ', '.join(os.path.basename(p) for p in file_info['paths_in_repo'])
11601170
else:
@@ -1163,22 +1173,22 @@ def _easyconfigs_pr_common(paths, ecs, start_branch=None, pr_branch=None, start_
11631173
if not new]
11641174
if modified_files:
11651175
msg += '\nModified: ' + ', '.join(modified_files)
1166-
if paths['files_to_delete']:
1167-
msg += '\nDeleted: ' + ', '.join(paths['files_to_delete'])
1176+
if paths.files_to_delete:
1177+
msg += '\nDeleted: ' + ', '.join(paths.files_to_delete)
11681178
raise EasyBuildError("A meaningful commit message must be specified via --pr-commit-msg when "
11691179
"modifying/deleting files or targeting the framework repo." + msg,
11701180
exit_code=EasyBuildExit.OPTION_ERROR)
11711181

11721182
# figure out to which software name patches relate, and copy them to the right place
1173-
if paths['patch_files']:
1174-
patch_specs = det_patch_specs(paths['patch_files'], file_info, [target_dir])
1183+
if paths.patch_files:
1184+
patch_specs = det_patch_specs(paths.patch_files, file_info, [target_dir])
11751185

11761186
print_msg("copying patch files to %s..." % target_dir)
11771187
patch_info = copy_patch_files(patch_specs, target_dir)
11781188

11791189
# determine path to files to delete (if any)
11801190
deleted_paths = []
1181-
for fn in paths['files_to_delete']:
1191+
for fn in paths.files_to_delete:
11821192
fullpath = os.path.join(repo_path, fn)
11831193
if os.path.exists(fullpath):
11841194
deleted_paths.append(fullpath)
@@ -1217,8 +1227,8 @@ def _easyconfigs_pr_common(paths, ecs, start_branch=None, pr_branch=None, start_
12171227
if pr_branch is None:
12181228
if ec_paths and pr_target_repo == GITHUB_EASYCONFIGS_REPO:
12191229
label = file_info['ecs'][0].name + re.sub('[.-]', '', file_info['ecs'][0].version)
1220-
elif pr_target_repo == GITHUB_EASYBLOCKS_REPO and paths.get('py_files'):
1221-
label = os.path.splitext(os.path.basename(paths['py_files'][0]))[0]
1230+
elif pr_target_repo == GITHUB_EASYBLOCKS_REPO and paths.py_files:
1231+
label = os.path.splitext(os.path.basename(paths.py_files[0]))[0]
12221232
else:
12231233
label = ''.join(random.choice(ascii_letters) for _ in range(10))
12241234
pr_branch = '%s_new_pr_%s' % (time.strftime("%Y%m%d%H%M%S"), label)
@@ -1233,7 +1243,7 @@ def _easyconfigs_pr_common(paths, ecs, start_branch=None, pr_branch=None, start_
12331243
git_repo.index.add(file_info['paths_in_repo'])
12341244
git_repo.index.add(dep_info['paths_in_repo'])
12351245

1236-
if paths['patch_files']:
1246+
if paths.patch_files:
12371247
_log.debug("Staging all %d new/modified patch files", len(patch_info['paths_in_repo']))
12381248
git_repo.index.add(patch_info['paths_in_repo'])
12391249

@@ -1913,7 +1923,7 @@ def add_pr_labels(pr, branch=GITHUB_DEVELOP_BRANCH):
19131923

19141924

19151925
@only_if_module_is_available('git', pkgname='GitPython')
1916-
def new_branch_github(paths, ecs, commit_msg=None):
1926+
def new_branch_github(paths: CategorizedPaths, ecs, commit_msg=None):
19171927
"""
19181928
Create new branch on GitHub using specified files
19191929
@@ -1971,7 +1981,8 @@ def det_pr_title(ecs):
19711981

19721982

19731983
@only_if_module_is_available('git', pkgname='GitPython')
1974-
def new_pr_from_branch(branch_name, title=None, descr=None, pr_target_repo=None, pr_metadata=None, commit_msg=None):
1984+
def new_pr_from_branch(branch_name: CategorizedPaths, title=None, descr=None,
1985+
pr_target_repo=None, pr_metadata=None, commit_msg=None):
19751986
"""
19761987
Create new pull request from specified branch on GitHub.
19771988
"""
@@ -2152,7 +2163,7 @@ def new_pr_from_branch(branch_name, title=None, descr=None, pr_target_repo=None,
21522163
print_msg("This PR should be labelled %s" % ', '.join(labels), log=_log, prefix=False)
21532164

21542165

2155-
def new_pr(paths, ecs, title=None, descr=None, commit_msg=None):
2166+
def new_pr(paths: CategorizedPaths, ecs, title=None, descr=None, commit_msg=None):
21562167
"""
21572168
Open new pull request using specified files
21582169
@@ -2184,8 +2195,8 @@ def new_pr(paths, ecs, title=None, descr=None, commit_msg=None):
21842195
raise EasyBuildError(msg, exit_code=EasyBuildExit.EASYCONFIG_ERROR)
21852196
patch = patch_info['name']
21862197

2187-
if patch not in paths['patch_files'] and not os.path.isfile(os.path.join(os.path.dirname(ec_path),
2188-
patch)):
2198+
if patch not in paths.patch_files and not os.path.isfile(os.path.join(os.path.dirname(ec_path),
2199+
patch)):
21892200
print_warning("new patch file %s, referenced by %s, is not included in this PR" %
21902201
(patch, ec.filename()))
21912202

@@ -2226,29 +2237,26 @@ def det_account_branch_for_pr(pr_id, github_user=None, pr_target_repo=None):
22262237
return account, branch
22272238

22282239

2229-
def det_pr_target_repo(paths):
2240+
def det_pr_target_repo(paths: CategorizedPaths):
22302241
"""Determine target repository for pull request from given cagetorized list of files
22312242
22322243
:param paths: paths to categorized lists of files (easyconfigs, files to delete, patches, .py files)
22332244
"""
22342245
pr_target_repo = build_option('pr_target_repo')
22352246

22362247
# determine target repository for PR based on which files are provided
2237-
# (see categorize_files_by_type function)
22382248
if pr_target_repo is None:
22392249

22402250
_log.info("Trying to derive target repository based on specified files...")
22412251

2242-
easyconfigs, files_to_delete, patch_files, py_files = [paths[key] for key in sorted(paths.keys())]
2243-
22442252
# Python files provided, and no easyconfig files or patches
2245-
if py_files and not (easyconfigs or patch_files):
2253+
if paths.py_files and not (paths.easyconfigs or paths.patch_files):
22462254

22472255
_log.info("Only Python files provided, no easyconfig files or patches...")
22482256

22492257
# if all Python files are easyblocks, target repo should be easyblocks;
22502258
# otherwise, target repo is assumed to be framework
2251-
if all(get_easyblock_class_name(path) for path in py_files):
2259+
if all(get_easyblock_class_name(path) for path in paths.py_files):
22522260
pr_target_repo = GITHUB_EASYBLOCKS_REPO
22532261
_log.info("All Python files are easyblocks, target repository is assumed to be %s", pr_target_repo)
22542262
else:
@@ -2257,7 +2265,8 @@ def det_pr_target_repo(paths):
22572265

22582266
# if no Python files are provided, only easyconfigs & patches, or if files to delete are .eb files,
22592267
# then target repo is assumed to be easyconfigs
2260-
elif easyconfigs or patch_files or (files_to_delete and all(x.endswith('.eb') for x in files_to_delete)):
2268+
elif paths.easyconfigs or paths.patch_files or (paths.files_to_delete and all(x.endswith('.eb')
2269+
for x in paths.files_to_delete)):
22612270
pr_target_repo = GITHUB_EASYCONFIGS_REPO
22622271
_log.info("Only easyconfig and patch files found, target repository is assumed to be %s", pr_target_repo)
22632272

@@ -2268,7 +2277,7 @@ def det_pr_target_repo(paths):
22682277

22692278

22702279
@only_if_module_is_available('git', pkgname='GitPython')
2271-
def update_branch(branch_name, paths, ecs, github_account=None, commit_msg=None):
2280+
def update_branch(branch_name, paths: CategorizedPaths, ecs, github_account=None, commit_msg=None):
22722281
"""
22732282
Update specified branch in GitHub using specified files
22742283
@@ -2303,7 +2312,7 @@ def update_branch(branch_name, paths, ecs, github_account=None, commit_msg=None)
23032312

23042313

23052314
@only_if_module_is_available('git', pkgname='GitPython')
2306-
def update_pr(pr_id, paths, ecs, commit_msg=None):
2315+
def update_pr(pr_id, paths: CategorizedPaths, ecs, commit_msg=None):
23072316
"""
23082317
Update specified pull request using specified files
23092318

test/framework/easyconfig.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@
6060
from easybuild.framework.easyconfig.style import check_easyconfigs_style
6161
from easybuild.framework.easyconfig.tools import alt_easyconfig_paths, categorize_files_by_type, check_sha256_checksums
6262
from easybuild.framework.easyconfig.tools import dep_graph, det_copy_ec_specs, find_related_easyconfigs, get_paths_for
63-
from easybuild.framework.easyconfig.tools import parse_easyconfigs
63+
from easybuild.framework.easyconfig.tools import parse_easyconfigs, CategorizedPaths
6464
from easybuild.framework.easyconfig.tweak import obtain_ec_for, tweak, tweak_one
6565
from easybuild.framework.extension import construct_exts_filter_cmds
6666
from easybuild.toolchains.system import SystemToolchain
@@ -3876,8 +3876,7 @@ def test_hidden_toolchain(self):
38763876

38773877
def test_categorize_files_by_type(self):
38783878
"""Test categorize_files_by_type"""
3879-
self.assertEqual({'easyconfigs': [], 'files_to_delete': [], 'patch_files': [], 'py_files': []},
3880-
categorize_files_by_type([]))
3879+
self.assertEqual(categorize_files_by_type([]), CategorizedPaths([], [], [], []))
38813880

38823881
test_dir = os.path.dirname(os.path.abspath(__file__))
38833882
test_ecs_dir = os.path.join(test_dir, 'easyconfigs')
@@ -3904,10 +3903,10 @@ def test_categorize_files_by_type(self):
39043903
gzip_ec,
39053904
'foo',
39063905
]
3907-
self.assertEqual(res['easyconfigs'], expected)
3908-
self.assertEqual(res['files_to_delete'], ['toy-0.0-deps.eb'])
3909-
self.assertEqual(res['patch_files'], [toy_patch])
3910-
self.assertEqual(res['py_files'], [toy_easyblock, configuremake])
3906+
self.assertEqual(res.easyconfigs, expected)
3907+
self.assertEqual(res.files_to_delete, ['toy-0.0-deps.eb'])
3908+
self.assertEqual(res.patch_files, [toy_patch])
3909+
self.assertEqual(res.py_files, [toy_easyblock, configuremake])
39113910

39123911
# Error cases
39133912
tmpdir = tempfile.mkdtemp()

0 commit comments

Comments
 (0)