Skip to content

Commit 35cc26b

Browse files
authored
Merge pull request #40 from mts-ai/feature/openapi-generation-fix
fixed openapi generation
2 parents 5ebf0da + d6066b5 commit 35cc26b

File tree

4 files changed

+76
-24
lines changed

4 files changed

+76
-24
lines changed

fastapi_jsonapi/data_layers/base.py

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@
55
"""
66

77
import types
8-
from typing import List, Optional, Tuple, Type
8+
from typing import Dict, List, Optional, Tuple, Type
99

1010
from fastapi_jsonapi.data_typing import TypeModel, TypeSchema
1111
from fastapi_jsonapi.querystring import QueryStringManager
1212
from fastapi_jsonapi.schema import BaseJSONAPIItemInSchema
13+
from fastapi_jsonapi.schema_builder import FieldConfig, TransferSaveWrapper
1314

1415

1516
class BaseDataLayer:
@@ -75,6 +76,38 @@ async def atomic_start(self, previous_dl: Optional["BaseDataLayer"] = None):
7576
async def atomic_end(self, success: bool = True):
7677
raise NotImplementedError
7778

79+
def _unwrap_field_config(self, extra: Dict):
80+
field_config_wrapper: Optional[TransferSaveWrapper] = extra.get("field_config")
81+
82+
if field_config_wrapper:
83+
return field_config_wrapper.get_field_config()
84+
85+
return FieldConfig()
86+
87+
def _apply_client_generated_id(
88+
self,
89+
data_create: BaseJSONAPIItemInSchema,
90+
model_kwargs: dict,
91+
):
92+
"""
93+
:param data_create: the data validated by pydantic.
94+
:param model_kwargs: the data validated by pydantic.
95+
"""
96+
if data_create.id is None:
97+
return model_kwargs
98+
99+
extra = data_create.__fields__["id"].field_info.extra
100+
if extra.get("client_can_set_id"):
101+
id_value = data_create.id
102+
field_config = self._unwrap_field_config(extra)
103+
104+
if field_config.cast_type:
105+
id_value = field_config.cast_type(id_value)
106+
107+
model_kwargs["id"] = id_value
108+
109+
return model_kwargs
110+
78111
async def create_object(self, data_create: BaseJSONAPIItemInSchema, view_kwargs: dict) -> TypeModel:
79112
"""
80113
Create an object

fastapi_jsonapi/data_layers/sqla_orm.py

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -184,27 +184,6 @@ async def apply_relationships(self, obj: TypeModel, data_create: BaseJSONAPIItem
184184
# todo: relation name may be different?
185185
setattr(obj, relation_name, related_data)
186186

187-
def _apply_client_generated_id(
188-
self,
189-
data_create: BaseJSONAPIItemInSchema,
190-
model_kwargs: dict,
191-
):
192-
"""
193-
:param data_create: the data validated by pydantic.
194-
:param model_kwargs: the data validated by pydantic.
195-
"""
196-
if data_create.id is None:
197-
return model_kwargs
198-
199-
extra = data_create.__fields__["id"].field_info.extra
200-
if extra.get("client_can_set_id"):
201-
id_value = data_create.id
202-
if cast_type := extra.get("id_cast_func"):
203-
id_value = cast_type(id_value)
204-
model_kwargs["id"] = id_value
205-
206-
return model_kwargs
207-
208187
async def create_object(self, data_create: BaseJSONAPIItemInSchema, view_kwargs: dict) -> TypeModel:
209188
"""
210189
Create an object through sqlalchemy.

fastapi_jsonapi/schema_builder.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,28 @@
4646
# todo: when 3.9 support is dropped, return back `slots=True to JSONAPIObjectSchemas dataclass`
4747

4848

49+
class FieldConfig:
50+
cast_type: Callable
51+
52+
def __init__(self, cast_type: Optional[Callable] = None):
53+
self.cast_type = cast_type
54+
55+
56+
class TransferSaveWrapper:
57+
"""
58+
Types doesn't allowed to be passed as keywords to pydantic Field,
59+
so this exists to help save them
60+
61+
In other case OpenAPI generation will fail
62+
"""
63+
64+
def __init__(self, field_config: FieldConfig):
65+
def get_field_config() -> FieldConfig:
66+
return field_config
67+
68+
self.get_field_config = get_field_config
69+
70+
4971
@dataclass(frozen=True)
5072
class JSONAPIObjectSchemas:
5173
attributes_schema: Type[BaseModel]
@@ -479,7 +501,9 @@ def _build_jsonapi_object(
479501
**field_info.extra,
480502
}
481503
if id_cast_func:
482-
id_field_kw.update(id_cast_func=id_cast_func)
504+
id_field_kw.update(
505+
field_config=TransferSaveWrapper(field_config=FieldConfig(cast_type=id_cast_func)),
506+
)
483507

484508
object_jsonapi_schema_fields = {
485509
"attributes": (attributes_schema, ...),

tests/test_api/test_api_sqla_with_includes.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from itertools import chain, zip_longest
44
from json import dumps
55
from typing import Dict, List
6-
from uuid import uuid4
6+
from uuid import UUID, uuid4
77

88
from fastapi import APIRouter, FastAPI, status
99
from httpx import AsyncClient
@@ -1667,6 +1667,22 @@ async def test_openapi_endpoint_ok(self, client: AsyncClient, app: FastAPI):
16671667
resp = response.json()
16681668
assert isinstance(resp, dict)
16691669

1670+
async def test_openapi_for_client_can_set_id(self):
1671+
class Schema(BaseModel):
1672+
id: UUID = Field(client_can_set_id=True)
1673+
1674+
app = build_app_custom(
1675+
model=User,
1676+
schema=Schema,
1677+
schema_in_post=Schema,
1678+
schema_in_patch=Schema,
1679+
resource_type="openapi_case_1",
1680+
)
1681+
1682+
async with AsyncClient(app=app, base_url="http://test") as client:
1683+
response = await client.get(app.openapi_url)
1684+
assert response.status_code == status.HTTP_200_OK, response.text
1685+
16701686

16711687
class TestFilters:
16721688
async def test_filters_really_works(

0 commit comments

Comments
 (0)