Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

I've been planning on packaging a python package recently, and the internet is annoyingly full of guides which are, I think, out of date. They at least suggest quite different things.

I just have a single python file, meant to be treated as an executable (no package at present). There are a whole bunch of tests, but that's obviously separate. Any suggestions on modern best practices welcome!



If it's pure Python, the only packaging file you need is `pyproject.toml`. You can fill that file with packaging metadata per PEP 518 and PEP 621, including using modern build tooling like flit[1] for the build backend and build[2] for the frontend.

With that, you entire package build (for all distribution types) should be reducible to `python -m build`. Here's an example of a full project doing everything with just `pyproject.toml`[3] (FD: my project).

[1]: https://github.com/pypa/flit

[2]: https://github.com/pypa/build

[3]: https://github.com/pypa/pip-audit


> including using modern build tooling like flit[1] for the build backend and build[2] for the frontend.

Or you can use setuptools, which is the package that enables old setup.py builds, as the backend with pyproject.toml. This has the advantage of being mature, unlikely to be abandoned, and possibly some familiarity if you've used it before. Even then, you can use build as the front end build tool.


Yes, setuptools is also perfectly fine. It had some rough edges around PEP 621 support for a while, but those were mostly smoothed out in 2021.

(I'll note that maturity is not strong evidence here: distutils is very mature, but is in the process of being deprecated and removed from Python entirely. I don't think that's likely to happen to setuptools, but the fact that behavioral PEPs now exist for all of these tools means that the decline/abandonment of any poses much less of an ecosystem risk.)


I suppose I meant maturity as in: actively maintained and recommended for use for a long period, which doesn't apply to distutils.

And I should've been more upfront about the real reason for suggesting setuptools: there seem to be a number of build tools that support pyproject.toml, including flit, poetry and setuptools (and I'm sure I've seen at least one other). For me, at least, when I was making a small library recently, it was an overwhelming choice for a part of my project that feels like just admin rather than core business logic. I came close to giving up and just using setup.py with `setup()`. At least setuptools with pyproject.toml is a choice that feels safe; it may not be the best, but it will certainly be good enough that I'm unlikely to regret it later, so I didn't need to spend a lot of time looking at the detailed pros and cons of all the choices.


That's very reasonable! I don't mean to disparage that decision at all: setuptools is rock solid and a very safe choice.


Good luck finding clear documentation on how to write the pyproject file. For something that is supposed to be the way forward there hasn't been much effort to make it easy to implement.

There's also annoyances like the inability to install a script in the search path without implementing it as a module. Something setup.py doesn't require.


The hatch project (PyPA's own tool) has a good guide. https://hatch.pypa.io/latest/config/metadata/#project-metada...

You can also use `hatch new --init` to convert a `setup.py` (whether imperative or setup.cfg-backed) to a `pyproject.toml`.


> Good luck finding clear documentation on how to write the pyproject file. For something that is supposed to be the way forward there hasn't been much effort to make it easy to implement.

PEP 621, which I mentioned, covers the format of `pyproject.toml` in detail. I also linked an example which, to the best of my knowledge, covers all current best practices for that file.

> There's also annoyances like the inability to install a script in the search path without implementing it as a module. Something setup.py doesn't require.

I'm not sure I'm following. A module in Python is just a Python file, so your script is a module. Are you saying that you can't distribute single-module packages with pyproject.toml? Because I don't think that's true.


The PEP in no way explains how to write a usable pyproject for ordinary projects. It's basically just targeted at people developing installers.

I meant package. A directory with an __init__.py. You can't install standalone script.py (or a generated wrapper) as /usr/local/bin/script with a pyproject.


> The PEP in no way explains how to write a usable pyproject for ordinary projects. It's basically just targeted at people developing installers.

Did you look at it[1]?

> I meant package. A directory with an __init__.py. You can't install standalone script.py (or a generated wrapper) as /usr/local/bin/script with a pyproject.

I still don't think I understand what your expectation is here: a `pyproject.toml` is just a metadata specification. The only difference between it and `setup.py` is that the latter is arbitrary code.

There's an old, long deprecated way to use `setup.py`, namely `setup.py install`. But that's been discouraged in favor of `pip install` for years, which behaves precisely the same way with `pyproject.toml`. If you want to install a script into `/usr/local/bin`, `pip install` with a package specified in `pyproject.toml` will work just fine.

[1]: https://peps.python.org/pep-0621/#example


Not doing that either … until there are ways for only the person (me) to hold the key to the revision submission into pip.


You're confusing `pip` with PyPI. `pip` is a package installer; you can use it to install local packages, or packages that are hosted on an index. In this case, we're solely talking about local packages.


For me that is all handled by Poetry. I really like Poetry. But now I've been struggling (off and on) to install my private package from a private repo with PyPI dependencies for a week now...


If you are wanting to release it to pypi as a python package, I would personally use Poetry. But your case- a single pure Python package, is a simple case that won't have many problems like are brought up in the article, whatever tool you use.

If you want a stand alone executable, I haven't found a good, single, cross platform tool for that yet... seems like there is a separate tool for each platform.


> If you want a stand alone executable, I haven't found a good, single, cross platform tool for that yet.

PyInstaller is cross platform, and arguably good.


Nuitka works on Windows, Linux and Mac

https://nuitka.net/


Keep it simple and fashion-proof. Been using setup.py for a one-script package for one or two? decades:

    from setuptools import setup

    setup(
        name          = 'foobar',
        scripts       = ['foo'],  # install a script from current fldr
        # ...
    )
A few years ago I had to start using twine to register and upload it to pypi.


I wouldn't recommend that at all these days. setup.py definitely is not future-proof: it's undergoing deprecation.

Better would be a basic pyproject.toml file along the lines of the following:

    [build-system]
    requires = ["setuptools"]
    build-backend = "setuptools.build_meta"

    [project]
    name = "foobar"
    version = "0.0.1"
    dependencies = [
        "...",
    ]

    [project.scripts]
    foo = "foobar:main"
See: https://setuptools.pypa.io/en/latest/userguide/quickstart.ht...


> it's undergoing deprecation

In the "we would rather people not use this but it's going to stay around for a long time" sense. I strongly doubt it will disappear within the next decade or two. There's a long tail of setup.py-based tools.

Last I checked pyproject.toml only supports the simplest of Python/C extensions. Anything fancy, like --with/--without compilation flags to enable/disable optional support, compiler-specific flags (in my case, to add OpenMP), compile-time code generation (like using yacc/lex), etc. requires a setup.py and a bunch of hacking.


The new style to do that is a PEP 517 build backend.


Yes. That PEP comments:

> The difficulty of interfacing with distutils means that there aren’t many such systems right now, but to give a sense of what we’re thinking about see flit or bento.

Bento is dead. Flit doesn't handle extensions, and points instead to Enscons, which in turn depends on SCons - a build system I have no experience with.

Plus, I sell a source code license. My customers make wheels for their internal PyPI mirrors. I would need to consider how any change might affect them, without the experience to make that judgement.

It seems far easier for me to stay with setup.py than explore a PEP 517 alternative.

So far what I've seen is either people using something like Enscons, or a very complex build system like SciPy's where setuptools just doesn't work. I haven't seen much migration for smaller setup.py systems like mine .. but I also haven't been tracking that well enough.

Any pointers for how that would work?


You should continue to use the setup.py to define the extensions but put all the remaining metadata in the pyproject.toml. The pyproject.toml will reference setuptools as your build backend like https://github.com/jborean93/pyspnego/blob/main/pyproject.to... and the setup.py will reference anything that cannot be expressed in the pyproject.toml like C extensions https://github.com/jborean93/pyspnego/blob/main/setup.py.

The benefits of this is now your project has metadata that tools like pip/poetry/etc can use to figure out what is required (Python project wise) to build your project. For example pip will create an isolated venv with setuptools and Cython for the project I listed when installing from the sdist. You can now also take advantage of `python -m build` to build this project rather than a setuptools specific incantation. This is universal across all build providers so if you want to change to poetry in the future you can will hopefully no build script changes.


I know my project is an oddball. So far I have no required external dependencies, and my optional dependencies are for what the linked-to pages refer to as "native dependencies", which can't be specified by pyproject.toml.

My "setuptools specific incantation" is "pip install" or "pip install -e". I do have a setup.cfg.

The recommendation last year was "If you're building an application, use Poetry. If you're building a library, use Flit", and since my package is a library, I've never really considered poetry.

But! I'm switching from argparse to click - my first required dependency! - so within a month or so I'll be putting my toes into the pyproject.toml waters.

Thank you for your pointers. Isn't there also a way to specify the requirements for building the documentation? I didn't see it in your example.


“Undergoing deprecation” as in not deprecated yet. Great majority of pkgs using it, and Python takes a decade+ to deprecate things.

Also this is a few lines of well understood Python, not exactly a huge investment, right? Does several lines even need to be future proof?

My bet is you’ll need to modify the toml solution more often than the setup.py in the next decade.


Is there a place you recommend where I can learn more about this?


It’s documented, try google, stackoverflow, and reading other packages’ setup.py for tricks.

However the scripts=[], keyword is the key to the case above.


As detailed in the other answers, there are two parts to this: 1) Creating a python package from your project (and possibly share this on pypi), and 2) Making this package available as an end-user application.

For step 2 you can use nuitka or similar, but if your audience is somewhat developer-oriented, you can also propose for them to use pipx: https://github.com/pypa/pipx.


The approach I prefer is to not mess with setuptools etc at all in the first place, and simply make a nice executable package.

e.g. https://github.com/tpapastylianou/self-contained-runnable-py...




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: