Allow non-existant path dependencies#507
Allow non-existant path dependencies#507adriangb wants to merge 9 commits intopython-poetry:mainfrom
Conversation
|
I looked into the version history for this file to see if I could find some rational for the way it currently works. This goes all the way back to the "Initial commit" so I guess at this point only @sdispater knows. |
|
@radoering any thoughts on this? |
|
I think it makes sense, but I have to take a deeper look if python-poetry/poetry#6844 is sufficient (i.e. covers all commands). For example, does Further, I think we should do a similar change for file dependencies because it's the same use case. |
|
related: python-poetry/poetry#668, #210 |
| return | ||
|
|
||
| if self._full_path.is_file(): | ||
| raise ValueError(f"{self._path} is a file, expected a directory") |
There was a problem hiding this comment.
Either all checks should raise an error or all checks should emit a warning (different modes). If I don't care if the directory exists, I won't care if it has been replaced with a file.
| raise ValueError(f"{self._path} is a file, expected a directory") | ||
|
|
||
| if not is_python_project(self._full_path): | ||
| raise ValueError( |
There was a problem hiding this comment.
Same here. If I don't care for existence, I won't care if it's not a Python package.
There was a problem hiding this comment.
I'd like to push back on this a bit. There's a real use case for creating a DirectoryDependency pointing to a path that does not exist: you deleted or renamed the dependency and are updating the lock file. I can't think of any use case where you'd want to point at an existing but invalid dependency, so in those cases I think failing fast is the best option.
| try: | ||
| self._full_path = self._base.joinpath(self._path).resolve() | ||
| except FileNotFoundError: | ||
| warn(f"Directory {self._path} does not exist", category=UserWarning) |
There was a problem hiding this comment.
I like mentioning the name of the dependency (how you did in the companion PR). Maybe, we can add it in the warning/error messages here, too.
| # cache this function to avoid multiple IO reads and parsing | ||
| self.supports_poetry = functools.lru_cache(maxsize=1)(self._supports_poetry) | ||
|
|
||
| def _validate(self) -> None: |
There was a problem hiding this comment.
I think we should try the approach mentioned in #210 (comment). We need two modes for _validate: a warning mode and an exception mode. The warning mode can be used in the __init__ and the exception mode in full_path. I think it should be save to assume that when accessing full_path the directory must exist (and be a valid project).
|
Kudos, SonarCloud Quality Gate passed!
|
| def test_create_poetry_fails_with_invalid_dev_dependencies_iff_with_dev_is_true() -> ( | ||
| None | ||
| ): | ||
| with pytest.raises(ValueError) as err: | ||
| Factory().create_poetry(fixtures_dir / "project_with_invalid_dev_deps") | ||
| assert "does not exist" in str(err.value) | ||
|
|
||
| Factory().create_poetry( | ||
| fixtures_dir / "project_with_invalid_dev_deps", with_groups=False | ||
| ) | ||
|
|
||
|
|
There was a problem hiding this comment.
Removing this test since we actually want to be able to create a Poetry object with invalid path deps, we just want it to fail for certain operations (install, show, etc.).
| def test_file_dependency_wrong_path() -> None: | ||
| with pytest.raises(ValueError): | ||
| FileDependency("demo", DIST_PATH / "demo-0.2.0.tar.gz") | ||
| FileDependency("demo", DIST_PATH / "demo-0.2.0.tar.gz").full_path |
There was a problem hiding this comment.
I think these tests should be testing each individual failure mode, but this was good enough for years so I suppose it will do
| extras=extras, | ||
| ) | ||
|
|
||
| def _get_full_path(self, raise_if_invalid: bool, name: str) -> Path: |
There was a problem hiding this comment.
Adding name was necessary because .name doesn't get set until super().__init__ is called which already requires full_path.
| if self._full_path is not None: | ||
| return self._full_path |
There was a problem hiding this comment.
This little bit of caching means that if we already checked in __init__ we don't need to check again. We could also cache the negative case but it doesn't seem worth the complexity IMO.
| raise ValueError(f"{self._path} is a file, expected a directory") | ||
|
|
||
| if not is_python_project(self._full_path): | ||
| raise ValueError( |
There was a problem hiding this comment.
I'd like to push back on this a bit. There's a real use case for creating a DirectoryDependency pointing to a path that does not exist: you deleted or renamed the dependency and are updating the lock file. I can't think of any use case where you'd want to point at an existing but invalid dependency, so in those cases I think failing fast is the best option.
|
So this works. Conceptually though I don't like the idea of having |
|
You have got a point there. On the one hand it's an easier transition from "validation in |
|
Sounds good. Do you think we could move forward with what we've got in the meantime? I think what we've got here is better than what we had before and the behavior ("automatic" validation) is largely the same, so we could treat making |
|
I took a deeper look into this topic and believe to have found a cleaner solution in #520. Please take a look if this approach fits your needs. |
|
Yep should work |








Currently
poetry lock --no-updatefails when you remove any path dependency because it first loads all dependencies from the lock file (which still has a reference to the dependency you just deleted) and it fails at that point because whenDirectoryDependencyis instantiated with a non-existent path it immediately throws an exception.This change allows the process to continue, which is the same behavior VCS dependencies have.
If the
pyproject.tomlhas a path dependency that doesn't exist we will want to error forpoetry installandpoetry lock(poetry buildwith path dependencies is already kinda a no-go).poetry installalready fails gracefully (if you lock the project then delete the path dependency and try to install; i.e. when no locking happens before we start installing), I opened python-poetry/poetry#6844 to makepoetry lockfail gracefully.