├── .coveragerc ├── .gitignore ├── .travis.yml ├── LICENSE ├── MANIFEST.in ├── README.md ├── appskel ├── autogen.sh ├── default │ ├── .gitignore │ ├── README.md │ ├── frontend │ │ ├── locale │ │ │ ├── es_ES │ │ │ │ └── LC_MESSAGES │ │ │ │ │ ├── modname.mo │ │ │ │ │ └── modname.po │ │ │ └── pt_BR │ │ │ │ └── LC_MESSAGES │ │ │ │ ├── modname.mo │ │ │ │ └── modname.po │ │ ├── static │ │ │ └── favicon.ico │ │ └── template │ │ │ ├── base.html │ │ │ ├── index.html │ │ │ └── post.html │ ├── modname.conf │ ├── modname │ │ ├── __init__.py │ │ ├── config.py │ │ ├── storage.py │ │ ├── utils.py │ │ ├── views.py │ │ └── web.py │ ├── scripts │ │ ├── cookie_secret.py │ │ ├── debian-init.d │ │ ├── debian-multicore-init.d │ │ └── localefix.py │ └── start.sh ├── foreman │ ├── .env │ ├── .gitignore │ ├── Procfile │ ├── README.md │ ├── frontend │ │ ├── locale │ │ │ ├── es_ES │ │ │ │ └── LC_MESSAGES │ │ │ │ │ ├── modname.mo │ │ │ │ │ └── modname.po │ │ │ └── pt_BR │ │ │ │ └── LC_MESSAGES │ │ │ │ ├── modname.mo │ │ │ │ └── modname.po │ │ ├── static │ │ │ └── favicon.ico │ │ └── template │ │ │ ├── base.html │ │ │ ├── index.html │ │ │ └── post.html │ ├── modname.conf │ ├── modname │ │ ├── __init__.py │ │ ├── config.py │ │ ├── main.py │ │ ├── utils.py │ │ ├── views.py │ │ └── web.py │ ├── requirements.txt │ └── scripts │ │ ├── cookie_secret.py │ │ └── localefix.py └── signup │ ├── .gitignore │ ├── README.md │ ├── frontend │ ├── locale │ │ ├── es_ES │ │ │ └── LC_MESSAGES │ │ │ │ ├── modname.mo │ │ │ │ └── modname.po │ │ └── pt_BR │ │ │ └── LC_MESSAGES │ │ │ ├── modname.mo │ │ │ └── modname.po │ ├── static │ │ ├── favicon.ico │ │ └── legal.txt │ └── template │ │ ├── account.html │ │ ├── admin.html │ │ ├── base.html │ │ ├── dashboard.html │ │ ├── error_all.html │ │ ├── index.html │ │ ├── passwd.html │ │ ├── passwd_email.html │ │ ├── passwd_email_subject.txt │ │ ├── passwd_ok.html │ │ ├── signin.html │ │ ├── signup.html │ │ ├── signup_email.html │ │ ├── signup_email_subject.txt │ │ └── signup_ok.html │ ├── modname.conf │ ├── modname.sql │ ├── modname │ ├── __init__.py │ ├── config.py │ ├── storage.py │ ├── txdbapi.py │ ├── utils.py │ ├── views.py │ └── web.py │ ├── scripts │ ├── cookie_secret.py │ ├── debian-init.d │ ├── debian-multicore-init.d │ └── localefix.py │ └── start.sh ├── cyclone ├── __init__.py ├── app.py ├── appskel_default.zip ├── appskel_foreman.zip ├── appskel_signup.zip ├── auth.py ├── bottle.py ├── escape.py ├── httpclient.py ├── httpserver.py ├── httputil.py ├── jsonrpc.py ├── locale.py ├── mail.py ├── options.py ├── redis.py ├── sqlite.py ├── sse.py ├── template.py ├── testing │ ├── __init__.py │ ├── client.py │ └── testcase.py ├── tests │ ├── __init__.py │ ├── test_app.py │ ├── test_auth.py │ ├── test_db.py │ ├── test_escape.py │ ├── test_httpclient.py │ ├── test_httpserver.py │ ├── test_httputil.py │ ├── test_mail.py │ ├── test_requirements.txt │ ├── test_template.py │ ├── test_testing.py │ ├── test_utils.py │ └── test_web.py ├── util.py ├── web.py ├── websocket.py └── xmlrpc.py ├── debian ├── changelog ├── compat ├── control ├── docs ├── postinst ├── postrm └── rules ├── demos ├── auth │ └── authdemo.py ├── bottle │ ├── README │ ├── bottledemo.py │ └── xmlrpc_client.py ├── chat │ ├── chatdemo.py │ ├── static │ │ ├── chat.css │ │ └── chat.js │ └── templates │ │ ├── index.html │ │ └── message.html ├── digest_auth │ ├── README.md │ ├── authdemo.py │ └── digest.py ├── dropbox_auth │ ├── README.md │ ├── dropbox.py │ └── main.py ├── email │ ├── emaildemo.py │ ├── static │ │ ├── index.html │ │ ├── info.txt │ │ └── me.png │ └── template │ │ └── response.html ├── facebook │ ├── README │ ├── facebook.py │ ├── static │ │ ├── facebook.css │ │ └── facebook.js │ ├── templates │ │ ├── modules │ │ │ └── post.html │ │ └── stream.html │ └── uimodules.py ├── fbgraphapi │ ├── README │ ├── facebook.py │ ├── static │ │ ├── facebook.css │ │ └── facebook.js │ ├── templates │ │ ├── modules │ │ │ └── post.html │ │ └── stream.html │ └── uimodules.py ├── github_auth │ ├── README.md │ ├── auth_screen.png │ ├── github.py │ ├── github_auth_example.py │ ├── register_new_app.png │ └── templates │ │ └── gists.html ├── helloworld │ ├── helloworld.py │ ├── helloworld_bottle.py │ ├── helloworld_simple.py │ ├── helloworld_twistd.py │ └── nginx.conf ├── httpauth │ ├── README │ ├── httpauthdemo.py │ ├── httpauthdemo_mongo.py │ └── httpauthdemo_redis.py ├── httpclient │ └── httpclient.py ├── locale │ ├── README │ ├── frontend │ │ ├── locale │ │ │ ├── es_ES │ │ │ │ └── LC_MESSAGES │ │ │ │ │ └── mytest.mo │ │ │ └── pt_BR │ │ │ │ └── LC_MESSAGES │ │ │ │ └── mytest.mo │ │ └── template │ │ │ ├── hello.txt │ │ │ └── index.html │ ├── localedemo.py │ ├── mytest_es_ES.po │ └── mytest_pt_BR.po ├── pycket │ ├── README.md │ └── pycketdemo.py ├── redis │ ├── README │ └── redisdemo.py ├── rpc │ ├── jsonrpc_async_client.py │ ├── jsonrpc_sync_client.py │ ├── rpcdemo.py │ └── xmlrpc_client.py ├── s3 │ ├── README │ ├── s3server.py │ └── se.tac ├── sse │ ├── README │ ├── ssedemo.py │ └── static │ │ └── index.html ├── ssl │ ├── README │ ├── helloworld_simple.py │ ├── helloworld_ssl.py │ └── mkcert.sh ├── upload │ ├── template │ │ └── index.html │ └── uploaddemo.py └── websocket │ ├── chat │ ├── chatdemo.py │ ├── static │ │ ├── chat.css │ │ └── chat.js │ └── templates │ │ ├── index.html │ │ ├── message.html │ │ └── stats.html │ └── echo │ ├── echo.py │ ├── static │ └── jquery-latest.js │ └── templates │ └── echo.html ├── scripts └── cyclone ├── setup.py ├── twisted └── plugins │ └── cyclone_plugin.py └── website ├── Makefile ├── index.html ├── sphinx ├── app.rst ├── auth.rst ├── bottle.rst ├── conf.py ├── databases.rst ├── deferreds.rst ├── devserver.rst ├── e-mail.rst ├── escape.rst ├── httpclient.rst ├── httpserver.rst ├── httputil.rst ├── index.rst ├── integration.rst ├── jsonrpc.rst ├── locale.rst ├── networking.rst ├── overview.rst ├── redis.rst ├── releases.rst ├── releases │ ├── next.rst │ └── v1.0.rst ├── sqlite.rst ├── sse.rst ├── template.rst ├── web.rst ├── webframework.rst ├── websocket.rst └── xmlrpc.rst └── static ├── base.css ├── facebook.png ├── favicon.ico ├── friendfeed.png ├── robots.txt ├── sphinx.css ├── tornado.png └── twitter.png /.coveragerc: -------------------------------------------------------------------------------- 1 | [report] 2 | include = cyclone/* 3 | omit = setup.py,cyclone/app.py 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.pyc 3 | *.so 4 | *~ 5 | build 6 | dist 7 | cyclone.egg-info 8 | dropin.cache 9 | *DS_Store* 10 | _trial_temp 11 | htmlcov 12 | .coverage 13 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | 3 | # because python3.7 is not available on trusty 4 | # @see https://github.com/travis-ci/travis-ci/issues/9815 5 | dist: xenial 6 | 7 | # Test on python3 8 | python: 9 | - 3.5 # debian 10 | - 3.6 # ubuntu / amazonlinux 11 | - 3.7 # fedora 12 | - nightly 13 | 14 | # Allow failures on cpython nightly build 15 | matrix: 16 | fast_finish: true 17 | allow_failures: 18 | - python: nightly 19 | 20 | install: 21 | - pip install -r cyclone/tests/test_requirements.txt 22 | - pip install coveralls 23 | 24 | # cyclone has to be installed 25 | # so as trial (twisted test framework) could run 26 | before_script: python setup.py install 27 | 28 | script: coverage run `which trial` cyclone 29 | after_success: coveralls 30 | 31 | # deploy on pypi 32 | # only on 3.6 (avoid multiple deployment) 33 | deploy: 34 | provider: pypi 35 | user: fiorix 36 | password: 37 | secure: EgUIImke5aN2Q9ybtPfB1RDtvYLouJSjhvetdIWiUJZHxeViaufQQBv2eXkyUaIvwYAagfhnCNSR4Ql9mNr/nksSGvJ+tn9AH014OUSpPcrdmDMZdImWPo7hraNNnEs+pG7an6faBdLDYRtTv8Hx74dW54jCmo9AYBBelwklxwA= 38 | on: 39 | tags: true 40 | branch: master 41 | condition: $TRAVIS_PYTHON_VERSION = 3.6 42 | distributions: "sdist bdist_wheel" 43 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include cyclone/appskel_default.zip 2 | include cyclone/appskel_foreman.zip 3 | include cyclone/appskel_signup.zip 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Cyclone 2 | ======= 3 | 4 | [![Build Status](https://travis-ci.org/fiorix/cyclone.svg?branch=master)](https://travis-ci.org/fiorix/cyclone) 5 | [![Coverage Status](https://coveralls.io/repos/github/fiorix/cyclone/badge.svg?branch=master)](https://coveralls.io/github/fiorix/cyclone?branch=master) 6 | 7 | Cyclone is a web server framework for Python, that implements the Tornado API 8 | as a Twisted protocol. 9 | 10 | :warning: `cyclone` does not support `python` **2.x** anymore :warning: 11 | 12 | See http://cyclone.io for details. 13 | 14 | Installation 15 | ------------ 16 | 17 | Cyclone is listed in PyPI and can be installed with pip or easy_install. 18 | Note that the source distribution includes demo applications that are not 19 | present when Cyclone is installed in this way, so you may wish to download a 20 | copy of the source tarball as well. 21 | 22 | Manual installation 23 | ------------------- 24 | 25 | Download the latest release from http://pypi.python.org/pypi/cyclone 26 | 27 | tar zxvf cyclone-$VERSION.tar.gz 28 | cd cyclone-$VERSION 29 | sudo python setup.py install 30 | 31 | The Cyclone source code is hosted on GitHub: https://github.com/fiorix/cyclone 32 | 33 | Prerequisites 34 | ------------- 35 | 36 | Cyclone runs on Python 2.5, 2.6 and 2.7, and requires: 37 | 38 | - Twisted: http://twistedmatrix.com/trac/wiki/Downloads 39 | - pyOpenSSL: https://launchpad.net/pyopenssl (only if you want SSL/TLS) 40 | 41 | On Python 2.5, simplejson is required too. 42 | 43 | Platforms 44 | --------- 45 | 46 | Cyclone should run on any Unix-like platform, although for the best 47 | performance and scalability only Linux and BSD (including BSD derivatives like 48 | Mac OS X) are recommended. 49 | 50 | Credits 51 | ------- 52 | 53 | Thanks to (in no particular order): 54 | 55 | - Nuswit Telephony API 56 | - Granting permission for this code to be published and sponsoring 57 | 58 | - Gleicon Moraes 59 | - Testing and using on RestMQ 60 | 61 | - Vanderson Mota 62 | - Patching setup.py and PyPi maintenance 63 | 64 | - Andrew Badr 65 | - Fixing auth bugs and adding current Tornado's features 66 | 67 | - Jon Oberheide 68 | - Syncing code with Tornado and security features/fixes 69 | 70 | - Silas Sewell 71 | - Syncing code and minor mail fix 72 | 73 | - Twitter Bootstrap 74 | - For making our demo applications look good 75 | 76 | - Dan Griffin 77 | - WebSocket Keep-Alive for OpDemand 78 | 79 | - Toby Padilla 80 | - WebSocket server 81 | 82 | - Jeethu Rao 83 | - Minor bugfixes and patches 84 | 85 | - Flavio Grossi 86 | - Minor code fixes and websockets chat statistics example 87 | 88 | - Gautam Jeyaraman 89 | - Minor code fixes and patches 90 | 91 | - DhilipSiva 92 | - Minor patches 93 | -------------------------------------------------------------------------------- /appskel/autogen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | cd `dirname $0` 5 | for d in `find . -type d -depth 1 -exec basename {} \;` 6 | do 7 | name="appskel_$d.zip" 8 | skel="../../cyclone/${name}" 9 | echo Generating ${name}... 10 | rm -f $skel 11 | cd $d 12 | zip -r $skel . 13 | cd .. 14 | echo done 15 | done 16 | -------------------------------------------------------------------------------- /appskel/default/.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.pyc 3 | dropin.cache 4 | -------------------------------------------------------------------------------- /appskel/default/frontend/locale/es_ES/LC_MESSAGES/modname.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fiorix/cyclone/fefdc51dbf25b7470467fc8db6cf96d08191cf28/appskel/default/frontend/locale/es_ES/LC_MESSAGES/modname.mo -------------------------------------------------------------------------------- /appskel/default/frontend/locale/es_ES/LC_MESSAGES/modname.po: -------------------------------------------------------------------------------- 1 | # cyclone-tools sample translation. 2 | # Copyright (C) 2011 Alexandre Fiori 3 | # This file is distributed under the same license as the cyclone package. 4 | # Alexandre Fiori , 2011. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: 0.1\n" 10 | "Report-Msgid-Bugs-To: \n" 11 | "POT-Creation-Date: 2011-02-24 15:36-0300\n" 12 | "PO-Revision-Date: 2011-02-28 15:44+-0300\n" 13 | "Last-Translator: Alexandre Fiori \n" 14 | "Language-Team: Alexandre Fiori \n" 15 | "MIME-Version: 1.0\n" 16 | "Content-Type: text/plain; charset=utf-8\n" 17 | "Content-Transfer-Encoding: 8bit\n" 18 | 19 | #: standard input:5 20 | msgid "cyclone web server" 21 | msgstr "servidor web cyclone" 22 | 23 | #: standard input:8 24 | msgid "It works!" 25 | msgstr "¡Funciona!" 26 | -------------------------------------------------------------------------------- /appskel/default/frontend/locale/pt_BR/LC_MESSAGES/modname.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fiorix/cyclone/fefdc51dbf25b7470467fc8db6cf96d08191cf28/appskel/default/frontend/locale/pt_BR/LC_MESSAGES/modname.mo -------------------------------------------------------------------------------- /appskel/default/frontend/locale/pt_BR/LC_MESSAGES/modname.po: -------------------------------------------------------------------------------- 1 | # cyclone-tools sample translation. 2 | # Copyright (C) 2011 Alexandre Fiori 3 | # This file is distributed under the same license as the cyclone package. 4 | # Alexandre Fiori , 2011. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: 0.1\n" 10 | "Report-Msgid-Bugs-To: \n" 11 | "POT-Creation-Date: 2011-02-24 15:36-0300\n" 12 | "PO-Revision-Date: 2011-02-28 15:44+-0300\n" 13 | "Last-Translator: Alexandre Fiori \n" 14 | "Language-Team: Alexandre Fiori \n" 15 | "MIME-Version: 1.0\n" 16 | "Content-Type: text/plain; charset=utf-8\n" 17 | "Content-Transfer-Encoding: 8bit\n" 18 | 19 | #: standard input:5 20 | msgid "cyclone web server" 21 | msgstr "servidor web cyclone" 22 | 23 | #: standard input:8 24 | msgid "It works!" 25 | msgstr "Funciona!" 26 | -------------------------------------------------------------------------------- /appskel/default/frontend/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fiorix/cyclone/fefdc51dbf25b7470467fc8db6cf96d08191cf28/appskel/default/frontend/static/favicon.ico -------------------------------------------------------------------------------- /appskel/default/frontend/template/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{_("cyclone web server")}} 6 | 7 | 8 | 9 | 10 | 11 | 14 | 15 | 16 | 17 | 18 |
19 | 23 | {% block page %}{% end %} 24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /appskel/default/frontend/template/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block page %} 3 |

{{_("It works!")}}

4 |

5 | English  6 | Español  7 | Português 8 |

9 |

hello = {{hello}}

10 |

awesome = {{awesome}}

11 |
12 | 13 |
14 | {% end %} 15 | -------------------------------------------------------------------------------- /appskel/default/frontend/template/post.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block page %} 3 |

POST example

4 |

This variable exists = {{fields.ip}}

5 |

This variable does not exist = {{fields.something}}

6 |

This one comes from .conf = {{fields.mysql_host}}

7 | {% end %} 8 | -------------------------------------------------------------------------------- /appskel/default/modname.conf: -------------------------------------------------------------------------------- 1 | [server] 2 | debug = true 3 | xheaders = false 4 | xsrf_cookies = false 5 | cookie_secret = $cookie_secret 6 | 7 | [frontend] 8 | locale_path = frontend/locale 9 | static_path = frontend/static 10 | template_path = frontend/template 11 | 12 | [sqlite] 13 | enabled = yes 14 | database = :memory: 15 | 16 | [redis] 17 | enabled = no 18 | # unixsocket = /tmp/redis.sock 19 | host = 127.0.0.1 20 | port = 6379 21 | dbid = 0 22 | poolsize = 10 23 | 24 | [mysql] 25 | enabled = no 26 | host = 127.0.0.1 27 | port = 3306 28 | username = foo 29 | password = bar 30 | database = dummy 31 | poolsize = 10 32 | debug = yes 33 | ping_interval = 3600 34 | 35 | [email] 36 | enabled = no 37 | host = smtp.gmail.com 38 | port = 587 39 | tls = yes 40 | username = foo 41 | password = bar 42 | -------------------------------------------------------------------------------- /appskel/default/modname/__init__.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | # 3 | $license 4 | 5 | __author__ = "$name <$email>" 6 | __version__ = "$version" 7 | -------------------------------------------------------------------------------- /appskel/default/modname/storage.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | # 3 | $license 4 | 5 | try: 6 | sqlite_ok = True 7 | import cyclone.sqlite 8 | except ImportError as sqlite_err: 9 | sqlite_ok = False 10 | 11 | import cyclone.redis 12 | 13 | from twisted.enterprise import adbapi 14 | from twisted.internet import defer 15 | from twisted.internet import reactor 16 | from twisted.python import log 17 | 18 | 19 | class DatabaseMixin(object): 20 | mysql = None 21 | redis = None 22 | sqlite = None 23 | 24 | @classmethod 25 | def setup(cls, conf): 26 | if "sqlite_settings" in conf: 27 | if sqlite_ok: 28 | DatabaseMixin.sqlite = \ 29 | cyclone.sqlite.InlineSQLite(conf["sqlite_settings"].database) 30 | else: 31 | log.err("SQLite is currently disabled: %s" % sqlite_err) 32 | 33 | if "redis_settings" in conf: 34 | if conf["redis_settings"].get("unixsocket"): 35 | DatabaseMixin.redis = \ 36 | cyclone.redis.lazyUnixConnectionPool( 37 | conf["redis_settings"].unixsocket, 38 | conf["redis_settings"].dbid, 39 | conf["redis_settings"].poolsize) 40 | else: 41 | DatabaseMixin.redis = \ 42 | cyclone.redis.lazyConnectionPool( 43 | conf["redis_settings"].host, 44 | conf["redis_settings"].port, 45 | conf["redis_settings"].dbid, 46 | conf["redis_settings"].poolsize) 47 | 48 | if "mysql_settings" in conf: 49 | DatabaseMixin.mysql = \ 50 | adbapi.ConnectionPool("MySQLdb", 51 | host=conf["mysql_settings"].host, 52 | port=conf["mysql_settings"].port, 53 | db=conf["mysql_settings"].database, 54 | user=conf["mysql_settings"].username, 55 | passwd=conf["mysql_settings"].password, 56 | cp_min=1, 57 | cp_max=conf["mysql_settings"].poolsize, 58 | cp_reconnect=True, 59 | cp_noisy=conf["mysql_settings"].debug) 60 | 61 | # Ping MySQL to avoid timeouts. On timeouts, the first query 62 | # responds with the following error, before it reconnects: 63 | # mysql.Error: (2006, 'MySQL server has gone away') 64 | # 65 | # There's no way differentiate this from the server shutting down 66 | # and write() failing. To avoid the timeout, we ping. 67 | @defer.inlineCallbacks 68 | def _ping_mysql(): 69 | try: 70 | yield cls.mysql.runQuery("select 1") 71 | except Exception as e: 72 | log.msg("MySQL ping error:", e) 73 | else: 74 | if conf["mysql_settings"].debug: 75 | log.msg("MySQL ping: OK") 76 | 77 | reactor.callLater(conf["mysql_settings"].ping, _ping_mysql) 78 | 79 | if conf["mysql_settings"].ping > 1: 80 | _ping_mysql() 81 | -------------------------------------------------------------------------------- /appskel/default/modname/utils.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | # 3 | $license 4 | 5 | import cyclone.escape 6 | import cyclone.web 7 | 8 | 9 | class TemplateFields(dict): 10 | """Helper class to make sure our 11 | template doesn't fail due to an invalid key""" 12 | def __getattr__(self, name): 13 | try: 14 | return self[name] 15 | except KeyError: 16 | return None 17 | 18 | def __setattr__(self, name, value): 19 | self[name] = value 20 | 21 | 22 | class BaseHandler(cyclone.web.RequestHandler): 23 | #def get_current_user(self): 24 | # user_json = self.get_secure_cookie("user") 25 | # if user_json: 26 | # return cyclone.escape.json_decode(user_json) 27 | 28 | def get_user_locale(self): 29 | lang = self.get_secure_cookie("lang") 30 | if lang: 31 | return cyclone.locale.get(lang) 32 | -------------------------------------------------------------------------------- /appskel/default/modname/views.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | # 3 | $license 4 | 5 | import cyclone.escape 6 | import cyclone.locale 7 | import cyclone.web 8 | 9 | from twisted.internet import defer 10 | from twisted.python import log 11 | 12 | from $modname.storage import DatabaseMixin 13 | from $modname.utils import BaseHandler 14 | from $modname.utils import TemplateFields 15 | 16 | 17 | class IndexHandler(BaseHandler): 18 | def get(self): 19 | self.render("index.html", hello="world", awesome="bacon") 20 | 21 | def post(self): 22 | f = TemplateFields(post=True, ip=self.request.remote_ip) 23 | #f["this_is_a_dict"] = True 24 | #f["raw_config"] = self.settings.raw 25 | #f["mysql_host"] = self.settings.raw.get("mysql", "host") 26 | self.render("post.html", fields=f) 27 | 28 | 29 | class LangHandler(BaseHandler): 30 | def get(self, lang_code): 31 | if lang_code in cyclone.locale.get_supported_locales(): 32 | self.set_secure_cookie("lang", lang_code) 33 | 34 | self.redirect(self.request.headers.get("Referer", 35 | self.get_argument("next", "/"))) 36 | 37 | 38 | class SampleSQLiteHandler(BaseHandler, DatabaseMixin): 39 | def get(self): 40 | if self.sqlite: 41 | response = self.sqlite.runQuery("select strftime('%Y-%m-%d')") 42 | self.write({"response": response}) 43 | else: 44 | self.write("SQLite is disabled\r\n") 45 | 46 | 47 | class SampleRedisHandler(BaseHandler, DatabaseMixin): 48 | @defer.inlineCallbacks 49 | def get(self): 50 | if self.redis: 51 | try: 52 | response = yield self.redis.get("foo") 53 | except Exception as e: 54 | log.msg("Redis query failed: %s" % str(e)) 55 | raise cyclone.web.HTTPError(503) # Service Unavailable 56 | else: 57 | self.write({"response": response}) 58 | else: 59 | self.write("Redis is disabled\r\n") 60 | 61 | 62 | class SampleMySQLHandler(BaseHandler, DatabaseMixin): 63 | @defer.inlineCallbacks 64 | def get(self): 65 | if self.mysql: 66 | try: 67 | response = yield self.mysql.runQuery("select now()") 68 | except Exception as e: 69 | log.msg("MySQL query failed: %s" % str(e)) 70 | raise cyclone.web.HTTPError(503) # Service Unavailable 71 | else: 72 | self.write({"response": str(response[0][0])}) 73 | else: 74 | self.write("MySQL is disabled\r\n") 75 | -------------------------------------------------------------------------------- /appskel/default/modname/web.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | # 3 | $license 4 | 5 | import cyclone.locale 6 | import cyclone.web 7 | 8 | from $modname import views 9 | from $modname import config 10 | from $modname.storage import DatabaseMixin 11 | 12 | 13 | class Application(cyclone.web.Application): 14 | def __init__(self, config_file): 15 | handlers = [ 16 | (r"/", views.IndexHandler), 17 | (r"/lang/(.+)", views.LangHandler), 18 | (r"/sample/mysql", views.SampleMySQLHandler), 19 | (r"/sample/redis", views.SampleRedisHandler), 20 | (r"/sample/sqlite", views.SampleSQLiteHandler), 21 | ] 22 | 23 | conf = config.parse_config(config_file) 24 | 25 | # Initialize locales 26 | if "locale_path" in conf: 27 | cyclone.locale.load_gettext_translations(conf["locale_path"], 28 | "$modname") 29 | 30 | # Set up database connections 31 | DatabaseMixin.setup(conf) 32 | 33 | #conf["login_url"] = "/auth/login" 34 | #conf["autoescape"] = None 35 | cyclone.web.Application.__init__(self, handlers, **conf) 36 | -------------------------------------------------------------------------------- /appskel/default/scripts/cookie_secret.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | # 4 | $license 5 | 6 | import base64 7 | import uuid 8 | 9 | if __name__ == "__main__": 10 | print(base64.b64encode(uuid.uuid4().bytes + uuid.uuid4().bytes)) 11 | -------------------------------------------------------------------------------- /appskel/default/scripts/debian-init.d: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ### BEGIN INIT INFO 4 | # Provides: $modname 5 | # Required-Start: $$all 6 | # Required-Stop: $$all 7 | # Default-Start: 2 3 4 5 8 | # Default-Stop: 0 1 6 9 | # Short-Description: Starts a service on the cyclone web server 10 | # Description: Foobar 11 | ### END INIT INFO 12 | 13 | PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin 14 | DAEMON=/usr/bin/twistd 15 | 16 | SERVICE_DIR=/path/to/$modname 17 | SERVICE_NAME=$modname 18 | 19 | PYTHONPATH=$$SERVICE_DIR:$$PYTHONPATH 20 | export PYTHONPATH 21 | 22 | PORT=8888 23 | LISTEN="127.0.0.1" 24 | CONFIG=$$SERVICE_DIR/$$SERVICE_NAME.conf 25 | PIDFILE=/var/run/$$SERVICE_NAME.pid 26 | LOGFILE=/var/log/$$SERVICE_NAME.log 27 | APP=$${SERVICE_NAME}.web.Application 28 | 29 | USER=www-data 30 | GROUP=www-data 31 | DAEMON_OPTS="-u $$USER -g $$GROUP --pidfile=$$PIDFILE --logfile=$$LOGFILE cyclone --port $$PORT --listen $$LISTEN --app $$APP -c $$CONFIG" 32 | 33 | if [ ! -x $$DAEMON ]; then 34 | echo "ERROR: Can't execute $$DAEMON." 35 | exit 1 36 | fi 37 | 38 | if [ ! -d $$SERVICE_DIR ]; then 39 | echo "ERROR: Directory doesn't exist: $$SERVICE_DIR" 40 | exit 1 41 | fi 42 | 43 | start_service() { 44 | echo -n " * Starting $$SERVICE_NAME... " 45 | start-stop-daemon -Sq -p $$PIDFILE -x $$DAEMON -- $$DAEMON_OPTS 46 | e=$$? 47 | if [ $$e -eq 1 ]; then 48 | echo "already running" 49 | return 50 | fi 51 | 52 | if [ $$e -eq 255 ]; then 53 | echo "couldn't start" 54 | return 55 | fi 56 | 57 | echo "done" 58 | } 59 | 60 | stop_service() { 61 | echo -n " * Stopping $$SERVICE_NAME... " 62 | start-stop-daemon -Kq -R 10 -p $$PIDFILE 63 | e=$$? 64 | if [ $$e -eq 1 ]; then 65 | echo "not running" 66 | return 67 | fi 68 | 69 | echo "done" 70 | } 71 | 72 | case "$$1" in 73 | start) 74 | start_service 75 | ;; 76 | stop) 77 | stop_service 78 | ;; 79 | restart) 80 | stop_service 81 | start_service 82 | ;; 83 | *) 84 | echo "Usage: /etc/init.d/$$SERVICE_NAME {start|stop|restart}" >&2 85 | exit 1 86 | ;; 87 | esac 88 | 89 | exit 0 90 | -------------------------------------------------------------------------------- /appskel/default/scripts/debian-multicore-init.d: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ### BEGIN INIT INFO 4 | # Provides: $modname 5 | # Required-Start: $$all 6 | # Required-Stop: $$all 7 | # Default-Start: 2 3 4 5 8 | # Default-Stop: 0 1 6 9 | # Short-Description: Starts a service on the cyclone web server 10 | # Description: Foobar 11 | ### END INIT INFO 12 | 13 | PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin 14 | DAEMON=/usr/bin/twistd 15 | 16 | SERVICE_DIR=/path/to/$modname 17 | SERVICE_NAME=$modname 18 | 19 | PYTHONPATH=$$SERVICE_DIR:$$PYTHONPATH 20 | export PYTHONPATH 21 | 22 | INSTANCES=4 23 | START_PORT=9901 24 | LISTEN="127.0.0.1" 25 | CONFIG=$$SERVICE_DIR/$$SERVICE_NAME.conf 26 | APP=$${SERVICE_NAME}.web.Application 27 | 28 | USER=www-data 29 | GROUP=www-data 30 | 31 | # Check out the start_service function for other customization options 32 | # such as setting CPU affinity. 33 | 34 | if [ ! -x $$DAEMON ]; then 35 | echo "ERROR: Can't execute $$DAEMON." 36 | exit 1 37 | fi 38 | 39 | if [ ! -d $$SERVICE_DIR ]; then 40 | echo "ERROR: Directory doesn't exist: $$SERVICE_DIR" 41 | exit 1 42 | fi 43 | 44 | start_service() { 45 | echo -n " * Starting $$SERVICE_NAME... " 46 | for n in `seq 1 $$INSTANCES` 47 | do 48 | PORT=$$[START_PORT] 49 | PIDFILE=/var/run/$$SERVICE_NAME.$$PORT.pid 50 | LOGFILE=/var/log/$$SERVICE_NAME.$$PORT.log 51 | DAEMON_OPTS="-u $$USER -g $$GROUP --pidfile=$$PIDFILE --logfile=$$LOGFILE cyclone --port $$PORT --listen $$LISTEN --app $$APP -c $$CONFIG" 52 | START_PORT=$$[PORT+1] 53 | 54 | start-stop-daemon -Sq -p $$PIDFILE -x $$DAEMON -- $$DAEMON_OPTS 55 | e=$$? 56 | if [ $$e -eq 1 ]; then 57 | echo "already running" 58 | return 59 | fi 60 | 61 | if [ $$e -eq 255 ]; then 62 | echo "couldn't start" 63 | return 64 | fi 65 | 66 | # Set CPU affinity 67 | if [ -x /usr/bin/taskset ]; then 68 | sleep 1 69 | /usr/bin/taskset -pc $$n `cat $$PIDFILE` &> /dev/null 70 | fi 71 | done 72 | echo "done" 73 | } 74 | 75 | stop_service() { 76 | echo -n " * Stopping $$SERVICE_NAME... " 77 | for n in `seq 1 $$INSTANCES` 78 | do 79 | PORT=$$[START_PORT] 80 | PIDFILE=/var/run/$$SERVICE_NAME.$$PORT.pid 81 | START_PORT=$$[PORT+1] 82 | start-stop-daemon -Kq -R 10 -p $$PIDFILE 83 | e=$$? 84 | if [ $$e -eq 1 ]; then 85 | echo "not running" 86 | return 87 | fi 88 | done 89 | 90 | echo "done" 91 | } 92 | 93 | case "$$1" in 94 | start) 95 | start_service 96 | ;; 97 | stop) 98 | stop_service 99 | ;; 100 | restart) 101 | sp=$$START_PORT 102 | stop_service 103 | START_PORT=$$sp 104 | start_service 105 | ;; 106 | *) 107 | echo "Usage: /etc/init.d/$$SERVICE_NAME {start|stop|restart}" >&2 108 | exit 1 109 | ;; 110 | esac 111 | 112 | exit 0 113 | -------------------------------------------------------------------------------- /appskel/default/scripts/localefix.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | # 4 | $license 5 | 6 | import re 7 | import sys 8 | 9 | if __name__ == "__main__": 10 | try: 11 | filename = sys.argv[1] 12 | assert filename != "-" 13 | fd = open(filename) 14 | except: 15 | fd = sys.stdin 16 | 17 | line_re = re.compile(r'="([^"]+)"') 18 | for line in fd: 19 | line = line_re.sub(r"=\\1", line) 20 | sys.stdout.write(line) 21 | fd.close() 22 | -------------------------------------------------------------------------------- /appskel/default/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # see scripts/debian-init.d for production deployments 3 | 4 | export PYTHONPATH=`dirname $$0` 5 | twistd -n cyclone -p 8888 -l 0.0.0.0 \ 6 | -r $modname.web.Application -c $modname.conf $$* 7 | -------------------------------------------------------------------------------- /appskel/foreman/.env: -------------------------------------------------------------------------------- 1 | PYTHONPATH=. 2 | -------------------------------------------------------------------------------- /appskel/foreman/.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.pyc 3 | dropin.cache 4 | -------------------------------------------------------------------------------- /appskel/foreman/Procfile: -------------------------------------------------------------------------------- 1 | web: python $modname/main.py foobar.conf 2 | -------------------------------------------------------------------------------- /appskel/foreman/frontend/locale/es_ES/LC_MESSAGES/modname.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fiorix/cyclone/fefdc51dbf25b7470467fc8db6cf96d08191cf28/appskel/foreman/frontend/locale/es_ES/LC_MESSAGES/modname.mo -------------------------------------------------------------------------------- /appskel/foreman/frontend/locale/es_ES/LC_MESSAGES/modname.po: -------------------------------------------------------------------------------- 1 | # cyclone-tools sample translation. 2 | # Copyright (C) 2011 Alexandre Fiori 3 | # This file is distributed under the same license as the cyclone package. 4 | # Alexandre Fiori , 2011. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: 0.1\n" 10 | "Report-Msgid-Bugs-To: \n" 11 | "POT-Creation-Date: 2011-02-24 15:36-0300\n" 12 | "PO-Revision-Date: 2011-02-28 15:44+-0300\n" 13 | "Last-Translator: Alexandre Fiori \n" 14 | "Language-Team: Alexandre Fiori \n" 15 | "MIME-Version: 1.0\n" 16 | "Content-Type: text/plain; charset=utf-8\n" 17 | "Content-Transfer-Encoding: 8bit\n" 18 | 19 | #: standard input:5 20 | msgid "cyclone web server" 21 | msgstr "servidor web cyclone" 22 | 23 | #: standard input:8 24 | msgid "It works!" 25 | msgstr "¡Funciona!" 26 | -------------------------------------------------------------------------------- /appskel/foreman/frontend/locale/pt_BR/LC_MESSAGES/modname.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fiorix/cyclone/fefdc51dbf25b7470467fc8db6cf96d08191cf28/appskel/foreman/frontend/locale/pt_BR/LC_MESSAGES/modname.mo -------------------------------------------------------------------------------- /appskel/foreman/frontend/locale/pt_BR/LC_MESSAGES/modname.po: -------------------------------------------------------------------------------- 1 | # cyclone-tools sample translation. 2 | # Copyright (C) 2011 Alexandre Fiori 3 | # This file is distributed under the same license as the cyclone package. 4 | # Alexandre Fiori , 2011. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: 0.1\n" 10 | "Report-Msgid-Bugs-To: \n" 11 | "POT-Creation-Date: 2011-02-24 15:36-0300\n" 12 | "PO-Revision-Date: 2011-02-28 15:44+-0300\n" 13 | "Last-Translator: Alexandre Fiori \n" 14 | "Language-Team: Alexandre Fiori \n" 15 | "MIME-Version: 1.0\n" 16 | "Content-Type: text/plain; charset=utf-8\n" 17 | "Content-Transfer-Encoding: 8bit\n" 18 | 19 | #: standard input:5 20 | msgid "cyclone web server" 21 | msgstr "servidor web cyclone" 22 | 23 | #: standard input:8 24 | msgid "It works!" 25 | msgstr "Funciona!" 26 | -------------------------------------------------------------------------------- /appskel/foreman/frontend/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fiorix/cyclone/fefdc51dbf25b7470467fc8db6cf96d08191cf28/appskel/foreman/frontend/static/favicon.ico -------------------------------------------------------------------------------- /appskel/foreman/frontend/template/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{_("cyclone web server")}} 6 | 7 | 8 | 9 | 10 | 11 | 14 | 15 | 16 | 17 | 18 |
19 | 23 | {% block page %}{% end %} 24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /appskel/foreman/frontend/template/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block page %} 3 |

{{_("It works!")}}

4 |

5 | English  6 | Español  7 | Português 8 |

9 |

hello = {{hello}}

10 |

awesome = {{awesome}}

11 |
12 | 13 |
14 | {% end %} 15 | -------------------------------------------------------------------------------- /appskel/foreman/frontend/template/post.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block page %} 3 |

POST example

4 |

This variable exists = {{fields.ip}}

5 |

This variable does not exist = {{fields.something}}

6 |

This one comes from .conf = {{fields.mysql_host}}

7 | {% end %} 8 | -------------------------------------------------------------------------------- /appskel/foreman/modname.conf: -------------------------------------------------------------------------------- 1 | [server] 2 | debug = true 3 | xheaders = false 4 | xsrf_cookies = false 5 | cookie_secret = $cookie_secret 6 | 7 | [frontend] 8 | locale_path = frontend/locale 9 | static_path = frontend/static 10 | template_path = frontend/template 11 | 12 | [sqlite] 13 | enabled = yes 14 | database = :memory: 15 | 16 | [redis] 17 | enabled = no 18 | host = 127.0.0.1 19 | port = 6379 20 | dbid = 0 21 | poolsize = 10 22 | 23 | [mysql] 24 | enabled = no 25 | host = 127.0.0.1 26 | port = 3306 27 | username = foo 28 | password = bar 29 | database = dummy 30 | poolsize = 10 31 | debug = no 32 | -------------------------------------------------------------------------------- /appskel/foreman/modname/__init__.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | # 3 | $license 4 | 5 | __author__ = "$name <$email>" 6 | __version__ = "$version" 7 | -------------------------------------------------------------------------------- /appskel/foreman/modname/config.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | # 3 | $license 4 | 5 | import os 6 | import ConfigParser 7 | from cyclone.util import ObjectDict 8 | 9 | 10 | def xget(func, section, option, default=None): 11 | try: 12 | return func(section, option) 13 | except: 14 | return default 15 | 16 | 17 | def parse_config(filename): 18 | cfg = ConfigParser.RawConfigParser() 19 | with open(filename) as fp: 20 | cfg.readfp(fp) 21 | fp.close() 22 | 23 | settings = {'raw': cfg} 24 | 25 | # web server settings 26 | settings["debug"] = xget(cfg.getboolean, "server", "debug", False) 27 | settings["xheaders"] = xget(cfg.getboolean, "server", "xheaders", False) 28 | settings["cookie_secret"] = cfg.get("server", "cookie_secret") 29 | settings["xsrf_cookies"] = xget(cfg.getboolean, "server", "xsrf_cookies", 30 | False) 31 | 32 | # get project's absolute path 33 | root = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) 34 | getpath = lambda k, v: os.path.join(root, xget(cfg.get, k, v)) 35 | 36 | # locale, template and static directories' path 37 | settings["locale_path"] = getpath("frontend", "locale_path") 38 | settings["static_path"] = getpath("frontend", "static_path") 39 | settings["template_path"] = getpath("frontend", "template_path") 40 | 41 | # sqlite support 42 | if xget(cfg.getboolean, "sqlite", "enabled", False): 43 | settings["sqlite_settings"] = ObjectDict(database=cfg.get("sqlite", 44 | "database")) 45 | else: 46 | settings["sqlite_settings"] = None 47 | 48 | # redis support 49 | if xget(cfg.getboolean, "redis", "enabled", False): 50 | settings["redis_settings"] = ObjectDict( 51 | host=cfg.get("redis", "host"), 52 | port=cfg.getint("redis", "port"), 53 | dbid=cfg.getint("redis", "dbid"), 54 | poolsize=cfg.getint("redis", "poolsize")) 55 | else: 56 | settings["redis_settings"] = None 57 | 58 | # mysql support 59 | if xget(cfg.getboolean, "mysql", "enabled", False): 60 | settings["mysql_settings"] = ObjectDict( 61 | host=cfg.get("mysql", "host"), 62 | port=cfg.getint("mysql", "port"), 63 | username=xget(cfg.get, "mysql", "username"), 64 | password=xget(cfg.get, "mysql", "password"), 65 | database=xget(cfg.get, "mysql", "database"), 66 | poolsize=xget(cfg.getint, "mysql", "poolsize", 10), 67 | debug=xget(cfg.getboolean, "mysql", "debug", False)) 68 | else: 69 | settings["mysql_settings"] = None 70 | 71 | return settings 72 | -------------------------------------------------------------------------------- /appskel/foreman/modname/main.py: -------------------------------------------------------------------------------- 1 | import web 2 | import sys, os 3 | from twisted.python import log 4 | from twisted.internet import defer, reactor 5 | 6 | 7 | def main(config_file): 8 | log.startLogging(sys.stdout) 9 | application = web.Application(config_file) 10 | 11 | port = os.environ.get("PORT", 8888) 12 | reactor.listenTCP(int(port), application) 13 | reactor.run() 14 | 15 | if __name__ == "__main__": 16 | if len(sys.argv) > 1: 17 | main(sys.argv[1]) 18 | else: 19 | log.error("no config file given") 20 | sys.exit(-1) 21 | -------------------------------------------------------------------------------- /appskel/foreman/modname/utils.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | # 3 | $license 4 | 5 | import cyclone.escape 6 | import cyclone.web 7 | 8 | from twisted.enterprise import adbapi 9 | 10 | 11 | class TemplateFields(dict): 12 | """Helper class to make sure our 13 | template doesn't fail due to an invalid key""" 14 | def __getattr__(self, name): 15 | try: 16 | return self[name] 17 | except KeyError: 18 | return None 19 | 20 | def __setattr__(self, name, value): 21 | self[name] = value 22 | 23 | 24 | class BaseHandler(cyclone.web.RequestHandler): 25 | #def get_current_user(self): 26 | # user_json = self.get_secure_cookie("user") 27 | # if user_json: 28 | # return cyclone.escape.json_decode(user_json) 29 | 30 | def get_user_locale(self): 31 | lang = self.get_secure_cookie("lang") 32 | if lang: 33 | return cyclone.locale.get(lang) 34 | 35 | -------------------------------------------------------------------------------- /appskel/foreman/modname/views.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | # 3 | $license 4 | 5 | import cyclone.escape 6 | import cyclone.locale 7 | import cyclone.web 8 | 9 | from twisted.internet import defer 10 | from twisted.python import log 11 | 12 | from $modname.utils import BaseHandler 13 | from $modname.utils import TemplateFields 14 | 15 | 16 | class IndexHandler(BaseHandler): 17 | def get(self): 18 | self.render("index.html", hello='world', awesome='bacon') 19 | # another option would be 20 | # fields = {'hello': 'world', 'awesome': 'bacon'} 21 | # self.render('index.html', **fields) 22 | 23 | def post(self): 24 | tpl_fields = TemplateFields() 25 | tpl_fields['post'] = True 26 | tpl_fields['ip'] = self.request.remote_ip 27 | # you can also fetch your own config variables defined in 28 | # $modname.conf using 29 | # self.settings.raw.get('section', 'parameter') 30 | tpl_fields['mysql_host'] = self.settings.raw.get('mysql', 'host') 31 | self.render("post.html", fields=tpl_fields) 32 | 33 | 34 | class LangHandler(BaseHandler): 35 | def get(self, lang_code): 36 | if lang_code in cyclone.locale.get_supported_locales(): 37 | self.set_secure_cookie("lang", lang_code) 38 | 39 | self.redirect(self.request.headers.get("Referer", 40 | self.get_argument("next", "/"))) 41 | 42 | -------------------------------------------------------------------------------- /appskel/foreman/modname/web.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | # 3 | $license 4 | 5 | import cyclone.locale 6 | import cyclone.web 7 | 8 | from $modname import views 9 | from $modname import config 10 | 11 | 12 | class Application(cyclone.web.Application): 13 | def __init__(self, config_file): 14 | handlers = [ 15 | (r"/", views.IndexHandler), 16 | (r"/lang/(.+)", views.LangHandler), 17 | ] 18 | 19 | settings = config.parse_config(config_file) 20 | 21 | # Initialize locales 22 | locales = settings.get("locale_path") 23 | if locales: 24 | cyclone.locale.load_gettext_translations(locales, "$modname") 25 | 26 | #settings["login_url"] = "/auth/login" 27 | #settings["autoescape"] = None 28 | cyclone.web.Application.__init__(self, handlers, **settings) 29 | -------------------------------------------------------------------------------- /appskel/foreman/requirements.txt: -------------------------------------------------------------------------------- 1 | Twisted==12.2.0 2 | cyclone==1.0-rc13 3 | -------------------------------------------------------------------------------- /appskel/foreman/scripts/cookie_secret.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | # 4 | $license 5 | 6 | import base64 7 | import uuid 8 | 9 | if __name__ == "__main__": 10 | print(base64.b64encode(uuid.uuid4().bytes + uuid.uuid4().bytes)) 11 | -------------------------------------------------------------------------------- /appskel/foreman/scripts/localefix.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | # 4 | $license 5 | 6 | import re 7 | import sys 8 | 9 | if __name__ == "__main__": 10 | try: 11 | filename = sys.argv[1] 12 | assert filename != "-" 13 | fd = open(filename) 14 | except: 15 | fd = sys.stdin 16 | 17 | line_re = re.compile(r'="([^"]+)"') 18 | for line in fd: 19 | line = line_re.sub(r"=\\1", line) 20 | sys.stdout.write(line) 21 | fd.close() 22 | -------------------------------------------------------------------------------- /appskel/signup/.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.pyc 3 | dropin.cache 4 | -------------------------------------------------------------------------------- /appskel/signup/frontend/locale/es_ES/LC_MESSAGES/modname.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fiorix/cyclone/fefdc51dbf25b7470467fc8db6cf96d08191cf28/appskel/signup/frontend/locale/es_ES/LC_MESSAGES/modname.mo -------------------------------------------------------------------------------- /appskel/signup/frontend/locale/pt_BR/LC_MESSAGES/modname.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fiorix/cyclone/fefdc51dbf25b7470467fc8db6cf96d08191cf28/appskel/signup/frontend/locale/pt_BR/LC_MESSAGES/modname.mo -------------------------------------------------------------------------------- /appskel/signup/frontend/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fiorix/cyclone/fefdc51dbf25b7470467fc8db6cf96d08191cf28/appskel/signup/frontend/static/favicon.ico -------------------------------------------------------------------------------- /appskel/signup/frontend/static/legal.txt: -------------------------------------------------------------------------------- 1 | Terms of Service: whatever. 2 | -------------------------------------------------------------------------------- /appskel/signup/frontend/template/account.html: -------------------------------------------------------------------------------- 1 | {% extends "admin.html" %} 2 | {% block header %} 3 | 32 | {% end %} 33 | 34 | {% block page %} 35 |
36 |

{{_("Account settings")}}

37 | 38 | 39 | {% if fields.err and "invalid_name" in fields.err %} 40 |
{{_("Invalid name")}}×
41 | {% end %} 42 | 43 | 44 | 45 | {% if fields.err and "old_nomatch" in fields.err %} 46 |
{{_("Invalid password")}}×
47 | {% elif fields.err and "old_missing" in fields.err %} 48 |
{{_("This field is mandatory")}}×
49 | {% end %} 50 | 51 | 52 | 53 | {% if fields.err and "invalid_passwd" in fields.err %} 54 |
{{_("Invalid password")}}×
55 | {% elif fields.err and "nomatch" in fields.err %} 56 |
{{_("Passwords don't match")}}×
57 | {% elif fields.updated %} 58 |
{{_("Saved!")}}×
59 | {% end %} 60 | 61 | 62 |
63 | 64 |
65 | 66 | 69 | 70 | {% end %} 71 | -------------------------------------------------------------------------------- /appskel/signup/frontend/template/admin.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block header %} 3 | 6 | {% end %} 7 | 8 | {% block navbar %} 9 | 44 | {% end %} 45 | -------------------------------------------------------------------------------- /appskel/signup/frontend/template/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {% block title %}$modname{% end %} 6 | 7 | 8 | 9 | 10 | 11 | 12 | {% block header %}{% end %} 13 | 14 | 15 | 16 | {% block navbar %}{% end %} 17 | 18 |
19 | {% block page %}{% end %} 20 |
21 | 22 | 23 | 24 | 25 | {% block scripts %}{% end %} 26 | 27 | 28 | -------------------------------------------------------------------------------- /appskel/signup/frontend/template/dashboard.html: -------------------------------------------------------------------------------- 1 | {% extends "admin.html" %} 2 | {% block dashboard_menu %}active{% end %} 3 | {% block page %} 4 |

{{_("Bootstrap starter template")}}

5 |

{{_("Use this document as a way to quick start any new project.
All you get is this message and a barebones HTML document.")}}

6 | 7 |
8 | 9 | 12 | 13 | {% end %} 14 | -------------------------------------------------------------------------------- /appskel/signup/frontend/template/error_all.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block header %} 3 | 26 | {% end %} 27 | 28 | {% block page %} 29 |
30 |

Oooops!

31 |

{{"HTTP %s: %s" % (fields["code"], fields["message"])}}

32 | 33 | {% if "exception" in fields and handler.settings.debug is True %} 34 |

Debug:

35 |
{{escape(str(fields["exception"]))}}
36 | {% end %} 37 |
38 | 39 | 42 | 43 | {% end %} 44 | -------------------------------------------------------------------------------- /appskel/signup/frontend/template/index.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block header %} 3 | 50 | {% end %} 51 | 52 | {% block page %} 53 |
54 | 58 |

$modname

59 |
60 | 61 |
62 | 63 |
64 |

{{_("Super awesome marketing speak!")}}

65 |

Cras justo odio, dapibus ac facilisis in, egestas eget quam. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus.

66 | {{_("Sign up today")}} 67 | 68 | 73 |
74 | 75 |
76 | 77 |
78 |
79 |

Subheading

80 |

Donec id elit non mi porta gravida at eget metus. Maecenas faucibus mollis interdum.

81 | 82 |

Subheading

83 |

Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Cras mattis consectetur purus sit amet fermentum.

84 | 85 |

Subheading

86 |

Maecenas sed diam eget risus varius blandit sit amet non magna.

87 |
88 | 89 |
90 |

Subheading

91 |

Donec id elit non mi porta gravida at eget metus. Maecenas faucibus mollis interdum.

92 | 93 |

Subheading

94 |

Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Cras mattis consectetur purus sit amet fermentum.

95 | 96 |

Subheading

97 |

Maecenas sed diam eget risus varius blandit sit amet non magna.

98 |
99 |
100 | 101 |
102 | 103 | 106 | 107 | {% end %} 108 | -------------------------------------------------------------------------------- /appskel/signup/frontend/template/passwd.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block header %} 3 | 35 | {% end %} 36 | 37 | {% block scripts %} 38 | 49 | {% end %} 50 | 51 | {% block page %} 52 |
53 | 57 |

$modname

58 |
59 | 60 |
61 | 62 |
63 |

{{_("Reset password")}}

64 | 65 | {% if fields.err and "email" in fields.err %} 66 |
{{_("Invalid email address")}}×
67 | {% elif fields.err and "notfound" in fields.err %} 68 |
{{_("Email address not registered")}}×
69 | {% elif fields.err and "pending" in fields.err %} 70 |
{{_("This account is pending confirmation")}}×
71 | {% elif fields.err and "send" in fields.err %} 72 |
{{_("Please try again later")}}×
73 | {% end %} 74 | 75 |
76 | {% end %} 77 | -------------------------------------------------------------------------------- /appskel/signup/frontend/template/passwd_email.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |

{{_("New password")}}

11 |

{{_("Your password has been reset, and must be used to reactivate the account within 24 hours.")}}

12 |

{{_("Here's the new password:")}} {{passwd}}

13 | 14 |
15 |

{{_("Requested by IP %s on %s") % (ip, date)}}

16 | 17 | 18 | -------------------------------------------------------------------------------- /appskel/signup/frontend/template/passwd_email_subject.txt: -------------------------------------------------------------------------------- 1 | $modname: {{_("Password reset")}} 2 | -------------------------------------------------------------------------------- /appskel/signup/frontend/template/passwd_ok.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block header %} 3 | 32 | {% end %} 33 | 34 | {% block page %} 35 |
36 | 40 |

$modname

41 |
42 | 43 |
44 | 45 |
46 |

Done!

47 |

{{_("A confirmation email has been sent to %s with instructions.") % email}}

48 | {{_("Sign in")}} 49 |
50 | 51 |
52 | 53 | 56 | 57 | {% end %} 58 | -------------------------------------------------------------------------------- /appskel/signup/frontend/template/signin.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block header %} 3 | 35 | {% end %} 36 | 37 | {% block scripts %} 38 | 49 | {% end %} 50 | 51 | {% block page %} 52 |
53 | 57 |

$modname

58 |
59 | 60 |
61 | 62 |
63 |

{{_("Sign in")}}

64 | 65 | 66 | {% if fields.err and "auth" in fields.err %} 67 |
{{_("Invalid email or password")}}×
68 | {% end %} 69 | 72 | 73 | {{_("Forgot password?")}} 74 |
75 | {% end %} 76 | 77 | -------------------------------------------------------------------------------- /appskel/signup/frontend/template/signup.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block header %} 3 | 35 | {% end %} 36 | 37 | {% block scripts %} 38 | 49 | {% end %} 50 | 51 | {% block page %} 52 |
53 | 57 |

$modname

58 |
59 | 60 |
61 | 62 |
63 |

{{_("Sign up")}}

64 | 65 | {% if fields.err and "legal" not in fields.err %} 66 |
67 | {% if "email" in fields.err %} 68 | {{_("Invalid email address")}} 69 | {% elif "exists" in fields.err %} 70 | {{_("Email already registered")}} 71 | {% elif "send" in fields.err %} 72 | {{_("Please try again later")}} 73 | {% end %} 74 | ×
75 | {% end %} 76 | 79 | {% if fields.err and "legal" in fields.err %} 80 |
{{_("Please accept the Terms of Service")}}×
81 | {% end %} 82 | 83 |
84 | {% end %} 85 | -------------------------------------------------------------------------------- /appskel/signup/frontend/template/signup_email.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |

{{_("Welcome!")}}

11 |

{{_("Your account has been created, and must be activated within 24 hours.")}}

12 |

{{_("Here's your password:")}} {{passwd}}

13 | 14 |
15 |

{{_("Requested by IP %s on %s") % (ip, date)}}

16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /appskel/signup/frontend/template/signup_email_subject.txt: -------------------------------------------------------------------------------- 1 | $modname: {{_("Sign up confirmation")}} 2 | -------------------------------------------------------------------------------- /appskel/signup/frontend/template/signup_ok.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block header %} 3 | 32 | {% end %} 33 | 34 | {% block page %} 35 |
36 | 40 |

$modname

41 |
42 | 43 |
44 | 45 |
46 |

Thank you!

47 |

{{_("A confirmation email has been sent to %s with instructions.") % email}}

48 | Sign in 49 |
50 | 51 |
52 | 53 | 56 | 57 | {% end %} 58 | -------------------------------------------------------------------------------- /appskel/signup/modname.conf: -------------------------------------------------------------------------------- 1 | [server] 2 | debug = true 3 | xheaders = false 4 | xsrf_cookies = false 5 | cookie_secret = $cookie_secret 6 | 7 | [frontend] 8 | locale_path = frontend/locale 9 | static_path = frontend/static 10 | template_path = frontend/template 11 | 12 | [sqlite] 13 | enabled = yes 14 | database = :memory: 15 | 16 | [redis] 17 | enabled = yes 18 | # unixsocket = /tmp/redis.sock 19 | host = 127.0.0.1 20 | port = 6379 21 | dbid = 0 22 | poolsize = 10 23 | 24 | [mysql] 25 | enabled = yes 26 | host = 127.0.0.1 27 | port = 3306 28 | username = foo 29 | password = bar 30 | database = dummy 31 | poolsize = 10 32 | debug = yes 33 | ping_interval = 3600 34 | 35 | [email] 36 | enabled = yes 37 | host = smtp.gmail.com 38 | port = 587 39 | tls = yes 40 | username = foo 41 | password = bar 42 | -------------------------------------------------------------------------------- /appskel/signup/modname.sql: -------------------------------------------------------------------------------- 1 | drop database if exists dummy; 2 | create database dummy; 3 | grant all privileges on dummy.* to 'foo'@'localhost' identified by 'bar'; 4 | use dummy; 5 | 6 | create table users ( 7 | id integer not null auto_increment, 8 | user_email varchar(50) not null, 9 | user_passwd varchar(40) not null, 10 | user_full_name varchar(80) null, 11 | user_is_active boolean not null, 12 | primary key(id) 13 | ); 14 | -------------------------------------------------------------------------------- /appskel/signup/modname/__init__.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | # 3 | $license 4 | 5 | __author__ = "$name <$email>" 6 | __version__ = "$version" 7 | -------------------------------------------------------------------------------- /appskel/signup/modname/web.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | # 3 | $license 4 | 5 | import cyclone.locale 6 | import cyclone.web 7 | 8 | from $modname import views 9 | from $modname import config 10 | from $modname.storage import DatabaseMixin 11 | 12 | 13 | class Application(cyclone.web.Application): 14 | def __init__(self, config_file): 15 | conf = config.parse_config(config_file) 16 | handlers = [ 17 | (r"/", views.IndexHandler), 18 | (r"/lang/(.+)", views.LangHandler), 19 | (r"/dashboard", views.DashboardHandler), 20 | (r"/account", views.AccountHandler), 21 | (r"/signup", views.SignUpHandler), 22 | (r"/signin", views.SignInHandler), 23 | (r"/signout", views.SignOutHandler), 24 | (r"/passwd", views.PasswdHandler), 25 | (r"/legal", cyclone.web.RedirectHandler, 26 | {"url": "/static/legal.txt"}), 27 | ] 28 | 29 | # Initialize locales 30 | if "locale_path" in conf: 31 | cyclone.locale.load_gettext_translations(conf["locale_path"], 32 | "$modname") 33 | 34 | # Set up database connections 35 | DatabaseMixin.setup(conf) 36 | 37 | conf["login_url"] = "/signin" 38 | conf["autoescape"] = None 39 | cyclone.web.Application.__init__(self, handlers, **conf) 40 | -------------------------------------------------------------------------------- /appskel/signup/scripts/cookie_secret.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | # 4 | $license 5 | 6 | import base64 7 | import uuid 8 | 9 | if __name__ == "__main__": 10 | print(base64.b64encode(uuid.uuid4().bytes + uuid.uuid4().bytes)) 11 | -------------------------------------------------------------------------------- /appskel/signup/scripts/debian-init.d: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ### BEGIN INIT INFO 4 | # Provides: $modname 5 | # Required-Start: $$all 6 | # Required-Stop: $$all 7 | # Default-Start: 2 3 4 5 8 | # Default-Stop: 0 1 6 9 | # Short-Description: Starts a service on the cyclone web server 10 | # Description: Foobar 11 | ### END INIT INFO 12 | 13 | PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin 14 | DAEMON=/usr/bin/twistd 15 | 16 | SERVICE_DIR=/path/to/$modname 17 | SERVICE_NAME=$modname 18 | 19 | PYTHONPATH=$$SERVICE_DIR:$$PYTHONPATH 20 | export PYTHONPATH 21 | 22 | PORT=8888 23 | LISTEN="127.0.0.1" 24 | CONFIG=$$SERVICE_DIR/$$SERVICE_NAME.conf 25 | PIDFILE=/var/run/$$SERVICE_NAME.pid 26 | LOGFILE=/var/log/$$SERVICE_NAME.log 27 | APP=$${SERVICE_NAME}.web.Application 28 | 29 | USER=www-data 30 | GROUP=www-data 31 | DAEMON_OPTS="-u $$USER -g $$GROUP --pidfile=$$PIDFILE --logfile=$$LOGFILE cyclone --port $$PORT --listen $$LISTEN --app $$APP -c $$CONFIG" 32 | 33 | if [ ! -x $$DAEMON ]; then 34 | echo "ERROR: Can't execute $$DAEMON." 35 | exit 1 36 | fi 37 | 38 | if [ ! -d $$SERVICE_DIR ]; then 39 | echo "ERROR: Directory doesn't exist: $$SERVICE_DIR" 40 | exit 1 41 | fi 42 | 43 | start_service() { 44 | echo -n " * Starting $$SERVICE_NAME... " 45 | start-stop-daemon -Sq -p $$PIDFILE -x $$DAEMON -- $$DAEMON_OPTS 46 | e=$$? 47 | if [ $$e -eq 1 ]; then 48 | echo "already running" 49 | return 50 | fi 51 | 52 | if [ $$e -eq 255 ]; then 53 | echo "couldn't start" 54 | return 55 | fi 56 | 57 | echo "done" 58 | } 59 | 60 | stop_service() { 61 | echo -n " * Stopping $$SERVICE_NAME... " 62 | start-stop-daemon -Kq -R 10 -p $$PIDFILE 63 | e=$$? 64 | if [ $$e -eq 1 ]; then 65 | echo "not running" 66 | return 67 | fi 68 | 69 | echo "done" 70 | } 71 | 72 | case "$$1" in 73 | start) 74 | start_service 75 | ;; 76 | stop) 77 | stop_service 78 | ;; 79 | restart) 80 | stop_service 81 | start_service 82 | ;; 83 | *) 84 | echo "Usage: /etc/init.d/$$SERVICE_NAME {start|stop|restart}" >&2 85 | exit 1 86 | ;; 87 | esac 88 | 89 | exit 0 90 | -------------------------------------------------------------------------------- /appskel/signup/scripts/debian-multicore-init.d: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ### BEGIN INIT INFO 4 | # Provides: $modname 5 | # Required-Start: $$all 6 | # Required-Stop: $$all 7 | # Default-Start: 2 3 4 5 8 | # Default-Stop: 0 1 6 9 | # Short-Description: Starts a service on the cyclone web server 10 | # Description: Foobar 11 | ### END INIT INFO 12 | 13 | PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin 14 | DAEMON=/usr/bin/twistd 15 | 16 | SERVICE_DIR=/path/to/$modname 17 | SERVICE_NAME=$modname 18 | 19 | PYTHONPATH=$$SERVICE_DIR:$$PYTHONPATH 20 | export PYTHONPATH 21 | 22 | INSTANCES=4 23 | START_PORT=9901 24 | LISTEN="127.0.0.1" 25 | CONFIG=$$SERVICE_DIR/$$SERVICE_NAME.conf 26 | APP=$${SERVICE_NAME}.web.Application 27 | 28 | USER=www-data 29 | GROUP=www-data 30 | 31 | # Check out the start_service function for other customization options 32 | # such as setting CPU affinity. 33 | 34 | if [ ! -x $$DAEMON ]; then 35 | echo "ERROR: Can't execute $$DAEMON." 36 | exit 1 37 | fi 38 | 39 | if [ ! -d $$SERVICE_DIR ]; then 40 | echo "ERROR: Directory doesn't exist: $$SERVICE_DIR" 41 | exit 1 42 | fi 43 | 44 | start_service() { 45 | echo -n " * Starting $$SERVICE_NAME... " 46 | for n in `seq 1 $$INSTANCES` 47 | do 48 | PORT=$$[START_PORT] 49 | PIDFILE=/var/run/$$SERVICE_NAME.$$PORT.pid 50 | LOGFILE=/var/log/$$SERVICE_NAME.$$PORT.log 51 | DAEMON_OPTS="-u $$USER -g $$GROUP --pidfile=$$PIDFILE --logfile=$$LOGFILE cyclone --port $$PORT --listen $$LISTEN --app $$APP -c $$CONFIG" 52 | START_PORT=$$[PORT+1] 53 | 54 | start-stop-daemon -Sq -p $$PIDFILE -x $$DAEMON -- $$DAEMON_OPTS 55 | e=$$? 56 | if [ $$e -eq 1 ]; then 57 | echo "already running" 58 | return 59 | fi 60 | 61 | if [ $$e -eq 255 ]; then 62 | echo "couldn't start" 63 | return 64 | fi 65 | 66 | # Set CPU affinity 67 | if [ -x /usr/bin/taskset ]; then 68 | sleep 1 69 | /usr/bin/taskset -pc $$n `cat $$PIDFILE` &> /dev/null 70 | fi 71 | done 72 | echo "done" 73 | } 74 | 75 | stop_service() { 76 | echo -n " * Stopping $$SERVICE_NAME... " 77 | for n in `seq 1 $$INSTANCES` 78 | do 79 | PORT=$$[START_PORT] 80 | PIDFILE=/var/run/$$SERVICE_NAME.$$PORT.pid 81 | START_PORT=$$[PORT+1] 82 | start-stop-daemon -Kq -R 10 -p $$PIDFILE 83 | e=$$? 84 | if [ $$e -eq 1 ]; then 85 | echo "not running" 86 | return 87 | fi 88 | done 89 | 90 | echo "done" 91 | } 92 | 93 | case "$$1" in 94 | start) 95 | start_service 96 | ;; 97 | stop) 98 | stop_service 99 | ;; 100 | restart) 101 | sp=$$START_PORT 102 | stop_service 103 | START_PORT=$$sp 104 | start_service 105 | ;; 106 | *) 107 | echo "Usage: /etc/init.d/$$SERVICE_NAME {start|stop|restart}" >&2 108 | exit 1 109 | ;; 110 | esac 111 | 112 | exit 0 113 | -------------------------------------------------------------------------------- /appskel/signup/scripts/localefix.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | # 4 | $license 5 | 6 | import re 7 | import sys 8 | 9 | if __name__ == "__main__": 10 | try: 11 | filename = sys.argv[1] 12 | assert filename != "-" 13 | fd = open(filename) 14 | except: 15 | fd = sys.stdin 16 | 17 | line_re = re.compile(r'="([^"]+)"') 18 | for line in fd: 19 | line = line_re.sub(r"=\\1", line) 20 | sys.stdout.write(line) 21 | fd.close() 22 | -------------------------------------------------------------------------------- /appskel/signup/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # see scripts/debian-init.d for production deployments 3 | 4 | export PYTHONPATH=`dirname $$0` 5 | twistd -n cyclone -p 8888 -l 0.0.0.0 \ 6 | -r $modname.web.Application -c $modname.conf $$* 7 | -------------------------------------------------------------------------------- /cyclone/__init__.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | # 3 | # Copyright 2010 Alexandre Fiori 4 | # based on the original Tornado by Facebook 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 7 | # not use this file except in compliance with the License. You may obtain 8 | # a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 14 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 15 | # License for the specific language governing permissions and limitations 16 | # under the License. 17 | 18 | __author__ = "Alexandre Fiori" 19 | __version__ = version = "1.2" 20 | -------------------------------------------------------------------------------- /cyclone/appskel_default.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fiorix/cyclone/fefdc51dbf25b7470467fc8db6cf96d08191cf28/cyclone/appskel_default.zip -------------------------------------------------------------------------------- /cyclone/appskel_foreman.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fiorix/cyclone/fefdc51dbf25b7470467fc8db6cf96d08191cf28/cyclone/appskel_foreman.zip -------------------------------------------------------------------------------- /cyclone/appskel_signup.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fiorix/cyclone/fefdc51dbf25b7470467fc8db6cf96d08191cf28/cyclone/appskel_signup.zip -------------------------------------------------------------------------------- /cyclone/jsonrpc.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | # 3 | # Copyright 2010 Alexandre Fiori 4 | # based on the original Tornado by Facebook 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 7 | # not use this file except in compliance with the License. You may obtain 8 | # a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 14 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 15 | # License for the specific language governing permissions and limitations 16 | # under the License. 17 | 18 | """Server-side implementation of the JSON-RPC protocol. 19 | 20 | `JSON-RPC `_ is a lightweight remote 21 | procedure call protocol, designed to be simple. 22 | 23 | For more information, check out the `RPC demo 24 | `_. 25 | """ 26 | 27 | import cyclone.escape 28 | from cyclone.web import HTTPError, RequestHandler 29 | 30 | from twisted.internet import defer 31 | from twisted.python import log, failure 32 | 33 | 34 | class JsonrpcRequestHandler(RequestHandler): 35 | """Subclass this class and define jsonrpc_* to make a handler. 36 | 37 | Example:: 38 | 39 | class MyRequestHandler(JsonrpcRequestHandler): 40 | def jsonrpc_echo(self, text): 41 | return text 42 | 43 | def jsonrpc_sort(self, items): 44 | return sorted(items) 45 | 46 | @defer.inlineCallbacks 47 | def jsonrpc_geoip_lookup(self, address): 48 | response = yield cyclone.httpclient.fetch( 49 | "http://freegeoip.net/json/%s" % address.encode("utf-8")) 50 | defer.returnValue(response.body) 51 | """ 52 | def post(self, *args): 53 | self._auto_finish = False 54 | try: 55 | req = cyclone.escape.json_decode(self.request.body) 56 | jsonid = req["id"] 57 | method = req["method"] 58 | assert isinstance(method, str), "Invalid method type: %s" % type(method) 59 | params = req.get("params", []) 60 | assert isinstance(params, (list, tuple)), "Invalid params type: %s" % type(params) 61 | except Exception as e: 62 | log.msg("Bad Request: %s" % str(e)) 63 | raise HTTPError(400) 64 | 65 | function = getattr(self, "jsonrpc_%s" % method, None) 66 | if callable(function): 67 | args = list(args) + params 68 | d = defer.maybeDeferred(function, *args) 69 | d.addBoth(self._cbResult, jsonid) 70 | else: 71 | self._cbResult(AttributeError("method not found: %s" % method), 72 | jsonid) 73 | 74 | def set_default_headers(self): 75 | self.set_header('Content-Type', 'application/json; charset=UTF-8') 76 | 77 | def _cbResult(self, result, jsonid): 78 | if isinstance(result, failure.Failure): 79 | error = {'code': 0, 'message': str(result.value)} 80 | result = None 81 | else: 82 | error = None 83 | data = {"result": result, "error": error, "id": jsonid} 84 | self.finish(cyclone.escape.json_encode(data)) 85 | -------------------------------------------------------------------------------- /cyclone/sqlite.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | # 3 | # Copyright 2010 Alexandre Fiori 4 | # based on the original Tornado by Facebook 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 7 | # not use this file except in compliance with the License. You may obtain 8 | # a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 14 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 15 | # License for the specific language governing permissions and limitations 16 | # under the License. 17 | 18 | """An inline SQLite helper class. 19 | 20 | All queries run inline, temporarily blocking the execution. Please make sure 21 | you understand the limitations of using SQLite like this. 22 | 23 | Example:: 24 | 25 | import cyclone.web 26 | import cyclone.sqlite 27 | 28 | class SQLiteMixin(object): 29 | sqlite = cyclone.sqlite.InlineSQLite("mydb.sqlite") 30 | 31 | class MyRequestHandler(cyclone.web.RequestHandler): 32 | def get(self): 33 | rs = self.sqlite.runQuery("SELECT 1") 34 | ... 35 | 36 | There is no ``Deferred`` responses, and no need to ``yield`` anything. 37 | """ 38 | 39 | import sqlite3 40 | 41 | 42 | class InlineSQLite: 43 | """An inline SQLite instance""" 44 | def __init__(self, dbname=":memory:", autoCommit=True): 45 | """Create new SQLite instance.""" 46 | self.autoCommit = autoCommit 47 | self.conn = sqlite3.connect(dbname) 48 | self.curs = self.conn.cursor() 49 | 50 | def runQuery(self, query, *args, **kwargs): 51 | """Use this function to execute queries that return a result set, 52 | like ``SELECT``. 53 | 54 | Example (with variable substitution):: 55 | 56 | sqlite.runQuery("SELECT * FROM asd WHERE x=? and y=?", [x, y]) 57 | """ 58 | self.curs.execute(query, *args, **kwargs) 59 | return [row for row in self.curs] 60 | 61 | def runOperation(self, command, *args, **kwargs): 62 | """Use this function to execute queries that do NOT return a result 63 | set, like ``INSERT``, ``UPDATE`` and ``DELETE``. 64 | 65 | Example:: 66 | 67 | sqlite.runOperation("CREATE TABLE asd (x int, y text)") 68 | sqlite.runOperation("INSERT INTO asd VALUES (?, ?)", [x, y]) 69 | """ 70 | self.curs.execute(command, *args, **kwargs) 71 | if self.autoCommit is True: 72 | self.conn.commit() 73 | 74 | def runOperationMany(self, command, *args, **kwargs): 75 | """Same as `runOperation`, but for multiple rows. 76 | 77 | Example:: 78 | 79 | sqlite.runOperationMany("INSERT INTO asd VALUES (?, ?)", [ 80 | [x1, y1], [x2, y2], [x3, y3] 81 | ]) 82 | """ 83 | self.curs.executemany(command, *args, **kwargs) 84 | if self.autoCommit is True: 85 | self.conn.commit() 86 | 87 | def commit(self): 88 | """Commits pending transactions""" 89 | self.conn.commit() 90 | 91 | def rollback(self): 92 | """Gives up pending transactions""" 93 | self.conn.rollback() 94 | 95 | def close(self): 96 | """Destroys the instance""" 97 | self.conn.close() 98 | -------------------------------------------------------------------------------- /cyclone/testing/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2014 David Novakovic 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 5 | # not use this file except in compliance with the License. You may obtain 6 | # a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # License for the specific language governing permissions and limitations 14 | # under the License. 15 | 16 | from .testcase import CycloneTestCase 17 | from .client import Client 18 | -------------------------------------------------------------------------------- /cyclone/testing/testcase.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2014 David Novakovic 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 5 | # not use this file except in compliance with the License. You may obtain 6 | # a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # License for the specific language governing permissions and limitations 14 | # under the License. 15 | 16 | from twisted.trial import unittest 17 | from .client import Client 18 | 19 | 20 | class CycloneTestCase(unittest.TestCase, object): 21 | client_impl = Client 22 | app_builder = None 23 | 24 | def __init__(self, *args, **kwargs): 25 | """ 26 | Create a test case for a cyclone app. 27 | 28 | The ``app_builder`` param should be a function that returns a 29 | cyclone.web.Application instance will all the appropriate handlers 30 | loaded etc. 31 | 32 | For most use cases this should be as simple as creating a function 33 | that returns you application instead of just declaring it in a file 34 | somewhere. 35 | """ 36 | app_builder = None 37 | if "app_builder" in kwargs: 38 | app_builder = kwargs.pop("app_builder") 39 | if not app_builder and not self.app_builder: 40 | raise ValueError( 41 | "You need to either pass an app_builder named param to " 42 | "__init__ or set the app_builder attribute on your test case. " 43 | "it should be a callable that returns an app instance for " 44 | "your app. The Application class for your project may work." 45 | ) 46 | super(CycloneTestCase, self).__init__(*args, **kwargs) 47 | builder = app_builder or self.app_builder 48 | self._app = builder() 49 | self.client = self.client_impl(self._app) 50 | -------------------------------------------------------------------------------- /cyclone/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fiorix/cyclone/fefdc51dbf25b7470467fc8db6cf96d08191cf28/cyclone/tests/__init__.py -------------------------------------------------------------------------------- /cyclone/tests/test_app.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2014 David Novakovic 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 5 | # not use this file except in compliance with the License. You may obtain 6 | # a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # License for the specific language governing permissions and limitations 14 | # under the License. 15 | 16 | from twisted.trial import unittest 17 | 18 | 19 | class AppTests(unittest.TestCase): 20 | def test_something(self): 21 | pass -------------------------------------------------------------------------------- /cyclone/tests/test_auth.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | # 3 | # Copyright 2010 Alexandre Fiori 4 | # based on the original Tornado by Facebook 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 7 | # not use this file except in compliance with the License. You may obtain 8 | # a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 14 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 15 | # License for the specific language governing permissions and limitations 16 | # under the License. 17 | 18 | from urllib import parse as urllib_parse 19 | import cyclone.web 20 | 21 | from twisted.trial import unittest 22 | from twisted.internet import defer 23 | from cyclone.auth import FacebookGraphMixin 24 | from unittest.mock import patch, MagicMock 25 | 26 | 27 | class TestHandler(cyclone.web.RequestHandler, 28 | FacebookGraphMixin): 29 | pass 30 | 31 | 32 | class TestFacebookGraphMixin(unittest.TestCase): 33 | def setUp(self): 34 | self.fgm = TestHandler(MagicMock(), MagicMock()) 35 | 36 | @patch('cyclone.auth.httpclient.fetch') 37 | def test_facebook_request_post(self, mock): 38 | _args = {'message': 'test message'} 39 | self.fgm.facebook_request( 40 | "/me/feed", 41 | callback=self.fgm.async_callback(lambda x: x), 42 | access_token='dummy_token', 43 | post_args=_args 44 | ) 45 | 46 | self.assertTrue(mock.called) 47 | args, kwargs = mock.call_args 48 | self.assertIn('postdata', kwargs) 49 | self.assertEqual(kwargs['postdata'], 50 | urllib_parse.urlencode(_args)) 51 | -------------------------------------------------------------------------------- /cyclone/tests/test_db.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2014 David Novakovic 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 5 | # not use this file except in compliance with the License. You may obtain 6 | # a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # License for the specific language governing permissions and limitations 14 | # under the License. 15 | 16 | from twisted.trial import unittest 17 | from cyclone.sqlite import InlineSQLite 18 | from unittest.mock import Mock 19 | 20 | 21 | class InlineSQLiteTest(unittest.TestCase): 22 | def setUp(self): 23 | self.isq = InlineSQLite() 24 | self.isq.runOperation( 25 | "create table `nothing` (val1 string, val2 string)") 26 | self.isq.runOperation('insert into `nothing` values ("a", "b")') 27 | 28 | def test_init(self): 29 | self.assertTrue(hasattr(self.isq, "autoCommit")) 30 | self.assertTrue(hasattr(self.isq, "conn")) 31 | self.assertTrue(hasattr(self.isq, "curs")) 32 | 33 | def test_runQuery(self): 34 | self.isq.curs = Mock() 35 | self.isq.curs.__iter__ = Mock(return_value=iter([1, 2, 3])) 36 | res = self.isq.runQuery("a query") 37 | self.assertEqual(res, [1, 2, 3]) 38 | 39 | def test_runOperation(self): 40 | self.isq.runOperation('insert into `nothing` values ("c", "d")') 41 | res = self.isq.runQuery("select count(*) from `nothing`") 42 | self.assertEqual(res[0][0], 2) 43 | 44 | def test_runOperationMany(self): 45 | self.isq.runOperationMany( 46 | 'insert into `nothing` values (?, ?)', 47 | [["a", "b"], ["c", "d"]] 48 | ) 49 | res = self.isq.runQuery("select count(*) from `nothing`") 50 | self.assertEqual(res[0][0], 3) 51 | 52 | def test_commit(self): 53 | self.isq.conn = Mock() 54 | self.isq.commit() 55 | self.isq.conn.commit.assert_called_with() 56 | 57 | def test_rollback(self): 58 | self.isq.conn = Mock() 59 | self.isq.rollback() 60 | self.isq.conn.rollback.assert_called_with() 61 | 62 | def test_close(self): 63 | self.isq.conn = Mock() 64 | self.isq.close() 65 | self.isq.conn.close.assert_called_with() 66 | -------------------------------------------------------------------------------- /cyclone/tests/test_escape.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2014 David Novakovic 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 5 | # not use this file except in compliance with the License. You may obtain 6 | # a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # License for the specific language governing permissions and limitations 14 | # under the License. 15 | 16 | from twisted.trial import unittest 17 | from unittest.mock import Mock 18 | 19 | from cyclone import escape 20 | 21 | 22 | class TestEscape(unittest.TestCase): 23 | 24 | def test_xhtml(self): 25 | self.assertEqual( 26 | escape.xhtml_escape("abc42"), 27 | "abc42" 28 | ) 29 | self.assertEqual( 30 | escape.xhtml_escape("<>"), 31 | "<>" 32 | ) 33 | self.assertEqual( 34 | escape.xhtml_escape("\"'"), 35 | ""'" 36 | ) -------------------------------------------------------------------------------- /cyclone/tests/test_httputil.py: -------------------------------------------------------------------------------- 1 | from twisted.trial import unittest 2 | 3 | from cyclone.httputil import HTTPHeaders 4 | 5 | 6 | class TestHTTPHeaders(unittest.TestCase): 7 | def test_parse_no_cr(self): 8 | """ 9 | https://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html#sec19.3 10 | "The line terminator for message-header fields is the sequence CRLF. 11 | However, we recommend that applications, when parsing such headers, 12 | recognize a single LF as a line terminator and ignore the leading CR." 13 | 14 | https://tools.ietf.org/html/rfc7230#section-3.5 15 | "Although the line terminator for the start-line and header fields is 16 | the sequence CRLF, a recipient MAY recognize a single LF as a line 17 | terminator and ignore any preceding CR." 18 | """ 19 | header_data = u"Foo: bar\n" u"Baz: qux" 20 | headers = HTTPHeaders.parse(header_data) 21 | self.assertEqual(len(list(headers.get_all())), 2) 22 | self.assertEqual(headers.get("foo"), "bar") 23 | self.assertEqual(headers.get("baz"), "qux") 24 | 25 | def test_parse_crlf(self): 26 | """ 27 | https://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html#sec19.3 28 | "The line terminator for message-header fields is the sequence CRLF. 29 | However, we recommend that applications, when parsing such headers, 30 | recognize a single LF as a line terminator and ignore the leading CR." 31 | """ 32 | header_data = u"Foo: bar\r\n" u"Baz: qux" 33 | headers = HTTPHeaders.parse(header_data) 34 | self.assertEqual(len(list(headers.get_all())), 2) 35 | self.assertEqual(headers.get("foo"), "bar") 36 | self.assertEqual(headers.get("baz"), "qux") 37 | 38 | def test_parse_problematic_newlines(self): 39 | """ 40 | There are some problematic characters that Python considers to be newlines 41 | for the purpose of splitlines, but aren't newlines per the RFCs. 42 | 43 | https://docs.python.org/3/library/stdtypes.html#str.splitlines 44 | """ 45 | header_data = ( 46 | u"Foo: bar\x0b\x0c\x1c\x1d\x1e\x85\u2028\u2029asdf: jkl\r\n" u"Baz: qux" 47 | ) 48 | headers = HTTPHeaders.parse(header_data) 49 | self.assertEqual(len(list(headers.get_all())), 2) 50 | self.assertEqual( 51 | headers.get("foo"), u"bar\x0b\x0c\x1c\x1d\x1e\x85\u2028\u2029asdf: jkl" 52 | ) 53 | self.assertEqual(headers.get("baz"), "qux") 54 | -------------------------------------------------------------------------------- /cyclone/tests/test_mail.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2014 David Novakovic 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 5 | # not use this file except in compliance with the License. You may obtain 6 | # a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # License for the specific language governing permissions and limitations 14 | # under the License. 15 | from twisted.trial import unittest 16 | from cyclone.mail import ContextFactory, ClientContextFactory, Message 17 | from cyclone.mail import sendmail 18 | from unittest.mock import Mock, patch 19 | 20 | 21 | class ContextFactoryTest(unittest.TestCase): 22 | def test_no_sslv3(self): 23 | """We must disable ssl v3.""" 24 | ClientContextFactory.getContext = Mock() 25 | cf = ContextFactory() 26 | ctx = cf.getContext() 27 | ctx.set_options.assert_called_with(33554432) 28 | 29 | 30 | class MessageTest(unittest.TestCase): 31 | def setUp(self): 32 | self.message = Message( 33 | "foo@example.com", 34 | ["bar@example.com"], 35 | "hi thar", 36 | "This is a message." 37 | ) 38 | 39 | def test_init(self): 40 | self.assertTrue(self.message.message) 41 | str(self.message) 42 | 43 | def test_init_single_addr(self): 44 | message = Message( 45 | "foo@example.com", 46 | "bar@example.com", 47 | "hi thar", 48 | "This is a message." 49 | ) 50 | self.assertTrue(isinstance(message.to_addrs, list)) 51 | 52 | def test_attach(self): 53 | open("foo.txt", "w").write("sometext") 54 | self.message.attach("foo.txt") 55 | self.assertTrue(self.message.msg) 56 | 57 | def test_render(self): 58 | self.message.add_header("X-MailTag", "foobar") 59 | sio = self.message.render() 60 | self.assertTrue("foo@example.com" in sio.getvalue()) 61 | 62 | @patch("cyclone.mail.reactor.connectTCP") 63 | def test_sendmail(self, conn): 64 | sendmail( 65 | {"host": "localhost", "tls": True}, 66 | self.message 67 | ) 68 | self.assertTrue(conn.call_count) 69 | -------------------------------------------------------------------------------- /cyclone/tests/test_requirements.txt: -------------------------------------------------------------------------------- 1 | mock 2 | -------------------------------------------------------------------------------- /cyclone/util.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | # 3 | # Copyright 2010 Alexandre Fiori 4 | # based on the original Tornado by Facebook 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 7 | # not use this file except in compliance with the License. You may obtain 8 | # a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 14 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 15 | # License for the specific language governing permissions and limitations 16 | # under the License. 17 | 18 | from twisted.python import log 19 | 20 | 21 | def _emit(self, eventDict): 22 | text = log.textFromEventDict(eventDict) 23 | if not text: 24 | return 25 | 26 | timeStr = self.formatTime(eventDict['time']) 27 | log.util.untilConcludes(self.write, "%s %s\n" % (timeStr, 28 | text.replace("\n", "\n\t"))) 29 | log.util.untilConcludes(self.flush) 30 | 31 | log.FileLogObserver.emit = _emit 32 | 33 | 34 | class ObjectDict(dict): 35 | """Makes a dictionary behave like an object.""" 36 | def __getattr__(self, name): 37 | try: 38 | return self[name] 39 | except KeyError: 40 | raise AttributeError(name) 41 | 42 | def __setattr__(self, name, value): 43 | self[name] = value 44 | 45 | 46 | def import_object(name): 47 | """Imports an object by name. 48 | 49 | import_object('x.y.z') is equivalent to 'from x.y import z'. 50 | 51 | >>> import cyclone.escape 52 | >>> import_object('cyclone.escape') is cyclone.escape 53 | True 54 | >>> import_object('cyclone.escape.utf8') is cyclone.escape.utf8 55 | True 56 | """ 57 | parts = name.split('.') 58 | obj = __import__('.'.join(parts[:-1]), None, None, [parts[-1]], 0) 59 | method = getattr(obj, parts[-1], None) 60 | if method: 61 | return method 62 | else: 63 | raise ImportError("No method named %s" % parts[-1]) 64 | 65 | 66 | bytes_type = bytes 67 | unicode_type = str 68 | basestring_type = str 69 | 70 | def doctests(): # pragma: no cover 71 | import doctest 72 | return doctest.DocTestSuite() 73 | -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | python-cyclone (1.0) unstable; urgency=low 2 | 3 | * Updated docs 4 | * Synch with latest txredisapi 5 | 6 | -- Alexandre Fiori Mon, 31 Dec 2012 11:02:13 -0500 7 | 8 | python-cyclone (1.0-rc16) unstable; urgency=low 9 | 10 | * New skel: sign up 11 | 12 | -- Alexandre Fiori Wed, 12 Dec 2012 10:31:40 -0500 13 | 14 | python-cyclone (1.0-rc15) unstable; urgency=low 15 | 16 | * Updated app skel 17 | * Updated setup.py for pep8 compliance 18 | 19 | -- Alexandre Fiori Mon, 10 Dec 2012 12:00:20 -0500 20 | 21 | python-cyclone (1.0-rc11) unstable; urgency=low 22 | 23 | * Fix postinst script to install twisted plugin o python2.5 as well as 24 | python2.6 25 | 26 | -- Rodrigo Sampaio Vaz Tue, 21 Ago 2012 15:48:22 -0300 27 | 28 | python-cyclone (1.0-rc9) unstable; urgency=low 29 | 30 | * Version synch with git repo 31 | 32 | -- Alexandre Fiori Sat, 16 Jun 2012 12:59:22 -0400 33 | 34 | python-cyclone (1.0-rc8) unstable; urgency=low 35 | 36 | * Version synch with git repo 37 | 38 | -- Alexandre Fiori Sat, 16 Jun 2012 11:30:42 -0400 39 | 40 | python-cyclone (1.0-rc7) unstable; urgency=low 41 | 42 | * Version synch with git repo 43 | 44 | -- Alexandre Fiori Sun, 10 Jun 2012 22:33:13 -0400 45 | 46 | python-cyclone (0.1-1) unstable; urgency=low 47 | 48 | * Initial debian package 49 | 50 | -- Rodrigo Sampaio Vaz Mon, 26 Dec 2011 03:08:38 -0200 51 | -------------------------------------------------------------------------------- /debian/compat: -------------------------------------------------------------------------------- 1 | 8 2 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: python-cyclone 2 | Section: python 3 | Priority: extra 4 | Maintainer: Rodrigo Sampaio Vaz 5 | Build-Depends: debhelper (>= 8.0.0), python-setuptools 6 | Standards-Version: 3.9.2 7 | Homepage: http://cyclone.io 8 | Vcs-Git: git://github.com/fiorix/cyclone.git 9 | 10 | Package: python-cyclone 11 | Architecture: any 12 | Depends: python-twisted-core, python-twisted-web, python-twisted-mail 13 | Description: cyclone is a clone of facebook's tornado, on top of twisted. 14 | cyclone is a web framework based on tornado, but designed to run on top of 15 | the most popular and stable event-driven framework for python, twisted. 16 | It implements HTTP 1.1 and supports many protocols, such as WebSocket and SSE, 17 | as well as SSL. 18 | -------------------------------------------------------------------------------- /debian/docs: -------------------------------------------------------------------------------- 1 | README.rst 2 | -------------------------------------------------------------------------------- /debian/postinst: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | set -e 4 | 5 | #DEBHELPER# 6 | 7 | rebuild_cache() 8 | { 9 | # remove all cache files, then rebuild for the installed python versions 10 | rm -f /usr/lib/python[23].?/*-packages/twisted/plugins/dropin.cache 11 | for p in $(pyversions -i); do 12 | $p -c 'from twisted.plugin import IPlugin, getPlugins; list(getPlugins(IPlugin))' \ 13 | >/dev/null 2>&1 || true 14 | if [ -d /usr/lib/${p}/dist-packages/twisted/plugins ]; then 15 | ln -sf /usr/share/pyshared/twisted/plugins/cyclone_plugin.py /usr/lib/${p}/dist-packages/twisted/plugins/ 16 | elif [ -d /usr/lib/${p}/site-packages/twisted/plugins ]; then 17 | ln -sf /usr/share/pyshared/twisted/plugins/cyclone_plugin.py /usr/lib/${p}/site-packages/twisted/plugins/ 18 | fi 19 | 20 | done 21 | } 22 | 23 | case "$1" in 24 | triggered) 25 | if [ "$2" = twisted-plugins-cache ]; then 26 | rebuild_cache 27 | fi 28 | ;; 29 | configure) 30 | rebuild_cache 31 | ;; 32 | esac 33 | 34 | exit 0 35 | -------------------------------------------------------------------------------- /debian/postrm: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | set -e 4 | for p in $(pyversions -i); do 5 | rm -f /usr/lib/${p}/dist-packages/twisted/plugins/cyclone* &> /dev/null 6 | done 7 | 8 | exit 0 9 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | # -*- makefile -*- 3 | # Sample debian/rules that uses debhelper. 4 | # This file was originally written by Joey Hess and Craig Small. 5 | # As a special exception, when this file is copied by dh-make into a 6 | # dh-make output file, you may use that output file without restriction. 7 | # This special exception was added by Craig Small in version 0.37 of dh-make. 8 | 9 | # Uncomment this to turn on verbose mode. 10 | #export DH_VERBOSE=1 11 | 12 | %: 13 | dh $@ 14 | -------------------------------------------------------------------------------- /demos/auth/authdemo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | # 4 | # Copyright 2010 Alexandre Fiori 5 | # based on the original Tornado by Facebook 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 8 | # not use this file except in compliance with the License. You may obtain 9 | # a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 15 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 16 | # License for the specific language governing permissions and limitations 17 | # under the License. 18 | 19 | import sys 20 | 21 | import cyclone.auth 22 | import cyclone.escape 23 | import cyclone.web 24 | 25 | from twisted.python import log 26 | from twisted.internet import reactor 27 | 28 | 29 | class Application(cyclone.web.Application): 30 | def __init__(self): 31 | handlers = [ 32 | (r"/", MainHandler), 33 | (r"/auth/login", AuthHandler), 34 | (r"/auth/logout", LogoutHandler), 35 | ] 36 | settings = dict( 37 | cookie_secret="32oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2XdTP1o/Vo=", 38 | debug=True, 39 | login_url="/auth/login", 40 | ) 41 | cyclone.web.Application.__init__(self, handlers, **settings) 42 | 43 | 44 | class BaseHandler(cyclone.web.RequestHandler): 45 | def get_current_user(self): 46 | user_json = self.get_secure_cookie("user") 47 | if not user_json: 48 | return None 49 | return cyclone.escape.json_decode(user_json) 50 | 51 | 52 | class MainHandler(BaseHandler): 53 | @cyclone.web.authenticated 54 | def get(self): 55 | name = cyclone.escape.xhtml_escape(self.current_user["name"]) 56 | self.write("Hello, " + name) 57 | self.write("

Log out") 58 | 59 | 60 | class AuthHandler(BaseHandler, cyclone.auth.GoogleMixin): 61 | @cyclone.web.asynchronous 62 | def get(self): 63 | if self.get_argument("openid.mode", None): 64 | self.get_authenticated_user(self._on_auth) 65 | return 66 | self.authenticate_redirect() 67 | 68 | def _on_auth(self, user): 69 | if not user: 70 | raise cyclone.web.HTTPError(500, "Google auth failed") 71 | self.set_secure_cookie("user", cyclone.escape.json_encode(user)) 72 | self.redirect("/") 73 | 74 | 75 | class LogoutHandler(BaseHandler): 76 | def get(self): 77 | self.clear_cookie("user") 78 | self.redirect("/") 79 | 80 | 81 | def main(): 82 | log.startLogging(sys.stdout) 83 | reactor.listenTCP(8888, Application(), interface="127.0.0.1") 84 | reactor.run() 85 | 86 | 87 | if __name__ == "__main__": 88 | main() 89 | -------------------------------------------------------------------------------- /demos/bottle/README: -------------------------------------------------------------------------------- 1 | This is a complete cyclone.bottle demo. 2 | For a basic hello world, check out the helloworld_bottle.py demo. 3 | 4 | The idea is to show how easy it is to create simple bottle-like apps, and 5 | still capture all the functionality such as database support, setting 6 | a parent class (the BaseHandler) for your request handlers and integrating with 7 | other types of handlers such as XmlRPC and WebSocket. 8 | 9 | The authentication mechanism is similar to the one used in 10 | httpauthdemo_redis.py and require that the "cyclone:user" key exists in redis. 11 | 12 | Basically, run the redis server and use the redis client to set up a fake 13 | user account: 14 | 15 | $ redis-cli set cyclone:root 123 16 | 17 | Then authenticate yourself at http://localhost:8888 as root/123. 18 | 19 | XmlRPC functionality can be tested by executing the xmlrpc_client.py. 20 | -------------------------------------------------------------------------------- /demos/bottle/xmlrpc_client.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | # 4 | # Copyright 2010 Alexandre Fiori 5 | # based on the original Tornado by Facebook 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 8 | # not use this file except in compliance with the License. You may obtain 9 | # a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 15 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 16 | # License for the specific language governing permissions and limitations 17 | # under the License. 18 | 19 | import xmlrpclib 20 | 21 | srv = xmlrpclib.Server("http://localhost:8888/xmlrpc") 22 | print "echo:", srv.echo("hello world!") 23 | -------------------------------------------------------------------------------- /demos/chat/static/chat.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2009 FriendFeed 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 5 | * not use this file except in compliance with the License. You may obtain 6 | * a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | body { 18 | background: white; 19 | margin: 10px; 20 | } 21 | 22 | body, 23 | input { 24 | font-family: sans-serif; 25 | font-size: 10pt; 26 | color: black; 27 | } 28 | 29 | table { 30 | border-collapse: collapse; 31 | border: 0; 32 | } 33 | 34 | td { 35 | border: 0; 36 | padding: 0; 37 | } 38 | 39 | #body { 40 | position: absolute; 41 | bottom: 10px; 42 | left: 10px; 43 | } 44 | 45 | #input { 46 | margin-top: 0.5em; 47 | } 48 | 49 | #inbox .message { 50 | padding-top: 0.25em; 51 | } 52 | 53 | #nav { 54 | float: right; 55 | z-index: 99; 56 | } 57 | -------------------------------------------------------------------------------- /demos/chat/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Tornado Chat Demo 6 | 7 | 8 | 9 | 13 |
14 |
15 | {% for message in messages %} 16 | {% module Template("message.html", message=message) %} 17 | {% end %} 18 |
19 |
20 |
21 | 22 | 23 | 24 | 29 | 30 |
25 | 26 | 27 | {{ xsrf_form_html() }} 28 |
31 |
32 |
33 |
34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /demos/chat/templates/message.html: -------------------------------------------------------------------------------- 1 |
{{ escape(message["from"]) }}: {{ escape(message["body"]) }}
2 | -------------------------------------------------------------------------------- /demos/digest_auth/README.md: -------------------------------------------------------------------------------- 1 | # Cyclone auth digest example 2 | 3 | This is a port of https://github.com/bkjones/curtain (Apache License) 4 | 5 | ## Basic usage 6 | 7 | ### import digest.py at the top of your views file 8 | 9 | import digest 10 | 11 | ### subclass digest.DigestAuthMixin in authenticated views 12 | 13 | class MainHandler(digest.DigestAuthMixin, cyclone.web.RequestHandler): 14 | 15 | ### define a password store. This function is expected to return a hash containing 16 | auth\_username and auth\_password. 17 | 18 | def passwordz(username): 19 | creds = { 20 | 'auth_username': 'test', 21 | 'auth_password': 'foobar' 22 | } 23 | if username == creds['auth_username']: 24 | return creds 25 | 26 | ### decorate views (get/post) with digest.digest_auth. Passing in authentication realm and password store. 27 | If authenticated, `self.current_user` will be properly set. 28 | 29 | @digest.digest_auth('Cyclone', passwordz) 30 | def get(self): 31 | self.write("Hello %s" % (self.current_user)) 32 | -------------------------------------------------------------------------------- /demos/digest_auth/authdemo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | # 4 | # Copyright 2010 Alexandre Fiori 5 | # based on the original Tornado by Facebook 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 8 | # not use this file except in compliance with the License. You may obtain 9 | # a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 15 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 16 | # License for the specific language governing permissions and limitations 17 | # under the License. 18 | 19 | import sys 20 | import hashlib 21 | import time 22 | 23 | import cyclone.escape 24 | import cyclone.web 25 | import digest 26 | 27 | from twisted.python import log 28 | from twisted.internet import reactor 29 | 30 | 31 | class Application(cyclone.web.Application): 32 | def __init__(self): 33 | handlers = [ 34 | (r"/", MainHandler), 35 | ] 36 | settings = dict( 37 | cookie_secret="32oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2XdTP1o/Vo=", 38 | debug=True, 39 | login_url="/auth/login", 40 | ) 41 | cyclone.web.Application.__init__(self, handlers, **settings) 42 | 43 | 44 | class MainHandler(digest.DigestAuthMixin, cyclone.web.RequestHandler): 45 | # forces this handler to parse only GET / PROPFIND methods 46 | SUPPORTED_METHODS = ("GET", "PROPFIND") 47 | def passwordz(username): 48 | creds = { 49 | 'auth_username': 'test', 50 | 'auth_password': 'foobar' 51 | } 52 | if username == creds['auth_username']: 53 | return creds 54 | 55 | @digest.digest_auth('Cyclone', passwordz) 56 | def get(self): 57 | self.write("Hello %s" % (self.current_user)) 58 | 59 | @digest.digest_auth('Cyclone', passwordz) 60 | def propfind(self): 61 | self.write("Hello %s" % (self.current_user)) 62 | 63 | 64 | def main(): 65 | log.startLogging(sys.stdout) 66 | reactor.listenTCP(8888, Application(), interface="127.0.0.1") 67 | reactor.run() 68 | 69 | 70 | if __name__ == "__main__": 71 | main() 72 | -------------------------------------------------------------------------------- /demos/dropbox_auth/README.md: -------------------------------------------------------------------------------- 1 | ### Dropbox auth demo 2 | 3 | Implements an oauth2 mixin for dropbox inspired on https://gist.github.com/andreadipersio/7547259. 4 | 5 | Create an application on dropbox, add token and secret to main.py and run it with python main.py 6 | 7 | -------------------------------------------------------------------------------- /demos/dropbox_auth/dropbox.py: -------------------------------------------------------------------------------- 1 | import cyclone.web 2 | import cyclone.auth 3 | from cyclone import escape, httpclient 4 | from twisted.internet import task, defer 5 | 6 | try: 7 | import urllib.parse as urllib_parse # py3 8 | except ImportError: 9 | import urllib as urllib_parse # py2 10 | 11 | 12 | class DropboxMixin(cyclone.auth.OAuth2Mixin): 13 | """Dropbox authentication using OAuth2. 14 | 15 | https://www.dropbox.com/developers/core/docs 16 | 17 | """ 18 | _OAUTH_AUTHORIZE_URL = "https://www.dropbox.com/1/oauth2/authorize" 19 | _OAUTH_ACCESS_TOKEN_URL = "https://api.dropbox.com/1/oauth2/token" 20 | _OAUTH_SETTINGS_KEY = 'dropbox_oauth' 21 | 22 | @property 23 | def oauth_settings(self): 24 | return self.settings[self._OAUTH_SETTINGS_KEY] 25 | 26 | @defer.inlineCallbacks 27 | def get_authenticated_user(self, code): 28 | """Handles the login for the Dropbox user, returning a user object.""" 29 | body = urllib_parse.urlencode({ 30 | "redirect_uri": self.oauth_settings["redirect"], 31 | "code": code, 32 | "client_id": self.oauth_settings['key'], 33 | "client_secret": self.oauth_settings['secret'], 34 | "grant_type": "authorization_code", 35 | }) 36 | print body 37 | 38 | response = yield cyclone.httpclient.fetch( 39 | self._OAUTH_ACCESS_TOKEN_URL, 40 | method="POST", 41 | headers={'Content-Type': ['application/x-www-form-urlencoded']}, 42 | postdata=body 43 | ) 44 | 45 | if response.error: 46 | msg = 'Dropbox auth error: {}'.format(str(response)) 47 | cyclone.auth.AuthError(msg) 48 | defer.returnValue(None) 49 | 50 | args = escape.json_decode(response.body) 51 | defer.returnValue(args) 52 | 53 | def authorize_redirect(self): 54 | kwargs = { 55 | "redirect_uri": self.oauth_settings["redirect"], 56 | "client_id": self.oauth_settings["key"], 57 | "extra_params": {"response_type": "code"} 58 | } 59 | 60 | return super(DropboxMixin, self).authorize_redirect(**kwargs) 61 | 62 | -------------------------------------------------------------------------------- /demos/dropbox_auth/main.py: -------------------------------------------------------------------------------- 1 | import cyclone.web 2 | import cyclone.auth 3 | from twisted.python import log 4 | from twisted.internet import task, defer, reactor 5 | import sys 6 | from dropbox import DropboxMixin 7 | 8 | class AuthHandler(cyclone.web.RequestHandler, DropboxMixin): 9 | @cyclone.web.asynchronous 10 | @defer.inlineCallbacks 11 | def get(self): 12 | code = self.get_argument("code", None) 13 | 14 | if code: 15 | user = yield self.get_authenticated_user(code=code) 16 | print user 17 | self.set_secure_cookie("oauth_user", user.get("uid", "")) 18 | self.set_secure_cookie("oauth_token", user.get("access_token", "")) 19 | self.redirect("/") 20 | else: 21 | yield self.authorize_redirect() 22 | 23 | 24 | class LogoutHandler(cyclone.web.RequestHandler, DropboxMixin): 25 | def get(self): 26 | self.clear_cookie("oauth_user") 27 | self.clear_cookie("oauth_token") 28 | return "Logged out" 29 | 30 | 31 | class MainHandler(cyclone.web.RequestHandler): 32 | def get_current_user(self): 33 | return self.get_secure_cookie("oauth_user") 34 | 35 | #@cyclone.web.authenticated 36 | def get(self): 37 | user = self.get_current_user() 38 | print user 39 | if not user: 40 | raise cyclone.web.HTTPError(401, "Access denied") 41 | 42 | self.write("Welcome back, {}".format(user)) 43 | 44 | 45 | class Application(cyclone.web.Application): 46 | def __init__(self): 47 | handlers = [ 48 | (r"/auth", AuthHandler), 49 | (r"/logout", LogoutHandler), 50 | (r"/", MainHandler), 51 | ] 52 | 53 | 54 | opts = { 55 | "cookie_secret": "II*^WvQiOkv)QihQwQZ 2 | 3 | 4 | 5 | cyclone email demo 6 | 7 | 8 | 9 | 12 | 13 | 14 | 15 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 |

cyclone email demo

28 |
29 | 30 |
31 | 32 | 33 | List of destination addresses, separated by comma 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 |
42 |
43 | 44 |
45 | 49 | 53 |
54 |
55 |
56 | 57 | 58 |
59 | 60 |
61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /demos/email/static/info.txt: -------------------------------------------------------------------------------- 1 | cyclone email demo 2 | -------------------------------------------------------------------------------- /demos/email/static/me.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fiorix/cyclone/fefdc51dbf25b7470467fc8db6cf96d08191cf28/demos/email/static/me.png -------------------------------------------------------------------------------- /demos/email/template/response.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{title}} 6 | 7 | 8 | 9 | 12 | 13 | 14 | 15 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 |

{{title}}

28 |
29 | « 30 |

31 |

Response:

32 | 33 |
{{response}}
34 | 35 |
36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /demos/facebook/README: -------------------------------------------------------------------------------- 1 | Running the Tornado Facebook example 2 | ===================================== 3 | To work with the provided Facebook api key, this example must be 4 | accessed at http://localhost:8888/ to match the Connect URL set in the 5 | example application. 6 | 7 | To use any other domain, a new Facebook application must be registered 8 | with a Connect URL set to that domain. 9 | -------------------------------------------------------------------------------- /demos/facebook/static/facebook.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2009 Facebook 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 5 | * not use this file except in compliance with the License. You may obtain 6 | * a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | body { 18 | background: white; 19 | color: black; 20 | margin: 15px; 21 | } 22 | 23 | body, 24 | input, 25 | textarea { 26 | font-family: "Lucida Grande", Tahoma, Verdana, sans-serif; 27 | font-size: 10pt; 28 | } 29 | 30 | table { 31 | border-collapse: collapse; 32 | border: 0; 33 | } 34 | 35 | td { 36 | border: 0; 37 | padding: 0; 38 | } 39 | 40 | img { 41 | border: 0; 42 | } 43 | 44 | a { 45 | text-decoration: none; 46 | color: #3b5998; 47 | } 48 | 49 | a:hover { 50 | text-decoration: underline; 51 | } 52 | 53 | .post { 54 | border-bottom: 1px solid #eeeeee; 55 | min-height: 50px; 56 | padding-bottom: 10px; 57 | margin-top: 10px; 58 | } 59 | 60 | .post .picture { 61 | float: left; 62 | } 63 | 64 | .post .picture img { 65 | height: 50px; 66 | width: 50px; 67 | } 68 | 69 | .post .body { 70 | margin-left: 60px; 71 | } 72 | 73 | .post .media img { 74 | border: 1px solid #cccccc; 75 | padding: 3px; 76 | } 77 | 78 | .post .media:hover img { 79 | border: 1px solid #3b5998; 80 | } 81 | 82 | .post a.actor { 83 | font-weight: bold; 84 | } 85 | 86 | .post .meta { 87 | font-size: 11px; 88 | } 89 | 90 | .post a.permalink { 91 | color: #777777; 92 | } 93 | 94 | #body { 95 | max-width: 700px; 96 | margin: auto; 97 | } 98 | -------------------------------------------------------------------------------- /demos/facebook/static/facebook.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fiorix/cyclone/fefdc51dbf25b7470467fc8db6cf96d08191cf28/demos/facebook/static/facebook.js -------------------------------------------------------------------------------- /demos/facebook/templates/modules/post.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |
6 | {{ escape(actor["name"]) }} 7 | {% if post["message"] %} 8 | {{ escape(post["message"]) }} 9 | {% end %} 10 | {% if post["attachment"] %} 11 |
12 | {% if post["attachment"].get("name") %} 13 | 14 | {% end %} 15 | {% if post["attachment"].get("description") %} 16 |
{{ post["attachment"]["description"] }}
17 | {% end %} 18 | {% for media in filter(lambda m: m.get("src") and m["type"] in ("photo", "link"), post["attachment"].get("media", [])) %} 19 | 20 | {{ escape(media.get( 21 | 22 | {% end %} 23 |
24 | {% end %} 25 | 28 |
29 |
30 | 31 | -------------------------------------------------------------------------------- /demos/facebook/templates/stream.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Tornado Facebook Stream Demo 6 | 7 | 8 | 9 |
10 |
11 | {{ escape(current_user["name"]) }} - 12 | {{ _("Sign out") }} 13 |
14 | 15 |
16 | {% for post in stream["posts"] %} 17 | {{ modules.Post(post, stream["profiles"][post["actor_id"]]) }} 18 | {% end %} 19 |
20 |
21 | 22 | 23 | -------------------------------------------------------------------------------- /demos/facebook/uimodules.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | # 4 | # Copyright 2010 Alexandre Fiori 5 | # based on the original Tornado by Facebook 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 8 | # not use this file except in compliance with the License. You may obtain 9 | # a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 15 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 16 | # License for the specific language governing permissions and limitations 17 | # under the License. 18 | 19 | import cyclone.web 20 | 21 | 22 | class Entry(cyclone.web.UIModule): 23 | def render(self): 24 | return '
ENTRY
' 25 | -------------------------------------------------------------------------------- /demos/fbgraphapi/README: -------------------------------------------------------------------------------- 1 | Running the Tornado Facebook example 2 | ===================================== 3 | To work with the provided Facebook api key, this example must be 4 | accessed at http://localhost:8888/ to match the Connect URL set in the 5 | example application. 6 | 7 | To use any other domain, a new Facebook application must be registered 8 | with a Connect URL set to that domain. 9 | -------------------------------------------------------------------------------- /demos/fbgraphapi/static/facebook.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2009 Facebook 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 5 | * not use this file except in compliance with the License. You may obtain 6 | * a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | body { 18 | background: white; 19 | color: black; 20 | margin: 15px; 21 | } 22 | 23 | body, 24 | input, 25 | textarea { 26 | font-family: "Lucida Grande", Tahoma, Verdana, sans-serif; 27 | font-size: 10pt; 28 | } 29 | 30 | table { 31 | border-collapse: collapse; 32 | border: 0; 33 | } 34 | 35 | td { 36 | border: 0; 37 | padding: 0; 38 | } 39 | 40 | img { 41 | border: 0; 42 | } 43 | 44 | a { 45 | text-decoration: none; 46 | color: #3b5998; 47 | } 48 | 49 | a:hover { 50 | text-decoration: underline; 51 | } 52 | 53 | .post { 54 | border-bottom: 1px solid #eeeeee; 55 | min-height: 50px; 56 | padding-bottom: 10px; 57 | margin-top: 10px; 58 | } 59 | 60 | .post .picture { 61 | float: left; 62 | } 63 | 64 | .post .picture img { 65 | height: 50px; 66 | width: 50px; 67 | } 68 | 69 | .post .body { 70 | margin-left: 60px; 71 | } 72 | 73 | .post .media img { 74 | border: 1px solid #cccccc; 75 | padding: 3px; 76 | } 77 | 78 | .post .media:hover img { 79 | border: 1px solid #3b5998; 80 | } 81 | 82 | .post a.actor { 83 | font-weight: bold; 84 | } 85 | 86 | .post .meta { 87 | font-size: 11px; 88 | } 89 | 90 | .post a.permalink { 91 | color: #777777; 92 | } 93 | 94 | #body { 95 | max-width: 700px; 96 | margin: auto; 97 | } 98 | -------------------------------------------------------------------------------- /demos/fbgraphapi/static/facebook.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fiorix/cyclone/fefdc51dbf25b7470467fc8db6cf96d08191cf28/demos/fbgraphapi/static/facebook.js -------------------------------------------------------------------------------- /demos/fbgraphapi/templates/modules/post.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | {% set author_url="http://www.facebook.com/profile.php?id=" + escape(post["from"]["id"]) %} 4 | 5 |
6 |
7 | {{ escape(post["from"]["name"]) }} 8 | {% if "message" in post %} 9 | {{ escape(post["message"]) }} 10 | {% end %} 11 | 16 |
17 |
-------------------------------------------------------------------------------- /demos/fbgraphapi/templates/stream.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Tornado Facebook Stream Demo 6 | 7 | 8 | 9 |
10 |
11 | {{ escape(current_user["name"]) }} - 12 | {{ _("Sign out") }} 13 |
14 | 15 |
16 | {% for post in stream["data"] %} 17 | {{ modules.Post(post) }} 18 | {% end %} 19 |
20 |
21 | 22 | -------------------------------------------------------------------------------- /demos/fbgraphapi/uimodules.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | # 4 | # Copyright 2010 Alexandre Fiori 5 | # based on the original Tornado by Facebook 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 8 | # not use this file except in compliance with the License. You may obtain 9 | # a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 15 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 16 | # License for the specific language governing permissions and limitations 17 | # under the License. 18 | 19 | import cyclone.web 20 | 21 | 22 | class Entry(cyclone.web.UIModule): 23 | def render(self): 24 | return '
ENTRY
' 25 | -------------------------------------------------------------------------------- /demos/github_auth/README.md: -------------------------------------------------------------------------------- 1 | ### GitHub auth demo 2 | 3 | Implements an oauth2 mixin for github. 4 | Inspiration from fbgraphdemo and 5 | http://casbon.me/connecting-to-githubs-oauth2-api-with-tornado. 6 | 7 | To test the demo app that lists the description of all your gists: 8 | 9 | [Register new app](github_auth/register_new_app.png) 10 | 11 | Change github_client_id and github_secret with the respective values after 12 | you register the new app. 13 | 14 | $ python github_auth_example.py 15 | 16 | [Authorize your login](github_auth/auth_screen.png) 17 | -------------------------------------------------------------------------------- /demos/github_auth/auth_screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fiorix/cyclone/fefdc51dbf25b7470467fc8db6cf96d08191cf28/demos/github_auth/auth_screen.png -------------------------------------------------------------------------------- /demos/github_auth/register_new_app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fiorix/cyclone/fefdc51dbf25b7470467fc8db6cf96d08191cf28/demos/github_auth/register_new_app.png -------------------------------------------------------------------------------- /demos/github_auth/templates/gists.html: -------------------------------------------------------------------------------- 1 | {% for gist in gists %} 2 | -- {{ gist["description"] }}
3 | {% end %} 4 | 5 | -------------------------------------------------------------------------------- /demos/helloworld/helloworld.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | # 4 | # Copyright 2010 Alexandre Fiori 5 | # based on the original Tornado by Facebook 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 8 | # not use this file except in compliance with the License. You may obtain 9 | # a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 15 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 16 | # License for the specific language governing permissions and limitations 17 | # under the License. 18 | 19 | import cyclone.web 20 | import sys 21 | 22 | from twisted.internet import reactor 23 | from twisted.python import log 24 | 25 | 26 | class MainHandler(cyclone.web.RequestHandler): 27 | def get(self): 28 | self.write("Hello, world") 29 | 30 | 31 | if __name__ == "__main__": 32 | application = cyclone.web.Application([ 33 | (r"/", MainHandler) 34 | ]) 35 | 36 | log.startLogging(sys.stdout) 37 | reactor.listenTCP(8888, application, interface="127.0.0.1") 38 | reactor.run() 39 | -------------------------------------------------------------------------------- /demos/helloworld/helloworld_bottle.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | # 4 | # Copyright 2010 Alexandre Fiori 5 | # based on the original Tornado by Facebook 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 8 | # not use this file except in compliance with the License. You may obtain 9 | # a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 15 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 16 | # License for the specific language governing permissions and limitations 17 | # under the License. 18 | 19 | import sys 20 | from cyclone.bottle import run, route 21 | 22 | 23 | @route("/") 24 | def index(web): 25 | web.write("Hello, world") 26 | 27 | run(host="127.0.0.1", port=8888, log=sys.stdout) 28 | -------------------------------------------------------------------------------- /demos/helloworld/helloworld_simple.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | # 3 | # Copyright 2010 Alexandre Fiori 4 | # based on the original Tornado by Facebook 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 7 | # not use this file except in compliance with the License. You may obtain 8 | # a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 14 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 15 | # License for the specific language governing permissions and limitations 16 | # under the License. 17 | # 18 | # Start the server: 19 | # cyclone run helloworld_simple.py 20 | # 21 | # For more info: 22 | # cyclone run --help 23 | # 24 | # If this server is reverse proxied by nginx, some headers must be added to 25 | # the request. Check our sample nginx.conf for details, and set xheaders=True 26 | # to make cyclone use those headers. 27 | 28 | 29 | import cyclone.web 30 | 31 | 32 | class MainHandler(cyclone.web.RequestHandler): 33 | def get(self): 34 | self.write("Hello, world") 35 | 36 | 37 | class Application(cyclone.web.Application): 38 | def __init__(self): 39 | cyclone.web.Application.__init__(self, [(r"/", MainHandler)], 40 | xheaders=False) 41 | -------------------------------------------------------------------------------- /demos/helloworld/helloworld_twistd.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env twistd -ny 2 | # coding: utf-8 3 | # 4 | # Copyright 2010 Alexandre Fiori 5 | # based on the original Tornado by Facebook 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 8 | # not use this file except in compliance with the License. You may obtain 9 | # a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 15 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 16 | # License for the specific language governing permissions and limitations 17 | # under the License. 18 | 19 | import cyclone.web 20 | from twisted.application import internet 21 | from twisted.application import service 22 | 23 | 24 | class MainHandler(cyclone.web.RequestHandler): 25 | def get(self): 26 | self.write("Hello, world") 27 | 28 | webapp = cyclone.web.Application([ 29 | (r"/", MainHandler) 30 | ]) 31 | 32 | application = service.Application("helloworld_twistd") 33 | server = internet.TCPServer(8888, webapp, interface="127.0.0.1") 34 | server.setServiceParent(application) 35 | -------------------------------------------------------------------------------- /demos/helloworld/nginx.conf: -------------------------------------------------------------------------------- 1 | upstream backend { 2 | server localhost:9901; 3 | server localhost:9902; 4 | server localhost:9903; 5 | server localhost:9904; 6 | # server unix:/tmp/cyclone.1; 7 | # server unix:/tmp/cyclone.2; 8 | } 9 | 10 | server { 11 | listen 80; 12 | server_name localhost; 13 | #access_log /var/log/nginx/access.log; 14 | 15 | location / { 16 | proxy_pass http://backend; 17 | proxy_redirect off; 18 | proxy_set_header Host $host; 19 | proxy_set_header X-Real-IP $remote_addr; 20 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /demos/httpauth/README: -------------------------------------------------------------------------------- 1 | HTTP Basic Authentication 2 | 3 | httpauthdemo.py: 4 | A very simple, pretty much useless example. 5 | The idea is to show how to build a decorator to request valid credentials, 6 | and send HTTP "401 Unauthorized" when necessary. 7 | 8 | 9 | httpauthdemo_redis.py: 10 | This is more of a real-world example, authenticating against a database, 11 | in this case, redis. You can easily port it to standard RDBMs such as 12 | MySQL, PostgreSQL and others, using twisted.enterprise.adbapi. 13 | 14 | Again, it does so via the decorator, which extract credentials from Redis 15 | and compare them against what people typed on their browser. 16 | 17 | If you run this example without a redis-server, it will always return HTTP 18 | 503 "Service Unavailable" due to lack of communication with the database 19 | backend. Once you start redis, it will automatically connect to it. 20 | 21 | Make sure you create a user in redis to test the authentication system. 22 | From the command line, run: 23 | 24 | $ redis-cli set cyclone:root 123 25 | 26 | Then point your browser to http://localhost:8888 and authenticate as root/123. 27 | 28 | httpauthdemo_mongo.py: 29 | Same as the above, but for the MongoDB database. 30 | Make sure you have txredis installed, otherwise go grab it from GitHub: 31 | https://github.com/fiorix/mongo-async-python-driver 32 | 33 | Create a new user: 34 | curl -D - -d "username=root&password=123" http://localhost:8888/createuser 35 | 36 | Then point your browser to http://localhost:8888 and authenticate as root/123. 37 | 38 | 39 | Have fun! 40 | -------------------------------------------------------------------------------- /demos/httpauth/httpauthdemo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | # 4 | # Copyright 2010 Alexandre Fiori 5 | # based on the original Tornado by Facebook 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 8 | # not use this file except in compliance with the License. You may obtain 9 | # a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 15 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 16 | # License for the specific language governing permissions and limitations 17 | # under the License. 18 | 19 | import base64 20 | import functools 21 | import sys 22 | 23 | import cyclone.web 24 | 25 | from twisted.python import log 26 | from twisted.internet import reactor 27 | 28 | 29 | class Application(cyclone.web.Application): 30 | def __init__(self): 31 | handlers = [ 32 | (r"/", IndexHandler), 33 | ] 34 | cyclone.web.Application.__init__(self, handlers, debug=True) 35 | 36 | 37 | def HTTPBasic(method): 38 | @functools.wraps(method) 39 | def wrapper(self, *args, **kwargs): 40 | msg = None 41 | if "Authorization" in self.request.headers: 42 | auth_type, data = self.request.headers["Authorization"].split() 43 | try: 44 | assert auth_type == "Basic" 45 | usr, pwd = base64.b64decode(data).split(":", 1) 46 | assert usr == "root@localhost" 47 | assert pwd == "123" 48 | except AssertionError: 49 | msg = "Authentication failed" 50 | else: 51 | msg = "Authentication required" 52 | 53 | if msg: 54 | raise cyclone.web.HTTPAuthenticationRequired( 55 | log_message=msg, auth_type="Basic", realm="DEMO") 56 | else: 57 | self._current_user = usr 58 | return method(self, *args, **kwargs) 59 | return wrapper 60 | 61 | 62 | class IndexHandler(cyclone.web.RequestHandler): 63 | @HTTPBasic 64 | def get(self): 65 | self.write("Hi, %s." % self._current_user) 66 | 67 | 68 | def main(): 69 | log.startLogging(sys.stdout) 70 | reactor.listenTCP(8888, Application(), interface="0.0.0.0") 71 | reactor.run() 72 | 73 | 74 | if __name__ == "__main__": 75 | main() 76 | -------------------------------------------------------------------------------- /demos/httpauth/httpauthdemo_redis.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | # 4 | # Copyright 2010 Alexandre Fiori 5 | # based on the original Tornado by Facebook 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 8 | # not use this file except in compliance with the License. You may obtain 9 | # a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 15 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 16 | # License for the specific language governing permissions and limitations 17 | # under the License. 18 | 19 | import base64 20 | import functools 21 | import sys 22 | 23 | import cyclone.redis 24 | import cyclone.web 25 | 26 | from twisted.python import log 27 | from twisted.internet import defer, reactor 28 | 29 | 30 | class Application(cyclone.web.Application): 31 | def __init__(self): 32 | # Defaults to localhost:6379, dbid=0 33 | redisdb = cyclone.redis.lazyConnectionPool() 34 | 35 | handlers = [ 36 | (r"/", IndexHandler, dict(redisdb=redisdb)), 37 | ] 38 | cyclone.web.Application.__init__(self, handlers, debug=True) 39 | 40 | 41 | def HTTPBasic(method): 42 | @defer.inlineCallbacks 43 | @functools.wraps(method) 44 | def wrapper(self, *args, **kwargs): 45 | msg = None 46 | if "Authorization" in self.request.headers: 47 | auth_type, data = self.request.headers["Authorization"].split() 48 | try: 49 | assert auth_type == "Basic" 50 | usr, pwd = base64.b64decode(data).split(":", 1) 51 | redis_pwd = yield self.redisdb.get("cyclone:%s" % usr) 52 | assert pwd == str(redis_pwd) # it may come back as an int! 53 | except AssertionError: 54 | msg = "Authentication failed" 55 | except cyclone.redis.ConnectionError as e: 56 | # There's nothing we can do here. Just wait for the 57 | # connection to resume. 58 | log.msg("Redis is unavailable: %s" % e) 59 | raise cyclone.web.HTTPError(503) # Service Unavailable 60 | else: 61 | msg = "Authentication required" 62 | 63 | if msg: 64 | raise cyclone.web.HTTPAuthenticationRequired( 65 | log_message=msg, auth_type="Basic", realm="DEMO") 66 | else: 67 | self._current_user = usr 68 | yield defer.maybeDeferred(method, self, *args, **kwargs) 69 | return wrapper 70 | 71 | 72 | class IndexHandler(cyclone.web.RequestHandler): 73 | def initialize(self, redisdb): 74 | self.redisdb = redisdb 75 | 76 | @HTTPBasic 77 | def get(self): 78 | self.write("Hi, %s." % self._current_user) 79 | 80 | 81 | def main(): 82 | log.startLogging(sys.stdout) 83 | log.msg(">>>> Set the password from command line: " 84 | "redis-cli set cyclone:root 123") 85 | log.msg(">>>> Then authenticate as root/123 from the browser") 86 | reactor.listenTCP(8888, Application(), interface="127.0.0.1") 87 | reactor.run() 88 | 89 | 90 | if __name__ == "__main__": 91 | main() 92 | -------------------------------------------------------------------------------- /demos/httpclient/httpclient.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | # 4 | # Copyright 2010 Alexandre Fiori 5 | # based on the original Tornado by Facebook 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 8 | # not use this file except in compliance with the License. You may obtain 9 | # a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 15 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 16 | # License for the specific language governing permissions and limitations 17 | # under the License. 18 | 19 | import cyclone.escape 20 | import cyclone.httpclient 21 | from twisted.internet import defer, reactor 22 | 23 | 24 | @defer.inlineCallbacks 25 | def test(): 26 | print("get http://google.com followRecirect=0") 27 | response = yield cyclone.httpclient.fetch("http://google.com/") 28 | print("headers:", response.headers) 29 | print("code and phrase:", response.code, response.phrase) 30 | print("length and body:", response.length, response.body) 31 | 32 | print("\nget http://google.com followRecirect=1") 33 | response = yield cyclone.httpclient.fetch("http://google.com/", 34 | followRedirect=1, maxRedirects=2) 35 | print("headers:", response.headers) 36 | print("code and phrase:", response.code, response.phrase) 37 | print("length and body:", response.length, response.body) 38 | 39 | #print("\npost") 40 | #json = cyclone.escape.json_encode({"user":"foo", "pass":"bar"}) 41 | #response = yield cyclone.httpclient.fetch("http://localhost:8888/", 42 | # followRedirect=1, maxRedirects=2, postdata=json, 43 | # headers={"Content-Type":["application/json"]}) 44 | #print("headers:", response.headers) 45 | #print("code and phrase:", response.code, response.phrase) 46 | #print("length and body:", response.length, response.body) 47 | 48 | 49 | if __name__ == "__main__": 50 | test().addCallback(lambda ign: reactor.stop()) 51 | reactor.run() 52 | -------------------------------------------------------------------------------- /demos/locale/README: -------------------------------------------------------------------------------- 1 | Generate pot translation file using "mytest" as locale domain: 2 | xgettext --language=Python --keyword=_:1,2 -d mytest localedemo.py frontend/template/* 3 | 4 | When using translatable strings in templates, variable arguments must be 5 | placed outside of the _() function. Example: 6 | _("foobar %s" % x) # wrong 7 | _("foobar %s") % x # correct 8 | 9 | If xgettext fails parsing html elements like this: 10 | 11 | 12 | Try --language=Php, or pipe it to localefix.py: 13 | 14 | #!/usr/bin/env python 15 | # localefix.py 16 | import re 17 | import sys 18 | 19 | if __name__ == "__main__": 20 | try: 21 | filename = sys.argv[1] 22 | assert filename != "-" 23 | fd = open(filename) 24 | except: 25 | fd = sys.stdin 26 | 27 | line_re = re.compile(r'="([^"]+)"') 28 | for line in fd: 29 | line = line_re.sub(r"=\\1", line) 30 | sys.stdout.write(line) 31 | fd.close() 32 | 33 | 34 | Merge against existing pot file: 35 | msgmerge old.po mytest.po > new.po 36 | mv new.po mytest.po 37 | 38 | Compile: 39 | msgfmt mytest.po -o locale/{lang}/LC_MESSAGES/mytest.mo 40 | 41 | 42 | Compile files in this demo: 43 | msgfmt mytest_pt_BR.po -o frontend/locale/pt_BR/LC_MESSAGES/mytest.mo 44 | msgfmt mytest_es_ES.po -o frontend/locale/es_ES/LC_MESSAGES/mytest.mo 45 | -------------------------------------------------------------------------------- /demos/locale/frontend/locale/es_ES/LC_MESSAGES/mytest.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fiorix/cyclone/fefdc51dbf25b7470467fc8db6cf96d08191cf28/demos/locale/frontend/locale/es_ES/LC_MESSAGES/mytest.mo -------------------------------------------------------------------------------- /demos/locale/frontend/locale/pt_BR/LC_MESSAGES/mytest.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fiorix/cyclone/fefdc51dbf25b7470467fc8db6cf96d08191cf28/demos/locale/frontend/locale/pt_BR/LC_MESSAGES/mytest.mo -------------------------------------------------------------------------------- /demos/locale/frontend/template/hello.txt: -------------------------------------------------------------------------------- 1 | Simple text file. 2 | {{ _("%s, translated") % msg }} 3 | -------------------------------------------------------------------------------- /demos/locale/frontend/template/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{_("cyclone locale demo")}} 6 | 7 | 8 | 9 | 12 | 13 | 14 | 15 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 |

{{_("cyclone locale demo")}}

26 |
27 | 28 |
29 | 30 | 35 | 36 | 37 | 38 |
39 | 40 |
41 | 42 |

{{_("One apple", "%(count)d apples", apples) % {"count":apples} }}

43 | 44 |
45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /demos/locale/localedemo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | # 4 | # Copyright 2010 Alexandre Fiori 5 | # based on the original Tornado by Facebook 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 8 | # not use this file except in compliance with the License. You may obtain 9 | # a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 15 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 16 | # License for the specific language governing permissions and limitations 17 | # under the License. 18 | 19 | import sys 20 | 21 | import cyclone.locale 22 | import cyclone.web 23 | 24 | from twisted.python import log 25 | from twisted.internet import reactor 26 | 27 | 28 | class Application(cyclone.web.Application): 29 | def __init__(self): 30 | handlers = [ 31 | (r"/", IndexHandler), 32 | (r"/hello", HelloHandler), 33 | ] 34 | settings = dict( 35 | debug=True, 36 | template_path="./frontend/template", 37 | ) 38 | cyclone.locale.load_gettext_translations("./frontend/locale", "mytest") 39 | cyclone.web.Application.__init__(self, handlers, **settings) 40 | 41 | 42 | class BaseHandler(cyclone.web.RequestHandler): 43 | def get_user_locale(self): 44 | lang = self.get_cookie("lang", default="en_US") 45 | return cyclone.locale.get(lang) 46 | 47 | 48 | class IndexHandler(BaseHandler): 49 | def _apples(self): 50 | try: 51 | return int(self.get_argument("apples", 1)) 52 | except: 53 | return 1 54 | 55 | def get(self): 56 | self.render("index.html", 57 | apples=self._apples(), 58 | locale=self.locale.code, 59 | languages=cyclone.locale.get_supported_locales()) 60 | 61 | def post(self): 62 | lang = self.get_argument("lang") 63 | # assert lang in cyclone.locale.get_supported_locales() 64 | 65 | # Either set self._locale or override get_user_locale() 66 | # self._locale = cyclone.locale.get(lang) 67 | # self.render(...) 68 | 69 | self.set_cookie("lang", lang) 70 | self.redirect("/?apples=%d" % self._apples()) 71 | 72 | 73 | class HelloHandler(BaseHandler): 74 | def get(self): 75 | # Test with es_ES or pt_BR: 76 | # curl -D - -H "Cookie: lang=es_ES" http://localhost:8888/hello 77 | _ = self.locale.translate 78 | msg = _("hello world") 79 | text = self.render_string("hello.txt", msg=msg) 80 | self.set_header("Content-Type", "text/plain") 81 | self.write(text) 82 | 83 | 84 | def main(): 85 | log.startLogging(sys.stdout) 86 | reactor.listenTCP(8888, Application(), interface="127.0.0.1") 87 | reactor.run() 88 | 89 | 90 | if __name__ == "__main__": 91 | main() 92 | -------------------------------------------------------------------------------- /demos/locale/mytest_es_ES.po: -------------------------------------------------------------------------------- 1 | # cyclone locale demo 2 | # Copyright (C) 2010 Alexandre Fiori 3 | # This file is distributed under the same license as the cyclone package. 4 | # Alexandre Fiori , 2010 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: 0.1\n" 10 | "Report-Msgid-Bugs-To: \n" 11 | "POT-Creation-Date: 2010-01-20 12:19-0200\n" 12 | "PO-Revision-Date: 2010-01-20 12:21-0300\n" 13 | "Last-Translator: Alexandre Fiori \n" 14 | "Language-Team: \n" 15 | "MIME-Version: 1.0\n" 16 | "Content-Type: text/plain; charset=UTF-8\n" 17 | "Content-Transfer-Encoding: 8bit\n" 18 | "Plural-Forms: nplurals=INTEGER; plural=n != 1;\n" 19 | 20 | #: localedemo.py:76 21 | msgid "hello world" 22 | msgstr "hola mundo" 23 | 24 | #: frontend/template/hello.txt:2 25 | #, python-format 26 | msgid "%s, translated" 27 | msgstr "%s, traducido" 28 | 29 | #: frontend/template/index.html:5 frontend/template/index.html:25 30 | msgid "cyclone locale demo" 31 | msgstr "demonstración de internacionalización de cyclone" 32 | 33 | #: frontend/template/index.html:29 34 | msgid "Please select your language:" 35 | msgstr "Por favor, seleccione su idioma:" 36 | 37 | #: frontend/template/index.html:35 38 | msgid "How many apples?" 39 | msgstr "Cuantas manzanas?" 40 | 41 | #: frontend/template/index.html:39 42 | msgid "Submit" 43 | msgstr "Enviar" 44 | 45 | #: frontend/template/index.html:42 46 | #, python-format 47 | msgid "One apple" 48 | msgid_plural "%(count)d apples" 49 | msgstr[0] "Una manzana" 50 | msgstr[1] "%(count)d manzanas" 51 | -------------------------------------------------------------------------------- /demos/locale/mytest_pt_BR.po: -------------------------------------------------------------------------------- 1 | # cyclone locale demo 2 | # Copyright (C) 2010 Alexandre Fiori 3 | # This file is distributed under the same license as the cyclone package. 4 | # Alexandre Fiori , 2010 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: 0.1\n" 10 | "Report-Msgid-Bugs-To: \n" 11 | "POT-Creation-Date: 2010-01-20 12:19-0200\n" 12 | "PO-Revision-Date: 2010-01-20 12:21-0300\n" 13 | "Last-Translator: Alexandre Fiori \n" 14 | "Language-Team: \n" 15 | "MIME-Version: 1.0\n" 16 | "Content-Type: text/plain; charset=UTF-8\n" 17 | "Content-Transfer-Encoding: 8bit\n" 18 | "Plural-Forms: nplurals=INTEGER; plural=n != 1;\n" 19 | 20 | #: localedemo.py:76 21 | msgid "hello world" 22 | msgstr "olá mundo" 23 | 24 | #: frontend/template/hello.txt:2 25 | #, python-format 26 | msgid "%s, translated" 27 | msgstr "%s, traduzido" 28 | 29 | #: frontend/template/index.html:5 frontend/template/index.html:25 30 | msgid "cyclone locale demo" 31 | msgstr "demonstração de internacionalização do cyclone" 32 | 33 | #: frontend/template/index.html:29 34 | msgid "Please select your language:" 35 | msgstr "Por favor, selecione sua língua:" 36 | 37 | #: frontend/template/index.html:35 38 | msgid "How many apples?" 39 | msgstr "Quantas maçãs?" 40 | 41 | #: frontend/template/index.html:39 42 | msgid "Submit" 43 | msgstr "Enviar" 44 | 45 | #: frontend/template/index.html:42 46 | #, python-format 47 | msgid "One apple" 48 | msgid_plural "%(count)d apples" 49 | msgstr[0] "Uma maçã" 50 | msgstr[1] "%(count)d maçãs" 51 | -------------------------------------------------------------------------------- /demos/pycket/README.md: -------------------------------------------------------------------------------- 1 | # pycket Demo 2 | 3 | [pycket] is a session library, written for use with Redis or Memcached, and Tornado web server. It works equally well with cyclone. 4 | 5 | [pycket]: https://github.com/diogobaeder/pycket 6 | 7 | This demo shows how to set, get, and delete sessions stored persistently in redis (though it can easily be made to use memcached). See the [pycket] page for additional documentation. 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /demos/redis/README: -------------------------------------------------------------------------------- 1 | Test basic redis functionality 2 | 3 | Set: curl -D - -d "value=hello world" http://localhost:8888/text/foobar 4 | Get: curl -D - http://localhost:8888/text/foobar 5 | 6 | 7 | Test redis pubsub feature (redis 2.x) 8 | 9 | Subscribe to channels or patterns: 10 | curl -D - http://localhost:8888/queue/foo.*,msgs,l33t 11 | 12 | Send messages to channels: 13 | curl -D - -d "message=hello world" http://localhost:8888/queue/foo.bar 14 | curl -D - -d "message=yes, we can" http://localhost:8888/queue/l33t 15 | 16 | NOTE 17 | ---- 18 | 19 | 1. It is recommned not to use numeric channel names. The QueueHandler will 20 | get the channel as integer while at other places it would be string. 21 | If you still want to use numeric channel name, make sure you cast the 22 | name to str to avoid any issues. 23 | -------------------------------------------------------------------------------- /demos/rpc/jsonrpc_async_client.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | # 4 | # Copyright 2010 Alexandre Fiori 5 | # based on the original Tornado by Facebook 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 8 | # not use this file except in compliance with the License. You may obtain 9 | # a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 15 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 16 | # License for the specific language governing permissions and limitations 17 | # under the License. 18 | 19 | import cyclone.httpclient 20 | from twisted.internet import defer, reactor 21 | 22 | 23 | @defer.inlineCallbacks 24 | def main(): 25 | cli = cyclone.httpclient.JsonRPC("http://localhost:8888/jsonrpc") 26 | print "echo:", (yield cli.echo("foo bar")) 27 | print "sort:", (yield cli.sort(["foo", "bar"])) 28 | print "count:", (yield cli.count(["foo", "bar"])) 29 | print "geoip_lookup:", (yield cli.geoip_lookup("google.com")) 30 | reactor.stop() 31 | 32 | if __name__ == "__main__": 33 | main() 34 | reactor.run() 35 | -------------------------------------------------------------------------------- /demos/rpc/jsonrpc_sync_client.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | # 4 | # Copyright 2010 Alexandre Fiori 5 | # based on the original Tornado by Facebook 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 8 | # not use this file except in compliance with the License. You may obtain 9 | # a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 15 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 16 | # License for the specific language governing permissions and limitations 17 | # under the License. 18 | 19 | import json 20 | import urllib 21 | 22 | 23 | def request(url, func, *args): 24 | req = json.dumps({"method": func, "params": args, "id": 1}) 25 | result = urllib.urlopen(url, req).read() 26 | try: 27 | response = json.loads(result) 28 | except: 29 | return "error: %s" % result 30 | else: 31 | return response.get("result", response.get("error")) 32 | 33 | url = "http://localhost:8888/jsonrpc" 34 | print "echo:", request(url, "echo", "foo bar") 35 | print "sort:", request(url, "sort", ["foo", "bar"]) 36 | print "count:", request(url, "count", ["foo", "bar"]) 37 | print "geoip_lookup:", request(url, "geoip_lookup", "google.com") 38 | -------------------------------------------------------------------------------- /demos/rpc/rpcdemo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | # 4 | # Copyright 2010 Alexandre Fiori 5 | # based on the original Tornado by Facebook 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 8 | # not use this file except in compliance with the License. You may obtain 9 | # a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 15 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 16 | # License for the specific language governing permissions and limitations 17 | # under the License. 18 | 19 | import sys 20 | 21 | import cyclone.httpclient 22 | import cyclone.jsonrpc 23 | import cyclone.xmlrpc 24 | 25 | from twisted.python import log 26 | from twisted.internet import defer, reactor 27 | 28 | 29 | class XmlrpcHandler(cyclone.xmlrpc.XmlrpcRequestHandler): 30 | allowNone = True 31 | 32 | def xmlrpc_echo(self, text): 33 | return text 34 | 35 | def xmlrpc_sort(self, items): 36 | return sorted(items) 37 | 38 | def xmlrpc_count(self, items): 39 | return len(items) 40 | 41 | @defer.inlineCallbacks 42 | def xmlrpc_geoip_lookup(self, address): 43 | result = yield cyclone.httpclient.fetch( 44 | "http://freegeoip.net/xml/%s" % address.encode("utf-8")) 45 | defer.returnValue(result.body) 46 | 47 | 48 | class JsonrpcHandler(cyclone.jsonrpc.JsonrpcRequestHandler): 49 | def jsonrpc_echo(self, text): 50 | return text 51 | 52 | def jsonrpc_sort(self, items): 53 | return sorted(items) 54 | 55 | def jsonrpc_count(self, items): 56 | return len(items) 57 | 58 | @defer.inlineCallbacks 59 | def jsonrpc_geoip_lookup(self, address): 60 | result = yield cyclone.httpclient.fetch( 61 | "http://freegeoip.net/json/%s" % address.encode("utf-8")) 62 | defer.returnValue(result.body) 63 | 64 | 65 | def main(): 66 | log.startLogging(sys.stdout) 67 | application = cyclone.web.Application([ 68 | (r"/xmlrpc", XmlrpcHandler), 69 | (r"/jsonrpc", JsonrpcHandler), 70 | ]) 71 | 72 | reactor.listenTCP(8888, application) 73 | reactor.run() 74 | 75 | if __name__ == "__main__": 76 | main() 77 | -------------------------------------------------------------------------------- /demos/rpc/xmlrpc_client.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | # 4 | # Copyright 2010 Alexandre Fiori 5 | # based on the original Tornado by Facebook 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 8 | # not use this file except in compliance with the License. You may obtain 9 | # a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 15 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 16 | # License for the specific language governing permissions and limitations 17 | # under the License. 18 | 19 | import xmlrpclib 20 | 21 | srv = xmlrpclib.Server("http://localhost:8888/xmlrpc") 22 | print "echo:", srv.echo("hello world!") 23 | print "sort:", srv.sort(["foo", "bar"]) 24 | print "count:", srv.count(["foo", "bar"]) 25 | print "geoip_lookup:\n", srv.geoip_lookup("google.com") 26 | -------------------------------------------------------------------------------- /demos/s3/README: -------------------------------------------------------------------------------- 1 | Port of http://github.com/facebook/tornado/raw/master/tornado/s3server.py (s3 clone on tornado) 2 | TODO: 3 | - plug auth module 4 | - plug riak, redis or another storage besides FS 5 | - be awesome. 6 | 7 | 8 | 9 | RUNNING 10 | $ twistd -ny se.tac 11 | 12 | TESTING: 13 | create bucket: $ curl --request PUT "http://localhost:4000/meh/" 14 | 15 | put some data (README file): curl --data "@README" --request PUT --header "Content-Type: text/plain" "http://localhost:4000/meh/README" 16 | 17 | retrieve: curl http://localhost:4000/meh/README 18 | 19 | 20 | -------------------------------------------------------------------------------- /demos/s3/se.tac: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | # twistd -ny s3.tac 4 | # gleicon moraes (http://zenmachine.wordpress.com | http://github.com/gleicon) 5 | 6 | SERVER_PORT = 4000 7 | 8 | import s3server 9 | from twisted.application import service, internet 10 | 11 | application = service.Application("s3") 12 | srv = internet.TCPServer(SERVER_PORT, s3server.S3Application(root_directory="/tmp/s3")) 13 | srv.setServiceParent(application) 14 | 15 | -------------------------------------------------------------------------------- /demos/sse/README: -------------------------------------------------------------------------------- 1 | Server Sent Events Demo 2 | 3 | SSE is a mechanism akin to comet but with fine grained control over the data. You can name events, send structured and partial data and set the proper timeout. 4 | 5 | To understand better, check http://www.html5rocks.com/en/tutorials/eventsource/basics/ 6 | 7 | The client side gets really simple than controlling a COMET resource using js, by using the window.EventSource object. 8 | 9 | -------------------------------------------------------------------------------- /demos/sse/ssedemo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | # 4 | # Copyright 2010 Alexandre Fiori 5 | # based on the original Tornado by Facebook 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 8 | # not use this file except in compliance with the License. You may obtain 9 | # a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 15 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 16 | # License for the specific language governing permissions and limitations 17 | # under the License. 18 | 19 | import sys 20 | 21 | import cyclone.sse 22 | import cyclone.web 23 | 24 | from twisted.internet import protocol 25 | from twisted.internet import reactor 26 | from twisted.protocols import telnet 27 | from twisted.python import log 28 | 29 | 30 | class Application(cyclone.web.Application): 31 | def __init__(self): 32 | handlers = [ 33 | (r"/", cyclone.web.RedirectHandler, {"url": "/static/index.html"}), 34 | (r"/live", LiveHandler), 35 | ] 36 | settings = dict( 37 | debug=True, 38 | static_path="./static", 39 | template_path="./template", 40 | ) 41 | cyclone.web.Application.__init__(self, handlers, **settings) 42 | 43 | 44 | class StarWarsMixin(object): 45 | mbuffer = "" 46 | waiters = [] 47 | 48 | def subscribe(self, client): 49 | StarWarsMixin.waiters.append(client) 50 | 51 | def unsubscribe(self, client): 52 | StarWarsMixin.waiters.remove(client) 53 | 54 | def broadcast(self, message): 55 | cls = StarWarsMixin 56 | 57 | chunks = (self.mbuffer + message.replace("\x1b[J", "")).split("\x1b[H") 58 | self.mbuffer = "" 59 | for chunk in chunks: 60 | if len(chunk) == 985: 61 | chunk = chunk.replace("\r\n", "
") 62 | log.msg("Sending new message to %r listeners" % \ 63 | len(cls.waiters)) 64 | for client in cls.waiters: 65 | try: 66 | client.sendEvent(chunk) 67 | except: 68 | log.err() 69 | else: 70 | self.mbuffer = chunk 71 | 72 | 73 | class LiveHandler(cyclone.sse.SSEHandler, StarWarsMixin): 74 | def bind(self): 75 | self.subscribe(self) 76 | 77 | def unbind(self): 78 | self.unsubscribe(self) 79 | 80 | 81 | class BlinkenlightsProtocol(telnet.Telnet, StarWarsMixin): 82 | def dataReceived(self, data): 83 | self.broadcast(data) 84 | 85 | 86 | def main(): 87 | log.startLogging(sys.stdout) 88 | 89 | blinkenlights = protocol.ReconnectingClientFactory() 90 | blinkenlights.protocol = BlinkenlightsProtocol 91 | reactor.connectTCP("towel.blinkenlights.nl", 23, blinkenlights) 92 | 93 | reactor.listenTCP(8888, Application(), interface="127.0.0.1") 94 | reactor.run() 95 | 96 | 97 | if __name__ == "__main__": 98 | main() 99 | -------------------------------------------------------------------------------- /demos/sse/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 21 | 22 | 23 |
24 |

Episode IV Live from towel.blinkenlights.nl

25 |

26 |   
27 | 28 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /demos/ssl/README: -------------------------------------------------------------------------------- 1 | create a pair of certs: 2 | 3 | $ mkcert (some questions, write down your passphrase, etc) 4 | $ python helloworld_ssl.py 5 | 6 | point your browser to https://localhost:8443 7 | 8 | try using cyclone's twisted plugin: 9 | 10 | $ twistd -n cyclone --ssl-app=helloworld_simple.Application 11 | -------------------------------------------------------------------------------- /demos/ssl/helloworld_simple.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | # 3 | # Copyright 2010 Alexandre Fiori 4 | # based on the original Tornado by Facebook 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 7 | # not use this file except in compliance with the License. You may obtain 8 | # a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 14 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 15 | # License for the specific language governing permissions and limitations 16 | # under the License. 17 | # 18 | # Run: twistd -n cyclone --ssl-app helloworld_simple.Application 19 | # For more info: twistd -n cyclone --help 20 | 21 | import cyclone.web 22 | 23 | 24 | class MainHandler(cyclone.web.RequestHandler): 25 | def get(self): 26 | self.write("Hello, %s" % self.request.protocol) 27 | 28 | 29 | Application = lambda: cyclone.web.Application([(r"/", MainHandler)]) 30 | -------------------------------------------------------------------------------- /demos/ssl/helloworld_ssl.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | # 4 | # Copyright 2010 Alexandre Fiori 5 | # based on the original Tornado by Facebook 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 8 | # not use this file except in compliance with the License. You may obtain 9 | # a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 15 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 16 | # License for the specific language governing permissions and limitations 17 | # under the License. 18 | 19 | import cyclone.web 20 | import sys 21 | from twisted.internet import reactor 22 | from twisted.internet import ssl 23 | from twisted.python import log 24 | 25 | 26 | class MainHandler(cyclone.web.RequestHandler): 27 | def get(self): 28 | self.write("Hello, %s" % self.request.protocol) 29 | 30 | 31 | def main(): 32 | log.startLogging(sys.stdout) 33 | application = cyclone.web.Application([ 34 | (r"/", MainHandler) 35 | ]) 36 | 37 | interface = "127.0.0.1" 38 | reactor.listenTCP(8888, application, interface=interface) 39 | reactor.listenSSL(8443, application, 40 | ssl.DefaultOpenSSLContextFactory("server.key", 41 | "server.crt"), 42 | interface=interface) 43 | reactor.run() 44 | 45 | 46 | if __name__ == "__main__": 47 | main() 48 | -------------------------------------------------------------------------------- /demos/ssl/mkcert.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo -- key 4 | openssl genrsa -des3 -out server.key 1024 5 | 6 | echo -- csr 7 | openssl req -new -key server.key -out server.csr 8 | 9 | echo -- remove passphrase 10 | cp server.key orig.server.key 11 | openssl rsa -in orig.server.key -out server.key 12 | 13 | echo -- generate crt 14 | openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt 15 | -------------------------------------------------------------------------------- /demos/upload/template/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | cyclone upload demo 6 | 7 | 8 | 9 | 12 | 13 | 14 | 15 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 |

cyclone upload demo

28 |
29 | 30 |
32 | Who are you? 33 |
34 |
35 | 36 |
37 | 38 |

Your full name

39 |
40 |
41 | 42 |
43 | 44 |
45 | 46 |

A picture of you

47 |
48 |
49 | 50 |
51 | 52 | 53 |
54 |
55 |
56 | 57 | {% if info is not None %} 58 |
59 |

Name: {{info["name"]}}

60 |

File: {{info["file"]}}

61 |
62 | {% end %} 63 | 64 |
65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /demos/websocket/chat/static/chat.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2009 FriendFeed 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 5 | * not use this file except in compliance with the License. You may obtain 6 | * a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | body { 18 | background: white; 19 | margin: 10px; 20 | } 21 | 22 | body, 23 | input { 24 | font-family: sans-serif; 25 | font-size: 10pt; 26 | color: black; 27 | } 28 | 29 | table { 30 | border-collapse: collapse; 31 | border: 0; 32 | } 33 | 34 | td { 35 | border: 0; 36 | padding: 0; 37 | } 38 | 39 | #body { 40 | position: absolute; 41 | bottom: 10px; 42 | left: 10px; 43 | } 44 | 45 | #input { 46 | margin-top: 0.5em; 47 | } 48 | 49 | #inbox .message { 50 | padding-top: 0.25em; 51 | } 52 | 53 | #nav { 54 | float: right; 55 | z-index: 99; 56 | } 57 | -------------------------------------------------------------------------------- /demos/websocket/chat/static/chat.js: -------------------------------------------------------------------------------- 1 | // Copyright 2009 FriendFeed 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | $(document).ready(function() { 16 | if (!window.console) window.console = {}; 17 | if (!window.console.log) window.console.log = function() {}; 18 | 19 | $("#messageform").live("submit", function() { 20 | newMessage($(this)); 21 | return false; 22 | }); 23 | $("#messageform").live("keypress", function(e) { 24 | if (e.keyCode == 13) { 25 | newMessage($(this)); 26 | return false; 27 | } 28 | }); 29 | $("#message").select(); 30 | updater.start(); 31 | }); 32 | 33 | $(window).unload(function() { 34 | updater.stop() 35 | }); 36 | 37 | function newMessage(form) { 38 | var message = form.formToDict(); 39 | updater.socket.send(JSON.stringify(message)); 40 | form.find("input[type=text]").val("").select(); 41 | } 42 | 43 | jQuery.fn.formToDict = function() { 44 | var fields = this.serializeArray(); 45 | var json = {} 46 | for (var i = 0; i < fields.length; i++) { 47 | json[fields[i].name] = fields[i].value; 48 | } 49 | if (json.next) delete json.next; 50 | return json; 51 | }; 52 | 53 | var updater = { 54 | socket: null, 55 | 56 | start: function() { 57 | if ("WebSocket" in window) { 58 | updater.socket = new WebSocket("ws://localhost:8888/chatsocket"); 59 | } else { 60 | updater.socket = new MozWebSocket("ws://localhost:8888/chatsocket"); 61 | } 62 | updater.socket.onmessage = function(event) { 63 | console.log(event.data); 64 | updater.showMessage(JSON.parse(event.data)); 65 | } 66 | }, 67 | 68 | stop: function() { 69 | updater.socket.close(); 70 | updater.socket = null; 71 | }, 72 | 73 | showMessage: function(message) { 74 | var existing = $("#m" + message.id); 75 | if (existing.length > 0) return; 76 | var node = $(message.html); 77 | node.hide(); 78 | $("#inbox").append(node); 79 | node.slideDown(); 80 | } 81 | }; 82 | -------------------------------------------------------------------------------- /demos/websocket/chat/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Cyclone Chat Demo 6 | 7 | 8 | 9 |
10 |
11 | {% for message in messages %} 12 | {% include "message.html" %} 13 | {% end %} 14 |
15 |
16 |
17 | 18 | 19 | 20 | 25 | 26 | 27 | 28 | 29 |
21 | 22 | 23 | {{ xsrf_form_html() }} 24 |
site statistics
30 |
31 |
32 |
33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /demos/websocket/chat/templates/message.html: -------------------------------------------------------------------------------- 1 |
{{ message["body"] }}
2 | -------------------------------------------------------------------------------- /demos/websocket/chat/templates/stats.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 78 | Site statistics 79 | 80 | 81 |
82 |
83 |
84 |
85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /demos/websocket/echo/echo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import cyclone.escape 4 | import cyclone.web 5 | import cyclone.websocket 6 | import os.path 7 | import sys 8 | from twisted.python import log 9 | from twisted.internet import reactor 10 | 11 | 12 | class Application(cyclone.web.Application): 13 | def __init__(self): 14 | settings = dict( 15 | cookie_secret="43oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2XdTP1o/Vo=", 16 | template_path=os.path.join(os.path.dirname(__file__), "templates"), 17 | static_path=os.path.join(os.path.dirname(__file__), "static"), 18 | xsrf_cookies=True, 19 | autoescape=None, 20 | ) 21 | 22 | handlers = [ 23 | (r"/", MainHandler), 24 | (r"/echo", EchoSocketHandler), 25 | (r"/(jquery-latest\.js)", cyclone.web.StaticFileHandler, 26 | dict(path=settings['static_path'])), 27 | ] 28 | cyclone.web.Application.__init__(self, handlers, **settings) 29 | 30 | 31 | class MainHandler(cyclone.web.RequestHandler): 32 | def get(self): 33 | self.render("echo.html") 34 | 35 | 36 | class EchoSocketHandler(cyclone.websocket.WebSocketHandler): 37 | 38 | def connectionMade(self, *args, **kwargs): 39 | log.msg("ws opened") 40 | 41 | def connectionLost(self, reason): 42 | log.msg("ws closed") 43 | 44 | def messageReceived(self, message): 45 | log.msg("got message %s" % message) 46 | self.sendMessage(message) 47 | 48 | 49 | def main(): 50 | reactor.listenTCP(8888, Application()) 51 | reactor.run() 52 | 53 | 54 | if __name__ == "__main__": 55 | log.startLogging(sys.stdout) 56 | main() 57 | -------------------------------------------------------------------------------- /demos/websocket/echo/templates/echo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 55 | 56 | 57 | 58 | 59 |

Interaction experiment

60 | 61 |

Debug

62 |
63 | 64 |
65 | Last message time: 66 |
67 |
68 | 69 |
70 | out 71 |
Output should appear here
72 |
73 | 74 |

Enter something in the entry below, 75 | the server will send it back to the out region above 76 |

77 | 78 |
79 | entry 80 |

Enter:

81 |
82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /scripts/cyclone: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright 2012 Alexandre Fiori 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 5 | # not use this file except in compliance with the License. You may obtain 6 | # a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # License for the specific language governing permissions and limitations 14 | # under the License. 15 | 16 | opt=$1 17 | shift 18 | 19 | case "$opt" in 20 | run) 21 | twistd -n cyclone $* 22 | ;; 23 | app) 24 | python -m cyclone.app $* 25 | ;; 26 | *) 27 | echo "usage: $0 [run|app] [options]" 28 | esac 29 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright 2010 Alexandre Fiori 4 | # based on the original Tornado by Facebook 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 7 | # not use this file except in compliance with the License. You may obtain 8 | # a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 14 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 15 | # License for the specific language governing permissions and limitations 16 | # under the License. 17 | 18 | import re 19 | import sys 20 | import platform 21 | import setuptools 22 | from distutils import log 23 | 24 | CLASSIFIERS = [ 25 | 'Development Status :: 5 - Production/Stable', 26 | 'Environment :: Web Environment', 27 | 'Intended Audience :: Developers', 28 | 'License :: OSI Approved :: Apache Software License', 29 | 'Operating System :: POSIX', 30 | 'Programming Language :: Python', 31 | 'Programming Language :: Python :: 3', 32 | 'Programming Language :: Python :: 3.5', 33 | 'Programming Language :: Python :: 3.6', 34 | 'Programming Language :: Python :: 3.7', 35 | 'Programming Language :: Python :: 3 :: Only', 36 | 'Topic :: Internet', 37 | 'Topic :: Utilities', 38 | 'Topic :: Software Development :: Libraries :: Python Modules', 39 | 'Topic :: Internet :: WWW/HTTP', 40 | 'Topic :: Internet :: WWW/HTTP :: WSGI', 41 | 'Topic :: Internet :: WWW/HTTP :: WSGI :: Server', 42 | 'Topic :: Internet :: WWW/HTTP :: Dynamic Content'] 43 | 44 | 45 | setuptools.setup( 46 | name="cyclone", 47 | version="1.3", 48 | author="fiorix", 49 | author_email="fiorix@gmail.com", 50 | url="http://cyclone.io/", 51 | license="http://www.apache.org/licenses/LICENSE-2.0", 52 | description="Non-blocking web server for Python. " 53 | "Tornado API as a Twisted protocol.", 54 | keywords="python non-blocking web server twisted facebook tornado", 55 | packages=["cyclone", "twisted.plugins", "cyclone.tests", "cyclone.testing"], 56 | package_data={"twisted": ["plugins/cyclone_plugin.py"], 57 | "cyclone": ["appskel_default.zip", 58 | "appskel_foreman.zip", 59 | "appskel_signup.zip"]}, 60 | scripts=["scripts/cyclone"], 61 | install_requires=["twisted==19.2.1","pyOpenSSL==19.0.0"], 62 | classifiers=CLASSIFIERS, 63 | ) 64 | -------------------------------------------------------------------------------- /website/Makefile: -------------------------------------------------------------------------------- 1 | SPHINXOPTS=-d sphinx/build/doctrees sphinx 2 | 3 | .PHONY: sphinx 4 | sphinx: 5 | sphinx-build -b html $(SPHINXOPTS) sphinx/build/html 6 | 7 | .PHONY: coverage 8 | coverage: 9 | sphinx-build -b coverage ${SPHINXOPTS} sphinx/build/coverage 10 | cat sphinx/build/coverage/python.txt 11 | 12 | .PHONY: latex 13 | latex: 14 | sphinx-build -b latex $(SPHINXOPTS) sphinx/build/latex 15 | 16 | # Building a pdf requires a latex installation. For macports, the needed 17 | # packages are texlive-latex-extra and texlive-fonts-recommended. 18 | # The output is in sphinx/build/latex/tornado.pdf 19 | .PHONY: pdf 20 | pdf: latex 21 | cd sphinx/build/latex && pdflatex -interaction=nonstopmode tornado.tex 22 | 23 | clean: 24 | rm -rf sphinx/build 25 | -------------------------------------------------------------------------------- /website/sphinx/app.rst: -------------------------------------------------------------------------------- 1 | ``cyclone.app`` --- Command-line line tool 2 | ========================================== 3 | 4 | .. automodule:: cyclone.app 5 | :members: 6 | -------------------------------------------------------------------------------- /website/sphinx/auth.rst: -------------------------------------------------------------------------------- 1 | ``cyclone.auth`` --- Third-party login with OpenID and OAuth 2 | ============================================================ 3 | 4 | .. automodule:: cyclone.auth 5 | 6 | Common protocols 7 | ---------------- 8 | 9 | .. autoclass:: OpenIdMixin 10 | :members: 11 | 12 | .. autoclass:: OAuthMixin 13 | :members: 14 | 15 | .. autoclass:: OAuth2Mixin 16 | :members: 17 | 18 | Twitter 19 | ------- 20 | 21 | .. autoclass:: TwitterMixin 22 | :members: 23 | 24 | FriendFeed 25 | ---------- 26 | 27 | .. autoclass:: FriendFeedMixin 28 | :members: 29 | 30 | Google 31 | ------ 32 | 33 | .. autoclass:: GoogleMixin 34 | :members: 35 | 36 | Facebook 37 | -------- 38 | 39 | .. autoclass:: FacebookMixin 40 | :members: 41 | 42 | .. autoclass:: FacebookGraphMixin 43 | :members: 44 | -------------------------------------------------------------------------------- /website/sphinx/bottle.rst: -------------------------------------------------------------------------------- 1 | ``cyclone.bottle`` --- Bottle application style 2 | =============================================== 3 | 4 | .. automodule:: cyclone.bottle 5 | :members: 6 | -------------------------------------------------------------------------------- /website/sphinx/conf.py: -------------------------------------------------------------------------------- 1 | # Ensure we get the local copy of tornado instead of what's on the standard path 2 | import os 3 | import sys 4 | sys.path.insert(0, os.path.abspath("../..")) 5 | import cyclone 6 | 7 | master_doc = "index" 8 | 9 | project = "cyclone" 10 | copyright = "2013, Alexandre Fiori" 11 | 12 | version = release = cyclone.version 13 | 14 | extensions = ["sphinx.ext.autodoc", 15 | "sphinx.ext.coverage", 16 | "sphinx.ext.viewcode"] 17 | 18 | primary_domain = 'py' 19 | default_role = 'py:obj' 20 | 21 | autodoc_member_order = "bysource" 22 | autoclass_content = "both" 23 | 24 | coverage_skip_undoc_in_source = True 25 | #coverage_ignore_modules = [ 26 | # "tornado.platform.twisted", 27 | # ] 28 | # I wish this could go in a per-module file... 29 | #coverage_ignore_classes = [ 30 | # # tornado.gen 31 | # "Multi", 32 | # "Runner", 33 | # "YieldPoint", 34 | # 35 | # # tornado.ioloop 36 | # "PollIOLoop", 37 | # 38 | # # tornado.web 39 | # "ChunkedTransferEncoding", 40 | # "GZipContentEncoding", 41 | # "OutputTransform", 42 | # "TemplateModule", 43 | # "url", 44 | # 45 | # # tornado.websocket 46 | # "WebSocketProtocol", 47 | # "WebSocketProtocol13", 48 | # "WebSocketProtocol76", 49 | # ] 50 | # 51 | #coverage_ignore_functions = [ 52 | # # various modules 53 | # "doctests", 54 | # "main", 55 | #] 56 | 57 | html_static_path = [os.path.abspath("../static")] 58 | html_style = "sphinx.css" 59 | highlight_language = "none" 60 | html_theme_options = dict( 61 | footerbgcolor="#fff", 62 | footertextcolor="#000", 63 | sidebarbgcolor="#fff", 64 | #sidebarbtncolor 65 | sidebartextcolor="#4d8cbf", 66 | sidebarlinkcolor="#216093", 67 | relbarbgcolor="#fff", 68 | relbartextcolor="#000", 69 | relbarlinkcolor="#216093", 70 | bgcolor="#fff", 71 | textcolor="#000", 72 | linkcolor="#216093", 73 | visitedlinkcolor="#216093", 74 | headbgcolor="#fff", 75 | headtextcolor="#4d8cbf", 76 | codebgcolor="#fff", 77 | codetextcolor="#060", 78 | bodyfont="Georgia, serif", 79 | headfont="Calibri, sans-serif", 80 | stickysidebar=True, 81 | ) 82 | 83 | latex_documents = [ 84 | ('index', 'cyclone.tex', 'cyclone documentation', 'Alexandre Fiori', 85 | 'manual', False), 86 | ] 87 | -------------------------------------------------------------------------------- /website/sphinx/databases.rst: -------------------------------------------------------------------------------- 1 | Database connections 2 | ==================== 3 | 4 | Cyclone supports a number of different databases, from RDBMs to NoSQL: 5 | 6 | - Built-in SQLite: `cyclone.sqlite` 7 | - Built-in Redis: `cyclone.redis` 8 | - RDBMs like MySQL and PostgreSQL: `twisted.enterprise.adbapi `_ 9 | - MongoDB: `txmongo `_ 10 | 11 | Here is an example of a server with both Redis and MySQL:: 12 | 13 | import MySQLdb 14 | import functools 15 | import cyclone.redis 16 | from twisted.internet import defer 17 | from twisted.python import log 18 | from twisted.enterprise import adbapi 19 | 20 | def dbsafe(method): 21 | @defer.inlineCallbacks 22 | @functools.wraps(method) 23 | def wrapper(self, *args, **kwargs): 24 | try: 25 | result = yield defer.maybeDeferred(method, self, *args, **kwargs) 26 | except MySQLdb.OperationalError as e: 27 | log.msg("MySQL error: " + str(e)) 28 | except cyclone.redis.RedisError as e: 29 | log.msg("Redis error: " + str(e)) 30 | else: 31 | defer.returnValue(result) 32 | raise web.HTTPError(503) # Service Unavailable 33 | return wrapper 34 | 35 | class DatabaseMixin(object): 36 | redis = cyclone.redis.lazyConnectionPool() 37 | mysql = adbapi.ConnectionPool("MySQLdb", db="dummy") 38 | 39 | class MainHandler(web.RequestHandler, DatabaseMixin): 40 | @dbsafe 41 | @defer.inlineCallbacks 42 | def get(self): 43 | rs1 = yield self.mysql.runQuery("SELECT 1") 44 | rs2 = yield self.redis.get("foo") 45 | ... 46 | -------------------------------------------------------------------------------- /website/sphinx/e-mail.rst: -------------------------------------------------------------------------------- 1 | ``cyclone.mail`` --- Send e-mails with attachments 2 | ================================================== 3 | 4 | .. automodule:: cyclone.mail 5 | :members: 6 | -------------------------------------------------------------------------------- /website/sphinx/escape.rst: -------------------------------------------------------------------------------- 1 | ``cyclone.escape`` --- Escaping and string manipulation 2 | ======================================================= 3 | 4 | .. automodule:: cyclone.escape 5 | 6 | Escaping functions 7 | ------------------ 8 | 9 | .. autofunction:: xhtml_escape 10 | .. autofunction:: xhtml_unescape 11 | 12 | .. autofunction:: url_escape 13 | .. autofunction:: url_unescape 14 | 15 | .. autofunction:: json_encode 16 | .. autofunction:: json_decode 17 | 18 | Byte/unicode conversions 19 | ------------------------ 20 | These functions are used extensively within Cyclone itself, 21 | but should not be directly needed by most applications. 22 | 23 | .. autofunction:: utf8 24 | .. autofunction:: to_unicode 25 | .. function:: native_str 26 | 27 | Converts a byte or unicode string into type `str`. Equivalent to 28 | `utf8` on Python 2 and `to_unicode` on Python 3. 29 | 30 | .. autofunction:: to_basestring 31 | 32 | .. autofunction:: recursive_unicode 33 | 34 | Miscellaneous functions 35 | ----------------------- 36 | .. autofunction:: linkify 37 | .. autofunction:: squeeze 38 | -------------------------------------------------------------------------------- /website/sphinx/httpclient.rst: -------------------------------------------------------------------------------- 1 | ``cyclone.httpclient`` --- Non-blocking HTTP client 2 | =================================================== 3 | 4 | .. automodule:: cyclone.httpclient 5 | :members: 6 | -------------------------------------------------------------------------------- /website/sphinx/httpserver.rst: -------------------------------------------------------------------------------- 1 | ``cyclone.httpserver`` --- Non-blocking HTTP server 2 | =================================================== 3 | 4 | .. automodule:: cyclone.httpserver 5 | 6 | ``HTTPRequest`` objects 7 | ----------------------- 8 | .. autoclass:: HTTPRequest 9 | :members: 10 | 11 | HTTP Server 12 | ----------- 13 | .. autoclass:: HTTPConnection 14 | :members: 15 | -------------------------------------------------------------------------------- /website/sphinx/httputil.rst: -------------------------------------------------------------------------------- 1 | ``cyclone.httputil`` --- Manipulate HTTP headers and URLs 2 | ========================================================= 3 | 4 | .. automodule:: cyclone.httputil 5 | :members: 6 | -------------------------------------------------------------------------------- /website/sphinx/index.rst: -------------------------------------------------------------------------------- 1 | Cyclone documentation 2 | ===================== 3 | 4 | .. toctree:: 5 | :titlesonly: 6 | 7 | overview 8 | deferreds 9 | devserver 10 | webframework 11 | networking 12 | integration 13 | releases 14 | 15 | 16 | 17 | Indices and tables 18 | ================== 19 | 20 | * :ref:`genindex` 21 | * :ref:`modindex` 22 | * :ref:`search` 23 | -------------------------------------------------------------------------------- /website/sphinx/integration.rst: -------------------------------------------------------------------------------- 1 | Integration with other services 2 | =============================== 3 | 4 | .. toctree:: 5 | 6 | databases 7 | auth 8 | sse 9 | xmlrpc 10 | jsonrpc 11 | websocket 12 | -------------------------------------------------------------------------------- /website/sphinx/jsonrpc.rst: -------------------------------------------------------------------------------- 1 | ``cyclone.jsonrpc`` --- JSON-encoded Remote Procedure Call 2 | ========================================================== 3 | 4 | .. automodule:: cyclone.jsonrpc 5 | :members: 6 | -------------------------------------------------------------------------------- /website/sphinx/locale.rst: -------------------------------------------------------------------------------- 1 | ``cyclone.locale`` --- Internationalization support 2 | =================================================== 3 | 4 | .. automodule:: cyclone.locale 5 | :members: 6 | -------------------------------------------------------------------------------- /website/sphinx/networking.rst: -------------------------------------------------------------------------------- 1 | Asynchronous networking 2 | ======================= 3 | 4 | .. toctree:: 5 | 6 | redis 7 | httpclient 8 | e-mail 9 | -------------------------------------------------------------------------------- /website/sphinx/releases.rst: -------------------------------------------------------------------------------- 1 | Release notes 2 | ============= 3 | 4 | .. toctree:: 5 | :maxdepth: 2 6 | 7 | releases/next 8 | releases/v1.0 9 | -------------------------------------------------------------------------------- /website/sphinx/releases/next.rst: -------------------------------------------------------------------------------- 1 | What's new in the next release of Cyclone 2 | ========================================= 3 | 4 | In progress 5 | ----------- 6 | 7 | * Documentation 8 | * Code synch with latest Tornado 9 | * Better error handling (installer, engine, templates) 10 | * New log format, new features in `cyclone.app` 11 | * Updated demos 12 | -------------------------------------------------------------------------------- /website/sphinx/releases/v1.0.rst: -------------------------------------------------------------------------------- 1 | What's new in Cyclone 1.0 2 | ========================= 3 | 4 | December 31, 2012 5 | ----------------- 6 | 7 | :: 8 | 9 | We are pleased to announce the release of Cyclone 1.0, available 10 | from https://github.com/fiorix/cyclone/tags. 11 | 12 | * New set of application skeletons 13 | * PEP8 compliance 14 | * Documentation 15 | * Synch with latest txredisapi 16 | 17 | Many thanks to everyone who contributed patches, bug reports, and 18 | feedback that went into this release! 19 | 20 | -AF 21 | -------------------------------------------------------------------------------- /website/sphinx/sqlite.rst: -------------------------------------------------------------------------------- 1 | ``cyclone.sqlite`` --- Inline SQLite 2 | ==================================== 3 | 4 | .. automodule:: cyclone.sqlite 5 | :members: 6 | -------------------------------------------------------------------------------- /website/sphinx/sse.rst: -------------------------------------------------------------------------------- 1 | ``cyclone.sse`` --- Server-Sent Events 2 | ==================================================================== 3 | 4 | .. automodule:: cyclone.sse 5 | :members: 6 | -------------------------------------------------------------------------------- /website/sphinx/template.rst: -------------------------------------------------------------------------------- 1 | ``cyclone.template`` --- Flexible output generation 2 | =================================================== 3 | 4 | .. automodule:: cyclone.template 5 | 6 | Class reference 7 | --------------- 8 | 9 | .. autoclass:: Template(template_string, name="", loader=None, compress_whitespace=None, autoescape="xhtml_escape") 10 | :members: 11 | 12 | .. autoclass:: BaseLoader 13 | :members: 14 | 15 | .. autoclass:: Loader 16 | :members: 17 | 18 | .. autoclass:: DictLoader 19 | :members: 20 | 21 | .. autoexception:: ParseError 22 | -------------------------------------------------------------------------------- /website/sphinx/webframework.rst: -------------------------------------------------------------------------------- 1 | Core web framework 2 | ================== 3 | 4 | .. toctree:: 5 | :maxdepth: 2 6 | 7 | web 8 | httpserver 9 | httputil 10 | template 11 | escape 12 | locale 13 | bottle 14 | sqlite 15 | -------------------------------------------------------------------------------- /website/sphinx/websocket.rst: -------------------------------------------------------------------------------- 1 | ``cyclone.websocket`` --- Bidirectional communication to the browser 2 | ==================================================================== 3 | 4 | .. automodule:: cyclone.websocket 5 | :members: 6 | -------------------------------------------------------------------------------- /website/sphinx/xmlrpc.rst: -------------------------------------------------------------------------------- 1 | ``cyclone.xmlrpc`` --- XML-encoded Remote Procedure Call 2 | ======================================================== 3 | 4 | .. automodule:: cyclone.xmlrpc 5 | :members: 6 | -------------------------------------------------------------------------------- /website/static/base.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: white; 3 | color: black; 4 | font-family: Georgia, serif; 5 | font-size: 11pt; 6 | margin: 10px; 7 | margin-top: 15px; 8 | margin-bottom: 15px; 9 | } 10 | 11 | h1, 12 | h2, 13 | h3, 14 | h4 { 15 | font-family: Calibri, sans-serif; 16 | margin: 0; 17 | } 18 | 19 | img { 20 | border: 0; 21 | } 22 | 23 | pre, 24 | code { 25 | color: #060; 26 | } 27 | 28 | a, 29 | a code { 30 | color: #216093; 31 | } 32 | 33 | table { 34 | border-collapse: collapse; 35 | border: 0; 36 | } 37 | 38 | td { 39 | border: 0; 40 | padding: 0; 41 | } 42 | 43 | #body { 44 | margin: auto; 45 | max-width: 850px; 46 | } 47 | 48 | #header { 49 | margin-bottom: 15px; 50 | margin-right: 30px; 51 | } 52 | 53 | #content, 54 | #footer { 55 | margin-left: 31px; 56 | margin-right: 31px; 57 | } 58 | 59 | #content p, 60 | #content li, 61 | #footer { 62 | line-height: 16pt; 63 | } 64 | 65 | #content pre { 66 | line-height: 14pt; 67 | margin: 17pt; 68 | padding-left: 1em; 69 | border-left: 1px solid #ccc; 70 | } 71 | 72 | #footer { 73 | margin-top: 5em; 74 | } 75 | 76 | #header .logo { 77 | line-height: 0; 78 | padding-bottom: 5px; 79 | padding-right: 15px; 80 | } 81 | 82 | #header .logo img { 83 | width: 286px; 84 | height: 72px; 85 | } 86 | 87 | #header .title { 88 | vertical-align: bottom; 89 | } 90 | 91 | #header .title h1 { 92 | font-size: 35px; 93 | font-weight: normal; 94 | } 95 | 96 | #header .title h1, 97 | #header .title h1 a { 98 | color: #666; 99 | } 100 | 101 | #content h1, 102 | #content h2, 103 | #content h3 { 104 | color: #4d8cbf; 105 | margin-bottom: 2pt; 106 | margin-top: 17pt; 107 | } 108 | 109 | #content h2 { 110 | font-size: 19pt; 111 | } 112 | 113 | #content h3 { 114 | font-size: 15pt; 115 | } 116 | 117 | #content p { 118 | margin: 0; 119 | margin-bottom: 1em; 120 | } 121 | -------------------------------------------------------------------------------- /website/static/facebook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fiorix/cyclone/fefdc51dbf25b7470467fc8db6cf96d08191cf28/website/static/facebook.png -------------------------------------------------------------------------------- /website/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fiorix/cyclone/fefdc51dbf25b7470467fc8db6cf96d08191cf28/website/static/favicon.ico -------------------------------------------------------------------------------- /website/static/friendfeed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fiorix/cyclone/fefdc51dbf25b7470467fc8db6cf96d08191cf28/website/static/friendfeed.png -------------------------------------------------------------------------------- /website/static/robots.txt: -------------------------------------------------------------------------------- 1 | User-Agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /website/static/sphinx.css: -------------------------------------------------------------------------------- 1 | @import url("default.css"); 2 | 3 | /* These style tweaks are probably going to turn out to be a little fragile. 4 | They're currently based on the default theme from sphinx 1.0.7. 5 | */ 6 | 7 | div.body h1, 8 | div.body h2, 9 | div.body h3, 10 | div.body h4, 11 | div.body h5, 12 | div.body h6, 13 | div.sphinxsidebar h3, 14 | div.sphinxsidebar h4 { 15 | font-weight: bold; 16 | border-bottom: none; 17 | } 18 | 19 | pre { 20 | line-height: 14pt; 21 | margin: 17pt; 22 | padding-left: 1em; 23 | border: none; 24 | border-left: 1px solid #ccc; 25 | } 26 | 27 | div.body p, div.body dd, div.body li { 28 | text-align: left; 29 | } 30 | 31 | .highlight { 32 | background: #fff !important; 33 | } 34 | 35 | th.field-name { 36 | background: #fff; 37 | } 38 | 39 | tt { 40 | background: #fff; 41 | } 42 | 43 | /* "related" = top header */ 44 | div.related { 45 | position: fixed; 46 | } 47 | 48 | /* body settings copied from div.sphinxsidebar so following a link to a 49 | specific object positions that object below the fixed header */ 50 | div.body { 51 | top: 30px; 52 | bottom: 0; 53 | right: 0; 54 | left: 230px; 55 | margin: 0; 56 | position: fixed; 57 | overflow: auto; 58 | height: auto; 59 | } 60 | 61 | div.related, div.sphinxsidebar { 62 | font-family: Calibri, sans-serif; 63 | } -------------------------------------------------------------------------------- /website/static/tornado.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fiorix/cyclone/fefdc51dbf25b7470467fc8db6cf96d08191cf28/website/static/tornado.png -------------------------------------------------------------------------------- /website/static/twitter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fiorix/cyclone/fefdc51dbf25b7470467fc8db6cf96d08191cf28/website/static/twitter.png --------------------------------------------------------------------------------