Creating Resources#
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:
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:
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:
Short simpler form with just a resource data
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)
Complete form with ResourceEdge
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:
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:
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.
# 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).
# 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:
# 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.
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
$resourceand$edge) for custom edge dataHandle exceptions for recovery from creation failures
API Reference#
See nova.resource.base.Resource for the full API and advanced usage.