Git Product home page Git Product logo

pypi2nix's Introduction

pypi2nix - generate Nix expressions for Python packages

pypi2nix is a command line tool that generates Nix expressions from different Python specific sources (requirements.txt, buildout.cfg, ...). This is useful for:

  • Building a Nix derivation for a program written in Python as part of packaging it.

  • Setting up a development environment to hack on a program written in Python.

    The only way we can fix bugs with pypi2nix is if you report them. Please create an issue if you discover problems.

pypi2nix will (until further notice) only work with latest unstable channel. This is due to ongoing changes in Python infrastructure happening in Nixpkgs.

The Nixpkgs manual section about Python makes good reading if you haven't seen it already.

Make sure Nix is installed:

% curl https://nixos.org/nix/install | sh

And now install it using nix-env command:

% nix-env -if https://github.com/garbas/pypi2nix/tarball/master

The easiest way to generate a Nix expressions is to invoke:

% pypi2nix -V "3.5" -e packageA -e packageB==0.1

If you also have a requirements.txt file for your Python project you can use the -r option.

% pypi2nix -V "3.5" -e packageA -e packageB==0.1 \
    -r requirements.txt -r requirements-dev.txt

If your project relies on zc.buildout you can give -b option a try:

% pypi2nix -V "2.7" -b buildout.cfg

Option -V tells pypi2nix which python version to be used. To see which Python versions are available consult pypi2nix --help.

Once Nix expressions are generated you should be able to see 3 new files:

  • requirements_frozen.txt - full frozen set for your for your pypi2nix call. This is the output you would expect from pip freeze.
  • requirements.nix is a file which contains a nix expression to for the package set that was built.
  • requirements_override.nix - this is an empty file which lets you override generated nix expressions.

Build one package:

% nix build -f requirements.nix packages.empy

Build all packages:

% nix build -f requirements.nix packages

Build python interpreter with all packages loaded:

% nix build -f requirements.nix interpreter
% ./result/bin/python -c "import empy"

Enter development environment:

% nix run -f requirements.nix interpreter
[user@localhost:~/dev/nixos/pypi2nix) % python -c "import empy"

If you are working on a project where its dependencies are defined in requirements.txt then you can create a default.nix and add generated packages as buildInputs, as demonstrated here:

{}:
let
  python = import ./requirements.nix { inherit pkgs; };
in python.mkDerivation {
  name = "ProjectA-1.0.0";
  src = ./.;
  buildInputs = [
    python.packages."coverage"
    python.packages."flake8"
    python.packages."mock"
    python.packages."pytest"
    python.packages."pytest-asyncio"
    python.packages."pytest-cov"
    python.packages."pytest-mock"
    python.packages."pytest-xdist"
    python.packages."virtualenv"
  ];
  propagatedBuildInputs = [
    python.packages."aiohttp"
    python.packages."arrow"
    python.packages."defusedxml"
    python.packages."frozendict"
    python.packages."jsonschema"
    python.packages."taskcluster"
    python.packages."virtualenv"
  ];
  ...
}

As you can see you can access all packages via python.packages."<name>". If you want to depend on all packages you can even do:

propagatedBuildInputs = builtins.attrValues python.packages;

I hope nobody is expecting pypi2nix to do always a perfect job. In Python packaging, there are just too many different cases that we will never be able to cover. What pypi2nix tries to do is to get you very close.

Sometimes pypi2nix fails entirely. If this happens, open a bug -- it's almost always a bug in pypi2nix. However, sometimes pypi2nix succeeds but the resulting requirements.nix file fails during the building of your Python package. Depending on what the problem is, this section may be helpful.

Quite a few Python packages require non-Python dependencies to be present at build time. These packages will fail to build with error messages about not being able to find foo.h or some fooconfig file. To work around this, pypi2nix has -E options which can be used to include extra non-Python dependencies.

For example, psycopg2 requires pg_config binary to be present at installation time:

% pypi2nix -v -V 2.7 -e psycopg2 -E postgresql

lxml requires libxml2 and libxslt system package:

% pypi2nix -v -V 2.7 -e lxml -E libxml2 -E libxslt

Some packages expect additional environment variables to be set:

% pypi2nix -v -V 2.7 -e bsddb3 -N 'BERKELEYDB_DIR=${pkgs.db.dev}'

Some other failures might be caused because the derivation that pypi2nix wrote was incomplete. A very common situation is that pypi2nix didn't include all the dependencies of some package. As an example, execnet depends on setuptools-scm, but pypi2nix may not detect this.

When this happens, Nix will fail to build execnet, perhaps with an error message from distutils/setuptools complaining that it can't find a distribution for setuptools-scm. What's happening here is that normally execnet would fetch setuptools-scm from PyPI, but Nix disables network access to guarantee reproducability. So when you build execnet, it fails to find setuptools-scm.

For these situations, pypi2nix provides a requirements_override.nix file, which lets you override anything that it generated. You can even add new packages to the dependency set this way.

As an example, let's add setuptools-scm as a build-time dependency of execnet. Here's the requirements_override.nix:

{ pkgs, python }:

self: super: {

  "execnet" = python.overrideDerivation super."execnet" (old: {
    buildInputs = old.buildInputs ++ [ self."setuptools-scm" ];
  });

}

In a similar way, you can add or remove any Python package.

In addition to the empty autogenerated requirements_overrides.nix file, you can include pre-existing overrides files. These overrides will be included the same way as your requirements_overrides.nix.

The pypi2nix author also maintains a set of "default" overrides at https://github.com/garbas/nixpkgs-python/blob/master/overrides.nix -- you can include these by using the --default-overrides argument to pypi2nix. These overrides are designed in such a way that they only override dependencies that were already present in your requirements.nix.

You can also include an overrides file using the -O command line argument. pypi2nix can fetch these overrides from a local file or over certain common protocols.

http and https

pypi2nix -V 3 --overrides https://raw.githubusercontent.com/garbas/nixpkgs-python/master/overrides.nix

Note that the generated Nix expression will check if contents of the overrides file differs from when the Nix expression was built, and fail if this was the case (or the file does not exist anymore).

Local files
pypi2nix -V 3 --override ../some/relative/path --override /some/absolute/path
Git repositories

pypi2nix -V 3 --override git+https://github.com/garbas/pypi2nix.git#path=overrides.nix

If you want to import a file from a specific git repository you have to prefix its URL with git+, quite similar to how you would do in a requirements.txt file for pip.

Nothing speaks better than an example:

{ }:

let
  pkgs = import <nixpkgs> {};
  python = import ./requirements.nix { inherit pkgs; };
in python.mkDerivation {
  name = "projectA-1.0.0";
  src = ./.;
  buildInputs = [
    python.packages."coverage"
    python.packages."flake8"
    python.packages."mock"
    python.packages."pytest"
    python.packages."pytest-asyncio"
    python.packages."pytest-cov"
    python.packages."pytest-mock"
    python.packages."pytest-xdist"
  ];
  propagatedBuildInputs = [
    python.packages."aiohttp"
    python.packages."arrow"
    python.packages."defusedxml"
    python.packages."frozendict"
    python.packages."jsonschema"
  ];
  checkPhase = ''
    export NO_TESTS_OVER_WIRE=1
    export PYTHONDONTWRITEBYTECODE=1

    flake8 src/
    py.test --cov=src -cov-report term-missing
    coverage html
  '';
}

Important to know here is that you instantiate all generated packages as python = import ./requirements.nix { inherit pkgs; }; which gives you a Python environment with all the packages generated by pypi2nix as well as some common utilities.

To create a package you use python.mkDerivation which works like the pythonPackages.buildPythonPackage function in nixpkgs. All generated packages are available as one attribute set under python.packages.

One of future goals of pypi2nix project is to also improve the UX of our Python tooling in nixpkgs. While this is very hard to do within nixpkgs it is almost trivial to experiment with this outside nixpkgs.

A working example is worth 1000 words.

overlay.nix:

self: super:
{
  customPython =
      (import ./requirements.nix { pkgs = self; });
}

shell.nix:

with (import <nixpkgs> { overlays = [ (import ./overlay.nix) ]; });
customPython.interpreter

Clone pypi2nix repository and using nix run command enter development environment.:

% git clone https://github.com/garbas/pypi2nix
% cd pypi2nix
% nix run -f .

Code is located in src/pypi2nix.

Pypi2nix comes with two kinds of tests: unit tests and integration tests. They can be found in the folders /unittests and /integrationtests respectively.

Unit tests are straight forward. They are run via pytest and (try to) follow pytest best practices. Idealy all of pypi2nix's code should be covered by unittests. If possible unittests should not go online and fetch data from the internet. If this cannot be avoided use the @nix decorator, found in unittests.switches to mark tests that require network access.

Integration tests are a little bit more involved. We implemented a small framework to write new tests and maintain old ones. Check out integrationtests.framework for information on how to write custom integration tests.

pypi2nix's People

Contributors

acowley avatar aszlig avatar bennofs avatar blueyed avatar cillianderoiste avatar datakurre avatar domenkozar avatar edofic avatar flokli avatar garbas avatar glasserc avatar globin avatar jonafato avatar jpic avatar jraygauthier avatar la0 avatar marco-c avatar mattbillenstein avatar mic92 avatar nlewo avatar plumps avatar seppeljordan avatar sorki avatar

Watchers

 avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.