How to write a minimally working pyproject.toml file that can install packages? - setuptools

Pip supports the pyproject.toml file but so far all practical usage of the new schema requires a 3rd party tool that auto-generates these files (e.g., poetry and pip). Unlike setup.py which is already human-writeable, pyproject.toml is not (yet).
From setuptools docs,
[build-system]
requires = [
"setuptools >= 40.9.0",
"wheel",
]
build-backend = "setuptools.build_meta"
However, this file does not include package dependencies (as outlined in PEP 621). Pip does support installing packages using pyproject.toml but nowhere does pep specify how to write package dependencies in pyproject.toml for the official build system setuptools.
How do I write package dependencies in pyproject.toml?
Related StackOverflow Questions:
How to init the pyproject.toml file
This question asks for a method to auto-generate pyproject.toml, my question differ because I ask for a human-written pyproject.toml.

my question differ because I ask for a human-written pyproject.toml
First, the pyproject.toml file is always "human-writable".
Then, it is important to know that in this context setuptools and Poetry take the role of what are called "PEP 517 build back-ends", and there are many such back-ends available today, setuptools and Poetry are just two examples of them.
As of today, it seems like most (if not all) of the build back-ends I know of expect their configuration (including dependencies) to be written in pyproject.toml.
PEP 621
There is a standard called PEP 621 that specifies how a project's metadata, including dependencies, should be laid out in the pyproject.toml file.
Here is a list of build back-ends I know of that have support for PEP 621:
enscons
flit_core (see flit)
hatchling (see hatch)
pdm-pep517 (see pdm)
setuptools (experimental support since version 61.0.0)
trampolim
whey
For all PEP 621 compatible build back-ends, the dependencies should be written in pyproject.toml file like in the following:
[project]
name = "Thing"
version = "1.2.3"
# ...
dependencies = [
"SomeLibrary ~= 2.2",
]
References:
https://peps.python.org/pep-0621/
https://setuptools.pypa.io/en/latest/userguide/pyproject_config.html (experimental support for PEP 621 has been added to setuptools version 61.0.0, released 2022-03-24)
setuptools (before version 61.0.0)
In setuptools before version 61.0.0 there is no support for writing the configuration in pyproject.toml (in other words: no PEP 621 support). You have to either write a setup.cfg, or a setup.py, or a combination of both.
My recommendation is to write as much as possible in setup.cfg. Such a setup.cfg could look like this:
[metadata]
name = Thing
version = 1.2.3
[options]
install_requires =
SomeLibrary ~= 2.2
packages = find:
and in most cases the setup.py can be omitted completely or it can be as short as:
import setuptools
setuptools.setup()
References about the dependencies specifically:
https://setuptools.readthedocs.io/en/latest/userguide/dependency_management.html
https://www.python.org/dev/peps/pep-0508/
https://www.python.org/dev/peps/pep-0440/
Again, note that in most cases it is possible to omit the setup.py file entirely, one of the conditions is that the setup.cfg file and a pyproject.toml file are present and contain all the necessary information. Here is an example of pyproject.toml that works well for a setuptools build backend:
[build-system]
build-backend = 'setuptools.build_meta'
requires = [
'setuptools >= 43.0.0',
]
poetry
In poetry everything is defined in pyproject.toml, but it uses poetry-specific sections. In other words, Poetry does not currently use the PEP 621 standard, but there are some plans to move to this standard in the future.
This file can be hand-written. As far as I can tell, there is no strict need to ever explicitly install poetry itself (commands such as pip install and pip wheel can get you far enough).
The pyproject.toml file can be as simple as:
[tool.poetry]
name = 'Thing'
version = '1.2.3'
[tool.poetry.dependencies]
python = '^3.6'
SomeLibrary = '~2.2'
[build-system]
requires = ['poetry-core~=1.0']
build-backend = 'poetry.core.masonry.api'
References:
https://python-poetry.org/docs/pyproject/
https://python-poetry.org/docs/dependency-specification/

Related

Add python project based on pyproject.toml to yocto image

I'm putting together a recipe that's supposed to add amqtt to my image (https://github.com/Yakifo/amqtt). The project only comes with a pyproject.toml but lacks a setup.py. Thus, bitbake is complaining that setup.py cannot be found I'm on branch dunfell and these are the most relevant parts of my recipe:
HOMEPAGE = "https://github.com/Yakifo/amqtt"
SRC_URI = "git://github.com/Yakifo/amqtt;protocol=https"
SRCREV = "4beb912c2a0d58d66140ce68b6a31991c2c48b30"
S = "${WORKDIR}/git"
inherit setuptools3 pypi distutils
Your input is highly appreciated.
As of Yocto Release 4.0 (Kirkstone), installation of Python packages using Poetry is supported via the python_poetry_core class - Release 4.0 Migration Guide - Python Changes
My particular scenario (using Gatesgarth 3.2.3) doesn't really justify the effort in updating to Kirkstone. Just changing the packaging system is not an option (it's not my package I'm trying to install).
My workaround: I've opted to simply use Poetry (1.2.1, the latest at time of writing) to manually build the .tar.gz. It turns out that Poetry generates a setup.py with the info contained in pyproject.toml. I commit that package archive to my Yocto project repo, and override SRC_URI in the appropriate recipe to use it instead of the version from PyPi.
Note that I did have some issues in getting poetry build to run due to TOML format difference between the version that it was written for (which I was unable to zero in on) and the latest. I had to modify the pyproject.toml a bit because of this.
If the pyproject.toml file is composed in a way that it can be used with setuptools then just creating simple setup.py
from setuptools import setup
setup()
by adding the following to the bitbake recipe fixes the problem:
do_configure:prepend() {
cat > ${S}/setup.py <<-EOF
from setuptools import setup
setup()
EOF
}
More details here
My full bitbake recipe python3-leapseconddata_2.0.0.bb:
HOMEPAGE = "https://pypi.org/project/leapseconddata/"
SUMMARY = "Python Leap Second List."
LICENSE = "GPLv3"
LIC_FILES_CHKSUM = "file://LICENSES/GPL-3.0-only.txt;md5=8da5784ab1c72e63ac74971f88658166"
PYPI_PACKAGE = "leapseconddata"
SRC_URI[sha256sum] = "c72d40f56bf7a1a98ee0c0c12ea2fca76a38a5cb7239f8e182f13e639436992d"
inherit pypi setuptools3
do_configure:prepend() {
cat > ${S}/setup.py <<-EOF
from setuptools import setup
setup()
EOF
}
I switched to the pypi installer in the end. The most important parts are:
PYPI_PACKAGE = "amqtt"
inherit pypi setuptools3

Poetry: How to publish project packages targeting multiple Python versions?

I have one project I'd like to publish as packages targeting two Python versions (3.6 and 3.8).
What I understand:
How to install and activate different python versions using pyenv.
How to get poetry to create virtual environments that correspond to the chosen Python version.
How to setup pyproject.toml to specify the python version, manage dependencies, and publish a package using this configuration.
What I do not understand: how can I publish the same package for more than one Python version? I can't be the only one with this use-case right?
Does need two pyproject.toml files? (one for each python version and set of corresponding dependencies...)
Are there established ways of doing this with Poetry, or are other tools/workflows necessary?
Edit
Doing a bit more digging, I found this https://python-poetry.org/docs/versions/#multiple-constraints-dependencies which looks like it might be relevant.
Here's the example at the link above.
[tool.poetry.dependencies]
foo = [
{version = "<=1.9", python = "^2.7"},
{version = "^2.0", python = "^3.4"}
]
I've also found you can specify the Python version using poetry add like this...
poetry add cleo --python 3.6.10
Which adds dependencies in pyproject.toml like this...
cleo = {version = "^0.8.1", python = "3.6.10"}
Going to experiment and see if this works.
You probably need something like that in your pyproject.toml:
[tool.poetry.dependencies]
python = '3.6 || 3.8'
But I am not sure on the exact notation, it's a bit vague.
It seems to generate a setup.py with the following:
'>=3.6, !=2.7.*, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.7.*'
So that would include 3.9, 3.10, etc. and this is incorrect.
No. You don't need to create multiple pyproject.toml files or otherwise create separate workflows for each Python version you're targeting (for this specific situation targeting similar versions at least).
You can simply use dependency syntax to say you want to target >=3.6<4.0 like this...
[tool.poetry.dependencies]
python = '^3.6'
And then add dependencies similarly...
poetry add <dependency> python ^3.6
Which results in something like this...
[tool.poetry.dependencies]
python = '^3.6'
cleo = {version = "^0.8.1", python = "^3.6"}
pyyaml = {version = "^5.4.1", python = "^3.6"}
...
This worked, though I further went and made some of dependencies less specific to avoid incompatibilities on certain hosts.
pyyaml = {version = "^5.0", python = "^3.6"}
...

Cleanly handling /usr/local/ with Swift package manager and libevent

I have 2 dependencies in my project libevent and libressl. Both of which are installed locally ( respectively under /usr/local/include and /usr/local/opt/libressl/include )
What I am looking for to achieve is for SPM to automatically understand to search in those directories.
I know I can pass flags to swift build to achieve this; but my ultimate goal is that I can properly generate xcode projects from the command line without having to constantly add custom build flags in Xcode.
I'm pretty sure it is possible, since I do not have to enter the custom settings for PostgreSQL.
Swift-tools version is at 4.0.x
Package.swift for reference:
// swift-tools-version:4.0
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "CEvent",
providers: [
.brew(["libevent"]),
.apt(["libevent-dev"])
],
products: [
// Products define the executables and libraries produced by a package, and make them visible to other packages.
.library(
name: "CEvent",
targets: ["CEvent"]),
],
dependencies: [
],
targets: [
.target(
name: "CEvent",
dependencies: []
),
]
)
Module map:
module CEvent [system] {
header "shim.h"
link "event"
export *
}
And my current build script ( build.sh ):
#!/usr/local/bin/fish
swift build -Xcc -O0 -Xcc -fblocks -Xswiftc -lbcrypt -Xswiftc -I/usr/local/include -Xswiftc -L/usr/local/lib -Xswiftc -ltls -Xswiftc -lcrypto -Xswiftc -lssl -Xswiftc -L/usr/local/opt/postgresql/lib -Xswi$
As for the reason that I want this. If I add/update/remove dependencies in swift I want to generate a new xcode project, and not have to fix its settings on respective build machines; as well as apt/ubuntu /usr/lib instead.
What you've found out, and documented in your answer, is a good start but not the full story. Yes, SwiftPM uses pkg-config to determine where certain libraries are installed. Yes, SwiftPM uses the pkgConfig name which it'll pass on to pkg-config. However the search paths are a bit more involved. On macOS it uses the following list as a base search path:
/usr/local/lib/pkgconfig
/usr/local/share/pkgconfig
/usr/lib/pkgconfig
/usr/share/pkgconfig
PKG_CONFIG_PATH environment variable
However SwiftPM doesn't use the pkg-config command, but instead parses .pc files directly. By setting the pkgConfig parameter on your package, it knows what filename to look for in the paths listed above. And, for the example in your answer, the story stops here. If there's a libevent.pc file found it parses that file, and any flags returned are passed on to the compiler and linker.
However if you were to define package providers, e.g.:
providers: [
.Brew("libsodium"),
.Apt("libsodium-dev")
]
Then SwiftPM adds additional search paths depending on the package provider for the platform it is building for. Continuing the example of macOS, SwiftPM will run brew --prefix. If this returns a path, the following path is added as a additional search path:
[brewPrefix]/opt/[packageName]/lib/pkgconfig
In my example of libsodium, SwiftPM is now able to infer the location of the library without requiring brew link or symlinks at all. In my verbose build output it lists the libsodium library path in my cellar: -L/usr/local/Cellar/libsodium/1.0.11/lib.
Alright so the thing I ignored from analysing other projects ( IBM-Swift/CLibpq in particular ) seems to be making use of the tool pkg-config which is not something I personally ever touched before.
pkg-config looks in /usr/lib/pkgconfig /usr/share/pkgconfig and the local variants for config files used in during the build process.
Inside Package.swift, after the name parameter you need to insert something for example:
let package = Package(
name: "CEvent",
pkgConfig: "libevent",
Some caveats I discovered with this:
The bcrypt library I am using does not have a full fletched install or build inside the makefile so I compiled it using the new options found in swift4 PM instead found here: BCrypt example on github and the Swift docs for more help here: SPM API Redesign
LibreSSL found in Homebrew will not install its pkgconfig on the system; so it is easiest or in my eyes best maintenance wise to either manually add it or to compile LibreSSL-portable from source.
Overall great learning experience for me today.

How to install local tar file dependencies together with dependencies from CPAN?

Let's assume there is a distribution "Example" that is packed in example-1.0.tar.gz which contains a Makefile.PL that points to all dependencies. After extracting, "Example" could be easily installed (including all its dependencies) by calling cpanm . if all its dependencies were available on CPAN.
How do I install a simialar file (let's call it example-2.0.tar.gz) if I am given a folder "deps" containing most of the distribution's dependencies
dependency-1.tar.gz
dependency-2.tar.gz
...
dependency-N.tar.gz
where N is a huge number?
Further assumptions are:
dependency-*tar.gz files have to be preferred over the available CPAN modules
some dependencies are not available on CPAN but only in this "deps" folder
the folder "deps" is incomplete and further dependencies have to be installed from CPAN.
Is there a simple way to install example-2.0.tar.gz?
I would like to know if there is some command line like cpanm --use-local-deps=~/deps example-2.0.tar.gz which does not need further workarounds.
You could use this tool http://search.cpan.org/~bingos/CPANPLUS-0.9152/bin/cpan2dist . It lets you specify filters to define what modules are loaded locally and what is fetched from CPAN. It's part of the CPANPLUS module, which should cone with newer versions of Perl I think.

Defining required packages and versions for a perl project

I am familiar with using package.json with node.js, Gemfile for Ruby, Podfile for Objective-C, et al.
What is the equivalent file for Perl and what is the syntax used?
I've installed a couple packages using cpanm and would like to save the package names and version in a single file that can be executed by team members.
For simple use cases, writing a cpanfile is a good choice. A sample file might look like
requires 'Marpa::R2', '2.078';
requires 'String::Escape', '2010.002';
requires 'Moo', '1.003001';
requires 'Eval::Closure', '0.11';
on test => sub {
requires 'Test::More', '0.98';
};
That is, it's actually a Perl script, not a data format. The dependencies can then be installed like
$ cd /path/to/your/module
$ cpanm --installdeps .
This does not install your module! But it makes sure that all dependencies are satisfied, so we can do:
use lib '/path/to/your-module/lib'; # add the location as a module search root
use Your::Module; # works! yay
This is usually sufficient e.g. for a git repository which you want others to tinker with.
If you want to create a tarball that can be distributed and installed easily, I'd recommend Dist::Zilla (although it's geared towards CPAN releases). Instead of a cpanfile we use a dist.ini:
name = Your-Module
version = 1.2.3
author = Your Self <you#example.com>
license = GPL_3
copyright_holder = Your Self
[#Basic]
[Prereqs]
Marpa::R2 = 2.078
String::Escape = 2010.002
Moo = 1.003001
Eval::Closure = 0.11
[Prereqs / TestRequires]
Test::More = 0.98
Then:
$ dzil test # sanity checks, and runs your tests
$ dzil build # creates a tarball
Dist::Zilla takes care of creating a Makefile.PL and other infrastructure that is needed to install the module.
You can then distribute that tarball, and install it like cpanm Your-Module-1.2.3.tar.gz. Dependencies are resolved, your packages are copied to a permanent location, and you can now use Your::Module in any script without having to specify the location.
Note that you should adhere to the standard directory layout for Perl modules:
./
lib/
Your/
Module.pm # package Your::Module
Module/
Helper.pm # package Your::Module::Helper
t/ # tests to verify the module works on the target syste,
foo.t
bar.t
xt/ # optional: Author tests that are not run on installation
baz.t
bin/ # optional: scripts that will later end up in the target system's $PATH
command-line-tool
Makefile.PL usually (along with a few other files; Perl has had packages for longer then any of the other languages you mention and suffers from a bit of inelegance here).
Module Starter is a sensible way to start writing a package. It has a getting started guide.