pax_global_header00006660000000000000000000000064125532313150014512gustar00rootroot0000000000000052 comment=9c05f32bd98d4ea1fbf7bcd65d7ae8e628322c2c python-pykka-1.2.1/000077500000000000000000000000001255323131500141515ustar00rootroot00000000000000python-pykka-1.2.1/.coveragerc000066400000000000000000000001161255323131500162700ustar00rootroot00000000000000[report] omit = */pyshared/* */python?.?/* */site-packages/nose/* python-pykka-1.2.1/.gitignore000066400000000000000000000001651255323131500161430ustar00rootroot00000000000000*.egg-info *.py,cover *.pyc *.swp .coverage .tox MANIFEST build/ cover dist/ docs/_build/* nosetests.xml xunit-*.xml python-pykka-1.2.1/.travis.yml000066400000000000000000000006121255323131500162610ustar00rootroot00000000000000sudo: false language: python addons: apt: packages: - libev-dev env: - TOX_ENV=py26 - TOX_ENV=py27 - TOX_ENV=py32 - TOX_ENV=py33 - TOX_ENV=py34 - TOX_ENV=pypy - TOX_ENV=pypy3 - TOX_ENV=docs - TOX_ENV=flake8 install: - "pip install tox" script: - "tox -e $TOX_ENV" after_success: - "if [ $TOX_ENV == 'py27' ]; then pip install coveralls; coveralls; fi" python-pykka-1.2.1/LICENSE000066400000000000000000000261361255323131500151660ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. python-pykka-1.2.1/MANIFEST.in000066400000000000000000000003441255323131500157100ustar00rootroot00000000000000include *.rst include .coveragerc include .travis.yml include LICENSE include pylintrc include setup.cfg include tox.ini recursive-include docs * prune docs/_build recursive-include examples *.py recursive-include tests *.py python-pykka-1.2.1/README.rst000066400000000000000000000315361255323131500156500ustar00rootroot00000000000000===== Pykka ===== Pykka is a Python implementation of the `actor model `_. The actor model introduces some simple rules to control the sharing of state and cooperation between execution units, which makes it easier to build concurrent applications. Rules of the actor model ======================== - An actor is an execution unit that executes concurrently with other actors. - An actor does not share state with anybody else, but it can have its own state. - An actor can only communicate with other actors by sending and receiving messages. It can only send messages to actors whose address it has. - When an actor receives a message it may take actions like: - altering its own state, e.g. so that it can react differently to a future message, - sending messages to other actors, or - starting new actors. None of the actions are required, and they may be applied in any order. - An actor only processes one message at a time. In other words, a single actor does not give you any concurrency, and it does not need to use locks internally to protect its own state. The actor implementations ========================= Pykka's actor API comes with the following implementations: - Threads: Each ``ThreadingActor`` is executed by a regular thread, i.e. ``threading.Thread``. As handles for future results, it uses ``ThreadingFuture`` which is a thin wrapper around a ``Queue.Queue``. It has no dependencies outside Python itself. ``ThreadingActor`` plays well together with non-actor threads. Note: If you monkey patch the standard library with ``gevent`` or ``eventlet`` you can still use ``ThreadingActor`` and ``ThreadingFuture``. Python's threads will transparently use the underlying implementation provided by gevent or Eventlet. - gevent: Each ``GeventActor`` is executed by a gevent greenlet. `gevent `_ is a coroutine-based Python networking library built on top of a libevent (in 0.13) or libev (in 1.0) event loop. ``GeventActor`` is generally faster than ``ThreadingActor``, but as of gevent 0.13 it doesn't work in processes with other threads, which limits when it can be used. With gevent 1.0, which is currently available as a release candidate, this is no longer an issue. Pykka works with both gevent 0.13 and 1.0. - Eventlet: Each ``EventletActor`` is executed by a Eventlet greenlet. Pykka is tested with Eventlet 0.12.1. Pykka has an extensive test suite, and is tested on CPython 2.6, 2.7, and 3.2+, as well as PyPy. gevent and eventlet are currently not available for CPython 3.x or PyPy. A basic actor ============= In its most basic form, a Pykka actor is a class with an ``on_receive(message)`` method:: import pykka class Greeter(pykka.ThreadingActor): def on_receive(self, message): print('Hi there!') To start an actor, you call the class' method ``start()``, which starts the actor and returns an actor reference which can be used to communicate with the running actor:: actor_ref = Greeter.start() If you need to pass arguments to the actor upon creation, you can pass them to the ``start()`` method, and receive them using the regular ``__init__()`` method:: import pykka class Greeter(pykka.ThreadingActor): def __init__(self, greeting='Hi there!'): super(Greeter, self).__init__() self.greeting = greeting def on_receive(self, message): print(self.greeting) actor_ref = Greeter.start(greeting='Hi you!') It can be useful to know that the init method is run in the execution context that starts the actor. There are also hooks for running code in the actor's own execution context when the actor starts, when it stops, and when an unhandled exception is raised. Check out the full API docs for the details. To stop an actor, you can either call ``stop()`` on the ``actor_ref``:: actor_ref.stop() Or, if an actor wants to stop itself, it can simply do so:: self.stop() Once an actor has been stopped, it cannot be restarted. Sending messages ---------------- To send a message to the actor, you can either use the ``tell()`` method or the ``ask()`` method on the ``actor_ref`` object. ``tell()`` will fire of a message without waiting for an answer. In other words, it will never block. ``ask()`` will by default block until an answer is returned, potentially forever. If you provide a ``timeout`` keyword argument to ``ask()``, you can specify for how long it should wait for an answer. If you want an answer, but don't need it right away because you have other stuff you can do first, you can pass ``block=False``, and ``ask()`` will immediately return a "future" object. The message itself must always be a dict, but you're mostly free to use whatever dict keys you want to. Summarized in code:: actor_ref.tell({'msg': 'Hi!'}) # => Returns nothing. Will never block. answer = actor_ref.ask({'msg': 'Hi?'}) # => May block forever waiting for an answer answer = actor_ref.ask({'msg': 'Hi?'}, timeout=3) # => May wait 3s for an answer, then raises exception if no answer. future = actor_ref.ask({'msg': 'Hi?'}, block=False) # => Will return a future object immediately. answer = future.get() # => May block forever waiting for an answer answer = future.get(timeout=0.1) # => May wait 0.1s for an answer, then raises exception if no answer. For performance reasons, Pykka **does not** clone the dict you send before delivering it to the receiver. You are yourself responsible for either using immutable data structures or to ``copy.deepcopy()`` the data you're sending off to other actors. Replying to messages -------------------- If a message is sent using ``actor_ref.ask()`` you can reply to the sender of the message by simply returning a value from ``on_receive`` method:: import pykka class Greeter(pykka.ThreadingActor): def on_receive(self, message): return 'Hi there!' actor_ref = Greeter.start() answer = actor_ref.ask({'msg': 'Hi?'}) print(answer) # => 'Hi there!' ``None`` is a valid response so if you return ``None`` explicitly, or don't return at all, a response containing ``None`` will be returned to the sender. From the point of view of the actor it doesn't matter whether the message was sent using ``actor_ref.tell()`` or ``actor_ref.ask()`` . When the sender doesn't expect a response the ``on_receive`` return value will be ignored. The situation is similar in regard to exceptions: when ``actor_ref.ask()`` is used and you raise an exception from within ``on_receive`` method it will propagate to the sender:: import pykka class Raiser(pykka.ThreadingActor): def on_receive(self, message): raise Exception('Oops') actor_ref = Raiser.start() try: actor_ref.ask({'msg': 'How are you?'}) except Exception as e: print(repr(e)) # => Exception('Oops') Actor proxies ============= With the basic building blocks provided by actors and futures, we got everything we need to build more advanced abstractions. Pykka provides a single abstraction on top of the basic actor model, named "actor proxies". You can use Pykka without proxies, but we've found it to be a very convenient abstraction when builing `Mopidy `_. Let's create an actor and start it:: import pykka class Calculator(pykka.ThreadingActor): def __init__(self): super(Calculator, self).__init__() self.last_result = None def add(self, a, b=None): if b is not None: self.last_result = a + b else: self.last_result += a return self.last_result def sub(self, a, b=None): if b is not None: self.last_result = a - b else: self.last_result -= a return self.last_result actor_ref = Calculator.start() You can create a proxy from any reference to a running actor:: proxy = actor_ref.proxy() The proxy object will use introspection to figure out what public attributes and methods the actor has, and then mirror the full API of the actor. Any attribute or method prefixed with underscore will be ignored, which is the convention for keeping stuff private in Python. When we access attributes or call methods on the proxy, it will ask the actor to access the given attribute or call the given method, and return the result to us. All results are wrapped in "future" objects, so you must use the ``get()`` method to get the actual data:: future = proxy.add(1, 3) future.get() # => 4 proxy.last_result.get() # => 4 Since an actor only processes one message at the time and all messages are kept in order, you don't need to add the call to ``get()`` just to block processing until the actor has completed processing your last message:: proxy.sub(5) proxy.add(3) proxy.last_result.get() # => 2 Since assignment doesn't return anything, it works just like on regular objects:: proxy.last_result = 17 proxy.last_result.get() # => 17 Under the hood, the proxy does everything by sending messages to the actor using the regular ``actor_ref.ask()`` method we talked about previously. By doing so, it maintains the actor model restrictions. The only "magic" happening here is some basic introspection and automatic building of three different message types; one for method calls, one for attribute reads, and one for attribute writes. Traversable attributes on proxies --------------------------------- Sometimes you'll want to access an actor attribute's methods or attributes through a proxy. For this case, Pykka supports "traversable attributes". By marking an actor attribute as traversable, Pykka will not return the attribute when accessed, but wrap it in a new proxy which is returned instead. To mark an attribute as traversable, simply set the ``pykka_traversable`` attribute to ``True``:: import pykka class AnActor(pykka.ThreadingActor): playback = Playback() class Playback(object): pykka_traversable = True def play(self): # ... return True proxy = AnActor.start().proxy() play_success = proxy.playback.play().get() You can access methods and attributes nested as deep as you like, as long as all attributes on the path between the actor and the method or attribute on the end is marked as traversable. Examples ======== See the ``examples/`` dir in `Pykka's Git repo `_ for some runnable examples. What Pykka is not ================= Much of the naming of concepts and methods in Pykka is taken from the `Akka `_ project which implements actors on the JVM. Though, Pykka does not aim to be a Python port of Akka, and supports far fewer features. Notably, Pykka **does not** support the following features: - Supervision: Linking actors, supervisors, or supervisor groups. - Remoting: Communicating with actors running on other hosts. - Routers: Pykka does not come with a set of predefined message routers, though you may make your own actors for routing messages. Installation ============ Install Pykka's dependencies: - Python 2.6, 2.7, or 3.2+ - Optionally, Python 2.6/2.7 only: - `gevent `_, if you want to use gevent based actors from ``pykka.gevent``. - `eventlet `_, if you want to use eventlet based actors from ``pykka.eventlet``. Eventlet is known to work with PyPy 2.0 as well but Pykka is not tested with it yet. To install Pykka you can use pip:: pip install pykka To upgrade your Pykka installation to the latest released version:: pip install --upgrade pykka To install the latest development snapshot:: pip install pykka==dev License ======= Pykka is copyright 2010-2015 Stein Magnus Jodal and contributors. Pykka is licensed under the `Apache License, Version 2.0 `_. Project resources ================= - `Documentation `_ - `Source code `_ - `Issue tracker `_ - `CI server `_ - `Download development snapshot `_ .. image:: https://img.shields.io/pypi/v/Pykka.svg?style=flat :target: https://pypi.python.org/pypi/Pykka/ :alt: Latest PyPI version .. image:: https://img.shields.io/pypi/dm/Pykka.svg?style=flat :target: https://pypi.python.org/pypi/Pykka/ :alt: Number of PyPI downloads .. image:: https://img.shields.io/travis/jodal/pykka/develop.svg?style=flat :target: https://travis-ci.org/jodal/pykka :alt: Travis CI build status .. image:: https://img.shields.io/coveralls/jodal/pykka/develop.svg?style=flat :target: https://coveralls.io/r/jodal/pykka?branch=develop :alt: Test coverage python-pykka-1.2.1/dev-requirements.txt000066400000000000000000000005421255323131500202120ustar00rootroot00000000000000# Build documentation sphinx # Check code style, error, etc flake8 flake8-import-order # Mock dependencies in tests mock # Test runners nose tox # Measure test's code coverage coverage # Check that MANIFEST.in matches Git repo contents before making a release check-manifest # To make wheel packages wheel # Securely upload packages to PyPI twine python-pykka-1.2.1/docs/000077500000000000000000000000001255323131500151015ustar00rootroot00000000000000python-pykka-1.2.1/docs/Makefile000066400000000000000000000107341255323131500165460ustar00rootroot00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " text to make text files" @echo " man to make manual pages" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: -rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Pykka.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Pykka.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/Pykka" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Pykka" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." make -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." python-pykka-1.2.1/docs/_build/000077500000000000000000000000001255323131500163375ustar00rootroot00000000000000python-pykka-1.2.1/docs/_build/.gitignore000066400000000000000000000000001255323131500203150ustar00rootroot00000000000000python-pykka-1.2.1/docs/_static/000077500000000000000000000000001255323131500165275ustar00rootroot00000000000000python-pykka-1.2.1/docs/_static/.gitignore000066400000000000000000000000001255323131500205050ustar00rootroot00000000000000python-pykka-1.2.1/docs/api.rst000066400000000000000000000043421255323131500164070ustar00rootroot00000000000000========= Pykka API ========= .. module:: pykka .. attribute:: __version__ Pykka's :pep:`386` and :pep:`396` compatible version number Actors ====== .. autoexception:: pykka.ActorDeadError .. autoclass:: pykka.Actor :members: .. autoclass:: pykka.ThreadingActor :members: .. autoclass:: pykka.ActorRef :members: Proxies ======= .. autoclass:: pykka.ActorProxy :members: Futures ======= .. autoexception:: pykka.Timeout .. autoclass:: pykka.Future :members: .. autoclass:: pykka.ThreadingFuture :members: .. autofunction:: pykka.get_all Registry ======== .. autoclass:: pykka.ActorRegistry :members: Gevent support ============== .. automodule:: pykka.gevent :members: Eventlet support ================ .. automodule:: pykka.eventlet :members: Logging ======= Pykka uses Python's standard :mod:`logging` module for logging debug statements and any unhandled exceptions in the actors. All log records emitted by Pykka are issued to the logger named "pykka", or a sublogger of it. Out of the box, Pykka is set up with :class:`logging.NullHandler` as the only log record handler. This is the recommended approach for logging in libraries, so that the application developer using the library will have full control over how the log records from the library will be exposed to the application's users. In other words, if you want to see the log records from Pykka anywhere, you need to add a useful handler to the root logger or the logger named "pykka" to get any log output from Pykka. The defaults provided by :meth:`logging.basicConfig` is enough to get debug log statements out of Pykka:: import logging logging.basicConfig(level=logging.DEBUG) If your application is already using :mod:`logging`, and you want debug log output from your own application, but not from Pykka, you can ignore debug log messages from Pykka by increasing the threshold on the Pykka logger to "info" level or higher:: import logging logging.basicConfig(level=logging.DEBUG) logging.getLogger('pykka').setLevel(logging.INFO) For more details on how to use :mod:`logging`, please refer to the Python standard library documentation. Debug helpers ============= .. automodule:: pykka.debug :members: python-pykka-1.2.1/docs/changes.rst000066400000000000000000000267511255323131500172560ustar00rootroot00000000000000======= Changes ======= v1.2.1 (2015-07-20) =================== - Increase log level of :func:`pykka.debug.log_thread_tracebacks` debugging helper from :attr:`logging.INFO` to :attr:`logging.CRITICAL`. - Fix errors in docs examples. (PR: :issue:`29`, :issue:`43`) - Fix typos in docs. - Various project setup and development improvements. v1.2.0 (2013-07-15) =================== - Enforce that multiple calls to :meth:`pykka.Future.set` raises an exception. This was already the case for some implementations. The exception raised is not specified. - Add :meth:`pykka.Future.set_get_hook`. - Add :meth:`~Pykka.Future.filter`, :meth:`~pykka.Future.join`, :meth:`~pykka.Future.map`, and :meth:`~pykka.Future.reduce` as convenience methods using the new :meth:`~pykka.Future.set_get_hook` method. - Add support for running actors based on eventlet greenlets. See :mod:`pykka.eventlet` for details. Thanks to Jakub Stasiak for the implementation. - Update documentation to reflect that the ``reply_to`` field on the message is private to Pykka. Actors should reply to messages simply by returning the response from :meth:`~pykka.Actor.on_receive`. The internal field is renamed to ``pykka_reply_to`` a to avoid collisions with other message fields. It is also removed from the message before the message is passed to :meth:`~pykka.Actor.on_receive`. Thanks to Jakub Stasiak. - When messages are left in the actor inbox after the actor is stopped, those messages that are expecting a reply is now rejected by replying with an :exc:`~pykka.ActorDeadError` exception. This causes other actors blocking on the returned :class:`~pykka.Future` without a timeout to raise the exception instead of waiting forever. Thanks to Jakub Stasiak. This makes the behavior of messaging an actor around the time it is stopped more consistent: - Messaging an already dead actor immediately raises :exc:`~pykka.ActorDeadError`. - Messaging an alive actor that is stopped before it processes the message will cause the reply future to raise :exc:`~pykka.ActorDeadError`. Similarly, if you ask an actor to stop multiple times, and block on the responses, all the messages will now get an reply. Previously only the first message got a reply, potentially making the application wait forever on replies to the subsequent stop messages. - When :meth:`~pykka.ActorRef.ask` is used to asynchronously message a dead actor (e.g. ``block`` set to :class:`False`), it will no longer immediately raise :exc:`~pykka.ActorDeadError`. Instead, it will return a future and fail the future with the :exc:`~pykka.ActorDeadError` exception. This makes the interface more consistent, as you'll have one instead of two ways the call can raise exceptions under normal conditions. If :meth:`~pykka.ActorRef.ask` is called synchronously (e.g. ``block`` set to :class:`True`), the behavior is unchanged. - A change to :meth:`~pykka.ActorRef.stop` reduces the likelyhood of a race condition when asking an actor to stop multiple times by not checking if the actor is dead before asking it to stop, but instead just go ahead and leave it to :meth:`~pykka.ActorRef.tell` to do the alive-or-dead check a single time, and as late as possible. - Change :meth:`~pykka.ActorRef.is_alive` to check the actor's runnable flag instead of checking if the actor is registrered in the actor registry. v1.1.0 (2013-01-19) =================== - An exception raised in :meth:`pykka.Actor.on_start` didn't stop the actor properly. Thanks to Jay Camp for finding and fixing the bug. - Make sure exceptions in :meth:`pykka.Actor.on_stop` and :meth:`pykka.Actor.on_failure` is logged. - Add :attr:`pykka.ThreadingActor.use_daemon_thread` flag for optionally running an actor on a daemon thread, so that it doesn't block the Python program from exiting. (Fixes: :issue:`14`) - Add :func:`pykka.debug.log_thread_tracebacks` debugging helper. (Fixes: :issue:`17`) v1.0.1 (2012-12-12) =================== - Name the threads of :class:`pykka.ThreadingActor` after the actor class name instead of "PykkaThreadingActor-N" to ease debugging. (Fixes: :issue:`12`) v1.0.0 (2012-10-26) =================== - **Backwards incompatible:** Removed :attr:`pykka.VERSION` and :func:`pykka.get_version`, which have been deprecated since v0.14. Use :attr:`pykka.__version__` instead. - **Backwards incompatible:** Removed :meth:`pykka.ActorRef.send_one_way` and :meth:`pykka.ActorRef.send_request_reply`, which have been deprecated since v0.14. Use :meth:`pykka.ActorRef.tell` and :meth:`pykka.ActorRef.ask` instead. - **Backwards incompatible:** Actors no longer subclass :class:`threading.Thread` or :class:`gevent.Greenlet`. Instead they *have* a thread or greenlet that executes the actor's main loop. This is backwards incompatible because you no longer have access to fields/methods of the thread/greenlet that runs the actor through fields/methods on the actor itself. This was never advertised in Pykka's docs or examples, but the fields/methods have always been available. As a positive side effect, this fixes an issue on Python 3.x, that was introduced in Pykka 0.16, where :class:`pykka.ThreadingActor` would accidentally override the method :meth:`threading.Thread._stop`. - **Backwards incompatible:** Actors that override :meth:`__init__() ` *must* call the method they override. If not, the actor will no longer be properly initialized. Valid ways to call the overridden :meth:`__init__` method include:: super(MyActorSubclass, self).__init__() # or pykka.ThreadingActor.__init__() # or pykka.gevent.GeventActor.__init__() - Make :meth:`pykka.Actor.__init__` accept any arguments and keyword arguments by default. This allows you to use :func:`super` in :meth:`__init__` like this:: super(MyActorSubclass, self).__init__(1, 2, 3, foo='bar') Without this fix, the above use of :func:`super` would cause an exception because the default implementation of :meth:`__init__` in :class:`pykka.Actor` would not accept the arguments. - Allow all public classes and functions to be imported directly from the :mod:`pykka` module. E.g. ``from pykka.actor import ThreadingActor`` can now be written as ``from pykka import ThreadingActor``. The exception is :mod:`pykka.gevent`, which still needs to be imported from its own package due to its additional dependency on gevent. v0.16 (2012-09-19) ================== - Let actors access themselves through a proxy. See the :class:`pykka.ActorProxy` documentation for use cases and usage examples. (Fixes: :issue:`9`) - Give proxies direct access to the actor instances for inspecting available attributes. This access is only used for reading, and works since both threading and gevent based actors share memory with other actors. This reduces the creation cost for proxies, which is mostly visible in test suites that are starting and stopping lots of actors. For the Mopidy test suite the run time was reduced by about 33%. This change also makes self-proxying possible. - Fix bug where :meth:`pykka.Actor.stop` called by an actor on itself did not process the remaining messages in the inbox before the actor stopped. The behavior now matches the documentation. v0.15 (2012-08-11) ================== - Change the argument of :meth:`pykka.Future.set_exception` from an exception instance to a ``exc_info`` three-tuple. Passing just an exception instance to the method still works, but it is deprecated and may be unsupported in a future release. - Due to the above change, :meth:`pykka.Future.get` will now reraise exceptions with complete traceback from the point when the exception was first raised, and not just a traceback from when it was reraised by :meth:`get`. (Fixes: :issue:`10`) v0.14 (2012-04-22) ================== - Add :attr:`pykka.__version__` to conform with :pep:`396`. This deprecates :attr:`pykka.VERSION` and :meth:`pykka.get_version`. - Add :meth:`pykka.ActorRef.tell` method in favor of now deprecated :meth:`pykka.ActorRef.send_one_way`. - Add :meth:`pykka.ActorRef.ask` method in favor of now deprecated :meth:`pykka.ActorRef.send_request_reply`. - :class:`ThreadingFuture.set() ` no longer makes a copy of the object set on the future. The setter is urged to either only pass immutable objects through futures or copy the object himself before setting it on the future. This is a less safe default, but it removes unecessary overhead in speed and memory usage for users of immutable data structures. For example, the Mopidy test suite of about 1000 tests, many which are using Pykka, is still passing after this change, but the test suite runs approximately 20% faster. v0.13 (2011-09-24) ================== - 10x speedup of traversible attribute access by reusing proxies. - 1.1x speedup of callable attribute access by reusing proxies. v0.12.4 (2011-07-30) ==================== - Change and document order in which :meth:`pykka.ActorRegistry.stop_all` stops actors. The new order is the reverse of the order the actors were started in. This should make ``stop_all`` work for programs with simple dependency graphs in between the actors. For applications with more complex dependency graphs, the developer still needs to pay attention to the shutdown sequence. (Fixes: :issue:`8`) v0.12.3 (2011-06-25) ==================== - If an actor that was stopped from :meth:`pykka.Actor.on_start`, it would unregister properly, but start the receive loop and forever block on receiving incoming messages that would never arrive. This left the thread alive and isolated, ultimately blocking clean shutdown of the program. The fix ensures that the receive loop is never executed if the actor is stopped before the receive loop is started. - Set the thread name of any :class:`pykka.ThreadingActor` to ``PykkaActorThread-N`` instead of the default ``Thread-N``. This eases debugging by clearly labeling actor threads in e.g. the output of :func:`threading.enumerate`. - Add utility method :meth:`pykka.ActorRegistry.broadcast` which broadcasts a message to all registered actors or to a given class of registred actors. (Fixes: :issue:`7`) - Allow multiple calls to :meth:`pykka.ActorRegistry.unregister` with the same :class:`pykka.actor.ActorRef` as argument without throwing a :exc:`ValueError`. (Fixes: :issue:`5`) - Make the :class:`pykka.ActorProxy`'s reference to its :class:`pykka.ActorRef` public as :attr:`pykka.ActorProxy.actor_ref`. The ``ActorRef`` instance was already exposed as a public field by the actor itself using the same name, but making it public directly on the proxy makes it possible to do e.g. ``proxy.actor_ref.is_alive()`` without waiting for a potentially dead actor to return an ``ActorRef`` instance you can use. (Fixes: :issue:`3`) v0.12.2 (2011-05-05) ==================== - Actors are now registered in :class:`pykka.registry.ActorRegistry` before they are started. This fixes a race condition where an actor tried to stop and unregister itself before it was registered, causing an exception in :meth:`ActorRegistry.unregister`. v0.12.1 (2011-04-25) ==================== - Stop all running actors on :exc:`BaseException` instead of just :exc:`KeyboardInterrupt`, so that ``sys.exit(1)`` will work. v0.12 (2011-03-30) ================== - First stable release, as Pykka now is used by the `Mopidy `_ project. From now on, a changelog will be maintained and we will strive for backwards compatability. python-pykka-1.2.1/docs/conf.py000066400000000000000000000050521255323131500164020ustar00rootroot00000000000000# encoding: utf-8 """Pykka documentation build configuration file""" from __future__ import unicode_literals import os import re import sys # -- Workarounds to have autodoc generate API docs ---------------------------- sys.path.insert(0, os.path.abspath('..')) class Mock(object): def __init__(self, *args, **kwargs): pass def __call__(self, *args, **kwargs): return Mock() @classmethod def __getattr__(self, name): if name in ('__file__', '__path__'): return '/dev/null' elif name[0] == name[0].upper(): return type(name, (), {}) else: return Mock() MOCK_MODULES = [ 'gevent', 'gevent.event', 'gevent.queue', 'eventlet', 'eventlet.event', 'eventlet.queue', ] for mod_name in MOCK_MODULES: sys.modules[mod_name] = Mock() # -- General configuration ---------------------------------------------------- needs_sphinx = '1.0' extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.extlinks', 'sphinx.ext.intersphinx', 'sphinx.ext.viewcode', ] templates_path = ['_templates'] source_suffix = '.rst' master_doc = 'index' project = u'Pykka' copyright = u'2010-2015, Stein Magnus Jodal' def get_version(): init_py = open('../pykka/__init__.py').read() metadata = dict(re.findall("__([a-z]+)__ = '([^']+)'", init_py)) return metadata['version'] release = get_version() version = '.'.join(release.split('.')[:2]) exclude_patterns = ['_build'] pygments_style = 'sphinx' modindex_common_prefix = ['pykka.'] # -- Options for HTML output -------------------------------------------------- html_theme = 'default' html_static_path = ['_static'] html_use_modindex = True html_use_index = True html_split_index = False html_show_sourcelink = True htmlhelp_basename = 'Pykka' # -- Options for LaTeX output ------------------------------------------------- latex_documents = [ ( 'index', 'Pykka.tex', 'Pykka Documentation', 'Stein Magnus Jodal', 'manual', ), ] # -- Options for manual page output ------------------------------------------- man_pages = [] # -- Options for autodoc extension -------------------------------------------- autodoc_member_order = 'bysource' # -- Options for extlink extension -------------------------------------------- extlinks = { 'issue': ('https://github.com/jodal/pykka/issues/%s', '#'), } # -- Options for intersphinx extension ---------------------------------------- intersphinx_mapping = { 'python': ('http://docs.python.org/2', None), } python-pykka-1.2.1/docs/index.rst000066400000000000000000000001441255323131500167410ustar00rootroot00000000000000.. include:: ../README.rst Table of contents ================= .. toctree:: api changes python-pykka-1.2.1/examples/000077500000000000000000000000001255323131500157675ustar00rootroot00000000000000python-pykka-1.2.1/examples/counter.py000077500000000000000000000012351255323131500200240ustar00rootroot00000000000000#! /usr/bin/env python import pykka class Adder(pykka.ThreadingActor): def add_one(self, i): print('{} is increasing {}'.format(self, i)) return i + 1 class Bookkeeper(pykka.ThreadingActor): def __init__(self, adder): super(Bookkeeper, self).__init__() self.adder = adder def count_to(self, target): i = 0 while i < target: i = self.adder.add_one(i).get() print('{} got {} back'.format(self, i)) if __name__ == '__main__': adder = Adder.start().proxy() bookkeeper = Bookkeeper.start(adder).proxy() bookkeeper.count_to(10).get() pykka.ActorRegistry.stop_all() python-pykka-1.2.1/examples/deadlock_debugging.py000077500000000000000000000024051255323131500221260ustar00rootroot00000000000000#! /usr/bin/env python import logging import os import signal import time import pykka import pykka.debug class DeadlockActorA(pykka.ThreadingActor): def foo(self, b): logging.debug('This is foo calling bar') return b.bar().get() class DeadlockActorB(pykka.ThreadingActor): def __init__(self, a): super(DeadlockActorB, self).__init__() self.a = a def bar(self): logging.debug('This is bar calling foo; BOOM!') return self.a.foo().get() if __name__ == '__main__': print('Setting up logging to get output from signal handler...') logging.basicConfig(level=logging.DEBUG) print('Registering signal handler...') signal.signal(signal.SIGUSR1, pykka.debug.log_thread_tracebacks) print('Starting actors...') a = DeadlockActorA.start().proxy() b = DeadlockActorB.start(a).proxy() print('Now doing something stupid that will deadlock the actors...') a.foo(b) time.sleep(0.01) # Yield to actors, so we get output in a readable order print('Making main thread relax; not block, not quit') print('1) Use `kill -SIGUSR1 %d` to log thread tracebacks' % os.getpid()) print('2) Then `kill %d` to terminate the process' % os.getpid()) while True: time.sleep(1) python-pykka-1.2.1/examples/plain_actor.py000077500000000000000000000011161255323131500206360ustar00rootroot00000000000000#! /usr/bin/env python import pykka class PlainActor(pykka.ThreadingActor): def __init__(self): super(PlainActor, self).__init__() self.stored_messages = [] def on_receive(self, message): if message.get('command') == 'get_messages': return self.stored_messages else: self.stored_messages.append(message) if __name__ == '__main__': actor = PlainActor.start() actor.tell({'no': 'Norway', 'se': 'Sweden'}) actor.tell({'a': 3, 'b': 4, 'c': 5}) print(actor.ask({'command': 'get_messages'})) actor.stop() python-pykka-1.2.1/examples/resolver.py000077500000000000000000000024511255323131500202070ustar00rootroot00000000000000#! /usr/bin/env python """ Resolve a bunch of IP addresses using a pool of resolver actors. Based on example contributed by Kristian Klette . Either run without arguments: ./resolver.py Or specify pool size and IPs to resolve: ./resolver.py 3 129.240.2.{1,2,3,4,5,6,7,8,9} """ import pprint import socket import sys import pykka class Resolver(pykka.ThreadingActor): def resolve(self, ip): try: info = socket.gethostbyaddr(ip) print('Finished resolving {}'.format(ip)) return info[0] except: print('Failed resolving {}'.format(ip)) return None def run(pool_size, *ips): # Start resolvers resolvers = [Resolver.start().proxy() for _ in range(pool_size)] # Distribute work by mapping IPs to resolvers (not blocking) hosts = [] for i, ip in enumerate(ips): hosts.append(resolvers[i % len(resolvers)].resolve(ip)) # Gather results (blocking) ip_to_host = zip(ips, pykka.get_all(hosts)) pprint.pprint(list(ip_to_host)) # Clean up pykka.ActorRegistry.stop_all() if __name__ == '__main__': if len(sys.argv[1:]) >= 2: run(int(sys.argv[1]), *sys.argv[2:]) else: ips = ['129.241.93.%s' % i for i in range(1, 50)] run(10, *ips) python-pykka-1.2.1/examples/typed_actor.py000077500000000000000000000026051255323131500206640ustar00rootroot00000000000000#! /usr/bin/env python import threading import time import pykka class AnActor(pykka.ThreadingActor): field = 'this is the value of AnActor.field' def proc(self): log('this was printed by AnActor.proc()') def func(self): time.sleep(0.5) # Block a bit to make it realistic return 'this was returned by AnActor.func() after a delay' def log(s): print('{}: {}'.format(threading.current_thread().name, s)) if __name__ == '__main__': actor = AnActor.start().proxy() for i in range(3): # Method with side effect log('calling AnActor.proc() ...') actor.proc() # Method with return value log('calling AnActor.func() ...') result = actor.func() # Does not block, returns a future log('printing result ... (blocking)') log(result.get()) # Blocks until ready # Field reading log('reading AnActor.field ...') result = actor.field # Does not block, returns a future log('printing result ... (blocking)') log(result.get()) # Blocks until ready # Field writing log('writing AnActor.field ...') actor.field = 'new value' # Assignment does not block result = actor.field # Does not block, returns a future log('printing new field value ... (blocking)') log(result.get()) # Blocks until ready actor.stop() python-pykka-1.2.1/pykka/000077500000000000000000000000001255323131500152705ustar00rootroot00000000000000python-pykka-1.2.1/pykka/__init__.py000066400000000000000000000016121255323131500174010ustar00rootroot00000000000000from pykka.actor import Actor, ActorRef from pykka.exceptions import ActorDeadError, Timeout from pykka.future import Future, get_all from pykka.proxy import ActorProxy from pykka.registry import ActorRegistry from pykka.threading import ThreadingActor, ThreadingFuture __all__ = [ 'Actor', 'ActorDeadError', 'ActorProxy', 'ActorRef', 'ActorRegistry', 'Future', 'ThreadingActor', 'ThreadingFuture', 'Timeout', 'get_all', ] #: Pykka's :pep:`396` and :pep:`440` compatible version number __version__ = '1.2.1' def _add_null_handler_for_logging(): import logging try: NullHandler = logging.NullHandler # Python 2.7 and upwards except AttributeError: class NullHandler(logging.Handler): def emit(self, record): pass logging.getLogger('pykka').addHandler(NullHandler()) _add_null_handler_for_logging() python-pykka-1.2.1/pykka/actor.py000066400000000000000000000402671255323131500167630ustar00rootroot00000000000000from __future__ import absolute_import import logging import sys import threading import uuid from pykka.exceptions import ActorDeadError from pykka.proxy import ActorProxy from pykka.registry import ActorRegistry __all__ = [ 'Actor', 'ActorRef', ] logger = logging.getLogger('pykka') class Actor(object): """ To create an actor: 1. subclass one of the :class:`Actor` implementations, e.g. :class:`GeventActor ` or :class:`ThreadingActor`, 2. implement your methods, including :meth:`__init__`, as usual, 3. call :meth:`Actor.start` on your actor class, passing the method any arguments for your constructor. To stop an actor, call :meth:`Actor.stop()` or :meth:`ActorRef.stop()`. For example:: import pykka class MyActor(pykka.ThreadingActor): def __init__(self, my_arg=None): super(MyActor, self).__init__() ... # My optional init code with access to start() arguments def on_start(self): ... # My optional setup code in same context as on_receive() def on_stop(self): ... # My optional cleanup code in same context as on_receive() def on_failure(self, exception_type, exception_value, traceback): ... # My optional cleanup code in same context as on_receive() def on_receive(self, message): ... # My optional message handling code for a plain actor def a_method(self, ...): ... # My regular method to be used through an ActorProxy my_actor_ref = MyActor.start(my_arg=...) my_actor_ref.stop() """ @classmethod def start(cls, *args, **kwargs): """ Start an actor and register it in the :class:`ActorRegistry `. Any arguments passed to :meth:`start` will be passed on to the class constructor. Behind the scenes, the following is happening when you call :meth:`start`: 1. The actor is created: 1. :attr:`actor_urn` is initialized with the assigned URN. 2. :attr:`actor_inbox` is initialized with a new actor inbox. 3. :attr:`actor_ref` is initialized with a :class:`pykka.ActorRef` object for safely communicating with the actor. 4. At this point, your :meth:`__init__()` code can run. 2. The actor is registered in :class:`pykka.ActorRegistry`. 3. The actor receive loop is started by the actor's associated thread/greenlet. :returns: a :class:`ActorRef` which can be used to access the actor in a safe manner """ obj = cls(*args, **kwargs) assert obj.actor_ref is not None, ( 'Actor.__init__() have not been called. ' 'Did you forget to call super() in your override?') ActorRegistry.register(obj.actor_ref) logger.debug('Starting %s', obj) obj._start_actor_loop() return obj.actor_ref @staticmethod def _create_actor_inbox(): """Internal method for implementors of new actor types.""" raise NotImplementedError('Use a subclass of Actor') @staticmethod def _create_future(): """Internal method for implementors of new actor types.""" raise NotImplementedError('Use a subclass of Actor') def _start_actor_loop(self): """Internal method for implementors of new actor types.""" raise NotImplementedError('Use a subclass of Actor') #: The actor URN string is a universally unique identifier for the actor. #: It may be used for looking up a specific actor using #: :meth:`ActorRegistry.get_by_urn #: `. actor_urn = None #: The actor's inbox. Use :meth:`ActorRef.tell`, :meth:`ActorRef.ask`, and #: friends to put messages in the inbox. actor_inbox = None #: The actor's :class:`ActorRef` instance. actor_ref = None #: A :class:`threading.Event` representing whether or not the actor should #: continue processing messages. Use :meth:`stop` to change it. actor_stopped = None def __init__(self, *args, **kwargs): """ Your are free to override :meth:`__init__`, but you must call your superclass' :meth:`__init__` to ensure that fields :attr:`actor_urn`, :attr:`actor_inbox`, and :attr:`actor_ref` are initialized. You can use :func:`super`:: super(MyActor, self).__init__() Or call you superclass directly:: pykka.ThreadingActor.__init__(self) # or pykka.gevent.GeventActor.__init__(self) :meth:`__init__` is called before the actor is started and registered in :class:`ActorRegistry `. """ self.actor_urn = uuid.uuid4().urn self.actor_inbox = self._create_actor_inbox() self.actor_stopped = threading.Event() self.actor_ref = ActorRef(self) def __str__(self): return '%(class)s (%(urn)s)' % { 'class': self.__class__.__name__, 'urn': self.actor_urn, } def stop(self): """ Stop the actor. It's equivalent to calling :meth:`ActorRef.stop` with ``block=False``. """ self.actor_ref.tell({'command': 'pykka_stop'}) def _stop(self): """ Stops the actor immediately without processing the rest of the inbox. """ ActorRegistry.unregister(self.actor_ref) self.actor_stopped.set() logger.debug('Stopped %s', self) try: self.on_stop() except Exception: self._handle_failure(*sys.exc_info()) def _actor_loop(self): """ The actor's event loop. This is the method that will be executed by the thread or greenlet. """ try: self.on_start() except Exception: self._handle_failure(*sys.exc_info()) while not self.actor_stopped.is_set(): message = self.actor_inbox.get() reply_to = None try: reply_to = message.pop('pykka_reply_to', None) response = self._handle_receive(message) if reply_to: reply_to.set(response) except Exception: if reply_to: logger.debug( 'Exception returned from %s to caller:' % self, exc_info=sys.exc_info()) reply_to.set_exception() else: self._handle_failure(*sys.exc_info()) try: self.on_failure(*sys.exc_info()) except Exception: self._handle_failure(*sys.exc_info()) except BaseException: exception_value = sys.exc_info()[1] logger.debug( '%s in %s. Stopping all actors.' % (repr(exception_value), self)) self._stop() ActorRegistry.stop_all() while not self.actor_inbox.empty(): msg = self.actor_inbox.get() reply_to = msg.pop('pykka_reply_to', None) if reply_to: if msg.get('command') == 'pykka_stop': reply_to.set(None) else: reply_to.set_exception(ActorDeadError( '%s stopped before handling the message' % self.actor_ref)) def on_start(self): """ Hook for doing any setup that should be done *after* the actor is started, but *before* it starts processing messages. For :class:`ThreadingActor`, this method is executed in the actor's own thread, while :meth:`__init__` is executed in the thread that created the actor. If an exception is raised by this method the stack trace will be logged, and the actor will stop. """ pass def on_stop(self): """ Hook for doing any cleanup that should be done *after* the actor has processed the last message, and *before* the actor stops. This hook is *not* called when the actor stops because of an unhandled exception. In that case, the :meth:`on_failure` hook is called instead. For :class:`ThreadingActor` this method is executed in the actor's own thread, immediately before the thread exits. If an exception is raised by this method the stack trace will be logged, and the actor will stop. """ pass def _handle_failure(self, exception_type, exception_value, traceback): """Logs unexpected failures, unregisters and stops the actor.""" logger.error( 'Unhandled exception in %s:' % self, exc_info=(exception_type, exception_value, traceback)) ActorRegistry.unregister(self.actor_ref) self.actor_stopped.set() def on_failure(self, exception_type, exception_value, traceback): """ Hook for doing any cleanup *after* an unhandled exception is raised, and *before* the actor stops. For :class:`ThreadingActor` this method is executed in the actor's own thread, immediately before the thread exits. The method's arguments are the relevant information from :func:`sys.exc_info`. If an exception is raised by this method the stack trace will be logged, and the actor will stop. """ pass def _handle_receive(self, message): """Handles messages sent to the actor.""" if message.get('command') == 'pykka_stop': return self._stop() if message.get('command') == 'pykka_call': callee = self._get_attribute_from_path(message['attr_path']) return callee(*message['args'], **message['kwargs']) if message.get('command') == 'pykka_getattr': attr = self._get_attribute_from_path(message['attr_path']) return attr if message.get('command') == 'pykka_setattr': parent_attr = self._get_attribute_from_path( message['attr_path'][:-1]) attr_name = message['attr_path'][-1] return setattr(parent_attr, attr_name, message['value']) return self.on_receive(message) def on_receive(self, message): """ May be implemented for the actor to handle regular non-proxy messages. Messages where the value of the "command" key matches "pykka_*" are reserved for internal use in Pykka. :param message: the message to handle :type message: picklable dict :returns: anything that should be sent as a reply to the sender """ logger.warning('Unexpected message received by %s: %s', self, message) def _get_attribute_from_path(self, attr_path): """ Traverses the path and returns the attribute at the end of the path. """ attr = self for attr_name in attr_path: attr = getattr(attr, attr_name) return attr class ActorRef(object): """ Reference to a running actor which may safely be passed around. :class:`ActorRef` instances are returned by :meth:`Actor.start` and the lookup methods in :class:`ActorRegistry `. You should never need to create :class:`ActorRef` instances yourself. :param actor: the actor to wrap :type actor: :class:`Actor` """ #: The class of the referenced actor. actor_class = None #: See :attr:`Actor.actor_urn`. actor_urn = None #: See :attr:`Actor.actor_inbox`. actor_inbox = None #: See :attr:`Actor.actor_stopped`. actor_stopped = None def __init__(self, actor): self._actor = actor self.actor_class = actor.__class__ self.actor_urn = actor.actor_urn self.actor_inbox = actor.actor_inbox self.actor_stopped = actor.actor_stopped def __repr__(self): return '' % str(self) def __str__(self): return '%(class)s (%(urn)s)' % { 'urn': self.actor_urn, 'class': self.actor_class.__name__, } def is_alive(self): """ Check if actor is alive. This is based on the actor's stopped flag. The actor is not guaranteed to be alive and responding even though :meth:`is_alive` returns :class:`True`. :return: Returns :class:`True` if actor is alive, :class:`False` otherwise. """ return not self.actor_stopped.is_set() def tell(self, message): """ Send message to actor without waiting for any response. Will generally not block, but if the underlying queue is full it will block until a free slot is available. :param message: message to send :type message: picklable dict :raise: :exc:`pykka.ActorDeadError` if actor is not available :return: nothing """ if not self.is_alive(): raise ActorDeadError('%s not found' % self) self.actor_inbox.put(message) def ask(self, message, block=True, timeout=None): """ Send message to actor and wait for the reply. The message must be a picklable dict. If ``block`` is :class:`False`, it will immediately return a :class:`Future ` instead of blocking. If ``block`` is :class:`True`, and ``timeout`` is :class:`None`, as default, the method will block until it gets a reply, potentially forever. If ``timeout`` is an integer or float, the method will wait for a reply for ``timeout`` seconds, and then raise :exc:`pykka.Timeout`. :param message: message to send :type message: picklable dict :param block: whether to block while waiting for a reply :type block: boolean :param timeout: seconds to wait before timeout if blocking :type timeout: float or :class:`None` :raise: :exc:`pykka.Timeout` if timeout is reached if blocking :raise: any exception returned by the receiving actor if blocking :return: :class:`pykka.Future`, or response if blocking """ future = self.actor_class._create_future() message['pykka_reply_to'] = future try: self.tell(message) except ActorDeadError: future.set_exception() if block: return future.get(timeout=timeout) else: return future def stop(self, block=True, timeout=None): """ Send a message to the actor, asking it to stop. Returns :class:`True` if actor is stopped or was being stopped at the time of the call. :class:`False` if actor was already dead. If ``block`` is :class:`False`, it returns a future wrapping the result. Messages sent to the actor before the actor is asked to stop will be processed normally before it stops. Messages sent to the actor after the actor is asked to stop will be replied to with :exc:`pykka.ActorDeadError` after it stops. The actor may not be restarted. ``block`` and ``timeout`` works as for :meth:`ask`. :return: :class:`pykka.Future`, or a boolean result if blocking """ ask_future = self.ask({'command': 'pykka_stop'}, block=False) def _stop_result_converter(timeout): try: ask_future.get(timeout=timeout) return True except ActorDeadError: return False converted_future = ask_future.__class__() converted_future.set_get_hook(_stop_result_converter) if block: return converted_future.get(timeout=timeout) else: return converted_future def proxy(self): """ Wraps the :class:`ActorRef` in an :class:`ActorProxy `. Using this method like this:: proxy = AnActor.start().proxy() is analogous to:: proxy = ActorProxy(AnActor.start()) :raise: :exc:`pykka.ActorDeadError` if actor is not available :return: :class:`pykka.ActorProxy` """ return ActorProxy(self) python-pykka-1.2.1/pykka/compat.py000066400000000000000000000007431255323131500171310ustar00rootroot00000000000000import sys PY2 = sys.version_info[0] == 2 PY3 = sys.version_info[0] == 3 if PY2: import Queue as queue # noqa string_types = basestring # noqa def reraise(tp, value, tb=None): exec('raise tp, value, tb') else: import queue # noqa string_types = (str,) def reraise(tp, value, tb=None): if value is None: value = tp() if value.__traceback__ is not tb: raise value.with_traceback(tb) raise value python-pykka-1.2.1/pykka/debug.py000066400000000000000000000044601255323131500167340ustar00rootroot00000000000000from __future__ import absolute_import import logging import sys import threading import traceback logger = logging.getLogger('pykka') __all__ = [ 'log_thread_tracebacks', ] def log_thread_tracebacks(*args, **kwargs): """Logs at ``CRITICAL`` level a traceback for each running thread. This can be a convenient tool for debugging deadlocks. The function accepts any arguments so that it can easily be used as e.g. a signal handler, but it does not use the arguments for anything. To use this function as a signal handler, setup logging with a :attr:`logging.CRITICAL` threshold or lower and make your main thread register this with the :mod:`signal` module:: import logging import signal import pykka.debug logging.basicConfig(level=logging.DEBUG) signal.signal(signal.SIGUSR1, pykka.debug.log_thread_tracebacks) If your application deadlocks, send the `SIGUSR1` signal to the process:: kill -SIGUSR1 Signal handler caveats: - The function *must* be registered as a signal handler by your main thread. If not, :func:`signal.signal` will raise a :exc:`ValueError`. - All signals in Python are handled by the main thread. Thus, the signal will only be handled, and the tracebacks logged, if your main thread is available to do some work. Making your main thread idle using :func:`time.sleep` is OK. The signal will awaken your main thread. Blocking your main thread on e.g. :func:`Queue.Queue.get` or :meth:`pykka.Future.get` will break signal handling, and thus you won't be able to signal your process to print the thread tracebacks. The morale is: setup signals using your main thread, start your actors, then let your main thread relax for the rest of your application's life cycle. For a complete example of how to use this, see ``examples/deadlock_debugging.py`` in Pykka's source code. .. versionadded:: 1.1 """ thread_names = dict((t.ident, t.name) for t in threading.enumerate()) for ident, frame in sys._current_frames().items(): name = thread_names.get(ident, '?') stack = ''.join(traceback.format_stack(frame)) logger.critical( 'Current state of %s (ident: %s):\n%s', name, ident, stack) python-pykka-1.2.1/pykka/eventlet.py000066400000000000000000000051511255323131500174720ustar00rootroot00000000000000from __future__ import absolute_import import sys import eventlet import eventlet.event import eventlet.queue from pykka import Timeout from pykka.actor import Actor from pykka.future import Future __all__ = [ 'EventletActor', 'EventletEvent', 'EventletFuture', ] class EventletEvent(eventlet.event.Event): """ :class:`EventletEvent` adapts :class:`eventlet.event.Event` to :class:`threading.Event` interface. """ def set(self): if self.ready(): self.reset() self.send() def is_set(self): return self.ready() isSet = is_set def clear(self): if self.ready(): self.reset() def wait(self, timeout): if timeout is not None: wait_timeout = eventlet.Timeout(timeout) try: with wait_timeout: super(EventletEvent, self).wait() except eventlet.Timeout as t: if t is not wait_timeout: raise return False else: self.event.wait() return True class EventletFuture(Future): """ :class:`EventletFuture` implements :class:`pykka.Future` for use with :class:`EventletActor`. """ event = None def __init__(self): super(EventletFuture, self).__init__() self.event = eventlet.event.Event() def get(self, timeout=None): try: return super(EventletFuture, self).get(timeout=timeout) except NotImplementedError: pass if timeout is not None: wait_timeout = eventlet.Timeout(timeout) try: with wait_timeout: return self.event.wait() except eventlet.Timeout as t: if t is not wait_timeout: raise raise Timeout(t) else: return self.event.wait() def set(self, value=None): self.event.send(value) def set_exception(self, exc_info=None): if isinstance(exc_info, BaseException): exc_info = (exc_info,) self.event.send_exception(*(exc_info or sys.exc_info())) class EventletActor(Actor): """ :class:`EventletActor` implements :class:`pykka.Actor` using the `eventlet `_ library. This implementation uses eventlet green threads. """ @staticmethod def _create_actor_inbox(): return eventlet.queue.Queue() @staticmethod def _create_future(): return EventletFuture() def _start_actor_loop(self): eventlet.greenthread.spawn(self._actor_loop) python-pykka-1.2.1/pykka/exceptions.py000066400000000000000000000003771255323131500200320ustar00rootroot00000000000000__all__ = [ 'ActorDeadError', 'Timeout', ] class ActorDeadError(Exception): """Exception raised when trying to use a dead or unavailable actor.""" pass class Timeout(Exception): """Exception raised at future timeout.""" pass python-pykka-1.2.1/pykka/future.py000066400000000000000000000207261255323131500171630ustar00rootroot00000000000000import collections import functools from pykka import compat __all__ = [ 'Future', 'get_all', ] def _is_iterable(x): return ( isinstance(x, collections.Iterable) and not isinstance(x, compat.string_types)) def _map(func, *iterables): if len(iterables) == 1 and not _is_iterable(iterables[0]): return func(iterables[0]) else: return list(map(func, *iterables)) class Future(object): """ A :class:`Future` is a handle to a value which are available or will be available in the future. Typically returned by calls to actor methods or accesses to actor fields. To get hold of the encapsulated value, call :meth:`Future.get`. """ def __init__(self): super(Future, self).__init__() self._get_hook = None def get(self, timeout=None): """ Get the value encapsulated by the future. If the encapsulated value is an exception, it is raised instead of returned. If ``timeout`` is :class:`None`, as default, the method will block until it gets a reply, potentially forever. If ``timeout`` is an integer or float, the method will wait for a reply for ``timeout`` seconds, and then raise :exc:`pykka.Timeout`. The encapsulated value can be retrieved multiple times. The future will only block the first time the value is accessed. :param timeout: seconds to wait before timeout :type timeout: float or :class:`None` :raise: :exc:`pykka.Timeout` if timeout is reached :raise: encapsulated value if it is an exception :return: encapsulated value if it is not an exception """ if self._get_hook is not None: return self._get_hook(timeout) raise NotImplementedError def set(self, value=None): """ Set the encapsulated value. :param value: the encapsulated value or nothing :type value: any picklable object or :class:`None` :raise: an exception if set is called multiple times """ raise NotImplementedError def set_exception(self, exc_info=None): """ Set an exception as the encapsulated value. You can pass an ``exc_info`` three-tuple, as returned by :func:`sys.exc_info`. If you don't pass ``exc_info``, :func:`sys.exc_info` will be called and the value returned by it used. In other words, if you're calling :meth:`set_exception`, without any arguments, from an except block, the exception you're currently handling will automatically be set on the future. .. versionchanged:: 0.15 Previously, :meth:`set_exception` accepted an exception instance as its only argument. This still works, but it is deprecated and will be removed in a future release. :param exc_info: the encapsulated exception :type exc_info: three-tuple of (exc_class, exc_instance, traceback) """ raise NotImplementedError def set_get_hook(self, func): """ Set a function to be executed when :meth:`get` is called. The function will be called when :meth:`get` is called, with the ``timeout`` value as the only argument. The function's return value will be returned from :meth:`get`. .. versionadded:: 1.2 :param func: called to produce return value of :meth:`get` :type func: function accepting a timeout value """ self._get_hook = func def filter(self, func): """ Return a new future with only the items passing the predicate function. If the future's value is an iterable, :meth:`filter` will return a new future whose value is another iterable with only the items from the first iterable for which ``func(item)`` is true. If the future's value isn't an iterable, a :exc:`TypeError` will be raised when :meth:`get` is called. Example:: >>> import pykka >>> f = pykka.ThreadingFuture() >>> g = f.filter(lambda x: x > 10) >>> g >>> f.set(range(5, 15)) >>> f.get() [5, 6, 7, 8, 9, 10, 11, 12, 13, 14] >>> g.get() [11, 12, 13, 14] .. versionadded:: 1.2 """ future = self.__class__() future.set_get_hook(lambda timeout: list(filter( func, self.get(timeout)))) return future def join(self, *futures): """ Return a new future with a list of the result of multiple futures. One or more futures can be passed as arguments to :meth:`join`. The new future returns a list with the results from all the joined futures. Example:: >>> import pykka >>> a = pykka.ThreadingFuture() >>> b = pykka.ThreadingFuture() >>> c = pykka.ThreadingFuture() >>> f = a.join(b, c) >>> a.set('def') >>> b.set(123) >>> c.set(False) >>> f.get() ['def', 123, False] .. versionadded:: 1.2 """ future = self.__class__() future.set_get_hook(lambda timeout: [ f.get(timeout) for f in [self] + list(futures)]) return future def map(self, func): """ Return a new future with the result of the future passed through a function. If the future's result is a single value, it is simply passed to the function. If the future's result is an iterable, the function is applied to each item in the iterable. Example:: >>> import pykka >>> f = pykka.ThreadingFuture() >>> g = f.map(lambda x: x + 10) >>> f.set(30) >>> g.get() 40 >>> f = pykka.ThreadingFuture() >>> g = f.map(lambda x: x + 10) >>> f.set([30, 300, 3000]) >>> g.get() [40, 310, 3010] .. versionadded:: 1.2 """ future = self.__class__() future.set_get_hook(lambda timeout: _map(func, self.get(timeout))) return future def reduce(self, func, *args): """ reduce(func[, initial]) Return a new future with the result of reducing the future's iterable into a single value. The function of two arguments is applied cumulatively to the items of the iterable, from left to right. The result of the first function call is used as the first argument to the second function call, and so on, until the end of the iterable. If the future's value isn't an iterable, a :exc:`TypeError` is raised. :meth:`reduce` accepts an optional second argument, which will be used as an initial value in the first function call. If the iterable is empty, the initial value is returned. Example:: >>> import pykka >>> f = pykka.ThreadingFuture() >>> g = f.reduce(lambda x, y: x + y) >>> f.set(['a', 'b', 'c']) >>> g.get() 'abc' >>> f = pykka.ThreadingFuture() >>> g = f.reduce(lambda x, y: x + y) >>> f.set([1, 2, 3]) >>> (1 + 2) + 3 6 >>> g.get() 6 >>> f = pykka.ThreadingFuture() >>> g = f.reduce(lambda x, y: x + y, 5) >>> f.set([1, 2, 3]) >>> ((5 + 1) + 2) + 3 11 >>> g.get() 11 >>> f = pykka.ThreadingFuture() >>> g = f.reduce(lambda x, y: x + y, 5) >>> f.set([]) >>> g.get() 5 .. versionadded:: 1.2 """ future = self.__class__() future.set_get_hook(lambda timeout: functools.reduce( func, self.get(timeout), *args)) return future def get_all(futures, timeout=None): """ Collect all values encapsulated in the list of futures. If ``timeout`` is not :class:`None`, the method will wait for a reply for ``timeout`` seconds, and then raise :exc:`pykka.Timeout`. :param futures: futures for the results to collect :type futures: list of :class:`pykka.Future` :param timeout: seconds to wait before timeout :type timeout: float or :class:`None` :raise: :exc:`pykka.Timeout` if timeout is reached :returns: list of results """ return [future.get(timeout=timeout) for future in futures] python-pykka-1.2.1/pykka/gevent.py000066400000000000000000000043271255323131500171400ustar00rootroot00000000000000from __future__ import absolute_import import sys import gevent import gevent.event import gevent.queue from pykka import Timeout from pykka.actor import Actor from pykka.future import Future __all__ = [ 'GeventActor', 'GeventFuture', ] class GeventFuture(Future): """ :class:`GeventFuture` implements :class:`pykka.Future` for use with :class:`GeventActor`. It encapsulates a :class:`gevent.event.AsyncResult` object which may be used directly, though it will couple your code with gevent. """ #: The encapsulated :class:`gevent.event.AsyncResult` async_result = None def __init__(self, async_result=None): super(GeventFuture, self).__init__() if async_result is not None: self.async_result = async_result else: self.async_result = gevent.event.AsyncResult() def get(self, timeout=None): try: return super(GeventFuture, self).get(timeout=timeout) except NotImplementedError: pass try: return self.async_result.get(timeout=timeout) except gevent.Timeout as e: raise Timeout(e) def set(self, value=None): assert not self.async_result.ready(), 'value has already been set' self.async_result.set(value) def set_exception(self, exc_info=None): if isinstance(exc_info, BaseException): exception = exc_info else: exc_info = exc_info or sys.exc_info() exception = exc_info[1] self.async_result.set_exception(exception) class GeventActor(Actor): """ :class:`GeventActor` implements :class:`pykka.Actor` using the `gevent `_ library. gevent is a coroutine-based Python networking library that uses greenlet to provide a high-level synchronous API on top of libevent event loop. This is a very fast implementation, but as of gevent 0.13.x it does not work in combination with other threads. """ @staticmethod def _create_actor_inbox(): return gevent.queue.Queue() @staticmethod def _create_future(): return GeventFuture() def _start_actor_loop(self): gevent.Greenlet.spawn(self._actor_loop) python-pykka-1.2.1/pykka/proxy.py000066400000000000000000000170661255323131500170350ustar00rootroot00000000000000import collections import sys from pykka.exceptions import ActorDeadError __all__ = [ 'ActorProxy', ] class ActorProxy(object): """ An :class:`ActorProxy` wraps an :class:`ActorRef ` instance. The proxy allows the referenced actor to be used through regular method calls and field access. You can create an :class:`ActorProxy` from any :class:`ActorRef `:: actor_ref = MyActor.start() actor_proxy = ActorProxy(actor_ref) You can also get an :class:`ActorProxy` by using :meth:`proxy() `:: actor_proxy = MyActor.start().proxy() When reading an attribute or getting a return value from a method, you get a :class:`Future ` object back. To get the enclosed value from the future, you must call :meth:`get() ` on the returned future:: print actor_proxy.string_attribute.get() print actor_proxy.count().get() + 1 If you call a method just for it's side effects and do not care about the return value, you do not need to accept the returned future or call :meth:`get() ` on the future. Simply call the method, and it will be executed concurrently with your own code:: actor_proxy.method_with_side_effect() If you want to block your own code from continuing while the other method is processing, you can use :meth:`get() ` to block until it completes:: actor_proxy.method_with_side_effect().get() An actor can use a proxy to itself to schedule work for itself. The scheduled work will only be done after the current message and all messages already in the inbox are processed. For example, if an actor can split a time consuming task into multiple parts, and after completing each part can ask itself to start on the next part using proxied calls or messages to itself, it can react faster to other incoming messages as they will be interleaved with the parts of the time consuming task. This is especially useful for being able to stop the actor in the middle of a time consuming task. To create a proxy to yourself, use the actor's :attr:`actor_ref ` attribute:: proxy_to_myself_in_the_future = self.actor_ref.proxy() If you create a proxy in your actor's constructor or :meth:`on_start ` method, you can create a nice API for deferring work to yourself in the future:: def __init__(self): ... self.in_future = self.actor_ref.proxy() ... def do_work(self): ... self.in_future.do_more_work() ... def do_more_work(self): ... An example of :class:`ActorProxy` usage: .. literalinclude:: ../examples/counter.py :param actor_ref: reference to the actor to proxy :type actor_ref: :class:`pykka.ActorRef` :raise: :exc:`pykka.ActorDeadError` if actor is not available """ #: The actor's :class:`pykka.ActorRef` instance. actor_ref = None def __init__(self, actor_ref, attr_path=None): if not actor_ref.is_alive(): raise ActorDeadError('%s not found' % actor_ref) self.actor_ref = actor_ref self._actor = actor_ref._actor self._attr_path = attr_path or tuple() self._known_attrs = self._get_attributes() self._actor_proxies = {} self._callable_proxies = {} def _get_attributes(self): """Gathers actor attributes needed to proxy the actor""" result = {} attr_paths_to_visit = [[attr_name] for attr_name in dir(self._actor)] while attr_paths_to_visit: attr_path = attr_paths_to_visit.pop(0) if self._is_exposable_attribute(attr_path[-1]): attr = self._actor._get_attribute_from_path(attr_path) result[tuple(attr_path)] = { 'callable': self._is_callable_attribute(attr), 'traversable': self._is_traversable_attribute(attr), } if self._is_traversable_attribute(attr): for attr_name in dir(attr): attr_paths_to_visit.append(attr_path + [attr_name]) return result def _is_exposable_attribute(self, attr_name): """ Returns true for any attribute name that may be exposed through :class:`ActorProxy`. """ return not attr_name.startswith('_') def _is_callable_attribute(self, attr): """Returns true for any attribute that is callable.""" # isinstance(attr, collections.Callable), as recommended by 2to3, does # not work on CPython 2.6.4 if the attribute is an Queue.Queue, but # works on 2.6.6. if sys.version_info < (3,): return callable(attr) else: return isinstance(attr, collections.Callable) def _is_traversable_attribute(self, attr): """ Returns true for any attribute that may be traversed from another actor through a proxy. """ return hasattr(attr, 'pykka_traversable') def __repr__(self): return '' % ( self.actor_ref, self._attr_path) def __dir__(self): result = ['__class__'] result += list(self.__class__.__dict__.keys()) result += list(self.__dict__.keys()) result += [ attr_path[0] for attr_path in list(self._known_attrs.keys())] return sorted(result) def __getattr__(self, name): """Get a field or callable from the actor.""" attr_path = self._attr_path + (name,) if attr_path not in self._known_attrs: self._known_attrs = self._get_attributes() attr_info = self._known_attrs.get(attr_path) if attr_info is None: raise AttributeError('%s has no attribute "%s"' % (self, name)) if attr_info['callable']: if attr_path not in self._callable_proxies: self._callable_proxies[attr_path] = _CallableProxy( self.actor_ref, attr_path) return self._callable_proxies[attr_path] elif attr_info['traversable']: if attr_path not in self._actor_proxies: self._actor_proxies[attr_path] = ActorProxy( self.actor_ref, attr_path) return self._actor_proxies[attr_path] else: message = { 'command': 'pykka_getattr', 'attr_path': attr_path, } return self.actor_ref.ask(message, block=False) def __setattr__(self, name, value): """ Set a field on the actor. Blocks until the field is set to check if any exceptions was raised. """ if name == 'actor_ref' or name.startswith('_'): return super(ActorProxy, self).__setattr__(name, value) attr_path = self._attr_path + (name,) message = { 'command': 'pykka_setattr', 'attr_path': attr_path, 'value': value, } return self.actor_ref.ask(message) class _CallableProxy(object): """Internal helper class for proxying callables.""" def __init__(self, ref, attr_path): self.actor_ref = ref self._attr_path = attr_path def __call__(self, *args, **kwargs): message = { 'command': 'pykka_call', 'attr_path': self._attr_path, 'args': args, 'kwargs': kwargs, } return self.actor_ref.ask(message, block=False) python-pykka-1.2.1/pykka/registry.py000066400000000000000000000122171255323131500175150ustar00rootroot00000000000000from __future__ import absolute_import import logging import threading from pykka import compat logger = logging.getLogger('pykka') __all__ = [ 'ActorRegistry', ] class ActorRegistry(object): """ Registry which provides easy access to all running actors. Contains global state, but should be thread-safe. """ _actor_refs = [] _actor_refs_lock = threading.RLock() @classmethod def broadcast(cls, message, target_class=None): """ Broadcast ``message`` to all actors of the specified ``target_class``. If no ``target_class`` is specified, the message is broadcasted to all actors. :param message: the message to send :type message: picklable dict :param target_class: optional actor class to broadcast the message to :type target_class: class or class name """ if isinstance(target_class, compat.string_types): targets = cls.get_by_class_name(target_class) elif target_class is not None: targets = cls.get_by_class(target_class) else: targets = cls.get_all() for ref in targets: ref.tell(message) @classmethod def get_all(cls): """ Get :class:`ActorRef ` for all running actors. :returns: list of :class:`pykka.ActorRef` """ with cls._actor_refs_lock: return cls._actor_refs[:] @classmethod def get_by_class(cls, actor_class): """ Get :class:`ActorRef` for all running actors of the given class, or of any subclass of the given class. :param actor_class: actor class, or any superclass of the actor :type actor_class: class :returns: list of :class:`pykka.ActorRef` """ with cls._actor_refs_lock: return [ ref for ref in cls._actor_refs if issubclass(ref.actor_class, actor_class)] @classmethod def get_by_class_name(cls, actor_class_name): """ Get :class:`ActorRef` for all running actors of the given class name. :param actor_class_name: actor class name :type actor_class_name: string :returns: list of :class:`pykka.ActorRef` """ with cls._actor_refs_lock: return [ ref for ref in cls._actor_refs if ref.actor_class.__name__ == actor_class_name] @classmethod def get_by_urn(cls, actor_urn): """ Get an actor by its universally unique URN. :param actor_urn: actor URN :type actor_urn: string :returns: :class:`pykka.ActorRef` or :class:`None` if not found """ with cls._actor_refs_lock: refs = [ ref for ref in cls._actor_refs if ref.actor_urn == actor_urn] if refs: return refs[0] @classmethod def register(cls, actor_ref): """ Register an :class:`ActorRef` in the registry. This is done automatically when an actor is started, e.g. by calling :meth:`Actor.start() `. :param actor_ref: reference to the actor to register :type actor_ref: :class:`pykka.ActorRef` """ with cls._actor_refs_lock: cls._actor_refs.append(actor_ref) logger.debug('Registered %s', actor_ref) @classmethod def stop_all(cls, block=True, timeout=None): """ Stop all running actors. ``block`` and ``timeout`` works as for :meth:`ActorRef.stop() `. If ``block`` is :class:`True`, the actors are guaranteed to be stopped in the reverse of the order they were started in. This is helpful if you have simple dependencies in between your actors, where it is sufficient to shut down actors in a LIFO manner: last started, first stopped. If you have more complex dependencies in between your actors, you should take care to shut them down in the required order yourself, e.g. by stopping dependees from a dependency's :meth:`on_stop() ` method. :returns: If not blocking, a list with a future for each stop action. If blocking, a list of return values from :meth:`pykka.ActorRef.stop`. """ return [ref.stop(block, timeout) for ref in reversed(cls.get_all())] @classmethod def unregister(cls, actor_ref): """ Remove an :class:`ActorRef ` from the registry. This is done automatically when an actor is stopped, e.g. by calling :meth:`Actor.stop() `. :param actor_ref: reference to the actor to unregister :type actor_ref: :class:`pykka.ActorRef` """ removed = False with cls._actor_refs_lock: if actor_ref in cls._actor_refs: cls._actor_refs.remove(actor_ref) removed = True if removed: logger.debug('Unregistered %s', actor_ref) else: logger.debug( 'Unregistered %s (not found in registry)', actor_ref) python-pykka-1.2.1/pykka/threading.py000066400000000000000000000064631255323131500176200ustar00rootroot00000000000000from __future__ import absolute_import import sys import threading from pykka import compat from pykka.actor import Actor from pykka.exceptions import Timeout from pykka.future import Future __all__ = [ 'ThreadingActor', 'ThreadingFuture', ] class ThreadingFuture(Future): """ :class:`ThreadingFuture` implements :class:`Future` for use with :class:`ThreadingActor `. The future is implemented using a :class:`Queue.Queue`. The future does *not* make a copy of the object which is :meth:`set() ` on it. It is the setters responsibility to only pass immutable objects or make a copy of the object before setting it on the future. .. versionchanged:: 0.14 Previously, the encapsulated value was a copy made with :func:`copy.deepcopy`, unless the encapsulated value was a future, in which case the original future was encapsulated. """ def __init__(self): super(ThreadingFuture, self).__init__() self._queue = compat.queue.Queue(maxsize=1) self._data = None def get(self, timeout=None): try: return super(ThreadingFuture, self).get(timeout=timeout) except NotImplementedError: pass try: if self._data is None: self._data = self._queue.get(True, timeout) if 'exc_info' in self._data: compat.reraise(*self._data['exc_info']) else: return self._data['value'] except compat.queue.Empty: raise Timeout('%s seconds' % timeout) def set(self, value=None): self._queue.put({'value': value}, block=False) def set_exception(self, exc_info=None): if isinstance(exc_info, BaseException): exc_info = (exc_info.__class__, exc_info, None) self._queue.put({'exc_info': exc_info or sys.exc_info()}) class ThreadingActor(Actor): """ :class:`ThreadingActor` implements :class:`Actor` using regular Python threads. This implementation is slower than :class:`GeventActor `, but can be used in a process with other threads that are not Pykka actors. """ use_daemon_thread = False """ A boolean value indicating whether this actor is executed on a thread that is a daemon thread (:class:`True`) or not (:class:`False`). This must be set before :meth:`pykka.Actor.start` is called, otherwise :exc:`RuntimeError` is raised. The entire Python program exits when no alive non-daemon threads are left. This means that an actor running on a daemon thread may be interrupted at any time, and there is no guarantee that cleanup will be done or that :meth:`pykka.Actor.on_stop` will be called. Actors do not inherit the daemon flag from the actor that made it. It always has to be set explicitly for the actor to run on a daemonic thread. """ @staticmethod def _create_actor_inbox(): return compat.queue.Queue() @staticmethod def _create_future(): return ThreadingFuture() def _start_actor_loop(self): thread = threading.Thread(target=self._actor_loop) thread.name = thread.name.replace('Thread', self.__class__.__name__) thread.daemon = self.use_daemon_thread thread.start() python-pykka-1.2.1/pylintrc000066400000000000000000000014501255323131500157400ustar00rootroot00000000000000[MESSAGES CONTROL] # # Disabled messages # ----------------- # # C0103 - Invalid name "%s" (should match %s) # C0111 - Missing docstring # E0102 - %s already defined line %s # Does not understand @property getters and setters # E0202 - An attribute inherited from %s hide this method # Does not understand @property getters and setters # E1101 - %s %r has no %r member # Does not understand @property getters and setters # R0201 - Method could be a function # R0801 - Similar lines in %s files # R0903 - Too few public methods (%s/%s) # R0904 - Too many public methods (%s/%s) # R0921 - Abstract class not referenced # W0141 - Used builtin function '%s' # W0142 - Used * or ** magic # W0613 - Unused argument %r # disable = C0103,C0111,E0102,E0202,E1101,R0201,R0801,R0903,R0904,R0921,W0141,W0142,W0613 python-pykka-1.2.1/setup.cfg000066400000000000000000000002401255323131500157660ustar00rootroot00000000000000[flake8] application-import-names = pykka,tests exclude = .git,.tox [nosetests] verbosity = 1 cover-package = pykka cover-inclusive = 1 [wheel] universal = 1 python-pykka-1.2.1/setup.py000066400000000000000000000023731255323131500156700ustar00rootroot00000000000000import re from setuptools import setup def get_version(): init_py = open('pykka/__init__.py').read() metadata = dict(re.findall("__([a-z]+)__ = '([^']+)'", init_py)) return metadata['version'] setup( name='Pykka', version=get_version(), author='Stein Magnus Jodal', author_email='stein.magnus@jodal.no', packages=['pykka'], url='http://www.pykka.org/', license='Apache License, Version 2.0', description='Pykka is a Python implementation of the actor model', long_description=open('README.rst').read(), classifiers=[ 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', 'License :: OSI Approved :: Apache Software License', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.2', 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', 'Topic :: Software Development :: Libraries', ], ) python-pykka-1.2.1/tests/000077500000000000000000000000001255323131500153135ustar00rootroot00000000000000python-pykka-1.2.1/tests/__init__.py000066400000000000000000000022021255323131500174200ustar00rootroot00000000000000import collections import logging import threading class TestLogHandler(logging.Handler): def __init__(self, *args, **kwargs): self.lock = threading.RLock() with self.lock: self.events = collections.defaultdict(threading.Event) self.messages = {} self.reset() logging.Handler.__init__(self, *args, **kwargs) def emit(self, record): with self.lock: level = record.levelname.lower() self.messages[level].append(record) self.events[level].set() def reset(self): with self.lock: for level in ('debug', 'info', 'warning', 'error', 'critical'): self.events[level].clear() self.messages[level] = [] def wait_for_message(self, level, num_messages=1): """Wait until at least ``num_messages`` log messages have been emitted to the given log level.""" while True: with self.lock: if len(self.messages[level]) >= num_messages: return self.events[level].clear() self.events[level].wait(5) python-pykka-1.2.1/tests/actor_test.py000066400000000000000000000325141255323131500200410ustar00rootroot00000000000000import threading import unittest import uuid from pykka import ActorDeadError, ActorRegistry, ThreadingActor class AnActor(object): def __init__(self, **events): super(AnActor, self).__init__() self.on_start_was_called = events['on_start_was_called'] self.on_stop_was_called = events['on_stop_was_called'] self.on_failure_was_called = events['on_failure_was_called'] self.actor_was_registered_before_on_start_was_called = events[ 'actor_was_registered_before_on_start_was_called'] self.greetings_was_received = events['greetings_was_received'] def on_start(self): self.on_start_was_called.set() if ActorRegistry.get_by_urn(self.actor_urn) is not None: self.actor_was_registered_before_on_start_was_called.set() def on_stop(self): self.on_stop_was_called.set() def on_failure(self, *args): self.on_failure_was_called.set() def on_receive(self, message): if message.get('command') == 'raise exception': raise Exception('foo') elif message.get('command') == 'raise base exception': raise BaseException() elif message.get('command') == 'stop twice': self.stop() self.stop() elif message.get('command') == 'message self then stop': self.actor_ref.tell({'command': 'greetings'}) self.stop() elif message.get('command') == 'greetings': self.greetings_was_received.set() elif message.get('command') == 'callback': message['callback']() else: super(AnActor, self).on_receive(message) class EarlyStoppingActor(object): def __init__(self, on_stop_was_called): super(EarlyStoppingActor, self).__init__() self.on_stop_was_called = on_stop_was_called def on_start(self): self.stop() def on_stop(self): self.on_stop_was_called.set() class EarlyFailingActor(object): def __init__(self, on_start_was_called): super(EarlyFailingActor, self).__init__() self.on_start_was_called = on_start_was_called def on_start(self): try: raise RuntimeError('on_start failure') finally: self.on_start_was_called.set() class LateFailingActor(object): def __init__(self, on_stop_was_called): super(LateFailingActor, self).__init__() self.on_stop_was_called = on_stop_was_called def on_start(self): self.stop() def on_stop(self): try: raise RuntimeError('on_stop failure') finally: self.on_stop_was_called.set() class FailingOnFailureActor(object): def __init__(self, on_failure_was_called): super(FailingOnFailureActor, self).__init__() self.on_failure_was_called = on_failure_was_called def on_receive(self, message): if message.get('command') == 'raise exception': raise Exception('on_receive failure') else: super(FailingOnFailureActor, self).on_receive(message) def on_failure(self, *args): try: raise RuntimeError('on_failure failure') finally: self.on_failure_was_called.set() class ActorTest(object): def setUp(self): self.on_start_was_called = self.event_class() self.on_stop_was_called = self.event_class() self.on_failure_was_called = self.event_class() self.actor_was_registered_before_on_start_was_called = ( self.event_class()) self.greetings_was_received = self.event_class() self.actor_ref = self.AnActor.start( on_start_was_called=self.on_start_was_called, on_stop_was_called=self.on_stop_was_called, on_failure_was_called=self.on_failure_was_called, actor_was_registered_before_on_start_was_called=( self.actor_was_registered_before_on_start_was_called), greetings_was_received=self.greetings_was_received) self.actor_proxy = self.actor_ref.proxy() def tearDown(self): ActorRegistry.stop_all() def test_messages_left_in_queue_after_actor_stops_receive_an_error(self): event = self.event_class() self.actor_ref.tell({'command': 'callback', 'callback': event.wait}) self.actor_ref.stop(block=False) response = self.actor_ref.ask({'command': 'irrelevant'}, block=False) event.set() self.assertRaises(ActorDeadError, response.get, timeout=0.5) def test_stop_requests_left_in_queue_after_actor_stops_are_handled(self): event = self.event_class() self.actor_ref.tell({'command': 'callback', 'callback': event.wait}) self.actor_ref.stop(block=False) response = self.actor_ref.ask({'command': 'pykka_stop'}, block=False) event.set() response.get(timeout=0.5) def test_actor_has_an_uuid4_based_urn(self): self.assertEqual(4, uuid.UUID(self.actor_ref.actor_urn).version) def test_actor_has_unique_uuid(self): event = self.event_class() actors = [ self.AnActor.start( on_start_was_called=event, on_stop_was_called=event, on_failure_was_called=event, actor_was_registered_before_on_start_was_called=event, greetings_was_received=event) for _ in range(3)] self.assertNotEqual(actors[0].actor_urn, actors[1].actor_urn) self.assertNotEqual(actors[1].actor_urn, actors[2].actor_urn) self.assertNotEqual(actors[2].actor_urn, actors[0].actor_urn) def test_str_on_raw_actor_contains_actor_class_name(self): event = self.event_class() unstarted_actor = self.AnActor( on_start_was_called=event, on_stop_was_called=event, on_failure_was_called=event, actor_was_registered_before_on_start_was_called=event, greetings_was_received=event) self.assert_('AnActor' in str(unstarted_actor)) def test_str_on_raw_actor_contains_actor_urn(self): event = self.event_class() unstarted_actor = self.AnActor( on_start_was_called=event, on_stop_was_called=event, on_failure_was_called=event, actor_was_registered_before_on_start_was_called=event, greetings_was_received=event) self.assert_(unstarted_actor.actor_urn in str(unstarted_actor)) def test_init_can_be_called_with_arbitrary_arguments(self): self.SuperInitActor(1, 2, 3, foo='bar') def test_on_start_is_called_before_first_message_is_processed(self): self.on_start_was_called.wait(5) self.assertTrue(self.on_start_was_called.is_set()) def test_on_start_is_called_after_the_actor_is_registered(self): # NOTE: If the actor is registered after the actor is started, this # test may still occasionally pass, as it is dependant on the exact # timing of events. When the actor is first registered and then # started, this test should always pass. self.on_start_was_called.wait(5) self.assertTrue(self.on_start_was_called.is_set()) self.actor_was_registered_before_on_start_was_called.wait(0.1) self.assertTrue( self.actor_was_registered_before_on_start_was_called.is_set()) def test_on_start_can_stop_actor_before_receive_loop_is_started(self): # NOTE: This test will pass even if the actor is allowed to start the # receive loop, but it will cause the test suite to hang, as the actor # thread is blocking on receiving messages to the actor inbox forever. # If one made this test specifically for ThreadingActor, one could add # an assertFalse(actor_thread.is_alive()), which would cause the test # to fail properly. stop_event = self.event_class() another_actor = self.EarlyStoppingActor.start(stop_event) stop_event.wait(5) self.assertTrue(stop_event.is_set()) self.assertFalse(another_actor.is_alive()) def test_on_start_failure_causes_actor_to_stop(self): # Actor should not be alive if on_start fails. start_event = self.event_class() actor_ref = self.EarlyFailingActor.start(start_event) start_event.wait(5) actor_ref.actor_stopped.wait(5) self.assertFalse(actor_ref.is_alive()) def test_on_stop_is_called_when_actor_is_stopped(self): self.assertFalse(self.on_stop_was_called.is_set()) self.actor_ref.stop() self.on_stop_was_called.wait(5) self.assertTrue(self.on_stop_was_called.is_set()) def test_on_stop_failure_causes_actor_to_stop(self): stop_event = self.event_class() actor = self.LateFailingActor.start(stop_event) stop_event.wait(5) self.assertFalse(actor.is_alive()) def test_on_failure_is_called_when_exception_cannot_be_returned(self): self.assertFalse(self.on_failure_was_called.is_set()) self.actor_ref.tell({'command': 'raise exception'}) self.on_failure_was_called.wait(5) self.assertTrue(self.on_failure_was_called.is_set()) self.assertFalse(self.on_stop_was_called.is_set()) def test_on_failure_failure_causes_actor_to_stop(self): failure_event = self.event_class() actor = self.FailingOnFailureActor.start(failure_event) actor.tell({'command': 'raise exception'}) failure_event.wait(5) self.assertFalse(actor.is_alive()) def test_actor_is_stopped_when_unhandled_exceptions_are_raised(self): self.assertFalse(self.on_failure_was_called.is_set()) self.actor_ref.tell({'command': 'raise exception'}) self.on_failure_was_called.wait(5) self.assertTrue(self.on_failure_was_called.is_set()) self.assertEqual(0, len(ActorRegistry.get_all())) def test_all_actors_are_stopped_on_base_exception(self): start_event = self.event_class() stop_event = self.event_class() fail_event = self.event_class() registered_event = self.event_class() greetings_event = self.event_class() self.AnActor.start( on_start_was_called=start_event, on_stop_was_called=stop_event, on_failure_was_called=fail_event, actor_was_registered_before_on_start_was_called=registered_event, greetings_was_received=greetings_event) self.assertEqual(2, len(ActorRegistry.get_all())) self.assertFalse(self.on_stop_was_called.is_set()) self.actor_ref.tell({'command': 'raise base exception'}) self.on_stop_was_called.wait(5) self.assertTrue(self.on_stop_was_called.is_set()) self.assert_(1 >= len(ActorRegistry.get_all())) stop_event.wait(5) self.assertTrue(stop_event.is_set()) self.assertEqual(0, len(ActorRegistry.get_all())) def test_actor_can_call_stop_on_self_multiple_times(self): self.actor_ref.ask({'command': 'stop twice'}) def test_actor_processes_all_messages_before_stop_on_self_stops_it(self): self.actor_ref.ask({'command': 'message self then stop'}) self.greetings_was_received.wait(5) self.assertTrue(self.greetings_was_received.is_set()) self.on_stop_was_called.wait(5) self.assertEqual(0, len(ActorRegistry.get_all())) def ConcreteActorTest(actor_class, event_class): class C(ActorTest, unittest.TestCase): class AnActor(AnActor, actor_class): pass class EarlyStoppingActor(EarlyStoppingActor, actor_class): pass class EarlyFailingActor(EarlyFailingActor, actor_class): pass class LateFailingActor(LateFailingActor, actor_class): pass class FailingOnFailureActor(FailingOnFailureActor, actor_class): pass class SuperInitActor(actor_class): pass C.__name__ = '%sTest' % actor_class.__name__ C.event_class = event_class return C class ThreadingActorTest(ConcreteActorTest(ThreadingActor, threading.Event)): class DaemonActor(ThreadingActor): use_daemon_thread = True def test_actor_thread_is_named_after_pykka_actor_class(self): alive_threads = threading.enumerate() alive_thread_names = [t.name for t in alive_threads] named_correctly = [ name.startswith(AnActor.__name__) for name in alive_thread_names] self.assert_(any(named_correctly)) def test_actor_thread_is_not_daemonic_by_default(self): alive_threads = threading.enumerate() actor_threads = [ t for t in alive_threads if t.name.startswith('AnActor')] self.assertEqual(1, len(actor_threads)) self.assertFalse(actor_threads[0].daemon) def test_actor_thread_is_daemonic_if_use_daemon_thread_flag_is_set(self): actor_ref = self.DaemonActor.start() alive_threads = threading.enumerate() actor_threads = [ t for t in alive_threads if t.name.startswith('DaemonActor')] self.assertEqual(1, len(actor_threads)) self.assertTrue(actor_threads[0].daemon) actor_ref.stop() try: import gevent.event from pykka.gevent import GeventActor GeventActorTest = ConcreteActorTest(GeventActor, gevent.event.Event) except ImportError: pass try: import eventlet # noqa from pykka.eventlet import EventletActor, EventletEvent EventletActorTest = ConcreteActorTest(EventletActor, EventletEvent) except ImportError: pass python-pykka-1.2.1/tests/field_access_test.py000066400000000000000000000034351255323131500213350ustar00rootroot00000000000000import unittest from pykka import ThreadingActor class SomeObject(object): pykka_traversable = False baz = 'bar.baz' _private_field = 'secret' class ActorWithFields(object): foo = 'foo' bar = SomeObject() bar.pykka_traversable = True _private_field = 'secret' class FieldAccessTest(object): def setUp(self): self.proxy = self.ActorWithFields.start().proxy() def tearDown(self): self.proxy.stop() def test_actor_field_can_be_read_using_get_postfix(self): self.assertEqual('foo', self.proxy.foo.get()) def test_actor_field_can_be_set_using_assignment(self): self.assertEqual('foo', self.proxy.foo.get()) self.proxy.foo = 'foo2' self.assertEqual('foo2', self.proxy.foo.get()) def test_private_field_access_raises_exception(self): try: self.proxy._private_field.get() self.fail('Should raise AttributeError exception') except AttributeError: pass except Exception: self.fail('Should raise AttributeError exception') def test_attr_of_traversable_attr_can_be_read(self): self.assertEqual('bar.baz', self.proxy.bar.baz.get()) def ConcreteFieldAccessTest(actor_class): class C(FieldAccessTest, unittest.TestCase): class ActorWithFields(ActorWithFields, actor_class): pass C.__name__ = '%sFieldAccessTest' % actor_class.__name__ return C ThreadingFieldAccessTest = ConcreteFieldAccessTest(ThreadingActor) try: from pykka.gevent import GeventActor GeventFieldAccessTest = ConcreteFieldAccessTest(GeventActor) except ImportError: pass try: from pykka.eventlet import EventletActor EventletFieldAccessTest = ConcreteFieldAccessTest(EventletActor) except ImportError: pass python-pykka-1.2.1/tests/future_test.py000066400000000000000000000147651255323131500202530ustar00rootroot00000000000000import sys import traceback import unittest from pykka import Future, ThreadingFuture, Timeout, get_all class FutureBaseTest(unittest.TestCase): def setUp(self): self.future = Future() def test_future_get_is_not_implemented(self): self.assertRaises(NotImplementedError, self.future.get) def test_future_set_is_not_implemented(self): self.assertRaises(NotImplementedError, self.future.set, None) def test_future_set_exception_is_not_implemented(self): self.assertRaises(NotImplementedError, self.future.set_exception, None) class FutureTest(object): def setUp(self): self.results = [self.future_class() for _ in range(3)] def test_set_multiple_times_fails(self): self.results[0].set(0) self.assertRaises(Exception, self.results[0].set, 0) def test_get_all_blocks_until_all_futures_are_available(self): self.results[0].set(0) self.results[1].set(1) self.results[2].set(2) result = get_all(self.results) self.assertEqual(result, [0, 1, 2]) def test_get_all_raises_timeout_if_not_all_futures_are_available(self): self.results[0].set(0) self.results[1].set(1) self.assertRaises(Timeout, get_all, self.results, timeout=0) def test_get_all_can_be_called_multiple_times(self): self.results[0].set(0) self.results[1].set(1) self.results[2].set(2) result1 = get_all(self.results) result2 = get_all(self.results) self.assertEqual(result1, result2) def test_future_in_future_works(self): inner_future = self.future_class() inner_future.set('foo') outer_future = self.future_class() outer_future.set(inner_future) self.assertEqual(outer_future.get().get(), 'foo') def test_get_raises_exception_with_full_traceback(self): exc_class_get = None exc_class_set = None exc_instance_get = None exc_instance_set = None exc_traceback_get = None exc_traceback_set = None future = self.future_class() try: raise NameError('foo') except NameError: exc_class_set, exc_instance_set, exc_traceback_set = sys.exc_info() future.set_exception() # We could move to another thread at this point try: future.get() except NameError: exc_class_get, exc_instance_get, exc_traceback_get = sys.exc_info() self.assertEqual(exc_class_set, exc_class_get) self.assertEqual(exc_instance_set, exc_instance_get) exc_traceback_list_set = list(reversed( traceback.extract_tb(exc_traceback_set))) exc_traceback_list_get = list(reversed( traceback.extract_tb(exc_traceback_get))) # All frames from the first traceback should be included in the # traceback from the future.get() reraise self.assert_(len(exc_traceback_list_set) < len(exc_traceback_list_get)) for i, frame in enumerate(exc_traceback_list_set): self.assertEquals(frame, exc_traceback_list_get[i]) def test_filter_excludes_items_not_matching_predicate(self): future = self.results[0].filter(lambda x: x > 10) self.results[0].set([1, 3, 5, 7, 9, 11, 13, 15, 17, 19]) self.assertEqual(future.get(timeout=0), [11, 13, 15, 17, 19]) def test_filter_on_noniterable(self): future = self.results[0].filter(lambda x: x > 10) self.results[0].set(1) self.assertRaises(TypeError, future.get, timeout=0) def test_filter_preserves_the_timeout_kwarg(self): future = self.results[0].filter(lambda x: x > 10) self.assertRaises(Timeout, future.get, timeout=0) def test_join_combines_multiple_futures_into_one(self): future = self.results[0].join(self.results[1], self.results[2]) self.results[0].set(0) self.results[1].set(1) self.results[2].set(2) self.assertEqual(future.get(timeout=0), [0, 1, 2]) def test_join_preserves_timeout_kwarg(self): future = self.results[0].join(self.results[1], self.results[2]) self.results[0].set(0) self.results[1].set(1) self.assertRaises(Timeout, future.get, timeout=0) def test_map_returns_future_which_passes_noniterable_through_func(self): future = self.results[0].map(lambda x: x + 10) self.results[0].set(30) self.assertEqual(future.get(timeout=0), 40) def test_map_returns_future_which_maps_iterable_through_func(self): future = self.results[0].map(lambda x: x + 10) self.results[0].set([10, 20, 30]) self.assertEqual(future.get(timeout=0), [20, 30, 40]) def test_map_preserves_timeout_kwarg(self): future = self.results[0].map(lambda x: x + 10) self.assertRaises(Timeout, future.get, timeout=0) def test_reduce_applies_function_cumulatively_from_the_left(self): future = self.results[0].reduce(lambda x, y: x + y) self.results[0].set([1, 2, 3, 4]) self.assertEqual(future.get(timeout=0), 10) def test_reduce_accepts_an_initial_value(self): future = self.results[0].reduce(lambda x, y: x + y, 5) self.results[0].set([1, 2, 3, 4]) self.assertEqual(future.get(timeout=0), 15) def test_reduce_on_noniterable(self): future = self.results[0].reduce(lambda x, y: x + y) self.results[0].set(1) self.assertRaises(TypeError, future.get, timeout=0) def test_reduce_preserves_the_timeout_kwarg(self): future = self.results[0].reduce(lambda x, y: x + y) self.assertRaises(Timeout, future.get, timeout=0) class ThreadingFutureTest(FutureTest, unittest.TestCase): future_class = ThreadingFuture try: from gevent.event import AsyncResult from pykka.gevent import GeventFuture class GeventFutureTest(FutureTest, unittest.TestCase): future_class = GeventFuture def test_can_wrap_existing_async_result(self): async_result = AsyncResult() future = GeventFuture(async_result) self.assertEquals(async_result, future.async_result) def test_get_raises_exception_with_full_traceback(self): # gevent prints the first half of the traceback instead of # passing it through to the other side of the AsyncResult pass except ImportError: pass try: from pykka.eventlet import EventletFuture class EventletFutureTest(FutureTest, unittest.TestCase): future_class = EventletFuture except ImportError: pass python-pykka-1.2.1/tests/logging_test.py000066400000000000000000000160371255323131500203610ustar00rootroot00000000000000import logging import threading import unittest from pykka import ActorRegistry, ThreadingActor from tests import TestLogHandler from tests.actor_test import ( EarlyFailingActor, FailingOnFailureActor, LateFailingActor) class LoggingNullHandlerTest(unittest.TestCase): def test_null_handler_is_added_to_avoid_warnings(self): logger = logging.getLogger('pykka') handler_names = [h.__class__.__name__ for h in logger.handlers] self.assert_('NullHandler' in handler_names) class ActorLoggingTest(object): def setUp(self): self.on_stop_was_called = self.event_class() self.on_failure_was_called = self.event_class() self.actor_ref = self.AnActor.start( self.on_stop_was_called, self.on_failure_was_called) self.actor_proxy = self.actor_ref.proxy() self.log_handler = TestLogHandler(logging.DEBUG) self.root_logger = logging.getLogger() self.root_logger.addHandler(self.log_handler) def tearDown(self): self.log_handler.close() ActorRegistry.stop_all() def test_unexpected_messages_are_logged(self): self.actor_ref.ask({'unhandled': 'message'}) self.log_handler.wait_for_message('warning') with self.log_handler.lock: self.assertEqual(1, len(self.log_handler.messages['warning'])) log_record = self.log_handler.messages['warning'][0] self.assertEqual( 'Unexpected message received by %s' % self.actor_ref, log_record.getMessage().split(': ')[0]) def test_exception_is_logged_when_returned_to_caller(self): try: self.actor_proxy.raise_exception().get() self.fail('Should raise exception') except Exception: pass self.log_handler.wait_for_message('debug') with self.log_handler.lock: self.assertEqual(1, len(self.log_handler.messages['debug'])) log_record = self.log_handler.messages['debug'][0] self.assertEqual( 'Exception returned from %s to caller:' % self.actor_ref, log_record.getMessage()) self.assertEqual(Exception, log_record.exc_info[0]) self.assertEqual('foo', str(log_record.exc_info[1])) def test_exception_is_logged_when_not_reply_requested(self): self.on_failure_was_called.clear() self.actor_ref.tell({'command': 'raise exception'}) self.on_failure_was_called.wait(5) self.assertTrue(self.on_failure_was_called.is_set()) self.log_handler.wait_for_message('error') with self.log_handler.lock: self.assertEqual(1, len(self.log_handler.messages['error'])) log_record = self.log_handler.messages['error'][0] self.assertEqual( 'Unhandled exception in %s:' % self.actor_ref, log_record.getMessage()) self.assertEqual(Exception, log_record.exc_info[0]) self.assertEqual('foo', str(log_record.exc_info[1])) def test_base_exception_is_logged(self): self.log_handler.reset() self.on_stop_was_called.clear() self.actor_ref.tell({'command': 'raise base exception'}) self.on_stop_was_called.wait(5) self.assertTrue(self.on_stop_was_called.is_set()) self.log_handler.wait_for_message('debug', num_messages=3) with self.log_handler.lock: self.assertEqual(3, len(self.log_handler.messages['debug'])) log_record = self.log_handler.messages['debug'][0] self.assertEqual( 'BaseException() in %s. Stopping all actors.' % self.actor_ref, log_record.getMessage()) def test_exception_in_on_start_is_logged(self): self.log_handler.reset() start_event = self.event_class() actor_ref = self.EarlyFailingActor.start(start_event) start_event.wait(5) self.log_handler.wait_for_message('error') with self.log_handler.lock: self.assertEqual(1, len(self.log_handler.messages['error'])) log_record = self.log_handler.messages['error'][0] self.assertEqual( 'Unhandled exception in %s:' % actor_ref, log_record.getMessage()) def test_exception_in_on_stop_is_logged(self): self.log_handler.reset() stop_event = self.event_class() actor_ref = self.LateFailingActor.start(stop_event) stop_event.wait(5) self.log_handler.wait_for_message('error') with self.log_handler.lock: self.assertEqual(1, len(self.log_handler.messages['error'])) log_record = self.log_handler.messages['error'][0] self.assertEqual( 'Unhandled exception in %s:' % actor_ref, log_record.getMessage()) def test_exception_in_on_failure_is_logged(self): self.log_handler.reset() failure_event = self.event_class() actor_ref = self.FailingOnFailureActor.start(failure_event) actor_ref.tell({'command': 'raise exception'}) failure_event.wait(5) self.log_handler.wait_for_message('error', num_messages=2) with self.log_handler.lock: self.assertEqual(2, len(self.log_handler.messages['error'])) log_record = self.log_handler.messages['error'][0] self.assertEqual( 'Unhandled exception in %s:' % actor_ref, log_record.getMessage()) class AnActor(object): def __init__(self, on_stop_was_called, on_failure_was_called): super(AnActor, self).__init__() self.on_stop_was_called = on_stop_was_called self.on_failure_was_called = on_failure_was_called def on_stop(self): self.on_stop_was_called.set() def on_failure(self, exception_type, exception_value, traceback): self.on_failure_was_called.set() def on_receive(self, message): if message.get('command') == 'raise exception': return self.raise_exception() elif message.get('command') == 'raise base exception': raise BaseException() else: super(AnActor, self).on_receive(message) def raise_exception(self): raise Exception('foo') def ConcreteActorLoggingTest(actor_class, event_class): class C(ActorLoggingTest, unittest.TestCase): class AnActor(AnActor, actor_class): pass class EarlyFailingActor(EarlyFailingActor, actor_class): pass class LateFailingActor(LateFailingActor, actor_class): pass class FailingOnFailureActor(FailingOnFailureActor, actor_class): pass C.event_class = event_class C.__name__ = '%sLoggingTest' % actor_class.__name__ return C ThreadingActorLoggingTest = ConcreteActorLoggingTest( ThreadingActor, threading.Event) try: import gevent.event from pykka.gevent import GeventActor GeventActorLoggingTest = ConcreteActorLoggingTest( GeventActor, gevent.event.Event) except ImportError: pass try: from pykka.eventlet import EventletActor, EventletEvent EventletActorLoggingTest = ConcreteActorLoggingTest( EventletActor, EventletEvent) except ImportError: pass python-pykka-1.2.1/tests/method_call_test.py000066400000000000000000000102211255323131500211730ustar00rootroot00000000000000import unittest from pykka import ThreadingActor class ActorWithMethods(object): cat = 'dog' def functional_hello(self, s): return 'Hello, %s!' % s def set_cat(self, s): self.cat = s def raise_keyboard_interrupt(self): raise KeyboardInterrupt def talk_with_self(self): return self.actor_ref.proxy().functional_hello('from the future') class ActorExtendableAtRuntime(object): def add_method(self, name): setattr(self, name, lambda: 'returned by ' + name) def use_foo_through_self_proxy(self): return self.actor_ref.proxy().foo() class StaticMethodCallTest(object): def setUp(self): self.proxy = self.ActorWithMethods.start().proxy() def tearDown(self): self.proxy.stop() def test_functional_method_call_returns_correct_value(self): self.assertEqual( 'Hello, world!', self.proxy.functional_hello('world').get()) self.assertEqual( 'Hello, moon!', self.proxy.functional_hello('moon').get()) def test_side_effect_of_method_is_observable(self): self.assertEqual('dog', self.proxy.cat.get()) self.proxy.set_cat('eagle') self.assertEqual('eagle', self.proxy.cat.get()) def test_calling_unknown_method_raises_attribute_error(self): try: self.proxy.unknown_method() self.fail('Should raise AttributeError') except AttributeError as e: result = str(e) self.assert_(result.startswith('')) def test_repr_reveals_that_this_is_a_proxy(self): self.assert_('ActorProxy' in repr(self.proxy)) def test_repr_contains_actor_class_name(self): self.assert_('AnActor' in repr(self.proxy)) def test_repr_contains_actor_urn(self): self.assert_(self.proxy.actor_ref.actor_urn in repr(self.proxy)) def test_repr_contains_attr_path(self): self.assert_('bar' in repr(self.proxy.bar)) def test_str_contains_actor_class_name(self): self.assert_('AnActor' in str(self.proxy)) def test_str_contains_actor_urn(self): self.assert_(self.proxy.actor_ref.actor_urn in str(self.proxy)) def test_dir_on_proxy_lists_attributes_of_the_actor(self): result = dir(self.proxy) self.assert_('foo' in result) self.assert_('cat' in result) self.assert_('func' in result) def test_dir_on_proxy_lists_private_attributes_of_the_proxy(self): result = dir(self.proxy) self.assert_('__class__' in result) self.assert_('__dict__' in result) self.assert_('__getattr__' in result) self.assert_('__setattr__' in result) def test_refs_proxy_method_returns_a_proxy(self): proxy_from_ref_proxy = self.AnActor.start().proxy() self.assert_(isinstance(proxy_from_ref_proxy, ActorProxy)) proxy_from_ref_proxy.stop().get() def test_proxy_constructor_raises_exception_if_actor_is_dead(self): actor_ref = self.AnActor.start() actor_ref.stop() try: ActorProxy(actor_ref) self.fail('Should raise ActorDeadError') except ActorDeadError as exception: self.assertEqual('%s not found' % actor_ref, str(exception)) def test_actor_ref_may_be_retrieved_from_proxy_if_actor_is_dead(self): self.proxy.actor_ref.stop() self.assertFalse(self.proxy.actor_ref.is_alive()) def ConcreteProxyTest(actor_class): class C(ProxyTest, unittest.TestCase): class AnActor(AnActor, actor_class): pass C.__name__ = '%sProxyTest' % actor_class.__name__ return C ThreadingActorProxyTest = ConcreteProxyTest(ThreadingActor) try: from pykka.gevent import GeventActor GeventActorProxyTest = ConcreteProxyTest(GeventActor) except ImportError: pass try: from pykka.eventlet import EventletActor EventletActorProxyTest = ConcreteProxyTest(EventletActor) except ImportError: pass python-pykka-1.2.1/tests/ref_test.py000066400000000000000000000106421255323131500175030ustar00rootroot00000000000000import time import unittest from pykka import ActorDeadError, ThreadingActor, ThreadingFuture, Timeout class AnActor(object): def __init__(self, received_message): super(AnActor, self).__init__() self.received_message = received_message def on_receive(self, message): if message.get('command') == 'ping': self.sleep(0.01) return 'pong' else: self.received_message.set(message) class RefTest(object): def setUp(self): self.received_message = self.future_class() self.ref = self.AnActor.start(self.received_message) def tearDown(self): self.ref.stop() def test_repr_is_wrapped_in_lt_and_gt(self): result = repr(self.ref) self.assert_(result.startswith('<')) self.assert_(result.endswith('>')) def test_repr_reveals_that_this_is_a_ref(self): self.assert_('ActorRef' in repr(self.ref)) def test_repr_contains_actor_class_name(self): self.assert_('AnActor' in repr(self.ref)) def test_repr_contains_actor_urn(self): self.assert_(self.ref.actor_urn in repr(self.ref)) def test_str_contains_actor_class_name(self): self.assert_('AnActor' in str(self.ref)) def test_str_contains_actor_urn(self): self.assert_(self.ref.actor_urn in str(self.ref)) def test_is_alive_returns_true_for_running_actor(self): self.assertTrue(self.ref.is_alive()) def test_is_alive_returns_false_for_dead_actor(self): self.ref.stop() self.assertFalse(self.ref.is_alive()) def test_stop_returns_true_if_actor_is_stopped(self): self.assertTrue(self.ref.stop()) def test_stop_does_not_stop_already_dead_actor(self): self.ref.stop() try: self.assertFalse(self.ref.stop()) except ActorDeadError: self.fail('Should never raise ActorDeadError') def test_tell_delivers_message_to_actors_custom_on_receive(self): self.ref.tell({'command': 'a custom message'}) self.assertEqual( {'command': 'a custom message'}, self.received_message.get()) def test_tell_fails_if_actor_is_stopped(self): self.ref.stop() try: self.ref.tell({'command': 'a custom message'}) self.fail('Should raise ActorDeadError') except ActorDeadError as exception: self.assertEqual('%s not found' % self.ref, str(exception)) def test_ask_blocks_until_response_arrives(self): result = self.ref.ask({'command': 'ping'}) self.assertEqual('pong', result) def test_ask_can_timeout_if_blocked_too_long(self): try: self.ref.ask({'command': 'ping'}, timeout=0) self.fail('Should raise Timeout exception') except Timeout: pass def test_ask_can_return_future_instead_of_blocking(self): future = self.ref.ask({'command': 'ping'}, block=False) self.assertEqual('pong', future.get()) def test_ask_fails_if_actor_is_stopped(self): self.ref.stop() try: self.ref.ask({'command': 'ping'}) self.fail('Should raise ActorDeadError') except ActorDeadError as exception: self.assertEqual('%s not found' % self.ref, str(exception)) def test_ask_nonblocking_fails_future_if_actor_is_stopped(self): self.ref.stop() future = self.ref.ask({'command': 'ping'}, block=False) try: future.get() self.fail('Should raise ActorDeadError') except ActorDeadError as exception: self.assertEqual('%s not found' % self.ref, str(exception)) def ConcreteRefTest(actor_class, future_class, sleep_function): class C(RefTest, unittest.TestCase): class AnActor(AnActor, actor_class): def sleep(self, seconds): sleep_function(seconds) C.__name__ = '%sRefTest' % (actor_class.__name__,) C.future_class = future_class return C ThreadingActorRefTest = ConcreteRefTest( ThreadingActor, ThreadingFuture, time.sleep) try: import gevent from pykka.gevent import GeventActor, GeventFuture GeventActorRefTest = ConcreteRefTest( GeventActor, GeventFuture, gevent.sleep) except ImportError: pass try: import eventlet from pykka.eventlet import EventletActor, EventletFuture EventletActorRefTest = ConcreteRefTest( EventletActor, EventletFuture, eventlet.sleep) except ImportError: pass python-pykka-1.2.1/tests/registry_test.py000066400000000000000000000141401255323131500205740ustar00rootroot00000000000000import unittest import mock from pykka import ActorRegistry, ThreadingActor class ActorRegistryTest(object): def setUp(self): self.ref = self.AnActor.start() self.a_actors = [self.AnActor.start() for _ in range(3)] self.b_actors = [self.BeeActor.start() for _ in range(5)] self.a_actor_0_urn = self.a_actors[0].actor_urn def tearDown(self): ActorRegistry.stop_all() def test_actor_is_registered_when_started(self): self.assert_(self.ref in ActorRegistry.get_all()) def test_actor_is_unregistered_when_stopped(self): self.assert_(self.ref in ActorRegistry.get_all()) self.ref.stop() self.assert_(self.ref not in ActorRegistry.get_all()) def test_actor_may_be_registered_manually(self): ActorRegistry.unregister(self.ref) self.assert_(self.ref not in ActorRegistry.get_all()) ActorRegistry.register(self.ref) self.assert_(self.ref in ActorRegistry.get_all()) def test_actor_may_be_unregistered_multiple_times_without_error(self): ActorRegistry.unregister(self.ref) self.assert_(self.ref not in ActorRegistry.get_all()) ActorRegistry.unregister(self.ref) self.assert_(self.ref not in ActorRegistry.get_all()) ActorRegistry.register(self.ref) self.assert_(self.ref in ActorRegistry.get_all()) def test_all_actors_can_be_stopped_through_registry(self): self.assertEquals(9, len(ActorRegistry.get_all())) ActorRegistry.stop_all(block=True) self.assertEquals(0, len(ActorRegistry.get_all())) @mock.patch.object(ActorRegistry, 'get_all') def test_stop_all_stops_last_started_actor_first_if_blocking( self, mock_method): stopped_actors = [] started_actors = [mock.Mock(name=i) for i in range(3)] started_actors[0].stop.side_effect = lambda *a, **kw: \ stopped_actors.append(started_actors[0]) started_actors[1].stop.side_effect = lambda *a, **kw: \ stopped_actors.append(started_actors[1]) started_actors[2].stop.side_effect = lambda *a, **kw: \ stopped_actors.append(started_actors[2]) ActorRegistry.get_all.return_value = started_actors ActorRegistry.stop_all(block=True) self.assertEqual(stopped_actors[0], started_actors[2]) self.assertEqual(stopped_actors[1], started_actors[1]) self.assertEqual(stopped_actors[2], started_actors[0]) def test_actors_may_be_looked_up_by_class(self): result = ActorRegistry.get_by_class(self.AnActor) for a_actor in self.a_actors: self.assert_(a_actor in result) for b_actor in self.b_actors: self.assert_(b_actor not in result) def test_actors_may_be_looked_up_by_superclass(self): result = ActorRegistry.get_by_class(AnActor) for a_actor in self.a_actors: self.assert_(a_actor in result) for b_actor in self.b_actors: self.assert_(b_actor not in result) def test_actors_may_be_looked_up_by_class_name(self): result = ActorRegistry.get_by_class_name('AnActor') for a_actor in self.a_actors: self.assert_(a_actor in result) for b_actor in self.b_actors: self.assert_(b_actor not in result) def test_actors_may_be_looked_up_by_urn(self): result = ActorRegistry.get_by_urn(self.a_actor_0_urn) self.assertEqual(self.a_actors[0], result) def test_get_by_urn_returns_none_if_not_found(self): result = ActorRegistry.get_by_urn('urn:foo:bar') self.assertEqual(None, result) def test_broadcast_sends_message_to_all_actors_if_no_target(self): ActorRegistry.broadcast({'command': 'foo'}) for actor_ref in ActorRegistry.get_all(): received_messages = actor_ref.proxy().received_messages.get() self.assert_({'command': 'foo'} in received_messages) def test_broadcast_sends_message_to_all_actors_of_given_class(self): ActorRegistry.broadcast({'command': 'foo'}, target_class=self.AnActor) for actor_ref in ActorRegistry.get_by_class(self.AnActor): received_messages = actor_ref.proxy().received_messages.get() self.assert_({'command': 'foo'} in received_messages) for actor_ref in ActorRegistry.get_by_class(self.BeeActor): received_messages = actor_ref.proxy().received_messages.get() self.assert_({'command': 'foo'} not in received_messages) def test_broadcast_sends_message_to_all_actors_of_given_class_name(self): ActorRegistry.broadcast({'command': 'foo'}, target_class='AnActor') for actor_ref in ActorRegistry.get_by_class(self.AnActor): received_messages = actor_ref.proxy().received_messages.get() self.assert_({'command': 'foo'} in received_messages) for actor_ref in ActorRegistry.get_by_class(self.BeeActor): received_messages = actor_ref.proxy().received_messages.get() self.assert_({'command': 'foo'} not in received_messages) class AnActor(object): received_messages = None def __init__(self): super(AnActor, self).__init__() self.received_messages = [] def on_receive(self, message): self.received_messages.append(message) class BeeActor(object): received_messages = None def __init__(self): super(BeeActor, self).__init__() self.received_messages = [] def on_receive(self, message): self.received_messages.append(message) def ConcreteRegistryTest(actor_class): class C(ActorRegistryTest, unittest.TestCase): class AnActor(AnActor, actor_class): pass class BeeActor(BeeActor, actor_class): pass C.__name__ = '%sRegistryTest' % (actor_class.__name__,) return C ThreadingActorRegistryTest = ConcreteRegistryTest(ThreadingActor) try: from pykka.gevent import GeventActor GeventActorRegistryTest = ConcreteRegistryTest(GeventActor) except ImportError: pass try: from pykka.eventlet import EventletActor EventletActorRegistryTest = ConcreteRegistryTest(EventletActor) except ImportError: pass python-pykka-1.2.1/tox.ini000066400000000000000000000011741255323131500154670ustar00rootroot00000000000000[tox] envlist = py26, py27, py32, py33, py34, pypy, pypy3, docs, flake8 [testenv] deps = coverage mock nose commands = nosetests -v --where=tests --with-xunit --xunit-file=xunit-{envname}.xml --with-coverage --cover-package=pykka # gevent and eventlet only support Python 2.x [testenv:py26] deps = {[testenv]deps} eventlet gevent [testenv:py27] deps = {[testenv]deps} eventlet gevent [testenv:docs] changedir = docs deps = sphinx commands = sphinx-build -b html -d {envtmpdir}/doctrees . {envtmpdir}/html [testenv:flake8] deps = flake8 flake8-import-order commands = flake8