You shouldn't invoke setup.py directly
blog.ganssle.io
blog.ganssle.io
I started contributing to Python about a year ago. At that time, all of the results when you google "how to build a python package" refer to the setuptools CLI with setup.py. I put it in my workflow and bam, I've been using it ever since without incident.
Also, it takes only minimal changes to bring a setuptools-based project into the present. You just need to add a `pyproject.toml` file to your project with the following:
[build-system]
requires = ["setuptools >= 40.9.0", "wheel"]
build-backend = "setuptools.build_meta"
Then when it's time to distribute the project, instead of `setup.py sdist` and `setup.py bdist_wheel`, you can just use the official build[1] tool like so: pyproject-build --sdist --wheel
And that's all there is to it. Once that's in place and if you're feeling frisky, you can try taking advantage of new setuptools features that can allow you to eliminate `setup.py` entirely and specify everything in `setup.cfg`[2] so that all your project's information is static and no code needs to be run when installing it, assuming your project doesn't contain any compiled code.As and added benefit, by putting pyproject.toml in your projects now, if you wanted to switch over to another tool like poetry or flit later on, the act of packaging the project doesn't have to change as long as you update the pyproject.toml file with the new build system. The same `pyproject-build` command will work for all three systems.
[1] https://pypi.org/project/build/
[2] https://setuptools.pypa.io/en/latest/userguide/declarative_c...
python -m build
With this invocation it'll produce an sdist, and then it'll build a wheel from that tarball, not from your Git checkout which is much closer to what pip does in the wild. When building both artifacts from a Git checkout, it's possible to mess up packaging making it impossible to produce a wheel out of an sdist (which is necessary in many places, including installs from tarballs that pip tries to build wheels from and downstream packaging in different OS ecosystems).And C/C++ extensions in your package makes things even worse. CMake for extension, setup.py for python of you hope to build cross-platform.
Pybind11 helped a bit but for each python version you have to compile the wheel again, even with manylinux docker image. Imagine learning docker so you can release a wheel!
The other language I often use, C++, is also no better. I am drawn to Rust and Nim lately (not entirely because of these issues).
There was one way to do things. It was simple, and it worked.
Then python 3 wanted to become JavaScript. Async, Unicode, floating point division. Move fast and break things. No one way to do things, it's however you want!
I had to explain python deployments to a novice the other day. What a nightmare.
Python 3.5 to 3.8 just kept drastically changing things. My first experience wasn't pleasant, and for the first time I needed virtual environments and custom apt repositories to install multiple versions of python.
But it finally seems things have settled a bit. Here's hoping the Python cultural revolution settles down, and stops trying to be a trendy language, and instead a dependable one.
Relative to Python, that language which notoriously makes C look like a sloth on training wheels...
In the vast majority of my use cases, I'll take the half second load hit in exchange for that (if it's even that long).
I fully admit that the cult of performance is silly - and anyway, it can sometimes be easier to write performant code in a slightly more high-level language that you're extremely adept at writing. For instance, I'd wager most people could write a more performant web server in Go than they could in C, even if they could write a web server in C.
Not if you have a Python/C extension.
Not if your build step includes code generation (other than Cython).
Not if you have special per-file compilation args.
Also, in the declarative configuration format, how do I enable/disable compile-time flags like "compile with OpenMP" ?
Yes, I have these, and it really irks me reading all of these "you don't need setup.py" when as far as I can tell, I can't.
Is it possible for Cython? Their guide shows using setup.py:
https://cython.readthedocs.io/en/latest/src/tutorial/cython_...
> setuptools will detect at build time whether Cython is installed or not. If Cython is not found setuptools will ignore pyx files.
> To ensure Cython is available, include Cython in the build-requires section of your pyproject.toml:
[build-system]
requires=[..., "cython"]
> Built with pip 10 or later, that declaration is sufficient to include Cython in the build. For broader compatibility, declare the dependency in your setup-requires of setup.cfg:That said, it would be really nice if at least the C-extension part of this worked with the declarative format. My OSS time has been severely curtailed lately and I'm spread way too thin, so I probably won't be able to execute on this, but my long term vision for setuptools has always been that most common build configurations should be achievable using declarative config only, and everything else has setup.py as an escape hatch. This is similar to the way almost all Rust projects are configured with Cargo.toml, but in rare cases you can use build.rs to execute some Rust code as part of the build.
> and as part of the deprecation of distutils, having setuptools installed at all even when you don't import it can change the behavior of import distutils.
How am I supposed to replace the following (a modified version of https://stackoverflow.com/questions/724664/python-distutils-... ):
from setuptools import Extension, setup
from distutils.command.build_ext import build_ext
copt = {'msvc': ['/openmp', '/Ox', '/fp:fast','/favor:INTEL64','/Og'] ,
'mingw32' : ['-fopenmp','-O3','-ffast-math','-march=native'] }
lopt = {'mingw32' : ['-fopenmp'] }
class build_ext_subclass( build_ext ):
def build_extensions(self):
c = self.compiler.compiler_type
if copt.has_key(c):
for e in self.extensions:
e.extra_compile_args = copt[ c ]
if lopt.has_key(c):
for e in self.extensions:
e.extra_link_args = lopt[ c ]
build_ext.build_extensions(self)
If distutils is deprecated, and setuptools causes problems ... what should I do?I know setuptools is a "hot mess". It took hours of puzzling to figure out how to compile things correctly, and includes input from other people who spent hours puzzling things out.
So I am ... sad isn't quite right. Depressed? Down? Feeling like and outsider? .. every time I am reminded that I am not one of the "most people" in "works for most people".
While I (and you) are here, is some way to distinguish between "developer" build environments and "deployment" build environments?
I was looking more at the general topic, trying to figure out how to migrate. I saw this example:
[build-system]
requires = ["setuptools >= 40.6.0", "wheel"]
build-backend = "setuptools.build_meta"
I have two build environments. A development build environment requires Cython (I have hand-written Python/C extensions and Cython extensions). On the other hand, source distributions include the "cythonized" C code from Cython, so people who install and deploy from source don't need Cython. [1]In my setup.py I say:
try:
from Cython.Build import cythonize
except ImportError:
def cythonize(x):
return x
def rename(filename):
return filename.replace(".pyx", ".c")
else:
def rename(filename):
return filename
...
= cythonize([Extension("spam", sources = [rename("foo.pyx"), "bar.c"])])
Is there a way to distinguish these two environments in pyproject.toml?[1] Other than 'setuptools' and the need for a C compiler, my package has no dependencies when building from a source distribution.
EDIT: P.S. A reason to stick with "python setup.py develop" instead of "pip install -e ." is to avoid that frequent nag to "consider upgrading" pip. ;)
The article recommends tox, but how did tox get installed?
I can currently create and activate a new empty virtualenv, "pip install -U pip", clone and cd into my project, and run "python setup.py test". My test dependencies are automatically setup and my preferred runner (pytest) is used. If I change those in the future, the same commands will keep working because I am configuring those in setup.cfg, etc.
If I am now expected to manually "pip install tox" before testing my project and change that in the future if we start using something else, isn't that a step backward to the situation pyproject.toml et al were supposed to solve with needing to manually install specific stuff before using a project?
For a number of reasons (detailed in the article), that thing shouldn't be `setuptools`. Even if the setuptools maintainers wanted to continue supporting this use case, setuptools is uniquely unsuited to working with this, because of the way its hooks don't fire until all its dependencies have been imported already. With `tox` you just need to make sure you have `tox` installed, and you can even specify a minimum version in the `tox.ini` file.
Additionally, tox doesn't need to be installed in your Python environment (in fact, it's an implementation detail that it's Python at all — if it were instead a Go executable, it would work the same). You just need the tool available to be invoked. This means you can install it with pipx, in its own virtual environment, through your system package manager, whatever is most convenient for you.
The Python Foundation needs to adopt or create standard and supported ways of doing things. I spend so much time dealing with issues that should have been solved a long time ago. Special shout out to the PyInstaller devs for providing so much value while constantly fighting to deal with upstream changes.
The Python community has adopted or created a standard way of doing things for the (checks calendar) fourth or fifth time now.
Unfortunately, each response had a different recommendation. I wish I could find that thread again.
We have been talking about how to support customers who want to use python. At least for me it isn't very appealing, seems like it could take a lot of attention to keep up with what's expected.
It seems crazy to me that we don't have this yet.
python setup.py build_ext --inplace
I don't see a valid replacement for this invocation, but fortunately, it's the only one.When I include the library from a project that uses it, the same `pip install' command that brings the library in also performs all the compilation described by my setup.py.
All I'd need to move away from deprecated syntax entirely is a way to build the module when I'm testing it within it's own source directory. Until then I won't be changing anything.
[0]: There should be one-- and preferably only one --obvious way to do it. (from the zen of python)
As someone who actually worked weeks on their spare time, I beg to differ. This is a very complicated problem to solve. And everyone has their own opinion on how things should work, which is goverened by their narrow use case. But you see a "standard" packaging tool needs to be the opposite of narrow use case. The main reason Anacond Inc exists is because they wanted to solve this for data science. Even with them being a relatively big corporation their "solution" is not loved universally, but works ok most of the time.
What has been happening is that someone writes a build tool to solve a specific problem that they have but they neglect to solve all the other ones. Why would they? They’re not being funded.
That’s what caused (and still causes) a lot of the churn in JS too.
Try out Ruby: so many Python syntax decisions make no sense after you see the Ruby way.
I must say though, I do miss Ruby and the syntactic sugar, packaging/dependency system, etc. The transition itself was also very painless. I’m keen on checking out the current state of Ruby and/or Rails 4-5 years later, as I’m sure much has changed!
The two main frameworks I use(d) have relative equivalents in both languages which is super nice (Flask and Sinatra, Rails and Django). Right now I’m in the FastAPI craze, but perhaps I’ll opt for Rails for my next personal project.
There are new niceties and improvements, but bundler is still bundler, everything is still an object, etc.
There have been a few version management tools (rvm, rbenv), but those were all "fine" and still are, AFAICT, and you don't strictly need them.
Meanwhile for inexplicable reasons in pythonland we keep reinventing package/version/environment management.
I had a friend who managed to mess up their local env this morning to the point of screwing up their OS install, and it reminded me of the XKCD on python env[0].
It's at least 3 years old, and things have not improved.
I know this is more rails but the fact that autoloading is even a thing speaks to the wild nature of ruby.
For some of my projects, I produce a "compiled"/bundled executable that takes all the pain out of distribution. Here's my config, maybe it will be useful to you:
https://gitlab.com/stavros/harbormaster/-/blob/master/.gitla...
The poetry author doesn't want to support package version range overrides like yarn does [1]. His call I suppose, but I think it's the one major flaw in the otherwise giant step that poetry takes forward.
Maybe the single most underappreciated thing about Poetry compared to other options is its tendency to fail safe.
While it doesn't really matter for web/scripting use cases, it's an absolute godsend for DS/ML workflows.
But the resolver, oh why does the resolver take so long?
Agreed. Though I would not call it a "nightmare," the python ecosystem and runtime are cumbersome and annoying to work with when compared to some other languages that have put in a lot of thought into DX.
Still, it could be worse.
- for librairy packaging and general dep maangement, poetry is the solution
- for packaging a web project and ship it on the server, shiv is the solution
- for distribution, half the solution is nuitka for compiling the program. Creating the installer is now the hard part
Python's environment difficulties make it exponentially harder for beginners to focus on actual programming concepts. Things like pyenv or virtualenvs or any of those things are blackboxes to beginners.
Go is simple, it's tightly coupled to version control so you can teach git at the same time in a meaningful way, and it's low level enough that CS concepts come up frequently, while being powerful enough that it will scale with a beginner's knowledge.
Go By Example will take a beginner from Hello World to writing a server in a short time, and you can naturally splice in other skills.
I love Python, and the ecosystem is vast, you're right, but it has a serious issue with packaging, delivery, and dependency management. I have almost a decade of software engineering under my belt, and Python environment issues give me a hard time _regularly_. For beginners, it is a major demotivating factor that can be a death sentence for learning.
Here's my simple-minded metric on when this problem can be called "solved":
Get a VPS from GoDaddy.
Deploy your Django app as easily as you can deploy a PHP app, say, Wordpress.
I have written about this before. I love Django. And yet I think that they have done a huge disservice with the development server and DB configuration out of the box. Even for a simple application, going from developing on your desktop to deploying the same application on, as an example, GoDaddy, is in a range between nightmare and impossible. I have personally given up multiple times and just said "Fuck it! Just use WP" even when I really, truly wanted to stay in the Python/Django ecosystem.
Try it. Develop a simple photo album application. Get a VPS from GoDaddy and deploy it.
No Heroku et. al., are not solutions. They are indicative of the problem.
GoDaddy should burn in hell for their shady shit.
C'mon, get some perspective.
I have been using GoDaddy for, well, I forget how long, maybe two decades. I have run websites for multiple companies from their servers. I can't remember a single issue. Not one. Even hosting and managing email. Frankly, I don't know what you are talking about.
I have also used Linode, AWS, Dreamhost, Rackspace and a bunch of others for different kinds of work. Not sure where the hatred for GoDaddy comes from.
I also know a bunch of people using GoDaddy servers. I think people repeat shit just because they think they sound "cool" and yet have no clue what they are talking about. Also, GoDaddy support, on the rare occasions they are needed, has always been top notch. So, yeah, no clue what you are talking about.
i'm not saying that python packaging isn't a mess. but it can be largely mitigated.
I can teach a slightly technical person to deploy WordPress on a shared host with a pre-installed shared LAMP stack. I cannot do this with Docker, not to to the extent it's sustainable without ongoing help from me.
This coming from someone who would truly like to use Python/Django exclusively. I hate, hate, hate telling people to just use WP or go a different route. I do not enjoy touching PHP. And yet...
Python is such a joy to write and such a nightmare to maintain, set up, package and distribute.
Fixed that for you.
In the off-chance you end up depending on that legacy code, repackaging that one or two dependencies shouldn't be that big of a deal.
Probably the biggest stragglers will be build scripts, and as recently as mid-2020, the answer to "how do I build distributable artifacts without using setup.py" was still "you can do it but there's no good definitive answer": https://stackoverflow.com/q/58753970/467366
There's also been a lot of mixed messaging in this space, so I think there's a decent population of people who think that the way to stop invoking setup.py is to stop using setuptools entirely, mixing up "don't invoke setup.py" with "use a backend that doesn't have a setup.py".
But you are right to say that this has gotten a lot better over time and the word is slowly getting out.
I did always find the setup.py approach misguided for several reasons, so it's more of a casual observation I've done that I thankfully rarely encounter it these days.
I feel like Python the language peaked somewhere between 2.4 and 2.7.
Python was much slower than most other languages, but in return you got great readability ("runnable pseudocode") and rapid prototyping. Now, with all the additions to the language (more syntax and features, static typing), those advantages are weakening but you're still not getting the advantages of faster compiled languages.
It seems to me that Go or Nim (or D or Zig or ...) are preferable for readability, and that Rust is more worth one's time if you don't mind the complexity.
- not broken backward compatibility (an astonishing value-destroying move with huge externalities that are still being felt today)
- provided a standard and improved packaging system
There's worse languages, it definitely gets the job done, but I'd rather stick with better languages.
Which in terms or "works for most people, for most use cases", really has that down. And of course uses the same PEP standards as the other tools mentioned.
What benefit does this provide?
(aka "vendoring")
Personally I’m also confused by Ruby. I guess Java is sort of okay, but which languages do people feel have excellent package management?
Ruby's rubygems, node's npm, and the elm package manager all get different things right and wrong, but at least they are a common approach that is somewhat sane. Python is total anarchy. I think most people would disagree with you about .Net, Go, and Javascript today vs python.
With pip you can manually pin versions, or 'freeze' what's currently installed, but then updating them is a manual effort or a third-party service like pyup.io.
> which languages do people feel have excellent package management?
Rust (technically cargo) is probably the best I've seen or used.
The second most important thing they did right was install dependencies locally in the project directory. There is no global state or environment dependency (unless you go out of your way to install some tool globally). Want to do a clean install? rm -rf node_modules and you're done. The only system-wide prerequisite is a single Node installation. No environment variables, nothing.
One mistake they did make was punting on the management of different Node versions, so you have to reach for nvm and any swapping-out has to happen at a global level. Cargo made the right decision to in-house this. However, even then, in my experience it's rarely an issue because Node is so stable.
Please elaborate. I've found Ruby gems to be the simplest packaging system. You make a file containing the gem specification and that's pretty much it. The most annoying part for me was bikeshedding the neatest way to glob all files in the project.
I will say that Ruby gems containing C extensions are really bad. It's cemented in mind the notion that foreign function interfaces must be built into the languages so that compiling code is never necessary.
> which languages do people feel have excellent package management?
Rust packages are really impressive. They just work. It's an isolated ecosystem like all the others, which is weird for a native language like Rust. Dependencies are mostly used at build time, many don't make it to the Linux distrubitons.
I love making libraries but this Python packaging stuff is just so painful. Such a shame, Python has a really nice module system. There's one feature that I love though: editable mode. Finally I can develop and use a library at the same time! I've been trying to accomplish this with git submodules since forever.