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.