6

There is following structure in my python project:

├───pyproject.toml
└───mypackage
    │
    ├───lib
    │       localdep-0.2.0-py3-none-any.whl
    │       localdep-0.2.0.tar.gz
    └───service
            app.py
            home.py
            modules.py

I need to build mypackage using poetry and local dependency localdep from mypackage/lib/localdep-0.2.0... to be able to install mypackage just using simple pip install mypackage-0.1.0.tar.gz command without any additional files. I've tried to use path and file specifiers in pyproject.toml however I continuously get following error:

ERROR: Could not find a version that satisfies the requirement localdep(from mypackage==0.1.0) (from versions: none)

Current version of my pyproject.toml:

[build-system]
requires = [ "poetry>=0.12",]
build-backend = "poetry.masonry.api"

[tool.poetry]
name = "myproject"
version = "0.1.0"
description = "Simple demo project."
authors = ["Some Author"]
license = "MPL 2.0"

[tool.poetry.dependencies]
python = "3.7.3"
localdep = {file = "mypackage/lib/localdep-0.2.0-py3-none-any.whl"}

Does anyone know how to pass local dependency to pyproject.toml so poetry build will be able to package it in the correct way?

Alimantu
  • 63
  • 1
  • 6
  • I can't reproduce your bug, if I set up two projects in the same way as you it works as it should. The only issue I see is that the path to the package is `myproject/lib/...` when it should be `mypackage/lib/...`. Any chance that's the issue? – Arne Sep 24 '19 at 20:28
  • @Arne unfortunately it's just mistype here, so no, it's not the case. The problem occurs during of installation of `mypackage` using `pip` after building it with poetry. So it seems that pip search for this package in pypi instead of using it from local. So as I understand your build and installation after it works smooth and doesn't produce any errors? – Alimantu Sep 24 '19 at 21:26
  • I see, I initially misunderstood the problem. It's going to be a bit hard to write an answer here because it is unusual in python to bundle packages together with each other. In the few cases where it needs to be done because a dependency is absolutely unavailable the [venodring process in python](https://stackoverflow.com/questions/52538252/import-vendored-dependencies-in-python-package-without-modifying-sys-path-or-3rd) works by including the source files directly and just treating them as a submodule. – Arne Sep 25 '19 at 06:29

1 Answers1

5

The use case of poetry's local dependency syntax is different from what you need, so it can't solve your problem here.

From the usage examples in those docs you can see that the path points to packages that are not part of the package itself, they always leave the root first, like this: ../some/different/location. The whole construct is only useful during development, and its logic will be run with poetry install, not poetry build.

What you want is to bundle the local dependency together with your project so that pip will know during a deployment of your project's .whl where to pull the local dependency from. But since pyproject.toml does not get packaged together with the wheel metadata, the information where to get dependencies from is no longer available, so it can't work. A built package only knows what dependencies it has, not where to get them from. This philosophy might be a bit unusual coming from other languages where it is not unusual to bundle all dependencies together with your code.

So even if you are able to build your package to include another wheel, which by default doesn't work because setuptools only includes .py files in the sdist/bdist by default, there is no way for pip to know that the dependency is reachable. I see four options to solve your problem in a way that python supports.

  1. Use a version of your localdep that is on pyPI, or upload it there. But if that were a possibility, you would have probably not asked this question.
  2. If localdep is under your control and is only used by mypackage, write it to be a simple submodule of mypackage instead - read, the initial decision to make localdep its own package was overengineering, which may or may not be true.
  3. Use a way to vendor localdep that python understands. Take this guide for an in-depth explanation with all the considerations and pitfalls, or this hacky post if you want to get it to work without needing to really understand why or how.
  4. Deploy your package as a "wheelhouse".

What is a wheelhouse?

The wheelhouse part needs a little bit more text, but it might be closest to what you initially had in mind. In this approach, you don't need to include localdep in your project, and the whole mypackage/lib folder should best be deleted. localdep only needs to be installed into your python interpreter, which you can ensure by running pip freeze and checking the output for localdep's name and version.

That same output will then be used to build said wheelhouse with pip wheel -w wheelhouse $(pip freeze). If you don't want to include dev dependencies, delete your current virtualenv and set it up and enter it before running the wheel command with poetry install; poetry shell again.

For completeness' sake, you can build dist/myproject.whl with poetry build and throw it in there as well, then you can just use this wheelhouse to install your package wherever you like by running python -m pip install wheelhouse/* with whichever python you want.

Why use pip and not poetry for that?

Poetry is a dependency manager for developing packages, as such it doesn't deal with deployment issues as much and only targets them tangentially. This is usually fine, because pip, as we saw, is quite capable in that regard. pip is not that convenient during development, which is why poetry is nice, but poetry does not completely replace pip.

Community
  • 1
  • 1
Arne
  • 17,706
  • 5
  • 83
  • 99
  • thanks for your answer, I'm really appreciate it. Yeah, wheelhouse is seems to be pretty close to the goal I'm trying to reach - thing is that it creates wheels based on current OS/device setup meanwhile I'm pretty interested in creating kinda OS independent solution - so I'll be able to install result bundle on any of macOS/Win/Linux machine. Thing I'm looking at now is `[tools.poetry.scripts]` part of the `toml` config file. As it mentioned previously the only issue is to have kinda point `pip` where to search for local dependency. – Alimantu Sep 26 '19 at 17:33
  • Are any of your dependencies note [pure](https://packaging.python.org/guides/distributing-packages-using-setuptools/#id75)? Because if they are, they should be cross platform compatible. And just in case, the script section of the `pyproject.toml` doesn't run a script on install, it install part of your code as a callable script. – Arne Sep 27 '19 at 17:14
  • It's been a while, but since writing this post I've used [`pex`](https://www.pantsbuild.org/docs/pex-files) to turn wheelhouses into platform-independent runnable applications. If you're still interested in this topic I can extend the post with a "how to make a pex file from a poetry project" section or something similar. – Arne Feb 14 '23 at 08:19