Simple RESTful API - make a patch

Kearney Taaffe <[email protected]> Thu, 10 Nov 2016 08:10:32 -0800 (PST)
Newsgroups gmane.comp.python.cherrypy
Message-ID <[email protected]>
I'm making an API in CherryPy. I must admit, it's super easy, and I'd like 
to turn this example into something that the CherryPy community puts on 
it's website as a "how to API"

The problem I'm having is how to handle an HTTP PATCH request. The 
following code fails with the error for HTTP PATCH requests

AttributeError: 'Request' object has no attribute 'json'


Here's my main.py
import cherrypy


from controllers.userController import UserController




def CORS():
    """Allow web apps not on the same server to use our API
    """
    cherrypy.response.headers["Access-Control-Allow-Origin"] = "*" 
    cherrypy.response.headers["Access-Control-Allow-Headers"] = (
        "content-type, Authorization, X-Requested-With"
    )
    
    cherrypy.response.headers["Access-Control-Allow-Methods"] = (
        'GET, POST, PUT, DELETE, OPTIONS'
    )
    
if __name__ == '__main__':
    """Starts a cherryPy server and listens for requests
    """
    
    userController = UserController()
    
    cherrypy.tools.CORS = cherrypy.Tool('before_handler', CORS)
    
    cherrypy.config.update({
        'server.socket_host': '0.0.0.0',
        'server.socket_port': 8080,
        'tools.CORS.on': True,
    })


    # API method dispatcher
    # we are defining this here because we want to map the HTTP verb to
    # the same method on the controller class. This _api_user_conf will
    # be used on each route we want to be RESTful
    _api_conf = {
        '/': {
            'request.dispatch': cherrypy.dispatch.MethodDispatcher(),
        }
    }


    # _api_user_conf better explained
    # The default dispatcher in CherryPy stores the HTTP method name at
    # :attr:`cherrypy.request.method<cherrypy._cprequest.Request.method>`.


    # Because HTTP defines these invocation methods, the most direct
    # way to implement REST using CherryPy is to utilize the
    # :class:`MethodDispatcher<cherrypy._cpdispatch.MethodDispatcher>`
    # instead of the default dispatcher. To enable
    # the method dispatcher, add the
    # following to your configuration for the root URI ("/")::


    #     '/': {
    #         'request.dispatch': cherrypy.dispatch.MethodDispatcher(),
    #     }


    # Now, the REST methods will map directly to the same method names on
    # your resources. That is, a GET method on a CherryPy class implements
    # the HTTP GET on the resource represented by that class.


    # http://cherrypy.readthedocs.org/en/3.2.6/_sources/progguide/REST.txt


    cherrypy.tree.mount(userController, '/api/users', _api_conf)
    


    cherrypy.engine.start()
    cherrypy.engine.block() 



Here's the user controller (controllers/userController.py) (*NOTE: be sure 
the file __init__.py is in the controllers directory and is blank)*

import cherrypy


# from services.userServiceProvider import UserServiceProvider


from typing import Dict, List


'''
NOTES
 + @cherrypy.tools.json_out() - automatically outputs response in JSON
 + @cherrypy.tools.json_in()  - automatically parses JSON body
'''
class UserController():


    # expose all the class methods at once
    exposed = True
    
    def __init__(self):
        # create an instance of the service provider
        # self.userService = UserServiceProvider()
        pass


    '''
    This code allows for our routes to look like 
http://example.com/api/users/uuid
    and the uuid will be made available to the routes like the user input
    http://example.com/api/users?uuid=uuid
    ''' 
    def _cp_dispatch(self, vpath: List[str]):
        
        # since our routes will only contain the GUID, we'll only have 1 
        # path. If we have more, just ignore it
        if len(vpath) == 1:
            cherrypy.request.params['uuid'] = vpath.pop()
        
        return self


    @cherrypy.tools.json_out()
    def GET(self, **kwargs: Dict[str, str]) -> str:
        """
        Either gets all the users or a particular user if ID was passed in.
        By using the cherrypy tools decorator we can automagically output 
JSON
        without having to using json.dumps()
        """


        # our URI should be /api/users/{GUID}, by using _cp_dispatch, this 
        # changes the URI to look like /api/users?uuid={GUID}
        
        if 'uuid' not in kwargs:
            # if no GUID was passed in the URI, we should get all users' 
info
            # from the database
            # results =  self.userService.getAllUsers()
            results = {
                'status' : 'getting all users'
            }
        else:
            # results = self.userService.getUser(kwargs['uuid'])
            results = {
                'status' : 'searching for user ' + kwargs['uuid']
            }


        return results
    
    @cherrypy.tools.json_in()
    @cherrypy.tools.json_out()
    def POST(self):
        """Creates a new user
        """
        input = cherrypy.request.json
        inputParams = {}
        
        # convert the keys from unicode to regular strings
        for key, value in input.items():
           inputParams[key] = str(value)
        
        try:
            # result = self.userService.addUser(inputParams)
            result = {
                'status' : 'inserting new record'
            }


            if len(inputParams) == 0:
                raise Exception('no body')
        except Exception as err:
            result = {'error' : 'Failed to create user. ' + err.__str__()}


        return result
    
    @cherrypy.tools.json_out()
    def DELETE(self, **kwargs: Dict[str, str]):
        # convert the keys from unicode to regular strings
        uuid = ''
        if 'uuid' not in kwargs:
            result = {
                'success' : False,
                'message' : 'You must specfy a user.'
            }


            return result


        uuid = kwargs['uuid']


        try:
            if len(uuid) == 0:
                raise Exception('must pass in user ID')
            
            # result = self.userService.deleteUser(inputParams)
            result = {
                'status' : 'deleting user with ID: ' + uuid
            }
        except Exception as err:
            result = {'error' : 'could not delete. ' + err.__str__()}


        return result


    @cherrypy.tools.json_in()
    @cherrypy.tools.json_out()
    def PUT(self):
        # get the request body
        data = cherrypy.request.json
        print('BODY:\n' + str(data))
        # result = self.userService.updateUser(data)
        result = {
            'status' : 'updating user'
        }


        return result


    @cherrypy.tools.json_in()
    @cherrypy.tools.json_out()
    def PATCH(self, **kwargs: Dict[str, str]):


        # the _cp_dispatch() method
        if 'uuid' not in kwargs:
            result = {
                'success' : False,
                'message' : 'You must specfy a user.'
            }


            return result
        else:
            print('found uuid: ' + kwargs['uuid'])


        # get the request body
        data = cherrypy.request.json
        
        # result = self.userService.updateUser(data, kwargs['uuid'])
        result = {
            'status' : 'patching user ({})'.format(kwargs['uuid'])
        }


        return result


    def OPTIONS(self):
        return 'Allow: DELETE, GET, HEAD, OPTIONS, POST, PUT'


    def HEAD(self):
        return ''



By the way, feel to kang this code all you want. And, if I'm doing 
something wrong, please tell me.

-- 
You received this message because you are subscribed to the Google Groups "cherrypy-users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to cherrypy-users+unsubscribe-/JYPxA39Uh5TLH3MbocFF+G/[email protected]
To post to this group, send email to cherrypy-users-/JYPxA39Uh5TLH3MbocFF+G/[email protected]
Visit this group at https://groups.google.com/group/cherrypy-users.
For more options, visit https://groups.google.com/d/optout.