A light-weight, fast and scalable api solution for Django. Uses a comfy set of functionality that isn't boilerplatey or opinionated.
Don't disregard Caterpillar because it's cute, it's scrapy to; with built in type conversion, session management, dead simple file handling, self documenting reports and error management. Caterpillar makes writing APIs fun.
pip3 install caterpillar-api
Inside your django project, create a function with a @Cocoon wrapper that defines your paramaters.
from caterpillar_api import Cocoon, monarch
@Cocoon( param_req=(
('a', int),
('b', int),
))
def add( request, a, b ):
return { "c": a + b }
Inside url.py.
urlpatterns = [
path('add/', views.add),
...
]
Now using curl, you can post data to the API and get a JSON response.
curl -d "a=17&b=35" -X POST http://127.0.0.1:8000/add/; echo
{"c": 52, "successful": true}
- Minimum boiler-plate
- Readable syntax
- Crazy fast
- Easy to use
- Unopnionated
Caterpillar doesn't leave you hanging. We have an ever growing list of client libraries to speed up your development efforts. Although you don't need a custom client side library, it sure does feel good using one.
- React fetch_js
- Qt C++/QML interface
post_req takes a tuple of tuples. The tuple entries define (variable name, python type). You can trust the parameter will be the expected type. If a parameter is missing from a post_req, Caterpillar will respond with an error before the endpoint is called.
@Cocoon( post_req=(
('name', str),
('age', int),
))
def add_user( request, name, age ):
usr = User.objects.create( name=name, age=age )
return { 'id': usr.id }
post_opt uses the same format as post_req. Fields can exist or not. If a field isn't present, the default paramater value or None is used.
@Cocoon(
post_req=(
('uid', str),
),
post_opt=(
('name', str),
('age', int),
('type', str, 'ADMIN'),
)
)
def modify_user( request, uid, name, age, type ):
usr = User.objects.get(uid=uid)
if name is not None:
usr.name = name
if age is not None:
usr.age = age
usr.type = type
usr.save()
return monarch.succ( request, {})
post_opt argument checking using if statements does work, but it's a little ugly. It also creates a lot of code. The same operation can be done with kwargs.
@Cocoon(
post_req=(
('uid', str),
),
post_opt=(
('name', str),
('age', int),
('type', str, 'ADMIN'),
)
)
def modify_user( request, uid, **kwargs ):
usr = User.objects.get(uid=uid)
for key in ('name', 'age', 'type'):
if kwargs[key] is not None:
usr.__setattr__( key, kwargs[key] )
usr.save()
return monarch.succ( request, {})
@Coccon can provide meta data as an argument. This is handy because it can further reduce duplicate logic. Parameters are defined in one place only. Take the above example, now using meta data.
@Cocoon(
post_req=(
('uid', str),
),
post_opt=(
('name', str),
('age', int),
('type', str),
),
meta="meta"
)
def modify_user( request, uid, meta, **kwargs ):
usr = User.objects.get(uid=uid)
for key, _ in meta.post_opt:
if kwargs[key] is not None:
usr.__setattr__( key, kwargs[key] )
usr.save()
return monarch.succ( request, {})
Caterpillar also helps you work with session information. Session data can be used to authenticate a user and confirm their login state.
from django.forms import model_to_dict
@Cocoon( param_req=(
('uid', str),
('password', str),
))
def login( request, uid, password ):
if (usr := getByUid(User, uid)) is None:
return "Couldn't find user"
if usr.password != password:
return "Invalid password"
# Store the user's info into the session, this effectively logs the user in
request.session['usr'] = model_to_dict(usr)
return {}
@Cocoon(
sess_req=[('usr', dict)]
)
def logout( request, usr ):
del request.session['usr']
return monarch.succ( request, {})
# sess_req['usr'] will only allow this endpoint to be called by users that are logged in
@Cocoon(
sess_req=[('usr', dict)]
)
def user_details( request, usr ):
usr = getByUid(User, usr['uid'])
return monarch.succ( request, model_to_dict(usr))
If optional session checking is required, you can always use sess_opt
@Cocoon(
sess_opt=[('usr', dict)]
)
def is_online( request, usr ):
return monarch.succ( request, { 'online': usr is not None })
The same way POST can have required and optional arguments, GET variables can be accessed with @Cocoon.
@Cocoon(
get_req=[('video_code', str)]
)
def get_video( request, usr, video_code ):
#http://server.com/?video_code=XXX
print(video_code) # Prints XXX
return monarch.succ( request, {'video_info': 'info'})
Caterpillar seamlessly handles file uploads as parameters. Files, like GET/POST, can be required or optional. The contents of the uploaded file can be read by calling .data(). If a hash of the data is required .hash() will return a sha256 hash.
@Cocoon(
sess_req=[('usr', dict)],
file_req=["logo"]
)
def upload_logo( request, usr, logo ):
if not s3.put_data(logo.data(), usr['uid']):
return monarch.fail( request, "Couldn't upload to S3")
return monarch.succ( request, {})
Not a problem; Caterpillar will happily get out of your way. Passing None for the request parameter will tell Caterpillar you are using the functions directly. Instead of a HttpResponse, you'll get the dict of the response.
@Cocoon(
post_req=(
('a', int),
('b', int),
)
)
def add( request, a, b ):
return monarch.succ( request, { "c": a + b })
add( None, 4, 3) # Returns { "c": 12 }
add( None, "cat", "fish") # Returns { "c": "catfish" } # Type checking isn't done when calling directly
They didn't make the cut. PUT and DELETE don't add any new functionality over update and remove endpoints. PUT/DELETE aren't fundimental to Django and since Caterpillar aims to be as lightweight as possible, this feature is out of scope.
Cocoon is a function decorator that defines endpoint arguments and data types.
- sess_opt - Optional session parameters. Array/Tuple of tuples. ('name', type) or ('name', type, default)
- sess_req - Required session parameters. Array/Tuple of tuples. ('name', type).
- param_opt - Optional POST/GET parameters. Array/Tuple of tuples. ('name', type) or ('name', type, default).
- param_req - Required POST/GET parameters. Array/Tuple of tuples. ('name', type).
- post_opt - Optional POST parameters. Array/Tuple of tuples. ('name', type) or ('name', type, default).
- post_req - Required POST parameters. Array/Tuple of tuples. ('name', type).
- get_opt - Optional GET parameters. Array/Tuple of tuples. ('name', type) or ('name', type, default).
- get_req - Required GET parameters. Array/Tuple of tuples. ('name', type).
- file_opt - Optional file parameters. Returns the ApiFile object with helper functions data() and hash() Array/Tuple of names. ('name').
- file_req - Required file parameters. Returns the ApiFile object with helper functions data() and hash() Array/Tuple of names. ('name').
- files - 'name' which recieves an array of Apifile classes. Handy for uploading arrays of unnamed filess.
- meta - 'name' which recieves a CaterpillarMeta object. All of the Cocoon parameters are represented along with an 'args' dictionary which holds all data passed to this endpoint.
- str
- int
- float
- bool - Can take true/false strings or true/false values or ints, 0 !0
- dict - A dictionary of key value pairs. JSON
- list - An array of parameters. Can take a string of delenated elements.
- name - The name of the variable.
- args - dict of all posted data.
- param_opt - Contents of @Cocoon value for this param.
- param_req - Contents of @Cocoon value for this param.
- sess_opt - Contents of @Cocoon value for this param.
- sess_req - Contents of @Cocoon value for this param.
- post_opt - Contents of @Cocoon value for this param.
- post_req - Contents of @Cocoon value for this param.
- get_opt - Contents of @Cocoon value for this param.
- get_req - Contents of @Cocoon value for this param.
- file_opt - Contents of @Cocoon value for this param.
- file_req - Contents of @Cocoon value for this param.
- files - Contents of @Cocoon value for this param.
Monarch functions provide handy success failed responses. If the standard response format isn't flexible enough, you can create your own using util.raw()
from caterpillar_api import Cocoon, monarch
A successful response. 'successful' = true is added to the response and then an HttpResponse is generated.
- request - The request variable passed by Django.
- response - A dict key/value pair.
return { 'key': 'value' } # Return success
return monarch.succ( request, { 'key': 'value' })
A fail response. 'successful' = false is added to the response and then an HttpResponse is generated.
- request - The request variable passed by Django.
- reason - A string "reason" why the error occurred.
- code="" - An optional error code.
- extra={} - A dict of any other information that should be passed.
return "Invalid access" # Return error message
return monarch.fail( request, "Invalid access", code="ERR_CODE_A")
return monarch.fail( request, "Invalid access", extra={'info': 'data'})
util.raw provides HttpResponse logic monarch.succ and monarch.fail use to communicate with Django. This function should only be used in exceptional cases.
- objs - String of response.
- status - Status code
- content - content_type of response
- callback=None - Optional JSON-P support
return util.raw( '{"key": 8}', 200, content='application/json' )
Yes. Caterpillar provides JSON-P support out of the box by passing a GET variable callback=xxx. If the callback is passed as POST or cannot use the name 'callback' to pass the callback name, a custom resp/err function set should be created.
# http://xxx.com/endpoint?callback=ham_eggs
# Yields a response with JSON-P
ham_eggs( {"key": 917 })
No one likes bugs in their code, but Caterpillars are bugs and sometimes it encounters other bugs.
Caterpillar works by injecting named variables directly into functions. If the variable name doesn't exist in the paramaters of the function, you'll see a TypeError.
@Cocoon( post_req=(
('a', int),
('b', int),
))
def add( request, a ): # TypeError Missing variable 'b'
return monarch.succ( request, { "c": a })
There are two possible solutions. Add all variables or add **kwargs at the end of your parameters.
# Solution of adding all variables
@Cocoon( post_req=(
('a', int),
('b', int),
))
def add( request, a, b):
return monarch.succ( request, { "c": a + b })
# Solution adding **kwargs
@Cocoon( post_req=(
('a', int),
('b', int),
))
def add( request, a, **kwargs ):
return monarch.succ( request, { "c": a + kwargs['b'] })
{"successful": false, "reason": "Missing required argument(s): GET[] POST['b'] SESS[] FILE[]", "code": ""}
If required parameters are missing, the endpoint will not be called. A message similar to the above will be sent instead. Caterpillar attempts to provide detailed information for any GET / POST / SESS / FILE data that is required and missing.
Caterpillar has limited testing support. Please see the contribution section to get involved.
If you love Caterpillar as much as we do, there are lots of ways to get involved.
Caterpiller could use reflections to generated API reports. If you're interested in giving Caterpillar it's documentation butterfly wings, please contact us.
When passing a 'dict' argument type to Cocoon, there needs to be the ability to enforce an expectation of structure.
Love Angular? Flutter? Vue? Bootstrap? Android? IOS? Caterpillar needs native client libraries for them all.
We'd love to have a real website. If you want to help give Caterpillar a personality, please contact us.
Do you have ideas for new awesome features? Please contact lukedupin about becoming a contributer.