Skip to content

Commit fe05d76

Browse files
authored
Merge pull request #33 from mts-ai/feature/atomic-operations
Atomic operations
2 parents 141ace7 + 7bb7777 commit fe05d76

File tree

96 files changed

+4608
-193
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

96 files changed

+4608
-193
lines changed

docs/atomic_operations.rst

Lines changed: 274 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,274 @@
1+
.. _atomic_operations:
2+
3+
Atomic Operations
4+
=================
5+
6+
.. currentmodule:: fastapi_jsonapi
7+
8+
9+
Atomic Operations allows to perform multiple “operations” in a linear and atomic manner.
10+
Operations are a serialized form of the mutations allowed in the base JSON:API specification.
11+
12+
Clients can send an array of operations in a single request.
13+
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+
145+
Operations include remove object action
146+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
147+
148+
You can mix any actions, for example you can create, update, remove at the same time:
149+
150+
Request:
151+
152+
.. literalinclude:: ./http_snippets/snippets/example_atomic_five__mixed_actions
153+
:language: HTTP
154+
155+
Response:
156+
157+
.. literalinclude:: ./http_snippets/snippets/example_atomic_five__mixed_actions_result
158+
:language: HTTP
159+
160+
161+
All operations remove objects
162+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
163+
164+
If all actions are to delete objects, empty response will be returned:
165+
166+
167+
Request:
168+
169+
.. literalinclude:: ./http_snippets/snippets/example_atomic_five__only_remove_actions
170+
:language: HTTP
171+
172+
Response:
173+
174+
.. literalinclude:: ./http_snippets/snippets/example_atomic_five__only_remove_actions_result
175+
:language: HTTP
176+
177+
178+
179+
Local identifier
180+
----------------
181+
182+
Sometimes you need to create an object, create another object and link it to the first one:
183+
184+
Create user and create bio for this user:
185+
186+
Request:
187+
188+
.. literalinclude:: ./http_snippets/snippets/example_atomic_three__create_user_and_user_bio
189+
:language: HTTP
190+
191+
Response:
192+
193+
.. literalinclude:: ./http_snippets/snippets/example_atomic_three__create_user_and_user_bio_result
194+
:language: HTTP
195+
196+
197+
198+
Many to many with local identifier
199+
----------------------------------
200+
201+
If you have many-to-many association (:ref:`examples with many-to-many <include_many_to_many>`),
202+
atomic operations should look like this:
203+
204+
205+
Request:
206+
207+
.. literalinclude:: ./http_snippets/snippets/example_atomic_four__create_many-to-many
208+
:language: HTTP
209+
210+
Response:
211+
212+
.. literalinclude:: ./http_snippets/snippets/example_atomic_four__create_many-to-many_result
213+
:language: HTTP
214+
215+
216+
Check that objects and relationships were created. Pass includes in the url path, like this
217+
``/parent-to-child-association/1?include=parent,child``
218+
219+
220+
Request:
221+
222+
.. literalinclude:: ./http_snippets/snippets/example_atomic_four__get-many-to-many_with_includes
223+
:language: HTTP
224+
225+
Response:
226+
227+
.. literalinclude:: ./http_snippets/snippets/example_atomic_four__get-many-to-many_with_includes_result
228+
:language: HTTP
229+
230+
231+
232+
233+
Errors
234+
------
235+
236+
If any action on the operations list fails, everything will be rolled back
237+
and an error will be returned. Example:
238+
239+
240+
Request:
241+
242+
.. literalinclude:: ./http_snippets/snippets/example_atomic_fail__create_computer_and_update_user_bio
243+
:language: HTTP
244+
245+
Response:
246+
247+
.. literalinclude:: ./http_snippets/snippets/example_atomic_fail__create_computer_and_update_user_bio_result
248+
:language: HTTP
249+
250+
251+
Since ``update`` action requires ``id`` field and user-bio update schema requires ``birth_city`` field,
252+
the app rollbacks all actions and computer is not saved in DB (and user-bio is not updated).
253+
254+
Error is not in JSON:API style yet, PRs are welcome.
255+
256+
257+
Notes
258+
-----
259+
260+
261+
.. note::
262+
See `examples for SQLAlchemy <SQLA_examples>`_ in the repo, all examples are based on it.
263+
264+
265+
.. warning::
266+
Field "href" is not supported yet. Resource can be referenced only by the "type" field.
267+
268+
Relationships resources are not implemented yet,
269+
so updating relationships directly through atomic operations
270+
is not supported too (see skipped tests).
271+
272+
Includes in the response body are not supported (and not planned, until you PR it)
273+
274+
.. _SQLA_examples: https://github.com/mts-ai/FastAPI-JSONAPI/tree/main/examples/api_for_sqlalchemy

docs/changelog.rst

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,21 @@ Changelog
22
#########
33

44

5+
**2.1.0**
6+
*********
7+
8+
Atomic Operations
9+
=================
10+
11+
* Atomic Operations (see :ref:`example <atomic_operations>`, `JSON:API doc <https://jsonapi.org/ext/atomic/>`_)
12+
* Create view now accepts ``BaseJSONAPIItemInSchema`` as update view does
13+
14+
Authors
15+
"""""""
16+
17+
* `@mahenzon`_
18+
19+
520
**2.0.0**
621
*********
722

docs/conf.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#
1919
import os
2020
import sys
21+
from datetime import datetime
2122

2223
sys.path.insert(0, os.path.abspath(".."))
2324

@@ -52,19 +53,20 @@
5253
# The master toctree document.
5354
master_doc = "index"
5455

56+
today_year = datetime.today().year
5557
# General information about the project.
5658
project = "FastAPI-JSONAPI"
57-
copyright = "2022, MTS AI"
59+
copyright = f"{today_year}, MTS AI"
5860
author = "MTS AI"
5961

6062
# The version info for the project you're documenting, acts as replacement for
6163
# |version| and |release|, also used in various other places throughout the
6264
# built documents.
6365
#
6466
# The short X.Y version.
65-
version = "2.0"
67+
version = "2.1"
6668
# The full version, including alpha/beta/rc tags.
67-
release = "2.0.0"
69+
release = "2.1.0"
6870

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

docs/http_snippets/README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,14 @@
55

66

77
```shell
8+
# checked on node@14 and npm@6.14.18
89
# to use in cli
9-
npm install --global httpsnippet
10+
npm install --global httpsnippet@2
1011
```
1112

1213
```shell
1314
# to use as a module
14-
npm install --save httpsnippet
15+
npm install --save httpsnippet@2
1516
```
1617

1718
## Spec
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+
}

0 commit comments

Comments
 (0)