Skip to content

Commit 72cc393

Browse files
committed
minimal seemingly working approach to dynamic dependencies for legacydeps
1 parent a0ba4d9 commit 72cc393

File tree

6 files changed

+137
-45
lines changed

6 files changed

+137
-45
lines changed

compile_protos.py

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
#!/usr/bin/env python3
22

3+
import inspect
4+
from typing import Any
35
from importlib import resources
46
from typing import Any
5-
import grpc_tools.protoc
67

78
try:
89
from hatchling.builders.hooks.plugin.interface import BuildHookInterface
@@ -14,6 +15,10 @@
1415

1516

1617
def run_protoc():
18+
# Here because during the build process, CustomBuildHook will be imported
19+
# *before* knowing the dependencies of the hook itself.
20+
import grpc_tools.protoc
21+
1722
grpc_tools_proto = (resources.files("grpc_tools") / "_proto").resolve()
1823
grpc_tools.protoc.main(
1924
[
@@ -29,12 +34,38 @@ def run_protoc():
2934
)
3035

3136

37+
def find_config_settings_in_hatchling() -> dict[str, Any]:
38+
# Terrible workaround (their words, not mine) given by @virtuald
39+
# https://github.com/pypa/hatch/issues/1072#issuecomment-2448985229
40+
# Hopefully this will be fixed in the future
41+
for frame_info in inspect.stack():
42+
frame = frame_info.frame
43+
module = inspect.getmodule(frame)
44+
if (
45+
module
46+
and module.__name__.startswith("hatchling.build")
47+
and "config_settings" in frame.f_locals
48+
):
49+
return frame.f_locals["config_settings"]
50+
51+
return {}
52+
53+
3254
class CustomBuildHook(BuildHookInterface):
3355
def initialize(self, version: str, build_data: dict[str, Any]) -> None:
3456
run_protoc()
3557

3658
def dependencies(self):
37-
return ["grpcio-tools==1.48.2"]
59+
if find_config_settings_in_hatchling().get("LEGACY_DEPS", "False").lower() in (
60+
"true",
61+
"on",
62+
"1",
63+
"y",
64+
"yes",
65+
):
66+
return ["grpcio-tools==1.48.2"]
67+
else:
68+
return ["grpcio-tools==1.67.1"]
3869

3970

4071
if __name__ == "__main__":

dynamic_dependencies.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import inspect
2+
import os
3+
import re
4+
from typing import Any
5+
6+
from hatchling.metadata.plugin.interface import MetadataHookInterface
7+
from packaging.requirements import Requirement
8+
from packaging.utils import canonicalize_name
9+
10+
# The helper functions in this file are heavily inspired on the job layed
11+
# out by the hatch plugin `hatch-requirements-txt`
12+
13+
14+
COMMENT_RE = re.compile(r"(^|\s+)#.*$")
15+
PIP_COMMAND_RE = re.compile(r"\s+(-[A-Za-z]|--[A-Za-z]+)")
16+
17+
18+
def find_config_settings_in_hatchling() -> dict[str, Any]:
19+
# Terrible workaround (their words, not mine) given by @virtuald
20+
# https://github.com/pypa/hatch/issues/1072#issuecomment-2448985229
21+
# Hopefully this will be fixed in the future
22+
for frame_info in inspect.stack():
23+
frame = frame_info.frame
24+
module = inspect.getmodule(frame)
25+
if (
26+
module
27+
and module.__name__.startswith("hatchling.build")
28+
and "config_settings" in frame.f_locals
29+
):
30+
return frame.f_locals["config_settings"]
31+
32+
return {}
33+
34+
35+
def parse_requirements(requirements: list[str]) -> tuple[list[Requirement], list[str]]:
36+
comments = []
37+
parsed_requirements: list[Requirement] = []
38+
39+
for line in requirements:
40+
if line.lstrip().startswith("#"):
41+
comments.append(line)
42+
elif line.lstrip().startswith("-"):
43+
# Likely an argument to pip from a requirements.txt file intended for pip
44+
# (e.g. from pip-compile)
45+
pass
46+
elif line:
47+
# Strip comments from end of line
48+
line = COMMENT_RE.sub("", line)
49+
if "-" in line:
50+
line = PIP_COMMAND_RE.split(line)[0]
51+
req = Requirement(line)
52+
req.name = canonicalize_name(req.name)
53+
parsed_requirements.append(req)
54+
55+
return parsed_requirements, comments
56+
57+
58+
def load_requirements(filename: str) -> list[str]:
59+
if not os.path.isfile(filename):
60+
raise FileNotFoundError(filename)
61+
with open(filename, encoding="UTF-8") as fp:
62+
contents = fp.read()
63+
# Unfold lines ending with \
64+
contents = re.sub(r"\\\s*\n", " ", contents)
65+
parsed_requirements, _ = parse_requirements(contents.splitlines())
66+
67+
return [str(r) for r in parsed_requirements]
68+
69+
70+
class DynamicDependenciesMetaDataHook(MetadataHookInterface):
71+
def update(self, metadata):
72+
if find_config_settings_in_hatchling().get("LEGACY_DEPS", "False").lower() in (
73+
"true",
74+
"on",
75+
"1",
76+
"y",
77+
"yes",
78+
):
79+
metadata["dependencies"] = load_requirements("requirements-legacydeps.txt")
80+
else:
81+
metadata["dependencies"] = load_requirements("requirements.txt")

pyproject.toml

Lines changed: 10 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -26,27 +26,13 @@ classifiers = [
2626
"Programming Language :: Python :: 3 :: Only",
2727
]
2828
requires-python = ">=3.9,<3.14"
29-
dependencies = [
30-
"aiorwlock>=1.4.0",
31-
"bcrypt>=4.1.1",
32-
"grpcio>=1.67.1",
33-
"grpcio-health-checking>=1.67.1",
34-
"hiredis>=3.0.0",
35-
"opentelemetry-api>=1.28.1",
36-
"protobuf>=4.25.0",
37-
"psutil>=6.1.0",
38-
"pydantic-settings>=2.6.0",
39-
"redis>=5.1.1",
40-
"get-annotations;python_version<\"3.10\"",
41-
"PyJWT>=2.9.0",
42-
"threadpoolctl>=3.5.0",
43-
]
44-
dynamic = ["version"]
29+
dynamic = ["version", "dependencies"]
4530

4631
[project.urls]
47-
"Documentation" = "https://dataclay.bsc.es/"
48-
"Code" = "https://github.com/bsc-dom/dataclay"
49-
"Issue tracker" = "https://github.com/bsc-dom/dataclay/issues"
32+
"Homepage" = "https://dataclay.bsc.es/"
33+
"Documentation" = "https://dataclay.readthedocs.io/"
34+
"Source" = "https://github.com/bsc-dom/dataclay"
35+
"Issues" = "https://github.com/bsc-dom/dataclay/issues"
5036

5137
[project.optional-dependencies]
5238
dev = [
@@ -106,7 +92,7 @@ filterwarnings = [
10692

10793
[tool.hatch.build.targets.wheel]
10894
packages = ["src/dataclay", "src/storage"]
109-
dependencies = ["grpcio-tools==1.48.2"]
95+
ignore-vcs = true
11096

11197
[tool.hatch.build.targets.sdist]
11298
include = ["src/dataclay", "src/storage", "dataclay-common"]
@@ -116,4 +102,7 @@ path = "src/dataclay/__init__.py"
116102

117103
[tool.hatch.build.hooks.custom]
118104
path = "compile_protos.py"
119-
dependencies = ["grpcio-tools==1.48.2"]
105+
106+
[tool.hatch.metadata.hooks.custom]
107+
path = "dynamic_dependencies.py"
108+
dependencies = ["packaging"]

requirements-dev.txt

Lines changed: 0 additions & 10 deletions
This file was deleted.

requirements-legacydeps.txt

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,3 @@
1-
# This requirements should be used for installing dataClay in the "legacy dependencies" flavour
2-
3-
# You will need to install these requirements before installing dataClay. You can do it with:
4-
#
5-
# $ pip install -r requirements-legacydeps.txt -r requirements.txt
6-
#
7-
# Tweak the previous command as you see fit, assuming requirements.txt contains
8-
# your own requirements. You may want to merge into a single requirements.txt.
9-
10-
# After the requirements are in place, install dataClay without dependencies:
11-
# $ pip install --no-deps "dataclay=={version}"
12-
131
aiorwlock>=1.4.0
142
bcrypt>=4.1.1
153
grpcio>=1.48.2

requirements.txt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
aiorwlock>=1.4.0
2+
bcrypt>=4.1.1
3+
grpcio>=1.67.1
4+
grpcio-health-checking>=1.67.1
5+
hiredis>=3.0.0
6+
opentelemetry-api>=1.28.1
7+
protobuf>=4.25.0
8+
psutil>=6.1.0
9+
pydantic-settings>=2.6.0
10+
redis>=5.1.1
11+
get-annotations;python_version<"3.10"
12+
PyJWT>=2.9.0
13+
threadpoolctl>=3.5.0

0 commit comments

Comments
 (0)