Code Monkey home page Code Monkey logo

poetry-multiproject-plugin's Introduction

Poetry Multiproject Plugin

This is a Python Poetry plugin, adding the build-project and check-project commands.

CircleCI

CodeScene Code Health

Quality Gate Status

Download Stats

The build-project command will make it possible to use relative package includes. This feature is very useful for monorepos and when sharing code between projects.

The check-project command is useful to check that dependencies are added properly in a project. It uses the MyPy tool under the hood, and will output any errors from the static type checker.

Use cases

Microservices and apps

The main use case is to support having one or more microservices or apps in a Monorepo, and share code between the services with namespaced packages. The build-project command will collect the project-specific packages and build an installable artifact from it (i.e. a wheel or an sdist).

A basic building block for the Polylith Architecture

The Multiproject plugin makes it possible to organize Python projects according to the Polylith Architecture. The plugin is the foundation for the Python tools for the Polylith Architecture - also implemented as a Poetry plugin.

For more about Polylith, have a look at the Python-specific Polylith documentation.

Libraries?

Building libraries is also supported, but you will need to consider that the code will likely share the same top namespace with other libraries built from the same monorepo. It depends on your monorepo structure. This will likely be a problem when more than one of your libraries are installed into the same virtual environment.

Since Python libraries by default are installed in a "flat" folder structure, two libraries with the same top namespace will collide.

There is a way to solve this issue, by using the --with-top-namespace flag of the build-project command. See usage for libraries.

Usage

Navigate to the project folder (where the pyproject.toml file is).

Build a project:

poetry build-project

Check the code used in a project:

poetry check-project

Check the code, with a custom MyPy configuration to override the defaults:

poetry check-project --config-file <PATH-TO-MYPY.INI-CONFIG-FILE>

Usage for libraries

The build-project has a solution to the problem with top namespaces in libraries for Python 3.9 and more. You can choose a custom namespace to be used in the build process, by using the --with-top-namespace flag.

The command will organize the namespaced packages according to the custom top namespace, and more importantly, re-write the imports made in the actual source code. The re-organizing and re-writing is performed on the relative includes.

The build-project command, with a custom top namespace:

poetry build-project --with-top-namespace my_namespace

The build output

Default(no flag)
/my_package
   __init__.py
   my_module.py
Namespace(--with-top-namespace=my_namespace)
my_namespace/
    /my_package
       __init__.py
       my_module.py
Namespace with path(--with-top-namespace=my_namespace/subdir)
my_namespace/
    /subdir
        /my_package
           __init__.py
           my_module.py

And will re-write the relevant module(s):

Default(no flag)
from my_package import my_function
Namespace(--with-top-namespace=my_namespace)
from my_namespace.my_package import my_function
Namespace with path(--with-top-namespace=my_namespace/subdir)
from my_namespace.subdir.my_package import my_function
How is this done?

The code in this repo uses AST (Abstract Syntax Tree) parsing to modify source code. The Python built-in ast module is used to parse and un-parse Python code.

Installation

This plugin can be installed according to the official Poetry docs.

poetry self add poetry-multiproject-plugin

What does it do?

the poetry build-project command will:

  1. copy the actual project into a temporary folder.
  2. collect relative includes - such as include = "foo/bar", from = "../../shared" - and copy them into the temprary folder.
  3. generate a new pyproject.toml.
  4. run the poetry build command in the temporary folder.
  5. copy the built dist folder (containing the wheel and sdist) into the actual project folder.
  6. remove the temporary folder.

the poetry check-project command will:

  1. copy the actual project into a temporary folder.
  2. collect relative includes - such as include = "foo/bar", from = "../../shared" - and copy them into the temprary folder.
  3. generate a new pyproject.toml.
  4. run poetry install in the temporary folder.
  5. run poetry run mypy in the temporary folder.
  6. remove the temporary folder.

The default setting for the underlying MyPy configuration is:

--explicit-package-bases --namespace-packages --no-error-summary --no-color-output

How is it different from the "poetry build" command?

Poetry does not allow package includes outside of the project root.

# Note the structure of the shared folder: namespace/package

packages = [
    { include = "my_namespace/my_package", from = "../../shared" }
    { include = "my_namespace/my_other_package", from = "../../shared" }
]

This plugin will allow relative package includes. You will now be able to share code between projects.

Organizing code

An example Monorepo structure, having the shared code extracted into a separate folder structure:

projects/
  my_app/
    pyproject.toml (including selected shared packages)

  my_service/
    pyproject.toml (including selected shared packages)

shared/
  my_namespace/
    my_package/
      __init__.py
      code.py

    my_other_package/
      __init__.py
      code.py

A suggested structure, using Polylith:

workspace/
  bases/
  components/
  development/
  projects/

  poetry.lock

  pyproject.toml
  workspace.toml

  README.md

poetry-multiproject-plugin's People

Contributors

brejkarn avatar davidvujic avatar mirosta avatar ryaminal avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

poetry-multiproject-plugin's Issues

One use-case for including packages breaks with --with-top-namespace=top_namespace

I am running the following command to build my project:

poetry build-project --directory ./ --with-top-namespace=top_namespace

If my pyproject.toml has the following, things work as expected:

...
packages = [
    {include = "namespace/entrypoint",from = ".."},
]
...
[tool.poetry.scripts]
entrypoint = "namespace.entrypoint.task:run"

entrypoint package is now in top_namespace/namespace/entrypoint and the script points to top_namespace.namespace.entrypoint.task:run. Everything good!

But when I have the following in my pyproject.toml, things are not good:

...
packages = [
    {include = "namespace/entrypoint",from = "."},
]
...
[tool.poetry.scripts]
entrypoint = "namespace.entrypoint.task:run"

In this case, entrypoint is present in namespace/entrypoint but the script points to top_namespace.namespace.entrypoint.task:run.

Why does one setup work and the other does not?

Also, more surprisingly, this works-

...
packages = [
    {include = "namespace/entrypoint",from = "../project"},
]
...
[tool.poetry.scripts]
entrypoint = "namespace.entrypoint.task:run"

But from="../project" and from="." point to the same directory!

check-project: does not analyze code correctly

Describe the bug
A clear and concise description of what the bug is.

The check-project command does not work as intended when using it from a top folder and with the --directory option.

Support for `{ include = "*", from = "path" }` syntax

Is your feature request related to a problem? Please describe.
We are using packages using the syntax { include = "*", from = "path" } which is not supported. Currently build-project will throw a No such file or directory: error.

Describe the solution you'd like
It should behave as poetry behaves. For a * include it "simply" produces a pth file to add that path. So I think it would be sufficient to add ALL folders specified in the from directory (maybe filter based on presence of pyproject.toml?) to the temp directory.

Describe alternatives you've considered
Rewriting our packages to not use * in include, but given our specific setup this is almost impossible.

Additional context

Resolve dependencies from external package at build time

First of all, thank you for this plugin, which I find very useful.

I have a use case that I'd love to be able to handle with this plugin. I have a package outside of my project root, that have its own dependencies. I include it in my project using your plugin and if currently if is well embedded at build time, the additional dependencies are not added / resolved. The final package cannot therefore be installed correctly.

I found a plugin for ngx that do what I wish for here : https://betterprogramming.pub/poetry-python-nx-monorepo-5750d8627024 but I would like to avoid using ngx to manage Python project.

A solution (which is the solution used by the ngx plugin) could be to parse the dependencies from the foreign package and "poetry add" them in the temporary copy of the package at build time.

Is this request something you could consider adding to this plugin? If so, I could give a try, but I'm not familiar with poetry plugins yet.

Thank for your attention.

Regards,

Simon

Broken dist build paths when collecting shared packages in workspace

I think that this need some more work. When building, the dist will contain code that are in separate folders and that will probably not work when installing the build code as a dependency (entry point, imports that are one level only).

A possible solution:
The BuildIncludeFile.relative_to_source_root could return a custom path for the workspace scenario
Pull request #6

Rewrite relative package dependencies as regular dependencies

Is your feature request related to a problem? Please describe.
I have a multi package repository.

- package-a/
- package-b/
- package-c/
...

These packages can depend on one another in an acyclic fashion. These packages can also depend on 3rd party dependencies. All packages are versioned and published together.
I'd like to publish each of these packages separately to my package index and have the relative dependencies turned into regular transitive dependencies. The current behavior I observe with this plugin is that transitive dependencies are copied entirely in the wheel instead of being referred to by package coordinates in the wheel metadata.

Describe the solution you'd like
Publish the packages separately instead of folding everything into a fat package.

Describe alternatives you've considered
None.

Additional context
I'm coming from the jvm world where publishing modules this way is common. At my orgnanisation, we maintain several multi module repositories and publish all modules separately with the same version.

Make mypy an optional dependency or loosen the dep requirement

Is your feature request related to a problem? Please describe.
I have a large set of repos I'm moving to the mono repo structure. We're currently on the latest mypy which gets downgraded with the plugin being in the base env.

Describe the solution you'd like
I'm playing with creating a build venv for poetry but may run into issues with the CI/CD we use. Ideally we'd have mypy as optional and opt-in or allow the version to vary past 1.x. If it were optional I just wouldn't use the checker, we have a whole tox setup and pre-commit for this anyway

Describe alternatives you've considered
As above

Additional context
Many thanks for all the work here

Poetry groups and build-project

Our main pyproject.toml has several groups. For each deployment, we create different folders in the projects/ folder with specialized dependencies that are needed only for that project. This setup works well.

The problem is that for each project, there are many flavors and different environments it needs to work in. Getting all our projects working on these cross-product of flavors and environments is resulting in many project directories but more importantly a lot of manual editing. Adding a new dependency needs to update the pyproject.toml files in all the associated flavor-environment project folders.

I am not sure if this is something you have encountered or solved for. If so, we would love to learn how you solved it.

One thought is to have --with to pick up groups in build-project. I understand it is still not a viable solution since it changes what it means to be a poetry project - the build name comes from the name in pyproject.toml so how will you different between which --with commands were used to create the build, etc.

I would love to hear your thoughts. Thanks!

Missing `-f` option in 1.2

Describe the bug
The -f option has been removed in v1.2

To Reproduce

  • Install 1.2

Error Log

root@7a84c5cfeaad:/home/presets/echo# poetry self add poetry-multiproject-plugin==1.1.6

Updating dependencies
Resolving dependencies... (0.6s)

Writing lock file

Package operations: 4 installs, 0 updates, 0 removals

  • Installing mypy-extensions (1.0.0)
  • Installing typing-extensions (4.5.0)
  • Installing mypy (0.991)
  • Installing poetry-multiproject-plugin (1.1.6)
root@7a84c5cfeaad:/home/presets/echo# poetry build-project --help

Description:
  Builds a package, as a tarball and a wheel by default.

Usage:
  build-project [options]

Options:
  -f, --format=FORMAT        Limit the format to either sdist or wheel.
  -h, --help                 Display help for the given command. When no command is given display help for the list command.
  -q, --quiet                Do not output any message.
  -V, --version              Display this application version.
      --ansi                 Force ANSI output.
      --no-ansi              Disable ANSI output.
  -n, --no-interaction       Do not ask any interactive question.
      --no-plugins           Disables plugins.
      --no-cache             Disables Poetry source caches.
  -C, --directory=DIRECTORY  The working directory for the Poetry command (defaults to the current working directory).
  -v|vv|vvv, --verbose       Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug.
root@7a84c5cfeaad:/home/presets/echo# poetry self add poetry-multiproject-plugin~=1.1

Updating dependencies
Resolving dependencies... (0.3s)

Writing lock file

Package operations: 0 installs, 1 update, 0 removals

  • Updating poetry-multiproject-plugin (1.1.6 -> 1.2.0)
root@7a84c5cfeaad:/home/presets/echo# poetry build-project --help

Description:
  Builds a package, as a tarball and a wheel by default.

Usage:
  build-project [options]

Options:
      --with-top-namespace=WITH-TOP-NAMESPACE  To arrange relative includes, and to modify import statements.
  -h, --help                                   Display help for the given command. When no command is given display help for the list command.
  -q, --quiet                                  Do not output any message.
  -V, --version                                Display this application version.
      --ansi                                   Force ANSI output.
      --no-ansi                                Disable ANSI output.
  -n, --no-interaction                         Do not ask any interactive question.
      --no-plugins                             Disables plugins.
      --no-cache                               Disables Poetry source caches.
  -C, --directory=DIRECTORY                    The working directory for the Poetry command (defaults to the current working directory).
  -v|vv|vvv, --verbose                         Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug.
root@7a84c5cfeaad:/home/presets/echo# 

Expected behavior
Able to use -f sdist to specify that I only want an sdist to be built.

Screenshots
If applicable, add screenshots to help explain your problem.

Desktop (please complete the following information):

  • OS: Linux
  • Python version: 3.6.9
  • Poetry version: 1.3.2

Additional context
I think that adding the --with-top-namespace option has replaced the -f option instead of adding a second option.

missing support for the new "--directory" option

Describe the bug
A clear and concise description of what the bug is.
Since Poetry 1.3.x there is a possibility to add the --directory option. This is very useful for monorepos and makes folder navigation unnecessary. But that option does not currently work as expected for any of the commands in this plugin.

To Reproduce
Steps to reproduce the behavior:

  1. Open a terminal window
  2. Go to root of the project
  3. Run the command check-project or build-project with the directory option
  4. The actual project isn't checked or built.

Expected behavior
The check-project and build-project commands should to the actions on the directory pointed at with the new --directory option.

Poly check can't understand pillow dependency

Describe the bug
poetry poly check returns 🤔 Cannot locate PIL in my_package

To Reproduce
Steps to reproduce the behavior:

  1. Have an import like from PIL import Image
  2. Have a dependency like pillow = "^10.0.0"
  3. Run poetry poly check

Error Log

poetry poly check -vvv                     
Loading configuration file .../pypoetry/config.toml
🤔 Cannot locate PIL in my_package
ℹ my_package is importing skimage, PIL, numpy

Expected behavior
PIL == pillow

Desktop (please complete the following information):

  • OS: M1 MacOS Ventura 13.5.2
  • Python version: 3.9.16
  • Poetry version: 1.6.1

Additional context
I thought it was interesting that it does find scikit-image even though its import is skimage.

Check-project generates boatload of permission denied errors

Describe the bug
After setting up a polylith repo, with a first base and component in it, the check-project command generated some fixes, on first run and none on second run. However on both runs it ends with a long list of permission denied errors.

Error Log
Export of console session (windows under poetry shell). I had to redact some content, but I think it clearly seems to be looking into .git subfolders, where I don't think it should go.
scratch_1.txt

Expected behavior
Run without permission denied errors

Desktop (please complete the following information):

  • OS: Windows 11 Pro 22H2
  • Python version: 3.11.2
  • Poetry version: 1.5.1

build-project command fails due to TOML decode error with Poetry 1.5.0 on Windows

Describe the bug
I've been successfully running poetry and the multiproject plugin with development version of poetry (and with 1.4.2 before that). After updating to the newly released 1.5.0, there's a TOMLDecodeError exception thrown when reading the transient pyproject.toml when building a project.

The exception is thrown at lib\site-packages\tomli\_parser.py:127 in loads, and is thrown because the compatibility TOML library, tomli expects a newline character exactly, and doesn't handle the \r\n sequence. It seems to be an issue primarily with tomli, but could also be addressed by normalizing newlines when creating project files.

The relevant stack trace is:

   7  .poetry-venv\lib\site-packages\poetry_multiproject_plugin\commands\buildproject\project.py:88 in handle
        86│             self.rewrite_modules(project_path, top_ns)
        87│
     →  88│         self.prepare_for_build(project_path.absolute())
        89│
        90│         super(ProjectBuildCommand, self).handle()

   6  .poetry-venv\lib\site-packages\poetry_multiproject_plugin\commands\buildproject\project.py:73 in prepare_for_build
        71│
        72│     def prepare_for_build(self, path: Path):
     →  73│         project_poetry = Factory().create_poetry(path)
        74│
        75│         self.set_poetry(project_poetry)

The path comes from create.create_new_project_file.

I tried with Python versions 3.8.10 and 3.11.2 on Windows 10.

To Reproduce

  1. Create a virtual environment
  2. Install poetry==1.5.0 and poetry-multiproject-plugin (optionally the poety-polylith-plugin),
  3. Create a sample poetry project
  4. Create a project, for example with the polylith tool, for example at projects/test,
  5. Lock the project at project/test,
  6. Run the command poetry -vvv build-project -C project/test, (or to debug run python -m pdb -c continue -m poetry -vvv build-project -C project/test,
  7. Observe the error.

Error Log
The error should be roughly:

Loading the poetry-multiproject-plugin plugin
Loading the export plugin
Loading the poetry-polylith-plugin plugin
Using virtualenv: C:\Users\...\.poetry-venv
Using C:\Users\...\projects\test\pyproject.toml
Copied project & packages into temporary folder C:\Users\...\projects\.prepare_test
Generated C:\Users\...\projects\.prepare_test\pyproject.toml

TOMLDecodeError

  Expected newline or end of document after a statement (at line 1, column 14)

  at .poetry-venv\lib\site-packages\tomli\_parser.py:127 in loads
      123│             char = src[pos]
      124│         except IndexError:
      125│             break
      126│         if char != "\n":
    → 127│             raise suffixed_err(
      128│                 src, pos, "Expected newline or end of document after a statement"
      129│             )
      130│         pos += 1
      131│

On 3.11.2, with built-in tomllib the same error is thrown:

 at ...\3.11.2\Lib\tomllib\_parser.py:127 in loads
      123│             char = src[pos]
      124│         except IndexError:
      125│             break
      126│         if char != "\n":
    → 127│             raise suffixed_err(
      128│                 src, pos, "Expected newline or end of document after a statement"
      129│             )
      130│         pos += 1

Expected behavior
Loading the intermediate/transient project file during build-project should not fail due to TOML decoding errors.

The error does not appear when I insert the following line after components/project/create.py:11:

    generated = generate.generate_valid_dist_project_file(original, top_ns)
+   generated = generated.replace("\r\n", "\n")

Desktop

  • OS: Windows 10
  • Python version: 3.8.10 and 3.11.2
  • Poetry version: 1.5.0

another similar plugin

Hello,

I saw your poetry-core PR to add workspaces. Thanks and I hope it works out!

As I was looking through available solutions I also found this plugin.
I haven't had enought time yet to look in much detail but I thought you might be interested in seeing/comparing.

Anyway, thanks and have a good day!

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.