Skip to content

Commit 7eb8acb

Browse files
committed
llm.get_key() is now a documented utility, closes #1094
Refs #1093, simonw/llm-tools-datasette#2
1 parent 5a8d717 commit 7eb8acb

File tree

4 files changed

+62
-5
lines changed

4 files changed

+62
-5
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,7 @@ See also [the llm tag](https://simonwillison.net/tags/llm/) on my blog.
275275
* [Attachments for multi-modal models](https://llm.datasette.io/en/stable/plugins/advanced-model-plugins.html#attachments-for-multi-modal-models)
276276
* [Tracking token usage](https://llm.datasette.io/en/stable/plugins/advanced-model-plugins.html#tracking-token-usage)
277277
* [Utility functions for plugins](https://llm.datasette.io/en/stable/plugins/plugin-utilities.html)
278+
* [llm.get_key()](https://llm.datasette.io/en/stable/plugins/plugin-utilities.html#llm-get-key)
278279
* [llm.user_dir()](https://llm.datasette.io/en/stable/plugins/plugin-utilities.html#llm-user-dir)
279280
* [llm.ModelError](https://llm.datasette.io/en/stable/plugins/plugin-utilities.html#llm-modelerror)
280281
* [Response.fake()](https://llm.datasette.io/en/stable/plugins/plugin-utilities.html#response-fake)

docs/plugins/plugin-utilities.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,31 @@
33

44
LLM provides some utility functions that may be useful to plugins.
55

6+
(plugin-utilities-get-key)=
7+
## llm.get_key()
8+
9+
This method can be used to look up secrets that users have stored using the {ref}`llm keys set <help-keys-set>` command. If your plugin needs to access an API key or other secret this can be a convenient way to provide that.
10+
11+
This returns either a string containing the key or `None` if the key could not be resolved.
12+
13+
Use the `alias="name"` option to retrieve the key set with that alias:
14+
15+
```python
16+
github_key = llm.get_key(alias="github")
17+
```
18+
You can also add `env="ENV_VAR"` to fall back to looking in that environment variable if the key has not been configured:
19+
```python
20+
github_key = llm.get_key(alias="github", env="GITHUB_TOKEN")
21+
```
22+
In some cases you may allow users to provide a key as input, where they could input either the key itself or specify an alias to lookup in `keys.json`. Use the `input=` parameter for that:
23+
24+
```python
25+
github_key = llm.get_key(input=input_from_user, alias="github", env="GITHUB_TOKEN")
26+
```
27+
28+
An previous version of function used positional arguments in a confusing order. These are still supported but the new keyword arguments are recommended as a better way to use `llm.get_key()` going forward.
29+
30+
(plugin-utilities-user-dir)=
631
## llm.user_dir()
732

833
LLM stores various pieces of logging and configuration data in a directory on the user's machine.
@@ -21,6 +46,7 @@ plugin_dir.mkdir(exist_ok=True)
2146
data_path = plugin_dir / "plugin-data.db"
2247
```
2348

49+
(plugin-utilities-modelerror)=
2450
## llm.ModelError
2551

2652
If your model encounters an error that should be reported to the user you can raise this exception. For example:
@@ -32,6 +58,7 @@ raise ModelError("MPT model not installed - try running 'llm mpt30b download'")
3258
```
3359
This will be caught by the CLI layer and displayed to the user as an error message.
3460

61+
(plugin-utilities-response-fake)=
3562
## Response.fake()
3663

3764
When writing tests for a model it can be useful to generate fake response objects, for example in this test from [llm-mpt30b](https://github.com/simonw/llm-mpt30b):

llm/__init__.py

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -333,15 +333,28 @@ def get_model(name: Optional[str] = None, _skip_async: bool = False) -> Model:
333333

334334

335335
def get_key(
336-
explicit_key: Optional[str], key_alias: str, env_var: Optional[str] = None
336+
explicit_key: Optional[str] = None,
337+
key_alias: Optional[str] = None,
338+
env_var: Optional[str] = None,
339+
*,
340+
alias: Optional[str] = None,
341+
env: Optional[str] = None,
342+
input: Optional[str] = None,
337343
) -> Optional[str]:
338344
"""
339-
Return an API key based on a hierarchy of potential sources.
345+
Return an API key based on a hierarchy of potential sources. You should use the keyword arguments,
346+
the positional arguments are here purely for backwards-compatibility with older code.
340347
341-
:param provided_key: A key provided by the user. This may be the key, or an alias of a key in keys.json.
342-
:param key_alias: The alias used to retrieve the key from the keys.json file.
343-
:param env_var: Name of the environment variable to check for the key.
348+
:param input: Input provided by the user. This may be the key, or an alias of a key in keys.json.
349+
:param alias: The alias used to retrieve the key from the keys.json file.
350+
:param env: Name of the environment variable to check for the key as a final fallback.
344351
"""
352+
if alias:
353+
key_alias = alias
354+
if env:
355+
env_var = env
356+
if input:
357+
explicit_key = input
345358
stored_keys = load_keys()
346359
# If user specified an alias, use the key stored for that alias
347360
if explicit_key in stored_keys:

tests/test_utils.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import json
12
import pytest
23
from llm.utils import (
34
extract_fenced_code_block,
@@ -7,6 +8,7 @@
78
simplify_usage_dict,
89
truncate_string,
910
)
11+
from llm import get_key
1012

1113

1214
@pytest.mark.parametrize(
@@ -444,3 +446,17 @@ def test_instantiate_valid(spec, expected_cls, expected_attrs):
444446
def test_instantiate_invalid(spec):
445447
with pytest.raises(ValueError):
446448
instantiate_from_spec({"Files": Files, "ValueFlag": ValueFlag}, spec)
449+
450+
451+
def test_get_key(user_path, monkeypatch):
452+
monkeypatch.setenv("ENV", "from-env")
453+
(user_path / "keys.json").write_text(json.dumps({"testkey": "TEST"}), "utf-8")
454+
assert get_key(alias="testkey") == "TEST"
455+
assert get_key(input="testkey") == "TEST"
456+
assert get_key(alias="missing", env="ENV") == "from-env"
457+
assert get_key(alias="missing") is None
458+
# found key should over-ride env
459+
assert get_key(input="testkey", env="ENV") == "TEST"
460+
# explicit key should over-ride alias
461+
assert get_key(input="explicit", alias="testkey") == "explicit"
462+
assert get_key(input="explicit", alias="testkey", env="ENV") == "explicit"

0 commit comments

Comments
 (0)