Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions adr/013-markers-on-annotated-fields.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ marker chain (`Secret`, `Alias`, ...) with the field name as the key.
```python
class Login(TypedDict):
user_name: Annotated[str, Key(alias=["user-name", "userName"])]
password: Annotated[str, Key(secret=True)]
token: Annotated[str, Key(exclusive="auth")]
password: Annotated[str, Key(secret=True)]
token: Annotated[str, Key(exclusive="auth")]
```

The same spelling works on a dataclass. Plain dict schemas keep using the markers
Expand Down
7 changes: 3 additions & 4 deletions adr/014-annotation-driven-argument-decorator.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,11 @@ drop-in.

```python
@probatio
async def fetch(user_id: Annotated[int, Range(min=1)], name: str) -> Response:
...
async def fetch(user_id: Annotated[int, Range(min=1)], name: str) -> Response: ...


@probatio({"name": Length(min=2)}, returns=User)
def make(name: str, age: int) -> User:
...
def make(name: str, age: int) -> User: ...
```

- **Inference is the default.** Each annotated parameter becomes its inferred
Expand Down
6 changes: 4 additions & 2 deletions docs/src/content/docs/guides/combinators.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ from probatio import Schema, Any

schema = Schema(Any(int, str))

schema(5) # 5
schema(5) # 5
schema("a") # 'a'
```

Expand Down Expand Up @@ -81,9 +81,11 @@ validate a tagged union by its tag:
```python
from probatio import Schema, Union


def by_type(value, alternatives):
return [a for a in alternatives if a["type"] == value.get("type")]


schema = Schema(
Union(
{"type": "point", "x": int, "y": int},
Expand Down Expand Up @@ -171,7 +173,7 @@ schema = Schema(
)

schema({"level": "high"}) # {'level': 'high'}
schema({"level": "7"}) # {'level': 7}
schema({"level": "7"}) # {'level': 7}
```

## Passing options through
Expand Down
8 changes: 5 additions & 3 deletions docs/src/content/docs/guides/compiled-schemas.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ from probatio import Schema, All, Coerce, Range
PORT = Schema(All(Coerce(int), Range(min=1, max=65535)))

PORT("443") # 443
PORT(8080) # 8080
PORT(8080) # 8080
```

## Asking for it explicitly
Expand Down Expand Up @@ -69,11 +69,13 @@ function with no intermediate dict.
from dataclasses import dataclass
from probatio import DataclassSchema


@dataclass
class Point:
x: int
y: int


POINT = DataclassSchema(Point).compile()
POINT({"x": 1, "y": 2}) # Point(x=1, y=2)
```
Expand All @@ -92,8 +94,8 @@ behavior invisibly.
from probatio import CompilePolicy, set_compile_policy

set_compile_policy(CompilePolicy.OFF) # never compile unless a schema opts in
set_compile_policy(CompilePolicy.ON) # compile every eligible schema on first use
set_compile_policy(CompilePolicy.AUTO) # the default: compile a schema once it is hot
set_compile_policy(CompilePolicy.ON) # compile every eligible schema on first use
set_compile_policy(CompilePolicy.AUTO) # the default: compile a schema once it is hot
```

A per-schema `compile` flag always wins over the policy, in either direction. The
Expand Down
12 changes: 9 additions & 3 deletions docs/src/content/docs/guides/custom-error-messages.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ for a consumer instead of only prose:
```python
from probatio import Schema, Invalid, MultipleInvalid


def quiet_hour(value):
if not isinstance(value, int) or not 0 <= value <= 23:
raise Invalid(
Expand All @@ -76,16 +77,17 @@ def quiet_hour(value):
)
return value


schema = Schema({"start": quiet_hour})

try:
schema({"start": 25})
except MultipleInvalid as err:
error = err.errors[0]
print(error) # expected an hour between 0 and 23 at 'start'
print(error.code) # quiet_hour
print(error) # expected an hour between 0 and 23 at 'start'
print(error.code) # quiet_hour
print(error.translation_key) # quiet_hour_out_of_range
print(error.placeholders) # {'min': 0, 'max': 23}
print(error.placeholders) # {'min': 0, 'max': 23}
```

The message is the fallback for anyone who just prints the error; the
Expand Down Expand Up @@ -157,12 +159,14 @@ DUTCH = {
"length_min": "lengte moet minimaal {min} zijn",
}


def render_dutch(error):
template = DUTCH.get(error.translation_key)
if template is None:
return error.error_message
return template.format(**error.placeholders)


schema = Schema({"port": int, "host": str})

try:
Expand Down Expand Up @@ -196,6 +200,7 @@ DUTCH = {
"did_you_mean": ", bedoelde je {candidates}?",
}


def render_dutch(error):
text = DUTCH.get(error.translation_key)
if text is None:
Expand All @@ -207,6 +212,7 @@ def render_dutch(error):
text += DUTCH["did_you_mean"].format(candidates=joined)
return text


schema = Schema({"name": str, "email": str})

try:
Expand Down
33 changes: 32 additions & 1 deletion docs/src/content/docs/guides/custom-validators.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@ it returns becomes the validated result.
```python
from probatio import Schema


def double(value):
return value * 2


schema = Schema(double)

schema(21) # 42
Expand All @@ -34,11 +36,13 @@ caller sees, and Probatio attaches the path to the offending value for you.
```python
from probatio import Schema, Invalid, Required


def even(value):
if value % 2 != 0:
raise Invalid("must be even")
return value


schema = Schema({Required("count"): even})

schema({"count": 4}) # {'count': 4}
Expand All @@ -50,11 +54,13 @@ key that broke:
```python
from probatio import Schema, Invalid, Required, MultipleInvalid


def even(value):
if value % 2 != 0:
raise Invalid("must be even")
return value


schema = Schema({Required("count"): even})

try:
Expand All @@ -73,12 +79,14 @@ is on purpose: a lot of standard-library and third-party functions already raise
```python
from probatio import Schema


def port(value):
number = int(value) # raises ValueError on "nope"
if not 0 < number <= 65535:
raise ValueError("out of range")
return number


schema = Schema(port)

schema("443") # 443
Expand All @@ -90,12 +98,14 @@ The catch keeps the `ValueError` reason, appending it after `not a valid value:
```python
from probatio import Schema, MultipleInvalid


def port(value):
number = int(value)
if not 0 < number <= 65535:
raise ValueError("out of range")
return number


schema = Schema(port)

try:
Expand All @@ -122,10 +132,12 @@ rejects it when it is falsy.
```python
from probatio import Schema, truth


@truth
def positive(value):
return value > 0


schema = Schema(positive)
schema(5) # 5
```
Expand All @@ -138,10 +150,12 @@ message.
```python
from probatio import Schema, MultipleInvalid, truth


@truth
def positive(value):
return value > 0


schema = Schema(positive)

try:
Expand All @@ -160,9 +174,11 @@ hands back what you return, and the original input stays as it was (see
```python
from probatio import Schema


def to_slug(value):
return value.strip().lower().replace(" ", "-")


schema = Schema(to_slug)

schema(" Hello World ") # 'hello-world'
Expand Down Expand Up @@ -202,9 +218,11 @@ specifically:
```python
from probatio import Schema, Msg, Match, Invalid, MultipleInvalid


class BadName(Invalid):
"""The name does not look right."""


schema = Schema(Msg(Match(r"^[a-z]+$"), "lowercase letters only", cls=BadName))

try:
Expand All @@ -222,11 +240,13 @@ validator feeds the next (see [combinators](/guides/combinators/)).
```python
from probatio import Schema, All, Strip, Lower, Invalid, Required


def not_empty(value):
if not value:
raise Invalid("must not be empty")
return value


schema = Schema({Required("name"): All(Strip, Lower, not_empty)})

schema({"name": " Frenck "}) # {'name': 'frenck'}
Expand All @@ -238,11 +258,13 @@ field of only spaces collapses to an empty string and is rejected:
```python
from probatio import Schema, All, Strip, Lower, Invalid, Required, MultipleInvalid


def not_empty(value):
if not value:
raise Invalid("must not be empty")
return value


schema = Schema({Required("name"): All(Strip, Lower, not_empty)})

try:
Expand All @@ -260,13 +282,16 @@ every built-in factory works, `Range(min=...)` and `Length(max=...)` included.
```python
from probatio import Schema, Invalid, MultipleInvalid


def at_least(minimum):
def check(value):
if value < minimum:
raise Invalid(f"must be at least {minimum}")
return value

return check


schema = Schema(at_least(18))
schema(21) # 21

Expand All @@ -282,6 +307,7 @@ validator carries several settings or wants a readable `repr`:
```python
from probatio import Schema, Invalid


class AtLeast:
def __init__(self, minimum):
self.minimum = minimum
Expand All @@ -291,6 +317,7 @@ class AtLeast:
raise Invalid(f"must be at least {self.minimum}")
return value


Schema(AtLeast(18))(21) # 21
```

Expand All @@ -314,7 +341,7 @@ class Color(enum.Enum):


schema = Schema(Color)
schema("red") # Color.RED
schema("red") # Color.RED
schema(Color.BLUE) # Color.BLUE
```

Expand Down Expand Up @@ -391,10 +418,12 @@ input.
```python
from probatio import probatio, MultipleInvalid


@probatio
def area(width: int, height: int) -> int:
return width * height


area(3, 4) # 12

try:
Expand All @@ -409,10 +438,12 @@ Layer extra rules with a `constraints` map, and validate the result against the
```python
from probatio import probatio, Length


@probatio({"name": Length(min=2)}, returns=True)
def greet(name: str) -> str:
return "hi " + name


greet("ada") # 'hi ada'
```

Expand Down
16 changes: 12 additions & 4 deletions docs/src/content/docs/guides/dataclasses.md
Original file line number Diff line number Diff line change
Expand Up @@ -211,9 +211,15 @@ from probatio import DataclassSchema, Key, Length
@dataclass
class Account:
name: str
password: Annotated[str, Key(secret=True), Length(min=8)] # redacted, length-checked
user_name: Annotated[str, Key(alias=["user-name", "userName"])] = "" # accept aliases
is_admin: Annotated[bool, Key(forbidden=True)] = False # reject if the caller sends it
password: Annotated[
str, Key(secret=True), Length(min=8)
] # redacted, length-checked
user_name: Annotated[str, Key(alias=["user-name", "userName"])] = (
"" # accept aliases
)
is_admin: Annotated[bool, Key(forbidden=True)] = (
False # reject if the caller sends it
)


schema = DataclassSchema(Account)
Expand Down Expand Up @@ -428,7 +434,9 @@ class Event:
when: Annotated[datetime, Coerce(datetime.fromisoformat)]


DataclassSchema(Event)({"when": "2020-01-01T12:00"}) # Event(when=datetime(2020, 1, 1, 12, 0))
DataclassSchema(Event)(
{"when": "2020-01-01T12:00"}
) # Event(when=datetime(2020, 1, 1, 12, 0))
```

The `Coerce` runs first and the `datetime` type confirms the result, so the field
Expand Down
Loading
Loading