How to safely move a file to another directory in Python - python-3.x

The below works as expected:
import shutil
source = "c:\\mydir\myfile.txt"
dest_dir = "c:\\newdir"
shutil.move(source,dest_dir)
However, this also succeeds. I would want this to fail.
import shutil
source = "c:\\mydir"
dest_dir = "c:\\newdir"
shutil.move(source,dest_dir)
Any way to ensure that only a file is moved. Both Windows and Unix would be great. If not, Unix at least.

You could use pathlib's purepath.suffix to determine if a path points to a file or a directory, like so:
import pathlib
def points_to_file(path) -> bool:
if pathlib.PurePath(path).suffix:
return True
else:
return False
pathtodir = r'C:\Users\username'
pathtofile = r'C:\Users\username\filename.extension'
print (f'Does "{pathtodir}" point to a file? {points_to_file(pathtodir)}')
# Result -> Does "C:\Users\username" point to a file? False
print (f'Does "{pathtofile}" point to a file? {points_to_file(pathtofile)}')
# Result -> Does "C:\Users\username\filename.extension" point to a file? True

You can define a custom function to ensure that source is a file (with os.path.isfile function):
from os import path
def move_file(src, dst):
if not path.isfile(src):
raise IsADirectoryError('Source is not a file')
shutil.move(src, dst)

Related

Python script output need to save as a text file

import os ,fnmatch
import os.path
import os
file_dir= '/home/deeghayu/Desktop/mec_sim/up_folder'
file_type = ['*.py']
for root, dirs,files in os.walk( file_dir ):
for extension in ( tuple(file_type) ):
for filename in fnmatch.filter(files, extension):
filepath = os.path.join(root, filename)
if os.path.isfile( filepath ):
print(filename , 'executing...');
exec(open('/home/deeghayu/Desktop/mec_sim/up_folder/{}'.format(filename)).read())
else:
print('Execution failure!!!')
Hello everyone I am working on this code which execute a python file using a python code. I need to save my output of the code as a text file. Here I have shown my code. Can any one give me a solution how do I save my output into a text file?
Piggybacking off of the original answer since they are close but it isn't a best practice to open and close files that way.
It's better to use a context manager instead of saying f = open() since the context manager will handle closing the resource for you regardless of whether your code succeeds or not.
You use it like,
with open("file.txt","w+") as f:
for i in range(10):
f.write("This is line %d\r\n" % (i+1))
try
Open file
f= open("file.txt","w+")
Insert data into file
for i in range(10):
f.write("This is line %d\r\n" % (i+1))
Close the file
f.close()

Python how to search files using regular expression [duplicate]

I recently started getting into Python and I am having a hard time searching through directories and matching files based on a regex that I have created.
Basically I want it to scan through all the directories in another directory and find all the files that ends with .zip or .rar or .r01 and then run various commands based on what file it is.
import os, re
rootdir = "/mnt/externa/Torrents/completed"
for subdir, dirs, files in os.walk(rootdir):
if re.search('(w?.zip)|(w?.rar)|(w?.r01)', files):
print "match: " . files
import os
import re
rootdir = "/mnt/externa/Torrents/completed"
regex = re.compile('(.*zip$)|(.*rar$)|(.*r01$)')
for root, dirs, files in os.walk(rootdir):
for file in files:
if regex.match(file):
print(file)
CODE BELLOW ANSWERS QUESTION IN FOLLOWING COMMENT
That worked really well, is there a way to do this if match is found on regex group 1 and do this if match is found on regex group 2 etc ? – nillenilsson
import os
import re
regex = re.compile('(.*zip$)|(.*rar$)|(.*r01$)')
rx = '(.*zip$)|(.*rar$)|(.*r01$)'
for root, dirs, files in os.walk("../Documents"):
for file in files:
res = re.match(rx, file)
if res:
if res.group(1):
print("ZIP",file)
if res.group(2):
print("RAR",file)
if res.group(3):
print("R01",file)
It might be possible to do this in a nicer way, but this works.
Given that you are a beginner, I would recommend using glob in place of a quickly written file-walking-regex matcher.
Snippets of functions using glob and a file-walking-regex matcher
The below snippet contains two file-regex searching functions (one using glob and the other using a custom file-walking-regex matcher). The snippet also contains a "stopwatch" function to time the two functions.
import os
import sys
from datetime import timedelta
from timeit import time
import os
import re
import glob
def stopwatch(method):
def timed(*args, **kw):
ts = time.perf_counter()
result = method(*args, **kw)
te = time.perf_counter()
duration = timedelta(seconds=te - ts)
print(f"{method.__name__}: {duration}")
return result
return timed
#stopwatch
def get_filepaths_with_oswalk(root_path: str, file_regex: str):
files_paths = []
pattern = re.compile(file_regex)
for root, directories, files in os.walk(root_path):
for file in files:
if pattern.match(file):
files_paths.append(os.path.join(root, file))
return files_paths
#stopwatch
def get_filepaths_with_glob(root_path: str, file_regex: str):
return glob.glob(os.path.join(root_path, file_regex))
Comparing runtimes of the above functions
On using the above two functions to find 5076 files matching the regex filename_*.csv in a dir called root_path (containing 66,948 files):
>>> glob_files = get_filepaths_with_glob(root_path, 'filename_*.csv')
get_filepaths_with_glob: 0:00:00.176400
>>> oswalk_files = get_filepaths_with_oswalk(root_path,'filename_(.*).csv')
get_filepaths_with_oswalk: 0:03:29.385379
The glob method is much faster and the code for it is shorter.
For your case
For your case, you can probably use something like the following to get your *.zip,*.rar and *.r01 files:
files = []
for ext in ['*.zip', '*.rar', '*.r01']:
files += get_filepaths_with_glob(root_path, ext)
Here's an alternative using glob.
from pathlib import Path
rootdir = "/mnt/externa/Torrents/completed"
for extension in 'zip rar r01'.split():
for path in Path(rootdir).glob('*.' + extension):
print("match: " + path)
I would do it this way:
import re
from pathlib import Path
def glob_re(path, regex="", glob_mask="**/*", inverse=False):
p = Path(path)
if inverse:
res = [str(f) for f in p.glob(glob_mask) if not re.search(regex, str(f))]
else:
res = [str(f) for f in p.glob(glob_mask) if re.search(regex, str(f))]
return res
NOTE: per default it will recursively scan all subdirectories. If you want to scan only the current directory then you should explicitly specify glob_mask="*"

How to copy from zip file to a folder without unzipping it?

How to make this code works?
There is a zip file with folders and .png files in it. Folder ".\icons_by_year" is empty. I need to get every file one by one without unzipping it and copy to the root of the selected folder (so no extra folders made).
class ArrangerOutZip(Arranger):
def __init__(self):
self.base_source_folder = '\\icons.zip'
self.base_output_folder = ".\\icons_by_year"
def proceed(self):
self.create_and_copy()
def create_and_copy(self):
reg_pattern = re.compile('.+\.\w{1,4}$')
f = open(self.base_source_folder, 'rb')
zfile = zipfile.ZipFile(f)
for cont in zfile.namelist():
if reg_pattern.match(cont):
with zfile.open(cont) as file:
shutil.copyfileobj(file, self.base_output_folder)
zfile.close()
f.close()
arranger = ArrangerOutZip()
arranger.proceed()
shutil.copyfileobj uses file objects for source and destination files. To open the destination you need to construct a file path for it. pathlib is a part of the standard python library and is a nice way to handle file paths. And ZipFile.extract does some of the work of creating intermediate output directories for you (plus sets file metadata) and can be used instead of copyfileobj.
One risk of unzipping files is that they can contain absolute or relative paths outside of the target directory you intend (e.g., "../../badvirus.exe"). extract is a bit too lax about that - putting those files in the root of the target directory - so I wrote a little something to reject the whole zip if you are being messed with.
With a few tweeks to make this a testable program,
from pathlib import Path
import re
import zipfile
#import shutil
#class ArrangerOutZip(Arranger):
class ArrangerOutZip:
def __init__(self, base_source_folder, base_output_folder):
self.base_source_folder = Path(base_source_folder).resolve(strict=True)
self.base_output_folder = Path(base_output_folder).resolve()
def proceed(self):
self.create_and_copy()
def create_and_copy(self):
"""Unzip files matching pattern to base_output_folder, raising
ValueError if any resulting paths are outside of that folder.
Output folder created if it does not exist."""
reg_pattern = re.compile('.+\.\w{1,4}$')
with open(self.base_source_folder, 'rb') as f:
with zipfile.ZipFile(f) as zfile:
wanted_files = [cont for cont in zfile.namelist()
if reg_pattern.match(cont)]
rebased_files = self._rebase_paths(wanted_files,
self.base_output_folder)
for cont, rebased in zip(wanted_files, rebased_files):
print(cont, rebased, rebased.parent)
# option 1: use shutil
#rebased.parent.mkdir(parents=True, exist_ok=True)
#with zfile.open(cont) as file, open(rebased, 'wb') as outfile:
# shutil.copyfileobj(file, outfile)
# option 2: zipfile does the work for you
zfile.extract(cont, self.base_output_folder)
#staticmethod
def _rebase_paths(pathlist, target_dir):
"""Rebase relative file paths to target directory, raising
ValueError if any resulting paths are not within target_dir"""
target = Path(target_dir).resolve()
newpaths = []
for path in pathlist:
newpath = target.joinpath(path).resolve()
newpath.relative_to(target) # raises ValueError if not subpath
newpaths.append(newpath)
return newpaths
#arranger = ArrangerOutZip('\\icons.zip', '.\\icons_by_year')
import sys
try:
arranger = ArrangerOutZip(sys.argv[1], sys.argv[2])
arranger.proceed()
except IndexError:
print("usage: test.py zipfile targetdir")
I'd take a look at the zipfile libraries' getinfo() and also ZipFile.Path() for construction since the constructor class can also use paths that way if you intend to do any creation.
Specifically PathObjects. This is able to do is to construct an object with a path in it, and it appears to be based on pathlib. Assuming you don't need to create zipfiles, you can ignore this ZipFile.Path()
However, that's not exactly what I wanted to point out. Rather consider the following:
zipfile.getinfo()
There is a person who I think is getting at this exact situation here:
https://www.programcreek.com/python/example/104991/zipfile.getinfo
This person seems to be getting a path using getinfo(). It's also clear that NOT every zipfile has the info.

Type hinting a method that returns a string or error in Python 3.7+

I have a Python script that starts with a method searching for a CSV file in the current directory or downloads a directory to make some processing. If no CSV file is found, the program should not run and exit with an error message.
I type annotated a tentative method as follows:
import glob
import os
def get_csv_filename() -> str:
""" Returns first csv filename in current folder or Downloads folder """
csv_filenames = glob.glob('*.csv')
if csv_filenames:
return csv_filenames[0]
home = os.path.expanduser("~")
csv_filenames = glob.glob(home + "/Downloads/*.csv")
if csv_filenames:
return csv_filenames[0]
# If I don't use return, I also get problems with pylint
return exit("Error: no file found, check the documentation for more info.")
def main() -> None:
""" Reads a CSV and does some processing """
filename = get_csv_filename()
If I type check with eg. pytype I get the error:
get_csv_filename: bad option in return type [bad-return-type]
Expected: str
Actually returned: None
What would you recommend doing to make this code compliant?
This section of PEP 484 may be helpful. I don't have mypy or pytype installed to try it, but maybe this would work:
from typing import NoReturn
def get_csv_filename() -> str:
""" Returns first csv filename in current folder or Downloads folder """
csv_filenames = glob.glob('*.csv')
if csv_filenames:
return csv_filenames[0]
...
stop("Error: no file found, check the documentation for more info.")
return ""
def stop(msg) -> NoReturn:
exit(msg)
Another option would be:
from typing import Union
def get_csv_filename() -> Union[None, str]:
...
From the ideas of Steve Bremer's response, the problem can be solved with a simpler approach:
from typing import Optional
def get_csv_filename() -> Optional[str]:
...
In fact, Optional[something] is equivalent to Union[None, something].

Home-made "look for files in directory" function runs, but not properly

I wrote a function that is supposed to look for all the file with the extension chosen, in the selected directory. Actually, it runs but it doesn't return anything.
I am trying to keep things simple/stupid, since I am just at the beginning of my journey in Python
Below, I reported the code.
Thanks for your help!
THIS ONE RUNS, BUT RETURNS AN EMPTY LIST
import fnmatch
import glob
def lookfor(dir, ext):
direct = glob.glob(dir)
files = []
for file in direct:
if fnmatch.fnmatch(file, ext):
files.append(file)
return files
print(lookfor('C:/Users/nameuser/where/folder/', '*.docx'))
THIS ONE WORKS PROPERLY, BUT ONLY FOR .docx FILE, AS WRITTEN INSIDE THE FUNCT
import fnmatch
import glob
def lookfor(dir):
direct = glob.glob(dir)
files = []
for file in direct:
if fnmatch.fnmatch(file, '*.docx'):
files.append(file)
return files
print(lookfor('C:/Users/nameuser/where/folder/*.docx'))

Resources