Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support setup.cfg #60

Open
aeisenbarth opened this issue Apr 12, 2023 · 2 comments
Open

Support setup.cfg #60

aeisenbarth opened this issue Apr 12, 2023 · 2 comments

Comments

@aeisenbarth
Copy link

aeisenbarth commented Apr 12, 2023

Setup.py has been replaced by setup.cfg and is now moving towards pyproject.toml. Probably there aren't many projects on setup.py anymore.

While I was cleaning up an old setup.py & miniver project, migration seemed straight forward (with pypa/setuptools#2570 now implemented).

Except that cmdclass dict values must be module paths to a public, top-level class. Since the change 1c9f8ca, the build command classes can only be created through function calls, which does not work for the declarative setup.cfg.

I tried working around by creating a helper object with lazy properties (so I can preserve the lazy import of super classes), but that is not detected by setuptools and gives an error.

_version.py
PACKAGE_SOURCE_PATH = os.path.dirname(__file__)

class CmdClass:
    build_py = None
    sdist = None

    def __init__(self, pkg_source_path):
        self._pkg_source_path = pkg_source_path

    @property
    def build_py(self):
        from setuptools.command.build_py import build_py as build_py_orig

        pkg_source_path = self._pkg_source_path

        class _build_py(build_py_orig):
            def run(self):
                super().run()

                src_marker = "".join(["src", os.path.sep])

                if pkg_source_path.startswith(src_marker):
                    path = pkg_source_path[len(src_marker):]
                else:
                    path = pkg_source_path
                _write_version(os.path.join(self.build_lib, path, STATIC_VERSION_FILE))

        return _build_py


    @property
    def sdist(self):
        from setuptools.command.sdist import sdist as sdist_orig

        pkg_source_path = self._pkg_source_path

        class _sdist(sdist_orig):
            def make_release_tree(self, base_dir, files):
                super().make_release_tree(base_dir, files)
                _write_version(os.path.join(base_dir, pkg_source_path, STATIC_VERSION_FILE))

        return _sdist


cmdclass = CmdClass(pkg_source_path=PACKAGE_SOURCE_PATH)


def get_cmdclass(pkg_source_path=PACKAGE_SOURCE_PATH):
    cmds = CmdClass(pkg_source_path=pkg_source_path)
    return dict(sdist=cmds.sdist, build_py=cmds.build_py)

# Unlike expected, setup.cfg cannot access cmdclass.build_py, but the following would work:
# build_py = cmdclass.build_py
# sdist = cmdclass.sdist
setup.cfg
[options]
cmdclass =
    build_py = my_package._version.cmdclass.build_py
    sdist = my_package._version.cmdclass.sdist
ModuleNotFoundError: __path__ attribute not found on 'my_package._version' while trying to find 'my_package._version.cmdclass'

In short, first I think it would be good to support setup.cfg, and second, I am not sure whether it is feasible without moving the build command classes to the top level.

@jsacrist
Copy link
Contributor

I have only played with standalone setup.py and with setup.py + pyproject.toml, so I'm not very familiar with setup.cfg

From my understanding, in a setting that has setup.py + pyproject.toml you can specify under the [project] section that "version" is dynamic:

#### pyproject.toml
[build-system]
requires = ["setuptools", "setuptools-scm"]
build-backend = "setuptools.build_meta"

[tool.setuptools.packages.find]
where = ["."]

[project]
name = "some_package"
# ... some other unrelated fields
dynamic = ["version", "readme"]

[tool.setuptools.dynamic]
version = {attr = "some_package.__version__"}
readme = {file = "README.md", content-type = "text/markdown"}
#### setup.py
from setuptools import setup

pkg = "some_package"

# Loads _version.py module without importing the whole package.
def get_version_and_cmdclass(package_name):
    import os
    from importlib.util import module_from_spec, spec_from_file_location

    spec = spec_from_file_location("version", os.path.join(package_name, "_version.py"))
    module = module_from_spec(spec)
    spec.loader.exec_module(module)
    return module.__version__, module.get_cmdclass(pkg)


version, cmdclass = get_version_and_cmdclass(pkg)
setup(
    version=version,
    cmdclass=cmdclass,
)

Is it not the case that you can do something similar with a setting if you have setup.py and with setup.cfg ?

@aeisenbarth
Copy link
Author

We don't have setup.py anymore, just pyproject.toml for build-system and tools and setup.cfg for project metadata and requirements. Thanks for the dynamic version trick, I didn't know about that!

This request may also become obsolete when we can move to have everything in pyproject.toml. Although then still, it would be great being able to point cmdclass to a public class without needing a setup.py for that.

In the declarative formats (setup.cfg, and maybe in future pyproject.toml), the command class can not be dynamically returned by a getter function like get_cmdclass, but must be a public object so it can be accessed through an object path similar to entry points syntax without colon:

cmdclass =
    build_py = mypackage.some_module.build_py

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants