nh2
nh2

Reputation: 25675

How to package a single Python script with nix?

I have a single Python script called myscript.py and would like to package it up as a nix derivation with mkDerivation.

The only requirement is that my Python script has a run-time dependency, say, for the consul Python library (which itself depends on the requests and six Python libraries).

For example for myscript.py:

#!/usr/bin/env python3

import consul
print('hi')

How to do that?

I can't figure out how to pass mkDerivation a single script (its src seems to always want a directory, or fetchgit or similar), and also can't figure out how to make the dependency libraries available at runtime.

Upvotes: 15

Views: 7854

Answers (3)

ntninja
ntninja

Reputation: 1325

Based on @nh2’s answer and the NixPkgs manual, here is a version using the more standard buildPythonApplication rather than a custom derivation:

{ lib
, python3Packages
}:

python3Packages.buildPythonApplication rec {
    pname = "nix-prefetch-pypi";
    version = "0.1.0";
    pyproject = false;

    propagatedBuildInputs = with python3Packages; [
        # List of dependencies
        httpx
    ];

    # Do direct install
    #
    # Add further lines to `installPhase` to install any extra data files if needed.
    dontUnpack = true;
    installPhase = ''
        install -Dm755 "${./${pname}}" "$out/bin/${pname}"
    '';
}

The script file would then be something like:

#!/usr/bin/env python3
# ^ Using this shebang allows the script to also be
#   executed directly if the Python interpreter and
#   all used packages were specified in the user
#   package set or `environment.systemPackages`:
#
#       environment.systemPackages = with pkgs; {
#           # …
#           python3.withPackages (pp: with pp; [
#               httpx
#           ])
#           # …
#       };

import sys

import httpx


# Extremely simplified example code
with httpx.Client(base_url="https://pypi.org/pypi/") as client:
    resp = client.get(f{sys.argv[1]}/{sys.argv[2]}/json")
    print(resp.json())

…placed in a file named after the Nix package (nix-prefetch-pypi in this case).

Upvotes: 2

nh2
nh2

Reputation: 25675

When you have a single Python file as your script, you don't need src in your mkDerivation and you also don't need to unpack any source code.

The default mkDerivation will try to unpack your source code; to prevent that, simply set dontUnpack = true.

myscript-package = pkgs.stdenv.mkDerivation {
  name = "myscript";
  propagatedBuildInputs = [
    (pkgs.python3.withPackages (pythonPackages: with pythonPackages; [
      consul
      six
      requests2
    ]))
  ];
  dontUnpack = true;
  installPhase = "install -Dm755 ${./myscript.py} $out/bin/myscript";
};

If your script is executable (which we ensure with install -m above) Nix will automatically replace your #!/usr/bin/env python3 line with one which invokes the right specific python interpreter (the one for python36 in the example above), and which does so in an environment that has the Python packages you've specifified in propagatedBuildInputs available.

If you use NixOS, you can then also put your package into environment.systemPackages, and myscript will be available in shells on that NixOS.

Upvotes: 25

Peter Esselius
Peter Esselius

Reputation: 151

This helper function is really nice:

pkgs.writers.writePython3Bin "github-owner-repos" { libraries = [ pkgs.python3Packages.PyGithub ]; } ''
 import os
 import sys
 from github import Github

 if __name__ == '__main__':
     gh = Github(os.environ['GITHUB_TOKEN'])

     for repo in gh.get_user(login=sys.argv[1]).get_repos():
         print(repo.ssh_url)
''

https://github.com/nixos/nixpkgs/blob/master/pkgs/build-support/writers/default.nix#L319

Upvotes: 15

Related Questions