Get install prefix for module at runtime
Hi all, i am pretty new to distutils (since yesterday) and am struggling with accessing installed data_files. I want to ship a library that benefits from some data_files from another package. My wishes: * distribution should go in 'PREFIX/lib/foo' (site-packages?) * datafiles should go in 'PREFIX/share/foo' * Access to data files from modules My question is: "How to reliably access data files from a module in foo?" A thorough explanation follows. If you can answer the aforementioned question just think tl;dr and skip it ;-) Installation Schemes ==================== There seem to be three different installation schemes regarding setting of this PREFIX. The following examples assume installation of package 'foo' which contains module 'bar' and data file 'baz.data'. Default install (setup.py install) ----------------------------------------- * Packages installation path: 'sys.prefix/lib/pythonX.Y/site-packages/' * Data files installation path: 'sys.prefix/share/' That means that in the aforementioned example the following file system structure will be created upon installation: Package: /usr/lib/python2.5/site-packages/foo/bar.py Data: /usr/share/foo/baz.data Home scheme (setup.py install --home=HOME) --------------------------------------- * Packages installation path: 'HOME/lib/python' * Data files installation path: 'HOME/share/' Resulting structure: Package: HOME/lib/foo/bar.py Data HOME/share/foo/baz.data Prefix scheme (setup.py --prefix=PREFIX) * Package installation path: 'PREFIX/lib/pythonX.Y/site-packages' * Data installation path: 'PREFIX/share/' Resulting structure (for PREFIX==/usr/local) Package: /usr/local/lib/python2.5/site-packages/foo/bar.py Data: /usr/share/foo/baz.data Finding Data files ================== My problem is finding a reliable way to access the data files from within bar.py. Approach 1 - using module.__file__ ---------------------------------- bar_path_elements = bar.__file__.split(os.path.sep) prefix = os.path.join( os.path.sep, *itertools.takewhile(lambda el: el != 'lib')) data_file_path = os.path.join(prefix, 'share', 'foo') * Will this work reliably? * What if the user specifies a different data directory with '--install-data' ? Approach 2 - writing a build.py file at installation time --------------------------------------------------------- The last question made me think, that Approach 1 works for the three standard installation schemes, but fails miserably if the user decides to do anything fancy. As i do not comprehend all the options you can give to 'setup.py install' or know about them i am not sure if there are not many more problems hidden in there. The solution seems to be to create a build.py file that includes this information at installation time and access the data there. My problem is just: HOW? I tried with the following setup.py script, but am unsure how to access the correct options: --- snip --- # -*- coding: UTF-8 -*- #!/usr/bin/env python from __future__ import with_statement from distutils.core import setup import sys import getopt def create_build_file(): try: opts, args = getopt.getopt(sys.argv[1:], "", []) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ What to do here???? except getopt.GetoptError, err: pass with open('lib/mwdb/build.py', 'w') as build_fp: #build_fp.write('# -*- coding: UTF-8 -*-\n') #build_fp.write("LIB_PREFIX = '%s'\n"%(options.lib_prefix)) #build_fp.write("DATA_PREFIX = '%s'\n"%(options.data_prefix)) #build_fp.write("EXEC_PREFIX = '%s'\n"%(options.exec_prefix)) try: if 'install' == sys.argv[1]: create_build_file() except IndexError: pass setup(name='foo', version='0.1', packages=['foo'] package_dir = { '':'lib' }, ... ) --- snip --- The problem i am facing now is: How to parse the options? Do I really have to recreate the complete distutils option parsing logic - and probably fail? All that without optparse! How can i get the aforementioned options? Is the file creation way the correct way to handle this? I really don't know what to do now and am also wondering why nobody had that problem before! Are all other libraries shipped with external data buggy for some installation schemes? How is this normally solved? with kind regards and thanks for reading all this Wolodja Wentland
At 05:38 PM 9/15/2009 +0200, Wolodja Wentland wrote:
How is this normally solved?
Installing the data with one project: http://peak.telecommunity.com/DevCenter/setuptools#including-data-files Retrieving it from the same or a different project: http://peak.telecommunity.com/DevCenter/PkgResources#basic-resource-access (Note: this assumes that the data files in question are static, i.e., constant data which does not change. If by "data" you mean files that are actually changed by the programs involved, this is *not* the way to do it.)
On Tue, Sep 15, 2009 at 6:29 PM, P.J. Eby <pje@telecommunity.com> wrote:
At 05:38 PM 9/15/2009 +0200, Wolodja Wentland wrote:
How is this normally solved?
Installing the data with one project: http://peak.telecommunity.com/DevCenter/setuptools#including-data-files
Retrieving it from the same or a different project: http://peak.telecommunity.com/DevCenter/PkgResources#basic-resource-access
(Note: this assumes that the data files in question are static, i.e., constant data which does not change. If by "data" you mean files that are actually changed by the programs involved, this is *not* the way to do it.)
Note #2: Beware that this also assumes that you use Setuptools instead of Distutils for your project. Things like "include_package_data" are not part of the Distutils project. I'll answer for a Distutils solution as soon as I have some time (hopefully tonight) Tarek
On Tue, Sep 15, 2009 at 18:37 +0200, Tarek Ziadé wrote:
At 05:38 PM 9/15/2009 +0200, Wolodja Wentland wrote:
How is this normally solved?
Note #2: Beware that this also assumes that you use Setuptools instead of Distutils for your project.
I don't actually plan to use setuptools but distutils, although i will definitely consider if there is no other way.
I'll answer for a Distutils solution as soon as I have some time (hopefully tonight) That would be great!
with kind regards Wolodja Wentland
On Tue, Sep 15, 2009 at 17:38 +0200, Wolodja Wentland wrote:
My question is: "How to reliably access data files from a module in foo?"
Approach 2 - writing a build.py file at installation time ---------------------------------------------------------
The last question made me think, that Approach 1 works for the three standard installation schemes, but fails miserably if the user decides to do anything fancy.
I implemented this approach like this: --- snip --- # -*- coding: UTF-8 -*- #!/usr/bin/env python from __future__ import with_statement import distutils.core as core from distutils.command.install import install as _install import sys import getopt import os.path class install(_install): """install command This specific install command will create a file 'build_config.py' which contains information on """ def run(self): with open('lib/foo/build_config.py', 'w') as build_fp: build_fp.write('# -*- coding: UTF-8 -*-\n\n') build_fp.write("DATA_DIR = '%s'\n"%( os.path.join(self.install_data, 'share'))) build_fp.write("LIB_DIR = '%s'\n"%(self.install_lib)) build_fp.write("SCRIPT_DIR = '%s'\n"%(self.install_scripts)) _install.run(self) core.setup(name='foo', version='0.1', description='Foo is Bar and Baz', author='Wolodja Wentland', ... ) --- snip --- Is this a good approach? Is there anything i can do better? with kind regards Wolodja Wentland
On Wed, Sep 16, 2009 at 17:40 +0200, Wolodja Wentland wrote:
On Tue, Sep 15, 2009 at 17:38 +0200, Wolodja Wentland wrote:
My question is: "How to reliably access data files from a module in foo?"
Approach 2 - writing a build.py file at installation time ---------------------------------------------------------
The last question made me think, that Approach 1 works for the three standard installation schemes, but fails miserably if the user decides to do anything fancy.
I implemented this approach like this:
--- snip --- # -*- coding: UTF-8 -*- #!/usr/bin/env python
from __future__ import with_statement
import distutils.core as core from distutils.command.install import install as _install import sys import getopt import os.path
class install(_install): """install command
This specific install command will create a file 'build_config.py' which contains information on """ def run(self): with open('lib/foo/build_config.py', 'w') as build_fp: build_fp.write('# -*- coding: UTF-8 -*-\n\n') build_fp.write("DATA_DIR = '%s'\n"%( os.path.join(self.install_data, 'share'))) build_fp.write("LIB_DIR = '%s'\n"%(self.install_lib)) build_fp.write("SCRIPT_DIR = '%s'\n"%(self.install_scripts)) _install.run(self)
core.setup(name='foo', version='0.1', description='Foo is Bar and Baz', author='Wolodja Wentland', ... ) --- snip ---
I get the following error if i try to install the library: --- snip --- reating new virtualenv environment in /home/bar/.virtualenvs/test New python executable in /home/bar/.virtualenvs/test/bin/python Please make sure you remove any previous custom paths from your /home/bar/.pydistutils.cfg file. Installing setuptools...done..... Unpacking ./dist/foo-0.1.tar.bz2 Running setup.py egg_info for package from file:///home/bar/src/foo/dist/foo-0.1.tar.bz2 Installing collected packages: foo Running setup.py install for foo usage: -c [global_opts] cmd1 [cmd1_opts] [cmd2 [cmd2_opts] ...] or: -c --help [cmd1 cmd2 ...] or: -c --help-commands or: -c cmd --help error: option --single-version-externally-managed not recognized Complete output from command /home/bar/.virtualenvs/test/bin/python -c "import setuptools; __file__='/tmp/pip-CITX4k-build/setup.py'; execfile('/tmp/pip-CITX4k-build/setup.py')" install --single-version-externally-managed --record /tmp/pip-8lWuFa-record/install-record.txt --install-headers /lib/include: usage: -c [global_opts] cmd1 [cmd1_opts] [cmd2 [cmd2_opts] ...] or: -c --help [cmd1 cmd2 ...] or: -c --help-commands or: -c cmd --help error: option --single-version-externally-managed not recognized ---------------------------------------- Command /home/bar/.virtualenvs/test/bin/python -c "import setuptools; __file__='/tmp/pip-CITX4k-build/setup.py'; execfile('/tmp/pip-CITX4k-build/setup.py')" install --single-version-externally-managed --record /tmp/pip-8lWuFa-record/install-record.txt --install-headers /lib/include failed with error code 1 Storing complete log in ./pip-log.txt Complete output from command /home/bar/.virtualenvs/test/bin/python /usr/lib/python2.5/site-packages/pip-0.4-py2.5.egg/pip.py install -E test dist/foo-0.1.tar.bz2 /home/bar/.virtualenvs/test ___VENV_RESTART___: ---------------------------------------- Traceback (most recent call last): File "/usr/bin/pip", line 5, in <module> pkg_resources.run_script('pip==0.4', 'pip') File "/usr/lib/python2.5/site-packages/pkg_resources.py", line 448, in run_script self.require(requires)[0].run_script(script_name, ns) File "/usr/lib/python2.5/site-packages/pkg_resources.py", line 1166, in run_script execfile(script_filename, namespace, namespace) File "/usr/lib/python2.5/site-packages/pip-0.4-py2.5.egg/EGG-INFO/scripts/pip", line 3, in <module> pip.main() File "/usr/lib/python2.5/site-packages/pip-0.4-py2.5.egg/pip.py", line 926, in main return command.main(initial_args, args[1:], options) File "/usr/lib/python2.5/site-packages/pip-0.4-py2.5.egg/pip.py", line 258, in main restart_in_venv(options.venv, site_packages, complete_args) File "/usr/lib/python2.5/site-packages/pip-0.4-py2.5.egg/pip.py", line 1009, in restart_in_venv call_subprocess([python, file] + args + [base, '___VENV_RESTART___']) File "/usr/lib/python2.5/site-packages/pip-0.4-py2.5.egg/pip.py", line 3643, in call_subprocess % (command_desc, proc.returncode)) pip.InstallationError: Command /home/bar/.virtualenvs/test/bin/python /usr/lib/python2.5/site-packages/pip-0.4-py2.5.egg/pip.py install -E test dist/foo-0.1.tar.bz2 /home/bar/.virtualenvs/test ___VENV_RESTART___ failed with error code 1 --- snip --- What is causing this? How do i handle that error? Is this really the first time someone faces that problem? with kind regards Wolodja Wentland P.S And could pip be changed to *only* print the traceback if --verbose/--debug is given? Including that makes it so much harder to find the real error.
On Wed, Sep 16, 2009 at 5:40 PM, Wolodja Wentland <wentland@cl.uni-heidelberg.de> wrote:
On Tue, Sep 15, 2009 at 17:38 +0200, Wolodja Wentland wrote:
My question is: "How to reliably access data files from a module in foo?"
Approach 2 - writing a build.py file at installation time ---------------------------------------------------------
The last question made me think, that Approach 1 works for the three standard installation schemes, but fails miserably if the user decides to do anything fancy.
I implemented this approach like this:
Sorry for the late reply,
Is this a good approach? Is there anything i can do better?
The idea of providing your own install command seems good, except that I find writing all informations in a .py overkill. In the same way the option --record works, you can memorize all installation locations into a simple ConfigParser-like file you add to the files that are installed in Python in your package. Your code can then read the .cfg file it at execution time using __file__ to get your paths. Although with this approach you have to be careful if your package is distributed in some environments (py2exe, setuptools eggs) where you might be located in a zipped file. What are your target platforms ? Tarek
On Thu, Sep 17, 2009 at 12:55 PM, Tarek Ziadé <ziade.tarek@gmail.com> wrote:
On Wed, Sep 16, 2009 at 5:40 PM, Wolodja Wentland <wentland@cl.uni-heidelberg.de> wrote:
On Tue, Sep 15, 2009 at 17:38 +0200, Wolodja Wentland wrote:
My question is: "How to reliably access data files from a module in foo?"
Approach 2 - writing a build.py file at installation time ---------------------------------------------------------
The last question made me think, that Approach 1 works for the three standard installation schemes, but fails miserably if the user decides to do anything fancy.
I implemented this approach like this:
Sorry for the late reply,
Is this a good approach? Is there anything i can do better?
The idea of providing your own install command seems good, except that I find writing all informations in a .py overkill.
In the same way the option --record works, you can memorize all installation locations into a simple ConfigParser-like file you add to the files that are installed in Python in your package.
Your code can then read the .cfg file it at execution time using __file__ to get your paths.
Although with this approach you have to be careful if your package is distributed in some environments (py2exe, setuptools eggs) where you might be located in a zipped file.
On a second thaught, if the file already exists in you package with default values it's simpler : you might be able to alter it on-the-fly by overriding the build_py command instead of the install command and AFAIR, you won't be bothered by setuptools' monkey patches.
On Thu, Sep 17, 2009 at 18:51 +0200, Tarek Ziadé wrote:
you might be able to alter it on-the-fly by overriding the build_py command instead of the install command
That worked perfectly! Thanks again for the help and pointers you gave me. I really appreciate that. The solution i came up with is: --- snip --- class build_py(_build_py): """build_py command This specific build_py command will modify module 'foo.build_config' so that it contains information on installation prefixes afterwards. """ def build_module (self, module, module_file, package): if type(package) is StringType: _package = string.split(package, '.') elif type(package) not in (ListType, TupleType): raise TypeError, \ "'package' must be a string (dot-separated), list, or tuple" if ( module == 'build_info' and len(_package) == 1 and package[0] == 'foo'): iobj = self.distribution.command_obj['install'] with open(module_file, 'w') as module_fp: module_fp.write('# -*- coding: UTF-8 -*-\n\n') module_fp.write("DATA_DIR = '%s'\n"%( os.path.join(iobj.install_data, 'share'))) module_fp.write("LIB_DIR = '%s'\n"%(iobj.install_lib)) module_fp.write("SCRIPT_DIR = '%s'\n"%(iobj.install_scripts)) _build_py.build_module(self, module, module_file, package) --- snip --- I might change the 'detect my module' logic a little because i rely on python short circuit evaluation a bit too much. I have some final questions: 1. Is the distutils API i am using here likely to change? 2. Is there a better way to access install_{lib,scripts,data} than going through the install command object available from distribution? 3. Could you include something like this in distutils? My idea on how to handle this would be to define an additional argument 'foo_bar' for core.setup() which will take a user defined dotted module name in which information like this will be saved. So if a user defines: core.setup( ... foo_bar : 'foo.build_info', ... ) A file lib_prefix/foo/build_info.py will be injected into the library or an already one would be altered according to user defined string templates. Something like this would end module.__file__ hacks once and for all and is IMHO a much cleaner way to advertise this information to libraries/application than trying to take care of this externally. so long Wolodja Wentland
On Thu, Sep 17, 2009 at 21:05 +0200, Wolodja Wentland wrote:
On Thu, Sep 17, 2009 at 18:51 +0200, Tarek Ziadé wrote:
you might be able to alter it on-the-fly by overriding the build_py command instead of the install command
That worked perfectly! Thanks again for the help and pointers you gave me. I really appreciate that.
The solution i came up with is:
--- snip --- class build_py(_build_py): """build_py command
This specific build_py command will modify module 'foo.build_config' so that it contains information on installation prefixes afterwards. """ def build_module (self, module, module_file, package): if type(package) is StringType: _package = string.split(package, '.') elif type(package) not in (ListType, TupleType): raise TypeError, \ "'package' must be a string (dot-separated), list, or tuple"
if ( module == 'build_info' and len(_package) == 1 and package[0] == 'foo'): iobj = self.distribution.command_obj['install']
with open(module_file, 'w') as module_fp: module_fp.write('# -*- coding: UTF-8 -*-\n\n') module_fp.write("DATA_DIR = '%s'\n"%( os.path.join(iobj.install_data, 'share'))) module_fp.write("LIB_DIR = '%s'\n"%(iobj.install_lib)) module_fp.write("SCRIPT_DIR = '%s'\n"%(iobj.install_scripts))
_build_py.build_module(self, module, module_file, package) --- snip ---
I might change the 'detect my module' logic a little because i rely on python short circuit evaluation a bit too much.
I have some final questions:
1. Is the distutils API i am using here likely to change?
2. Is there a better way to access install_{lib,scripts,data} than going through the install command object available from distribution?
3. Could you include something like this in distutils? My idea on how to handle this would be to define an additional argument 'foo_bar' for core.setup() which will take a user defined dotted module name in which information like this will be saved.
So if a user defines:
core.setup( ... foo_bar : 'foo.build_info', ... )
A file lib_prefix/foo/build_info.py will be injected into the library or an already one would be altered according to user defined string templates.
Something like this would end module.__file__ hacks once and for all and is IMHO a much cleaner way to advertise this information to libraries/application than trying to take care of this externally.
Bump. Is this approach a feasible one, or am I better served by using the Distribute API, which I am not familiar at this point? What about the distutils internal I am using here? TIA Wolodja
participants (3)
-
P.J. Eby
-
Tarek Ziadé
-
Wolodja Wentland