Skip to content

Some issues with doctests in the uv_keyring crate #18916

@musicinmybrain

Description

@musicinmybrain

Summary

For reasons that aren’t clear to me, I’ve started encountering failures from the uv_keyring crate’s doctests in Fedora’s uv package since 0.11.3. It doesn’t seem like the issues are new; it seems like what’s new is that uv-keyring’s doctests are actually compiled and executed. I cannot easily say why that is, but I can reproduce the issue in a git checkout:

$ git checkout 0.11.3
$ cargo test -p uv-keyring
[…]
running 4 tests
test crates/uv-keyring/src/lib.rs - readme (line 422) ... FAILED
test crates/uv-keyring/src/mock.rs - mock (line 25) ... FAILED
test crates/uv-keyring/src/mock.rs - mock (line 11) ... FAILED
test crates/uv-keyring/src/lib.rs - (line 115) ... ok

failures:

---- crates/uv-keyring/src/lib.rs - readme (line 422) stdout ----
error[E0432]: unresolved import `keyring`
   --> crates/uv-keyring/src/lib.rs:423:5
    |
423 | use keyring::{Entry, Result};
    |     ^^^^^^^ use of unresolved module or unlinked crate `keyring`
    |
    = help: if you wanted to use a crate named `keyring`, use `cargo add keyring` to add it to your `Cargo.toml`

error[E0728]: `await` is only allowed inside `async` functions and blocks
   --> crates/uv-keyring/src/lib.rs:427:45
    |
425 | fn main() -> Result<()> {
    | ----------------------- this is not `async`
426 |     let entry = Entry::new("my-service", "my-name")?;
427 |     entry.set_password("topS3cr3tP4$$w0rd").await?;
    |                                             ^^^^^ only allowed inside `async` functions and blocks

error[E0728]: `await` is only allowed inside `async` functions and blocks
   --> crates/uv-keyring/src/lib.rs:428:41
    |
425 | fn main() -> Result<()> {
    | ----------------------- this is not `async`
...
428 |     let password = entry.get_password().await?;
    |                                         ^^^^^ only allowed inside `async` functions and blocks

error[E0728]: `await` is only allowed inside `async` functions and blocks
   --> crates/uv-keyring/src/lib.rs:430:31
    |
425 | fn main() -> Result<()> {
    | ----------------------- this is not `async`
...
430 |     entry.delete_credential().await?;
    |                               ^^^^^ only allowed inside `async` functions and blocks

error: aborting due to 4 previous errors

Some errors have detailed explanations: E0432, E0728.
For more information about an error, try `rustc --explain E0432`.
Couldn't compile the test.
---- crates/uv-keyring/src/mock.rs - mock (line 25) stdout ----
error[E0433]: failed to resolve: use of unresolved module or unlinked crate `keyring`
  --> crates/uv-keyring/src/mock.rs:26:5
   |
26 | use keyring::{Entry, Error, mock, mock::MockCredential};
   |     ^^^^^^^ use of unresolved module or unlinked crate `keyring`
   |
   = help: if you wanted to use a crate named `keyring`, use `cargo add keyring` to add it to your `Cargo.toml`

error[E0432]: unresolved import `keyring`
  --> crates/uv-keyring/src/mock.rs:26:5
   |
26 | use keyring::{Entry, Error, mock, mock::MockCredential};
   |     ^^^^^^^ use of unresolved module or unlinked crate `keyring`
   |
   = help: if you wanted to use a crate named `keyring`, use `cargo add keyring` to add it to your `Cargo.toml`

error[E0433]: failed to resolve: use of unresolved module or unlinked crate `keyring`
  --> crates/uv-keyring/src/mock.rs:27:1
   |
27 | keyring::set_default_credential_builder(mock::default_credential_builder());
   | ^^^^^^^ use of unresolved module or unlinked crate `keyring`
   |
   = help: if you wanted to use a crate named `keyring`, use `cargo add keyring` to add it to your `Cargo.toml`

error: aborting due to 3 previous errors

Some errors have detailed explanations: E0432, E0433.
For more information about an error, try `rustc --explain E0432`.
Couldn't compile the test.
---- crates/uv-keyring/src/mock.rs - mock (line 11) stdout ----
error[E0433]: failed to resolve: use of unresolved module or unlinked crate `keyring`
  --> crates/uv-keyring/src/mock.rs:12:41
   |
12 | keyring::set_default_credential_builder(keyring::mock::default_credential_builder());
   |                                         ^^^^^^^ use of unresolved module or unlinked crate `keyring`
   |
   = help: if you wanted to use a crate named `keyring`, use `cargo add keyring` to add it to your `Cargo.toml`
help: consider importing this module
   |
11 + use uv_keyring::mock;
   |
help: if you import `mock`, refer to it directly
   |
12 - keyring::set_default_credential_builder(keyring::mock::default_credential_builder());
12 + keyring::set_default_credential_builder(mock::default_credential_builder());
   |

error[E0433]: failed to resolve: use of unresolved module or unlinked crate `keyring`
  --> crates/uv-keyring/src/mock.rs:12:1
   |
12 | keyring::set_default_credential_builder(keyring::mock::default_credential_builder());
   | ^^^^^^^ use of unresolved module or unlinked crate `keyring`
   |
   = help: if you wanted to use a crate named `keyring`, use `cargo add keyring` to add it to your `Cargo.toml`

error: aborting due to 2 previous errors

For more information about this error, try `rustc --explain E0433`.
Couldn't compile the test.

failures:
    crates/uv-keyring/src/lib.rs - readme (line 422)
    crates/uv-keyring/src/mock.rs - mock (line 11)
    crates/uv-keyring/src/mock.rs - mock (line 25)

test result: FAILED. 1 passed; 3 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.79s

all doctests ran in 0.84s; merged doctests compilation took 0.05s
error: doctest failed, to rerun pass `-p uv-keyring --doc`

The first obvious issue here is that the uv_keyring crate still has several references to keyring::, from which it was forked. Since there is no dependency on https://crates.io/crates/keyring, we shouldn’t be surprised that these don’t compile. Working locally, I tried fixing these as follows:

From d366081e192967f0071d48695f6e379c4d00ca29 Mon Sep 17 00:00:00 2001
From: "Benjamin A. Beasley" <code@musicinmybrain.net>
Date: Tue, 7 Apr 2026 12:09:51 +0100
Subject: [PATCH 1/2] =?UTF-8?q?Change=20remaining=20uses=20of=20=E2=80=9Ck?=
 =?UTF-8?q?eyring=E2=80=9D=20in=20uv-keyring=E2=80=99s=20doctests?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 crates/uv-keyring/README.md   | 4 ++--
 crates/uv-keyring/src/mock.rs | 6 +++---
 2 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/crates/uv-keyring/README.md b/crates/uv-keyring/README.md
index 613bff88a..f55de0536 100644
--- a/crates/uv-keyring/README.md
+++ b/crates/uv-keyring/README.md
@@ -19,7 +19,7 @@ platform's persistent credential store.) The password or secret can then be read
 can then be removed using the `delete_credential` method.

 `‌``rust
-use keyring::{Entry, Result};
+use uv_keyring::{Entry, Result};

 fn main() -> Result<()> {
     let entry = Entry::new("my-service", "my-name")?;
@@ -33,7 +33,7 @@ fn main() -> Result<()> {

 ## Errors

-Creating and operating on entries can yield a `keyring::Error` which provides both a
+Creating and operating on entries can yield a `uv_keyring::Error` which provides both a
 platform-independent code that classifies the error and, where relevant, underlying platform errors
 or more information about what went wrong.

diff --git a/crates/uv-keyring/src/mock.rs b/crates/uv-keyring/src/mock.rs
index 891cb0291..0fffba903 100644
--- a/crates/uv-keyring/src/mock.rs
+++ b/crates/uv-keyring/src/mock.rs
@@ -10,7 +10,7 @@ in this store have no attributes at all.
 To use this credential store instead of the default, make this call during
 application startup _before_ creating any entries:
 `‌``rust
-keyring::set_default_credential_builder(keyring::mock::default_credential_builder());
+uv_keyring::set_default_credential_builder(uv_keyring::mock::default_credential_builder());
 `‌``

 You can then create entries as you usually do, and call their usual methods
@@ -24,8 +24,8 @@ with the appropriate error.  The next entry method called on the credential
 will fail with the error you set.  The error will then be cleared, so the next
 call on the mock will operate as usual.  Here's a complete example:
 `‌``rust
-# use keyring::{Entry, Error, mock, mock::MockCredential};
-# keyring::set_default_credential_builder(mock::default_credential_builder());
+# use uv_keyring::{Entry, Error, mock, mock::MockCredential};
+# uv_keyring::set_default_credential_builder(mock::default_credential_builder());
 let entry = Entry::new("service", "user").unwrap();
 let mock: &MockCredential = entry.get_credential().downcast_ref().unwrap();
 mock.set_error(Error::Invalid("mock error".to_string(), "takes precedence".to_string()));
-- 
2.53.0

(The above diff has some triple backticks within the diff, which I broke up with zero width non-joiner characters so I could embed it cleanly in Markdown. Therefore, it won’t apply when copy-pasted into a file unless the triple backticks are restored.)

Fixing these occurrences of keyring:: got me a little further:

test crates/uv-keyring/src/lib.rs - readme (line 422) ... FAILED
test crates/uv-keyring/src/mock.rs - mock (line 25) ... FAILED
test crates/uv-keyring/src/mock.rs - mock (line 11) ... ok
test crates/uv-keyring/src/lib.rs - (line 115) ... ok

failures:

---- crates/uv-keyring/src/lib.rs - readme (line 422) stdout ----
error[E0728]: `await` is only allowed inside `async` functions and blocks
   --> crates/uv-keyring/src/lib.rs:428:45
    |
426 | fn main() -> Result<()> {
    | ----------------------- this is not `async`
427 |     let entry = Entry::new("my-service", "my-name")?;
428 |     entry.set_password("topS3cr3tP4$$w0rd").await?;
    |                                             ^^^^^ only allowed inside `async` functions and blocks

error[E0728]: `await` is only allowed inside `async` functions and blocks
   --> crates/uv-keyring/src/lib.rs:429:41
    |
426 | fn main() -> Result<()> {
    | ----------------------- this is not `async`
...
429 |     let password = entry.get_password().await?;
    |                                         ^^^^^ only allowed inside `async` functions and blocks

error[E0728]: `await` is only allowed inside `async` functions and blocks
   --> crates/uv-keyring/src/lib.rs:431:31
    |
426 | fn main() -> Result<()> {
    | ----------------------- this is not `async`
...
431 |     entry.delete_credential().await?;
    |                               ^^^^^ only allowed inside `async` functions and blocks

error: aborting due to 3 previous errors

For more information about this error, try `rustc --explain E0728`.
Couldn't compile the test.
---- crates/uv-keyring/src/mock.rs - mock (line 25) stdout ----
error[E0599]: no method named `expect_err` found for opaque type `impl Future<Output = Result<(), uv_keyring::Error>>` in the current scope
  --> crates/uv-keyring/src/mock.rs:32:28
   |
32 | entry.set_password("test").expect_err("error will override");
   |                            ^^^^^^^^^^
   |
help: consider `await`ing on the `Future` and calling the method on its `Output`
   |
32 | entry.set_password("test").await.expect_err("error will override");
   |                            ++++++
help: there is a method `inspect_err` with a similar name
   |
32 - entry.set_password("test").expect_err("error will override");
32 + entry.set_password("test").inspect_err("error will override");
   |

error[E0599]: no method named `expect` found for opaque type `impl Future<Output = Result<(), uv_keyring::Error>>` in the current scope
  --> crates/uv-keyring/src/mock.rs:33:28
   |
33 | entry.set_password("test").expect("error has been cleared");
   |                            ^^^^^^ method not found in `impl Future<Output = Result<(), uv_keyring::Error>>`
   |
help: consider `await`ing on the `Future` and calling the method on its `Output`
   |
33 | entry.set_password("test").await.expect("error has been cleared");
   |                            ++++++

error: aborting due to 2 previous errors

For more information about this error, try `rustc --explain E0599`.
Couldn't compile the test.

failures:
    crates/uv-keyring/src/lib.rs - readme (line 422)
    crates/uv-keyring/src/mock.rs - mock (line 25)

test result: FAILED. 2 passed; 2 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.85s

all doctests ran in 0.92s; merged doctests compilation took 0.06s
error: doctest failed, to rerun pass `-p uv-keyring --doc`

Looking at that example, this seems reasonable enough: compared to the original version in keyring, .await? calls were added, but no async context, so this isn’t really a correct example.

use keyring::{Entry, Result};
fn main() -> Result<()> {
let entry = Entry::new("my-service", "my-name")?;
entry.set_password("topS3cr3tP4$$w0rd").await?;
let password = entry.get_password().await?;
println!("My password is '{}'", password);
entry.delete_credential().await?;
Ok(())
}

I tried working around this in the simplest possible way:

From b44e5f9a2e0a2cb0b557fc8dbbaff3ab5b83562c Mon Sep 17 00:00:00 2001
From: "Benjamin A. Beasley" <code@musicinmybrain.net>
Date: Tue, 7 Apr 2026 13:58:14 +0100
Subject: [PATCH 2/2] Fix missing async context in uv-keyring README example

---
 crates/uv-keyring/Cargo.toml |  1 +
 crates/uv-keyring/README.md  | 10 ++++++----
 2 files changed, 7 insertions(+), 4 deletions(-)

diff --git a/crates/uv-keyring/Cargo.toml b/crates/uv-keyring/Cargo.toml
index a1339c5c1..18f9077f4 100644
--- a/crates/uv-keyring/Cargo.toml
+++ b/crates/uv-keyring/Cargo.toml
@@ -43,6 +43,7 @@ zeroize = { workspace = true }
 doc-comment = "0.3"
 env_logger = "0.11.5"
 fastrand = { workspace = true }
+futures = { workspace = true }

 [package.metadata.docs.rs]
 default-target = "x86_64-unknown-linux-gnu"
diff --git a/crates/uv-keyring/README.md b/crates/uv-keyring/README.md
index f55de0536..e5f525627 100644
--- a/crates/uv-keyring/README.md
+++ b/crates/uv-keyring/README.md
@@ -23,10 +23,12 @@ use uv_keyring::{Entry, Result};

 fn main() -> Result<()> {
     let entry = Entry::new("my-service", "my-name")?;
-    entry.set_password("topS3cr3tP4$$w0rd").await?;
-    let password = entry.get_password().await?;
-    println!("My password is '{}'", password);
-    entry.delete_credential().await?;
+    futures::executor::block_on(async {
+        entry.set_password("topS3cr3tP4$$w0rd").await?;
+        let password = entry.get_password().await?;
+        println!("My password is '{}'", password);
+        entry.delete_credential().await?;
+    }
     Ok(())
 }
 `‌``
-- 
2.53.0

(Again, the above diff has triple backticks that I split up with zero width non-joiners to embed it in Markdown.)

I’m kind of on the right track here, but this approach doesn’t suffice: I get errors like the following.

---- crates/uv-keyring/src/lib.rs - readme (line 422) stdout ----
error[E0277]: the `?` operator can only be used in an async block that returns `Result` or `Option` (or another type that implements `FromResidual`)
   --> crates/uv-keyring/src/lib.rs:429:54
    |
428 |     futures::executor::block_on(async {
    |                                 ----- this function should return `Result` or `Option` to accept `?`
429 |         entry.set_password("topS3cr3tP4$$w0rd").await?;
    |                                                      ^ cannot use the `?` operator in an async block that returns `()`

Plus, I haven’t dealt with crates/uv-keyring/src/mock.rs.

I’m not prepared to spend much more time investigating these tests at the moment, so I think I’m going to just skip compiling them and keep moving, but I would certainly appreciate it if someone more comfortable with async Rust could fix up these doctests so that cargo test -p uv-keyring actually works.

Platform

Fedora 45/Rawhide, x86_64

Version

uv 0.11.3

Python version

Python 3.14.3

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions