Repository: pypa/pipenv

Issue:
Default specifier operator regression from v2023.6.18
### Issue description

Describe the issue briefly here.

Look like requirement lib update change the format expected in a Pipfile to specify version requirements

### Expected result

Expecting `package = "X.Y.Z"` to be a valid spec 

### Actual result

Stack trace with `Invalid specifier: '0.95.0'`

### Steps to replicate

```
mkdir -p  /tmp/pyenvbug
cd /tmp/pyenvbug/
virtualenv env
. env/bin/activate
echo -e '[packages]\nfastapi = "0.95.0"\n' > Pipfile

pip install pipenv==2023.6.26
pipenv install  # fails with: Invalid specifier: '0.95.0'

pip install pipenv==2023.6.18
pipenv install  # works

deactivate
cd
rm -rf /tmp/pyenvbug
```

Looking at the code, look like the operator is compulsory so that `package = "==X.Y.Z"` is the way to specify version requirement

Correct me if I am wrong, but the syntax `package = "X.Y.Z"` is / was expected to work before, right? 

I have a bunch of Pipfile to fix with the "new" syntax if this is the way moving forward.


Discussion:
You are probably right, this is likely a regression.   If you see where to patch requirementslib or pipenv for this issue feel free to open a PR against pipenv and we can handle porting the fix over to reqlib -- otherwise I will look into it when I can get some more time.

Resolving patch:
diff --git a/pipenv/vendor/requirementslib/models/requirements.py b/pipenv/vendor/requirementslib/models/requirements.py
--- a/pipenv/vendor/requirementslib/models/requirements.py
+++ b/pipenv/vendor/requirementslib/models/requirements.py
@@ -34,7 +34,7 @@
 from pipenv.patched.pip._internal.req.req_install import InstallRequirement
 from pipenv.patched.pip._internal.utils.temp_dir import global_tempdir_manager
 from pipenv.patched.pip._internal.utils.urls import path_to_url, url_to_path
-from pipenv.patched.pip._vendor.distlib.util import cached_property
+from pipenv.patched.pip._vendor.distlib.util import cached_property, COMPARE_OP
 from pipenv.patched.pip._vendor.packaging.markers import Marker
 from pipenv.patched.pip._vendor.packaging.requirements import Requirement as PackagingRequirement
 from pipenv.patched.pip._vendor.packaging.specifiers import (
@@ -2544,6 +2544,10 @@ def from_pipfile(cls, name, pipfile):
         if hasattr(pipfile, "keys"):
             _pipfile = dict(pipfile).copy()
         _pipfile["version"] = get_version(pipfile)
+
+        # We ensure version contains an operator. Default to equals (==)
+        if _pipfile["version"] and COMPARE_OP.match(_pipfile["version"]) is None:
+            _pipfile["version"] = "=={}".format(_pipfile["version"])
         vcs = next(iter([vcs for vcs in VCS_LIST if vcs in _pipfile]), None)
         if vcs:
             _pipfile["vcs"] = vcs


Test patch:
diff --git a/tests/integration/test_install_basic.py b/tests/integration/test_install_basic.py
--- a/tests/integration/test_install_basic.py
+++ b/tests/integration/test_install_basic.py
@@ -90,6 +90,22 @@ def test_install_without_dev(pipenv_instance_private_pypi):
         assert c.returncode == 0
 
 
+@pytest.mark.basic
+@pytest.mark.install
+def test_install_with_version_req_default_operator(pipenv_instance_pypi):
+    """Ensure that running `pipenv install` work when spec is package = "X.Y.Z". """
+    with pipenv_instance_pypi(chdir=True) as p:
+        with open(p.pipfile_path, "w") as f:
+            contents = """
+[packages]
+fastapi = "0.95.0"
+            """.strip()
+            f.write(contents)
+        c = p.pipenv("install")
+        assert c.returncode == 0
+        assert "fastapi" in p.pipfile["packages"]
+
+
 @pytest.mark.basic
 @pytest.mark.install
 def test_install_without_dev_section(pipenv_instance_pypi):