Hi all. Has anyone encountered issues with local p...
# development
t
Hi all. Has anyone encountered issues with local poetry libraries not getting detected by pants? I have a monorepo setup using poetry that successfully creates poetry lock files with all the dependencies I need, including my local libraries. However when I try to create pants lock files for my projects, my local libraries are always omitted. Should I be ignoring my pants lock files and basing my resolve off of my exported poetry requirements? I can't seem to find a reason in the docs as to why pants is missing or not parsing my local library pathing.
h
I'm not a poetry expert, so for clarification: by "local libraries" you mean that there is some way to configure poetry to consume local code as an editable install or similar?
t
Ah yes exactly. I have some local code with various utility functions shared throughout multiple other projects within my codebase. Within my
pyproject.toml
file for one of those projects,
my_project
, I have a relative path to an editable install:
my_library = { path = ".../.../.../libraries/my_library", develop = true}
, The usual poetry commands, poetry lock and install, correctly build this local library and let me use it throughout my project. However running
pants generate-lockfiles --resolve=my_project
omits
my_library
.
c
Clarifying the goal here (I work with Stefan): • We're in the process of adopting/integrating pants into a monorepo structure. The main language in the repo right now is python ◦ Until we've fully integrated / adopted pants, we want to allow developers to use "regular" python tools: pip, virtual environments, etc. • We have a couple "projects" in the monorepo already. We're working on setting up some internal "libraries" as well ◦ Libraries can use / reference other libraries ◦ Projects can use / reference libraries ◦ Nothing can use / reference projects What we're trying to do: Support local imports / dependencies using
pants
and "regular" python tools: pip, virtual environments, etc. Monorepo structure is currently something like this:
Copy code
.
├── 3rdparty    (will eventually hold references to all external libraries)
│   ├── java
│   │   └── ivy.xml
│   └── python
│       └── requirements.txt
└── src
    ├── libs    (These can import one another; CAN NOT import projects)
    │   ├── java
    │   │   ├── library_1
    |   |   │   └── ...
    │   │   └── library_2
    |   |       └── ...
    │   │
    │   └── python    
    │        ├── my_lib_1
    │        |   ├── pyproject.toml    (and/or poetry lock file, whatever)
    │        |   ├── ...
    │        |   ├── my_lib_1
    │        |   |       ├── __init__.py
    │        |   |       └── ...
    │        |   └── tests
    │        |       └── ...
    │        └── my_lib_2
    │            ├── pyproject.toml    (and/or poetry lock file, whatever)
    │            ├── ...
    │            ├── my_lib_2
    │            |       ├── __init__.py
    │            |       └── ...
    │            └── tests
    │                └── ...
    │
    └── projects    (These can import from libs, and CAN NOT import each other)
        ├── java
        │   ├── project_1
        |   │   └── ...
        │   └── project_2
        |       └── ...
        │
        └── python
            ├── project_1
            │   ├── pyproject.toml    (and/or poetry lock file, whatever)
            │   ├── project_1
            │   |   ├── __init__.py
            |   │   ├── ...
            |   └── tests
            |       └── ...
            └── project_2
                ├── pyproject.toml    (and/or poetry lock file, whatever)
                ├── project_2
                |   ├── __init__.py
                │   ├── ...
                └── tests
                    └── ...
Currently, each project or library has its own pyproject.toml / requirements file that specifies third-party dependencies and local dependencies. So, for example, the pyproject for
project_1
might look something like this:
Copy code
# pyproject.toml

dependencies = [
    "flask",

    # Internal tools
    "lib_1 @ file:///absolute/path/to/src/libs/my_lib_1"
]
Each of our developers will have a different path for lib_1 It seems like pants supports some relative pathing, or path templating: https://www.pantsbuild.org/2.19/docs/python/overview/third-party-dependencies#local-requirements To support "regular" pip-like functionality we've tried: • hatch/hatchling, which supports local dependencies: https://hatch.pypa.io/latest/config/dependency/#local • Stefan is currently also looking at poetry, which would support local dependencies as well https://python-poetry.org/docs/dependency-specification/#path-dependencies
Using hatchling as the build backend worked with absolute paths, for both
pants
and
pip
But the paths vary from developer to developer, and in CI
I'm wondering if we can write a plug-in that addresses our use case. I think what we want is to give pants the ability to understand a relative path that hatchling (or poetry, or whatever other build tool) understands. I'm going to just use hatchling in the example for now, since it's pretty lightweight and has simple(r) syntax. In pyproject.toml we'd specify a library as something like:
Copy code
# pyproject.toml

dependencies = [
    "flask",

    # In the string below, hatchling expands {root:parent:parent:parent:uri}
    # into something like file:///absolute/path/to/src/libs/my_lib_1
    # where:
    #  - 'root' is the location of the pyproject.toml file
    #  - 'parent' is equivalent to something like '/..' on a linux system
    #  - 'uri' yields the normalized absolute URI path prefixed by 'file:'
    "lib_1 @ {root:parent:parent:parent:uri}/libs/python/my_lib_1"
]
And we'd write a pants plug-in that can interpret a requirement in that format to something pants understands. I think this seems like something a plug-in could be used for?
h
I see. So, Pants is designed to consume internal code directly from the source tree, without any concept such as an "editable install" or any other way of creating a fake distribution. If your source roots are set up correctly then that side of things should "just work"...
Let me dive more closely into the info you provided and see if that clarifies the issue for me
But basically local code indeed should not be in lockfiles, those are for 3rd party code that is consumed by resolving requirements and downloading wheels.
local code just "is"...
So, for example, it looks like each of your
src/libs/python/my_lib_*
should be a source root, assuming that the nested
my_lib_*
dirs are the top-level packages). And then pants will know how to map imports of them to those source files, and include them in the necessary contexts.
And everything local is consumed at HEAD, rather than there needing to be a publishing/installing step (not even a "local publish" of an editable install).
Of course there is still the question of how to deploy this code. It sounds like you want to, at least for now, still publish each library as a wheel to some internal artifact repo?
Anyway, is this making sense?
Also, adding your use case in https://github.com/pantsbuild/pants/discussions/20897 would be helpful!
c
Benjy - thanks for the thoughts. I'll take a look and report back
Closing the loop: Where we started: • Our current approach for sharing code in the monorepo is to: ◦ Specify libraries as dependencies in pyproject.toml file ◦ Use hatchling as our build backend, which supports specifying local filepaths • We're beginning to adopt pants in our monorepo. ◦ At this point, just linting and testing. And only on a subset of the monorepo. ◦ Using other tools / processes for building deployables • Trying to get pants and hatchling to play nice together, since we're trying to support two use-cases while rolling pants out What we ended up with: Writing a pants plugin that alters how some of the pyproject parsing works. Namely, skipping dependencies that look like hatchling local dependencies So with a pyproject that looks something like below, pants does the "right things for pants" and only tracks
flask
in our lockfile. And when we pip install from the pyproject, hatchling grabs flask and the local library and installs them in a virtual environment.
Copy code
# pyproject.toml

dependencies = [
    "flask",
    # Dependencies that look like the one below get skipped
    "lib_1 @ {root.parent.parent.uri}/path/to/a/lib"
]
I'm not sure overriding those behaviors was something the plug-in system was intended to support, but it seems to work. At least for now 🙂 Hoping it's a short-term shim while we roll pants out across our whole monorepo 🤞 Thanks again @happy-kitchen-89482!