Repository: pdm-project/pdm

Issue:
TypeError: a bytes-like object is required, not 'str'
- [X] I have searched the issue tracker and believe that this is not a duplicate.

## Steps to reproduce

pdm add -v model-bakery==1.3.3

## Actual behavior

```
Adding packages to default dependencies: model-bakery==1.3.3
STATUS: Resolving dependencies
pdm.termui: ======== Start resolving requirements ========
pdm.termui:   model-bakery==1.3.3
pdm.termui:   python>=3.11
pdm.termui:   Adding requirement model-bakery==1.3.3
pdm.termui:   Adding requirement python>=3.11
pdm.termui: ======== Starting round 0 ========
STATUS: Resolving: new pin python>=3.11
pdm.termui: Pinning: python None
pdm.termui: ======== Ending round 0 ========
pdm.termui: ======== Starting round 1 ========
pdm.termui:   Adding requirement b''(from model-bakery 1.3.3)
Traceback (most recent call last):
  File "/opt/homebrew/bin/pdm", line 8, in <module>
    sys.exit(main())
             ^^^^^^
  File "/opt/homebrew/Cellar/pdm/2.4.8/libexec/lib/python3.11/site-packages/pdm/core.py", line 247, in main
    return Core().main(args)
           ^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/Cellar/pdm/2.4.8/libexec/lib/python3.11/site-packages/pdm/core.py", line 181, in main
    raise cast(Exception, err).with_traceback(traceback) from None
  File "/opt/homebrew/Cellar/pdm/2.4.8/libexec/lib/python3.11/site-packages/pdm/core.py", line 176, in main
    f(project, options)
  File "/opt/homebrew/Cellar/pdm/2.4.8/libexec/lib/python3.11/site-packages/pdm/cli/commands/add.py", line 57, in handle
    actions.do_add(
  File "/opt/homebrew/Cellar/pdm/2.4.8/libexec/lib/python3.11/site-packages/pdm/cli/actions.py", line 271, in do_add
    resolved = do_lock(project, strategy, tracked_names, reqs, dry_run=dry_run, hooks=hooks)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/Cellar/pdm/2.4.8/libexec/lib/python3.11/site-packages/pdm/cli/actions.py", line 94, in do_lock
    mapping, dependencies = resolve(
                            ^^^^^^^^
  File "/opt/homebrew/Cellar/pdm/2.4.8/libexec/lib/python3.11/site-packages/pdm/resolver/core.py", line 35, in resolve
    result = resolver.resolve(requirements, max_rounds)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/Cellar/pdm/2.4.8/libexec/lib/python3.11/site-packages/resolvelib/resolvers.py", line 521, in resolve
    state = resolution.resolve(requirements, max_rounds=max_rounds)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/Cellar/pdm/2.4.8/libexec/lib/python3.11/site-packages/resolvelib/resolvers.py", line 402, in resolve
    failure_causes = self._attempt_to_pin_criterion(name)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/Cellar/pdm/2.4.8/libexec/lib/python3.11/site-packages/resolvelib/resolvers.py", line 238, in _attempt_to_pin_criterion
    criteria = self._get_updated_criteria(candidate)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/Cellar/pdm/2.4.8/libexec/lib/python3.11/site-packages/resolvelib/resolvers.py", line 229, in _get_updated_criteria
    self._add_to_criteria(criteria, requirement, parent=candidate)
  File "/opt/homebrew/Cellar/pdm/2.4.8/libexec/lib/python3.11/site-packages/resolvelib/resolvers.py", line 172, in _add_to_criteria
    if not criterion.candidates:
  File "/opt/homebrew/Cellar/pdm/2.4.8/libexec/lib/python3.11/site-packages/resolvelib/structs.py", line 127, in __bool__
    next(iter(self))
  File "/opt/homebrew/Cellar/pdm/2.4.8/libexec/lib/python3.11/site-packages/pdm/resolver/providers.py", line 241, in matches_gen
    yield from super_find()
               ^^^^^^^^^^^^
  File "/opt/homebrew/Cellar/pdm/2.4.8/libexec/lib/python3.11/site-packages/pdm/resolver/providers.py", line 139, in matches_gen
    candidates = self._find_candidates(reqs[0])
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/Cellar/pdm/2.4.8/libexec/lib/python3.11/site-packages/pdm/resolver/providers.py", line 118, in _find_candidates
    can = make_candidate(requirement)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/Cellar/pdm/2.4.8/libexec/lib/python3.11/site-packages/pdm/models/candidates.py", line 592, in make_candidate
    return Candidate(req, name, version, link)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/Cellar/pdm/2.4.8/libexec/lib/python3.11/site-packages/pdm/models/candidates.py", line 147, in __init__
    link = req.as_file_link()  # type: ignore[attr-defined]
           ^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/Cellar/pdm/2.4.8/libexec/lib/python3.11/site-packages/pdm/models/requirements.py", line 318, in as_file_link
    return Link(url)
           ^^^^^^^^^
  File "<string>", line 9, in __init__
  File "/opt/homebrew/Cellar/pdm/2.4.8/libexec/lib/python3.11/site-packages/unearth/link.py", line 54, in __post_init__
    if self.url.startswith(vcs_prefixes):
       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: a bytes-like object is required, not 'str'
```

## Expected behavior

```
Adding packages to default dependencies: model-bakery
🔒 Lock successful
Changes are written to pdm.lock.
Changes are written to pyproject.toml.
Synchronizing working set with lock file: 4 to add, 0 to update, 0 to remove

  ✔ Install asgiref 3.6.0 successful
  ✔ Install model-bakery 1.3.3 successful
  ✔ Install sqlparse 0.4.3 successful
  ✔ Install django 4.1.7 successful

🎉 All complete!
```

## Environment Information

```bash
PDM version:
  2.4.8
Python Interpreter:
  /Users/wgordon/.venv/bin/python (3.11)
Project Root:
  /Users/wgordon
Project Packages:
  None
{
  "implementation_name": "cpython",
  "implementation_version": "3.11.2",
  "os_name": "posix",
  "platform_machine": "arm64",
  "platform_release": "22.3.0",
  "platform_system": "Darwin",
  "platform_version": "Darwin Kernel Version 22.3.0: Mon Jan 30 20:38:37 PST 2023; root:xnu-8792.81.3~2/RELEASE_ARM64_T6000",
  "python_full_version": "3.11.2",
  "platform_python_implementation": "CPython",
  "python_version": "3.11",
  "sys_platform": "darwin"
}
```


Comments:

Patch:
diff --git a/news/1765.bugfix.md b/news/1765.bugfix.md
new file mode 100644
index 00000000..3d58604a
--- /dev/null
+++ b/news/1765.bugfix.md
@@ -0,0 +1,1 @@
+Improve the error message when parsing an invalid requirement string.
diff --git a/src/pdm/models/requirements.py b/src/pdm/models/requirements.py
index 56d75472..e9e23db2 100644
--- a/src/pdm/models/requirements.py
+++ b/src/pdm/models/requirements.py
@@ -473,7 +473,10 @@ def parse_requirement(line: str, editable: bool = False) -> Requirement:
             m = _file_req_re.match(line)
             if m is None:
                 raise RequirementError(str(e)) from None
-            r = FileRequirement.create(**m.groupdict())
+            args = m.groupdict()
+            if not args["url"] and args["path"] and not os.path.exists(args["path"]):
+                raise RequirementError(str(e)) from None
+            r = FileRequirement.create(**args)
         else:
             r = Requirement.from_pkg_requirement(pkg_req)
         if replaced:


Test patch:
diff --git a/tests/models/test_candidates.py b/tests/models/test_candidates.py
index 5ee2373b..a9e58a38 100644
--- a/tests/models/test_candidates.py
+++ b/tests/models/test_candidates.py
@@ -102,6 +102,7 @@ def test_extras_warning(project, recwarn):
     assert candidate.version == "0.0.1"
 
 
+@pytest.mark.xfail(reason="packaging 22 no longer supports legacy specifiers")
 @pytest.mark.usefixtures("local_finder")
 def test_parse_abnormal_specifiers(project):
     req = parse_requirement("http://fixtures.test/artifacts/celery-4.4.2-py2.py3-none-any.whl")
diff --git a/tests/models/test_requirements.py b/tests/models/test_requirements.py
index 32b9a96a..a1ecc622 100644
--- a/tests/models/test_requirements.py
+++ b/tests/models/test_requirements.py
@@ -74,8 +74,9 @@ def test_convert_req_dict_to_req_line(req, result):
 @pytest.mark.parametrize(
     "line,expected",
     [
-        ("requests; os_name=>'nt'", "Invalid marker:"),
+        ("requests; os_name=>'nt'", None),
         ("./tests", r"The local path (.+)? is not installable"),
+        ("django>=2<4", None),
     ],
 )
 def test_illegal_requirement_line(line, expected):