Repository: pdm-project/pdm

Issue:
Command `pdm py path` printing the path towards a Python
### Feature Description

Some tools can accept as argument the path towards a Python interpreter. For example with Nox, one can write:

```sh
pdm py install 3.12
pdm py list
nox -s build -P ~/.local/share/pdm/python/cpython@3.12.3/bin/python
```


### Problem and Solution

It seems to me it would be useful (and very simple) to be able to print the path towards a Python executable with `pdm py path 3.12`, so that I could write:

```sh
nox -s build -P $(pdm py path 3.12)
```


### Additional Context

_No response_

### Are you willing to contribute to the development of this feature?

- [x] Yes, I am willing to contribute to the development of this feature.

Discussion:
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/pdm-project/pdm/pull/3425?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pdm-project) Report
Attention: Patch coverage is `39.13043%` with `14 lines` in your changes missing coverage. Please review.
> Project coverage is 85.16%. Comparing base [(`d33544c`)](https://app.codecov.io/gh/pdm-project/pdm/commit/d33544cba96c025973f1d3f2637824b8142de399?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pdm-project) to head [(`9cd62d9`)](https://app.codecov.io/gh/pdm-project/pdm/commit/9cd62d92b76e95454ccf47b28e099ae2a9784dfd?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pdm-project).

| [Files with missing lines](https://app.codecov.io/gh/pdm-project/pdm/pull/3425?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pdm-project) | Patch % | Lines |
|---|---|---|
| [src/pdm/cli/commands/python.py](https://app.codecov.io/gh/pdm-project/pdm/pull/3425?src=pr&el=tree&filepath=src%2Fpdm%2Fcli%2Fcommands%2Fpython.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pdm-project#diff-c3JjL3BkbS9jbGkvY29tbWFuZHMvcHl0aG9uLnB5) | 39.13% | [14 Missing :warning: ](https://app.codecov.io/gh/pdm-project/pdm/pull/3425?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pdm-project) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main    #3425      +/-   ##
==========================================
- Coverage   85.49%   85.16%   -0.34%     
==========================================
  Files         112      112              
  Lines       11509    11532      +23     
  Branches     2526     2528       +2     
==========================================
- Hits         9840     9821      -19     
- Misses       1142     1185      +43     
+ Partials      527      526       -1     
```

| [Flag](https://app.codecov.io/gh/pdm-project/pdm/pull/3425/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pdm-project) | Coverage Δ | |
|---|---|---|
| [unittests](https://app.codecov.io/gh/pdm-project/pdm/pull/3425/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pdm-project) | `84.96% <39.13%> (-0.33%)` | :arrow_down: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pdm-project#carryforward-flags-in-the-pull-request-comment) to find out more.

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/pdm-project/pdm/pull/3425?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pdm-project).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pdm-project).

<details><summary>🚀 New features to boost your workflow: </summary>

- ❄ [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
</details>

Patch:
diff --git a/news/3389.feature.md b/news/3389.feature.md
new file mode 100644
index 00000000..77b78f5b
--- /dev/null
+++ b/news/3389.feature.md
@@ -0,0 +1,1 @@
+Add `pdm python find` command to search for a python interpreter.
diff --git a/src/pdm/cli/commands/python.py b/src/pdm/cli/commands/python.py
index 8d13a999..c322e476 100644
--- a/src/pdm/cli/commands/python.py
+++ b/src/pdm/cli/commands/python.py
@@ -1,8 +1,10 @@
 from __future__ import annotations
 
+import os
 import shutil
 import sys
 import tempfile
+from argparse import ArgumentParser
 from pathlib import Path
 from typing import TYPE_CHECKING, cast
 
@@ -33,6 +35,7 @@ class Command(BaseCommand):
         RemoveCommand.register_to(subparsers, name="remove")
         InstallCommand.register_to(subparsers, name="install")
         LinkCommand.register_to(subparsers, name="link")
+        FindCommand.register_to(subparsers, name="find")
 
     @classmethod
     def register_to(cls, subparsers: _SubParsersAction, name: str | None = None, **kwargs: Any) -> None:
@@ -209,3 +212,40 @@ class LinkCommand(BaseCommand):
         link_path.parent.mkdir(parents=True, exist_ok=True)
         link_path.symlink_to(exe_dir)
         project.core.ui.echo(f"[success]Successfully linked {link_name} to {exe_dir}[/]")
+
+
+class FindCommand(BaseCommand):
+    """Search for a Python interpreter"""
+
+    arguments = (verbose_option,)
+
+    def add_arguments(self, parser: ArgumentParser) -> None:
+        parser.add_argument("--managed", action="store_true", help="Only find interpreters managed by PDM")
+        parser.add_argument("request", help="The Python version to find. E.g. 3.12, cpython@3.13")
+
+    def handle(self, project: Project, options: Namespace) -> None:
+        from findpython import Finder
+
+        if options.managed:
+            old_rye_root = os.getenv("RYE_PY_ROOT")
+            os.environ["RYE_PY_ROOT"] = os.path.expanduser(project.config["python.install_root"])
+            try:
+                finder = Finder(resolve_symlinks=True, selected_providers=["rye"])
+            finally:
+                if old_rye_root:  # pragma: no cover
+                    os.environ["RYE_PY_ROOT"] = old_rye_root
+                else:
+                    del os.environ["RYE_PY_ROOT"]
+        else:
+            finder = project._get_python_finder()
+
+        python_version = finder.find(options.request)
+        location = (
+            "managed installations"
+            if options.managed
+            else "virtual environments, managed installations, or search paths"
+        )
+        if python_version is None:
+            project.core.ui.error(f"Python interpreter {options.request!r} not found in {location}")
+            raise SystemExit(1)
+        print(python_version.executable)
diff --git a/src/pdm/cli/completions/pdm.bash b/src/pdm/cli/completions/pdm.bash
index b0441710..4c8b760e 100644
--- a/src/pdm/cli/completions/pdm.bash
+++ b/src/pdm/cli/completions/pdm.bash
@@ -29,7 +29,7 @@ _pdm_a919b69078acdf0a_complete()
         case "$com" in
 
             (add)
-            opts="--config-setting --dev --dry-run --editable --fail-fast --frozen-lockfile --global --group --help --lockfile --no-editable --no-isolation --no-self --no-sync --override --prerelease --project --quiet --save-compatible --save-exact --save-minimum --save-wildcard --skip --stable --unconstrained --update-all --update-eager --update-reuse --update-reuse-installed --venv --verbose"
+            opts="--config-setting --dev --dry-run --editable --fail-fast --frozen-lockfile --global --group --help --lockfile --no-editable --no-isolation --no-self --no-sync --override --prerelease --project --quiet --save-compatible --save-exact --save-minimum --save-safe-compatible --save-wildcard --skip --stable --unconstrained --update-all --update-eager --update-reuse --update-reuse-installed --venv --verbose"
             ;;
 
             (build)
@@ -81,7 +81,7 @@ _pdm_a919b69078acdf0a_complete()
             ;;
 
             (outdated)
-            opts="--global --help --json --project --quiet --verbose"
+            opts="--global --help --include-sub --json --project --quiet --verbose"
             ;;
 
             (plugin)
@@ -121,15 +121,15 @@ _pdm_a919b69078acdf0a_complete()
             ;;
 
             (sync)
-            opts="--clean --config-setting --dev --dry-run --fail-fast --global --group --help --lockfile --no-default --no-editable --no-isolation --no-self --clean-unselected --only-keep --production --project --quiet --reinstall --skip --venv --verbose --without"
+            opts="--clean --clean-unselected --config-setting --dev --dry-run --fail-fast --global --group --help --lockfile --no-default --no-editable --no-isolation --no-self --production --project --quiet --reinstall --skip --venv --verbose --without"
             ;;
 
             (update)
-            opts="--config-setting --dev --fail-fast --frozen-lockfile --global --group --help --lockfile --no-default --no-editable --no-isolation --no-self --no-sync --outdated --override --prerelease --production --project --quiet --save-compatible --save-exact --save-minimum --save-wildcard --skip --stable --top --unconstrained --update-all --update-eager --update-reuse --update-reuse-installed --venv --verbose --without"
+            opts="--config-setting --dev --fail-fast --frozen-lockfile --global --group --help --lockfile --no-default --no-editable --no-isolation --no-self --no-sync --outdated --override --prerelease --production --project --quiet --save-compatible --save-exact --save-minimum --save-safe-compatible --save-wildcard --skip --stable --top --unconstrained --update-all --update-eager --update-reuse --update-reuse-installed --venv --verbose --without"
             ;;
 
             (use)
-            opts="--auto-install-max --auto-install-min --first --global --help --ignore-remembered --project --quiet --skip --venv --verbose"
+            opts="--auto-install-max --auto-install-min --first --global --help --ignore-remembered --no-version-file --project --quiet --skip --venv --verbose"
             ;;
 
             (venv)
diff --git a/src/pdm/cli/completions/pdm.fish b/src/pdm/cli/completions/pdm.fish
index 7097dc25..3037b32e 100644
--- a/src/pdm/cli/completions/pdm.fish
+++ b/src/pdm/cli/completions/pdm.fish
@@ -45,6 +45,7 @@ complete -c pdm -A -n '__fish_seen_subcommand_from add' -l quiet -d 'Suppress ou
 complete -c pdm -A -n '__fish_seen_subcommand_from add' -l save-compatible -d 'Save compatible version specifiers'
 complete -c pdm -A -n '__fish_seen_subcommand_from add' -l save-exact -d 'Save exact version specifiers'
 complete -c pdm -A -n '__fish_seen_subcommand_from add' -l save-minimum -d 'Save minimum version specifiers'
+complete -c pdm -A -n '__fish_seen_subcommand_from add' -l save-safe-compatible -d 'Save safe compatible version specifiers'
 complete -c pdm -A -n '__fish_seen_subcommand_from add' -l save-wildcard -d 'Save wildcard version specifiers'
 complete -c pdm -A -n '__fish_seen_subcommand_from add' -l skip -d 'Skip some tasks and/or hooks by their comma-separated names. Can be supplied multiple times. Use ":all" to skip all hooks. Use ":pre" and ":post" to skip all pre or post hooks.'
 complete -c pdm -A -n '__fish_seen_subcommand_from add' -l stable -d 'Only allow stable versions to be pinned'
@@ -123,7 +124,7 @@ complete -c pdm -A -n '__fish_seen_subcommand_from export' -l editable-self -d '
 complete -c pdm -A -n '__fish_seen_subcommand_from export' -l expandvars -d 'Expand environment variables in requirements'
 complete -c pdm -A -n '__fish_seen_subcommand_from export' -l format -d 'Only requirements.txt is supported for now.'
 complete -c pdm -A -n '__fish_seen_subcommand_from export' -l global -d 'Use the global project, supply the project root with `-p` option'
-complete -c pdm -A -n '__fish_seen_subcommand_from export' -l group -d 'Select group of optional-dependencies separated by comma or dev-dependencies (with `-d`). Can be supplied multiple times, use ":all" to include all groups under the same species.'
+complete -c pdm -A -n '__fish_seen_subcommand_from export' -l group -d 'Select group of optional-dependencies separated by comma or dependency-groups (with `-d`). Can be supplied multiple times, use ":all" to include all groups under the same species.'
 complete -c pdm -A -n '__fish_seen_subcommand_from export' -l help -d 'Show this help message and exit.'
 complete -c pdm -A -n '__fish_seen_subcommand_from export' -l lockfile -d 'Specify another lockfile path. Default: pdm.lock. [env var: PDM_LOCKFILE]'
 complete -c pdm -A -n '__fish_seen_subcommand_from export' -l no-default -d 'Don\'t include dependencies from the default group'
@@ -136,7 +137,7 @@ complete -c pdm -A -n '__fish_seen_subcommand_from export' -l pyproject -d 'Read
 complete -c pdm -A -n '__fish_seen_subcommand_from export' -l quiet -d 'Suppress output'
 complete -c pdm -A -n '__fish_seen_subcommand_from export' -l self -d 'Include the project itself'
 complete -c pdm -A -n '__fish_seen_subcommand_from export' -l verbose -d 'Use `-v` for detailed output and `-vv` for more detailed'
-complete -c pdm -A -n '__fish_seen_subcommand_from export' -l without -d 'Exclude groups of optional-dependencies or dev-dependencies'
+complete -c pdm -A -n '__fish_seen_subcommand_from export' -l without -d 'Exclude groups of optional-dependencies or dependency-groups'
 complete -c pdm -A -n '__fish_seen_subcommand_from export' -l without-hashes -d 'Don\'t include artifact hashes'
 
 # fix
@@ -174,7 +175,12 @@ complete -c pdm -A -n '__fish_seen_subcommand_from info' -l verbose -d 'Use `-v`
 complete -c pdm -A -n '__fish_seen_subcommand_from info' -l where -d 'Show the project root path'
 
 # init
-complete -c pdm -f -n '__fish_pdm_a919b69078acdf0a_complete_no_subcommand' -a init -d 'Initialize a pyproject.toml for PDM'
+complete -c pdm -f -n '__fish_pdm_a919b69078acdf0a_complete_no_subcommand' -a init -d 'Initialize a pyproject.toml for PDM.
+
+    Built-in templates:
+    - default: `pdm init`, A simple template with a basic structure.
+    - minimal: `pdm init minimal`, A minimal template with only `pyproject.toml`.
+    '
 complete -c pdm -A -n '__fish_seen_subcommand_from init' -l backend -d 'Specify the build backend, which implies --dist'
 complete -c pdm -A -n '__fish_seen_subcommand_from init' -l cookiecutter -d 'Use Cookiecutter to generate project [32m[installed][0m'
 complete -c pdm -A -n '__fish_seen_subcommand_from init' -l copier -d 'Use Copier to generate project [32m[installed][0m'
@@ -200,7 +206,7 @@ complete -c pdm -A -n '__fish_seen_subcommand_from install' -l dry-run -d 'Show
 complete -c pdm -A -n '__fish_seen_subcommand_from install' -l fail-fast -d 'Abort on first installation error'
 complete -c pdm -A -n '__fish_seen_subcommand_from install' -l frozen-lockfile -d 'Don\'t try to create or update the lockfile. [env var: PDM_FROZEN_LOCKFILE]'
 complete -c pdm -A -n '__fish_seen_subcommand_from install' -l global -d 'Use the global project, supply the project root with `-p` option'
-complete -c pdm -A -n '__fish_seen_subcommand_from install' -l group -d 'Select group of optional-dependencies separated by comma or dev-dependencies (with `-d`). Can be supplied multiple times, use ":all" to include all groups under the same species.'
+complete -c pdm -A -n '__fish_seen_subcommand_from install' -l group -d 'Select group of optional-dependencies separated by comma or dependency-groups (with `-d`). Can be supplied multiple times, use ":all" to include all groups under the same species.'
 complete -c pdm -A -n '__fish_seen_subcommand_from install' -l help -d 'Show this help message and exit.'
 complete -c pdm -A -n '__fish_seen_subcommand_from install' -l lockfile -d 'Specify another lockfile path. Default: pdm.lock. [env var: PDM_LOCKFILE]'
 complete -c pdm -A -n '__fish_seen_subcommand_from install' -l no-default -d 'Don\'t include dependencies from the default group'
@@ -215,7 +221,7 @@ complete -c pdm -A -n '__fish_seen_subcommand_from install' -l quiet -d 'Suppres
 complete -c pdm -A -n '__fish_seen_subcommand_from install' -l skip -d 'Skip some tasks and/or hooks by their comma-separated names. Can be supplied multiple times. Use ":all" to skip all hooks. Use ":pre" and ":post" to skip all pre or post hooks.'
 complete -c pdm -A -n '__fish_seen_subcommand_from install' -l venv -d 'Run the command in the virtual environment with the given key. [env var: PDM_IN_VENV]'
 complete -c pdm -A -n '__fish_seen_subcommand_from install' -l verbose -d 'Use `-v` for detailed output and `-vv` for more detailed'
-complete -c pdm -A -n '__fish_seen_subcommand_from install' -l without -d 'Exclude groups of optional-dependencies or dev-dependencies'
+complete -c pdm -A -n '__fish_seen_subcommand_from install' -l without -d 'Exclude groups of optional-dependencies or dependency-groups'
 
 # list
 complete -c pdm -f -n '__fish_pdm_a919b69078acdf0a_complete_no_subcommand' -a list -d 'List packages installed in the current working set'
@@ -245,7 +251,7 @@ complete -c pdm -A -n '__fish_seen_subcommand_from lock' -l config-setting -d 'P
 complete -c pdm -A -n '__fish_seen_subcommand_from lock' -l dev -d 'Select dev dependencies'
 complete -c pdm -A -n '__fish_seen_subcommand_from lock' -l exclude-newer -d 'Exclude packages newer than the given UTC date in format `YYYY-MM-DD[THH:MM:SSZ]`'
 complete -c pdm -A -n '__fish_seen_subcommand_from lock' -l global -d 'Use the global project, supply the project root with `-p` option'
-complete -c pdm -A -n '__fish_seen_subcommand_from lock' -l group -d 'Select group of optional-dependencies separated by comma or dev-dependencies (with `-d`). Can be supplied multiple times, use ":all" to include all groups under the same species.'
+complete -c pdm -A -n '__fish_seen_subcommand_from lock' -l group -d 'Select group of optional-dependencies separated by comma or dependency-groups (with `-d`). Can be supplied multiple times, use ":all" to include all groups under the same species.'
 complete -c pdm -A -n '__fish_seen_subcommand_from lock' -l help -d 'Show this help message and exit.'
 complete -c pdm -A -n '__fish_seen_subcommand_from lock' -l implementation -d 'The Python implementation to lock for. E.g. `cpython`, `pypy`, `pyston`'
 complete -c pdm -A -n '__fish_seen_subcommand_from lock' -l lockfile -d 'Specify another lockfile path. Default: pdm.lock. [env var: PDM_LOCKFILE]'
@@ -266,12 +272,13 @@ complete -c pdm -A -n '__fish_seen_subcommand_from lock' -l strategy -d 'Specify
 complete -c pdm -A -n '__fish_seen_subcommand_from lock' -l update-reuse -d 'Reuse pinned versions already present in lock file if possible'
 complete -c pdm -A -n '__fish_seen_subcommand_from lock' -l update-reuse-installed -d 'Reuse installed packages if possible'
 complete -c pdm -A -n '__fish_seen_subcommand_from lock' -l verbose -d 'Use `-v` for detailed output and `-vv` for more detailed'
-complete -c pdm -A -n '__fish_seen_subcommand_from lock' -l without -d 'Exclude groups of optional-dependencies or dev-dependencies'
+complete -c pdm -A -n '__fish_seen_subcommand_from lock' -l without -d 'Exclude groups of optional-dependencies or dependency-groups'
 
 # outdated
 complete -c pdm -f -n '__fish_pdm_a919b69078acdf0a_complete_no_subcommand' -a outdated -d 'Check for outdated packages and list the latest versions on indexes.'
 complete -c pdm -A -n '__fish_seen_subcommand_from outdated' -l global -d 'Use the global project, supply the project root with `-p` option'
 complete -c pdm -A -n '__fish_seen_subcommand_from outdated' -l help -d 'Show this help message and exit.'
+complete -c pdm -A -n '__fish_seen_subcommand_from outdated' -l include-sub -d 'Include sub-dependencies'
 complete -c pdm -A -n '__fish_seen_subcommand_from outdated' -l json -d 'Output in JSON format'
 complete -c pdm -A -n '__fish_seen_subcommand_from outdated' -l project -d 'Specify another path as the project root, which changes the base of pyproject.toml and __pypackages__ [env var: PDM_PROJECT]'
 complete -c pdm -A -n '__fish_seen_subcommand_from outdated' -l quiet -d 'Suppress output'
@@ -310,6 +317,7 @@ complete -c pdm -A -n '__fish_seen_subcommand_from plugin; and __fish_seen_subco
 complete -c pdm -f -n '__fish_seen_subcommand_from plugin; and not __fish_seen_subcommand_from $plugin_subcommands' -a update -d 'Update PDM itself'
 complete -c pdm -A -n '__fish_seen_subcommand_from plugin; and __fish_seen_subcommand_from update' -l head -d 'Update to the latest commit on the main branch'
 complete -c pdm -A -n '__fish_seen_subcommand_from plugin; and __fish_seen_subcommand_from update' -l help -d 'Show this help message and exit.'
+complete -c pdm -A -n '__fish_seen_subcommand_from plugin; and __fish_seen_subcommand_from update' -l no-frozen-deps -d 'Do not install frozen dependency versions'
 complete -c pdm -A -n '__fish_seen_subcommand_from plugin; and __fish_seen_subcommand_from update' -l pip-args -d 'Additional arguments that will be passed to pip install'
 complete -c pdm -A -n '__fish_seen_subcommand_from plugin; and __fish_seen_subcommand_from update' -l pre -d 'Update to the latest prerelease version'
 complete -c pdm -A -n '__fish_seen_subcommand_from plugin; and __fish_seen_subcommand_from update' -l quiet -d 'Suppress output'
@@ -338,7 +346,14 @@ complete -c pdm -A -n '__fish_seen_subcommand_from publish' -l verbose -d 'Use `
 complete -c pdm -f -n '__fish_pdm_a919b69078acdf0a_complete_no_subcommand' -a py -d 'Manage installed Python interpreters'
 complete -c pdm -A -n '__fish_seen_subcommand_from py' -l help -d 'Show this help message and exit.'
 # py subcommands
-set -l py_subcommands install list remove
+set -l py_subcommands find install link list remove
+# py find
+complete -c pdm -f -n '__fish_seen_subcommand_from py; and not __fish_seen_subcommand_from $py_subcommands' -a find -d 'Search for a Python interpreter'
+complete -c pdm -A -n '__fish_seen_subcommand_from py; and __fish_seen_subcommand_from find' -l help -d 'Show this help message and exit.'
+complete -c pdm -A -n '__fish_seen_subcommand_from py; and __fish_seen_subcommand_from find' -l managed -d 'Only find interpreters managed by PDM'
+complete -c pdm -A -n '__fish_seen_subcommand_from py; and __fish_seen_subcommand_from find' -l quiet -d 'Suppress output'
+complete -c pdm -A -n '__fish_seen_subcommand_from py; and __fish_seen_subcommand_from find' -l verbose -d 'Use `-v` for detailed output and `-vv` for more detailed'
+
 # py install
 complete -c pdm -f -n '__fish_seen_subcommand_from py; and not __fish_seen_subcommand_from $py_subcommands' -a install -d 'Install a Python interpreter with PDM'
 complete -c pdm -A -n '__fish_seen_subcommand_from py; and __fish_seen_subcommand_from install' -l help -d 'Show this help message and exit.'
@@ -347,6 +362,13 @@ complete -c pdm -A -n '__fish_seen_subcommand_from py; and __fish_seen_subcomman
 complete -c pdm -A -n '__fish_seen_subcommand_from py; and __fish_seen_subcommand_from install' -l quiet -d 'Suppress output'
 complete -c pdm -A -n '__fish_seen_subcommand_from py; and __fish_seen_subcommand_from install' -l verbose -d 'Use `-v` for detailed output and `-vv` for more detailed'
 
+# py link
+complete -c pdm -f -n '__fish_seen_subcommand_from py; and not __fish_seen_subcommand_from $py_subcommands' -a link -d 'Link an external Python interpreter to PDM'
+complete -c pdm -A -n '__fish_seen_subcommand_from py; and __fish_seen_subcommand_from link' -l help -d 'Show this help message and exit.'
+complete -c pdm -A -n '__fish_seen_subcommand_from py; and __fish_seen_subcommand_from link' -l name -d 'The name of the link'
+complete -c pdm -A -n '__fish_seen_subcommand_from py; and __fish_seen_subcommand_from link' -l quiet -d 'Suppress output'
+complete -c pdm -A -n '__fish_seen_subcommand_from py; and __fish_seen_subcommand_from link' -l verbose -d 'Use `-v` for detailed output and `-vv` for more detailed'
+
 # py list
 complete -c pdm -f -n '__fish_seen_subcommand_from py; and not __fish_seen_subcommand_from $py_subcommands' -a list -d 'List all Python interpreters installed with PDM'
 complete -c pdm -A -n '__fish_seen_subcommand_from py; and __fish_seen_subcommand_from list' -l help -d 'Show this help message and exit.'
@@ -363,7 +385,14 @@ complete -c pdm -A -n '__fish_seen_subcommand_from py; and __fish_seen_subcomman
 complete -c pdm -f -n '__fish_pdm_a919b69078acdf0a_complete_no_subcommand' -a python -d 'Manage installed Python interpreters'
 complete -c pdm -A -n '__fish_seen_subcommand_from python' -l help -d 'Show this help message and exit.'
 # python subcommands
-set -l python_subcommands install list remove
+set -l python_subcommands find install link list remove
+# python find
+complete -c pdm -f -n '__fish_seen_subcommand_from python; and not __fish_seen_subcommand_from $python_subcommands' -a find -d 'Search for a Python interpreter'
+complete -c pdm -A -n '__fish_seen_subcommand_from python; and __fish_seen_subcommand_from find' -l help -d 'Show this help message and exit.'
+complete -c pdm -A -n '__fish_seen_subcommand_from python; and __fish_seen_subcommand_from find' -l managed -d 'Only find interpreters managed by PDM'
+complete -c pdm -A -n '__fish_seen_subcommand_from python; and __fish_seen_subcommand_from find' -l quiet -d 'Suppress output'
+complete -c pdm -A -n '__fish_seen_subcommand_from python; and __fish_seen_subcommand_from find' -l verbose -d 'Use `-v` for detailed output and `-vv` for more detailed'
+
 # python install
 complete -c pdm -f -n '__fish_seen_subcommand_from python; and not __fish_seen_subcommand_from $python_subcommands' -a install -d 'Install a Python interpreter with PDM'
 complete -c pdm -A -n '__fish_seen_subcommand_from python; and __fish_seen_subcommand_from install' -l help -d 'Show this help message and exit.'
@@ -372,6 +401,13 @@ complete -c pdm -A -n '__fish_seen_subcommand_from python; and __fish_seen_subco
 complete -c pdm -A -n '__fish_seen_subcommand_from python; and __fish_seen_subcommand_from install' -l quiet -d 'Suppress output'
 complete -c pdm -A -n '__fish_seen_subcommand_from python; and __fish_seen_subcommand_from install' -l verbose -d 'Use `-v` for detailed output and `-vv` for more detailed'
 
+# python link
+complete -c pdm -f -n '__fish_seen_subcommand_from python; and not __fish_seen_subcommand_from $python_subcommands' -a link -d 'Link an external Python interpreter to PDM'
+complete -c pdm -A -n '__fish_seen_subcommand_from python; and __fish_seen_subcommand_from link' -l help -d 'Show this help message and exit.'
+complete -c pdm -A -n '__fish_seen_subcommand_from python; and __fish_seen_subcommand_from link' -l name -d 'The name of the link'
+complete -c pdm -A -n '__fish_seen_subcommand_from python; and __fish_seen_subcommand_from link' -l quiet -d 'Suppress output'
+complete -c pdm -A -n '__fish_seen_subcommand_from python; and __fish_seen_subcommand_from link' -l verbose -d 'Use `-v` for detailed output and `-vv` for more detailed'
+
 # python list
 complete -c pdm -f -n '__fish_seen_subcommand_from python; and not __fish_seen_subcommand_from $python_subcommands' -a list -d 'List all Python interpreters installed with PDM'
 complete -c pdm -A -n '__fish_seen_subcommand_from python; and __fish_seen_subcommand_from list' -l help -d 'Show this help message and exit.'
@@ -459,6 +495,7 @@ complete -c pdm -A -n '__fish_seen_subcommand_from self; and __fish_seen_subcomm
 complete -c pdm -f -n '__fish_seen_subcommand_from self; and not __fish_seen_subcommand_from $self_subcommands' -a update -d 'Update PDM itself'
 complete -c pdm -A -n '__fish_seen_subcommand_from self; and __fish_seen_subcommand_from update' -l head -d 'Update to the latest commit on the main branch'
 complete -c pdm -A -n '__fish_seen_subcommand_from self; and __fish_seen_subcommand_from update' -l help -d 'Show this help message and exit.'
+complete -c pdm -A -n '__fish_seen_subcommand_from self; and __fish_seen_subcommand_from update' -l no-frozen-deps -d 'Do not install frozen dependency versions'
 complete -c pdm -A -n '__fish_seen_subcommand_from self; and __fish_seen_subcommand_from update' -l pip-args -d 'Additional arguments that will be passed to pip install'
 complete -c pdm -A -n '__fish_seen_subcommand_from self; and __fish_seen_subcommand_from update' -l pre -d 'Update to the latest prerelease version'
 complete -c pdm -A -n '__fish_seen_subcommand_from self; and __fish_seen_subcommand_from update' -l quiet -d 'Suppress output'
@@ -482,20 +519,19 @@ complete -c pdm -A -n '__fish_seen_subcommand_from show' -l version -d 'Show ver
 # sync
 complete -c pdm -f -n '__fish_pdm_a919b69078acdf0a_complete_no_subcommand' -a sync -d 'Synchronize the current working set with lock file'
 complete -c pdm -A -n '__fish_seen_subcommand_from sync' -l clean -d 'Clean packages not in the lockfile'
+complete -c pdm -A -n '__fish_seen_subcommand_from sync' -l clean-unselected -d 'Only keep the selected packages'
 complete -c pdm -A -n '__fish_seen_subcommand_from sync' -l config-setting -d 'Pass options to the builder. options with a value must be specified after "=": `--config-setting=key(=value)` or `-Ckey(=value)`'
 complete -c pdm -A -n '__fish_seen_subcommand_from sync' -l dev -d 'Select dev dependencies'
 complete -c pdm -A -n '__fish_seen_subcommand_from sync' -l dry-run -d 'Show the difference only and don\'t perform any action'
 complete -c pdm -A -n '__fish_seen_subcommand_from sync' -l fail-fast -d 'Abort on first installation error'
 complete -c pdm -A -n '__fish_seen_subcommand_from sync' -l global -d 'Use the global project, supply the project root with `-p` option'
-complete -c pdm -A -n '__fish_seen_subcommand_from sync' -l group -d 'Select group of optional-dependencies separated by comma or dev-dependencies (with `-d`). Can be supplied multiple times, use ":all" to include all groups under the same species.'
+complete -c pdm -A -n '__fish_seen_subcommand_from sync' -l group -d 'Select group of optional-dependencies separated by comma or dependency-groups (with `-d`). Can be supplied multiple times, use ":all" to include all groups under the same species.'
 complete -c pdm -A -n '__fish_seen_subcommand_from sync' -l help -d 'Show this help message and exit.'
 complete -c pdm -A -n '__fish_seen_subcommand_from sync' -l lockfile -d 'Specify another lockfile path. Default: pdm.lock. [env var: PDM_LOCKFILE]'
 complete -c pdm -A -n '__fish_seen_subcommand_from sync' -l no-default -d 'Don\'t include dependencies from the default group'
 complete -c pdm -A -n '__fish_seen_subcommand_from sync' -l no-editable -d 'Install non-editable versions for all packages. [env var: PDM_NO_EDITABLE]'
 complete -c pdm -A -n '__fish_seen_subcommand_from sync' -l no-isolation -d 'Disable isolation when building a source distribution that follows PEP 517, as in: build dependencies specified by PEP 518 must be already installed if this option is used.'
 complete -c pdm -A -n '__fish_seen_subcommand_from sync' -l no-self -d 'Don\'t install the project itself. [env var: PDM_NO_SELF]'
-complete -c pdm -A -n '__fish_seen_subcommand_from sync' -l only-keep -d 'Remove all but the selected packages'
-complete -c pdm -A -n '__fish_seen_subcommand_from sync' -l clean-unselected -d 'Remove all but the selected packages'
 complete -c pdm -A -n '__fish_seen_subcommand_from sync' -l production -d 'Unselect dev dependencies'
 complete -c pdm -A -n '__fish_seen_subcommand_from sync' -l project -d 'Specify another path as the project root, which changes the base of pyproject.toml and __pypackages__ [env var: PDM_PROJECT]'
 complete -c pdm -A -n '__fish_seen_subcommand_from sync' -l quiet -d 'Suppress output'
@@ -503,7 +539,7 @@ complete -c pdm -A -n '__fish_seen_subcommand_from sync' -l reinstall -d 'Force
 complete -c pdm -A -n '__fish_seen_subcommand_from sync' -l skip -d 'Skip some tasks and/or hooks by their comma-separated names. Can be supplied multiple times. Use ":all" to skip all hooks. Use ":pre" and ":post" to skip all pre or post hooks.'
 complete -c pdm -A -n '__fish_seen_subcommand_from sync' -l venv -d 'Run the command in the virtual environment with the given key. [env var: PDM_IN_VENV]'
 complete -c pdm -A -n '__fish_seen_subcommand_from sync' -l verbose -d 'Use `-v` for detailed output and `-vv` for more detailed'
-complete -c pdm -A -n '__fish_seen_subcommand_from sync' -l without -d 'Exclude groups of optional-dependencies or dev-dependencies'
+complete -c pdm -A -n '__fish_seen_subcommand_from sync' -l without -d 'Exclude groups of optional-dependencies or dependency-groups'
 
 # update
 complete -c pdm -f -n '__fish_pdm_a919b69078acdf0a_complete_no_subcommand' -a update -d 'Update package(s) in pyproject.toml'
@@ -512,7 +548,7 @@ complete -c pdm -A -n '__fish_seen_subcommand_from update' -l dev -d 'Select dev
 complete -c pdm -A -n '__fish_seen_subcommand_from update' -l fail-fast -d 'Abort on first installation error'
 complete -c pdm -A -n '__fish_seen_subcommand_from update' -l frozen-lockfile -d 'Don\'t try to create or update the lockfile. [env var: PDM_FROZEN_LOCKFILE]'
 complete -c pdm -A -n '__fish_seen_subcommand_from update' -l global -d 'Use the global project, supply the project root with `-p` option'
-complete -c pdm -A -n '__fish_seen_subcommand_from update' -l group -d 'Select group of optional-dependencies separated by comma or dev-dependencies (with `-d`). Can be supplied multiple times, use ":all" to include all groups under the same species.'
+complete -c pdm -A -n '__fish_seen_subcommand_from update' -l group -d 'Select group of optional-dependencies separated by comma or dependency-groups (with `-d`). Can be supplied multiple times, use ":all" to include all groups under the same species.'
 complete -c pdm -A -n '__fish_seen_subcommand_from update' -l help -d 'Show this help message and exit.'
 complete -c pdm -A -n '__fish_seen_subcommand_from update' -l lockfile -d 'Specify another lockfile path. Default: pdm.lock. [env var: PDM_LOCKFILE]'
 complete -c pdm -A -n '__fish_seen_subcommand_from update' -l no-default -d 'Don\'t include dependencies from the default group'
@@ -529,6 +565,7 @@ complete -c pdm -A -n '__fish_seen_subcommand_from update' -l quiet -d 'Suppress
 complete -c pdm -A -n '__fish_seen_subcommand_from update' -l save-compatible -d 'Save compatible version specifiers'
 complete -c pdm -A -n '__fish_seen_subcommand_from update' -l save-exact -d 'Save exact version specifiers'
 complete -c pdm -A -n '__fish_seen_subcommand_from update' -l save-minimum -d 'Save minimum version specifiers'
+complete -c pdm -A -n '__fish_seen_subcommand_from update' -l save-safe-compatible -d 'Save safe compatible version specifiers'
 complete -c pdm -A -n '__fish_seen_subcommand_from update' -l save-wildcard -d 'Save wildcard version specifiers'
 complete -c pdm -A -n '__fish_seen_subcommand_from update' -l skip -d 'Skip some tasks and/or hooks by their comma-separated names. Can be supplied multiple times. Use ":all" to skip all hooks. Use ":pre" and ":post" to skip all pre or post hooks.'
 complete -c pdm -A -n '__fish_seen_subcommand_from update' -l stable -d 'Only allow stable versions to be pinned'
@@ -540,7 +577,7 @@ complete -c pdm -A -n '__fish_seen_subcommand_from update' -l update-reuse -d 'R
 complete -c pdm -A -n '__fish_seen_subcommand_from update' -l update-reuse-installed -d 'Reuse installed packages if possible'
 complete -c pdm -A -n '__fish_seen_subcommand_from update' -l venv -d 'Run the command in the virtual environment with the given key. [env var: PDM_IN_VENV]'
 complete -c pdm -A -n '__fish_seen_subcommand_from update' -l verbose -d 'Use `-v` for detailed output and `-vv` for more detailed'
-complete -c pdm -A -n '__fish_seen_subcommand_from update' -l without -d 'Exclude groups of optional-dependencies or dev-dependencies'
+complete -c pdm -A -n '__fish_seen_subcommand_from update' -l without -d 'Exclude groups of optional-dependencies or dependency-groups'
 
 # use
 complete -c pdm -f -n '__fish_pdm_a919b69078acdf0a_complete_no_subcommand' -a use -d 'Use the given python version or path as base interpreter. If not found, PDM will try to install one.'
@@ -550,6 +587,7 @@ complete -c pdm -A -n '__fish_seen_subcommand_from use' -l first -d 'Select the
 complete -c pdm -A -n '__fish_seen_subcommand_from use' -l global -d 'Use the global project, supply the project root with `-p` option'
 complete -c pdm -A -n '__fish_seen_subcommand_from use' -l help -d 'Show this help message and exit.'
 complete -c pdm -A -n '__fish_seen_subcommand_from use' -l ignore-remembered -d 'Ignore the remembered selection'
+complete -c pdm -A -n '__fish_seen_subcommand_from use' -l no-version-file -d 'Do not write .python-version file'
 complete -c pdm -A -n '__fish_seen_subcommand_from use' -l project -d 'Specify another path as the project root, which changes the base of pyproject.toml and __pypackages__ [env var: PDM_PROJECT]'
 complete -c pdm -A -n '__fish_seen_subcommand_from use' -l quiet -d 'Suppress output'
 complete -c pdm -A -n '__fish_seen_subcommand_from use' -l skip -d 'Skip some tasks and/or hooks by their comma-separated names. Can be supplied multiple times. Use ":all" to skip all hooks. Use ":pre" and ":post" to skip all pre or post hooks.'
diff --git a/src/pdm/cli/completions/pdm.ps1 b/src/pdm/cli/completions/pdm.ps1
index fcd80580..b992edf1 100644
--- a/src/pdm/cli/completions/pdm.ps1
+++ b/src/pdm/cli/completions/pdm.ps1
@@ -213,7 +213,7 @@ function TabExpansion($line, $lastWord) {
                 $completer.AddOpts(@(
                         [Option]::new((
                             "-d", "--dev", "--save-compatible", "--save-wildcard", "--dry-run", "--save-exact", "--override",
-                            "--save-minimum", "--update-eager", "--update-reuse", "--update-all", "-g", "--global",
+                            "--save-minimum", "--save-safe-compatible", "--update-eager", "--update-reuse", "--update-all", "-g", "--global",
                             "--no-sync", "--no-editable", "--no-self", "-u", "--unconstrained", "--no-isolation", "-C", "--config-setting", "--stable",
                             "--pre", "--prerelease", "-L", "--lockfile", "--fail-fast", "-x", "--frozen-lockfile", "--update-reuse-installed"
                         )),
@@ -407,6 +407,11 @@ function TabExpansion($line, $lastWord) {
                         $command = $subCommand
                         break
                     }
+                    "find" {
+                        $completer.AddOpts(([Option]::new(("--managed"))))
+                        $command = $subCommand
+                        break
+                    }
                     Default {
                         break
                     }
@@ -470,7 +475,7 @@ function TabExpansion($line, $lastWord) {
                             "--save-minimum", "--update-eager", "--update-reuse", "--update-all", "-g", "--global", "--dry-run",
                             "--outdated", "--top", "-u", "--unconstrained", "--no-editable", "--no-self", "--no-isolation",
                             "--no-sync", "--pre", "--prerelease", "-L", "--lockfile", "--fail-fast", "-x", "--frozen-lockfile",
-                            "-C", "--config-setting", "--update-reuse-installed", "--stable", "--override"
+                            "-C", "--config-setting", "--update-reuse-installed", "--stable", "--override", "--save-safe-compatible"
                         )),
                         $sectionOption,
                         $skipOption,
@@ -483,7 +488,7 @@ function TabExpansion($line, $lastWord) {
             "use" {
                 $completer.AddOpts(
                     @(
-                        [Option]::new(@("--global", "-g", "-f", "--first", "-i", "--ignore-remembered", "--skip", "--auto-install-min", "--auto-install-max")),
+                        [Option]::new(@("--global", "-g", "-f", "--first", "-i", "--ignore-remembered", "--skip", "--auto-install-min", "--auto-install-max", "--no-version-file")),
                         $venvOption,
                         $projectOption
                     ))
diff --git a/src/pdm/cli/completions/pdm.zsh b/src/pdm/cli/completions/pdm.zsh
index b1c7fbdf..c638ef6f 100644
--- a/src/pdm/cli/completions/pdm.zsh
+++ b/src/pdm/cli/completions/pdm.zsh
@@ -73,6 +73,7 @@ _pdm() {
         '--save-compatible[Save compatible version specifiers]'
         '--save-wildcard[Save wildcard version specifiers]'
         '--save-exact[Save exact version specifiers]'
+        '--save-safe-compatible[Save safe compatible version specifiers]'
         '--save-minimum[Save minimum version specifiers]'
         '--update-reuse[Reuse pinned versions already present in lock file if possible]'
         '--update-reuse-installed[Reuse installed packages if possible]'
@@ -344,6 +345,7 @@ _pdm() {
             "remove:Remove a Python interpreter installed with PDM"
             "list:List all Python interpreters installed with PDM"
             "install:Install a Python interpreter with PDM"
+            "find:Search for a Python interpreter"
           )
           _describe -t command 'pdm python actions' actions && ret=0
           ;;
@@ -361,6 +363,12 @@ _pdm() {
                 ':python:_files'
               )
               ;;
+            find)
+              arguments+=(
+                '--managed[Only find interpreters managed by PDM]'
+                ':request:'
+              )
+              ;;
             *)
               ;;
           esac
@@ -474,6 +482,7 @@ _pdm() {
         '--save-wildcard[Save wildcard version specifiers]'
         '--save-exact[Save exact version specifiers]'
         '--save-minimum[Save minimum version specifiers]'
+        '--save-safe-compatible[Save safe compatible version specifiers]'
         '--update-reuse[Reuse pinned versions already present in lock file if possible]'
         '--update-eager[Try to update the packages and their dependencies recursively]'
         '--update-all[Update all dependencies and sub-dependencies]'
@@ -506,6 +515,7 @@ _pdm() {
         '--auto-install-max[If `python` argument not given, auto install maximum best match - otherwise has no effect]'
         {-i,--ignore-remembered}'[Ignore the remembered selection]'
         '--venv[Use the interpreter in the virtual environment with the given name]:venv:'
+        '--no-version-file[Do not write the version file]'
         '*:python:_files'
       )
       ;;


Test patch:
diff --git a/tests/cli/test_python.py b/tests/cli/test_python.py
index 06a52044..47f1c915 100644
--- a/tests/cli/test_python.py
+++ b/tests/cli/test_python.py
@@ -186,3 +186,15 @@ def test_link_python_invalid_interpreter(project, pdm):
     result = pdm(["python", "link", sys.executable, "--name", "foo"], obj=project)
     assert result.exit_code != 0
     assert "Link foo already exists" in result.stderr
+
+
+def test_find_python(project, pdm, mock_install):
+    pdm(["py", "install", "3.10.8"], obj=project, strict=True)
+    result = pdm(["py", "find", "3.10.8"], obj=project, strict=True)
+    assert "3.10.8" in result.stdout
+
+    result = pdm(["py", "find", "3.10.8", "--managed"], obj=project, strict=True)
+    assert "3.10.8" in result.stdout
+
+    result = pdm(["py", "find", "3.10.9"], obj=project)
+    assert result.exit_code != 0