[openstack-dev] PBR and Pipfile

Monty Taylor mordred at inaugust.com
Mon Apr 9 21:05:41 UTC 2018


On 04/08/2018 04:10 AM, Gaetan wrote:
> Hello OpenStack dev community,
> 
> I am currently working on the support of Pipfile for PBR ([1]), and I 
> also follow actively the work on pipenv, which is now in officially 
> supported by PyPA.

Awesome - welcome! This is a fun topic ...

> There have been recently an intense discussion on the difficulties about 
> Python libraries development, and how to spread good practices [2] on 
> the pipenv community and enhance its documentation.
> 
> As a user of PBR, and big fan of it, I try to bridge the link between 
> pbr and pipenv (with [1]) but I am interested in getting the feedback of 
> Python developers of OpenStack that may have much more experience using 
> PBR and more generally packaging python libraries than me.

Great - I'll comment more on this a little later.

> The main point is that packaging an application is quite easy or at 
> least understandable by newcomers, using `requirements.txt` or 
> `Pipfile`+ `Pipfile.lock` with pipenv. At least it is easily "teachable".
> Packaging a library is harder, and require to explain why by default 
> `requirements.txt`(or `Pipfile`) does not work. Some "advanced" 
> documentation exists but it still hard to understand why Python ended up 
> with something complex for libraries ([3]).
> One needs to ensure `install_requires`declares the dependencies to that 
> pip can find them during transitive dependencies installation (that is, 
> installing the dependencies of a given dependency). PBR helps on this 
> point but some does not want its other features.

In general, as you might imagine, pbr has a difference of opinion with 
the pypa community about requirements.txt and install_requires. I'm 
going to respond from my POV about how things should work - and how I 
believe they MUST work for a project such as OpenStack to be able to 
operate.

There are actually three different relevant use cases here, with some 
patterns available to draw from. I'm going to spell them out to just 
make sure we're on the same page.

* Library
* Application
* Suite of Coordinated Applications

A Library needs to declare the requirements it has along with any 
relevant ranges. Such as "this library requires 'foo' at at least 
version 2 but less than version 4". Since it's a library it needs to be 
able to handle being included in more than one application that may have 
different sets of requirements, so as a library it should attempt to 
have as wide a set of acceptable requirements as possible - but it 
should declare if there are versions of requirements it does not work 
with. In Pipfile world, this means "commit Pipfile but not 
Pipfile.lock". In pbr+requirements.txt it means "commit the 
requirements.txt with ranges and not == declared."

An Application isn't included in other things, it's the end point. So 
declaring a specific set of versions of things that the application is 
known to work in addition to the logical requirement range is considered 
a best practice. In Pipfile world, this is "commit both Pipefile and 
Pipfile.lock". There isn't a direct analog for pbr+requirements.txt, 
although you could simulate this by executing pip with a -c 
constraints.txt file.

A Suite of Coordinated Applications (like OpenStack) needs to 
communicate the specific versions the applications have been tested to 
work with, but they need to be the same so that all of the applications 
can be deployed side-by-side on the same machine without conflict. In 
OpenStack we do this by keeping a centrally managed constraints file [1] 
that our CI system adds to the pip install line when installing any of 
the OpenStack projects. A person who wants to install OpenStack from pip 
can also choose to do so using the upper-constraints.txt file and they 
can know they'll be getting the versions of dependencies we tested with. 
There is also no direct support for making this easier in pbr. For 
Pipfile, I believe we'd want to see is adding support for --constraints 
to pipenv install - so that we can update our Pipfile.lock file for each 
application in the context of the global constraints file. This can be 
simulated today without any support from pipenv directly like this:

   pipenv install
   $(pipenv --venv)/bin/pip install -U -c 
https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt 
-r requirements.txt
   pipenv lock

> There is also works on PEP around pyproject.toml ([4]), which looks 
> quite similar to PBR's setup.cfg. What do you think about it?

It's a bit different. There is also a philosophical disagreement about 
the use of TOML that's not worth going in to here - but from a pbr 
perspecitve I'd like to minimize use of pyproject.toml to the bare 
minimm needed to bootstrap things into pbr's control. In the first phase 
I expect to replace our current setup.py boilerplate:

setuptools.setup(
     setup_requires=['pbr'],
     pbr=True)

with:

setuptool.setup(pbr=True)

and add pyproject.toml files with:

[build-system]
requires = ["setuptools", "wheel", "pbr"]

This will allow us to reasonably have projects declare minimum ranges on 
the pbr depend - and can allow us to give pbr dependencies (which is 
impossible today due to how setup_requires works) If we made setuptools 
and wheel depends of pbr,we could redue the pyproject.toml file to:

[build-system]
requires = ["pbr"]

but we need to test to make sure that works first.

Once pep517 is implemented, we can implement a hook in pbr and add a 
line to pyproject.toml in all of the projects, something like:

build-backend = "pbr.core:build"

Come to think of it, we could go ahead and implement pep517 support in 
pbr today and go ahead and start having the pbr pyproject.toml file to be:

[build-system]
requires = ["pbr"]
build-backend = "pbr.core:build"

We'll have to keep the setup.py files until such a time as pip has full 
517 supported added.

> My opinion is this difference in behaviourbetween lib and app has 
> technical reasons, but as a community we would gain a lot of unifying 
> both workflows. I am using PBR + a few hacks [5], and I am pretty 
> satisfied with the overall result.

There are two topics your pbr patch opens up that need to be covered:

* pbr behavior
* dependencies

** pbr behavior **

I appreciate what you're saying about unifying the lib and app workflow, 
but I think the general pattern across the language communities 
(javascript and rust both have similar patterns to Pipefile) is that the 
two different options are important. We may just need to have a better 
document - rust has an excellent description:

https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html

In any case, I think what pbr should do with pipfiles is:

* If pbr discovers a Pipfile and no Pipfile.lock, it should treat the 
content in the packages section of Pipfile as it currently does with 
requirements.txt (and how you have done in the current patch)

* If pbr discoves a Pipfile.lock, it should treat the content in 
Pipfile.lock as it currently does requirements.txt. This way if someone 
commits a Pipfile.lock because they have chosen the application 
workflow, pbr will behave as they would expect.

Then, we either need to:

* Add support to pipfile install for specifying a pip-style constraints file

* Add support to pipfile install for specifying a constraints file that 
is in the format of a Pipfile.lock - but which does the same thing.

* Write a pbr utility subcommand for generating a Pipfile.lock from a 
Pipfile taking a provided constraints file into account.

We may also want to write a utility for creating a Pipefile and/or lock 
from a pbr-oriented requirements.txt/test-requirements.txt. (it should 
use pipfile on the backend of course) that can do the appropriate 
initial dance.

** dependencies **

The pep518 support in pip10 is really important here. Because ...

We should not vendor code into pbr.

While vendoring code has been found to be acceptable by other portions 
of the Python community, it is not acceptable here.

Once pip10 is released next week with pyproject.toml support, as 
mentioned earlier, we'll be able to start using (a reasonable set) of 
dependencies as is appropriate. In order to ensure backwards compat, I 
would recommend we do the following:

* Add toml and pipfile as depends to pbr
* Protect their imports with a try/except (for people using old pip 
which won't install any depends pbr has)
* Declare that pbr support for Pipfile will only work with people using 
pip>=10 and for projects that add a pyproject.toml to their project 
containing

   [build-system]
   requires = ["pbr"]

* If pbr tries to import toml/pipfile and fails, it should fall back to 
reading requirements.txt (this allows us to keep backwards compat until 
it's reasonable to expect everyone to be on pip10)

To support that last point, we should write a utility function, let's 
call it 'pbr lock', with the following behavior:

* If a Pipfile and a Pipfile.lock are present, it runs:

   pipfile lock -r

* If there is no Pipfile.lock, simply read the Pipfile and write the 
specifiers into requirements.txt in non-pinned format.

This will allow pbr users to maintain their projects in such a way as to 
be backwards compatible while they start to use Pipfile/Pipefile.lock

We MAY want to consider adding an option flag to setup.cfg, like:

   [pbr]
   type = application

or

   [pbr]
   type = library

for declaring to pbr which of Pipfile / Pipfile.lock should pbr pay 
attention to, regardless of which files might be present. I'm not sure 
whether that would be better or worse than inferring behavior from the 
presence of files. Of course, we could have the default behavior if the 
config setting isn't there to be to infer behavior from presence of 
files, but have the config setting for people who want to be explicit - 
and in the docs just don't mention omitting the setting - tell people to 
choose one or the other. What do you think?

> So, in short, I simply start a general thread here to retrieve your 
> general feedback around these points.
> 
> Thanks for your feedbacks


[1] 
http://git.openstack.org/cgit/openstack/requirements/tree/upper-constraints.txt

> Gaetan
> 
> [1]: https://review.openstack.org/#/c/524436/
> [2]: https://github.com/pypa/pipenv/issues/1911
> [3]: https://docs.pipenv.org/advanced/#pipfile-vs-setup-py
> [4]: https://www.python.org/dev/peps/pep-0518/
> [5]: library:
>    - pipenv to maintain Pipfile and Pipfile.lock
>    - Pipfile.lock not tracked (local reproductivity),
>    - pipenv-to-requirements [6] to generate a `requirements.txt` without 
> version freeze, also tracked
> applications:
>    - pipenv to maintain Pipfile and Pipfile.lock
>    - Pipfile.lock not tracked (global reproductivity),
>    - pipenv-to-requirements [6] to generate a `requirements.txt` and 
> `requirements-dev.txt` with version freeze, both tracked
> The development done with [1] should allow to get rid of [6].
> 
> [6] https://github.com/gsemet/pipenv-to-requirements
> -----
> Gaetan
> 
> 
> 
> __________________________________________________________________________
> OpenStack Development Mailing List (not for usage questions)
> Unsubscribe: OpenStack-dev-request at lists.openstack.org?subject:unsubscribe
> http://lists.openstack.org/cgi-bin/mailman/listinfo/openstack-dev
> 




More information about the OpenStack-dev mailing list