py2exe and the file system - python

I have a Python app. It loads config files (and various other files) by
doing stuff such as:
_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
CONFIG_DIR = os.path.join(_path, 'conf')
This works fine. However, when I package the app with py2exe, bad things happen:
File "proj\config.pyc", line 8, in <module>
WindowsError: [Error 3] The system cannot find the path specified: 'C:\\proj\
\dist\\library.zip\\conf'
Obviously that's an invalid path... What's a more robust way of doing this? I don't
want to specify absolute paths in the program because it could be placed in different
folders. Should I just say "if it says the folder name is 'library.zip', then go
one more level down to the 'dist' folder"?
Note that I have pretty nested directory hierarchies... for example, I have
a module gui.utils.images, stored in "gui/utils/images.py", and it uses its path
to access "gui/images/ok.png", for example. Right now the py2exe version
would try to access "proj/dist/library.zip/gui/images/ok.png", or something,
which just won't work.

The usual approach to doing this sort of thing (if I understand properly) is this:
check sys.frozen, which py2exe contrives to set, using something like getattr(sys, 'frozen', '')
if that's not set, use the usual method since you're running from source
if it's set, check sys.executable since that will point you to where the .exe is (instead of to where python.exe is, which is what it normally points to). Use something like os.path.dirname(sys.executable) and then join that with your relative paths to find subfolders etc
Example:
frozen = getattr(sys, 'frozen', '')
if not frozen:
# not frozen: in regular python interpreter
approot = os.path.dirname(__file__)
elif frozen in ('dll', 'console_exe', 'windows_exe'):
# py2exe:
approot = os.path.dirname(sys.executable)
elif frozen in ('macosx_app',):
# py2app:
# Notes on how to find stuff on MAC, by an expert (Bob Ippolito):
# http://mail.python.org/pipermail/pythonmac-sig/2004-November/012121.html
approot = os.environ['RESOURCEPATH']
Or some variant thereof... the latter one handles the use of py2app. You could extend this for other builders, if needed.

What do you think about using relative paths for all of the included files? I guess it should be possible to use sys.path.append(".." + os.path.sep + "images") for your example about ok.png, then you could just open("ok.png", "rb"). Using relative paths should fix the issues with the library.zip file that's generated by py2exe, at least that's what it does for me.

Use os.path.dirname(os.path.abspath(sys.argv[0])) from a py2exe app, it'll work the same way from the python script (so you can test without creating the exe every time) and from the exe.
This can be much better than using relative paths because you don't have to worry about where your app (.py or .exe) is running from.

Here's my solution (tested on Windows, Linux and Mac). It also works if the application or Python script is started via a symbolic link.
# Get if frozen
is_frozen = bool( getattr(sys, 'frozen', None) )
# Get path variable
path = sys.path if is_frozen else sys.argv
# Get nonempty first element or raise error
if path and path[0]:
apppath = path[0]
elif is_frozen():
raise RuntimeError('Cannot determine app path because sys.path[0] is empty.')
else:
raise RuntimeError('Cannot determine app path in interpreter mode.')
# Make absolute (eliminate symbolic links)
apppath = os.path.abspath( os.path.realpath(apppath) )
# Split and return
appdir, appname = os.path.split(apppath)

I use the following trick in windows.
When the program is frozen in a py2exe 'executable' (i.e my_application.exe), dirname(__file__) returns the folder where your main python script is running.
Oddly enough, that folder is not the folder containing my_application.exe but my_application.exe itself.
Note that my_application.exe is not a binary file but a compressed folder (you can unzip it) that contains python libs and your compiled scripts.
That's why the os.path.dirname of your __file__ is in fact my_application.exe.
Thus, this code gives you the root directory for your configuration files or images (in case of a frozen application, the folder containing the py2exe executable, otherwise the folder from where your python script is running):
dirname = os.path.dirname(__file__)
if dirname.endswith('.exe'):
dirname = os.path.split(dirname)[0]
Obviously you can use more general, portable methods to detect if the file is frozen as other answers show instead of the endswith method.

Related

Module sys has no _MEIPASS member error on Python 3.6.6

I have been trying to bundle a json file in a python executable created with pyinstaller. After a lot of researching, the solution I found involved making use of the _MEIPASS folder; however, VSCode claims that the sys package has no _MEIPASS member.
The relevant part of my code goes like this:
branches_path = 'bank_branches/bank_branches.json'
if hasattr(sys, "_MEIPASS"):
branches_path = os.path.join(sys._MEIPASS, branches_path)
The code works on the terminal version, as well as on the standalone application, so this is taken care of; however, I'd like to know if there is a solution which works and has no errors associated. If it helps, I'm using Python 3.6.6
I ran into a similar issue when creating an executable using pyinstaller. I had to make two changes to my script in order to get a functional executable.
First, I created this function:
def resource_path(relative_path):
try:
base_path = sys._MEIPASS
except Exception:
base_path = os.path.abspath(".")
return os.path.join(base_path, relative_path)
My script has several classes in it, so I put this at the very end, by itself, so that all classes could reference it. I then replaced any function I had that used
os.getcwd()
- which was probably a bad idea in the first place - with
resource_path()
and for the variable inside of resource_path() I used this function instead:
os.path.dirname(os.path.abspath(__file__))
This function returned what I wanted anyway; the location of THIS file/program that is running.
So, what previously was written like:
filePath = os.getcwd() + "\\my_file.csv"
Now reads as:
filePath = resource_path(os.path.dirname(os.path.abspath(__file__))) + "\\my_file.csv"
Once this was in place, my program compiled correctly and executed as expected, hopefully it can help you as well.

python exe in windows not writng

I have converted my python program to exe using pyinstaller. My exe creates a temporary folder _MEIxxxxx to keep files. In the same folder there is a file which is being changed by the program, but unfortunately it is not happening.
In the program I change the folder to go to above folder:
os.chdir('C:\\Users\\Public')
for foldername in os.listdir():
if foldername.startswith('_MEI'):
myfolder = foldername
os.chdir('C:\\Users\\Public'+myfolder+'\\Quiz')
Thanks in advance.
this doesn't work:
os.chdir('C:\\Users\\Public'+myfolder+'\\Quiz')
because myfolder doesn't contain a \ at start.
Don't hardcode C:\Users\Public, use PUBLIC env. var
And avoid chdir, it's equivalent as a global variable shared between all modules. What if some module needs one current dir, and the other another?
Your attempt looks more like a shell script ported to python cd xxx; ls; .... Break this habit.
Use absolute paths/paths passed as parameter instead. My proposal:
pubdir = os.getenv("PUBLIC")
for foldername in os.listdir(pubdir):
if foldername.startswith('_MEI'):
myfolder = foldername
quizdir = os.path.join(pubdir,myfolder,'Quiz')
# now do something with quizdir
and if you need an absolute directory to run a system call, subprocess functions have a cwd parameter for that. So you can avoid os.chdir 99% of the time.

current working directory mismatch in python

I have imported a existing filesystem folder as the new project folder in eclipse. I have a script which get the current working directory path of the code. I need to change directory location to acccess files in other directory related to it. But It is giving different value when executed from eclipse and from the command line. Location is same in both place. Please help me resolve this issue. Operating system is windows here
import os
print os.getcwd()
os.chdir(os.path.dirname(os.getcwd()))
print os.getcwd()
One result is this
C:\Automation\trunk\Base\TestScripts
C:\Automation\trunk\Base
Other result is this
C:\Automation\trunk\UsefulScripts
C:\Automation\trunk
Second result is the one I expect, and that is where the code is located exactly.
watch out you cannot rely on that. Do the following :
basedir = os.environ.get('PROJECT_LOC', None)
if not basedir:
basedir = sys.path[0] # We are on commandline. sys.path OK
Then use basedir to find your files
Update
you have to specify this variable in the runtime of the interpreter
window->preferences->PyDev->Interpreters->Python Interpreter TAB (environment) There you can specify PROJECT_LOC referring to project_loc by selecting NEW with name PROJECT_LOC and variable (the other button) and selecting project_loc.
For some reasons those variables are not visible in python.
You can check this now with
def read_all_variables():
for key in os.environ.keys():
print ("%30s %s" % (key,os.environ[key]))
PROJECT_LOC should be there now
I have used the sys package instead of os. It works as expected.
import os,sys
currentpath = sys.path[0]
print currentpath
I can able to run it from eclipse and also from command line to get the correct path. Thanks for the help .

Creating an executable python script, with OS commands, to start Blender and run another script

I need a little help creating an executable python OS command for Blender (Windows and Mac). I am setting up a stand-alone blender package for a client to use. I have a python script that imports some data and I want to create an executable python script that runs OS commands to run Blender, run the python script and define the data directories.
This is a generic version of the Windows command I normally run to import the data, using the stand-alone version of Blender:
"C:\Users\username\Desktop\Package\system\blender\blender.exe" "C:\Users\username\Desktop\Package\system\version_data\CurrentVersion.blend" -P "C:\Users\username\Desktop\Package\system\version_data\BlenderScript.py" "C:\Users\username\Desktop\Package\input_data\\" -y
From my research I have worked out that I need to:
import os
make the directories in the command relative
create an executable python file
My python experience is limited, but I believe it may be something like this:
import os
pythonDirectory = os.path.dirname(os.path.abspath(RunThisApp.exe)) # get the current dir of this file (which would be: C:\Users\username\Desktop\Package\)
os.path.join(pathDirectory, "//system\blender\blender.exe" "//system\version_data\CurrentVersion.blend" -P "//system\version_data\BlenderScript.py" "//input_data\\" -y)
However I had a look at this post and was a little fuzzy as to the best way to do this:
Calling an external command in Python
Then I could possibly use PyInstaller to create the python executable files. Which seems to be the simplest method suggested here:
How to make a Python script standalone executable to run without ANY dependency?
http://www.pyinstaller.org/
Am I close to the correct result here? I am guessing my syntax is off. I need to make sure it works for both Windows and Mac.
It should also be noted that the separate python script I run to import data into blender (which I have been using and updating for a couple of years), refers to OS arguments to get the desired path of data to import, so I need to make sure that I maintain that connection. Here is an example:
IMPORT_DATA_FILENAME = sys.argv[4]+'data.txt'
Any advice or input would be greatly appreciated.
Chris Lee
It looks like you have all the files and binaries you want to use included in one folder that you are sharing. If you place the initial script at the top of that directory you should be able to get it's location from argv[0] and calculate the location of the other files you want from that, which should allow the user to move your package folder anywhere they want.
You can use platform.system() to add any system specific variations to the paths.
#!/usr/bin/env python3
import os
from sys import argv
from platform import system
from subprocess import call
# define the package dir as the dir that this script is in
pkg_dir = os.path.abspath(os.path.dirname(argv[0]))
# use a package dir in the users home folder
#pkg_dir = os.path.join(os.path.expanduser('~'), 'Package')
# use a package dir in the users desktop folder
#pkg_dir = os.path.join(os.path.expanduser('~'), 'Desktop', 'Package')
sys_dir = os.path.join(pkg_dir, system())
vers_dir = os.path.join(pkg_dir,'version_data')
blend = os.path.join(vers_dir, 'CurrentVersion.blend')
script = os.path.join(vers_dir, 'BlenderScript.py')
if system() == 'Windows':
blender_exe = os.path.join(sys_dir, 'blender', 'blender.exe')
elif system() == 'Darwin':
blender_exe = os.path.join(sys_dir, 'blender', 'blender.app',
'Contents', 'MacOS', 'blender')
else:
# linux?
blender_exe = os.path.join(sys_dir, 'blender', 'blender')
calllist = [
blender_exe,
blend,
'--python',
script,
]
call(calllist)
Using Sambler's solution I have modified it slightly to got the following solution:
import os
from sys import argv
from platform import system
from subprocess import call
# define the package dir as the dir that this script is in
pkg_dir = os.path.abspath(os.path.dirname(argv[0]))
sys_dir = os.path.join(pkg_dir, 'private_sys', system())
vers_dir = os.path.join(pkg_dir, 'private_sys', '#version_data')
blend = os.path.join(vers_dir, 'CurrentVersion.blend')
script = os.path.join(vers_dir, 'BlenderScript.py')
input = os.path.join(pkg_dir, 'input_data')
if system() == 'Windows':
blender_exe = os.path.join(sys_dir, 'blender', 'blender.exe')
elif system() == 'Darwin':
blender_exe = os.path.join(sys_dir, 'blender', 'blender.app', 'Contents', 'MacOS', 'blender')
calllist = [
blender_exe,
blend,
'--python',
script,
input,
]
call(calllist)
To clarify, within the Package directory the folder structure is as follows:
+---input_data
\---data.txt
\---input.blend
\---private_sys
+---#version_data
\---BlenderScript.py
\---CurrentVersion.blend
+---Darwin
| \---blender
\---Windows
\---blender
Apart from removing the Linux and other suggested variations, I added an input directory. The BlenderScript.py that is called refers to the data.txt and the input.blend files and imports them into the CurrentVersion.blend file. I also moved the version data into a separate directory from the OS specific blender directories and inside the private_sys directory, so that the user doesn't have to see those files.
The last hurdle was lines like this inside BlenderScript.py:
IMPORT_DATA_FILENAME = sys.argv[4]+'data.txt'
The problem was that I was getting an error because the script would end up looking for: "\input_datadata.txt".
I fixed this by changing it to:
IMPORT_DATA_FILENAME = sys.argv[4]+'/data.txt'
Which returns: "\input_data/data.txt". I am assuming this will have the same result in OSX.
Thank you very much for your help. This is an area I am learning more in all the time, as the work I do for the client is far more complicated than 'past me' could have imagined. 'Future me' is going to be very pleased with himself when all this is done.

Accounting for a changing path

In relation to another question, how do you account for paths that may change? For example, if a program is calling a file in the same directory as the program, you can simply use the path ".\foo.py" in *nix. However, apparently Windows likes to have the path hard-coded, e.g. "C:\Python_project\foo.py".
What happens if the path changes? For example, the file may not be on the C: drive but on a thumb drive or external drive that can change the drive letter. The file may still be in the same directory as the program but it won't match the drive letter in the code.
I want the program to be cross-platform, but I expect I may have to use os.name or something to determine which path code block to use.
Simple answer: You work out the absolute path based on the environment.
What you really need is a few pointers. There are various bits of runtime and environment information that you can glean from various places in the standard library (and they certainly help me when I want to deploy an application on windows).
So, first some general things:
os.path - standard library module with lots of cross-platform path manipulation. Your best friend. "Follow the os.path" I once read in a book.
__file__ - The location of the current module.
sys.executable - The location of the running Python.
Now you can fairly much glean anything you want from these three sources. The functions from os.path will help you get around the tree:
os.path.join('path1', 'path2') - join path segments in a cross-platform way
os.path.expanduser('a_path') - find the path a_path in the user's home directory
os.path.abspath('a_path') - convert a relative path to an absolute path
os.path.dirname('a_path') - get the directory that a path is in
many many more...
So combining this, for example:
# script1.py
# Get the path to the script2.py in the same directory
import os
this_script_path = os.path.abspath(__file__)
this_dir_path = os.path.dirname(this_script_path)
script2_path = os.path.join(this_dir_path, 'script2.py')
print script2_path
And running it:
ali#work:~/tmp$ python script1.py
/home/ali/tmp/script2.py
Now for your specific case, it seems you are slightly confused between the concept of a "working directory" and the "directory that a script is in". These can be the same, but they can also be different. For example the "working directory" can be changed, and so functions that use it might be able to find what they are looking for sometimes but not others. subprocess.Popen is an example of this.
If you always pass paths absolutely, you will never get into working directory issues.
If your file is always in the same directory as your program then:
def _isInProductionMode():
""" returns True when running the exe,
False when running from a script, ie development mode.
"""
return (hasattr(sys, "frozen") or # new py2exe
hasattr(sys, "importers") # old py2exe
or imp.is_frozen("__main__")) #tools/freeze
def _getAppDir():
""" returns the directory name of the script or the directory
name of the exe
"""
if _isInProductionMode():
return os.path.dirname(sys.executable)
return os.path.dirname(__file__)
should work. Also, I've used py2exe for my own application, and haven't tested it with other exe conversion apps.
What -- specifically -- do you mean by "calling a file...foo.py"?
Import? If so, the path is totally outside of your program. Set the PYTHONPATH environment variable with . or c:\ or whatever at the shell level. You can, for example, write 2-line shell scripts to set an environment variable and run Python.
Windows
SET PYTHONPATH=C:\path\to\library
python myapp.py
Linux
export PYTHONPATH=./relative/path
python myapp.py
Execfile? Consider using import.
Read and Eval? Consider using import.
If the PYTHONPATH is too complicated, then put your module in the Python lib/site-packages directory, where it's put onto the PYTHONPATH by default for you.
I figured out by using os.getcwd(). I also learned about using os.path.join to automatically determine the correct path format based on the OS. Here's the code:
def openNewRecord(self, event): # wxGlade: CharSheet.<event_handler>
"""Create a new, blank record sheet."""
path = os.getcwd()
subprocess.Popen(os.path.join(path, "TW2K_char_rec_sheet.py"), shell=True).stdout
It appears to be working. Thanks for the ideas.

Categories