Python serverside toolkit
Project description
Python Serverside
About
A Python serverside toolkit for fast and secure server-side programming with python.
Installation
pip3 install serverside
Usage
The following outlines the current features of this toolkit. This toolkit was developed with one thing in mind:
To keep everything flexible so you can inject your own logic where nescessary, and to use other popular packages so there isn't another library you have to get familiar with.
That means that this toolkit makes extensive use of these packages: Django, aiohttp, Ariadne, djangorestframework.
GraphQL
GraphQL is a fantastic way to build API's that consume less of the backend developers time. We are a fan of Ariadne GraphQL. Schema first development is our favourite way of developing graphql applications, and allow for alot more flexibility compared to libraries such as graphene.
We wanted the ability to almost directly generate the logic for CRUD operations from defined Django models, but still keep it flexible for additional logic.
NOTE: Our automatic resolvers use Relay, as in practice it has superior pagination abilities. You can find more information here.
So let's say we have a (very simple) Django user model in models.py
:
from django.db import models
class User(models.Model):
class Meta:
ordering = ("-updated",)
db_table = "users__users"
id = models.CharField(primary_key=True, max_length=64)
username = models.CharField(max_length=32, unique=True, null=False, blank=False)
password = models.CharField(max_length=255, null=False, blank=False)
updated = models.DateTimeField(auto_now=True)
created = models.DateTimeField(auto_now_add=True)
We can now define a new file along side models.py
called resolvers.py
. Which looks like this:
import typing as ty
import uuid
from ariadne import ObjectType
from serverside.graphql.ariadne_helpers import (
BaseResolver, django_get_one, django_get_many,
django_create, django_update, django_delete
)
from apps.users.models import User
from django.conf import settings
class UserResolvers(BaseResolver):
user = ObjectType("User")
@staticmethod
@user.field("usernameModified") # A custom attribute not defined on the Model
async def get_modified_username(obj, *args, **kwargs):
return obj.username + "_modified"
@staticmethod
@settings.QUERY.field("login")
async def resolve_login(_, info, username: str, password: str):
# Here would be your custom login logic
return {"error": False, "message": "Successfull Login.", "token": "123"}
@staticmethod
@settings.QUERY.field("user")
async def resolve_get(_, info, id: str):
return await django_get_one(info, User, id)
@staticmethod
@settings.QUERY.field("users")
async def resolve_list(_, info, *args, **kwargs):
return await django_get_many(info, User, "users", kwargs)
@staticmethod
@settings.QUERY.field("createUser")
async def resolve_create(_, info, id: str):
return await django_create(info, User, id, lambda: str(uuid.uuid4())) # Custom pk generator
@staticmethod
@settings.QUERY.field("updateUsers")
async def resolve_update(_, info, *args, **kwargs):
return await django_update(info, User, "users", kwargs)
@staticmethod
@settings.QUERY.field("deleteUser")
async def resolve_delete(_, info, id: str):
return await django_delete(info, User, id)
def export_resolvers() -> ty.List:
return [
UserResolvers
]
Now, to use this, in your Django settings file, you just need to add two lines:
from ariadne import QueryType, MutationType
QUERY = QueryType()
MUTATION = MutationType()
And where you normally define your Ariadne schema, you can add the following:
import ariadne
from apps.users.resolvers import export_resolvers as export_user_resolvers
resolvers = []
resolvers.extend(export_user_resolvers().export_resolvers())
schema = open("/path/to/schema.graphql", "r").read()
schema = ariadne.make_executable_schema(
schema,
[
settings.QUERY,
settings.MUTATION,
*resolvers
]
And that's it. Assuming your schema.graphql
has been updated, for the example above we may see:
type User extends Node {
id: ID
username: String
usernameModified: String
password: String
updated: Datetime
created: Datetime
}
type Query {
login(username: String!, password: String!): LoginResponse!
user(id: String): User!
users(
first: Int, after: Int, before: Int, sortBy: String, sortDirection: String,
username: String, username__startswith: String, username__istartswith: String,
username__endswith: String, username__iendswith: String, username__contains: String, username__icontains: String,
): UserConnection!
}
type Mutation {
createUser(input: UserInput!): UserPayload
updateUser(id: ID!, prevUpdated: Float!, input: UserInput!): UserPayload
deleteUser(id: ID!): BasicPayload
}
With that little amount of code, we now have a lot of standard logic already implemented.
GraphQL: Technical Details
For the technical people out there, for django_get_many
, the graphql syntax of the arguments directly relates to how Django filters.
We have also made the SQL queries only fetch data that is needed. Typically many
graphql libraries fetch all columns from the database, and then filter out what is
needed before passing back to the client. django_get_one
and django_get_many
, know what is being queried from the request, and specifically only fetch those columns from the database
For ForeignKey
and ManyToMany
relationships, we make extensive use of Django prefetching capabilities, so that the SQL queries are at the absolute minimum.
Because python is snake case, but graphql is camelcase, we have already taken care of automatically converting between the two.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Hashes for serverside-1.0.1-py3-none-any.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | a8266bd684fe0642d2c45bbaf42143e69702a672f2ff49e4d166c8cc0e031665 |
|
MD5 | 11e6e38dc12a5d3f9f508f8569953a1f |
|
BLAKE2b-256 | f4f2fb43df7e5d705355e515fcb11015a309c8230b662916a102622d3b5f66f8 |