.. _resource-creation-guide: Creating Resources ====================== .. contents:: Table of Contents :depth: 2 :local: Let's Start Building ------------------------------------- While declaring resources is cool, it is useless unless we create and use them. Let's dive into the creation process. Assume we have the following models: .. code-block:: python class UserResource(Resource): # The main node (pivot) user = Pivot(User) # Posts where this user is the author (edge declared on Post) posts = ForeignMount(Post.author, array=True) # Team that this user belongs to (edge declared on User) team = Mount(User.team) class PostResource(Resource): # The main node (pivot) post = Pivot(Post) # Author of this post (edge declared on Post) author = Mount(Post.author) # Categories of this post (edge declared on Post) categories = Mount(Post.categories, array=True) # Comments on this post (edge declared on Comment) comments = ForeignMount(Comment.post, array=True) class CommentResource(Resource): # The main node (pivot) comment = Pivot(Comment) # Replies to this comment (edge declared on Reply) replies = ForeignMount(Reply.comment, array=True) class PostResourceWithExtendedComments(PostResource): post = Pivot(Post) comments = ForeignMount( Comment.post, resource=CommentResource, array=True ) Basic Resource Creation ---------------------- To create a resource, call the ``create`` class method on a resource class, passing a dictionary with the required pivot field: .. code-block:: python user_data = { 'user': { # This is the pivot field 'name': 'John', 'age': 30, 'email': 'john@example.com', 'password': 'password', } } user_resource = await UserResource.create(user_data) The pivot field name (e.g., ``user``) is defined by your resource class. Creating with Mounts and Subresources ------------------------------------- Resources can include subresources via ``Mount`` or ``ForeignMount`` fields. When sending a subresource data, you can follow one of two patterns: 1. Short simpler form with just a resource data .. code-block:: python post_data = { 'title': 'My first post', 'content': 'This is my first post', } user_data = { 'user': { 'name': 'John', 'age': 30, 'email': 'john@example.com', 'password': 'password', }, 'posts': [post_data], # ForeignMount field requires a list } user_resource = await UserResource.create(user_data) 2. Complete form with ResourceEdge .. code-block:: python post_data = { 'title': 'My first post', 'content': 'This is my first post', } user_data = { 'user': { 'name': 'John', 'age': 30, 'email': 'john@example.com', 'password': 'password', }, 'posts': [ { '$resource': post_data, '$edge': {... edge attributes ...}, } ], # ForeignMount field requires a list } user_resource = await UserResource.create(user_data) If the edge between the pivot and the subresource can be instantiated fully with default values and you are okay with default values, you can use the short form. Otherwise, use should use the complete form with ResourceEdge. .. note:: The default edge model is ``Link``. It allows any extra fields and has no required fields. If your relationship does not define a certain edge model with ``Relation(..., model=CustomModel)``, you can both omit the edge field and use the short form or supply any edge attributes you want. Resource and Node mounts ------------------------ Here are two resources: .. code-block:: python class CommentResource(Resource): comment = Pivot(Comment) replies = ForeignMount(Reply.comment, array=True) class PostResource(Resource): post = Pivot(Post) comments = ForeignMount(Comment.post, array=True) class PostResourceWithExtendedComments(PostResource): post = Pivot(Post) comments = ForeignMount( Comment.post, resource=CommentResource, array=True ) Both have the same ``post`` pivot. The difference is that ``PostResource`` mounts comments as ``Comment`` nodes and ``PostResourceWithExtendedComments`` mounts comments as ``CommentResource``. This fact is reflected in the resource creation: .. code-block:: python post_data = { 'post': { 'title': 'My first post', 'content': 'This is my first post' }, 'comments': [ { # For a Node mount, you should supply a node data # in order to create a new node 'content': 'Great post!' } ] } extended_post_data = { 'post': { 'title': 'My first post', 'content': 'This is my first post' }, 'comments': [ { # For a Resource mount, you should supply a resource payload # with its own pivot and optionally its subresources (subsubresources) 'comment': { # <-- `comment` is the pivot field of the CommentResource 'content': 'Great post!' # <-- `content` is the attribute of the Comment model }, 'replies': [ # <-- `replies` is a subresource of the CommentResource { 'content': 'Thanks!' # <-- `content` is the attribute of the Reply model, here Reply is not mounted as } ] } ] } When creating a subresource, you should supply the pivot node data under the pivot key (e.g. in case of ``CommentResource`` the key is ``comment`` and the value is the ``Comment`` node data). Create or Retrieve ------------------- When creating a resource, you can instantly connect to an existing node or a resource. For this, instead of node data, you should supply a ``uid`` of the existing node. .. code-block:: python # Get an existing post existing_post = await Post.first() # Create a user resource with the existing post user_data = { 'user': {'name': 'John', 'age': 30}, 'posts': [{ 'uid': existing_post.uid, }], } user_resource = await UserResource.create(user_data) In this case a new user will be created and the existing post will be connected to it. In case the field is mounted as resource, you should supply ``uid`` under the pivot key (e.g. ``comment`` for ``CommentResource``). .. code-block:: python # Get an existing comment existing_comment = await Comment.first() # Create a post resource with the existing comment post_data = { 'post': { 'title': 'My first post', 'content': 'This is my first post' }, 'comments': [{ 'comment': { 'uid': existing_comment.uid, } }] } post_resource = await PostResource.create(post_data) When giving a ``uid`` to a subresource, you may introduce a new subsubresource. Here is an example of creating a post, connecting it to a existing comment and creating a reply to this comment: .. code-block:: python # Get an existing comment existing_comment = await Comment.first() # Create a post resource with the existing comment post_data = { 'post': { 'title': 'My first post', 'content': 'This is my first post' }, 'comments': [{ 'comment': { 'uid': existing_comment.uid, }, 'replies': [{ 'content': 'Thanks!' }] }] } post_resource = await PostResourceWithExtendedComments.create(post_data) Batch Creation -------------- You can supply a list of resources to the ``create`` method to create multiple resources at once. .. code-block:: python users_data = [ {'user': {'name': 'John', 'age': 30}}, {'user': {'name': 'Jane', 'age': 25}}, ] users = await UserResource.create(users_data) Atomicity --------- The resource creation involves multiple database operations. All of them are executed in a single database transaction. If any part fails, everything is rolled back. Error Handling -------------- The creation method may raise two different errors: - ``CreateResourceFailed``: if the resource creation failed due to incorrect input data or other user errors. - ``CreateResourceInternalError``: if the resource creation failed due to logic errors in the resource class. Best Practices -------------- - Check your resource class for the correct pivot and subresource keys - Use the short form for subresources when no edge data is needed - Use the long form (with ``$resource`` and ``$edge``) for custom edge data - Handle exceptions for recovery from creation failures API Reference ------------- See :class:`nova.resource.base.Resource` for the full API and advanced usage.