Skip to content

Commit ba388d9

Browse files
committed
Expose which optional hooks are present or missing
Signed-off-by: Bernát Gábor <[email protected]>
1 parent 0f179c1 commit ba388d9

File tree

10 files changed

+86
-36
lines changed

10 files changed

+86
-36
lines changed

.pre-commit-config.yaml

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
repos:
22
- repo: https://github.com/pre-commit/pre-commit-hooks
3-
rev: v4.3.0
3+
rev: v4.4.0
44
hooks:
55
- id: check-ast
66
- id: check-builtin-literals
@@ -12,7 +12,7 @@ repos:
1212
- id: end-of-file-fixer
1313
- id: trailing-whitespace
1414
- repo: https://github.com/asottile/pyupgrade
15-
rev: v3.2.2
15+
rev: v3.3.0
1616
hooks:
1717
- id: pyupgrade
1818
args: ["--py37-plus"]
@@ -43,7 +43,7 @@ repos:
4343
- id: tox-ini-fmt
4444
args: ["-p", "fix"]
4545
- repo: https://github.com/PyCQA/flake8
46-
rev: 5.0.4
46+
rev: 6.0.0
4747
hooks:
4848
- id: flake8
4949
additional_dependencies:
@@ -52,10 +52,11 @@ repos:
5252
- flake8-pytest-style==1.6
5353
- flake8-spellcheck==0.28
5454
- flake8-unused-arguments==0.0.12
55-
- flake8-noqa==1.2.9
55+
- flake8-noqa==1.3
5656
- pep8-naming==0.13.2
57+
- flake8-pyproject==1.2.1
5758
- repo: https://github.com/pre-commit/mirrors-prettier
58-
rev: "v3.0.0-alpha.4"
59+
rev: "v2.7.1"
5960
hooks:
6061
- id: prettier
6162
additional_dependencies:

docs/changelog.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
Release History
22
===============
33

4+
v1.2.0 - (2022-12-04)
5+
---------------------
6+
- Expose which optional hooks are present or missing via :meth:`pyproject_api.Frontend.optional_hooks`
7+
48
v1.1.2 - (2022-10-30)
59
---------------------
610
- Fix editable classes not exported at root level

docs/index.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ Frontend
1414
--------
1515
.. autoclass:: Frontend
1616

17+
.. autoclass:: OptionalHooks
18+
1719
Exceptions
1820
----------
1921

pyproject.toml

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,15 @@ dependencies = [
2323
optional-dependencies.docs = [
2424
"furo>=2022.9.29",
2525
"sphinx>=5.3",
26-
"sphinx-autodoc-typehints>=1.19.4",
26+
"sphinx-autodoc-typehints>=1.19.5",
2727
]
2828
optional-dependencies.testing = [
29-
"covdefaults>=2.2",
29+
"covdefaults>=2.2.2",
3030
"pytest>=7.2",
3131
"pytest-cov>=4",
3232
"pytest-mock>=3.10",
33-
'importlib-metadata>=5; python_version < "3.8"',
34-
"wheel>=0.37.1",
33+
'importlib-metadata>=5.1; python_version < "3.8"',
34+
"wheel>=0.38.4",
3535
]
3636
dynamic = ["version"]
3737
classifiers = [
@@ -86,3 +86,15 @@ line_length = 120
8686
python_version = "3.11"
8787
show_error_codes = true
8888
strict = true
89+
90+
[tool.pep8]
91+
max-line-length = "120"
92+
93+
[tool.flake8]
94+
max-complexity = 22
95+
max-line-length = 120
96+
unused-arguments-ignore-abstract-functions = true
97+
noqa-require-code = true
98+
dictionaries = ["en_US", "python", "technical", "django"]
99+
ignore = [
100+
]

src/pyproject_api/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
Frontend,
88
MetadataForBuildEditableResult,
99
MetadataForBuildWheelResult,
10+
OptionalHooks,
1011
RequiresBuildEditableResult,
1112
RequiresBuildSdistResult,
1213
RequiresBuildWheelResult,
@@ -33,4 +34,5 @@
3334
"WheelResult",
3435
"EditableResult",
3536
"SubprocessFrontend",
37+
"OptionalHooks",
3638
]

src/pyproject_api/_backend.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,19 @@ def __str__(self):
3636
def _exit(self):
3737
return 0
3838

39+
def _optional_hooks(self):
40+
return {
41+
k: hasattr(self.backend, k)
42+
for k in (
43+
"get_requires_for_build_sdist",
44+
"prepare_metadata_for_build_wheel",
45+
"get_requires_for_build_wheel",
46+
"build_editable",
47+
"get_requires_for_build_editable",
48+
"prepare_metadata_for_build_editable",
49+
)
50+
}
51+
3952

4053
def flush():
4154
sys.stderr.flush()

src/pyproject_api/_backend.pyi

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ class BackendProxy:
1111
def __call__(self, name: str, *args: Any, **kwargs: Any) -> Any: ...
1212
def __str__(self) -> str: ...
1313
def _exit(self) -> None: ...
14+
def _optional_commands(self) -> dict[str, bool]: ...
1415

1516
def run(argv: Sequence[str]) -> int: ...
1617
def read_line() -> bytearray: ...

src/pyproject_api/_frontend.py

Lines changed: 39 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@
1515

1616
from pyproject_api._util import ensure_empty_dir
1717

18+
if sys.version_info >= (3, 8): # pragma: no cover (py38+)
19+
from typing import TypedDict
20+
else: # pragma: no cover (py38+)
21+
from typing_extensions import TypedDict
22+
1823
if sys.version_info >= (3, 11): # pragma: no cover (py311+)
1924
import tomllib
2025
else: # pragma: no cover (py311+)
@@ -24,6 +29,17 @@
2429
ConfigSettings = Optional[Dict[str, Any]]
2530

2631

32+
class OptionalHooks(TypedDict, total=True):
33+
"""A flag indicating if the backend supports the optional hook or not"""
34+
35+
get_requires_for_build_sdist: bool
36+
prepare_metadata_for_build_wheel: bool
37+
get_requires_for_build_wheel: bool
38+
build_editable: bool
39+
get_requires_for_build_editable: bool
40+
prepare_metadata_for_build_editable: bool
41+
42+
2743
class CmdStatus(ABC):
2844
@property
2945
@abstractmethod
@@ -190,6 +206,7 @@ def __init__(
190206
self._backend_obj = backend_obj
191207
self.requires: tuple[Requirement, ...] = requires
192208
self._reuse_backend = reuse_backend
209+
self._optional_hooks: OptionalHooks | None = None
193210

194211
@classmethod
195212
def create_args_from_folder(
@@ -243,17 +260,25 @@ def backend_args(self) -> list[str]:
243260
result.append(self._backend_obj)
244261
return result
245262

263+
@property
264+
def optional_hooks(self) -> OptionalHooks:
265+
""":return: a dictionary indicating if the optional hook is supported or not"""
266+
if self._optional_hooks is None:
267+
result, _, __ = self._send("_optional_hooks")
268+
self._optional_hooks = result
269+
return self._optional_hooks
270+
246271
def get_requires_for_build_sdist(self, config_settings: ConfigSettings | None = None) -> RequiresBuildSdistResult:
247272
"""
248273
Get build requirements for a source distribution (per PEP-517).
249274
250275
:param config_settings: run arguments
251276
:return: outcome
252277
"""
253-
try:
278+
if self.optional_hooks["get_requires_for_build_sdist"]:
254279
result, out, err = self._send(cmd="get_requires_for_build_sdist", config_settings=config_settings)
255-
except BackendFailed as exc:
256-
result, out, err = [], exc.out, exc.err
280+
else:
281+
result, out, err = [], "", ""
257282
if not isinstance(result, list) or not all(isinstance(i, str) for i in result):
258283
self._unexpected_response("get_requires_for_build_sdist", result, "list of string", out, err)
259284
return RequiresBuildSdistResult(tuple(Requirement(r) for r in cast(List[str], result)), out, err)
@@ -265,10 +290,10 @@ def get_requires_for_build_wheel(self, config_settings: ConfigSettings | None =
265290
:param config_settings: run arguments
266291
:return: outcome
267292
"""
268-
try:
293+
if self.optional_hooks["get_requires_for_build_wheel"]:
269294
result, out, err = self._send(cmd="get_requires_for_build_wheel", config_settings=config_settings)
270-
except BackendFailed as exc:
271-
result, out, err = [], exc.out, exc.err
295+
else:
296+
result, out, err = [], "", ""
272297
if not isinstance(result, list) or not all(isinstance(i, str) for i in result):
273298
self._unexpected_response("get_requires_for_build_wheel", result, "list of string", out, err)
274299
return RequiresBuildWheelResult(tuple(Requirement(r) for r in cast(List[str], result)), out, err)
@@ -282,10 +307,10 @@ def get_requires_for_build_editable(
282307
:param config_settings: run arguments
283308
:return: outcome
284309
"""
285-
try:
310+
if self.optional_hooks["get_requires_for_build_editable"]:
286311
result, out, err = self._send(cmd="get_requires_for_build_editable", config_settings=config_settings)
287-
except BackendFailed as exc:
288-
result, out, err = [], exc.out, exc.err
312+
else:
313+
result, out, err = [], "", ""
289314
if not isinstance(result, list) or not all(isinstance(i, str) for i in result):
290315
self._unexpected_response("get_requires_for_build_editable", result, "list of string", out, err)
291316
return RequiresBuildEditableResult(tuple(Requirement(r) for r in cast(List[str], result)), out, err)
@@ -301,13 +326,13 @@ def prepare_metadata_for_build_wheel(
301326
:return: metadata generation result
302327
"""
303328
self._check_metadata_dir(metadata_directory)
304-
try:
329+
if self.optional_hooks["prepare_metadata_for_build_wheel"]:
305330
basename, out, err = self._send(
306331
cmd="prepare_metadata_for_build_wheel",
307332
metadata_directory=metadata_directory,
308333
config_settings=config_settings,
309334
)
310-
except BackendFailed:
335+
else:
311336
# if backend does not provide it acquire it from the wheel
312337
basename, err, out = self._metadata_from_built_wheel(config_settings, metadata_directory, "build_wheel")
313338
if not isinstance(basename, str):
@@ -333,13 +358,13 @@ def prepare_metadata_for_build_editable(
333358
:return: metadata generation result
334359
"""
335360
self._check_metadata_dir(metadata_directory)
336-
try:
361+
if self.optional_hooks["prepare_metadata_for_build_editable"]:
337362
basename, out, err = self._send(
338363
cmd="prepare_metadata_for_build_editable",
339364
metadata_directory=metadata_directory,
340365
config_settings=config_settings,
341366
)
342-
except BackendFailed:
367+
else:
343368
# if backend does not provide it acquire it from the wheel
344369
basename, err, out = self._metadata_from_built_wheel(config_settings, metadata_directory, "build_editable")
345370
if not isinstance(basename, str):
@@ -481,5 +506,5 @@ def _send(self, cmd: str, **kwargs: Any) -> tuple[Any, str, str]:
481506

482507
@abstractmethod
483508
@contextmanager
484-
def _send_msg(self, cmd: str, result_file: Path, msg: str) -> Iterator[CmdStatus]: # noqa: U100
509+
def _send_msg(self, cmd: str, result_file: Path, msg: str) -> Iterator[CmdStatus]:
485510
raise NotImplementedError

tests/test_fronted.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@ def test_backend_get_requires_for_build_editable_miss(demo_pkg_inline: Path, mon
229229
fronted = SubprocessFrontend(*SubprocessFrontend.create_args_from_folder(demo_pkg_inline)[:-1])
230230
result = fronted.get_requires_for_build_editable()
231231
assert not result.requires
232-
assert " get_requires_for_build_editable " in result.out
232+
assert not result.out
233233
assert not result.err
234234

235235

tox.ini

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ description = run type check on code base
4848
setenv =
4949
{tty:MYPY_FORCE_COLOR = 1}
5050
deps =
51-
mypy==0.982
51+
mypy==0.991
5252
commands =
5353
mypy src/pyproject_api --strict
5454
mypy tests --strict
@@ -67,7 +67,7 @@ skip_install = true
6767
deps =
6868
build[virtualenv]>=0.9
6969
check-wheel-contents>=0.4
70-
twine>=4.0.1
70+
twine>=4.0.2
7171
commands =
7272
python -m build -o {envtmpdir} -s -w .
7373
twine check --strict {envtmpdir}{/}*
@@ -82,13 +82,3 @@ extras =
8282
commands =
8383
python -m pip list --format=columns
8484
python -c "print(r'{envpython}')"
85-
86-
[flake8]
87-
max-complexity = 22
88-
max-line-length = 120
89-
noqa-require-code = true
90-
dictionaries = en_US,python,technical,django
91-
ignore = E203, W503
92-
93-
[pep8]
94-
max-line-length = 120

0 commit comments

Comments
 (0)