From 7c77c4ef208ddd134cb55b55749e0154dc673989 Mon Sep 17 00:00:00 2001 From: dgelessus Date: Fri, 3 Apr 2020 22:31:23 +0200 Subject: [PATCH] Prepare setup.py/.cfg for additional import-time dependencies Reading the version number using attr: rsrcfork.__version__ will no longer work properly if rsrcfork has non-stdlib dependencies at import time, because setuptools needs to be able to import rsrcfork and read the version number before the dependencies are installed. As a workaround, our setup.py now manually parses the version number from rsrcfork/__init__.py using the ast module. --- setup.cfg | 8 +++++++- setup.py | 36 +++++++++++++++++++++++++++++++++++- 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/setup.cfg b/setup.cfg index b64877f..f36a107 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,12 @@ [metadata] name = rsrcfork -version = attr: rsrcfork.__version__ +# The version is defined in setup.py, +# which extracts the value of the __version__ attribute from rsrcfork/__init__.py, +# so that the version number/string is only explicitly written in a single place. +# We cannot use "version = attr: rsrcfork.__version__" here, +# because the attr directive needs to import the module to read its attributes, +# which won't work if any of the module's import-time dependencies are not installed yet +# (as is usually the case when setup.cfg is first evaluated before installation). url = https://github.com/dgelessus/python-rsrcfork author = dgelessus classifiers = diff --git a/setup.py b/setup.py index 5bce6c2..a6ecd8b 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,39 @@ #!/usr/bin/env python3 +import ast import setuptools -setuptools.setup() + +def attr(file, name): + """Read the constant value of a global variable from a Python file without importing/executing it. + + The variable in question must be assigned a constant literal + (as understood by :func:`ast.literal_eval`) + in a simple assignment. + The variable *should* only be assigned once + (later assignments are silently ignored). + + Based on https://github.com/pypa/setuptools/issues/1960#issue-547330414. + """ + + with open(file, "rb") as f: + module = ast.parse(f.read()) + + for node in ast.iter_child_nodes(module): + if ( + isinstance(node, ast.Assign) + and len(node.targets) == 1 + and isinstance(node.targets[0], ast.Name) + and node.targets[0].id == name + ): + return ast.literal_eval(node.value) + else: + raise ValueError(f"No simple assignment of variable {name!r} found in {file!r}") + + +setuptools.setup( + # Read the version number from the module source code without importing or executing it. + # This is necessary because at the time that setup.py is executed, + # the dependencies necessary to import rsrcfork may not be installed yet. + version=attr("rsrcfork/__init__.py", "__version__"), +)