Packaging/Python: Difference between revisions
imported>Milahu cleanup No module named 'pkg_resources' |
Phanirithvij (talk | contribs) m distutils deprecated in py 3.12 notice, fix link |
||
(29 intermediate revisions by 8 users not shown) | |||
Line 14: | Line 14: | ||
</syntaxHighlight> | </syntaxHighlight> | ||
You can now run <code>nix-shell</code> and it will drop you in a shell similar to the <code>python setup.py develop</code> mode which uses the local code in <tt>./path/to/source</tt> as input. <code>propagatedBuildInputs</code> will contain the packages you need in your project. | You can now run <code>nix-shell</code> and it will drop you in a shell similar to the <code>python setup.py develop</code> mode which uses the local code in <tt>./path/to/source</tt> as input. <code>propagatedBuildInputs</code> will contain the packages you need in your project. | ||
After you've finished developing you can replace the relative path with <code>fetchFromGitHub { ... }</code> or <code>[https://github.com/NixOS/nixpkgs/blob/master/pkgs/ | After you've finished developing you can replace the relative path with <code>fetchFromGitHub { ... }</code> or <code>[https://github.com/NixOS/nixpkgs/blob/master/pkgs/build-support/fetchpypi/default.nix fetchPypi] { ... }</code>. | ||
== Pip and Virtualenv enabled nix-shell == | == Pip and Virtualenv enabled nix-shell == | ||
Line 22: | Line 22: | ||
For a local working python environment you can use the following <code>shell.nix</code><ref>https://groups.google.com/forum/#!topic/nix-devel/3qPfwCAV3GE</ref>. | For a local working python environment you can use the following <code>shell.nix</code><ref>https://groups.google.com/forum/#!topic/nix-devel/3qPfwCAV3GE</ref>. | ||
<syntaxHighlight lang=nix> | <syntaxHighlight lang=nix> | ||
{ pkgs ? import <nixpkgs> { } }: | |||
let | |||
pythonEnv = pkgs.python3.withPackages(ps: [ ]); | |||
in | |||
pkgs.mkShell { | |||
packages = [ | |||
pythonEnv | |||
]; | ]; | ||
} | } | ||
</syntaxHighlight> | </syntaxHighlight> | ||
Line 73: | Line 62: | ||
Please report such issues at https://github.com/NixOS/nixpkgs/issues | Please report such issues at https://github.com/NixOS/nixpkgs/issues | ||
== setup.py == | === HTTP 404 with fetchPypi === | ||
example error: | |||
<blockquote> | |||
curl: (22) The requested URL returned error: 404<br> | |||
error: cannot download stt-1.2.0.tar.gz from any mirror | |||
</blockquote> | |||
when we look at https://pypi.org/project/stt/#files we see only <code>*.whl</code> files: | |||
* <code>stt-1.2.0-cp310-cp310-win_amd64.whl</code> | |||
* <code>stt-1.2.0-cp310-cp310-manylinux_2_24_x86_64.whl</code> | |||
* <code>stt-1.2.0-cp310-cp310-macosx_10_10_universal2.whl</code> | |||
* ... | |||
this means, this is a binary release, so we have two options: | |||
# build from source | |||
# install binary release | |||
==== build from source ==== | |||
replace this | |||
<syntaxHighlight lang=nix> | |||
buildPythonPackage { | |||
pname = "TODO"; | |||
version = "TODO"; | |||
src = fetchPypi { | |||
inherit pname version; | |||
sha256 = ""; # TODO | |||
}; | |||
</syntaxHighlight> | |||
with this | |||
<syntaxHighlight lang=nix> | |||
buildPythonPackage { | |||
pname = "TODO"; | |||
version = "TODO"; | |||
src = fetchFromGitHub { | |||
owner = "TODO"; | |||
repo = "TODO"; | |||
rev = "v${version}"; | |||
sha256 = ""; # TODO | |||
}; | |||
</syntaxHighlight> | |||
==== install binary release ==== | |||
replace this | |||
<syntaxHighlight lang=nix> | |||
buildPythonPackage { | |||
pname = "TODO"; | |||
version = "TODO"; | |||
src = fetchPypi { | |||
inherit pname version; | |||
sha256 = ""; # TODO | |||
}; | |||
</syntaxHighlight> | |||
with this | |||
<syntaxHighlight lang=nix> | |||
buildPythonPackage { | |||
pname = "TODO"; | |||
version = "TODO"; | |||
format = "wheel"; | |||
src = fetchPypi rec { | |||
inherit pname version format; | |||
sha256 = ""; # TODO | |||
dist = python; | |||
python = "py3"; | |||
#abi = "none"; | |||
#platform = "any"; | |||
}; | |||
</syntaxHighlight> | |||
... or use <code>fetchurl</code> to download the <code>*.whl</code> file directly.<br> | |||
reference: [https://github.com/NixOS/nixpkgs/blob/master/pkgs/build-support/fetchpypi/default.nix fetchPypi implementation] | |||
== Fix Missing <code>setup.py</code> == | |||
The <code>setup.py</code> file is required for <code>buildPythonPackage</code>, | |||
but it's missing in some packages. | |||
If you get the following error, you need to one of the workarounds below. | |||
<pre> | |||
FileNotFoundError: [Errno 2] No such file or directory: 'setup.py' | |||
</pre> | |||
If the package has a <code>pyproject.toml</code> file, set | |||
<syntaxHighlight lang= | <syntaxHighlight lang=nix> | ||
buildPythonPackage { | |||
format = "pyproject"; | |||
} | |||
</syntaxHighlight> | |||
If both <code>setup.py</code> and <code>pyproject.toml</code> are missing, | |||
you have to add one of these files. | |||
'''Note:''' sometimes you will be able to find <code>pyproject.toml</code> in the source for a package despite it not being present in a <code>.whl</code> file. You can inspect the contents of a <code>.whl</code> file by downloading it from PyPi and then extracting it with <code>nix-shell -p python311Packages.wheel --command wheel unpack path/to/package.whl</code>. | |||
For example, you can create the <code>setup.py</code> in the <code>preBuild</code> phase. | |||
<syntaxHighlight lang=nix> | |||
buildPythonPackage { | |||
preBuild = '' | |||
cat > setup.py << EOF | |||
from setuptools import setup | from setuptools import setup | ||
Line 97: | Line 192: | ||
], | ], | ||
entry_points={ | entry_points={ | ||
# example: file some_module.py -> function main | |||
#'console_scripts': ['someprogram=some_module:main'] | #'console_scripts': ['someprogram=some_module:main'] | ||
}, | }, | ||
) | ) | ||
EOF | |||
''; | |||
} | |||
</syntaxHighlight> | </syntaxHighlight> | ||
More info about the <code>setup.py</code> can be found [https://docs.python.org/3.11/distutils/setupscript.html here]. (<b>note:</b> from python 3.12 onwards, distutils is deprecated see https://docs.python.org/3.11/distutils/index.html) | |||
<code>scripts</code> is useful for self-contained python scripts with no local imports. | |||
If a python script has local imports, | |||
for example <code>from .some_module import some_function</code>, | |||
either include all files in the <code>scripts</code> array, | |||
or add only the entry function to <code>entry_points</code>. | |||
In this example, <code>someprogram.py</code> would be installed as <code>$out/bin/someprogram.py</code>.<br> | |||
To rename the binary, for example to remove the <code>.py</code> file extension, you can use <code>postInstall</code> | To rename the binary, for example to remove the <code>.py</code> file extension, you can use <code>postInstall</code> | ||
Line 129: | Line 236: | ||
== Automatic packaging == | == Automatic packaging == | ||
TODO https://github.com/nix-community/poetry2nix | TODO https://github.com/nix-community/poetry2nix - 400 stars | ||
TODO https://github.com/nix-community/pip2nix - 100 stars | |||
TODO https://github.com/nix-community/pypi2nix - 200 stars - archived | |||
== Testing via this command is deprecated == | |||
In most cases, tests will pass anyway and you can ignore the warning. | |||
In some cases, tests will fail, for example: | |||
<blockquote> | |||
running test<br> | |||
WARNING: Testing via this command is deprecated and will be removed in a future version. Users looking for a generic test entry point independent of test runner are encouraged to use tox.<br> | |||
[ ... ]<br> | |||
TypeError: some_function() missing 1 required positional argument: 'some_argument' | |||
</blockquote> | |||
quick fix: run tests with python's [https://docs.python.org/3/library/unittest.html unittest] module | |||
<syntaxHighlight lang=nix> | |||
checkPhase = '' | |||
runHook preCheck | |||
${python3.interpreter} -m unittest | |||
runHook postCheck | |||
''; | |||
</syntaxHighlight> | |||
== See also == | == See also == | ||
* [https://github.com/NixOS/nixpkgs/blob/master/pkgs/development/interpreters/python/mk-python-derivation.nix buildPythonPackage implementation] | * [https://github.com/NixOS/nixpkgs/blob/master/pkgs/development/interpreters/python/mk-python-derivation.nix buildPythonPackage implementation] | ||
* [https://nixos.org/manual/nixpkgs | * [https://nixos.org/manual/nixpkgs/#python Python] in the nixpkgs manual | ||
* [https://github.com/on-nix/python Python on Nix] is an "Extensive collection of Python projects from PyPI" | |||
* [https://nixos.org/manual/nixpkgs/stable/#examples Rust section of Nixpkgs manual] - build Rust code in Python projects | |||
== References == | == References == |
Latest revision as of 09:15, 15 September 2024
See Language-specific package helpers for a list of tools to package python packages.
Prepare Packaging
When you want to package a new software from a local checkout with the inputs coming from nixpkgs (and not virtualenv+pip) you can use the following shell.nix
[1]:
with import <nixpkgs> {};
with pkgs.python3Packages;
buildPythonPackage rec {
name = "mypackage";
src = ./path/to/source;
propagatedBuildInputs = [ pytest numpy pkgs.libsndfile ];
}
You can now run nix-shell
and it will drop you in a shell similar to the python setup.py develop
mode which uses the local code in ./path/to/source as input. propagatedBuildInputs
will contain the packages you need in your project.
After you've finished developing you can replace the relative path with fetchFromGitHub { ... }
or fetchPypi { ... }
.
Pip and Virtualenv enabled nix-shell
It might be the case that you simply need to prototype fast small projects with pip
and virtualenv
without the need for relying on the dependencies being already packaged in nixpkgs.
{{Notice|Keep in mind that the virtualenv symlinks will be invalidated if you update your system!||
For a local working python environment you can use the following shell.nix
[2].
{ pkgs ? import <nixpkgs> { } }:
let
pythonEnv = pkgs.python3.withPackages(ps: [ ]);
in
pkgs.mkShell {
packages = [
pythonEnv
];
}
When invoked with nix-shell, this environment gives you a readline-enabled Python, plus virtualenv and pip, from which you can create a virtual environment and then proceed to fill it with pip-installed packages from requirements.txt or any other source of packages.
And the only other thing you need to do is figure out which non-Python packages your pip-installable packages will need, and include them in buildInputs.
Caveats
ModuleNotFoundError: No module named 'pkg_resources'
If you see this runtime error
ModuleNotFoundError: No module named 'pkg_resources'
add setuptools
to your derivation
buildPythonPackage {
# ...
propagatedBuildInputs = [
# ...
setuptools
];
}
Please report such issues at https://github.com/NixOS/nixpkgs/issues
HTTP 404 with fetchPypi
example error:
curl: (22) The requested URL returned error: 404
error: cannot download stt-1.2.0.tar.gz from any mirror
when we look at https://pypi.org/project/stt/#files we see only *.whl
files:
stt-1.2.0-cp310-cp310-win_amd64.whl
stt-1.2.0-cp310-cp310-manylinux_2_24_x86_64.whl
stt-1.2.0-cp310-cp310-macosx_10_10_universal2.whl
- ...
this means, this is a binary release, so we have two options:
- build from source
- install binary release
build from source
replace this
buildPythonPackage {
pname = "TODO";
version = "TODO";
src = fetchPypi {
inherit pname version;
sha256 = ""; # TODO
};
with this
buildPythonPackage {
pname = "TODO";
version = "TODO";
src = fetchFromGitHub {
owner = "TODO";
repo = "TODO";
rev = "v${version}";
sha256 = ""; # TODO
};
install binary release
replace this
buildPythonPackage {
pname = "TODO";
version = "TODO";
src = fetchPypi {
inherit pname version;
sha256 = ""; # TODO
};
with this
buildPythonPackage {
pname = "TODO";
version = "TODO";
format = "wheel";
src = fetchPypi rec {
inherit pname version format;
sha256 = ""; # TODO
dist = python;
python = "py3";
#abi = "none";
#platform = "any";
};
... or use fetchurl
to download the *.whl
file directly.
reference: fetchPypi implementation
Fix Missing setup.py
The setup.py
file is required for buildPythonPackage
,
but it's missing in some packages.
If you get the following error, you need to one of the workarounds below.
FileNotFoundError: [Errno 2] No such file or directory: 'setup.py'
If the package has a pyproject.toml
file, set
buildPythonPackage {
format = "pyproject";
}
If both setup.py
and pyproject.toml
are missing,
you have to add one of these files.
Note: sometimes you will be able to find pyproject.toml
in the source for a package despite it not being present in a .whl
file. You can inspect the contents of a .whl
file by downloading it from PyPi and then extracting it with nix-shell -p python311Packages.wheel --command wheel unpack path/to/package.whl
.
For example, you can create the setup.py
in the preBuild
phase.
buildPythonPackage {
preBuild = ''
cat > setup.py << EOF
from setuptools import setup
with open('requirements.txt') as f:
install_requires = f.read().splitlines()
setup(
name='someprogram',
#packages=['someprogram'],
version='0.1.0',
#author='...',
#description='...',
install_requires=install_requires,
scripts=[
'someprogram.py',
],
entry_points={
# example: file some_module.py -> function main
#'console_scripts': ['someprogram=some_module:main']
},
)
EOF
'';
}
More info about the setup.py
can be found here. (note: from python 3.12 onwards, distutils is deprecated see https://docs.python.org/3.11/distutils/index.html)
scripts
is useful for self-contained python scripts with no local imports.
If a python script has local imports,
for example from .some_module import some_function
,
either include all files in the scripts
array,
or add only the entry function to entry_points
.
In this example, someprogram.py
would be installed as $out/bin/someprogram.py
.
To rename the binary, for example to remove the .py
file extension, you can use postInstall
buildPythonPackage {
# ...
postInstall = ''
mv -v $out/bin/someprogram.py $out/bin/someprogram
'';
}
requirements.txt
requirements.txt
in it's simplest form is a list of python packages
numpy Requests Pillow
buildPythonPackage
will check these dependencies, but you still must declare the nix dependencies in buildInputs
, propagatedBuildInputs
, checkInputs
, ...
Automatic packaging
TODO https://github.com/nix-community/poetry2nix - 400 stars
TODO https://github.com/nix-community/pip2nix - 100 stars
TODO https://github.com/nix-community/pypi2nix - 200 stars - archived
Testing via this command is deprecated
In most cases, tests will pass anyway and you can ignore the warning.
In some cases, tests will fail, for example:
running test
WARNING: Testing via this command is deprecated and will be removed in a future version. Users looking for a generic test entry point independent of test runner are encouraged to use tox.
[ ... ]
TypeError: some_function() missing 1 required positional argument: 'some_argument'
quick fix: run tests with python's unittest module
checkPhase = ''
runHook preCheck
${python3.interpreter} -m unittest
runHook postCheck
'';
See also
- buildPythonPackage implementation
- Python in the nixpkgs manual
- Python on Nix is an "Extensive collection of Python projects from PyPI"
- Rust section of Nixpkgs manual - build Rust code in Python projects