Repository: python-poetry/poetry

Issue:
Malformed METADATA when using optional git dependencies
<!--
  Hi there! Thank you for discovering and submitting an issue.

  Before you submit this; let's make sure of a few things.
  Please make sure the following boxes are ticked if they are correct.
  If not, please try and fulfill these first.
-->

<!-- Checked checkbox should look like this: [x] -->
- [x] I am on the [latest](https://github.com/sdispater/poetry/releases/latest) Poetry version.
- [x] I have searched the [issues](https://github.com/sdispater/poetry/issues) of this repo and believe that this is not a duplicate.
- [x] If an exception occurs when executing a command, I executed it again in debug mode (`-vvv` option).

<!--
  Once those are done, if you're able to fill in the following list with your information,
  it'd be very helpful to whoever handles the issue.
-->

- **Poetry version**: 0.12.16
- **Link of a [Gist](https://gist.github.com/) with the contents of your pyproject.toml file**: https://gist.github.com/tyrion/fb7b70d437951548f5ee1b5946b9386e

## Issue

Poetry generates a malformed `METADATA` file when there is an optional git dependency specified in `pyproject.toml` (see attached gist).

To reproduce you just need to place the files contained in the attached gist in a directory and run:
```
$ pip install .
[..]
pip._vendor.pkg_resources.RequirementParseError: Parse error at "'extra =='": Expected stringEnd
```

The responsible for this behaviour is the `poetry.masonry.api.prepare_metadata_for_build_wheel` function, which is called by pip.

The generated `METADATA` file looks something like this:
```
Metadata-Version: 2.1
[..]
Requires-Python: >=3.6
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7
Provides-Extra: stubs
Requires-Dist: numpy-stubs @ git+https://github.com/numpy/numpy-stubs.git@master; extra == "stubs"
```

You can verify that the last line is incorrect using `pkg_resources`:
```
>>> pkg_resources.Requirement.parse('numpy-stubs @ git+https://github.com/numpy/numpy-stubs.git@master; extra == "stubs"')
pkg_resources.RequirementParseError: Invalid requirement, parse error at "'extra =='"
```
Or in the exact same way pip does:
```
>>> from pip._vendor.packaging import requirements as req
>>> req.REQUIREMENT.parseString('numpy-stubs @ git+https://github.com/numpy/numpy-stubs.git@master; extra == "stubs"')
pip._vendor.pyparsing.ParseException: Expected stringEnd (at char 67), (line:1, col:68)
```

To fix the issue it is necessary to add a space before the ';':
```
>>> req.REQUIREMENT.parseString('numpy-stubs @ git+https://github.com/numpy/numpy-stubs.git@master ; extra == "stubs"')
(['numpy-stubs', 'git+https://github.com/numpy/numpy-stubs.git@master', <Marker('extra == "stubs"')>], {'name': ['numpy-stubs'], 'url': ['git+https://github.com/numpy/numpy-stubs.git@master'], 'marker': [<Marker('extra == "stubs"')>]})
```




Discussion:

Resolving patch:
diff --git a/poetry/packages/dependency.py b/poetry/packages/dependency.py
--- a/poetry/packages/dependency.py
+++ b/poetry/packages/dependency.py
@@ -200,6 +200,9 @@ def to_pep_508(self, with_extras=True):  # type: (bool) -> str
             )
 
         if markers:
+            if self.is_vcs():
+                requirement += " "
+
             if len(markers) > 1:
                 markers = ["({})".format(m) for m in markers]
                 requirement += "; {}".format(" and ".join(markers))


Test patch:
diff --git a/tests/packages/test_vcs_dependency.py b/tests/packages/test_vcs_dependency.py
--- a/tests/packages/test_vcs_dependency.py
+++ b/tests/packages/test_vcs_dependency.py
@@ -20,3 +20,36 @@ def test_to_pep_508_with_extras():
     expected = "poetry[foo] @ git+https://github.com/sdispater/poetry.git@master"
 
     assert expected == dependency.to_pep_508()
+
+
+def test_to_pep_508_in_extras():
+    dependency = VCSDependency(
+        "poetry", "git", "https://github.com/sdispater/poetry.git"
+    )
+    dependency.in_extras.append("foo")
+
+    expected = (
+        'poetry @ git+https://github.com/sdispater/poetry.git@master ; extra == "foo"'
+    )
+    assert expected == dependency.to_pep_508()
+
+    dependency = VCSDependency(
+        "poetry", "git", "https://github.com/sdispater/poetry.git"
+    )
+    dependency.in_extras.append("foo")
+    dependency.extras.append("bar")
+
+    expected = 'poetry[bar] @ git+https://github.com/sdispater/poetry.git@master ; extra == "foo"'
+
+    assert expected == dependency.to_pep_508()
+
+    dependency = VCSDependency(
+        "poetry", "git", "https://github.com/sdispater/poetry.git", "b;ar;"
+    )
+    dependency.in_extras.append("foo;")
+
+    expected = (
+        'poetry @ git+https://github.com/sdispater/poetry.git@b;ar; ; extra == "foo;"'
+    )
+
+    assert expected == dependency.to_pep_508()