Simple RESTful API - make a patch

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
13 messages Options
Reply | Threaded
Open this post in threaded view
|

Simple RESTful API - make a patch

Kearney Taaffe
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 [hidden email].
To post to this group, send email to [hidden email].
Visit this group at https://groups.google.com/group/cherrypy-users.
For more options, visit https://groups.google.com/d/optout.
Reply | Threaded
Open this post in threaded view
|

Re: Simple RESTful API - make a patch

Tim Roberts
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'

I've never used the JSON tool, but I can't help but notice this:

    cherrypy.response.headers["Access-Control-Allow-Methods"] = (
       
'GET, POST, PUT, DELETE, OPTIONS'
   
)
...
    def OPTIONS(self):
       
return 'Allow: DELETE, GET, HEAD, OPTIONS, POST, PUT'

Neither of those lists include PATCH.
-- 
Tim Roberts, [hidden email]
Providenza & Boekelheide, Inc.

--
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 [hidden email].
To post to this group, send email to [hidden email].
Visit this group at https://groups.google.com/group/cherrypy-users.
For more options, visit https://groups.google.com/d/optout.
Reply | Threaded
Open this post in threaded view
|

Re: Simple RESTful API - make a patch

Kearney Taaffe
@Tim, unfortunately, adding the PATCH to the response headers and the OPTIONS function didn't do anything. I'm still getting the error

line 143, in PATCH
data = cherrypy.request.json

Here's the data I'm sending in for the PATCH request:

PATCH /api/users/123-123-123 HTTP/1.1
Content-Type: application/json; charset=utf-8
Host: localhost:8080
Connection: close
User-Agent: Paw/3.0.12 (Macintosh; OS X/10.12.1) GCDHTTPRequest
Content-Length: 21


{"last_name":"Jones"}


On Thursday, November 10, 2016 at 11:50:14 AM UTC-6, Tim Roberts wrote:
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'

I've never used the JSON tool, but I can't help but notice this:

    cherrypy.response.headers["Access-Control-Allow-Methods"] = (
       
'GET, POST, PUT, DELETE, OPTIONS'
   
)
...
    def OPTIONS(self):
       
return 'Allow: DELETE, GET, HEAD, OPTIONS, POST, PUT'

Neither of those lists include PATCH.
-- 
Tim Roberts, <a href="javascript:" target="_blank" gdf-obfuscated-mailto="39FogSCDAgAJ" rel="nofollow" onmousedown="this.href=&#39;javascript:&#39;;return true;" onclick="this.href=&#39;javascript:&#39;;return true;">ti...@...
Providenza & Boekelheide, Inc.

--
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 [hidden email].
To post to this group, send email to [hidden email].
Visit this group at https://groups.google.com/group/cherrypy-users.
For more options, visit https://groups.google.com/d/optout.
Reply | Threaded
Open this post in threaded view
|

Re: Simple RESTful API - make a patch

Joseph S. Tate
In reply to this post by Kearney Taaffe
When I try to reproduce via "telnet localhost 8080", the body line gets pasted to my prompt, so cherrypy is terminating the request before the body is sent. So it's likely a configuration error of some kind.

I'm going to pursue that for a bit, but wanted to report that before you go diving into something in the request handler itself.


On Thu, Nov 10, 2016 at 11:10 AM Kearney Taaffe 
<snipped>

--
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 [hidden email].
To post to this group, send email to [hidden email].
Visit this group at https://groups.google.com/group/cherrypy-users.
For more options, visit https://groups.google.com/d/optout.
Reply | Threaded
Open this post in threaded view
|

Re: Simple RESTful API - make a patch

Joseph S. Tate
It looks like there's at least one bug in CherryPy. _cprequest.py
needs to be updated to look like this: 

    methods_with_bodies = ('POST', 'PUT', 'PATCH')

But then I'm getting an error on the JSON document. I hope this is enough to help you get going.

If you have time to submit a pull request with this change (and hopefully a test), I'd appreciate it.

On Thu, Nov 10, 2016 at 1:51 PM Joseph S. Tate <[hidden email]> wrote:
When I try to reproduce via "telnet localhost 8080", the body line gets pasted to my prompt, so cherrypy is terminating the request before the body is sent. So it's likely a configuration error of some kind.

I'm going to pursue that for a bit, but wanted to report that before you go diving into something in the request handler itself.


On Thu, Nov 10, 2016 at 11:10 AM Kearney Taaffe 
<snipped>

--
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 [hidden email].
To post to this group, send email to [hidden email].
Visit this group at https://groups.google.com/group/cherrypy-users.
For more options, visit https://groups.google.com/d/optout.
Reply | Threaded
Open this post in threaded view
|

Re: Simple RESTful API - make a patch

Kearney Taaffe
@Josheph

That fixed it!

I changed my def PATHCH method to the following:

    @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


       
print('HTTP BODY: ' + str(data))
       
       
# result = self.userService.updateUser(data, kwargs['uuid'])
        result
= {
           
'status' : 'patching user ({})'.format(kwargs['uuid'])
       
}


       
return result


And, that printed both the body and the route parameter!

So, what do I need to do next? Submit a bug report? Branch, and ask my branch to be merged into the master branch?

Thanks for all your help! I super appreciate it! 
 

On Thursday, November 10, 2016 at 1:22:13 PM UTC-6, Joseph Tate wrote:
It looks like there's at least one bug in CherryPy. _cprequest.py
needs to be updated to look like this: 

    methods_with_bodies = ('POST', 'PUT', 'PATCH')

But then I'm getting an error on the JSON document. I hope this is enough to help you get going.

If you have time to submit a pull request with this change (and hopefully a test), I'd appreciate it.

On Thu, Nov 10, 2016 at 1:51 PM Joseph S. Tate <<a href="javascript:" target="_blank" gdf-obfuscated-mailto="oAA7aiWIAgAJ" rel="nofollow" onmousedown="this.href=&#39;javascript:&#39;;return true;" onclick="this.href=&#39;javascript:&#39;;return true;">dragon...@...> wrote:
When I try to reproduce via "telnet localhost 8080", the body line gets pasted to my prompt, so cherrypy is terminating the request before the body is sent. So it's likely a configuration error of some kind.

I'm going to pursue that for a bit, but wanted to report that before you go diving into something in the request handler itself.


On Thu, Nov 10, 2016 at 11:10 AM Kearney Taaffe 
<snipped>

--
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 [hidden email].
To post to this group, send email to [hidden email].
Visit this group at https://groups.google.com/group/cherrypy-users.
For more options, visit https://groups.google.com/d/optout.
Reply | Threaded
Open this post in threaded view
|

Re: Simple RESTful API - make a patch

Kearney Taaffe
In reply to this post by Kearney Taaffe
@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.


   
# <a href="http://cherrypy.readthedocs.org/en/3.2.6/_sources/progguide/REST.txt" target="_blank" rel="nofollow" onmousedown="this.href=&#39;http://www.google.com/url?q\x3dhttp%3A%2F%2Fcherrypy.readthedocs.org%2Fen%2F3.2.6%2F_sources%2Fprogguide%2FREST.txt\x26sa\x3dD\x26sntz\x3d1\x26usg\x3dAFQjCNGH6t5sMjBWMsLsv71-DAQql7uXiA&#39;;return true;" onclick="this.href=&#39;http://www.google.com/url?q\x3dhttp%3A%2F%2Fcherrypy.readthedocs.org%2Fen%2F3.2.6%2F_sources%2Fprogguide%2FREST.txt\x26sa\x3dD\x26sntz\x3d1\x26usg\x3dAFQjCNGH6t5sMjBWMsLsv71-DAQql7uXiA&#39;;return true;">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 <a href="http://example.com/api/users/uuid" target="_blank" rel="nofollow" onmousedown="this.href=&#39;http://www.google.com/url?q\x3dhttp%3A%2F%2Fexample.com%2Fapi%2Fusers%2Fuuid\x26sa\x3dD\x26sntz\x3d1\x26usg\x3dAFQjCNF-pUSBdgh1kcGrO8YUbdpS3SZAzA&#39;;return true;" onclick="this.href=&#39;http://www.google.com/url?q\x3dhttp%3A%2F%2Fexample.com%2Fapi%2Fusers%2Fuuid\x26sa\x3dD\x26sntz\x3d1\x26usg\x3dAFQjCNF-pUSBdgh1kcGrO8YUbdpS3SZAzA&#39;;return true;">http://example.com/api/users/uuid
    and the uuid will be made available to the routes like the user input
    <a href="http://example.com/api/users?uuid=uuid" target="_blank" rel="nofollow" onmousedown="this.href=&#39;http://www.google.com/url?q\x3dhttp%3A%2F%2Fexample.com%2Fapi%2Fusers%3Fuuid%3Duuid\x26sa\x3dD\x26sntz\x3d1\x26usg\x3dAFQjCNGV39nZssVhHVrDdBFTiYRuXwnIhQ&#39;;return true;" onclick="this.href=&#39;http://www.google.com/url?q\x3dhttp%3A%2F%2Fexample.com%2Fapi%2Fusers%3Fuuid%3Duuid\x26sa\x3dD\x26sntz\x3d1\x26usg\x3dAFQjCNGV39nZssVhHVrDdBFTiYRuXwnIhQ&#39;;return true;">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 [hidden email].
To post to this group, send email to [hidden email].
Visit this group at https://groups.google.com/group/cherrypy-users.
For more options, visit https://groups.google.com/d/optout.
Reply | Threaded
Open this post in threaded view
|

Re: Simple RESTful API - make a patch

Joseph S. Tate
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 <[hidden email]> 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 [hidden email].
To post to this group, send email to [hidden email].
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 [hidden email].
To post to this group, send email to [hidden email].
Visit this group at https://groups.google.com/group/cherrypy-users.
For more options, visit https://groups.google.com/d/optout.
Reply | Threaded
Open this post in threaded view
|

Re: Simple RESTful API - make a patch

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


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 <[hidden email]> 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 <[hidden email]> 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 [hidden email].
To post to this group, send email to [hidden email].
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 [hidden email].
To post to this group, send email to [hidden email].
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 [hidden email].
To post to this group, send email to [hidden email].
Visit this group at https://groups.google.com/group/cherrypy-users.
For more options, visit https://groups.google.com/d/optout.
Reply | Threaded
Open this post in threaded view
|

Re: Simple RESTful API - make a patch

Joseph S. Tate
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 <[hidden email]> 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.


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
<a href="tel:(817)%20239-2783" value="+18172392783" class="gmail_msg" target="_blank">(817) 239-2783

On Tue, Nov 15, 2016 at 12:43 AM, Joseph S. Tate <[hidden email]> 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 <[hidden email]> 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 [hidden email].
To post to this group, send email to [hidden email].
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 [hidden email].

To post to this group, send email to [hidden email].
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 [hidden email].
To post to this group, send email to [hidden email].
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 [hidden email].
To post to this group, send email to [hidden email].
Visit this group at https://groups.google.com/group/cherrypy-users.
For more options, visit https://groups.google.com/d/optout.
Reply | Threaded
Open this post in threaded view
|

Re: Simple RESTful API - make a patch

Joseph S. Tate
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 <[hidden email]> 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 <[hidden email]> 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.


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
<a href="tel:(817)%20239-2783" value="+18172392783" class="gmail_msg" target="_blank">(817) 239-2783

On Tue, Nov 15, 2016 at 12:43 AM, Joseph S. Tate <[hidden email]> 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 <[hidden email]> 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 [hidden email].
To post to this group, send email to [hidden email].
Visit this group at https://groups.google.com/group/cherrypy-users.
For more options, visit https://groups.google.com/d/optout.
Reply | Threaded
Open this post in threaded view
|

Re: Simple RESTful API - make a patch

Tim Roberts
In reply to this post by Kearney Taaffe
Kearney Taaffe wrote:

Man, I feel stupid. I work with Git at work, but, all we do is just branch and merge.

Don't feel stupid.  Git is approaching sendmail in scope and complexity.  Everything that is computable can be done with git, but someone has to tell you the spelling.
-- 
Tim Roberts, [hidden email]
Providenza & Boekelheide, Inc.

--
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 [hidden email].
To post to this group, send email to [hidden email].
Visit this group at https://groups.google.com/group/cherrypy-users.
For more options, visit https://groups.google.com/d/optout.
Reply | Threaded
Open this post in threaded view
|

Re: Simple RESTful API - make a patch

Kearney Taaffe
In reply to this post by Joseph S. Tate
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 <<a href="javascript:" target="_blank" gdf-obfuscated-mailto="Y_NU3r0cBAAJ" rel="nofollow" onmousedown="this.href=&#39;javascript:&#39;;return true;" onclick="this.href=&#39;javascript:&#39;;return true;">dragon...@...> wrote:
you're almost there. Go to <a href="https://github.com/cherrypy/cherrypy/" target="_blank" rel="nofollow" onmousedown="this.href=&#39;https://www.google.com/url?q\x3dhttps%3A%2F%2Fgithub.com%2Fcherrypy%2Fcherrypy%2F\x26sa\x3dD\x26sntz\x3d1\x26usg\x3dAFQjCNENsfbXxOekur0yqWSkQ_nY3Y1wlA&#39;;return true;" onclick="this.href=&#39;https://www.google.com/url?q\x3dhttps%3A%2F%2Fgithub.com%2Fcherrypy%2Fcherrypy%2F\x26sa\x3dD\x26sntz\x3d1\x26usg\x3dAFQjCNENsfbXxOekur0yqWSkQ_nY3Y1wlA&#39;;return true;">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 <<a href="javascript:" target="_blank" gdf-obfuscated-mailto="Y_NU3r0cBAAJ" rel="nofollow" onmousedown="this.href=&#39;javascript:&#39;;return true;" onclick="this.href=&#39;javascript:&#39;;return true;">kearney...@...> 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.

<a href="https://github.com/gitKearney/cherrypy/tree/feature/1516_fix_get_PATCH_body" target="_blank" rel="nofollow" onmousedown="this.href=&#39;https://www.google.com/url?q\x3dhttps%3A%2F%2Fgithub.com%2FgitKearney%2Fcherrypy%2Ftree%2Ffeature%2F1516_fix_get_PATCH_body\x26sa\x3dD\x26sntz\x3d1\x26usg\x3dAFQjCNGy2TxhN0Lw1a-e-D4oaREsuqmUZw&#39;;return true;" onclick="this.href=&#39;https://www.google.com/url?q\x3dhttps%3A%2F%2Fgithub.com%2FgitKearney%2Fcherrypy%2Ftree%2Ffeature%2F1516_fix_get_PATCH_body\x26sa\x3dD\x26sntz\x3d1\x26usg\x3dAFQjCNGy2TxhN0Lw1a-e-D4oaREsuqmUZw&#39;;return true;">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

On Tue, Nov 15, 2016 at 12:43 AM, Joseph S. Tate <<a href="javascript:" target="_blank" gdf-obfuscated-mailto="Y_NU3r0cBAAJ" rel="nofollow" onmousedown="this.href=&#39;javascript:&#39;;return true;" onclick="this.href=&#39;javascript:&#39;;return true;">dragon...@...> 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 <<a href="javascript:" target="_blank" gdf-obfuscated-mailto="Y_NU3r0cBAAJ" rel="nofollow" onmousedown="this.href=&#39;javascript:&#39;;return true;" onclick="this.href=&#39;javascript:&#39;;return true;">kearney...@...> 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.


   
# <a href="http://cherrypy.readthedocs.org/en/3.2.6/_sources/progguide/REST.txt" rel="nofollow" target="_blank" onmousedown="this.href=&#39;http://www.google.com/url?q\x3dhttp%3A%2F%2Fcherrypy.readthedocs.org%2Fen%2F3.2.6%2F_sources%2Fprogguide%2FREST.txt\x26sa\x3dD\x26sntz\x3d1\x26usg\x3dAFQjCNGH6t5sMjBWMsLsv71-DAQql7uXiA&#39;;return true;" onclick="this.href=&#39;http://www.google.com/url?q\x3dhttp%3A%2F%2Fcherrypy.readthedocs.org%2Fen%2F3.2.6%2F_sources%2Fprogguide%2FREST.txt\x26sa\x3dD\x26sntz\x3d1\x26usg\x3dAFQjCNGH6t5sMjBWMsLsv71-DAQql7uXiA&#39;;return true;">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 <a href="http://example.com/api/users/uuid" rel="nofollow" target="_blank" onmousedown="this.href=&#39;http://www.google.com/url?q\x3dhttp%3A%2F%2Fexample.com%2Fapi%2Fusers%2Fuuid\x26sa\x3dD\x26sntz\x3d1\x26usg\x3dAFQjCNF-pUSBdgh1kcGrO8YUbdpS3SZAzA&#39;;return true;" onclick="this.href=&#39;http://www.google.com/url?q\x3dhttp%3A%2F%2Fexample.com%2Fapi%2Fusers%2Fuuid\x26sa\x3dD\x26sntz\x3d1\x26usg\x3dAFQjCNF-pUSBdgh1kcGrO8YUbdpS3SZAzA&#39;;return true;">http://example.com/api/users/uuid
    and the uuid will be made available to the routes like the user input
    <a href="http://example.com/api/users?uuid=uuid" rel="nofollow" target="_blank" onmousedown="this.href=&#39;http://www.google.com/url?q\x3dhttp%3A%2F%2Fexample.com%2Fapi%2Fusers%3Fuuid%3Duuid\x26sa\x3dD\x26sntz\x3d1\x26usg\x3dAFQjCNGV39nZssVhHVrDdBFTiYRuXwnIhQ&#39;;return true;" onclick="this.href=&#39;http://www.google.com/url?q\x3dhttp%3A%2F%2Fexample.com%2Fapi%2Fusers%3Fuuid%3Duuid\x26sa\x3dD\x26sntz\x3d1\x26usg\x3dAFQjCNGV39nZssVhHVrDdBFTiYRuXwnIhQ&#39;;return true;">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 [hidden email].
To post to this group, send email to [hidden email].
Visit this group at https://groups.google.com/group/cherrypy-users.
For more options, visit https://groups.google.com/d/optout.