From ec052826733d272859005c99f17352f8cd1005ad Mon Sep 17 00:00:00 2001 From: Nathan Farrington Date: Fri, 2 Mar 2012 13:43:32 -0800 Subject: [PATCH] Version 0.3.2 Fixed some formatting in README.markdown that was causing problems when converted to reStructredText. Added version information to README.markdown. Added installation instructions to README.markdown. Python: Added RPC message logging using the logging module. Python: Now using Distribute instead of distutils. Python: Added redis as an installation dependency. --- .gitignore | 9 ++- Makefile | 87 +++++++++++++++++++---- README.markdown | 116 ++++++++++++++++++++++++++++--- docs/github-flavored-markdown.rb | 65 +++++++++++++++++ python/examples/client.py | 11 ++- python/examples/server.py | 12 +++- python/redisrpc.py | 17 ++--- python/setup.py | 13 ++-- 8 files changed, 282 insertions(+), 48 deletions(-) create mode 100755 docs/github-flavored-markdown.rb diff --git a/.gitignore b/.gitignore index 6e85796..65d5857 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +README.html +# Ruby +.rvmrc # PHP composer.lock composer.phar @@ -5,7 +8,7 @@ php/vendor # Python *.pyc python/LICENSE -python/MANIFEST python/README.rst -python/build/ -python/dist/ +python/build +python/dist +python/redisrpc.egg-info diff --git a/Makefile b/Makefile index 692010d..16eebb6 100644 --- a/Makefile +++ b/Makefile @@ -1,17 +1,71 @@ -TARGETS:=\ - composer.phar\ - php/vendor\ - python/LICENSE\ - python/README.rst\ - python/MANIFEST\ - python/dist +BASIC_TARGETS:=\ + README.html + +PHP_TARGETS:=\ + $(BASIC_TARGETS)\ + composer.phar\ + php/vendor + +PYTHON_TARGETS:=\ + $(BASIC_TARGETS)\ + python/LICENSE\ + python/README.rst\ + python/dist + +ALL_TARGETS:=\ + $(BASIC_TARGETS)\ + $(PHP_TARGETS)\ + $(PYTHON_TARGETS) + +.PHONY: help +help: + @echo 'Build Targets:' + @echo ' all' + @echo ' php' + @echo ' python' + @echo 'Clean Targets:' + @echo ' clean-all' + @echo ' clean-php' + @echo ' clean-python' .PHONY: all -all: $(TARGETS) +all: $(ALL_TARGETS) + +.PHONY: basic +basic: $(BASIC_TARGETS) + +.PHONY: php +php: $(PHP_TARGETS) + +.PHONY: python +python: $(PYTHON_TARGETS) + +.PHONY: clean-all +clean-all: clean-php clean-python -.PHONY: clean -clean: - rm -rf $(TARGETS) python/build +.PHONY: clean-php +clean-php: + @rm -rf $(PHP_TARGETS) + @rm -rf composer.lock + +.PHONY: clean-python +clean-python: + @rm -rf $(PYTHON_TARGETS) + @rm -rf python/*.pyc python/examples/*.pyc + @rm -rf python/build + @rm -rf python/redisrpc.egg-info + +######################### +# Rules for Basic Targets +######################### + +# Ref: https://github.com/alampros/Docter +README.html: README.markdown + docs/github-flavored-markdown.rb $< >$@ + +####################### +# Rules for PHP Targets +####################### composer.phar: curl -s http://getcomposer.org/installer | php @@ -19,12 +73,15 @@ composer.phar: php/vendor: composer.phar php composer.phar install +########################## +# Rules for Python Targets +########################## + python/LICENSE: LICENSE cp -a $< $@ -# Ref: https://github.com/alampros/Docter -python/README.rst: README.markdown - ~/bin/github-flavored-markdown.rb $< | pandoc --from=html --to=rst --output=$@ +python/README.rst: README.html + cat $< | pandoc --from=html --to=rst --output=$@ -python/MANIFEST python/dist: python/LICENSE python/README.rst +python/dist: python/LICENSE python/README.rst cd python && python setup.py sdist diff --git a/README.markdown b/README.markdown index 7918d34..591d0ce 100644 --- a/README.markdown +++ b/README.markdown @@ -4,6 +4,9 @@ RedisRPC by Nathan Farrington +RedisRPC is the easiest to use RPC library in the world. (No small claim!) It +has implementations in PHP and Python, with Ruby comming soon. + Introduction ------------ @@ -32,19 +35,22 @@ languages are interoperable. src="http://github.com/nfarring/redisrpc/raw/master/docs/redisrpc_example.png" width=438 height=238> -1. The client issues an RPC Request by `RPUSH`ing an RPC Request message into a -Redis list called 'calc'. -2. The server `BLPOP`s the RPC Request message. +1. The client issues an RPC Request by using the Redis `RPUSH` command to push +an RPC Request message into a Redis list called `calc`. +2. The server retrieves the RPC Request message by using the Redis `BLPOP` +command. 3. The server dispatches the RPC Request to a local object, which in this case is a Calculator object. 4. The server accepts the return value (or exception) from the Calculator object. -5. The server issues an RPC Response by `RPUSH`ing an RPC Response message into -a Redis list called 'calc:rpc:``', which was chosen by the client. -6. The client `BLPOP`s the RPC Response message. +5. The server issues an RPC Response by using the Redis `RPUSH` command to push +an RPC Response message into a Redis list called `calc:rpc:`, +which was chosen by the client. +6. The client retrieves the RPC Response message by using the Redis `BLPOP` +command. -*Note that the server or client can be made non-blocking by using `LPOP` -instead of `BLPOP`. I currently do not need this feature and have not added -support for this; patches are welcome.* +*Note that the server or client can be made non-blocking by using the Redis +LPOP command instead of BLPOP. I currently do not need this feature and have +not added support for this, but patches are welcome.* That's all there is to it! @@ -104,6 +110,28 @@ server = redisrpc.Server(redis_server, input_queue, local_object) server.run() ``` +Installation +------------ + +### PHP Installation + +The [Predis][Predis] library is required. + +The RedisRPC PHP library is available from [Packagist][Packagist] at: +. You can +use [Composer][Composer] to install into your PHP project. + +### Python Installation + +The [redis-py][redis-py] library is required. + +The RedisRPC Python library is available from [PyPI][PyPI] at: +. You can install with [pip][pip]. + +```python +pip install redisrpc +``` + Internal Message Formats ------------------------ All RPC messages are JSON objects. User code will never see these objects @@ -161,6 +189,64 @@ License ------- This software is available under the [GPLv3][GPLv3] or later. +Changelog +---------- +Version 0.3.2 + +* Fixed some formatting in README.markdown that was causing problems when + converted to reStructredText. +* Added version information to README.markdown. +* Added installation instructions to README.markdown. +* Python: Added RPC message logging using the logging module. +* Python: Added redis as an installation dependency. +* Python: Now using Distribute instead of distutils. + +Version 0.3.1 + +* PHP: Changed composer.json predis dependency version. + +Version 0.3.0 + +* Empty function call args and kwargs are no longer transmitted. +* PHP: Added support for the PHP language. +* PHP: Now installable with PHP Composer. +* Python: Shortened the Client and Server class names. +* Python: Debugging modified to print JSON representation. +* Python: Switched the README file back to ReStructred Text. + +Version 0.2.1 + +* Python: Fixed MANIFEST.in to reflect filename changes. + +Version 0.2.0 + +* Simplified the JSON RPC message format. +* Documented the JSON RPC message format. +* Python: Using HTML file for README, will it work? +* Python: Renamed calc_client to client.py. +* Python: Renamed calc_server to server.py. +* Python: Added a RemoteException class, which can be raised by the client. + +Version 0.1.2 + +* Python: Fixed the download_url in setup.py. +* Python: Renamed the README file to README.rst to support browsing on Github. + +Version 0.1.1 + +* Python: Added README. +* Python: Added long_description to setup.py. +* Python: Added MANIFEST.in file. +* Python: Added examples/ subdirectory to MANIFEST. +* Python: Modified examples/ directory to be consistent with README file. +* Python: Fixed the download_url in setup.py. + +Version 0.1.0 + +* Changed to the GPL license. +* Python: Removed unused functionality from python/redisrpc.py. +* Python: Added a setup.py installer script. + [Redis]: http://redis.io/ [JSON]: http://json.org/ @@ -169,4 +255,16 @@ This software is available under the [GPLv3][GPLv3] or later. [Thrift]: https://en.wikipedia.org/wiki/Apache_Thrift +[Predis]: https://github.com/nrk/predis + +[Packagist]: http://packagist.org/ + +[Composer]: https://github.com/composer/composer + +[redis-py]: https://github.com/andymccurdy/redis-py + +[PyPI]: http://pypi.python.org/ + +[pip]: http://pypi.python.org/pypi/pip + [GPLv3]: http://www.gnu.org/licenses/gpl.html diff --git a/docs/github-flavored-markdown.rb b/docs/github-flavored-markdown.rb new file mode 100755 index 0000000..019336c --- /dev/null +++ b/docs/github-flavored-markdown.rb @@ -0,0 +1,65 @@ +#!/usr/bin/env ruby +# @author Aaron Lampros +# +# Github-flavored markdown to HTML, in a command-line util. +# +# $ cat README.md | ./github-flavored-markdown.rb +# +# Notes: +# You will need to install Pygments for syntax coloring +# ```bash +# $ sudo easy_install pygments +# ``` +# +# Install the gems `redcarpet` and `Pygments` +# +# +require 'rubygems' +require 'redcarpet' +require 'pygments.rb' + +# puts Pygments.styles() +# monokai +# manni +# perldoc +# borland +# colorful +# default +# murphy +# vs +# trac +# tango +# fruity +# autumn +# bw +# emacs +# vim +# pastie +# friendly +# native +class HTMLwithPygments < Redcarpet::Render::XHTML +# def doc_header() +# '' +# end +# def block_code(code, language) +# Pygments.highlight(code, :lexer => language, :options => {:encoding => 'utf-8'}) +# end +end + + +def fromMarkdown(text) + # options = [:fenced_code => true, :generate_toc => true, :hard_wrap => true, :no_intraemphasis => true, :strikethrough => true ,:gh_blockcode => true, :autolink => true, :xhtml => true, :tables => true] + markdown = Redcarpet::Markdown.new(HTMLwithPygments, + :fenced_code_blocks => true, + :no_intra_emphasis => true, + :autolink => true, + :strikethrough => true, + :lax_html_blocks => true, + :superscript => true, + :hard_wrap => true, + :tables => true, + :xhtml => true) + markdown.render(text) +end + +puts fromMarkdown(ARGF.read) diff --git a/python/examples/client.py b/python/examples/client.py index 3c70ad1..385261e 100755 --- a/python/examples/client.py +++ b/python/examples/client.py @@ -1,13 +1,18 @@ #!/usr/bin/env python +import logging import traceback +import sys import redis import redisrpc import calc -#redisrpc.DEBUG=True + +# Direct all RedisPRC logging messages to stderr. +logging.basicConfig(stream=sys.stderr, level=logging.DEBUG) + def do_calculations(calculator): calculator.clr() @@ -22,12 +27,14 @@ def do_calculations(calculator): except (AttributeError, redisrpc.RemoteException): pass + # 1. Local object calculator = calc.Calculator() do_calculations(calculator) # 2. Remote object, should act like local object redis_server = redis.Redis() -calculator = redisrpc.Client(redis_server, 'calc') +input_queue = 'calc' +calculator = redisrpc.Client(redis_server, input_queue) do_calculations(calculator) print('success!') diff --git a/python/examples/server.py b/python/examples/server.py index 04daff0..f28f725 100755 --- a/python/examples/server.py +++ b/python/examples/server.py @@ -1,12 +1,20 @@ #!/usr/bin/env python +import logging +import sys + import redis import redisrpc import calc -#redisrpc.DEBUG=True + +# Direct all RedisPRC logging messages to stderr. +logging.basicConfig(stream=sys.stderr, level=logging.DEBUG) + redis_server = redis.Redis() -server = redisrpc.Server(redis_server, 'calc', calc.Calculator()) +input_queue = 'calc' +local_object = calc.Calculator() +server = redisrpc.Server(redis_server, input_queue, local_object) server.run() diff --git a/python/redisrpc.py b/python/redisrpc.py index 2260bb6..1e6708f 100644 --- a/python/redisrpc.py +++ b/python/redisrpc.py @@ -15,6 +15,7 @@ import json +import logging import random import string import sys @@ -29,10 +30,6 @@ ] -# Set this to True to print additional debugging information. -DEBUG=False - - def random_string(size=8, chars=string.ascii_uppercase + string.digits): """Ref: http://stackoverflow.com/questions/2257441""" return ''.join(random.choice(chars) for x in xrange(size)) @@ -66,7 +63,7 @@ def from_dict(dictionary): kwargs = dictionary.get('kwargs') return FunctionCall(name, args, kwargs) - def __init__(self, name, args=(), kwargs={}): + def __init__(self, name, args=None, kwargs=None): """Create a new FunctionCall from a method name, an optional argument tuple, and an optional keyword argument dictionary.""" self['name'] = name @@ -102,16 +99,15 @@ def call(self, method_name, *args, **kwargs): response_queue = self.input_queue + ':rpc:' + random_string() rpc_request = dict(function_call=function_call, response_queue=response_queue) message = json.dumps(rpc_request) - if DEBUG: print('RPC Request: %s' % message) + logging.debug('RPC Request: %s' % message) self.redis_server.rpush(self.input_queue, message) timeout_s = 0 # Block forever. message_queue, message = self.redis_server.blpop(response_queue, timeout_s) assert message_queue == response_queue - if DEBUG: print('RPC Response: %s\n' % message) + logging.debug('RPC Response: %s' % message) rpc_response = json.loads(message) exception = rpc_response.get('exception') if exception is not None: - if DEBUG: print('exception: %s\n' % exception) raise RemoteException(exception) if 'return_value' not in rpc_response: raise RemoteException('Malformed RPC Response message: %s' % rpc_response) @@ -134,12 +130,11 @@ def run(self): while True: message_queue, message = self.redis_server.blpop(self.input_queue) assert message_queue == self.input_queue - if DEBUG: print('RPC Request: %s' % message) + logging.debug('RPC Request: %s' % message) rpc_request = json.loads(message) response_queue = rpc_request['response_queue'] function_call = FunctionCall.from_dict(rpc_request['function_call']) code = 'return_value = self.local_object.' + function_call.as_python_code() - if DEBUG: print(code) try: exec(code) rpc_response = dict(return_value=return_value) @@ -147,7 +142,7 @@ def run(self): (type, value, traceback) = sys.exc_info() rpc_response = dict(exception=repr(value)) message = json.dumps(rpc_response) - if DEBUG: print('RPC Response: %s\n' % message) + logging.debug('RPC Response: %s' % message) self.redis_server.rpush(response_queue, message) diff --git a/python/setup.py b/python/setup.py index bd3b672..b31077a 100644 --- a/python/setup.py +++ b/python/setup.py @@ -1,7 +1,7 @@ -from distutils.core import setup +from setuptools import setup # Ref: http://semver.org/ -VERSION='0.3.1' +VERSION='0.3.2' AUTHOR='Nathan Farrington' AUTHOR_EMAIL='nfarring@gmail.com' @@ -18,6 +18,10 @@ ] DESCRIPTION='Lightweight RPC using Redis' DOWNLOAD_URL='https://github.com/downloads/nfarring/redisrpc/redisrpc-python-%s.tar.gz' % VERSION +INSTALL_REQUIRES=[ + 'distribute', + 'redis' +] KEYWORDS=['Redis','RPC'] with open('README.rst','r') as f: LONG_DESCRIPTION=''.join(f.readlines()) @@ -25,9 +29,6 @@ MAINTAINER_EMAIL=AUTHOR_EMAIL NAME='redisrpc' PY_MODULES=['redisrpc'] -REQUIRES=[ - 'redis (>=2.0.0)' -] URL='http://github.com/nfarring/redisrpc' setup( @@ -36,13 +37,13 @@ classifiers=CLASSIFIERS, description=DESCRIPTION, download_url=DOWNLOAD_URL, + install_requires=INSTALL_REQUIRES, keywords=KEYWORDS, long_description=LONG_DESCRIPTION, maintainer=MAINTAINER, maintainer_email=MAINTAINER_EMAIL, name=NAME, py_modules=PY_MODULES, - requires=REQUIRES, url=URL, version=VERSION )