Skip to content

Commit

Permalink
switch to uv (#1)
Browse files Browse the repository at this point in the history
  • Loading branch information
samuelcolvin authored Oct 10, 2024
1 parent d9fb537 commit 76b318e
Show file tree
Hide file tree
Showing 14 changed files with 1,015 additions and 220 deletions.
129 changes: 129 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
name: CI

on:
push:
branches:
- main
tags:
- '**'
pull_request: {}

env:
CI: true

jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Install uv
uses: astral-sh/setup-uv@v3
with:
enable-cache: true

- name: Set up Python 3.12
run: uv python install 3.12

- name: Install dependencies
run: uv sync --python 3.12 --frozen

- run: make lint
- run: make typecheck

test:
name: test on ${{ matrix.python-version }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
env:
PYTHON: ${{ matrix.python-version }}
steps:
- uses: actions/checkout@v4

- name: Install uv
uses: astral-sh/setup-uv@v3
with:
enable-cache: true

- name: Set up Python ${{ matrix.python-version }}
run: uv python install ${{ matrix.python-version }}

- run: uv sync --python ${{ matrix.python-version }} --upgrade

- run: mkdir coverage
- run: make test
env:
COVERAGE_FILE: coverage/.coverage.${{ runner.os }}-py${{ matrix.python-version }}
- name: store coverage files
uses: actions/upload-artifact@v4
with:
name: coverage-${{ matrix.python-version }}
path: coverage
include-hidden-files: true

coverage:
runs-on: ubuntu-latest
needs: [test]
steps:
- uses: actions/checkout@v4

- name: get coverage files
uses: actions/download-artifact@v4
with:
merge-multiple: true
path: coverage

- run: pip install coverage[toml]
- run: coverage combine coverage
- run: coverage xml
# - uses: codecov/codecov-action@v4
# with:
# token: ${{ secrets.CODECOV_TOKEN }}
# file: ./coverage.xml
- run: coverage report --fail-under 70

# https://github.com/marketplace/actions/alls-green#why used for branch protection checks
check:
if: always()
needs: [lint, test, coverage]
runs-on: ubuntu-latest
steps:
- name: Decide whether the needed jobs succeeded or failed
uses: re-actors/alls-green@release/v1
with:
jobs: ${{ toJSON(needs) }}

release:
needs: [check]
if: "success() && startsWith(github.ref, 'refs/tags/')"
runs-on: ubuntu-latest
environment: release

permissions:
id-token: write

steps:
- uses: actions/checkout@v4

- name: Install uv
uses: astral-sh/setup-uv@v3
with:
enable-cache: true

- name: Set up Python 3.12
run: uv python install 3.12

- name: check GITHUB_REF matches package version
uses: samuelcolvin/[email protected]
with:
version_file_path: pyproject.toml

- run: uv build

- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
skip-existing: true
15 changes: 7 additions & 8 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,26 @@ repos:
hooks:
# - id: no-commit-to-branch # prevent direct commits to the `main` branch
- id: check-yaml
args: ["--unsafe"]
- id: check-toml
- id: end-of-file-fixer
- id: trailing-whitespace

- repo: local
hooks:
- id: lint
name: Lint
entry: make
args: [lint]
types: [python]
language: system
pass_filenames: false
- id: format
name: Format
entry: make
args: [format]
language: system
types: [python]
pass_filenames: false
- id: lint
name: Lint
entry: make
args: [lint]
types: [python]
language: system
pass_filenames: false
- id: typecheck
name: Typecheck
entry: make
Expand Down
27 changes: 13 additions & 14 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,42 +1,41 @@
.DEFAULT_GOAL := all
sources = pydantic_ai
sources = pydantic_ai tests

.PHONY: .rye # Check that Rye is installed
.rye:
@rye --version || echo 'Please install Rye: https://rye-up.com/guide/installation/'
.PHONY: .uv # Check that uv is installed
.uv:
@uv --version || echo 'Please install uv: https://docs.astral.sh/uv/getting-started/installation/'

.PHONY: .pre-commit # Check that pre-commit is installed
.pre-commit:
@pre-commit -V || echo 'Please install pre-commit: https://pre-commit.com/'

.PHONY: install # Install the package, dependencies, and pre-commit for local development
install: .rye .pre-commit
rye show
rye sync --no-lock
install: .uv .pre-commit
uv sync --frozen
pre-commit install --install-hooks

.PHONY: format # Format the code
format:
rye format
rye lint --fix -- --fix-only
uv run ruff format $(sources)
uv run ruff check --fix --fix-only $(sources)

.PHONY: lint # Lint the code
lint:
rye lint
rye format --check
uv run ruff format --check $(sources)
uv run ruff check $(sources)

.PHONY: typecheck # Run static type checking
typecheck:
rye run pyright
uv run pyright

.PHONY: test # Run tests and collect coverage data
test:
rye run coverage run -m pytest
uv run coverage run -m pytest

.PHONY: testcov # Run tests and generate a coverage report
testcov: test
@echo "building coverage html"
@rye run coverage html --show-contexts
@uv run coverage html --show-contexts

.PHONY: all
all: format lint typecheck test
10 changes: 7 additions & 3 deletions pydantic_ai/_pydantic.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
from inspect import Parameter, Signature, signature
from typing import Any, Callable, Literal, TypedDict, cast, get_origin

from griffe.dataclasses import Docstring, Object as GriffeObject
from griffe.enumerations import DocstringSectionKind
from _griffe.enumerations import DocstringSectionKind
from _griffe.models import Docstring, Object as GriffeObject
from pydantic._internal import _decorators, _generate_schema, _typing_extra
from pydantic._internal._config import ConfigWrapper
from pydantic.config import ConfigDict
Expand Down Expand Up @@ -45,7 +45,7 @@ def function_schema(function: Callable[..., Any]) -> FunctionSchema:
Returns:
A `FunctionSchema` instance.
"""
namespace = _typing_extra.add_module_globals(function, None)
namespace = _typing_extra.get_module_ns_of(function)
config = ConfigDict(title=function.__name__)
config_wrapper = ConfigWrapper(config)
gen_schema = _generate_schema.GenerateSchema(config_wrapper, namespace)
Expand Down Expand Up @@ -82,6 +82,8 @@ def function_schema(function: Callable[..., Any]) -> FunctionSchema:
if p.kind == Parameter.VAR_POSITIONAL:
annotation = list[annotation]

# FieldInfo.from_annotation expects a type, `annotation` is Any
annotation = cast(type[Any], annotation)
field_info = FieldInfo.from_annotation(annotation)
if field_info.description is None:
field_info.description = field_descriptions.get(field_name)
Expand Down Expand Up @@ -111,6 +113,8 @@ def function_schema(function: Callable[..., Any]) -> FunctionSchema:
core_config,
config_wrapper.plugin_settings,
)
# PluggableSchemaValidator is api compat with SchemaValidator
schema_validator = cast(SchemaValidator, schema_validator)
json_schema = GenerateJsonSchema().generate(schema)
return FunctionSchema(
description=description,
Expand Down
18 changes: 12 additions & 6 deletions pydantic_ai/_utils.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,25 @@
from __future__ import annotations as _annotations

import asyncio
from dataclasses import dataclass, is_dataclass
from functools import partial
from types import GenericAlias
from typing import (
Any,
Callable,
Generic,
Literal,
ParamSpec,
TypeAlias,
TypedDict,
TypeVar,
Union,
cast,
get_args,
is_typeddict,
overload,
)

from pydantic import BaseModel
from pydantic.json_schema import JsonSchemaValue
from typing_extensions import ParamSpec, TypeAlias, is_typeddict

_P = ParamSpec('_P')
_R = TypeVar('_R')
Expand All @@ -30,7 +32,7 @@ async def run_in_executor(func: Callable[_P, _R], *args: _P.args, **kwargs: _P.k
return await asyncio.get_running_loop().run_in_executor(None, func, *args) # type: ignore


_UnionType = type(int | str)
_UnionType = type(Union[int, str])


def allow_plain_str(response_type: Any) -> bool:
Expand All @@ -44,7 +46,11 @@ def is_model_like(type_: Any) -> bool:
These should all generate a JSON Schema with `{"type": "object"}` and therefore be usable directly as
function parameters.
"""
return isinstance(type_, type) and (issubclass(type_, BaseModel) or is_dataclass(type_) or is_typeddict(type_))
return (
isinstance(type_, type)
and not isinstance(type_, GenericAlias)
and (issubclass(type_, BaseModel) or is_dataclass(type_) or is_typeddict(type_))
)


class ObjectJsonSchema(TypedDict):
Expand Down Expand Up @@ -72,7 +78,7 @@ class Some(Generic[_T]):


# Analogous to Rust's `Option` type, usage: `Option[Thing]` is equivalent to `Some[Thing] | None`
Option: TypeAlias = Some[_T] | None
Option: TypeAlias = Union[Some[_T], None]


_Left = TypeVar('_Left')
Expand Down
17 changes: 10 additions & 7 deletions pydantic_ai/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@

import asyncio
import inspect
from typing import Any, Awaitable, Callable, Generic, Literal, Sequence, assert_never, cast, overload
from collections.abc import Awaitable, Sequence
from typing import Any, Callable, Generic, Literal, Union, cast, overload

from typing_extensions import assert_never

from . import _utils, messages as _messages, models as _models, result as _result, retrievers as _r
from .result import ResultData
Expand Down Expand Up @@ -223,9 +226,9 @@ async def _run_system_prompt_function(self, func: _SystemPromptFunction[AgentCon

# This is basically a function that may or maybe not take `CallInfo` as an argument, and may or may not be async.
# Usage `SystemPrompt[AgentContext]`
_SystemPromptFunction = (
Callable[[_r.CallInfo[AgentContext]], str]
| Callable[[_r.CallInfo[AgentContext]], Awaitable[str]]
| Callable[[], str]
| Callable[[], Awaitable[str]]
)
_SystemPromptFunction = Union[
Callable[[_r.CallInfo[AgentContext]], str],
Callable[[_r.CallInfo[AgentContext]], Awaitable[str]],
Callable[[], str],
Callable[[], Awaitable[str]],
]
8 changes: 5 additions & 3 deletions pydantic_ai/messages.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
from __future__ import annotations as _annotations

import json
from dataclasses import dataclass, field
from datetime import datetime
from typing import Annotated, Literal
from typing import Annotated, Literal, Union

import pydantic
import pydantic_core
Expand Down Expand Up @@ -76,7 +78,7 @@ class LLMFunctionCalls:
role: Literal['llm-function-calls'] = 'llm-function-calls'


LLMMessage = LLMResponse | LLMFunctionCalls
Message = SystemPrompt | UserPrompt | FunctionReturn | FunctionRetry | PlainResponseForbidden | LLMMessage
LLMMessage = Union[LLMResponse, LLMFunctionCalls]
Message = Union[SystemPrompt, UserPrompt, FunctionReturn, FunctionRetry, PlainResponseForbidden, LLMMessage]

MessagesTypeAdapter = pydantic.TypeAdapter(list[Annotated[Message, pydantic.Field(discriminator='role')]])
2 changes: 2 additions & 0 deletions pydantic_ai/models/openai.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations as _annotations

from dataclasses import dataclass
from datetime import datetime
from functools import cache
Expand Down
6 changes: 5 additions & 1 deletion pydantic_ai/result.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
from __future__ import annotations as _annotations

from collections.abc import AsyncIterable
from dataclasses import dataclass
from typing import Any, AsyncIterable, Generic, Self, TypedDict, TypeVar
from typing import Any, Generic, TypedDict, TypeVar

from pydantic import TypeAdapter, ValidationError
from typing_extensions import Self

from . import _utils, messages

Expand Down
Loading

0 comments on commit 76b318e

Please sign in to comment.