Repository: python-poetry/poetry

Issue:
Pre-release `~=` constrains are mis calculated
<!--
  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.
- [ ] 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.
-->

- **OS version and name**: Windows
- **Poetry version**: 0.12.16
- **Link of a [Gist](https://gist.github.com/) with the contents of your pyproject.toml file**: N/A, requires access to a proprietary pypi instance

## Issue
<!-- Now feel free to write your issue, but please be descriptive! Thanks again 🙌 ❤️ -->

My constraints aren't resolving.

A rough look at my dependency tree
- A: `B = "^0.7.5" via poetry
- B: `"C~=0.2.0dev16"` via setup.py

Poetry complains that it cannot resolve B's dependency on C
```
[SolverProblemError]
Because B (0.7.5) depends on C(>=0.2.0,<0.3.0) which doesn't match any versions, B is forbidden.
So, because no versions of B match >0.7.5,<0.8.0
 and A depends on B (^0.7.5), version solving failed.
```

I traced the problem down into [`semver/__init__.py:parse_single_constraint`](https://github.com/sdispater/poetry/blob/master/poetry/semver/__init__.py#L67) where the constraint
- `~=0.2.0dev16`
gets compiled into
- `>=0.2.0,<0.3.0`

In contrast, the constraint
- `~=2.0.dev0`
correctly gets compiled into
- ` >=2.0.dev0,<3.0.0`

The problem seems to be
```python
        if precision == 2:
            low = version
            high = version.stable.next_major
        else:
            low = Version(version.major, version.minor, 0)
            high = version.stable.next_minor
```
where if the `precision` is 1 or 3, then the pre-release is dropped from `low`, disqualifying them from resolving the constraint.


Discussion:
I'm assuming we could just do `low = Version(version.major, version.minor, 0, pre=".".join(str(p) for p in version.prerelease))`?

The part that confuses me is why `precision` being 1 or 3 gets that `else` clause.

EDIT: Corrected possible fix for the actual names and types
[This line](https://github.com/sdispater/poetry/blame/master/poetry/semver/__init__.py#L82) conflicts with [PEP440 compatible release](https://www.python.org/dev/peps/pep-0440/#compatible-release) semantics.

The false negative test:

https://github.com/sdispater/poetry/commit/c55d55a888538dbe8ae0b650997db6f9ef4d0aa9#diff-20f6d1388627d02d179420d26445d183R65


So as I was digging into this, it appears that `~=` semantics are broken.  `~=3.5.3` should be `VersionRange(Version(3, 5, 3), Version(3, 6, 0), True)` instead of `VersionRange(Version(3, 5, 0), Version(3, 6, 0), True)`.

@drunkwcodes  is that the problem you are highlighting?  I'm leaning towards handling that in a separate issue / PR.
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

As a user, this is a bit frustrating for issues to be automatically closed when the problem is unresponsiveness from the maintainers.

Resolving patch:
diff --git a/poetry/semver/__init__.py b/poetry/semver/__init__.py
--- a/poetry/semver/__init__.py
+++ b/poetry/semver/__init__.py
@@ -76,14 +76,12 @@ def parse_single_constraint(constraint):  # type: (str) -> VersionConstraint
         version = Version.parse(m.group(1))
 
         if precision == 2:
-            low = version
             high = version.stable.next_major
         else:
-            low = Version(version.major, version.minor, version.patch)
             high = version.stable.next_minor
 
         return VersionRange(
-            low, high, include_min=True, always_include_max_prerelease=True
+            version, high, include_min=True, always_include_max_prerelease=True
         )
 
     # Caret range


Test patch:
diff --git a/tests/semver/test_main.py b/tests/semver/test_main.py
--- a/tests/semver/test_main.py
+++ b/tests/semver/test_main.py
@@ -68,6 +68,10 @@ def test_parse_constraint_wildcard(input, constraint):
         ("~3.5", VersionRange(Version(3, 5, 0), Version(3, 6, 0), True)),
         ("~=3.5", VersionRange(Version(3, 5, 0), Version(4, 0, 0), True)),  # PEP 440
         ("~=3.5.3", VersionRange(Version(3, 5, 3), Version(3, 6, 0), True)),  # PEP 440
+        (
+            "~=3.5.3rc1",
+            VersionRange(Version(3, 5, 3, pre="rc1"), Version(3, 6, 0), True),
+        ),  # PEP 440
     ],
 )
 def test_parse_constraint_tilde(input, constraint):