I'm packaging some python packages using a well known third party packaging system, and I'm encountering an issue with the way entry points are created.
When I install an entry point on my machine, the entry point will contain a shebang pointed at whatever python interpreter, like so:
in /home/me/development/test/setup.py
from setuptools import setup
setup(
entry_points={
"console_scripts": [
'some-entry-point = test:main',
]
}
)
in /home/me/.virtualenvs/test/bin/some-entry-point:
#!/home/me/.virtualenvs/test/bin/python
# EASY-INSTALL-ENTRY-SCRIPT: 'test==1.0.0','console_scripts','some-entry-point'
__requires__ = 'test==1.0.0'
import sys
from pkg_resources import load_entry_point
sys.exit(
load_entry_point('test==1.0.0', 'console_scripts', 'some-entry-point')()
)
As you can see, the entry point boilerplate contains a hard-coded path to the python interpreter that's in the virtual environment that I'm using to create my third party package.
Installing this entry point using my third-party packaging system results in the entry point being installed on the machine. However, with this hard-coded reference to a python interpreter which doesn't exist on the target machine, the user must run python /path/to/some-entry-point.
The shebang makes this pretty unportable. (which isn't a design goal of virtualenv for sure; but I just need to MAKE it a little more portable here.)
I'd rather not resort to crazed find/xargs/sed commands. (Although that's my fallback.)
Is there some way that I can change the interpreter path after the shebang using setuptools flags or configs?
You can customize the console_scripts' shebang line by setting 'sys.executable' (learned this from a debian bug report). That is to say...
sys.executable = '/bin/custom_python'
setup(
entry_points={
'console_scripts': [
... etc...
]
}
)
Better though would be to include the 'execute' argument when building...
setup(
entry_points={
'console_scripts': [
... etc...
]
},
options={
'build_scripts': {
'executable': '/bin/custom_python',
},
}
)
For future reference for someone who wants to do this at runtime without modifying the setup.py, it's possible to pass the interpreter path to setup.py build via pip with:
$ ./venv/bin/pip install --global-option=build \
--global-option='--executable=/bin/custom_python' .
...
$ head -1 ./venv/bin/some-entry-point
#!/bin/custom_python
Simply change the shebang of your setup.py to match the python you want your entry points to use:
#!/bin/custom_python
(I tried #damian answer but not working for me, maybe the setuptools version on Debian Jessie is too old)
Related
I wrote an application with pyqt5 and packaged it using python setup.py sdist to make it available on PyPi the app runs well on Linux distributions but there is a problem with windows when i run it ,it does open a console then the actual app shows up is there any way to fix it ?
setup.py
from distutils.core import setup
from setuptools import setup
setup(
name="subtodl",
packages = ['Scripts'],
version="0.0.1",
license='MIT',
description = 'a handy tools to download subtitle ',
author = 'mehdi',
install_requires=[
"pyqt5; platform_system=='Windows'",
"beautifulsoup4",
"lxml",
"PyQt5==5.11.3 ;platform_system=='Linux'",
"requests",
"wget",
],
classifiers=[
"Programming Language :: Python :: 3.6",
],
entry_points={
'console_scripts': [
'subtodl= Scripts.subtodlmain:main',
],
},
)
Instead of 'console_scripts', use 'gui_scripts'.
Two groups of entry points have special significance in packaging: console_scripts and gui_scripts.
[...]
The difference between console_scripts and gui_scripts only affects Windows systems. console_scripts are wrapped in a console executable, so they are attached to a console and can use sys.stdin, sys.stdout and sys.stderr for input and output. gui_scripts are wrapped in a GUI executable, so they can be started without a console, but cannot use standard streams unless application code redirects them. Other platforms do not have the same distinction.
-- The packaging guide
In order to make pip or jupyter available from command-line on Windows no matter the current working directory with just pip ... or jupyter ..., Python on Windows seems to use this method:
put C:\Python37\Scripts in the PATH
create a small 100KB .exe file C:\Python37\Scripts\pip.exe or jupyter.exe that probably does not much more than calling a Python script with the interpreter (I guess?)
Then doing pip ... or jupyter ... in command-line works, no matter the current directory.
Question: how can I create a similar 100KB mycommand.exe that I could put in a directory which is in the PATH, so that I could do mycommand from anywhere in command-line?
Note: I'm not speaking about pyinstaller, cxfreeze, etc. (that I already know and have used before); I don't think these C:\Python37\Scripts\ exe files use this.
Assuming you are using setuptools to package this application, you need to create a console_scripts entry point as documented here:
https://packaging.python.org/guides/distributing-packages-using-setuptools/#console-scripts
For a single top-level module it could look like the following:
setup.py
#!/usr/bin/env python3
import setuptools
setuptools.setup(
py_modules=['my_top_level_module'],
entry_points={
'console_scripts': [
'mycommand = my_top_level_module:my_main_function',
],
},
# ...
name='MyProject',
version='1.0.0',
)
my_top_level_module.py
#!/usr/bin/env python3
def my_main_function():
print("Hi!")
if __name__ == '__main__':
my_main_function()
And then install it with a command such as:
path/to/pythonX.Y -m pip install path/to/MyProject
I recently noticed that I am unable to install my own Python packages. I was getting an error that indicated that a package containing Python modules was invalid. So, I updated my setup.py and removed some elements, this is what I have now:
from setuptools import setup
setup(
name='project',
version='0.3.0',
packages=['project'],
license='GPL',
#zip_safe=False,
#include_package_data=True,
#package_data = { 'package': [ 'README.txt', '*.py' ] },
install_requires=[
'PyYAML >= 3.11',
'logger >= 0.2.0',
],
entry_points={
'console_scripts': ['project = project:main']
},
)
I removed some elements and called the project project. Essentially, within project, I had a package, libraries, with some Python modules. Prior to removing these lines:
#zip_safe=False,
#include_package_data=True,
#package_data = { 'package': [ 'README.txt', '*.py' ] },
... it was not working recently.
Oddly enough though, this setup.py was working as far as I could tell up until a month ago. That said, after commenting those items out and running python setup.py build, I no longer get the error about the package being invalid, but at the same token, I see that nothing gets installed when running pip install dist/project-0.0.1.tar.gz. Inside the file, built by python setup.py sdist, I do see all the files that I would expect to see. They just don't get installed, so I'm effectively missing all of the packages underneath the root folder (which is everything except init).
What am I missing here?
EDIT:
The solution was:
packages=find_packages(),
The hackish solution for me was to do this:
packages=['project', 'project/libraries', 'project/system', 'project/services'],
For whatever reason, packages was no longer working recursively.
As soon as I did that, voila, it worked. I'll probably circle back to this later as I'm curious what changed.
I have a Python package that I'm distributing on PyPI. I create a script called run_program1 that launches a GUI on the command line.
A snippet of my setup.py file:
setup(
name='my_package',
...
entry_points={
'gui_scripts': [
'run_program1 = program1:start_func',
]
}
)
Unfortunately, the run_program1 executable fails to when installed with Anaconda Python, with an error like this:
This program needs access to the screen. Please run with a Framework build of python, and only when you are logged in on the main display of your Mac.
This issue turns out to be a fundamental issue between Anaconda and setuptools:
https://groups.google.com/a/continuum.io/forum/#!topic/anaconda/9kQreoBIj3A
I'm trying to create an ugly hack to change the environment in the executable that pip creates -- run_program1 -- from #!/Users/***/anaconda2/bin/python to #/usr/bin/env pythonw. I can do this manually after installing on my machine by opening ~/anaconda2/bin/run_program1 and simply replacing the first line. With that edit, the executable works as expected. However, I need to create a hack that will allow me to do this for all users who use pip to install my_package.
I am using this approach to insert custom logic into my setup.py file: https://blog.niteoweb.com/setuptools-run-custom-code-in-setup-py/
class CustomInstallCommand(install):
"""Customized setuptools install command - prints a friendly greeting."""
def run(self):
print "Hello, developer, how are you? :)"
install.run(self)
setup(
...
cmdclass={
'install': CustomInstallCommand,
}, ...)
What I can't figure out is, what should I put into the custom class to change the header in the run_program1 executable? Any ideas of how to approach this?
So I'm trying to put together an auto-update functionality for my standalone OSX Python application, that's built on PyObjC. It works great simply packaging it via py2app, but I'm attemping to freeze it with Esky as part of an effort to implement the update feature.
As far as I can tell it's my setup.py formatting for Esky. I'm not sure exactly how to tell Esky to pass on the name of my .Xib file to py2app. Here's what my direct py2app setup.py looks like, successfully including the required .Xib file for the GUI:
setup.py for Py2app
from setuptools import setup
APP = ['MyApp.py']
DATA_FILES = ['MyApp.xib']
OPTIONS = {'argv_emulation': False, 'packages' : ['PIL']}
setup(
app=APP,
data_files=DATA_FILES,
options={'py2app': OPTIONS},
setup_requires=['py2app'],
iconfile="MyApp.icns"
)
Looking around at other people's posts, it looks like you can pass settings to py2app via the slightly differently structured Esky setup.py, but I can't for the life of me figure out the exact structure of the arguments to pass the .Xib file to py2app, from Esky.
setup.py for Esky
from esky import bdist_esky
from distutils.core import setup
setup(name="MyApp",
version="1.3.3",
iconfile="MyApp.icns",
data_files=['MyApp.xib'],
scripts=["MyApp.py","midheaven.py"],
options={"bdist_esky":{
"includes":["PIL"],
"excludes":['pydoc'],
"freezer_module": "py2app",
"freezer_options": {
"plist": {
'argv_emulation': False,
'packages': ['PIL'],
},
"data_files": ['MyApp.xib'],
},
},
},
)
Everything packages without an error, but of course if I try to run the Esky freeze of the app it crashes right away. I'm positive it's because it's not attaching the .Xib GUI properly. Anyone have experience with this, or ideas on how this should actually be formatted? Would absolutely love to figure this out and have it up on here for posterity.
You are correct esky does something different than what you might expect. Looking in the demo/tutorial folder is what got me on the right path.
setup(name="MyApp",
data_files=[('', ['MyApp.xib']),
('files', ['file1', 'file2']),
('img', glob(r'.\img\*.*'))
]
...
So you have a whole bunch of tuples, where the first entry is the path in your package to include the files and the second is an iterable of files to put there
You can remove the second instance of data_files that you have in the options dict.
Update
Try
from esky.bdist_esky import Executable
executables = [Executable('example_gui.py', icon='myico.ico', gui_only=True,)]
setup(
scripts = executables
...