20
Loafang! An alternative to GrapQl?
Full disclaimer, I made loafang
as a way to kill my time and is still in development.
The current implementation that we will be discussing is written in python. And the example shown will be in python 3.9.
So, let's begin.
Well, technically it's more about readability and being more verbose in terms of how the query is written and also removing the need to use an external library to send a request from the client's perspective. The way loafang achieves this is by making use of JSON syntax as it's one of those file types that is supported by a lot of languages out of the box.
And it's way too much fun to do something like this. So.....
{
"GET:get-user": {
"user --name ad": ["username", "email"],
"posts --user-id ad34": ["latest"]
}
}
Let's talk about the parts of the query.
"GET:get-user": {
"user --name ad": ["username", "email"],
"posts --user-id ad34": ["latest"]
}
This is an execution block in terms of loafang terminology. You can have multiple execution blocks in the same loafang query (obviously)
GET:get-user
This is the block header, It specifies three things,
- A Request method, in this case, it's
GET
(The schema is case sensitive), - A unique id that represents the block. (get-user)
- A block property key. (It's optional)
These three things are arranged in the following order.
METHOD:id:property-key
"user --name ad": ["username", "email"]
This is a query that needs to be executed from the server. And it has three parts.
- The head, here its user (this can be a database name or something like that).
- The args (
--name
) - contents, here
["username", "email"]
Now, the contents type must be a list for GET
and DELETE
queries.
And, must be a dict for POST
, PUT
, and PATCH
, as these three methods require you to update or add things.
We will make a simple example where you can ADD
, UPDATE
, and GET
data from the DB.
I won't let you remove your data from MY database.
pip3 install loafang fastapi uvicorn tinydb
# app.py
from __future__ import annotations
from typing import Any, Union
from argparse import Namespace # type hinting purposes only
from fastapi import FastAPI, HTTPException
from loafang import parse, Methods, MethodsError, QueryBuilder
from tinydb import TinyDB, Query
# what our query looks like
query = QueryBuilder("database")
query.add_argument("uname", type=str)
class RequestMethods(Methods):
# class to handle/create a response for queries in a block
def __init__(self) -> None:
# you can have different query parser for different request methods
self.get_query_parser = query
self.post_query_parser = query
self.put_query_parser = query
def get(self, args: Namespace, contents: list[str]) -> Any:
# our database only has fields for name, username and email.
if not all(i in ["name", "email"] for i in contents):
raise MethodsError(400, "Bad contents arguments")
User = Query()
db = TinyDB(args.database)
db_ret = db.search(User.username == args.uname)
if not db_ret:
raise MethodsError(404, "User does not exists")
else:
user = db_ret[0]
return {i: user[i] for i in contents}
def post(self, args: Namespace, contents: dict[str, Any]) -> Any:
if not all(i in contents for i in ["name", "email"]):
raise MethodsError(400, "Bad contents arguments")
User = Query()
db = TinyDB(args.database)
if db.search(User.username == args.uname):
raise MethodsError(403, "User already exists")
else:
db.insert({"username": args.uname, **contents})
return {"msg": f"user {args.uname} added."}
def put(self, args: Namespace, contents: dict[str, Any]) -> Any:
if not all(i in contents for i in ["name", "email"]):
raise MethodsError(400, "Bad contents arguments")
User = Query()
db = TinyDB(args.database)
user = db.search(User.username == args.uname)
if not user:
raise MethodsError(404, "User already exists")
db.update(contents, User.username == args.uname)
return {"msg": f"User updated successfully. {args.uname}"}
request_methods = RequestMethods()
# a vague representation of the loafang query
QueryType = Union[dict[str, Any], list[str]]
# initialize the server
app = FastAPI()
@app.get("/")
def read_root():
return {"ping": "pong"}
@app.post("/loaf")
def loaf(data: QueryType):
# the parsers gives you three things
# The parsed data with all the output (None, if something goes wrong)
# err code (only if something goes wrong, else None)
# err message (only if something goes wrong else None)
parsed_data, err, msg = parse(request_methods, data)
return parsed_data if parsed_data else HTTPException(status_code=err, detail=msg)
uvicorn app:app
Now let's send a sample query to the loaf
endpoint and see what happens.
{
"GET:get-user:pe": {
"db.json --uname adwaith": ["name", "email"]
},
"POST:add-user": {
"db.json --uname adwaith": {
"name": "ad",
"email": "[email protected]"
}
},
"PUT:update-user": {
"db.json --uname adwaith | update-ad": {
"name": "ad2",
"email": "[email protected]"
},
"after": "get-user"
}
}
{
"add-user": {
"db.json --uname adwaith": {
"msg": "user adwaith added."
}
},
"update-user": {
"update-ad": {
"msg": "User updated successfully. adwaith"
},
"after": {
"db.json --uname adwaith": {
"name": "ad2",
"email": "[email protected]"
}
}
}
}
Let's analyze what the hell just happened here,
In the first execution block, you can see that there is a pe
property key (currently that is the only one). It tells the parser to skip the block or "prevents the execution" of the block, the block can now be called from other blocks.
The second block is a POST
block and has one query in it that adds a user with the name adwaith to the DB 'db.json'.
The third is a PUT
block that has one query that updates a user with the name adwaith. But there is something special with that block. It has an after
key. It tells the parser that after it executes the current block it needs to execute the block specified in the after
.
The
after
key can only point to a block that haspe
as its property key.
A block withpe
as its property cannot have anafter
key
You might also notice that there is a pipe |
in between the query. It specifies that anything after the pipe is an alias to the query. As you can see in the result, the key to a query's values is the query itself. So it might be hard at times to retrieve data using the query as the key.
So, that a brief intro to loafang
, feel free to ask questions, use the package and if you have any errors, pls report it or ask me on discord.
Full Docs: https://adwaith-rajesh.github.io/loafang/
Have a nice day.
Happy Coding.
20