🐛 Caterpillar Api, field management for Django without the boiler-plate.
Project description
Caterpillar
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 small; 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 simple so you can focus on functionality, not CRUD.
Install Caterpillar
pip3 install caterpillar-api
Define your API
Inside your django project, create a function with a @Cocoon wrapper that defines your paramaters. Add your params to your func.
from caterpillar import Cocoon, pillar
@Cocoon( post_req=(
('a', int),
('b', int),
))
def add( request, a, b ):
return pillar.resp( request, { "c": a + b })
Add your endpoint to Django
Inside url.py.
urlpatterns = [
path('add/', views.add),
...
]
Call your API using curl
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}
Why Caterpillar?
- Minimum boiler-plate
- Readable syntax
- Crazy fast
- Easy to use
- Unopnionated
Client libraries
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 not writing that code.
- React fetch_js
- Qt C++/QML interface
Recipes
Required POST arguments
post_req takes a tuple of tuples. The tuple entries define variable name, python 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 pillar.resp( request, { id: usr.id })
Optional POST arguments
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 pillar.resp( request, {})
Optional POST using kwargs
post_opt argument checking works fine, 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 pillar.resp( request, {})
Meta data
@Coccon can also provide meta data as an argument. This is handy because it can further reduce duplicate logic. 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 pillar.resp( request, {})
Authenticate user and check login status
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( post_req=(
('uid', str),
('password', str),
))
def login( request, uid, password ):
if (usr := getByUid(User, uid)) is None:
return pillar.err("Couldn't find user")
if usr.password != password:
return pillar.err("Invalid password")
# Store the user's id into the session, this effectively logs the user in
request.session['usr'] = model_to_dict(usr)
return pillar.resp( request, {})
@Cocoon(
sess_req=[('usr', dict)]
)
def logout( request, usr ):
del request.session['usr']
return pillar.resp( 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 pillar.resp( 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 pillar.resp( request, { 'online': usr is not None })
GET data from the URL
The same way POST can have required and optional arguments, GET variables can be accessed through a @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 pillar.resp( request, {'video_info': 'info'})
Read a file
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 errResponse(request, "Couldn't upload to S3")
return pillar.resp( request, {})
Need to call an endpoint directly?
Not a problem. Caterpillar will happy to 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 pillar.resp( 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
Where are PUT and DELETE?
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, since Caterpillar aims to be as lightweight as possible, this feature is out of scope.
@Cocoon
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).
- 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.
@Cocoon Types
- 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.
pillar functions
Pillar 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 import Cocoon, pillar
pillar.resp
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 pillar.resp( request, { 'key': 'value' })
pillar.err
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 pillar.err( request, "Invalid access")
util.raw
util.raw provides HttpResponse logic pillar.resp and pillar.err 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
JSON-P support?
Yes. Caterpillar provides JSON-P support out of the box by passing a GET variable callback=xxx. If the data is passed as POST or cannot use the name 'callback' to pass the callback name, a custom resp/err function set should be created.
Common errors
No one likes bugs in their code, but Caterpillar is a bug and sometimes it encounters other bugs.
TypeError: xxx() got an unexpected keyword argument 'xxx'
Caterpillar works by injecting 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 pillar.resp( 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 pillar.resp( request, { "c": a + b })
# Solution adding **kwargs
@Cocoon( post_req=(
('a', int),
('b', int),
))
def add( request, a, **kwargs ):
return pillar.resp( request, { "c": a + kwargs['b'] })
{"successful": false, "reason": "Missing required argument(s): GET[] POST['b'] SESS[] FILE[]", "code": ""}
If a required parameter is 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.
Testing
Caterpillar has limited testing support. Please see the contribution section to get involved.
Contribute
If you love Caterpillar as much as we do and want to help, there are lots of ways to get involved.
Self documenting reports
Caterpiller could use reflections to generated API reports. If you're interested in giving Caterpillar its documentation wings, please contact lukedupin.
Extended type checking for dict arguments
When passing dict arguments to Cocoon, there needs to be the ability to enforce an expectation of structure.
Client libraries for all the major clients
Love Angular? Flutter? Vue? Bootstrap? Android? IOS? We want libraries to natively support Caterpillar JSON.
Website or graphic design?
We'd love to have a real website. If you want to help give Caterpillar have a personality, please contact us.
Maybe it's something really cool we don't even know about
Do you have ideas for new awesome features? Please contact lukedupin about becoming a contributer.
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 caterpillar_api-1.44-py3-none-any.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 913e99d2088532266f9a9b789ae0c1bb090a5ce4397993bad0485a645e10a2ea |
|
MD5 | 57711b38c4958ad10c254615179c245d |
|
BLAKE2b-256 | fb001305cba443c691cc6f7f3db32cc5ae66f2815f270224439d7177d134b8b0 |