-
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
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
Properly handle pre-existing .venv files #2690
Conversation
Hi, thanks for the work! I would recommend splitting this into two PRs instead—we are very close to feature freeze for a new version, and while the |
Sure. How do you recommend I split the PR? Create two new ones or try to salvage this PR by rewriting history? Will have to work on it tomorrow either way. |
Either would do, whatever is more convenient for you :) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you include the relevant check it keeps the control flow logic the same I think, so if @uranusjr is ok with that I also would be
name = self.virtualenv_name | ||
if self.project_directory: | ||
venv_path = os.path.join(self.project_directory, ".venv") | ||
if os.path.isfile(venv_path): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you use Path(venv_path)
you can use the check .exists() and not Path(venv_path).isdir()
for files. You should do the same path object instantiation on the files contents to make sure the virtualenv exists.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not isdir()
rather than just isfile()
? Furthermore, how would using pathlib over os.path help? Seems to me like an equivalent check can be written with os.path. I like pathlib but I've used os.path just to remain consistent with the rest of the code.
You should do the same path object instantiation on the files contents to make sure the virtualenv exists.
Is this just because the path wont be created if it doesn't exist when the venv is being created? If this is the case, it should only apply to the base directory and not the venv folder itself, right? Regardless, what would my code do if it determines the path doesn't exist? Create it? Throw an error?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are edge cases where isfile
and isdir
can both be false, such as device files. While it doesn’t really matter practically, it is semantically more correct to check for “not directory” because those special files are still readable. It also matches the directory check in is_venv_in_project
, which makes the code easier to reason with.
I’m wondering though, it may be a better idea to merge the two checks. is_venv_in_project
already checks for directory, so when you reach this part, an exists
check is enough. We shouldn’t reach here if it’s a directory; if it exists, we want to try reading it (and fails with a straight-up error if that fails).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree that it's better to rely on is_venv_in_project
's directory check. exists
should be sufficient here.
What should be done when the directory specified in a .venv file does not exist? That hasn't been addressed yet.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would need to fail back to use self.virtualenv_name
. Now you mentioned it, I realise the code is a bit tangled here and the implementation doesn’t work well. I’ll try to find some time to sort the structure out so this can progress.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
virtualenv
just creates the directory if it doesn't exist. I consider that to be a feature, so I don't think we need to fall back to self.virtualenv_name
after all.
Should a test case still be written for the |
It would be best to have a test for the |
Just rewriting history to split the PRs. Will try prioritising the bugfix PR and then come back to this PR once I've finished the former. |
@@ -267,7 +267,18 @@ def virtualenv_exists(self): | |||
def get_location_for_virtualenv(self): | |||
if self.is_venv_in_project(): | |||
return os.path.join(self.project_directory, ".venv") | |||
return str(get_workon_home().joinpath(self.virtualenv_name)) | |||
|
|||
name = self.virtualenv_name |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need to check for the presence of the project directory, the instantiation of a project in the first place depends on the project directory existing
given the current situation this can just become something like
def get_location_for_virtualenv(self):
if self.is_venv_in_project()
return os.path.join(self.project_directory, ".venv")
elif os.path.exists(self.path_to('.venv')):
venv_contents = None
try:
with open(self.path_to('.venv'), 'r') as fh:
venv_contents = fh.read()
except OSError:
pass
else:
venv_name = venv_contents.strip()
if any(sep in venv_name for sep in [os.path.sep, os.path.altsep]):
return _normalized(venv_name)
elif venv_name:
return str(get_workon_home().joinpath(venv_name))
return str(get_workon_home().joinpath(self.virtualenv_name))
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thoughts @uranusjr ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need to check for the presence of the project directory
Wouldn't that mean the same check in is_venv_in_project
is also redundant?
tests/integration/test_dot_venv.py
Outdated
def test_venv_file_exists(PipenvInstance, pypi): | ||
"""Tests virtualenv creation & package installation when a .venv file exists | ||
at the project root. | ||
""" | ||
with PipenvInstance(pypi=pypi, chdir=True) as p: | ||
file_path = os.path.join(p.path, '.venv') | ||
with open(file_path, 'w') as f: | ||
f.write('test-project') | ||
f.write('') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The merge should have kept test_venv_file_with_name
over test_venv_file_exists
(or kept both). We do want to write something to the file so we can test if the written name is used to find/create a venv of the same name in workon home.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hmmmmm I think I prioritized your test over the one he wrote, but that wasn't really intentional, I was just trying to triage and get everything moving so I can get a release out soon(tm). There is a test right below that one though and it does write a name of a directory to the file which is why I undid my inclusion of it here
The merge removed this project name in favor of creating a temporary directory in the test below it and using that directory's location as the path in the ,venv
file.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The difference between the two tests was one wrote a path to the file, the other just a name. If you think that the former adequately covers the latter case too then all is fine as is.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think those should be merged into one with pytest fixtures. The fixture would be something like
@pytest.mark.fixtures('content, venv_location', [
('just-a-name', Path(WORKON_HOME, 'just-a-name')),
('/an/absolute/path', Path('/an/absolute/path')),
('a/relative/path', Path(project_root, 'a/relative/path')),
])
(I added a third test case. If a relative path is specified, the venv should be created relative to the project’s root.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good call, but does it really need to be a fixture if we'll only have one test that uses it? Could just use @pytest.mark.parametrize
.
I'm not sure how WORKON_HOME
and project_root
be passed. My first thought was fixtures, but pytest currently doesn't support the use of fixtures in @pytest.mark.parametrize
. Would it work if it was left a fixture, and fixtures were used for the fixture's params?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah yes, excellent call, I was totally thinking about parametrize
, not fixtures!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don’t think we need to refactor tests to merge this PR but the point about a path vs a name is valid. You can’t parametrize a temporary directory as an input though so that would still need a separate test
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I played around with this idea of parametrisation but couldn't come up with any elegant solution - it'd create a bigger mess than it is intended to prevent.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you mind the messy commit history? I could just commit on top of what we have to make the test write a name again, or I could just rewrite history and re-do the merge.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I usually squash the commits a bit before merging, so make it as messy as you need to :p
We're only interested in testing if the virtual environment can be successfully created.
I don't know what's causing the build to fail on Buildkite - I can't see the results. Everything is fine on my end, other than |
the build is failing for unclear reasons relating to a tlsv1 error now on buildkite, which is basically unusable I guess. I'll turn travis back on tomorrow if I can't work it out sorry for the unreasonably long delay on this, we have been kind of sequestered putting together a bunch of critical elements of a refactor of the resolver |
It's fine; priorities are priorities. Don't worry I'm not going anywhere. Before continuing, I just wanted to make sure my changes weren't the cause of the build failure. |
I really can't tell what's going on here but it's failing to connect to the local package index due to an SSL issue? this is the only branch / test suite that fails on buildkite and i really don't know why... |
Is it failing to connect while running one of my tests or at some other point? The major changes to the tests were changing how the venv location is retrieved, doing However, I don't see anything obviously wrong with the tests and they do all pass on my machine. Maybe some underlying issue has been unearthed? What's the course of action? I'm not sure how I can help here. |
I don't see anything wrong either, let me check your branch out locally and see if I have any issues... I'm super confused about this one at the moment and I'm wondering if there is some unrelated ssl issue on the build machine The tests look fine, and the connection that's failing is to localhost... I'll show you whenever buildkite's frontend is willing to let me copy things |
|
Yeah, I also have no idea. Double checked that test and it indeed passes on my machine. Did you manage to test it locally. If so, how did that go? |
sigh. I think I have this sorted out -- checked out your branch and worked from there... it has absolutely nothing to do with any of these changes and I have no idea how this is passing in master |
Fixes #2680 as discussed in the issue with bonus support for custom paths in the .venv file. Not sure what to categorise this under for the news fragment so I will wait for someone's advice on that before writing up the news fragment.
Would like reviewers to especially look at the test cases written. Could the repeated code be moved elsewhere? Are path comparisons properly handled? - concerns are accounting for symlinks and case-insensitivity on Windows.