pax_global_header00006660000000000000000000000064146033551440014517gustar00rootroot0000000000000052 comment=4c9496fe79edaa4e1e2ae4f92776c29bab80117f circuits-3.2.3/000077500000000000000000000000001460335514400133515ustar00rootroot00000000000000circuits-3.2.3/.coveragerc000066400000000000000000000001221460335514400154650ustar00rootroot00000000000000[run] source = circuits [html] directory = coverage [xml] output = coverage.xml circuits-3.2.3/.dockerignore000066400000000000000000000001531460335514400160240ustar00rootroot00000000000000*~ tmp .git .tox .env dist .eggs *.pyc *.pyo *.bak *.xml *.sw? build .cache coverage .coverage* docs/build circuits-3.2.3/.github/000077500000000000000000000000001460335514400147115ustar00rootroot00000000000000circuits-3.2.3/.github/ISSUE_TEMPLATE.md000066400000000000000000000010711460335514400174150ustar00rootroot00000000000000### Expected Behaviour Please clearly describe the problem you are having and expected behaviour you believe you should be expecting. ### Steps to reproduce Please ideally provide a minimal viable reproducible example in code of the problem that we can clearly run and observe the broken behaviour or missing feature. If not please describe in as much clear detail how we can reproduce the problem ourselves. ### Environment and Platform - OS: Linux, Windows or BSD / OS X - Python: 2.7, 3.5, 3.6, 3.7, 3.8, 3.9, 3.10, PyPy - circuits: 3.2? Something older? ---- circuits-3.2.3/.github/PULL_REQUEST_TEMPLATE000066400000000000000000000002251460335514400201120ustar00rootroot00000000000000Please describe what this pull request implements, improves or fixes. Please be clear and concise but terse. We can read the code :) Fixes # circuits-3.2.3/.github/workflows/000077500000000000000000000000001460335514400167465ustar00rootroot00000000000000circuits-3.2.3/.github/workflows/pre-commit.yml000066400000000000000000000003471460335514400215510ustar00rootroot00000000000000name: pre-commit on: pull_request: push: branches: [main] jobs: pre-commit: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v3 - uses: pre-commit/action@v3.0.1 circuits-3.2.3/.github/workflows/python-app.yml000066400000000000000000000023771460335514400216010ustar00rootroot00000000000000# This workflow will install Python dependencies, run tests and lint with a single version of Python # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions name: circuits on: push: branches: [ master ] pull_request: branches: [ master ] jobs: build: runs-on: ubuntu-latest strategy: fail-fast: false matrix: python-version: - '3.7' - '3.8' - '3.9' - '3.10' - '3.11' - '3.12' # - 'pypy2' # FIXME: flaky # - 'pypy3' # FIXME: flaky steps: - uses: actions/checkout@v2 - name: Set up Python uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | python -m pip install --upgrade pip pip install -U pytest pip install setuptools-scm==5.0.2 if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - name: Install test requirements run: | pip install -Ur requirements-test.txt pip install -Ue . - name: Test with pytest run: | tox -e py -- --tb=native -vvv -l --cov --no-cov-on-fail --cov-report= circuits-3.2.3/.gitignore000066400000000000000000000002031460335514400153340ustar00rootroot00000000000000*~ tmp .git .tox .env dist .eggs *.pyc *.pyo *.bak *.xml *.sw? build .cache coverage .coverage* docs/build *.egg-info */version.py circuits-3.2.3/.pre-commit-config.yaml000066400000000000000000000025731460335514400176410ustar00rootroot00000000000000# See https://pre-commit.com for more information # See https://pre-commit.com/hooks.html for more hooks exclude: "^circuits/web/parsers/multipart.py$" repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.5.0 hooks: - id: check-added-large-files - id: check-json - id: check-xml - id: check-yaml - id: check-merge-conflict - id: pretty-format-json args: - --autofix - --no-ensure-ascii - id: trailing-whitespace - repo: https://github.com/pre-commit/pygrep-hooks rev: v1.10.0 hooks: # - id: python-check-blanket-noqa - id: python-no-eval exclude: "^tests/" - id: rst-backticks - id: rst-directive-colons - id: rst-inline-touching-normal - repo: https://github.com/pycqa/flake8 rev: '7.0.0' hooks: - id: flake8 exclude: "^docs/" - repo: https://github.com/astral-sh/ruff-pre-commit rev: 'v0.3.4' hooks: - id: ruff - id: ruff alias: "ruff-fix" stages: [ manual ] args: ["--fix", "--unsafe-fixes"] - id: ruff alias: "ruff-statistics" stages: [ manual ] args: ["--statistics"] - id: ruff alias: "ruff-isort" args: ["--select", "I", "--fix"] - id: ruff-format - id: ruff-format alias: "ruff-format-check" stages: [ manual ] args: ["--check"] circuits-3.2.3/CHANGES.rst000066400000000000000000000247431460335514400151650ustar00rootroot00000000000000:orphan: ========== Change Log ========== - :release:`3.2.3 <2024-04-04>` - :support:`281` Dropped support for Python 2.7, 3.5, 3.6 - :support:`327` Added support for Python 3.11, 3.12 - :bug:`-`` namespacing is now done with ``importlib`` instead of ``pkgutil.extend_path()`` - :bug:`-`` the code has been autoformatted and refactored with ``ruff`` via pre-commit - :bug:`-`` unused ``parse_body()``, ``dictform()``, ``is_ssl_handshake()`` and ``quoted_slash`` have been removed from ``circuits.web.utils`` - :release:`3.2.2 <2021-10-19>`` - :support:`298` Added support for Python 3.10 - :feature:`132` The initial ``request`` containing the ``session`` has been added to the websocket dispatcher events in ``circuits.web.websockets`` - :feature:`96` ``circuits.tools.graph()`` has been split into smaller functions (for creating ``dot``, ``ascii``, ``png`` separately) - :bug:`197` Exceptions during initialization of ``Poller`` is now handlded via an ``error`` Event - :bug:`197` Exceptions during ``socket.accept()`` are now re-raised in the main thread - :bug:`261` A workaroung for websocket clients has been added, which prevents that the first websocket is not lost - :bug:`307` Various format string syntaxes have been relaxed so that they don't cause exceptions on non string input - :bug:`-`` typos in docstrings/comments have been fixed - :bug:`293` (security) HTML escaping of error responses, 3XX redirects and Powered-by texts has been fixed - :bug:`251` (security) A HTTP header injection vulnerability in ``circuits.web.websockets.client`` has been fixed - :bug:`289` (security) potential XSS attacks via crafted files in directory listing is now prevented via HTML escaping in the ``circuits.web.dispatchers.static`` component. - :bug:`291` HTTP ``Connection`` header values are now correctly evaluated case insensitive in ``websocket`` components - :bug:`292` HTTP ``Connection`` header values are now correctly evaluated case insensitive in ``web.client`` components - :bug:`-`` Fixed Python 3 compatibility for ``circuits.web.tools.validate_etag()`` with ``MD5` hashes - :bug:`238` Reverted changes fixed by upstream ``http-parser`` library - :bug:`285` ``circuits.web.parsers.http`` has been upgraded to latest upstream ``http-parser`` version - :bug:`285` requests with chunked transfer encoding are not dispatched if the message body is not yet received completely - :bug:`253` ``circuits.io.serial``: add readline argument to only fire read events for full lines - :bug:`252` ``circuits.io.serial``: missing encoding parameter has been added - :release:`3.2.1 <2020-10-30>` - :support:`-` Added support for Python 3.6, 3.7, 3.8, 3.9-dev - :support:`152` Dropped the support for Python 2.6 and 3.x < 3.4 - :bug:`176` Generator expressions don't raise StopIteration anymore - :feature:`-` The exception handling has been improoved - :feature:`273` Added a ``bufsize`` argument to the ``__init__`` of BaseServer - :bug:`270` fix TLS support for websockets (unhandled SSLWantReadError) - :bug:`263` Improove error handling during TLS handshake - :bug:`269` Fix error handling when TLS handshake fails - :bug:`266` Fix python2 ``str(circuits.core.values.Value())`` - :bug:`264` Improoved robustness of IRC messages - :bug:`257` Fix WSGI component for Python 2.7 and Python 3 - :bug:`254` Fix CRLF injection in IRC protocol - :feature:`245` IRC: enhance stripping of colors - :feature:`249` Add ``irc.utils.irc_color_to_ansi()`` - :bug:`241` Adjust ``circuits.tools.graph()`` to API change in ``networkx`` - :feature:`240` Added ``auto_add`` to ``circuits.io.notify`` - :feature:`231` Add support for ``STOMP`` protocol - :bug:`238` Fix parsing HTTP request without headers - :bug:`235` the ``prefix`` in the ``Debugger`` might be a ``callable`` now - :feature:`233` ``circuits.core.values.Value`` is now ``__str__`` compatible with Python 2 - :feature:`212` Improves the API for session management and adds expire support - :feature:`224` Add new HTTP status code ``308 moved permanently`` (:rfc:`7538`) - :feature:`214` Implement ``STARTTLS`` for sockets as ``event`` - :feature:`-` Add support to set additional socket options - :bug:`198` Made pushing onto the event queue via ``fire`` threadsafe. - :feature:`202` Removed ``EventType`` metaclass - :bug:`-` Fixed ``manager.join()`` - :bug:`202` Removed the (unused) internal cache from ``EventType``. - :feature:`168` Add interface for selecting the websocket subprotocol - :bug:`54` Fix a memory leak due to ``on_done`` handlers - :bug:`-` Fix python3 compatibility when parsing ``HTTP`` request body - :bug:`-` Fix error handling if error contains traceback instance - :bug:`187` Fix parsing and decoding of ``application/x-www-urlencoded`` payloads - :bug:`185` Fix Denial of Service socket/memory leak for not connected clients - :bug:`184` Fix websocket data parsing if content is larger than BUFSIZE - :bug:`170` Fix crash from deleting undefined variables - :bug:`173` Fix the type difference between _current_thread and _flushing_thread - :bug:`123` Fixes bug in the ``complete`` event - :bug:`165` Fix ``Host`` HTTP header parsing when ``circuits.web.Server`` is bound to a ``UNIX`` Socket - :release:`3.2 <2016-06-02>` - :bug:`119` Fixed bug in ``circuits.web.url.parse_url()`` that caused a display issue with port bindings on ports 80 and 443. - :release:`3.1 <2014-11-01>` - :bug:`-` Bridge waits for event processing on the other side before proxy handler ends. Now it is possible to collect values from remote handlers in %_success event. - :bug:`-` Rename the FallbackErrorHandler to FallbackExceptionHandler and the event it listens to to exception - :bug:`-` Fixes optional parameters handling (client / server). - :bug:`-` Node: add peer node: return channel name. - :bug:`-` Node: add event firewall (client / server). - :bug:`-` Node: fixes the event value issue. - :bug:`-` Node: fixes event response flood. - :bug:`-` Node: Add node examples. - :bug:`-` Fixed import of FallBackExceptionHandler - :bug:`-` Fixed exception handing in circuits.web - :bug:`-` Fixed issue in brige with ommiting all but the first events sent at once - :bug:`-` Bridge: Do not propagate no results via bridge - :bug:`-` Bridge: Send exceptions via brige before change the exceptions weren't propagated via bridge because traceback object is not pickable, now traceback object is replaced by corresponding traceback list - :bug:`113` Fixed bug with forced shutdown of subprocesses in Windows. - :bug:`115` Fixed FallbackErrorHandler API Change - :release:`3.0.1 <2014-11-01>` - :support:`117` Fixed inconsistent top-level examples. - :support:`96` Link to ChangeLog from README - :release:`3.0 <2014-08-31>` - :bug:`111 major` Fixed broken Digest Auth Test for circuits.web - :feature:`112` Improved Signal Handling - :bug:`109 major` Fixed ``Event.create()`` factory and metaclass. - :feature:`108` Improved server support for the IRC Protocol. - :bug:`107 major` Added ``__le__`` and ``__ge__`` methods to ``circuits.web.wrappers.HTTPStatus`` - :bug:`106 major` Added ``__format__`` method to circuits.web.wrappers.HTTPStatus. - :bug:`104 major` Prevent other websockets sessions from closing. - :feature:`103` Added the firing of a ``disconnect`` event for the WebSocketsDispatcher. - :bug:`102 major` Fixed minor bug with WebSocketsDispatcher causing superflusous ``connect()`` events from being fired. - :bug:`100 major` Fixed returned Content-Type in JSON-RPC Dispatcher. - :feature:`99` Added Digest Auth support to the ``circuits.web`` CLI Tool - :feature:`98` Dockerized circuits. See: https://docker.io/ - :bug:`97 major` Fixed ``tests.net.test_tcp.test_lookup_failure`` test for Windows - :support:`95` Updated Developer Documentation with corrections and a new workflow. - :feature:`94` Modified the :class:`circuits.web.Logger` to use the ``response_success`` event. - :support:`86` Telnet Tutorial - :bug:`47 major` Dispatcher does not fully respect optional arguments. web - :support:`61` circuits.web documentation enhancements docs - :support:`85` Migrate away from ShiningPanda - :support:`87` A rendered example of ``circuits.tools.graph()``. docs - :support:`88` Document the implicit registration of components attached as class attributes docs - :bug:`89 major` Class attribtues that reference methods cause duplicate event handlers core - :support:`92` Update circuitsframework.com content docs - :support:`71` Document the value_changed event docs - :support:`78` Migrate Change Log maintenance and build to Releases - :bug:`91 major` Call/Wait and specific instances of events - :bug:`59 major` circuits.web DoS in serve_file (remote denial of service) web - :bug:`66 major` web examples jsonserializer broken web - :support:`73` Fix duplication in auto generated API Docs. docs - :support:`72` Update Event Filtering section of Users Manual docs - :bug:`76 major` Missing unit test for DNS lookup failures net - :support:`70` Convention around method names of event handlers - :support:`75` Document and show examples of using circuits.tools docs - :bug:`81 major` "index" method not serving / web - :bug:`77 major` Uncaught exceptions Event collides with sockets and others core - :support:`69` Merge #circuits-dev FreeNode Channel into #circuits - :support:`65` Update tutorial to match circuits 3.0 API(s) and Semantics docs - :support:`60` meantion @handler decorator in tutorial docs - :bug:`67 major` web example jsontool is broken on python3 web - :support:`63` typos in documentation docs - :bug:`53 major` WebSocketClient treating WebSocket data in same TCP segment as HTTP response as part the HTTP response. web - :bug:`62 major` Fix packaging and bump circuits 1.5.1 for @dsuch (*Dariusz Suchojad*) for `Zato `_ - :bug:`56 major` circuits.web HEAD request send response body web - :bug:`45 major` Fixed use of ``cmp()`` and ``__cmp__()`` for Python 3 compatibility. - :bug:`48 major` Allow ``event`` to be passed to the decorated function (*the request handler*) for circuits.web - :bug:`46 major` Set ``Content-Type`` header on response for errors. (circuits.web) - :bug:`38 major` Guard against invalid headers. (circuits.web) - :bug:`37 major` Fixed a typo in :class:`~circuits.io.file.File` Older Change Logs ================= For older Change Logs of previous versions of circuits please see the respective `PyPi `_ page(s): - `circuits-2.1.0 `_ - `circuits-2.0.1 `_ - `circuits-2.0.0 `_ - `circuits-1.6 `_ - `circuits-1.5 `_ circuits-3.2.3/CONTRIBUTING.md000066400000000000000000000107651460335514400156130ustar00rootroot00000000000000# Welcome to the contributing guide for circuits! ## Team members - [@spaceone](https://github.com/spaceone) (*Project Maintainer*) - James Mills [@prologic](https://github.com/prologic) (*Project Author*) - [@Osso](https://github.com/Osso) - [@treemo](https://github.com/treemo) - [@y0no](https://github.com/y0no) ## Learn & listen * Mailing list: [circuits-dev](https://groups.google.com/forum/#!forum/circuits-dev) * IRC channel: [#circuits](https://web.libera.chat/#circuits) on the [Libera.Chat IRC Network](https://libera.chat/) (``irc.libera.chat:6697`` TLS).\ * Blog: [James Mills circuits Blog](http://shortcircuit.net.au/~prologic/blog/tag/circuits/) ## Adding new features Got a great new feature you'd like to add? Great! First let's discuss it either on the [#circuits](https://web.libera.chat/#circuits) IRC Channel or create a new [Discussion Issue](https://github.com/circuits/circuits/issues/new). Once we're all on the same page and we've nutted down the design and requirements together let's get you hacking and take ownership of the new feature! * [Fork circuits](https://github.com/circuits/circuits/issues/14#fork-destination-box) * Clone your newly created fork: ```bash $ git clone git@github.com:myuser/circuits.git ``` * Create a new feature branch: ```bash $ git checkout -b my-feature master ``` * Hack on your feature with your favorite editor or IDE! * Commit and Push your changes up: ```bash $ git add -A $ git commit -m "my fancy new feature. Closes #xx" $ git push -u origin my-feature ``` * Create a new [Pull Request](https://github.com/circuits/circuits/compare/) That's it! Six easy steps to contributing a new feature! Generally we'll respond pretty quickly to new issues, pull requests and general discussions on IRC. So come and join us! # Reporting Bugs Found a bug? Great! We wants to help fix it! * File a new [Bug Report](https://github.com/circuits/circuits/issues/new) * Label it as a "Bug" When describing your bug report; please be concise and as detailed as you can so we can easily work out what the problem is. It's also very helpful if you are able to provide a test case that repeatedly demonstrates the bug at hand: Example: ```python from circuits import Event, Component class test(Event): """test Event""" class App(Component): message = None def started(self): self.message = "Hello World!" self.stop() def test(): App().run() assert app.message == "Hello World!" ``` Obviously this test *would pass* but this is ideally what we'd like to see bug reports in the form of; a reliable, repeatable way of demonstrating the bug. If you don't feel comfortable writing a test case; a good description is enough! (*We'll take care of the hard work of ensuring the bug never occurs again!*) # Documentation Please help us with [Documentation](http://circuits.readthedocs.org/) Our documentation is written in [reStructuredText](http://en.wikipedia.org/wiki/ReStructuredText) using the [Sphinx](http://sphinx-doc.org/) documentation toolkit. See: [Documentation Sources](https://github.com/circuits/circuits/tree/master/docs) You can contribute in these easy steps: 1. Navigate our [Documentation Sources](https://github.com/circuits/circuits/tree/master/docs) 2. Find a document you wish to improve. 3. Click on the Pen (*Edit this file*) button. 4. Make your changes and submit a new Pull Request using the Github editor See: [Editing files in another user's repository](https://help.github.com/articles/editing-files-in-another-user-s-repository/) # Community This section includes ideas on how non-developers can help with the project. Here's a few examples: * You can help us answer questions our users have here: [StackOverflow circuits-framework](http://stackoverflow.com/questions/tagged/circuits-framework) * You can help build and design our website here: [circuitsframework.com](https://github.com/circuits/circuitsframework.com) * You can help write blog posts about the project by: sharing them with the [Community](http://circuitsframework.com/Community) * You can tweet about your use of circuits and tag [@pythoncircuits](https://twitter.com/pythoncircuits) * Create an example of the project in real world by building something or showing what others have built. * Write about other people’s projects based on this project. Show how it’s used in daily life. Take screenshots and make videos! ---- If you have further questions, contact: [James Mills](mailto:prologic+circuits@shortcircuit.net.au) ([@therealprologic](https://twitter.com/therealprologic) circuits-3.2.3/Dockerfile000066400000000000000000000012701460335514400153430ustar00rootroot00000000000000# Docker Image for circuits # # This image essentially packages up the circuits # (a Python Application Framework*) and it's tool(s) # into a Docker Image/Container. # # You can also use this Image as a Base Image for all your # circuits Applications. # # Website: http://circuitsframework.com/ # PyPi: https://pypi.python.org/pypi/circuits # # Usage Examples(s):: # # $ docker run -d -v /path/to/www:/var/www prologic/circuits circuits.web /var/www # $ docker run -i -t prologic/circuits circuits.bench # # VERSION: 0.0.2 # # Last Updated: 20141115 FROM crux/python:onbuild MAINTAINER James Mills # Services EXPOSE 80 8000 443 # Volumes VOLUME /var/www circuits-3.2.3/LICENSE000066400000000000000000000021161460335514400143560ustar00rootroot00000000000000Copyright (C) 2004-2016 James Mills Circuits is covered by the MIT license:: Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. circuits-3.2.3/MANIFEST.in000066400000000000000000000003071460335514400151070ustar00rootroot00000000000000recursive-include man * recursive-include tests * include LICENSE README.rst CHANGES.rst prune docs prune fabfile prune examples prune setup.cfg global-exclude __pycache__ global-exclude *.py[co] circuits-3.2.3/README.md000066400000000000000000000066701460335514400146410ustar00rootroot00000000000000[![Build Status](https://github.com/circuits/circuits/actions/workflows/python-app.yml/badge.svg)](https://github.com/circuits/circuits/actions/workflows/python-app.yml) [![codecov](https://codecov.io/gh/circuits/circuits/branch/master/graph/badge.svg)](https://codecov.io/gh/circuits/circuits) circuits is a **Lightweight** **Event** driven and **Asynchronous** **Application Framework** for the [Python Programming Language](http://www.python.org/) with a strong **Component** Architecture. circuits also includes a lightweight, high performance and scalable HTTP/WSGI compliant web server as well as various I/O and Networking components. - [Website](http://circuitsframework.com/) - [Downloads](https://github.com/circuits/circuits/releases) - [Documentation](http://circuits.readthedocs.org/en/latest/) Got questions? - [Ask a Question](http://stackoverflow.com/questions/ask) (Tag it: `circuits-framework`) Examples ======== Features ======== - event driven - concurrency support - component architecture - asynchronous I/O components - no required external dependencies - full featured web framework (circuits.web) - coroutine based synchronization primitives Requirements ============ - circuits has no dependencies beyond the [Python Standard Library](http://docs.python.org/library/). Supported Platforms =================== - Linux, FreeBSD, Mac OS X, Windows - Python 3.7, 3.8, 3.9, 3.10, 3.11, 3.12 - pypy (the newer the better) Installation ============ The simplest and recommended way to install circuits is with pip. You may install the latest stable release from PyPI with pip: $ pip install circuits If you do not have pip, you may use easy\_install: $ easy_install circuits Alternatively, you may download the source package from the [PyPi](http://pypi.python.org/pypi/circuits) or the [Downloads](https://github.com/circuits/circuits/releases) extract it and install using: $ python setup.py install > **note** > > You can install the [development version](https://github.com/circuits/circuits/archive/master.zip#egg=circuits-dev) > via `pip install circuits==dev`. > License ======= circuits is licensed under the [MIT License](http://www.opensource.org/licenses/mit-license.php). Feedback ======== We welcome any questions or feedback about bugs and suggestions on how to improve circuits. Let us know what you think about circuits. [@pythoncircuits](http://twitter.com/pythoncircuits). Do you have suggestions for improvement? Then please [Create an Issue](https://github.com/circuits/circuits/issues/new) with details of what you would like to see. I'll take a look at it and work with you to either incorporate the idea or find a better solution. Community ========= There are also several places you can reach out to the circuits community: - [Mailing List](http://groups.google.com/group/circuits-users) - [\#circuits IRC Channel](https://web.libera.chat/#circuits) on the [Libera.Chat IRC Network](https://libera.chat) - [Ask a Question](http://stackoverflow.com/questions/ask) on [Stackoverflow](http://stackoverflow.com/) (Tag it: `circuits-framework`) ------------------------------------------------------------------------ Disclaimer ========== Whilst I (James Mills) continue to contribute and maintain the circuits project I do not represent the interests or business of my employer Facebook Inc. The contributions I make are of my own free time and have no bearing or relevance to Facebook Inc. circuits-3.2.3/README.rst000066400000000000000000000071641460335514400150500ustar00rootroot00000000000000.. _Python Programming Language: http://www.python.org/ .. _#circuits IRC Channel: https://web.libera.chat/#circuits .. _Libera.Chat IRC Network: https://libera.chat .. _Python Standard Library: http://docs.python.org/library/ .. _MIT License: http://www.opensource.org/licenses/mit-license.php .. _Create an Issue: https://github.com/circuits/circuits/issues/new .. _Mailing List: http://groups.google.com/group/circuits-users .. _Website: http://circuitsframework.com/ .. _PyPi: http://pypi.python.org/pypi/circuits .. _Documentation: http://circuits.readthedocs.org/en/latest/ .. _Downloads: https://github.com/circuits/circuits/releases .. _Ask a Question: http://stackoverflow.com/questions/ask .. _Stackoverflow: http://stackoverflow.com/ .. image:: https://github.com/circuits/circuits/actions/workflows/python-app.yml/badge.svg :target: https://github.com/circuits/circuits/actions/workflows/python-app.yml :alt: Build Status .. image:: https://codecov.io/gh/circuits/circuits/branch/master/graph/badge.svg :target: https://codecov.io/gh/circuits/circuits :alt: Coverage circuits is a **Lightweight** **Event** driven and **Asynchronous** **Application Framework** for the `Python Programming Language`_ with a strong **Component** Architecture. circuits also includes a lightweight, high performance and scalable HTTP/WSGI compliant web server as well as various I/O and Networking components. - `Website`_ - `Downloads`_ - `Documentation`_ Got questions? - `Ask a Question`_ (Tag it: ``circuits-framework``) Examples -------- .. include:: examples/index.rst Features -------- - event driven - concurrency support - component architecture - asynchronous I/O components - no required external dependencies - full featured web framework (circuits.web) - coroutine based synchronization primitives Requirements ------------ - circuits has no dependencies beyond the `Python Standard Library`_. Supported Platforms ------------------- - Linux, FreeBSD, Mac OS X, Windows - Python 3.7, 3.8, 3.9, 3.10, 3.11, 3.12 - pypy (the newer the better) Installation ------------ The simplest and recommended way to install circuits is with pip. You may install the latest stable release from PyPI with pip:: $ pip install circuits If you do not have pip, you may use easy_install:: $ easy_install circuits Alternatively, you may download the source package from the `PyPi`_ or the `Downloads`_ extract it and install using:: $ python setup.py install .. note:: You can install the `development version `_ via ``pip install circuits==dev``. License ------- circuits is licensed under the `MIT License`_. Feedback -------- We welcome any questions or feedback about bugs and suggestions on how to improve circuits. Let us know what you think about circuits. `@pythoncircuits `_. Do you have suggestions for improvement? Then please `Create an Issue`_ with details of what you would like to see. I'll take a look at it and work with you to either incorporate the idea or find a better solution. Community --------- There are also several places you can reach out to the circuits community: - `Mailing List`_ - `#circuits IRC Channel`_ on the `Libera.Chat IRC Network`_ - `Ask a Question`_ on `Stackoverflow`_ (Tag it: ``circuits-framework``) ---- Disclaimer ---------- Whilst I (James Mills) continue to contribute and maintain the circuits project I do not represent the interests or business of my employer Facebook Inc. The contributions I make are of my own free time and have no bearing or relevance to Facebook Inc. circuits-3.2.3/bin/000077500000000000000000000000001460335514400141215ustar00rootroot00000000000000circuits-3.2.3/bin/circuits.bench000077500000000000000000000116521460335514400167570ustar00rootroot00000000000000#!/usr/bin/env python """ (Tool) Bench Marking Tool THis tool does some simple benchmaking of the circuits library. """ import math import optparse import sys from time import sleep from circuits import Component, Debugger, Event, Manager, __version__ as systemVersion, handler if sys.platform == 'win32': from time import clock as time else: from time import time # NOQA try: import hotshot import hotshot.stats except ImportError: hotshot = None # NOQA try: import psyco except ImportError: psyco = None # NOQA USAGE = '%prog [options]' VERSION = '%prog v' + systemVersion def duration(seconds): days = int(seconds / 60 / 60 / 24) seconds = (seconds) % (60 * 60 * 24) hours = int(seconds / 60 / 60) seconds = (seconds) % (60 * 60) mins = int(seconds / 60) seconds = int((seconds) % (60)) return (days, hours, mins, seconds) def parse_options(): parser = optparse.OptionParser(usage=USAGE, version=VERSION) parser.add_option( '-t', '--time', action='store', type='int', default=0, dest='time', help='Stop after specified elapsed seconds' ) parser.add_option( '-e', '--events', action='store', type='int', default=0, dest='events', help='Stop after specified number of events' ) parser.add_option( '-p', '--profile', action='store_true', default=False, dest='profile', help='Enable execution profiling support' ) parser.add_option('-d', '--debug', action='store_true', default=False, dest='debug', help='Enable debug mode') parser.add_option( '-m', '--mode', action='store', type='choice', default='speed', dest='mode', choices=['sync', 'speed', 'latency'], help='Operation mode', ) parser.add_option( '-s', '--speed', action='store_true', default=False, dest='speed', help='Enable psyco (circuits on speed!)' ) parser.add_option('-q', '--quiet', action='store_false', default=True, dest='verbose', help='Suppress output') opts, args = parser.parse_args() return opts, args class stop(Event): """stop Event""" class term(Event): """term Event""" class hello(Event): """hello Event""" class received(Event): """received Event""" class Base(Component): def __init__(self, opts, *args, **kwargs): super().__init__(*args, **kwargs) self.opts = opts class SpeedTest(Base): def received(self, message=''): self.fire(hello('hello')) def hello(self, message): self.fire(received(message)) class LatencyTest(Base): t = None def received(self, message=''): print('Latency: %0.9f us' % ((time() - self.t) * 1e6)) sleep(1) self.fire(hello('hello')) def hello(self, message=''): self.t = time() self.fire(received(message)) class State(Base): done = False def stop(self): self.fire(term()) def term(self): self.done = True class Monitor(Base): sTime = sys.maxsize events = 0 state = 0 @handler(filter=True) def event(self, *args, **kwargs): self.events += 1 if self.events > self.opts.events: self.stop() def main(): opts, _args = parse_options() if opts.speed and psyco: psyco.full() manager = Manager() monitor = Monitor(opts) manager += monitor state = State(opts) manager += state if opts.debug: manager += Debugger() if opts.mode.lower() == 'speed': if opts.verbose: print('Setting up Speed Test...') manager += SpeedTest(opts) monitor.sTime = time() elif opts.mode.lower() == 'latency': if opts.verbose: print('Setting up Latency Test...') manager += LatencyTest(opts) monitor.sTime = time() if opts.verbose: print('Setting up Sender...') print('Setting up Receiver...') monitor.sTime = time() if opts.profile: if hotshot: profiler = hotshot.Profile('bench.prof') profiler.start() manager.fire(hello('hello')) while not state.done: try: manager.tick() if opts.events > 0 and monitor.events > opts.events: manager.fire(stop()) if opts.time > 0 and (time() - monitor.sTime) > opts.time: manager.fire(stop()) except KeyboardInterrupt: manager.fire(stop()) if opts.verbose: print() eTime = time() tTime = eTime - monitor.sTime events = monitor.events speed = int(math.ceil(float(monitor.events) / tTime)) print('Total Events: %d (%d/s after %0.2fs)' % (events, speed, tTime)) if opts.profile and hotshot: profiler.stop() profiler.close() stats = hotshot.stats.load('bench.prof') stats.strip_dirs() stats.sort_stats('time', 'calls') stats.print_stats(20) if __name__ == '__main__': main() circuits-3.2.3/bin/htpasswd000077500000000000000000000113711460335514400157070ustar00rootroot00000000000000#!/usr/bin/env python """ Pure Python replacement for Apache's htpasswd Borrowed from: https://gist.github.com/eculver/1420227 Modifications by James Mills, prologic at shortcircuit dot net dot au - Added support for MD5 and SHA1 hashing. """ # Original author: Eli Carter import os import random import sys from hashlib import md5, sha1 from optparse import OptionParser try: from crypt import crypt except ImportError: try: from fcrypt import crypt except ImportError: crypt = None def salt(): """Returns a string of 2 randome letters""" letters = 'abcdefghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' '0123456789/.' return random.choice(letters) + random.choice(letters) class HtpasswdFile: """A class for manipulating htpasswd files.""" def __init__(self, filename, create=False, encryption=None): self.filename = filename if encryption is None: self.encryption = lambda p: md5(p).hexdigest() else: self.encryption = encryption self.entries = [] if not create: if os.path.exists(self.filename): self.load() else: raise Exception('%s does not exist' % self.filename) def load(self): """Read the htpasswd file into memory.""" lines = open(self.filename).readlines() self.entries = [] for line in lines: username, pwhash = line.split(':') entry = [username, pwhash.rstrip()] self.entries.append(entry) def save(self): """Write the htpasswd file to disk""" open(self.filename, 'w').writelines([f'{entry[0]}:{entry[1]}\n' for entry in self.entries]) def update(self, username, password): """Replace the entry for the given user, or add it if new.""" pwhash = self.encryption(password) matching_entries = [entry for entry in self.entries if entry[0] == username] if matching_entries: matching_entries[0][1] = pwhash else: self.entries.append([username, pwhash]) def delete(self, username): """Remove the entry for the given user.""" self.entries = [entry for entry in self.entries if entry[0] != username] def main(): """ %prog [-c] -b filename username password Create or update an htpasswd file """ # For now, we only care about the use cases that affect tests/functional.py parser = OptionParser(usage=main.__doc__) parser.add_option( '-b', action='store_true', dest='batch', default=False, help='Batch mode; password is passed on the command line IN THE CLEAR.', ) parser.add_option( '-c', action='store_true', dest='create', default=False, help='Create a new htpasswd file, overwriting any existing file.', ) parser.add_option( '-D', action='store_true', dest='delete_user', default=False, help='Remove the given user from the password file.' ) if crypt is not None: parser.add_option('-d', action='store_true', dest='crypt', default=False, help='Use crypt() encryption for passwords.') parser.add_option('-m', action='store_true', dest='md5', default=False, help='Use MD5 encryption for passwords. (Default)') parser.add_option('-s', action='store_true', dest='sha', default=False, help='Use SHA encryption for passwords.') options, args = parser.parse_args() def syntax_error(msg): """ Utility function for displaying fatal error messages with usage help. """ sys.stderr.write('Syntax error: ' + msg) sys.stderr.write(parser.get_usage()) sys.exit(1) if not options.batch: syntax_error('Only batch mode is supported\n') # Non-option arguments if len(args) < 2: syntax_error('Insufficient number of arguments.\n') filename, username = args[:2] if options.delete_user: if len(args) != 2: syntax_error('Incorrect number of arguments.\n') password = None else: if len(args) != 3: syntax_error('Incorrect number of arguments.\n') password = args[2] if options.crypt: def encryption(p): return crypt(p, salt()) elif options.md5: def encryption(p): return md5(p).hexdigest() elif options.sha: def encryption(p): return sha1(p).hexdigest() else: def encryption(p): return md5(p).hexdigest() passwdfile = HtpasswdFile(filename, create=options.create, encryption=encryption) if options.delete_user: passwdfile.delete(username) else: passwdfile.update(username, password) passwdfile.save() if __name__ == '__main__': main() circuits-3.2.3/circuits/000077500000000000000000000000001460335514400151765ustar00rootroot00000000000000circuits-3.2.3/circuits/__init__.py000066400000000000000000000030271460335514400173110ustar00rootroot00000000000000"""Lightweight Event driven and Asynchronous Application Framework circuits is a **Lightweight** **Event** driven and **Asynchronous** **Application Framework** for the `Python Programming Language`_ with a strong **Component** Architecture. :copyright: CopyRight (C) 2004-2016 by James Mills :license: MIT (See: LICENSE) .. _Python Programming Language: http://www.python.org/ """ __author__ = 'James Mills' __date__ = '24th February 2013' try: from .version import version as __version__ except ImportError: __version__ = 'unknown' from .core import ( BaseComponent, Bridge, Component, Debugger, Event, Loader, Manager, TimeoutError, Timer, Worker, handler, ipc, reprhandler, sleep, task, ) # See http://peak.telecommunity.com/DevCenter/setuptools#namespace-packages try: __import__('pkg_resources').declare_namespace(__name__) except ImportError: import importlib.metadata import sys try: namespace_pkg = importlib.metadata.distribution(__name__) except importlib.metadata.PackageNotFoundError: pass else: namespace_pkg_files = namespace_pkg.files if namespace_pkg_files: for file in namespace_pkg_files: if str(file).endswith('__init__.py'): namespace_pkg_path = str(file).split('__init__.py')[0] if namespace_pkg_path not in sys.path: sys.path.append(namespace_pkg_path) break # flake8: noqa # pylama:skip=1 circuits-3.2.3/circuits/app/000077500000000000000000000000001460335514400157565ustar00rootroot00000000000000circuits-3.2.3/circuits/app/__init__.py000066400000000000000000000004211460335514400200640ustar00rootroot00000000000000"""Application Components Contains various components useful for application development and tasks common to applications. """ from .daemon import Daemon from .dropprivileges import DropPrivileges __all__ = ('Daemon', 'DropPrivileges') # flake8: noqa # pylama: skip=1 circuits-3.2.3/circuits/app/daemon.py000066400000000000000000000061711460335514400176000ustar00rootroot00000000000000""" Daemon Component Component to daemonize a system into the background and detach it from its controlling PTY. Supports PID file writing, logging stdin, stdout and stderr and changing the current working directory. """ from os import _exit, chdir, closerange, dup2, fork, getpid, remove, setsid, umask from os.path import isabs from resource import RLIM_INFINITY, RLIMIT_NOFILE, getrlimit from sys import stderr, stdin, stdout from circuits.core import Component, Event, handler class daemonize(Event): """daemonize Event""" class daemonized(Event): """daemonized Event""" class deletepid(Event): """deletepid Event""" class writepid(Event): """writepid Event""" class Daemon(Component): """ Daemon Component :param pidfile: .pid filename :type pidfile: str or unicode :param stdin: filename to log stdin :type stdin: str or unicode :param stdout: filename to log stdout :type stdout: str or unicode :param stderr: filename to log stderr :type stderr: str or unicode """ channel = 'daemon' def init(self, pidfile, path='/', stdin=None, stdout=None, stderr=None, channel=channel): assert isabs(path), 'path must be absolute' self.pidfile = pidfile self.path = path self.stdin = stdin if stdin is not None and isabs(stdin) else '/dev/null' self.stdout = stdout if stdout is not None and isabs(stdout) else '/dev/null' self.stderr = stderr if stderr is not None and isabs(stderr) else '/dev/null' def deletepid(self): remove(self.pidfile) def writepid(self): with open(self.pidfile, 'w') as fd: fd.write(str(getpid())) def daemonize(self): try: pid = fork() if pid > 0: # exit first parent _exit(0) except OSError as e: stderr.write(f'fork #1 failed: {e.errno:d} ({e})\n') raise SystemExit(1) # decouple from parent environment chdir(self.path) setsid() umask(0) # do second fork try: pid = fork() if pid > 0: # exit from second parent _exit(0) except OSError as e: stderr.write(f'fork #2 failed: {e.errno:d} ({e})\n') raise SystemExit(1) # redirect standard file descriptors stdout.flush() stderr.flush() maxfd = getrlimit(RLIMIT_NOFILE)[1] if maxfd == RLIM_INFINITY: maxfd = 2048 closerange(0, maxfd) si = open(self.stdin) so = open(self.stdout, 'a+') se = open(self.stderr, 'a+') dup2(si.fileno(), stdin.fileno()) dup2(so.fileno(), stdout.fileno()) dup2(se.fileno(), stderr.fileno()) self.fire(writepid()) self.fire(daemonized(self)) def registered(self, component, manager): if component == self and manager.root.running: self.fire(daemonize()) @handler('started', priority=100.0, channel='*') def on_started(self, component): if component is not self: self.fire(daemonize()) circuits-3.2.3/circuits/app/dropprivileges.py000066400000000000000000000025521460335514400213720ustar00rootroot00000000000000from grp import getgrnam from os import getuid, setgid, setgroups, setuid, umask from pwd import getpwnam from traceback import format_exc from circuits.core import BaseComponent, handler class DropPrivileges(BaseComponent): def init(self, user='nobody', group='nobody', umask=0o077, **kwargs): self.user = user self.group = group self.umask = umask def drop_privileges(self): if getuid() > 0: # Running as non-root. Ignore. return try: # Get the uid/gid from the name uid = getpwnam(self.user).pw_uid gid = getgrnam(self.group).gr_gid except KeyError as error: print(f'ERROR: Could not drop privileges {error}') print(format_exc()) raise SystemExit(-1) try: # Remove group privileges setgroups([]) # Try setting the new uid/gid setgid(gid) setuid(uid) if self.umask is not None: umask(self.umask) except Exception as error: print(f'ERROR: Could not drop privileges {error}') print(format_exc()) raise SystemExit(-1) @handler('ready', channel='*') def on_ready(self, server, bind): try: self.drop_privileges() finally: self.unregister() circuits-3.2.3/circuits/core/000077500000000000000000000000001460335514400161265ustar00rootroot00000000000000circuits-3.2.3/circuits/core/__init__.py000066400000000000000000000012261460335514400202400ustar00rootroot00000000000000"""Core This package contains the essential core parts of the circuits framework. """ from .bridge import Bridge, ipc from .components import BaseComponent, Component from .debugger import Debugger from .events import Event from .handlers import handler, reprhandler from .loader import Loader from .manager import Manager, TimeoutError, sleep from .timers import Timer from .values import Value from .workers import Worker, task __all__ = ( 'handler', 'BaseComponent', 'Component', 'Event', 'task', 'Worker', 'ipc', 'Bridge', 'Debugger', 'Timer', 'Manager', 'TimeoutError', ) # flake8: noqa # pylama: skip=1 circuits-3.2.3/circuits/core/bridge.py000066400000000000000000000104651460335514400177420ustar00rootroot00000000000000""" Bridge The Bridge Component is used for inter-process communications between processes. Bridge is used internally when a Component is started in "process mode" via :meth:`circuits.core.manager.start`. Typically a Pipe is used as the socket transport between two sides of a Bridge (*there must be a :class:`~Bridge` instance on both sides*). """ import traceback from pickle import dumps, loads from .components import BaseComponent from .events import Event, exception from .handlers import handler from .values import Value _sentinel = b'~~~' class ipc(Event): """ ipc Event Send an event to a child/parent process """ def __init__(self, event, channel=None): """ :param event: Event to execute remotely. :type event: :class:`circuits.core.events.Event` :param channel: IPC Channel (channel to use on child/parent). :type channel: str """ super().__init__(event, channel=channel) class Bridge(BaseComponent): channel = 'bridge' def init(self, socket, channel=channel): self._buffer = b'' self._socket = socket self._values = {} if self._socket is not None: self._socket.register(self) def _process_packet(self, eid, obj): if isinstance(obj, Event): obj.remote = True obj.notify = 'value_changed' obj.waitingHandlers = 0 value = self.fire(obj) self._values[value] = eid elif isinstance(obj, Value): if obj.result: if isinstance(obj.value, list): for item in obj.value: self._values[eid].value = item else: self._values[eid].value = obj.value event = Event.create(Bridge.__waiting_event(eid)) event.remote = True self.fire(event, self.channel) @handler('value_changed', channel='*') def _on_value_changed(self, value): try: eid = self._values[value] if value.errors: Bridge.__adapt_error_value(value) self.__write(eid, value) except Exception: pass @handler('read') def _on_read(self, data): self._buffer += data items = self._buffer.split(_sentinel) if items[-1] != '': self._buffer = items.pop() for item in filter(None, items): self._process_packet(*loads(item)) def __send(self, eid, event): try: if isinstance(event, exception): Bridge.__adapt_exception(event) self._values[eid] = event.value self.__write(eid, event) except Exception: pass def __write(self, eid, data): self._socket.write(dumps((eid, data)) + _sentinel) @handler('ipc') def _on_ipc(self, event, ipc_event, channel=None): """ Send event to a child/parentprocess Event handler to run an event on a child/parent process (the event definition is :class:`circuits.core.bridge.ipc`) :param event: The event triggered (by the handler) :type event: :class:`circuits.node.events.remote` :param ipc_event: Event to execute in child/parent process. :type ipc_event: :class:`circuits.core.events.Event` :param channel: Remote channel (channel to use on peer). :type channel: str :return: The result of remote event :rtype: generator :Example: ``# hello is your event to execute in the child process result = yield self.fire(ipc(hello())) print(result.value)`` """ ipc_event.channels = (channel,) if channel is not None else event.channels event.value.value = ipc_event.value = Value(ipc_event, self) eid = hash(ipc_event) self.__send(eid, ipc_event) yield self.wait(Bridge.__waiting_event(eid)) @staticmethod def __waiting_event(eid): return '%s_done' % eid @staticmethod def __adapt_exception(ex): fevent_value = ex.kwargs['fevent'].value Bridge.__adapt_error_value(fevent_value) @staticmethod def __adapt_error_value(value): if not isinstance(value[2], list): value._value = (value[0], value[1], traceback.extract_tb(value[2])) circuits-3.2.3/circuits/core/components.py000066400000000000000000000204311460335514400206650ustar00rootroot00000000000000"""This module defines the BaseComponent and its subclass Component.""" from collections.abc import Callable from inspect import getmembers from itertools import chain from types import MethodType from .events import Event, registered, unregistered from .handlers import HandlerMetaClass, handler from .manager import Manager class prepare_unregister(Event): """ This event is fired when a component is about to be unregistered from the component tree. Unregistering a component actually detaches the complete subtree that the unregistered component is the root of. Components that need to know if they are removed from the main tree (e.g. because they maintain relationships to other components in the tree) handle this event, check if the component being unregistered is one of their ancestors and act accordingly. :param component: the component that will be unregistered :type type: :class:`~.BaseComponent` """ complete = True def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) def in_subtree(self, component): """ Convenience method that checks if the given *component* is in the subtree that is about to be detached. """ while True: if component == self.args[0]: return True if component == component.root: return False component = component.parent class BaseComponent(Manager): """ This is the base class for all components in a circuits based application. Components can (and should, except for root components) be registered with a parent component. BaseComponents can declare methods as event handlers using the handler decoration (see :func:`circuits.core.handlers.handler`). The handlers are invoked for matching events from the component's channel (specified as the component's ``channel`` attribute). BaseComponents inherit from :class:`circuits.core.manager.Manager`. This provides components with the :func:`circuits.core.manager.Manager.fireEvent` method that can be used to fire events as the result of some computation. Apart from the ``fireEvent()`` method, the Manager nature is important for root components that are started or run. :ivar channel: a component can be associated with a specific channel by setting this attribute. This should either be done by specifying a class attribute *channel* in the derived class or by passing a keyword parameter *channel="..."* to *__init__*. If specified, the component's handlers receive events on the specified channel only, and events fired by the component will be sent on the specified channel (this behavior may be overridden, see :class:`~circuits.core.events.Event`, :meth:`~.fireEvent` and :func:`~circuits.core.handlers.handler`). By default, the channel attribute is set to "*", meaning that events are fired on all channels and received from all channels. """ channel = '*' def __new__(cls, *args, **kwargs): self = super().__new__(cls) handlers = {k: v for k, v in list(cls.__dict__.items()) if getattr(v, 'handler', False)} def overridden(x): return x in handlers and handlers[x].override for base in cls.__bases__: if issubclass(cls, base): for k, v in list(base.__dict__.items()): p1 = isinstance(v, Callable) p2 = getattr(v, 'handler', False) p3 = overridden(k) if p1 and p2 and not p3: name = f'{base.__name__}_{k}' method = MethodType(v, self) setattr(self, name, method) return self def __init__(self, *args, **kwargs): """Initializes x; see x.__class__.__doc__ for signature""" super().__init__(*args, **kwargs) self.channel = kwargs.get('channel', self.channel) or '*' for _k, v in getmembers(self): if getattr(v, 'handler', False) is True: self.addHandler(v) # TODO: Document this feature. See Issue #88 if v is not self and isinstance(v, BaseComponent) and v not in ('parent', 'root'): v.register(self) if hasattr(self, 'init') and isinstance(self.init, Callable): self.init(*args, **kwargs) @handler('prepare_unregister_complete', channel=self) def _on_prepare_unregister_complete(self, event, e, value): self._do_prepare_unregister_complete(event.parent, value) self.addHandler(_on_prepare_unregister_complete) def register(self, parent): """ Inserts this component in the component tree as a child of the given *parent* node. :param parent: the parent component after registration has completed. :type parent: :class:`~.manager.Manager` This method fires a :class:`~.events.Registered` event to inform other components in the tree about the new member. """ self.parent = parent self.root = parent.root # Make sure that structure is consistent before firing event # because event may be handled in a concurrent thread. if parent is not self: parent.registerChild(self) self._updateRoot(parent.root) self.fire(registered(self, self.parent)) else: self._updateRoot(parent.root) return self def unregister(self): """ Removes this component from the component tree. Removing a component from the component tree is a two stage process. First, the component is marked as to be removed, which prevents it from receiving further events, and a :class:`~.components.prepare_unregister` event is fired. This allows other components to e.g. release references to the component to be removed before it is actually removed from the component tree. After the processing of the ``prepare_unregister`` event has completed, the component is removed from the tree and an :class:`~.events.unregistered` event is fired. """ if self.unregister_pending or self.parent is self: return self # tick shouldn't be called anymore, although component is still in tree self._unregister_pending = True self.root._cache_needs_refresh = True # Give components a chance to prepare for unregister evt = prepare_unregister(self) evt.complete_channels = (self,) self.fire(evt) return self @property def unregister_pending(self): return getattr(self, '_unregister_pending', False) def _do_prepare_unregister_complete(self, e, value): # Remove component from tree now delattr(self, '_unregister_pending') self.fire(unregistered(self, self.parent)) if self.parent is not self: self.parent.unregisterChild(self) self.parent = self self._updateRoot(self) return self def _updateRoot(self, root): self.root = root for c in self.components: c._updateRoot(root) @classmethod def handlers(cls): """Returns a list of all event handlers for this Component""" return list({getattr(cls, k) for k in dir(cls) if getattr(getattr(cls, k), 'handler', False)}) @classmethod def events(cls): """Returns a list of all events this Component listens to""" handlers = (getattr(cls, k).names for k in dir(cls) if getattr(getattr(cls, k), 'handler', False)) return list({name for name in chain(*handlers) if not name.startswith('_')}) @classmethod def handles(cls, *names): """Returns True if all names are event handlers of this Component""" return all(name in cls.events() for name in names) Component = HandlerMetaClass('Component', (BaseComponent,), {}) """ If you use Component instead of BaseComponent as base class for your own component class, then all methods that are not marked as private (i.e: start with an underscore) are automatically decorated as handlers. The methods are invoked for all events from the component's channel where the event's name matches the method's name. """ circuits-3.2.3/circuits/core/debugger.py000066400000000000000000000070371460335514400202730ustar00rootroot00000000000000""" Debugger component used to debug each event in a system by printing each event to sys.stderr or to a Logger Component instance. """ import os import sys from signal import SIGINT, SIGTERM from traceback import format_exc, format_exception_only from .components import BaseComponent from .handlers import handler, reprhandler class Debugger(BaseComponent): """ Create a new Debugger Component Creates a new Debugger Component that listens to all events in the system printing each event to sys.stderr or a Logger Component. :var IgnoreEvents: list of events (str) to ignore :var IgnoreChannels: list of channels (str) to ignore :var enabled: Enabled/Disabled flag :param log: Logger Component instance or None (*default*) """ IgnoreEvents = ['generate_events'] IgnoreChannels = [] def __init__(self, errors=True, events=True, file=None, logger=None, prefix=None, trim=None, **kwargs): """Initializes x; see x.__class__.__doc__ for signature""" super().__init__() self._errors = errors self._events = events if isinstance(file, str): self.file = open(os.path.abspath(os.path.expanduser(file)), 'a') elif hasattr(file, 'write'): self.file = file else: self.file = sys.stderr self.logger = logger self.prefix = prefix self.trim = trim self.IgnoreEvents.extend(kwargs.get('IgnoreEvents', [])) self.IgnoreChannels.extend(kwargs.get('IgnoreChannels', [])) @handler('signal', channel='*') def _on_signal(self, signo, stack): if signo in [SIGINT, SIGTERM]: raise SystemExit(0) @handler('exception', channel='*', priority=100.0) def _on_exception(self, error_type, value, traceback, handler=None, fevent=None): if not self._errors: return s = [] handler = '' if handler is None else reprhandler(handler) msg = f'ERROR {handler} ({fevent!r}) ({error_type!r}): {value!r}\n' s.append(msg) s.append('Traceback (most recent call last):\n') s.extend(traceback) s.extend(format_exception_only(error_type, value)) s.append('\n') if self.logger is not None: self.logger.error(''.join(s)) else: try: self.file.write(''.join(s)) self.file.flush() except OSError: pass @handler(priority=101.0) def _on_event(self, event, *args, **kwargs): """ Global Event Handler Event handler to listen to all events printing each event to self.file or a Logger Component instance by calling self.logger.debug """ try: if not self._events: return channels = event.channels if event.name in self.IgnoreEvents: return if all(channel in self.IgnoreChannels for channel in channels): return s = repr(event) if self.prefix: s = f'{self.prefix()}: {s}' if hasattr(self.prefix, '__call__') else f'{self.prefix}: {s}' if self.trim: s = '%s ...>' % s[: self.trim] if self.logger is not None: self.logger.debug(s) else: self.file.write(s) self.file.write('\n') self.file.flush() except Exception as e: sys.stderr.write(f'ERROR (Debugger): {e}') sys.stderr.write(f'{format_exc()}') circuits-3.2.3/circuits/core/events.py000066400000000000000000000252071460335514400200120ustar00rootroot00000000000000"""This module defines the basic event class and common events.""" from inspect import ismethod from traceback import format_tb class Event: channels = () 'The channels this message is sent to.' parent = None notify = False success = False failure = False complete = False alert_done = False waitingHandlers = 0 @classmethod def create(cls, _name, *args, **kwargs): return type(cls)(_name, (cls,), {})(*args, **kwargs) def child(self, name, *args, **kwargs): e = Event.create(f'{self.name:s}_{name:s}', *args, **kwargs) e.parent = self return e def __init__(self, *args, **kwargs): """ An event is a message send to one or more channels. It is eventually dispatched to all components that have handlers for one of the channels and the event type. All normal arguments and keyword arguments passed to the constructor of an event are passed on to the handler. When declaring a handler, its argument list must therefore match the arguments used for creating the event. Every event has a :attr:`name` attribute that is used for matching the event with the handlers. :cvar channels: an optional attribute that may be set before firing the event. If defined (usually as a class variable), the attribute specifies the channels that the event should be delivered to as a tuple. This overrides the default behavior of sending the event to the firing component's channel. When an event is fired, the value in this attribute is replaced for the instance with the channels that the event is actually sent to. This information may be used e.g. when the event is passed as a parameter to a handler. :ivar value: this is a :class:`circuits.core.values.Value` object that holds the results returned by the handlers invoked for the event. :var success: if this optional attribute is set to ``True``, an associated event ``success`` (original name with "_success" appended) will automatically be fired when all handlers for the event have been invoked successfully. :var success_channels: the success event is, by default, delivered to same channels as the successfully dispatched event itself. This may be overridden by specifying an alternative list of destinations using this attribute. :var complete: if this optional attribute is set to ``True``, an associated event ``complete`` (original name with "_complete" appended) will automatically be fired when all handlers for the event and all events fired by these handlers (recursively) have been invoked successfully. :var complete_channels: the complete event is, by default, delivered to same channels as the initially dispatched event itself. This may be overridden by specifying an alternative list of destinations using this attribute. """ self.args = list(args) self.kwargs = kwargs self.uid = None self.value = None self.handler = None self.stopped = False self.cancelled = False if not hasattr(self, 'name'): self.name = self.__class__.__name__ def __getstate__(self): odict = self.__dict__.copy() del odict['handler'] return odict def __setstate__(self, dict): self.__dict__.update(dict) def __le__(self, other): return False def __gt__(self, other): return False def __repr__(self): """x.__repr__() <==> repr(x)""" if len(self.channels) > 1: channels = repr(self.channels) elif len(self.channels) == 1: channels = str(self.channels[0]) else: channels = '' data = '%s %s' % ( ', '.join(repr(arg) for arg in self.args), ', '.join(f'{k}={v!r}' for k, v in self.kwargs.items()), ) return f'<{self.name}[{channels}] ({data})>' def __getitem__(self, x): """ x.__getitem__(y) <==> x[y] Get and return data from the event object requested by "x". If an int is passed to x, the requested argument from self.args is returned index by x. If a str is passed to x, the requested keyword argument from self.kwargs is returned keyed by x. Otherwise a TypeError is raised as nothing else is valid. """ if isinstance(x, int): return self.args[x] if isinstance(x, str): return self.kwargs[x] raise TypeError('Expected int or str, got %r' % type(x)) def __setitem__(self, i, y): """ x.__setitem__(i, y) <==> x[i] = y Modify the data in the event object requested by "x". If i is an int, the ith requested argument from self.args shall be changed to y. If i is a str, the requested value keyed by i from self.kwargs, shall by changed to y. Otherwise a TypeError is raised as nothing else is valid. """ if isinstance(i, int): self.args[i] = y elif isinstance(i, str): self.kwargs[i] = y else: raise TypeError('Expected int or str, got %r' % type(i)) def cancel(self): """Cancel the event from being processed (if not already)""" self.cancelled = True def stop(self): """Stop further processing of this event""" self.stopped = True class exception(Event): """ exception Event This event is sent for any exceptions that occur during the execution of an event Handler that is not SystemExit or KeyboardInterrupt. :param type: type of exception :type type: type :param value: exception object :type value: exceptions.Exceptions :param traceback: traceback of exception :type traceback: traceback :param handler: handler that raised the exception :type handler: @handler() :param fevent: event that failed :type fevent: event """ def __init__(self, type, value, traceback, handler=None, fevent=None): super().__init__(type, value, self.format_traceback(traceback), handler=handler, fevent=fevent) def format_traceback(self, traceback): return format_tb(traceback) class started(Event): """ started Event This Event is sent when a Component or Manager has started running. :param manager: The component or manager that was started :type manager: Component or Manager """ def __init__(self, manager): super().__init__(manager) class stopped(Event): """ stopped Event This Event is sent when a Component or Manager has stopped running. :param manager: The component or manager that has stopped :type manager: Component or Manager """ def __init__(self, manager): super().__init__(manager) class signal(Event): """ signal Event This Event is sent when a Component receives a signal. :param signo: The signal number received. :type int: An int value for the signal :param stack: The interrupted stack frame. :type object: A stack frame """ def __init__(self, signo, stack): super().__init__(signo, stack) class registered(Event): """ registered Event This Event is sent when a Component has registered with another Component or Manager. This Event is only sent if the Component or Manager being registered which is not itself. :param component: The Component being registered :type component: Component :param manager: The Component or Manager being registered with :type manager: Component or Manager """ def __init__(self, component, manager): super().__init__(component, manager) class unregistered(Event): """ unregistered Event This Event is sent when a Component has been unregistered from its Component or Manager. """ class generate_events(Event): """ generate_events Event This Event is sent by the circuits core. All components that generate timed events or events from external sources (e.g. data becoming available) should fire any pending events in their "generate_events" handler. The handler must either call :meth:`~stop` (*preventing other handlers from being called in the same iteration*) or must invoke :meth:`~.reduce_time_left` with parameter 0. :param max_wait: maximum time available for generating events. :type time_left: float Components that actually consume time waiting for events to be generated, thus suspending normal execution, must provide a method ``resume`` that interrupts waiting for events. """ def __init__(self, lock, max_wait): super().__init__() self._time_left = max_wait self._lock = lock @property def time_left(self): """ The time left for generating events. A value less than 0 indicates unlimited time. You should have only one component in your system (usually a poller component) that spends up to "time left" until it generates an event. """ return self._time_left def reduce_time_left(self, time_left): """ Update the time left for generating events. This is typically used by event generators that currently don't want to generate an event but know that they will within a certain time. By reducing the time left, they make sure that they are reinvoked when the time for generating the event has come (at the latest). This method can only be used to reduce the time left. If the parameter is larger than the current value of time left, it is ignored. If the time left is reduced to 0 and the event is currently being handled, the handler's *resume* method is invoked. """ with self._lock: if time_left >= 0 and (self._time_left < 0 or self._time_left > time_left): self._time_left = time_left if self._time_left == 0 and self.handler is not None: m = getattr( getattr( self.handler, 'im_self', self.handler.__self__, ), 'resume', None, ) if m is not None and ismethod(m): m() @property def lock(self): return self._lock circuits-3.2.3/circuits/core/handlers.py000066400000000000000000000110611460335514400202770ustar00rootroot00000000000000"""This module define the @handler decorator/function and the HandlesType type.""" from collections.abc import Callable from circuits.tools import getargspec def handler(*names, **kwargs): """ Creates an Event Handler This decorator can be applied to methods of classes derived from :class:`circuits.core.components.BaseComponent`. It marks the method as a handler for the events passed as arguments to the ``@handler`` decorator. The events are specified by their name. The decorated method's arguments must match the arguments passed to the :class:`circuits.core.events.Event` on creation. Optionally, the method may have an additional first argument named *event*. If declared, the event object that caused the handler to be invoked is assigned to it. By default, the handler is invoked by the component's root :class:`~.manager.Manager` for events that are propagated on the channel determined by the BaseComponent's *channel* attribute. This may be overridden by specifying a different channel as a keyword parameter of the decorator (``channel=...``). Keyword argument ``priority`` influences the order in which handlers for a specific event are invoked. The higher the priority, the earlier the handler is executed. If you want to override a handler defined in a base class of your component, you must specify ``override=True``, else your method becomes an additional handler for the event. **Return value** Normally, the results returned by the handlers for an event are simply collected in the :class:`circuits.core.events.Event`'s :attr:`value` attribute. As a special case, a handler may return a :class:`types.GeneratorType`. This signals to the dispatcher that the handler isn't ready to deliver a result yet. Rather, it has interrupted it's execution with a ``yield None`` statement, thus preserving its current execution state. The dispatcher saves the returned generator object as a task. All tasks are reexamined (i.e. their :meth:`next()` method is invoked) when the pending events have been executed. This feature avoids an unnecessarily complicated chaining of event handlers. Imagine a handler A that needs the results from firing an event E in order to complete. Then without this feature, the final action of A would be to fire event E, and another handler for an event ``SuccessE`` would be required to complete handler A's operation, now having the result from invoking E available (actually it's even a bit more complicated). Using this "suspend" feature, the handler simply fires event E and then yields ``None`` until e.g. it finds a result in E's :attr:`value` attribute. For the simplest scenario, there even is a utility method :meth:`circuits.core.manager.Manager.callEvent` that combines firing and waiting. """ def wrapper(f): if names and isinstance(names[0], bool) and not names[0]: f.handler = False return f f.handler = True f.names = names f.priority = kwargs.get('priority', 0) f.channel = kwargs.get('channel', None) f.override = kwargs.get('override', False) args = getargspec(f)[0] if args and args[0] == 'self': del args[0] f.event = getattr(f, 'event', bool(args and args[0] == 'event')) return f return wrapper class Unknown: """Unknown Dummy Component""" def reprhandler(handler): format = '' channel = getattr(handler, 'channel', '*') if channel is None: channel = '*' from circuits.core.manager import Manager if isinstance(channel, Manager): channel = '' names = ','.join(handler.names) instance = getattr(handler, 'im_self', getattr(handler, '__self__', Unknown())).__class__.__name__ method = handler.__name__ priority = f'[{handler.priority:0.2f}]' if handler.priority else '' return format % (channel, names, priority, instance, method) class HandlerMetaClass(type): def __init__(cls, name, bases, ns): super().__init__(name, bases, ns) callables = (x for x in ns.items() if isinstance(x[1], Callable)) for name, callable in callables: if not (name.startswith('_') or hasattr(callable, 'handler')): try: setattr(cls, name, handler(name)(callable)) except ValueError as e: raise ValueError(f'{e!s} - {cls!r} {name}') circuits-3.2.3/circuits/core/helpers.py000066400000000000000000000066711460335514400201540ustar00rootroot00000000000000""".. codeauthor: mnl""" from signal import SIGINT, SIGTERM from sys import stderr from threading import Event from traceback import format_exception_only from circuits.core.handlers import reprhandler from .components import BaseComponent from .handlers import handler class FallBackGenerator(BaseComponent): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._continue = Event() @handler('generate_events', priority=-100) def _on_generate_events(self, event): """ Fall back handler for the :class:`~.events.GenerateEvents` event. When the queue is empty a GenerateEvents event is fired, here we sleep for as long as possible to avoid using extra cpu cycles. A poller would override this with a higher priority handler. e.g: ``@handler("generate_events", priority=0)`` and provide a different way to idle when the queue is empty. """ with event.lock: if event.time_left == 0: event.stop() self._continue.clear() if event.time_left > 0: # If we get here, there is no component with work to be # done and no new event. But some component has requested # to be checked again after a certain timeout. self._continue.wait(event.time_left) # Either time is over or _continue has been set, which # implies resume has been called, which means that # reduce_time_left(0) has been called. So calling this # here is OK in any case. event.reduce_time_left(0) event.stop() while event.time_left < 0: # If we get here, there was no work left to do when creating # the GenerateEvents event and there is no other handler that # is prepared to supply new events within a limited time. The # application will continue only if some other Thread fires # an event. # # Python ignores signals when waiting without timeout. self._continue.wait(10000) event.stop() def resume(self): """ Implements the resume method as required from components that handle :class:`~.events.GenerateEvents`. """ self._continue.set() class FallBackExceptionHandler(BaseComponent): """ If there is no handler for error events in the component hierarchy, this component's handler is added automatically. It simply prints the error information on stderr. """ @handler('exception', channel='*') def _on_exception(self, error_type, value, traceback, handler=None, fevent=None): s = [] handler = '' if handler is None else reprhandler(handler) msg = f'ERROR {handler} ({fevent!r}) ({error_type!r}): {value!r}\n' s.append(msg) s.append('Traceback (most recent call last):\n') s.extend(traceback) s.extend(format_exception_only(error_type, value)) s.append('\n') stderr.write(''.join(s)) class FallBackSignalHandler(BaseComponent): """ If there is no handler for signal events in the component hierarchy, this component's handler is added automatically. It simply terminates the system if the signal is SIGINT or SIGTERM. """ @handler('signal', channel='*') def _on_signal(self, signo, stack): if signo in [SIGINT, SIGTERM]: raise SystemExit(0) circuits-3.2.3/circuits/core/loader.py000066400000000000000000000032361460335514400177520ustar00rootroot00000000000000""" This module implements a generic Loader suitable for dynamically loading components from other modules. This supports loading from local paths, eggs and zip archives. Both setuptools and distribute are fully supported. """ import sys from inspect import getmembers, getmodule, isclass from .components import BaseComponent from .handlers import handler from .utils import safeimport class Loader(BaseComponent): """ Create a new Loader Component Creates a new Loader Component that enables dynamic loading of components from modules either in local paths, eggs or zip archives. """ channel = 'loader' def __init__(self, auto_register=True, init_args=None, init_kwargs=None, paths=None, channel=channel): """Initializes x; see x.__class__.__doc__ for signature""" super().__init__(channel=channel) self._auto_register = auto_register self._init_args = init_args or () self._init_kwargs = init_kwargs or {} if paths: for path in paths: sys.path.insert(0, path) @handler('load') def load(self, name): module = safeimport(name) if module is not None: def test(x): return isclass(x) and issubclass(x, BaseComponent) and getmodule(x) is module components = [x[1] for x in getmembers(module, test)] if components: TheComponent = components[0] component = TheComponent(*self._init_args, **self._init_kwargs) if self._auto_register: component.register(self) return component return None return None circuits-3.2.3/circuits/core/manager.py000066400000000000000000001004341460335514400201140ustar00rootroot00000000000000"""This module defines the Manager class.""" import _thread import atexit import contextlib import types from collections import deque from heapq import heappop, heappush from inspect import isfunction from itertools import chain, count from multiprocessing import Process, current_process from operator import attrgetter from os import getpid, kill from signal import SIGINT, SIGTERM, signal as set_signal_handler from sys import exc_info as _exc_info, stderr from threading import RLock, Thread, current_thread from time import time from traceback import format_exc from types import GeneratorType from uuid import uuid4 as uuid from .events import Event, exception, generate_events, signal, started, stopped from .handlers import handler from .values import Value try: from signal import SIGKILL except ImportError: SIGKILL = SIGTERM TIMEOUT = 0.1 # 100ms timeout when idle class UnregistrableError(Exception): """Raised if a component cannot be registered as child.""" class TimeoutError(Exception): """Raised if wait event timeout occurred""" class CallValue: def __init__(self, value): self.value = value class ExceptionWrapper: def __init__(self, exception): self.exception = exception def extract(self): return self.exception class Sleep: def __init__(self, seconds): self._task = None try: self.expiry = time() + float(seconds) except ValueError: raise TypeError('a float is required') def __iter__(self): return self def __repr__(self): return f'sleep({self.expiry - time()!r})' def __next__(self): if time() >= self.expiry: raise StopIteration() return self @property def expired(self): return time() >= self.expiry @property def task(self): return self._task @task.setter def task(self, task): self._task = task def sleep(seconds): """ Delay execution of a coroutine for a given number of seconds. The argument may be a floating point number for subsecond precision. """ return Sleep(seconds) class Dummy: channel = None _dummy = Dummy() del Dummy class _State: __slots__ = ('event', 'flag', 'parent', 'run', 'task', 'task_event', 'tick_handler', 'timeout') def __init__(self, timeout): self.task = None self.run = False self.flag = False self.event = None self.timeout = timeout self.parent = None self.task_event = None self.tick_handler = None class _EventQueue: __slots__ = ('_counter', '_flush_batch', '_priority_queue', '_queue') def __init__(self): self._queue = deque() self._priority_queue = [] self._counter = count() self._flush_batch = 0 def __len__(self): return len(self._queue) + len(self._priority_queue) def drainFrom(self, other_queue): self._queue.extend(other_queue._queue) other_queue._queue.clear() # Queue is currently flushing events /o\ assert not len(other_queue._priority_queue) def append(self, event, channel, priority): self._queue.append((priority, next(self._counter), (event, channel))) def dispatchEvents(self, dispatcher): if self._flush_batch == 0: # FIXME: Might be faster to use heapify instead of pop + # heappush. Though, with regards to thread safety this # appears to be the better approach. self._flush_batch = count = len(self._queue) while count: count -= 1 heappush(self._priority_queue, self._queue.popleft()) while self._flush_batch > 0: self._flush_batch -= 1 # Decrement first! (event, channels) = heappop(self._priority_queue)[2] dispatcher(event, channels, self._flush_batch) class Manager: """ The manager class has two roles. As a base class for component implementation, it provides methods for event and handler management. The method :meth:`.fireEvent` appends a new event at the end of the event queue for later execution. :meth:`.waitEvent` suspends the execution of a handler until all handlers for a given event have been invoked. :meth:`.callEvent` combines the last two methods in a single method. The methods :meth:`.addHandler` and :meth:`.removeHandler` allow handlers for events to be added and removed dynamically. (The more common way to register a handler is to use the :func:`~.handlers.handler` decorator or derive the class from :class:`~.components.Component`.) In its second role, the :class:`.Manager` takes the role of the event executor. Every component hierarchy has a root component that maintains a queue of events. Firing an event effectively means appending it to the event queue maintained by the root manager. The :meth:`.flush` method removes all pending events from the queue and, for each event, invokes all the handlers. Usually, :meth:`.flush` is indirectly invoked by :meth:`run`. The manager optionally provides information about the execution of events as automatically generated events. If an :class:`~.events.Event` has its :attr:`success` attribute set to True, the manager fires a :class:`~.events.Success` event if all handlers have been executed without error. Note that this event will be enqueued (and dispatched) immediately after the events that have been fired by the event's handlers. So the success event indicates both the successful invocation of all handlers for the event and the processing of the immediate follow-up events fired by those handlers. Sometimes it is not sufficient to know that an event and its immediate follow-up events have been processed. Rather, it is important to know when all state changes triggered by an event, directly or indirectly, have been performed. This also includes the processing of events that have been fired when invoking the handlers for the follow-up events and the processing of events that have again been fired by those handlers and so on. The completion of the processing of an event and all its direct or indirect follow-up events may be indicated by a :class:`~.events.Complete` event. This event is generated by the manager if :class:`~.events.Event` has its :attr:`complete` attribute set to True. Apart from the event queue, the root manager also maintains a list of tasks, actually Python generators, that are updated when the event queue has been flushed. """ _currently_handling = None """ The event currently being handled. """ def __init__(self, *args, **kwargs): """Initializes x; see x.__class__.__doc__ for signature""" self._queue = _EventQueue() self._tasks = set() self._cache = {} self._globals = set() self._handlers = {} self._flush_batch = 0 self._cache_needs_refresh = False self._executing_thread = None self._flushing_thread = None self._running = False self.__thread = None self.__process = None self._lock = RLock() self.root = self.parent = self self.components = set() def __nonzero__(self): """x.__nonzero__() <==> bool(x)""" return True __bool__ = __nonzero__ def __repr__(self): """x.__repr__() <==> repr(x)""" name = self.__class__.__name__ channel = '/{}'.format(getattr(self, 'channel', '')) q = len(self._queue) state = 'R' if self.running else 'S' pid = current_process().pid id = f'{pid}:{current_thread().name}' if pid else current_thread().name format = '<%s%s %s (queued=%d) [%s]>' return format % (name, channel, id, q, state) def __contains__(self, y): """ x.__contains__(y) <==> y in x Return True if the Component y is registered. """ components = self.components.copy() return y in components or y in [c.__class__ for c in components] def __len__(self): """ x.__len__() <==> len(x) Returns the number of events in the Event Queue. """ return len(self._queue) def __add__(self, y): """ x.__add__(y) <==> x+y (Optional) Convenience operator to register y with x Equivalent to: y.register(x) @return: x @rtype Component or Manager """ y.register(self) return self def __iadd__(self, y): """ x.__iadd__(y) <==> x += y (Optional) Convenience operator to register y with x Equivalent to: y.register(x) @return: x @rtype Component or Manager """ y.register(self) return self def __sub__(self, y): """ x.__sub__(y) <==> x-y (Optional) Convenience operator to unregister y from x.parent Equivalent to: y.unregister() @return: x @rtype Component or Manager """ if y.parent is not y: y.unregister() return self def __isub__(self, y): """ x.__sub__(y) <==> x -= y (Optional) Convenience operator to unregister y from x Equivalent to: y.unregister() @return: x @rtype Component or Manager """ if y.parent is not y: y.unregister() return self @property def name(self): """Return the name of this Component/Manager""" return self.__class__.__name__ @property def running(self): """Return the running state of this Component/Manager""" return self._running @property def pid(self): """Return the process id of this Component/Manager""" return getpid() if self.__process is None else self.__process.pid def getHandlers(self, event, channel, **kwargs): name = event.name handlers = set() _handlers = set() _handlers.update(self._handlers.get('*', [])) _handlers.update(self._handlers.get(name, [])) for _handler in _handlers: handler_channel = _handler.channel if handler_channel is None: # TODO: Why do we care about the event handler's channel? # This probably costs us performance for what? # I've not ever had to rely on this in practice... handler_channel = getattr( getattr(_handler, 'im_self', getattr(_handler, '__self__', _dummy)), 'channel', None, ) if channel == '*' or handler_channel in ('*', channel) or channel is self: handlers.add(_handler) if not kwargs.get('exclude_globals', False): handlers.update(self._globals) for c in self.components.copy(): handlers.update(c.getHandlers(event, channel, **kwargs)) return handlers def addHandler(self, f): method = types.MethodType(f, self) if isfunction(f) else f setattr(self, method.__name__, method) if not method.names and method.channel == '*': self._globals.add(method) elif not method.names: self._handlers.setdefault('*', set()).add(method) else: for name in method.names: self._handlers.setdefault(name, set()).add(method) self.root._cache_needs_refresh = True return method def removeHandler(self, method, event=None): names = method.names if event is None else [event] for name in names: self._handlers[name].remove(method) if not self._handlers[name]: del self._handlers[name] try: delattr(self, method.__name__) except AttributeError: # Handler was never part of self pass self.root._cache_needs_refresh = True def registerChild(self, component): if component._executing_thread is not None: if self.root._executing_thread is not None: raise UnregistrableError() self.root._executing_thread = component._executing_thread component._executing_thread = None self.components.add(component) self.root._queue.drainFrom(component._queue) self.root._cache_needs_refresh = True def unregisterChild(self, component): self.components.remove(component) self.root._cache_needs_refresh = True def _fire(self, event, channel, priority=0): # check if event is fired while handling an event th = self._executing_thread or self._flushing_thread if _thread.get_ident() == (th.ident if th else None) and not isinstance(event, signal): if self._currently_handling is not None and getattr(self._currently_handling, 'cause', None): # if the currently handled event wants to track the # events generated by it, do the tracking now event.cause = self._currently_handling event.effects = 1 self._currently_handling.effects += 1 self._queue.append(event, channel, priority) # the event comes from another thread else: # Another thread has provided us with something to do. # If the component is running, we must make sure that # any pending generate event waits no longer, as there # is something to do now. with self._lock: # Modifications of attribute self._currently_handling # (in _dispatch()), calling reduce_time_left(0). and adding an # event to the (empty) event queue must be atomic, so we have # to lock. We can save the locking around # self._currently_handling = None though, but then need to copy # it to a local variable here before performing a sequence of # operations that assume its value to remain unchanged. handling = self._currently_handling self._queue.append(event, channel, priority) if isinstance(handling, generate_events): handling.reduce_time_left(0) def fireEvent(self, event, *channels, **kwargs): """ Fire an event into the system. :param event: The event that is to be fired. :param channels: The channels that this event is delivered on. If no channels are specified, the event is delivered to the channels found in the event's :attr:`channel` attribute. If this attribute is not set, the event is delivered to the firing component's channel. And eventually, when set neither, the event is delivered on all channels ("*"). """ if not channels: channels = event.channels or (getattr(self, 'channel', '*'),) event.channels = channels event.value = Value(event, self) self.root._fire(event, channels, **kwargs) return event.value fire = fireEvent def registerTask(self, g): self.root._tasks.add(g) def unregisterTask(self, g): if g in self.root._tasks: self.root._tasks.remove(g) def waitEvent(self, event, *channels, **kwargs): # noqa # TODO: C901: This has a high McCabe complexity score of 16. # TODO: Refactor this method. if isinstance(event, Event): event_object = event event_name = event.name channels = event.channels or channels else: event_object = None event_name = event state = _State(timeout=kwargs.get('timeout', -1)) def _on_event(self, event, *args, **kwargs): if not state.run and (event_object is None or event is event_object): self.removeHandler(_on_event_handler, event_name) event.alert_done = True state.run = True state.event = event def _on_done(self, event, *args, **kwargs): if state.event == event.parent: state.flag = True self.registerTask((state.task_event, state.task, state.parent)) if state.timeout > 0: self.removeHandler(state.tick_handler, 'generate_events') def _on_tick(self): if state.timeout == 0: self.registerTask( ( state.task_event, (e for e in (ExceptionWrapper(TimeoutError()),)), state.parent, ), ) self.removeHandler(_on_done_handler, '%s_done' % event_name) self.removeHandler(_on_tick_handler, 'generate_events') elif state.timeout > 0: state.timeout -= 1 if not channels: channels = (None,) for channel in channels: _on_event_handler = self.addHandler(handler(event_name, channel=channel)(_on_event)) _on_done_handler = self.addHandler(handler('%s_done' % event_name, channel=channel)(_on_done)) if state.timeout >= 0: _on_tick_handler = state.tick_handler = self.addHandler(handler('generate_events', channel=channel)(_on_tick)) yield state self.removeHandler(_on_done_handler, '%s_done' % event_name) if state.event is not None: yield CallValue(state.event.value) wait = waitEvent def callEvent(self, event, *channels, **kwargs): """ Fire the given event to the specified channels and suspend execution until it has been dispatched. This method may only be invoked as argument to a ``yield`` on the top execution level of a handler (e.g. "``yield self.callEvent(event)``"). It effectively creates and returns a generator that will be invoked by the main loop until the event has been dispatched (see :func:`circuits.core.handlers.handler`). """ value = self.fire(event, *channels) yield from self.waitEvent(event, *event.channels, **kwargs) yield CallValue(value) call = callEvent def _flush(self): # Handle events currently on queue, but none of the newly generated # events. Note that _flush can be called recursively. old_flushing = self._flushing_thread try: self._flushing_thread = current_thread() self._queue.dispatchEvents(self._dispatcher) finally: self._flushing_thread = old_flushing def flushEvents(self): """ Flush all Events in the Event Queue. If called on a manager that is not the root of an object hierarchy, the invocation is delegated to the root manager. """ self.root._flush() flush = flushEvents def _dispatcher(self, event, channels, remaining): # noqa # TODO: C901: This has a high McCabe complexity score of 22. # TODO: Refactor this method. if event.cancelled: return if event.complete: if not getattr(event, 'cause', None): event.cause = event event.effects = 1 # event itself counts (must be done) eargs = event.args ekwargs = event.kwargs if self._cache_needs_refresh: # Don't call self._cache.clear() from other threads, # this may interfere with cache rebuild. self._cache.clear() self._cache_needs_refresh = False try: # try/except is fastest if successful in most cases event_handlers = self._cache[(event.name, channels)] except KeyError: h = (self.getHandlers(event, channel) for channel in channels) event_handlers = sorted( chain(*h), key=attrgetter('priority'), reverse=True, ) if isinstance(event, generate_events): from .helpers import FallBackGenerator event_handlers.append(FallBackGenerator()._on_generate_events) elif isinstance(event, exception) and len(event_handlers) == 0: from .helpers import FallBackExceptionHandler event_handlers.append(FallBackExceptionHandler()._on_exception) elif isinstance(event, signal) and len(event_handlers) == 0: from .helpers import FallBackSignalHandler event_handlers.append(FallBackSignalHandler()._on_signal) self._cache[(event.name, channels)] = event_handlers if isinstance(event, generate_events): with self._lock: self._currently_handling = event if remaining > 0 or len(self._queue) or not self._running: event.reduce_time_left(0) elif self._tasks: event.reduce_time_left(TIMEOUT) # From now on, firing an event will reduce time left # to 0, which prevents event handlers from waiting (or wakes # them up with resume if they should be waiting already) else: self._currently_handling = event value = None err = None for event_handler in event_handlers: event.handler = event_handler try: value = event_handler(event, *eargs, **ekwargs) if event_handler.event else event_handler(*eargs, **ekwargs) except KeyboardInterrupt: self.stop() except SystemExit as e: self.stop(e.code) except BaseException: value = err = _exc_info() event.value.errors = True if event.failure: self.fire(event.child('failure', event, err), *event.channels) self.fire(exception(*err, handler=event_handler, fevent=event)) if value is not None: if isinstance(value, GeneratorType): event.waitingHandlers += 1 event.value.promise = True self.registerTask((event, value, None)) else: event.value.value = value # it is kind of a temporal hack to allow processing # of tasks, added in one of event handlers here if isinstance(event, generate_events) and self._tasks: event.reduce_time_left(TIMEOUT) if event.stopped: break # Stop further event processing self._currently_handling = None self._eventDone(event, err) def _eventDone(self, event, err=None): if event.waitingHandlers: return # The "%s_done" event is for internal use by waitEvent only. # Use the "%s_success" event in your application if you are # interested in being notified about the last handler for # an event having been invoked. if event.alert_done: self.fire(event.child('done', event.value.value), *event.channels) if err is None and event.success: channels = getattr(event, 'success_channels', event.channels) self.fire(event.child('success', event, event.value.value), *channels) while True: # cause attributes indicates interest in completion event cause = getattr(event, 'cause', None) if not cause: break # event takes part in complete detection (as nested or root event) event.effects -= 1 if event.effects > 0: break # some nested events remain to be completed if event.complete: # does this event want signaling? self.fire( event.child('complete', event, event.value.value), *getattr(event, 'complete_channels', event.channels), ) # this event and nested events are done now delattr(event, 'cause') delattr(event, 'effects') # cause has one of its nested events done, decrement and check event = cause def _signal_handler(self, signo, stack): self.fire(signal(signo, stack)) def start(self, process=False, link=None): """ Start a new thread or process that invokes this manager's ``run()`` method. The invocation of this method returns immediately after the task or process has been started. """ if process: # Parent<->Child Bridge if link is not None: from circuits.core.bridge import Bridge from circuits.net.sockets import Pipe channels = (uuid(),) * 2 parent, child = Pipe(*channels) bridge = Bridge(parent, channel=channels[0]).register(link) args = (child,) else: args = () bridge = None self.__process = Process(target=self.run, args=args, name=self.name) self.__process.daemon = True self.__process.start() return self.__process, bridge self.__thread = Thread(target=self.run, name=self.name) self.__thread.daemon = True self.__thread.start() return self.__thread, None def join(self): if self.__thread is not None: return self.__thread.join() if self.__process is not None: return self.__process.join() return None def stop(self, code=None): """ Stop this manager. Invoking this method causes an invocation of ``run()`` to return. """ if self.__process not in (None, current_process()) and self.__process.is_alive(): self.__process.terminate() self.__process.join(TIMEOUT) if self.__process.is_alive(): kill(self.__process.pid, SIGKILL) if not self.running: return self._running = False self.fire(stopped(self)) if self.root._executing_thread is None: for _ in range(3): self.tick() if code is not None: raise SystemExit(code) def processTask(self, event, task, parent=None): # noqa # TODO: C901: This has a high McCabe complexity score of 16. # TODO: Refactor this method. value = None try: value = next(task) if isinstance(value, CallValue): # Done here, next() will StopIteration anyway self.unregisterTask((event, task, parent)) # We are in a callEvent value = parent.send(value.value) if isinstance(value, GeneratorType): # We loose a yield but we gain one, # we don't need to change # event.waitingHandlers # The below code is delegated to handlers # in the waitEvent generator # self.registerTask((event, value, parent)) task_state = next(value) task_state.task_event = event task_state.task = value task_state.parent = parent else: event.waitingHandlers -= 1 if value is not None: event.value.value = value self.registerTask((event, parent, None)) elif isinstance(value, GeneratorType): event.waitingHandlers += 1 self.unregisterTask((event, task, None)) # First yielded value is always the task state task_state = next(value) task_state.task_event = event task_state.task = value task_state.parent = task # The below code is delegated to handlers # in the waitEvent generator # self.registerTask((event, value, task)) # TODO: ^^^ Why is this commented out anyway? elif isinstance(value, ExceptionWrapper): self.unregisterTask((event, task, parent)) if parent: value = parent.throw(value.extract()) if value is not None: value_generator = (val for val in (value,)) self.registerTask((event, value_generator, parent)) else: raise value.extract() elif isinstance(value, Sleep): if value is not task: value.task = (event, task, parent) self.registerTask((event, value, parent)) self.unregisterTask((event, task, parent)) elif value is not None: event.value.value = value except StopIteration: event.waitingHandlers -= 1 self.unregisterTask((event, task, parent)) if parent: self.registerTask((event, parent, None)) elif hasattr(task, 'task'): # TODO: The subtask is considered a "waiting handler" event.waitingHandlers += 1 self.registerTask(task.task) elif event.waitingHandlers == 0: event.value.inform(True) self._eventDone(event) except KeyboardInterrupt: self.stop() except SystemExit as e: self.stop(e.code) except BaseException: self.unregisterTask((event, task, parent)) err = _exc_info() event.value.value = err event.value.errors = True event.value.inform(True) if event.failure: self.fire(event.child('failure', event, err), *event.channels) self.fire(exception(*err, handler=None, fevent=event)) def tick(self, timeout=-1): """ Execute all possible actions once. Process all registered tasks and flush the event queue. If the application is running fire a GenerateEvents to get new events from sources. This method is usually invoked from :meth:`~.run`. It may also be used to build an application specific main loop. :param timeout: the maximum waiting time spent in this method. If negative, the method may block until at least one action has been taken. :type timeout: float, measuring seconds """ # process tasks if self._tasks: for task in self._tasks.copy(): self.processTask(*task) if self._running: self.fire(generate_events(self._lock, timeout), '*') if len(self._queue): self.flush() def run(self, socket=None): """ Run this manager. The method fires the :class:`~.events.Started` event and then continuously calls :meth:`~.tick`. The method returns when the manager's :meth:`~.stop` method is invoked. If invoked by a programs main thread, a signal handler for the ``INT`` and ``TERM`` signals is installed. This handler fires the corresponding :class:`~.events.Signal` events and then calls :meth:`~.stop` for the manager. """ atexit.register(self.stop) if current_thread().name == 'MainThread': try: set_signal_handler(SIGINT, self._signal_handler) set_signal_handler(SIGTERM, self._signal_handler) except ValueError: # Ignore if we can't install signal handlers pass self._running = True self.root._executing_thread = current_thread() # Setup Communications Bridge if socket is not None: from circuits.core.bridge import Bridge Bridge(socket, channel=socket.channel).register(self) self.fire(started(self)) try: while self.running or len(self._queue): self.tick() # Fading out, handle remaining work from stop event for _ in range(3): self.tick() except Exception as exc: stderr.write(f'Unhandled ERROR: {exc}\n') stderr.write(format_exc()) finally: with contextlib.suppress(Exception): self.tick() self.root._executing_thread = None self.__thread = None self.__process = None circuits-3.2.3/circuits/core/pollers.py000066400000000000000000000353741460335514400201740ustar00rootroot00000000000000""" Poller Components for asynchronous file and socket I/O. This module contains Poller components that enable polling of file or socket descriptors for read/write events. Pollers: - Select - Poll - EPoll """ import contextlib import os import platform import select import sys from errno import EBADF, EINTR from socket import AF_INET, SOCK_STREAM, create_connection, socket from threading import Thread from circuits.core.handlers import handler from .components import BaseComponent from .events import Event class _read(Event): """_read Event""" class _write(Event): """_write Event""" class _error(Event): """_error Event""" class _disconnect(Event): """_disconnect Event""" class BasePoller(BaseComponent): channel = None def __init__(self, channel=channel): super().__init__(channel=channel) self._read = [] self._write = [] self._targets = {} self._ctrl_recv, self._ctrl_send = self._create_control_con() def _create_control_con(self): if platform.system() == 'Linux': return os.pipe() server = socket(AF_INET, SOCK_STREAM) server.bind(('localhost', 0)) server.listen(1) res_list = [] exc = [] def accept(): try: sock, _ = server.accept() sock.setblocking(False) res_list.append(sock) except OSError: exc.append(sys.exc_info()) at = Thread(target=accept) at.start() clnt_sock = create_connection(server.getsockname()) at.join() if exc: raise exc[1].with_traceback(exc[2]) return (res_list[0], clnt_sock) @handler('generate_events', priority=-9) def _on_generate_events(self, event): """ Pollers have slightly higher priority than the default handler from Manager to ensure that they are invoked before the default handler. They act as event filters to avoid the additional invocation of the default handler which would be unnecessary overhead. """ event.stop() self._generate_events(event) def resume(self): if isinstance(self._ctrl_send, socket): self._ctrl_send.send(b'\0') else: os.write(self._ctrl_send, b'\0') def _read_ctrl(self): try: if isinstance(self._ctrl_recv, socket): return self._ctrl_recv.recv(1) return os.read(self._ctrl_recv, 1) except (OSError, EOFError): return b'\0' def addReader(self, source, fd): channel = getattr(source, 'channel', '*') self._read.append(fd) self._targets[fd] = channel def addWriter(self, source, fd): channel = getattr(source, 'channel', '*') self._write.append(fd) self._targets[fd] = channel def removeReader(self, fd): if fd in self._read: self._read.remove(fd) if not (fd in self._read or fd in self._write) and fd in self._targets: del self._targets[fd] def removeWriter(self, fd): if fd in self._write: self._write.remove(fd) if not (fd in self._read or fd in self._write) and fd in self._targets: del self._targets[fd] def isReading(self, fd): return fd in self._read def isWriting(self, fd): return fd in self._write def discard(self, fd): if fd in self._read: self._read.remove(fd) if fd in self._write: self._write.remove(fd) if fd in self._targets: del self._targets[fd] def getTarget(self, fd): return self._targets.get(fd, self.parent) class Select(BasePoller): """ Select(...) -> new Select Poller Component Creates a new Select Poller Component that uses the select poller implementation. This poller is not recommended but is available for legacy reasons as most systems implement select-based polling for backwards compatibility. """ channel = 'select' def __init__(self, channel=channel): super().__init__(channel=channel) self._read.append(self._ctrl_recv) def _preenDescriptors(self): for socks in (self._read[:], self._write[:]): for sock in socks: try: select.select([sock], [sock], [sock], 0) except Exception: self.discard(sock) def _generate_events(self, event): try: if not any([self._read, self._write]): return None timeout = event.time_left if timeout < 0: r, w, _ = select.select(self._read, self._write, []) else: r, w, _ = select.select(self._read, self._write, [], timeout) except ValueError: # Possibly a file descriptor has gone negative? return self._preenDescriptors() except TypeError: # Something *totally* invalid (object w/o fileno, non-integral # result) was passed return self._preenDescriptors() except OSError as e: # select(2) encountered an error if e.args[0] in (0, 2): # windows does this if it got an empty list if (not self._read) and (not self._write): return None raise if e.args[0] == EINTR: return None if e.args[0] == EBADF: return self._preenDescriptors() # OK, I really don't know what's going on. Blow up. raise for sock in w: if self.isWriting(sock): self.fire(_write(sock), self.getTarget(sock)) for sock in r: if sock == self._ctrl_recv: self._read_ctrl() continue if self.isReading(sock): self.fire(_read(sock), self.getTarget(sock)) return None class Poll(BasePoller): """ Poll(...) -> new Poll Poller Component Creates a new Poll Poller Component that uses the poll poller implementation. """ channel = 'poll' def __init__(self, channel=channel): super().__init__(channel=channel) self._map = {} self._poller = select.poll() self._disconnected_flag = select.POLLHUP | select.POLLERR | select.POLLNVAL self._read.append(self._ctrl_recv) self._updateRegistration(self._ctrl_recv) def _updateRegistration(self, fd): fileno = fd.fileno() if not isinstance(fd, int) else fd with contextlib.suppress(KeyError, ValueError): self._poller.unregister(fileno) mask = 0 if fd in self._read: mask = mask | select.POLLIN if fd in self._write: mask = mask | select.POLLOUT if mask: self._poller.register(fd, mask) self._map[fileno] = fd else: super().discard(fd) with contextlib.suppress(KeyError): del self._map[fileno] def addReader(self, source, fd): super().addReader(source, fd) self._updateRegistration(fd) def addWriter(self, source, fd): super().addWriter(source, fd) self._updateRegistration(fd) def removeReader(self, fd): super().removeReader(fd) self._updateRegistration(fd) def removeWriter(self, fd): super().removeWriter(fd) self._updateRegistration(fd) def discard(self, fd): super().discard(fd) self._updateRegistration(fd) def _generate_events(self, event): try: timeout = event.time_left ll = self._poller.poll() if timeout < 0 else self._poller.poll(1000 * timeout) except OSError as e: if e.args[0] == EINTR: return raise for fileno, event in ll: self._process(fileno, event) def _process(self, fileno, event): if fileno not in self._map: return fd = self._map[fileno] if fd == self._ctrl_recv: self._read_ctrl() return if event & self._disconnected_flag and not (event & select.POLLIN): self.fire(_disconnect(fd), self.getTarget(fd)) self._poller.unregister(fileno) super().discard(fd) del self._map[fileno] else: try: if event & select.POLLIN: self.fire(_read(fd), self.getTarget(fd)) if event & select.POLLOUT: self.fire(_write(fd), self.getTarget(fd)) except Exception as e: self.fire(_error(fd, e), self.getTarget(fd)) self.fire(_disconnect(fd), self.getTarget(fd)) self._poller.unregister(fileno) super().discard(fd) del self._map[fileno] class EPoll(BasePoller): """ EPoll(...) -> new EPoll Poller Component Creates a new EPoll Poller Component that uses the epoll poller implementation. """ channel = 'epoll' def __init__(self, channel=channel): super().__init__(channel=channel) self._map = {} self._poller = select.epoll() self._disconnected_flag = select.EPOLLHUP | select.EPOLLERR self._read.append(self._ctrl_recv) self._updateRegistration(self._ctrl_recv) def _updateRegistration(self, fd): try: fileno = fd.fileno() if not isinstance(fd, int) else fd self._poller.unregister(fileno) except (OSError, ValueError) as e: if e.args[0] == EBADF: keys = [k for k, v in list(self._map.items()) if v == fd] for key in keys: del self._map[key] mask = 0 if fd in self._read: mask = mask | select.EPOLLIN if fd in self._write: mask = mask | select.EPOLLOUT if mask: self._poller.register(fd, mask) self._map[fileno] = fd else: super().discard(fd) def addReader(self, source, fd): super().addReader(source, fd) self._updateRegistration(fd) def addWriter(self, source, fd): super().addWriter(source, fd) self._updateRegistration(fd) def removeReader(self, fd): super().removeReader(fd) self._updateRegistration(fd) def removeWriter(self, fd): super().removeWriter(fd) self._updateRegistration(fd) def discard(self, fd): super().discard(fd) self._updateRegistration(fd) def _generate_events(self, event): try: timeout = event.time_left ll = self._poller.poll() if timeout < 0 else self._poller.poll(timeout) except OSError as e: if e.args[0] == EINTR: return except OSError as e: if e.args[0] == EINTR: return raise for fileno, event in ll: self._process(fileno, event) def _process(self, fileno, event): if fileno not in self._map: return fd = self._map[fileno] if fd == self._ctrl_recv: self._read_ctrl() return if event & self._disconnected_flag and not (event & select.POLLIN): self.fire(_disconnect(fd), self.getTarget(fd)) self._poller.unregister(fileno) super().discard(fd) del self._map[fileno] else: try: if event & select.EPOLLIN: self.fire(_read(fd), self.getTarget(fd)) if event & select.EPOLLOUT: self.fire(_write(fd), self.getTarget(fd)) except Exception as e: self.fire(_error(fd, e), self.getTarget(fd)) self.fire(_disconnect(fd), self.getTarget(fd)) self._poller.unregister(fileno) super().discard(fd) del self._map[fileno] class KQueue(BasePoller): """ KQueue(...) -> new KQueue Poller Component Creates a new KQueue Poller Component that uses the kqueue poller implementation. """ channel = 'kqueue' def __init__(self, channel=channel): super().__init__(channel=channel) self._map = {} self._poller = select.kqueue() self._read.append(self._ctrl_recv) self._map[self._ctrl_recv.fileno()] = self._ctrl_recv self._poller.control([select.kevent(self._ctrl_recv, select.KQ_FILTER_READ, select.KQ_EV_ADD)], 0) def addReader(self, source, sock): super().addReader(source, sock) self._map[sock.fileno()] = sock self._poller.control([select.kevent(sock, select.KQ_FILTER_READ, select.KQ_EV_ADD)], 0) def addWriter(self, source, sock): super().addWriter(source, sock) self._map[sock.fileno()] = sock self._poller.control([select.kevent(sock, select.KQ_FILTER_WRITE, select.KQ_EV_ADD)], 0) def removeReader(self, sock): super().removeReader(sock) self._poller.control([select.kevent(sock, select.KQ_FILTER_READ, select.KQ_EV_DELETE)], 0) def removeWriter(self, sock): super().removeWriter(sock) self._poller.control([select.kevent(sock, select.KQ_FILTER_WRITE, select.KQ_EV_DELETE)], 0) def discard(self, sock): super().discard(sock) del self._map[sock.fileno()] self._poller.control( [select.kevent(sock, select.KQ_FILTER_WRITE | select.KQ_FILTER_READ, select.KQ_EV_DELETE)], 0, ) def _generate_events(self, event): try: timeout = event.time_left ll = self._poller.control(None, 1000) if timeout < 0 else self._poller.control(None, 1000, timeout) except OSError as e: if e[0] == EINTR: return raise for event in ll: self._process(event) def _process(self, event): if event.ident not in self._map: # shouldn't happen ? # we unregister the socket since we don't care about it anymore self._poller.control([select.kevent(event.ident, event.filter, select.KQ_EV_DELETE)], 0) return sock = self._map[event.ident] if sock == self._ctrl_recv: self._read_ctrl() return if event.flags & select.KQ_EV_ERROR: self.fire(_error(sock, 'error'), self.getTarget(sock)) elif event.flags & select.KQ_EV_EOF: self.fire(_disconnect(sock), self.getTarget(sock)) elif event.filter == select.KQ_FILTER_WRITE: self.fire(_write(sock), self.getTarget(sock)) elif event.filter == select.KQ_FILTER_READ: self.fire(_read(sock), self.getTarget(sock)) Poller = Select __all__ = ('BasePoller', 'EPoll', 'KQueue', 'Poll', 'Poller', 'Select') circuits-3.2.3/circuits/core/timers.py000066400000000000000000000051601460335514400200050ustar00rootroot00000000000000"""Timer component to facilitate timed events.""" from datetime import datetime from time import mktime, time from circuits.core.handlers import handler from .components import BaseComponent class Timer(BaseComponent): """ Timer Component A timer is a component that fires an event once after a certain delay or periodically at a regular interval. """ def __init__(self, interval, event, *channels, **kwargs): """ :param interval: the delay or interval to wait for until the event is fired. If interval is specified as datetime, the interval is recalculated as the time span from now to the given datetime. :type interval: ``datetime`` or number of seconds as a ``float`` :param event: the event to fire. :type event: :class:`~.events.Event` :param persist: An optional keyword argument which if ``True`` will cause the event to be fired repeatedly once per configured interval until the timer is unregistered. If ``False``, the event fires exactly once after the specified interval, and the timer is unregistered. **Default:** ``False`` :type persist: ``bool`` """ super().__init__() self.expiry = None self.interval = None self.event = event self.channels = channels self.persist = kwargs.get('persist', False) self.reset(interval) @handler('generate_events') def _on_generate_events(self, event): if self.expiry is None: return now = time() if now >= self.expiry: if self.unregister_pending: return self.fire(self.event, *self.channels) if self.persist: self.reset() else: self.unregister() event.reduce_time_left(0) else: event.reduce_time_left(self.expiry - now) def reset(self, interval=None): """ Reset the timer, i.e. clear the amount of time already waited for. """ if interval is not None and isinstance(interval, datetime): self.interval = mktime(interval.timetuple()) - time() elif interval is not None: self.interval = interval self.expiry = time() + self.interval @property def expiry(self): return getattr(self, '_expiry', None) @expiry.setter def expiry(self, seconds): self._expiry = seconds circuits-3.2.3/circuits/core/utils.py000066400000000000000000000024201460335514400176360ustar00rootroot00000000000000""" Utils This module defines utilities used by circuits. """ import sys from importlib import reload def flatten(root, visited=None): if not visited: visited = set() yield root for component in root.components.copy(): if component not in visited: visited.add(component) yield from flatten(component, visited) def findchannel(root, channel, all=False): components = [x for x in flatten(root) if x.channel == channel] if all: return components if components: return components[0] return None def findtype(root, component, all=False): components = [x for x in flatten(root) if issubclass(type(x), component)] if all: return components if components: return components[0] return None findcmp = findtype def findroot(component): if component.parent == component: return component return findroot(component.parent) def safeimport(name): modules = sys.modules.copy() try: if name in sys.modules: return reload(sys.modules[name]) return __import__(name, globals(), locals(), ['']) except Exception: for name in sys.modules.copy(): if name not in modules: del sys.modules[name] circuits-3.2.3/circuits/core/values.py000066400000000000000000000065051460335514400200050ustar00rootroot00000000000000"""This defines the Value object used by components and events.""" from .events import Event class Value: """ Create a new future Value Object Creates a new future Value Object which is used by Event Objects and the Manager to store the result(s) of an Event Handler's exeuction of some Event in the system. :param event: The Event this Value is associated with. :type event: Event instance :param manager: The Manager/Component used to trigger notifications. :type manager: A Manager/Component instance. :ivar result: True if this value has been changed. :ivar errors: True if while setting this value an exception occurred. :ivar notify: True or an event name to notify of changes to this value This is a Future/Promise implementation. """ def __init__(self, event=None, manager=None): self.event = event self.manager = manager self.notify = False self.promise = False self.result = False self.errors = False self.parent = self self.handled = False self._value = None def __getstate__(self): odict = self.__dict__.copy() del odict['manager'] return odict def __contains__(self, y): value = self.value return y in value if isinstance(value, list) else y == value def __getitem__(self, y): v = self.value[y] if isinstance(v, Value): return v.value return v def __iter__(self): return iter((v.value if isinstance(v, Value) else v for v in self.value)) def __repr__(self): """x.__repr__() <==> repr(x)""" value = '' if self.result: value = repr(self.value) format = '' return format % (value, self.result, self.errors, self.event) def __str__(self): """x.__str__() <==> str(x)""" return str(self.value) def inform(self, force=False): if self.promise and not force: return notify = getattr(self.event, 'notify', False) or self.notify if self.manager is not None and notify: e = Event.create(notify, self) if isinstance(notify, str) else self.event.child('value_changed', self) self.manager.fire(e, self.manager) def getValue(self, recursive=True): value = self._value if not recursive: return value while isinstance(value, Value): value = value._value return value def setValue(self, value): if isinstance(value, Value): value.parent = self if self.result and isinstance(self._value, list): self._value.append(value) elif self.result: self._value = [self._value] self._value.append(value) else: self._value = value def update(o, v): if isinstance(v, Value): o.errors = v.errors o.result = v.result elif v is not None: o.result = True o.inform() if o.parent is not o: o.parent.errors = o.errors o.parent.result = o.result update(o.parent, v) update(self, value) value = property(getValue, setValue, None, 'Value of this Value') circuits-3.2.3/circuits/core/workers.py000066400000000000000000000052721460335514400202020ustar00rootroot00000000000000""" Workers Worker is a component used to perform "work" in independent threads or processes. Simply create an instance of Worker() with either `process=True` to create a pool of workers using sub-processes for CPU-bound work or `False` (the default) for a thread pool of workers for I/O bound work. Then fire `task()` events with a function and `*args` and `**kwargs` to pass to the function when called from within the workers. """ from multiprocessing import Pool as ProcessPool, cpu_count from multiprocessing.pool import ThreadPool from threading import current_thread from weakref import WeakKeyDictionary from .components import BaseComponent from .events import Event from .handlers import handler DEFAULT_WORKERS = 10 class task(Event): """ task Event This Event is used to initiate a new task to be performed by a Worker :param f: The function to be executed. :type f: function :param args: Arguments to pass to the function :type args: tuple :param kwargs: Keyword Arguments to pass to the function :type kwargs: dict """ success = True failure = True def __init__(self, f, *args, **kwargs): """x.__init__(...) initializes x; see x.__class__.__doc__ for signature""" super().__init__(f, *args, **kwargs) class Worker(BaseComponent): """ A thread/process Worker Component This Component creates a pool of workers (either a thread or process) and executures the supplied function from a `task()` event passing supplied arguments and keyword-arguments to the function. A `task_success` event is fired upon successful execution of the function and `task_failure` if it failed and threw an exception. The `task()` event can also be "waited" upon by using the `.call()` and `.wait()` primitives. :param process: True to start this Worker as a process (Thread otherwise) :type process: bool """ channel = 'worker' def init(self, process=False, workers=None, channel=channel): if not hasattr(current_thread(), '_children'): current_thread()._children = WeakKeyDictionary() self.workers = workers or (cpu_count() if process else DEFAULT_WORKERS) Pool = ProcessPool if process else ThreadPool self.pool = Pool(self.workers) @handler('stopped', 'unregistered', channel='*') def _on_stopped(self, event, *args): if event.name == 'unregistered' and args[0] is not self: return self.pool.close() self.pool.join() @handler('task') def _on_task(self, f, *args, **kwargs): result = self.pool.apply_async(f, args, kwargs) while not result.ready(): yield yield result.get() circuits-3.2.3/circuits/io/000077500000000000000000000000001460335514400156055ustar00rootroot00000000000000circuits-3.2.3/circuits/io/__init__.py000066400000000000000000000012001460335514400177070ustar00rootroot00000000000000"""I/O Support This package contains various I/O Components. Provided are a generic File Component, StdIn, StdOut and StdErr components. Instances of StdIn, StdOut and StdErr are also created by importing this package. """ import sys from .events import close, open, seek, write from .file import File from .process import Process try: from .notify import Notify except: pass try: from .serial import Serial except: pass try: stdin = File(sys.stdin, channel='stdin') stdout = File(sys.stdout, channel='stdout') stderr = File(sys.stderr, channel='stderr') except: pass # flake8: noqa # pylama: skip=1 circuits-3.2.3/circuits/io/events.py000066400000000000000000000016661460335514400174740ustar00rootroot00000000000000""" I/O Events This module implements commonly used I/O events used by other I/O modules. """ from circuits.core import Event class eof(Event): """eof Event""" class seek(Event): """seek Event""" class read(Event): """read Event""" class close(Event): """close Event""" class write(Event): """write Event""" class error(Event): """error Event""" class open(Event): """open Event""" class opened(Event): """opened Event""" class closed(Event): """closed Event""" class ready(Event): """ready Event""" class started(Event): """started Event""" class stopped(Event): """stopped Event""" class moved(Event): """moved Event""" class created(Event): """created Event""" class deleted(Event): """deleted Event""" class accessed(Event): """accessed Event""" class modified(Event): """modified Event""" class unmounted(Event): """unmounted Event""" circuits-3.2.3/circuits/io/file.py000066400000000000000000000132221460335514400170760ustar00rootroot00000000000000""" File I/O This module implements a wrapper for basic File I/O. """ try: from os import O_NONBLOCK except ImportError: # If it fails, that's fine. the fcntl import # will fail anyway. pass import contextlib from collections import deque from errno import EINTR, EWOULDBLOCK from os import read as fd_read, write as fd_write from sys import getdefaultencoding from circuits.core import Component, Event, handler from circuits.core.pollers import BasePoller, Poller from circuits.core.utils import findcmp from circuits.tools import tryimport from .events import close, closed, eof, error, opened, read, ready fcntl = tryimport('fcntl') TIMEOUT = 0.2 BUFSIZE = 4096 class _open(Event): """_open Event""" class File(Component): channel = 'file' def init(self, filename, mode='r', bufsize=BUFSIZE, encoding=None, channel=channel): self._mode = mode self._bufsize = bufsize self._filename = filename self._encoding = encoding or getdefaultencoding() self._fd = None self._poller = None self._buffer = deque() self._closeflag = False @property def closed(self): return getattr(self._fd, 'closed', True) if hasattr(self, '_fd') else True @property def filename(self): return getattr(self, '_filename', None) @property def mode(self): return getattr(self, '_mode', None) @handler('ready') def _on_ready(self, component): self.fire(_open(), self.channel) @handler('_open') def _on_open(self, filename=None, mode=None, bufsize=None): self._filename = filename or self._filename self._bufsize = bufsize or self._bufsize self._mode = mode or self._mode if isinstance(self._filename, str): kwargs = {'encoding': self._encoding} self._fd = open(self.filename, self.mode, **kwargs) else: self._fd = self._filename self._mode = self._fd.mode self._filename = self._fd.name self._encoding = getattr(self._fd, 'encoding', self._encoding) if fcntl is not None: # Set non-blocking file descriptor (non-portable) flag = fcntl.fcntl(self._fd, fcntl.F_GETFL) flag = flag | O_NONBLOCK fcntl.fcntl(self._fd, fcntl.F_SETFL, flag) if 'r' in self.mode or '+' in self.mode: self._poller.addReader(self, self._fd) self.fire(opened(self.filename, self.mode)) @handler('registered', 'started', channel='*') def _on_registered_or_started(self, component, manager=None): if self._poller is None: if isinstance(component, BasePoller): self._poller = component self.fire(ready(self)) else: if component is not self: return component = findcmp(self.root, BasePoller) if component is not None: self._poller = component self.fire(ready(self)) else: self._poller = Poller().register(self) self.fire(ready(self)) @handler('stopped', channel='*') def _on_stopped(self, component): self.fire(close()) @handler('prepare_unregister', channel='*') def _on_prepare_unregister(self, event, c): if event.in_subtree(self): self._close() def _close(self): if self.closed: return self._poller.discard(self._fd) self._buffer.clear() self._closeflag = False self._connected = False with contextlib.suppress(OSError): self._fd.close() self.fire(closed()) def close(self): if not self._buffer: self._close() elif not self._closeflag: self._closeflag = True def _read(self): try: data = fd_read(self._fd.fileno(), self._bufsize) if not isinstance(data, bytes): data = data.encode(self._encoding) if data: self.fire(read(data)).notify = True else: self.fire(eof()) if not any(m in self.mode for m in ('a', '+')): self.close() else: self._poller.discard(self._fd) except OSError as exc: if exc.args[0] in (EWOULDBLOCK, EINTR): return self.fire(error(exc)) self._close() def seek(self, offset, whence=0): self._fd.seek(offset, whence) def _write(self, data): try: if not isinstance(data, bytes): data = data.encode(self._encoding) nbytes = fd_write(self._fd.fileno(), data) if nbytes < len(data): self._buffer.appendleft(data[nbytes:]) except OSError as e: if e.args[0] in (EWOULDBLOCK, EINTR): return self.fire(error(e)) self._close() def write(self, data): if self._poller is not None and not self._poller.isWriting(self._fd): self._poller.addWriter(self, self._fd) self._buffer.append(data) @handler('_disconnect') def __on_disconnect(self, sock): self._close() @handler('_read') def __on_read(self, sock): self._read() @handler('_write') def __on_write(self, sock): if self._buffer: data = self._buffer.popleft() self._write(data) if not self._buffer: if self._closeflag: self._close() elif self._poller.isWriting(self._fd): self._poller.removeWriter(self._fd) circuits-3.2.3/circuits/io/notify.py000066400000000000000000000062361460335514400174760ustar00rootroot00000000000000""" File Notification Support A Component wrapping the inotify API using the pyinotify library. """ try: from pyinotify import ( ALL_EVENTS, IN_ACCESS, IN_ATTRIB, IN_CLOSE_NOWRITE, IN_CLOSE_WRITE, IN_CREATE, IN_DELETE, IN_DELETE_SELF, IN_MODIFY, IN_MOVE_SELF, IN_MOVED_FROM, IN_MOVED_TO, IN_OPEN, IN_UNMOUNT, Notifier, WatchManager, ) except ImportError: raise ImportError('No pyinotify support available. Is pyinotify installed?') from circuits.core import BaseComponent, handler from circuits.core.pollers import BasePoller, Poller from circuits.core.utils import findcmp from .events import accessed, closed, created, deleted, modified, moved, opened, ready, unmounted MASK = ALL_EVENTS EVENT_MAP = { IN_MOVED_TO: moved, IN_MOVE_SELF: moved, IN_MOVED_FROM: moved, IN_CLOSE_WRITE: closed, IN_CLOSE_NOWRITE: closed, IN_OPEN: opened, IN_DELETE_SELF: deleted, IN_DELETE: deleted, IN_CREATE: created, IN_ACCESS: accessed, IN_MODIFY: modified, IN_ATTRIB: modified, IN_UNMOUNT: unmounted, } class Notify(BaseComponent): channel = 'notify' def __init__(self, channel=channel): super().__init__(channel=channel) self._poller = None self._wm = WatchManager() self._notifier = Notifier(self._wm, self._on_process_events) def _on_process_events(self, event): dir = event.dir mask = event.mask path = event.path name = event.name pathname = event.pathname for k, v in EVENT_MAP.items(): if mask & k: self.fire(v(name, path, pathname, dir)) def add_path(self, path, mask=None, recursive=False, auto_add=True): mask = mask or MASK self._wm.add_watch(path, mask, rec=recursive, auto_add=auto_add) def remove_path(self, path, recursive=False): wd = self._wm.get_wd(path) if wd: self._wm.rm_watch(wd, rec=recursive) @handler('ready') def _on_ready(self, component): self._poller.addReader(self, self._notifier._fd) @handler('registered', channel='*') def _on_registered(self, component, manager): if self._poller is None: if isinstance(component, BasePoller): self._poller = component self.fire(ready(self)) else: if component is not self: return component = findcmp(self.root, BasePoller) if component is not None: self._poller = component self.fire(ready(self)) else: self._poller = Poller().register(self) self.fire(ready(self)) @handler('started', channel='*', priority=1) def _on_started(self, event, component): if self._poller is None: self._poller = Poller().register(self) self.fire(ready(self)) event.stop() @handler('_read', priority=1) def __on_read(self, fd): self._notifier.read_events() self._notifier.process_events() circuits-3.2.3/circuits/io/process.py000066400000000000000000000077331460335514400176470ustar00rootroot00000000000000""" Process This module implements a wrapper for basic ``subprocess.Popen`` functionality. """ from io import BytesIO from subprocess import PIPE, Popen from circuits import BaseComponent, Event, handler from circuits.core.manager import TIMEOUT from .events import close, started, write from .file import File class terminated(Event): """ terminated Event This Event is sent when a process is completed :param args: (process) :type tuple: tuple """ def __init__(self, *args): super().__init__(*args) class Process(BaseComponent): channel = 'process' def init(self, args, cwd=None, shell=False): self.args = args self.cwd = cwd self.shell = shell self.p = None self.stderr = BytesIO() self.stdout = BytesIO() self._status = None self._terminated = False self._stdout_closed = False self._stderr_closed = False self._stdin = None self._stderr = None self._stdout = None self._stdin_closed_handler = None self._stderr_read_handler = None self._stdout_read_handler = None self._stderr_closed_handler = None self._stdout_closed_handler = None def start(self): self.p = Popen( self.args, cwd=self.cwd, shell=self.shell, stdin=PIPE, stderr=PIPE, stdout=PIPE, ) self.stderr = BytesIO() self.stdout = BytesIO() self._status = None self._stdin = File( self.p.stdin, channel=f'{self.p.pid:d}.stdin', ).register(self) self._stderr = File( self.p.stderr, channel=f'{self.p.pid:d}.stderr', ).register(self) self._stdout = File( self.p.stdout, channel=f'{self.p.pid:d}.stdout', ).register(self) self._stderr_read_handler = self.addHandler( handler('read', channel=self._stderr.channel)( lambda self, data: self.stderr.write(data), ), ) self._stdout_read_handler = self.addHandler( handler('read', channel=self._stdout.channel)( lambda self, data: self.stdout.write(data), ), ) self._stderr_closed_handler = self.addHandler( handler('closed', channel=self._stderr.channel)( lambda self: setattr(self, '_stderr_closed', True), ), ) self._stdout_closed_handler = self.addHandler( handler('closed', channel=self._stdout.channel)( lambda self: setattr(self, '_stdout_closed', True), ), ) self.fire(started(self)) def stop(self): if self.p is not None: self.p.terminate() def kill(self): self.p.kill() def signal(self, signal): self.p.send_signal(signal) def wait(self): return self.p.wait() def write(self, data): self.fire(write(data), f'{self.p.pid:d}.stdin') @property def status(self): if getattr(self, 'p', None) is not None: return self.p.poll() return None @handler('generate_events') def _on_generate_events(self, event): if self.p is not None and self._status is None: self._status = self.p.poll() if self._status is not None and self._stderr_closed and self._stdout_closed and not self._terminated: self._terminated = True self.removeHandler(self._stderr_read_handler) self.removeHandler(self._stdout_read_handler) self.removeHandler(self._stderr_closed_handler) self.removeHandler(self._stdout_closed_handler) self.fire(terminated(self)) self.fire(close(), self._stdin.channel, self._stdout.channel, self._stderr.channel) event.reduce_time_left(0) event.stop() else: event.reduce_time_left(TIMEOUT) circuits-3.2.3/circuits/io/serial.py000066400000000000000000000110211460335514400174310ustar00rootroot00000000000000""" Serial I/O This module implements basic Serial (RS232) I/O. """ import contextlib from collections import deque from circuits.core import Component, Event, handler from circuits.core.pollers import BasePoller, Poller from circuits.core.utils import findcmp from circuits.tools import tryimport from .events import close, closed, error, opened, read, ready serial = tryimport('serial') TIMEOUT = 0.2 BUFSIZE = 4096 class _open(Event): """_open Event""" class Serial(Component): channel = 'serial' def __init__( self, port, baudrate=115200, bufsize=BUFSIZE, timeout=TIMEOUT, encoding='UTF-8', readline=False, channel=channel ): super().__init__(channel=channel) if serial is None: raise RuntimeError('No serial support available') self._port = port self._baudrate = baudrate self._bufsize = bufsize self._encoding = encoding self._readline = readline self._serial = None self._poller = None self._buffer = deque() self._closeflag = False @handler('ready') def _on_ready(self, component): self.fire(_open(), self.channel) @handler('_open') def _on_open(self, port=None, baudrate=None, bufsize=None): self._port = port or self._port self._baudrate = baudrate or self._baudrate self._bufsize = bufsize or self._bufsize self._serial = serial.Serial(port=self._port, baudrate=self._baudrate, timeout=0) self._fd = self._serial.fileno() # not portable! self._poller.addReader(self, self._fd) self.fire(opened(self._port, self._baudrate)) @handler('registered', 'started', channel='*') def _on_registered_or_started(self, component, manager=None): if self._poller is None: if isinstance(component, BasePoller): self._poller = component self.fire(ready(self)) else: if component is not self: return component = findcmp(self.root, BasePoller) if component is not None: self._poller = component self.fire(ready(self)) else: self._poller = Poller().register(self) self.fire(ready(self)) @handler('stopped', channel='*') def _on_stopped(self, component): self.fire(close()) @handler('prepare_unregister', channel='*') def _on_prepare_unregister(self, event, c): if event.in_subtree(self): self._close() def _close(self): if self._closeflag: return self._poller.discard(self._fd) self._buffer.clear() self._closeflag = False self._connected = False with contextlib.suppress(OSError): self._serial.close() self.fire(closed()) def close(self): if not self._buffer: self._close() elif not self._closeflag: self._closeflag = True def _read(self): try: data = self._serial.readline(self._bufsize) if self._readline else self._serial.read(self._bufsize) if not isinstance(data, bytes): data = data.encode(self._encoding) if data: self.fire(read(data)).notify = True except OSError as exc: self.fire(error(exc)) self._close() def _write(self, data): try: if not isinstance(data, bytes): data = data.encode(self._encoding) try: nbytes = self._serial.write(data) except serial.SerialTimeoutException: nbytes = 0 if nbytes < len(data): self._buffer.appendleft(data[nbytes:]) except OSError as e: self.fire(error(e)) self._close() def write(self, data): if self._poller is not None and not self._poller.isWriting(self._fd): self._poller.addWriter(self, self._fd) self._buffer.append(data) @handler('_disconnect') def __on_disconnect(self, sock): self._close() @handler('_read') def __on_read(self, sock): self._read() @handler('_write') def __on_write(self, sock): if self._buffer: data = self._buffer.popleft() self._write(data) if not self._buffer: if self._closeflag: self._close() elif self._poller.isWriting(self._fd): self._poller.removeWriter(self._fd) circuits-3.2.3/circuits/net/000077500000000000000000000000001460335514400157645ustar00rootroot00000000000000circuits-3.2.3/circuits/net/__init__.py000066400000000000000000000002461460335514400200770ustar00rootroot00000000000000""" Networking Components This package contains components that implement network sockets and protocols for implementing client and server network applications. """ circuits-3.2.3/circuits/net/events.py000066400000000000000000000144511460335514400176470ustar00rootroot00000000000000""" Networking Events This module implements commonly used Networking events used by socket components. """ from circuits.core import Event class connect(Event): """ connect Event This Event is sent when a new client connection has arrived on a server. This event is also used for client's to initiate a new connection to a remote host. .. note :: This event is used for both Client and Server Components. :param args: Client: (host, port) Server: (sock, host, port) :type args: tuple :param kwargs: Client: (ssl) :type kwargs: dict """ def __init__(self, *args, **kwargs): """x.__init__(...) initializes x; see x.__class__.__doc__ for signature""" super().__init__(*args, **kwargs) class disconnect(Event): """ disconnect Event This Event is sent when a client connection has closed on a server. This event is also used for client's to disconnect from a remote host. .. note:: This event is used for both Client and Server Components. :param args: Client: () Server: (sock) :type tuple: tuple """ def __init__(self, *args, **kwargs): """x.__init__(...) initializes x; see x.__class__.__doc__ for signature""" super().__init__(*args, **kwargs) class connected(Event): """ connected Event This Event is sent when a client has successfully connected. .. note:: This event is for Client Components. :param host: The hostname connected to. :type str: str :param port: The port connected to :type int: int """ def __init__(self, host, port): """x.__init__(...) initializes x; see x.__class__.__doc__ for signature""" super().__init__(host, port) class disconnected(Event): """ disconnected Event This Event is sent when a client has disconnected .. note:: This event is for Client Components. """ def __init__(self): """x.__init__(...) initializes x; see x.__class__.__doc__ for signature""" super().__init__() class read(Event): """ read Event This Event is sent when a client or server connection has read any data. .. note:: This event is used for both Client and Server Components. :param args: Client: (data) Server: (sock, data) :type tuple: tuple """ def __init__(self, *args): """x.__init__(...) initializes x; see x.__class__.__doc__ for signature""" super().__init__(*args) class error(Event): """ error Event This Event is sent when a client or server connection has an error. .. note:: This event is used for both Client and Server Components. :param args: Client: (error) Server: (sock, error) :type tuple: tuple """ def __init__(self, *args): """x.__init__(...) initializes x; see x.__class__.__doc__ for signature""" super().__init__(*args) class unreachable(Event): """ unreachable Event This Event is sent when a server is unreachable for a client :param host: Server hostname or IP :type str: str :param port: Server port :type int: int """ def __init__(self, host, port, reason=None): """x.__init__(...) initializes x; see x.__class__.__doc__ for signature""" super().__init__(host, port, reason) class broadcast(Event): """ broadcast Event This Event is used by the UDPServer/UDPClient sockets to send a message on the ```` network. .. note:: - This event is never sent, it is used to send data. - This event is used for both Client and Server UDP Components. :param args: (data, port) :type tuple: tuple """ def __init__(self, *args): """x.__init__(...) initializes x; see x.__class__.__doc__ for signature""" super().__init__(*args) class write(Event): """ write Event This Event is used to notify a client, client connection or server that we have data to be written. .. note:: - This event is never sent, it is used to send data. - This event is used for both Client and Server Components. :param args: Client: (data) Server: (sock, data) :type tuple: tuple """ def __init__(self, *args): """x.__init__(...) initializes x; see x.__class__.__doc__ for signature""" super().__init__(*args) class close(Event): """ close Event This Event is used to notify a client, client connection or server that we want to close. .. note:: - This event is never sent, it is used to close. - This event is used for both Client and Server Components. :param args: Client: () Server: (sock) :type tuple: tuple """ def __init__(self, *args): """x.__init__(...) initializes x; see x.__class__.__doc__ for signature""" super().__init__(*args) class ready(Event): """ ready Event This Event is used to notify the rest of the system that the underlying Client or Server Component is ready to begin processing connections or incoming/outgoing data. (This is triggered as a direct result of having the capability to support multiple client/server components with a single poller component instance in a system). .. note:: This event is used for both Client and Server Components. :param component: The Client/Server Component that is ready. :type tuple: Component (Client/Server) :param bind: The (host, port) the server has bound to. :type tuple: (host, port) """ def __init__(self, component, bind=None): """x.__init__(...) initializes x; see x.__class__.__doc__ for signature""" args = (component, bind) if bind is not None else (component,) super().__init__(*args) class closed(Event): """ closed Event This Event is sent when a server has closed its listening socket. .. note:: This event is for Server components. """ class starttls(Event): """ starttls Event This event can be fired to upgrade the socket connection to a TLS secured connection. .. note:: This event is currently only available for Server Components. :param sock: The client socket where to start TLS. :type sock: socket.socket """ def __init__(self, sock): super().__init__(sock) circuits-3.2.3/circuits/net/sockets.py000066400000000000000000000652101460335514400200150ustar00rootroot00000000000000""" Socket Components This module contains various Socket Components for use with Networking. """ import contextlib import os import select from collections import defaultdict, deque from errno import ( EAGAIN, EALREADY, EBADF, ECONNABORTED, EINPROGRESS, EINTR, EINVAL, EISCONN, EMFILE, ENFILE, ENOBUFS, ENOMEM, ENOTCONN, EPERM, EPIPE, EWOULDBLOCK, ) from socket import ( AF_INET, AF_INET6, IPPROTO_IP, IPPROTO_TCP, SO_BROADCAST, SO_REUSEADDR, SOCK_DGRAM, SOCK_STREAM, SOL_SOCKET, TCP_NODELAY, gaierror, getaddrinfo, getfqdn, gethostbyname, gethostname, socket, ) from time import time from _socket import socket as SocketType from circuits.core import BaseComponent, handler from circuits.core.pollers import BasePoller, Poller from circuits.core.utils import findcmp from .events import close, closed, connect, connected, disconnect, disconnected, error, read, ready, unreachable, write try: from socket import AF_UNIX except ImportError: AF_UNIX = None try: from socket import socketpair except ImportError: socketpair = None try: from ssl import ( CERT_NONE, CERT_REQUIRED, PROTOCOL_TLS, SSL_ERROR_WANT_READ, SSL_ERROR_WANT_WRITE, Purpose, SSLContext, SSLError, create_default_context, ) HAS_SSL = 1 except ImportError: import warnings warnings.warn('No SSL support available.') HAS_SSL = 0 CERT_NONE = None PROTOCOL_TLS = None BUFSIZE = 4096 # 4KB Buffer BACKLOG = 5000 # 5K Concurrent Connections def wrap_socket( sock, keyfile=None, certfile=None, ca_certs=None, purpose=Purpose.SERVER_AUTH, verify_mode=None, minimum_version=PROTOCOL_TLS, check_hostname=None, **kwargs, ): if minimum_version != PROTOCOL_TLS: context = SSLContext(minimum_version) if purpose == Purpose.SERVER_AUTH: context.verify_mode = CERT_REQUIRED context.check_hostname = True if ca_certs: context.load_verify_locations(cafile=ca_certs) else: context = create_default_context(purpose, cafile=ca_certs) if check_hostname is not None: context.check_hostname = check_hostname if verify_mode: if verify_mode == CERT_NONE: context.check_hostname = False context.verify_mode = verify_mode if certfile: context.load_cert_chain(certfile=certfile, keyfile=keyfile) return context.wrap_socket(sock, **kwargs) def do_handshake(sock, on_done=None, on_error=None, extra_args=None): """ SSL Async Handshake :param on_done: Function called when handshake is complete :type on_done: :function: :param on_error: Function called when handshake errored :type on_error: :function: """ extra_args = extra_args or () while True: try: sock.do_handshake() break except SSLError as err: if err.args[0] == SSL_ERROR_WANT_READ: select.select([sock], [], []) elif err.args[0] == SSL_ERROR_WANT_WRITE: select.select([], [sock], []) else: callable(on_error) and on_error(sock, err) return yield callable(on_done) and on_done(sock, *extra_args) class Client(BaseComponent): channel = 'client' socket_family = AF_INET socket_type = SOCK_STREAM socket_protocol = IPPROTO_IP socket_options = [] def __init__(self, bind=None, bufsize=BUFSIZE, channel=channel, **kwargs): super().__init__(channel=channel, **kwargs) if isinstance(bind, SocketType): self._bind = bind.getsockname() self._sock = bind else: self._bind = self.parse_bind_parameter(bind) self._sock = self._create_socket() self._bufsize = bufsize self._ssock = None self._poller = None self._buffer = deque() self._closeflag = False self._connected = False self.host = None self.port = 0 self.secure = False self.server = {} self.issuer = {} def parse_bind_parameter(self, bind_parameter): return parse_ipv4_parameter(bind_parameter) @property def connected(self): return getattr(self, '_connected', None) @handler('registered', 'started', channel='*') def _on_registered_or_started(self, component, manager=None): if self._poller is None: if isinstance(component, BasePoller): self._poller = component self.fire(ready(self)) else: if component is not self: return component = findcmp(self.root, BasePoller) if component is not None: self._poller = component self.fire(ready(self)) else: self._poller = Poller().register(self) self.fire(ready(self)) @handler('stopped', channel='*') def _on_stopped(self, component): self.fire(close()) @handler('read_value_changed') def _on_read_value_changed(self, value): if isinstance(value, bytes): self.fire(write(value)) @handler('prepare_unregister', channel='*') def _on_prepare_unregister(self, event, c): if event.in_subtree(self): self._close() def _close(self): if not self._connected: return self._poller.discard(self._sock) self._buffer.clear() self._closeflag = False self._connected = False with contextlib.suppress(OSError): self._sock.shutdown(2) with contextlib.suppress(OSError): self._sock.close() self.fire(disconnected()) @handler('close') def close(self): if not self._buffer: self._close() elif not self._closeflag: self._closeflag = True def _read(self): try: try: data = self._ssock.read(self._bufsize) if self.secure and self._ssock else self._sock.recv(self._bufsize) except SSLError as exc: if exc.errno in (SSL_ERROR_WANT_READ, SSL_ERROR_WANT_WRITE): return raise if data: self.fire(read(data)).notify = True else: self.close() except OSError as e: if e.args[0] == EWOULDBLOCK: return self.fire(error(e)) self._close() def _write(self, data): try: nbytes = self._ssock.write(data) if self.secure and self._ssock else self._sock.send(data) if nbytes < len(data): self._buffer.appendleft(data[nbytes:]) except OSError as e: if e.args[0] in (EPIPE, ENOTCONN): self._close() else: self.fire(error(e)) @handler('write') def write(self, data): if not self._poller.isWriting(self._sock): self._poller.addWriter(self, self._sock) self._buffer.append(data) @handler('_disconnect', priority=1) def __on_disconnect(self, sock): self._close() @handler('_read', priority=1) def __on_read(self, sock): self._read() @handler('_write', priority=1) def __on_write(self, sock): if self._buffer: data = self._buffer.popleft() self._write(data) if not self._buffer: if self._closeflag: self._close() elif self._poller.isWriting(self._sock): self._poller.removeWriter(self._sock) def _create_socket(self): sock = socket(self.socket_family, self.socket_type, self.socket_protocol) for option in self.socket_options: sock.setsockopt(*option) sock.setblocking(False) if self._bind is not None: sock.bind(self._bind) return sock class TCPClient(Client): socket_family = AF_INET socket_type = SOCK_STREAM socket_protocol = IPPROTO_TCP socket_options = [ (IPPROTO_TCP, TCP_NODELAY, 1), ] def init(self, connect_timeout=5, *args, **kwargs): self.connect_timeout = connect_timeout @handler('connect') # noqa def connect(self, host, port, secure=False, **kwargs): # TODO: C901: This has a high McCacbe complexity score of 10. # TODO: Refactor this! self.host = host self.port = port self.secure = secure if self.secure: self.certfile = kwargs.get('certfile', None) self.keyfile = kwargs.get('keyfile', None) self.ca_certs = kwargs.get('ca_certs', None) try: r = self._sock.connect((host, port)) except OSError as e: if e.args[0] in (EBADF, EINVAL): self._sock = self._create_socket() r = self._sock.connect_ex((host, port)) else: r = e.args[0] if r not in (EISCONN, EWOULDBLOCK, EINPROGRESS, EALREADY): self.fire(unreachable(host, port, e)) self.fire(error(e)) self._close() return stop_time = time() + self.connect_timeout while time() < stop_time: try: self._sock.getpeername() self._connected = True break except Exception: yield if not self._connected: self.fire(unreachable(host, port)) return def on_done(sock): self._poller.addReader(self, sock) self.fire(connected(host, port)) if self.secure: def on_error(sock, err): self.fire(error(sock, err)) self._close() self._sock = wrap_socket( self._sock, self.keyfile, self.certfile, ca_certs=self.ca_certs, purpose=Purpose.SERVER_AUTH, server_hostname=self.host, do_handshake_on_connect=False, ) for _ in do_handshake(self._sock, on_done, on_error): yield else: on_done(self._sock) class TCP6Client(TCPClient): socket_family = AF_INET6 def parse_bind_parameter(self, bind_parameter): return parse_ipv6_parameter(bind_parameter) class UNIXClient(Client): socket_family = AF_UNIX socket_type = SOCK_STREAM socket_options = [] @handler('ready') def ready(self, component): if self._poller is not None and self._connected: self._poller.addReader(self, self._sock) @handler('connect') # noqa def connect(self, path, secure=False, **kwargs): # TODO: C901: This has a high McCacbe complexity score of 10. # TODO: Refactor this! self.path = path self.secure = secure if self.secure: self.certfile = kwargs.get('certfile', None) self.keyfile = kwargs.get('keyfile', None) self.ca_certs = kwargs.get('ca_certs', None) try: r = self._sock.connect_ex(path) except OSError as e: r = e.args[0] if r: if r in (EISCONN, EWOULDBLOCK, EINPROGRESS, EALREADY): self._connected = True else: self.fire(error(r)) return self._connected = True self._poller.addReader(self, self._sock) if self.secure: def on_done(sock): self.fire(connected(gethostname(), path)) def on_error(sock, err): self.fire(error(err)) self._ssock = wrap_socket( self._sock, self.keyfile, self.certfile, ca_certs=self.ca_certs, purpose=Purpose.SERVER_AUTH, server_hostname=self.host, do_handshake_on_connect=False, ) for _ in do_handshake(self._ssock, on_done, on_error): yield else: self.fire(connected(gethostname(), path)) class Server(BaseComponent): channel = 'server' socket_protocol = IPPROTO_IP def __init__(self, bind, secure=False, backlog=BACKLOG, bufsize=BUFSIZE, channel=channel, **kwargs): super().__init__(channel=channel) self.socket_options = self.socket_options[:] + kwargs.get('socket_options', []) self._bind = self.parse_bind_parameter(bind) self._backlog = backlog self._bufsize = bufsize if isinstance(bind, socket): self._sock = bind else: self._sock = self._create_socket() self._closeq = [] self._clients = [] self._poller = None self._buffers = defaultdict(deque) self.__starttls = set() self.secure = secure self.certfile = kwargs.get('certfile') self.keyfile = kwargs.get('keyfile', None) self.cert_reqs = kwargs.get('cert_reqs', CERT_NONE) self.ssl_version = kwargs.get('ssl_version', PROTOCOL_TLS) self.ca_certs = kwargs.get('ca_certs', None) if self.secure and not self.certfile: raise RuntimeError('certfile must be specified for server-side operations') def parse_bind_parameter(self, bind_parameter): return parse_ipv4_parameter(bind_parameter) @property def connected(self): return True @property def host(self): if getattr(self, '_sock', None) is not None: try: sockname = self._sock.getsockname() if isinstance(sockname, tuple): return sockname[0] return sockname except OSError: return None return None @property def port(self): if getattr(self, '_sock', None) is not None: try: sockname = self._sock.getsockname() if isinstance(sockname, tuple): return sockname[1] except OSError: return None return None @handler('registered', 'started', channel='*') def _on_registered_or_started(self, component, manager=None): if self._poller is None: if isinstance(component, BasePoller): self._poller = component self._poller.addReader(self, self._sock) self.fire(ready(self, (self.host, self.port))) else: if component is not self: return component = findcmp(self.root, BasePoller) if component is not None: self._poller = component self._poller.addReader(self, self._sock) self.fire(ready(self, (self.host, self.port))) else: try: self._poller = Poller().register(self) except OSError as err: self.fire(error(err)) else: self._poller.addReader(self, self._sock) self.fire(ready(self, (self.host, self.port))) @handler('stopped', channel='*') def _on_stopped(self, component): self.fire(close()) @handler('read_value_changed') def _on_read_value_changed(self, value): if isinstance(value.value, bytes): sock = value.event.args[0] self.fire(write(sock, value.value)) def _close(self, sock): if sock is None: return if sock != self._sock and sock not in self._clients: return self._poller.discard(sock) if sock in self._buffers: del self._buffers[sock] if sock in self._clients: self._clients.remove(sock) else: self._sock = None if sock in self.__starttls: self.__starttls.remove(sock) with contextlib.suppress(OSError): sock.shutdown(2) with contextlib.suppress(OSError): sock.close() self.fire(disconnect(sock)) @handler('close') def close(self, sock=None): is_closed = sock is None if sock is None: socks = [self._sock] socks.extend(self._clients[:]) else: socks = [sock] for sock in socks: if not self._buffers[sock]: self._close(sock) elif sock not in self._closeq: self._closeq.append(sock) if is_closed: self.fire(closed()) def _read(self, sock): if sock not in self._clients: return try: data = sock.recv(self._bufsize) if data: self.fire(read(sock, data)).notify = True else: self.close(sock) except OSError as e: if e.args[0] == EWOULDBLOCK: return self.fire(error(sock, e)) self._close(sock) def _write(self, sock, data): if sock not in self._clients: return try: nbytes = sock.send(data) if nbytes < len(data): self._buffers[sock].appendleft(data[nbytes:]) except OSError as e: if e.args[0] not in (EINTR, EWOULDBLOCK, ENOBUFS): self.fire(error(sock, e)) self._close(sock) else: self._buffers[sock].appendleft(data) @handler('write') def write(self, sock, data): if not self._poller.isWriting(sock): self._poller.addWriter(self, sock) self._buffers[sock].append(data) def _accept(self): try: newsock, _host = self._sock.accept() except OSError as e: if e.args[0] in (EWOULDBLOCK, EAGAIN): return elif e.args[0] == EPERM: # Netfilter on Linux may have rejected the # connection, but we get told to try to accept() # anyway. return elif e.args[0] in (EMFILE, ENOBUFS, ENFILE, ENOMEM, ECONNABORTED): # Linux gives EMFILE when a process is not allowed # to allocate any more file descriptors. *BSD and # Win32 give (WSA)ENOBUFS. Linux can also give # ENFILE if the system is out of inodes, or ENOMEM # if there is insufficient memory to allocate a new # dentry. ECONNABORTED is documented as possible on # both Linux and Windows, but it is not clear # whether there are actually any circumstances under # which it can happen (one might expect it to be # possible if a client sends a FIN or RST after the # server sends a SYN|ACK but before application code # calls accept(2), however at least on Linux this # _seems_ to be short-circuited by syncookies. return else: raise if self.secure and HAS_SSL: for _ in self._do_handshake(newsock): yield else: self._on_accept_done(newsock) def _do_handshake(self, sock, fire_connect_event=True): sslsock = wrap_socket( sock, self.keyfile, self.certfile, ca_certs=self.ca_certs, purpose=Purpose.CLIENT_AUTH, verify_mode=self.cert_reqs, minimum_version=self.ssl_version, server_side=True, do_handshake_on_connect=False, ) yield from do_handshake(sslsock, self._on_accept_done, self._on_handshake_error, (fire_connect_event,)) def _on_accept_done(self, sock, fire_connect_event=True): sock.setblocking(False) self._poller.addReader(self, sock) self._clients.append(sock) if fire_connect_event: try: self.fire(connect(sock, *sock.getpeername())) except OSError as exc: # errno 107 (ENOTCONN): the client already disconnected self._on_handshake_error(sock, exc) def _on_handshake_error(self, sock, err): self.fire(error(sock, err)) self._close(sock) @handler('starttls') def starttls(self, sock): if not HAS_SSL: raise RuntimeError('Cannot start TLS. No TLS support.') if sock in self.__starttls: raise RuntimeError('Cannot reuse socket for already started STARTTLS.') self.__starttls.add(sock) self._poller.removeReader(sock) self._clients.remove(sock) for _ in self._do_handshake(sock, False): yield @handler('_disconnect', priority=1) def _on_disconnect(self, sock): self._close(sock) @handler('_read', priority=1) def _on_read(self, sock): if sock == self._sock: return self._accept() self._read(sock) return None @handler('_write', priority=1) def _on_write(self, sock): if self._buffers[sock]: data = self._buffers[sock].popleft() self._write(sock, data) if not self._buffers[sock]: if sock in self._closeq: self._closeq.remove(sock) self._close(sock) elif self._poller.isWriting(sock): self._poller.removeWriter(sock) def _create_socket(self): sock = socket(self.socket_family, self.socket_type, self.socket_protocol) for option in self.socket_options: sock.setsockopt(*option) sock.setblocking(False) if self._bind is not None: sock.bind(self._bind) return sock class TCPServer(Server): socket_family = AF_INET socket_type = SOCK_STREAM socket_options = [ (SOL_SOCKET, SO_REUSEADDR, 1), (IPPROTO_TCP, TCP_NODELAY, 1), ] def _create_socket(self): sock = super()._create_socket() sock.listen(self._backlog) return sock def parse_bind_parameter(self, bind_parameter): return parse_ipv4_parameter(bind_parameter) def parse_ipv4_parameter(bind_parameter): if isinstance(bind_parameter, int): try: bind = (gethostbyname(gethostname()), bind_parameter) except gaierror: bind = ('0.0.0.0', bind_parameter) elif isinstance(bind_parameter, str) and ':' in bind_parameter: host, port = bind_parameter.split(':') port = int(port) bind = (host, port) else: bind = bind_parameter return bind def parse_ipv6_parameter(bind_parameter): if isinstance(bind_parameter, int): try: _, _, _, _, bind = getaddrinfo(getfqdn(), bind_parameter, AF_INET6)[0] except (gaierror, IndexError): bind = ('::', bind_parameter) else: bind = bind_parameter return bind class TCP6Server(TCPServer): socket_family = AF_INET6 def parse_bind_parameter(self, bind_parameter): return parse_ipv6_parameter(bind_parameter) class UNIXServer(Server): socket_family = AF_UNIX socket_type = SOCK_STREAM socket_options = [ (SOL_SOCKET, SO_REUSEADDR, 1), ] def _create_socket(self): if os.path.exists(self._bind): os.unlink(self._bind) sock = super()._create_socket() sock.listen(self._backlog) return sock class UDPServer(Server): socket_family = AF_INET socket_type = SOCK_DGRAM socket_options = [ (SOL_SOCKET, SO_BROADCAST, 1), (SOL_SOCKET, SO_REUSEADDR, 1), ] def _close(self, sock): self._poller.discard(sock) if sock in self._buffers: del self._buffers[sock] with contextlib.suppress(OSError): sock.shutdown(2) with contextlib.suppress(OSError): sock.close() self.fire(disconnect(sock)) @handler('close', override=True) def close(self): self.fire(closed()) if self._buffers[self._sock] and self._sock not in self._closeq: self._closeq.append(self._sock) else: self._close(self._sock) def _read(self): try: data, address = self._sock.recvfrom(self._bufsize) if data: self.fire(read(address, data)).notify = True except OSError as e: if e.args[0] in (EWOULDBLOCK, EAGAIN): return self.fire(error(self._sock, e)) self._close(self._sock) def _write(self, address, data): try: bytes = self._sock.sendto(data, address) if bytes < len(data): self._buffers[self._sock].appendleft(data[bytes:]) except OSError as e: if e.args[0] in (EPIPE, ENOTCONN): self._close(self._sock) else: self.fire(error(self._sock, e)) @handler('write', override=True) def write(self, address, data): if not self._poller.isWriting(self._sock): self._poller.addWriter(self, self._sock) self._buffers[self._sock].append((address, data)) @handler('broadcast', override=True) def broadcast(self, data, port): self.write(('', port), data) @handler('_disconnect', priority=1, override=True) def _on_disconnect(self, sock): self._close(sock) @handler('_read', priority=1, override=True) def _on_read(self, sock): self._read() @handler('_write', priority=1, override=True) def _on_write(self, sock): if self._buffers[self._sock]: address, data = self._buffers[self._sock].popleft() self._write(address, data) if not self._buffers[self._sock]: if self._sock in self._closeq: self._closeq.remove(self._sock) self._close(self._sock) elif self._poller.isWriting(self._sock): self._poller.removeWriter(self._sock) UDPClient = UDPServer class UDP6Server(UDPServer): socket_family = AF_INET6 def parse_bind_parameter(self, bind_parameter): return parse_ipv6_parameter(bind_parameter) UDP6Client = UDP6Server def Pipe(*channels, **kwargs): """ Create a new full duplex Pipe Returns a pair of UNIXClient instances connected on either side of the pipe. """ if socketpair is None: raise RuntimeError('No socketpair support available.') if not channels: channels = ('a', 'b') s1, s2 = socketpair() s1.setblocking(False) s2.setblocking(False) a = UNIXClient(s1, channel=channels[0], **kwargs) b = UNIXClient(s2, channel=channels[1], **kwargs) a._connected = True b._connected = True return a, b circuits-3.2.3/circuits/net/utils.py000066400000000000000000000006251460335514400175010ustar00rootroot00000000000000"""Utilities""" def is_ssl_handshake(buf): """Detect an SSLv2 or SSLv3 handshake""" # SSLv3, TLS 1.1 - 1.3 v = buf[:3] if v in ('\x16\x03\x00', '\x16\x03\x01', '\x16\x03\x02', '\x16\x03\x03', '\x16\x03\x04'): return True # SSLv2 v = list(iter(buf[:2])) + [0x00, 0x00] if (v[0] & 0x80 == 0x80) and ((v[0] & 0x7F) << 8 | v[1]) > 9: return True return None circuits-3.2.3/circuits/node/000077500000000000000000000000001460335514400161235ustar00rootroot00000000000000circuits-3.2.3/circuits/node/__init__.py000066400000000000000000000002171460335514400202340ustar00rootroot00000000000000"""Node Distributed processing support for circuits. """ from .events import remote from .node import Node # flake8: noqa # pylama: skip=1 circuits-3.2.3/circuits/node/client.py000066400000000000000000000051511460335514400177550ustar00rootroot00000000000000from circuits import BaseComponent, handler from circuits.net.events import close, connect from circuits.net.sockets import TCPClient from .protocol import Protocol class Client(BaseComponent): """Node Client (peer)""" channel = 'node_client' def __init__(self, host, port, channel=channel, receive_event_firewall=None, send_event_firewall=None, **kwargs): """ Create new connection for a node. :param hostname: hostname to connect. :type hostname: str :param port: port to connect. :type port: int :param channel: An optional keyword argument which if defined, set channel used for node event. **Default:** ``node_client`` :type channel: str :param receive_event_firewall: An optional keyword argument which if defined, function or method to call for check if event is allowed for sending. **Default:** ``None`` (no firewall) :type receive_event_firewall: function :type receive_event_firewall: method :param send_event_firewall: An optional keyword argument which if defined, function or method to call for check if event is allowed for executing **Default:** ``None`` (no firewall) :type send_event_firewall: function :type send_event_firewall: method """ super().__init__(channel=channel, **kwargs) self.__host = host self.__port = port self.__protocol = Protocol( receive_event_firewall=receive_event_firewall, send_event_firewall=send_event_firewall, channel=channel, ).register(self) TCPClient(channel=channel, **kwargs).register(self) @handler('ready') def _on_ready(self, component): self.connect() def close(self): """Close the connection""" self.fire(close()) def connect(self): """Create the connection""" self.fire(connect(self.__host, self.__port)) def send(self, event): """ Send event through the connection :param event: Event to send. :type event: :class:`circuits.core.events.Event` :return: The result object of the sended event :rtype: generator """ return self.__protocol.send(event) @handler('read') def _on_read(self, data): self.__protocol.add_buffer(data) circuits-3.2.3/circuits/node/events.py000066400000000000000000000025211460335514400200010ustar00rootroot00000000000000from circuits import Event class connected_to(Event): """Connected to a peer""" def __init__(self, connection_name, hostname, port, client_channel, client_obj): """ :param connection_name: Connection name. :type connection_name: str :param hostname: hostname of the remote system. :type hostname: str :param port: connection port of the remote system. :type port: int :param client_channel: Channel used for client event. :type client_channel: str :param client_obj: Client object. :type client_obj: :class:`circuits.net.sockets.Client` """ super().__init__(connection_name, hostname, port, client_channel, client_obj) class disconnected_from(connected_to): """Disconnected from a peer""" class remote(Event): """send event to a peer""" def __init__(self, event, connection_name, channel=None): """ :param event: Event to execute remotely. :type event: :class:`circuits.core.events.Event` :param connection_name: Connection name. :type connection_name: str :param channel: Remote channel (channel to use on peer). :type channel: str """ super().__init__(event, connection_name, channel=channel) circuits-3.2.3/circuits/node/node.py000066400000000000000000000174411460335514400174310ustar00rootroot00000000000000""" Node this module manage node (start server, add peer, ...) .. seealso:: Examples in :file:`examples/node` """ from circuits import BaseComponent, Timer, handler from circuits.net.events import connect from .client import Client from .events import connected_to, disconnected_from, remote from .server import Server class Node(BaseComponent): """ Node this class manage node (start server, add peer, ...) .. seealso:: Examples in :file:`examples/node` """ channel = 'node' __peers = {} def __init__(self, port=None, channel=channel, **kwargs): """ Start node system. :param port: An optional keyword argument which if defined, start server on this port. **Default:** ``None`` (don't start the server) :type port: int :param server_ip: An optional keyword argument which define ip where the socket has listen to. **Default:** ``0.0.0.0`` (all ip is allowed) :type server_ip: str :param channel: An optional keyword argument which if defined, set channel used for node event. **Default:** ``node`` :type channel: str :param receive_event_firewall: An optional keyword argument which if defined, set function or method to call to check if event is allowed for sending. **Default:** ``None`` (no firewall) :type receive_event_firewall: function :type receive_event_firewall: method :param send_event_firewall: An optional keyword argument which if defined, set function or method to call to check if event is allowed for executing **Default:** ``None`` (no firewall) :type send_event_firewall: function :type send_event_firewall: method """ super().__init__(channel=channel, **kwargs) if port is not None: self.server = Server(port, channel=channel, **kwargs).register(self) else: self.server = None def add(self, connection_name, hostname, port, **kwargs): """ Add new peer to the node. :param connection_name: Connection name. :type connection_name: str :param hostname: hostname of the remote node. :type hostname: str :param port: port of the remote node. :type port: int :param auto_remote_event: An optional keyword argument which if defined, bind events automatically to remote execution. **Default:** ``{}`` (no events) :type auto_remote_event: dict :param channel: An optional keyword argument which if defined, set channel used for client event. If this keyword is not defined the method will generate the channel name automatically. :type channel: str :param reconnect_delay: An optional keyword argument which if defined, set auto reconnect delay. **Default:** ``10`` (seconde) :type reconnect_delay: int :param receive_event_firewall: An optional keyword argument which if defined, function or method to call for check if event is allowed for sending. **Default:** ``None`` (no firewall) :type receive_event_firewall: method :param send_event_firewall: An optional keyword argument which if defined, setfunction or method to call to check if event is allowed for executing **Default:** ``None`` (no firewall) :type send_event_firewall: method :return: Channel used on client event. :rtype: str """ # automatic send event to peer auto_remote_event = kwargs.pop('auto_remote_event', {}) for event_name in auto_remote_event: for channel in auto_remote_event[event_name]: @handler(event_name, channel=channel) def event_handle(self, event, *args, **kwargs): yield self.call(remote(event, connection_name)) self.addHandler(event_handle) client_channel = kwargs.pop( 'channel', f'{self.channel}_client_{connection_name}', ) reconnect_delay = kwargs.pop('reconnect_delay', 10) client = Client(hostname, port, channel=client_channel, **kwargs) # connected event binding @handler('connected', channel=client_channel) def connected(self, hostname, port): self.fire( connected_to( connection_name, hostname, port, client_channel, client, ) ) self.addHandler(connected) # disconnected event binding @handler('disconnected', 'unreachable', channel=client_channel) def disconnected(self, event, *args, **kwargs): if event.name == 'disconnected': self.fire( disconnected_from( connection_name, hostname, port, client_channel, client, ) ) # auto reconnect if reconnect_delay > 0: Timer( reconnect_delay, connect(hostname, port), client_channel, ).register(self) self.addHandler(disconnected) client.register(self) self.__peers[connection_name] = client return client_channel def get_connection_names(self): """ Get connections names :return: The list of connections names :rtype: list of str """ return list(self.__peers) def get_peer(self, connection_name): """ Get a client object by name :param connection_name: Connection name. :type connection_name: str :return: The Client object :rtype: :class:`circuits.node.client.Client` """ return self.__peers.get(connection_name, None) @handler('remote', channel='*') def __on_remote(self, event, remote_event, connection_name, channel=None): """ Send event to peer Event handler to run an event on peer (the event definition is :class:`circuits.node.events.remote`) :param event: The event triggered (by the handler) :type event: :class:`circuits.node.events.remote` :param remote_event: Event to execute remotely. :type remote_event: :class:`circuits.core.events.Event` :param connection_name: Connection name (peer selection). :type connection_name: str :param channel: Remote channel (channel to use on peer). :type channel: str :return: The result of remote event :rtype: generator :Example: ``# hello is your event to execute remotely # peer_test is peer name result = yield self.fire(remote(hello())), 'peer_test') print(result.value)`` """ node = self.__peers[connection_name] remote_event.channels = (channel,) if channel is not None else event.channels return node.send(remote_event) circuits-3.2.3/circuits/node/protocol.py000066400000000000000000000100141460335514400203320ustar00rootroot00000000000000from circuits import Component, handler from circuits.core import Value from circuits.net.events import write from .utils import dump_event, dump_value, load_event, load_value DELIMITER = b'~~~' class Protocol(Component): __buffer = b'' __nid = 0 __events = {} def init(self, sock=None, server=None, **kwargs): self.__server = server self.__sock = sock self.__receive_event_firewall = kwargs.get('receive_event_firewall', None) self.__send_event_firewall = kwargs.get('send_event_firewall', None) def add_buffer(self, data=''): if data: self.__buffer += data packets = self.__buffer.split(DELIMITER) self.__buffer = b'' for packet in packets: try: self.__process_packet(packet) except ValueError: self.__buffer = packet @handler(channel='node_result', priority=100) def result_handler(self, event, *args, **kwargs): if event.name.endswith('_success'): source_event = args[0] if getattr(args[0], 'node_call_id', False) is not False: self.send_result(source_event.node_call_id, source_event.value) def send(self, event): if self.__send_event_firewall and not self.__send_event_firewall(event, self.__sock): yield Value(event, self) else: id = self.__nid self.__nid += 1 packet = dump_event(event, id).encode('utf-8') + DELIMITER self.__send(packet) if not getattr(event, 'node_without_result', False): self.__events[id] = event while not hasattr(self.__events[id], 'remote_finish'): yield del self.__events[id] yield event.value def send_result(self, id, value): value.node_call_id = id value.node_sock = self.__sock packet = dump_value(value).encode('utf-8') + DELIMITER self.__send(packet) def __send(self, packet): if self.__server is not None: self.fire(write(self.__sock, packet)) else: self.fire(write(packet)) def __process_packet(self, packet): packet = packet.decode('utf-8') if '"value":' in packet: self.__process_packet_value(packet) else: self.__process_packet_call(packet) def __process_packet_call(self, packet): event, id = load_event(packet) if self.__receive_event_firewall and not self.__receive_event_firewall(event, self.__sock): self.send_result(id, Value(event, self)) else: event.success = True # fire %s_success event event.success_channels = ('node_result',) event.node_call_id = id event.node_sock = self.__sock # convert byte to str event.args = [arg.decode('utf-8') if isinstance(arg, bytes) else arg for arg in event.args] for i in event.kwargs: v = event.kwargs[i] index = i.decode('utf-8') if isinstance(i, bytes) else i value = v.decode('utf-8') if isinstance(v, bytes) else v del event.kwargs[i] event.kwargs[index] = value self.fire(event, *event.channels) def __process_packet_value(self, packet): value, id, error, meta = load_value(packet) if id in self.__events: # convert byte to str value = value.decode('utf-8') if isinstance(value, bytes) else value error = error.decode('utf-8') if isinstance(error, bytes) else error if not hasattr(self.__events[id], 'value') or not self.__events[id].value: self.__events[id].value = Value(self.__events[id], self) # save result self.__events[id].value.setValue(value) self.__events[id].errors = error self.__events[id].remote_finish = True for k, v in dict(meta).items(): setattr(self.__events[id], k, v) circuits-3.2.3/circuits/node/server.py000066400000000000000000000112001460335514400177750ustar00rootroot00000000000000import contextlib from circuits import BaseComponent, handler from circuits.net.sockets import TCPServer from .protocol import Protocol class Server(BaseComponent): """Node server.""" channel = 'node' __protocols = {} def __init__( self, port, server_ip='0.0.0.0', channel=channel, receive_event_firewall=None, send_event_firewall=None, **kwargs ): """ Create server on node system. :param port: start server on this port. :type port: int :param server_ip: An optional keyword argument which which define ip where the socket has listen to. **Default:** ``0.0.0.0`` (all ip is allowed) :type server_ip: str :param channel: An optional keyword argument which if defined, set channel used for node event. **Default:** ``node`` :type channel: str :param receive_event_firewall: An optional keyword argument which if defined, set function or method to call to check if event is allowed for sending **Default:** ``None`` (no firewall) :type receive_event_firewall: function :type receive_event_firewall: method :param send_event_firewall: An optional keyword argument which if defined, set function or method to call to check if event is allowed for executing **Default:** ``None`` (no firewall) :type send_event_firewall: function :type send_event_firewall: method """ super().__init__(channel=channel, **kwargs) bind = (server_ip, port) self.server = TCPServer(bind, channel=self.channel, **kwargs) self.server.register(self) self.__receive_event_firewall = receive_event_firewall self.__send_event_firewall = send_event_firewall def send(self, event, sock, no_result=False): """ Send event to peer :param event: Event to execute remotely. :type event: :class:`circuits.core.events.Event` :param sock: Client's socket (peer selection). :type sock: :class:`socket.socket` :param no_result: An optional keyword argument which if True don't return the event result. **Default:** ``False`` (wait the result) :type no_result: bool :return: The result of remote event :rtype: generator """ iterator = self.__protocols[sock].send(event) if no_result: event.node_without_result = True with contextlib.suppress(StopIteration): next(iterator) return iterator def send_to(self, event, socks): """ Send event to multiple peer :param event: Event to execute remotely. :type event: :class:`circuits.core.events.Event` :param socks: Client's socket list (peer selection). :type socks: list of :class:`socket.socket` """ for sock in socks: self.send(event, sock, no_result=True) def send_all(self, event): """ Send event to all peer :param event: Event to execute remotely. :type event: :class:`circuits.core.events.Event` """ self.send_to(event, list(self.__protocols)) @handler('read') def _on_read(self, sock, data): self.__protocols[sock].add_buffer(data) @property def host(self): if hasattr(self, 'server'): return self.server.host return None @property def port(self): if hasattr(self, 'server'): return self.server.port return None def get_socks(self): """ Get clients sockets list :return: The list of client socket :rtype: list of :class:`socket.socket` """ return list(self.__protocols) @handler('connect') def __connect_peer(self, sock, host, port): self.__protocols[sock] = Protocol( sock=sock, server=self.server, receive_event_firewall=self.__receive_event_firewall, send_event_firewall=self.__send_event_firewall, channel=self.channel, ).register(self) @handler('disconnect') def __disconnect_peer(self, sock): for s in self.__protocols.copy(): try: s.getpeername() except Exception: del self.__protocols[s] circuits-3.2.3/circuits/node/utils.py000066400000000000000000000032601460335514400176360ustar00rootroot00000000000000import json from circuits.core import Event META_EXCLUDE = set(dir(Event())) META_EXCLUDE.add('node_call_id') META_EXCLUDE.add('node_sock') META_EXCLUDE.add('node_without_result') META_EXCLUDE.add('success_channels') def load_event(s): data = json.loads(s) name = data['name'] args = [] for arg in data['args']: if isinstance(arg, str): arg = arg.encode('utf-8') args.append(arg) kwargs = {} for k, v in data['kwargs'].items(): if isinstance(v, str): v = v.encode('utf-8') kwargs[str(k)] = v e = Event.create(name, *args, **kwargs) e.success = bool(data['success']) e.failure = bool(data['failure']) e.notify = bool(data['notify']) e.channels = tuple(data['channels']) for k, v in dict(data['meta']).items(): setattr(e, k, v) return e, data['id'] def dump_event(e, id): meta = {} for name in list(set(dir(e)) - META_EXCLUDE): meta[name] = getattr(e, name) data = { 'id': id, 'name': e.name, 'args': e.args, 'kwargs': e.kwargs, 'success': e.success, 'failure': e.failure, 'channels': e.channels, 'notify': e.notify, 'meta': meta, } return json.dumps(data) def dump_value(v): meta = {} e = v.event if e: for name in list(set(dir(e)) - META_EXCLUDE): meta[name] = getattr(e, name) data = { 'id': v.node_call_id, 'errors': v.errors, 'value': v._value, 'meta': meta, } return json.dumps(data) def load_value(v): data = json.loads(v) return data['value'], data['id'], data['errors'], data['meta'] circuits-3.2.3/circuits/protocols/000077500000000000000000000000001460335514400172225ustar00rootroot00000000000000circuits-3.2.3/circuits/protocols/__init__.py000066400000000000000000000002511460335514400213310ustar00rootroot00000000000000""" Networking Protocols This package contains components that implement various networking protocols. """ from .irc import IRC # noqa from .line import Line # noqa circuits-3.2.3/circuits/protocols/http.py000066400000000000000000000036771460335514400205700ustar00rootroot00000000000000from io import BytesIO from circuits.core import BaseComponent, Event, handler class request(Event): """request Event""" class response(Event): """response Event""" class ResponseObject: def __init__(self, headers, status, version): self.headers = headers self.status = status self.version = version self.body = BytesIO() # TODO: This sucks :/ Avoiding the circuit import here :/ from circuits.web.constants import HTTP_STATUS_CODES self.reason = HTTP_STATUS_CODES[self.status] def __repr__(self): return ''.format( self.status, self.reason, self.headers.get('Content-Type'), len(self.body.getvalue()), ) def read(self): return self.body.read() class HTTP(BaseComponent): channel = 'web' def __init__(self, encoding='utf-8', channel=channel): super().__init__(channel=channel) self._encoding = encoding # TODO: This sucks :/ Avoiding the circuit import here :/ from circuits.web.parsers import HttpParser self._parser = HttpParser(1, True) @handler('read') def _on_client_read(self, data): self._parser.execute(data, len(data)) if ( self._parser.is_message_complete() or self._parser.is_upgrade() or (self._parser.is_headers_complete() and self._parser._clen == 0) ): status = self._parser.get_status_code() version = self._parser.get_version() headers = self._parser.get_headers() res = ResponseObject(headers, status, version) res.body.write(self._parser.recv_body()) res.body.seek(0) self.fire(response(res)) # TODO: This sucks :/ Avoiding the circuit import here :/ from circuits.web.parsers import HttpParser self._parser = HttpParser(1, True) circuits-3.2.3/circuits/protocols/irc/000077500000000000000000000000001460335514400177775ustar00rootroot00000000000000circuits-3.2.3/circuits/protocols/irc/__init__.py000066400000000000000000000010251460335514400221060ustar00rootroot00000000000000""" Internet Relay Chat Protocol This package implements the Internet Relay Chat Protocol or commonly known as IRC. Support for both server and client is implemented. """ from .commands import * # noqa from .events import reply, response # noqa from .message import Message # noqa from .numerics import * # noqa from .protocol import IRC # noqa from .utils import ( # noqa irc_color_to_ansi, joinprefix, parsemsg, parseprefix, strip, ) sourceJoin = joinprefix sourceSplit = parseprefix # pylama:skip=1 circuits-3.2.3/circuits/protocols/irc/commands.py000066400000000000000000000030111460335514400221450ustar00rootroot00000000000000"""Internet Relay Chat Protocol commands""" from .events import request from .message import Message def AWAY(message=None): return request(Message('AWAY', message)) def NICK(nickname, hopcount=None): return request(Message('NICK', nickname, hopcount)) def USER(user, host, server, name): return request(Message('USER', user, host, server, name)) def PASS(password): return request(Message('PASS', password)) def PONG(daemon1, daemon2=None): return request(Message('PONG', daemon1, daemon2)) def QUIT(message=None): return request(Message('QUIT', message)) def JOIN(channels, keys=None): return request(Message('JOIN', channels, keys)) def PART(channels, message=None): return request(Message('PART', channels, message)) def PRIVMSG(receivers, message): return request(Message('PRIVMSG', receivers, message)) def NOTICE(receivers, message): return request(Message('NOTICE', receivers, message)) def KICK(channel, user, comment=None): return request(Message('KICK', channel, user, comment)) def TOPIC(channel, topic=None): return request(Message('TOPIC', channel, topic)) def MODE(target, *args): return request(Message('MODE', target, *args)) def INVITE(nickname, channel): return request(Message('INVITE', nickname, channel)) def NAMES(channels=None): return request(Message('NAMES', channels)) def WHOIS(nickmasks, server=None): return request(Message(server, nickmasks)) def WHO(name=None, o=None): return request(Message('WHO', name, o)) circuits-3.2.3/circuits/protocols/irc/events.py000066400000000000000000000003721460335514400216570ustar00rootroot00000000000000"""Internet Relay Chat Protocol events""" from circuits import Event class response(Event): """response Event (Server and Client)""" class reply(Event): """reply Event (Server)""" class request(Event): """request Event (Client)""" circuits-3.2.3/circuits/protocols/irc/message.py000066400000000000000000000035171460335514400220030ustar00rootroot00000000000000"""Internet Relay Chat message""" from .utils import parsemsg class Error(Exception): """Error Exception""" class Message: def __init__(self, command, *args, **kwargs): self.command = command self.prefix = str(kwargs['prefix']) if 'prefix' in kwargs else None self.encoding = kwargs.get('encoding', 'utf-8') self.add_nick = kwargs.get('add_nick', False) self.args = [arg if isinstance(arg, str) else arg.decode(self.encoding) for arg in args if arg is not None] self._check_args() def _check_args(self): if any(type(arg)(' ') in arg in arg for arg in self.args[:-1] if isinstance(arg, str)): raise Error('Space can only appear in the very last arg') if any(type(arg)('\n') in arg for arg in self.args if isinstance(arg, str)): raise Error('No newline allowed') @staticmethod def from_string(s): if len(s) > 512: raise Error('Message must not be longer than 512 characters') prefix, command, args = parsemsg(s) return Message(command, *args, prefix=prefix) def __bytes__(self): return str(self).encode(self.encoding) def __str__(self): self._check_args() args = self.args[:] if args and ' ' in args[-1] and not args[-1].startswith(':'): args[-1] = f':{args[-1]}' return '{prefix}{command} {args}\r\n'.format( prefix=(f':{self.prefix} ' if self.prefix is not None else ''), command=str(self.command), args=' '.join(args), ) def __repr__(self): return repr(str(self)[:-2]) def __eq__(self, other): return ( isinstance(other, Message) and self.prefix == other.prefix and self.command == other.command and self.args == other.args ) circuits-3.2.3/circuits/protocols/irc/numerics.py000066400000000000000000000047461460335514400222110ustar00rootroot00000000000000"""Internet Relay Chat Protocol numerics""" RPL_WELCOME = 1 RPL_YOURHOST = 2 RPL_TRACELINK = 200 RPL_TRACECONNECTING = 201 RPL_TRACEHANDSHAKE = 202 RPL_TRACEUNKNOWN = 203 RPL_TRACEOPERATOR = 204 RPL_TRACEUSER = 205 RPL_TRACESERVER = 206 RPL_TRACENEWTYPE = 208 RPL_TRACELOG = 261 RPL_STATSLINKINFO = 211 RPL_STATSCOMMANDS = 212 RPL_STATSCLINE = 213 RPL_STATSNLINE = 214 RPL_STATSILINE = 215 RPL_STATSKLINE = 216 RPL_STATSYLINE = 218 RPL_ENDOFSTATS = 219 RPL_STATSLLINE = 241 RPL_STATSUPTIME = 242 RPL_STATSOLINE = 243 RPL_STATSHLINE = 244 RPL_UMODEIS = 221 RPL_LUSERCLIENT = 251 RPL_LUSEROP = 252 RPL_LUSERUNKNOWN = 253 RPL_LUSERCHANNELS = 254 RPL_LUSERME = 255 RPL_ADMINME = 256 RPL_ADMINLOC1 = 257 RPL_ADMINLOC2 = 258 RPL_ADMINEMAIL = 259 RPL_NONE = 300 RPL_USERHOST = 302 RPL_ISON = 303 RPL_AWAY = 301 RPL_UNAWAY = 305 RPL_NOWAWAY = 306 RPL_WHOISUSER = 311 RPL_WHOISSERVER = 312 RPL_WHOISOPERATOR = 313 RPL_WHOISIDLE = 317 RPL_ENDOFWHOIS = 318 RPL_WHOISCHANNELS = 319 RPL_WHOWASUSER = 314 RPL_ENDOFWHOWAS = 369 RPL_LIST = 322 RPL_LISTEND = 323 RPL_CHANNELMODEIS = 324 RPL_NOTOPIC = 331 RPL_TOPIC = 332 RPL_INVITING = 341 RPL_SUMMONING = 342 RPL_VERSION = 351 RPL_WHOREPLY = 352 RPL_ENDOFWHO = 315 RPL_NAMEREPLY = 353 RPL_ENDOFNAMES = 366 RPL_LINKS = 364 RPL_ENDOFLINKS = 365 RPL_BANLIST = 367 RPL_ENDOFBANLIST = 368 RPL_INFO = 371 RPL_ENDOFINFO = 374 RPL_MOTDSTART = 375 RPL_MOTD = 372 RPL_ENDOFMOTD = 376 RPL_YOUREOPER = 381 RPL_REHASHING = 382 RPL_TIME = 391 RPL_USERSSTART = 392 RPL_USERS = 393 RPL_ENDOFUSERS = 394 RPL_NOUSERS = 395 ERR_NOSUCHNICK = 401 ERR_NOSUCHSERVER = 402 ERR_NOSUCHCHANNEL = 403 ERR_CANNOTSENDTOCHAN = 404 ERR_TOOMANYCHANNELS = 405 ERR_WASNOSUCHNICK = 406 ERR_TOOMANYTARGETS = 407 ERR_NOORIGIN = 409 ERR_NORECIPIENT = 411 ERR_NOTEXTTOSEND = 412 ERR_NOTOPLEVEL = 413 ERR_WILDTOPLEVEL = 414 ERR_UNKNOWNCOMMAND = 421 ERR_NOMOTD = 422 ERR_NOADMININFO = 423 ERR_FILEERROR = 424 ERR_NONICKNAMEGIVEN = 431 ERR_ERRONEUSNICKNAME = 432 ERR_NICKNAMEINUSE = 433 ERR_NICKCOLLISION = 436 ERR_NOTONCHANNEL = 442 ERR_USERONCHANNEL = 443 ERR_NOLOGIN = 444 ERR_SUMMONDISABLED = 445 ERR_USERSDISABLED = 446 ERR_NOTREGISTERED = 451 ERR_NEEDMOREPARAMS = 461 ERR_ALREADYREGISTRED = 462 ERR_PASSWDMISMATCH = 464 ERR_YOUREBANNEDCREEP = 465 ERR_KEYSET = 467 ERR_CHANNELISFULL = 471 ERR_UNKNOWNMODE = 472 ERR_INVITEONLYCHAN = 473 ERR_BANNEDFROMCHAN = 474 ERR_BADCHANNELKEY = 475 ERR_NOPRIVILEGES = 481 ERR_CHANOPRIVSNEEDED = 482 ERR_CANTKILLSERVER = 483 ERR_NOOPERHOST = 491 ERR_UMODEUNKNOWNFLAG = 501 ERR_USERSDONTMATCH = 502 circuits-3.2.3/circuits/protocols/irc/protocol.py000066400000000000000000000054361460335514400222220ustar00rootroot00000000000000"""Internet Relay Chat Protocol""" from re import compile as compile_regex from circuits import Component from circuits.net.events import write from circuits.protocols.line import Line from .commands import PONG from .events import response from .utils import parsemsg NUMERIC = compile_regex('[0-9]+') class IRC(Component): """ IRC Protocol Component Creates a new IRC Component instance that implements the IRC Protocol. Incoming messages are handled by the "read" Event Handler, parsed and processed with appropriate Events created and exposed to the rest of the system to listen to and handle. """ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.encoding = kwargs.get('encoding', 'utf-8') Line(**kwargs).register(self) def line(self, *args): """ line Event Handler Process a line of text and generate the appropriate event. This must not be overridden by subclasses, if it is, this must be explicitly called by the subclass. Other Components may however listen to this event and process custom IRC events. """ if len(args) == 1: # Client read sock, line = None, args[0] else: # Server read sock, line = args prefix, command, args = parsemsg(line, encoding=self.encoding) command = command.lower() if NUMERIC.match(command): args.insert(0, int(command)) command = 'numeric' if sock is not None: self.fire(response.create(command, sock, prefix, *args)) else: self.fire(response.create(command, prefix, *args)) def request(self, event, message): """ request Event Handler (Default) This is a default event handler to respond to ``request`` events by converting the given message to bytes and firing a ``write`` event to a hopefully connected client socket. Components may override this, but be sure to respond to ``request`` events by either explicitly calling this method or sending your own ``write`` events as the client socket. """ event.stop() message.encoding = self.encoding self.fire(write(bytes(message))) def ping(self, event, *args): """ ping Event Handler (Default) This is a default event to respond to ``ping`` events by sending a ``PONG`` in response. Subclasses or components may override this, but be sure to respond to ``ping`` events by either explicitly calling this method or sending your own ``PONG`` response. """ if len(args) == 2: # Client read self.fire(PONG(args[1])) event.stop() circuits-3.2.3/circuits/protocols/irc/replies.py000066400000000000000000000145501460335514400220210ustar00rootroot00000000000000"""Internet Relay Chat Protocol replies""" from .message import Message def _M(*args, **kwargs): kwargs['add_nick'] = True return Message(*args, **kwargs) def ERROR(host, reason=None): return Message('ERROR', ':Closing link: {} ({})'.format(host, reason or '')) def JOIN(name, prefix=None): return Message('JOIN', name, prefix=prefix) def KICK(channel, nick, reason=None, prefix=None): return Message('KICK', channel, nick, reason, prefix=prefix) def MODE(target, modes, params=None, prefix=None): if params is None: return Message('MODE', target, modes, prefix=prefix) return Message('MODE', target, modes, ' '.join(params), prefix=prefix) def PART(channel, nick, reason=None, prefix=None): return Message('PART', channel, nick, reason, prefix=prefix) def PING(server): return Message('PING', f':{server}') def PONG(server, text): return Message('PONG', server, f':{text}') def TOPIC(channel, topic, prefix=None): return Message('TOPIC', channel, topic, prefix=prefix) def RPL_WELCOME(network): return _M('001', f'Welcome to the {network} IRC Network') def RPL_YOURHOST(host, version): return _M('002', f'Your host is {host} running {version}') def RPL_CREATED(date): return _M('003', f'This server was created {date}') def RPL_MYINFO(server, version, umodes, chmodes): return _M('004', server, version, umodes, chmodes) def RPL_ISUPPORT(features): return _M('005', *(features + ('are supported by this server',))) def RPL_UMODEIS(modes): return _M('221', modes) def RPL_LUSERCLIENT(nusers, nservices, nservers): return _M( '251', f'There are {nusers} users and {nservices} services on {nservers} servers', ) def RPL_LUSEROP(noperators): return _M('252', f'{noperators}', 'operator(s) online') def RPL_LUSERUNKOWN(nunknown): return _M('253', f'{nunknown}', 'unknown connection(s)') def RPL_LUSERCHANNELS(nchannels): return _M('254', f'{nchannels}', 'channels formed') def RPL_LUSERME(nclients, nservers): return _M('255', f'I have {nclients} clients and {nservers} servers') def RPL_AWAY(nick, message): return _M('301', nick, f':{message}') def RPL_UNAWAY(): return _M('305', 'You are no longer marked as being away') def RPL_NOWAWAY(): return _M('306', 'You have been marked as being away') def RPL_WHOISUSER(nick, user, host, realname): return _M('311', nick, user, host, '*', f':{realname}') def RPL_WHOISSERVER(nick, server, server_info): return _M('312', nick, server, server_info) def RPL_WHOISOPERATOR(nick): return _M('313', nick, 'is an IRC operator') def RPL_ENDOFWHO(mask): return _M('315', mask, 'End of WHO list') def RPL_WHOISIDLE(nick, idle, signon): return _M( '317', nick, f'{idle}', f'{signon}', 'seconds idle, signon time', ) def RPL_ENDOFWHOIS(nick): return _M('318', nick, 'End of WHOIS list') def RPL_WHOISCHANNELS(nick, channels): return _M('319', nick, ':{}'.format(' '.join(channels))) def RPL_LISTSTART(header=None): return _M('321', header or 'Channels :Users Name') def RPL_LIST(channel, nvisible, topic): return _M('322', channel, f'{nvisible}', topic) def RPL_LISTEND(): return _M('323', 'End of LIST') def RPL_CHANNELMODEIS(channel, mode, params=None): if params is None: return _M('324', channel, mode) return _M('324', channel, mode, params) def RPL_NOTOPIC(channel): return _M('331', channel, 'No topic is set') def RPL_TOPIC(channel, topic): return _M('332', channel, topic) def RPL_TOPICWHO(channel, setter, timestamp): return _M('333', channel, setter, f'{timestamp}') def RPL_INVITING(channel, nick): return _M('341', f'{channel} {nick}') def RPL_SUMMONING(user): return _M('342', f'{user} :Summoning user to IRC') def RPL_INVITELIST(channel, invitemask): return _M('346', f'{channel} {invitemask}') def RPL_ENDOFINVITELIST(channel): return _M('347', f'{channel} :End of channel invite list') def RPL_VERSION(name, version, hostname, url): return _M('351', name, version, hostname, url) def RPL_WHOREPLY(channel, user, host, server, nick, status, hops, name): return _M( '352', channel, user, host, server, nick, status, f':{hops} {name}', ) def RPL_NAMEREPLY(channel, names): return _M('353', '=', channel, ' '.join(names)) def RPL_ENDOFNAMES(channel): return _M('366', channel, 'End of NAMES list') def RPL_MOTD(text): return _M('372', f'- {text}') def RPL_MOTDSTART(server): return _M('375', f'- {server} Message of the day -') def RPL_ENDOFMOTD(): return _M('376', 'End of MOTD command') def RPL_YOUREOPER(): return _M('381', 'You are now an IRC operator') def ERR_NOSUCHNICK(nick): return _M('401', nick, 'No such nick') def ERR_NOSUCHCHANNEL(channel): return _M('403', channel, 'No such channel') def ERR_CANNOTSENDTOCHAN(channel): return _M('404', channel, 'Cannot send to channel') def ERR_TOOMANYCHANNELS(channel): return _M('405', channel, 'You have joined too many channels') def ERR_UNKNOWNCOMMAND(command): return _M('421', command, 'Unknown command') def ERR_NOMOTD(): return _M('422', 'MOTD file is missing') def ERR_NONICKNAMEGIVEN(): return _M('431', 'No nickname given') def ERR_ERRONEUSNICKNAME(nick): return _M('432', nick, 'Erroneous nickname') def ERR_NICKNAMEINUSE(nick): return _M('433', nick, 'Nickname is already in use') def ERR_USERNOTINCHANNEL(nick, channel): return _M('441', nick, channel, "They aren't on that channel") def ERR_NOTREGISTERED(): return _M('451', 'You have not registered') def ERR_NEEDMOREPARAMS(command): return _M('461', command, 'Need more parameters') def ERR_PASSWDMISMATCH(): return _M('464', 'Password incorrect') def ERR_UNKNOWNMODE(mode, channel=None): if channel is None: return _M('472', mode, 'is unknown mode char to me') return _M('472', mode, f'is unknown mode char to me for channel {channel}') def ERR_CHANOPRIVSNEEDED(channel): return _M('482', channel, "You're not channel operator") def ERR_NOPRIVILEGES(): return _M('481', "Permission Denied- You're not an IRC operator") def ERR_NOOPERHOST(): return _M('491', 'No O-lines for your host') def ERR_USERSDONTMATCH(): return _M('502', 'Cannot change mode for other users') circuits-3.2.3/circuits/protocols/irc/utils.py000066400000000000000000000153641460335514400215220ustar00rootroot00000000000000"""Internet Relay Chat Utilities""" from re import compile as compile_regex PREFIX = compile_regex('([^!].*)!(.*)@(.*)') COLOR_CODE = compile_regex(r'(?:(\d\d?)(?:(,)(\d\d?))?)?') COLOR = compile_regex(r'\x03(?:(\d\d?)(?:,(\d\d?))?)?') class Error(Exception): """Error Exception""" def strip(s, color=False): """ strip(s, color=False) -> str Strips the : from the start of a string and optionally also strips all colors if color is True. :param s str: string to process :param color bool: whether to strip colors :returns str: returns processes string """ if len(s) > 0 and s[0] == ':': s = s[1:] if color: s = s.replace('\x01', '') s = s.replace('\x02', '') # bold s = s.replace('\x1d', '') # italics s = s.replace('\x1f', '') # underline s = s.replace('\x1e', '') # strikethrough s = s.replace('\x11', '') # monospace s = s.replace('\x16', '') # reverse color s = COLOR.sub('', s) # color codes s = s.replace('\x03', '') # color s = s.replace('\x0f', '') # reset return s def joinprefix(nick, user, host): """ Join the parts of a prefix :param nick str: nickname :param user str: username :param host str: hostname :returns str: a string in the form of !@ """ return '{}!{}@{}'.format(nick or '', user or '', host or '') def parseprefix(prefix): """ Parse a prefix into it's parts :param prefix str: prefix to parse :returns tuple: tuple of strings in the form of (nick, user, host) """ m = PREFIX.match(prefix) if m is not None: return m.groups() return prefix or None, None, None def parsemsg(s, encoding='utf-8'): """ Parse an IRC Message from s :param s bytes: bytes to parse :param encoding str: encoding to use (Default: utf-8) :returns tuple: parsed message in the form of (prefix, command, args) """ s = s.decode(encoding, 'replace') prefix = '' trailing = [] if s and s[0] == ':': prefix, s = s[1:].split(' ', 1) prefix = parseprefix(prefix) if s.find(' :') != -1: s, trailing = s.split(' :', 1) args = s.split() args.append(trailing) else: args = s.split() args = iter(args) command = next(args, None) command = command and str(command) return prefix, command, list(args) def irc_color_to_ansi(data, reset=True): """Maps IRC color codes to ANSI terminal escape sequences""" def ansi(*seq): return '\33[{}m'.format(';'.join(f'{x:02}' for x in seq if x)) ansi_default_fg = 39 ansi_default_bg = 49 color_map_fg = { 0: 37, 1: 30, 2: 34, 3: 32, 4: 31, 5: 36, 6: 35, 7: 33, 8: 93, 9: 92, 10: 36, 11: 96, 12: 94, 13: 95, 14: 90, 15: 37, 16: 52, 17: 94, 18: 100, 19: 58, 20: 22, 21: 29, 22: 23, 23: 24, 24: 17, 25: 54, 26: 53, 27: 89, 28: 88, 29: 130, 30: 142, 31: 64, 32: 28, 33: 35, 34: 30, 35: 25, 36: 18, 37: 91, 38: 90, 39: 125, 40: 124, 41: 166, 42: 184, 43: 106, 44: 34, 45: 49, 46: 37, 47: 33, 48: 19, 49: 129, 50: 127, 51: 161, 52: 196, 53: 208, 54: 226, 55: 154, 56: 46, 57: 86, 58: 51, 59: 75, 60: 21, 61: 171, 62: 201, 63: 198, 64: 203, 65: 215, 66: 227, 67: 191, 68: 83, 69: 122, 70: 87, 71: 111, 72: 63, 73: 177, 74: 207, 75: 205, 76: 217, 77: 223, 78: 229, 79: 193, 80: 157, 81: 158, 82: 159, 83: 153, 84: 147, 85: 183, 86: 219, 87: 212, 88: 16, 89: 233, 90: 235, 91: 237, 92: 239, 93: 241, 94: 244, 95: 247, 96: 250, 97: 254, 98: 231, 99: ansi_default_fg, } color_map_bg = { 0: 47, 1: 40, 2: 44, 3: 42, 4: 41, 5: 46, 6: 45, 7: 43, 8: 103, 10: 46, 14: 97, 15: 47, 99: ansi_default_bg, } enable_char = { '\x02': 1, '\x1d': 3, '\x1e': 9, '\x1f': 4, '\x16': 7, } revert_char = { '\x02': 22, '\x1d': 23, '\x1f': 24, '\x16': 27, '\x1e': 29, } def escape(data): ignore = set() start = [] current_fg = ansi_default_fg current_bg = ansi_default_bg for i, char in enumerate(data): if i in ignore: continue if char == '\x0f': # reset start = [] yield ansi(0) elif char in start and char in revert_char: start.remove(char) yield ansi(revert_char[char]) elif char in enable_char: start.append(char) yield ansi(enable_char[char]) elif char == '\x03': i += 1 m = COLOR_CODE.match(data[i : i + 5]) colors = [] if m: fg, has_bg, bg = m.groups() if fg: ignore.update(range(i, i + len(fg))) colors.append(color_map_fg.get(int(fg), current_fg)) current_fg = int(fg) if has_bg: ignore.update(range(i + len(fg), i + len(fg) + 1)) if bg: ignore.update(range(i + len(fg) + 1, i + len(fg) + 1 + len(bg))) colors.append(color_map_bg.get(int(bg), current_bg)) current_bg = int(bg) if char in start: start.remove(char) if colors: start.append(char) yield ansi(*colors) else: yield ansi(ansi_default_fg, ansi_default_bg) # elif char == u'\x04': # if char[i + 1:i + 6].isdigit(): # ignore.update(range(i, i + 6)) # # TODO: parse hex representation # elif char == u'\x11': # monospace # start.append(char) else: yield char if start and reset: yield ansi(0) return ''.join(escape(data)) circuits-3.2.3/circuits/protocols/line.py000066400000000000000000000066431460335514400205340ustar00rootroot00000000000000""" Line Protocol This module implements the basic Line protocol. This module can be used in both server and client implementations. """ import re from circuits.core import BaseComponent, Event, handler LINESEP = re.compile(b'\r?\n') def splitLines(s, buffer): """ splitLines(s, buffer) -> lines, buffer Append s to buffer and find any new lines of text in the string splitting at the standard IRC delimiter CRLF. Any new lines found, return them as a list and the remaining buffer for further processing. """ lines = LINESEP.split(buffer + s) return lines[:-1], lines[-1] class line(Event): """line Event""" class Line(BaseComponent): """ Line Protocol Implements the Line Protocol. Incoming data is split into lines with a splitter function. For each line of data processed a Line Event is created. Any unfinished lines are appended into an internal buffer. A custom line splitter function can be passed to customize how data is split into lines. This function must accept two arguments, the data to process and any left over data from a previous invocation of the splitter function. The function must also return a tuple of two items, a list of lines and any left over data. :param splitter: a line splitter function :type splitter: function This Component operates in two modes. In normal operation it's expected to be used in conjunction with components that expose a Read Event on a "read" channel with only one argument (data). Some builtin components that expose such events are: - circuits.net.sockets.TCPClient - circuits.io.File The second mode of operation works with circuits.net.sockets.Server components such as TCPServer, UNIXServer, etc. It's expected that two arguments exist in the Read Event, sock and data. The following two arguments can be passed to affect how unfinished data is stored and retrieved for such components: :param getBuffer: function to retrieve the buffer for a client sock :type getBuffer: function This function must accept one argument (sock,) the client socket whoose buffer is to be retrieved. :param updateBuffer: function to update the buffer for a client sock :type updateBuffer: function This function must accept two arguments (sock, buffer,) the client socket and the left over buffer to be updated. @note: This Component must be used in conjunction with a Component that exposes Read events on a "read" Channel. """ def __init__(self, *args, **kwargs): """Initializes x; see x.__class__.__doc__ for signature""" super().__init__(*args, **kwargs) self.encoding = kwargs.get('encoding', 'utf-8') # Used for Servers self.getBuffer = kwargs.get('getBuffer') self.updateBuffer = kwargs.get('updateBuffer') self.splitter = kwargs.get('splitter', splitLines) self.buffer = b'' @handler('read') def _on_read(self, *args): if len(args) == 1: # Client read (data,) = args lines, self.buffer = self.splitter(data, self.buffer) [self.fire(line(x)) for x in lines] else: # Server read sock, data = args lines, buffer = self.splitter(data, self.getBuffer(sock)) self.updateBuffer(sock, buffer) [self.fire(line(sock, x)) for x in lines] circuits-3.2.3/circuits/protocols/stomp/000077500000000000000000000000001460335514400203645ustar00rootroot00000000000000circuits-3.2.3/circuits/protocols/stomp/__init__.py000066400000000000000000000003571460335514400225020ustar00rootroot00000000000000""" STOMP Protocol This package contains a component implementing the STOMP Client protocol. This can be used with products such as ActiveMQ, RabbitMQ, etc """ from .client import StompClient __all__ = ('StompClient',) # pylama:skip=1 circuits-3.2.3/circuits/protocols/stomp/client.py000077500000000000000000000253551460335514400222310ustar00rootroot00000000000000"""Circuits component for handling Stomp Connection""" import logging import ssl import time import traceback from circuits import BaseComponent, Timer from circuits.core.handlers import handler from circuits.protocols.stomp.events import ( client_heartbeat, connected, connection_failed, disconnected, heartbeat_timeout, message, on_stomp_error, server_heartbeat, ) from circuits.protocols.stomp.transport import EnhancedStompFrameTransport try: from stompest.config import StompConfig from stompest.error import StompConnectionError, StompError from stompest.protocol import StompSession, StompSpec from stompest.sync import Stomp from stompest.sync.client import LOG_CATEGORY except ImportError: raise ImportError('No stomp support available. Is stompest installed?') StompSpec.DEFAULT_VERSION = '1.2' ACK_CLIENT_INDIVIDUAL = StompSpec.ACK_CLIENT_INDIVIDUAL ACK_AUTO = StompSpec.ACK_AUTO ACK_CLIENT = StompSpec.ACK_CLIENT ACK_MODES = (ACK_CLIENT_INDIVIDUAL, ACK_AUTO, ACK_CLIENT) LOG = logging.getLogger(__name__) class StompClient(BaseComponent): """Send and Receive messages from a STOMP queue""" channel = 'stomp' def init( self, host, port, username=None, password=None, connect_timeout=3, connected_timeout=3, version=StompSpec.VERSION_1_2, accept_versions=['1.0', '1.1', '1.2'], heartbeats=(0, 0), ssl_context=None, use_ssl=True, key_file=None, cert_file=None, ca_certs=None, ssl_version=ssl.PROTOCOL_TLS, key_file_password=None, proxy_host=None, proxy_port=None, proxy_user=None, proxy_password=None, channel=channel, ): """Initialize StompClient. Called after __init__""" self.channel = channel if proxy_host: LOG.info('Connect to %s:%s through proxy %s:%d', host, port, proxy_host, proxy_port) else: LOG.info('Connect to %s:%s', host, port) if use_ssl and not ssl_context: ssl_params = { 'key_file': key_file, 'cert_file': cert_file, 'ca_certs': ca_certs, 'ssl_version': ssl_version, 'password': key_file_password, } LOG.info('Request to use old-style socket wrapper: %s', ssl_params) ssl_context = ssl_params uri = f'ssl://{host}:{port}' if use_ssl else f'tcp://{host}:{port}' # Configure failover options so it only tries to connect once self._stomp_server = 'failover:(%s)?maxReconnectAttempts=1,startupMaxReconnectAttempts=1' % uri self._stomp_config = StompConfig( uri=self._stomp_server, sslContext=ssl_context, version=version, login=username, passcode=password ) self._heartbeats = heartbeats self._accept_versions = accept_versions self._connect_timeout = connect_timeout self._connected_timeout = connected_timeout Stomp._transportFactory = EnhancedStompFrameTransport Stomp._transportFactory.proxy_host = proxy_host Stomp._transportFactory.proxy_port = proxy_port Stomp._transportFactory.proxy_user = proxy_user Stomp._transportFactory.proxy_password = proxy_password self._client = Stomp(self._stomp_config) self._subscribed = {} self.server_heartbeat = None self.client_heartbeat = None self.ALLOWANCE = 2 # multiplier for heartbeat timeouts @property def connected(self): if self._client.session: return self._client.session.state == StompSession.CONNECTED return False @property def subscribed(self): return self._subscribed.keys() @property def stomp_logger(self): return LOG_CATEGORY @handler('disconnect') def _disconnect(self, receipt=None): if self.connected: self._client.disconnect(receipt=receipt) self._client.close(flush=True) self.fire(disconnected(reconnect=False)) self._subscribed = {} return 'disconnected' def start_heartbeats(self): LOG.info('Client HB: %s Server HB: %s', self._client.clientHeartBeat, self._client.serverHeartBeat) if self._client.clientHeartBeat: if self.client_heartbeat: # Timer already exists, just reset it self.client_heartbeat.reset() else: LOG.info('Client will send heartbeats to server') # Send heartbeats at 80% of agreed rate self.client_heartbeat = Timer((self._client.clientHeartBeat / 1000.0) * 0.8, client_heartbeat(), persist=True) self.client_heartbeat.register(self) else: LOG.info('No Client heartbeats will be sent') if self._client.serverHeartBeat: if self.server_heartbeat: # Timer already exists, just reset it self.server_heartbeat.reset() else: LOG.info('Requested heartbeats from server.') # Allow a grace period on server heartbeats self.server_heartbeat = Timer( (self._client.serverHeartBeat / 1000.0) * self.ALLOWANCE, server_heartbeat(), persist=True ) self.server_heartbeat.register(self) else: LOG.info('Expecting no heartbeats from Server') @handler('connect') def connect(self, event, host=None, *args, **kwargs): """Connect to Stomp server""" LOG.info('Connect to Stomp...') try: self._client.connect( heartBeats=self._heartbeats, host=host, versions=self._accept_versions, connectTimeout=self._connect_timeout, connectedTimeout=self._connected_timeout, ) LOG.info('State after Connection Attempt: %s', self._client.session.state) if self.connected: LOG.info('Connected to %s', self._stomp_server) self.fire(connected()) self.start_heartbeats() return 'success' except StompConnectionError: LOG.debug(traceback.format_exc()) self.fire(connection_failed(self._stomp_server)) event.success = False return 'fail' @handler('server_heartbeat') def check_server_heartbeat(self, event): """Confirm that heartbeat from server hasn't timed out""" now = time.time() last = self._client.lastReceived or 0 elapsed = now - last if last else -1 LOG.debug('Last received data %d seconds ago', elapsed) if ((self._client.serverHeartBeat / 1000.0) * self.ALLOWANCE + last) < now: LOG.error('Server heartbeat timeout. %d seconds since last heartbeat. Disconnecting.', elapsed) event.success = False self.fire(heartbeat_timeout()) if self.connected: self._client.disconnect() # TODO: Try to auto-reconnect? @handler('client_heartbeat') def send_heartbeat(self, event): if self.connected: LOG.debug('Sending heartbeat') try: self._client.beat() except StompConnectionError: event.success = False self.fire(disconnected()) @handler('generate_events') def generate_events(self, event): if not self.connected: return try: if self._client.canRead(1): frame = self._client.receiveFrame() LOG.debug('Recieved frame %s', frame) self.fire(message(frame)) except StompConnectionError: self.fire(disconnected()) @handler('send') def send(self, event, destination, body, headers=None, receipt=None): LOG.debug('send()') if not self.connected: LOG.error("Can't send when Stomp is disconnected") self.fire(on_stomp_error(None, Exception('Message send attempted with stomp disconnected'))) event.success = False return try: self._client.send(destination, body=body.encode('utf-8'), headers=headers, receipt=receipt) LOG.debug('Message sent') except StompConnectionError: event.success = False self.fire(disconnected()) except StompError as err: LOG.error('Error sending ack') event.success = False self.fire(on_stomp_error(None, err)) @handler('subscribe') def _subscribe(self, event, destination, ack=ACK_CLIENT_INDIVIDUAL): if ack not in ACK_MODES: raise ValueError('Invalid client ack mode specified') LOG.info('Subscribe to message destination %s', destination) try: # Set ID to match destination name for easy reference later _frame, token = self._client.subscribe(destination, headers={StompSpec.ACK_HEADER: ack, 'id': destination}) self._subscribed[destination] = token except StompConnectionError: event.success = False self.fire(disconnected()) except StompError as err: event.success = False LOG.debug(traceback.format_exc()) self.fire(on_stomp_error(None, err)) @handler('unsubscribe') def _unsubscribe(self, event, destination): if destination not in self._subscribed: LOG.error('Unsubscribe Request Ignored. Not subscribed to %s', destination) return try: token = self._subscribed.pop(destination) frame = self._client.unsubscribe(token) LOG.debug('Unsubscribed: %s', frame) except StompConnectionError: event.success = False self.fire(disconnected()) except StompError as err: LOG.error('Error sending ack') event.success = False self.fire(on_stomp_error(frame, err)) @handler('message') def on_message(self, event, headers, message): LOG.info('Stomp message received') @handler('ack') def ack_frame(self, event, frame): LOG.debug('ack_frame()') try: self._client.ack(frame) LOG.debug('Ack Sent') except StompConnectionError: LOG.error('Error sending ack') event.success = False self.fire(disconnected()) except StompError as err: LOG.error('Error sending ack') event.success = False self.fire(on_stomp_error(frame, err)) def get_subscription(self, frame): """Get subscription from frame""" LOG.info(self._subscribed) _, token = self._client.message(frame) return self._subscribed[token] circuits-3.2.3/circuits/protocols/stomp/events.py000066400000000000000000000043101460335514400222400ustar00rootroot00000000000000"""Circuits events for STOMP Client""" import logging from circuits import Event LOG = logging.getLogger(__name__) class stomp_event(Event): """A Circuits event with less verbose repr""" success = True def _repr(self): return '' def __repr__(self): """x.__repr__() <==> repr(x)""" if len(self.channels) > 1: channels = repr(self.channels) elif len(self.channels) == 1: channels = str(self.channels[0]) else: channels = '' data = self._repr() return f'<{self.name}[{channels}] ({data})>' class disconnected(stomp_event): def __init__(self, reconnect=True, receipt=None): super().__init__(receipt=receipt) self.reconnect = reconnect class disconnect(stomp_event): pass class message(stomp_event): def __init__(self, frame): super().__init__(headers=frame.headers, message=frame.body) self.frame = frame class send(stomp_event): def __init__(self, headers, body, destination): super().__init__(headers=headers, body=body, destination=destination) class client_heartbeat(stomp_event): pass class server_heartbeat(stomp_event): pass class connect(stomp_event): def __init__(self, subscribe=False, host=None): super().__init__(host=host) self.subscribe = subscribe class connected(stomp_event): pass class connection_failed(stomp_event): reconnect = True class on_stomp_error(stomp_event): def __init__(self, frame, err): headers = frame.headers if frame else {} body = frame.body if frame else None super().__init__(headers=headers, message=body, error=err) self.frame = frame class heartbeat_timeout(stomp_event): pass class subscribe(stomp_event): def __init__(self, destination, **kwargs): super().__init__(destination=destination, **kwargs) self.destination = destination class unsubscribe(stomp_event): def __init__(self, destination): super().__init__(destination=destination) self.destination = destination class ack(stomp_event): def __init__(self, frame): super().__init__(frame=frame) self.frame = frame circuits-3.2.3/circuits/protocols/stomp/transport.py000066400000000000000000000046671460335514400230070ustar00rootroot00000000000000"""stompest StompFrameTransport allowing for ssl.wrap_socket""" import logging import socket import ssl try: from stompest.error import StompConnectionError from stompest.sync.transport import StompFrameTransport except ImportError: raise ImportError('No stomp support available. Is stompest installed?') LOG = logging.getLogger(__name__) class EnhancedStompFrameTransport(StompFrameTransport): """add support for older ssl module and http proxy""" proxy_host = None proxy_port = None proxy_user = None proxy_password = None def connect(self, timeout=None): """Allow older versions of ssl module, allow http proxy connections""" LOG.debug('stomp_transport.connect()') ssl_params = None if isinstance(self.sslContext, dict): # This is actually a dictionary of ssl parameters for wrapping the socket ssl_params = self.sslContext self.sslContext = ssl.create_default_context() if ssl_params['ca_certs']: self.sslContext.load_verify_locations(cafile=ssl_params['ca_certs']) self.sslContext.load_cert_chain(certfile=ssl_params['cert_file'], keyfile=ssl_params['key_file']) cert_required = ssl.CERT_REQUIRED if ssl_params['ca_certs'] else ssl.CERT_NONE self.sslContext.verify_mode = cert_required self.sslContext.minimum_version = ssl_params['ssl_version'] try: if self.proxy_host: try: # Don't try to import this unless we need it import socks except ImportError: raise ImportError('No http proxy support available. Is pysocks installed?') LOG.info('Connecting through proxy %s', self.proxy_host) self._socket = socks.socksocket() self._socket.set_proxy( socks.HTTP, self.proxy_host, self.proxy_port, True, username=self.proxy_user, password=self.proxy_password ) else: self._socket = socket.socket() self._socket.settimeout(timeout) self._socket.connect((self.host, self.port)) if self.sslContext: self._socket = self.sslContext.wrap_socket(self._socket, server_hostname=self.host) except OSError as e: raise StompConnectionError('Could not establish connection [%s]' % e) self._parser.reset() circuits-3.2.3/circuits/protocols/websocket.py000066400000000000000000000174631460335514400215750ustar00rootroot00000000000000""".. codeauthor: mnl""" import os import random from circuits.core.components import BaseComponent from circuits.core.handlers import handler from circuits.net.events import close, read, write class WebSocketCodec(BaseComponent): """ WebSocket Protocol Implements the Data Framing protocol for WebSocket. This component is used in conjunction with a parent component that receives Read events on its channel. When created (after a successful WebSocket setup handshake), the codec registers a handler on the parent's channel that filters out these Read events for a given socket (if used in a server) or all Read events (if used in a client). The data is decoded and the contained payload is emitted as Read events on the codec's channel. The data from write events sent to the codec's channel (with socket argument if used in a server) is encoded according to the WebSocket Data Framing protocol. The encoded data is then forwarded as write events on the parents channel. """ channel = 'ws' def __init__(self, sock=None, data=bytearray(), *args, **kwargs): """ Creates a new codec. :param sock: the socket used in Read and write events (if used in a server, else None) """ super().__init__(*args, **kwargs) self._sock = sock self._pending_payload = bytearray() self._pending_type = None self._close_received = False self._close_sent = False self._buffer = bytearray() messages = self._parse_messages(bytearray(data)) for message in messages: if self._sock is not None: self.fire(read(self._sock, message)) else: self.fire(read(message)) @handler('registered') def _on_registered(self, component, parent): if component is self: @handler('read', priority=10, channel=parent.channel) def _on_read_raw(self, event, *args): if self._sock is not None: if args[0] != self._sock: return data = args[1] else: data = args[0] messages = self._parse_messages(bytearray(data)) for message in messages: if self._sock is not None: self.fire(read(self._sock, message)) else: self.fire(read(message)) event.stop() self.addHandler(_on_read_raw) @handler('disconnect', channel=parent.channel) def _on_disconnect(self, *args): if self._sock is not None and args[0] != self._sock: return self.unregister() self.addHandler(_on_disconnect) def _parse_messages(self, data): msgs = [] # one chunk of bytes may result in several messages if self._close_received: return msgs data = self._buffer + data while data: # extract final flag, opcode and masking final = bool(data[0] & 0x80 != 0) opcode = data[0] & 0xF masking = bool(data[1] & 0x80 != 0) # evaluate payload length payload_length = data[1] & 0x7F offset = 2 if payload_length >= 126: payload_bytes = 2 if payload_length == 126 else 8 payload_length = 0 for _ in range(payload_bytes): payload_length = payload_length * 256 + data[offset] offset += 1 # retrieve optional masking key if masking: masking_key = data[offset : offset + 4] offset += 4 # if not enough bytes available yet, retry after next read if len(data) - offset < payload_length: self._buffer = data break self._buffer = bytearray() # rest of _buffer is payload msg = data[offset : offset + payload_length] if masking: # unmask msg = list(msg) for i, c in enumerate(msg): msg[i] = c ^ masking_key[i % 4] msg = bytearray(msg) # remove bytes of processed frame from byte _buffer offset += payload_length data = data[offset:] # if there have been parts already, combine msg = self._pending_payload + msg if final: if opcode < 8: # if text or continuation of text, convert if opcode == 1 or (opcode == 0 and self._pending_type == 1): msg = msg.decode('utf-8', 'replace') self._pending_type = None self._pending_payload = bytearray() msgs.append(msg) # check for client closing the connection elif opcode == 8: self._close_received = True if self._sock: self.fire(close(self._sock)) else: self.fire(close()) break # check for Ping elif opcode == 9: if self._close_sent: return None frame = bytearray(b'\x8a') frame += self._encode_tail(msg, self._sock is None) self._write(frame) else: self._pending_payload = msg if opcode != 0: self._pending_type = opcode return msgs @handler('write') def _on_write(self, *args): if self._close_sent: return if self._sock is not None: if args[0] != self._sock: return data = args[1] else: data = args[0] frame = bytearray() first = 0x80 # set FIN flag, we never fragment if isinstance(data, str): first += 1 # text data = bytearray(data, 'utf-8') else: first += 2 # binary frame.append(first) frame += self._encode_tail(data, self._sock is None) self._write(frame) def _encode_tail(self, data, mask=False): tail = bytearray() data_length = len(data) if data_length <= 125: len_byte = data_length lbytes = 0 elif data_length <= 0xFFFF: len_byte = 126 lbytes = 2 else: len_byte = 127 lbytes = 8 if mask: len_byte = len_byte | 0x80 tail.append(len_byte) for i in range(lbytes - 1, -1, -1): tail.append(data_length >> (i * 8) & 0xFF) if mask: try: masking_key = bytearray(list(os.urandom(4))) except NotImplementedError: masking_key = bytearray(random.randint(0, 255) for i in range(4)) tail += masking_key for i, c in enumerate(data): tail.append(c ^ masking_key[i % 4]) else: tail += data return tail def _write(self, data): if self._sock is not None: self.fire(write(self._sock, data), self.parent.channel) else: self.fire(write(data), self.parent.channel) @handler('close') def _on_close(self, *args): if self._sock is not None and args and (args[0] != self._sock): return if not self._close_sent: self._write(b'\x88\x00') self._close_sent = True if self._close_received and self._close_sent: if self._sock: self.fire(close(self._sock), self.parent.channel) else: self.fire(close(), self.parent.channel) circuits-3.2.3/circuits/tools/000077500000000000000000000000001460335514400163365ustar00rootroot00000000000000circuits-3.2.3/circuits/tools/__init__.py000066400000000000000000000117651460335514400204610ustar00rootroot00000000000000""" Circuits Tools circuits.tools contains a standard set of tools for circuits. These tools are installed as executables with a prefix of "circuits." """ import inspect as _inspect from functools import wraps from warnings import warn, warn_explicit def tryimport(modules, obj=None, message=None): modules = (modules,) if isinstance(modules, str) else modules for module in modules: try: m = __import__(module, globals(), locals()) return getattr(m, obj) if obj is not None else m except Exception: pass if message is not None: warn(message) return None return None def getargspec(func): getargs = _inspect.getfullargspec if hasattr(_inspect, 'getfullargspec') else _inspect.getargspec return getargs(func)[:4] def walk(x, f, d=0, v=None): if not v: v = set() yield f(d, x) for c in x.components.copy(): if c not in v: v.add(c) yield from walk(c, f, d + 1, v) def edges(x, e=None, v=None, d=0): if not e: e = set() if not v: v = [] d += 1 for c in x.components.copy(): e.add((x, c, d)) edges(c, e, v, d) return e def findroot(x): if x.parent == x: return x return findroot(x.parent) def kill(x): for c in x.components.copy(): kill(c) if x.parent is not x: x.unregister() def _graph(x): """ Create a directed graph of the Component structure of x :param x: A Component or Manager to graph :type x: Component or Manager """ networkx = tryimport('networkx') pygraphviz = tryimport('pygraphviz') plt = tryimport('matplotlib.pyplot', 'pyplot') if not all([networkx, pygraphviz, plt]): return None, None graph_edges = [] for u, v, d in edges(x): graph_edges.append((u.name, v.name, float(d))) g = networkx.DiGraph() g.add_weighted_edges_from(graph_edges) elarge = [(u, v) for (u, v, d) in g.edges(data=True) if d['weight'] > 3.0] esmall = [(u, v) for (u, v, d) in g.edges(data=True) if d['weight'] <= 3.0] pos = networkx.spring_layout(g) # positions for all nodes # nodes networkx.draw_networkx_nodes(g, pos, node_size=700) # edges networkx.draw_networkx_edges(g, pos, edgelist=elarge, width=1) networkx.draw_networkx_edges( g, pos, edgelist=esmall, width=1, alpha=0.5, edge_color='b', style='dashed', ) # labels networkx.draw_networkx_labels( g, pos, font_size=10, font_family='sans-serif', ) plt.axis('off') plt.clf() return plt, g def graph_ascii(x): """ Display a directed graph of the Component structure of x :param x: A Component or Manager to graph :type x: Component or Manager @return: A directed graph representing x's Component structure. @rtype: str """ def printer(d, x): return '%s* %s' % (' ' * d, x) return '\n'.join(walk(x, printer)) def graph_dot(x, name=None): """ :param x: A Component or Manager to graph :type x: Component or Manager :param name: A name for the graph (defaults to x's name) :type name: str """ networkx = tryimport('networkx') _plt, g = _graph(x) if g is not None: networkx.drawing.nx_agraph.write_dot(g, f'{name or x.name}.dot') def graph_png(x, name=None): """ :param x: A Component or Manager to graph :type x: Component or Manager :param name: A name for the graph (defaults to x's name) :type name: str """ plt, _g = _graph(x) if plt is not None: plt.savefig(f'{name or x.name}.png') def graph(x, name=None): """ :param x: A Component or Manager to graph :type x: Component or Manager :param name: A name for the graph (defaults to x's name) :type name: str """ graph_dot(x, name) graph_png(x, name) return graph_ascii(x) def inspect(x): """ Display an inspection report of the Component or Manager x :param x: A Component or Manager to graph :type x: Component or Manager @return: A detailed inspection report of x @rtype: str """ s = [] write = s.append write(' Components: %d\n' % len(x.components)) for component in x.components: write(' %s\n' % component) write('\n') from circuits import reprhandler write(' Event Handlers: %d\n' % len(x._handlers.values())) for event in x._handlers: write(' %s; %d\n' % (event, len(x._handlers[event]))) for handler in x._handlers[event]: write(' %s\n' % reprhandler(handler)) return ''.join(s) def deprecated(f): @wraps(f) def wrapper(*args, **kwargs): warn_explicit( f'Call to deprecated function {f.__name__}', category=DeprecationWarning, filename=f.__code__.co_filename, lineno=f.__code__.co_firstlineno + 1, ) return f(*args, **kwargs) return wrapper circuits-3.2.3/circuits/web/000077500000000000000000000000001460335514400157535ustar00rootroot00000000000000circuits-3.2.3/circuits/web/__init__.py000066400000000000000000000012371460335514400200670ustar00rootroot00000000000000"""Circuits Library - Web circuits.web contains the circuits full stack web server that is HTTP and WSGI compliant. """ from .controllers import BaseController, Controller, expose from .dispatchers import XMLRPC, Dispatcher, Static, VirtualHosts from .errors import forbidden, httperror, notfound, redirect from .events import request, response, stream from .loggers import Logger from .servers import BaseServer, Server from .sessions import Sessions from .url import URL, parse_url try: from .dispatchers import JSONRPC except ImportError: pass try: from .controllers import JSONController except ImportError: pass # flake8: noqa # pylama: skip=1 circuits-3.2.3/circuits/web/_httpauth.py000077500000000000000000000313371460335514400203370ustar00rootroot00000000000000""" httpauth modules defines functions to implement HTTP Digest Authentication (RFC 2617). This has full compliance with 'Digest' and 'Basic' authentication methods. In 'Digest' it supports both MD5 and MD5-sess algorithms. Usage: First use 'doAuth' to request the client authentication for a certain resource. You should send an httplib.UNAUTHORIZED response to the client so he knows he has to authenticate itself. Then use 'parseAuthorization' to retrieve the 'auth_map' used in 'checkResponse'. To use 'checkResponse' you must have already verified the password associated with the 'username' key in 'auth_map' dict. Then you use the 'checkResponse' function to verify if the password matches the one sent by the client. SUPPORTED_ALGORITHM - list of supported 'Digest' algorithms SUPPORTED_QOP - list of supported 'Digest' 'qop'. """ import time from base64 import decodebytes as base64_decodebytes from hashlib import md5, sha1 from urllib.request import parse_http_list, parse_keqv_list __version__ = 1, 0, 1 __author__ = 'Tiago Cogumbreiro ' __credits__ = """ Peter van Kampen for its recipe which implement most of Digest authentication: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/302378 """ __license__ = """ Copyright (c) 2005, Tiago Cogumbreiro All rights reserved. Redistribution and use in source and binary forms, with or without modification are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Sylvain Hellegouarch nor the names of his contributor may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ __all__ = ( 'SUPPORTED_ALGORITHM', 'SUPPORTED_QOP', 'basicAuth', 'calculateNonce', 'checkResponse', 'digestAuth', 'doAuth', 'md5SessionKey', 'parseAuthorization', ) ############################################################################### MD5 = 'MD5' SHA1 = 'SHA1' MD5_SESS = 'MD5-sess' AUTH = 'auth' AUTH_INT = 'auth-int' SUPPORTED_ALGORITHM = (MD5, MD5_SESS) SUPPORTED_QOP = (AUTH, AUTH_INT) ############################################################################### # doAuth # DIGEST_AUTH_ENCODERS = { MD5: lambda val: md5(val).hexdigest(), MD5_SESS: lambda val: md5(val).hexdigest(), SHA1: lambda val: sha1.new(val).hexdigest(), } def calculateNonce(realm, algorithm=MD5): """ This is an auxaliary function that calculates 'nonce' value. It is used to handle sessions. """ assert algorithm in SUPPORTED_ALGORITHM try: encoder = DIGEST_AUTH_ENCODERS[algorithm] except KeyError: raise NotImplementedError( 'The chosen algorithm (%s) does not have an implementation yet' % algorithm, ) s = '%d:%s' % (time.time(), realm) return encoder(s.encode('utf-8')) def digestAuth(realm, algorithm=MD5, nonce=None, qop=AUTH): """Challenges the client for a Digest authentication.""" assert algorithm in SUPPORTED_ALGORITHM assert qop in SUPPORTED_QOP if nonce is None: nonce = calculateNonce(realm, algorithm) return 'Digest realm="%s", nonce="%s", algorithm="%s", qop="%s"' % ( realm, nonce, algorithm, qop, ) def basicAuth(realm): """Challengenes the client for a Basic authentication.""" assert '"' not in realm, 'Realms cannot contain the " (quote) character.' return 'Basic realm="%s"' % realm def doAuth(realm): """ 'doAuth' function returns the challenge string b giving priority over Digest and fallback to Basic authentication when the browser doesn't support the first one. This should be set in the HTTP header under the key 'WWW-Authenticate'. """ return digestAuth(realm) + ' ' + basicAuth(realm) ############################################################################### # Parse authorization parameters # def _parseDigestAuthorization(auth_params): # Convert the auth params to a dict items = parse_http_list(auth_params) params = parse_keqv_list(items) # Now validate the params # Check for required parameters required = ['username', 'realm', 'nonce', 'uri', 'response'] for k in required: if k not in params: return None # If qop is sent then cnonce and nc MUST be present if 'qop' in params and not ('cnonce' in params and 'nc' in params): return None # If qop is not sent, neither cnonce nor nc can be present if ('cnonce' in params or 'nc' in params) and 'qop' not in params: return None return params def _parseBasicAuthorization(auth_params): auth_params = auth_params.encode('utf-8') username, password = base64_decodebytes(auth_params).split(b':', 1) username = username.decode('utf-8') password = password.decode('utf-8') return {'username': username, 'password': password} AUTH_SCHEMES = { 'basic': _parseBasicAuthorization, 'digest': _parseDigestAuthorization, } def parseAuthorization(credentials): """ parseAuthorization will convert the value of the 'Authorization' key in the HTTP header to a map itself. If the parsing fails 'None' is returned. """ auth_scheme, auth_params = credentials.split(' ', 1) auth_scheme = auth_scheme.lower() parser = AUTH_SCHEMES[auth_scheme] params = parser(auth_params) if params is None: return None assert 'auth_scheme' not in params params['auth_scheme'] = auth_scheme return params ############################################################################### # Check provided response for a valid password # def md5SessionKey(params, password): """ If the "algorithm" directive's value is "MD5-sess", then A1 [the session key] is calculated only once - on the first request by the client following receipt of a WWW-Authenticate challenge from the server. This creates a 'session key' for the authentication of subsequent requests and responses which is different for each "authentication session", thus limiting the amount of material hashed with any one key. Because the server need only use the hash of the user credentials in order to create the A1 value, this construction could be used in conjunction with a third party authentication service so that the web server would not need the actual password value. The specification of such a protocol is beyond the scope of this specification. """ keys = ('username', 'realm', 'nonce', 'cnonce') params_copy = {} for key in keys: params_copy[key] = params[key] params_copy['algorithm'] = MD5_SESS return _A1(params_copy, password) def _A1(params, password): algorithm = params.get('algorithm', MD5) H = DIGEST_AUTH_ENCODERS[algorithm] if algorithm == MD5: # If the "algorithm" directive's value is "MD5" or is # unspecified, then A1 is: # A1 = unq(username-value) ":" unq(realm-value) ":" passwd return '%s:%s:%s' % (params['username'], params['realm'], password) if algorithm == MD5_SESS: # This is A1 if qop is set # A1 = H( unq(username-value) ":" unq(realm-value) ":" passwd ) # ":" unq(nonce-value) ":" unq(cnonce-value) s = '%s:%s:%s' % (params['username'], params['realm'], password) h_a1 = H(s.encode('utf-8')) return '%s:%s:%s' % (h_a1, params['nonce'], params['cnonce']) return None def _A2(params, method, kwargs): # If the "qop" directive's value is "auth" or is unspecified, then A2 is: # A2 = Method ":" digest-uri-value qop = params.get('qop', 'auth') if qop == 'auth': return method + ':' + params['uri'] if qop == 'auth-int': # If the "qop" value is "auth-int", then A2 is: # A2 = Method ":" digest-uri-value ":" H(entity-body) entity_body = kwargs.get('entity_body', '') H = kwargs['H'] return '%s:%s:%s' % ( method, params['uri'], H(entity_body), ) raise NotImplementedError("The 'qop' method is unknown: %s" % qop) def _computeDigestResponse(auth_map, password, method='GET', A1=None, **kwargs): """Generates a response respecting the algorithm defined in RFC 2617""" params = auth_map algorithm = params.get('algorithm', MD5) H = DIGEST_AUTH_ENCODERS[algorithm] def KD(secret, data): s = secret + ':' + data return H(s.encode('utf-8')) qop = params.get('qop', None) s = _A2(params, method, kwargs) H_A2 = H(s.encode('utf-8')) if algorithm == MD5_SESS and A1 is not None: H_A1 = H(A1.encode('utf-8')) else: s = _A1(params, password) H_A1 = H(s.encode('utf-8')) if qop in ('auth', 'auth-int'): # If the "qop" value is "auth" or "auth-int": # request-digest = <"> < KD ( H(A1), unq(nonce-value) # ":" nc-value # ":" unq(cnonce-value) # ":" unq(qop-value) # ":" H(A2) # ) <"> request = '%s:%s:%s:%s:%s' % ( params['nonce'], params['nc'], params['cnonce'], params['qop'], H_A2, ) elif qop is None: # If the "qop" directive is not present (this construction is # for compatibility with RFC 2069): # request-digest = # <"> < KD ( H(A1), unq(nonce-value) ":" H(A2) ) > <"> request = '%s:%s' % (params['nonce'], H_A2) return KD(H_A1, request) def _checkDigestResponse(auth_map, password, method='GET', A1=None, **kwargs): """ This function is used to verify the response given by the client when he tries to authenticate. Optional arguments: entity_body - when 'qop' is set to 'auth-int' you MUST provide the raw data you are going to send to the client (usually the HTML page. request_uri - the uri from the request line compared with the 'uri' directive of the authorization map. They must represent the same resource (unused at this time). """ if auth_map['realm'] != kwargs.get('realm', None): return False response = _computeDigestResponse(auth_map, password, method, A1, **kwargs) return response == auth_map['response'] def _checkBasicResponse(auth_map, password, method='GET', encrypt=None, **kwargs): # Note that the Basic response doesn't provide the realm value so we cannot # test it try: return encrypt(auth_map['password'], auth_map['username']) == password except TypeError: return encrypt(auth_map['password']) == password AUTH_RESPONSES = { 'basic': _checkBasicResponse, 'digest': _checkDigestResponse, } def checkResponse(auth_map, password, method='GET', encrypt=None, **kwargs): """ 'checkResponse' compares the auth_map with the password and optionally other arguments that each implementation might need. If the response is of type 'Basic' then the function has the following signature: checkBasicResponse (auth_map, password) -> bool If the response is of type 'Digest' then the function has the following signature: checkDigestResponse (auth_map, password, method = 'GET', A1 = None) -> bool The 'A1' argument is only used in MD5_SESS algorithm based responses. Check md5SessionKey() for more info. """ checker = AUTH_RESPONSES[auth_map['auth_scheme']] return checker( auth_map, password, method=method, encrypt=encrypt, **kwargs, ) circuits-3.2.3/circuits/web/client.py000066400000000000000000000066721460335514400176160ustar00rootroot00000000000000from urllib.parse import urlparse from circuits.core import BaseComponent, Event, handler from circuits.net.events import close, connect, write from circuits.net.sockets import TCPClient from circuits.protocols.http import HTTP from circuits.web.headers import Headers def parse_url(url): p = urlparse(url) if p.hostname: host = p.hostname else: raise ValueError('URL must be absolute') if p.scheme == 'http': secure = False port = p.port or 80 elif p.scheme == 'https': secure = True port = p.port or 443 else: raise ValueError('Invalid URL scheme') path = p.path or '/' if p.query: path += '?' + p.query return (host, port, path, secure) class HTTPException(Exception): pass class NotConnected(HTTPException): pass class request(Event): """ request Event This Event is used to initiate a new request. :param method: HTTP Method (PUT, GET, POST, DELETE) :type method: str :param url: Request URL :type url: str """ def __init__(self, method, path, body=None, headers=None): """x.__init__(...) initializes x; see x.__class__.__doc__ for signature""" super().__init__(method, path, body, headers) class Client(BaseComponent): channel = 'client' def __init__(self, channel=channel): super().__init__(channel=channel) self._response = None self._transport = TCPClient(channel=channel).register(self) HTTP(channel=channel).register(self._transport) @handler('write') def write(self, data): if self._transport.connected: self.fire(write(data), self._transport) @handler('close') def close(self): if self._transport.connected: self.fire(close(), self._transport) @handler('connect', priority=1) def connect(self, event, host=None, port=None, secure=None): if not self._transport.connected: self.fire(connect(host, port, secure), self._transport) event.stop() @handler('request') def request(self, method, url, body=None, headers=None): host, port, path, secure = parse_url(url) if not self._transport.connected: self.fire(connect(host, port, secure)) yield self.wait('connected', self._transport.channel) headers = Headers(list((headers or {}).items())) # Clients MUST include Host header in HTTP/1.1 requests (RFC 2616) if 'Host' not in headers: headers['Host'] = '{}{}'.format( host, '' if port in (80, 443) else f':{port:d}', ) if body is not None: headers['Content-Length'] = len(body) command = f'{method} {path} HTTP/1.1' message = f'{command}\r\n{headers}' self.fire(write(message.encode('utf-8')), self._transport) if body is not None: self.fire(write(body), self._transport) yield (yield self.wait('response')) @handler('response') def _on_response(self, response): self._response = response if response.headers.get('Connection', '').lower() == 'close': self.fire(close(), self._transport) return response @property def connected(self): if hasattr(self, '_transport'): return self._transport.connected return None @property def response(self): return getattr(self, '_response', None) circuits-3.2.3/circuits/web/constants.py000066400000000000000000000044731460335514400203510ustar00rootroot00000000000000""" Global Constants This module implements required shared global constants. """ from circuits import __version__ SERVER_PROTOCOL = (1, 1) SERVER_VERSION = 'circuits.web/%s' % __version__ SERVER_URL = 'http://circuitsweb.com/' DEFAULT_ERROR_MESSAGE = """\ %(code)d %(name)s

%(name)s

%(description)s
%(traceback)s
%(powered_by)s """ POWERED_BY = """
Powered by %(version)s
""" HTTP_STATUS_CODES = { 100: 'Continue', 101: 'Switching Protocols', 102: 'Processing', 200: 'OK', 201: 'Created', 202: 'Accepted', 203: 'Non Authoritative Information', 204: 'No Content', 205: 'Reset Content', 206: 'Partial Content', 207: 'Multi Status', 226: 'IM Used', 300: 'Multiple Choices', 301: 'Moved Permanently', 302: 'Found', 303: 'See Other', 304: 'Not Modified', 305: 'Use Proxy', 307: 'Temporary Redirect', 400: 'Bad Request', 401: 'Unauthorized', 402: 'Payment Required', 403: 'Forbidden', 404: 'Not Found', 405: 'Method Not Allowed', 406: 'Not Acceptable', 407: 'Proxy Authentication Required', 408: 'Request Timeout', 409: 'Conflict', 410: 'Gone', 411: 'Length Required', 412: 'Precondition Failed', 413: 'Request Entity Too Large', 414: 'Request URI Too Long', 415: 'Unsupported Media Type', 416: 'Requested Range Not Satisfiable', 417: 'Expectation Failed', 418: 'I"m a teapot', 422: 'Unprocessable Entity', 423: 'Locked', 424: 'Failed Dependency', 426: 'Upgrade Required', 449: 'Retry With', 500: 'Internal Server Error', 501: 'Not Implemented', 502: 'Bad Gateway', 503: 'Service Unavailable', 504: 'Gateway Timeout', 505: 'HTTP Version Not Supported', 507: 'Insufficient Storage', 510: 'Not Extended', } circuits-3.2.3/circuits/web/controllers.py000066400000000000000000000125121460335514400206740ustar00rootroot00000000000000""" Controllers This module implements ... """ import json from collections.abc import Callable from functools import update_wrapper from circuits.core import BaseComponent, handler from circuits.tools import getargspec from . import tools from .errors import forbidden, httperror, notfound, redirect from .wrappers import Response def expose(*channels, **config): def decorate(f): @handler(*channels, **config) def wrapper(self, event, *args, **kwargs): try: if not hasattr(self, 'request'): (self.request, self.response), args = args[:2], args[2:] self.request.args = args self.request.kwargs = kwargs self.cookie = self.request.cookie if hasattr(self.request, 'session'): self.session = self.request.session if not getattr(f, 'event', False): return f(self, *args, **kwargs) return f(self, event, *args, **kwargs) finally: if hasattr(self, 'request'): del self.request del self.response del self.cookie if hasattr(self, 'session'): del self.session wrapper.args, wrapper.varargs, wrapper.varkw, wrapper.defaults = getargspec(f) if wrapper.args and wrapper.args[0] == 'self': del wrapper.args[0] if wrapper.args and wrapper.args[0] == 'event': f.event = True del wrapper.args[0] wrapper.event = True return update_wrapper(wrapper, f) return decorate class ExposeMetaClass(type): def __init__(cls, name, bases, dct): super().__init__(name, bases, dct) for k, v in dct.items(): if isinstance(v, Callable) and not (k[0] == '_' or hasattr(v, 'handler')): setattr(cls, k, expose(k)(v)) class BaseController(BaseComponent): channel = '/' @property def uri(self): """ Return the current Request URI .. seealso:: :py:class:`circuits.web.url.URL` """ if hasattr(self, 'request'): return self.request.uri return None def forbidden(self, description=None): """ Return a 403 (Forbidden) response :param description: Message to display :type description: str """ return forbidden(self.request, self.response, description=description) def notfound(self, description=None): """ Return a 404 (Not Found) response :param description: Message to display :type description: str """ return notfound(self.request, self.response, description=description) def redirect(self, urls, code=None): """ Return a 30x (Redirect) response Redirect to another location specified by urls with an optional custom response code. :param urls: A single URL or list of URLs :type urls: str or list :param code: HTTP Redirect code :type code: int """ return redirect(self.request, self.response, urls, code=code) def serve_file(self, path, type=None, disposition=None, name=None): return tools.serve_file( self.request, self.response, path, type, disposition, name, ) def serve_download(self, path, name=None): return tools.serve_download( self.request, self.response, path, name, ) def expires(self, secs=0, force=False): tools.expires(self.request, self.response, secs, force) Controller = ExposeMetaClass('Controller', (BaseController,), {}) def exposeJSON(*channels, **config): def decorate(f): @handler(*channels, **config) def wrapper(self, *args, **kwargs): try: if not hasattr(self, 'request'): self.request, self.response = args[:2] args = args[2:] self.cookie = self.request.cookie if hasattr(self.request, 'session'): self.session = self.request.session self.response.headers['Content-Type'] = 'application/json' result = f(self, *args, **kwargs) if isinstance(result, (httperror, Response)): return result return json.dumps(result) finally: if hasattr(self, 'request'): del self.request del self.response del self.cookie if hasattr(self, 'session'): del self.session wrapper.args, wrapper.varargs, wrapper.varkw, wrapper.defaults = getargspec(f) if wrapper.args and wrapper.args[0] == 'self': del wrapper.args[0] return update_wrapper(wrapper, f) return decorate class ExposeJSONMetaClass(type): def __init__(cls, name, bases, dct): super().__init__(name, bases, dct) for k, v in dct.items(): if isinstance(v, Callable) and not (k[0] == '_' or hasattr(v, 'handler')): setattr(cls, k, exposeJSON(k)(v)) JSONController = ExposeJSONMetaClass('JSONController', (BaseController,), {}) circuits-3.2.3/circuits/web/dispatchers/000077500000000000000000000000001460335514400202645ustar00rootroot00000000000000circuits-3.2.3/circuits/web/dispatchers/__init__.py000066400000000000000000000006261460335514400224010ustar00rootroot00000000000000"""Dispatchers This package contains various circuits.web dispatchers By default a ``circuits.web.Server`` Component uses the ``dispatcher.Dispatcher`` """ from ..websockets.dispatcher import WebSocketsDispatcher from .dispatcher import Dispatcher from .jsonrpc import JSONRPC from .static import Static from .virtualhosts import VirtualHosts from .xmlrpc import XMLRPC # flake8: noqa # pylama: skip=1 circuits-3.2.3/circuits/web/dispatchers/dispatcher.py000066400000000000000000000074001460335514400227650ustar00rootroot00000000000000""" Dispatcher This module implements a basic URL to Channel dispatcher. This is the default dispatcher used by circuits.web """ from circuits import BaseComponent, Event, handler from circuits.web.controllers import BaseController from circuits.web.events import response from circuits.web.processors import process from circuits.web.utils import parse_qs def resolve_path(paths, parts): def rebuild_path(url_parts): return '/%s' % '/'.join(url_parts) left_over = [] while parts: if rebuild_path(parts) in paths: yield rebuild_path(parts), left_over left_over.insert(0, parts.pop()) if '/' in paths: yield '/', left_over def resolve_methods(parts): if parts: method = parts[0] vpath = parts[1:] yield method, vpath yield 'index', parts def find_handlers(req, paths): def get_handlers(path, method): component = paths[path] return component._handlers.get(method, None) def accepts_vpath(handlers, vpath): args_no = len(vpath) return all( len(h.args) == args_no or h.varargs or (h.defaults is not None and args_no <= len(h.defaults)) for h in handlers ) # Split /hello/world to ['hello', 'world'] starting_parts = [x for x in req.path.strip('/').split('/') if x] for path, parts in resolve_path(paths, starting_parts): handlers = get_handlers(path, req.method) if handlers: return handlers, req.method, path, parts for method, vpath in resolve_methods(parts): handlers = get_handlers(path, method) if handlers and (not vpath or accepts_vpath(handlers, vpath)): req.index = method == 'index' return handlers, method, path, vpath method, vpath = 'index', [method] + vpath handlers = get_handlers(path, method) if handlers and (not vpath or accepts_vpath(handlers, vpath)): req.index = True return handlers, method, path, vpath return [], None, None, None class Dispatcher(BaseComponent): channel = 'web' def __init__(self, **kwargs): super().__init__(**kwargs) self.paths = {} @handler('registered', channel='*') def _on_registered(self, component, manager): if isinstance(component, BaseController) and component.channel not in self.paths: self.paths[component.channel] = component @handler('unregistered', channel='*') def _on_unregistered(self, component, manager): if isinstance(component, BaseController) and component.channel in self.paths: del self.paths[component.channel] @handler('request', priority=0.1) def _on_request(self, event, req, res, peer_cert=None): if peer_cert: event.peer_cert = peer_cert _handlers, name, channel, vpath = find_handlers(req, self.paths) if name is not None and channel is not None: event.kwargs = parse_qs(req.qs) process(req, event.kwargs) if vpath: event.args += tuple(vpath) return self.fire( Event.create( name, *event.args, **event.kwargs, ), channel, ) return None @handler('request_value_changed') def _on_request_value_changed(self, value): if value.handled: return _req, res = value.event.args[:2] if value.result and not value.errors: res.body = value.value self.fire(response(res)) elif value.promise: value.event.notify = True else: # Errors are handled by the ``HTTP`` Protocol Component return circuits-3.2.3/circuits/web/dispatchers/jsonrpc.py000066400000000000000000000040531460335514400223160ustar00rootroot00000000000000""" JSON RPC This module implements a JSON RPC dispatcher that translates incoming RPC calls over JSON into RPC events. """ import json from circuits import BaseComponent, Event, handler class rpc(Event): """RPC Event""" class JSONRPC(BaseComponent): channel = 'web' def __init__(self, path=None, encoding='utf-8', rpc_channel='*'): super().__init__() if json is None: raise RuntimeError('No json support available') self.path = path self.encoding = encoding self.rpc_channel = rpc_channel @handler('request', priority=0.2) def _on_request(self, event, req, res): if self.path is not None and self.path != req.path.rstrip('/'): return res.headers['Content-Type'] = 'application/json' try: data = req.body.read().decode(self.encoding) o = json.loads(data) id, method, params = o['id'], o['method'], o.get('params', {}) if isinstance(params, dict): params = {str(k): v for k, v in params.items()} method = str(method) if isinstance(params, dict): value = yield self.call(rpc.create(method, **params), self.rpc_channel) else: value = yield self.call(rpc.create(method, *params), self.rpc_channel) yield self._response(id, value.value) except Exception as e: yield self._error(-1, 100, f'{e.__class__.__name__}: {e}') finally: event.stop() def _response(self, id, result): data = { 'id': id, 'version': '1.1', 'result': result, 'error': None, } return json.dumps(data).encode(self.encoding) def _error(self, id, code, message): data = { 'id': id, 'version': '1.1', 'error': { 'name': 'JSONRPCError', 'code': code, 'message': message, }, } return json.dumps(data).encode(self.encoding) circuits-3.2.3/circuits/web/dispatchers/static.py000066400000000000000000000114231460335514400221260ustar00rootroot00000000000000""" Static This modStatic implements a Static dispatcher used to serve up static resources and an optional apache-style directory listing. """ import os from html import escape from string import Template from urllib.parse import quote, unquote from circuits import BaseComponent, handler from circuits.web.tools import serve_file DEFAULT_DIRECTORY_INDEX_TEMPLATE = """ Index of $directory

Index of $directory

    $url_up $listing
""" _dirlisting_template = Template(DEFAULT_DIRECTORY_INDEX_TEMPLATE) class Static(BaseComponent): channel = 'web' def __init__(self, path=None, docroot=None, defaults=('index.html', 'index.xhtml'), dirlisting=False, **kwargs): super().__init__(**kwargs) self.path = path self.docroot = os.path.abspath(docroot) if docroot is not None else os.path.abspath(os.getcwd()) self.defaults = defaults self.dirlisting = dirlisting @handler('request', priority=0.9) def _on_request(self, event, request, response): if self.path is not None and not request.path.startswith(self.path): return None path = request.path if self.path is not None: path = path[len(self.path) :] path = unquote(path.strip('/')) if path: location = os.path.abspath(os.path.join(self.docroot, path)) else: location = os.path.abspath(os.path.join(self.docroot, '.')) if not os.path.exists(location): return None if not location.startswith(os.path.dirname(self.docroot)): return None # hacking attempt e.g. /foo/../../../../../etc/shadow # Is it a file we can serve directly? if os.path.isfile(location): # Don't set cookies for static content response.cookie.clear() try: return serve_file(request, response, location) finally: event.stop() # Is it a directory? elif os.path.isdir(location): # Try to serve one of default files first.. for default in self.defaults: location = os.path.abspath( os.path.join(self.docroot, path, default), ) if os.path.exists(location): # Don't set cookies for static content response.cookie.clear() try: return serve_file(request, response, location) finally: event.stop() # .. serve a directory listing if allowed to. if self.dirlisting: directory = os.path.abspath(os.path.join(self.docroot, path)) cur_dir = os.path.join(self.path, path) if self.path else '' if not path: url_up = '' else: url_up = os.path.join('/', os.path.split(path)[0]) if self.path is None else os.path.join(cur_dir, '..') url_up = '
  • %s
  • ' % (escape(url_up, True), '..') listing = [] for item in os.listdir(directory): if not item.startswith('.'): url = os.path.join('/', path, cur_dir, item) location = os.path.abspath( os.path.join(self.docroot, path, item), ) if os.path.isdir(location): li = '
  • %s/
  • ' % ( escape(quote(url), True), escape(item), ) else: li = '
  • %s
  • ' % ( escape(quote(url), True), escape(item), ) listing.append(li) ctx = {} ctx['directory'] = cur_dir or os.path.join('/', cur_dir, path) ctx['url_up'] = url_up ctx['listing'] = '\n'.join(listing) try: return _dirlisting_template.safe_substitute(ctx) finally: event.stop() return None return None circuits-3.2.3/circuits/web/dispatchers/virtualhosts.py000066400000000000000000000031411460335514400234040ustar00rootroot00000000000000""" VirtualHost This module implements a virtual host dispatcher that sends requests for configured virtual hosts to different dispatchers. """ from urllib.parse import urljoin from circuits import BaseComponent, handler class VirtualHosts(BaseComponent): """ Forward to anotehr Dispatcher based on the Host header. This can be useful when running multiple sites within one server. It allows several domains to point to different parts of a single website structure. For example: - http://www.domain.example -> / - http://www.domain2.example -> /domain2 - http://www.domain2.example:443 -> /secure :param domains: a dict of {host header value: virtual prefix} pairs. :type domains: dict The incoming "Host" request header is looked up in this dict, and, if a match is found, the corresponding "virtual prefix" value will be prepended to the URL path before passing the request onto the next dispatcher. Note that you often need separate entries for "example.com" and "www.example.com". In addition, "Host" headers may contain the port number. """ channel = 'web' def __init__(self, domains): super().__init__() self.domains = domains @handler('request', priority=1.0) def _on_request(self, event, request, response): path = request.path.strip('/') header = request.headers.get domain = header('X-Forwarded-Host', header('Host', '')) prefix = self.domains.get(domain, '') if prefix: path = urljoin('/%s/' % prefix, path) request.path = path circuits-3.2.3/circuits/web/dispatchers/xmlrpc.py000066400000000000000000000026211460335514400221440ustar00rootroot00000000000000""" XML RPC This module implements a XML RPC dispatcher that translates incoming RPC calls over XML into RPC events. """ from xmlrpc.client import Fault, dumps, loads from circuits import BaseComponent, Event, handler class rpc(Event): """rpc Event""" class XMLRPC(BaseComponent): channel = 'web' def __init__(self, path=None, encoding='utf-8', rpc_channel='*'): super().__init__() self.path = path self.encoding = encoding self.rpc_channel = rpc_channel @handler('request', priority=0.2) def _on_request(self, event, req, res): if self.path is not None and self.path != req.path.rstrip('/'): return res.headers['Content-Type'] = 'text/xml' try: data = req.body.read() params, method = loads(data) if not isinstance(method, str): method = str(method) value = yield self.call(rpc.create(method, *params), self.rpc_channel) yield self._response(value.value) except Exception as exc: yield self._error(1, f'{type(exc).__name__}: {exc}') finally: event.stop() def _response(self, result): return dumps((result,), encoding=self.encoding, allow_none=True) def _error(self, code, message): fault = Fault(code, message) return dumps(fault, encoding=self.encoding, allow_none=True) circuits-3.2.3/circuits/web/errors.py000066400000000000000000000174551460335514400176550ustar00rootroot00000000000000""" Errors This module implements a set of standard HTTP Errors. """ import json import traceback from html import escape from circuits import Event from .constants import DEFAULT_ERROR_MESSAGE, HTTP_STATUS_CODES, POWERED_BY, SERVER_URL, SERVER_VERSION class httperror(Event): """An event for signaling an HTTP error""" code = 500 description = '' def __init__(self, request, response, code=None, **kwargs): """ The constructor creates a new instance and modifies the *response* argument to reflect the error. """ super().__init__(request, response, code, **kwargs) # Override HTTPError subclasses self.name = 'httperror' self.request = request self.response = response if code is not None: self.code = code self.error = kwargs.get('error', None) self.description = kwargs.get( 'description', getattr(self.__class__, 'description', ''), ) if self.error is not None: stack = self.error[2] if isinstance(self.error[2], (list, tuple)) else traceback.format_tb(self.error[2]) self.traceback = 'ERROR: (%s) %s\n%s' % ( self.error[0], self.error[1], ''.join(stack), ) else: self.traceback = '' self.response.close = True self.response.status = self.code powered_by = ( POWERED_BY % { 'url': escape(SERVER_URL, True), 'version': escape(SERVER_VERSION), } if getattr(request.server, 'display_banner', False) else '' ) self.data = { 'code': self.code, 'name': HTTP_STATUS_CODES.get(self.code, '???'), 'description': self.description, 'traceback': self.traceback, 'powered_by': powered_by, } def sanitize(self): if self.code != 201 and not (299 < self.code < 400) and 'Location' in self.response.headers: del self.response.headers['Location'] def __str__(self): self.sanitize() if self.code < 200 or self.code in (204, 205, 304): return '' if 'json' in self.response.headers.get('Content-Type', ''): index = ['code', 'name', 'description'] if self.request.print_debug: index.append('traceback') return json.dumps({key: self.data[key] for key in index}) if not self.request.print_debug: self.data['traceback'] = '' # FIXME: description is a possible XSS attack vector return DEFAULT_ERROR_MESSAGE % { key: ( escape(value, True) if key not in ('powered_by', 'description') and not isinstance(value, (int, float)) else value ) for key, value in self.data.items() } def __repr__(self): return '<%s %d %s>' % ( self.__class__.__name__, self.code, HTTP_STATUS_CODES.get( self.code, '???', ), ) class forbidden(httperror): """An event for signaling the HTTP Forbidden error""" code = 403 class unauthorized(httperror): """An event for signaling the HTTP Unauthorized error""" code = 401 class notfound(httperror): """An event for signaling the HTTP Not Fouond error""" code = 404 class redirect(httperror): """An event for signaling the HTTP Redirect response""" def __init__(self, request, response, urls, code=None): """ The constructor creates a new instance and modifies the *response* argument to reflect a redirect response to the given *url*. """ if isinstance(urls, str): urls = [urls] abs_urls = [] for url in urls: # Note that urljoin will "do the right thing" whether url is: # 1. a complete URL with host (e.g. "http://www.example.com/test") # 2. a URL relative to root (e.g. "/dummy") # 3. a URL relative to the current path # Note that any query string in request is discarded. url = request.uri.relative(url).unicode() abs_urls.append(url) self.urls = urls = abs_urls # RFC 2616 indicates a 301 response code fits our goal; however, # browser support for 301 is quite messy. Do 302/303 instead. See # http://ppewww.ph.gla.ac.uk/~flavell/www/post-redirect.html if code is None: code = 303 if request.protocol >= (1, 1) else 302 else: if code < 300 or code > 399: raise ValueError('status code must be between 300 and 399.') super().__init__(request, response, code) if code in (300, 301, 302, 303, 307, 308): response.headers['Content-Type'] = 'text/html' # "The ... URI SHOULD be given by the Location field # in the response." response.headers['Location'] = urls[0] # "Unless the request method was HEAD, the entity of the response # SHOULD contain a short hypertext note with a hyperlink to the # new URI(s)." msg = { 300: "This resource can be found at %s.", 301: ('This resource has permanently moved to %s.'), 302: ('This resource resides temporarily at %s.'), 303: ('This resource can be found at %s.'), 307: ('This resource has moved temporarily to %s.'), 308: ('This resource has permanently moved to %s.'), }[code] response.body = '
    \n'.join([msg % (escape(u, True), escape(u)) for u in urls]) # Previous code may have set C-L, so we have to reset it # (allow finalize to set it). response.headers.pop('Content-Length', None) elif code == 304: # Not Modified. # "The response MUST include the following header fields: # Date, unless its omission is required by section 14.18.1" # The "Date" header should have been set in Response.__init__ # "...the response SHOULD NOT include other entity-headers." for key in ( 'Allow', 'Content-Encoding', 'Content-Language', 'Content-Length', 'Content-Location', 'Content-MD5', 'Content-Range', 'Content-Type', 'Expires', 'Last-Modified', ): if key in response.headers: del response.headers[key] # "The 304 response MUST NOT contain a message-body." response.body = None # Previous code may have set C-L, so we have to reset it. response.headers.pop('Content-Length', None) elif code == 305: # Use Proxy. # urls[0] should be the URI of the proxy. response.headers['Location'] = urls[0] response.body = None # Previous code may have set C-L, so we have to reset it. response.headers.pop('Content-Length', None) else: raise ValueError('The %s status code is unknown.' % code) def __repr__(self): if len(self.channels) > 1: channels = repr(self.channels) elif len(self.channels) == 1: channels = str(self.channels[0]) else: channels = '' return '<%s %d[%s.%s] %s>' % ( self.__class__.__name__, self.code, channels, self.name, ' '.join(self.urls), ) circuits-3.2.3/circuits/web/events.py000066400000000000000000000012001460335514400176220ustar00rootroot00000000000000""" Events This module implements the necessary Events needed. """ from circuits import Event class request(Event): """ request(Event) -> request Event args: request, response """ success = True failure = True complete = True class response(Event): """ response(Event) -> response Event args: request, response """ success = True failure = True complete = True class stream(Event): """ stream(Event) -> stream Event args: request, response """ success = True failure = True complete = True class terminate(Event): """terminate Event""" circuits-3.2.3/circuits/web/exceptions.py000066400000000000000000000210501460335514400205040ustar00rootroot00000000000000""" Exceptions This module implements a set of standard HTTP Errors as Python Exceptions. Note: This code is mostly borrowed from werkzeug and adapted for circuits.web """ from .constants import HTTP_STATUS_CODES __all__ = ( 'BadGateway', 'BadRequest', 'Forbidden', 'Gone', 'HTTPException', 'InternalServerError', 'LengthRequired', 'MethodNotAllowed', 'NotAcceptable', 'NotFound', 'NotImplemented', 'PreconditionFailed', 'RangeUnsatisfiable', 'Redirect', 'RequestEntityTooLarge', 'RequestTimeout', 'RequestURITooLarge', 'ServiceUnavailable', 'Unauthorized', 'UnicodeError', 'UnsupportedMediaType', ) class HTTPException(Exception): """ Baseclass for all HTTP exceptions. This exception can be called by WSGI applications to render a default error page or you can catch the subclasses of it independently and render nicer error messages. """ code = None traceback = True description = None def __init__(self, description=None, traceback=None): super().__init__('%d %s' % (self.code, self.name)) if description is not None: self.description = description if traceback is not None: self.traceback = traceback @property def name(self): """The status name.""" return HTTP_STATUS_CODES.get(self.code, '') def __repr__(self): return f'<{self.__class__.__name__} {str(self)!r}>' class BadRequest(HTTPException): """ *400* `Bad Request` Raise if the browser sends something to the application the application or server cannot handle. """ code = 400 description = '

    The browser (or proxy) sent a request that this server could not understand.

    ' class UnicodeError(HTTPException): """ raised by the request functions if they were unable to decode the incoming data properly. """ class Unauthorized(HTTPException): """ *401* `Unauthorized` Raise if the user is not authorized. Also used if you want to use HTTP basic auth. """ code = 401 description = ( '

    The server could not verify that you are authorized to access ' 'the URL requested. You either supplied the wrong credentials (e.g. ' "a bad password), or your browser doesn't understand how to supply " 'the credentials required.

    In case you are allowed to request ' 'the document, please check your user-id and password and try ' 'again.

    ' ) class Forbidden(HTTPException): """ *403* `Forbidden` Raise if the user doesn't have the permission for the requested resource but was authenticated. """ code = 403 description = ( "

    You don't have the permission to access the requested resource. " 'It is either read-protected or not readable by the server.

    ' ) class NotFound(HTTPException): """ *404* `Not Found` Raise if a resource does not exist and never existed. """ code = 404 description = ( '

    The requested URL was not found on the server.

    ' '

    If you entered the URL manually please check your spelling and ' 'try again.

    ' ) class MethodNotAllowed(HTTPException): """ *405* `Method Not Allowed` Raise if the server used a method the resource does not handle. For example `POST` if the resource is view only. Especially useful for REST. The first argument for this exception should be a list of allowed methods. Strictly speaking the response would be invalid if you don't provide valid methods in the header which you can do with that list. """ code = 405 def __init__(self, method, description=None): HTTPException.__init__(self, description) if description is None: self.description = ('

    The method %s is not allowed for the requested URL.

    ') % method class NotAcceptable(HTTPException): """ *406* `Not Acceptable` Raise if the server can't return any content conforming to the `Accept` headers of the client. """ code = 406 description = ( '

    The resource identified by the request is only capable of ' 'generating response entities which have content characteristics ' 'not acceptable according to the accept headers sent in the ' 'request.

    ' ) class RequestTimeout(HTTPException): """ *408* `Request Timeout` Raise to signalize a timeout. """ code = 408 description = ( '

    The server closed the network connection because the browser ' "didn't finish the request within the specified time.

    " ) class Gone(HTTPException): """ *410* `Gone` Raise if a resource existed previously and went away without new location. """ code = 410 description = ( '

    The requested URL is no longer available on this server and ' 'there is no forwarding address.

    If you followed a link ' 'from a foreign page, please contact the author of this page.' ) class LengthRequired(HTTPException): """ *411* `Length Required` Raise if the browser submitted data but no ``Content-Length`` header which is required for the kind of processing the server does. """ code = 411 description = '

    A request with this method requires a valid Content-Length header.

    ' class PreconditionFailed(HTTPException): """ *412* `Precondition Failed` Status code used in combination with ``If-Match``, ``If-None-Match``, or ``If-Unmodified-Since``. """ code = 412 description = '

    The precondition on the request for the URL failed positive evaluation.

    ' class RequestEntityTooLarge(HTTPException): """ *413* `Request Entity Too Large` The status code one should return if the data submitted exceeded a given limit. """ code = 413 description = '

    The data value transmitted exceeds the capacity limit.

    ' class RequestURITooLarge(HTTPException): """ *414* `Request URI Too Large` Like *413* but for too long URLs. """ code = 414 description = ( '

    The length of the requested URL exceeds the capacity limit ' 'for this server. The request cannot be processed.

    ' ) class UnsupportedMediaType(HTTPException): """ *415* `Unsupported Media Type` The status code returned if the server is unable to handle the media type the client transmitted. """ code = 415 description = '

    The server does not support the media type transmitted in the request.

    ' class RangeUnsatisfiable(HTTPException): """ *416* `Range Unsatisfiable` The status code returned if the server is unable to satisfy the request range """ code = 416 description = '

    The server cannot satisfy the request range(s).

    ' class InternalServerError(HTTPException): """ *500* `Internal Server Error` Raise if an internal server error occurred. This is a good fallback if an unknown error occurred in the dispatcher. """ code = 500 description = ( '

    The server encountered an internal error and was unable to ' 'complete your request. Either the server is overloaded or there ' 'is an error in the application.

    ' ) class NotImplemented(HTTPException): """ *501* `Not Implemented` Raise if the application does not support the action requested by the browser. """ code = 501 description = '

    The server does not support the action requested by the browser.

    ' class BadGateway(HTTPException): """ *502* `Bad Gateway` If you do proxying in your application you should return this status code if you received an invalid response from the upstream server it accessed in attempting to fulfill the request. """ code = 502 description = '

    The proxy server received an invalid response from an upstream server.

    ' class ServiceUnavailable(HTTPException): """ *503* `Service Unavailable` Status code you should return if a service is temporarily unavailable. """ code = 503 description = ( '

    The server is temporarily unable to service your request due to ' 'maintenance downtime or capacity problems. Please try again ' 'later.

    ' ) class Redirect(HTTPException): code = 303 def __init__(self, urls, status=None): super().__init__() if isinstance(urls, str): self.urls = [urls] else: self.urls = urls self.status = status circuits-3.2.3/circuits/web/headers.py000066400000000000000000000226271460335514400177510ustar00rootroot00000000000000""" Headers Support This module implements support for parsing and handling headers. """ import re # Regular expression that matches `special' characters in parameters, the # existence of which force quoting of the parameter value. tspecials = re.compile(r'[ \(\)<>@,;:\\"/\[\]\?=]') q_separator = re.compile(r'; *q *=') def _formatparam(param, value=None, quote=1): """ Convenience function to format and return a key=value pair. This will quote the value if needed or if quote is true. """ if value is not None and len(value) > 0: if quote or tspecials.search(value): value = value.replace('\\', '\\\\').replace('"', r'\"') return f'{param}="{value}"' return f'{param}={value}' return param def header_elements(fieldname, fieldvalue): """ Return a sorted HeaderElement list. Returns a sorted HeaderElement list from a comma-separated header string. """ if not fieldvalue: return [] result = [] for element in fieldvalue.split(','): if fieldname.startswith('Accept') or fieldname == 'TE': hv = AcceptElement.from_str(element) else: hv = HeaderElement.from_str(element) result.append(hv) return sorted(result, reverse=True) class HeaderElement: """An element (with parameters) from an HTTP header's element list.""" def __init__(self, value, params=None): self.value = value if params is None: params = {} self.params = params def __eq__(self, other): return self.value == other.value def __lt__(self, other): return self.value < other.value def __str__(self): p = [f';{k}={v}' for k, v in self.params.items()] return '%s%s' % (self.value, ''.join(p)) def __bytes__(self): return self.__str__().encode('ISO8859-1') def parse(elementstr): """Transform 'token;key=val' to ('token', {'key': 'val'}).""" # Split the element into a value and parameters. The 'value' may # be of the form, "token=token", but we don't split that here. atoms = [x.strip() for x in elementstr.split(';') if x.strip()] initial_value = '' if not atoms else atoms.pop(0).strip() params = {} for atom in atoms: atom = [x.strip() for x in atom.split('=', 1) if x.strip()] key = atom.pop(0) val = atom[0] if atom else '' params[key] = val return initial_value, params parse = staticmethod(parse) @classmethod def from_str(cls, elementstr): """Construct an instance from a string of the form 'token;key=val'.""" ival, params = cls.parse(elementstr) return cls(ival, params) class AcceptElement(HeaderElement): """ An element (with parameters) from an Accept* header's element list. AcceptElement objects are comparable; the more-preferred object will be "less than" the less-preferred object. They are also therefore sortable; if you sort a list of AcceptElement objects, they will be listed in priority order; the most preferred value will be first. Yes, it should have been the other way around, but it's too late to fix now. """ @classmethod def from_str(cls, elementstr): qvalue = None # The first "q" parameter (if any) separates the initial # media-range parameter(s) (if any) from the accept-params. atoms = q_separator.split(elementstr, 1) media_range = atoms.pop(0).strip() if atoms: # The qvalue for an Accept header can have extensions. The other # headers cannot, but it's easier to parse them as if they did. qvalue = HeaderElement.from_str(atoms[0].strip()) media_type, params = cls.parse(media_range) if qvalue is not None: params['q'] = qvalue return cls(media_type, params) def qvalue(self): val = self.params.get('q', '1') if isinstance(val, HeaderElement): val = val.value return float(val) qvalue = property(qvalue, doc='The qvalue, or priority, of this value.') def __eq__(self, other): return self.qvalue == other.qvalue def __lt__(self, other): if self.qvalue == other.qvalue: return str(self) < str(other) return self.qvalue < other.qvalue class CaseInsensitiveDict(dict): """ A case-insensitive dict subclass. Each key is changed on entry to str(key).title(). """ def __init__(self, *args, **kwargs): d = dict(*args, **kwargs) for key, value in d.items(): dict.__setitem__(self, str(key).title(), value) dict.__init__(self) def __getitem__(self, key): return dict.__getitem__(self, str(key).title()) def __setitem__(self, key, value): dict.__setitem__(self, str(key).title(), value) def __delitem__(self, key): dict.__delitem__(self, str(key).title()) def __contains__(self, key): return dict.__contains__(self, str(key).title()) def get(self, key, default=None): return dict.get(self, str(key).title(), default) def update(self, E): for k in E: self[str(k).title()] = E[k] @classmethod def fromkeys(cls, seq, value=None): newdict = cls() for k in seq: newdict[k] = value return newdict def setdefault(self, key, x=None): key = str(key).title() try: return dict.__getitem__(self, key) except KeyError: self[key] = x return x def pop(self, key, default=None): return dict.pop(self, str(key).title(), default) class Headers(CaseInsensitiveDict): """ This class implements a storage for headers as key value pairs. The underlying model of a case insensitive dict matches the requirements for headers quite well, because usually header keys are unique. If several values may be associated with a header key, most HTTP headers represent the values as an enumeration using a comma as item separator. There is, however one exception (currently) to this rule. In order to set several cookies, there should be multiple headers with the same key, each setting one cookie ("Set-Cookie: some_cookie"). This is modeled by having either a string (common case) or a list (cookie case) as value in the underlying dict. In order to allow easy iteration over all headers as they appear in the HTTP request, the items() method expands associated lists of values. So if you have { "Set-Cookie": [ "cookie1", "cookie2" ] }, the items() method returns the two pairs ("Set-Cookie", "cookie1") and ("Set-Cookie", "cookie2"). This is convenient for most use cases. The only drawback is that len(keys()) is not equal to len(items()) for this specialized dict. """ def elements(self, key): """Return a sorted list of HeaderElements for the given header.""" return header_elements(key, self.get(key)) def get_all(self, name): """Return a list of all the values for the named field.""" value = self.get(name, '') if isinstance(value, list): return value return [val.strip() for val in value.split(',')] def __repr__(self): return 'Headers(%s)' % repr(list(self.items())) def __str__(self): headers = [f'{k}: {v}\r\n' for k, v in self.items()] return ''.join(headers) + '\r\n' def items(self): for k, v in super().items(): if isinstance(v, list): for vv in v: yield (str(k), str(vv)) else: yield (str(k), str(v)) def __bytes__(self): return str(self).encode('latin1') def append(self, key, value): """ If a header with the given name already exists, the value is normally appended to the existing value separated by a comma. If, however, the already existing entry associated key with a value of type list (as is the case for "Set-Cookie"), the new value is appended to that list. """ if key not in self: if key.lower() == 'set-cookie': self[key] = [value] else: self[key] = value else: if isinstance(self[key], list): self[key].append(value) else: self[key] = ', '.join([self[key], value]) def add_header(self, _name, _value, **_params): """ Extended header setting. _name is the header field to add. keyword arguments can be used to set additional parameters for the header field, with underscores converted to dashes. Normally the parameter will be added as key="value" unless value is None, in which case only the key will be added. Example: ------- h.add_header('content-disposition', 'attachment', filename='bud.gif') Note that unlike the corresponding 'email.Message' method, this does *not* handle '(charset, language, value)' tuples: all values must be strings or None. """ parts = [] if _value is not None: parts.append(_value) for k, v in list(_params.items()): k = k.replace('_', '-') if v is None: parts.append(k) else: parts.append(_formatparam(k, v)) self.append(_name, '; '.join(parts)) circuits-3.2.3/circuits/web/http.py000066400000000000000000000401311460335514400173030ustar00rootroot00000000000000""" Hyper Text Transfer Protocol This module implements the server side Hyper Text Transfer Protocol or commonly known as HTTP. """ from io import BytesIO from socket import socket from urllib.parse import quote from circuits.core import BaseComponent, Value, handler from circuits.net.events import close, write from circuits.net.utils import is_ssl_handshake from . import wrappers from .constants import SERVER_PROTOCOL, SERVER_VERSION from .errors import httperror, notfound, redirect from .events import request, response, stream from .exceptions import HTTPException, Redirect as RedirectException from .parsers import BAD_FIRST_LINE, HttpParser from .url import parse_url from .utils import is_unix_socket HTTP_ENCODING = 'utf-8' class HTTP(BaseComponent): """ HTTP Protocol Component Implements the HTTP server protocol and parses and processes incoming HTTP messages, creating and sending an appropriate response. The component handles :class:`~circuits.net.sockets.Read` events on its channel and collects the associated data until a complete HTTP request has been received. It parses the request's content and puts it in a :class:`~circuits.web.wrappers.Request` object and creates a corresponding :class:`~circuits.web.wrappers.Response` object. Then it emits a :class:`~circuits.web.events.Request` event with these objects as arguments. The component defines several handlers that send a response back to the client. """ channel = 'web' def __init__(self, server, encoding=HTTP_ENCODING, channel=channel): super().__init__(channel=channel) self._server = server self._encoding = encoding self._uri = None self._clients = {} self._buffers = {} @property def version(self): return SERVER_VERSION @property def protocol(self): return SERVER_PROTOCOL @property def scheme(self): return 'https' if self._server.secure else 'http' @property def base(self): if self.uri is None: return '' return self.uri.utf8().rstrip(b'/').decode(self._encoding) @property def uri(self): return self._uri @handler('ready', priority=1.0) def _on_ready(self, server, bind): if is_unix_socket(server.host): url = server.host else: url = '{}://{}{}'.format( (server.secure and 'https') or 'http', server.host or '0.0.0.0', f':{server.port or 80:d}' if server.port not in (80, 443) else '', ) self._uri = parse_url(url) @handler('stream') # noqa def _on_stream(self, res, data): sock = res.request.sock if data is not None: if isinstance(data, str): data = data.encode(self._encoding) if res.chunked: buf = [ hex(len(data))[2:].encode(self._encoding), b'\r\n', data, b'\r\n', ] data = b''.join(buf) self.fire(write(sock, data)) if res.body and not res.done: try: data = next(res.body) while not data: # Skip over any null byte sequences data = next(res.body) except StopIteration: data = None self.fire(stream(res, data)) else: if res.body: res.body.close() if res.chunked: self.fire(write(sock, b'0\r\n\r\n')) if res.close: self.fire(close(sock)) if sock in self._clients: del self._clients[sock] res.done = True @handler('response') # noqa def _on_response(self, res): """ ``Response`` Event Handler :param response: the ``Response`` object created when the HTTP request was initially received. :type response: :class:`~circuits.web.wrappers.Response` This handler builds an HTTP response data stream from the information contained in the *response* object and sends it to the client (firing ``write`` events). """ req = res.request headers = res.headers sock = req.sock # send HTTP response status line and headers res.prepare() self.fire(write(sock, b'%s%s' % (bytes(res), bytes(headers)))) if req.method == 'HEAD': return if res.stream and res.body: try: data = next(res.body) except StopIteration: data = None self.fire(stream(res, data)) else: if isinstance(res.body, bytes): body = res.body elif isinstance(res.body, str): body = res.body.encode(self._encoding) else: parts = (s if isinstance(s, bytes) else s.encode(self._encoding) for s in res.body if s is not None) body = b''.join(parts) if body: if res.chunked: buf = [ hex(len(body))[2:].encode(self._encoding), b'\r\n', body, b'\r\n', ] body = b''.join(buf) self.fire(write(sock, body)) if res.chunked: self.fire(write(sock, b'0\r\n\r\n')) if not res.stream: if res.close: self.fire(close(sock)) # Delete the request/response objects if present if sock in self._clients: del self._clients[sock] res.done = True @handler('disconnect') def _on_disconnect(self, sock): if sock in self._clients: del self._clients[sock] @handler('read') # noqa def _on_read(self, sock, data): """ Read Event Handler Process any incoming data appending it to an internal buffer. Split the buffer by the standard HTTP delimiter CRLF and create Raw Event per line. Any unfinished lines of text, leave in the buffer. """ if sock in self._buffers: parser = self._buffers[sock] else: self._buffers[sock] = parser = HttpParser(0, True) # If we receive an SSL handshake at the start of a request # and we're not a secure server, then immediately close the # client connection since we can't respond to it anyway. if is_ssl_handshake(data) and not self._server.secure: if sock in self._buffers: del self._buffers[sock] if sock in self._clients: del self._clients[sock] return self.fire(close(sock)) _scheme = 'https' if self._server.secure else 'http' parser.execute(data, len(data)) if not parser.is_headers_complete(): if parser.errno is not None: if parser.errno == BAD_FIRST_LINE: req = wrappers.Request(sock, server=self._server) else: req = wrappers.Request( sock, parser.get_method(), parser.get_scheme() or _scheme, parser.get_path(), parser.get_version(), parser.get_query_string(), server=self._server, ) req.server = self._server res = wrappers.Response(req, encoding=self._encoding) del self._buffers[sock] return self.fire(httperror(req, res, 400)) return None if sock in self._clients: req, res = self._clients[sock] else: method = parser.get_method() scheme = parser.get_scheme() or _scheme path = parser.get_path() version = parser.get_version() query_string = parser.get_query_string() req = wrappers.Request( sock, method, scheme, path, version, query_string, headers=parser.get_headers(), server=self._server, ) res = wrappers.Response(req, encoding=self._encoding) self._clients[sock] = (req, res) rp = req.protocol sp = self.protocol if rp[0] != sp[0]: # the major HTTP version differs return self.fire(httperror(req, res, 505)) res.protocol = 'HTTP/{:d}.{:d}'.format(*min(rp, sp)) res.close = not parser.should_keep_alive() clen = int(req.headers.get('Content-Length', '0')) if (clen or req.headers.get('Transfer-Encoding') == 'chunked') and not parser.is_message_complete(): return None if hasattr(sock, 'getpeercert'): peer_cert = sock.getpeercert() e = request(req, res, peer_cert) if peer_cert else request(req, res) else: e = request(req, res) if req.protocol != (1, 0) and not req.headers.get('Host'): del self._buffers[sock] return self.fire(httperror(req, res, 400, description='No host header defined')) # Guard against unwanted request paths (SECURITY). path = req.path _path = req.uri._path if (path.encode(self._encoding) != _path) and (quote(path).encode(self._encoding) != _path): return self.fire(redirect(req, res, [req.uri.utf8()], 301)) req.body = BytesIO(parser.recv_body()) del self._buffers[sock] self.fire(e) return None @handler('httperror') def _on_httperror(self, event, req, res, code, **kwargs): """ Default HTTP Error Handler Default Error Handler that by default just fires a ``Response`` event with the *response* as argument. The *response* is normally modified by a :class:`~circuits.web.errors.HTTPError` instance or a subclass thereof. """ res.body = str(event) self.fire(response(res)) @handler('request_success') # noqa def _on_request_success(self, e, value): """ Handler for the ``RequestSuccess`` event that is automatically generated after all handlers for a :class:`~circuits.web.events.Request` event have been invoked successfully. :param e: the successfully handled ``Request`` event (having as attributes the associated :class:`~circuits.web.wrappers.Request` and :class:`~circuits.web.wrappers.Response` objects). :param value: the value(s) returned by the invoked handler(s). This handler converts the value(s) returned by the (successfully invoked) handlers for the initial ``Request`` event to a body and assigns it to the ``Response`` object's ``body`` attribute. It then fires a :class:`~circuits.web.events.Response` event with the ``Response`` object as argument. """ # We only want the non-recursive value at this point. # If the value is an instance of Value we will set # the .notify flag and be notified of changes to the value. value = e.value.getValue(recursive=False) if isinstance(value, Value) and not value.promise: value = value.getValue(recursive=False) req, res = e.args[:2] if value is None: self.fire(notfound(req, res)) elif isinstance(value, httperror): res.body = str(value) self.fire(response(res)) elif isinstance(value, wrappers.Response): self.fire(response(value)) elif isinstance(value, Value): if value.result and not value.errors: res.body = value.value self.fire(response(res)) elif value.errors: error = value.value _etype, evalue, _traceback = error if isinstance(evalue, RedirectException): self.fire(redirect(req, res, evalue.urls, evalue.code)) elif isinstance(evalue, HTTPException): if evalue.traceback: self.fire(httperror(req, res, evalue.code, description=evalue.description, error=error)) else: self.fire(httperror(req, res, evalue.code, description=evalue.description)) else: self.fire(httperror(req, res, error=error)) else: # We want to be notified of changes to the value value = e.value.getValue(recursive=False) value.event = e value.notify = True elif isinstance(value, tuple): _etype, evalue, _traceback = error = value if isinstance(evalue, RedirectException): self.fire(redirect(req, res, evalue.urls, evalue.code)) elif isinstance(evalue, HTTPException): if evalue.traceback: self.fire(httperror(req, res, evalue.code, description=evalue.description, error=error)) else: self.fire(httperror(req, res, evalue.code, description=evalue.description)) else: self.fire(httperror(req, res, error=error)) elif not isinstance(value, bool): res.body = value self.fire(response(res)) @handler('exception') def _on_exception(self, *args, **kwargs): if len(args) != 3: return etype, evalue, etraceback = args fevent = kwargs['fevent'] if isinstance(fevent, response): res = fevent.args[0] req = res.request elif isinstance(fevent.value.parent.event, request): req, res = fevent.value.parent.event.args[:2] elif len(fevent.args[2:]) == 4: req, res = fevent.args[2:] elif len(fevent.args) == 2 and isinstance(fevent.args[0], socket): req = wrappers.Request(fevent.args[0], server=self._server) res = wrappers.Response(req, self._encoding, 500) else: return code = evalue.code if isinstance(evalue, HTTPException) else None self.fire(httperror(req, res, code=code, error=(etype, evalue, etraceback))) @handler('request_failure') def _on_request_failure(self, erequest, error): req, res = erequest.args # Ignore filtered requests already handled (eg: HTTPException(s)). if req.handled: return req.handled = True _etype, evalue, _traceback = error if isinstance(evalue, RedirectException): self.fire(redirect(req, res, evalue.urls, evalue.code)) elif isinstance(evalue, HTTPException): self.fire(httperror(req, res, evalue.code, description=evalue.description, error=error)) else: self.fire(httperror(req, res, error=error)) @handler('response_failure') def _on_response_failure(self, eresponse, error): res = eresponse.args[0] req = res.request # Ignore failed "response" handlers (eg: Loggers or Tools) if res.done: return res = wrappers.Response(req, self._encoding, 500) self.fire(httperror(req, res, error=error)) @handler('request_complete') def _on_request_complete(self, *args, **kwargs): """ Dummy Event Handler for request events - request_complete """ @handler('response_success', 'response_complete') def _on_response_feedback(self, *args, **kwargs): """ Dummy Event Handler for response events - response_success - response_complete """ @handler('stream_success', 'stream_failure', 'stream_complete') def _on_stream_feedback(self, *args, **kwargs): """ Dummy Event Handler for stream events - stream_success - stream_failure - stream_complete """ circuits-3.2.3/circuits/web/loggers.py000066400000000000000000000044071460335514400177740ustar00rootroot00000000000000""" Logger Component This module implements Logger Components. """ import datetime import os import sys from io import IOBase from circuits.core import BaseComponent, handler def formattime(): return datetime.datetime.now().strftime('[%d/%b/%Y:%H:%M:%S]') class Logger(BaseComponent): channel = 'web' format = '%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s"' def __init__(self, file=None, logger=None, **kwargs): super().__init__(**kwargs) if isinstance(file, str): self.file = open(os.path.abspath(os.path.expanduser(file)), 'a') elif isinstance(file, IOBase) or hasattr(file, 'write'): self.file = file else: self.file = sys.stdout self.logger = logger @handler('response_success') def log_response(self, response_event, value): response = response_event.args[0] self.log(response) def log(self, response): request = response.request remote = request.remote outheaders = response.headers inheaders = request.headers or {} protocol = 'HTTP/%d.%d' % request.protocol host = inheaders.get('X-Forwarded-For', (remote.name or remote.ip)) atoms = { 'h': host, 'l': '-', 'u': getattr(request, 'login', None) or '-', 't': formattime(), 'r': f'{request.method} {request.path} {protocol}', 's': int(response.status), 'b': outheaders.get('Content-Length', '') or '-', 'f': inheaders.get('Referer', ''), 'a': inheaders.get('User-Agent', ''), } for k, v in list(atoms.items()): if isinstance(v, str): v = v.encode('utf8') elif not isinstance(v, str): v = str(v) # Fortunately, repr(str) escapes unprintable chars, \n, \t, etc # and backslash for us. All we have to do is strip the quotes. v = repr(v)[1:-1] # Escape double-quote. atoms[k] = v.replace('"', '\\"') if self.logger is not None: self.logger.info(self.format % atoms) else: self.file.write(self.format % atoms) self.file.write('\n') self.file.flush() circuits-3.2.3/circuits/web/main.py000077500000000000000000000130311460335514400172520ustar00rootroot00000000000000#!/usr/bin/env python """ Main circutis.web Web Server and Testing Tool. """ import os from argparse import ArgumentParser from hashlib import md5 from sys import stderr from wsgiref.simple_server import make_server from wsgiref.validate import validator import circuits from circuits import Component, Debugger, Manager, handler from circuits.core.pollers import Select from circuits.tools import graph, inspect from circuits.web import BaseServer, Controller, Logger, Server, Static from circuits.web.tools import check_auth, digest_auth from circuits.web.wsgi import Application try: import hotshot import hotshot.stats except ImportError: hostshot = None try: from circuits.core.pollers import Poll except ImportError: Poll = None # NOQA try: from circuits.core.pollers import EPoll except ImportError: EPoll = None # NOQA def parse_options(): parser = ArgumentParser() parser.add_argument( '-b', '--bind', action='store', default='0.0.0.0:8000', help='Bind to address:[port]', ) parser.add_argument( '-l', '--logging', action='store_true', default=False, help='Enable logging of requests', ) parser.add_argument( '-p', '--passwd', action='store', default=None, help='Location to passwd file for Digest Auth', ) parser.add_argument( '-j', '--jobs', action='store', type=int, default=0, help='Specify number of jobs/processes to start', ) parser.add_argument( '--poller', action='store', default='select', help='Specify type of poller to use', ) parser.add_argument( '--server', action='store', default='server', help='Specify server to use', ) parser.add_argument( '--profile', action='store_true', default=False, help='Enable execution profiling support', ) parser.add_argument( '--debug', action='store_true', default=False, help='Enable debug mode', ) parser.add_argument( '--validate', action='store_true', default=False, help='Enable WSGI validation mode', ) parser.add_argument('-v', '--version', action='version', version=f'%(prog)s v{circuits.__version__}') parser.add_argument('docroot', nargs='?', default=os.getcwd()) return parser.parse_args() class Authentication(Component): channel = 'web' realm = 'Secure Area' users = {'admin': md5(b'admin').hexdigest()} def __init__(self, channel=channel, realm=None, passwd=None): super().__init__(self, channel=channel) if realm is not None: self.realm = realm if passwd is not None: with open(passwd) as f: lines = (line.strip() for line in f) self.users = dict(line.split(':', 1) for line in lines) @handler('request', priority=10) def request(self, event, request, response): if not check_auth(request, response, self.realm, self.users): event.stop() return digest_auth(request, response, self.realm, self.users) return None class HelloWorld(Component): channel = 'web' def request(self, request, response): return 'Hello World!' class Root(Controller): def hello(self): return 'Hello World!' def select_poller(poller): if poller == 'poll': if Poll is None: stderr.write( 'No poll support available - defaulting to Select...', ) Poller = Select else: Poller = Poll elif poller == 'epoll': if EPoll is None: stderr.write( 'No epoll support available - defaulting to Select...', ) Poller = Select else: Poller = EPoll else: Poller = Select return Poller def parse_bind(bind): if ':' in bind: address, port = bind.split(':') port = int(port) else: address, port = bind, 8000 return (address, port) def main(): opts = parse_options() bind = parse_bind(opts.bind) if opts.validate: application = Application() + Root() app = validator(application) httpd = make_server(bind[0], bind[1], app) httpd.serve_forever() raise SystemExit(0) manager = Manager() opts.debug and Debugger().register(manager) Poller = select_poller(opts.poller.lower()) Poller().register(manager) if opts.server.lower() == 'base': BaseServer(bind).register(manager) HelloWorld().register(manager) else: Server(bind).register(manager) Root().register(manager) Static(docroot=opts.docroot, dirlisting=True).register(manager) opts.passwd and Authentication(passwd=opts.passwd).register(manager) opts.logging and Logger().register(manager) if opts.profile and hotshot: profiler = hotshot.Profile('.profile') profiler.start() if opts.debug: print(graph(manager, name='circuits.web')) print() print(inspect(manager)) for _i in range(opts.jobs): manager.start(process=True) manager.run() if opts.profile and hotshot: profiler.stop() profiler.close() stats = hotshot.stats.load('.profile') stats.strip_dirs() stats.sort_stats('time', 'calls') stats.print_stats(20) if __name__ == '__main__': main() circuits-3.2.3/circuits/web/parsers/000077500000000000000000000000001460335514400174325ustar00rootroot00000000000000circuits-3.2.3/circuits/web/parsers/__init__.py000066400000000000000000000002751460335514400215470ustar00rootroot00000000000000"""circuits.web parsers""" from .http import BAD_FIRST_LINE, HttpParser from .multipart import MultipartParser from .querystring import QueryStringParser # flake8: noqa # pylama: skip=1 circuits-3.2.3/circuits/web/parsers/http.py000066400000000000000000000343721460335514400207740ustar00rootroot00000000000000# # This file is part of http-parser released under the MIT license. # See the NOTICE for more information. # # This module is liberally borrowed (with modifications) from: # https://raw.githubusercontent.com/benoitc/http-parser/master/http_parser/pyparser.py import contextlib import re import zlib from sys import maxsize from urllib.parse import urlsplit from circuits.web.headers import Headers METHOD_RE = re.compile('^[A-Z0-9$-_.]{1,20}$') VERSION_RE = re.compile(r'^HTTP/(\d+).(\d+)$') STATUS_RE = re.compile(r'^(\d{3})(?:\s+([\s\w]*))$') HEADER_RE = re.compile('[\\x00-\\x1F\\x7F()<>@,;:/\\[\\]={} \\t\\\\"]') # errors BAD_FIRST_LINE = 0 INVALID_HEADER = 1 INVALID_CHUNK = 2 class InvalidRequestLine(Exception): """error raised when first line is invalid""" class InvalidHeader(Exception): """error raised on invalid header""" class InvalidChunkSize(Exception): """error raised when we parse an invalid chunk size""" class HttpParser: def __init__(self, kind=2, decompress=False): self.kind = kind self.decompress = decompress # errors vars self.errno = None self.errstr = '' # protected variables self._buf = [] self._version = None self._method = None self._status_code = None self._status = None self._reason = None self._url = None self._path = None self._query_string = None self._headers = Headers([]) self._environ = {} self._chunked = False self._body = [] self._trailers = None self._partial_body = False self._clen = None self._clen_rest = None # private events self.__on_firstline = False self.__on_headers_complete = False self.__on_message_begin = False self.__on_message_complete = False self.__decompress_obj = None self.__decompress_first_try = True def get_version(self): return self._version def get_method(self): return self._method def get_status_code(self): return self._status_code def get_url(self): return self._url def get_scheme(self): return self._scheme def get_path(self): return self._path def get_query_string(self): return self._query_string def get_headers(self): return self._headers def recv_body(self): """Return last chunk of the parsed body""" body = b''.join(self._body) self._body = [] self._partial_body = False return body def recv_body_into(self, barray): """ Receive the last chunk of the parsed body and store the data in a buffer rather than creating a new string. """ length = len(barray) body = b''.join(self._body) m = min(len(body), length) data, rest = body[:m], body[m:] barray[0:m] = data if not rest: self._body = [] self._partial_body = False else: self._body = [rest] return m def is_upgrade(self): """ Do we get upgrade header in the request. Useful for websockets """ hconn = self._headers.get('connection', '').lower() hconn_parts = [x.strip() for x in hconn.split(',')] return 'upgrade' in hconn_parts def is_headers_complete(self): """Return True if all headers have been parsed.""" return self.__on_headers_complete def is_partial_body(self): """Return True if a chunk of body have been parsed""" return self._partial_body def is_message_begin(self): """Return True if the parsing start""" return self.__on_message_begin def is_message_complete(self): """Return True if the parsing is done (we get EOF)""" return self.__on_message_complete def is_chunked(self): """Return True if Transfer-Encoding header value is chunked""" return self._chunked def should_keep_alive(self): """Return True if the connection should be kept alive""" hconn = self._headers.get('connection', '').lower() if hconn == 'close': return False if hconn == 'keep-alive': return True return self._version == (1, 1) def execute(self, data, length): # end of body can be passed manually by putting a length of 0 if length == 0: self.__on_message_complete = True return length # start to parse nb_parsed = 0 while True: if not self.__on_firstline: idx = data.find(b'\r\n') if idx < 0: self._buf.append(data) return len(data) self.__on_firstline = True self._buf.append(data[:idx]) first_line = b''.join(self._buf) first_line = str(first_line, 'unicode_escape') nb_parsed = nb_parsed + idx + 2 rest = data[idx + 2 :] data = b'' if self._parse_firstline(first_line): self._buf = [rest] else: return nb_parsed elif not self.__on_headers_complete: if data: self._buf.append(data) data = b'' try: to_parse = b''.join(self._buf) ret = self._parse_headers(to_parse) if ret is False: return length nb_parsed = nb_parsed + (len(to_parse) - ret) except InvalidHeader as e: self.errno = INVALID_HEADER self.errstr = str(e) return nb_parsed elif not self.__on_message_complete: if not self.__on_message_begin: self.__on_message_begin = True if data: self._buf.append(data) data = b'' ret = self._parse_body() if ret is None: return length if ret < 0: return ret if ret == 0: self.__on_message_complete = True return length nb_parsed = max(length, ret) else: return 0 def _parse_firstline(self, line): try: if self.kind == 2: # auto detect try: self._parse_request_line(line) except InvalidRequestLine: self._parse_response_line(line) elif self.kind == 1: self._parse_response_line(line) elif self.kind == 0: self._parse_request_line(line) except InvalidRequestLine as e: self.errno = BAD_FIRST_LINE self.errstr = str(e) return False return True def _parse_response_line(self, line): bits = line.split(None, 1) if len(bits) != 2: raise InvalidRequestLine(line) # version matchv = VERSION_RE.match(bits[0]) if matchv is None: raise InvalidRequestLine('Invalid HTTP version: %s' % bits[0]) self._version = (int(matchv.group(1)), int(matchv.group(2))) # status matchs = STATUS_RE.match(bits[1]) if matchs is None: raise InvalidRequestLine(f'Invalid status: {bits[1]!r}') self._status = bits[1] self._status_code = int(matchs.group(1)) self._reason = matchs.group(2) def _parse_request_line(self, line): bits = line.split(None, 2) if len(bits) != 3: raise InvalidRequestLine(line) # Method if not METHOD_RE.match(bits[0]): raise InvalidRequestLine('invalid Method: %s' % bits[0]) self._method = bits[0].upper() # URI self._url = bits[1] parts = urlsplit(bits[1]) self._scheme = parts.scheme or None self._path = parts.path or '' self._query_string = parts.query or '' if parts.fragment: raise InvalidRequestLine( 'HTTP requests may not contain fragment(s)', ) # Version match = VERSION_RE.match(bits[2]) if match is None: raise InvalidRequestLine('Invalid HTTP version: %s' % bits[2]) self._version = (int(match.group(1)), int(match.group(2))) # update environ if hasattr(self, '_environ'): self._environ.update( { 'PATH_INFO': self._path, 'QUERY_STRING': self._query_string, 'RAW_URI': self._url, 'REQUEST_METHOD': self._method, 'SERVER_PROTOCOL': bits[2], } ) def _parse_headers(self, data): if data == b'\r\n': self.__on_headers_complete = True self._buf = [] return 0 idx = data.find(b'\r\n\r\n') if idx < 0: # we don't have all headers if self._status_code == 204 and data == b'\r\n': self._buf = [] self.__on_headers_complete = True return 0 return False # Split lines on \r\n keeping the \r\n on each line lines = [str(line, 'unicode_escape') + '\r\n' for line in data[:idx].split(b'\r\n')] # Parse headers into key/value pairs paying attention # to continuation lines. while len(lines): # Parse initial header name : value pair. curr = lines.pop(0) if curr.find(':') < 0: raise InvalidHeader('invalid line %s' % curr.strip()) name, value = curr.split(':', 1) name = name.rstrip(' \t').upper() if HEADER_RE.search(name): raise InvalidHeader('invalid header name %s' % name) if value.endswith('\r\n'): value = value[:-2] name, value = name.strip(), [value.lstrip()] # Consume value continuation lines while len(lines) and lines[0].startswith((' ', '\t')): curr = lines.pop(0) if curr.endswith('\r\n'): curr = curr[:-2] value.append(curr) value = ''.join(value).rstrip() # store new header value self._headers.add_header(name, value) # update WSGI environ key = 'HTTP_%s' % name.upper().replace('-', '_') self._environ[key] = value # detect now if body is sent by chunks. clen = self._headers.get('content-length') te = self._headers.get('transfer-encoding', '').lower() if clen is not None: with contextlib.suppress(ValueError): self._clen_rest = self._clen = int(clen) else: self._chunked = te == 'chunked' if not self._chunked: self._clen_rest = maxsize # detect encoding and set decompress object encoding = self._headers.get('content-encoding') if self.decompress: if encoding == 'gzip': self.__decompress_obj = zlib.decompressobj(16 + zlib.MAX_WBITS) self.__decompress_first_try = False elif encoding == 'deflate': self.__decompress_obj = zlib.decompressobj() rest = data[idx + 4 :] self._buf = [rest] self.__on_headers_complete = True return len(rest) def _parse_body(self): if self._status_code == 204 and len(self._buf) == 0: self.__on_message_complete = True return None if not self._chunked: body_part = b''.join(self._buf) if not body_part and self._clen is None: if not self._status: # message complete only for servers self.__on_message_complete = True return None self._clen_rest -= len(body_part) # maybe decompress if self.__decompress_obj is not None: if not self.__decompress_first_try: body_part = self.__decompress_obj.decompress(body_part) else: try: body_part = self.__decompress_obj.decompress(body_part) except zlib.error: self.__decompress_obj.decompressobj = zlib.decompressobj(-zlib.MAX_WBITS) body_part = self.__decompress_obj.decompress(body_part) self.__decompress_first_try = False self._partial_body = True self._body.append(body_part) self._buf = [] if self._clen_rest <= 0: self.__on_message_complete = True return None data = b''.join(self._buf) try: size, rest = self._parse_chunk_size(data) except InvalidChunkSize as e: self.errno = INVALID_CHUNK self.errstr = 'invalid chunk size [%s]' % str(e) return -1 if size == 0: return size if size is None or len(rest) < size: return None body_part, rest = rest[:size], rest[size:] if len(rest) < 2: self.errno = INVALID_CHUNK self.errstr = 'chunk missing terminator [%s]' % data return -1 # maybe decompress if self.__decompress_obj is not None: body_part = self.__decompress_obj.decompress(body_part) self._partial_body = True self._body.append(body_part) self._buf = [rest[2:]] return len(rest) def _parse_chunk_size(self, data): idx = data.find(b'\r\n') if idx < 0: return None, None line, rest_chunk = data[:idx], data[idx + 2 :] chunk_size = line.split(b';', 1)[0].strip() try: chunk_size = int(chunk_size, 16) except ValueError: raise InvalidChunkSize(chunk_size) if chunk_size == 0: self._parse_trailers(rest_chunk) return 0, None return chunk_size, rest_chunk def _parse_trailers(self, data): idx = data.find(b'\r\n\r\n') if data[:2] == b'\r\n': self._trailers = self._parse_headers(data[:idx]) circuits-3.2.3/circuits/web/parsers/multipart.py000066400000000000000000000421111460335514400220240ustar00rootroot00000000000000''' Parser for multipart/form-data ============================== This module provides a parser for the multipart/form-data format. It can read from a file, a socket or a WSGI environment. The parser can be used to replace cgi.FieldStorage (without the bugs) and works with Python 3.x. Licence (MIT) ------------- Copyright (c) 2010, Marcel Hellkamp. Inspired by the Werkzeug library: http://werkzeug.pocoo.org/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ''' __author__ = 'Marcel Hellkamp' __version__ = '0.1' __license__ = 'MIT' import re from tempfile import TemporaryFile from wsgiref.headers import Headers from urllib.parse import parse_qs from io import BytesIO ############################################################################## ################################ Helper & Misc ################################ ############################################################################## # Some of these were copied from bottle: http://bottle.paws.de/ from collections.abc import MutableMapping class MultiDict(MutableMapping): """ A dict that remembers old values for each key """ def __init__(self, *a, **k): self.dict = {} for k, v in dict(*a, **k).iteritems(): self[k] = v def __len__(self): return len(self.dict) def __iter__(self): return iter(self.dict) def __contains__(self, key): return key in self.dict def __delitem__(self, key): del self.dict[key] def keys(self): return self.dict.keys() def __getitem__(self, key): return self.get(key, KeyError, -1) def __setitem__(self, key, value): self.append(key, value) def append(self, key, value): self.dict.setdefault(key, []).append(value) def replace(self, key, value): self.dict[key] = [value] def getall(self, key): return self.dict.get(key) or [] def get(self, key, default=None, index=-1): if key not in self.dict and default != KeyError: return [default][index] return self.dict[key][index] def iterallitems(self): for key, values in self.dict.iteritems(): for value in values: yield key, value def tob(data, enc='utf8'): # Convert strings to bytes (py2 and py3) return data.encode(enc) if isinstance(data, str) else data def copy_file(stream, target, maxread=-1, buffer_size=2 * 16): ''' Read from :stream and write to :target until :maxread or EOF. ''' size, read = 0, stream.read while 1: to_read = buffer_size if maxread < 0 else min( buffer_size, maxread - size) part = read(to_read) if not part: return size target.write(part) size += len(part) ############################################################################## ################################ Header Parser ################################ ############################################################################## _special = re.escape('()<>@,;:\\"/[]?={} \t') _re_special = re.compile('[%s]' % _special) _qstr = '"(?:\\\\.|[^"])*"' # Quoted string _value = f'(?:[^{_special}]+|{_qstr})' # Save or quoted string _option = fr'(?:;|^)\s*([^{_special}]+)\s*=\s*({_value})' # key=value part of an Content-Type like header _re_option = re.compile(_option) def header_quote(val): if not _re_special.search(val): return val return '"' + val.replace('\\', '\\\\').replace('"', '\\"') + '"' def header_unquote(val, filename=False): if val[0] == val[-1] == '"': val = val[1:-1] if val[1:3] == ':\\' or val[:2] == '\\\\': val = val.split('\\')[-1] # fix ie6 bug: full path --> filename return val.replace('\\\\', '\\').replace('\\"', '"') return val def parse_options_header(header, options=None): if ';' not in header: return header.lower().strip(), {} ctype, tail = header.split(';', 1) options = options or {} for match in _re_option.finditer(tail): key = match.group(1).lower() value = header_unquote(match.group(2), key == 'filename') options[key] = value return ctype, options ############################################################################## ################################## Multipart ################################## ############################################################################## class MultipartError(ValueError): pass class MultipartParser: def __init__(self, stream, boundary, content_length=-1, disk_limit=2 ** 30, mem_limit=2 ** 20, memfile_limit=2 ** 18, buffer_size=2 ** 16, charset='latin1'): ''' Parse a multipart/form-data byte stream. This object is an iterator over the parts of the message. :param stream: A file-like stream. Must implement ``.read(size)``. :param boundary: The multipart boundary as a byte string. :param content_length: The maximum number of bytes to read. ''' self.stream, self.boundary = stream, boundary self.content_length = content_length self.disk_limit = disk_limit self.memfile_limit = memfile_limit self.mem_limit = min(mem_limit, self.disk_limit) self.buffer_size = min(buffer_size, self.mem_limit) self.charset = charset if self.buffer_size - 6 < len(boundary): # "--boundary--\r\n" raise MultipartError('Boundary does not fit into buffer_size.') self._done = [] self._part_iter = None def __iter__(self): ''' Iterate over the parts of the multipart message. ''' if not self._part_iter: self._part_iter = self._iterparse() for part in self._done: yield part for part in self._part_iter: self._done.append(part) yield part def parts(self): ''' Returns a list with all parts of the multipart message. ''' return list(iter(self)) def get(self, name, default=None): ''' Return the first part with that name or a default value (None). ''' for part in self: if name == part.name: return part return default def get_all(self, name): ''' Return a list of parts with that name. ''' return [p for p in self if p.name == name] def _lineiter(self): ''' Iterate over a binary file-like object line by line. Each line is returned as a (line, line_ending) tuple. If the line does not fit into self.buffer_size, line_ending is empty and the rest of the line is returned with the next iteration. ''' read = self.stream.read maxread, maxbuf = self.content_length, self.buffer_size _bcrnl = tob('\r\n') _bcr = _bcrnl[:1] _bnl = _bcrnl[1:] _bempty = _bcrnl[:0] # b'rn'[:0] -> b'' buffer = _bempty # buffer for the last (partial) line while 1: data = read(maxbuf if maxread < 0 else min(maxbuf, maxread)) maxread -= len(data) lines = (buffer + data).splitlines(True) len_first_line = len(lines[0]) # be sure that the first line does not become too big if len_first_line > self.buffer_size: # at the same time don't split a '\r\n' accidentally if (len_first_line == self.buffer_size + 1 and lines[0].endswith(_bcrnl)): splitpos = self.buffer_size - 1 else: splitpos = self.buffer_size lines[:1] = [lines[0][:splitpos], lines[0][splitpos:]] if data: buffer = lines[-1] lines = lines[:-1] for line in lines: if line.endswith(_bcrnl): yield line[:-2], _bcrnl elif line.endswith(_bnl): yield line[:-1], _bnl elif line.endswith(_bcr): yield line[:-1], _bcr else: yield line, _bempty if not data: break def _iterparse(self): lines, line = self._lineiter(), '' separator = tob('--') + tob(self.boundary) terminator = tob('--') + tob(self.boundary) + tob('--') # Consume first boundary. Ignore leading blank lines for line, _nl in lines: if line: break if line != separator: raise MultipartError("Stream does not start with boundary") # For each part in stream... mem_used, disk_used = 0, 0 # Track used resources to prevent DoS is_tail = False # True if the last line was incomplete (cutted) opts = {'buffer_size': self.buffer_size, 'memfile_limit': self.memfile_limit, 'charset': self.charset} part = MultipartPart(**opts) for line, nl in lines: if line == terminator and not is_tail: part.file.seek(0) yield part break elif line == separator and not is_tail: if part.is_buffered(): mem_used += part.size else: disk_used += part.size part.file.seek(0) yield part part = MultipartPart(**opts) else: is_tail = not nl # The next line continues this one part.feed(line, nl) if part.is_buffered(): if part.size + mem_used > self.mem_limit: raise MultipartError("Memory limit reached.") elif part.size + disk_used > self.disk_limit: raise MultipartError("Disk limit reached.") if line != terminator: raise MultipartError("Unexpected end of multipart stream.") class MultipartPart: def __init__(self, buffer_size=2 ** 16, memfile_limit=2 ** 18, charset='latin1'): self.headerlist = [] self.headers = None self.file = False self.size = 0 self._buf = tob('') self.disposition, self.name, self.filename = None, None, None self.content_type, self.charset = None, charset self.memfile_limit = memfile_limit self.buffer_size = buffer_size def feed(self, line, nl=''): if self.file: return self.write_body(line, nl) return self.write_header(line, nl) def write_header(self, line, nl): line = line.decode(self.charset or 'latin1') if not nl: raise MultipartError('Unexpected end of line in header.') if not line.strip(): # blank line -> end of header segment self.finish_header() elif line[0] in ' \t' and self.headerlist: name, value = self.headerlist.pop() self.headerlist.append((name, value + line.strip())) else: if ':' not in line: raise MultipartError("Syntax error in header: No colon.") name, value = line.split(':', 1) self.headerlist.append((name.strip(), value.strip())) def write_body(self, line, nl): if not line and not nl: return # This does not even flush the buffer self.size += len(line) + len(self._buf) self.file.write(self._buf + line) self._buf = nl if self.content_length > 0 and self.size > self.content_length: raise MultipartError('Size of body exceeds Content-Length header.') if self.size > self.memfile_limit and isinstance(self.file, BytesIO): # TODO: What about non-file uploads that exceed the memfile_limit? self.file, old = TemporaryFile(mode='w+b'), self.file old.seek(0) copy_file(old, self.file, self.size, self.buffer_size) def finish_header(self): self.file = BytesIO() self.headers = Headers(self.headerlist) cdis = self.headers.get('Content-Disposition', '') ctype = self.headers.get('Content-Type', '') self.headers.get('Content-Length', '-1') if not cdis: raise MultipartError('Content-Disposition header is missing.') self.disposition, self.options = parse_options_header(cdis) self.name = self.options.get('name') self.filename = self.options.get('filename') self.content_type, options = parse_options_header(ctype) self.charset = options.get('charset') or self.charset self.content_length = int(self.headers.get('Content-Length', '-1')) def is_buffered(self): ''' Return true if the data is fully buffered in memory.''' return isinstance(self.file, BytesIO) @property def value(self): ''' Data decoded with the specified charset ''' pos = self.file.tell() self.file.seek(0) val = self.file.read() self.file.seek(pos) return val.decode(self.charset) def save_as(self, path): fp = open(path, 'wb') pos = self.file.tell() try: self.file.seek(0) size = copy_file(self.file, fp) finally: self.file.seek(pos) return size ############################################################################## #################################### WSGI #################################### ############################################################################## def parse_form_data(environ, charset='utf8', strict=False, **kw): ''' Parse form data from an environ dict and return a (forms, files) tuple. Both tuple values are dictionaries with the form-field name as a key (str) and lists as values (multiple values per key are possible). The forms-dictionary contains form-field values as str strings. The files-dictionary contains :class:`MultipartPart` instances, either because the form-field was a file-upload or the value is to big to fit into memory limits. :param environ: An WSGI environment dict. :param charset: The charset to use if unsure. (default: utf8) :param strict: If True, raise :exc:`MultipartError` on any parsing errors. These are silently ignored by default. ''' forms, files = MultiDict(), MultiDict() try: if environ.get('REQUEST_METHOD', 'GET').upper() not in ('POST', 'PUT'): raise MultipartError("Request method other than POST or PUT.") content_length = int(environ.get('CONTENT_LENGTH', '-1')) content_type = environ.get('CONTENT_TYPE', '') if not content_type: raise MultipartError("Missing Content-Type header.") content_type, options = parse_options_header(content_type) stream = environ.get('wsgi.input') or BytesIO() kw['charset'] = charset = options.get('charset', charset) if content_type == 'multipart/form-data': boundary = options.get('boundary', '') if not boundary: raise MultipartError("No boundary for multipart/form-data.") for part in MultipartParser(stream, boundary, content_length, **kw): if part.filename or not part.is_buffered(): files[part.name] = part else: # TODO: Big form-fields are in the files dict. really? forms[part.name] = part.value elif content_type in ('application/x-www-form-urlencoded', 'application/x-url-encoded'): mem_limit = kw.get('mem_limit', 2 ** 20) if content_length > mem_limit: raise MultipartError("Request to big. Increase MAXMEM.") data = stream.read(mem_limit).decode(charset) if stream.read(1): # These is more that does not fit mem_limit raise MultipartError("Request to big. Increase MAXMEM.") data = parse_qs(data, keep_blank_values=True) for key, values in data.iteritems(): for value in values: forms[key] = value else: raise MultipartError("Unsupported content type.") except MultipartError: if strict: raise return forms, files circuits-3.2.3/circuits/web/parsers/querystring.py000066400000000000000000000075411460335514400224070ustar00rootroot00000000000000from urllib.parse import parse_qsl class QueryStringToken: ARRAY = 'ARRAY' OBJECT = 'OBJECT' KEY = 'KEY' class QueryStringParser: def __init__(self, data): self.result = {} sorted_pairs = self._sorted_from_string(data) if isinstance(data, str) else self._sorted_from_obj(data) [self.process(x) for x in sorted_pairs] def _sorted_from_string(self, data): stage1 = parse_qsl(data, keep_blank_values=True) stage2 = [(x[0].strip(), x[1].strip()) for x in stage1] return sorted(stage2, key=lambda p: p[0]) def _sorted_from_obj(self, data): # data is a list of the type generated by parse_qsl if isinstance(data, list): items = data else: # complex objects: try: # django.http.QueryDict, items = [(i[0], j) for i in data.lists() for j in i[1]] except AttributeError: # webob.multidict.MultiDict # werkzeug.datastructures.MultiDict items = data.items() return sorted(items, key=lambda p: p[0]) def process(self, pair): key = pair[0] value = pair[1] # faster than invoking a regex try: key.index('[') self.parse(key, value) return except ValueError: pass try: key.index('.') self.parse(key, value) return except ValueError: pass self.result[key] = value def parse(self, key, value): ref = self.result tokens = self.tokens(key) for token in tokens: token_type, key = token if token_type == QueryStringToken.ARRAY: if key not in ref: ref[key] = [] ref = ref[key] elif token_type == QueryStringToken.OBJECT: if key not in ref: ref[key] = {} ref = ref[key] elif token_type == QueryStringToken.KEY: try: ref = ref[key] next(tokens) # TypeError is for pet[]=lucy&pet[]=ollie # if the array key is empty a type error will be raised except (IndexError, KeyError, TypeError): # the index didn't exist # so we look ahead to see what we are setting # there is not a next token # set the value try: next_token = next(tokens) if next_token[0] == QueryStringToken.ARRAY: ref.append([]) ref = ref[key] elif next_token[0] == QueryStringToken.OBJECT: try: ref[key] = {} except IndexError: ref.append({}) ref = ref[key] except StopIteration: try: ref.append(value) except AttributeError: ref[key] = value return def tokens(self, key): buf = '' for char in key: if char == '[': yield QueryStringToken.ARRAY, buf buf = '' elif char == '.': yield QueryStringToken.OBJECT, buf buf = '' elif char == ']': try: yield QueryStringToken.KEY, int(buf) buf = '' except ValueError: yield QueryStringToken.KEY, None else: buf = buf + char if len(buf) > 0: yield QueryStringToken.KEY, buf circuits-3.2.3/circuits/web/processors.py000066400000000000000000000042531460335514400205330ustar00rootroot00000000000000import re from cgi import parse_header from .headers import HeaderElement from .parsers import MultipartParser, QueryStringParser def process_multipart(request, params): headers = request.headers ctype = headers.elements('Content-Type') ctype = ctype[0] if ctype else HeaderElement.from_str('application/x-www-form-urlencoded') ib = '' if 'boundary' in ctype.params: # http://tools.ietf.org/html/rfc2046#section-5.1.1 # "The grammar for parameters on the Content-type field is such that it # is often necessary to enclose the boundary parameter values in quotes # on the Content-type line" ib = ctype.params['boundary'].strip('"') if not re.match('^[ -~]{0,200}[!-~]$', ib): raise ValueError(f'Invalid boundary in multipart form: {ib!r}') parser = MultipartParser(request.body, ib) for part in parser: if part.filename or not part.is_buffered(): params[part.name] = part else: params[part.name] = part.value def process_urlencoded(request, params, encoding='utf-8'): params.update(QueryStringParser(request.qs).result) body = request.body.getvalue().decode(encoding) result = QueryStringParser(body).result for key, value in result.items(): params[_decode_value(key, encoding)] = _decode_value(value, encoding) def _decode_value(value, encoding): if isinstance(value, bytes): value = value.decode(encoding) elif isinstance(value, list): value = [_decode_value(val, encoding) for val in value] elif isinstance(value, dict): value = {key.decode(encoding): _decode_value(val, encoding) for key, val in value.iteritems()} return value def process(request, params): ctype = request.headers.get('Content-Type') if not ctype: return mtype, mencoding = ctype.split('/', 1) if '/' in ctype else (ctype, None) mencoding, extra = parse_header(mencoding) charset = extra.get('charset', 'utf-8') if mtype == 'multipart': process_multipart(request, params) elif mtype == 'application' and mencoding == 'x-www-form-urlencoded': process_urlencoded(request, params, encoding=charset) circuits-3.2.3/circuits/web/servers.py000066400000000000000000000117031460335514400200200ustar00rootroot00000000000000""" Web Servers This module implements the several Web Server components. """ from sys import stderr from circuits import io from circuits.core import BaseComponent, Timer, handler from circuits.net.events import close, read, write from circuits.net.sockets import BUFSIZE, TCPServer, UNIXServer from .dispatchers import Dispatcher from .events import terminate from .http import HTTP class BaseServer(BaseComponent): """ Create a Base Web Server Create a Base Web Server (HTTP) bound to the IP Address / Port or UNIX Socket specified by the 'bind' parameter. :ivar server: Reference to underlying Server Component :param bind: IP Address / Port or UNIX Socket to bind to. :type bind: Instance of int, list, tuple or str The 'bind' parameter is quite flexible with what valid values it accepts. If an int is passed, a TCPServer will be created. The Server will be bound to the Port given by the 'bind' argument and the bound interface will default (normally to "0.0.0.0"). If a list or tuple is passed, a TCPServer will be created. The Server will be bound to the Port given by the 2nd item in the 'bind' argument and the bound interface will be the 1st item. If a str is passed and it contains the ':' character, this is assumed to be a request to bind to an IP Address / Port. A TCpServer will thus be created and the IP Address and Port will be determined by splitting the string given by the 'bind' argument. Otherwise if a str is passed and it does not contain the ':' character, a file path is assumed and a UNIXServer is created and bound to the file given by the 'bind' argument. """ channel = 'web' def __init__( self, bind, encoding='utf-8', secure=False, certfile=None, channel=channel, display_banner=True, bufsize=BUFSIZE, **kwargs, ): """x.__init__(...) initializes x; see x.__class__.__doc__ for signature""" super().__init__(channel=channel) self._display_banner = display_banner SocketType = TCPServer if isinstance(bind, (int, list, tuple)) else TCPServer if ':' in bind else UNIXServer self.server = SocketType( bind, secure=secure, certfile=certfile, channel=channel, bufsize=bufsize, **kwargs, ).register(self) self.http = HTTP( self, encoding=encoding, channel=channel, ).register(self) @property def host(self): if hasattr(self, 'server'): return self.server.host return None @property def port(self): if hasattr(self, 'server'): return self.server.port return None @property def display_banner(self): return getattr(self, '_display_banner', False) @property def secure(self): if hasattr(self, 'server'): return self.server.secure return None @handler('connect') def _on_connect(self, *args, **kwargs): """Dummy Event Handler for connect""" @handler('closed') def _on_closed(self, *args, **kwargs): """Dummy Event Handler for closed""" @handler('signal') def _on_signal(self, *args, **kwargs): """Signal Event Handler""" self.fire(close()) Timer(3, terminate()).register(self) @handler('terminate') def _on_terminate(self): raise SystemExit(0) @handler('ready') def _on_ready(self, server, bind): stderr.write( f'{self.http.version} ready! Listening on: {self.http.base}\n', ) class Server(BaseServer): """ Create a Web Server Create a Web Server (HTTP) complete with the default Dispatcher to parse requests and posted form data dispatching to appropriate Controller(s). See: circuits.web.servers.BaseServer """ def __init__(self, bind, **kwargs): """x.__init__(...) initializes x; see x.__class__.__doc__ for signature""" super().__init__(bind, **kwargs) Dispatcher(channel=self.channel).register(self.http) class FakeSock: def getpeername(self): return (None, None) class StdinServer(BaseComponent): channel = 'web' def __init__(self, encoding='utf-8', channel=channel): super().__init__(channel=channel) self.server = (io.stdin + io.stdout).register(self) self.http = HTTP( self, encoding=encoding, channel=channel, ).register(self) Dispatcher(channel=self.channel).register(self) @property def host(self): return io.stdin.filename @property def port(self): return 0 @property def secure(self): return False @handler('read', channel='stdin') def read(self, data): self.fire(read(FakeSock(), data)) @handler('write') def write(self, sock, data): self.fire(write(data)) circuits-3.2.3/circuits/web/sessions.py000066400000000000000000000060621460335514400201770ustar00rootroot00000000000000""" Session Components This module implements Session Components that can be used to store and access persistent information. """ from abc import ABC, abstractmethod from collections import defaultdict from hashlib import sha1 as sha from uuid import uuid4 as uuid from circuits import Component, handler def who(request, encoding='utf-8'): """Create a SHA1 Hash of the User's IP Address and User-Agent""" ip = request.remote.ip agent = request.headers.get('User-Agent', '') return sha(f'{ip}{agent}'.encode(encoding)).hexdigest() def create_session(request): """ Create a unique session id from the request Returns a unique session using ``uuid4()`` and a ``sha1()`` hash of the users IP Address and User Agent in the form of ``sid/who``. """ return f'{uuid().hex}/{who(request)}' def verify_session(request, sid): """ Verify a User's Session This verifies the User's Session by verifying the SHA1 Hash of the User's IP Address and User-Agent match the provided Session ID. """ if '/' not in sid: return create_session(request) user = sid.split('/', 1)[1] if user != who(request): return create_session(request) return sid class Session(dict): def __init__(self, sid, data, store): super().__init__(data) self._sid = sid self._store = store @property def sid(self): return self._sid @property def store(self): return self._store def expire(self): self.store.delete(self.sid) def __enter__(self): return self def __exit__(self, exc_type, exc_value, traceback): if exc_type is None: self.store.save(self.sid, self) class Store(ABC): @abstractmethod def delete(self, sid): """Delete the session data identified by sid""" @abstractmethod def load(self, sid): """Load the session data identified by sid""" @abstractmethod def save(self, sid): """Save the session data identified by sid""" class MemoryStore(Store): def __init__(self): self._data = defaultdict(dict) @property def data(self): return self._data def delete(self, sid): del self.data[sid] def load(self, sid): return Session(sid, self.data[sid], self) def save(self, sid, data): self.data[sid] = data class Sessions(Component): channel = 'web' def __init__(self, name='circuits', store=MemoryStore, channel=channel): super().__init__(channel=channel) self._name = name self._store = store() @property def name(self): return self._name @property def store(self): return self._store @handler('request', priority=10) def request(self, request, response): if self.name in request.cookie: sid = request.cookie[self._name].value sid = verify_session(request, sid) else: sid = create_session(request) request.session = self.store.load(sid) response.cookie[self.name] = sid circuits-3.2.3/circuits/web/tools.py000066400000000000000000000415461460335514400174770ustar00rootroot00000000000000""" Tools This module implements tools used throughout circuits.web. These tools can also be used within Controllers and request handlers. """ import hashlib import mimetypes import os import stat from collections.abc import Callable from datetime import datetime, timedelta from email.generator import _make_boundary from email.utils import formatdate from time import mktime from circuits import BaseComponent, handler from circuits.web.wrappers import Host from . import _httpauth from .errors import httperror, notfound, redirect, unauthorized from .utils import compress, get_ranges mimetypes.init() mimetypes.add_type('image/x-dwg', '.dwg') mimetypes.add_type('image/x-icon', '.ico') mimetypes.add_type('text/javascript', '.js') mimetypes.add_type('application/xhtml+xml', '.xhtml') def expires(request, response, secs=0, force=False): """ Tool for influencing cache mechanisms using the 'Expires' header. 'secs' must be either an int or a datetime.timedelta, and indicates the number of seconds between response.time and when the response should expire. The 'Expires' header will be set to (response.time + secs). If 'secs' is zero, the 'Expires' header is set one year in the past, and the following "cache prevention" headers are also set: - 'Pragma': 'no-cache' - 'Cache-Control': 'no-cache, must-revalidate' If 'force' is False (the default), the following headers are checked: 'Etag', 'Last-Modified', 'Age', 'Expires'. If any are already present, none of the above response headers are set. """ headers = response.headers cacheable = False if not force: # some header names that indicate that the response can be cached for indicator in ('Etag', 'Last-Modified', 'Age', 'Expires'): if indicator in headers: cacheable = True break if not cacheable: if isinstance(secs, timedelta): secs = secs.total_seconds() if secs == 0: if force or 'Pragma' not in headers: headers['Pragma'] = 'no-cache' if request.protocol >= (1, 1) and (force or 'Cache-Control' not in headers): headers['Cache-Control'] = 'no-cache, must-revalidate' # Set an explicit Expires date in the past. now = datetime.now() lastyear = now.replace(year=now.year - 1) expiry = formatdate( mktime(lastyear.timetuple()), usegmt=True, ) else: expiry = formatdate(response.time + secs, usegmt=True) if force or 'Expires' not in headers: headers['Expires'] = expiry def serve_file(request, response, path, type=None, disposition=None, name=None): """ Set status, headers, and body in order to serve the given file. The Content-Type header will be set to the type arg, if provided. If not provided, the Content-Type will be guessed by the file extension of the 'path' argument. If disposition is not None, the Content-Disposition header will be set to "; filename=". If name is None, it will be set to the basename of path. If disposition is None, no Content-Disposition header will be written. """ if not os.path.isabs(path): raise ValueError("'%s' is not an absolute path." % path) try: st = os.stat(path) except OSError: return notfound(request, response) # Check if path is a directory. if stat.S_ISDIR(st.st_mode): # Let the caller deal with it as they like. return notfound(request, response) # Set the Last-Modified response header, so that # modified-since validation code can work. response.headers['Last-Modified'] = formatdate( st.st_mtime, usegmt=True, ) result = validate_since(request, response) if result is not None: return result if type is None: # Set content-type based on filename extension ext = os.path.splitext(path)[-1].lower() type = mimetypes.types_map.get(ext, 'text/plain') response.headers['Content-Type'] = type if disposition is not None: if name is None: name = os.path.basename(path) cd = f'{disposition}; filename="{name}"' response.headers['Content-Disposition'] = cd # Set Content-Length and use an iterable (file object) # this way CP won't load the whole file in memory c_len = st.st_size bodyfile = open(path, 'rb') # HTTP/1.0 didn't have Range/Accept-Ranges headers, or the 206 code if request.protocol >= (1, 1): response.headers['Accept-Ranges'] = 'bytes' r = get_ranges(request.headers.get('Range'), c_len) if r == []: response.headers['Content-Range'] = 'bytes */%s' % c_len return httperror(request, response, 416) if r: if len(r) == 1: # Return a single-part response. start, stop = r[0] r_len = stop - start response.status = 206 response.headers['Content-Range'] = f'bytes {start}-{stop - 1}/{c_len}' response.headers['Content-Length'] = r_len bodyfile.seek(start) response.body = bodyfile.read(r_len) else: # Return a multipart/byteranges response. response.status = 206 boundary = _make_boundary() ct = 'multipart/byteranges; boundary=%s' % boundary response.headers['Content-Type'] = ct if 'Content-Length' in response.headers: # Delete Content-Length header so finalize() recalcs it. del response.headers['Content-Length'] def file_ranges(): # Apache compatibility: yield '\r\n' for start, stop in r: yield '--' + boundary yield '\r\nContent-type: %s' % type yield ('\r\nContent-range: bytes %s-%s/%s\r\n\r\n' % (start, stop - 1, c_len)) bodyfile.seek(start) yield bodyfile.read(stop - start) yield '\r\n' # Final boundary yield '--' + boundary + '--' # Apache compatibility: yield '\r\n' response.body = file_ranges() else: response.headers['Content-Length'] = c_len response.body = bodyfile else: response.headers['Content-Length'] = c_len response.body = bodyfile return response def serve_download(request, response, path, name=None): """Serve 'path' as an application/x-download attachment.""" type = 'application/x-download' disposition = 'attachment' return serve_file(request, response, path, type, disposition, name) def validate_etags(request, response, autotags=False): """ Validate the current ETag against If-Match, If-None-Match headers. If autotags is True, an ETag response-header value will be provided from an MD5 hash of the response body (unless some other code has already provided an ETag header). If False (the default), the ETag will not be automatic. WARNING: the autotags feature is not designed for URL's which allow methods other than GET. For example, if a POST to the same URL returns no content, the automatic ETag will be incorrect, breaking a fundamental use for entity tags in a possibly destructive fashion. Likewise, if you raise 304 Not Modified, the response body will be empty, the ETag hash will be incorrect, and your application will break. See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.24 """ # Guard against being run twice. if hasattr(response, 'ETag'): return None status = response.status etag = response.headers.get('ETag') # Automatic ETag generation. See warning in docstring. if (not etag) and autotags and status == 200: etag = response.collapse_body() etag = '"%s"' % hashlib.new('md5', etag).hexdigest() response.headers['ETag'] = etag response.ETag = etag # "If the request would, without the If-Match header field, result in # anything other than a 2xx or 412 status, then the If-Match header # MUST be ignored." if status >= 200 and status <= 299: conditions = request.headers.elements('If-Match') or [] conditions = [str(x) for x in conditions] if conditions and not (conditions == ['*'] or etag in conditions): return httperror( request, response, 412, description='If-Match failed: ETag %r did not match %r' % ( etag, conditions, ), ) conditions = request.headers.elements('If-None-Match') or [] conditions = [str(x) for x in conditions] if conditions == ['*'] or etag in conditions: if request.method in ('GET', 'HEAD'): return redirect(request, response, [], code=304) return httperror( request, response, 412, description=( 'If-None-Match failed: ETag %r matched %r' % ( etag, conditions, ) ), ) return None return None def validate_since(request, response): """ Validate the current Last-Modified against If-Modified-Since headers. If no code has set the Last-Modified response header, then no validation will be performed. """ lastmod = response.headers.get('Last-Modified') if lastmod: status = response.status since = request.headers.get('If-Unmodified-Since') if since and since != lastmod and ((status >= 200 and status <= 299) or status == 412): return httperror(request, response, 412) since = request.headers.get('If-Modified-Since') if since and since == lastmod and ((status >= 200 and status <= 299) or status == 304): if request.method in ('GET', 'HEAD'): return redirect(request, response, [], code=304) return httperror(request, response, 412) return None return None def check_auth(request, response, realm, users, encrypt=None): """ Check Authentication If an Authorization header contains credentials, return True, else False. :param realm: The authentication realm. :type realm: str :param users: A dict of the form: {username: password} or a callable returning a dict. :type users: dict or callable :param encrypt: Callable used to encrypt the password returned from the user-agent. if None it defaults to a md5 encryption. :type encrypt: callable """ if 'Authorization' in request.headers: # make sure the provided credentials are correctly set ah = _httpauth.parseAuthorization(request.headers.get('Authorization')) if ah is None: return httperror(request, response, 400) if not encrypt: encrypt = _httpauth.DIGEST_AUTH_ENCODERS[_httpauth.MD5] if isinstance(users, Callable): try: # backward compatibility users = users() # expect it to return a dictionary if not isinstance(users, dict): raise ValueError('Authentication users must be a dict') # fetch the user password password = users.get(ah['username'], None) except TypeError: # returns a password (encrypted or clear text) password = users(ah['username']) else: if not isinstance(users, dict): raise ValueError('Authentication users must be a dict') # fetch the user password password = users.get(ah['username'], None) # validate the Authorization by re-computing it here # and compare it with what the user-agent provided if _httpauth.checkResponse(ah, password, method=request.method, encrypt=encrypt, realm=realm): request.login = ah['username'] return True request.login = False return False def basic_auth(request, response, realm, users, encrypt=None): """ Perform Basic Authentication If auth fails, returns an Unauthorized error with a basic authentication header. :param realm: The authentication realm. :type realm: str :param users: A dict of the form: {username: password} or a callable returning a dict. :type users: dict or callable :param encrypt: Callable used to encrypt the password returned from the user-agent. if None it defaults to a md5 encryption. :type encrypt: callable """ if check_auth(request, response, realm, users, encrypt): return None # inform the user-agent this path is protected response.headers['WWW-Authenticate'] = _httpauth.basicAuth(realm) return unauthorized(request, response) def digest_auth(request, response, realm, users): """ Perform Digest Authentication If auth fails, raise 401 with a digest authentication header. :param realm: The authentication realm. :type realm: str :param users: A dict of the form: {username: password} or a callable returning a dict. :type users: dict or callable """ if check_auth(request, response, realm, users): return None # inform the user-agent this path is protected response.headers['WWW-Authenticate'] = _httpauth.digestAuth(realm) return unauthorized(request, response) def gzip(response, level=4, mime_types=('text/html', 'text/plain')): """ Try to gzip the response body if Content-Type in mime_types. response.headers['Content-Type'] must be set to one of the values in the mime_types arg before calling this function. No compression is performed if any of the following hold: * The client sends no Accept-Encoding request header * No 'gzip' or 'x-gzip' is present in the Accept-Encoding header * No 'gzip' or 'x-gzip' with a qvalue > 0 is present * The 'identity' value is given with a qvalue > 0. """ if not response.body: # Response body is empty (might be a 304 for instance) return response # If returning cached content (which should already have been gzipped), # don't re-zip. if getattr(response.request, 'cached', False): return response acceptable = response.request.headers.elements('Accept-Encoding') if not acceptable: # If no Accept-Encoding field is present in a request, # the server MAY assume that the client will accept any # content coding. In this case, if "identity" is one of # the available content-codings, then the server SHOULD use # the "identity" content-coding, unless it has additional # information that a different content-coding is meaningful # to the client. return response ct = response.headers.get('Content-Type', 'text/html').split(';')[0] for coding in acceptable: if coding.value == 'identity' and coding.qvalue != 0: return response if coding.value in ('gzip', 'x-gzip'): if coding.qvalue == 0: return response if ct in mime_types: # Return a generator that compresses the page varies = response.headers.get('Vary', '') varies = [x.strip() for x in varies.split(',') if x.strip()] if 'Accept-Encoding' not in varies: varies.append('Accept-Encoding') response.headers['Vary'] = ', '.join(varies) response.headers['Content-Encoding'] = 'gzip' response.body = compress(response.body, level) if 'Content-Length' in response.headers: # Delete Content-Length header so finalize() recalcs it. del response.headers['Content-Length'] return response return httperror( response.request, response, 406, description='identity, gzip', ) class ReverseProxy(BaseComponent): headers = ('X-Real-IP', 'X-Forwarded-For') def init(self, headers=None): """ Web Component for identifying the original client IP when a reverse proxy is used :param headers: List of HTTP headers to read the original client IP """ if headers: self.headers = headers @handler('request', priority=1) def _on_request(self, req, *_): ip = [v for v in map(req.headers.get, self.headers) if v] req.remote = (ip and Host(ip[0], '', ip[0])) or req.remote circuits-3.2.3/circuits/web/url.py000077500000000000000000000233721460335514400171410ustar00rootroot00000000000000#!/usr/bin/env python # # Copyright (c) 2012 SEOmoz # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """This is a module for dealing with urls. In particular, sanitizing them.""" import codecs import re from urllib.parse import quote, unquote, urljoin, urlparse, urlunparse # Come codes that we'll need IDNA = codecs.lookup('idna') UTF8 = codecs.lookup('utf-8') ASCII = codecs.lookup('ascii') W1252 = codecs.lookup('windows-1252') # The default ports associated with each scheme PORTS = { 'http': 80, 'https': 443, } def parse_url(url, encoding='utf-8'): """Parse the provided url string and return an URL object""" return URL.parse(url, encoding) class URL: """ For more information on how and what we parse / sanitize: http://tools.ietf.org/html/rfc1808.html The more up-to-date RFC is this one: http://www.ietf.org/rfc/rfc3986.txt """ @classmethod def parse(cls, url, encoding): """Parse the provided url, and return a URL instance""" parsed = urlparse(url.encode('utf-8')) if isinstance(url, str) else urlparse(url.decode(encoding).encode('utf-8')) if isinstance(parsed.port, int): port = str(parsed.port).encode('utf-8') if parsed.port not in (80, 443) else None else: port = None return cls( parsed.scheme, parsed.hostname, port, parsed.path, parsed.params, parsed.query, parsed.fragment, ) def __init__(self, scheme, host, port, path, params=b'', query=b'', fragment=b''): assert type(port) is not int self._scheme = scheme self._host = host self._port = port self._path = path or b'/' self._params = re.sub(b'^;+', b'', params) self._params = re.sub( b'^;|;$', b'', re.sub(b';{2,}', b';', self._params), ) # Strip off extra leading ?'s self._query = query.lstrip(b'?') self._query = re.sub( b'^&|&$', b'', re.sub(b'&{2,}', b'&', self._query), ) self._fragment = fragment def __call__(self, path, encoding='utf-8'): return self.relative(path, encoding=encoding).unicode() def equiv(self, other): """Return true if this url is equivalent to another""" _other = self.parse(other, 'utf-8') if isinstance(other, str) else self.parse(other.utf8(), 'utf-8') _self = self.parse(self.utf8(), 'utf-8') _self.lower().canonical().defrag().abspath().escape().punycode() _other.lower().canonical().defrag().abspath().escape().punycode() result = ( _self._scheme == _other._scheme and _self._host == _other._host and _self._path == _other._path and _self._params == _other._params and _self._query == _other._query ) if result: if _self._port and not _other._port: # Make sure _self._port is the default for the scheme return _self._port == PORTS.get(_self._scheme, None) if _other._port and not _self._port: # Make sure _other._port is the default for the scheme return _other._port == PORTS.get(_other._scheme, None) return _self._port == _other._port return False def __eq__(self, other): """Return true if this url is /exactly/ equal to another""" if isinstance(other, str): return self.__eq__(self.parse(other, 'utf-8')) return ( self._scheme == other._scheme and self._host == other._host and self._path == other._path and self._port == other._port and self._params == other._params and self._query == other._query and self._fragment == other._fragment ) def __ne__(self, other): return not self.__eq__(other) def __str__(self): return self.utf8() def __repr__(self): return '' % self.utf8() def canonical(self): """ Canonicalize this url. This includes reordering parameters and args to have a consistent ordering """ self._query = b'&'.join( sorted(q for q in self._query.split(b'&')), ) self._params = b';'.join( sorted(q for q in self._params.split(b';')), ) return self def defrag(self): """Remove the fragment from this url""" self._fragment = None return self def deparam(self, params=None): """Strip any of the provided parameters out of the url""" # And remove all the black-listed query parameters self._query = '&'.join(q for q in self._query.split('&') if q.partition('=')[0].lower() not in params) # And remove all the black-listed param parameters self._params = ';'.join(q for q in self._params.split(';') if q.partition('=')[0].lower() not in params) return self def abspath(self): """Clear out any '..' and excessive slashes from the path""" # Remove double forward-slashes from the path path = re.sub(rb'\/{2,}', b'/', self._path) # With that done, go through and remove all the relative references unsplit = [] directory = False for part in path.split(b'/'): # If we encounter the parent directory, and there's # a segment to pop off, then we should pop it off. if part == b'..' and (not unsplit or unsplit.pop() is not None): directory = True elif part != b'.': directory = False unsplit.append(part) else: directory = True # With all these pieces, assemble! if directory: # If the path ends with a period, then it refers to a directory, # not a file path unsplit.append(b'/') self._path = b'/'.join(unsplit) return self def lower(self): """Lowercase the hostname""" if self._host is not None: self._host = self._host.lower() return self def sanitize(self): """A shortcut to abspath, escape and lowercase""" return self.abspath().escape().lower() def escape(self): """Make sure that the path is correctly escaped""" self._path = quote(unquote(self._path.decode('utf-8'))).encode('utf-8') return self def unescape(self): """Unescape the path""" self._path = unquote(self._path) return self def encode(self, encoding): """Return the url in an arbitrary encoding""" netloc = self._host if self._port: netloc += b':' + bytes(self._port) result = urlunparse( ( self._scheme, netloc, self._path, self._params, self._query, self._fragment, ) ) return result.decode('utf-8').encode(encoding) def relative(self, path, encoding='utf-8'): """Evaluate the new path relative to the current url""" if isinstance(path, str): newurl = urljoin(self.utf8(), path.encode('utf-8')) else: newurl = urljoin( self.utf8(), path.decode(encoding).encode('utf-8'), ) return URL.parse(newurl, 'utf-8') def punycode(self): """Convert to punycode hostname""" if self._host: self._host = IDNA.encode(self._host.decode('utf-8'))[0] return self raise TypeError('Cannot punycode a relative url') def unpunycode(self): """Convert to an unpunycoded hostname""" if self._host: self._host = IDNA.decode(self._host.decode('utf-8'))[0].encode('utf-8') return self raise TypeError('Cannot unpunycode a relative url') ########################################################################### # Information about the type of url it is ########################################################################### def absolute(self): """ Return True if this is a fully-qualified URL with a hostname and everything """ return bool(self._host) ########################################################################### # Get a string representation. These methods can't be chained, as they # return strings ########################################################################### def unicode(self): """Return a unicode version of this url""" return self.encode('utf-8').decode('utf-8') def utf8(self): """Return a utf-8 version of this url""" return self.encode('utf-8') circuits-3.2.3/circuits/web/utils.py000066400000000000000000000106651460335514400174750ustar00rootroot00000000000000""" Utilities This module implements utility functions. """ import os import re import stat import struct import time import zlib from math import sqrt from urllib.parse import parse_qs as _parse_qs from .exceptions import RangeUnsatisfiable image_map_pattern = re.compile('^[0-9]+,[0-9]+$') def is_unix_socket(path): if not os.path.exists(path): return False mode = os.stat(path).st_mode return stat.S_ISSOCK(mode) def average(xs): return sum(xs) * 1.0 / len(xs) def variance(xs): avg = average(xs) return [(x - avg) ** 2 for x in xs] def stddev(xs): return sqrt(average(variance(xs))) def parse_qs(query_string, keep_blank_values=True): """ parse_qs(query_string) -> dict Build a params dictionary from a query_string. If keep_blank_values is True (the default), keep values that are blank. """ if image_map_pattern.match(query_string): # Server-side image map. Map the coords to "x" and "y" # (like CGI::Request does). pm = query_string.split(',') return {'x': int(pm[0]), 'y': int(pm[1])} pm = _parse_qs(query_string, keep_blank_values) return {k: v[0] for k, v in pm.items() if v} def compress(body, compress_level): """Compress 'body' at the given compress_level.""" # Header yield b'\037\213\010\0' + struct.pack('= content_length: # From rfc 2616 sec 14.16: # "If the server receives a request (other than one # including an If-Range request-header field) with an # unsatisfiable Range request-header field (that is, # all of whose byte-range-spec values have a first-byte-pos # value greater than the current length of the selected # resource), it SHOULD return a response code of 416 # (Requested range not satisfiable)." continue if stop < start: # From rfc 2616 sec 14.16: # "If the server ignores a byte-range-spec because it # is syntactically invalid, the server SHOULD treat # the request as if the invalid Range header field # did not exist. (Normally, this means return a 200 # response containing the full entity)." return None # Prevent duplicate ranges. See Issue #59 if (start, stop + 1) not in result: result.append((start, stop + 1)) else: if not stop: # See rfc quote above. return None # Negative subscript (last N bytes) # Prevent duplicate ranges. See Issue #59 if (content_length - int(stop), content_length) not in result: result.append((content_length - int(stop), content_length)) # Can we satisfy the requested Range? # If we have an exceedingly high standard deviation # of Range(s) we reject the request. # See Issue #59 if len(result) > 1 and stddev([x[1] - x[0] for x in result]) > 2.0: raise RangeUnsatisfiable() return result circuits-3.2.3/circuits/web/websockets/000077500000000000000000000000001460335514400201245ustar00rootroot00000000000000circuits-3.2.3/circuits/web/websockets/__init__.py000066400000000000000000000002221460335514400222310ustar00rootroot00000000000000"""circuits.web websockets""" from .client import WebSocketClient from .dispatcher import WebSocketsDispatcher # flake8: noqa # pylama: skip=1 circuits-3.2.3/circuits/web/websockets/client.py000066400000000000000000000132521460335514400217570ustar00rootroot00000000000000import base64 import os import random import re from errno import ECONNRESET from socket import error as SocketError from urllib.parse import urlparse from circuits.core.components import BaseComponent from circuits.core.handlers import handler from circuits.net.events import close, connect, write from circuits.net.sockets import TCPClient from circuits.protocols.http import HTTP from circuits.protocols.websocket import WebSocketCodec from circuits.web.client import NotConnected from circuits.web.headers import Headers class WebSocketClient(BaseComponent): """ An RFC 6455 compliant WebSocket client component. Upon receiving a :class:`circuits.web.client.Connect` event, the component tries to establish the connection to the server in a two stage process. First, a :class:`circuits.net.events.connect` event is sent to a child :class:`~.sockets.TCPClient`. When the TCP connection has been established, the HTTP request for opening the WebSocket is sent to the server. A failure in this setup process is signaled by raising an :class:`~.client.NotConnected` exception. When the server accepts the request, the WebSocket connection is established and can be used very much like an ordinary socket by handling :class:`~.net.events.read` events on and sending :class:`~.net.events.write` events to the channel specified as the ``wschannel`` parameter of the constructor. Firing a :class:`~.net.events.close` event on that channel closes the connection in an orderly fashion (i.e. as specified by the WebSocket protocol). """ channel = 'wsclient' def __init__(self, url, channel=channel, wschannel='ws', headers=None): """ :param url: the URL to connect to. :param channel: the channel used by this component :param wschannel: the channel used for the actual WebSocket communication (read, write, close events) :param headers: additional headers to be passed with the WebSocket setup HTTP request """ super().__init__(channel=channel) self._url = url self._headers = headers or {} self._response = None self._pending = 0 self._wschannel = wschannel self._codec = None self._transport = TCPClient(channel=self.channel).register(self) HTTP(channel=self.channel).register(self._transport) @handler('ready') def _on_ready(self, event, *args, **kwargs): p = urlparse(self._url) if not p.hostname: raise ValueError('URL must be absolute') self._host = p.hostname if p.scheme == 'ws': self._secure = False self._port = p.port or 80 elif p.scheme == 'wss': self._secure = True self._port = p.port or 443 else: raise NotConnected() self._resource = p.path or '/' if p.query: self._resource += '?' + p.query self.fire(connect(self._host, self._port, self._secure), self._transport) @handler('connected') def _on_connected(self, host, port): headers = Headers(list(self._headers.items())) # Clients MUST include Host header in HTTP/1.1 requests (RFC 2616) if 'Host' not in headers: headers['Host'] = self._host + (':' + str(self._port)) if self._port else '' headers['Upgrade'] = 'websocket' headers['Connection'] = 'Upgrade' try: sec_key = os.urandom(16) except NotImplementedError: sec_key = ''.join([chr(random.randint(0, 255)) for i in range(16)]) headers['Sec-WebSocket-Key'] = base64.b64encode(sec_key).decode('latin1') headers['Sec-WebSocket-Version'] = '13' UNSAFE_CHARS = re.compile( '[^0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~]' ) # noqa: E501 escaped_resource = UNSAFE_CHARS.sub('', self._resource.encode('ASCII', 'replace').decode('ASCII')) command = f'GET {escaped_resource} HTTP/1.1' message = f'{command}\r\n{headers}' self._pending += 1 self.fire(write(message.encode('utf-8')), self._transport) return True @handler('response') def _on_response(self, response): self._response = response self._pending -= 1 if response.headers.get('Connection', '').lower() == 'close' or response.status != 101: self.fire(close(), self._transport) raise NotConnected() self._codec = WebSocketCodec(data=response.body.read(), channel=self._wschannel).register(self) @handler('read') def _on_read(self, event, *args): # FIXME: every read-event is lost due to a race condition between # WebSocketCodec().register() and the registered()-event of that instance. if len(args) != 1: return if self._codec is not None and self._codec.parent is self: if 'read' not in self._codec.events(): event.stop() self.fire(event.create('read', *args)) else: self.removeHandler(self._on_read) @handler('error', priority=10) def _on_error(self, event, error, *args, **kwargs): # For HTTP 1.1 we leave the connection open. If the peer closes # it after some time and we have no pending request, that's OK. if isinstance(error, SocketError) and error.args[0] == ECONNRESET and self._pending == 0: event.stop() def close(self): if self._transport is not None: self._transport.close() @property def connected(self): return getattr(self._transport, 'connected', False) if hasattr(self, '_transport') else False circuits-3.2.3/circuits/web/websockets/dispatcher.py000066400000000000000000000116211460335514400226250ustar00rootroot00000000000000import base64 import contextlib import hashlib from circuits import BaseComponent, handler from circuits.net.events import connect, disconnect from circuits.protocols.websocket import WebSocketCodec from circuits.web.errors import httperror class WebSocketsDispatcher(BaseComponent): """ This class implements an RFC 6455 compliant WebSockets dispatcher that handles the WebSockets handshake and upgrades the connection. The dispatcher listens on its channel for :class:`~.web.events.Request` events and tries to match them with a given path. Upon a match, the request is checked for the proper Opening Handshake information. If successful, the dispatcher confirms the establishment of the connection to the client. Any subsequent data from the client is handled as a WebSocket data frame, decoded and fired as a :class:`~.sockets.Read` event on the ``wschannel`` passed to the constructor. The data from :class:`~.net.events.write` events on that channel is encoded as data frames and forwarded to the client. Firing a :class:`~.sockets.Close` event on the ``wschannel`` closes the connection in an orderly fashion (i.e. as specified by the WebSocket protocol). """ channel = 'web' def __init__(self, path=None, wschannel='wsserver', *args, **kwargs): """ :param path: the path to handle. Requests that start with this path are considered to be WebSocket Opening Handshakes. :param wschannel: the channel on which :class:`~.sockets.read` events from the client will be delivered and where :class:`~.net.events.write` events to the client will be sent to. """ super().__init__(*args, **kwargs) self._requests = {} self._path = path self._wschannel = wschannel self._codecs = {} @handler('read', channel=wschannel, priority=100) def _on_read_handler(self, event, socket, data): if socket in self._requests: event.request = self._requests[socket] self.addHandler(_on_read_handler) @handler('request', priority=0.2) def _on_request(self, event, request, response): if self._path is not None and not request.path.startswith(self._path): return None self._protocol_version = 13 headers = request.headers sec_key = headers.get('Sec-WebSocket-Key', '').encode('utf-8') subprotocols = headers.elements('Sec-WebSocket-Protocol') connection_tokens = [s.strip() for s in headers.get('Connection', '').lower().split(',')] try: if ( 'Host' not in headers or headers.get('Upgrade', '').lower() != 'websocket' or 'upgrade' not in connection_tokens or sec_key is None or len(base64.b64decode(sec_key)) != 16 ): return httperror(request, response, code=400) if headers.get('Sec-WebSocket-Version', '') != '13': response.headers['Sec-WebSocket-Version'] = '13' return httperror(request, response, code=400) # Generate accept header information msg = sec_key + b'258EAFA5-E914-47DA-95CA-C5AB0DC85B11' hasher = hashlib.sha1() hasher.update(msg) accept = base64.b64encode(hasher.digest()) # Successful completion response.status = 101 response.close = False with contextlib.suppress(KeyError): del response.headers['Content-Type'] response.headers['Upgrade'] = 'WebSocket' response.headers['Connection'] = 'Upgrade' response.headers['Sec-WebSocket-Accept'] = accept.decode('ASCII') if subprotocols: response.headers['Sec-WebSocket-Protocol'] = self.select_subprotocol(subprotocols) codec = WebSocketCodec(request.sock, channel=self._wschannel) self._codecs[request.sock] = codec codec.register(self) return response finally: event.stop() def select_subprotocol(self, subprotocols): return subprotocols[0] @handler('response_complete') def _on_response_complete(self, e, value): response = e.args[0] request = response.request if request.sock in self._codecs: self._requests[request.sock] = request cevent = connect( request.sock, *request.sock.getpeername(), ) cevent.request = request self.fire(cevent, self._wschannel) @handler('disconnect') def _on_disconnect(self, sock): if sock in self._codecs: devent = disconnect(sock) devent.request = self._requests.get(sock) self.fire(devent, self._wschannel) del self._codecs[sock] self._requests.pop(sock, None) circuits-3.2.3/circuits/web/wrappers.py000066400000000000000000000232441460335514400201750ustar00rootroot00000000000000""" Request/Response Wrappers This module implements the Request and Response objects. """ from email.utils import formatdate from functools import partial from http.cookies import SimpleCookie from io import BytesIO from time import time from circuits.net.sockets import BUFSIZE from .constants import HTTP_STATUS_CODES, SERVER_VERSION from .errors import httperror from .headers import Headers from .url import parse_url formatdate = partial(formatdate, usegmt=True) def file_generator(input, chunkSize=BUFSIZE): chunk = input.read(chunkSize) while chunk: yield chunk chunk = input.read(chunkSize) input.close() class Host: """ An internet address. name should be the client's host name. If not available (because no DNS lookup is performed), the IP address should be used instead. """ ip = '0.0.0.0' port = 80 name = 'unknown.tld' def __init__(self, ip, port, name=None): self.ip = ip self.port = port if name is None: name = ip self.name = name def __repr__(self): return f'Host({self.ip!r}, {self.port!r}, {self.name!r})' class HTTPStatus: __slots__ = ('_reason', '_status') def __init__(self, status=200, reason=None): self._status = status self._reason = reason or HTTP_STATUS_CODES.get(status, '') def __int__(self): return self._status def __lt__(self, other): if isinstance(other, int): return self._status < other return super().__lt__(other) def __gt__(self, other): if isinstance(other, int): return self._status > other return super().__gt__(other) def __le__(self, other): if isinstance(other, int): return self._status <= other return super().__le__(other) def __ge__(self, other): if isinstance(other, int): return self._status >= other return super().__ge__(other) def __eq__(self, other): if isinstance(other, int): return self._status == other return super().__eq__(other) def __str__(self): return f'{self._status:d} {self._reason}' def __repr__(self): return f'' def __format__(self, format_spec): return format(str(self), format_spec) @property def status(self): return self._status @property def reason(self): return self._reason class Request: """ Creates a new Request object to hold information about a request. :param sock: The socket object of the request. :type sock: socket.socket :param method: The requested method. :type method: str :param scheme: The requested scheme. :type scheme: str :param path: The requested path. :type path: str :param protocol: The requested protocol. :type protocol: str :param qs: The query string of the request. :type qs: str """ server = None """:cvar: A reference to the underlying server""" scheme = 'http' protocol = (1, 1) host = '' local = Host('127.0.0.1', 80) remote = Host('', 0) index = None script_name = '' login = None handled = False def __init__(self, sock, method='GET', scheme='http', path='/', protocol=(1, 1), qs='', headers=None, server=None): """Initializes x; see x.__class__.__doc__ for signature""" self.sock = sock self.method = method self.scheme = scheme or Request.scheme self.path = path self.protocol = protocol self.qs = qs self.print_debug = getattr(server, 'display_banner', False) self.headers = headers or Headers() self.server = server self.cookie = SimpleCookie() if sock is not None: name = sock.getpeername() try: ip, port = name name = None except ValueError: # AF_UNIX ip, port = None, None self.remote = Host(ip, port, name) cookie = self.headers.get('Cookie') if cookie is not None: self.cookie.load(cookie) self.body = BytesIO() if self.server is not None: self.local = Host(self.server.host, self.server.port) try: host = self.headers['Host'] if ':' in host: parts = host.split(':', 1) host = parts[0] port = int(parts[1]) else: port = 443 if self.scheme == 'https' else 80 except KeyError: host = self.local.name or self.local.ip port = self.server.port self.host = host self.port = port base = '{}://{}{}/'.format( self.scheme, self.host, f':{self.port:d}' if self.port not in (80, 443) else '', ) self.base = parse_url(base) url = '{}{}{}'.format( base, self.path, f'?{self.qs}' if self.qs else '', ) self.uri = parse_url(url) self.uri.sanitize() def __repr__(self): protocol = 'HTTP/%d.%d' % self.protocol return f'' class Body: """Response Body""" encode_errors = 'strict' def __get__(self, response, cls=None): if response is None: return self return response._body def __set__(self, response, value): if response == value: return if isinstance(value, bytes): value = [value] if value else [] elif isinstance(value, str): value = [value.encode(response.encoding, self.encode_errors)] if value else [] elif hasattr(value, 'read'): response.stream = True value = file_generator(value) elif isinstance(value, httperror): value = [str(value)] elif value is None: value = [] response._body = value class Status: """Response Status""" def __get__(self, response, cls=None): if response is None: return self return response._status def __set__(self, response, value): value = HTTPStatus(value) if isinstance(value, int) else value response._status = value class Response: """ Response(sock, request) -> new Response object A Response object that holds the response to send back to the client. This ensure that the correct data is sent in the correct order. """ body = Body() status = Status() done = False close = False stream = False chunked = False def __init__(self, request, encoding='utf-8', status=None): """Initializes x; see x.__class__.__doc__ for signature""" self.request = request self.encoding = encoding self._body = [] self._status = HTTPStatus(status if status is not None else 200) self.time = time() self.headers = Headers() self.headers['Date'] = formatdate() if getattr(self.request.server, 'display_banner', False): if self.request.server is not None: self.headers.add_header('Server', request.server.http.version) else: self.headers.add_header('X-Powered-By', SERVER_VERSION) self.cookie = self.request.cookie self.protocol = 'HTTP/%d.%d' % self.request.protocol def __repr__(self): return '' % ( self.status, self.headers.get('Content-Type'), (len(self.body) if isinstance(self.body, str) else 0), ) def __str__(self): return f'{self.protocol} {self.status}\r\n' def __bytes__(self): return str(self).encode('ISO8859-1') def prepare(self): # Set a default content-Type if we don't have one. self.headers.setdefault( 'Content-Type', f'text/html; charset={self.encoding}', ) cLength = None if self.body is not None: if isinstance(self.body, bytes): cLength = len(self.body) elif isinstance(self.body, str): cLength = len(self.body.encode(self.encoding)) elif isinstance(self.body, list): cLength = sum( len(s.encode(self.encoding)) if not isinstance(s, bytes) else len(s) for s in self.body if s is not None ) if cLength is not None: self.headers['Content-Length'] = str(cLength) for v in self.cookie.values(): self.headers.add_header('Set-Cookie', v.OutputString()) status = self.status if status == 413: self.close = True elif 'Content-Length' not in self.headers: if status < 200 or status in (204, 205, 304): pass else: if ( self.protocol == 'HTTP/1.1' and self.request.method != 'HEAD' and self.request.server is not None and cLength != 0 ): self.chunked = True self.headers.add_header('Transfer-Encoding', 'chunked') else: self.close = True if self.request.server is not None and 'Connection' not in self.headers: if self.protocol == 'HTTP/1.1': if self.close: self.headers.add_header('Connection', 'close') else: if not self.close: self.headers.add_header('Connection', 'Keep-Alive') if self.headers.get('Transfer-Encoding', '') == 'chunked': self.chunked = True circuits-3.2.3/circuits/web/wsgi.py000066400000000000000000000141441460335514400173020ustar00rootroot00000000000000""" WSGI Components This module implements WSGI Components. """ from io import StringIO from operator import itemgetter from sys import exc_info as _exc_info from traceback import format_tb from types import GeneratorType from circuits.core import BaseComponent, handler from circuits.web import wrappers from .dispatchers import Dispatcher from .errors import httperror from .events import request from .headers import Headers from .http import HTTP def create_environ(errors, path, req): environ = {} env = environ.__setitem__ env('REQUEST_METHOD', req.method) env('SERVER_NAME', req.host.split(':', 1)[0]) env('SERVER_PORT', '%i' % (req.server.port or 0)) env('SERVER_PROTOCOL', 'HTTP/%d.%d' % req.server.http.protocol) env('QUERY_STRING', req.qs) env('SCRIPT_NAME', req.script_name) env('CONTENT_TYPE', req.headers.get('Content-Type', '')) env('CONTENT_LENGTH', req.headers.get('Content-Length', '')) env('REMOTE_ADDR', req.remote.ip) env('REMOTE_PORT', '%i' % (req.remote.port or 0)) env('wsgi.version', (1, 0)) env('wsgi.input', req.body) env('wsgi.errors', errors) env('wsgi.multithread', False) env('wsgi.multiprocess', False) env('wsgi.run_once', False) env('wsgi.url_scheme', req.scheme) if req.path: req.script_name = req.path[: len(path)] req.path = req.path[len(path) :] env('SCRIPT_NAME', req.script_name) env('PATH_INFO', req.path) for k, v in list(req.headers.items()): env('HTTP_%s' % k.upper().replace('-', '_'), v) return environ class Application(BaseComponent): channel = 'web' headerNames = { 'HTTP_CGI_AUTHORIZATION': 'Authorization', 'CONTENT_LENGTH': 'Content-Length', 'CONTENT_TYPE': 'Content-Type', 'REMOTE_HOST': 'Remote-Host', 'REMOTE_ADDR': 'Remote-Addr', } def init(self): self._finished = False HTTP(self).register(self) Dispatcher().register(self) def translateHeaders(self, environ): for cgiName in environ: # We assume all incoming header keys are uppercase already. if cgiName in self.headerNames: yield self.headerNames[cgiName], environ[cgiName] elif cgiName[:5] == 'HTTP_': # Hackish attempt at recovering original header names. translatedHeader = cgiName[5:].replace('_', '-') yield translatedHeader, environ[cgiName] def getRequestResponse(self, environ): env = environ.get headers = Headers(list(self.translateHeaders(environ))) protocol = tuple(map(int, env('SERVER_PROTOCOL')[5:].split('.'))) req = wrappers.Request( None, env('REQUEST_METHOD'), env('wsgi.url_scheme'), env('PATH_INFO', ''), protocol, env('QUERY_STRING', ''), headers=headers, ) req.remote = wrappers.Host(env('REMOTE_ADDR'), env('REMTOE_PORT')) req.script_name = env('SCRIPT_NAME') req.wsgi_environ = environ try: cl = int(headers.get('Content-Length', '0')) except ValueError: cl = 0 req.body.write(env('wsgi.input').read(cl)) # FIXME: what about chunked encoding? req.body.seek(0) res = wrappers.Response(req) res.gzip = 'gzip' in req.headers.get('Accept-Encoding', '') return req, res def __call__(self, environ, start_response, exc_info=None): self.request, self.response = self.getRequestResponse(environ) self.fire(request(self.request, self.response)) self._finished = False while self._queue or not self._finished: self.tick() self.response.prepare() body = self.response.body status = self.response.status headers = list(self.response.headers.items()) start_response(str(status), headers, exc_info) return body @handler('response', channel='web') def on_response(self, event, response): self._finished = True event.stop() @property def host(self): return '' @property def port(self): return 0 @property def secure(self): return False class _Empty(str): __slots__ = () def __bool__(self): return True __nonzero__ = __bool__ empty = _Empty() del _Empty class Gateway(BaseComponent): channel = 'web' def init(self, apps): self.apps = apps self.errors = {k: StringIO() for k in self.apps} @handler('request', priority=0.2) def _on_request(self, event, req, res): if not self.apps: return None parts = req.path.split('/') candidates = [] for i in range(len(parts)): k = '/'.join(parts[: (i + 1)]) or '/' if k in self.apps: candidates.append((k, self.apps[k])) candidates = sorted(candidates, key=itemgetter(0), reverse=True) if not candidates: return None path, app = candidates[0] buffer = StringIO() def start_response(status, headers, exc_info=None): res.status = int(status.split(' ', 1)[0]) for header in headers: res.headers.add_header(*header) return buffer.write errors = self.errors[path] environ = create_environ(errors, path, req) try: body = app(environ, start_response) if isinstance(body, list): _body = type(body[0])() if body else '' body = _body.join(body) elif isinstance(body, GeneratorType): res.body = body res.stream = True return res if not body: if not buffer.tell(): return empty buffer.seek(0) return buffer return body except Exception: etype, evalue, etraceback = _exc_info() error = (etype, evalue, format_tb(etraceback)) return httperror(req, res, 500, error=error) finally: event.stop() circuits-3.2.3/docs/000077500000000000000000000000001460335514400143015ustar00rootroot00000000000000circuits-3.2.3/docs/Makefile000066400000000000000000000060761460335514400157520ustar00rootroot00000000000000# 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) source .PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex 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 " 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 " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @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." 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/circuits.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/circuits.qhc" latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ "run these through (pdf)latex." 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." circuits-3.2.3/docs/check_docs.py000066400000000000000000000016341460335514400167440ustar00rootroot00000000000000#!/usr/bin/env python from subprocess import PIPE, STDOUT, Popen def test_linkcheck(tmpdir): doctrees = tmpdir.join('doctrees') htmldir = tmpdir.join('html') p = Popen( [ 'sphinx-build', '-W', '-blinkcheck', '-d', str(doctrees), 'source', str(htmldir), ], stdout=PIPE, stderr=STDOUT, ) stdout, _ = p.communicate() if p.wait() != 0: print(stdout) def test_build_docs(tmpdir): doctrees = tmpdir.join('doctrees') htmldir = tmpdir.join('html') p = Popen( [ 'sphinx-build', '-W', '-bhtml', '-d', str(doctrees), 'source', str(htmldir), ], stdout=PIPE, stderr=STDOUT, ) stdout, _ = p.communicate() if p.wait() != 0: print(stdout) circuits-3.2.3/docs/logo.png000066400000000000000000000451531460335514400157570ustar00rootroot00000000000000PNG  IHDR5t`iCCPICC Profilexy<ǯ{v c_ʚ]&.dɾeXYٲْVO$*!%F",y~|_g^sssu@xxzLTXZqc?~ s$!ñU^K箥a6i@c?lAd2Cc"0ELM`.?c?rn{{{9{?Wٟ s˞p~^~!P 3}W؂8!tg ׭ ? >.;[1} Wi *l7عUSrb_X( ; '0-d IAÈ'U/bjz^ѝ95@.{gw|=SB"b8 FIicwՕVohjTh!t*t7U ;z&ѧ.sY:sjk+cgkDzƓ밷$9%(<4|~H^hnXv͈  J/F?؝8xDk^IןJYMKPϴ[Ws>vѝ⺒Һ.5&u;o&7ݳ@!#G?O5uU$9B+{ֳѶ/^vpt̽*tbNymC&a/]>~)!f2uƷ?O|:g2jJfjsƴL٦/ _|0'5W?/=oA~yQckt2iy[w;k\?776nno0e v#wwa (8BtP) ye.){4_ lL,l38E x 9ʈ3HI6IG(MVv|lZu> L ɧʍ[LN/Ag- mڎm:: 8\\Ϻ"ΦQ}ϧ7-"P;H.X ·"6#.\ܽ+bULu!>=ƴ􏙒Y/_n+O,\Z[l_rY,|#}`5lMy781SVŶSSq 7 Ft Q[X;09<uAv>a)` >+O_ACCCgÊ]##.\5tQt+F1± vjU\DIĎkyId7S)is22moFet˽O.p*-/Z+n)I.+/QUv9k5=u՗Ioz7}w^ؤ_OO5?nMԮ\ˡWZ;*s_'D z{ar{Apȋяc#m&>uN1|&M7¾#fst[WO c,`@W,cX_S9 pU]C/ ?Ak06! " %A%P#0.<HFT#sH*a>GCPJ('T,ՇBd-+:cb& Xml.v'#qN()3(ԨVFZjj_44!0GkKN^AQIYE [UΰqsqFp qrGH|8tW&!.*tXJ!'V,/qȔd%iG7e=%+-߯PtIvw/jcCÚt6p Lo45;hndNJƦ?P8Iquiv% z{z; tZ@r QnJWhcƮ'${nLIgɒ,\.a(0 .|޽UM -tTt ~50J}ec^][Lٶ?dx> >H2\0(ZhAFh"(D1D!בa."rPmE4+Z Fw70BKL<=uap8s\&nݍ%Қ@IՅoR;QU&2hiiiS003>f:لyœk aNK8r987ʹ-y<-x!  #DN3Ky)Y"-zTGFqEfJT%Z'T$Tyx9485ĵtuHA EZ>NZ%7[l[*Y]~nbba]!7]R9 [>OɛAoϫ<(~E0xkɪWS3(3nYKR}-i++Vu:6>!#&'-TK^i 83){|Um+pT`B@? @$ (Bt"V\H=dP({uXhQ&C`^`)t0.MMB1HBYMAG#483VΓ6 c*$s, ls ZpsZrqpMp%n~-[b%f,HJ9rx]*j+Z: zNFMJO/Z,Ya4,~G礷=?.3AM0ȠQgP_U{{#.ٚu)G+wVM]Eq}yVE~:; ==h|B\m۳"ݶ=o&z Hl=}6&7^7)qrts>r˖* ~]q\UYZ[[a)9nw[z{i׍=K{ k9x/'S߳>d97gWwM}vvT>iYG]}v2;>ۿkOLb?/lyټ< c:L8`h9G_[]5o~n$2 gg7qz3tU=UUVVHHBF ;dauHHH@"@ @ >N$@$@O S D$@$@ @ >N$@$@O S D$@$@ @ >N$@$@O S D$@$@ @ >N$@$@O S D$@$@ @ >N$@$@O S D$@$@ @ >N$@$@O @nPZ/vɟݻwU @ӦM>={t$@$`@RѣG?? .'8ٟmPuί^qVNDϲTjQ*URYj&V]dPbjTN,Ŀ85)PPV,!SңQ)ڷsӉ.^8B_ԬYc}p/ 0!}q0`xAAŝeiSۤ,"2K3Ԥԧ7DT G&" )ʓ; kX*d=.'IيT+)UCJ c|)STA"gA [*ar.m"Y%r>dRHId"=+޴igW ~^x 1H@O7d8t~!%TQYUp@S41g| k(q-**#gTR\Rz']up5K/xG$@O[nLz.Ŵ"5 Ib'U'>2:TG}ٳgө+( 3>'8D`XoÆ fo8\0fID.H0,N;h>8ĻGZlwLJ$@Z>?C{ ntad8U\g>2˦F3I$  C:es:e E ~)« ƥhXm d4=L;&S^=Xɚ{(ʫVkt>]ݗ4?L+KATB$yjݺ^?Qwd?%_?}  ܕno3 !ӃI!]$?atg??bg -O<',r*K[!nG/cjPI ǽ:+}8R~ܗ:4O,/Ҙ'dʥ~3%xd}M?ޜZ[[G\|ءl* @piOnyR9M3cJ4y,ҐyEb/\ư~jjɦ$ piVndÁ Kb~ξXK]x"6 d{>U;C\3YiؿQx•`On66C$@ ƿꤪ` \wS/`IBB}2Өr_9dw7Ն^ Oe2;Tܔ|<"H<6 UPPrG%r?Qu4$)1 KW??[ lU|SAty()F@pcLέe(i]o].,-Ku M~Nj :Vo-x^A+k!J @c!tvN[XY4I]W.;,Ns]fwة5+m6Uڔ $cmaMQ4fd}8M+>ɂAiP;uB}^4& O\T!35?譧Th0N~x!Q3ٷP. QYU{z8+o2^Î5S @>|'T֊q*j[U6ґ;-_ڰ{=;^ivʓ @9bC`R$(' s̉;4h|X7HQ٧gh}"}"_3ߗgE,c5I % f~+܄-//+ϊ7|E֩נvu`ψy |˪lˍLC$']牧[q,S+]F۟פ0;;+'+V.ّYYKvYfZ1$@٧'C.8B8]o43uW -kZ+oYkl\aa11$@$oPgi}3n91d-.\vYC%5;Z d*`g3඗zgV?s4lg#{qs;Ub|wCj֑Ɗ؞.csՀ[וewZR&߯hyY?x<j*5{L/׭4?wȐ!$ v<U{`殮(Aұ>a/r=$3'J~?y[%Y^KFL>9ˆ1$@ SS*5 ԰Ղ7\kHgJ"}W-fum{۵HyV/~8B5Fr9H2@.7?ϋ2Uգ`xGV>j)IMEMz{|g-zCM:>4wqn +ȑ# @)a_$uӋ⌶U8zZ{rB3fbV,@yq45%&2=WR`pU!Kq./J&DI^ O)1KBI@TJ~s1JIsO\`l#'kAgNܿf7t}&HO:yn5s5*D3q@xFppOi$Oޠcw>1N9۴4=,Q~{+/esgUr];rݝ[\lSط#C;uk]K50ܺ>oG1g$ (R>5Gi{iw)i tV~#'̝$"gmHf=s؏Է3^Ǐ$@I e8a׾#dž2YtF|@<|`\6(KwLDG)b̉Ә1c8)3@H 9']ϩMjᐖywӼŘ[~}.oVrέJv0"؁/&OsUf! $"iǫojyWήZT|\gYhg' N2x -OX~;7N㤽 ڷa) ?'G|D$%iSko9IȗW̟Q4=q}^x'FT -'$M?u֞ 8sw_gpw'b$nrmUbSGN9?W'=(m`ks+WZlT+U B-j-)TbjD=FX>^yrLYy-'tU8S ˗=pp9%؇)FI}:?jkW M:G(ݳ߱^S͋r%s1g9egKIGaOMvOK$t?}JU*[O SaOj{*UaS:g.ؼf.6!W53;ŏB()c<2}4z]L^ENbPL%$yiWZrSM6E8r):ʓ HswT}6aʂ.`^zwN.1 @:'An`|AI8]f͚PXXn:Ea#G֯_o@Qߎ j* S80KJ j\ &)v<0eG-&Gw-)/IHik.8\Tds[`]h`|&Q K =SóE+{1eA<iR _>X-S<>' HO4/:' aGg5HHi)-MkNd$@$NihrO;sO0!" 0HCtac;1} XC$@%)Y1  h쳢$ @>%5K" O>+J $SX$  h쳢$ @>%5K" O>+J $SX$  h쳢$ @>%5K" O>+J $SX$  h쳢$ @>%5K" O>+J $SX$  h쳢$ @>%5K" O>+J $SX$  h쳢$ @>%5K" O>+J $SX$  h쳢$ @>%5K" O>+J $SX$  h쳢$ @>%5K" O>+J $SX$  h쳢$ @>%5K" O>+J $SX$  h쳢$ @>%5K" O>+J $SX$  h쳢$ @>%5K" O>+J $SX$  h쳢$ @>%5K" O>+J $SX$  h쳢$ @>%5K" O>+J $SX$  rRH>ӧ/\pݵj"$pљ3g.[رc7߿UW]ΪAh& "~g6lP5k֬ϵ: >oQ3gF"|nݺN:/OJK#F@I1 {wftIL x/~q]m-[>|sM/wen0r|Qi @0 x%3{aD)Sx- ?)^[ly78"Q'xBz9{l]d&_?">?<J_}UA*BK`Ϟ=+VT7f R;nEuTi aRST5INwmIHO*S8k.ڷo/hJӦML -mۊ֤IS]}rYH .{7nZ$2rHA*BKޡC w 7R@ڧ*u,-Zq#N[n1U#+Vb,p*?~wWR2O𼀉ʜBcոj7։C㱥lʰ> \|)P{hzk36)8p :R0TdMݺu3'U`U piΪݻPYI (GIwN)i^sw#Is|-YW% жV<>x\b ^za艣:J6/`%Öe6}6m֭[wmWܹ3b7oѣGRS \d -..V+v|5x<_ʕ+9-P/֭CtO[<0!YU|p"Х}#(l)S5k,,,2#vډ@f:?ofsMb qݴsOFJ _r%⌁,7nQڥ~UVqZ8ZԢgڜL@yhMek'~_Yeeea1cn{i⛅Eo=$+곲"~"yy0U\CCCEE>c0 oU塇CСCP{#'۶mӦjJ|&!2EW 6Fu]O RrS3z ! @(S*aS Aޓ :IC%~zסGH'D~ڻqBk KDmDJ x`íkPyDe}R(+VKp_=)_>]GxmA(4S7)mؼLLgQ&1Dy }RMȎmz` Gr[k0FO0;Na] 4੽f_TQ+ID3d ^df|r& I\syv=JJ5LS{ni}M#6LiYKΖ,;W_`iFFz$'SRJW*p GCv˓5`)='̹y͛7/⛒X8Ģ(G1@3`>UsZD}OrgGjB8WT HG?r(;+%)*D$EgGӢzR$aBHRRS8A"gE8[*arlM"Y%TWHId"?Y{|<_IUwT̚8zj׭[ 4c/@sΝ1cKYzXD[)nnDୗa~Ɗ/z7tSr1ֳ3QF,attUvPh3#aOwP0i}n{JπȗIDATM0h蜏޵T>~Z~2j*Va7qSʠ3EaTcǘ~ ⚴lٲk׮K[<4+I,_|Νl^Jӳ/I5ި/U{j1$S!'~,bh< RoeEn"U^=Iawp#ү/H`8O t* l$˧#Q&MRv4CVgرxNh!nu#ţ8Ph쨮=裎'ɹDxe'!Oس!^^(]X5l#TSe˖aƀ;v@uuJ^䔱 @Ngc[_*ᫌ3<+xjDNM@C6cÀ7?at[B0Z;=;vsj]J\ 688ň*C )`[jwjV,OacQn/y֫WzL]؈{ MFŲ:u GO>k 郞imrdJADّ/WZ!e'NabߵW mXbpϒаHP࿇aG% >̪8Ů|`Vm7Eh4Թ}6\KL#T%|B?$3Nk h*zG}$VĭΜB ZkɓnR<^/?q[ NmW.R[ň_?N.E*J!^ԍ:Y_:ÇO]J%K&:8>eiV5:up>`CX,=ژ1Ļ9}, ǞJTwoH[<*;r[QecE %R=ɏj=٣ygE9G#'Pg\#*\5ZsaIl4Iu&N F/դX!"i%)X iVTb9ERy^\$ (f},4m{Sj$_3 ^(6O>}{éA%7T$a#]1ևOߊ`@~C[O)$Zإ"v8] ++7KVӪcW>AD)~R*#KJr$JP(R$aBH"-Qu ,paI#]yA*G$A<>8CV)!G!Fzkoܩ}|*8p@seoU`!E|qKW"79Ivv\41|) [P*=܍5.UGc+HQnI۫wӦz|mJ*bⅴ 0| b'G8`Swmڴ|Ayߟ|UͮU+p|N\k9qr* vL~.v %]j]u[eb\zGpQ;U 6;}Z݋=5Qr_+6a kFƫ ƾW~\5O)'tYy\t(ATS(cL >B%Y "h操K^ϋ8ޚ<$@v7>So]8P)?Lh˖-.zNv;`Qq}F=Eo(mHaٺ~8P+Vp LuŶ,I<Ǐ&'tJCR{slm{INa}.>`: hT[T^ērcqIO8i)+Eݪ[nsҌ29~|=  J͎7}{?>ۧx4h}~xӦbiڿԴ'7Pi-,O\*^akuĉi 6 : #~0Tp<1^no 9=iP]Ģ;JPOvGFݦ9UD*9]4ByWsaeC$YR7n ,'mGD7{2RDQ92*Pr7Ӭlo!IV| 4mOob`|k?CСC8*xO92i$|`fiH+&ݑnFT4r00<5=ܰaǗ_5<ܯRTOؒp@ LCbp1GQ} އ8"BxsW#w~ mt^$//OPJE,$s0HKl?<Q7pi<<6yMsdݴSi*vĴ2"bN -.]UV%zaW/"nڛx(qnw>v>oRZ::K]ا]=bLN찆c+rU?8M]ϝn>)abIvl'C<넘!ݱ|.HE;z24;wc50)e&B{3B!hh3Z˛6g'Q0 Ce$O_-YįӾ}毞T'.E6?R[ (voUzXbdOUاO5l:uiHR`Fq9*qk׮n6m L8!]r0kQؓŲ]R>8AvlШQ#q[틉Px,hذa⚄-SP]ӽM.0T1NL=B@o4}]L*'4QRNjmv=N{LJ?SFixHQ^ҐW<g? l-bҤIfw{MP'|R4Kxm.0+4iZO]a r)-   > ɫbPQKLS2⼌5Ϋ\@ǰKPAbD+eCUSu\٨k,/5qR0z&ŒI&l+_+HyL\U IEi1u1hʶQQyXv|ߣu#XHӄy|y+3[,<-ǂ, u:Cr ڂ6zJYêwyμ_X&;"Z1a-~H*ghu1;Nb2qsP#9uC,f%pYҌgΜQx)|1Kkc^g<~{#۶mSk<e=쳖[j=_ک}zk׮5nX^Q8=&5պa .jġᆌ7t=ܣkRAL ξ(G>ڢv rI%2MJp P'B6[O8L x/T6mڠd_ 2m۶˴p&զx#Ukz7#=VA{| gtOWfsp)Ũر¦6bXބJƌ!0^e9oSM1 vyttYPł'ì20-z"/F]( gހ[R'ib8g&uۘ1$! xC"9-Io⵺k ڵ3bU&ǂR#IA+!͛77*Kk Z }VdF\BEnߓ'(xh~N+3f %&jɴ12a[n@0ۆ32U>} XL.P'wRQ$<0Gz/z{BBU|:%;އ*`^9XW3}kU?~/nIO낏6&cF0!~Isc_vޖ+رcCEwxdXjHLZVz .\MK>ֆKKutD߯o޻1opҳ@J؈Ԗ@u7o.+kѢN8󴗉H: D8ݾy=u^UF|pu.vV9Raz 3}nXp8x9QCb;xZ1i|$vTV R a,WF X_-o <`eFa?0Y$hmSW7(,z5ki}6o\2Gd S@aKiP$m+a aw fMֆi4^0R+}KI6 j!SY8 @IZtF 9's.% SY: 9's.% SY: 9's.% SY: 9's.% SY: 9's.% SY: 9's.% SY: 9's.% SY: 9's.% SY: 9's.% SY: 9's.% SY: 9's.% SY: 9's.% SY: 9's.% SY: 9's.%  ,G;h(IENDB`circuits-3.2.3/docs/logo_small.png000066400000000000000000000251241460335514400171430ustar00rootroot00000000000000PNG  IHDR;LDiCCPiccHǭuTT.I FiicHiBR1@@1TARPR A]}:k~>~Κ}^p|}=a/o2Y KJ_%]c=q.1ޝ8Ԫ߬OpS?wФ=( LO/69`/nwsp"r8MT\~/~uAx{@Q{;{^$sx0Sދ D^!{qD>\_% @tĝz&?c̍gMhM~tbꝎ9>>?/1Y/_/,5/-w|Xu[] -msfw[[>??vvvh[8 <= *YO!hi/3 <򎭑h*e^o>{-AI!&hIRI2UO3+Y+|PcR7Ѹ٨53'I4(7l14^2̨͏*~f3amO(t2uvt9w.gWw}r,@ `apd؉܎ڹw ]Wǂk8x7$ԒRR-l32ndiܒ+_RD^[P2\<"ҼJk5zfvnmCƀ64?d|}h@P|xQKaދ7/=}%A ,VozǍDBer *.u=m(IFnfxMlD|nw޽[1ێEabe`lk!oy m^rvv_Pc̟7 Rp݈Ũ'.G\=wbpč$tLҬ;qVdE_J*nT] kol{i^sW 3y{[ЃG4+?M~"Xqpsgt\`{3(Jjnh(+upx0B;D2HA#&EBŠ^I 88& MAJ2DDZMJEb08=Rʋm24},0USL ̅GXXY3,Y'99Jsvyx+ĈGy%u JI֧bJʢHUT4Zشeuuc *Xd8$Mg״}kՠMwp]%)&&RbXAJ\.=115RFέ۶E$fe;Uw6jR66f=pz})Is3m_^ tLwt+w{/1;|TkD$OZsٿ..X[.[\5[ؚúf-cۋ?vvwW*S/ 3&5z;!~b/7fwu䠪CTW7 ~}ϟ߹wP3G7f?UGӝ~3ǔzR .琟ywW7v rA_5缽<[ cHRMz&u0`:pQ<bKGD oFFsgB pHYs   vpAg5#aHIDATx{\WϙD@R'twV-\ b]m>ĮPX~]]h烷m[hKbV%G}DQ5J1a&d}ə3gN2_Μ93n#eL&l6h4~z%f3 ß? z>---===**ǢOF1??߿ Z6'''33s% E/i&8&h47 a[2L6m2ph ǃ['f9555##fJTe222@,!hJJJt:̃e2222t DŽO !r|=}, c`t,9f .=0B4ffhDصεbphg}ջE$ ?2L)))8P5g~ӢbGƒ_ sYman1B7pN(3cnu"+.p\EW zdvK(LVџ 0A`A`pc1F H`ǺAJAA.vĘtk,Vq kx"0&0&○(q0AysKVVVr_/͖ Yb6jkvN k"1qwCCdmm>ċ/? - YrEږG/P\\,Az{Bj֒a 'G'4 īZ_* 'ǀ;9 :Zڳ;v{dGL +DaǐѪIZGJR~x߿ۯaÆ2VfEGG^-&|),*B%GEܸU_s:dD*[/ 0H{{9.vaKXP<u !dXq]EO}q.nfup82Qt]fC TE\EnhlnnvOqUTTt%&6+4"9Aݢ]uۙrIvԋFPP$P/]$1M5nf_nB}--3|ձPO*1u6v"oh==ݣT^j*?JOL^=*Y-~یS#^E\ a&P=gP"ܯ$!}%oW<>!"y9|@d<95T"\H_UŮ)ɫ t,H /ѯazEvEQTR[5W-IqU/1,ѯ{ᯞ01LڽZ._}p!''OOxq_mq1aZ8Z 9QƮk*z]0<$J^흦TӞf,9Kn7[O[:v&!!f]j|X,9i˿ x6%a-}.{y:h'?>YWrJD "Fq! cWmBOLԟ? #r933_p~="W"].*͹(A*BQ?IA`!t cK 0&g߾;?VeJ \9ZC]?w1a:bܝrJ19I,,,A/; ^:ck} u51y)a yhx&3u%"2 :QLAwdv$4˹. LQe,BZζ`̼GO-VK{-88(:::'''%%Æ~j Ѯxp]A0rvE"_Uc݀DeϞ=k׮-ϛB戇;au5U;QCTgXn1GGG rJϋ^0PmMj_ KZRT9OGGGJJJss3w4zNeqE5GkJ V]n(aݻ]'KjDSA.aTC5U}3_LoF^(.(䩉Xu5U9[<(X7!{B&r׆R8lg9a`Jdo?Co͙eu @}に0+/fu1'gp:ح{hn+WsW[vn[MEhf gJjVb=$pu1; a_rO[0H9jF >&JR/; w W~n|TT"T$ɬ,V7G/ZR >~"',zx`/Bm9:aUru ģ& ժgcXAAoȗzⰙI/wo E߲L O>Fcs YQd'*HT)k^ݶ1ΨQbbb?.hb|65k~鱷~D۷<Μ9Cܼyرc7o šv!R(g:)k]pRt5T'>}$k!_[fߵsI^?U` /}Ϲ'qOhZqdć:[9sG_h@0nN/_>fF{F1 3 @2k3 {C`t̡ nB(--=EsjvfYfn<d2eggC3ɻu:]mmmAAF4]NQQQQQ"vinn6 U/wFc}}f3L6_䭷btIp'23I'N`|рNNNV+v0N2r1 mwgϳB d7L:I Bbр^ `d%tEXtdate:create2014-03-07T23:13:57+10:00Mg%tEXtdate:modify2014-03-07T23:12:40+10:00)ߊ{JtEXtsignature9d14811ec09a04aaeeb8cc278b7646b54cc4f6f403a5580cce42dde4bacfe5c1YwIENDB`circuits-3.2.3/docs/make.bat000066400000000000000000000060071460335514400157110ustar00rootroot00000000000000@ECHO OFF REM Command file for Sphinx documentation set SPHINXBUILD=sphinx-build set BUILDDIR=build set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source if NOT "%PAPER%" == "" ( set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% ) if "%1" == "" goto help if "%1" == "help" ( :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. 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. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter echo. changes to make an overview over 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 goto end ) if "%1" == "clean" ( for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i del /q /s %BUILDDIR%\* goto end ) if "%1" == "html" ( %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html echo. echo.Build finished. The HTML pages are in %BUILDDIR%/html. goto end ) if "%1" == "dirhtml" ( %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml echo. echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. goto end ) if "%1" == "pickle" ( %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle echo. echo.Build finished; now you can process the pickle files. goto end ) if "%1" == "json" ( %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json echo. echo.Build finished; now you can process the JSON files. goto end ) if "%1" == "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. goto end ) if "%1" == "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\circuits.qhcp echo.To view the help file: echo.^> assistant -collectionFile %BUILDDIR%\qthelp\circuits.ghc goto end ) if "%1" == "latex" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex echo. echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. goto end ) if "%1" == "changes" ( %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes echo. echo.The overview file is in %BUILDDIR%/changes. goto end ) if "%1" == "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. goto end ) if "%1" == "doctest" ( %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest echo. echo.Testing of doctests in the sources finished, look at the ^ results in %BUILDDIR%/doctest/output.txt. goto end ) :end circuits-3.2.3/docs/requirements.txt000066400000000000000000000000321460335514400175600ustar00rootroot00000000000000Sphinx releases pyinotify circuits-3.2.3/docs/source/000077500000000000000000000000001460335514400156015ustar00rootroot00000000000000circuits-3.2.3/docs/source/_static/000077500000000000000000000000001460335514400172275ustar00rootroot00000000000000circuits-3.2.3/docs/source/_static/logo.png000066400000000000000000000216601460335514400207020ustar00rootroot00000000000000PNG  IHDRy7rDiCCPiccHǭuTT.I FiicHiBR1@@1TARPR A]}:k~>~Κ}^p|}=a/o2Y KJ_%]c=q.1ޝ8Ԫ߬OpS?wФ=( LO/69`/nwsp"r8MT\~/~uAx{@Q{;{^$sx0Sދ D^!{qD>\_% @tĝz&?c̍gMhM~tbꝎ9>>?/1Y/_/,5/-w|Xu[] -msfw[[>??vvvh[8 <= *YO!hi/3 <򎭑h*e^o>{-AI!&hIRI2UO3+Y+|PcR7Ѹ٨53'I4(7l14^2̨͏*~f3amO(t2uvt9w.gWw}r,@ `apd؉܎ڹw ]Wǂk8x7$ԒRR-l32ndiܒ+_RD^[P2\<"ҼJk5zfvnmCƀ64?d|}h@P|xQKaދ7/=}%A ,VozǍDBer *.u=m(IFnfxMlD|nw޽[1ێEabe`lk!oy m^rvv_Pc̟7 Rp݈Ũ'.G\=wbpč$tLҬ;qVdE_J*nT] kol{i^sW 3y{[ЃG4+?M~"Xqpsgt\`{3(Jjnh(+upx0B;D2HA#&EBŠ^I 88& MAJ2DDZMJEb08=Rʋm24},0USL ̅GXXY3,Y'99Jsvyx+ĈGy%u JI֧bJʢHUT4Zشeuuc *Xd8$Mg״}kՠMwp]%)&&RbXAJ\.=115RFέ۶E$fe;Uw6jR66f=pz})Is3m_^ tLwt+w{/1;|TkD$OZsٿ..X[.[\5[ؚúf-cۋ?vvwW*S/ 3&5z;!~b/7fwu䠪CTW7 ~}ϟ߹wP3G7f?UGӝ~3ǔzR .琟ywW7v rA_5缽<[ cHRMz&u0`:pQ<bKGD pHYs  rIDATx\{tTչo3{nI,jD< (eEZDBEAl*.ZP+WYg\!b * !y̙ !UcE5̞3}`8+BGrwm۶m6""~ tŚsn۶& 44MطCoYXscnҷK?>XmFp>,yJJ?ф^o0>q?ĶmY ø^ صm"B(\bɓ'nǚs.x-[N2?IC$<1DDD@B$" F!!JD$ʌ$BCv<NEF;ZwxZ3nn4MϷx~~Pb `#GvnBdHڱ& 2"Pb( %Bš0[@%Wt9@jXۛCm'+**222bl*ŋ9犢466ǭ3ܤ+nFn"XHLR lfiMM&Ѷڏ3mML;<;$)9CX8%5#7t?Fcm]meLe7Z{ضe\\>m<(N@ "ی#&);\uؙ Ĥ֖9ɼ썒K]"N!"zstSZv>fŕ́ :5vf`BЩ*l\R{o1x} @Rz~ul۾Pig$&C-=AD1I(2p֎5ID(!I %BL(1rH4ejw -;}uڵ{=N۶` ,6!p޿:8F; 2RSfՈADI6 GD`#Z&#4%B%9]~Cz}g5r&?33{T腪sn?sފɒ֏ݎn!#&pܶD4wSmFdU;?ʫ'%$$Vhin2ƘeY$}[H7-Bs&m;;MRB?]"LH"ܶ8!2 %8HNyh_y巒HHΜv}/fN+]GlokF1eceFl %Eb-F43Ɯ$&IHJ q69fQU Swoxp_̼c/^O+WM,G[` sǶ@0 +_ &\@`ȹm!"M<~dq+F0'==p@[ #a{6`vBbҵhܸ4|ּ=Pfn*_z}Ӵ?MӾ'@ypȑ?qӂ p>|}[9oHdcpɅE{O.b{rޓX\ĺ今eYP%"$֜s׫DwAD+I*WXȍ|/pl۶p|CpieeemmmnxTU$۶%41v}Me9cN2E4"]q;B,KU'(?~? ˲(/¸q-[(R9 䭭Dt]*kJdTNgKKK u4ΡeYDp8db1q󩪚"Ą(BDXLܶToZ뺨Yw;; 4ɶ@ AQ1[\.+eYL4 C$kQ-ۛy_گ&#aΝ;w˖-G~'dY 5kfCCC(RU5==iD&Lp1ߏ"icǎE" YEh$9~>/##Cc@9朻nDm{ӧOOKKs?@fy HLL4Msuѹ  -CKp/We>i}T0Pgxvf-ѣ_m&[>s?|EEHpr- ,p\eeeVJLL\lك>#ȲhѢH$|{キqٲe4M͛7oƌuuuҥK(ȶmYXXm۶3fꫯ61~ǖeۧM ^#G?8q3f̘zo߾qϽWh$)v,h1%iA6ьj]DCGQCGEI "BnBE;w> >oP۴iӉ'6lؐ~Æ ^{?v hnnnkk?pdeeח?ӧO_~=򗿌󏠗;wx 6DQ˵i&1xc9G7r92}Gk׮ؾ}, h̶:gΜp8i\`Auux)Q4 ӧ@ QX,g"?~VVӧhС7onnnKpo5% ̽ё>X)?b^FmniTj#?D@BB@c%Iڿqq… h޽m;m[4Y  >bk֬V8rˠAyfyVV7D9躎/HC0 Ƙa`mpX|>m۫V߿W\!LAQL@ּØ!fԝ3"0NezCC3n'_$.(Q'Hf$K$K!j;y?ok=p5ט)IRMMm ^zi2(X(2Mw6l> >Oxa<^,; .\}~{Сcƌ;v1cxh|C $I(! nYNn譎 Fb܊!IN+j'iDvBYI쐘C"EfCr:"cfeOyO|G%mvwYb# F1 B\N#cH7|{R4ho~YfE"3z-ZsdA)?3T)f³q@lA⥰,Cx}tĉ@(a]B`.舗]BgA;"r邎 8!P(4eʔ',#@G`0HD 8p=ܳo߾ٳgWUU[nv4-+/΢o(O3An܈G5\\C\grwjo0BI0 '{:[u;D=3榛nEnnnrrS6oEADLҿeY,wv9"!B.ZiZ<;p/ޡ(-smvG$.5:FPX{K_"޿<~.=O-c>wT.b{rޓJ@|| i%tEXtdate:create2014-03-07T23:13:57+10:00Mg%tEXtdate:modify2014-03-07T23:12:40+10:00)ߊ{IENDB`circuits-3.2.3/docs/source/_static/rtd.css000066400000000000000000000356261460335514400205460ustar00rootroot00000000000000/* * rtd.css * ~~~~~~~~~~~~~~~ * * Sphinx stylesheet -- sphinxdoc theme. Originally created by * Armin Ronacher for Werkzeug. * * Customized for ReadTheDocs by Eric Pierce & Eric Holscher * * :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. * :license: BSD, see LICENSE for details. * */ /* RTD colors * light blue: #e8ecef * medium blue: #8ca1af * dark blue: #465158 * dark grey: #444444 * * white hover: #d1d9df; * medium blue hover: #697983; * green highlight: #8ecc4c * light blue (project bar): #e8ecef */ @import url("basic.css"); /* PAGE LAYOUT -------------------------------------------------------------- */ body { font: 100%/1.5 "ff-meta-web-pro-1","ff-meta-web-pro-2",Arial,"Helvetica Neue",sans-serif; text-align: center; color: black; background-color: #465158; padding: 0; margin: 0; } div.document { text-align: left; background-color: #e8ecef; } div.bodywrapper { background-color: #ffffff; border-left: 1px solid #ccc; border-bottom: 1px solid #ccc; margin: 0 0 0 16em; } div.body { margin: 0; padding: 0.5em 1.3em; max-width: 55em; min-width: 20em; } div.related { font-size: 1em; background-color: #465158; } div.documentwrapper { float: left; width: 100%; background-color: #e8ecef; } /* HEADINGS --------------------------------------------------------------- */ h1 { margin: 0; padding: 0.7em 0 0.3em 0; font-size: 1.5em; line-height: 1.15; color: #111; clear: both; } h2 { margin: 2em 0 0.2em 0; font-size: 1.35em; padding: 0; color: #465158; } h3 { margin: 1em 0 -0.3em 0; font-size: 1.2em; color: #6c818f; } div.body h1 a, div.body h2 a, div.body h3 a, div.body h4 a, div.body h5 a, div.body h6 a { color: black; } h1 a.anchor, h2 a.anchor, h3 a.anchor, h4 a.anchor, h5 a.anchor, h6 a.anchor { display: none; margin: 0 0 0 0.3em; padding: 0 0.2em 0 0.2em; color: #aaa !important; } h1:hover a.anchor, h2:hover a.anchor, h3:hover a.anchor, h4:hover a.anchor, h5:hover a.anchor, h6:hover a.anchor { display: inline; } h1 a.anchor:hover, h2 a.anchor:hover, h3 a.anchor:hover, h4 a.anchor:hover, h5 a.anchor:hover, h6 a.anchor:hover { color: #777; background-color: #eee; } /* LINKS ------------------------------------------------------------------ */ /* Normal links get a pseudo-underline */ a { color: #444; text-decoration: none; border-bottom: 1px solid #ccc; } /* Links in sidebar, TOC, index trees and tables have no underline */ .sphinxsidebar a, .toctree-wrapper a, .indextable a, #indices-and-tables a { color: #444; text-decoration: none; border-bottom: none; } /* Most links get an underline-effect when hovered */ a:hover, div.toctree-wrapper a:hover, .indextable a:hover, #indices-and-tables a:hover { color: #111; text-decoration: none; border-bottom: 1px solid #111; } /* Footer links */ div.footer a { color: #86989B; text-decoration: none; border: none; } div.footer a:hover { color: #a6b8bb; text-decoration: underline; border: none; } /* Permalink anchor (subtle grey with a red hover) */ div.body a.headerlink { color: #ccc; font-size: 1em; margin-left: 6px; padding: 0 4px 0 4px; text-decoration: none; border: none; } div.body a.headerlink:hover { color: #c60f0f; border: none; } /* NAVIGATION BAR --------------------------------------------------------- */ div.related ul { height: 2.5em; } div.related ul li { margin: 0; padding: 0.65em 0; float: left; display: block; color: white; /* For the >> separators */ font-size: 0.8em; } div.related ul li.right { float: right; margin-right: 5px; color: transparent; /* Hide the | separators */ } /* "Breadcrumb" links in nav bar */ div.related ul li a { order: none; background-color: inherit; font-weight: bold; margin: 6px 0 6px 4px; line-height: 1.75em; color: #ffffff; padding: 0.4em 0.8em; border: none; border-radius: 3px; } /* previous / next / modules / index links look more like buttons */ div.related ul li.right a { margin: 0.375em 0; background-color: #697983; text-shadow: 0 1px rgba(0, 0, 0, 0.5); border-radius: 3px; -webkit-border-radius: 3px; -moz-border-radius: 3px; } /* All navbar links light up as buttons when hovered */ div.related ul li a:hover { background-color: #8ca1af; color: #ffffff; text-decoration: none; border-radius: 3px; -webkit-border-radius: 3px; -moz-border-radius: 3px; } /* Take extra precautions for tt within links */ a tt, div.related ul li a tt { background: inherit !important; color: inherit !important; } /* SIDEBAR ---------------------------------------------------------------- */ div.sphinxsidebarwrapper { padding: 0; } div.sphinxsidebar { margin: 0; margin-left: -100%; float: left; top: 3em; left: 0; padding: 0 1em; width: 14em; font-size: 1em; text-align: left; background-color: #e8ecef; } div.sphinxsidebar img { max-width: 12em; } div.sphinxsidebar h3, div.sphinxsidebar h4 { margin: 1.2em 0 0.3em 0; font-size: 1em; padding: 0; color: #222222; font-family: "ff-meta-web-pro-1", "ff-meta-web-pro-2", "Arial", "Helvetica Neue", sans-serif; } div.sphinxsidebar h3 a { color: #444444; } div.sphinxsidebar ul, div.sphinxsidebar p { margin-top: 0; padding-left: 0; line-height: 130%; background-color: #e8ecef; } /* No bullets for nested lists, but a little extra indentation */ div.sphinxsidebar ul ul { list-style-type: none; margin-left: 1.5em; padding: 0; } /* A little top/bottom padding to prevent adjacent links' borders * from overlapping each other */ div.sphinxsidebar ul li { padding: 1px 0; } /* A little left-padding to make these align with the ULs */ div.sphinxsidebar p.topless { padding-left: 0 0 0 1em; } /* Make these into hidden one-liners */ div.sphinxsidebar ul li, div.sphinxsidebar p.topless { white-space: nowrap; overflow: hidden; } /* ...which become visible when hovered */ div.sphinxsidebar ul li:hover, div.sphinxsidebar p.topless:hover { overflow: visible; } /* Search text box and "Go" button */ #searchbox { margin-top: 2em; margin-bottom: 1em; background: #ddd; padding: 0.5em; border-radius: 6px; -moz-border-radius: 6px; -webkit-border-radius: 6px; } #searchbox h3 { margin-top: 0; } /* Make search box and button abut and have a border */ input, div.sphinxsidebar input { border: 1px solid #999; float: left; } /* Search textbox */ input[type="text"] { margin: 0; padding: 0 3px; height: 20px; width: 144px; border-top-left-radius: 3px; border-bottom-left-radius: 3px; -moz-border-radius-topleft: 3px; -moz-border-radius-bottomleft: 3px; -webkit-border-top-left-radius: 3px; -webkit-border-bottom-left-radius: 3px; } /* Search button */ input[type="submit"] { margin: 0 0 0 -1px; /* -1px prevents a double-border with textbox */ height: 22px; color: #444; background-color: #e8ecef; padding: 1px 4px; font-weight: bold; border-top-right-radius: 3px; border-bottom-right-radius: 3px; -moz-border-radius-topright: 3px; -moz-border-radius-bottomright: 3px; -webkit-border-top-right-radius: 3px; -webkit-border-bottom-right-radius: 3px; } input[type="submit"]:hover { color: #ffffff; background-color: #8ecc4c; } div.sphinxsidebar p.searchtip { clear: both; padding: 0.5em 0 0 0; background: #ddd; color: #666; font-size: 0.9em; } /* Sidebar links are unusual */ div.sphinxsidebar li a, div.sphinxsidebar p a { background: #e8ecef; /* In case links overlap main content */ border-radius: 3px; -moz-border-radius: 3px; -webkit-border-radius: 3px; border: 1px solid transparent; /* To prevent things jumping around on hover */ padding: 0 5px 0 5px; } div.sphinxsidebar li a:hover, div.sphinxsidebar p a:hover { color: #111; text-decoration: none; border: 1px solid #888; } /* Tweak any link appearing in a heading */ div.sphinxsidebar h3 a { } /* OTHER STUFF ------------------------------------------------------------ */ cite, code, tt { font-family: 'Consolas', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace; font-size: 0.95em; letter-spacing: 0.01em; } tt { background-color: #f2f2f2; color: #444; } tt.descname, tt.descclassname, tt.xref { border: 0; } hr { border: 1px solid #abc; margin: 2em; } pre, #_fontwidthtest { font-family: 'Consolas', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace; margin: 1em 2em; font-size: 0.95em; letter-spacing: 0.015em; line-height: 120%; padding: 0.5em; border: 1px solid #ccc; background-color: #eee; border-radius: 6px; -moz-border-radius: 6px; -webkit-border-radius: 6px; } pre a { color: inherit; text-decoration: underline; } td.linenos pre { padding: 0.5em 0; } div.quotebar { background-color: #f8f8f8; max-width: 250px; float: right; padding: 2px 7px; border: 1px solid #ccc; } div.topic { background-color: #f8f8f8; } table { border-collapse: collapse; margin: 0 -0.5em 0 -0.5em; } table td, table th { padding: 0.2em 0.5em 0.2em 0.5em; } /* ADMONITIONS AND WARNINGS ------------------------------------------------- */ /* Shared by admonitions and warnings */ div.admonition, div.warning { font-size: 0.9em; margin: 2em; padding: 0; /* border-radius: 6px; -moz-border-radius: 6px; -webkit-border-radius: 6px; */ } div.admonition p, div.warning p { margin: 0.5em 1em 0.5em 1em; padding: 0; } div.admonition pre, div.warning pre { margin: 0.4em 1em 0.4em 1em; } div.admonition p.admonition-title, div.warning p.admonition-title { margin: 0; padding: 0.1em 0 0.1em 0.5em; color: white; font-weight: bold; font-size: 1.1em; text-shadow: 0 1px rgba(0, 0, 0, 0.5); } div.admonition ul, div.admonition ol, div.warning ul, div.warning ol { margin: 0.1em 0.5em 0.5em 3em; padding: 0; } /* Admonitions only */ div.admonition { border: 1px solid #609060; background-color: #e9ffe9; } div.admonition p.admonition-title { background-color: #70A070; border-bottom: 1px solid #609060; } /* Warnings only */ div.warning { border: 1px solid #900000; background-color: #ffe9e9; } div.warning p.admonition-title { background-color: #b04040; border-bottom: 1px solid #900000; } div.versioninfo { margin: 1em 0 0 0; border: 1px solid #ccc; background-color: #DDEAF0; padding: 8px; line-height: 1.3em; font-size: 0.9em; } .viewcode-back { font-family: 'Lucida Grande', 'Lucida Sans Unicode', 'Geneva', 'Verdana', sans-serif; } div.viewcode-block:target { background-color: #f4debf; border-top: 1px solid #ac9; border-bottom: 1px solid #ac9; } dl { margin: 1em 0 2.5em 0; } /* Highlight target when you click an internal link */ dt:target { background: #ffe080; } /* Don't highlight whole divs */ div.highlight { background: transparent; } /* But do highlight spans (so search results can be highlighted) */ span.highlight { background: #ffe080; } div.footer { background-color: #465158; color: #eeeeee; padding: 0 2em 2em 2em; clear: both; font-size: 0.8em; text-align: center; } p { margin: 0.8em 0 0.5em 0; } .section p img { margin: 1em 2em; } /* MOBILE LAYOUT -------------------------------------------------------------- */ @media screen and (max-width: 600px) { h1, h2, h3, h4, h5 { position: relative; } ul { padding-left: 1.75em; } div.bodywrapper a.headerlink, #indices-and-tables h1 a { color: #e6e6e6; font-size: 80%; float: right; line-height: 1.8; position: absolute; right: -0.7em; visibility: inherit; } div.bodywrapper h1 a.headerlink, #indices-and-tables h1 a { line-height: 1.5; } pre { font-size: 0.7em; overflow: auto; word-wrap: break-word; white-space: pre-wrap; } div.related ul { height: 2.5em; padding: 0; text-align: left; } div.related ul li { clear: both; color: #465158; padding: 0.2em 0; } div.related ul li:last-child { border-bottom: 1px dotted #8ca1af; padding-bottom: 0.4em; margin-bottom: 1em; width: 100%; } div.related ul li a { color: #465158; padding-right: 0; } div.related ul li a:hover { background: inherit; color: inherit; } div.related ul li.right { clear: none; padding: 0.65em 0; margin-bottom: 0.5em; } div.related ul li.right a { color: #fff; padding-right: 0.8em; } div.related ul li.right a:hover { background-color: #8ca1af; } div.body { clear: both; min-width: 0; word-wrap: break-word; } div.bodywrapper { margin: 0 0 0 0; } div.sphinxsidebar { float: none; margin: 0; width: auto; } div.sphinxsidebar input[type="text"] { height: 2em; line-height: 2em; width: 70%; } div.sphinxsidebar input[type="submit"] { height: 2em; margin-left: 0.5em; width: 20%; } div.sphinxsidebar p.searchtip { background: inherit; margin-bottom: 1em; } div.sphinxsidebar ul li, div.sphinxsidebar p.topless { white-space: normal; } .bodywrapper img { display: block; margin-left: auto; margin-right: auto; max-width: 100%; } div.documentwrapper { float: none; } div.admonition, div.warning, pre, blockquote { margin-left: 0em; margin-right: 0em; } .body p img { margin: 0; } #searchbox { background: transparent; } .related:not(:first-child) li { display: none; } .related:not(:first-child) li.right { display: block; } div.footer { padding: 1em; } .rtd_doc_footer .badge { float: none; margin: 1em auto; position: static; } .rtd_doc_footer .badge.revsys-inline { margin-right: auto; margin-bottom: 2em; } table.indextable { display: block; width: auto; } .indextable tr { display: block; } .indextable td { display: block; padding: 0; width: auto !important; } .indextable td dt { margin: 1em 0; } ul.search { margin-left: 0.25em; } ul.search li div.context { font-size: 90%; line-height: 1.1; margin-bottom: 1; margin-left: 0; } } circuits-3.2.3/docs/source/_static/tracsphinx.css000066400000000000000000000053051460335514400221270ustar00rootroot00000000000000/* Trac specific styling */ @import url("sphinxdoc.css"); /* Structure */ div.footer { background-color: #4b4d4d; text-align: center; } div.bodywrapper { border-right: none; } /* Sidebar */ div.sphinxsidebarwrapper { -moz-box-shadow: 2px 2px 7px 0 grey; -webkit-box-shadow: 2px 2px 7px 0 grey; box-shadow: 2px 2px 7px 0 grey; padding: 0 0 1px .4em; } div.sphinxsidebar h3 a, div.sphinxsidebar h4 a { color: #b00; } div.sphinxsidebar h3, div.sphinxsidebar h4 { padding: 0; color: black; } div.sphinxsidebar h3, div.sphinxsidebar h4 { background: none; border: none; border-bottom: 1px solid #ddd; } div.sphinxsidebar input { border: 1px solid #d7d7d7; } p.searchtip { font-size: 90%; color: #999; } /* Navigation */ div.related ul li a { color: #b00 } div.related ul li a:hover { color: #b00; } /* Content */ body { font: normal 13px Verdana,Arial,'Bitstream Vera Sans',Helvetica,sans-serif; background-color: #4b4d4d; border: none; border-top: 1px solid #aaa; } h1, h2, h3, h4 { font-family: Arial,Verdana,'Bitstream Vera Sans',Helvetica,sans-serif; font-weight: bold; letter-spacing: -0.018em; page-break-after: avoid; } h1 { color: #555 } h2 { border-bottom: 1px solid #ddd } div.body a { text-decoration: none } a, a tt { color: #b00 } a:visited, a:visited tt { color: #800 } :link:hover, :visited:hover, a:link:hover tt, a:visited:hover tt { background-color: #eee; color: #555; } a.headerlink, a.headerlink:hover { color: #d7d7d7 !important; font-size: .8em; font-weight: normal; vertical-align: text-top; margin: 0; padding: .5em; } a.headerlink:hover { background: none; } div.body h1 a, div.body h2 a, div.body h3 a, div.body h4 a, div.body h5 a, div.body h6 a { color: #d7d7d7 !important; } dl.class { -moz-box-shadow: 1px 1px 6px 0 #888; -webkit-box-shadow: 1px 1px 6px 0 #888; box-shadow: 1px 1px 6px 0 #888; padding: .5em; } dl.function { margin-bottom: 24px; } dl.class > dt, dl.function > dt { border-bottom: 1px solid #ddd; } th.field-name { white-space: nowrap; font-size: 90%; color: #555; } td.field-body > ul { list-style-type: square; } td.field-body > ul > li > strong { font-weight: normal; font-style: italic; } /* Admonitions */ div.admonition p.admonition-title, div.warning p.admonition-title { background: none; color: #555; border: none; } div.admonition { background: none; border: none; border-left: 2px solid #acc; } div.warning { background: none; border: none; border-left: 3px solid #c33; } /* Search */ dl:target, dt:target, .highlighted { background-color: #ffa } circuits-3.2.3/docs/source/_templates/000077500000000000000000000000001460335514400177365ustar00rootroot00000000000000circuits-3.2.3/docs/source/_templates/layout.html000066400000000000000000000016041460335514400221420ustar00rootroot00000000000000{% extends "!layout.html" %} {%- block extrahead %} {{ super() }} {% endblock %} {% block sidebarlogo %} {{ super() }} {% if fabric_tags %}

    Project Versions

    {% endif %} {% endblock %} circuits-3.2.3/docs/source/_themes/000077500000000000000000000000001460335514400172255ustar00rootroot00000000000000circuits-3.2.3/docs/source/_themes/om/000077500000000000000000000000001460335514400176405ustar00rootroot00000000000000circuits-3.2.3/docs/source/_themes/om/genindex.html000066400000000000000000000001611460335514400223250ustar00rootroot00000000000000{% extends "basic/genindex.html" %} {% block bodyclass %}{% endblock %} {% block sidebarwrapper %}{% endblock %}circuits-3.2.3/docs/source/_themes/om/layout.html000066400000000000000000000051661460335514400220530ustar00rootroot00000000000000{% extends "basic/layout.html" %} {%- macro secondnav() %} {%- if prev %} « previous {{ reldelim2 }} {%- endif %} {%- if parents %} up {%- else %} up {%- endif %} {%- if next %} {{ reldelim2 }} next » {%- endif %} {%- endmacro %} {% block extrahead %} {{ super() }} {% endblock %} {% block document %}

    {{ docstitle }}

    {% block body %}{% endblock %}
    {% block sidebarwrapper %} {% if pagename != 'index' %} {% endif %} {% endblock %}
    {% endblock %} {% block sidebarrel %}

    Browse

    You are here:

    {% endblock %} {# Empty some default blocks out #} {% block relbar1 %}{% endblock %} {% block relbar2 %}{% endblock %} {% block sidebar1 %}{% endblock %} {% block sidebar2 %}{% endblock %} {% block footer %}{% endblock %} circuits-3.2.3/docs/source/_themes/om/modindex.html000066400000000000000000000001601460335514400223320ustar00rootroot00000000000000{% extends "basic/modindex.html" %} {% block bodyclass %}{% endblock %} {% block sidebarwrapper %}{% endblock %}circuits-3.2.3/docs/source/_themes/om/search.html000066400000000000000000000001561460335514400217750ustar00rootroot00000000000000{% extends "basic/search.html" %} {% block bodyclass %}{% endblock %} {% block sidebarwrapper %}{% endblock %}circuits-3.2.3/docs/source/_themes/om/static/000077500000000000000000000000001460335514400211275ustar00rootroot00000000000000circuits-3.2.3/docs/source/_themes/om/static/default.css000066400000000000000000000001331460335514400232620ustar00rootroot00000000000000@import url(reset-fonts-grids.css); @import url(djangodocs.css); @import url(homepage.css);circuits-3.2.3/docs/source/_themes/om/static/djangodocs.css000066400000000000000000000152541460335514400237630ustar00rootroot00000000000000/*** setup ***/ html { background:#092e20;} body { font:12px/1.5 Verdana,sans-serif; background:#092e20; color: white;} #custom-doc { width:76.54em;*width:74.69em;min-width:995px; max-width:100em; margin:auto; text-align:left; padding-top:16px; margin-top:0;} #hd { padding: 4px 0 12px 0; } #bd { background:#234F32; } #ft { color:#487858; font-size:90%; padding-bottom: 2em; } /*** links ***/ a {text-decoration: none;} a img {border: none;} a:link, a:visited { color:#ffc757; } #bd a:link, #bd a:visited { color:#ab5603; text-decoration:underline; } #bd #sidebar a:link, #bd #sidebar a:visited { color:#ffc757; text-decoration:none; } a:hover { color:#ffe761; } #bd a:hover { background-color:#E0FFB8; color:#234f32; text-decoration:none; } #bd #sidebar a:hover { color:#ffe761; background:none; } h2 a, h3 a, h4 a { text-decoration:none !important; } a.reference em { font-style: normal; } /*** sidebar ***/ #sidebar div.sphinxsidebarwrapper { font-size:92%; margin-right: 14px; } #sidebar h3, #sidebar h4 { color: white; font-size: 125%; } #sidebar a { color: white; } #sidebar ul ul { margin-top:0; margin-bottom:0; } #sidebar li { margin-top: 0.2em; margin-bottom: 0.2em; } /*** nav ***/ div.nav { margin: 0; font-size: 11px; text-align: right; color: #487858;} #hd div.nav { margin-top: -27px; } #ft div.nav { margin-bottom: -18px; } #hd h1 a { color: white; } #global-nav { position:absolute; top:5px; margin-left: -5px; padding:7px 0; color:#263E2B; } #global-nav a:link, #global-nav a:visited {color:#487858;} #global-nav a {padding:0 4px;} #global-nav a.about {padding-left:0;} #global-nav:hover {color:#fff;} #global-nav:hover a:link, #global-nav:hover a:visited { color:#ffc757; } /*** content ***/ #yui-main div.yui-b { position: relative; } #yui-main div.yui-b { margin: 0 0 0 20px; background: white; color: black; padding: 0.3em 2em 1em 2em; } /*** basic styles ***/ dd { margin-left:15px; } h1,h2,h3,h4 { margin-top:1em; font-family:"Trebuchet MS",sans-serif; font-weight:normal; } h1 { font-size:218%; margin-top:0.6em; margin-bottom:.4em; line-height:1.1em; } h2 { font-size:175%; margin-bottom:.6em; line-height:1.2em; color:#092e20; } h3 { font-size:150%; font-weight:bold; margin-bottom:.2em; color:#487858; } h4 { font-size:125%; font-weight:bold; margin-top:1.5em; margin-bottom:3px; } div.figure { text-align: center; } div.figure p.caption { font-size:1em; margin-top:0; margin-bottom:1.5em; color: #555;} hr { color:#ccc; background-color:#ccc; height:1px; border:0; } p, ul, dl { margin-top:.6em; margin-bottom:1em; padding-bottom: 0.1em;} #yui-main div.yui-b img { max-width: 50em; margin-left: auto; margin-right: auto; display: block; } caption { font-size:1em; font-weight:bold; margin-top:0.5em; margin-bottom:0.5em; margin-left: 2px; text-align: center; } blockquote { padding: 0 1em; margin: 1em 0; font:125%/1.2em "Trebuchet MS", sans-serif; color:#234f32; border-left:2px solid #94da3a; } strong { font-weight: bold; } em { font-style: italic; } ins { font-weight: bold; text-decoration: none; } /*** lists ***/ ul { padding-left:30px; } ol { padding-left:30px; } ol.arabic li { list-style-type: decimal; } ul li { list-style-type:square; margin-bottom:.4em; } ol li { margin-bottom: .4em; } ul ul { padding-left:1.2em; } ul ul ul { padding-left:1em; } ul.linklist, ul.toc { padding-left:0; } ul.toc ul { margin-left:.6em; } ul.toc ul li { list-style-type:square; } ul.toc ul ul li { list-style-type:disc; } ul.linklist li, ul.toc li { list-style-type:none; } dt { font-weight:bold; margin-top:.5em; font-size:1.1em; } dd { margin-bottom:.8em; } ol.toc { margin-bottom: 2em; } ol.toc li { font-size:125%; padding: .5em; line-height:1.2em; clear: right; } ol.toc li.b { background-color: #E0FFB8; } ol.toc li a:hover { background-color: transparent !important; text-decoration: underline !important; } ol.toc span.release-date { color:#487858; float: right; font-size: 85%; padding-right: .5em; } ol.toc span.comment-count { font-size: 75%; color: #999; } /*** tables ***/ table { color:#000; margin-bottom: 1em; width: 100%; } table.docutils td p { margin-top:0; margin-bottom:.5em; } table.docutils td, table.docutils th { border-bottom:1px solid #dfdfdf; padding:4px 2px;} table.docutils thead th { border-bottom:2px solid #dfdfdf; text-align:left; font-weight: bold; white-space: nowrap; } table.docutils thead th p { margin: 0; padding: 0; } table.docutils { border-collapse:collapse; } /*** code blocks ***/ .literal { white-space:nowrap; } .literal { color:#234f32; } #sidebar .literal { color:white; background:transparent; font-size:11px; } h4 .literal { color: #234f32; font-size: 13px; } pre { font-size:small; background:#E0FFB8; border:1px solid #94da3a; border-width:1px 0; margin: 1em 0; padding: .3em .4em; overflow: hidden; line-height: 1.3em;} dt .literal, table .literal { background:none; } #bd a.reference { text-decoration: none; } #bd a.reference tt.literal { border-bottom: 1px #234f32 dotted; } /* Restore colors of pygments hyperlinked code */ #bd .highlight .k a:link, #bd .highlight .k a:visited { color: #000000; text-decoration: none; border-bottom: 1px dotted #000000; } #bd .highlight .nf a:link, #bd .highlight .nf a:visited { color: #990000; text-decoration: none; border-bottom: 1px dotted #990000; } /*** notes & admonitions ***/ .note, .admonition { padding:.8em 1em .8em; margin: 1em 0; border:1px solid #94da3a; } .admonition-title { font-weight:bold; margin-top:0 !important; margin-bottom:0 !important;} .admonition .last { margin-bottom:0 !important; } .note, .admonition { padding-left:65px; background:url(docicons-note.png) .8em .8em no-repeat;} div.admonition-philosophy { padding-left:65px; background:url(docicons-philosophy.png) .8em .8em no-repeat;} div.admonition-behind-the-scenes { padding-left:65px; background:url(docicons-behindscenes.png) .8em .8em no-repeat;} /*** versoinadded/changes ***/ div.versionadded, div.versionchanged { } div.versionadded span.title, div.versionchanged span.title { font-weight: bold; } /*** p-links ***/ a.headerlink { color: #c60f0f; font-size: 0.8em; padding: 0 4px 0 4px; text-decoration: none; visibility: hidden; } h1:hover > a.headerlink, h2:hover > a.headerlink, h3:hover > a.headerlink, h4:hover > a.headerlink, h5:hover > a.headerlink, h6:hover > a.headerlink, dt:hover > a.headerlink { visibility: visible; } /*** index ***/ table.indextable td { text-align: left; vertical-align: top;} table.indextable dl, table.indextable dd { margin-top: 0; margin-bottom: 0; } table.indextable tr.pcap { height: 10px; } table.indextable tr.cap { margin-top: 10px; background-color: #f2f2f2;} /*** page-specific overrides ***/ div#contents ul { margin-bottom: 0;} div#contents ul li { margin-bottom: 0;} div#contents ul ul li { margin-top: 0.3em;} /*** IE hacks ***/ * pre { width: 100%; } circuits-3.2.3/docs/source/_themes/om/static/docicons-behindscenes.png000066400000000000000000000043351460335514400260730ustar00rootroot00000000000000PNG  IHDR//E pHYs  IDATX ͙kLWYY q>ƂXeSX[Q؆jMiZ4&M%Fjm54>P)Z*"X< *͞0̂m'pswFӧOr<]ZOԨ܅0 p,Gsй;U 3)"ut+膼> MYvp)l2ʥKjo(f Kcxj@>VG+JG0@`~D,bW}TfE_tE;% 8"&΋`1Vtlƍųт/-+'CesqS´`wY\t[P˯dGM@Pԉ.|SMg+\)o$wXl0]µX22$]KV"EZǰ)Qrʱ`,J1A`89I:C8KjuQ|sok`C  ÌƇ]OSu%8`Odeeڽ[YٓI;] t *Ko*haedo'䭨ȡSqGu;KH}uȸ) ɑjժB5vD:0_,Wf4IU^@aJ h#e`Ez sPV2a kCȡ岧tˆ4{O&1]鄹+ǐ^Q Y Ohҽn#edCY ^YAK[lYzuhhh{/$$LFzUC?L .5>(󫪂/+k.4w>~ghs_ &(mly)R!??2ܬٛ.RKw3;6qu`يW#Φ򑑦 ztw#Esl$[OT u*q I 'd})6-M<;Lq5=$)Lܬ{ !'$9DR:8|uGҨ;VJwGG/-kg*gGT#ZEH[R&zA|Y*b|҉{q3m!l!Ey, d̚5kM/ݯ8Շ%YqH JYa-/uC/.ţ=87.pt? ѓXԶoSJjAT*uF8JMIcIj$jwn(PAe o(;ؼR@# 'R"M} Qd4C *AO@Wa{72O)͆`IJhdϏKZ3].ҺG!hQ8"eFs#|0 h܅0 pTҗIENDB`circuits-3.2.3/docs/source/_themes/om/static/docicons-note.png000066400000000000000000000017651460335514400244120ustar00rootroot00000000000000PNG  IHDR//E pHYs  IDATX ͙MhAǓuUFE[i+ 'IVZz9Y)jAOzbO VE6dggvv[J}>~y3;j>;3~ZYV[eM&Uٺ3?)VA P1 }Ҫ$ښn&!) ~wO2;;w{99̙lXYYo=_"5Yͼ 88\yّ?n_s3{d3MOVMO/4ǟ޲eLu[ߌACD* |SC҅ÎwєξD[TG;2"O񞻾DdmE}UQLLr™SD*Pd6|wS ܍Orq t*\XXc;^I DٕwDP../@X-/ZjHnLzG"9ރͺ +@vuǁVFR)9&c}ބ?L:=11AϜzYeH'])(A K(qb *1e{{{XdDIbaQttt(J1\uȨe'@7**jllp(I1JԪ=44433c!^Ua#a*0SBs||\%"#R%  )'!)P$MOO#O!P6xu1ܐfAiX 8&&( "y薀rty$![e3dreض.( )e(u|!fREta)}gX$A+/ X@HTU2(>P%a~FD!@ DԘ>Ն|'H!B! C70[]ܟ!8ORc3V[9?GOp|7V7>9Gù }(n\x^GTӸҨ߽iE]4U=Uu[~: ڰԖ08;DqUf" E(ຄEĔП[­#S YmsqGfVb۾TM[Œ-4@ ph?56z(3iP0mA H].8:5u((hrIțs[btP%ĩ^/VtϰkKpOVǟaŞa?G}bfIENDB`circuits-3.2.3/docs/source/_themes/om/static/homepage.css000066400000000000000000000015731460335514400234340ustar00rootroot00000000000000#index p.rubric { font-size:150%; font-weight:normal; margin-bottom:.2em; color:#487858; } #index div.section dt { font-weight: normal; } #index #s-getting-help { float: right; width: 35em; background: #E1ECE2; padding: 1em; margin: 2em 0 2em 2em; } #index #s-getting-help h2 { margin: 0; } #index #s-django-documentation div.section div.section h3 { margin: 0; } #index #s-django-documentation div.section div.section { background: #E1ECE2; padding: 1em; margin: 2em 0 2em 40.3em; } #index #s-django-documentation div.section div.section a.reference { white-space: nowrap; } #index #s-using-django dl, #index #s-add-on-contrib-applications dl, #index #s-solving-specific-problems dl, #index #s-reference dl { float: left; width: 41em; } #index #s-add-on-contrib-applications, #index #s-solving-specific-problems, #index #s-reference, #index #s-and-all-the-rest { clear: left; }circuits-3.2.3/docs/source/_themes/om/static/reset-fonts-grids.css000066400000000000000000000126221460335514400252230ustar00rootroot00000000000000/* Copyright (c) 2008, Yahoo! Inc. All rights reserved. Code licensed under the BSD License: http://developer.yahoo.net/yui/license.txt version: 2.5.1 */ html{color:#000;background:#FFF;}body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,textarea,p,blockquote,th,td{margin:0;padding:0;}table{border-collapse:collapse;border-spacing:0;}fieldset,img{border:0;}address,caption,cite,code,dfn,em,strong,th,var{font-style:normal;font-weight:normal;}li{list-style:none;}caption,th{text-align:left;}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal;}q:before,q:after{content:'';}abbr,acronym {border:0;font-variant:normal;}sup {vertical-align:text-top;}sub {vertical-align:text-bottom;}input,textarea,select{font-family:inherit;font-size:inherit;font-weight:inherit;}input,textarea,select{*font-size:100%;}legend{color:#000;}body {font:13px/1.231 arial,helvetica,clean,sans-serif;*font-size:small;*font:x-small;}table {font-size:inherit;font:100%;}pre,code,kbd,samp,tt{font-family:monospace;*font-size:108%;line-height:100%;} body{text-align:center;}#ft{clear:both;}#doc,#doc2,#doc3,#doc4,.yui-t1,.yui-t2,.yui-t3,.yui-t4,.yui-t5,.yui-t6,.yui-t7{margin:auto;text-align:left;width:57.69em;*width:56.25em;min-width:750px;}#doc2{width:73.076em;*width:71.25em;}#doc3{margin:auto 10px;width:auto;}#doc4{width:74.923em;*width:73.05em;}.yui-b{position:relative;}.yui-b{_position:static;}#yui-main .yui-b{position:static;}#yui-main{width:100%;}.yui-t1 #yui-main,.yui-t2 #yui-main,.yui-t3 #yui-main{float:right;margin-left:-25em;}.yui-t4 #yui-main,.yui-t5 #yui-main,.yui-t6 #yui-main{float:left;margin-right:-25em;}.yui-t1 .yui-b{float:left;width:12.30769em;*width:12.00em;}.yui-t1 #yui-main .yui-b{margin-left:13.30769em;*margin-left:13.05em;}.yui-t2 .yui-b{float:left;width:13.8461em;*width:13.50em;}.yui-t2 #yui-main .yui-b{margin-left:14.8461em;*margin-left:14.55em;}.yui-t3 .yui-b{float:left;width:23.0769em;*width:22.50em;}.yui-t3 #yui-main .yui-b{margin-left:24.0769em;*margin-left:23.62em;}.yui-t4 .yui-b{float:right;width:13.8456em;*width:13.50em;}.yui-t4 #yui-main .yui-b{margin-right:14.8456em;*margin-right:14.55em;}.yui-t5 .yui-b{float:right;width:18.4615em;*width:18.00em;}.yui-t5 #yui-main .yui-b{margin-right:19.4615em;*margin-right:19.125em;}.yui-t6 .yui-b{float:right;width:23.0769em;*width:22.50em;}.yui-t6 #yui-main .yui-b{margin-right:24.0769em;*margin-right:23.62em;}.yui-t7 #yui-main .yui-b{display:block;margin:0 0 1em 0;}#yui-main .yui-b{float:none;width:auto;}.yui-gb .yui-u,.yui-g .yui-gb .yui-u,.yui-gb .yui-g,.yui-gb .yui-gb,.yui-gb .yui-gc,.yui-gb .yui-gd,.yui-gb .yui-ge,.yui-gb .yui-gf,.yui-gc .yui-u,.yui-gc .yui-g,.yui-gd .yui-u{float:left;}.yui-g .yui-u,.yui-g .yui-g,.yui-g .yui-gb,.yui-g .yui-gc,.yui-g .yui-gd,.yui-g .yui-ge,.yui-g .yui-gf,.yui-gc .yui-u,.yui-gd .yui-g,.yui-g .yui-gc .yui-u,.yui-ge .yui-u,.yui-ge .yui-g,.yui-gf .yui-g,.yui-gf .yui-u{float:right;}.yui-g div.first,.yui-gb div.first,.yui-gc div.first,.yui-gd div.first,.yui-ge div.first,.yui-gf div.first,.yui-g .yui-gc div.first,.yui-g .yui-ge div.first,.yui-gc div.first div.first{float:left;}.yui-g .yui-u,.yui-g .yui-g,.yui-g .yui-gb,.yui-g .yui-gc,.yui-g .yui-gd,.yui-g .yui-ge,.yui-g .yui-gf{width:49.1%;}.yui-gb .yui-u,.yui-g .yui-gb .yui-u,.yui-gb .yui-g,.yui-gb .yui-gb,.yui-gb .yui-gc,.yui-gb .yui-gd,.yui-gb .yui-ge,.yui-gb .yui-gf,.yui-gc .yui-u,.yui-gc .yui-g,.yui-gd .yui-u{width:32%;margin-left:1.99%;}.yui-gb .yui-u{*margin-left:1.9%;*width:31.9%;}.yui-gc div.first,.yui-gd .yui-u{width:66%;}.yui-gd div.first{width:32%;}.yui-ge div.first,.yui-gf .yui-u{width:74.2%;}.yui-ge .yui-u,.yui-gf div.first{width:24%;}.yui-g .yui-gb div.first,.yui-gb div.first,.yui-gc div.first,.yui-gd div.first{margin-left:0;}.yui-g .yui-g .yui-u,.yui-gb .yui-g .yui-u,.yui-gc .yui-g .yui-u,.yui-gd .yui-g .yui-u,.yui-ge .yui-g .yui-u,.yui-gf .yui-g .yui-u{width:49%;*width:48.1%;*margin-left:0;}.yui-g .yui-gb div.first,.yui-gb .yui-gb div.first{*margin-right:0;*width:32%;_width:31.7%;}.yui-g .yui-gc div.first,.yui-gd .yui-g{width:66%;}.yui-gb .yui-g div.first{*margin-right:4%;_margin-right:1.3%;}.yui-gb .yui-gc div.first,.yui-gb .yui-gd div.first{*margin-right:0;}.yui-gb .yui-gb .yui-u,.yui-gb .yui-gc .yui-u{*margin-left:1.8%;_margin-left:4%;}.yui-g .yui-gb .yui-u{_margin-left:1.0%;}.yui-gb .yui-gd .yui-u{*width:66%;_width:61.2%;}.yui-gb .yui-gd div.first{*width:31%;_width:29.5%;}.yui-g .yui-gc .yui-u,.yui-gb .yui-gc .yui-u{width:32%;_float:right;margin-right:0;_margin-left:0;}.yui-gb .yui-gc div.first{width:66%;*float:left;*margin-left:0;}.yui-gb .yui-ge .yui-u,.yui-gb .yui-gf .yui-u{margin:0;}.yui-gb .yui-gb .yui-u{_margin-left:.7%;}.yui-gb .yui-g div.first,.yui-gb .yui-gb div.first{*margin-left:0;}.yui-gc .yui-g .yui-u,.yui-gd .yui-g .yui-u{*width:48.1%;*margin-left:0;}s .yui-gb .yui-gd div.first{width:32%;}.yui-g .yui-gd div.first{_width:29.9%;}.yui-ge .yui-g{width:24%;}.yui-gf .yui-g{width:74.2%;}.yui-gb .yui-ge div.yui-u,.yui-gb .yui-gf div.yui-u{float:right;}.yui-gb .yui-ge div.first,.yui-gb .yui-gf div.first{float:left;}.yui-gb .yui-ge .yui-u,.yui-gb .yui-gf div.first{*width:24%;_width:20%;}.yui-gb .yui-ge div.first,.yui-gb .yui-gf .yui-u{*width:73.5%;_width:65.5%;}.yui-ge div.first .yui-gd .yui-u{width:65%;}.yui-ge div.first .yui-gd div.first{width:32%;}#bd:after,.yui-g:after,.yui-gb:after,.yui-gc:after,.yui-gd:after,.yui-ge:after,.yui-gf:after{content:".";display:block;height:0;clear:both;visibility:hidden;}#bd,.yui-g,.yui-gb,.yui-gc,.yui-gd,.yui-ge,.yui-gf{zoom:1;}circuits-3.2.3/docs/source/_themes/om/theme.conf000066400000000000000000000001071460335514400216070ustar00rootroot00000000000000[theme] inherit = basic stylesheet = default.css pygments_style = trac circuits-3.2.3/docs/source/api/000077500000000000000000000000001460335514400163525ustar00rootroot00000000000000circuits-3.2.3/docs/source/api/circuits.app.daemon.rst000066400000000000000000000002241460335514400227500ustar00rootroot00000000000000circuits.app.daemon module ========================== .. automodule:: circuits.app.daemon :members: :undoc-members: :show-inheritance: circuits-3.2.3/docs/source/api/circuits.app.rst000066400000000000000000000003371460335514400215130ustar00rootroot00000000000000circuits.app package ==================== Submodules ---------- .. toctree:: circuits.app.daemon Module contents --------------- .. automodule:: circuits.app :members: :undoc-members: :show-inheritance: circuits-3.2.3/docs/source/api/circuits.core.bridge.rst000066400000000000000000000002271460335514400231140ustar00rootroot00000000000000circuits.core.bridge module =========================== .. automodule:: circuits.core.bridge :members: :undoc-members: :show-inheritance: circuits-3.2.3/docs/source/api/circuits.core.components.rst000066400000000000000000000002431460335514400240430ustar00rootroot00000000000000circuits.core.components module =============================== .. automodule:: circuits.core.components :members: :undoc-members: :show-inheritance: circuits-3.2.3/docs/source/api/circuits.core.debugger.rst000066400000000000000000000002351460335514400234430ustar00rootroot00000000000000circuits.core.debugger module ============================= .. automodule:: circuits.core.debugger :members: :undoc-members: :show-inheritance: circuits-3.2.3/docs/source/api/circuits.core.events.rst000066400000000000000000000002271460335514400231640ustar00rootroot00000000000000circuits.core.events module =========================== .. automodule:: circuits.core.events :members: :undoc-members: :show-inheritance: circuits-3.2.3/docs/source/api/circuits.core.handlers.rst000066400000000000000000000002351460335514400234570ustar00rootroot00000000000000circuits.core.handlers module ============================= .. automodule:: circuits.core.handlers :members: :undoc-members: :show-inheritance: circuits-3.2.3/docs/source/api/circuits.core.helpers.rst000066400000000000000000000002321460335514400233160ustar00rootroot00000000000000circuits.core.helpers module ============================ .. automodule:: circuits.core.helpers :members: :undoc-members: :show-inheritance: circuits-3.2.3/docs/source/api/circuits.core.loader.rst000066400000000000000000000002271460335514400231260ustar00rootroot00000000000000circuits.core.loader module =========================== .. automodule:: circuits.core.loader :members: :undoc-members: :show-inheritance: circuits-3.2.3/docs/source/api/circuits.core.manager.rst000066400000000000000000000002321460335514400232660ustar00rootroot00000000000000circuits.core.manager module ============================ .. automodule:: circuits.core.manager :members: :undoc-members: :show-inheritance: circuits-3.2.3/docs/source/api/circuits.core.pollers.rst000066400000000000000000000002321460335514400233340ustar00rootroot00000000000000circuits.core.pollers module ============================ .. automodule:: circuits.core.pollers :members: :undoc-members: :show-inheritance: circuits-3.2.3/docs/source/api/circuits.core.rst000066400000000000000000000010161460335514400216560ustar00rootroot00000000000000circuits.core package ===================== Submodules ---------- .. toctree:: circuits.core.bridge circuits.core.components circuits.core.debugger circuits.core.events circuits.core.handlers circuits.core.helpers circuits.core.loader circuits.core.manager circuits.core.pollers circuits.core.timers circuits.core.utils circuits.core.values circuits.core.workers Module contents --------------- .. automodule:: circuits.core :members: :undoc-members: :show-inheritance: circuits-3.2.3/docs/source/api/circuits.core.timers.rst000066400000000000000000000002271460335514400231630ustar00rootroot00000000000000circuits.core.timers module =========================== .. automodule:: circuits.core.timers :members: :undoc-members: :show-inheritance: circuits-3.2.3/docs/source/api/circuits.core.utils.rst000066400000000000000000000002241460335514400230150ustar00rootroot00000000000000circuits.core.utils module ========================== .. automodule:: circuits.core.utils :members: :undoc-members: :show-inheritance: circuits-3.2.3/docs/source/api/circuits.core.values.rst000066400000000000000000000002271460335514400231570ustar00rootroot00000000000000circuits.core.values module =========================== .. automodule:: circuits.core.values :members: :undoc-members: :show-inheritance: circuits-3.2.3/docs/source/api/circuits.core.workers.rst000066400000000000000000000002321460335514400233500ustar00rootroot00000000000000circuits.core.workers module ============================ .. automodule:: circuits.core.workers :members: :undoc-members: :show-inheritance: circuits-3.2.3/docs/source/api/circuits.io.events.rst000066400000000000000000000002211460335514400226350ustar00rootroot00000000000000circuits.io.events module ========================= .. automodule:: circuits.io.events :members: :undoc-members: :show-inheritance: circuits-3.2.3/docs/source/api/circuits.io.file.rst000066400000000000000000000002131460335514400222510ustar00rootroot00000000000000circuits.io.file module ======================= .. automodule:: circuits.io.file :members: :undoc-members: :show-inheritance: circuits-3.2.3/docs/source/api/circuits.io.notify.rst000066400000000000000000000002211460335514400226410ustar00rootroot00000000000000circuits.io.notify module ========================= .. automodule:: circuits.io.notify :members: :undoc-members: :show-inheritance: circuits-3.2.3/docs/source/api/circuits.io.process.rst000066400000000000000000000002241460335514400230120ustar00rootroot00000000000000circuits.io.process module ========================== .. automodule:: circuits.io.process :members: :undoc-members: :show-inheritance: circuits-3.2.3/docs/source/api/circuits.io.rst000066400000000000000000000004621460335514400213410ustar00rootroot00000000000000circuits.io package =================== Submodules ---------- .. toctree:: circuits.io.events circuits.io.file circuits.io.notify circuits.io.process circuits.io.serial Module contents --------------- .. automodule:: circuits.io :members: :undoc-members: :show-inheritance: circuits-3.2.3/docs/source/api/circuits.io.serial.rst000066400000000000000000000002211460335514400226100ustar00rootroot00000000000000circuits.io.serial module ========================= .. automodule:: circuits.io.serial :members: :undoc-members: :show-inheritance: circuits-3.2.3/docs/source/api/circuits.net.events.rst000066400000000000000000000002241460335514400230170ustar00rootroot00000000000000circuits.net.events module ========================== .. automodule:: circuits.net.events :members: :undoc-members: :show-inheritance: circuits-3.2.3/docs/source/api/circuits.net.rst000066400000000000000000000003671460335514400215240ustar00rootroot00000000000000circuits.net package ==================== Submodules ---------- .. toctree:: circuits.net.events circuits.net.sockets Module contents --------------- .. automodule:: circuits.net :members: :undoc-members: :show-inheritance: circuits-3.2.3/docs/source/api/circuits.net.sockets.rst000066400000000000000000000002271460335514400231710ustar00rootroot00000000000000circuits.net.sockets module =========================== .. automodule:: circuits.net.sockets :members: :undoc-members: :show-inheritance: circuits-3.2.3/docs/source/api/circuits.node.client.rst000066400000000000000000000002271460335514400231330ustar00rootroot00000000000000circuits.node.client module =========================== .. automodule:: circuits.node.client :members: :undoc-members: :show-inheritance: circuits-3.2.3/docs/source/api/circuits.node.events.rst000066400000000000000000000002271460335514400231610ustar00rootroot00000000000000circuits.node.events module =========================== .. automodule:: circuits.node.events :members: :undoc-members: :show-inheritance: circuits-3.2.3/docs/source/api/circuits.node.node.rst000066400000000000000000000002211460335514400225740ustar00rootroot00000000000000circuits.node.node module ========================= .. automodule:: circuits.node.node :members: :undoc-members: :show-inheritance: circuits-3.2.3/docs/source/api/circuits.node.rst000066400000000000000000000005001460335514400216500ustar00rootroot00000000000000circuits.node package ===================== Submodules ---------- .. toctree:: circuits.node.client circuits.node.events circuits.node.node circuits.node.server circuits.node.utils Module contents --------------- .. automodule:: circuits.node :members: :undoc-members: :show-inheritance: circuits-3.2.3/docs/source/api/circuits.node.server.rst000066400000000000000000000002271460335514400231630ustar00rootroot00000000000000circuits.node.server module =========================== .. automodule:: circuits.node.server :members: :undoc-members: :show-inheritance: circuits-3.2.3/docs/source/api/circuits.node.utils.rst000066400000000000000000000002241460335514400230120ustar00rootroot00000000000000circuits.node.utils module ========================== .. automodule:: circuits.node.utils :members: :undoc-members: :show-inheritance: circuits-3.2.3/docs/source/api/circuits.protocols.http.rst000066400000000000000000000002401460335514400237260ustar00rootroot00000000000000circuits.protocols.http module ============================== .. automodule:: circuits.protocols.http :members: :undoc-members: :show-inheritance: circuits-3.2.3/docs/source/api/circuits.protocols.irc.rst000066400000000000000000000002351460335514400235300ustar00rootroot00000000000000circuits.protocols.irc module ============================= .. automodule:: circuits.protocols.irc :members: :undoc-members: :show-inheritance: circuits-3.2.3/docs/source/api/circuits.protocols.line.rst000066400000000000000000000002401460335514400236760ustar00rootroot00000000000000circuits.protocols.line module ============================== .. automodule:: circuits.protocols.line :members: :undoc-members: :show-inheritance: circuits-3.2.3/docs/source/api/circuits.protocols.rst000066400000000000000000000005121460335514400227520ustar00rootroot00000000000000circuits.protocols package ========================== Submodules ---------- .. toctree:: circuits.protocols.http circuits.protocols.irc circuits.protocols.line circuits.protocols.websocket Module contents --------------- .. automodule:: circuits.protocols :members: :undoc-members: :show-inheritance: circuits-3.2.3/docs/source/api/circuits.protocols.websocket.rst000066400000000000000000000002571460335514400247450ustar00rootroot00000000000000circuits.protocols.websocket module =================================== .. automodule:: circuits.protocols.websocket :members: :undoc-members: :show-inheritance: circuits-3.2.3/docs/source/api/circuits.rst000066400000000000000000000006111460335514400207270ustar00rootroot00000000000000circuits package ================ Subpackages ----------- .. toctree:: circuits.app circuits.core circuits.io circuits.net circuits.node circuits.protocols circuits.tools circuits.web Submodules ---------- .. toctree:: circuits.version Module contents --------------- .. automodule:: circuits :members: :undoc-members: :show-inheritance: circuits-3.2.3/docs/source/api/circuits.tools.rst000066400000000000000000000002501460335514400220650ustar00rootroot00000000000000circuits.tools package ====================== Module contents --------------- .. automodule:: circuits.tools :members: :undoc-members: :show-inheritance: circuits-3.2.3/docs/source/api/circuits.version.rst000066400000000000000000000002131460335514400224110ustar00rootroot00000000000000circuits.version module ======================= .. automodule:: circuits.version :members: :undoc-members: :show-inheritance: circuits-3.2.3/docs/source/api/circuits.web.client.rst000066400000000000000000000002241460335514400227600ustar00rootroot00000000000000circuits.web.client module ========================== .. automodule:: circuits.web.client :members: :undoc-members: :show-inheritance: circuits-3.2.3/docs/source/api/circuits.web.constants.rst000066400000000000000000000002351460335514400235200ustar00rootroot00000000000000circuits.web.constants module ============================= .. automodule:: circuits.web.constants :members: :undoc-members: :show-inheritance: circuits-3.2.3/docs/source/api/circuits.web.controllers.rst000066400000000000000000000002431460335514400240510ustar00rootroot00000000000000circuits.web.controllers module =============================== .. automodule:: circuits.web.controllers :members: :undoc-members: :show-inheritance: circuits-3.2.3/docs/source/api/circuits.web.dispatchers.dispatcher.rst000066400000000000000000000003041460335514400261370ustar00rootroot00000000000000circuits.web.dispatchers.dispatcher module ========================================== .. automodule:: circuits.web.dispatchers.dispatcher :members: :undoc-members: :show-inheritance: circuits-3.2.3/docs/source/api/circuits.web.dispatchers.jsonrpc.rst000066400000000000000000000002731460335514400254740ustar00rootroot00000000000000circuits.web.dispatchers.jsonrpc module ======================================= .. automodule:: circuits.web.dispatchers.jsonrpc :members: :undoc-members: :show-inheritance: circuits-3.2.3/docs/source/api/circuits.web.dispatchers.rst000066400000000000000000000006461460335514400240230ustar00rootroot00000000000000circuits.web.dispatchers package ================================ Submodules ---------- .. toctree:: circuits.web.dispatchers.dispatcher circuits.web.dispatchers.jsonrpc circuits.web.dispatchers.static circuits.web.dispatchers.virtualhosts circuits.web.dispatchers.xmlrpc Module contents --------------- .. automodule:: circuits.web.dispatchers :members: :undoc-members: :show-inheritance: circuits-3.2.3/docs/source/api/circuits.web.dispatchers.static.rst000066400000000000000000000002701460335514400253020ustar00rootroot00000000000000circuits.web.dispatchers.static module ====================================== .. automodule:: circuits.web.dispatchers.static :members: :undoc-members: :show-inheritance: circuits-3.2.3/docs/source/api/circuits.web.dispatchers.virtualhosts.rst000066400000000000000000000003121460335514400265570ustar00rootroot00000000000000circuits.web.dispatchers.virtualhosts module ============================================ .. automodule:: circuits.web.dispatchers.virtualhosts :members: :undoc-members: :show-inheritance: circuits-3.2.3/docs/source/api/circuits.web.dispatchers.xmlrpc.rst000066400000000000000000000002701460335514400253200ustar00rootroot00000000000000circuits.web.dispatchers.xmlrpc module ====================================== .. automodule:: circuits.web.dispatchers.xmlrpc :members: :undoc-members: :show-inheritance: circuits-3.2.3/docs/source/api/circuits.web.errors.rst000066400000000000000000000002241460335514400230160ustar00rootroot00000000000000circuits.web.errors module ========================== .. automodule:: circuits.web.errors :members: :undoc-members: :show-inheritance: circuits-3.2.3/docs/source/api/circuits.web.events.rst000066400000000000000000000002241460335514400230060ustar00rootroot00000000000000circuits.web.events module ========================== .. automodule:: circuits.web.events :members: :undoc-members: :show-inheritance: circuits-3.2.3/docs/source/api/circuits.web.exceptions.rst000066400000000000000000000002401460335514400236610ustar00rootroot00000000000000circuits.web.exceptions module ============================== .. automodule:: circuits.web.exceptions :members: :undoc-members: :show-inheritance: circuits-3.2.3/docs/source/api/circuits.web.headers.rst000066400000000000000000000002271460335514400231200ustar00rootroot00000000000000circuits.web.headers module =========================== .. automodule:: circuits.web.headers :members: :undoc-members: :show-inheritance: circuits-3.2.3/docs/source/api/circuits.web.http.rst000066400000000000000000000002161460335514400224620ustar00rootroot00000000000000circuits.web.http module ======================== .. automodule:: circuits.web.http :members: :undoc-members: :show-inheritance: circuits-3.2.3/docs/source/api/circuits.web.loggers.rst000066400000000000000000000002271460335514400231470ustar00rootroot00000000000000circuits.web.loggers module =========================== .. automodule:: circuits.web.loggers :members: :undoc-members: :show-inheritance: circuits-3.2.3/docs/source/api/circuits.web.main.rst000066400000000000000000000002161460335514400224270ustar00rootroot00000000000000circuits.web.main module ======================== .. automodule:: circuits.web.main :members: :undoc-members: :show-inheritance: circuits-3.2.3/docs/source/api/circuits.web.parsers.http.rst000066400000000000000000000002461460335514400241430ustar00rootroot00000000000000circuits.web.parsers.http module ================================ .. automodule:: circuits.web.parsers.http :members: :undoc-members: :show-inheritance: circuits-3.2.3/docs/source/api/circuits.web.parsers.multipart.rst000066400000000000000000000002651460335514400252060ustar00rootroot00000000000000circuits.web.parsers.multipart module ===================================== .. automodule:: circuits.web.parsers.multipart :members: :undoc-members: :show-inheritance: circuits-3.2.3/docs/source/api/circuits.web.parsers.querystring.rst000066400000000000000000000002731460335514400255600ustar00rootroot00000000000000circuits.web.parsers.querystring module ======================================= .. automodule:: circuits.web.parsers.querystring :members: :undoc-members: :show-inheritance: circuits-3.2.3/docs/source/api/circuits.web.parsers.rst000066400000000000000000000005031460335514400231610ustar00rootroot00000000000000circuits.web.parsers package ============================ Submodules ---------- .. toctree:: circuits.web.parsers.http circuits.web.parsers.multipart circuits.web.parsers.querystring Module contents --------------- .. automodule:: circuits.web.parsers :members: :undoc-members: :show-inheritance: circuits-3.2.3/docs/source/api/circuits.web.processors.rst000066400000000000000000000002401460335514400237020ustar00rootroot00000000000000circuits.web.processors module ============================== .. automodule:: circuits.web.processors :members: :undoc-members: :show-inheritance: circuits-3.2.3/docs/source/api/circuits.web.rst000066400000000000000000000013541460335514400215100ustar00rootroot00000000000000circuits.web package ==================== Subpackages ----------- .. toctree:: circuits.web.dispatchers circuits.web.parsers circuits.web.websockets Submodules ---------- .. toctree:: circuits.web.client circuits.web.constants circuits.web.controllers circuits.web.errors circuits.web.events circuits.web.exceptions circuits.web.headers circuits.web.http circuits.web.loggers circuits.web.main circuits.web.processors circuits.web.servers circuits.web.sessions circuits.web.tools circuits.web.url circuits.web.utils circuits.web.wrappers circuits.web.wsgi Module contents --------------- .. automodule:: circuits.web :members: :undoc-members: :show-inheritance: circuits-3.2.3/docs/source/api/circuits.web.servers.rst000066400000000000000000000002271460335514400231760ustar00rootroot00000000000000circuits.web.servers module =========================== .. automodule:: circuits.web.servers :members: :undoc-members: :show-inheritance: circuits-3.2.3/docs/source/api/circuits.web.sessions.rst000066400000000000000000000002321460335514400233470ustar00rootroot00000000000000circuits.web.sessions module ============================ .. automodule:: circuits.web.sessions :members: :undoc-members: :show-inheritance: circuits-3.2.3/docs/source/api/circuits.web.tools.rst000066400000000000000000000002211460335514400226370ustar00rootroot00000000000000circuits.web.tools module ========================= .. automodule:: circuits.web.tools :members: :undoc-members: :show-inheritance: circuits-3.2.3/docs/source/api/circuits.web.url.rst000066400000000000000000000002131460335514400223020ustar00rootroot00000000000000circuits.web.url module ======================= .. automodule:: circuits.web.url :members: :undoc-members: :show-inheritance: circuits-3.2.3/docs/source/api/circuits.web.utils.rst000066400000000000000000000002211460335514400226370ustar00rootroot00000000000000circuits.web.utils module ========================= .. automodule:: circuits.web.utils :members: :undoc-members: :show-inheritance: circuits-3.2.3/docs/source/api/circuits.web.websockets.client.rst000066400000000000000000000002651460335514400251350ustar00rootroot00000000000000circuits.web.websockets.client module ===================================== .. automodule:: circuits.web.websockets.client :members: :undoc-members: :show-inheritance: circuits-3.2.3/docs/source/api/circuits.web.websockets.dispatcher.rst000066400000000000000000000003011460335514400257740ustar00rootroot00000000000000circuits.web.websockets.dispatcher module ========================================= .. automodule:: circuits.web.websockets.dispatcher :members: :undoc-members: :show-inheritance: circuits-3.2.3/docs/source/api/circuits.web.websockets.rst000066400000000000000000000004611460335514400236560ustar00rootroot00000000000000circuits.web.websockets package =============================== Submodules ---------- .. toctree:: circuits.web.websockets.client circuits.web.websockets.dispatcher Module contents --------------- .. automodule:: circuits.web.websockets :members: :undoc-members: :show-inheritance: circuits-3.2.3/docs/source/api/circuits.web.wrappers.rst000066400000000000000000000002321460335514400233440ustar00rootroot00000000000000circuits.web.wrappers module ============================ .. automodule:: circuits.web.wrappers :members: :undoc-members: :show-inheritance: circuits-3.2.3/docs/source/api/circuits.web.wsgi.rst000066400000000000000000000002161460335514400224540ustar00rootroot00000000000000circuits.web.wsgi module ======================== .. automodule:: circuits.web.wsgi :members: :undoc-members: :show-inheritance: circuits-3.2.3/docs/source/api/index.rst000066400000000000000000000001601460335514400202100ustar00rootroot00000000000000================= API Documentation ================= .. toctree:: :maxdepth: 1 :glob: circuits* circuits-3.2.3/docs/source/changes.rst000066400000000000000000000000371460335514400177430ustar00rootroot00000000000000.. include:: ../../CHANGES.rst circuits-3.2.3/docs/source/conf.py000066400000000000000000000170241460335514400171040ustar00rootroot00000000000000# # circuits documentation build configuration file, created by # sphinx-quickstart on Thu Feb 4 09:44:50 2010. # # This file is execfile()d with the current directory set to its containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import sys from os import path from pkg_resources import parse_version from circuits import __version__ # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use path.abspath to make it absolute, like shown here. sys.path.insert(0, path.abspath('../..')) # -- General configuration ----------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = [] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. # source_encoding = 'utf-8' # The master toctree document. master_doc = 'index' # General information about the project. project = 'circuits' copyright = '2004-2016, James Mills' url = 'http://circuitsframework.com/' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = parse_version(__version__).base_version # The full version, including alpha/beta/rc tags. release = __version__ # Devel or Release mode for the documentation (if devel, include TODOs, # can also be used in conditionals: .. ifconfig :: devel) devel = 'dev' in __version__ # -- Autodoc extensions.append('sphinx.ext.autodoc') autodoc_default_flags = ['show-inheritance'] autoclass_content = 'both' autodoc_member_order = 'bysource' # -- AutoSummary extensions.append('sphinx.ext.autosummary') # -- Graphviz extensions.append('sphinx.ext.graphviz') # -- Conditional content (see setup() below) extensions.append('sphinx.ext.ifconfig') # -- Keep track of :todo: items extensions.append('sphinx.ext.todo') todo_include_todos = devel # -- track statistics with google analytics # extensions.append("sphinxcontrib.googleanalytics") # googleanalytics_id = "UA-38618352-3" # -- release and issues extensions.append('releases') # 'releases' (changelog) settings releases_debug = False releases_issue_uri = 'https://github.com/circuits/circuits/issues/%s' releases_release_uri = 'https://github.com/circuits/circuits/tree/%s' releases_document_name = ['changes'] # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: # today = '' # Else, today_fmt is used as the format for a strftime call. today_fmt = '%B %d, %Y' # List of documents that shouldn't be included in the build. # unused_docs = [] # List of directories, relative to source directory, that shouldn't be searched # for source files. exclude_trees = [] # The reST default role (used for this markup: `text`) to use for all documents. # default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. # add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). # add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. # show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] # -- Options for HTML output --------------------------------------------- # The theme to use for HTML and HTML Help pages. Major themes that come with # Sphinx are currently 'default' and 'sphinxdoc'. html_theme = 'default' html_style = 'rtd.css' html_context = {} # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. html_theme_path = ['_themes'] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". # html_title = None # A shorter title for the navigation bar. Default is the same as html_title. # html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. # html_logo = "_static/logo.png" # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. # html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. # html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. # html_use_smartypants = True # Custom sidebar templates, maps document names to template names. # html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. # html_additional_pages = {} # If false, no module index is generated. # html_use_modindex = True # If false, no index is generated. # html_use_index = True # If true, the index is split into individual pages for each letter. # html_split_index = False # If true, links to the reST sources are added to the pages. # html_show_sourcelink = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. # html_use_opensearch = '' # If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). # html_file_suffix = '' # Output file base name for HTML help builder. htmlhelp_basename = 'circuitsdoc' # -- Options for LaTeX output -------------------------------------------- # The paper size ('letter' or 'a4'). # latex_paper_size = 'letter' # The font size ('10pt', '11pt' or '12pt'). # latex_font_size = '10pt' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ ( 'index', 'circuits.tex', 'circuits Documentation', 'James Mills', 'manual', ), ] # The name of an image file (relative to this directory) to place at the top of # the title page. # latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. # latex_use_parts = False # Additional stuff for the LaTeX preamble. # latex_preamble = '' # Documents to append as an appendix to all manuals. # latex_appendices = [] # If false, no module index is generated. # latex_use_modindex = True def setup(app): # ifconfig variables app.add_config_value('devel', '', devel) circuits-3.2.3/docs/source/contributors.rst000066400000000000000000000010661460335514400210730ustar00rootroot00000000000000Contributors ============ circuits was originally designed, written and primarily maintained by James Mills (http://prologic.shortcircuit.net.au/). The following users and developers have contributed to circuits: - Alessio Deiana - Dariusz Suchojad - Tim Miller - Holger Krekel - Justin Giorgi - Edwin Marshall - Alex Mayfield - Toni Alatalo - Michael Lipp - Matthieu Chevrier - Yoann Ono Dit Biot Anyone not listed here (*apologies as this list is taken directly from Mercurial's churn command and output*). We appreciate any and all contributions to circuits. circuits-3.2.3/docs/source/dev/000077500000000000000000000000001460335514400163575ustar00rootroot00000000000000circuits-3.2.3/docs/source/dev/contributing.rst000066400000000000000000000032141460335514400216200ustar00rootroot00000000000000.. _Fork circuits: https://github.com/circuits/circuits/issues/new#fork-destination-box .. _Create an Issue: https://github.com/circuits/circuits/issues/new .. _Pull Request: https://github.com/circuits/circuits/compare/ Contributing to circuits ======================== Here's how you can contribute to circuits Share your story ---------------- One of the best ways you can contribute to circuits is by using circuits. Share with us your story of how you've used circuits to solve a problem or create a new software solution using the circuits framework and library of components. .. see: http://circuitsframework.com/Community Submitting Bug Reports ---------------------- We welcome all bug reports. We do however prefer bug reports in a clear and concise form with repeatable steps. One of the best ways you can report a bug to us is by writing a unit test (//similar to the ones in our tests//) so that we can verify the bug, fix it and commit the fix along with the test. To submit a bug report, please `Create an Issue`_ Writing new tests ----------------- We're not perfect, and we're still writing more tests to ensure quality code. If you'd like to help, please `Fork circuits`_, write more tests that cover more of our code base and submit a `Pull Request`_. Many Thanks! Adding New Features ------------------- If you'd like to see a new feature added to circuits, then we'd like to hear about it~ We would like to see some discussion around any new features as well as valid use-cases. To start the discussions off, please either: - `Chat to us on #circuits on the Libera.Chat IRC Network `_ or - `Create an Issue`_ circuits-3.2.3/docs/source/dev/environment.rst000066400000000000000000000044121460335514400214560ustar00rootroot00000000000000.. _virtualenvwrapper: https://pypi.python.org/pypi/virtualenvwrapper .. _virtualenv: https://pypi.python.org/pypi/virtualenv .. _pip: https://pypi.python.org/pypi/pip .. _Fabric: http://www.fabfile.org/ .. _Python: https://www.python.org/ .. _Git: https://git-scm.com/ Setting up a circuits Development Environment ============================================= This is the recommended way to setup a development enviornment for developing with or on circuits. .. note:: This document *assumes* you already have a working `Python`_ environment with a minimum `Python`_ version of 2.7 as well as `pip`_ already installed. Prequisites ----------- It is highly recommended that you install and use `virtualenv`_ for all your `Python`_ development and production deployments (*not just circuits*). It is also convenient to install and use the accompanying shell scripts and tools `virtualenvwrapper`_ which adds a nice set of workflows and functions useful for both development and deployments. .. code-block:: bash $ pip install -U virtualenvwrapper $ source $(which virtualenvwrapper.sh) .. note:: You should put ``source $(which virtualenvwrapper.sh)`` in either your ``$HOME/.bashrc`` or ``$HOME/.profile`` depending on how you login and interact with your terminals. In addition to the above recommendations you must also have a `Git`_ client installed and ready to use as well as your Editor/IDE of choice ready to use. Getting Started --------------- 1. `Fork circuits `_ (*if you haven't done so already*) 2. Clone your forked repository using `Git`_ 3. Create a new virtual environment using `virtualenvwrapper`_ 4. Install the `Development Requirements `_ 5. Install circuits in "develop" mode And you're done! Example: .. code-block:: bash $ git clone git@github.com:prologic/circuits.git $ cd circuits $ mkvirtualenv circuits $ pip install -r requirements-dev.txt $ python setup.py develop Alternatively if you already have `Fabric`_ installed: .. code-block:: bash $ git clone git@github.com:prologic/circuits.git $ cd circuits $ mkvirtualenv circuits $ fab develop circuits-3.2.3/docs/source/dev/index.rst000066400000000000000000000005401460335514400202170ustar00rootroot00000000000000Developer Docs ============== So, you'd like to contribute to circuits in some way? Got a bug report? Having problems running the examples? Having problems getting circuits working in your environment? Excellent. Here's what you need to know. .. toctree:: :maxdepth: 2 introduction contributing environment processes standards circuits-3.2.3/docs/source/dev/introduction.rst000066400000000000000000000033611460335514400216350ustar00rootroot00000000000000.. _Developer Mailing List: http://groups.google.com/group/circuits-dev .. _Issue Tracker: https://github.com/circuits/circuits/issues .. _Libera.Chat IRC Network: https://libera.chat .. _IRC Channel: https://web.libera.chat/#circuits Development Introduction ======================== Here's how we do things in circuits... Communication ------------- - `IRC Channel`_ on the `Libera.Chat IRC Network`_ - `Developer Mailing List`_ - `Issue Tracker`_ .. note:: If you are familiar with `IRC `_ and use your own IRC Client then connect to the Libera.Chat Network and ``/join #circuits``. Standards --------- We use the following coding standard: - `PEP-008 `_ We also lint our codebase with the following tools: - `pyflakes `_ - `pep8 `_ - `mccabe `_ Please ensure your Development IDE or Editor has the above linters and checkers in place and enabled. Alternatively you can use the following command line tool: - `flake8 `_ Tools ----- We use the following tools to develop circuits and share code: - **Code Sharing:** `Git `_ - **Code Hosting and Bug Reporting:** `GitHub `_ - **Issue Tracker:** `Issue Tracker `_ - **Documentation Hosting:** `Read the Docs `_ - **Package Hosting:** `Python Package Index (PyPi) `_ - **Continuous Integration:** `Github Actions `_ circuits-3.2.3/docs/source/dev/processes.rst000066400000000000000000000067341460335514400211310ustar00rootroot00000000000000.. _Issue Tracker: https://github.com/circuits/circuits/issues Development Processes ===================== We document all our internal development processes here so you know exactly how we work and what to expect. If you find any issues or problems please let us know! Software Development Life Cycle (SDLC) -------------------------------------- We employ the use of the `SCRUM Agile Process `_ and use our `Issue Tracker`_ to track features, bugs, chores and releases. If you wish to contribute to circuits, please familiarize yourself with SCRUM and `GitHub `_'s Issue Tracker. Bug Reports ----------- - New Bug Reports are submitted via: https://github.com/circuits/circuits/issues - Confirmation and Discussion of all New Bug Reports. - Once confirmed, a new Bug is raised in our `Issue Tracker`_ - An appropriate milestone will be set (*depending on current milestone's schedule and resources*) - A unit test developed that demonstrates the bug's failure. - A fix developed that passes the unit test and breaks no others. - A `New Pull Request `_ created with the fix. This must contains: - A new or modified unit test. - A patch that fixes the bug ensuring all unit tests pass. - The `Change Log `_ updated. - Appropriate documentation updated. - The `Pull Request `_ is reviewed and approved by at least two other developers. Feature Requests ---------------- - New Feature Requests are submitted via: https://github.com/circuits/circuits/issues - Confirmation and Discussion of all New Feature Requests. - Once confirmed, a new Feature is raised in our `Issue Tracker`_ - An appropriate milestone will be set (*depending on current milestone's schedule and resources*) - A unit test developed that demonstrates the new feature. - The new feature developed that passes the unit test and breaks no others. - A `New Pull Request `_ created with the fix. This must contains: - A new or modified unit test. - A patch that implements the new feature ensuring all unit tests pass. - The `Change Log `_ updated. - Appropriate documentation updated. - The `Pull Request `_ is reviewed and approved by at least two other developers. Writing new Code ---------------- - Submit a `New Issue `_ - Write your code. - Use `flake8 `_ to ensure code quality. - Run the tests:: $ tox - Ensure any new or modified code does not break existing unit tests. - Update any relevant doc strings or documentation. - Update the `Change Log `_ updated. - Submit a `New Pull Request `_. Running the Tests ----------------- To run the tests you will need the following installed: - `tox `_ installed as well as - `pytest-cov `_ - `pytest `_ All of these can be installed via ``pip``. Please also ensure that you you have all supported versions of Python that circuits supports installed in your local environment. To run the tests:: $ tox circuits-3.2.3/docs/source/dev/standards.rst000066400000000000000000000022771460335514400211040ustar00rootroot00000000000000Development Standards ===================== We use the following development standards: Cyclomatic Complexity --------------------- - Code Complexity shall not exceed ``10`` See: `Limiting Cyclomatic Complexity `_ Coding Style ------------ - Code shall confirm to the `PEP8 `_ Style Guide. .. note:: This includes the 79 character limit! - Doc Strings shall confirm to the `PEP257 `_ Convention. .. note:: Arguments, Keyword Arguments, Return and Exceptions must be documented with the appropriate Sphinx`Python Domain `_. Revision History ---------------- - Commits shall be small tangible pieces of work. - Each commit must be concise and manageable. - Large changes are to be done over smaller commits. - There shall be no commit squashing. - Rebase your changes as often as you can. Unit Tests ---------- - Every new feature and bug fix must be accompanied with a unit test. (*The only exception to this are minor trivial changes*). circuits-3.2.3/docs/source/examples/000077500000000000000000000000001460335514400174175ustar00rootroot00000000000000circuits-3.2.3/docs/source/examples/echoserver.py000077500000000000000000000017261460335514400221470ustar00rootroot00000000000000#!/usr/bin/env python """ Simple TCP Echo Server This example shows how you can create a simple TCP Server (an Echo Service) utilizing the builtin Socket Components that the circuits library ships with. """ from circuits import Debugger, handler from circuits.net.sockets import TCPServer class EchoServer(TCPServer): @handler('read') def on_read(self, sock, data): """ Read Event Handler This is fired by the underlying Socket Component when there has been new data read from the connected client. ..note :: By simply returning, client/server socket components listen to ValueChagned events (feedback) to determine if a handler returned some data and fires a subsequent Write event with the value returned. """ return data # Start and "run" the system. # Bind to port 0.0.0.0:8000 app = EchoServer(('0.0.0.0', 8000)) Debugger().register(app) app.run() circuits-3.2.3/docs/source/examples/hello.py000077500000000000000000000011231460335514400210740ustar00rootroot00000000000000#!/usr/bin/env python """circuits Hello World""" from circuits import Component, Event class hello(Event): """hello Event""" class App(Component): def hello(self): """Hello Event Handler""" print('Hello World!') def started(self, component): """ Started Event Handler This is fired internally when your application starts up and can be used to trigger events that only occur once during startup. """ self.fire(hello()) # Fire hello Event raise SystemExit(0) # Terminate the Application App().run() circuits-3.2.3/docs/source/examples/helloweb.py000077500000000000000000000007241460335514400216000ustar00rootroot00000000000000#!/usr/bin/env python from circuits.web import Controller, Server class Root(Controller): def index(self): """ Index Request Handler Controller(s) expose implicitly methods as request handlers. Request Handlers can still be customized by using the ``@expose`` decorator. For example exposing as a different path. """ return 'Hello World!' app = Server(('0.0.0.0', 8000)) Root().register(app) app.run() circuits-3.2.3/docs/source/examples/index.rst000066400000000000000000000011231460335514400212550ustar00rootroot00000000000000Hello ..... .. literalinclude:: examples/hello.py :language: python :linenos: Download Source Code: :download:`hello.py: ` Echo Server ........... .. literalinclude:: examples/echoserver.py :language: python :linenos: Download Source Code: :download:`echoserver.py: ` Hello Web ......... .. literalinclude:: examples/helloweb.py :language: python :linenos: Download Source Code: :download:`helloweb.py: ` More `examples `_... circuits-3.2.3/docs/source/faq.rst000066400000000000000000000037431460335514400171110ustar00rootroot00000000000000.. _#circuits IRC Channel: https://web.libera.chat/#circuits .. _Mailing List: http://groups.google.com/group/circuits-users .. faq:: Frequently Asked Questions ========================== .. general:: General ------- ... What is circuits? circuits is an event-driven framework with a high focus on Component architectures making your life as a software developer much easier. circuits allows you to write maintainable and scalable systems easily ... Can I write networking applications with circuits? Yes absolutely. circuits comes with socket I/O components for tcp, udp and unix sockets with asynchronous polling implementations for select, poll, epoll and kqueue. ... Can I integrate circuits with a GUI library? This is entirely possible. You will have to hook into the GUI's main loop. ... What are the core concepts in circuits? Components and Events. Components are maintainable reusable units of behavior that communicate with other components via a powerful message passing system. ... How would you compare circuits to Twisted? Others have said that circuits is very elegant in terms of it's usage. circuits' component architecture allows you to define clear interfaces between components while maintaining a high level of scalability and maintainability. ... Can Components communicate with other processes? Yes. circuits implements currently component bridging and nodes ... What platforms does circuits support? circuits currently supports Linux, FreeBSD, OSX and Windows and is currently continually tested against Linux against Python versions 2.7, 3.5, 3.6, 3.7, 3.8, 3.9 and 3.10. Windows support is best efford. ... Can circuits be used for concurrent or distributed programming? Yes. We also have plans to build more distributed components into circuits making distributing computing with circuits very trivial. Got more questions? * Send an email to our `Mailing List`_. * Talk to us online on the `#circuits IRC Channel`_ circuits-3.2.3/docs/source/glossary.rst000066400000000000000000000002111460335514400201700ustar00rootroot00000000000000======== Glossary ======== .. glossary:: :sorted: VCS Version Control System, what you use for versioning your source code circuits-3.2.3/docs/source/images/000077500000000000000000000000001460335514400170465ustar00rootroot00000000000000circuits-3.2.3/docs/source/images/CircuitsWebServer.png000066400000000000000000000735051460335514400232000ustar00rootroot00000000000000PNG  IHDRh MbKGD IDATxyXT?  DMԦT^|c4ik!FX6&&iMbQUDAPVeXdهmx~pf~]\:p8f8sy=!B! .B!B(B!BF!B!*B!>R===L&Bww7w_(`z&N}}„ ӃΈ=B!DP@#\jTVVΝ;H$H$CCC$ ޽HRtuu)MMM077 LLL`aab鰲¸qV !2őBƆ.7n@EEb1b1!\ 233wzzz{`kܸqFT*?7pknnFKK X[[ D2( Ғ4if !šF! c 7oDvv6]"C,ژ9sP3p6m ~ގJs!(++Css3Û#0gŅQBQY!D#==W\+WVFwJUWW"%%%(((@CCD"̙OOO瞃ʇRB!c4BQ3wErr2RRR".txxx`?>I߫YSSS~~~TB!c4BQq1ԩS8}4󡥥OOO,^i{O1".𦦦ݻ@hh(VXAB!DBTP__q)DGGΝ;ʕ+ PB%` 8}4Ҡ XVeB(B ѣGq VªU0k,shA.cŊذa,Y2#2(B:;;_?GNNlmm/׿5Owy:::p);v III011e̘1!!' '֭ƍ`K#OPYY ?Q[[5k3͛wiB4Baw;#?zzzxױyf]^8q>puu4B!jJw2Vtuu߇-~@DD35%~zhkk֮]J#hBF@bb"~B"`֭ؾ};.(c N¶mpڵ ۶mf"B!(QOOv܉?k֬G} "J&G߆;?KKK"(BTUUa(//7׿$2~zb?~+V$B!*A#%())E< gc._W^y?<+K"4.BF|,^8u.H[[ɓR:eBQQ4őBpttĉ'wID9r7oFBB|}}.B F! /YYY?oEnn..BF! r)_ٰSF.?q?i=C]Fz{{kkk?~\i!(B00gWݻPPTR`v|"77NNN#MB!!(@JJ QUU9{T-#hC G-;{񁋋 !.jO! ^?z?k{1$$qqqlB67n5G4=yO"eeeH$B!*F!D:;;1~QP4kÖyX81؞u=ֳ9%(Bݻ#] \[IS_2zk(QԶFcc#```0b$(Bxzz";;{DyyXwtI?4gEgCڕ+W0!AB`ըBVVֈoIׄ=6O 5Ϻ?#yK!b(BXZZ5u@C*ⷿ-ߥBQ1!DAcǎ)u;Ùiֳitcزe vEןBy}P5!(Б#GoҥKprrR6֠q_ }E|Բ/3Q2 -bcc!yRB!Q@#[npY8;;+|qeP~m=gYpe~?rK#J///۷>affwYB4BQ{ذan݊s">>ﲈ矇77xvB4B8x 1{lOF"w#nܸ8$$$! h2lll?"==Xr%̙/===|G)99AAA3gpa\v !!!|F!DMQ}BQQQv܉3g@$᷿-6l777K#PSS'Nѣχ3 CCCa4B!FBFX[[Ο?xǣ={6ZZZ'''lܸ/0}tK::: gϞ֮]7b޼y~ۉHJJBJJ ZZZ`aa.sI!I(B(((@\\㑞>xxx 44!!!pwwIرc׿F̛7+Vʕ+#;jkkqiERRcŊ~!''IIIHHHŋ [[[.x!u@B (X, %K<༯iiiFtt4nݺ3f ((=ѯ/^DJJ  }}}bʕ S/^DRRn` ~TB 4BQn… dpssCHHBCC {3HLLDFF:;;agg???,Z5kt\t HNN˗!1w\#44>>>R[[[D$&&ZZZ'D"·M!DQ@#ى.AAA AXXLMMmL˗/#99IIIta``777̟?ܹsaee ԡ.PXXl\r٨bʕXr%|}}addK}kLBzF!PVVx!%%]]]pqqF.\8aLJ~l߾ mmmNNNìY`ii }}Ud2QVV"hnnX[[cpww***B,cƍصkL#nݺŅdH$ߟk:bkkwB!\|qqqիW nƆ2I&yGvfTG% (--o'N 㻬ׇnŋ̙3xbhI!c4BZH$8w&XZZr=/^<1*RDgFU6ڀ|#::-¾}wYϤtlٙ lTB(BJ?rrr3g ;;Xh7JfggwCvogƗ^z QQQJȧdeeaΝHLLDpp0wwwR6r7N!uB򚚚pynD"ԩSQ@;cIIIǥKzjٳg5ߨC&%%Ǐ77Em$U@rcFɲ pB!,, NNN|TFKg+mӧ[,}6dH$L4 \ӑYf]&! hڊ!&&uuu077:.2h8c-?O=y$v X7b׮]2e ߥ) c ׯ_FRSS!J1m4.‚R !DQ@#&??񈉉ŋ.]0] Ό1\cǎ!226mDWD\,.effB&CCCK%B2bR)XTWWK,e4j:Ό1L> QQQ֭[opuvv"---77nnnט:yA!CT%%%Ù3g\777nmM O*8pB!mۆ-[PԄC7 p"ݡw2(B)))8s add`,[ 011LKڃZZZpA:tرc^}1ݮΝ;\ÑD`„ O{&'Fyfeee(م n|4c3pP@{DEEȑ#077GDD֯_?^f j82c $Fn\p111EYY K"$$|tmmmxdg᠀dUUUѣGacc={`͚54Z?}}}C^x9s&7?jG !c 4BȐbn,%%prr²e kE3P@RDDDᅦ "##wY*\˗/\H___{B(BA&!==%+))L:2GԽy1ۙq8( _~~> ///DEELJTV{{;RSS xzzrmc?BFTWWs-!Jaooύ-ZhPgƧGeeeaΝHLLDpp0wwwRy HJJBBBP^^]]]x{{sװtdBJF&ˑ3g >>E@@7JfiiwΌώڳKJJBxx8.]իWcϞ=ptt,qmnt-)) 022pd֬Y|I!(2qd hmm /_@GG2yG>}(((ukkkR+1s"SSSގiӦqk&&&ԙQ()V?N<ݻwC,cƍصkLwijI.\`DOO燉']*!dF($HX?MMMҥK ???]JΌCM9r9;HH$lڴ ۷o1ߥN\xk韛 puuF׼hJQ hFrrr ??? 44vvv|֙qΝt\()L&g}(tvvb֭x7a``wiBss3RRRDܸqX`!c|!D(X;wӧO-g=ufFT*ÇqBl۶ [lQ`s7;w@__\GGGqBSF`!77111˗!バɉ2ufYFVKK 444 @[[R !hLI̸w^[;kػw- IDATǏɓؽ{7b16n܈]v( ŋD1WWW7tuu.2Ln\p e%%%000@`` BBBɓ']FUիqԩ!-ڊ &("4r9;HH$lڴ ۷o1ߥGhnnFJJ wZqq1`n|Jy hvP@={v"OK&>CTT:;;uV0004555tȤ$TUUA__}!*!dHOOBY~~>QW۷ʕ+ԙQuvv]]]\F ~իG2,R)>@(b۶mزe \7ora-99055ŋ`k Q P]]ϣ vvv Ehh(|}}wcufTO/2N8hƏF0^5҂СCǎ;ꫯQ#//k韖XYYqam؊P@#c\.GFFrss ???bҥ1Ό.,YwYdD"]ǎ٢B544 ** G9"""~zjd2#YYY냣#w/]3JFƌ:.={---AHH.] ???]FG^?wF* UUUѣGacc={`͚5t]JHMMFخ_ # X`$Ȩׇ˗/#..qqqɁ6|||˗Ɔ2 3hi&|Ls4iie)--EDD{ 22򑣨}'H5utuuō5! B*Ν>,\sL \1ΌLzz:}M$^áCx([~~> ///DEE744 :uVJEEEŠuuu8q".E~ >>111r 455h"nΎ2ǜ2T6u:3n1L:555 T)YYYGBBo>?:tӧOGvv6LLLx]v!??֭CDDۇǏs HWWW$''TQ /^C1nۛsBAV$$$ >>gΜA]]͹AAAtU 88CD"ڵkԙq EYYt2e ɓ'{nDAll,}(҂CdQQ`3<==wQGll,°tR(ׇ+Wٳ|_(⭷Ν;3o>|CT\.… 3qI 8rk|}}쬐. hT T?>---ץ=hH^SSDĉH4bc$%$$@__ORCLL abb`,_AAA022RpժhmmEww7Z[[ʽJܲ&L---Lk?~_$a͚5F2HrDOOOFWWנeֆ: ī BPCC^dtv\]] /y;1ڊvHRttt :H$> O#COPZZʅdܽ{&&&DΜ9s}6fΜݻwc׮]cydqnOOw|+J8I9_?ZZZ}Fm@H$ŨD"A]]$ ޽:444 /EahhI&&&&0119LLL`jjiӦJGzzzc|x' gKJJ@\777nmTL}X,ƭ[PWW*444Gss0(000N 33AN6 Wۇ]v=oH  ''𬵵555hhhݻw܌f455q@Sku'nFFF777LMMGdYlΝ;ѳ{ BVbn\[[ D emmm Ì7zzz044,,,`jj SSSְPZ꠿ׯ_3f!?@ ܹsabtttΝ;ݻw}w777RƏ}}}_?_L2v`c86rܾ}(--Eyy9*++!Q^^>(̌qpzzzm :swtvvB&q#q}kmmBD"A}}= H311%f̘3fvvvpppNH _尶ƭ[|gg'RRRXb!((˗/GppZUnkkõkpuܸq(--EEE7o2e ͹MMM1qD q위Bk?\Vkjj;wPWW+H577-lll`kk ggg8;;~M6 iYMMM̙3C^?:jkkQ^^XJr:pryXoE@@vv6PRRܼy2 B 2;ydx[[[Q]]=J,nBcc#x͛Н"1tѭ[`mm/++C|| ....(;çсLdddڵkvn߾ aoo`֬Yҟd@YYPZZ2ܼy\]]wwwx{{cʔ)+::W~⨊444VgϞEppRhۋ2q'@+**79mmmL6 $333L4I-;8\SS:.VWWsn3g΄6L~w >v_G=w_./_QTT"Ɔ{N---O6Mgrqr6p1?ptt#ϟ771JHKK! ΍-X022z dBoYZk]]PRR2۷)B'Oɽ'wollϟ!z+HHHx TSS>XlBBBx{HMMŅ \a֬Y;w.bΝ;*HR\~ׯ_G^^putuuXhP[[ L&B!ttt`kkspppը 6p90PRR",,,ܙ8c}m Hagg;;;EN&n͛7qMMo'^z%XXXEGG&N9s 8::{PIR8``?QPP BXp!ݻHNN暎bܸqpvvFVVCfkx7yZݻwAlְ4:jiiӧS<$ƭ[@0hNNNprraxhpRRRapww\\\;R\r٨pBOΜ9W^yӒ@''',]XpZGLL ␑@777,ZSӃl#-- /^| q`ggWWWn*X~4d2 Wի~:ͅ7ٳgN-f+***PRR2( 7owsss!ߥ ktn޼v?lCHH@lmmi??/_tkjj J$&&?ƵkexP('?>&$wENNΠ[EE`ppp̙ccc<62X?%%%hooH$œ9sݜUtc W\Att4bbbp5D"̟?~~~ F$_ܹTBWWXb-[S>v#G@ 8իկ~XسMǒ%KQRow}888`ڵXb}0H$GBB&L_xwy*A" 11{C/,,72777X[[HjllիWqddd ==077-Z O?ĪU///:6PΝéS &`ٲeXz5.]:*O࡭E(bܹxZrS1o<=WCb999BZZߏ9sUj@|2?_hii/_+WVY%!ˑӧO#::n݂6n܈_裏CCC㑟iu?H^{ Rrφ1$>|O9^z%_...|q |wœ9syf_~LB2Ɛ}˗ ۛf r}}}ByZZ˗ŏ:|W8vJJJ0gZ WƼy.ǫAtt4~'$''C__k׮ņ wy //'.'  7䴞QgggQH*"##iiiHII+WׇE!,, ˖-{Ύ`NNNΝ;QLyffhhDL[[ikk3HĄBe^_0~rK]~߱q1CCCm6&H.Kid2;s ۰a377g {7ٳgYWW%屨(444 a}kll仼q9rJ&ۼy3,g|srr>cwilϞ==f100`VVVݝ1+++fk׮e_5`}}}|? 2:::ةSثʦN0+++e}ݾ}}KD"lܸ LfbmmmϜ JJJktuuۻw/们ax"sqqaB֭[|4IR2###fdd>g$؞={،3<Ȫ.N߳+W2mmm~_lK{&c3gd^`999|Eڵ1===sNwYϤm߾M< =Fb۷ӧ3Ã=zuww+lO$ ۴iD)cD"ao6c/P궶6yf& Ypp0+(($rf[o1H.\VONN۰af&&&lǎ(Ȏ9\]]h"vIwiÒɞ{9& ƍYYY%g8 X~W\ak׮e"M<;ﲈc_fZZZܜٳ544<wf``,,,vodx٦M&srrR3lڴiؘ?~rc\vyxx0---]cedd0___9;;/)#JžyO>3moogo@ `~~~ի|Dhļ&7o+,,令'd>>> ssscǏg===|EFsNfllttt͛ad9*𔔔&޽{U`/dlժU 9sAO.0MMMvZF[ZZV^0???wId *//g7ofZZZO|Pl֬YĄ?Dl…LWW}|P ,HII$2FtvvO?YXX &;z(3PȢ$RSS) dRZn߾<==Nojjjغu@ `o<1}gL ݻw?sdtilld=`%%%#oLCCΐUUUʊ-^CGa:::lѢEbķOP?L(^zPHS\\̦Nʼx{-l„ ՕBoa&L`L,?qT>cHFTʖ,YGt䣠ikkzĶIvÄB!nDm?1ooo2>s d'NT^^,--~_~444ؖ-[E+--ecӧO4GZfaanݪfϞ^|ٞ\.glÆ #=2ΈOFd{(RYY7,Fdwa,22rDGCNNΈK&;xmgc=2[yyy)}`m,xcw10]]]v)o/`&&&Jk(AU_oC/BCCl&Fc5s2TkYCY2^g=OSO3|:tM4577+u;|m{뭷{ 5󤟿CYwzԝ"^O󜌄V^e+lk׮ '+J+{Y冲2DDDɓ(((xd 憠 U6|sǗ'=׏{1^r=`eemOkL|zw?j=zL||̞=[i۹v͛B)m;{Уg=֧y|3C+Z?ΝҶ/"޽+~Oן,NsPP@0br8;;oŊ+CHLL"G j(z0}܁pNӦMCBBW 7nY `;XEnwaa=>̝;˗/޽{~ *++1qDlq{ɗy; v8.hrJoQ66oތ|*mz6q?&eСC8p*++! 8::";;Jƣ(55LׁGmm`|AXIII(,,XCѯϾ7 9ms'Ò .YVX6?T_hYSLJږeeʡC ޠ {sNb1\#2`;\|<\t sXr%4 8`:#Ebֵ)quÚҮ.ݻעmiƍ˓diX{`4Ru.hv¼yh:233ۋ?bu7ўF7cа$7ĺuss&h<cgN}#v5wb=[Lॗ^½ދSZR+Wb֭c(R$iƴ+Kk)Ү]ݍbuDGG㡇#<FczF|,;)֫Hq4??OkC;wh=-Fl۶ /bzr9O=}%g kֆ:fGfek;pA|1^{MJ5"f>>\9#=?R=j c0͔e¢!D^^pttÔ6rc-8k)qCDGGg}bu5770b j-^__}Cס:c1F5[cv=z,O>vvvV_ʕ+d4X^gbXNV)))BY>V+Ν+RSSE{{9hlg[7e5,giyyyM+C.3dTZVq"))ItvvZ$@CFv (j4VHpuTyMLYƜ򄯯x衇,RPDddXf1c_3eLӔq[h}^>|e@nI9s cjhϱ3}|)?,d2/ץKXnHkcs-]1e߶m۶ {{{e"22RXѴs[r.?~vf~M1'N'^u,233EOOϰˏٳgEXXHJJuuuf i=O?!r{,dž r:Yݻx$Jr-M{`N&"-Ğ={$#//Ox{{˗ iB&Y(@;W_嘀lʵkĊ+,z{{G|Q{/X.:4@-ك'hZ/ GGGcY;}=zTKz:/{I?風HxƂ ~ '''&._,uHDTO?IYQGGشioN'^y(nV'uH4[ &=jk{_vMZJى͛7[01*++ży󄋋xWG!DNNP*b٢Zp'2LlٲEp 9"Ν+Dffp! ġh+" P*6jZΟ?/̙#<==ŧ~*u8d"11Qxyy۷KN?b…B&_oA<&G~(<==ELLoL}řz^70h4^bROuu={P*>:2BiiHMM5N'o.O.\]]ţ>*JKK(&F/P!?/ja S2LZ*W%ӏ\]]mzRt׮]RD\ooسgXx V^=pT{nJ%V^-ŋӀhxb֭"$$Dr /H}3c7 L&-[Y3%~ '''*Ξ=+uHDttdn﷩#4={V<]( NJQ8 bccM'dN'DXX_"K;bƌB&KZ(hhkkovvvbٲe"''gLeiji@k׮GS0d;:;;| g7pa1sL!"uH$~ue\%9:N|7⮻2LDFF?ܹsRF@SSDZZdbƌ⭷ NOOx7B_46/N&8K/q"FMӉ\qF-ƍvvt:sNq-bܹb^8FAlٲE ggga\v:;;şgP(xy ۷O̚5.J%uHcR^^.z)&dkCqDш;vUV ggg.֯_/q5y13B!<==͛3fvvvb͚5X̢DlڴI ;;;q뭷>@477KgϞo&"""1gkׯ~9ȑ#bڵ^~Z V++q ggg'{qsl8MMMr\(JsωK.I֤!>C1k,!ʕ+mc+rrr?.|||;wزe8q/L7t.V\)rptt˖-}ٸ?NYj[SUU%{9'< ⺻?i&1}t@|Pl߾}BLx4>5k  Xnضmhhh:D~vvv܉6q3444w}W "**JKRbΝbÆ _bMɊjjjħ~*֯_/|||qM7g}V;v*2!!p7oߎ$%%aŊX|9f͚ei8x ۇ111կ~LI`Νx7q1Gff&ojhhۑcǎ!((7n#<ÓDEEߏo999̙3`,\-BDDa577ȑ#8|0>'N 뮻l2̛7R*/?Ƈ~jDEEaXjRSSagg'uڅ _b֮ͅ]| .L&:Dt:8qQRR$''cѢEXh,XOOOC%3QTCpaTVV K,wߍe˖aʔ)Vɢ Z_Z999ػw/ۇ*!##/ƒ%KjP&^ď?ltuua޼yX|9VX3gJdΟ?,deeXbn^mZUUk߿;֬YL,^_(((@NN>cǎAV#$$)))HJJœ9s0gΜIЎG'ODQQ q A&ᦛn2$ ,ڬ"޽_~%JKK;iiiHOOGPP!{Fvv68b(JXVBFF\\\&]v 8trrrpI@BBnf$%%!)) prr8ZIcc#N8B"??/_ϟoHofIקʰo>8z(0}t,Z .DRRbcc93ZFqq1?C!77͘2e /^ ,]~~~Rjs?;woAii)|}}qw; x@KK 9l|W(//R]w݅+Wbٲepvv:qAբGQQ*** @HH!aGLL O.^rEEE8{,z{{9s 99!uRee%كhkkCLL Ұ`?6BSS p1dgg#//ZgFzz:-[s5 Enn. PXX&8::"11III={6R)uȓNCUUΜ93g2JDRRRSSpB̞=ۦl,AKբpG?Yf!99g̘ ~| ĉ8wt:!!ߦM&uJuu5[۷B{{;BBBpB̟?C||nBTUU8z(>ӧOC&aΜ9;qw#99"Ikk+QTTd8"SQQV L6 qqqFtt4fΜ1vTWW(//Ǚ3gP^^sAV1{~G=}dӃ|N ˗R53gի CcKNNFrrjQ\\#G ''GA}}=Yfa֬Y馛0c HZ[[QQQ'O%%%8y$Z[[j8^zyyIݍٳ8 ;pwwԩSSbڴi:u*Rcc#\bWQ]] JJ="""Lxtt4ۿzzzPRR?~(//Nbcco;uuuPT#Oٳgq%?nfC⚒bЉիIo 5 A@@O;AUUoptt4%~m08 @Ixx8BBBT*(J8 KVʕ+hhh@CC\\p0hr z{{JFs%O?ؐ̔IDATӧnӦMCHH RԄZ\rx"*++QQQJr9 IٳyNtx񢡯6츪Q[[ Nda1/ ]ss3h؈z\z˗Q__.k "HFFF~Fڊ2ígΜAmm-)Sֳ~|I>.B z*P[[oVTbbb`MMM駟nH4wwwaYno>[A3F[[QWW>whiiשsw0#Vc+JM?s7~ꯣ0#TSSc!mnnXXCrNNN2 *􉟏Ows#D4hjj2Ѿ}b=}_W  }l<== }CѠW^5^~c@X8o...7)OOO>NȒ14 GD{{;Յtvv_ܷM'N0g NNN0W2?rֆ5MAT ׈l4""""""F0A#"""""LЈl4""""""F0A#"""""LЈl4""""""F0A#"""""LЈl4""""""F0A#"""""LЈl4""""""F0A#"""""LЈl4""""""F0A#"""""LЈl4""""""F0A#"""""LЈl4""""""F8HIST~;::"11jMV2!:"""""VxyyU{n GD<őh;ӂ 4""""IH'չbVhrcFDDDD4-]nnnC>舵k 4""""Is}ׯrT4""""I.33===>兌 +G4y1A#""""{㎎X~GMr̼!A#""""";v Xpp0._ L&QTΝPNNNdrfeLЈ2 <4n(HDDDDDӧO#110}tTTTH#hDDDDDHHH@TT`Æ 3I9HYNCKK ZZZV Z VkXᨨ'nnnpvv\.FSƩ&T*\|uuuz*  Zv ( " JG@@Np8;;[,  kllDYYPYYjTUUARٰT* I? G <==gggPGb޼yhkkCOOyj߭hiiAmm-P__o `ԩ6m"""ebFDDDDdt:Ξ=|:uʐ]rӧ#""[hhhSmMSS.^Juíppp@dd$$ 00P |塠jG|| VX˗cɒ%: ܹsg:tXt)֬Y˗ uuu/G}lcժUXv-/^ ]~{Ů]p`ݺuذan34""""Q믿> j˗/??;MZ֭[QRR$|wXx{ڜ2 p ua.cO())u.&5k k~>єa ieKKFKK wEcFDDDDZ}}=BBB"==ݢu Bfj_ ~f裏BR U$ ,\PlᘉVhF̷~;rrre&hDDDD4ɝ;wprrZ$2r{P?ƲZ̙3 JeѤ 7775Ծ PFzp7e>gk\.[>&hDDDD4Vo d 4 1wrAZ[}}=*1A#"""Im֬YpՓ.!ܧ81YȖE+..íR4""""-Z|V{6RyZvիV4""""ԜqFEoo뷵ɓ'?׿dFDDDD޿뿢izL9mo[3k%L"33 g P5+WDvv~jT !nXƜ$כrcH rmӦMػw/JJJP(,V@ׯzѣGىdWBiiԡt:v܉dSOaʔ)x'h"cǎHHH:AwЈ //v܉^s=ذanVKޤ܌/|9H<x$ux#bFDDDDdmmm|G˃/V\իW###R8a]z{_|~Xf |Aˌ.^]v/Dnn.qmnCZZq\A~~><رcҥKfypww:QaFDDDDdAػw/;v[o)))9s:cm(**B^^~:tmmmEzz:.]4899I1A#""""N'O8x rss/// %%HLLDxxJ(**ǑSNA"44[pp4"""""̐?~eee텧'bcc8#22SLԡY{{;T*QVV2ZHNNFjj!y :lcFDDDDdCQVVӧO̙3(--EYYjjj EDD"""0uTAT"((P*ptt=\zuuuGmm-.^jT*T*444OTT!CBB`g7~ 8Ԅ~INuu5peh4~+J򂗗<<< ;;;xyy^ 777Ч ZjK[[jԄf\zbCXX!'3&hDDDDD@[[\zQ@Vp_VCK/i[n}9BaH<<< BB (J"00J وwR'bFDDDDDd#&hDDDDDD6.{IENDB`circuits-3.2.3/docs/source/index.rst000066400000000000000000000013121460335514400174370ustar00rootroot00000000000000================================ circuits |version| Documentation ================================ :Release: |release| :Date: |today| About ===== .. include:: ../../README.rst .. _documentation-index: Documentation ============= .. toctree:: :maxdepth: 1 start/index tutorials/index man/index web/index api/index dev/index changes roadmap contributors faq .. toctree:: :hidden: glossary examples/index .. ifconfig:: devel .. toctree:: :hidden: todo readme Indices and tables ================== * :ref:`Index ` * :ref:`modindex` * :ref:`search` * :doc:`glossary` .. ifconfig:: devel * :doc:`todo` * :doc:`readme` circuits-3.2.3/docs/source/man/000077500000000000000000000000001460335514400163545ustar00rootroot00000000000000circuits-3.2.3/docs/source/man/bridge.rst000066400000000000000000000000601460335514400203360ustar00rootroot00000000000000.. module:: circuits.core.bridge Bridge ====== circuits-3.2.3/docs/source/man/components.rst000066400000000000000000000101741460335514400212760ustar00rootroot00000000000000.. module:: circuits.core.components Components ========== The architectural concept of circuits is to encapsulate system functionality into discrete manageable and reusable units, called *Components*, that interact by sending and handling events that flow throughout the system. Technically, a circuits *Component* is a Python class that inherits (*directly or indirectly*) from :class:`~BaseComponent`. Components can be sub-classed like any other normal Python class, however components can also be composed of other components and it is natural to do so. These are called *Complex Components*. An example of a Complex Component within the circuits library is the :class:`circuits.web.servers.Server` Component which is comprised of: - :class:`circuits.net.sockets.TCPServer` - :class:`circuits.web.servers.BaseServer` - :class:`circuits.web.http.HTTP` - :class:`circuits.web.dispatchers.dispatcher.Dispatcher` .. note:: There is no class or other technical means to mark a component as a complex component. Rather, all component instances in a circuits based application belong to some component tree (there may be several), with Complex Components being a subtree within that structure. A Component is attached to the tree by registering with the parent and detached by unregistering itself. See methods: - :meth:`~BaseComponent.register` - :meth:`~BaseComponent.unregister` The hierarchy of components facilitates addition and removal of complex components at runtime. All registered components in the hierarchy receive all applicable events regardless of lineage. Component Registration ---------------------- To register a component use the :meth:`~Component.register` method. .. code-block:: python :linenos: from circuits import Component class Foo(Component): """Foo Component""" class App(Component): """App Component""" def init(self): Foo().register(self) app = App() debugger = Debugger().register(app) app.run() Unregistering Components ------------------------ Components are unregistered via the :meth:`~Component.unregister` method. .. code-block:: python debugger.unregister() .. note:: You need a reference to the component you wish to unregister. The :meth:`~Component.register` method returns you a reference of the component that was registered. Convenient Shorthand Form ------------------------- After a while when your application becomes rather large and complex with many components and component registrations you will find it cumbersome to type ``.register(blah)``. circuits has several convenient methods for component registration and deregistration that work in an identical fashion to their :meth:`~Component.register` and :meth:`~Component.unregister` counterparts. These convenience methods follow normal mathematical operator precedence rules and are implemented by overloading the Python ``__add__``, ``__iadd__``, ``__sub__`` and ``__isub__``. The mapping is as follow: - :meth:`~Component.register` map to ``+`` and ``+=`` - :meth:`~Component.unregister` map to> ``-`` and ``-=`` For example the above could have been written as: .. code-block:: python :linenos: from circuits import Component class Foo(Component): """Foo Component""" class App(Component): """App Component""" def init(self): self += Foo() (App() + Debugger()).run() Implicit Component Registration(s) ---------------------------------- Sometimes it's handy to implicitly register components into another component by simply referencing the other component instance as a class attribute of the other. Example: .. code-block:: python >>> from circuits import Component >>> >>> class Foo(Component): ... """Foo Component""" ... >>> class App(Component): ... """App Component""" ... ... foo = Foo() ... >>> app = App() >>> app.components set([]) >>> The `telnet Example `_ does this for example. circuits-3.2.3/docs/source/man/debugger.rst000066400000000000000000000043201460335514400206710ustar00rootroot00000000000000Debugger ======== .. module:: circuits.core.debugger The :mod:`~circuits.core` :class:`~Debugger` component is the standard way to debug your circuits applications. It services two purposes: - Logging events as they flow through the system. - Logging any exceptions that might occurs in your application. Usage ----- Using the :class:`~Debugger` in your application is very straight forward just like any other component in the circuits component library. Simply add it to your application and register it somewhere (*it doesn't matter where*). Example: .. code-block:: python :linenos: from circuits import Component, Debugger class App(Component): """Your Application""" app = App() Debugger().register(app) app.run() Sample Output(s) ---------------- Here are some example outputs that you should expect to see when using the :class:`~Debugger` component in your application. Example Code: .. code-block:: python :linenos: from circuits import Event, Component, Debugger class foo(Event): """foo Event""" class App(Component): def foo(self, x, y): return x + y app = App() + Debugger() app.start() Run with:: python -i app.py Logged Events:: , )> )> >>> app.fire(foo(1, 2)) >>> Logged Exceptions:: >>> app.fire(foo()) >>> , TypeError('foo() takes exactly 3 arguments (1 given)',), [' File "/home/prologic/work/circuits/circuits/core/manager.py", line 561, in _dispatcher\n value = handler(*eargs, **ekwargs)\n'] handler=>, fevent=)> ERROR () {}: foo() takes exactly 3 arguments (1 given) File "/home/prologic/work/circuits/circuits/core/manager.py", line 561, in _dispatcher value = handler(*eargs, **ekwargs) circuits-3.2.3/docs/source/man/events.rst000066400000000000000000000123541460335514400204170ustar00rootroot00000000000000Events ====== Basic usage ----------- Events are objects that contain data (*arguments and keyword arguments*) about the message being sent to a receiving component. Events are triggered by using the :meth:`~circuits.core.manager.Manager.fire` method of any registered component. Some events in circuits are fired implicitly by the circuits core like the :class:`~circuits.core.events.started` event used in the tutorial or explicitly by components while handling some other event. Once fired, events are dispatched to the components that are interested in these events (*components whose event handlers match events of interest*). Events are usually fired on one or more channels, allowing components to gather in "interest groups". This is especially useful if you want to reuse basic components such as a :class:`~circuits.net.sockets.TCPServer`. A :class:`~circuits.net.sockets.TCPServer` component fires a :class:`~circuits.net.events.read` event for every package of data that it receives. If we did not have support for channels, it would be very difficult to build two servers in a single process without their read events colliding. Using channels, we can put one server and all components interested in its events on one channel, and another server and the components interested in this other server's events on another channel. Components are associated with a channel by setting their ``channel`` class or instance attribute. .. seealso:: :class:`~.components.Component` Besides having a name, events carry additional arbitrary information. This information is passed as arguments or keyword arguments to the constructor. It is then delivered to the event handler method that must have exactly the same number of arguments and keyword arguments. Of course, as is usual in Python, you can also pass additional information by setting attributes of the event object, though this usage pattern is discouraged. Filtering --------- Events can be filtered by stopping other event handlers from continuing to process the event. To do this, simply call the :meth:`~circuits.core.events.Event.stop` method. Example: .. code-block:: python @handler("foo") def stop_foo(self, event, *args, **kwargs): event.stop() Here any other event handlers also listening to "foo" will not be processed. .. note:: It's important to use priority event handlers here in this case as all event handlers and events run with the same priority unless explicitly told otherwise. .. versionchanged:: 3.0 In circuits 2.x you declared your event handler to be a filter by using ``@handler(filter=True)`` and returned a ``True``-ish value from the respective event handler to achieve the same effect. This is **no longer** the case in circuits 3.x Please use ``event.stop()`` as noted above. Events as result collectors --------------------------- Apart from delivering information to handlers, event objects may also collect information. If a handler returns something that is not ``None``, it is stored in the event's ``value`` attribute. If a second (or any subsequent) handler invocation also returns a value, the values are stored as a list. Note that the value attribute is of type :class:`~.values.Value` and you must access its property ``value`` to access the data stored (``collected_information = event.value.value``). The collected information can be accessed by handlers in order to find out about any return values from the previously invoked handlers. More useful though, is the possibility to access the information after all handlers have been invoked. After all handlers have run successfully (i.e. no handler has thrown an error) circuits may generate an event that indicates the successful handling. This event has the name of the event just handled with "Success" appended. So if the event is called ``Identify`` then the success event is called ``IdentifySuccess``. Success events aren't delivered by default. If you want successful handling to be indicated for an event, you have to set the optional attribute ``success`` of this event to ``True``. The handler for a success event must be defined with two arguments. When invoked, the first argument is the event just having been handled successfully and the second argument is (as a convenience) what has been collected in ``event.value.value`` (note that the first argument may not be called ``event``, for an explanation of this restriction as well as for an explanation why the method is called ``identify_success`` see the section on handlers). .. literalinclude:: examples/handler_returns.py :language: python :linenos: :download:`Download handler_returns.py ` Advanced usage -------------- Sometimes it may be necessary to take some action when all state changes triggered by an event are in effect. In this case it is not sufficient to wait for the completion of all handlers for this particular event. Rather, we also have to wait until all events that have been fired by those handlers have been processed (and again wait for the events fired by those events' handlers, and so on). To support this scenario, circuits can fire a ``Complete`` event. The usage is similar to the previously described success event. Details can be found in the API description of :class:`circuits.core.events.Event`. circuits-3.2.3/docs/source/man/examples/000077500000000000000000000000001460335514400201725ustar00rootroot00000000000000circuits-3.2.3/docs/source/man/examples/Telnet.dot000066400000000000000000000002011460335514400221260ustar00rootroot00000000000000strict digraph { TCPClient -> Select [weight="2.0"]; Telnet -> TCPClient [weight="1.0"]; Telnet -> File [weight="1.0"]; } circuits-3.2.3/docs/source/man/examples/Telnet.png000066400000000000000000000364511460335514400221440ustar00rootroot00000000000000PNG  IHDR XvpsBIT|d pHYsaa?i IDATxyt]HB ,b\n]ZEDEK+Z[uqt~u]quu ڊh-n 8f{?A$|Ys7=ԓW>["I$I@2$I$uI$I1H$ID$IRd $I"c$I$I@$I$E"I$)2I$I1H$ID$IRd $I"c$I$I@$I$E"I$)2I$I1H$ID$IRd $I"c$I$I@$I$E"I$)2I$I1H$ID$IRd $I"c$I$I@$I$E"I$)2I$I1H$ID$IRd $I"c$I$I@$I$E"I$)2I$I1H$ID$IRd $I"c$I$I@$I$E"I$)2I$I1H$ID$IRd $I"c$I$I@$I$E"I$)2I$I1H$ID$IRd $I"c$I$I@$I$E"I$)2I$I1H$ID$IRd $I"c$I$I@$I$E"I$)2I$I1H$ID$IRd $I"c$I$I@$I$E"I$)2I$I1H$ID$IRd $I"c$I$I@$I$E"I$)2I$I1H$ID$IRd $I"c$I$I@$I$E"I$)2I$I1H$ID$IRd $I"c$I$I@$I$E"I$)2I$I1H$ID$IRdq I>iӦtRH1dguwj% $Ije˖1fCL}*I=i,u6Ku0psYb$I&̜9nqcǒd89`_`/:f9Tt/3sA\v1D"P $Iڤj~keD2z75S)^f9裹{ׯ_V"I3Ys o 4t|nF -p,I$_$7goa s .18 ]$IuzW98 z;C^`p@&9K$I={6/UVT.GA wp?+[S $IZO&3x'(vnKywtvmZUԜ"I\L}]D>~ ;\2L-+JI$}ck*80 љ S}o9$Iq-ML xuSI$P^^Θ{G cRŋ?cj)I$0vXJI#;u~ށpU 8(ϷܲOQke$IOQ$9/]DkK2q$JKKIjm $I"r^NmX[ӻOV $I/`e5uJGv D=$SNݼ/6"I${= hH];Xoj+Y`ZmV4pO3J]$Iz5¬Y賉{6' oTj/;n̚[tݺ(ּ6ﻵWfp!/g /2zhD*jO$eUUmW^W\.C ;o~@Ϟ Ok3;B<G~8$6s&{ i"W^ih$IkDn+ְaw-/kܸquYIzl\n}$OӃ>  63u5#F'#u $CAv wg`0Xt>,=+)\`KGKx7~A@$IjC |5/9vg5˹xpXK&,cE 5dvb7j $2 _׽li)tW/_ocZPSO߿}i~p0Pl[儡c*0.lنK.+'u8IZXu pT} ݷ2c nvC,[ mi Djjy4v_z)C%m" "}SNeܹTTT׏HAj>f\EUl_3V u.ڂ O:u*ӦMcҥTVVҩS'2d%%% <  ZP6;W^atKL*ջv~Fbȑt9ՑԿG{Kg4>='v wԖ"٭^noO.dTY%#ܗWOYL#<1\w /+dmU,5^4<<ܻbqa/$i k?~)#s9.q5ܛJrmq9pmҥxu]Z aߟZb$5fQUUr7r`2lA[ec'K޽\ /RpӆAo_i$m"iqa2iW9$[~s$ߌOWkW^æָprv}JK1 "iTTTp±27G6-'4޽y7ׯ_ -zuݓ wŋ럸]T],$3Hj 8mp?x!n3;4w؁w}]pZcSF aSk$ah(*hxSO 7ݣDS$}3HjÆO3+=ZD82dv1gn.?55k9vݵgTTkp IZDR͛728/fƍcȑ1UѼԽ;x`y'ԿvQB:@$5UW]Řnl8"$8 ^~r/o@+// ;^fJP`>sCwkvoذҒ$@$5+ئRs`p  N$ۑGw 0;5S ܋poK:@$5JUM .T|L{ug֟ާ} -`:0vk@P -ܷon9@$"Q:uz +j&ikoUNqjm:n$Iҽeeelαm'|C|o}R=-75[\$ IgI >4l07^u#&5X [%p=p$5 m5݀ʂ,+VgϞMlM$Eid}Hz^@8k4Ἆ#Xb-22ccO7v]''I~{JJJu$IR ]R< :hxhT64׿I9(z(<w!sL6۠$Ij5 {pG:MM̵9d=`I /L'b\K.D$$5r&9Y8y23 #n;'ѳ',\HaaHDR~|L~x< $1IM2h x%v.K8sHN==%IRk,IMf9Ù曼Ͳ[ 82bi^L5nQ$57{@$5Y*'J~ pL*®]y $QI[d⟓&w84aI-Tݻ3q$-H@$m޽{߽ʷ L`D"fxn%p5pp"A=7dj'K@$m?S;G=5gMx F`p:͍4w[$I%m;a`HH$`ҥ\{J91\}!@sdL2駟 \IԎ@$5Yu5}7dCɢcr3mt vN$DYs9vqG~x\p'/%IZDR=̜ ^[oWfƌL:sRQQA.ߞgzM$)IMlY8˩$is@$I$EU$I$E"I$)2I$I1H$ID&-Z|w$=0HjPE<0f$IRI ?*+aH$H$uIze?N; ܠ\$5:͝ &G·w5$0HЫACF$'IdI煅0|>$IRJ]% F$7 paMI$Ip$I@$I$E"I$)2I$I1H؂PYw$#1HqW"I:UV Gw5$#1HLz5qtwE$#1H+ypiVqW#I:ԁ̛&;]$I RbE8$IRq ):pH]$IAq!I$cp$I@$I$E"I$)2I$I1HЗ_]$IR R;3s&q,Xw%$I3HȒ%пH$m"У f$u2H@ PV#GB^^I$"sB^qW#IT?}"v w5$I 3Hm\M +qDܕH$mZ" "$I$u H$ID$IRd $I"c$IFrF$ImDj#&N;+$Ij:|:t]$IR@VnR0$IR[fZ*xa D"$IDj (-#z%IJM gP\w5$I"BA.]$IRIA]>$IR{Iu3xH!X$I"c$I$I@UWp.I@r0n^  ̟w%$IDjf<(ׇ$I҆ R34 >N; zI"59sWਣ`F$1Hd2x1ewF$u2H͠~v!"I"5?+3!??j$IZDAEH_CnqW!IԺ@$I$E!X$I"c$I$I@$I$E"5¢EpP^w%$ImS:"lF$mD ACe%qtwE$ImD / iAϞqW#Iv@M;&M#o;j$I6ԀáW]$IRg0q"H]$IRۗ  WZ*\rwI$=D|9|3z@~C$9"55pݰxqnaa$IR1 x11cp$IC૯7G$=3C瞫6G[$IR{gQUZ <\8cµ `H)%I:l7(+ _~qꩮ~%I ꐞ>~Yf qDY$I᭷VgK 3~xJ$_u(O?-]wΝgpU"o$IDFe%LˆPT}]$IDRO¢Eu?ص%Iڥ)S` m=$I @,\>sys{'٠$IR\\KJYns=w|GtI&1  p\IDAT 5!S){o.br98 l=G%>0G23݋1|Ϙ8 Y`J()Ν߆ bI$)^E.pka;L󀞍|ټ0. OO`V=zO~I$H͟?FdAogs4"'$x[ɠAH `mpI$5 (2p+Ogr?lyL A⫿#X+N:!IH̜9ÿL6Kq pL"Mwa{ңGjI$IMaQ[t){ ˖1)#LT9IgK$fw٥RlOG>f+r-(IeZԣ>駟X}pO~>w9%ITZLyy9ߞCV u4=^|1%IT`Ō;˗@ |%fΜC$IڐD-"|͜HC3>w??OzIۛ I$5D J& w}#FЧO:wLyoLՎ[@mͫ8puFLC7"V$x 555;9sp19 7000:P16p {\r%\|̝;~'h"ƏK$iS "`]vؒ%KX|9{qAM+EqOc8 8p{$Hp'rE1`jy饗(..W^ݛ=>Q$cH]ڶI\'o`[Nfm0R {>\uקO.]LUH$>h 4ٳgsW#K$8uEAdk@&N:w_" ma$IȱKqq1]w7UAc]'o8co`7 9E|ﺯ@A~~wF:E$I-RXXwSO=ŰaØ8q"eee@&.nijFs Wzw)e\ Nֻwo:w3<ŋYjU#$IbQ$|L|&*$IJ0YS U.G$CD-bcj"A&B)f)))Iq'"w(֍C9$R$I:<ZĞ{M8?sα"I$Z%]\n+Y.$INBWbN;1/g.y] N8褓?aBĭK$.s1qG k"AErm1.I@Ԣ>h~IGcAMvma˒$IjCJKK)sO j&CnudN>_扒$I= jqEEEKTsD:ͧ-$d9Ǝ5|H$2Edt'YDm/J$BEf[s ~hK]~7/܅3$IjVEO><ӌ=ǻvetOx0L_?^ynFN8!=O`I$('+697pロUHpTP/)TTi,;|[\|e\z~sm6 = ¨QЧOd_K$I 0(vWfܸq7z4SLtΉ9I@ԪdY>CN… LRXX)))_~Uy9"IZ@$I$EA)$I"c$I$I@aUW]$IRcQr%|3|AܕH$u,uHݻ;‚qW#Iq@!%p)пcqW$I1@a0r$ <V]$IRgQիH$}35 !WñjjH$2H@^pP\$IRHA]$II$I1H$ID$IRd $I"c6!`$X$J$I> 550{v[_]$IRf6!//ܨ0CHEEI$]i3Bi)n$IMe6Sq1u|\I$=# $I|Na򸫑$Ij[Ap(- H$i@$I$E!X$I"c$I$I@fjTV]$IRdQg2qW#I@fH{|<J$m"5O>g5H$"]vOނɓF$H]^o[ /@׮qW$I?Ԃ8" !?=z@qW$I/ԂLJj+7j$IJ$I%I$E"I$)2I$I1H1s$IH R^y%'D$0H1wJwtIQ2xy(*w"IebvQn&@a!SI$`I1K$`P0~2$IZDjR)1xV"IaZ<8,,Iw!Z\i@$SI$I﬒$I"c$I$I@6 ͋ I-cڈɓa8䓸+$Ij:F|aY(j$I"ql< \wE$Igڐ|8lH`+$Ijt 0v,TW]$I3HmV[=!KIqW#IAq!i> :%I@$I$E!X$I"c$I$I@vy%IRkdڡ[aժ+$IZDj d2-"j$I2HPQsCAMMI$ R;U\ g_~ > \I$@v[߂#`\xipQI7 C”)+qW#I:tHjy{5J$IRGeH$IC$I$E"I$)2I$I1H$IDVgL&J$IRG`:=BƏwtI RfٳÞ$I-"AओF$g. (++$IR{dRx v9$IR{,IH${߃a$HŐpYΝD$7I$Iq$I@$I$E"I$)2IxcI3HlK_']$Ij 6[á /O>?.Iv F pa`((χ^N$vHjD ?_=\ PVom$3Hj _ wKf̀ X|P$fIJX;pl~8,ITHjj_畕aǶۮ=pp-Iu@$5Iy9{/,[Vc&I9KRt]}~dxhk$IDR g u9xhk$IDv[92^ ㏣I$^I[l>|A5n\B$IDR2?~ϙL8jŊpլ ?K"x rH9Y_34%ճ$IReԬ># CaaRQ˗رa$I U"ÆN;-=3#.IRGeR)8 5sBY?x".I:g[m;!dj5 r9>&NJI5ӵk[z.PT k[o[$IV"!e} p~zN$IRg׷o[z2~0 R$IDR$v N9s,<,^mM$)zɓ>WT\=zUU>SNe֬Yd(((wޔPRRB~Hl"Ibg o}??pj{1rmd2 tA@U23ٓg_n7$I")rA> 3g=bE\oA|[Buu9_n˖qx*ňl}=u< `**t% Gz(?9($IjDR,2;>8 ${o rW{߹|p!r\ 4vj`7t޽y$IlIѣaѢpw߅.)}%ey R)zl5ˑG @$5DR{!X˗W2nX4p9jƶ>F%LиqvitI9\WR얞_sϜJgFf< fnA$mDRzY3O?˄ -V@pJ0rMւI 9KR}YN8~QUف{EԲ$I= bj*.5cI.|`L69soa˒$ulIkXt)wrDo/?̙3'%I bj*.f>LrmT$IDRl>4PC>pa&_wfx$IjDR,,Y/ `-p<09Z~ ^nC$u,I:t(+Wg< `y l|1.IRb+W;0 G85?z݁36ܻ]εop3@/kmocsR$dO$/w:]ҩS'55׳ ,a a _ \XeaCr3gΤnݺ5w$I3H\:f!C1m=}k;Ꜵ+)?fwWTG5;s/¹ $ Xs:p>& p0W >Y'Fa= Ӄ־;Dej:G׾~Aس+!R/q_]bwlp.U{UWWovItI/(jcXD oxcN2˞= CfEhQ$5`Iܲey/` 7yT&?'Qn!>0S}j9p֣ÿ |r+{B$Ij!I+**b]weNp8_nxk5O.;0@,$  A} ^5ױݻ7ƍk+I֑zSOqI'1Fߟy ]$I= bk@T I=Xl6~IYN9M^+IoA8oR*d?@q5MЂRTjb"dA%xAJt` M/qЩ-ضϜB}J={211IO}sĉ{w8Ã$N;u*]7蛡|xl:~8Io` SSS}`}2pnn?z³od#9}t} r|bљ'94{xnܺxO\ ,|V|3?/>P%yʕ` The handler decorator on line 14 turned the method ``system_started`` into an event handler for the event ``started``. When defining explicit event handlers in this way, it's convention to use the following pattern:: @handler("foo") def print_foobar(self, ...): print("FooBar!") This makes reading code clear and concise and obvious to the reader that the method is not part of the class's public API (*leading underscore as per Python convention*) and that it is invoked for events of type ``SomeEvent``. The optional keyword argument "``channel``" can be used to attach the handler to a different channel than the component's channel (*as specified by the component's channel attribute*). Handler methods must be declared with arguments and keyword arguments that match the arguments passed to the event upon its creation. Looking at the API for :class:`~circuits.core.events.started` you'll find that the component that has been started is passed as an argument to its constructor. Therefore, our handler method must declare one argument (*Line 14*). The :func:`~circuits.core.handlers.handler` decorator accepts other keyword arguments that influence the behavior of the event handler and its invocation. Details can be found in the API description of :func:`~circuits.core.handlers.handler`. Implicit Event Handlers ----------------------- To make things easier for the developer when creating many event handlers and thus save on some typing, the :class:`~circuits.core.components.Component` can be used and subclassed instead which provides an implicit mechanism for creating event handlers. Basically every method in the component is automatically and implicitly marked as an event handler with ``@handler()`` where ```` is the name of each method applied. The only exceptions are: - Methods that start with an underscore ``_``. - Methods already marked explicitly with the :func:`~circuits.core.handlers.handler` decorator. Example: .. code:: python #!/usr/bin/env python from circuits import handler, Component, Event class hello(Event): """hello Event""" class App(Component): def _say(self, message): """Print the given message This is a private method as denoted via the prefixed underscore. This will not be turned into an event handler. """ print(message) def started(self, manager): self._say("App Started!") self.fire(hello()) raise SystemExit(0) @handler("hello") def print_hello(self): """hello Event Handlers Print "Hello World!" when the ``hello`` Event is received. As this is already decorated with the ``@handler`` decorator, it will be left as it is and won't get touched by the implicit event handler creation mechanisms. """ print("Hello World!") @handler(False) def test(self, *args, **kwargs): """A simple test method that does nothing This will not be turned into an event handlers because of the ``False`` argument passed to the ``@handler`` decorator. This only makes sense when subclassing ``Component`` and you want to have fine grained control over what methods are not turned into event handlers. """ pass App().run() .. note:: You can specify that a method will not be marked as an event handler by passing ``False`` as the first argument to ``@handler()``. circuits-3.2.3/docs/source/man/helpers.rst000066400000000000000000000000631460335514400205470ustar00rootroot00000000000000.. module:: circuits.core.helpers Helpers ======= circuits-3.2.3/docs/source/man/index.rst000066400000000000000000000005241460335514400202160ustar00rootroot00000000000000==================== circuits User Manual ==================== Core Library ------------ .. toctree:: :maxdepth: 2 bridge components debugger events handlers helpers loader manager pollers timers utils values workers Miscellaneous ------------- .. toctree:: :maxdepth: 2 misc/tools circuits-3.2.3/docs/source/man/loader.rst000066400000000000000000000000601460335514400203500ustar00rootroot00000000000000.. module:: circuits.core.loader Loader ====== circuits-3.2.3/docs/source/man/manager.rst000066400000000000000000000023221460335514400205170ustar00rootroot00000000000000Manager ======= .. module:: circuits.core.manager The :mod:`~circuits.core` :class:`~Manager` class is the base class of all components in circuits. It is what defines the API(s) of all components and necessary machinery to run your application smoothly. .. note:: It is not recommended to actually use the :class:`~Manager` in your application code unless you know what you're doing. .. warning:: A :class:`~Manager` **does not** know how to register itself to other components! It is a manager, not a component, however it does form the basis of every component. Usage ----- Using the :class:`~Manager` in your application is not really recommended except in some special circumstances where you want to have a top-level object that you can register things to. Example: .. code-block:: python :linenos: from circuits import Component, Manager class App(Component): """Your Application""" manager = Manager() App().register(manager) manager.run() .. note:: If you *think* you need a :class:`~Manager` chances are you probably don't. Use a :class:`~circuits.core.components.Component` instead. circuits-3.2.3/docs/source/man/misc/000077500000000000000000000000001460335514400173075ustar00rootroot00000000000000circuits-3.2.3/docs/source/man/misc/tools.rst000066400000000000000000000055251460335514400212100ustar00rootroot00000000000000Tools ===== There are two main tools of interest in circuits. These are: - :py:func:`circuits.tools.inspect` - :py:func:`circuits.tools.graph` These can be found in the :py:mod:`circuits.tools` module. Introspecting your Application ------------------------------ The :py:func:`~circuits.tools.inspect` function is used to help introspect your application by displaying all the channels and events handlers defined through the system including any additional meta data about them. Example: .. code:: pycon >>> from circuits import Component >>> class App(Component): ... def foo(self): ... pass ... >>> app = App() >>> from circuits.tools import inspect >>> print(inspect(app)) Components: 0 Event Handlers: 3 unregister; 1 foo; 1 prepare_unregister_complete; 1 ][prepare_unregister_complete] (App._on_prepare_unregister_complete)> Displaying a Visual Representation of your Application ------------------------------------------------------ The :py:func:`~circuits.tools.graph` function is used to help visualize the different components in your application and how they interact with one another and how they are registered in the system. In order to get a image from this you must have the following packages installed: - `networkx `_ - `pygraphviz `_ - `matplotlib `_ You can install the required dependencies via:: pip install matplotlib networkx pygraphviz Example: .. code:: pycon >>> from circuits import Component, Debugger >>> from circuits.net.events import write >>> from circuits.net.sockets import TCPServer >>> >>> class EchoServer(Component): ... def init(self, host="0.0.0.0", port=8000): ... TCPServer((host, port)).register(self) ... Debugger().register(self) ... def read(self, sock, data): ... self.fire(write(sock, data)) ... >>> server = EchoServer() >>> >>> from circuits.tools import graph >>> print(graph(server)) * * * An output image will be saved to your current working directory and by called ``.png`` where **** is the name of the top-level component in your application of the value you pass to the ``name=`` keyword argument of ``~circuits.tools.graph``. Example output of `telnet Example `_: .. image:: ../examples/Telnet.png And its DOT Graph: .. graphviz:: ../examples/Telnet.dot circuits-3.2.3/docs/source/man/pollers.rst000066400000000000000000000000631460335514400205650ustar00rootroot00000000000000.. module:: circuits.core.pollers Pollers ======= circuits-3.2.3/docs/source/man/timers.rst000066400000000000000000000000601460335514400204050ustar00rootroot00000000000000.. module:: circuits.core.timers Timers ====== circuits-3.2.3/docs/source/man/utils.rst000066400000000000000000000000551460335514400202460ustar00rootroot00000000000000.. module:: circuits.core.utils Utils ===== circuits-3.2.3/docs/source/man/values.rst000066400000000000000000000060351460335514400204110ustar00rootroot00000000000000Values ====== .. module:: circuits.core.values The :mod:`~circuits.core` :class:`~Value` class is an internal part of circuits' `Futures and Promises `_ used to fulfill promises of the return value of an event handler and any associated chains of events and event handlers. Basically when you fire an event ``foo()`` such as: .. code-block:: python x = self.fire(foo()) ``x`` here is an instance of the :class:`~Value` class which will contain the value returned by the event handler for ``foo`` in the ``.value`` property. .. note:: There is also :meth:`~Value.getValue` which can be used to also retrieve the underlying value held in the instance of the :class:`~Value` class but you should not need to use this as the ``.value`` property takes care of this for you. The only other API you may need in your application is the :py:attr:`~Value.notify` which can be used to trigger a ``value_changed`` event when the underlying :class:`~Value` of the event handler has changed. In this way you can do something asynchronously with the event handler's return value no matter when it finishes. Example Code: .. code-block:: python :linenos: #!/usr/bin/python -i from circuits import handler, Event, Component, Debugger class hello(Event): "hello Event" class test(Event): "test Event" class App(Component): def hello(self): return "Hello World!" def test(self): return self.fire(hello()) @handler("hello_value_changed") def _on_hello_value_changed(self, value): print("hello's return value was: {}".format(value)) app = App() Debugger().register(app) Example Session: .. code-block:: console :linenos: $ python -i ../app.py >>> x = app.fire(test()) >>> x.notify = True >>> app.tick() , )> >>> app.tick() >>> app.tick() ] ( )> >>> app.tick() >>> x >>> x.value 'Hello World!' >>> The :py:attr:`Value.notify` attribute can also be set to the name of an event which should be used to fire the ``value_changed`` event to. If the form ``x.notify = True`` used then the event that gets fired is a concatenation of the original event and the ``value_changed`` event. e.g: ``foo_value_changed``. .. note:: This is a bit advanced and should only be used by experienced users of the circuits framework. If you simply want basic synchronization of event handlers it's recommended that you try the :meth:`circuits.Component.call` and :meth:`circuits.Component.wait` synchronization primitives first. circuits-3.2.3/docs/source/man/workers.rst000066400000000000000000000000631460335514400206010ustar00rootroot00000000000000.. module:: circuits.core.workers Workers ======= circuits-3.2.3/docs/source/readme.rst000066400000000000000000000001231460335514400175640ustar00rootroot00000000000000================ PyPi README Page ================ .. include:: ../../README.rst circuits-3.2.3/docs/source/roadmap.rst000066400000000000000000000002641460335514400177600ustar00rootroot00000000000000Road Map ======== We managed our Roadmap on our [Github Project](https://github.com/circuits/circuits) .. seealso:: [Milestones](https://github.com/circuits/circuits/milestones) circuits-3.2.3/docs/source/start/000077500000000000000000000000001460335514400167365ustar00rootroot00000000000000circuits-3.2.3/docs/source/start/downloading.rst000066400000000000000000000011471460335514400220000ustar00rootroot00000000000000Downloading =========== Latest Stable Release --------------------- The latest stable releases can be downloaded from the `Releases `_ page (*specifically the Tags tab*). Latest Development Source Code ------------------------------ We use `Git `_ for source control and code sharing. The latest development branch can be cloned using the following command: .. code-block:: sh $ git clone https://github.com/circuits/circuits.git For further instructions on how to use Git, please refer to the `Git Website `_. circuits-3.2.3/docs/source/start/index.rst000066400000000000000000000001651460335514400206010ustar00rootroot00000000000000Getting Started =============== .. toctree:: :maxdepth: 2 quick downloading installing requirements circuits-3.2.3/docs/source/start/installing.rst000066400000000000000000000017341460335514400216410ustar00rootroot00000000000000Installing ========== Installing from a Source Package -------------------------------- *If you have downloaded a source archive, this applies to you.* .. code-block:: sh $ python setup.py install For other installation options see: .. code-block:: sh $ python setup.py --help install Installing from the Development Repository ------------------------------------------ *If you have cloned the source code repository, this applies to you.* If you have cloned the development repository, it is recommended that you use setuptools and use the following command: .. code-block:: sh $ python setup.py develop This will allow you to regularly update your copy of the circuits development repository by simply performing the following in the circuits working directory: .. code-block:: sh $ hg pull -u .. note:: You do not need to reinstall if you have installed with setuptools via the circuits repository and used setuptools to install in "develop" mode. circuits-3.2.3/docs/source/start/quick.rst000066400000000000000000000012161460335514400206040ustar00rootroot00000000000000.. _pip: http://pypi.python.org/pypi/pip Quick Start Guide ================= The easiest way to download and install circuits is to use the `pip`_ command: .. code-block:: sh $ pip install circuits Now that you have successfully downloaded and installed circuits, let's test that circuits is properly installed and working. First, let's check the installed version: .. code-block:: python >>> import circuits >>> print circuits.__version__ This should output: .. program-output: python -c "import circuits; print circuits.__version__" Try some of the examples in the examples/ directory shipped with the distribution. Have fun :) circuits-3.2.3/docs/source/start/requirements.rst000066400000000000000000000014301460335514400222110ustar00rootroot00000000000000.. _Python Standard Library: http://docs.python.org/library/ Requirements and Dependencies ============================= - circuits has no **required** dependencies beyond the `Python Standard Library`_. - Python: >= 2.7 or pypy >= 2.0 :Supported Platforms: Linux, FreeBSD, Mac OS X, Windows :Supported Python Versions: 2.7, 3.4, 3.5, 3.6, 3.7, 3.8, 3.9, 3.10 :Supported pypy Versions: 2.0 Other Optional Dependencies --------------------------- These dependencies are not strictly required and only add additional features. - `pydot `_ -- For rendering component graphs of an application. - `pyinotify `_ -- For asynchronous file system event notifications and the :mod:`circuits.io.notify` module. circuits-3.2.3/docs/source/todo.rst000066400000000000000000000000661460335514400173020ustar00rootroot00000000000000Documentation TODO ================== .. todolist:: circuits-3.2.3/docs/source/tutorials/000077500000000000000000000000001460335514400176275ustar00rootroot00000000000000circuits-3.2.3/docs/source/tutorials/index.rst000066400000000000000000000001671460335514400214740ustar00rootroot00000000000000================== circuits Tutorials ================== .. toctree:: :maxdepth: 2 woof/index telnet/index circuits-3.2.3/docs/source/tutorials/telnet/000077500000000000000000000000001460335514400211225ustar00rootroot00000000000000circuits-3.2.3/docs/source/tutorials/telnet/Telnet.dot000066400000000000000000000002011460335514400230560ustar00rootroot00000000000000strict digraph { TCPClient -> Select [weight="2.0"]; Telnet -> TCPClient [weight="1.0"]; Telnet -> File [weight="1.0"]; } circuits-3.2.3/docs/source/tutorials/telnet/index.rst000066400000000000000000000132001460335514400227570ustar00rootroot00000000000000.. _Python Programming Language: http://www.python.org/ Telnet Tutorial =============== Overview -------- Welcome to our 2nd circuits tutorial. This tutorial is going to walk you through the `telnet Example `_ showing you how to various parts of the circuits component library for building a simple TCP client that also accepts user input. Be sure you have circuits installed before you start: .. code-block:: bash pip install circuits See: :doc:`../../start/installing` Components ---------- You will need the following components: 1. The :class:`~.net.sockets.TCPClient` Component 2. The :class:`~.io.file.File` Component 3. The :class:`~.Component` Component All these are available in the circuits library so there is nothing for you to do. Click on each to read more about them. Design ------ .. graphviz:: Telnet.dot The above graph is the overall design of our Telnet application. What's shown here is a relationship of how the components fit together and the overall flow of events. For example: 1. Connect to remote TCP Server. 2. Read input from User. 3. Write input from User to connected Socket. 4. Wait for data from connected Socket and display. .. note:: The :class:`~.core.pollers.Select` Component shown is required by our application for Asynchronous I/O polling however we do not need to explicitly use it as it is automatically imported and registered simply by utilizing the :class:`~.net.sockets.TCPClient` Component. Implementation -------------- Without further delay here's the code: .. literalinclude:: telnet.py :language: python :linenos: :download:`Download telnet.py ` Discussion ---------- Some important things to note... 1. Notice that we defined a ``channel`` for out ``Telnet`` Component? This is so that the events of :class:`~.net.sockets.TCPClient` and :class:`~.io.file.File` don't collide. Both of these components share a very similar interface in terms of the events they listen to. .. code-block:: python class Telnet(Component): channel = "telnet" 2. Notice as well that in defining a ``channel`` for our ``Telnet`` Component we've also "registered" the :class:`~.net.sockets.TCPClient` Component so that it has the same channel as our ``Telnet`` Component. Why? We want our ``Telnet`` Component to receive all of the events of the :class:`~.net.sockets.TCPClient` Component. .. code-block:: python TCPClient(channel=self.channel).register(self) 3. In addition to our :class:`~.net.sockets.TCPClient` Component being registered with the same ``channel`` as our ``Telnet`` Component we can also see that we have registered a :class:`~.io.file.File` Component however we have chosen a different channel here called ``stdin``. Why? We don't want the events from :class:`~.net.sockets.TCPClient` and subsequently our ``Telnet`` Component to collide with the events from :class:`~.io.file.File`. So we setup a Component for reading user input by using the :class:`~.io.file.File` Component and attaching an event handler to our ``Telnet`` Component but listening to events from our ``stdin`` channel. .. code-block:: python File(sys.stdin, channel="stdin").register(self) .. code-block:: python @handler("read", channel="stdin") def read_user_input(self, data): self.fire(write(data)) Here is what the event flow would look like if you were to register the :class:`~.Debugger` to the ``Telnet`` Component. .. code-block:: python from circuits import Debugger (Telnet(host, port) + Debugger()).run() .. code-block:: bash $ python telnet.py 10.0.0.2 8000 , )> , )> , )> )> , )> )> )> <_open[stdin] ( )> ', 'r' )> Hello World! <_read[stdin] (', mode 'r' at 0x7f32ff5ab0c0> )> <_write[telnet] ( )> <_read[telnet] ( )> Hello World! ^C )> )> Testing ------- To try this example out, download a copy of the `echoserver Example `_ and copy and paste the full source code of the ``Telnet`` example above into a file called ``telnet.py``. In one terminal run:: $ python echoserver.py In a second terminal run:: $ python telnet.py localhost 8000 Have fun! For more examples see `examples `_. .. seealso:: - :doc:`../../faq` - :doc:`../../api/index` circuits-3.2.3/docs/source/tutorials/telnet/index.rst.bak000066400000000000000000000146271460335514400235310ustar00rootroot00000000000000.. _Python Programming Language: http://www.python.org/ Telnet Tutorial =============== Overview -------- Welcome to our 2nd circuits tutorial. This tutorial is going to walk you through the `telnet Example `_ showing you how to various parts of the circuits component library for building a simple TCP client that also accepts user input. Be sure you have circuits installed before you start: .. code-block:: bash pip install circuits See: :doc:`../../start/installing` Components ---------- You will need the following components: 1. The :class:`~.net.sockets.TCPClient` Component 2. The :class:`~.io.file.File` Component 3. The :class:`~.Component` Component All these are available in the circuits library so there is nothing for you to do. Click on each to read more about them. Design ------ .. graphviz:: ../../man/examples/Telnet.dot The above graph is the overall design of our Telnet application. What's shown here is a relationship of how the components fit together and the overall flow of events. For example: 1. Connect to remote TCP Server. 2. Read input from User. 3. Write input from User to connected Socket. 4. Wait for data from connected Socket and display. .. note:: The :class:`~.core.pollers.Select` Component shown is required by our application for Asynchronous I/O polling however we do not need to explicitly use it as it is automatically imported and registered simply by utilizing the :class:`~.net.sockets.TCPClient` Component. Implementation -------------- Without further adue here's the code: .. code-block:: python :linenos: #!/usr/bin/env python import sys from circuits.io import File from circuits import handler, Component from circuits.net.sockets import TCPClient from circuits.net.events import connect, write class Telnet(Component): channel = "telnet" def init(self, host, port): self.host = host self.port = port TCPClient(channel=self.channel).register(self) File(sys.stdin, channel="stdin").register(self) def ready(self, socket): self.fire(connect(self.host, self.port)) def read(self, data): print(data.strip()) @handler("read", channel="stdin") def read_user_input(self, data): self.fire(write(data)) host = sys.argv[1] port = int(sys.argv[2]) Telnet(host, port).run() Discussion ---------- Some important things to note... 1. Notice that we defined a ``channel`` for out ``Telnet`` Component? This is so that the events of :class:`~.net.sockets.TCPClient` and :class:`~.io.file.File` don't collide. Both of these components share a very similar interface in terms of the events they listen to. .. code-block:: python class Telnet(Component): channel = "telnet" 2. Notice as well that in defining a ``channel`` for our ``Telnet`` Component we've also "registered" the :class:`~.net.sockets.TCPClient` Component so that it has the same channel as our ``Telnet`` Component. Why? We want our ``Telnet`` Component to receieve all of the events of the :class:`~.net.sockets.TCPClient` Component. .. code-block:: python TCPClient(channel=self.channel).register(self) 3. In addition to our :class:`~.net.sockets.TCPClient` Component being registered with the same ``channel`` as our ``Telnet`` Component we can also see that we have registered a :class:`~.io.file.File` Component however we have chosen a different channel here called ``stdin``. Why? We don't want the events from :class:`~.net.sockets.TCPClient` and subsequently our ``Telnet`` Component to collide with the events from :class:`~.io.file.File`. So we setup a Component for reading user input by using the :class:`~.io.file.File` Component and attaching an event handler to our ``Telnet`` Component but listening to events from our ``stdin`` channel. .. code-block:: python File(sys.stdin, channel="stdin").register(self) .. code-block:: python @handler("read", channel="stdin") def read_user_input(self, data): self.fire(write(data)) Here is what the event flow would look like if you were to register the :class:`~.Debugger` to the ``Telnet`` Component. .. code-block:: python from circuits import Debugger (Telnet(host, port) + Debugger()).run() .. code-block:: bash $ python telnet.py 10.0.0.2 8000 , )> , )> , )> )> , )> )> )> <_open[stdin] ( )> ', 'r' )> Hello World! <_read[stdin] (', mode 'r' at 0x7f32ff5ab0c0> )> <_write[telnet] ( )> <_read[telnet] ( )> Hello World! ^C )> )> Testing ------- To try this example out, download a copy of the `echoserver Example `_ and copy and paste the full source code of the ``Telnet`` example above into a file called ``telnet.py``. In one terminal run:: $ python echoserver.py In a second terminal run:: $ python telnet.py localhost 8000 Have fun! For more examples see `examples `_. .. seealso:: - :doc:`../../faq` - :doc:`../../api/index` circuits-3.2.3/docs/source/tutorials/telnet/telnet.py000066400000000000000000000013571460335514400227750ustar00rootroot00000000000000#!/usr/bin/env python import sys from circuits import Component, handler from circuits.io import File from circuits.net.events import connect, write from circuits.net.sockets import TCPClient class Telnet(Component): channel = 'telnet' def init(self, host, port): self.host = host self.port = port TCPClient(channel=self.channel).register(self) File(sys.stdin, channel='stdin').register(self) def ready(self, socket): self.fire(connect(self.host, self.port)) def read(self, data): print(data.strip()) @handler('read', channel='stdin') def read_user_input(self, data): self.fire(write(data)) host = sys.argv[1] port = int(sys.argv[2]) Telnet(host, port).run() circuits-3.2.3/docs/source/tutorials/woof/000077500000000000000000000000001460335514400206015ustar00rootroot00000000000000circuits-3.2.3/docs/source/tutorials/woof/001.py000066400000000000000000000001121460335514400214450ustar00rootroot00000000000000#!/usr/bin/env python from circuits import Component Component().run() circuits-3.2.3/docs/source/tutorials/woof/002.py000066400000000000000000000002031460335514400214470ustar00rootroot00000000000000#!/usr/bin/env python from circuits import Component class MyComponent(Component): """My Component""" MyComponent().run() circuits-3.2.3/docs/source/tutorials/woof/003.py000066400000000000000000000002501460335514400214520ustar00rootroot00000000000000#!/usr/bin/env python from circuits import Component class MyComponent(Component): def started(self, *args): print('Hello World!') MyComponent().run() circuits-3.2.3/docs/source/tutorials/woof/004.py000066400000000000000000000003751460335514400214630ustar00rootroot00000000000000#!/usr/bin/env python from circuits import Component class Bob(Component): def started(self, *args): print("Hello I'm Bob!") class Fred(Component): def started(self, *args): print("Hello I'm Fred!") (Bob() + Fred()).run() circuits-3.2.3/docs/source/tutorials/woof/005.py000066400000000000000000000007641460335514400214660ustar00rootroot00000000000000#!/usr/bin/env python from circuits import Component from circuits.tools import graph class Pound(Component): def __init__(self): super().__init__() self.bob = Bob().register(self) self.fred = Fred().register(self) def started(self, *args): print(graph(self.root)) class Bob(Component): def started(self, *args): print("Hello I'm Bob!") class Fred(Component): def started(self, *args): print("Hello I'm Fred!") Pound().run() circuits-3.2.3/docs/source/tutorials/woof/006.py000066400000000000000000000007511460335514400214630ustar00rootroot00000000000000#!/usr/bin/env python from circuits import Component, Event class woof(Event): """woof Event""" class Pound(Component): def __init__(self): super().__init__() self.bob = Bob().register(self) self.fred = Fred().register(self) def started(self, *args): self.fire(woof()) class Dog(Component): def woof(self): print("Woof! I'm %s!" % self.name) class Bob(Dog): """Bob""" class Fred(Dog): """Fred""" Pound().run() circuits-3.2.3/docs/source/tutorials/woof/007.py000066400000000000000000000010361460335514400214610ustar00rootroot00000000000000#!/usr/bin/env python from circuits import Component, Event class woof(Event): """woof Event""" class Pound(Component): def __init__(self): super().__init__() self.bob = Bob().register(self) self.fred = Fred().register(self) def started(self, *args): self.fire(woof(), self.bob) class Dog(Component): def woof(self): print("Woof! I'm %s!" % self.name) class Bob(Dog): """Bob""" channel = 'bob' class Fred(Dog): """Fred""" channel = 'fred' Pound().run() circuits-3.2.3/docs/source/tutorials/woof/008.py000066400000000000000000000010241460335514400214570ustar00rootroot00000000000000#!/usr/bin/env python from circuits import Component, Event class bark(Event): """bark Event""" class Pound(Component): def __init__(self): super().__init__() self.bob = Bob().register(self) self.fred = Fred().register(self) class Dog(Component): def started(self, *args): self.fire(bark()) def bark(self): print("Woof! I'm %s!" % self.name) class Bob(Dog): """Bob""" channel = 'bob' class Fred(Dog): """Fred""" channel = 'fred' Pound().run() circuits-3.2.3/docs/source/tutorials/woof/009.py000066400000000000000000000010271460335514400214630ustar00rootroot00000000000000#!/usr/bin/env python from circuits import Component, Event class bark(Event): """bark Event""" class Pound(Component): def __init__(self): super().__init__() self.bob = Bob().register(self) self.fred = Fred().register(self) class Dog(Component): def started(self, *args): self.fire(bark()) def bark(self): print("Woof! I'm %s!" % name) # noqa class Bob(Dog): """Bob""" channel = 'bob' class Fred(Dog): """Fred""" channel = 'fred' Pound().run() circuits-3.2.3/docs/source/tutorials/woof/index.rst000066400000000000000000000275741460335514400224610ustar00rootroot00000000000000.. _Python Programming Language: http://www.python.org/ Tutorial ======== Overview -------- Welcome to the circuits tutorial. This 5-minute tutorial will guide you through the basic concepts of circuits. The goal is to introduce new concepts incrementally with walk-through examples that you can try out! By the time you've finished, you should have a good basic understanding of circuits, how it feels and where to go from there. The Component ------------- First up, let's show how you can use the ``Component`` and run it in a very simple application. .. literalinclude:: 001.py :language: python :linenos: :download:`Download 001.py <001.py>` Okay so that's pretty boring as it doesn't do very much! But that's okay... Read on! Let's try to create our own custom Component called ``MyComponent``. This is done using normal Python subclassing. .. literalinclude:: 002.py :language: python :linenos: :download:`Download 002.py <002.py>` Okay, so this still isn't very useful! But at least we can create custom components with the behavior we want. Let's move on to something more interesting... .. note:: Component(s) in circuits are what sets circuits apart from other Asynchronous or Concurrent Application Frameworks. Components(s) are used as building blocks from simple behaviors to complex ones (*composition of simpler components to form more complex ones*). Event Handlers -------------- Let's now extend our little example to say "Hello World!" when it's started. .. literalinclude:: 003.py :language: python :linenos: :download:`Download 003.py <003.py>` Here we've created a simple **Event Handler** that listens for the ``started`` Event. .. note:: Methods defined in a custom subclassed ``Component`` are automatically turned into **Event Handlers**. The only exception to this are methods prefixed with an underscore (``_``). .. note:: If you do not want this *automatic* behavior, inherit from ``BaseComponent`` instead which means you will **have to** use the ``~circuits.core.handlers.handler`` decorator to define your **Event Handlers**. Running this we get:: Hello World! Alright! We have something slightly more useful! Whoohoo it says hello! .. note:: Press ^C (*CTRL + C*) to exit. Registering Components ---------------------- So now that we've learned how to use a Component, create a custom Component and create simple Event Handlers, let's try something a bit more complex by creating a complex component made up of two simpler ones. .. note:: We call this **Component Composition** which is the very essence of the circuits Application Framework. Let's create two components: - ``Bob`` - ``Fred`` .. literalinclude:: 004.py :language: python :linenos: :download:`Download 004.py <004.py>` Notice the way we register the two components ``Bob`` and ``Fred`` together ? Don't worry if this doesn't make sense right now. Think of it as putting two components together and plugging them into a circuit board. Running this example produces the following result:: Hello I'm Bob! Hello I'm Fred! Cool! We have two components that each do something and print a simple message on the screen! Complex Components ------------------ Now, what if we wanted to create a Complex Component? Let's say we wanted to create a new Component made up of two other smaller components? We can do this by simply registering components to a Complex Component during initialization. .. note:: This is also called **Component Composition** and avoids the classical `Diamond problem `_ of Multiple Inheritance. In circuits we do not use Multiple Inheritance to create **Complex Components** made up of two or more base classes of components, we instead compose them together via registration. .. literalinclude:: 005.py :language: python :linenos: :download:`Download 005.py <005.py>` So now ``Pound`` is a Component that consists of two other components registered to it: ``Bob`` and ``Fred`` The output of this is identical to the previous:: * * * Hello I'm Bob! Hello I'm Fred! The only difference is that ``Bob`` and ``Fred`` are now part of a more Complex Component called ``Pound``. This can be illustrated by the following diagram: .. graphviz:: digraph G { "Pound-1344" -> "Bob-9b0c"; "Pound-1344" -> "Fred-e98a"; } .. note:: The extra lines in the above output are an ASCII representation of the above graph (*produced by pydot + graphviz*). Cool :-) Component Inheritance --------------------- Since circuits is a framework written for the `Python Programming Language`_ it naturally inherits properties of Object Orientated Programming (OOP) -- such as inheritance. So let's take our ``Bob`` and ``Fred`` components and create a Base Component called ``Dog`` and modify our two dogs (``Bob`` and ``Fred``) to subclass this. .. literalinclude:: 006.py :language: python :linenos: :download:`Download 006.py <006.py>` Now let's try to run this and see what happens:: Woof! I'm Bob! Woof! I'm Fred! So both dogs barked! Hmmm Component Channels ------------------ What if we only want one of our dogs to bark? How do we do this without causing the other one to bark as well? Easy! Use a separate ``channel`` like so: .. literalinclude:: 007.py :language: python :linenos: :download:`Download 007.py <007.py>` .. note:: Events can be fired with either the ``.fire(...)`` or ``.fireEvent(...)`` method. If you run this, you'll get:: Woof! I'm Bob! Event Objects ------------- So far in our tutorial we have been defining an Event Handler for a builtin Event called ``started``. What if we wanted to define our own Event Handlers and our own Events? You've already seen how easy it is to create a new Event Handler by simply defining a normal Python method on a Component. Defining your own Events helps with documentation and testing and makes things a little easier. Example:: class MyEvent(Event): """MyEvent""" So here's our example where we'll define a new Event called ``Bark`` and make our ``Dog`` fire a ``Bark`` event when our application starts up. .. literalinclude:: 008.py :language: python :linenos: :download:`Download 008.py <008.py>` If you run this, you'll get:: Woof! I'm Bob! Woof! I'm Fred! The Debugger ------------ Lastly... Asynchronous programming has many advantages but can be a little harder to write and follow. A silently caught exception in an Event Handler, or an Event that never gets fired, or any number of other weird things can cause your application to fail and leave you scratching your head. Fortunately circuits comes with a ``Debugger`` Component to help you keep track of what's going on in your application, and allows you to tell what your application is doing. Let's say that we defined out ``bark`` Event Handler in our ``Dog`` Component as follows:: def bark(self): print("Woof! I'm %s!" % name) Now clearly there is no such variable as ``name`` in the local scope. For reference here's the entire example... .. literalinclude:: 009.py :language: python :linenos: :download:`Download 009.py <009.py>` If you run this, you'll get: That's right! You get nothing! Why? Well in circuits any error or exception that occurs in a running application is automatically caught and dealt with in a way that lets your application "keep on going". Crashing is unwanted behavior in a system so we expect to be able to recover from horrible situations. SO what do we do? Well that's easy. circuits comes with a ``Debugger`` that lets you log all events as well as all errors so you can quickly and easily discover which Event is causing a problem and which Event Handler to look at. If you change Line 34 of our example... From: .. literalinclude:: 009.py :language: python :lines: 34 To: .. code-block:: python from circuits import Debugger (Pound() + Debugger()).run() Then run this, you'll get the following:: , ] {}> , ] {}> , ] {}> , None] {}> , NameError("global name 'name' is not defined",), [' File "/home/prologic/work/circuits/circuits/core/manager.py", line 459, in __handleEvent\n retval = handler(*eargs, **ekwargs)\n', ' File "source/tutorial/009.py", line 22, in bark\n print("Woof! I\'m %s!" % name)\n'], >] {}> ERROR (): global name 'name' is not defined File "/home/prologic/work/circuits/circuits/core/manager.py", line 459, in __handleEvent retval = handler(*eargs, **ekwargs) File "source/tutorial/009.py", line 22, in bark print("Woof! I'm %s!" % name) , NameError("global name 'name' is not defined",), [' File "/home/prologic/work/circuits/circuits/core/manager.py", line 459, in __handleEvent\n retval = handler(*eargs, **ekwargs)\n', ' File "source/tutorial/009.py", line 22, in bark\n print("Woof! I\'m %s!" % name)\n'], >] {}> ERROR (): global name 'name' is not defined File "/home/prologic/work/circuits/circuits/core/manager.py", line 459, in __handleEvent retval = handler(*eargs, **ekwargs) File "source/tutorial/009.py", line 22, in bark print("Woof! I'm %s!" % name) ^C] {}> ] {}> ] {}> You'll notice whereas there was no output before there is now a pretty detailed output with the ``Debugger`` added to the application. Looking through the output, we find that the application does indeed start correctly, but when we fire our ``Bark`` Event it coughs up two exceptions, one for each of our dogs (``Bob`` and ``Fred``). From the error we can tell where the error is and roughly where to look in the code. .. note:: You'll notice many other events that are displayed in the above output. These are all default events that circuits has builtin which your application can respond to. Each builtin Event has a special meaning with relation to the state of the application at that point. See: :py:mod:`circuits.core.events` for detailed documentation regarding these events. The correct code for the ``bark`` Event Handler should be:: def bark(self): print("Woof! I'm %s!" % self.name) Running again with our correction results in the expected output:: Woof! I'm Bob! Woof! I'm Fred! That's it folks! Hopefully this gives you a feel of what circuits is all about and an easy tutorial on some of the basic concepts. As you're no doubt itching to get started on your next circuits project, here's some recommended reading: - :doc:`../../faq` - :doc:`../../api/index` circuits-3.2.3/docs/source/web/000077500000000000000000000000001460335514400163565ustar00rootroot00000000000000circuits-3.2.3/docs/source/web/features.rst000066400000000000000000000360761460335514400207420ustar00rootroot00000000000000.. _CherryPy: http://www.cherrypy.org/ .. module:: circuits.web Features ======== circuits.web is not a **Full Stack** or **High Level** web framework, rather it is more closely aligned with `CherryPy`_ and offers enough functionality to make quickly developing web applications easy and as flexible as possible. circuits.web does not provide high level features such as: - Templating - Database access - Form Validation - Model View Controller - Object Relational Mapper The functionality that circutis.web **does** provide ensures that circuits.web is fully HTTP/1.1 and WSGI/1.0 compliant and offers all the essential tools you need to build your web application or website. To demonstrate each feature, we're going to use the classical "Hello World!" example as demonstrated earlier in :doc:`gettingstarted`. Here's the code again for easy reference: .. code-block:: python :linenos: from circuits.web import Server, Controller class Root(Controller): def index(self): return "Hello World!" (Server(8000) + Root()).run() Logging ------- circuits.web's :class:`~.Logger` component allows you to add logging support compatible with Apache log file formats to your web application. To use the :class:`~Logger` simply add it to your application: .. code-block:: python (Server(8000) + Logger() + Root()).run() Example Log Output:: 127.0.0.1 - - [05/Apr/2014:10:13:01] "GET / HTTP/1.1" 200 12 "" "curl/7.35.0" 127.0.0.1 - - [05/Apr/2014:10:13:02] "GET /docs/build/html/index.html HTTP/1.1" 200 22402 "" "curl/7.35.0" Cookies ------- Access to cookies are provided through the :class:`~.Request` Object which holds data about the request. The attribute :attr:`~.Request.cookie` is provided as part of the :class:`~.Request` Object. It is a dict-like object, an instance of ``Cookie.SimpleCookie`` from the python standard library. To demonstrate "Using Cookies" we'll write a very simple application that remembers who we are: If a cookie **name** is found, display "Hello !". Otherwise, display "Hello World!" If an argument is given or a query parameter **name** is given, store this as the **name** for the cookie. Here's how we do it: .. code-block:: python :linenos: from circuits.web import Server, Controller class Root(Controller): def index(self, name=None): if name: self.cookie["name"] = name else: name = self.cookie.get("name", None) name = "World!" if name is None else name.value return "Hello {0:s}!".format(name) (Server(8000) + Root()).run() .. note:: To access the actual value of a cookie use the ``.value`` attribute. .. warning:: Cookies can be vulnerable to XSS (*Cross Site Scripting*) attacks so use them at your own risk. See: http://en.wikipedia.org/wiki/Cross-site_scripting#Cookie_security Dispatchers ----------- circuits.web provides several dispatchers in the :mod:`~.dispatchers` module. Most of these are available directly from the circuits.web namespace by simply importing the required "dispatcher" from circuits.web. Example: .. code-block:: python from circuits.web import Static The most important "dispatcher" is the default :class:`~.Dispatcher` used by the circuits.web :class:`~.Server` to dispatch incoming requests onto a channel mapping (*remember that circuits is event-driven and uses channels*), quite similar to that of CherryPy or any other web framework that supports object traversal. Normally you don't have to worry about any of the details of the *default* :class:`~.Dispatcher` nor do you have to import it or use it in any way as it's already included as part of the circuits.web :class:`~.Server` Component structure. Static ...... The :class:`~.Static` "dispatcher" is used for serving static resources/files in your application. To use this, simply add it to your application. It takes some optional configuration which affects it's behavior. The simplest example (*as per our Base Example*): .. code-block:: python (Server(8000) + Static() + Root()).run() This will serve up files in the *current directory* as static resources. .. note:: This may override your **index** request handler of your top-most (``Root``) :class:`~.Controller`. As this might be undesirable and it's normally common to serve static resources via a different path and even have them stored in a separate physical file path, you can configure the Static "dispatcher". Static files stored in ``/home/joe/www/``: .. code-block:: python (Server(8000) + Static(docroot="/home/joe/www/") + Root()).run() Static files stored in ``/home/joe/www/`` **and** we want them served up as ``/static`` URI(s): .. code-block:: python (Server(8000) + Static("/static", docroot="/home/joe/www/") + Root()).run() Dispatcher .......... The :class:`~.Dispatcher` (*the default*) is used to dispatch requests and map them onto channels with a similar URL Mapping as CherryPy's. A set of "paths" are maintained by the Dispatcher as Controller(s) are registered to the system or unregistered from it. A channel mapping is found by traversing the set of known paths (*Controller(s)*) and successively matching parts of the path (*split by /*) until a suitable Controller and Request Handler is found. If no Request Handler is found that matches but there is a "default" Request Handler, it is used. This Dispatcher also included support for matching against HTTP methods: - GET - POST - PUT - DELETE. Here are some examples: .. code-block:: python :linenos: class Root(Controller): def index(self): return "Hello World!" def foo(self, arg1, arg2, arg3): return "Foo: %r, %r, %r" % (arg1, arg2, arg3) def bar(self, kwarg1="foo", kwarg2="bar"): return "Bar: kwarg1=%r, kwarg2=%r" % (kwarg1, kwarg2) def foobar(self, arg1, kwarg1="foo"): return "FooBar: %r, kwarg1=%r" % (arg1, kwarg1) With the following requests:: http://127.0.0.1:8000/ http://127.0.0.1:8000/foo/1/2/3 http://127.0.0.1:8000/bar?kwarg1=1 http://127.0.0.1:8000/bar?kwarg1=1&kwarg=2 http://127.0.0.1:8000/foobar/1 http://127.0.0.1:8000/foobar/1?kwarg1=1 The following output is produced:: Hello World! Foo: '1', '2', '3' Bar: kwargs1='1', kwargs2='bar' Bar: kwargs1='1', kwargs2='bar' FooBar: '1', kwargs1='foo' FooBar: '1', kwargs1='1' This demonstrates how the Dispatcher handles basic paths and how it handles extra parts of a path as well as the query string. These are essentially translated into arguments and keyword arguments. To define a Request Handler that is specifically for the HTTP ``POST`` method, simply define a Request Handler like: .. code-block:: python :linenos: class Root(Controller): def index(self): return "Hello World!" class Test(Controller): channel = "/test" def POST(self, *args, **kwargs): #*** return "%r %r" % (args, kwargs) This will handles ``POST`` requests to "/test", which brings us to the final point of creating URL structures in your application. As seen above to create a sub-structure of Request Handlers (*a tree*) simply create another :class:`~.Controller` Component giving it a different channel and add it to the system along with your existing Controller(s). .. warning:: All public methods defined in your :class:`~.Controller`(s) are exposed as valid URI(s) in your web application. If you don't want something exposed either subclass from :class:`~BaseController` whereby you have to explicitly use :meth:`~.expose` or use ``@expose(False)`` to decorate a public method as **NOT Exposed** or simply prefix the desired method with an underscore (e.g: ``def _foo(...):``). VirtualHosts ............ The :class:`~.VirtualHosts` "dispatcher" allows you to serves up different parts of your application for different "virtual" hosts. Consider for example you have the following hosts defined:: localdomain foo.localdomain bar.localdomain You want to display something different on the default domain name "localdomain" and something different for each of the sub-domains "foo.localdomain" and "bar.localdomain". To do this, we use the VirtualHosts "dispatcher": .. code-block:: python :linenos: from circuits.web import Server, Controller, VirtualHosts class Root(Controller): def index(self): return "I am the main vhost" class Foo(Controller): channel = "/foo" def index(self): return "I am foo." class Bar(Controller): channel = "/bar" def index(self): return "I am bar." domains = { "foo.localdomain:8000": "foo", "bar.localdomain:8000": "bar", } (Server(8000) + VirtualHosts(domains) + Root() + Foo() + Bar()).run() With the following requests:: http://localdomain:8000/ http://foo.localdomain:8000/ http://bar.localdomain:8000/ The following output is produced:: I am the main vhost I am foo. I am bar. The argument **domains** pasted to VirtualHosts' constructor is a mapping (*dict*) of: domain -> channel XMLRPC ...... The :class:`~.XMLRPC` "dispatcher" provides a circuits.web application with the capability of serving up RPC Requests encoded in XML (XML-RPC). Without going into too much details (*if you're using any kind of RPC "dispatcher" you should know what you're doing...*), here is a simple example: .. code-block:: python :linenos: from circuits import Component from circuits.web import Server, Logger, XMLRPC class Test(Component): def foo(self, a, b, c): return a, b, c (Server(8000) + Logger() + XMLRPC() + Test()).run() Here is a simple interactive session:: >>> import xmlrpclib >>> xmlrpc = xmlrpclib.ServerProxy("http://127.0.0.1:8000/rpc/") >>> xmlrpc.foo(1, 2, 3) [1, 2, 3] >>> JSONRPC ....... The :class:`~.JSONRPC` "dispatcher" is Identical in functionality to the :class:`~.XMLRPC` "dispatcher". Example: .. code-block:: python :linenos: from circuits import Component from circuits.web import Server, Logger, JSONRPC class Test(Component): def foo(self, a, b, c): return a, b, c (Server(8000) + Logger() + JSONRPC() + Test()).run() Interactive session (*requires the `jsonrpclib `_ library*):: >>> import jsonrpclib >>> jsonrpc = jsonrpclib.ServerProxy("http://127.0.0.1:8000/rpc/") >>> jsonrpc.foo(1, 2, 3) {'result': [1, 2, 3], 'version': '1.1', 'id': 2, 'error': None} >>> Caching ------- circuits.web includes all the usual **Cache Control**, **Expires** and **ETag** caching mechanisms. For simple expires style caching use the :meth:`~.tools.expires` tool from :mod:`.circuits.web.tools`. Example: .. code-block:: python :linenos: from circuits.web import Server, Controller class Root(Controller): def index(self): self.expires(3600) return "Hello World!" (Server(8000) + Root()).run() For other caching mechanisms and validation please refer to the :mod:`circuits.web.tools` documentation. See in particular: - :meth:`~.tools.expires` - :meth:`~.tools.validate_since` .. note:: In the example above we used ``self.expires(3600)`` which is just a convenience method built into the :class:`~.Controller`. The :class:`~.Controller` has other such convenience methods such as ``.uri``, ``.forbidden()``, ``.redirect()``, ``.notfound()``, ``.serve_file()``, ``.serve_download()`` and ``.expires()``. These are just wrappers around :mod:`~.tools` and :mod:`~.events`. Compression ----------- circuits.web includes the necessary low-level tools in order to achieve compression. These tools are provided as a set of functions that can be applied to the response before it is sent to the client. Here's how you can create a simple Component that enables compression in your web application or website. .. code-block:: python :linenos: from circuits import handler, Component from circuits.web.tools import gzip from circuits.web import Server, Controller, Logger class Gzip(Component): @handler("response", priority=1.0) def compress_response(self, event, response): event[0] = gzip(response) class Root(Controller): def index(self): return "Hello World!" (Server(8000) + Gzip() + Root()).run() Please refer to the documentation for further details: - :func:`.tools.gzip` - :func:`.utils.compress` Authentication -------------- circuits.web provides both HTTP Plain and Digest Authentication provided by the functions in :mod:`circuits.web.tools`: - :func:`.tools.basic_auth` - :func:`.tools.check_auth` - :func:`.tools.digest_auth` The first 2 arguments are always (*as with most circuits.web tools*): - ``(request, response)`` An example demonstrating the use of "Basic Auth": .. code-block:: python :linenos: from circuits.web import Server, Controller from circuits.web.tools import check_auth, basic_auth class Root(Controller): def index(self): realm = "Test" users = {"admin": "admin"} encrypt = str if check_auth(self.request, self.response, realm, users, encrypt): return "Hello %s" % self.request.login return basic_auth(self.request, self.response, realm, users, encrypt) (Server(8000) + Root()).run() For "Digest Auth": .. code-block:: python :linenos: from circuits.web import Server, Controller from circuits.web.tools import check_auth, digest_auth class Root(Controller): def index(self): realm = "Test" users = {"admin": "admin"} encrypt = str if check_auth(self.request, self.response, realm, users, encrypt): return "Hello %s" % self.request.login return digest_auth(self.request, self.response, realm, users, encrypt) (Server(8000) + Root()).run() Session Handling ---------------- Session Handling in circuits.web is very similar to Cookies. A dict-like object called **.session** is attached to every Request Object during the life-cycle of that request. Internally a Cookie named **circuits.session** is set in the response. Rewriting the Cookie Example to use a session instead: .. code-block:: python :linenos: from circuits.web import Server, Controller, Sessions class Root(Controller): def index(self, name=None): if name: self.session["name"] = name else: name = self.session.get("name", "World!") return "Hello %s!" % name (Server(8000) + Sessions() + Root()).run() .. note:: The only Session Handling provided is a temporary in-memory based one and will not persist. No future Session Handling components are planned. For persistent data you should use some kind of Database. circuits-3.2.3/docs/source/web/gettingstarted.rst000066400000000000000000000031011460335514400221330ustar00rootroot00000000000000.. _web_getting_started: Getting Started =============== Just like any application or system built with circuits, a circuits.web application follows the standard Component based design and structure whereby functionality is encapsulated in components. circuits.web itself is designed and built in this fashion. For example a circuits.web Server's structure looks like this: .. image:: ../images/CircuitsWebServer.png To illustrate the basic steps, we will demonstrate developing your classical "Hello World!" applications in a web-based way with circuits.web To get started, we first import the necessary components: .. code-block:: python from circutis.web import Server, Controller Next we define our first Controller with a single Request Handler defined as our index. We simply return "Hello World!" as the response for our Request Handler. .. code-block:: python class Root(Controller): def index(self): return "Hello World!" This completes our simple web application which will respond with "Hello World!" when anyone accesses it. *Admittedly this is a stupidly simple web application! But circuits.web is very powerful and plays nice with other tools.* Now we need to run the application: .. code-block:: python (Server(8000) + Root()).run() That's it! Navigate to: http://127.0.0.1:8000/ and see the result. Here's the complete code: .. code-block:: python :linenos: from circuits.web import Server, Controller class Root(Controller): def index(self): return "Hello World!" (Server(8000) + Root()).run() Have fun! circuits-3.2.3/docs/source/web/howtos.rst000066400000000000000000000241351460335514400204400ustar00rootroot00000000000000.. module:: circuits How To Guides ============= These "How To" guides will steer you in the right direction for common aspects of modern web applications and website design. How Do I: Use a Templating Engine --------------------------------- circuits.web tries to stay out of your way as much as possible and doesn't impose any restrictions on what external libraries and tools you can use throughout your web application or website. As such you can use any template language/engine you wish. Example: Using Mako ................... This basic example of using the Mako Templating Language. First a TemplateLookup instance is created. Finally a function called ``render(name, **d)`` is created that is used by Request Handlers to render a given template and apply data to it. Here is the basic example: .. code-block:: python :linenos: #!/usr/bin/env python import os import mako from mako.lookup import TemplateLookup from circuits.web import Server, Controller templates = TemplateLookup( directories=[os.path.join(os.path.dirname(__file__), "tpl")], module_directory="/tmp", output_encoding="utf-8" ) def render(name, **d): #** try: return templates.get_template(name).render(**d) #** except: return mako.exceptions.html_error_template().render() class Root(Controller): def index(self): return render("index.html") def submit(self, firstName, lastName): msg = "Thank you %s %s" % (firstName, lastName) return render("index.html", message=msg) (Server(8000) + Root()).run() Other Examples .............. Other Templating engines will be quite similar to integrate. How Do I: Integrate with a Database ----------------------------------- .. warning:: Using databases in an asynchronous framework is problematic because most database implementations don't support asynchronous I/O operations. Generally the solution is to use threading to hand off database operations to a separate thread. Here are some ways to help integrate databases into your application: 1. Ensure your queries are optimized and do not block for extensive periods of time. 2. Use a library like `SQLAlchemy `_ that supports multi-threaded database operations to help prevent your circuits.web web application from blocking. 3. *Optionally* take advantage of the :class:`~circuits.Worker` component to fire :class:`~circuits.task` events wrapping database calls in a thread or process pool. You can then use the :meth:`~circuits.Component.call` and :meth:`~.circuits.Component.wait` synchronization primitives to help with the control flow of your requests and responses. Another way you can help improve performance is by load balancing across multiple backends of your web application. Using things like `haproxy `_ or `nginx `_ for load balancing can really help. How Do I: Use WebSockets ------------------------ Since the :class:`~circuits.web.websockets.WebSocketDispatcher` id a circuits.web "dispatcher" it's quite easy to integrate into your web application. Here's a simple trivial example: .. code-block:: python :linenos: #!/usr/bin/env python from circuits.net.events import write from circuits import Component, Debugger from circuits.web.dispatchers import WebSocketsDispatcher from circuits.web import Controller, Logger, Server, Static class Echo(Component): channel = "wsserver" def read(self, sock, data): self.fireEvent(write(sock, "Received: " + data)) class Root(Controller): def index(self): return "Hello World!" app = Server(("0.0.0.0", 8000)) Debugger().register(app) Static().register(app) Echo().register(app) Root().register(app) Logger().register(app) WebSocketsDispatcher("/websocket").register(app) app.run() See the `circuits.web examples `_. How do I: Build a Simple Form ----------------------------- circuits.web parses all POST data as a request comes through and creates a dictionary of kwargs (Keyword Arguments) that are passed to Request Handlers. Here is a simple example of handling form data: .. code-block:: python :linenos: #!/usr/bin/env python from circuits.web import Server, Controller class Root(Controller): html = """\ Basic Form Handling

    Basic Form Handling

    Example of using circuits and it's Web Components to build a simple web application that handles some basic form data.

    First Name:
    Last Name:
    """ def index(self): return self.html def submit(self, firstName, lastName): return "Hello %s %s" % (firstName, lastName) (Server(8000) + Root()).run( How Do I: Upload a File ----------------------- You can easily handle File Uploads as well using the same techniques as above. Basically the "name" you give your tag of type="file" will get passed as the Keyword Argument to your Request Handler. It has the following two attributes:: .filename - The name of the uploaded file. .value - The contents of the uploaded file. Here's the code! .. code-block:: python :linenos: #!/usr/bin/env python from circuits.web import Server, Controller UPLOAD_FORM = """ Upload Form

    Upload Form

    Description:
    """ UPLOADED_FILE = """ Uploaded File

    Uploaded File

    Filename: %s
    Description: %s

    File Contents:

          %s
          
    """ class Root(Controller): def index(self, file=None, desc=""): if file is None: return UPLOAD_FORM else: filename = file.filename return UPLOADED_FILE % (file.filename, desc, file.value) (Server(8000) + Root()).run() circuits.web automatically handles form and file uploads and gives you access to the uploaded file via arguments to the request handler after they've been processed by the dispatcher. How Do I: Integrate with WSGI Applications ------------------------------------------ Integrating with other WSGI Applications is quite easy to do. Simply add in an instance of the :class:`~circuits.web.wsgi.Gateway` component into your circuits.web application. Example: .. code-block:: python :linenos: #!/usr/bin/env python from circuits.web.wsgi import Gateway from circuits.web import Controller, Server def foo(environ, start_response): start_response("200 OK", [("Content-Type", "text/plain")]) return ["Foo!"] class Root(Controller): """App Rot""" def index(self): return "Hello World!" app = Server(("0.0.0.0", 10000)) Root().register(app) Gateway({"/foo": foo}).register(app) app.run() The ``apps`` argument of the :class:`~circuits.web.wsgi.Gateway` component takes a key/value pair of ``path -> callable`` (*a Python dictionary*) that maps each URI to a given WSGI callable. How Do I: Deploy with Apache and mod_wsgi ----------------------------------------- Here's how to deploy your new Circuits powered Web Application on Apache using mod_wsgi. Let's say you have a Web Hosting account with some provider. - Your Username is: "joblogs" - Your URL is: http://example.com/~joeblogs/ - Your Docroot is: /home/joeblogs/www/ Configuring Apache .................. The first step is to add in the following .htaccess file to tell Apache hat we want any and all requests to http://example.com/~joeblogs/ to be served up by our circuits.web application. Created the .htaccess file in your **Docroot**:: ReWriteEngine On ReWriteCond %{REQUEST_FILENAME} !-f ReWriteCond %{REQUEST_FILENAME} !-d RewriteRule ^(.*)$ /~joeblogs/index.wsgi/$1 [QSA,PT,L] Running your Application with Apache/mod_wsgi ............................................. The get your Web Application working and deployed on Apache using mod_wsgi, you need to make a few changes to your code. Based on our Basic Hello World example earlier, we modify it to the following: .. code-block:: python :linenos: #!/usr/bin/env python from circuits.web import Controller from circuits.web.wsgi import Application class Root(Controller): def index(self): return "Hello World!" application = Application() + Root() That's it! To run this, save it as index.wsgi and place it in your Web Root (public-html or www directory) as per the above guidelines and point your favorite Web Browser to: http://example.com/~joeblogs/ .. note:: It is recommended that you actually use a reverse proxy setup for deploying circuits.web web application so that you don't loose the advantages and functionality of using an event-driven component architecture in your web apps. In **production** you should use a load balance and reverse proxy combination for best performance. circuits-3.2.3/docs/source/web/index.rst000066400000000000000000000002631460335514400202200ustar00rootroot00000000000000======================== circuits.web User Manual ======================== .. toctree:: :maxdepth: 2 introduction gettingstarted features howtos miscellaneous circuits-3.2.3/docs/source/web/introduction.rst000066400000000000000000000005741460335514400216370ustar00rootroot00000000000000Introduction ============ circuits.web is a set of components for building high performance HTTP/1.1 and WSGI/1.0 compliant web applications. These components make it easy to rapidly develop rich, scalable web applications with minimal effort. circuits.web borrows from * `CherryPy `_ * BaseHTTPServer (*Python std. lib*) * wsgiref (*Python std. lib*) circuits-3.2.3/docs/source/web/miscellaneous.rst000066400000000000000000000070331460335514400217560ustar00rootroot00000000000000Miscellaneous ============= Writing Tools ------------- Most of the internal tools used by circuits.web in circuits.web.tools are simply functions that modify the Request or Response objects in some way or another... We won't be covering that here... What we will cover is how to build simple tools that do something to the Request or Response along it's life-cycle. Here is a simple example of a tool that uses the pytidylib library to tidy up the HTML output before it gets sent back to the requesting client. Code: .. code-block:: python :linenos: #!/usr/bin/env python from tidylib import tidy_document from circuits import Component class Tidy(Component): channel = "http" def response(self, response): document, errors = tidy_document("".join(response.body)) response.body = document Usage: (Server(8000) + Tidy() + Root()).run() **How it works:** This tool works by intercepting the Response Event on the "response" channel of the "http" target (*or Component*). For more information about the life cycle of Request and Response events, their channels and where and how they can be intercepted to perform various tasks read the Request/Response Life Cycle section. Writing Dispatchers ------------------- In circuits.web writing a custom "dispatcher" is only a matter of writing a Component that listens for incoming Request events on the "request" channel of the "web" target. The simplest kind of "dispatcher" is one that simply modifies the request.path in some way. To demonstrate this we'll illustrate and describe how the !VirtualHosts "dispatcher" works. VirtualHosts code: .. code-block:: python :linenos: class VirtualHosts(Component): channel = "web" def __init__(self, domains): super(VirtualHosts, self).__init__() self.domains = domains @handler("request", filter=True, priority=1) def request(self, event, request, response): path = request.path.strip("/") header = request.headers.get domain = header("X-Forwarded-Host", header("Host", "")) prefix = self.domains.get(domain, "") if prefix: path = _urljoin("/%s/" % prefix, path) request.path = path The important thing here to note is the Event Handler listening on the appropriate channel and the request.path being modified appropriately. You'll also note that in [source:circuits/web/dispatchers.py] all of the dispatchers have a set priority. These priorities are defined as:: $ grin "priority" circuits/web/dispatchers/ circuits/web/dispatchers/dispatcher.py: 92 : @handler("request", filter=True, priority=0.1) circuits/web/dispatchers/jsonrpc.py: 38 : @handler("request", filter=True, priority=0.2) circuits/web/dispatchers/static.py: 59 : @handler("request", filter=True, priority=0.9) circuits/web/dispatchers/virtualhosts.py: 49 : @handler("request", filter=True, priority=1.0) circuits/web/dispatchers/websockets.py: 53 : @handler("request", filter=True, priority=0.2) circuits/web/dispatchers/xmlrpc.py: 36 : @handler("request", filter=True, priority=0.2) in web applications that use multiple dispatchers these priorities set precedences for each "dispatcher" over another in terms of who's handling the Request Event before the other. .. note:: Some dispatchers are designed to filter the Request Event and prevent it from being processed by other dispatchers in the system. circuits-3.2.3/examples/000077500000000000000000000000001460335514400151675ustar00rootroot00000000000000circuits-3.2.3/examples/99bottles.py000077500000000000000000000023701460335514400174040ustar00rootroot00000000000000#!/usr/bin/env python """ An implementation of the Python Concurrency Problem of 99 Bottles of Beer See: http://wiki.python.org/moin/Concurrency/99Bottles """ import sys from circuits import Component from circuits.io import File from circuits.protocols import Line class Tail(Component): """ A complex component which combines the ``File`` and ``LP`` (Line Protoco) components together to implement similar functionality to the UNIX ``tail`` command. """ def init(self, filename): """ Initialize the Component. NB: This is automatically called after ``__new__`` and ``__init__``. """ (File(filename, 'r') + Line()).register(self) class Grep(Component): """ A simple component that simply listens for ``line`` events from the ``Tail`` component and performs a regular expression match against each line. If the line matches it is printed to standard output. """ def init(self, pattern): self.pattern = pattern def line(self, line): """Line Event Handler""" line = line.decode('utf-8') if self.pattern in line: print(line) # Configure and "run" the System. app = Tail(sys.argv[1]) Grep(sys.argv[2]).register(app) app.run() circuits-3.2.3/examples/async_worker_webpage_download.py000066400000000000000000000024151460335514400236320ustar00rootroot00000000000000from time import sleep import requests from circuits import Component, Debugger, Event, Timer, Worker, task def download_web_page(url): print(f'Downloading {url}') response = requests.get(url) sleep(2) # This website is really slow. # Only returning portion of web page. # You would probably process web page for data before sending back return response.text[:200] class App(Component): def init(self, *args, **kwargs): self.foo_count = 0 Worker(process=False).register(self) def foo(self): self.foo_count += 1 print('Foo!') if self.foo_count > 10: self.stop() def started(self, component): # x = yield self.call(task(factorial, 10)) Timer(1, Event.create('foo'), persist=True).register(self) self.fire(task(download_web_page, 'http://www.slickdeals.net')) # async self.fire(task(download_web_page, 'http://www.google.com')) # async self.fire(task(download_web_page, 'http://www.yahoo.com')) # async def task_success(self, function_called, function_result): _func, url_called = function_called print(f'url {url_called} gave {function_result}') if __name__ == '__main__': app = App() Debugger().register(app) app.run() circuits-3.2.3/examples/cat.py000077500000000000000000000015551460335514400163210ustar00rootroot00000000000000#!/usr/bin/env python """ Clone of the standard UNIX "cat" command. This example shows how you can utilize some of the buitlin I/O components in circuits to write a very simple clone of the standard UNIX "cat" command. """ import sys from circuits.io import File, stdout, write class Cat(File): # This adds the already instantiated stdout instnace stdout = stdout def read(self, data): """ Read Event Handler This is fired by the File Component when there is data to be read from the underlying file that was opened. """ self.fire(write(data), stdout) def eof(self): """ End Of File Event This is fired by the File Component when the underlying input file has been exhcuasted. """ raise SystemExit(0) # Start and "run" the system. Cat(sys.argv[1]).run() circuits-3.2.3/examples/cert.pem000066400000000000000000000042051460335514400166300ustar00rootroot00000000000000-----BEGIN RSA PRIVATE KEY----- MIICWwIBAAKBgQC9B7hV67bzhLA//STrZUIi0iHD4WFtftOhvj+xiHRNnYw0+r+4 WdQv1YiL+ab03pn/J9R1SQuOGwYDVPQvYX+qEFVRUFP9yvXIQl7PG40HQzfs8lJz hnmI+64HJT//oJ9e7PiyDHLfFH1FuCqSy9RlvzOd4hmydX9J3VxFFzrpJQIDAQAB AoGAHhGxT/Gb+6a6xqMFEXDdEV7twhQDBIDtN0hlJ192aLZMDE1q2+9mImnMO7/t v/v88Sqr0DBbZzKDRVppMXRH80ZtnmMu3/3kUCtA3WAbKxyFpIiXGv/NAUHZe5Gz rua+z3lUvmt6CmwMm2ReB70Q61zxr9q4HjrjYI82dtJ4M40CQQDnGujxdBdbPmiR oc8mcShfmPNP7igQrUkf/DpB0GWnLWdA97mmXLw4jHXpHy9gm3wGc+9uOi0Ex8Ml 1t9xAGFjAkEA0WSGwG45d5dmYV8Oa/9UsY5/F6hAlYAAI1TxRsKcl2YTqaQastac glV1GSUrgGw/8UBvdKKS2REF7cpAkiQV1wJAPtQhCiuOgf7YfOcpowDWgg7Z7xwH Bmml3K08xVG7oRSF4rK2ZRUHErSVBbi1r6T1tedk62mjfY41bp8ZBeadkwJAD7AG YHhhmdIf+3+Rpwm0ILFaWD1kyU6TtBHzGagO71DYfEctMOTfSOx6H24nejGiAMMh Fo3vjo+18ADNIaXOdQJAYdm+dUdyVaW2IDUi7ew0shyKTC4OaEZtXIwwINvrqUsX //6z8mw/S5rXGlfKadiz4uwzcXlSvg727O1efpibvA== -----END RSA PRIVATE KEY----- -----BEGIN CERTIFICATE----- MIIDjTCCAvagAwIBAgIJAN0msyL5El/jMA0GCSqGSIb3DQEBBQUAMIGMMQswCQYD VQQGEwJBVTETMBEGA1UECBMKUXVlZW5zbGFuZDERMA8GA1UEBxMIQnJpc2JhbmUx FTATBgNVBAoTDFNob3J0Q2lyY3VpdDEUMBIGA1UEAxMLSmFtZXMgTWlsbHMxKDAm BgkqhkiG9w0BCQEWGWFkbWluQHNob3J0Y2lyY3VpdC5uZXQuYXUwHhcNMTAwMTEz MDczOTAwWhcNMTEwMTEzMDczOTAwWjCBjDELMAkGA1UEBhMCQVUxEzARBgNVBAgT ClF1ZWVuc2xhbmQxETAPBgNVBAcTCEJyaXNiYW5lMRUwEwYDVQQKEwxTaG9ydENp cmN1aXQxFDASBgNVBAMTC0phbWVzIE1pbGxzMSgwJgYJKoZIhvcNAQkBFhlhZG1p bkBzaG9ydGNpcmN1aXQubmV0LmF1MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB gQC9B7hV67bzhLA//STrZUIi0iHD4WFtftOhvj+xiHRNnYw0+r+4WdQv1YiL+ab0 3pn/J9R1SQuOGwYDVPQvYX+qEFVRUFP9yvXIQl7PG40HQzfs8lJzhnmI+64HJT// oJ9e7PiyDHLfFH1FuCqSy9RlvzOd4hmydX9J3VxFFzrpJQIDAQABo4H0MIHxMB0G A1UdDgQWBBSq+tU5ZDymUuMcgZ83gxk1PTN89zCBwQYDVR0jBIG5MIG2gBSq+tU5 ZDymUuMcgZ83gxk1PTN896GBkqSBjzCBjDELMAkGA1UEBhMCQVUxEzARBgNVBAgT ClF1ZWVuc2xhbmQxETAPBgNVBAcTCEJyaXNiYW5lMRUwEwYDVQQKEwxTaG9ydENp cmN1aXQxFDASBgNVBAMTC0phbWVzIE1pbGxzMSgwJgYJKoZIhvcNAQkBFhlhZG1p bkBzaG9ydGNpcmN1aXQubmV0LmF1ggkA3SazIvkSX+MwDAYDVR0TBAUwAwEB/zAN BgkqhkiG9w0BAQUFAAOBgQAokSGDpbFV2osC8nM8K12vheeDBVDHGxOaENXGVIm8 SWPXsaIUsm6JQx0wm/eouWRPbNJkOBwBrNCls1oMmdxdxG8mBh+kAMWUkdVeuT2H lCo9BRJnhUr4L6poJ7ORzL2oUilGZNwONpHGY0cWzFG8/tOoRJsfKZm23bwXbIxv Hw== -----END CERTIFICATE----- circuits-3.2.3/examples/chatserver.py000077500000000000000000000067661460335514400177310ustar00rootroot00000000000000#!/usr/bin/env python """ Chat Server Example This example demonstrates how to create a very simple telnet-style chat server that supports many connecting clients. """ from optparse import OptionParser from circuits import Component, Debugger from circuits.net.events import write from circuits.net.sockets import TCPServer __version__ = '0.0.1' USAGE = '%prog [options]' VERSION = '%prog v' + __version__ def parse_options(): parser = OptionParser(usage=USAGE, version=VERSION) parser.add_option( '-b', '--bind', action='store', type='string', default='0.0.0.0:8000', dest='bind', help='Bind to address:[port]', ) parser.add_option( '-d', '--debug', action='store_true', default=False, dest='debug', help='Enable debug mode', ) opts, args = parser.parse_args() return opts, args class ChatServer(Component): def init(self, args, opts): """ Initialize our ``ChatServer`` Component. This uses the convenience ``init`` method which is called after the component is properly constructed and initialized and passed the same args and kwargs that were passed during construction. """ self.args = args self.opts = opts self.clients = {} if opts.debug: Debugger().register(self) if ':' in opts.bind: address, port = opts.bind.split(':') port = int(port) else: address, port = opts.bind, 8000 bind = (address, port) TCPServer(bind).register(self) def broadcast(self, data, exclude=None): exclude = exclude or [] targets = (sock for sock in self.clients if sock not in exclude) for target in targets: self.fire(write(target, data)) def connect(self, sock, host, port): """Connect Event -- Triggered for new connecting clients""" self.clients[sock] = { 'host': sock, 'port': port, 'state': { 'nickname': None, 'registered': False, }, } self.fire(write(sock, b'Welcome to the circuits Chat Server!\n')) self.fire(write(sock, b'Please enter a desired nickname: ')) def disconnect(self, sock): """Disconnect Event -- Triggered for disconnecting clients""" if sock not in self.clients: return nickname = self.clients[sock]['state']['nickname'] self.broadcast( f'!!! {nickname:s} has left !!!\n'.encode(), exclude=[sock], ) del self.clients[sock] def read(self, sock, data): """Read Event -- Triggered for when client connections have data""" data = data.strip().decode('utf-8') if not self.clients[sock]['state']['registered']: nickname = data self.clients[sock]['state']['registered'] = True self.clients[sock]['state']['nickname'] = nickname self.broadcast( f'!!! {nickname:s} has joined !!!\n'.encode(), exclude=[sock], ) else: nickname = self.clients[sock]['state']['nickname'] self.broadcast( f'<{nickname:s}> {data:s}\n'.encode(), exclude=[sock], ) def main(): opts, args = parse_options() # Configure and "run" the System. ChatServer(args, opts).run() if __name__ == '__main__': main() circuits-3.2.3/examples/circ.py000077500000000000000000000221551460335514400164710ustar00rootroot00000000000000#!/usr/bin/env python """ Circuits IRC Client A circuits based IRC Client demonstrating integration with urwid - a curses application development library and interacting with and processing irc events from an IRC server. NB: This is not a full featured client. For usage type: ./circ.py --help """ import os import sys from optparse import OptionParser from re import compile as compile_regex from select import select from socket import gethostname from urwid import AttrWrap, Edit, Frame, ListBox, Pile, SimpleListWalker, Text from urwid.raw_display import Screen from circuits import Component, __version__ as systemVersion, handler from circuits.net.sockets import TCPClient, connect from circuits.protocols.irc import ( ERR_NICKNAMEINUSE, ERR_NOMOTD, IRC, JOIN, NICK, PART, PRIVMSG, QUIT, RPL_ENDOFMOTD, USER, Message, request, ) from circuits.tools import getargspec USAGE = '%prog [options] host [port]' VERSION = '%prog v' + systemVersion MAIN_TITLE = f'cIRC - {systemVersion:s}' HELP_STRINGS = { 'main': 'For help, type: /help', } CMD_REGEX = compile_regex( r'\/(?P[a-z]+) ?' '(?P.*)(?iu)', ) def back_merge(line, n, t=' '): return line[:-n].extend([t.join(line[-n:])]) def parse_options(): parser = OptionParser(usage=USAGE, version=VERSION) parser.add_option( '-c', '--channel', action='store', default='#circuits', dest='channel', help='Channel to join', ) parser.add_option( '', '--debug', action='store_true', default=False, dest='debug', help='Enable debug mode', ) parser.add_option( '-n', '--nick', action='store', default=os.environ['USER'], dest='nick', help='Nickname to use', ) opts, args = parser.parse_args() if len(args) < 1: parser.print_help() raise SystemExit(1) return opts, args class Client(Component): channel = 'client' def init(self, host, port=6667, opts=None): self.host = host self.port = port self.opts = opts self.hostname = gethostname() self.nick = opts.nick self.ircchannel = opts.channel # Add TCPClient and IRC to the system. TCPClient(channel=self.channel).register(self) IRC(channel=self.channel).register(self) self.create_interface() def create_interface(self): self.screen = Screen() self.screen.start() self.screen.register_palette( [('title', 'white', 'dark blue', 'standout'), ('line', 'light gray', 'black'), ('help', 'white', 'dark blue')], ) self.body = ListBox(SimpleListWalker([])) self.lines = self.body.body self.title = Text(MAIN_TITLE) self.header = AttrWrap(self.title, 'title') self.help = AttrWrap( Text(HELP_STRINGS['main']), 'help', ) self.input = Edit(caption='%s> ' % self.ircchannel) self.footer = Pile([self.help, self.input]) self.top = Frame(self.body, self.header, self.footer) def ready(self, component): """ Ready Event This event is triggered by the underlying ``TCPClient`` Component when it is ready to start making a new connection. """ self.fire(connect(self.host, self.port)) def connected(self, host, port): """ connected Event This event is triggered by the underlying ``TCPClient`` Component when a successfully connection has been made. """ nick = self.nick hostname = self.hostname name = f'{nick} on {hostname} using circuits/{systemVersion}' self.fire(NICK(nick)) self.fire(USER(nick, hostname, host, name)) def numeric(self, source, numeric, *args): """ Numeric Event This event is triggered by the ``IRC`` Protocol Component when we have received an IRC Numberic Event from server we are connected to. """ if numeric == ERR_NICKNAMEINUSE: self.fire(NICK(f'{args[0]:s}_')) elif numeric in (RPL_ENDOFMOTD, ERR_NOMOTD): self.fire(JOIN(self.ircchannel)) @handler('stopped', channel='*') def _on_stopped(self, component): self.screen.stop() @handler('generate_events') def _on_generate_events(self, event): event.reduce_time_left(0) size = self.screen.get_cols_rows() if select(self.screen.get_input_descriptors(), [], [], 0.1)[0] != []: _timeout, keys, _raw = self.screen.get_input_nonblocking() for k in keys: if k == 'window resize': size = self.screen.get_cols_rows() continue if k == 'enter': self.processCommand(self.input.get_edit_text()) self.input.set_edit_text('') continue self.top.keypress(size, k) self.input.set_edit_text(self.input.get_edit_text() + k) self.update_screen(size) def unknownCommand(self, command): self.lines.append(Text('Unknown command: %s' % command)) def syntaxError(self, command, args, expected): self.lines.append( Text( f'Syntax error ({command:s}): {args:s} Expected: {expected:s}', ), ) def processCommand(self, s): # noqa match = CMD_REGEX.match(s) if match is not None: command = match.groupdict()['command'] tokens = match.groupdict()['args'].split(' ') if match.groupdict()['args'] != '' else [] fn = 'cmd' + command.upper() if hasattr(self, fn): f = getattr(self, fn) if callable(f): args, vargs, _kwargs, default = getargspec(f) args.remove('self') if len(args) == len(tokens): if len(args) == 0: f() else: f(*tokens) else: if len(tokens) > len(args): if vargs is None: if len(args) > 0: factor = len(tokens) - len(args) + 1 f(*back_merge(tokens, factor)) else: self.syntaxError( command, ' '.join(tokens), ' '.join(x for x in args + [vargs] if x is not None), ) else: f(*tokens) elif default is not None and len(args) == (len(tokens) + len(default)): f(*(tokens + list(default))) else: self.syntaxError( command, ' '.join(tokens), ' '.join(x for x in args + [vargs] if x is not None), ) else: if self.ircchannel is not None: self.lines.append(Text(f'<{self.nick}> {s}')) self.fire(PRIVMSG(self.ircchannel, s)) else: self.lines.append(Text('No channel joined. Try /join #')) def cmdEXIT(self, message=''): self.fire(QUIT(message)) raise SystemExit(0) def cmdSERVER(self, host, port=6667): self.fire(connect(host, port)) def cmdSSLSERVER(self, host, port=6697): self.fire(connect(host, port, secure=True)) def cmdJOIN(self, channel): if self.ircchannel is not None: self.cmdPART(self.ircchannel, 'Joining %s' % channel) self.fire(JOIN(channel)) self.ircchannel = channel def cmdPART(self, channel=None, message='Leaving'): if channel is None: channel = self.ircchannel if channel is not None: self.fire(PART(channel, message)) self.ircchannel = None def cmdQUOTE(self, message): self.fire(request(Message(message))) def cmdQUIT(self, message='Bye'): self.fire(QUIT(message)) def update_screen(self, size): canvas = self.top.render(size, focus=True) self.screen.draw_screen(size, canvas) @handler('notice', 'privmsg') def _on_notice_or_privmsg(self, event, source, target, message): nick, _ident, _host = source if event.name == 'notice': self.lines.append(Text(f'-{nick}- {message}')) else: self.lines.append(Text(f'<{nick}> {message}')) def main(): opts, args = parse_options() host = args[0] port = int(args[1]) if len(args) > 1 else 6667 # Configure and run the system. client = Client(host, port, opts=opts) if opts.debug: from circuits import Debugger Debugger(file=sys.stderr).register(client) client.run() if __name__ == '__main__': main() circuits-3.2.3/examples/dirwatch.py000077500000000000000000000014251460335514400173530ustar00rootroot00000000000000#!/usr/bin/env python """ Directory Watch Example This example demonstrates the inotify I/O Component ``Notify`` which can be used for real-time monitoring of file system events. The example simply takes a path to watch as the first Command Line Argument and prints to stdour every file system event it sees. """ import sys from circuits import Component from circuits.io import Notify class FileWatcher(Component): channel = 'notify' def opened(self, filename, path, fullpath, isdir): print(f'File {filename:s} opened') def closed(self, filename, path, fullpath, isdir): print(f'File {filename:s} closed') # Configure the system app = Notify() FileWatcher().register(app) # Add the path to watch app.add_path(sys.argv[1]) # Run the system app.run() circuits-3.2.3/examples/dnsclient.py000077500000000000000000000042641460335514400175350ustar00rootroot00000000000000#!/usr/bin/env python """ DNS Client Example A simple little DNS Client example using `dnslib `_ to handle the DNS protocol parsing and packet deconstruction (*a really nice library btw with great integration into circuits*). Specify the server, port, and query as arguments to perform a lookup against a server using UDP. To run this example:: pip install dnslib ./dnsclient.py 8.8.8.8 53 google.com """ import sys from dnslib import DNSQuestion, DNSRecord from circuits import Component, Debugger, Event from circuits.net.events import write from circuits.net.sockets import UDPClient class reply(Event): """reply Event""" class DNS(Component): """DNS Protocol Handling""" def read(self, peer, data): self.fire(reply(peer, DNSRecord.parse(data))) class Dummy(Component): """ A Dummy DNS Handler This just parses the reply packet and prints any RR records it finds. """ def reply(self, peer, response): id = response.header.id qname = response.q.qname print( f'DNS Response from {peer[0]:s}:{peer[1]:d} id={id:d} qname={str(qname):s}', file=sys.stderr, ) for rr in response.rr: print(f' {rr!s:s}') raise SystemExit(0) class DNSClient(Component): """ DNS Client This ties everything together in a nice configurable way with protocol, transport, and dummy handler as well as optional debugger. """ def init(self, server, port, query, verbose=False): self.server = server self.port = int(port) self.query = query if verbose: Debugger().register(self) self.transport = UDPClient(0).register(self) self.protocol = DNS().register(self) self.dummy = Dummy().register(self) def started(self, manager): print('DNS Client Started!', file=sys.stderr) def ready(self, client, bind): print('Ready! Bound to {:s}:{:d}'.format(*bind), file=sys.stderr) request = DNSRecord(q=DNSQuestion(self.query)) self.fire(write((self.server, self.port), request.pack())) DNSClient(*sys.argv[1:], verbose=True).run() circuits-3.2.3/examples/dnsserver.py000077500000000000000000000042531460335514400175630ustar00rootroot00000000000000#!/usr/bin/env python """ DNS Server Example A simple little DNS Server example using `dnslib `_ to handle the DNS protocol parsing and packet construction (*a really nice library btw with great integration into circuits*). Answers ``A 127.0.0.1`` for any query of any type! To run this example:: pip install dnslib ./dnsserver.py Usage (*using dig*):: dig @localhost -p 1053 test.com """ import sys from dnslib import QTYPE, RR, A, DNSHeader, DNSRecord from circuits import Component, Debugger, Event from circuits.net.events import write from circuits.net.sockets import UDPServer class query(Event): """query Event""" class DNS(Component): """DNS Protocol Handling""" def read(self, peer, data): self.fire(query(peer, DNSRecord.parse(data))) class Dummy(Component): """ A Dummy DNS Handler This just returns an A record response of 127.0.0.1 for any query of any type! """ def query(self, peer, request): id = request.header.id qname = request.q.qname print( f'DNS Request for qname({qname!s:s})', file=sys.stderr, ) reply = DNSRecord( DNSHeader(id=id, qr=1, aa=1, ra=1), q=request.q, ) # Add A Record reply.add_answer(RR(qname, QTYPE.A, rdata=A('127.0.0.1'))) # Send To Client self.fire(write(peer, reply.pack())) class DNSServer(Component): """ DNS Server This ties everything together in a nice configurable way with protocol, transport and dummy handler as well as optional debugger. """ def init(self, bind=None, verbose=False): self.bind = bind or ('0.0.0.0', 53) if verbose: Debugger().register(self) self.transport = UDPServer(self.bind).register(self) self.protocol = DNS().register(self) self.dummy = Dummy().register(self) def started(self, manager): print('DNS Server Started!', file=sys.stderr) def ready(self, server, bind): print('Ready! Listening on {:s}:{:d}'.format(*bind), file=sys.stderr) DNSServer(('0.0.0.0', 1053), verbose=True).run() circuits-3.2.3/examples/echoserial.py000077500000000000000000000015221460335514400176620ustar00rootroot00000000000000#!/usr/bin/env python """ Simple Serial Example This example shows how to use the ``circuits.io.Serial`` Component to access serial data. This example simply echos back what it receives on the serial port. .. warning:: This example is currently untested. """ from circuits import Component, Debugger, handler from circuits.io import Serial from circuits.io.events import write class EchoSerial(Component): def init(self, port): Serial(port).register(self) @handler('read') def on_read(self, data): """ Read Event Handler This is fired by the underlying Serial Component when there has been new data read from the serial port. """ self.fire(write(data)) # Start and "run" the system. # Connect to /dev/ttyS0 app = EchoSerial('/dev/ttyS0') Debugger().register(app) app.run() circuits-3.2.3/examples/echoserver.py000077500000000000000000000017651460335514400177220ustar00rootroot00000000000000#!/usr/bin/env python """ Simple TCP Echo Server This example shows how you can create a simple TCP Server (an Echo Service) utilizing the builtin Socket Components that the circuits library ships with. """ from circuits import Debugger, handler from circuits.net.sockets import TCPServer class EchoServer(TCPServer): @handler('read') def on_read(self, sock, data): """ Read Event Handler This is fired by the underlying Socket Component when there has been new data read from the connected client. ..note :: By simply returning, client/server socket components listen to ValueChagned events (feedback) to determine if a handler returned some data and fires a subsequent Write event with the value returned. """ return data # Start and "run" the system. # Bind to port 0.0.0.0:8000 app = EchoServer(('0.0.0.0', 8000), secure=True, certfile='cert.pem') Debugger().register(app) app.run() circuits-3.2.3/examples/echoserverunix.py000077500000000000000000000017441460335514400206230ustar00rootroot00000000000000#!/usr/bin/env python """ Simple UNIX Echo Server This example shows how you can create a simple UNIX Server (an Echo Service) utilizing the builtin Socket Components that the circuits library ships with. """ from circuits import Debugger, handler from circuits.net.sockets import UNIXServer class EchoServer(UNIXServer): @handler('read') def on_read(self, sock, data): """ Read Event Handler This is fired by the underlying Socket Component when there has been new data read from the connected client. ..note :: By simply returning, client/server socket components listen to ValueChagned events (feedback) to determine if a handler returned some data and fires a subsequent Write event with the value returned. """ return data # Start and "run" the system. # Bind to a UNIX Socket at /tmp/test.sock app = EchoServer('/tmp/test.sock') Debugger().register(app) app.run() circuits-3.2.3/examples/factorial.py000077500000000000000000000012041460335514400175050ustar00rootroot00000000000000#!/usr/bin/env python from time import sleep from circuits import Component, Debugger, Event, Timer, Worker, task def factorial(n): x = 1 for i in range(1, (n + 1)): x = x * (i + 1) sleep(1) # deliberate! return x class App(Component): def init(self, *args, **kwargs): Worker(process=True).register(self) def foo(self): print('Foo!') def started(self, component): Timer(1, Event.create('foo'), persist=True).register(self) x = yield self.call(task(factorial, 10)) print(f'{x.value:d}') self.stop() app = App() Debugger().register(app) app.run() circuits-3.2.3/examples/factorial_multiple.py000066400000000000000000000023321460335514400214200ustar00rootroot00000000000000#!/usr/bin/env python from time import sleep from circuits import Component, Debugger, Event, Timer, Worker, task def factorial(n): x = 1 for i in range(1, (n + 1)): x = x * (i + 1) sleep(1) # deliberate! return x class App(Component): def init(self, *args, **kwargs): Worker(process=True).register(self) def foo(self): print('Foo!') def started(self, component): self.fire(task(factorial, 3)) # async self.fire(task(factorial, 5)) # async self.fire(task(factorial, 7)) # async self.fire(task(factorial, 10)) # async self.fire(task(factorial, 11)) # async self.fire(task(factorial, 11)) # async self.fire(task(factorial, 12)) # async self.fire(task(factorial, 14)) # async Timer(1, Event.create('foo'), persist=True).register(self) def task_success(self, function_called, factorial_result): _func, argument = function_called print(f'factorial({argument!s}) = {factorial_result:d}') # Stop after the last and longest running task if argument == 14: self.stop() if __name__ == '__main__': app = App() Debugger().register(app) app.run() circuits-3.2.3/examples/filter.py000077500000000000000000000022521460335514400170320ustar00rootroot00000000000000#!/usr/bin/env python """ Simple Event Filtering This example shows how you can filter events by using the ``Event.stop()`` method which prevents other event handlers listening to the event from running. When this example is run you should only get a single line of output "Hello World!". .. code-block:: sh $ ./filter.py Hello World! """ from circuits import Component, Event class hello(Event): """hello Event""" class App(Component): def hello(self, event): """Hello Event Handler""" event.stop() # Stop further event processing print('Hello World!') def started(self, event, component): """ Started Event Handler This is fired internally when your application starts up and can be used to trigger events that only occur once during startup. """ event.stop() # Stop further event processing self.fire(hello()) # Fire a Hello event raise SystemExit(0) # Terminate the application # Start and "run" the system. # We're deliberately creating two instances of ``App`` # so we can demonstrate event filtering. app = App() App().register(app) # 2nd App app.run() circuits-3.2.3/examples/hello.py000077500000000000000000000013021460335514400166430ustar00rootroot00000000000000#!/usr/bin/env python """circuits Hello World""" from circuits import Component, Event class hello(Event): """hello Event""" class terminate(Event): """terminate Event""" class App(Component): def hello(self): """Hello Event Handler""" print('Hello World!') def started(self, *args): """ Started Event Handler This is fired internally when your application starts up and can be used to trigger events that only occur once during startup. """ self.fire(hello()) # Fire hello Event self.fire(terminate()) def terminate(self): raise SystemExit(0) # Terminate the Application App().run() circuits-3.2.3/examples/hello_bridge.py000077500000000000000000000014221460335514400201620ustar00rootroot00000000000000#!/usr/bin/env python """ Bridge Example This example is quite similar to the Hello example but displays a hello form both the parent and child processing demonstrating how IPC works using the Bridge. """ from os import getpid from circuits import Component, Event, ipc class hello(Event): """hello Event""" class Child(Component): def hello(self): return f'Hello from child with pid {getpid()}' class App(Component): def init(self): Child().start(process=True, link=self) def ready(self, *args): x = yield self.call(hello()) yield print(x) y = yield self.call(ipc(hello())) yield print(y) raise SystemExit(0) def hello(self): return f'Hello from parent with pid {getpid()}' App().run() circuits-3.2.3/examples/hello_multi_bridge.py000077500000000000000000000020451460335514400213760ustar00rootroot00000000000000#!/usr/bin/env python """ Multi Bridge Example Identical to the Hello Bridge Example but with a 2nd child. """ from os import getpid from circuits import Component, Event, ipc class go(Event): """go Event""" class hello(Event): """hello Event""" class Child(Component): def hello(self): return f'Hello from child with pid {getpid()}' class App(Component): def init(self): self.counter = 0 self.child1 = Child().start(process=True, link=self) self.child2 = Child().start(process=True, link=self) def ready(self, *args): self.counter += 1 if self.counter < 2: return self.fire(go()) def go(self): x = yield self.call(hello()) yield print(x) y = yield self.call(ipc(hello()), self.child1[1].channel) yield print(y) z = yield self.call(ipc(hello()), self.child2[1].channel) yield print(z) raise SystemExit(0) def hello(self): return f'Hello from parent with pid {getpid()}' App().run() circuits-3.2.3/examples/index.rst000066400000000000000000000045701460335514400170360ustar00rootroot00000000000000Hello ..... .. code:: python #!/usr/bin/env python """circuits Hello World""" from circuits import Component, Event class hello(Event): """hello Event""" class App(Component): def hello(self): """Hello Event Handler""" print("Hello World!") def started(self, component): """Started Event Handler This is fired internally when your application starts up and can be used to trigger events that only occur once during startup. """ self.fire(hello()) # Fire hello Event raise SystemExit(0) # Terminate the Application App().run() Echo Server ........... .. code:: python #!/usr/bin/env python """Simple TCP Echo Server This example shows how you can create a simple TCP Server (an Echo Service) utilizing the builtin Socket Components that the circuits library ships with. """ from circuits import handler, Debugger from circuits.net.sockets import TCPServer class EchoServer(TCPServer): @handler("read") def on_read(self, sock, data): """Read Event Handler This is fired by the underlying Socket Component when there has been new data read from the connected client. ..note :: By simply returning, client/server socket components listen to ValueChagned events (feedback) to determine if a handler returned some data and fires a subsequent Write event with the value returned. """ return data # Start and "run" the system. # Bind to port 0.0.0.0:8000 app = EchoServer(8000) Debugger().register(app) app.run() Hello Web ......... .. code:: python #!/usr/bin/env python from circuits.web import Server, Controller class Root(Controller): def index(self): """Index Request Handler Controller(s) expose implicitly methods as request handlers. Request Handlers can still be customized by using the ``@expose`` decorator. For example exposing as a different path. """ return "Hello World!" app = Server(("0.0.0.0", 8000)) Root().register(app) app.run() More `examples `_... circuits-3.2.3/examples/ircbot.py000077500000000000000000000053031460335514400170270ustar00rootroot00000000000000#!/usr/bin/env python """ IRC Bot Example This example shows how to use several components in circuits as well as one of the builtin networking protocols. This IRC Bot simply connects to the Libera.Chat IRC Network and joins the #circuits channel. It will also echo anything privately messages to it in response. """ import sys from circuits import Component, Debugger from circuits.net.sockets import TCPClient, connect from circuits.protocols.irc import ERR_NICKNAMEINUSE, ERR_NOMOTD, IRC, JOIN, NICK, PRIVMSG, RPL_ENDOFMOTD, USER class Bot(Component): # Define a separate channel so we can create many instances of ``Bot`` channel = 'ircbot' def init(self, host='irc.libera.chat', port='6667', channel=channel): self.host = host self.port = int(port) # Add TCPClient and IRC to the system. TCPClient(channel=self.channel).register(self) IRC(channel=self.channel).register(self) def ready(self, component): """ Ready Event This event is triggered by the underlying ``TCPClient`` Component when it is ready to start making a new connection. """ self.fire(connect(self.host, self.port)) def connected(self, host, port): """ connected Event This event is triggered by the underlying ``TCPClient`` Component when a successfully connection has been made. """ self.fire(NICK('circuits')) self.fire(USER('circuits', 'circuits', host, 'Test circuits IRC Bot')) def disconnected(self): """ disconnected Event This event is triggered by the underlying ``TCPClient`` Component when the connection is lost. """ raise SystemExit(0) def numeric(self, source, numeric, *args): """ Numeric Event This event is triggered by the ``IRC`` Protocol Component when we have received an IRC Numberic Event from server we are connected to. """ if numeric == ERR_NICKNAMEINUSE: self.fire(NICK(f'{args[0]:s}_')) elif numeric in (RPL_ENDOFMOTD, ERR_NOMOTD): self.fire(JOIN('#circuits')) def privmsg(self, source, target, message): """ Message Event This event is triggered by the ``IRC`` Protocol Component for each message we receieve from the server. """ if target.startswith('#'): self.fire(PRIVMSG(target, message)) else: self.fire(PRIVMSG(source[0], message)) # Configure and run the system bot = Bot(*sys.argv[1:]) Debugger().register(bot) # To register a 2nd ``Bot`` instance. Simply use a separate channel. # Bot(*sys.argv[1:], channel="foo").register(bot) bot.run() circuits-3.2.3/examples/ircclient.py000077500000000000000000000121731460335514400175240ustar00rootroot00000000000000#!/usr/bin/env python """ Example IRC Client A basic IRC client with a very basic console interface. For usage type: ./ircclient.py --help """ import os from optparse import OptionParser from socket import gethostname from circuits import Component, Debugger, __version__ as systemVersion, handler from circuits.io import stdin from circuits.net.events import connect from circuits.net.sockets import TCPClient from circuits.protocols.irc import IRC, JOIN, NICK, PRIVMSG, USER USAGE = '%prog [options] host [port]' VERSION = '%prog v' + systemVersion def parse_options(): parser = OptionParser(usage=USAGE, version=VERSION) parser.add_option( '-n', '--nick', action='store', default=os.environ['USER'], dest='nick', help='Nickname to use', ) parser.add_option( '-d', '--debug', action='store_true', default=False, dest='debug', help='Enable debug verbose logging', ) parser.add_option( '-c', '--channel', action='store', default='#circuits', dest='channel', help='Channel to join', ) opts, args = parser.parse_args() if len(args) < 1: parser.print_help() raise SystemExit(1) return opts, args class Client(Component): # Set a separate channel in case we want multiple ``Client`` instances. channel = 'ircclient' def init(self, host, port=6667, opts=None): self.host = host self.port = port self.opts = opts self.hostname = gethostname() self.nick = opts.nick self.ircchannel = opts.channel # Add TCPClient and IRC to the system. TCPClient(channel=self.channel).register(self) IRC(channel=self.channel).register(self) # Enable Debugging? if opts.debug: Debugger().register(self) def ready(self, component): """ ready Event This event is triggered by the underlying ``TCPClient`` Component when it is ready to start making a new connection. """ self.fire(connect(self.host, self.port)) def connected(self, host, port): """ connected Event This event is triggered by the underlying ``TCPClient`` Component when a successfully connection has been made. """ print('Connected to %s:%d' % (host, port)) nick = self.nick hostname = self.hostname name = f'{nick} on {hostname} using circuits/{systemVersion}' self.fire(NICK(nick)) self.fire(USER(nick, nick, self.hostname, name)) def disconnected(self): """ disconnected Event This event is triggered by the underlying ``TCPClient`` Component when the connection has been disconnected. """ print('Disconnecetd from %s:%d' % (self.host, self.port)) raise SystemExit(0) def numeric(self, source, numeric, *args): """ numeric Event This event is triggered by the ``IRC`` Protocol Component when we have received an IRC Numberic Event from server we are connected to. """ if numeric == 1: self.fire(JOIN(self.ircchannel)) elif numeric == 433: self.nick = newnick = '%s_' % self.nick self.fire(NICK(newnick)) def join(self, source, channel): """ join Event This event is triggered by the ``IRC`` Protocol Component when a user has joined a channel. """ if source[0].lower() == self.nick.lower(): print('Joined %s' % channel) else: print( '--> %s (%s) has joined %s' % ( source[0], '@'.join(source[1:]), channel, ), ) def notice(self, source, target, message): """ notice Event This event is triggered by the ``IRC`` Protocol Component for each notice we receieve from the server. """ print(f'-{source[0]}- {message}') def privmsg(self, source, target, message): """ privmsg Event This event is triggered by the ``IRC`` Protocol Component for each message we receieve from the server. """ if target[0] == '#': print(f'<{source[0]}> {message}') else: print(f'-{source[0]}- {message}') @handler('read', channel='stdin') def stdin_read(self, data): """ read Event (on channel ``stdin``) This is the event handler for ``read`` events specifically from the ``stdin`` channel. This is triggered each time stdin has data that it has read. """ data = data.strip().decode('utf-8') print(f'<{self.nick:s}> {data:s}') self.fire(PRIVMSG(self.ircchannel, data)) def main(): opts, args = parse_options() host = args[0] port = int(args[1]) if len(args) > 1 else 6667 # Configure and run the system. client = Client(host, port, opts=opts) stdin.register(client) client.run() if __name__ == '__main__': main() circuits-3.2.3/examples/ircd.py000077500000000000000000000256171460335514400165000ustar00rootroot00000000000000#!/usr/bin/env python """ Example IRC Server .. note:: This is an example only and is feature incomplete. Implements commands:: USER NICK JOIN PART NICK WHO QUIT """ import logging from argparse import ArgumentDefaultsHelpFormatter, ArgumentParser from collections import defaultdict from itertools import chain from logging import getLogger from operator import attrgetter from sys import stderr from time import time from circuits import Component, Debugger, handler from circuits.net.events import close, write from circuits.net.sockets import TCPServer from circuits.protocols.irc import IRC, Message, joinprefix, reply, response from circuits.protocols.irc.replies import ( ERR_NICKNAMEINUSE, ERR_NOMOTD, ERR_NOSUCHCHANNEL, ERR_NOSUCHNICK, ERR_UNKNOWNCOMMAND, RPL_CHANNELMODEIS, RPL_ENDOFNAMES, RPL_ENDOFWHO, RPL_LIST, RPL_LISTEND, RPL_LISTSTART, RPL_NAMEREPLY, RPL_NOTOPIC, RPL_TOPIC, RPL_WELCOME, RPL_WHOREPLY, RPL_YOURHOST, ) __version__ = '0.0.1' def parse_args(): parser = ArgumentParser( description=__doc__, formatter_class=ArgumentDefaultsHelpFormatter, ) parser.add_argument( '-v', '--version', action='version', version=f'%(prog)s {__version__}', ) parser.add_argument( '-b', '--bind', action='store', type=str, default='0.0.0.0:6667', dest='bind', help='Bind to address:[port]', ) parser.add_argument( '--debug', action='store_true', default=False, dest='debug', help='Enable debug mode', ) return parser.parse_args() class Channel: def __init__(self, name): self.name = name self.topic = None self.mode = '+n' self.users = [] class User: def __init__(self, sock, host, port): self.sock = sock self.host = host self.port = port self.nick = None self.mode = '' self.away = False self.channels = [] self.signon = None self.registered = False self.userinfo = UserInfo() @property def prefix(self): userinfo = self.userinfo return joinprefix(self.nick, userinfo.user, userinfo.host) class UserInfo: def __init__(self, user=None, host=None, name=None): self.user = user self.host = host self.name = name class Server(Component): channel = 'server' network = 'Test' host = 'localhost' version = f'ircd v{__version__:s}' def init(self, args, logger=None): self.args = args self.logger = logger or getLogger(__name__) self.buffers = defaultdict(bytes) self.nicks = {} self.users = {} self.channels = {} Debugger(events=args.debug, logger=self.logger).register(self) if ':' in args.bind: address, port = args.bind.split(':') port = int(port) else: address, port = args.bind, 6667 bind = (address, port) self.transport = TCPServer( bind, channel=self.channel, ).register(self) self.protocol = IRC( channel=self.channel, getBuffer=self.buffers.__getitem__, updateBuffer=self.buffers.__setitem__, ).register(self) def _notify(self, users, message, exclude=None): for user in users: if exclude is not None and user is exclude: continue self.fire(reply(user.sock, message)) def read(self, sock, data): user = self.users[sock] host, port = user.host, user.port self.logger.info( f'I: [{host:s}:{port:d}] {data!r:s}', ) def write(self, sock, data): user = self.users[sock] host, port = user.host, user.port self.logger.info( f'O: [{host:s}:{port:d}] {data!r:s}', ) def ready(self, server, bind): stderr.write( 'ircd v{:s} ready! Listening on: {:s}\n'.format( __version__, '{:s}:{:d}'.format(*bind), ), ) def connect(self, sock, host, port): self.users[sock] = User(sock, host, port) self.logger.info(f'C: [{host:s}:{port:d}]') def disconnect(self, sock): if sock not in self.users: return user = self.users[sock] self.logger.info(f'D: [{user.host:s}:{user.port:d}]') nick = user.nick user, host = user.userinfo.user, user.userinfo.host yield self.call( response.create('quit', sock, (nick, user, host), 'Leaving'), ) del self.users[sock] if nick in self.nicks: del self.nicks[nick] def quit(self, sock, source, reason='Leaving'): user = self.users[sock] channels = [self.channels[channel] for channel in user.channels] for channel in channels: channel.users.remove(user) if not channel.users: del self.channels[channel.name] users = chain(*map(attrgetter('users'), channels)) self.fire(close(sock)) self._notify( users, Message('QUIT', reason, prefix=user.prefix), user, ) def nick(self, sock, source, nick): user = self.users[sock] if nick in self.nicks: return self.fire(reply(sock, ERR_NICKNAMEINUSE(nick))) if not user.registered: user.registered = True self.fire(response.create('signon', sock, user)) user.nick = nick self.nicks[nick] = user return None def user(self, sock, source, nick, user, host, name): _user = self.users[sock] _user.userinfo.user = user _user.userinfo.host = host _user.userinfo.name = name if _user.nick is not None: _user.registered = True self.fire(response.create('signon', sock, source)) def signon(self, sock, source): user = self.users[sock] if user.signon: return user.signon = time() self.fire(reply(sock, RPL_WELCOME(self.network))) self.fire(reply(sock, RPL_YOURHOST(self.host, self.version))) self.fire(reply(sock, ERR_NOMOTD())) # Force users to join #circuits self.fire(response.create('join', sock, source, '#circuits')) def join(self, sock, source, name): user = self.users[sock] if name not in self.channels: channel = self.channels[name] = Channel(name) else: channel = self.channels[name] if user in channel.users: return user.channels.append(name) channel.users.append(user) self._notify( channel.users, Message('JOIN', name, prefix=user.prefix), ) if channel.topic: self.fire(reply(sock, RPL_TOPIC(channel.topic))) else: self.fire(reply(sock, RPL_NOTOPIC(channel.name))) self.fire(reply(sock, RPL_NAMEREPLY(channel.name, [u.prefix for u in channel.users]))) self.fire(reply(sock, RPL_ENDOFNAMES(channel.name))) def part(self, sock, source, name, reason='Leaving'): user = self.users[sock] channel = self.channels[name] self._notify( channel.users, Message('PART', name, reason, prefix=user.prefix), ) user.channels.remove(name) channel.users.remove(user) if not channel.users: del self.channels[name] def privmsg(self, sock, source, target, message): user = self.users[sock] if target.startswith('#'): if target not in self.channels: return self.fire(reply(sock, ERR_NOSUCHCHANNEL(target))) channel = self.channels[target] self._notify( channel.users, Message('PRIVMSG', target, message, prefix=user.prefix), user, ) return None if target not in self.nicks: return self.fire(reply(sock, ERR_NOSUCHNICK(target))) self.fire( reply( self.nicks[target].sock, Message('PRIVMSG', target, message, prefix=user.prefix), ), ) return None def who(self, sock, source, mask): if mask.startswith('#'): if mask not in self.channels: return self.fire(reply(sock, ERR_NOSUCHCHANNEL(mask))) channel = self.channels[mask] for user in channel.users: self.fire(reply(sock, RPL_WHOREPLY(user, mask, self.host))) self.fire(reply(sock, RPL_ENDOFWHO(mask))) return None if mask not in self.nicks: return self.fire(reply(sock, ERR_NOSUCHNICK(mask))) user = self.nicks[mask] self.fire(reply(sock, RPL_WHOREPLY(user, mask, self.host))) self.fire(reply(sock, RPL_ENDOFWHO(mask))) return None def ping(self, event, sock, source, server): event.stop() self.fire(reply(sock, Message('PONG', server))) def reply(self, target, message): user = self.users[target] if message.add_nick: message.args.insert(0, user.nick or '') if message.prefix is None: message.prefix = self.host self.fire(write(target, bytes(message))) def mode(self, sock, source, mask, mode=None, params=None): if mask.startswith('#'): if mask not in self.channels: return self.fire(reply(sock, ERR_NOSUCHCHANNEL(mask))) channel = self.channels[mask] if not params: self.fire(reply(sock, RPL_CHANNELMODEIS(channel.name, channel.mode))) return None elif mask not in self.users: return self.fire(reply(sock, ERR_NOSUCHNICK(mask))) return None def list(self, sock, source): self.fire(reply(sock, RPL_LISTSTART())) for channel in self.channels.values(): self.fire(reply(sock, RPL_LIST(channel, str(len(channel.users)), channel.topic or ''))) self.fire(reply(sock, RPL_LISTEND())) @property def commands(self): exclude = {'ready', 'connect', 'disconnect', 'read', 'write'} return list(set(self.events()) - exclude) @handler() def _on_event(self, event, *args, **kwargs): if event.name.endswith('_done'): return if isinstance(event, response) and event.name not in self.commands: event.stop() self.fire(reply(args[0], ERR_UNKNOWNCOMMAND(event.name))) def main(): args = parse_args() logging.basicConfig( format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', stream=stderr, level=logging.DEBUG if args.debug else logging.INFO, ) logger = getLogger(__name__) Server(args, logger=logger).run() if __name__ == '__main__': main() circuits-3.2.3/examples/node/000077500000000000000000000000001460335514400161145ustar00rootroot00000000000000circuits-3.2.3/examples/node/hello_node.py000077500000000000000000000025121460335514400206010ustar00rootroot00000000000000#!/usr/bin/python -i """Node Example To use this example run it interactively through the Python interactive shell using the -i option as per the shebang line above. i.e: python -i hello_node.py host port At the python prompt: >>> x = app.fire(hello()) >>> >>> x >>> y = app.fire(remote(hello(), "test")) . . . >>> y , 'app2' channel=None)> """ # noqa import sys from os import getpid from circuits import Component, Event from circuits.node import Node, remote # noqa class hello(Event): """hello Event""" class App(Component): def ready(self, client): print('Ready!') def connected(self, host, port): print(f'Connected to {host}:{port}') print('Try: x = app.fire(hello())') def hello(self): print('Now try: y = app.fire(remote(hello(), "test"))') return f'Hello World! ({getpid():d})' # Setup app1 with a debugger app = App() node = Node().register(app) host = sys.argv[1] port = int(sys.argv[2]) bind = (host, port) # Add an address of a node to talk to called "test" node.add('test', *bind) # Start app as a thread app.start() circuits-3.2.3/examples/node/increment/000077500000000000000000000000001460335514400201005ustar00rootroot00000000000000circuits-3.2.3/examples/node/increment/client.py000077500000000000000000000030061460335514400217320ustar00rootroot00000000000000#!/usr/bin/env python from optparse import OptionParser from circuits import Component, Debugger, Event from circuits.node import Node, remote __version__ = '0.0.1' USAGE = '%prog [options]' VERSION = '%prog v' + __version__ class increment(Event): def __init__(self, value): Event.__init__(self, value) def parse_options(): parser = OptionParser(usage=USAGE, version=VERSION) parser.add_option( '-i', '--ip', action='store', type='string', default='127.0.0.1:8000', dest='bind', help='Bind to address:[port]', ) parser.add_option( '-d', '--debug', action='store_true', default=False, dest='debug', help='Enable debug mode', ) opts, args = parser.parse_args() return opts, args class NodeClient(Component): def init(self, args, opts): if opts.debug: Debugger().register(self) if ':' in opts.bind: address, port = opts.bind.split(':') port = int(port) else: address, port = opts.bind, 8000 node = Node().register(self) node.add('peer_name', address, port) def connected_to(self, *args, **kwargs): i = 0 while True: print(i) i = (yield self.call(remote(increment(i), 'peer_name'))).value def main(): opts, args = parse_options() # Configure and "run" the System. NodeClient(args, opts).run() if __name__ == '__main__': main() circuits-3.2.3/examples/node/increment/client_auto_binding.py000077500000000000000000000031251460335514400244560ustar00rootroot00000000000000#!/usr/bin/env python from optparse import OptionParser from circuits import Component, Debugger, Event from circuits.node import Node __version__ = '0.0.1' USAGE = '%prog [options]' VERSION = '%prog v' + __version__ class increment(Event): def __init__(self, value): Event.__init__(self, value) def parse_options(): parser = OptionParser(usage=USAGE, version=VERSION) parser.add_option( '-i', '--ip', action='store', type='string', default='127.0.0.1:8000', dest='bind', help='Bind to address:[port]', ) parser.add_option( '-d', '--debug', action='store_true', default=False, dest='debug', help='Enable debug mode', ) opts, args = parser.parse_args() return opts, args class NodeClient(Component): def init(self, args, opts): if opts.debug: Debugger().register(self) if ':' in opts.bind: address, port = opts.bind.split(':') port = int(port) else: address, port = opts.bind, 8000 auto_remote_event = { 'increment': '*', } node = Node().register(self) node.add('peer_name', address, port, auto_remote_event=auto_remote_event) def connected_to(self, *args, **kwargs): i = 0 while True: print(i) i = (yield self.call(increment(i))).value def main(): opts, args = parse_options() # Configure and "run" the System. NodeClient(args, opts).run() if __name__ == '__main__': main() circuits-3.2.3/examples/node/increment/server.py000077500000000000000000000031531460335514400217650ustar00rootroot00000000000000#!/usr/bin/env python from optparse import OptionParser from circuits import Component, Debugger, handler from circuits.node import Node __version__ = '0.0.1' USAGE = '%prog [options]' VERSION = '%prog v' + __version__ def parse_options(): parser = OptionParser(usage=USAGE, version=VERSION) parser.add_option( '-b', '--bind', action='store', type='string', default='0.0.0.0:8000', dest='bind', help='Bind to address:[port]', ) parser.add_option( '-d', '--debug', action='store_true', default=False, dest='debug', help='Enable debug mode', ) opts, args = parser.parse_args() return opts, args class NodeServer(Component): def init(self, args, opts): if opts.debug: Debugger().register(self) if ':' in opts.bind: address, port = opts.bind.split(':') port = int(port) else: address, port = opts.bind, 8000 Node(port=port, server_ip=address).register(self) def connect(self, sock, host, port): print('Peer connected: %s:%d' % (host, port)) def disconnect(self, sock): print('Peer disconnected: %s' % sock) def ready(self, server, bind): print('Server ready: %s:%d' % bind) @handler('increment') def increment(self, value): print('Execute increment event (value: %d)' % value) return value + 1 def main(): opts, args = parse_options() # Configure and "run" the System. NodeServer(args, opts).run() if __name__ == '__main__': main() circuits-3.2.3/examples/node/nodeserver.py000077500000000000000000000045711460335514400206540ustar00rootroot00000000000000#!/usr/bin/env python """ Node Server Example This example demonstrates how to create a very simple node server that supports bi-diractional messaging between server and connected clients forming a cluster of nodes. """ from optparse import OptionParser from os import getpid from circuits import Component, Debugger from circuits.node import Node __version__ = '0.0.1' USAGE = '%prog [options]' VERSION = '%prog v' + __version__ def parse_options(): parser = OptionParser(usage=USAGE, version=VERSION) parser.add_option( '-b', '--bind', action='store', type='string', default='0.0.0.0:8000', dest='bind', help='Bind to address:[port]', ) parser.add_option( '-d', '--debug', action='store_true', default=False, dest='debug', help='Enable debug mode', ) opts, args = parser.parse_args() return opts, args class NodeServer(Component): def init(self, args, opts): """ Initialize our ``ChatServer`` Component. This uses the convenience ``init`` method which is called after the component is proeprly constructed and initialized and passed the same args and kwargs that were passed during construction. """ self.args = args self.opts = opts self.clients = {} if opts.debug: Debugger().register(self) if ':' in opts.bind: address, port = opts.bind.split(':') port = int(port) else: address, port = opts.bind, 8000 Node(port=port, server_ip=address).register(self) def connect(self, sock, host, port): """Connect Event -- Triggered for new connecting clients""" self.clients[sock] = { 'host': sock, 'port': port, } def disconnect(self, sock): """Disconnect Event -- Triggered for disconnecting clients""" if sock not in self.clients: return del self.clients[sock] def ready(self, server, bind): print('Ready! Listening on {}:{}'.format(*bind)) print('Waiting for remote events...') def hello(self): return f'Hello World! ({getpid():d})' def main(): opts, args = parse_options() # Configure and "run" the System. NodeServer(args, opts).run() if __name__ == '__main__': main() circuits-3.2.3/examples/node/send_all_client/000077500000000000000000000000001460335514400212335ustar00rootroot00000000000000circuits-3.2.3/examples/node/send_all_client/client.py000077500000000000000000000024651460335514400230750ustar00rootroot00000000000000#!/usr/bin/env python from optparse import OptionParser from circuits import Component, Debugger from circuits.node import Node __version__ = '0.0.1' USAGE = '%prog [options]' VERSION = '%prog v' + __version__ def parse_options(): parser = OptionParser(usage=USAGE, version=VERSION) parser.add_option( '-i', '--ip', action='store', type='string', default='127.0.0.1:8000', dest='bind', help='Bind to address:[port]', ) parser.add_option( '-d', '--debug', action='store_true', default=False, dest='debug', help='Enable debug mode', ) opts, args = parser.parse_args() return opts, args class NodeClient(Component): def init(self, args, opts): if opts.debug: Debugger().register(self) if ':' in opts.bind: address, port = opts.bind.split(':') port = int(port) else: address, port = opts.bind, 8000 node = Node().register(self) node.add('peer_name', address, port) def send_all_event(self, infos): print('receive: %s' % infos) def main(): opts, args = parse_options() # Configure and "run" the System. NodeClient(args, opts).run() if __name__ == '__main__': main() circuits-3.2.3/examples/node/send_all_client/server.py000077500000000000000000000035071460335514400231230ustar00rootroot00000000000000#!/usr/bin/env python from datetime import datetime from optparse import OptionParser from circuits import Component, Debugger, Event, Timer from circuits.node import Node __version__ = '0.0.1' USAGE = '%prog [options]' VERSION = '%prog v' + __version__ def parse_options(): parser = OptionParser(usage=USAGE, version=VERSION) parser.add_option( '-b', '--bind', action='store', type='string', default='0.0.0.0:8000', dest='bind', help='Bind to address:[port]', ) parser.add_option( '-d', '--debug', action='store_true', default=False, dest='debug', help='Enable debug mode', ) opts, args = parser.parse_args() return opts, args class send_all_event(Event): def __init__(self, infos): super().__init__(infos) class NodeServer(Component): def init(self, args, opts): if opts.debug: Debugger().register(self) if ':' in opts.bind: address, port = opts.bind.split(':') port = int(port) else: address, port = opts.bind, 8000 self.node = Node(port=port, server_ip=address).register(self) def connect(self, sock, host, port): print('Peer connected: %s:%d' % (host, port)) def disconnect(self, sock): print('Peer disconnected: %s' % sock) def ready(self, server, bind): print('Server ready: %s:%d' % bind) Timer(3, Event.create('send_all'), persist=True).register(self) def send_all(self): event = send_all_event(str(datetime.now())) print('send: %s' % event) self.node.server.send_all(event) def main(): opts, args = parse_options() # Configure and "run" the System. NodeServer(args, opts).run() if __name__ == '__main__': main() circuits-3.2.3/examples/ping.py000077500000000000000000000020401460335514400164750ustar00rootroot00000000000000#!/usr/bin/env python """ Clone of the standard UNIX "ping" command. This example shows how you can utilize some of the buitlin I/O components in circuits to write a very simple clone of the standard UNIX "ping" command. This example merely wraps the standard UNIX "/usr/bin/ping" command in a subprocess using the ``circuits.io.Process`` Component for Asyncchronous I/O communications with the process. """ import sys from circuits import Component, Debugger from circuits.io import Process, stdout, write class Ping(Component): # This adds the already instantiated stdout instnace stdout = stdout def init(self, host): self.p = Process(['/bin/ping', host]).register(self) self.p.start() def read(self, data): """ read Event Handler This is fired by the File Component when there is data to be read from the underlying file that was opened. """ self.fire(write(data), stdout) # Start and "run" the system. app = Ping(sys.argv[1]) Debugger().register(app) app.run() circuits-3.2.3/examples/pingpong.py000077500000000000000000000026261460335514400173730ustar00rootroot00000000000000#!/usr/bin/env python """ Bridge Example A Bridge example that demonstrates bidirectional parent/child communications and displays the no. of events per second and latency. """ import sys from signal import SIGINT, SIGTERM from time import time from traceback import format_exc from circuits import Component, Event, handler, ipc def log(msg, *args, **kwargs): sys.stderr.write('{:s}{:s}'.format(msg.format(*args), kwargs.get('n', '\n'))) sys.stderr.flush() def error(e): log('ERROR: {0:s}', e) log(format_exc()) def status(msg, *args): log('\r\x1b[K{0:s}', msg.format(*args), n='') class ping(Event): """ping Event""" class pong(Event): """pong Event""" class Child(Component): def ping(self, ts): self.fire(ipc(pong(ts, time()))) class App(Component): def init(self): self.events = 0 self.stime = time() Child().start(process=True, link=self) def ready(self, *args): self.fire(ipc(ping(time()))) def pong(self, ts1, ts2): latency = (ts2 - ts1) * 1000.0 status( f'{int(self.events / (time() - self.stime)):d} event/s @ {latency:0.2f}ms latency', ) self.fire(ipc(ping(time()))) def signal(self, signo, stack): if signo in [SIGINT, SIGTERM]: raise SystemExit(0) @handler() def on_event(self, *args, **kwargs): self.events += 1 App().run() circuits-3.2.3/examples/portforward.py000077500000000000000000000122631460335514400201210ustar00rootroot00000000000000#!/usr/bin/env python """ A Port Forwarding Example This example demonstrates slightly more complex features and behaviors implementing a TCP/UDP Port Forwarder of network traffic. This can be used as a simple tool to forward traffic from one port to another. Example: ------- ./portforward.py 0.0.0.0:2222 127.0.0.1:22 This example also has support for daemonizing the process into the background. """ from optparse import OptionParser from uuid import uuid4 as uuid from circuits import Component, Debugger, handler from circuits.app import Daemon from circuits.net.events import close, connect, write from circuits.net.sockets import TCPClient, TCPServer __version__ = '0.2' USAGE = '%prog [options] ' VERSION = '%prog v' + __version__ def parse_options(): parser = OptionParser(usage=USAGE, version=VERSION) parser.add_option( '-d', '--daemon', action='store_true', default=False, dest='daemon', help='Enable daemon mode (fork into the background)', ) parser.add_option( '', '--debug', action='store_true', default=False, dest='debug', help='Enable debug mode (verbose event output)', ) opts, args = parser.parse_args() if len(args) < 2: parser.print_help() raise SystemExit(1) return opts, args def _on_target_disconnected(self, event): """ Disconnected Event Handler This unbound function will be later added as an event handler to a dynamically created and registered client instance and used to process Disconnected events of a connected client. """ channel = event.channels[0] sock = self._sockets[channel] self.fire(close(sock), 'source') del self._sockets[channel] del self._clients[sock] def _on_target_ready(self, component): """ Ready Event Handler This unbound function will be later added as an event handler to a dynamically created and registered client instance and used to process Ready events of a registered client. """ self.fire(connect(*self._target, secure=self._secure), component.channel) def _on_target_read(self, event, data): """ Read Event Handler This unbound function will be later added as an event handler to a dynamically created and registered client instance and used to process Read events of a connected client. """ sock = self._sockets[event.channels[0]] self.fire(write(sock, data), 'source') class PortForwarder(Component): def init(self, source, target, secure=False): self._source = source self._target = target self._secure = secure self._clients = {} self._sockets = {} # Setup our components and register them. server = TCPServer(self._source, secure=self._secure, channel='source') server.register(self) @handler('connect', channel='source') def _on_source_connect(self, sock, host, port): """ Explicitly defined connect Event Handler This evens is triggered by the underlying TCPServer Component when a new client connection has been made. Here we dynamically create a Client instance, registere it and add custom event handlers to handle the events of the newly created client. The client is registered with a unique channel per connection. """ bind = 0 channel = uuid() client = TCPClient(bind, channel=channel) client.register(self) self.addHandler( handler('disconnected', channel=channel)(_on_target_disconnected), ) self.addHandler( handler('ready', channel=channel)(_on_target_ready), ) self.addHandler( handler('read', channel=channel)(_on_target_read), ) self._clients[sock] = client self._sockets[client.channel] = sock @handler('read', channel='source') def _on_source_read(self, sock, data): """ Explicitly defined Read Event Handler This evens is triggered by the underlying TCPServer Component when a connected client has some data ready to be processed. Here we simply fire a cooresponding write event to the cooresponding matching client which we lookup using the socket object as the key to determinte the unique id. """ client = self._clients[sock] self.fire(write(data), client.channel) def sanitize(s): if ':' in s: address, port = s.split(':') port = int(port) return address, port return s def main(): opts, args = parse_options() source = sanitize(args[0]) target = sanitize(args[1]) if type(source) is not tuple: print('ERROR: source address must specify port (address:port)') raise SystemExit(-1) if type(target) is not tuple: print('ERROR: target address must specify port (address:port)') raise SystemExit(-1) system = PortForwarder(source, target) if opts.daemon: Daemon('portforward.pid').register(system) if opts.debug: Debugger().register(system) system.run() if __name__ == '__main__': main() circuits-3.2.3/examples/primitives/000077500000000000000000000000001460335514400173625ustar00rootroot00000000000000circuits-3.2.3/examples/primitives/call.py000077500000000000000000000010351460335514400206510ustar00rootroot00000000000000#!/usr/bin/env python from circuits import Component, Event class hello(Event): """hello Event""" class foo(Event): """foo Event""" class bar(Event): """bar Event""" class App(Component): def foo(self): return 1 def bar(self): return 2 def hello(self): x = yield self.call(foo()) y = yield self.call(bar()) yield x.value + y.value def started(self, component): x = yield self.call(hello()) print(f'{x.value:d}') self.stop() App().run() circuits-3.2.3/examples/primitives/fire.py000077500000000000000000000007511460335514400206670ustar00rootroot00000000000000#!/usr/bin/env python from circuits import Component, Event class hello(Event): """hello Event""" class foo(Event): """foo Event""" class bar(Event): """bar Event""" class App(Component): def foo(self): print('Foo!') def bar(self): print('Bar!') def hello(self): self.fire(foo()) self.fire(bar()) print('Hello World!') def started(self, component): self.fire(hello()) self.stop() App().run() circuits-3.2.3/examples/primitives/wait.py000077500000000000000000000011541460335514400207040ustar00rootroot00000000000000#!/usr/bin/env python from circuits import Component, Event class hello(Event): """hello Event""" class foo(Event): """foo Event""" class bar(Event): """bar Event""" class App(Component): def foo(self): return 1 def bar(self): return 2 def hello(self): x = self.fire(foo()) yield self.wait('foo') y = self.fire(bar()) yield self.wait('bar') yield x.value + y.value def started(self, component): x = self.fire(hello()) yield self.wait('hello') print(f'{x.value:d}') self.stop() App().run() circuits-3.2.3/examples/proxy.py000077500000000000000000000030241460335514400167240ustar00rootroot00000000000000#!/usr/bin/env python from uuid import uuid4 as uuid from circuits import Component, Debugger from circuits.net.events import close, connect, write from circuits.net.sockets import TCPClient, TCPServer class Client(Component): channel = 'client' def init(self, sock, host, port, channel=channel): self.sock = sock self.host = host self.port = port TCPClient(channel=self.channel).register(self) def ready(self, *args): self.fire(connect(self.host, self.port)) def disconnect(self, *args): self.fire(close(self.sock), self.parent.channel) def read(self, data): self.fire(write(self.sock, data), self.parent.channel) class Proxy(Component): channel = 'server' def init(self, bind, host, port): self.bind = bind self.host = host self.port = port self.clients = {} TCPServer(self.bind).register(self) def connect(self, sock, host, port): channel = uuid() client = Client( sock, self.host, self.port, channel=channel, ).register(self) self.clients[sock] = client def disconnect(self, sock): client = self.clients.get(sock) if client is not None: client.unregister() del self.clients[sock] def read(self, sock, data): client = self.clients[sock] self.fire(write(data), client.channel) app = Proxy(('0.0.0.0', 3333), '127.0.0.1', 22) Debugger().register(app) app.run() circuits-3.2.3/examples/signals.py000077500000000000000000000016261460335514400172110ustar00rootroot00000000000000#!/usr/bin/env python """ circuits Signal Handling A modified version of the circuits Hello World example to demonstrate how to customize signal handling and cause a delayed system terminate. """ from circuits import Component, Debugger, Event, Timer class hello(Event): """hello Event""" class App(Component): def hello(self): """Hello Event Handler""" print('Hello World!') def signal(self, event, signo, stack): Timer(3, Event.create('terminate')).register(self) print('Terminating in 3 seconds...') def terminate(self): self.stop() def started(self, component): """ Started Event Handler This is fired internally when your application starts up and can be used to trigger events that only occur once during startup. """ self.fire(hello()) # Fire hello Event (App() + Debugger()).run() circuits-3.2.3/examples/stompclient.py000066400000000000000000000054251460335514400201100ustar00rootroot00000000000000#!/usr/bin/env python """ Example usage for StompClient component Requires a STOMP server to connect to. """ import logging import ssl from circuits import Component, Event, Timer from circuits.protocols.stomp.client import ACK_AUTO, StompClient from circuits.protocols.stomp.events import connect, send, subscribe LOG = logging.getLogger(__name__) class QueueHandler(Component): def __init__(self, queue, host=None, *args, **kwargs): super().__init__(*args, **kwargs) self.queue = queue self.host = host def registered(self, event, component, parent): if component.parent is self: self.fire(Event.create('reconnect')) def connected(self): """Client has connected to the STOMP server""" LOG.info('STOMP connected.') # Let's subscribe to the message destination self.fire(subscribe(self.queue, ack=ACK_AUTO)) def subscribe_success(self, event, *args, **kwargs): """Subscribed to message destination""" # Let's fire off some messages self.fire(send(headers=None, body='Hello World', destination=self.queue)) self.fire(send(headers=None, body='Hello Again World', destination=self.queue)) def heartbeat_timeout(self): """Heartbeat timed out from the STOMP server""" LOG.error('STOMP heartbeat timeout!') # Set a timer to automatically reconnect Timer(10, Event.create('Reconnect')).register(self) def on_stomp_error(self, headers, message, error): """STOMP produced an error.""" LOG.error('STOMP listener: Error:\n%s', message or error) def message(self, event, headers, message): """STOMP produced a message.""" LOG.info('Message Received: %s', message) def disconnected(self, event, *args, **kwargs): # Wait a while then try to reconnect LOG.info('We got disconnected, reconnect') Timer(10, Event.create('reconnect')).register(self) def reconnect(self): """Try (re)connect to the STOMP server""" LOG.info('STOMP attempting to connect') self.fire(connect(host=self.host)) def main(): logging.basicConfig() logging.getLogger().setLevel(logging.INFO) # Configure and run context = ssl.create_default_context() context.check_hostname = True context.verify_mode = ssl.CERT_REQUIRED # You can create an STOMP server to test for free at https://www.cloudamqp.com/ uri = 'orangutan.rmq.cloudamqp.com' port = 61614 login = 'xxxyyy' passcode = 'somepassword' host = 'xxxyyy' queue = 'test1' s = StompClient(uri, port, username=login, password=passcode, heartbeats=(10000, 10000), ssl_context=context) qr = QueueHandler(queue, host=host) s.register(qr) qr.run() if __name__ == '__main__': main() circuits-3.2.3/examples/tail.py000077500000000000000000000024241460335514400164770ustar00rootroot00000000000000#!/usr/bin/env python """ Clone of the standard UNIX "tail" command. This example shows how you can utilize some of the buitlin I/O components in circuits to write a very simple clone of the standard UNIX "tail" command. """ import sys from circuits import Component, Debugger from circuits.io import File, Write, stdout class Tail(Component): # Shorthand for declaring a compoent to be a part of this component. stdout = stdout def init(self, filename): """ Initialize Tail Component Using the convenience ``init`` method we simply register a ``File`` Component as part of our ``Tail`` Component and ask it to seek to the end of the file. """ File(filename, 'r', autoclose=False).register(self).seek(0, 2) def read(self, data): """ Read Event Handler This event is triggered by the underlying ``File`` Component for when there is data to be processed. Here we simply fire a ``Write`` event and target the ``stdout`` component instance that is a part of our system -- thus writing the contents of the file we read out to standard output. """ self.fire(Write(data), self.stdout) # Setup and run the system. (Tail(sys.argv[1]) + Debugger()).run() circuits-3.2.3/examples/telnet.py000077500000000000000000000103731460335514400170430ustar00rootroot00000000000000#!/usr/bin/env python """ Telnet Example A basic telnet-like clone that connects to remote hosts via tcp and allows the user to send data to the remote server. This example demonstrates: * Basic Component creation. * Basic Event handling. * Basiv Networking * Basic Request/Response Networking This example makes use of: * Component * Event * net.sockets.TCPClient """ import os from optparse import OptionParser import circuits from circuits import Component, handler from circuits.io import stdin from circuits.net.events import connect, write from circuits.net.sockets import TCPClient, UDPClient, UNIXClient from circuits.tools import graph USAGE = '%prog [options] host [port]' VERSION = '%prog v' + circuits.__version__ def parse_options(): parser = OptionParser(usage=USAGE, version=VERSION) parser.add_option( '-s', '--secure', action='store_true', default=False, dest='secure', help='Enable secure mode', ) parser.add_option( '-u', '--udp', action='store_true', default=False, dest='udp', help='Use UDP transport', ) parser.add_option( '-v', '--verbose', action='store_true', default=False, dest='verbose', help='Enable verbose debugging', ) opts, args = parser.parse_args() if len(args) < 1: parser.print_help() raise SystemExit(1) return opts, args class Telnet(Component): # Define a separate channel for this component so we don't clash with # the ``read`` event of the ``stdin`` component. channel = 'telnet' def __init__(self, *args, **opts): super().__init__() self.args = args self.opts = opts if len(args) == 1: if os.path.exists(args[0]): UNIXClient(channel=self.channel).register(self) host = dest = port = args[0] dest = (dest,) else: raise OSError('Path %s not found' % args[0]) else: if not opts['udp']: TCPClient(channel=self.channel).register(self) else: UDPClient(0, channel=self.channel).register(self) host, port = args port = int(port) dest = host, port self.host = host self.port = port print('Trying %s ...' % host) if not opts['udp']: self.fire(connect(*dest, secure=opts['secure'])) else: self.fire(write((host, port), b'\x00')) def ready(self, *args): graph(self.root) def connected(self, host, port=None): """ connected Event Handler This event is fired by the TCPClient Componentt to indicate a successful connection. """ print(f'connected to {host}') def error(self, *args, **kwargs): """ error Event Handler If any exception/error occurs in the system this event is triggered. """ if len(args) == 3: print(f'ERROR: {args[1]}') else: print(f'ERROR: {args[0]}') def read(self, *args): """ read Event Handler This event is fired by the underlying TCPClient Component when there is data to be read from the connection. """ if len(args) == 1: data = args[0] else: _peer, data = args data = data.strip().decode('utf-8') print(data) # Setup an Event Handler for "read" events on the "stdin" channel. @handler('read', channel='stdin') def _on_stdin_read(self, data): """ read Event Handler for stdin This event is triggered by the connected ``stdin`` component when there is new data to be read in from standard input. """ if not self.opts['udp']: self.fire(write(data)) else: self.fire(write((self.host, self.port), data)) def main(): opts, args = parse_options() # Configure and "run" the System. app = Telnet(*args, **opts.__dict__) if opts.verbose: from circuits import Debugger Debugger().register(app) stdin.register(app) app.run() if __name__ == '__main__': main() circuits-3.2.3/examples/testing/000077500000000000000000000000001460335514400166445ustar00rootroot00000000000000circuits-3.2.3/examples/testing/pytest/000077500000000000000000000000001460335514400201745ustar00rootroot00000000000000circuits-3.2.3/examples/testing/pytest/README.rst000066400000000000000000000014051460335514400216630ustar00rootroot00000000000000Hello World Example -- Test Driven Development ============================================== This is the classical "Hello World!" style application written in circuits using the `Test Driven Development (TDD) `_ software development process. This example serves as a useful basis for developing your own circuits components and applications that are developed using this process. Furthermore all unit tests in the circuits framework and library of components are all written this way in an almost identical fashion. This requires the use of: - `pytest `_ To install pytest: $ pip install pytest To run this example: $ ./hello.py To run the unit test: $ py.test test_hello.py circuits-3.2.3/examples/testing/pytest/conftest.py000066400000000000000000000050221460335514400223720ustar00rootroot00000000000000"""py.test config""" import sys import threading from collections import deque from time import sleep import pytest from circuits import BaseComponent, Debugger, Manager, handler from circuits.core.manager import TIMEOUT class Watcher(BaseComponent): def init(self): self._lock = threading.Lock() self.events = deque() @handler(channel='*', priority=999.9) def _on_event(self, event, *args, **kwargs): with self._lock: self.events.append(event) def wait(self, name, channel=None, timeout=6.0): for _i in range(int(timeout / TIMEOUT)): if channel is None: with self._lock: for event in self.events: if event.name == name: return True else: with self._lock: for event in self.events: if event.name == name and channel in event.channels: return True sleep(TIMEOUT) return None class Flag: status = False class WaitEvent: def __init__(self, manager, name, channel=None, timeout=6.0): if channel is None: channel = getattr(manager, 'channel', None) self.timeout = timeout self.manager = manager flag = Flag() @handler(name, channel=channel) def on_event(self, *args, **kwargs): flag.status = True self.handler = self.manager.addHandler(on_event) self.flag = flag def wait(self): try: for _i in range(int(self.timeout / TIMEOUT)): if self.flag.status: return True sleep(TIMEOUT) finally: self.manager.removeHandler(self.handler) @pytest.fixture(scope='session') def manager(request): manager = Manager() def finalizer(): manager.stop() request.addfinalizer(finalizer) waiter = WaitEvent(manager, 'started') manager.start() assert waiter.wait() if request.config.option.verbose: Debugger().register(manager) return manager @pytest.fixture() def watcher(request, manager): watcher = Watcher().register(manager) def finalizer(): waiter = WaitEvent(manager, 'unregistered') watcher.unregister() waiter.wait() request.addfinalizer(finalizer) return watcher for key, value in {'WaitEvent': WaitEvent, 'PLATFORM': sys.platform, 'PYVER': sys.version_info[:3]}.items(): setattr(pytest, key, value) circuits-3.2.3/examples/testing/pytest/hello.py000077500000000000000000000014301460335514400216520ustar00rootroot00000000000000#!/usr/bin/env python """circuits Hello World""" from circuits import Component, Event class hello(Event): """hello Event""" class App(Component): def hello(self): """Hello Event Handler""" return 'Hello World!' def started(self, component): """ Started Event Handler This is fired internally when your application starts up and can be used to trigger events that only occur once during startup. """ x = self.fire(hello()) # Fire hello Event yield self.wait('hello') # Wait for Hello Event to fire print(x.value) # Print the value we got back from firing Hello raise SystemExit(0) # Terminate the Application def main(): App().run() if __name__ == '__main__': main() circuits-3.2.3/examples/testing/pytest/test_hello.py000066400000000000000000000006311460335514400227100ustar00rootroot00000000000000#!/usr/bin/env python import pytest from hello import App, hello @pytest.fixture() def app(request, manager, watcher): app = App().register(manager) watcher.wait('registered') def finalizer(): app.unregister() request.addfinalizer(finalizer) return app def test(app, watcher): x = app.fire(hello()) assert watcher.wait('hello') assert x.value == 'Hello World!' circuits-3.2.3/examples/timers.py000077500000000000000000000020561460335514400170520ustar00rootroot00000000000000#!/usr/bin/env python """ Simple Timers A trivial simple example of using circuits and timers. """ from circuits import Component, Event, Timer class App(Component): def hello(self): """ hello Event handler Fired once in 5 seconds. """ print('Hello World') def foo(self): """ foo Event handler Fired every 1 seconds. """ print('Foo') def bar(self): """ bar Event handler Fired every 3 seconds. """ print('Bar') def started(self, component): """ started Event handler Setup 3 timers at 5, 1 and 3 seconds. The 2nd two timers are persitent meaning that they are fired repeatedly every 1 and 3 seconds respectively. """ # Timer(seconds, event, persist=False) Timer(5, Event.create('hello')).register(self) Timer(1, Event.create('foo'), persist=True).register(self) Timer(3, Event.create('bar'), persist=True).register(self) App().run() circuits-3.2.3/examples/web/000077500000000000000000000000001460335514400157445ustar00rootroot00000000000000circuits-3.2.3/examples/web/acldemo.py000077500000000000000000000016631460335514400177330ustar00rootroot00000000000000#!/usr/bin/env python from circuits import Component, Debugger, handler from circuits.web import Controller, Server from circuits.web.errors import forbidden class ACL(Component): allowed = ['127.0.0.1'] @handler('request', priority=1.0) def on_request(self, event, request, response): """ Filter Requests applying IP based Authorization Filter any incoming requests at a higher priority than the default dispatcher and apply IP based Authorization returning a 403 Forbidden response if the Remote IP Address does not match the allowed set. """ if request.remote.ip not in self.allowed: event.stop() return forbidden(request, response) return None class Root(Controller): def index(self): return 'Hello World!' app = Server(('0.0.0.0', 8000)) ACL().register(app) Root().register(app) Debugger().register(app) app.run() circuits-3.2.3/examples/web/authdemo.py000077500000000000000000000017411460335514400201320ustar00rootroot00000000000000#!/usr/bin/env python from hashlib import md5 from circuits import Component, handler from circuits.web import Controller, Server from circuits.web.tools import basic_auth, check_auth class Auth(Component): realm = 'Test' users = {'admin': md5(b'admin').hexdigest()} @handler('request', priority=1.0) def on_request(self, event, request, response): """ Filter Requests applying Basic Authentication Filter any incoming requests at a higher priority than the default dispatcher and apply Basic Authentication returning a 403 Forbidden response if Authentication failed. """ if not check_auth(request, response, self.realm, self.users): event.stop() return basic_auth(request, response, self.realm, self.users) return None class Root(Controller): def index(self): return 'Hello World!' app = Server(('0.0.0.0', 8000)) Auth().register(app) Root().register(app) app.run() circuits-3.2.3/examples/web/basecontrollers.py000077500000000000000000000007641460335514400215310ustar00rootroot00000000000000#!/usr/bin/env python from circuits.web import Server from circuits.web.controllers import BaseController, expose class Root(BaseController): @expose('index') def index(self): """ Index Request Handler BaseController(s) do not expose methods as request handlers. Request Handlers _must_ be exposed explicitly using the ``@expose`` decorator. """ return 'Hello World!' app = Server(('0.0.0.0', 8000)) Root().register(app) app.run() circuits-3.2.3/examples/web/baseservers.py000077500000000000000000000010141460335514400206410ustar00rootroot00000000000000#!/usr/bin/env python from circuits import Component from circuits.web import BaseServer class Root(Component): def request(self, request, response): """ Request Handler Using ``BaseServer`` provides no URL dispatching of any kind. Requests are handled by any event handler listening to ``Request`` events and must accept a (request, response) as arguments. """ return 'Hello World!' app = BaseServer(('0.0.0.0', 8000)) Root().register(app) app.run() circuits-3.2.3/examples/web/ca-chain.pem000066400000000000000000000131421460335514400201130ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIDtzCCAp+gAwIBAgIJAMnzwJQDL4VeMA0GCSqGSIb3DQEBBAUAMHIxEzARBgNV BAoTCk15IENvbXBhbnkxEDAOBgNVBAsTB015IFVuaXQxEDAOBgNVBAcTB015IFRv d24xETAPBgNVBAgTCE15IFN0YXRlMQswCQYDVQQGEwJVUzEXMBUGA1UEAxMOU0FN UExFIFJvb3QgQ0EwHhcNMTAwOTE0MTkwOTEwWhcNMjAwOTExMTkwOTEwWjByMRMw EQYDVQQKEwpNeSBDb21wYW55MRAwDgYDVQQLEwdNeSBVbml0MRAwDgYDVQQHEwdN eSBUb3duMREwDwYDVQQIEwhNeSBTdGF0ZTELMAkGA1UEBhMCVVMxFzAVBgNVBAMT DlNBTVBMRSBSb290IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA vtsgzTwqnpHKblC8uHi+L+98q1FhB0hFNHfBTri7BFhOjsvJnOk/T8y2f6o8tpfS 8q0fUtzxOQbgsSekLBdgHGDMdVcEgCM5+zNW4Rvm62pXAi2yo4y4b9/Xl1GZQp1O cCG5jz1s9FbeCYlrfRDPbSDCcxbtK0umnk6XmVYT47JXK8pieSlLidveMkhrrhlv fg8KjQjljihZRQBeR4OQ/EbCKbLYTQWpGJCle/JZe/N0nm0iYKgehBjKZreCM9oS fVUpp3ID46VQ3S9J3x1PbHiv75EBRMltY6kCZSllxT9TmifVSO2BWRJB0H5g7hR1 xSY/PegUj09clRIyKVhu6QIDAQABo1AwTjAMBgNVHRMEBTADAQH/MB0GA1UdDgQW BBSXjmOlgHIg/X32Gf6byJzr6ME2QDAfBgNVHSMEGDAWgBSXjmOlgHIg/X32Gf6b yJzr6ME2QDANBgkqhkiG9w0BAQQFAAOCAQEArXdsXg/nUeJk3Usslm1PDeHztiGQ GH2yNdAjRVL0yV1z087Ikn9S0MCrqFsvc0b1LdlpfOWrzn5Kg7JvkhfqYvIHfmv1 bwtSXfKh1/Sbd2mZzdFzRzcZPYQSab0+IiT4/M1+IGwPnMMnBuAcQE7rUQScy2/4 pRYR4tP8VDC3XIxrLfZ5oSZB16IPrBvMIKSMb4Rgi9LRamo20UbSfy3WMjYpSL17 01f/ix/lMm3UwNsia4N9p4EVoOcs+f09mw9f1a19oj8x/jVdseMXs+LGJ7i8VTCo tItbnFb2Oy71LwBpTZibQ3YBe3rMcKkLDgvJCroq3D9BQW12yR7m8d2REg== -----END CERTIFICATE----- Certificate: Data: Version: 3 (0x2) Serial Number: 1048595 (0x100013) Signature Algorithm: md5WithRSAEncryption Issuer: O=My Company, OU=My Unit, L=My Town, ST=My State, C=US, CN=SAMPLE Root CA Validity Not Before: Sep 14 19:19:53 2010 GMT Not After : Sep 11 19:19:53 2020 GMT Subject: C=US, ST=My State, O=My Company, OU=My Unit, CN=SAMPLE Signing CA Subject Public Key Info: Public Key Algorithm: rsaEncryption RSA Public Key: (2048 bit) Modulus (2048 bit): 00:ce:79:81:1f:b5:38:62:a9:9a:8b:80:26:ec:66: f2:86:13:32:53:12:16:c7:d5:38:c0:e4:ee:1d:11: bd:fc:8c:5b:f5:59:b5:a4:18:77:04:54:4c:57:8c: 85:0a:9e:02:b2:f6:f4:d5:d5:a6:cc:eb:91:d7:17: ec:75:86:30:c9:60:79:40:69:13:7e:65:d5:e5:f8: 89:7e:de:9b:c9:c1:19:74:ae:d9:d7:e1:86:c0:1e: 2e:be:44:73:45:d9:3c:06:1a:4a:4d:f1:86:79:f8: 68:e6:24:d0:7a:5d:92:8e:76:62:63:9a:bd:b7:43: 52:a9:be:ad:3b:43:92:99:43:18:80:09:e9:9e:65: d4:02:1d:97:c4:e4:6a:d9:9f:23:3d:66:2a:64:0c: ad:41:48:ca:16:bf:82:34:32:ec:c2:09:5a:dd:0c: a7:cc:99:a0:a5:5f:6a:e4:42:01:13:a9:e4:f7:ad: e7:f0:78:51:9f:f0:7e:21:94:ff:0b:12:ce:19:a3: 51:a6:a3:53:3c:65:3f:26:3e:31:b4:f3:98:82:4e: 81:a3:14:aa:a4:63:6a:7e:6c:50:dd:86:74:cb:33: f5:67:c7:29:24:d5:e9:86:9d:82:55:e3:d2:c2:4e: 85:3f:0d:03:fc:5a:15:29:cf:94:d8:e6:59:8c:d4: 13:25 Exponent: 65537 (0x10001) X509v3 extensions: X509v3 Basic Constraints: CA:TRUE X509v3 Subject Key Identifier: FE:24:B7:46:DF:51:66:99:27:F6:68:02:F8:F2:9C:21:13:5E:5D:C1 X509v3 Authority Key Identifier: keyid:97:8E:63:A5:80:72:20:FD:7D:F6:19:FE:9B:C8:9C:EB:E8:C1:36:40 Signature Algorithm: md5WithRSAEncryption 3b:ea:41:d7:02:49:79:41:fb:35:07:85:ae:ae:40:86:63:08: 82:52:4b:03:32:42:21:b9:f9:24:f9:ef:14:9c:e2:0f:99:42: 36:a3:cf:41:79:e7:0b:90:27:4b:85:d7:57:a0:6e:de:05:8c: 6e:27:f5:33:fe:d6:c5:fc:8a:39:2d:b3:4e:41:15:a3:71:1f: a2:75:4f:c7:aa:30:f4:1d:18:4b:44:a3:39:11:d2:05:c7:80: cd:76:59:79:67:25:25:e0:f2:5e:5d:14:3a:ec:71:eb:c8:d3: 12:20:8f:f7:99:96:28:c7:40:7d:0f:66:bb:1f:58:c1:df:7d: d3:31:cb:1e:f5:bc:67:23:f6:74:b7:9c:d9:7f:d9:9a:d2:fe: 71:05:8b:ba:05:51:a5:bc:e3:e8:db:b2:31:72:89:32:80:ff: 61:09:37:7b:57:c8:c6:6a:06:e2:9a:7c:73:22:43:d2:9a:d8: 9e:4b:3c:1c:50:38:40:84:da:b6:0a:a5:93:8c:21:1e:b7:4b: 6b:f7:88:34:c4:16:d5:72:ed:dd:01:5b:b7:a5:9c:a5:46:0c: e9:cd:36:04:30:4f:ab:4b:96:a7:0c:71:8e:89:3c:3e:37:6f: d4:1f:8f:9b:01:16:ca:4e:16:17:93:a4:60:6f:c6:a2:55:a1: f0:4e:1c:e2 -----BEGIN CERTIFICATE----- MIIDojCCAoqgAwIBAgIDEAATMA0GCSqGSIb3DQEBBAUAMHIxEzARBgNVBAoTCk15 IENvbXBhbnkxEDAOBgNVBAsTB015IFVuaXQxEDAOBgNVBAcTB015IFRvd24xETAP BgNVBAgTCE15IFN0YXRlMQswCQYDVQQGEwJVUzEXMBUGA1UEAxMOU0FNUExFIFJv b3QgQ0EwHhcNMTAwOTE0MTkxOTUzWhcNMjAwOTExMTkxOTUzWjBjMQswCQYDVQQG EwJVUzERMA8GA1UECBMITXkgU3RhdGUxEzARBgNVBAoTCk15IENvbXBhbnkxEDAO BgNVBAsTB015IFVuaXQxGjAYBgNVBAMTEVNBTVBMRSBTaWduaW5nIENBMIIBIjAN BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAznmBH7U4Yqmai4Am7GbyhhMyUxIW x9U4wOTuHRG9/Ixb9Vm1pBh3BFRMV4yFCp4Csvb01dWmzOuR1xfsdYYwyWB5QGkT fmXV5fiJft6bycEZdK7Z1+GGwB4uvkRzRdk8BhpKTfGGefho5iTQel2SjnZiY5q9 t0NSqb6tO0OSmUMYgAnpnmXUAh2XxORq2Z8jPWYqZAytQUjKFr+CNDLswgla3Qyn zJmgpV9q5EIBE6nk963n8HhRn/B+IZT/CxLOGaNRpqNTPGU/Jj4xtPOYgk6BoxSq pGNqfmxQ3YZ0yzP1Z8cpJNXphp2CVePSwk6FPw0D/FoVKc+U2OZZjNQTJQIDAQAB o1AwTjAMBgNVHRMEBTADAQH/MB0GA1UdDgQWBBT+JLdG31FmmSf2aAL48pwhE15d wTAfBgNVHSMEGDAWgBSXjmOlgHIg/X32Gf6byJzr6ME2QDANBgkqhkiG9w0BAQQF AAOCAQEAO+pB1wJJeUH7NQeFrq5AhmMIglJLAzJCIbn5JPnvFJziD5lCNqPPQXnn C5AnS4XXV6Bu3gWMbif1M/7WxfyKOS2zTkEVo3EfonVPx6ow9B0YS0SjORHSBceA zXZZeWclJeDyXl0UOuxx68jTEiCP95mWKMdAfQ9mux9Ywd990zHLHvW8ZyP2dLec 2X/ZmtL+cQWLugVRpbzj6NuyMXKJMoD/YQk3e1fIxmoG4pp8cyJD0prYnks8HFA4 QITatgqlk4whHrdLa/eINMQW1XLt3QFbt6WcpUYM6c02BDBPq0uWpwxxjok8Pjdv 1B+PmwEWyk4WF5OkYG/GolWh8E4c4g== -----END CERTIFICATE----- circuits-3.2.3/examples/web/cert.pem000066400000000000000000000042051460335514400174050ustar00rootroot00000000000000-----BEGIN RSA PRIVATE KEY----- MIICWwIBAAKBgQC9B7hV67bzhLA//STrZUIi0iHD4WFtftOhvj+xiHRNnYw0+r+4 WdQv1YiL+ab03pn/J9R1SQuOGwYDVPQvYX+qEFVRUFP9yvXIQl7PG40HQzfs8lJz hnmI+64HJT//oJ9e7PiyDHLfFH1FuCqSy9RlvzOd4hmydX9J3VxFFzrpJQIDAQAB AoGAHhGxT/Gb+6a6xqMFEXDdEV7twhQDBIDtN0hlJ192aLZMDE1q2+9mImnMO7/t v/v88Sqr0DBbZzKDRVppMXRH80ZtnmMu3/3kUCtA3WAbKxyFpIiXGv/NAUHZe5Gz rua+z3lUvmt6CmwMm2ReB70Q61zxr9q4HjrjYI82dtJ4M40CQQDnGujxdBdbPmiR oc8mcShfmPNP7igQrUkf/DpB0GWnLWdA97mmXLw4jHXpHy9gm3wGc+9uOi0Ex8Ml 1t9xAGFjAkEA0WSGwG45d5dmYV8Oa/9UsY5/F6hAlYAAI1TxRsKcl2YTqaQastac glV1GSUrgGw/8UBvdKKS2REF7cpAkiQV1wJAPtQhCiuOgf7YfOcpowDWgg7Z7xwH Bmml3K08xVG7oRSF4rK2ZRUHErSVBbi1r6T1tedk62mjfY41bp8ZBeadkwJAD7AG YHhhmdIf+3+Rpwm0ILFaWD1kyU6TtBHzGagO71DYfEctMOTfSOx6H24nejGiAMMh Fo3vjo+18ADNIaXOdQJAYdm+dUdyVaW2IDUi7ew0shyKTC4OaEZtXIwwINvrqUsX //6z8mw/S5rXGlfKadiz4uwzcXlSvg727O1efpibvA== -----END RSA PRIVATE KEY----- -----BEGIN CERTIFICATE----- MIIDjTCCAvagAwIBAgIJAN0msyL5El/jMA0GCSqGSIb3DQEBBQUAMIGMMQswCQYD VQQGEwJBVTETMBEGA1UECBMKUXVlZW5zbGFuZDERMA8GA1UEBxMIQnJpc2JhbmUx FTATBgNVBAoTDFNob3J0Q2lyY3VpdDEUMBIGA1UEAxMLSmFtZXMgTWlsbHMxKDAm BgkqhkiG9w0BCQEWGWFkbWluQHNob3J0Y2lyY3VpdC5uZXQuYXUwHhcNMTAwMTEz MDczOTAwWhcNMTEwMTEzMDczOTAwWjCBjDELMAkGA1UEBhMCQVUxEzARBgNVBAgT ClF1ZWVuc2xhbmQxETAPBgNVBAcTCEJyaXNiYW5lMRUwEwYDVQQKEwxTaG9ydENp cmN1aXQxFDASBgNVBAMTC0phbWVzIE1pbGxzMSgwJgYJKoZIhvcNAQkBFhlhZG1p bkBzaG9ydGNpcmN1aXQubmV0LmF1MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB gQC9B7hV67bzhLA//STrZUIi0iHD4WFtftOhvj+xiHRNnYw0+r+4WdQv1YiL+ab0 3pn/J9R1SQuOGwYDVPQvYX+qEFVRUFP9yvXIQl7PG40HQzfs8lJzhnmI+64HJT// oJ9e7PiyDHLfFH1FuCqSy9RlvzOd4hmydX9J3VxFFzrpJQIDAQABo4H0MIHxMB0G A1UdDgQWBBSq+tU5ZDymUuMcgZ83gxk1PTN89zCBwQYDVR0jBIG5MIG2gBSq+tU5 ZDymUuMcgZ83gxk1PTN896GBkqSBjzCBjDELMAkGA1UEBhMCQVUxEzARBgNVBAgT ClF1ZWVuc2xhbmQxETAPBgNVBAcTCEJyaXNiYW5lMRUwEwYDVQQKEwxTaG9ydENp cmN1aXQxFDASBgNVBAMTC0phbWVzIE1pbGxzMSgwJgYJKoZIhvcNAQkBFhlhZG1p bkBzaG9ydGNpcmN1aXQubmV0LmF1ggkA3SazIvkSX+MwDAYDVR0TBAUwAwEB/zAN BgkqhkiG9w0BAQUFAAOBgQAokSGDpbFV2osC8nM8K12vheeDBVDHGxOaENXGVIm8 SWPXsaIUsm6JQx0wm/eouWRPbNJkOBwBrNCls1oMmdxdxG8mBh+kAMWUkdVeuT2H lCo9BRJnhUr4L6poJ7ORzL2oUilGZNwONpHGY0cWzFG8/tOoRJsfKZm23bwXbIxv Hw== -----END CERTIFICATE----- circuits-3.2.3/examples/web/compression.py000066400000000000000000000015001460335514400206530ustar00rootroot00000000000000#!/usr/bin/env python # Examples: # curl http://127.0.0.1:8000/ -i -H 'Accept-Encoding: gzip' # curl http://127.0.0.1:8000/ -i -H 'Accept-Encoding: deflate' # curl http://127.0.0.1:8000/ -i -H 'Accept-Encoding: identity' from circuits import Component, Debugger, handler from circuits.web import Server from circuits.web.controllers import BaseController, expose from circuits.web.tools import gzip class Gzip(Component): @handler('response', priority=1.0) def compress_response(self, event, response): event[0] = gzip(response) class Root(BaseController): @expose('index') def index(self): return 'Hello World! This is some test string which is compressed using gzip-4 if you want!' app = Server(('0.0.0.0', 8000)) Root().register(app) Gzip().register(app) Debugger().register(app) app.run() circuits-3.2.3/examples/web/controllers.py000077500000000000000000000010511460335514400206640ustar00rootroot00000000000000#!/usr/bin/env python from circuits import Debugger from circuits.web import Controller, Server, Static class Root(Controller): def index(self): """ Index Request Handler Controller(s) expose implicitly methods as request handlers. Request Handlers can still be customized by using the ``@expose`` decorator. For example exposing as a different path. """ return 'Hello World!' app = Server(('0.0.0.0', 8000)) Debugger().register(app) Static().register(app) Root().register(app) app.run() circuits-3.2.3/examples/web/crud.py000077500000000000000000000016071460335514400172620ustar00rootroot00000000000000#!/usr/bin/env python from circuits.web import Controller, Logger, Server class Root(Controller): def GET(self, *args, **kwargs): """ GET Request Handler The default Dispatcher in circuits.web also looks for GET, POST, PUT, DELETE and HEAD Request handlers on registered Controller(s). These can be used to implement RESTful resources with CRUD operations. """ return f'GET({args!r:s}, {kwargs!r:s})' def POST(self, *args, **kwargs): return f'POST({args!r:s}, {kwargs!r:s})' def PUT(self, *args, **kwargs): return f'PUT({args!r:s}, {kwargs!r:s})' def DELETE(self, *args, **kwargs): return f'DELETE({args!r:s}, {kwargs!r:s})' def HEAD(self, *args, **kwargs): return f'HEAD({args!r:s}, {kwargs!r:s})' app = Server(('0.0.0.0', 8000)) Logger().register(app) Root().register(app) app.run() circuits-3.2.3/examples/web/etc/000077500000000000000000000000001460335514400165175ustar00rootroot00000000000000circuits-3.2.3/examples/web/etc/shadow000066400000000000000000000002031460335514400177220ustar00rootroot00000000000000test:$6$m2CLzkTCVd5gOZCs$.kDzePgsHDSjkq3loTWTN.M71HmY3POdt2NsiEJjeJImsOWvmKgzaral3MdHOXmNSMR47TuO1Uo.TaJDTSjjt0:18928:0:99999:7::: circuits-3.2.3/examples/web/fileupload.py000077500000000000000000000025221460335514400204460ustar00rootroot00000000000000#!/usr/bin/env python """ File Upload A simple example showing how to access an uploaded file. """ from circuits.web import Controller, Server UPLOAD_FORM = """ Upload Form

    Upload Form

    Description:
    """ UPLOADED_FILE = """ Uploaded File

    Uploaded File

    Filename: %s
    Description: %s

    File Contents:

      %s
      
    """ class Root(Controller): def index(self, file=None, desc=''): """ Request Handler If we haven't received an uploaded file yet, repond with the UPLOAD_FORM template. Otherwise respond with the UPLOADED_FILE template. The file is accessed through the ``file`` keyword argument and the description via the ``desc`` keyword argument. These also happen to be the same fields used on the form. """ if file is None: return UPLOAD_FORM return UPLOADED_FILE % (file.filename, desc, file.value) app = Server(('0.0.0.0', 8000)) Root().register(app) app.run() circuits-3.2.3/examples/web/filtering.py000077500000000000000000000023511460335514400203050ustar00rootroot00000000000000#!/usr/bin/env python """ Filtering A simple example showing how to intercept and potentially filter requests. This example demonstrates how you could intercept the response before it goes out changing the response's content into ALL UPPER CASE! """ from circuits import Component, handler from circuits.web import Controller, Server class Upper(Component): channel = 'web' # By default all web related events # go to the "web" channel. @handler('response', priority=1.0) def _on_response(self, response): """ Response Handler Here we set the priority slightly higher than the default response handler in circutis.web (0.0) so that can we do something about the response before it finally goes out to the client. Here's we're simply modifying the response body by changing the content into ALL UPPERCASE! """ response.body = ''.join(response.body).upper() class Root(Controller): def index(self): """ Request Handler Our normal request handler simply returning "Hello World!" as the response. """ return 'Hello World!' app = Server(('0.0.0.0', 8000)) Upper().register(app) Root().register(app) app.run() circuits-3.2.3/examples/web/forms.py000077500000000000000000000031321460335514400174460ustar00rootroot00000000000000#!/usr/bin/env python """ Forms A simple example showing how to deal with data forms. """ from circuits.web import Controller, Server FORM = """ Basic Form Handling

    Basic Form Handling

    Example of using circuits and its Web Components to build a simple web application that handles some basic form data.

    First Name:
    Last Name:
    """ class Root(Controller): def index(self): """ Request Handler Our index request handler which simply returns a response containing the contents of our form to display. """ return FORM def save(self, firstName, lastName): """ Save Request Handler Our /save request handler (which our form above points to). This handler accepts the same arguments as the fields in the form either as positional arguments or keyword arguments. We will use the date to pretend we've saved the data and tell the user what was saved. """ return f'Data Saved. firstName={firstName:s} lastName={lastName:s}' app = Server(('0.0.0.0', 8000)) Root().register(app) app.run() circuits-3.2.3/examples/web/httpauth.py000077500000000000000000000010061460335514400201570ustar00rootroot00000000000000#!/usr/bin/env python from circuits.web import Controller, Server from circuits.web.tools import basic_auth, check_auth class Root(Controller): def index(self): realm = 'Test' users = {'admin': 'admin'} encrypt = str if check_auth(self.request, self.response, realm, users, encrypt): return 'Hello %s' % self.request.login return basic_auth(self.request, self.response, realm, users, encrypt) app = Server(('0.0.0.0', 8000)) Root().register(app) app.run() circuits-3.2.3/examples/web/jsoncontroller.py000077500000000000000000000003661460335514400214030ustar00rootroot00000000000000#!/usr/bin/env python from circuits.web import JSONController, Server class Root(JSONController): def index(self): return {'success': True, 'message': 'Hello World!'} app = Server(('0.0.0.0', 8000)) Root().register(app) app.run() circuits-3.2.3/examples/web/jsonrpc.py000077500000000000000000000006601460335514400200010ustar00rootroot00000000000000#!/usr/bin/env python """ curl -i http://localhost:8000/ -H 'Content-Type: application/json' \ -d '{"id": "test", "method": "test", "params": {"foo": "bar"}}' """ from circuits import Component from circuits.web import JSONRPC, Logger, Server class Test(Component): def foo(self, a, b, c): return a, b, c app = Server(('0.0.0.0', 8000)) Logger().register(app) JSONRPC().register(app) Test().register(app) app.run() circuits-3.2.3/examples/web/jsonserializer.py000077500000000000000000000012071460335514400213640ustar00rootroot00000000000000#!/usr/bin/env python from json import dumps from circuits import Component, handler from circuits.web import Controller, Logger, Server class JSONSerializer(Component): channel = 'web' # 1 higher than the default response handler @handler('response', priority=1.0) def serialize_response_body(self, response): response.headers['Content-Type'] = 'application/json' response.body = dumps(response.body) class Root(Controller): def index(self): return {'message': 'Hello World!'} app = Server(('0.0.0.0', 8000)) JSONSerializer().register(app) Logger().register(app) Root().register(app) app.run() circuits-3.2.3/examples/web/jsontool.py000077500000000000000000000006621460335514400201740ustar00rootroot00000000000000#!/usr/bin/env python # curl -i http://localhost:8000/getrange?limit=8 from json import dumps from circuits.web import Controller, Server def json(f): def wrapper(self, *args, **kwargs): return dumps(f(self, *args, **kwargs)) return wrapper class Root(Controller): @json def getrange(self, limit=4): return list(range(int(limit))) app = Server(('0.0.0.0', 8000)) Root().register(app) app.run() circuits-3.2.3/examples/web/makotemplates.py000077500000000000000000000015311460335514400211670ustar00rootroot00000000000000#!/usr/bin/env python import os import mako from mako.lookup import TemplateLookup from circuits.web import Controller, Server, Static DEFAULTS = {} templates = TemplateLookup( directories=[os.path.join(os.path.dirname(__file__), 'tpl')], module_directory='/tmp', output_encoding='utf-8', ) def render(name, **d): try: d.update(DEFAULTS) tpl = templates.get_template(name) return tpl.render(**d) except Exception: return mako.exceptions.html_error_template().render() class Root(Controller): tpl = 'index.html' def index(self): return render(self.tpl) def submit(self, firstName, lastName): msg = f'Thank you {firstName} {lastName}' return render(self.tpl, message=msg) app = Server(('0.0.0.0', 8000)) Static().register(app) Root().register(app) app.run() circuits-3.2.3/examples/web/server-cert.pem000066400000000000000000000022641460335514400207140ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIDSzCCAjMCAxAAGzANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzERMA8G A1UECBMITXkgU3RhdGUxEzARBgNVBAoTCk15IENvbXBhbnkxEDAOBgNVBAsTB015 IFVuaXQxGjAYBgNVBAMTEVNBTVBMRSBTaWduaW5nIENBMB4XDTEwMDkxNDE5NDgy MVoXDTIwMDkxMTE5NDgyMVowcjELMAkGA1UEBhMCVVMxETAPBgNVBAgTCE15IFN0 YXRlMRAwDgYDVQQHEwdNeSBUb3duMRMwEQYDVQQKEwpNeSBDb21wYW55MRAwDgYD VQQLEwdNeSBVbml0MRcwFQYDVQQDEw5NeVNhbXBsZVNlcnZlcjCCASIwDQYJKoZI hvcNAQEBBQADggEPADCCAQoCggEBAJsQvrygLjZfFwU4Th8bjyGcOLXg0AQaynaa ZTcXojHAhapmt8AxeeoA4a+bN7tD1BUg25gh5iFs7sdJ26E1FeZsOlp5z4xWuSja Jy3PQ9SZd0hB+foZvAhBms8t5+BV6dS8aYXhYl90lQgB1dx/dapdkRbt3k7Iz1zp Q2/Oxs1Y0UMb8LbVHczXRLzcDJwSi0Syc3oc5vUIVsvaYnLW2lqINzi7rsJ0N7Pk rt4oscyebMrBde780U1sAbSf7rj0JSWzhqtfiJ8NQgMxxaJCRvgtEhXORT/wvEIe pu/32YtbqBwl7YO+S/SD5NN3zrr0iwk/GOzK2Ljd/FDNtA+ibAcCAwEAATANBgkq hkiG9w0BAQUFAAOCAQEAKURnSJwOP7DdZZWjCeFY2rURWW3gdP4zopvKRDIbEj0v egjQjfHMSmKloE4ifu9HbTIecZ3ojUxXNHIJoG0eKTFnLN4rdE7TyLMkdkTCe07Y ydh1uv4GadbCH0wf9JAaRD5EDSsYvBxn0x7s6SNk7fDLOdprkcuAetKG7pvKxckZ HRU7hH4L06Lb50yol1AswrDuB5avBosNx/aoZFtOPv7SXK5wlvAU3T8yNLK0o5M3 ViM9nYCo41BP5JcrDyUFYd4ih8opwMgx/1lL+NgZZLvv9gImHodxS3KOjdDBpjwL TLJxThm42HbbSgn3ZoS01+as0+nQZ5L4s0THkOEIXg== -----END CERTIFICATE----- circuits-3.2.3/examples/web/server-key.pem000066400000000000000000000032131460335514400205420ustar00rootroot00000000000000-----BEGIN RSA PRIVATE KEY----- MIIEowIBAAKCAQEAmxC+vKAuNl8XBThOHxuPIZw4teDQBBrKdpplNxeiMcCFqma3 wDF56gDhr5s3u0PUFSDbmCHmIWzux0nboTUV5mw6WnnPjFa5KNonLc9D1Jl3SEH5 +hm8CEGazy3n4FXp1LxpheFiX3SVCAHV3H91ql2RFu3eTsjPXOlDb87GzVjRQxvw ttUdzNdEvNwMnBKLRLJzehzm9QhWy9pictbaWog3OLuuwnQ3s+Su3iixzJ5sysF1 7vzRTWwBtJ/uuPQlJbOGq1+Inw1CAzHFokJG+C0SFc5FP/C8Qh6m7/fZi1uoHCXt g75L9IPk03fOuvSLCT8Y7MrYuN38UM20D6JsBwIDAQABAoIBABUtJj8wSN9YARbP Z6vL4bIfWYdNGltVJU0pLKVnbtkIh7iLqpBusU2JrUiEFApY6v+vqw2No5XxAHLq 3TmYvFLpeNaeR//MYCD6GduhsIu6IZYWnILRPOKLww6EIGR8lyBcUrTb4MlUbH3Z clFYfsMzX/sXpQJxXhA8Mt90B6ZHQm8ky4dNlQnX3oAB04m3fGpmxiEo+3p46cWo Q8hzxX6EfDtifuT9Zo3x+WoHa3momxvi2b0jt2xdwSxNT4/phGw1uBWrcrt9BtS5 l6EGaIFA6PsUrkie9vHelaaKhjF18uwpqjS9Zb7eNHQX9/hGtrNMhtJNBm8GrQex 86V/AlECgYEAyuu+bNifb/xPyYxittV+xqGpyX8QUSu3wo5ULRplvscuygGv29dc N0XnzLQizyz13oHmsP2NEumzXw25unsMJMsa3YFQgXQaJzgHFYiTKIR3DIhIzBL5 CiLwlHPAz8vDSwKi9Mn0zVSySMkLNPhO05MlmRrxZM3nG0rTojG2+jMCgYEAw6Bw t6Mj/gjouQiIyzvvo2cpXvwMDU6JuLjNu5bPVCUOLal++RcFIvGpm9tlqdgBNEjM a9C61wsTCP49crvYM+ep/GrjI5KddQQEoqObvUR5ms4stOtfey3z6B679ygblbsp NCbkQOQCtljmVa1ncqQsUR2JzNDKBv98AUrv2t0CgYBpgkI1Hj1oYOyrg08gecm9 RfmeR28YhX66rn6eJQeaNr7hUhc6W7QbGUH5cgBXcK020Jw+ktdzaghV+DEGAUzD JMgHPGG7rb6bfcpRK/44Jwgvf/05/vN2jcxBpB4w7WXR7sEEPq4GxW8d4Uruc92o rO3zucqh+12bF0ELKIZXeQKBgGGbHIJTkLLASTWBL5ePmRqDb13oDi9Zf1e+RVAS h/Go53Ea/7JSrQppX0HXbtsWXktzAyPMKlz/NoknKQuk89O6A9NglWH7Vjq7PYDU dvExSCdYNXAzfBlerTKkmw5PYawMjRtrSDmkSkInCw22jkXh6gay4T1i81oYgQu4 EwK1AoGBAMqAGXhtLRS8wmgM3riXcoGoq/34SHbnqf7mJ+25cIF5FAeOS7FtLMTA 9Fml25ZkB2vBHBxP6quvEehpPJ64cu5/GXDeatwOS+rhhIA7Q4kpM0uyjLBBlAZv PdtX16f32bb/fj9lLCp3FFBHZ7HMFdX2MjYC8RWaGb0T7PpM+z86 -----END RSA PRIVATE KEY----- circuits-3.2.3/examples/web/sessions.py000077500000000000000000000005641460335514400201740ustar00rootroot00000000000000#!/usr/bin/env python from circuits.web import Controller, Server, Sessions class Root(Controller): def index(self, name='World'): if 'name' in self.session: name = self.session['name'] self.session['name'] = name return 'Hello %s!' % name app = Server(('0.0.0.0', 8000)) Sessions().register(app) Root().register(app) app.run() circuits-3.2.3/examples/web/shadowauth.py000077500000000000000000000036661460335514400205030ustar00rootroot00000000000000#!/usr/bin/env python """ Shadow Auth Demo An example of a Circuits Component that requires users authenticate against /etc/passwd or /etc/shadow before letting them into the web site. curl -i http://test:test@localhost:8000/ curl -i http://root:test@localhost:8000/ """ from crypt import crypt from os import path from re import compile as compile_regex from socket import gethostname from circuits import Component, handler from circuits.web import Controller, Server, _httpauth from circuits.web.errors import httperror, unauthorized def check_auth(user, password): salt_pattern = compile_regex(r'\$.*\$.*\$') passwd = './etc/shadow' if path.exists('./etc/shadow') else '/etc/passwd' with open(passwd) as f: rows = (line.strip().split(':') for line in f) records = [row for row in rows if row[0] == user] if not records: return False hash = records[0][1] salt = salt_pattern.match(hash).group() return crypt(password, salt) == hash class PasswdAuth(Component): channel = 'web' def init(self, realm=None): self.realm = realm or gethostname() @handler('request', priority=1.0) def _on_request(self, event, request, response): if 'authorization' in request.headers: ah = _httpauth.parseAuthorization(request.headers['authorization']) if ah is None: event.stop() return httperror(request, response, 400) username, password = ah['username'], ah['password'] if check_auth(username, password): request.login = username return None response.headers['WWW-Authenticate'] = _httpauth.basicAuth(self.realm) event.stop() return unauthorized(request, response) class Root(Controller): def index(self): return f'Hello, {self.request.login:s}' app = Server(('0.0.0.0', 8000)) PasswdAuth().register(app) Root().register(app) app.run() circuits-3.2.3/examples/web/singleclickandrun.py000077500000000000000000000010141460335514400220140ustar00rootroot00000000000000#!/usr/bin/env python import webbrowser from circuits.web import Controller, Server HTML = """\ An example application

    This is my sample application

    Put the content here...
    Quit """ class Root(Controller): def index(self): return HTML def exit(self): raise SystemExit(0) app = Server(('0.0.0.0', 8000)) Root().register(app) app.start() webbrowser.open('http://127.0.0.1:8000/') app.join() circuits-3.2.3/examples/web/ssl-forward-cert.py000077500000000000000000000007121460335514400215170ustar00rootroot00000000000000#!/usr/bin/env python # stdlib import ssl from circuits import Debugger from circuits.web import Controller, Server class Root(Controller): def GET(self, peer_cert=None): return "Here's your cert %s" % peer_cert app = Server( ('0.0.0.0', 8443), secure=True, certfile='server-cert.pem', keyfile='server-key.pem', ca_certs='ca-chain.pem', cert_reqs=ssl.CERT_OPTIONAL, ) Root().register(app) app += Debugger() app.run() circuits-3.2.3/examples/web/sslserver.py000077500000000000000000000004511460335514400203510ustar00rootroot00000000000000#!/usr/bin/env python from circuits import Debugger from circuits.web import Controller, Server class Root(Controller): def index(self): return 'Hello World!' app = Server(('0.0.0.0', 8443), secure=True, certfile='cert.pem') Debugger().register(app) Root().register(app) app.run() circuits-3.2.3/examples/web/static/000077500000000000000000000000001460335514400172335ustar00rootroot00000000000000circuits-3.2.3/examples/web/static/css/000077500000000000000000000000001460335514400200235ustar00rootroot00000000000000circuits-3.2.3/examples/web/static/css/base.css000066400000000000000000000036011460335514400214470ustar00rootroot00000000000000@import url(/css/ext/ext-all.css); @import url(/css/ext/xtheme-aero.css); /* Main Layout */ * { font-family: "Bitstream Vera Sans", "Verdana", sans-serif; font-size: 10pt; margin: 0; padding: 0; } body { background: none rgb(240, 255, 240); min-width: 600px; } h1 { font-size: 18pt; } h2 { font-size: 16pt; } h3 { font-size: 14pt; } h4 { font-size: 12pt; } h5 { font-size: 8pt; } a:link, a:visited { color: #888888; text-decoration: none; border-bottom: 1px dashed; } a:focus, a:hover, a:active { border-bottom: 1px solid; } #window { position: relative; width: 600px; top: 10px; margin: 0 auto; border: 2px solid rgb(100, 155, 100); background: none rgb(255, 255, 255); } #header, #footer { background: none rgb(34, 170, 34); color: rgb(255, 255, 255); } #header { padding: 20px 5px 5px 5px; } #header h1 { text-align: center; } #footer { padding: 5px 10px; } /* Content */ #main { margin: 0 auto; padding: 20px; height: 300px; border: none; background: none rgb(255, 255, 255); } .main h2 { padding: 6px 0; } .main dt { padding: 4px 0: } .main dd { padding: 0 20px; } /* Menu */ #menu { color: rgb(0, 0, 0); background-color: transparent; font-family: arial, helvetica, sans-serif; white-space: nowrap; list-style-type: none; margin-top: 20px; } #menu ul { background: none; } #menu li { display: inline; border: 1px solid rgb(70, 100, 70); background: none rgb(255, 255, 240); } #menu a { padding: 2px; margin: 0px; color: rgb(0, 0, 0); text-decoration: none; border: none; } #menu a:hover { background: rgb(100, 100, 100); } /* validate icons */ #icons { float: right; margin-top: 5px; } #icons img, a:link, a:active, a:focus, a:visited, a:hover { border: none; } #message { color: green; } circuits-3.2.3/examples/web/static/img/000077500000000000000000000000001460335514400200075ustar00rootroot00000000000000circuits-3.2.3/examples/web/static/img/rss.gif000066400000000000000000000025571460335514400213160ustar00rootroot00000000000000GIF89a$8HWT YZ V_^PX#cjc e`mmw|?(6"#)=#6I HJPYM$X8M R']8g?DINVVPKP WZ$V&^.T%[3W W([&\&]1bag?is6ɄVׇK݈GہLՆUԌZŃIĉfƑrՌbԓiޔgԕtENUSXmch`rS[yajbv~|`jmt}vyw{}ťϦܹۨ߰始ꪆꭑ봋쿨̸ƄȄǍɕțЅіŧĭʹԤ٢ڭױҼ߹ֹ!,$7[xӥM,a {[ְ`|f˖Yx;B^"iAsfM\y0l*L/_ZG I㓧HeP&2RDOSN)h .tDNݽfPpg/^=vVqfݽweIsh]9bO CFblkTQ :u]eNF)Ad> !+C &MhV^ PM9ܤR=~RB<}yUB9~`H2>$A>S H>cJWhpO@+fEx 0@0 `4<0@뀹0@;circuits-3.2.3/examples/web/static/img/valid-xhtml10.png000066400000000000000000000045561460335514400231210ustar00rootroot00000000000000PNG  IHDRX#DPLTE{9ތBBcZZބB{{ޥ{޵RssޭRZs{Rc19Jc猭RZ)J!{{{ZZJs9JZRJsssJk1!c1kkkBs9ƜJccck1c1kcRk99J֜kkƥZε9999R){9{c1s9111J!1{Jk1){)))ZֵBƽZ9))csZΥZkcRcR)cJ)9c1{9Zs1Z{skZB{BJJsBsBBJJ֌kB9skR)!sZ1kB1scRkZRB!ΥRkR)ZcsRcZZZZ9BJZ)ZJRRR!ZJ1kk)BJ{JJJ{ֵccZR֭cBBBޭc91c޵Z{sR1){J9{kR1!{ZJ!֭R)!sZ)ZZB!c֥R{RcckR!sZsZRJ!޽9sJRRJ9kRJJ!R޵cB?tRNSlIt y]Dq>`IDATxڕV_E˻aFڊQY DTrDiPP"|94B":$4G):[F~3w~=fs@Ŵ{"Ք5CYeF$WPVFt'1|NcDc>1Y Õ-{~BI`[%[#I$H%aD|D cO(Loau( )y*aҺ)}*`&lf0wvӿfXmM>Dزn9kZ2ps!acf&Ia:Tqsr=4On XL|Ԥjar|c9G+@yuJ\#1twA#G4H_5A =xb9*t5o{x5I,M*: AN(Y$Im9Hbmi feNBK~nfk~[pcD16)@hi)M?R&ȅHY)m4ZdTt#4~"T%.eVv%_ȥXd^.ЪhlzfRJdds*tYF\8LhX:֝U" _7e[ʆigJ;ܽqg7:AQOzr`7PaaaL፞g4@> n;4h[l``'N !Ҩ ύWO*oхG`f3 ;y_+'H6ssGAoѪh9A q>g1999 ÜN^f3a̙3JZޱX{sf( ffffffffffffffffffffffffffffffffffffffffffffff! Valid CSS!!,X$$%GF+%ĭʓ͟Ǵ՛А ب  <!xEI-&` PP၅&@B`M C H`ҁ 8DTIJ.L@${$po҃ #i9tp*|C1$(0)0pXd`A@:6@H.RIH7i,lxM7kGAZu;p8u )12701<c 6J>jXN逺+Q` 9Ǎ8t/ؿ@"x%K,e9.U"WR8%`5aA=&9ʀ%&@FlYFpw@dWwbenH`Z % pɋ"\"IY `m_W9 B(ESIq Ί9 T #@H'K s7M"C @[E(?!l⒰G, Wlg0Q! ,$     L~@,ny;Aj{r$DAgi[7Bs%e bi^;'Z*P?D$w+T#aDM r<7#P/D6Q<BD $oL{0 +XϠC;circuits-3.2.3/examples/web/terminal/000077500000000000000000000000001460335514400175575ustar00rootroot00000000000000circuits-3.2.3/examples/web/terminal/static/000077500000000000000000000000001460335514400210465ustar00rootroot00000000000000circuits-3.2.3/examples/web/terminal/static/css/000077500000000000000000000000001460335514400216365ustar00rootroot00000000000000circuits-3.2.3/examples/web/terminal/static/css/base.css000066400000000000000000000000621460335514400232600ustar00rootroot00000000000000body { margin: 0; white-space: pre; } circuits-3.2.3/examples/web/terminal/static/favicon.ico000066400000000000000000000015311460335514400231670ustar00rootroot00000000000000PNG  IHDRasBIT|d pHYs|4ktEXtSoftwarewww.inkscape.org<IDAT8mKh\ey%IIH"cbm}% ,\Z Rp q+*A7n*Tmт| kijI33yL^t㢴L?8s~|ߙo/{8$b0zOU7s/-n!"$"(v5k?@wk#DQL'IgRi'e#0Wtm5v7Na $182ġ0X~xw4ͤU#ɣ0re繱pvz{sF $ILl '1b۵<ťofG׸% ݙcά(^?q^ n!M;YZiڤo_A':|?sj߿漏=S,mFkmY(P Z Terminal
    circuits-3.2.3/examples/web/terminal/static/js/000077500000000000000000000000001460335514400214625ustar00rootroot00000000000000circuits-3.2.3/examples/web/terminal/static/js/jquery.js000066400000000000000000003533741460335514400233560ustar00rootroot00000000000000/*! * jQuery JavaScript Library v1.3.2 * http://jquery.com/ * * Copyright (c) 2009 John Resig * Dual licensed under the MIT and GPL licenses. * http://docs.jquery.com/License * * Date: 2009-02-19 17:34:21 -0500 (Thu, 19 Feb 2009) * Revision: 6246 */ (function(){ var // Will speed up references to window, and allows munging its name. window = this, // Will speed up references to undefined, and allows munging its name. undefined, // Map over jQuery in case of overwrite _jQuery = window.jQuery, // Map over the $ in case of overwrite _$ = window.$, jQuery = window.jQuery = window.$ = function( selector, context ) { // The jQuery object is actually just the init constructor 'enhanced' return new jQuery.fn.init( selector, context ); }, // A simple way to check for HTML strings or ID strings // (both of which we optimize for) quickExpr = /^[^<]*(<(.|\s)+>)[^>]*$|^#([\w-]+)$/, // Is it a simple selector isSimple = /^.[^:#\[\.,]*$/; jQuery.fn = jQuery.prototype = { init: function( selector, context ) { // Make sure that a selection was provided selector = selector || document; // Handle $(DOMElement) if ( selector.nodeType ) { this[0] = selector; this.length = 1; this.context = selector; return this; } // Handle HTML strings if ( typeof selector === "string" ) { // Are we dealing with HTML string or an ID? var match = quickExpr.exec( selector ); // Verify a match, and that no context was specified for #id if ( match && (match[1] || !context) ) { // HANDLE: $(html) -> $(array) if ( match[1] ) selector = jQuery.clean( [ match[1] ], context ); // HANDLE: $("#id") else { var elem = document.getElementById( match[3] ); // Handle the case where IE and Opera return items // by name instead of ID if ( elem && elem.id != match[3] ) return jQuery().find( selector ); // Otherwise, we inject the element directly into the jQuery object var ret = jQuery( elem || [] ); ret.context = document; ret.selector = selector; return ret; } // HANDLE: $(expr, [context]) // (which is just equivalent to: $(content).find(expr) } else return jQuery( context ).find( selector ); // HANDLE: $(function) // Shortcut for document ready } else if ( jQuery.isFunction( selector ) ) return jQuery( document ).ready( selector ); // Make sure that old selector state is passed along if ( selector.selector && selector.context ) { this.selector = selector.selector; this.context = selector.context; } return this.setArray(jQuery.isArray( selector ) ? selector : jQuery.makeArray(selector)); }, // Start with an empty selector selector: "", // The current version of jQuery being used jquery: "1.3.2", // The number of elements contained in the matched element set size: function() { return this.length; }, // Get the Nth element in the matched element set OR // Get the whole matched element set as a clean array get: function( num ) { return num === undefined ? // Return a 'clean' array Array.prototype.slice.call( this ) : // Return just the object this[ num ]; }, // Take an array of elements and push it onto the stack // (returning the new matched element set) pushStack: function( elems, name, selector ) { // Build a new jQuery matched element set var ret = jQuery( elems ); // Add the old object onto the stack (as a reference) ret.prevObject = this; ret.context = this.context; if ( name === "find" ) ret.selector = this.selector + (this.selector ? " " : "") + selector; else if ( name ) ret.selector = this.selector + "." + name + "(" + selector + ")"; // Return the newly-formed element set return ret; }, // Force the current matched set of elements to become // the specified array of elements (destroying the stack in the process) // You should use pushStack() in order to do this, but maintain the stack setArray: function( elems ) { // Resetting the length to 0, then using the native Array push // is a super-fast way to populate an object with array-like properties this.length = 0; Array.prototype.push.apply( this, elems ); return this; }, // Execute a callback for every element in the matched set. // (You can seed the arguments with an array of args, but this is // only used internally.) each: function( callback, args ) { return jQuery.each( this, callback, args ); }, // Determine the position of an element within // the matched set of elements index: function( elem ) { // Locate the position of the desired element return jQuery.inArray( // If it receives a jQuery object, the first element is used elem && elem.jquery ? elem[0] : elem , this ); }, attr: function( name, value, type ) { var options = name; // Look for the case where we're accessing a style value if ( typeof name === "string" ) if ( value === undefined ) return this[0] && jQuery[ type || "attr" ]( this[0], name ); else { options = {}; options[ name ] = value; } // Check to see if we're setting style values return this.each(function(i){ // Set all the styles for ( name in options ) jQuery.attr( type ? this.style : this, name, jQuery.prop( this, options[ name ], type, i, name ) ); }); }, css: function( key, value ) { // ignore negative width and height values if ( (key == 'width' || key == 'height') && parseFloat(value) < 0 ) value = undefined; return this.attr( key, value, "curCSS" ); }, text: function( text ) { if ( typeof text !== "object" && text != null ) return this.empty().append( (this[0] && this[0].ownerDocument || document).createTextNode( text ) ); var ret = ""; jQuery.each( text || this, function(){ jQuery.each( this.childNodes, function(){ if ( this.nodeType != 8 ) ret += this.nodeType != 1 ? this.nodeValue : jQuery.fn.text( [ this ] ); }); }); return ret; }, wrapAll: function( html ) { if ( this[0] ) { // The elements to wrap the target around var wrap = jQuery( html, this[0].ownerDocument ).clone(); if ( this[0].parentNode ) wrap.insertBefore( this[0] ); wrap.map(function(){ var elem = this; while ( elem.firstChild ) elem = elem.firstChild; return elem; }).append(this); } return this; }, wrapInner: function( html ) { return this.each(function(){ jQuery( this ).contents().wrapAll( html ); }); }, wrap: function( html ) { return this.each(function(){ jQuery( this ).wrapAll( html ); }); }, append: function() { return this.domManip(arguments, true, function(elem){ if (this.nodeType == 1) this.appendChild( elem ); }); }, prepend: function() { return this.domManip(arguments, true, function(elem){ if (this.nodeType == 1) this.insertBefore( elem, this.firstChild ); }); }, before: function() { return this.domManip(arguments, false, function(elem){ this.parentNode.insertBefore( elem, this ); }); }, after: function() { return this.domManip(arguments, false, function(elem){ this.parentNode.insertBefore( elem, this.nextSibling ); }); }, end: function() { return this.prevObject || jQuery( [] ); }, // For internal use only. // Behaves like an Array's method, not like a jQuery method. push: [].push, sort: [].sort, splice: [].splice, find: function( selector ) { if ( this.length === 1 ) { var ret = this.pushStack( [], "find", selector ); ret.length = 0; jQuery.find( selector, this[0], ret ); return ret; } else { return this.pushStack( jQuery.unique(jQuery.map(this, function(elem){ return jQuery.find( selector, elem ); })), "find", selector ); } }, clone: function( events ) { // Do the clone var ret = this.map(function(){ if ( !jQuery.support.noCloneEvent && !jQuery.isXMLDoc(this) ) { // IE copies events bound via attachEvent when // using cloneNode. Calling detachEvent on the // clone will also remove the events from the orignal // In order to get around this, we use innerHTML. // Unfortunately, this means some modifications to // attributes in IE that are actually only stored // as properties will not be copied (such as the // the name attribute on an input). var html = this.outerHTML; if ( !html ) { var div = this.ownerDocument.createElement("div"); div.appendChild( this.cloneNode(true) ); html = div.innerHTML; } return jQuery.clean([html.replace(/ jQuery\d+="(?:\d+|null)"/g, "").replace(/^\s*/, "")])[0]; } else return this.cloneNode(true); }); // Copy the events from the original to the clone if ( events === true ) { var orig = this.find("*").andSelf(), i = 0; ret.find("*").andSelf().each(function(){ if ( this.nodeName !== orig[i].nodeName ) return; var events = jQuery.data( orig[i], "events" ); for ( var type in events ) { for ( var handler in events[ type ] ) { jQuery.event.add( this, type, events[ type ][ handler ], events[ type ][ handler ].data ); } } i++; }); } // Return the cloned set return ret; }, filter: function( selector ) { return this.pushStack( jQuery.isFunction( selector ) && jQuery.grep(this, function(elem, i){ return selector.call( elem, i ); }) || jQuery.multiFilter( selector, jQuery.grep(this, function(elem){ return elem.nodeType === 1; }) ), "filter", selector ); }, closest: function( selector ) { var pos = jQuery.expr.match.POS.test( selector ) ? jQuery(selector) : null, closer = 0; return this.map(function(){ var cur = this; while ( cur && cur.ownerDocument ) { if ( pos ? pos.index(cur) > -1 : jQuery(cur).is(selector) ) { jQuery.data(cur, "closest", closer); return cur; } cur = cur.parentNode; closer++; } }); }, not: function( selector ) { if ( typeof selector === "string" ) // test special case where just one selector is passed in if ( isSimple.test( selector ) ) return this.pushStack( jQuery.multiFilter( selector, this, true ), "not", selector ); else selector = jQuery.multiFilter( selector, this ); var isArrayLike = selector.length && selector[selector.length - 1] !== undefined && !selector.nodeType; return this.filter(function() { return isArrayLike ? jQuery.inArray( this, selector ) < 0 : this != selector; }); }, add: function( selector ) { return this.pushStack( jQuery.unique( jQuery.merge( this.get(), typeof selector === "string" ? jQuery( selector ) : jQuery.makeArray( selector ) ))); }, is: function( selector ) { return !!selector && jQuery.multiFilter( selector, this ).length > 0; }, hasClass: function( selector ) { return !!selector && this.is( "." + selector ); }, val: function( value ) { if ( value === undefined ) { var elem = this[0]; if ( elem ) { if( jQuery.nodeName( elem, 'option' ) ) return (elem.attributes.value || {}).specified ? elem.value : elem.text; // We need to handle select boxes special if ( jQuery.nodeName( elem, "select" ) ) { var index = elem.selectedIndex, values = [], options = elem.options, one = elem.type == "select-one"; // Nothing was selected if ( index < 0 ) return null; // Loop through all the selected options for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) { var option = options[ i ]; if ( option.selected ) { // Get the specifc value for the option value = jQuery(option).val(); // We don't need an array for one selects if ( one ) return value; // Multi-Selects return an array values.push( value ); } } return values; } // Everything else, we just grab the value return (elem.value || "").replace(/\r/g, ""); } return undefined; } if ( typeof value === "number" ) value += ''; return this.each(function(){ if ( this.nodeType != 1 ) return; if ( jQuery.isArray(value) && /radio|checkbox/.test( this.type ) ) this.checked = (jQuery.inArray(this.value, value) >= 0 || jQuery.inArray(this.name, value) >= 0); else if ( jQuery.nodeName( this, "select" ) ) { var values = jQuery.makeArray(value); jQuery( "option", this ).each(function(){ this.selected = (jQuery.inArray( this.value, values ) >= 0 || jQuery.inArray( this.text, values ) >= 0); }); if ( !values.length ) this.selectedIndex = -1; } else this.value = value; }); }, html: function( value ) { return value === undefined ? (this[0] ? this[0].innerHTML.replace(/ jQuery\d+="(?:\d+|null)"/g, "") : null) : this.empty().append( value ); }, replaceWith: function( value ) { return this.after( value ).remove(); }, eq: function( i ) { return this.slice( i, +i + 1 ); }, slice: function() { return this.pushStack( Array.prototype.slice.apply( this, arguments ), "slice", Array.prototype.slice.call(arguments).join(",") ); }, map: function( callback ) { return this.pushStack( jQuery.map(this, function(elem, i){ return callback.call( elem, i, elem ); })); }, andSelf: function() { return this.add( this.prevObject ); }, domManip: function( args, table, callback ) { if ( this[0] ) { var fragment = (this[0].ownerDocument || this[0]).createDocumentFragment(), scripts = jQuery.clean( args, (this[0].ownerDocument || this[0]), fragment ), first = fragment.firstChild; if ( first ) for ( var i = 0, l = this.length; i < l; i++ ) callback.call( root(this[i], first), this.length > 1 || i > 0 ? fragment.cloneNode(true) : fragment ); if ( scripts ) jQuery.each( scripts, evalScript ); } return this; function root( elem, cur ) { return table && jQuery.nodeName(elem, "table") && jQuery.nodeName(cur, "tr") ? (elem.getElementsByTagName("tbody")[0] || elem.appendChild(elem.ownerDocument.createElement("tbody"))) : elem; } } }; // Give the init function the jQuery prototype for later instantiation jQuery.fn.init.prototype = jQuery.fn; function evalScript( i, elem ) { if ( elem.src ) jQuery.ajax({ url: elem.src, async: false, dataType: "script" }); else jQuery.globalEval( elem.text || elem.textContent || elem.innerHTML || "" ); if ( elem.parentNode ) elem.parentNode.removeChild( elem ); } function now(){ return +new Date; } jQuery.extend = jQuery.fn.extend = function() { // copy reference to target object var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, options; // Handle a deep copy situation if ( typeof target === "boolean" ) { deep = target; target = arguments[1] || {}; // skip the boolean and the target i = 2; } // Handle case when target is a string or something (possible in deep copy) if ( typeof target !== "object" && !jQuery.isFunction(target) ) target = {}; // extend jQuery itself if only one argument is passed if ( length == i ) { target = this; --i; } for ( ; i < length; i++ ) // Only deal with non-null/undefined values if ( (options = arguments[ i ]) != null ) // Extend the base object for ( var name in options ) { var src = target[ name ], copy = options[ name ]; // Prevent never-ending loop if ( target === copy ) continue; // Recurse if we're merging object values if ( deep && copy && typeof copy === "object" && !copy.nodeType ) target[ name ] = jQuery.extend( deep, // Never move original objects, clone them src || ( copy.length != null ? [ ] : { } ) , copy ); // Don't bring in undefined values else if ( copy !== undefined ) target[ name ] = copy; } // Return the modified object return target; }; // exclude the following css properties to add px var exclude = /z-?index|font-?weight|opacity|zoom|line-?height/i, // cache defaultView defaultView = document.defaultView || {}, toString = Object.prototype.toString; jQuery.extend({ noConflict: function( deep ) { window.$ = _$; if ( deep ) window.jQuery = _jQuery; return jQuery; }, // See test/unit/core.js for details concerning isFunction. // Since version 1.3, DOM methods and functions like alert // aren't supported. They return false on IE (#2968). isFunction: function( obj ) { return toString.call(obj) === "[object Function]"; }, isArray: function( obj ) { return toString.call(obj) === "[object Array]"; }, // check if an element is in a (or is an) XML document isXMLDoc: function( elem ) { return elem.nodeType === 9 && elem.documentElement.nodeName !== "HTML" || !!elem.ownerDocument && jQuery.isXMLDoc( elem.ownerDocument ); }, // Evalulates a script in a global context globalEval: function( data ) { if ( data && /\S/.test(data) ) { // Inspired by code by Andrea Giammarchi // http://webreflection.blogspot.com/2007/08/global-scope-evaluation-and-dom.html var head = document.getElementsByTagName("head")[0] || document.documentElement, script = document.createElement("script"); script.type = "text/javascript"; if ( jQuery.support.scriptEval ) script.appendChild( document.createTextNode( data ) ); else script.text = data; // Use insertBefore instead of appendChild to circumvent an IE6 bug. // This arises when a base node is used (#2709). head.insertBefore( script, head.firstChild ); head.removeChild( script ); } }, nodeName: function( elem, name ) { return elem.nodeName && elem.nodeName.toUpperCase() == name.toUpperCase(); }, // args is for internal usage only each: function( object, callback, args ) { var name, i = 0, length = object.length; if ( args ) { if ( length === undefined ) { for ( name in object ) if ( callback.apply( object[ name ], args ) === false ) break; } else for ( ; i < length; ) if ( callback.apply( object[ i++ ], args ) === false ) break; // A special, fast, case for the most common use of each } else { if ( length === undefined ) { for ( name in object ) if ( callback.call( object[ name ], name, object[ name ] ) === false ) break; } else for ( var value = object[0]; i < length && callback.call( value, i, value ) !== false; value = object[++i] ){} } return object; }, prop: function( elem, value, type, i, name ) { // Handle executable functions if ( jQuery.isFunction( value ) ) value = value.call( elem, i ); // Handle passing in a number to a CSS property return typeof value === "number" && type == "curCSS" && !exclude.test( name ) ? value + "px" : value; }, className: { // internal only, use addClass("class") add: function( elem, classNames ) { jQuery.each((classNames || "").split(/\s+/), function(i, className){ if ( elem.nodeType == 1 && !jQuery.className.has( elem.className, className ) ) elem.className += (elem.className ? " " : "") + className; }); }, // internal only, use removeClass("class") remove: function( elem, classNames ) { if (elem.nodeType == 1) elem.className = classNames !== undefined ? jQuery.grep(elem.className.split(/\s+/), function(className){ return !jQuery.className.has( classNames, className ); }).join(" ") : ""; }, // internal only, use hasClass("class") has: function( elem, className ) { return elem && jQuery.inArray( className, (elem.className || elem).toString().split(/\s+/) ) > -1; } }, // A method for quickly swapping in/out CSS properties to get correct calculations swap: function( elem, options, callback ) { var old = {}; // Remember the old values, and insert the new ones for ( var name in options ) { old[ name ] = elem.style[ name ]; elem.style[ name ] = options[ name ]; } callback.call( elem ); // Revert the old values for ( var name in options ) elem.style[ name ] = old[ name ]; }, css: function( elem, name, force, extra ) { if ( name == "width" || name == "height" ) { var val, props = { position: "absolute", visibility: "hidden", display:"block" }, which = name == "width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ]; function getWH() { val = name == "width" ? elem.offsetWidth : elem.offsetHeight; if ( extra === "border" ) return; jQuery.each( which, function() { if ( !extra ) val -= parseFloat(jQuery.curCSS( elem, "padding" + this, true)) || 0; if ( extra === "margin" ) val += parseFloat(jQuery.curCSS( elem, "margin" + this, true)) || 0; else val -= parseFloat(jQuery.curCSS( elem, "border" + this + "Width", true)) || 0; }); } if ( elem.offsetWidth !== 0 ) getWH(); else jQuery.swap( elem, props, getWH ); return Math.max(0, Math.round(val)); } return jQuery.curCSS( elem, name, force ); }, curCSS: function( elem, name, force ) { var ret, style = elem.style; // We need to handle opacity special in IE if ( name == "opacity" && !jQuery.support.opacity ) { ret = jQuery.attr( style, "opacity" ); return ret == "" ? "1" : ret; } // Make sure we're using the right name for getting the float value if ( name.match( /float/i ) ) name = styleFloat; if ( !force && style && style[ name ] ) ret = style[ name ]; else if ( defaultView.getComputedStyle ) { // Only "float" is needed here if ( name.match( /float/i ) ) name = "float"; name = name.replace( /([A-Z])/g, "-$1" ).toLowerCase(); var computedStyle = defaultView.getComputedStyle( elem, null ); if ( computedStyle ) ret = computedStyle.getPropertyValue( name ); // We should always get a number back from opacity if ( name == "opacity" && ret == "" ) ret = "1"; } else if ( elem.currentStyle ) { var camelCase = name.replace(/\-(\w)/g, function(all, letter){ return letter.toUpperCase(); }); ret = elem.currentStyle[ name ] || elem.currentStyle[ camelCase ]; // From the awesome hack by Dean Edwards // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291 // If we're not dealing with a regular pixel number // but a number that has a weird ending, we need to convert it to pixels if ( !/^\d+(px)?$/i.test( ret ) && /^\d/.test( ret ) ) { // Remember the original values var left = style.left, rsLeft = elem.runtimeStyle.left; // Put in the new values to get a computed value out elem.runtimeStyle.left = elem.currentStyle.left; style.left = ret || 0; ret = style.pixelLeft + "px"; // Revert the changed values style.left = left; elem.runtimeStyle.left = rsLeft; } } return ret; }, clean: function( elems, context, fragment ) { context = context || document; // !context.createElement fails in IE with an error but returns typeof 'object' if ( typeof context.createElement === "undefined" ) context = context.ownerDocument || context[0] && context[0].ownerDocument || document; // If a single string is passed in and it's a single tag // just do a createElement and skip the rest if ( !fragment && elems.length === 1 && typeof elems[0] === "string" ) { var match = /^<(\w+)\s*\/?>$/.exec(elems[0]); if ( match ) return [ context.createElement( match[1] ) ]; } var ret = [], scripts = [], div = context.createElement("div"); jQuery.each(elems, function(i, elem){ if ( typeof elem === "number" ) elem += ''; if ( !elem ) return; // Convert html string into DOM nodes if ( typeof elem === "string" ) { // Fix "XHTML"-style tags in all browsers elem = elem.replace(/(<(\w+)[^>]*?)\/>/g, function(all, front, tag){ return tag.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i) ? all : front + ">"; }); // Trim whitespace, otherwise indexOf won't work as expected var tags = elem.replace(/^\s+/, "").substring(0, 10).toLowerCase(); var wrap = // option or optgroup !tags.indexOf("", "" ] || !tags.indexOf("", "" ] || tags.match(/^<(thead|tbody|tfoot|colg|cap)/) && [ 1, "", "
    " ] || !tags.indexOf("", "" ] || // matched above (!tags.indexOf("", "" ] || !tags.indexOf("", "" ] || // IE can't serialize and circuits-3.2.3/examples/web/websockets.py000077500000000000000000000012621460335514400204730ustar00rootroot00000000000000#!/usr/bin/env python # firefox http://127.0.0.1:8000/websocket.html from circuits import Component, Debugger from circuits.net.events import write from circuits.web import Controller, Logger, Server, Static from circuits.web.dispatchers import WebSocketsDispatcher class Echo(Component): channel = 'wsserver' def read(self, sock, data): self.fireEvent(write(sock, 'Received: ' + data)) class Root(Controller): def index(self): return 'Hello World!' app = Server(('0.0.0.0', 8000)) Debugger().register(app) Static().register(app) Echo().register(app) Root().register(app) Logger().register(app) WebSocketsDispatcher('/websocket').register(app) app.run() circuits-3.2.3/examples/web/wiki/000077500000000000000000000000001460335514400167075ustar00rootroot00000000000000circuits-3.2.3/examples/web/wiki/.gitignore000066400000000000000000000000101460335514400206660ustar00rootroot00000000000000wiki.db circuits-3.2.3/examples/web/wiki/Dockerfile000066400000000000000000000001071460335514400206770ustar00rootroot00000000000000FROM crux/python:onbuild EXPOSE 8000 ENTRYPOINT ["./wiki.py"] CMD [] circuits-3.2.3/examples/web/wiki/defaultpages/000077500000000000000000000000001460335514400213535ustar00rootroot00000000000000circuits-3.2.3/examples/web/wiki/defaultpages/BulletList000066400000000000000000000000501460335514400233540ustar00rootroot00000000000000* Bullet list * Second item ** Sub item circuits-3.2.3/examples/web/wiki/defaultpages/CheatSheet000066400000000000000000000044741460335514400233240ustar00rootroot00000000000000= Cheat Sheet = |= Wiki |= Rendered |= Source |##{{{//italics//}}}## |//italics// |<>//italics//<> |##{{{**bold**}}}## |**bold** |<>**bold**<> |##{{{^^super^^script}}}## |^^super^^script |<>^^super^^script<> |##{{{,,sub,,script}}}## |,,sub,,script |<>,,sub,,script<> |##{{{##monospace##}}}## |##monospace## |<>##monospace##<> |<> |<> |<> |<> |<> |<> |<> |<> |<> |<> |<> |<> |##Link to ~[[WikiPage]]## |Link to [[WikiPage]] |<
    >

    Link to WikiPage

    <
    > |##{{{[[http://google.com|Google]]}}}## |[[http://google.com|Google]]|<>[[http://google.com|Google]]<> |<> |<> |<> |<> |<> |<> |##Force~\\linebreak## |Force\\linebreak |<>Force\\linebreak<> |<> |<> |<> |##{{{{{Image.jpg|Cod}}}}}## |{{Image.jpg|Cod}} |<>{{Image.jpg|Cod}}<> |<> |<> |<> |<> |<> |<> |<> |<> |<> |{{{use a tilde to ~**escape}}} |use a tilde to ~**escape |<>use a tilde to ~**escape<> |##{{{<>a //simple// macro<>}}}##|<>a //simple// macro<>|<
    >

    a simple macro

    <
    > circuits-3.2.3/examples/web/wiki/defaultpages/DefinitionList000066400000000000000000000000371460335514400242220ustar00rootroot00000000000000; Definition List : Definition circuits-3.2.3/examples/web/wiki/defaultpages/FrontPage000066400000000000000000000027161460335514400231710ustar00rootroot00000000000000= Front Page = Welcome to the simple [[http://circuitsweb.com/|circuits.web]] wiki engine in < 100 lines of code!!!! This little demo wiki makes use of the following software/library: * [[http://www.python.org|Python]] D'uh :) * [[http://circuitsweb.com/|circuits.web]] -- web application framework * [[http://code.google.com/p/creoleparser/|creoleparser]] -- wiki parser * [[http://sqlite.org|SQLite]] -- wiki database * [[http://genshi.edgewall.org/|genshi]] -- used by creoleparser and macros * [[http://pygments.org/|pygments]] -- used by code macro for highlighting This is the simplest, most concise demonstration of quite a number of the features of the [[http://circuitsweb.com/|circuits.web]] web framework as well as the smallest (//without resorting to obfuscation//). This wiki engine also fully supports macros and a list of available macros can be found here: [[Macros]] For help with wiki syntax see the [[CheatSheet]] page. THe entire source code to this wiki can be found on the [[http://circuitsframework.com/|circuits]] project home page here: https://github.com/circuits/circuits/tree/master/examples/web/wiki/ Try the [[SandBox]] :) **NB**: This circuits.web wiki demo is not a complete wiki engine and lacks some rather basic wiki features such as history and recent changes. For a more complete wiki engine please see the [[http://sahriswiki.org/|sahriswiki]] project. Enjoy! --[[http://prologic.shortcircuit.net.au|JamesMills]] circuits-3.2.3/examples/web/wiki/defaultpages/HeadingsPage000066400000000000000000000000671460335514400236200ustar00rootroot00000000000000== Large heading === Medium Heading ==== Small heading circuits-3.2.3/examples/web/wiki/defaultpages/HorizontalLine000066400000000000000000000000261460335514400242350ustar00rootroot00000000000000Horizontal line: ---- circuits-3.2.3/examples/web/wiki/defaultpages/Indented000066400000000000000000000000651460335514400230310ustar00rootroot00000000000000> Indented regions\\ > can include >> block elements circuits-3.2.3/examples/web/wiki/defaultpages/Macros000066400000000000000000000000271460335514400225210ustar00rootroot00000000000000= Macros = <> circuits-3.2.3/examples/web/wiki/defaultpages/MixedList000066400000000000000000000000601460335514400231740ustar00rootroot00000000000000# Mixed list ** Sub bullet item # Numbered item circuits-3.2.3/examples/web/wiki/defaultpages/NoLineBreak000066400000000000000000000000351460335514400234250ustar00rootroot00000000000000No linebreak! Use empty row circuits-3.2.3/examples/web/wiki/defaultpages/NumberedList000066400000000000000000000000521460335514400236700ustar00rootroot00000000000000# Numbered list # Second item ## Sub item circuits-3.2.3/examples/web/wiki/defaultpages/RenderedPre000066400000000000000000000000521460335514400234720ustar00rootroot00000000000000{{{ == [[Nowiki]]: //**don't format// }}} circuits-3.2.3/examples/web/wiki/defaultpages/RenderedTable000066400000000000000000000000571460335514400240000ustar00rootroot00000000000000|=|=table|=header| |a|table|row| |b|table|row| circuits-3.2.3/examples/web/wiki/defaultpages/SandBox000066400000000000000000000001621460335514400226330ustar00rootroot00000000000000= The Sand Box = This is just a page to practice and learn [[WikiFormatting]]. Go ahead, edit it freely. ---- circuits-3.2.3/examples/web/wiki/defaultpages/SiteMenu000066400000000000000000000000641460335514400230270ustar00rootroot00000000000000* [[FrontPage|Home]] * [[CheatSheet]] * [[SandBox]] circuits-3.2.3/examples/web/wiki/macros/000077500000000000000000000000001460335514400201735ustar00rootroot00000000000000circuits-3.2.3/examples/web/wiki/macros/__init__.py000066400000000000000000000027421460335514400223110ustar00rootroot00000000000000""" Macro Macro support and dispatcher """ import os from inspect import getmembers, getmodule, isfunction from creoleparser import parse_args class Macro: def __init__(self, name, arg_string, body, isblock): super().__init__() self.name = name self.arg_string = arg_string self.body = body self.isblock = isblock def dispatcher(name, arg_string, body, isblock, environ): if name in environ['macros']: macro = Macro(name, arg_string, body, isblock) args, kwargs = parse_args(arg_string) try: return environ['macros'][name](macro, environ, *args, **kwargs) except Exception as e: return f'ERROR: Error while executing macro {name!r} ({e})' else: return 'Macro not found!' def loadMacros(): path = os.path.abspath(os.path.dirname(__file__)) def p(x): return os.path.splitext(x)[1] == '.py' modules = [x for x in os.listdir(path) if p(x) and x != '__init__.py'] macros = {} for module in modules: name, _ = os.path.splitext(module) moduleName = f'{__package__}.{name}' m = __import__(moduleName, globals(), locals(), __package__) def p(x): return isfunction(x) and getmodule(x) is m for name, function in getmembers(m, p): name = name.replace('_', '-') try: macros[name] = function except Exception: continue return macros circuits-3.2.3/examples/web/wiki/macros/html.py000066400000000000000000000052171460335514400215160ustar00rootroot00000000000000""" HTML macros Macros for generating snippets of HTML. """ import genshi import pygments import pygments.formatters import pygments.lexers import pygments.util from genshi import builder from genshi.filters import HTMLSanitizer sanitizer = HTMLSanitizer() def pre(macro, environ, *args, **kwargs): """ Return the raw text of body, rendered in a
     block.
    
        **Arguments:** //None//
    
        **Example:**
        {{{
        <
    >
        def hello():
            print "Hello World!"
    
        hello()
        <
    > }}} """ if macro.body is None: return None return builder.tag.pre(macro.body) def code(macro, environ, *args, **kwargs): """Render syntax highlighted code""" if not macro.body: return None lang = kwargs.get('lang', None) if lang is not None: if not macro.isblock: return None try: lexer = pygments.lexers.get_lexer_by_name(lang, stripall=True) except pygments.util.ClassNotFound: return None else: lexer = None if lexer: text = pygments.highlight( macro.body, lexer, pygments.formatters.HtmlFormatter(), ) output = genshi.core.Markup(text) elif macro.isblock: output = genshi.builder.tag.pre(macro.body) else: output = genshi.builder.tag.code( macro.body, style='white-space:pre-wrap', class_='highlight', ) return output def source(macro, environ, *args, **kwargs): """Return the parsed text of body, rendered in a
     block."""
        if macro.body is None:
            return None
    
        return builder.tag.pre(environ['parser'].render(macro.body, environ=environ).decode('utf-8'))
    
    
    def div(macro, environ, cls=None, float=None, id=None, style=None, *args, **kwargs):
        if macro.body is None:
            return None
    
        if float and float in ('left', 'right'):
            style = f'float: {float}; {style}'
    
        if style:
            style = ';'.join(sanitizer.sanitize_css(style))
    
        context = 'block' if macro.isblock else 'inline'
    
        contents = environ['parser'].generate(
            macro.body,
            environ=environ,
            context=context,
        )
    
        return builder.tag.div(contents, id=id, class_=cls, style=style)
    
    
    def span(macro, environ, class_=None, id=None, style=None, *args, **kwargs):
        """..."""
        if macro.body is None:
            return None
    
        if style:
            style = ';'.join(sanitizer.sanitize_css(style))
    
        contents = environ['parser'].generate(
            macro.body,
            environ=environ,
            context='inline',
        )
    
        return builder.tag.span(contents, id=id, class_=class_, style=style)
    circuits-3.2.3/examples/web/wiki/macros/include.py000066400000000000000000000024751460335514400222000ustar00rootroot00000000000000"""
    Include macros
    
    Macros for inclusion of other wiki pages
    """
    
    from genshi import builder
    
    
    def include(macro, environ, pagename=None, *args, **kwargs):
        """Return the parsed content of the page identified by arg_string"""
        if pagename is None:
            return None
    
        db = environ['db']
        page = db.get(pagename, None)
    
        if page is not None:
            environ['page.name'] = pagename
    
            return environ['parser'].generate(page, environ=environ)
        return None
    
    
    def include_raw(macro, environ, pagename=None, *args, **kwargs):
        """
        Return the raw text of the page identified by arg_string, rendered
        in a 
     block.
        """
        if pagename is None:
            return None
    
        db = environ['db']
        page = db.get(pagename, None)
    
        if page is not None:
            return builder.tag.pre(page, class_='plain')
        return None
    
    
    def include_source(macro, environ, pagename=None, *args, **kwargs):
        """
        Return the parsed text of the page identified by arg_string, rendered
        in a 
     block.
        """
        if pagename is None:
            return None
    
        db = environ['db']
        page = db.get(pagename, None)
    
        if page is not None:
            environ['page.name'] = pagename
    
            return builder.tag.pre(
                environ['parser'].render(page, environ=environ).decode('utf-8'),
            )
        return None
    circuits-3.2.3/examples/web/wiki/macros/utils.py000066400000000000000000000005021460335514400217020ustar00rootroot00000000000000"""
    Utils macros
    
    Utility macros
    """
    
    from inspect import getdoc
    
    
    def macros(macro, environ, *args, **kwargs):
        """Return a list of available macros"""
        macros = environ['macros'].items()
        s = '\n'.join([f'== {k} ==\n{getdoc(v)}\n' for k, v in macros])
    
        return environ['parser'].generate(s, environ=environ)
    circuits-3.2.3/examples/web/wiki/macros/wiki.py000066400000000000000000000002711460335514400215100ustar00rootroot00000000000000"""Wiki macros"""
    
    from genshi import builder
    
    
    def title(macro, environ, *args, **kwargs):
        """Return the title of the current page."""
        return builder.tag(environ['page.name'])
    circuits-3.2.3/examples/web/wiki/requirements.txt000066400000000000000000000000371460335514400221730ustar00rootroot00000000000000circuits
    pygments
    creoleparser
    circuits-3.2.3/examples/web/wiki/setup.py000066400000000000000000000006341460335514400204240ustar00rootroot00000000000000#!/usr/bin/env python
    from setuptools import setup
    
    
    def parse_requirements(filename):
        with open(filename) as f:
            for line in f:
                if line and line[0] != '#':
                    yield line.strip()
    
    
    setup(
        name='wiki',
        version='dev',
        description='circuits wiki demo',
        scripts=('wiki.py',),
        install_requires=list(parse_requirements('requirements.txt')),
        zip_safe=True,
    )
    circuits-3.2.3/examples/web/wiki/static/000077500000000000000000000000001460335514400201765ustar00rootroot00000000000000circuits-3.2.3/examples/web/wiki/static/Image.jpg000066400000000000000000000110231460335514400217170ustar00rootroot00000000000000JFIF
    	
    
    
    
    
    
    
    
    
    
    
    
    
    	
    
    
    
    
    
    
    	
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    `"	C	!1A"Q2aqB	#3R$CSbrDT(!1AQ"a#R?([+ĸg߽QHĸy=1r8o>TϧiŒCw8x8k*#!7c28A=1ob%8]P`cУr$619z,[J&p
    nL6PvKO}!G|1jpZg)޸,9+'1!8Et`APGkG&ΡcVtt2*:|q)q!
    DqG3_*O;~$'C8	ؗԜ#3|=Xهbv
    MXoPE"<>yTOD&ύ gN$+b {`s%C^<~M5㭨W`+NENrG'pN	x&GE
    LsfO<>ѧ-?اd@KP5W4:^}XN)S`kld_A=~XeO8sD\rImẂDZśW٩wْ,k0u:y8(YdmvK3])@!y&4"y96, aRe#7 +DCQ:Ag.IiLG)%Q&M\eԀv	
    N΢
    (h_JۖUHJ"@$sUc(1ߒW}<7ZVÖoLk|LIŇbeIG,6F+Mȣ關f65d\mLb[l~"Eq_fYyEfՃ!%ISjԤmaMǤ=2ED`b}q8$MVJ3D]U:٣,JQ%$lX;'?I<ŵ^Gtn@sE'3Q^?ν6đ
    @JKbПC]Ђښ=1
    33yAӿ+,6~j"?Kc^_/:15٭TXXt3B)=6Z	'|w3'{GBX^hhr{Vꇙ3=f+'Ȁ~LUV`9e EG
    IE7@H;$(h}71jDXeT3[f _7l/]/շ`98Т3v'`ں=[C08MB!GDG.LqJB[$w#	X8GvUSq+,3)K*RlaFsm^/#-²^'7IJ˕-ZfyX@bP[XٶMO/#"eL"Օ]+9=wXpsnOYaܫʖh̛#Wm<Yn+5g@>b@5Q>k၉spS-4y$SzմyI6q[jedF--S[-9[Պ(ԡf,s2ȌnJEr[H֝܂>8Zv)2~lZpi[=fA&nH%fjyeQ$`m^,AjuS""O%cZt;7N/;[BG%XD؃g;7魱٢RIFvēHUEN0?8^&g(?:o^.,#}dIJeHޚF}t7d9V	!UQf?d|~+fkd$=q4vаj>L&~ HuЧr]vљdb‚
    %\M@Hhu)*|
    *PIXgqrீXU)}Һg	et‹\eƶX-ŜvYAc=0Zc=HG$dgUQb6"y.vds .ܱF"FbYoTl7n$R5n#ӝEm{.r6_|l:413C>%>2I%yu5Y
    9Ъ
     XD,PdeE儖lRG;E,|6^B:
    U\vGfyQ_+DrB
    	,@82I
    ( $ql7ßR0x`Ut(ڏ1fQӌk;ܙw-8tԺEZCm)G>q"tx$:6ს#r=*<]Li&Ñ"A+7lQK~!EיP>$~ʥjEVZ{cϹD9zA\ı6Z&՚ 4	Hx t=kJUV3fg\Ť iŔ:vbُ;Ve$|Ï.u&PHʆؿR.m}Ͱ&e̮\^WKCg$"5iUPsC;.$gW0UԥKz[w#Yk;eDid:#)rHg.59"RFZ ǩ9rԅBkx|C)i h<ǔYdCD#empּ̘x߯o%haL%R# %I+CS"“X]e2ň
    c_i';?ҋ&r~05,O&h.}3ВG}z`G0jXNfyFW[tt?v/8Fh-fF	)05I͠F9hU
    3Z)a\ܲDa$]t6bC:;
    	'h%ifUU)@/?SNռOG,2:G49l1o=Dښ̲SVڣ&u ^IV4Q2eiKjFugu;Ėw^dGܹwa5gH%rtE˽iq!hq,Ix:u23a¬e@"n:GZOIĹd}circuits-3.2.3/examples/web/wiki/static/css/000077500000000000000000000000001460335514400207665ustar00rootroot00000000000000circuits-3.2.3/examples/web/wiki/static/css/pygments.css000066400000000000000000000062311460335514400233500ustar00rootroot00000000000000.hll { background-color: #ffffcc }
    .c { color: #408090; font-style: italic } /* Comment */
    .err { border: 1px solid #FF0000 } /* Error */
    .k { color: #007020; font-weight: bold } /* Keyword */
    .o { color: #666666 } /* Operator */
    .cm { color: #408090; font-style: italic } /* Comment.Multiline */
    .cp { color: #007020 } /* Comment.Preproc */
    .c1 { color: #408090; font-style: italic } /* Comment.Single */
    .cs { color: #408090; background-color: #fff0f0 } /* Comment.Special */
    .gd { color: #A00000 } /* Generic.Deleted */
    .ge { font-style: italic } /* Generic.Emph */
    .gr { color: #FF0000 } /* Generic.Error */
    .gh { color: #000080; font-weight: bold } /* Generic.Heading */
    .gi { color: #00A000 } /* Generic.Inserted */
    .go { color: #303030 } /* Generic.Output */
    .gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */
    .gs { font-weight: bold } /* Generic.Strong */
    .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
    .gt { color: #0040D0 } /* Generic.Traceback */
    .kc { color: #007020; font-weight: bold } /* Keyword.Constant */
    .kd { color: #007020; font-weight: bold } /* Keyword.Declaration */
    .kn { color: #007020; font-weight: bold } /* Keyword.Namespace */
    .kp { color: #007020 } /* Keyword.Pseudo */
    .kr { color: #007020; font-weight: bold } /* Keyword.Reserved */
    .kt { color: #902000 } /* Keyword.Type */
    .m { color: #208050 } /* Literal.Number */
    .s { color: #4070a0 } /* Literal.String */
    .na { color: #4070a0 } /* Name.Attribute */
    .nb { color: #007020 } /* Name.Builtin */
    .nc { color: #0e84b5; font-weight: bold } /* Name.Class */
    .no { color: #60add5 } /* Name.Constant */
    .nd { color: #555555; font-weight: bold } /* Name.Decorator */
    .ni { color: #d55537; font-weight: bold } /* Name.Entity */
    .ne { color: #007020 } /* Name.Exception */
    .nf { color: #06287e } /* Name.Function */
    .nl { color: #002070; font-weight: bold } /* Name.Label */
    .nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */
    .nt { color: #062873; font-weight: bold } /* Name.Tag */
    .nv { color: #bb60d5 } /* Name.Variable */
    .ow { color: #007020; font-weight: bold } /* Operator.Word */
    .w { color: #bbbbbb } /* Text.Whitespace */
    .mf { color: #208050 } /* Literal.Number.Float */
    .mh { color: #208050 } /* Literal.Number.Hex */
    .mi { color: #208050 } /* Literal.Number.Integer */
    .mo { color: #208050 } /* Literal.Number.Oct */
    .sb { color: #4070a0 } /* Literal.String.Backtick */
    .sc { color: #4070a0 } /* Literal.String.Char */
    .sd { color: #4070a0; font-style: italic } /* Literal.String.Doc */
    .s2 { color: #4070a0 } /* Literal.String.Double */
    .se { color: #4070a0; font-weight: bold } /* Literal.String.Escape */
    .sh { color: #4070a0 } /* Literal.String.Heredoc */
    .si { color: #70a0d0; font-style: italic } /* Literal.String.Interpol */
    .sx { color: #c65d09 } /* Literal.String.Other */
    .sr { color: #235388 } /* Literal.String.Regex */
    .s1 { color: #4070a0 } /* Literal.String.Single */
    .ss { color: #517918 } /* Literal.String.Symbol */
    .bp { color: #007020 } /* Name.Builtin.Pseudo */
    .vc { color: #bb60d5 } /* Name.Variable.Class */
    .vg { color: #bb60d5 } /* Name.Variable.Global */
    .vi { color: #bb60d5 } /* Name.Variable.Instance */
    .il { color: #208050 } /* Literal.Number.Integer.Long */
    circuits-3.2.3/examples/web/wiki/static/css/screen.css000066400000000000000000000060261460335514400227630ustar00rootroot00000000000000* {
    	margin : 0;
    	padding : 0;
    }
    
    a {
    	color : #437fda;
    	text-decoration : none;
    }
    a:visited {
    	color : #437fda;
    	text-decoration : none;
    }
    a:hover {
    	color : #ba8f43;
    }
    
    h2 {
    	color : #343434;
    	font : italic 200% sans-serif;
    }
    h3 {
    	color : #343434;
    	font : italic 160% sans-serif;
    }
    h4 {
    	color : #343434;
    	font : bold italic 110% sans-serif;
    	padding : 1em 1em 0 1em;
    }
    
    html {
    	color : #565656;
    	font : 70%/170% sans-serif;
    	text-align : justify;
    }
    
    img {
    	margin : 1em 1em 0 1em;
    }
    img.left {
    	float : left;
    	margin : 1em 1em 0 0;
    }
    img.right {
    	float : right;
    	margin : 1em 0 0 1em;
    }
    
    blockquote {
    	font-style : italic;
    	margin : 1em 1em 0 1em;
    	padding : 0 0 1em 0;
    }
    blockquote span {
    	font-size : 200%;
    	line-height : 1%;
    	margin : 0 0.15em;
    	position : relative;
    	top : 0.25em;
    }
    
    form button {
    	background : #ffffff;
    	border : 1px solid #cfcfcf;
    	padding : 0.25em;
    	margin : 0 0 0 0.75em;
    }
    form input {
    	border : none;
    	width : 100%;
    }
    form textarea {
    	border : none;
    	width : 100%;
    	height : 100%;
    }
    form p.button {
    	text-align : right;
    }
    form p.input,
    form p.text {
    	background : #ffffff;
    	border : 1px solid #cfcfcf;
    	padding : 0.25em;
    	margin : 0.25em 1em 0 1em;
    }
    
    p {
    	padding : 1em 1em 0 1em;
    }
    
    ul,
    ol {
    	padding : 1em 1em 0 3em;
    }
    
    code, pre {
        font-size: 90%;
        white-space: pre-wrap;
        line-height: 1.2;
        color: #666;
        font-family: Droid Mono, DejaVu Mono, Lucida Console, Lucida Typewriter, monospace;
        background: #f7f7f7;
        border: 1px solid #d7d7d7;
        margin: 1em 1.75em;
        padding: .25em;
        overflow: auto;
    }
    
    img.smiley {
     vertical-align: baseline;
    }
    
    .wiki table {
        border: 2px solid #ccc;
        border-collapse: collapse;
        border-spacing: 0;
    }
    
    #main {
    	margin : auto;
    	max-width : 65em;
    	min-width : 40em;
    	width : auto !important;
    	width : 65em;
    }
    
    #header {
    	background : #2b548c url('../images/header_bg.png') repeat-x bottom left;
    	padding : 6em 4em 1em 4em;
    }
    #header h1 {
    	color : #ffffff;
    	font : italic 200% sans-serif;
    }
    
    #menu {
    	background : #437fda;
    	border-bottom : 1px solid #2b548c;
    	font : 100% sans-serif;
    }
    #menu ul {
    	padding : 0.75em 4em;
    }
    #menu li {
    	display : inline;
    }
    #menu li a {
    	color : #ffffff;
    	padding : 0.75em 1.5em;
    }
    #menu li a:hover {
    	background : #2b548c;
    }
    #menu li.selected a {
    	background : #ffffff;
    	border : 1px solid #2b548c;
    	border-bottom : 1px solid #ffffff;
    	color : #437fda;
    }
    #menu li.selected a:hover {
    	background : #ffffff;
    	color : #ba8f43;
    }
    
    #content {
    	border-bottom : 1px solid #cfcfcf;
    	height : auto !important;
    	height : 1%;
    	overflow : hidden;
    	padding : 2em 0 0 0;
    }
    #content div {
    	padding : 0 4em 2em 4em;
    }
    
    div.left {
    	left : -1em;
    	float : left;
    	position : relative;
    	width : 50%;
    }
    div.right {
    	left : 1em;
    	float : left;
    	position : relative;
    	width : 50%;
    }
    
    #footer {
    	font-size : 85%;
    	margin : auto;
    	padding : 1em 0 3em 0;
    	text-align : center;
    	width : 65%;
    }
    circuits-3.2.3/examples/web/wiki/static/favicon.ico000066400000000000000000000026161460335514400223240ustar00rootroot00000000000000x(
    
    			
    
    
    
    
    
       !!!"""###$$$%%%&&&'''((()))***+++,,,---...///000111222333444555666777888999:::;;;<<<===>>>???@@@AAABBBCCCDDDEEEFFFGGGHHHIIIJJJKKKLLLMMMNNNOOOPPPQQQRRRSSSTTTUUUVVVWWWXXXYYYZZZ[[[\\\]]]^^^___```aaabbbcccdddeeefffggghhhiiijjjkkklllmmmnnnooopppqqqrrrssstttuuuvvvwwwxxxyyyzzz{{{|||}}}~~~ٺGGj(((((;{{((Xj;j;(GG;G{X胺XG(jjj(G(胘jXGG{{XjXgC		@ @circuits-3.2.3/examples/web/wiki/static/images/000077500000000000000000000000001460335514400214435ustar00rootroot00000000000000circuits-3.2.3/examples/web/wiki/static/images/header_bg.png000066400000000000000000000010451460335514400240510ustar00rootroot00000000000000PNG
    
    
    IHDRl(?gAMA7tEXtSoftwareAdobe ImageReadyqe<PLTE@{,U;q2`@zC=tB~.Y,U?x,VB~A|-W3b4e:pw>v>v1^9m-W8l0\A}B}?y-WA}=uA{w,V.Y5e?y:o3a0]-X1_7i0]9n>w/[YIDATx"Ce%;!3ى;ǶtƖ_L#J1fƧm?o=ׁ{6yUԆKYuT[5w'j
    ",ə4WW&[oZ4=+Iɦ
     IENDB`circuits-3.2.3/examples/web/wiki/tpl/000077500000000000000000000000001460335514400175065ustar00rootroot00000000000000circuits-3.2.3/examples/web/wiki/tpl/edit.html000066400000000000000000000022271460335514400213240ustar00rootroot00000000000000
    
    	
    		Editing %(title)s
    		
    		
    		
    		
    		
    	
    	
    		
    circuits-3.2.3/examples/web/wiki/tpl/view.html000066400000000000000000000017011460335514400213450ustar00rootroot00000000000000 %(title)s
    %(text)s
    Edit
    circuits-3.2.3/examples/web/wiki/wiki.py000077500000000000000000000045141460335514400202330ustar00rootroot00000000000000#!/usr/bin/env python import os import sqlite3 import macros from creoleparser import Parser, create_dialect, creole11_base import circuits from circuits.web import Controller, Logger, Server, Static text2html = Parser( create_dialect(creole11_base, macro_func=macros.dispatcher), method='xhtml', ) class Wiki: def __init__(self, database): super().__init__() create = not os.path.exists(database) self._cx = sqlite3.connect(database) self._cu = self._cx.cursor() if create: self._cu.execute('CREATE TABLE pages (name, text)') for defaultpage in os.listdir('defaultpages'): filename = os.path.join('defaultpages', defaultpage) self.save(defaultpage, open(filename).read()) def save(self, name, text): self._cu.execute('SELECT COUNT() FROM pages WHERE name=?', (name,)) row = self._cu.fetchone() if row[0]: self._cu.execute( 'UPDATE pages SET text=? WHERE name=?', (text, name), ) else: self._cu.execute( 'INSERT INTO pages (name, text) VALUES (?, ?)', (name, text), ) self._cx.commit() def get(self, name, default=None): self._cu.execute('SELECT text FROM pages WHERE name=?', (name,)) row = self._cu.fetchone() return row[0] if row else default class Root(Controller): db = Wiki('wiki.db') environ = {'db': db, 'macros': macros.loadMacros()} def GET(self, name='FrontPage', action='view'): environ = self.environ.copy() environ['page.name'] = name environ['parser'] = text2html d = {} d['title'] = name d['version'] = circuits.__version__ d['menu'] = text2html(self.db.get('SiteMenu', ''), environ=environ) text = self.db.get(name, '') s = open('tpl/%s.html' % action).read() if action == 'view': d['text'] = text2html(text, environ=environ) else: d['text'] = text return s % d def POST(self, name='FrontPage', **form): self.db.save(name, form.get('text', '')) return self.redirect(name) app = Server(('0.0.0.0', 8000)) Static(docroot='static').register(app) Root().register(app) Logger().register(app) app.run() circuits-3.2.3/examples/web/wsgi.py000077500000000000000000000007121460335514400172720ustar00rootroot00000000000000#!/usr/bin/env python # curl -i http://localhost:8000/foo/ from circuits.web import Controller, Server from circuits.web.wsgi import Gateway def foo(environ, start_response): start_response('200 OK', [('Content-Type', 'text/plain')]) return ['Foo!'] class Root(Controller): """App Rot""" def index(self): return 'Hello World!' app = Server(('0.0.0.0', 8000)) Root().register(app) Gateway({'/foo': foo}).register(app) app.run() circuits-3.2.3/examples/web/wsgiapp.py000077500000000000000000000003541460335514400177750ustar00rootroot00000000000000#!/usr/bin/env python from circuits.web import Controller from circuits.web.wsgi import Application class Root(Controller): def index(self): return 'Hello World!' application = Application() Root().register(application) circuits-3.2.3/examples/web/xmlrpc_demo.py000077500000000000000000000004401460335514400206300ustar00rootroot00000000000000#!/usr/bin/env python from circuits import Component from circuits.web import XMLRPC, Logger, Server class Test(Component): def foo(self, a, b, c): return a, b, c app = Server(('0.0.0.0', 8000)) Logger().register(app) XMLRPC().register(app) Test().register(app) app.run() circuits-3.2.3/examples/wget.py000077500000000000000000000013061460335514400165120ustar00rootroot00000000000000#!/usr/bin/env python """ wget Example A basic wget-like clone that asynchronously connections a remote web server requesting a given resource. """ import sys from circuits import Component from circuits.web.client import Client, request class WebClient(Component): def init(self, url): self.url = url Client().register(self) def ready(self, *args): self.fire(request('GET', self.url)) def response(self, response): print(f'{response.status:d} {response.reason:s}') print( '\n'.join(f'{k:s}: {v:s}' for k, v in response.headers.items()), ) print(response.read()) raise SystemExit(0) WebClient(sys.argv[1]).run() circuits-3.2.3/fabfile/000077500000000000000000000000001460335514400147415ustar00rootroot00000000000000circuits-3.2.3/fabfile/__init__.py000066400000000000000000000046461460335514400170640ustar00rootroot00000000000000"""Development Task""" from os import getcwd import docker # noqa import help # noqa from fabric.api import abort, cd, execute, hide, hosts, local, prefix, prompt, run, settings, task import docs # noqa from .utils import msg, pip, requires, tobool @task() @requires('pip') def build(**options): """ Build and install required dependencies Options can be provided to customize the build. The following options are supported: - dev -> Whether to install in development mode (Default: Fase) """ dev = tobool(options.get('dev', False)) if dev: pip(requirements='requirements-dev.txt') with settings(hide('stdout', 'stderr'), warn_only=True): local('python setup.py {:s}'.format('develop' if dev else 'install')) @task() def clean(): """Clean up build files and directories""" files = ['build', '.coverage', 'coverage', 'dist', 'docs/build'] local('rm -rf {:s}'.format(' '.join(files))) local("find . -type f -name '*~' -delete") local("find . -type f -name '*.pyo' -delete") local("find . -type f -name '*.pyc' -delete") local("find . -type d -name '__pycache__' -delete") local("find . -type d -name '*egg-info' -exec rm -rf {} +") @task() def develop(): """Build and Install in Development Mode""" return execute(build, dev=True) @task() @requires('pytest') def test(): """Run all unit tests and doctests.""" local('python setup.py test') @task() @hosts('localhost') def release(): """Performs a full release""" with cd(getcwd()): with msg('Creating env'): run('mkvirtualenv test') with msg('Building'), prefix('workon test'): run('fab develop') with msg('Running tests'), prefix('workon test'): run('fab test') with msg('Building docs'), prefix('workon test'): run('pip install -r docs/requirements.txt') run('fab docs') version = run('python setup.py --version') if 'dev' in version: abort('Detected Development Version!') print(f'Release version: {version:s}') if prompt('Is this ok?', default='Y', validate=r'^[YyNn]?$') in 'yY': run(f'git tag {version:s}') run('python setup.py egg_info sdist bdist_egg bdist_wheel register upload') run('python setup.py build_sphinx upload_sphinx') with msg('Destroying env'): run('rmvirtualenv test') circuits-3.2.3/fabfile/docker.py000066400000000000000000000020621460335514400165620ustar00rootroot00000000000000"""Docker Tasks""" from fabric.api import local, task from .utils import msg, requires, tobool TAG = 'prologic/circuits' @task(default=True) @requires('docker') def build(**options): """ Build Docker Image Options can be provided to customize the build. The following options are supported: - rebuild -> Whether to rebuild without a cache. - version -> Specific version to tag the image with (Default: latest) """ rebuild = tobool(options.get('rebuild', False)) version = options.get('version', 'latest') tag = f'{TAG:s}:{version:s}' args = ['docker', 'build', '-t', tag, '.'] if rebuild: args.insert(-1, '--no-cache') with msg('Building Image'): local(' '.join(args)) @task() @requires('docker') def publish(): """Publish Docker Image""" args = ['docker', 'push', TAG] with msg('Pushing Image'): local(' '.join(args)) @task() @requires('docker') def run(): """Run Docker Container""" args = ['docker', 'run', '-i', '-t', '--rm', TAG] local(' '.join(args)) circuits-3.2.3/fabfile/docs.py000066400000000000000000000015751460335514400162530ustar00rootroot00000000000000"""Documentation Tasks""" from fabric.api import lcd, local, task from .utils import pip, requires PACKAGE = 'circuits' @task() def api(): """Generate the API Documentation""" if PACKAGE is not None: pip(requirements='docs/requirements.txt') local(f'sphinx-apidoc -f -e -T -o docs/source/api {PACKAGE:s}') @task() @requires('make') def clean(): """Delete Generated Documentation""" with lcd('docs'): pip(requirements='requirements.txt') local('make clean') @task(default=True) @requires('make') def build(**options): """Build the Documentation""" pip(requirements='docs/requirements.txt') with lcd('docs'): local('make html') @task() @requires('open') def view(**options): """View the Documentation""" with lcd('docs'): import webbrowser webbrowser.open_new_tab('build/html/index.html') circuits-3.2.3/fabfile/help.py000066400000000000000000000015431460335514400162460ustar00rootroot00000000000000"""Help Tasks""" from fabric import state from fabric.api import task from fabric.task_utils import crawl from fabric.tasks import Task @task(default=True) def help(name=None): """ Display help for a given task Options: name - The task to display help on. To display a list of available tasks type: $ fab -l To display help on a specific task type: $ fab help: """ if name is None: name = 'help' task = crawl(name, state.commands) if isinstance(task, Task): doc = getattr(task, '__doc__', None) if doc is not None: print(f'Help on {name:s}:') print() print(doc) else: print(f'No help available for {name:s}') else: print(f'No such task {name:s}') print('For a list of tasks type: fab -l') circuits-3.2.3/fabfile/utils.py000066400000000000000000000036531460335514400164620ustar00rootroot00000000000000"""Utilities""" from contextlib import contextmanager from functools import wraps from imp import find_module from fabric.api import abort, hide, local, puts, quiet, settings, warn def tobool(s): if isinstance(s, bool): return s return s.lower() in ['yes', 'y'] def toint(s): if isinstance(s, int): return s return int(s) @contextmanager def msg(s): """ Print message given as ``s`` in a context manager Prints "{s} ... OK" """ puts(f'{s:s} ... ', end='', flush=True) with settings(hide('everything')): yield puts('OK', show_prefix=False, flush=True) def pip(*args, **kwargs): requirements = kwargs.get('requirements', None) if requirements is not None: local('pip install -U -r {:s}'.format(kwargs['requirements'])) else: args = [arg for arg in args if not has_module(arg)] if args: local('pip install {:s}'.format(' '.join(args))) def has_module(name): try: return find_module(name) except ImportError: return False def has_binary(name): with quiet(): return local(f'which {name:s}').succeeded def requires(*names, **kwargs): """ Decorator/Wrapper that aborts if not all requirements are met. Aborts if not all requirements are met given a test function (defaulting to :func:`~has_binary`). :param kwargs: Optional kwargs. e.g: ``test=has_module`` :type kwargs: dict :returns: None or aborts :rtype: None """ test = kwargs.get('test', has_binary) def decorator(f): @wraps(f) def wrapper(*args, **kwds): if all(test(name) for name in names): return f(*args, **kwds) for name in names: if not test(name): warn(f'{name:s} not found') abort(f'requires({names!r:s}) failed') return None return wrapper return decorator circuits-3.2.3/man/000077500000000000000000000000001460335514400141245ustar00rootroot00000000000000circuits-3.2.3/man/circuits.bench.1000066400000000000000000000027461460335514400171220ustar00rootroot00000000000000.TH circuits.bench 1 "Jun 2011" "circuits 2.0.2" "User Commands" .SH NAME circuits.bench \- simple benchmaking of the circuits library .SH SYNOPSIS .B circuits.bench [\fIoptions\fR] .SH DESCRIPTION circuits.bench does some simple benchmaking of the circuits library. .SH OPTIONS .TP \fB-b\fR \fIaddress:[port]\fR Bind to address:[port] (UDP) to test remote events. Default address is 0.0.0.0. .TP \fB-c\fR \fIinteger\fR Set concurrency level to \fIinteger\fR. Default is 1. .TP \fB-d\fR Enable debug mode. .TP \fB-e\fR \fInumber\fR Stop after specified \fInumber\fR of events. Default is 0. .TP \fB-f\fR \fInumber\fR \fInumber\fR of dummy events to fill queue with. Default is 0. .TP \fB-l\fR Listen on 0.0.0.0:8000 (UDP) to test remote events. .TP \fB-m\fR \fImode\fR Set operation mode. \fImode\fR can be \fIlatency\fR, \fIspeed\fR or \fIsync\fR. Default \fImode\fR is \fIspeed\fR. .TP \fB-o\fR \fIformat\fR Specify output format. For example \fIformat\fR can be:"cicuits.bench: events:%s, speed:%s, time:%s" .TP \fB-p\fR Enable execution profiling support. .TP \fB-q\fR Suppress output. .TP \fB-s\fR Enable psyco (circuits on speed!) if it is available. .TP \fB-t\fR \fIseconds\fR Stop after specified elapsed \fIseconds\fR. .TP \fB-w\fR Wait for remote nodes to connect. .TP \fB--version\fR Output version information and exit. .SH AUTHOR James Mills .PP This manual page was written by Daniele Tricoli , for the Debian project (but may be used by others). circuits-3.2.3/man/circuits.web.1000066400000000000000000000023401460335514400166060ustar00rootroot00000000000000.TH circuits.web 1 "Jun 2011" "circuits 2.0.2" "User Commands" .SH NAME circuits.web \- Web Server and testing tool .SH SYNOPSIS .B circuits.web [\fIoptions\fR] [\fIdocroot\fR] .SH DESCRIPTION circuits.web is a component based, event-driven light weight and high performance HTTP/WSGI framework. circuits.web applications are stand-alone applications with a high performance, multi-process web server with great concurrent scalability with full support for WSGI and deployment with other web servers. .SH OPTIONS .TP \fB-b\fR \fIaddress:[port]\fR Bind to address:[port]. Default address is 0.0.0.0:8000. .TP \fB-d\fR Enable debug mode. .TP \fB-j\fR Use python JIT (psyco) if it is available. .TP \fB-m\fR \fInumber\fR Specify \fInumber\fR of processes to start (multiprocessing). .TP \fB-p\fR Enable execution profiling support. .TP \fB-s\fR \fIserver\fR Specify server to use. .TP \fB-t\fR \fItype\fR Specify type of poller to use. \fItype\fR can be \fIepoll\fR, \fIpoll\fR or \fIselect\fR. Default \fItype\fR is \fIselect\fR. .TP \fB-v\fR Enable WSGI validation mode. .SH AUTHOR James Mills .PP This manual page was written by Daniele Tricoli , for the Debian project (but may be used by others). circuits-3.2.3/pyproject.toml000066400000000000000000000046011460335514400162660ustar00rootroot00000000000000[tool.ruff] line-length = 127 target-version = "py37" extend-exclude = ["circuits/web/parsers/multipart.py"] [tool.ruff.lint] preview = true ignore-init-module-imports = true select = ["E", "W", "F", "I", "D", "TD"] ignore = [ "E501", # line-too-long "E203", # whitespace-before-punctuation "COM812", # missing-trailing-comma "D203", # one-blank-line-before-class "D212", # multi-line-summary-first-line "D400", # ends-in-period "D415", # ends-in-punctuation "D102", # Missing docstring in public method "D103", # Missing docstring in public function "D101", # Missing docstring in public class "D100", # Missing docstring in public module "D107", # Missing docstring in `__init__` "D205", # 1 blank line required between summary line and description "D105", # Missing docstring in magic method "D401", # First line of docstring should be in imperative mood: "A shortcut to abspath, escape and lowercase." "D402", # First line should not be the function's signature "D404", # First word of the docstring should not be "This" "D104", # Missing docstring in public package "T201", "T203", # print, p-print "TD001", # invalid-todo-tag "TD002", # missing-todo-author "TD003", # missing-todo-link ] # Exclude a variety of commonly ignored directories. exclude = [ ".bzr", ".direnv", ".eggs", ".git", ".hg", ".mypy_cache", ".nox", ".pants.d", ".ruff_cache", ".svn", ".tox", ".venv", "__pypackages__", "_build", "buck-out", "build", "dist", "node_modules", "venv", ] # Allow unused variables when underscore-prefixed. dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" task-tags = ["TODO", "FIXME"] [tool.ruff.lint.mccabe] max-complexity = 40 [tool.ruff.lint.flake8-quotes] docstring-quotes = "double" multiline-quotes = "double" inline-quotes = "single" [tool.ruff.lint.flake8-unused-arguments] ignore-variadic-names = true [tool.ruff.lint.flake8-pytest-style] parametrize-names-type = "csv" [tool.ruff.format] quote-style = "single" [tool.ruff.lint.isort] combine-as-imports = true #filter-files = true #force-grid-wrap = false known-first-party = ["circuits"] # https://github.com/astral-sh/ruff/issues/2600 : #multi-line-output = 5 #include-trailing-comma = true lines-after-imports = 2 #skip = ".tox,docs,circuits/web/parsers/multipart.py" #skip-glob = "venv/*" split-on-trailing-comma = false circuits-3.2.3/requirements-dev.txt000066400000000000000000000003021460335514400174040ustar00rootroot00000000000000# Convenient Development Tasks Fabric # Debugging pudb # Packaging wheel # Running Tests tox pytest uhttplib codecov pytest-cov # Generating Documentation Sphinx releases Sphinx-PyPI-upload circuits-3.2.3/requirements-test.txt000066400000000000000000000000641460335514400176120ustar00rootroot00000000000000codecov pytest pytest-cov pytest-timeout flake8 tox circuits-3.2.3/requirements.txt000066400000000000000000000000001460335514400166230ustar00rootroot00000000000000circuits-3.2.3/setup.cfg000066400000000000000000000004011460335514400151650ustar00rootroot00000000000000[build_sphinx] source-dir = docs/source build-dir = docs/build [upload_sphinx] upload-dir = docs/build/html [bdist_wheel] universal=1 [flake8] exclude = build,.git,.tox,docs,circuits/web/parsers/multipart.py ignore=E501,W503,E203 max-line-length = 119 circuits-3.2.3/setup.py000077500000000000000000000064511460335514400150740ustar00rootroot00000000000000#!/usr/bin/env python from glob import glob from setuptools import find_packages, setup def read_file(filename): try: return open(filename).read() except OSError: return '' setup( name='circuits', description='Asynchronous Component based Event Application Framework', long_description=open('README.rst') .read() .replace( '.. include:: examples/index.rst', read_file('examples/index.rst'), ), author='James Mills', author_email='prologic@shortcircuit.net.au', url='http://circuitsframework.com/', download_url='http://bitbucket.org/circuits/circuits/downloads/', classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Console', 'Environment :: No Input/Output (Daemon)', 'Environment :: Other Environment', 'Environment :: Plugins', 'Environment :: Web Environment', 'Intended Audience :: Developers', 'Intended Audience :: Information Technology', 'Intended Audience :: Science/Research', 'Intended Audience :: System Administrators', 'Intended Audience :: Telecommunications Industry', 'License :: OSI Approved :: MIT License', 'Natural Language :: English', 'Operating System :: POSIX :: BSD', 'Operating System :: POSIX :: Linux', 'Operating System :: MacOS :: MacOS X', 'Operating System :: Microsoft :: Windows', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', 'Programming Language :: Python :: 3.12', 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', 'Topic :: Adaptive Technologies', 'Topic :: Communications :: Chat :: Internet Relay Chat', 'Topic :: Communications :: Email :: Mail Transport Agents', 'Topic :: Database', 'Topic :: Internet :: WWW/HTTP :: HTTP Servers', 'Topic :: Internet :: WWW/HTTP :: WSGI :: Application', 'Topic :: Internet :: WWW/HTTP :: WSGI :: Middleware', 'Topic :: Internet :: WWW/HTTP :: WSGI :: Server', 'Topic :: Software Development :: Libraries :: Application Frameworks', 'Topic :: Software Development :: Libraries :: Python Modules', 'Topic :: System :: Clustering', 'Topic :: System :: Distributed Computing', ], license='MIT', keywords='event framework distributed concurrent component asynchronous', platforms='POSIX', packages=find_packages( exclude=[ '*.tests', '*.tests.*', 'tests.*', 'tests', '*.fabfile', '*.fabfile.*', 'fabfile.*', 'fabfile', ], ), scripts=glob('bin/*'), entry_points={ 'console_scripts': [ 'circuits.web=circuits.web.main:main', ], }, zip_safe=True, use_scm_version={ 'write_to': 'circuits/version.py', }, setup_requires=[ 'setuptools_scm', ], extras_require={'stomp': ['stompest>=2.3.0', 'pysocks>=1.6.7']}, python_requires='>=3.7', ) circuits-3.2.3/tests/000077500000000000000000000000001460335514400145135ustar00rootroot00000000000000circuits-3.2.3/tests/__init__.py000066400000000000000000000000251460335514400166210ustar00rootroot00000000000000"""circuits tests""" circuits-3.2.3/tests/app/000077500000000000000000000000001460335514400152735ustar00rootroot00000000000000circuits-3.2.3/tests/app/__init__.py000066400000000000000000000000001460335514400173720ustar00rootroot00000000000000circuits-3.2.3/tests/app/app.py000066400000000000000000000014651460335514400164330ustar00rootroot00000000000000#!/usr/bin/env python import sys from os.path import abspath from circuits import Component from circuits.app import Daemon try: from coverage import coverage HAS_COVERAGE = True except ImportError: HAS_COVERAGE = False class App(Component): def init(self, pidfile): self.pidfile = pidfile def started(self, *args): Daemon(self.pidfile).register(self) def prepare_unregister(self, *args): return def main(): if HAS_COVERAGE: _coverage = coverage(data_suffix=True) _coverage.start() args = iter(sys.argv) next(args) # executable pidfile = next(args) # pidfile pidfile = abspath(pidfile) App(pidfile).run() if HAS_COVERAGE: _coverage.stop() _coverage.save() if __name__ == '__main__': main() circuits-3.2.3/tests/app/test_daemon.py000066400000000000000000000017331460335514400201530ustar00rootroot00000000000000#!/usr/bin/env python import sys from errno import ESRCH from os import kill from signal import SIGTERM from subprocess import Popen from time import sleep import pytest from . import app pytestmark = pytest.mark.skipif(pytest.PLATFORM == 'win32', reason='Unsupported Platform') def is_running(pid): try: kill(pid, 0) except OSError as error: if error.errno == ESRCH: return False return True def wait(pid, timeout=3): count = timeout while is_running(pid) and count: sleep(1) def test(tmpdir): tmpdir.ensure('app.pid') pid_path = tmpdir.join('app.pid') args = [sys.executable, app.__file__, str(pid_path)] Popen(args, env={'PYTHONPATH': ':'.join(sys.path)}).wait() sleep(1) assert pid_path.check(exists=True, file=True) pid = None with pid_path.open() as f: pid = int(f.read().strip()) assert isinstance(pid, int) assert pid > 0 kill(pid, SIGTERM) wait(pid) circuits-3.2.3/tests/conftest.py000066400000000000000000000077101460335514400167170ustar00rootroot00000000000000"""py.test config""" import sys import threading from collections import deque from collections.abc import Callable from time import sleep import pytest from circuits import BaseComponent, Debugger, Manager, handler from circuits.core.manager import TIMEOUT class Watcher(BaseComponent): def init(self): self._lock = threading.Lock() self.events = deque() @handler(channel='*', priority=999.9) def _on_event(self, event, *args, **kwargs): with self._lock: self.events.append(event) def clear(self): self.events.clear() def wait(self, name, channel=None, timeout=30.0): for _i in range(int(timeout / TIMEOUT)): with self._lock: for event in self.events: if ( event.name == name and event.waitingHandlers == 0 and ((channel is None) or (channel in event.channels)) ): return True sleep(TIMEOUT) return False def count(self, name, channel=None, n=1, timeout=30.0): n = 0 with self._lock: for event in self.events: if event.name == name and event.waitingHandlers == 0 and ((channel is None) or (channel in event.channels)): n += 1 return n class Flag: status = False def call_event_from_name(manager, event, event_name, *channels): fired = False value = None for _r in manager.waitEvent(event_name): if not fired: fired = True value = manager.fire(event, *channels) sleep(0.1) return value def call_event(manager, event, *channels): return call_event_from_name(manager, event, event.name, *channels) class WaitEvent: def __init__(self, manager, name, channel=None, timeout=30.0): if channel is None: channel = getattr(manager, 'channel', None) self.timeout = timeout self.manager = manager flag = Flag() @handler(name, channel=channel) def on_event(self, *args, **kwargs): flag.status = True self.handler = self.manager.addHandler(on_event) self.flag = flag def wait(self): try: for _i in range(int(self.timeout / TIMEOUT)): if self.flag.status: return True sleep(TIMEOUT) finally: self.manager.removeHandler(self.handler) def wait_for(obj, attr, value=True, timeout=30.0): from circuits.core.manager import TIMEOUT for _i in range(int(timeout / TIMEOUT)): if isinstance(value, Callable): if value(obj, attr): return True elif getattr(obj, attr) == value: return True sleep(TIMEOUT) return None class SimpleManager(Manager): def tick(self, timeout=-1): self._running = False return super().tick(timeout) @pytest.fixture() def simple_manager(request): manager = SimpleManager() Debugger(events=request.config.option.verbose).register(manager) return manager @pytest.fixture() def manager(request): manager = Manager() def finalizer(): manager.stop() request.addfinalizer(finalizer) waiter = WaitEvent(manager, 'started') manager.start() assert waiter.wait() Debugger(events=request.config.option.verbose).register(manager) return manager @pytest.fixture() def watcher(request, manager): watcher = Watcher().register(manager) def finalizer(): waiter = WaitEvent(manager, 'unregistered') watcher.unregister() waiter.wait() request.addfinalizer(finalizer) return watcher for key, value in { 'WaitEvent': WaitEvent, 'wait_for': wait_for, 'call_event': call_event, 'PLATFORM': sys.platform, 'PYVER': sys.version_info[:3], 'call_event_from_name': call_event_from_name, }.items(): setattr(pytest, key, value) circuits-3.2.3/tests/core/000077500000000000000000000000001460335514400154435ustar00rootroot00000000000000circuits-3.2.3/tests/core/__init__.py000066400000000000000000000000001460335514400175420ustar00rootroot00000000000000circuits-3.2.3/tests/core/app.py000066400000000000000000000002421460335514400165730ustar00rootroot00000000000000from circuits import Component class App(Component): def test(self): return 'Hello World!' def prepare_unregister(self, *args): return circuits-3.2.3/tests/core/exitcodeapp.py000066400000000000000000000005101460335514400203160ustar00rootroot00000000000000#!/usr/bin/env python import sys from circuits import Component class App(Component): def started(self, *args): try: code = int(sys.argv[1]) except ValueError: code = sys.argv[1] raise SystemExit(code) def main(): App().run() if __name__ == '__main__': main() circuits-3.2.3/tests/core/signalapp.py000066400000000000000000000015651460335514400200020ustar00rootroot00000000000000#!/usr/bin/env python import os import sys from circuits import Component from circuits.app import Daemon try: from coverage import coverage HAS_COVERAGE = True except ImportError: HAS_COVERAGE = False class App(Component): def init(self, pidfile, signalfile): self.pidfile = pidfile self.signalfile = signalfile Daemon(self.pidfile).register(self) def signal(self, signal, stack): f = open(self.signalfile, 'w') f.write(str(signal)) f.close() self.stop() def main(): if HAS_COVERAGE: _coverage = coverage(data_suffix=True) _coverage.start() pidfile = os.path.abspath(sys.argv[1]) signalfile = os.path.abspath(sys.argv[2]) App(pidfile, signalfile).run() if HAS_COVERAGE: _coverage.stop() _coverage.save() if __name__ == '__main__': main() circuits-3.2.3/tests/core/test_bridge.py000066400000000000000000000013211460335514400203050ustar00rootroot00000000000000#!/usr/bin/python -i from os import getpid import pytest from circuits import Component, Event, ipc pytestmark = pytest.mark.skipif(pytest.PLATFORM == 'win32', reason='Unsupported Platform') pytest.importorskip('multiprocessing') class hello(Event): """hello Event""" class App(Component): def hello(self): return f'Hello from {getpid():d}' def test(manager, watcher): app = App() _process, bridge = app.start(process=True, link=manager) assert watcher.wait('ready') x = manager.fire(ipc(hello())) assert pytest.wait_for(x, 'result') assert x.value == f'Hello from {app.pid:d}' app.stop() app.join() bridge.unregister() watcher.wait('unregistered') circuits-3.2.3/tests/core/test_call_wait.py000066400000000000000000000061611460335514400210170ustar00rootroot00000000000000#!/usr/bin/env python import pytest from circuits import Component, Event, handler class wait(Event): """wait Event""" success = True class call(Event): """call Event""" success = True class long_call(Event): """long_call Event""" success = True class long_wait(Event): """long_wait Event""" success = True class wait_return(Event): """wait_return Event""" success = True class hello(Event): """hello Event""" success = True class foo(Event): """foo Event""" success = True class get_x(Event): """get_x Event""" success = True class get_y(Event): """get_y Event""" success = True class eval(Event): """eval Event""" success = True class App(Component): @handler('wait') def _on_wait(self): x = self.fire(hello()) yield self.wait('hello') yield x.value @handler('call') def _on_call(self): x = yield self.call(hello()) yield x.value def hello(self): return 'Hello World!' def long_wait(self): x = self.fire(foo()) yield self.wait('foo') yield x.value def wait_return(self): self.fire(foo()) yield (yield self.wait('foo')) def long_call(self): x = yield self.call(foo()) yield x.value def foo(self): yield from range(1, 10) def get_x(self): return 1 def get_y(self): return 2 def eval(self): x = yield self.call(get_x()) y = yield self.call(get_y()) yield x.value + y.value @pytest.fixture() def app(request, manager, watcher): app = App().register(manager) assert watcher.wait('registered') def finalizer(): app.unregister() request.addfinalizer(finalizer) return app def test_wait_simple(manager, watcher, app): x = manager.fire(wait()) assert watcher.wait('wait_success') value = x.value assert value == 'Hello World!' def call_simple(manager, watcher, app): x = manager.fire(call()) assert watcher.wait('call_success') value = x.value assert value == 'Hello World!' def test_long_call(manager, watcher, app): x = manager.fire(long_call()) assert watcher.wait('long_call_success') value = x.value assert value == list(range(1, 10)) def test_long_wait(manager, watcher, app): x = manager.fire(long_wait()) assert watcher.wait('long_wait_success') value = x.value assert value == list(range(1, 10)) def test_wait_return(manager, watcher, app): x = manager.fire(wait_return()) assert watcher.wait('wait_return_success') value = x.value assert value == list(range(1, 10)) def test_eval(manager, watcher, app): x = manager.fire(eval()) assert watcher.wait('eval_success') value = x.value assert value == 3 @pytest.mark.xfail(reason='Issue #226') @pytest.mark.timeout(1) def test_wait_too_late(manager, watcher, app): event = foo() manager.fire(event) assert watcher.wait('foo_success') manager.tick() x = manager.wait(event, timeout=0.1) value = next(x) assert value == list(range(1, 10)) circuits-3.2.3/tests/core/test_call_wait_instance.py000066400000000000000000000014731460335514400227040ustar00rootroot00000000000000#!/usr/bin/env python import pytest from circuits import Component, Event, handler class wait(Event): """wait Event""" success = True class hello(Event): """hello Event""" success = True class App(Component): @handler('wait') def _on_wait(self): e = hello() x = self.fire(e) yield self.wait(e) yield x.value def hello(self): return 'Hello World!' @pytest.fixture() def app(request, manager, watcher): app = App().register(manager) assert watcher.wait('registered') def finalizer(): app.unregister() request.addfinalizer(finalizer) return app def test_wait_instance(manager, watcher, app): x = manager.fire(wait()) assert watcher.wait('wait_success') value = x.value assert value == 'Hello World!' circuits-3.2.3/tests/core/test_call_wait_order.py000066400000000000000000000017601460335514400222120ustar00rootroot00000000000000#!/usr/bin/env python from random import random, seed from time import sleep, time import pytest from circuits.core import Component, Event, Worker, handler, task class hello(Event): """hello Event""" success = True def process(x=None): sleep(random()) return x class App(Component): @handler('hello') def _on_hello(self): e1 = task(process, 1) self.fire(task(process, 2)) self.fire(task(process, 3)) yield (yield self.call(e1)) @pytest.fixture() def app(request, manager, watcher): seed(time()) app = App().register(manager) assert watcher.wait('registered') worker = Worker().register(manager) assert watcher.wait('registered') def finalizer(): app.unregister() worker.unregister() request.addfinalizer(finalizer) return app def test_call_order(manager, watcher, app): x = manager.fire(hello()) assert watcher.wait('hello_success') value = x.value assert value == 1 circuits-3.2.3/tests/core/test_call_wait_timeout.py000066400000000000000000000034341460335514400225650ustar00rootroot00000000000000#!/usr/bin/env python import pytest from circuits.core import Component, Event, TimeoutError, handler class wait(Event): """wait Event""" success = True class call(Event): """call Event""" success = True class hello(Event): """hello Event""" success = True class App(Component): @handler('wait') def _on_wait(self, timeout=-1): result = self.fire(hello()) try: yield self.wait('hello', timeout=timeout) except TimeoutError as e: yield e else: yield result @handler('hello') def _on_hello(self): return 'hello' @handler('call') def _on_call(self, timeout=-1): result = None try: result = yield self.call(hello(), timeout=timeout) except TimeoutError as e: yield e else: yield result @pytest.fixture() def app(request, manager, watcher): app = App().register(manager) assert watcher.wait('registered') def finalizer(): app.unregister() request.addfinalizer(finalizer) return app def test_wait_success(manager, watcher, app): x = manager.fire(wait(10)) assert watcher.wait('wait_success') value = x.value assert value == 'hello' def test_wait_failure(manager, watcher, app): x = manager.fire(wait(0)) assert watcher.wait('wait_success') value = x.value assert isinstance(value, TimeoutError) def test_call_success(manager, watcher, app): x = manager.fire(call(10)) assert watcher.wait('call_success') value = x.value assert value == 'hello' def test_call_failure(manager, watcher, app): x = manager.fire(call(0)) assert watcher.wait('call_success') value = x.value assert isinstance(value, TimeoutError) circuits-3.2.3/tests/core/test_channel_selection.py000066400000000000000000000017731460335514400225410ustar00rootroot00000000000000#!/usr/bin/python -i from circuits import Component, Event, Manager class foo(Event): """foo Event""" channels = ('a',) class bar(Event): """bar Event""" class A(Component): channel = 'a' def foo(self): return 'Foo' class B(Component): channel = 'b' def foo(self): return 'Hello World!' class C(Component): channel = 'c' def foo(self): return self.fire(bar()) def bar(self): return 'Bar' def test(): m = Manager() + A() + B() + C() while len(m): m.flush() # Rely on Event.channels x = m.fire(foo()) m.flush() assert x.value == 'Foo' # Explicitly specify the channel x = m.fire(foo(), 'b') m.flush() assert x.value == 'Hello World!' # Explicitly specify a set of channels x = m.fire(foo(), 'a', 'b') m.flush() assert x.value == ['Foo', 'Hello World!'] # Rely on self.channel x = m.fire(foo(), 'c') m.flush() m.flush() assert x.value == 'Bar' circuits-3.2.3/tests/core/test_complete.py000066400000000000000000000043421460335514400206670ustar00rootroot00000000000000#!/usr/bin/python from circuits import Component, Event class simple_event(Event): complete = True class test(Event): """test Event""" success = True class Nested3(Component): channel = 'nested3' def test(self): """Updating state. Must be called twice to reach final state.""" if self.root._state != 'Pre final state': self.root._state = 'Pre final state' else: self.root._state = 'Final state' class Nested2(Component): channel = 'nested2' def test(self): """Updating state.""" self.root._state = 'New state' # State change involves even more components as well. self.fire(test(), Nested3.channel) self.fire(test(), Nested3.channel) class Nested1(Component): channel = 'nested1' def test(self): """State change involves other components as well.""" self.fire(test(), Nested2.channel) class App(Component): channel = 'app' _simple_event_completed = False _state = 'Old state' _state_when_success = None _state_when_complete = None def simple_event_complete(self, e, value): self._simple_event_completed = True def test(self): """Fire the test event that should produce a state change.""" evt = test() evt.complete = True evt.complete_channels = [self.channel] self.fire(evt, Nested1.channel) def test_success(self, e, value): """Test event has been processed, save the achieved state.""" self._state_when_success = self._state def test_complete(self, e, value): """Test event has been completely processed, save the achieved state.""" self._state_when_complete = self._state app = App() Nested1().register(app) Nested2().register(app) Nested3().register(app) while len(app): app.flush() def test_complete_simple(): """Test if complete works for an event without further effects""" app.fire(simple_event()) while len(app): app.flush() assert app._simple_event_completed def test_complete_nested(): app.fire(test()) while len(app): app.flush() assert app._state_when_success == 'Old state' assert app._state_when_complete == 'Final state' circuits-3.2.3/tests/core/test_component_repr.py000066400000000000000000000016041460335514400221070ustar00rootroot00000000000000""" Component Repr Tests Test Component's representation string. """ import os from threading import current_thread from circuits import Component, Event class App(Component): def test(self, event, *args, **kwargs): pass class test(Event): pass def test_main(): id = f'{os.getpid()}:{current_thread().name}' app = App() assert repr(app) == '' % id app.fire(test()) assert repr(app) == '' % id app.flush() assert repr(app) == '' % id def test_non_str_channel(): id = f'{os.getpid()}:{current_thread().name}' app = App(channel=(1, 1)) assert repr(app) == '' % id app.fire(test()) assert repr(app) == '' % id app.flush() assert repr(app) == '' % id circuits-3.2.3/tests/core/test_component_setup.py000066400000000000000000000026061460335514400223020ustar00rootroot00000000000000from circuits import Component, Manager from circuits.core.handlers import handler """Component Setup Tests Tests that event handlers of a Component are automatically registered as event handlers. """ class App(Component): def test(self, event, *args, **kwargs): pass class A(Component): pass class B(Component): informed = False @handler('prepare_unregister', channel='*') def _on_prepare_unregister(self, event, c): if event.in_subtree(self): self.informed = True class Base(Component): channel = 'base' class C(Base): channel = 'c' def test_basic(): m = Manager() app = App() app.register(m) assert app.test in app._handlers.get('test', set()) app.unregister() while len(m): m.flush() assert not m._handlers def test_complex(): m = Manager() a = A() b = B() a.register(m) b.register(a) assert a in m assert a.root == m assert a.parent == m assert b in a assert b.root == m assert b.parent == a a.unregister() while len(m): m.flush() assert b.informed assert a not in m assert a.root == a assert a.parent == a assert b in a assert b.root == a assert b.parent == a def test_subclassing_with_custom_channel(): base = Base() assert base.channel == 'base' c = C() assert c.channel == 'c' circuits-3.2.3/tests/core/test_component_targeting.py000066400000000000000000000011661460335514400231260ustar00rootroot00000000000000#!/usr/bin/env python import pytest from circuits import Component, Event class hello(Event): """hello Event""" success = True class App(Component): channel = 'app' def hello(self): return 'Hello World!' @pytest.fixture() def app(request, manager, watcher): app = App().register(manager) assert watcher.wait('registered') def finalizer(): app.unregister() request.addfinalizer(finalizer) return app def test(manager, watcher, app): x = manager.fire(hello(), app) assert watcher.wait('hello_success') value = x.value assert value == 'Hello World!' circuits-3.2.3/tests/core/test_core.py000066400000000000000000000010211460335514400177760ustar00rootroot00000000000000#!/usr/bin/python -i from circuits import Component, Event, Manager class test(Event): """test Event""" class App(Component): def test(self): return 'Hello World!' def unregistered(self, *args): return def prepare_unregister(self, *args): return m = Manager() app = App() app.register(m) while len(app): app.flush() def test_fire(): x = m.fire(test()) m.flush() assert x.value == 'Hello World!' def test_contains(): assert App in m assert m not in app circuits-3.2.3/tests/core/test_coroutine.py000066400000000000000000000024261460335514400210670ustar00rootroot00000000000000#!/usr/bin/env python import pytest from circuits import Component, Event pytestmark = pytest.mark.skip('XXX: This test fails intermittently') class test(Event): """test Event""" class coroutine1(Event): """coroutine Event""" complete = True class coroutine2(Event): """coroutine Event""" complete = True class App(Component): returned = False def test(self, event): event.stop() return 'Hello World!' def coroutine1(self): print('coroutine1') yield self.call(test()) print('returned') self.returned = True def coroutine2(self): print('coroutine2') self.fire(test()) yield self.wait('test') print('returned') self.returned = True @pytest.fixture() def app(request, manager, watcher): app = App().register(manager) assert watcher.wait('registered') def finalizer(): app.unregister() request.addfinalizer(finalizer) return app def test_coroutine(manager, watcher, app): manager.fire(coroutine1()) assert watcher.wait('coroutine1_complete') assert app.returned, 'coroutine1' app.returned = False manager.fire(coroutine2()) assert watcher.wait('coroutine2_complete') assert app.returned, 'coroutine2' circuits-3.2.3/tests/core/test_debugger.py000066400000000000000000000122541460335514400206440ustar00rootroot00000000000000"""Debugger Tests""" import sys from io import StringIO import pytest from circuits import Debugger from circuits.core import Component, Event class test(Event): """test Event""" class App(Component): def test(self, raiseException=False): if raiseException: raise Exception() class Logger: error_msg = None debug_msg = None def error(self, msg): self.error_msg = msg def debug(self, msg): self.debug_msg = msg def test_main(): app = App() stderr = StringIO() debugger = Debugger(file=stderr) debugger.register(app) while len(app): app.flush() stderr.seek(0) stderr.truncate() assert debugger._events e = Event() app.fire(e) app.flush() stderr.seek(0) s = stderr.read().strip() assert s == str(e) stderr.seek(0) stderr.truncate() debugger._events = False assert not debugger._events e = Event() app.fire(e) stderr.seek(0) s = stderr.read().strip() assert s == '' stderr.seek(0) stderr.truncate() def test_file(tmpdir): logfile = str(tmpdir.ensure('debug.log')) stderr = open(logfile, 'w+') app = App() debugger = Debugger(file=stderr) debugger.register(app) while len(app): app.flush() stderr.seek(0) stderr.truncate() assert debugger._events e = Event() app.fire(e) app.flush() stderr.seek(0) s = stderr.read().strip() assert s == str(e) stderr.seek(0) stderr.truncate() debugger._events = False assert not debugger._events e = Event() app.fire(e) stderr.seek(0) s = stderr.read().strip() assert s == '' stderr.seek(0) stderr.truncate() def test_filename(tmpdir): if '__pypy__' in sys.modules: pytest.skip('Broken on pypy') logfile = str(tmpdir.ensure('debug.log')) stderr = open(logfile, 'r+') app = App() debugger = Debugger(file=logfile) debugger.register(app) while len(app): app.flush() stderr.seek(0) stderr.truncate() assert debugger._events e = Event() app.fire(e) app.flush() stderr.seek(0) s = stderr.read().strip() assert s == str(e) stderr.seek(0) stderr.truncate() debugger._events = False assert not debugger._events e = Event() app.fire(e) stderr.seek(0) s = stderr.read().strip() assert s == '' stderr.seek(0) stderr.truncate() def test_exceptions(): app = App() stderr = StringIO() debugger = Debugger(file=stderr) debugger.register(app) while len(app): app.flush() stderr.seek(0) stderr.truncate() assert debugger._events assert debugger._errors e = test(raiseException=True) app.fire(e) app.flush() stderr.seek(0) s = stderr.read().strip() assert s == str(e) stderr.seek(0) stderr.truncate() app.flush() stderr.seek(0) s = stderr.read().strip() assert s.startswith(' (') circuits-3.2.3/tests/core/test_dynamic_handlers.py000066400000000000000000000015271460335514400223650ustar00rootroot00000000000000#!/usr/bin/env python import pytest from circuits import Event, Manager, handler class foo(Event): """foo Event""" @handler('foo') def on_foo(self): return 'Hello World!' def test_addHandler(): m = Manager() m.start() m.addHandler(on_foo) waiter = pytest.WaitEvent(m, 'foo') x = m.fire(foo()) waiter.wait() s = x.value assert s == 'Hello World!' m.stop() def test_removeHandler(): m = Manager() m.start() method = m.addHandler(on_foo) waiter = pytest.WaitEvent(m, 'foo') x = m.fire(foo()) waiter.wait() s = x.value assert s == 'Hello World!' m.removeHandler(method) waiter = pytest.WaitEvent(m, 'foo') x = m.fire(foo()) waiter.wait() assert x.value is None assert on_foo not in dir(m) assert 'foo' not in m._handlers m.stop() circuits-3.2.3/tests/core/test_errors.py000066400000000000000000000022141460335514400203670ustar00rootroot00000000000000#!/usr/bin/env python import pytest from circuits import Component, Event class test(Event): """test Event""" class App(Component): def __init__(self): super().__init__() self.etype = None self.evalue = None self.etraceback = None self.handler = None self.fevent = None def test(self): return x # noqa: F821 def exception(self, etype, evalue, etraceback, handler=None, fevent=None): self.etype = etype self.evalue = evalue self.etraceback = etraceback self.handler = handler self.fevent = fevent def reraise(e): raise e @pytest.fixture() def app(request, manager, watcher): app = App().register(manager) watcher.wait('registered') def finalizer(): app.unregister() request.addfinalizer(finalizer) return app def test_main(app, watcher): e = test() app.fire(e) watcher.wait('exception') assert app.etype is NameError pytest.raises(NameError, lambda e: reraise(e), app.evalue) assert isinstance(app.etraceback, list) assert app.handler == app.test assert app.fevent == e circuits-3.2.3/tests/core/test_event.py000066400000000000000000000025041460335514400201760ustar00rootroot00000000000000"""Event Tests""" import pytest from circuits import Component, Event class test(Event): """test Event""" class App(Component): def test(self): return 'Hello World!' def test_repr(): app = App() while len(app): app.flush() e = test() s = repr(e) assert s == '' app.fire(e) s = repr(e) assert s == '' def test_create(): app = App() while len(app): app.flush() e = Event.create('test') s = repr(e) assert s == '' app.fire(e) s = repr(e) assert s == '' def test_getitem(): app = App() while len(app): app.flush() e = test(1, 2, 3, foo='bar') assert e[0] == 1 assert e['foo'] == 'bar' def f(e, k): return e[k] pytest.raises(TypeError, f, e, None) def test_setitem(): app = App() while len(app): app.flush() e = test(1, 2, 3, foo='bar') assert e[0] == 1 assert e['foo'] == 'bar' e[0] = 0 e['foo'] = 'Hello' def f(e, k, v): e[k] = v pytest.raises(TypeError, f, e, None, None) assert e[0] == 0 assert e['foo'] == 'Hello' def test_subclass_looses_properties(): class hello(Event): success = True e = hello().child('success') assert e.success is False circuits-3.2.3/tests/core/test_event_priority.py000066400000000000000000000012651460335514400221420ustar00rootroot00000000000000#!/usr/bin/env python from circuits import Component, Event class foo(Event): """foo Event""" class done(Event): """done Event""" class App(Component): def init(self): self.results = [] def foo(self, value): self.results.append(value) def done(self): self.stop() def test1(): app = App() # Normal Order [app.fire(foo(1)), app.fire(foo(2))] # noqa: B018 app.fire(done()) app.run() assert app.results == [1, 2] def test2(): app = App() # Priority Order [app.fire(foo(1), priority=2), app.fire(foo(2), priority=0)] # noqa: B018 app.fire(done()) app.run() assert app.results == [2, 1] circuits-3.2.3/tests/core/test_exit_code.py000066400000000000000000000011471460335514400210220ustar00rootroot00000000000000#!/usr/bin/env python import sys from subprocess import PIPE, Popen from . import exitcodeapp def test_ints(tmpdir): for expected_status in range(4): args = [sys.executable, exitcodeapp.__file__, f'{expected_status:d}'] p = Popen(args, env={'PYTHONPATH': ':'.join(sys.path)}) status = p.wait() assert status == expected_status def test_string(tmpdir): args = [sys.executable, exitcodeapp.__file__, 'foobar'] p = Popen(args, env={'PYTHONPATH': ':'.join(sys.path)}, stderr=PIPE) status = p.wait() assert status == 1 assert p.stderr.read() == b'foobar\n' circuits-3.2.3/tests/core/test_feedback.py000066400000000000000000000034051460335514400206020ustar00rootroot00000000000000"""Feedback Channels Tests""" import pytest from circuits import Component, Event, handler class test(Event): """test Event""" success = True failure = True class App(Component): def __init__(self): super().__init__() self.e = None self.error = None self.value = None self.success = False self.failure = False @handler('*') def event(self, event, *args, **kwargs): if kwargs.get('filter', False): event.stop() def test(self, error=False): if error: raise Exception('Hello World!') return 'Hello World!' def test_success(self, e, value): self.e = e self.value = value self.success = True def test_failure(self, e, error): self.e = e self.error = error self.failure = True def reraise(e): raise e def test_success(): app = App() while len(app): app.flush() e = test() value = app.fire(e) while len(app): app.flush() # The Event s = value.value assert s == 'Hello World!' while len(app): app.flush() assert app.e == e assert app.success assert app.e.value == value assert app.value == value.value def test_failure(): app = App() while len(app): app.flush() e = test(error=True) x = app.fire(e) while len(app): app.flush() # The Event pytest.raises(Exception, lambda x: reraise(x[1]), x.value) while len(app): app.flush() assert app.e == e etype, evalue, _etraceback = app.error pytest.raises(Exception, lambda x: reraise(x), evalue) assert etype is Exception assert app.failure assert not app.success assert app.e.value == x circuits-3.2.3/tests/core/test_filters.py000066400000000000000000000007551460335514400205330ustar00rootroot00000000000000#!/usr/bin/env python from circuits import BaseComponent, Event, handler class test(Event): """test Event""" class App(BaseComponent): @handler('test') def _on_test(self, event): try: return 'Hello World!' finally: event.stop() def _on_test2(self): pass # Never reached def test_main(): app = App() while len(app): app.flush() x = app.fire(test()) app.flush() assert x.value == 'Hello World!' circuits-3.2.3/tests/core/test_generate_events.py000077500000000000000000000020321460335514400222320ustar00rootroot00000000000000#!/usr/bin/env python import pytest from circuits import Component, Event class App(Component): def init(self): self._ready = False self._done = False self._counter = 0 def registered(self, component, manager): if component is self: self.fire(Event.create('ready')) def generate_events(self, event): if not self._ready or self._done: return if self._counter < 10: self.fire(Event.create('hello')) else: self.fire(Event.create('done')) event.reduce_time_left(0) def done(self): self._done = True def hello(self): self._counter += 1 def ready(self): self._ready = True @pytest.fixture() def app(request, manager, watcher): app = App().register(manager) def finalizer(): app.unregister() request.addfinalizer(finalizer) assert watcher.wait('ready') return app def test(manager, watcher, app): watcher.wait('done') assert app._counter == 10 circuits-3.2.3/tests/core/test_generator_value.py000066400000000000000000000013371460335514400222420ustar00rootroot00000000000000#!/usr/bin/env python from circuits import Component, Event class test(Event): """test Event""" class hello(Event): """hello Event""" class App(Component): def test(self): def f(): while True: yield 'Hello' return f() def hello(self): yield 'Hello ' yield 'World!' def test_return_generator(): app = App() while len(app): app.flush() v = app.fire(test()) app.tick() app.tick() x = v.value assert x == 'Hello' def test_yield(): app = App() while len(app): app.flush() v = app.fire(hello()) app.tick() app.tick() app.tick() x = v.value assert x == ['Hello ', 'World!'] circuits-3.2.3/tests/core/test_globals.py000066400000000000000000000022321460335514400204760ustar00rootroot00000000000000#!/usr/bin/env python from circuits import Component, Event, handler class foo(Event): """foo Event""" class test(Event): """test Event""" class A(Component): channel = 'a' def test(self): return 'Hello World!' @handler(priority=1.0) def _on_event(self, event, *args, **kwargs): return 'Foo' class B(Component): @handler(priority=10.0, channel='*') def _on_channel(self, event, *args, **kwargs): return 'Bar' def test_main(): app = A() + B() while len(app): app.flush() x = app.fire(test(), 'a') while len(app): app.flush() assert x.value[0] == 'Bar' assert x.value[1] == 'Foo' assert x.value[2] == 'Hello World!' def test_event(): app = A() + B() while len(app): app.flush() e = test() x = app.fire(e) while len(app): app.flush() assert x.value[0] == 'Bar' assert x.value[1] == 'Foo' assert x.value[2] == 'Hello World!' def test_channel(): app = A() + B() while len(app): app.flush() e = foo() x = app.fire(e, 'b') while len(app): app.flush() assert x.value == 'Bar' circuits-3.2.3/tests/core/test_imports.py000066400000000000000000000003701460335514400205510ustar00rootroot00000000000000#!/usr/bin/env python from circuits.core.components import BaseComponent def test(): try: from circuits.core.pollers import BasePoller assert issubclass(BasePoller, BaseComponent) except ImportError: assert False circuits-3.2.3/tests/core/test_inheritence.py000066400000000000000000000014121460335514400213470ustar00rootroot00000000000000#!/usr/bin/env python import pytest from circuits import Component, Event, handler class test(Event): """test Event""" class Base(Component): def test(self): return 'Hello World!' class App1(Base): @handler('test', priority=-1) def test(self): return 'Foobar' class App2(Base): @handler('test', override=True) def test(self): return 'Foobar' def test_inheritence(): app = App1() app.start() x = app.fire(test()) assert pytest.wait_for(x, 'result') v = x.value assert v == ['Hello World!', 'Foobar'] app.stop() def test_override(): app = App2() app.start() x = app.fire(test()) assert pytest.wait_for(x, 'result') v = x.value assert v == 'Foobar' app.stop() circuits-3.2.3/tests/core/test_interface_query.py000066400000000000000000000012361460335514400222430ustar00rootroot00000000000000#!/usr/bin/env python """ Test Interface Query Test the capabilities of querying a Component class or instance for it's interface. That is it's event handlers it responds to. """ from circuits import Component class Base(Component): def foo(self): pass class SuperBase(Base): def bar(self): pass def test_handles_base_class(): assert Base.handles('foo') def test_handles_super_base_class(): assert SuperBase.handles('foo', 'bar') def test_handles_base_instance(): base = Base() assert base.handles('foo') def test_handles_super_base_instance(): superbase = SuperBase() assert superbase.handles('foo', 'bar') circuits-3.2.3/tests/core/test_loader.py000066400000000000000000000006361460335514400203270ustar00rootroot00000000000000#!/usr/bin/env python from os.path import dirname import pytest from circuits import Event, Loader, Manager class test(Event): """test Event""" def test_main(): m = Manager() loader = Loader(paths=[dirname(__file__)]).register(m) m.start() loader.load('app') x = m.fire(test()) assert pytest.wait_for(x, 'result') s = x.value assert s == 'Hello World!' m.stop() circuits-3.2.3/tests/core/test_manager_repr.py000066400000000000000000000014371460335514400215230ustar00rootroot00000000000000""" Manager Repr Tests Test Manager's representation string. """ import os from threading import current_thread from time import sleep import pytest from circuits import Component, Manager class App(Component): def test(self, event, *args, **kwargs): pass def test_main(): id = f'{os.getpid()}:{current_thread().name}' m = Manager() assert repr(m) == '' % id app = App() app.register(m) s = repr(m) assert s == '' % id m.start() pytest.wait_for(m, '_running', True) sleep(0.1) s = repr(m) assert s == '' % id m.stop() pytest.wait_for(m, '_Manager__thread', None) s = repr(m) assert s == '' % id circuits-3.2.3/tests/core/test_memory_leaks.py000066400000000000000000000014541460335514400215470ustar00rootroot00000000000000#!/usr/bin/env python import pytest from circuits import Component, Event, handler class call(Event): """call Event""" success = True class hello(Event): """hello Event""" success = True class App(Component): @handler('call') def _on_call(self): x = yield self.call(hello()) yield x.value def hello(self): return 'Hello World!' @pytest.fixture() def app(request, manager, watcher): app = App().register(manager) assert watcher.wait('registered') def finalizer(): app.unregister() request.addfinalizer(finalizer) return app def test_done_handlers_dont_leak(manager, watcher, app): manager.fire(call()) manager.fire(call()) assert watcher.wait('call_success') assert 'hello_done' not in app._handlers circuits-3.2.3/tests/core/test_new_filter.py000066400000000000000000000015301460335514400212110ustar00rootroot00000000000000#!/usr/bin/env python import pytest from circuits import Component, Event class hello(Event): """hello Event""" success = True class App(Component): def hello(self, event, *args, **kwargs): if kwargs.get('stop', False): event.stop() return 'Hello World!' @pytest.fixture() def app(request, manager, watcher): app = (App() + App()).register(manager) watcher.wait('registered') def finalizer(): app.unregister() watcher.wait('unregistered') request.addfinalizer(finalizer) return app def test_normal(app, watcher): x = app.fire(hello()) watcher.wait('hello_success') assert x.value == ['Hello World!', 'Hello World!'] def test_filter(app, watcher): x = app.fire(hello(stop=True)) watcher.wait('hello_success') assert x.value == 'Hello World!' circuits-3.2.3/tests/core/test_priority.py000066400000000000000000000010361460335514400207350ustar00rootroot00000000000000#!/usr/bin/python -i from circuits import Component, Event, Manager, handler class test(Event): """test Event""" class App(Component): @handler('test') def test_0(self): return 0 @handler('test', priority=3) def test_3(self): return 3 @handler('test', priority=2) def test_2(self): return 2 m = Manager() app = App() app.register(m) while len(m): m.flush() def test_main(): v = m.fire(test()) while len(m): m.flush() x = list(v) assert x == [3, 2, 0] circuits-3.2.3/tests/core/test_signals.py000066400000000000000000000024001460335514400205100ustar00rootroot00000000000000#!/usr/bin/env python import os import sys from errno import ESRCH from os import kill, remove from signal import SIGTERM from subprocess import Popen from time import sleep import pytest from . import signalapp def is_running(pid): try: kill(pid, 0) except OSError as error: if error.errno == ESRCH: return False return True def wait(pid, timeout=3): count = timeout while is_running(pid) and count: sleep(1) def test(tmpdir): if os.name != 'posix': pytest.skip('Cannot run test on a non-POSIX platform.') tmpdir.ensure('.pid') tmpdir.ensure('.signal') pidfile = str(tmpdir.join('.pid')) signalfile = str(tmpdir.join('.signal')) args = [sys.executable, signalapp.__file__, pidfile, signalfile] cmd = ' '.join(args) p = Popen(cmd, shell=True, env={'PYTHONPATH': ':'.join(sys.path)}) status = p.wait() assert status == 0 sleep(1) assert os.path.exists(pidfile) assert os.path.isfile(pidfile) f = open(pidfile) pid = int(f.read().strip()) f.close() kill(pid, SIGTERM) wait(pid) with open(signalfile) as fd: signal = fd.read().strip() assert int(signal) == int(SIGTERM) remove(pidfile) remove(signalfile) circuits-3.2.3/tests/core/test_timers.py000066400000000000000000000032401460335514400203560ustar00rootroot00000000000000"""Timers Tests""" from datetime import datetime, timedelta from itertools import starmap from operator import sub from time import time import pytest from circuits import Component, Event, Timer, sleep @pytest.fixture() def app(request, manager, watcher): app = App().register(manager) assert watcher.wait('registered') def finalizer(): app.unregister() assert watcher.wait('unregistered') request.addfinalizer(finalizer) return app class single(Event): """single Event""" complete = True class persistent(Event): """persistent Event""" complete = True class App(Component): def init(self): self.flag = False self.count = 0 self.timestamps = [] def single(self): self.timestamps.append(time()) self.count += 1 self.flag = True def persistent(self, interval): timer = Timer(interval, single(), persist=True) timer.register(self) yield sleep(interval * 10) timer.unregister() def test_single(app, watcher): Timer(0.1, single()).register(app) assert watcher.wait('single_complete') assert app.flag def test_persistent(app, watcher): exponent = -1 interval = 10.0**exponent app.fire(persistent(interval)) assert watcher.wait('persistent_complete') xs = list(map(abs, starmap(sub, zip(app.timestamps, app.timestamps[1:])))) avg = sum(xs) / len(xs) assert round(avg, abs(exponent)) == interval def test_datetime(app, watcher): now = datetime.now() d = now + timedelta(seconds=0.1) Timer(d, single()).register(app) assert watcher.wait('single_complete') assert app.flag circuits-3.2.3/tests/core/test_utils.py000066400000000000000000000031311460335514400202120ustar00rootroot00000000000000#!/usr/bin/env python import sys from types import ModuleType from circuits import Component from circuits.core.utils import findchannel, findroot, findtype FOO = """\ def foo(): return "Hello World!" """ FOOBAR = """\ def foo(); return "Hello World!' """ class Base(Component): """Base""" class App(Base): def hello(self): return 'Hello World!' class A(Component): channel = 'a' class B(Component): channel = 'b' def test_safeimport(tmpdir): from circuits.core.utils import safeimport sys.path.insert(0, str(tmpdir)) foo_path = tmpdir.ensure('foo.py') foo_path.write(FOO) foo = safeimport('foo') assert foo is not None assert type(foo) is ModuleType s = foo.foo() assert s == 'Hello World!' pyc = foo_path.new(ext='pyc') if pyc.check(file=1): pyc.remove(ignore_errors=True) pyd = foo_path.dirpath('__pycache__') if pyd.check(dir=1): pyd.remove(ignore_errors=True) foo_path.write(FOOBAR) foo = safeimport('foo') assert foo is None assert foo not in sys.modules def test_findroot(): app = App() a = A() b = B() b.register(a) a.register(app) while len(app): app.flush() root = findroot(b) assert root == app def test_findchannel(): app = App() (A() + B()).register(app) while len(app): app.flush() a = findchannel(app, 'a') assert a.channel == 'a' def test_findtype(): app = App() (A() + B()).register(app) while len(app): app.flush() a = findtype(app, A) assert isinstance(a, A) circuits-3.2.3/tests/core/test_value.py000066400000000000000000000045511460335514400201750ustar00rootroot00000000000000#!/usr/bin/python -i from types import TracebackType import pytest from circuits import Component, Event, handler class hello(Event): """Hello Event""" class test(Event): """test Event""" class foo(Event): """foo Event""" class values(Event): """values Event""" complete = True class App(Component): def hello(self): return 'Hello World!' def test(self): return self.fire(hello()) def foo(self): raise Exception('ERROR') @handler('hello_value_changed') def _on_hello_value_changed(self, value): self.value = value @handler('test_value_changed') def _on_test_value_changed(self, value): self.value = value @handler('values', priority=2.0) def _value1(self): return 'foo' @handler('values', priority=1.0) def _value2(self): return 'bar' @handler('values', priority=0.0) def _value3(self): return self.fire(hello()) @pytest.fixture() def app(request, simple_manager): return App().register(simple_manager) def test_value(app, simple_manager): x = app.fire(hello()) simple_manager.run() assert 'Hello World!' in x assert x.value == 'Hello World!' def test_nested_value(app, simple_manager): x = app.fire(test()) simple_manager.run() assert x.value == 'Hello World!' assert str(x) == 'Hello World!' def test_value_notify(app, simple_manager): ev = hello() ev.notify = True x = app.fire(ev) simple_manager.run() assert 'Hello World!' in x assert x.value == 'Hello World!' assert app.value is x def test_nested_value_notify(app, simple_manager): ev = test() ev.notify = True x = app.fire(ev) simple_manager.run() assert x.value == 'Hello World!' assert str(x) == 'Hello World!' assert app.value is x def test_error_value(app, simple_manager): x = app.fire(foo()) simple_manager.run() etype, evalue, etraceback = x assert etype is Exception assert str(evalue) == 'ERROR' assert isinstance(etraceback, TracebackType) def test_multiple_values(app, simple_manager): v = app.fire(values()) simple_manager.run() assert isinstance(v.value, list) x = list(v) assert 'foo' in v assert x == ['foo', 'bar', 'Hello World!'] assert x[0] == 'foo' assert x[1] == 'bar' assert x[2] == 'Hello World!' circuits-3.2.3/tests/core/test_worker_process.py000066400000000000000000000020601460335514400221210ustar00rootroot00000000000000"""Workers Tests""" from os import getpid import pytest from circuits import Worker, task @pytest.fixture() def worker(request, manager): worker = Worker().register(manager) def finalizer(): worker.unregister() request.addfinalizer(finalizer) return worker def err(): return x * 2 # noqa: F821 def foo(): x = 0 i = 0 while i < 1000000: x += 1 i += 1 return x def pid(): return f'Hello from {getpid():d}' def add(a, b): return a + b def test_failure(manager, watcher, worker): e = task(err) e.failure = True x = worker.fire(e) assert watcher.wait('task_failure') assert isinstance(x.value[1], Exception) def test_success(manager, watcher, worker): e = task(foo) e.success = True x = worker.fire(e) assert watcher.wait('task_success') assert x.value == 1000000 def test_args(manager, watcher, worker): e = task(add, 1, 2) e.success = True x = worker.fire(e) assert watcher.wait('task_success') assert x.value == 3 circuits-3.2.3/tests/core/test_worker_thread.py000066400000000000000000000015061460335514400217160ustar00rootroot00000000000000"""Workers Tests""" import pytest from circuits import Worker, task task.complete = True @pytest.fixture() def worker(request, manager, watcher): worker = Worker().register(manager) assert watcher.wait('registered') def finalizer(): worker.unregister() assert watcher.wait('unregistered') request.addfinalizer(finalizer) return worker def f(): x = 0 i = 0 while i < 1000000: x += 1 i += 1 return x def add(a, b): return a + b def test(manager, watcher, worker): x = manager.fire(task(f)) assert watcher.wait('task_complete') assert x.result assert x.value == 1000000 def test_args(manager, watcher, worker): x = manager.fire(task(add, 1, 2)) assert watcher.wait('task_complete') assert x.result assert x.value == 3 circuits-3.2.3/tests/io/000077500000000000000000000000001460335514400151225ustar00rootroot00000000000000circuits-3.2.3/tests/io/__init__.py000066400000000000000000000000001460335514400172210ustar00rootroot00000000000000circuits-3.2.3/tests/io/test_file.py000066400000000000000000000041201460335514400174470ustar00rootroot00000000000000#!/usr/bin/env python from io import BytesIO import pytest from circuits import Component from circuits.io import File from circuits.io.events import close, write pytestmark = pytest.mark.skipif(pytest.PLATFORM == 'win32', reason='Unsupported Platform') class FileApp(Component): def init(self, *args, **kwargs): self.file = File(*args, **kwargs).register(self) self.eof = False self.closed = False self.buffer = BytesIO() def read(self, data): self.buffer.write(data) def eof(self): self.eof = True def closed(self): self.closed = True def test_open_fileobj(manager, watcher, tmpdir): filename = str(tmpdir.ensure('helloworld.txt')) with open(filename, 'w') as f: f.write('Hello World!') fileobj = open(filename) app = FileApp(fileobj).register(manager) assert watcher.wait('opened', app.file.channel) assert watcher.wait('eof', app.file.channel) assert app.eof app.fire(close(), app.file.channel) assert watcher.wait('closed', app.file.channel) assert app.closed app.unregister() assert watcher.wait('unregistered') s = app.buffer.getvalue() assert s == b'Hello World!' def test_read_write(manager, watcher, tmpdir): filename = str(tmpdir.ensure('helloworld.txt')) app = FileApp(filename, 'w').register(manager) assert watcher.wait('opened', app.file.channel) app.fire(write(b'Hello World!'), app.file.channel) assert watcher.wait('write', app.file.channel) app.fire(close(), app.file.channel) assert watcher.wait('closed', app.file.channel) assert app.closed app.unregister() assert watcher.wait('unregistered') app = FileApp(filename, 'r').register(manager) assert watcher.wait('opened', app.file.channel) assert watcher.wait('eof', app.file.channel) assert app.eof app.fire(close(), app.file.channel) assert watcher.wait('closed', app.file.channel) assert app.closed app.unregister() assert watcher.wait('unregistered') s = app.buffer.getvalue() assert s == b'Hello World!' circuits-3.2.3/tests/io/test_notify.py000066400000000000000000000077051460335514400200540ustar00rootroot00000000000000#!/usr/bin/env python import os import pytest from circuits import Component, handler try: from circuits.io.notify import Notify except ImportError: pytest.importorskip('pyinotify') class App(Component): def init(self, *args, **kwargs): self.created_status = False @handler('created', channel='notify') def created(self, *args, **kwargs): self.created_status = True class Creator: def __init__(self, app, watcher, tmpdir, timeout=0.5): self.app = app self.watcher = watcher self.tmpdir = tmpdir self.timeout = timeout def create(self, *targets, **kwargs): assert_created = kwargs.get('assert_created', True) target = os.path.join(*targets) self.tmpdir.ensure(target, dir=kwargs.get('dir', False)) self.watcher.wait('created', timeout=self.timeout) assert self.app.created_status == assert_created # Reset for next call self.watcher.clear() self.app.created_status = False @pytest.fixture() def app(manager, watcher): app = App().register(manager) yield app # Unregister application on test end app.unregister() watcher.wait('unregistered') @pytest.fixture() def notify(app, watcher): notify = Notify().register(app) watcher.wait('registered') return notify @pytest.fixture() def creator(app, watcher, tmpdir): return Creator(app, watcher, tmpdir) # TESTS def test_notify_file(notify, tmpdir, creator): # Add a path to the watch notify.add_path(str(tmpdir)) # Test creation and detection of a file creator.create('helloworld.txt') # Remove the path from the watch notify.remove_path(str(tmpdir)) # Test creation and NON detection of a file creator.create('helloworld2.txt', assert_created=False) def test_notify_dir(notify, tmpdir, creator): # Add a path to the watch notify.add_path(str(tmpdir)) # Test creation and detection of a file creator.create('hellodir', dir=True) # Remove the path from the watch notify.remove_path(str(tmpdir)) # Test creation and NON detection of a file creator.create('hellodir2', dir=True, assert_created=False) def test_notify_subdir_recursive(notify, tmpdir, creator): # Add a subdir subdir = 'sub' tmpdir.ensure(subdir, dir=True) # Add a path to the watch notify.add_path(str(tmpdir), recursive=True) # Test creation and detection of a file in subdir creator.create(subdir, 'helloworld.txt', assert_created=True) @pytest.mark.xfail(reason='pyinotify issue #133') def test_notify_subdir_recursive_remove_path(notify, tmpdir, creator): # This is logically the second part of the above test, # but pyinotify fails on rm_watch(...., rec=True) # Add a subdir subdir = 'sub' tmpdir.ensure(subdir, dir=True) # Add a path to the watch notify.add_path(str(tmpdir), recursive=True) # Remove the path from the watch notify.remove_path(str(tmpdir), recursive=True) # Test creation and NON detection of a file in subdir creator.create(subdir, 'helloworld2.txt', assert_created=False) def test_notify_subdir_recursive_auto_add(notify, tmpdir, creator): # Add a path to the watch notify.add_path(str(tmpdir), recursive=True) # Create/detect subdirectory subdir = 'sub' creator.create(subdir, dir=True, assert_created=True) # Create/detect file in subdirectory creator.create(subdir, 'helloworld.txt', assert_created=True) # Skip notify.remove_path() because pyinotify is broken def test_notify_subdir_recursive_no_auto_add(notify, tmpdir, creator): # Add a path to the watch notify.add_path(str(tmpdir), recursive=True, auto_add=False) # Create/detect subdirectory subdir = 'sub' creator.create(subdir, dir=True, assert_created=True) # Create, not detect file in subdirectory creator.create(subdir, 'helloworld.txt', assert_created=False) # Skip notify.remove_path() because pyinotify is broken circuits-3.2.3/tests/io/test_process.py000066400000000000000000000030541460335514400202130ustar00rootroot00000000000000#!/usr/bin/env python import pytest from circuits.io import Process, write if pytest.PLATFORM == 'win32': pytest.skip('Unsupported Platform') def test(manager, watcher): p = Process(['echo', 'Hello World!']).register(manager) assert watcher.wait('registered') p.start() assert watcher.wait('started', p.channel) assert watcher.wait('terminated', p.channel) s = p.stdout.getvalue() assert s == b'Hello World!\n' def test2(manager, watcher, tmpdir): foo = tmpdir.ensure('foo.txt') p = Process([f'cat - > {foo!s:s}'], shell=True).register(manager) assert watcher.wait('registered') p.start() assert watcher.wait('started', p.channel) p.fire(write('Hello World!'), p._stdin) assert watcher.wait('write', p._stdin) p.stop() assert watcher.wait('eof', p._stdout.channel) with foo.open('r') as f: assert f.read() == 'Hello World!' def test_two_procs(manager, watcher): p1 = Process(['echo', '1']).register(manager) p2 = Process('echo 2 ; sleep 1', shell=True).register(manager) p1.start() p2.start() assert watcher.wait('terminated', p1.channel) assert p1._terminated assert not p2._terminated assert not p2._stdout_closed assert not p2._stderr_closed watcher.clear() # Get rid of first terminated() s1 = p1.stdout.getvalue() assert s1 == b'1\n' assert watcher.wait('terminated', p2.channel) assert p2._terminated assert p2._stdout_closed assert p2._stderr_closed s2 = p2.stdout.getvalue() assert s2 == b'2\n' circuits-3.2.3/tests/net/000077500000000000000000000000001460335514400153015ustar00rootroot00000000000000circuits-3.2.3/tests/net/__init__.py000066400000000000000000000000001460335514400174000ustar00rootroot00000000000000circuits-3.2.3/tests/net/cert.pem000066400000000000000000000056611460335514400167510ustar00rootroot00000000000000-----BEGIN RSA PRIVATE KEY----- MIIEowIBAAKCAQEAw56dcICp8bf9GHCIoKKXjnGwRTHh3Ibm6FxuE+b0Pm/zX48d pWEyhw/cGpiTL860Bmpu8K/zEELRPNBU0HMbSqXYX0kODStgXzrsJDy4rPUuzeGN 8fDLH/8/2gIaIqnLoVhkOoF2d4cnTH96j/Q7z2QeCftHLzVovPqmucpxgHPZYZ8M XYlP4MvN0PAskaON+AQxayNUEVGUDEj9w1w7kwR0D70l3AJL+yHHeJpqF4TRsXGF /mi8IGBAaWRXerK2qt6X09IbmCyS+5GTOt9BRuhfRgfkXDLnrJKa1ymSVl3E3y7Y ViXz/QT8UssjYW61E0E9Q0SZh5DkOSnr9a1J9QIDAQABAoIBAAHZwkuKLBFpWC+f AOvkHeLvGGfPLP/VFuIj3ba6fJ0KmhbRV8p0vPGiKMbnopzVK8bTwvDr+TSyLSCS J/tA24U9RGrCWiutuV+tQwakvGqGd0bjV2Bukr1ewM/jLTQevxprp6cWCnTfBnQm 0JYEDXzMyav2gJnlu3PuOx2O2LwKWEqERFWEj2pvitD1LGgw1rN0qjssXtlhuBtr xfxECyhtfa1fyD9Dx9lKK14vJMLq6027DLsFf5qOFmTr8399SnuPwxvldzNzJaVB 9nLQHVNR59e292UsaPAPhKOpH2bZTDy3MJrfzJHe5p59JcU9vSJsOkluaQXP2ivq H2d9hwECgYEA7IYcTv9Tf5joxUDhWcjjaKND3Bpsc3f1Qo9jQuFBlZoW441gZQpr d1W9DUQ86tLRHNs6M4lJAGzs2fLweGgjNesn0+Q+sUDFnpW1sra89ucK81Xp8Ikn v7eKhp8an6daMGzX1dvQGRSEePPiNLvym0Y46XF6P4pN6ArigM5kceUCgYEA07o/ f4V6y72U5AVToKwop6/aEAOqeyCO51BaafAhfiOaHV1smZLRAMwy6E3BB4BKvNOX wEKnu63MMgUsrcFORpcl+O2v3ZOK1oByS4z8PwvzPQYUaQf5xKqFUAQhgLvwI049 sLJSyA0OWYGwq5h5d+BKGVP64vs/sE2Jw6yuNtECgYBjTU73T7VDvfQEVOAH7RKk 7N7huupLdFKxVjgLbT02zRHNCZ8t7Lj/yixsNHkK8eW/or8Fwh63IgQy4Q9azgXy bj8zdAFqM9KEaUB2vsgJNSlgznJAfaUFlc6ABK6N1xpDeH8Jl5b/4KBZk7MmBr6t uEbOo8j6gluBD4jXIVAEjQKBgHy8U0B7kPaLQDZ99ODJzEHOVjftEPjtG4OnUTzs Xa8Epnz6V0q6tis0IiG9/STALkfEmLiKDGuDXrNxXPsY0VbBIXvf/CYcEEWC8tMT wmAaWDjxZgDi1AFLPLMBXAONtVH3fFynEiINnxCYWU8eyyEWoFD/quUihEkHxUvk ZdahAoGBANz6W+VbTAK5iNq81CBSIvCROi7OXObdyVlbb0W5ehB7hWCAelmt0KCt D/ZlSr4IC6ztq7XsgCitP7GROA+yi/7Pi6dWO2hkJbe/iCDgvvaGTNWbZQLccFBJ XIAPPkyssmKgYVGtJfBHLxj7FWB7TMwXmLOuALcyCPJe/HDcFySQ -----END RSA PRIVATE KEY----- -----BEGIN CERTIFICATE----- MIIDoTCCAokCFC9S/3ETWtegvP0F5DrxSUoPaRTgMA0GCSqGSIb3DQEBCwUAMIGM MQswCQYDVQQGEwJBVTETMBEGA1UECAwKUXVlZW5zbGFuZDERMA8GA1UEBwwIQnJp c2JhbmUxFTATBgNVBAoMDFNob3J0Q2lyY3VpdDEUMBIGA1UEAwwLSmFtZXMgTWls bHMxKDAmBgkqhkiG9w0BCQEWGWFkbWluQHNob3J0Y2lyY3VpdC5uZXQuYXUwHhcN MjAxMjE0MjMzMTM2WhcNMzAxMjEyMjMzMTM2WjCBjDELMAkGA1UEBhMCQVUxEzAR BgNVBAgMClF1ZWVuc2xhbmQxETAPBgNVBAcMCEJyaXNiYW5lMRUwEwYDVQQKDAxT aG9ydENpcmN1aXQxFDASBgNVBAMMC0phbWVzIE1pbGxzMSgwJgYJKoZIhvcNAQkB FhlhZG1pbkBzaG9ydGNpcmN1aXQubmV0LmF1MIIBIjANBgkqhkiG9w0BAQEFAAOC AQ8AMIIBCgKCAQEAw56dcICp8bf9GHCIoKKXjnGwRTHh3Ibm6FxuE+b0Pm/zX48d pWEyhw/cGpiTL860Bmpu8K/zEELRPNBU0HMbSqXYX0kODStgXzrsJDy4rPUuzeGN 8fDLH/8/2gIaIqnLoVhkOoF2d4cnTH96j/Q7z2QeCftHLzVovPqmucpxgHPZYZ8M XYlP4MvN0PAskaON+AQxayNUEVGUDEj9w1w7kwR0D70l3AJL+yHHeJpqF4TRsXGF /mi8IGBAaWRXerK2qt6X09IbmCyS+5GTOt9BRuhfRgfkXDLnrJKa1ymSVl3E3y7Y ViXz/QT8UssjYW61E0E9Q0SZh5DkOSnr9a1J9QIDAQABMA0GCSqGSIb3DQEBCwUA A4IBAQAUr7fRHFMy8MSQbYj4PQdu4mIA+beIrEN8+wnMgdONv4o30aetilC55ffN c94TlezlpzX6aHrRJ0jiEOW2PbjVgayMG2um7QICLjqwyrK+18NCwEQxDthX/wXv KFTMHrDo5FSLRLxe2m3wNT38EaVM0NHMwai71ehEnLCYJo9shw/PphXkXONIltuS lGOEu/mvI7PA/2fJUS8U+3Fdr2K0i22c7JfNvr8gu2mkydsNMxDnKnLVq6s8QIz5 lzezCkqIpNfJT8E2KlIsAFQXE6awrAcc7Fcc9/PbwrNOB7x7DVwz7GGb5vSTvrMg dXTYnVO0WJFrlipOjt3DnazbRRtN -----END CERTIFICATE----- circuits-3.2.3/tests/net/client.py000066400000000000000000000014701460335514400171330ustar00rootroot00000000000000from circuits import Component class Client(Component): channel = 'client' def __init__(self, channel=channel): super().__init__(channel=channel) self.data = '' self.error = None self.ready = False self.closed = False self.connected = False self.disconnected = False def ready(self, *args): self.ready = True def error(self, error): self.error = error def connected(self, host, port): self.connected = True def disconnect(self, *args): return def disconnected(self): self.disconnected = True def closed(self): self.closed = True def read(self, *args): if len(args) == 2: _, data = args else: data = args[0] self.data = data circuits-3.2.3/tests/net/server.py000066400000000000000000000015231460335514400171620ustar00rootroot00000000000000from circuits import Component from circuits.net.events import write class Server(Component): channel = 'server' def init(self): self.data = '' self.host = None self.port = None self.client = None self.ready = False self.closed = False self.connected = False self.disconnected = False def ready(self, server, bind): self.ready = True self.host, self.port = bind def close(self): return def closed(self): self.closed = True def connect(self, sock, *args): self.connected = True self.client = args self.fire(write(sock, b'Ready')) def disconnect(self, sock): self.client = None self.disconnected = True def read(self, sock, data): self.data = data return data circuits-3.2.3/tests/net/test_client.py000066400000000000000000000016241460335514400201730ustar00rootroot00000000000000#!/usr/bin/env python from socket import gaierror def test_client_bind_int(): from circuits.net.sockets import Client class TestClient(Client): def _create_socket(self): return None client = TestClient(1234) assert client._bind[1] == 1234 def test_client_bind_int_gaierror(monkeypatch): from circuits.net import sockets def broken_gethostname(): raise gaierror() monkeypatch.setattr(sockets, 'gethostname', broken_gethostname) class TestClient(sockets.Client): def _create_socket(self): return None client = TestClient(1234) assert client._bind == ('0.0.0.0', 1234) def test_client_bind_str(): from circuits.net.sockets import Client class TestClient(Client): def _create_socket(self): return None client = TestClient('0.0.0.0:1234') assert client._bind == ('0.0.0.0', 1234) circuits-3.2.3/tests/net/test_pipe.py000066400000000000000000000020421460335514400176450ustar00rootroot00000000000000#!/usr/bin/env python import pytest from circuits import Manager from circuits.core.pollers import Select from circuits.net.events import close, write from circuits.net.sockets import Pipe from .client import Client pytestmark = pytest.mark.skipif(pytest.PLATFORM == 'win32', reason='Unsupported Platform') def pytest_generate_tests(metafunc): metafunc.parametrize('Poller', [Select]) def test_pipe(Poller): m = Manager() + Poller() a, b = Pipe('a', 'b') a.register(m) b.register(m) a = Client(channel=a.channel).register(m) b = Client(channel=b.channel).register(m) m.start() try: assert pytest.wait_for(a, 'ready') assert pytest.wait_for(b, 'ready') a.fire(write(b'foo')) assert pytest.wait_for(b, 'data', b'foo') b.fire(write(b'foo')) assert pytest.wait_for(a, 'data', b'foo') a.fire(close()) assert pytest.wait_for(a, 'disconnected') b.fire(close()) assert pytest.wait_for(b, 'disconnected') finally: m.stop() circuits-3.2.3/tests/net/test_poller_reuse.py000066400000000000000000000007641460335514400214210ustar00rootroot00000000000000#!/usr/bin/env python from circuits import Manager from circuits.core.pollers import BasePoller, Poller from circuits.core.utils import findtype from circuits.net.sockets import TCPClient, TCPServer def test(): m = Manager() poller = Poller().register(m) TCPServer(0).register(m) TCPClient().register(m) m.start() try: pollers = findtype(m, BasePoller, all=True) assert len(pollers) == 1 assert pollers[0] is poller finally: m.stop() circuits-3.2.3/tests/net/test_server.py000066400000000000000000000014311460335514400202170ustar00rootroot00000000000000from circuits.net.sockets import BUFSIZE from circuits.web.servers import BaseServer class MockClass(BaseServer): pass def test_dynamic_bufsize_in_baseserver(): bufsize = 10000 try: # Assert when we set BUFSIZE ourselves mock = MockClass(bind='0.0.0.0:1234', bufsize=bufsize) # that it will be set to our given value assert bufsize == mock.server._bufsize finally: mock.server.unregister() mock.stop() def test_constant_bufsize_in_baseserver(): try: # Assert when we dont set BUFSIZE ourself mock = MockClass(bind='0.0.0.0:1235') # that it will be set to the constant default value assert mock.server._bufsize == BUFSIZE finally: mock.server.unregister() mock.stop() circuits-3.2.3/tests/net/test_socket_options.py000066400000000000000000000005741460335514400217630ustar00rootroot00000000000000import pytest from circuits.net.sockets import TCPServer try: from socket import SO_REUSEPORT, SOL_SOCKET except ImportError: pytestmark = pytest.mark.skip(reason='Missing SO_REUSEPORT') def test_socket_options_server(): s = TCPServer(('0.0.0.0', 8090), socket_options=[(SOL_SOCKET, SO_REUSEPORT, 1)]) assert s._sock.getsockopt(SOL_SOCKET, SO_REUSEPORT) == 1 circuits-3.2.3/tests/net/test_tcp.py000066400000000000000000000215311460335514400175020ustar00rootroot00000000000000#!/usr/bin/env python import contextlib import os.path import select from socket import AF_INET, AF_INET6, EAI_NODATA, EAI_NONAME, SOCK_STREAM, has_ipv6, socket from ssl import CERT_NONE, PROTOCOL_TLS_CLIENT, SSLContext import pytest from circuits import Debugger, Manager from circuits.core.pollers import EPoll, KQueue, Poll, Select from circuits.net.events import close, connect, write from circuits.net.sockets import TCP6Client, TCP6Server, TCPClient, TCPServer from tests.conftest import WaitEvent from .client import Client from .server import Server CERT_FILE = os.path.join(os.path.dirname(__file__), 'cert.pem') class _TestClient: def __init__(self, ipv6=False): self._sockname = None self.sock = socket( AF_INET6 if ipv6 else AF_INET, SOCK_STREAM, ) ctx = SSLContext(PROTOCOL_TLS_CLIENT) ctx.check_hostname = False ctx.verify_mode = CERT_NONE self.ssock = ctx.wrap_socket(self.sock) @property def sockname(self): return self._sockname def connect(self, host, port): self.ssock.connect_ex((host, port)) self._sockname = self.ssock.getsockname() def send(self, data): self.ssock.send(data) def recv(self, buflen=4069): return self.ssock.recv(buflen) def disconnect(self): with contextlib.suppress(OSError): self.ssock.shutdown(2) with contextlib.suppress(OSError): self.ssock.close() @pytest.fixture() def client(request, ipv6): client = _TestClient(ipv6=ipv6) def finalizer(): client.disconnect() request.addfinalizer(finalizer) return client def wait_host(server): def checker(obj, attr): return all(getattr(obj, a) for a in attr) assert pytest.wait_for(server, ('host', 'port'), checker) def pytest_generate_tests(metafunc): ipv6 = [False] if has_ipv6: ipv6.append(True) for ipv6 in ipv6: poller = [(Select, ipv6)] if hasattr(select, 'poll'): poller.append((Poll, ipv6)) if hasattr(select, 'epoll'): poller.append((EPoll, ipv6)) if hasattr(select, 'kqueue'): poller.append((KQueue, ipv6)) metafunc.parametrize('Poller,ipv6', poller) def test_tcp_basic(Poller, ipv6): m = Manager() + Poller() if ipv6: tcp_server = TCP6Server(('::1', 0)) tcp_client = TCP6Client() else: tcp_server = TCPServer(0) tcp_client = TCPClient() server = Server() + tcp_server client = Client() + tcp_client server.register(m) client.register(m) m.start() try: assert pytest.wait_for(client, 'ready') assert pytest.wait_for(server, 'ready') wait_host(server) client.fire(connect(server.host, server.port)) assert pytest.wait_for(client, 'connected') assert pytest.wait_for(server, 'connected') assert pytest.wait_for(client, 'data', b'Ready') client.fire(write(b'foo')) assert pytest.wait_for(server, 'data', b'foo') assert pytest.wait_for(client, 'data', b'foo') client.fire(close()) assert pytest.wait_for(client, 'disconnected') assert pytest.wait_for(server, 'disconnected') server.fire(close()) assert pytest.wait_for(server, 'closed') finally: m.stop() def test_tcps_basic(manager, watcher, client, Poller, ipv6): poller = Poller().register(manager) if ipv6: tcp_server = TCP6Server(('::1', 0), secure=True, certfile=CERT_FILE) else: tcp_server = TCPServer(0, secure=True, certfile=CERT_FILE) server = Server() + tcp_server server.register(manager) try: watcher.wait('ready', 'server') client.connect(server.host, server.port) assert watcher.wait('connect', 'server') assert client.recv() == b'Ready' client.send(b'foo') assert watcher.wait('read', 'server') assert client.recv() == b'foo' client.disconnect() assert watcher.wait('disconnect', 'server') server.fire(close()) assert watcher.wait('closed', 'server') finally: poller.unregister() server.unregister() def test_tcp_reconnect(Poller, ipv6): # TODO: Apparently this doesn't work on Windows either? # TODO: UPDATE: Apparently Broken on Windows + Python 3.2 # TODO: Need to look into this. Find out why... if pytest.PLATFORM == 'win32' and pytest.PYVER[:2] >= (3, 2): pytest.skip('Broken on Windows on Python 3.2') m = Manager() + Poller() if ipv6: tcp_server = TCP6Server(('::1', 0)) tcp_client = TCP6Client() else: tcp_server = TCPServer(0) tcp_client = TCPClient() server = Server() + tcp_server client = Client() + tcp_client server.register(m) client.register(m) m.start() try: assert pytest.wait_for(client, 'ready') assert pytest.wait_for(server, 'ready') wait_host(server) # 1st connect client.fire(connect(server.host, server.port)) assert pytest.wait_for(client, 'connected') assert pytest.wait_for(server, 'connected') assert pytest.wait_for(client, 'data', b'Ready') client.fire(write(b'foo')) assert pytest.wait_for(server, 'data', b'foo') # disconnect client.fire(close()) assert pytest.wait_for(client, 'disconnected') # 2nd reconnect client.fire(connect(server.host, server.port)) assert pytest.wait_for(client, 'connected') assert pytest.wait_for(server, 'connected') assert pytest.wait_for(client, 'data', b'Ready') client.fire(write(b'foo')) assert pytest.wait_for(server, 'data', b'foo') client.fire(close()) assert pytest.wait_for(client, 'disconnected') assert pytest.wait_for(server, 'disconnected') server.fire(close()) assert pytest.wait_for(server, 'closed') finally: m.stop() def test_tcp_connect_closed_port(Poller, ipv6): if pytest.PLATFORM == 'win32': pytest.skip('Broken on Windows') m = Manager() + Poller() + Debugger() if ipv6: tcp_server = TCP6Server(('::1', 0)) tcp_client = TCP6Client(connect_timeout=1) else: tcp_server = TCPServer(0) tcp_client = TCPClient(connect_timeout=1) server = Server() + tcp_server client = Client() + tcp_client server.register(m) client.register(m) m.start() try: assert pytest.wait_for(client, 'ready') assert pytest.wait_for(server, 'ready') wait_host(server) host, port = server.host, server.port tcp_server._sock.close() # 1st connect client.fire(connect(host, port)) waiter = WaitEvent(m, 'unreachable', channel='client') assert waiter.wait() finally: server.unregister() client.unregister() m.stop() def test_tcp_bind(Poller, ipv6): m = Manager() + Poller() if ipv6: sock = socket(AF_INET6, SOCK_STREAM) sock.bind(('::1', 0)) sock.listen(5) _, _bind_port, _, _ = sock.getsockname() sock.close() server = Server() + TCP6Server(('::1', 0)) client = Client() + TCP6Client() else: sock = socket(AF_INET, SOCK_STREAM) sock.bind(('', 0)) sock.listen(5) _, _bind_port = sock.getsockname() sock.close() server = Server() + TCPServer(0) client = Client() + TCPClient() server.register(m) client.register(m) m.start() try: assert pytest.wait_for(client, 'ready') assert pytest.wait_for(server, 'ready') wait_host(server) client.fire(connect(server.host, server.port)) assert pytest.wait_for(client, 'connected') assert pytest.wait_for(server, 'connected') assert pytest.wait_for(client, 'data', b'Ready') # assert server.client[1] == bind_port client.fire(write(b'foo')) assert pytest.wait_for(server, 'data', b'foo') client.fire(close()) assert pytest.wait_for(client, 'disconnected') assert pytest.wait_for(server, 'disconnected') server.fire(close()) assert pytest.wait_for(server, 'closed') finally: m.stop() def test_tcp_lookup_failure(manager, watcher, Poller, ipv6): poller = Poller().register(manager) tcp_client = TCP6Client() if ipv6 else TCPClient() client = Client() + tcp_client client.register(manager) try: assert watcher.wait('ready', 'client') client.fire(connect('foo.bar.baz', 1234)) assert watcher.wait('error', 'client') if pytest.PLATFORM == 'win32': assert client.error.errno == 11004 else: assert client.error.errno in (EAI_NODATA, EAI_NONAME) finally: poller.unregister() client.unregister() circuits-3.2.3/tests/net/test_udp.py000066400000000000000000000046641460335514400175140ustar00rootroot00000000000000#!/usr/bin/env python import select import socket import pytest from circuits import Manager from circuits.core.pollers import EPoll, KQueue, Poll, Select from circuits.net.events import close, write from circuits.net.sockets import UDP6Client, UDP6Server, UDPClient, UDPServer from .client import Client from .server import Server def wait_host(server): def checker(obj, attr): return all(getattr(obj, a) for a in attr) assert pytest.wait_for(server, ('host', 'port'), checker) def pytest_generate_tests(metafunc): ipv6 = [False] if socket.has_ipv6: ipv6.append(True) for ipv6 in ipv6: poller = [(Select, ipv6)] if hasattr(select, 'poll'): poller.append((Poll, ipv6)) if hasattr(select, 'epoll'): poller.append((EPoll, ipv6)) if hasattr(select, 'kqueue'): poller.append((KQueue, ipv6)) metafunc.parametrize('Poller,ipv6', poller) def test_basic(Poller, ipv6): m = Manager() + Poller() if ipv6: udp_server = UDP6Server(('::1', 0)) udp_client = UDP6Client(('::1', 0), channel='client') else: udp_server = UDPServer(0) udp_client = UDPClient(0, channel='client') server = Server() + udp_server client = Client() + udp_client server.register(m) client.register(m) m.start() try: assert pytest.wait_for(server, 'ready') assert pytest.wait_for(client, 'ready') wait_host(server) client.fire(write((server.host, server.port), b'foo')) assert pytest.wait_for(server, 'data', b'foo') client.fire(close()) assert pytest.wait_for(client, 'closed') server.fire(close()) assert pytest.wait_for(server, 'closed') finally: m.stop() def test_close(Poller, ipv6): m = Manager() + Poller() server = Server() + UDPServer(0) server.register(m) m.start() try: assert pytest.wait_for(server, 'ready') wait_host(server) host, port = server.host, server.port server.fire(close()) assert pytest.wait_for(server, 'disconnected') server.unregister() def test(obj, attr): return attr not in obj.components assert pytest.wait_for(m, server, value=test) server = Server() + UDPServer((host, port)) server.register(m) assert pytest.wait_for(server, 'ready', timeout=30.0) finally: m.stop() circuits-3.2.3/tests/net/test_unix.py000066400000000000000000000032331460335514400176760ustar00rootroot00000000000000#!/usr/bin/env python import os import select import sys import tempfile import pytest from pytest import fixture from circuits import Manager from circuits.core.pollers import EPoll, KQueue, Poll, Select from circuits.net.sockets import UNIXClient, UNIXServer, close, connect, write from .client import Client from .server import Server if sys.platform in ('win32', 'cygwin'): pytest.skip('Test Not Applicable on Windows') @fixture() def tmpfile(request): tmpdir = tempfile.mkdtemp() return os.path.join(tmpdir, 'test.sock') def pytest_generate_tests(metafunc): poller = [Select] if hasattr(select, 'poll'): poller.append(Poll) if hasattr(select, 'epoll'): poller.append(EPoll) if hasattr(select, 'kqueue'): poller.append(KQueue) metafunc.parametrize('Poller', poller) def test_unix(tmpfile, Poller): m = Manager() + Poller() server = Server() + UNIXServer(tmpfile) client = Client() + UNIXClient() server.register(m) client.register(m) m.start() try: assert pytest.wait_for(server, 'ready') assert pytest.wait_for(client, 'ready') client.fire(connect(tmpfile)) assert pytest.wait_for(client, 'connected') assert pytest.wait_for(server, 'connected') assert pytest.wait_for(client, 'data', b'Ready') client.fire(write(b'foo')) assert pytest.wait_for(server, 'data', b'foo') client.fire(close()) assert pytest.wait_for(client, 'disconnected') assert pytest.wait_for(server, 'disconnected') server.fire(close()) assert pytest.wait_for(server, 'closed') finally: m.stop() circuits-3.2.3/tests/node/000077500000000000000000000000001460335514400154405ustar00rootroot00000000000000circuits-3.2.3/tests/node/test_node.py000066400000000000000000000031211460335514400177730ustar00rootroot00000000000000#!/usr/bin/env python from pytest import PLATFORM, fixture, skip from circuits import Component, Event from circuits.net.events import close from circuits.net.sockets import UDPServer from circuits.node import Node, remote if PLATFORM == 'win32': skip('Broken on Windows') class App(Component): _ready = False value = False disconnected = False def foo(self): return 'Hello World!' def ready(self, *args): self._ready = True def disconnect(self, component): self.disconnected = True def remote_value_changed(self, value): self.value = True @fixture() def bind(request, manager, watcher): server = UDPServer(0).register(manager) assert watcher.wait('ready') host, port = server.host, server.port server.fire(close()) assert watcher.wait('closed') server.unregister() assert watcher.wait('unregistered') return host, port @fixture() def app(request, manager, watcher, bind): app = App().register(manager) node = Node().register(app) watcher.wait('ready') child = App() + Node(port=bind[1], server_ip=bind[0]) child.start(process=True) node.add('child', *bind) watcher.wait('connected') def finalizer(): child.stop() request.addfinalizer(finalizer) return app def test_return_value(app, watcher): event = Event.create('foo') event.notify = True remote_event = remote(event, 'child') remote_event.notify = True value = app.fire(remote_event) assert watcher.wait('remote_value_changed') assert value.value == 'Hello World!' circuits-3.2.3/tests/node/test_protocol.py000066400000000000000000000126301460335514400207140ustar00rootroot00000000000000#!/usr/bin/env python import pytest from circuits import Component, Event from circuits.core import Value from circuits.node.protocol import Protocol from circuits.node.utils import dump_event, dump_value pytestmark = pytest.mark.skipif(pytest.PLATFORM == 'win32', reason='Broken on Windows') class return_value(Event): success = True class firewall_block(Event): pass class AppClient(Component): write_data = b'' def return_value(self): return 'Hello server!' def write(self, data): self.write_data = data class AppFirewall(Component): write_data = b'' def fw_receive(self, event, sock): return self.__event_is_allow(event) def fw_send(self, event, sock): return self.__event_is_allow(event) def write(self, data): self.write_data = data def __event_is_allow(self, event): allow = event.name == 'return_value' and 'prohibits_channel' not in event.channels if not allow: self.fire(firewall_block()) return allow class AppServer(Component): write_data = b'' write_sock = None def return_value(self): return 'Hello client!' def write(self, sock, data): self.write_sock = sock self.write_data = data @pytest.fixture() def app_client(request, manager, watcher): app = AppClient() app.register(manager) watcher.wait('registered') app.protocol = Protocol().register(app) watcher.wait('registered') def finalizer(): app.unregister() request.addfinalizer(finalizer) return app @pytest.fixture() def app_firewall(request, manager, watcher): app = AppFirewall() app.register(manager) watcher.wait('registered') app.protocol = Protocol( sock='sock obj', receive_event_firewall=app.fw_receive, send_event_firewall=app.fw_send, ).register(app) watcher.wait('registered') def finalizer(): app.unregister() request.addfinalizer(finalizer) return app @pytest.fixture() def app_server(request, manager, watcher): app = AppServer() app.register(manager) watcher.wait('registered') app.protocol = Protocol(sock='sock obj', server=True).register(app) watcher.wait('registered') def finalizer(): app.unregister() request.addfinalizer(finalizer) return app def test_add_buffer(app_client, watcher): packet = str.encode(dump_event(return_value(), 1)) app_client.protocol.add_buffer(packet) assert watcher.wait('return_value_success') assert watcher.wait('write') value = Value() value.value = 'Hello server!' value.errors = False value.node_call_id = 1 assert app_client.write_data == str.encode(dump_value(value) + '~~~') def test_add_buffer_server(app_server, watcher): packet = str.encode(dump_event(return_value(), 1)) app_server.protocol.add_buffer(packet) assert watcher.wait('return_value_success') assert watcher.wait('write') value = Value() value.value = 'Hello client!' value.errors = False value.node_call_id = 1 assert app_server.write_data == str.encode(dump_value(value) + '~~~') assert app_server.write_sock == 'sock obj' def test_firewall_receive(app_firewall, watcher): # good event packet = str.encode(dump_event(return_value(), 1)) app_firewall.protocol.add_buffer(packet) assert watcher.wait('return_value') # bad name packet = str.encode(dump_event(Event.create('unallow_event'), 1)) app_firewall.protocol.add_buffer(packet) assert watcher.wait('firewall_block') # bad channel event = return_value() event.channels = ('prohibits_channel',) packet = str.encode(dump_event(event, 1)) app_firewall.protocol.add_buffer(packet) assert watcher.wait('firewall_block') def test_firewall_send(app_firewall, watcher): # good event event = return_value() generator = app_firewall.protocol.send(event) next(generator) # exec assert watcher.wait('write') assert app_firewall.write_data == str.encode(dump_event(event, 0) + '~~~') # bad name generator = app_firewall.protocol.send(Event.create('unallow_event')) next(generator) # exec assert watcher.wait('firewall_block') # bad channel event = return_value() event.channels = ('prohibits_channel',) generator = app_firewall.protocol.send(event) next(generator) # exec assert watcher.wait('firewall_block') def test_send(app_client, watcher): event = return_value() generator = app_client.protocol.send(event) next(generator) # exec assert watcher.wait('write') assert app_client.write_data == str.encode(dump_event(event, 0) + '~~~') value = Value() value.value = 'Hello server!' value.errors = False value.node_call_id = 0 app_client.protocol.add_buffer(str.encode(dump_value(value) + '~~~')) assert next(generator).getValue() == value.value def test_send_server(app_server, watcher): event = return_value() generator = app_server.protocol.send(event) next(generator) # exec assert watcher.wait('write') assert app_server.write_data == str.encode(dump_event(event, 0) + '~~~') assert app_server.write_sock == 'sock obj' value = Value() value.value = 'Hello client!' value.errors = False value.node_call_id = 0 app_server.protocol.add_buffer(str.encode(dump_value(value) + '~~~')) assert next(generator).getValue() == value.value circuits-3.2.3/tests/node/test_server.py000066400000000000000000000076351460335514400203720ustar00rootroot00000000000000#!/usr/bin/env python from time import sleep import pytest from circuits import Component, Event from circuits.net.events import close from circuits.net.sockets import UDPServer from circuits.node import Node pytestmark = pytest.mark.skipif(pytest.PLATFORM == 'win32', reason='Broken on Windows') class return_value(Event): success = True class App(Component): def return_value(self, event): print('Hello client!', event.channels) @pytest.fixture() def bind(manager, watcher): server = UDPServer(0).register(manager) assert watcher.wait('ready', channel='server') host, port = server.host, server.port server.fire(close()) assert watcher.wait('closed', channel='server') server.unregister() assert watcher.wait('unregistered', channel='server') return host, port @pytest.fixture() def app(manager, watcher, bind): server = Node(port=bind[1], server_ip=bind[0]) server.register(manager) server.bind = bind assert watcher.wait('registered', channel='node') return server def test_auto_reconnect(app, watcher, manager): # add client client = App().register(manager) node = Node().register(client) chan = node.add('client1', *app.bind, reconnect_delay=1, connect_timeout=1) assert watcher.wait('connected', channel=chan) watcher.clear() # close server app.fire(close(), app.channel) assert watcher.wait('closed', channel=app.channel) watcher.clear() # client gets an unreachable assert watcher.wait('connect', channel=chan) assert watcher.wait('unreachable', channel=chan) watcher.clear() # start a new server node2 = Node(port=app.bind[1], server_ip=app.bind[0]) node2.register(manager) assert watcher.wait('ready', channel=node2.channel) watcher.clear() assert watcher.wait('connected', channel=chan) client.unregister() def test_server_send_all(app, watcher, manager): client1 = App().register(manager) node1 = Node().register(client1) chan = node1.add('client1', *app.bind) assert watcher.wait('connected', channel=chan) client2 = App().register(manager) node2 = Node().register(client2) chan = node2.add('client2', *app.bind) assert watcher.wait('connected', channel=chan) event = return_value() app.server.send_all(event) assert watcher.wait('return_value') client1.unregister() client2.unregister() def test_server_send(app, watcher, manager): client1 = App().register(manager) node1 = Node().register(client1) chan1 = node1.add('client1', *app.bind) assert watcher.wait('connected', channel=chan1) client2 = App().register(manager) node2 = Node().register(client2) chan2 = node2.add('client2', *app.bind) assert watcher.wait('connected', channel=chan2) event = return_value() app.server.send(event, app.server.get_socks()[0], no_result=True) assert watcher.wait('return_value') client1.unregister() client2.unregister() def test_server_send_multicast(app, watcher, manager): client1 = App().register(manager) node1 = Node().register(client1) chan1 = node1.add('client1', *app.bind) assert watcher.wait('connected', channel=chan1) watcher.clear() client2 = App().register(manager) node2 = Node().register(client2) chan2 = node2.add('client2', *app.bind) assert watcher.wait('connected', channel=chan2) watcher.clear() client3 = App().register(manager) node3 = Node().register(client3) chan3 = node3.add('client3', *app.bind) assert watcher.wait('connected', channel=chan3) watcher.clear() event = return_value() app.server.send_to(event, app.server.get_socks()) assert watcher.wait('return_value') for _ in range(3): if watcher.count('return_value') == 3: break sleep(1) assert watcher.count('return_value') == 3 client1.unregister() client2.unregister() client3.unregister() circuits-3.2.3/tests/node/test_utils.py000066400000000000000000000017701460335514400202160ustar00rootroot00000000000000""" test_utils ... """ from circuits import Event from circuits.core import Value from circuits.node.utils import dump_event, dump_value, load_event, load_value class test(Event): """test Event""" def test_events(): event = test(1, 2, 3, foo='bar') event.success = True event.failure = False event.test_meta = 1 id = 1 s = dump_event(event, id) event, id = load_event(s) assert hasattr(event, 'args') assert hasattr(event, 'kwargs') assert hasattr(event, 'success') assert hasattr(event, 'failure') assert hasattr(event, 'channels') assert hasattr(event, 'notify') assert hasattr(event, 'test_meta') def test_values(): event = Event() event.test_meta = 1 value = Value(event=event) value.value = 'foo' value.errors = False value.node_call_id = 1 x, id, errors, meta = load_value(dump_value(value)) assert value.value == x assert id == 1 assert not errors assert meta['test_meta'] == event.test_meta circuits-3.2.3/tests/protocols/000077500000000000000000000000001460335514400165375ustar00rootroot00000000000000circuits-3.2.3/tests/protocols/__init__.py000066400000000000000000000000001460335514400206360ustar00rootroot00000000000000circuits-3.2.3/tests/protocols/test_irc.py000066400000000000000000000137271460335514400207370ustar00rootroot00000000000000#!/usr/bin/env python import pytest from pytest import fixture from circuits import Component, Event, handler from circuits.net.events import read, write from circuits.protocols.irc import ( AWAY, INVITE, IRC, JOIN, KICK, MODE, NAMES, NICK, NOTICE, PART, PASS, PONG, PRIVMSG, QUIT, TOPIC, USER, WHOIS, irc_color_to_ansi, joinprefix, parsemsg, parseprefix, strip, ) class App(Component): def init(self): IRC().register(self) self.data = [] self.events = [] @handler(False) def reset(self): self.data = [] self.events = [] @handler() def _on_event(self, event, *args, **kwargs): self.events.append(event) def request(self, message): self.fire(write(bytes(message))) def write(self, data): self.data.append(data) @fixture() def app(request): app = App() while len(app): app.flush() return app def test_strip(): s = ':\x01\x02test\x02\x01' s = strip(s) assert s == '\x01\x02test\x02\x01' s = ':\x01\x02test\x02\x01' s = strip(s, color=True) assert s == 'test' def test_joinprefix(): nick, ident, host = 'test', 'foo', 'localhost' s = joinprefix(nick, ident, host) assert s == 'test!foo@localhost' def test_parsemsg(): s = b':foo!bar@localhost NICK foobar' source, command, args = parsemsg(s) assert source == ('foo', 'bar', 'localhost') assert command == 'NICK' assert args == ['foobar'] s = b'' source, command, args = parsemsg(s) assert source == (None, None, None) assert command is None assert args == [] def test_parseprefix(): s = 'test!foo@localhost' nick, ident, host = parseprefix(s) assert nick == 'test' assert ident == 'foo' assert host == 'localhost' s = 'test' nick, ident, host = parseprefix(s) assert nick == 'test' assert ident is None assert host is None @pytest.mark.parametrize( 'event,data', [ (PASS('secret'), b'PASS secret\r\n'), ( USER('foo', 'localhost', 'localhost', 'Test Client'), b'USER foo localhost localhost :Test Client\r\n', ), (NICK('test'), b'NICK test\r\n'), pytest.param(PONG('localhost'), b'PONG :localhost\r\n', marks=pytest.mark.xfail), pytest.param(QUIT(), b'QUIT Leaving\r\n', marks=pytest.mark.xfail), (QUIT('Test'), b'QUIT Test\r\n'), (QUIT('Test Message'), b'QUIT :Test Message\r\n'), (JOIN('#test'), b'JOIN #test\r\n'), (JOIN('#test', 'secret'), b'JOIN #test secret\r\n'), (PART('#test'), b'PART #test\r\n'), (PRIVMSG('test', 'Hello'), b'PRIVMSG test Hello\r\n'), (PRIVMSG('test', 'Hello World'), b'PRIVMSG test :Hello World\r\n'), (NOTICE('test', 'Hello'), b'NOTICE test Hello\r\n'), (NOTICE('test', 'Hello World'), b'NOTICE test :Hello World\r\n'), pytest.param(KICK('#test', 'test'), b'KICK #test test :\r\n', marks=pytest.mark.xfail), (KICK('#test', 'test', 'Bye'), b'KICK #test test Bye\r\n'), (KICK('#test', 'test', 'Good Bye!'), b'KICK #test test :Good Bye!\r\n'), (TOPIC('#test', 'Hello World!'), b'TOPIC #test :Hello World!\r\n'), (MODE('+i'), b'MODE +i\r\n'), (MODE('#test', '+o', 'test'), b'MODE #test +o test\r\n'), (INVITE('test', '#test'), b'INVITE test #test\r\n'), pytest.param(NAMES(), b'NAMES\r\n', marks=pytest.mark.xfail), (NAMES('#test'), b'NAMES #test\r\n'), (AWAY('I am away.'), b'AWAY :I am away.\r\n'), pytest.param(WHOIS('somenick'), b'WHOIS :somenick\r\n', marks=pytest.mark.xfail), ], ) def test_commands(event, data): message = event.args[0] assert bytes(message) == data @pytest.mark.parametrize( 'data,event', [ ( b':localhost NOTICE * :*** Looking up your hostname...\r\n', Event.create( 'notice', ('localhost', None, None), '*', '*** Looking up your hostname...', ), ), ], ) def test_responses(app, data, event): app.reset() app.fire(read(data)) while len(app): app.flush() e = app.events[-1] assert event.name == e.name assert event.args == e.args assert event.kwargs == e.kwargs @pytest.mark.parametrize( 'inp,out', [ ( 'hi \x02bold\x02 \x1ditalic\x1d \x1funderline\x1f \x1estrikethrough\x1e', 'hi \x1b[01mbold\x1b[22m \x1b[03mitalic\x1b[23m \x1b[04munderline\x1b[24m \x1b[09mstrikethrough\x1b[29m', ), # noqa: E501 ( '\x0300white\x03 \x0301black\x03 \x0302blue\x03 \x0303green\x03 \x0304red\x03 ', '\x1b[37mwhite\x1b[39;49m \x1b[30mblack\x1b[39;49m \x1b[34mblue\x1b[39;49m \x1b[32mgreen\x1b[39;49m \x1b[31mred\x1b[39;49m ', ), # noqa: E501 ( '\x0305brown\x03 \x0306magenta\x03 \x0307orange\x03 \x0308yellow\x03 ', '\x1b[36mbrown\x1b[39;49m \x1b[35mmagenta\x1b[39;49m \x1b[33morange\x1b[39;49m \x1b[93myellow\x1b[39;49m ', ), # noqa: E501 ( '\x0309lightgreen\x03 \x0310cyan\x03 \x0311lightcyan\x03 \x0312lightblue\x03 ', '\x1b[92mlightgreen\x1b[39;49m \x1b[36mcyan\x1b[39;49m \x1b[96mlightcyan\x1b[39;49m \x1b[94mlightblue\x1b[39;49m ', ), # noqa: E501 ( '\x0313pink\x03 \x0314grey\x03 \x0315lightgrey\x03', '\x1b[95mpink\x1b[39;49m \x1b[90mgrey\x1b[39;49m \x1b[37mlightgrey\x1b[39;49m', ), # noqa: E501 ( '\x0300white\x03 \x0301,01black\x03 \x0301,02blue\x03 \x0301,03green\x03 \x0301,04red\x03 ', '\x1b[37mwhite\x1b[39;49m \x1b[30;40mblack\x1b[39;49m \x1b[30;44mblue\x1b[39;49m \x1b[30;42mgreen\x1b[39;49m \x1b[30;41mred\x1b[39;49m ', ), # noqa: E501 ('\x0f', '\x1b[m'), ('\x0302blue', '\x1b[34mblue\x1b[m'), ], ) def test_ansi_color(inp, out): assert irc_color_to_ansi(inp) == out circuits-3.2.3/tests/protocols/test_line.py000066400000000000000000000023521460335514400211010ustar00rootroot00000000000000#!/usr/bin/env python from collections import defaultdict from circuits import Component, Event from circuits.protocols import Line class read(Event): """read Event""" class App(Component): lines = [] def line(self, line): self.lines.append(line) class AppServer(Component): channel = 'server' lines = [] def line(self, sock, line): self.lines.append((sock, line)) def test(): app = App() Line().register(app) while len(app): app.flush() app.fire(read(b'1\n2\r\n3\n4')) while len(app): app.flush() assert app.lines[0] == b'1' assert app.lines[1] == b'2' assert app.lines[2] == b'3' def test_server(): app = AppServer() buffers = defaultdict(bytes) Line( getBuffer=buffers.__getitem__, updateBuffer=buffers.__setitem__, ).register(app) while len(app): app.flush() app.fire(read(1, b'1\n2\r\n3\n4')) app.fire(read(2, b'1\n2\r\n3\n4')) while len(app): app.flush() assert app.lines[0] == (1, b'1') assert app.lines[1] == (1, b'2') assert app.lines[2] == (1, b'3') assert app.lines[3] == (2, b'1') assert app.lines[4] == (2, b'2') assert app.lines[5] == (2, b'3') circuits-3.2.3/tests/tools/000077500000000000000000000000001460335514400156535ustar00rootroot00000000000000circuits-3.2.3/tests/tools/__init__.py000066400000000000000000000000001460335514400177520ustar00rootroot00000000000000circuits-3.2.3/tests/tools/test_tools.py000066400000000000000000000046751460335514400204400ustar00rootroot00000000000000""" Tools Test Suite Test all functionality of the tools package. """ import pytest from circuits import Component, reprhandler from circuits.tools import findroot, inspect, kill, tryimport class A(Component): def foo(self): print('A!') class B(Component): def foo(self): print('B!') class C(Component): def foo(self): print('C!') class D(Component): def foo(self): print('D!') class E(Component): def foo(self): print('E!') class F(Component): def foo(self): print('F!') def test_kill(): a = A() b = B() c = C() d = D() e = E() f = F() a += b b += c e += f d += e a += d assert a.parent == a assert b.parent == a assert c.parent == b assert not c.components assert b in a.components assert d in a.components assert d.parent == a assert e.parent == d assert f.parent == e assert f in e.components assert e in d.components assert not f.components assert kill(d) is None while len(a): a.flush() assert a.parent == a assert b.parent == a assert c.parent == b assert not c.components assert b in a.components assert d not in a.components assert e not in d.components assert f not in e.components assert d.parent == d assert e.parent == e assert f.parent == f assert not d.components assert not e.components assert not f.components def test_inspect(): a = A() s = inspect(a) assert 'Components: 0' in s assert 'Event Handlers: 2' in s assert 'foo; 1' in s assert '' in s assert 'prepare_unregister_complete; 1' in s assert '][prepare_unregister_complete] (A._on_prepare_unregister_complete)>' in s def test_findroot(): a = A() b = B() c = C() a += b b += c root = findroot(a) assert root == a root = findroot(b) assert root == a root = findroot(c) assert root == a def test_reprhandler(): a = A() s = reprhandler(a.foo) assert s == '' pytest.raises(AttributeError, reprhandler, lambda: None) def test_tryimport(): import os m = tryimport('os') assert m is os def test_tryimport_obj(): from os import path m = tryimport('os', 'path') assert m is path def test_tryimport_fail(): m = tryimport('asdf') assert m is None circuits-3.2.3/tests/web/000077500000000000000000000000001460335514400152705ustar00rootroot00000000000000circuits-3.2.3/tests/web/__init__.py000066400000000000000000000000001460335514400173670ustar00rootroot00000000000000circuits-3.2.3/tests/web/cert.pem000066400000000000000000000056611460335514400167400ustar00rootroot00000000000000-----BEGIN RSA PRIVATE KEY----- MIIEowIBAAKCAQEAw56dcICp8bf9GHCIoKKXjnGwRTHh3Ibm6FxuE+b0Pm/zX48d pWEyhw/cGpiTL860Bmpu8K/zEELRPNBU0HMbSqXYX0kODStgXzrsJDy4rPUuzeGN 8fDLH/8/2gIaIqnLoVhkOoF2d4cnTH96j/Q7z2QeCftHLzVovPqmucpxgHPZYZ8M XYlP4MvN0PAskaON+AQxayNUEVGUDEj9w1w7kwR0D70l3AJL+yHHeJpqF4TRsXGF /mi8IGBAaWRXerK2qt6X09IbmCyS+5GTOt9BRuhfRgfkXDLnrJKa1ymSVl3E3y7Y ViXz/QT8UssjYW61E0E9Q0SZh5DkOSnr9a1J9QIDAQABAoIBAAHZwkuKLBFpWC+f AOvkHeLvGGfPLP/VFuIj3ba6fJ0KmhbRV8p0vPGiKMbnopzVK8bTwvDr+TSyLSCS J/tA24U9RGrCWiutuV+tQwakvGqGd0bjV2Bukr1ewM/jLTQevxprp6cWCnTfBnQm 0JYEDXzMyav2gJnlu3PuOx2O2LwKWEqERFWEj2pvitD1LGgw1rN0qjssXtlhuBtr xfxECyhtfa1fyD9Dx9lKK14vJMLq6027DLsFf5qOFmTr8399SnuPwxvldzNzJaVB 9nLQHVNR59e292UsaPAPhKOpH2bZTDy3MJrfzJHe5p59JcU9vSJsOkluaQXP2ivq H2d9hwECgYEA7IYcTv9Tf5joxUDhWcjjaKND3Bpsc3f1Qo9jQuFBlZoW441gZQpr d1W9DUQ86tLRHNs6M4lJAGzs2fLweGgjNesn0+Q+sUDFnpW1sra89ucK81Xp8Ikn v7eKhp8an6daMGzX1dvQGRSEePPiNLvym0Y46XF6P4pN6ArigM5kceUCgYEA07o/ f4V6y72U5AVToKwop6/aEAOqeyCO51BaafAhfiOaHV1smZLRAMwy6E3BB4BKvNOX wEKnu63MMgUsrcFORpcl+O2v3ZOK1oByS4z8PwvzPQYUaQf5xKqFUAQhgLvwI049 sLJSyA0OWYGwq5h5d+BKGVP64vs/sE2Jw6yuNtECgYBjTU73T7VDvfQEVOAH7RKk 7N7huupLdFKxVjgLbT02zRHNCZ8t7Lj/yixsNHkK8eW/or8Fwh63IgQy4Q9azgXy bj8zdAFqM9KEaUB2vsgJNSlgznJAfaUFlc6ABK6N1xpDeH8Jl5b/4KBZk7MmBr6t uEbOo8j6gluBD4jXIVAEjQKBgHy8U0B7kPaLQDZ99ODJzEHOVjftEPjtG4OnUTzs Xa8Epnz6V0q6tis0IiG9/STALkfEmLiKDGuDXrNxXPsY0VbBIXvf/CYcEEWC8tMT wmAaWDjxZgDi1AFLPLMBXAONtVH3fFynEiINnxCYWU8eyyEWoFD/quUihEkHxUvk ZdahAoGBANz6W+VbTAK5iNq81CBSIvCROi7OXObdyVlbb0W5ehB7hWCAelmt0KCt D/ZlSr4IC6ztq7XsgCitP7GROA+yi/7Pi6dWO2hkJbe/iCDgvvaGTNWbZQLccFBJ XIAPPkyssmKgYVGtJfBHLxj7FWB7TMwXmLOuALcyCPJe/HDcFySQ -----END RSA PRIVATE KEY----- -----BEGIN CERTIFICATE----- MIIDoTCCAokCFC9S/3ETWtegvP0F5DrxSUoPaRTgMA0GCSqGSIb3DQEBCwUAMIGM MQswCQYDVQQGEwJBVTETMBEGA1UECAwKUXVlZW5zbGFuZDERMA8GA1UEBwwIQnJp c2JhbmUxFTATBgNVBAoMDFNob3J0Q2lyY3VpdDEUMBIGA1UEAwwLSmFtZXMgTWls bHMxKDAmBgkqhkiG9w0BCQEWGWFkbWluQHNob3J0Y2lyY3VpdC5uZXQuYXUwHhcN MjAxMjE0MjMzMTM2WhcNMzAxMjEyMjMzMTM2WjCBjDELMAkGA1UEBhMCQVUxEzAR BgNVBAgMClF1ZWVuc2xhbmQxETAPBgNVBAcMCEJyaXNiYW5lMRUwEwYDVQQKDAxT aG9ydENpcmN1aXQxFDASBgNVBAMMC0phbWVzIE1pbGxzMSgwJgYJKoZIhvcNAQkB FhlhZG1pbkBzaG9ydGNpcmN1aXQubmV0LmF1MIIBIjANBgkqhkiG9w0BAQEFAAOC AQ8AMIIBCgKCAQEAw56dcICp8bf9GHCIoKKXjnGwRTHh3Ibm6FxuE+b0Pm/zX48d pWEyhw/cGpiTL860Bmpu8K/zEELRPNBU0HMbSqXYX0kODStgXzrsJDy4rPUuzeGN 8fDLH/8/2gIaIqnLoVhkOoF2d4cnTH96j/Q7z2QeCftHLzVovPqmucpxgHPZYZ8M XYlP4MvN0PAskaON+AQxayNUEVGUDEj9w1w7kwR0D70l3AJL+yHHeJpqF4TRsXGF /mi8IGBAaWRXerK2qt6X09IbmCyS+5GTOt9BRuhfRgfkXDLnrJKa1ymSVl3E3y7Y ViXz/QT8UssjYW61E0E9Q0SZh5DkOSnr9a1J9QIDAQABMA0GCSqGSIb3DQEBCwUA A4IBAQAUr7fRHFMy8MSQbYj4PQdu4mIA+beIrEN8+wnMgdONv4o30aetilC55ffN c94TlezlpzX6aHrRJ0jiEOW2PbjVgayMG2um7QICLjqwyrK+18NCwEQxDthX/wXv KFTMHrDo5FSLRLxe2m3wNT38EaVM0NHMwai71ehEnLCYJo9shw/PphXkXONIltuS lGOEu/mvI7PA/2fJUS8U+3Fdr2K0i22c7JfNvr8gu2mkydsNMxDnKnLVq6s8QIz5 lzezCkqIpNfJT8E2KlIsAFQXE6awrAcc7Fcc9/PbwrNOB7x7DVwz7GGb5vSTvrMg dXTYnVO0WJFrlipOjt3DnazbRRtN -----END CERTIFICATE----- circuits-3.2.3/tests/web/conftest.py000066400000000000000000000034101460335514400174650ustar00rootroot00000000000000"""py.test config""" import os import pytest from circuits import Component, Debugger, handler from circuits.net.sockets import close from circuits.web import Server, Static from circuits.web.client import Client, request DOCROOT = os.path.join(os.path.dirname(__file__), 'static') class WebApp(Component): channel = 'web' def init(self): self.closed = False self.server = Server(0).register(self) Static('/static', DOCROOT, dirlisting=True).register(self) class WebClient(Client): def init(self, *args, **kwargs): self.closed = False def __call__(self, method, path, body=None, headers={}): waiter = pytest.WaitEvent(self, 'response', channel=self.channel) self.fire(request(method, path, body, headers)) assert waiter.wait() return self.response @handler('closed', channel='*', priority=1.0) def _on_closed(self): self.closed = True @pytest.fixture() def webapp(request, manager, watcher): webapp = WebApp().register(manager) assert watcher.wait('ready') if hasattr(request.module, 'application'): from circuits.web.wsgi import Gateway application = request.module.application Gateway({'/': application}).register(webapp) assert watcher.wait('registered') Root = getattr(request.module, 'Root', None) if Root is not None: Root().register(webapp) assert watcher.wait('registered') if request.config.option.verbose: Debugger().register(webapp) assert watcher.wait('registered') def finalizer(): webapp.fire(close()) assert watcher.wait('closed') webapp.unregister() assert watcher.wait('unregistered') request.addfinalizer(finalizer) return webapp circuits-3.2.3/tests/web/helpers.py000066400000000000000000000005561460335514400173120ustar00rootroot00000000000000from http.cookiejar import CookieJar # noqa from urllib.error import HTTPError, URLError # noqa from urllib.parse import quote, urlencode, urljoin, urlparse # noqa from urllib.request import ( # noqa HTTPBasicAuthHandler, HTTPCookieProcessor, HTTPDigestAuthHandler, Request, build_opener, install_opener, urlopen, ) # pylama:skip=1 circuits-3.2.3/tests/web/jsonrpclib.py000066400000000000000000000274671460335514400200270ustar00rootroot00000000000000# a port of xmlrpclib to json.... # # # The JSON-RPC client interface is based on the XML-RPC client # # Copyright (c) 1999-2002 by Secret Labs AB # Copyright (c) 1999-2002 by Fredrik Lundh # Copyright (c) 2006 by Matt Harrison # # By obtaining, using, and/or copying this software and/or its # associated documentation, you agree that you have read, understood, # and will comply with the following terms and conditions: # # Permission to use, copy, modify, and distribute this software and # its associated documentation for any purpose and without fee is # hereby granted, provided that the above copyright notice appears in # all copies, and that both that copyright notice and this permission # notice appear in supporting documentation, and that the name of # Secret Labs AB or the author not be used in advertising or publicity # pertaining to distribution of the software without specific, written # prior permission. # # SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD # TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT- # ABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR # BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY # DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, # WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS # ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE # OF THIS SOFTWARE. # -------------------------------------------------------------------- import base64 import json from http.client import HTTPConnection, HTTPSConnection from urllib.parse import splithost, splittype, splituser, unquote __version__ = '0.0.1' ID = 1 def _gen_id(): global ID ID = ID + 1 return ID # -------------------------------------------------------------------- # Exceptions ## # Base class for all kinds of client-side errors. class Error(Exception): """Base class for client errors.""" def __str__(self): return repr(self) ## # Indicates an HTTP-level protocol error. This is raised by the HTTP # transport layer, if the server returns an error code other than 200 # (OK). # # @param url The target URL. # @param errcode The HTTP error code. # @param errmsg The HTTP error message. # @param headers The HTTP header dictionary. class ProtocolError(Error): """Indicates an HTTP protocol error.""" def __init__(self, url, errcode, errmsg, headers, response): Error.__init__(self) self.url = url self.errcode = errcode self.errmsg = errmsg self.headers = headers self.response = response def __repr__(self): return '' % (self.url, self.errcode, self.errmsg) def getparser(encoding): un = Unmarshaller(encoding) par = Parser(un) return par, un def dumps(params, methodname=None, methodresponse=None, encoding=None, allow_none=0): if methodname: request = {} request['method'] = methodname request['params'] = params request['id'] = _gen_id() return json.dumps(request) return None class Unmarshaller: def __init__(self, encoding): self.data = None self.encoding = encoding def feed(self, data): if self.data is None: self.data = data else: self.data = self.data + data def close(self): # try to convert string to json return json.loads(self.data.decode(self.encoding)) class Parser: def __init__(self, unmarshaller): self._target = unmarshaller self.data = None def feed(self, data): if self.data is None: self.data = data else: self.data = self.data + data def close(self): self._target.feed(self.data) class _Method: # some magic to bind an JSON-RPC method to an RPC server. # supports "nested" methods (e.g. examples.getStateName) def __init__(self, send, name): self.__send = send self.__name = name def __getattr__(self, name): return _Method(self.__send, f'{self.__name}.{name}') def __call__(self, *args): return self.__send(self.__name, args) ## # Standard transport class for JSON-RPC over HTTP. #

    # You can create custom transports by subclassing this method, and # overriding selected methods. class Transport: """Handles an HTTP transaction to an JSON-RPC server.""" # client identifier (may be overridden) user_agent = 'jsonlib.py/%s (by matt harrison)' % __version__ ## # Send a complete request, and parse the response. # # @param host Target host. # @param handler Target PRC handler. # @param request_body JSON-RPC request body. # @param verbose Debugging flag. # @return Parsed response. def request(self, host, handler, request_body, encoding, verbose=0): # issue JSON-RPC request h = self.make_connection(host) if verbose: h.set_debuglevel(1) self.send_request(h, handler, request_body) self.send_user_agent(h) self.send_content(h, request_body) try: errcode, errmsg, headers = h.getreply() r = h.getfile() except AttributeError: r = h.getresponse() errcode = r.status errmsg = r.reason headers = r.getheaders() if errcode != 200: response = r.read() raise ProtocolError( host + handler, errcode, errmsg, headers, response, ) self.verbose = verbose try: sock = h._conn.sock except AttributeError: sock = None return self._parse_response(r, sock, encoding) ## # Create parser. # # @return A 2-tuple containing a parser and a unmarshaller. def getparser(self, encoding): # get parser and unmarshaller return getparser(encoding) ## # Get authorization info from host parameter # Host may be a string, or a (host, x509-dict) tuple; if a string, # it is checked for a "user:pw@host" format, and a "Basic # Authentication" header is added if appropriate. # # @param host Host descriptor (URL or (URL, x509 info) tuple). # @return A 3-tuple containing (actual host, extra headers, # x509 info). The header and x509 fields may be None. def get_host_info(self, host): x509 = {} if isinstance(host, tuple): host, x509 = host auth, host = splituser(host) if auth: auth = base64.encodestring(unquote(auth)) auth = ''.join(auth.split()) # get rid of whitespace extra_headers = [ ('Authorization', 'Basic ' + auth), ] else: extra_headers = None return host, extra_headers, x509 ## # Connect to server. # # @param host Target host. # @return A connection handle. def make_connection(self, host): # create a HTTP connection object from a host descriptor host, _extra_headers, _x509 = self.get_host_info(host) return HTTPConnection(host) ## # Send request header. # # @param connection Connection handle. # @param handler Target RPC handler. # @param request_body JSON-RPC body. def send_request(self, connection, handler, request_body): connection.putrequest('POST', handler) ## # Send host name. # # @param connection Connection handle. # @param host Host name. def send_host(self, connection, host): host, extra_headers, _x509 = self.get_host_info(host) connection.putheader('Host', host) if extra_headers: if isinstance(extra_headers, dict): extra_headers = list(extra_headers.items()) for key, value in extra_headers: connection.putheader(key, value) ## # Send user-agent identifier. # # @param connection Connection handle. def send_user_agent(self, connection): connection.putheader('User-Agent', self.user_agent) ## # Send request body. # # @param connection Connection handle. # @param request_body JSON-RPC request body. def send_content(self, connection, request_body): connection.putheader('Content-Type', 'text/xml') connection.putheader('Content-Length', str(len(request_body))) connection.endheaders() if request_body: connection.send(request_body) ## # Parse response. # # @param file Stream. # @return Response tuple and target method. def parse_response(self, file): # compatibility interface return self._parse_response(file, None) ## # Parse response (alternate interface). This is similar to the # parse_response method, but also provides direct access to the # underlying socket object (where available). # # @param file Stream. # @param sock Socket handle (or None, if the socket object # could not be accessed). # @return Response tuple and target method. def _parse_response(self, file, sock, encoding): # read response from input file/socket, and parse it p, u = self.getparser(encoding) while 1: response = sock.recv(1024) if sock else file.read(1024) if not response: break if self.verbose: print('body:', repr(response)) p.feed(response) file.close() p.close() return u.close() ## # Standard transport class for JSON-RPC over HTTPS. class SafeTransport(Transport): """Handles an HTTPS transaction to an JSON-RPC server.""" # FIXME: mostly untested def make_connection(self, host): # create a HTTPS connection object from a host descriptor # host may be a string, or a (host, x509-dict) tuple host, _extra_headers, x509 = self.get_host_info(host) try: HTTPS = HTTPSConnection except AttributeError: raise NotImplementedError( "your version of httplib doesn't support HTTPS", ) else: return HTTPS(host, None, **(x509 or {})) class ServerProxy: def __init__(self, uri, transport=None, encoding=None, verbose=None, allow_none=0): utype, uri = splittype(uri) if utype not in ('http', 'https'): raise OSError('Unsupported JSONRPC protocol') self.__host, self.__handler = splithost(uri) if not self.__handler: self.__handler = '/RPC2' if transport is None: transport = SafeTransport() if utype == 'https' else Transport() self.__transport = transport self.__encoding = encoding self.__verbose = verbose self.__allow_none = allow_none def __request(self, methodname, params): """Call a method on the remote server""" request = dumps(params, methodname, encoding=self.__encoding, allow_none=self.__allow_none) response = self.__transport.request( self.__host, self.__handler, request.encode(self.__encoding), self.__encoding, verbose=self.__verbose, ) if len(response) == 1: response = response[0] return response def __repr__(self): return '' % (self.__host, self.__handler) __str__ = __repr__ def __getattr__(self, name): # dispatch return _Method(self.__request, name) # note: to call a remote object with an non-standard name, use # result getattr(server, "strange-python-name")(args) if __name__ == '__main__': s = ServerProxy('http://localhost:8080/foo/', verbose=1) c = s.echo('foo bar') print(c) d = s.bad('other') print(d) e = s.echo('foo bar', 'baz') print(e) f = s.echo(5) print(f) circuits-3.2.3/tests/web/multipartform.py000066400000000000000000000037261460335514400205570ustar00rootroot00000000000000import itertools from email.generator import _make_boundary from mimetypes import guess_type class MultiPartForm(dict): def __init__(self): self.files = [] self.boundary = _make_boundary() def get_content_type(self): return 'multipart/form-data; boundary="%s"' % self.boundary def add_file(self, fieldname, filename, fd, mimetype=None): body = fd.read() if mimetype is None: mimetype = guess_type(filename)[0] or 'application/octet-stream' self.files.append((fieldname, filename, mimetype, body)) def bytes(self): parts = [] part_boundary = bytearray('--%s' % self.boundary, 'ascii') # Add the form fields parts.extend( [ part_boundary, bytearray( 'Content-Disposition: form-data; name="%s"' % k, 'ascii', ), b'', v if isinstance(v, bytes) else bytearray(v, 'ascii'), ] for k, v in list(self.items()) ) # Add the files to upload parts.extend( [ part_boundary, bytearray( 'Content-Disposition: form-data; name="%s"; filename="%s"' % (fieldname, filename), 'ascii', ), bytearray('Content-Type: %s' % content_type, 'ascii'), bytearray(), body if isinstance(body, bytes) else bytearray(body, 'ascii'), ] for fieldname, filename, content_type, body in self.files ) # Flatten the list and add closing boundary marker, # then return CR+LF separated data flattened = list(itertools.chain(*parts)) flattened.append(bytearray('--%s--' % self.boundary, 'ascii')) res = bytearray() for item in flattened: res += item res += bytearray('\r\n', 'ascii') return res circuits-3.2.3/tests/web/static/000077500000000000000000000000001460335514400165575ustar00rootroot00000000000000circuits-3.2.3/tests/web/static/#foobar.txt000066400000000000000000000000151460335514400206270ustar00rootroot00000000000000Hello World! circuits-3.2.3/tests/web/static/helloworld.txt000066400000000000000000000000151460335514400214670ustar00rootroot00000000000000Hello World! circuits-3.2.3/tests/web/static/largefile.txt000066400000000000000000000313071460335514400212560ustar00rootroot00000000000000Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World!circuits-3.2.3/tests/web/static/test.css000066400000000000000000000000111460335514400202400ustar00rootroot00000000000000body { } circuits-3.2.3/tests/web/static/unicode.txt000066400000000000000000000333441460335514400207550ustar00rootroot00000000000000 UTF-8 encoded sample plain-text file ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ Markus Kuhn [ˈmaʳkʊs kuːn] — 2002-07-25 The ASCII compatible UTF-8 encoding used in this plain-text file is defined in Unicode, ISO 10646-1, and RFC 2279. Using Unicode/UTF-8, you can write in emails and source code things such as Mathematics and sciences: ∮ E⋅da = Q, n → ∞, ∑ f(i) = ∏ g(i), ⎧⎡⎛┌─────┐⎞⎤⎫ ⎪⎢⎜│a²+b³ ⎟⎥⎪ ∀x∈ℝ: ⌈x⌉ = −⌊−x⌋, α ∧ ¬β = ¬(¬α ∨ β), ⎪⎢⎜│───── ⎟⎥⎪ ⎪⎢⎜⎷ c₈ ⎟⎥⎪ ℕ ⊆ ℕ₀ ⊂ ℤ ⊂ ℚ ⊂ ℝ ⊂ ℂ, ⎨⎢⎜ ⎟⎥⎬ ⎪⎢⎜ ∞ ⎟⎥⎪ ⊥ < a ≠ b ≡ c ≤ d ≪ ⊤ ⇒ (⟦A⟧ ⇔ ⟪B⟫), ⎪⎢⎜ ⎲ ⎟⎥⎪ ⎪⎢⎜ ⎳aⁱ-bⁱ⎟⎥⎪ 2H₂ + O₂ ⇌ 2H₂O, R = 4.7 kΩ, ⌀ 200 mm ⎩⎣⎝i=1 ⎠⎦⎭ Linguistics and dictionaries: ði ıntəˈnæʃənəl fəˈnɛtık əsoʊsiˈeıʃn Y [ˈʏpsilɔn], Yen [jɛn], Yoga [ˈjoːgɑ] APL: ((V⍳V)=⍳⍴V)/V←,V ⌷←⍳→⍴∆∇⊃‾⍎⍕⌈ Nicer typography in plain text files: ╔══════════════════════════════════════════╗ ║ ║ ║ • ‘single’ and “double” quotes ║ ║ ║ ║ • Curly apostrophes: “We’ve been here” ║ ║ ║ ║ • Latin-1 apostrophe and accents: '´` ║ ║ ║ ║ • ‚deutsche‘ „Anführungszeichen“ ║ ║ ║ ║ • †, ‡, ‰, •, 3–4, —, −5/+5, ™, … ║ ║ ║ ║ • ASCII safety test: 1lI|, 0OD, 8B ║ ║ ╭─────────╮ ║ ║ • the euro symbol: │ 14.95 € │ ║ ║ ╰─────────╯ ║ ╚══════════════════════════════════════════╝ Combining characters: STARGΛ̊TE SG-1, a = v̇ = r̈, a⃑ ⊥ b⃑ Greek (in Polytonic): The Greek anthem: Σὲ γνωρίζω ἀπὸ τὴν κόψη τοῦ σπαθιοῦ τὴν τρομερή, σὲ γνωρίζω ἀπὸ τὴν ὄψη ποὺ μὲ βία μετράει τὴ γῆ. ᾿Απ᾿ τὰ κόκκαλα βγαλμένη τῶν ῾Ελλήνων τὰ ἱερά καὶ σὰν πρῶτα ἀνδρειωμένη χαῖρε, ὦ χαῖρε, ᾿Ελευθεριά! From a speech of Demosthenes in the 4th century BC: Οὐχὶ ταὐτὰ παρίσταταί μοι γιγνώσκειν, ὦ ἄνδρες ᾿Αθηναῖοι, ὅταν τ᾿ εἰς τὰ πράγματα ἀποβλέψω καὶ ὅταν πρὸς τοὺς λόγους οὓς ἀκούω· τοὺς μὲν γὰρ λόγους περὶ τοῦ τιμωρήσασθαι Φίλιππον ὁρῶ γιγνομένους, τὰ δὲ πράγματ᾿ εἰς τοῦτο προήκοντα, ὥσθ᾿ ὅπως μὴ πεισόμεθ᾿ αὐτοὶ πρότερον κακῶς σκέψασθαι δέον. οὐδέν οὖν ἄλλο μοι δοκοῦσιν οἱ τὰ τοιαῦτα λέγοντες ἢ τὴν ὑπόθεσιν, περὶ ἧς βουλεύεσθαι, οὐχὶ τὴν οὖσαν παριστάντες ὑμῖν ἁμαρτάνειν. ἐγὼ δέ, ὅτι μέν ποτ᾿ ἐξῆν τῇ πόλει καὶ τὰ αὑτῆς ἔχειν ἀσφαλῶς καὶ Φίλιππον τιμωρήσασθαι, καὶ μάλ᾿ ἀκριβῶς οἶδα· ἐπ᾿ ἐμοῦ γάρ, οὐ πάλαι γέγονεν ταῦτ᾿ ἀμφότερα· νῦν μέντοι πέπεισμαι τοῦθ᾿ ἱκανὸν προλαβεῖν ἡμῖν εἶναι τὴν πρώτην, ὅπως τοὺς συμμάχους σώσομεν. ἐὰν γὰρ τοῦτο βεβαίως ὑπάρξῃ, τότε καὶ περὶ τοῦ τίνα τιμωρήσεταί τις καὶ ὃν τρόπον ἐξέσται σκοπεῖν· πρὶν δὲ τὴν ἀρχὴν ὀρθῶς ὑποθέσθαι, μάταιον ἡγοῦμαι περὶ τῆς τελευτῆς ὁντινοῦν ποιεῖσθαι λόγον. Δημοσθένους, Γ´ ᾿Ολυνθιακὸς Georgian: From a Unicode conference invitation: გთხოვთ ახლავე გაიაროთ რეგისტრაცია Unicode-ის მეათე საერთაშორისო კონფერენციაზე დასასწრებად, რომელიც გაიმართება 10-12 მარტს, ქ. მაინცში, გერმანიაში. კონფერენცია შეჰკრებს ერთად მსოფლიოს ექსპერტებს ისეთ დარგებში როგორიცაა ინტერნეტი და Unicode-ი, ინტერნაციონალიზაცია და ლოკალიზაცია, Unicode-ის გამოყენება ოპერაციულ სისტემებსა, და გამოყენებით პროგრამებში, შრიფტებში, ტექსტების დამუშავებასა და მრავალენოვან კომპიუტერულ სისტემებში. Russian: From a Unicode conference invitation: Зарегистрируйтесь сейчас на Десятую Международную Конференцию по Unicode, которая состоится 10-12 марта 1997 года в Майнце в Германии. Конференция соберет широкий круг экспертов по вопросам глобального Интернета и Unicode, локализации и интернационализации, воплощению и применению Unicode в различных операционных системах и программных приложениях, шрифтах, верстке и многоязычных компьютерных системах. Thai (UCS Level 2): Excerpt from a poetry on The Romance of The Three Kingdoms (a Chinese classic 'San Gua'): [----------------------------|------------------------] ๏ แผ่นดินฮั่นเสื่อมโทรมแสนสังเวช พระปกเกศกองบู๊กู้ขึ้นใหม่ สิบสองกษัตริย์ก่อนหน้าแลถัดไป สององค์ไซร้โง่เขลาเบาปัญญา ทรงนับถือขันทีเป็นที่พึ่ง บ้านเมืองจึงวิปริตเป็นนักหนา โฮจิ๋นเรียกทัพทั่วหัวเมืองมา หมายจะฆ่ามดชั่วตัวสำคัญ เหมือนขับไสไล่เสือจากเคหา รับหมาป่าเข้ามาเลยอาสัญ ฝ่ายอ้องอุ้นยุแยกให้แตกกัน ใช้สาวนั้นเป็นชนวนชื่นชวนใจ พลันลิฉุยกุยกีกลับก่อเหตุ ช่างอาเพศจริงหนาฟ้าร้องไห้ ต้องรบราฆ่าฟันจนบรรลัย ฤๅหาใครค้ำชูกู้บรรลังก์ ฯ (The above is a two-column text. If combining characters are handled correctly, the lines of the second column should be aligned with the | character above.) Ethiopian: Proverbs in the Amharic language: ሰማይ አይታረስ ንጉሥ አይከሰስ። ብላ ካለኝ እንደአባቴ በቆመጠኝ። ጌጥ ያለቤቱ ቁምጥና ነው። ደሀ በሕልሙ ቅቤ ባይጠጣ ንጣት በገደለው። የአፍ ወለምታ በቅቤ አይታሽም። አይጥ በበላ ዳዋ ተመታ። ሲተረጉሙ ይደረግሙ። ቀስ በቀስ፥ ዕንቁላል በእግሩ ይሄዳል። ድር ቢያብር አንበሳ ያስር። ሰው እንደቤቱ እንጅ እንደ ጉረቤቱ አይተዳደርም። እግዜር የከፈተውን ጉሮሮ ሳይዘጋው አይድርም። የጎረቤት ሌባ፥ ቢያዩት ይስቅ ባያዩት ያጠልቅ። ሥራ ከመፍታት ልጄን ላፋታት። ዓባይ ማደሪያ የለው፥ ግንድ ይዞ ይዞራል። የእስላም አገሩ መካ የአሞራ አገሩ ዋርካ። ተንጋሎ ቢተፉ ተመልሶ ባፉ። ወዳጅህ ማር ቢሆን ጨርስህ አትላሰው። እግርህን በፍራሽህ ልክ ዘርጋ። Runes: ᚻᛖ ᚳᚹᚫᚦ ᚦᚫᛏ ᚻᛖ ᛒᚢᛞᛖ ᚩᚾ ᚦᚫᛗ ᛚᚪᚾᛞᛖ ᚾᚩᚱᚦᚹᛖᚪᚱᛞᚢᛗ ᚹᛁᚦ ᚦᚪ ᚹᛖᛥᚫ (Old English, which transcribed into Latin reads 'He cwaeth that he bude thaem lande northweardum with tha Westsae.' and means 'He said that he lived in the northern land near the Western Sea.') Braille: ⡌⠁⠧⠑ ⠼⠁⠒ ⡍⠜⠇⠑⠹⠰⠎ ⡣⠕⠌ ⡍⠜⠇⠑⠹ ⠺⠁⠎ ⠙⠑⠁⠙⠒ ⠞⠕ ⠃⠑⠛⠔ ⠺⠊⠹⠲ ⡹⠻⠑ ⠊⠎ ⠝⠕ ⠙⠳⠃⠞ ⠱⠁⠞⠑⠧⠻ ⠁⠃⠳⠞ ⠹⠁⠞⠲ ⡹⠑ ⠗⠑⠛⠊⠌⠻ ⠕⠋ ⠙⠊⠎ ⠃⠥⠗⠊⠁⠇ ⠺⠁⠎ ⠎⠊⠛⠝⠫ ⠃⠹ ⠹⠑ ⠊⠇⠻⠛⠹⠍⠁⠝⠂ ⠹⠑ ⠊⠇⠻⠅⠂ ⠹⠑ ⠥⠝⠙⠻⠞⠁⠅⠻⠂ ⠁⠝⠙ ⠹⠑ ⠡⠊⠑⠋ ⠍⠳⠗⠝⠻⠲ ⡎⠊⠗⠕⠕⠛⠑ ⠎⠊⠛⠝⠫ ⠊⠞⠲ ⡁⠝⠙ ⡎⠊⠗⠕⠕⠛⠑⠰⠎ ⠝⠁⠍⠑ ⠺⠁⠎ ⠛⠕⠕⠙ ⠥⠏⠕⠝ ⠰⡡⠁⠝⠛⠑⠂ ⠋⠕⠗ ⠁⠝⠹⠹⠔⠛ ⠙⠑ ⠡⠕⠎⠑ ⠞⠕ ⠏⠥⠞ ⠙⠊⠎ ⠙⠁⠝⠙ ⠞⠕⠲ ⡕⠇⠙ ⡍⠜⠇⠑⠹ ⠺⠁⠎ ⠁⠎ ⠙⠑⠁⠙ ⠁⠎ ⠁ ⠙⠕⠕⠗⠤⠝⠁⠊⠇⠲ ⡍⠔⠙⠖ ⡊ ⠙⠕⠝⠰⠞ ⠍⠑⠁⠝ ⠞⠕ ⠎⠁⠹ ⠹⠁⠞ ⡊ ⠅⠝⠪⠂ ⠕⠋ ⠍⠹ ⠪⠝ ⠅⠝⠪⠇⠫⠛⠑⠂ ⠱⠁⠞ ⠹⠻⠑ ⠊⠎ ⠏⠜⠞⠊⠊⠥⠇⠜⠇⠹ ⠙⠑⠁⠙ ⠁⠃⠳⠞ ⠁ ⠙⠕⠕⠗⠤⠝⠁⠊⠇⠲ ⡊ ⠍⠊⠣⠞ ⠙⠁⠧⠑ ⠃⠑⠲ ⠔⠊⠇⠔⠫⠂ ⠍⠹⠎⠑⠇⠋⠂ ⠞⠕ ⠗⠑⠛⠜⠙ ⠁ ⠊⠕⠋⠋⠔⠤⠝⠁⠊⠇ ⠁⠎ ⠹⠑ ⠙⠑⠁⠙⠑⠌ ⠏⠊⠑⠊⠑ ⠕⠋ ⠊⠗⠕⠝⠍⠕⠝⠛⠻⠹ ⠔ ⠹⠑ ⠞⠗⠁⠙⠑⠲ ⡃⠥⠞ ⠹⠑ ⠺⠊⠎⠙⠕⠍ ⠕⠋ ⠳⠗ ⠁⠝⠊⠑⠌⠕⠗⠎ ⠊⠎ ⠔ ⠹⠑ ⠎⠊⠍⠊⠇⠑⠆ ⠁⠝⠙ ⠍⠹ ⠥⠝⠙⠁⠇⠇⠪⠫ ⠙⠁⠝⠙⠎ ⠩⠁⠇⠇ ⠝⠕⠞ ⠙⠊⠌⠥⠗⠃ ⠊⠞⠂ ⠕⠗ ⠹⠑ ⡊⠳⠝⠞⠗⠹⠰⠎ ⠙⠕⠝⠑ ⠋⠕⠗⠲ ⡹⠳ ⠺⠊⠇⠇ ⠹⠻⠑⠋⠕⠗⠑ ⠏⠻⠍⠊⠞ ⠍⠑ ⠞⠕ ⠗⠑⠏⠑⠁⠞⠂ ⠑⠍⠏⠙⠁⠞⠊⠊⠁⠇⠇⠹⠂ ⠹⠁⠞ ⡍⠜⠇⠑⠹ ⠺⠁⠎ ⠁⠎ ⠙⠑⠁⠙ ⠁⠎ ⠁ ⠙⠕⠕⠗⠤⠝⠁⠊⠇⠲ (The first couple of paragraphs of "A Christmas Carol" by Dickens) Compact font selection example text: ABCDEFGHIJKLMNOPQRSTUVWXYZ /0123456789 abcdefghijklmnopqrstuvwxyz £©µÀÆÖÞßéöÿ –—‘“”„†•…‰™œŠŸž€ ΑΒΓΔΩαβγδω АБВГДабвгд ∀∂∈ℝ∧∪≡∞ ↑↗↨↻⇣ ┐┼╔╘░►☺♀ fi�⑀₂ἠḂӥẄɐː⍎אԱა Greetings in various languages: Hello world, Καλημέρα κόσμε, コンニチハ Box drawing alignment tests: █ ▉ ╔══╦══╗ ┌──┬──┐ ╭──┬──╮ ╭──┬──╮ ┏━━┳━━┓ ┎┒┏┑ ╷ ╻ ┏┯┓ ┌┰┐ ▊ ╱╲╱╲╳╳╳ ║┌─╨─┐║ │╔═╧═╗│ │╒═╪═╕│ │╓─╁─╖│ ┃┌─╂─┐┃ ┗╃╄┙ ╶┼╴╺╋╸┠┼┨ ┝╋┥ ▋ ╲╱╲╱╳╳╳ ║│╲ ╱│║ │║ ║│ ││ │ ││ │║ ┃ ║│ ┃│ ╿ │┃ ┍╅╆┓ ╵ ╹ ┗┷┛ └┸┘ ▌ ╱╲╱╲╳╳╳ ╠╡ ╳ ╞╣ ├╢ ╟┤ ├┼─┼─┼┤ ├╫─╂─╫┤ ┣┿╾┼╼┿┫ ┕┛┖┚ ┌┄┄┐ ╎ ┏┅┅┓ ┋ ▍ ╲╱╲╱╳╳╳ ║│╱ ╲│║ │║ ║│ ││ │ ││ │║ ┃ ║│ ┃│ ╽ │┃ ░░▒▒▓▓██ ┊ ┆ ╎ ╏ ┇ ┋ ▎ ║└─╥─┘║ │╚═╤═╝│ │╘═╪═╛│ │╙─╀─╜│ ┃└─╂─┘┃ ░░▒▒▓▓██ ┊ ┆ ╎ ╏ ┇ ┋ ▏ ╚══╩══╝ └──┴──┘ ╰──┴──╯ ╰──┴──╯ ┗━━┻━━┛ ▗▄▖▛▀▜ └╌╌┘ ╎ ┗╍╍┛ ┋ ▁▂▃▄▅▆▇█ ▝▀▘▙▄▟ circuits-3.2.3/tests/web/test_bad_requests.py000066400000000000000000000011541460335514400213630ustar00rootroot00000000000000#!/usr/bin/env python from http.client import HTTPConnection from circuits.web import Controller class Root(Controller): def index(self): return 'Hello World!' def test_bad_header(webapp): connection = HTTPConnection(webapp.server.host, webapp.server.port) connection.connect() connection.putrequest('GET', '/', 'HTTP/1.1') connection.putheader('Connection', 'close') connection._output(b'X-Foo') # Bad Header connection.endheaders() response = connection.getresponse() assert response.status == 400 assert response.reason == 'Bad Request' connection.close() circuits-3.2.3/tests/web/test_basicauth.py000066400000000000000000000017371460335514400206540ustar00rootroot00000000000000from circuits.web import Controller from circuits.web.tools import basic_auth, check_auth from .helpers import HTTPBasicAuthHandler, HTTPError, build_opener, install_opener, urlopen class Root(Controller): def index(self): realm = 'Test' users = {'admin': 'admin'} encrypt = str if check_auth(self.request, self.response, realm, users, encrypt): return 'Hello World!' return basic_auth(self.request, self.response, realm, users, encrypt) def test(webapp): try: f = urlopen(webapp.server.http.base) except HTTPError as e: assert e.code == 401 assert e.msg == 'Unauthorized' else: assert False handler = HTTPBasicAuthHandler() handler.add_password('Test', webapp.server.http.base, 'admin', 'admin') opener = build_opener(handler) install_opener(opener) f = urlopen(webapp.server.http.base) s = f.read() assert s == b'Hello World!' install_opener(None) circuits-3.2.3/tests/web/test_call_wait.py000066400000000000000000000011051460335514400206350ustar00rootroot00000000000000#!/usr/bin/env python from circuits import Component, Event from circuits.web import Controller from .helpers import urlopen class foo(Event): """foo Event""" class App(Component): channel = 'app' def foo(self): return 'Hello World!' class Root(Controller): def index(self): value = yield self.call(foo(), 'app') yield value.value def test(webapp): app = App().register(webapp) try: f = urlopen(webapp.server.http.base) s = f.read() assert s == b'Hello World!' finally: app.unregister() circuits-3.2.3/tests/web/test_client.py000077500000000000000000000010171460335514400201610ustar00rootroot00000000000000#!/usr/bin/env python from circuits.web import Controller from circuits.web.client import Client, request class Root(Controller): def index(self): return 'Hello World!' def test(webapp): client = Client() client.start() client.fire(request('GET', webapp.server.http.base)) while client.response is None: pass client.stop() response = client.response assert response.status == 200 assert response.reason == 'OK' s = response.read() assert s == b'Hello World!' circuits-3.2.3/tests/web/test_conn.py000066400000000000000000000011261460335514400176360ustar00rootroot00000000000000#!/usr/bin/env python from http.client import HTTPConnection from circuits.web import Controller class Root(Controller): def index(self): return 'Hello World!' def test(webapp): connection = HTTPConnection(webapp.server.host, webapp.server.port) connection.auto_open = False connection.connect() for _i in range(2): connection.request('GET', '/') response = connection.getresponse() assert response.status == 200 assert response.reason == 'OK' s = response.read() assert s == b'Hello World!' connection.close() circuits-3.2.3/tests/web/test_cookies.py000066400000000000000000000011771460335514400203430ustar00rootroot00000000000000#!/usr/bin/env python from circuits.web import Controller from .helpers import CookieJar, HTTPCookieProcessor, build_opener class Root(Controller): def index(self): visited = self.cookie.get('visited') if visited and visited.value: return 'Hello again!' self.cookie['visited'] = True return 'Hello World!' def test(webapp): cj = CookieJar() opener = build_opener(HTTPCookieProcessor(cj)) f = opener.open(webapp.server.http.base) s = f.read() assert s == b'Hello World!' f = opener.open(webapp.server.http.base) s = f.read() assert s == b'Hello again!' circuits-3.2.3/tests/web/test_core.py000066400000000000000000000047371460335514400176440ustar00rootroot00000000000000#!/usr/bin/env python import pytest from circuits.web import Controller from .helpers import HTTPError, urlencode, urlopen class Root(Controller): def index(self): return 'Hello World!' def test_args(self, *args, **kwargs): return f'{args!r}\n{kwargs!r}' def test_default_args(self, a=None, b=None): return f'a={a}\nb={b}' def test_redirect(self): return self.redirect('/') def test_forbidden(self): return self.forbidden() def test_notfound(self): return self.notfound() def test_failure(self): raise Exception() def test_root(webapp): f = urlopen(webapp.server.http.base) s = f.read() assert s == b'Hello World!' def test_404(webapp): try: urlopen('%s/foo' % webapp.server.http.base) except HTTPError as e: assert e.code == 404 assert e.msg == 'Not Found' else: assert False def test_args(webapp): args = ('1', '2', '3') kwargs = {'1': 'one', '2': 'two', '3': 'three'} url = '%s/test_args/%s' % (webapp.server.http.base, '/'.join(args)) data = urlencode(kwargs).encode('utf-8') f = urlopen(url, data) data = f.read().split(b'\n') assert eval(data[0]) == args assert eval(data[1]) == kwargs @pytest.mark.parametrize( 'data,expected', [ ((['1'], {}), b'a=1\nb=None'), ((['1', '2'], {}), b'a=1\nb=2'), ((['1'], {'b': '2'}), b'a=1\nb=2'), ], ) def test_default_args(webapp, data, expected): args, kwargs = data url = '{:s}/test_default_args/{:s}'.format( webapp.server.http.base, '/'.join(args), ) data = urlencode(kwargs).encode('utf-8') f = urlopen(url, data) assert f.read() == expected def test_redirect(webapp): f = urlopen('%s/test_redirect' % webapp.server.http.base) s = f.read() assert s == b'Hello World!' def test_forbidden(webapp): try: urlopen('%s/test_forbidden' % webapp.server.http.base) except HTTPError as e: assert e.code == 403 assert e.msg == 'Forbidden' else: assert False def test_notfound(webapp): try: urlopen('%s/test_notfound' % webapp.server.http.base) except HTTPError as e: assert e.code == 404 assert e.msg == 'Not Found' else: assert False def test_failure(webapp): try: urlopen('%s/test_failure' % webapp.server.http.base) except HTTPError as e: assert e.code == 500 else: assert False circuits-3.2.3/tests/web/test_digestauth.py000066400000000000000000000020751460335514400210460ustar00rootroot00000000000000#!/usr/bin/env python import pytest from circuits.web import Controller from circuits.web.tools import check_auth, digest_auth from .helpers import HTTPDigestAuthHandler, HTTPError, build_opener, install_opener, urlopen pytestmark = pytest.mark.skipif(pytest.PYVER[:2] == (3, 3), reason='Broken on Python 3.3') class Root(Controller): def index(self): realm = 'Test' users = {'admin': 'admin'} if check_auth(self.request, self.response, realm, users): return 'Hello World!' return digest_auth(self.request, self.response, realm, users) def test(webapp): try: f = urlopen(webapp.server.http.base) except HTTPError as e: assert e.code == 401 assert e.msg == 'Unauthorized' else: assert False handler = HTTPDigestAuthHandler() handler.add_password('Test', webapp.server.http.base, 'admin', 'admin') opener = build_opener(handler) install_opener(opener) f = urlopen(webapp.server.http.base) s = f.read() assert s == b'Hello World!' install_opener(None) circuits-3.2.3/tests/web/test_dispatcher.py000066400000000000000000000026731460335514400210370ustar00rootroot00000000000000#!/usr/bin/env python from circuits.web import Controller from circuits.web.client import Client, request class Root(Controller): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self += Leaf() def index(self): return 'Hello World!' def name(self): return 'Earth' class Leaf(Controller): channel = '/world/country/region' def index(self): return 'Hello cities!' def city(self): return 'Hello City!' def make_request(webapp, path): client = Client() client.start() client.fire(request('GET', path)) while client.response is None: pass client.stop() response = client.response s = response.read() return response.status, s def test_root(webapp): status, content = make_request(webapp, webapp.server.http.base) assert status == 200 assert content == b'Hello World!' def test_root_name(webapp): status, content = make_request(webapp, '%s/name' % webapp.server.http.base) assert status == 200 assert content == b'Earth' def test_leaf(webapp): status, content = make_request(webapp, '%s/world/country/region' % webapp.server.http.base) assert status == 200 assert content == b'Hello cities!' def test_city(webapp): status, content = make_request(webapp, '%s/world/country/region/city' % webapp.server.http.base) assert status == 200 assert content == b'Hello City!' circuits-3.2.3/tests/web/test_dispatcher2.py000066400000000000000000000040071460335514400211120ustar00rootroot00000000000000#!/usr/bin/env python from circuits.web import Controller, expose from .helpers import urlopen class Root(Controller): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self += Hello() self += World() def index(self): return 'index' def hello1(self): return 'hello1' @expose('hello2') def hello2(self): return 'hello2' def query(req, test): return 'query %s' % test class Hello(Controller): channel = '/hello' def index(self): return 'hello index' def test(self): return 'hello test' def query(req, test): return 'hello query %s' % test class World(Controller): channel = '/world' def index(self): return 'world index' def test(self): return 'world test' def test_simple(webapp): url = '%s/hello1' % webapp.server.http.base f = urlopen(url) s = f.read() assert s == b'hello1' def test_expose(webapp): url = '%s/hello2' % webapp.server.http.base f = urlopen(url) s = f.read() assert s == b'hello2' def test_index(webapp): f = urlopen(webapp.server.http.base) s = f.read() assert s == b'index' def test_controller_index(webapp): url = '%s/hello/' % webapp.server.http.base f = urlopen(url) s = f.read() assert s == b'hello index' url = '%s/world/' % webapp.server.http.base f = urlopen(url) s = f.read() assert s == b'world index' def test_controller_expose(webapp): url = '%s/hello/test' % webapp.server.http.base f = urlopen(url) s = f.read() assert s == b'hello test' url = '%s/world/test' % webapp.server.http.base f = urlopen(url) s = f.read() assert s == b'world test' def test_query(webapp): url = '%s/query?test=1' % webapp.server.http.base f = urlopen(url) s = f.read() assert s == b'query 1' url = '%s/hello/query?test=2' % webapp.server.http.base f = urlopen(url) s = f.read() assert s == b'hello query 2' circuits-3.2.3/tests/web/test_dispatcher3.py000066400000000000000000000013441460335514400211140ustar00rootroot00000000000000#!/usr/bin/env python from http.client import HTTPConnection import pytest from circuits.web import Controller class Root(Controller): def GET(self): return 'GET' def PUT(self): return 'PUT' def POST(self): return 'POST' def DELETE(self): return 'DELETE' @pytest.mark.parametrize(('method'), ['GET', 'PUT', 'POST', 'DELETE']) def test(webapp, method): connection = HTTPConnection(webapp.server.host, webapp.server.port) connection.connect() connection.request(method, '/') response = connection.getresponse() assert response.status == 200 assert response.reason == 'OK' s = response.read() assert s == f'{method:s}'.encode() connection.close() circuits-3.2.3/tests/web/test_disps.py000066400000000000000000000032501460335514400200230ustar00rootroot00000000000000#!/usr/bin/env python from circuits.core.components import BaseComponent from circuits.core.handlers import handler from circuits.web import BaseServer, Controller from circuits.web.dispatchers.dispatcher import Dispatcher from .helpers import urljoin, urlopen class PrefixingDispatcher(BaseComponent): """Forward to another Dispatcher based on the channel.""" @handler('request', priority=1.0) def _on_request(self, event, request, response): path = request.path.strip('/') path = urljoin('/%s/' % self.channel, path) request.path = path class DummyRoot(Controller): channel = '/' def index(self): return 'Not used' class Root1(Controller): channel = '/site1' def index(self): return 'Hello from site 1!' class Root2(Controller): channel = '/site2' def index(self): return 'Hello from site 2!' def test_disps(manager, watcher): server1 = BaseServer(0, channel='site1').register(manager) PrefixingDispatcher(channel='site1').register(manager) Dispatcher(channel='site1').register(manager) Root1().register(manager) assert watcher.wait('ready', channel=server1.channel) server2 = BaseServer(('localhost', 0), channel='site2').register(manager) PrefixingDispatcher(channel='site2').register(manager) Dispatcher(channel='site2').register(manager) Root2().register(manager) assert watcher.wait('ready', channel=server2.channel) DummyRoot().register(manager) f = urlopen(server1.http.base, timeout=3) s = f.read() assert s == b'Hello from site 1!' f = urlopen(server2.http.base, timeout=3) s = f.read() assert s == b'Hello from site 2!' circuits-3.2.3/tests/web/test_exceptions.py000066400000000000000000000051201460335514400210600ustar00rootroot00000000000000#!/usr/bin/env python import json from circuits.web import Controller from circuits.web.exceptions import Forbidden, NotFound, Redirect from .helpers import HTTPError, urlopen class Root(Controller): def index(self): return 'Hello World!' def test_redirect(self): raise Redirect('/') def test_forbidden(self): raise Forbidden() def test_notfound(self): raise NotFound() def test_contenttype(self): raise Exception() def test_contenttype_json(self): self.response.headers['Content-Type'] = 'application/json' raise Exception() def test_contenttype_json_no_debug(self): self.response.headers['Content-Type'] = 'application/json' self.request.print_debug = False raise Exception() def test_redirect(webapp): f = urlopen('%s/test_redirect' % webapp.server.http.base) s = f.read() assert s == b'Hello World!' def test_forbidden(webapp): try: urlopen('%s/test_forbidden' % webapp.server.http.base) except HTTPError as e: assert e.code == 403 assert e.msg == 'Forbidden' else: assert False def test_notfound(webapp): try: urlopen('%s/test_notfound' % webapp.server.http.base) except HTTPError as e: assert e.code == 404 assert e.msg == 'Not Found' else: assert False def test_contenttype(webapp): try: urlopen('%s/test_contenttype' % webapp.server.http.base) except HTTPError as e: assert e.code == 500 assert e.msg == 'Internal Server Error' assert 'text/html' in e.headers.get('Content-Type') else: assert False def test_contenttype_json(webapp): try: urlopen('%s/test_contenttype_json' % webapp.server.http.base) except HTTPError as e: assert 'json' in e.headers.get('Content-Type') result = json.loads(e.read().decode('utf-8')) assert result['code'] == 500 assert result['name'] == 'Internal Server Error' assert result['description'] == '' assert 'raise Exception' in result['traceback'] else: assert False def test_contenttype_json_no_debug(webapp): try: urlopen('%s/test_contenttype_json_no_debug' % webapp.server.http.base) except HTTPError as e: assert 'json' in e.headers.get('Content-Type') result = json.loads(e.read().decode('utf-8')) assert result['code'] == 500 assert result['name'] == 'Internal Server Error' assert result['description'] == '' assert 'traceback' not in result else: assert False circuits-3.2.3/tests/web/test_expires.py000066400000000000000000000022151460335514400203600ustar00rootroot00000000000000#!/usr/bin/env python from datetime import datetime from email.utils import parsedate from time import mktime from circuits.web import Controller from .helpers import urlopen class Root(Controller): def index(self): self.expires(60) return 'Hello World!' def nocache(self): self.expires(0) return 'Hello World!' def test(webapp): f = urlopen(webapp.server.http.base) s = f.read() assert s == b'Hello World!' expires = f.headers['Expires'] diff = mktime(parsedate(expires)) - mktime(datetime.utcnow().timetuple()) assert 60 - (60 * 0.1) < diff < 60 + (60 * 0.1) # diff is about 60 +- 10% def test_nocache(webapp): f = urlopen('%s/nocache' % webapp.server.http.base) s = f.read() assert s == b'Hello World!' expires = f.headers['Expires'] pragma = f.headers['Pragma'] cacheControl = f.headers['Cache-Control'] now = datetime.utcnow() lastyear = now.replace(year=now.year - 1) diff = mktime(parsedate(expires)) - mktime(lastyear.utctimetuple()) assert diff < 1.0 assert pragma == 'no-cache' assert cacheControl == 'no-cache, must-revalidate' circuits-3.2.3/tests/web/test_expose.py000066400000000000000000000012761460335514400202120ustar00rootroot00000000000000from circuits.web import Controller, expose from .helpers import urlopen class Root(Controller): def index(self): return 'Hello World!' @expose('+test') def test(self): return 'test' @expose('foo+bar', 'foo_bar') def foobar(self): return 'foobar' def test(webapp): f = urlopen(webapp.server.http.base) s = f.read() assert s == b'Hello World!' f = urlopen('%s/+test' % webapp.server.http.base) s = f.read() assert s == b'test' f = urlopen('%s/foo+bar' % webapp.server.http.base) s = f.read() assert s == b'foobar' f = urlopen('%s/foo_bar' % webapp.server.http.base) s = f.read() assert s == b'foobar' circuits-3.2.3/tests/web/test_generator.py000066400000000000000000000005361460335514400206730ustar00rootroot00000000000000#!/usr/bin/env python from circuits.web import Controller from .helpers import urlopen class Root(Controller): def index(self): def response(): yield 'Hello ' yield 'World!' return response() def test(webapp): f = urlopen(webapp.server.http.base) s = f.read() assert s == b'Hello World!' circuits-3.2.3/tests/web/test_gzip.py000066400000000000000000000027231460335514400176560ustar00rootroot00000000000000#!/usr/bin/env python from io import BytesIO from os import path from pytest import fixture from circuits import Component, handler from circuits.web import Controller from circuits.web.tools import gzip from .conftest import DOCROOT from .helpers import Request, build_opener class Gzip(Component): channel = 'web' @handler('response', priority=1.0) def _on_response(self, event, *args, **kwargs): event[0] = gzip(event[0]) class Root(Controller): def index(self): return 'Hello World!' @fixture() def gziptool(request, webapp): gziptool = Gzip().register(webapp) def finalizer(): gziptool.unregister() request.addfinalizer(finalizer) return gziptool def decompress(body): import gzip zbuf = BytesIO() zbuf.write(body) zbuf.seek(0) zfile = gzip.GzipFile(mode='rb', fileobj=zbuf) data = zfile.read() zfile.close() return data def test1(webapp, gziptool): request = Request(webapp.server.http.base) request.add_header('Accept-Encoding', 'gzip') opener = build_opener() f = opener.open(request) s = decompress(f.read()) assert s == b'Hello World!' def test2(webapp, gziptool): request = Request('%s/static/largefile.txt' % webapp.server.http.base) request.add_header('Accept-Encoding', 'gzip') opener = build_opener() f = opener.open(request) s = decompress(f.read()) assert s == open(path.join(DOCROOT, 'largefile.txt'), 'rb').read() circuits-3.2.3/tests/web/test_headers.py000066400000000000000000000022271460335514400203170ustar00rootroot00000000000000#!/usr/bin/env python from circuits.web import Controller from .helpers import urlopen class Root(Controller): def index(self): return 'Hello World!' def foo(self): self.response.headers['Content-Type'] = 'text/plain' return 'Hello World!' def empty(self): return '' def test_default(webapp): f = urlopen(webapp.server.http.base) s = f.read() assert s == b'Hello World!' content_type = f.headers['Content-Type'] assert content_type == 'text/html; charset=utf-8' def test_explicit(webapp): f = urlopen(f'{webapp.server.http.base:s}/foo') s = f.read() assert s == b'Hello World!' content_type = f.headers['Content-Type'] assert content_type == 'text/plain' def test_static(webapp): f = urlopen(f'{webapp.server.http.base:s}/static/test.css') s = f.read() assert s == b'body { }\n' content_type = f.headers['Content-Type'] assert content_type == 'text/css' def test_empty(webapp): f = urlopen(f'{webapp.server.http.base:s}/empty') s = f.read() assert s == b'' content_length = f.headers['Content-Length'] assert int(content_length) == 0 circuits-3.2.3/tests/web/test_http.py000066400000000000000000000043261460335514400176650ustar00rootroot00000000000000#!/usr/bin/env python import pytest from circuits import Component from circuits.net.events import connect, write from circuits.net.sockets import TCPClient from circuits.web import Controller from circuits.web.client import parse_url class Client(Component): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._buffer = [] self.done = False def read(self, data): self._buffer.append(data) if data.find(b'\r\n') != -1: self.done = True def buffer(self): return b''.join(self._buffer) class Root(Controller): def index(self): return 'Hello World!' def test(webapp): transport = TCPClient() client = Client() client += transport client.start() host, port, _resource, _secure = parse_url(webapp.server.http.base) client.fire(connect(host, port)) assert pytest.wait_for(transport, 'connected') client.fire(write(b'GET / HTTP/1.1\r\n')) client.fire(write(b'Host: localhost\r\n')) client.fire(write(b'Content-Type: text/plain\r\n\r\n')) assert pytest.wait_for(client, 'done') client.stop() ss = client.buffer().decode('utf-8') s = ss.split('\r\n')[0] assert s == 'HTTP/1.1 200 OK', ss def test_http_1_0(webapp): transport = TCPClient() client = Client() client += transport client.start() host, port, _resource, _secure = parse_url(webapp.server.http.base) client.fire(connect(host, port)) assert pytest.wait_for(transport, 'connected') client.fire(write(b'GET / HTTP/1.0\r\n\r\n')) assert pytest.wait_for(client, 'done') client.stop() s = client.buffer().decode('utf-8').split('\r\n')[0] assert s == 'HTTP/1.0 200 OK' def test_http_1_1_no_host_headers(webapp): transport = TCPClient() client = Client() client += transport client.start() host, port, _resource, _secure = parse_url(webapp.server.http.base) client.fire(connect(host, port)) assert pytest.wait_for(transport, 'connected') client.fire(write(b'GET / HTTP/1.1\r\n\r\n')) assert pytest.wait_for(client, 'done') client.stop() s = client.buffer().decode('utf-8').split('\r\n')[0] assert s == 'HTTP/1.1 400 Bad Request' circuits-3.2.3/tests/web/test_json.py000066400000000000000000000027311460335514400176550ustar00rootroot00000000000000#!/usr/bin/env python from json import loads from circuits.web import JSONController, Sessions from .helpers import CookieJar, HTTPCookieProcessor, build_opener, urlopen class Root(JSONController): def index(self): return {'success': True, 'message': 'Hello World!'} def test_sessions(self, name=None): if name: with self.session as data: data['name'] = name else: name = self.session.get('name', 'World!') return {'success': True, 'message': 'Hello %s' % name} def test(webapp): f = urlopen(webapp.server.http.base) data = f.read() data = data.decode('utf-8') d = loads(data) assert d['success'] assert d['message'] == 'Hello World!' def test_sessions(webapp): Sessions().register(webapp) cj = CookieJar() opener = build_opener(HTTPCookieProcessor(cj)) f = opener.open('%s/test_sessions' % webapp.server.http.base) data = f.read() data = data.decode('utf-8') d = loads(data) assert d['success'] assert d['message'] == 'Hello World!' f = opener.open('%s/test_sessions/test' % webapp.server.http.base) data = f.read() data = data.decode('utf-8') d = loads(data) assert d['success'] assert d['message'] == 'Hello test' f = opener.open('%s/test_sessions' % webapp.server.http.base) data = f.read() data = data.decode('utf-8') d = loads(data) assert d['success'] assert d['message'] == 'Hello test' circuits-3.2.3/tests/web/test_jsonrpc.py000066400000000000000000000013411460335514400203560ustar00rootroot00000000000000#!/usr/bin/env python from circuits import Component from circuits.web import JSONRPC, Controller from .helpers import urlopen from .jsonrpclib import ServerProxy class App(Component): def eval(self, s): return eval(s) class Root(Controller): def index(self): return 'Hello World!' def test(webapp): rpc = JSONRPC('/rpc') test = App() rpc.register(webapp) test.register(webapp) f = urlopen(webapp.server.http.base) s = f.read() assert s == b'Hello World!' url = '%s/rpc' % webapp.server.http.base jsonrpc = ServerProxy(url, allow_none=True, encoding='utf-8') data = jsonrpc.eval('1 + 2') assert data['result'] == 3 rpc.unregister() test.unregister() circuits-3.2.3/tests/web/test_large_post.py000066400000000000000000000010111460335514400210310ustar00rootroot00000000000000#!/usr/bin/env python from circuits.web import Controller from .helpers import urlencode, urlopen class Root(Controller): def index(self, *args, **kwargs): return f'{args!r}\n{kwargs!r}' def test(webapp): args = ('1', '2', '3') kwargs = {'data': '\x00' * 4096} url = '%s/%s' % (webapp.server.http.base, '/'.join(args)) data = urlencode(kwargs).encode('utf-8') f = urlopen(url, data) data = f.read().split(b'\n') assert eval(data[0]) == args assert eval(data[1]) == kwargs circuits-3.2.3/tests/web/test_logger.py000066400000000000000000000056521460335514400201700ustar00rootroot00000000000000#!/usr/bin/env python import re import sys from io import StringIO from socket import gaierror, gethostbyname, gethostname from circuits.web import Controller, Logger from .helpers import urlopen class DummyLogger: def __init__(self): super().__init__() self.message = None def info(self, message): self.message = message class Root(Controller): def index(self): return 'Hello World!' def test_file(webapp): logfile = StringIO() logger = Logger(file=logfile) logger.register(webapp) f = urlopen(webapp.server.http.base) s = f.read() assert s == b'Hello World!' logfile.seek(0) s = logfile.read().strip() try: address = gethostbyname(gethostname()) except gaierror: address = '127.0.0.1' d = {} d['h'] = address d['l'] = '-' d['u'] = '-' d['r'] = 'GET / HTTP/1.1' d['s'] = '200' d['b'] = '12' d['f'] = '' d['a'] = 'Python-urllib/%s' % sys.version[:3] keys = list(d.keys()) for k in keys: if d[k] and d[k].startswith('127.'): # loopback network: 127.0.0.0/8 assert re.search(r'127(\.[0-9]{1,3}){3}', s) else: assert d[k] in s logfile.close() logger.unregister() def test_logger(webapp): logobj = DummyLogger() logger = Logger(logger=logobj) logger.register(webapp) f = urlopen(webapp.server.http.base) s = f.read() assert s == b'Hello World!' s = logobj.message try: address = gethostbyname(gethostname()) except gaierror: address = '127.0.0.1' d = {} d['h'] = address d['l'] = '-' d['u'] = '-' d['r'] = 'GET / HTTP/1.1' d['s'] = '200' d['b'] = '12' d['f'] = '' d['a'] = 'Python-urllib/%s' % sys.version[:3] keys = list(d.keys()) for k in keys: if d[k] and d[k].startswith('127.'): # loopback network: 127.0.0.0/8 assert re.search(r'127(\.[0-9]{1,3}){3}', s) else: assert d[k] in s logger.unregister() def test_filename(webapp, tmpdir): logfile = str(tmpdir.ensure('logfile')) logger = Logger(file=logfile) logger.register(webapp) logfile = open(logfile) f = urlopen(webapp.server.http.base) s = f.read() assert s == b'Hello World!' logfile.seek(0) s = logfile.read().strip() try: address = gethostbyname(gethostname()) except gaierror: address = '127.0.0.1' d = {} d['h'] = address d['l'] = '-' d['u'] = '-' d['r'] = 'GET / HTTP/1.1' d['s'] = '200' d['b'] = '12' d['f'] = '' d['a'] = 'Python-urllib/%s' % sys.version[:3] keys = list(d.keys()) for k in keys: if d[k] and d[k].startswith('127.'): # loopback network: 127.0.0.0/8 assert re.search(r'127(\.[0-9]{1,3}){3}', s) else: assert d[k] in s logfile.close() logger.unregister() circuits-3.2.3/tests/web/test_methods.py000066400000000000000000000013671460335514400203530ustar00rootroot00000000000000#!/usr/bin/env python from http.client import HTTPConnection from circuits.web import Controller class Root(Controller): def index(self): return 'Hello World!' def test_GET(webapp): connection = HTTPConnection(webapp.server.host, webapp.server.port) connection.request('GET', '/') response = connection.getresponse() assert response.status == 200 assert response.reason == 'OK' s = response.read() assert s == b'Hello World!' def test_HEAD(webapp): connection = HTTPConnection(webapp.server.host, webapp.server.port) connection.request('HEAD', '/') response = connection.getresponse() assert response.status == 200 assert response.reason == 'OK' s = response.read() assert s == b'' circuits-3.2.3/tests/web/test_multipartformdata.py000066400000000000000000000037331460335514400224460ustar00rootroot00000000000000#!/usr/bin/env python from io import BytesIO from os import path import pytest from circuits.web import Controller from .helpers import Request, urlopen from .multipartform import MultiPartForm @pytest.fixture() def sample_file(request): return open( path.join( path.dirname(__file__), 'static', 'unicode.txt', ), 'rb', ) class Root(Controller): def index(self, file, description=''): yield 'Filename: %s\n' % file.filename yield 'Description: %s\n' % description yield 'Content:\n' yield file.value def upload(self, file, description=''): return file.value def test(webapp): form = MultiPartForm() form['description'] = 'Hello World!' fd = BytesIO(b'Hello World!') form.add_file('file', 'helloworld.txt', fd, 'text/plain; charset=utf-8') # Build the request url = webapp.server.http.base data = form.bytes() headers = { 'Content-Type': form.get_content_type(), 'Content-Length': len(data), } request = Request(url, data, headers) f = urlopen(request) s = f.read() lines = s.split(b'\n') assert lines[0] == b'Filename: helloworld.txt' assert lines[1] == b'Description: Hello World!' assert lines[2] == b'Content:' assert lines[3] == b'Hello World!' def test_unicode(webapp, sample_file): form = MultiPartForm() form['description'] = sample_file.name form.add_file( 'file', 'helloworld.txt', sample_file, 'text/plain; charset=utf-8', ) # Build the request url = f'{webapp.server.http.base:s}/upload' data = form.bytes() headers = { 'Content-Type': form.get_content_type(), 'Content-Length': len(data), } request = Request(url, data, headers) f = urlopen(request) s = f.read() sample_file.seek(0) expected_output = sample_file.read() # use the byte stream assert s == expected_output circuits-3.2.3/tests/web/test_null_response.py000066400000000000000000000005121460335514400215670ustar00rootroot00000000000000from circuits.web import Controller from .helpers import HTTPError, urlopen class Root(Controller): def index(self): pass def test(webapp): try: urlopen(webapp.server.http.base) except HTTPError as e: assert e.code == 404 assert e.msg == 'Not Found' else: assert False circuits-3.2.3/tests/web/test_request_failure.py000066400000000000000000000007661460335514400221110ustar00rootroot00000000000000#!/usr/bin/env python from circuits.core.components import BaseComponent from circuits.core.handlers import handler from .helpers import HTTPError, urlopen class Root(BaseComponent): channel = 'web' @handler('request', priority=0.2) def request(self, request, response): raise Exception() def test(webapp): try: Root().register(webapp) urlopen(webapp.server.http.base) except HTTPError as e: assert e.code == 500 else: assert False circuits-3.2.3/tests/web/test_security.py000066400000000000000000000016201460335514400205470ustar00rootroot00000000000000#!/usr/bin/env python from http.client import HTTPConnection import pytest from circuits.web import Controller from .helpers import HTTPError, urlopen class Root(Controller): def index(self): return 'Hello World!' def test_root(webapp): f = urlopen(webapp.server.http.base) s = f.read() assert s == b'Hello World!' def test_badpath_notfound(webapp): url = '%s/../../../../../../etc/passwd' % webapp.server.http.base with pytest.raises(HTTPError) as exc: urlopen(url) assert exc.value.code == 404 def test_badpath_redirect(webapp): connection = HTTPConnection(webapp.server.host, webapp.server.port) connection.connect() path = '/../../../../../../etc/passwd' connection.request('GET', path) response = connection.getresponse() assert response.status == 301 assert response.reason == 'Moved Permanently' connection.close() circuits-3.2.3/tests/web/test_serve_download.py000066400000000000000000000014731460335514400217210ustar00rootroot00000000000000#!/usr/bin/env python import os from tempfile import mkstemp from circuits.web import Controller from .helpers import urlopen class Root(Controller): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) fd, self.filename = mkstemp() os.write(fd, b'Hello World!') os.close(fd) def __del__(self): os.remove(self.filename) def index(self): return self.serve_download(self.filename) def test(webapp): f = urlopen(webapp.server.http.base) s = f.read() assert s == b'Hello World!' contentType = f.headers['Content-Type'] contentDisposition = f.headers['Content-Disposition'] assert contentType == 'application/x-download' assert contentDisposition.startswith('attachment;') assert 'filename' in contentDisposition circuits-3.2.3/tests/web/test_serve_file.py000066400000000000000000000011001460335514400210140ustar00rootroot00000000000000#!/usr/bin/env python import os from tempfile import mkstemp from circuits.web import Controller from .helpers import urlopen class Root(Controller): def __init__(self, *args, **kwargs): super().__init__(self, *args, **kwargs) fd, self.filename = mkstemp() os.write(fd, b'Hello World!') os.close(fd) def __del__(self): os.remove(self.filename) def index(self): return self.serve_file(self.filename) def test(webapp): f = urlopen(webapp.server.http.base) s = f.read() assert s == b'Hello World!' circuits-3.2.3/tests/web/test_servers.py000066400000000000000000000102071460335514400203720ustar00rootroot00000000000000#!/usr/bin/env python import os import ssl import sys import tempfile from os import path from socket import gaierror import pytest from pytest import fixture from circuits import Component from circuits.web import BaseServer, Controller, Server from .helpers import URLError, urlopen CERTFILE = path.join(path.dirname(__file__), 'cert.pem') # self signed cert if pytest.PYVER >= (2, 7, 9): SSL_CONTEXT = ssl.SSLContext(ssl.PROTOCOL_TLS) SSL_CONTEXT.verify_mode = ssl.CERT_NONE @fixture() def tmpfile(request): tmpdir = tempfile.mkdtemp() return os.path.join(tmpdir, 'test.sock') class BaseRoot(Component): channel = 'web' def request(self, request, response): return 'Hello World!' class Root(Controller): def index(self): return 'Hello World!' class MakeQuiet(Component): channel = 'web' def ready(self, event, *args): event.stop() def test_baseserver(manager, watcher): server = BaseServer(0).register(manager) MakeQuiet().register(server) watcher.wait('ready') BaseRoot().register(server) watcher.wait('registered') try: f = urlopen(server.http.base) except URLError as e: if isinstance(e.reason, gaierror): f = urlopen('http://127.0.0.1:8000') else: raise s = f.read() assert s == b'Hello World!' server.unregister() watcher.wait('unregistered') def test_server(manager, watcher): server = Server(0).register(manager) MakeQuiet().register(server) watcher.wait('ready') Root().register(server) try: f = urlopen(server.http.base) except URLError as e: if isinstance(e.reason, gaierror): f = urlopen('http://127.0.0.1:8000') else: raise s = f.read() assert s == b'Hello World!' server.unregister() watcher.wait('unregistered') @pytest.mark.skipif((2,) < sys.version_info < (3, 4, 3), reason='Context not implemented under python 3.4.3') @pytest.mark.skipif(sys.version_info < (2, 7, 9), reason='Context not implemented under python 2.7.9') def test_secure_server(manager, watcher): pytest.importorskip('ssl') server = Server(0, secure=True, certfile=CERTFILE).register(manager) MakeQuiet().register(server) watcher.wait('ready') Root().register(server) try: f = urlopen(server.http.base, context=SSL_CONTEXT) except URLError as e: if isinstance(e.reason, gaierror): f = urlopen('http://127.0.0.1:8000') else: raise s = f.read() assert s == b'Hello World!' server.unregister() watcher.wait('unregistered') def test_unixserver(manager, watcher, tmpfile): if pytest.PLATFORM == 'win32': pytest.skip('Unsupported Platform') server = Server(tmpfile).register(manager) MakeQuiet().register(server) assert watcher.wait('ready') Root().register(server) assert path.basename(server.host) == 'test.sock' try: from uhttplib import UnixHTTPConnection client = UnixHTTPConnection(server.http.base) client.request('GET', '/') response = client.getresponse() s = response.read() assert s == b'Hello World!' except ImportError: pass server.unregister() watcher.wait('unregistered') @pytest.mark.skipif((2, 7, 9) < sys.version_info < (3, 4, 3), reason='Context not implemented under python 3.4.3') @pytest.mark.skipif(sys.version_info < (2, 7, 9), reason='Context not implemented under python 2.7.9') def test_multi_servers(manager, watcher): pytest.importorskip('ssl') insecure_server = Server(0, channel='insecure') secure_server = Server( 0, channel='secure', secure=True, certfile=CERTFILE, ) server = (insecure_server + secure_server).register(manager) MakeQuiet().register(server) watcher.wait('ready') Root().register(server) f = urlopen(insecure_server.http.base) s = f.read() assert s == b'Hello World!' f = urlopen(secure_server.http.base, context=SSL_CONTEXT) s = f.read() assert s == b'Hello World!' server.unregister() watcher.wait('unregistered') circuits-3.2.3/tests/web/test_sessions.py000066400000000000000000000027561460335514400205610ustar00rootroot00000000000000#!/usr/bin/env python from circuits.web import Controller, Sessions from .helpers import CookieJar, HTTPCookieProcessor, build_opener class Root(Controller): def index(self, vpath=None): if vpath: name = vpath with self.session as data: data['name'] = name else: name = self.session.get('name', 'World!') return 'Hello %s' % name def logout(self): self.session.expire() return 'OK' def test(webapp): Sessions().register(webapp) cj = CookieJar() opener = build_opener(HTTPCookieProcessor(cj)) f = opener.open(webapp.server.http.base) s = f.read() assert s == b'Hello World!' f = opener.open(webapp.server.http.base + '/test') s = f.read() assert s == b'Hello test' f = opener.open(webapp.server.http.base) s = f.read() assert s == b'Hello test' def test_expire(webapp): Sessions().register(webapp) cj = CookieJar() opener = build_opener(HTTPCookieProcessor(cj)) f = opener.open(webapp.server.http.base) s = f.read() assert s == b'Hello World!' f = opener.open(webapp.server.http.base + '/test') s = f.read() assert s == b'Hello test' f = opener.open(webapp.server.http.base) s = f.read() assert s == b'Hello test' f = opener.open(webapp.server.http.base + '/logout') s = f.read() assert s == b'OK' f = opener.open(webapp.server.http.base) s = f.read() assert s == b'Hello World!' circuits-3.2.3/tests/web/test_static.py000066400000000000000000000061611460335514400201740ustar00rootroot00000000000000#!/usr/bin/env python from http.client import HTTPConnection from os import path from circuits.web import Controller from .conftest import DOCROOT from .helpers import HTTPError, quote, urlopen class Root(Controller): def index(self): return 'Hello World!' def test(webapp): f = urlopen(webapp.server.http.base) s = f.read() assert s == b'Hello World!' def test_404(webapp): try: urlopen('%s/foo' % webapp.server.http.base) except HTTPError as e: assert e.code == 404 assert e.msg == 'Not Found' else: assert False def test_file(webapp): url = '%s/static/helloworld.txt' % webapp.server.http.base f = urlopen(url) s = f.read().strip() assert s == b'Hello World!' def test_largefile(webapp): url = '%s/static/largefile.txt' % webapp.server.http.base f = urlopen(url) s = f.read().strip() assert s == open(path.join(DOCROOT, 'largefile.txt'), 'rb').read() def test_file404(webapp): try: urlopen('%s/static/foo.txt' % webapp.server.http.base) except HTTPError as e: assert e.code == 404 assert e.msg == 'Not Found' else: assert False def test_directory(webapp): f = urlopen('%s/static/' % webapp.server.http.base) s = f.read() assert b'helloworld.txt' in s def test_file_quoting(webapp): url = '{:s}{:s}'.format(webapp.server.http.base, quote('/static/#foobar.txt')) f = urlopen(url) s = f.read().strip() assert s == b'Hello World!' def test_range(webapp): connection = HTTPConnection(webapp.server.host, webapp.server.port) connection.request('GET', '%s/static/largefile.txt' % webapp.server.http.base, headers={'Range': 'bytes=0-100'}) response = connection.getresponse() assert response.status == 206 s = response.read() assert s == open(path.join(DOCROOT, 'largefile.txt'), 'rb').read(101) def test_ranges(webapp): connection = HTTPConnection(webapp.server.host, webapp.server.port) connection.request('GET', '%s/static/largefile.txt' % webapp.server.http.base, headers={'Range': 'bytes=0-50,51-100'}) response = connection.getresponse() assert response.status == 206 # TODO: Complete this test. # ``response.read()`` is a multipart/bytes-range # See: Issue #59 # s = response.read() # assert s == open(path.join(DOCROOT, "largefile.txt"), "rb").read(101) def test_unsatisfiable_range1(webapp): connection = HTTPConnection(webapp.server.host, webapp.server.port) connection.request( 'GET', '%s/static/largefile.txt' % webapp.server.http.base, headers={'Range': 'bytes=0-100,100-10000,0-1'} ) response = connection.getresponse() assert response.status == 416 # TODO: Implement this test and condition # e.g: For a 10 byte file; Range: bytes=0-1,2-3,4-5,6-7,8-9 # def test_unsatisfiable_range2(webapp): # connection = HTTPConnection(webapp.server.host, webapp.server.port) # # connection.request("GET", "%s/static/largefile.txt" % (webapp.server.http.base, # headers={"Range": "bytes=0-100,100-10000,0-1"})) # response = connection.getresponse() # assert response.status == 416 circuits-3.2.3/tests/web/test_unicode.py000066400000000000000000000056711460335514400203400ustar00rootroot00000000000000#!/usr/bin/env python from http.client import HTTPConnection import pytest from circuits.web import Controller from circuits.web.client import Client, request from .helpers import urlopen class Root(Controller): def index(self): return 'Hello World!' def request_body(self): return self.request.body.read() def response_body(self): return 'ä' def request_headers(self): return self.request.headers['A'] def response_headers(self): self.response.headers['A'] = 'ä' return 'ä' def argument(self, arg): return arg def test_index(webapp): f = urlopen(webapp.server.http.base) s = f.read() assert s == b'Hello World!' @pytest.mark.parametrize( 'body', [ 'ä'.encode(), 'ä'.encode('iso8859-1'), ], ) def test_request_body(webapp, body): connection = HTTPConnection(webapp.server.host, webapp.server.port) connection.connect() connection.request('POST', '/request_body', body) response = connection.getresponse() assert response.status == 200 assert response.reason == 'OK' s = response.read() assert s == body connection.close() def test_response_body(webapp): connection = HTTPConnection(webapp.server.host, webapp.server.port) connection.connect() connection.request('GET', '/response_body') response = connection.getresponse() assert response.status == 200 assert response.reason == 'OK' s = response.read() assert s == 'ä'.encode() connection.close() def test_request_headers(webapp): connection = HTTPConnection(webapp.server.host, webapp.server.port) connection.connect() body = b'' headers = {'A': 'ä'} connection.request('GET', '/request_headers', body, headers) response = connection.getresponse() assert response.status == 200 assert response.reason == 'OK' s = response.read() assert s == 'ä'.encode() connection.close() def test_response_headers(webapp): client = Client() client.start() client.fire( request( 'GET', 'http://%s:%s/response_headers' % ( webapp.server.host, webapp.server.port, ), ), ) while client.response is None: pass assert client.response.status == 200 assert client.response.reason == 'OK' s = client.response.read() a = client.response.headers.get('A') assert a == 'ä' assert s == 'ä'.encode() def test_argument(webapp): connection = HTTPConnection(webapp.server.host, webapp.server.port) connection.connect() data = 'arg=%E2%86%92' connection.request('POST', '/argument', data, {'Content-type': 'application/x-www-form-urlencoded'}) response = connection.getresponse() assert response.status == 200 assert response.reason == 'OK' s = response.read() assert s.decode('utf-8') == '\u2192' connection.close() circuits-3.2.3/tests/web/test_utils.py000066400000000000000000000011201460335514400200330ustar00rootroot00000000000000#!/usr/bin/env python from io import BytesIO from circuits.web.utils import compress, get_ranges try: from gzip import decompress except ImportError: import zlib decompress = zlib.decompressobj(16 + zlib.MAX_WBITS).decompress # NOQA def test_ranges(): assert get_ranges('bytes=3-6', 8) == [(3, 7)] assert get_ranges('bytes=2-4,-1', 8) == [(2, 5), (7, 8)] def test_gzip(): s = b'Hello World!' contents = BytesIO(s) compressed = b''.join(compress(contents, 1)) uncompressed = decompress(compressed) assert uncompressed == s contents.close() circuits-3.2.3/tests/web/test_value.py000066400000000000000000000007201460335514400200140ustar00rootroot00000000000000#!/usr/bin/env python from circuits import Component, Event from circuits.web import Controller from .helpers import urlopen class hello(Event): """hello Event""" class App(Component): def hello(self): return 'Hello World!' class Root(Controller): def index(self): return self.fire(hello()) def test(webapp): App().register(webapp) f = urlopen(webapp.server.http.base) s = f.read() assert s == b'Hello World!' circuits-3.2.3/tests/web/test_vpath_args.py000066400000000000000000000012311460335514400210340ustar00rootroot00000000000000#!/usr/bin/env python from circuits.web import Controller, expose from .helpers import urlopen class Root(Controller): @expose('test.txt') def index(self): return 'Hello world!' class Leaf(Controller): channel = '/test' @expose('test.txt') def index(self, vpath=None): if vpath is None: return 'Hello world!' return 'Hello world! ' + vpath def test(webapp): Leaf().register(webapp) f = urlopen(webapp.server.http.base + '/test.txt') s = f.read() assert s == b'Hello world!' f = urlopen(webapp.server.http.base + '/test/test.txt') s = f.read() assert s == b'Hello world!' circuits-3.2.3/tests/web/test_websockets.py000066400000000000000000000051641460335514400210600ustar00rootroot00000000000000#!/usr/bin/env python import pytest from circuits import Component from circuits.net.sockets import BUFSIZE, close, write from circuits.web.controllers import Controller from circuits.web.websockets import WebSocketClient, WebSocketsDispatcher from .helpers import urlopen class Echo(Component): channel = 'wsserver' def init(self): self.clients = [] def connect(self, sock, host, port): self.clients.append(sock) print('WebSocket Client Connected:', host, port) self.fire(write(sock, f'Welcome {host:s}:{port:d}')) def disconnect(self, sock): self.clients.remove(sock) def read(self, sock, data): self.fire(write(sock, 'Received: ' + data)) class Root(Controller): def index(self): return 'Hello World!' class Client(Component): channel = 'ws' def init(self, *args, **kwargs): self.response = None def read(self, data): self.response = data @pytest.mark.parametrize('chunksize', [BUFSIZE, BUFSIZE + 1, BUFSIZE * 2]) def test(manager, watcher, webapp, chunksize): echo = Echo().register(webapp) assert watcher.wait('registered', channel='wsserver') f = urlopen(webapp.server.http.base) s = f.read() assert s == b'Hello World!' watcher.clear() WebSocketsDispatcher('/websocket').register(webapp) assert watcher.wait('registered', channel='web') uri = f'ws://{webapp.server.host:s}:{webapp.server.port:d}/websocket' WebSocketClient(uri).register(manager) client = Client().register(manager) assert watcher.wait('registered', channel='wsclient') assert watcher.wait('connected', channel='wsclient') assert watcher.wait('connect', channel='wsserver') assert len(echo.clients) == 1 assert watcher.wait('read', channel='ws') assert client.response.startswith('Welcome') watcher.clear() client.fire(write('Hello!'), 'ws') assert watcher.wait('read', channel='ws') assert client.response == 'Received: Hello!' watcher.clear() client.fire(write('World!'), 'ws') assert watcher.wait('read', channel='ws') assert client.response == 'Received: World!' watcher.clear() data = 'A' * (chunksize + 1) client.fire(write(data), 'ws') assert watcher.wait('read', channel='ws') assert client.response == f'Received: {data}' f = urlopen(webapp.server.http.base) s = f.read() assert s == b'Hello World!' assert len(echo.clients) == 1 client.fire(close(), 'ws') assert watcher.wait('disconnect', channel='wsserver') assert len(echo.clients) == 0 client.unregister() assert watcher.wait('unregistered') circuits-3.2.3/tests/web/test_wsgi_application.py000066400000000000000000000053201460335514400222350ustar00rootroot00000000000000#!/usr/bin/env python import json import pytest from circuits.web import Controller from circuits.web.wsgi import Application from .helpers import HTTPError, urlencode, urlopen class Root(Controller): def index(self): return 'Hello World!' def test_args(self, *args, **kwargs): self.response.headers['Content-Type'] = 'application/json' return json.dumps( { 'args': args, 'kwargs': kwargs, 'path': self.request.path, 'uri_path': self.request.uri._path.decode(), 'base_path': self.request.base._path.decode(), 'method': self.request.method, 'scheme': self.request.scheme, 'protocol': self.request.protocol, 'qs': self.request.qs, 'script_name': self.request.script_name, 'content_type': self.request.headers['Content-Type'], } ) def test_redirect(self): return self.redirect('/') def test_forbidden(self): return self.forbidden() def test_notfound(self): return self.notfound() application = Application() + Root() def test(webapp): f = urlopen(webapp.server.http.base) s = f.read() assert s == b'Hello World!' def test_404(webapp): with pytest.raises(HTTPError) as exc: urlopen('%s/foo' % webapp.server.http.base) assert exc.value.code == 404 assert exc.value.msg == 'Not Found' def test_args(webapp): args = ['1', '2', '3'] kwargs = {'1': 'one', '2': 'two', '3': 'three'} url = '%s/test_args/%s' % (webapp.server.http.base, '/'.join(args)) data = urlencode(kwargs).encode() f = urlopen(url, data) data = json.load(f) assert data['args'] == args assert data['kwargs'] == kwargs assert data['path'] == 'test_args/1/2/3' assert data['uri_path'] == '/test_args/1/2/3' assert data['base_path'] == '/' assert data['method'] == 'POST' assert data['scheme'] == 'http' assert data['protocol'] == [1, 1] assert data['qs'] == '' assert data['script_name'] == '/' assert data['content_type'] == 'application/x-www-form-urlencoded' def test_redirect(webapp): f = urlopen('%s/test_redirect' % webapp.server.http.base) s = f.read() assert s == b'Hello World!' def test_forbidden(webapp): with pytest.raises(HTTPError) as exc: urlopen('%s/test_forbidden' % webapp.server.http.base) assert exc.value.code == 403 assert exc.value.msg == 'Forbidden' def test_notfound(webapp): with pytest.raises(HTTPError) as exc: urlopen('%s/test_notfound' % webapp.server.http.base) assert exc.value.code == 404 assert exc.value.msg == 'Not Found' circuits-3.2.3/tests/web/test_wsgi_application_generator.py000066400000000000000000000007521460335514400243070ustar00rootroot00000000000000#!/usr/bin/env python from circuits.web import Controller from circuits.web.wsgi import Application from .helpers import urlopen class Root(Controller): def index(self): def response(): yield 'Hello ' yield 'World!' return response() application = Application() + Root() def test(webapp): f = urlopen(webapp.server.http.base) s = f.read() assert s == b'Hello World!' assert f.headers.get('Transfer-Encoding') != 'chunked' circuits-3.2.3/tests/web/test_wsgi_application_yield.py000066400000000000000000000006571460335514400234330ustar00rootroot00000000000000#!/usr/bin/env python from circuits.web import Controller from circuits.web.wsgi import Application from .helpers import urlopen class Root(Controller): def index(self): yield 'Hello ' yield 'World!' application = Application() + Root() def test(webapp): f = urlopen(webapp.server.http.base) s = f.read() assert s == b'Hello World!' assert f.headers.get('Transfer-Encoding') != 'chunked' circuits-3.2.3/tests/web/test_wsgi_gateway.py000066400000000000000000000006351460335514400213770ustar00rootroot00000000000000#!/usr/bin/env python from .helpers import urlopen def application(environ, start_response): status = '200 OK' response_headers = [('Content-type', 'text/plain')] start_response(status, response_headers) return 'Hello World!' def test(webapp): f = urlopen(webapp.server.http.base) s = f.read() assert s == b'Hello World!' assert f.headers.get('Transfer-Encoding') != 'chunked' circuits-3.2.3/tests/web/test_wsgi_gateway_errors.py000066400000000000000000000010241460335514400227640ustar00rootroot00000000000000import pytest from .helpers import HTTPError, urlopen def application(environ, start_response): status = '200 OK' response_headers = [('Content-type', 'text/plain')] start_response(status, response_headers) raise Exception('Hello World!') def test(webapp): with pytest.raises(HTTPError) as exc: urlopen(webapp.server.http.base) assert exc.value.code == 500 assert exc.value.msg == 'Internal Server Error' s = exc.value.read() assert b'Exception' in s assert b'Hello World!' in s circuits-3.2.3/tests/web/test_wsgi_gateway_generator.py000066400000000000000000000007351460335514400234460ustar00rootroot00000000000000#!/usr/bin/env python from .helpers import urlopen def application(environ, start_response): status = '200 OK' response_headers = [('Content-type', 'text/plain')] start_response(status, response_headers) def response(): yield 'Hello ' yield 'World!' return response() def test(webapp): f = urlopen(webapp.server.http.base) s = f.read() assert s == b'Hello World!' assert f.headers.get('Transfer-Encoding') == 'chunked' circuits-3.2.3/tests/web/test_wsgi_gateway_multiple_apps.py000066400000000000000000000021001460335514400243220ustar00rootroot00000000000000#!/usr/bin/env python import pytest from circuits.web import Server from circuits.web.wsgi import Gateway from .helpers import urlopen def hello(environ, start_response): status = '200 OK' response_headers = [('Content-type', 'text/plain')] start_response(status, response_headers) return 'Hello World!' def foobar(environ, start_response): status = '200 OK' response_headers = [('Content-type', 'text/plain')] start_response(status, response_headers) return 'FooBar!' @pytest.fixture() def apps(request): return { '/': hello, '/foobar': foobar, } def test(apps): server = Server(0) Gateway(apps).register(server) waiter = pytest.WaitEvent(server, 'ready') server.start() waiter.wait() f = urlopen(server.http.base) s = f.read() assert s == b'Hello World!' assert f.headers.get('Transfer-Encoding') != 'chunked' f = urlopen(f'{server.http.base:s}/foobar/') s = f.read() assert s == b'FooBar!' assert f.headers.get('Transfer-Encoding') != 'chunked' server.stop() circuits-3.2.3/tests/web/test_wsgi_gateway_null_response.py000066400000000000000000000006751460335514400243530ustar00rootroot00000000000000#!/usr/bin/env python from circuits.web import Controller from .helpers import urlopen class Root(Controller): def index(self, *args, **kwargs): return 'ERROR' def application(environ, start_response): status = '200 OK' start_response(status, []) return [''] def test(webapp): f = urlopen(webapp.server.http.base) s = f.read() assert s == b'' assert f.headers.get('Transfer-Encoding') != 'chunked' circuits-3.2.3/tests/web/test_wsgi_gateway_write.py000066400000000000000000000006651460335514400226140ustar00rootroot00000000000000#!/usr/bin/env python from .helpers import urlopen def application(environ, start_response): status = '200 OK' response_headers = [('Content-type', 'text/plain')] write = start_response(status, response_headers) write('Hello World!') return [''] def test(webapp): f = urlopen(webapp.server.http.base) s = f.read() assert s == b'Hello World!' assert f.headers.get('Transfer-Encoding') == 'chunked' circuits-3.2.3/tests/web/test_wsgi_gateway_yield.py000066400000000000000000000006511460335514400225630ustar00rootroot00000000000000#!/usr/bin/env python from .helpers import urlopen def application(environ, start_response): status = '200 OK' response_headers = [('Content-type', 'text/plain')] start_response(status, response_headers) yield 'Hello ' yield 'World!' def test(webapp): f = urlopen(webapp.server.http.base) s = f.read() assert s == b'Hello World!' assert f.headers.get('Transfer-Encoding') == 'chunked' circuits-3.2.3/tests/web/test_xmlrpc.py000066400000000000000000000012761460335514400202140ustar00rootroot00000000000000#!/usr/bin/env python from xmlrpc.client import ServerProxy from circuits import Component from circuits.web import XMLRPC, Controller from .helpers import urlopen class App(Component): def eval(self, s): return eval(s) class Root(Controller): def index(self): return 'Hello World!' def test(webapp): rpc = XMLRPC('/rpc') test = App() rpc.register(webapp) test.register(webapp) f = urlopen(webapp.server.http.base) s = f.read() assert s == b'Hello World!' url = '%s/rpc' % webapp.server.http.base server = ServerProxy(url, allow_none=True) r = server.eval('1 + 2') assert r == 3 rpc.unregister() test.unregister() circuits-3.2.3/tests/web/test_yield.py000066400000000000000000000004431460335514400200100ustar00rootroot00000000000000#!/usr/bin/env python from circuits.web import Controller from .helpers import urlopen class Root(Controller): def index(self): yield 'Hello ' yield 'World!' def test(webapp): f = urlopen(webapp.server.http.base) s = f.read() assert s == b'Hello World!' circuits-3.2.3/tests/web/websocket.py000066400000000000000000000354011460335514400176330ustar00rootroot00000000000000""" websocket - WebSocket client library for Python Copyright (C) 2010 Hiroki Ohtani(liris) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """ import logging import random import socket import struct from hashlib import md5 from .helpers import urlparse logger = logging.getLogger() class WebSocketException(Exception): pass class ConnectionClosedException(WebSocketException): pass default_timeout = None traceEnabled = False def enableTrace(tracable): """Turn on/off the tracability.""" global traceEnabled traceEnabled = tracable if tracable: if not logger.handlers: logger.addHandler(logging.StreamHandler()) logger.setLevel(logging.DEBUG) def setdefaulttimeout(timeout): """Set the global timeout setting to connect.""" global default_timeout default_timeout = timeout def getdefaulttimeout(): """Return the global timeout setting to connect.""" return default_timeout def _parse_url(url): """ parse url and the result is tuple of (hostname, port, resource path and the flag of secure mode) """ parsed = urlparse(url) if parsed.hostname: hostname = parsed.hostname else: raise ValueError('hostname is invalid') port = 0 if parsed.port: port = parsed.port is_secure = False if parsed.scheme == 'ws': if not port: port = 80 elif parsed.scheme == 'wss': is_secure = True if not port: port = 443 else: raise ValueError('scheme %s is invalid' % parsed.scheme) resource = parsed.path if parsed.path else '/' return (hostname, port, resource, is_secure) def create_connection(url, timeout=None, **options): """ connect to url and return websocket object. Connect to url and return the WebSocket object. Passing optional timeout parameter will set the timeout on the socket. If no timeout is supplied, the global default timeout setting returned by getdefauttimeout() is used. """ websock = WebSocket() websock.settimeout((timeout is not None and timeout) or default_timeout) websock.connect(url, **options) return websock _MAX_INTEGER = (1 << 32) - 1 _AVAILABLE_KEY_CHARS = list(range(0x21, 0x2F + 1)).extend( list(range(0x3A, 0x7E + 1)), ) _MAX_CHAR_BYTE = (1 << 8) - 1 _MAX_ASCII_BYTE = (1 << 7) - 1 # ref. Websocket gets an update, and it breaks stuff. # http://axod.blogspot.com/2010/06/websocket-gets-update-and-it-breaks.html def _create_sec_websocket_key(): spaces_n = random.randint(1, 12) max_n = _MAX_INTEGER / spaces_n number_n = random.randint(0, int(max_n)) product_n = number_n * spaces_n key_n = str(product_n) for _i in range(random.randint(1, 12)): c = random.choice(_AVAILABLE_KEY_CHARS) pos = random.randint(0, len(key_n)) key_n = key_n[0:pos] + chr(c) + key_n[pos:] for _i in range(spaces_n): pos = random.randint(1, len(key_n) - 1) key_n = key_n[0:pos] + ' ' + key_n[pos:] return number_n, key_n def _create_key3(): return ''.join([chr(random.randint(0, _MAX_ASCII_BYTE)) for i in range(8)]) HEADERS_TO_CHECK = { 'upgrade': 'websocket', 'connection': 'upgrade', } HEADERS_TO_EXIST_FOR_HYBI00 = [ 'sec-websocket-origin', 'sec-websocket-location', ] HEADERS_TO_EXIST_FOR_HIXIE75 = [ 'websocket-origin', 'websocket-location', ] class _SSLSocketWrapper: def __init__(self, sock): self.ssl = socket.ssl(sock) def recv(self, bufsize): return self.ssl.read(bufsize) def send(self, payload): return self.ssl.write(payload) class WebSocket: """ Low level WebSocket interface. This class is based on The WebSocket protocol draft-hixie-thewebsocketprotocol-76 http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76 We can connect to the websocket server and send/recieve data. The following example is a echo client. >>> import websocket >>> ws = websocket.WebSocket() >>> ws.Connect("ws://localhost:8080/echo") >>> ws.send("Hello, Server") >>> ws.recv() 'Hello, Server' >>> ws.close() """ def __init__(self): """Initalize WebSocket object.""" self.connected = False self.io_sock = self.sock = socket.socket() def settimeout(self, timeout): """Set the timeout to the websocket.""" self.sock.settimeout(timeout) def gettimeout(self): """Get the websocket timeout.""" return self.sock.gettimeout() def connect(self, url, **options): """ Connect to url. url is websocket url scheme. ie. ws://host:port/resource """ hostname, port, resource, is_secure = _parse_url(url) # TODO: we need to support proxy self.sock.connect((hostname, port)) if is_secure: self.io_sock = _SSLSocketWrapper(self.sock) self._handshake(hostname, port, resource, **options) def _handshake(self, host, port, resource, **options): sock = self.io_sock headers = [] headers.append('GET %s HTTP/1.1' % resource) headers.append('Upgrade: WebSocket') headers.append('Connection: Upgrade') hostport = host if port == 80 else '%s:%d' % (host, port) headers.append('Host: %s' % hostport) headers.append('Origin: %s' % hostport) number_1, key_1 = _create_sec_websocket_key() headers.append('Sec-WebSocket-Key1: %s' % key_1) number_2, key_2 = _create_sec_websocket_key() headers.append('Sec-WebSocket-Key2: %s' % key_2) if 'header' in options: headers.extend(options['header']) headers.append('') key3 = _create_key3() headers.append(key3) header_str = '\r\n'.join(headers) sock.send(header_str.encode('utf-8')) if traceEnabled: logger.debug('--- request header ---') logger.debug(header_str) logger.debug('-----------------------') status, resp_headers = self._read_headers() if status != 101: self.close() raise WebSocketException('Handshake Status %d' % status) success, secure = self._validate_header(resp_headers) if not success: self.close() raise WebSocketException('Invalid WebSocket Header') if secure: resp = self._get_resp() if not self._validate_resp(number_1, number_2, key3, resp): self.close() raise WebSocketException('challenge-response error') self.connected = True def _validate_resp(self, number_1, number_2, key3, resp): challenge = struct.pack('!I', number_1) challenge += struct.pack('!I', number_2) challenge += key3.encode('utf-8') digest = md5(challenge).digest() return resp == digest def _get_resp(self): result = self._recv(16) if traceEnabled: logger.debug('--- challenge response result ---') logger.debug(repr(result)) logger.debug('---------------------------------') return result def _validate_header(self, headers): # TODO: check other headers for key, value in HEADERS_TO_CHECK.items(): v = headers.get(key, None) if value != v: return False, False success = 0 for key in HEADERS_TO_EXIST_FOR_HYBI00: if key in headers: success += 1 if success == len(HEADERS_TO_EXIST_FOR_HYBI00): return True, True if success != 0: return False, True success = 0 for key in HEADERS_TO_EXIST_FOR_HIXIE75: if key in headers: success += 1 if success == len(HEADERS_TO_EXIST_FOR_HIXIE75): return True, False return False, False def _read_headers(self): status = None headers = {} if traceEnabled: logger.debug('--- response header ---') while True: line = self._recv_line() if line == b'\r\n': break line = line.strip() if traceEnabled: logger.debug(line) if not status: status_info = line.split(b' ', 2) status = int(status_info[1]) else: kv = line.split(b':', 1) if len(kv) == 2: key, value = kv headers[key.lower().decode('utf-8')] = value.strip().lower().decode('utf-8') else: raise WebSocketException('Invalid header') if traceEnabled: logger.debug('-----------------------') return status, headers def send(self, payload): """Send the data as string. payload must be utf-8 string or unicoce.""" if isinstance(payload, str): payload = payload.encode('utf-8') data = b''.join([b'\x00', payload, b'\xff']) self.io_sock.send(data) if traceEnabled: logger.debug('send: ' + repr(data)) def recv(self): """Reeive utf-8 string data from the server.""" b = self._recv(1) if enableTrace: logger.debug('recv frame: ' + repr(b)) frame_type = ord(b) if frame_type == 0x00: bytes = [] while True: b = self._recv(1) if b == b'\xff': break bytes.append(b) return b''.join(bytes) if 0x80 < frame_type < 0xFF: # which frame type is valid? length = self._read_length() return self._recv_strict(length) if frame_type == 0xFF: self._recv(1) self._closeInternal() return None raise WebSocketException('Invalid frame type') def _read_length(self): length = 0 while True: b = ord(self._recv(1)) length = length * (1 << 7) + (b & 0x7F) if b < 0x80: break return length def close(self): """Close Websocket object""" if self.connected: try: self.io_sock.send('\xff\x00') timeout = self.sock.gettimeout() self.sock.settimeout(1) try: result = self._recv(2) if result != '\xff\x00': logger.error('bad closing Handshake') except Exception: pass self.sock.settimeout(timeout) self.sock.shutdown(socket.SHUT_RDWR) except Exception: pass self._closeInternal() def _closeInternal(self): self.connected = False self.sock.close() self.io_sock = self.sock def _recv(self, bufsize): bytes = self.io_sock.recv(bufsize) if not bytes: raise ConnectionClosedException() return bytes def _recv_strict(self, bufsize): remaining = bufsize bytes = '' while remaining: bytes += self._recv(remaining) remaining = bufsize - len(bytes) return bytes def _recv_line(self): line = [] while True: c = self._recv(1) line.append(c) if c == b'\n': break return b''.join(line) class WebSocketApp: """ Higher level of APIs are provided. The interface is like JavaScript WebSocket object. """ def __init__(self, url, on_open=None, on_message=None, on_error=None, on_close=None): """ url: websocket url. on_open: callable object which is called at opening websocket. this function has one argument. The arugment is this class object. on_message: callbale object which is called when recieved data. on_message has 2 arguments. The 1st arugment is this class object. The passing 2nd arugment is utf-8 string which we get from the server. on_error: callable object which is called when we get error. on_error has 2 arguments. The 1st arugment is this class object. The passing 2nd arugment is exception object. on_close: callable object which is called when closed the connection. this function has one argument. The arugment is this class object. """ self.url = url self.on_open = on_open self.on_message = on_message self.on_error = on_error self.on_close = on_close self.sock = None def send(self, data): """Send message. data must be utf-8 string or unicode.""" self.sock.send(data) def close(self): """Close websocket connection.""" self.sock.close() def run_forever(self): """ run event loop for WebSocket framework. This loop is infinite loop and is alive during websocket is available. """ if self.sock: raise WebSocketException('socket is already opened') try: self.sock = WebSocket() self.sock.connect(self.url) self._run_with_no_err(self.on_open) while True: data = self.sock.recv() if data is None: break self._run_with_no_err(self.on_message, data) except Exception as e: self._run_with_no_err(self.on_error, e) finally: self.sock.close() self._run_with_no_err(self.on_close) self.sock = None def _run_with_no_err(self, callback, *args): if callback: try: callback(self, *args) except Exception as e: if logger.isEnabledFor(logging.DEBUG): logger.exception(e) if __name__ == '__main__': enableTrace(True) # ws = create_connection("ws://localhost:8080/echo") ws = create_connection('ws://localhost:5000/chat') print("Sending 'Hello, World'...") ws.send('Hello, World') print('Sent') print('Receiving...') result = ws.recv() print("Received '%s'" % result) ws.close() circuits-3.2.3/tox.ini000066400000000000000000000007201460335514400146630ustar00rootroot00000000000000[tox] envlist=docs,py37,py38,py39,py310,py311,py312,pypy skip_missing_interpreters=True [pytest] addopts=-r fsxX --durations=10 --ignore=tmp --tb=native -lvv [testenv] commands=py.test --timeout=30 {posargs} extras= stomp deps= pytest pytest-cov pytest-timeout passenv=TEST_STOMP_* [testenv:docs] basepython=python skip_install=True changedir=docs deps= sphinx pytest releases commands=py.test --junitxml=circuits-docs.xml check_docs.py