Re: Re: Simple RESTful API - make a patch

Kearney Taaffe <[email protected]> Thu, 17 Nov 2016 09:26:50 -0800 (PST)
Newsgroups gmane.comp.python.cherrypy
Message-ID <[email protected]>
So, I *FINALLY* figured out how to create a test, and get it working :-D it 
took way to much time.

Can you take a look at my test to see if I'm doing it correctly? I'm 
unfamiliar with writing test cases, but I think the test is valid.

On and aside, I also found (from reading one of the test cases) that we can 
overwrite in the config the HTTP request types that accept a body

        appconf = {
            '/method': {
                'request.dispatch': cherrypy.dispatch.MethodDispatcher(),
                'request.methods_with_bodies': ('POST', 'PUT', 'PATCH')
            },
        }


        cherrypy.tree.mount(root, config=appconf) 


But, I still think the ticket is valid. HTTP PATCH requests should accept a 
body no matter what




On Tuesday, November 15, 2016 at 4:56:31 PM UTC-6, Joseph Tate wrote:
>
> Ahh. I see in the ticket that you're already working it. Thanks!
>
> On Tue, Nov 15, 2016 at 5:55 PM Joseph S. Tate <[email protected] 
> <javascript:>> wrote:
>
>> you're almost there. Go to https://github.com/cherrypy/cherrypy/ and 
>> click the "New Pull Request" button. Then click the "compare across forks" 
>> link. Then you can pick your repo and branch as the "head fork".
>>
>> On Tue, Nov 15, 2016 at 8:15 AM Kearney Taaffe <[email protected] 
>> <javascript:>> wrote:
>>
>>> 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] 
>>> <javascript:>> 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] 
>>>> <javascript:>> 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
>>>>>>
>>>>>

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