Skip to content

Commit c83b14c

Browse files
authored
Merge pull request #43 from mts-ai/feature/fix_atomic_operations_method_config_resolution
fix atomic operations method config resolution
2 parents ec13807 + 47f1dea commit c83b14c

File tree

11 files changed

+332
-71
lines changed

11 files changed

+332
-71
lines changed

docs/changelog.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,18 @@
11
Changelog
22
#########
33

4+
**2.2.2**
5+
*********
6+
7+
Atomic Operation dependency resolution fixes
8+
============================================
9+
10+
* fixed Atomic Operation dependency resolution `#43 <https://github.com/mts-ai/FastAPI-JSONAPI/pull/43>`_
11+
12+
Authors
13+
"""""""
14+
15+
* `@mahenzon`_
416

517
**2.2.1**
618
*********

docs/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@
6666
# The short X.Y version.
6767
version = "2.2"
6868
# The full version, including alpha/beta/rc tags.
69-
release = "2.2.1"
69+
release = "2.2.2"
7070

7171
# The language for content autogenerated by Sphinx. Refer to documentation
7272
# for a list of supported languages.

fastapi_jsonapi/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from fastapi_jsonapi.exceptions.json_api import HTTPException
99
from fastapi_jsonapi.querystring import QueryStringManager
1010

11-
__version__ = "2.2.1"
11+
__version__ = "2.2.2"
1212

1313
__all__ = [
1414
"init",

fastapi_jsonapi/api.py

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from fastapi_jsonapi.schema_base import BaseModel
2323
from fastapi_jsonapi.schema_builder import SchemaBuilder
2424
from fastapi_jsonapi.signature import create_additional_query_params
25+
from fastapi_jsonapi.utils.dependency_helper import DependencyHelper
2526
from fastapi_jsonapi.views.utils import (
2627
HTTPMethod,
2728
HTTPMethodConfig,
@@ -408,13 +409,6 @@ def _update_method_config_and_get_dependency_params(
408409

409410
return self._create_dependency_params_from_pydantic_model(method_config.dependencies)
410411

411-
def get_method_config_for_create(self):
412-
method_config = self._update_method_config(
413-
view=self.list_view_resource,
414-
method=HTTPMethod.POST,
415-
)
416-
return method_config
417-
418412
def prepare_dependencies_handler_signature(
419413
self,
420414
custom_handler: Callable[..., Any],
@@ -432,6 +426,35 @@ def prepare_dependencies_handler_signature(
432426

433427
return sig.replace(parameters=params + list(additional_dependency_params) + tail_params)
434428

429+
async def handle_view_dependencies(
430+
self,
431+
request: Request,
432+
view_cls: Type["ViewBase"],
433+
method: HTTPMethod,
434+
) -> Dict[str, Any]:
435+
"""
436+
Consider method config is already prepared for generic views
437+
Reuse the same config for atomic operations
438+
439+
:param request:
440+
:param view_cls:
441+
:param method:
442+
:return:
443+
"""
444+
method_config: HTTPMethodConfig = view_cls.method_dependencies[method]
445+
446+
def handle_dependencies(**dep_kwargs):
447+
return dep_kwargs
448+
449+
handle_dependencies.__signature__ = self.prepare_dependencies_handler_signature(
450+
custom_handler=handle_dependencies,
451+
method_config=method_config,
452+
)
453+
454+
dep_helper = DependencyHelper(request=request)
455+
dependencies_result: Dict[str, Any] = await dep_helper.run(handle_dependencies)
456+
return dependencies_result
457+
435458
def _create_get_resource_list_view(self):
436459
"""
437460
Create wrapper for GET list (get objects list)

fastapi_jsonapi/atomic/atomic_handler.py

Lines changed: 1 addition & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
Any,
99
Awaitable,
1010
Callable,
11-
Dict,
1211
List,
1312
Optional,
1413
TypedDict,
@@ -22,8 +21,6 @@
2221
from fastapi_jsonapi import RoutersJSONAPI
2322
from fastapi_jsonapi.atomic.prepared_atomic_operation import LocalIdsType, OperationBase
2423
from fastapi_jsonapi.atomic.schemas import AtomicOperation, AtomicOperationRequest, AtomicResultResponse
25-
from fastapi_jsonapi.utils.dependency_helper import DependencyHelper
26-
from fastapi_jsonapi.views.utils import HTTPMethodConfig
2724

2825
if TYPE_CHECKING:
2926
from fastapi_jsonapi.data_layers.base import BaseDataLayer
@@ -73,23 +70,6 @@ def __init__(
7370
self.operations_request = operations_request
7471
self.local_ids_cache: LocalIdsType = defaultdict(dict)
7572

76-
async def handle_view_dependencies(
77-
self,
78-
jsonapi: RoutersJSONAPI,
79-
) -> Dict[str, Any]:
80-
method_config: HTTPMethodConfig = jsonapi.get_method_config_for_create()
81-
82-
def handle_dependencies(**dep_kwargs):
83-
return dep_kwargs
84-
85-
handle_dependencies.__signature__ = jsonapi.prepare_dependencies_handler_signature(
86-
custom_handler=handle_dependencies,
87-
method_config=method_config,
88-
)
89-
90-
dependencies_result: Dict[str, Any] = await DependencyHelper(request=self.request).run(handle_dependencies)
91-
return dependencies_result
92-
9373
async def prepare_one_operation(self, operation: AtomicOperation):
9474
"""
9575
:param operation:
@@ -102,16 +82,12 @@ async def prepare_one_operation(self, operation: AtomicOperation):
10282
raise ValueError(msg)
10383
jsonapi = RoutersJSONAPI.all_jsonapi_routers[operation_type]
10484

105-
dependencies_result: Dict[str, Any] = await self.handle_view_dependencies(
106-
jsonapi=jsonapi,
107-
)
108-
one_operation = OperationBase.prepare(
85+
one_operation = await OperationBase.prepare(
10986
action=operation.op,
11087
request=self.request,
11188
jsonapi=jsonapi,
11289
ref=operation.ref,
11390
data=operation.data,
114-
data_layer_view_dependencies=dependencies_result,
11591
)
11692
return one_operation
11793

fastapi_jsonapi/atomic/prepared_atomic_operation.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
from fastapi_jsonapi import RoutersJSONAPI
99
from fastapi_jsonapi.atomic.schemas import AtomicOperationAction, AtomicOperationRef, OperationDataType
10+
from fastapi_jsonapi.views.utils import HTTPMethod
1011

1112
if TYPE_CHECKING:
1213
from fastapi_jsonapi.data_layers.base import BaseDataLayer
@@ -27,14 +28,13 @@ class OperationBase:
2728
op_type: str
2829

2930
@classmethod
30-
def prepare(
31+
async def prepare(
3132
cls,
3233
action: str,
3334
request: Request,
3435
jsonapi: RoutersJSONAPI,
3536
ref: Optional[AtomicOperationRef],
3637
data: OperationDataType,
37-
data_layer_view_dependencies: Dict[str, Any],
3838
) -> "OperationBase":
3939
view_cls: Type[ViewBase] = jsonapi.detail_view_resource
4040

@@ -55,6 +55,11 @@ def prepare(
5555

5656
view = view_cls(request=request, jsonapi=jsonapi)
5757

58+
data_layer_view_dependencies: Dict[str, Any] = await jsonapi.handle_view_dependencies(
59+
request=request,
60+
view_cls=view_cls,
61+
method=operation_cls.http_method,
62+
)
5863
return operation_cls(
5964
jsonapi=jsonapi,
6065
view=view,
@@ -127,6 +132,8 @@ class DetailOperationBase(OperationBase):
127132

128133

129134
class OperationAdd(ListOperationBase):
135+
http_method = HTTPMethod.POST
136+
130137
async def handle(self, dl: BaseDataLayer):
131138
data_in = self.jsonapi.schema_in_post(data=self.data)
132139
response = await self.view.process_create_object(
@@ -137,6 +144,8 @@ async def handle(self, dl: BaseDataLayer):
137144

138145

139146
class OperationUpdate(DetailOperationBase):
147+
http_method = HTTPMethod.PATCH
148+
140149
async def handle(self, dl: BaseDataLayer):
141150
if self.data is None:
142151
# TODO: clear to-one relationships
@@ -153,6 +162,8 @@ async def handle(self, dl: BaseDataLayer):
153162

154163

155164
class OperationRemove(DetailOperationBase):
165+
http_method = HTTPMethod.DELETE
166+
156167
async def handle(
157168
self,
158169
dl: BaseDataLayer,

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ packages = [
7272

7373
[tool.poetry]
7474
name = "fastapi-jsonapi"
75-
version = "2.2.1"
75+
version = "2.2.2"
7676
description = "FastAPI extension to create REST web api according to JSON:API specification"
7777
authors = [
7878
"Aleksei Nekrasov <nekrasov.aleks@mail.ru>",

tests/fixtures/app.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
from pathlib import Path
2+
from typing import Type
23

34
import uvicorn
45
from fastapi import APIRouter, FastAPI
56
from pytest import fixture # noqa PT013
67

78
from fastapi_jsonapi import RoutersJSONAPI, init
89
from fastapi_jsonapi.atomic import AtomicOperations
10+
from fastapi_jsonapi.views.detail_view import DetailViewBase
11+
from fastapi_jsonapi.views.list_view import ListViewBase
912
from tests.fixtures.views import (
1013
DetailViewBaseGeneric,
1114
ListViewBaseGeneric,
@@ -191,3 +194,36 @@ def app(app_plain: FastAPI):
191194
reload=True,
192195
app_dir=str(CURRENT_DIR),
193196
)
197+
198+
199+
def build_app_custom(
200+
model,
201+
schema,
202+
schema_in_patch=None,
203+
schema_in_post=None,
204+
path: str = "/misc",
205+
resource_type: str = "misc",
206+
class_list: Type[ListViewBase] = ListViewBaseGeneric,
207+
class_detail: Type[DetailViewBase] = DetailViewBaseGeneric,
208+
) -> FastAPI:
209+
router: APIRouter = APIRouter()
210+
211+
RoutersJSONAPI(
212+
router=router,
213+
path=path,
214+
tags=["Misc"],
215+
class_list=class_list,
216+
class_detail=class_detail,
217+
schema=schema,
218+
resource_type=resource_type,
219+
schema_in_patch=schema_in_patch,
220+
schema_in_post=schema_in_post,
221+
model=model,
222+
)
223+
224+
app = build_app_plain()
225+
app.include_router(router, prefix="")
226+
227+
atomic = AtomicOperations()
228+
app.include_router(atomic.router, prefix="")
229+
return app

tests/fixtures/views.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,15 @@
1616
from tests.fixtures.db_connection import async_session_dependency
1717

1818

19-
class SessionDependency(BaseModel):
20-
session: AsyncSession = Depends(async_session_dependency)
21-
19+
class ArbitraryModelBase(BaseModel):
2220
class Config:
2321
arbitrary_types_allowed = True
2422

2523

24+
class SessionDependency(ArbitraryModelBase):
25+
session: AsyncSession = Depends(async_session_dependency)
26+
27+
2628
def common_handler(view: ViewBase, dto: SessionDependency) -> Dict:
2729
return {"session": dto.session}
2830

tests/test_api/test_api_sqla_with_includes.py

Lines changed: 2 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from typing import Dict, List
66
from uuid import UUID, uuid4
77

8-
from fastapi import APIRouter, FastAPI, status
8+
from fastapi import FastAPI, status
99
from httpx import AsyncClient
1010
from pydantic import BaseModel, Field, root_validator, validator
1111
from pytest import fixture, mark, param # noqa PT013
@@ -15,9 +15,8 @@
1515
from fastapi_jsonapi.exceptions import BadRequest
1616
from fastapi_jsonapi.schema_builder import SchemaBuilder
1717
from fastapi_jsonapi.views.view_base import ViewBase
18-
from tests.fixtures.app import build_app_plain
18+
from tests.fixtures.app import build_app_custom
1919
from tests.fixtures.entities import build_workplace, create_user
20-
from tests.fixtures.views import DetailViewBaseGeneric, ListViewBaseGeneric
2120
from tests.misc.utils import fake
2221
from tests.models import (
2322
Computer,
@@ -51,34 +50,6 @@ def association_key(data: dict):
5150
return data["type"], data["id"]
5251

5352

54-
def build_app_custom(
55-
model,
56-
schema,
57-
schema_in_patch=None,
58-
schema_in_post=None,
59-
resource_type: str = "misc",
60-
) -> FastAPI:
61-
router: APIRouter = APIRouter()
62-
63-
RoutersJSONAPI(
64-
router=router,
65-
path="/misc",
66-
tags=["Misc"],
67-
class_detail=DetailViewBaseGeneric,
68-
class_list=ListViewBaseGeneric,
69-
schema=schema,
70-
resource_type=resource_type,
71-
schema_in_patch=schema_in_patch,
72-
schema_in_post=schema_in_post,
73-
model=model,
74-
)
75-
76-
app = build_app_plain()
77-
app.include_router(router, prefix="")
78-
79-
return app
80-
81-
8253
async def test_root(client: AsyncClient):
8354
response = await client.get("/docs")
8455
assert response.status_code == status.HTTP_200_OK

0 commit comments

Comments
 (0)