Skip to content

Commit e6af5f6

Browse files
authored
Add: clarification for version specs for deps in pyproject.toml
Version specification in main text of `tutorials/pyproject-toml.md`
2 parents 5033703 + 6bb0f9d commit e6af5f6

File tree

1 file changed

+54
-18
lines changed

1 file changed

+54
-18
lines changed

tutorials/pyproject-toml.md

Lines changed: 54 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,10 @@ license = {file = "LICENSE"}
296296
```
297297
### Step 3: Specify Python version with `requires-python`
298298

299-
Finally, add the `requires-python` field to your `pyproject.toml` `[project]` table. The `requires-python` field, helps pip understand the lowest version of Python that you package supports when it's installed. It is thus a single value.
299+
Add the `requires-python` field to your `pyproject.toml` `[project]` table.
300+
The `requires-python` field helps pip identify which Python versions that your package supports.
301+
It is set to a single value.
302+
The [packaging specification](https://packaging.python.org/en/latest/specifications/core-metadata/#core-metadata-requires-python) defines`requires-python` as a string that uses version specifiers. Most projects will specify the oldest Python version supported by the package. In some advanced cases, an upper bound is set to indicate which future Python versions, if any, will be supported.
300303

301304

302305
{emphasize-lines="22"}
@@ -334,8 +337,42 @@ The `dependencies =` section contains a list (or array in the toml language) of
334337
[build-system] # <- this is a table
335338
requires = ["hatchling"] # this is an array (or list) of requirements
336339
```
340+
337341
dependencies are added in an array (similar to a Python list) structure.
338342

343+
```toml
344+
dependencies = ["numpy", "requests", "pandas", "pydantic"]
345+
```
346+
347+
A dependency can be limited to specific versions using a **version specifier.**
348+
If the dependency has no version specifier after the dependency name, your package can use any version of the dependent package.
349+
Code changes over time, bugs are fixed, APIs change, and so it's good to be clear about which version of the dependency you wrote your code to be compatible with - a package you wrote this year probably isn't compatible with numpy v0.0.1!
350+
351+
[Learn more about various ways to specify ranges of package versions here.](https://packaging.python.org/en/latest/specifications/version-specifiers/#id5)
352+
353+
The most common version specifier is a **lower bound,** allowing any version higher than the specified version.
354+
Ideally you should set this to the lowest version that is still compatible with your package, but in practice for new packages this is often set at the version that was current at the time the package was written[^lowerbound].
355+
356+
[^lowerbound]: Some packaging tools will do this for you when you add a dependency using their cli interface. For example [`poetry add`](https://python-poetry.org/docs/cli/#add) will add the most recent version with a `^` specifier, and [`pdm add`](https://pdm-project.org/latest/reference/cli/#add) will add the most recent version with `>=`.
357+
358+
Lower bounds look like this:
359+
360+
```toml
361+
dependencies = [ "numpy>=1.0" ]
362+
```
363+
364+
Commas are used to separate individual dependencies, and each package in your `dependencies` section can use different types of version specifiers:
365+
366+
```toml
367+
dependencies = [
368+
"numpy>=1.0", # Greater than or equal to 1.0
369+
"requests==10.1", # Exactly 10.1
370+
"pandas", # Any version
371+
"pydantic>=1.7,<2" # Greater than or equal to 1.7, but less than 2
372+
]
373+
```
374+
375+
Your `pyproject.toml` file will now look like this:
339376

340377
{emphasize-lines="24"}
341378
```toml
@@ -362,31 +399,30 @@ readme = "README.md"
362399
license = {file = 'LICENSE'}
363400
requires-python = ">=3.10"
364401

365-
dependencies = ["numpy", "requests", "pandas", "pydantic"]
402+
dependencies = ["numpy>=1.0", "requests==10.1", "pandas", "pydantic>=1.7,<2"]
366403
```
367404

368405
:::{admonition} Pin dependencies with caution
369-
Pinning dependencies refers to specifying a specific version of a dependency like this `numpy == 1.0`. In some specific cases, you may chose to pin a package version for a specific package dependency.
406+
"Pinning" a dependency means setting it to a specific version, like this:
370407

371-
Declaring lower bounds involves ensuring that a user has at least a specific version (or greater) of a package installed. This is important as often your package is not backwards compatible with an older version of a tool - for example a version of Pandas that was released 5 years ago.
372-
373-
You can declare a lower bound using syntax like this:
374-
375-
`ruamel-yaml>=0.17.21`
376-
377-
[Learn more about various ways to specify ranges of package versions here.](https://packaging.python.org/en/latest/specifications/version-specifiers/#id5)
378-
379-
Note that unless you are building an application, you want to be cautious about pinning dependencies to precise versions. For example:
380-
381-
`numpy == 1.0.2`
408+
`numpy == 1.0`.
382409

410+
If you are building a library package that other developers will depend upon, you must be cautious before pinning to a precise dependency version. Applications, such as production websites, will often pin their dependencies since other packages will not depend on their project.
383411
This is because
384412
users will be installing your package into various environments.
385413
A dependency pinned to a single specific version can make
386414
resolving a Python environment more challenging. As such only
387415
pin dependencies to a specific version if you absolutely need to
388416
do so.
389417

418+
Similarly, you should be cautious when specifying an upper bound on a package.
419+
These two specifications are equivalent:
420+
421+
```
422+
pydantic>=1.10,<2
423+
pydantic^1.10
424+
```
425+
390426
One build tool that you should be aware of that pins dependencies to an upper bound by default is Poetry. [Read more about how to safely add dependencies with Poetry, here.](challenges-with-poetry)
391427
:::
392428

@@ -438,7 +474,7 @@ readme = "README.md"
438474
license = {file = 'LICENSE'}
439475
requires-python = ">=3.10"
440476

441-
dependencies = ["numpy", "requests", "pandas", "pydantic"]
477+
dependencies = ["numpy>=1.0", "requests==10.1", "pandas", "pydantic>=1.7,<2"]
442478

443479
classifiers = [
444480
"Development Status :: 4 - Beta",
@@ -488,7 +524,7 @@ readme = "README.md"
488524
license = {file = 'LICENSE'}
489525
requires-python = ">=3.10"
490526

491-
dependencies = ["ruamel-yaml>=0.17.21", "requests", "python-dotenv", "pydantic"]
527+
dependencies = ["numpy>=1.0", "requests==10.1", "pandas", "pydantic>=1.7,<2"]
492528

493529
classifiers = [
494530
"Development Status :: 4 - Beta",
@@ -539,7 +575,7 @@ readme = "README.md"
539575
license = {file = 'LICENSE'}
540576
requires-python = ">=3.10"
541577

542-
dependencies = ["ruamel-yaml>=0.17.21", "requests", "python-dotenv", "pydantic"]
578+
dependencies = ["numpy>=1.0", "requests==10.1", "pandas", "pydantic>=1.7,<2"]
543579

544580
classifiers = [
545581
"Development Status :: 4 - Beta",
@@ -612,7 +648,7 @@ classifiers = [
612648
]
613649

614650

615-
dependencies = ["xarray", "requests"]
651+
dependencies = ["numpy>=1.0", "requests==10.1", "pandas", "pydantic>=1.7,<2"]
616652
# This is the metadata that pip reads to understand what versions your package supports
617653
requires-python = ">=3.10"
618654
readme = "README.md"

0 commit comments

Comments
 (0)