diff --git a/src/poetry/config/config.py b/src/poetry/config/config.py index 22906db6a19..4a94f6a716b 100644 --- a/src/poetry/config/config.py +++ b/src/poetry/config/config.py @@ -206,6 +206,15 @@ def _get_environment_repositories() -> dict[str, dict[str, str]]: "url": os.environ[env_key] } + pattern = re.compile(r"POETRY_REPOSITORIES_(?P[A-Z_]+)_PATH") + + for env_key in os.environ: + match = pattern.match(env_key) + if match: + repositories[match.group("name").lower().replace("_", "-")] = { + "path": os.environ[env_key] + } + return repositories @property diff --git a/src/poetry/config/source.py b/src/poetry/config/source.py index aa7d9a25743..0f4856f1dd9 100644 --- a/src/poetry/config/source.py +++ b/src/poetry/config/source.py @@ -15,6 +15,7 @@ class Source: name: str url: str = "" + path: str = "" priority: Priority = ( Priority.PRIMARY ) # cheating in annotation: str will be converted to Priority in __post_init__ diff --git a/src/poetry/console/commands/source/add.py b/src/poetry/console/commands/source/add.py index 81b42d3fbea..35a82844e70 100644 --- a/src/poetry/console/commands/source/add.py +++ b/src/poetry/console/commands/source/add.py @@ -32,12 +32,17 @@ class SourceAddCommand(Command): argument( "url", "Source repository URL." - " Required, except for PyPI, for which it is not allowed.", + "Required for http repositoy, except for PyPI, for which it is not allowed.", optional=True, ), ] options: ClassVar[list[Option]] = [ + option( + "path", + "Source repository path.", + flag=False, + ), option( "priority", "p", @@ -54,6 +59,7 @@ def handle(self) -> int: name: str = self.argument("name") lower_name = name.lower() url: str = self.argument("url") + path: str = self.option("path", "") priority_str: str | None = self.option("priority", None) if lower_name == "pypi": @@ -63,7 +69,7 @@ def handle(self) -> int: "The URL of PyPI is fixed and cannot be set." ) return 1 - elif not url: + elif not url and not path: self.line_error( "A custom source cannot be added without a URL." ) @@ -75,7 +81,7 @@ def handle(self) -> int: priority = Priority[priority_str.upper()] sources = AoT([]) - new_source = Source(name=name, url=url, priority=priority) + new_source = Source(name=name, url=url, path=path, priority=priority) is_new_source = True for source in self.poetry.get_sources(): diff --git a/src/poetry/factory.py b/src/poetry/factory.py index d2f6e3f695b..47d1fedca26 100644 --- a/src/poetry/factory.py +++ b/src/poetry/factory.py @@ -90,6 +90,9 @@ def create_poetry( url = source.get("url") if name and url and name not in existing_repositories: repositories[name] = {"url": url} + path = source.get("path") + if name and path and name not in existing_repositories: + repositories[name] = {"path": path} config.merge({"repositories": repositories}) @@ -146,7 +149,7 @@ def create_pool( if io.is_debug(): io.write_line( - f"Adding repository {repository.name} ({repository.url})" + f"Adding repository {repository.name} ({getattr(repository, 'url', repository.path)})" f" and setting it as {priority.name.lower()}" ) @@ -182,6 +185,7 @@ def create_package_source( from poetry.repositories.legacy_repository import LegacyRepository from poetry.repositories.pypi_repository import PyPiRepository from poetry.repositories.single_page_repository import SinglePageRepository + from poetry.repositories.wheelhouse_repository import LocalPathRepository try: name = source["name"] @@ -204,20 +208,25 @@ def create_package_source( 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) + else: + repository_class = LegacyRepository - if re.match(r".*\.(htm|html)$", url): - repository_class = SinglePageRepository + if re.match(r".*\.(htm|html)$", url): + repository_class = SinglePageRepository - return repository_class( - name, - url, - config=config, - disable_cache=disable_cache, - pool_size=pool_size, - ) + return repository_class( + name, + url, + config=config, + disable_cache=disable_cache, + pool_size=pool_size, + ) @classmethod def create_pyproject_from_package(cls, package: Package) -> TOMLDocument: diff --git a/src/poetry/json/schemas/poetry.json b/src/poetry/json/schemas/poetry.json index ca48f5a9e3c..10d6a072a43 100644 --- a/src/poetry/json/schemas/poetry.json +++ b/src/poetry/json/schemas/poetry.json @@ -43,6 +43,10 @@ "description": "The url of the repository.", "format": "uri" }, + "path": { + "type": "string", + "description": "The path of the repository." + }, "priority": { "enum": [ "primary", diff --git a/src/poetry/repositories/wheelhouse_repository.py b/src/poetry/repositories/wheelhouse_repository.py new file mode 100644 index 00000000000..9e63687bda1 --- /dev/null +++ b/src/poetry/repositories/wheelhouse_repository.py @@ -0,0 +1,39 @@ +from __future__ import annotations + +import logging +from typing import Iterator +from pathlib import Path + +from cachecontrol.controller import logger as cache_control_logger +from poetry.core.packages.package import Package +from poetry.core.packages.utils.link import Link +from poetry.repositories.repository import Repository +from poetry.inspection.info import PackageInfo +from poetry.core.packages.utils.utils import path_to_url + +cache_control_logger.setLevel(logging.ERROR) + +logger = logging.getLogger(__name__) + + +class LocalPathRepository(Repository): + def __init__(self, name: str, path: str) -> None: + self._path = Path(path) + if not self._path.is_dir(): + raise ValueError(f"Path {self._path} is not a directory") + + super().__init__(name, self._list_packages_in_path()) + + @property + def path(self) -> str: + return self._path + + def _list_packages_in_path(self) -> Iterator[Package]: + for file_path in self._path.iterdir(): + try: + yield PackageInfo.from_path(path=file_path).to_package(root_dir=file_path) + except Exception: + continue + + def find_links_for_package(self, package: Package) -> list[Link]: + return [Link(path_to_url(package.source_type))] \ No newline at end of file