Re: Re: Simple RESTful API - make a patch

Kearney Taaffe <[email protected]> Tue, 15 Nov 2016 07:15:25 -0600
Newsgroups gmane.comp.python.cherrypy
Message-ID <CAPmL=oVSewBX6DzOfJW_i15ZAP=smga3suho9Y_osEAvqptyBQ@mail.gmail.com>
Man, I feel stupid. I work with Git at work, but, all we do is just branch
and merge. I forked the project (since I couldn't seem to branch) and made
a branch with the ticket number.

https://github.com/gitKearney/cherrypy/tree/feature/1516_fix_get_PATCH_body

I don't know if you can check that out and merge it. I created a tests
folder, with a simple server and controller, and a caller file that uses
"requests" to send a PATCH request.




Kearney J. Taaffe
(817) 239-2783

On Tue, Nov 15, 2016 at 12:43 AM, Joseph S. Tate <[email protected]>
wrote:

> Ideally you'd submit a pull request from your branch with a test that
> proves that the fix works. Perhaps by copying and modifying a test for PUT
> or POST. Then I'll merge it in.
>
> Joseph
>
> On Mon, Nov 14, 2016 at 11:24 AM Kearney Taaffe <[email protected]>
> wrote:
>
>> @Josheph, sorry, my response wasn't very explicit. Changing the line 315
>> in cherrypy._cprequest.py from
>>
>> methods_with_bodies = ('POST', 'PUT')
>>
>>
>> to
>>
>> methods_with_bodies = ('POST', 'PUT', 'PATCH')
>>
>>
>> worked.
>>
>> I posted the new PATCH() method to show that the body and route param
>> were printed to the log
>>
>>
>>
>> On Thursday, November 10, 2016 at 10:10:32 AM UTC-6, Kearney Taaffe wrote:
>>
>> 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.
>>
> --
> You received this message because you are subscribed to a topic in the
> Google Groups "cherrypy-users" group.
> To unsubscribe from this topic, visit https://groups.google.com/d/
> topic/cherrypy-users/-HvSQC0Lmas/unsubscribe.
> To unsubscribe from this group and all its topics, 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.
>

-- 
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.