Skip to main content

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

serverside-1.0.1.tar.gz (7.4 kB view details)

Uploaded Source

Built Distribution

serverside-1.0.1-py3-none-any.whl (7.4 kB view details)

Uploaded Python 3

File details

Details for the file serverside-1.0.1.tar.gz.

File metadata

  • Download URL: serverside-1.0.1.tar.gz
  • Upload date:
  • Size: 7.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/1.13.0 pkginfo/1.5.0.1 requests/2.22.0 setuptools/41.0.1 requests-toolbelt/0.9.1 tqdm/4.19.1 CPython/3.7.4

File hashes

Hashes for serverside-1.0.1.tar.gz
Algorithm Hash digest
SHA256 43025003747ebca779cdae9969dec31ea493cdb3c720c77ece41ea5c06868534
MD5 b4cab854817267e2ebe442287257a0fe
BLAKE2b-256 6a5dd483d18ef7f5f87b8a80c72b8789651df8d169738a08a2133c8a4ac7ef34

See more details on using hashes here.

File details

Details for the file serverside-1.0.1-py3-none-any.whl.

File metadata

  • Download URL: serverside-1.0.1-py3-none-any.whl
  • Upload date:
  • Size: 7.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/1.13.0 pkginfo/1.5.0.1 requests/2.22.0 setuptools/41.0.1 requests-toolbelt/0.9.1 tqdm/4.19.1 CPython/3.7.4

File hashes

Hashes for serverside-1.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 a8266bd684fe0642d2c45bbaf42143e69702a672f2ff49e4d166c8cc0e031665
MD5 11e6e38dc12a5d3f9f508f8569953a1f
BLAKE2b-256 f4f2fb43df7e5d705355e515fcb11015a309c8230b662916a102622d3b5f66f8

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page