Pykka-2.0.2/0000775000175000017500000000000013571214203012776 5ustar jodaljodal00000000000000Pykka-2.0.2/.circleci/0000775000175000017500000000000013571214203014631 5ustar jodaljodal00000000000000Pykka-2.0.2/.circleci/config.yml0000664000175000017500000000326113564006023016624 0ustar jodaljodal00000000000000version: 2.1 orbs: codecov: codecov/codecov@1.0.5 workflows: version: 2 test: jobs: - py27 - py35 - py36 - py37 - py38 - pypy - pypy3 - docs - flake8 - check-manifest - black jobs: py37: &test-template docker: - image: circleci/python:3.7 steps: - checkout - run: name: Install test dependencies command: sudo pip install tox || pip install tox - restore_cache: name: Restoring tox cache keys: - tox-v1-{{ .Environment.CIRCLE_JOB }}-{{ .BuildNum }} - tox-v1-{{ .Environment.CIRCLE_JOB }} - run: name: Run tests command: | tox -e $CIRCLE_JOB -- \ --junit-xml=test-results/pytest/results.xml \ --cov-report=xml - save_cache: name: Saving tox cache key: tox-v1-{{ .Environment.CIRCLE_JOB }}-{{ .BuildNum }} paths: - ./.tox - ~/.cache/pip - codecov/upload: file: coverage.xml - store_test_results: path: test-results py27: <<: *test-template docker: - image: circleci/python:2.7 py35: <<: *test-template docker: - image: circleci/python:3.5 py36: <<: *test-template docker: - image: circleci/python:3.6 py38: <<: *test-template docker: - image: circleci/python:3.8-rc pypy: <<: *test-template docker: - image: pypy:2.7 pypy3: <<: *test-template docker: - image: pypy:3.5 docs: *test-template flake8: *test-template check-manifest: *test-template black: *test-template Pykka-2.0.2/.readthedocs.yml0000664000175000017500000000025613547724426016107 0ustar jodaljodal00000000000000version: 2 build: image: latest python: version: 3.7 install: - method: pip path: . sphinx: builder: htmldir configuration: docs/conf.py formats: all Pykka-2.0.2/LICENSE0000644000175000017500000002613613077462072014023 0ustar jodaljodal00000000000000 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. Pykka-2.0.2/MANIFEST.in0000664000175000017500000000041613547724426014555 0ustar jodaljodal00000000000000include *.rst include *.txt include *.yml include LICENSE include pylintrc include pyproject.toml include setup.cfg include tox.ini recursive-include .circleci * recursive-include docs * prune docs/_build recursive-include examples *.py recursive-include tests *.py Pykka-2.0.2/PKG-INFO0000664000175000017500000000567013571214203014103 0ustar jodaljodal00000000000000Metadata-Version: 2.1 Name: Pykka Version: 2.0.2 Summary: Pykka is a Python implementation of the actor model Home-page: https://www.pykka.org/ Author: Stein Magnus Jodal Author-email: stein.magnus@jodal.no License: Apache License, Version 2.0 Project-URL: Issues, https://github.com/jodal/pykka/issues Project-URL: Source, https://github.com/jodal/pykka Description: ===== 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. For details and code examples, see the `Pykka documentation `_. Pykka is available from PyPI. To install it, run:: pip install pykka Pykka works with CPython 2.7 and 3.5+, as well as PyPy 2.7 and 3.5+. License ======= Pykka is copyright 2010-2019 Stein Magnus Jodal and contributors. Pykka is licensed under the `Apache License, Version 2.0 `_. Project resources ================= - `Documentation `_ - `Source code `_ - `Issue tracker `_ .. image:: https://img.shields.io/pypi/v/Pykka.svg :target: https://pypi.org/project/Pykka/ :alt: Latest PyPI version .. image:: https://img.shields.io/circleci/project/github/jodal/pykka/develop.svg :target: https://circleci.com/gh/jodal/pykka :alt: CircleCI build status .. image:: https://img.shields.io/readthedocs/pykka.svg :target: https://www.pykka.org/ :alt: Read the Docs build status .. image:: https://img.shields.io/codecov/c/github/jodal/pykka/develop.svg :target: https://codecov.io/gh/jodal/pykka :alt: Test coverage Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: Topic :: Software Development :: Libraries Classifier: License :: OSI Approved :: Apache Software License Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.* Provides-Extra: dev Pykka-2.0.2/Pykka.egg-info/0000775000175000017500000000000013571214203015547 5ustar jodaljodal00000000000000Pykka-2.0.2/Pykka.egg-info/PKG-INFO0000664000175000017500000000567013571214203016654 0ustar jodaljodal00000000000000Metadata-Version: 2.1 Name: Pykka Version: 2.0.2 Summary: Pykka is a Python implementation of the actor model Home-page: https://www.pykka.org/ Author: Stein Magnus Jodal Author-email: stein.magnus@jodal.no License: Apache License, Version 2.0 Project-URL: Issues, https://github.com/jodal/pykka/issues Project-URL: Source, https://github.com/jodal/pykka Description: ===== 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. For details and code examples, see the `Pykka documentation `_. Pykka is available from PyPI. To install it, run:: pip install pykka Pykka works with CPython 2.7 and 3.5+, as well as PyPy 2.7 and 3.5+. License ======= Pykka is copyright 2010-2019 Stein Magnus Jodal and contributors. Pykka is licensed under the `Apache License, Version 2.0 `_. Project resources ================= - `Documentation `_ - `Source code `_ - `Issue tracker `_ .. image:: https://img.shields.io/pypi/v/Pykka.svg :target: https://pypi.org/project/Pykka/ :alt: Latest PyPI version .. image:: https://img.shields.io/circleci/project/github/jodal/pykka/develop.svg :target: https://circleci.com/gh/jodal/pykka :alt: CircleCI build status .. image:: https://img.shields.io/readthedocs/pykka.svg :target: https://www.pykka.org/ :alt: Read the Docs build status .. image:: https://img.shields.io/codecov/c/github/jodal/pykka/develop.svg :target: https://codecov.io/gh/jodal/pykka :alt: Test coverage Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: Topic :: Software Development :: Libraries Classifier: License :: OSI Approved :: Apache Software License Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.* Provides-Extra: dev Pykka-2.0.2/Pykka.egg-info/SOURCES.txt0000664000175000017500000000325213571214203017435 0ustar jodaljodal00000000000000.readthedocs.yml LICENSE MANIFEST.in README.rst pyproject.toml setup.cfg setup.py tox.ini .circleci/config.yml Pykka.egg-info/PKG-INFO Pykka.egg-info/SOURCES.txt Pykka.egg-info/dependency_links.txt Pykka.egg-info/requires.txt Pykka.egg-info/top_level.txt docs/Makefile docs/changes.rst docs/conf.py docs/examples.rst docs/index.rst docs/inspiration.rst docs/quickstart.rst docs/testing.rst docs/_static/.gitignore docs/api/actors.rst docs/api/debug.rst docs/api/exceptions.rst docs/api/futures.rst docs/api/index.rst docs/api/logging.rst docs/api/messages.rst docs/api/proxies.rst docs/api/registry.rst docs/runtimes/eventlet.rst docs/runtimes/gevent.rst docs/runtimes/index.rst docs/runtimes/threading.rst examples/counter.py examples/deadlock_debugging.py examples/plain_actor.py examples/producer.py examples/producer_test.py examples/resolver.py examples/typed_actor.py pykka/__init__.py pykka/_actor.py pykka/_envelope.py pykka/_exceptions.py pykka/_future.py pykka/_proxy.py pykka/_ref.py pykka/_registry.py pykka/_threading.py pykka/debug.py pykka/eventlet.py pykka/gevent.py pykka/messages.py pykka/_compat/__init__.py pykka/_compat/await_py3.py tests/__init__.py tests/conftest.py tests/log_handler.py tests/performance.py tests/test_actor.py tests/test_envelope.py tests/test_future.py tests/test_future_py3.py tests/test_logging.py tests/test_messages.py tests/test_ref.py tests/test_registry.py tests/test_threading_actor.py tests/proxy/__init__.py tests/proxy/test_attribute_access.py tests/proxy/test_dynamic_method_calls.py tests/proxy/test_legacy_message_format.py tests/proxy/test_mocking.py tests/proxy/test_proxy.py tests/proxy/test_static_method_calls.py tests/proxy/test_traversable.pyPykka-2.0.2/Pykka.egg-info/dependency_links.txt0000664000175000017500000000000113571214203021615 0ustar jodaljodal00000000000000 Pykka-2.0.2/Pykka.egg-info/requires.txt0000664000175000017500000000017513571214203020152 0ustar jodaljodal00000000000000 [dev] black check-manifest flake8 flake8-import-order pytest pytest-cov pytest-mock sphinx sphinx_rtd_theme tox twine wheel Pykka-2.0.2/Pykka.egg-info/top_level.txt0000664000175000017500000000000613571214203020275 0ustar jodaljodal00000000000000pykka Pykka-2.0.2/README.rst0000664000175000017500000000270613547724426014512 0ustar jodaljodal00000000000000===== 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. For details and code examples, see the `Pykka documentation `_. Pykka is available from PyPI. To install it, run:: pip install pykka Pykka works with CPython 2.7 and 3.5+, as well as PyPy 2.7 and 3.5+. License ======= Pykka is copyright 2010-2019 Stein Magnus Jodal and contributors. Pykka is licensed under the `Apache License, Version 2.0 `_. Project resources ================= - `Documentation `_ - `Source code `_ - `Issue tracker `_ .. image:: https://img.shields.io/pypi/v/Pykka.svg :target: https://pypi.org/project/Pykka/ :alt: Latest PyPI version .. image:: https://img.shields.io/circleci/project/github/jodal/pykka/develop.svg :target: https://circleci.com/gh/jodal/pykka :alt: CircleCI build status .. image:: https://img.shields.io/readthedocs/pykka.svg :target: https://www.pykka.org/ :alt: Read the Docs build status .. image:: https://img.shields.io/codecov/c/github/jodal/pykka/develop.svg :target: https://codecov.io/gh/jodal/pykka :alt: Test coverage Pykka-2.0.2/docs/0000775000175000017500000000000013571214203013726 5ustar jodaljodal00000000000000Pykka-2.0.2/docs/Makefile0000644000175000017500000001073413077462072015403 0ustar jodaljodal00000000000000# 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." Pykka-2.0.2/docs/_static/0000775000175000017500000000000013571214203015354 5ustar jodaljodal00000000000000Pykka-2.0.2/docs/_static/.gitignore0000644000175000017500000000000013077462072017342 0ustar jodaljodal00000000000000Pykka-2.0.2/docs/api/0000775000175000017500000000000013571214203014477 5ustar jodaljodal00000000000000Pykka-2.0.2/docs/api/actors.rst0000664000175000017500000000015413547724426016544 0ustar jodaljodal00000000000000====== Actors ====== .. autoclass:: pykka.Actor :members: .. autoclass:: pykka.ActorRef :members: Pykka-2.0.2/docs/api/debug.rst0000664000175000017500000000725013547724426016343 0ustar jodaljodal00000000000000============= Debug helpers ============= .. automodule:: pykka.debug :members: Deadlock debugging ================== This is a complete example of how to use :func:`log_thread_tracebacks` to debug deadlocks: .. literalinclude:: ../../examples/deadlock_debugging.py Running the script outputs the following:: Setting up logging to get output from signal handler... Registering signal handler... Starting actors... DEBUG:pykka:Registered DeadlockActorA (urn:uuid:60803d09-cf5a-46cc-afdc-0c813e2e6647) DEBUG:pykka:Starting DeadlockActorA (urn:uuid:60803d09-cf5a-46cc-afdc-0c813e2e6647) DEBUG:pykka:Registered DeadlockActorB (urn:uuid:626adc83-ae35-439c-866a-85a3e29fd42c) DEBUG:pykka:Starting DeadlockActorB (urn:uuid:626adc83-ae35-439c-866a-85a3e29fd42c) Now doing something stupid that will deadlock the actors... DEBUG:root:This is foo calling bar DEBUG:root:This is bar calling foo; BOOM! Making main thread relax; not block, not quit 1) Use `kill -SIGUSR1 2284` to log thread tracebacks 2) Then `kill 2284` to terminate the process The two actors are now deadlocked waiting for each other while the main thread is idling, ready to process any signals. To debug the deadlock, send the ``SIGUSR1`` signal to the process, which has PID 2284 in this example:: kill -SIGUSR1 2284 This makes the main thread log the current traceback for each thread. The logging output shows that the two actors are both waiting for data from the other actor:: CRITICAL:pykka:Current state of DeadlockActorB-2 (ident: 140151493752576): File "/usr/lib/python3.6/threading.py", line 884, in _bootstrap self._bootstrap_inner() File "/usr/lib/python3.6/threading.py", line 916, in _bootstrap_inner self.run() File "/usr/lib/python3.6/threading.py", line 864, in run self._target(*self._args, **self._kwargs) File ".../pykka/actor.py", line 195, in _actor_loop response = self._handle_receive(message) File ".../pykka/actor.py", line 297, in _handle_receive return callee(*message['args'], **message['kwargs']) File "examples/deadlock_debugging.py", line 25, in bar return self.a.foo().get() File ".../pykka/threading.py", line 47, in get self._data = self._queue.get(True, timeout) File "/usr/lib/python3.6/queue.py", line 164, in get self.not_empty.wait() File "/usr/lib/python3.6/threading.py", line 295, in wait waiter.acquire() CRITICAL:pykka:Current state of DeadlockActorA-1 (ident: 140151572883200): File "/usr/lib/python3.6/threading.py", line 884, in _bootstrap self._bootstrap_inner() File "/usr/lib/python3.6/threading.py", line 916, in _bootstrap_inner self.run() File "/usr/lib/python3.6/threading.py", line 864, in run self._target(*self._args, **self._kwargs) File ".../pykka/actor.py", line 195, in _actor_loop response = self._handle_receive(message) File ".../pykka/actor.py", line 297, in _handle_receive return callee(*message['args'], **message['kwargs']) File "examples/deadlock_debugging.py", line 15, in foo return b.bar().get() File ".../pykka/threading.py", line 47, in get self._data = self._queue.get(True, timeout) File "/usr/lib/python3.6/queue.py", line 164, in get self.not_empty.wait() File "/usr/lib/python3.6/threading.py", line 295, in wait waiter.acquire() CRITICAL:pykka:Current state of MainThread (ident: 140151593330496): File ".../examples/deadlock_debugging.py", line 49, in time.sleep(1) File ".../pykka/debug.py", line 63, in log_thread_tracebacks stack = ''.join(traceback.format_stack(frame)) Pykka-2.0.2/docs/api/exceptions.rst0000664000175000017500000000015413547724426017432 0ustar jodaljodal00000000000000========== Exceptions ========== .. autoexception:: pykka.ActorDeadError .. autoexception:: pykka.Timeout Pykka-2.0.2/docs/api/futures.rst0000664000175000017500000000014413547724426016745 0ustar jodaljodal00000000000000======= Futures ======= .. autoclass:: pykka.Future :members: .. autofunction:: pykka.get_all Pykka-2.0.2/docs/api/index.rst0000664000175000017500000000037613547724426016366 0ustar jodaljodal00000000000000========= Pykka API ========= .. module:: pykka .. attribute:: __version__ Pykka's :pep:`386` and :pep:`396` compatible version number .. toctree:: actors proxies futures registry exceptions messages logging debug Pykka-2.0.2/docs/api/logging.rst0000664000175000017500000000607413547724426016706 0ustar jodaljodal00000000000000.. _logging: ======= Logging ======= Pykka uses Python's standard :mod:`logging` module for logging debug messages and any unhandled exceptions in the actors. All log messages emitted by Pykka are issued to the logger named ``pykka``, or a sub-logger of it. Log levels ========== Pykka logs at several different log levels, so that you can filter out the parts you're not interested in: :attr:`~logging.CRITICAL` (highest) This level is only used by the debug helpers in :mod:`pykka.debug`. :attr:`~logging.ERROR` Exceptions raised by an actor that are not captured into a reply future are logged at this level. :attr:`~logging.WARNING` Unhandled messages and other potential programming errors are logged at this level. :attr:`~logging.INFO` Exceptions raised by an actor that are captured into a reply future are logged at this level. If the future result is used elsewhere, the exceptions is reraised there too. If the future result isn't used, the log message is the only trace of the exception happening. To catch bugs earlier, it is recommended to show log messages this level during development. :attr:`~logging.DEBUG` (lowest) Every time an actor is started or stopped, and registered or unregistered in the actor registry, a message is logged at this level. In summary, you probably want to always let log messages at :attr:`~logging.WARNING` and higher through, while :attr:`~logging.INFO` should also be kept on during development. Log handlers ============ 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 messages from the library will be exposed to the application's users. In other words, if you want to see the log messages 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 :func:`logging.basicConfig` is enough to get debug log messages from Pykka:: import logging logging.basicConfig(level=logging.DEBUG) Recommended setup ================= 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 :attr:`~logging.INFO` level or higher:: import logging logging.basicConfig(level=logging.DEBUG) logging.getLogger('pykka').setLevel(logging.INFO) Given that you've fixed all unhandled exceptions logged at the :attr:`~logging.INFO` level during development, you probably want to disable logging from Pykka at the :attr:`~logging.INFO` level in production to avoid logging exceptions that are properly handled:: import logging logging.basicConfig(level=logging.DEBUG) logging.getLogger('pykka').setLevel(logging.WARNING) For more details on how to use :mod:`logging`, please refer to the Python standard library documentation. Pykka-2.0.2/docs/api/messages.rst0000664000175000017500000000014413547724426017057 0ustar jodaljodal00000000000000======== Messages ======== .. automodule:: pykka.messages :members: .. versionadded:: 2.0 Pykka-2.0.2/docs/api/proxies.rst0000664000175000017500000000027413547724426016745 0ustar jodaljodal00000000000000======= Proxies ======= .. autoclass:: pykka.ActorProxy :members: .. autoclass:: pykka.CallableProxy :members: .. automethod:: __call__ .. autofunction:: pykka.traversable Pykka-2.0.2/docs/api/registry.rst0000664000175000017500000000011513547724426017116 0ustar jodaljodal00000000000000======== Registry ======== .. autoclass:: pykka.ActorRegistry :members: Pykka-2.0.2/docs/changes.rst0000664000175000017500000004232413571214130016074 0ustar jodaljodal00000000000000======= Changes ======= v2.0.2 (2019-12-02) =================== Bugfix release. - Fix test suite run with pytest-mocker >= 1.11.2. (Fixes: :issue:`85`) v2.0.1 (2019-10-10) =================== Bugfix release. - Make :class:`~pykka.ActorRef` hashable. v2.0.0 (2019-05-07) =================== Major feature release. Dependencies ------------ - Drop support for Python 2.6, 3.2, 3.3, and 3.4. All have reached their end of life and do no longer receive security updates. - Include CPython 3.5, 3.6, 3.7, and 3.8 pre-releases, and PyPy 3.5 in the test matrix. - Include gevent and Eventlet tests in all environments. Since Pykka was originally developed, both has grown support for Python 3 and PyPy. - On Python 3, import :class:`Callable` and :class:`Iterable` from :mod:`collections.abc` instead of :mod:`collections`. This fixes a deprecation warning on Python 3.7 and prepares for Python 3.8. Actors ------ - Actor messages are no longer required to be ``dict`` objects. Any object type can be used as an actor message. (Fixes: :issue:`39`, :issue:`45`, PR: :issue:`79`) For existing code, this means that :meth:`~pykka.Actor.on_receive` implementations should no longer assume the received message to be a ``dict``, and guard with the appropriate amount of :func:`isinstance` checks. As an existing application will not observe any new message types before it starts using them itself, this is not marked as backwards incompatible. Proxies ------- - **Backwards incompatible:** Avoid accessing actor properties when creating a proxy for the actor. For properties with side effects, this is a major bug fix. For properties which does heavy work, this is a major startup performance improvement. This is backwards incompatible if you in a property getter returned an object instance with the ``pykka_traversable`` marker. Previously, this would work just like a traversable attribute. Now, the property always returns a future with the property getter's return value. - Fix infinite recursion when creating a proxy for an actor with an attribute or method replaced with a :class:`~unittest.mock.Mock` without a ``spec`` defined. (Fixes: :issue:`26`, :issue:`27`) - Fix infinite recursion when creating a proxy for an actor with an attribute that was itself a proxy to the same actor. The attribute will now be ignored and a warning log message will ask you to consider making the self-proxy private. (Fixes: :issue:`48`) - Add :meth:`~pykka.CallableProxy.defer` to support method calls through a proxy with :meth:`~pykka.ActorRef.tell` semantics. (Contributed by Andrey Gubarev. Fixes: :issue:`63`. PR: :issue:`72`) - Add :func:`~pykka.traversable` for marking an actor's attributes as traversable when used through actor proxies. The old way of manually adding a ``pykka_traversable`` attribute to the object to be traversed still works, but the new function is recommended as it provides protection against typos in the marker name, and keeps the traversable marking in the actor class itself. (PR: :issue:`81`) Futures ------- - **Backwards incompatible:** :meth:`pykka.Future.set_exception` no longer accepts an exception instance, which was deprecated in 0.15. The method can be called with either an ``exc_info`` tuple or :class:`None`, in which case it will use :func:`sys.exc_info` to get information on the current exception. - **Backwards incompatible:** :meth:`pykka.Future.map` on a future with an iterable result no longer applies the map function to each item in iterable. Instead, the entire future result is passed to the map function. (Fixes: :issue:`64`) To upgrade existing code, make sure to explicitly apply the core of your map function to each item in the iterable:: >>> f = pykka.ThreadingFuture() >>> f.set([1, 2, 3]) >>> f.map(lambda x: x + 1).get() # Pykka < 2.0 [2, 3, 4] >>> f.map(lambda x: [i + 1 for i in x]).get() # Pykka >= 2.0 [2, 3, 4] This change makes it easy to use :meth:`~pykka.Future.map` to extract a field from a future that returns a dict:: >>> f = pykka.ThreadingFuture() >>> f.set({'foo': 'bar'}) >>> f.map(lambda x: x['foo']).get() 'bar' Because dict is an iterable, the now removed special handling of iterables made this pattern difficult to use. - Reuse result from :meth:`pykka.Future.filter`, :meth:`pykka.Future.map`, and :meth:`pykka.Future.reduce`. Recalculating the result on each call to :meth:`pykka.Future.get` is both inconsistent with regular futures and can cause problems if the function is expensive or has side effects. (Fixes: :issue:`32`) - If using Python 3.5+, one can now use the ``await`` keyword to get the result from a future. (Contributed by Joshua Doncaster-Marsiglio. PR: :issue:`78`) Logging ------- - Pykka's use of different log levels has been :ref:`documented `. - Exceptions raised by an actor that are captured into a reply future are now logged on the :attr:`~logging.INFO` level instead of the :attr:`~logging.DEBUG` level. This makes it possible to detect potentially unhandled exceptions during development without having to turn on debug logging, which can have a low signal to noise ratio. (Contributed by Stefan Möhl. Fixes: :issue:`73`) Gevent support -------------- - Ensure that the original traceback is preserved when an exception is returned through a future from a Gevent actor. (Contributed by Arne Brutschy. Fixes: :issue:`74`, PR: :issue:`75`) Internals --------- - **Backwards incompatible:** Prefix all internal modules with ``_``. This is backwards incompatible if you have imported objects from other import paths than what is used in the documentation. - Port tests to pytest. - Format code with Black. - Change internal messaging format from ``dict`` to ``namedtuple``. (PR: :issue:`80`) 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 registered 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().__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().__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 traversable 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 compatibility. Pykka-2.0.2/docs/conf.py0000664000175000017500000000506513547724426015253 0ustar jodaljodal00000000000000# 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-2019, Stein Magnus Jodal and contributors' 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 = 'sphinx_rtd_theme' 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': ('https://docs.python.org/3', None)} Pykka-2.0.2/docs/examples.rst0000664000175000017500000001064313547724426016322 0ustar jodaljodal00000000000000======== Examples ======== The ``examples/`` dir in `Pykka's Git repo `_ includes some runnable examples of Pykka usage. Plain actor =========== .. literalinclude:: ../examples/plain_actor.py Output:: [{'no': 'Norway', 'se': 'Sweden'}, {'a': 3, 'b': 4, 'c': 5}] Actor with proxy ================ .. literalinclude:: ../examples/typed_actor.py Output:: MainThread: calling AnActor.proc() ... MainThread: calling AnActor.func() ... MainThread: printing result ... (blocking) AnActor-1: this was printed by AnActor.proc() MainThread: this was returned by AnActor.func() after a delay MainThread: reading AnActor.field ... MainThread: printing result ... (blocking) MainThread: this is the value of AnActor.field MainThread: writing AnActor.field ... MainThread: printing new field value ... (blocking) MainThread: new value MainThread: calling AnActor.proc() ... MainThread: calling AnActor.func() ... MainThread: printing result ... (blocking) AnActor-1: this was printed by AnActor.proc() MainThread: this was returned by AnActor.func() after a delay MainThread: reading AnActor.field ... MainThread: printing result ... (blocking) MainThread: new value MainThread: writing AnActor.field ... MainThread: printing new field value ... (blocking) MainThread: new value MainThread: calling AnActor.proc() ... MainThread: calling AnActor.func() ... AnActor-1: this was printed by AnActor.proc() MainThread: printing result ... (blocking) MainThread: this was returned by AnActor.func() after a delay MainThread: reading AnActor.field ... MainThread: printing result ... (blocking) MainThread: new value MainThread: writing AnActor.field ... MainThread: printing new field value ... (blocking) MainThread: new value Multiple cooperating actors =========================== .. literalinclude:: ../examples/counter.py Output:: Adder (urn:uuid:f50029eb-7cea-4ab9-98bf-a5bf65af8b8f) is increasing 0 Bookkeeper (urn:uuid:4f2d4e78-7a33-4c4f-86ac-7c415a7205f4) got 1 back Adder (urn:uuid:f50029eb-7cea-4ab9-98bf-a5bf65af8b8f) is increasing 1 Bookkeeper (urn:uuid:4f2d4e78-7a33-4c4f-86ac-7c415a7205f4) got 2 back Adder (urn:uuid:f50029eb-7cea-4ab9-98bf-a5bf65af8b8f) is increasing 2 Bookkeeper (urn:uuid:4f2d4e78-7a33-4c4f-86ac-7c415a7205f4) got 3 back Adder (urn:uuid:f50029eb-7cea-4ab9-98bf-a5bf65af8b8f) is increasing 3 Bookkeeper (urn:uuid:4f2d4e78-7a33-4c4f-86ac-7c415a7205f4) got 4 back Adder (urn:uuid:f50029eb-7cea-4ab9-98bf-a5bf65af8b8f) is increasing 4 Bookkeeper (urn:uuid:4f2d4e78-7a33-4c4f-86ac-7c415a7205f4) got 5 back Adder (urn:uuid:f50029eb-7cea-4ab9-98bf-a5bf65af8b8f) is increasing 5 Bookkeeper (urn:uuid:4f2d4e78-7a33-4c4f-86ac-7c415a7205f4) got 6 back Adder (urn:uuid:f50029eb-7cea-4ab9-98bf-a5bf65af8b8f) is increasing 6 Bookkeeper (urn:uuid:4f2d4e78-7a33-4c4f-86ac-7c415a7205f4) got 7 back Adder (urn:uuid:f50029eb-7cea-4ab9-98bf-a5bf65af8b8f) is increasing 7 Bookkeeper (urn:uuid:4f2d4e78-7a33-4c4f-86ac-7c415a7205f4) got 8 back Adder (urn:uuid:f50029eb-7cea-4ab9-98bf-a5bf65af8b8f) is increasing 8 Bookkeeper (urn:uuid:4f2d4e78-7a33-4c4f-86ac-7c415a7205f4) got 9 back Adder (urn:uuid:f50029eb-7cea-4ab9-98bf-a5bf65af8b8f) is increasing 9 Bookkeeper (urn:uuid:4f2d4e78-7a33-4c4f-86ac-7c415a7205f4) got 10 back Pool of actors sharing work =========================== .. literalinclude:: ../examples/resolver.py Mopidy music server =================== Pykka was originally created back in 2011 as a formalization of concurrency patterns that emerged in the `Mopidy music server `_. The original Pykka source code wasn't extracted from Mopidy, but it built and improved on the concepts from Mopidy. Mopidy was later ported to build on Pykka instead of its own concurrency abstractions. Mopidy still use Pykka extensively to keep independent parts, like the MPD and HTTP frontend servers or the Spotify and Google Music integrations, running independently. Every one of Mopidy's more than 100 extensions has at least one Pykka actor. By running each extension as an independent actor, errors and bugs in one extension is attempted isolated, to reduce the effect on the rest of the system. You can browse the `Mopidy source code `_ to find many real life examples of Pykka usage. Pykka-2.0.2/docs/index.rst0000664000175000017500000000133613547724426015612 0ustar jodaljodal00000000000000===== 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. For details and code examples, see the `Pykka documentation `_. Pykka is available from PyPI. To install it, run:: pip install pykka Pykka works with CPython 2.7 and 3.5+, as well as PyPy 2.7 and 3.5+. .. toctree:: :maxdepth: 2 :caption: Usage quickstart examples api/index runtimes/index testing .. toctree:: :maxdepth: 2 :caption: About inspiration changes Pykka-2.0.2/docs/inspiration.rst0000664000175000017500000000116513547724426017042 0ustar jodaljodal00000000000000=========== Inspiration =========== 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. What Pykka is not ================= 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. Pykka-2.0.2/docs/quickstart.rst0000664000175000017500000002460713547724426016703 0ustar jodaljodal00000000000000========== Quickstart ========== 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 :class:`~pykka.ThreadingActor` is executed by a regular thread, i.e. :class:`threading.Thread`. As handles for future results, it uses :class:`~pykka.ThreadingFuture` which is a thin wrapper around a :class:`queue.Queue`. It has no dependencies outside Python itself. :class:`~pykka.ThreadingActor` plays well together with non-actor threads. Note: If you monkey patch the standard library with ``gevent`` or ``eventlet`` you can still use :class:`~pykka.ThreadingActor` and :class:`~pykka.ThreadingFuture`. Python's threads will transparently use the underlying implementation provided by gevent or Eventlet. - gevent: Each :class:`~pykka.gevent.GeventActor` is executed by a gevent greenlet. `gevent `_ is a coroutine-based Python networking library built on top of libev event loop. :class:`~pykka.gevent.GeventActor` is generally faster than :class:`~pykka.ThreadingActor`. - Eventlet: Each :class:`~pykka.eventlet.EventletActor` is executed by an `Eventlet `_ greenlet. Pykka has an extensive test suite, and is tested on CPython 2.7, and 3.5+, as well as PyPy. A basic actor ============= In its most basic form, a Pykka actor is a class with an :meth:`~pykka.Actor.on_receive` method:: import pykka class Greeter(pykka.ThreadingActor): def on_receive(self, message): print('Hi there!') To start an actor, you call the class' method :meth:`~pykka.Actor.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 :meth:`~pykka.Actor.start` method, and receive them using the regular ``__init__()`` method:: import pykka class Greeter(pykka.ThreadingActor): def __init__(self, greeting='Hi there!'): super().__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 :meth:`~pykka.ActorRef.stop` on the :class:`~pykka.ActorRef`:: 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 :meth:`~pykka.ActorRef.tell` method or the :meth:`~pykka.ActorRef.ask` method on the ``actor_ref`` object. :meth:`~pykka.ActorRef.tell` will fire off a message without waiting for an answer. In other words, it will never block. :meth:`~pykka.ActorRef.ask` will by default block until an answer is returned, potentially forever. If you provide a ``timeout`` keyword argument to :meth:`~pykka.ActorRef.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 :meth:`~pykka.ActorRef.ask` will immediately return a "future" object. The message itself can be of any type, for example a dict or your own message class type. Summarized in code:: actor_ref.tell('Hi!') # => Returns nothing. Will never block. answer = actor_ref.ask('Hi?') # => May block forever waiting for an answer answer = actor_ref.ask('Hi?', timeout=3) # => May wait 3s for an answer, then raises exception if no answer. future = actor_ref.ask('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. .. warning:: For performance reasons, Pykka **does not** clone the message you send before delivering it to the receiver. You are yourself responsible for either using immutable data structures or to :func:`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 the :meth:`~pykka.Actor.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('Hi?') print(answer) # => 'Hi there!' :class:`None` is a valid response so if you return :class:`None` explicitly, or don't return at all, a response containing :class:`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 :meth:`~pykka.ActorRef.tell` or :meth:`~pykka.ActorRef.ask`. When the sender doesn't expect a response the :meth:`~pykka.Actor.on_receive` return value will be ignored. The situation is similar in regard to exceptions: when :meth:`~pykka.ActorRef.ask` is used and you raise an exception from within :meth:`~pykka.Actor.on_receive` method, the exception 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('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 building `Mopidy `_. Let's create an actor and start it:: import pykka class Calculator(pykka.ThreadingActor): def __init__(self): super().__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 :meth:`~pykka.Future.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 :meth:`~pykka.Future.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 :meth:`~pykka.ActorRef.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 mark it with the :func:`~pykka.traversable` function:: import pykka class AnActor(pykka.ThreadingActor): playback = pykka.traversable(Playback()) class Playback(object): 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 are marked as traversable. Pykka-2.0.2/docs/runtimes/0000775000175000017500000000000013571214203015574 5ustar jodaljodal00000000000000Pykka-2.0.2/docs/runtimes/eventlet.rst0000664000175000017500000000117213547724426020175 0ustar jodaljodal00000000000000======== eventlet ======== Installation ============ To run Pykka on top of `eventlet `_, you first need to install the `eventlet package `_ from PyPI:: pip install eventlet Code changes ============ Next, all actors must subclass :class:`pykka.eventlet.EventletActor` instead of :class:`pykka.ThreadingActor`. If you create any futures yourself, you must replace :class:`pykka.ThreadingFuture` with :class:`pykka.eventlet.EventletFuture`. With those changes in place, Pykka should run on top of eventlet. API === .. automodule:: pykka.eventlet :members: Pykka-2.0.2/docs/runtimes/gevent.rst0000664000175000017500000000114113547724426017633 0ustar jodaljodal00000000000000====== gevent ====== Installation ============ To run Pykka on top of `gevent `_, you first need to install the `gevent package `_ from PyPI:: pip install gevent Code changes ============ Next, all actors must subclass :class:`pykka.gevent.GeventActor` instead of :class:`pykka.ThreadingActor`. If you create any futures yourself, you must replace :class:`pykka.ThreadingFuture` with :class:`pykka.gevent.GeventFuture`. With those changes in place, Pykka should run on top of gevent. API === .. automodule:: pykka.gevent :members: Pykka-2.0.2/docs/runtimes/index.rst0000664000175000017500000000071113547724426017454 0ustar jodaljodal00000000000000======== Runtimes ======== By default, Pykka builds on top of Python's regular threading concurrency model, via the standard library modules :mod:`threading` and :mod:`queue`. Alternatively, you may run Pykka on top of :mod:`gevent` or :mod:`eventlet`. Note that Pykka does no attempt at supporting a mix of concurrency runtimes. Such a future feature has briefly been discussed in issue :issue:`11`. .. toctree:: threading gevent eventlet Pykka-2.0.2/docs/runtimes/threading.rst0000664000175000017500000000042713547724426020316 0ustar jodaljodal00000000000000========= Threading ========= Installation ============ The default threading runtime has no dependencies other than Pykka itself and the Python standard library. API === .. autoclass:: pykka.ThreadingFuture :members: .. autoclass:: pykka.ThreadingActor :members: Pykka-2.0.2/docs/testing.rst0000664000175000017500000000233413547724426016157 0ustar jodaljodal00000000000000======= Testing ======= Pykka actors can be tested using the regular Python testing tools like `pytest `_, :mod:`unittest`, and :mod:`unittest.mock`. To test actors in a setting as close to production as possible, a typical pattern is the following: 1. In the test setup, start an actor together with any actors/collaborators it depends on. The dependencies will often be replaced by mocks to control their behavior. 2. In the test, :meth:`~pykka.ActorRef.ask` or :meth:`~pykka.ActorRef.tell` the actor something. 3. In the test, assert on the actor's state or the return value from the :meth:`~pykka.ActorRef.ask`. 4. In the test teardown, stop the actor to properly clean up before the next test. An example ========== Let's look at an example actor that we want to test: .. literalinclude:: ../examples/producer.py We can test this actor with `pytest`_ by mocking the consumer and asserting that it receives a newly produced item: .. literalinclude:: ../examples/producer_test.py If this way of setting up and tearing down test resources is unfamiliar to you, it is strongly recommended to read up on pytest's great `fixture `_ feature. Pykka-2.0.2/examples/0000775000175000017500000000000013571214203014614 5ustar jodaljodal00000000000000Pykka-2.0.2/examples/counter.py0000775000175000017500000000116713547724426016675 0ustar jodaljodal00000000000000#!/usr/bin/env python3 import pykka class Adder(pykka.ThreadingActor): def add_one(self, i): print(f'{self} is increasing {i}') return i + 1 class Bookkeeper(pykka.ThreadingActor): def __init__(self, adder): super().__init__() self.adder = adder def count_to(self, target): i = 0 while i < target: i = self.adder.add_one(i).get() print(f'{self} got {i} back') if __name__ == '__main__': adder = Adder.start().proxy() bookkeeper = Bookkeeper.start(adder).proxy() bookkeeper.count_to(10).get() pykka.ActorRegistry.stop_all() Pykka-2.0.2/examples/deadlock_debugging.py0000775000175000017500000000240513547724426020773 0ustar jodaljodal00000000000000#!/usr/bin/env python3 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().__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 pid = os.getpid() print('Making main thread relax; not block, not quit') print('1) Use `kill -SIGUSR1 {:d}` to log thread tracebacks'.format(pid)) print('2) Then `kill {:d}` to terminate the process'.format(pid)) while True: time.sleep(1) Pykka-2.0.2/examples/plain_actor.py0000775000175000017500000000106413547724426017505 0ustar jodaljodal00000000000000#!/usr/bin/env python3 import pykka GetMessages = object() class PlainActor(pykka.ThreadingActor): def __init__(self): super().__init__() self.stored_messages = [] def on_receive(self, message): if message is GetMessages: 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(GetMessages)) actor.stop() Pykka-2.0.2/examples/producer.py0000664000175000017500000000042713547724426017034 0ustar jodaljodal00000000000000import pykka class ProducerActor(pykka.ThreadingActor): def __init__(self, consumer): super(ProducerActor, self).__init__() self.consumer = consumer def produce(self): new_item = {'item': 1, 'new': True} self.consumer.consume(new_item) Pykka-2.0.2/examples/producer_test.py0000664000175000017500000000147613547724426020100 0ustar jodaljodal00000000000000from producer import ProducerActor import pytest @pytest.fixture def consumer_mock(mocker): yield mocker.Mock() @pytest.fixture def producer(consumer_mock): # Step 1: The actor under test is wired up with # its dependencies and is started. proxy = ProducerActor.start(consumer_mock).proxy() yield proxy # Step 4: The actor is stopped to clean up before the next test. proxy.stop() def test_producer_actor(consumer_mock, producer): # Step 2: Interact with the actor. # We call .get() on the last future returned by the actor to wait # for the actor to process all messages before asserting anything. producer.produce().get() # Step 3: Assert that the return values or actor state is as expected. consumer_mock.consume.assert_called_once_with({'item': 1, 'new': True}) Pykka-2.0.2/examples/resolver.py0000775000175000017500000000243713547724426017060 0ustar jodaljodal00000000000000#!/usr/bin/env python3 """ 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 193.35.52.{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(f'Finished resolving {ip}') return info[0] except Exception: print(f'Failed resolving {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 = [f'193.35.52.{i}' for i in range(1, 50)] run(10, *ips) Pykka-2.0.2/examples/typed_actor.py0000775000175000017500000000263513547724426017534 0ustar jodaljodal00000000000000#!/usr/bin/env python3 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(msg): thread_name = threading.current_thread().name print(f'{thread_name}: {msg}') 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() Pykka-2.0.2/pykka/0000775000175000017500000000000013571214203014115 5ustar jodaljodal00000000000000Pykka-2.0.2/pykka/__init__.py0000664000175000017500000000140413571214106016227 0ustar jodaljodal00000000000000import logging as _logging from pykka._exceptions import ActorDeadError, Timeout from pykka._future import Future, get_all from pykka._proxy import ActorProxy, CallableProxy, traversable from pykka._ref import ActorRef from pykka._registry import ActorRegistry from pykka._actor import Actor # noqa: Must be imported late from pykka._threading import ThreadingActor, ThreadingFuture __all__ = [ 'Actor', 'ActorDeadError', 'ActorProxy', 'ActorRef', 'ActorRegistry', 'CallableProxy', 'Future', 'ThreadingActor', 'ThreadingFuture', 'Timeout', 'get_all', 'traversable', ] #: Pykka's :pep:`396` and :pep:`440` compatible version number __version__ = '2.0.2' _logging.getLogger('pykka').addHandler(_logging.NullHandler()) Pykka-2.0.2/pykka/_actor.py0000664000175000017500000003014413547724426015760 0ustar jodaljodal00000000000000from __future__ import absolute_import import logging import sys import threading import uuid from pykka import ActorDeadError, ActorRef, ActorRegistry, messages __all__ = ['Actor'] logger = logging.getLogger('pykka') class Actor(object): """ To create an actor: 1. subclass one of the :class:`Actor` implementations: - :class:`~pykka.ThreadingActor` - :class:`~pykka.gevent.GeventActor` - :class:`~pykka.eventlet.EventletActor` 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().__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 {}'.format(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().__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 '{} ({})'.format(self.__class__.__name__, self.actor_urn) def stop(self): """ Stop the actor. It's equivalent to calling :meth:`ActorRef.stop` with ``block=False``. """ self.actor_ref.tell(messages._ActorStop()) 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 {}'.format(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(): envelope = self.actor_inbox.get() try: response = self._handle_receive(envelope.message) if envelope.reply_to is not None: envelope.reply_to.set(response) except Exception: if envelope.reply_to is not None: logger.info( 'Exception returned from {} to caller:'.format(self), exc_info=sys.exc_info(), ) envelope.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( '{!r} in {}. Stopping all actors.'.format( exception_value, self ) ) self._stop() ActorRegistry.stop_all() while not self.actor_inbox.empty(): envelope = self.actor_inbox.get() if envelope.reply_to is not None: if isinstance(envelope.message, messages._ActorStop): envelope.reply_to.set(None) else: envelope.reply_to.set_exception( exc_info=( ActorDeadError, ActorDeadError( '{} stopped before handling the message'.format( self.actor_ref ) ), None, ) ) 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 {}:'.format(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.""" message = messages._upgrade_internal_message(message) if isinstance(message, messages._ActorStop): return self._stop() if isinstance(message, messages.ProxyCall): callee = self._get_attribute_from_path(message.attr_path) return callee(*message.args, **message.kwargs) if isinstance(message, messages.ProxyGetAttr): attr = self._get_attribute_from_path(message.attr_path) return attr if isinstance(message, messages.ProxySetAttr): 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. :param message: the message to handle :type message: any :returns: anything that should be sent as a reply to the sender """ logger.warning( 'Unexpected message received by {}: {}'.format(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 def _introspect_attribute_from_path(self, attr_path): """Get attribute information from ``__dict__`` on the container.""" if not attr_path: return self parent = self._get_attribute_from_path(attr_path[:-1]) parent_attrs = self._introspect_attributes(parent) attr_name = attr_path[-1] try: return parent_attrs[attr_name] except KeyError: raise AttributeError( 'type object {!r} has no attribute {!r}'.format( parent.__class__.__name__, attr_name ) ) def _introspect_attributes(self, obj): """Combine ``__dict__`` from ``obj`` and all its superclasses.""" result = {} for cls in reversed(obj.__class__.mro()): result.update(cls.__dict__) if hasattr(obj, '__dict__'): result.update(obj.__dict__) return result Pykka-2.0.2/pykka/_compat/0000775000175000017500000000000013571214203015537 5ustar jodaljodal00000000000000Pykka-2.0.2/pykka/_compat/__init__.py0000664000175000017500000000154613547724426017676 0ustar jodaljodal00000000000000import sys PY2 = sys.version_info[0] == 2 if PY2: import Queue as queue # noqa from collections import Callable, Iterable # noqa string_types = basestring # noqa def reraise(tp, value, tb=None): exec('raise tp, value, tb') await_dunder_future = None await_keyword = None else: import queue # noqa from collections.abc import Callable, Iterable # 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 # `async def` and return inside a generator are syntax errors on Python 2 # so these must be hidden behind a conditional import. from pykka._compat.await_py3 import ( # noqa await_dunder_future, await_keyword, ) Pykka-2.0.2/pykka/_compat/await_py3.py0000664000175000017500000000020613547724426020027 0ustar jodaljodal00000000000000def await_dunder_future(self): yield value = self.get() return value async def await_keyword(val): return await val Pykka-2.0.2/pykka/_envelope.py0000664000175000017500000000123513547724426016464 0ustar jodaljodal00000000000000class Envelope(object): """ Envelope to add metadata to a message. This is an internal type and is not part of the public API. :param message: the message to send :type message: any :param reply_to: the future to reply to if there is a response :type reply_to: :class:`pykka.Future` """ # Using slots speeds up envelope creation with ~20% __slots__ = ['message', 'reply_to'] def __init__(self, message, reply_to=None): self.message = message self.reply_to = reply_to def __repr__(self): return 'Envelope(message={!r}, reply_to={!r})'.format( self.message, self.reply_to ) Pykka-2.0.2/pykka/_exceptions.py0000664000175000017500000000036413547724426017032 0ustar jodaljodal00000000000000__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 Pykka-2.0.2/pykka/_future.py0000664000175000017500000002062013547724426016160 0ustar jodaljodal00000000000000import functools from pykka import _compat __all__ = ['Future', 'get_all'] class Future(object): """ A :class:`Future` is a handle to a value which is 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` or, if using Python 3.5+, ``await`` the future. """ def __init__(self): super(Future, self).__init__() self._get_hook = None self._get_hook_result = 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: if self._get_hook_result is None: self._get_hook_result = self._get_hook(timeout) return self._get_hook_result raise NotImplementedError def set(self, value=None): """ Set the encapsulated value. :param value: the encapsulated value or nothing :type value: any 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. :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. 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['foo']) >>> f.set({'foo': 'bar'}}) >>> g.get() 'bar' .. versionadded:: 1.2 .. versionchanged:: 2.0 Previously, if the future's result was an iterable (except a string), the function was applied to each item in the iterable. This behavior is unpredictable and makes regular use cases like extracting a single field from a dict difficult, thus the behavior has been simplified. Now, the entire result value is passed to the function. """ future = self.__class__() future.set_get_hook(lambda timeout: 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 __await__ = _compat.await_dunder_future __iter__ = __await__ 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] Pykka-2.0.2/pykka/_proxy.py0000664000175000017500000002724213547724432016033 0ustar jodaljodal00000000000000from __future__ import absolute_import import logging from pykka import ActorDeadError, _compat, messages __all__ = ['ActorProxy'] logger = logging.getLogger('pykka') 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() **Attributes and method calls** 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() If you're using Python 3.5+, you can also use the ``await`` keyword to block until the method completes:: await actor_proxy.method_with_side_effect() If you access a proxied method as an attribute, without calling it, you get an :class:`CallableProxy`. **Proxy to itself** 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): ... To avoid infinite loops during proxy introspection, proxies to self should be kept as private instance attributes by prefixing the attribute name with ``_``. **Examples** 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('{} not found'.format(actor_ref)) self.actor_ref = actor_ref self._actor = actor_ref._actor self._attr_path = attr_path or tuple() self._known_attrs = self._introspect_attributes() self._actor_proxies = {} self._callable_proxies = {} def _introspect_attributes(self): """Introspects the actor's attributes.""" 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 not self._is_exposable_attribute(attr_path[-1]): continue attr = self._actor._introspect_attribute_from_path(attr_path) if self._is_self_proxy(attr): logger.warning( ( '{} attribute {!r} is a proxy to itself. ' 'Consider making it private by renaming it to {!r}.' ).format( self._actor, '.'.join(attr_path), '_' + attr_path[-1] ) ) continue traversable = self._is_traversable_attribute(attr) result[tuple(attr_path)] = { 'callable': self._is_callable_attribute(attr), 'traversable': traversable, } if traversable: 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_self_proxy(self, attr): """Returns true if attribute is an equivalent actor proxy.""" return attr == self def _is_callable_attribute(self, attr): """Returns true for any attribute that is callable.""" return isinstance(attr, _compat.Callable) def _is_traversable_attribute(self, attr): """ Returns true for any attribute that may be traversed from another actor through a proxy. """ return ( getattr(attr, '_pykka_traversable', False) is True or getattr(attr, 'pykka_traversable', False) is True ) def __eq__(self, other): if not isinstance(other, ActorProxy): return False if self._actor != other._actor: return False if self._attr_path != other._attr_path: return False return True def __hash__(self): return hash((self._actor, self._attr_path)) def __repr__(self): return ''.format( 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._introspect_attributes() attr_info = self._known_attrs.get(attr_path) if attr_info is None: raise AttributeError('{} has no attribute {!r}'.format(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 = messages.ProxyGetAttr(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 = messages.ProxySetAttr(attr_path=attr_path, value=value) return self.actor_ref.ask(message) class CallableProxy(object): """Proxy to a single method. :class:`CallableProxy` instances are returned when accessing methods on a :class:`ActorProxy` without calling them. Example:: proxy = AnActor.start().proxy() # Ask semantics returns a future. See `__call__()` docs. future = proxy.do_work() # Tell semantics are fire and forget. See `defer()` docs. proxy.do_work.defer() """ def __init__(self, actor_ref, attr_path): self.actor_ref = actor_ref self._attr_path = attr_path def __call__(self, *args, **kwargs): """Call with :meth:`~pykka.ActorRef.ask` semantics. Returns a future which will yield the called method's return value. If the call raises an exception is set on the future, and will be reraised by :meth:`~pykka.Future.get`. If the future is left unused, the exception will not be reraised. Either way, the exception will also be logged. See :ref:`logging` for details. """ message = messages.ProxyCall( attr_path=self._attr_path, args=args, kwargs=kwargs ) return self.actor_ref.ask(message, block=False) def defer(self, *args, **kwargs): """Call with :meth:`~pykka.ActorRef.tell` semantics. Does not create or return a future. If the call raises an exception, there is no future to set the exception on. Thus, the actor's :meth:`~pykka.Actor.on_failure` hook is called instead. .. versionadded:: 2.0 """ message = messages.ProxyCall( attr_path=self._attr_path, args=args, kwargs=kwargs ) return self.actor_ref.tell(message) def traversable(obj): """Marks an actor attribute as traversable. The traversable marker makes the actor attribute's own methods and attributes available to users of the actor through an :class:`~pykka.ActorProxy`. Used as a function to mark a single attribute:: class AnActor(pykka.ThreadingActor): playback = pykka.traversable(Playback()) class Playback(object): def play(self): return True This function can also be used as a class decorator, making all instances of the class traversable:: class AnActor(pykka.ThreadingActor): playback = Playback() @pykka.traversable class Playback(object): def play(self): return True The third alternative, and the only way in Pykka < 2.0, is to manually mark a class as traversable by setting the ``pykka_traversable`` attribute to :class:`True`:: class AnActor(pykka.ThreadingActor): playback = Playback() class Playback(object): pykka_traversable = True def play(self): return True When the attribute is marked as traversable, its methods can be executed in the context of the actor through an actor proxy:: proxy = AnActor.start().proxy() assert proxy.playback.play().get() is True .. versionadded:: 2.0 """ if hasattr(obj, '__slots__'): raise Exception( 'pykka.traversable() cannot be used to mark ' 'an object using slots as traversable.' ) obj._pykka_traversable = True return obj Pykka-2.0.2/pykka/_ref.py0000664000175000017500000001245713547724426015433 0ustar jodaljodal00000000000000from pykka import ActorDeadError, ActorProxy from pykka._envelope import Envelope from pykka.messages import _ActorStop __all__ = ['ActorRef'] 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 ''.format(self) def __str__(self): return '{} ({})'.format(self.actor_class.__name__, self.actor_urn) 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: any :raise: :exc:`pykka.ActorDeadError` if actor is not available :return: nothing """ if not self.is_alive(): raise ActorDeadError('{} not found'.format(self)) self.actor_inbox.put(Envelope(message)) def ask(self, message, block=True, timeout=None): """ Send message to actor and wait for the reply. The message can be of any type. 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: any :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() try: if not self.is_alive(): raise ActorDeadError('{} not found'.format(self)) except ActorDeadError: future.set_exception() else: self.actor_inbox.put(Envelope(message, reply_to=future)) 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(_ActorStop(), 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) Pykka-2.0.2/pykka/_registry.py0000664000175000017500000001232713547724426016523 0ustar jodaljodal00000000000000from __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: any :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 {}'.format(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 {}'.format(actor_ref)) else: logger.debug( 'Unregistered {} (not found in registry)'.format(actor_ref) ) Pykka-2.0.2/pykka/_threading.py0000664000175000017500000000626013547724426016617 0ustar jodaljodal00000000000000from __future__ import absolute_import import sys import threading from pykka import Actor, Future, Timeout, _compat __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('{} seconds'.format(timeout)) def set(self, value=None): self._queue.put({'value': value}, block=False) def set_exception(self, exc_info=None): assert exc_info is None or len(exc_info) == 3 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() Pykka-2.0.2/pykka/debug.py0000664000175000017500000000432613547724426015602 0ustar jodaljodal00000000000000from __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 :attr:`logging.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. .. 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 {} (ident: {}):\n{}'.format(name, ident, stack) ) Pykka-2.0.2/pykka/eventlet.py0000664000175000017500000000501413547724426016335 0ustar jodaljodal00000000000000from __future__ import absolute_import import sys import eventlet import eventlet.event import eventlet.queue from pykka import Actor, Future, Timeout __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): assert exc_info is None or len(exc_info) == 3 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) Pykka-2.0.2/pykka/gevent.py0000664000175000017500000000376413547724426016011 0ustar jodaljodal00000000000000from __future__ import absolute_import import sys import gevent import gevent.event import gevent.queue from pykka import Actor, Future, Timeout __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 None: async_result = gevent.event.AsyncResult() self.async_result = async_result 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): assert exc_info is None or len(exc_info) == 3 exc_info = exc_info or sys.exc_info() self.async_result.set_exception(exc_info[1], exc_info=exc_info) 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. """ @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) Pykka-2.0.2/pykka/messages.py0000664000175000017500000000500213547724426016313 0ustar jodaljodal00000000000000""" The :mod:`pykka.messages` module contains Pykka's own actor messages. In general, you should not need to use any of these classes. However, they have been made part of the public API so that certain optimizations can be done without touching Pykka's internals. An example is to combine :meth:`~pykka.ActorRef.ask` and :class:`ProxyCall` to call a method on an actor without having to spend any resources on creating a proxy object:: reply = actor_ref.ask( ProxyCall( attr_path=['my_method'], args=['foo'], kwargs={'bar': 'baz'} ) ) Another example is to use :meth:`~pykka.ActorRef.tell` instead of :meth:`~pykka.ActorRef.ask` for the proxy method call, and thus avoid the creation of a future for the return value if you don't need it. It should be noted that these optimizations should only be necessary in very special circumstances. """ import warnings from collections import namedtuple # Internal actor messages _ActorStop = namedtuple('ActorStop', []) # Public proxy messages # TODO Add docstrings to classes and attributes once we drop Python 2.7 support ProxyCall = namedtuple('ProxyCall', ['attr_path', 'args', 'kwargs']) ProxyGetAttr = namedtuple('ProxyGetAttr', ['attr_path']) ProxySetAttr = namedtuple('ProxySetAttr', ['attr_path', 'value']) def _upgrade_internal_message(message): """Filter that upgrades dict-based internal messages to the new format. This is needed for a transitional period because Mopidy < 3 uses the old internal message format directly, and maybe others. """ if not isinstance(message, dict): return message if not message.get('command', '').startswith('pykka_'): return message warnings.warn( 'Pykka received a dict-based internal message. ' 'This is deprecated and will be unsupported in the future. ' 'Message: {!r}'.format(message), DeprecationWarning, ) command = message.get('command') if command == 'pykka_stop': return _ActorStop() elif command == 'pykka_call': return ProxyCall( attr_path=message['attr_path'], args=message['args'], kwargs=message['kwargs'], ) elif command == 'pykka_getattr': return ProxyGetAttr(attr_path=message['attr_path']) elif command == 'pykka_setattr': return ProxySetAttr( attr_path=message['attr_path'], value=message['value'] ) else: raise ValueError('Unknown internal message: {!r}'.format(message)) Pykka-2.0.2/pyproject.toml0000664000175000017500000000016213547724426015731 0ustar jodaljodal00000000000000[build-system] requires = ["setuptools", "wheel"] [tool.black] line-length = 80 skip-string-normalization = true Pykka-2.0.2/setup.cfg0000664000175000017500000000023513571214203014617 0ustar jodaljodal00000000000000[flake8] application-import-names = pykka,tests exclude = .git,.tox max-line-length = 80 [bdist_wheel] universal = 1 [egg_info] tag_build = tag_date = 0 Pykka-2.0.2/setup.py0000664000175000017500000000355113547724426014534 0ustar jodaljodal00000000000000import re from setuptools import find_packages, setup def get_version(): init_py = open('pykka/__init__.py').read() metadata = dict(re.findall("__([a-z]+)__ = '([^']+)'", init_py)) return metadata['version'] with open('README.rst') as fh: long_description = fh.read() setup( name='Pykka', version=get_version(), description='Pykka is a Python implementation of the actor model', long_description=long_description, url='https://www.pykka.org/', author='Stein Magnus Jodal', author_email='stein.magnus@jodal.no', license='Apache License, Version 2.0', classifiers=[ 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', 'Topic :: Software Development :: Libraries', 'License :: OSI Approved :: Apache Software License', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', ], packages=find_packages(exclude=['tests', 'tests.*']), python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*', extras_require={ 'dev': [ 'black', 'check-manifest', 'flake8', 'flake8-import-order', 'pytest', 'pytest-cov', 'pytest-mock', 'sphinx', 'sphinx_rtd_theme', 'tox', 'twine', 'wheel', ] }, project_urls={ 'Issues': 'https://github.com/jodal/pykka/issues', 'Source': 'https://github.com/jodal/pykka', }, ) Pykka-2.0.2/tests/0000775000175000017500000000000013571214203014140 5ustar jodaljodal00000000000000Pykka-2.0.2/tests/__init__.py0000664000175000017500000000064013547724426016271 0ustar jodaljodal00000000000000import pytest try: import eventlet # noqa except ImportError: has_eventlet = pytest.mark.skipif(True, reason='eventlet required') else: has_eventlet = pytest.mark.skipif(False, reason='eventlet required') try: import gevent # noqa except ImportError: has_gevent = pytest.mark.skipif(True, reason='gevent required') else: has_gevent = pytest.mark.skipif(False, reason='gevent required') Pykka-2.0.2/tests/conftest.py0000664000175000017500000001172713547724426016367 0ustar jodaljodal00000000000000import logging import os import threading import time from collections import namedtuple import pytest from pykka import ActorRegistry, ThreadingActor, ThreadingFuture, _compat from tests.log_handler import PykkaTestLogHandler Runtime = namedtuple( 'Runtime', ['name', 'actor_class', 'event_class', 'future_class', 'sleep_func'], ) class NullCollector(pytest.collect.File): def collect(self): return [] # skip test files that end with _py3 if not testing under Python 3 def pytest_pycollect_makemodule(path, parent): file_name = os.path.splitext(path.basename)[0] if _compat.PY2 and file_name.endswith('_py3'): return NullCollector(path, parent=parent) RUNTIMES = { 'threading': pytest.param( Runtime( name='threading', actor_class=ThreadingActor, event_class=threading.Event, future_class=ThreadingFuture, sleep_func=time.sleep, ), id='threading', ) } try: import gevent import gevent.event from pykka.gevent import GeventActor, GeventFuture except ImportError: RUNTIMES['gevent'] = pytest.param( None, id='gevent', marks=pytest.mark.skip(reason='skipping gevent tests'), ) else: RUNTIMES['gevent'] = pytest.param( Runtime( name='gevent', actor_class=GeventActor, event_class=gevent.event.Event, future_class=GeventFuture, sleep_func=gevent.sleep, ), id='gevent', ) try: import eventlet from pykka.eventlet import EventletActor, EventletEvent, EventletFuture except ImportError: RUNTIMES['eventlet'] = pytest.param( None, id='eventlet', marks=pytest.mark.skip(reason='skipping eventlet tests'), ) else: RUNTIMES['eventlet'] = pytest.param( Runtime( name='eventlet', actor_class=EventletActor, event_class=EventletEvent, future_class=EventletFuture, sleep_func=eventlet.sleep, ), id='eventlet', ) @pytest.fixture(scope='session') def eventlet_runtime(): return RUNTIMES['eventlet'].values[0] @pytest.fixture(scope='session') def gevent_runtime(): return RUNTIMES['gevent'].values[0] @pytest.fixture(scope='session', params=RUNTIMES.values()) def runtime(request): return request.param @pytest.fixture def stop_all(): yield ActorRegistry.stop_all() @pytest.fixture def log_handler(): log_handler = PykkaTestLogHandler() root_logger = logging.getLogger() root_logger.addHandler(log_handler) # pytest sets the root logger level to WARNING. We reset it to NOTSET # so that all log messages reaches our log handler. root_logger.setLevel(logging.NOTSET) yield log_handler log_handler.close() @pytest.fixture def events(runtime): class Events(object): on_start_was_called = runtime.event_class() on_stop_was_called = runtime.event_class() on_failure_was_called = runtime.event_class() greetings_was_received = runtime.event_class() actor_registered_before_on_start_was_called = runtime.event_class() return Events() @pytest.fixture(scope='module') def early_failing_actor_class(runtime): class EarlyFailingActor(runtime.actor_class): def __init__(self, events): super(EarlyFailingActor, self).__init__() self.events = events def on_start(self): try: raise RuntimeError('on_start failure') finally: self.events.on_start_was_called.set() return EarlyFailingActor @pytest.fixture(scope='module') def late_failing_actor_class(runtime): class LateFailingActor(runtime.actor_class): def __init__(self, events): super(LateFailingActor, self).__init__() self.events = events def on_start(self): self.stop() def on_stop(self): try: raise RuntimeError('on_stop failure') finally: self.events.on_stop_was_called.set() return LateFailingActor @pytest.fixture(scope='module') def failing_on_failure_actor_class(runtime): class FailingOnFailureActor(runtime.actor_class): def __init__(self, events): super(FailingOnFailureActor, self).__init__() self.events = events 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.events.on_failure_was_called.set() return FailingOnFailureActor @pytest.fixture def future(runtime): return runtime.future_class() @pytest.fixture def futures(runtime): return [runtime.future_class() for _ in range(3)] Pykka-2.0.2/tests/log_handler.py0000664000175000017500000000245113547724426017012 0ustar jodaljodal00000000000000import collections import logging import threading import time class PykkaTestLogHandler(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, timeout=5): """Wait until at least ``num_messages`` log messages have been emitted to the given log level.""" deadline = time.time() + timeout while time.time() < deadline: with self.lock: if len(self.messages[level]) >= num_messages: return self.events[level].clear() self.events[level].wait(1) raise Exception('Timeout: Waited {:d}s for log message'.format(timeout)) Pykka-2.0.2/tests/performance.py0000664000175000017500000000256413547724426017042 0ustar jodaljodal00000000000000import time from pykka import ActorRegistry, ThreadingActor def time_it(func): start = time.time() func() print('{!r} took {:.3f}s'.format(func.__name__, time.time() - start)) class SomeObject(object): pykka_traversable = False cat = 'bar.cat' def func(self): pass class AnActor(ThreadingActor): bar = SomeObject() bar.pykka_traversable = True foo = 'foo' def __init__(self): super(AnActor, self).__init__() self.cat = 'quox' def func(self): pass def test_direct_plain_attribute_access(): actor = AnActor.start().proxy() for _ in range(10000): actor.foo.get() def test_direct_callable_attribute_access(): actor = AnActor.start().proxy() for _ in range(10000): actor.func().get() def test_traversable_plain_attribute_access(): actor = AnActor.start().proxy() for _ in range(10000): actor.bar.cat.get() def test_traversable_callable_attribute_access(): actor = AnActor.start().proxy() for _ in range(10000): actor.bar.func().get() if __name__ == '__main__': try: time_it(test_direct_plain_attribute_access) time_it(test_direct_callable_attribute_access) time_it(test_traversable_plain_attribute_access) time_it(test_traversable_callable_attribute_access) finally: ActorRegistry.stop_all() Pykka-2.0.2/tests/proxy/0000775000175000017500000000000013571214203015321 5ustar jodaljodal00000000000000Pykka-2.0.2/tests/proxy/__init__.py0000664000175000017500000000000013547724426017440 0ustar jodaljodal00000000000000Pykka-2.0.2/tests/proxy/test_attribute_access.py0000664000175000017500000000430613547724426022301 0ustar jodaljodal00000000000000import pytest @pytest.fixture def actor_class(runtime): class ActorWithProperties(runtime.actor_class): an_attr = 'an_attr' _private_attr = 'secret' @property def a_ro_property(self): return 'a_ro_property' _a_rw_property = 'a_rw_property' @property def a_rw_property(self): return self._a_rw_property @a_rw_property.setter def a_rw_property(self, value): self._a_rw_property = value return ActorWithProperties @pytest.fixture def proxy(actor_class): proxy = actor_class.start().proxy() yield proxy proxy.stop() def test_attr_can_be_read_using_get_postfix(proxy): assert proxy.an_attr.get() == 'an_attr' def test_attr_can_be_set_using_assignment(proxy): assert proxy.an_attr.get() == 'an_attr' proxy.an_attr = 'an_attr_2' assert proxy.an_attr.get() == 'an_attr_2' def test_private_attr_access_raises_exception(proxy): with pytest.raises(AttributeError) as exc_info: proxy._private_attr.get() assert "has no attribute '_private_attr'" in str(exc_info.value) def test_missing_attr_access_raises_exception(proxy): with pytest.raises(AttributeError) as exc_info: proxy.missing_attr.get() assert "has no attribute 'missing_attr'" in str(exc_info.value) def test_property_can_be_read_using_get_postfix(proxy): assert proxy.a_ro_property.get() == 'a_ro_property' assert proxy.a_rw_property.get() == 'a_rw_property' def test_property_can_be_set_using_assignment(proxy): proxy.a_rw_property = 'a_rw_property_2' assert proxy.a_rw_property.get() == 'a_rw_property_2' def test_read_only_property_cannot_be_set(proxy): with pytest.raises(AttributeError): proxy.a_ro_property = 'a_ro_property_2' def test_property_is_not_accessed_when_creating_proxy(runtime): class ExpensiveSideEffectActor(runtime.actor_class): @property def a_property(self): # Imagine code with side effects or heavy resource usage here raise Exception('Proxy creation accessed property') actor_ref = ExpensiveSideEffectActor.start() try: actor_ref.proxy() finally: actor_ref.stop() Pykka-2.0.2/tests/proxy/test_dynamic_method_calls.py0000664000175000017500000000231713547724426023117 0ustar jodaljodal00000000000000import pytest @pytest.fixture(scope='module') def actor_class(runtime): class ActorA(runtime.actor_class): 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() return ActorA @pytest.fixture def proxy(actor_class): proxy = actor_class.start().proxy() yield proxy proxy.stop() def test_can_call_method_that_was_added_at_runtime(proxy): # We need to .get() after .add_method() to be sure that the method has # been added before we try to use it through the proxy. proxy.add_method('foo').get() assert proxy.foo().get() == 'returned by foo' def test_can_proxy_itself_and_use_attrs_added_at_runtime(proxy): # We don't need to .get() after .add_method() here, because the actor # will process the .add_method() call before processing the # .use_foo_through_self_proxy() call, which again will use the new # method, .foo(). proxy.add_method('foo') outer_future = proxy.use_foo_through_self_proxy() inner_future = outer_future.get(timeout=1) result = inner_future.get(timeout=1) assert result == 'returned by foo' Pykka-2.0.2/tests/proxy/test_legacy_message_format.py0000664000175000017500000000236613547724426023301 0ustar jodaljodal00000000000000import pytest @pytest.fixture(scope='module') def actor_class(runtime): class ActorA(runtime.actor_class): an_attr = 'a value' def a_method(self): return 'a return value' return ActorA @pytest.fixture def actor_ref(actor_class): actor_ref = actor_class.start() yield actor_ref actor_ref.stop() def test_proxy_call_via_legacy_message(actor_ref): with pytest.deprecated_call(): result = actor_ref.ask( { 'command': 'pykka_call', 'attr_path': ['a_method'], 'args': [], 'kwargs': {}, } ) assert result == 'a return value' def test_proxy_attr_via_legacy_message(actor_ref): with pytest.deprecated_call(): result_before = actor_ref.ask( {'command': 'pykka_getattr', 'attr_path': ['an_attr']} ) actor_ref.tell( { 'command': 'pykka_setattr', 'attr_path': ['an_attr'], 'value': 'new value', } ) result_after = actor_ref.ask( {'command': 'pykka_getattr', 'attr_path': ['an_attr']} ) assert result_before == 'a value' assert result_after == 'new value' Pykka-2.0.2/tests/proxy/test_mocking.py0000664000175000017500000000427213571213344020373 0ustar jodaljodal00000000000000import pytest from pykka import _compat @pytest.fixture def actor_class(runtime): class ActorForMocking(runtime.actor_class): _a_rw_property = 'a_rw_property' @property def a_rw_property(self): return self._a_rw_property @a_rw_property.setter def a_rw_property(self, value): self._a_rw_property = value def a_method(self): raise Exception('This method should be mocked') return ActorForMocking @pytest.fixture def proxy(actor_class): proxy = actor_class.start().proxy() yield proxy proxy.stop() def test_actor_with_noncallable_mock_property_works( actor_class, stop_all, mocker ): mock = mocker.NonCallableMock() mock.__get__ = mocker.Mock(return_value='mocked property value') assert not isinstance(mock, _compat.Callable) actor_class.a_rw_property = mock proxy = actor_class.start().proxy() # When using NonCallableMock to fake the property, the value still behaves # as a property when access through the proxy. assert proxy.a_rw_property.get() == 'mocked property value' assert mock.__get__.call_count == 1 def test_actor_with_callable_mock_property_does_not_work( actor_class, stop_all, mocker ): mock = mocker.Mock() mock.__get__ = mocker.Mock(return_value='mocked property value') assert isinstance(mock, _compat.Callable) actor_class.a_rw_property = mock proxy = actor_class.start().proxy() # XXX Because Mock and MagicMock are callable by default, they cause the # property to be wrapped in a `CallableProxy`. Thus, the property no # longer behaves as a property when mocked and accessed through a proxy. with pytest.raises(AttributeError) as exc_info: assert proxy.a_rw_property.get() assert "'CallableProxy' object has no attribute 'get'" in str( exc_info.value ) def test_actor_with_mocked_method_works(actor_class, stop_all, mocker): mock = mocker.MagicMock(return_value='mocked method return') mocker.patch.object(actor_class, 'a_method', new=mock) proxy = actor_class.start().proxy() assert proxy.a_method().get() == 'mocked method return' assert mock.call_count == 1 Pykka-2.0.2/tests/proxy/test_proxy.py0000664000175000017500000000717313547724432020140 0ustar jodaljodal00000000000000import pytest import pykka from pykka import ActorDeadError, ActorProxy class NestedObject(object): pass @pytest.fixture(scope='module') def actor_class(runtime): class ActorForProxying(runtime.actor_class): a_nested_object = pykka.traversable(NestedObject()) a_class_attr = 'class_attr' def __init__(self): super(runtime.actor_class, self).__init__() self.an_instance_attr = 'an_instance_attr' def a_method(self): pass return ActorForProxying @pytest.fixture def proxy(actor_class): proxy = ActorProxy(actor_class.start()) yield proxy proxy.stop() def test_eq_to_self(proxy): assert proxy == proxy def test_is_hashable(proxy): assert hash(proxy) == hash(proxy) def test_eq_to_another_proxy_for_same_actor_and_attr_path(proxy): proxy2 = proxy.actor_ref.proxy() assert proxy == proxy2 def test_not_eq_to_proxy_with_different_attr_path(proxy): assert proxy != proxy.a_nested_object def test_repr_is_wrapped_in_lt_and_gt(proxy): result = repr(proxy) assert result.startswith('<') assert result.endswith('>') def test_repr_reveals_that_this_is_a_proxy(proxy): assert 'ActorProxy' in repr(proxy) def test_repr_contains_actor_class_name(proxy): assert 'ActorForProxying' in repr(proxy) def test_repr_contains_actor_urn(proxy): assert proxy.actor_ref.actor_urn in repr(proxy) def test_repr_contains_attr_path(proxy): assert 'a_nested_object' in repr(proxy.a_nested_object) def test_str_contains_actor_class_name(proxy): assert 'ActorForProxying' in str(proxy) def test_str_contains_actor_urn(proxy): assert proxy.actor_ref.actor_urn in str(proxy) def test_dir_on_proxy_lists_attributes_of_the_actor(proxy): result = dir(proxy) assert 'a_class_attr' in result assert 'an_instance_attr' in result assert 'a_method' in result def test_dir_on_proxy_lists_private_attributes_of_the_proxy(proxy): result = dir(proxy) assert '__class__' in result assert '__dict__' in result assert '__getattr__' in result assert '__setattr__' in result def test_refs_proxy_method_returns_a_proxy(actor_class): proxy_from_ref_proxy = actor_class.start().proxy() assert isinstance(proxy_from_ref_proxy, ActorProxy) proxy_from_ref_proxy.stop().get() def test_proxy_constructor_raises_exception_if_actor_is_dead(actor_class): actor_ref = actor_class.start() actor_ref.stop() with pytest.raises(ActorDeadError) as exc_info: ActorProxy(actor_ref) assert str(exc_info.value) == '{} not found'.format(actor_ref) def test_actor_ref_may_be_retrieved_from_proxy_if_actor_is_dead(proxy): proxy.actor_ref.stop() assert not proxy.actor_ref.is_alive() def test_actor_proxy_does_not_expose_proxy_to_self(runtime, log_handler): class Actor(runtime.actor_class): def __init__(self): super(Actor, self).__init__() self.self_proxy = self.actor_ref.proxy() self.foo = 'bar' actor_ref = Actor.start() try: proxy = actor_ref.proxy() assert proxy.foo.get() == 'bar' with pytest.raises( AttributeError, match="has no attribute 'self_proxy'" ): proxy.self_proxy.foo.get() finally: actor_ref.stop() log_handler.wait_for_message('warning') with log_handler.lock: assert len(log_handler.messages['warning']) == 2 log_record = log_handler.messages['warning'][0] assert ( "attribute 'self_proxy' is a proxy to itself. " "Consider making it private by renaming it to '_self_proxy'." ) in log_record.getMessage() Pykka-2.0.2/tests/proxy/test_static_method_calls.py0000664000175000017500000000573213547724426022766 0ustar jodaljodal00000000000000import pytest @pytest.fixture(scope='module') def actor_class(runtime): class ActorA(runtime.actor_class): cat = 'dog' def __init__(self, events): super(ActorA, self).__init__() self.events = events def on_stop(self): self.events.on_stop_was_called.set() def on_failure(self, *args): self.events.on_failure_was_called.set() def functional_hello(self, s): return 'Hello, {}!'.format(s) def set_cat(self, s): self.cat = s def raise_exception(self): raise Exception('boom!') def talk_with_self(self): return self.actor_ref.proxy().functional_hello('from the future') return ActorA @pytest.fixture def proxy(actor_class, events): proxy = actor_class.start(events).proxy() yield proxy proxy.stop() def test_functional_method_call_returns_correct_value(proxy): assert proxy.functional_hello('world').get() == 'Hello, world!' assert proxy.functional_hello('moon').get() == 'Hello, moon!' def test_side_effect_of_method_call_is_observable(proxy): assert proxy.cat.get() == 'dog' future = proxy.set_cat('eagle') assert future.get() is None assert proxy.cat.get() == 'eagle' def test_side_effect_of_deferred_method_call_is_observable(proxy): assert proxy.cat.get() == 'dog' result = proxy.set_cat.defer('eagle') assert result is None assert proxy.cat.get() == 'eagle' def test_exception_in_method_reraised_by_future(proxy, events): assert not events.on_failure_was_called.is_set() future = proxy.raise_exception() with pytest.raises(Exception) as exc_info: future.get() assert str(exc_info.value) == 'boom!' assert not events.on_failure_was_called.is_set() def test_exception_in_deferred_method_call_triggers_on_failure(proxy, events): assert not events.on_failure_was_called.is_set() result = proxy.raise_exception.defer() assert result is None events.on_failure_was_called.wait(5) assert events.on_failure_was_called.is_set() assert not events.on_stop_was_called.is_set() def test_call_to_unknown_method_raises_attribute_error(proxy): with pytest.raises(AttributeError) as exc_info: proxy.unknown_method() result = str(exc_info.value) assert result.startswith('= 2, the property getter always returns a future: assert ( proxy.nested_object_property.get().inner == 'nested_with_attr_marker.inner' ) def test_traversable_cannot_mark_object_using_slots(): with pytest.raises(Exception) as exc_info: pykka.traversable(NestedWithNoMarkerAndSlots()) assert 'cannot be used to mark an object using slots' in str(exc_info.value) Pykka-2.0.2/tests/test_actor.py0000664000175000017500000001677013547724426016714 0ustar jodaljodal00000000000000import uuid import pytest from pykka import ActorDeadError, ActorRegistry pytestmark = pytest.mark.usefixtures('stop_all') @pytest.fixture(scope='module') def actor_class(runtime): class ActorA(runtime.actor_class): def __init__(self, events): super(ActorA, self).__init__() self.events = events def on_start(self): self.events.on_start_was_called.set() if ActorRegistry.get_by_urn(self.actor_urn) is not None: self.events.actor_registered_before_on_start_was_called.set() def on_stop(self): self.events.on_stop_was_called.set() def on_failure(self, *args): self.events.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.events.greetings_was_received.set() elif message.get('command') == 'callback': message['callback']() else: super(ActorA, self).on_receive(message) return ActorA @pytest.fixture def actor_ref(actor_class, events): ref = actor_class.start(events) yield ref ref.stop() @pytest.fixture(scope='module') def early_stopping_actor_class(runtime): class EarlyStoppingActor(runtime.actor_class): def __init__(self, events): super(EarlyStoppingActor, self).__init__() self.events = events def on_start(self): self.stop() def on_stop(self): self.events.on_stop_was_called.set() return EarlyStoppingActor def test_messages_left_in_queue_after_actor_stops_receive_an_error( runtime, actor_ref ): event = runtime.event_class() actor_ref.tell({'command': 'callback', 'callback': event.wait}) actor_ref.stop(block=False) response = actor_ref.ask({'command': 'irrelevant'}, block=False) event.set() with pytest.raises(ActorDeadError): response.get(timeout=0.5) def test_stop_requests_left_in_queue_after_actor_stops_are_handled( runtime, actor_ref ): event = runtime.event_class() actor_ref.tell({'command': 'callback', 'callback': event.wait}) actor_ref.stop(block=False) response = actor_ref.stop(block=False) event.set() response.get(timeout=0.5) def test_actor_has_an_uuid4_based_urn(actor_ref): assert uuid.UUID(actor_ref.actor_urn).version == 4 def test_actor_has_unique_uuid(actor_class, events): actors = [actor_class.start(events) for _ in range(3)] assert actors[0].actor_urn != actors[1].actor_urn assert actors[1].actor_urn != actors[2].actor_urn assert actors[2].actor_urn != actors[0].actor_urn def test_str_on_raw_actor_contains_actor_class_name(actor_class, events): unstarted_actor = actor_class(events) assert 'ActorA' in str(unstarted_actor) def test_str_on_raw_actor_contains_actor_urn(actor_class, events): unstarted_actor = actor_class(events) assert unstarted_actor.actor_urn in str(unstarted_actor) def test_init_can_be_called_with_arbitrary_arguments(runtime): runtime.actor_class(1, 2, 3, foo='bar') def test_on_start_is_called_before_first_message_is_processed( actor_ref, events ): events.on_start_was_called.wait(5) assert events.on_start_was_called.is_set() def test_on_start_is_called_after_the_actor_is_registered(actor_ref, events): # 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. events.on_start_was_called.wait(5) assert events.on_start_was_called.is_set() events.actor_registered_before_on_start_was_called.wait(0.1) assert events.actor_registered_before_on_start_was_called.is_set() def test_on_start_can_stop_actor_before_receive_loop_is_started( early_stopping_actor_class, events ): # 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. actor_ref = early_stopping_actor_class.start(events) events.on_stop_was_called.wait(5) assert events.on_stop_was_called.is_set() assert not actor_ref.is_alive() def test_on_start_failure_causes_actor_to_stop( early_failing_actor_class, events ): # Actor should not be alive if on_start fails. actor_ref = early_failing_actor_class.start(events) events.on_start_was_called.wait(5) actor_ref.actor_stopped.wait(5) assert not actor_ref.is_alive() def test_on_stop_is_called_when_actor_is_stopped(actor_ref, events): assert not events.on_stop_was_called.is_set() actor_ref.stop() events.on_stop_was_called.wait(5) assert events.on_stop_was_called.is_set() def test_on_stop_failure_causes_actor_to_stop(late_failing_actor_class, events): actor_ref = late_failing_actor_class.start(events) events.on_stop_was_called.wait(5) assert not actor_ref.is_alive() def test_on_failure_is_called_when_exception_cannot_be_returned( actor_ref, events ): assert not events.on_failure_was_called.is_set() actor_ref.tell({'command': 'raise exception'}) events.on_failure_was_called.wait(5) assert events.on_failure_was_called.is_set() assert not events.on_stop_was_called.is_set() def test_on_failure_failure_causes_actor_to_stop( failing_on_failure_actor_class, events ): actor_ref = failing_on_failure_actor_class.start(events) actor_ref.tell({'command': 'raise exception'}) events.on_failure_was_called.wait(5) assert not actor_ref.is_alive() def test_actor_is_stopped_when_unhandled_exceptions_are_raised( actor_ref, events ): assert not events.on_failure_was_called.is_set() actor_ref.tell({'command': 'raise exception'}) events.on_failure_was_called.wait(5) assert events.on_failure_was_called.is_set() assert len(ActorRegistry.get_all()) == 0 def test_all_actors_are_stopped_on_base_exception(events, actor_ref): assert len(ActorRegistry.get_all()) == 1 assert not events.on_stop_was_called.is_set() actor_ref.tell({'command': 'raise base exception'}) events.on_stop_was_called.wait(5) assert events.on_stop_was_called.is_set() assert len(ActorRegistry.get_all()) == 0 events.on_stop_was_called.wait(5) assert events.on_stop_was_called.is_set() assert len(ActorRegistry.get_all()) == 0 def test_actor_can_call_stop_on_self_multiple_times(actor_ref): actor_ref.ask({'command': 'stop twice'}) def test_actor_processes_all_messages_before_stop_on_self_stops_it( actor_ref, events ): actor_ref.ask({'command': 'message self then stop'}) events.greetings_was_received.wait(5) assert events.greetings_was_received.is_set() events.on_stop_was_called.wait(5) assert len(ActorRegistry.get_all()) == 0 Pykka-2.0.2/tests/test_envelope.py0000664000175000017500000000027413547724426017411 0ustar jodaljodal00000000000000from pykka._envelope import Envelope def test_envelope_repr(): envelope = Envelope('message', reply_to=123) assert repr(envelope) == "Envelope(message='message', reply_to=123)" Pykka-2.0.2/tests/test_future.py0000664000175000017500000001507513547724426017113 0ustar jodaljodal00000000000000import sys import traceback import pytest from pykka import Future, Timeout, get_all from tests import has_gevent def test_base_future_get_is_not_implemented(): future = Future() with pytest.raises(NotImplementedError): future.get() def test_base_future_set_is_not_implemented(): future = Future() with pytest.raises(NotImplementedError): future.set(None) def test_base_future_set_exception_is_not_implemented(): future = Future() with pytest.raises(NotImplementedError): future.set_exception(None) def test_set_multiple_times_fails(future): future.set(0) with pytest.raises(Exception): future.set(0) def test_get_all_blocks_until_all_futures_are_available(futures): futures[0].set(0) futures[1].set(1) futures[2].set(2) result = get_all(futures) assert result == [0, 1, 2] def test_get_all_raises_timeout_if_not_all_futures_are_available(futures): futures[0].set(0) futures[1].set(1) # futures[2] is unset with pytest.raises(Timeout): get_all(futures, timeout=0) def test_get_all_can_be_called_multiple_times(futures): futures[0].set(0) futures[1].set(1) futures[2].set(2) result1 = get_all(futures) result2 = get_all(futures) assert result1 == result2 def test_future_in_future_works(runtime): inner_future = runtime.future_class() inner_future.set('foo') outer_future = runtime.future_class() outer_future.set(inner_future) assert outer_future.get().get() == 'foo' def test_get_raises_exception_with_full_traceback(runtime): 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 = runtime.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() assert exc_class_set == exc_class_get assert 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 assert len(exc_traceback_list_set) < len(exc_traceback_list_get) for i, frame in enumerate(exc_traceback_list_set): assert frame == exc_traceback_list_get[i] def test_filter_excludes_items_not_matching_predicate(future): filtered = future.filter(lambda x: x > 10) future.set([1, 3, 5, 7, 9, 11, 13, 15, 17, 19]) assert filtered.get(timeout=0) == [11, 13, 15, 17, 19] def test_filter_on_noniterable(future): filtered = future.filter(lambda x: x > 10) future.set(1) with pytest.raises(TypeError): filtered.get(timeout=0) def test_filter_preserves_the_timeout_kwarg(future): filtered = future.filter(lambda x: x > 10) with pytest.raises(Timeout): filtered.get(timeout=0) def test_filter_reuses_result_if_called_multiple_times(future, mocker): raise_on_reuse_func = mocker.Mock(side_effect=[False, True, Exception]) filtered = future.filter(raise_on_reuse_func) future.set([1, 2]) assert filtered.get(timeout=0) == [2] assert filtered.get(timeout=0) == [2] # First result is reused assert filtered.get(timeout=0) == [2] # First result is reused def test_join_combines_multiple_futures_into_one(futures): joined = futures[0].join(futures[1], futures[2]) futures[0].set(0) futures[1].set(1) futures[2].set(2) assert joined.get(timeout=0) == [0, 1, 2] def test_join_preserves_timeout_kwarg(futures): joined = futures[0].join(futures[1], futures[2]) futures[0].set(0) futures[1].set(1) # futures[2] is unset with pytest.raises(Timeout): joined.get(timeout=0) def test_map_returns_future_which_passes_result_through_func(future): mapped = future.map(lambda x: x + 10) future.set(30) assert mapped.get(timeout=0) == 40 def test_map_works_on_dict(future): # Regression test for issue #64 mapped = future.map(lambda x: x['foo']) future.set({'foo': 'bar'}) assert mapped.get(timeout=0) == 'bar' def test_map_does_not_map_each_value_in_futures_iterable_result(future): # Behavior changed in Pykka 2.0: # This used to map each value in the future's result through the func, # yielding [20, 30, 40]. mapped = future.map(lambda x: x + 10) future.set([10, 20, 30]) with pytest.raises(TypeError): mapped.get(timeout=0) def test_map_preserves_timeout_kwarg(future): mapped = future.map(lambda x: x + 10) with pytest.raises(Timeout): mapped.get(timeout=0) def test_map_reuses_result_if_called_multiple_times(future, mocker): raise_on_reuse_func = mocker.Mock(side_effect=[10, Exception]) mapped = future.map(raise_on_reuse_func) future.set(30) assert mapped.get(timeout=0) == 10 assert mapped.get(timeout=0) == 10 # First result is reused def test_reduce_applies_function_cumulatively_from_the_left(future): reduced = future.reduce(lambda x, y: x + y) future.set([1, 2, 3, 4]) assert reduced.get(timeout=0) == 10 def test_reduce_accepts_an_initial_value(future): reduced = future.reduce(lambda x, y: x + y, 5) future.set([1, 2, 3, 4]) assert reduced.get(timeout=0) == 15 def test_reduce_on_noniterable(future): reduced = future.reduce(lambda x, y: x + y) future.set(1) with pytest.raises(TypeError): reduced.get(timeout=0) def test_reduce_preserves_the_timeout_kwarg(future): reduced = future.reduce(lambda x, y: x + y) with pytest.raises(Timeout): reduced.get(timeout=0) def test_reduce_reuses_result_if_called_multiple_times(future, mocker): raise_on_reuse_func = mocker.Mock(side_effect=[3, 6, Exception]) reduced = future.reduce(raise_on_reuse_func) future.set([1, 2, 3]) assert reduced.get(timeout=0) == 6 assert reduced.get(timeout=0) == 6 # First result is reused assert reduced.get(timeout=0) == 6 # First result is reused @has_gevent def test_gevent_future_can_wrap_existing_async_result(gevent_runtime): from gevent.event import AsyncResult async_result = AsyncResult() future = gevent_runtime.future_class(async_result) assert async_result == future.async_result Pykka-2.0.2/tests/test_future_py3.py0000664000175000017500000000131313547724426017674 0ustar jodaljodal00000000000000import asyncio import sys import pytest from pykka import _compat def run_async(coroutine): loop = asyncio.get_event_loop() f = asyncio.ensure_future(coroutine, loop=loop) return loop.run_until_complete(f) @pytest.mark.skipif( sys.version_info < (3, 5), reason='await requires Python 3.5+' ) def test_future_supports_await_syntax(future): @asyncio.coroutine def get_value(): return _compat.await_keyword(future) future.set(1) assert run_async(get_value()) == 1 def test_future_supports_yield_from_syntax(future): @asyncio.coroutine def get_value(): val = yield from future return val future.set(1) assert run_async(get_value()) == 1 Pykka-2.0.2/tests/test_logging.py0000664000175000017500000001161713547724426017225 0ustar jodaljodal00000000000000import logging import pytest pytestmark = pytest.mark.usefixtures('stop_all') @pytest.fixture(scope='module') def actor_class(runtime): class ActorA(runtime.actor_class): def __init__(self, events): super(ActorA, self).__init__() self.events = events def on_stop(self): self.events.on_stop_was_called.set() def on_failure(self, exception_type, exception_value, traceback): self.events.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(ActorA, self).on_receive(message) def raise_exception(self): raise Exception('foo') return ActorA @pytest.fixture def actor_ref(actor_class, events): ref = actor_class.start(events) yield ref ref.stop() def test_null_handler_is_added_to_avoid_warnings(): logger = logging.getLogger('pykka') handler_names = [h.__class__.__name__ for h in logger.handlers] assert 'NullHandler' in handler_names def test_unexpected_messages_are_logged(actor_ref, log_handler): actor_ref.ask({'unhandled': 'message'}) log_handler.wait_for_message('warning') with log_handler.lock: assert len(log_handler.messages['warning']) == 1 log_record = log_handler.messages['warning'][0] assert log_record.getMessage().split(': ')[0] == ( 'Unexpected message received by {}'.format(actor_ref) ) def test_exception_is_logged_when_returned_to_caller(actor_ref, log_handler): with pytest.raises(Exception): actor_ref.proxy().raise_exception().get() log_handler.wait_for_message('info') with log_handler.lock: assert len(log_handler.messages['info']) == 1 log_record = log_handler.messages['info'][0] assert log_record.getMessage() == ( 'Exception returned from {} to caller:'.format(actor_ref) ) assert log_record.exc_info[0] == Exception assert str(log_record.exc_info[1]) == 'foo' def test_exception_is_logged_when_not_reply_requested( actor_ref, events, log_handler ): events.on_failure_was_called.clear() actor_ref.tell({'command': 'raise exception'}) events.on_failure_was_called.wait(5) assert events.on_failure_was_called.is_set() log_handler.wait_for_message('error') with log_handler.lock: assert len(log_handler.messages['error']) == 1 log_record = log_handler.messages['error'][0] assert log_record.getMessage() == 'Unhandled exception in {}:'.format( actor_ref ) assert log_record.exc_info[0] == Exception assert str(log_record.exc_info[1]) == 'foo' def test_base_exception_is_logged(actor_ref, events, log_handler): log_handler.reset() events.on_stop_was_called.clear() actor_ref.tell({'command': 'raise base exception'}) events.on_stop_was_called.wait(5) assert events.on_stop_was_called.is_set() log_handler.wait_for_message('debug', num_messages=3) with log_handler.lock: assert len(log_handler.messages['debug']) == 3 log_record = log_handler.messages['debug'][0] assert log_record.getMessage() == ( 'BaseException() in {}. Stopping all actors.'.format(actor_ref) ) def test_exception_in_on_start_is_logged( early_failing_actor_class, events, log_handler ): log_handler.reset() actor_ref = early_failing_actor_class.start(events) events.on_start_was_called.wait(5) log_handler.wait_for_message('error') with log_handler.lock: assert len(log_handler.messages['error']) == 1 log_record = log_handler.messages['error'][0] assert log_record.getMessage() == 'Unhandled exception in {}:'.format( actor_ref ) def test_exception_in_on_stop_is_logged( late_failing_actor_class, events, log_handler ): log_handler.reset() actor_ref = late_failing_actor_class.start(events) events.on_stop_was_called.wait(5) log_handler.wait_for_message('error') with log_handler.lock: assert len(log_handler.messages['error']) == 1 log_record = log_handler.messages['error'][0] assert log_record.getMessage() == 'Unhandled exception in {}:'.format( actor_ref ) def test_exception_in_on_failure_is_logged( failing_on_failure_actor_class, events, log_handler ): log_handler.reset() actor_ref = failing_on_failure_actor_class.start(events) actor_ref.tell({'command': 'raise exception'}) events.on_failure_was_called.wait(5) log_handler.wait_for_message('error', num_messages=2) with log_handler.lock: assert len(log_handler.messages['error']) == 2 log_record = log_handler.messages['error'][0] assert log_record.getMessage() == 'Unhandled exception in {}:'.format( actor_ref ) Pykka-2.0.2/tests/test_messages.py0000664000175000017500000000254713547724426017410 0ustar jodaljodal00000000000000import warnings import pytest from pykka.messages import ( ProxyCall, ProxyGetAttr, ProxySetAttr, _ActorStop, _upgrade_internal_message, ) @pytest.mark.parametrize( 'old_format, new_format', [ ({'command': 'pykka_stop'}, _ActorStop()), ( { 'command': 'pykka_call', 'attr_path': ['nested', 'method'], 'args': [1], 'kwargs': {'a': 'b'}, }, ProxyCall( attr_path=['nested', 'method'], args=[1], kwargs={'a': 'b'} ), ), ( {'command': 'pykka_getattr', 'attr_path': ['nested', 'attr']}, ProxyGetAttr(attr_path=['nested', 'attr']), ), ( { 'command': 'pykka_setattr', 'attr_path': ['nested', 'attr'], 'value': 'abcdef', }, ProxySetAttr(attr_path=['nested', 'attr'], value='abcdef'), ), ], ) def test_upgrade_internal_message(old_format, new_format): with warnings.catch_warnings(record=True) as w: assert _upgrade_internal_message(old_format) == new_format assert len(w) == 1 assert issubclass(w[0].category, DeprecationWarning) assert 'Pykka received a dict-based internal message.' in str( w[0].message ) Pykka-2.0.2/tests/test_ref.py0000664000175000017500000000721013547724426016345 0ustar jodaljodal00000000000000import pytest from pykka import ActorDeadError, Timeout @pytest.fixture(scope='module') def actor_class(runtime): class ActorA(runtime.actor_class): received_messages = None def __init__(self, received_message): super(runtime.actor_class, self).__init__() self.received_message = received_message def on_receive(self, message): if isinstance(message, dict) and message.get('command') == 'ping': runtime.sleep_func(0.01) return 'pong' else: self.received_message.set(message) return ActorA @pytest.fixture def received_messages(runtime): return runtime.future_class() @pytest.fixture def actor_ref(actor_class, received_messages): ref = actor_class.start(received_messages) yield ref ref.stop() def test_repr_is_wrapped_in_lt_and_gt(actor_ref): result = repr(actor_ref) assert result.startswith('<') assert result.endswith('>') def test_repr_reveals_that_this_is_a_ref(actor_ref): assert 'ActorRef' in repr(actor_ref) def test_repr_contains_actor_class_name(actor_ref): assert 'ActorA' in repr(actor_ref) def test_repr_contains_actor_urn(actor_ref): assert actor_ref.actor_urn in repr(actor_ref) def test_str_contains_actor_class_name(actor_ref): assert 'ActorA' in str(actor_ref) def test_str_contains_actor_urn(actor_ref): assert actor_ref.actor_urn in str(actor_ref) def test_is_alive_returns_true_for_running_actor(actor_ref): assert actor_ref.is_alive() def test_is_alive_returns_false_for_dead_actor(actor_ref): actor_ref.stop() assert not actor_ref.is_alive() def test_stop_returns_true_if_actor_is_stopped(actor_ref): assert actor_ref.stop() def test_stop_does_not_stop_already_dead_actor(actor_ref): assert actor_ref.stop() assert not actor_ref.stop() def test_tell_delivers_message_to_actors_custom_on_receive( actor_ref, received_messages ): actor_ref.tell({'command': 'a custom message'}) assert received_messages.get(timeout=1) == {'command': 'a custom message'} @pytest.mark.parametrize( 'message', [ 123, 123.456, {'a': 'dict'}, ('a', 'tuple'), ['a', 'list'], Exception('an exception'), ], ) def test_tell_accepts_any_object_as_the_message( actor_ref, message, received_messages ): actor_ref.tell(message) assert received_messages.get(timeout=1) == message def test_tell_fails_if_actor_is_stopped(actor_ref): actor_ref.stop() with pytest.raises(ActorDeadError) as exc_info: actor_ref.tell({'command': 'a custom message'}) assert str(exc_info.value) == '{} not found'.format(actor_ref) def test_ask_blocks_until_response_arrives(actor_ref): result = actor_ref.ask({'command': 'ping'}) assert result == 'pong' def test_ask_can_timeout_if_blocked_too_long(actor_ref): with pytest.raises(Timeout): actor_ref.ask({'command': 'ping'}, timeout=0) def test_ask_can_return_future_instead_of_blocking(actor_ref): future = actor_ref.ask({'command': 'ping'}, block=False) assert future.get() == 'pong' def test_ask_fails_if_actor_is_stopped(actor_ref): actor_ref.stop() with pytest.raises(ActorDeadError) as exc_info: actor_ref.ask({'command': 'ping'}) assert str(exc_info.value) == '{} not found'.format(actor_ref) def test_ask_nonblocking_fails_future_if_actor_is_stopped(actor_ref): actor_ref.stop() future = actor_ref.ask({'command': 'ping'}, block=False) with pytest.raises(ActorDeadError) as exc_info: future.get() assert str(exc_info.value) == '{} not found'.format(actor_ref) Pykka-2.0.2/tests/test_registry.py0000664000175000017500000001250213547724426017441 0ustar jodaljodal00000000000000import pytest from pykka import ActorRegistry pytestmark = pytest.mark.usefixtures('stop_all') class ActorBase(object): received_messages = None def __init__(self): super(ActorBase, self).__init__() self.received_messages = [] def on_receive(self, message): self.received_messages.append(message) @pytest.fixture(scope='module') def actor_a_class(runtime): class ActorA(ActorBase, runtime.actor_class): pass return ActorA @pytest.fixture(scope='module') def actor_b_class(runtime): class ActorB(ActorBase, runtime.actor_class): pass return ActorB @pytest.fixture def actor_ref(actor_a_class): return actor_a_class.start() @pytest.fixture def a_actor_refs(actor_a_class): return [actor_a_class.start() for _ in range(3)] @pytest.fixture def b_actor_refs(actor_b_class): return [actor_b_class.start() for _ in range(5)] def test_actor_is_registered_when_started(actor_ref): assert actor_ref in ActorRegistry.get_all() def test_actor_is_unregistered_when_stopped(actor_ref): assert actor_ref in ActorRegistry.get_all() actor_ref.stop() assert actor_ref not in ActorRegistry.get_all() def test_actor_may_be_registered_manually(actor_ref): ActorRegistry.unregister(actor_ref) assert actor_ref not in ActorRegistry.get_all() ActorRegistry.register(actor_ref) assert actor_ref in ActorRegistry.get_all() def test_actor_may_be_unregistered_multiple_times_without_error(actor_ref): ActorRegistry.unregister(actor_ref) assert actor_ref not in ActorRegistry.get_all() ActorRegistry.unregister(actor_ref) assert actor_ref not in ActorRegistry.get_all() ActorRegistry.register(actor_ref) assert actor_ref in ActorRegistry.get_all() def test_all_actors_can_be_stopped_through_registry(a_actor_refs, b_actor_refs): assert len(ActorRegistry.get_all()) == 8 ActorRegistry.stop_all(block=True) assert len(ActorRegistry.get_all()) == 0 def test_stop_all_stops_last_started_actor_first_if_blocking(mocker): mocker.patch.object(ActorRegistry, 'get_all') stopped_actors = [] started_actors = [mocker.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) assert stopped_actors[0] == started_actors[2] assert stopped_actors[1] == started_actors[1] assert stopped_actors[2] == started_actors[0] def test_actors_may_be_looked_up_by_class( actor_a_class, a_actor_refs, b_actor_refs ): result = ActorRegistry.get_by_class(actor_a_class) for a_actor in a_actor_refs: assert a_actor in result for b_actor in b_actor_refs: assert b_actor not in result def test_actors_may_be_looked_up_by_superclass( actor_a_class, a_actor_refs, b_actor_refs ): result = ActorRegistry.get_by_class(actor_a_class) for a_actor in a_actor_refs: assert a_actor in result for b_actor in b_actor_refs: assert b_actor not in result def test_actors_may_be_looked_up_by_class_name( actor_a_class, a_actor_refs, b_actor_refs ): result = ActorRegistry.get_by_class_name('ActorA') for a_actor in a_actor_refs: assert a_actor in result for b_actor in b_actor_refs: assert b_actor not in result def test_actors_may_be_looked_up_by_urn(actor_ref): result = ActorRegistry.get_by_urn(actor_ref.actor_urn) assert result == actor_ref def test_get_by_urn_returns_none_if_not_found(): result = ActorRegistry.get_by_urn('urn:foo:bar') assert result is None def test_broadcast_sends_message_to_all_actors_if_no_target( a_actor_refs, b_actor_refs ): ActorRegistry.broadcast({'command': 'foo'}) running_actors = ActorRegistry.get_all() assert running_actors for actor_ref in running_actors: received_messages = actor_ref.proxy().received_messages.get() assert {'command': 'foo'} in received_messages def test_broadcast_sends_message_to_all_actors_of_given_class( actor_a_class, actor_b_class ): ActorRegistry.broadcast({'command': 'foo'}, target_class=actor_a_class) for actor_ref in ActorRegistry.get_by_class(actor_a_class): received_messages = actor_ref.proxy().received_messages.get() assert {'command': 'foo'} in received_messages for actor_ref in ActorRegistry.get_by_class(actor_b_class): received_messages = actor_ref.proxy().received_messages.get() assert {'command': 'foo'} not in received_messages def test_broadcast_sends_message_to_all_actors_of_given_class_name( actor_a_class, actor_b_class ): ActorRegistry.broadcast({'command': 'foo'}, target_class='ActorA') for actor_ref in ActorRegistry.get_by_class(actor_a_class): received_messages = actor_ref.proxy().received_messages.get() assert {'command': 'foo'} in received_messages for actor_ref in ActorRegistry.get_by_class(actor_b_class): received_messages = actor_ref.proxy().received_messages.get() assert {'command': 'foo'} not in received_messages Pykka-2.0.2/tests/test_threading_actor.py0000664000175000017500000000241213547724426020725 0ustar jodaljodal00000000000000import threading import pytest from pykka import ThreadingActor class RegularActor(ThreadingActor): pass class DaemonActor(ThreadingActor): use_daemon_thread = True @pytest.fixture def regular_actor_ref(): ref = RegularActor.start() yield ref ref.stop() @pytest.fixture def daemon_actor_ref(): ref = DaemonActor.start() yield ref ref.stop() def test_actor_thread_is_named_after_pykka_actor_class(regular_actor_ref): alive_threads = threading.enumerate() alive_thread_names = [t.name for t in alive_threads] named_correctly = [ name.startswith(RegularActor.__name__) for name in alive_thread_names ] assert any(named_correctly) def test_actor_thread_is_not_daemonic_by_default(regular_actor_ref): alive_threads = threading.enumerate() actor_threads = [ t for t in alive_threads if t.name.startswith('RegularActor') ] assert len(actor_threads) == 1 assert not actor_threads[0].daemon def test_actor_thread_is_daemonic_if_use_daemon_thread_flag_is_set( daemon_actor_ref ): alive_threads = threading.enumerate() actor_threads = [ t for t in alive_threads if t.name.startswith('DaemonActor') ] assert len(actor_threads) == 1 assert actor_threads[0].daemon Pykka-2.0.2/tox.ini0000664000175000017500000000124213547724426014330 0ustar jodaljodal00000000000000[tox] envlist = py27, py35, py36, py37, py38, pypy, pypy3, docs, flake8, check-manifest, black [testenv] deps = eventlet gevent pytest pytest-cov pytest-mock commands = pytest \ --basetemp={envtmpdir} \ --cov=pykka --cov-report=term-missing \ {posargs} [testenv:docs] changedir = docs deps = sphinx sphinx_rtd_theme commands = sphinx-build -b html -d {envtmpdir}/doctrees . {envtmpdir}/html [testenv:flake8] deps = flake8 flake8-import-order commands = flake8 [testenv:check-manifest] deps = check-manifest commands = check-manifest [testenv:black] deps = black commands = black --check .