Skip to content

Commit 098218e

Browse files
committed
added origin validators to attributes schema
1 parent c08d0ad commit 098218e

File tree

2 files changed

+77
-2
lines changed

2 files changed

+77
-2
lines changed

fastapi_jsonapi/schema_builder.py

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""JSON API schemas builder class."""
22
from dataclasses import dataclass
33
from typing import (
4+
TYPE_CHECKING,
45
Any,
56
Callable,
67
Dict,
@@ -14,9 +15,9 @@
1415
)
1516

1617
import pydantic
17-
from pydantic import BaseConfig
18+
from pydantic import BaseConfig, validator
1819
from pydantic import BaseModel as PydanticBaseModel
19-
from pydantic.fields import FieldInfo, ModelField
20+
from pydantic.fields import FieldInfo, ModelField, Validator
2021

2122
from fastapi_jsonapi.data_typing import TypeSchema
2223
from fastapi_jsonapi.schema import (
@@ -34,6 +35,9 @@
3435
from fastapi_jsonapi.schema_base import BaseModel, Field, RelationshipInfo, registry
3536
from fastapi_jsonapi.splitter import SPLIT_REL
3637

38+
if TYPE_CHECKING:
39+
pass
40+
3741
JSON_API_RESPONSE_TYPE = Dict[Union[int, str], Dict[str, Any]]
3842

3943
JSONAPIObjectSchemaType = TypeVar("JSONAPIObjectSchemaType", bound=PydanticBaseModel)
@@ -279,6 +283,7 @@ class ConfigOrmMode(BaseConfig):
279283
f"{base_name}AttributesJSONAPI",
280284
**attributes_schema_fields,
281285
__config__=ConfigOrmMode,
286+
__validators__=self._extract_validators(schema),
282287
)
283288

284289
relationships_schema = pydantic.create_model(
@@ -346,6 +351,29 @@ def create_relationship_data_schema(
346351
self.relationship_schema_cache[cache_key] = relationship_data_schema
347352
return relationship_data_schema
348353

354+
def _extract_validators(self, model: Type[BaseModel]) -> Dict[str, Callable]:
355+
validators = {}
356+
validator_origin_param_keys = ["pre", "each_item", "always", "check_fields"]
357+
358+
for field_name, model_field in model.__fields__.items():
359+
model_field: ModelField
360+
361+
for validator_name, field_validator in model_field.class_validators.items():
362+
validator_name: str
363+
field_validator: Validator
364+
365+
validators[validator_name] = validator(
366+
field_name,
367+
allow_reuse=True,
368+
**{
369+
# copy origin params
370+
param_name: getattr(field_validator, param_name)
371+
for param_name in validator_origin_param_keys
372+
},
373+
)(field_validator.func)
374+
375+
return validators
376+
349377
def _build_jsonapi_object(
350378
self,
351379
base_name: str,

tests/test_api/test_api_sqla_with_includes.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@
66

77
from fastapi import APIRouter, FastAPI, status
88
from httpx import AsyncClient
9+
from pydantic import BaseModel, validator
910
from pytest import mark, param # noqa PT013
1011
from sqlalchemy.ext.asyncio import AsyncSession
1112

1213
from fastapi_jsonapi import RoutersJSONAPI
14+
from fastapi_jsonapi.exceptions import BadRequest
1315
from fastapi_jsonapi.views.view_base import ViewBase
1416
from tests.fixtures.app import build_app_plain
1517
from tests.fixtures.entities import build_workplace, create_user
@@ -2040,4 +2042,49 @@ async def test_sort(
20402042
}
20412043

20422044

2045+
class TestValidators:
2046+
async def test_field_validator(self):
2047+
class UserSchemaWithValidator(BaseModel):
2048+
name: str
2049+
2050+
@validator("name")
2051+
def validate_name(cls, v):
2052+
# checks that cls arg is not bound to the origin class
2053+
assert cls is not UserSchemaWithValidator
2054+
2055+
raise BadRequest(detail="Check validator")
2056+
2057+
class Config:
2058+
orm_mode = True
2059+
2060+
resource_type = "user"
2061+
app = build_app_custom(
2062+
model=User,
2063+
schema=UserSchemaWithValidator,
2064+
schema_in_post=UserSchemaWithValidator,
2065+
schema_in_patch=UserSchemaWithValidator,
2066+
resource_type=resource_type,
2067+
)
2068+
2069+
attrs = {"name": fake.name()}
2070+
create_user_body = {"data": {"attributes": attrs}}
2071+
2072+
async with AsyncClient(app=app, base_url="http://test") as client:
2073+
url = app.url_path_for(f"get_{resource_type}_list")
2074+
res = await client.post(url, json=create_user_body)
2075+
assert res.status_code == status.HTTP_400_BAD_REQUEST, res.text
2076+
assert res.json() == {
2077+
"detail": {
2078+
"errors": [
2079+
{
2080+
"detail": "Check validator",
2081+
"source": {"pointer": ""},
2082+
"status_code": 400,
2083+
"title": "Bad Request",
2084+
},
2085+
],
2086+
},
2087+
}
2088+
2089+
20432090
# todo: test errors

0 commit comments

Comments
 (0)