Skip to content

Commit 5e8663b

Browse files
Create folders return split by datatype (#382)
* Output folders split by datatype, update test. * Update docs. * Fix tests, if datatype passed without ses names then coerce to proper input and display message. * fix 'sub_path' -> 'ses_path'!! Co-authored-by: Niko Sirmpilatze <niko.sirbiladze@gmail.com> * Rename 'folder_path_list' to 'folder_path_dict' in getting_started.md * 'created_folder_list' -> 'created_folder_dict' in tests. * Add 'Returns' section to 'create_folders()', fix incorrect type hints. * Remove unused tests. * Add test to cover split sub / ses behaviour. * Search and replace 'created_folders_list' -> 'created_folders_dict'. * Fix rebase conflicts still in the docstring. --------- Co-authored-by: Niko Sirmpilatze <niko.sirbiladze@gmail.com>
1 parent 5ca2e72 commit 5e8663b

File tree

7 files changed

+102
-26
lines changed

7 files changed

+102
-26
lines changed

datashuttle/datashuttle.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ def create_folders(
149149
datatype: Union[str, List[str]] = "",
150150
bypass_validation: bool = False,
151151
log: bool = True,
152-
) -> List[Path]:
152+
) -> Dict[str, List[Path]]:
153153
"""
154154
Create a subject / session folder tree in the project
155155
folder. The passed subject / session names are
@@ -191,6 +191,16 @@ def create_folders(
191191
log : bool
192192
If `True`, details of folder creation will be logged.
193193
194+
Returns
195+
-------
196+
created_paths :
197+
A dictionary of the full filepaths made during folder creation,
198+
where the keys are the type of folder made and the values are a
199+
list of created folder paths (Path objects). If datatype were
200+
created, the dict keys will separate created folders by datatype
201+
name. Similarly, if only subject or session level folders were
202+
created, these are separated by "sub" and "ses" keys.
203+
194204
Notes
195205
-----
196206
@@ -223,6 +233,13 @@ def create_folders(
223233

224234
self._check_top_level_folder(top_level_folder)
225235

236+
if ses_names is None and datatype != "":
237+
datatype = ""
238+
utils.log_and_message(
239+
"`datatype` passed without `ses_names`, no datatype "
240+
"folders will be created."
241+
)
242+
226243
utils.log("\nFormatting Names...")
227244
ds_logger.log_names(["sub_names", "ses_names"], [sub_names, ses_names])
228245

datashuttle/utils/folders.py

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ def create_folder_trees(
3636
ses_names: Union[str, list],
3737
datatype: Union[List[str], str],
3838
log: bool = True,
39-
) -> List[Path]:
39+
) -> Dict[str, List[Path]]:
4040
"""
4141
Entry method to make a full folder tree. It will
4242
iterate through all passed subjects, then sessions, then
@@ -64,7 +64,12 @@ def create_folder_trees(
6464
if is_invalid:
6565
utils.log_and_raise_error(message, NeuroBlueprintError)
6666

67-
all_paths = []
67+
all_paths: Dict = {}
68+
else:
69+
all_paths = {
70+
"sub": [],
71+
"ses": [],
72+
}
6873

6974
for sub in sub_names:
7075
sub_path = cfg.build_project_path(
@@ -76,7 +81,7 @@ def create_folder_trees(
7681
create_folders(sub_path, log)
7782

7883
if not any(ses_names):
79-
all_paths.append(sub_path)
84+
all_paths["sub"].append(sub_path)
8085
continue
8186

8287
for ses in ses_names:
@@ -89,12 +94,16 @@ def create_folder_trees(
8994
create_folders(ses_path, log)
9095

9196
if datatype_passed:
92-
datatype_paths = make_datatype_folders(
93-
cfg, datatype, ses_path, "ses", log=log
97+
make_datatype_folders(
98+
cfg,
99+
datatype,
100+
ses_path,
101+
"ses",
102+
save_paths=all_paths,
103+
log=log,
94104
)
95-
all_paths.extend(datatype_paths)
96105
else:
97-
all_paths.append(ses_path)
106+
all_paths["ses"].append(ses_path)
98107

99108
return all_paths
100109

@@ -104,15 +113,18 @@ def make_datatype_folders(
104113
datatype: Union[list, str],
105114
sub_or_ses_level_path: Path,
106115
level: str,
116+
save_paths: Dict,
107117
log: bool = True,
108-
) -> List[Path]:
118+
):
109119
"""
110120
Make datatype folder (e.g. behav) at the sub or ses
111121
level. Checks folder_class.Folders attributes,
112122
whether the datatype is used and at the current level.
113123
114124
Parameters
115125
----------
126+
cfg : ConfigsClass
127+
116128
datatype : datatype (e.g. "behav", "all") to use. Use
117129
empty string ("") for none.
118130
@@ -123,21 +135,28 @@ def make_datatype_folders(
123135
level : The folder level that the
124136
folder will be made at, "sub" or "ses"
125137
138+
save_paths : A dictionary, which will be filled
139+
with created paths split by datatype name.
140+
126141
log : whether to log on or not (if True, logging must
127142
already be initialised).
128143
"""
129144
datatype_items = cfg.get_datatype_as_dict_items(datatype)
130145

131-
all_datatype_paths = []
132-
133146
for datatype_key, datatype_folder in datatype_items: # type: ignore
134147
if datatype_folder.level == level:
135-
datatype_path = sub_or_ses_level_path / datatype_folder.name
148+
149+
datatype_name = datatype_folder.name
150+
151+
datatype_path = sub_or_ses_level_path / datatype_name
136152

137153
create_folders(datatype_path, log)
138-
all_datatype_paths.append(datatype_path)
139154

140-
return all_datatype_paths
155+
# Use the custom datatype names for the output.
156+
if datatype_name in save_paths:
157+
save_paths[datatype_name].append(datatype_path)
158+
else:
159+
save_paths[datatype_name] = [datatype_path]
141160

142161

143162
# Create Folders Helpers --------------------------------------------------------
Loading

docs/source/pages/how_tos/create-folders.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -166,8 +166,8 @@ created_folders = project.create_folders(
166166
)
167167
```
168168

169-
The method outputs `created_folders`, which contains a list of all
170-
`Path`s to all created datatype folders. See the below section for
169+
The method outputs `created_folders`, which contains the
170+
`Path`s to created datatype folders. See the below section for
171171
details on the `@DATE@` and other convenience tags.
172172

173173
By default, an error will be raised if the folder names break

docs/source/pages/tutorials/getting_started.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -442,15 +442,15 @@ Finally, hover the mouse over the `Directory Tree` and press `CTRL+R` to refresh
442442
These can be used in acquisition scripts to save data to these folders:
443443

444444
```python
445-
folder_path_list = project.create_folders(
445+
folder_path_dict = project.create_folders(
446446
top_level_folder="rawdata",
447447
sub_names=["sub-001"],
448448
ses_names=["ses-001_@DATE@"],
449449
datatype=["behav", "ephys"]
450450

451451
)
452452

453-
print([path_ for path_ in folder_path_list if path_.name == "behav"])
453+
print([path_ for path_ in folder_path_dict["behav"]])
454454
# ["C:\Users\Joe\data\local\my_first_project\sub-001\ses-001_16052024\behav"]
455455
```
456456

tests/test_utils.py

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ def get_all_folders_used(value=True):
220220

221221

222222
def check_folder_tree_is_correct(
223-
base_folder, subs, sessions, folder_used, created_folder_list=None
223+
base_folder, subs, sessions, folder_used, created_folder_dict=None
224224
):
225225
"""
226226
Automated test that folders are made based
@@ -235,6 +235,9 @@ def check_folder_tree_is_correct(
235235
rely on project settings itself,
236236
as this doesn't explicitly test this.
237237
"""
238+
if created_folder_dict is None:
239+
created_folder_dict = {}
240+
238241
for sub in subs:
239242
path_to_sub_folder = join(base_folder, sub)
240243
check_and_cd_folder(path_to_sub_folder)
@@ -262,12 +265,22 @@ def check_folder_tree_is_correct(
262265

263266
if folder_used[key]:
264267
check_and_cd_folder(datatype_path)
265-
if created_folder_list:
266-
assert Path(datatype_path) in created_folder_list
268+
269+
# Check the created path is found only in the expected
270+
# dict entry.
271+
for (
272+
datatype_name,
273+
all_datatype_paths,
274+
) in created_folder_dict.items():
275+
if datatype_name == key:
276+
assert Path(datatype_path) in all_datatype_paths
277+
else:
278+
assert (
279+
Path(datatype_path) not in all_datatype_paths
280+
)
267281
else:
268282
assert not os.path.isdir(datatype_path)
269-
if created_folder_list:
270-
assert Path(datatype_path) not in created_folder_list
283+
assert key not in created_folder_dict
271284

272285

273286
def check_and_cd_folder(path_):
@@ -344,7 +357,7 @@ def make_and_check_local_project_folders(
344357

345358

346359
def make_local_folders_with_files_in(
347-
project, top_level_folder, subs, sessions=None, datatype="all"
360+
project, top_level_folder, subs, sessions=None, datatype=""
348361
):
349362
project.create_folders(top_level_folder, subs, sessions, datatype)
350363
for root, dirs, _ in os.walk(project.cfg["local_path"]):

tests/tests_integration/test_create_folders.py

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ def test_every_datatype_passed(self, project, behav, ephys, funcimg, anat):
9595
subs = ["sub-001", "sub-002"]
9696
sessions = ["ses-001", "ses-002"]
9797

98-
created_folder_list = project.create_folders(
98+
created_folder_dict = project.create_folders(
9999
"rawdata", subs, sessions, datatypes_to_make
100100
)
101101

@@ -110,7 +110,7 @@ def test_every_datatype_passed(self, project, behav, ephys, funcimg, anat):
110110
"funcimg": funcimg,
111111
"anat": anat,
112112
},
113-
created_folder_list=created_folder_list,
113+
created_folder_dict=created_folder_dict,
114114
)
115115

116116
def test_custom_folder_names(self, project, monkeypatch):
@@ -252,6 +252,33 @@ def test_datetime_flag_in_session(self, project):
252252
assert all([re.search(datetime_regexp, name) for name in ses_names])
253253
assert all([tags("time") not in name for name in ses_names])
254254

255+
def test_created_paths_dict_sub_or_ses_only(self, project):
256+
"""
257+
Test that the `created_folders` dictionary returned by
258+
`create_folders` correctly splits paths when only
259+
subject or session is passed. The `datatype` case is
260+
tested in `test_utils.check_folder_tree_is_correct()`.
261+
"""
262+
created_path_sub = project.create_folders("rawdata", "sub-001")
263+
264+
assert len(created_path_sub) == 2
265+
assert created_path_sub["ses"] == []
266+
assert (
267+
created_path_sub["sub"][0]
268+
== project.get_local_path() / "rawdata" / "sub-001"
269+
)
270+
271+
created_path_ses = project.create_folders(
272+
"rawdata", "sub-002", "ses-001"
273+
)
274+
275+
assert len(created_path_ses) == 2
276+
assert created_path_ses["sub"] == []
277+
assert (
278+
created_path_ses["ses"][0]
279+
== project.get_local_path() / "rawdata" / "sub-002" / "ses-001"
280+
)
281+
255282
# ----------------------------------------------------------------------------------------------------------
256283
# Test Make Folders in Different Top Level Folders
257284
# ----------------------------------------------------------------------------------------------------------

0 commit comments

Comments
 (0)