Finding the user's directory using python - python

So I am writing a script to automate some things that me and my teammates do. We have a git repo and this script is intended for all members to use. It has a part that is hardcoded to specifically be my folder path: C:/Users/jorge.padilla/etc...
I am still relatively new to python and not familiar with all the different libraries. I basically want to turn the user directory, i.e. jorge.padilla, into a variable that is not hardcoded, and that doesn't need to take user input so that the script will search for whatever the current user directory is and substitute it.
Below is a small snippet of the automation script I am writing to use as an example.
import os, sys
from pathlib import Path
from enum import Enum
#Global Variables
PRODUCTS_FOLDER = "Products"
APP_FOLDER = "App"
DEV_BUILD = "ionic cordova build android"
PROD_BUILD = "ionic cordova build android --release --prod"
class BuildConfig():
def __init__(self, start_path):
self.start_path = start_path
def getProductFolder(self):
return os.path.join(self.start_path, PRODUCTS_FOLDER)
class BuildTypeEnum(Enum):
PROD = 1
DEV = 2
def buildingApp(ConfigPath:BuildConfig, DEVvPROD:BuildTypeEnum):
path = ConfigPath.getProductFolder()
app_path = os.path.join(path, APP_FOLDER)
os.chdir(app_path)
if DEVvPROD == BuildTypeEnum.DEV:
os.system(DEV_BUILD)
elif DEVvPROD == BuildTypeEnum.PROD:
os.system(PROD_BUILD)
else:
print("Invalid input.")
return
if __name__ == "__main__":
root_start_path = "C:/Users/jorge.padilla/Documents/"
build = BuildConfig(root_start_path)
buildType = None
buildTypeInput = input("Is this a dev or production build? (d/p): ")
if (buildTypeInput.lower() == 'd'):
buildType = BuildTypeEnum.DEV
elif (buildTypeInput.lower() == 'p'):
buildType = BuildTypeEnum.PROD
else:
print("Please specify if this is a development or production build.")
return
The main variable I want to do this for is root_start_path

You should use pathlib (which you imported, but never used?):
import pathlib
root_start_path = pathlib.Path.home() # WindowsPath('C:/Users/jorge.padilla')
It works across platforms as well, and it's really the best way to handle file paths (IMO)
It can even simplify the syntax in accessing other directories within that path:
root_start_path = pathlib.Path.home() / 'Documents' # WindowsPath('C:/Users/jorge.padilla/Documents')

You could also do:
from os.path import expanduser
home = expanduser("~")

Related

What do I set the download path to for users?

This is for a project where a user can download all their GitHub Gists.
This code gets the directory of the user's Download folder on their computer for files to download into. But what if the user's browser's download location is not the computer's Download folder? Maybe it's the Desktop or some random folder.
Am I supposed to check what browser the user is using and somehow get the path of where their download location is? Though a Google search says there's 200 different browsers...
Even if I was to ignore the user's browser's download location and save to the operating system's Download folder there are at least 33 according to a search.
# Find the user's download folder
# Get the operating system
system = platform.system()
# Set the path to save the files to the user's Download folder location
if system == "Windows":
save_path = os.path.join(os.environ['USERPROFILE'], 'Downloads')
elif system == "Darwin":
save_path = os.path.expanduser("~/Downloads")
elif system == "Linux":
save_path = os.path.expanduser("~/Downloads")
on windows to get the path of the Downloads folder, it should be done through win32Api, specifically through SHGetKnownFolderPath, python has access to it through ctypes, the way to access this specific function is taken from Windows Special and Known Folders from python stack overflow answer. with some modifications to read c_wchar_p.
you have to pass in the GUID for the downloads folder from KNOWNFOLDERID which is "{374DE290-123F-4565-9164-39C4925E467B}".
, so you end up with the following code that works only on 64-bit python, for 32-bit you will probably have to change the argument types.
from ctypes import windll, wintypes
from ctypes import *
from uuid import UUID
from itertools import count
from functools import partial
# ctypes GUID copied from MSDN sample code
class GUID(Structure):
_fields_ = [
("Data1", wintypes.DWORD),
("Data2", wintypes.WORD),
("Data3", wintypes.WORD),
("Data4", wintypes.BYTE * 8)
]
def __init__(self, uuidstr):
uuid = UUID(uuidstr)
Structure.__init__(self)
self.Data1, self.Data2, self.Data3, self.Data4[0], self.Data4[1], rest = uuid.fields
for i in range(2, 8):
self.Data4[i] = rest>>(8-i-1)*8 & 0xff
FOLDERID_Downloads = '{374DE290-123F-4565-9164-39C4925E467B}'
SHGetKnownFolderPath = windll.shell32.SHGetKnownFolderPath
SHGetKnownFolderPath.argtypes = [
POINTER(GUID), wintypes.DWORD, wintypes.HANDLE, POINTER(c_char_p)]
def get_known_folder_path(uuidstr):
pathptr = c_char_p()
guid = GUID(uuidstr)
if SHGetKnownFolderPath(byref(guid), 0, 0, byref(pathptr)):
raise Exception('Whatever you want here...')
resp = cast(pathptr,POINTER(c_wchar))
iterator = (resp.__getitem__(i) for i in count(0))
result = ''.join(list(iter(iterator.__next__, '\x00')))
return result
print(get_known_folder_path(FOLDERID_Downloads))
this will return the Downloads folder location even if the user changes it through the properties, or for different languages.
on linux a similar method is to get it from $HOME/.config/user-dirs.dirs under the name of XDG_DOWNLOAD_DIR, which is changed with user settings changes.
$ grep XDG_DOWNLOAD_DIR ~/.config/user-dirs.dirs
XDG_DOWNLOAD_DIR="$HOME/Downloads"
This is obviously only the "default" location, you should allow the user to manually specify his own custom downloads path.
Using a hardcoded path is a recipe for "but it works on my machine", so just ask the OS about its path.

How to run the RegexMatchingEventHandler of Watchdog correctly?

I'm working on a small tool for a GameApi. This Api works with .log-files. They are provided in a specific location. I want to observe this location with with watchdog and it is working fine, if I use the PatternMatchingEventHandler. But if I use the RegexMatchingEventHandler it fails. I want to use Regex because there a many .log-files and I only want to check the files from today.
Extension: I use Watchdog with the functions:
on_created
on_deleted
on_moved
on_modified
This Website shows the code I am using:
https://www.thepythoncorner.com/2019/01/how-to-create-a-watchdog-in-python-to-look-for-filesystem-changes/
I tested my Regex function normaly with re. This is doing absolutly fine. But even if I try the Regex Entry: ['\w+.log'] it did not work.
I provide you my Regex to understand what I want to do:
regexes = ["^Journal\.190901\d{6}\.\d{2}\.log$"]
I expected a message everytime I change one of my .log-files but this is only happend i f i use the PatternMatchingEventHAndle
EDIT:
Now I present you you my minimal Example:
import time
from watchdog.observers import Observer
from watchdog.events import RegexMatchingEventHandler
if __name__ == "__main__":
regexes = ["^Journal\.190901\d{6}\.\d{2}\.log$"]
ignore_regexes= []
ignore_directories = False
case_sensitive = True
my_event_handler = RegexMatchingEventHandler(regexes,ignore_regexes,ignore_directories,case_sensitive)
def on_modified(event):
print(f"hey buddy, {event.src_path} has been modified")
my_event_handler.on_modified = on_modified
# Observer
path = "."
go_recursively = True
my_observer = Observer()
my_observer.schedule(my_event_handler, path, recursive=go_recursively)
my_observer.start()
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
my_observer.stop()
my_observer.join()
This is just about the pattern of the path returned by the event handler, it begins by the path to the root of the folder specified in path (path = "." in your example) .
It can help to inspect any patterns of the paths returned, and check what you need exactly:
regexes = ['.+'] # will match everything
To track files in the current directory with path = ".":
# linux (example path : ./Journal[...].log)
regexes = ['^\./Journal\.190901\d{6}\.\d{2}\.log$']
# windows (example path : .\Journal[...].log)
regexes = ["^\.\\\\Journal\.190901\d{6}\.\d{2}\.log$"]
# both
regexes = ["^\.(/|\\\\)Journal\.190901\d{6}\.\d{2}\.log$"]
if you define a subfolder named logs and you specify the root like path = "logs"
# (example path : logs/Journal[...].log or logs\Journal[...].log
regexes = ['^logs(/|\\\\)logs/Journal\.190901\d{6}\.\d{2}\.log$']

python - Finding the user's "Downloads" folder

I already found this question that suggests to use os.path.expanduser(path) to get the user's home directory.
I would like to achieve the same with the "Downloads" folder. I know that this is possible in C#, yet I'm new to Python and don't know if this is possible here too, preferable platform-independent (Windows, Ubuntu).
I know that I just could do download_folder = os.path.expanduser("~")+"/Downloads/", yet (at least in Windows) it is possible to change the Default download folder.
from pathlib import Path
downloads_path = str(Path.home() / "Downloads")
This fairly simple solution (expanded from this reddit post) worked for me
import os
def get_download_path():
"""Returns the default downloads path for linux or windows"""
if os.name == 'nt':
import winreg
sub_key = r'SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders'
downloads_guid = '{374DE290-123F-4565-9164-39C4925E467B}'
with winreg.OpenKey(winreg.HKEY_CURRENT_USER, sub_key) as key:
location = winreg.QueryValueEx(key, downloads_guid)[0]
return location
else:
return os.path.join(os.path.expanduser('~'), 'downloads')
The GUID can be obtained from Microsoft's KNOWNFOLDERID docs
This can be expanded to work more generically other directories
For python3+ mac or linux
from pathlib import Path
path_to_download_folder = str(os.path.join(Path.home(), "Downloads"))
Correctly locating Windows folders is somewhat of a chore in Python. According to answers covering Microsoft development technologies, such as this one, they should be obtained using the Vista Known Folder API. This API is not wrapped by the Python standard library (though there is an issue from 2008 requesting it), but one can use the ctypes module to access it anyway.
Adapting the above answer to use the folder id for downloads shown here and combining it with your existing Unix code should result in code that looks like this:
import os
if os.name == 'nt':
import ctypes
from ctypes import windll, wintypes
from uuid import UUID
# ctypes GUID copied from MSDN sample code
class GUID(ctypes.Structure):
_fields_ = [
("Data1", wintypes.DWORD),
("Data2", wintypes.WORD),
("Data3", wintypes.WORD),
("Data4", wintypes.BYTE * 8)
]
def __init__(self, uuidstr):
uuid = UUID(uuidstr)
ctypes.Structure.__init__(self)
self.Data1, self.Data2, self.Data3, \
self.Data4[0], self.Data4[1], rest = uuid.fields
for i in range(2, 8):
self.Data4[i] = rest>>(8-i-1)*8 & 0xff
SHGetKnownFolderPath = windll.shell32.SHGetKnownFolderPath
SHGetKnownFolderPath.argtypes = [
ctypes.POINTER(GUID), wintypes.DWORD,
wintypes.HANDLE, ctypes.POINTER(ctypes.c_wchar_p)
]
def _get_known_folder_path(uuidstr):
pathptr = ctypes.c_wchar_p()
guid = GUID(uuidstr)
if SHGetKnownFolderPath(ctypes.byref(guid), 0, 0, ctypes.byref(pathptr)):
raise ctypes.WinError()
return pathptr.value
FOLDERID_Download = '{374DE290-123F-4565-9164-39C4925E467B}'
def get_download_folder():
return _get_known_folder_path(FOLDERID_Download)
else:
def get_download_folder():
home = os.path.expanduser("~")
return os.path.join(home, "Downloads")
A more complete module for retrieving known folders from Python is available on github.
Some linux distributions localize the name of the Downloads folder. E.g. after changing my locale to zh_TW, the Downloads folder became /home/user/下載. The correct way on linux distributions (using xdg-utils from freedesktop.org) is to call xdg-user-dir:
import subprocess
# Copy windows part from other answers here
try:
folder = subprocess.run(["xdg-user-dir", "DOWNLOAD"],
capture_output=True, text=True).stdout.strip("\n")
except FileNotFoundError: # if the command is missing
import os.path
folder = os.path.expanduser("~/Downloads") # fallback
Note that the use of capture_output requires Python ≥3.7.
If you already use GLib or don't mind adding more dependencies, see also
these approaches using packages.
For python3 on windows try:
import os
folder = os.path.join(os.path.join(os.environ['USERPROFILE']), 'folder_name')
print(folder)

how to preserve module path of a module executed as a script

I have a function called get_full_class_name(instance), which returns the full module-qualified class name of instance.
Example my_utils.py:
def get_full_class_name(instance):
return '.'.join([instance.__class__.__module__,
instance.__class__.__name__])
Unfortunately, this function fails when given a class that's defined in a currently running script.
Example my_module.py:
#! /usr/bin/env python
from my_utils import get_full_class_name
class MyClass(object):
pass
def main():
print get_full_class_name(MyClass())
if __name__ == '__main__':
main()
When I run the above script, instead of printing my_module.MyClass, it prints __main__.MyClass:
$ ./my_module.py
__main__.MyClass
I do get the desired behavior if I run the above main() from another script.
Example run_my_module.py:
#! /usr/bin/env python
from my_module import main
if __name__ == '__main__':
main()
Running the above script gets:
$ ./run_my_module.py
my_module.MyClass
Is there a way I could write the get_full_class_name() function such that it always returns my_module.MyClass regardless of whether my_module is being run as a script?
I propose handling the case __name__ == '__main__' using the techniques discussed in Find Path to File Being Run. This results in this new my_utils:
import sys
import os.path
def get_full_class_name(instance):
if instance.__class__.__module__ == '__main__':
return '.'.join([os.path.basename(sys.argv[0]),
instance.__class__.__name__])
else:
return '.'.join([instance.__class__.__module__,
instance.__class__.__name__])
This does not handle interactive sessions and other special cases (like reading from stdin). For this you may have to include techniques like discussed in detect python running interactively.
Following mkiever's answer, I ended up changing get_full_class_name() to what you see below.
If instance.__class__.__module__ is __main__, it doesn't use that as the module path. Instead, it uses the relative path from sys.argv[0] to the closest directory in sys.path.
One problem is that sys.path always includes the directory of sys.argv[0] itself, so this relative path ends up being just the filename part of sys.argv[0]. As a quick hack-around, the code below assumes that the sys.argv[0] directory is always the first element of sys.path, and disregards it. This seems unsafe, but safer options are too tedious for my personal code for now.
Any better solutions/suggestions would be greatly appreciated.
import os
import sys
from nose.tools import assert_equal, assert_not_equal
def get_full_class_name(instance):
'''
Returns the fully-qualified class name.
Handles the case where a class is declared in the currently-running script
(where instance.__class__.__module__ would be set to '__main__').
'''
def get_module_name(instance):
def get_path_relative_to_python_path(path):
path = os.path.abspath(path)
python_paths = [os.path.abspath(p) for p in sys.path]
assert_equal(python_paths[0],
os.path.split(os.path.abspath(sys.argv[0]))[0])
python_paths = python_paths[1:]
min_relpath_length = len(path)
result = None
for python_path in python_paths:
relpath = os.path.relpath(path, python_path)
if len(relpath) < min_relpath_length:
min_relpath_length = len(relpath)
result = os.path.join(os.path.split(python_path)[-1],
relpath)
if result is None:
raise ValueError("Path {} doesn't seem to be in the "
"PYTHONPATH.".format(path))
else:
return result
if instance.__class__.__module__ == '__main__':
script_path = os.path.abspath(sys.argv[0])
relative_path = get_path_relative_to_python_path(script_path)
relative_path = relative_path.split(os.sep)
assert_not_equal(relative_path[0], '')
assert_equal(os.path.splitext(relative_path[-1])[1], '.py')
return '.'.join(relative_path[1:-1])
else:
return instance.__class__.__module__
module_name = get_module_name(instance)
return '.'.join([module_name, instance.__class__.__name__])

How to create a shortcut in startmenu using setuptools windows installer

I want to create a start menu or Desktop shortcut for my Python windows installer package. I am trying to follow https://docs.python.org/3.4/distutils/builtdist.html#the-postinstallation-script
Here is my script;
import sys
from os.path import dirname, join, expanduser
pyw_executable = sys.executable.replace('python.exe','pythonw.exe')
script_file = join(dirname(pyw_executable), 'Scripts', 'tklsystem-script.py')
w_dir = expanduser(join('~','lsf_files'))
print(sys.argv)
if sys.argv[1] == '-install':
print('Creating Shortcut')
create_shortcut(
target=pyw_executable,
description='A program to work with L-System Equations',
filename='L-System Tool',
arguments=script_file,
workdir=wdir
)
I also specified this script in scripts setup option, as indicated by aforementioned docs.
Here is the command I use to create my installer;
python setup.py bdist_wininst --install-script tklsystem-post-install.py
After I install my package using created windows installer, I can't find where my shorcut is created, nor I can confirm whether my script run or not?
How can I make setuptools generated windows installer to create desktop or start menu shortcuts?
Like others have commented here and elsewhere the support functions don't seem to work at all (at least not with setuptools). After a good day's worth of searching through various resources I found a way to create at least the Desktop shortcut. I'm sharing my solution (basically an amalgam of code I found here and here). I should add that my case is slightly different from yasar's, because it creates a shortcut to an installed package (i.e. an .exe file in Python's Scripts directory) instead of a script.
In short, I added a post_install function to my setup.py, and then used the Python extensions for Windows to create the shortcut. The location of the Desktop folder is read from the Windows registry (there are other methods for this, but they can be unreliable if the Desktop is at a non-standard location).
#!/usr/bin/env python
import os
import sys
import sysconfig
if sys.platform == 'win32':
from win32com.client import Dispatch
import winreg
def get_reg(name,path):
# Read variable from Windows Registry
# From https://stackoverflow.com/a/35286642
try:
registry_key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, path, 0,
winreg.KEY_READ)
value, regtype = winreg.QueryValueEx(registry_key, name)
winreg.CloseKey(registry_key)
return value
except WindowsError:
return None
def post_install():
# Creates a Desktop shortcut to the installed software
# Package name
packageName = 'mypackage'
# Scripts directory (location of launcher script)
scriptsDir = sysconfig.get_path('scripts')
# Target of shortcut
target = os.path.join(scriptsDir, packageName + '.exe')
# Name of link file
linkName = packageName + '.lnk'
# Read location of Windows desktop folder from registry
regName = 'Desktop'
regPath = r'Software\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders'
desktopFolder = os.path.normpath(get_reg(regName,regPath))
# Path to location of link file
pathLink = os.path.join(desktopFolder, linkName)
shell = Dispatch('WScript.Shell')
shortcut = shell.CreateShortCut(pathLink)
shortcut.Targetpath = target
shortcut.WorkingDirectory = scriptsDir
shortcut.IconLocation = target
shortcut.save()
setup(name='mypackage',
...,
...)
if sys.argv[1] == 'install' and sys.platform == 'win32':
post_install()
Here's a link to a full setup script in which I used this:
https://github.com/KBNLresearch/iromlab/blob/master/setup.py
If you want to confirm whether the script is running or not, you can print to a file instead of the console. Looks like text you print to console in the post-install script won't show up.
Try this:
import sys
from os.path import expanduser, join
pyw_executable = join(sys.prefix, "pythonw.exe")
shortcut_filename = "L-System Toolsss.lnk"
working_dir = expanduser(join('~','lsf_files'))
script_path = join(sys.prefix, "Scripts", "tklsystem-script.py")
if sys.argv[1] == '-install':
# Log output to a file (for test)
f = open(r"C:\test.txt",'w')
print('Creating Shortcut', file=f)
# Get paths to the desktop and start menu
desktop_path = get_special_folder_path("CSIDL_COMMON_DESKTOPDIRECTORY")
startmenu_path = get_special_folder_path("CSIDL_COMMON_STARTMENU")
# Create shortcuts.
for path in [desktop_path, startmenu_path]:
create_shortcut(pyw_executable,
"A program to work with L-System Equations",
join(path, shortcut_filename),
script_path,
working_dir)
At least with Python 3.6.5, 32bit on Windows, setuptools does work for this. But based on the accepted answer, by trial and error I found some issues that may have caused your script to fail to do what you wanted.
create_shortcut does not accept keyword arguments, only positional, so its usage in your code is invalid
You must add a .lnk extension for Windows to recognise the shortcut
I found sys.executable will be the name of the installer executable, not the python executable
As mentioned, you can't see stdout or stderr so you might want to log to a text file. I would suggest also redirecting sys.stdout and sys.stderr to the log file.
(Maybe not relevant) as mentioned in this question there appears to be a bug with the version string generated by bdist_wininst. I used the hexediting hack from an answer there to work around this. The location in the answer is not the same, you have to find the -32 yourself.
Full example script:
import sys
import os
import datetime
global datadir
datadir = os.path.join(get_special_folder_path("CSIDL_APPDATA"), "mymodule")
def main(argv):
if "-install" in argv:
desktop = get_special_folder_path("CSIDL_DESKTOPDIRECTORY")
print("Desktop path: %s" % repr(desktop))
if not os.path.exists(datadir):
os.makedirs(datadir)
dir_created(datadir)
print("Created data directory: %s" % repr(datadir))
else:
print("Data directory already existed at %s" % repr(datadir))
shortcut = os.path.join(desktop, "MyModule.lnk")
if os.path.exists(shortcut):
print("Remove existing shortcut at %s" % repr(shortcut))
os.unlink(shortcut)
print("Creating shortcut at %s...\n" % shortcut)
create_shortcut(
r'C:\Python36\python.exe',
"MyModuleScript",
shortcut,
"",
datadir)
file_created(shortcut)
print("Successfull!")
elif "-remove" in sys.argv:
print("Removing...")
pass
if __name__ == "__main__":
logfile = r'C:\mymodule_install.log' # Fallback location
if os.path.exists(datadir):
logfile = os.path.join(datadir, "install.log")
elif os.environ.get("TEMP") and os.path.exists(os.environ.get("TEMP"),""):
logfile = os.path.join(os.environ.get("TEMP"), "mymodule_install.log")
with open(logfile, 'a+') as f:
f.write("Opened\r\n")
f.write("Ran %s %s at %s" % (sys.executable, " ".join(sys.argv), datetime.datetime.now().isoformat()))
sys.stdout = f
sys.stderr = f
try:
main(sys.argv)
except Exception as e:
raise
f.close()
sys.exit(0)
UPD: on an off chance that the client machine has pywin32 installed, we try in-process creation first. Somewhat cleaner that way.
Here is another take. This assumes the package is called myapp, and that also becomes the executable that you want a shortcut to. Substitute your own package name and your own shortcut text.
Uses a Windows Scripting Host COM class - in process if possible, inside a Powershell command line as a subprocess if not. Tested on Python 3.6+.
from setuptools import setup
from setuptools.command.install import install
import platform, sys, os, site
from os import path, environ
def create_shortcut_under(root, exepath):
# Root is an env variable name -
# either ALLUSERSPROFILE for the all users' Start menu,
# or APPDATA for the current user specific one
profile = environ[root]
linkpath = path.join(profile, "Microsoft", "Windows", "Start Menu", "Programs", "My Python app.lnk")
try:
from win32com.client import Dispatch
from pywintypes import com_error
try:
sh = Dispatch('WScript.Shell')
link = sh.CreateShortcut(linkpath)
link.TargetPath = exepath
link.Save()
return True
except com_error:
return False
except ImportError:
import subprocess
s = "$s=(New-Object -COM WScript.Shell).CreateShortcut('" + linkpath + "');$s.TargetPath='" + exepath + "';$s.Save()"
return subprocess.call(['powershell', s], stdout = subprocess.DEVNULL, stderr = subprocess.DEVNULL) == 0
def create_shortcut(inst):
try:
exepath = path.join(path.dirname(sys.executable), "Scripts", "myapp.exe")
if not path.exists(exepath):
# Support for "pip install --user"
exepath = path.join(path.dirname(site.getusersitepackages()), "Scripts", "myapp.exe")
# If can't modify the global menu, fall back to the
# current user's one
if not create_shortcut_under('ALLUSERSPROFILE', exepath):
create_shortcut_under('APPDATA', exepath)
except:
pass
class my_install(install):
def run(self):
install.run(self)
if platform.system() == 'Windows':
create_shortcut(self)
#...
setup(
#...
cmdclass={'install': my_install},
entry_points={"gui_scripts": ["myapp = myapp.__main__:main"]},

Categories