Skip to content

Commit 2c101f0

Browse files
committed
docs and examples for atomic operations, some fixes in examples
1 parent eabb8f4 commit 2c101f0

File tree

37 files changed

+987
-13
lines changed

37 files changed

+987
-13
lines changed

docs/atomic_operations.rst

Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,258 @@ Operations are a serialized form of the mutations allowed in the base JSON:API s
1111

1212
Clients can send an array of operations in a single request.
1313
This extension guarantees that those operations will be processed in order and will either completely succeed or fail together.
14+
15+
16+
What can I do?
17+
--------------
18+
19+
Atomic operations extension supports these three actions:
20+
21+
* ``add`` - create a new object
22+
* ``update`` - update any existing object
23+
* ``remove`` - delete any existing object
24+
25+
You can send one or more atomic operations in one request.
26+
27+
If anything fails in one of the operations, everything will be rolled back.
28+
29+
.. note::
30+
Only SQLAlchemy data layer supports atomic operations right now.
31+
Feel free to send PRs to add support for other data layers.
32+
33+
34+
Configuration
35+
-------------
36+
37+
You need to include atomic router:
38+
39+
.. code-block:: python
40+
41+
from fastapi import FastAPI
42+
from fastapi_jsonapi.atomic import AtomicOperations
43+
44+
45+
def add_routes(app: FastAPI):
46+
atomic = AtomicOperations()
47+
app.include_router(atomic.router)
48+
49+
Default path for atomic operations is ``/operations``
50+
51+
52+
There's a way to customize url path, you can also pass your custom APIRouter:
53+
54+
.. code-block:: python
55+
56+
from fastapi import APIRouter
57+
from fastapi_jsonapi.atomic import AtomicOperations
58+
59+
my_router = APIRouter(prefix="/qwerty", tags=["Atomic Operations"])
60+
61+
AtomicOperations(
62+
# you can pass custom url path
63+
url_path="/atomic",
64+
# also you can pass your custom router
65+
router=my_router,
66+
)
67+
68+
69+
Create some objects
70+
-------------------
71+
72+
Create two objects, they are not linked anyhow:
73+
74+
Request:
75+
76+
.. literalinclude:: ./http_snippets/snippets/example_atomic_one__create_computer_and_separate_user
77+
:language: HTTP
78+
79+
Response:
80+
81+
.. literalinclude:: ./http_snippets/snippets/example_atomic_one__create_computer_and_separate_user_result
82+
:language: HTTP
83+
84+
85+
86+
Update object
87+
-------------
88+
89+
Update details
90+
^^^^^^^^^^^^^^
91+
92+
Atomic operations array has to contain at least one operation.
93+
Body in each atomic action has to be as in other regular requests.
94+
For example, update any existing object:
95+
96+
97+
.. literalinclude:: ./http_snippets/snippets/example_atomic_two__update_user
98+
:language: HTTP
99+
100+
Response:
101+
102+
.. literalinclude:: ./http_snippets/snippets/example_atomic_two__update_user_result
103+
:language: HTTP
104+
105+
106+
Update details and relationships
107+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
108+
109+
.. warning::
110+
There may be issues when updating to-many relationships. This feature is not fully-tested yet.
111+
112+
Update already any existing computer and link it to any existing user:
113+
114+
115+
Request:
116+
117+
.. literalinclude:: ./http_snippets/snippets/example_atomic_two__update_computer
118+
:language: HTTP
119+
120+
Response:
121+
122+
.. literalinclude:: ./http_snippets/snippets/example_atomic_two__update_computer_result
123+
:language: HTTP
124+
125+
126+
You can check that details and relationships are updated by fetching the object and related objects:
127+
128+
129+
Request:
130+
131+
.. literalinclude:: ./http_snippets/snippets/example_atomic_two__after_update_computer_check_details
132+
:language: HTTP
133+
134+
Response:
135+
136+
.. literalinclude:: ./http_snippets/snippets/example_atomic_two__after_update_computer_check_details_result
137+
:language: HTTP
138+
139+
140+
141+
Remove object
142+
-------------
143+
144+
You can mix any actions, for example you can create, update, remove at the same time:
145+
146+
147+
Request:
148+
149+
.. literalinclude:: ./http_snippets/snippets/example_atomic_five__mixed_actions
150+
:language: HTTP
151+
152+
Response:
153+
154+
.. literalinclude:: ./http_snippets/snippets/example_atomic_five__mixed_actions_result
155+
:language: HTTP
156+
157+
158+
If all actions are to delete objects, empty response will be returned:
159+
160+
161+
Request:
162+
163+
.. literalinclude:: ./http_snippets/snippets/example_atomic_five__only_remove_actions
164+
:language: HTTP
165+
166+
Response:
167+
168+
.. literalinclude:: ./http_snippets/snippets/example_atomic_five__only_remove_actions_result
169+
:language: HTTP
170+
171+
172+
173+
Local identifier
174+
----------------
175+
176+
Sometimes you need to create an object, create another object and link it to the first one:
177+
178+
Create user and create bio for this user:
179+
180+
Request:
181+
182+
.. literalinclude:: ./http_snippets/snippets/example_atomic_three__create_user_and_user_bio
183+
:language: HTTP
184+
185+
Response:
186+
187+
.. literalinclude:: ./http_snippets/snippets/example_atomic_three__create_user_and_user_bio_result
188+
:language: HTTP
189+
190+
191+
192+
Many to many with local identifier
193+
----------------------------------
194+
195+
If you have many-to-many association (:ref:`examples with many-to-many <include_many_to_many>`),
196+
atomic operations should look like this:
197+
198+
199+
Request:
200+
201+
.. literalinclude:: ./http_snippets/snippets/example_atomic_four__create_many-to-many
202+
:language: HTTP
203+
204+
Response:
205+
206+
.. literalinclude:: ./http_snippets/snippets/example_atomic_four__create_many-to-many_result
207+
:language: HTTP
208+
209+
210+
Check that objects and relationships were created. Pass includes in the url path, like this
211+
``/parent-to-child-association/1?include=parent,child``
212+
213+
214+
Request:
215+
216+
.. literalinclude:: ./http_snippets/snippets/example_atomic_four__get-many-to-many_with_includes
217+
:language: HTTP
218+
219+
Response:
220+
221+
.. literalinclude:: ./http_snippets/snippets/example_atomic_four__get-many-to-many_with_includes_result
222+
:language: HTTP
223+
224+
225+
226+
227+
Errors
228+
------
229+
230+
If any action on the operations list fails, everything will be rolled back
231+
and an error will be returned. Example:
232+
233+
234+
Request:
235+
236+
.. literalinclude:: ./http_snippets/snippets/example_atomic_fail__create_computer_and_update_user_bio
237+
:language: HTTP
238+
239+
Response:
240+
241+
.. literalinclude:: ./http_snippets/snippets/example_atomic_fail__create_computer_and_update_user_bio_result
242+
:language: HTTP
243+
244+
245+
Since ``update`` action requires ``id`` field and user-bio update schema requires ``birth_city`` field,
246+
the app rollbacks all actions and computer is not saved in DB (and user-bio is not updated).
247+
248+
Error is not in JSON:API style yet, PRs are welcome.
249+
250+
251+
Notes
252+
-----
253+
254+
255+
.. note::
256+
See `examples for SQLAlchemy <SQLA_examples>`_ in the repo, all examples are based on it.
257+
258+
259+
.. warning::
260+
Field "href" is not supported yet. Resource can be referenced only by the "type" field.
261+
262+
Relationships resources are not implemented yet,
263+
so updating relationships directly through atomic operations
264+
is not supported too (see skipped tests).
265+
266+
Includes in the response body are not supported (and not planned, until you PR it)
267+
268+
.. _SQLA_examples: https://github.com/mts-ai/FastAPI-JSONAPI/tree/main/examples/api_for_sqlalchemy
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"method": "POST",
3+
"url": "http://localhost:8000/operations",
4+
"httpVersion": "HTTP/1.1",
5+
"queryString": [
6+
],
7+
"headers": [
8+
{
9+
"name": "content-type",
10+
"value": "application/vnd.api+json"
11+
}
12+
],
13+
"postData": {
14+
"mimeType": "application/json",
15+
"text": "{\n \"atomic:operations\": [\n {\n \"op\": \"add\",\n \"data\": {\n \"type\": \"computer\",\n \"attributes\": {\n \"name\": \"Commodore\"\n }\n }\n },\n {\n \"op\": \"update\",\n \"data\": {\n \"type\": \"user_bio\",\n \"attributes\": {\n \"favourite_movies\": \"Saw\"\n }\n }\n }\n ]\n}"
16+
}
17+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"method": "POST",
3+
"url": "http://localhost:8000/operations",
4+
"httpVersion": "HTTP/1.1",
5+
"queryString": [
6+
],
7+
"headers": [
8+
{
9+
"name": "content-type",
10+
"value": "application/vnd.api+json"
11+
}
12+
],
13+
"postData": {
14+
"mimeType": "application/json",
15+
"text": "{\n \"atomic:operations\": [\n {\n \"op\": \"add\",\n \"data\": {\n \"type\": \"computer\",\n \"attributes\": {\n \"name\": \"Liza\"\n },\n \"relationships\": {\n \"user\": {\n \"data\": {\n \"id\": \"1\",\n \"type\": \"user\"\n }\n }\n }\n }\n },\n {\n \"op\": \"update\",\n \"data\": {\n \"id\": \"2\",\n \"type\": \"user_bio\",\n \"attributes\": {\n \"birth_city\": \"Saint Petersburg\",\n \"favourite_movies\": \"\\\"The Good, the Bad and the Ugly\\\", \\\"Once Upon a Time in America\\\"\"\n }\n }\n },\n {\n \"op\": \"remove\",\n \"ref\": {\n \"id\": \"2\",\n \"type\": \"child\"\n }\n }\n ]\n}"
16+
}
17+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"method": "POST",
3+
"url": "http://localhost:8000/operations",
4+
"httpVersion": "HTTP/1.1",
5+
"queryString": [
6+
],
7+
"headers": [
8+
{
9+
"name": "content-type",
10+
"value": "application/vnd.api+json"
11+
}
12+
],
13+
"postData": {
14+
"mimeType": "application/json",
15+
"text": "{\n \"atomic:operations\": [\n {\n \"op\": \"remove\",\n \"ref\": {\n \"id\": \"6\",\n \"type\": \"computer\"\n }\n },\n {\n \"op\": \"remove\",\n \"ref\": {\n \"id\": \"7\",\n \"type\": \"computer\"\n }\n }\n ]\n}"
16+
}
17+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"method": "POST",
3+
"url": "http://localhost:8000/operations",
4+
"httpVersion": "HTTP/1.1",
5+
"queryString": [
6+
],
7+
"headers": [
8+
{
9+
"name": "content-type",
10+
"value": "application/vnd.api+json"
11+
}
12+
],
13+
"postData": {
14+
"mimeType": "application/json",
15+
"text": "{\n \"atomic:operations\":[\n {\n \"op\":\"add\",\n \"data\":{\n \"lid\":\"new-parent\",\n \"type\":\"parent\",\n \"attributes\":{\n \"name\":\"David Newton\"\n }\n }\n },\n {\n \"op\":\"add\",\n \"data\":{\n \"lid\":\"new-child\",\n \"type\":\"child\",\n \"attributes\":{\n \"name\":\"James Owen\"\n }\n }\n },\n {\n \"op\":\"add\",\n \"data\":{\n \"type\":\"parent-to-child-association\",\n \"attributes\":{\n \"extra_data\":\"Lay piece happy box.\"\n },\n \"relationships\":{\n \"parent\":{\n \"data\":{\n \"lid\":\"new-parent\",\n \"type\":\"parent\"\n }\n },\n \"child\":{\n \"data\":{\n \"lid\":\"new-child\",\n \"type\":\"child\"\n }\n }\n }\n }\n }\n ]\n}"
16+
}
17+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"method": "GET",
3+
"url": "http://localhost:8000/parent-to-child-association/1?include=parent,child",
4+
"httpVersion": "HTTP/1.1",
5+
"queryString": [
6+
],
7+
"headers": [
8+
{
9+
"name": "content-type",
10+
"value": "application/vnd.api+json"
11+
}
12+
]
13+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"method": "POST",
3+
"url": "http://localhost:8000/operations",
4+
"httpVersion": "HTTP/1.1",
5+
"queryString": [
6+
],
7+
"headers": [
8+
{
9+
"name": "content-type",
10+
"value": "application/vnd.api+json"
11+
}
12+
],
13+
"postData": {
14+
"mimeType": "application/json",
15+
"text": "{\n \"atomic:operations\":[\n {\n \"op\":\"add\",\n \"data\":{\n \"type\":\"computer\",\n \"attributes\":{\n \"name\":\"Commodore\"\n }\n }\n },\n {\n \"op\":\"add\",\n \"data\":{\n \"type\":\"user\",\n \"attributes\":{\n \"first_name\":\"Kate\",\n \"last_name\":\"Grey\"\n }\n }\n }\n ]\n}"
16+
}
17+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"method": "POST",
3+
"url": "http://localhost:8000/operations",
4+
"httpVersion": "HTTP/1.1",
5+
"queryString": [
6+
],
7+
"headers": [
8+
{
9+
"name": "content-type",
10+
"value": "application/vnd.api+json"
11+
}
12+
],
13+
"postData": {
14+
"mimeType": "application/json",
15+
"text": "{\n \"atomic:operations\":[\n {\n \"op\":\"add\",\n \"data\":{\n \"lid\":\"some-local-id\",\n \"type\":\"user\",\n \"attributes\":{\n \"first_name\":\"Bob\",\n \"last_name\":\"Pink\"\n }\n }\n },\n {\n \"op\":\"add\",\n \"data\":{\n \"type\":\"user_bio\",\n \"attributes\":{\n \"birth_city\":\"Moscow\",\n \"favourite_movies\":\"Jaws, Alien\"\n },\n \"relationships\":{\n \"user\":{\n \"data\":{\n \"lid\":\"some-local-id\",\n \"type\":\"user\"\n }\n }\n }\n }\n }\n ]\n}"
16+
}
17+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"method": "GET",
3+
"url": "http://localhost:8000/computers/4?include=user",
4+
"httpVersion": "HTTP/1.1",
5+
"queryString": [
6+
],
7+
"headers": [
8+
{
9+
"name": "content-type",
10+
"value": "application/vnd.api+json"
11+
}
12+
]
13+
}

0 commit comments

Comments
 (0)