question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Support subprojects in a poetry project

See original GitHub issue

Background & Rationale

This request is inspired by RPM Package Manger’s capability to build subpackages from the same Spec File.

Here, I want to propose and discuss replication a version of this capability can be replicated within poetry to allow for simplified user experience for a python project maintainer, especially when either maintaining namespace packages and/or multi-project source trees. While strict project separation is a good thing in most cases, it might not always be the more pragmatic scenario for package maintainers.

For our purposes here, we can refer to each of theses packages as a subproject. And all subprojects are managed under a single poetry project. This means that there is only a single pyproject.toml file and a shared project root directory with either a shared source tree or independent source trees (subdirectory) for each subproject.

Description

Let us consider the scenario of multiple namespace packages being maintained in a single repository with the following structure.

    namespace-project/
    └── src
        └── namespace
            └── package
                ├── one
                │   └── __init__.py
                ├── three
                │   └── __init__.py
                └── two
                    └── __init__.py

Note that this will still apply even if different source directories exists within the root directory for each subproject.

Here the intention could be that we want to distribute 3 packages, namely, namespace-package-one , namespace-package-two and namespace-package-three.

For the purpose of this example, let us assume that namespace-package-three depends on namespace-package-one. The pyproject.toml file could look something like this.

New sections are annotated with comments detailing them and expected behaviour.

[build-system]
requires = ["poetry-core>=1.0"]
build-backend = "poetry.core.masonry.api"

[tool.poetry]
name = "namespace-package"
version = "1.0.0-alpha.0"
description = ""
authors = [
    "Bender Rodriguez <bender@planetexpress.com>"
]
license = "MIT"
readme = "README.md"
repository = "https://git.planetexpress.com/bender/python-namespace-package"
keywords = []
classifiers = [
    "Intended Audience :: Developers",
    "Operating System :: OS Independent",
    "License :: OSI Approved :: MIT License",
    "Programming Language :: Python :: 3 :: Only",
    "Programming Language :: Python :: 3.8",
]

# this section remains as is, but now specifies shared dependencies
[tool.poetry.dependencies]
python = "^3.8"

[tool.poetry.dev-dependencies]
pre-commit = "^2.1"
flake8 = "^3.7"
black = "^19.10b0"
pytest = "^5.2"

# the following are package specific section
[tool.poetry.packages.one]
name = "namespace-package-one"  # this is optional as name would be derrived from <project.name>-<package name from section>
description = ""  # this will overide the description from the project for this package
readme = "README.one.md"  # this will overide the readme from the project for this package
packages = [  # this is mandatory for sub-packages
    # any package not included in a sub-package is added to the base package (ie. "namespace-package")
    # if the "packages" property is not explicitly configured in the base
    { include = "namespace.package.one", from = "src" }
]

[tool.poetry.packages.one.dependencies]
ujson = "^1.35"

[tool.poetry.packages.one.dev-dependencies]
pytest-mock = "^2.0"

[tool.poetry.packages.two]
packages = [ 
    { include = "namespace.package.two", from = "src" }
]

[tool.poetry.packages.two.dependencies]
psycopg2 = "^2.8.4"

[tool.poetry.packages.two.dev-dependencies]
pytest-postgresql = "^2.3.0"

[tool.poetry.packages.three]
requires = [ # this enables us to specify the relationships between sub-packages
    "one" # this could also be namespace-package-one
]
packages = [ 
    { include = "namespace.package.two", from = "src" }
]

[tool.poetry.packages.three.dependencies]
aiohttp = "^3.5"

[tool.poetry.packages.three.dev-dependencies]
beautifulsoup4 = "^4.8"
aioresponses = "^0.6"
pytest-asyncio = "^0.10"

Under this scenario, the following might be what the cli commands look like. Current behaviour will remain unaltered as these are additive changes.

$ poetry add --package one <dependency>
.. <similar to current add output>

$ poetry packages list
namespace-package-one
namespace-package-two
namespace-package-three

$ poetry build
<builds all three packages>

$ poetry build --package one
<builds only namespace-package-one>

$ poetry publish --dry-run
...
Publishing namespace-package-one (1.0.0-alpha.0) to PyPI
  - Uploading namespace-package-one-1.0.0-alpha.0.tar.gz
  - Uploading namespace-package-one-1.0.0-alpha.0-py3-none-any.whl

Publishing namespace-package-two (1.0.0-alpha.0) to PyPI
  - Uploading namespace-package-two-1.0.0-alpha.0.tar.gz
  - Uploading namespace-package-two-1.0.0-alpha.0-py3-none-any.whl

Publishing namespace-package-three (1.0.0-alpha.0) to PyPI
  - Uploading namespace-package-three-1.0.0-alpha.0.tar.gz
  - Uploading namespace-package-three-1.0.0-alpha.0-py3-none-any.whl

Variations

The above is an initial though of how it might work. That said there are variations to this that should be discussed.

  1. Does a per-package dev-dependnecy section make sense? This only really makes sense if we want to allow for developing a single package at a time. However, this will become tricky in cases like here where “three” depends on “one”. This will mean that when developing “three”, dev dependencies for “one” should also be installed. If isolation is required, then multiple virtual environments will be required, which might be overkill for majority use cases for this feature.

  2. Will all packages be installed under PEP-0517? Is it even possible to install only specific package when being installed under PEP-0517? One possible solution might be to make use of “extras” here as a way of specifying which package if any to install, but default to all.

Extensions

  1. Optional Project Package As an extension to this, one might also want to optionally distribute a a namespace only package namespace-package (let’s call this the “project package” for now) that installs the core dependencies and also allow for “extras” as we do today without requiring the distribution of the entire source tree with the binary distribution.

This means that if someone does pip install namespace-package, the maintainer might expect the the following to be installed:

  1. The namespace namepace.package.
  2. Packages namespace-package-one and namespace-package-three, which are required for the “default” install.

An end-user can also install the remaining package, like so - pip install namespace-package[two] which simply will install a dependency namespace-package-two.

This behaviour might not be desired in all cases, and can be considered opt-in.

Issue Analytics

  • State:open
  • Created 3 years ago
  • Reactions:300
  • Comments:20 (6 by maintainers)

github_iconTop GitHub Comments

47reactions
kapiltcommented, Apr 18, 2020

I recently went through converting over a mono repo with several packages over to poetry, and thought it might be useful to share what we did, and pain points and bug work arounds. Although also recognizing this proposal would hopefully make it all obsolete 😃 Still this might provide some utility to those who want to do mono repos prior to native support in poetry.

first a few context/caveats, we don’t use namespace packages vs a common prefix, and our fs layout is little different. that’s non material to the techniques used, but perhaps relevant to the proposal.

main_pkg
tools/
   pkg_1
   pkg_2
   pkg_3
   ...  

at the moment all the packages under tools have dependencies on the main package declared as a path based dev-dependency.

[tool.poetry.dev-dependencies]
# setup in tree as a dev dependency                                                                                                                                                                                                                                                                                                                  
c7n = {path = "../..", develop = true}

i attempted to resolve it as a normal dependency caused a few issues with poetry build (issues #2046, partial fix #2047, also reported/pr by others).

so using as a dev dependency worked but also meant not using poetry directly as a build/publish tool to work around those issues and still needed the injection of the main_pkg as a regular project dep when publishing. we ended up using poetry metadata/api to generate setup/requirements for that purpose, converting dev dependencies to regular dependencies in the process. https://github.com/cloud-custodian/cloud-custodian/blob/master/tools/dev/poetrypkg.py#L121

unrelated to multi-project, but to the generation workaround, we ran into another issue that in that the masonry sdist builder didn’t really support markdown readmes (pr #1994)

for handling ergonomics simplicity around multiple commands that needed to update versions/ or release, we added in makefile targets to frontend,

pkg-update:
	poetry update
	for pkg in $(PKG_SET); do cd $$pkg && poetry update && cd ../..; done

One interesting consequence of source directory dependencies in poetry is that it break any attempts to distribute/publish a package even if they are dev deps. ie. per the pyproject.toml spec is that via the build-system PEP, poetry will be invoked during install. The invocation/installation of poetry as a build sys is transparently handled by pip. Simple resolution/parse of pyproject.toml dev dependencies will cause a poetry failure for an source distribution install, as installation of an sdist, is actually a wheel compilation.

As a result of this as a publishing limitation we only publish wheels instead of sdists which avoids the build system entirely, as a wheel is extractable installation container/format file.

we’re also maintaining compatibility with tox/setuptools ecosystem for compatibility with developer workflows, there’s a few more details on what we did here https://cloudcustodian.io/docs/developer/packaging.html

37reactions
xinbinhuangcommented, Jan 17, 2021

This proposal is really valuable! I wonder what’s the latest status of this? Is this currently being working on? I would love to devote some time to speed up the process if possible.

Read more comments on GitHub >

github_iconTop Results From Across the Web

The pyproject.toml file | Documentation | Poetry
Poetry supports the use of PyPI and private repositories for discovery of packages as well as for publishing your projects. By default, Poetry...
Read more >
Package Python Projects the Proper Way with Poetry
Revolutionize your development workflow with an elegant CLI to handle dependencies, environments, configuration, and packaging.
Read more >
Developers - Support subprojects in a poetry project - - Bountysource
This request is inspired by RPM Package Manger's capability to build subpackages from the same Spec File. Here, I want to propose and...
Read more >
poetry-multiproject-plugin - PyPI
This is a Python Poetry plugin, adding the build-project command. The command will make it possible to use relative package includes. This feature...
Read more >
pyproject.toml - Poetry configuration
toml files for this project and all its subprojects (such as the BookServer and the Runestone Components) specified by path dependencies. Therefore, you...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found