👽️ add optional pydantic support
Pydantic is now an optional dependency, and the middleware provides proper import guards and fallback behavior if Pydantic is not installed. Updated docs to clarify installation and configuration steps.
This commit is contained in:
parent
223c7f3cc6
commit
ced11ac2da
3 changed files with 44 additions and 6 deletions
|
@ -9,6 +9,9 @@ app = SpiderwebRouter(
|
|||
```
|
||||
When working with form data, you may not want to always have to perform your own validation on the incoming data. Spiderweb gives you a way out of the box to perform this validation using Pydantic.
|
||||
|
||||
> [!WARNING]
|
||||
> Pydantic is not installed by default. Install it with `pip install pydantic` or `pip install spiderweb[pydantic]`.
|
||||
|
||||
Let's assume that we have a form view that looks like this:
|
||||
|
||||
```python
|
||||
|
@ -54,4 +57,4 @@ The Pydantic middleware will automatically detect that the model that you want t
|
|||
|
||||
If the validation fails, the middleware will call `on_error`, which by default will return a 400 with a list of the broken fields. You may not want this behavior, so the easiest way to address it is to subclass PydanticMiddleware with your own version and override `on_error` to do whatever you'd like.
|
||||
|
||||
If validation succeeds, the data from the validator will appear on the request object under `request.validated_data` — to access it, just call `.dict()` on the validated data.
|
||||
If validation succeeds, the data from the validator will appear on the request object under `request.validated_data` — to access it, just call `.dict()` on the validated data.
|
||||
|
|
|
@ -34,7 +34,10 @@ peewee = "^3.17.6"
|
|||
jinja2 = "^3.1.4"
|
||||
cryptography = "^43.0.0"
|
||||
email-validator = "^2.2.0"
|
||||
pydantic = "^2.8.2"
|
||||
|
||||
[tool.poetry.extras]
|
||||
# Optional dependencies groups
|
||||
pydantic = ["pydantic>=2.8.2,<3"]
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
ruff = "^0.5.5"
|
||||
|
@ -85,4 +88,4 @@ exclude_also = [
|
|||
"if TYPE_CHECKING:",
|
||||
]
|
||||
|
||||
ignore_errors = true
|
||||
ignore_errors = true
|
||||
|
|
|
@ -1,8 +1,26 @@
|
|||
import inspect
|
||||
from typing import get_type_hints
|
||||
|
||||
from pydantic import BaseModel
|
||||
from pydantic_core._pydantic_core import ValidationError
|
||||
try: # pragma: no cover - import guard
|
||||
from pydantic import BaseModel # type: ignore
|
||||
from pydantic_core._pydantic_core import ValidationError # type: ignore
|
||||
PYDANTIC_AVAILABLE = True
|
||||
except Exception: # pragma: no cover - executed only when pydantic isn't installed
|
||||
PYDANTIC_AVAILABLE = False
|
||||
|
||||
class BaseModel: # minimal stub to allow module import without pydantic
|
||||
@classmethod
|
||||
def parse_obj(cls, *args, **kwargs): # noqa: D401 - simple shim
|
||||
raise RuntimeError(
|
||||
"Pydantic is not installed. Install with 'pip install"
|
||||
" spiderweb-framework[pydantic]' or 'pip install pydantic'"
|
||||
" to use PydanticMiddleware."
|
||||
)
|
||||
|
||||
class ValidationError(Exception): # simple stand-in so type hints resolve
|
||||
def errors(self): # match pydantic's ValidationError API used below
|
||||
return []
|
||||
|
||||
from spiderweb import SpiderwebMiddleware
|
||||
from spiderweb.request import Request
|
||||
from spiderweb.response import JsonResponse
|
||||
|
@ -19,6 +37,12 @@ class PydanticMiddleware(SpiderwebMiddleware):
|
|||
def process_request(self, request):
|
||||
if not request.method == "POST":
|
||||
return
|
||||
if not PYDANTIC_AVAILABLE:
|
||||
raise RuntimeError(
|
||||
"Pydantic is not installed. Install with 'pip install"
|
||||
" spiderweb-framework[pydantic]' or 'pip install pydantic'"
|
||||
" to use PydanticMiddleware."
|
||||
)
|
||||
types = get_type_hints(request.handler)
|
||||
# we don't know what the user named the request object, but
|
||||
# we know that it's first in the list, and it's always an arg.
|
||||
|
@ -34,7 +58,15 @@ class PydanticMiddleware(SpiderwebMiddleware):
|
|||
# Separated out into its own method so that it can be overridden
|
||||
errors = e.errors()
|
||||
error_dict = {"message": "Validation error", "errors": []}
|
||||
# [{'type': 'missing', 'loc': ('comment',), 'msg': 'Field required', 'input': {'email': 'a@a.com'}, 'url': 'https://errors.pydantic.dev/2.8/v/missing'}]
|
||||
# [
|
||||
# {
|
||||
# 'type': 'missing',
|
||||
# 'loc': ('comment',),
|
||||
# 'msg': 'Field required',
|
||||
# 'input': {'email': 'a@a.com'},
|
||||
# 'url': 'https://errors.pydantic.dev/2.8/v/missing'
|
||||
# }
|
||||
# ]
|
||||
for error in errors:
|
||||
field = error["loc"][0]
|
||||
msg = error["msg"]
|
||||
|
|
Loading…
Add table
Reference in a new issue