Repository: python-poetry/poetry

Issue:
Poetry installs successfully a dependency via URL, but fails via local path
<!--
    Hi there! Thank you for submitting a bug report!

    Before you submit your issue, please review and follow the instructions at
    https://python-poetry.org/docs/contributing/#reporting-bugs. Fully fill out the template below; insufficient
    information or bad reproduction instructions will impair the ability of others to help you.
-->

<!-- All the below information must be provided for others to understand and help with your issue. -->
- **Poetry version**: `1.7.0`
- **Python version**: `3.10.12`
- **OS version and name**: MacOS 14.0
- **pyproject.toml**: <!-- Replace with a link to a Gist (https://gist.github.com/) containing your sanitized pyproject.toml -->

<!-- All the below steps should be completed before submitting your issue. Checked checkbox should look like this: [x] -->
- [x] I am on the [latest](https://github.com/python-poetry/poetry/releases/latest) stable Poetry version, installed using a recommended method.
- [x] I have searched the [issues](https://github.com/python-poetry/poetry/issues) of this repo and believe that this is not a duplicate.
- [x] I have consulted the [FAQ](https://python-poetry.org/docs/faq/) and [blog](https://python-poetry.org/blog/) for any relevant entries or release notes.
- [x] If an exception occurs when executing a command, I executed it again in debug mode (`-vvv` option) and have included the output below.

## Issue

Poetry successfully installs a `tar.gz` served via a URL, but if the exact same dep/file is downloaded locally, then Poetry fails to install it. I'm very confused, I'm not sure I'm missing something blatantly obvious here.

Here is very straightforward way to reproduce:

Successful:
```
> poetry new teeeest
> cd teeeest
> poetry add https://developers.google.com/static/search-ads/reporting/download/python/searchads360-py.tar.gz
```

Fails:
```
> poetry remove google-ads-searchads360
> mkdir libs && wget https://developers.google.com/static/search-ads/reporting/download/python/searchads360-py.tar.gz -P libs
> poetry add libs/searchads360-py.tar.gz


Updating dependencies
Resolving dependencies... (5.5s)Path /var/folders/9g/r0mdzn2s7_l5cd3_2v_y6m6c0000gn/T/tmpwwsu787v/searchads360-py for google-ads-searchads360 does not exist
Path /var/folders/9g/r0mdzn2s7_l5cd3_2v_y6m6c0000gn/T/tmpwwsu787v/searchads360-py for google-ads-searchads360 does not exist
Path /var/folders/9g/r0mdzn2s7_l5cd3_2v_y6m6c0000gn/T/tmpwwsu787v/searchads360-py for google-ads-searchads360 does not exist
Path /var/folders/9g/r0mdzn2s7_l5cd3_2v_y6m6c0000gn/T/tmpwwsu787v/searchads360-py for google-ads-searchads360 does not exist
Path /var/folders/9g/r0mdzn2s7_l5cd3_2v_y6m6c0000gn/T/tmpwwsu787v/searchads360-py for google-ads-searchads360 does not exist


  OverrideNeeded

  ({Package('google-ads-searchads360', '0.0.1', source_type='directory', source_url='/var/folders/9g/r0mdzn2s7_l5cd3_2v_y6m6c0000gn/T/tmpwwsu787v/searchads360-py'): {'proto-plus': <Dependency proto-plus (>=1.22.2,<2.0.0dev)>}}, {Package('google-ads-searchads360', '0.0.1', source_type='directory', source_url='/var/folders/9g/r0mdzn2s7_l5cd3_2v_y6m6c0000gn/T/tmpwwsu787v/searchads360-py'): {'proto-plus': <Dependency proto-plus (>=1.19.4,<2.0.0dev)>}})

  at ~/.pyenv/versions/3.10.12/lib/python3.10/site-packages/poetry/puzzle/provider.py:660 in complete_package
      656│                     current_overrides.update({dependency_package: package_overrides})
      657│                     overrides.append(current_overrides)
      658│ 
      659│             if overrides:
    → 660│                 raise OverrideNeeded(*overrides)
      661│ 
      662│         # Modifying dependencies as needed
      663│         clean_dependencies = []
      664│         for dep in dependencies:

The following error occurred when trying to handle this error:


  AssertionError

  

  at ~/.pyenv/versions/3.10.12/lib/python3.10/site-packages/poetry/mixology/partial_solution.py:151 in _register
      147│         name = assignment.dependency.complete_name
      148│         old_positive = self._positive.get(name)
      149│         if old_positive is not None:
      150│             value = old_positive.intersect(assignment)
    → 151│             assert value is not None
      152│             self._positive[name] = value
      153│ 
      154│             return
      155│ 
```

And succesful again:
```
> python -m http.server 8888 --directory libs &
> poetry add http://localhost:8888/searchads360-py.tar.gz
```
Poetry installs successfully a dependency via URL, but fails via local path
<!--
    Hi there! Thank you for submitting a bug report!

    Before you submit your issue, please review and follow the instructions at
    https://python-poetry.org/docs/contributing/#reporting-bugs. Fully fill out the template below; insufficient
    information or bad reproduction instructions will impair the ability of others to help you.
-->

<!-- All the below information must be provided for others to understand and help with your issue. -->
- **Poetry version**: `1.7.0`
- **Python version**: `3.10.12`
- **OS version and name**: MacOS 14.0
- **pyproject.toml**: <!-- Replace with a link to a Gist (https://gist.github.com/) containing your sanitized pyproject.toml -->

<!-- All the below steps should be completed before submitting your issue. Checked checkbox should look like this: [x] -->
- [x] I am on the [latest](https://github.com/python-poetry/poetry/releases/latest) stable Poetry version, installed using a recommended method.
- [x] I have searched the [issues](https://github.com/python-poetry/poetry/issues) of this repo and believe that this is not a duplicate.
- [x] I have consulted the [FAQ](https://python-poetry.org/docs/faq/) and [blog](https://python-poetry.org/blog/) for any relevant entries or release notes.
- [x] If an exception occurs when executing a command, I executed it again in debug mode (`-vvv` option) and have included the output below.

## Issue

Poetry successfully installs a `tar.gz` served via a URL, but if the exact same dep/file is downloaded locally, then Poetry fails to install it. I'm very confused, I'm not sure I'm missing something blatantly obvious here.

Here is very straightforward way to reproduce:

Successful:
```
> poetry new teeeest
> cd teeeest
> poetry add https://developers.google.com/static/search-ads/reporting/download/python/searchads360-py.tar.gz
```

Fails:
```
> poetry remove google-ads-searchads360
> mkdir libs && wget https://developers.google.com/static/search-ads/reporting/download/python/searchads360-py.tar.gz -P libs
> poetry add libs/searchads360-py.tar.gz


Updating dependencies
Resolving dependencies... (5.5s)Path /var/folders/9g/r0mdzn2s7_l5cd3_2v_y6m6c0000gn/T/tmpwwsu787v/searchads360-py for google-ads-searchads360 does not exist
Path /var/folders/9g/r0mdzn2s7_l5cd3_2v_y6m6c0000gn/T/tmpwwsu787v/searchads360-py for google-ads-searchads360 does not exist
Path /var/folders/9g/r0mdzn2s7_l5cd3_2v_y6m6c0000gn/T/tmpwwsu787v/searchads360-py for google-ads-searchads360 does not exist
Path /var/folders/9g/r0mdzn2s7_l5cd3_2v_y6m6c0000gn/T/tmpwwsu787v/searchads360-py for google-ads-searchads360 does not exist
Path /var/folders/9g/r0mdzn2s7_l5cd3_2v_y6m6c0000gn/T/tmpwwsu787v/searchads360-py for google-ads-searchads360 does not exist


  OverrideNeeded

  ({Package('google-ads-searchads360', '0.0.1', source_type='directory', source_url='/var/folders/9g/r0mdzn2s7_l5cd3_2v_y6m6c0000gn/T/tmpwwsu787v/searchads360-py'): {'proto-plus': <Dependency proto-plus (>=1.22.2,<2.0.0dev)>}}, {Package('google-ads-searchads360', '0.0.1', source_type='directory', source_url='/var/folders/9g/r0mdzn2s7_l5cd3_2v_y6m6c0000gn/T/tmpwwsu787v/searchads360-py'): {'proto-plus': <Dependency proto-plus (>=1.19.4,<2.0.0dev)>}})

  at ~/.pyenv/versions/3.10.12/lib/python3.10/site-packages/poetry/puzzle/provider.py:660 in complete_package
      656│                     current_overrides.update({dependency_package: package_overrides})
      657│                     overrides.append(current_overrides)
      658│ 
      659│             if overrides:
    → 660│                 raise OverrideNeeded(*overrides)
      661│ 
      662│         # Modifying dependencies as needed
      663│         clean_dependencies = []
      664│         for dep in dependencies:

The following error occurred when trying to handle this error:


  AssertionError

  

  at ~/.pyenv/versions/3.10.12/lib/python3.10/site-packages/poetry/mixology/partial_solution.py:151 in _register
      147│         name = assignment.dependency.complete_name
      148│         old_positive = self._positive.get(name)
      149│         if old_positive is not None:
      150│             value = old_positive.intersect(assignment)
    → 151│             assert value is not None
      152│             self._positive[name] = value
      153│ 
      154│             return
      155│ 
```

And succesful again:
```
> python -m http.server 8888 --directory libs &
> poetry add http://localhost:8888/searchads360-py.tar.gz
```


Discussion:
installing a source distribution as a direct dependency seems to have been broken for years, if indeed it ever worked.

I guess no-one is doing that, and I recommend that you don't either!
I see. Thanks for clarifying that. It should be reflected in the documentation with a warning because it currently contains clear examples for doing just that.
this has also been true for years, but feel free to submit a merge request
I reckon this should fix it, if anyone wants this enough to write a testcase and submit a merge request:
```diff
diff --git a/src/poetry/inspection/info.py b/src/poetry/inspection/info.py
index 2e6875aa..9d317933 100644
--- a/src/poetry/inspection/info.py
+++ b/src/poetry/inspection/info.py
@@ -319,6 +319,8 @@ class PackageInfo:

             # now this is an unpacked directory we know how to deal with
             new_info = cls.from_directory(path=sdist_dir)
+            new_info._source_type = "file"
+            new_info._source_url = path.resolve().as_posix()

         if not info:
             return new_info
```
installing a source distribution as a direct dependency seems to have been broken for years, if indeed it ever worked.

I guess no-one is doing that, and I recommend that you don't either!
I see. Thanks for clarifying that. It should be reflected in the documentation with a warning because it currently contains clear examples for doing just that.
this has also been true for years, but feel free to submit a merge request
I reckon this should fix it, if anyone wants this enough to write a testcase and submit a merge request:
```diff
diff --git a/src/poetry/inspection/info.py b/src/poetry/inspection/info.py
index 2e6875aa..9d317933 100644
--- a/src/poetry/inspection/info.py
+++ b/src/poetry/inspection/info.py
@@ -319,6 +319,8 @@ class PackageInfo:

             # now this is an unpacked directory we know how to deal with
             new_info = cls.from_directory(path=sdist_dir)
+            new_info._source_type = "file"
+            new_info._source_url = path.resolve().as_posix()

         if not info:
             return new_info
```

Resolving patch:
diff --git a/src/poetry/inspection/info.py b/src/poetry/inspection/info.py
--- a/src/poetry/inspection/info.py
+++ b/src/poetry/inspection/info.py
@@ -247,8 +247,8 @@ def _from_distribution(
         else:
             requires = Path(dist.filename) / "requires.txt"
             if requires.exists():
-                with requires.open(encoding="utf-8") as f:
-                    requirements = parse_requires(f.read())
+                text = requires.read_text(encoding="utf-8")
+                requirements = parse_requires(text)
 
         info = cls(
             name=dist.name,
@@ -274,16 +274,13 @@ def _from_sdist_file(cls, path: Path) -> PackageInfo:
         """
         info = None
 
-        try:
-            info = cls._from_distribution(pkginfo.SDist(str(path)))
-        except ValueError:
-            # Unable to determine dependencies
-            # We pass and go deeper
-            pass
-        else:
-            if info.requires_dist is not None:
-                # we successfully retrieved dependencies from sdist metadata
-                return info
+        with contextlib.suppress(ValueError):
+            sdist = pkginfo.SDist(str(path))
+            info = cls._from_distribution(sdist)
+
+        if info is not None and info.requires_dist is not None:
+            # we successfully retrieved dependencies from sdist metadata
+            return info
 
         # Still not dependencies found
         # So, we unpack and introspect
@@ -313,6 +310,8 @@ def _from_sdist_file(cls, path: Path) -> PackageInfo:
 
             # now this is an unpacked directory we know how to deal with
             new_info = cls.from_directory(path=sdist_dir)
+            new_info._source_type = "file"
+            new_info._source_url = path.resolve().as_posix()
 
         if not info:
             return new_info
@@ -518,7 +517,8 @@ def from_wheel(cls, path: Path) -> PackageInfo:
         :param path: Path to wheel.
         """
         try:
-            return cls._from_distribution(pkginfo.Wheel(str(path)))
+            wheel = pkginfo.Wheel(str(path))
+            return cls._from_distribution(wheel)
         except ValueError:
             return PackageInfo()
 
@@ -529,14 +529,12 @@ def from_bdist(cls, path: Path) -> PackageInfo:
 
         :param path: Path to bdist.
         """
-        if isinstance(path, (pkginfo.BDist, pkginfo.Wheel)):
-            cls._from_distribution(dist=path)
-
         if path.suffix == ".whl":
             return cls.from_wheel(path=path)
 
         try:
-            return cls._from_distribution(pkginfo.BDist(str(path)))
+            bdist = pkginfo.BDist(str(path))
+            return cls._from_distribution(bdist)
         except ValueError as e:
             raise PackageInfoError(path, e)
 


Test patch:
diff --git a/tests/fixtures/distributions/demo_no_pkg_info-0.1.0.tar.gz b/tests/fixtures/distributions/demo_no_pkg_info-0.1.0.tar.gz
new file mode 100644
Binary files /dev/null and b/tests/fixtures/distributions/demo_no_pkg_info-0.1.0.tar.gz differ
diff --git a/tests/inspection/test_info.py b/tests/inspection/test_info.py
--- a/tests/inspection/test_info.py
+++ b/tests/inspection/test_info.py
@@ -114,16 +114,30 @@ def demo_check_info(info: PackageInfo, requires_dist: set[str] | None = None) ->
 def test_info_from_sdist(demo_sdist: Path) -> None:
     info = PackageInfo.from_sdist(demo_sdist)
     demo_check_info(info)
+    assert info._source_type == "file"
+    assert info._source_url == demo_sdist.resolve().as_posix()
+
+
+def test_info_from_sdist_no_pkg_info(fixture_dir: FixtureDirGetter) -> None:
+    path = fixture_dir("distributions") / "demo_no_pkg_info-0.1.0.tar.gz"
+    info = PackageInfo.from_sdist(path)
+    demo_check_info(info)
+    assert info._source_type == "file"
+    assert info._source_url == path.resolve().as_posix()
 
 
 def test_info_from_wheel(demo_wheel: Path) -> None:
     info = PackageInfo.from_wheel(demo_wheel)
     demo_check_info(info)
+    assert info._source_type == "file"
+    assert info._source_url == demo_wheel.resolve().as_posix()
 
 
 def test_info_from_bdist(demo_wheel: Path) -> None:
     info = PackageInfo.from_bdist(demo_wheel)
     demo_check_info(info)
+    assert info._source_type == "file"
+    assert info._source_url == demo_wheel.resolve().as_posix()
 
 
 def test_info_from_poetry_directory(fixture_dir: FixtureDirGetter) -> None: