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

feat: allow a folder with a list of wheels to be a repository #10017

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

florianvazelle
Copy link

@florianvazelle florianvazelle commented Jan 11, 2025

Pull Request Check List

Resolves: #5983

  • Added tests for changed code.
  • Updated documentation for changed code.

Feature Request

Hi 👋

I submit a PR to allow a folder with a list of wheels (or sdist) to be a repository.
This is usefull to work offline with wheelhouse, for container or CI stuff.

I work on a POC that allow to set sources, like this:

[[tool.poetry.source]]
name = "wheelhouse"
path = "./wheelhouse"

I'm open to feedback 🙂

Minimal, Reproducible Example

With a pyproject.toml, like:

[build-system]
requires = ["poetry-core>=2.0.0"]
build-backend = "poetry.core.masonry.api"

[project]
name = "test"
description = ""
version = "1.0.0"

[tool.poetry.dependencies]
python = "~3.12"
mypy = "*"

Run these commands to setup a wheelhouse (using poetry):

$ export WHEELHOUSE_DIR="./wheelhouse"
$ mkdir -p $WHEELHOUSE_DIR
$ poetry lock
$ poetry self add poetry-plugin-export
$ poetry export --format requirements.txt --output $WHEELHOUSE_DIR/requirements.txt
$ poetry run pip wheel --ignore-requires-python --no-deps --wheel-dir $WHEELHOUSE_DIR --requirement $WHEELHOUSE_DIR/$ requirements.txt --require-hashes --only-binary :all:

And now regenerate the lockfile with the wheelhouse as source:

$ poetry source add wheelhouse --path=$WHEELHOUSE_DIR
$ poetry lock

This produces a lockfile, like this:

# This file is automatically @generated by Poetry 2.0.0 and should not be changed by hand.

[[package]]
name = "mypy"
version = "1.14.1"
description = "Optional static typing for Python"
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = []

[package.dependencies]
mypy_extensions = ">=1.0.0"
typing_extensions = ">=4.6.0"

[package.extras]
dmypy = ["psutil (>=4.0)"]
faster-cache = ["orjson"]
install-types = ["pip"]
mypyc = ["setuptools (>=50)"]
reports = ["lxml"]

[package.source]
type = "file"
url = "wheelhouse/mypy-1.14.1-py3-none-any.whl"

[[package]]
name = "mypy-extensions"
version = "1.0.0"
description = "Type system extensions for programs checked with the mypy type checker."
optional = false
python-versions = ">=3.5"
groups = ["main"]
files = []

[package.source]
type = "file"
url = "wheelhouse/mypy_extensions-1.0.0-py3-none-any.whl"

[[package]]
name = "typing_extensions"
version = "4.12.2"
description = "Backported and Experimental Type Hints for Python 3.8+"
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = []

[package.source]
type = "file"
url = "wheelhouse/typing_extensions-4.12.2-py3-none-any.whl"

[metadata]
lock-version = "2.1"
python-versions = "~3.12"
content-hash = "0f9b3c65330adba20ac5fb67def1b4d65e530295b30746dca5acf3d1a996a1a2"

And the dependency can normally be installed.

Summary by Sourcery

New Features:

  • Add local wheelhouse repositories as a source type, enabling offline installs and custom sources for pre-built packages.

Copy link

sourcery-ai bot commented Jan 11, 2025

Reviewer's Guide by Sourcery

This pull request introduces a new feature that allows using a local directory containing wheel files as a repository. This is particularly useful for offline work, containerized environments, or CI/CD pipelines where direct internet access might be restricted.

Sequence diagram for adding a local wheelhouse repository

sequenceDiagram
    actor User
    participant CLI as Poetry CLI
    participant Factory as Poetry Factory
    participant Config as Poetry Config
    participant Repo as LocalPathRepository

    User->>CLI: poetry source add wheelhouse --path=./wheelhouse
    CLI->>Factory: create_package_source()
    Factory->>Config: validate source config
    Factory->>Repo: create LocalPathRepository
    Repo->>Repo: validate directory
    Repo->>Repo: scan for wheel files
    Repo-->>Factory: return repository instance
    Factory-->>CLI: repository added
    CLI-->>User: success message
Loading

Class diagram for the new LocalPathRepository implementation

classDiagram
    class Repository {
        <<abstract>>
        +name: str
        +packages: list
    }

    class LocalPathRepository {
        -_path: Path
        +path: str
        +__init__(name: str, path: str)
        -_list_packages_in_path(): Iterator[Package]
        +find_links_for_package(package: Package): list[Link]
    }

    class Source {
        +name: str
        +url: str
        +path: str
        +priority: Priority
    }

    Repository <|-- LocalPathRepository
    note for LocalPathRepository "New repository type for local wheel directories"
Loading

File-Level Changes

Change Details Files
Added support for local wheelhouse repositories.
  • Added LocalPathRepository to handle local paths.
  • Modified create_poetry to handle paths in source definitions.
  • Updated create_pool to log repository paths.
  • Added path option to SourceAddCommand.
  • Added support for path-based repositories in _get_environment_repositories.
  • Added path attribute to Source config.
  • Added a new file wheelhouse_repository.py to implement the LocalPathRepository class.
  • Updated poetry.json schema to include the path option for sources.
  • Modified create_package_source to handle local paths as sources and return a LocalPathRepository instance.
  • Updated SourceAddCommand to handle the path option and create a Source object with the provided path.
  • Updated config.py to include environment variables for repository paths.
  • Updated source.py to include the path attribute for sources.
  • Added find_links_for_package to LocalPathRepository to return a list of links for a given package.
  • Added _list_packages_in_path to LocalPathRepository to list all packages in the given path.
  • Added path property to LocalPathRepository to return the path of the repository.
  • Added a new LocalPathRepository class to handle local repositories.
  • Updated factory.py to handle local repository paths.
  • Updated add.py to handle local repository paths.
  • Updated config.py to handle local repository paths.
  • Updated source.py to handle local repository paths.
  • Updated poetry.json to handle local repository paths.
src/poetry/factory.py
src/poetry/console/commands/source/add.py
src/poetry/config/config.py
src/poetry/config/source.py
src/poetry/json/schemas/poetry.json
src/poetry/repositories/wheelhouse_repository.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time. You can also use
    this command to specify where the summary should be inserted.

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey @florianvazelle - I've reviewed your changes - here's some feedback:

Overall Comments:

  • Please add documentation for the new local path repository feature, including configuration options and usage examples.
  • Tests should be added to verify the local path repository functionality, including error cases for malformed wheels and invalid paths.
  • Consider improving error handling in LocalPathRepository._list_packages_in_path() to provide more informative errors when processing invalid wheel files.
Here's what I looked at during the review
  • 🟡 General issues: 3 issues found
  • 🟢 Security: all looks good
  • 🟢 Testing: all looks good
  • 🟢 Complexity: all looks good
  • 🟢 Documentation: all looks good

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +38 to +39
def find_links_for_package(self, package: Package) -> list[Link]:
return [Link(path_to_url(package.source_type))]
Copy link

Choose a reason for hiding this comment

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

issue (bug_risk): The find_links_for_package method appears to be using the wrong attribute for path generation

Using package.source_type (which is typically a string like 'directory' or 'file') as a path will likely cause errors. Consider using package.source_url or constructing the correct path from self._path and the package filename.

Comment on lines 208 to +216
try:
url = source["url"]
except KeyError:
raise InvalidSourceError(f"Missing [url] in source {name!r}.")

repository_class = LegacyRepository
try:
path = source["path"]
except KeyError:
raise InvalidSourceError(f"Missing [url] or [path] in source {name!r}.")
else:
return LocalPathRepository(name, path)
Copy link

Choose a reason for hiding this comment

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

suggestion: Consider validating that only one of 'url' or 'path' is provided in source configuration

When both 'url' and 'path' are provided, the code silently ignores 'path'. Consider adding validation to explicitly handle this case and provide clear feedback to users.

for file_path in self._path.iterdir():
try:
yield PackageInfo.from_path(path=file_path).to_package(root_dir=file_path)
except Exception:
Copy link

Choose a reason for hiding this comment

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

suggestion (bug_risk): Avoid catching bare Exception without logging

Catching all exceptions silently could hide important errors. Consider logging the exceptions to help with debugging and potentially catch more specific exceptions.

Suggested implementation:

            try:
                yield PackageInfo.from_path(path=file_path).to_package(root_dir=file_path)
            except (ValueError, OSError) as e:
                logger.warning(
                    "Failed to load package info from %s: %s", file_path, str(e)
                )
                continue

You'll also need to add a logger instance at the top of the file if it doesn't exist already:

logger = logging.getLogger(__name__)

The exceptions I've chosen (ValueError, OSError) are common ones that might occur when reading package info, but you may want to adjust these based on the specific exceptions that PackageInfo.from_path() and to_package() can raise.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Allow a folder with a list of wheels to be a repository, mimic pip --find-links
1 participant