8

When you have a setup.py file, you can get the name of the package via the command:

C:\some\dir>python setup.py --name

And this would print the name of the package to the command line.

In an attempt to adhere to best practice, I'm trying to migrate away from setup.py by putting everything in setup.cfg since everything that was previously in setup.py was static content.

But our build pipeline depends on being able to call python setup.py --name. I'm looking to rewrite the pipeline in such a way that I don't need to create a setup.py file.

Is there way to get the name of the package when you have a setup.cfg but not a setup.py file?

sinoroc
  • 18,409
  • 2
  • 39
  • 70
myoungberg
  • 137
  • 6
  • https://setuptools.pypa.io/en/latest/setuptools.html#setup-cfg-only-projects talks a bit about setup-cfg-only projects emulating a dummy setup.py file that simply runs `setuptools.setup()`. Explicitly creating a setup.py file that calls a no-arg `setup()` method actually reads the setup.cfg and is enough here to get me the name, but I'm still wondering if there's a better way. – myoungberg Feb 25 '22 at 21:33
  • 2
    Keeping a minimal `setup.py` is a valid solution. Otherwise use parsing. _setuptools_ uses `ConfigParser` from the standard library to read `setup.cfg`, so as mentioned in an answer this is a valid solution. Soon _setuptools_ should have support for the [_PEP 621_](https://www.python.org/dev/peps/pep-0621/) standard which uses TOML format, and a TOML parser will be added to Python's standard library (Python 3.11), so it should be easy to extract info for almost any build back-end (not just _setuptools_). – sinoroc Feb 26 '22 at 10:15
  • If one wants to go one step further and be able to get metadata (name, etc.) from any [_PEP 517_](https://www.python.org/dev/peps/pep-0517/) project (aka `pyproject.toml`), then it is possible to build some kind of _PEP 517_ "build front-end" just for this purpose and only focus on the [_`prepare_metadata_for_build_wheel()`_](https://www.python.org/dev/peps/pep-0517/#prepare-metadata-for-build-wheel) side of things, maybe the [_`pep517`_ library](https://pypi.org/project/pep517/) can help with that. – sinoroc Feb 26 '22 at 10:28
  • [`python -c 'import setuptools; setuptools.setup()' --name`](https://stackoverflow.com/a/73310963) – sinoroc Aug 10 '22 at 18:46

3 Answers3

4

Maybe using the ConfigParser Python module ?

python -c "from configparser import ConfigParser; cf = ConfigParser(); cf.read('setup.cfg'); print(cf['metadata']['name'])"
Cubix48
  • 2,607
  • 2
  • 5
  • 17
  • 3
    This would cause quite a problem if the desired metadata was the version but you ran into a project declaring `version=attr: my_package.__version__` https://setuptools.pypa.io/en/latest/userguide/declarative_config.html#specifying-values This would be the way to do it, now that I scrolled enough: https://stackoverflow.com/a/71276404/11715259 – N1ngu Apr 11 '22 at 12:22
4

For an "over-kill" solution that allows getting any meta-data field from any PEP 517-compatible project (i.e. pyproject.toml), with the help of the build project:

#!/usr/bin/env python3

import argparse
import pathlib

import build.util

def _main():
    args_parser = argparse.ArgumentParser()
    args_parser.add_argument('path')
    args = args_parser.parse_args()
    path_name = getattr(args, 'path')
    path = pathlib.Path(path_name)
    #
    metadata = build.util.project_wheel_metadata(path)
    print(metadata)

if __name__ == '__main__':
    _main()

This will actually call the build back-end (setuptools, poetry, flit, pdm, or watever), so this might take some seconds.

The build.util API is documented here (on "latest") and it was added in "0.7.0 (16-09-2021)", in this change.


But yes, realistically, as you have already said it I would recommend just keeping a minimal setup.py:

#!/usr/bin/env python3

import setuptools
setuptools.setup()

so that python setup.py --name keeps working

And of course, as others have already said it, parsing is a viable and simple solution. setuptools uses ConfigParser from the standard library to read the setup.cfg file, so you can do it as well.


Another possible solution (credit to wim):

python -c 'import setuptools; setuptools.setup()' --name
sinoroc
  • 18,409
  • 2
  • 39
  • 70
  • Unlike my solution https://stackoverflow.com/a/71693117/11715259, this WILL work despite the build backend not implementing the optional `prepare_metadata_for_build_wheel` hook. Nice! Too bad build.util won't show in the project API documentation. – N1ngu Apr 11 '22 at 12:36
  • 1
    @N1ngu It is [here (on "_latest_")](https://pypa-build.readthedocs.io/en/latest/api.html#module-build.util), confusing... it was added in ["0.7.0 (16-09-2021)"](https://pypa-build.readthedocs.io/en/latest/changelog.html#id3), in this [change](https://github.com/pypa/build/pull/340). – sinoroc Apr 11 '22 at 13:21
4

TL;DR, use the setuptools configuration API https://setuptools.pypa.io/en/latest/setuptools.html#configuration-api.

In your case, this line will give the name of the package:

python -c 'from setuptools.config import read_configuration as c; print(c("setup.cfg")["metadata"]["name"])'

Edit:

In setuptools v61.0.0 (24 Mar 2022) setuptools.config.read_configuration was deprecated . Using the new API, the command becomes:

python -c 'from setuptools.config.setupcfg import read_configuration as c; print(c("setup.cfg")["metadata"]["name"])'

Explanation:

Setuptools exposes a read_configuration() function for parsing metadata and options sections of the configuration. Internally, setuptools uses the configparser module to parse the configuration file setup.cfg. For simple str-type data such as the "name" key, configparser can be used to read the data. However, setuptools also allows dynamic configuration using directives that cannot be directly parsed with configparser.

Here is an example that shows the difference between the two approaches for replacing python setup.py --version:

$ tree .
.
├── my_package
│   └── __init__.py
├── pyproject.toml
└── setup.cfg

1 directory, 3 files

$ cat setup.cfg
[metadata]
name = my_package
version = attr:my_package.__version__

[options]
packages = find:

$ cat my_package/__init__.py 
__version__ = "1.0.0"

$ cat pyproject.toml

$ python -c 'from setuptools.config import read_configuration as c; print(c("setup.cfg")["metadata"]["version"])'
1.0.0

$ python -c 'from configparser import ConfigParser; c = ConfigParser(); c.read("setup.cfg"); print(c["metadata"]["version"])'
attr:my_package.__version__

hamdanal
  • 477
  • 3
  • 10
  • 2
    _Note:_ If you use a `pyproject.toml` (with `setuptools.build_meta` backend) then you wouldn't necessarily have a `setup.cfg` file to parse at all, project name and version would be specified in the TOML ([PEP621](https://peps.python.org/pep-0621/)). Then this approach fails, but the simpler `python -c 'from setuptools import setup; setup()' --name` and `python3 -c 'from setuptools import setup; setup()' --version` should continue to work in any case. – wim Aug 10 '22 at 21:51