|
| 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 |
0 commit comments