Conversation
|
Cool. I think pip supports installing from a directory, so we could build a Cython wheel and let pip install that for the test environment.
|
@scoder did you mean in CI? For CI, I tend to build dists first and then, use them in tests so that the following job could publish what's actually been tested. Let me know if you'd like a PR or pointers. |
You can add |
|
Oh, and the downstreams have |
| @@ -0,0 +1 @@ | |||
| from ._backend import * | |||
There was a problem hiding this comment.
You have to re-expose the hooks from setuptools before this. Otherwise, your backend won't pick up new hooks that might appear there. This happened to me once with PEP 660, because I was conservative and exposed stuff declared in PEP 517, then setuptools added support for PEP 660, but my backend didn't have editable installs support for a long time because I didn't take this into account. As a result, I documented how to properly do this in the setuptools' docs.
There was a problem hiding this comment.
I don't think I can do that. I want to declare setuptools as a requirement in get_requires_for_build_editable which means I don't think I can import setuptools first.
I think this is something you'd live with in an in-tree backend (which you only use for your own project) but for something that Cython's distributing for other people to use, we don't want them to have to remember to list setuptools as a dependency too.
There was a problem hiding this comment.
Here's how you do it:
| from ._backend import * | |
| from contextlib import suppress | |
| with suppress(ImportError): | |
| from setuptools.build_meta import * | |
| from ._backend import * |
I used this trick before — one subprocess wouldn't have setuptools but once the front-end installs it, it will be in place.
There was a problem hiding this comment.
One caveat would be that you still can't use setuptools' get_requires_for_*() hooks but the rest should be fine.
| # I don't really want to be dealing with finding/vendoring replacement toml | ||
| # libraries for Python <3.11. Therefore use setuptools if possible. | ||
| # Try a number of options for maximum chance of success. | ||
| try: | ||
| # pyprojecttoml is marked as private in setuptools | ||
| from setuptools.config.pyprojecttoml import load_file | ||
| except ImportError: | ||
| try: | ||
| # setuptools 69.1.0+ | ||
| from setuptools.compat.py310 import tomllib | ||
| except ImportError: | ||
| import tomllib # standard library from 3.11 onwards | ||
| def load_file(filename): | ||
| with open(filename, "rb") as f: | ||
| return tomllib.load(f) | ||
| return load_file(filename) |
There was a problem hiding this comment.
You don't need to do this. Just return tomlli; python_version < "3.11" from the get_requires_for_build_*() hooks.
This library is API-compatible with tomllib and is maintained by a few CPython Core Devs (PEP 680 authors/sponsors). In fact, tomllib grew out of tomli so it's a very safe bet: https://realpython.com/python311-tomllib/#read-toml-with-tomllib.
| # I don't really want to be dealing with finding/vendoring replacement toml | |
| # libraries for Python <3.11. Therefore use setuptools if possible. | |
| # Try a number of options for maximum chance of success. | |
| try: | |
| # pyprojecttoml is marked as private in setuptools | |
| from setuptools.config.pyprojecttoml import load_file | |
| except ImportError: | |
| try: | |
| # setuptools 69.1.0+ | |
| from setuptools.compat.py310 import tomllib | |
| except ImportError: | |
| import tomllib # standard library from 3.11 onwards | |
| def load_file(filename): | |
| with open(filename, "rb") as f: | |
| return tomllib.load(f) | |
| return load_file(filename) | |
| try: | |
| import tomllib | |
| except ImportError: | |
| import tomli as tomllib # is in standard library from 3.11 onwards | |
| with open(filename, "rb") as f: | |
| return tomllib.load(f) |
| @@ -0,0 +1,142 @@ | |||
| # Note - setuptools imports are all lazy so that we can specify it as a dependency | |||
| from contextlib import contextmanager as _contextmanager | |||
| from functools import lru_cache | |||
There was a problem hiding this comment.
This name should also be private so that re-export in the init wouldn't pick it up by default.
|
|
||
| return _Extension(name, sources) | ||
|
|
||
| @lru_cache() # incase we go through multiple calls to this function |
There was a problem hiding this comment.
Why do you think it's called several times? PEP 517 hooks each are called in subprocesses IIUC.
There was a problem hiding this comment.
Yes - it looks like they are. In which case caching is pointless
|
Let's also invite @abravalheri to get an opinion from the |
I forgot one more trick — you can have a constraints file and set the |
Last year we did something in setuptools-rust to add support for For example, considering the following already exists in setuptools:
Something like the following could be implemented: # Draft based on setuptools-rust
if sys.version_info[:2] >= (3, 11):
import tomllib
else:
import tomli as tomlib
def pyprojecttoml_config(dist: Distribution) -> None:
try:
with open("pyproject.toml", "rb") as f:
cfg = tomllib.read(f).get("tool", {}).get("cython")
except FileNotFoundError:
return None
if cfg:
existing = dist.ext_modules or []
new = map(_create, cfg.get("ext-modules", []))
dist.ext_modules = [*existing, *new]
def _create(config: dict) -> Extension:
from setuptools.extension import Extension
# PEP 517/621 convention: pyproject.toml uses dashes
kwargs = {k.replace("-", "_"): v for k, v in config.items()}
return Extension(**kwargs)
# Then, an entrypoint needs to be defined in the Cython distribution:
#
# [project.entry-points."setuptools.finalize_distribution_options"]
# cython = "Cython.whichever_module:pyprojecttoml_config"That said, no specific code seems to be necessary to handle something like the following definition: [[tool.cython.module_list]]
name = "a"
sources = ["a.pyx"]... and from the setuptools point of view, this is probably likely to be implemented some day in the form of: [[tool.setuptools.ext-modules]]
name = "a"
sources = ["a.pyx"](in fact we reached a point in the development of setuptools that we can start working on a PR for that1). What is unlikely to be supported directly in setuptools is something like the following: [tool.cython]
cythonize = ["a.pyx", "b.pyx", "c.py"]But this could be implemented following the methodology discussed above (setuptools.finalize_distribution_options). The difference is that for each /crossref pypa/setuptools#4568 Footnotes
|
|
Thanks @abravalheri - one of the things I was initially worried about was that it seemed to be (mostly) implementing a feature that would be better off in setuptools, and that the "cython" part of this was comparatively small. It sounds like I should wait for the setuptools PR, and then investigate what's missing. And try to implement it with the less intrusive |
|
@joshua-auchincloss this ain't for hatchling but perhaps you'd be interested in checking out the interface for building Cython extensions. Any feedback? |
|
I'm not sure this is worth anyone reviewing at this point - most of it will hopefully be removed fairly soon. |
|
Simple use-cases can now be tried out with [build-system]
requires = ["setuptools>74.1", "cython"]
build-backend = "setuptools.build_meta"
[project]
name = "something"
version = "0.1"
description = "description"
[tool.setuptools]
ext-modules = [
{name = "a", sources = ["a.pyx"]},
{name = "b", sources = ["b.pyx"]},
]Feedback targeting setuptools functionality should be directed at the More complex use cases (e.g. passing flags and configuration parameters to Footnotes
|
|
Thanks for merging that @abravalheri I think what I'll do in the near future is add some documentation for it in Cython so that people know start using the declarative version. I'll have to think about where because our "source files and compilation" docs page already seems to big and unwieldy to me so I don't really want to just dump more information in it. I'm definitely willing to expand it a bit with Cython-specific additions as you suggest. Possibly worth seeing what gets requested first though. I'll close this PR because I think it's completely superseded. |
Initial work on a PEP517 build backend. Essentially allows build Cython projects declaratively (with pyproject.toml) rather than with a setup.py file.
Step 1 of #6305 (@webknjaz)
It's currently missing:
It's deliberately missing most customization at this point just for the sake of merging something simple then improving it. My plan is to go for a "direct" translation of setup.py where possible.
Tests currently seem hard. I can usefully add unit tests for individual components, but testing the whole thing involves allowing pip to use isolated build environments, and at that stage it will want to download Cython via pypi rather than use the local version that we want to test.
Examples of valid pyproject.toml files: