Merge pull request #236875 from mweinelt/wyoming_piper_whisper

wyoming-piper: init at 0.0.3, wyoming-faster-whisper: ini at 0.0.3
This commit is contained in:
Martin Weinelt 2023-06-12 21:02:18 +02:00 committed by GitHub
commit eb6e16e3c5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 741 additions and 25 deletions

View File

@ -328,6 +328,8 @@
./services/audio/spotifyd.nix
./services/audio/squeezelite.nix
./services/audio/tts.nix
./services/audio/wyoming/faster-whisper.nix
./services/audio/wyoming/piper.nix
./services/audio/ympd.nix
./services/backup/automysqlbackup.nix
./services/backup/bacula.nix

View File

@ -0,0 +1,186 @@
{ config
, lib
, pkgs
, ...
}:
let
cfg = config.services.wyoming.faster-whisper;
inherit (lib)
escapeShellArgs
mkOption
mdDoc
mkEnableOption
mkPackageOptionMD
types
;
inherit (builtins)
toString
;
in
{
options.services.wyoming.faster-whisper = with types; {
package = mkPackageOptionMD pkgs "wyoming-faster-whisper" { };
servers = mkOption {
default = {};
description = mdDoc ''
Attribute set of faster-whisper instances to spawn.
'';
type = types.attrsOf (types.submodule (
{ ... }: {
options = {
enable = mkEnableOption (mdDoc "Wyoming faster-whisper server");
model = mkOption {
type = enum [
"tiny"
"tiny-int8"
"base"
"base-int8"
"small"
"small-int8"
"medium"
"medium-int8"
];
default = "tiny-int8";
example = "medium-int8";
description = mdDoc ''
Name of the voice model to use.
'';
};
uri = mkOption {
type = strMatching "^(tcp|unix)://.*$";
example = "tcp://0.0.0.0:10300";
description = mdDoc ''
URI to bind the wyoming server to.
'';
};
device = mkOption {
# https://opennmt.net/CTranslate2/python/ctranslate2.models.Whisper.html#
type = types.enum [
"cpu"
"cuda"
"auto"
];
default = "cpu";
description = mdDoc ''
Id of a speaker in a multi-speaker model.
'';
};
language = mkOption {
type = enum [
# https://github.com/home-assistant/addons/blob/master/whisper/config.yaml#L20
"auto" "af" "am" "ar" "as" "az" "ba" "be" "bg" "bn" "bo" "br" "bs" "ca" "cs" "cy" "da" "de" "el" "en" "es" "et" "eu" "fa" "fi" "fo" "fr" "gl" "gu" "ha" "haw" "he" "hi" "hr" "ht" "hu" "hy" "id" "is" "it" "ja" "jw" "ka" "kk" "km" "kn" "ko" "la" "lb" "ln" "lo" "lt" "lv" "mg" "mi" "mk" "ml" "mn" "mr" "ms" "mt" "my" "ne" "nl" "nn" "no" "oc" "pa" "pl" "ps" "pt" "ro" "ru" "sa" "sd" "si" "sk" "sl" "sn" "so" "sq" "sr" "su" "sv" "sw" "ta" "te" "tg" "th" "tk" "tl" "tr" "tt" "uk" "ur" "uz" "vi" "yi" "yo" "zh"
];
example = "en";
description = mdDoc ''
The language used to to parse words and sentences.
'';
};
beamSize = mkOption {
type = ints.unsigned;
default = 1;
example = 5;
description = mdDoc ''
The number of beams to use in beam search.
'';
apply = toString;
};
extraArgs = mkOption {
type = listOf str;
default = [ ];
description = mdDoc ''
Extra arguments to pass to the server commandline.
'';
apply = escapeShellArgs;
};
};
}
));
};
};
config = let
inherit (lib)
mapAttrs'
mkIf
nameValuePair
;
in mkIf (cfg.servers != {}) {
systemd.services = mapAttrs' (server: options:
nameValuePair "wyoming-faster-whisper-${server}" {
description = "Wyoming faster-whisper server instance ${server}";
after = [
"network-online.target"
];
wantedBy = [
"multi-user.target"
];
serviceConfig = {
DynamicUser = true;
User = "wyoming-faster-whisper";
StateDirectory = "wyoming/faster-whisper";
# https://github.com/home-assistant/addons/blob/master/whisper/rootfs/etc/s6-overlay/s6-rc.d/whisper/run
ExecStart = ''
${cfg.package}/bin/wyoming-faster-whisper \
--data-dir $STATE_DIRECTORY \
--download-dir $STATE_DIRECTORY \
--uri ${options.uri} \
--model ${options.model} \
--language ${options.language} \
--beam-size ${options.beamSize} ${options.extraArgs}
'';
CapabilityBoundingSet = "";
DeviceAllow = if builtins.elem options.device [ "cuda" "auto" ] then [
# https://docs.nvidia.com/dgx/pdf/dgx-os-5-user-guide.pdf
"/dev/nvidia1"
"/dev/nvidia2"
"/dev/nvidia3"
"/dev/nvidia4"
"/dev/nvidia-caps/nvidia-cap1"
"/dev/nvidia-caps/nvidia-cap2"
"/dev/nvidiactl"
"/dev/nvidia-modeset"
"/dev/nvidia-uvm"
"/dev/nvidia-uvm-tools"
] else "";
DevicePolicy = "closed";
LockPersonality = true;
MemoryDenyWriteExecute = true;
PrivateDevices = true;
PrivateUsers = true;
ProtectHome = true;
ProtectHostname = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectControlGroups = true;
ProtectProc = "invisible";
ProcSubset = "pid";
RestrictAddressFamilies = [
"AF_INET"
"AF_INET6"
"AF_UNIX"
];
RestrictNamespaces = true;
RestrictRealtime = true;
SystemCallArchitectures = "native";
SystemCallFilter = [
"@system-service"
"~@privileged"
];
UMask = "0077";
};
}) cfg.servers;
};
}

View File

@ -0,0 +1,174 @@
{ config
, lib
, pkgs
, ...
}:
let
cfg = config.services.wyoming.piper;
inherit (lib)
escapeShellArgs
mkOption
mdDoc
mkEnableOption
mkPackageOptionMD
types
;
inherit (builtins)
toString
;
in
{
meta.buildDocsInSandbox = false;
options.services.wyoming.piper = with types; {
package = mkPackageOptionMD pkgs "wyoming-piper" { };
servers = mkOption {
default = {};
description = mdDoc ''
Attribute set of piper instances to spawn.
'';
type = types.attrsOf (types.submodule (
{ ... }: {
options = {
enable = mkEnableOption (mdDoc "Wyoming Piper server");
piper = mkPackageOptionMD pkgs "piper-tts" { };
voice = mkOption {
type = str;
example = "en-us-ryan-medium";
description = mdDoc ''
Name of the voice model to use. See the following website for samples:
https://rhasspy.github.io/piper-samples/
'';
};
uri = mkOption {
type = strMatching "^(tcp|unix)://.*$";
example = "tcp://0.0.0.0:10200";
description = mdDoc ''
URI to bind the wyoming server to.
'';
};
speaker = mkOption {
type = ints.unsigned;
default = 0;
description = mdDoc ''
ID of a specific speaker in a multi-speaker model.
'';
apply = toString;
};
noiseScale = mkOption {
type = float;
default = 0.667;
description = mdDoc ''
Generator noise value.
'';
apply = toString;
};
noiseWidth = mkOption {
type = float;
default = 0.333;
description = mdDoc ''
Phoneme width noise value.
'';
apply = toString;
};
lengthScale = mkOption {
type = float;
default = 1.0;
description = mdDoc ''
Phoneme length value.
'';
apply = toString;
};
extraArgs = mkOption {
type = listOf str;
default = [ ];
description = mdDoc ''
Extra arguments to pass to the server commandline.
'';
apply = escapeShellArgs;
};
};
}
));
};
};
config = let
inherit (lib)
mapAttrs'
mkIf
nameValuePair
;
in mkIf (cfg.servers != {}) {
systemd.services = mapAttrs' (server: options:
nameValuePair "wyoming-piper-${server}" {
description = "Wyoming Piper server instance ${server}";
after = [
"network-online.target"
];
wantedBy = [
"multi-user.target"
];
serviceConfig = {
DynamicUser = true;
User = "wyoming-piper";
StateDirectory = "wyoming/piper";
# https://github.com/home-assistant/addons/blob/master/piper/rootfs/etc/s6-overlay/s6-rc.d/piper/run
ExecStart = ''
${cfg.package}/bin/wyoming-piper \
--data-dir $STATE_DIRECTORY \
--download-dir $STATE_DIRECTORY \
--uri ${options.uri} \
--piper ${options.piper}/bin/piper \
--voice ${options.voice} \
--speaker ${options.speaker} \
--length-scale ${options.lengthScale} \
--noise-scale ${options.noiseScale} \
--noise-w ${options.noiseWidth} ${options.extraArgs}
'';
CapabilityBoundingSet = "";
DeviceAllow = "";
DevicePolicy = "closed";
LockPersonality = true;
MemoryDenyWriteExecute = true;
PrivateDevices = true;
PrivateUsers = true;
ProtectHome = true;
ProtectHostname = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectControlGroups = true;
ProtectProc = "invisible";
ProcSubset = "pid";
RestrictAddressFamilies = [
"AF_INET"
"AF_INET6"
"AF_UNIX"
];
RestrictNamespaces = true;
RestrictRealtime = true;
SystemCallArchitectures = "native";
SystemCallFilter = [
"@system-service"
"~@privileged"
];
UMask = "0077";
};
}) cfg.servers;
};
}

View File

@ -0,0 +1,59 @@
{ lib
, stdenv
, fetchFromGitHub
, cmake
, darwin # Accelerate
, llvmPackages # openmp
, oneDNN
, openblas
, withMkl ? false, mkl
}:
let
cmakeBool = b: if b then "ON" else "OFF";
in
stdenv.mkDerivation rec {
pname = "ctranslate2";
version = "3.15.1";
src = fetchFromGitHub {
owner = "OpenNMT";
repo = "CTranslate2";
rev = "v${version}";
hash = "sha256-lh4j53+LQj09tq3qiHrL2YrACzWY1V/HX8Ixnq0TTyY=";
fetchSubmodules = true;
};
nativeBuildInputs = [
cmake
];
cmakeFlags = [
# https://opennmt.net/CTranslate2/installation.html#build-options
"-DWITH_DNNL=OFF" # requires oneDNN>=3.0
"-DWITH_OPENBLAS=ON"
"-DWITH_MKL=${cmakeBool withMkl}"
]
++ lib.optional stdenv.isDarwin "-DWITH_ACCELERATE=ON";
buildInputs = [
llvmPackages.openmp
openblas
oneDNN
] ++ lib.optional withMkl [
mkl
] ++ lib.optionals stdenv.isDarwin [
darwin.apple_sdk.frameworks.Accelerate
] ++ lib.optionals (stdenv.isDarwin && stdenv.isx86_64) [
darwin.apple_sdk.frameworks.CoreGraphics
darwin.apple_sdk.frameworks.CoreVideo
];
meta = with lib; {
description = "Fast inference engine for Transformer models";
homepage = "https://github.com/OpenNMT/CTranslate2";
changelog = "https://github.com/OpenNMT/CTranslate2/blob/${src.rev}/CHANGELOG.md";
license = licenses.mit;
maintainers = with maintainers; [ hexa ];
};
}

View File

@ -0,0 +1,84 @@
{ lib
, buildPythonPackage
# build-system
, pybind11
, setuptools
# dependencies
, ctranslate2-cpp
, numpy
, pyyaml
# tests
, pytestCheckHook
, tensorflow
, torch
, transformers
, wurlitzer
}:
buildPythonPackage rec {
inherit (ctranslate2-cpp) pname version src;
format = "setuptools";
# https://github.com/OpenNMT/CTranslate2/tree/master/python
sourceRoot = "source/python";
nativeBuildInputs = [
pybind11
setuptools
];
buildInputs = [
ctranslate2-cpp
];
propagatedBuildInputs = [
numpy
pyyaml
];
pythonImportsCheck = [
# https://opennmt.net/CTranslate2/python/overview.html
"ctranslate2"
"ctranslate2.converters"
"ctranslate2.models"
"ctranslate2.specs"
];
nativeCheckInputs = [
pytestCheckHook
tensorflow
torch
transformers
wurlitzer
];
preCheck = ''
# run tests against build result, not sources
rm -rf ctranslate2
export HOME=$TMPDIR
'';
disabledTests = [
# AssertionError: assert 'int8' in {'float32'}
"test_get_supported_compute_types"
];
disabledTestPaths = [
# TODO: ModuleNotFoundError: No module named 'opennmt'
"tests/test_opennmt_tf.py"
# OSError: We couldn't connect to 'https://huggingface.co' to load this file
"tests/test_transformers.py"
];
meta = with lib; {
description = "Fast inference engine for Transformer models";
homepage = "https://github.com/OpenNMT/CTranslate2";
changelog = "https://github.com/OpenNMT/CTranslate2/blob/${src.rev}/CHANGELOG.md";
license = licenses.mit;
maintainers = with maintainers; [ hexa ];
};
}

View File

@ -0,0 +1,63 @@
{ lib
, buildPythonPackage
, fetchFromGitHub
# dependencies
, av
, ctranslate2
, huggingface-hub
, onnxruntime
, tokenizers
# tests
, pytestCheckHook
}:
buildPythonPackage rec {
pname = "faster-whisper";
version = "0.6.0";
format = "setuptools";
src = fetchFromGitHub {
owner = "guillaumekln";
repo = "faster-whisper";
rev = "v${version}";
hash = "sha256-tBajxrAhV7R9VnAzUr7ONAYH9h8Uh/UUeu2YZAAotBo=";
};
postPatch = ''
substituteInPlace requirements.txt \
--replace "onnxruntime>=1.14,<2" "onnxruntime"
'';
propagatedBuildInputs = [
av
ctranslate2
huggingface-hub
onnxruntime
tokenizers
];
pythonImportsCheck = [
"faster_whisper"
];
# all tests require downloads
doCheck = false;
nativeCheckInputs = [
pytestCheckHook
];
preCheck = ''
export HOME=$TMPDIR
'';
meta = with lib; {
changelog = "https://github.com/guillaumekln/faster-whisper/releases/tag/v${version}";
description = "Faster Whisper transcription with CTranslate2";
homepage = "https://github.com/guillaumekln/faster-whisper";
license = licenses.mit;
maintainers = with maintainers; [ hexa ];
};
}

View File

@ -1,6 +1,5 @@
{ lib
, buildPythonPackage
, larynx
{ buildPythonPackage
, piper-tts
# build
, cython
@ -15,10 +14,10 @@
, torch
}:
buildPythonPackage rec {
inherit (larynx) version src meta;
buildPythonPackage {
inherit (piper-tts) version src meta;
pname = "larynx-train";
pname = "piper-train";
format = "setuptools";
sourceRoot = "source/src/python";
@ -35,13 +34,13 @@ buildPythonPackage rec {
'';
postBuild = ''
make -C larynx_train/vits/monotonic_align
make -C piper_train/vits/monotonic_align
'';
postInstall = ''
export MONOTONIC_ALIGN=$out/${python.sitePackages}/larynx_train/vits/monotonic_align/monotonic_align
export MONOTONIC_ALIGN=$out/${python.sitePackages}/piper_train/vits/monotonic_align/monotonic_align
mkdir -p $MONOTONIC_ALIGN
cp -v ./larynx_train/vits/monotonic_align/larynx_train/vits/monotonic_align/core.*.so $MONOTONIC_ALIGN/
cp -v ./piper_train/vits/monotonic_align/piper_train/vits/monotonic_align/core.*.so $MONOTONIC_ALIGN/
'';
propagatedBuildInputs = [
@ -54,7 +53,7 @@ buildPythonPackage rec {
];
pythonImportsCheck = [
"larynx_train"
"piper_train"
];
doCheck = false; # no tests

View File

@ -6,11 +6,11 @@
, espeak-ng
, onnxruntime
, pcaudiolib
, larynx-train
, piper-train
}:
let
pname = "larynx";
pname = "piper";
version = "0.0.2";
in
stdenv.mkDerivation {
@ -18,9 +18,9 @@ stdenv.mkDerivation {
src = fetchFromGitHub {
owner = "rhasspy";
repo = "larynx2";
rev = "refs/tags/v${version}";
hash = "sha256-6SZ1T2A1DyVmBH2pJBHJdsnniRuLrI/dthRTRRyVSQQ=";
repo = "piper";
rev = "70afec58bc131010c8993c154ff02a78d1e7b8b0";
hash = "sha256-zTW7RGcV8Hh7G6Braf27F/8s7nNjAqagp7tmrKO10BY=";
};
sourceRoot = "source/src/cpp";
@ -45,19 +45,19 @@ stdenv.mkDerivation {
runHook preInstall
mkdir -p $out/bin
install -m 0755 larynx $out/bin
install -m 0755 piper $out/bin
runHook postInstall
'';
passthru.tests = {
inherit larynx-train;
inherit piper-train;
};
meta = with lib; {
changelog = "https://github.com/rhasspy/larynx2/releases/tag/v${version}";
changelog = "https://github.com/rhasspy/piper/releases/tag/v${version}";
description = "A fast, local neural text to speech system";
homepage = "https://github.com/rhasspy/larynx2";
homepage = "https://github.com/rhasspy/piper";
license = licenses.mit;
maintainers = with maintainers; [ hexa ];
};

View File

@ -0,0 +1,29 @@
diff --git a/setup.py b/setup.py
index 1c0b2d2..bbff1d1 100644
--- a/setup.py
+++ b/setup.py
@@ -35,4 +35,9 @@ setup(
"Programming Language :: Python :: 3.10",
],
keywords="rhasspy wyoming whisper",
+ entry_points={
+ 'console_scripts': [
+ 'wyoming-faster-whisper = wyoming_faster_whisper:__main__.run'
+ ]
+ }
)
diff --git a/wyoming_faster_whisper/__main__.py b/wyoming_faster_whisper/__main__.py
index 5557cc5..bb9d69f 100755
--- a/wyoming_faster_whisper/__main__.py
+++ b/wyoming_faster_whisper/__main__.py
@@ -131,5 +131,9 @@ async def main() -> None:
# -----------------------------------------------------------------------------
-if __name__ == "__main__":
+def run():
asyncio.run(main())
+
+
+if __name__ == "__main__":
+ run()

View File

@ -0,0 +1,40 @@
{ lib
, python3
, fetchPypi
}:
python3.pkgs.buildPythonApplication rec {
pname = "wyoming-faster-whisper";
version = "0.0.3";
format = "setuptools";
src = fetchPypi {
pname = "wyoming_faster_whisper";
inherit version;
hash = "sha256-uqepa70lprzV3DJK2wrNAAyZkMMJ5S86RKK716zxYU4=";
};
patches = [
./faster-whisper-entrypoint.patch
];
propagatedBuildInputs = with python3.pkgs; [
ctranslate2
tokenizers
wyoming
];
pythonImportsCheck = [
"wyoming_faster_whisper"
];
# no tests
doCheck = false;
meta = with lib; {
description = "Wyoming Server for Faster Whisper";
homepage = "https://pypi.org/project/wyoming-faster-whisper/";
license = licenses.mit;
maintainers = with maintainers; [ hexa ];
};
}

View File

@ -0,0 +1,30 @@
diff --git a/setup.py b/setup.py
index 1355313..3b144c1 100644
--- a/setup.py
+++ b/setup.py
@@ -35,4 +35,9 @@ setup(
"Programming Language :: Python :: 3.10",
],
keywords="rhasspy wyoming piper",
+ entry_points={
+ 'console_scripts': [
+ 'wyoming-piper = wyoming_piper:__main__.run'
+ ]
+ }
)
diff --git a/wyoming_piper/__main__.py b/wyoming_piper/__main__.py
index f60cf13..a0a15f7 100755
--- a/wyoming_piper/__main__.py
+++ b/wyoming_piper/__main__.py
@@ -143,5 +143,9 @@ async def main() -> None:
# -----------------------------------------------------------------------------
-if __name__ == "__main__":
+def run():
asyncio.run(main())
+
+
+if __name__ == "__main__":
+ run()
\ No newline at end of file

View File

@ -0,0 +1,37 @@
{ lib
, python3
, fetchPypi
}:
python3.pkgs.buildPythonApplication rec {
pname = "wyoming-piper";
version = "0.0.3";
format = "setuptools";
src = fetchPypi {
pname = "wyoming_piper";
inherit version;
hash = "sha256-vl7LjW/2HBx6o/+vpap+wSG3XXzDwFacNmcbeU/8bOs=";
};
patches = [
./piper-entrypoint.patch
];
propagatedBuildInputs = with python3.pkgs; [
wyoming
];
pythonImportsCheck = [
"wyoming_piper"
];
doCheck = false;
meta = with lib; {
description = "Wyoming Server for Piper";
homepage = "https://pypi.org/project/wyoming-piper/";
license = licenses.mit;
maintainers = with maintainers; [ hexa ];
};
}

View File

@ -832,6 +832,7 @@ mapAliases ({
### L ###
larynx = piper-tts; # Added 2023-05-09
lastfmsubmitd = throw "lastfmsubmitd was removed from nixpkgs as the project is abandoned"; # Added 2022-01-01
latinmodern-math = lmmath;
letsencrypt = throw "'letsencrypt' has been renamed to/replaced by 'certbot'"; # Converted to throw 2022-02-22

View File

@ -9687,10 +9687,6 @@ with pkgs;
jump = callPackage ../tools/system/jump { };
larynx = callPackage ../tools/audio/larynx { };
larynx-train = with python3Packages; toPythonApplication larynx-train;
latex2html = callPackage ../tools/misc/latex2html { };
lazycli = callPackage ../tools/misc/lazycli { };
@ -11320,6 +11316,9 @@ with pkgs;
pim6sd = callPackage ../servers/pim6sd { };
piper-train = with python3Packages; toPythonApplication piper-train;
piper-tts = callPackage ../tools/audio/piper { };
phosh = callPackage ../applications/window-managers/phosh { };
phosh-mobile-settings = callPackage ../applications/window-managers/phosh/phosh-mobile-settings.nix { };
@ -20156,6 +20155,8 @@ with pkgs;
cpp-jwt = callPackage ../development/libraries/cpp-jwt { };
ctranslate2 = callPackage ../development/libraries/ctranslate2 { };
ubus = callPackage ../development/libraries/ubus { };
uci = callPackage ../development/libraries/uci { };
@ -36201,6 +36202,10 @@ with pkgs;
tts = callPackage ../tools/audio/tts { };
wyoming-faster-whisper = callPackage ../tools/audio/wyoming/faster-whisper.nix { };
wyoming-piper = callPackage ../tools/audio/wyoming/piper.nix { };
### GAMES
_1oom = callPackage ../games/1oom { };

View File

@ -158,6 +158,7 @@ mapAliases ({
jupyter_server = jupyter-server; # added 2023-01-05
Kajiki = kajiki; # added 2023-02-19
Keras = keras; # added 2021-11-25
larynx-train = piper-train; # added 2023-06-09
ldap = python-ldap; # added 2022-09-16
lammps-cython = throw "lammps-cython no longer builds and is unmaintained"; # added 2021-07-04
logilab_astng = throw "logilab-astng has not been released since 2013 and is unmaintained"; # added 2022-11-29

View File

@ -2218,6 +2218,10 @@ self: super: with self; {
ctap-keyring-device = callPackage ../development/python-modules/ctap-keyring-device { };
ctranslate2 = callPackage ../development/python-modules/ctranslate2 {
ctranslate2-cpp = pkgs.ctranslate2;
};
cu2qu = callPackage ../development/python-modules/cu2qu { };
cucumber-tag-expressions = callPackage ../development/python-modules/cucumber-tag-expressions { };
@ -3486,6 +3490,8 @@ self: super: with self; {
fastentrypoints = callPackage ../development/python-modules/fastentrypoints { };
faster-whisper = callPackage ../development/python-modules/faster-whisper { };
fastimport = callPackage ../development/python-modules/fastimport { };
fastjet = toPythonModule (pkgs.fastjet.override {
@ -5530,8 +5536,6 @@ self: super: with self; {
lark = callPackage ../development/python-modules/lark { };
larynx-train = callPackage ../development/python-modules/larynx-train { };
laspy = callPackage ../development/python-modules/laspy { };
laszip = callPackage ../development/python-modules/laszip { };
@ -7637,6 +7641,8 @@ self: super: with self; {
pipenv-poetry-migrate = callPackage ../development/python-modules/pipenv-poetry-migrate { };
piper-train = callPackage ../development/python-modules/piper-train { };
pip-api = callPackage ../development/python-modules/pip-api { };
pip-tools = callPackage ../development/python-modules/pip-tools { };