Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support extracting minimum constraints for monorepo #250

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 45 additions & 10 deletions .github/actions/base-setup/create_constraints_file.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,56 @@
import sys
from pathlib import Path
from zipfile import ZipFile
from typing import TYPE_CHECKING, Any, List, cast

from build.util import project_wheel_metadata # type:ignore[import-not-found]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you know why this import is not found by mypy when type checking?

from packaging.requirements import Requirement

if TYPE_CHECKING:
# not importing this at runtime as it is only exposed in Python 3.10+
# (although the interface was already followed in earlier versions)
from importlib.metadata import PackageMetadata # type:ignore[attr-defined]
else:
PackageMetadata = Any

Comment on lines +8 to +14
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(No action needed) Thanks for adding a comment here to explain things. I just noticed that the upstream documentation doesn't even seem to state the interface for PackageMetadata: https://build.pypa.io/en/stable/api.html

Curiously, the unofficial importlib_metadata (3rd-party implementation of the importlib.metadata module) does have documentation for this object: https://importlib-metadata.readthedocs.io/en/latest/api.html#importlib_metadata._meta.PackageMetadata

Given all of the caveats involved with using importlib.metadata (weird typing issues, dependence on Python version of current env), it may be worthwhile to consider using importlib_metadata in this script instead. However, I'm not sure if that's possible given that this is a script and not its own package.

output_file = sys.argv[-2]
fname = sys.argv[-1]
top_level_project_dir = sys.argv[-1]
constraints = {}

archive = ZipFile(fname)
reqs = []
for f in archive.namelist():
if f.endswith("METADATA"):
for li in archive.open(f).read().decode("utf-8").split("\n"):
if li.startswith("Requires-Dist"):
reqs.append(li.replace("Requires-Dist: ", ""))
archive.close()

def extract_dependencies(project_dir: str) -> List[str]:
reqs = []
print(f"Extracting metadata from wheel for {project_dir}...")
metadata = project_wheel_metadata(source_dir=project_dir)
reqs.extend(get_requires_dist(metadata))

# extract requirements from local dependencies specified with file: protocol
# to support the mono-repo usecase
local_projects = {}
for req in reqs:
r = Requirement(req)
if r.url and r.url.startswith("file://"):
path = r.url.replace("file://", "")
local_projects[r.name] = path

reqs_from_local_dependencies = []
for dependency_name, path in local_projects.items():
print(f"Discovering constraints in local {dependency_name} package under {path}")
sub_dependencies = extract_dependencies(path)
# filter out dependencies between local packages (e.g. jupyter-ai depends on
# a fixed minimum version of `jupyter-ai-magics`, but here we want to test
# the latest version against its third-party dependencies - not the old one).
sub_dependencies = [
req for req in sub_dependencies if Requirement(req).name not in local_projects
]
reqs_from_local_dependencies.extend(sub_dependencies)
return reqs + reqs_from_local_dependencies


def get_requires_dist(metadata: PackageMetadata) -> List[str]:
return cast(List[str], metadata.get_all("Requires-Dist")) or []


reqs = extract_dependencies(top_level_project_dir)

for req in reqs:
r = Requirement(req)
Expand Down
4 changes: 1 addition & 3 deletions .github/actions/base-setup/setup_constraints.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@
python -m venv $HOME/.venv
source $HOME/.venv/bin/activate
pip install build packaging pkginfo
mkdir $HOME/dist
python -m build --outdir $HOME/dist --wheel .

SCRIPT_DIR=$(cd $(dirname "${BASH_SOURCE[0]}") && pwd)
python $SCRIPT_DIR/create_constraints_file.py $HOME/constraints.txt $HOME/dist/*.whl
python $SCRIPT_DIR/create_constraints_file.py $HOME/constraints.txt $(pwd)
cat $HOME/constraints.txt
echo "PIP_CONSTRAINT=$HOME/constraints.txt" >> $GITHUB_ENV
Loading