11

I am trying to migrate my package from setup.py to pyproject.toml and I am not sure how to do the dynamic versioning in the same way as before. Currently I can pass the development version using environment variables when the build is for development.

The setup.py file looks similar to this:

import os

from setuptools import setup

import my_package


if __name__ == "__main__":
    dev_version = os.environ.get("DEV_VERSION")
    version = dev_version if dev_version else f"{my_package.__version__}"
    
    setup(
        name="my_package",
        version=version,
        ...
    )

Is there a way to do similar thing when using pyproject.toml file?

DC Slagel
  • 528
  • 1
  • 7
  • 13
Ilya
  • 730
  • 4
  • 16
  • Does this answer your question? [Using the return value of a python function as the value for version field in pyproject.toml](https://stackoverflow.com/questions/76081078/using-the-return-value-of-a-python-function-as-the-value-for-version-field-in-py) – sinoroc Apr 27 '23 at 18:40
  • Also this one: https://stackoverflow.com/a/76043070 – sinoroc Apr 27 '23 at 18:41
  • @sinoroc Thank for the references but I want to manage everything in one place i.e. `pyproject.py` and not to have the `setup.py` file – Ilya Apr 28 '23 at 10:20
  • 1
    Not possible in `pyproject.toml` as far as I know. -- You can of course write a plugin (for *setuptools* or any other build back-end) whose job it is to read environment variables and whose configuration would be in `pyproject.toml`. --- Currently, without having to code anything, what I linked is your best bet as far as I can tell. – sinoroc Apr 28 '23 at 10:46

2 Answers2

2

At the time that I write this answer, the most genuinely valid approach on how to do that is to go ahead and write your pyproject.toml as you normally would, but instead of having a version = "..." field you add a dynamic = ["version"].

Then you can keep a very minimal setup.py file which only resolves the dynamic value of version. For example:

# pyproject.toml
[project]
name = "hello-world"
readme = "README.md"
authors = [...]
dependencies = [...]
dynamic = ["version"]

[build-system]
requires = ["setuptools"]
build-backend = "setuptools.build_meta:__legacy__"
# The difference between `__legacy__` and the regular `build_meta`
# is that `__legacy__` does the equivalent of
# `sys.path.insert(0, os.path.dirname(__file__))`.
# This allows you to `import` your modules from the `CWD`.
# If you don't like using `__legacy__` you can
# manually add `CWD` to `sys.path` inside your `setup.py`.
# setup.py
import os
from setuptools import setup

import my_package

dev_version = os.environ.get("DEV_VERSION")

setup(
  version=dev_version if dev_version else f"{my_package.__version__}"
)

These are my remarks about this approach:

  • Setuptools allows you to keep all your static metadata/configuration parameters in pyproject.toml and at the same time use logic to compute all the dynamic parts in setup.py. Setuptools docs explicitly say:

    Metadata and configuration supplied via setup() is complementary to ... the information present in ... pyproject.toml.

    It is important however to remember to write dynamic = [...] in accordance with the spec to allow setuptools to overwrite any value that can be specified via the [project] table.

  • setup.py is only deprecated as a "runnable CLI tool" that can be invoked directly with python setup.py .... But it is still a perfectly valid configuration file in the same way that conftest.py is a valid configuration file for pytest and noxfile.py is a configuration file for nox (you usually don't call python conftest.py or python noxfile.py...). One evidence for this is article https://blog.ganssle.io/articles/2021/10/setup-py-deprecated.html written by one of the authors of PEP 621 (which stablishes the [project] table in pyproject.toml) and maintainer of setuptools.

  • Note that I used in the example build-backend = "setuptools.build_meta:__legacy__" instead of build-backend = "setuptools.build_meta". This happens because __legacy__ tries to emulate the "side-effect" behaviour of running python setup.py ..., i.e. to add CWD to sys.path. If you don't like that, you can manipulate sys.path explicitly in your setup.py file before the relevant imports.

1

I wanted something similar.

Unfortunately, toml does not support environment variables and I don't think it wants to, ever.

I've experimented a bit with custom build backend wrappers, and sifting through the layers of setuptools (and setuputils3).

My conclusion is that there is no simpler solution than to write a short build script to dynamically edit the pyproject.toml file...

This kind of thing:

#!/bin/bash

#fix version - as toml doesn't support environment vars...
sed -i -e "s/.*version.*/version=\"${DEV_VERSION}\"/g" pyproject.toml

#build python source and binary packages
python3 -m build

For your other case (non-development) you can "dynamically" pull in the version as described by someone else in this answer: https://stackoverflow.com/a/74514742/4126317

davido
  • 21
  • 2
  • 1
    This is similar to the solution I used where instead of changing the `pyproject.py` file, I change the `my_package/__init__.py` file using sed. Why do you think that toml does not want to support environment variables? – Ilya Apr 28 '23 at 10:23
  • 2
    I currently have an opened PR for `setuptools` to add that capability https://github.com/pypa/setuptools/pull/3885 – Ilya Apr 28 '23 at 10:25
  • 1
    @Ilya Awesome, hope it gets accepted! In answer to your question, I just inferred it from [this](https://github.com/toml-lang/toml/issues/255#issuecomment-62277325). – davido May 02 '23 at 15:57