forked from cctbx/dxtbx
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Migrate dxtbx build backend to hatchling (cctbx#766)
Hatch/Hatchling (https://hatch.pypa.io/) Uses standard pyproject.toml [project] table settings, like setuptools, but provides more places for extensibility. That's used here to automatically generate the `dxtbx.format` and `console_scripts` entry-points on build. A change was needed because pip had started raising "pending removal" noises about the way we deployed before. Opportunity take to do some more pyproject.toml consolidations.
- Loading branch information
Showing
8 changed files
with
140 additions
and
180 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -85,7 +85,7 @@ run: | |
test: | ||
- dials-data | ||
- pip | ||
- pytest | ||
- pytest >6 | ||
- pytest-mock | ||
- pytest-nunit # [win] | ||
- pytest-xdist |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
""" | ||
Dynamically generate the list of console_scripts dxtbx.format entry-points. | ||
""" | ||
|
||
from __future__ import annotations | ||
|
||
import ast | ||
import re | ||
from pathlib import Path | ||
|
||
from hatchling.metadata.plugin.interface import MetadataHookInterface | ||
|
||
|
||
def get_entry_point( | ||
filename: Path, prefix: str, import_path: str | ||
) -> list[tuple[str, str]]: | ||
"""Returns any entry point strings for a given path. | ||
This looks for LIBTBX_SET_DISPATCHER_NAME, and a root function | ||
named 'run'. It can return multiple results for each file, if more | ||
than one dispatcher name is bound. | ||
Args: | ||
filename: | ||
The python file to parse. Will look for a run() function | ||
and any number of LIBTBX_SET_DISPATCHER_NAME. | ||
prefix: The prefix to output the entry point console script with | ||
import_path: The import path to get to the package the file is in | ||
Returns: | ||
A list of entry_point specifications | ||
""" | ||
contents = filename.read_text() | ||
tree = ast.parse(contents) | ||
# Find root functions named "run" | ||
has_run = any( | ||
x | ||
for x in tree.body | ||
if (isinstance(x, ast.FunctionDef) and x.name == "run") | ||
or (isinstance(x, ast.ImportFrom) and "run" in [a.name for a in x.names]) | ||
) | ||
if not has_run: | ||
return [] | ||
# Find if we need an alternate name via LIBTBX_SET_DISPATCHER_NAME | ||
alternate_names = re.findall( | ||
r"^#\s*LIBTBX_SET_DISPATCHER_NAME\s+(.*)$", contents, re.M | ||
) | ||
if alternate_names: | ||
return [ | ||
(name, f"{import_path}.{filename.stem}:run") for name in alternate_names | ||
] | ||
|
||
return [(f"{prefix}.{filename.stem}", f"{import_path}.{filename.stem}:run")] | ||
|
||
|
||
def enumerate_format_classes(path: Path) -> list[tuple(str, str)]: | ||
"""Find all Format*.py files and contained Format classes in a path""" | ||
format_classes = [] | ||
for filename in path.glob("Format*.py"): | ||
content = filename.read_bytes() | ||
try: | ||
parsetree = ast.parse(content) | ||
except SyntaxError: | ||
print(f" *** Could not parse {filename.name}") | ||
continue | ||
for top_level_def in parsetree.body: | ||
if not isinstance(top_level_def, ast.ClassDef): | ||
continue | ||
base_names = [ | ||
baseclass.id | ||
for baseclass in top_level_def.bases | ||
if isinstance(baseclass, ast.Name) and baseclass.id.startswith("Format") | ||
] | ||
if base_names: | ||
classname = top_level_def.name | ||
format_classes.append( | ||
( | ||
f"{classname}:{','.join(base_names)}", | ||
f"dxtbx.format.{filename.stem}:{classname}", | ||
) | ||
) | ||
return format_classes | ||
|
||
|
||
class CustomMetadataHook(MetadataHookInterface): | ||
def update(self, metadata): | ||
scripts = metadata.setdefault("scripts", {}) | ||
package_path = Path(self.root) / "src" / "dxtbx" | ||
for file in package_path.joinpath("command_line").glob("*.py"): | ||
for name, symbol in get_entry_point(file, "dxtbx", "dxtbx.command_line"): | ||
if name not in scripts: | ||
scripts[name] = symbol | ||
|
||
plugins = metadata.setdefault("entry-points", {}) | ||
formats = plugins.setdefault("dxtbx.format", {}) | ||
for name, symbol in sorted(enumerate_format_classes(package_path / "format")): | ||
if name not in formats: | ||
formats[name] = symbol |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Switch build backend to hatchling. This lets us avoid deprecated setuptools behaviour, and automatically generate metadata in a more future-proof way. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,34 @@ | ||
[tool.black] | ||
include = '\.pyi?$|/SConscript$|/libtbx_config$' | ||
[build-system] | ||
requires = ["hatchling"] | ||
build-backend = "hatchling.build" | ||
|
||
[project] | ||
name = "dxtbx" | ||
version = "3.23.dev" | ||
description = "Diffraction Experiment Toolkit" | ||
authors = [ | ||
{ name = "Diamond Light Source", email = "[email protected]" }, | ||
] | ||
license = { file = "LICENSE.txt" } | ||
readme = "README.md" | ||
requires-python = ">=3.9, <3.13" | ||
classifiers = [ | ||
"Development Status :: 5 - Production/Stable", | ||
"Environment :: Console", | ||
"Intended Audience :: Science/Research", | ||
"License :: OSI Approved :: BSD License", | ||
"Operating System :: MacOS", | ||
"Operating System :: Microsoft :: Windows", | ||
"Operating System :: POSIX :: Linux", | ||
"Programming Language :: Python :: 3", | ||
] | ||
dynamic = ["entry-points", "scripts"] | ||
|
||
[project.urls] | ||
Homepage = "https://dials.github.io" | ||
Repository = "https://github.com/cctbx/dxtbx" | ||
|
||
[tool.hatch.metadata.hooks.custom.entry-points] | ||
|
||
[tool.towncrier] | ||
package = "dxtbx" | ||
|
@@ -67,3 +96,12 @@ section-order = [ | |
|
||
[tool.mypy] | ||
no_implicit_optional = true | ||
|
||
[tool.pytest.ini_options] | ||
addopts = "-rsxX" | ||
filterwarnings = [ | ||
"ignore:the matrix subclass is not the recommended way:PendingDeprecationWarning", | ||
"ignore:numpy.dtype size changed:RuntimeWarning", | ||
"ignore:Deprecated call to `pkg_resources.declare_namespace:DeprecationWarning", | ||
"ignore:`product` is deprecated as of NumPy:DeprecationWarning:h5py|numpy", | ||
] |
This file was deleted.
Oops, something went wrong.