Repository: pypa/pipenv

Issue:
Cannot parse python beta version markers, eg: 3.11.0b1
### Issue description

`pipenv install` fails if run in parallel in multiple projects in multiple tabs. It returns the following error:

```
Traceback (most recent call last):
  File "/home/robert/anaconda3/lib/python3.7/site-packages/pipenv/resolver.py", line 856, in <module>
    main()
  File "/home/robert/anaconda3/lib/python3.7/site-packages/pipenv/resolver.py", line 842, in main
    _main(
  File "/home/robert/anaconda3/lib/python3.7/site-packages/pipenv/resolver.py", line 822, in _main
    resolve_packages(
  File "/home/robert/anaconda3/lib/python3.7/site-packages/pipenv/resolver.py", line 783, in resolve_packages
    results = clean_results(results, resolver, project, dev)
  File "/home/robert/anaconda3/lib/python3.7/site-packages/pipenv/resolver.py", line 652, in clean_results
    entry_dict = translate_markers(entry.get_cleaned_dict(keep_outdated=False))
  File "/home/robert/anaconda3/lib/python3.7/site-packages/pipenv/resolver.py", line 277, in get_cleaned_dict
    if self.original_markers and not self.markers:
  File "/home/robert/anaconda3/lib/python3.7/site-packages/pipenv/resolver.py", line 634, in __getattribute__
    return super().__getattribute__(key)
  File "/home/robert/anaconda3/lib/python3.7/site-packages/pipenv/resolver.py", line 239, in original_markers
    original_markers, lockfile_dict = self.get_markers_from_dict(self.lockfile_dict)
  File "/home/robert/anaconda3/lib/python3.7/site-packages/pipenv/resolver.py", line 212, in get_markers_from_dict
    markers.add(normalize_marker_str(entry_dict["markers"]))
  File "/home/robert/.local/share/virtualenvs/cube_norman-5aqotYjS/lib/python3.10/site-packages/pipenv/vendor/requirementslib/models/markers.py", line 692, in normalize_marker_str
    parts = cleanup_pyspecs(pyversion)
  File "/home/robert/.local/share/virtualenvs/cube_norman-5aqotYjS/lib/python3.10/site-packages/pipenv/vendor/requirementslib/models/markers.py", line 286, in cleanup_pyspecs
    for op_and_version_type, versions in _group_by_op(tuple(specs)):
  File "/home/robert/.local/share/virtualenvs/cube_norman-5aqotYjS/lib/python3.10/site-packages/pipenv/vendor/requirementslib/models/markers.py", line 200, in _group_by_op
    specs = [_get_specs(x) for x in list(specs)]
  File "/home/robert/.local/share/virtualenvs/cube_norman-5aqotYjS/lib/python3.10/site-packages/pipenv/vendor/requirementslib/models/markers.py", line 200, in <listcomp>
    specs = [_get_specs(x) for x in list(specs)]
  File "/home/robert/.local/share/virtualenvs/cube_norman-5aqotYjS/lib/python3.10/site-packages/pipenv/vendor/requirementslib/models/markers.py", line 193, in _get_specs
    result.append((spec.operator, _tuplize_version(spec.version)))
  File "/home/robert/.local/share/virtualenvs/cube_norman-5aqotYjS/lib/python3.10/site-packages/pipenv/vendor/requirementslib/models/markers.py", line 124, in _tuplize_version
    return tuple(int(x) for x in filter(lambda i: i != "*", version.split(".")))
  File "/home/robert/.local/share/virtualenvs/cube_norman-5aqotYjS/lib/python3.10/site-packages/pipenv/vendor/requirementslib/models/markers.py", line 124, in <genexpr>
    return tuple(int(x) for x in filter(lambda i: i != "*", version.split(".")))
ValueError: invalid literal for int() with base 10: '0b1'
```

### Expected result

All `pipenv install` should execute without any interference between each other because these are completely independent projects.

### Steps to replicate

run `pipenv install` in multiple linux tabs and multiple (independent) Python projects in parallel. The more projects the more likely it will fail (I guess this is a caching issue so this cannot be replicated deterministically).

The only way to mitigate this is to run `pipenv --clear` and `pipenv install` sequentially.

-------------------------------------------------------------------------------

<details><summary>$ pipenv --support</summary>

Pipenv version: `'2022.6.7'`

Pipenv location: `'/home/robert/anaconda3/lib/python3.7/site-packages/pipenv'`

Python location: `'/home/robert/anaconda3/bin/python'`

Python installations found:

  - `3.8.10`: `/usr/bin/python3`
  - `3.8.10`: `/usr/bin/python3.8`
  - `3.7.13`: `/home/robert/anaconda3/bin/python3`
  - `3.7.13`: `/home/robert/anaconda3/bin/python3.7`
  - `3.7.13`: `/home/robert/anaconda3/bin/python`
  - `3.7.13`: `/home/robert/anaconda3/bin/python3.7m`
  - `2.7.18`: `/usr/bin/python2.7`
  - `2.7.18`: `/usr/bin/python2`
  - `2.7.18`: `/usr/bin/python`

PEP 508 Information:

```
{'implementation_name': 'cpython',
 'implementation_version': '3.7.13',
 'os_name': 'posix',
 'platform_machine': 'x86_64',
 'platform_python_implementation': 'CPython',
 'platform_release': '5.4.0-113-generic',
 'platform_system': 'Linux',
 'platform_version': '#127-Ubuntu SMP Wed May 18 14:30:56 UTC 2022',
 'python_full_version': '3.7.13',
 'python_version': '3.7',
 'sys_platform': 'linux'}
```

System environment variables:

  - `SHELL`
  - `SESSION_MANAGER`
  - `QT_ACCESSIBILITY`
  - `COLORTERM`
  - `XDG_CONFIG_DIRS`
  - `SSH_AGENT_LAUNCHER`
  - `XDG_MENU_PREFIX`
  - `GNOME_DESKTOP_SESSION_ID`
  - `LC_ADDRESS`
  - `GNOME_SHELL_SESSION_MODE`
  - `LC_NAME`
  - `SSH_AUTH_SOCK`
  - `XMODIFIERS`
  - `DESKTOP_SESSION`
  - `LC_MONETARY`
  - `GTK_MODULES`
  - `PWD`
  - `LOGNAME`
  - `XDG_SESSION_DESKTOP`
  - `XDG_SESSION_TYPE`
  - `XAUTHORITY`
  - `GJS_DEBUG_TOPICS`
  - `HOME`
  - `USERNAME`
  - `IM_CONFIG_PHASE`
  - `LC_PAPER`
  - `LANG`
  - `LS_COLORS`
  - `XDG_CURRENT_DESKTOP`
  - `VTE_VERSION`
  - `WAYLAND_DISPLAY`
  - `GNOME_TERMINAL_SCREEN`
  - `INVOCATION_ID`
  - `MANAGERPID`
  - `GJS_DEBUG_OUTPUT`
  - `GNOME_SETUP_DISPLAY`
  - `LESSCLOSE`
  - `XDG_SESSION_CLASS`
  - `TERM`
  - `LC_IDENTIFICATION`
  - `LESSOPEN`
  - `USER`
  - `GNOME_TERMINAL_SERVICE`
  - `DISPLAY`
  - `SHLVL`
  - `LC_TELEPHONE`
  - `QT_IM_MODULE`
  - `LC_MEASUREMENT`
  - `PAPERSIZE`
  - `XDG_RUNTIME_DIR`
  - `LC_TIME`
  - `JOURNAL_STREAM`
  - `XDG_DATA_DIRS`
  - `PATH`
  - `GDMSESSION`
  - `DBUS_SESSION_BUS_ADDRESS`
  - `LC_NUMERIC`
  - `OLDPWD`
  - `_`
  - `PIP_SHIMS_BASE_MODULE`
  - `PIP_DISABLE_PIP_VERSION_CHECK`
  - `PIP_PYTHON_PATH`
  - `PYTHONDONTWRITEBYTECODE`
  - `PYTHONFINDER_IGNORE_UNSUPPORTED`

Pipenv–specific environment variables:


Debug–specific environment variables:

  - `PATH`: `~/bin:/home/robert/anaconda3/bin:/home/robert/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin`
  - `SHELL`: `/bin/bash`
  - `LANG`: `en_US.UTF-8`
  - `PWD`: `/home/robert/PythonWorkspace/CubeProject/cube_norman`


---------------------------

Contents of `Pipfile` ('/home/robert/PythonWorkspace/CubeProject/cube_norman/Pipfile'):

```toml
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"

[[source]]
name = "Alcemy"
url = "https://__token__:${GITLAB_TOKEN_DONALD_READ}@gitlab.com/api/v4/projects/32572043/packages/pypi/simple"
verify_ssl = true

[packages]
flake8 = "==4.0.1"
black = "==22.3"
mypy = "==0.961"
isort = "==5.10.1"

pytest = "==7.1.2"
pytest-cov = "==3.0.0"
pytest-xdist = "==2.5.0"

simplejson = "==3.17.6"
fastapi = "==0.78.0"                                     # specifies pydantic as well
uvicorn = "==0.17.6"
pyjwt = "==2.4.0"
passlib = { version = "==1.7.4", extras = ["bcrypt"] }
boto3 = "==1.24.4"
requests = "==2.27"
sentry-sdk = "==1.5.12"
pytz = "==2022.1"
types-pytz = "2021.3"
python-dateutil = "==2.8.2"
types-python-dateutil = "==2.8.2"
cube-donald = { index = "Alcemy", version = "==0.14.0" }

[dev-packages]
pre-commit = "==2.19.0"
pyupgrade = "==2.34.0"

[scripts]
format = "bash -c 'black . && isort .'"
check = "bash -c 'flake8 . && mypy cube_norman'"

[requires]
python_version = "3.10"

[pipenv]
allow_prereleases = true
</details>




Discussion:
Okay, only starting it in parallel kinda triggered it, now the error persists and even happens when just running pipenv in a single tab every time. I did not change anything, but seriously the exact same setup worked yesterday.
@SmokinCaterpillar It appears to have run into a new recently reported issue of requirementslib:  https://github.com/sarugaku/requirementslib/issues/317
ah, yes looks like it. Pinning hypothesis to an older version fixed it. Thanks can be closed as duplicate.
@SmokinCaterpillar Its not a duplicate since the other ticket is in requirementslib.  I am going to keep this open until we can bring the fix into pipenv.

Resolving patch:
diff --git a/pipenv/vendor/requirementslib/__init__.py b/pipenv/vendor/requirementslib/__init__.py
--- a/pipenv/vendor/requirementslib/__init__.py
+++ b/pipenv/vendor/requirementslib/__init__.py
@@ -10,7 +10,7 @@
 from .models.pipfile import Pipfile
 from .models.requirements import Requirement
 
-__version__ = "1.6.4"
+__version__ = "1.6.5"
 
 
 logger = logging.getLogger(__name__)
diff --git a/pipenv/vendor/requirementslib/models/markers.py b/pipenv/vendor/requirementslib/models/markers.py
--- a/pipenv/vendor/requirementslib/models/markers.py
+++ b/pipenv/vendor/requirementslib/models/markers.py
@@ -9,7 +9,7 @@
 import distlib.markers
 import packaging.version
 from pipenv.vendor.packaging.markers import InvalidMarker, Marker
-from pipenv.vendor.packaging.specifiers import Specifier, SpecifierSet
+from pipenv.vendor.packaging.specifiers import LegacySpecifier, Specifier, SpecifierSet
 from pipenv.vendor.vistir.misc import dedup
 
 from ..environment import MYPY_RUNNING
@@ -21,7 +21,6 @@
 
     STRING_TYPE = Union[str, bytes, Text]
 
-
 MAX_VERSIONS = {1: 7, 2: 7, 3: 11, 4: 0}
 DEPRECATED_VERSIONS = ["3.0", "3.1", "3.2", "3.3"]
 
@@ -120,8 +119,17 @@ def from_pipfile(cls, name, pipfile):
 
 @lru_cache(maxsize=1024)
 def _tuplize_version(version):
-    # type: (STRING_TYPE) -> Tuple[int, ...]
-    return tuple(int(x) for x in filter(lambda i: i != "*", version.split(".")))
+    # type: (STRING_TYPE) -> Union[Tuple[()], Tuple[int, ...], Tuple[int, int, str]]
+    output = []
+    for idx, part in enumerate(version.split(".")):
+        if part == "*":
+            break
+        if idx in (0, 1):
+            # Only convert the major and minor identifiers into integers (if present),
+            # the patch identifier can include strings like 'b' marking a beta: ex 3.11.0b1
+            part = int(part)
+        output.append(part)
+    return tuple(output)
 
 
 @lru_cache(maxsize=1024)
@@ -140,15 +148,19 @@ def _format_version(version):
 def _format_pyspec(specifier):
     # type: (Union[STRING_TYPE, Specifier]) -> Specifier
     if isinstance(specifier, str):
-        if not any(op in specifier for op in Specifier._operators.keys()):
+        if not specifier.startswith(tuple(Specifier._operators.keys())):
             specifier = "=={0}".format(specifier)
         specifier = Specifier(specifier)
     version = getattr(specifier, "version", specifier).rstrip()
-    if version and version.endswith("*"):
-        if version.endswith(".*"):
-            version = version[:-2]
-        version = version.rstrip("*")
-        specifier = Specifier("{0}{1}".format(specifier.operator, version))
+    if version:
+        if version.startswith("*"):
+            # don't parse invalid identifiers
+            return specifier
+        if version.endswith("*"):
+            if version.endswith(".*"):
+                version = version[:-2]
+            version = version.rstrip("*")
+            specifier = Specifier("{0}{1}".format(specifier.operator, version))
     try:
         op = REPLACE_RANGES[specifier.operator]
     except KeyError:
@@ -172,7 +184,7 @@ def _format_pyspec(specifier):
 def _get_specs(specset):
     if specset is None:
         return
-    if is_instance(specset, Specifier):
+    if is_instance(specset, Specifier) or is_instance(specset, LegacySpecifier):
         new_specset = SpecifierSet()
         specs = set()
         specs.add(specset)
diff --git a/pipenv/vendor/requirementslib/models/utils.py b/pipenv/vendor/requirementslib/models/utils.py
--- a/pipenv/vendor/requirementslib/models/utils.py
+++ b/pipenv/vendor/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: