diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1b12640 --- /dev/null +++ b/.gitignore @@ -0,0 +1,50 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +bin/ +build/ +develop-eggs/ +dist/ +eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.cache +nosetests.xml +coverage.xml + +# Mr Developer +.mr.developer.cfg +.project +.pydevproject + +# Rope +.ropeproject + +# Sphinx documentation +docs/_build/ + +# System +.DS_Store + diff --git a/README.rst b/README.rst index a448ca6..5219a01 100644 --- a/README.rst +++ b/README.rst @@ -5,20 +5,26 @@ Elegant WebSockets for your Flask apps. .. image:: http://farm4.staticflickr.com/3689/9755961864_577e32a106_c.jpg + +Simple usage of ``route`` decorator: + .. code-block:: python from flask import Flask from flask_sockets import Sockets + app = Flask(__name__) sockets = Sockets(app) + @sockets.route('/echo') def echo_socket(ws): while not ws.closed: message = ws.receive() ws.send(message) + @app.route('/') def hello(): return 'Hello World!' @@ -31,6 +37,43 @@ Elegant WebSockets for your Flask apps. server.serve_forever() +Usage of `Flask blueprints`_: + +.. code-block:: python + + from flask import Flask, Blueprint + from flask_sockets import Sockets + + + html = Blueprint(r'html', __name__) + ws = Blueprint(r'ws', __name__) + + + @html.route('/') + def hello(): + return 'Hello World!' + + @ws.route('/echo') + def echo_socket(socket): + while not socket.closed: + message = socket.receive() + socket.send(message) + + + app = Flask(__name__) + sockets = Sockets(app) + + app.register_blueprint(html, url_prefix=r'/') + sockets.register_blueprint(ws, url_prefix=r'/') + + + if __name__ == "__main__": + from gevent import pywsgi + from geventwebsocket.handler import WebSocketHandler + server = pywsgi.WSGIServer(('', 5000), app, handler_class=WebSocketHandler) + server.serve_forever() + + Serving WebSockets in Python was really difficult. Now it's not. @@ -85,6 +128,12 @@ The basic methods are fairly straightforward —  Release History --------------- +v0.2.1 +~~~~~~ + +- Add support of `Flask blueprints`_. + + v0.2.0 ~~~~~~ @@ -98,5 +147,7 @@ v0.1.0 - Initial release. +.. _Flask blueprints: http://flask.pocoo.org/docs/latest/blueprints/ + diff --git a/flask_sockets.py b/flask_sockets.py index de48e3b..64e19cf 100644 --- a/flask_sockets.py +++ b/flask_sockets.py @@ -1,7 +1,9 @@ # -*- coding: utf-8 -*- + from werkzeug.routing import Map, Rule from werkzeug.exceptions import NotFound + def log_request(self): log = self.server.log if log: @@ -18,13 +20,13 @@ try: except ImportError: pass + if 'gevent' in locals(): # Freedom-Patch logger for Gunicorn. if hasattr(gevent, 'pywsgi'): gevent.pywsgi.WSGIHandler.log_request = log_request - class SocketMiddleware(object): def __init__(self, wsgi_app, app, socket): @@ -49,7 +51,19 @@ class SocketMiddleware(object): class Sockets(object): def __init__(self, app=None): + #: Compatibility with 'Flask' application. + #: The :class:`~werkzeug.routing.Map` for this instance. You can use + #: this to change the routing converters after the class was created + #: but before any routes are connected. self.url_map = Map() + + #: Compatibility with 'Flask' application. + #: All the attached blueprints in a dictionary by name. Blueprints + #: can be attached multiple times so this dictionary does not tell + #: you how often they got attached. + self.blueprints = {} + self._blueprint_order = [] + if app: self.init_app(app) @@ -67,6 +81,29 @@ class Sockets(object): def add_url_rule(self, rule, _, f, **options): self.url_map.add(Rule(rule, endpoint=f)) + def register_blueprint(self, blueprint, **options): + """ + Registers a blueprint for web sockets like for 'Flask' application. + + Decorator :meth:`~flask.app.setupmethod` is not applied, because it + requires ``debug`` and ``_got_first_request`` attributes to be defined. + """ + first_registration = False + + if blueprint.name in self.blueprints: + assert self.blueprints[blueprint.name] is blueprint, ( + 'A blueprint\'s name collision occurred between %r and ' + '%r. Both share the same name "%s". Blueprints that ' + 'are created on the fly need unique names.' + % (blueprint, self.blueprints[blueprint.name], blueprint.name)) + else: + self.blueprints[blueprint.name] = blueprint + self._blueprint_order.append(blueprint) + first_registration = True + + blueprint.register(self, options, first_registration) + + # CLI sugar. if 'Worker' in locals(): worker = Worker diff --git a/setup.py b/setup.py index b4e4e01..23f1136 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ from setuptools import setup setup( name='Flask-Sockets', - version='0.2.0', + version='0.2.1', url='https://github.com/kennethreitz/flask-sockets', license='See License', author='Kenneth Reitz', @@ -36,4 +36,4 @@ setup( 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', 'Topic :: Software Development :: Libraries :: Python Modules' ] -) \ No newline at end of file +)