Repository: pdm-project/pdm

Issue:
Inconsistent behaviour of `pdm add` in mode direct_minimal_versions
- [ x] I have searched the issue tracker and believe that this is not a duplicate.

## Steps to reproduce

1. Create an empty project, with no dependencies
2. run `pdm lock --strategy direct_minimal_versions` to activate the locking strategy
3. run `pdm add django`. This will modify the pyproject file to install the latest compatible django package (-> `django>=4.2.11` for python 3.9)
4. run again `pdm add django`. The pyproject file will now be modified to install the earliest compatible django package (-> `django>=1.1.3`)


## Expected behavior

* Running `pdm add` a second time should have no effect

## Environment Information

```bash
# Paste the output of `pdm info && pdm info --env` below:
PDM version:
  2.15.1
Python Interpreter:
  D:\tmp\test_pdm\.venv\Scripts\python.exe (3.9)
Project Root:
  D:/tmp/test_pdm
Local Packages:

{
  "implementation_name": "cpython",
  "implementation_version": "3.9.13",
  "os_name": "nt",
  "platform_machine": "AMD64",
  "platform_release": "10",
  "platform_system": "Windows",
  "platform_version": "10.0.19045",
  "python_full_version": "3.9.13",
  "platform_python_implementation": "CPython",
  "python_version": "3.9",
  "sys_platform": "win32"
}
```

## Discussion

* One could wonder whether `pdm add` should look for the latest or earliest compatible version on its first call. IMHO, the latest version should be sought, even in direct_minimal_versions strategy, since that strategy specifies how the lock file consistency with the pyproject file is enforced, not how the pyproject file is specified
* Another issue is what `pdm add` should do with respect to what is already specified in the pyproject file. The documentation doesn't say much about the policies followed by PDM if `pdm add` is called for an already-specified dependency
* When reading the documentation, it is not obvious how to reliably increase the lower bounds of the dependencies specified in the pyproject file with PDM. Should we always first edit the pyproject file and then run `pdm install`?

Thanks for the awesome work done on PDM!

Comments:

Patch:
diff --git a/news/2853.bugfix.md b/news/2853.bugfix.md
new file mode 100644
index 00000000..c3541790
--- /dev/null
+++ b/news/2853.bugfix.md
@@ -0,0 +1,1 @@
+Make `direct_minimal_versions` work on newly added dependencies.
diff --git a/src/pdm/cli/commands/add.py b/src/pdm/cli/commands/add.py
index 5f56bc52..df4d2ce1 100644
--- a/src/pdm/cli/commands/add.py
+++ b/src/pdm/cli/commands/add.py
@@ -155,6 +155,8 @@ class Command(BaseCommand):
         reqs = [
             r for g, deps in all_dependencies.items() if lock_groups is None or g in lock_groups for r in deps.values()
         ]
+        # pre-write the dependencies to the pyproject.toml to make them recognized by the resolver
+        project.add_dependencies(requirements, group, selection.dev or False, write=False)
         with hooks.skipping("post_lock"):
             resolved = do_lock(
                 project,
diff --git a/src/pdm/project/core.py b/src/pdm/project/core.py
index fa3a3ea1..89014ebb 100644
--- a/src/pdm/project/core.py
+++ b/src/pdm/project/core.py
@@ -570,6 +570,7 @@ class Project:
         to_group: str = "default",
         dev: bool = False,
         show_message: bool = True,
+        write: bool = True,
     ) -> None:
         deps, setter = self.use_pyproject_dependencies(to_group, dev)
         for _, dep in requirements.items():
@@ -583,7 +584,8 @@ class Project:
             else:
                 deps[matched_index] = req
         setter(cast(Array, deps).multiline(True))
-        self.pyproject.write(show_message)
+        if write:
+            self.pyproject.write(show_message)
 
     def init_global_project(self) -> None:
         if not self.is_global or not self.pyproject.empty():


Test patch:
diff --git a/tests/cli/test_add.py b/tests/cli/test_add.py
index 564938e7..22a5270d 100644
--- a/tests/cli/test_add.py
+++ b/tests/cli/test_add.py
@@ -256,6 +256,7 @@ def test_add_cached_vcs_requirement(project, mocker, pdm):
 @pytest.mark.usefixtures("repository")
 def test_add_with_dry_run(project, pdm):
     result = pdm(["add", "--dry-run", "requests"], obj=project, strict=True)
+    project.pyproject.reload()
     assert not project.get_dependencies()
     assert "requests 2.19.1" in result.stdout
     assert "urllib3 1.22" in result.stdout
@@ -330,3 +331,14 @@ def test_add_disable_cache(project, pdm, working_set):
 
     files = [file for file in cache_dir.rglob("*") if file.is_file()]
     assert not files
+
+
+@pytest.mark.usefixtures("working_set")
+def test_add_dependency_with_direct_minimal_versions(project, pdm, repository):
+    pdm(["lock", "-S", "direct_minimal_versions"], obj=project, strict=True)
+    repository.add_candidate("pytz", "2019.6")
+    pdm(["add", "django"], obj=project, strict=True)
+    all_candidates = project.locked_repository.all_candidates
+    assert "django>=1.11.8" in project.pyproject.metadata["dependencies"]
+    assert all_candidates["django"].version == "1.11.8"
+    assert all_candidates["pytz"].version == "2019.6"