pathlib.Path, which is great, but it can do a lot more:
- Operations like
os.path.splitext should be modernized using the
suffix attribute of
Path objects; for a full list of those designated replacements, see the documentation.
os.scandir can in this case be replaced by
Path.rglob. This is where the recursion you mentioned comes into play. But that's it: Recursive globbing is an irrelevant implementation detail here. The point is that we do not have to do it "manually".
rglob takes a pattern as its string argument, which can be the suffix you mentioned.
os.makedirs can be
parents=True for the latter. This is not as big a win as the others, but nice.
Eventually, it turns out the
os import can be done without altogether, and recursion is neatly tucked away:
from pathlib import Path
from shutil import copy2 # "identical to copy() but attempts to preserve to file metadata"
COPY_FROM = Path("source")
COPY_TO = Path("dest")
SUFFIX = "*.py"
for source in COPY_FROM.rglob(SUFFIX):
subpath = source.relative_to(COPY_FROM)
destination = COPY_TO.joinpath(subpath)
Note the use of
copy2, which is identical to
copy but attempts to preserve metadata.
This can be convenient for e.g. music files.
parent attribute is the logical parent path to the found file, so essentially all path elements except for the found file itself.
Like in your solution, those path elements (directories) have to be created first.
exist_ok=False is the default and would prohibit us from most desired operations, like copying files in subdirectories.
The above gives the following result, which is hopefully what you are aiming for:
│ ├── file1.py
│ ├── test1
│ │ ├── file2.py
│ │ └── file3.py
│ └── test2
│ ├── file4.py
│ └── test3
│ └── file5.py
│ ├── file2.py
│ └── file3.py