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.