Python: Difference between revisions
m Poetry doesn't have an input named `python` https://github.com/NixOS/nixpkgs/blob/7c503a8a9d182df45a4e4fc2f836bcc7316fed2c/pkgs/tools/package-management/poetry/default.nix#L1 |
Include mention of poetry2nix |
||
(16 intermediate revisions by 8 users not shown) | |||
Line 16: | Line 16: | ||
in pkgs.mkShell { | in pkgs.mkShell { | ||
packages = [ | packages = [ | ||
(pkgs.python3.withPackages (python-pkgs: [ | (pkgs.python3.withPackages (python-pkgs: with python-pkgs; [ | ||
# select Python packages here | # select Python packages here | ||
pandas | |||
requests | |||
])) | ])) | ||
]; | ]; | ||
Line 64: | Line 64: | ||
]; | ]; | ||
} | } | ||
</syntaxhighlight> | </syntaxhighlight>Given the file above is named <code>toolz.nix</code> and is the same directory as the previous <code>shell.nix</code> , you can edit <code>shell.nix</code> to use the package <code>toolz</code> above like so: <syntaxhighlight lang="nix" line="1"> | ||
Given the file above is named <code>toolz.nix</code> and is the same directory as the previous <code>shell.nix</code> , you can edit <code>shell.nix</code> to use the package <code>toolz</code> above like so: <syntaxhighlight lang="nix" line="1"> | |||
# shell.nix | # shell.nix | ||
let | let | ||
pkgs = import <nixpkgs> {}; | pkgs = import <nixpkgs> {}; | ||
python = pkgs.python3.override { | |||
self = python; | |||
packageOverrides = pyfinal: pyprev: { | |||
toolz = pyfinal.callPackage ./toolz.nix { }; | |||
}; | |||
}; | |||
in pkgs.mkShell { | in pkgs.mkShell { | ||
packages = [ | packages = [ | ||
( | (python.withPackages (python-pkgs: [ | ||
# select Python packages here | # select Python packages here | ||
python-pkgs.pandas | python-pkgs.pandas | ||
python-pkgs.requests | python-pkgs.requests | ||
python-pkgs.toolz | |||
])) | ])) | ||
]; | ]; | ||
} | } | ||
</syntaxhighlight> | </syntaxhighlight>Next time you enter the shell specified by this file, Nix will build and include the Python package you have written. | ||
=== Running compiled libraries === | |||
If you want to run some compiled libraries as for example <code>grpcio</code><ref>https://pypi.org/project/grpcio/</ref>, you may encounter the following error :<syntaxhighlight lang="shell-session"> | |||
$ python -c 'import grpc' | |||
Traceback (most recent call last): | |||
File "<string>", line 1, in <module> | |||
File "/.../grpc/__init__.py", line 22, in <module> | |||
from grpc import _compression | |||
File "/.../grpc/_compression.py", line 20, in <module> | |||
from grpc._cython import cygrpc | |||
ImportError: libstdc++.so.6: cannot open shared object file: No such file or directory | |||
</syntaxhighlight>This means that the library use compiled dynamically linked binaries that your NixOs environment fail to resolve. | |||
On NixOS, installing packages that need to compile code or use C libraries from outside of the <code>nix</code> package manager may fail if dependencies are not found in the expected locations. | |||
There are multiple ways to make it work: | |||
* Use [https://github.com/GuillaumeDesforges/fix-python/ fix-python], this is most suited for beginners. | |||
* Create a FHS user env with <code>buildFHSUserEnv</code>. | |||
* Setup <code>nix-ld</code><ref name=":0">https://github.com/Mic92/nix-ld</ref> in your NixOS configuration. | |||
* Prefix library paths using wrapProgram utility. | |||
==== Setup nix-ld ==== | |||
< | nix-ld<ref name=":0" /> allow you to run unpatched dynamic binaries on NixOS. | ||
The following configuration automatically fix the dependencies:<syntaxhighlight lang="nixos" line="1"> | |||
let | let | ||
python = pkgs.python311; | |||
# We currently take all libraries from systemd and nix as the default | |||
# https://github.com/NixOS/nixpkgs/blob/c339c066b893e5683830ba870b1ccd3bbea88ece/nixos/modules/programs/nix-ld.nix#L44 | |||
pythonldlibpath = lib.makeLibraryPath (with pkgs; [ | |||
zlib | |||
zstd | |||
stdenv.cc.cc | |||
curl | |||
openssl | |||
attr | |||
libssh | |||
bzip2 | |||
libxml2 | |||
acl | |||
libsodium | |||
util-linux | |||
xz | |||
systemd | |||
]); | |||
patchedpython = (python.overrideAttrs ( | |||
previousAttrs: { | |||
# Add the nix-ld libraries to the LD_LIBRARY_PATH. | |||
# creating a new library path from all desired libraries | |||
postInstall = previousAttrs.postInstall + '' | |||
mv "$out/bin/python3.11" "$out/bin/unpatched_python3.11" | |||
cat << EOF >> "$out/bin/python3.11" | |||
#!/run/current-system/sw/bin/bash | |||
export LD_LIBRARY_PATH="${pythonldlibpath}" | |||
exec "$out/bin/unpatched_python3.11" "\$@" | |||
EOF | |||
chmod +x "$out/bin/python3.11" | |||
''; | |||
} | |||
)); | |||
# if you want poetry | |||
patchedpoetry = ((pkgs.poetry.override { python3 = patchedpython; }).overrideAttrs ( | |||
previousAttrs: { | |||
# same as above, but for poetry | |||
# not that if you dont keep the blank line bellow, it crashes :( | |||
postInstall = previousAttrs.postInstall + '' | |||
mv "$out/bin/poetry" "$out/bin/unpatched_poetry" | |||
cat << EOF >> "$out/bin/poetry" | |||
#!/run/current-system/sw/bin/bash | |||
export LD_LIBRARY_PATH="${pythonldlibpath}" | |||
exec "$out/bin/unpatched_poetry" "\$@" | |||
EOF | |||
chmod +x "$out/bin/poetry" | |||
''; | |||
} | |||
)); | |||
in | |||
{ | |||
# Some other config... | |||
environment.systemPackages = with pkgs; [ | |||
patchedpython | |||
# if you want poetry | |||
# | patchedpoetry | ||
]; | ]; | ||
} | } | ||
</syntaxhighlight> | </syntaxhighlight>This configuration set the <code>LD_LIBRARY_PATH</code> environment variable before running python using the <code>overrideAttrs</code><ref>https://nixos.org/manual/nixpkgs/stable/#sec-pkg-overrideAttrs</ref> function to override the <code>postInstall</code> script of cpython <code>mkDerivation</code><ref>https://github.com/NixOS/nixpkgs/blob/24.05/pkgs/development/interpreters/python/cpython/default.nix</ref>. | ||
< | |||
# python | |||
After this step, you should be able to install compiled libraries using venv, poetry, conda or other packages managers... | |||
==== Prefix library paths using wrapProgram ==== | |||
wrapProgram is a part of the makeWrapper build input<ref>https://github.com/NixOS/nixpkgs/blob/master/pkgs/build-support/setup-hooks/make-wrapper.sh</ref>. By combining it with the symlinkJoin, we can create a wrapper around the Python executable that will always set the required library paths. It’s worth noting that, for this solution to be compatible with Darwin, we need to use a different wrap prefix, as shown in the example below.<syntaxhighlight lang="nixos" line="1"> | |||
let | let | ||
# We currently take all libraries from systemd and nix as the default | |||
# https://github.com/NixOS/nixpkgs/blob/c339c066b893e5683830ba870b1ccd3bbea88ece/nixos/modules/programs/nix-ld.nix#L44 | |||
pythonldlibpath = lib.makeLibraryPath (with pkgs; [ | |||
zlib | |||
zstd | |||
stdenv.cc.cc | |||
curl | |||
openssl | |||
attr | |||
libssh | |||
bzip2 | |||
libxml2 | |||
acl | |||
libsodium | |||
util-linux | |||
xz | |||
systemd | |||
]); | |||
# Darwin requires a different library path prefix | |||
wrapPrefix = if (!pkgs.stdenv.isDarwin) then "LD_LIBRARY_PATH" else "DYLD_LIBRARY_PATH"; | |||
patchedpython = (pkgs.symlinkJoin { | |||
name = "python"; | |||
paths = [ pkgs.python312 ]; | |||
buildInputs = [ pkgs.makeWrapper ]; | |||
] | postBuild = '' | ||
wrapProgram "$out/bin/python3.12" --prefix ${wrapPrefix} : "${pythonldlibpath}" | |||
''; | |||
}); | |||
}; | |||
in | in | ||
{ | |||
environment.systemPackages = with pkgs; [ | |||
patchedpython | |||
]; | |||
} | |||
</syntaxhighlight> | </syntaxhighlight> | ||
=== Using <code>venv</code> === | === Using <code>venv</code> === | ||
Line 183: | Line 223: | ||
</syntaxhighlight>You can then activate and use the Python virtual environment as usual and install dependencies with <code>pip</code> and similar. | </syntaxhighlight>You can then activate and use the Python virtual environment as usual and install dependencies with <code>pip</code> and similar. | ||
=== | === Using uv === | ||
<blockquote>A single tool to replace <code>pip</code>, <code>pip-tools</code>, <code>pipx</code>, <code>poetry</code>, <code>pyenv</code>, <code>virtualenv</code>, and more.</blockquote>uv is very simple to use. Simply <code>uv init</code> to get started. No need for shells, as it creates virtual environments. | |||
As a systemPackage<syntaxhighlight lang="nix"> | |||
environment.systemPackages = with pkgs; [ | |||
uv | |||
]; | |||
</syntaxhighlight>or as a home-manager package<syntaxhighlight lang="nix"> | |||
home.packages = with pkgs; [ | |||
uv | |||
]; | |||
</syntaxhighlight> | |||
=== Using poetry === | === Using poetry === | ||
Line 207: | Line 250: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
==== poetry2nix ==== | |||
[https://github.com/nix-community/poetry2nix poetry2nix] uses the contents of a <code>poetry.lock</code> and <code>pyproject.toml</code> file to create Nix derivations. It has several functions for generating development environments and python projects. Because some older python projects rely on deprecated build systems (see [https://github.com/nix-community/poetry2nix/blob/master/docs/edgecases.md edgecase.md] for more info), poetry2nix provides overrides so these packages can still be built. | |||
=== Using micromamba === | === Using micromamba === | ||
Line 264: | Line 309: | ||
== Package a Python application == | == Package a Python application == | ||
=== With <code>setup.py</code> === | |||
To package a Python application that uses <code>setup.py</code> you can use <code>buildPythonApplication</code>. More details about this and similar functions can be found in [https://nixos.org/manual/nixpkgs/stable/#building-packages-and-applications the nixpkgs manual]. | |||
For example, we can package this simple flask server <code>main.py:</code> | |||
<syntaxhighlight lang="python"> | <syntaxhighlight lang="python"> | ||
#!/usr/bin/env python | #!/usr/bin/env python | ||
from flask import Flask | from flask import Flask | ||
app = Flask(__name__) | app = Flask(__name__) | ||
Line 281: | Line 329: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
We also need a <code>setup.py</code> file, like this: | |||
<syntaxhighlight lang="python"> | <syntaxhighlight lang="python"> | ||
from setuptools import setup, find_packages | from setuptools import setup, find_packages | ||
setup(name=' | setup(name='myFlaskServer', | ||
version='1.0', | version='1.0', | ||
# Modules to import from other scripts: | # Modules to import from other scripts: | ||
packages=find_packages(), | packages=find_packages(), | ||
# Executables | # Executables | ||
scripts=[" | scripts=["main.py"], | ||
) | ) | ||
</syntaxhighlight> | </syntaxhighlight> | ||
Then, we use the <code>buildPythonApplication</code> in the <code>default.nix</code>: | |||
<syntaxhighlight lang="nix"> | <syntaxhighlight lang="nix"> | ||
{ | { pkgs ? import <nixpkgs> {} }: | ||
propagatedBuildInputs = [ flask ]; | pkgs.python3Packages.buildPythonApplication { | ||
pname = "myFlaskApp"; | |||
version = "0.1.0"; | |||
propagatedBuildInputs = with pkgs.python3Packages; [ | |||
flask | |||
]; | |||
src = ./.; | src = ./.; | ||
} | } | ||
</syntaxhighlight> | </syntaxhighlight>Finally, build your project using <code>nix-build</code>. The result will be executable in <code>./result/bin/app.py</code>. | ||
=== With <code>pyproject.toml</code> === | |||
When your project is using <code>pyproject.toml</code>you can use [https://github.com/nix-community/pyproject.nix pyproject.nix] to package your application. | |||
First, a simple file structure could look like this:<syntaxhighlight> | |||
├── app/ | |||
└── main.py | |||
├── flake.nix | |||
├── pyproject.toml | |||
└── README.md | |||
</syntaxhighlight>To reuse the example from above, we use the same flask application:<syntaxhighlight lang="python"> | |||
from flask import Flask | |||
app = Flask(__name__) | |||
@app.route('/') | |||
def hello_world(): | |||
return 'Hello, World!' | |||
if __name__ == '__main__': | |||
app.run(host="0.0.0.0", port=8080) | |||
</syntaxhighlight>Also, you need to define the <code>pyproject.toml</code>. Here, we only show some of the important parts. Please refer to <code>pyproject.nix</code> [https://nix-community.github.io/pyproject.nix/use-cases/pyproject.html documentation] for a full example.<syntaxhighlight lang="toml"> | |||
[project] | |||
name = "my-app" | |||
version = "0.1.0" | |||
description = "Simple app" | |||
# define any Python dependencies | |||
dependencies = [ | |||
"flask>3", | |||
] | |||
# define the CLI executable | |||
# Here, we define the entry point to be the 'main()' function in the module 'app/main.py' | |||
[project.scripts] | |||
cli = "app.main:main" | |||
</syntaxhighlight>We package the application by calling the <code>loadPyproject</code> function from <code>pyproject.nix</code>. Again, we only show a minimal example. More information can be found in the [https://nix-community.github.io/pyproject.nix/use-cases/pyproject.html documentation].<syntaxhighlight lang="nix"> | |||
{ | |||
description = "A basic flake using pyproject.toml project metadata"; | |||
inputs = { | |||
pyproject-nix = { | |||
url = "github:nix-community/pyproject.nix"; | |||
inputs.nixpkgs.follows = "nixpkgs"; | |||
}; | |||
}; | |||
outputs = { nixpkgs, pyproject-nix, ... }: | |||
let | |||
inherit (nixpkgs) lib; | |||
project = pyproject-nix.lib.project.loadPyproject { | |||
# Read & unmarshal pyproject.toml relative to this project root. | |||
# projectRoot is also used to set `src` for renderers such as buildPythonPackage. | |||
projectRoot = ./.; | |||
}; | |||
# This example is only using x86_64-linux | |||
pkgs = nixpkgs.legacyPackages.x86_64-linux; | |||
python = pkgs.python3; | |||
pkgs. | |||
in | |||
{ | |||
# Build our package using `buildPythonPackage | |||
packages.x86_64-linux.default = | |||
let | |||
# Returns an attribute set that can be passed to `buildPythonPackage`. | |||
attrs = project.renderers.buildPythonPackage { inherit python; }; | |||
in | |||
# Pass attributes to buildPythonPackage. | |||
# Here is a good spot to add on any missing or custom attributes. | |||
python.pkgs.buildPythonPackage (attrs // { | |||
env.CUSTOM_ENVVAR = "hello"; | |||
}); | |||
}; | |||
} | |||
</syntaxhighlight>To run the application, call <code>nix run</code>. | |||
> | |||
</ | |||
== Nixpkgs Python contribution guidelines == | == Nixpkgs Python contribution guidelines == | ||
Line 432: | Line 530: | ||
* [https://nixos.org/manual/nixpkgs/unstable/#python "Python" in Nixpkgs Manual] | * [https://nixos.org/manual/nixpkgs/unstable/#python "Python" in Nixpkgs Manual] | ||
[[Category:Languages]] | [[Category:Languages]] | ||
[[Category:Python]] |