A tool for quickly creating REST/HATEOAS/Hypermedia APIs in python

Overview

ripozo

test status test coverage Documentation Status python versions stars

Ripozo is a tool for building RESTful/HATEOAS/Hypermedia apis. It provides strong, simple, and fully qualified linking between resources, the ability to expose available actions and necessary parameters on a resource, and exposing multiple REST protocols (i.e. SIREN and HAL). Finally, ripozo is highly extensible. It is able to integrate with any web framework or database and you can easily roll out your own REST protocols.

Why use ripozo?

  • Strong support for inter-resource linking (HATEOAS/Hypermedia)
  • Flexible (can be used with any web framework, database, or response protocol)
  • Fast development (especially when using the extensions, such as flask-ripozo or django-ripozo)
  • Ability to expose actions on resource from the web api (self-discoverability)

Examples

You'll need to create a dispatcher using one of the dispatchers included in the framework extensions. You can find a list of framework extensions in The ripozo ecosystem section. If it's not there you can always roll out your own.

Minimal

from ripozo import apimethod, adapters, ResourceBase
# import the dispatcher class for your preferred webframework

class MyResource(ResourceBase):
    @apimethod(methods=['GET'])
    def say_hello(cls, request):
        return cls(properties=dict(hello='world'))

# initialize the dispatcher for your framework
# e.g. dispatcher = FlaskDispatcher(app)
dispatcher.register_adapters(adapters.SirenAdapter, adapters.HalAdapter)
dispatcher.register_resources(MyResource)

And just like that, you have an api that can return either Siren or Hal formatted responses. Pretty easy, right?

Full CRUD+L

On the other hand, if you wanted a full CRUD+L (Create, Retrieve, Update, Delete, and List), you could use one of the manager extensions (django-ripozo, ripozo-sqlalchemy, and ripozo-cassandra all include ready to use base managers). There are slight differences on creating Manager classes and instances in the different extensions but at a core they all follow this format.

from ripozo import restmixins
from fake_ripozo_extension import Manager
from myapp.models import MyModel # An ORM model for example a sqlalchemy or Django model.

class MyManager(Manager):
    fields = ('id', 'field1', 'field2',)
    model = MyModel

class MyResource(restmixins.CRUDL):
    manager = MyManager()
    pks = ('id',)

# Create your dispatcher and register the resource...

It is important to note that there are restmixins for each of the individual CRUD+L (i.e. restmixins.Create, restmixins.Retrieve, etc.) actions that can be mixed and matched to your pleasure.

Links

The coolest part of ripozo is the ability to easily create fully qualified links between resources.

from ripozo import restmixins, Relationship

class MyResource(restmixins.CRUDL):
    manager = MyManager()
    pks = ('id',)
    _relationships = [Relationship('related', relation='RelatedResource')]

class RelatedResource(restmixins.CRUDL)
    manager = RelatedManager()
    pks = ('id',)

Now whenever you request MyResource you'll get a link pointing to the related resource.

Documentation

ripozo documentation

The ripozo ecosystem

Currently, ripozo has integrations with Django, Flask, SQLAlchemy, and Cassandra (via cqlengine). The documentation links are provided below.

Frameworks Databases
flask-ripozo ripozo-sqlalchemy
django-ripozo ripozo-cassandra

Built an extension for ripozo? Let us know and we'll add it in here!

Helpful links

Installation

pip install ripozo

Versioning

Prior to version 1.0.0 ripozo versioning follows sentimental versioning. Releases after 1.0.0 follow a standard major.minor.patch style.

  • patch: forwards and backwards compatible
  • minor: backwards compatible
  • major: No guarantees

Contributing

Want to help out? We'd love it! Github will be the hub of development for ripozo. If you have any issues, comments, or complaints post them there. Additionally, we are definitely accepting pull requests (hint: we almost always love more tests and documentation). We do have just a few requests:

  • Every method, function, and class should have a thorough docstring
  • There should be at least one unit test for each function and method
  • Keep your pull requests to one issue. (Preferably open an issue on github first for record keeping)

Behind the name

Ripozo translates to "rest" in Esperanto. Esperanto was designed to be a universal language. Anyone, no matter their native language, can learn and use it easily. Similarly, ripozo is intended to be a universal ReST framework. No matter your preference of database, web framework, or protocol, ripozo makes it easy to build.

Comments
  • Hal adapter. Embedded items provide self link

    Hal adapter. Embedded items provide self link

    Update so that embedded resources provide self link:

    {
        "_links": {
            "self": {
                "href": "http://example.org/api/user?page=3"
            },
         ...
        }
        "_embedded": {
            "users": [
                {
                    "_links": {
                        "self": {
                            "href": "http://example.org/api/user/mwop"
                        }
                    },
                    "id": "mwop",
                    "name": "Matthew Weier O'Phinney"
                },
                {
                    "_links": {
                        "self": {
                            "href": "http://example.org/api/user/mac_nibblet"
                        }
                    },
                    "id": "mac_nibblet",
                    "name": "Antoine Hedgecock"
                },
               ...
            ]
        }
    }
    
    opened by tomdottom 11
  • Handle 204

    Handle 204 "No Response" in all adapters.

    Since all adapters should respond with an empty string in the case of a 204 status code, should this logic should be baked in to the base adapter? If so, how?

    To meet my immediate need, I've applied the logic to each adapter.

    Thanks!

    opened by jesselang 9
  • When returning a resource with no properties, use the 204 status code.

    When returning a resource with no properties, use the 204 status code.

    Some managers (AlchemyManager for instance) return an empty dict on delete(). Along with #60, this change facilitates an empty response when deleting a resource.

    opened by jesselang 3
  • One line ripozo implementation

    One line ripozo implementation

    Stupid ripozo implementations that automatically generate rest endpoints in a single line. These would most likely have to be framework/database dependent.

    Possible combos: flask/sqlalchemy, Django.

    extension 
    opened by timmartin19 2
  • The HalAdapter embedded resources need to include _links

    The HalAdapter embedded resources need to include _links

    As @tomdottom pointed out ripozo was missing the _links attribute in embedded resources. These should also include other relationships within the embedded resource.

    opened by timmartin19 1
  • A BaseResource dynamically created in the Dispatcher that automatically points to all resources

    A BaseResource dynamically created in the Dispatcher that automatically points to all resources

    This should be optional but default to True. This would be a resource that points to all of the other resources. This is helpful in discovery of what is capable in the api. This should be created on a per dispatcher basis.

    enhancement 
    opened by timmartin19 1
  • apimethods with route extensions don't return the correct self.

    apimethods with route extensions don't return the correct self.

    for example

    class MyResource(ResourceBase):
        resource_name = 'myresource'
    
        @apimethod(route='/extra')
        def my_method(cls, request):
            return cls()
    

    Would be something like

    >>> MyResource.my_method(RequestContainer()).url
    '/myresource'
    

    When it should be

    >>> MyResource.my_method(RequestContainer()).url
    '/myresource/extra'
    
    bug 
    opened by timmartin19 1
  • Add create_fields, update_fields, retrieve_fields to BaseManager

    Add create_fields, update_fields, retrieve_fields to BaseManager

    These are useful if you want to restrict what can be updated, created, and or retrieved as those may be different. For example, passwords on a User object should be able to be set but not retrieved.

    enhancement 
    opened by timmartin19 1
  • Allow multiple _pks lists

    Allow multiple _pks lists

    For example, on a resource like a user, there can be multiple possible unique identifiers (e.g. id, email, username), that all refer to the same resource. Currently you can inherit, change the _pks, and create a new resource endpoint, but this hides the implementation layer. However, it could be hard to implement since I'm not sure how you would actually go about it.

    enhancement wontfix 
    opened by timmartin19 1
  • @translate decorator fails when it is inherited?

    @translate decorator fails when it is inherited?

    File "/Users/martin/oss/ripozo/ripozo/viewsets/constructor.py", line 30, in __new__
        mcs._register_class(klass)
      File "/Users/martin/oss/ripozo/ripozo/viewsets/constructor.py", line 55, in _register_class
        method.cls = klass
    AttributeError: 'instancemethod' object has no attribute 'cls'
    

    Came from a third level inherited class

    from ripozo.utilities import pick_processor
    from ripozo.viewsets.restmixins import RetrieveUpdate
    
    class UserResource(RetrieveUpdate):
        _pks = ['username']
        _preprocessors = [picky_processor(login_required, exclude=['login_user']),
                          picky_processor(admin_required, include=['update'])]
        _resource_name = 'user'
        _manager = UserManager
    
    class UserIdResource(UserResource):
        _pks = ['id']
        _resource_name = 'user_id'
    
    bug 
    opened by timmartin19 1
  • Possibly there should be a ripozo request object?

    Possibly there should be a ripozo request object?

    This would mean that dispatchers would simply have to construct the Request object and pass it to the apimethods instead of the mess it is now. More or less it would just be an intermediate step.

    opened by timmartin19 1
  • JSONAPIAdapter calls json.dumps with option ensure_ascii=True

    JSONAPIAdapter calls json.dumps with option ensure_ascii=True

    I'm implementing a RESTful server that reponses in the portuguese language and with the ensure_ascii=True the 'ã' looks like '\u00e3' in the json responses.

    Is there any problem what will cause make this change (ensure_ascii=False)? Or is there any way to implement an option to JSONAPIAdapter call json.dumps with the argument ensure_ascii=False?

    I think that decode the json to utf8 is not job for the client application.

    Maybe can I use the postprocessors in some way?

    opened by yudi-matsuzake 3
  • ripozo will automatically append slash in the end of the URL where append_slash is true or not.

    ripozo will automatically append slash in the end of the URL where append_slash is true or not.

    problem
    ripozo will automatically append slash in the end of the URL where append_slash is true or not.

    Fix problem
    Modify ripozo/utilities:join_url_parts()

    if part is  '':
        continue
    
    opened by hotbaby 0
  • Pass the request into managers

    Pass the request into managers

    Currently, the manager interface more or less assumes that it can operate completely independently of the request object and doesn't pass it in. You can see this in code like:

    class Create(ResourceBase):
        """
        A base class to extend that allows for
        adding the create ability to your resource.
        """
        __abstract__ = True
    
        @apimethod(methods=['POST'], no_pks=True)
        @manager_translate(validate=True, fields_attr='create_fields')
        def create(cls, request):
            """
            Creates a new resource using the cls.manager.create
            method.  Returns the resource that was just created
            as an instance of the class that created it.
            :param RequestContainer request: The request in the standardized
                ripozo style.
            :return: An instance of the class
                that was called.
            :rtype: Update
            """
            _logger.debug('Creating a resource using manager %s', cls.manager)
            props = cls.manager.create(request.body_args)
            meta = dict(links=dict(created=props))
            return cls(properties=props, meta=meta, status_code=201)
    

    This forces managers that need to access something off of the request (such as, the database transaction associated with this current request) to use something like thread local storage to smuggle the currently active transaction into the manager. Global state and indirect passing is a bit of an anti pattern and I generally prefer to pass things like that explicitly into the methods.

    The resources themselves generally support this just fine since each of those methods accepts a request object, but the same isn't the true for managers (and by extension, any of the code that calls a manager, like the CRUD mixins).

    If instead a function was called with the request object to get these, that would make it possible to do this cleanly. For example:

    
    class ResourceBase:
    
        @classmethod
        def get_manager(cls, request):
            return self.manager
    
    class Create(ResourceBase):
    
        __abstract__ = True
    
        @apimethod(methods=['POST'], no_pks=True)
        @manager_translate(validate=True, fields_attr='create_fields')
        def create(cls, request):
            """
            Creates a new resource using the cls.manager.create
            method.  Returns the resource that was just created
            as an instance of the class that created it.
            :param RequestContainer request: The request in the standardized
                ripozo style.
            :return: An instance of the class
                that was called.
            :rtype: Update
            """
            _logger.debug('Creating a resource using manager %s', cls.manager)
            props = cls.get_manager(request).create(request.body_args)
            meta = dict(links=dict(created=props))
            return cls(properties=props, meta=meta, status_code=201)
    

    This would mean that by default, folks who assigned their manager to Resource.manager will have everything still work, but folks like me who want to have explicit request bound sessions can then do something like:

    from ripozo_sqlalchemy import AlchemyManager, SessionHandler
    
    
    class PersonManager(AlchemyManager):
        model = Person
        fields = ('id', 'first_name', 'last_name')
    
    
    class RequestBoundManager:
        @classmethod
        def get_manager(cls, request):
            return PersonManager(SessionHandler(request.db)
    

    And then things should just work, without needing to use thread locals to smuggle that information in.

    opened by dstufft 1
  • Nav links for next/previous pages do not contain applied filters

    Nav links for next/previous pages do not contain applied filters

    When I do the following query (count is not necessary to reproduce):

    GET /courses/?day=5&count=1
    

    Actual result:

    {
        "_embedded": {},
        "_links": {
            "courses": [
                {
                    "href": "/courses/276"
                }
            ],
            "next": {
                "href": "/courses?page=2&count=1"      <----HERE
            },
            "self": {
                "href": "http://127.0.0.1:5000/courses?day=5"
            }
        },
        "count": [
            "1"
        ],
        "day": 5
    }
    

    Expected result:

    {
        "_embedded": {},
        "_links": {
            "courses": [
                {
                    "href": "/courses/276"
                }
            ],
            "next": {
                "href": "/courses?day=5&page=2&count=1"      <----HERE
            },
            "self": {
                "href": "http://127.0.0.1:5000/courses?day=5"
            }
        },
        "count": [
            "1"
        ],
        "day": 5
    }
    
    opened by hroncok 0
  • Overriding GET method to search with TSVectorType

    Overriding GET method to search with TSVectorType

    I try to create an API which inherited from ResourceBase, The problem is i can't set every record as jsonApi, I expected,

    {
      "data": [
        {
          "type": "trademarks",
          "id": "1",
          "attributes": {
            "expediente": "1717801",
            "fecha_de_presentacion": "01/02/2016 12:00:00 AM",
            "figura_juridica": "REGISTRO DE MARCA",
            "logotipo": null,
            "signo": "IGUY",
            "tipo": "NOMINATIVA",
            "titular": "SAMSONITE IP HOLDINGS S.\u00c0.R.L."
          }
        },
        .
        . rest of the data
        .
        ]
    }
    

    Got

    {
        "data": {
            "attributes": {
                "trademarks": [
                    {
                        "fecha_de_presentacion": "01/02/2016 12:00:00 AM",
                        "figura_juridica": "REGISTRO DE MARCA",
                        "id": 1,
                        "signo": "IGUY",
                        "logotipo": null,
                        "tipo": "NOMINATIVA",
                        "titular": "SAMSONITE IP HOLDINGS S.À.R.L.",
                        "expediente": "1717801"
                    },
        .
        . rest of the data
        .
        ]
    }
    
    # python implementation using ripozo
    class Trademarks(restmixins.ResourceBase):
        """
        Trademarks Class provide a resource for search Using a TSVector
        to create a search box
        """
        @apimethod(methods=['GET'])
        def search_trademark(cls, request):
            print(request.query_args)
            props = {'pks': ('id',)}
            if 'search' in request.query_args:
                search = request.query_args.pop('search')[0]
                result = TrademarkMarcanet.query.search(search).all()
            else:
                result = TrademarkMarcanet.query.all()
            response = [r.serialize() for r in result]
            props.update(dict(trademarks=make_json_safe(response)))
            return cls(properties=props, status_code=200)
    

    exists another way to do this task using ripozo api?

    opened by paridin 3
Chisel is a light-weight Python WSGI application framework built for creating well-documented, schema-validated JSON web APIs

chisel Chisel is a light-weight Python WSGI application framework built for creating well-documented, schema-validated JSON web APIs. Here are its fea

Craig Hobbs 2 Dec 2, 2021
NO LONGER MAINTAINED - A Flask extension for creating simple ReSTful JSON APIs from SQLAlchemy models.

NO LONGER MAINTAINED This repository is no longer maintained due to lack of time. You might check out the fork https://github.com/mrevutskyi/flask-res

null 1k Jan 4, 2023
NO LONGER MAINTAINED - A Flask extension for creating simple ReSTful JSON APIs from SQLAlchemy models.

NO LONGER MAINTAINED This repository is no longer maintained due to lack of time. You might check out the fork https://github.com/mrevutskyi/flask-res

null 1k Jan 15, 2021
Containers And REST APIs Workshop

Containers & REST APIs Workshop Containers vs Virtual Machines Ferramentas Podman: https://podman.io/ Docker: https://www.docker.com/ IBM CLI: https:/

Vanderlei Munhoz 8 Dec 16, 2021
A Python package to easily create APIs in Python.

API_Easy An Python Package for easily create APIs in Python pip install easy-api-builder Requiremnets: <= python 3.6 Required modules --> Flask Docume

Envyre-Coding 2 Jan 4, 2022
Flask Sugar is a web framework for building APIs with Flask, Pydantic and Python 3.6+ type hints.

Flask Sugar is a web framework for building APIs with Flask, Pydantic and Python 3.6+ type hints. check parameters and generate API documents automatically. Flask Sugar是一个基于flask,pyddantic,类型注解的API框架, 可以检查参数并自动生成API文档

null 162 Dec 26, 2022
Web APIs for Django. 🎸

Django REST framework Awesome web-browsable Web APIs. Full documentation for the project is available at https://www.django-rest-framework.org/. Fundi

Encode 24.7k Jan 3, 2023
Web3.py plugin for using Flashbots' bundle APIs

This library works by injecting a new module in the Web3.py instance, which allows submitting "bundles" of transactions directly to miners. This is done by also creating a middleware which captures calls to eth_sendBundle and eth_callBundle, and sends them to an RPC endpoint which you have specified, which corresponds to mev-geth.

Georgios Konstantopoulos 294 Jan 4, 2023
Web3.py plugin for using Flashbots' bundle APIs

This library works by injecting a new module in the Web3.py instance, which allows submitting "bundles" of transactions directly to miners. This is do

Flashbots 293 Dec 31, 2022
Otter is framework for creating microservices in Flask like fassion using RPC communication via message queue.

Otter Framework for microservices. Overview Otter is framework for creating microservices in Flask like fassion using RPC communication via message qu

Volodymyr Biloshytskyi 4 Mar 23, 2022
The no-nonsense, minimalist REST and app backend framework for Python developers, with a focus on reliability, correctness, and performance at scale.

The Falcon Web Framework Falcon is a reliable, high-performance Python web framework for building large-scale app backends and microservices. It encou

Falconry 9k Jan 1, 2023
Endpoints is a lightweight REST api framework written in python and used in multiple production systems that handle millions of requests daily.

Endpoints Quickest API builder in the West! Endpoints is a lightweight REST api framework written in python and used in multiple production systems th

Jay Marcyes 30 Mar 5, 2022
REST API framework designed for human beings

Eve Eve is an open source Python REST API framework designed for human beings. It allows to effortlessly build and deploy highly customizable, fully f

eve 6.6k Jan 7, 2023
REST API framework designed for human beings

Eve Eve is an open source Python REST API framework designed for human beings. It allows to effortlessly build and deploy highly customizable, fully f

eve 6.3k Feb 17, 2021
Django Ninja - Fast Django REST Framework

Django Ninja is a web framework for building APIs with Django and Python 3.6+ type hints.

Vitaliy Kucheryaviy 3.8k Jan 2, 2023
A Flask API REST to access words' definition

A Flask API to access words' definitions

Pablo Emídio S.S 9 Jul 22, 2022
Pretty tornado wrapper for making lightweight REST API services

CleanAPI Pretty tornado wrapper for making lightweight REST API services Installation: pip install cleanapi Example: Project folders structure: . ├──

Vladimir Kirievskiy 26 Sep 11, 2022
An alternative serializer implementation for REST framework written in cython built for speed.

drf-turbo An alternative serializer implementation for REST framework written in cython built for speed. Free software: MIT license Documentation: htt

Mng 74 Dec 30, 2022
Free & open source Rest API for YTDislike

RestAPI Free & open source Rest API for YTDislike, read docs.ytdislike.com for implementing. Todo Add websockets Installation Git clone git clone http

null 1 Nov 25, 2021