cherrypy and ajax calls

classic Classic list List threaded Threaded
10 messages Options
Reply | Threaded
Open this post in threaded view
|

cherrypy and ajax calls

Keith Brown
i have a page which has several divs (10). Each div updates every 10 seconds where the source is a page.

It seems when more than 5 users come to the site I see my python process at 100%. Thats because its 10*5 requests in parallel. I suppose I can increase the threshold from 10 seconds to 60 seconds but it will lose the interactivity of the site. What are some techniques I should be using to resolve this scaling issue.

Should I consider looking at node.js ? Never used it BTW.



--
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 http://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: cherrypy and ajax calls

Anders Langworthy
On Fri, Oct 03, 2014 at 12:14:14PM -0700, Keith Brown wrote:

> i have a page which has several divs (10). Each div updates every 10
> seconds where the source is a page.
>
> It seems when more than 5 users come to the site I see my python process at
> 100%. Thats because its 10*5 requests in parallel. I suppose I can increase
> the threshold from 10 seconds to 60 seconds but it will lose the
> interactivity of the site. What are some techniques I should be using to
> resolve this scaling issue.
>
> Should I consider looking at node.js ? Never used it BTW.

If you aren't already doing it, stagger the requests so that they aren't
so parallel -- just add a random offset to each client's poll time time
so that everybody isn't pulling at 12:00, 12:10, etc.

--
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 http://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: cherrypy and ajax calls

Andrew Maizels
In reply to this post by Keith Brown
Sounds like you're running CherryPy's built-in server.  While it is multi-threaded, it can only effectively use one CPU core (due to the way Python is designed).

You can get around this easily using a multi-process server like Green Unicorn.  (http://gunicorn.org/)  Green Unicorn is a server for WSGI apps, and a CherryPy application is automatically a WSGI app, so all you need to do is write a little wrapper and hand it over to Green Unicorn to handle the scaling.  uWSGI is another server designed for this, but it's a bit more complicated to get started with; Green Unicorn is relatively simple.

I'd also suggest taking a look at your code and seeing if you can simplify it.  50 requests every 10 seconds isn't a lot, even for a single CherryPy process.  Are you doing a lot of complex database queries or something like that?

On Sat, Oct 4, 2014 at 5:14 AM, Keith Brown <[hidden email]> wrote:
i have a page which has several divs (10). Each div updates every 10 seconds where the source is a page.

It seems when more than 5 users come to the site I see my python process at 100%. Thats because its 10*5 requests in parallel. I suppose I can increase the threshold from 10 seconds to 60 seconds but it will lose the interactivity of the site. What are some techniques I should be using to resolve this scaling issue.

Should I consider looking at node.js ? Never used it BTW.



--
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 http://groups.google.com/group/cherrypy-users.
For more options, visit https://groups.google.com/d/optout.



--
Ahh... We are all heroes; you and Boo and I.
Hamsters and rangers everywhere, rejoice!
Read Peopleware! http://www.amazon.com/Peopleware-Productive-Projects-Teams-Second/dp/0932633439

--
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 http://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: cherrypy and ajax calls

Eric Larson-5

Andrew Maizels writes:

> Sounds like you're running CherryPy's built-in server.  While it is
> multi-threaded, it can only effectively use one CPU core (due to the way
> Python is designed).
>
> You can get around this easily using a multi-process server like Green
> Unicorn.  (http://gunicorn.org/)  Green Unicorn is a server for WSGI apps,
> and a CherryPy application is automatically a WSGI app, so all you need to
> do is write a little wrapper and hand it over to Green Unicorn to handle
> the scaling.  uWSGI is another server designed for this, but it's a bit
> more complicated to get started with; Green Unicorn is relatively simple.
>

While Andrew has a point regarding other servers, gunicorn is not
actually multiprocess, but uses "green" threads via gevent. What this
means is that operations that do I/O are done within a loop where other
operations can occur while waiting for the I/O to complete.

For example, if your page was using websockets where a connection is
maintained for each div, using gunicorn might be helpful. The reason for
this is because while the content for one div is being sent, the loop
can switch to other code to do work in the mean time.

But, since you said your CPU is maxed out, I/O may not be your bottleneck.

> I'd also suggest taking a look at your code and seeing if you can simplify
> it.  50 requests every 10 seconds isn't a lot, even for a single CherryPy
> process.  Are you doing a lot of complex database queries or something like
> that?
>

This is a good question to answer :)

Along similar lines, you can configure your cherrypy server to use more
threads:

  cherrypy.config.update({'server.thread_pool': 30}))

 That might be beneficial to use a larger thread pool as you know you
have X number of divs that will be using a thread per connection every
10 seconds. That said, if you are CPU bound, then you will need to
consider how you can use more cores.

The suggestion to stagger the updates is also an excellent idea IMO.


Eric

--
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 http://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: cherrypy and ajax calls

Andrew Maizels
On Sat, Oct 4, 2014 at 4:49 PM, Eric Larson <[hidden email]> wrote:

While Andrew has a point regarding other servers, gunicorn is not
actually multiprocess, but uses "green" threads via gevent. What this
means is that operations that do I/O are done within a loop where other
operations can occur while waiting for the I/O to complete.

In fact, it's both.  You can configure the number of separate processes, and also the number of threads per process:  You can also specify gevent or synchronous workers.

--
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 http://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: cherrypy and ajax calls

Keith Brown
Thanks for the good advice and responses.

Basically, I am doing this for my divs

@cherrypy.expose
def totalUsers:
  return str(len(open('/etc/passwd').readlines()))

@cherrypy.expose
def totalUsers:
  return str(len(open('/data/file').readlines()))


This is an easy example but some of my examples I am doing some data merging with pandas.

I tried using celery to do my work in a schedule and dump the output to a file. Lot of my output is in XML so I have some functions which need to parse the XML and put it in a pandas dataframe. 








On Saturday, October 4, 2014 5:11:27 AM UTC-4, Pixy Misa wrote:
On Sat, Oct 4, 2014 at 4:49 PM, Eric Larson <<a href="javascript:" target="_blank" gdf-obfuscated-mailto="f_OwIjIhmQ4J" onmousedown="this.href='javascript:';return true;" onclick="this.href='javascript:';return true;">er...@...> wrote:

While Andrew has a point regarding other servers, gunicorn is not
actually multiprocess, but uses "green" threads via gevent. What this
means is that operations that do I/O are done within a loop where other
operations can occur while waiting for the I/O to complete.

In fact, it's both.  You can configure the number of separate processes, and also the number of threads per process:  You can also specify gevent or synchronous workers.

<a href="http://docs.gunicorn.org/en/latest/settings.html#worker-processes" target="_blank" onmousedown="this.href='http://www.google.com/url?q\75http%3A%2F%2Fdocs.gunicorn.org%2Fen%2Flatest%2Fsettings.html%23worker-processes\46sa\75D\46sntz\0751\46usg\75AFQjCNHNuEES1goIX8GX3X8VUD2_eOR12w';return true;" onclick="this.href='http://www.google.com/url?q\75http%3A%2F%2Fdocs.gunicorn.org%2Fen%2Flatest%2Fsettings.html%23worker-processes\46sa\75D\46sntz\0751\46usg\75AFQjCNHNuEES1goIX8GX3X8VUD2_eOR12w';return true;">http://docs.gunicorn.org/en/latest/settings.html#worker-processes 

--
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 http://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: cherrypy and ajax calls

Andrew Maizels
Hmm.

So, are all the users looking at the same information?  If you can cache the results, that will make subsequent calls very fast.

You could either use something like Nginx in front of CherryPy, or have a caching function inside your CherryPy app, something like this:


import time
import cherrypy

cache = {}
empty = {'expiry': 0, 'result': None}


def getCache(key):
    return cache.get(key, empty)['result']


def setCache(key, result, ttl=10):
    cache[key] = {'result': result, 'expiry': time.time()+ttl}
    return result
    

@cherrypy.expose
def totalUsers():
    result = getCache('totalUsers')
    return result if result else setCache('totalUsers', str(len(open('/etc/passwd').readlines())))


(Note that I've only syntax-checked that code, not actually tried it...)


On Sat, Oct 4, 2014 at 9:52 PM, Keith Brown <[hidden email]> wrote:
Thanks for the good advice and responses.

Basically, I am doing this for my divs

@cherrypy.expose
def totalUsers:
  return str(len(open('/etc/passwd').readlines()))

@cherrypy.expose
def totalUsers:
  return str(len(open('/data/file').readlines()))


This is an easy example but some of my examples I am doing some data merging with pandas.

I tried using celery to do my work in a schedule and dump the output to a file. Lot of my output is in XML so I have some functions which need to parse the XML and put it in a pandas dataframe. 








On Saturday, October 4, 2014 5:11:27 AM UTC-4, Pixy Misa wrote:
On Sat, Oct 4, 2014 at 4:49 PM, Eric Larson <[hidden email]> wrote:

While Andrew has a point regarding other servers, gunicorn is not
actually multiprocess, but uses "green" threads via gevent. What this
means is that operations that do I/O are done within a loop where other
operations can occur while waiting for the I/O to complete.

In fact, it's both.  You can configure the number of separate processes, and also the number of threads per process:  You can also specify gevent or synchronous workers.

--
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 http://groups.google.com/group/cherrypy-users.
For more options, visit https://groups.google.com/d/optout.



--
Ahh... We are all heroes; you and Boo and I.
Hamsters and rangers everywhere, rejoice!
Read Peopleware! http://www.amazon.com/Peopleware-Productive-Projects-Teams-Second/dp/0932633439

--
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 http://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: cherrypy and ajax calls

Andrew Maizels
Whoops, change getCache to:

def getCache(key):
    data = cache.get(key, empty)
    return data['result'] if data['expiry'] > time.time() else None


Trying to simplify the code and I forgot about expiring the data from the cache...

On Sat, Oct 4, 2014 at 10:21 PM, Andrew Maizels <[hidden email]> wrote:
Hmm.

So, are all the users looking at the same information?  If you can cache the results, that will make subsequent calls very fast.

You could either use something like Nginx in front of CherryPy, or have a caching function inside your CherryPy app, something like this:


import time
import cherrypy

cache = {}
empty = {'expiry': 0, 'result': None}


def getCache(key):
    return cache.get(key, empty)['result']


def setCache(key, result, ttl=10):
    cache[key] = {'result': result, 'expiry': time.time()+ttl}
    return result
    

@cherrypy.expose
def totalUsers():
    result = getCache('totalUsers')
    return result if result else setCache('totalUsers', str(len(open('/etc/passwd').readlines())))


(Note that I've only syntax-checked that code, not actually tried it...)


On Sat, Oct 4, 2014 at 9:52 PM, Keith Brown <[hidden email]> wrote:
Thanks for the good advice and responses.

Basically, I am doing this for my divs

@cherrypy.expose
def totalUsers:
  return str(len(open('/etc/passwd').readlines()))

@cherrypy.expose
def totalUsers:
  return str(len(open('/data/file').readlines()))


This is an easy example but some of my examples I am doing some data merging with pandas.

I tried using celery to do my work in a schedule and dump the output to a file. Lot of my output is in XML so I have some functions which need to parse the XML and put it in a pandas dataframe. 








On Saturday, October 4, 2014 5:11:27 AM UTC-4, Pixy Misa wrote:
On Sat, Oct 4, 2014 at 4:49 PM, Eric Larson <[hidden email]> wrote:

While Andrew has a point regarding other servers, gunicorn is not
actually multiprocess, but uses "green" threads via gevent. What this
means is that operations that do I/O are done within a loop where other
operations can occur while waiting for the I/O to complete.

In fact, it's both.  You can configure the number of separate processes, and also the number of threads per process:  You can also specify gevent or synchronous workers.

--
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 http://groups.google.com/group/cherrypy-users.
For more options, visit https://groups.google.com/d/optout.



--
Ahh... We are all heroes; you and Boo and I.
Hamsters and rangers everywhere, rejoice!
Read Peopleware! http://www.amazon.com/Peopleware-Productive-Projects-Teams-Second/dp/0932633439



--
Ahh... We are all heroes; you and Boo and I.
Hamsters and rangers everywhere, rejoice!
Read Peopleware! http://www.amazon.com/Peopleware-Productive-Projects-Teams-Second/dp/0932633439

--
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 http://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: cherrypy and ajax calls

Keith Brown
In reply to this post by Andrew Maizels

Yes, the users will be seeing the same data.

The application is a dashboard. 

I am going to give your technique a try and let you know...


On Saturday, October 4, 2014 8:21:45 AM UTC-4, Pixy Misa wrote:
Hmm.

So, are all the users looking at the same information?  If you can cache the results, that will make subsequent calls very fast.

You could either use something like Nginx in front of CherryPy, or have a caching function inside your CherryPy app, something like this:


import time
import cherrypy

cache = {}
empty = {'expiry': 0, 'result': None}


def getCache(key):
    return cache.get(key, empty)['result']


def setCache(key, result, ttl=10):
    cache[key] = {'result': result, 'expiry': time.time()+ttl}
    return result
    

@cherrypy.expose
def totalUsers():
    result = getCache('totalUsers')
    return result if result else setCache('totalUsers', str(len(open('/etc/passwd').readlines())))


(Note that I've only syntax-checked that code, not actually tried it...)


On Sat, Oct 4, 2014 at 9:52 PM, Keith Brown <<a href="javascript:" target="_blank" gdf-obfuscated-mailto="zhvjAvkR2ZcJ" onmousedown="this.href='javascript:';return true;" onclick="this.href='javascript:';return true;">keit...@...> wrote:
Thanks for the good advice and responses.

Basically, I am doing this for my divs

@cherrypy.expose
def totalUsers:
  return str(len(open('/etc/passwd').readlines()))

@cherrypy.expose
def totalUsers:
  return str(len(open('/data/file').readlines()))


This is an easy example but some of my examples I am doing some data merging with pandas.

I tried using celery to do my work in a schedule and dump the output to a file. Lot of my output is in XML so I have some functions which need to parse the XML and put it in a pandas dataframe. 








On Saturday, October 4, 2014 5:11:27 AM UTC-4, Pixy Misa wrote:
On Sat, Oct 4, 2014 at 4:49 PM, Eric Larson <[hidden email]> wrote:

While Andrew has a point regarding other servers, gunicorn is not
actually multiprocess, but uses "green" threads via gevent. What this
means is that operations that do I/O are done within a loop where other
operations can occur while waiting for the I/O to complete.

In fact, it's both.  You can configure the number of separate processes, and also the number of threads per process:  You can also specify gevent or synchronous workers.

<a href="http://docs.gunicorn.org/en/latest/settings.html#worker-processes" target="_blank" onmousedown="this.href='http://www.google.com/url?q\75http%3A%2F%2Fdocs.gunicorn.org%2Fen%2Flatest%2Fsettings.html%23worker-processes\46sa\75D\46sntz\0751\46usg\75AFQjCNHNuEES1goIX8GX3X8VUD2_eOR12w';return true;" onclick="this.href='http://www.google.com/url?q\75http%3A%2F%2Fdocs.gunicorn.org%2Fen%2Flatest%2Fsettings.html%23worker-processes\46sa\75D\46sntz\0751\46usg\75AFQjCNHNuEES1goIX8GX3X8VUD2_eOR12w';return true;">http://docs.gunicorn.org/en/latest/settings.html#worker-processes 

--
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 <a href="javascript:" target="_blank" gdf-obfuscated-mailto="zhvjAvkR2ZcJ" onmousedown="this.href='javascript:';return true;" onclick="this.href='javascript:';return true;">cherrypy-user...@googlegroups.com.
To post to this group, send email to <a href="javascript:" target="_blank" gdf-obfuscated-mailto="zhvjAvkR2ZcJ" onmousedown="this.href='javascript:';return true;" onclick="this.href='javascript:';return true;">cherryp...@googlegroups.com.
Visit this group at <a href="http://groups.google.com/group/cherrypy-users" target="_blank" onmousedown="this.href='http://groups.google.com/group/cherrypy-users';return true;" onclick="this.href='http://groups.google.com/group/cherrypy-users';return true;">http://groups.google.com/group/cherrypy-users.
For more options, visit <a href="https://groups.google.com/d/optout" target="_blank" onmousedown="this.href='https://groups.google.com/d/optout';return true;" onclick="this.href='https://groups.google.com/d/optout';return true;">https://groups.google.com/d/optout.



--
Ahh... We are all heroes; you and Boo and I.
Hamsters and rangers everywhere, rejoice!
Read Peopleware! <a href="http://www.amazon.com/Peopleware-Productive-Projects-Teams-Second/dp/0932633439" target="_blank" onmousedown="this.href='http://www.google.com/url?q\75http%3A%2F%2Fwww.amazon.com%2FPeopleware-Productive-Projects-Teams-Second%2Fdp%2F0932633439\46sa\75D\46sntz\0751\46usg\75AFQjCNGhnhdXr8L8VXAXvpeAXSgo1dyCGw';return true;" onclick="this.href='http://www.google.com/url?q\75http%3A%2F%2Fwww.amazon.com%2FPeopleware-Productive-Projects-Teams-Second%2Fdp%2F0932633439\46sa\75D\46sntz\0751\46usg\75AFQjCNGhnhdXr8L8VXAXvpeAXSgo1dyCGw';return true;">http://www.amazon.com/Peopleware-Productive-Projects-Teams-Second/dp/0932633439

--
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 http://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: cherrypy and ajax calls

Keith Brown
pixa

interesting technique. this works. you simplified it quite a lot :p


On Saturday, October 4, 2014 11:22:22 AM UTC-4, Keith Brown wrote:

Yes, the users will be seeing the same data.

The application is a dashboard. 

I am going to give your technique a try and let you know...


On Saturday, October 4, 2014 8:21:45 AM UTC-4, Pixy Misa wrote:
Hmm.

So, are all the users looking at the same information?  If you can cache the results, that will make subsequent calls very fast.

You could either use something like Nginx in front of CherryPy, or have a caching function inside your CherryPy app, something like this:


import time
import cherrypy

cache = {}
empty = {'expiry': 0, 'result': None}


def getCache(key):
    return cache.get(key, empty)['result']


def setCache(key, result, ttl=10):
    cache[key] = {'result': result, 'expiry': time.time()+ttl}
    return result
    

@cherrypy.expose
def totalUsers():
    result = getCache('totalUsers')
    return result if result else setCache('totalUsers', str(len(open('/etc/passwd').readlines())))


(Note that I've only syntax-checked that code, not actually tried it...)


On Sat, Oct 4, 2014 at 9:52 PM, Keith Brown <[hidden email]> wrote:
Thanks for the good advice and responses.

Basically, I am doing this for my divs

@cherrypy.expose
def totalUsers:
  return str(len(open('/etc/passwd').readlines()))

@cherrypy.expose
def totalUsers:
  return str(len(open('/data/file').readlines()))


This is an easy example but some of my examples I am doing some data merging with pandas.

I tried using celery to do my work in a schedule and dump the output to a file. Lot of my output is in XML so I have some functions which need to parse the XML and put it in a pandas dataframe. 








On Saturday, October 4, 2014 5:11:27 AM UTC-4, Pixy Misa wrote:
On Sat, Oct 4, 2014 at 4:49 PM, Eric Larson <[hidden email]> wrote:

While Andrew has a point regarding other servers, gunicorn is not
actually multiprocess, but uses "green" threads via gevent. What this
means is that operations that do I/O are done within a loop where other
operations can occur while waiting for the I/O to complete.

In fact, it's both.  You can configure the number of separate processes, and also the number of threads per process:  You can also specify gevent or synchronous workers.

<a href="http://docs.gunicorn.org/en/latest/settings.html#worker-processes" target="_blank" onmousedown="this.href='http://www.google.com/url?q\75http%3A%2F%2Fdocs.gunicorn.org%2Fen%2Flatest%2Fsettings.html%23worker-processes\46sa\75D\46sntz\0751\46usg\75AFQjCNHNuEES1goIX8GX3X8VUD2_eOR12w';return true;" onclick="this.href='http://www.google.com/url?q\75http%3A%2F%2Fdocs.gunicorn.org%2Fen%2Flatest%2Fsettings.html%23worker-processes\46sa\75D\46sntz\0751\46usg\75AFQjCNHNuEES1goIX8GX3X8VUD2_eOR12w';return true;">http://docs.gunicorn.org/en/latest/settings.html#worker-processes 

--
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-user...@googlegroups.com.
To post to this group, send email to [hidden email].
Visit this group at <a href="http://groups.google.com/group/cherrypy-users" target="_blank" onmousedown="this.href='http://groups.google.com/group/cherrypy-users';return true;" onclick="this.href='http://groups.google.com/group/cherrypy-users';return true;">http://groups.google.com/group/cherrypy-users.
For more options, visit <a href="https://groups.google.com/d/optout" target="_blank" onmousedown="this.href='https://groups.google.com/d/optout';return true;" onclick="this.href='https://groups.google.com/d/optout';return true;">https://groups.google.com/d/optout.



--
Ahh... We are all heroes; you and Boo and I.
Hamsters and rangers everywhere, rejoice!
Read Peopleware! <a href="http://www.amazon.com/Peopleware-Productive-Projects-Teams-Second/dp/0932633439" target="_blank" onmousedown="this.href='http://www.google.com/url?q\75http%3A%2F%2Fwww.amazon.com%2FPeopleware-Productive-Projects-Teams-Second%2Fdp%2F0932633439\46sa\75D\46sntz\0751\46usg\75AFQjCNGhnhdXr8L8VXAXvpeAXSgo1dyCGw';return true;" onclick="this.href='http://www.google.com/url?q\75http%3A%2F%2Fwww.amazon.com%2FPeopleware-Productive-Projects-Teams-Second%2Fdp%2F0932633439\46sa\75D\46sntz\0751\46usg\75AFQjCNGhnhdXr8L8VXAXvpeAXSgo1dyCGw';return true;">http://www.amazon.com/Peopleware-Productive-Projects-Teams-Second/dp/0932633439

--
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 http://groups.google.com/group/cherrypy-users.
For more options, visit https://groups.google.com/d/optout.