Repository: sarugaku/requirementslib (python)

Issue:
Support for paths with at sign (@)
It seems requirementslib is causing a crash in [pipenv](https://github.com/pypa/pipenv) in some rare circumstances.

### To reproduce:

- The user running the command has a home folder with an `@` sign in the name. In my example, the home folder is `/Users/some.user@acme.com`. 
  - While this may look weird and is probably not too common, it can be an issue with mobile user accounts on macOS. In some configurations, home folders may be set to the user's e-mail address when the user account is created, and this is actually valid.
  - I'm pretty sure you can reproduce the error by just putting the project inside a folder with an `@` sign anywhere in the path, but I haven't actually tried it myself.
- Create a python project within the path containing an `@` sign. In my example I use `/Users/some.user@acme.com/dev/myproject`.
- Define the project as an editable dependency in your `Pipfile`:
  ```
  [dev-packages]
  myproject = {editable = true, path = "."}
  ```
- Run `pipenv install --dev`.
- `pipenv` will crash with the following message (example):
  ```
  FileNotFoundError: [Errno 2] No such file or directory: '/Users/some.user'
  ```
  Full stack trace can be seen further down, but it is actually a red herring.

### The issue:
`pipenv` crashes [here](https://github.com/pypa/pipenv/blob/8753ceea30b2045d96fed112e70ca7e73d0ca08d/pipenv/environment.py#L769):
```python
if req.editable and req.line_instance.is_local and self.find_egg(match):
    requested_path = req.line_instance.path
    return requested_path and vistir.compat.samefile(requested_path, match.location)
```
The problem is that the property `req.line_instance.path` has stripped away everything after the `@` sign, containing just `/Users/some.user` in my example, while `match.location` contains the entire path.

The cause is the [`split_ref_from_uri(uri)` method](https://github.com/sarugaku/requirementslib/blob/330ad38e3ebe1fab99209aaa6373c5652b2b869e/src/requirementslib/models/utils.py#L555-L573) in this library. When [called from here](https://github.com/sarugaku/requirementslib/blob/330ad38e3ebe1fab99209aaa6373c5652b2b869e/src/requirementslib/models/requirements.py#L1165-L1166), you can probably see the issue:
```python
if path is not None and "@" in path:
    path, _ = split_ref_from_uri(path)
```
Here path will contain an `@` character, but it will not be an uri, meaning it is _not_ prefixed with `file://`. In the split method, scheme is thus set to `""`, and the following `if` check will be true:
```python
if scheme != "file" and "@" in path:
    path, _, ref = path.rpartition("@")
```
This strips away everything after the `@` character and thus `pipenv` crashes down the line.

### Full stack trace:
```$ pipenv install --dev
Courtesy Notice: Pipenv found itself running within a virtual environment, so it will automatically use that environment, instead of creating its own for any project. You can set PIPENV_IGNORE_VIRTUALENVS=1 to force pipenv to ignore that environment and create its own instead. You can set PIPENV_VERBOSITY=-1 to suppress this warning.
Installing dependencies from Pipfile.lock (2fec9f)...

Traceback (most recent call last):
  File "/Users/some.user@acme.com/dev/virtualenvs/myproject/bin/pipenv", line 8, in <module>
    sys.exit(cli())
  File "/Users/some.user@acme.com/dev/virtualenvs/myproject/lib/python3.9/site-packages/pipenv/vendor/click/core.py", line 1128, in __call__
    return self.main(*args, **kwargs)
  File "/Users/some.user@acme.com/dev/virtualenvs/myproject/lib/python3.9/site-packages/pipenv/vendor/click/core.py", line 1053, in main
    rv = self.invoke(ctx)
  File "/Users/some.user@acme.com/dev/virtualenvs/myproject/lib/python3.9/site-packages/pipenv/vendor/click/core.py", line 1659, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "/Users/some.user@acme.com/dev/virtualenvs/myproject/lib/python3.9/site-packages/pipenv/vendor/click/core.py", line 1395, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/Users/some.user@acme.com/dev/virtualenvs/myproject/lib/python3.9/site-packages/pipenv/vendor/click/core.py", line 754, in invoke
    return __callback(*args, **kwargs)
  File "/Users/some.user@acme.com/dev/virtualenvs/myproject/lib/python3.9/site-packages/pipenv/vendor/click/decorators.py", line 84, in new_func
    return ctx.invoke(f, obj, *args, **kwargs)
  File "/Users/some.user@acme.com/dev/virtualenvs/myproject/lib/python3.9/site-packages/pipenv/vendor/click/core.py", line 754, in invoke
    return __callback(*args, **kwargs)
  File "/Users/some.user@acme.com/dev/virtualenvs/myproject/lib/python3.9/site-packages/pipenv/cli/command.py", line 194, in install
    do_install(
  File "/Users/some.user@acme.com/dev/virtualenvs/myproject/lib/python3.9/site-packages/pipenv/core.py", line 1996, in do_install
    do_init(
  File "/Users/some.user@acme.com/dev/virtualenvs/myproject/lib/python3.9/site-packages/pipenv/core.py", line 1260, in do_init
    do_install_dependencies(
  File "/Users/some.user@acme.com/dev/virtualenvs/myproject/lib/python3.9/site-packages/pipenv/core.py", line 823, in do_install_dependencies
    batch_install(
  File "/Users/some.user@acme.com/dev/virtualenvs/myproject/lib/python3.9/site-packages/pipenv/core.py", line 687, in batch_install
    deps_to_install = [
  File "/Users/some.user@acme.com/dev/virtualenvs/myproject/lib/python3.9/site-packages/pipenv/core.py", line 688, in <listcomp>
    dep for dep in deps_to_install if not project.environment.is_satisfied(dep)
  File "/Users/some.user@acme.com/dev/virtualenvs/myproject/lib/python3.9/site-packages/pipenv/environment.py", line 769, in is_satisfied
    return requested_path and vistir.compat.samefile(requested_path, match.location)
  File "/opt/homebrew/Cellar/python@3.9/3.9.10/Frameworks/Python.framework/Versions/3.9/lib/python3.9/genericpath.py", line 100, in samefile
    s1 = os.stat(f1)
FileNotFoundError: [Errno 2] No such file or directory: '/Users/some.user'
```

Pull request description:
Fix an issue for projects with an at sign (`@`) in the path
It seems `requirementslib` is causing a crash in `pipenv` in some rare circumstances. If you define a package like this in a `Pipfile`:
```
myproject = {editable = true, path = "."}
```
and this project path contains an at sign (`@`), then requirementslib will parse the path wrong. The second clause in the added test case will fail without this fix.

Fixes #309

Patch:
diff --git a/news/309.bugfix.rst b/news/309.bugfix.rst
new file mode 100644
index 0000000..edf93a1
--- /dev/null
+++ b/news/309.bugfix.rst
@@ -0,0 +1,1 @@
+Fix an issue for projects with an at sign (``@``) in the path
diff --git a/src/requirementslib/models/utils.py b/src/requirementslib/models/utils.py
index c0f4b23..5b11f26 100644
--- a/src/requirementslib/models/utils.py
+++ b/src/requirementslib/models/utils.py
@@ -567,7 +567,7 @@ def split_ref_from_uri(uri):
     path = parsed.path if parsed.path else ""
     scheme = parsed.scheme if parsed.scheme else ""
     ref = None
-    if scheme != "file" and "@" in path:
+    if scheme != "file" and (re.match("^.*@[^/@]*$", path) or path.count("@") >= 2):
         path, _, ref = path.rpartition("@")
     parsed = parsed._replace(path=path)
     return (parsed.url, ref)


Test patch:
diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py
index 9fb4ce7..fb16961 100644
--- a/tests/unit/test_utils.py
+++ b/tests/unit/test_utils.py
@@ -100,6 +100,35 @@ def test_split_vcs_method_from_uri():
     )
 
 
+def test_split_ref_from_uri():
+    assert utils.split_ref_from_uri("https://github.com/sarugaku/plette.git") == (
+        "https://github.com/sarugaku/plette.git",
+        None,
+    )
+    assert utils.split_ref_from_uri("/Users/some.user@acme.com/dev/myproject") == (
+        "/Users/some.user@acme.com/dev/myproject",
+        None,
+    )
+    assert utils.split_ref_from_uri(
+        "https://user:password@github.com/sarugaku/plette.git"
+    ) == (
+        "https://user:password@github.com/sarugaku/plette.git",
+        None,
+    )
+    assert utils.split_ref_from_uri(
+        "git+https://github.com/pypa/pipenv.git@master#egg=pipenv"
+    ) == (
+        "git+https://github.com/pypa/pipenv.git#egg=pipenv",
+        "master",
+    )
+    assert utils.split_ref_from_uri(
+        "/Users/some.user@acme.com/dev/myproject@bugfix/309"
+    ) == (
+        "/Users/some.user@acme.com/dev/myproject",
+        "bugfix/309",
+    )
+
+
 # tests from pip-tools