Skip to content

Commit 8895d79

Browse files
authored
Pytest examples (#53)
1 parent ff51013 commit 8895d79

File tree

22 files changed

+481
-125
lines changed

22 files changed

+481
-125
lines changed

.github/workflows/ci.yml

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,27 @@ jobs:
2626
enable-cache: true
2727

2828
- name: Install dependencies
29-
run: uv sync --python 3.12 --frozen --all-extras
29+
run: uv sync --python 3.12 --frozen --all-extras --no-dev --group lint
3030

3131
- uses: pre-commit/[email protected]
3232
with:
3333
extra_args: --all-files --verbose
3434
env:
3535
SKIP: no-commit-to-branch
3636

37+
# mypy and lint are a bit slower than other jobs, so we run them separately
38+
mypy:
39+
runs-on: ubuntu-latest
40+
steps:
41+
- uses: actions/checkout@v4
42+
43+
- uses: astral-sh/setup-uv@v3
44+
with:
45+
enable-cache: true
46+
47+
- name: Install dependencies
48+
run: uv sync --python 3.12 --frozen --no-dev --group lint
49+
3750
- run: make typecheck-mypy
3851

3952
docs:
@@ -138,7 +151,7 @@ jobs:
138151
# https://github.com/marketplace/actions/alls-green#why used for branch protection checks
139152
check:
140153
if: always()
141-
needs: [lint, docs, test-live, test, coverage]
154+
needs: [lint, mypy, docs, test-live, test, coverage]
142155
runs-on: ubuntu-latest
143156

144157
steps:

Makefile

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
.PHONY: install # Install the package, dependencies, and pre-commit for local development
1212
install: .uv .pre-commit
13-
uv sync --frozen --all-extras --group docs
13+
uv sync --frozen --all-extras --group lint --group docs
1414
pre-commit install --install-hooks
1515

1616
.PHONY: format # Format the code
@@ -67,12 +67,15 @@ docs:
6767
docs-serve:
6868
uv run mkdocs serve --no-strict
6969

70-
# install insiders packages for docs (to avoid running this on every build, `touch .docs-insiders-install`)
70+
.PHONY: .docs-insiders-install # install insiders packages for docs if necessary
7171
.docs-insiders-install:
72-
@echo 'installing insiders packages'
73-
@uv pip install -U \
74-
--extra-index-url https://pydantic:${PPPR_TOKEN}@pppr.pydantic.dev/simple/ \
75-
mkdocs-material mkdocstrings-python
72+
ifeq ($(shell uv pip show mkdocs-material | grep -q insiders && echo 'installed'), installed)
73+
@echo 'insiders packages already installed'
74+
else
75+
@echo 'installing insiders packages...'
76+
@uv pip install -U mkdocs-material mkdocstrings-python \
77+
--extra-index-url https://pydantic:${PPPR_TOKEN}@pppr.pydantic.dev/simple/
78+
endif
7679

7780
.PHONY: docs-insiders # Build the documentation using insiders packages
7881
docs-insiders: .docs-insiders-install

docs/api/agent.md

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,3 @@
1515
- retriever_plain
1616
- retriever_context
1717
- result_validator
18-
19-
::: pydantic_ai.agent
20-
options:
21-
members:
22-
- KnownModelName

docs/api/models/base.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
::: pydantic_ai.models
44
options:
55
members:
6+
- KnownModelName
67
- Model
78
- AgentModel
89
- AbstractToolDefinition
File renamed without changes.

docs/concepts/dependencies.md

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Dependencies
22

3-
PydanticAI uses a dependency injection system to provide data and services to your agent's [system prompts](system-prompt.md), [retrievers](retrievers.md) and [result validators](result-validation.md#TODO).
3+
PydanticAI uses a dependency injection system to provide data and services to your agent's [system prompts](system-prompt.md), [retrievers](retrievers.md) and [result validators](results.md#TODO).
44

55
Matching PydanticAI's design philosophy, our dependency system tries to use existing best practice in Python development rather than inventing esoteric "magic", this should make dependencies type-safe, understandable easier to test and ultimately easier to deploy in production.
66

@@ -13,7 +13,7 @@ Here's an example of defining an agent that requires dependencies.
1313

1414
(**Note:** dependencies aren't actually used in this example, see [Accessing Dependencies](#accessing-dependencies) below)
1515

16-
```python title="unused_dependencies.py"
16+
```py title="unused_dependencies.py"
1717
from dataclasses import dataclass
1818

1919
import httpx
@@ -41,6 +41,7 @@ async def main():
4141
deps=deps, # (3)!
4242
)
4343
print(result.data)
44+
#> Did you hear about the toothpaste scandal? They called it Colgate.
4445
```
4546

4647
1. Define a dataclass to hold dependencies.
@@ -78,7 +79,7 @@ agent = Agent(
7879
async def get_system_prompt(ctx: CallContext[MyDeps]) -> str: # (2)!
7980
response = await ctx.deps.http_client.get( # (3)!
8081
'https://example.com',
81-
headers={'Authorization': f'Bearer {ctx.deps.api_key}'} # (4)!
82+
headers={'Authorization': f'Bearer {ctx.deps.api_key}'}, # (4)!
8283
)
8384
response.raise_for_status()
8485
return f'Prompt: {response.text}'
@@ -89,6 +90,7 @@ async def main():
8990
deps = MyDeps('foobar', client)
9091
result = await agent.run('Tell me a joke.', deps=deps)
9192
print(result.data)
93+
#> Did you hear about the toothpaste scandal? They called it Colgate.
9294
```
9395

9496
1. [`CallContext`][pydantic_ai.dependencies.CallContext] may optionally be passed to a [`system_prompt`][pydantic_ai.Agent.system_prompt] function as the only argument.
@@ -134,8 +136,7 @@ agent = Agent(
134136
@agent.system_prompt
135137
def get_system_prompt(ctx: CallContext[MyDeps]) -> str: # (2)!
136138
response = ctx.deps.http_client.get(
137-
'https://example.com',
138-
headers={'Authorization': f'Bearer {ctx.deps.api_key}'}
139+
'https://example.com', headers={'Authorization': f'Bearer {ctx.deps.api_key}'}
139140
)
140141
response.raise_for_status()
141142
return f'Prompt: {response.text}'
@@ -148,6 +149,7 @@ async def main():
148149
deps=deps,
149150
)
150151
print(result.data)
152+
#> Did you hear about the toothpaste scandal? They called it Colgate.
151153
```
152154

153155
1. Here we use a synchronous `httpx.Client` instead of an asynchronous `httpx.AsyncClient`.
@@ -157,7 +159,7 @@ _(This example is complete, it can be run "as is")_
157159

158160
## Full Example
159161

160-
As well as system prompts, dependencies can be used in [retrievers](retrievers.md) and [result validators](result-validation.md#TODO).
162+
As well as system prompts, dependencies can be used in [retrievers](retrievers.md) and [result validators](results.md#TODO).
161163

162164
```python title="full_example.py" hl_lines="27-35 38-48"
163165
from dataclasses import dataclass
@@ -215,6 +217,7 @@ async def main():
215217
deps = MyDeps('foobar', client)
216218
result = await agent.run('Tell me a joke.', deps=deps)
217219
print(result.data)
220+
#> Did you hear about the toothpaste scandal? They called it Colgate.
218221
```
219222

220223
1. To pass `CallContext` and to a retriever, us the [`retriever_context`][pydantic_ai.Agent.retriever_context] decorator.
@@ -264,7 +267,6 @@ async def application_code(prompt: str) -> str: # (3)!
264267
app_deps = MyDeps('foobar', client)
265268
result = await joke_agent.run(prompt, deps=app_deps) # (4)!
266269
return result.data
267-
268270
```
269271

270272
1. Define a method on the dependency to make the system prompt easier to customise.
@@ -273,7 +275,7 @@ async def application_code(prompt: str) -> str: # (3)!
273275
4. Call the agent from within the application code, in a real application this call might be deep within a call stack. Note `app_deps` here will NOT be used when deps are overridden.
274276

275277
```py title="test_joke_app.py" hl_lines="10-12"
276-
from joke_app import application_code, joke_agent, MyDeps
278+
from joke_app import MyDeps, application_code, joke_agent
277279

278280

279281
class TestMyDeps(MyDeps): # (1)!
@@ -284,8 +286,8 @@ class TestMyDeps(MyDeps): # (1)!
284286
async def test_application_code():
285287
test_deps = TestMyDeps('test_key', None) # (2)!
286288
with joke_agent.override_deps(test_deps): # (3)!
287-
joke = application_code('Tell me a joke.') # (4)!
288-
assert joke == 'funny'
289+
joke = await application_code('Tell me a joke.') # (4)!
290+
assert joke.startswith('Did you hear about the toothpaste scandal?')
289291
```
290292

291293
1. Define a subclass of `MyDeps` in tests to customise the system prompt factory.
@@ -314,7 +316,7 @@ joke_agent = Agent(
314316
system_prompt=(
315317
'Use the "joke_factory" to generate some jokes, then choose the best. '
316318
'You must return just a single joke.'
317-
)
319+
),
318320
)
319321

320322
factory_agent = Agent('gemini-1.5-pro', result_type=list[str])
@@ -328,6 +330,7 @@ async def joke_factory(ctx: CallContext[MyDeps], count: int) -> str:
328330

329331
result = joke_agent.run_sync('Tell me a joke.', deps=MyDeps(factory_agent))
330332
print(result.data)
333+
#> Did you hear about the toothpaste scandal? They called it Colgate.
331334
```
332335

333336
## Examples

0 commit comments

Comments
 (0)