PEP 621: how to specify dependencies?

What the “RHS” values in dependencies = ... (and hence, optional-dependencies = ...) should be.

I made an initial draft PEP 631 - Dependency specification in pyproject.toml (I’ve never written a PEP btw)


Discussion of the PEP 508 PEP can be found at PEP 631 - Dependency specification in pyproject.toml.

1 Like

As a reminder, there is less than two weeks left to start discussions around some draft PEP. The PEP 508 camp have their draft and started discussions; I have not seen anything from the exploded table camp up as of today.


We are now one week out from needing a discussion started for an exploded table format in TOML if we are going to consider it.

/cc @sdispater, @finswimmer, @EpicWink as they seemed like proponents for the exploded table approach (sorry if I’m wrong about that assessment).

I’m ambivalent but not too invested. I’ll write up a basic PEP now

Edit: done. Discuss here

1 Like

Thought I would share this here as well. A few weeks ago, mostly to cater to my own curiosity, I created a (totally unscientific) survey to collect some feedback regarding the dependency specification options and share it via various means hoping it will reach a wider audience than this thread. Ended up getting some responses. Hopefully it has some useful insights for someone (most likely nothing new to the folks on this thread).


Thanks for doing this!

Can you share the examples from the second last question? They don’t appear in the results, and I don’t really want (all of us) to take the survey just to look at what the questions were.

My takeaway, unsurprisingly: the strongest responses were “we don’t need yet another way to do it”, nobody in the verbatims considered compatibility with existing formats (and a few people seemed to think that PEP 508 was “new”), and complaints about having to parse PEP 508 style didn’t acknowledge that they’d still need to parse the version constraint in all the likely TOML formats.

So despite the percentages seeming to suggest both are equally acceptable to end-users (2/3 of which maintain a package on PyPI), I’m not feeling swayed by the data.


I’d be careful generalising it like that. Especially since only 22 descriptive responses exist for 113 responses (at the time of writing this). And out of that 22, only less than 1/4 even indicate anything to that effect. And it should not be discounted that around 80% chose the TOML version over the PEP 508 version. And as you mentioned 2/3 of these are people who maintain packages on PyPI. Unsurprisingly when considering more that “simple” specification of dependency, this percentage bumps to over 90%.

Of course all this needs to be taken with a grain of salt because 113 responses does not cover the ecosystem. This, if anything highlights that the current way needs to change.

Personal Opinion: While, I definitely agree that having multiple ways can be troublesome, this needs to be considered as a transition that helps forge a better future for the language’s packaging ecosystem. Moving into a structured format has its advantages.

As for the examples I used. Here are the direct links.


This survey is welcomed but I have to say it has biased options and forces users to make incorrect picks. I find “I am familar with it, but never used it in any meaningful way.” more of a leading question because is the only thing you can pick if you fit into category “I used it because there was not other option (or used it and hated it)”.

Never heard of it / Never had to use it.
I am familar with it, but never used it in any meaningful way.
I make use of it regulary.

The way is phrased it it forces user hand and also produces misleading results. Even if the user figured it out that the middle is most appropriate it produces misleading results, mainly because there is no way to distinguish between users that are somehow familiar with that option but that do not like/support it. That is because “never used it in any meaningful way” is leads towards “that user didn’t had any clue about the subject”.

Sadly filling the form reminded me a little bit of Piers Morgan. Wouldn’t a simple “I am relatively familiar with” be less leading?


Survey design is hard. I really appreciate the fact that @abn did this at all. We need to be careful how we use the results, certainly, but given how little hard data we have in any of this discussion, I think that even with any flaws it has, this is really useful information to add to the discussion.

I’d be perfectly happy to see other surveys, too. With extra data we can smooth over any biases that individual surveys might have.


Yep, fair response. Reading verbatims is more of an art form than a science though - numbers don’t really mean anything because they’re essentially anecdotes.

Thanks! These are all good examples to have used. My only concern (which I’ll withdraw if these aren’t how they were actually displayed in the survey) is that the colour contrast is terrible :slight_smile: and anyone who chose the better looking option is almost certainly being influenced by the PEP 508 examples being almost unreadable. The TOML examples I feel are also unfair, because while the colours make the structure more obvious, it makes it harder to read the actual information.

It might be interesting to convert these into a timed comprehension test, but then again, dependency specifications are generally write once, update rarely, read occasionally, so if anyone has the time and motivation to run this kind of study I’ll happily suggest some other interfaces that would be more beneficial to do instead besides this one :slight_smile:

1 Like

This is a real-world example port of what docker-compose defines, which both PEPs show.

PEP 508

dependencies = [
  'cached-property >= 1.2.0, < 2',
  'distro >= 1.5.0, < 2',
  'docker[ssh] >= 4.2.2, < 5',
  'dockerpty >= 0.4.1, < 1',
  'docopt >= 0.6.1, < 1',
  'jsonschema >= 2.5.1, < 4',
  'PyYAML >= 3.10, < 6',
  'python-dotenv >= 0.13.0, < 1',
  'requests >= 2.20.0, < 3',
  'texttable >= 0.9.0, < 2',
  'websocket-client >= 0.32.0, < 1',

  # Conditional
  'backports.shutil_get_terminal_size == 1.0.0; python_version < "3.3"',
  'backports.ssl_match_hostname >= 3.5, < 4; python_version < "3.5"',
  'colorama >= 0.4, < 1; sys_platform == "win32"',
  'enum34 >= 1.0.4, < 2; python_version < "3.4"',
  'ipaddress >= 1.0.16, < 2; python_version < "3.3"',
  'subprocess32 >= 3.5.4, < 4; python_version < "3.2"',

socks = [ 'PySocks >= 1.5.6, != 1.5.7, < 2' ]
tests = [
  'ddt >= 1.2.2, < 2',
  'pytest < 6',
  'mock >= 1.0.1, < 4; python_version < "3.4"',

Exploded table

cached-property = { version = '>= 1.2.0, < 2' }
distro = { version = '>= 1.2.0, < 2' }
docker = { extras = [ 'ssh' ], version = '>= 4.2.2, < 5' }
docopt.version = '>= 0.6.1, < 1'
jsonschema.version = '>= 2.5.1, < 4'
PyYAML.version = '>= 3.10, < 6'
python-dotenv = { version = '>= 0.13.0, < 1' }
requests = { version = '>= 2.20.0, < 3' }

# Conditional
'backports.shutil_get_terminal_size' = { version = '== 1.0.0', markers = 'python_version < "3.3"' }
colorama.version = '>= 0.4, < 1'
colorama.markers = 'sys_platform == "win32"'

version = '>= 0.9.0, < 2'

version = '>= 0.32.0, < 1'

version = '>= 3.5, < 4'
markers = 'python_version < "3.5"'

version = '>= 0.4, < 1'
markers = 'sys_platform == "win32"'

version = '>= 1.0.4, < 2'
markers = 'python_version < "3.4"'

version = '>= 1.0.16, < 2'
markers = 'python_version < "3.3"'

version = '>= 3.5.4, < 4'
markers = 'python_version < "3.2"'

socks = { PySocks = { version = '>= 1.5.6, != 1.5.7, < 2' ] } }

ddt = { version = '>= 1.2.2, < 2' }
pytest = { version = '< 6' ] }
mock = { version = '>= 1.0.1, < 4' }

markers = 'python_version < "3.4"'
1 Like

Thanks for posting a real-life comparison. It’s a useful checkpoint on what we’re debating about.

I presume it won’t come as much surprise to anyone here, but personally I find the PEP 508 version very easy to read, and the TOML one really difficult - there’s too much clutter from the extra syntax.

Of course, readability is only one aspect. How hard is it to write something like this is also a question. Personally, I don’t find PEP 508 syntax up to the point of 'docker[ssh] >= 4.2.2, < 5' at all difficult to write - we see it all the time in pip command lines, in requirements.txt files, etc, none of which are going away. For more complex stuff like markers or extras, I’d have to look up the syntax. But for the TOML version, I would too (and it’s quite possible I’d have to look up the TOML version of simpler cases as well, where I know PEP 508).

To put it another way, simple cases in PEP 508 syntax have the significant advantage of “looks like a requirements.txt line”. And unless we’re going to deprecate PEP 508 totally, that’s a pretty significant difference between the two options.


Definitely anecdotal and not as well-formed as @abn’s survey (great job again) but here’s a minor survey of Datadog’s internal Python slack channel (I’ll try to keep updating the image):

Here’s a particularly poignant feedback:

'backports.shutil_get_terminal_size' = { version = '== 1.0.0', markers = 'python_version < "3.3"' } should be enough to discard the 2nd solution IMO

It feels like a really disingenuous way of representing each PEP (one being still in discussion). The TOML representation purposefully mixes notations, where there is no need to, which makes it less readable.

I have yet to mention it of the TOML PEP discussion but we could have a shorthand syntax for version-only cases to avoid clutter. At this point the TOML representation would be (still provisional though):

cached-property = ">= 1.2.0, < 2"
distro = ">= 1.2.0, < 2"
docker = { version = ">= 4.2.2, < 5", extras = ["ssh"] }
docopt = ">= 0.6.1, < 1"
jsonschema = ">= 2.5.1, < 4"
PyYAML = ">= 3.10, < 6"
python-dotenv = ">= 0.13.0, < 1"
requests = ">= 2.20.0, < 3"
texttable = ">= 0.9.0, < 2"
websocket-client = ">= 0.32.0, < 1"

# Conditional
"backports.shutil_get_terminal_size" = { version = "== 1.0.0", markers = "python_version < '3.3'" }
"backports.ssl_match_hostname" = { version = ">= 3.5, < 4", markers = "python_version < '3.5'"}
colorama = { version = ">= 0.4, < 1", markers = "sys_platform == 'win32'" }
enum34 = { version = ">= 1.0.4, < 2", markers = "python_version < '3.4'" }
ipaddress = { version = ">= 1.0.16, < 2", markers = "python_version < '3.3'" }
subprocess32 = { version = ">= 3.5.4, < 4", markers = "python_version < '3.2'" }

I simply copy/pasted the section from each PEP…


Oh, my bad then :sweat_smile:

It seems there is still work to do to iron out the TOML PEP. Like I said I still need to catch up on it and give feedback to improve it.

Again I am sorry for that comment but I think we should hold off on making comparisons just yet while it’s still a work in progress.

1 Like

You’re right, I deliberately mixed notations to showcase the flexibility of TOML, when perhaps I should have presented the most readable option. Should have followed the KISS paradigm.

A quick example of something that nearly achieves this is dotted-key notation

cached-property.version = ">= 1.2.0, < 2"

There’s immediately a likely point of confusion for users there, however, for projects with dots in the name (which is really an issue in all situations in the exploded table form)

"backports.shutil_get_terminal_size".version = "== 1.0.0"

The only project I had come across other than backports which had dots was ruamel.yaml, so perhaps it’s not common enough to be an issue, and can be solved via documentation in the project’s installation instructions

Probably should have put this post in the PEP’s discussion topic

Ultimately, the PEP needs to present the agreed-upon option. If there’s no agreed-upon option yet, then framing it as a PEP was probably misleading (certainly to people who are not actively pushing for a TOML format, who are basically waiting for the people who are to come up with a consensus). If it’s still just a starter for discussion, then yes, it probably is too early to be comparing the two options.

Brett’s proposal was that there should be “a draft proposal being actively discussed as a topic here”. I take that as meaning that the people in favour of the expanded approach should have reached at least rough consensus on what they want to be considered as their proposal. It sounds like there’s still some work to be done to get that rough consensus. I’d consider the best time to post a draft PEP to be once you have that consensus on what you want to say, personally…

At some point we really do need to limit the decision to just two proposals - PEP 508 and some well-defined “expanded” syntax. If we can’t get to that point, we’ll need to decide what to do - drop the whole thing, go with PEP 508 syntax because it’s the only concrete proposal we have, or let things drift on for even longer. I personally think the debating has gone on long enough, and we need to make a decision and be done with it.