pax_global_header00006660000000000000000000000064140413072720014512gustar00rootroot0000000000000052 comment=5054de8874f275ed0de55007b87cff4817b1d9f7 RxPY-3.2.0/000077500000000000000000000000001404130727200123565ustar00rootroot00000000000000RxPY-3.2.0/.coveragerc000066400000000000000000000003011404130727200144710ustar00rootroot00000000000000[report] omit = */python?.?/* */site-packages/nose/* exclude_lines = pragma: no cover return NotImplemented raise NotImplementedError \.\.\. [xml] output = coverage.xml RxPY-3.2.0/.gitattributes000066400000000000000000000007171404130727200152560ustar00rootroot00000000000000# Set the default behavior, in case people don't have core.autocrlf set. * text=auto # Explicitly declare text files you want to always be normalized and converted # to native line endings on checkout. *.py text *.js text *.xml text *.yml text *.md text # Declare files that will always have CRLF line endings on checkout. *.sln text eol=crlf *.pyproj text eol=crlf # Denote all files that are truly binary and should not be modified. *.png binary *.jpg binaryRxPY-3.2.0/.github/000077500000000000000000000000001404130727200137165ustar00rootroot00000000000000RxPY-3.2.0/.github/lock.yml000066400000000000000000000021771404130727200154000ustar00rootroot00000000000000# Configuration for Lock Threads - https://github.com/dessant/lock-threads # Number of days of inactivity before a closed issue or pull request is locked daysUntilLock: 365 # Skip issues and pull requests created before a given timestamp. Timestamp must # follow ISO 8601 (`YYYY-MM-DD`). Set to `false` to disable skipCreatedBefore: false # Issues and pull requests with these labels will be ignored. Set to `[]` to disable exemptLabels: [] # Label to add before locking, such as `outdated`. Set to `false` to disable lockLabel: false # Comment to post before locking. Set to `false` to disable lockComment: > This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs. # Assign `resolved` as the reason for locking. Set to `false` to disable setLockReason: true # Limit to only `issues` or `pulls` # only: issues # Optionally, specify configuration settings just for `issues` or `pulls` # issues: # exemptLabels: # - help-wanted # lockLabel: outdated # pulls: # daysUntilLock: 30 # Repository to extend settings from # _extends: repo RxPY-3.2.0/.github/workflows/000077500000000000000000000000001404130727200157535ustar00rootroot00000000000000RxPY-3.2.0/.github/workflows/pythonpackage.yml000066400000000000000000000027351404130727200213420ustar00rootroot00000000000000name: Python package on: [push, pull_request] jobs: build: strategy: matrix: platform: [ubuntu-latest, macos-latest, windows-latest] python-version: [3.6, 3.7, 3.8, 3.9, pypy3] runs-on: ${{ matrix.platform }} steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | pip install -r requirements.txt - name: Lint with flake8 run: | # stop the build if there are Python syntax errors or undefined names flake8 . --count --select=E9,F63,F7 --show-source --statistics # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics - name: Test with pytest run: | pytest coverage: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Set up Python 3.9 uses: actions/setup-python@v2 with: python-version: 3.9 - name: Install dependencies run: | pip install -r requirements.txt - name: Run coverage tests run: | coverage run -m pytest - name: Coveralls env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} COVERALLS_SERVICE_NAME: github run: | coveralls RxPY-3.2.0/.gitignore000066400000000000000000000013261404130727200143500ustar00rootroot00000000000000*.py[cod] # C extensions *.so # Packages *.egg *.egg-info dist build eggs .eggs parts bin var sdist develop-eggs .installed.cfg lib lib64 *tmp_dir # Installer logs pip-log.txt # Unit test / coverage reports .coverage .tox nosetests.xml coverage.xml htmlcov .mypy_cache .pytest_cache # Virtual env venv # Translations *.mo # Mr Developer .mr.developer.cfg .project .pydevproject # Visual studio *.suo *.DotSettings.user TestResults/ # Log files *.log Rx.pyperf *.coverage UpgradeLog.htm TestResults/Rx.TE.Tests.mdf TestResults/Rx.TE.Tests_log.ldf *.user # Cloud9 .c9 # PyCharm .idea .zedstate # Spyder IDE .spyproject/ .ipynb_checkpoints .cache/ .vscode/ .noseids _build # Mac OS .DS_Store # Type checkers .pyre RxPY-3.2.0/.pylintrc000066400000000000000000000002071404130727200142220ustar00rootroot00000000000000[BASIC] good-names=n,x,xs,ys,zs,ex,obv,obs,T_in,T_out,do,_ [MESSAGES CONTROL] disable=missing-docstring,unused-argument,invalid-nameRxPY-3.2.0/LICENSE000066400000000000000000000021461404130727200133660ustar00rootroot00000000000000The MIT License =============== Copyright 2013-2019, Dag Brattli, Microsoft Corp., and Contributors. 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. RxPY-3.2.0/MANIFEST.in000066400000000000000000000000701404130727200141110ustar00rootroot00000000000000include LICENSE include project.cfg include rx/py.typed RxPY-3.2.0/README.rst000066400000000000000000000072731404130727200140560ustar00rootroot00000000000000========================================== The Reactive Extensions for Python (RxPY) ========================================== .. image:: https://github.com/ReactiveX/RxPY/workflows/Python%20package/badge.svg :target: https://github.com/ReactiveX/RxPY/actions .. image:: https://img.shields.io/coveralls/ReactiveX/RxPY.svg :target: https://coveralls.io/github/ReactiveX/RxPY .. image:: https://img.shields.io/pypi/v/rx.svg :target: https://pypi.python.org/pypi/Rx .. image:: https://img.shields.io/readthedocs/rxpy.svg :target: https://readthedocs.org/projects/rxpy/builds/ :alt: Documentation Status *A library for composing asynchronous and event-based programs using observable collections and query operator functions in Python* RxPY v3.0 ---------------- For v1.X please go to the `v1 branch `_. RxPY v3.x runs on `Python `_ 3.6 or above. To install RxPY: .. code:: console pip3 install rx About ReactiveX ------------------ Reactive Extensions for Python (RxPY) is a set of libraries for composing asynchronous and event-based programs using observable sequences and pipable query operators in Python. Using Rx, developers represent asynchronous data streams with Observables, query asynchronous data streams using operators, and parameterize concurrency in data/event streams using Schedulers. .. code:: python import rx from rx import operators as ops source = rx.of("Alpha", "Beta", "Gamma", "Delta", "Epsilon") composed = source.pipe( ops.map(lambda s: len(s)), ops.filter(lambda i: i >= 5) ) composed.subscribe(lambda value: print("Received {0}".format(value))) Learning RxPY -------------- Read the `documentation `_ to learn the principles of RxPY and get the complete reference of the available operators. If you need to migrate code from RxPY v1.x, read the `migration `_ section. There is also a list of third party documentation available `here `_. Community ---------- Join the conversation on Slack! The gracious folks at `PySlackers `_ have given us a home in the `#rxpy `_ Slack channel. Please join us there for questions, conversations, and all things related to RxPy. To join, navigate the page above to receive an email invite. After signing up, join us in the #rxpy channel. Please follow the community guidelines and terms of service. Differences from .NET and RxJS ------------------------------ RxPY is a fairly complete implementation of `Rx `_ with more than `120 operators `_, and over `1300 passing unit-tests `_. RxPY is mostly a direct port of RxJS, but also borrows a bit from RxNET and RxJava in terms of threading and blocking operators. RxPY follows `PEP 8 `_, so all function and method names are lowercase with words separated by underscores as necessary to improve readability. Thus .NET code such as: .. code:: c# var group = source.GroupBy(i => i % 3); need to be written with an `_` in Python: .. code:: python group = source.pipe(ops.group_by(lambda i: i % 3)) With RxPY you should use `named keyword arguments `_ instead of positional arguments when an operator has multiple optional arguments. RxPY will not try to detect which arguments you are giving to the operator (or not). RxPY-3.2.0/authors.txt000066400000000000000000000001631404130727200146040ustar00rootroot00000000000000RxPY v3 team Dag Brattli @dbrattli Erik Kemperman, @erikkemperman Jérémie Fache, @jcafhe Romain Picard, @MainRo RxPY-3.2.0/changes.md000066400000000000000000000132701404130727200143130ustar00rootroot00000000000000# Changes ## 2.0.0-alpha - Extension methods and extension class methods have been removed. This makes it much easier for editors and IDEs to validate the code and perform code completion. - Python 3.6+ only with type hints - Google docstring style. ## 1.5.0 - Refactored virtual time scheduling. Fixes #95. Thanks to @djarb - Fixed Visual Studio project files and moved to ide folder. - Remove timer operations from base `SchedulerBase` class. - Scheduler.now is now a property to align with Rx.NET - Bugfix for periodic scheduling. Fixes #91. Thanks to @frederikaalund - Demonize all threads in `TimeoutScheduler`. Fixes #90 - Enable subscription with duck-typed observer. - Added new core module. Observable, Observer, Scheduler and Disposable are now ABCs. - Synced backpressure with RxJS to fix #87 - Do not overwrite scheduler keyword arg. Fixes #85. Thanks to @rjayatilleka - Added async iterator example. - Added support for awaiting observables - Fixed issue #83 with `int + datetime.datetime` in timer.py. Thanks to @AlexMost ## 1.2.6 - Fixes for TwistedScheduler raising AlreadyCalled error #78. Thanks to @mchen402 and @jcwilson. - Use CurrentThreadScheduler as default for just/return_value. Fixes #76 ## 1.2.5 - Added wxscheduler.py for use with wxPython applications thanks to @bosonogi - Added eventletscheduler.py for use with Eventlet thanks to @jalandip - Protect generators that are not thread safe. Fixes #71 ## 1.2.4 - Threads are now daemonic by default. Thus they will exit if parent thread exits which should probably be what most people want. - Fix for recursive scheduling, thanks to @smeder - Fix for NewThreadScheduler. Now uses EventLoopScheduler to make sure scheduled actions by scheduled actions happens on the same thread. - Uses shields.io to uniformize and fix the badges, thanks to @DavidJFelix ## 1.2.3 - Fix optional parameter in `delay_subscription`. Thanks to @angelsanz. - Simplified `adapt_call` in `util.py` which makes higher order functions accept more forms of callables. - Fix for Python 2.7 in `timeflies_qt.py`. ## 1.2.2 - Added Qt mainloop scheduler thanks to @jdreaver. - Bugfix, wse `threading.RLock` instead of `threading.Lock` since `BehaviorSubject` may share lock with "child" operator at subscribe time. Fixes #50 ## 1.2.1 - Fix to preserve the original error message for exceptions #44, thanks to @hangtwenty - Fixed bug in `combine_latest()`. Fixes #48. - Added `to_marbles()` and `from_marbles()`. Available from module `rx.testing.marbles`. - Added [Getting Started](https://github.com/ReactiveX/RxPY/blob/master/notebooks/Getting%20Started.ipynb) IPython Notebook. - Added `share()` as alias for `publish().ref_count()`. - Added error handling example at https://github.com/ReactiveX/RxPY/blob/master/examples/errors/failing.py ## 1.2.0 - Removed impl. of `merge_observable` and made it an alias of `merge_all` - Bugfix for #40. Every subscription needs it's own iterator in `from_()`. Thanks to @hangtwenty. - Bugfix in `from_string()` debug method. - Added `TkInterScheduler.schedule_periodic()` thanks to @pillmuncher. #39 - Bugfix for #35. Refactored `zip_array` to use `zip` instead. - AsyncIOScheduler now works with Python-2.7 and Trollius. Fixes #37 thanks to @hangtwenty. - Added `with_latest_from` extension method #34. Thanks to @pillmuncher. ## 1.1.0 - Transducers via `Observable.transduce()` - `adapt_call` no longer requires the inspect module - Support callable instance, instance method, and class method for `adapt_call` thanks to @succhiello. - Added example using concurrent futures for compute-intensive task parallelization, thanks to @38elements. - Got chess example working again under Python 2.7 thansks to @enobayram. - Added example for async generator. - Many PEP 8 fixes. ## 1.0.0 - Fixed bug in ScheduledDisposable#dispose. Only dispose if not disposed - Fixed typo in `Pattern#_and`. Should be `Pattern#and_` - Fixed bug. Replaced push with append in controlledsubject.py - Refeactored `observer_from_notifier` to `Observer.from_notification` - Added missing rx.linq.observable.blocking from setup.py - Added missing rx.joins from setup.py - Removed some non git files files that were added to the package by accident - Added `Observable#to_iterable()` - Fixed examples. Use `debounce` instead of `throttle` - Fixed wrong aliases for `select_switch`. - Added join patterns. `Observable.when` and `Observable#and_` - Added `BlockingObservable`and operators `for_each` and `to_iterable` - Started adding docstrings as reStructuredText in order for PyCharm to infer types. Operators will eventually be converted to new syntax - Refactored operators to use C# like extensionmethods using function decorators - More PEP8 alignment ## 0.15 - Python slicing and indexing of observables. Thus you can write xs[1:-1:2] - Aligned backpressure with RxJS - Renamed all `select()` to `map()` and `where()` to `map()` - `from_` is now an alias for `from_iterable`. Removed `from_array` - Fixes for `flat_map`/`flat_map`. Selector may return iterable ## 0.14 - Made `ScheduledObserver` thread safe - Thread safe handling for `take_while` and `group_join` - Removed dependecy on six (https://pythonhosted.org/six/) - Added support for IronPython (by removing six) - Aggregate is now an alias for reduce ## 0.13 - Aligning throttle type operator naming with RxJS and RxJava - Added `throttle_last()` as alias for `sample()` - Renamed `throttle()` to `debounce()` and added `throttle_with_timeout()` as alias - Renamed `any()` to `some()` - Simplified `sequence_equal()` - Bugfix for `take()` when no count given - Removed internal operator `final_value()` which did exactly the same as `last()` - Added `to_iterable()` as alias to `to_list()` - Added `throttle_first()` RxPY-3.2.0/docs/000077500000000000000000000000001404130727200133065ustar00rootroot00000000000000RxPY-3.2.0/docs/Makefile000066400000000000000000000011311404130727200147420ustar00rootroot00000000000000# Minimal makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build SPHINXPROJ = RxPY SOURCEDIR = . BUILDDIR = _build # Put it first so that "make" without argument is like "make help". help: @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) .PHONY: help Makefile # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)RxPY-3.2.0/docs/additional_reading.rst000066400000000000000000000032741404130727200176470ustar00rootroot00000000000000.. _additional_reading: Additional Reading ================== Open Material ------------- The RxPY source repository contains `example notebooks `_. The official ReactiveX website contains additional tutorials and documentation: * `Introduction `_ * `Tutorials `_ * `Operators `_ Several commercial contents have their associated example code available freely: * `Packt Reactive Programming in Python `_ RxPY 3.0.0 has removed support for backpressure here are the known community projects supporting backpressure: * `rxbackpressure rxpy extension `_ * `rxpy_backpressure observer decorators `_ Commercial Material -------------------- **O\'Reilly Video** O'Reilly has published the video *Reactive Python for Data Science* which is available on both the `O\'Reilly Store `_ as well as `O\'Reilly Safari `_. This video teaches RxPY from scratch with applications towards data science, but should be helpful for anyone seeking to learn RxPY and reactive programming. **Packt Video** Packt has published the video *Reactive Programming in Python*, available on `Packt store `_. This video teaches how to write reactive GUI and network applications. RxPY-3.2.0/docs/conf.py000066400000000000000000000155311404130727200146120ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Configuration file for the Sphinx documentation builder. # # This file does only contain a selection of the most common options. For a # full list see the documentation: # http://www.sphinx-doc.org/en/stable/config # -- Path setup -------------------------------------------------------------- # 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 os.path.abspath to make it absolute, like shown here. import os import re import sys import guzzle_sphinx_theme from configparser import ConfigParser root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) sys.path.insert(0, root) # -- Project information ----------------------------------------------------- # General project metadata is stored in project.cfg with open(os.path.join(root, 'project.cfg')) as project_file: config = ConfigParser() config.read_file(project_file) project_meta = dict(config.items('project')) project = project_meta['project'] author = project_meta['author'] copyright = project_meta['copyright'] description = project_meta['description'] url = project_meta['url'] title = project + ' Documentation' # The full version, including alpha/beta/rc tags release = project_meta['version'] # The short X.Y.Z version version = re.sub('[^0-9.].*$', '', release) # -- General configuration --------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. # needs_sphinx = '2.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.napoleon', 'sphinx_autodoc_typehints', 'guzzle_sphinx_theme', 'sphinxcontrib_dooble', ] # Include a separate entry for special methods, like __init__, where provided. autodoc_default_options = { 'member-order': 'bysource', 'special-members': True, 'exclude-members': '__dict__,__weakref__' } # Hack to get crosslinks working where things are available (and referred to) by # several paths. E.g. we export rx.Observable but that's not its fully qualified # name, and this causes the automatically generated links to fail for the # argument / return types of functions (e.g. as defined in rx/__init__.py). # # It looks as though the intention of sphinx_autodoc_typehints is to include # support for something like this in a future version: # # https://github.com/agronholm/sphinx-autodoc-typehints/issues/38 import sphinx_autodoc_typehints qualname_overrides = { 'rx.core.observable.observable.Observable': 'rx.Observable' } _format_annotation = sphinx_autodoc_typehints.format_annotation def format_annotation(annotation, fully_qualified=False): if isinstance(annotation, type): full_name = f'{annotation.__module__}.{annotation.__qualname__}' override = qualname_overrides.get(full_name) if override is not None: return f':py:class:`~{override}`' return _format_annotation(annotation, fully_qualified) sphinx_autodoc_typehints.format_annotation = format_annotation # End hack. # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # source_suffix = '.rst' # The master toctree document. master_doc = 'index' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. language = "en" # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path . exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # -- Options for HTML output ------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # html_translator_class = 'guzzle_sphinx_theme.HTMLTranslator' html_theme_path = guzzle_sphinx_theme.html_theme_path() html_theme = 'guzzle_sphinx_theme' html_title = title html_short_title = project + ' ' + version # 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 = {} html_theme_options = { 'projectlink': url } # 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'] # Custom sidebar templates, must be a dictionary that maps document names # to template names. # # The default sidebars (for documents that don't match any pattern) are # defined by theme itself. Builtin themes are using these templates by # default: ``['localtoc.html', 'relations.html', 'sourcelink.html', # 'searchbox.html']``. # html_sidebars = { '**': ['logo-text.html', 'globaltoc.html', 'searchbox.html'] } # -- Options for HTMLHelp output --------------------------------------------- # Output file base name for HTML help builder. htmlhelp_basename = project + 'doc' # -- Options for LaTeX output ------------------------------------------------ latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). # # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. # # 'preamble': '', # Latex figure (float) alignment # # 'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ (master_doc, project + '.tex', title, author, 'manual') ] # -- Options for manual page output ------------------------------------------ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ (master_doc, project.lower(), title, [author], 1) ] # -- Options for Texinfo output ---------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ (master_doc, project, title, author, project, description, 'Miscellaneous') ] # -- Extension configuration ------------------------------------------------- RxPY-3.2.0/docs/contributing.rst000066400000000000000000000011101404130727200165400ustar00rootroot00000000000000Contributing ============= You can contribute by reviewing and sending feedback on code checkins, suggesting and trying out new features as they are implemented, register issues and help us verify fixes as they are checked in, as well as submit code fixes or code contributions of your own. The main repository is at `ReactiveX/RxPY `_. Please register any issues to `ReactiveX/RxPY/issues `_. Please submit any pull requests against the `master `_ branch. RxPY-3.2.0/docs/get_started.rst000066400000000000000000000344371404130727200163600ustar00rootroot00000000000000.. get_started Get Started ============ An :class:`Observable ` is the core type in ReactiveX. It serially pushes items, known as *emissions*, through a series of operators until it finally arrives at an Observer, where they are consumed. Push-based (rather than pull-based) iteration opens up powerful new possibilities to express code and concurrency much more quickly. Because an :class:`Observable ` treats events as data and data as events, composing the two together becomes trivial. There are many ways to create an :class:`Observable ` that hands items to an Observer. You can use a :func:`create() ` factory and pass it functions that handle items: * The *on_next* function is called each time the Observable emits an item. * The *on_completed* function is called when the Observable completes. * The *on_error* function is called when an error occurs on the Observable. You do not have to specify all three event types. You can pick and choose which events you want to observe by providing only some of the callbacks, or simply by providing a single lambda for *on_next*. Typically in production, you will want to provide an *on_error* handler so that errors are explicitly handled by the subscriber. Let's consider the following example: .. code:: python from rx import create def push_five_strings(observer, scheduler): observer.on_next("Alpha") observer.on_next("Beta") observer.on_next("Gamma") observer.on_next("Delta") observer.on_next("Epsilon") observer.on_completed() source = create(push_five_strings) source.subscribe( on_next = lambda i: print("Received {0}".format(i)), on_error = lambda e: print("Error Occurred: {0}".format(e)), on_completed = lambda: print("Done!"), ) An Observable is created with create. On subscription, the *push_five_strings* function is called. This function emits five items. The three callbacks provided to the *subscribe* function simply print the received items and completion states. Note that the use of lambdas simplify the code in this basic example. Output: .. code:: console Received Alpha Received Beta Received Gamma Received Delta Received Epsilon Done! However, there are many :ref:`Observable factories ` for common sources of emissions. To simply push five items, we can rid the :func:`create() ` and its backing function, and use :func:`of() `. This factory accepts an argument list, iterates on each argument to emit them as items, and the completes. Therefore, we can simply pass these five Strings as arguments to it: .. code:: python from rx import of source = of("Alpha", "Beta", "Gamma", "Delta", "Epsilon") source.subscribe( on_next = lambda i: print("Received {0}".format(i)), on_error = lambda e: print("Error Occurred: {0}".format(e)), on_completed = lambda: print("Done!"), ) And a single parameter can be provided to the subscribe function if completion and error are ignored: .. code:: python from rx import of source = of("Alpha", "Beta", "Gamma", "Delta", "Epsilon") source.subscribe(lambda value: print("Received {0}".format(value))) Output: .. code:: console Received Alpha Received Beta Received Gamma Received Delta Received Epsilon Operators and Chaining -------------------------- You can also derive new Observables using over 130 operators available in RxPY. Each operator will yield a new :class:`Observable ` that transforms emissions from the source in some way. For example, we can :func:`map() ` each `String` to its length, then :func:`filter() ` for lengths being at least 5. These will yield two separate Observables built off each other. .. code:: python from rx import of, operators as op source = of("Alpha", "Beta", "Gamma", "Delta", "Epsilon") composed = source.pipe( op.map(lambda s: len(s)), op.filter(lambda i: i >= 5) ) composed.subscribe(lambda value: print("Received {0}".format(value))) Output: .. code:: console Received 5 Received 5 Received 5 Received 7 Typically, you do not want to save Observables into intermediary variables for each operator, unless you want to have multiple subscribers at that point. Instead, you want to strive to inline and create an "Observable pipeline" of operations. That way your code is readable and tells a story much more easily. .. code:: python from rx import of, operators as op of("Alpha", "Beta", "Gamma", "Delta", "Epsilon").pipe( op.map(lambda s: len(s)), op.filter(lambda i: i >= 5) ).subscribe(lambda value: print("Received {0}".format(value))) Custom operator --------------- As operators chains grow up, the chains must be split to make the code more readable. New operators are implemented as functions, and can be directly used in the *pipe* operator. When an operator is implemented as a composition of other operators, then the implementation is straightforward, thanks to the *pipe* function: .. code:: python import rx from rx import operators as ops def length_more_than_5(): return rx.pipe( ops.map(lambda s: len(s)), ops.filter(lambda i: i >= 5), ) rx.of("Alpha", "Beta", "Gamma", "Delta", "Epsilon").pipe( length_more_than_5() ).subscribe(lambda value: print("Received {0}".format(value))) In this example, the *map* and *filter* operators are grouped in a new *length_more_than_5* operator. It is also possible to create an operator that is not a composition of other operators. This allows to fully control the subscription logic and items emissions: .. code:: python import rx def lowercase(): def _lowercase(source): def subscribe(observer, scheduler = None): def on_next(value): observer.on_next(value.lower()) return source.subscribe( on_next, observer.on_error, observer.on_completed, scheduler) return rx.create(subscribe) return _lowercase rx.of("Alpha", "Beta", "Gamma", "Delta", "Epsilon").pipe( lowercase() ).subscribe(lambda value: print("Received {0}".format(value))) In this example, the *lowercase* operator converts all received items to lowercase. The structure of the *_lowercase* function is a very common way to implement custom operators: It takes a source Observable as input, and returns a custom Observable. The source observable is subscribed only when the output Observable is subscribed. This allows to chain subscription calls when building a pipeline. Output: .. code:: console Received alpha Received beta Received gamma Received delta Received epsilon Concurrency ----------- CPU Concurrency ................ To achieve concurrency, you use two operators: :func:`subscribe_on() ` and :func:`observe_on() `. Both need a :ref:`Scheduler ` which provides a thread for each subscription to do work (see section on Schedulers below). The :class:`ThreadPoolScheduler ` is a good choice to create a pool of reusable worker threads. .. attention:: `GIL `_ has the potential to undermine your concurrency performance, as it prevents multiple threads from accessing the same line of code simultaneously. Libraries like `NumPy `_ can mitigate this for parallel intensive computations as they free the GIL. RxPy may also minimize thread overlap to some degree. Just be sure to test your application with concurrency and ensure there is a performance gain. The :func:`subscribe_on() ` instructs the source :class:`Observable ` at the start of the chain which scheduler to use (and it does not matter where you put this operator). The :func:`observe_on() `, however, will switch to a different *Scheduler* **at that point** in the *Observable* chain, effectively moving an emission from one thread to another. Some :ref:`Observable factories ` and :ref:`operators `, like :func:`interval() ` and :func:`delay() `, already have a default *Scheduler* and thus will ignore any :func:`subscribe_on() ` you specify (although you can pass a *Scheduler* usually as an argument). Below, we run three different processes concurrently rather than sequentially using :func:`subscribe_on() ` as well as an :func:`observe_on() `. .. code:: python import multiprocessing import random import time from threading import current_thread import rx from rx.scheduler import ThreadPoolScheduler from rx import operators as ops def intense_calculation(value): # sleep for a random short duration between 0.5 to 2.0 seconds to simulate a long-running calculation time.sleep(random.randint(5, 20) * 0.1) return value # calculate number of CPUs, then create a ThreadPoolScheduler with that number of threads optimal_thread_count = multiprocessing.cpu_count() pool_scheduler = ThreadPoolScheduler(optimal_thread_count) # Create Process 1 rx.of("Alpha", "Beta", "Gamma", "Delta", "Epsilon").pipe( ops.map(lambda s: intense_calculation(s)), ops.subscribe_on(pool_scheduler) ).subscribe( on_next=lambda s: print("PROCESS 1: {0} {1}".format(current_thread().name, s)), on_error=lambda e: print(e), on_completed=lambda: print("PROCESS 1 done!"), ) # Create Process 2 rx.range(1, 10).pipe( ops.map(lambda s: intense_calculation(s)), ops.subscribe_on(pool_scheduler) ).subscribe( on_next=lambda i: print("PROCESS 2: {0} {1}".format(current_thread().name, i)), on_error=lambda e: print(e), on_completed=lambda: print("PROCESS 2 done!"), ) # Create Process 3, which is infinite rx.interval(1).pipe( ops.map(lambda i: i * 100), ops.observe_on(pool_scheduler), ops.map(lambda s: intense_calculation(s)), ).subscribe( on_next=lambda i: print("PROCESS 3: {0} {1}".format(current_thread().name, i)), on_error=lambda e: print(e), ) input("Press any key to exit\n") **OUTPUT:** .. code:: console Press any key to exit PROCESS 1: Thread-1 Alpha PROCESS 2: Thread-2 1 PROCESS 3: Thread-4 0 PROCESS 2: Thread-2 2 PROCESS 1: Thread-1 Beta PROCESS 3: Thread-7 100 PROCESS 3: Thread-7 200 PROCESS 2: Thread-2 3 PROCESS 1: Thread-1 Gamma PROCESS 1: Thread-1 Delta PROCESS 2: Thread-2 4 PROCESS 3: Thread-7 300 IO Concurrency ................ IO concurrency is also supported for several asynchronous frameworks, in combination with associated RxPY schedulers. The following example implements a simple echo TCP server that delays its answers by 5 seconds. It uses AsyncIO as an event loop. The TCP server is implemented in AsyncIO, and the echo logic is implemented as an RxPY operator chain. Futures allow the operator chain to drive the loop of the coroutine. .. code:: python from collections import namedtuple import asyncio import rx import rx.operators as ops from rx.subject import Subject from rx.scheduler.eventloop import AsyncIOScheduler EchoItem = namedtuple('EchoItem', ['future', 'data']) def tcp_server(sink, loop): def on_subscribe(observer, scheduler): async def handle_echo(reader, writer): print("new client connected") while True: data = await reader.readline() data = data.decode("utf-8") if not data: break future = asyncio.Future() observer.on_next(EchoItem( future=future, data=data )) await future writer.write(future.result().encode("utf-8")) print("Close the client socket") writer.close() def on_next(i): i.future.set_result(i.data) print("starting server") server = asyncio.start_server(handle_echo, '127.0.0.1', 8888, loop=loop) loop.create_task(server) sink.subscribe( on_next=on_next, on_error=observer.on_error, on_completed=observer.on_completed) return rx.create(on_subscribe) loop = asyncio.get_event_loop() proxy = Subject() source = tcp_server(proxy, loop) aio_scheduler = AsyncIOScheduler(loop=loop) source.pipe( ops.map(lambda i: i._replace(data="echo: {}".format(i.data))), ops.delay(5.0) ).subscribe(proxy, scheduler=aio_scheduler) loop.run_forever() print("done") loop.close() Execute this code from a shell, and connect to it via telnet. Then each line that you type is echoed 5 seconds later. .. code:: console telnet localhost 8888 Connected to localhost. Escape character is '^]'. foo echo: foo If you connect simultaneously from several clients, you can see that requests are correctly served, multiplexed on the AsyncIO event loop. Default Scheduler .................. There are several ways to choose a scheduler. The first one is to provide it explicitly to each operator that supports a scheduler. However this can be annoying when a lot of operators are used. So there is a second way to indicate what scheduler will be used as the default scheduler for the whole chain: The scheduler provided in the subscribe call is the default scheduler for all operators in a pipe. .. code:: python source.pipe( ... ).subscribe(proxy, scheduler=my_default_scheduler) Operators that accept a scheduler select the scheduler to use in the following way: - If a scheduler is provided for the operator, then use it. - If a default scheduler is provided in subscribe, then use it. - Otherwise use the default scheduler of the operator. RxPY-3.2.0/docs/index.rst000066400000000000000000000011461404130727200151510ustar00rootroot00000000000000.. RxPY documentation master file, created by sphinx-quickstart on Sat Feb 17 23:30:45 2018. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. The Reactive Extensions for Python (RxPY) ========================================== RxPY is a library for composing asynchronous and event-based programs using observable collections and pipable query operators in Python. .. toctree:: :maxdepth: 2 installation rationale get_started migration operators additional_reading reference contributing license RxPY-3.2.0/docs/installation.rst000066400000000000000000000003671404130727200165470ustar00rootroot00000000000000.. Installation Installation ============= RxPY v3.x runs on `Python `__ 3. To install RxPY: .. code:: python pip3 install rx For Python 2.x you need to use version 1.6 .. code:: python pip install rx==1.6.1 RxPY-3.2.0/docs/license.rst000066400000000000000000000021461404130727200154650ustar00rootroot00000000000000The MIT License =============== Copyright 2013-2019, Dag Brattli, Microsoft Corp., and Contributors. 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. RxPY-3.2.0/docs/migration.rst000066400000000000000000000156241404130727200160410ustar00rootroot00000000000000.. _migration: Migration ========= RxPY v3 is a major evolution from RxPY v1. This release brings many improvements, some of the most important ones being: * A better integration in IDEs via autocompletion support. * New operators can be implemented outside of RxPY. * Operator chains are now built via the :func:`pipe ` operator. * A default scheduler can be provided in an operator chain. Pipe Based Operator Chaining ----------------------------- The most fundamental change is the way operators are chained together. On RxPY v1, operators were methods of the Observable class. So they were chained by using the existing Observable methods: .. code:: python from rx import Observable Observable.of("Alpha", "Beta", "Gamma", "Delta", "Epsilon") \ .map(lambda s: len(s)) \ .filter(lambda i: i >= 5) \ .subscribe(lambda value: print("Received {0}".format(value))) Chaining in RxPY v3 is based on the :func:`pipe ` operator. This operator is now one of the only methods of the :class:`Observable ` class. In RxPY v3, operators are implemented as functions: .. code:: python import rx from rx import operators as ops rx.of("Alpha", "Beta", "Gamma", "Delta", "Epsilon").pipe( ops.map(lambda s: len(s)), ops.filter(lambda i: i >= 5) ).subscribe(lambda value: print("Received {0}".format(value))) The fact that operators are functions means that adding new operators is now very easy. Instead of wrapping custom operators with the *let* operator, they can be directly used in a pipe chain. Removal Of The Result Mapper ----------------------------- The mapper function is removed in operators that combine the values of several observables. This change applies to the following operators: :func:`combine_latest `, :func:`group_join `, :func:`join `, :func:`with_latest_from `, :func:`zip `, and :func:`zip_with_iterable `. In RxPY v1, these operators were used the following way: .. code:: python from rx import Observable import operator a = Observable.of(1, 2, 3, 4) b = Observable.of(2, 2, 4, 4) a.zip(b, lambda a, b: operator.mul(a, b)) \ .subscribe(print) Now they return an Observable of tuples, with each item being the combination of the source Observables: .. code:: python import rx from rx import operators as ops import operator a = rx.of(1, 2, 3, 4) b = rx.of(2, 2, 4, 4) a.pipe( ops.zip(b), # returns a tuple with the items of a and b ops.map(lambda z: operator.mul(z[0], z[1])) ).subscribe(print) Dealing with the tuple unpacking is made easier with the starmap operator that unpacks the tuple to args: .. code:: python import rx from rx import operators as ops import operator a = rx.of(1, 2, 3, 4) b = rx.of(2, 2, 4, 4) a.pipe( ops.zip(b), ops.starmap(operator.mul) ).subscribe(print) Scheduler Parameter In Create Operator --------------------------------------- The subscription function provided to the :func:`create ` operator now takes two parameters: An observer and a scheduler. The scheduler parameter is new: If a scheduler has been set in the call to subscribe, then this scheduler is passed to the subscription function. Otherwise this parameter is set to *None*. One can use or ignore this parameter. This new scheduler parameter allows the create operator to use the default scheduler provided in the subscribe call. So scheduling item emissions with relative or absolute due-time is now possible. Removal Of List Of Observables ------------------------------- The support of list of Observables as a parameter has been removed in the following operators: :func:`merge `, :func:`zip `, and :func:`combine_latest `. For example in RxPY v1 the *merge* operator could be called with a list: .. code:: python from rx import Observable obs1 = Observable.from_([1, 2, 3, 4]) obs2 = Observable.from_([5, 6, 7, 8]) res = Observable.merge([obs1, obs2]) res.subscribe(print) This is not possible anymore in RxPY v3. So Observables must be provided explicitly: .. code:: python import rx, operator as op obs1 = rx.from_([1, 2, 3, 4]) obs2 = rx.from_([5, 6, 7, 8]) res = rx.merge(obs1, obs2) res.subscribe(print) If for any reason the Observables are only available as a list, then they can be unpacked: .. code:: python import rx from rx import operators as ops obs1 = rx.from_([1, 2, 3, 4]) obs2 = rx.from_([5, 6, 7, 8]) obs_list = [obs1, obs2] res = rx.merge(*obs_list) res.subscribe(print) Blocking Observable ------------------- BlockingObservables have been removed from RxPY v3. In RxPY v1, blocking until an Observable completes was done the following way: .. code:: python from rx import Observable res = Observable.from_([1, 2, 3, 4]).to_blocking().last() print(res) This is now done with the :func:`run ` operator: .. code:: python import rx res = rx.from_([1, 2, 3, 4]).run() print(res) The *run* operator returns only the last value emitted by the source Observable. It is possible to use the previous blocking operators by using the standard operators before *run*. For example: * Get first item: obs.pipe(ops.first()).run() * Get all items: obs.pipe(ops.to_list()).run() Back-Pressure -------------- Support for back-pressure - and so ControllableObservable - has been removed in RxPY v3. Back-pressure can be implemented in several ways, and many strategies can be adopted. So we consider that such features are beyond the scope of RxPY. You are encouraged to provide independent implementations as separate packages so that they can be shared by the community. List of community projects supporting backpressure can be found in :ref:`additional_reading`. Time Is In Seconds ------------------ Operators that take time values as parameters now use seconds as a unit instead of milliseconds. This RxPY v1 example: .. code:: python ops.debounce(500) is now written as: .. code:: python ops.debounce(0.5) Packages Renamed ---------------- Some packages were renamed: +-----------------------+-------------------------+ | Old name | New name | +-----------------------+-------------------------+ | *rx.concurrency* | *rx.scheduler* | +-----------------------+-------------------------+ | *rx.disposables* | *rx.disposable* | +-----------------------+-------------------------+ | *rx.subjects* | *rx.subject* | +-----------------------+-------------------------+ Furthermore, the package formerly known as *rx.concurrency.mainloopscheduler* has been split into two parts, *rx.scheduler.mainloop* and *rx.scheduler.eventloop*. RxPY-3.2.0/docs/operators.rst000066400000000000000000000322251404130727200160620ustar00rootroot00000000000000.. _operators: Operators ========== Creating Observables --------------------- ====================================== ================================================ Operator Description ====================================== ================================================ :func:`create ` Create an Observable from scratch by calling observer methods programmatically. :func:`empty ` Creates an Observable that emits no item and completes immediately. :func:`never ` Creates an Observable that never completes. :func:`throw ` Creates an Observable that terminates with an error. :func:`from_ ` Convert some other object or data structure into an Observable. :func:`interval ` Create an Observable that emits a sequence of integers spaced by a particular time interval. :func:`just ` Convert an object or a set of objects into an Observable that emits that object or those objects. :func:`range ` Create an Observable that emits a range of sequential integers. :func:`repeat_value ` Create an Observable that emits a particular item or sequence of items repeatedly. :func:`start ` Create an Observable that emits the return value of a function. :func:`timer ` Create an Observable that emits a single item after a given delay. ====================================== ================================================ Transforming Observables ------------------------ ========================================= ================================================ Operator Description ========================================= ================================================ :func:`buffer ` Periodically gather items from an Observable into bundles and emit these bundles rather than emitting the items one at a time. :func:`flat_map ` Transform the items emitted by an Observable into Observables, then flatten the emissions from those into a single Observable. :func:`group_by ` Divide an Observable into a set of Observables that each emit a different group of items from the original Observable, organized by key. :func:`map ` Transform the items emitted by an Observable by applying a function to each item. :func:`scan ` Apply a function to each item emitted by an Observable, sequentially, and emit each successive value. :func:`window ` Periodically subdivide items from an Observable into Observable windows and emit these windows rather than emitting the items one at a time. ========================================= ================================================ Filtering Observables ---------------------- ====================================================== ================================================ Operator Description ====================================================== ================================================ :func:`debounce ` Only emit an item from an Observable if a particular timespan has passed without it emitting another item. :func:`distinct ` Suppress duplicate items emitted by an Observable. :func:`element_at ` Emit only item n emitted by an Observable. :func:`filter ` Emit only those items from an Observable that pass a predicate test. :func:`first ` Emit only the first item, or the first item that meets a condition, from an Observable. :func:`ignore_elements ` Do not emit any items from an Observable but mirror its termination notification. :func:`last ` Emit only the last item emitted by an Observable. :func:`sample ` Emit the most recent item emitted by an Observable within periodic time intervals. :func:`skip ` Suppress the first n items emitted by an Observable. :func:`skip_last ` Suppress the last n items emitted by an Observable. :func:`take ` Emit only the first n items emitted by an Observable. :func:`take_last ` Emit only the last n items emitted by an Observable. ====================================================== ================================================ Combining Observables ---------------------- ====================================================== ================================================ Operator Description ====================================================== ================================================ :func:`combine_latest ` When an item is emitted by either of two Observables, combine the latest item emitted by each Observable via a specified function and emit items based on the results of this function. :func:`join ` Combine items emitted by two Observables whenever an item from one Observable is emitted during a time window defined according to an item emitted by the other Observable. :func:`merge ` Combine multiple Observables into one by merging their emissions. :func:`start_with ` Emit a specified sequence of items before beginning to emit the items from the source Observable. :func:`switch_latest ` Convert an Observable that emits Observables into a single Observable that emits the items emitted by the most-recently-emitted of those Observables. :func:`zip ` Combine the emissions of multiple Observables together via a specified function and emit single items for each combination based on the results of this function. :func:`fork_join ` Wait for Observables to complete and then combine last values they emitted into a tuple. ====================================================== ================================================ Error Handling --------------- ====================================================== ================================================ Operator Description ====================================================== ================================================ :func:`catch ` Continues observable sequences which are terminated with an exception by switching over to the next observable sequence. :func:`retry ` If a source Observable sends an onError notification, resubscribe to it in the hopes that it will complete without error. ====================================================== ================================================ Utility Operators ------------------ ====================================================== ================================================ Operator Description ====================================================== ================================================ :func:`delay ` Shift the emissions from an Observable forward in time by a particular amount. :func:`do ` Register an action to take upon a variety of Observable lifecycle events. :func:`materialize ` Materializes the implicit notifications of an observable sequence as explicit notification values. :func:`dematerialize ` Dematerializes the explicit notification values of an observable sequence as implicit notifications. :func:`observe_on ` Specify the scheduler on which an observer will observe this Observable. :meth:`subscribe ` Operate upon the emissions and notifications from an Observable. :func:`subscribe_on ` Specify the scheduler an Observable should use when it is subscribed to. :func:`time_interval ` Convert an Observable that emits items into one that emits indications of the amount of time elapsed between those emissions. :func:`timeout ` Mirror the source Observable, but issue an error notification if a particular period of time elapses without any emitted items. :func:`timestamp ` Attach a timestamp to each item emitted by an Observable. ====================================================== ================================================ Conditional and Boolean Operators ---------------------------------- ========================================================== ================================================ Operator Description ========================================================== ================================================ :func:`all ` Determine whether all items emitted by an Observable meet some criteria. :func:`amb ` Given two or more source Observables, emit all of the items from only the first of these Observables to emit an item. :func:`contains ` Determine whether an Observable emits a particular item or not. :func:`default_if_empty ` Emit items from the source Observable, or a default item if the source Observable emits nothing. :func:`sequence_equal ` Determine whether two Observables emit the same sequence of items. :func:`skip_until ` Discard items emitted by an Observable until a second Observable emits an item. :func:`skip_while ` Discard items emitted by an Observable until a specified condition becomes false. :func:`take_until ` Discard items emitted by an Observable after a second Observable emits an item or terminates. :func:`take_whle ` Discard items emitted by an Observable after a specified condition becomes false. ========================================================== ================================================ Mathematical and Aggregate Operators ------------------------------------- ========================================= ================================================ Operator Description ========================================= ================================================ :func:`average ` Calculates the average of numbers emitted by an Observable and emits this average. :func:`concat ` Emit the emissions from two or more Observables without interleaving them. :func:`count ` Count the number of items emitted by the source Observable and emit only this value. :func:`max ` Determine, and emit, the maximum-valued item emitted by an Observable. :func:`min ` Determine, and emit, the minimum-valued item emitted by an Observable. :func:`reduce ` Apply a function to each item emitted by an Observable, sequentially, and emit the final value. :func:`sum ` Calculate the sum of numbers emitted by an Observable and emit this sum. ========================================= ================================================ Connectable Observable Operators --------------------------------- ===================================================== ================================================ Operator Description ===================================================== ================================================ :meth:`connect ` Instruct a connectable Observable to begin emitting items to its subscribers. :func:`publish ` Convert an ordinary Observable into a connectable Observable. :func:`ref_count ` Make a Connectable Observable behave like an ordinary Observable. :func:`replay ` Ensure that all observers see the same sequence of emitted items, even if they subscribe after the Observable has begun emitting items. ===================================================== ================================================ RxPY-3.2.0/docs/rationale.rst000066400000000000000000000025001404130727200160130ustar00rootroot00000000000000.. Rationale Rationale ========== Reactive Extensions for Python (RxPY) is a set of libraries for composing asynchronous and event-based programs using observable sequences and pipable query operators in Python. Using Rx, developers represent asynchronous data streams with Observables, query asynchronous data streams using operators, and parameterize concurrency in data/event streams using Schedulers. Using Rx, you can represent multiple asynchronous data streams (that come from diverse sources, e.g., stock quote, Tweets, computer events, web service requests, etc.), and subscribe to the event stream using the Observer object. The Observable notifies the subscribed Observer instance whenever an event occurs. You can put various transformations in-between the source Observable and the consuming Observer as well. Because Observable sequences are data streams, you can query them using standard query operators implemented as functions that can be chained with the pipe operator. Thus you can filter, map, reduce, compose and perform time-based operations on multiple events easily by using these operators. In addition, there are a number of other reactive stream specific operators that allow powerful queries to be written. Cancellation, exceptions, and synchronization are also handled gracefully by using dedicated operators. RxPY-3.2.0/docs/reference.rst000066400000000000000000000004441404130727200160000ustar00rootroot00000000000000.. reference: Reference ========== .. toctree:: :name: reference Observable Factory Observable Subject Scheduler Operators Typing RxPY-3.2.0/docs/reference_observable.rst000066400000000000000000000001361404130727200202020ustar00rootroot00000000000000.. _reference_observable: Observable =========== .. autoclass:: rx.Observable :members: RxPY-3.2.0/docs/reference_observable_factory.rst000066400000000000000000000001561404130727200217330ustar00rootroot00000000000000.. _reference_observable_factory: Observable Factory ===================== .. automodule:: rx :members: RxPY-3.2.0/docs/reference_operators.rst000066400000000000000000000001321404130727200200700ustar00rootroot00000000000000.. _reference_operators: Operators ========= .. automodule:: rx.operators :members: RxPY-3.2.0/docs/reference_scheduler.rst000066400000000000000000000012401404130727200200310ustar00rootroot00000000000000.. _reference_scheduler: Schedulers =========== .. automodule:: rx.scheduler :members: CatchScheduler, CurrentThreadScheduler, EventLoopScheduler, HistoricalScheduler, ImmediateScheduler, NewThreadScheduler, ThreadPoolScheduler, TimeoutScheduler, TrampolineScheduler, VirtualTimeScheduler .. automodule:: rx.scheduler.eventloop :members: AsyncIOScheduler, AsyncIOThreadSafeScheduler, EventletScheduler, GEventScheduler, IOLoopScheduler, TwistedScheduler .. automodule:: rx.scheduler.mainloop :members: GtkScheduler, PyGameScheduler, QtScheduler, TkinterScheduler, WxScheduler RxPY-3.2.0/docs/reference_subject.rst000066400000000000000000000004001404130727200175070ustar00rootroot00000000000000.. _reference_subject: Subject ======== .. autoclass:: rx.subject.Subject :members: .. autoclass:: rx.subject.BehaviorSubject :members: .. autoclass:: rx.subject.ReplaySubject :members: .. autoclass:: rx.subject.AsyncSubject :members: RxPY-3.2.0/docs/reference_typing.rst000066400000000000000000000001241404130727200173650ustar00rootroot00000000000000.. _reference_typing: Typing ======= .. automodule:: rx.core.typing :members: RxPY-3.2.0/docs/requirements.txt000066400000000000000000000001431404130727200165700ustar00rootroot00000000000000sphinx>=2.0 sphinx-autodoc-typehints>=1.10.3 guzzle_sphinx_theme>=0.7.11 sphinxcontrib_dooble>=1.0 RxPY-3.2.0/examples/000077500000000000000000000000001404130727200141745ustar00rootroot00000000000000RxPY-3.2.0/examples/asyncio/000077500000000000000000000000001404130727200156415ustar00rootroot00000000000000RxPY-3.2.0/examples/asyncio/await.py000066400000000000000000000004261404130727200173220ustar00rootroot00000000000000import asyncio import rx stream = rx.just("Hello, world!") async def hello_world(): n = await stream print(n) loop = asyncio.get_event_loop() # Blocking call which returns when the hello_world() coroutine is done loop.run_until_complete(hello_world()) loop.close() RxPY-3.2.0/examples/asyncio/toasyncgenerator.py000066400000000000000000000033631404130727200216070ustar00rootroot00000000000000import asyncio from asyncio import Future import rx from rx import operators as ops from rx.scheduler.eventloop import AsyncIOScheduler from rx.core import Observable def to_async_generator(sentinel=None): loop = asyncio.get_event_loop() future = Future() notifications = [] def _to_async_generator(source: Observable): def feeder(): nonlocal future if not notifications or future.done(): return notification = notifications.pop(0) if notification.kind == "E": future.set_exception(notification.exception) elif notification.kind == "C": future.set_result(sentinel) else: future.set_result(notification.value) def on_next(value): """Takes on_next values and appends them to the notification queue""" notifications.append(value) loop.call_soon(feeder) source.pipe(ops.materialize()).subscribe(on_next) @asyncio.coroutine def gen(): """Generator producing futures""" nonlocal future loop.call_soon(feeder) future = Future() return future return gen return _to_async_generator @asyncio.coroutine def go(loop): scheduler = AsyncIOScheduler(loop) xs = rx.from_([x for x in range(10)], scheduler=scheduler) gen = xs.pipe(to_async_generator()) # Wish we could write something like: # ys = (x for x in yield from gen()) while True: x = yield from gen() if x is None: break print(x) def main(): loop = asyncio.get_event_loop() loop.run_until_complete(go(loop)) if __name__ == '__main__': main() RxPY-3.2.0/examples/asyncio/toasynciterator.py000066400000000000000000000036031404130727200214470ustar00rootroot00000000000000import asyncio from asyncio import Future import rx from rx import operators as ops from rx import Observable from rx.scheduler.eventloop import AsyncIOScheduler def to_async_iterable(): def _to_async_iterable(source: Observable): class AIterable: def __aiter__(self): class AIterator: def __init__(self): self.notifications = [] self.future = Future() source.pipe(ops.materialize()).subscribe(self.on_next) def feeder(self): if not self.notifications or self.future.done(): return notification = self.notifications.pop(0) dispatch = { 'N': lambda: self.future.set_result(notification.value), 'E': lambda: self.future.set_exception(notification.exception), 'C': lambda: self.future.set_exception(StopAsyncIteration) } dispatch[notification.kind]() def on_next(self, notification): self.notifications.append(notification) self.feeder() async def __anext__(self): self.feeder() value = await self.future self.future = Future() return value return AIterator() return AIterable() return _to_async_iterable async def go(loop): scheduler = AsyncIOScheduler(loop) ai = rx.range(0, 10, scheduler=scheduler).pipe(to_async_iterable()) async for x in ai: print("got %s" % x) def main(): loop = asyncio.get_event_loop() loop.run_until_complete(go(loop)) if __name__ == '__main__': main() RxPY-3.2.0/examples/autocomplete/000077500000000000000000000000001404130727200166755ustar00rootroot00000000000000RxPY-3.2.0/examples/autocomplete/autocomplete.js000066400000000000000000000013471404130727200217410ustar00rootroot00000000000000(function (global, $, undefined) { function main() { var $input = $('#textInput'), $results = $('#results'); var ws = new WebSocket("ws://localhost:8080/ws"); $input.keyup(function(ev) { var msg = { term: ev.target.value }; ws.send(JSON.stringify(msg)); }); ws.onmessage = function(msg) { var data = JSON.parse(msg.data); var res = data[1]; // Append the results $results.empty(); $.each(res, function (_, value) { $('
  • ' + value + '
  • ').appendTo($results); }); $results.show(); } } main(); }(window, jQuery));RxPY-3.2.0/examples/autocomplete/autocomplete.py000066400000000000000000000053551404130727200217600ustar00rootroot00000000000000""" RxPY example running a Tornado server doing search queries against Wikipedia to populate the autocomplete dropdown in the web UI. Start using `python autocomplete.py` and navigate your web browser to http://localhost:8080 Uses the RxPY IOLoopScheduler. """ import os from tornado.websocket import WebSocketHandler from tornado.web import RequestHandler, StaticFileHandler, Application, url from tornado.httpclient import AsyncHTTPClient from tornado.httputil import url_concat from tornado.escape import json_decode from tornado import ioloop from rx import operators as ops from rx.subject import Subject from rx.scheduler.eventloop import IOLoopScheduler scheduler = IOLoopScheduler(ioloop.IOLoop.current()) def search_wikipedia(term): """Search Wikipedia for a given term""" url = 'http://en.wikipedia.org/w/api.php' params = { "action": 'opensearch', "search": term, "format": 'json' } # Must set a user agent for non-browser requests to Wikipedia user_agent = "RxPY/3.0 (https://github.com/dbrattli/RxPY; dag@brattli.net) Tornado/4.0.1" url = url_concat(url, params) http_client = AsyncHTTPClient() return http_client.fetch(url, method='GET', user_agent=user_agent) class WSHandler(WebSocketHandler): def open(self): print("WebSocket opened") # A Subject is both an observable and observer, so we can both subscribe # to it and also feed (send) it with new values self.stream = Subject() # Get all distinct key up events from the input and only fire if long enough and distinct searcher = self.stream.pipe( ops.map(lambda x: x["term"]), ops.filter(lambda text: len(text) > 2), # Only if the text is longer than 2 characters ops.debounce(0.750), # Pause for 750ms ops.distinct_until_changed(), # Only if the value has changed ops.flat_map_latest(search_wikipedia) ) def send_response(x): self.write_message(x.body) def on_error(ex): print(ex) searcher.subscribe(send_response, on_error, scheduler=scheduler) def on_message(self, message): obj = json_decode(message) self.stream.on_next(obj) def on_close(self): print("WebSocket closed") class MainHandler(RequestHandler): def get(self): self.render("index.html") def main(): port = os.environ.get("PORT", 8080) app = Application([ url(r"/", MainHandler), (r'/ws', WSHandler), (r'/static/(.*)', StaticFileHandler, {'path': "."}) ]) print("Starting server at port: %s" % port) app.listen(port) ioloop.IOLoop.current().start() if __name__ == '__main__': main() RxPY-3.2.0/examples/autocomplete/autocomplete_asyncio.py000066400000000000000000000055531404130727200235050ustar00rootroot00000000000000""" RxPY example running a Tornado server doing search queries against Wikipedia to populate the autocomplete dropdown in the web UI. Start using `python autocomplete.py` and navigate your web browser to http://localhost:8080 Uses the RxPY AsyncIOScheduler (Python 3.4 is required) """ import os import asyncio from tornado.platform.asyncio import AsyncIOMainLoop from tornado.escape import json_decode from tornado.httputil import url_concat from tornado.httpclient import AsyncHTTPClient from tornado.web import RequestHandler, StaticFileHandler, Application, url from tornado.websocket import WebSocketHandler from rx import operators as ops from rx.scheduler.eventloop import AsyncIOScheduler from rx.subject import Subject def search_wikipedia(term): """Search Wikipedia for a given term""" url = 'http://en.wikipedia.org/w/api.php' params = { "action": 'opensearch', "search": term, "format": 'json' } # Must set a user agent for non-browser requests to Wikipedia user_agent = "RxPY/1.0 (https://github.com/dbrattli/RxPY; dag@brattli.net) Tornado/4.0.1" url = url_concat(url, params) http_client = AsyncHTTPClient() return http_client.fetch(url, method='GET', user_agent=user_agent) class WSHandler(WebSocketHandler): def open(self): scheduler = AsyncIOScheduler(asyncio.get_event_loop()) print("WebSocket opened") # A Subject is both an observable and observer, so we can both subscribe # to it and also feed (send) it with new values self.subject = Subject() # Get all distinct key up events from the input and only fire if long enough and distinct searcher = self.subject.pipe( ops.map(lambda x: x["term"]), ops.filter(lambda text: len(text) > 2), # Only if the text is longer than 2 characters ops.debounce(0.750), # Pause for 750ms ops.distinct_until_changed(), # Only if the value has changed ops.flat_map_latest(search_wikipedia) ) def send_response(x): self.write_message(x.body) def on_error(ex): print(ex) searcher.subscribe(send_response, on_error, scheduler=scheduler) def on_message(self, message): obj = json_decode(message) self.subject.on_next(obj) def on_close(self): print("WebSocket closed") class MainHandler(RequestHandler): def get(self): self.render("index.html") def main(): AsyncIOMainLoop().make_current() port = os.environ.get("PORT", 8080) app = Application([ url(r"/", MainHandler), (r'/ws', WSHandler), (r'/static/(.*)', StaticFileHandler, {'path': "."}) ]) print("Starting server at port: %s" % port) app.listen(port) asyncio.get_event_loop().run_forever() if __name__ == '__main__': main() RxPY-3.2.0/examples/autocomplete/bottle_autocomplete.py000066400000000000000000000044731404130727200233310ustar00rootroot00000000000000""" The automcomplete example rewritten for bottle / gevent. - Requires besides bottle and gevent also the geventwebsocket pip package - Instead of a future we create the inner stream for flat_map_latest manually """ from bottle import request, Bottle, abort import gevent from geventwebsocket import WebSocketError from geventwebsocket.handler import WebSocketHandler import json, requests import rx from rx.subject import Subject from rx.scheduler.eventloop import GEventScheduler class WikiFinder: tmpl = 'http://en.wikipedia.org/w/api.php' tmpl += '?action=opensearch&search=%s&format=json' def __init__(self, term): self.res = r = gevent.event.AsyncResult() gevent.spawn(lambda: requests.get(self.tmpl % term).text).link(r) def subscribe(self, on_next, on_err, on_compl): try: self.res.get() on_next(self.res.value) except Exception as ex: on_err(ex.args) on_compl() app, PORT = Bottle(), 8081 scheduler = GEventScheduler(gevent) @app.route('/ws') def handle_websocket(): wsock = request.environ.get('wsgi.websocket') if not wsock: abort(400, 'Expected WebSocket request.') stream = Subject() query = stream.map( lambda x: x["term"] ).filter( lambda text: len(text) > 2 # Only if text is longer than 2 characters ).debounce( 0.750, # Pause for 750ms scheduler=scheduler ).distinct_until_changed() # Only if the value has changed searcher = query.flat_map_latest(lambda term: WikiFinder(term)) def send_response(x): wsock.on_next(x) def on_error(ex): print(ex) searcher.subscribe(send_response, on_error) while True: try: message = wsock.receive() # like {'term': ''} obj = json.loads(message) stream.on_next(obj) except WebSocketError: break @app.route('/static/autocomplete.js') def get_js(): # blatantly ignoring bottle's template engine: return open('autocomplete.js').read().replace('8080', str(PORT)) @app.route('/') def get_index(): return open("index.html").read() if __name__ == '__main__': h = ('0.0.0.0', PORT) server = gevent.pywsgi.WSGIServer(h, app, handler_class=WebSocketHandler) server.serve_forever() RxPY-3.2.0/examples/autocomplete/index.html000066400000000000000000000024341404130727200206750ustar00rootroot00000000000000 Rx for Python Rocks!
    RxPY-3.2.0/examples/chess/000077500000000000000000000000001404130727200153015ustar00rootroot00000000000000RxPY-3.2.0/examples/chess/chess.py000066400000000000000000000050061404130727200167610ustar00rootroot00000000000000import sys from os.path import dirname, join import pygame from rx import operators as ops from rx.subject import Subject from rx.scheduler.mainloop import PyGameScheduler #FORMAT = '%(asctime)-15s %(threadName)s %(message)s' #logging.basicConfig(format=FORMAT, level=logging.DEBUG) #log = logging.getLogger('Rx') def main(): pygame.init() size = 500, 500 screen = pygame.display.set_mode(size) pygame.display.set_caption("Rx for Python rocks") black = 0, 0, 0 background = pygame.Surface(screen.get_size()) background.fill(black) # fill the background black background = background.convert() # prepare for faster blitting scheduler = PyGameScheduler(pygame) mousemove = Subject() color = "white" base = dirname(__file__) files = [join(base, img % color) for img in [ "chess_rook_%s.png", "chess_knight_%s.png", "chess_bishop_%s.png", "chess_king_%s.png", "chess_queen_%s.png", "chess_bishop_%s.png", "chess_knight_%s.png", "chess_rook_%s.png" ]] images = [pygame.image.load(image).convert_alpha() for image in files] old = [None] * len(images) draw = [] erase = [] def handle_image(i, image): imagerect = image.get_rect() def on_next(ev): imagerect.top = ev[1] imagerect.left = ev[0] + i * 32 if old[i]: erase.append(old[i]) old[i] = imagerect.copy() draw.append((image, imagerect.copy())) def on_error(err): print("Got error: %s" % err) sys.exit() mousemove.pipe( ops.delay(0.1 * i, scheduler=scheduler) ).subscribe(on_next, on_error=on_error) for i, image in enumerate(images): handle_image(i, image) while True: for event in pygame.event.get(): if event.type == pygame.MOUSEMOTION: pos = event.pos mousemove.on_next(pos) elif event.type == pygame.QUIT: sys.exit() if len(draw): update = [] for rect in erase: screen.blit(background, (rect.x, rect.y), rect) update.append(rect) for image, rect in draw: screen.blit(image, rect) update.append(rect) pygame.display.update(update) pygame.display.flip() draw = [] erase = [] scheduler.run() if __name__ == '__main__': main() RxPY-3.2.0/examples/chess/chess_bishop_black.png000066400000000000000000000041171404130727200216170ustar00rootroot00000000000000PNG  IHDR szztEXtSoftwareAdobe ImageReadyqe<"iTXtXML:com.adobe.xmp IDATxڴVKoU>sl'W4jme ׉.wHEjAd19}rCcc:L%D`kRFq)Ab܎bf,$R.˙(Dq/K2/KQx ̪ݽ{߲촢4, عlc?=eh\ϟYuG(๻,IENDB`RxPY-3.2.0/examples/chess/chess_bishop_white.png000066400000000000000000000037451404130727200216710ustar00rootroot00000000000000PNG  IHDR szztEXtSoftwareAdobe ImageReadyqe<"iTXtXML:com.adobe.xmp j%v}YIDATxڴVKoE>pD a!6d~@8pKP KI$$xAd^Ư<{굱׬t7U_=X$`Aa~A}soNླྀ ϢI.~KyrO ʹln& ~tÃ'~/9us%"~uh=usW={q[rLbq'>ꉏ2"`ppB 2"> Θt\HTḡmb2^HJ) qoN-vNZ^.E:4jy=~Hn͕.#ߎ iAr`F,@p{e:Ȅ%>Baxp`OU`g`wp rC*vbTu՟x i\"V5XeoSf*0넢)Zn,:}xWA% Mjfc!=5{P/}λy 0 G^W::3(s[AyJ̽,!!XZ &!_5׉{'O<ijRzEthL>fBLKQipzNq1*G!*@)(2GYD>1, \ؐ˥GH_,ۖ[8s!pp-bzD<g* ƍ^(+_~j=b-Y 0UYfG=sjs8$8c{aKDZ00 r )쇛ޘ"dmge~ CcD(tOv9}G6v͉I4(} S*݌2"$d- 49yy Dșąn8@w~us-d b$X"6;קmh\eqV7)6fV1u!>%hvozOw̵F=MIфQ*!((7B+poiPAPFYltV+RNr{A}aFg+RʥIENDB`RxPY-3.2.0/examples/chess/chess_king_black.png000066400000000000000000000037361404130727200212710ustar00rootroot00000000000000PNG  IHDR szztEXtSoftwareAdobe ImageReadyqe<"iTXtXML:com.adobe.xmp ವRIDATxڴWYOWf16 ƎMEaCBT" 'y(OUHEBR 6j^*ֆ Dlg9Hctw~;Y\EKqՍ~zYi0Z\j_ܗPU0PȨJ2>h}s.}70%;M~?X}2 0M0Nnxmݛ{dv=wQ-ʕ =[ë-\z|7o ;`ai1B[!q<?3%Bs4g$׾ % r, 6YTt:4 ~WGkPT /_c=GS$`x˫+q1;7h8""(Hev14x -,@յY^[G4Lq :a|~O.@y$ɃPJv$`ZXنix]1icFijF3`]htT0%g~ũڞ6G7wC2DUв Ox6 ϶YgܪYEY1~+rB#קOn #MN#6txR Gj S+~b~/"EK"Tmۏ..O9h k8nvȞ3 > nР EL㑕IENDB`RxPY-3.2.0/examples/chess/chess_king_white.png000066400000000000000000000036171404130727200213330ustar00rootroot00000000000000PNG  IHDR szztEXtSoftwareAdobe ImageReadyqe<"iTXtXML:com.adobe.xmp 5AIDATxڴWoG3m @ \"!!D"VR9Tz8Pǭ AU@\"JU>N.!!|$8vl]ǵ:{lX!1dO_)ߘxGև[9`- P| v"(k[q۴o2s g. " ׮'Wy,rKn}9|v#~|JgP,, 7 ޽wK7̼jMσf`-7 YkXр& Q'{ʯ9&,ðНYJn(Dw0Tss;Kf/@qť ݡ0ۭ3E8@9p 3ry ndt!~HFgl' ^xCP"NcJG e2sדU~ 7LOM<9C]вe^CظGʙDA?sa55,J 4@|vpJ3Pf%- H!a h@ڵ:m GrnYpc ҙ`<ˑ Sz´4l' `NaDm̴TB\$)11~(tPtnfaI(cBS'j.݃QMMGHW\T v|ӓG+IaOЬQ30@6ՍrB(,QxtS,G=At5c'M7%8@ulDGB$@nz-kRćE,RMlPZMwX!˩ :@J´LWAW@ȖmA$~11҈ #c~m zMd>NZ5T(Q(%dּuzy*wy=jYg .Ј]͘R== +{Fp+13 ZUPIENDB`RxPY-3.2.0/examples/chess/chess_knight_black.png000066400000000000000000000033711404130727200216200ustar00rootroot00000000000000PNG  IHDR szzbKGD pHYs  tIME 864IDATXíOl?`n06Ƣ1 EjR5C/T)U{zC=V=UQ4Q5FUAc6ػ]{׻v5tm쒑v}}{V8XYBY|P(n@|טX5k @YUQXKsTCC|]vŨ9'x0epp'/@lx9sk-9,wf466n;NOƧ+O^c'sX{ /3~!9bvqhCCwy&FG{D<_s|7i__$>'D",,,pw.O[jj2^JWw7Zmߺ~=8gcφ3E Bh+>pW^e< 8--Dkj~VgC[2tZkxI)QJ1?;OtxfDggW#_:O)HjRkVqۋ݀_Qx==?Jq+rX(VrLQ>(^ox 7o~}֥*EP I୊Ԛ|>_)g."0n}/"n5#WuRXUKKKB*srn$[]ZZ/8kgU[lAx^% bU׋b钻vmWO_reu9CM^^K&Fy1}{} Z20}g24'ƍxJUZJvF._`dW_(jkm|T:]~s{06:J-m@H=k;r䛵u--NbnrO8{$*+G&DZ_Ρf;@.$? TԖ*Lir.1e,z3N|J)U2α.6s}υAuc4l Y5xYc$}}\X}bn`#9w\:|0ccd2.;.τP?"e0K}_z{2Ɯ+ LPbPA`!<.j?9s.،KKROzHROIоKXX(P, E"Εl J1pTЯ_ iZr,˹l'Wp5$ =oI);5XS.X ,E` 6H`B O=|97>`&ojW,9n k-WFrՒ?hԏ0IENDB`RxPY-3.2.0/examples/chess/chess_knight_white.png000066400000000000000000000032461404130727200216650ustar00rootroot00000000000000PNG  IHDR szzbKGD pHYs  tIME 9 b3IDATXík\erv{YܕvmEmH$c  D-j&*JM[q1h5Z궻9s춻L;23ɜ{;Â!DNS,~^hO|E) kÇ0n[?obtg?z -]'F+c晼p{bz_ã -]=12`!3Cqǔ26_qwyd{23vB~ Qq7X3DdgF(&Vbb\>mrkK`[F&GOQ*ΣThw3u4t -05v5H&ww_ @ 66\|R)5B*THQ"J̦$ټ\hJ㞻w~е}`O*nR*P!/;E[rq͝$]@q'Xo;EH+PB/.5])I!p 3$Sh;HO=PggK} "LtZB8`BTP)BK&o^琙BrRQ}#_ 0 e98B`ό`#$?@Jje=h+"ݥXIs3V$vBٹܐe-wަPV shC4B)oS.#O?%ݲwŰX9ٗ~7&<6  vQ&3W-@ H3Bx BshRKz2 Ăc0Hmz\-̇xcN:7EcC=mgϝ: x d.\{/65~= X$L]8sGO> dk sSRͿ8B-@X#ŜСy-!n@So\R--M6#D+S?G~ ̭jԞM(32W^^! bHAfφ9dn =7odqsHWh.$gOs\k'rs ZTZ(c(@iۙ).#JˎR]uU8xt}L* cV6JH"˷BMMzHub8BJUƃzf.q'w'T<} ;>3=v2~ `Lp"ڊҼzo`{.TrKi5nWn`h+2 Ph@:Y1z>m+@*j=~E 1w,bBT$)|⁸Vbƭw1aT>!/}0 {a=տ3jn7~JI ,B#FI2< p<?({e|D; Y{_l[]n(uY{zrX851&[SqQӞ{p).KXIENDB`RxPY-3.2.0/examples/chess/chess_pawn_black.png000066400000000000000000000041561404130727200213030ustar00rootroot00000000000000PNG  IHDR szztEXtSoftwareAdobe ImageReadyqe<"iTXtXML:com.adobe.xmp J lIDATxWOU?3bUWZƤMlLC5&&&>}ڴ1w15mB3sg.,鐳Νq9;X,‹|xxXiOyXEeHLgq^mvs\RX?~ іSӰ wy yo_.*۰z_7~_B~%W \<1<: Pb(. qߜ = dX"d x=wR\]09i̲,+"Cv 4,Q@^(XJ%.S?5C ͭhH%+-04txOMuF{> 4!J6 =Axy(<5(P8۶ÏE |"$ Ɉ c4\.5Um\%xZZ4Es&P0tX[If2e"woI((G_wɼiKQ`=B/~8 J#,UE4FoDQ5x4^pZ$"R;CD-o&.4=kL{S;zKsmll eZ; /#xj!ZO744: dq̅!i{ HxVcS3SHqZ""hT5UG2B{a]d!UX_[UQ0{_O` pUpHC>0X*")G`cou,ED??<2:9263!8DҬ 34d8g`vf<ݹ@eDRѶ_H$i 2fLDLj15t@$Z0/H5~]jimQ57ۘc]J<< CGbI #~\ 踒]/(.P5 n7&"UD4Lf;a0QUadױ>8G\ T* 7AbOľIC:v*c*rޱR,U&KubڂD!fiu^EQf2]*L"{cVyYI\І%:9|k+ a]c_>k]>Z/jlPZk-\wH+(O* |T[Z)cWa~ &&f&jaIENDB`RxPY-3.2.0/examples/chess/chess_pawn_white.png000066400000000000000000000040061404130727200213410ustar00rootroot00000000000000PNG  IHDR szztEXtSoftwareAdobe ImageReadyqe<"iTXtXML:com.adobe.xmp äzIDATxWk\U?{|I,iSvэ֢֊Hp4A](KAr#JQ֍B&34$Кϙf}=INC2o:A9yw=;G4M|%EQ6;G:?kgQ? xw-p¬ãqSn&Q n\zgvV-5G/~x)F lBcrVGP"hf`Y8йcW48 Cˁ8C$ ;R3(x;t@RUxSO2,5e 4Vµ?2o^F z1ΡÚnbyB$~ttںq?n<4zmܸL* 1lґ9o!:1 {ɀc76Pq2:#I5 G<J01yߓdP.㤻iױDi@P[`N;k />@"T;WYWt ؕ5mn wLf@{ | Qky $Sw$S>!X]By^Vk,BQ Aq+{`T/ܗ2EzlFuӦS_ 3.=ט. @  `ep4ڸT&uہ0hOklo0 P*:d?i*)=VU^Y@'轐gcgLG'c㯟$>t{chP}krؐmX֛c"[*;cu@dMEwAmD{NwێM9-gAS{~zP$d(%ZHJ{49w1_^{ 0]Ow8AE4@}(n'-: P)u- =Y}?wK&!PߨJIENDB`RxPY-3.2.0/examples/chess/chess_queen_black.png000066400000000000000000000040051404130727200214440ustar00rootroot00000000000000PNG  IHDR szztEXtSoftwareAdobe ImageReadyqe<"iTXtXML:com.adobe.xmp E0yIDATxڴW[oUe\@<^>ЪBQʓW ?x}^x##C7\Й(csZ]W>iET-- XD^!+Q)Ɍ(wmߋ^?LsGp$z 6cqSEכRG+&)=IENDB`RxPY-3.2.0/examples/chess/chess_queen_white.png000066400000000000000000000037001404130727200215110ustar00rootroot00000000000000PNG  IHDR szztEXtSoftwareAdobe ImageReadyqe<"iTXtXML:com.adobe.xmp q,4IDATxڴW[oWsYNR! %ZTK/¼_P5R7 MտP*>ZHCc@J%)ֻv88nTW:=gwgY!:c+8 tnd,_~z ` ó.o(䳹勽яl~3Ш@ $뙨V Z@.{upaQ k+?Kɯ _e8$0$R/q(p'޽AAd>4 z ,kh/!S`ɏf=Ma+牉j{oD!!7 MƅkHH@LhșrQX!:*toT!0<-9ihvTGɂ(>zzV~di ˆО)⛤!~g΃(J0iT7x)wJH"S-WJ9A20pk֍RFp&\T7 k?姏(H, 0ۯ{.CY@'- ƁwAǸyQ }6NC\ LkQ-UYGjvccWiH pl@rDaW>b,۩:Zy md ,yv, I ϲQa: ﯮܶmdI3s k`4^lru_>-_޴#r,ɗ~2r8רگaCHI~~ 0[mw 倅/Jy`_`3~9`J.mh낚uT-`@W'@2@ FVzOݑb):A9`:ٳsd9aT0Ďe Nh#;dy$I9ߨl׈3!F@ke8B C;t`z^k) xw~v@OqLFFJ86q:O֌>^Wtw\`9NIENDB`RxPY-3.2.0/examples/chess/chess_rook_black.png000066400000000000000000000026161404130727200213070ustar00rootroot00000000000000PNG  IHDR szztEXtSoftwareAdobe ImageReadyqe<0IDATxڴWKoUeU<$NiU(JUDI.(,ZUkdJ$lP DMi.X4#&=~̌g9cP7$c0W:s̝{EX^Ryb,q"Żb7@O&yCWGGGFs}DkmON=ˋѸ:_!z?{mڻ$;nP-F hojN#dž1-bo_0@wO{^wЏe  GC,֍~^@FKCR\ z)44q"V?A{{>B,Fs)֤@^m[$6IiA(NO4h]&ϐJaZ&vw- &";Xa(xx* ,rI]/0Յ`Y󏖗 LSaYB֠iӕuBxŀٝ"`\A^çe5  ?qޯ~5IՁP`lWVUv1H ȊL(eR`.pT1$# %E8y̴n*ߒҵP2i.OwsaM❐q/#l- KBAsg{_R02˒9< 4&]}G`zRׁZU@EԢ\MuVSpe`PtEZzIENDB`RxPY-3.2.0/examples/chess/chess_rook_white.png000066400000000000000000000040771404130727200213560ustar00rootroot00000000000000PNG  IHDR szztEXtSoftwareAdobe ImageReadyqe<"iTXtXML:com.adobe.xmp %h].IDATxڴWk\E?3w~&i¦T*Ii(_ }>ڗ>t| Z T|J"fٝ;9wan67dfw>ck?/BlVyqRFC8k8o~|0忥Tщg.jߨSdO 3w'禦_|nhiܫ# gqR>!obɓP^Ylnƙ0nIp,nj˖R 8FQnhZ,A]ZDj JM' 0*3mD _xtqܱJUڠR@!pY]/( V)S(6 #~*@;ٗ eٿ6k3#}mͧu-vN C^Ղy`[t׬,~o߻i;fً4[vI@e)0.COjUdn gӧȴ񺾘ܺ>yqKU)W[ilUc~s.RwR ]q4!M8[qD=B @|oqf(+~AN/ Q2XeygV洋Fzz-3 +oL')FI8BAF戨Z`IXH `tdirCaP \Ws!f[jI11> zkS- پ2R`Kz8EX%]+qe#Pc2:if0a,<C'!7[(&V?:6[nf%DH^CCp([Ȇ XvԊ9xjy\LF oVTho>+ hZ ƽOkkX+7g65+?8쥗bwoC|vѧJ]\> L7SmZͷsl|K؋ |*T֗{zOb/z|ʭ#!}cmݞ-˔~1ݴ}5 ߻v3F "(( | /f$U`W:E^$v) aMj;+[)PIENDB`RxPY-3.2.0/examples/errors/000077500000000000000000000000001404130727200155105ustar00rootroot00000000000000RxPY-3.2.0/examples/errors/failing.py000066400000000000000000000012661404130727200175000ustar00rootroot00000000000000""" This example shows how you can retry subscriptions that might sometime fail with an error. Note: you cannot use ref_count() in this case since that would make publish() re-subscribe the cold-observable and it would start looping forever. """ import time import rx from rx import operators as ops def failing(x): x = int(x) if not x % 2: raise Exception("Error") return x def main(): xs = rx.from_marbles("1-2-3-4-5-6-7-9-|").pipe(ops.publish()) xs.pipe( ops.map(failing), ops.retry() ).subscribe(print) xs.connect() # Must connect. Cannot use ref_count() with publish() time.sleep(5) if __name__ == '__main__': main() RxPY-3.2.0/examples/konamicode/000077500000000000000000000000001404130727200163055ustar00rootroot00000000000000RxPY-3.2.0/examples/konamicode/index.html000066400000000000000000000170051404130727200203050ustar00rootroot00000000000000 Rx for Python Rocks!

    Enter the Konami Code

    BA

    RxPY-3.2.0/examples/konamicode/konamicode.js000066400000000000000000000005301404130727200207520ustar00rootroot00000000000000$(function () { var result = $('#result'); var ws = new WebSocket("ws://localhost:8080/ws"); $(document).keyup(function(ev) { msg = { keycode: ev.keyCode }; ws.send(JSON.stringify(msg)); }); ws.onmessage = function(msg) { result.html(msg.data).show().fadeOut(2000); // print the result }; });RxPY-3.2.0/examples/konamicode/konamicode.py000066400000000000000000000035721404130727200207770ustar00rootroot00000000000000import os from tornado.websocket import WebSocketHandler from tornado.web import RequestHandler, StaticFileHandler, Application, url from tornado.escape import json_decode from tornado import ioloop from rx import operators as ops from rx.subject import Subject UP, DOWN, LEFT, RIGHT, B, A = 38, 40, 37, 39, 66, 65 codes = [UP, UP, DOWN, DOWN, LEFT, RIGHT, LEFT, RIGHT, B, A] class WSHandler(WebSocketHandler): def open(self): print("WebSocket opened") # A Subject is both an observable and observer, so we can both subscribe # to it and also feed (on_next) it with new values self.subject = Subject() # Now we take on our magic glasses and project the stream of bytes into # a ... query = self.subject.pipe( # 1. stream of keycodes ops.map(lambda obj: obj["keycode"]), # 2. stream of windows (10 ints long) ops.window_with_count(10, 1), # 3. stream of booleans, True or False ops.flat_map(lambda win: win.pipe(ops.sequence_equal(codes))), # 4. stream of Trues ops.filter(lambda equal: equal) ) # 4. we then subscribe to the Trues, and signal Konami! if we see any query.subscribe(lambda x: self.write_message("Konami!")) def on_message(self, message): obj = json_decode(message) self.subject.on_next(obj) def on_close(self): print("WebSocket closed") class MainHandler(RequestHandler): def get(self): self.render("index.html") def main(): port = os.environ.get("PORT", 8080) app = Application([ url(r"/", MainHandler), (r'/ws', WSHandler), (r'/static/(.*)', StaticFileHandler, {'path': "."}) ]) print("Starting server at port: %s" % port) app.listen(port) ioloop.IOLoop.current().start() if __name__ == '__main__': main() RxPY-3.2.0/examples/marbles/000077500000000000000000000000001404130727200156215ustar00rootroot00000000000000RxPY-3.2.0/examples/marbles/frommarbles_error.py000066400000000000000000000005321404130727200217150ustar00rootroot00000000000000import rx from rx import operators as ops """ Specify the error to be raised in place of the # symbol. """ err = ValueError("I don't like 5!") src0 = rx.from_marbles('12-----4-----67--|', timespan=0.2) src1 = rx.from_marbles('----3----5-# ', timespan=0.2, error=err) source = rx.merge(src0, src1).pipe(ops.do_action(print)) source.run() RxPY-3.2.0/examples/marbles/frommarbles_flatmap.py000066400000000000000000000007461404130727200222170ustar00rootroot00000000000000import rx from rx import operators as ops a = rx.cold(' ---a0---a1----------------a2-| ') b = rx.cold(' ---b1---b2---| ') c = rx.cold(' ---c1---c2---| ') d = rx.cold(' -----d1---d2---|') e1 = rx.cold('a--b--------c-----d-------| ') observableLookup = {"a": a, "b": b, "c": c, "d": d} source = e1.pipe( ops.flat_map(lambda value: observableLookup[value]), ops.do_action(lambda v: print(v)), ) source.run() RxPY-3.2.0/examples/marbles/frommarbles_lookup.py000066400000000000000000000007451404130727200221030ustar00rootroot00000000000000 import rx import rx.operators as ops """ Use a dictionnary to convert elements declared in the marbles diagram to the specified values. """ lookup0 = {'a': 1, 'b': 3, 'c': 5} lookup1 = {'x': 2, 'y': 4, 'z': 6} source0 = rx.cold('a---b----c----|', timespan=0.01, lookup=lookup0) source1 = rx.cold('---x---y---z--|', timespan=0.01, lookup=lookup1) observable = rx.merge(source0, source1).pipe(ops.to_iterable()) elements = observable.run() print('received {}'.format(list(elements))) RxPY-3.2.0/examples/marbles/frommarbles_merge.py000066400000000000000000000005451404130727200216670ustar00rootroot00000000000000 import rx from rx import operators as ops """ simple example that merges two cold observables. """ source0 = rx.cold('a-----d---1--------4-|', timespan=0.01) source1 = rx.cold('--b-c-------2---3-| ', timespan=0.01) observable = rx.merge(source0, source1).pipe(ops.to_iterable()) elements = observable.run() print('received {}'.format(list(elements))) RxPY-3.2.0/examples/marbles/hot_datetime.py000066400000000000000000000006701404130727200206440ustar00rootroot00000000000000import datetime import rx import rx.operators as ops """ Delay the emission of elements to the specified datetime. """ now = datetime.datetime.utcnow() dt = datetime.timedelta(seconds=3.0) duetime = now + dt print('{} -> now\n' '{} -> start of emission in {}s'.format(now, duetime, dt.total_seconds())) hot = rx.hot('10--11--12--13--(14,|)', timespan=0.2, duetime=duetime) source = hot.pipe(ops.do_action(print)) source.run() RxPY-3.2.0/examples/marbles/testing_debounce.py000066400000000000000000000012201404130727200215070ustar00rootroot00000000000000from rx import operators as ops from rx.testing.marbles import marbles_testing """ Tests debounceTime from rxjs https://github.com/ReactiveX/rxjs/blob/master/spec/operators/debounceTime-spec.ts it should delay all element by the specified time """ with marbles_testing(timespan=1.0) as (start, cold, hot, exp): e1 = cold('-a--------b------c----|') ex = exp( '------a--------b------(c,|)') expected = ex def create(): return e1.pipe( ops.debounce(5), ) results = start(create) assert results == expected print('debounce: results vs expected') for r, e in zip(results, expected): print(r, e) RxPY-3.2.0/examples/marbles/testing_flatmap.py000066400000000000000000000017641404130727200213640ustar00rootroot00000000000000from rx import operators as ops from rx.testing.marbles import marbles_testing """ Tests MergeMap from rxjs https://github.com/ReactiveX/rxjs/blob/master/spec/operators/mergeMap-spec.ts it should flat_map many regular interval inners """ with marbles_testing(timespan=1.0) as context: start, cold, hot, exp = context a = cold(' ----a---a----a----(a,|) ') b = cold(' ----1----b----(b,|) ') c = cold(' -------c---c---c----c---(c,|)') d = cold(' -------(d,|) ') e1 = hot('-a---b-----------c-------d------------| ') ex = exp('-----a---(a,1)(a,b)(a,b)c---c---(c,d)c---(c,|)') expected = ex observableLookup = {"a": a, "b": b, "c": c, "d": d} obs = e1.pipe( ops.flat_map(lambda value: observableLookup[value]) ) results = start(obs) assert results == expected print('flat_map: results vs expected') for r, e in zip(results, expected): print(r, e) RxPY-3.2.0/examples/marbles/tomarbles.py000066400000000000000000000007141404130727200201650ustar00rootroot00000000000000import rx from rx import scheduler as ccy from rx import operators as ops source0 = rx.cold('a-----d---1--------4-|', timespan=0.1) source1 = rx.cold('--b-c-------2---3-| ', timespan=0.1) print("to_marbles() is a blocking operator, we need to wait for completion...") print('expecting "a-b-c-d---1-2---3--4-|"') observable = rx.merge(source0, source1).pipe(ops.to_marbles(timespan=0.1)) diagram = observable.run() print('got "{}"'.format(diagram)) RxPY-3.2.0/examples/parallel/000077500000000000000000000000001404130727200157705ustar00rootroot00000000000000RxPY-3.2.0/examples/parallel/timer.py000066400000000000000000000006751404130727200174720ustar00rootroot00000000000000import concurrent.futures import time import rx from rx import operators as ops seconds = [5, 1, 2, 4, 3] def sleep(tm): time.sleep(tm) return tm def output(result): print('%d seconds' % result) with concurrent.futures.ProcessPoolExecutor(5) as executor: rx.from_(seconds).pipe( ops.flat_map(lambda s: executor.submit(sleep, s)) ).subscribe(output) # 1 seconds # 2 seconds # 3 seconds # 4 seconds # 5 seconds RxPY-3.2.0/examples/timeflies/000077500000000000000000000000001404130727200161555ustar00rootroot00000000000000RxPY-3.2.0/examples/timeflies/timeflies_gtk.py000066400000000000000000000031411404130727200213540ustar00rootroot00000000000000import rx from rx import operators as ops from rx.subject import Subject from rx.scheduler.mainloop import GtkScheduler import gi gi.require_version('Gtk', '3.0') from gi.repository import GLib, Gtk, Gdk class Window(Gtk.Window): def __init__(self): super().__init__() self.resize(600, 600) self.add_events(Gdk.EventMask.POINTER_MOTION_MASK) self.connect('motion-notify-event', self.on_mouse_move) self.mousemove = Subject() def on_mouse_move(self, widget, event): self.mousemove.on_next((event.x, event.y)) def main(): scheduler = GtkScheduler(GLib) scrolled_window = Gtk.ScrolledWindow() window = Window() window.connect("delete-event", Gtk.main_quit) container = Gtk.Fixed() scrolled_window.add(container) window.add(scrolled_window) text = 'TIME FLIES LIKE AN ARROW' def on_next(info): label, (x, y), i = info container.move(label, x + i*12 + 15, y) label.show() def handle_label(label, i): delayer = ops.delay(i*0.100) mapper = ops.map(lambda xy: (label, xy, i)) return window.mousemove.pipe( delayer, mapper, ) def make_label(char): label = Gtk.Label(label=char) container.put(label, 0, 0) label.hide() return label mapper = ops.map(make_label) labeler = ops.flat_map_indexed(handle_label) rx.from_(text).pipe( mapper, labeler, ).subscribe(on_next, on_error=print, scheduler=scheduler) window.show_all() Gtk.main() if __name__ == '__main__': main() RxPY-3.2.0/examples/timeflies/timeflies_qt.py000066400000000000000000000031501404130727200212130ustar00rootroot00000000000000import sys import rx from rx import operators as ops from rx.subject import Subject from rx.scheduler.mainloop import QtScheduler try: from PySide2 import QtCore from PySide2.QtWidgets import QApplication, QLabel, QWidget except ImportError: try: from PyQt5 import QtCore from PyQt5.QtWidgets import QApplication, QWidget, QLabel except ImportError: raise ImportError('Please ensure either PySide2 or PyQt5 is available!') class Window(QWidget): def __init__(self): QWidget.__init__(self) self.setWindowTitle("Rx for Python rocks") self.resize(600, 600) self.setMouseTracking(True) # This Subject is used to transmit mouse moves to labels self.mousemove = Subject() def mouseMoveEvent(self, event): self.mousemove.on_next((event.x(), event.y())) def main(): app = QApplication(sys.argv) scheduler = QtScheduler(QtCore) window = Window() window.show() text = 'TIME FLIES LIKE AN ARROW' def on_next(info): label, (x, y), i = info label.move(x + i*12 + 15, y) label.show() def handle_label(label, i): delayer = ops.delay(i * 0.100) mapper = ops.map(lambda xy: (label, xy, i)) return window.mousemove.pipe( delayer, mapper, ) labeler = ops.flat_map_indexed(handle_label) mapper = ops.map(lambda c: QLabel(c, window)) rx.from_(text).pipe( mapper, labeler, ).subscribe(on_next, on_error=print, scheduler=scheduler) sys.exit(app.exec_()) if __name__ == '__main__': main() RxPY-3.2.0/examples/timeflies/timeflies_tkinter.py000066400000000000000000000021041404130727200222450ustar00rootroot00000000000000from tkinter import Tk, Label, Frame import rx from rx import operators as ops from rx.subject import Subject from rx.scheduler.mainloop import TkinterScheduler def main(): root = Tk() root.title("Rx for Python rocks") scheduler = TkinterScheduler(root) mousemove = Subject() frame = Frame(root, width=600, height=600) frame.bind("", mousemove.on_next) text = 'TIME FLIES LIKE AN ARROW' def on_next(info): label, ev, i = info label.place(x=ev.x + i*12 + 15, y=ev.y) def handle_label(label, i): label.config(dict(borderwidth=0, padx=0, pady=0)) mapper = ops.map(lambda ev: (label, ev, i)) delayer = ops.delay(i*0.1) return mousemove.pipe( delayer, mapper ) labeler = ops.flat_map_indexed(handle_label) mapper = ops.map(lambda c: Label(frame, text=c)) rx.from_(text).pipe( mapper, labeler ).subscribe(on_next, on_error=print, scheduler=scheduler) frame.pack() root.mainloop() if __name__ == '__main__': main() RxPY-3.2.0/examples/timeflies/timeflies_wx.py000066400000000000000000000027211404130727200212300ustar00rootroot00000000000000import rx from rx import operators as ops from rx.subject import Subject from rx.scheduler.mainloop import WxScheduler import wx class Frame(wx.Frame): def __init__(self): super(Frame, self).__init__(None) self.SetTitle("Rx for Python rocks") self.SetSize((600, 600)) # This Subject is used to transmit mouse moves to labels self.mousemove = Subject() self.Bind(wx.EVT_MOTION, self.OnMotion) def OnMotion(self, event): self.mousemove.on_next((event.GetX(), event.GetY())) def main(): app = wx.App() scheduler = WxScheduler(wx) app.TopWindow = frame = Frame() frame.Show() text = 'TIME FLIES LIKE AN ARROW' def on_next(info): label, (x, y), i = info label.Move(x + i*12 + 15, y) label.Show() def handle_label(label, i): delayer = ops.delay(i * 0.100) mapper = ops.map(lambda xy: (label, xy, i)) return frame.mousemove.pipe( delayer, mapper, ) def make_label(char): label = wx.StaticText(frame, label=char) label.Hide() return label mapper = ops.map(make_label) labeler = ops.flat_map_indexed(handle_label) rx.from_(text).pipe( mapper, labeler, ).subscribe(on_next, on_error=print, scheduler=scheduler) frame.Bind(wx.EVT_CLOSE, lambda e: (scheduler.cancel_all(), e.Skip())) app.MainLoop() if __name__ == '__main__': main() RxPY-3.2.0/notebooks/000077500000000000000000000000001404130727200143615ustar00rootroot00000000000000RxPY-3.2.0/notebooks/Getting Started.ipynb000066400000000000000000000273421404130727200204240ustar00rootroot00000000000000{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Getting Started with RxPY\n", "\n", "[ReactiveX](http://reactivex.io), or Rx for short, is an API for programming with observable event streams. RxPY is a port of ReactiveX to Python. Learning Rx with Python is particularly interesting since Python removes much of the clutter that comes with statically typed languages. RxPY works with both Python 2 and Python 3 but all examples in this tutorial uses [Python 3.4](http://www.python.org)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Rx is about processing streams of events. With Rx you:\n", "\n", "* Tell what you want to process (Observable)\n", "* How you want to process it (A composition of operators)\n", "* What you want to do with the result (Observer)\n", "\n", "It's important to understand that with Rx you describe what you want to do with events if and when they arrive. It's all a declarative composition of operators that will do some processing the events when they arrive. If nothing happens, then nothing is processed.\n", "\n", "Thus the pattern is that you `subscribe` to an `Observable` using an `Observer`:\n", "\n", "```python\n", "subscription = Observable.subscribe(observer)\n", "```\n", "\n", "***NOTE:*** Observables are not active in themselves. They need to be subscribed to make something happen. Simply having an Observable lying around doesn't make anything happen." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Install\n", "\n", "Use `pip` to install RxPY:" ] }, { "cell_type": "code", "execution_count": 41, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Requirement already satisfied (use --upgrade to upgrade): rx in /Users/dbrattli/GitHub/RxPY\n" ] } ], "source": [ "%%bash\n", "pip install rx" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Importing the Rx module" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "collapsed": true }, "outputs": [], "source": [ "import rx\n", "from rx import Observable, Observer" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Generating a sequence\n", "\n", "There are many ways to generate a sequence of events. The easiest way to get started is to use the `from_iterable()` operator that is also called just `from_`. Other operators you may use to generate a sequence such as `just`, `generate`, `create` and `range`." ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Got: 0\n", "Got: 1\n", "Got: 2\n", "Got: 3\n", "Got: 4\n", "Got: 5\n", "Got: 6\n", "Got: 7\n", "Got: 8\n", "Got: 9\n", "Sequence completed\n" ] } ], "source": [ "class MyObserver(Observer):\n", " def on_next(self, x):\n", " print(\"Got: %s\" % x)\n", " \n", " def on_error(self, e):\n", " print(\"Got error: %s\" % e)\n", " \n", " def on_completed(self):\n", " print(\"Sequence completed\")\n", "\n", "xs = Observable.from_iterable(range(10))\n", "d = xs.subscribe(MyObserver())" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0\n", "1\n", "2\n", "3\n", "4\n", "5\n", "6\n", "7\n", "8\n", "9\n" ] } ], "source": [ "xs = Observable.from_(range(10))\n", "d = xs.subscribe(print)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**NOTE:** The subscribe method takes an observer, or one to three callbacks for handing `on_next()`, `on_error()`, and `on_completed()`. This is why we can use `print` directly as the observer in the example above, since it becomes the `on_next()` handler for an anonymous observer. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Filtering a sequence" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "collapsed": false, "scrolled": true }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1\n", "3\n", "5\n", "7\n", "9\n" ] } ], "source": [ "xs = Observable.from_(range(10))\n", "d = xs.filter(\n", " lambda x: x % 2\n", " ).subscribe(print)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Transforming a sequence" ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0\n", "2\n", "4\n", "6\n", "8\n", "10\n", "12\n", "14\n", "16\n", "18\n" ] } ], "source": [ "xs = Observable.from_(range(10))\n", "d = xs.map(\n", " lambda x: x * 2\n", " ).subscribe(print)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**NOTE: ** You can also take an index as the second parameter to the mapper function:" ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0: 20\n", "1: 24\n", "2: 28\n", "3: 32\n", "4: 36\n" ] } ], "source": [ "xs = Observable.from_(range(10, 20, 2))\n", "d = xs.map(\n", " lambda x, i: \"%s: %s\" % (i, x * 2)\n", " ).subscribe(print)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Merge\n", "\n", "Merging two observable sequences into a single observable sequence using the `merge` operator:" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "a\n", "1\n", "b\n", "2\n", "c\n", "3\n", "d\n", "4\n", "e\n", "5\n" ] } ], "source": [ "xs = Observable.range(1, 5)\n", "ys = Observable.from_(\"abcde\")\n", "zs = xs.merge(ys).subscribe(print)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## The Spacetime of Rx\n", "\n", "In the examples above all the events happen at the same moment in time. The events are only separated by ordering. This confuses many newcomers to Rx since the result of the `merge` operation above may have several valid results such as:\n", "\n", " a1b2c3d4e5\n", " 1a2b3c4d5e\n", " ab12cd34e5\n", " abcde12345\n", " \n", "The only guarantee you have is that 1 will be before 2 in `xs`, but 1 in `xs` can be before or after `a` in `ys`. It's up the the sort stability of the scheduler to decide which event should go first. For real time data streams this will not be a problem since the events will be separated by actual time. To make sure you get the results you \"expect\", it's always a good idea to add some time between the events when playing with Rx." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Marbles and Marble Diagrams\n", "\n", "As we saw in the previous section it's nice to add some time when playing with Rx and RxPY. A great way to explore RxPY is to use the `marbles` test module that enables us to play with [marble diagrams](http://rxmarbles.com). The marbles module adds two new extension methods to `Observable`. The methods are `from_marbles()` and `to_marbles()`.\n", "\n", "Examples:\n", "1. `res = rx.Observable.from_marbles(\"1-2-3-|\")`\n", "2. `res = rx.Observable.from_marbles(\"1-2-3-x\", rx.Scheduler.timeout)`\n", "\n", "The marble string consists of some special characters:\n", "\n", "```\n", " - = Timespan of 100 ms\n", " x = on_error()\n", " | = on_completed()\n", "```\n", "\n", "All other characters are treated as an `on_next()` event at the given moment they are found on the string. If you need to represent multi character values, then you can group then with brackets such as \"1-(42)-3\". \n", "\n", "Lets try it out:" ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "'a-b-c-|'" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from rx.testing import marbles\n", "\n", "xs = Observable.from_marbles(\"a-b-c-|\")\n", "xs.to_blocking().to_marbles()" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "It's now easy to also add errors into the even stream by inserting `x` into the marble string:" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "'11-22-33-4x'" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "xs = Observable.from_marbles(\"1-2-3-x-5\")\n", "ys = Observable.from_marbles(\"1-2-3-4-5\")\n", "xs.merge(ys).to_blocking().to_marbles()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Subjects and Streams\n", "\n", "A simple way to create an observable stream is to use a subject. It's probably called a subject after the Subject-Observer pattern described in the [Design Patterns](http://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612/ref=sr_1_1?s=books&ie=UTF8&qid=1431184351&sr=1-1&keywords=design+patterns) book by the gang of four (GOF).\n", "\n", "Anyway, a Subject is both an `Observable` and an `Observer`, so you can both subscribe to it and `on_next` it with events. This makes it an obvious candidate if need to publish values into an observable stream for processing:" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Got: 42\n" ] } ], "source": [ "from rx.subject import Subject\n", "\n", "stream = Subject()\n", "stream.on_next(41)\n", "\n", "d = stream.subscribe(lambda x: print(\"Got: %s\" % x))\n", "\n", "stream.on_next(42)\n", "\n", "d.dispose()\n", "stream.on_next(43)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "*That's all for now*" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.4.3" } }, "nbformat": 4, "nbformat_minor": 0 } RxPY-3.2.0/notebooks/reactivex.io/000077500000000000000000000000001404130727200167615ustar00rootroot00000000000000RxPY-3.2.0/notebooks/reactivex.io/A Decision Tree of Observable Operators. Part I - Creation.ipynb000066400000000000000000001162551404130727200321650ustar00rootroot00000000000000{ "cells": [ { "cell_type": "code", "execution_count": 1, "metadata": { "collapsed": true }, "outputs": [], "source": [ "%run startup.py" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "collapsed": false }, "outputs": [ { "data": { "application/javascript": [ "$.getScript('./assets/js/ipython_notebook_toc.js')" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "%%javascript\n", "$.getScript('./assets/js/ipython_notebook_toc.js')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# A Decision Tree of Observable Operators\n", "\n", "## Part 1: NEW Observables.\n", "\n", "> source: http://reactivex.io/documentation/operators.html#tree. \n", "> (transcribed to RxPY 1.5.7, Py2.7 / 2016-12, Gunther Klessinger, [axiros](http://www.axiros.com)) \n", "\n", "**This tree can help you find the ReactiveX Observable operator you’re looking for.** \n", "\n", "

    Table of Contents

    \n", "
    \n", "\n", "## Usage\n", "\n", "There are no configured behind the scenes imports or code except [`startup.py`](./edit/startup.py), which defines output helper functions, mainly:\n", "\n", "- `rst, reset_start_time`: resets a global timer, in order to have use cases starting from 0.\n", "- `subs(observable)`: subscribes to an observable, printing notifications with time, thread, value\n", "\n", "\n", "All other code is explicitly given in the notebook. \n", "Since all initialisiation of tools is in the first cell, you always have to run the first cell after ipython kernel restarts. \n", "**All other cells are autonmous.**\n", "\n", "In the use case functions, in contrast to the official examples we simply use **`rand`** quite often (mapped to `randint(0, 100)`), to demonstrate when/how often observable sequences are generated and when their result is buffered for various subscribers. \n", "*When in doubt then run the cell again, you might have been \"lucky\" and got the same random.*\n", "\n", "### RxJS\n", "The (bold printed) operator functions are linked to the [official documentation](http://reactivex.io/documentation/operators.html#tree) and created roughly analogous to the **RxJS** examples. The rest of the TOC lines links to anchors within the notebooks. \n", "\n", "### Output\n", "When the output is not in marble format we display it like so:\n", "\n", "```\n", "new subscription on stream 276507289 \n", "\n", " 3.4 M [next] 1.4: {'answer': 42}\n", " 3.5 T1 [cmpl] 1.6: fin\n", " \n", "```\n", "where the lines are syncronously `print`ed as they happen. \"M\" and \"T1\" would be thread names (\"M\" is main thread). \n", "For each use case in `reset_start_time()` (alias `rst`), a global timer is set to 0 and we show the offset to it, in *milliseconds* & with one decimal value and also the offset to the start of stream subscription. In the example 3.4, 3.5 are millis since global counter reset, while 1.4, 1.6 are offsets to start of subscription.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# I want to create a **NEW** Observable..." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## ... that emits a particular item: **[just](http://reactivex.io/documentation/operators/just.html) **" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== return_value ==========\n", "\n", "module rx.linq.observable.returnvalue\n", "@extensionclassmethod(Observable, alias=\"just\")\n", "def return_value(cls, value, scheduler=None):\n", " Returns an observable sequence that contains a single element,\n", " using the specified scheduler to send out observer messages.\n", " There is an alias called 'just'.\n", "\n", " example\n", " res = rx.Observable.return(42)\n", " res = rx.Observable.return(42, rx.Scheduler.timeout)\n", "\n", " Keyword arguments:\n", " value -- Single element in the resulting observable sequence.\n", " scheduler -- [Optional] Scheduler to send the single element on. If\n", " not specified, defaults to Scheduler.immediate.\n", "\n", " Returns an observable sequence containing the single specified\n", " element.\n", "--------------------------------------------------------------------------------\n", "\n", " 1.8 M New subscription on stream 273460685\n", " 2.8 M [next] 0.9: {'answer': 66}\n", " 3.3 M [cmpl] 1.5: fin\n", "\n", " 504.5 M New subscription on stream 273460685\n", " 505.0 M [next] 0.3: {'answer': 66}\n", " 505.1 M [cmpl] 0.4: fin\n", "\n", " 505.5 M New subscription on stream 272024237\n", " 506.3 M [next] 0.7: 132\n", " 506.8 M [cmpl] 1.1: fin\n" ] } ], "source": [ "reset_start_time(O.just)\n", "stream = O.just({'answer': rand()})\n", "disposable = subs(stream)\n", "sleep(0.5)\n", "disposable = subs(stream) # same answer\n", "# all stream ops work, its a real stream:\n", "disposable = subs(stream.map(lambda x: x.get('answer', 0) * 2))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## ..that was returned from a function *called at subscribe-time*: **[start](http://reactivex.io/documentation/operators/start.html)**" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "There is a little API difference to RxJS, see Remarks:\n", "\n", "\n", "\n", "========== start ==========\n", "\n", "module rx.linq.observable.start\n", "@extensionclassmethod(Observable)\n", "def start(cls, func, scheduler=None):\n", " Invokes the specified function asynchronously on the specified\n", " scheduler, surfacing the result through an observable sequence.\n", "\n", " Example:\n", " res = rx.Observable.start(lambda: pprint('hello'))\n", " res = rx.Observable.start(lambda: pprint('hello'), rx.Scheduler.timeout)\n", "\n", " Keyword arguments:\n", " func -- {Function} Function to run asynchronously.\n", " scheduler -- {Scheduler} [Optional] Scheduler to run the function on. If\n", " not specified, defaults to Scheduler.timeout.\n", "\n", " Returns {Observable} An observable sequence exposing the function's\n", " result value, or an exception.\n", "\n", " Remarks:\n", " The function is called immediately, not during the subscription of the\n", " resulting sequence. Multiple subscriptions to the resulting sequence can\n", " observe the function's result.\n", "--------------------------------------------------------------------------------\n", "\n", " 2.7 T4 function called 3.2 M New subscription on stream 274466149\n", "\n", " 3.7 M [next] 0.4: 43\n", " 3.8 M [cmpl] 0.5: fin\n", "\n", " 4.7 M New subscription on stream 274466149\n", " 5.1 M [next] 0.2: 43\n", " 5.3 M [cmpl] 0.4: fin\n", "\n", "\n", "========== Exceptions are handled correctly (an observable should never except): ==========\n", "\n", "\n", " 6.9 M New subscription on stream 274466197\n", " 7.5 M [err ] 0.5: integer division or modulo by zero\n", "\n", " 8.4 M New subscription on stream 274466197\n", " 8.9 M [err ] 0.3: integer division or modulo by zero\n" ] } ], "source": [ "print('There is a little API difference to RxJS, see Remarks:\\n')\n", "rst(O.start)\n", "\n", "def f():\n", " log('function called')\n", " return rand()\n", "\n", "stream = O.start(func=f)\n", "d = subs(stream)\n", "d = subs(stream)\n", "\n", "header(\"Exceptions are handled correctly (an observable should never except):\")\n", "\n", "def breaking_f(): \n", " return 1 / 0\n", "\n", "stream = O.start(func=breaking_f)\n", "d = subs(stream)\n", "d = subs(stream)\n", "\n", "\n", "\n", "# startasync: only in python3 and possibly here(?) http://www.tornadoweb.org/en/stable/concurrent.html#tornado.concurrent.Future\n", "#stream = O.start_async(f)\n", "#d = subs(stream)\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## ..that was returned from an Action, Callable, Runnable, or something of that sort, called at subscribe-time: **[from](http://reactivex.io/documentation/operators/from.html)**" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== from_iterable ==========\n", "\n", "module rx.linq.observable.fromiterable\n", "@extensionclassmethod(Observable, alias=[\"from_\", \"from_list\"])\n", "def from_iterable(cls, iterable, scheduler=None):\n", " Converts an array to an observable sequence, using an optional\n", " scheduler to enumerate the array.\n", "\n", " 1 - res = rx.Observable.from_iterable([1,2,3])\n", " 2 - res = rx.Observable.from_iterable([1,2,3], rx.Scheduler.timeout)\n", "\n", " Keyword arguments:\n", " :param Observable cls: Observable class\n", " :param Scheduler scheduler: [Optional] Scheduler to run the\n", " enumeration of the input sequence on.\n", "\n", " :returns: The observable sequence whose elements are pulled from the\n", " given iterable sequence.\n", " :rtype: Observable\n", "--------------------------------------------------------------------------------\n", "\n", " 3.3 M New subscription on stream 274466081\n", " 3.8 M [next] 0.4: 1\n", " 4.1 M [next] 0.7: 2\n", " 4.6 M [next] 1.1: 95\n", " 4.8 M [cmpl] 1.4: fin\n", "\n", " 5.4 M New subscription on stream 274466125\n", " 5.6 M [next] 0.2: 29\n", " 6.0 M [next] 0.6: 29\n", " 6.2 M [next] 0.8: 15\n", " 6.3 M [cmpl] 0.9: fin\n" ] } ], "source": [ "rst(O.from_iterable)\n", "def f():\n", " log('function called')\n", " return rand()\n", "# aliases: O.from_, O.from_list\n", "# 1.: From a tuple:\n", "stream = O.from_iterable((1,2,rand()))\n", "d = subs(stream)\n", "# d = subs(stream) # same result\n", "\n", "# 2. from a generator\n", "gen = (rand() for j in range(3))\n", "stream = O.from_iterable(gen)\n", "d = subs(stream)\n", "\n" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== from_callback ==========\n", "\n", "module rx.linq.observable.fromcallback\n", "@extensionclassmethod(Observable)\n", "def from_callback(cls, func, mapper=None):\n", " Converts a callback function to an observable sequence.\n", "\n", " Keyword arguments:\n", " func -- {Function} Function with a callback as the last parameter to\n", " convert to an Observable sequence.\n", " mapper -- {Function} [Optional] A mapper which takes the arguments\n", " from the callback to produce a single item to yield on next.\n", "\n", " Returns {Function} A function, when executed with the required\n", " parameters minus the callback, produces an Observable sequence with a\n", " single value of the arguments to the callback as a list.\n", "--------------------------------------------------------------------------------\n", "\n", " 4.6 M New subscription on stream 272024249\n", " 5.9 M called f\n" ] } ], "source": [ "rst(O.from_callback)\n", "# in my words: In the on_next of the subscriber you'll have the original arguments,\n", "# potentially objects, e.g. user original http requests.\n", "# i.e. you could merge those with the result stream of a backend call to\n", "# a webservice or db and send the request.response back to the user then.\n", "\n", "def g(f, a, b):\n", " f(a, b)\n", " log('called f')\n", "stream = O.from_callback(lambda a, b, f: g(f, a, b))('fu', 'bar')\n", "d = subs(stream.delay(200))\n", "# d = subs(stream.delay(200)) # does NOT work\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## ...after a specified delay: **[timer](http://reactivex.io/documentation/operators/timer.html)**" ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", " 0.8 M New subscription on stream 274470005\n", "\n", " 3.4 M New subscription on stream 274470005\n" ] } ], "source": [ "rst()\n", "# start a stream of 0, 1, 2, .. after 200 ms, with a delay of 100 ms:\n", "stream = O.timer(200, 100).time_interval()\\\n", " .map(lambda x: 'val:%s dt:%s' % (x.value, x.interval))\\\n", " .take(3)\n", "d = subs(stream, name='observer1')\n", "# intermix directly with another one\n", "d = subs(stream, name='observer2')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## ...that emits a sequence of items repeatedly: **[repeat](http://reactivex.io/documentation/operators/repeat.html) **" ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== repeat ==========\n", "\n", "module rx.linq.observable.repeat\n", "@extensionclassmethod(Observable)\n", "def repeat(cls, value=None, repeat_count=None, scheduler=None):\n", " Generates an observable sequence that repeats the given element the\n", " specified number of times, using the specified scheduler to send out\n", " observer messages.\n", "\n", " 1 - res = rx.Observable.repeat(42)\n", " 2 - res = rx.Observable.repeat(42, 4)\n", " 3 - res = rx.Observable.repeat(42, 4, Rx.Scheduler.timeout)\n", " 4 - res = rx.Observable.repeat(42, None, Rx.Scheduler.timeout)\n", "\n", " Keyword arguments:\n", " value -- Element to repeat.\n", " repeat_count -- [Optional] Number of times to repeat the element. If not\n", " specified, repeats indefinitely.\n", " scheduler -- Scheduler to run the producer loop on. If not specified,\n", " defaults to ImmediateScheduler.\n", "\n", " Returns an observable sequence that repeats the given element the\n", " specified number of times.\n", "--------------------------------------------------------------------------------\n", "\n", " 2.0 M New subscription on stream 274473961\n", " 2.9 M [next] 0.9: {'rand': 1482335562.344726}\n", " 4.5 M [next] 2.4: {'rand': 1482335562.344726}\n", " 5.1 M [next] 3.0: {'rand': 1482335562.344726}\n", " 5.2 M [cmpl] 3.1: fin\n", "\n", "\n", "========== do while: ==========\n", "\n", "\n", " 6.8 M New subscription on stream 273460681\n", " 7.5 M [next] 0.5: 42\n", " 8.7 M [next] 1.7: 42\n", " 9.2 M [cmpl] 2.2: fin\n" ] } ], "source": [ "rst(O.repeat)\n", "# repeat is over *values*, not function calls. Use generate or create for function calls!\n", "subs(O.repeat({'rand': time.time()}, 3))\n", "\n", "header('do while:')\n", "l = []\n", "def condition(x):\n", " l.append(1)\n", " return True if len(l) < 2 else False\n", "stream = O.just(42).do_while(condition)\n", "d = subs(stream)\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## ...from scratch, with custom logic and cleanup (calling a function again and again): **[create](http://reactivex.io/documentation/operators/create.html) **" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== create ==========\n", "\n", "module rx.linq.observable.create\n", "@extensionclassmethod(Observable, alias=\"create\")\n", "def create(cls, subscribe):\n", " n.a.\n", "--------------------------------------------------------------------------------\n", "\n", " 2.4 M New subscription on stream 273454757\n", " 3.9 M cleaning up...\n", "\n", " 4.5 M New subscription on stream 273454757\n", " 5.8 M cleaning up...\n", " 131.3 T6 [next] 202.3: ['fu', 'bar']\n", " 131.7 T6 [cmpl] 202.7: fin\n", " 142.0 T7 [next] 202.4: val:0 dt:0:00:00.202066 (observer1)\n", " 144.0 T8 [next] 201.8: val:0 dt:0:00:00.201505 (observer2)\n", " 208.2 T9 [next] 205.7: 59\n", " 208.8 T9 [next] 206.3: 68\n", " 209.2 T9 [cmpl] 206.7: fin\n", " 209.6 T10 [next] 204.9: 84\n", " 210.0 T10 [next] 205.3: 79\n", " 210.2 T10 [cmpl] 205.4: fin\n", " 246.3 T12 [next] 304.1: val:1 dt:0:00:00.102253 (observer2)\n", " 247.0 T11 [next] 307.4: val:1 dt:0:00:00.104979 (observer1)\n", " 345.7 T14 [next] 406.1: val:2 dt:0:00:00.098724 (observer1)\n", " 346.0 T14 [cmpl] 406.4: fin (observer1)\n", " 348.3 T13 [next] 406.2: val:2 dt:0:00:00.102073 (observer2)\n", " 348.5 T13 [cmpl] 406.3: fin (observer2)\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "MainThread:*** Exception: float division by zero\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "\n", " 0.6 M New subscription on stream 274475781\n", " 1.2 M [next] 0.3: 0 0.5 (observer hash: 272024249)\n", " 1.6 M [next] 0.7: 1 1.0 (observer hash: 272024249)\n", " 2.2 M [err ] 1.3: float division by zero\n", "\n", " 2.9 M New subscription on stream 274475781\n", " 3.2 M [next] 0.2: 0 -1.0 (observer hash: 272024253)\n", " 3.5 M [next] 0.5: 1 -0.5 (observer hash: 272024253)\n", " 4.0 M [next] 1.0: 2 -0.333333333333 (observer hash: 272024253)\n", " 4.1 M [cmpl] 1.0: fin\n", "(where are the first two values?)\n", "\n", " 0.7 M New subscription on stream 273460701\n", " 3.8 M [err ] 2.9: float division by zero\n", "\n", " 4.4 M New subscription on stream 273460701\n" ] } ], "source": [ "rx = O.create\n", "rst(rx)\n", "\n", "def f(obs):\n", " # this function is called for every observer\n", " obs.on_next(rand())\n", " obs.on_next(rand())\n", " obs.on_completed()\n", " def cleanup():\n", " log('cleaning up...')\n", " return cleanup\n", "stream = O.create(f).delay(200) # the delay causes the cleanup called before the subs gets the vals\n", "d = subs(stream)\n", "d = subs(stream)\n", "\n", "\n", "\n", "\n", "sleep(0.5)\n", "rst(title='Exceptions are handled nicely')\n", "l = []\n", "def excepting_f(obs):\n", " for i in range(3):\n", " l.append(1)\n", " obs.on_next('%s %s (observer hash: %s)' % (i, 1. / (3 - len(l)), hash(obs) ))\n", " obs.on_completed()\n", "\n", "stream = O.create(excepting_f)\n", "d = subs(stream)\n", "d = subs(stream)\n", "\n", "\n", "\n", "\n", "rst(title='Feature or Bug?')\n", "print('(where are the first two values?)')\n", "l = []\n", "def excepting_f(obs):\n", " for i in range(3):\n", " l.append(1)\n", " obs.on_next('%s %s (observer hash: %s)' % (i, 1. / (3 - len(l)), hash(obs) ))\n", " obs.on_completed()\n", "\n", "stream = O.create(excepting_f).delay(100)\n", "d = subs(stream)\n", "d = subs(stream)\n", "# I think its an (amazing) feature, preventing to process functions results of later(!) failing functions\n" ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== generate ==========\n", "\n", "module rx.linq.observable.generate\n", "@extensionclassmethod(Observable)\n", "def generate(cls, initial_state, condition, iterate, result_mapper, scheduler=None):\n", " Generates an observable sequence by running a state-driven loop\n", " producing the sequence's elements, using the specified scheduler to\n", " send out observer messages.\n", "\n", " 1 - res = rx.Observable.generate(0,\n", " lambda x: x < 10,\n", " lambda x: x + 1,\n", " lambda x: x)\n", " 2 - res = rx.Observable.generate(0,\n", " lambda x: x < 10,\n", " lambda x: x + 1,\n", " lambda x: x,\n", " Rx.Scheduler.timeout)\n", "\n", " Keyword arguments:\n", " initial_state -- Initial state.\n", " condition -- Condition to terminate generation (upon returning False).\n", " iterate -- Iteration step function.\n", " result_mapper -- Selector function for results produced in the\n", " sequence.\n", " scheduler -- [Optional] Scheduler on which to run the generator loop.\n", " If not provided, defaults to CurrentThreadScheduler.\n", "\n", " Returns the generated sequence.\n", "--------------------------------------------------------------------------------\n", "\n", " 4.8 M New subscription on stream 274475993\n", " 5.7 M [next] 0.7: 0\n", " 6.4 M [next] 1.4: 2.2\n", " 6.6 M [next] 1.6: 4.4\n", " 7.1 M [next] 2.1: 6.6\n", " 7.3 M [cmpl] 2.3: fin\n" ] } ], "source": [ "rx = O.generate\n", "rst(rx)\n", "\"\"\"The basic form of generate takes four parameters:\n", "\n", "the first item to emit\n", "a function to test an item to determine whether to emit it (true) or terminate the Observable (false)\n", "a function to generate the next item to test and emit based on the value of the previous item\n", "a function to transform items before emitting them\n", "\"\"\"\n", "def generator_based_on_previous(x): return x + 1.1\n", "def doubler(x): return 2 * x\n", "d = subs(rx(0, lambda x: x < 4, generator_based_on_previous, doubler))" ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== generate_with_relative_time ==========\n", "\n", "module rx.linq.observable.generatewithrelativetime\n", "@extensionclassmethod(Observable)\n", "def generate_with_relative_time(cls, initial_state, condition, iterate,\n", " Generates an observable sequence by iterating a state from an\n", " initial state until the condition fails.\n", "\n", " res = source.generate_with_relative_time(0,\n", " lambda x: True,\n", " lambda x: x + 1,\n", " lambda x: x,\n", " lambda x: 500)\n", "\n", " initial_state -- Initial state.\n", " condition -- Condition to terminate generation (upon returning false).\n", " iterate -- Iteration step function.\n", " result_mapper -- Selector function for results produced in the\n", " sequence.\n", " time_mapper -- Time mapper function to control the speed of values\n", " being produced each iteration, returning integer values denoting\n", " milliseconds.\n", " scheduler -- [Optional] Scheduler on which to run the generator loop.\n", " If not specified, the timeout scheduler is used.\n", "\n", " Returns the generated sequence.\n", "--------------------------------------------------------------------------------\n", "\n", " 4.7 M New subscription on stream 274475933\n" ] } ], "source": [ "rx = O.generate_with_relative_time\n", "rst(rx)\n", "stream = rx(1, lambda x: x < 4, lambda x: x + 1, lambda x: x, lambda t: 100)\n", "d = subs(stream)\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## ...for each observer that subscribes OR according to a condition at subscription time: **[defer / if_then](http://reactivex.io/documentation/operators/defer.html) **" ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== defer ==========\n", "\n", "module rx.linq.observable.defer\n", "@extensionclassmethod(Observable)\n", "def defer(cls, observable_factory):\n", " Returns an observable sequence that invokes the specified factory\n", " function whenever a new observer subscribes.\n", "\n", " Example:\n", " 1 - res = rx.Observable.defer(lambda: rx.Observable.from_([1,2,3]))\n", "\n", " Keyword arguments:\n", " :param types.FunctionType observable_factory: Observable factory function\n", " to invoke for each observer that subscribes to the resulting sequence.\n", "\n", " :returns: An observable sequence whose observers trigger an invocation\n", " of the given observable factory function.\n", " :rtype: Observable\n", "--------------------------------------------------------------------------------\n", "\n", " 2.7 M New subscription on stream 274475969\n", " 3.4 M [next] 0.6: 38\n", " 3.5 M [cmpl] 0.7: fin\n", "\n", " 4.4 M New subscription on stream 274475969\n", " 4.9 M [next] 0.4: 77\n", " 5.2 M [cmpl] 0.7: fin\n" ] } ], "source": [ "rst(O.defer)\n", "# plural! (unique per subscription)\n", "streams = O.defer(lambda: O.just(rand()))\n", "d = subs(streams)\n", "d = subs(streams) # gets other values - created by subscription!" ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== if_then ==========\n", "\n", "module rx.linq.observable.ifthen\n", "@extensionclassmethod(Observable)\n", "def if_then(cls, condition, then_source, else_source=None, scheduler=None):\n", " Determines whether an observable collection contains values.\n", "\n", " Example:\n", " 1 - res = rx.Observable.if(condition, obs1)\n", " 2 - res = rx.Observable.if(condition, obs1, obs2)\n", " 3 - res = rx.Observable.if(condition, obs1, scheduler=scheduler)\n", "\n", " Keyword parameters:\n", " condition -- {Function} The condition which determines if the\n", " then_source or else_source will be run.\n", " then_source -- {Observable} The observable sequence or Promise that\n", " will be run if the condition function returns true.\n", " else_source -- {Observable} [Optional] The observable sequence or\n", " Promise that will be run if the condition function returns False.\n", " If this is not provided, it defaults to rx.empty\n", " scheduler -- [Optional] Scheduler to use.\n", "\n", " Returns an observable {Observable} sequence which is either the\n", " then_source or else_source.\n", "--------------------------------------------------------------------------------\n", "\n", " 3.3 M New subscription on stream 274480673\n", " 3.6 M [next] 0.2: 43\n", " 3.8 M [cmpl] 0.5: fin\n", " 4.0 M condition will now evaluate falsy:\n", "\n", " 4.4 M New subscription on stream 274480817\n", " 4.6 M [next] 0.2: 52\n", " 4.7 M [cmpl] 0.3: fin\n", "\n", " 5.2 M New subscription on stream 274480817\n", " 5.6 M [next] 0.2: 52\n", " 5.8 M [cmpl] 0.4: fin\n" ] } ], "source": [ "# evaluating a condition at subscription time in order to decide which of two streams to take.\n", "rst(O.if_then)\n", "cond = True\n", "def should_run():\n", " return cond\n", "streams = O.if_then(should_run, O.return_value(43), O.return_value(56))\n", "d = subs(streams)\n", "\n", "log('condition will now evaluate falsy:')\n", "cond = False\n", "streams = O.if_then(should_run, O.return_value(43), O.return_value(rand()))\n", "d = subs(streams)\n", "d = subs(streams)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## ...that emits a sequence of integers: **[range](http://reactivex.io/documentation/operators/range.html) **" ] }, { "cell_type": "code", "execution_count": 14, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== range ==========\n", "\n", "module rx.linq.observable.range\n", "@extensionclassmethod(Observable)\n", "def range(cls, start, count, scheduler=None):\n", " Generates an observable sequence of integral numbers within a\n", " specified range, using the specified scheduler to send out observer\n", " messages.\n", "\n", " 1 - res = Rx.Observable.range(0, 10)\n", " 2 - res = Rx.Observable.range(0, 10, rx.Scheduler.timeout)\n", "\n", " Keyword arguments:\n", " start -- The value of the first integer in the sequence.\n", " count -- The number of sequential integers to generate.\n", " scheduler -- [Optional] Scheduler to run the generator loop on. If not\n", " specified, defaults to Scheduler.current_thread.\n", "\n", " Returns an observable sequence that contains a range of sequential\n", " integral numbers.\n", "--------------------------------------------------------------------------------\n", "\n", " 2.9 M New subscription on stream 274475905\n", " 3.7 M [next] 0.4: 0\n", " 4.3 M [next] 1.0: 1\n", " 4.6 M [next] 1.3: 2\n", " 4.9 M [cmpl] 1.6: fin\n" ] } ], "source": [ "rst(O.range)\n", "d = subs(O.range(0, 3))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### ...at particular intervals of time: **[interval](http://reactivex.io/documentation/operators/interval.html) **\n", "\n", "(you can `.publish()` it to get an easy \"hot\" observable)" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== interval ==========\n", "\n", "module rx.linq.observable.interval\n", "@extensionclassmethod(Observable)\n", "def interval(cls, period, scheduler=None):\n", " Returns an observable sequence that produces a value after each\n", " period.\n", "\n", " Example:\n", " 1 - res = rx.Observable.interval(1000)\n", " 2 - res = rx.Observable.interval(1000, rx.Scheduler.timeout)\n", "\n", " Keyword arguments:\n", " period -- Period for producing the values in the resulting sequence\n", " (specified as an integer denoting milliseconds).\n", " scheduler -- [Optional] Scheduler to run the timer on. If not specified,\n", " rx.Scheduler.timeout is used.\n", "\n", " Returns an observable sequence that produces a value after each period.\n", "--------------------------------------------------------------------------------\n", "\n", " 1.2 M New subscription (14365) on stream 276610125\n", " 102.3 T8 [next] 100.9: 0:00:00.100623 0 -> 14365\n", " 208.2 T9 [next] 206.9: 0:00:00.105960 1 -> 14365\n", " 310.8 T10 [next] 309.5: 0:00:00.102625 2 -> 14365\n", " 311.1 T10 [cmpl] 309.8: fin -> 14365\n" ] } ], "source": [ "rst(O.interval)\n", "d = subs(O.interval(100).time_interval()\\\n", " .map(lambda x, v: '%(interval)s %(value)s' \\\n", " % ItemGetter(x)).take(3))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### ...after a specified delay (see timer)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## ...that completes without emitting items: **[empty](http://reactivex.io/documentation/operators/empty-never-throw.html) **" ] }, { "cell_type": "code", "execution_count": 16, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== empty ==========\n", "\n", "module rx.linq.observable.empty\n", "@extensionclassmethod(Observable)\n", "def empty(cls, scheduler=None):\n", " Returns an empty observable sequence, using the specified scheduler\n", " to send out the single OnCompleted message.\n", "\n", " 1 - res = rx.empty()\n", " 2 - res = rx.empty(rx.Scheduler.timeout)\n", "\n", " scheduler -- Scheduler to send the termination call on.\n", "\n", " Returns an observable sequence with no elements.\n", "--------------------------------------------------------------------------------\n", "\n", " 2.9 M New subscription on stream 273460593\n", " 3.2 M [cmpl] 0.2: fin\n" ] } ], "source": [ "rst(O.empty)\n", "d = subs(O.empty())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## ...that does nothing at all: **[never](http://reactivex.io/documentation/operators/empty-never-throw.html) **" ] }, { "cell_type": "code", "execution_count": 17, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== never ==========\n", "\n", " 0.7 T18 [next] 104.4: 0 -1.0 (observer hash: 274473797)\n", " 1.1 T18 [next] 104.8: 1 -0.5 (observer hash: 274473797)module rx.linq.observable.never\n", "@extensionclassmethod(Observable)\n", "def never(cls):\n", " Returns a non-terminating observable sequence, which can be used to\n", " denote an infinite duration (e.g. when using reactive joins).\n", "\n", " Returns an observable sequence whose observers will never get called.\n", "--------------------------------------------------------------------------------\n", "\n", " 2.0 T18 [next] 105.7: 2 -0.333333333333 (observer hash: 274473797)\n", "\n", " 2.1 T18 [cmpl] 105.9: fin 2.7 M New subscription on stream 274473849\n", "\n" ] } ], "source": [ "rst(O.never)\n", "d = subs(O.never())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## ...that excepts: **[throw](http://reactivex.io/documentation/operators/empty-never-throw.html) **" ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== throw ==========\n", "\n", "module rx.linq.observable.throw\n", "@extensionclassmethod(Observable, alias=\"throw_exception\")\n", "def on_error(cls, exception, scheduler=None):\n", " Returns an observable sequence that terminates with an exception,\n", " using the specified scheduler to send out the single OnError message.\n", "\n", " 1 - res = rx.throw(Exception('Error'))\n", " 2 - res = rx.throw(Exception('Error'),\n", " rx.Scheduler.timeout)\n", "\n", " Keyword arguments:\n", " exception -- An object used for the sequence's termination.\n", " scheduler -- Scheduler to send the exceptional termination call on. If\n", " not specified, defaults to ImmediateScheduler.\n", "\n", " Returns the observable sequence that terminates exceptionally with the\n", " specified exception object.\n", "--------------------------------------------------------------------------------\n", "\n", " 1.8 M New subscription (23467) on stream 276521733\n", " 2.0 M [err ] 0.2: -> 23467\n" ] } ], "source": [ "rst(O.on_error)\n", "d = subs(O.on_error(ZeroDivisionError))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 2", "language": "python", "name": "python2" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", "version": "2.7.12" } }, "nbformat": 4, "nbformat_minor": 1 } RxPY-3.2.0/notebooks/reactivex.io/Marble Diagrams.ipynb000066400000000000000000000107321404130727200227410ustar00rootroot00000000000000{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Marble Diagrams with RxPY\n", "\n", "This is a fantastic feature to produce and visualize streams and to verify how various operators work on them.\n", "\n", "Have also a look at [rxmarbles](http://rxmarbles.com) for interactive visualisations.\n", "\n", "\n", "**ONE DASH IS 100 MILLISECONDS**!\n" ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "collapsed": true }, "outputs": [], "source": [ "%run startup.py" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Create Streams from Strings: **from_marbles**" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== from_marbles ==========\n", "\n", "module rx.testing.marbles\n", "@extensionclassmethod(Observable, alias=\"from_string\")\n", "def from_marbles(cls, string, scheduler=None):\n", " Convert a marble diagram string to an observable sequence, using\n", " an optional scheduler to enumerate the events.\n", "\n", " Special characters:\n", " - = Timespan of 100 ms\n", " x = on_error()\n", " | = on_completed()\n", "\n", " All other characters are treated as an on_next() event at the given\n", " moment they are found on the string.\n", "\n", " Examples:\n", " 1 - res = rx.Observable.from_string(\"1-2-3-|\")\n", " 2 - res = rx.Observable.from_string(\"1-(42)-3-|\")\n", " 3 - res = rx.Observable.from_string(\"1-2-3-x\", rx.Scheduler.timeout)\n", "\n", " Keyword arguments:\n", " string -- String with marble diagram\n", " scheduler -- [Optional] Scheduler to run the the input sequence on.\n", "\n", " Returns the observable sequence whose elements are pulled from the\n", " given marble diagram string.\n", "--------------------------------------------------------------------------------\n", "val: 1, dt: 0.0117020606995\n", "val: 2, dt: 0.125038146973\n", "val: 3, dt: 0.234502077103\n" ] } ], "source": [ "rst(O.from_marbles)\n", "ts = time.time()\n", "# producing a stream\n", "s = O.from_marbles('1-2-3|')\n", "# mapping into real time:\n", "s2 = s.to_blocking()\n", "# adding times\n", "s3 = s2.map(lambda x: 'val: %s, dt: %s' % (x, time.time()-ts))\n", "# subscribing to it:\n", "d = s3.subscribe(print)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Visualize Streams as Marble Strings: **to_marbles**" ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== to_marbles ==========\n", "\n", "module rx.testing.marbles\n", "@extensionmethod(BlockingObservable, alias=\"to_string\") # noqa\n", "def to_marbles(self, scheduler=None):\n", " Convert an observable sequence into a marble diagram string\n", "\n", " Keyword arguments:\n", " scheduler -- [Optional] The scheduler used to run the the input\n", " sequence on.\n", "\n", " Returns marble string.\n", "--------------------------------------------------------------------------------\n", "1-a-b-2c-3|\n" ] } ], "source": [ "rst(rx.core.blockingobservable.BlockingObservable.to_marbles)\n", "s1 = O.from_marbles('1---2-3|')\n", "s2 = O.from_marbles('-a-b-c-|')\n", "print(s1.merge(s2).to_blocking().to_marbles())\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 2", "language": "python", "name": "python2" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", "version": "2.7.12" } }, "nbformat": 4, "nbformat_minor": 1 } RxPY-3.2.0/notebooks/reactivex.io/Part II - Combination.ipynb000066400000000000000000000773231404130727200235700ustar00rootroot00000000000000{ "cells": [ { "cell_type": "code", "execution_count": 2, "metadata": { "collapsed": true }, "outputs": [], "source": [ "%run startup.py" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "collapsed": false }, "outputs": [ { "data": { "application/javascript": [ "$.getScript('./assets/js/ipython_notebook_toc.js')" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "%%javascript\n", "$.getScript('./assets/js/ipython_notebook_toc.js')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# A Decision Tree of Observable Operators\n", "\n", "## Part 2: Combining Observables\n", "\n", "> source: http://reactivex.io/documentation/operators.html#tree. \n", "> (transcribed to RxPY 1.5.7, Py2.7 / 2016-12, Gunther Klessinger, [axiros](http://www.axiros.com)) \n", "\n", "**This tree can help you find the ReactiveX Observable operator you’re looking for.** \n", "See [Part 1](./A Decision Tree of Observable Operators. Part I - Creation.ipynb) for Usage and Output Instructions. \n", "We also require acquaintance with the [marble diagrams](./Marble Diagrams.ipynb) feature of RxPy.\n", "\n", "[This](http://www.introtorx.com/Content/v1.0.10621.0/12_CombiningSequences.html) is a helpful accompanying read. \n", "

    Table of Contents

    \n", "
    \n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# I want to create an Observable by **combining** other Observables" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## ... and emitting all of the items from all of the Observables in whatever order they are received: **[merge / merge_all](http://reactivex.io/documentation/operators/merge.html) **" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== merge ==========\n", "\n", "module rx.linq.observable.merge\n", "@extensionclassmethod(Observable) # noqa\n", "def merge(cls, *args):\n", " Merges all the observable sequences into a single observable\n", " sequence. The scheduler is optional and if not specified, the\n", " immediate scheduler is used.\n", "\n", " 1 - merged = rx.Observable.merge(xs, ys, zs)\n", " 2 - merged = rx.Observable.merge([xs, ys, zs])\n", " 3 - merged = rx.Observable.merge(scheduler, xs, ys, zs)\n", " 4 - merged = rx.Observable.merge(scheduler, [xs, ys, zs])\n", "\n", " Returns the observable sequence that merges the elements of the\n", " observable sequences.\n", "--------------------------------------------------------------------------------\n", "\n", " 2.3 M New subscription on stream 276542077\n", " 3.0 M [next] 0.5: 0\n", " 3.2 M [next] 0.7: 1\n", " 3.4 M [err ] 0.9: integer division or modulo by zero\n", " 5.4 T5 [next] 1.1: a\n", "\n", " 5.9 T5 [next] 1.6: b 4.2 M New subscription on stream 276542101\n", "\n", " 6.0 T5 [next] 1.7: c\n", " 6.5 T5 [next] 2.2: 0\n", " 6.7 T5 [next] 2.4: 1\n", " 6.8 T5 [err ] 2.5: integer division or modulo by zero\n" ] } ], "source": [ "reset_start_time(O.merge)\n", "l = []\n", "def excepting_f(obs):\n", " for i in range(10):\n", " l.append(1)\n", " obs.on_next(1 / (3 - len(l)))\n", "stream1 = O.from_(('a', 'b', 'c'))\n", "stream2 = O.create(excepting_f)\n", "# merged stream stops in any case at first exception!\n", "# No guarantee of order of those immediately created streams !\n", "d = subs(stream1.merge(stream2))\n", "l = []\n", "d = subs(O.merge(new_thread_scheduler, [stream1, stream2]))" ] }, { "cell_type": "code", "execution_count": 167, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== merge_all ==========\n", "\n", "function merge_all of module rx.linq.observable.merge:\n", "Merges an observable sequence of observable sequences into an\n", " observable sequence.\n", "\n", " Returns the observable sequence that merges the elements of the inner\n", " sequences.\n", " \n", "--------------------------------------------------------------------------------\n", "\n", " 1.0 M New subscription on stream 276411209\n", " 2.5 M [next] 1.2: 1\n", " 3.6 M [next] 2.4: 2\n", " 3.9 M [next] 2.6: 1\n", " 4.5 M [next] 3.2: 3\n", " 4.9 M [next] 3.6: 2\n", " 5.0 M [next] 3.8: 1\n", " 5.6 M [next] 4.4: 3\n", " 5.9 M [next] 4.6: 2\n", " 6.5 M [next] 5.2: 3\n", " 6.8 M [cmpl] 5.5: fin\n", "\n", " 7.3 M New subscription on stream 276483801\n", " 19.0 T1046 [next] 11.7: 0 (streams with time delays between events)\n", " 19.9 T1047 [next] 12.6: 0 (streams with time delays between events)\n", " 21.1 T1048 [next] 13.8: 0 (streams with time delays between events)\n", " 30.2 T1049 [next] 22.9: 1 (streams with time delays between events)\n", " 31.6 T1050 [next] 24.4: 1 (streams with time delays between events)\n", " 32.4 T1051 [next] 25.1: 1 (streams with time delays between events)\n", " 41.7 T1052 [next] 34.4: 2 (streams with time delays between events)\n", " 42.4 T1053 [next] 35.1: 2 (streams with time delays between events)\n", " 43.3 T1054 [next] 36.0: 2 (streams with time delays between events)\n", " 43.8 T1054 [cmpl] 36.5: fin (streams with time delays between events)\n" ] } ], "source": [ "rst(O.merge_all, title='merge_all')\n", "meta = O.repeat(O.from_((1, 2, 3)), 3)\n", "# no guarantee of order, immediatelly created:\n", "d = subs(meta.merge_all())\n", "# Introducing delta ts:\n", "d = subs(O.repeat(O.timer(10, 10)\\\n", " .take(3), 3)\\\n", " .merge_all(),\n", " name='streams with time delays between events')\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## ... and emitting all of the items from all of the Observables, one Observable at a time: **[concat](http://reactivex.io/documentation/operators/concat.html) **" ] }, { "cell_type": "code", "execution_count": 168, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "function concat of module rx.linq.observable.concat:\n", "Concatenates all the observable sequences.\n", "\n", " 1 - res = Observable.concat(xs, ys, zs)\n", " 2 - res = Observable.concat([xs, ys, zs])\n", "\n", " Returns an observable sequence that contains the elements of each given\n", " sequence, in sequential order.\n", " \n", "--------------------------------------------------------------------------------\n", "\n", " 0.9 M New subscription on stream 278620693\n", " 1.4 M [next] 0.3: 1\n", " 1.6 M [next] 0.6: 2\n", " 1.8 M [cmpl] 0.8: fin\n", "\n", " 2.2 M New subscription on stream 278620893\n", " 2.7 M [next] 0.4: 3\n", " 2.8 M [next] 0.6: 4\n", " 3.0 M [cmpl] 0.7: fin\n", "\n", " 3.4 M New subscription on stream 276406121\n", " 3.8 M [next] 0.3: 3\n", " 4.1 M [next] 0.5: 4\n", " 4.4 M [next] 0.8: 1\n", " 4.6 M [next] 1.1: 2\n", " 5.0 M [cmpl] 1.5: fin\n" ] } ], "source": [ "rst(O.concat)\n", "s1 = O.from_((1, 2))\n", "s2 = O.from_((3, 4))\n", "# while normal subscriptions work as expected...\n", "d1, d2 = subs(s1), subs(s2)\n", "# ... another one can have the order reversed\n", "d = subs(O.concat([s2, s1]))" ] }, { "cell_type": "code", "execution_count": 169, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", " 0.9 M New subscription on stream 276485909\n", "\n", " 3.8 M New subscription on stream 276485905\n", " 5.8 M main thread sleeping 1s\n", " 12.0 T1058 [next] 10.9: 1 (A)\n", " 219.2 T1066 [next] 215.4: a (B)\n", " 226.2 T1059 [next] 225.1: 2 (A)\n", " 325.2 T1068 [next] 321.4: b (B)\n", " 439.1 T1069 [next] 435.3: c (B)\n", " 446.4 T1070 [cmpl] 442.5: fin (B)\n", " 536.3 T1061 [next] 535.2: 3 (A)\n", " 547.5 T1063 [cmpl] 546.3: fin (A)\n", "\n", "\n", "========== Concatenating in reverse order ==========\n", "\n", "\n", " 0.5 M New subscription on stream 276308601\n", " 216.5 T1074 [next] 215.7: a (C)\n", " 327.7 T1075 [next] 326.9: b (C)\n", " 436.7 T1077 [next] 436.0: c (C)\n", " 456.4 T1082 [next] 455.7: 1 (C)\n", " 670.3 T1083 [next] 669.5: 2 (C)\n", " 976.9 T1085 [next] 976.2: 3 (C)\n", " 990.9 T1087 [cmpl] 990.1: fin (C)\n" ] } ], "source": [ "rst()\n", "# See the marbles notebook:\n", "s1 = O.from_marbles('1--2---3|').to_blocking()\n", "s2 = O.from_marbles('--a-b-c|' ).to_blocking()\n", "d = (subs(s1, name='A'),\n", " subs(s2, name='B'))\n", "rst(title=\"Concatenating in reverse order\", sleep=1)\n", "d = subs(O.concat([s2, s1]), name='C')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## ... by combining the items from two or more Observables sequentially to come up with new items to emit" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### ... whenever *each* of the Observables has emitted a new item **[zip / zip_list](http://reactivex.io/documentation/operators/zip.html)**" ] }, { "cell_type": "code", "execution_count": 176, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "function zip of module rx.linq.observable.zip:\n", "Merges the specified observable sequences into one observable\n", " sequence by using the mapper function whenever all of the observable\n", " sequences have produced an element at a corresponding index.\n", "\n", " The last element in the arguments must be a function to invoke for each\n", " series of elements at corresponding indexes in the sources.\n", "\n", " Arguments:\n", " args -- Observable sources.\n", "\n", " Returns an observable {Observable} sequence containing the result of\n", " combining elements of the sources using the specified result mapper\n", " function.\n", " \n", "--------------------------------------------------------------------------------\n", "\n", " 0.8 M New subscription on stream 276463149\n", " 3.0 M [next] 2.2: 0 : 1 : 2\n", " 3.5 M [next] 2.6: 1 : 2 : 3\n", " 3.9 M [next] 3.0: 2 : 3 : 4\n", " 4.3 M [cmpl] 3.4: fin\n" ] } ], "source": [ "rst(O.zip)\n", "s1 = O.range(0, 5)\n", "d = subs(O.zip(s1, s1.skip(1), s1.skip(2), lambda s1, s2, s3: '%s : %s : %s' % (s1, s2, s3)))" ] }, { "cell_type": "code", "execution_count": 179, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "function zip_list of module rx.linq.observable.ziparray:\n", "Merge the specified observable sequences into one observable\n", " sequence by emitting a list with the elements of the observable\n", " sequences at corresponding indexes.\n", "\n", " Keyword arguments:\n", " :param Observable cls: Class\n", " :param Tuple args: Observable sources.\n", "\n", " :return: Returns an observable sequence containing lists of\n", " elements at corresponding indexes.\n", " :rtype: Observable\n", " \n", "--------------------------------------------------------------------------------\n", "\n", " 0.9 M New subscription on stream 276480037\n", " 2.9 M [next] 1.8: [0, 1, 2]\n", " 3.3 M [next] 2.2: [1, 2, 3]\n", " 3.9 M [next] 2.8: [2, 3, 4]\n", " 4.4 M [cmpl] 3.3: fin\n" ] } ], "source": [ "rst(O.zip_list) # alias: zip_array\n", "s1 = O.range(0, 5)\n", "d = subs(O.zip_list(s1, s1.skip(1), s1.skip(2)))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### ... whenever *any* of the Observables has emitted a new item **[combine_latest](http://reactivex.io/documentation/operators/combinelatest.html)**" ] }, { "cell_type": "code", "execution_count": 17, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== combine_latest ==========\n", "\n", "function combine_latest of module rx.linq.observable.combinelatest:\n", "Merges the specified observable sequences into one observable\n", " sequence by using the mapper function whenever any of the\n", " observable sequences produces an element.\n", "\n", " 1 - obs = Observable.combine_latest(obs1, obs2, obs3,\n", " lambda o1, o2, o3: o1 + o2 + o3)\n", " 2 - obs = Observable.combine_latest([obs1, obs2, obs3],\n", " lambda o1, o2, o3: o1 + o2 + o3)\n", "\n", " Returns an observable sequence containing the result of combining\n", " elements of the sources using the specified result mapper\n", " function.\n", " \n", "--------------------------------------------------------------------------------\n", "\n", " 1.0 M New subscription on stream 276805405\n", " 3.0 M main thread sleeping 1s\n", " 157.1 T195 [next] 156.1: First : 0, Second: 0\n", " 211.6 T196 [next] 210.6: First : 1, Second: 0\n", " 312.5 T197 [next] 311.6: First : 1, Second: 1\n", " 316.0 T198 [next] 315.0: First : 2, Second: 1\n", " 421.2 T200 [next] 420.2: First : 3, Second: 1\n", " 465.7 T199 [next] 464.7: First : 3, Second: 2\n", " 465.9 T199 [cmpl] 464.9: fin\n", "\n", "\n", "========== For comparison: merge ==========\n", "\n", "\n", " 1.0 M New subscription on stream 276157517\n", " 104.0 T204 [next] 103.0: First : 0\n", " 157.6 T203 [next] 156.5: Second: 0\n", " 207.3 T205 [next] 206.2: First : 1\n", " 311.4 T207 [next] 310.4: First : 2 311.7 T206 [next] 310.7: Second: 1\n", "\n", " 417.5 T208 [next] 416.5: First : 3\n", " 417.8 T208 [cmpl] 416.7: fin\n" ] } ], "source": [ "rst(O.combine_latest, title='combine_latest')\n", "s1 = O.interval(100).map(lambda i: 'First : %s' % i)\n", "s2 = O.interval(150).map(lambda i: 'Second: %s' % i)\n", "# the start is interesting, both must have emitted, so it starts at 150ms with 0/0:\n", "d = subs(s1.combine_latest(s2, lambda s1, s2: '%s, %s' % (s1, s2)).take(6))\n", "\n", "rst(title='For comparison: merge', sleep=1)\n", "d = subs(s1.merge(s2).take(6))\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### ... whenever *the first* of the Observables has emitted a new item **[with_latest_from](http://reactivex.io/documentation/operators/combinelatest.html)**" ] }, { "cell_type": "code", "execution_count": 16, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== with_latest_from ==========\n", "\n", "function with_latest_from of module rx.linq.observable.withlatestfrom:\n", "Merges the specified observable sequences into one observable sequence\n", " by using the mapper function only when the first observable sequence\n", " produces an element. The observables can be passed either as seperate\n", " arguments or as a list.\n", "\n", " 1 - obs = Observable.with_latest_from(obs1, obs2, obs3,\n", " lambda o1, o2, o3: o1 + o2 + o3)\n", " 2 - obs = Observable.with_latest_from([obs1, obs2, obs3],\n", " lambda o1, o2, o3: o1 + o2 + o3)\n", "\n", " Returns an observable sequence containing the result of combining\n", " elements of the sources using the specified result mapper function.\n", " \n", "--------------------------------------------------------------------------------\n", "\n", " 1.0 M New subscription on stream 276799777\n", " 147.0 T170 [next] 146.0: First : 0, Second: 1\n", " 292.0 T174 [next] 290.9: First : 1, Second: 4\n", " 437.8 T178 [next] 436.7: First : 2, Second: 7\n", " 581.7 T182 [next] 580.7: First : 3, Second: 9\n", " 723.3 T185 [next] 722.2: First : 4, Second: 12\n", " 867.0 T189 [next] 865.9: First : 5, Second: 15\n", " 867.4 T189 [cmpl] 866.3: fin\n" ] } ], "source": [ "rst(O.with_latest_from, title='with_latest_from')\n", "s1 = O.interval(140).map(lambda i: 'First : %s' % i)\n", "s2 = O.interval(50) .map(lambda i: 'Second: %s' % i)\n", "d = subs(s1.with_latest_from(s2, lambda s1, s2: '%s, %s' % (s1, s2)).take(6))\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### ... whenever an item is emitted by one Observable in a window defined by an item emitted by another **[join](http://reactivex.io/documentation/operators/join.html)**\n", "The join operator takes four parameters:\n", "\n", "1. the second Observable to combine with the source Observable\n", "1. a function that accepts an item from the source Observable and returns an Observable whose lifespan governs the duration during which that item will combine with items from the second Observable\n", "1. a function that accepts an item from the second Observable and returns an Observable whose lifespan governs the duration during which that item will combine with items from the first Observable\n", "1. a function that accepts an item from the first Observable and an item from the second Observable and returns an item to be emitted by the Observable returned from join\n" ] }, { "cell_type": "code", "execution_count": 23, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "function join of module rx.linq.observable.join:\n", "Correlates the elements of two sequences based on overlapping\n", " durations.\n", "\n", " Keyword arguments:\n", " right -- The right observable sequence to join elements for.\n", " left_duration_mapper -- A function to select the duration (expressed\n", " as an observable sequence) of each element of the left observable\n", " sequence, used to determine overlap.\n", " right_duration_mapper -- A function to select the duration (expressed\n", " as an observable sequence) of each element of the right observable\n", " sequence, used to determine overlap.\n", " result_mapper -- A function invoked to compute a result element for\n", " any two overlapping elements of the left and right observable\n", " sequences. The parameters passed to the function correspond with\n", " the elements from the left and right source sequences for which\n", " overlap occurs.\n", "\n", " Return an observable sequence that contains result elements computed\n", " from source elements that have an overlapping duration.\n", " \n", "--------------------------------------------------------------------------------\n", "\n", " 1.2 M New subscription on stream 275960897\n", " 109.1 T449 [next] 107.8: First : 0 Second: 0\n", " 215.3 T453 [next] 214.0: First : 1 Second: 1\n", " 321.6 T457 [next] 320.4: First : 2 Second: 2\n", " 426.0 T461 [next] 424.8: First : 3 Second: 3\n", " 533.3 T465 [next] 532.1: First : 4 Second: 4\n", " 533.7 T465 [cmpl] 532.4: fin\n" ] } ], "source": [ "rst(O.join)\n", "# this one is pretty timing critical and output seems swallowed with 2 threads (over)writing.\n", "# better try this with timer(0) on the console. Also the scheduler of the timers is critical,\n", "# try other O.timer schedulers...\n", "xs = O.interval(100).map(lambda i: 'First : %s' % i)\n", "ys = O.interval(101).map(lambda i: 'Second: %s' % i)\n", "d = subs(xs.join(ys, lambda _: O.timer(10), lambda _: O.timer(0), lambda x, y: '%s %s' % (x, y)).take(5))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### ... or, alternatively, **[group_join](http://reactivex.io/documentation/operators/join.html)**\n", "\n", "The groupJoin operator takes four parameters:\n", "\n", "1. the second Observable to combine with the source Observable\n", "1. a function that accepts an item from the source Observable and returns an Observable whose lifespan governs the duration during which that item will combine with items from the second Observable\n", "1. a function that accepts an item from the second Observable and returns an Observable whose lifespan governs the duration during which that item will combine with items from the first Observable\n", "1. a function that accepts an item from the first Observable and an Observable that emits items from the second Observable and returns an item to be emitted by the Observable returned from groupJoin\n" ] }, { "cell_type": "code", "execution_count": 27, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== group_join ==========\n", "\n", "function group_join of module rx.linq.observable.groupjoin:\n", "Correlates the elements of two sequences based on overlapping\n", " durations, and groups the results.\n", "\n", " Keyword arguments:\n", " right -- The right observable sequence to join elements for.\n", " left_duration_mapper -- A function to select the duration (expressed\n", " as an observable sequence) of each element of the left observable\n", " sequence, used to determine overlap.\n", " right_duration_mapper -- A function to select the duration (expressed\n", " as an observable sequence) of each element of the right observable\n", " sequence, used to determine overlap.\n", " result_mapper -- A function invoked to compute a result element for\n", " any element of the left sequence with overlapping elements from the\n", " right observable sequence. The first parameter passed to the\n", " function is an element of the left sequence. The second parameter\n", " passed to the function is an observable sequence with elements from\n", " the right sequence that overlap with the left sequence's element.\n", "\n", " Returns an observable sequence that contains result elements computed\n", " from source elements that have an overlapping duration.\n", " \n", "--------------------------------------------------------------------------------\n", "\n", " 1.3 M New subscription on stream 273932389\n", " 534.5 T500 [next] 533.0: First : 4 Second: 4\n", " 536.0 T501 [next] 534.5: First : 4 Second: 4\n", " 640.2 T504 [next] 638.8: First : 5 Second: 5\n", "18690.9 T1191 [next] 18689.5: First : 176 Second: 177\n", "19010.8 T1202 [next] 19009.4: First : 179 Second: 180\n", "19011.9 T1202 [cmpl] 19010.5: fin\n" ] } ], "source": [ "rst(O.group_join, title='group_join')\n", "xs = O.interval(100).map(lambda i: 'First : %s' % i)\n", "ys = O.interval(100).map(lambda i: 'Second: %s' % i)\n", "d = subs(xs.group_join(ys,\n", " lambda _: O.timer(0),\n", " lambda _: O.timer(0),\n", " lambda x, yy: yy.select(lambda y: '%s %s' % (x, y))).merge_all().take(5))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### ... by means of Pattern and Plan intermediaries And/Then/When, **[and / then / when](http://reactivex.io/documentation/operators/and-then-when.html)**\n", "The combination of the And, Then, and When operators behave much like the Zip operator, but they do so by means of intermediate data structures. And accepts two or more Observables and combines the emissions from each, one set at a time, into Pattern objects. Then operates on such Pattern objects, transforming them in a Plan. When in turn transforms these various Plan objects into emissions from an Observable.\n", "\n", "[details](http://www.introtorx.com/content/v1.0.10621.0/12_CombiningSequences.html#AndThenWhen) \n", "The And/Then/When trio has more overloads that enable you to group an even greater number of sequences. They also allow you to provide more than one 'plan' (the output of the Then method). This gives you the Merge feature but on the collection of 'plans'. I would suggest playing around with them if this functionality is of interest to you. The verbosity of enumerating all of the combinations of these methods would be of low value. You will get far more value out of using them and discovering for yourself.\n", "\n", "As we delve deeper into the depths of what the Rx libraries provide us, we can see more practical usages for it. Composing sequences with Rx allows us to easily make sense of the multiple data sources a problem domain is exposed to. We can concatenate values or sequences together sequentially with StartWith, Concat and Repeat. We can process multiple sequences concurrently with Merge, or process a single sequence at a time with Amb and Switch. Pairing values with CombineLatest, Zip and the And/Then/When operators can simplify otherwise fiddly operations like our drag-and-drop examples and monitoring system status.\n", "\n", "\n" ] }, { "cell_type": "code", "execution_count": 59, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", " 1.1 M New subscription on stream 275998165\n", "1002.9 T1468 [next] 1001.8: \n", "\n", "Seconds : 0 from time: 1.00\n", "HalfSecs: 0 from time: 0.50\n", "10thS : 0 from time: 0.11\n", "2008.3 T1477 [next] 2007.2: \n", "\n", "Seconds : 1 from time: 2.01\n", "HalfSecs: 1 from time: 1.01\n", "10thS : 1 from time: 0.21\n", "3011.7 T1480 [next] 3010.6: \n", "\n", "Seconds : 2 from time: 3.01\n", "HalfSecs: 2 from time: 1.51\n", "10thS : 2 from time: 0.32\n", "4014.4 T1483 [next] 4013.3: \n", "\n", "Seconds : 3 from time: 4.01\n", "HalfSecs: 3 from time: 2.01\n", "10thS : 3 from time: 0.42\n", "5018.9 T1484 [next] 5017.8: \n", "\n", "Seconds : 4 from time: 5.02\n", "HalfSecs: 4 from time: 2.52\n", "10thS : 4 from time: 0.52\n", "5019.9 T1484 [cmpl] 5018.8: fin\n" ] } ], "source": [ "rst()\n", "# see the similarity to zip. \n", "ts = time.time()\n", "def _dt():\n", " # giving us info when an element was created:\n", " return 'from time: %.2f' % (time.time() - ts)\n", "one = O.interval(1000) .map(lambda i: 'Seconds : %s %s' % (i, _dt())).take(5)\n", "two = O.interval(500) .map(lambda i: 'HalfSecs: %s %s' % (i, _dt())).take(5)\n", "three = O.interval(100).map(lambda i: '10thS : %s %s' % (i, _dt())).take(5)\n", "\n", "z = O.when(\n", " one \\\n", " .and_(two) \\\n", " .and_(three)\\\n", " .then_do(lambda a, b, c: '\\n'.join(('', '', a, b, c))))\n", "\n", "# from the output you see that the result stream consists of elements built at each interval\n", "# (which is in the past for 'two' and 'three'),\n", "# buffered until the 1 second sequence 'one' advances a step. \n", "d = subs(z)\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## ... and emitting the items from only the most-recently emitted of those Observables **[switch_latest](http://reactivex.io/documentation/operators/and-then-when.html)**" ] }, { "cell_type": "code", "execution_count": 71, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "function switch_latest of module rx.linq.observable.switchlatest:\n", "Transforms an observable sequence of observable sequences into an\n", " observable sequence producing values only from the most recent\n", " observable sequence.\n", "\n", " :returns: The observable sequence that at any point in time produces the\n", " elements of the most recent inner observable sequence that has been\n", " received.\n", " :rtype: Observable\n", " \n", "--------------------------------------------------------------------------------\n", "\n", " 0.9 M New subscription on stream 275998049\n", " 1.8 M [next] 0.8: 0 (from stream nr 0)\n", " 2.9 M [next] 1.9: 1 (from stream nr 1)\n", " 3.5 M [next] 2.5: 2 (from stream nr 2)\n", " 3.9 M [next] 2.9: 3 (from stream nr 2)\n", " 4.0 M [next] 3.0: 4 (from stream nr 2)\n", " 4.2 M [cmpl] 3.2: fin\n" ] } ], "source": [ "rst(O.switch_latest)\n", "s = O.range(0, 3).select(lambda x: O.range(x, 3)\\\n", " # showing from which stream our current value comes:\n", " .map(lambda v: '%s (from stream nr %s)' % (v, x)))\\\n", " .switch_latest()\n", "d = subs(s)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 2", "language": "python", "name": "python2" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", "version": "2.7.12" } }, "nbformat": 4, "nbformat_minor": 1 } RxPY-3.2.0/notebooks/reactivex.io/Part III - Transformation.ipynb000066400000000000000000000600301404130727200244300ustar00rootroot00000000000000{ "cells": [ { "cell_type": "code", "execution_count": 4, "metadata": { "collapsed": true }, "outputs": [], "source": [ "%run startup.py" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "collapsed": false }, "outputs": [ { "data": { "application/javascript": [ "$.getScript('./assets/js/ipython_notebook_toc.js')" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "%%javascript\n", "$.getScript('./assets/js/ipython_notebook_toc.js')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# A Decision Tree of Observable Operators\n", "\n", "## Part 3: Transformation\n", "\n", "> source: http://reactivex.io/documentation/operators.html#tree. \n", "> (transcribed to RxPY 1.5.7, Py2.7 / 2016-12, Gunther Klessinger, [axiros](http://www.axiros.com)) \n", "\n", "**This tree can help you find the ReactiveX Observable operator you’re looking for.** \n", "See [Part 1](./A Decision Tree of Observable Operators. Part I - Creation.ipynb) for Usage and Output Instructions. \n", "\n", "We also require acquaintance with the [marble diagrams](./Marble Diagrams.ipynb) feature of RxPy.\n", "\n", "

    Table of Contents

    \n", "
    \n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# I want emit the items from an Observable after **transforming** them\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## ... one at a time with a function **[map / pluck / pluck_attr](http://reactivex.io/documentation/operators/map.html) **" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== map ==========\n", "\n", "function select of module rx.operators.observable.select:\n", "Project each element of an observable sequence into a new form\n", " by incorporating the element's index.\n", "\n", " 1 - source.map(lambda value: value * value)\n", " 2 - source.map(lambda value, index: value * value + index)\n", "\n", " Keyword arguments:\n", " :param Callable[[Any, Any], Any] mapper: A transform function to\n", " apply to each source element; the second parameter of the\n", " function represents the index of the source element.\n", " :rtype: Observable\n", "\n", " Returns an observable sequence whose elements are the result of\n", " invoking the transform function on each element of source.\n", " \n", "--------------------------------------------------------------------------------\n", "\n", " 0.6 M New subscription on stream 276083189\n", " 2.1 M [next] 1.5: 2\n", " 2.5 M [next] 1.9: 4\n", " 3.0 M [next] 2.4: 6\n", " 3.3 M [cmpl] 2.7: fin\n" ] } ], "source": [ "reset_start_time(O.map, title='map') # alias is \"select\"\n", "# warming up:\n", "d = subs(O.from_((1, 2 , 3)).map(lambda x: x * 2))" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== pluck ==========\n", "\n", "function pluck of module rx.operators.observable.pluck:\n", "Retrieves the value of a specified key using dict-like access (as in\n", " element[key]) from all elements in the Observable sequence.\n", "\n", " Keyword arguments:\n", " key {String} The key to pluck.\n", "\n", " Returns a new Observable {Observable} sequence of key values.\n", "\n", " To pluck an attribute of each element, use pluck_attr.\n", "\n", " \n", "--------------------------------------------------------------------------------\n", "\n", " 1.0 M New subscription on stream 276081749\n", " 1.8 M [next] 0.6: 2\n", " 2.2 M [next] 1.0: 4\n", " 2.5 M [cmpl] 1.3: fin\n", "\n", "\n", "========== pluck_attr ==========\n", "\n", "\n", " 0.4 M New subscription on stream 276081825\n", " 0.6 M [next] 0.2: 2\n", " 0.8 M [next] 0.4: 4\n", " 1.0 M [cmpl] 0.6: fin\n" ] } ], "source": [ "rst(O.pluck, title='pluck')\n", "d = subs(O.from_([{'x': 1, 'y': 2}, {'x': 3, 'y': 4}]).pluck('y'))\n", "\n", "class Coord:\n", " def __init__(self, x, y): \n", " self.x = x\n", " self.y = y\n", "rst(title='pluck_attr') \n", "d = subs(O.from_([Coord(1, 2), Coord(3, 4)]).pluck_attr('y'))\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## ...by emitting all of the items emitted by corresponding Observables" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### **[flat_map(flat_map)](http://reactivex.io/documentation/operators/flatmap.html)**" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "function flat_map of module rx.operators.observable.selectmany:\n", "One of the Following:\n", " Projects each element of an observable sequence to an observable\n", " sequence and merges the resulting observable sequences into one\n", " observable sequence.\n", "\n", " 1 - source.flat_map(lambda x: Observable.range(0, x))\n", "\n", " Or:\n", " Projects each element of an observable sequence to an observable\n", " sequence, invokes the result mapper for the source element and each\n", " of the corresponding inner sequence's elements, and merges the results\n", " into one observable sequence.\n", "\n", " 1 - source.flat_map(lambda x: Observable.range(0, x), lambda x, y: x + y)\n", "\n", " Or:\n", " Projects each element of the source observable sequence to the other\n", " observable sequence and merges the resulting observable sequences into\n", " one observable sequence.\n", "\n", " 1 - source.flat_map(Observable.from_([1,2,3]))\n", "\n", " Keyword arguments:\n", " mapper -- A transform function to apply to each element or an\n", " observable sequence to project each element from the source\n", " sequence onto.\n", " result_mapper -- [Optional] A transform function to apply to each\n", " element of the intermediate sequence.\n", "\n", " Returns an observable sequence whose elements are the result of\n", " invoking the one-to-many transform function collectionSelector on each\n", " element of the input sequence and then mapping each of those sequence\n", " elements and their corresponding source element to a result element.\n", " \n", "--------------------------------------------------------------------------------\n", "\n", " 0.5 M New subscription on stream 275034569\n", " 2.2 M [next] 1.4: 1\n", " 2.9 M [next] 2.1: 2\n", " 3.0 M [next] 2.3: 2\n", " 3.5 M [next] 2.8: 3\n", " 3.9 M [cmpl] 3.1: fin\n" ] } ], "source": [ "rst(O.flat_map)\n", "stream = O.range(1, 2)\\\n", " .flat_map(lambda x: O.range(x, 2)) # alias: flat_map\n", "d = subs(stream)" ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", " 0.4 M New subscription on stream 276591581\n", " 1.6 M [next] 1.0: a\n", " 2.5 M [next] 1.9: b\n", " 3.5 M [next] 2.9: c\n", " 4.0 M [cmpl] 3.5: fin\n", "\n", " 4.5 M New subscription on stream 276589117\n", " 5.2 M [next] 0.6: a\n", " 5.6 M [next] 1.0: 0\n", " 5.9 M [next] 1.2: b\n", " 6.5 M [next] 1.8: 1\n", " 7.0 M [next] 2.4: c\n", " 7.9 M [next] 3.3: 2\n", " 8.7 M [cmpl] 4.1: fin\n", "\n", "\n", "========== using a result mapper ==========\n", "\n", "\n", " 10.1 M New subscription on stream 276589129\n", " 11.0 M [next] 0.7: a-a-0\n", " 11.8 M [next] 1.5: a-0-1\n", " 12.0 M [next] 1.7: b-b-0\n", " 12.9 M [next] 2.6: b-1-1\n", " 13.1 M [next] 2.9: c-c-0\n", " 14.3 M [next] 4.0: c-2-1\n", " 15.0 M [cmpl] 4.7: fin\n", "\n", " 15.9 M New subscription on stream 276582045\n", " 17.2 M [next] 1.0: a-a-0\n", " 18.4 M [next] 2.2: a-0-1\n", " 18.8 M [next] 2.6: b-b-0\n", " 20.0 M [next] 3.8: b-1-1\n", " 20.7 M [next] 4.5: c-c-0\n", " 21.4 M [next] 5.2: c-2-1\n", " 22.0 M [cmpl] 5.8: fin\n" ] } ], "source": [ "rst() # from an array\n", "s1 = O.from_(('a', 'b', 'c'))\n", "d = subs(s1.flat_map(lambda x: x))\n", "d = subs(s1.flat_map(lambda x, i: (x, i)))\n", "#d = subs(O.from_(('a', 'b', 'c')).flat_map(lambda x, i: '%s%s' % (x, i))) # ident, a string is iterable\n", "\n", "header('using a result mapper')\n", "\n", "def res_sel(*a):\n", " # in conrast to the RxJS example I get only 3 parameters, see output\n", " return '-'.join([str(s) for s in a])\n", "\n", "# for every el of the original stream we get *additional* two elements: the el and its index:\n", "d = subs(s1.flat_map(lambda x, i: (x, i) , res_sel))\n", "# ident, flat_map flattens the inner stream:\n", "d = subs(s1.flat_map(lambda x, i: O.from_((x, i)), res_sel))\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### **[flat_map_latest(select_switch)](http://reactivex.io/documentation/operators/flatmap.html)**" ] }, { "cell_type": "code", "execution_count": 29, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "function select_switch of module rx.operators.observable.selectswitch:\n", "Projects each element of an observable sequence into a new sequence\n", " of observable sequences by incorporating the element's index and then\n", " transforms an observable sequence of observable sequences into an\n", " observable sequence producing values only from the most recent\n", " observable sequence.\n", "\n", " Keyword arguments:\n", " mapper -- {Function} A transform function to apply to each source\n", " element; the second parameter of the function represents the index\n", " of the source element.\n", "\n", " Returns an observable {Observable} sequence whose elements are the\n", " result of invoking the transform function on each element of source\n", " producing an Observable of Observable sequences and that at any point in\n", " time produces the elements of the most recent inner observable sequence\n", " that has been received.\n", " \n", "--------------------------------------------------------------------------------\n", "\n", " 0.5 M New subscription on stream 276552361\n", " 1.6 M [next] 1.0: 1\n", " 2.4 M [next] 1.8: 2\n", " 2.7 M [next] 2.1: 3\n", " 3.0 M [cmpl] 2.5: fin\n", "\n", " 3.8 M New subscription on stream 274776265\n", " 4.5 M [next] 0.5: A0-a\n", " 5.2 M [next] 1.3: B1-a\n", " 5.6 M [next] 1.7: C2-a\n", " 6.0 M [next] 2.1: C2-b\n", " 6.3 M [next] 2.3: C2-c\n", " 6.5 M [cmpl] 2.5: fin\n", "\n", " 7.2 M New subscription on stream 276554429\n", " 29.4 T140 [next] 22.1: Aa\n", " 142.3 T142 [next] 135.0: Ab\n", " 240.7 T148 [next] 233.4: Ba\n", " 355.5 T149 [next] 348.2: Bb\n", " 456.5 T156 [next] 449.3: Ca\n", " 570.4 T157 [next] 563.1: Cb\n", " 677.2 T159 [next] 669.9: Cc\n", " 688.4 T161 [cmpl] 681.1: fin\n" ] } ], "source": [ "rst(O.flat_map_latest) # alias: select_switch\n", "\n", "d = subs(O.range(1, 2).flat_map_latest(lambda x: O.range(x, 2)))\n", "\n", "# maybe better to understand: A, B, C are emitted always more recent, then the inner streams' elements\n", "d = subs(O.from_(('A', 'B', 'C')).flat_map_latest(\n", " lambda x, i: O.from_(('%s%s-a' % (x, i),\n", " '%s%s-b' % (x, i),\n", " '%s%s-c' % (x, i),\n", " ))))\n", "\n", "# with emission delays: Now the inner's is faster:\n", "outer = O.from_marbles('A--B--C|').to_blocking()\n", "inner = O.from_marbles('a-b-c|').to_blocking()\n", "# the inner .map is to show also outer's value\n", "d = subs(outer.flat_map_latest(lambda X: inner.map(lambda x: '%s%s' % (X, x))))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### **[concat_map](http://reactivex.io/documentation/operators/flatmap.html)**" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "function for_in of module rx.operators.observable.forin:\n", "Concatenates the observable sequences obtained by running the\n", " specified result mapper for each element in source.\n", "\n", " sources -- {Array} An array of values to turn into an observable\n", " sequence.\n", " result_mapper -- {Function} A function to apply to each item in the\n", " sources array to turn it into an observable sequence.\n", " Returns an observable {Observable} sequence from the concatenated\n", " observable sequences.\n", " \n", "--------------------------------------------------------------------------------\n", "\n", " 0.9 M New subscription on stream 276573325\n", " 12.3 T28 [next] 11.2: a1\n", " 122.9 T29 [next] 121.7: b1\n", " 147.8 T34 [next] 146.6: a2\n", " 261.4 T36 [next] 260.2: b2\n", " 284.2 T40 [next] 283.0: a3\n", " 394.4 T41 [next] 393.3: b3\n", " 404.6 T42 [cmpl] 403.4: fin\n", "\n", " 505.1 M New subscription on stream 276563949\n", " 517.7 T46 [next] 12.6: a\n", " 627.3 T48 [next] 122.2: b\n", " 628.0 T48 [cmpl] 122.9: fin\n" ] } ], "source": [ "rst(O.for_in)\n", "abc = O.from_marbles('a-b|').to_blocking()\n", "\n", "# abc times 3, via:\n", "d = subs(O.for_in([1, 2, 3],\n", " lambda i: abc.map(\n", " # just to get the results of array and stream:\n", " lambda letter: '%s%s' % (letter, i) )))\n", "sleep(0.5)\n", "# we can also for_in from an observable.\n", "# TODO: Dont' understand the output though - __doc__ says only arrays.\n", "d = subs(O.for_in(O.from_((1, 2, 3)),\n", " lambda i: abc.map(lambda letter: '%s%s' % (letter, i) )).take(2))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### **[many_select](http://reactivex.io/documentation/operators/flatmap.html)**\n", "manySelect internally transforms each item emitted by the source Observable into an Observable that emits that item and all items subsequently emitted by the source Observable, in the same order. \n", "\n", "So, for example, it internally transforms an Observable that emits the numbers 1,2,3 into three Observables: one that emits 1,2,3, one that emits 2,3, and one that emits 3.\n", "\n", "Then manySelect passes each of these Observables into a function that you provide, and emits, as the emissions from the Observable that manySelect returns, the return values from those function calls.\n", "\n", "In this way, each item emitted by the resulting Observable is a function of the corresponding item in the source Observable and all of the items emitted by the source Observable after it." ] }, { "cell_type": "code", "execution_count": 25, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "function many_select of module rx.operators.observable.manyselect:\n", "Comonadic bind operator. Internally projects a new observable for each\n", " value, and it pushes each observable into the user-defined mapper function\n", " that projects/queries each observable into some result.\n", "\n", " Keyword arguments:\n", " mapper -- {Function} A transform function to apply to each element.\n", " scheduler -- {Object} [Optional] Scheduler used to execute the\n", " operation. If not specified, defaults to the ImmediateScheduler.\n", "\n", " Returns {Observable} An observable sequence which results from the\n", " comonadic bind operation.\n", " \n", "--------------------------------------------------------------------------------\n", "\n", " 1.0 M New subscription on stream 276604289\n", " 13.0 T196 [next] 11.7: a\n", " 123.5 T197 [next] 122.2: b\n", " 234.1 T200 [next] 232.8: c\n", " 246.1 T202 [cmpl] 244.7: fin\n" ] } ], "source": [ "rst(O.many_select) \n", "stream = O.from_marbles('a-b-c|')\n", "# TODO: more use cases\n", "d = subs(stream.many_select(lambda x: x.first()).merge_all())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## ... based on ALL of the items that preceded them **[scan](http://reactivex.io/documentation/operators/scan.html) **" ] }, { "cell_type": "code", "execution_count": 32, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "function scan of module rx.operators.observable.scan:\n", "Applies an accumulator function over an observable sequence and\n", " returns each intermediate result. The optional seed value is used as\n", " the initial accumulator value. For aggregation behavior with no\n", " intermediate results, see Observable.aggregate.\n", "\n", " 1 - scanned = source.scan(lambda acc, x: acc + x)\n", " 2 - scanned = source.scan(lambda acc, x: acc + x, 0)\n", "\n", " Keyword arguments:\n", " accumulator -- An accumulator function to be invoked on each element.\n", " seed -- [Optional] The initial accumulator value.\n", "\n", " Returns an observable sequence containing the accumulated values.\n", " \n", "--------------------------------------------------------------------------------\n", "\n", " 0.7 M New subscription on stream 276601945\n", " 14.1 T240 [next] 13.1: 10001\n", " 128.1 T242 [next] 127.0: 10003\n", " 234.7 T243 [next] 233.6: 10006\n", " 345.3 T246 [next] 344.3: 10010\n", " 656.9 T247 [next] 655.8: 10015\n", " 657.6 T249 [cmpl] 656.5: fin\n" ] } ], "source": [ "rst(O.scan)\n", "s = O.from_marbles(\"1-2-3-4---5\").to_blocking()\n", "d = subs(s.scan(lambda x, y: int(x) + int(y), seed=10000))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## ... by attaching a timestamp to them **[timestamp](http://reactivex.io/documentation/operators/timestamp.html) **" ] }, { "cell_type": "code", "execution_count": 38, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "function timestamp of module rx.operators.observable.timestamp:\n", "Records the timestamp for each value in an observable sequence.\n", "\n", " 1 - res = source.timestamp() # produces { \"value\": x, \"timestamp\": ts }\n", " 2 - res = source.timestamp(Scheduler.timeout)\n", "\n", " :param Scheduler scheduler: [Optional] Scheduler used to compute timestamps. If not\n", " specified, the timeout scheduler is used.\n", "\n", " Returns an observable sequence with timestamp information on values.\n", " \n", "--------------------------------------------------------------------------------\n", "\n", " 0.7 M New subscription on stream 276594097\n", " 12.1 T276 [next] 11.2: 2016-12-20 20:23:22.236924\n", " 122.7 T277 [next] 121.8: 2016-12-20 20:23:22.347565\n", " 235.4 T279 [next] 234.5: 2016-12-20 20:23:22.460266\n", " 244.0 T281 [cmpl] 243.1: fin\n" ] } ], "source": [ "rst(O.timestamp)\n", "# the timestamps are objects, not dicts:\n", "d = subs(marble_stream('a-b-c|').timestamp().pluck_attr('timestamp'))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## ... into an indicator of the amount of time that lapsed before the emission of the item **[time_interval](http://reactivex.io/documentation/operators/timeinterval.html) **" ] }, { "cell_type": "code", "execution_count": 44, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "function time_interval of module rx.operators.observable.timeinterval:\n", "Records the time interval between consecutive values in an\n", " observable sequence.\n", "\n", " 1 - res = source.time_interval();\n", " 2 - res = source.time_interval(Scheduler.timeout)\n", "\n", " Keyword arguments:\n", " scheduler -- [Optional] Scheduler used to compute time intervals. If\n", " not specified, the timeout scheduler is used.\n", "\n", " Return An observable sequence with time interval information on values.\n", " \n", "--------------------------------------------------------------------------------\n", "\n", " 0.8 M New subscription on stream 276593965\n", " 12.7 T316 [next] 11.8: 0:00:00.011533\n", " 127.7 T318 [next] 126.8: 0:00:00.115022\n", " 339.1 T319 [next] 338.2: 0:00:00.211367\n", " 344.4 T322 [cmpl] 343.6: fin\n" ] } ], "source": [ "rst(O.time_interval)\n", "d = subs(marble_stream('a-b--c|').time_interval().map(lambda x: x.interval))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 2", "language": "python", "name": "python2" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", "version": "2.7.12" } }, "nbformat": 4, "nbformat_minor": 1 } RxPY-3.2.0/notebooks/reactivex.io/Part IV - Grouping, Buffering, Delaying, misc.ipynb000066400000000000000000001345571404130727200276650ustar00rootroot00000000000000{ "cells": [ { "cell_type": "code", "execution_count": 4, "metadata": { "collapsed": true }, "outputs": [], "source": [ "%run startup.py" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "collapsed": false }, "outputs": [ { "data": { "application/javascript": [ "$.getScript('./assets/js/ipython_notebook_toc.js')" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "%%javascript\n", "$.getScript('./assets/js/ipython_notebook_toc.js')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# A Decision Tree of Observable Operators\n", "\n", "## Part 4: Grouping, Buffering, Delaying, misc\n", "\n", "> source: http://reactivex.io/documentation/operators.html#tree. \n", "> (transcribed to RxPY 1.5.7, Py2.7 / 2016-12, Gunther Klessinger, [axiros](http://www.axiros.com)) \n", "\n", "**This tree can help you find the ReactiveX Observable operator you’re looking for.** \n", "See [Part 1](./A Decision Tree of Observable Operators. Part I - Creation.ipynb) for Usage and Output Instructions. \n", "\n", "We also require acquaintance with the [marble diagrams](./Marble Diagrams.ipynb) feature of RxPy.\n", "\n", "

    Table of Contents

    \n", "
    \n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# I want to shift the items emitted by an Observable forward in time before reemitting them" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## ... **[delay](http://reactivex.io/documentation/operators/delay.html) **" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== delay ==========\n", "\n", "module rx.linq.observable.delay\n", "@extensionmethod(ObservableBase)\n", "def delay(self, duetime, scheduler=None):\n", " Time shifts the observable sequence by duetime. The relative time\n", " intervals between the values are preserved.\n", "\n", " 1 - res = rx.Observable.delay(datetime())\n", " 2 - res = rx.Observable.delay(datetime(), Scheduler.timeout)\n", "\n", " 3 - res = rx.Observable.delay(5000)\n", " 4 - res = rx.Observable.delay(5000, Scheduler.timeout)\n", "\n", " Keyword arguments:\n", " :param datetime|int duetime: Absolute (specified as a datetime object) or\n", " relative time (specified as an integer denoting milliseconds) by which\n", " to shift the observable sequence.\n", " :param Scheduler scheduler: [Optional] Scheduler to run the delay timers on.\n", " If not specified, the timeout scheduler is used.\n", "\n", " :returns: Time-shifted sequence.\n", " :rtype: Observable\n", "--------------------------------------------------------------------------------\n", "\n", " 3.0 M New subscription on stream 276458585\n", " 14.7 T4 [next] 11.7: 1\n", " 125.8 T5 [next] 122.8: 2\n", " 169.0 T20 [next] 165.9: a\n", " 235.4 T7 [next] 232.4: 3\n", " 279.2 T21 [next] 276.2: b\n", " 392.2 T22 [next] 389.2: c\n", " 402.1 T23 [cmpl] 399.1: fin\n" ] } ], "source": [ "reset_start_time(O.delay)\n", "d = subs(marble_stream('a-b-c|').delay(150).merge(marble_stream('1-2-3|')))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# I want to transform items and notifications from an Observable into items and reemit them" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## ... by wrapping them in Notification objects **[materialize](http://reactivex.io/documentation/operators/materialize-dematerialize.html)**" ] }, { "cell_type": "code", "execution_count": 15, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "function materialize of module rx.linq.observable.materialize:\n", "Materializes the implicit notifications of an observable sequence as\n", " explicit notification values.\n", "\n", " Returns an observable sequence containing the materialized notification\n", " values from the source sequence.\n", " \n", "--------------------------------------------------------------------------------\n", "\n", " 0.8 M New subscription on stream 277716117\n", " 1.4 M [next] 0.5: kind: N, value: 1\n", " 1.8 M [next] 0.9: kind: N, value: 2\n", " 2.3 M [next] 1.3: kind: N, value: 3\n", " 2.5 M [next] 1.5: kind: C, value: None\n", " 2.5 M [cmpl] 1.5: fin\n" ] } ], "source": [ "rst(O.materialize)\n", "def pretty(notif):\n", " # this are the interesting attributes:\n", " return 'kind: %(kind)s, value: %(value)s' % ItemGetter(notif)\n", "d = subs(O.from_((1, 2, 3)).materialize().map(lambda x: pretty(x)))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### ... which I can then unwrap again with **[dematerialize](http://reactivex.io/documentation/operators/materialize-dematerialize.html)**" ] }, { "cell_type": "code", "execution_count": 22, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "function dematerialize of module rx.linq.observable.dematerialize:\n", "Dematerializes the explicit notification values of an observable\n", " sequence as implicit notifications.\n", "\n", " Returns an observable sequence exhibiting the behavior corresponding to\n", " the source sequence's notification values.\n", " \n", "--------------------------------------------------------------------------------\n", "\n", " 0.8 M New subscription on stream 276437645\n", " 1.4 M [next] 0.5: 1\n", " 1.8 M [next] 0.8: 2\n", " 2.3 M [next] 1.4: 3\n", " 2.6 M [cmpl] 1.7: fin\n", "\n", "\n", "========== Dematerializing manually created notifs ==========\n", "\n", "\n", " 3.3 M New subscription on stream 276437669\n", " 3.6 M [next] 0.3: foo\n", " 3.9 M [cmpl] 0.5: fin\n" ] } ], "source": [ "rst(O.dematerialize)\n", "d = subs(O.from_((1, 2, 3)).materialize().dematerialize())\n", "\n", "header('Dematerializing manually created notifs')\n", "d = subs(O.from_((rx.core.notification.OnNext('foo'), rx.core.notification.OnCompleted())).dematerialize())" ] }, { "cell_type": "code", "execution_count": 28, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "898987.2 M New subscription on stream 276437705\n", "{mydump}-->{OnNext(1)}\n", "898987.9 M [next] 0.7: OnNext(1)\n", "{mydump}-->{OnNext(2)}\n", "898988.5 M [next] 1.3: OnNext(2)\n", "{mydump}-->{OnNext(3)}\n", "898989.0 M [next] 1.7: OnNext(3)\n", "{mydump}-->{OnCompleted()}\n", "898989.5 M [next] 2.2: OnCompleted()\n", "{mydump} completed\n", "898989.8 M [cmpl] 2.5: fin\n" ] } ], "source": [ "# Materializing a sequence can be very handy for performing analysis or logging of a sequence.\n", "# You can unwrap a materialized sequence by applying the Dematerialize extension method. \n", "from rx.testing import dump\n", "d = subs(O.range(1, 3).materialize().dump(name='mydump'))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# I want to ignore all items emitted by an Observable and only pass along its completed/error notification" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## ... **[ignore_elements](http://reactivex.io/documentation/operators/ignoreelements.html)**" ] }, { "cell_type": "code", "execution_count": 18, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "function ignore_elements of module rx.linq.observable.ignoreelements:\n", "Ignores all elements in an observable sequence leaving only the\n", " termination messages.\n", "\n", " Returns an empty observable {Observable} sequence that signals\n", " termination, successful or exceptional, of the source sequence.\n", " \n", "--------------------------------------------------------------------------------\n", "\n", " 0.5 M New subscription on stream 277710309\n", " 2.2 M [cmpl] 1.6: fin\n" ] } ], "source": [ "rst(O.ignore_elements)\n", "d = subs(O.range(0, 10).ignore_elements())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# I want to mirror an Observable but prefix items to its sequence **[start_with](http://reactivex.io/documentation/operators/startwith.html)**" ] }, { "cell_type": "code", "execution_count": 32, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "function start_with of module rx.linq.observable.startswith:\n", "Prepends a sequence of values to an observable sequence with an\n", " optional scheduler and an argument list of values to prepend.\n", "\n", " 1 - source.start_with(1, 2, 3)\n", " 2 - source.start_with(Scheduler.timeout, 1, 2, 3)\n", "\n", " Returns the source sequence prepended with the specified values.\n", " \n", "--------------------------------------------------------------------------------\n", "\n", " 1.7 M New subscription on stream 276437529\n", " 2.3 M [next] 0.5: 1\n", " 2.5 M [next] 0.7: 2\n", " 2.8 M [next] 0.9: 3\n", " 3.4 M [next] 1.5: a\n", " 3.6 M [next] 1.8: b\n", " 3.9 M [cmpl] 2.0: fin\n" ] } ], "source": [ "rst(O.start_with)\n", "d = subs(O.from_(('a', 'b')).start_with(1, 2, 3))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## ... only if its sequence is empty **[default_if_empty](http://reactivex.io/documentation/operators/defaultifempty.html)**" ] }, { "cell_type": "code", "execution_count": 36, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "function default_if_empty of module rx.linq.observable.defaultifempty:\n", "Returns the elements of the specified sequence or the specified value\n", " in a singleton sequence if the sequence is empty.\n", "\n", " res = obs = xs.defaultIfEmpty()\n", " obs = xs.defaultIfEmpty(False\n", "\n", " Keyword arguments:\n", " default_value -- The value to return if the sequence is empty. If not\n", " provided, this defaults to None.\n", "\n", " Returns an observable {Observable} sequence that contains the specified\n", " default value if the source is empty otherwise, the elements of the\n", " source itself.\n", " \n", "--------------------------------------------------------------------------------\n", "\n", " 0.8 M New subscription on stream 276444765\n", " 1.4 M [next] 0.4: None\n", " 1.5 M [cmpl] 0.5: fin\n", "\n", " 2.5 M New subscription on stream 276444761\n", " 3.0 M [next] 0.2: hello world\n", " 3.3 M [cmpl] 0.5: fin\n" ] } ], "source": [ "rst(O.default_if_empty)\n", "# the default here is to emit a None:\n", "d = subs(O.empty().default_if_empty())\n", "d = subs(O.empty().default_if_empty('hello world'))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# I want to collect items from an Observable and reemit them as buffers of items **[buffer](http://reactivex.io/documentation/operators/buffer.html)**\n", "\n", "Very good intro is [here](http://xgrommx.github.io/rx-book/content/observable/observable_instance_methods/buffer.html) \n", "Buffer 'closing' means: The buffer is flushed to the subscriber(s), then next buffer is getting filled.\n", "\n", "Note: The used scheduler seems not 100% exact timewise on the marble streams. But you get the idea." ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "function buffer of module rx.linq.observable.buffer:\n", "Projects each element of an observable sequence into zero or more\n", " buffers.\n", "\n", " Keyword arguments:\n", " buffer_openings -- Observable sequence whose elements denote the\n", " creation of windows.\n", " closing_mapper -- Or, a function invoked to define the boundaries of\n", " the produced windows (a window is started when the previous one is\n", " closed, resulting in non-overlapping windows).\n", " buffer_closing_mapper -- [optional] A function invoked to define the\n", " closing of each produced window. If a closing mapper function is\n", " specified for the first parameter, this parameter is ignored.\n", "\n", " Returns an observable sequence of windows.\n", " \n", "--------------------------------------------------------------------------------\n", "\n", "\n", "========== with closing mapper ==========\n", "\n", "Returns an Observable that emits buffers of items it collects from the source Observable. The resulting Observable emits connected, non-overlapping buffers. It emits the current buffer and replaces it with a new buffer whenever the Observable produced by the specified bufferClosingSelector emits an item.\n", "\n", "Calling the closer as is:\n", "\n", " 1.4 M New subscription on stream 276484633\n", " 323.4 T319 [next] 322.1: ['1', '2', '3']\n", " 533.3 T321 [next] 531.9: ['4', '5']\n", " 910.1 T317 [next] 908.8: ['6', '7', '8', '9']\n", " 911.0 T317 [cmpl] 909.6: fin\n", "\n", "Calling again and again -> equal buffer sizes flushed\n", "\n", "2010.0 M New subscription on stream 276473777\n", "2334.4 T346 [next] 324.4: ['1', '2', '3']\n", "2649.7 T350 [next] 639.7: ['4', '5', '6']\n", "2921.9 T343 [next] 911.9: ['7', '8', '9']\n", "2922.6 T343 [cmpl] 912.6: fin\n" ] } ], "source": [ "rst(O.buffer)\n", "\n", "header('with closing mapper')\n", "# the simplest one:\n", "print('''Returns an Observable that emits buffers of items it collects from the source Observable. The resulting Observable emits connected, non-overlapping buffers. It emits the current buffer and replaces it with a new buffer whenever the Observable produced by the specified bufferClosingSelector emits an item.''')\n", "xs = marble_stream('1-2-3-4-5-6-7-8-9|')\n", "# defining when to flush the buffer to the subscribers:\n", "cs = marble_stream('---e--e----------|')\n", "print('\\nCalling the closer as is:')\n", "d = subs(xs.buffer(closing_mapper=cs))\n", "sleep(2)\n", "print('\\nCalling again and again -> equal buffer sizes flushed')\n", "cs = marble_stream('---e|')\n", "d = subs(xs.buffer(closing_mapper=lambda: cs))" ] }, { "cell_type": "code", "execution_count": 37, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== with buffer closing mapper ==========\n", "\n", "Subscribing two times with different buffer sizes\n", "\n", " 1.3 M New subscription on stream 277176113\n", "\n", " 12.6 M New subscription on stream 281768273\n", " 234.6 T1826 [next] 221.8: ['1', '2'] (small bufs)\n", " 452.4 T1830 [next] 439.6: ['3', '4'] (small bufs)\n", " 624.7 T1802 [next] 623.3: ['1', '2', '3', '4', '5', '6'] (BIIIIIG bufs)\n", " 668.6 T1834 [next] 655.8: ['5', '6'] (small bufs)\n", " 883.7 T1842 [next] 871.0: ['7', '8'] (small bufs)\n", " 911.4 T1800 [next] 910.1: ['7', '8', '9'] (BIIIIIG bufs)\n", " 912.1 T1800 [cmpl] 910.7: fin (BIIIIIG bufs)\n", " 920.1 T1823 [next] 907.3: ['9'] (small bufs)\n", " 920.7 T1823 [cmpl] 907.9: fin (small bufs)\n" ] } ], "source": [ "rst(title='with buffer closing mapper')\n", "\n", "xs = marble_stream('1-2-3-4-5-6-7-8-9|')\n", "# the more '-' the bigger the emitted buffers.\n", "# Called again and again:\n", "cs = marble_stream('------e|')\n", "cs2 = marble_stream('--e|')\n", "print ('Subscribing two times with different buffer sizes')\n", "d = subs(xs.buffer(buffer_closing_mapper=lambda: cs), name='BIIIIIG bufs')\n", "d = subs(xs.buffer(buffer_closing_mapper=lambda: cs2),name='small bufs')\n" ] }, { "cell_type": "code", "execution_count": 36, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== with buffer opening mapper ==========\n", "\n", "\n", " 0.8 M New subscription on stream 276484729\n", " 323.2 T1769 [next] 322.3: ['1', '2', '3']\n", " 642.0 T1774 [next] 641.2: ['4', '5', '6']\n", " 911.3 T1768 [next] 910.4: ['7', '8', '9']\n", " 915.0 T1768 [cmpl] 914.2: fin\n" ] } ], "source": [ "rst(title='with buffer opening mapper')\n", "\n", "xs = marble_stream('1-2-3-4-5-6-7-8-9|')\n", "opens = marble_stream('---o|')\n", "d = subs(xs.buffer(buffer_openings=lambda: opens))\n" ] }, { "cell_type": "code", "execution_count": 35, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== with buffer opening and closing mapper ==========\n", "\n", "\n", " 1.0 M New subscription on stream 276476373\n", "2530.7 T1741 [next] 2529.6: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '1', '2', '3', '4', '5', '6', '7', '8', '9', '1', '2', '3', '4', '5']\n", "2900.7 T1739 [next] 2899.7: ['6', '7', '8', '9']\n", "2901.9 T1739 [cmpl] 2900.8: fin\n" ] } ], "source": [ "rst(title='with buffer opening and closing mapper')\n", "#TODO: behaviour not really understood. Bug?\n", "xs = marble_stream('1-2-3-4-5-6-7-8-9-1-2-3-4-5-6-7-8-9-1-2-3-4-5-6-7-8-9|')\n", "opens = marble_stream('oo---------------------------------------------------|')\n", "closes = marble_stream('-------------------------c|')\n", "d = subs(xs.buffer(buffer_openings=opens, buffer_closing_mapper=lambda: closes))\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### ... buffering by counts **[buffer_with_count](http://reactivex.io/documentation/operators/buffer.html)**" ] }, { "cell_type": "code", "execution_count": 41, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "function buffer_with_count of module rx.linq.observable.buffer:\n", "Projects each element of an observable sequence into zero or more\n", " buffers which are produced based on element count information.\n", "\n", " Example:\n", " res = xs.buffer_with_count(10)\n", " res = xs.buffer_with_count(10, 1)\n", "\n", " Keyword parameters:\n", " count -- {Number} Length of each buffer.\n", " skip -- {Number} [Optional] Number of elements to skip between creation\n", " of consecutive buffers. If not provided, defaults to the count.\n", "\n", " Returns an observable {Observable} sequence of buffers.\n", " \n", "--------------------------------------------------------------------------------\n", "\n", " 0.8 M New subscription on stream 276532201\n", " 126.4 T1963 [next] 125.4: ['1', '2']\n", " 676.4 T1973 [next] 675.4: ['6', '7']\n", "1228.0 T1983 [next] 1226.9: ['2', '3']\n", "1782.0 T1993 [next] 1781.0: ['7', '8']\n", "1902.2 T1997 [cmpl] 1901.2: fin\n" ] } ], "source": [ "rst(O.buffer_with_count)\n", "xs = marble_stream('1-2-3-4-5-6-7-8-9-1-2-3-4-5-6-7-8-9|')\n", "d = subs(xs.buffer_with_count(2, skip=5))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### ... and take only the last (by count) **[take_last_buffer](http://reactivex.io/documentation/operators/takelast.html)**" ] }, { "cell_type": "code", "execution_count": 44, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "function take_last_buffer of module rx.linq.observable.takelastbuffer:\n", "Returns an array with the specified number of contiguous elements\n", " from the end of an observable sequence.\n", "\n", " Example:\n", " res = source.take_last(5)\n", "\n", " Description:\n", " This operator accumulates a buffer with a length enough to store\n", " elements count elements. Upon completion of the source sequence, this\n", " buffer is drained on the result sequence. This causes the elements to be\n", " delayed.\n", "\n", " Keyword arguments:\n", " :param int count: Number of elements to take from the end of the source\n", " sequence.\n", "\n", " :returns: An observable sequence containing a single list with the specified \n", " number of elements from the end of the source sequence.\n", " :rtype: Observable\n", " \n", "--------------------------------------------------------------------------------\n", "\n", " 1.2 M New subscription on stream 276495909\n", " 468.7 T2029 [next] 467.4: ['4', '5']\n", " 469.2 T2029 [cmpl] 468.0: fin\n" ] } ], "source": [ "rst(O.take_last_buffer)\n", "xs = marble_stream('1-2-3-4-5|')\n", "d = subs(xs.take_last_buffer(2))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### ... and take only the first (by time) **[take_with_time](http://reactivex.io/documentation/operators/takelast.html)**" ] }, { "cell_type": "code", "execution_count": 46, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "function take_with_time of module rx.linq.observable.takewithtime:\n", "Takes elements for the specified duration from the start of the\n", " observable source sequence, using the specified scheduler to run timers.\n", "\n", " Example:\n", " res = source.take_with_time(5000, [optional scheduler])\n", "\n", " Description:\n", " This operator accumulates a queue with a length enough to store elements\n", " received during the initial duration window. As more elements are\n", " received, elements older than the specified duration are taken from the\n", " queue and produced on the result sequence. This causes elements to be\n", " delayed with duration.\n", "\n", " Keyword arguments:\n", " duration -- {Number} Duration for taking elements from the start of the\n", " sequence.\n", " scheduler -- {Scheduler} Scheduler to run the timer on. If not\n", " specified, defaults to rx.Scheduler.timeout.\n", "\n", " Returns {Observable} An observable sequence with the elements taken\n", " during the specified duration from the start of the source sequence.\n", " \n", "--------------------------------------------------------------------------------\n", "\n", " 1.2 M New subscription on stream 281756025\n", " 14.0 T2046 [next] 12.6: 1\n", " 125.7 T2048 [next] 124.3: 2\n", " 234.1 T2049 [next] 232.7: 3\n", " 317.1 T2045 [cmpl] 315.7: fin\n" ] } ], "source": [ "rst(O.take_with_time)\n", "xs = marble_stream('1-2-3-4-5|')\n", "d = subs(xs.take_with_time(310))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### ... or only the last (by time) **[take_last_with_time](http://reactivex.io/documentation/operators/takelast.html)**" ] }, { "cell_type": "code", "execution_count": 47, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "function take_last_with_time of module rx.linq.observable.takelastwithtime:\n", "Returns elements within the specified duration from the end of the\n", " observable source sequence, using the specified schedulers to run timers\n", " and to drain the collected elements.\n", "\n", " Example:\n", " res = source.take_last_with_time(5000, scheduler)\n", "\n", " Description:\n", " This operator accumulates a queue with a length enough to store elements\n", " received during the initial duration window. As more elements are\n", " received, elements older than the specified duration are taken from the\n", " queue and produced on the result sequence. This causes elements to be\n", " delayed with duration.\n", "\n", " Keyword arguments:\n", " duration -- {Number} Duration for taking elements from the end of the\n", " sequence.\n", " scheduler -- {Scheduler} [Optional] Scheduler to run the timer on. If\n", " not specified, defaults to rx.Scheduler.timeout.\n", "\n", " Returns {Observable} An observable sequence with the elements taken\n", " during the specified duration from the end of the source sequence.\n", " \n", "--------------------------------------------------------------------------------\n", "\n", " 0.8 M New subscription on stream 277184037\n", " 468.7 T2067 [next] 467.7: 3\n", " 469.0 T2067 [next] 468.0: 4\n", " 469.3 T2067 [next] 468.3: 5\n", " 469.4 T2067 [cmpl] 468.4: fin\n" ] } ], "source": [ "rst(O.take_last_with_time)\n", "xs = marble_stream('1-2-3-4-5|')\n", "d = subs(xs.take_last_with_time(310))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# I want to split one Observable into multiple Observables **[window](http://reactivex.io/documentation/operators/window.html)**\n", "\n", "Window is similar to Buffer, but rather than emitting packets of items from the source Observable, it emits Observables, each one of which emits a subset of items from the source Observable and then terminates with an onCompleted notification.\n", "\n", "Like Buffer, Window has many varieties, each with its own way of subdividing the original Observable into the resulting Observable emissions, each one of which contains a “window” onto the original emitted items. In the terminology of the Window operator, when a window “opens,” this means that a new Observable is emitted and that Observable will begin emitting items emitted by the source Observable. When a window “closes,” this means that the emitted Observable stops emitting items from the source Observable and terminates with an onCompleted notification to its observers.\n", "\n", "from: http://www.introtorx.com/Content/v1.0.10621.0/17_SequencesOfCoincidence.html#Window\n", "> A major difference we see here is that the Window operators can notify you of values from the source as soon as they are produced. The Buffer operators, on the other hand, must wait until the window closes before the values can be notified as an entire list." ] }, { "cell_type": "code", "execution_count": 55, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== window with count ==========\n", "\n", "function window_with_count of module rx.linq.observable.windowwithcount:\n", "Projects each element of an observable sequence into zero or more\n", " windows which are produced based on element count information.\n", "\n", " 1 - xs.window_with_count(10)\n", " 2 - xs.window_with_count(10, 1)\n", "\n", " count -- Length of each window.\n", " skip -- [Optional] Number of elements to skip between creation of\n", " consecutive windows. If not specified, defaults to the count.\n", "\n", " Returns an observable sequence of windows.\n", " \n", "--------------------------------------------------------------------------------\n", "\n", " 1.1 M New subscription on stream 277141053\n", " 2.0 M starting new window 1\n", "\n", " 2.4 M New subscription on stream 276132537\n", " 3.0 M [next] 1.6: None (outer subscription)\n", " 106.7 T1844 [next] 104.0: 0 (window id 1)\n", " 212.4 T1845 [next] 209.7: 1 (window id 1)\n", " 313.7 T1846 [next] 310.9: 2 (window id 1)\n", " 314.0 T1846 [cmpl] 311.3: fin (window id 1)\n", " 314.5 T1846 starting new window 2\n", "\n", " 314.7 T1846 New subscription on stream 276613473\n", " 315.3 T1846 [next] 314.0: None (outer subscription)\n", " 419.1 T1847 [next] 104.4: 3 (window id 2)\n", " 521.0 T1848 [next] 206.3: 4 (window id 2)\n", " 625.9 T1849 [next] 311.2: 5 (window id 2)\n", " 626.3 T1849 [cmpl] 311.6: fin (window id 2)\n", " 626.6 T1849 starting new window 3\n", "\n", " 627.0 T1849 New subscription on stream 276609545\n", " 627.4 T1849 [next] 626.0: None (outer subscription)\n", " 730.2 T1850 [next] 103.2: 6 (window id 3)\n", " 835.8 T1851 [next] 208.8: 7 (window id 3)\n", " 937.1 T1852 [next] 310.1: 8 (window id 3)\n", " 937.5 T1852 [cmpl] 310.4: fin (window id 3)\n", " 937.7 T1852 starting new window 4\n", "\n", " 937.9 T1852 New subscription on stream 276550141\n", " 938.2 T1852 [next] 936.8: None (outer subscription)\n", "1039.1 T1853 [next] 101.3: 9 (window id 4)\n", "1039.5 T1853 [cmpl] 101.6: fin (window id 4)\n", "1040.3 T1853 [cmpl] 1038.9: fin (outer subscription)\n" ] } ], "source": [ "rst(O.window_with_count, title=\"window with count\")\n", "wid = 0 # window id\n", "def show_stream(window):\n", " global wid\n", " wid += 1\n", " log('starting new window', wid)\n", " # yes we can subscribe normally, its not buffers but observables:\n", " subs(window, name='window id %s' % wid)\n", " \n", "src = O.interval(100).take(10).window_with_count(3).map(lambda window: show_stream(window))\n", "\n", "d = subs(src, name='outer subscription')\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "> It is left to the reader to explore the other window functions offered by RxPY, working similar to buffer:" ] }, { "cell_type": "code", "execution_count": 22, "metadata": { "collapsed": false, "scrolled": true }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== window ==========\n", "\n", "function window of module rx.linq.observable.window:\n", "Projects each element of an observable sequence into zero or more\n", " windows.\n", "\n", " Keyword arguments:\n", " :param Observable window_openings: Observable sequence whose elements\n", " denote the creation of windows.\n", " :param types.FunctionType window_closing_mapper: [Optional] A function\n", " invoked to define the closing of each produced window. It defines the\n", " boundaries of the produced windows (a window is started when the\n", " previous one is closed, resulting in non-overlapping windows).\n", "\n", " :returns: An observable sequence of windows.\n", " :rtype: Observable[Observable]\n", " \n", "--------------------------------------------------------------------------------\n", "\n", "\n", "========== window_with_time(self, timespan, timeshift=None, scheduler=None) ==========\n", "\n", "function window_with_time of module rx.linq.observable.windowwithtime:\n", "n.a.\n", "--------------------------------------------------------------------------------\n", "\n", "\n", "========== window_with_time_or_count(self, timespan, count, scheduler=None) ==========\n", "\n", "function window_with_time_or_count of module rx.linq.observable.windowwithtimeorcount:\n", "n.a.\n", "--------------------------------------------------------------------------------\n" ] } ], "source": [ "rst(O.window, title=\"window\")\n", "rst(O.window_with_time, title=\"window_with_time(self, timespan, timeshift=None, scheduler=None)\")\n", "rst(O.window_with_time_or_count, title=\"window_with_time_or_count(self, timespan, count, scheduler=None)\")\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## ...so that similar items end up on the same Observable **[group_by](http://reactivex.io/documentation/operators/groupby.html)**\n", "\n", "The GroupBy operator divides an Observable that emits items into an Observable that emits Observables, each one of which emits some subset of the items from the original source Observable. Which items end up on which Observable is typically decided by a discriminating function that evaluates each item and assigns it a key. All items with the same key are emitted by the same Observable.\n" ] }, { "cell_type": "code", "execution_count": 56, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "function group_by of module rx.linq.observable.groupby:\n", "Groups the elements of an observable sequence according to a\n", " specified key mapper function and comparer and selects the resulting\n", " elements by using a specified function.\n", "\n", " 1 - observable.group_by(lambda x: x.id)\n", " 2 - observable.group_by(lambda x: x.id, lambda x: x.name)\n", " 3 - observable.group_by(\n", " lambda x: x.id,\n", " lambda x: x.name,\n", " lambda x: str(x))\n", "\n", " Keyword arguments:\n", " key_mapper -- A function to extract the key for each element.\n", " element_mapper -- [Optional] A function to map each source element to\n", " an element in an observable group.\n", " comparer -- {Function} [Optional] Used to determine whether the objects\n", " are equal.\n", "\n", " Returns a sequence of observable groups, each of which corresponds to a\n", " unique key value, containing all elements that share that same key\n", " value.\n", " \n", "--------------------------------------------------------------------------------\n", "Total streams: 6\n", "Count 2\n", "Count 2\n", "Count 2\n", "Count 2\n", "Count 1\n", "Count 1\n" ] } ], "source": [ "rst(O.group_by)\n", "keyCode = 'keyCode'\n", "codes = [\n", " { keyCode: 38}, #// up\n", " { keyCode: 38}, #// up\n", " { keyCode: 40}, #// down\n", " { keyCode: 40}, #// down\n", " { keyCode: 37}, #// left\n", " { keyCode: 39}, #// right\n", " { keyCode: 37}, #// left\n", " { keyCode: 39}, #// right\n", " { keyCode: 66}, #// b\n", " { keyCode: 65} #// a\n", "]\n", "\n", "src = O.from_(codes).group_by(\n", " key_mapper = lambda x: x[keyCode], # id of (potentially new) streams\n", " element_mapper = lambda x: x[keyCode] # membership to which stream\n", ")\n", "# we have now 6 streams\n", "src.count().subscribe(lambda total: print ('Total streams:', total))\n", "d = src.subscribe(lambda obs: obs.count().subscribe(lambda x: print ('Count', x)))" ] }, { "cell_type": "code", "execution_count": 57, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== group by (with time intervals) ==========\n", "\n", "function group_by_until of module rx.linq.observable.groupbyuntil:\n", "Groups the elements of an observable sequence according to a\n", " specified key mapper function. A duration mapper function is used\n", " to control the lifetime of groups. When a group expires, it receives\n", " an OnCompleted notification. When a new element with the same key value\n", " as a reclaimed group occurs, the group will be reborn with a new\n", " lifetime request.\n", "\n", " 1 - observable.group_by_until(\n", " lambda x: x.id,\n", " None,\n", " lambda : Rx.rx.never()\n", " )\n", " 2 - observable.group_by_until(\n", " lambda x: x.id,\n", " lambda x: x.name,\n", " lambda: Rx.rx.never()\n", " )\n", " 3 - observable.group_by_until(\n", " lambda x: x.id,\n", " lambda x: x.name,\n", " lambda: Rx.rx.never(),\n", " lambda x: str(x))\n", "\n", " Keyword arguments:\n", " key_mapper -- A function to extract the key for each element.\n", " duration_mapper -- A function to signal the expiration of a group.\n", " comparer -- [Optional] {Function} Used to compare objects. When not\n", " specified, the default comparer is used. Note: this argument will be\n", " ignored in the Python implementation of Rx. Python objects knows,\n", " or should know how to compare themselves.\n", "\n", " Returns a sequence of observable groups, each of which corresponds to\n", " a unique key value, containing all elements that share that same key\n", " value. If a group's lifetime expires, a new group with the same key\n", " value can be created once an element with such a key value is\n", " encountered.\n", " \n", "--------------------------------------------------------------------------------\n", "\n", "\n", "========== grouping interval short ==========\n", "\n", "Distinct elements within 20ms: 10\n", "\n", "\n", "========== grouping interval medium ==========\n", "\n", "Distinct elements within 200ms: 8\n", "\n", "\n", "========== grouping interval long ==========\n", "\n", "Distinct elements within 1000ms: 6\n" ] } ], "source": [ "rst(O.group_by_until, title='group by (with time intervals)')\n", "\n", "src = marble_stream('-(38)-(38)-(40)-(40)-(37)-(39)-(37)-(39)-(66)-(65)-|')\n", "\n", "def count(interval):\n", " grouped = src.group_by_until(\n", " key_mapper = lambda x: x, # id of (potentially new) streams\n", " element_mapper = lambda x: x, # membership to which stream\n", " duration_mapper= lambda x: O.timer(interval))\n", "\n", " d = grouped.count().subscribe(lambda total: print (\n", " 'Distinct elements within %sms: %s' % (interval, total)))\n", "\n", "\n", "header('grouping interval short')\n", "# now every event is unique, any older stream is forgotten when it occurs:\n", "count(20)\n", "sleep(2)\n", "header('grouping interval medium')\n", "# just enough to detect the directly following doublicates:\n", "count(200)\n", "sleep(2)\n", "header('grouping interval long')\n", "count(1000)\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# I want to retrieve a particular item emitted by an Observable:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## ... the first item emitted **[first](http://reactivex.io/documentation/operators/first.html)**" ] }, { "cell_type": "code", "execution_count": 50, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "function first of module rx.linq.observable.first:\n", "Returns the first element of an observable sequence that satisfies\n", " the condition in the predicate if present else the first item in the\n", " sequence.\n", "\n", " Example:\n", " res = res = source.first()\n", " res = res = source.first(lambda x: x > 3)\n", "\n", " Keyword arguments:\n", " predicate -- {Function} [Optional] A predicate function to evaluate for\n", " elements in the source sequence.\n", "\n", " Returns {Observable} Sequence containing the first element in the\n", " observable sequence that satisfies the condition in the predicate if\n", " provided, else the first item in the sequence.\n", " \n", "--------------------------------------------------------------------------------\n", "\n", " 0.6 M New subscription on stream 277141121\n", " 1.4 M [next] 0.6: 1\n", " 1.5 M [cmpl] 0.7: fin\n" ] } ], "source": [ "rst(O.first)\n", "d = subs(O.from_((1, 2, 3, 4)).first(lambda x, i: x < 3))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## ... the sole item it emitted **[single](http://reactivex.io/documentation/operators/single.html)**" ] }, { "cell_type": "code", "execution_count": 48, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "function single of module rx.linq.observable.single:\n", "Returns the only element of an observable sequence that satisfies the\n", " condition in the optional predicate, and reports an exception if there\n", " is not exactly one element in the observable sequence.\n", "\n", " Example:\n", " res = source.single()\n", " res = source.single(lambda x: x == 42)\n", "\n", " Keyword arguments:\n", " predicate -- {Function} [Optional] A predicate function to evaluate for\n", " elements in the source sequence.\n", "\n", " Returns {Observable} Sequence containing the single element in the\n", " observable sequence that satisfies the condition in the predicate.\n", " \n", "--------------------------------------------------------------------------------\n", "\n", " 0.7 M New subscription on stream 277177261\n", " 1.6 M [next] 0.9: 3\n", " 1.7 M [cmpl] 1.0: fin\n" ] } ], "source": [ "rst(O.single)\n", "# you can also match on the index i:\n", "d = subs(O.from_((1, 2, 3, 4)).single(lambda x, i: (x, i) == (3, 2)))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## ... the last item emitted before it completed **[last](http://reactivex.io/documentation/operators/last.html)**" ] }, { "cell_type": "code", "execution_count": 47, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "function last of module rx.linq.observable.last:\n", "Returns the last element of an observable sequence that satisfies the\n", " condition in the predicate if specified, else the last element.\n", "\n", " Example:\n", " res = source.last()\n", " res = source.last(lambda x: x > 3)\n", "\n", " Keyword arguments:\n", " predicate -- {Function} [Optional] A predicate function to evaluate for\n", " elements in the source sequence.\n", "\n", " Returns {Observable} Sequence containing the last element in the\n", " observable sequence that satisfies the condition in the predicate.\n", " \n", "--------------------------------------------------------------------------------\n", "\n", " 0.6 M New subscription on stream 276611193\n", " 2.1 M [next] 1.5: 2\n", " 2.4 M [cmpl] 1.7: fin\n" ] } ], "source": [ "rst(O.last)\n", "d = subs(O.from_((1, 2, 3, 4)).last(lambda x: x < 3))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 2", "language": "python", "name": "python2" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", "version": "2.7.12" } }, "nbformat": 4, "nbformat_minor": 1 } RxPY-3.2.0/notebooks/reactivex.io/Part V - Consolidating Streams.ipynb000066400000000000000000001447631404130727200254310ustar00rootroot00000000000000{ "cells": [ { "cell_type": "code", "execution_count": 1, "metadata": { "collapsed": false }, "outputs": [], "source": [ "%run startup.py" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "collapsed": false }, "outputs": [ { "data": { "application/javascript": [ "$.getScript('./assets/js/ipython_notebook_toc.js')" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "%%javascript\n", "$.getScript('./assets/js/ipython_notebook_toc.js')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# A Decision Tree of Observable Operators\n", "\n", "## Part 5: Consolidating Streams\n", "\n", "> source: http://reactivex.io/documentation/operators.html#tree. \n", "> (transcribed to RxPY 1.5.7, Py2.7 / 2016-12, Gunther Klessinger, [axiros](http://www.axiros.com)) \n", "\n", "**This tree can help you find the ReactiveX Observable operator you’re looking for.** \n", "See [Part 1](./A Decision Tree of Observable Operators. Part I - Creation.ipynb) for Usage and Output Instructions. \n", "\n", "We also require acquaintance with the [marble diagrams](./Marble Diagrams.ipynb) feature of RxPy.\n", "\n", "

    Table of Contents

    \n", "
    \n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# I want to reemit only certain items from an Observable" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## ... by filtering out those that do not match some predicate **[filter/where](http://reactivex.io/documentation/operators/filter.html) **" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "collapsed": false }, "outputs": [ { "ename": "NameError", "evalue": "name 'reset_start_time' is not defined", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mreset_start_time\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mO\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfilter\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;31m# alias: where\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 2\u001b[0m \u001b[0md\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0msubs\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mO\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrange\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m5\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfilter\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;32mlambda\u001b[0m \u001b[0mx\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mi\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mx\u001b[0m \u001b[0;34m%\u001b[0m \u001b[0;36m2\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0;36m0\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;31mNameError\u001b[0m: name 'reset_start_time' is not defined" ] } ], "source": [ "reset_start_time(O.filter) # alias: where\n", "d = subs(O.range(0, 5).filter(lambda x, i: x % 2 == 0))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## ... by slicing **[slice](http://reactivex.io/documentation/operators/filter.html) **" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== slice_ ==========\n", "\n", "module rx.linq.observable.slice\n", "@extensionmethod(Observable, name=\"slice\")\n", "def slice_(self, start=None, stop=None, step=1):\n", " Slices the given observable. It is basically a wrapper around the\n", " operators skip(), skip_last(), take(), take_last() and filter().\n", "\n", " This marble diagram helps you remember how slices works with streams.\n", " Positive numbers is relative to the start of the events, while negative\n", " numbers are relative to the end (on_completed) of the stream.\n", "\n", " r---e---a---c---t---i---v---e---|\n", " 0 1 2 3 4 5 6 7 8\n", " -8 -7 -6 -5 -4 -3 -2 -1\n", "\n", " Example:\n", " result = source.slice(1, 10)\n", " result = source.slice(1, -2)\n", " result = source.slice(1, -1, 2)\n", "\n", " Keyword arguments:\n", " :param Observable self: Observable to slice\n", " :param int start: Number of elements to skip of take last\n", " :param int stop: Last element to take of skip last\n", " :param int step: Takes every step element. Must be larger than zero\n", "\n", " :returns: Returns a sliced observable sequence.\n", " :rtype: Observable\n", "--------------------------------------------------------------------------------\n", "\n", " 3.2 M New subscription on stream 275034569\n", " 571.3 T13 [next] 568.0: i\n", " 681.0 T15 [next] 677.6: v\n", " 788.2 T17 [next] 784.8: e\n", " 898.9 T19 [cmpl] 895.6: fin\n", "\n", "1009.3 M New subscription on stream 276459417\n", "1245.7 T25 [next] 236.2: e\n", "1465.8 T30 [next] 456.3: c\n", "1686.8 T33 [next] 677.3: i\n", "1910.5 T37 [cmpl] 901.0: fin\n" ] } ], "source": [ "reset_start_time(O.slice)\n", "s = marble_stream('r-e-a-c-t-i-v-e-|')\n", "d = subs(s.slice(5, 10))\n", "sleep(1)\n", "# start stop step:\n", "d = subs(s.slice(1, -1, 2))\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## ... that is, only the first item **[first](http://reactivex.io/documentation/operators/first.html) **" ] }, { "cell_type": "code", "execution_count": 27, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== first ==========\n", "\n", "module rx.linq.observable.first\n", "@extensionmethod(ObservableBase)\n", "def first(self, predicate=None):\n", " Returns the first element of an observable sequence that satisfies\n", " the condition in the predicate if present else the first item in the\n", " sequence.\n", "\n", " Example:\n", " res = res = source.first()\n", " res = res = source.first(lambda x: x > 3)\n", "\n", " Keyword arguments:\n", " predicate -- {Function} [Optional] A predicate function to evaluate for\n", " elements in the source sequence.\n", "\n", " Returns {Observable} Sequence containing the first element in the\n", " observable sequence that satisfies the condition in the predicate if\n", " provided, else the first item in the sequence.\n", "--------------------------------------------------------------------------------\n", "\n", " 2.2 M New subscription on stream 276538769\n", " 2.9 M [next] 0.6: 2\n", " 3.0 M [cmpl] 0.7: fin\n" ] } ], "source": [ "rst(O.first)\n", "# match on index:\n", "d = subs(O.from_((1, 2 ,3)).first(lambda x, i: i==1))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## ...that is, only the first item*s* **[take, take_with_time](http://reactivex.io/documentation/operators/take.html) **" ] }, { "cell_type": "code", "execution_count": 28, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== take ==========\n", "\n", "module rx.linq.observable.take\n", "@extensionmethod(ObservableBase)\n", "def take(self, count, scheduler=None):\n", " Returns a specified number of contiguous elements from the start of\n", " an observable sequence, using the specified scheduler for the edge case\n", " of take(0).\n", "\n", " 1 - source.take(5)\n", " 2 - source.take(0, rx.Scheduler.timeout)\n", "\n", " Keyword arguments:\n", " count -- The number of elements to return.\n", " scheduler -- [Optional] Scheduler used to produce an OnCompleted\n", " message in case count is set to 0.\n", "\n", " Returns an observable sequence that contains the specified number of\n", " elements from the start of the input sequence.\n", "--------------------------------------------------------------------------------\n", "\n", " 3.0 M New subscription on stream 276541045\n", " 3.6 M [next] 0.5: 1\n", " 3.8 M [next] 0.7: 2\n", " 4.0 M [cmpl] 0.9: fin\n", "\n", "\n", "========== take_with_time ==========\n", "\n", "module rx.linq.observable.takewithtime\n", "@extensionmethod(ObservableBase)\n", "def take_with_time(self, duration, scheduler=None):\n", " Takes elements for the specified duration from the start of the\n", " observable source sequence, using the specified scheduler to run timers.\n", "\n", " Example:\n", " res = source.take_with_time(5000, [optional scheduler])\n", "\n", " Description:\n", " This operator accumulates a queue with a length enough to store elements\n", " received during the initial duration window. As more elements are\n", " received, elements older than the specified duration are taken from the\n", " queue and produced on the result sequence. This causes elements to be\n", " delayed with duration.\n", "\n", " Keyword arguments:\n", " duration -- {Number} Duration for taking elements from the start of the\n", " sequence.\n", " scheduler -- {Scheduler} Scheduler to run the timer on. If not\n", " specified, defaults to rx.Scheduler.timeout.\n", "\n", " Returns {Observable} An observable sequence with the elements taken\n", " during the specified duration from the start of the source sequence.\n", "--------------------------------------------------------------------------------\n", "\n", " 1.4 M New subscription on stream 276540693\n", " 13.6 T150 [next] 12.1: 1\n", " 123.5 T151 [next] 122.0: 2\n", " 204.9 T149 [cmpl] 203.4: fin\n" ] } ], "source": [ "rst(O.take)\n", "d = subs(O.from_((1, 2, 3, 4)).take(2))\n", "rst(O.take_with_time)\n", "d = subs(marble_stream('1-2-3-4|').take_with_time(200))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## ... that is, only the last item **[last, last_or_default, take_last](http://reactivex.io/documentation/operators/last.html) **" ] }, { "cell_type": "code", "execution_count": 29, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== last ==========\n", "\n", "module rx.linq.observable.last\n", "@extensionmethod(ObservableBase)\n", "def last(self, predicate=None):\n", " Returns the last element of an observable sequence that satisfies the\n", " condition in the predicate if specified, else the last element.\n", "\n", " Example:\n", " res = source.last()\n", " res = source.last(lambda x: x > 3)\n", "\n", " Keyword arguments:\n", " predicate -- {Function} [Optional] A predicate function to evaluate for\n", " elements in the source sequence.\n", "\n", " Returns {Observable} Sequence containing the last element in the\n", " observable sequence that satisfies the condition in the predicate.\n", "--------------------------------------------------------------------------------\n", "\n", " 2.5 M New subscription on stream 276538821\n", " 3.2 M [next] 0.6: 2\n", " 3.4 M [cmpl] 0.8: fin\n", "\n", "\n", "========== last_or_default ==========\n", "\n", "module rx.linq.observable.lastordefault\n", "@extensionmethod(ObservableBase)\n", "def last_or_default(self, predicate=None, default_value=None):\n", " Return last or default element.\n", "\n", " Returns the last element of an observable sequence that satisfies\n", " the condition in the predicate, or a default value if no such\n", " element exists.\n", "\n", " Examples:\n", " res = source.last_or_default()\n", " res = source.last_or_default(lambda x: x > 3)\n", " res = source.last_or_default(lambda x: x > 3, 0)\n", " res = source.last_or_default(None, 0)\n", "\n", " predicate -- {Function} [Optional] A predicate function to evaluate\n", " for elements in the source sequence.\n", " default_value -- [Optional] The default value if no such element\n", " exists. If not specified, defaults to None.\n", "\n", " Returns {Observable} Sequence containing the last element in the\n", " observable sequence that satisfies the condition in the predicate,\n", " or a default value if no such element exists.\n", "--------------------------------------------------------------------------------\n", "\n", " 1.4 M New subscription on stream 276540749\n", " 2.0 M [next] 0.5: None\n", " 2.1 M [cmpl] 0.6: fin\n", "\n", " 2.7 M New subscription on stream 276540757\n", " 3.3 M [next] 0.5: 42\n", " 3.4 M [cmpl] 0.6: fin\n", "\n", "\n", "========== take_last ==========\n", "\n", "module rx.linq.observable.takelast\n", "@extensionmethod(ObservableBase)\n", "def take_last(self, count):\n", " Returns a specified number of contiguous elements from the end of an\n", " observable sequence.\n", "\n", " Example:\n", " res = source.take_last(5)\n", "\n", " Description:\n", " This operator accumulates a buffer with a length enough to store\n", " elements count elements. Upon completion of the source sequence, this\n", " buffer is drained on the result sequence. This causes the elements to be\n", " delayed.\n", "\n", " Keyword arguments:\n", " :param int count: Number of elements to take from the end of the source\n", " sequence.\n", "\n", " :returns: An observable sequence containing the specified number of elements \n", " from the end of the source sequence.\n", " :rtype: Observable\n", "--------------------------------------------------------------------------------\n", "\n", " 1.7 M New subscription on stream 276541765\n", " 2.4 M [next] 0.6: 3\n", " 2.5 M [next] 0.7: 4\n", " 2.6 M [cmpl] 0.8: fin\n" ] } ], "source": [ "rst(O.last, title=True)\n", "d = subs(O.from_((1, 2, 3)).last(lambda x: x < 3))\n", "rst(O.last_or_default, title=True)\n", "d = subs(O.from_((1, 2, 3)).last_or_default(lambda x: x > 3))\n", "d = subs(O.from_((1, 2, 3)).last_or_default(lambda x: x > 3, '42'))\n", "\n", "rst(O.take_last, title=True)\n", "d = subs(O.from_((1, 2, 3, 4)).take_last(2))\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## ... that is, only item n **[element_at, element_at_or_default](http://reactivex.io/documentation/operators/elementat.html) **" ] }, { "cell_type": "code", "execution_count": 30, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== element_at ==========\n", "\n", "module rx.linq.observable.elementat\n", "@extensionmethod(ObservableBase)\n", "def element_at(self, index):\n", " Returns the element at a specified index in a sequence.\n", "\n", " Example:\n", " res = source.element_at(5)\n", "\n", " Keyword arguments:\n", " :param int index: The zero-based index of the element to retrieve.\n", "\n", " :returns: An observable sequence that produces the element at the\n", " specified position in the source sequence.\n", " :rtype: Observable\n", "--------------------------------------------------------------------------------\n", "\n", " 2.2 M New subscription on stream 278634469\n", " 2.8 M [next] 0.5: 3\n", " 2.9 M [cmpl] 0.6: fin\n", "\n", "\n", "========== element_at_or_default ==========\n", "\n", "module rx.linq.observable.elementatordefault\n", "@extensionmethod(ObservableBase)\n", "def element_at_or_default(self, index, default_value=None):\n", " Returns the element at a specified index in a sequence or a default\n", " value if the index is out of range.\n", "\n", " Example:\n", " res = source.element_at_or_default(5)\n", " res = source.element_at_or_default(5, 0)\n", "\n", " Keyword arguments:\n", " index -- {Number} The zero-based index of the element to retrieve.\n", " default_value -- [Optional] The default value if the index is outside\n", " the bounds of the source sequence.\n", "\n", " Returns an observable {Observable} sequence that produces the element at\n", " the specified position in the source sequence, or a default value if\n", " the index is outside the bounds of the source sequence.\n", "--------------------------------------------------------------------------------\n", "\n", " 1.3 M New subscription on stream 276533629\n", " 2.1 M [next] 0.6: 42\n", " 2.2 M [cmpl] 0.7: fin\n" ] } ], "source": [ "rst(O.element_at)\n", "d = subs(O.from_((1, 2, 3, 4)).element_at(2))\n", "rst(O.element_at_or_default)\n", "d = subs(O.from_((1, 2, 3, 4)).element_at_or_default(6, '42'))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## ... that is, only those items after the first items" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### ... that is, after the first n items **[skip, skip_with_time](http://reactivex.io/documentation/operators/skip.html) **" ] }, { "cell_type": "code", "execution_count": 31, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== skip ==========\n", "\n", "module rx.linq.observable.skip\n", "@extensionmethod(ObservableBase)\n", "def skip(self, count):\n", " Bypasses a specified number of elements in an observable sequence\n", " and then returns the remaining elements.\n", "\n", " Keyword arguments:\n", " count -- The number of elements to skip before returning the remaining\n", " elements.\n", "\n", " Returns an observable sequence that contains the elements that occur\n", " after the specified index in the input sequence.\n", "--------------------------------------------------------------------------------\n", "\n", " 2.5 M New subscription on stream 276082845\n", " 3.1 M [next] 0.5: 2\n", " 3.3 M [next] 0.7: 3\n", " 3.7 M [next] 1.1: 4\n", " 3.9 M [cmpl] 1.3: fin\n", "\n", "\n", "========== skip_with_time ==========\n", "\n", "module rx.linq.observable.skipwithtime\n", "@extensionmethod(ObservableBase)\n", "def skip_with_time(self, duration, scheduler=None):\n", " Skips elements for the specified duration from the start of the\n", " observable source sequence, using the specified scheduler to run timers.\n", "\n", " Example:\n", " 1 - res = source.skip_with_time(5000, [optional scheduler])\n", "\n", " Description:\n", " Specifying a zero value for duration doesn't guarantee no elements will\n", " be dropped from the start of the source sequence. This is a side-effect\n", " of the asynchrony introduced by the scheduler, where the action that\n", " causes callbacks from the source sequence to be forwarded may not\n", " execute immediately, despite the zero due time.\n", "\n", " Errors produced by the source sequence are always forwarded to the\n", " result sequence, even if the error occurs before the duration.\n", "\n", " Keyword arguments:\n", " duration -- {Number} Duration for skipping elements from the start of\n", " the sequence.\n", " scheduler -- {Scheduler} Scheduler to run the timer on. If not\n", " specified, defaults to Rx.Scheduler.timeout.\n", "\n", " Returns n observable {Observable} sequence with the elements skipped\n", " during the specified duration from the start of the source sequence.\n", "--------------------------------------------------------------------------------\n", "\n", " 1.7 M New subscription on stream 276541889\n", " 236.1 T165 [next] 234.4: 3\n", " 347.9 T167 [next] 346.1: 4\n", " 455.7 T168 [next] 453.9: 5\n", " 565.7 T170 [next] 563.9: 6\n", " 566.5 T172 [cmpl] 564.7: fin\n" ] } ], "source": [ "rst(O.skip, title=True)\n", "d = subs(O.range(0, 5).skip(2))\n", "rst(O.skip_with_time, title=True)\n", "d = subs(marble_stream('1-2-3-4-5-6').skip_with_time(200))\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### ... that is, until one of those items matches a predicate **[skip_while](http://reactivex.io/documentation/operators/skipwhile.html) **" ] }, { "cell_type": "code", "execution_count": 16, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== skip_while ==========\n", "\n", "module rx.linq.observable.skipwhile\n", "@extensionmethod(ObservableBase)\n", "def skip_while(self, predicate):\n", " Bypasses elements in an observable sequence as long as a specified\n", " condition is true and then returns the remaining elements. The\n", " element's index is used in the logic of the predicate function.\n", "\n", " 1 - source.skip_while(lambda value: value < 10)\n", " 2 - source.skip_while(lambda value, index: value < 10 or index < 10)\n", "\n", " predicate -- A function to test each element for a condition; the\n", " second parameter of the function represents the index of the\n", " source element.\n", "\n", " Returns an observable sequence that contains the elements from the\n", " input sequence starting at the first element in the linear series that\n", " does not pass the test specified by predicate.\n", "--------------------------------------------------------------------------------\n", "\n", " 1.7 M New subscription on stream 275707413\n", " 2.2 M [next] 0.5: 3\n", " 2.5 M [next] 0.8: 4\n", " 2.8 M [next] 1.1: 5\n", " 3.1 M [next] 1.4: 6\n", " 3.4 M [cmpl] 1.6: fin\n" ] } ], "source": [ "rst(O.skip_while)\n", "# skipping only AS LONG AS the function is true. If already false at the beginning -> all flushed:\n", "d = subs(O.from_((1, 2, 3, 4, 5, 6)).skip_while(lambda x: x in (1, 2)))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### ... that is, after a second Observable emits an item **[skip_until, skip_until_with_time](http://reactivex.io/documentation/operators/skipuntil.html) **" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== skip_until ==========\n", "\n", "module rx.linq.observable.skipuntil\n", "@extensionmethod(ObservableBase)\n", "def skip_until(self, other):\n", " Returns the values from the source observable sequence only after\n", " the other observable sequence produces a value.\n", "\n", " other -- The observable sequence that triggers propagation of elements\n", " of the source sequence.\n", "\n", " Returns an observable sequence containing the elements of the source\n", " sequence starting from the point the other sequence triggered\n", " propagation.\n", "--------------------------------------------------------------------------------\n", "\n", " 2.8 M New subscription on stream 276474365\n", " 235.7 T8 [next] 232.9: 3\n", " 346.9 T10 [next] 344.1: 4\n", " 456.9 T12 [next] 454.1: 5\n", " 466.9 T13 [cmpl] 464.1: fin\n", "\n", "\n", "========== skip_until_with_time ==========\n", "\n", "module rx.linq.observable.skipuntilwithtime\n", "@extensionmethod(ObservableBase)\n", "def skip_until_with_time(self, start_time, scheduler=None):\n", " Skips elements from the observable source sequence until the\n", " specified start time, using the specified scheduler to run timers.\n", " Errors produced by the source sequence are always forwarded to the\n", " result sequence, even if the error occurs before the start time.\n", "\n", " Examples:\n", " res = source.skip_until_with_time(new Date(), [optional scheduler]);\n", " res = source.skip_until_with_time(5000, [optional scheduler]);\n", "\n", " Keyword arguments:\n", " start_time -- Time to start taking elements from the source sequence. If\n", " this value is less than or equal to Date(), no elements will be\n", " skipped.\n", " scheduler -- Scheduler to run the timer on. If not specified, defaults\n", " to rx.Scheduler.timeout.\n", "\n", " Returns {Observable} An observable sequence with the elements skipped\n", " until the specified start time.\n", "--------------------------------------------------------------------------------\n", "\n", " 3.2 M New subscription on stream 276064481\n", " 348.3 T26 [next] 344.9: 4\n", " 461.7 T28 [next] 458.4: 5\n", " 468.8 T29 [cmpl] 465.5: fin\n" ] } ], "source": [ "rst(O.skip_until)\n", "s1 = marble_stream('1-2-3-4-5|')\n", "s2 = marble_stream('--2------|')\n", "d = subs(s1.skip_until(s2))\n", "sleep(0.5)\n", "rst(O.skip_until_with_time)\n", "d = subs(s1.skip_until_with_time(300))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## ... that is, those items except the last items" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### ... that is, except the last n items **[skip_last, skip_last_with_time](http://reactivex.io/documentation/operators/skiplast.html) **" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== skip_last ==========\n", "\n", "module rx.linq.observable.skiplast\n", "@extensionmethod(ObservableBase)\n", "def skip_last(self, count):\n", " Bypasses a specified number of elements at the end of an observable\n", " sequence.\n", "\n", " Description:\n", " This operator accumulates a queue with a length enough to store the\n", " first `count` elements. As more elements are received, elements are\n", " taken from the front of the queue and produced on the result sequence.\n", " This causes elements to be delayed.\n", "\n", " Keyword arguments\n", " count -- Number of elements to bypass at the end of the source sequence.\n", "\n", " Returns an observable {Observable} sequence containing the source\n", " sequence elements except for the bypassed ones at the end.\n", "--------------------------------------------------------------------------------\n", "\n", " 2.6 M New subscription on stream 276525625\n", " 234.8 T31 [next] 232.2: 1\n", " 345.4 T33 [next] 342.8: 2\n", " 455.7 T36 [next] 453.0: 3\n", " 469.9 T37 [cmpl] 467.3: fin\n", "\n", "\n", "========== skip_last_with_time ==========\n", "\n", "module rx.linq.observable.skiplastwithtime\n", "@extensionmethod(ObservableBase)\n", "def skip_last_with_time(self, duration, scheduler=None):\n", " Skips elements for the specified duration from the end of the\n", " observable source sequence, using the specified scheduler to run timers.\n", "\n", " 1 - res = source.skip_last_with_time(5000)\n", " 2 - res = source.skip_last_with_time(5000, scheduler)\n", "\n", " Description:\n", " This operator accumulates a queue with a length enough to store elements\n", " received during the initial duration window. As more elements are\n", " received, elements older than the specified duration are taken from the\n", " queue and produced on the result sequence. This causes elements to be\n", " delayed with duration.\n", "\n", " Keyword arguments:\n", " duration -- {Number} Duration for skipping elements from the end of the\n", " sequence.\n", " scheduler -- {Scheduler} [Optional] Scheduler to run the timer on. If\n", " not specified, defaults to Rx.Scheduler.timeout\n", " Returns an observable {Observable} sequence with the elements skipped\n", " during the specified duration from the end of the source sequence.\n", "--------------------------------------------------------------------------------\n", "\n", " 3.9 M New subscription on stream 276128193\n", " 348.3 T46 [next] 344.2: 1\n", " 459.2 T47 [next] 455.2: 2\n", " 471.7 T50 [cmpl] 467.7: fin\n" ] } ], "source": [ "rst(O.skip_last)\n", "s1 = marble_stream('1-2-3-4-5|')\n", "s2 = marble_stream('--2------|')\n", "d = subs(s1.skip_last(2))\n", "sleep(0.5)\n", "rst(O.skip_last_with_time)\n", "d = subs(s1.skip_last_with_time(300))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### ... that is, until one of those items matches a predicate **[take_while](http://reactivex.io/documentation/operators/takewhile.html) **" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== take_while ==========\n", "\n", "module rx.linq.observable.takewhile\n", "@extensionmethod(ObservableBase)\n", "def take_while(self, predicate):\n", " Returns elements from an observable sequence as long as a specified\n", " condition is true. The element's index is used in the logic of the\n", " predicate function.\n", "\n", " 1 - source.take_while(lambda value: value < 10)\n", " 2 - source.take_while(lambda value, index: value < 10 or index < 10)\n", "\n", " Keyword arguments:\n", " predicate -- A function to test each element for a condition; the\n", " second parameter of the function represents the index of the source\n", " element.\n", "\n", " Returns an observable sequence that contains the elements from the\n", " input sequence that occur before the element at which the test no\n", " longer passes.\n", "--------------------------------------------------------------------------------\n", "\n", " 2.6 M New subscription on stream 278900289\n", " 2.9 M [next] 0.3: 1\n", " 3.5 M [next] 0.8: 2\n", " 4.0 M [cmpl] 1.4: fin\n" ] } ], "source": [ "rst(O.take_while)\n", "d = subs(O.from_((1, 2, 3)).take_while(lambda x: x<3))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### ...that is, except items emitted during a period of time before the source completes **[skip_last, skip_last_with_time](http://reactivex.io/documentation/operators/skiplast.html) **" ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# (see above)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### ...that is, except items emitted after a second Observable emits an item **[take_until, take_until_with_time](http://reactivex.io/documentation/operators/takeuntil.html) **" ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== take_until ==========\n", "\n", "module rx.linq.observable.takeuntil\n", "@extensionmethod(ObservableBase)\n", "def take_until(self, other):\n", " Returns the values from the source observable sequence until the\n", " other observable sequence produces a value.\n", "\n", " Keyword arguments:\n", " other -- Observable sequence that terminates propagation of elements of\n", " the source sequence.\n", "\n", " Returns an observable sequence containing the elements of the source\n", " sequence up to the point the other sequence interrupted further\n", " propagation.\n", "--------------------------------------------------------------------------------\n", "\n", " 2.1 M New subscription on stream 278898461\n", " 13.8 T131 [next] 11.6: 1\n", " 123.9 T133 [next] 121.7: 2\n", " 216.0 T143 [cmpl] 213.8: fin\n", "\n", "\n", "========== take_until_with_time ==========\n", "\n", "module rx.linq.observable.takeuntilwithtime\n", "@extensionmethod(ObservableBase)\n", "def take_until_with_time(self, end_time, scheduler=None):\n", " Takes elements for the specified duration until the specified end\n", " time, using the specified scheduler to run timers.\n", "\n", " Examples:\n", " 1 - res = source.take_until_with_time(dt, [optional scheduler])\n", " 2 - res = source.take_until_with_time(5000, [optional scheduler])\n", "\n", " Keyword Arguments:\n", " end_time -- {Number | Date} Time to stop taking elements from the source\n", " sequence. If this value is less than or equal to Date(), the\n", " result stream will complete immediately.\n", " scheduler -- {Scheduler} Scheduler to run the timer on.\n", "\n", " Returns an observable {Observable} sequence with the elements taken\n", " until the specified end time.\n", "--------------------------------------------------------------------------------\n", "\n", " 2.1 M New subscription on stream 278906801\n", " 14.7 T148 [next] 12.5: 1\n", " 128.0 T149 [next] 125.8: 2\n", " 235.5 T152 [next] 233.4: 3\n", " 307.8 T147 [cmpl] 305.7: fin\n" ] } ], "source": [ "rst(O.take_until)\n", "s1 = marble_stream('1-2-3-4-5|')\n", "s2 = marble_stream('--2------|')\n", "d = subs(s1.take_until(s2))\n", "sleep(0.5)\n", "rst(O.take_until_with_time)\n", "d = subs(s1.take_until_with_time(300))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## ... by sampling the Observable periodically **[sample](http://reactivex.io/documentation/operators/sample.html)**" ] }, { "cell_type": "code", "execution_count": 26, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== sample ==========\n", "\n", "module rx.linq.observable.sample\n", "@extensionmethod(Observable, alias=\"throttle_last\")\n", "def sample(self, interval=None, sampler=None, scheduler=None):\n", " Samples the observable sequence at each interval.\n", "\n", " 1 - res = source.sample(sample_observable) # Sampler tick sequence\n", " 2 - res = source.sample(5000) # 5 seconds\n", " 2 - res = source.sample(5000, rx.scheduler.timeout) # 5 seconds\n", "\n", " Keyword arguments:\n", " source -- Source sequence to sample.\n", " interval -- Interval at which to sample (specified as an integer\n", " denoting milliseconds).\n", " scheduler -- [Optional] Scheduler to run the sampling timer on. If not\n", " specified, the timeout scheduler is used.\n", "\n", " Returns sampled observable sequence.\n", "--------------------------------------------------------------------------------\n", "\n", " 1.9 M New subscription on stream 288677153\n", " 314.2 T847 [next] 312.2: 3\n", " 619.4 T849 [next] 617.4: 6\n", " 921.6 T850 [next] 919.6: 9\n", "1226.2 T851 [next] 1224.1: 2\n", "1530.3 T852 [next] 1528.2: 5\n", "1836.2 T853 [next] 1834.1: E\n", "1836.9 T853 [cmpl] 1834.9: fin\n", "\n", "2018.3 M New subscription on stream 279189229\n", "2338.6 T889 [next] 320.3: 3\n", "2651.7 T890 [next] 633.4: 6\n", "3661.6 T893 [next] 1643.3: 6\n", "4872.2 T894 [next] 2853.8: E\n", "4872.5 T894 [cmpl] 2854.2: fin\n" ] } ], "source": [ "rst(O.sample)\n", "xs = marble_stream('1-2-3-4-5-6-7-8-9-1-2-3-4-5-6-E|')\n", "sampler =marble_stream('---1---1----------1------------|')\n", "d = subs(xs.sample(300))\n", "sleep(2)\n", "d = subs(xs.sample(sampler=sampler))\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## ... by only emitting items that are not followed by other items within some duration **[debounce](http://reactivex.io/documentation/operators/debounce.html)**" ] }, { "cell_type": "code", "execution_count": 30, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== debounce ==========\n", "\n", "module rx.linq.observable.debounce\n", "@extensionmethod(Observable, alias=\"throttle_with_timeout\")\n", "def debounce(self, duetime, scheduler=None):\n", " Ignores values from an observable sequence which are followed by\n", " another value before duetime.\n", "\n", " Example:\n", " 1 - res = source.debounce(5000) # 5 seconds\n", " 2 - res = source.debounce(5000, scheduler)\n", "\n", " Keyword arguments:\n", " duetime -- {Number} Duration of the throttle period for each value\n", " (specified as an integer denoting milliseconds).\n", " scheduler -- {Scheduler} [Optional] Scheduler to run the throttle\n", " timers on. If not specified, the timeout scheduler is used.\n", "\n", " Returns {Observable} The debounced sequence.\n", "--------------------------------------------------------------------------------\n", "flushing a value every >= 300ms\n", "\n", " 3.4 M New subscription on stream 281048569\n", "1062.2 T977 [next] 1058.8: 6\n", "1368.6 T978 [next] 1365.2: 7\n", "1685.5 T979 [next] 1682.1: 8\n", "2092.7 T980 [next] 2089.4: 9\n", "2203.3 T970 [cmpl] 2199.9: fin\n" ] } ], "source": [ "rst(O.debounce)\n", "s = marble_stream('-12-3-4--5--6---7---8----9----a')\n", "print('flushing a value every >= 300ms')\n", "d = subs(s.debounce(300))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## ... by suppressing items that are duplicates of already-emitted items **[distinct](http://reactivex.io/documentation/operators/distinct.html)**" ] }, { "cell_type": "code", "execution_count": 43, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== distinct ==========\n", "\n", "module rx.linq.observable.distinct\n", "@extensionmethod(ObservableBase)\n", "def distinct(self, key_mapper=None, comparer=None):\n", " Returns an observable sequence that contains only distinct elements\n", " according to the key_mapper and the comparer. Usage of this operator\n", " should be considered carefully due to the maintenance of an internal\n", " lookup structure which can grow large.\n", "\n", " Example:\n", " res = obs = xs.distinct()\n", " obs = xs.distinct(lambda x: x.id)\n", " obs = xs.distinct(lambda x: x.id, lambda a,b: a == b)\n", "\n", " Keyword arguments:\n", " key_mapper -- {Function} [Optional] A function to compute the\n", " comparison key for each element.\n", " comparer -- {Function} [Optional] Used to compare items in the\n", " collection.\n", "\n", " Returns an observable {Observable} sequence only containing the distinct\n", " elements, based on a computed key value, from the source sequence.\n", "--------------------------------------------------------------------------------\n", "\n", " 2.6 M New subscription on stream 286822853\n", " 3.0 M [next] 0.3: 1\n", " 3.2 M [next] 0.5: 2\n", " 3.5 M [next] 0.9: 3\n", " 3.8 M [cmpl] 1.1: fin\n", "\n", " 4.3 M New subscription on stream 286822721\n", " 4.6 M [next] 0.2: 1\n", " 4.9 M [next] 0.5: 2\n", " 5.4 M [cmpl] 1.0: fin\n" ] } ], "source": [ "rst(O.distinct)\n", "s = O.from_((1, 2, 1, 1, 3))\n", "d = subs(s.distinct(lambda x: x*2))\n", "d = subs(s.distinct(lambda x: x, lambda a, b: a==2))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### ... if they immediately follow the item they are duplicates of **[distinct_until_changed](http://reactivex.io/documentation/operators/distinct.html)**" ] }, { "cell_type": "code", "execution_count": 45, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== distinct_until_changed ==========\n", "\n", "module rx.linq.observable.distinctuntilchanged\n", "@extensionmethod(ObservableBase)\n", "def distinct_until_changed(self, key_mapper=None, comparer=None):\n", " Returns an observable sequence that contains only distinct\n", " contiguous elements according to the key_mapper and the comparer.\n", "\n", " 1 - obs = observable.distinct_until_changed();\n", " 2 - obs = observable.distinct_until_changed(lambda x: x.id)\n", " 3 - obs = observable.distinct_until_changed(lambda x: x.id,\n", " lambda x, y: x == y)\n", "\n", " key_mapper -- [Optional] A function to compute the comparison key for\n", " each element. If not provided, it projects the value.\n", " comparer -- [Optional] Equality comparer for computed key values. If\n", " not provided, defaults to an equality comparer function.\n", "\n", " Return An observable sequence only containing the distinct contiguous\n", " elements, based on a computed key value, from the source sequence.\n", "--------------------------------------------------------------------------------\n", "\n", " 3.2 M New subscription on stream 281042609\n", " 4.1 M [next] 0.8: 1\n", " 5.0 M [next] 1.7: 2\n", " 5.4 M [next] 2.1: 1\n", " 5.9 M [next] 2.6: 3\n", " 6.2 M [cmpl] 2.9: fin\n", "\n", " 7.0 M New subscription on stream 286831301\n", " 7.5 M [next] 0.3: 1\n", " 7.8 M [next] 0.7: 2\n", " 8.2 M [cmpl] 1.0: fin\n" ] } ], "source": [ "rst(O.distinct_until_changed)\n", "s = O.from_((1, 2, 1, 1, 3))\n", "d = subs(s.distinct_until_changed(lambda x: x*2))\n", "d = subs(s.distinct_until_changed(lambda x: x, lambda a, b: a==2))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## ... by delaying my subscription to it for some time after it begins emitting items **[delay_subscription](http://reactivex.io/documentation/operators/delay.html)**" ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== delay ==========\n", "\n", "module rx.linq.observable.delay\n", "@extensionmethod(ObservableBase)\n", "def delay(self, duetime, scheduler=None):\n", " Time shifts the observable sequence by duetime. The relative time\n", " intervals between the values are preserved.\n", "\n", " 1 - res = rx.Observable.delay(datetime())\n", " 2 - res = rx.Observable.delay(datetime(), Scheduler.timeout)\n", "\n", " 3 - res = rx.Observable.delay(5000)\n", " 4 - res = rx.Observable.delay(5000, Scheduler.timeout)\n", "\n", " Keyword arguments:\n", " :param datetime|int duetime: Absolute (specified as a datetime object) or\n", " relative time (specified as an integer denoting milliseconds) by which\n", " to shift the observable sequence.\n", " :param Scheduler scheduler: [Optional] Scheduler to run the delay timers on.\n", " If not specified, the timeout scheduler is used.\n", "\n", " :returns: Time-shifted sequence.\n", " :rtype: Observable\n", "--------------------------------------------------------------------------------\n", "\n", "\n", "========== note the absolute time of emissions: ==========\n", "\n", "\n", " 2.1 M New subscription on stream 276542741\n", "1005.6 T11 [next] 1003.4: 0\n", "1005.9 T11 [next] 1003.7: 1\n", "1006.1 T11 [next] 1003.9: 2\n", "1006.2 T11 [next] 1003.9: 3\n", "1006.2 T11 [next] 1004.0: 4\n", "1006.3 T11 [next] 1004.0: 5\n", "1006.3 T11 [next] 1004.1: 6\n", "1006.4 T11 [next] 1004.1: 7\n", "1006.4 T11 [next] 1004.1: 8\n", "1006.4 T11 [next] 1004.2: 9\n", "1006.5 T11 [cmpl] 1004.2: fin\n" ] } ], "source": [ "rst(O.delay)\n", "header(\"note the absolute time of emissions:\")\n", "d = subs(O.range(0, 10).delay(1000))" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "# I want to reemit items from an Observable only on condition that it was the first of a collection of Observables to emit an item **[amb](http://reactivex.io/documentation/operators/amb.html)**" ] }, { "cell_type": "code", "execution_count": 18, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== amb ==========\n", "\n", "module rx.linq.observable.amb\n", "@extensionclassmethod(Observable)\n", "def amb(cls, *args):\n", " Propagates the observable sequence that reacts first.\n", "\n", " E.g. winner = rx.Observable.amb(xs, ys, zs)\n", "\n", " Returns an observable sequence that surfaces any of the given sequences,\n", " whichever reacted first.\n", "--------------------------------------------------------------------------------\n", "\n", " 3.4 M New subscription on stream 276511041\n", " 6.4 M [next] 2.8: 10\n", " 6.7 M [next] 3.0: 11\n", " 7.0 M [next] 3.4: 12\n", " 7.5 M [next] 3.9: 13\n", " 7.9 M [next] 4.2: 14\n", " 8.2 M [cmpl] 4.6: fin\n" ] } ], "source": [ "rst(O.amb)\n", "s1 = O.range(0, 5).delay(100)\n", "s2 = O.range(10, 5)\n", "\n", "d = subs(O.amb(s1, s2))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 2", "language": "python", "name": "python2" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", "version": "2.7.12" } }, "nbformat": 4, "nbformat_minor": 1 } RxPY-3.2.0/notebooks/reactivex.io/Part VI - Entirety Operations.ipynb000066400000000000000000000707621404130727200252520ustar00rootroot00000000000000{ "cells": [ { "cell_type": "code", "execution_count": 2, "metadata": { "collapsed": false }, "outputs": [], "source": [ "%run startup.py" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "collapsed": false }, "outputs": [ { "data": { "application/javascript": [ "$.getScript('./assets/js/ipython_notebook_toc.js')" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "%%javascript\n", "$.getScript('./assets/js/ipython_notebook_toc.js')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# A Decision Tree of Observable Operators\n", "\n", "## Part 6: Looking at Entire Streams\n", "\n", "> source: http://reactivex.io/documentation/operators.html#tree. \n", "> (transcribed to RxPY 1.5.7, Py2.7 / 2016-12, Gunther Klessinger, [axiros](http://www.axiros.com)) \n", "\n", "**This tree can help you find the ReactiveX Observable operator you’re looking for.** \n", "See [Part 1](./A Decision Tree of Observable Operators. Part I - Creation.ipynb) for Usage and Output Instructions. \n", "\n", "We also require acquaintance with the [marble diagrams](./Marble Diagrams.ipynb) feature of RxPy.\n", "\n", "

    Table of Contents

    \n", "
    \n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# I want to evaluate the entire sequence of items emitted by an Observable" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## ... and emit a single boolean indicating if all of the items pass some test **[all](http://reactivex.io/documentation/operators/all.html)**" ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== all ==========\n", "\n", "module rx.linq.observable.all\n", "@extensionmethod(Observable, alias=\"every\")\n", "def all(self, predicate):\n", " Determines whether all elements of an observable sequence satisfy a\n", " condition.\n", "\n", " 1 - res = source.all(lambda value: value.length > 3)\n", "\n", " Keyword arguments:\n", " :param bool predicate: A function to test each element for a condition.\n", "\n", " :returns: An observable sequence containing a single element determining\n", " whether all elements in the source sequence pass the test in the\n", " specified predicate.\n", "--------------------------------------------------------------------------------\n", "\n", " 1.5 M New subscription on stream 276540965\n", " 3.9 M [next] 2.4: True\n", " 4.2 M [cmpl] 2.6: fin\n", "\n", " 4.7 M New subscription on stream 276540989\n", " 5.1 M [next] 0.3: False\n", " 5.3 M [cmpl] 0.5: fin\n" ] } ], "source": [ "rst(O.all)\n", "for i in 9, 10:\n", " d = subs(O.range(10, 20).all(lambda v: v > i))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## ... and emit a single boolean indicating if the Observable emitted *any* item (that passes some test) **[contains, find_index, some](http://reactivex.io/documentation/operators/container.html)**" ] }, { "cell_type": "code", "execution_count": 19, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== contains ==========\n", "\n", "module rx.linq.observable.contains\n", "@extensionmethod(ObservableBase)\n", "def contains(self, value, comparer=None):\n", " Determines whether an observable sequence contains a specified\n", " element with an optional equality comparer.\n", "\n", " Example\n", " 1 - res = source.contains(42)\n", " 2 - res = source.contains({ \"value\": 42 }, lambda x, y: x[\"value\"] == y[\"value\")\n", "\n", " Keyword parameters:\n", " value -- The value to locate in the source sequence.\n", " comparer -- {Function} [Optional] An equality comparer to compare elements.\n", "\n", " Returns an observable {Observable} sequence containing a single element\n", " determining whether the source sequence contains an element that has\n", " the specified value.\n", "--------------------------------------------------------------------------------\n", "\n", " 1.2 M New subscription on stream 276590957\n", " 2.0 M [next] 0.6: True\n", " 2.2 M [cmpl] 0.7: fin\n", "\n", "\n", "========== equality operation ==========\n", "\n", "\n", " 3.4 M New subscription on stream 276590973\n", " 3.8 M [next] 0.3: True\n", " 3.9 M [cmpl] 0.4: fin\n" ] } ], "source": [ "rst(O.contains)\n", "d = subs(O.range(10, 20).contains(11))\n", "header(\"equality operation\")\n", "d = subs(O.range(10, 20).contains(11, comparer=lambda x, y: y == x + 1))" ] }, { "cell_type": "code", "execution_count": 42, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== find_index ==========\n", "\n", "module rx.linq.observable.findindex\n", "@extensionmethod(ObservableBase)\n", "def find_index(self, predicate):\n", " Searches for an element that matches the conditions defined by the\n", " specified predicate, and returns an Observable sequence with the\n", " zero-based index of the first occurrence within the entire Observable\n", " sequence.\n", "\n", " Keyword Arguments:\n", " predicate -- {Function} The predicate that defines the conditions of the\n", " element to search for.\n", "\n", " Returns an observable {Observable} sequence with the zero-based index of\n", " the first occurrence of an element that matches the conditions defined\n", " by match, if found; otherwise, -1.\n", "--------------------------------------------------------------------------------\n", "\n", " 1.6 M New subscription on stream 276591037\n", " 2.0 M comparer args: 4 [0] \n", " 2.3 M comparer args: 1 [1] \n", " 2.5 M comparer args: 2 [2] \n", " 2.8 M comparer args: 1 [3] \n", " 3.0 M [next] 1.4: 3\n", " 3.1 M [cmpl] 1.5: fin\n", "\n", "\n", "========== some ==========\n", "\n", "module rx.linq.observable.some\n", "@extensionmethod(ObservableBase)\n", "def some(self, predicate=None):\n", " Determines whether some element of an observable sequence satisfies a\n", " condition if present, else if some items are in the sequence.\n", "\n", " Example:\n", " result = source.some()\n", " result = source.some(lambda x: x > 3)\n", "\n", " Keyword arguments:\n", " predicate -- A function to test each element for a condition.\n", "\n", " Returns {Observable} an observable sequence containing a single element\n", " determining whether some elements in the source sequence pass the test\n", " in the specified predicate if given, else if some items are in the\n", " sequence.\n", "--------------------------------------------------------------------------------\n", "\n", " 1.6 M New subscription on stream 276563317\n", " 2.0 M comparer args: 4\n", " 2.3 M comparer args: 1\n", " 2.7 M comparer args: 2\n", " 2.9 M comparer args: 1\n", " 3.0 M [next] 1.3: True\n", " 3.2 M [cmpl] 1.5: fin\n" ] } ], "source": [ "def comparer(*a):\n", " ''' find_index: you get value, index and the observable itself as argument\n", " some: you get only the value\n", " '''\n", " log('comparer args:', *a)\n", " l.append(1)\n", " if len(l) > 3:\n", " # => index will be 0\n", " return True\n", " \n", "stream = O.from_((4, 1, 2, 1, 3))\n", "for name in 'find_index', 'some': \n", " l = []\n", " operator = getattr(stream, name)\n", " rst(operator) # output documentation, reset timer\n", " d = subs(operator(lambda *a: comparer(*a)))\n", "\n", " " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## ... and emit a single boolean indicating if the Observable emitted no items **[is_empty](http://reactivex.io/documentation/operators/contains.html)**" ] }, { "cell_type": "code", "execution_count": 45, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== is_empty ==========\n", "\n", "module rx.linq.observable.isempty\n", "@extensionmethod(ObservableBase)\n", "def is_empty(self):\n", " Determines whether an observable sequence is empty.\n", "\n", " Returns an observable {Observable} sequence containing a single element\n", " determining whether the source sequence is empty.\n", "--------------------------------------------------------------------------------\n", "\n", " 1.9 M New subscription on stream 276596853\n", " 2.4 M [next] 0.4: True\n", " 2.7 M [cmpl] 0.7: fin\n" ] } ], "source": [ "rst(O.is_empty)\n", "d = subs(O.from_([]).is_empty())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## ... and emit a single boolean indicating if the sequence is identical to one emitted by a second Observable **[sequence_equal](http://reactivex.io/documentation/operators/sequenceequal.html)**" ] }, { "cell_type": "code", "execution_count": 75, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== sequence_equal ==========\n", "\n", "module rx.linq.observable.sequenceequal\n", "@extensionmethod(ObservableBase)\n", "def sequence_equal(self, second, comparer=None):\n", " Determines whether two sequences are equal by comparing the\n", " elements pairwise using a specified equality comparer.\n", "\n", " 1 - res = source.sequence_equal([1,2,3])\n", " 2 - res = source.sequence_equal([{ \"value\": 42 }], lambda x, y: x.value == y.value)\n", " 3 - res = source.sequence_equal(rx.return_value(42))\n", " 4 - res = source.sequence_equal(rx.return_value({ \"value\": 42 }), lambda x, y: x.value == y.value)\n", "\n", " second -- Second observable sequence or array to compare.\n", " comparer -- [Optional] Comparer used to compare elements of both sequences.\n", "\n", " Returns an observable sequence that contains a single element which\n", " indicates whether both sequences are of equal length and their\n", " corresponding elements are equal according to the specified equality\n", " comparer.\n", "--------------------------------------------------------------------------------\n", "\n", " 4.8 M New subscription on stream 279265221\n", " 5.3 M got r R\n", " 5.6 M got x X\n", " 6.4 M got p P\n", " 6.8 M got y Y\n", " 7.0 M got \n", " 7.4 M got r R\n", " 7.6 M got o O\n", " 8.0 M got c C\n", " 8.5 M got k K\n", " 8.9 M got s S\n", " 9.4 M [next] 4.6: True\n", " 9.6 M [cmpl] 4.8: fin\n", "\n", "\n", "========== there is no order guarantee of arguments in the comparer: ==========\n", "\n", "\n", " 11.4 M New subscription on stream 276578009\n", " 27.6 M got a A\n", " 139.1 M got b B\n", " 749.0 T988 got C c\n", " 749.6 T988 [next] 738.1: False\n", " 749.7 T988 [cmpl] 738.2: fin\n" ] } ], "source": [ "rst(O.sequence_equal)\n", "def f(x, y):\n", " log('got', x, y)\n", " return str(y) == str(x).upper()\n", "d = subs(O.from_('rxpy rocks').sequence_equal(\n", " O.from_('RXPY ROCKS'), lambda x, y: f(x, y)))\n", "\n", "header(\"there is no order guarantee of arguments in the comparer:\")\n", "d = subs(marble_stream('a-b------c-d|').sequence_equal(\n", " marble_stream('A-B----C-D|'), lambda x, y: f(x, y)))\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## ... and emit the average of all of their values **[average](http://reactivex.io/documentation/operators/average.html)**" ] }, { "cell_type": "code", "execution_count": 83, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== average ==========\n", "\n", "module rx.linq.observable.average\n", "@extensionmethod(ObservableBase)\n", "def average(self, key_mapper=None):\n", " Computes the average of an observable sequence of values that are in\n", " the sequence or obtained by invoking a transform function on each\n", " element of the input sequence if present.\n", "\n", " Example\n", " res = source.average();\n", " res = source.average(lambda x: x.value)\n", "\n", " :param Observable self: Observable to average.\n", " :param types.FunctionType key_mapper: A transform function to apply to\n", " each element.\n", "\n", " :returns: An observable sequence containing a single element with the\n", " average of the sequence of values.\n", " :rtype: Observable\n", "--------------------------------------------------------------------------------\n", "\n", " 2.5 M New subscription on stream 279255829\n", " 4.0 M [next] 1.4: 5.0\n", " 4.1 M [cmpl] 1.5: fin\n" ] } ], "source": [ "rst(O.average)\n", "d = subs(O.from_('1199').average(lambda x: int(x)))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## ... and emit the sum of all of their values **[sum](http://reactivex.io/documentation/operators/sum.html)**" ] }, { "cell_type": "code", "execution_count": 84, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== sum ==========\n", "\n", "module rx.linq.observable.sum\n", "@extensionmethod(ObservableBase)\n", "def sum(self, key_mapper=None):\n", " Computes the sum of a sequence of values that are obtained by\n", " invoking an optional transform function on each element of the input\n", " sequence, else if not specified computes the sum on each item in the\n", " sequence.\n", "\n", " Example\n", " res = source.sum()\n", " res = source.sum(lambda x: x.value)\n", "\n", " key_mapper -- {Function} [Optional] A transform function to apply to\n", " each element.\n", "\n", " Returns an observable {Observable} sequence containing a single element\n", " with the sum of the values in the source sequence.\n", "--------------------------------------------------------------------------------\n", "\n", " 2.6 M New subscription on stream 276565865\n", " 3.9 M [next] 1.2: 20\n", " 4.1 M [cmpl] 1.4: fin\n" ] } ], "source": [ "rst(O.sum)\n", "d = subs(O.from_('1199').sum(lambda x: int(x)))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## ... and emit a number indicating how many items were in the sequence **[count](http://reactivex.io/documentation/operators/count.html)**\n" ] }, { "cell_type": "code", "execution_count": 88, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== count ==========\n", "\n", "module rx.linq.observable.count\n", "@extensionmethod(ObservableBase)\n", "def count(self, predicate=None):\n", " Returns an observable sequence containing a value that represents\n", " how many elements in the specified observable sequence satisfy a\n", " condition if provided, else the count of items.\n", "\n", " 1 - res = source.count()\n", " 2 - res = source.count(lambda x: x > 3)\n", "\n", " Keyword arguments:\n", " :param types.FunctionType predicate: A function to test each element for a\n", " condition.\n", "\n", " :returns: An observable sequence containing a single element with a\n", " number that represents how many elements in the input sequence satisfy\n", " the condition in the predicate function if provided, else the count of\n", " items in the sequence.\n", " :rtype: Observable\n", "--------------------------------------------------------------------------------\n", "\n", " 1.8 M New subscription on stream 279255949\n", " 3.6 M [next] 1.6: 2\n", " 3.7 M [cmpl] 1.8: fin\n" ] } ], "source": [ "rst(O.count)\n", "d = subs(O.from_('1199').count(lambda x: int(x) > 2))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## ...and emit the item with the maximum value **[max, max_by](http://reactivex.io/documentation/operators/max.html)**" ] }, { "cell_type": "code", "execution_count": 91, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== max ==========\n", "\n", "module rx.linq.observable.max\n", "@extensionmethod(ObservableBase)\n", "def max(self, comparer=None):\n", " Returns the maximum value in an observable sequence according to the\n", " specified comparer.\n", "\n", " Example\n", " res = source.max()\n", " res = source.max(lambda x, y: x.value - y.value)\n", "\n", " Keyword arguments:\n", " comparer -- {Function} [Optional] Comparer used to compare elements.\n", "\n", " Returns {Observable} An observable sequence containing a single element\n", " with the maximum element in the source sequence.\n", "--------------------------------------------------------------------------------\n", "\n", " 1.5 M New subscription on stream 279255829\n", " 3.0 M [next] 1.3: 1\n", " 3.1 M [cmpl] 1.5: fin\n" ] } ], "source": [ "rst(O.max)\n", "d = subs(O.from_('1199').max(lambda x, y: int(x) + int(y) < 5))" ] }, { "cell_type": "code", "execution_count": 97, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== max_by ==========\n", "\n", "module rx.linq.observable.maxby\n", "@extensionmethod(ObservableBase)\n", "def max_by(self, key_mapper, comparer=None):\n", " Returns the elements in an observable sequence with the maximum\n", " key value according to the specified comparer.\n", "\n", " Example\n", " res = source.max_by(lambda x: x.value)\n", " res = source.max_by(lambda x: x.value, lambda x, y: x - y)\n", "\n", " Keyword arguments:\n", " key_mapper -- {Function} Key mapper function.\n", " comparer -- {Function} [Optional] Comparer used to compare key values.\n", "\n", " Returns an observable {Observable} sequence containing a list of zero\n", " or more elements that have a maximum key value.\n", "--------------------------------------------------------------------------------\n", "\n", " 1.5 M New subscription on stream 276577929\n", " 2.8 M [next] 1.1: ['1', '2', '1', '2', '4']\n", " 3.3 M [cmpl] 1.7: fin\n" ] } ], "source": [ "rst(O.max_by)\n", "d = subs(O.from_('1271246').max_by(lambda x: int(x) < 5))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## ... and emit the item with the minimum value **[min, min_by](http://reactivex.io/documentation/operators/min.html)**" ] }, { "cell_type": "code", "execution_count": 99, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== min ==========\n", "\n", "module rx.linq.observable.min\n", "@extensionmethod(ObservableBase)\n", "def min(self, comparer=None):\n", " Returns the minimum element in an observable sequence according to\n", " the optional comparer else a default greater than less than check.\n", "\n", " Example\n", " res = source.min()\n", " res = source.min(lambda x, y: x.value - y.value)\n", "\n", " comparer -- {Function} [Optional] Comparer used to compare elements.\n", "\n", " Returns an observable sequence {Observable} containing a single element\n", " with the minimum element in the source sequence.\n", "--------------------------------------------------------------------------------\n", "\n", " 1.5 M New subscription on stream 279257813\n", " 2.7 M [next] 1.0: 1\n", " 2.8 M [cmpl] 1.2: fin\n" ] } ], "source": [ "rst(O.min)\n", "d = subs(O.from_('1199').min(lambda x, y: int(x) + int(y) > 5))" ] }, { "cell_type": "code", "execution_count": 100, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== min_by ==========\n", "\n", "module rx.linq.observable.minby\n", "@extensionmethod(ObservableBase)\n", "def min_by(self, key_mapper, comparer=None):\n", " Returns the elements in an observable sequence with the minimum key\n", " value according to the specified comparer.\n", "\n", " Example\n", " res = source.min_by(lambda x: x.value)\n", " res = source.min_by(lambda x: x.value, lambda x, y: x - y)\n", "\n", " Keyword arguments:\n", " key_mapper -- {Function} Key mapper function.\n", " comparer -- {Function} [Optional] Comparer used to compare key values.\n", "\n", " Returns an observable {Observable} sequence containing a list of zero\n", " or more elements that have a minimum key value.\n", "--------------------------------------------------------------------------------\n", "\n", " 1.7 M New subscription on stream 276595497\n", " 3.1 M [next] 1.3: ['7', '6']\n", " 3.3 M [cmpl] 1.6: fin\n" ] } ], "source": [ "rst(O.min_by)\n", "d = subs(O.from_('1271246').min_by(lambda x: int(x) < 5))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## ... by applying an aggregation function to each item in turn (or recursing into its result) and emitting the result **[scan, expand](http://reactivex.io/documentation/operators/scan.html)**" ] }, { "cell_type": "code", "execution_count": 110, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== scan ==========\n", "\n", "module rx.linq.observable.scan\n", "@extensionmethod(ObservableBase)\n", "def scan(self, accumulator, seed=None):\n", " Applies an accumulator function over an observable sequence and\n", " returns each intermediate result. The optional seed value is used as\n", " the initial accumulator value. For aggregation behavior with no\n", " intermediate results, see Observable.aggregate.\n", "\n", " 1 - scanned = source.scan(lambda acc, x: acc + x)\n", " 2 - scanned = source.scan(lambda acc, x: acc + x, 0)\n", "\n", " Keyword arguments:\n", " accumulator -- An accumulator function to be invoked on each element.\n", " seed -- [Optional] The initial accumulator value.\n", "\n", " Returns an observable sequence containing the accumulated values.\n", "--------------------------------------------------------------------------------\n", "\n", " 2.4 M New subscription on stream 278466789\n", " 3.0 M [next] 0.4: ['1', 1]\n", " 3.2 M [next] 0.7: ['2', 3]\n", " 3.5 M [next] 0.9: ['3', 6]\n", " 3.7 M [next] 1.2: ['4', 10]\n", " 4.0 M [next] 1.4: ['5', 15]\n", " 4.2 M [cmpl] 1.7: fin\n" ] } ], "source": [ "rst(O.scan)\n", "# printing original value next to the aggregate:\n", "d = subs(O.from_('12345').scan(\n", " lambda x, y: [y, int(x[1]) + int(y)],\n", " seed=[0, 0]))" ] }, { "cell_type": "code", "execution_count": 115, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== expand ==========\n", "\n", "module rx.linq.observable.expand\n", "@extensionmethod(ObservableBase)\n", "def expand(self, mapper, scheduler=None):\n", " Expands an observable sequence by recursively invoking mapper.\n", "\n", " mapper -- {Function} Selector function to invoke for each produced\n", " element, resulting in another sequence to which the mapper will be\n", " invoked recursively again.\n", " scheduler -- {Scheduler} [Optional] Scheduler on which to perform the\n", " expansion. If not provided, this defaults to the current thread\n", " scheduler.\n", "\n", " Returns an observable {Observable} sequence containing all the elements\n", " produced by the recursive expansion.\n", "--------------------------------------------------------------------------------\n", "\n", " 4.0 M New subscription on stream 278470673\n", " 4.6 M [next] 0.5: 42\n", " 5.3 M [next] 1.2: 84\n", " 5.7 M [next] 1.6: 126\n", " 6.3 M [next] 2.1: 168\n", " 6.5 M [next] 2.4: 210\n", " 6.6 M [cmpl] 2.5: fin\n" ] } ], "source": [ "rst(O.expand)\n", "# printing original value next to the aggregate:\n", "d = subs(O.just(42).expand(lambda x: O.just(42 + x)).take(5))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 2", "language": "python", "name": "python2" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", "version": "2.7.12" } }, "nbformat": 4, "nbformat_minor": 1 } RxPY-3.2.0/notebooks/reactivex.io/Part VII - Meta Operations.ipynb000066400000000000000000001461671404130727200244510ustar00rootroot00000000000000{ "cells": [ { "cell_type": "code", "execution_count": 1, "metadata": { "collapsed": false }, "outputs": [], "source": [ "%run startup.py" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "collapsed": false }, "outputs": [ { "data": { "application/javascript": [ "$.getScript('./assets/js/ipython_notebook_toc.js')" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "%%javascript\n", "$.getScript('./assets/js/ipython_notebook_toc.js')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# A Decision Tree of Observable Operators\n", "\n", "## Part 7: Meta Operations\n", "\n", "> source: http://reactivex.io/documentation/operators.html#tree. \n", "> (transcribed to RxPY 1.5.7, Py2.7 / 2016-12, Gunther Klessinger, [axiros](http://www.axiros.com)) \n", "\n", "**This tree can help you find the ReactiveX Observable operator you’re looking for.** \n", "See [Part 1](./A Decision Tree of Observable Operators. Part I - Creation.ipynb) for Usage and Output Instructions. \n", "\n", "We also require acquaintance with the [marble diagrams](./Marble Diagrams.ipynb) feature of RxPy.\n", "\n", "

    Table of Contents

    \n", "
    \n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# I want to convert the entire sequence of items emitted by an Observable into some other data structure [to_iterable/to_list, to_blocking, to_dict, to_future, to_marbles, to_set](http://reactivex.io/documentation/operators/to.html)" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== to_list ==========\n", "\n", "module rx.linq.observable.tolist\n", "@extensionmethod(Observable, alias=\"to_iterable\")\n", "def to_list(self):\n", " Creates a list from an observable sequence.\n", "\n", " Returns an observable sequence containing a single element with a list\n", " containing all the elements of the source sequence.\n", "--------------------------------------------------------------------------------\n", "('got', 'a', 0.011667013168334961)\n", "('got', 'b', 0.22758698463439941)\n", "('got', 'c', 0.335313081741333)\n", "('got', ['a', 'b', 'c'], 0.3470189571380615)\n" ] } ], "source": [ "rst(O.to_iterable)\n", "s = marble_stream(\"a--b-c|\")\n", "l, ts = [], time.time()\n", "def on_next(listed):\n", " print('got', listed, time.time()-ts)\n", "\n", "for i in (1, 2): \n", " d = s.subscribe(on_next)\n", " # second run: only one value, the list.\n", " s = s.to_list()\n", " # both are started around same time -> check time deltas\n" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== to_blocking ==========\n", "\n", "module rx.linq.observable.toblocking\n", "@extensionmethod(ObservableBase)\n", "def to_blocking(self):\n", " n.a.\n", "--------------------------------------------------------------------------------\n", "In some implementations of ReactiveX, there is also an operator that converts an Observable into a “Blocking” Observable. A Blocking Observable extends the ordinary Observable by providing a set of methods, operating on the items emitted by the Observable, that block. Some of the To operators are in this Blocking Obsevable set of extended operations.\n", "\n", "\n", "========== __iter__ ==========\n", "\n", "module rx.linq.observable.blocking.toiterable\n", "@extensionmethod(BlockingObservable)\n", "def __iter__(self):\n", " Returns an iterator that can iterate over items emitted by this\n", " `BlockingObservable`.\n", "\n", " :param BlockingObservable self: Blocking observable instance.\n", " :returns: An iterator that can iterate over the items emitted by this\n", " `BlockingObservable`.\n", " :rtype: Iterable[Any]\n", "--------------------------------------------------------------------------------\n", " 204.0 M 0\n", " 408.9 M 1\n", " 615.1 M 2\n", " 822.2 M 0\n", "1027.2 M 1\n", "1230.1 M 2\n", "\n", "\n", "========== for_each ==========\n", "\n", "module rx.linq.observable.blocking.foreach\n", "@extensionmethod(BlockingObservable)\n", "def for_each(self, action):\n", " Invokes a method on each item emitted by this BlockingObservable and\n", " blocks until the Observable completes.\n", "\n", " Note: This will block even if the underlying Observable is asynchronous.\n", "\n", " This is similar to Observable#subscribe(subscriber), but it blocks. Because\n", " it blocks it does not need the Subscriber#on_completed() or\n", " Subscriber#on_error(Throwable) methods. If the underlying Observable\n", " terminates with an error, rather than calling `onError`, this method will\n", " throw an exception.\n", "\n", " Keyword arguments:\n", " :param types.FunctionType action: the action to invoke for each item\n", " emitted by the `BlockingObservable`.\n", " :raises Exception: if an error occurs\n", " :returns: None\n", " :rtype: None\n", "--------------------------------------------------------------------------------\n", " 205.5 T28 0\n", " 408.5 T29 1\n", " 613.9 T30 2\n", "\n", "\n", "========== .observable -> getting async again ==========\n", "\n", "\n", " 615.9 M New subscription (observer 1) on stream 276608405\n", "\n", " 617.6 M New subscription (observer 2) on stream 276608405\n", " 821.8 T33 [next] 204.2: 0 -> observer 2 822.6 T32 [next] 206.4: 0 -> observer 1 \n", "\n", "1025.0 T34 [next] 407.4: 1 -> observer 2 1024.8 T35 [next] 408.6: 1 -> observer 1 \n", "\n", "1227.7 T36 [next] 610.1: 2 -> observer 2 1229.0 T37 [next] 612.8: 2 -> observer 1 \n", "\n", "1229.4 T37 [cmpl] 613.2: fin -> observer 1 1228.5 T36 [cmpl] 610.9: fin -> observer 2 \n", "\n" ] } ], "source": [ "rst(O.to_blocking)\n", "ts = time.time()\n", "s = O.interval(200).take(3)\n", "sb = s.to_blocking()\n", "# this is instant:\n", "assert time.time() - ts < 0.2\n", "\n", "print('''In some implementations of ReactiveX, there is also an operator that converts an Observable into a “Blocking” Observable. A Blocking Observable extends the ordinary Observable by providing a set of methods, operating on the items emitted by the Observable, that block. Some of the To operators are in this Blocking Obsevable set of extended operations.''')\n", "# -> diffing dir(s) with dir(sb) we get:\n", "# __iter__\n", "# for_each\n", "# observable\n", "rst(sb.__iter__)\n", "for i in (1, 2):\n", " # not interleaved results:\n", " for it in sb:\n", " log(it)\n", "\n", "rst(sb.for_each) \n", "sb.for_each(log)\n", "\n", "header(\".observable -> getting async again\")\n", "# interleaved again:\n", "d = subs(sb.observable, name='observer 1')\n", "d = subs(sb.observable, name='observer 2')" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== to_dict ==========\n", "\n", "module rx.linq.observable.todict\n", "@extensionmethod(ObservableBase)\n", "to_dict(self, key_mapper, element_mapper=None)\n", " Converts the observable sequence to a Map if it exists.\n", "\n", " Keyword arguments:\n", " key_mapper -- {Function} A function which produces the key for the\n", " Map.\n", " element_mapper -- {Function} [Optional] An optional function which\n", " produces the element for the Map. If not present, defaults to the\n", " value from the observable sequence.\n", " Returns {Observable} An observable sequence with a single value of a Map\n", " containing the values from the observable sequence.\n", "--------------------------------------------------------------------------------\n", "\n", " 1.6 M New subscription on stream 276526817\n", " 2.6 M [next] 0.7: {'a': 'aa', 'c': 'cc', 'b': 'bb'}\n", " 2.8 M [cmpl] 0.8: fin\n" ] } ], "source": [ "rst(O.to_dict)\n", "d = subs(O.from_('abc').to_dict(key_mapper=lambda x: x, element_mapper=lambda a: '%s%s' % (a, a)))" ] }, { "cell_type": "code", "execution_count": 29, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== to_future ==========\n", "\n", "module rx.linq.observable.tofuture\n", "@extensionmethod(ObservableBase)\n", "def to_future(self, future_ctor=None):\n", " Converts an existing observable sequence to a Future\n", "\n", " Example:\n", " future = rx.return_value(42).to_future(trollius.Future);\n", "\n", " With config:\n", " rx.config[\"Future\"] = trollius.Future\n", " future = rx.return_value(42).to_future()\n", "\n", " future_ctor -- {Function} [Optional] The constructor of the future.\n", " If not provided, it looks for it in rx.config.Future.\n", "\n", " Returns {Future} An future with the last value from the observable\n", " sequence.\n", "--------------------------------------------------------------------------------\n", " 506.5 M emitting first\n", "1008.0 M emitting second\n", "1008.7 M future.result(): second\n" ] } ], "source": [ "rst(O.to_future)\n", "def emit(obs): \n", " for ev in 'first', 'second':\n", " sleep(.5)\n", " log('emitting', ev)\n", " obs.on_next(ev)\n", " # vital for the future to get done:\n", " obs.on_completed()\n", " \n", " \n", "try:\n", " # required for py2 (backport of guidos' tulip stuffs, now asyncio)\n", " # caution: people say this is not production ready and will never be.\n", " import trollius\n", " f = rx.Observable.create(emit).to_future(trollius.Future)\n", " # this is async, not a busy loop\n", " log('future.result():', f.result())\n", "except: # notebook should always run all cells\n", " print ('skipping this; pip install trollius required')" ] }, { "cell_type": "code", "execution_count": 34, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== from_marbles ==========\n", "\n", "module rx.testing.marbles\n", "@extensionclassmethod(Observable, alias=\"from_string\")\n", "def from_marbles(cls, string, scheduler=None):\n", " Convert a marble diagram string to an observable sequence, using\n", " an optional scheduler to enumerate the events.\n", "\n", " Special characters:\n", " - = Timespan of 100 ms\n", " x = on_error()\n", " | = on_completed()\n", "\n", " All other characters are treated as an on_next() event at the given\n", " moment they are found on the string.\n", "\n", " Examples:\n", " 1 - res = rx.Observable.from_string(\"1-2-3-|\")\n", " 2 - res = rx.Observable.from_string(\"1-(42)-3-|\")\n", " 3 - res = rx.Observable.from_string(\"1-2-3-x\", rx.Scheduler.timeout)\n", "\n", " Keyword arguments:\n", " string -- String with marble diagram\n", " scheduler -- [Optional] Scheduler to run the the input sequence on.\n", "\n", " Returns the observable sequence whose elements are pulled from the\n", " given marble diagram string.\n", "--------------------------------------------------------------------------------\n", "\n", " 4.2 M New subscription on stream 276651201\n", " 16.2 T21 [next] 12.0: 1\n", " 127.7 T22 [next] 123.5: 42\n", " 236.9 T25 [next] 232.7: 3\n", " 347.0 T27 [cmpl] 342.7: fin\n" ] } ], "source": [ "rst(O.from_marbles)\n", "d = subs(rx.Observable.from_string(\"1-(42)-3-|\").to_blocking())" ] }, { "cell_type": "code", "execution_count": 40, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== to_set ==========\n", "\n", "module rx.linq.observable.toset\n", "@extensionmethod(ObservableBase)\n", "def to_set(self):\n", " Converts the observable sequence to a set.\n", "\n", " Returns {Observable} An observable sequence with a single value of a set\n", " containing the values from the observable sequence.\n", "--------------------------------------------------------------------------------\n", "\n", " 1.3 M New subscription on stream 276654145\n", " 3.0 M [next] 1.5: set(['a', 'c', 'b'])\n", " 3.4 M [cmpl] 1.9: fin\n" ] } ], "source": [ "rst(O.to_set)\n", "d = subs(O.from_(\"abcabc\").to_set())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# I want an operator to operate on a particular Scheduler: **[subscribe_on](http://reactivex.io/documentation/operators/subscribeon.html)**\n", "\n", "Advanced feature: Adding **side effects** to subscription and unsubscription events.\n", "\n", "[This](http://tomstechnicalblog.blogspot.de/2016/02/rxjava-understanding-observeon-and.html) is a good read:\n", "\n", "![](https://i.imgur.com/CRLzESV.png)\n", "\n", "Plus see the other\n", "links on [RX docu](http://reactivex.io/documentation/operators/subscribeon.html)" ] }, { "cell_type": "code", "execution_count": 54, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== subscribe_on ==========\n", "\n", "module rx.linq.observable.subscribeon\n", "@extensionmethod(ObservableBase)\n", "def subscribe_on(self, scheduler):\n", " Subscribe on the specified scheduler.\n", "\n", " Wrap the source sequence in order to run its subscription and\n", " unsubscription logic on the specified scheduler. This operation is not\n", " commonly used; see the remarks section for more information on the\n", " distinction between subscribe_on and observe_on.\n", "\n", " Keyword arguments:\n", " scheduler -- Scheduler to perform subscription and unsubscription\n", " actions on.\n", "\n", " Returns the source sequence whose subscriptions and unsubscriptions\n", " happen on the specified scheduler.\n", "\n", " This only performs the side-effects of subscription and unsubscription\n", " on the specified scheduler. In order to invoke observer callbacks on a\n", " scheduler, use observe_on.\n", "--------------------------------------------------------------------------------\n", "\n", "\n", "========== Switching Schedulers ==========\n", "\n", " 3.6 T451 [next] 0.9: 42 (SimpleSubs)\n", "\n", " 2.6 M New subscription on stream 276546517 3.8 T451 [cmpl] 1.2: fin (SimpleSubs)\n", "\n", "\n", "\n", "========== Custom Subscription Side Effects ==========\n", "\n", "\n", " 106.6 M New subscription on stream 276546553\n", " 106.9 M new scheduling task \n", "\n", " 107.9 M New subscription on stream 276546553\n", " 108.5 M new scheduling task \n", " 310.2 T454 [next] 203.5: 0 (subs1)\n", " 311.6 T456 [next] 203.4: 0 (subs2)\n", " 513.5 T457 [next] 406.8: 1 (subs1)\n", " 515.4 T458 [next] 407.3: 1 (subs2) 513.8 T457 [cmpl] 407.2: fin (subs1)\n", " 516.2 T458 [cmpl] 408.1: fin (subs2)\n", "\n" ] } ], "source": [ "rst(O.subscribe_on)\n", "\n", "# start simple:\n", "header('Switching Schedulers')\n", "s = O.just(42, rx.scheduler.ImmediateScheduler())\n", "d = subs(s.subscribe_on(rx.scheduler.TimeoutScheduler()), name='SimpleSubs')\n", "\n", "sleep(0.1)\n", "\n", "header('Custom Subscription Side Effects')\n", "\n", "from rx.scheduler.newthreadscheduler import NewThreadScheduler\n", "from rx.scheduler.eventloopscheduler import EventLoopScheduler\n", "\n", "class MySched(NewThreadScheduler):\n", " '''For adding side effects at subscription and unsubscription time'''\n", " def schedule(self, action, state=None):\n", " log('new scheduling task', action)\n", " scheduler = EventLoopScheduler(\n", " thread_factory=self.thread_factory,\n", " exit_if_empty=True)\n", " return scheduler.schedule(action, state)\n", " \n", "s = O.interval(200).take(2)\n", "s = s.subscribe_on(MySched())\n", "d = subs(s, name=\"subs1\")\n", "d = subs(s, name=\"subs2\")\n" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "## ...when it notifies observers **[observe_on](http://reactivex.io/documentation/operators/subscribeon.html)**\n", "\n", "Via this you can add side effects on any notification to any subscriber.\n", "\n", "This example shall demonstrate whats going on:" ] }, { "cell_type": "code", "execution_count": 44, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== observe_on ==========\n", "\n", "module rx.linq.observable.observeon\n", "@extensionmethod(ObservableBase)\n", "def observe_on(self, scheduler):\n", " Wraps the source sequence in order to run its observer callbacks on\n", " the specified scheduler.\n", "\n", " Keyword arguments:\n", " scheduler -- Scheduler to notify observers on.\n", "\n", " Returns the source sequence whose observations happen on the specified\n", " scheduler.\n", "\n", " This only invokes observer callbacks on a scheduler. In case the\n", " subscription and/or unsubscription actions have side-effects\n", " that require to be run on a scheduler, use subscribe_on.\n", "--------------------------------------------------------------------------------\n", "\n", "\n", "========== defining a custom thread factory for a custom scheduler ==========\n", "\n", "\n", " 2.8 M New subscription on stream 276529577\n", " 208.2 T392 RX called schedule on mysched\n", "\n", "\n", "created Thread-393\n", "\n", " 209.5 T393 [next] 206.4: 0\n", " 210.2 T393 RX called schedule on mysched\n", "\n", "\n", "created Thread-395\n", "\n", " 414.0 T394 RX called schedule on mysched\n", " 415.5 T396 [next] 412.4: 1\n", "\n", "\n", "created Thread-396\n", " 416.2 T396 RX called schedule on mysched\n", "\n", "\n", "\n", "created Thread-398\n", "\n", " 619.0 T397 RX called schedule on mysched\n", " 620.3 T399 [next] 617.3: 2\n", "\n", "\n", "created Thread-399\n", " 620.5 T399 RX called schedule on mysched\n", "\n", "\n", " 622.4 T400 [cmpl] 619.4: fin\n", "created Thread-400\n", "\n", "\n", " 622.9 T400 RX called schedule on mysched\n", "\n", "\n", "created Thread-402\n", "\n", "all threads after finish:\n", "MainThread Thread-2 IPythonHistorySavingThread Thread-1 Thread-3\n" ] } ], "source": [ "rst(O.observe_on)\n", "from rx.scheduler.newthreadscheduler import NewThreadScheduler\n", "\n", "header('defining a custom thread factory for a custom scheduler')\n", "def my_thread_factory(target, args=None): \n", " 'just to show that also here we can customize'\n", " t = threading.Thread(target=target, args=args or [])\n", " t.setDaemon(True)\n", " print ('\\ncreated %s\\n' % t.getName())\n", " return t\n", "\n", "\n", "class MySched:\n", " def __init__(self):\n", " self.rx_sched = NewThreadScheduler(my_thread_factory)\n", " \n", " def __getattr__(self, a):\n", " 'called whenever the observe_on scheduler is on duty'\n", " log('RX called', a, 'on mysched\\n')\n", " return getattr(self.rx_sched, a)\n", " \n", "mysched = MySched() \n", "s = O.interval(200).take(3) #.delay(100, mysched)\n", "\n", "d = subs(s.observe_on(mysched))\n", "\n", "sleep(2)\n", "print 'all threads after finish:' # all cleaned up\n", "print (' '.join([t.name for t in threading.enumerate()]))" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "# I want an Observable to invoke a particular action when certain events occur **[do_action/tap, finally_action](http://reactivex.io/documentation/operators/do.html)**" ] }, { "cell_type": "code", "execution_count": 66, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== do_action ==========\n", "\n", "module rx.linq.observable.doaction\n", "@extensionmethod(Observable, alias=\"tap\")\n", "def do_action(self, on_next=None, on_error=None, on_completed=None,\n", " Invokes an action for each element in the observable sequence and\n", " invokes an action upon graceful or exceptional termination of the\n", " observable sequence. This method can be used for debugging, logging,\n", " etc. of query behavior by intercepting the message stream to run\n", " arbitrary actions for messages on the pipeline.\n", "\n", " 1 - observable.do_action(observer)\n", " 2 - observable.do_action(on_next)\n", " 3 - observable.do_action(on_next, on_error)\n", " 4 - observable.do_action(on_next, on_error, on_completed)\n", "\n", " observer -- [Optional] Observer, or ...\n", " on_next -- [Optional] Action to invoke for each element in the\n", " observable sequence.\n", " on_error -- [Optional] Action to invoke upon exceptional termination\n", " of the observable sequence.\n", " on_completed -- [Optional] Action to invoke upon graceful termination\n", " of the observable sequence.\n", "\n", " Returns the source sequence with the side-effecting behavior applied.\n", "--------------------------------------------------------------------------------\n", "\n", " 3.4 M New subscription on stream 276579277\n", " 4.1 M NI! 10\n", " 4.5 M [next] 1.0: 10\n", " 4.7 M NI! 11\n", " 4.9 M [next] 1.5: 11\n", " 5.1 M EOF\n", " 5.2 M [cmpl] 1.8: fin\n" ] } ], "source": [ "rst(O.do_action)\n", "def say(v=None):\n", " if v:\n", " log('NI!', v)\n", " else:\n", " log('EOF')\n", " \n", "d = subs(O.range(10, 10).take(2).tap(say, on_completed=say)) " ] }, { "cell_type": "code", "execution_count": 72, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== finally_action ==========\n", "\n", "module rx.linq.observable.finallyaction\n", "@extensionmethod(ObservableBase)\n", "def finally_action(self, action):\n", " Invokes a specified action after the source observable sequence\n", " terminates gracefully or exceptionally.\n", "\n", " Example:\n", " res = observable.finally(lambda: print('sequence ended')\n", "\n", " Keyword arguments:\n", " action -- {Function} Action to invoke after the source observable\n", " sequence terminates.\n", " Returns {Observable} Source sequence with the action-invoking\n", " termination behavior applied.\n", "--------------------------------------------------------------------------------\n", "\n", " 2.2 M New subscription on stream 276553221\n", " 3.0 M [err ] 0.6: err\n", " 3.5 M EOF\n" ] } ], "source": [ "rst(O.finally_action)\n", "d = subs(O.on_error('err').take(2).finally_action(say))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# I want an Observable that will notify observers of an error [throw](http://reactivex.io/documentation/operators/throw.html)**" ] }, { "cell_type": "code", "execution_count": 76, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== throw ==========\n", "\n", "module rx.linq.observable.throw\n", "@extensionclassmethod(Observable, alias=\"throw_exception\")\n", "def on_error(cls, exception, scheduler=None):\n", " Returns an observable sequence that terminates with an exception,\n", " using the specified scheduler to send out the single OnError message.\n", "\n", " 1 - res = rx.throw(Exception('Error'))\n", " 2 - res = rx.throw(Exception('Error'),\n", " rx.Scheduler.timeout)\n", "\n", " Keyword arguments:\n", " exception -- An object used for the sequence's termination.\n", " scheduler -- Scheduler to send the exceptional termination call on. If\n", " not specified, defaults to ImmediateScheduler.\n", "\n", " Returns the observable sequence that terminates exceptionally with the\n", " specified exception object.\n", "--------------------------------------------------------------------------------\n", "\n", " 1.9 M New subscription on stream 276578025\n", " 2.6 M [next] 0.6: 1\n", " 3.0 M [next] 0.9: 2\n", " 3.3 M [next] 1.2: 3\n", " 3.6 M [err ] 1.5: ups\n" ] } ], "source": [ "rst(O.throw)\n", "d = subs(O.range(1, 3).concat(O.on_error(\"ups\")))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## ...if a specified period of time elapses without it emitting an item [timeout / timeout_with_selector](http://reactivex.io/documentation/operators/timeout.html)**" ] }, { "cell_type": "code", "execution_count": 85, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== timeout ==========\n", "\n", "module rx.linq.observable.timeout\n", "@extensionmethod(ObservableBase)\n", "def timeout(self, duetime, other=None, scheduler=None):\n", " Returns the source observable sequence or the other observable\n", " sequence if duetime elapses.\n", "\n", " 1 - res = source.timeout(new Date()); # As a date\n", " 2 - res = source.timeout(5000); # 5 seconds\n", " # As a date and timeout observable\n", " 3 - res = source.timeout(datetime(), rx.return_value(42))\n", " # 5 seconds and timeout observable\n", " 4 - res = source.timeout(5000, rx.return_value(42))\n", " # As a date and timeout observable\n", " 5 - res = source.timeout(datetime(), rx.return_value(42),\n", " rx.Scheduler.timeout)\n", " # 5 seconds and timeout observable\n", " 6 - res = source.timeout(5000, rx.return_value(42),\n", " rx.Scheduler.timeout)\n", "\n", " Keyword arguments:\n", " :param datetime|int duetime: Absolute (specified as a datetime object) or\n", " relative time (specified as an integer denoting milliseconds) when a\n", " timeout occurs.\n", " :param Observable other: Sequence to return in case of a timeout. If not\n", " specified, a timeout error throwing sequence will be used.\n", " :param Scheduler scheduler: Scheduler to run the timeout timers on. If not\n", " specified, the timeout scheduler is used.\n", "\n", " :returns: The source sequence switching to the other sequence in case of\n", " a timeout.\n", " :rtype: Observable\n", "--------------------------------------------------------------------------------\n", "\n", " 4.2 M New subscription on stream 276577449\n", " 17.1 T521 [next] 12.8: a\n", " 127.8 T522 [next] 123.5: b\n", " 332.7 T530 [next] 328.4: timeout\n", " 333.0 T530 [cmpl] 328.7: fin\n" ] } ], "source": [ "rst(O.timeout)\n", "d = subs(marble_stream(\"a-b---c|\").timeout(200, O.just('timeout')))\n", "# this also works with absolute time. See docstring:" ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== timeout_with_selector ==========\n", "\n", "module rx.linq.observable.timeoutwithselector\n", "@extensionmethod(ObservableBase)\n", "def timeout_with_selector(self, first_timeout=None,\n", " timeout_duration_mapper=None, other=None):\n", " Returns the source observable sequence, switching to the other\n", " observable sequence if a timeout is signaled.\n", "\n", " 1 - res = source.timeout_with_selector(rx.Observable.timer(500))\n", " 2 - res = source.timeout_with_selector(rx.Observable.timer(500),\n", " lambda x: rx.Observable.timer(200))\n", " 3 - res = source.timeout_with_selector(rx.Observable.timer(500),\n", " lambda x: rx.Observable.timer(200)),\n", " rx.return_value(42))\n", "\n", " first_timeout -- [Optional] Observable sequence that represents the\n", " timeout for the first element. If not provided, this defaults to\n", " rx.never().\n", " timeout_Duration_mapper -- [Optional] Selector to retrieve an\n", " observable sequence that represents the timeout between the current\n", " element and the next element.\n", " other -- [Optional] Sequence to return in case of a timeout. If not\n", " provided, this is set to rx.throw().\n", "\n", " Returns the source sequence switching to the other sequence in case of\n", " a timeout.\n", "--------------------------------------------------------------------------------\n", "\n", " 3.9 M New subscription on stream 276596365\n", " 17.0 T74 [next] 12.9: 2\n", " 126.1 T76 [next] 122.1: 2\n", " 237.1 T77 [next] 233.1: 1\n", " 341.6 T86 [next] 337.5: timeout\n", " 341.9 T86 [cmpl] 337.8: fin\n" ] } ], "source": [ "rst(O.timeout_with_selector)\n", "d = subs(marble_stream(\"2-2-1-1|\")\\\n", " .timeout_with_selector(\n", " # you get the value and can adjust the timeout accordingly:\n", " timeout_duration_mapper=lambda x: O.timer(100 * int(x)),\n", " other=O.just('timeout')))\n" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "# I want an Observable to recover gracefully" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## ...from a timeout by switching to a backup Observable [timeout / timeout_with_selector](http://reactivex.io/documentation/operators/timeout.html)**\n", "(example: see above)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## ...from an upstream error notification [catch_exception, on_error_resume_next](http://reactivex.io/documentation/operators/catch.html)**" ] }, { "cell_type": "code", "execution_count": 20, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== catch_exception ==========\n", "\n", "module rx.linq.observable.catch\n", "@extensionclassmethod(Observable)\n", "def catch(cls, *args):\n", " Continues an observable sequence that is terminated by an\n", " exception with the next observable sequence.\n", "\n", " 1 - res = Observable.catch(xs, ys, zs)\n", " 2 - res = Observable.catch([xs, ys, zs])\n", "\n", " Returns an observable sequence containing elements from consecutive\n", " source sequences until a source sequence terminates successfully.\n", "--------------------------------------------------------------------------------\n", "\n", " 3.5 M New subscription on stream 276515289\n", " 4.5 M [next] 0.8: 42\n", " 4.6 M [cmpl] 1.0: fin\n", "\n", "\n", "========== on_error_resume_next ==========\n", "\n", "module rx.linq.observable.onerrorresumenext\n", "@extensionclassmethod(Observable)\n", "def on_error_resume_next(cls, *args):\n", " Continues an observable sequence that is terminated normally or by\n", " an exception with the next observable sequence.\n", "\n", " 1 - res = Observable.on_error_resume_next(xs, ys, zs)\n", " 2 - res = Observable.on_error_resume_next([xs, ys, zs])\n", " 3 - res = Observable.on_error_resume_next(xs, factory)\n", "\n", " Returns an observable sequence that concatenates the source sequences,\n", " even if a sequence terminates exceptionally.\n", "--------------------------------------------------------------------------------\n", "\n", " 2.2 M New subscription on stream 276515085\n", " 2.6 M [next] 0.3: running\n", " 2.8 M [next] 0.5: 0\n", " 3.0 M [next] 0.7: 1\n", " 3.1 M [next] 0.8: 2\n", " 3.2 M [next] 0.9: 3\n", " 3.3 M notify complete\n", " 3.8 M [next] 1.6: 0\n", " 4.3 M [next] 2.0: 1\n", " 4.5 M [next] 2.2: 2\n", " 4.7 M [next] 2.4: 3\n", " 5.0 M [next] 2.7: 4\n", " 5.2 M notify error\n", " 5.4 M notify complete\n", " 6.0 M [next] 3.8: all good\n", " 6.4 M [cmpl] 4.2: fin\n" ] } ], "source": [ "rst(O.catch_exception)\n", "fubar1 = O.on_error('Ups')\n", "fubar2 = O.on_error('Argh')\n", "good = O.just(42)\n", "d = subs(O.catch(fubar1, fubar2, good))\n", "\n", "\n", "rst(O.on_error_resume_next)\n", "\n", "bucket = [0]\n", "def emitter(obs):\n", " v = bucket[-1]\n", " bucket.append(v)\n", " for i in range(0, len(bucket) + 2):\n", " obs.on_next(i)\n", " if len(bucket) > 2:\n", " log('notify error')\n", " obs.on_error(\"ups\")\n", " log('notify complete')\n", " obs.on_completed()\n", " \n", " \n", " \n", "d = subs(O.on_error_resume_next(O.just('running'),\n", " O.create(emitter),\n", " O.create(emitter),\n", " O.just('all good')\n", " ))\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### ... by attempting to resubscribe to the upstream Observable **[retry](http://reactivex.io/documentation/operators/retry.html)**" ] }, { "cell_type": "code", "execution_count": 25, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== retry ==========\n", "\n", "module rx.linq.observable.retry\n", "@extensionmethod(ObservableBase)\n", "def retry(self, retry_count=None):\n", " Repeats the source observable sequence the specified number of times\n", " or until it successfully terminates. If the retry count is not\n", " specified, it retries indefinitely.\n", "\n", " 1 - retried = retry.repeat()\n", " 2 - retried = retry.repeat(42)\n", "\n", " retry_count -- [Optional] Number of times to retry the sequence. If not\n", " provided, retry the sequence indefinitely.\n", "\n", " Returns an observable sequence producing the elements of the given\n", " sequence repeatedly until it terminates successfully.\n", "--------------------------------------------------------------------------------\n", "\n", " 1.4 M New subscription on stream 276578989\n", " 1.8 M [next] 0.3: try 0.00107383728027\n", " 204.6 M error\n", " 205.8 M [next] 204.3: try 0.205071926117\n", " 408.9 M error\n", " 410.7 M [next] 409.2: try 0.409952878952\n", " 613.3 M error\n", " 614.7 M [next] 613.2: try 0.613961935043\n", " 819.0 M error\n", " 820.0 M [next] 818.4: try 0.819205999374\n", "1024.7 M error\n", "1025.2 M [next] 1023.7: try 1.02444982529\n", "1025.4 M [cmpl] 1023.9: fin\n" ] } ], "source": [ "rst(O.retry)\n", "ts = time.time()\n", "def emit(obs):\n", " dt = time.time() - ts\n", " obs.on_next('try %s' % dt)\n", " if dt < 1:\n", " sleep(0.2)\n", " log('error')\n", " obs.on_error('ups')\n", " obs.on_completed()\n", " \n", "d = subs(O.create(emit).retry(10))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# I want to create a resource that has the same lifespan as the Observable **[using](http://reactivex.io/documentation/operators/using.html)**\n", "\n", "http://www.introtorx.com/Content/v1.0.10621.0/11_AdvancedErrorHandling.html#Using:\n", "\n", "The Using factory method allows you to bind the lifetime of a resource to the lifetime of an observable sequence. The signature itself takes two factory methods; one to provide the resource and one to provide the sequence. This allows everything to be lazily evaluated.\n", "\n", "This mechanism can find varied practical applications in the hands of an imaginative developer. The resource being an IDisposable is convenient; indeed, it makes it so that many types of resources can be bound to, such as other subscriptions, stream reader/writers, database connections, user controls and, with Disposable(Action), virtually anything else." ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== using ==========\n", "\n", "module rx.linq.observable.using\n", "@extensionclassmethod(Observable)\n", "def using(cls, resource_factory, observable_factory):\n", " Constructs an observable sequence that depends on a resource object,\n", " whose lifetime is tied to the resulting observable sequence's lifetime.\n", "\n", " 1 - res = rx.Observable.using(lambda: AsyncSubject(), lambda: s: s)\n", "\n", " Keyword arguments:\n", " resource_factory -- Factory function to obtain a resource object.\n", " observable_factory -- Factory function to obtain an observable sequence\n", " that depends on the obtained resource.\n", "\n", " Returns an observable sequence whose lifetime controls the lifetime of\n", " the dependent resource object.\n", "--------------------------------------------------------------------------------\n", "\n", " 2.2 M New subscription (outer stream) on stream 276622297\n", "\n", " 2.6 M New subscription (resource fac) on stream 276622093\n", " 106.8 T127 [next] 104.1: 0 -> resource fac\n", " \n", " 213.1 T129 [next] 210.5: 1 -> resource fac\n", " \n", " 318.8 T130 [next] 316.2: 2 -> resource fac\n", " \n", " 420.2 T131 [next] 417.5: 3 -> resource fac\n", " \n", " 521.3 T132 [next] 518.7: 4 -> resource fac\n", " \n", " 622.3 T133 [next] 619.7: 5 -> resource fac\n", " \n", " 724.3 T134 [next] 721.7: 6 -> resource fac\n", " \n", " 828.5 T135 [next] 825.8: 7 -> resource fac\n", " \n", " 929.4 T136 [next] 926.7: 8 -> resource fac\n", " \n", "1031.6 T137 [next] 1028.9: 9 -> resource fac\n", " \n", "1133.8 T138 [next] 1131.1: 10 -> resource fac\n", " \n", "1239.0 T139 [next] 1236.4: 11 -> resource fac\n", " \n", "1342.8 T140 [next] 1340.1: 12 -> resource fac\n", " \n", "1446.2 T141 [next] 1443.5: 13 -> resource fac\n", " \n", "1550.1 T142 [next] 1547.5: 14 -> resource fac\n", " \n", "1655.1 T143 [next] 1652.4: 15 -> resource fac\n", " \n", "1757.7 T144 [next] 1755.0: 16 -> resource fac\n", " \n", "1858.7 T145 [next] 1856.1: 17 -> resource fac\n", " \n", "1961.2 T146 [next] 1958.5: 18 -> resource fac\n", " \n", "2005.6 T128 [next] 2003.4: -> outer stream\n", " \n", "2006.0 T128 [cmpl] 2003.7: fin -> outer stream\n", " \n" ] } ], "source": [ "rst(O.using)\n", "#d = subs(O.interval(1000).take(2))\n", "\n", "lifetime = 2000\n", "def result(disposable_resource_fac):\n", " return O.just(disposable_resource_fac).delay(lifetime)\n", "\n", "d2 = subs(O.using(lambda: subs(O.interval(100).take(1000), name='resource fac\\n'),\n", " result), name='outer stream\\n')\n" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "# I want to subscribe to an Observable and receive a Future that blocks until the Observable completes **[start, start_async, to_async](http://reactivex.io/documentation/operators/start.html)**" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== start ==========\n", "\n", "module rx.linq.observable.start\n", "@extensionclassmethod(Observable)\n", "def start(cls, func, scheduler=None):\n", " Invokes the specified function asynchronously on the specified\n", " scheduler, surfacing the result through an observable sequence.\n", "\n", " Example:\n", " res = rx.Observable.start(lambda: pprint('hello'))\n", " res = rx.Observable.start(lambda: pprint('hello'), rx.Scheduler.timeout)\n", "\n", " Keyword arguments:\n", " func -- {Function} Function to run asynchronously.\n", " scheduler -- {Scheduler} [Optional] Scheduler to run the function on. If\n", " not specified, defaults to Scheduler.timeout.\n", "\n", " Returns {Observable} An observable sequence exposing the function's\n", " result value, or an exception.\n", "\n", " Remarks:\n", " The function is called immediately, not during the subscription of the\n", " resulting sequence. Multiple subscriptions to the resulting sequence can\n", " observe the function's result.\n", "--------------------------------------------------------------------------------\n", "\n", " 2.1 M New subscription (sub1) on stream 276594277\n", " 2.6 M [next] 0.2: ('start: ', 1482383023.989834) -> sub1 \n", " 2.9 M [next] 0.6: a -> sub1 \n", " 3.1 M [next] 0.7: b -> sub1 \n", " 3.2 M [next] 0.8: c -> sub1 \n", " 3.5 M [cmpl] 1.1: fin -> sub1 \n", "\n", " 3.9 M New subscription (sub2) on stream 276594277\n", " 4.2 M [next] 0.2: ('start: ', 1482383023.989834) -> sub2 \n", " 4.6 M [next] 0.6: a -> sub2 \n", " 4.9 M [next] 1.0: b -> sub2 \n", " 5.1 M [next] 1.2: c -> sub2 \n", " 5.3 M [cmpl] 1.3: fin -> sub2 \n" ] } ], "source": [ "rst(O.start)\n", "def starter():\n", " # called only once, async:\n", " return 'start: ', time.time()\n", "s = O.start(starter).concat(O.from_('abc'))\n", "d = subs(s, name='sub1')\n", "d = subs(s, name='sub2')" ] }, { "cell_type": "code", "execution_count": 23, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== start_async ==========\n", "\n", "module rx.linq.observable.startasync\n", "@extensionclassmethod(Observable)\n", "def start_async(cls, function_async):\n", " Invokes the asynchronous function, surfacing the result through an\n", " observable sequence.\n", "\n", " Keyword arguments:\n", " :param types.FunctionType function_async: Asynchronous function which\n", " returns a Future to run.\n", "\n", " :returns: An observable sequence exposing the function's result value, or an\n", " exception.\n", " :rtype: Observable\n", "--------------------------------------------------------------------------------\n", " 1.8 M called future\n", "\n", " 2.1 M New subscription (subs1) on stream 276008249\n", " 2.9 M [err ] 0.2: FINISHED: -> subs1 \n", "\n", " 3.5 M New subscription (subs2) on stream 276008249\n", " 3.9 M [err ] 0.1: FINISHED: -> subs2 \n" ] } ], "source": [ "rst(O.start_async)\n", "\n", "def emit(obs):\n", " \n", " for ev in 'first', 'second':\n", " sleep(.2)\n", " log('emitting', ev)\n", " obs.on_next(ev)\n", " # vital for the future to get done:\n", " obs.on_completed()\n", " \n", "def future():\n", " # only called once:\n", " log('called future')\n", " future = trollius.Future()\n", " future.set_result(('42', time.time()))\n", " future.set_exception(Exception('ups'))\n", " return future\n", " \n", "try:\n", " # required for py2 (backport of guidos' tulip stuffs, now asyncio)\n", " # caution: people say this is not production ready and will never be.\n", " import trollius\n", " s = O.start_async(future)\n", " d = subs(s, name='subs1')\n", " # same result:\n", " d = subs(s, name='subs2')\n", "except Exception as ex: # notebook should always run all cells\n", " print ('%s skipping this; pip install trollius required' % ex)" ] }, { "cell_type": "code", "execution_count": 25, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== to_async ==========\n", "\n", "module rx.linq.observable.toasync\n", "@extensionclassmethod(Observable)\n", "def to_async(cls, func, scheduler=None):\n", " Converts the function into an asynchronous function. Each invocation\n", " of the resulting asynchronous function causes an invocation of the\n", " original synchronous function on the specified scheduler.\n", "\n", " Example:\n", " res = Observable.to_async(lambda x, y: x + y)(4, 3)\n", " res = Observable.to_async(lambda x, y: x + y, Scheduler.timeout)(4, 3)\n", " res = Observable.to_async(lambda x: log.debug(x),\n", " Scheduler.timeout)('hello')\n", "\n", " func -- {Function} Function to convert to an asynchronous function.\n", " scheduler -- {Scheduler} [Optional] Scheduler to run the function on. If\n", " not specified, defaults to Scheduler.timeout.\n", "\n", " Returns {Function} Asynchronous function.\n", "--------------------------------------------------------------------------------\n", "\n", " 2.9 M New subscription (9223) on stream 276564689\n", " 3.4 M [next] 0.4: 7 -> 9223 \n", " 3.6 M [cmpl] 0.5: fin -> 9223 \n" ] } ], "source": [ "rst(O.to_async)\n", "d = subs(O.to_async(lambda x, y: x + y)(4, 3) )" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 2", "language": "python", "name": "python2" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", "version": "2.7.12" } }, "nbformat": 4, "nbformat_minor": 1 } RxPY-3.2.0/notebooks/reactivex.io/Part VIII - Hot & Cold.ipynb000066400000000000000000001126321404130727200232600ustar00rootroot00000000000000{ "cells": [ { "cell_type": "code", "execution_count": 1, "metadata": { "collapsed": false }, "outputs": [], "source": [ "%run startup.py" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "collapsed": false }, "outputs": [ { "data": { "application/javascript": [ "$.getScript('./assets/js/ipython_notebook_toc.js')" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "%%javascript\n", "$.getScript('./assets/js/ipython_notebook_toc.js')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# A Decision Tree of Observable Operators\n", "\n", "## Part 8: Hot and Cold Observables\n", "\n", "> source: http://reactivex.io/documentation/operators.html#tree. \n", "> (transcribed to RxPY 1.5.7, Py2.7 / 2016-12, Gunther Klessinger, [axiros](http://www.axiros.com)) \n", "\n", "**This tree can help you find the ReactiveX Observable operator you’re looking for.** \n", "See [Part 1](./A Decision Tree of Observable Operators. Part I - Creation.ipynb) for Usage and Output Instructions. \n", "\n", "We also require acquaintance with the [marble diagrams](./Marble Diagrams.ipynb) feature of RxPy.\n", "\n", "

    Table of Contents

    \n", "
    \n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# I want an Observable that does not start emitting items to subscribers until asked [publish, publish_value, multicast, let/let_bind](http://reactivex.io/documentation/operators/publish.html)\n", "This is basically multicast." ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== publish ==========\n", "\n", "module rx.linq.observable.publish\n", "@extensionmethod(ObservableBase)\n", "def publish(self, mapper=None):\n", " Returns an observable sequence that is the result of invoking the\n", " mapper on a connectable observable sequence that shares a single\n", " subscription to the underlying sequence. This operator is a\n", " specialization of Multicast using a regular Subject.\n", "\n", " Example:\n", " res = source.publish()\n", " res = source.publish(lambda x: x)\n", "\n", " mapper -- {Function} [Optional] Selector function which can use the\n", " multicasted source sequence as many times as needed, without causing\n", " multiple subscriptions to the source sequence. Subscribers to the\n", " given source will receive all notifications of the source from the\n", " time of the subscription on.\n", "\n", " Returns an observable {Observable} sequence that contains the elements\n", " of a sequence produced by multicasting the source sequence within a\n", " mapper function.\n", "--------------------------------------------------------------------------------\n", "\n", "\n", "========== Reminder: 2 subscribers on a cold stream: ==========\n", "\n", "\n", " 0.6 M New subscription (81636) on stream 276597469\n", " 1.6 M .........EMITTING........\n", " 107.1 M [next] 106.4: 78 -> 81636 \n", " 107.4 M [cmpl] 106.7: fin -> 81636 \n", "\n", " 108.3 M New subscription (94168) on stream 276597493\n", " 108.9 M .........EMITTING........\n", " 214.3 M main thread sleeping 0.4s\n", " 314.0 T4 [next] 205.7: 93 -> 94168 \n", " 314.2 T4 [cmpl] 205.9: fin -> 94168 \n", "\n", "\n", "========== Now 2 subscribers on a PUBLISHED (hot) stream ==========\n", "\n", "\n", " 0.7 M New subscription (subs1) on stream 276598873\n", "\n", " 1.7 M New subscription (subs2) on stream 276598929\n", " 2.6 M now connect\n", " 3.2 M .........EMITTING........\n", " 105.0 M [next] 104.3: 66 -> subs1 \n", " 106.2 M [cmpl] 105.3: fin -> subs1 \n", "\n", " 107.8 M New subscription (subs3) on stream 276598873\n", " 108.2 M [cmpl] 0.3: fin -> subs3 \n" ] } ], "source": [ "rst(O.publish)\n", " \n", "def emit(obs):\n", " log('.........EMITTING........')\n", " sleep(0.1)\n", " obs.on_next(rand())\n", " obs.on_completed()\n", " \n", "rst(title='Reminder: 2 subscribers on a cold stream:') \n", "s = O.create(emit)\n", "d = subs(s), subs(s.delay(100))\n", "\n", "\n", "rst(title='Now 2 subscribers on a PUBLISHED (hot) stream', sleep=0.4) \n", "sp = s.publish()\n", "subs(sp, name='subs1')\n", "subs(sp.delay(100), name='subs2')\n", "log('now connect')\n", "# this creates a 'single, intermediate subscription between stream and subs' \n", "d = sp.connect()\n", "\n", "# will only see the finish, since subscribed too late\n", "d = subs(sp, name='subs3')\n" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== publish_value ==========\n", "\n", "module rx.linq.observable.publishvalue\n", "@extensionmethod(ObservableBase)\n", "def publish_value(self, initial_value, mapper=None):\n", " Returns an observable sequence that is the result of invoking the\n", " mapper on a connectable observable sequence that shares a single\n", " subscription to the underlying sequence and starts with initial_value.\n", "\n", " This operator is a specialization of Multicast using a BehaviorSubject.\n", "\n", " Example:\n", " res = source.publish_value(42)\n", " res = source.publish_value(42, lambda x: x.map(lambda y: y * y))\n", "\n", " Keyword arguments:\n", " initial_value -- {Mixed} Initial value received by observers upon\n", " subscription.\n", " mapper -- {Function} [Optional] Optional mapper function which can\n", " use the multicasted source sequence as many times as needed, without\n", " causing multiple subscriptions to the source sequence. Subscribers\n", " to the given source will receive immediately receive the initial\n", " value, followed by all notifications of the source from the time of\n", " the subscription on.\n", "\n", " Returns {Observable} An observable sequence that contains the elements\n", " of a sequence produced by multicasting the source sequence within a\n", " mapper function.\n", "--------------------------------------------------------------------------------\n", "Everybody gets the initial value and the events, sideeffect only once per ev\n", "\n", " 4.2 M New subscription (81753) on stream 276592833\n", " 4.4 M [next] 0.2: 42 -> 81753 \n", "\n", " 5.0 M New subscription (94051) on stream 276592873\n", " 92.0 T5 [next] 209.7: 66 -> subs2 \n", " 92.5 T5 [cmpl] 210.1: fin -> subs2 \n", " 109.9 T6 [next] 104.7: 42 -> 94051 \n", " 508.5 T7 sideffect (0,)\n", " 509.1 T7 [next] 504.9: 0 -> 81753 \n", " 611.6 T8 [next] 606.4: 0 -> 94051 \n", "1012.0 T9 sideffect (1,)\n", "1012.6 T9 [next] 1008.4: 1 -> 81753 \n", "1117.0 T10 [next] 1111.8: 1 -> 94051 \n", "1312.3 M disposing now\n" ] } ], "source": [ "rst(O.publish_value)\n", "\n", "def sideeffect(*x):\n", " log('sideffect', x)\n", "\n", "print('Everybody gets the initial value and the events, sideeffect only once per ev')\n", "src = O.interval(500).take(20).do_action(sideeffect)\n", "published = src.publish_value(42)\n", "subs(published), subs(published.delay(100))\n", "d = published.connect()\n", "sleep(1.3)\n", "log('disposing now')\n", "d.dispose()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## ... and then only emits the last item in its sequence **[publish_last](http://reactivex.io/documentation/operators/publish.html)**" ] }, { "cell_type": "code", "execution_count": 36, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# not yet in RXPy" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "## ... via **[multicast](http://reactivex.io/documentation/operators/publish.html)**" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "RxPY also has a **[multicast](http://reactivex.io/documentation/operators/publish.html)** operator which operates on an ordinary Observable, multicasts that Observable by means of a particular Subject that you specify, applies a transformative function to each emission, and then emits those transformed values as its own ordinary Observable sequence.\n", "\n", "Each subscription to this new Observable will trigger a new subscription to the underlying multicast Observable. \n", "Following the **RXJS** example at [reactive.io docu](http://reactivex.io/documentation/operators/publish.html):\n" ] }, { "cell_type": "code", "execution_count": 34, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== multicast ==========\n", "\n", "module rx.linq.observable.multicast\n", "@extensionmethod(ObservableBase)\n", "def multicast(self, subject=None, subject_factory=None, mapper=None):\n", " Multicasts the source sequence notifications through an instantiated\n", " subject into all uses of the sequence within a mapper function. Each\n", " subscription to the resulting sequence causes a separate multicast\n", " invocation, exposing the sequence resulting from the mapper function's\n", " invocation. For specializations with fixed subject types, see Publish,\n", " PublishLast, and Replay.\n", "\n", " Example:\n", " 1 - res = source.multicast(observable)\n", " 2 - res = source.multicast(subject_factory=lambda: Subject(),\n", " mapper=lambda x: x)\n", "\n", " Keyword arguments:\n", " subject_factory -- {Function} Factory function to create an\n", " intermediate subject through which the source sequence's elements\n", " will be multicast to the mapper function.\n", " subject -- Subject {Subject} to push source elements into.\n", " mapper -- {Function} [Optional] Optional mapper function which can\n", " use the multicasted source sequence subject to the policies enforced\n", " by the created subject. Specified only if subject_factory\" is a\n", " factory function.\n", "\n", " Returns an observable {Observable} sequence that contains the elements\n", " of a sequence produced by multicasting the source sequence within a\n", " mapper function.\n", "--------------------------------------------------------------------------------\n", "\n", " 2.6 M New subscription (17367) on stream 276597497\n", "we have now 3 subscriptions, only two will see values.\n", "start multicast stream (calling connect):\n", " 5.1 M emitting 10\n", " 5.2 M [next] 2.3: 10 -> 17367 \n", " 5.4 M [next] 2.4: 10 -> 17367 \n", " 6.0 M emitting 67\n", " 6.5 M [next] 3.6: 67 -> 17367 \n", " 6.8 M [next] 3.9: 67 -> 17367 \n", " 6.9 M complete\n", " 7.0 M [cmpl] 4.1: fin -> 17367 \n", " 7.1 M [cmpl] 4.2: fin -> 17367 \n" ] } ], "source": [ "rst(O.multicast)\n", "# show actions on intermediate subject:\n", "show = False\n", "\n", "def emit(obs):\n", " 'instead of range we allow some logging:'\n", " for i in (1, 2):\n", " v = rand()\n", " log('emitting', v)\n", " obs.on_next(v)\n", " log('complete')\n", " obs.on_completed()\n", " \n", "\n", "class MySubject:\n", " def __init__(self):\n", " self.rx_subj = Subject()\n", " if show:\n", " log('New Subject %s created' % self)\n", "\n", " \n", " def __str__(self):\n", " return str(hash(self))[-4:]\n", " \n", " def __getattr__(self, a):\n", " 'called at any attr. access, logging it'\n", " if not a.startswith('__') and show:\n", " log('RX called', a, 'on MySub\\n')\n", " return getattr(self.rx_subj, a)\n", " \n", " \n", "subject1 = MySubject()\n", "subject2 = MySubject()\n", "\n", "source = O.create(emit).multicast(subject2)\n", "\n", "# a \"subscription\" *is* a disposable\n", "# (the normal d we return all the time):\n", "d, observer = subs(source, return_subscriber=True)\n", "ds1 = subject1.subscribe(observer)\n", "ds2 = subject2.subscribe(observer)\n", "print ('we have now 3 subscriptions, only two will see values.')\n", "print('start multicast stream (calling connect):')\n", "connected = source.connect()\n", "d.dispose()" ] }, { "cell_type": "code", "execution_count": 58, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== let_bind ==========\n", "\n", "module rx.linq.observable.let\n", "@extensionmethod(Observable, alias=\"let\")\n", "def let_bind(self, func):\n", " Returns an observable sequence that is the result of invoking the\n", " mapper on the source sequence, without sharing subscriptions. This\n", " operator allows for a fluent style of writing queries that use the same\n", " sequence multiple times.\n", "\n", " mapper -- {Function} Selector function which can use the source\n", " sequence as many times as needed, without sharing subscriptions to\n", " the source sequence.\n", "\n", " Returns an observable {Observable} sequence that contains the elements\n", " of a sequence produced by multicasting the source sequence within a\n", " mapper function.\n", "--------------------------------------------------------------------------------\n", "\n", "\n", "========== without let ==========\n", "\n", "\n", " 1.4 M New subscription (46742) on stream 276639557\n", " 1.8 M emitting 99\n", " 2.0 M [next] 0.4: 99 -> 46742 \n", " 2.2 M complete\n", " 2.4 M emitting 47\n", " 2.6 M [next] 1.1: 47 -> 46742 \n", " 2.7 M complete\n", " 2.9 M [cmpl] 1.3: fin -> 46742 \n", "\n", " 3.5 M New subscription (16738) on stream 276625117\n", " 3.8 M emitting 62\n", " 3.9 M [next] 0.3: 62 -> 16738 \n", " 4.0 M complete\n", " 4.3 M emitting 100\n", " 4.4 M [next] 0.8: 100 -> 16738 \n", " 4.5 M complete\n", " 4.6 M [cmpl] 1.0: fin -> 16738 \n", "\n", "\n", "========== now with let ==========\n", "\n", "\n", " 5.3 M New subscription (59075) on stream 276641713\n", " 5.6 M emitting 27\n", " 5.8 M [next] 0.4: 27 -> 59075 \n", " 5.8 M complete\n", " 5.9 M emitting 86\n", " 6.1 M [next] 0.7: 86 -> 59075 \n", " 6.3 M complete\n", " 6.4 M [cmpl] 1.1: fin -> 59075 \n", "\n", " 7.4 M New subscription (59039) on stream 276625113\n", " 7.7 M emitting 26\n", " 7.9 M [next] 0.5: 26 -> 59039 \n", " 8.2 M complete\n", " 8.6 M emitting 39\n", " 8.8 M [next] 1.3: 39 -> 59039 \n", " 9.0 M complete\n", " 9.3 M [cmpl] 1.8: fin -> 59039 \n" ] } ], "source": [ "rst(O.let)\n", "# show actions on intermediate subject:\n", "show = True\n", "\n", "def emit(obs):\n", " 'instead of range we allow some logging:'\n", " v = rand()\n", " log('emitting', v)\n", " obs.on_next(v)\n", " log('complete')\n", " obs.on_completed()\n", " \n", "source = O.create(emit)\n", "\n", "# following the RXJS example:\n", "header(\"without let\")\n", "d = subs(source.concat(source))\n", "d = subs(source.concat(source))\n", "\n", "header(\"now with let\")\n", "d = subs(source.let(lambda o: o.concat(o)))\n", "d = subs(source.let(lambda o: o.concat(o)))\n", "# TODO: Not understood:\n", "# \"This operator allows for a fluent style of writing queries that use the same sequence multiple times.\"\n", "# ... I can't verify this, the source sequence is not duplicated but called every time like a cold obs." ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "## ... and then emits the complete sequence, even to those who subscribe after the sequence has begun **[replay](http://reactivex.io/documentation/operators/replay.html)**\n", "\n", "A connectable Observable resembles an ordinary Observable, except that it does not begin emitting items when it is subscribed to, but only when the Connect operator is applied to it. In this way you can prompt an Observable to begin emitting items at a time of your choosing." ] }, { "cell_type": "code", "execution_count": 39, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== replay ==========\n", "\n", "module rx.linq.observable.replay\n", "@extensionmethod(ObservableBase)\n", "def replay(self, mapper, buffer_size=None, window=None, scheduler=None):\n", " Returns an observable sequence that is the result of invoking the\n", " mapper on a connectable observable sequence that shares a single\n", " subscription to the underlying sequence replaying notifications subject\n", " to a maximum time length for the replay buffer.\n", "\n", " This operator is a specialization of Multicast using a ReplaySubject.\n", "\n", " Example:\n", " res = source.replay(buffer_size=3)\n", " res = source.replay(buffer_size=3, window=500)\n", " res = source.replay(None, 3, 500, scheduler)\n", " res = source.replay(lambda x: x.take(6).repeat(), 3, 500, scheduler)\n", "\n", " Keyword arguments:\n", " mapper -- [Optional] Selector function which can use the multicasted\n", " source sequence as many times as needed, without causing multiple\n", " subscriptions to the source sequence. Subscribers to the given\n", " source will receive all the notifications of the source subject to\n", " the specified replay buffer trimming policy.\n", " buffer_size -- [Optional] Maximum element count of the replay buffer.\n", " window -- [Optional] Maximum time length of the replay buffer.\n", " scheduler -- [Optional] Scheduler where connected observers within the\n", " mapper function will be invoked on.\n", "\n", " Returns {Observable} An observable sequence that contains the elements\n", " of a sequence produced by multicasting the source sequence within a\n", " mapper function.\n", "--------------------------------------------------------------------------------\n", "\n", "\n", "========== playing and replaying... ==========\n", "\n", "\n", " 2.8 M New subscription (Replay Subs 1) on stream 276680345\n", " 3.6 M modified_stream (take 2)\n", " 4.4 M calling connect now...\n", " 5.3 M emitting nr 0, value 229 \n", "\n", " 5.5 M sync sideeffect (0.2s) ('nr 0, value 229',) \n", "\n", " 207.6 M end sideeffect ('nr 0, value 229',) \n", "\n", " 409.5 M emitting nr 1, value 981 \n", "\n", " 409.9 M sync sideeffect (0.2s) ('nr 1, value 981',) \n", "\n", " 612.9 M end sideeffect ('nr 1, value 981',) \n", "\n", " 818.6 M emitting nr 2, value 773 \n", "\n", " 819.5 M sync sideeffect (0.2s) ('nr 2, value 773',) \n", "\n", "1025.2 M end sideeffect ('nr 2, value 773',) \n", "\n", "1228.8 M emitting nr 3, value 711 \n", "\n", "1429.7 M emitting nr 4, value 427 \n", "\n", "1635.6 M [next] 1632.7: MODIFIED FOR REPLAY: nr 0, value 229 -> Replay Subs 1\n", "\n", "1635.9 M [next] 1633.1: MODIFIED FOR REPLAY: nr 1, value 981 -> Replay Subs 1\n", "\n", "1636.3 M [cmpl] 1633.5: fin -> Replay Subs 1\n", "\n" ] } ], "source": [ "rst(O.replay)\n", "\n", "def emit(obs):\n", " 'continuous emission'\n", " for i in range(0, 5):\n", " v = 'nr %s, value %s' % (i, rand())\n", " log('emitting', v, '\\n')\n", " obs.on_next(v)\n", " sleep(0.2) \n", " \n", "\n", "def sideeffect(*v):\n", " log(\"sync sideeffect (0.2s)\", v, '\\n')\n", " sleep(0.2)\n", " log(\"end sideeffect\", v, '\\n')\n", " \n", "\n", "def modified_stream(o):\n", " log('modified_stream (take 2)')\n", " return o.map(lambda x: 'MODIFIED FOR REPLAY: %s' % x).take(2)\n", "\n", "header(\"playing and replaying...\")\n", "subject = Subject()\n", "cold = O.create(emit).take(3).do_action(sideeffect)\n", "\n", "assert not getattr(cold, 'connect', None)\n", "hot = cold.multicast(subject)\n", "connect = hot.connect # present now.\n", "\n", "#d, observer = subs(hot, return_subscriber=True, name='normal subscriber\\n')\n", "#d1 = subject.subscribe(observer)\n", "\n", "published = hot.replay(modified_stream, 1000, 50000)\n", "d2 = subs(published, name='Replay Subs 1\\n')\n", "\n", "\n", "#header(\"replaying again\")\n", "#d = subs(published, name='Replay Subs 2\\n')\n", "log('calling connect now...')\n", "d3 = hot.connect()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If you apply the Replay operator to an Observable\n", "\n", "- **before** you convert it into a connectable Observable,\n", "- the resulting connectable Observable will always emit the same complete sequence to any future observers,\n", "- even those observers that subscribe after the connectable Observable has begun to emit items to other subscribed observers(!)" ] }, { "cell_type": "code", "execution_count": 23, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== True ==========\n", "\n", "\n", " 0.5 M New subscription (Normal) on stream 276498261\n", "\n", " 1.8 M New subscription (Replayer A) on stream 276487297\n", "\n", " 4.3 M New subscription (Replayer B) on stream 276487297\n", " 105.8 T160 sideeffect 0\n", "\n", " 106.5 T162 sideeffect 0\n", " 106.1 T161 sideeffect 0\n", " 106.8 T160 [next] 106.2: 0 -> Normal\n", "\n", "\n", "\n", " 108.4 T162 [next] 104.1: marked 0 -> Replayer B\n", " 108.1 T161 [next] 105.7: marked 0 -> Replayer A\n", "\n", "\n", " 214.5 T164 sideeffect 1\n", " 214.8 T165 sideeffect 1\n", " 215.2 T163 sideeffect 1\n", "\n", "\n", "\n", " 215.9 T164 [next] 215.3: 1 -> Normal\n", " 215.9 T165 [next] 213.5: marked 1 -> Replayer A\n", " 216.7 T163 [next] 212.4: marked 1 -> Replayer B\n", "\n", "\n", "\n", " 318.1 T166 sideeffect 2\n", "\n", " 318.7 T166 [next] 316.3: marked 2 -> Replayer A\n", "\n", " 320.0 T166 [next] 317.7: marked 0 -> Replayer A\n", "\n", " 320.7 T166 [next] 318.4: marked 1 -> Replayer A\n", "\n", " 321.4 T166 [next] 319.0: marked 2 -> Replayer A\n", "\n", " 321.9 T166 [cmpl] 319.5: fin -> Replayer A\n", "\n", " 323.3 T167 sideeffect 2\n", " 323.6 T168 sideeffect 2\n", "\n", "\n", " 325.0 T168 [next] 320.7: marked 2 -> Replayer B\n", " 323.6 T167 [next] 323.0: 2 -> Normal\n", "\n", "\n", " 326.4 T168 [next] 322.1: marked 0 -> Replayer B\n", " 324.4 T167 [cmpl] 323.8: fin -> Normal\n", "\n", "\n", " 327.0 T168 [next] 322.7: marked 1 -> Replayer B\n", "\n", " 327.4 T168 [next] 323.1: marked 2 -> Replayer B\n", "\n", " 327.5 T168 [cmpl] 323.2: fin -> Replayer B\n", "\n", "\n", "\n", "========== now with publish - no more sideeffects in the replays ==========\n", "\n", "\n", "\n", "========== True ==========\n", "\n", "\n", " 0.2 M New subscription (Normal) on stream 276784605\n", "\n", " 0.7 M New subscription (Replayer A) on stream 276784601\n", "\n", " 2.9 M New subscription (Replayer B) on stream 276784601\n", " 109.3 T172 sideeffect 0\n", "\n", " 109.9 T172 [next] 109.6: 0 -> Normal\n", "\n", " 110.6 T172 [next] 109.7: marked 0 -> Replayer A\n", "\n", " 111.5 T172 [next] 108.5: marked 0 -> Replayer B\n", "\n", " 214.4 T173 sideeffect 1\n", "\n", " 214.7 T173 [next] 214.4: 1 -> Normal\n", "\n", " 215.1 T173 [next] 214.1: marked 1 -> Replayer A\n", "\n", " 215.4 T173 [next] 212.4: marked 1 -> Replayer B\n", "\n", " 321.3 T174 sideeffect 2\n", "\n", " 321.6 T174 [next] 321.3: 2 -> Normal\n", "\n", " 322.0 T174 [next] 321.0: marked 2 -> Replayer A\n", "\n", " 322.9 T174 [next] 321.9: marked 0 -> Replayer A\n", "\n", " 323.3 T174 [next] 322.3: marked 1 -> Replayer A\n", "\n", " 323.8 T174 [next] 322.9: marked 2 -> Replayer A\n", "\n", " 323.9 T174 [cmpl] 322.9: fin -> Replayer A\n", "\n", " 325.0 T174 [next] 322.0: marked 2 -> Replayer B\n", "\n", " 326.5 T174 [next] 323.5: marked 0 -> Replayer B\n", "\n", " 327.0 T174 [next] 324.0: marked 1 -> Replayer B\n", "\n", " 327.1 T174 [next] 324.1: marked 2 -> Replayer B\n", "\n", " 327.2 T174 [cmpl] 324.2: fin -> Replayer B\n", "\n", " 327.9 T174 [cmpl] 327.7: fin -> Normal\n", "\n" ] } ], "source": [ "\n", "def mark(x):\n", " return 'marked %x' % x\n", "def side_effect(x):\n", " log('sideeffect %s\\n' % x)\n", " \n", " \n", "for i in 1, 2:\n", " s = O.interval(100).take(3).do_action(side_effect)\n", " if i == 2:\n", " sleep(1)\n", " header(\"now with publish - no more sideeffects in the replays\")\n", " s = s.publish()\n", " \n", " reset_start_time()\n", " published = s.replay(lambda o: o.map(mark).take(3).repeat(2), 3)\n", " \n", " d = subs(s, name='Normal\\n')\n", " d = subs(published, name='Replayer A\\n')\n", " d = subs(published, name='Replayer B\\n')\n", " if i == 2:\n", " d = s.connect()" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "## ... but I want it to go away once all of its subscribers unsubscribe **[ref_count, share](http://reactivex.io/documentation/operators/refcount.html)**\n", "\n", "A connectable Observable resembles an ordinary Observable, except that it does not begin emitting items when it is subscribed to, but only when the Connect operator is applied to it. In this way you can prompt an Observable to begin emitting items at a time of your choosing.\n", "\n", "The RefCount operator automates the process of connecting to and disconnecting from a connectable Observable. It operates on a connectable Observable and returns an ordinary Observable. When the first observer subscribes to this Observable, RefCount connects to the underlying connectable Observable. RefCount then keeps track of how many other observers subscribe to it and does not disconnect from the underlying connectable Observable until the last observer has done so." ] }, { "cell_type": "code", "execution_count": 33, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== publish ==========\n", "\n", "module rx.linq.observable.publish\n", "@extensionmethod(ObservableBase)\n", "def publish(self, mapper=None):\n", " Returns an observable sequence that is the result of invoking the\n", " mapper on a connectable observable sequence that shares a single\n", " subscription to the underlying sequence. This operator is a\n", " specialization of Multicast using a regular Subject.\n", "\n", " Example:\n", " res = source.publish()\n", " res = source.publish(lambda x: x)\n", "\n", " mapper -- {Function} [Optional] Selector function which can use the\n", " multicasted source sequence as many times as needed, without causing\n", " multiple subscriptions to the source sequence. Subscribers to the\n", " given source will receive all notifications of the source from the\n", " time of the subscription on.\n", "\n", " Returns an observable {Observable} sequence that contains the elements\n", " of a sequence produced by multicasting the source sequence within a\n", " mapper function.\n", "--------------------------------------------------------------------------------\n", "\n", " 2.2 M New subscription (00656) on stream 276501981\n", "\n", " 3.8 M New subscription (75193) on stream 276501981\n" ] }, { "data": { "text/plain": [ "" ] }, "execution_count": 33, "metadata": {}, "output_type": "execute_result" }, { "name": "stdout", "output_type": "stream", "text": [ "1006.1 T182 [next] 1003.8: 0 -> 00656\n", "1006.6 T182 [next] 1002.8: 0 -> 75193\n", "2009.3 T183 [next] 2007.0: 1 -> 00656\n", "2010.0 T183 [next] 2006.1: 1 -> 75193\n", "2010.4 T183 [cmpl] 2008.1: fin -> 00656\n", "2010.6 T183 [cmpl] 2006.8: fin -> 75193\n" ] } ], "source": [ "rst(O.interval(1).publish)\n", "publ = O.interval(1000).take(2).publish().ref_count()\n", "# be aware about potential race conditions here\n", "subs(publ)\n", "subs(publ)" ] }, { "cell_type": "code", "execution_count": 41, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== share ==========\n", "\n", "module rx.linq.observable.publish\n", "@extensionmethod(ObservableBase)\n", "def share(self):\n", " Share a single subscription among multple observers.\n", "\n", " Returns a new Observable that multicasts (shares) the original\n", " Observable. As long as there is at least one Subscriber this\n", " Observable will be subscribed and emitting data. When all\n", " subscribers have unsubscribed it will unsubscribe from the source\n", " Observable.\n", "\n", " This is an alias for Observable.publish().ref_count().\n", "--------------------------------------------------------------------------------\n", "\n", " 1.3 M New subscription (SourceA) on stream 276514157\n", "\n", " 2.8 M New subscription (SourceB) on stream 276514157\n" ] }, { "data": { "text/plain": [ "" ] }, "execution_count": 41, "metadata": {}, "output_type": "execute_result" }, { "name": "stdout", "output_type": "stream", "text": [ " 206.9 T190 sideeffect 0\n", " 207.4 T190 [next] 205.9: 0 -> SourceA\n", " 207.5 T190 [next] 204.5: 0 -> SourceB\n", " 410.9 T191 sideeffect 1\n", " 411.2 T191 [next] 409.7: 1 -> SourceA\n", " 411.3 T191 [next] 408.2: 1 -> SourceB\n", " 411.4 T191 [cmpl] 409.8: fin -> SourceA\n", " 411.6 T191 [cmpl] 408.5: fin -> SourceB\n" ] } ], "source": [ "rst(O.interval(1).share)\n", "def sideffect(v):\n", " log('sideeffect %s\\n' % v)\n", "publ = O.interval(200).take(2).do_action(sideeffect).share()\n", "\n", "'''\n", "When the number of observers subscribed to published observable goes from\n", "0 to 1, we connect to the underlying observable sequence.\n", "published.subscribe(createObserver('SourceA'));\n", "When the second subscriber is added, no additional subscriptions are added to the\n", "underlying observable sequence. As a result the operations that result in side\n", "effects are not repeated per subscriber.\n", "\n", "'''\n", "subs(publ, name='SourceA')\n", "subs(publ, name='SourceB')" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "## ... and then I want to ask it to start **[connect](http://reactivex.io/documentation/operators/connect.html)**\n", "\n", "You can use the publish operator to convert an ordinary Observable into a ConnectableObservable.\n", "\n", "Call a ConnectableObservable’s connect method to instruct it to begin emitting the items from its underlying Observable to its Subscribers.\n", "\n", "\n", "\n", "\n", "The connect method returns a Disposable. You can call that Disposable object’s dispose method to instruct the Observable to stop emitting items to its Subscribers.\n", "\n", "You can also use the connect method to instruct an Observable to begin emitting items (or, to begin generating items that would be emitted) even before any Subscriber has subscribed to it.\n", "\n", "**In this way you can turn a cold Observable into a hot one.**\n", "\n" ] }, { "cell_type": "code", "execution_count": 62, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "========== connect ==========\n", "\n", "module rx.linq.connectableobservable\n", "def connect(self):\n", " Connects the observable.\n", "--------------------------------------------------------------------------------\n", "\n", " 507.1 M New subscription (14637) on stream 276493689\n", " 517.2 T193 [next] 9.9: 5 -> 14637\n", " 622.8 T193 [next] 115.6: 6 -> 14637\n", " 724.9 T193 [next] 217.7: 7 -> 14637\n", " 826.5 T193 [next] 319.3: 8 -> 14637\n", " 931.7 T193 [next] 424.5: 9 -> 14637\n" ] } ], "source": [ "rst(O.interval(1).publish().connect)\n", "published = O.create(emit).publish()\n", "\n", "def emit(obs):\n", " for i in range(0, 10):\n", " log('emitting', i, obs.__class__.__name__, hash(obs))\n", " # going nowhere\n", " obs.on_next(i)\n", " sleep(0.1)\n", "\n", "import thread\n", "thread.start_new_thread(published.connect, ())\n", "sleep(0.5)\n", "d = subs(published, scheduler=new_thread_scheduler)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 2", "language": "python", "name": "python2" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", "version": "2.7.12" } }, "nbformat": 4, "nbformat_minor": 1 } RxPY-3.2.0/notebooks/reactivex.io/RxPy - Basic Usage and Mechanics.ipynb000066400000000000000000000132641404130727200254760ustar00rootroot00000000000000{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Extensionmethods\n", "Not all operators are loaded at import of rx." ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "error: type object 'Observable' has no attribute 'from_marbles'\n" ] } ], "source": [ "# Example: from_marbles\n", "import rx\n", "try:\n", " rx.Observable.from_marbles('a-b|')\n", "except Exception as ex:\n", " print 'error:', ex # shown only after ipython notebook kernel restart" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "a\n", "b\n", "c\n" ] } ], "source": [ "# -> to see whats there don't use e.g. `dir(Observable)` but find\n", "# 'def from_marbles' in the rx directory, to see the module,\n", "# then import it:\n", "'''\n", "~/GitHub/RxPY/rx $ ack 'def from_marbl'\n", "testing/marbles.py\n", "90:def from_marbles(self, scheduler=None):\n", "'''\n", "import rx.testing.marbles\n", "def show(x): print (x)\n", "stream = rx.Observable.from_marbles('a-b--c|').to_blocking().subscribe(show)" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "# Async Operations\n", "It is useful to understand on a high level how RxPY handles asyncronity and when.\n", "E.g. naively you might want to know, when notifying a value to a subscriber, what other subscribers are present. \n", "This makes no sense to ask (I think in general in reactive programming) and it will be clear looking at an example. \n", "\n", "Consider timing and thread outputs in the following:" ] }, { "cell_type": "code", "execution_count": 38, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1482356523.29513192 - AutoDetachObserver 275507917 - MainThread\n", "\n", "1482356523.29556108 - Observer 1 1 - MainThread\n", "\n", "1482356523.29563403 - Observer 1 1 - MainThread\n", "\n", "1482356523.29568505 - Observer 1 1 - MainThread\n", "\n", "1482356523.29573011 - Observer 1 1 - MainThread\n", "\n", "1482356523.29649496 - AutoDetachObserver 275507817 - MainThread\n", "\n", "1482356523.29657793 - Observer 2 1 - MainThread\n", "\n", "1482356523.29663706 - Observer 2 1 - MainThread\n", "\n", "1482356523.29668999 - Observer 2 1 - MainThread\n", "\n", "1482356523.29674101 - Observer 2 1 - MainThread\n", "\n" ] } ], "source": [ "# =============================\n", "# change these (both in millis)\n", "delay_stream, slow_emit = 0, 0\n", "# =============================\n", "\n", "import rx, threading, random, time\n", "thread = threading.currentThread\n", "\n", "\n", "def call_observer(obs):\n", " '''observer functions are invoked, blocking'''\n", " print_out(obs.__class__.__name__, hash(obs))\n", " for i in range(2):\n", " obs.on_next(1)\n", " if slow_emit:\n", " time.sleep(slow_emit/1000)\n", " obs.on_next(1)\n", " \n", "stream = rx.Observable.create(call_observer).take(10)\n", "if delay_stream:\n", " stream = stream.delay(delay_stream)\n", "\n", "def print_out(*v):\n", " '''printout of current time, v, and current thread'''\n", " v_pretty = ' '.join([str(s) for s in v])\n", " print ('%.8f - %30s - %s\\n' % (time.time(), v_pretty, thread().getName()))\n", " \n", " \n", "d = stream.subscribe(lambda x: print_out('Observer 1', x))\n", "d = stream.subscribe(lambda x: print_out('Observer 2', x))" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "- As long as there is no time related stream operator involved, then RXPy does everything *syncrononous*. \n", "- RXPy goes async only when it has to, according to the nature of the async operation declared by the user.\n", "- It defaults to reasonable mechanics, e.g. using threading.\n", "- You can overwrite these defaults, by picking a \"scheduler\" (e.g. gevent, e.g. twisted, e.g. futures)\n", "\n", "> => In the `call_observer` function you can't know about the concurrency situation\n", "> It soleley depends on the design of the stream operations applied.\n", "> See `.ref_count()` though, for published streams\n", "\n", "Check the [`.observe_on`](./Part%20VII%20-%20Meta%20Operations.ipynb#...when-it-notifies-observers-observe_on) example to get a deeper understanding how scheduling works.\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 2", "language": "python", "name": "python2" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", "version": "2.7.12" } }, "nbformat": 4, "nbformat_minor": 1 } RxPY-3.2.0/notebooks/reactivex.io/assets/000077500000000000000000000000001404130727200202635ustar00rootroot00000000000000RxPY-3.2.0/notebooks/reactivex.io/assets/Article about Publish - Refcount.ipynb000066400000000000000000000156261404130727200272700ustar00rootroot00000000000000{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "*saved from internet archive:https://web.archive.org/web/20160402152249/http://nerds.weddingpartyapp.com/tech/2015/01/21/rxjava-share-publish-refcount-and-all-that-jazz/*:\n", "\n", "\n", "Ok, so in my previous post I innocuously introduced the `.share()` operator.\n", "\n", "```java\n", "Observable tapEventEmitter = _rxBus.toObserverable().share();\n", "```\n", "\n", "# What is this share operator?\n", "The `.share()` operator is basically just a wrapper to the chained call `.publish().refcount()`.\n", "\n", "You’ll find the chained combo `.publish().refcount()` used in quite a few Rx examples on the web. It allows you to “share” the emission of the stream. Considering how powerpacked and frequently used this combo is, RxJava basically introduced the friendlier more useful operator share(). This mechanism of using observables is sometimes referred to as “multicasting”.\n", "\n", "Let’s dig into some of the basics first:\n", "\n", "> “ConnectedObservable” – This is a kind of observable which doesn’t emit items even if subscribed to. It only starts emitting items after its .connect() method is called.\n", "\n", "It is for this reason that a connected obesrvable is also considered **“cold”** or “inactive” before the connect method is invoked.\n", "\n", "> `.publish()`– This method allows us to change an ordinary observable into a “ConnectedObservable”. Simply call this method on an ordinary observable and it becomes a connected one.\n", "\n", "We now know what ½ of the operator `share` does. Now why would you ever use a Connected Observable? The docs say:\n", "\n", "> In this way you can wait for all intended Subscribers to subscribe to the Observable before the Observable begins emitting items.\n", "\n", "This essentially means that a regular usecase for `publish` would involve more than one subscriber. When you have more than one subscriber, it can get tricky to handle each of the subscriptions and dispose them off correctly. To make this process easier, Rx introduced this magical operator called `refcount()`:\n", "\n", "\n", "> `refcount()` – This operator keeps track of how many subscribers are subscribed to the resulting Observable and refrains from disconnecting from the source ConnectedObservable until all such Observables are unsubscribed.\n", "\n", "It essentially maintains a reference counter in the background and accordingly takes the correct action when a subscription needs to be unsubscribed or disposed off. This is the second ½ of the operator share. You are now armed with knowledge of what each of those terms mean.\n", "\n", "\n", "Let’s look at the example from debouncedBuffer again and see how share was used there:\n", "\n", "\n", "```\n", "Observable tapEventEmitter = _rxBus.toObserverable().share();\n", "// which is really the same as:\n", "Observable tapEventEmitter = _rxBus.toObserverable().publish().refcount();\n", "```\n", "\n", "We now have a “shareable” observable called “tapEventEmitter” and because it’s sharable and still not yet ‘live’ (publish from the share call changes it to a ConnectedObservable), we can use it to compose our niftier Observables and rest assured that we always have a reference to the original observable (the original observable being `_rxBus.toObserverable()` in this case).\n", "\n", "\n", "```\n", "Observable tapEventEmitter = _rxBus.toObserverable().share();\n", "Observable debouncedEventEmitter = tapEventEmitter.debounce(1, TimeUnit.SECONDS);\n", "tapEventEmitter.buffer(debouncedEventEmitter)\n", "//...\n", "```\n", "\n", "All this sounds good. There is however a possible **race condition** with this implementation (which Ben [pointed out](https://gist.github.com/benjchristensen/e4524a308456f3c21c0b#comment-1367814) through a comment on this gist).\n", "\n", "The race condition occurs because there are two subscribers here (debounce and buffer) and they may come and go at different points. Remember that the RxBus is backed by a hot/live Subject which is constantly emitting items. By using the share operator we guarantee a reference to the same source, but NOT that they’ll receive the exact same items if the subscribers enter at different points of time. Ben explains this well:\n", "\n", "\n", "> The race condition is when the two consumers subscribe. Often on a hot stream it doesn’t matter when subscribers come and go, and refCount is perfect for that. The race condition refCount protects against is having only 1 active subscription upstream. However, if 2 Subscribers subscribe to a refcounted stream that emits 1, 2, 3, 4, 5, the first may get 1, 2, 3, 4, 5 and the second may get 2, 3, 4, 5.\n", "> To ensure all subscribers start at exactly the same time and get the exact same values, refCount can not be used. Either ConnectableObservable with a manual, imperative invocation of connect needs to be done, or the variant of publish(function) which connects everything within the function before connecting the upstream.\n", "\n", "\n", "In our usage it’s almost immediate so it probably wouldn’t matter a whole lot. But our original intention was to have the debouncedBuffer function as a single operator. It seems conceptually incorrect if the same events are not emitted. I added a third improved implementation to handle this race condition using Ben’s latter suggestion:\n", "\n", "```\n", "// don't start emitting items just yet by turning the observable to a connected one\n", "ConnectableObservable tapEventEmitter = _rxBus.toObserverable().publish();\n", "\n", "tapEventEmitter.publish((Func1) (stream) -> {\n", "\n", " // inside `publish`, \"stream\" is truly multicasted\n", "\n", " // applying the same technique for getting a debounced buffer sequence\n", " return stream.buffer(stream.debounce(1, TimeUnit.SECONDS));\n", "\n", "}).subscribe((Action1) (taps) {\n", " _showTapCount(taps.size());\n", "});\n", "\n", "// start listening to events now\n", "tapEventEmitter.connect();\n", "```\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 2", "language": "python", "name": "python2" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", "version": "2.7.12" } }, "nbformat": 4, "nbformat_minor": 1 } RxPY-3.2.0/notebooks/reactivex.io/assets/img/000077500000000000000000000000001404130727200210375ustar00rootroot00000000000000RxPY-3.2.0/notebooks/reactivex.io/assets/img/publishConnect.png000066400000000000000000007373711404130727200245470ustar00rootroot00000000000000PNG  IHDRdsRGB pHYs%%IR$iTXtXML:com.adobe.xmp 5 2 1 2@IDATx eWQkzNNwBK dÒ( dH ,ʢ( 3n": ,  a!K%^թSguϭNs@Z -H i@Z -H i@Z -H i@Z -H i@Z -H i@Z -H i@Z -H i@Z -H i@Z -H i@Z -H i@Z -H i@Z -H i@Z -H i@Z -H i@Z -H i@Z -H i@Z -H iagi@Z6Z!iZ -H i@Z -H ,| L[-##H Zw"0}W/ͣH g g'H &Ƀکl<-H oy*v0h3LS2]՗y xjzDq3L|Z --G67]?˧i@Z`X`_N栲#i@Z})+6Θg i0_hmx,YqDV9p\ d&Jۄ')DŽދp/NK54`ZRH  }÷BiBSů-(e{ ?/[BqZ*}0EXfZH iydOKٕ@Z -pYxePt,7^"ZUSX8[v,i2O^(Y}oz|!7oJPR! d7龂;T? \gG*‹o(y@!-L g6iivy^$}V¿,_6i(MV~i@Z`XePt>K`|;,#bGvsy4%DI-zc]5Ex[ȑL#7 C-z\A[\^H _ L?hC rL4Uƒ [_ (dǻ/S"eg#~3-H b@Z -pXY~DJG -<ܝP)nzbE0.&p'4[y1c g nR5?&|er%,!-طV!ҭ1HCY*dg /C(c_$iA'ޑ@Z -8 ,0iqE{) /)8j 3O>;uݑ+Vt/V/[֭X۴iSwmu~Go>yӗtS|fv}*B^AxmKC)?ZyK?qEBG*%t^-!I 'CR Q xmg~!|)+FHH icOl5-H .H{% _#-P%w}5tء~;X`_%nAڴoԔ}閯uwEb/{ _s)5>X" 8XɁ pARv|4 +{ yZw5Wt^~aw+.V'mt2QOٶnxo޹?}>^;?maG]!\'r[ X28jǕi;a !}~pp`ڣ]E_~WvBPD߮SM70u{O~}臊#!&Hp޾~>!-H ^4@Z -pZ>]$ jEyG[2_&|pp1tzS>q)7+;ȋjȫ>8ȷr#]ݯuw ,~^#6t9 #P/Z^i1S/O\xĞXNGv3%7;;[z%]~ )اsZ"WӡEpFҠ%Xx۷n>m>v;|wH CK|){ P<9i7_ZFnaŊCQ{/^eb UNidS[ ۖdmٺ>R0->V[Dq]B ! r9!?i@ZY9!-H _5e,cqt{ w _`=_߭\ˆ Zgj"R;zY -Z%yu4 Z?]F@%ۻ7hG/)w(H{' Yy/E|Hjyem pƖǕ)# .{ ]Ww-o|94/@t{5%՛n>}KlM{e ;2H'-0?hg@?hs22[xb 'gtkh1WT@^tT堭[<"tt>tw_m\|y!@&-`3kghLvcinevZ -H PHPdҰ0/oa扛GydS/xAn^xxuU<X9zu`_``L+N^&}#G^BPaf;yݱמou[>pr,|'v w3V(i6F 2|9 & y7=nٲS͞,T(']S0T8R^rҨ4.u! y}|-TdX%1?n?JM4eMH h{ؿX  cTהy2ao_H!i@Z`X_:L· $dL8 c_ݙoԲJ*VEcUM+%ĊՋWCm;hѫX IՑW~iwOfn]|B+oЄ@Z`\-_L/췄 'š5GuC/9T{?5|2(f<7)Oi޸NOm$ri18Ս-,Zû<߭z~xy3O`MH mjb{ UŸHxB].oeF~a OZ -H EÔ/1 ~ #x4/NeZ/~Ǟq` I#j=Xx^r;t[u{/kЮvjo#Z/mx^)YjR+c7у(SceO:pFY1x(7Y Ͼ~ew."ЧLOD-\_^jKo.&+y%g @a$ۮgicw>T xMw5kn- >O$&]޷|xj7%7 kZsN3}oyww=9~ vڱ=vkvDzV])o '?֏wn#."9ynJW2_y y=G o}}~˻ ao_h!i@Z.X_>%jD"@ ?]8֬\ٽ5*b,s^iV#xdwG zDP &,U/Uyȫzݲ1 N[׽S{Fs업 y>m%mIӚ[$}( q&v|=V '#wԽ_-?|\ E놆EÂS^,*U.;`K T݃uЉhDʖg]ۭ{uIo*r : Rp#-pYׁ}>}YI-ӽ7ם|v; OA nTaWB +_kZno_ m8(6f9x CCM[T2!-pZ)D;:O}(S6œ gwJY6-H g FY 9{ﯽbTW4 ⾦$Ȫb ҉:kE%2XbhuD@TA}G}ԱGv:o}wtd IA@X%lѼ[0@lum5>Tg|U&!|;V_>@X{㗀D}W(rȑyg<~xo93R-*A@j͒EE.5vz{xT 0FfkSYjOH nN;?oo u%Y/|K 6*D<ӕ^*SZLkU5mS"kT^BN]C2t\FȇFRFA/seNe ooӖ's ؆[3Mg{{m_ {]0 i@Xʗ(iqq^'=pn׾Uă Ū,Zx_Y jHV yukWGPWATTr* MutXC*lYѽLHׄlr[ھ!>&< _S"pw{{\rQ4lq9 N(s4$HH"gSJ_Ԡ!#NM) bgtK^^7>o*pk"6^f(6.8 -o_VM 1XxIҗ{S_P iSSLaB&Ra/dDg}x0]B tJec*7CcN|\wʽ!_5lghBZPW;Ii_t QO=[Hi[6oL}N8/Fc~+s^`NBBZ -H 9 xz&,LQ8Ǯ]/|wz1 G+VGʃ +p˖^!\Jqe\$z|ۏ> 'GmZC/yGwkB'Kg؍ _팜F nj b< zQӟN8u'Sc}x;"0A @⿹Jz1SrjN([QdgVr(Sn3Fz-qTsn*r.b?6z<{ae$ ǽ(sZ{p\[QdWw]vm?mx`ӐKLML L1-n*/~ߒ>+BOezՆKIb/>:5EYQG%EWhucGzڋ#R4{߁wvܘ?>^/ }n<8z[ 0~BZ -H <R'0|B :"~E/p*(VB50 Xixjȫ$"tDiW7y7 SEs=P 1JB xi#zi']"p"a;c_z0"l}7c[[\O:eG,d e{٧?NMq o[ oK7JJSyT?t`| VdS^:Ee>n//w?&e3윴-1ՎL9im=>|]!IDx ?=QϘ~_])zAS!c:R^L}ok=q3H1kU+r\<-ruˎ|XbF*C.Ke)]؄!cv|{ۿp\] < mhޔݟ"|7BWΓ}xy(iӖ= w8!-H L=1 ?n1x=;Y(e*p?xBIYa VMʏU+0dBԳS~dXnݬ-A{'([{.}CɿW < Ah##rcqZ!6.|hQxfq"UIz;nO狿AШz;gf#M;&rB>SGsmQw YF=%mRxij#4'9z9<;?VjǔeI=`P*/z|UOrFu3#z!itLqԡ+q'<*<]Q1)?V{ǠvTO[)h*#`R:b J&ѣ™#%_y_!`Ko#=|/L&--7cM[4߁'uBy(eZd@{-Z;!nx9 3,|v!uQ[5:>g⩞H ; x1%@ _XO$+]q9˾hBЌ Ջxj#VuʷuT9@g3=YۋSҔk}=bjj&,m,9EڇN^ CB~ADTo$8,Xg\r 8yOR Z{cW}:Cy/~H-aܵ.i>q{*i@Z -0@;1'5 3(cW~9/|ƂUW^"¤WG.7L[QWWa*P2Ӓ1 U!Hvc%򊬀~[JG>R]/ Bɖ'=6e3p}>&p즬*/t {R9?R(g!Bi-cU!!V^5GޜV{@J{^=%WL'UC^^? !h[\p:D%mm_llt8T-Kд\ }}@Nۖ.={q>UAȗ;N]15ɵ+oJTӉ\\פ6-J! S* V> fyT!2>x~,KzpAFU>Q iCqxjk'@:B)?&ZvٞPPB"Ӝ۶;-?'a,oS%-H h'='!xO~TV8|gaeǫOajHy^PS/W9(;irʴGb~КDs Vg3%wB糄|I𗂖|NeK/jeeURH\Sp7LOy=kH}N3nB$~_ c1f <3 ؗ/=_18n$͕/C)R[Mu\*>N3$~ ڔb<ƹk} [ۧ`-h %SWcϵ>FZ̷Pʏ=@kؘ֞P5En ֯ݟ QZ!y=pN=&h s: @Z -b&')O^*Vಳp̴`ow`Qe FyS>p=5˚4]|hX/X]Q#iVcYOS|B:O<SEOSob=M~<ŏNIaSl"0A={҃'VBn4Yr=s_W5'9}1| 6O{_r8x"7{ yEDxXa%w 8,ЎovO?gWÙg]}C1{DL%8y4T5S &=zި ZcB"Xb}dL_ 6]VzV)h=wuͷu3G?!X?P(`J inchacB>t[W[yY>@d?tX/B>Eh-·ㅴebG>(ٴ@Z -`C'*ӧ|9XUëG`W;Z-\% +V)r XDGYh?6eYNHPk:z.Y6+PCsX,1} j>Qh9jC wc>^(0??|UB}g'ժ[O{h'f}֛W~EUr0Z=58ZxD(vzO& fշYta K5zNCQbOܷ \P M<ÀࠊKWj{ !X`=9w;pL=Ɣc8Kx[lqҨʎ>vf|+o"@bg^]UJ;S(:`c}P?ƧՇtblZk)rK3| {,q>X|d)M LWCSeW<;i}K OmL!\1G(cSV8{Jc:A:EYhgJ=q/y^XX{KGpDKO7_&F/b@XL ,h e#n4@}}]~ZlW)iSuMB@½"'*BSHB?hȝ'6=;i@Z`{;E]bCjaYlf\]٢45rz=ɩvX8H~b5r-Q]OZA>T|g #aʪܤ5W%%?aB>'M:{w5D0Z-b_DKЭ+Ŏ=K]wˎ>n)_!c_\c([b@/.:n.@ N]iEE/I8mBc;ei[4};}\#l~J#3g2p캾󚩪G+tJSnӚWt kr@L_ҋ)MyJRݩUCAm}ilkȼTlĤ#-_~l:K dz3P.WD[@OՎfbsi }.5梘jMus/̿*"owe>J/17ҋ8_Bl1缸 il1ht+vmxIt~+K~,`R͔seD6xG]݁}O6x[A7/|e/d!uz>30-H  09L@R%]qY#S5>f}@cjB {eu]>uR"Mմ+%?|J;F|(A!uf'`!i3>H[q8=[7N W? d7PQl@ӒE'}5mǖkC{~'w<-j!;J +ALFsX^joh}#i XOH -gz28D694HJ 4jULi5- {V('s*Zał+(GeyГ>9}UP+)vnE[@{)ﵬbζ97rE{ΔW@s/"^:U/Bn](r,iEބSmVwGٜZ2Cio T;M;o[XKrUĭYr508 2mǔϕ)V*KAZ`[~C}]j͆b"}ǴÔ!TOe7Br!ܿEtY~X-7.x8 BhK)/UY{O?&%JH ,h c=iVgj˙L˷u${ { 7"Ǐ2?W qG!+(Y[ s٧zO}qZ=#$i-ɀI ިWtοg"!\;yPC V}PDvЋU:sX:H2+$`D9ЉSJ;D_RCA΋NBL>P/-o#U99W0jZ*x> eշ" >TؕG&=tRK*G׃|*@`05HQ_ 8r/c]j&^j;K%ƺP8x8(yV鄴B@;~[~9EzI{~ g]*Ҧck[BB %:yսEt&7D9Os1mm@!zhs]4 "#"'-"[V`rR=Yc䀾 jW2o|j $ ;@A;u^&| ]3PIr8¿Å)Po ^H})mZf ]|.2;hd XT$it hDi.U*Z8bڳ(+VFW#%`'5i CL),zڪ^+R">η62k'q˔=/b;8MYuv}v`;l?X23J}[@UP =,@E"Aĺƌ5HۦxJm ^Ts-,;)]Ic͗ 8?1dLۘRW{dpwZodgy .*eZc/s wyN6'QD!#C1c]ވT}9 Z~@qe3[ui>範I+?Th֔NH ,D ԋ$nIӎ|fu_mٖV>延^*@IDAT 6f>Y p|!o}!n>RO;A*~Zg;BڢM`J>ut}D.ngwi=y2 7֭UN3͈:H-ӂ24S89Oܫȝ&`d 4xG \Ia6@ kQКi)Aq-]+Z:DBZ`[1뫁2IC{p ?gw]?=VR:jac:blb1Tay-G;փ7-x#ei UR<(i-݀[5?DvΊo凎@P.9`ωZ}Zie e݁0myҽ!_uۣ[F2('W Ux;Gc( _(+!?ht}798=i?L LH8{ON̠g=f9|i#VIaE+鲚$ZlnEVD(Irʐ!J^IE<FtT'uqS0}䇎uaRʋ/9̇)@^9hz;QS[{CT/cgٸ;I>ˋ@YG=L `ZRsA*Cc9W+ N,i5FԜ~?- r%G?ƢJ @v[D5dY'zM]qD\@9!·1n%_2xƹ}k%;K8QsGMK!mj^0V} 1ELcZ%8s߾#9m]-m>sDyVZZ:̚ /?6^9[oB=`|'5 0=ج=):-–#^b{J*1@۶|2s"?Ex-cdI{$id [4  QO'*H 2C?Uj`[l}GP'X^ :pϡKj:2.Y"91JceaZMPɋ~T}۵Ӆ@[p`[E0P)PHAdOD+[ E@KXQ z3BI{^(RnZ ).!-0,Ϧ?EYsjȣd *&]pE]u8| yڢZ"}JǦw&dW-SclKUsTHC7XliґO|G ΃ O=Lo^lBZ -K[S]iX4~z4r׍eim><;^{;s0dTi@Z``B4<2E8mn Ew Ri6r3I^i2S>}GSl. ĭ*G=Ta ~ފ,TkU[eMBVP>>?Ek ~B</?Iᱭ#Vn-A۷TT5GM Ѧ_Qۂn? Jz믃~Zm8#?n#0lT?AMҏ3Ƙ\v (Icއ]%<[d.?/+Ώ[06x.6!-0o-qfw>3OLkLC39dnpUBVl-/x:%o8S[q%eڪr+Qƴfy3~U?xtTO{?𢡊^##f'م̢D Rii@Z -Oq9ҭlcHf'V.<|uZ58Bv^.3H/rB'4J?E|yL|"v?@Z -08}0e‡ %'({4`̰CNsHFx+mWMkys>4z7וi+)bB* ?ݢs05atA&ar@B䘡,jd|G OQM@T3oe 0y|7n%rB׻[} rXu(XT9 vWx0&eT(nIDLO'0~k;<ΐ 3WwOҭ2}5'6ҢQ>VK^mýaړbuQ< OJneX-Uy*}_X+i~07+Wn1Ԗ7dR`9(-gc3mz#tRjf];F޴LcjO:U8ǝpy\fmvJ =(H32mnry8E PS1 t(2K/Ry~-xgT0*E=`OOmqr@"9\T |m{E\i@Z`[> e 18kJ @1xhItiFeL-E9 &9߫ʚ@ 2:,xEЎ;4)X?fL?ߏeOEyzN“3|:jG(Jx3ccM:{6esFb¤ BѪJ$e|Z\Fz A=KU!rڇ?;Croh{0̷T'|0x~·Wu]8 ܩtZv;xY'en9OGS? | e5yl_Pu-OH 7 xB{(> =M8Vعh_%f?HMm@ܤ߷P8әeӯ^Aѣ_=7B>~P/-onCW= P&"()%ϊ UѧCqҼ xw0!-,;Aޢ}i@^B׋@u@~XvBh"4\lkeA=NRnhඝ6m1Lۄ)Џ+ q,B;Ph^xx%10|Pߦhy{ΟT@Z --OK #|6P, ԕHb9vX*R*N^  #(^*S 74@]"*8o%@#pw+<uɃ 6Lx焢ʓ:Gy'_ pW[1\/g1y_I|lU/*;(Yzu+H7L;OYj'ͷԃ4kigZ -09ˆ/+,Cn;anq<P]0V=U%eX4z,n!6Ė5mZJ@=ؔW9Y?&tM=9'<^{5a1X!L('b{u ?hK3%ɴ4e l=5A)}jӜ"pb>곑"OzI._)?/-zӒ4x!|~, rK9gH6#ih| sԼ}E%':\a-krJ^{U"Olexмc*;w_x؎s;|,7 $@P=RloY"̋D#}r? 摷zJ?b_02Sa!'!-H ,x ^,ǪdTUgƊ@nR: @?}T+|Q++f5T}H |T_y>C-3 @ b c zsP#r ros ~EO}]_>1HbUjr)dK?!- ,ZxW֏[VK/|u>\([r˨>dڠH fVG7˴p@m10~1\\YzEJ[~-'Ű*'ҏ[~?tsW߄tU(VQu_hm .=$m}R7Ti=̪Z",?u>\2("3!3XoZx\)-t ~*wz?n8_{+y7y#(˔c_[ϴ@Z -8,(@3h~?y~F]p .h”8_mswUs.\gy)v\Kjϲ@To (G!FJ3"Z_#EGI"'xޟ%{/ r) :iUmR+[JH [)lkp96'r$. czRtp|tyM>'G{?%~VC7njb8'TB3fIH p$=!["Gw18(6YL5_ȁq3%B{y7ϴ@Z -o,ok쓈3 '_*[Yxp['S*@~5T)=ѧ#'!| lS`˄Eq} %SC:dJ;(@:Lj"Zd$dIxŠF8)+NC/rlmg`@j5LϧvV8HJJ;Xt<uxo!qE-pƕNH |s'bk?* lz\Nш_Ả@l? r:nmQ Ant߁H7oߤId } 02䞧^N*<|5X)cXA>9J=0f淼 pGwgK*gB!{͸CjM?/vFmX|rDn olKH 1AP|% %8CJT*P~MqP{G `P+@w2_sRs,jFhymQLSB|ϵ<גv~sM=1vn8f(ꣅIh\%Yk٩c?&m"ܷΎB"۝Y@[D_xrt3Ǡ<8Zއ0,C@48քy%o/QBZ`^X8m}} ?>;m?۶$3*RˮZ,bCr#AS>:H(C2Dڨ4?J &G𭏩S8 inW3~zH+9Eˠ}*Zyg-0͑~,oϙyt3#wڼVz,9O}P};ExghN%M<O>f|8` bØr.kJ i@Z`_ZǾ_iTqL#mwtImmR  ,\&}Qzo֋`ɣ_{?nY"oQ}CM{(2 ߒ=O>?,ZYOʭ@?!ئ͓ϸI|Ɠjdq o$hP_CʤlYV|zδ:w]9;?`2} T4xbS/G&nK: UPsHӋ iꥦIrqfֺKv?][+.h0O~|afz7я'f;#>Aiף0rΎ?`+&((]xM8؛=\(#M[ Q`v82IG[GF{ >fi@`ұŜ6y,A^q3 wyE&:xJ6Y^܌5h>E aP!;LbvRkǢ癏V|{>vGKkM{{KG?u4ABf lL٘J\Ez\!⁞JBǡޜW{}'ywzpG!#8C }Fov(")ѝ@{>K 큳K~mKI?2?=5M~MEIҞ/ ^ p  r'~[-P6~V|ԇʬ u4\yu8E硓GhjlUQzCeho\D.h1I#mph#K,;$_d&$/˟|iѯKM- e{bb46Up/ =MEqoO_ lFl*C+(C={{wuY"}Ʊ>>PN x@I7 ,|oiVr BX7kmN3]]4Pp{A%rMvͷΊcv9f(f?w 4-Ga|D7j̦p]zx2-qf֛%̓>[x;G^%8#SؿȾэSi󏤎S@UG;J0k[iU8pJk[UZ+_m*MIW C\Nmy[_f6쉁<a%hyKJKU*ڡhuT7򜞲BfD}LUdC1xsDeGxxaRa.}[ >~@ˣtsyw6.|p37٢MӇ/yЇb.O#Gd< 9˜*CPk5pC4]-r<7q䏨H|_&rMSL;{ÁT٧}ŗIKyvl  y娖4t2X&q|&Zסa߱iTWig̤ױ+8gpxj=w8DʼaC9&C0+~sKz=[OyRJgW~GO;'9C$ po@~=Cw8@<~E4>T E'&=p=TfVJ1û[͆ڛ.]|ŗ ƿΆ}v_4 /߀qi0|< ߰KuON6#}n3Jڂ,.qwpmhY0V !)Sw]:+N]ַOu.Ld%>Vc]R5;Ps_-A;MO)uK)|ȠeWvi*ZEpv=:g0pz:6uDh351 XF?x?:,QTnR-4+ov龢n l~[Ob3R|؉ QO<|gLy4 HQ3ڧ1=5k t]݋uY5~mC7cjf_/tvA>y3yyEk64WT OZGY<6ڙ&MɪlF`}_J?JmU:VLg 5{G?{O]14G!<qNc YT2-%[O)/kHM ?.3Pǫ+f6W=@x!wFZ_i;9uܩؐ=pf=gvkjH>Yd;?h3O@۰lGT;e=<xn+xdI^=i z'i^2>h`| c2rk S+ϕx8BZ}@{jyC bauA&~` o?˘0tᆒ* t㟜hlv'jYk}}St# OF [Ui'(uY#jT⫽Cp1q4k񰎃9.PGΘ$m'a#Αڒ6P7ƻOw'V>%;iXWOhۡx=Asռ<x|@ PyR)ym%ɋ>yo =)W T=z7֠~xT/Sڃ@ƔWx)hKl7= C--x6(Lj,qH6 1=p=^G@gߕs9%cnHr=?S2<5w 7zۃW;wGՔ(hwys1&+iuQ٤Hʵ.1Rѻ~ʸǨbO'?uQtkj(i7QGw_a6Ꮲ&Ə"uܙ 8`UW3#:DG|G>w˭Hr` y ngQ̿>VsI 욣pȳ}ICB񏾋4Mu! nKOi'/XNO 鿴a90O OE9 &ZRc8`c Vő&2Ry:xvIRqٲA_`}\r8'/ ^ yw 42h?؂PuP˳t*Czӯ:ۃY8Sp;4B {veJcnIl=h\Q#cqnE %sE柤F;_Kh_ l^YbhQO9PPf~98!W*6 ,6?pcAP&ƣoG Ҹ(׍(&iRdmOxj@/2J)&@IDATX_wD{_5P.e9[eR7]Y#W]zvQ l~ Yek@'^qy1l<@aيB܁]x1 >)xd}4B<̺b'ysl:ѿb-!wcP5yYVE_ε;]piH+p+); iE >J,_x@VN+{v GeKIcZ+W_)Óx%z&:f] 1 kko{{s60bE:Tu* -S.  \ =L 8iF?B81䉾ti:߇(2 8OЩ)Sv=w%m|O`s=IAІY㔀xGK]ssUj4K7]Ǘڸ kxlj_JWO.;A574-ac2cA^y ֓M?PlgȦ]-;"S풒~̀Z@g^ 'ohƜ}=a`E@óx3]a+p@6Yܲ9[-iMq^2W&Oi x'eliKg>7 !?7Q bN,Ŷ.Op;]7Eu@<6a}0ckb*%^ er@<\yQ'U/UUԦ؋ao&x_;;4k.I 3f/9֙G>se籟(@r07Sĵ5|\5Gc `JN0IWxH[*pZBg;(zT L&bK;G{|O 5̅.qxc̩i\,2W6άY-U,52jk#7T[躂 wPl O=3x%띪m.c 71du&}SRּy 2oC!괬Wl tA)i–δ@{J<`\q2F!à,@|SO(la_7BuSQl-E.aOptcfq{_<4Ƌy?`-Rj;)dzz,OI\l}ʦWғ'ߓ nNjӕ9FEz'7Mӟ4?]7p\)Ɨa!JhӃ:hZNW_۹U\)Ty"qPa-H"r?n~OJ_X^#H \Z_h@Zye.ZuUO:X|-ӼW}%<ԠnU:NDGU4 ju5!0c)Qm৓(PX8ԋވCk?R, y$Q+q j`})W:Xu?o u|ر~}M 1h2s~t30i_>a-$i|P3ˮam;mCGO!Sѐg *2SxZo4hˁXfh{>ă?591>@Gãϰ 3f3xS]G"-}Mf|޾wst˃Ȏh_r_>^6)~Wb @~b b~Wz|c ]8@/^,_'$ łV@{&vrs5@/oƕ[ 봪kPg>es^J+b8 :SyE׌$@ʢL_H^~a^k;qA8`^.-*fkdp#-n弄;=IЏ/l#PSwtz(j5NyMI߲zMU7 Aڙ}x8Ij@{=$ <7 Q6Dna[| ~}o&*?}ZR> >xMX*)<vIx oeAU=ci |sylI^b:aߊaWk/>戜ECA*G}ݠ+v^+CWyqǹaȟ\ڻBc~KDߜӃAˁ<=O ʛdj8e3t}?2t~2͇z#Ky:vI]5`lRCo-;?fm#?\HV ℆s rp9/,̮2ov=k>9'"zMiwh+7?PѾ=p<`ƯuW2-C.K_mnv~F#pKA8O>p8Ha!|j_Cy<1 טXu%^iLy(MA4t#q@{:z H 8:]GYF^ϾI9In; Z)"[3W0=:; 4zufM}ۚfsm.x w+yPTq[Ө;0n񲦌-ĎqA{";ґa[s젪׫ԲL]azlIC־Rxs˞>9-x=>dnq8q}짳 M jlAHJ"e1c:d><o裊w#lM q~-qQ[ʒ_>dxGk{%1 @{=.XXRe> zkb9d=/< HcJ鼙ħlC_,[~5@=.ta>m]ˁiچvyYHB9bCe#$a >xҠ 9ZaGqSye1ϘzJQ (5ΞV[UOU{m?#xa_<4xTKFjjz)lC{yg.;>b,b^pŗvǒ]xwDP<7īub^ oL)Po<ʁtg>h\l Ҥ=(S>1#OmCC<}=$eW%mAb _t{XЉ(>!. Y cԸ"jv\G_1WH9'R7oU_ۓ@+'HAxԼIћbh{ \Թ@MpeeyMߐCےh,UʃOue<|Y[2׉|X^86S=w$uewu-x 0d]xAe kjvU dju_J C:1U-* 6Σ9ިPyicmIg]{ByXsM{Ԥswd yp73f'}Xt7OQ(遟Fi<~'|WOAO ^XU.ޣ}y{_ wj̩OcCblh7Ըb,Asxxy,E'ZuR}X{5[̷NS_˂-ꠀzxmFw=h^bB獊 !:M{O >+xlfKn]\=ڱBN)UR퐱ü\g<"[>x: (%PCd)^*0&c+uW =< DW:UkjL`k>ZV{W=2>?16~я'?p e\UjcVҒGLQ6V;ֵ ~Q(\~rL{)ôz%ݴJM_h3]Й DOQ00vZvɷȽӓqO/]ˈ55uc 11(Nh80H#5s໢`][9Np ]HV E7F65A[J9ڭmLjO]CKk[khK{ŏ\*^5sP&inߔO걂y 状g;vC>dz?P.]#Oe?+ EӋ6Jz֍ùzRv蓏C'_:F+㉿ǼɇFyE|(aGS/ ~2Xc!d㏑OORx=p<`,q^O0~6N<ž7q%~~_[kixx2k>6 iGzh'󀋡u1dAL]ꋃʟ&$2MM ՋB*$jiCǁ_jL'?;zHsƖ? v{M~m(qq$us )7 T( iqn51$V|A?9 XnWtӓ5%?+@0Q&%<֔ g7Pǃpʘr\yxf[ci! O-ꔡuw~0 {~{~/}?]<4[=qA 4 ayx>ӶAJ1;_MSIΓFY RcO=u!x+F8WB~Ko9-_µQyNq (+~çsl T"J[G>ݔjh큓y `gq!$#t X([%Rӳۀu1[zt[!_2d8,P&Uxߐv[HFW[и)z82$xY6a4Džrq,<-q #/ÿұJ+u zxcT 8w V5?Ɩ'kQyyRѠAv@v{@kAR{*瀾Gkľjg4(u`Uʣ.z@{"EɛV:,_-Dgl9 tEkjʊoA4_v%@Wǡ`|.z>>~Cw8m~6m ƥA#T!\EIZKM~_p_At sG9=p%xm`o+oMjYj,G^k!SR_m+F<"kA{=<E.|B)5N|Nh Of#[ޚhN FX)#2C^D'8L7fA%^M-'OF|(\ˡ#h,xqS&).-t\jх=@RMՑ_=pJ <u6Nek ܰG7fo'c ޻-9X>Vx/ -OU7xKY"aa@Y=ߛ%ܝ ˄bG% 9EÞt(%ˠ٠74 D<@w++=8Xr<1^Sa-72^|s۶Ki4e@N ?Hk]niX NRο;Ơ\ӠƝK Kf>=[R]x<hxP?p˔]yŷx/VW]c%*=eWs]'rējܑquF48; H1U^[t/o\#(sDYPr JNu:>8u5?V2FʦU0(6MO&\C{=h\X\YXXA`Bw6%zHW/ ~kiK'Sv`w+H-5?)ȫO #U^B?P臡¹^6dW5'7b@7:l~4C@kH+͞7 R hozcqP>WȀ|[;_<(ց_dT>7 5Q3=%O$Z <#:W@V[}I=pxq,u|C_ĜAe2QfMmDK#d?K{cv|5|*m֐PLA|R8^"?R{o*`w?2518_cr$G{z`=gy1M0+ ;EZGQ7 TW@4S?k}]}e}QoyM/L@{=<(yu䍊Z*_S:5A:1x -wBr F437?M$%(<(7Ik`|x-\ZyNjzTeIInzorN|2Ȟy}6֧9J@J=Ir {w 2S,TyZ1$b~wҨ7=q@Ms%t\ C$xUς$z .&opqB㟑VoTġ |1?x1c @y xC ØՖdS㐱8kaK0y ~Gp 8wcAŰx䆓y]ȍ~iQlI@gfWy7 e8M֋ !M2  "M8sc vuFȍi|Z1rD hb@7PaklY>ZeqDG_d<}UIJׯ˫*715cnc c ?,lC{c5xWm锹Fe7 _n}a{9|<53y8b!{5渞c^뤱Gj1mh0p\;Ή+Pu _/ sr C.vdпʓOʱ?/_5\<*E큛.P@,xWb TY,=ȅ8,1& bNq3n#e7IҚn Aϒt.Ciխ~Ӱ_CO/5ߺ:*Ŵ큛Q_-&o,ypFGVB-z~IK چ }AXx!rӄ\uau$E 7yԉa'Kx8` 8>CRƅcL]kR_|A<t++wx`l{b2y-A1mh #6kܨzd0cȘDtm4y;C?k}ַ{ rͶ =l BgzlAG>.c?D&o*7=a0G;7H9Coq#zfAfbsHw,8?|1u5EeVӡ"ïc kceAO~ۣ m4cy\i !?26 {Gn:>:%v), @{={`z 7vlR 7.")i@vĆABсD!R6*[@xzIi'm.Ai6Pҡ2^~A (xF_q80 ĿoƎ>J['@bEtޯۈL2?}kR-rAx6kh`_NuA#< w>95O큛, @{=puvLV%mRӚFǑ|y-ms@IDAT:ᡴGjۢjh`.k2s03kʜ"Ơw9\|r5ơt<=I4 Ґ%[585_SO7n8ޡa@޹ O2`$g{& jC{=h\=xaވp>@|qpC;x7 l˦|P7Kn) ٛ(6K< U7.EG;i{&<5 vG>x ;E|ak7:3~~׋ f o2&W)!iTCm[mCWpA+R.?@k;[ <7  WC)9W碇1+Ii 4s&t )sH%+?lG1oh`ƖI: se8vU}큛, @{=pu= rYaaآ#VsƃM#G3cMzy7SЇ4B;@ڶABOQφ[tmeZEʇtz~_i4}9cgGPd_M훤ßɡ2=R9>ILtCzW״=p3{9v=s8eOs\sVmAV$R7ؾ7+_j<^e@[Wm 7aD4A+h)H'?7}@{y`}a7ؑF<ƍl2$!C9dSvƬL7LoHѹ$Fu(7F^I>}*E3MsSlֈMElaHc q%j ]OxLi٦Jic1sRZ2˺Î2|C{=p :bO>bqG+~bqw}E-޶!K;kj:TRW!IpBM<nlv׾i{ iC{=h\ԛx6,lؼ@Ft6~-N! 堃9BwM}Qomn( еލPLyQinjGi A^CCG!Tt=X]\S7]Rtۨ\vR(Dz=Ikhq)?0ϙCkҭmZn`5Z lfG&HON=_RxR>/{/ r75Rճ0uc v䅪o8[(q$:n5%]Z|OqA ڟޱX٢^S˪uCPG{=pb0A@j|@ZD ݸtoc Resjcf.?tw5ncֲeֶZֱ^\E)W-kIt @{|yy5xu5LJzʩwt^]u޵]U27꜇7m.#}]~s}x``e=inKiK_moqR^뫼Vږn@{@ ?.j[<ͷ}x[pxoh)z`צyU7vř]}ylYFFx=i@{=h@{=85<>Vvy'.ϴ=h@{=h遻wT~}@{=h@{=hs_O{y_t>gm\w_g_.3'%_|(ȍU}Q ߕ W)60n lB$!$ bC0IL57q|c]ʮOIz@@J L j%ϕ|g%@@ 8J/jX$ s/x)C'g7. 6C@@J`C* 6LBPz i6B}@Hk }H   @ћ/JIVa 6v @2_,CmP@.U2 @`-zyS@.-\V])Bp_cqh@CEG@nI0(T@/8M2[@Aꖼ~rK U'@R,@`26!ⱎ0#z?tK U'{H P 8Bn e  "=iXQ"|:0h@ F@>!wtK[!e ^@@%@0ZE[#K`ʕ#~ ,h+0Qb@@@![&/7LFT#dynX} @D!py4   @HI.y $(F@Odsim@"d ./hM@*SU'A@ z I~R%@r H/yg%@@@@@@@@@@@@@@@@@@@@@@@@@@g{@@@@2FM%$Kro$@@@@e s{`\ @@@@DUί\/y $G5@@@@]]vX+e]\)B0#   @Vyƣbp B   B@oP@@@@@^G&+3:@F   L+1{"0    W#3* X*CB@@@J滌Es)@@@@"&W~)G[@w@@P tތ7T3  q དྷgK>I2  PUb$N6A41pzJw   k2z%3CU|1 P  Ag@"<Wb .#PEz`n*!F`ljT~RM2%cJr@?~ʴ 9{0 2Z֣(22@Y% (SEiL[   i}7HR֑ 9syN#¾12\A."` |m=XB@4^αN zc%@iODy3,+Dzed= תk  (#/W!iuVzZ2 @@X4]iE:?Jak_z@ONd *_ }?#F rvs@FG  @Hv/ٝ/yđ7?@T(-U}\,@4\r9I  @U*((}*< )wz)Wvg&'i\_h@@o٧^;O9ʮ٥=Ie93n@ enQێ7?Gw r~?@@IF'=J2ރϣ1g %9< Ҫ.򡁌F@@ l-C[_~9mϻk9=A8h)(縵q455tٿC/C@@"0uE)n/9qzSt|WTB@/@'1hq~-9uc0@@ Gɘ0:W_QoH2>[`s^y Pd_zmc  PzI(y5*%=uf "lYߥ!).{:-!MD4M@X"ʳFxC3C42b+""2sK/}@Bo*@2a2=2_V}̎|+p̖9T2UCGbe酲  !~Pq<%!2D\"O)r E@J] KӍ%M!pyG{D^"@>~/bY@/z83v@-kxrc~},RA#@/K η#C_V|M('|)  3XH\[%"O9/џK#@ C i !xUzЍ!  t# JP7 0 Tk ]1-]D`imsjc|  RvFYU -Z)ыUŠ@ ۥQi{c,m"@"K   @ V~iZ$m6Vr.~? .RAoIz\TF٭߯_Wf(@醟n ǰE"~>e?Bh ߖE#yd~[AWa  @ ȶ~~XB_4zޏ$5 ̐?t|a{E`G`͗  q~?zp~iO~.5X`?{VI}blҡR_wmP@mR>`-*9]q :Q~,CEu*8@UkZ6H  ɴXw {N"^;{E@sdR&sEZ՟H.ul[{h@b?`݄I\->Ά(Q`]پAz#%@b_; D_XB1v|i)9ϻ m @f@1e7^)t״n3>CW  @e~zDre"C DV{BǴ1Gf }]'O@@. uhqb*UϡWB t]~)OD$*5u P  -LvGEHJ?VI9B IT2|i=OB 4H =KeDxC@@p<(]2#  @2EC`CDcH"&0W{E yMkĸ@@BR-}Z.u0uc#P+#kjtF"@9(g$lY/i/l H2>`#ZxfKZ$ujJ c CG  ѥs}ȚϊlG[[Wg"p 4Sq`T@gbɹ^GUX  P6eO^߮e ;~zqݭ>l%]?P) \-S׵xix   ^f)05sx0j(dne+|2!@c ״^z SIۇǓQ(/jEu@Ftbci,w0$]}_rf@ X3v,SITR@|FI+H37S`ʜGFN}Gr/G^ef%0^V>QAO ֋@r(;9ر# #!@fuIϩ&].3?4 LWZnהmOH{=ķ3 y_:sW=f#$0Gds29"PdYLHDPkݒt5a64}ʙA@3s{_;d̾MLMxm< ɗ%'%OrG$@@@@I@x34.֩4eK|kvSskvM:5?:@S?޺,    U)A=$?)hy.r(=iӷ #qgO˄    @ hTӬ2/3Iњdblk & 'Q3t8     Aͥ^19=OsGvVJ&gS}|]);c[@@@@"$A3A> ؚoCܙ u4ݺnfC*@@@@(ض[,s9WjP,4SM3B].8N43?    @5 \f`O5VfncZf=-;A;K3oy5iAe5    @U 8/g12iOi,:{a:56?t1    Pz 4اq2j644)*=Nj2 b>@*EB@@@4_ T:53 ғ73@t^N&i    @h4Yb#33=hN3ֶFM#lWU픶m?@KlOB@@@@ J3d5~q2ө2&Ӱ/hD;mbhC @@@@ 3cMLc&;%TM;g6?Sf&$@@@@"<;fx8Y.hZ{rt,T?uf̒@@@@z&ffbd?3fZH@g}Tfs@@@@f9ccl2ǭ%rM TɃ0;`    @LO&^ә8/Cq6KF&;e:83@]&!    glLf&SgsU˵nQ    @127Y;7:-:ꨖF)    @4.fg:sX8ciiP@g'3aM]    E@cc9g,7ְ̝!q3UԤet!@@@@bX`1rbh    %`L     T*|P    FE硋}:fL@@@@@ @gp.ݴil@@@@b 9-sQ弙$      `^WY״R:G%@@@@(M`i~l    @s    &@4?F@@@@ C9@@@@J X[#    j~x     ,͏@@@@P? @"-pB3tI'Xns u3sYsÐ>lk޼yY]9r$, @@Z8ZYƅ    i    @ !    @    U,3W10CC@Yge Ѷ2 @@*#@2@ ߿* @@j X͏.cC@kѢEV*:uT_H$b_~oXz$a^;=+VX~xbkֲzQ=.G@x @ҥKSO=յ~5bĈfkz^KstEccfY5ytpǵ1Ga}<:g/bwZ;/ ]}u]}7u뭷~Yz:lAu/"w&LZ{nVW[oey_,XЦjmmէOk7=Xk]vIT*raW\ar-ֿt4dAdr!Ӵ@(gѶ"5Vr}봃L57Hnl6ɣ%I7|sCVB@@ ޽XpcY?W_}յ)ܹs:<][[Odz j-{VY'tR:6c kҤIY̍4pv 'XvРüӎ;h=Yv깟ѣG[sڦZreVyf->l .4oҠ~3G?QM6h#k޼yYuF6_ѭNFw6I  @>9]~%N2:R+$jErJrL  F~ښ0aB?… O<:-=/jIpԱf%6 s9֮>խN|ڿB_==X<묳K/d?5@֨:~{_v[M  Z}h +^NKORSTL4|?{)-Gl}%OMVpzQi6w<ڲe< @hz衇ҧfkN_}u7zOx￿ֺK#8ׯ_m!$@@ *HO@hW`DO5:yzys=0]On}>S:x^x!ᵾn7 1]wҥKnzWҠa1IoG"q)o^ms&.^P TJ`/ *зo_k֬YvMGufZw`>.:/ЧOr F X+ثW/ϖ1cƸӸWқnYo6R/<Qh&! QqzK@@>ң⼒"+u+4䵯?~;n葑z'J\Oo7t743f(ֹkNyݰp6<  @@   @^|EKusw;vZhQ6^G9+>3::CY:>~a: .X:_~Fk>餓>}uiYo븞{9r @@ p`ܟ@*`o~vGr%x1W^V`]}-͹ҥ^kuQ]tQXWXZouֱ 9ٵ" T 65_vej`pҤI9y0+@@@o=#C /sN^I^;skkӒ/b릛nl9O:B=M+Ԥbh?#]veףh?/-_RP׎?xkM6)h*# q<Ҍ@*wǍW[nb٧G8mm.IKקOգ䦦{]Zzkwmwcǎ;|S@= @@_w]v٥mvuWG囎zԣ #F(>" @r\"Hz:ENWtUY-S NRpl TF@p?y~W?U>ipLGKnQG@B))|X    ǑV@@@@P>,t @@@@H+    R`(:    ?q@@@@B)@0 B@@@@8      e  !Э[7k…\)D@@"ȳ@"#ЩS"  N#A?@@@@@`4    @X呠     @0TD@@@@ ,H@@@@ *M"    ay$     &@@@@<@@@@J    E`X     @@I@@@@"@0,@@@@@ $    a G~     Pi@@@@ #A?@@@@@`4    @X呠     @0TD@@@@ ,H@@@@ *M"    ay$     &@@@@<@@@@J    E`X     @@I@@@@"@0,@@@@@ $    9Ru-2缣jqA4j3kB@@@@ ؙsi@QsZG: ;NZVI    A ּT$TZ阮y|, (3kLTSM3]AB@@@@ 5Ffd:d%reAu:y]d    qx\11/ӃL0PAs2O *Lf`: 3(=Q/4?2 P@ bV= YAi@ +CO 򙩊xk- NA#n4שt&#$(R`ӭkuo6 -;'%! PC/)|#@@ /xLLͼ̶I`.>N͠2z95dOB<4V^V۩և[G3Z@_:36>۰G#  +q} y%/lbc:ufgXhGMu03w2ѩ~ɏJ;띃N:~y \j.$ =ھ?Y~;H~z?ުdTnoEU9bUϙR>Tw;ith{Wl:Gg ƻTzWK$x颾3L)x/e(`^:ƾ4eBW&vflSMd'9PE w â`Z)ҩƽtjy:2gϜeVLov풒hM S̑RuNLTKNGNXvS6{  P&}w7swsi1[L5R2Y!k1!?e75}McHDJn'lS>yډ{<+'͎`ٛdH&Yo^5K9DBh-ݼL;J~C/ !P)g@NW&e&q/'7@z:fm̼L Vn4A9TDew@l3Gj]!Ly--; jD}K$B/gpd_-yf O- jWi&!t@doHwhͥuV@"!@t^k&!6?ҩfyTZ3>3?Nlsdu:iS@vJ`~15ML̀t[fEvL6:f{VxJ"Qy}'Px+l@y.kb^l:b夶cR$5'yu= 鄵jQ};χez@@ ҤS3SƵL6G9obSmGStuij2qqM:3rS # ,:ѶJ8mc uӍ{mq 2 M䮄]vyt؞߳6k:ytֲ(Gb.owE #sa T i5fx >t~4uVwO{.G.0`s6N6`': 9.X *`^f/7Se5Zfnot2墦As3 @L2utT ԴUtn'wskpn4pd:gI`AE^JuC!;^E,& :u]ހg?-z煥e= @|z:`|FyֻJ=5 @)1-R`)L3ؗӺ&ӆO֊m; ӺLL3>OlR%/: -)Q8xïXkf miX @4H~J$:>o[;脎F>԰auwg^ PwC>Mvg~ʧ>u@@4:@g u:5ٹF3۔Ғ ֊YөI&S30Epk]چ5i|ze>ӵiή޲눽C)0x? Nw$}v>cj}]r{]5 do)me XgZv&R"kmu Y2 T &yƷ4ifS:<*PutYStui j':3eSLu*7G fKS?ݬT۩=6/fPPI^[SA;O("\?#_nòk6]Wx#^w[O @(}k { ;{ggx1Z@PrNMPO̼ [d֭^oP@5q ֙~:5uԩ3;trV|o[wr%~qraehî{vJ=}%¶fm"O=1r; P]wed]?969Gӷ}@@z4eb_:*lZf~y]o|fj1Zߔ|I)vRuf޹4_3Gi}Ms~0yOGVf\t!W!PN ~2sn]BY۱D揅n#dy]oXhMg|ñYJNG]N?RЃe ݎ  yҁh\$SSg@g3k;[ 2::٠`2a֙Ifײ){qWNnɯ9۽^]#P=“o_SEӰv.??6J,҉@S0+8%j;8xᲃ>.xc6@@( s &e&eeNvZnj Si@i35=38Sgno֙yz;i5g[*;;1>tYG~B>[])嶮9Rb E}q'֚$N'c@ױ-JRɎ~l[G6 DTm`fY&ۯ.ʙyZofgЗn՘mMG {{5H^S ud+YEٖhwxF{WY *Z<`C@ l<h/RM% T3 Ř:g}q@۠A?6ږnAv}՗_J%x~P zZ)e:6 vCf(]cd挂2 @ƏoLRIRú8ǝh? @2?Lrf9<_ÙtrSNR-{˩oW[sT7 ^ܜjF1*DM5%5__EYOJ|mo?va"#K ~8ypm%/myo{iTrEoE @tk]a:"R I֓\#!/qoE)9 F7Եۺ֭\Bm*zZצzȑz^V;m[ ]`ŶVF>WǒgK^&Ix %W[ߔE2 Zz\~:3KVOIuȨq]3XDvC^k:}d j?,OcH#{mw A_rVȵVSf4<-]T҄m]VSn_γd܍n2T 6d%94z 5g &"pIDy/dW)n.M&#5mEvM" X}m,{h'ZZ۳>?G?fڪck䜙SW'^0v>!xN^נ!@?o }ݸT $Z exɞ@w&ɑ@Rva`A6ɶRw7ެ:1ێ{jGoـ 0+4mȑlGS:6b@ v1ԏi]1}W`yTn LNTB~8Z<=rohGl+R"tYn@"=h{9Oy=ֲ2fKJ=(A\V˟i+ Y R5-m4Zk4A+pK% d<ʨ"Hmݒ!uW`!&e2A7E_l}@Ezg:Zm&;^ꑲ#|36-Uڬ ӊxlB1 \HN !O+z:YfQf`e< @<^.A _>l dd3= }fvr_$>ummR$Ŷ%e'j4f&x@^=gyЌhŗEȆTH }Y;1I+H'~fi$^[#@xڣ: ໎2G()z4SwG6$Z`?V_ Db̬cl=   1030|-R--$"uܽѣw="z   ~ 9}Bh=$7IJJ@ zIgEz$t@@@ 5 [&z'UmiSvr vM9GgU%wd^3!Yӧz%M[ueۿ{&JփZTB@@@ a N} -:(>)e\Cٟmٟ۵ giG$(.;$wlgZɫrՓӣhni#5gjN2@@@VsTMƧeLޗGDʋKv>6R=S{ [!@ }d$]сLԒj>"s\+qmJ鑅$@@@^&#)}o2 $;IY}L)C@_%)u$+N+%-+AmCT !   Pa p,?$GĚkoF,cefYpLO$o%Yb%mɮ)8E8a(D@@@0~~]NK\>ڶ"$ea<=: PeG{ؾ+$kP sVl7[Ir@7-5N~3(W$kgm/  @Z*`zZ$Lo:Оi\>o-;tYozzUh"wIrøYrȯƿX6˱ @@ @EzIk5WI^(-M@q[^G'z*o&Y;C)3=H~$٤w̌4 >GQ H?-)kϒ%Ƅ) ^ GrkQeKaQT59͙2xHvpLNHC$ZL"G9_Vk3q?BkQb@ K`+SX<꼵QVe @@ r%R0BW@wc#EcIMOox&v}=X +ۼ8QMr@@P 0&4_yRc}ZRfm/IL7L7:/\bE:fno^.hd\?RMB~ 9w7ɅۏmOY={K"\Yu#2n*! @r}d_v5Vb7 <%Am?g[=ga]ll@9΄.iyJ5߷y֪Z?-^1/[@O%-٤7ef$VHΙcLZI~3Sk.j̹!+@b/ХbR?P @#b5z\|:mΔβK߇I^"]-J)9 PRَ0.HOB3.z$9_?t&ޖpA m{SD@rWoI.3EM^vA| H#PAXAv@tV6m3 מj@H@\?/,YOΕnI\J;RҥI\c@eI?g~8Eކ .oR mj/|bjK2B.p܌>Γe(,""N=@ud@}DoPj. ,U(@RvwګJRjP6Y22Q2@ EF@wYrͿ]Ye#!K/7ߖLB .ez&$ @VʣOn_=v=.z*)٤3dFoB VGQٌǐ(AG+> ԥ?@kv~%a'2sO<`(F ^OsL@Xk@Ct˼ѷH!M/HQe,#@rPS3^"1[K%;oӬohy x@<4!ق` PV> ^'*#'4k o9: VO@fa,}=Ke(yd~s38͆ p$Q( (($gb@@T0GEQџ3<9"Tr wTݙ}oMUz]ڽ@3M^\ge4hfHyuGKp`8FWNhݤOt9sc>IۘW}"ZA#\Jɫ]67OXqrrym 7mwu΋fQW7+8]@3`YD`@ol(0]s*xFlFǤuZGΩT{ ?)`oW[]9"׵֌a⿉,V1[EO?D 5̦q&'Gf rrG`{DbG|/fsG GSߪ@#߿ۉ7u?GN#KM8cX!Ŀ]^+OWIkW1FڔY&\p*gk-A@Wfä0,C!Xƕd4W?kR aca R 17#sOX|:]R<,CSdT&[/S[:NqKy%Wp&+s'.trݮ-9{#8!5AYNX6?UN;o%D|&1+~(MˇSUޕqq!pRr\9(iPb#՞A>&긩L:G|Zqw_^uS8_"fmY.UN#0}P$^JxGp(z3aUHVTdT:Dt.D*zpxɛuasRT1b-/cŦin}(h"o#0`ˉo)qXUsG(Zb[n+0Xm `.(dL3԰gظHiMZ4M>"nU~"~~I¤Ω</{JCjŠV'G~,Tjd+"T8#2_ 1/f5ŧ^'(*`V9Eૢ =+*hUj^Fׯթ[qVٕ6{ʌs[WVεBO>2w :ixlta*qGp(ŪRUy1z1QiW OO `iCxhR6uS!pTײ}_ΕiQ_V I[D:0 ϴh|C2[Y89#8G@t<KC| xL4dJ/#6cj'rT׻Cu@?Z'CZ@IDAT ¯1?8R@wV_.aUZ:yB,e.}I'GA1iT?$+R Wֶ+W\yL S_J2FCs*7/[Jx/ZnVh쩺pΊ| _uGczmvlE:-kwd"[,,;C<}[}czA2Wǐ'q& EEB˗ ~Ly$h>|ZҦzz@(*?G+5$%g,#0sa:EZh/SîkS~|ЦGʖܠ9u_U5?$NNNe9T#EʀOp鐌E'Ya4N^f7!^ 0` c:݆@$ ܩ04bٞb_ }˛| OMq4:)}ASj'.1!pRlL)ǟ^eg98DRYܘC'FK:m*W'x(tOJԆ/.^G#`9/#-WKi3ďy5bX\BN#Х|Vư?=H NcG=1R]3h2 *y׈IE+JHEjOa9G&,T G*{'Ghv0ʤ`W𕼓z@@y`h=fŅ_cVpQ;v ho1 dmHR pjNo-pn)Z,>D{/JO{|&w.da^#``+|˫>6n|.SkMd=H|D<@LIW%56MKc`V>EJX`X #ϖ k²#+<\gvVYv'~1OWlCpvT'k09ޞޓ,e.p3_ncuGNcCo; Ɲz*zog2_&USXe}i͔o5)8W8fzsG`:_0Wsp%gvMiyю@8Ju#߹.Kfю#0u`0 gxZ.c,myB7+%kWpʏTvM}@a:D`oժhm-JW}*bKs$X32YͿ:g僃Q2M;Qfg^u\/;9@9=WhF~^>|@M$`⚉r!V)jھPNR^#PʸpnD|a\^TB(+6cj'r(]W ۶0݆2XIU+oP?^yGpRڱ W},KQv#061dNB9CqL<If G- +T8j]<QDs쑑aHyM}OK$&L`8M`~#0 Z 6ZoR=& Ɍq'$,>ur#ƟM9\w8S k}ъ {|'WoJ"Y+#!c-o0 :TCc jǚӕ/i'?ӷ|ICqٲƩ\: NPnT><G`#GpX(}ya@(=-bvL+d,J9EeOf+a52^ИKW-o)/%%K2UP_:oauT~e2wKo Mp?\PP"!5u5lx=vAu)@Y jߟKLz0_X#)bV$[giXY\Z±Kpx蓦A2u q=y}Y8mC@Pyg`'XX[nP &DH4|:~ۖKJeiPev!J5ŗyM87?!^K<"rTx,JO?:1({|ӮsPu='G聑mjQ꒒ 0Eῖ=^ G` p5f"ϋq:@Y "P"hAlf> W</6oQ9UY%h)F"buR٩ʠ 2~#R /x7iZajX;7o9`'G7XT9[ys`<ƃu#*N{\;9@@Y ~E[OCkY ꞁ:=o9KZ *6ZFN@TV.l?;u+^\AϊUNZDFZmj f =*eqǁ-7s^鈀 }Edd{BYmri@E+ _'KJ .M+_188V\ ηV_m_=Bˣ,xJG9#0"}01o\pF@aQ'j݋Ĺp@Y tµtQ?4@m,RĊ$uy⧋[ݟNIƀ٣HstLmf-op#8"J08v"i5cwq!m^"{LUJڰ1%cLEVC5++umVUgwS<{49~&;Lh[-f!rGh'|{)/G#t+g,Z4<<#8#P~/>Mp䳫;#p}i|B/UE0aپb!pcPb}8B\X\N#L[&s_52G`{>Gr8:kX#2,T7M@>#tUXtw^iцoG]4=P_df_WLOXՎ#PcuyL ďĹpڋZʞ{( 8{RGDF&~jgEp&-{/sFG`X9#5{杵HVrϞڣJJ):.1w |)t;h#tȪGrDEhd(XkK)@Y(-7ӻ@j) p:TP O+YnKśKܔ:˯j84MP/PU]4`uudu2:#ixMJ>w!#09|]Ųo,cIi@OIϺ1dp85d"WV {pڊ_5:^Կ_go 15[ Sx}} Y .D`{I^ .{@cF[8Yu??Ń@+ltv9]@Y \61?E-ۉ/fЖ_G|5h^AQ%lҠXf#Ns]k^!q޼F2گzV4 "EG%nxQAћRG`2 M+>M2 ͟VJpg\J65t{H8fqṕhzBGh07MF\U;B+}G&=ƌ lĥyz} [Źӥqĵc\S>tEiG+IQÖK HdT{ DkUb \qt@jF7+W>hj8mFkb6=U|^qJ[(_߉["LK`:qI`gasSY'8%C|,k2ss*vUOé<3Y."t|c¤Lf.qN 05jЛgW{>CR/(ƙ\y{ċ7[I(yW_=#0jL]>J]Ws!&}29)|_}*) i  WΘ]J| BG` Ӥ c?<(xgā_Ą'!ȪM L_GF \)ЫgZ|rŽ!`!=&Me܌,jϓfH\hKT}-%awϿ[t(Ҿ֯d?Mccz6CB#oJhi>Z\ Re *ޭ׺nT?G"҅p$<h L2@= ߒ7xs,M H L:AED<FFMU_We X1?Ǒx /"?OPqY{au0Zylء2 o2YO(\~*U<[egKH4%ܬ?8 5`5v# [UǠ1Kx3q:^I_,zv?t;<#[A @.Jto(U*aVn4ʩEsi3~c)&Gf~\zlBe2C9u>p19S?aʯ!w?vl%GƄzy98Pڱ6ϓ #-uh}{SeZE KoOMFXL=K3GgRzm2:Z:Gm52)S8GhDnc&W*xvRm U/l%Nԛ MSYѷq N#YXaSbt ? U;UI4;{U}6Ita!%0|I~-<8qS$XޚIrs8ǵB* anE0ys<N݅WzuHsX[!lS*~"=藘Cl-Ṙx_ 5~T~ ae&wxx+141_ 7eK Wh5 ^c 89F`/UX60cfJ=}=/J]U_ȳx[Fn+dio덟7^2bxS=311Nm) /!%dʜ<Ob&ODIMp!s3F ;#1R7=op<=i>g|``BZGo\uB}?mLJ]ƦCVXR1F<ڸ?R6ٸklco~NtGo%_$Ŏ#0 ;G9@Yheu}ɦRYj O'c}vgn1Q2W1&,ZG]1+VɓMp!4,wZJ]Ip|.ͫR/ e3VA2tqybƴijēEש`3-g6I(L#0]j d, U}'Ghtm1igmŬnZG|n@)7{-O ; UVo׳*ZN3Z܌#+n*?"f_2QJ'H<-=L܉*fԤ7|I|}y&I筯O];ԧ59w<֧7U:08t.Sk'A7gmOokżL+`{0(.\lc59~'G`:#ETd,Pu##P='\TOۿE|tn32:Ńi@)z:Q ~ȶUkQef=]_ fVLh[:iR0iM!VPyIvVx`IUÔo]mXDSgU@|e$Wyyb0C M4 )&F\.޴ N#0F kcjB~arQq/>ULW KrS--P=6Qedw7g֬͆m7o~`5aW.YǗ;z0p ?AR薈ULRHf$CpN׃2ٹAߑd†lymkf_?́ʁǖ%. ?$<|ǃ#?$u+ʅ9S+ o2 ;*5͙Vmya oa]B?4rpR|؊.Z{-+Í"t{>䊏,rJe6Xf瓹6)#0]-eAv# -&k:OWA?W|1SsiEڄ$m{c _S_#x+q3z"~G>YTMhh L6W)mF5yvx;Tgb? O SfzeٰAj{ õPkLG{gc`[6_HȝjV7*ur"`Xӷ#3YLMQ`v[8H5T75RhM4_^~KKomfOd(k [G&̄#^ͯ:$9wh$V1A|8#Ҍ81ȇYW&eKOo#|7K^nTOi1iI*5dT?a`÷7xk^CLT`mr$07 gmy2s# =aӭ6ZZ' p\'#^ _|?LѨ5,qmd}˥ V?~sx{G3qoA#`-)2Xf=zaiÛ|boc20U3iCrY\^F̐f>g~ 'GX/A>i_E'@_vzzG>v}ЫT0FJ1G2|A}g{A!o'Vlӯ㚾魾{~(~)XXNx8L|l62*6vrw_$gYor#Poو܈A,&][FML;*G&Ht wu!->DEo(9qLpOw#ٍ .l$LmF⯈w7m}'y 8"eͬ%Ό(y"gqqfEtZMU8̦G,0?#R`-yY(pӶ ߾pMn&Ώu ļ>i&PȦŅ'<~I=9:OoQUt<2Aj~0|f2VFtL%YЌu~]O+WnM{TIyb~Gp}lpov_Wf/ϯr1I/VT#/WfĴ7ZufIwfU yaqRN0Vr_i&ޢD)!jUT$}Z{nwe᩻zzW)l*o4b89ᘗ;9]篝T¸%IxWMtPT-4N# P dȻ{xbE P/#tGj^^|}qo1' ~7Jv~>nbsOfBdVm1+ i7~ag<#gC|bæF1G=尊0&&JqDn$vFWQ_.ɳ`_CUQGzU8]}+>yj[àW9?y|%i)as~D9 9O6l}"gv`hg"J*l'l_? uu_>&Ⱦk![W@Bh(|!x^^&wie>/kmeU' !-N$+q& a>13h2!_M|x]'~4}[fӰ{ʍgUÖ+lhr1:ߚ~c[qerM0¸r]> ?vB\Tq1c6YG>ZQ/3B]nP# w]ڵ`as3mu32T׈rum:jE+_=XW ՝\잷 'Gh/Vg'Y⟉#w8Ƚ_l*FL55S Պn}+)tc7=鄉~&MfC|^p?^o=͊g%-Vό{0bK$BM D1ӁLƱ~5>do]vyV׏7dv HJ5}dI,I1;%/xΚۇ*{o]Xfah~I|yniꀣ0:Y8F#PRek3TmF22s] OutrC.< gb5Ƃω֛LE,8X I$䵏/x\zbG [X ߩGBZ&bVo%_:?\ U/1r`ήnRSXD9(WOlVsaus*\ uה dċWwMVWUb!QknA@t֋tǺ4鑥y !P-g[Y'ҜvUOY1pSxP$zֻ6||sw_8W|awbXzyWٯ!YZ zqe) !?^g?2CM,}4bF2e qΩєyҴt.Z"l}Ut& Ņ;/a]wvG\=AJ>[m}ɗ>8fD\iÓťyUgy|ѥ~=3Ö;~1lMki5%x qxj`akJ#Pf0Ե K|0I-i"8\3qƅ c"~LWB-AZ< ?,C׈f?Vća*Ql@TjVWZZ>O? /~ Df_ 3pF#LCkoj\*Eha w;5N3>ZEYz2̊zE_C(;Otd;cȦ4ϓG+Sk},auq<>0}.:ʰ޺<_EWyetØ_ٕ8trfCf)\0il[UhT I\*)~6y9BPm,[Y)a'GH Na2i֦T'~Ȯ,cEn IB3II]G%)q/z]|R1&@㠊VFU67p&^m]TabH\5+CDؠg&~&J'wN<1Vԩ >RFccwVU#؟ŕ~S81_fWc8CQIqgqd8ΙE͢C$| +j܏$IF4T 9Oaozn5S+(UZ7P+PkhɔYVV%_Wʘ6Nj;FBCt@KV\9 F%HFT;O2GuE}cccⷋ iy/ g|jz`E"ڐb)*5a(놸X3SyVO\EE1,CܧGȸ#):b4JgWf Z/El7ktvt<9u2Wz1/^™!O-/3h\'_ɏ3+*o >icJːv1ʈ H$@^YZ8G@>zb0! ~qã2*牵]G`2]*1X:cqwɧ|#̆ Eٵ z%IAVAf%>rFm *lyt1}˕?zޒQ]u_p Le6о'^+ HdbyR"`+09x{/o2!BiD8Fvl, ΐY/N&+5cƆ]xcK=PvobN/dQ8mHnLG( ft>ҍFCq܌[Cԁ0LLtq!ᕯ {N{L?ctsc_?c$oYQ#/0Nlx@y_x^iv`,S`AbDƼL3J ccUvc|U'o_+1_ DQN\'48<"TY <]KRV:8mQ[.%JZ(MDi*'rI{FmQ }呕^h9 2eFgg3Ęy@[lSN.je,kiY7PSkeQ.[iղ##L_/'`xha6r,G7`((Lq>>~/[i1kB߆'٦~ X`A&xp `q9q|Fی3=KG0Iw,ر"8&I_),؎.44Wѱ<©  Al*K^er-?bh+)9rبۦ9E+#0ܚ7O(p \'a3do-EV}Sšs3 I |?ΊT c#y"7wmꖧƼH,"}ԵrV {REhOa}UOnH?X9d9Y,y&|JHJfI⹑[;ѲbNyUBJ⏭*uyR{ )xc.Yv'7~WskOclC^}jqy5C0]uab_%eZ2`z}gy۫/Z_A9YwC}Y˙57(C'`.yWd[޵-enX#_ tl+( hu3?ևؽ0:Ed羹Of bl ٮM2 ė%~'=fl1׎g}XN"MQ&wA FQU= ﱏ:KWtC/[>bQ(0h#B*=xwהv⦃~nqy39:dr?ݑmzH6454Zh$TZ }V?d{^̃yGU^DuMV){c*֔IoF;L}uJgaA0Y#13bJ|S3+a!qr%g.yy̸b(3u|6 "kU0 Ve*RF˔L-wjuS!~ΣvS L p N Y(K > ~].ˈ@_[%603sgn Dc^:0YTp̟~٪> Lc8VC'ͲxX՟Gj1+H bn#;Żg’6ߚe>ZLLq,j2aVcM x '.,lpKzʧ,(uՍK"&cLbW$a]!+L1Uc$aB.&Vwq5̹e~aaJQt^sv8 cDҹ+d@ǫ1e|z34_0KQ3&ǝxf&eÜρxokƿZJ)Lk] kc& (3IP[Γz{hPsF\hOk}* - W|"̘1v`2CmbZ?ief90ę*]φ4O*\^'KOsaꯙx$ fT+G@r#l@iʟe),Y<7a N̰"O,+e r/?OF?dyu#_"'*뇓g~7Sϓ# U {EST$WU6Z*oNa/F:tڐ,Qrk<79ki,m.OOy4ǐ}Fv9t]ȳ4WYR=G-l 󢏫TC*á>xm2q/ #PRpQmYi:Ke=NQ!W_+fE߃V_nuG(̙i:/m#,gķy2t2ti7,v6Zbn̾vAf 6pm0[gSu!3 Py_+X²߬±\:Cb':D?l<3eM U8+2\M7śK>uUEJVF80T!2e2>!֪N yg8l)o1^K7bCX`().F'۠,*WPWJг1aӭOŠdcgVYU89@~9'evr7#I]t-lf]>:\t,Mn\!y_ϙUc m)oL' ;剃4vU` 6 #0kά!mE At`d x q5<2[aW`YRtN3)3e2T-]>28 W1ģY+8*?zODԓHѹ\N#_~Y !ШQS󤨎.\Pڡgf ={m׳eVtHL°WOѧҷjjg_zյ?rΆ !ˆ8t0|-t0CV<&Ot-M'OgyZ{Xb>T.*@^ 8a ~9TYEžb9vB|Y+; 5Xdڪuf9˛ӱɆa# ώ1g`L2Y|e@a3 Wb2WtCyOT3 JP1S4<&]?iec:.Ӗks5KmA7q,ÎmdBVX>GBEx._xAAq! JZ#7Hx-;m8qB32NN9 {ꐦ#e -rA*q()<=&b wţb!5`S#g mkx1$M2p<ǟe6gpu^]fGps;5)/:XBj>cyKi^bJY/k%=fY,smDmFԭ1/9 1Flc]+gGnJ4Gym9=Fa׳yLNLC x̓Xؾu>'noc>ͅ}M/ ~t߼D } &eXo G ㅫo<=4)!X֟ɿǵō$(ixv.^zIwٹ'ppUXeod P EFU ջ%R c0D2-Q%^\p_vT2ui抃{}߿@vJ>.&>"/X1Rբ$w'?K?.:>ʮb}}ET![6 )He'  9ix%o>WŢQCV$T&C$ڃ"n#: _G=X, !lq2l_K78c2z4ee.=l&M8s>+0+pz)K* ^sy E?h.{9yBvŻ!$ Dctdj>Fy^O&ށp//X?_8Mkߠyyy`nsy#3P<V=晗Yw]KB~MbO/|_&y{aGisn7zNy}PoU.cX#lm?X-yt{_qCKӱ\X\|~b yAYĠ,Fҏ.=]RiTH,Df"#rdep7屍A+"p! A8/*;3zC!GCq<4\@]q Rr̕"Nd HbD#@cB?Lk W lHvx@玓+rEwH;ꀲ{$pi927KPɬA@eye|胏~RMir;()sPlԎCv} NGƉ LcBςC~RP)a6 AܔyS rnǽ.9(aNm37 (3L'C2%m`)h/Ӽᥐ5EOS".e?v2Yx?򩘻"vXw$-& ݢjgdTT=`~G|z:}7]Adp},!Or~.|#4Yŕ1?;@}Y(itTU5#ntt\{㲵/v78>!<$!!҉`#@ݠ]h:FTu:&!3y~ĘrC(:d4ALj0(V6hXO0QS<رF^tfp^8t;\y7P.tIk&5rK 2]_y›ZHۄ"><M}!L̛J|<9 , cؖΛGyǔ?u8ٲ˺xn>^r v|B@swlw/Lnn<HPM|狅3@\zI!'nD},`X_y!G]G.#9ej=~#}aC# {G7! `QUR_UED-Ѯ$YTAV8S'4o$kZcr>"-'JPuǿ,x0pO;xDuät,'8\)z5iyԐ3̭|j^FPئ3 sGB$M~v&iiyiO׺Z711[j#(k>$ML͌V I?uw[n{ۥNџ ?.ײLde恵g-cΔb8|kgElK+}2'PzLu]arC!OV$!Q^&y5B?"_4F?>mo8Dq!Ȇi+/Wzd;3A7[W=>GEA@&ɹoԮ難k<41(#H tepŽ/.ܮkz>cKf'#67w1,<swC?'2n"'݀#' X ;mru"2e͐wQESH#A RXIBieZq󯸠vXm&VnE 1 zEwMOPc}$"_=O&@1 %+qS(G@j~Js)HOҋPb)2.ܜ}f=Nr{<']] nqsz+lPJUNY4wOk J?Cz,q2gȣ)4'd%e-Paf p̽eJ))!}JQ;ņ{5hX+p6yxy;6^zeHu_5C );|xd<^'H8PLOz%#Oi 䗺kO-lsT"8E`O ڡVFXr><!G`/COAn?xPaSWݣOcB|5NzL4] Fbz^BB}_s̶Q1O,i(>,vg%ǜ2Ǝ9,>YVjXvz&u-%&O3'=P|UAN~f<UI Ih _?HOwnTs `U\܃~<`1!J4 C=zqm q>65o* o!%/)}ciG׼DQ0^l[*RH0[<{*^/-BϯO|2[s`X-hޔ:aZuV{Lu WG74KM"D2a2 Vgb> Vs5 @kw'| 뉀+QFiՉ%IxEN<#SY}U4菣$K;U&kf VZKiV^f|@z̗nS#pdca3Pd>qrΏv#6sD#@^4:8>H ]6'^QFK̗T.F@1_x nUIP@j-ʂ2HT\ *)D<H'ߠy`==yʁgÓ;k{EЌa~ Za i1p09=eJ9@˖2MKa|j^q/dʋi/c *ha*>+*IQ&x]f9R!/ZkO% #:+y`m1Z459ifv;8/ۀONZX},,9i<<=pκh&K+y{.^00dbuW^+#vq7!ந34hZѣ,7;*\$zJq~;Cd`$z]w+vA?vyI_K'w3 ҡY}A‹{P% PRS'^y^p.GeۓӮAçegr/z7v;UeW` Xщ%vA?~bm_.} ?} ;Dˎ?v)MWs(b{5ej 1Cs4%AV6hXUxq2Wd2xya!R?B24GNg2S;mP? )2t3 iƂn@]x[O<^@yeW QHcm<&XV$\iC;˃qeᣅ,^\g첺o~y`M.;wR o[swh*ijM.Ϭ,BN6uDi4x\qG$Y+ulGj}!g6z,e!A^eP>0X!w49{PU/Q'g]%S)>XjFpj `3H yBUn &jh4wa9yc90SFM%tcwPR ZeKZ()Ak{1O%CJ>zjY,7vzf>CVKn~D|f_@xW+bY-MXlȧD+OQQ[P]ѡBW >큢L>mXG~8cj\O{Gɏ?*\6@ Yt7C }+U/W1Bω s9!%car0 r! xÅ a4UzR&@_"\";~ #OsI|W*V&kX!ɒ*#jZ.lRd^6<<<`&k6gT?G[.vua^CH88˿KԈɜPn ;F`aqJNPtӎj܍Kh d+ U( BFe#WrQ&;]ɥJie,l4`TZ:p}s?oo#?T?*(T2?OJ?)?"m* ꈲjwAD f~.JG)/VIǀC)g8_u]j#v`q9/v-ܮaǘ`8=1-?+|EIeoG62 |Y2v?n~b~7R@Lb z8ݵ<"?%Bv~QpBO+Ze$BZֽ?e5}1uܠyy`{𾻺Cm[}uwC-A6l)q'LxI<6壌2jSyvWz9 q$ 8)/ЋaGwD#{ %ODQ$۟~ۤRw 琼mmIwP=8H2$o>_ FD@n8ʗ'@)=ɢv;O~Ry_G<[9VW)Syi߉>OSp47>:/s3('4|cyQ:-SKjbn$¹_iZ`_L?;]bne "t,W,QxL%#OsiHc;t)E"G>FZ%'_ȇ>B#} ;`ޓ eyqO OzM`pburpPd-d{䉼pQ+w|/˟w[vhKE#_xn<=ϹW|c^v>o?ggƂN%f:}rz;.Gk~%qdiY怟`m! }'"= #"˿ɬ1_ro:Niؿ?`d τ&DK?S>/#WGSE#؇+p158FQ%+ J>wJ.Hw ĻꮿP:DwiIՒ*kwŮ;[& wwAFWB9`%L"U.@>~D/l\2=!"J 0C}+_lG?;=]w?wk#T2B0΋3:#RN*owN[a%go'=L[,2rPyô؊9M*z_OЙj[{`1 G$KB y 6J0*444l,xXZ|XUE»L,ľ# 0C\p@)Џ #G+Qھ)S%ǟfy`=}g_ޭܷ{䋯okY ߿_ sυq3}P@XَV :! }|;g>Uu wDÁ> yIgGKUG,7NAE\:qȨ?QvK,QiABA5=i89@<^[FyLJ;9$$ ͮ" Yɏ^л<} ADvNw2vA>cxןxοu~Qq|NcDãEVcѡ3.%c̶6?WC.6G=_|k%3T< 3roN}#(C'^_A3ο2AEOi1c'ke~Cg(oI)ǁ+Bg^Әx<`iT9LxAj{y_p : M#nB"J;% }jKXc-:ZJh!v)?઎fvQ Y?x!݆mtJ_3K2$S;qrV$r ()A`k_~$'2?=';`h;]߯`5c/vj%2';gmpU(idj=t>rIdQ0Ʉ?l)g=4g9ٵ 1ֆ|Ok1'^hdoj(3`< 8 RwAAG|78Q֟pPO1! fTqc_y_ #pB؂*{9$yʔ0LR5Gz%Aj{x|X̓7+R0Cy}Bg7N'Bi \)ι>tt~W˕l<*`sE1ccjMFxw 2PV>V5“gTB=.d.]XG!^WP4_țYk0Z>\^ u/PG<ǻ/@5*~`@Ad2K~-SnfӬ ;3@߰#Upx<&̛8{n}S[Uv),'Ç!>GXsxWE_(W!vQ7bU|_Gu|샶0>GPtK9wh$]"'iu;<̍X:[? 8&Ics(}aZ vl1 $Yrx(d=z<ҫd7qJ}ÞG22UI =("!ʑN%"G ie9؃'H_jHg PoJpӒ/TF*8!Jx9G~TG}o BѶajſg-l/67<۾Plj( U*wxDk+謞W}(y̻}{Kwv3~i| n@g@x34(Io(2`(phH3k^߲Qۿҙ#,%u.u~[8E̹'dfY,͢6#@C@?5Tcy_hHA|>#U<: x$ļGH"+|nƏBg6OM]׸恍 9}"q\{N)OWO4Yw TsCgixeKj~W;XeG|e<cnh|j.Y}ɝڜ8 }. TLB5?syeb "Pv~vA/t!wH;b _) G{=)LID>tbP2,Y M*jԘ˴mق NCGv-(-;?tOt) Z%Ӓ/ݪ)ku?QZ;ЏƎ?FZ/f:n? _2qB;cPCX}8ј{nȾ/_z%5=78.#7j}|-с>nN(/;d7 jt 'q c vǵwvߥ ۷0}%,P (Ʈ@J|tGջJ^ FO؝HШW~|ǀU #c@vP@^J89N[Ԥw{/|b6 YrmMM])2[+,'<<Vk2۞j73i.,?r wр>$Pk ,tj; V~L0v  #QڏoԜ8EwQcy)Ayӆy@dEM '܋!?>xSGg<8Z޼2', >Pi(yٹ'S5h8C=+ßq5X}x.U^òLǂq ĕVJX4#w>6?ߝO@Zc_e:(AWP Tb0(rڼ1`jYljet=ɲ4A{gt8GreϬ%籫XR{(-J0* qU&rj|\Ǩu&J:/V_ q;<a>QRy ]YܾCL?"`=\|= dead;֍B>E?ܳ,Ɯ#4х:/n\jj*t m>@& 1eD?P#`O|Ld+I+v-h2i? oղ!W~%ܸ}8|^bFF4 xdI=w ^aWvɭA8,;TJ5@XI OL+NQ㽑_uE1Jn??*bq]c uJCI{ޠ-ސ(2 [=JyI隠X3Z^q*4˜}PVSd kp~K:q(*XS G/i;s=s R?r,I q$%xjP֍V'/~|hvv+aV _>(Bi}؏`$]9G9~y]C,+`/yqs<P2~ _`靂U74&!Uy^Ş'݇u8_HVU\KN;; EY?.^@v d>Mp vI:>tj0ӑ2NҮ0,=N=Gf( 2oVrXqEM<`57'.̔5m<}30}fx]\k_ 5pH˭|?kx_]~?jC"Lt QAx VxT6GpxkQ8Y$Kͳ 6?#,yGF<}\ ropo]=~y=!:cJː37%!ᲵEޓ<>$_TAݦy\'cvLm<ׄF ϝ́rY 1z ;KY \W|zi]ϹS{l9?o=gGș<ù '/0O>BQ.ū @a>2.g<{.~{IS gg2Nx9q(\v6>?*R\b A=$ZF; e熠7!+;r|d1RA:<"P'AQ#&(q\wE5Qyؖ2KGYeWJ^(1:D4᜽`?Xf {Lg>,# xLx@Ӟ=cyG OXᧅgP#ƖXe}uev+_*w:pȠ<ɝ㎱L;hWow9ɓ;- y^|S3w^r!Vyꨩf c\;J(40`~Яegu;ì'bT:̉"4 % Zᨪ19S%llߣy))SdQ^qn _TGwEk [|ͽpaGs nc- (2$S=pN(R Zjf8 e< yi\j!{,SVvF/hߓ9 =NFՄk)/@ИDs:g%sg{')[P'L:WR b$N~7 1c:o^)w]%H1CUJ<-O)EZGW[&3*LA*P2-m<`T3_c cy4zrr?C.2#§uCsR@:!< ;[_WMg>뻌mٶ_kJjs}8~xp8 XǃdHiTmhhX_0W1yc2ofe J-m ו tk܏QiVB v/U*"kC<+|e-=K? kLdJlq5g479yg(o\aw$D*O$UeAU yKb$ 6J9yDNf"]{19zb [r(أ8SڠKi9y%sz?(j%}T e24`}7=M`w{)}3ˆ̞ev'zZ5Br;c<:PcccjHW}e\[]p%+t9,IS|L҆[h@@zyy|C / gn?ԽvvxQQݪ`1ca|vmCQNUn8/eJJGO[+?vҾIdC>e v0_uиAHY,~,N{ʰWM!$BN$wc* :H DP*_uL=@8^屋ܥNJGM`O A=W3vFE}kyWY^Qh0ʫ]WKβ g WP&[h^,A"*O4\g*͔68.рc84mͣGϣO2.ˈ|Oy^{~d{+d.a=8#t;fcm3Zfj]EnTY+; `A(/<< .Eo*6t 6< 7T̽H禉wU'>w3j?ٛ 8XGcW}CFZ>d~?`$_OUxF !0YeaB<}B>}d=T + [F"NjƼ_WW`{>LVTd9lR }3WΔwkJ#(2-3NgD>tߔߍ4NFDkΙ5Iibgkg< g`˯Xz"C%;$XnXd 1MQb}fhZ#Ve!#}$)w3C`%]vwW~7 7UT9Ҕvؠn3Aib48 /fOy 8f{1|d^ Cѡo-w_C,w  <U[equ2O91BC3q15.ZC:>(΄`x444lTx ] /HЇwV>e[_{Wo ܈xYt#{UYJ*]<~ y- x_UXA9+1RzrQ\Iԇ?GE16g/UqSA,eQd&=e#P8^VxMAuX}`O|<+qTB0N B .$- U+)%NoT YANĢJO4NvCpA>&ey^jя>05hXu0ʙ<0ˉ9F3ִ2 +=֡@yS MY5/6l~1“|]BOJBg͔M: e As69=)l=k8C9Ә]8u2ۄ g`Kug?[`]\,DkĮs֏#䢼؟eb ξH v\ tX|%(e(K,|,W R>DouxY6 ;R(=&AjzQ+VfW)ȌU0px4 +TG;1 Y 7L4HW ,'o #pܪ*[ұw:wX/:[GXf\<=(@YF'luvsj_zێ??~[Vbf e8}VS;<5͔}B&_n~Z2,D 5HHHK(GyͲ=<hW}Q0<!xh=q+2N?{GZN'f =ey j;9 oۖE j|z1r9^þmOWyF'm_hp_2S-{?}`sgYFX@:@#iO^ m&w!~C'#c:UN킚, ƣ!9֯S29>8@I~7+=PV_{2^i/#p(ۥ>Kor]oWYypu CLI^=kN8%D72e<; Pf'=L@G~dI=¿~D}6uZFx>#^C>2%懺ȝr紒arx"3 t;P?6yyy`#zy!CӼ9HЁ9t3Z-Tx$XTBplxO;!M"q= vJsGap y8peT6vSe\;FŇCZri 7PFY&X_2OVmίWNHJ:ǂG!K 5EvY#m;Ȭyk3o9 6Ο9pqin<|l"z|j=ڴu^Uq捭gq^s,n?->dٓIݑG&=aE>WnZ NUe +I'kw=9hG tbz%ش|_OsF^A_i[^+otk<1ǃG73M $L .=Pc!2hFde/tC^ Ӷu;3vSR+z4gdsA5yyy`ysU\3߲bV91le;g](]OE9@ѧ %63o=(Ώ{q⽊+ǺIx 9Sx\r{ #Sߩ]NsT-ǣٶZ |g=Bh`Pc(mBЌz9:÷wHƀ񥭁uzm r(}i-4|@Njuz5ЊFޛ?*<X/A?v}Qx=)b纜_瓆Rdyxa~γLر=(zQ~3x">#OLm8clq2-g= W,,.#f36ʳo=_ F?fq/7B-0׺YvM]f8P&0W뉠\ Q ީUïbG:E(2HIQzV_ǀ7,a `/+ٻn1%Aj{4}5qHjQ94qF'~~KݽA0B!?k; 'm9ƨ2x;h2e.2#ݐ FMo~w摿dcϑ; ćY>\ w@IDATppB"rW0i"3@_/X87(r׽~2 ֪Ie<(%*\-ړX!kYt"VrفǦwRm;.m!/ukl=*;O'9稺O i"UOI{˜F6֥|`PkEIYz5Bҗ\b{@^ZZAZ6Ѡr6oNCˇ3694ͻ!_8?Plj`x@Co3i5MP&ϓqqƁ}`ޢ§ pXnW|ogu G/<'.Tgx+UPiIP>(/L>| vLUBK@ώ1b >CMWش]Sg~%;m4ye 2— ޸.!g5`d3g1sg楝i6d.7E[#ܵRU-|2,bt}>Hۃ&S2,3?w%/y]&t2߷gȓ4ZC=GH2Q짆y-``q,4,~ȸ8 w./37n~p^{+͟:JAxM/AsyD9ѐU~A#UF㯖 ^`Krtl7ҲfxT倯 r(*%dFj0Rqv9|(iIY%PBFk<Yn<ȭC!۴.=-YgF\泞ytC][O0N[ߤC|u 7:D_!|KC(>)y'k6pr蜽;|^q54g%p %Xb<:G̙Wݹ芢`'=z}YPvJ#O--R@Ԙ,2s|Vh2jF5hhhO3C] !QxQe*a:WS]S+0Z< Cbr> `^b\;>eAnIQ\^W+&!E6$=8ju֖(g:dt~pV8 &dh2Lc j:.|C/·Yh9x໅DP1ަӓg -d0heyt\y2S|j,m|2s'rاWtA뒊]p䎏gxUq5æz鎿baOiwR|ݖ;+T+(%^o#ɑ%F'k,Sx~1/q |"d~^9y`1eWy2YuMɟg 8m>̻-N X[ V^, 2.ҡ,Pa7Ib29R./N@*L SRE,VM> ^˨ug!ݗ]Uf{n۞OʺN 7L9 /qUV{iIo6iI|X H cG6O.cN O^tzͷhrA=)6yL' f'a%!##PzzT?)d' jzہmtR`ߜrzMeٗ~ᓄ`{[XFօe \3e°4WJM:՞="hڪ=:S q}rGhڽ]"/,K 3!i<<Lt( ~X6o:PmYzxS}fJ2ZN-e!J߮!O:A@fb&oUʴ 'd}Q { Kk{b*3I'-SLW #_:C_;ņ^xׯ|/ʦi}:l6 O^QɒFw cjx, qCS4<=>f]tl|Nϳxcy7{KL=w:m|ˆzȳm]6U蹝cK5A@&a3:AmmaüBN 'ϋG6Y}}l-TwH4AA@[Q"HB \&v·""!bޭ7*w#VePw0+7MqB*i^+ЎU'2E60jp:Sy0Gk_x-Zurޙ2!3q糃v?۩Kg+Mɣټez8N:k([XAnI[cl} ʭ6\Y«=ܿ΁j[q /þC#eGMD@rLٹvìeaф1:)tjQ .辊++q X0q@UyyO#Yyʍ _UmuFӒ&vG8{aA8Mk֙@g3HWw~CV<3ٛ…AWg㔥Qh#gafO ^w$LS^YF0H=.eK>}BnSvV&>10k]taAAm!1dwŽcc,v&^ҍ+/0dIyMIʽFsbb҈Lorkh2֝2Spގkj-߉¿ìP VI`4!vl5hۄ}ΓpmSpK| 锩,F38s@鱋06eaWfSWB]pryS q[o9l e>{Nb7i_7P)t:ccS*8{ @y. ܇poK]YPWG!=2<pJgnɜr9y})LIϚ <7'.u.ۏ}~P&Vۺ:Z/>5Zi !\&A`Ӯ=͎ݸ5iIFQŽBOaע#oRU7uk sv6+F#TKyt H:ih?C)qN9Sb҄@v%&Pi}C˜rR?)1 8\ic!K FXi\<+ K}W p_(uҾ$ԅW7BL}#Z_\ỴZD=`q1a*'M(Y̓L{rEG%d^/[%8|C8CgM켛ؔTh#]uQQ;45Mo~Kwg|W%O<9B;<,AyH+ټvZRFiϓ3P)2 q>ua;S4,.K3_ OQR.Om`ln)M?W~ p5"S;i).p͹.Ì:3~RK"Ӝ#5a\qܗ9˳2L<@4d) 0" oLL'L久p/Zd"=v(OSJ-?.I>WU*I&K$yBdI57}S.oF]bcΓ6jفǑ0"Iaq=O6pFv5{,I9OdQ{jD|FlwXgweix4 xVQH*dJOo_^U{S=&Xflv =pvΚ2uẟjQG5}m@OXŸ䱪>/4}e4qg #a`"pF=N3a5Y)4p dϓK00z_ x1msv( OlKKլ 7kQ!mJ~xwjAx/5}C9*y[<^="(m2ec1^q-Ɓ~~!Stl; uDs< :xJɀx; 8dsqB?0y/ÔsY2E a e<1hb&pص;l%|>A8]@} ^-m>SDɎ?&>F}RTN㉒ôznm|,4B,nX'37>^td)^qp/bKS{%SpN$W"քo)ؚ=l?D` 2n7?.4;HwqS| s.T4I_\8|b<ƆFHZXW(=a4Kyxda X9`1VP<6[SWgI8,%@ aov7A?jXEBo.nv'ars=$#vOɐ~ Pkg"l ]9OfJΓq:;ǧ-<> >mOF?˾}.}'2͊@9 Hs jpoa+a'H ]ggFc<lq`s'3`ε~Ow \Vk< 2pce1!8m>~)MLA @@9X∳Ï07=7Y@ؚGܒutʕnp{-0/nmkmR8>)3.*>pT@ xE&07@Lj1!fc qe{E%ULvn*,0eJչYoㆀK9vOBp$bదllc}E?G#~ɦS:۝DB8M ώ *?Mp?hm6ccY/MqeM!HG8G|Z%kM?Mi>s}5y"3PcXW{ W.@Dl a !থtE 07@u0YD{hem09S`'.pDFrie $Vq;JyxI'I˰m l@#h;1q{s?rI㺷|PSY ݚޫh.>WTZ6q3ihx䱌q 7#/ء\eimtR=VQhonD>qcz=Y;ާwhm@6D83jd&* n s<)gׇ c&¶ҋf$ 0F&/ę<e& <9pyw儉07al] a͙!tMۄv+H[i$a+A۩u-6Y+i셶"9OouΓi1?g48tc-]" eZ겢rLeZelA7 匲& (XcZ۱n3x7m 8׆5Ma͚vv<.nx6b/N]l]oL4t$ 8{}Qw 6yqmGfb a`H+Mtq~n՚s[_4 Iy# O(|^qGr@~`A0i,yĄ>iMoey6}n5 6'=ufÔsْ´wv+y{Xg>,*RHGwSgT{LC7)øTjw2.McZ9ylgTFªRlAچ}="n}Q/6m-Jys<: ;i3ao\Sakl0j 08F@X yhW7VwQ+y&YM'LLf 4!ݓt9g򄳄 OО4=Y-eX _0v;v,9l]p~`Zq 9O6t<9/ s~2)(Wpo&0a491(4ca{քq7>rv->Cr\xUjGN4vfm#a`19]eB (m]ƺx]#a l_~FUzߴp;oz0 B:'r y "O9Q0M2m nm$liq4qd %Jz6rp,W'3ϕI?OVY}=_b|pIpE6dBSUlw5o gzg]}ôp,Qomr}p}Lw<Vx~ uk13~[ ghmX؉H!l\m)A[.4ym:lMz64rޮ#a`51s{}ZHa2a`0 a`37kg6+x&>8qӏ&ɇ M;qL<{%V6&m>-n8pnMhE;\nvlaxr̜S9OVI>Nt3[kjަaRTV'8sB?>MBB>4iı{<1ǜ>ݸTG˻ap9Ƹ!ce4e9Sn4K-: 6|ݭbA2@@&q4&Џ3 Fؓ O3qu9I" HsX v44nqOiny8]?iaqtΓs?*7`KuO=q֮hO Cvk)|26f 06@ml$gK@Ԍ;0&Ҁ20emw]ıЖ2l[ta `0h馑ƚ:75k+2aaF a2'Ew:ЈvgRfIؓ@aɚv5="aC{?F#->Nְ@'Ϗ7L~8zN8㣬π7$WŽ질"kà=~V9QӼ-?~"ԅv>cqa{=L>bw.lYo[& a 0`\0 ,3MFУ\FV>LO8L^H8̤8vOؚ4;\jOIx6ݞR;LӘ(Qp4y Dn9v ?H0h b#rfM@0aoCIۤ/Q;K mp>q^7qXʰma 0@ 0ef:]/%hMoE m`eƶKq>ak&>Ѷwe}*:> N}|L͡;ys#IΦн݊]k^M_򽢓bB,M}uyvrB젟C.:ЮZpe3Q"peYl:\jžXFxr!9iNWp6H]Xۋ!RXv˲9O`6 }Ocz4M{zyp?D 379.<00Bq1OGؠ-`G6oa Edq3: 0&4͚|FMvMI'7qO!NpZ Gg}W4kҼ ‘06d^$'D@X yxxa``QO,aim,2~ q@k N8(Ӊ#N6sr;l]lvޒG0!7W$]3NKJT8e.@ O]pwK]D$_HOn9x} M^lB=ȇD$.0 )9OJ(?t$O>( һum6kd($e_C`'uYݚ.u!lz|N0z3ps.x@8-@ʀ',rbcmrb808u=Dx7۰.Ky;ыÀyF8%mCrR@E]m6D6gey!m҆WY"a f`Ia9 p5 0퉎hO-ك L&(^_Mm:+۰i[ 'P~j8IDGTׅ-vWg W Fe=vO^\g=8Rz< 007kIj 3@+!석<˰qOn\I8V]&z<( a; FRq*YeD=}miuz̛pcb62\Fa`4 lj!xтߓn*H`B$ M&eeθ?XΉy4eR.Yv{ݿx7NAǶy-;?)r4a`^b[-U@]Ol;a`Y)5N0^_  00 (+ƹi0"οҷrP#7Q/,+mW훮{KRa ,^xV.הzݬ0g1*_CqNXV Sآx#1MuOUдjx ܬ?8 |[,aAX\ymuvݰ2ꎄ0hCja u$o2"zovXg?FH; a` æI@/KwH.cSUõ+~po,a 8TVێfr0rmvv /?JczP: pzwq1G֑ra8pmHU-eZ 5ӂUB|lh| x(nwdP5S@EӄKDp@8a-e M>]`=( yŹ  ~ǬT{zݩ>} gpUY !֐- bu 󤁔^an-K?UL(\RQNf { Y1һ ۯa`rApG$!MHLa"ea]*ͺkgVz[ֱZ OJ( T Lye$ Q'x!FE/Q}=L>ڵ^1y`٤ @ar3@5\7 0Ƌ^?9x, +nVa`)8_pw.`Y}W =NEUr[ODƯoHXTq s1/NJ(j{iBV޲Fgrƌ8XUnonJE^P2ѭ ^#NXu a 0jZ{ )Mo+Ϳˬ\ af}X⯩W&7@a`52P eeYQrID-7|n#g˿piݘxa 0ГV[+Aa ǃ0kNh޺@9^UY%> lףu@a`09ّ1bjˡ…6]}G`%ૺT[K NMoeĦl0@X F϶#n Dž[ F+p`&SOo[In=U?L) lcs7@a`0V@IDATɪٓH/p)?,R'((`}(:@a Woۘrcgsx#o>>Ҿ?F#R0p45 0F@3M2*_Sݱ0@^bg/Ru]o,<_ mArc[8;:u@+툥a 002( l_};gG8}mSz=M@a`}~-9Nh Zh\)/{dpy8 0 S! ,|qc–}V5jNWVuzݖxa UPz[B gP٠D@a 9Ș4oE2{>%[Nn[JZO@ؘkk|ejb cbSU",D@=9S8[LxC0εGoso@CK~ŬSK{j=CW08 !N#a ; e AK0d2 ]O|LC 1#эG`?#X V!-HZv6U0%3*q<2G(m[ _ׅܼ!pxa`BQ' 0v%P≆Q3p*Teݼ5qvu63Amb\l^| 0yƻwnI,a``r20e)e̛k g)msg0u]7ݬ:Omf$ 5T¿54y =l=iݐxX 4m))2p;Y8`ՎϨ500z'.ڔaz]`a' ;%Oo_@u1Ƿ׮{"p]Y쓏ڤ|n0kEɲRWY^O4:MVپgw }jU톞UklFa* @4+'ޏ8GDd \dGvIlKt&&zp]yJo+܁o)@9'[ސd Et!칪+#3ՅIe"+'~^H71W|`{~$rOrZ+_qVսTw"E(hX2jS*b Ut0+3k71[fj潎M]Sq>1AI0a`P3qey :^7 8'#PlB0@3M9n.$1$ z4EnS]0 lWWP}[n>x)1(Wqq*)\&JE-{ֲ݇&\ YL@G),8!?.|X_ڄB>jZqIa@O3I&*>2npw/ 8@@ʮVg<_Wjm]1fn\UnO- '4| 8CI3pP_Fmz5 8Me5o[2\%1M&ۼ! n/ 6oM/:]9b}<%bߧh[meHz1O!յH#` *@w[H߾VF+"w+ qd 1{L/۬)4$S=ڑnr{5^fwnΧ՚돾}3c-_\Fda` `b )0GK; ; uHh3' 4>t"a p$0Pcg߫ks~ y'A@L7kak#¡D#Ƣll% ,V:dÂ(\]>VlE4&5H]#/Us,z*pr*o@++]uK*e[ iM#Ykjiԓa 0VG*gpbǀm•EWZQr.VCqF@a ~G ?ׄd yqm?f/wڶTi 0@ֹltwʹ\r){)00 `)AI>00`a` 3 /Xxv^.,H>֕7_3uO}🄷nI'9Ua 0@opkm˰l2 ;qUq=2iWa 0@a`x 8[Ӻvd(ʤ*m8lu\6|>a g`s1pg!0@a`q`p75 lu -G 0 Bx! !NB$ 0@a`8>0\J}Id `ϸI>1nG$ a "{T؜vlwӣ@a 0c`e_5XӒ; L7.tu[070 6 a Ǩj}MvOSD@a xSştL#+t>"Ey> ti9N  a 0( |T.!RH;AAa{‘0@a<.D߿%3,m!A?e_9qbjL?bYv4NĤ-a` >jd`N(RkCAxp$0@cU E[ŚOH3EeTX/~^mM009 # sKM.y~Y#upߩ@a_ܝS|L>n]ƗbyLۗ<._?tvHy>^WdZl.@bWTq\[m0`~1؉8 @u<ҚeݸYWoA0nUuÿ0@a?Sn2]; Ck} 릟Uhitt{*HfcTV#/N]'Q$ 8 2 #bn=wpPN}W_9@a [k+¿ | ybp]JjOU{mhLl{"l3Po ois컵}0P0`AFa`D xcmDsi aZOvQa 0 ,vȝ:Gv`|V-Zj*ܮM-98pZޣ ou/ L˵ Pr 7f )K00/oU˻꓅76wxQ~Ǵ>?}Ua 0Ɵ}&];Ȱ]Ȫ5 5O7nWn"fyl~ G;gW0x2 Y˴l6 5aÓƭPv!=*8E2Z^L$z U{i:$>[,a 00 $/M:4 ER_Ʋr Tk4_V? R`_WIr R7'-EH*+8WQKW8x4Ӆ{Gg)p]6 Y׮ڼg# oxбm#d&k֗Vi$#a`8_ 6|=4pXB܍###+UId%i^%f6V S+ihXy .oNX4"Iݚ>^nY;c$ V-xH(KE.d!ǩbޣ[ ~=HcЂ nm$+$t}`D^ɊH `*w.ιpn I @|-7 LP~%\)59V'_XgQ=zWc3w*z 5*[>%:V?J<Wp,@ZTf#c cЅ*|B*H0:_*>$@ALa` jŖ|cN+?+~a` -XywGn+ /X~0a `[3+K0Г7YO&.ןDvx)`鋻h=]ik‚xWߧU;oݪ~#Z.^,XɻjY:3L a|)1*V/M7Jwoga`ikR'2|^¹B$ +qvBsV[K$0^UU_ *fjޫhTCK0z3p{0;a πzxWjx\|L4Q]mJi[MMmu1:7)-Rgο1QEx\5 SseHZ3*.cϚa`ru>d[ŖQEV}ZQlUMS$ ^<<*W~=)pq܃ը {·Ե}Jq_FXN(?yo CO0`S~1G@^7Krs0&/n)e0r:t?) #󩓟:%@/MU@/gp"n+gG{Vˣ_/.Wݠ0 ‘b`Џpɢ({iKYHĦ@f/M8J)lRaǮ׻ၕ>xv ۧZ:j _Q2pV~1pv]us苁=\s}"=:L 8W~J008?TQ;oT))@a`t UU حwi qe`?5,9wH-_pxV'o|MʖW1h|Gkc /Wtt&i?}̥/U-&fX.^ uڑl7 0€w<@x29 J&_'l"D@N}Cx 'VxFJd{w Of~Ӿ0@a 0_+0]o:/lR=K !|-Dl[ @Z0KP Ae>g@a` {*[9$i3s= 7[@ a`6t=k;]XeK\E3@a dw/uC`Q/>˦ZՏᡭect {VUC{La ,ǫP=D@ր= 1Gp(xy$ 0@C:ZnpZ[l?:ۢǖ^r7~\7&2jX[,؏00jd%k蓁7)l N] qF$ 0` \ٟ!S` p }_ L2UlljcJ)vu>WBcjGl5R K+Y;WxS ,1Wk{?&ƣ@a }nO>/G,%p{,Np0ۭ= ޻1Z)GFa`i1aa LU`΂0< ^.O30@+g #؏4p_a jo ٲ*ۆ2 , [TCD0 -WVbLIaMͱ0 0mo,d8ڵ^M֯RWʆ0@3x+Юd)ȣ:wvn+Xx^$:^iOU鍳ɫUӲ{ev5,Nh:~vP*5+"a  /ʬd u3xX:D?zoX;ؚ1唣ּz$0@gՄ?6wG}\pa>MX0<y¼ҞnruMyf^Ftξ^n|XpH;⥾&_lJWH0pBy?2@@A%0ƙ~`j8: zǵvma 0``K2|Ox矲U-]~G![/2֝Hvq|¤948 @`;l+ۀv^ ;MH23cew$ !`00ZXpn+z/2T^MO/Cl@a`2f5`x\Y+A>౮ijkc3gm{Y4E ta``Uׄ 9Ғ@ 'Q`pIl̫}<7 0@h`g~1W[wnRZjWxDĆ1)W)u'tj? WٷP\#12_8kihĶX!: H/uJk0R$ ̀&#wjGݞxa 00<|o{t׏h7|M[}Aq;0:.v"a`}8ן@Fm %00 qZʄx;˼g)7KK4>f1oB|o 0Ll,M7|c 0&A܏zN4@|Wᯅ ˱OST<ţw W 3^: H߶00i 00'fiH8 pxR2 bJ^}OPMwV~ ezIv \pMaOpr`ϹcrL0 }@h=.^g{Ǽppz!h`a|mo&@8\p>V;U;kV7rjͷ6dkk]]senY2?w 8ا~'Ȉ_VYik9N:=: !pR$ kc3#2WO:bx$ ^^&=|qzB@9SyJt^!]xp0]I`*u(Af8#Cc9"XзLMsuާu=[{f[Uv[ nFۻ?ͯϬ.3N>K xs "_;pd%}fLDO" 3{H#b j@ Ex\VHq#pS\8 h=탮pZr [M䆜&0X$5cH6@ \[ ( _s c],78=RCC3nB_VT~𣪇==qjmnqu~ovhDv@NAdoQv[Tݽ\P_~;w7x7 ? ds67MkM6UC;/^{ K@w(\-fSa`3v>&8ľ%\/Xnz(|Dqfjܣxlj4y ݧa+DB`E1£*lg ?ݶn% $L(FECxpC`~";ql¿ 8xVR=qO~ëM7)OO?tv^Wwή:|JF]7b.|Y]n~%@`@k| RSNs)_G00 m@C]]{{Ҟ"0 ar '0}ua2s0# @ýen=00 C8*MeT-kMe< :a=y:unMê?Nߡisj/#l+:n#p~Ej`<F[l{ m#uMZ$ >Gd aOͷ텳{ۯMO19Q]܄E xZg#:#/ΩtC0n+_ 8$\+DFπOql!k>xa}~ի__=]Fx⳧{;:V%8fɨΖ'HbƱ*@vՃyJuWT_wL_8*p~"7C( np$ D|>[yJ{끼\v]Hs0 a` L18\GJGNxL"`Gǣ2^m*o˖1b}F̀^?9xe-^7VO~Ֆ[5* >\hV1;ڑnv8BQ@`Kpcm3w3y^^Ue/Nx{E8Iח?{5G0Pc9h(?Q' ~ \)ImdGA =#{̫_5[!kLa POxB/\GphFsx{ο=طg~P=)G1ךS{p>+{ Pu{eZV:R77J);fݤ~o'vE΍S|^NRt Ege0tJM_#!&E@e?mJNG@O$ 2`Q}V|{my- ,i?-5s"K`$pooHtX x'MWo6z׎lv5I8p[6pr$G (_W/zڼ|?}XK@?щkM|ZW~<|u>B.QzZ=򝏂t+8:O6^(󣄋* @k@1uv5HX '!e8];i){M=.u'S~@ؘ850e`KOifBB /oJ- !{M/n'$ZH08yͅ %h* _ߛ寧NIOрɻ5a ]wиojjMy{DZbNُxY?N8|ÔN\vrCykH ;F:o9<h{ 9#OٯڻnGD3Fag#Ls,h:>#c\p̛慁jSMlqHa 00 tvjyfp=5aPNq!ow>!4~cͶ^T|XW: w;$N]!Ȋ=;XvutWpIx\ xuJ/} 79oPW+ 7 ~OեqͿߕ) d :W{MvvjeZQ]r8F2l\<ex2I[#a ,VկZSsa 0 hgl{ꎔy;œ?4q^7+t\YY~?dwЛy[ncu9<[!CNa -V A;snKHPW%alF-ݫ>(Cx=gLj9.H\$bSm(I=; up{."a 0 `$i8VL= ĝ6LyKxlի^jq5;㬡8oN㯛9>CAtGmms*xN|ݵ뒘ݼo~iUWҙL&ӷ!j!"qrgKAuGdn_!?We|`#q#C2u.m>̵wlmvZ0Ae 6Mc`_{t]|oB2s:nimV~-0@a`D hDIN0q3ؘSx6VkzK>PnɄs xFSΫJg+y8fU^;+سr~\!ɬqN=X x;7Kwow~ 'Q$Pk 6U[ǣ80sԄooM^ss Pi ׃YLҤ-Y!7B]\v!/eYSeX=o-,H?zo>cZu7N~G{*L0@a`Z+L4siOe}grn7?^\&ӳSn輝m60n[5͏FZַh p MY% `w|.y]ܧ, K{{oB?Sy&T|L8D1縕b2ّ0Fv8n-XGg5οfcC82it~ a 0CGE/.MɾrȡϪ}3g8@)#v9n 7\qka8]u{v6i~py!$Nx198?)&ӝ磼0k+vKxd1HeU*ϒ>HءG=BZ"CtBCo*^7}@=0@a`p&x`}rI59;prG| ㌓Cc58^VuV)l\zwzG{1k {XZû;&p]UuCBBIHt4tx@rx^bwz}L&4b &AD = 0 iOSk}^9uTF2j9\s9\k9z2`U}] GOoJ9MlLY<1!K5 s@%c&]yL+{طVT0v>Oљ{ЙC'Tj9zyyXvphPC[_I^Y\+lEA  OFhurӐ}X| 3tE:O@xq1׏*,^ҎA;$ZwY:Qo#v.ö%`([:8UW+ EL`((m{QUfo9$H^2zl岎<#P]ᥛ͞K< g?>0uP]/2tpԘy~7gbZ-*+?&fS-$ifvħ":S.vp$Eߎx_Hk @A`Cdtx<MZaۙ/ȼ`q,ġCg|c 9Fwv)xVvq*\z)_?w] RGMf{Dž(f/SLݸOVHOzh|ܓ92>F\wͶ!CA`E4,e\^Ё6c3\]qOi$ ď7|myu[6~ZZI]Kٔ A`[ɒUg*=jA  0g\pXᤅ5HSw\jfFڋS' i::1u;@ N*x7S< 9hEZmXlԣo'_O\{m#Nܧ}rX*Fُ| zűpCML:lj&|2؏2\.:Btϧ)ek ɎybȿHo\{\I  A`cp[ϊ=i/a$J/tS&K+ڝO" @88.ymGOg<{?iox^yFb=~]{T#v\w2v w*߁{ QV>a]xC٩;G_H>;>qڅocʷʈ?>#O-{x8Gb&Rҡ 0Ox\Qy2 Ewy%rM*׊q5N_' Wο&f3&7tڱ {vuZЁPߩUuo#[@A )o;ouusڽVˋ0/H{~#3r%FRΕQ3=hhiJnY;PνBjd Vif_m5D݂HX?G:?>}*89JKLiXVҜ:]rI_]l'<8Wǻmq}BkCX BF1_.gwo/^ z_(sbgϵ29}{ϰhg!|h{F‹ˣ.΄UuMёUm>e8m:!¿uGɇ HQZ%p I$ _NZ lsۑw(<"UE W*l ~T<W#LdhKLp>Z5dt$D@IDAT|A`ey 9YLYg9W"`̌/ @m7y{f,ZbEcŞn׺F}uR.!P[[|bV^#K/(^ћ5ýW{{$@A  p X8f݋/( v噏驝xyԕaYGw۠ۏ4Yi?z;LS^%~upAΦ`$&WvZ'-mh > GNzM2A ƾOP<.}؝Y[@vĴ5ktw/è動K1у\pUQywC4K -A9DWtKd.HcwdT}jz m:FY~_<_^P>q»b+|,W.yPjs.q0Kk A pUTħ!fRJ1Y?vZ1?x/[w@A  0dCdlCg~\Ziv<:5M7CBHA9HnM^-TO HZZFc7VAV,YzcC#5it<+qE}V؇8МS"C]^^[O?M|xH9¿*~ϊiH-i$>Gϲq [}!>mFN[O L_<#SŗY@ԗI mG+Î^O-1g$PV~^W@:vЃɇ`9f6^ly;LG~~^!U>-9MGb~Sf5|m%9bǢ-nJ(9@q]C_M՝ [f %6;(sy?ZZ(bo3SM_CY!miE-O[:=#?%>MZ ^.~xŗd y@kğY{.CAI;__%?nBA lx^@yG|$n G޾%pU )UZM}Cvpnî~~}^y_[/iگo$%^%!e|~]_?ʣ*<+|2c*#b=^y+y# r>RBt7z|>r'G@SG}4 BLP|8^)x R"p a,Xb L|=QGN/*BGmII m}yކg;|U艃~ &paOOGe[l^A:ie5vI_9NĒd [/[t,74D3T6P$vҡ U[#9x>e|{ƴ{-}MP]M#s~itk_<ԯv{M?$SGĬ1N'egmO۟$oGPkGP/Bk6L@%~wKL7CA`x:lLj_gCEāg܄@^xdQi03a֣>Qvԙ&Qt6rw| 9܅s-:Jt}{ }//TVޢ0; (` ~K/OG@]ẻAWJlIS/ycQ/a !+W99^7VUPw0Tesfd.!`s4 N]f;A?yu=JryW_%>S̻Gjr1j{bڀ}qB3m!mt2?}ŭkZ~$Pf{^~l nȸ kiȶu87[1n/-K;o03BA?mؿY|Kl(G@4|g.Jg;lz*PzJ0;6߁>uYҶma_塗cWdi ʶpɣO]^_,>Oz4.߶%YBA``i1z>\Xf#f<|XLayN3@[t@B=G9mHyT1;Oy5?)>z\<ܴsH(#6CAz(*MA`8D@fΈpSF׎e5dW/:8o+-L?=?z}{Q\gP5\ϬJI,; 6vЯzGj0u|;6^5xmwSaw9BKckSFyW6mn@5^sSpZ7/1aZ?@UzeSl=p_a*.Sit0P<;OSP~jY3]-3fٯy< NϙB8ȯ_&~,Bj49o_(&HݎUu i|  @8~ *K![mwMF`爗(ӝ }xϋՍ6+9a\0k$a736Nnw}.9l;J`i|-l5#Phr(>A6lj߁@l._US /;Tvɶ_%GM3xӏGW-^C:烖JPZ|ΐYA`S+$ϳ:s5|Q-_ꍽABk^8|/u%~9"ݯB<_"")bvoPx k,FLf_bsCmA  01<= I"nB \+b 3ҶYW<v~hfpٶ=cdф~.tc+XYC?=-`RvFQt;'D&Bؖ/5C'Ȓsy.A?,8|ԒB]]ߺ}]?\|P%_oykoN8(u*]r  @8,ڇn'c3$ rG}7^3kBPlVtMMzq8&:Hߊ5G4Z8`.@*T heKeWݫ&VM(-TI`J@POq }EW|H*S$D6|Gˎ@TR(ZZeL}>hiJCA``Na>AE̘隣S~ڴDh!L|gZ)q#!چ.j:m<Ӟ[.i' x5?l=IF+k/m"mA#侸ou,&iؤL Ǽrr⭣FvRE3w_ŶK?`yAin|ێԷ7eӑ}| H\1#ǂd]؜_kow#ŢAڎ^܈v9<r]PvWBg_}ؽ;KP? $~V; +Sa@ȅ} a; 9N6YpdKUDCbUj(lO2ܶil5m4u 8şz} 6;tw8A&jCFgtC/gMI|eٿe>F+b^h=s导"pGM+z0Ym?| n39-Y?-#vd|aᶯ}d`` a#Ju>#4N~oq ȣ<O/c%GM7P%mW/wwGӝPA`^~k󏾚\yGvOT2{nbjơNS!{/7]%q,u?ȃl&w]HsA ^1mK$xϞ'^;C ~NQ _> ~P0ϒ}[(+ky6d6I84Ro+{8z ِĻ|聏a?@VJP\99ǎ4̖@_k#8ȯ.;TS]OK>v-abyQUcX 6&\H  v La&S˾Oe- L#:K.*g ŗCNck!^6?ϡ ֎#Uwx.f&_'Yڷx/-- `6 +UyUYّp?:YxFӄCyF_ тC6^}A;o:FkW\q7m.~?.?%-;:)G@?g-kM*K5CA ]?07:VzN|,Ub<}9?L~ȃ_&LNDTԟ sᯅx 忋AL$}ONV!P_( NsS mS&KK[o-1tˍsd]މgS1;IR;~3A=lu a|ةؕƗwywzz_mvqkHe 7f@QėiL7BA  0F:g;YswNxzO{*[XvzkF$PG}~J~S[=RO *e`/:c" URJE㾔-:?ɣVDt+7O-v M` XkI$= @{xO|~:5=?J-ibTJpSNc;1O*閹jWļ+w%RӇxxU[ tY}rߞtA l:%VжՁ_TS'CsetwUzOǗ?1RF/jMǟK84A6|)EޱD94~ >RX qGA?_暝2Ţz`egi?QLl2-0v A l̄u&48K{9u?am)%/}C\_,5%mqȫ+ @;e}/qM Շ #ح[Rb m2{;;~'7~6Tɝٸ<ܻ0xԬg{Gf!?"}/_=R5gx^M^uZ:$'wC鵝jr}# /|YǚXxWʴ3 Rλd'e%Ċ"a;7,K*RCA`S4W1is%y2r1N[g?xZߢbwօ^8v˾j *u@8|,HbKl֥nq@X?*^(5'fjrzzdxE}bfQ0pc A A%-s#a[;uY nꕣOOm? L+5?u6/Hco#^*wךڬޭ ص.Fpm\>p";h;Ї{ǟw/ϕSw5q HOqW| HpXzA 0ƀ[!|u|I"<c}HRc`Hfч|3֑iq&~{LD́of9My%6U7+淑91<->` 6/_i!^S[暻REbS5ӂ^ I(@7vokAH6֑5 Y6 @={ܫ,lGd˵fH:&4#wȏ+@6tv*('{_W햸<,AC!K[?y`7ǭ >'HKJZFoBZ(l8%9ŒÜ?lf~yb]k%u"[(hZcWu56;M k>0^tO&,  #>zM)dod}^Cuo88<<Y=.ؐ,x`vd,Y+GcN-\ڱAs ZyLvUkk%O%@ ?G~K P~Sì* +N`.vȱ2ioM}ʇ ÷l@84xH_?W߃#iOzg1;s6us4Dal[JO# {/[|^5PM+ /{3ƼT>@4~yz@6<Dzӥ[pt_XF\Џ ObǦزÁ*~u{@zG~&<8#~%H0P&,G@;+hh PݱD`hl(@.o?kȭEsbni;6uv0}#V1)9Cm-DM|>>D7}sj)A‡_M74 /^i$ Z=]w: s8ͤoI~=+ё;C p0<cW +lzV*ZKcv568|{}KMG??KwoI_+I!2P_ Uxy֒<.K Y캆 M#0iziuԺ[pHl[C[ џ+|Cb~Lh2źj_5vy!̶YZ{tBA`s(\M! '\!PXzlIZڏCլ|t}ԉO[?tbW{Ab>TV= |V<gndM>l}o7 4@A ;MfgN.GYHZ&Vc1F+Hl˴͟9~] p%U[{lhlW+vǯOd{@~|*_E)ihZ>&=%ڕCˡCMj,k n0. @B:op 3>aJuF1eva+AƪțF;EՒ+X6햶Q/6߯/CYT؜Fֺ8FnGxo*G@{H!Qۧ/)?s !)[j!< @A`# o@Ƙƚ$#kV2֍pk;2ѫϋOO52ZJ%d,.5ˍKZoB4TfvIPǕ@P85yEo?;G#5%h'# (kDEPwͻJxx:A\Vђ ;Lڌ=V29;}ʇ,k{S#ijyڜ1=HG;JǏI~Wݞ!Sic7=Ys1㪥;¾eA@A ]o}YEaMr}ӌ/-6㪿(i?:)y3Nomm ifҤUqܰ+}׭> swnT|Y[ c6~2^+=<2ZGZڟ$GFn%eЧ @A`!Mo9taeXtn2 ^GsJ=gځǢb^~xyr?5mknKK*>f 4SuܧT꧛Ү ~%k$dⰠV: X[6`mSGqT*D:՗[eNpeH/Jm/l 9Y.J_k'Vq^NגOƽ@Xk|t23*/i}.'>W|x?{9s_7%;c>{eN%}<-+Ko9% A l+8]|P<@%m;7ܨrhMo`IS7P_6 kGcыvk@%j4ӋNfȏ^.z?\d歗xLwT^g*׶W|}7(iRX<@~) +#EM݀E}qt~VotlT@j;: ߜ) I0g0\d1m8g|[>E@;Nچϓ<|NJ/xo3/.o}=<<&L=#?zUu`b) Oc,JͤmG9E6`I MByÿw\ |+9!b!:: ~ 뺕\5qN ~sv@cX|HLyilK]uNeus۷$oK(-= T_#>% ~@08BgSZq17l(|M(7qX,lM)ModR. nkCY$c|Y}Q|n)ZwG wxdө+-t~m-NwYŦϭuJt Evm\7{.v{ʧOufPtynE}nVET8@ )iCOz(l{\e_(ulݒ1^K3lalېN>S|l#ab|BC1FI춥vTA7i翖䓶dܠóǘ#unle n HA m?ٽ=e$ۅffRn ajH߰L6d'2l7/^|" ;Hiu0Z8qSf {8+t$<C+Bi*Q}h շ iy-6a#-k9wQ߮<  x1$>,㯲+XTPp98(;\* _"{Hl{TvG)=6)H_HY\K_ G3d91ia"K_3)7it{NJ(~ Z+}RM!w06O>}|t}8#HuNy0f]n-!~CA ~Tݳ~ޱsEy'O(l$Ϩ31.f;.G|U@(؈2[ɢ/+gF$^Y-#:`Sm'c .SiGC~9xtկ>:t;ft)562%!Ƨtγ%yJ;^͌ F -~1*nE]$|"I3 Ґ%K3lηi:,VJ%8!B_xK5p#>z=]W)GlޡvY-7C&8p̀ "|9Xgm c^\ ^\ӌ5 @;OO-Mzh|⳻;&?L4Jmz:@\wr@ )9k0䲄ڴ%<+Gg{kn  7Oev=bp{|~uP?`i Ɵ^Kr><{layʭ 3׃'sbz:NV SbGZiEZ'2'Hz&Y?򟥟+>V*o_%y^s5Hb0&gsP 0s! Ɵu$剎#uˌzl{^K=aDχ 9~VkUw?%jSr8w߬{I_d͞;@:ϲΫu!ݷ\ݎܺ>aw}(-> A`dQ//gIIpc`N-l|S?VnB+&BFt}]qùZ˭L@K\۰wpMvw=98f|-~M(`mvUI oH.~6\&k-v,>]_#id ڤ?:H@wy eEm΁@dSlCO|Q'7F׿ -b$ 5 WXDctH_FH8ҙ5*Nh:};-]%uj~}۰Wźr!&g & h:fn[X#Zcp6DD@]7NGF`YGi)\7k1Z8{"jcydV;MNΟ5VTzhamv5~Ů@rM}z쮷 5;*!XܲԘwt뿷)j]z-?Vsl_@΅:f^d MCwV)&bI'}׹>`4l`#_7FUAσϹL9{,8uUy;mDŽη2֑u~`3mN#炖e;DgΙHCA`G0f!0k*1ps Y c}`¨6o/Лɗ_23\zjoy0U=%0פԂm0욏z(]헪&G~kC@b(ڙ}"v\$v5AjO(l51dmÑ}9Tf5>Cv |8G0k΍uWoV>?I\E]򸰤^E1|$3A;gͰǼ5>`6^pP,X7 ;P;V4;Z'G p4^8ʨTg.zZ^2"trF^g˝]xd)`^#n}w`vToL"'~EcˣCۓǟj݆%mj{;k|ѻI 'N؝(+x$\Cɷ^Kw3ߑY'vύN;.[s;$ 9mVOxVҧ p=LZ30S$xJ~DQ{1 륟_ 0x 8tN,b`/Y2!xn?5Z}t+B+]]qvhՂmЎ,?&G7>oHlpBA``JݴFкYG23&;Ç wyʢ{^tmC\6Kziu|~9H}ؠ:϶&g&8'( A  +c?/~◈PF| o/@>5N{Tf,L<1OӍ <29PWb\ڥW^~ne\^u^S߈@\xwz('$Z<-uC}z,mů7%a /A@~` W`@%S!tdAxZnI<_*f#䀟2MS#% 7ui@.ӗ~mm+Jp[t6D`yq` ;=٭,KXV4v{u 3v*Mx}>$ ?Yi;G]Ҟ=Up/2@A x"? BvGp &I@wC45)ucPS〞ei"޹W(WYao/`]PTξqy_{([ATtn}Wxl Q?0ңֆn:TCA`^ش_>]4c>10vӫty>\%yϡ箾9%:m2l6ewNN99]%?4Й/Vs3 nN?u 8@A U"bO,#8E`i&hŚ»7ۋOɵZ?;ZX𲄦8lMoȨwő?ۂ%׾K`u#g;&ԣHqG. C-_83.V· i݋asXfH-2h[@IDAT<"ొh9_I mmxUژ%.K3f?Xfi߾t~-ک>8mVy¿;rDA`!p -yi2~;Y௟1aAC A  ֏ /XX@N<9@E@L@ 63AtqkFK'||ZfݤYtq?zU(nm>gZ0d-HWևds;Ja FAtzB;PW4)vWle. N. x<"k.Xfި׋S}uyׇgJK|sƹyZ^fj9_G>yPٰCMjm~QwЂn@@7LZky7r1 @84pOQ/YHbWKX.@t b'W:\yvh|}ڤ!f^* _j睝.. || pYcʹ :UAӞ@8$~F7 Lr&3H ;TS> ؼؕ s8Y$cA$@ ׈6.o]掰ֆvƸ\<zyWis]߲os @A  0oHOxcYS`;,>'>C|x%-Aziqlk:umֿLgm7YRM>ckcjU㶽2~ZێU~R+u'&>ࢎ:=|M+o?$_Pv0|pPm }^REBA`<04Gxnϙ@o_!gČuYf4d; s6랗,9/f?b!f{TޏFG|hX޴#Wvn\G`nIM:خ yvKஎ%=}W8@)kMvݢ>_Z-c0p\S&1>, Z)/~a+ B<}EI%U|= pkJFs66뵬#OX+mN#k^aΫϽu-ĖPA  @AxQd$b8dAOP I"mk\s0hrM}h|3ncVko/GGv=/ -MJ`I_v8$=T!WmM3}E['}x{>zHB: hvEYTv($>+.\qv0 {ͶGF0,}F9&5uߎ&7^<T}tk>|P^ۊCt~Hp?$+ @A R,Y$`‘_6%W%&}EwrE?ӎo=|/ XWy2w;pqů)?Y$?;۲/F7[:=[`?}Uo_-&V}t>ta΁u/S@ y@)1EUJbg\7^]뫋a p,Thѧ7@A  քh/ة4q{C'GiYj!Ү!鈞 Ӥ8&H|F#=v*nbbr]-7N?5V*={BndH]'%ω#瀟}Бq =NGtpT pجfXr>\H[R١ Hp؂@A  @`Aw 6 `͢..'6_)%>0M[G14YУ=E $|GwѦ>u>(tD1E=͈$oja _v*.PL vYuϒHTr~BA`^;a3g]p;mI}),LA  @A x!, `gE}%͗~-A/:alym5|s^Fs0@:Y:Б`GK+\b[mGb!,zp2E.4!&BQ/d|d2;7<{1? @A  5{O` xs9ĵ˹.dͧ+}O)b,~_&w1uws@zď4_݁Jh|iڷ>07Ⱦ=F^ C -6Fƛ>GOyya/%֏7jg /  /ҽ'@wC?z' y%?O;(wW]Z>JĆ;},bl!I?f0s!;?yaCw}m?%c;BA`^|QKq-;aC:ݺem+'i  q!+:d|DaQ^+ʋſ&!KŷM"cq( m6 v Ty]&/$Q:" Qpߡğc# Hb}0؁9?h1o]J>F5SC_mtٺ.cPkU |"e>Z<}\uHrڲo3Ld 0W =&,CA`8^A|b[! _M-b" b/Y|J~š v~x::eRi$iuX'5y< Hn_%X@v}PzF J>fl.?||H#,i=CA``:h1k1n(W3ƚA  " ,1 @G(_d%ٝtv"LAh& &Rj0}mr w\utM؝7$@@ A`#4֏OxtipN2ƶ౻!QP;yXgQ<8Xyqޗ)cS` ikI06DiFwD}CD>@H2>QIY)W3mv2u}  Yku>erNrу@!`$@A`&vQXxCy \CA@^:A>$`!P,i;}(7D?tz7K{Ivbw]H3ꇌ_ \AϹF=}Wۢ 0@7w'}_M[(xGl(-zwI`k tv჎t~`y$MI[t0MYSDݼ7~Ò294ev㲶ujݶ ]`ց>1]ak_t-m A`h@,FԷ1lI*Y7WI[m=ۙ" v6}k{pXox.׌^HYRT#IL:A@tSf@F,G$y[H׶Z8^A[@7N֑ `LhW}:~I 0? 3 K-iMIu 0&P`>D,CA |oA,,sPy^OY+C_-iL_Hx~}8`G?<+rc:H6it:2v Z˭ ;wiA&.yGI/ӑA`M `A A`w ߉:HdR0a@mWQ6K''~|KN?$'y v>Yl/g) b֋!F c}|!|& KÏ:;ٺ]ž#.xs׮ |f< {̻ fq3= 9^;h݁?ðs[4@si$/?K| W釹vTɫpn|4d{ @? @BߋW9 +[݉uؗR?Bcq v^cX]zHG|ҮzF%:dI9tn!_ԫzY:!M=!~-+;@A`2@A`5X@% ЮGf!'c׋ ~뇂@x#eW-ѝv iZgN;V0:yC9[Z_K-)oBmtKc# @%lLUA  @A z  n>dmCiaCjmR ,w;~i$>ԁ&A@mH΋ A  # A  @A J\;pG۳ye,)kunR;vD@8gAK;MEw{}I^( !CPA  @AP#G-;G#;6kiUlw ttӵ`}uu2M!bȟ @vRA  @A:Y>SDi(`tHسY?`-@M*@+>]P@#զ @A  ~Z lu>>ٯIK0:GijAAlBA ,#pԋ[6~MQ@X'A]g5)@A  @X^::g6I\WCuC:_$2^۰@FEJδ\K={A qkS, @A  .d#fr:Tl8u;HmVkCA  q>X81 ZPK @A  6͐uvlN>63i.;Kǯ_/P7΋ A^P@d A  @A`p0 ua~.GDY'oϫu ZKш։@0Ń ؗMNm2zžU_GA  ։@@wAGykZ۲պ#@Xz#tXCR4@ &{d鳋7޶o_u}lߊ6@A CA3=mHې}HQ6#0+.u A`umP&CS^>9vidr;hG A  .@`( Cv\9 07dܜtd'#N>9]>䬾- @A  !pa95 @K 0O">f?ɨ8[A  @A`>@X#  n ;[ @A  !?V}QE A`+0Ń@A  @A 5#pJ-~K@@@0SU ƓY/t1 @;oѱ>b7iš Euo+Eؙ$3k* p(8]U|l$uoKWO_&o 7(zM;C(Q A  ;w)x#^;D+Z(l$Ve_.VRqv Ј?_#q/@sCX'"@A tg8Ƿ8YZyKWb kF`JG ~?~L]*- gTD A`8uF  @|WNF_/f}Ǔߪmу:@OŴC&@A  @bF p ž]X 6Zx0dm!pG|@A  @DM;MCQá p(APy;qA  D` <RA .(:~bbA  @3fݰY7ӝ @b0ϺHpO\v  %':@u>Dr@A`7"n,~8@A  @=^t!OtoN(rA 9Gyҽ @A  "pZI1LP0gJy5#!@A  @A  @A  @A  @A  @A  @A  @A  @AN2mcΟo$GJAtq{Ŀ#>D(A 1-Cc?MNw`2&]UL/sB&x H`ҊLAQ(ҍmhcwdH 3($ [[@A$@Hzߩ]nիުZuk=٫9gP ȿ}t۸^#6_;e5mj{VveOR6oOCZxtg ѧ*H@: \fKfжCS+c|BhN>Uz^~FV3/4J8?ܵvR׾ǵ~}h0eD`qpqeD HثǴxZx` Xczש]bOJ!Л%{x`R,t.̓<\&#jחVn:觧"Wf,ZHQ"5zd݉:B d桌/s-KD`9`;ƍuQ7n8t{޼,?R/$C` >NuQ"0fKKR",]hT|$+@o,zp__5fmˎ6mU>n7oڨ'#;Hf iX8nT$bA-$ D  ,C`%3o&g(S _vF?)cr_U;zLB3n'$G7aZ qWf$XC왥fѧ:H@:ZfI ۛi '`832;Ǣ%m#r4F'CMy5j%1F'$1hM7 $% !|C|O`R,t.̗ NWLkNH""0.0]=6$X!q@$% *GM{x^OL$5lt"@7/m&_oL4o{70JE""p#nCV?v>szU5תGjYY".VCu̡bLCԸPEqR",#\F0D H?vzm=Kfmg"pT9SgqzyU>+K9`%Zď W,=X3>{!{p?P 5qR"$@"$ ubߐ : 5dzWOTk lm"2P}3F!Ǜ%/qRl["$@"$+<-폭eKBWG7\R3s"lm4g9稹=my"pFu=ui<$@"$E?뛣 VGoXƁD}>еQ2HFUj[u=oqg8"pU_'ާ8eD HD X-oW&=uYϒO/ř9H6`W{Gm$Xy"ٍzO:cHD HD H5KG7yºFtAߠΤD H2]Ӂ7{RD`:<@(ҟ2KD HD`c"tثy3uշecBfVQ]Ǯ9@"0JR5FQϫFyYw"0\8nV9nټD HD HV VMZ׮QeEˁWoqƲD`c!pw9scG,Kd=ki~sAGƉ@"$@"sVQϳ9ZǮ7xD`詆/WcMVmx<ո[6D HD HT_fjv4LZ[lWso/7 Mk lm" vwYf3qF࿫qKΖ3ߏ3PٶD HD HFVO%!UfD /6,.NJ[j9?>wS35HD X\܅?n5|.Gt̻1าʥ#1׹]|SldZcle\cO'd?۵Z]jQ lw'8y_D7o&d?oe/лR**<]QU_^(,?N+|gzBcp06|k ڑMHt)% C4~z#i]kJ~D H_})̑ߕWsgR"LGZX[:YZA kLe;Y ir<#׿`.\5{ҒRnUnvX)wɻ.,D P;޵Bma7*;M#7xdTN妷@^$Rz̒?wL9q>*Mzt)&s#09^;Өf*S̉JӜĕ謙ӟ M_\V2%?A|Wo""D Xޖܭ>`ڐG7$yЉ@"ҫ.o%cզTa~XlQ\S+D79^VjYHbcel,*H`ޱeE`Oē,aW^lW"$C`nXc;̵,CfX3e$@)Z0 #VI@"8xuQ33%tnNvd~39LDҿr$_+aڬX-eRrQ$r#p28K,&9 >:D`88&NkMx|5)X(|9m9>QB۞+n R)]*?HfC=JlUvm%\$Y YǚG klt>|qo֗MáĖ_H"0P;}ДTu/:D [ 9$KCʾ\֝ LK ̮>Mibӣ&o61q'p> _mʢD x"jԅd8HvrtYZ$@"$@" I{l7QL#pZ2#oy6 XyD{}v uO;˭iou$F-G7 2$K~89-w)d*"yo2K"`h$R&kÚkCS,UPU< 7ͻļLFR&@"0&\=_I@",f`>Y)k\gpnnZAŌ>yd=Mm a2.r0M&Icϑe}|Hsx_|Usqܺ.KJD`6[RB78,~!D`YdI#KOvSs`z$zmg٦o".9QIjvi$s 9ɤD`- 0U7Jt-Fq„;;zz8!^Xb[?˲ݠC(#i0e}vn.U >غ/aelL<XaƤvH?RTk SRwczOtKD`8\t?:A%'HD +z{:tÖ^${Y::L|[bOGy':Ü;Q>rЎ:j]]g]̒x<5N0e//BR",Kƺz^Vqe Y0N[h1Po<yaaDxR"DN؍{̤i2$F Ȍ@"0zοr~?fZKz?͏zE˽XZ:x;[|O{y$ zh7p8oAŸ ,/bqTGBa!S/qw,Ӳ_ɔ@"0@؃wfiJ{L=*>Va/P1HGb]ğ3y<8aRA 6@"z(p{̤D X"pfD`,迴 zwn4m81|O[4qZO_(>0.82So/ InHbD6m^,'nR% AA P-9EU&!58nYO=qz~[ 29?^oa<Ȝ68oI:V;sJ>,,>u(RgIKu",&ϤD XeI:;7OLއ_'EGZ/@"pgO#StEk*}] "ƒzE[e}_lސǟ/j10ui.U3}@Y u͛:93GĦm1}1LO[nbsM׷6_ǚ[o*nbuse=kcl,ȶr8.@"L0&%@"5{aI1/Q^cm|yE_sYg5G~xqX)S+ 8k*]}r ~RN+͵_ѿȀ'fBʋ'$%_ϋ(T/%cO>y7fч7{ܛM68^'/HTﻣ75_W+7z{&o6hE+qz/ƨz{Z?Jl>Go8 xJ6e'uJNJ \cč_<:&D`.ӌ&@" xB9;w6W9}Ǝ]}OG,{Zߔn.Wrܩ?mvx z'ׯ[Ӻgo#8/f*WH~H.UN;vOts/l>(Ky*Ϗ73uz^SwӦr߶Nyoteϟ|Mg7i\H]< yTpR"Qu/ & 6N &D H"Pԋ%/,Y0yt\M?Ú_}ɋ7yfۤ08~JMЗ}pÆo0C?Hp:t~Rq#LYAh=vģk>u͗w+]IOVf*J7PE ,/qX׼y^՜y3MM* OD:g:W q X~u/kI)&_G3[G/aͮOa)쑰Ǧzo_l%fR7Pkn>X_g*SWLYcƎjxLCwQf[ZF2Vɒc&5w^s3ٽ;-B'yV&dp&]GU&w– ӰA8i+D? /?~6_7OKC>_)emšǗ~!;8]id_ol ȗqUͤo[3 >q}qC91BA=F9̼ _}{56}[O>٢?yM*94fS<v&OI<%xdpw1m]\۶Мt#|7>a:4.@c|ܠ:A$kZ^i&jŇ,k<颿k[2H`RNJu#?K^qOx)<7˻^9*yz8߬7B`"lwwg|꫚JƪN-Z{muY11h~!'}X+ՕVNémegwv\՜г--rןh_S,}}H~<;͑WG\ yTQq޽[sY>e{N@8b .jz䵬л k<Ό'F̀19 8W~JG}|^^=<94>)ξ[R$n|ݴĔ”#MeaG:,JSQJ=_hGaHGsqsQ5TwAyH̎vpTUd <>X:N-c?(. 'HF>6D _.x}3e4-Ζ 7DB,`tJ?7-)5nPVK}_l(qȚ#Y [tV {i<& $F%=B >dCHs9i@bv%X E&7l CsڌnL{Xat/~Ǟ޼Co4wm+}CWmSt_nQzP6ث9H *Qڃ-ArPƎ¶0Œx}fNKH3% %^k1xxXutWq=.1]\( C;8! Qeʊq'mB /.&Q+J9Pa])ٕcּ9G4CvڿY?Q|R=yB7P4Ix#~[7Wpdr$s $k~Z0Ӓ;kxʱC7( 'ZNj䋚߾ͦZ?ަW8>Hd힧Raޭj(麕T,R5rk~:'tb*F.[v&<}woS9SX2 Vx~>yEMN?~O g4əEb"ƃ:q$^;V(䓃XVKԣ_GϋR fIv2iCq`HF@{1W,aH^$9?R<9 oPs1Lu '`|x^0? 4#%v#eBe{"ߑ y*WzQAV۲^UsяIQ V_А _3:,yr}GϲNߚ,qpҒD` #_ ksә!KaHN"zt ڤ_j^Bmv3Ï2ZS IĶg>VRjWTH:jgIR#JNP/] SC|QF5 _$6vH)cGqjń+_K ^p _He7qvZXŮ6g'M_J]{:ϜAbg ;a[PSߗ{S(i:Gktu#Zd0 |5"뱊qX?%]^CK~yois!x5ߕN"s513.8aKFQ}@##E=R1U)ic/,?ų9F5D`-!}f;zl!lpA8iiJAAZNJR"m KgHp}!iLiX_.~а㠐W>ɺ!VpiRKS8:lc6NqQˠ9JGS]]J4%"!KLm]#]:"S~KמD (&2Dp%@~D%InBX;^W5CJ?Kv21Xvuq%%%c+ t>Mufac7x-`DIG0 >W4gg!IHJ5=S]OSedxDYOq\|/yI.mӼ/}?">" zIu,[KV8؅UG8wؑfO[ BZ'_ȡK=-<)w $ZM,d"ؒ Nq$> 2nrſҧ]yW} v?9'@pD#㼴"|H|b: q閪m8]) QIpI۟9FOw8~1hR_HxEΣ=!6/75o-S+<; $λb!AgGa\`GG]i4G8Cv5#GfO<*YnflE"!X&`߬ ZI}lwwu̖?CWAs4(ٔ]yd}^>guʒpBD HV&;qwp$Mdy|q(̳>]"%bUjQ®4'rJ+e`k#IXҜ ʳmQr!6䏕 iuo1u2Pl1^ FD>86!N9gxKOw)5fgܥ ؖӠ:uJ*MPƹ #:Qd)v|ms- HJ .8'Gq# :̇4?Ż5lw :bwh:9qþbs-mb*iu0'~Qzb86Q(%oobS̷7=o#M18ƒqIZB>Ku?3#`jIlX?T4Zy4t xR"a2mI |+}A:P va/~/]W8jF;[Jԏm::Ҟ %Jo=j6f(1Xo7 F9*=v;_\G4?_ljn*2J2>(\{%~W汢mDB"{[F+RtTAj#u6[xL˒0Ug[\cjqeXMk_mp"0j|e!͌U\ :?O^:y+9hRu;Zf?9rg=VʯZ`\RN:f BaERL!O8aJHT"ͫT؅m%8\U~:ۺuuxyPh؀/o>n(Cv N;OT f_3<)9#j8)T`O- v|(mt(k^kfm;EE9UUgк 6t8~1MF0$9]+ev:/HS\N $BAz`a{+foS H{1gX]NJ{!L~=Z{=,oqaqRܽ%g?E|ytVJZN=)H0Ъ}imn d1zbg&/Ϡ-ڽ_١%Ʋ"V**UnA5bb2jegSzX$ ЏmHqF&;*⤉"lVi!í-%3G5 Yl$% 9zUG^nbx[9tG/Z[/z 0A;)8?z_N $Ŧq^+ GRd[J)KZ' *{_s"4gŰ6.%\ifMf?Џ=)W4cebgڠ[֮ݭmXs~aۻCO!dʋ~q FC9xx ezw+4Qġk&Nej/hqC !{ >Pp~u9% í&@}~0JłzkY01f >u1 ᭳1w[?yp([*J\QU{,wnKJguV4e"0p,?^<μ͓nb: .o#SLGLee:CkW;%Y=4GQZm6Qlۆ &O, ~V^ q?{$wKy5QyXYKF@]e@³Q{Cs2S?30żf>Ľs)W$->96 } '-edD )h[ږ =i;rdx',Oh{;UˇڼIۮxX!SV@zVPZx('v:g]>E"U嶥"أjG+=m)XSiwTD!f 'ʣTƷ 5%-umXT8S'{c;pg3 )*_s.)?0ӏE9QD<쫂:iQqdeP a5,]Oo/B,+Ё)_!ǵ_hh*#Ws=Nfl\F^b&&67zi'1tӔ&)KvЅv_sd4ӨP< a[pT=a1zC2>H/|!XYXt< *tnlI|er @`SK YEܙpAQ.?"#I=$zϬd XҒ"Kqã誑?^PxkKډw>tώ>9~ӯu|3кH~}}"Sݧ*WDG [Jmv)NܭB%/T%G6OWڽbϸamN]"0* =zPiڢ>h;)G\AY<5uA UQ_hHԄe((?8HN?mlzR K58E%M"))GQw0r_9Paen&"טLi*_\q%~ء|s@mT4@nR"}^: 8i<ZOLȐ!?Zd z\@}Զ^EҢT  ϋV(A% j&+CV3ȃs/V]eCCi`0h{僜F%W?]y\;JrtfNRZr*Z$b7]bWoIIT ̈́kjE%=.~ם#a+؅p8HTG_}~ajmYXk-̣|T(R^D.TQ10*vCYǕ]PB.3 W.I/f05h4{.=аvcvJSUl@d! "f7Ek$\ q>" oILJD Xc=y63i̻0x >98}DYiWX$jZqA~pt^vcuS{A*MdS&oqpɎ?#O gR6p`P{݅S g12^@AB>Wml~]/O >Ax~qSwqeI4:.=lvw5G{_yX@19 SC*G͔L~7dWn=Gs6+;]#H)ԕV ~1JFWjX8 ?G<9JiBC Jg GZw:G6æC[_|ec Ey6Fbx ŐԱ規ےfA.gG' :(/'ذv*1fſTpAu+]A!a?/+u(4{8#g{vM1^4O#L 8")rSӻ42@1}9R6!Z֎G9g_A5ki#~,BIA78y4e/%~CYѤD` I{\AFm0eA.B0S6^X ySmBȏ2}L_'=.ֻ_06sXI$@"ԓ'!a_z5۩J+%V+l:wyE]zOb5ʥKiR†2Vhu'n$ea[jW#r##.J0-&$0 )O&I \vZrcל@ QL;.}rON;ﺋw}KZW]Z]ޭ6NSh\tc硰4Ndnَ8cӇ kyC85;%~ToKO6'Ō0[Ðui-b[$ #PO Np&'vtP{YJGñ+"M{ ƦȈ(g%X-)j&+CG\ig䴡N=F1Rc7 \*n@vQRm9C}9?SS$LDojμn:nGoˎ=vߵ9~8ƻŇ.v *,Kmy~v2vMu>DRrO;pŎA;uB!NvDO\}[tXD?fd~x8C޲&C`8&aMYOkvu3 15Z%;H 4 c*<5bG2rڴT9s $J<4+v(pnIIQOSi_}10x R1J& M[oAw9sĈhsGSqEt![Lm3PWK-̈#!&;uYysy3f9~0S<_b^zT>"LnA}u,$%%@",/,0c/ΣimӦNqQ dN2SQN*AWEzʎJ.zEg!{EzkKѯ6oW2M꙯Iڍ J8f直m r>8dy􍍒 /lKwشuSs wE{TB}ߠVj`<aT8^xP󏏇n_\yJáGuLx1'vUx`y¬їy_4 L:*GImiT<~1!I_MQ+ϠO}L']lfb) ȶݸ֪~QSuun$LyHOqIX8BRw(=y ֏?FG BnwgsC(>_n2*$4B<_2|GaVSG}n zr,[T>X$'+)HD``310iࡻ'9F+Mrs+us"^!Y B;o랋b.PV>cECA+]H/6v *r0“bOåqn}wӹ2-yG]Hfs!L0a (MxD&pm_t)Mk%-=6RXp\y5bI,uvҹ|%{O>Q6?Ҳ Gq Tdz_|Wwhg`<+x1ԍCEKW4=4_8 e=[j>Qhۉ㌄))t%/w_]Na{2 fZ<}=)1SObOz㸊p?%-vЈV?gč]/ W6@-tByMiIj!G}in&㫇@=Nx%as$/a#¾jƶ8xҲr~iFCɃҦtܿ$犹(u$\j`.C\m4ce0)[Zktu3>s6,\q?"VG+¼/qN0mDgVpZ#ǚFO\@n22-X;θty:ٿwùW?K0um[0gKsy6' oW hWSa6d,wGd~b?v:z82wa?/c4_UD;in@d23p L:Mo90doMK>A|A 2"ӏeW .C;Q=B#ɇ;@~;j;8qj_ -nǣ=> y9j`P %Nx&&jHM"0:J|E0\vx{*+;b4aDH;xM~ Pw>KOKGT/l4~XO]1\_ʉhginIn?$;d'e*TvmW<]< UXĩ%z8)XML7,_s=$t`Ҙ< J/#!Ml۵VS8.cgyDq'A^^zI#i/aiJ OXjP݉p]lzlJp"!m:-Oob;]V &)p/,v :H% {I''sY0"zU(I:GYNWF2qQ@l .b4^Q!J]s[#0uc%jSL w>EȦ HB(;]p &*6&^v(iyN#LgTӏ2ak~V{ԈRa%c^;Q%*qFI zYQIID`5=O=ԺaN@IDAT7v~rrS`1ni2z8fb<"X: 򠇄㭶O˦w//3 ׊q>SP#.5ݵnaO+шDĢ`zwxR&;wJ8s =ic, :0οZ}ԪSJ!Ps 4UQ򪉕 m[Kt< R!ĝA=i9ȇ;hWUc`NMVMIt(tXX08]tgʄA)P0MzBKon] nq_\kPw{-ڎ6XRس}0# H;K۶( ' >0Ձ @]wi.vI!- CF܃_(MlqV`Cm23yfrŌ"!0#'hY_3h!gSQ\ƘH$,b r|zq._G4Uq3vұ WұrbwQI ?GdTnj(BG &%c>O%i<[\<GB@j"1Gg=p6dsbIyS,m \i- #]/S<_,m? })Cv(ak#jlmI`fǑ'w06:ͺGmz"J\3JEc$ܜ׌FwxW+%|^ILVÔRb!lp饐]:i ; sݮ?-.6 YMEYbE%@YY"BGY*I# ̾0W9*Y=%<{?Y9dnRK$Ë%;iuҸDR|k_`QpJӣ/i)gܦxG.H6AadS@-y&Oλ,?}CB>Cû#͑=66qhI4"v琪#}R ,j3$_ah.``޶非2p㎹7 <`&Ȁ )KnޏV8vaĪ+:`\%txՕu)N?lb:Pcn+ vEGn4g_G.T2ve/k7ɎTGR!Mo=$vo#iJ䉜K@0{RjQ~q=.@90 %z^xWIBL8" ,ȓ9q ^ՙ!0??;Zgr~~/vI#͘HF Yeɵ9SQC8謷3[N02:Wt<8BWl _8HWsUXo n?8AuQ@Ķpm1-&Z9y B"9NS0)XC BZ',ܣwo?TC2B2b~DAQ%:4 kݪɅÎzu>$d6ZD`C#0o?~<] [^8yqf,Mk,RP Rhy`ԑ qQ. 95ߑNxiJ9dANi;h !&E+a̺ I:PIڏ :°9-Jjg&ydL8x7:4%iltw%{qO)ku[ }F *?Vk&<ØPJN~;駬t"\OO30)0?v׮i@KE;7.#h2O3Lܳ-g #L-A\";X7|ĸ) Yz=z!;ƘppFh~ǜ>1vMS?c\+-tY=vJv;8^7\D`]"A1-hm"ж??/615UnŒ٠]r@OHk7/\ˊ @,7qnCӯvUMC#>ǓT'ފ:C'۫3?1>u x >1=o-=@NK "G,􎿀Oj?"R v_o{nw([{VyRw~/xܷ7DP{N鬡)ܮ=MK$ˆ@d[5Hcd<[:Քe *)SB4윁: 8ph*OqƠ$'_?%2vmkLr'H<ط5 % X1 6cR) *  !" EI*!`1y8T~2f$M Ղ*H>|M:rR\|9] uv/CM RCi8ZN,U>eF#zΒw0k 2vǥ/d"\y%Q#IpQB׫I ' 'n9{d@ڶq+5W>>{U   DUޥo$M__ȓO| ,>&J ө0W|/T_;-0-0-0-p-OxK|ħ'\XVj(D= 'h}޸AHF6γ#  ˮWLUz*h슬cko& ST]xOA˿8 Oo`z(W4LX_LG8Sz !Jb\ՕBn&4l*pI:Gz@X{,d }mhI^ez嗾FaG^]:KY__ޜhкJ t [?]E4f(z "1ͽM Z7Em^0 xx~ou0A ::'W:8ZZ@6iiiilV} ϒ8t\+騜d l _0-0-pxv;v/jOGs5*NA3v0d'k<IK /~sﱤ9<l\M2u15{:ɃiZVf1MJ `VXTx˨;2 B~o[ÒbS>A_R?Z/ORQS(}~{zoGOP0Cmw0-0-0-pv[yͬ ^ Y_` _%0xEFaU ^NÔ=_MTlq ݵ 'Lob b| i2Itg1y*#juZ\Uto^1@9ydCrl)eૂ7ܷm\c3{ =In0-R)'] ?aZ`Z`ZY?ς04H<#a½s2iT%t>26K"}ׄhSͣBս"7_T 42RAW5rYo)/myZUuTYOx{ڂӧS'rPp?3NMJHhu|(7 '2/b 9 LGk`_@ -1POQӀKT[Ih9G?~,G~wu_' F#ַ而@~_ryӿpbtZl,ϹF~nu / d.I| nye%w~- xI@|K~R+% 1B*Gɫ8T*))JcB Gɚ0-p-GGON0;*/=v X@w;ih#|]!lH3O+&UCrC_o y dgȼޙ7ԭvb{1ryON8\q ^|$1mGvNY/%#͠S;"U.bATK6d ~ )SPʘ1 }*Opߓu_d,էnv)b?) `I*lFjh4ORƚ΋Kz^%V ȅB$F< h`PhrU& ad_#VPc9'U*G ^hI%LѸ,?#X{M ;?>:%_D5c?{/MNy-%0ޟppC; _7z:Ň֓cPNsMxa|AQSsRk'#gSh 3PG'?Q{|62jg,&##(] ebW} -7xw>ߏ+sE%SZsanTq(GM[)y^$7NwT0j:' ?,uN==@' \n崻1.wZ`Z`Z|Y z5ĿϿ v_NG/^z7o[ q K>zv:IWMy{^K;4&JY#ߧa_JZ=C #ثҔȒ~2OP~`z+DŽIMv΋=9w'|c1I3D@?,;?kpd)A+sᳵMylG;ĢWח rѨbo_%'̉cN.$0@PvZ1v2Ns^N I-J߹7oX"K pB#><3L{ x6o +_}#e~_`i=)>jIOzAŏ Q]xcuTԾBUJVdn̻;r}q29MYx(|I|G>,-oqeC2匹sw 5ۂ_aA^@'Gڤ=8N~geCX "9NutK"-OZI./gZ`Z`ZZ@ӗIe@% k>ZFcDU*8HRX "6+{zV^">2Gя$ H]3H?9@X eE ϊH Dv$0iX'*'@ѓ9~оh!umXihGA{>9_a =4 _}fj12Q _WC/,=f UT`}c_.k̩D~:1/U/Co3P!G>bʤ]F@@O漨?)o]O9I w(FP4%5˗,Oq7Ķ'&8>7՞=܌ξ׳O "_ /7n=T AkɟugK $ò7D, 3}N~dy1|Q&t!M]L:~y}C|}Nzx<Lѩa-A)WwGh|6o QQT{ *>ԯRW~, }}NA$P^0m>4Sa@\}2>>}?{ϋi3+w,_ʭ}uW[¤l]e/_AQ|`5_ 素zk_X:o!'*ziNЩrۏ}JO$D3`)Nٴ9txr7۽W7|MV/{6p.'y]`R> I+bh#=-̃+:qM8O`1oIm{.G~^<o}賻?ʗzKrٙ)Jz :̀&X)?wU ~ ]GЏ4+_aBwث6?VLtH2CIp, qqR$-neh\?` x=ߍy1zRA>/WZ#(x)Qd +XeխQ}M 1S~i~o Q7E9XbV.$W?j&?2h;%{P>-gjZl- N;Q4 x O|.~ a ,! ^W|( _-W~uPXK N}t# *zEh |Uݞ?؝Kx_ 2aZT,f>v::זoN q$2mYeiyƗ9W|1{ ɿ|QA?F;pyA;t앭A41oQ\lO= xŗlE*Q/PVf#0DI~;(# ގLXXnN)d'|Oy^r_^ڢMN=Zl>U!^$ ?7 H?}DXHrPq"@:G\hxŗ>[G L;X|),XS`DL#'L )ޙ- 7l:8k)Xl7mv\iLO=+cOy]# GI^Pwy ԑ-H~;JZ޹i_2PA^Tq_9x\,zKsR 钚Ώ @݃{#^TW^Ϫ- ȯ ʒ:<{)OA!W@~xSc>s*[!sf&ZWDKP0ȫX}Wu,JyUɧ\͍eT҅'5.%pITd,xn> #2ӄB"-I?S5`uj&VP@KXӌ@SAwD":>z(I^5kik"L87N/-7r'AHX ~y<#/]GiՒBCW|iC:$em?s#W?=t=Ϳ|/XLZVyKD8}Өy)p´iY;ͻK30q;ʗ`ǹ u^/72}p榺pl/|AΘ~S%_Mw.ȇ0?D:KCձԹ oC:[`B,H' Fƣ#u"$k|´yMG]P>׀C|ׯ>{sv>ս5Y4r6)BmX+U@tgi?!h;EHr|l_ j/@_Ҝi n_ `TFI%߅5(}û3` LN@> 4"R4Tyq @2yyF|OM`x,ĀX}yiDu]ԕw4uso-if´w wy yȼReȝфd/oy3Qxa06$~& m87(~S*BoK걎^g|#;u 0>\c,aK:$:0-0-0-p,"ß~͓LJ# #|l^uKD0M)yw:y?1.'5ld*CxzcS É΋w&1y .| *?p02F8Rdң:żO~Z-8wy?Kww=?e{pUunJˬ G`!\Z Fg;(7g},e#}\&@g܅" =r&EN*FҾ \2CCO~,xo (tw8N0-p:.vJ㞧qw1A6N8! lǭ"cѡ y_#oQ`?_N1gP [@(@WO)Ի均꒦6DB~eJWiVcbl.\j1bupO8gןAgo {s'[T*ȓApپdWFh-?͓}Q#~ %]"pդSgkP/첺&g#i@&b} B-;GwJ_L*]MCN>]i} É΋}1O>,kob~o2H1/V!Qm8E &PaKoQ;jURC*Ǽ# vzإtH5%rQ&%9e9Td,LX;_wys7qg~qSCߑ_)e~{.hg0/YϪXe0bXݩ|.' jive '~k>A׵[@"U@Vmj}{tf pP/*P.^30-p-fށ yWo1hEˤ,L](Q=sfvn@I`5[]Ci?.Uz7G>h VP`C' &n qb<;:{(@wb.&coPy:yO%)y=m~}tYT:ݸC!s%Mքiiiso| 8uBAKG|hnq'U-Pˉ r"~C]~S_!7Ȏz8{N9A cAJVn Ej?h jHZ ŝV cz3@Nt^i?_ cDzR.ߐ;*?Z Xlw|WuG= :'z#c4(OZ#Q]7-sVbۏ8k 觼w1y`_H'w|Ov}uiKⰖKqrQڂ'Vr~|:ɣ5`+SǨw9 x蜪o;!u.鵝͇ׅ/\Tcg*2(: d L8 0)4;EtUXnessgfYG+c dg̿CjˡMw]4q(`ͿbsM3J@7'>/v? 1 UiP1/B%UqMb-< *PjT} WU|)ZׇSp ,E0-pn,]!Aǃ"x.w7z/6{TBFS ndk+M`%A[:[`}RN L L W w@syr!m yg;՗%VKJPQ6=jzeԅwr"G8Q! E~Lzi ,e_a[~xw 'm7LnnB]FBz{B7 ]\+|ChU@O2yYc#:0jP@9t_^/շyRdLK#ީpMɴY; k25 o)v伞vI;;E߀|t =k9=_] \8L N ao9Wr.u>Ko &8 )%PHpwWKsC>oŁ=}.^umrrxC]NLqKCAa\\gN-c˺iq%<H;[9sw]GC15 &pR@: A xyP k9M,/Au\A?bQ՟̿&- qI'"ӣ.Uzi~ bwU^=pG>+*yY[!'<2D[)=*Ϫ]9F`y[lΪ骀W||U@zR0Dq?Ȩzr >~O~t__;vI]4e ĄiSsQwM ohL'k/0;2ʜʠoa'vﶗW.()|u?{D8OI}(' :Edd)uo,k9)y|O+?Uoq bH D}IΫi9|߾yA<+W']vY }l^:]U~S7'FpWt謐dfS4i_醮-=׀`-gUrCVߗGwxNr(Jɇ \hZi-OYqeV'Q+)[/XP<(vdեSFeIZ 3~8bϦtZl,{O _xF(g?8%i'"Ih&jc۸<IDAT=bZϒ#xю=XD@?G^4-^՗pEi 9}>>r#=/T 7Li: b9l fnzV$tG" -:Ǖ2 n> 8. ͘(s,_'xk7 ǟv/$ ʓϼ2GLom5HՁ[Hﲲl^2p H̛tZ`Z`ZZBoӿ]L n9_ ~U|PZ!y{#ʇzo`%t:Ĥ٠u zbУnNI[FZSc"Pi{5NmOA>dV溌[6/vU+(6^a*@]k em( P˓} %dy{8gcw=Y@2?0(ૂG3߱߳o-@Za )xշꠖN ڗW97{ Joef %B4|"ׁqB談"{+JUMO)\le}+l´iZyM/*# {f;\32%}婗:mtͧl]۰~x87@johXJHnp.RƆ&vX^ ^/2(`Z1 CW)ԃ4?%8d`[ɓHW&0-p&pLe?]9WWp\`n `WI8I<I9̐zOa#/U.esdu򽬼֕ky󠂼TmNHiִmT 8[_`Y@ڥ-}%#c(ȟ/qT .ۀAʤUmHCTG\zFN&?|ʠcY(%%K^(cU.:*8~:y1xxcb6Sӑ1=KKj*ں->s&Hk+t'm/V0OH,Lls.zZS YӞmLyrL E= G) Q/A⊀ML`}mBExZ~K= ?@^K qV}ikItϩE8YhBu\w={#Hصq@V E+V0)X`M;/{z+3zyK{]_,mXXe/z OX e8?9Ϣy-gѷ䛖v|}`緲m^z><`۝.9s;18``ჲAA!4 $i;%p}?#$9o񇟟5K)9Uض ]oޞ2CH~ 0\P*| '>Jo:#7# E8qYP* {b@u^NZ 20ڧ쓙= UyO}.@]G<#eyuϿ;[g?yS:-p6k@Akwg;q6x?b]q]k dF/LV^gvn ($Ww;o8; /5p@ Rm\eA&P1X'ѧAGO~J?\dgL )2+d´Zz\׽rZ=_EHuJiT~v'vq0# Gzȗձ!\HЮMڡ|KJQoRU~R\#ޅRON>#׫7q8aku>5&x.j_7ND/~*8Я"d ~+p(bL/lV' ?Ӯȶ>>X@0-tϷ#f T/N Rzb G_ԩ5 Ou̻Ly+uu}5_ Hׁ/cvZ`Z`ZXEps%QN-5)A^:g9zcι|iY_dnz- ߩup<˳3A#(K+9uӃGNE?~]c?b"䷓ qbd3 (5VtcZ\{m^PZVd] W 8XR+$ydX nrY[}Im%_T<2yOxdyq|gδY@?%k|WR7d Jlpw9kp#W5d):NFN-'֓{ʯ81鼥L_z:sY}]O;)WI/.tסr|!vwX}kyxB!^ҋ1i3Cσow[M*ew'}썖 K-ɗ"wL- %g!S.ŷu]F6Lܶe]$MuC'_g_sM@^fb%ri`CjH%šrHWts%t^Jk@oA:W}.ʫoo/FKЛ}op qYP!<]' "Kw?M+ Ыrȵ uΓG9FʠE鞧!P>/̙8 h]fAouCe/n #g4 #W8pWU_Z3}k=Y' /GNC 6JJo @\|s~FC~?;x50C2#rAzFQ:aZ-}ϸyt["g~3ue>i ׶vח3=uJ>v:>Nx"÷9D|4T~۟x3ȟN8Y Zk_;"_d2(6_5!ndڜ|u4 R 7&22rj5/ubtXȯC+Kv"N?Ϥϵ}J%!vd* sq ,Oaׇ.\̋Fkza8y-hyZ Y~ԄKZ=(u!|Q<@=5‡s'̋c/`fL SPy|KN`ΫsMTƉ>{= W:g  B rY/s>O-S+9E{\qd S+/֭t\*+sAt0-pV\YGNo7>Q֛E{ kX{o@)vI ?踚)v=y):-='?c0-0-0-0-p`Qd1qvx仐*v/ R8M@4XEC ڭR@c _"OW4|dmi $+'{y6Hn 6M PZGVщF*uo4/~8riy-@OɇeRhnA]&o}P}Ү }o[ ̫4/r3kZL-O_AK2+}zʹsXg=/%9iѓf8vqۓ~kˉ Vhhr#y:BW9'IǡM,'xk?b ky摏GR.HO M8k x_/i{jI}#\1f|P\S|T7Σem^˧:7=#0-0-0-0-pEJPλJՑ)@_$}l ?m'd5܋)] jz.!F=*KpI~I5(x<<,|1o4B.vn|ˍ4U' L4-/Ejq6tµ--Om;oix _3 PtDW3 'L L L L X$b|\JOye5Cr99x ERp5O~;ҵJ'vyQj][WQ߯&m;٫%*p_e0ڵQn< (݀P h^XT%C}N@4}5A8jʠX4XRtr/d,!GF*H}ɺW4s/7!2}iN`j}?آL-|}7A 键+Ko^ :6I\>>b`g=_EFo9)tr?qc DY6 ItDxǫ{(L8 l i~qU&n2}}\h)&IC;2"r x1ENʬ˭M?$° Ω0-0-0-p{,-oΣGZi_ 9)ۊ ~EZ$5;̒X+j(;V@0rVcVN♮^m?drк!ca=7_|˥DIմ BO-v}؆eO9"\#d_yq+gc}Ko]l/7Iձ.>?x%@]/u+_KqzRINfм{`/eK/pdqHm#j.7Os(}(G<)آkr"{M:腇-:aZ,'HawEAd>877tµ-QJw9c`[ZガYG|mG2O~,g+|k,?W^eA~tvG:Ko)eİ>l'LCÀs9QٸǍ Lz{=@'}\a|;VPƤ5< tοWX`0L L L \(}a#CwKX>|Y\{6˼@/I Uz`'~/ԯ?!Z^KjywdOr{W6H?$Æ ҵ(lD8|vOt9To.|;LSzE]Rjޗ_XN(Lc{>֥\#CGj2F2o5ӥC|:̋\Ƅisg HC],O vOߔ/F ~YyV't(UD_2FBFL㤔7H?9sI*'7r[xYK)E'64?aZ-<+6 u[j:,ݎU8vmN˙IS װ ^@3{Z`Z`Z-Uix-OIVCAǫnXA?ǿv 0xtNV/T oЏ|{љG(zhޜxwp|NM8N<", ^t3EZ(xJ7$aׇ ƒ6/?9KCvh5ܴҖ[^}![|or8/}ScZt-'.6Vׁ'O}y> 4xi{';vtwq~]\yo|4Nz7uPKP1N@_9FM?_k57>sm^mBax^NJ#:&L \W '@;}<̓밀cXPqҞ\۱c::R;oݝҮKˤi>YdM L L L ܀,P0 9y)S ɧ yW)Hy7ߙ&xg!wt^^ OO( _$U1 xOo, @urfIIJ؈MiH[uFİ0p.E})wwo~UA q_)XQy 7j:/ SsZt,-X%vwb_~eY4'&7J[,ʰi$W!]1jr$/f[4/nlMio|zÒ/x-)Ca.[rp!U= 4W] Ax9a[֙w Q1鎮c} CCxR1x.ױ&L /|?qC<소{z:&\v2Vcrc$8塢mZ9t [`;`'\nZ`Z`Z`Z`X@b yi" eMveZ=@p"A+p/Xկйn'nwdYry@89/:/ǔO U"kw0/h_ m):} <2tS0֩s ;G ?AKّ{ 0jT]]P:Cg´Y[@@? ![䮵e(>IFIp|ű3ฑ>7dž|G.Zx[ANylq:nD-iA@H: V>~+s~!7 s^|~~´G>ߤSE{cϯH~qZCA>NO]Xw\5j]\\M v´>uw9OpnKlYRZ®6^/O])IO0-0-0-0-p:`!DžzAp5 )t>0Ƀxu/ x;6ټq ?d JAh"-ӡ,ƩvĹy1řOفikX@߉—#p|/-X/i5=x0,X_ER=iomi3Q rPe_=.^Ph#(8aZ[y=<fޓF>xenqndHHMw/꺽ۡ>L-9<b΋0gftx}:k ? ꃡ)5uaIA Y/0kH aA^}OJ:%Fytz kG2Λy3<ܻo۲{t~̿jlz+-MJݳ XFhnV1-0-0-0-,al6[ƍᷛ go.A;|0CΫVlЧ>ᛯԱ}H#mK-O9끧>("7Qm -l hCӋ|A׷̜OyolZ,-7=Sୟ"Z6G,)km\ ~$5dݢ >Q },Hy?4u嚅unu9<e;i_ {'=aZNs?_:Wix唅prs>fMf kPx\HyaCMڔ@u""lXwn4wGnϾ!P7NPqbt5.mmzrΤ nWݡA{P\9/b@"Y@O_XOHo2((eLS QUЎIV0" t?H[K67Pyzs22Jc_4*νBܫ}^v0e${ii=`0-0-0-0-peÐ8 NA7Nn&<6扴`iƤ)R}F>ЩoPڗnH9ma xmH;a1uy&2/L=z[_WY7 z~}e4'2eȨ<ɇlvmװk< "mP>B2(iϖꗤ蠿 "Cn뷍'yi;W7^s۴2Rm%g83xifƴm3>5<p c3u#!e$рa_ {a }/K}sEQ"L<<p ~y1yoX5e7ɃGJ=˰N;ԅ9uAM @>]ǐ6}?]s\{DLˣ#m6ʄi;kCxih!W&%#ZiikXv´Yԇ6 k(: !h@`SAN4 ʃio o?2(26KPҠg>Tnہxٝu΋9/:̵~eϳ@e(:*c >OA5µK%uv>ɵ? gP-{5@񺥽BM:-p'Xݟ!IL/+:]>yיiiii.lL|Jl6AtEtIK A0Aa"-@H@N>PdR6A𠛦̓Z_ovQ[7twp s^yq'gU ̚guw#kk%G< 2!)}! ^m}/9<>P>!]QdZ6a?+i;o{RPf{4?iiXs´[||ᡈCA'7 qy7@7؀eSSmGH ͇8>!wiK|x|A(i<ʒGzZ.d}}Юki kS6ڵON=^x` NqBt  ^( P_(h?Mo|u6y9/9sܼy&΋ܞWf_lds|P*i<'N+VF߃ l_O@sAr~´-wnfzZ`Z&,b9aZ`Z`Z`ZX~~y4q#AM(t(Awӄ>X?v/g_<>Gƨuۆyew2u졂yȱ @9/;nKc} ~p!yU7MеK#z : /4ZEz mQMmDTr( s^,͜O>NJ:}qBGyXߔP<,kT_"ݮ7}mG_JZ6z'L L L L L -'VhZ`Z`Z`Z-v#llD~=ieRE./Mi6CP7So.ioC>U}c8F[B[ey'L L . 蛨ut> :(+oӶ^TIXCr6޾`V>" Kj8A 8N8Ww&yP;Am۴a}ȄΓFEFopEzOd [<@L1c ξ NhG$%OoCѳ֩,XOיC Ϻe Gta+'L L L L L  N3,6:MnK-C)ylXvFXSFVz/.@^;m[M|~1Ľv!@^N>G?ö7Ǯ`sv jmϔ=yf=gnjbXD 2{~"MD O#}[r"ܕ\8׮Z꽴xa6{Q21ǜs#%7 F?9c#꾋? 'VcgZ~d mlc~Y=isGQD +xbֈ@"eٹ!h3q΃v=51.4p B ˒+Fz.ݟ{x5[zS뻀B<"`X͟h96>BL^<<ϵ{gQ|o^D  !Z"@L`8>"dyy:F_ɽ ^~̣mL:8~rM7`՟sZNqݿSoam;@,"=hm{z cGξo?ҷc^uȩoў@"@L"D'|/;{ U kw Sh1;v9g}cWu":۳=zymxfVl"En;qD B1@"j/G5\vN9/C䍭S9Cc~ MQZw$,۫ϼ!*1<>uHD 87x^!@D`{%][xy?Y6n#}xf@={kޅ7xmlug:@"Ʒ{^(@Jj^06 iC_ŷ&{fe}cj諽_@3lcj鬿y{#Y{ͧ#D oEAV/D 8ƨ j!pvzmrqt"+ۇ3~֞Α@"=_@"/!\xeK 7KD @"D @"D @"D @"D @"D @"D @"D @"!H mIENDB`RxPY-3.2.0/notebooks/reactivex.io/assets/img/threading.png000066400000000000000000013000431404130727200235130ustar00rootroot00000000000000PNG  IHDR  IDATxeYv={_*wu3ݓ ga)Q)˴ ða/0 Kё)R 9a:WwWw{7h|>AҶs_=a^^ۓT9 4h(P@C  4uWsSC  4h(P@CFP7P@C  4h(P!( ^skC  4h(P@CP7]ۺ7_ׯ_ѵz'%/ҭ;;:u1V)Y ZmCn k`>SwM+FRۿWzWhndή BOY )IT+#bsii4cżWp͛mvZo?"(KJۯ_џ:/Sm\WTQ_7oԷ_5 93K=ȴ~zuySd\̧jEM%]z}Q|*_~̩Ӻpދ7_C~ Nx lm~k_%7ҏJ[tttt&W ywzw~^nWI̭iuuUkjEz7$˼C?ԙSt磛z;:snݼo]VեlVOo}iƛ__+}_ʒSV:=c}Ͼi.K?WחUM >߉cǕ wWe=}t#CMTH}ͦS]n߽ O>|Z^} ~#GkkzԊ;_~e]xQ=&~uɊa'4N4K3<{NGN06Od>3RKKKʒβT+KK* %t+z}^od2d:t k8, 0g>2[X0ps(3kik )hDu ׷_է 7BvUN"YSDw v:_qZ`'c;_O7 o< Ea6`@ ~ _27BG֏? ͓G(eۭ.xޔ&PI E-ztk2})TIBweISib}@(32k+EK^ oSt9Ms zd~h``?k 4[~p>5,G}w<:~䘾_zzK:RZ[^5|5:Kz5 |V[{ p$e4}=@>G,/5-4,`)/3;?g-}㼠4ahw8.$2*Ba $x'R# ft,2y偎=.MqI3ΰ c .i cQVזU vn$W2|*/`؉P'd ki[[[@kg8P:PkCu=;ZP?7nW"+\^R aN72~O5?8T8JRh[[5]Tb^?PYH9 7U:s`&YdZ$39 AXE16G V^T#Lth*,~YK&j*rqYA<Ǣ,3&"x>^V-KQpUi|01ɞgq@j\v0#x_x7O/&)yo:l>7BنAs0æwlC홳u"k' *X_Weat O+5^[ \\,S9eѵں]= gfsi6nwI(Abft2^ G^Zra<_GO̹ L⸥0Fy۞}y'Vݾ`hO^Q!=6MmwTFsS Ej>(Ms_r `V6ZIijP^἖xv $yGa_Q+-c--HKu+JÙ{UN:s@Y,u Mf3%#^l4Ν;Z?j|m>pP~ˋ;N~d΃ ͫ祺?R6.%acbLge㩁!xX5\DhV0ӾӹڝwwIy;vL_;s{ƵJ?U*ϖ-w ؍neo^Ǽ٢2kՎX 0Ptƣ|fcl(AR^ 19X%Cp) jYAF*I޶拱Ef< w<>yH)r#pn!R4缍' OgM$ D. ">O>uA(TjᮺPnL sAP_ƆB7^ӓg_̩ ;w1v<"a`+gcijs|;7ouM_;~'c猏} TۿGW_%edckH%,pbez}3*2Ee4m8qҞ7Z(nuY}W9W+I28jΦ5sxOg?sEDڞWQVd}Oq;}cSlűĥysl`1Qzɳ2S+22ٍa ]shxo[9`:9އCuPq ct]ɏc\.EՉ_9s>wz~{d5e'y綅|:ݎ̥7>Li{ h CSGȤwD)R I>/Al¾)y@7 JuYTJ`3x>O,4oRE,[Eח\Fu]lR2_h0\.v|@ljvTKCy3a0H%ck7 f<~nO*ښNS0 /N5]-+nuVZx0n0ʬ=j1H?AN*猻 RrnP}AҌ=8^=g$ !.2Gi`ʏ:t ބc}`Qh`#1asyùd1 гl9B8g|*?Hj DE ZmcTEhm:V3;JΖу& =@ZEnuQ:)]{:],/dohu֯7_T/G{[omj̺ʪU)Dum}G3̪PY%~Ɲ6wBfg7P@ŝr|m!N2@Fs`Ha5E^*-Sko173/rERg|{z׵ra[Q?5:}wD^"MuG=s X[Byښ- W|_Q܎޽|i>ḵ#?z|I>}R8v%E4O,'ILmL׮򪎟5.)Q, Q؊X$<3zOkOQ_/>Jpl0O}\P~g;lw,}Mzei"fDhy3/`|5ۓCUmk2kstSYjX8y*seNx?W)r.tǡUնUd (EbBGCf;9/d* LWn^=>2zbL4Mpg% PLLPqYJw9քA"e&q9(:W^9@/<&r %kMd8KY7 {-\oa{N&)*xȝƻl|2O-0d橁qeM%ƘW@  6M`hb+nux&N"yuj %]Owohh5&Mu!`7mOGOeni$Ϗury0V;-4ocV7pDSjE1Bw0ʞ-reBr ^coUh}u=L=4%,tJ: g Dk^3w)$БWtQ. , mK:Y߼eE屜Ox'RO@Oxc泅ҩe"}a"7x5T^cxn9§Pi). eS@y)rsИMyZ#<ϣ(D.(6c,ji:K ɡЕnBBe~2TzWtUO?3WWayぞs9xs1 KGF}{Sx =Yˈn:,]Z]ѥKLZ<:d@;N =cvMZ10e" my3FUYu=C7yDGK*J`6x.cC[84#@}9}\̘MU#.J^ =lmj̴G~nݴN>rMQyMV^NOp`F2Kl_Eym-կYnwtI̧tŤΜ;{/}K p]]rY?եwo7uɧ4hw0clMd內4KML?|_Goĉ ]~U~+m]u]/T!_F̜Rqdy8y¶"htͅ0Lʒ9z3M:e%C=fe$sâ\iuCE n8pdu(uc5?,`)) M򞣊qR1w̛gt8ߓDtu ,-ǝpG8ln|P,``\ޘ*{pA;!cǤP"Iy UKYiEm5 ;HMyG5P{&/aG`Guڶ(@ IDAT"9|_ДRn&+6Y-=,0KElFY>uF2*K DXi>{Puj/Y"N5WiG!c)CoݾNl{Ӻz'?G7ojI4aIݶ[hwt1B@`o #<`>ޫ@ђ,>eLl HB]]z5(cV·>'ffQʛwoNë NA`H_Z Wd>ޡ7Ӟm 9C67U@;]$6Xxm!L؝y v./ٵ[3A;m>ƀ*B@+΢4?F5vsWE`dc7N-\^k:M/Y ("+el TY  26?da6NF4tDc`AVץ1&:2{]2@zܯQk"d60#)+EJH1p28qd KkԍeyfUh0a?L\f8"zk,4Ygn z]\,gDnђEL[^=v =]-3HܖN"{e42l]NS{vׯ*0>Ɠ[t7,R9<GqF|c`8ayb۷u}\N(O O+CM]l\Zt̆2b6VjB~ߌ>v;6]ssVWVRf)s8O~yݖTm}_O=cdrh "MO=u?^|0khYBgxR9ά@Ϟѣ\XdR W4O }3_ҳ/|R.;sMNdJ膷uyc4Y[uH1㱦~K,-&W(y8zQni1l;6d<9*rq^N^_-G^'2cOJ7rd%cW #0"e)&x7'SpnXT'f]'8 7KQ1@Ǘ|!W,<0W9@` QB@,XJmeU{hQ`&;r̀10̈uYL}&H[ڊy'1Q@)XfL`@)L>`rGtd17Eʄb"46u*qh˱vS,`ZR<;-x8̃GSvk~c0?w[my.M6&kGM{k'ܧ}~ @R ?7!g sChzBª֏֭ۗw$|"]Ir5 &]<}¯T9 ʐFQ96|SF{nLm!JnXJSMWxC#} _{L**tyyŔVטu[*ŝ:wVǏɋRKG/Q^,/+qv& V:}I'zlU>/mK;p3}vk f_.~SS_4es_8l~+ŭ_җ-gu:{NXA;P?ԙǺf\z]+k KSm޽k #Uaq|%[[6g+@!3FR<8EYQlrGTV"mR*;2FC٬CWUb"EcxEtchӚ=%ҟ@Ӟt ĻzD@jq6Ts?lJ`nmj4\ֹ#yNm?$VJGkɓZZ]`ec6Nԭ;"|ohPQJ,G]'!y}On2A_JhbNTGyz*#kr5psVkƳ9 x/{,śhh1uGF k= lYNȚY3nxZ=8 ৙;QBqư'08Yh5x/qPRU `+jwTdcOaK7z:۷E[uE7uĆF+˶rsGPO;fue sKyc^O=S?LyYAbASJg|_Ob6׋ю ON.}%4@顲~ `A-^ bQ Dq{uӛ'@D ݢQ2č#"O'mSgXs ^p| ~d-IYbXPbcZa8VsFGGZR'sxc2;tJ3<=˭wZy@V*>e~<=GGP)Wyad0&F,̊>EXaU#;)0s'p+ҹa{17f3 =ϩ=`u;O/Ș % f&Fͻeteii(J0KM-/p:"ibvL%VZMrZ)fYד ~c1[?f#bN\[ xNM0^=/P=3p^\Lt˚j@ a y%. ," \M9@ |"%5FVУXgWEUUӜsbB9ƃ >uBɰj庭_[#d js F/E\Z8"smNm!kU갿;5(+39ڀ1m9Zʆ0cQc_ԗ??+/0}Kі'MgY,vKnVݖTf%QJ7c(Od`TwF Z^] ]5_-Lx5yn1HrBjYmcƻxv])o\'*`ܓAmĕ6Dn#Yb2H"@vXc^/b8ƌM^;M1MVkrfWhk |uVVfd/Ded>\ݫGԤ{`&}gU;l$b4TA}FS;1q,dY>XN"|@^I@r !E?+XPl9缂([@@#38,$Ѯzn%$әڼЖFqTuo%0j@W,;MBZx,X(%}˿sg/~>.>VEꐖV -rNV{݁E9xk] _ MV=S!Єvs/on`ZP{GʓPy/?5:zB# բT˜ ގ,8"J)-m$js4½od崟yOh?|Z`02}6 |HT`sX`^-+iWS 2y|loݟ֖~nStjEVFK}l6`'ZnS)&-h`G;7baq[ hXfzQdjAe-%8mmU-#$wa%䰪gB%;4m78d~gחps55ÞUӅyu(uyF˺u^b[c?3NUT&i"6Z4S7T ޽ncx(a&qF!lW&ɬ5Jq`w W2-\93/hE+ߘ!u*& 6WH"ҁ= vCNWՔi[.> 00y̠:c{X\[p `gx/cxGݫ긚7cDkM:G$-{.F9AAP2[σ@d#JE'Svؼrׁ',w#%Ԍ˗?yIݾ&vvcc:{~ClhuuM+xc}- f$ZE'|܇-r]UЌ* (Y0vΊČ&XÜK)%YcgmȬr ICbG;h@ɢEx02-Sju DIvyvCT t5Eqґ,֮q҄-ʰ&OB5B7N4a<"#Έ!qp<`IقMHǝh"[ya IDATt{l|YzrUCA$ΔsKB!|cRmcYw%>t:7bR[i xB˶bb2cCE>i&ETk{-8d~Qt~9 -}?` XxTqѹώ{qr(|<2])ĉ7j:E͈6܅8 )AkxnlKh!8Ο(n[i 2vg?yyL>~UWyG/_ڪQ+D)tf b^|1WX@̕#KY?Q:YV;&O+k*--XYcG%%)4э0#8JQi/r{]gpMJh{bEz7F8Bf%D>!fBLk7Sf] 6QP6HvG`p>/&mAVJ9O|ǟ|NN믽7x]~[zK=)u$05`m<m d U0P[W;xve"{̓–m︞:yΎ_(su[jy#߶W6gH Iij>=De) Q7@sc8c1㑚mAe}>n8[Ucoc]yRS.JJִaAÂj*/( (H/0sa}R1Qyum||^/_՝[/^Lq06hu" X2eb ,EM695(| `>~V]Xͼa ,o 펅y& Dexaq ;ČP(Ԝw3'"ӭ݅P;E3+,3 Dm<ף }xOYX4T/HɣoH^=A,&j`ڙn:큁?Rӈ R>w($6g٬nxڅn.nbzEji~J9 3_v&LC4Ϥ Y&s" /wWCg-1B& D8c½2x2p`_P~)̋' :ݡ>/ OW^[o[7?ŋ-LgL$|@U2HfٺmVRl^ZmL7Lw h]]OC}dJ2e uOvry*6XyAUrUbE7PWur&Jci'R>3[_~G'?ẖV {:Aɮ Dl[]wyWlq-=_+Շ +v=t,19x۵>AwWvDV?t?S03T #750fiWcX;,‚ޮ9z3.vq ZRMif{@uona*:;NaH]us rni *nεAQ;,8,0]wGGPcB+EM+XCs*qp9~i<5MхLҔp8~h[TرMZkp0#islΟsINK1h;K6!&+<mu}+s7v%V.!H]%]Wxhoy-bqGse5S%T!X6<["8Sr8eBCkK֎:p] 浱IW []+y £^X k`d=:W癠2\]a D{ T (r/ڱ׏̙3zG?Ǐ΁ B(ҰZW(ϕ&-ΝVi4?ڶM}8 %s2:~m@0a%|њ69ijiwf{,ĘXYI3\S.!fW,Lg*eT-k0B샇G~~r.Ԑ"3|n}/y89?k p[sV2;v %kpKعT/?5?>xXݻcwL PS-Eۻ;u綞Z~R?~ܾkj:(˧j.# gdTiiudLou0,bdk׮{_UȠB>\0 o%&i:%m HiTNzϘ0\ 6Uif9\z* mk@)7v6G,vt1Q@}BAR\xgj&Ka xvd][mNxqpZ2`- [ZBM>-@C#FY&W3l¥^!HW^m m3)[΍gcKEXXN[BxYAь1(h`U?sgYT(Wl2Uli/?u4:!kh{t}c{̳(8TӂOL= ( o` z3<aKcscNrQ\՟HU e )x`VvQbY ZR  ra˟ʆ=)^k$B6a,c†{HdR;.KLF]7Y<c! `' ڟv֖ )K]ECeSCE @&+VLH3b<3ms{_I0. Sqd R 6i`k69ի /]_ڹ}[+,Hś8.niPS PbyjO5!͌v;hXr l`ACmprFurWuF?"2k Tl<"ϛw_^~I?ui]x 3eg/T9eJ,Є81a. G˿㚏nfz7F紾r#•J 7:ϽgS^S ]?vRay[hh ;dޞ7a^o`LQ0~Ѵ.@ U~Zmcʧy+Eg&TO=r)ۼ0*CT`ѐ\W,.Dv0*oԱ){ xկ;GZ۔r.L|K%wqR˸dA4[MU9M8<ی;*2>ށ׌WV;>|VԱ:'NÑBץ# +#FV9gϜv#A mfP C0{^OuDC(ؽs>PM`3g]M'v -7/cp`0kkF*Tr];ȑc UV>u}ܢȔ-Tcn5臁`ͥ/tqiV#6bMGX ^6ຊnY낡u>!}R9`SN7t!l]j׳?GʕkYOU@ť:yx!GJٵ@HV9|TAaUse blRBL#*wnށ"!jf*B_K,JM\f \éab+,lK*`[ⱏi%n1ѲR[7ϵ:^`Wj{sKPzDi˼<}a4``ͫHU@;Y5A'o*ZZ 4^rEYZx$0=:rqhp6C2Mjsrhn೪2c^k . =yZ89'd)$fx|p'O*ʨc;sI6 |l@$i!wZT?{ɮ}_rY ,&-Z-MO4,` da<b^0l= {`aZGb"$EXk{܈a9Q5liF+-dE]=s}a#)!׹o0.2pNτ\@|g<'3m1၁Y/DFf9-^6J`V//ol_{ݸy]>M)_4!R:tOXTJ{rUf(M G☑FXKϋ9;4A!X m-p[u00MAkZ 'z D˷m*54RkߋR)IcZYq ́ "s 3@s-&_U ϻ/0v Lwz".Ҝc نx_v֪Zxx-aUwů$hcl5o(kdyj3yT 9ٟ$\JlάYY{67|؋|ٚ+M5l h8Uh}mnnڽ>#!댹cg&`|##ŃxuM\9jh_=?zfN̼4\% jђg\֨r`dƇv å@Ӥ=?B\1) I`l9wf2y1Cx?0T}h =3o^tkG3gU*+bW|<5~͵ar  kpڈ ՃZ%!iz4ʘIsnH*r RI47@#d(U[0bC֞B'Vz^'6ӊ;Dۛ`bueMI;ؗD 11Nv!->ɥ*午IWw$K36Bc=A>IOV!9}+*bRn@"]/4%I]&#5mgnkA< @*.:Qd`$/d6 ?t?4 I2GR\D`NfX jfv2[ZS:|j+2#b T,app`OvlLhKlid4bP4hfi,!_"Ʃ êʨ Ȕl* sXYez[~tǹ<߿ꞥ۟F``Wy|fsQJZXV+nsv{yolYtUs&zC*lVL4/ɯ\5ˌȣ;P0ƍwm’|3[h+7/?&`1Ri=r.y 5.R-yZk[V " /N>f!v_*M#Gb %bۗ)l0̂,)"]KL v1;!s_LmЄg9\j^x fU ӄn'~z_5\P!#{ ƂF{?uL(*Fc;+l2I.m}}Mڍ_qd#ɨ!pf8# "xЕg_>`s,wa›k#3<8?PBQ9z6S\Nd)]׬7 2X-2.,NhX#|BO8h ө⧞6-rmjҁ iw@ CYd x*Au7rSbA-XM &՘3#V`N 5JJ-Mh"6.>Bb@*Sf%6Z-Ge7V1q(ʶ|m_,J Dhf4"_ H,dm6wzhӣ /YdBPhX",pZzfb^eΉ#bDSNU*B[kpd_|q?ڷ{鍮]{/ g_I +mmw0ULeJüèvQ*BPbqg\}?O/|YUmpNZ#e,`4AsQ|'aҹrƾ7޵G_} +eems `h|2 U [ՠYd3k#t=nO=j9#0eqzAr_I?a `ZܮP@@{d_~Q.X ܉kʠscڀr(4kJQS 3sk~F)fPĄbŒz_/7~המ ?a}s?~ldd=HBa-I_~IC8cPPbᙹu! 3H^]54̊kݓ+|Mp9@ j#ft63er'7q# ?.Eh Ci/BPjuӢ29|õ /3i5MC6"#ͽhmrz3:C,C<y|UkNDcM)ml أP5b5ѦBKL35˕ A$?{ҸRZCHÇ^J{J)x ?YMr&srRx[2%5bB]{nSi┖h$6av} `(-&$,ÜL5 ./U]>jH|.vxkO iCBL@THцb.Xt8 µՊíWJGCM Y*F hДKb,&he+w1ydg Y|D:]Ȉf%֘+bjW߷.]q"s;>|C2S< mTE O6؜ պ)nhGVoUպ(H/Jvpt`{CB?6ٔ"łRASf6JVהjlz͋Tc6_S}_[5|(_}-G F1UGm`~q9Qs 1=R'ɑT0 -ky`P5"\sDuF |<;ն̤:%B3w -݋#h`Ba & e̳9[[d]pؾ3{o^{.\$7F1e܉&64C0ՠTEB< H(y6 |lo6(i5ѱ;we2jLgffe穝Ϊjl{:KtdhC ]w[i#9) ,yv'{o~'Z2M5{۷ޱ?L#ΣUĒ  y1Ŭ% LZv怾rQI?j\튬f.43>p4L7 2>c+g]e .aޘh])!``uP%>  gE_:N8)0.e4\"EɁ9qJo,!w=?1QhHl" `~/ܕF4Xt{wD3Šg5rUĘ: p|Y#"++…C5_OJxе\+曖CN\+iqOϳκ8pW1:M4d/Bu2u l21`@N\NKO"7v7'}sk#!rg| QӾi"py>Tl0PQ6<.H @qy $(Mq)Z984d dJ`=9xP(ak֪!YO}{rҳuַm}rGZ&v,0SxybS=5,+2B*LۈH03\,Ւs&, :w]zYZ#{Ukk ZJͺMse3Kӏ?k|uوW&N@Xq߃>,aʈm43E-L5N>hAă5-! \$heSmL~][ viYy&Sqh my{M{w>6U-MJ!V)8HATjx S]hGs&a JURW*|[ |CkWҔ& ؟&c&#{r]v]zM-DXD)g\sb5Q^~5SYLo:},Ѹ__~6b;"NZoYȷ\G/ɡ6\#sm Buƀٛj= ٞ&N~rŢGcˌ'FQ4`mm 1vX7tΝO |'E)WƺPY5rev(0E& *)o4k\w9,e/ w\BpBT/$cyυForj!q as0?l} 吽 bIEieH' _/xW8Oĩ@OFJIO}⬖+TjS\FlG*<(*B{ª^~x $ Uf]9Ӑf{S=8G?.?rl~.PH-mr`dz| Ӑ~׬R\ > 4g:ݯ:8ͧ~?O~ޱr{|U[?\g9s̳wWIsH[(ZO%MRh}?p88՝ratpKxLZKyV7 5=V*uNԁT $ n*?2gQ]f@$Z[Hn >&h_!&Auy[w?d1U̔ F:Ef /~l㓞 Tiq2+J9X{eî\~n\bVom y6vL,"`UT\LV(=Z6W7߲?37o^z5% ?Hdd9m( ==2#3L$$ I>Zwbfe12Ts{G];4dQhH\U)n;mwJHVăL@n@Q'O(󕶽pak/ё ,ʰD9L U rb֊՛ 66-l1v_a4(msyb5%!( =t2^7S;XҺnsAay 畩I8pjGť;X:W >*fRY\h8&?Z,#P>!}Լ:L7ɟKMYp{;XRkdE- U܏1OwwX$-wT"YP[]Wd;5>zQeļhLlܟhܳĠYlX;rؗ {XRi֭Zdw"g`dӰu7s/._;:o_&蕺w%<JųI;|C{T<ԋ>IT&p.Q}"2S0JȠq|鴅sPC$IUWLD >ͨeODw̗ Tt xCi r97{4䭛vfCm{Ï$a_xȇ5F\h) *emE-9IBTdB,!&'Zv7sV2޸oG6mqH%٬pԵ]1f~U[ 9QXgRѯGy?sW?E;6|` tECsX H9Bmw9|&YJ~ J^4|C:3xZ_nWpBKGQ1F$$B^bjxOߙ6. !cƬhkݞutB;jAiKW, jU 䩮w>{9Kᔏh/^u*PgX 5Gm >s 'v|/7Dƴg\^ʆTdI.`<}+-PqaP )v? R_>Ƈˇ xKZ#>3DSk2 D Ψ"dAr{TG H3ACJ% a _3 C 30¬RA|h O^L P 4o|dܺe?m?y޵}&2^rի>K7?_gH*'m]핗_V*Tp'* Z],%٩իE+nGc;R(`Xӊt).߰hnIb7_e.'w?Sft<z!Yxa6<0>A6B{ d;Crˈ d"b3=)Ί0F_7H}2EszqWrhD =ڔVz9:xL_upY@cG9¶ϋ><\{:dRLFVȑ3:d2om$S/0M0TBL[hu*xsKNv|6фSRIt6hhВ1y3Ҙmۯx>)INR jMu Vē*0ɉBd`# mt<ޫl \4~">+am:KBN\*<*B,Um2!XA9{`|"[jawB$Q> Hf!45 \JIُ:Vo}JMWmbٛxfr !J_>~d-}#+ ޠw:6ѰW`=[]2\fj|`;;;iԺ+Ue@q> 5z,U4Csv:*D2v\A!ԙ"!2 2nG&#'\g̈́_0>0) O+Ygc ;.1u*IKק|b;ߨk/.8 7/3%&.2Vɂvr8.vN躮\s{CWcjUںUr" hmgfdy¦("}rkL O#:#M5P yۍ5< %u#:Q7=\GpsB8.n;EBIq-g?s6~iW<}>7 5C@`H둜 q Dp:rT*DS5B!!l |EbJ&7,DH>ޱ߱Woj| {Rԑ. m/셫?Lx\Vs.ڛx˷X\^jlmm6CKobR`rD6l>آ`~_[e TAk[{.T_c,+D's@ݴ7~*Eu r#-HCyfAZ !͘EÑV"!\~!KD2\H5AXA+z&0J|/DN_Yd, %ZVg9zMŀ e3mm_Ak#ҩ1R("MRbҦOv XѤ3p dw7 :#!XӤv=p(594Ds>O\(W߰V>Q(Ut' kϚa`9|np8c9/yyvo3!%~zf%Sꮐ?g ceX„~/_-SŒOĹ]e0\XCfd/٫_)E e.E/}}[:y5Jve+6mo._؍BJ Ɯƚ|bх|ݑf|d,8 "xf;V)̬V-YsdoO/ mpLeԞ\%8CK0\ 67@wzR^P8P$wR/|TAaffיi]6T'qB뜌Ѳz:TC#e#A%MCIp6#tdF{roe$d VZ~ CZ5Bw]ć#eaƅ4Ԅzj_QnZb'8$d2-#'VOj9 #[vƳ$炠~3^Bahزva"Kc,SU#mJ>I_"C1Z_ig}FFmq2Ax}/sw*IռQ+D[=KۨJ|<7 5*W xei`lj;bLr@IY8kBDKVv :TswS(c?0'á ;{}t|Sܲ P i(:K^ A^C{౽tPW IDAT{Oh󩢜ǣ 9_ev|W#>L JϬH~T"Y+b.άwܱtX:M͟\tzA߆DWr>ɉ2sl[?Mldm5Ls+,< T-g-#FH'j@\c989.9`=y?voB=}6b_"dy.şpƒ֒(H d07he1)(:`ri"s< Xٲb"";R9:s1~d۟Ըܛ!ʧ9cC ,"|nS`jǔ.)ցW5uǰwxPEux|~c B6Qb)Y1seg;31ac!zb/GkܼjGvֶDnt-p 4g)żȢi&#hu, #8f}GV߿OYAՈ䗞s6*4d2d$kc{[ M/m_=pVdPaߘSa s*d}=P V,{1`(%FLFsCڑPUxU40qqIfۼ\^)n)sNI{:)x )eE;H9ː?쮙ZϨy} ;ly_h~:A&6Xa]OfXOuY3v+|ma誄3nVt](1b#_{^w:0n98{a &0F)./-J;4)^ '0ډrlZpؗf_Ҩ0hi:ԙljhRw=ݱ#k:)њ+mI`<_oع?yldVJ&CK&#+3rIf5!*hب`dun lCډ7۶LlrrdL&jʵuC@H ~!A=s6Bk6<q:s&;@ ĆqĠ TLƅ.w}$2avM;%Nw- x@ΩeR4p&(?fڰ*|)(uX`n1=N7h8c~CkJDjMȃ+.Q%"Ji(c].J9XQ>x#m2ҺVUt5AKd9:8tjf՚hWݎ##S[좴V\ƴ|h/qQ{G魕Q託ҧY~!dX5rv,x2T#pPX t4zUƫ~1O0K)'}fl0 gpqʑyjd=A3d)LJ%X7L=eZ;M #"Æ"c>gvې4# f ۉbȒ%$AU9WY3w#B?fb1Rъ PjW2͔Bv-knn["*n-yX"]xVWkR2Vhp).G/ UĹenDDg/jN*~wYp%!lNpN) D\xrc*Зw{!#1" J)iey?*j:(`WIPFx@z[m ZBH熡NP Z }|k<E'B p @0x A4Cp`]d` .i5>gJ,- b6mnw@c{9 ~.hTXARY%3\س֮߳pUY(l}w87Iʺ #1B ,~,J%eZ] .5;~pbq +c,i\9ZP;n_IL4spN_>Uu:vuַ&j\<ٱ'O Y2ֺQ  (ɚ"쮮ߣ$r [*τrt`i2_rn{w\Fx1teّdBJ`>{`Q> fh ΂O^[{m1~ 9 0FmmD6BA\jye%/`(\>G6c-޽k|zz-[,6%4LC#d 4=yDt\˷ޘ=`b}}ʢZXC\#y";p7ɾ%w:S!U1ۤ*+d@!CfSgڂR$`7x*;wLT.d@ 7a/gBq# Es7:sh/׹NEs-ZDqΞ6>$f+}F,E}9  kr熡ybcil.`CoK. N<0gA3H^6tsfNҙe`قc6$l8HAPhT)6D_kkvg$ȬZ{9 2s\T^*N`X% ҹюwA6żM;9ܳڶYeղb!Ɯ3lc;l\] !?z!̖7s=u>YـHy `YGb=E.ޣγ"6].0Nt=gO/ۅr! Eә2^ٖ{rh;X9z$hl4M':6>'K}+.f66w$I- mӉ< `ÁWW ޲_w5\TYD雧cElP3_  Ck |_ZUas<wF# yeᢕEXf\J6*lO#;xѢ"0{1Ex7ވKg,,"`Ͻ!ÄU;tU= |U vq뜵[-e]8?щqWHS+Ω[؋7_vpgkxFZ(hr*Y(2e U*6by\R "LBQJŮ|IWh{g: K,Axb>^8 ^ ug p"%1*sUH5soHbDE~oNp p8|>tZI05CM2Ph9$~ŹkPHQȑ?3M00Ih*##3'+E񑭭|.iNF}C;z:3f=M]kvy>yM`8x*% L&N#iKm&<&۟IXi,Of`tgZ*Mrh 2E<>Hi0/Dj:g4K^4gmm=R'x xsj1kE0lbEbr2bm.]q]~ponݲf!豲Ty3TLbU1%Js# I)r%OT< 9YtN] ]Cξ̷RO k2xO2Ny7+hı!(2|Gp|<GBR#F5D+I q6MLbpa6=zgɨc"6/;Gv%㻲}]mnFÞ4g+mlڨ7R,XOjUɞ1Um?>>V[\AZ'J%% Ed&X,ypbS|6GT8wyY|nxͶ.\V6P VN=C劕+52\w~"@ЦӜu. g 2L?s/|S9 T2X ThJ  )^e8Q488G\;gD(xps#*(u_ΌCO=%#,1/1!U-$<و'toȍuͯ߭P w"@b}F@)0oۜ^~<7 uQP$((_.&6+b:H 8ش<=\g#lt6:8l iuyUk"F0O3p5k*ҬWmݴ1 % vC׭7Y7Ve7A>+v45 abU+Y+_R'-ь2dƆ1sM`2%ײhsB-ZslMkjN9B& :3HxQEPr_M|ÊG#>G9|#׹'@Ep U#n!s4G|v}O~S'jt0["O ĈO3>v}MPr riګ2ZHL.Yz/xcmlv ̫e}bOx3k;mow~es%{W&c{}~L^vS\*d=mf+G$YJ`hS @$|]}-#$9r8"F,`KWHd 3<Fy EujDO0,Z+u2.|Ŝ!ؓ MLRzqq3 \]|59[YY߱{>>õlkY#| %,nk6Gn̸G͗_h>Zn=:p7%p~1?_~FXT:,>l&]F`ǒUe_-^=X*gK-m:$g=yZ!1(t֏?!\G:a׽ \Ǖ:"5G']Ϧ}ssYDhyMՏVowj!8 `uLP7LX+EФZ&㓶)"iG:% œLkhJB hy*q"-;}r6 Je`I.{M1!IV+M CPPI!^A_޵N(b$дF =ֿ0ԓ`+s++4~+l4"NbHwE7Or yH {$0AE~sDuz2a&Y%=SEҫ^˕`fZِWZLJFh4*zӺssiܿGZ[iV?Qgh]ҡ3X_B-rV8Ji9<R"8 jx`z򥚵7• 6Jxaˡp4jc&e=3<1:60s@Rm7+AhhɜY=A29D22r=jpe@o,!lV{o}ߝ?-D$r 陀bϒF9;ğ AyG; #$GE7ZvtgA(AgfC~m@WAL#eZ -U+e{𡍒7lZ1:0r~X,s`bٌ`{Z,!/%ZA!ʜG.E$ďíFB$qĘh5K/IYqh:|)Zn2{w<Y`cwΣG,׶Ywo /[RUlfL9-`Գ 몎Y0J=qʴQ?:l.A/o۳ leeñuON9t2f!7x]J~iE}:!xng|?+I,cr #v<9ʺp;\0CZ:ba`X(H˿b$.: mJe egꀅ ymۿLPce.w*\<e(D%yvnZѮ^ܶͭm[]mή}}75ڛ[WP:E|d'7-ziGe ba`dn>Ÿ4UMPpEqW2<̓2X(2@ F@*~}yRǞr~wDXh2f{}˺}zs+vy.oADi9+ՆrJ҃ky2y| eύ?|,B_î> G>A-UN,::\^Ty Zp܄8Úsu#a %V=faRkMk$r yxx C `Z@ S+@2l2(dH,7 IDAT(d朹c3HS\:@DVGG:T/F TN0)N8U;;Qs(Ŭ]z᜽l%Mc$?'Y+㮤ICOI-7pԷk%j 6(C k|M{=kvuۼtޚmz~[),_bal2iIS$0ĦƤJ6OR02fCnro߹N|:G\Gq xb $j%d #ߡxQ*:>JjAY lqɟTA4 JcIQ24b]ҲsɸPɑ2.t:];8<^ZD)3kU}"0~PMcdB)9LZiveNdQ r/^8#ܦ`2jB"Lk!3׀O/XWN&L]..~H>0.NcTJmhOI q3uk*}h| q=2Ri|9! n޼)OPf\Zɩ\ԕS+WK7s:4YXoB񧌵d?ٝ⹶uNz'Z6[A%R2 o_xA>XR(PDpLb%*aR:KN# Lgό!ƃvs2ؠT)J1FPZl%rbZFsDY|rX *RIx,}bѪA(>w 0YuF @w)ShlD[6@*ixА4Rp0r>iQAD]V B/_$;37Jr+;PE2uf䱞.$7meS?Fx>Y>Xsu ʒ+;ofv24dn''6J3ҮJf) 2ƶ0Y٥{5kslqߪ[u[ota% Nd+ W rOmMŢ批LԤKf*%03ϚˌJ'LBX.,%4rsg$`IHD *V[j/fVUH #8*"w3t7wyEs!-i&'pO F'pu?(M` 6׌]֪oٓGE{Cg^vf1A H_iWd^!KPmnnZH?zM}S9e oFZͶ?k".Z&t|'#=9&͗`ݳ@5ac +q-Λ#F Ǐ`4+kjMҜQH9Qg{yg" SE!;i\*c<檵kce)d~zhalQRj(g1_0;wƺ-)L'*yA} VH de2jMͭqM-+M ۤ hK1Ƣj ^?WA炂bS 7* u#=_u-&:\\?\cm :$@ܩ5>VXLf|sas -3|4UЈa&)p2&s 4k6 ,~w^X)Jdz FVKaebJT0@|$2M8@DB,.%i^PT薘#FceV76-uʉ` Zb0q7RcNzeei6g'":+-kjv;?K__7cwfRIIQ9S IFK-&@9L'9*ʥɸYIcY0V"ܡs'a:)v/ʠ ƉtfY`<(pH1HL%su>ES\P˕A Gp7yƼKa[KLvBH Ijv^um&̉rW֪,MfD7ٷhbVÆ'm<^ܾ+QAc?dG&qI(!N-Ae7i`e=g(-ήah_pS %ko-_*W2Ibx0o/Ύf"{r s?޷t-cH DI9g-Z>Kb?\|1rOoyDճ,H%PђLhn3Re+XXsy`tÌ}eNv k=dj3JϧFSN-0hsLNЃ[h/2j/ksflAƗY0.!W؃/?5cxBC D.`Dg e+?qNxNNl666y>E>I!?  X 3'A'QR#{s@KD"E H̾v+3Ωuvܮ̓'O;}'_<*-([&V&bfTw=7-rv9RKvhomDZ Rr*52y^_-lYLl1ZkL0_3JgEyk4#\*^#O-tSlU w+xN[ww!kDom YM{$Dn8mOW-W+ # &f_aI7Erá|0 C,xVqҬoum[^XtmdUjܲ{1N2#I: 0t6"X0jZ3Rj=-k/B2Y&0G\ǜ|]wm+7iسӧ"7+B;{N!$B"ih[P,G0(4E7,s]A(CheN._?rJuWYV͗+uNY߷VֳВFne/ QZa^g_}m(k_[v|X qMʝX}yAC\[PLffvzE[AGPKK,3pQ(8bR` Ux %0]v%t'FL 2t%ÃÇ}U,oݗ ࣏Tp a 碜Y`v-]ZS;BNue5[ 4OGpNCb(><4[s%$L $$l4iG44yy[Qr6XahAhOuP =c孷|-Ԏoݲ'ٷYO>7 amsl{S*7a,7\V獇%ɍ ~ڋXSi45vN }Y;!t rQt(`@D1@WA%(V晴;8Pe rl3M[?v |\/j AH {s6u fS W~@6KZV.M:al(3/27`VM"՗ n(V0~tosd6?_N|fhɧ?k JUWkoom|U&GVgbʪa[ۍ0܍%7`ȠZm]T|C04T|.N%aSf dh/)kTgSŵՃfA6(l¦#iY6: ~hSRE.#p6=#2ٽF~ CbT;s XfBDo~qKoٯl, Xb4Ƨ'SB|n z}-J~V' HyG{HsH,ѱAd o_!}9|qv}blVTcKiMi%)*u/gqlm6bT9(Kuȭb< Yfvm{  Kԓv|c`. Tb2;/\ImO Tgpr9h$UY«VWkLԬMS)f*E-&:28g)6`0`FIC {X<XekgX%24L䐿#Eo Du|4pi5nc ^U0K=!΄uc^x> 8 %, Ictw6W%ЊT \=ϬbnLF٭{ރ3^5K[r IDAT.OOl:N,-jo}  U!n%ui902z/^KtHZxj𕗏 X L9j%(;Kj_8/e}+gziT<_/vA02ckπi|+ .-lx~v!J-ikoޱ|x<Xv 9s|kP_ѳ,X'v~~)c[0q/BZETDpv"ho2*ҞMA|@|㓗,DX` HANKwc/}ˋ ]h8thmb!Z?Ǐ?^+fcmaz~?}_1Ɠ3e.!Ha$iJSnD/Dxw)o_UL){]\]VwJ[Abak2Ҫi. 8j A0oDoh2HrұsLJ$bTB֌W |q 34 r ۩GJ߿x_N!A 3sc.w0bV{ZZe"R&eJ3=ŜHasţ!xiBkݾuhiRdxՖJ+SIJR߽{הq(l˗wY8EH/c̹3h7{٫^|"94t%З[ |Y:: |#ذT0N'CXu&Liܺ{v~OAs{z6wXxSn.߷A8Rё6Yy5#V#uLb1" иzM`\c\g-CF&x?h%ZSmTjebSiaq)OVՓM%LRmN4dܳ=ƍC F,B#nj>v!ר@=51Sp)8_l:͔\eXSj3Wѷ83ASg_˦QY"gFL3T<.ȗ_͎Tl~=w΃̮bn >"EH?A kѤ-\ȅ 0lr7#|r~00YTj@>+;9yjy71 P~/>RD0-f3twt2e93evb5JS; LkbgX=V6)Ǝ0.6Q7zTgbM>IZb!x&R䭭s-KLZ>/r/z&}]!Wb7B}uxşrсSGd^B4RyJz3W9rzbq9Y]ݵ>`01jl\2$ItWЀz[gKw  xp::xᗖapN{!`E*ا G~} ;0n1 }/_b"rVP3*W9Lt֠'f"4[wnL4 ;?;?@x^}e[ז[Z9) i b[U&G7앇?x4dP?}9*1!NQ`sD?| ohל֏y1[GT`K̶`)iaTRT6z 5{`ӓg٥{VJ qt%Hx5@c;S+JUAabPǣ"74 ]?QT.6<2f]R2Uaj6!e=}.лw^sr@ɥaay{3]Vlp4NmiU$ngY֖%~(1Lj/59jp\W4!1iJ y$HLx Ӧp `_˅jh02̻R8WAR"H!3vܯ 'Ҭ]sk4{O:G 6 5@?ٙ9I X1 -6 8Vji㌜,spm J0_^ ܇j@ &_+ `wZ ncy_R_.ufTj;_;=;Q6.&<[[g=}}g;<ܷմn*mؤ֯Hr^'xS7sMNQ|Ñ24 1+#6̞U"sf*լ[ZבBVZЬ-5kG>L'U뫘 }C Ⱥs (6eWa#E;c # BF´k g"ݾ^Tch)Eq7^tol/ˬ:cn[̬Sy&hX3Bᄗvmyat ϧFЩ3 4ߨrG-%=]@] EU/VU> 0OލHl_HCAӧe^q|(;_FM0Khv{>ؽh ؤ?>+S)E;R< K'8A ֞ o**XZӕuԺdXw Nmv]A2uWIswгMc{ko{KP$}ݸy,3C>ʘvq+< gĪ``*l0\pݧ^uM$iK4*qZ#ts>|g$C-DGnecQ#{dר'K>՚LN%yjǷ;vt㶽\l>t44<~ ۣ'v:x`Rq;k/g%V.*8n>v/ObVײv](60Z[Y}hUIЪʙbf#YY }n^. [Ws[W O3(LKaHxz~)D?{{XکSC ! \+.Yfѹ 8Srf.4V07sHT.sI0%\aT0]>t>7鋋43z!pRk!ժ%!5Q0W?9!$5Eܽ5$ Mαn'@۫0^}>A|{h'?0<4L/89qmX}ӷR2q}_A%cn*t/0;2c%hMǥ\Jb+/J@A[5/ɚ`@`0h^Ogg0'NUKbL|_b- 01Q_pN/>rjS@0aUJU,q;K,|&xF;LPvIjV.N?>>yf==?N/~n߾|i)7m?_΅7Ti4FiH!q4{Am[;3>;Na^ܑ!Y3#9>@S@ݶ ם=w{OpC -54J<#k}Q6mw!aܫ\5k^%_){y S<,=qkF0q#3 E4ë¯S/i,/sx 0 h_ b3G;p4(hkdYM=9ԏ@ #_5O I͚!ƣK'YJj´6SyYѠm_~ |V[ͬFR,laXStz[L6M.`pq'6 A)(ZjisrP$:_ʍJػq:tZbaa^./m/Vw婚! HYO912"9Q8yȗ4Gg/-H)|W ASjM< fe->_'|D^b0</㝞xL. ~\lB&4OcgyΩ&s)A1t`F/$͕uA+*{?{K҆]EA6h pQ\F` PvG̚\žU{SP/nZ 0V'a3;MgCUɲ"mG|S;;/#<7 $:sq|I `u_kg\@xF&C,|\xdo߼) 4LSࣽSqVֆV؊d1PPB)vR{)1YJH[SϻR&U[BbH}bǂbc/5 fӾ9ki6ۻ!p?mcu_J`}bɕwt0X`x#RF(opҶ~n#{eq2KOOOd([ YYtOۆ4ʟ/f6j@/>l^Ij(QjnnőɰF>5/P-mUG Y9H2ɟ*">ER}*ʫ^/Z34 g|t>spp{] D>O4θ8 "ijny1 *3Vk{{Լw>5X4Q7)RϢ-V +4RC{cRVK/nSOX*(_ e7s?f6ߡJyJCEM`xuH)KLn(^$ 62rֱQb,ac{;Mۍb?A`k7Kl}cb~pA b'GW‹RRx BNmiV{מ=}oƱ!rm&Z_\uug]Ħ.Ǜ#nqxѺ#M5^07,*gLv^7fnB3Zbavk /5td:R&ٌ_˄fo׀mPؐ'6{7(fop՘y^c k`An_+3U/jxQUFֲf<ޡ5$,3>ܬPL-EKCI J- ._695nixH$lS<8VnK}|/]|:/CߨʊYa%ٕ:Ӑ@`{5DӠH">24,}]SPL-kߵaa`jT5dр }ahFsA6Y iS@v6C{4 pam:JÄORZ#[VoY=YXAH]ۇ"3 BPiQY:lSL 4e¦156AV㣖 0?{bN8?Wъ 3ƾ(+| tB'oA_ 5SU:+ 2- Fvȹ̟h·#^Z%g@HOɄ7oV"º4l:V,$//`?~"^ؔ⺫@qgeY"Vnww4qK8q΃J^y8=\ h?+j!W}ƽ /B\%V{ſ` dYk0.F5p<'>ns??? j8vm,H0&HMĆ#b6s`,uuztwЪ2#MPC6h!Vkxl!R)N[aT٢,m4-\5eXonX=E@} 4T ObN,(c KK { <o#ɕlͼQB fGdXI x{TP;e͔w85 4Z}Y1c}Ɠ`ڡEl& HV\c !i1"Z~^t1ZJ*aāy;L@uΓ`S ́H]^`P[iHu`ӳ2h招І^U ->W2B0kghJt[ rzG| ƴriyfٲ߷'>Su1gϬӧ'6NrlҺ,ǏNLXO搞!,mw=PodmBS IDATU\6PUYFJDt;5 M'6Lt/BNYH6+iNBaHnU䋹H,\0Z>]KMy+Uɐ&2 #n5О}x.K鱟+saӟ mv~Q?kݻ0Kn \KFrFkI[ 5a_qωn~sE Qx$Hkq+ٮDGBԦMSi{7b: ˥Fǝ>p:CLw<0֋s7B%u6L=~gIn|Hyv%_MZv&?K75o/# {΢tNF}J )$B&w=ѻM<beмwi)(ϫ;zE;[ /ce ǥT+sv2o~+f*oք#ddp7M(>ڱJ< )!>)Z[]iL#NQHe@p) 5X[I?[0Id!L}MJQd20wnfSh-7(ruƃ{8:kfsT㉭X:+ ?/;^@c(֝b@њ5 (GD\Nsg^:s % iK4y߻hM]vZk[\?V˦ =<03E0_-u_m T.vy5m^Vx28mE> [۴jx 6 5I 46C thg s,5&rb҅Ǝ@j)4d ! L^H3 !y!(5 Σ D6 X5YU ʳ a*H;"T޶ݵceW+방co>zbᅵk_؍[6-)n=9ح7=[k$ҼK?vw`7nYwoGfBlKj %XHskJ2%0 \~v+68/%#Q>y&p&&^7i2yF?(~:0Z*Rmj{ C[G\7fi5wzey;'FԌ"șInwD$1qVO'#b׵Zٹ_BF`sN[@ \lcC`#~)3bxfEF86^.j:CuM{=&!'4zYт#;9#Ԕ8G@&$:=O'V k!Y-ž>yb$ٿaǩcgAPp.7RĴM̃ҒR@ڷJLv"(82ht0kڲVV,I7vvD>o}M='?Msrxi}cKW^[矹h=xw`̬Rr.^}ރ9hLR0hGD Bsw,K]_03(k[ wJfٶzIrκ O7e܌qO PkDyC"B]PsW(;[>0wͤ+kyBk *k~ji9AXR F ef۳V" Ϻ*v gmRAte' L>om\*^SCnZN_+oxX.9!X 7zyj`:CeeP1(l]h(}x|V܏U (|&c:h&Ib%`sS˜šðÂ1(PBpCOn f6b5/Dvv׷kPb # "E}h8( !@DypQ Tsc_?0Ԝ0vZmc`vd)mF8Ab&7|$͵dAU.rE{7jU>ӱf {g3 ::d㸴Ucbޞ?/l?9QP!ܱ7,ຑřfiN2sx|*8Fɚ-M$%Jf}aEcN@GD|4$s<ߙPCP5C@@9 ؋ CItTH#lw p߻vF)¶b;}g|d܇k} u nJB8qⰻ3!.o If5bQڳgτ+ݾ:ܯp)Gk2a>Yh\1'8s71\Pfr1>Q@#L&,4aTeLiN)!?A\hu݁\(܂ڴ[r-QfgO×lXk.vv?kPmٗ^g@fo\2p%M|Og-;=v:nT~,ڞi:ݮJ *L햀 <"nR{<ˆ3:-qHȴuCѨrI{0Gt~0jֈIGZ'ܕCT.k5A\ L2pE䭖 [ZB>Ӧ,McxYRakCԲb)ގ,)k < kP4o0 0R0ÞjX ^YaՕiF9!kJCV⟄tǭ_eaƴEAe8׽R$R6o[R.Xa{6LVImvڽĺgh3(Sĕx[cΡS szڻ\ye_ x,vCBFj?xy6;y*Uᛏ5@fsCj|X&Dh$0לgc4(|ޏzd?p2i'ZU Ђ ا aޤ9 o'AhJ x7m+V5;I@xc[.mH@R-O[Jkw讵On9Eml~?]d53kj,M [.=Ga^zh]+*'ɠ9Nok4RM$iiVG^{+WqLZ0B?4\aB`Vdjn%A{>UՔC!s3cY<"13*}dukuc^ 9QBR/g`|W]۞6{۶TYa;hc;׸/yׇ('ܪEe͔"?R[R1so^gM&s[:?opҖk %EGx< ^M\#گuHd8`ߺ\tkuuLu'(x4h=A_̛|qܢRv9z4 ˒*M#.*JK4Yٽ-?z󿰣7ֱD;vew`=+{?™h a4K"jm7^)2VZ-] LEqw=44i+1^#.xNmxN{n_;~o)_ c9 ʞ8Vo P,]xt!HHrPf. q8یYIII\/3hTWm-ZEAYhz - '>!o޼iG7R5 5p #,Lp$B`#p2*[xc>_WM"}e4a >:FC pF=5ez#bV@B "<~ӎʀ#sk1ǯV9(b~9x&0(6XxPRTS| +*X6y DvWM<9|d99v`Yw`w޶7mj RINȰQãc;~9z!ǁpQybf3HIp!gķe;tlQs= y6}w5e\C7 fysJ]al cض< w}߲x_AWew7gCȳsy6xVg }qx"87F/k[5ԗ涡(rI@cJ$Q| |`0Aq.wo "\'>P=珌Z$EhhXCDvI0rLdXPDzWX{+wP%r?.;gIk16vδ_׆&*JiX]#jYLH)Hޑk @BGLm\))f_dlٜ'6Jin6lA16+)LH̫/B,b C.dP}4|,\C@Bá&m$w17Z b'IXRKG<Vq ;{vjf)cp `<If4\ zVɘPwexfAp?[ײ^sY7( %kiu}c^qӻf7ibYj5֤i Rtۮd50pSsMU;8 D43WPl*b؀Y|G{O5*KwqnA]i7ͽ-D[YBC g]}f;-Yz%([!1m~- w=HXu /{_-|vwv>ڰ۬+n59{G!1;fwP\) (.1 HǢ츄AyGNK7qs;-DxE%'C%^f |.*ZXWOx{aPXx(L<&Z 95:}=1?7?4\q1dHP&)gԘH䶩 i~_ C F[LgZ1r&!$HFz-s\4#=q?"a~WR1r1!R|XeNc<$ƧWKx?ZkFr+VRԒG7^wn>{.C޴RlfUϭDl%B6@bne9f=FF>JQxtxY͕ (Z*TXͭ*ȴд,iY|lR&pz8p8:F&yYHK1T뿈 BkJR;~CM >kk)O%ƬQ!c=lf17ߥQ ΌL2i>4+#3ldCv/O֘A8R|EY3{8 |r!~nϞ>;sB˥42>{=yjd͠J0]=ꌣm6R㏪gXTX{gu{hqt2In~OmWeAW;-YO!܃E`,*u:4NWà p8W~^z}ɇuwm[V=UhEO&vO&E4ppK3;G ĸ:6kA\Kİ@[0`wl`XFyUN'ڰӼ:m纄_ѧq6jx=8=?w;]j!m7 7x!F 3@X!^iƩ Y-&Mϕ[̨cq\1q\! x\)RVfۄ 2P ~ +Y[K1"6S "H7`?Жȶ,XbqkѫԾ*ĺmol7Xs f_%zl(/nϚԓ-\iN׆Q:@8 Hp fi8#%9@J.Toi#?<1W3P!.i)@ I}mZw+t3/&QGZEMȝM.neiʹfhg(JyPi~*J =m_Ww7߲gOm~kGZKVSB @++3e庐f?e3 24H\BZ7T\XֲF@@u߬G; 4 %8(<4DX G'G/Bꄜ,NhQzzᖀŀ&x]|w niؾkhIsĐ@i l!kk"8zq8$0 _׫sZ92V 0* ;>#KFljGG";bR̻X-SOOyr EDw-3WN4g:5W3Pl8Gʻ7o!N*~}{qme7٧v~ybdL vebnURYZ4)su$Jȓ T_>e^] \8#ɸ.+t*6#AXh~GX'8a^߸oije;q]#;k$w-Vv1.W; K[^FL^i`}Ža<=(KY;.޵k|fτfڏW+^t>q.|! pV{6jM/{mwy3oOSxDyN~B{XBJ!^BG\+q5bg9@MJ_x_zm=HPe c`c4ĝ1g/ IDATgQԃ)%5g%,G7d}kɿ/Fۊ1-,Z߈4/ܫ'*5怸~dI+/=>i(%reۨs 34KPd^}#d;c] tNi~=m>xё+L4tႃeƷLiBOBz@C4Ļ+qƃq}NѹsGⒼ59PH\%Y|k5rw-.\p6w`{%,5}&Zp !H_1dXH1A.E P y<8GpJ( ` >÷[[ lrR&ͽ"4o arX0p,A8.\ـ1,{Y(0ԤnY)["!X**D @珂-_VBnsF\Sw)|"s 5hO#?X: /,<Lh Ks _[[ci &|[Yj}ٿssZnܾc?gV*;\[6Gt;p#wo[NZ?c_y ̚u,橞VxJC;ܓ&9GfM8^ w/e\qbE׊#J 6X׼ӑ͏l#u#귾ai#&?k` w99A;WZTIH{{2> & 7´Iⷀ#.l]xн;'xv<^s2r>z~+/\O@w{~gM J%# Q[ϧPu>{LIcø:֕!Ա?k5ݨĴlbBZ-q&֨d{j+6=ZځH@w@(.`RWwDw%֗g}w@ƾa{l8;68 oi6+vE8f#_0h I@|2YJC@?`WcmԢpʕI\%i 4k\5@>'nh,A)2eO &H #SF|Eخl/~n'O_uuDSCzh~ gƛ ̖uYI0it:b0ǫP9- R2&h"7 ̨2s/9PYbN-*)&RHS5QowLrkfuHgƿ WXd "}R&ׄ `9?#EEw8+o(M85]$[K0k)\gl"\_0{Q_u=c?}/j1w$(`܋Am Os !X hˌ}, xK?O>/u( yvr4thCTfD0o FK pY'MƠ,?|7׍\ȯ.w^eqMHWY)-{teq'K<:w$cG% !s3nQ/' rT!-qM+qrmF3;'q}=T 9S~y XVQo6؞~M/'ɧ_d_W-żD.Mx%% ͷUtQZm>񽚵%4(H\2KskH `ș:t |,c&M3r{C+ۄ4!'䆁0c0j1b1RJZmK ƷMR\peiˤt偉Ԛ Vg@92b&*m4=v? < Q]D<ގXD"Yi@;]WzK*XLB4 Z560wv1}Oc /f3wx[.6=WibQ}*-(fᆧ┦ qXܭ -3(e$+2m2Jje'5m(^|3JZN!;[#?f.,Q)Xk@dM#48Lݨтa ^\y=E34Ra!lj03;?g>p(\C}ozda*6/P$sF,P~w ekwY*f#hHa'?Kv4fЯ\.?`|I=s]Ͻ3=hǞԏֈ<7>̹ ͛Z9w晖c)6". mU8p2x(^)xA =+O0@@q<&O՗PdR߸*E8gW?.~R19*Jr;xP:1Zž;k8o!c4p՗}bk++:~^ @3 5#]]F|-Bh N몴u82>(-y4]ƽCzҡʆ \R60c H aw;>~ukoa^>zq{ls{x{}\Ѧo`21d}hp8[oh^0EL1 &Z3*+߬_I:wZm!ڲ Cp^bATVHafѶmKU3:ںCd6Ã}krk Z"y~,uS1|ڂ*9s &sƖ3 Q.m2[L6;?SВbank߰doC2 ъ`2/oW/ô^M LV(I=T>tw-P sdڒGT%{sWp͉_w|8q5W##L_D>7>~ooili/ D<,J.^Q 0 (UPh@ ؄_X~D G k[`0X'X)BUZSMLw¿<(K?yg2_ !t ;L1m&$> YBx'O"6 5:2T$<0#VZ^,*qZ,||heDw<#0@n `1_a&\Ƕ&Nm/Jp?1ui*tezf|]rzq~j[^[g~l fTU32NմRs69h;?Y-K^|VX><.Q_3"MmkBE8aI[\X.V%J*enzj/ 3<(ZHXB";fD$IExECZ/4`F_,0KdPba6O\pNoEdiq'>8G:'x.^`9f= q Lߙ#3rW ]G>?pp&O8s`ha~;m<% Hp ylm{>^m"bTsܿ.6Mqg2-Yuhϒ+Ho .GڼsZ|jYp8Z#s401wv`q^#<^tиwm8Z?ڢ}}G? blDD;my)dc$o:"7beW=o Mi`疶!Juq~a Sk&kv㸲V#ڑm6ܦӹT`МzҴ6?\V w;UD?gp k69@#0qsA\ ɜ <ϡsG[<٫Aki'T@*vN3W1ֈwۿ_ԎwϿ3>k"æftZh#VĘAX&Th9ifpOXF gBٯƒ!Ff߬'k ђϷrioߌpʚU@L^Uz`lRGV7_ ?JI-Ȫ1a VZh(+\0XLxXI!8i15߈h g0پ' Ұzc9 &E;ɢPm1* ( *D(NVVRBdjgeQsD® /ٮixȀH )s?aJ[J^a6/P@\< I@uW7ðÞf(@Bp5`ݸS>RBe`:ogܛ [gԢh{h n ǻ*gski켚9A/8D .h>"&2zA;=gW!VV8`?YQ `}1;χq1+ <)#t?7s5}Jl]J)88 0ԓ(eԁ!}9+# dHyk)S#쒥?J)RvF)ѐT(6+6FR#'4Ԃ&JEޛXe}sܘ222fL6E 6` !}0O$~0 x,2)siŪ*rȌ^V7Iq k1Us9DJf Iu<z)"̢) g,fYqYs !YLhz|=|:]+`މI -&q˦s a, ;ٙJvJMSҴəP+6 |(x(m]Vo! ZMt09zvl͐btD瞢9 }b\AI30.m0ż3G3yDkQMa<j Y5\hj@Um'b ^-0QuwyEgx:I E!^i<vXd2 )  lE(|ŎX:$9<8ZrŲ r`&a8V Y̑Z#6b1N&ժ*I'xuzZrf#w4ӵZU1qt.,$|RJ{rzϘX+Iz?|Y_38X8"eyh_&~d))E0d=0=CDf6<H"1g&pp O#ˆ}[aCT KRy]*5%W/4꽮}]miX 8;U=65dӹ$sn!6{Rwk,r9-"`CAMnq}Rv#,;'@X`D a5:bjw]ߗ#6|dO_}~?ti yw%5ztl~ʅeә=}zA^V.zV~rd;Ǯ_{S6芻;Y ?ZgkuG fĕ/ sFvrUf-0{0*+3cL2 dj?eS.g$UI>N'P' >)- K9>UPGlxNzThd6MDRUCI( GC~>BU&09CZĭ`@?G$!`V3#m`RKL5Sw~gk[ eD,+NH9N/¯TTxGx;;L6䵚5K_ \b,o<6=H6Bl%GȢIx0sH;$bM ?S+vהƘhP8dsR1$6W\{jqڙ : ~a08/ ̖ dVؽ'FB]BZO|hl-WSO?DӨ ϟsk> 14Ric`T\쎉84b0fIh0mq%\s4|E0-҆ML|Ⱦ>$l.y|s?Gq_7ΙOs!Q 7hg]=ɚC=ԯ°F=1Fu>Ajd̟3CQg&%\HT{ǁt**H¼WD(oHF/uh<ϔU1/E[ >Ćd^\WoaAJq+{{<&5iw]䀬rD0$.*`liF$A@@KLRXE(Qqa}'{:ޑ`m x k"C\SFL!})3#Ąv.#Qģ*KRΞy)-[o/fKF}+ګڲ'm|*H{{Vk׭QmdfulUh/r|$UڰUGd$ή{ur"gJ땒ݿwe[ M,!GO$u*UDdVmNis9Uhpc~a (g+3Hb>bbj3p#N*)$^,,-e!ц tut]Z t/o\aM>-ɷ|ݷP6f|M.J2գ9q4fp:x"P"~=`H~txjX\~;{DԀhHӆ?PX!lY +i."C} ~0O#yY{6V{N0 T 3˦v'Olxve&6 Ǯ4m ;b^UJZ,!ODUP'zl ۷cU^ m9ح5;s{Ğ+;vxgnسgs#FٴmwTy]`>u$W Zv_%}:8Qo`sƒ8;97J!Z&1 [U+HRHÖm k'C)$hq_klEgVnEb*jylN )G(R_X w-\5Q!q+ (8BD;;I`VW&c>Nnl+3 fWj,yxfvc~"=:f(A @=4{؊ݕ8p*zlf1}qj$<^1X50C6V3ffqtvmvZv>93"=}h".ٱ RRBo:$/׌!fAE"(ΰ'`r~vw|)lJ,뙴 c b 4%hъRCD I^_[/,KxL?l U,d6Eygm_< Ip͆%wq-Gaɚ/mv׷Fӂw) uYSTRhcv'{m9KAA)dBID)pɺ]h$Joa1i aLRA,*Tg+ٴP ,4eKUZhށ)Qd !&"`9yx|M<^: &B =Ri}CL!}#;r{j)S'g6D`EPw 5;==L;~kn[} v|ѷZko%;kGObvcZMk;jwQhBZ桕K ړGGwg?ч6 -w% ձّ5{vpa{>|Pc*UkZnAAr <"6?ƠJLEI5DxH"M:zv+;WOb9ޙ"x#0eq"iIH<ILQ Yzz'įn y<ƅ~;b:9KcӇ&) WoB (ybaY_hF㡑*uXb90ݣYִZaR GE60Ь#lhl24ޣ4H)Qα\ulQtv-PW"BrVjh3]YEh<;;;YO=OG}:w pdqaEL>=w>h\ H[cH.6 G羖7l}r8|?4.n Z ^Ou)L>|:?$n {u}0E;q,mtRo``ɘ:;~ŰF trQk5piTs{M<͝+OKw$ އX> uo C/AV l] q85ܺ\`=+ED՗ҰX }@$D8ԫ嵭]Ȟa.w &}Pad)lVrZ bG-aIe!Pb3!$!¤g@޹M'#?*Ɠؿ_;M{Vfvֻ3P;NݷBfw_}ݺ{v>yrl;;{V\Fe[XVw>Ul=ٻvNǟX}iǾͯ?!S?wb'/nXkf+o 0_لD3$O2MP}bjBa0[`0X'! O躔xDp$Qͺ)SRT`aJDOlytOi.֜?w v"qD^l1vIkvM`G_ti/`Ų՛5k0iL*+Hw0EN-1K>dW5 |1V3.7b&?N)i/H=gwʱi%i\Y e1bX#j8lڵ6f| x eXq*p^T)K$ fJA~̅J/ 89gafD #9$bb6L,#V=W:W6l2[a5#5[U~nDzFc;}v.V3ya&l889b߹^_9TY03?v MG3Wϙ_C:P[ tCk*|WpzL6ΈU&]Ϋ t,ƈ-$R4M>n C]X Ć2%E(gB:-2 /<ra) 0D()ڨSԠrY N^CԗKjr?(| fj!?w`Z3}j{}'@mEd"1rڕ Kݺw;{ֽ}˶v7IgmU<˪dD*}9lV]o~;XѴyaa'OLF*IZ0@"Xj b9tisz18M/Nœ4^*bRYdlR2X-Vb*$sE$$`haHW6>O\u\IJ -@XFx?_/pm _m;_5Ϯ{/?{ΰ$ϦU &]|1e%Emtnw+s1ym-.QńCNChu16b kAe%'L8&{8$L0l8 D٧?Hb&6ڥ55 -L| `UXT1|ɰV+vl.Iy\`p6++.dy[~sp6Яjݚ5۷ؠX36 0*բ4nRPNlқ/mlc 1HᓹuF.m|㧏ei=  !uQd` 82Fn;ǎE?3t<ς iߡ/ϙ_[ uօrٜ}mbXPT1w^P-@w{ĞY0ǀ&@) FT>G߄?Vl$nqcje B"lXuiȖb039Hk~llN^{M۵2^D]gV8C uғJ3+|393vލo>aj,VfÌDNƖJ It,b4 x:m 8.:J%)c$31~=2A%$]FF9=7_{拞nG|}qoSg/*= #B߮Q~(I. *Gj}#I&$X1Nlq  *Ғ HSihV"=aA[,4/ - 5"XJݿ#9R1EIY6!j-cX)ăK t vaS=t0<a`\\8~QG̭fzW>ЃYڲZDNZڠ?gsx%~bG,kә55mgH%]@NR⒄#?8:yu0X{=yg>1.؋~ "'Z1=Ů9^SvkFv@~U&80snx1ѽuRs_xL|G'ݐ?Nc9纃eٴ+Ќ,Aєh\Dnim a[Wv;:H!}aD>4GqA$-G_8`M W2!M*5\]X\H  &t F[ A6}?m8)W+TI96 0TBEsн`ΗD\n^j=ށHɞZ1%b±8щN|7eٟ(oW?l;~ÇYjm;:dϦs>cFwl{oߦϴ1t6_t"N^}&/h$sJO˓M K7$)NTRK(/fMPϥ IDAT& RL~}P Vіժqa0rbEE$ S6MTV+Od68'd\6Nou3=e7*dv.$l"jLIpЪap-x;,iSrs?_:gɳs|WFjw?*آe<Ņԛ$kaFK!,2&ďF IAwI!˦fӱ|g %Ii`8ю`>x)I"E嚵FY6 D\C|Kp ;nG`;uה0.Z] d#m&\ NA/^Kҿ6N3٦;ü3dk䛀kiSzG6³uLe*r-lӁYoowtؾ>4/BɦlyB)_9췽7|olXUCZ?gկkpB2MZ»Dz<=X?w^ˆ Z(B_}Ww@'LgbU5%t\è;]o?#14L;{ՠO ~1倾 8Řꛕ0}Dk;o C" ^lTTu|͆ZYUgf~T2Cp#2@`!dhA@ɘ!M  E|LhWNZďOp!E}ʨrY;ITʓإHۗ sE mfzM6O'ۯ_ŻG'vzrb/zɟO:[m۾ַ~_;UOm{3ُf 5l9BqiҢOf*֬5^Wz^~ RS/Ǥ鮉!lmuW*l؟5j%+Ԝv`&Ño& R"Z*媒Q08FX5׶v%IABBnispE‡bjT=ԭy 쇽kFtUDB(R75?MWy9u YGUóDH:%"$UzΦ x$oC Z "",DqS''bt1&m}$#pՐ6dj2> ;0W=JΜ(?tls&m$E`,n`|S #}ƽe$.Y̬~gJWw\qbTjwʉN"@L}\(36ljq7ޔ_93OKkwvYQ |c E8Ij_}?{o5+nmEu'!>muF"+毜)KӶ^<ǚ84a/ 6ux!6hyirTY_ʯ'THӟ!x?~}W0|/:/\R5!?ۍ6ܡ|HWhY>ᘕ) s+dB=uO51,zHSe43 oxsDSB'LH_V0`yZzMK3M8J) qH *΍#A ,BH 0JAXxi/gR]x'LH8\dídnl$e q[CgbS/䄅Cd#o:#-w+_Hv|_h4 v~|l]ۭJ6H*$YLnԯc )䝯>SMGqrIK ߺ?-$:$RTn"AI=MBVڪ\J&<&Pٯ #5ĶZX|0 .H؝c)PO,6\c9O) MpH @1GlÞ-h`fבp?ԛoVn9E8zq(rQG<˿`xRFqZS);'lU#E N7tDo-AV%g.;1}כ؜,Py )S 9 C"_N!RU0G79Z/ݵ;|dO>~`[{;>~'+o#t9r 'aJdW.!=&W< /|THdB2 g F $N8=ϭ2/VahaϟYҲ/%G~lO^f L>+.2cXk""ȍ+s}uW8X~z&ί6 xH994Uku.PLp ]A|{K7%83|ft;}FppT~ok4h-ν4_sq3?Q,r 0|ii| np8 ;@M.=O$p$K5#b;ꨛ| FL}WaЧ!wW/%̒fS+,]( 9&7&yP9AQaX֪g0/:YrDc(|2 @e4nO4& ;lڏT`[(e9E\@.L\Vr6O?D2˲"`U>!UVɃ[yWW^d+_6jIAφ/і/Vh.X5+eb>v!Yzsܽm{;{'~~̦##6l v 9L-l2+ib([? i=X{'MplC5{GYmoղ^h80}6l"װdQ:&)H,du֣YbS:ѐb mVIZ (2rۡT %Ko=󽫸E"\/Aι2l58V*yFba0?f=4b#?j<`7m1 ^gh A iGE} YfnqcjQHٚ`2SyL$Ⴡ@X*ZaAuWoG cD[vds"a&ڀH>!Oy >k2K=<Ë_#V'%'`I]HʛԷ0ҋ޹̩}Gv||,hpag'6ᅽۃ~dYڳg6y%IȾVZUlY,>A۵W^o/|OLs3#OI#\eURf&܊x{uIKEs'5'tB*l|l!B?/>zXY{U֎hΈ]=%[W Z9e&%KŬhY]Ù4BTl69oCC2O< HS{/5t畧lCy?]WWl޸}ɿg5\& &c<Ѻb`b8%16u'B??'6ʬm<)ڭVo\&JF%h$kZE%1&ZXQ"߷۷of:Vkul^([ ӑr%%AKdr'fɆ3: efd=zxP$㣣#cwNFq'J շ߶m9$ϞaʎIծte<=9=oao}ptd?nzzN.OMQoi1@;>@Ԇ 2>r*> Nc$Bq` t?:σdwϸ7{ub{%ןO}_ھT_Z/SP!S=dGRīrߘ&Zsl_siv필|!v-W?wMAq@9Ka ֶǍa%\`s@3AsB`A8`/DZ,:2 o TGA\RCX!QD ѮV IR^JL"xk!&92.$(RmAaH_.р;NyL4eQ6kvCeuliO=TVT`dpZ2CL}[GyWD!=8M=bxzI.'EJ-==t"$h1.Z㌣njƖ\\)r'b'HF]qhaN,Sf֊eH5܊()Cllfd:+@!e%12KKn6<X"}uTcʔd Xx;@UVVqU`[,?iF<>$M]`/tI +[ VUg݃vVb&3&&fw?>?W䢭NzSknwۺ[{_@}}I|"0,,[=hjGٷ磙4iHшZUд&³?9Q©׾޲~k6VTjL;'cF-V9f2,X{_2׮ R&ߟm\}杕=6Qk`@BtgOm1\MΗXס*xߟcx1\${#C)T(+Q],T,i8bv8Ga\íp75d>k AYr בfL3c{}B0U,!.w@b`C}IY u }9.$BOv]SQ)5e囊+TzOJiPڒ:ӅnwUm8)hطhlв0[OHA\!B҂ďڰ"*ȘqʔEcw޲ZdR ""l Hi.b\IrٵZg۸'&/e4rnd>*yE2I;;2V]ZPVwךMz1FAt  ΟŷߴEb-IcEye$fg]b"!20%S0b#dj#q]T2wvLgL_W>ۋvD>!Ŧd M L$)%?ᕤ 2R$>1QiVX8b}m+:.zf>$O/5ljٓs|G2(m<'DW[M%Tli3ҫ/gϬVY/ۨ?rfؖE]<{f'ۛ_yɥIOh?zl_vvzaϟ۷Kon]{~tlS۲:gGǶ _}S$ܞ̸  V*6gv1?w'?-j-k‹֛lܱYVǏZ^Zg{vV)l|k_fbsŕ$s)#TUAk gf0}0:A_Ғ`aE{fI"qhp4I5q(B\dT F?D$FNpLc#qf8i [1Σj{kQop:ڋpc5?Z\m6Rj+~7 &&s`c!_*4l,zFMլSKl}dj2J[€c^h ހ)L<0O?B%1ޅ//. 0P`$Ŝ+* y`d D "ф +V0']1Is -]DLtWUSC/QƈEAE2 i'6,avB~c?9C2i, Qdʙs$ݴi­xqjkVk$&xFbe[tIAzuB^ mؿh ʶ:0Ofnw -qaRv9\P$d\d2Y숒ӵkoXAns̃GmaKDYY 5nc[6=fo;նs9E=m0_#F0_9r!oHǩ?[ grTVMR;;At?p}GGWs<^<_e⽸xw}Eb\m4/,Fv=k6Q_o~蛤 99uj<,\>g] \q9@(mSXꖷ6fm~v)oDcTԿu-̳+B./)?^Ǎad7:&b=A_yȄRX2F<)s~av9 h[4Tx,&-=$ ԕdcF"V;+i?ʾuY=rKx#NE7 dAWPLԉdrM`URA]н*$ 8e6Uғ=K[T> upieg#ef+=zv-ZrU=*Bqӳ짻0ZU-NoFzHǃdjVmvo-*uFFԗqffHWso}_vM3g%J4"H;\:Q k|3d m@e6Kx4]x#<3s}oVUW3<}zt-̤3μĎn,!ZV&e=i?V(*)٦?HpפD+=uR IDAT^Y]L<܇3~QTplKxM8.mXX'%\!v^QNäNSg|ƣ=?j6lRXVv˲0i6ԗxjC:8#۩֯}WmeljYfjՆgfΗlk0u]Sh:2mylZ-%f;?3v uomv1ب޲`|ʻS.3%$PH>04!pY+[ػ gdR6K\d!F2H7^jfԡBςBupfO$'qPu ZI\!qu󸦜\ǻĿefԗ$NHs .Dq:YOmXTiyvL$iRd;%/ ekւ oGdztJ3^ /70a^R:$"0&b:&#Iɔ$)E;'\ C "DLiW9 gR $C_$pzKRf%m0fQSaůB vYP.Y`!$Dl%lUoRD7[|o\4Bk­M {c4il] j@4v$ZaMJP=c6/u%E_ >ltz/*.lcNt+5elխjɖ([&-EI $Fgmj,H-mocGbdFrm7& ֬WK_t8@ v@ D8P'̵'ƹ+pC<W%+̦nL>41`6_\ g]/]YEyלG___bf!$.+Rj6 O~f6 ge㉽}]+uݶgϞu36Zz=T]ۑؼP{|0EF(m߹gltq!VFfnݶ/d?'69c͟"\b39)Sy<~+~9y!&ڙ.G] &gsOeN=Vk%J$}և2}ߗfwbUw?f E?A0<)!Ez9 "RaZ*Ia}+V\6XD3aR*dbBʤL1EK Nٙ5+kV F"s-&%Mia]ڳG E+yuVoYT 11l呸n"U=Ld&`4~V6EJ4+c]ƑScB.i:sa綒F?lw޶/ܷyq% +yrƙC $&[ѕh(J̱6x愍og^}N͆ClIRdu5o}0q/sj/zv](EQ/\p{A3=ilLK.gp| $šѳJM?J)e62}}]m&u'hBWsIZ'jTA608/#wœͻꈇ W]Cpwe='pNsݯۀ\Wi1JnJA{CIQw`P㔤`0C۩gd -opKQhx\+Hq3p2Q!Z|EhZ9aCM>n#ϲɄT e[& V<1)uKglaW۶=[XX4lw0%V ?G[UNl:[o޳Fk|bi϶S;^س 9ROg-3{3i)T6)=[Xw >he뜓\_4O5q~49> H9S6fj[p&{i˒4X{Ji9y%>y~kyvO"ދ:YqO\R6P4 D frk-3yy/O]ΐè@@I\*ڡn7Ȳ0a1a ؔHjs4@Hݿ7v[CÜVr20ѨAѸd֥=0A Ϣq&.'TG8x/IRcg_, Swy{$bpF܉ph$l8HaK! bfFϓ$ {O$B$sBhKUvbީ}ONmY5RĞ?NvF<$jȎ?eӲbm+H|YJdX)1qWvzz&twg&7ZU?'vxxM 4.t1e RqYn޷OVin%G61 IRq);`d+̙X̀[#|H&[y $vȟǽ~c>Zw?:y|чA$}Nd=])45{8gbbejVJl(=~Gca,mM*Ec0?)kIp1zCkk߲݃ª*%DN.l8ism5TuRje*ffs:|6z۞ѣ#Es*uSc]̏l81Jf}={6ٳ~voߵ9}kMl 윏Oo}ߴVǾKV)\YSbalّNdMX?ͷ߰~h;ӚӘ4U7Cuܱia1-f^ FNRs L=HA VY0ݴ9_K3#XR!`>`9!1sӵYFWb|J(B-b݂K#Rf# LzPtgxeh#Y'?#Zȯ*z}d S2ħ}ReJ}$~:*VH){ՑwӖ-m{ )+p8 |iv ICnDQ̼_sDpaB%n7S)#La&D~;;g3S>!pbX8d92<e^$ԧ]jb931BGvA%IPO1VӴ`@O&b%bQ!bTzd1$Zzo BYeCR7= ''K Wvڛo};gI`0bd>k7;;ϭrn9طon-]<m<O?~`_cEZݚ-qU[&[̬SL!U'RBa&;J"u/Ъ@XLUI̊sf)⢰vWY!fնI:4@@h"LԐ0(B1:iٺkܩHNF] s3-/!qdab~Wqh^TG{/jwa= 3-ipAf-Ibs3PA[ZԚ$D"Qh涚Ĥ`TrC ~s&y9JD56S;MDFM%-$uAdȷ LG[XW"pɉBu[mhB`똈Tz4ZgIf3ۨ?~b,*XWl4o =Vkx$+L՜h%e+KS){Ƕ[kV%ZIزivܞGO,ٵ[c6LwŎ=}I8߶C+?1Z)= 5a=KLL\ -3)#Q+pk~l0b}Hioۙ ~{\~h#+Ϥf#R [kB.VA~bYvyA6}s0;A:Ͽ/\EEG< J9 i CXz(wCK1ݜKcQϜ+4!="`L尫KZM3i3{Qù8kI7f⃩&tK :W @+``y:!;2 )sXȽ p'g9ePzs(U=30vG} lf3M DXNWJOfS[ed- |O8X;1T*ͺ56ggvtܶwƁpf@}Q Bv۰tff3᧲=².m0_/+؜Ւ0?xq0aWSţ&Z@yG!s9(3Âh [J [eoG1I Y.3ZƧoB?c=Qge,;1{σ9˅D?Aq1u+.QMU/ ! % }צm[:&i6dI@TgFfI9Rio0~K<9>FEDqB3,.IXn 1 l2lnw_6Ǐ>m[Dh &2$oXSNe+9vzznkZV+SŒƆyZ-ŻO:fBi* iQZͪu:٣OX֗sBm  1g$͋EIЉP#l i UsRL1b4gE.%1s|FpHM( ہblM̀@Ƨ DS~|s3r5O4.=H~$̗. (v6  әrB1n84嘸#1_W5B-rSL&6 &-8O%RJl>wZ4CbRWKO.-A 鴭2q1p2Q?4;M#<0PXVw:;1]R8^ڷΎ! >L>z1Xˁ|\s/-( anPmm1ό?p!$8Ec-hkG|iCCr_Ww7X9I|n/tpanK#QpVY#`ױt'}׈p4c_Y<wӮo C yokYv}<z$iHIDي%lX 0 I ɿ O !bD6Ö%R Qv,eQnPߝ9]{wM2Sxu=g=kgZef",`=^.2p`ryrsg܃P3:qn} ԛB=]$.zBp|knzr H|?w8<7g $E}qضB<`Aht!>:ԯ?G_-^k\Lt:o::УS_Bbvsz_UZKi>ܽHzĖƉ(nxG0DngԟتY͝恠īb:qmVT[yp sT]ę#C$Ȝن/&q0?XxPr}$$k3jGK4X!mIٸg0FN;) IDAT͈;,?[mgߔ >QtA>d_Fطc,6 5u{C۴$"74V~k3 ;>DXDڬsE |xfELx#1g$k IZ~K7^/~8RRߺwߌ]m9sZڪ765:/Uo5-V:Pc3=:kkYLy]j1;ֽ^z%q^}u Xճrh$"庬6'xGE%x(\k5J\mz螾״7txr[/hE|{G@Bz'f'3F:bKNYZ5?7;ï{,ĞU LZO|-ˠoodrrʡ )**5Zq^ ,ڦ0!IcȌrIx<Ӓy˰IY5ΦK~4,f@To+3uPs=~?q3V.ۈy7&<>Qf+f_󒌨ZmbU2k3&/G5+ZzPVWfCz]vh]jFT;%ڝPoRuVL5?VyC{;~OH;æܟYOvn &:;L +0lTL}Ut^h}V m]PUc;?O;tѡ]֤\9A՝_| `^L'S`%!V̰ +}6GƾeYG*(^OMހPg rLS7M"3.OӘ/gE 8doy~?,|~x%o0;R" c+^!5|l7_ ۹2[]>?&!cy@W FN31 .W]6a9] !NBQvŒb# R$uO9a3CiD9!P( #*ӣGxlw<aXL̟_'g®;f'JAYCظ{ ]R(?˄&{ XIe g;g6”_@/i9k~BZDf^ÅouŻ:,tZ;:|΄dzQS]ˣu=P!M0`^MG`E|i< 5>=ռM릖T4\ ,'R •e CˆjqT d:}Puղ#bnop> sTd@e$8g0S^'>:{!g<>udϧ5C/wGGbq8<jg>⠸TN]'fBŒs !Whk  ÎC/:LK02{f9i, \':kƁvyl H:ӯ1 6^pHն\T6oOF_5ͦg 7/ٯx:ֶ.fVLƋJ'h:=M6-#'S?gxq%$NDXU++5ËNz?'GS_#< 0O\q'Fv1DG 6$rp9;C9Ӽf"Zc͜W)1P^?!{gPr8)4w7ߋѓqjn08(;_Gb\F(s2LGOa rY43ft9eőϘ?=vF Qԝ1CwLlzṺ%¡Bg6|?7 u-P]Hn+2-HNz.A Rblh+!m.DUnJ$Y*| r$҅Vs \asJ.v 0fmgW GŲT2]3A [m L汁8p?l}2iwSbo k؟)`:\@?I'//K=xm_s/Xrwr֪n[vҭq箶:4_5vCuCղP/<:ZMbuUҒjA U'''6 6CMbCzԌIU"emܸiI|d{b5tk&uOV*<vgATa"MI=MKD9QyA03s&8yFQih!dp3RFvJXV뚊dDjvrL2*`l7`#;d1K~~Ϗ\N(YdgefVL ~>QW:)"y˷F֌DCUz6T#'K$AOj:ߡ2'!=;MuajFMNvDA{PwXrq7\kZL4ink4[t:>ITSYBTUDMu"n.T\ FZV ][+ݽ{WMJSm*Vd]iq$B6}%IhZ'̺T.W:-NFXc>;H"vMvUQ!Ck$a1kxR$gzn~?7 5WKͧ F #9NMd)\,LfJlYe+m\J9 `Hy)Y l~mw;f 1dU(XHF01Ø 3c1~IJ(JO_0 a&%kUtRz޻_Mٟ)}>Ooj4r-NN'GzH7_zI 괻"v41{)+%5K-o w HY;gL\9ic5*u;'Zt;voj1r1Ufu-Ki2'%HFv]qBx~t{6H0ٙn(mpKR "w^\/sf: GWWkHM/u&T=>^`sIa_0a)T2fcǘDEx$<C ¡s%p L u7v_O|RH޹w_7vwus{OU`Gݭ&GtPïa6ת,UVϮۧMݾq]𕯩Z%ǤҢ8t60[0n!qT'jUL/~3/fBv_N4)IRάm[v{Ӊ.V9C`z>If~q4fykIN"{n39c Y,x:Ȅg `#k.``6wb¼wCCcYqr.ZCIf[Ht0Y6-7F0yqWƸ3/y6$Owվp8̒_LW)|znbb1ͻ_=K#D4^v1h20UpRn^X{p8,̂8r #Vp @ `rsP8 Uzh )Lս)f+Ԁ R.,8wy2IuB̴͆9C5W8 Hw&BJjܺ rV4lc;\fA)ㅘiaCL*.K͒ |A5l,Qom2F{w;1QDPeY꽷/tO|o]_WarOD/~@/x[{޿>s?}MZ-MZ-OYturz[ w_oxk[a7( iЛؙ'gi֘˒[G70bDk@rx1p5ÜXp_?>/3&*pDx`65~ŨhW ZD4[u;pM ]Z2!1%J42kKC@Sc. 7mdbS}쏫-_(7WȜyc|.H`􁝎yIKѝ-H5ۈÑ?F5>P296߄san vҁҔ h:?N:insaA"FS > $ATKcqCj$Oy#E]91udT$72 |nbNi#褶ŻzI % LPMgr&rTKy IU:Dt%H+*nk =(K]H[Q\( 0KBzw4wo~E+->y7o^7KNLoN<OЏv'??Q[V5ۄcNARno`geK4vCHǷ:5UfE|S& v5+ :g1g֕s/֙?.F7!lIG>1X,o+Š .כ܇T^)K=˿'e+gslV |8ز9y쯫~rSm{D: k:_LլcfԶȶVAHaHcͼ$rDqXc/"R#Lʌ?pK L}f-{x@\7p|F<2;o}wXi\͚/q&I? ?y="IUM\竱.2Rˉzz[_Wsf1luYS|h4sp7bӳ3]P0hg_gpCրS|HaxHt"Nk| N$ff-Πh)D&mv҈ x0DY!M;#6Zڎ*I3\7S\v0%0xq{'qqԶs L7%e竱YI4:#ކ糯/|x^{f7~F8q&4px/po2Oja.9vR'0ѴǨ򣀧(KlsUW>eq ̿ՖP_sqy0d3HdF&>+o(^,fXn x\ ,P70DrQISRN&2 &{c1݀t]9KNZmufu,+ݼ=W1_h1wV^C=zpIVjgw_?7l }Go׵dItVoᠯr1/blÞ]8U5}Mv.f'pd0%rM ]BUIt^VTp09m˕tbŧ3-F|X/l?ps yP;"Xe~33WzI)6Bj'ei..4$^x)B _n|~ww>׿;QiNC#~渮Us1̝eBJhS`f>̵X^ĆH7/lqemBDxȡތ0%BAfr/kp;$XZ.}x%}&syeݼ}K{/}Io߶OB0~Di7vzi'n[:~] =5&KRyƃV] ^;Ջ/_Zq5ɓM^mVɩ'qUi|I5vnlhʵq3] -}סdW &feҼ#A<τAF"~|v30a<~8ʰwc\vVqɃ] P/*=<;r}h-P^hk%lmkQ8/1]EVD6 d,| ^#B8pؾa3IHZ8.m A"v, 7B!V:kd|%fӒg@B>H3p\JRtC" x?A;#3ѓkIJZNN" aS-g){|Dnm҆Q7o{$MlۆƮ׺v/f>^Yb q3 a9|?/\`ܰVy$N#D8BFD⣳֤FE eneD͐<ԇKpEJ|oy1gG߹9{#e (=̅lED sش?ϼ?s|-]LV[U6j(˜+i,3l4SnC6lں*:s#gǼƭԬ5t|~fGŲ}.Ougkhfoo`KDE׾-Gԧ?;z0&:>zjЋ/ZuxDH{zցjRR'S'asc;-ۚfK5W53Zى+5lNx2SkkcY>\q&952%g}YC@O*(gюHJ E4zd yYK}0+f0 ~~&`6na}`s$x^ ܐv{0fKh/;Z785ZHTȦv35M:=1~ktD{OrGl]me>}dG ;mhCu6 a0=ynť ?`av|/mT!8UEНn!޶h~~-TN'cI Q[:* mYCRݵ*jUY` 7r,i1L#NS.QB !}">o ZYh2[5h"4"mIOCaUn˒C.>3σ,V\cQ25QF|HX0s 0KSU[[݊9jJj`ԃ'0;ys?0ǻ~>cT l7~G-T&9esy^s(z2cƝ, HR8֤R RaJ0A[kr ;4F[3u9`a  BȾkkKN($XZi|6b>x2/XLLu)Z 3pWLJGfpNtֶj*j -X'JNYT6[j} qjtE‹I|Ëu[(f旓R>[oc켇"v+@:{6%#!z'<`TgN!e#&`| a4 (+3ӑr0K\kRa Ыm-a*CN);ca??&'$hKܞ9%v*3? 8^D1"̰tJ3uaɺ ӝj;z2s_I0N[6 \cbڐL!+p2A>f?)Rypw̧%ᖼ>}t|@/?1#s kRe4$% A,3_anFy19a]ap(3W]MLsfN-QrifӍUd91oJ}Zn1F5?H<PTh$B@[]#ttZΌ<FϿ) v Q{fI? }L]0KYn'vߋiîh+5,~pMu|xlncKdeu2c&u-nX. M :K:T,=&MlCcayaK`3r'|%N9K߄!~A \-u8Pv.?.rOc<Y'2m66X:}(aâН!j0ԝVүZ2CG `iQ" @BTt9 R@>NZ@6iiK2J8bn+vsDFM 3-"f&0Ł"ۙ̇!le2JZnpj+[dD=3kI*mǴ#ov$*DG BB]5k-:n[9OlNdH8pqQcoSolItի_:_ZުtNl<~pb #]mVB%' H;u#|Ϲ%xDM!Lt4)J|Hk a kK&5b>Y :x.80! y " T#F^9ԁ8nHьZSC"$`v͒Rsו,jHPc?L2{LDp>܏MNX6?s,|?+"uG &Am㟊?N&ϦV3BB(T-;4'n2*bJ#OݞᓬmUx'K;D8.D__k}U/_/O'{pk[G^N:=׊.F ͖5͡31Z5C^}ܻǏB'E۪jMv Woկo^}^-: &;Or٫\>b&훷fWqo3EY|P8!bS[i|B-26K h- 5`rZd3>z.?53)pbM@ҬB@;6O"@ "RX Dc@u0\xY[H F#Ks azRHLpW_pvlVdgD.$viGߣ6j8lj$ٖ꫖:[55p\TޤC0o~CLqsc#\rjF}Rbeeʚ4[HrY6`>/!`XX[5@J0DlH9J$l1)sU㿜#B}vpJvU 1wk"RZ/?NEȀ%sQ&P>,?2&A&dTƫNp ]7dD \ЇG*ɟnџhcssܹf8f2{_5xe`Rz:i6;usZda*dB d'#L u?xC?X, i$'D? IWsίyԑ^uZ,v$D.N3f- Ci[nK㋑V;[,2+f:z!#?7u`۷닿u|?:{Qv=;w_Ս/hklh>hj铱.fb^88Ko|D{;-f3y5HzHo^sT 4í.n-+1}K;T8-*4Lbݳq)7  ZщS w &?!.aTZ!tA;j:a/Sץ8{WRX4gvkBٹٙ?5ȤYOU)q$=RUQ@ٵ9pz.tg3mv{ϔg+c^>`P?էop3yL`s դ}q d gByrxs44_ eyRwĬOf!K#0qF*OBY!sGXr %;>IJ&|& VCn;D%IėRHFDlg3}wb<ֵHUV F)08n#|bd55L-R7 œ<8l7A^[8e%9s{X<4QTZL6޸}!d JK<}, I&k cX7}t@HХ(<2$i$h#āьH5=lÆhXۃy.@V;}uڿl(!0ǏE9[gVgpzzMO^ͮ<>QRk{Ozp2Ѥ皬kj yK.5KM;[я7^TÇ-'ɝ;w GP+4~6HC?ՇF$U*8j zP"\U$)}QG0)Fa&t}m86֤ O84Ӂަ͓)q6CRi {>u2GvjthZp ܳmSȺ{<3؟h!1)9 n1X~ rxExm>}?gy7ƽ4E~9 )zr;y^6>CyޫV.&\DKS.K' erH IQ5x`%rp^y vyPqHIyI P& j\#bBUj"\Ͱ\\M%I'mxz i1jg,$NY`*]&ixM3U}"|`=-qT貭he!eW}zxFE+y.2|g6$ؘPDgKݠܬ)OýJqpQ` 1%65G5 c#]9Vo6mZtD¡Mp S=/Nm?F0 B>c"V+jjv=3ݸ}^> _ ;}ժȐx5mz%/t|zVShthLi/k/oԭ~m {hZ/j]3ׇ7)fY?a?*0!dcA\m]Z,4U3mSb v͖Zݞqvo-q;n.mN8 #/igWZu<|^'ǃk-Nz4X!QPIO9&=C4h²})$[f9jxW:pB3hsK&3u``}C%%+|qڒ^;cR|2t0kOM:A74ra.[sjh6/#b jJF8#K-LP yXHӹ{펙L$H7` &ξU60;BcbD'(7/"РSCƣtc0hG2l:(>)<_ٔǪ$iH@FطR"o\Q`@pޠFZaCiЋpcHY%H8V1Nƀ2aP5cS*qz0 t7ƈc+jDe]b>:[ :) Ež֏edr>1][$?\cEجd|7YH!:-mU%I"#jjYm9,Us 3Vstb IDATeQD^d> `0`bup$J 8Qš/':?>[}fGwr_Yel$L UNDi#`>Z/ B) x%|g3"|S8E[L#5|9WܻHg\P_m {3~J rW T|6ʬ8Z[9O"G}i&NS@,`$ nOe1uf۶p@Tu{)u=0%Eb #Oߌ7 5>}xS>: dM [fa0l9j{ 9D" o|$M)ib<[5/3 z3# i#0#HxNgSG !̽D *Hn!!^YBT* 'ʟFhx=1^ȩI$ =hlˉQ)|#Mw$$iχaÊ~~7 l7} WTSyÄry)CL,% n#НNg:9? [fC[f(9Α~cc2m?-&~59d"yrl7a4nI u䴬tі;C_np]/x .Fk~s/|A?b6r>h1[ד3vwMW+3?jwox֬bvC#B‰jrm]=NtgƶiG.>Zj19QMj nQj6iJAW/|t48?ɡ.Z-^5CQZLO5TSDZ4jR <@6[/a1TG%9oz'0JWgơht0=;-mhTi?&膃uLr+Cڝ}a~ Y ՞{ɛ̘#}|3’gSx'¤j~mG(|K፣t,2:0/}[_I8HHh 㚗3&}fTL>YIЎUR I:xW"@ `s.G^d2䦚'f#æ9IxY!`:G*lˆDC&F;.7["I )F]k"NuvqT$Ğ >")F" i>lfaV$H`C?MU#KbZM*h D ddaJlʑF5vî;0HJ싹ɬđ`>:ޝy3uRzj(T`I0Ԇ!愾(UQSmP( rMA:3_4HO +3Nd;0kfIRFEP&Ě;daza(mꊨgjs fT G=}#щo~2blϵY.=~{&:1eoX]I }Y`4]OK/0@vx|mooybE,vF g:ؒH=$gL{P}>] U|Av"8Ѳ UQ|>kZJ޷A_5̟ioo~]u~|޻XU}5b|J+/i`a93f\6Bw⦻w_%o;UD A L*\UUhi\TJ8X3v8NC? q/+Zɷ׫Rӓ#i>lD;3\NZo(7MԁƼZZN_d2lt}h _-naBWXH/N/9@AG"c&.czx뭶]/uт(tsxiNai,vڭV؁TobQb5KmVpaT ^{Y}ӗ>WsCCTpC暟X$]"gpxuû %sMqU ܀߆5f.uo^zn$H[/.FZ$%Xi<FU]s` [5  ;aC2 #BW`0݀dӂ421u j6YJR;&DiM- MTF0 8sj5̸-Vfa:0g:ș)b#;6p$m1"&0cIJC^M%󹽇5la㒺ǫ>D!7G3e_[%VH_Q^u'`泹Jt^1|xm=2gD7q&I7/fءur6j6:qS-7k mCȰi_K $H|[d` {Zl+.Ʊ$}Lm HT7<̟!9"dI ԓۥ\ I@'UnK?#,L|/z4QC/}s'sk=>~y㴯7= } "$|0OX}uk&v:*Otq~nse`a>y=X3H2 AfĒ;u yM&WP|)ל~'m;`sz[Ĝ\758GX``q$|M:g,ժڪck.qƫٟb&b󳉣ؒ5M%J¤|4x73͡{1h(5DH.4]h?n+u}NSb-y}k?d4"uvl,-TK¯6ZZ& .0IXF/TowHnZUUbx t4v0NQ b!MV00Euf枽HeJL "ҒVPq0me|$ul="9qb.M_R7_<3If+ގfGJL4hR]|m9exq1-e:軷P7ZI|?*Vˈ<đ*jgөFi3i<&-4Zo4#e b"Crm$Qqcbl`31!z LaI_i;-Nt1ǹ SJ` Qr0j|f ݔ%lZ)g?c[f\#T!D Ip{_u]ԪN RLHBĩm`+\u hayƱNxQ|eXi>/uP-$|viDzƓGЍ;7Dj5jR]hXOu2`q@gY$L/Z߀+S6hV.f3XCܤ$T?aoMۛրYȄk͘'6x6gL#=o$:3fڡo.%4)H HL y R2xaqZ#\Ŭξrq$wI @5G 1}A82Ugv:Gx&koykHVײ Lj)˻Duj7EFTDC†'%@vxl8e9Cϩ1nW#Pf:<<׼A FK;;;=9DrG0SBvvFxX>p D|DS'Z85Hlw2bB~,@t]rH&PA1oԪi=]~o}m/v-~>|Ŭ>f~Gw_~YǧGc_?W~Nw_{E SNpFPDV#|w'GO=ig~G{'6ZT"49CPM٩@&2hq1l1Ԑ>e6iך*ΧZN578f(*dw-*MO/lO*|>cw{Hת5M u: '¼阌(Mu=-ƅ?XÝZkݻ 5] m 1M<@: >Ӎaʄ 2Bx?h3Bscϡ%_û}H; kގs?ar7mpyWXks5אd_>үĈg^ !VݎZ:n&Mt< 4uG@`3 :~ڂ,{rC4(PW\0HpA2? XyDԨNSDpvwv,L*Zh`|Y& 7a6C0N#]ЗdbQp?ϸm3碊PJŊ;7t+t)sufdykԅnw(HR `щFK=N=jt֮~4-tZ!i\àPaˏ4ymHqFWfW8139J-Q!`F^LDARe,3TҜ ϳ&& 0u:jv4Lm#qm5\ Y6lZc<#\E2bcb?ƞ2T:}6Ð5M :O +~+9Wx]7o^>kr~o|z0>u֥?Oco['!^]IP8T- &S VwѺ)MfDjĬB2ͺHI.DBg)r\]h{kG=uzvhPNÎ0{mRO޽oƽAt$ 5:cT*KjKc!d8I擅V%@ xylҿ ǾZaIkA?p@#u,1}y'ߙːpgIW/T ۲ChnP?fEVZ+sf7nJo~+3Sύ7|G>K'%oRe\Τ?x⛙t!?=y}gsPlNn +i5&"q,b2be0I@=Z"\ e@N,lA=2hMgcǔ5IXw`8)k[\#ᐐ {MpA`a1@*ON02{\3cن ]Mfxrn'.Ȉ$d*ԁ̦smwܱ}OկA4^1#?ؿnIg׍݁ KtdmZ1g[umj"cP76lk]X=э;s!YKMZ??֎?=OBrԽHUG90bR'6@NҒB!E f4a0ϱ#!MJ.ڥ8Zfcol>|'ٞ>/1C|s<:; e;uOovp=ɸbZqnKX{OGGzwaڬ5Ľ3:I[ścӵ(/N+EgIYqnDvwc8KqIB%Ɓk=ݾ}Kg?pW3 FXG*hTjK?^S^rl!yMUJYdc5UP0K5Pˍ&45ڇVw vAi+@b9G/5.k>^πQbڲJ;#XP]v 3$iA.jab} 9WYۏ瓡:&hX#&:ya.3Cs~u༠G9:d~rM^0:6.bνrj2Ot*V P7һtƯۂL=Ipqr#q^5:e&HE?2XFK\r~zn IDAT"ۉr8?γ1 Bvq!Ȃin/ [gCȃLMwz}L3Vcqq-OM0RM`>+U0d΍g#'A&RK9Z!,٥V1V'4FXL,&Ł{XqB5HCs( KS&`.^i6ƳjӎsV Lir[Y'm Uu&bL^>ɦM֌nhhtABLGݮ0HBfE#2`I+XZE$<>qw`unT4]5)*jH/=xNu1/ޚk΁D ?ƘsǮrl)gBM_8nMs*NHЯRxtvi^fTuσ?i."4Eb33M4=)v;ypIe65e1J=G9bY\G1d=EjXwt;u)uF S+2w"+jeepH'8c߲Aݷ0 _`jGzV|яw!Pw@0ʎq `'Eo[o_^*# .iþ:jTWmDQ&a:?T+eM.ǚ Vkݍr]kW "1--[:$}jAR0+:\,UٖeNZN7jVGg/mGc^;_Fie6&'^NˁM]tU0bHRȌh $,Q,2#m]#`Tq=cE'e>"6 <+4޾ Jl$}%*6YsɺQB!ba9[UKo7OSk\2a oUD/5=~=$%?y.\1'\3#P#"0`tX'sDcL$`9h3Jٿ1/(^֛CA a0r+);UpBb&!TE{}Akvp4&8-Y+D#pɃ(wA!xqqgiF? s^] :BIHƅ@>~8;_-Ն) -(t2Sş>>{~nDO[t6RË@sIχ"`Rok]i4L'C a?ytjHHog+_K}vJ'wlihGRiMENOoݺ.N y~9t=#Pů^+w0U,YG/sXARZay\Hڕnk|{.La|`9yX_ OLXv&|!"- QZBi:ڥܦ'׿K~6ݷ`1VuX?~CO㋞L ggsw˳3osR`Ϧ;^̓n+UC8L$u+CY]S"{ A4H;t`_fCGVyj52%Eu R]k5ݗv GiN. YV/gZWlZ \ܾ%m>ʭ+Ɨ_:VZxM'_"N!Yntw5\SB?2m(1`cElGbfG}K}Fߎ:IaVՋ/@7/`wEX-v2-5dI"AN_#W\Lj5G"4XM=\%Yy$`)V6T 528O~>y1r _hN2'3XQ- )^7 fwk{ەXrgIx3O,OhqKXl4=B'n[cfg0:Y`΂'s1$'ǶI1NÖQ&RD;vFO0X.pF}x'N~x't@3#P[MA<eFG 5#2$5-Zv8BAcn0r)!$܆k, tCllX7L?ѐLs-VVs+ BRzk{ p.ǜ'9ߵ#4O֗C͗kos𥫕<_ݎz{=[fB")Um4l9vzw%׬m7 f3 FS_x} ([} h8K,`]"֫fs ;bŋTӷ]W#fn./*OX׋e0x.lG+eY^g7sg\!N\%UA]& R<u>:a9Oc0sS;m_߯ګ0mʬ=aXDXD͡id}0[>0Ө~~? G!>i[NGu?/=_ї 5a\$O03r-@qc}] ! مqrp:p//Uٍz9&̅`ք$Aq@tfW*21#xL9b*ҰEXSO, xij`٬Li+uM/ 2N- sh䜃[8K6e55|qsnJTSqz۽'Z`xlX[_~gЯ_[}/\!y @craE?`GcuMV L.zk4ۺZRQ7n^Lm!$5VW՘ $uHyA;~KST;}Uuj rXenlwuQn|A v+ }>԰Vq~ƛ BpN&(c!8w{cP/>`>a( H0S08s:VC]l9B8*'zi7_oovk 󂵜]R=0> 4tdoVǙ=./\ye)"+ lbbh6<X1(GSx*:^ۍZoHoRZR}+*,tϰWXUzE Sb;~lc^NiTTwp j].[^c87~: bWܔ&36敦zG˯ivX@D:MۆP1TC҂NgxH1vMmg6 `GhzS ڝ~v_dUh<ʍ'g-BpFym TrM=bxAuhj(3Ouq1A;QѦưynOJC͵ሢۂ`y> ~טi♱Lg(Ũ*+osFzMg%OX7ᝳ}R9IQ~s6^$zxmU0!vĩ@<;OoY}LJ!>z3]d;?,G\-ټnl|t>uБ+c>3ߞMV+`onz&3Q`,ZJE?'p< A: 3pTU ZVT "VS![eȱvVmʀ ;$IviwO>򼸭HBS\r07C3 ȱ8Ǚ5T3㬂0`ɨB[JӮq_`7I$]Nirm6(嵊Č:yp_[:< zb_, `XEHs|dkp|]fVW7 EY~O'?JcóorFkjP%Tv!r^G' Vo2'p Rrc_/\ L L{-"Љ<#JlrEFzC%9(bfVuJmU]&:EXXvM<7L `>xYOu28(Y}C9٠]Qk=KŅޕ \x/G;:nTwUWZ4db^ֺ.u >ԣ4 T:!]`}S׺بVשxwjz5m-:~oϿzDF?e@-X(VjZ.+]훆\ '"uYWvCsaUtVT@( v.ƒXܮHkKr75YZi 3xGl-6ⲫB"Rw$ a٥_lj$H[Z1< )$μhczP?Sh-1`|=:lJs-P؏3~;X1chgh0.V( H^p)%-YIF> V,vmȖ?I'0,*`sJ) )z1%Z QI/ =ݒa&=W-2 s@@ӉJ땪iU9e- ƛi@/Q9 QkF螩( U413!.`BcBIFwb \bJ8H]Nu#Wysm*빦Պ:s`XڜAB / d-% am=ff'}hsu'Ʌ6IJBQ+VM$\uV\Jaup Sy2;3k`(YBF3#@.OVvkφϵ#RR8R+%J̀I85K[5|!Dst@+"92*gWr:*2_?ֱZCG5gG;جwP 7]ow~z"yw' 'kQ,Ԭ4~r^1 0v;ΤH' 9 /=x=vI턛ɟI}LJ[dwhg#}RuӨi\{=ݸy[^M1].nb< ~QQyw!7g@ڋ1N(5xIZ+qjs5՝,w~f*'Vr"u%8k 6jhi<5h#{R9+!9rY1{~uV9zܢO|Gm#Ar!2!WQ>n{?07^0! n`qFz;]P>pN7O#E%Am 0&\0,AH`E7,YfD_ n; $MrIMB0aD@l&=>!@0X|Zr3?Js!W-x6?*7A$%xS`aѦM,92VN_qW`#fΗ3!bHS֝;ٟyk~L= m:Td1֔ fMnܾw(7.Qk4x$\ 'R}8C #x 6CQ\+Ŭgp#&޳]\6|CZne:oΉ9<4/lsRGAt/&.x@Ci0ҫ!.g]1fib♯5 i,|<ƒ@S9y []x|=*iJencr-ƁW_xv 5.=ր (7#XX֡ivz# v `"`~ Af8C@ڂ5궺@n՘NiANAPX8g`ͱ"r !}Qlюh]Tyq_,%JM'j7[}V.",r,3bYPH\ÂK^X $'둍! q$?/Zk5߱Xy&Tcy>JV=Gȣɩ78PlggG>+QT.!}xz7o36ZUZjwTmt *EnxL\H5a[z6ى^e}K/LfE;vMȁ  +`'c!ZnWkˋs]YLɩ>VQ:{ri6)^3+:9:=ݸ*Ys^p/s0`L8G% -*B51|3xJ6R ,Ur] !Mpu[%<ˏb^yyx0ﳵs/fdaހvTHQFj\rh!X;1=UJLUXAnTe!M^HZFAF"rnQ֡[^ȍd`P+[aV|~;3sxE%޼om ]h$:-i5sfI3*$s6%u[}M'qpCw,Mi+@4 {uAiGPugFja *3hya<ӡ'?LHBx?- YO p[hY W٤"g~X= z_}҆튶]+<:7c;`ΰ77OoVmD[O=ս8]ۼF[G,~>35^8 VrXUZ klsVF:\VKo~k @[u2EJJKFZ":g 9&h`"Q2f┲'d V# !ϓt^B&>0lew1;facɥO܏垹_m55粘yr[20ƭS!nL^1ܼ8;=x4ѱNof.R]^i>ծ*QPuj|V5,P<˗ζ@>aV>ψJeXFfK!ky0" P*PNQt0 &FAO&NG>1Vd%NZqdq3neU "ȌbLf+%DoЕ<+y9_n"A!yc/)Dp`YHVถq}SpF8p?wp0)]FrY݌8˸AcBpusO` <8_q#*QAOP Z}ͩBG1R27wmo\žCj̈Tep.?^O9Dϵ3<́(.YkK1P iGTsIGc: $Dz;λ:qOw?#}3ҏ.Bp23͊&b`RI\k95)..4MѣEu^2`N6WhUZƁ0o:0;_ЯE!]hBN[Ŧw+?7_)4_۩ީpX{w.m&6t]$O&=66FRFtK,7_FCLN Ӛ9I>+̡BsijLZ.g%-¿?t=bD)bV(J`xn ꧏ]gZ㻚E:hRѳm:$͢c}yC!]qv! tVq%-<{ό@ 䛎d1}L*u\X0!WmY& xik\zLr mPE,XQt tth!hW&!gkޕ3Z \` `XB@;¾eV m 1CXE\K򝅜!b)(69|3A֎U0H Bz#Pz\ǚDv lѝ4ļw-H#cY GC WBh2egcW:/ BFѕ5V%@uI Ɓ }O:[x:Ӽ[Jj\!dbk3%d,l7FcC}#Ӽ903@vG}4SA}Ե?#= [/6-+E%X(7EU݉4{Τ Mq?0:à`i s5yk77<mByC ^#}VJgJG\L; D;HXjըWT|J#-ng[}^FCsUz9SoZ2 [*ӳNs HNCRU 940כּ;~_yY3le}O_cZ-*l&}*(XƁk)̌dxigN5XXSYF 6Gbm7̼;2@s$2|'RBVhyaϴ!ai-Ӭ=>y1W@|}pPzG:ggxZv5㮴q<oG?sz^c6g|޶}<*u\0gG[1Og?GE'Mu+!@לz%,qpfkbv1_)[QP( n&,&]k֙d&D%2Ya ysVĄPF9 EQҒ+XΌ}x VxGI[\C?x E/ȋO6w1rc-["FNHekH(B_;Ñ`9 #`*ZsvU13Bj&˹F[՚3`<BVD8~Eurl蒿x03- <PƑqe8Z0",.C6 "X;xrɐVsBdrX~NGԚGaƜ`23-CMϕZf@@PJ2tmf,m/rG5s!zH 8X:Wv d !Tm>/>]>vo~|ʜ_"v9])UC@cJux%T`l-={>3&#fiMDOlRKX6[^LT*&~AS & ra0in8iٚ P e t,@M ggIpB){2+0!Xu oUTRqbef}cSڿs JC{ẊmI0LL;M3!hU+mu#~v04pWb:n AZ< #XPJH_Zh\9@/m"g@/O~i뎏w&㑪fPхN+`@t$ L25KU#9|nr/ךzB;q@O:(P&V&6lj3d%v^v]fR1덫B&RmZxH̬E0 |s)68d5Ogq8%)Vt=xL9ZߖT1ws>Bqzo~8/{Ibmr])?u_Xl?h9뭺f>%"Ć_7twJ"$m??#H)1YO[V;X^zKUV8=f)pK4~JYSCc N/0PKYm.˂b+ {&bP=g&9B5pnV!(=#Bg!GN%}-^ۮrA9i)\xB?+܂ꥆn>;=JRF*.'f]|S %ZVWEIuΆ-TQ $mbj^ ujm{N2%V₲~vp9>jH2>3ȌC%^fˉ.fI7J(eR/>wݾ4՛RuAz fg뉽W-><`Zri<ۂ"/ 6!EQ 5Z&:s#F8//=cXbJA>m?5C s>%S#n1bܨW_Sμa!)3NLA#G/&uآ߷)O<8-q3S`W8O0h"afa|H7!|Vm+>Kh+QSoo3In# :FwΫ) t:sw E?xABŒHeF14فhkbf`=ЛKyvF,unzǺwF4;y\wڣ+\/rF#U&!+.LiE`#rrꦢ^w4<@_ᆳwhYjzUmtuv9Q}^RoXg:N'`N_F|`a=6Sh]id{Ay,Ğ_ gFPddbI`G RցVߪ^j6Pi9SrU@MSwhJC9qp0µb`jYĆ'3Un|Ф,8,m]ܴû5ZɁRa[UKjluu WF=[mE#HVq/R1K.xݵ(ckE(ybjo}rM̟`\̟|yw3ߒ`])ӫʓLsvi}c;d%Ad>_^tN.z{Gb~t #`flYGp`b/TeluC*/ f Ტdbufuhz7-5%4{!f䟥$trlp /W&BAƵ]KR?)J=YG g^6:,kry.ό$5_Q5tZNc'1~bT۳I)KIZӁFcad ѻ}ChFM5,Q3[30dmT\jZO gBi &["s4_\֢:y5eyg ܡ^BxxKE,x 'A ?s:Jv\ y~%,[ͼl)$,a-Nn5&\B.[bKu, dhVDx&Ѓu@Wd Imdl^b\8_ԬwTu䑕CO>]u:.4t9)t6pLOu ݹwW,PLv'ipq  <;{=k]%"kP{g#$;t@3-II_e ˪궺ztJ|\/G>XreBR2z#m:[P‹r-$m$zo5 ?%?/>a v)c6jOӵjxӄPPLpm4)Cτ dXEŸvgh|fz`8z8& -F3- rUG19=3#16 T@FIt k,8沌 -&_Ӄk#s}%:Vҷ}`Fvy>n8󾘻FwϵygL!U@ZXO)$32ڎ0/{o#wbYYqt)v>rl2 l!JU @PH @D Gg FHZ d>湯aohޟ)޽HN3 gvlk|4nDI{1~7x"d%'/5 _`s>h4݂<,7łRʠDL2BZj::8FӳKU}m# f-G鉆'կJ/twP纸꒠`jsR_}M}q՛K1ތa7o9_aL!CFsδnoGfʼn^W^ꭓ3 +Pҽ;tK=:P}֣Hcov?'G<1}SŵDUuHz5bzA#.QDT-C\V˙ӑH-[n|O٤+G~jcFe&v+ MW@۬֐ JfaOwNQiJzE~JGfrBȂΦיO\N(XQ$ EX8$ma;\ 󀟄+ 2 Op)Ɉ½g@Q.hAM={K⋺jZ{}PPB.8=TSd Xy<  [ ͦXP8wZ0ӣvv& MScvqo@vfd|)tbc0(Rn6uh0+Jmtjw>K.plVjv4\ d9^~U-9{媆K=xxÃ쪿OeؙH#fKQ*i4O񹺭s{Z:Ê\Nֱvw{G]vM˵/ r*zF_-w;"'K@#`+sBq)ݦOsqK1۱;&d㵫^k <OGv{z_+yi uas/9K3`#)@@FK1溺;kд O4q\X)e/3F ;SXuDG0c# szn &ڼZCwc_ P1їXOYߞv,,6 c Lc4Z` ˤ2Xf& n.Aœ3V̎ +8̜"TKTi#];m2:ňg .m&e.aa@0"[[3 v9̜Vr6?8K 'ߌcv,X!G^|_m&5nH~q`%\ֵnܺ^ˡOJI1FPg/-%碞OSvp“ˁsfVF+b) C?:5wnt||j.CD1)@皫UxpV*BZdB=r0QzE~ ܟ`fqKBIJ{>v^;x!@ySNs9k]&R:nZCiLf6s33tKK|(XW\3RU][}v>Yqʁ$[6\՘T;V-bHnLw8<h!va $x3^<.:T:nܯn~Y&[4±d$զW<6rxQZQ-MK NTk7wڵVZ:/fuMDh0]kSRUy^l~ZWo+4=4\p&0-F.SRZ{lR}_φkEjz[&<_wZTznONېPJ5(J,}u;Z{Zx3x Ŵ6Hi"͇Z O4\h;^kD"PZj׽G^:w5Q g i]W~yv5osL- 6,|\52I ̦T 8&Fþ $<7Π%/LrœgHУ;~s.pg#6vki/#^Lj&7ӽy3m`L0 Edqsqt}R V-WCp NDCwn:&)H& 1J+j:;RцB+`jLxӍV*)UC)+-Tv|_HZu+eY#熀 ,d.A%4М+AVla~8?,%"8 (kA=ynx9l~#wajN9pH( w=[]{}SVP}{ouC~9=uN%>|՗mTEa+[YغJlNEsޙ;Y&ܼR *2ռXr?s.k5u8WzD`uhu1<{MOP- ipq~F%(_}r}&Vi.]6Q.\DhS-L 5vZz3njz3FEzJQv oݼiX u0|QG98==UԊ 55ExJՅ.gG1NSƓã]Ҭ^'A?׾mO(6jN?ht!aM3Uuuv;>ޭ.2C^|S2}x{((fAcdI i6 PМpt\ɥʫBbR9)0(17j*5jvwe^S~A8#d4jK6s, &^ ;Z-TQdN2Cn/y}x7}V%> F suQ@k,&ktP,9 X8",'RǶgY?Iͧ<t <<ىnqx}fjr)ЄG,r1YL ,,L*\\ϔdA5vv|2ljj. 㡭%hq bف ^Lp;#Öb0]jRg* 9 JVx.:Mӧ4)Ah5qs skT;cǹ`z60} Dľc03T' 7KT!4'G 4a"#?}{ammS[ǪZׁc@!8U*J TY5,ђA<O)0wk=|@h$Qm6?d0>U%\XT?yURB)EP(ejՒ [!-c*ƕ' oJ IDATѩp99a5h@1&+=,6BVaˉjݱ[ȩ>7*(wF B17pk3D0 B<,f9~ڒV9 ,"4N*K|DtXd F?ɽ|a]c^|IQ v5xd3ҔFF;ň#$MSPSaa9!0g`A0j䐓AkE?(6}a] @zіy)#m1aW5Yi%>Y*5I2BdFhNChJ yᝳi}pS 06cbVka?݄ c%GB}h`y`C}s8 1iB9oM~ffz,>oSdBѬUpy(i0Z < |Tgm[/}y]Iɗ`|ny':& MRH 4~q5(RoyTxnnZ*Ssx7lDo|_GFjo/s/Rᘬ-n6KjժڸrEjooGO<ں9fʑMXKp__4[BH0;߳$Pl#Şm?kZcsBb4#Pu^FáMZQ ވE7~'Ugskq}i`h|#P-k<Ñsk60Zbs[$/hi0P0-%S#6@h# &jZ23b,Yl6M4GA4<@/{$tjitl$Ki~L]D`'9)!I[Ɖ!~||6ȋo1m6Lv<؂!iy'ĚO GB͂gfؾvi,68RY[g>F}[P@=]nnnk-U +Ue5ΣQ.4t]Ys0\[uL[-#:Z.*ztwwO//?I]˕޻}[J*4Q KjT2gsw׿?u@txFGk۽<T&@.1Դ 6^;_%z?G\ >/ٵpwޞZJYWjEHǹ,zUƍx}?͜4)8yJE}Z#2}TxI]xAmy\4'l !*2WV&&wty:vXVҺኙC=Roī4jZ|([ĻXɍ>ĭ(qh2(j\ֵxc򗃥 ejqy0> PpwصgccCNہp_ \V=^f DubÚI&d6uhi,@ . '2f&`?,'ɯЗj`ai0ڽܹ ~ޮfI#KGƌ5 S}q x2Gڂfb(ȇ3mr T6;7sw4g8cE)q+qv浛Apcֹ\0zBM?WE&bM,X.]_ ~?:mc>~s!`H[Gu<:`Seer[3=uk5QՎ,ݾu)uvMPYRLk砧1ʓ,ں['ZMp݇;%,^܍vKW4Xkiƶ.ݸ*/f.N r?0բٴ[ f f9K)*@ ޝ;^x%moy~SYiJ2qwʏ(,MMs#ڹZj[F yjttiVUe5KfBHe߃w`@i?2TK[9(qYְq`^֠|+|PV+w St2E|77X$4^~Y['Tn9&3=#P>ܷDNp  RQߌ- J{֪1.\vtQ#(`\||9@,)jVOAllj3MJ҆;ÇX"S,TBBFp"DSj>8 pi?Ȕǚq_+1=01B}!6c=>9xfD#>Cn`BL3; X9sK%Q W"]ْ0!W,D׮R  Ȑ|8R^fzxb wJ?@Nw=ʤZ8tYa7tqr2Ԛ=<ֵ,xm]ҷ_dkfRJYEUBǘlmu-[S}'~RwSnr{Cc{O wC$Vw&5QܡVŚ?^2h^WAd$Si97C܂XA,@Ğ-XHb\i!6}ZƠA?0m5~]/`ZǞ,h.Y'6t[vqSkkzxݽ#-*B{CU;g&,k]+w‡{:88ͥ :N'~LOiVhVk^+Ts\9p;a9Z~jñ,S 6sc_f`4@tF谼v; -gc޻ڻ>ikkK!Nw^fG*/VT&GjE*e,G41ttWԼe`n2Vyn2"ƣ4^" ;`82]Xi2hh><{][y ;÷Ɖ `! %Tq)U&9C@ ²C `NlFM "j&W~({8PvNrEYBD2] &.G;p 8dr).C4hDdLdEա12Ӄ W, z^tk i%!4QS΂sd5~0ol3% ^X[o~K/xZWmjsηުkskڢf4‡|:/|󙅠^ݣZ'nhCAdթfGfbnaN-p m Zv;:ѐf dhm3) 9Љ`X `N <)=[ă5Ŏ=A.bG>/>aA\}ɲxr-&BA^nw%Ui-ܖbp!\{y|pݽkzt~+($5P g&J*hbsTvG;;j^y=ڸ6#UMmo_CM=[4W:4.Ltee;;8S1LZG~ZWii3+}k'HXJ"99HwG81E1j1ϧ!x1v\m7$n|[_Kh-=ePg,h\P6,aw|AWƺUcTE MVr]O \:{{0e ܔE͂DW8 _oEDaI/%fK( ܐAR83y]y._$ٹW go_^)sO Iߒ/˪:? <2 5"@6_&x%I{ȳZ*v횮=*( T̽ ~|@@l$N>D:W*u)fCsZV3kd0&.q<K߂;m}| " t.7]UoYh!֌~G{667:KU~r<4|0y3`|+ywbDXqd `3YA1a9>PHbJ {bZ[{ګڿ{G{woֆέwX%Xv;*ehz[cƹ|@PONnhe3`1hӵPo5${].k0r"ddm/m|2 [1BSV3K8P<0̂)cin7#X0#% h9:&pK[gU}+$H]Q#40d%mw̲}GzlbD*6YXJY4c$]}S7H2nU^Z1saKذtEmo;{O~3vy{o}܅K׵*SD \Ps{HU85庻ΟӕkW̌$Oƺxܳԫ6uzM|{w58:T^7n#/FiMw ,~5|N̙;-=}Xr6PM775T[LT4B w;REuPN!nAoD,SX+Ξ5O qZw}IӸ #+5fgN/E;X<&ùTM.h qkw 0Z?҆RT }a?-F[p <.v QN׌ ca)^e:b/mD58%n9އrQN|dj iD`Cڥ@a gHxH"@Lo"/r.-π<EN5Z( b , x̙dn'U t]*`Oz>lfmwd-r N}6H=|fe 3佘)τf,p5l[Zt!6 RvekCO A;*x#­%ݾuK7Ν{/}Vwq_i֨knlnwpCH\e޻uWR[C}czi1[Zt>,+, zejښajh֜D.;|catpƇ+' cZ;N}Mb0ח&#p?}/y\̜}\lWrW "q֜q0N@8\r|?xE&]ƳI!3_|q8oj_7qX•3!CXf+֋Ͽy9*qo~?t㵊兺s}u6\kxO[]ۑ4R48>_,ty.xVskkzq{Yh4Г='_zQ\90DM>SY js/j]ݻH{W"@_E`1;@X!0'嫔]Z Ua28[Zv1Q|0ήZ聽%EX[R\) GGNg IDAT $Pۉ@Zk=:5]&t҆=2Oy։/ huܛ12ϟX?6GgBU(BP*OgvxY!BArga%3:c/X-Anrvh !OB,x7ce Q@GS'U|p/͡ =5}Mʸ$\~~7k)93=%>sG F1bN_hs(v8A/=،T $\0KH_?s7oÁ{욈y!a:O)>wꥏ6ioX-xhAD:MhmYC T12u*UZv+f\$i0&Zm-& u[zw2zy].`6®L#RWсd``p%iby$9p,̀h|2Gg&rc 4z8}p1X<7)T1d%4aM/8ɉw`]l}}|3?npL?Ѩfaj%?(T=.g*o1V(k0ΧD=]vfbKM.` s.2c>,~ a= jƾd=!vX۽b(xɕSqDѱc+\[S' U]EJuO&/ qҍNC׵u)R2\X[4_hwkztZ9-"z}ի+wnG!X7iVzy-UխEˉUE_2C¿7 *K.extQ[1s5z҆ Gڻ;UQ}EL.ٚʑbhl^Q67:&\b*dK9Tny~ށHttRW֦U-n v;n,Qw}jj6)a`o 7 p^`']Xr'TSm_Iƙ̮kpXm?Xo"PĔIնPrIǚ΂ߴpJqϣ0 _<f - ȒMD0kIb$B|^*-r@ j`K8"ELbtXBtʉOYD8O}r6`P,`RL |Uj3W% =0|`o\xgdQh%׾X//+{?;ժ}۷oks}͙bvU^(:=nGvW.9 Ȣ^'S?oi䠯WϻrH,9t6dğ40#hԧ6;*܉h]"9 MjEyXP]]v.բzYUoL[ A0"q1Ry c\N44k:z3Umrٶɶ-c_:EN&0gړO3L+|-pAˉM z>@Ko{416)"QJ=BxA tG0v! S֒ l{dpJP~e$NhBYg40f}pԨүp޳%=,yU X #(UE9 Zj'5x?5x.׀IN?8:B00Z͙Rp;P6bj\{)P,0vF ֮wnچ~U@o|WZ}m_vzO, q>l5ӗ%{o*~O==<:YVkm]z '؈0Gs]tF: ;4FJBr7D|a|RCa9GlE>=ޮ+ MJzU|[=u*Nk>4k[y4R)mY|2bР?0lWoԅ+i67Vk浆*6_@ϸp`Ái)ʇXdp:~|`zamWhYd 8)ą~hb B}AEĵ%&V|6c#;) @"5thX6quUmiE-5M]zYO=wC 6:Rٚmb-2 !i&jC1k0{I7HYf!Li5*dg3 wO駮D{}-8/q&o0JFh9adӜ]EAڽQLy>rNaruZLf"L>ZZ6wӟ1C)3 Y֪nHC̛8bŅ OdXI>gI&DT-i_⁓ -g0g>XȰA!2*~ [q 6oWCslʑKJ̗]II$#xs'w.Zż*;s 9?[)24yJsf ZN%ue&GX)APBC`PȊD·V; Z +^PscCϼ}1n~/}E\ר4Y_.\ b`?k!iB4'ZM\HÞF;ZFOj54!W(V*L-6wU+ Gq)wB%쉿\w{Uuzmj߾>Y.?W>V38lww%S! }f̐;gO%(P"8 x΋1R$M: gu$7L4?.~TH  $}TRX̾P46(lML#-h?%`@s߹?˿CGZl7;z)6e`q'7(AT$"Z-0&!|1}7<aXd_*|zI _x{hd萀l6Js#G8_YBVNgM\鐬խñ{,00z,d@Ȩq[[vybN^4h60&l~֜)g~nHddV<]2 MXdCZˊaOG{;P/Λo^| L)P)xT䲮^{L׮?asf0&RL5eKsXZKFETW.mkkoK=Groa `J‡6i ,i;49FTB)9n65)~Sot1dc &O!8,.JYw֟g{N)<1Z4qV+ e9 xG{A7D|#ߋOĖvXsƉ:Vi&O#ޒ <\05 L&m".L!x:~#3{"\ oJi14Ұfaj6^>CS=`77}75}揹Z̗VpxƂr\&}@9#R'sXq #586(>UToCX&%IO|ByԛNtOԟ4](nPs޿{SvKm{C_sZZ׽?p/Aplv֜>w fR2Qt#Sgh2Huf޹EاЭSULV47Zޢ<=:?3ALI R1#9xRGc6!rBa!Ҡ0TWm{=t15 Cͦ)zZ ~f M4 aN PHdFg˳i|/ "o6: 3aѮ +r2v *2*|!@ i6y{9Ҫϩrco~WQz>8adb@_ (nN06Kλ0ș/^<;<%'&՚o@Ty{)ürm$ HXW=zӸE1N#;՚ߪl~˺u77_[h>wp͏}fXk]ݮ|-Mu?7-7KO\M,׿Z[hKnl*`*(9^Kz^ 4Wsi4zjVHQ#cXj8"Q7NJsfJ5?YY=ƓƓhK5ŵnGk]jZ kLd;٘옐TU^ӧ.?e\p^gy:ez=A'd6_g +lO0yCN_+43*rb);ph@ǃ+HnE,|->O1gÎ1KqO͸!~ľGa̿KlxHʦЁlAhpc0;@Rd~ τ I `̹Sq`0%L59`?Oϐ{~͑N +G?5H\'شœ|bTtrtL{x?q d5(ΈuDN{䳅XQa <ÑO >GO}c;: fOu–ߺkjnoV.m:TOLQ YFZ?rdE5+|?8d<ָwJSh48>l8dr:"Ae-M57{io;UBqf BF$juJum\f&zQRG3O&wQ!@`$i2vDY}>R=C0F},f\ѡװt VBC9g?Wr{ zS S`wxÔo_9yKY'>qU ItZ03γTS!i|F F繗O!d=6!ΏMm x€$<`\K 8~.;jY/,*!+p 7goxIq Z" )(;75Lիj] fM*K2kb>[dU h{áv-*qwn?#c/嫶h)tw灃߿n޼FWֺx~[W_++-;ρ :S< (/Xδ8>_Q|TX|hQ'hkC֚=@Ijf ,hV'4]8NAFmݾ㍦d"@ZojLJj]+dŏc,eܹ֪twLh" {t#&o~cx;%qʈ3;88wP4Y.iSOS4QvKݮZWGbJ/a\9hַ6pEє"mXcV"g 6ٺr,]0l60er9g P;Ml 1!z^' 6 rۼOYia)"e&5{+yf~}*>ݷ3D1 }ڭD.Ѥ'1/4Dv<Ȩ*Juũ(.) |[SHrĽ7Kô:UL½TK6EYb6.=Ӷ%R׆lOfhXP^q".DjM :p8όtp`^G0ȧ xnl;l^("3n|\-hHkR!uh IDAT-j =[%w^o׸JD^~_wWp糏U&(tF&pdrApM-R&84XQ-蠟^>&}IR ʌ eƵO2VA` "J 3J&4+hiHɅϞCji1QmGԢ SL7`AxrZ]xZ@P0h9G}\څyy!!ǚ )35&m3iCk\LgqNYZQ:ėc0Ѡ5j5IfX`9Dr tm)MhM$AԽ icqq `9?TB$ytIDaC.9O6t/tUml/VBCJ[: *tjp` g2.ź1QX*\\ UXL|iG#hy5s*NHE+֦^g}K.nt4G1E/3>"3JNך6nj{SeҶ]"8+#7,ZN0br|2W'FA&\#%766`8Kn03i袂5ƠtΝ\#`J? >#c_dJI\bE 179>X 064m6N'hd ~UZn(O5nN4O j׌LKedv4 uxaP\0/?='C-5V>׵rx0@uf:Tq1"PG딜X =#cTTyúq$a$Ak5/~).o4s {hs((8peB~u?SN'ߧ#9+"g y  w"^YGqN_0%+_0r1)R dXQ!k:PyѺ@ -,Ch LS%L)\r!$h " *QҊYӊ'2|Bx1 w"jX9”[ b3A(⺑Ku 12՛ugtp VîSz'tzܿhCmmgvǬys5 DnQk .P nZ58uU-}#kD|S>-mcoif25!]zZquфhb`j¥ laO kM2櫉j˚4 hOPZj}Fx1ě5. 'Hw3ۤ)!F$`-[j-RhNd7cC偾P8j"?P3 ;aK1򞣛'9-wf=!~P{NǺtM[F[L0O~/@[ Kxl+;3L1sc q076Gx4Ixq( 8 ֑haDi<»~  -+)1Y` F?/К<>}-Oy@P",`.G3fZ咚v1KOEF'|FS]> 3[n/jckS[eb:8SYl>ocO\օV.X{;:U'x2 SXC4(,ϴ$sLa|҂ttvh?ci[Bh BD5N'؉&u&J_|*u[ok4/kC]TB}]Gz(>`=ا?׻]1$) 4 Lw@Bn.xpw{? t8{KB~$N0 ʙl'pdvG`BX.' {GsvC! ̜ H`Ǐ17dZ:[hG_dM5")m`) J 5>\30F5gC7-Y AT|@;]8rm@ Z,Ky0Y&>4{ffUA d-**dL{bl񳤿 _\]?yҾC+32Dj3~ }7Db+xXlg<\z x/8w?|W ^*/4h,=71.#?Njb 楕kM$Ч^W졛m-L9Y:/-i9d0` 9֖K "k}! Nj\k^Dv*@G"H0'K4|2R¥`b {x`s:]V+ RNJh?6E o>TjA 9f824e&!) Wc2g=!q>֢@& 0KՇ&S>(15#ъʞA ,- 72H@lh*OJPKANYbAvNVoXV37hp|F)O.YhB&#,Dͧ&{X3`x:0lxq8`lX`_bL<=w9eb˪ǽȾPW/=@Hk|ayIa5n.\hj'I}d=v ?7-34g|3^xv̗tt@vnNqU+4tnk]N۝ Fc~Z#T+kNzS,@@C-GCK+lZL\)0`otjo8& EY3rlO"tszVsnny4 iڛ_ыFþxe~Mf?ػϐ+, 8Qkuڇ !` pA)F! (_qҴ2jIQ~ųaɷ^/Xq>>2 u)eȾ8A|[D0wIl!6m9AT͂"–60 z=!`T .(4B) m_v6i t}1 0 &ܥ>*0LJ04Ehĥ af]0ƍg0!N@+e$\Ex}1cm7A;),*&ƍZ&] L}kxs[L$bV<, Ս+jjfR$9V} AIN2/fnBjrW*X NOHQXSSM`^33 \U&|]x>s{f-74M)2Navf (K{rԴ4v!QHY[YM;c ĺ8c@@vi.2ЈCe XZbmYOXdޥb]ۿ@QiMQ(wJv'?)#t#iFwAO_eXRgRd {nMٯ:do ~2^j{p]<0\.K[$scGЛiMY;}jOԬp=q kH!\Պ,G= !=>{~^'a1)Edඃ/٦p6FP %}X,+O_SO=Ϩ?Na*eenܸaO {ׯ[vEyuju]U۲Vdn5v9q`E0FGGz[Z{ZZ&ӉvDV*L{jf*kL~v[ GC'fѾr1=퍖yqCoWÑ>?o=>?Nm5 7zica*CI麈jj[G0-4-C>)Fʸ. 0z>`ߗ8s>ؽ~1^rC荂d+L. WuZtEz( &G4G  2V|JNmxOr5i]kxX##PhD%Ah4Tӹs[gy"l4rMAT:QADh>}dth[Ӊ5.M9x1£2WWY0 evV gipb` h P9e25-qaLF>㉬ ƅQaC BL'b\ÁLBAo5kY1Gh-K[ Lb$gnpмӛ7\!9XU 0Qz>p! 3X7(zb_ yr :pwh+{< G{0h"TAŞ Y0/Eic[&_H ׶F7|VWk60W0p80f9 q]ZX-Q2+U`] BIQ}5~L\(nUI.$l/}Kg3kA'GG+nWse] 7ըgz~E:kj"+#NxWKg+|cdw|s۪,J/j(3@VtҶ2j,V.5<8mu/Zjݬ l,c_F|-Wo=uշv-k6mlUw`p=d9UٰJ%4cFgޓRXv.{ J'8B2 Iqwr枴E>A"\,,1y?:VҪr{ò! .2Lq'0s1 4hnb0I ]/s~xүG,f ۅ9M2 P3+n>iT#j}] T{[936Zꬭ"ג`([#ڑ#9|X-> 98e 0&ug v)MR˱H,0\-3KZ"0s.y? 2OT'u^`q tOh'?/Qc>OMg 3GIuJ&` bl`xk[mؙ9 jqJsD;1f,k0y honv j6=ifRv:@f`p"rG*}1"!uN{\w?]^@`qnے;pdڇ\hى*r܁c>Z[wjys?w&C/>.zb3O:|nmg~BT轸ҹ ۚ-5B;(_ dDQ#} A21Vgy]e:UnxK޻J}OWuU>3\zyDR%۔#*6 H 8MWKv^$D"\9s >>}~/eeJFNk[mnqX IڤIV$={r"XV<ɰsL_-7=>:G''wݸ};V \ۺC#-BZVk=F+#vW t~ntd҅NS"eW<wM,ki{իL"[~֠-L9["@f;}ao^)iFx6.A 9a|$3ۖKQ3% Rz. CMryHn4+EQ),jl0AyӫAyf Hc@N?h>/HAt**VkFB[dL~xD>L _`8( gRAx`a]]ˆ -(BꁑX1 Bp@l{!b0hF@L MFkZ_r f5Ao";b7J_3Y5wXǺ& $0k!,ԑ!k} #]5x}$ LGZ6[w=m_D'L#ڂv4yrdEe]y]+6ʈC~h7F%kտ^q7H">UI^n4{]`~T6;_J#ͺ(WX1=W5N[~u"h%X[mb*8fˤj[)> ۩ƻ4RCP3TWG 3ܪBB (&0֙VE=O}%l891T]'5Ѝ IDAT ~FLěʟphz# AS̀%4{1>N;cHf].మVFA`\r/{H3w,/ )U eB8 3~ca(Yq ,BlAҎv/[Zt(ɘ6UiB. W<_>XYo<fwOS;;s܏Vy/XK812b%kGJ\8'O?.B*qvdܶ+6Cev̺#wp*ha~^Z[^Z'i[5Gv~xjkiiG'hxD6yhzkMAMxjY)Wz.`whi]d44ӳ˷ l+[r I^ oܰk7Z2X$oW-*[3HCHc`a]1>KDǸ]Sa~m\竬X~H{| o>=F5 >pA\?hXp'|l>V%%.&=>s#"&#[VpwrvZg\F f yPNR>T+4F"qA3.ͤ+HDx8b( 6!TwC< Dy]~EL;l7ET|`I 26p1a$MN/g"xkoh]Ɨ2dLht>܀Xh?v"!D d։=\q=~]CqY~XgHHg`da)J˺ $V<"&SIa Etbi`ߙGPĖә͎[99" |eR>? s"/i~XpuBÉ@zm'ǧ6RF SS `N< 1#nU$:>}*'tNl+5eMx7Q&zp=%g=.CJ3>G- pEg/hg3ŋ{ ;s3K,V6;ߪBk7z큞O]WuhEf\B"}@~ęlaN_gq!Ac8 ##$3|֝߸`>a6|dzٯxdR1,e21T\:ϓB̞3cw:..y0_F?X2$-kyEsf=|E?yqEDQỷ6Lk aXxg} ^AxmkepUx n5.2 v%t v[Lϔj7=nowڪHTS+u+.hyF>N߶@ag,)qhNϬ;ޱ֏ua3 T^۠hL4fӥڋKu.t۴muc۰ӶYԩ+W.٭}kF lF5b##r'bRn޹a_l8ؔTӕ m!Kg"`3{}t*,7VkR܄HY+q `lD}!8V?O/{ UT1 LBe`xXwUo dP}su]X+s8C:SGA{(U‹YKzr U{uy*lDAshAt >ϰQ*>iggd.]2HY' $5Wl7H:1NNݠ,s6 :D%|;}K8 t%Ve.\0hx)v `ld{dl!huxx,E5g3\S+nL$KY0BG8 t"^֑1}B!@,hʣƅ \91|m^文-eqs+E{ -X 0Hjٗ&9ݴ`e@@J3Rml1o]10>%Z;g%"wMjzÁW05ٹ*C{0Hж )isFZ FCbnhȑ{ȑ=$XXc} yց :ApAE ZiۺؕKiŠ5(e*RmKb!B&i޽#G^e9y`0,8?51Eqo>s?qU3B~`ɵνǻFc Cϻ9oC{ sAg SJ\p?g5Y8PR k}<Å,2ʕ :!LǸ/[3dR70gW}O49a|<=έ3`c1 ̂yQA{ "Tu[fLԚ->㵝OO۝{ֽrՎmJoٕνvMWN(r5ӣY ~9ߵߖe%!MVmW*~Zڢ\[yUnZ4Ϭ7=ߵc\2wfN:,U/&+_K!ZB~CQ"vA9`8W*[R'M7 T5}r-E+#Z&w ec?mx1(7 Qo\[܃ªW)kE½]-@ `r8@t̘ܳ\,looO}Zw!vm XDA~uwD,~V< tv0߸+~&k0?H%J3&%l1%lT2]f!̷Wcy8\%5ng'"ahT! XYȈJPlf7s/&_FɅij}?  】 B>7/>0 @aGTyeu& |K.(j8C ڶַX*B!έ,ӉxGYbV0,z| +0TD+Jc^aиFQB0͗+\8-eE\Z!gx` SKumHeA ) +5g/ aǯD.xdb''g8TBi\'Y&` 1`8 q9)FRxXےߺ|fM(/,*_ۉs/;t6]ܦvK-XC 8%2WizGC LFQ 7D/50 21I`rʆ(Kx<3>d ¥d c[e ;2O-)x1po^h] yMg`!jqʇ,mdl\pw5oY;E0Ib)E }>.<5-|Xr}l +Y03v+yoOvc 6S1k.=޲·d{mjGӹN6Y;8_͛b޸Fr8MlX'}j_vkoWn+~DQѥ=|-[zѣOlKFm}Wj_ڗl]I%a$/6L0ϓ@k]z nYzru3[o7ā*˫3NZcG)&$p4U3 oXlF_:KxGdi45ZA0aM-x7d>GCp.>7Blp@7v_vT/*v%-ff> aa(`1A١*awQ.d%!kjLl- ۗL/781B"DYJRa9-'\ siא`zAO2Z9g봼 y+w/od(X QHR'|_3 1hA1ljkDK 8! =coɚDL~mθ@08$ ɉ;&/Z MD ,82 I\Ιw7iըXI>U^<-Ʌm[zy(ь`&4uҞ7T[i1 L7.ېʎ:JedMVQjMUp˟!ec:&N ِCn e)|˕u{-w)n5BW=v(ܵΨg)9$6 373ՒRQusWB,- Ac#`þkgaw^[bE*IۢtXeQ%&OT1[r=:.3RSEmO Z<lc{_pzfӹӰ7 ?wdA0B J%lOzj>lsf[.[|5b|;8Z֘Tx^8bq>ڣ-m^!"rCjPF[]裏nMgX%Udr98:CrW޳!s@JK\sA,70 OVr+Biyc-b׺v^b7߰o}nܻg} @`^-7^ǏggϤ(!5gZ60 3&L<^w6ƾͯ_o%*E-WW~䙭E?Ջj5iǒl6i̱0Fo7t;Y 2 Co?/p|'eT%jHqم]Yt5'[jQ/؂P ( LF.x'lxNT!o=xO]. C-&89`c<,. C#q J\@E9s&~t0q@\m%hasRbQ6,7!KnƃYW4t zUՀq19F{Bq7nKd8qD C^jz鐾@ ~g}\4BiK"0HH6aXi_7E`@ @ pMb)ƄqK2Rf.74Bpi0hyOfϳR! #۶) #]<]OݵZ?KWVV۷ǟ>偃X4*YNxHoQPs  Bw䦄fJ],%YoM+pΉ-QD#:ϽZ,'`^T팥z!4e?AS H>=uEDJ`y!\p^BP XV#. Vb+ !Ѕ[`X_;x~iF J8GXgSep,GB۠ /hh_߿Y)!Xh*lSllں) d^K.':.$ߺbOCa!1JZWe\h[;x& L_\TB 10aw"%cglX`2Slrnۼqwĵ`Z=cR2p0{ppYg"~+{'?w,cV±{W~=-'~6'''u[O5nH0^5։7}_~nݿo˦il3E}CSr@p}l緿ckuz@']zYKּji VA(t{{˲^nr6ƚ*(^8iWm= ᮪:G5[@-$6 ݂$>lK;0d'r: Lgiw+Я:+|B%6A8cx@wx xLiȁ"Bkmxxb{UUEҙ3Olt'z_ E&+.uaj|dOtabTUHbHږL49@uF܄LJC&\ ?UnA0~M*67SSA\.<@4{4ԪU$\920|a%W$ A"֕ϣ B' Ҹ+0vҲuDF&0펵zDhB½ZPN!J!pe?Ȝ@=A;!Q0ԑW2Zpk'WҦIMv=YZ4mTA,ch=J!s ,{JAV dq9}M_ m܊ mUS۪(- ゙ NOhV0բa0|M;)xrćm)C 8}Er~XԵh L\16kT5rr27P*Dͣ{9h3N֗wrKs/qW>"X)`)6h#FZ\`چqnw,i Sޱ m&v>޺rٶJtT2x`5y֕V~:0 )p+b1A`ENϧ/juyu6˅N׼k2\kanOz8vs@V+v kEc#ÇvAZ1` 6h IDAT;~ϝ^ #RT (7<Ϟ@h9txrF{O?BP!MLİr} r,h{ayükik)ENZG#j۷+M›bB6p'\̦qMm0xFH EJ9C ,%v`ځMvT L~a]!yr:4>PLx pxX? -yp:cq_/lC>5=dǤ= .@\ g*^|+,Eù(!TcV+p8Į07V0_+O754AY49YTҁy)L}m6 -ٶx1W%nssJ&+ǵץ=~~4IC5Xnֱ+3;|3m3L@y&~ĽP'N7Ҹ)1gK\qE:XH(4U閙e=[:V^O/?-a $([X.ο½)$%#s3dMM70Ib0CQTs@g;-,5EY3V\OPt; Ucp1&AJŻ/aIHONiQ[T Q(@ 񥱈 hA)\bSE$rjg1;F"{MWTSX-aD٨[w 2 3-VNG3>L@ #Rb99+T+H)BFL&Mj, wEu;!}( -M B 2֑^1!rx`'Nj,#2oz)mcĔܮXB0$Hn #}caxq73w3 ypF-YK>y }!-W*<UnC\$H߅51=MmϏND(}k kg]eX!kLY+.J b4kMi>;XA:o0ڱ7HWd]U9"vn [ e+d>Zޚi[~g>z sQ }nIaYw.O@#/ /c=RM`g12 |$B"%E<{ s JE܅2w@{D,8kA뚒;SPa . qۮܬa[~у|^;ddq`E sw_t4>XTPpoCa .@C A8W\%Lg0C= q/ YE^1 5 D"Pr>\|@`b;s{736q y|ɺBw¸wU0b=}aҡ JYU2d1֭[֙bu3JNʉ(b!-jR9oNZF\%X7<5?KwwvبUs`6v>?WuCuĆXivb+o~{+Z'v,Da4>jSɈF1B99RhY QLN,,jde.*9#t!B-.~k9?q֭ΩY?y,+rb(#TވKU/LJ"191#!TŶxDt PC@7fi!j#ƚɩ~],I)_ё_qF &(IUL38dr?- K$)02fB!q㹈AzҔ䙻>p[rvv*;>yCwueG GJ@,X稝>Lsg`LCP2ގ#A@>kNjw[Q*"r[5Ч3 u'd86 @.817,P%Q+QacO{TmݽҾϗփ)Ӷt3ufʲ\fmK \ZLo= (TKR,kUŔVqR^m<[w٧ZlK:fm׮XIl9ko;82'>_8 Or#72IcFrΞz D0M:YD9Epq\2+HC?s#plsa cM@Eq`4+-ܙ9FrMK~c<"0?.Edj #Σ֟T$[Np*M=sDH$ 5" J5^ڃA @{d Xwiӹrso|`J !@2J^^&8w07mSds~O2z.[ ,\F9P-4+\u ˅֊ʤ|fpa.dG2NbEnm5 Ei}ɬ$+C  _ #%[֛Z)€^׳ wTuf шH)v٠.V@0k'ڨ3g:ZݲKMɥ2y{|LB?~*oO mmw{soڽwBUYZX-d +$DJ 4M،$nٖ4U!bۆ8Gy?Myjoڞ,*odK\,ܷ9ZYO[rݵ`lJ1_$~@= ]E& %0nY\ +%9# @q AvS(@dĻ_~+͵Z?dZq$>-mT03?g=y:D^͖ hwr0&-'76dB7BQib``ɾpִLuZZ υ_)3a#c{@-; 8 z=ݞW.}1.rC8UAˋɃO-\p=ڙsɟ5:ׯ)iB0c\*boQQ~g#B֌\(%FxBNKVe gnC$[k="Tnֶo~S_e?CJKggST6[MXV :]Ï_|j{>~@ăN?.$hoڕ[7l"Ӓg:;Sq_oP'V>0Ӥ~2٘h9aXYx!X\榫eՔJ#9%n F^GK<_|4 HE#ykV߯fˀ17RU x1 ǹ%>%!AB]XhdUu//`=΁B`|?(-\TB^H<jb1Ԙ]p6[N-@LSPHBu#dy0k? \U˥5,RH#@rg8%WGG$[Us i}qL3d̋TJG mx^lVVA;Dڌ(82?  "I"͂t86,a|;?dw*l'0>.*Mb߸f|g dXyNkq0eՉV g28w#jY߃lG`%uU*BxB`"6.z!+͟KA,'}0>xg a_U $AF)]qߌ1 p :yϒj+떯ʬ :ŘL&IA ;bNNNɓgvttde!z=c;طWY&p>guwFv6- ;M۶p ]/oY޲4֘o8 Z_ڈX\ʇE{5vc# )6[#e`kmX 3 X  w3:F83G-gX ;>Pvw&4 "E. C #k5>^! Az 4D[6 0bv-K2z` F̓2 d  pZgX Ӧ+΀%3*DnJGSKq&8M&ԛd΅4-ь=dr*DH %8u#$݃s2^$!ug(͜A(hĄ(B{" L,c} +VVko@S0m{UU. LaLSJ Jޠg۪% ug7洪۶9rgc \o.+~vK\ <!1LqQlAi}F1?(U}z>F1/LBARkɁYh-W]^O>;;@״s'v  $X`0t@:|v\ Y3@ H#%Irm4JF =y:Á4S$h{AfLM'6 t1)11>=\|7M^|͞<Sj:a#D@O焗gA}R--5bCvH$E3[4b׶3v(Gm*LidW7$S#if WwhݾQZQy^{'mĊuaA[͢ӳ%K:}PqbT ! f1Dnk_Y]l-T `G*j! ;&np-x(F? +6 eqk砼:67rӝ)o| P)+Wр&R4R-+ʍ.S sXBhC9P.rU[Y`E f'{ș3L\CsNGf|u&x^a(?2̇gDQCfymzazYsC{\vy oamQ@BCX '[8[=.rJ,d+9`9ø>>Қ|$A(n\Mm}v}C7-]{/O>smƌU 7''vȰֽyض1XK|uR8þ֛^,Ml8lb?˿PƢܷzmg՛ lR5 %"vL$m7fcpWz#.18HOs\{ ]_6k`+ ?$m|Fg?cڲr7E46d B!rAk^%qn){x@7ٻl5$ԇF*-mɍ#m&n`L*% 60ţe_%1l+ 8\d"W/n6ဇC1Qctm" Z IDAT@s/O/M {L&I)Muȹowm߫9';d>+gӧQDKw٨ѣ12|waoFqcV6 Sϑ~rM,e $u+26Zzﺰ\)S?Wvztj2sXb!fXOLh9kC mEp}9~<Oa.:0NP!_)Zn+̉>2C+)@ gqL*(ȒX8xX+t&y`꤅HmZn "6蹍VmMVf?;3XEB`{6_m\Xm;]/>yhW#)y~˜vbAI1v 0xGMAc HaeZR)87) |+;tIϷ\-AF&o6r;}ڏo}[vM#k*P}dJ8w374~.V EZ^/B!’Ű9ׄI~^4̸ gk;q5r&XR@_Z{OIC\FT~~_Tne)LgO k%u^K8rID`zVsG?#wmouCjЭa#7jfU0$N֟ʚjçO3oӤIOaͲ ;G<'}&6B)8}fb_cT}w#3HNS?ii.蠾#ip^X,'^QUXϹh9y>W;ʕȘy!9B r h/?^-فlZ-L.40˗7< j$FC P.+[]ɽğRH+Jbi~SECh- B&jPQWW+ˆݘP.ڃ~p[H+ K/^ W(*,*L-ie תub2L>j#IgSq%lXKNH"`м4iwKKY"Zh(lX9 ݵ``kx7ib߸)a >28ޭ|Oa^ |kHUMώ%׾j'?c+g?XU&&QD;n[޵?sObWv&Id>lMe*IRR>6- O2NW3N ۬jIʰ̶{;b^Ql,wYr8$ 𻿫VhsCAX2欣&!KR΃^(!N3xeluZW6|15_{QIA6,&~aRx$܉Ҫ?(jpgZ)|aI=Gg 1E4y/ abRdEU6,%kxD\[ qϳK =(0IjVW{jkZ.N]G \{"&!rW _,-5{ RB~=8WK`, gwPꚹldx0b 94y3k1;3WqZɏJu~5 &Gm10-1-»v}'ivSFCZToR#tPj[V+B/FmMm_Eb=2e1vbAz mQFZȭYpKǟ|lO>o~ 0(-ˋp ญH;L(H(ya?l'ib]ypvȞ>sP̢ӳސ`& %@g ų=uB |ϋy70ύuƄ)J_u#>k4ZX#9i?Vo P0B4B}4RMB4~gp^im]{8Ϭ-HZ96Iܭd)+p'Y< `Y;FN5E,Nc?)U+ BEFXcӹ;ڀ׸[Ɣq)֌znĴ 'Ld!FvW&"wN;chwmC#h (=)HVb+*Dnsk7+۴v3KF֮ ea=KĞ|Tgvzrb<|ܷ+<ݭ vrnu;V"(0acAW^\|;ր4ޅ%@cN |(ʧ3GkKl#Z#E`}PR$  [uh:x RM:#\SwWw"gCόreM*o;`\& ZQ ٺ'4,%&: Á5HL +*ljA#5ķTD3n F3ZqBVgB!i(sHE-Sd rV"+k/鸘3c!f-sۃ{ \8`Z,WKjB%c$VfN 6x6H{9L7[=Q\\uށݥ .0O,eUD_KPng 9. >0&a-ȧ-+}VdMQZ&nٓv[6u٣Ç6ʺ6?̵DΘac.]fe;]>Wb>ޕ6?xʕiIwL0  LgǦ3#?ޱvR[o<|гU"ȣe l?}vwa0"-_b&\/\J4nK*VR̍" b $" bMV&bYǙ7\u:UOr*_3aѯZ];s5R8o>p.U;S/!̌\4!& 3ѱ kopZnD#)r*+g9M!W`d?&Xp,,&L7"1^.[{:RCcڍ}sPT*w!&L? ֘5/c~y :X3~gqGɤ4 `梔sFwq<(X__|?3fgM7~vs65f>2#1?U G5RP*-Ԥ_ZY/lHԫTnZY#5븆_xow0)i}l"ef*)vvxRj~_ &ёY'.w`[S;:|aw޳V`Z/v:I"K/{q[p_νsθ,"" eqx3R)0^/\m@AG&1Jn|[.m>fG4J]({3a#"_@ bobpmR2Qt b40cJP9ZX;Ui?8N|A&A?sC oܸ!CGVU˅u}Oq$Ș|O G * 4p(<ˋdmwăO0b>D;C"ϻHD|;C[.Lγ|q(8IiB~O1>vhzAbcum k Awc̔{eNL"A 4 &6 x_́11^^ f-C4ɴ0;ٟ˻5ب۰۱lW`M)* fS7۵o~Ά;hR,-`}/[g4.>xpę{z[;AA"l}͒B2m:YlcQxƣKʢ@:n'72|;|~lxzKd<̵p߬'/@5:BCVN`.[)cemOy9=?OԘaߕKв}q }M}q?G}eaߝFBC/b`5.O !J$k/1Mlheؓ}dĊr)kj}}̦ώfimb;կg۶[v~6U%{3×߰ó=>zn-bG:󤃋:A'0vWW=/ߴC;m OH!f :7* \-|xyH加'+ s ~Cܻ{c8uգEW> 0ѢŘ(@ } [ڂ\ vz:yM\ݜ$+qxq>N$@8[VG"^HK*1`ye%7ĺ@,Ja q`켃4 "ts0eV) @3 sbf 8)vBv .!&:h0~,x>rqb=' m);ĀQ} L' BQL3k1=fЎўO]h@;~G&qn{@: c2&4eB851?8rw3=ط'!Tøgǧ_gg&gnۿ~^ &z̚um1~?}lW3 ]ղo-s_=1\V`Jr`G =dݒMUZwcWGȵx]ѱ=>8/ښ=x2E)MӴ|5= ݡږS{t4]:};.]ۼ*XJM?hO՛7l Ց~϶x!ꌘ~ "V(.{1($3ICp!)w W4 xP/\5o4̃;{!4Fe \w833!sgg/o>Eè2fDa)^ EP cOu`XHh}>0ޛ6K^}'*+=;И  H $(!z!:v#DV8/D-%$GC$a ׻^T?Ov黶T IDATP;gn[UYO>y?pOC vcxojJЯhMVO? r3رܢ$?j $Kn}k[1tE}u1JY+RGU0(΂&t,5/\V}[m0ݷv;/߰x7/퇆jR%Rt0Ir璉@)x(9Qc"*r&J^Yؽ;٧– {koko|^yJ\ {/`4%قpH+M *!t9>c4ѕZ'7ql4s w40#JNR#;āĘh&ex#&2FF\D<) 4-9?3>^pQʃ6t&"jvZ:hVԑ<B BsKb͇"ѵ+կJ{Wu_}~ԥ{S v9DyA؀Cs oL2T}OGCW-p*L'k+6K[]jy!+Nlr-HxDD~V<c28P\y_n } h+|dWRo4F[<+,Y)dcNiTҥs-qDCnj~̕&:!!LN*e5k1F^Sk(d7څ(Jӣ:r 5;sG@JƢ9c|(OQQ9Z 9#Lrcߙ_J.|r e7\ņ>;P023q>g- ets* rqf Gd,h' ͱ]/<0Ν.sSdtg-t-5(sRIRǐ.*XwX~o[CK!K2ue)͗^{f`ߖfkM9lhW^ݳ7^o=^}R4Gþ=/~/| z6>YJl:lm Qf"@*=f|ݿDM4s$-ٙh^34%ꐩעg:!ǂ갗رSVW7儴ɩD:2f]Vh*]DW2NA:Mh:HmT B8EqP v`s~+?/?/B S%]%Fg-eaX fƥaJ Kf{+DEH>a&Ĉ !ɞD@6j6Ǐ?D._j?ÿjQ_9[(APGj{(-rmWhBWf'xѤ45nȲ@28ᣪ9e%Tĥ* Duijn#2޼eÃC[בX``\9 @?CqP<3P^æ< \pVaN g1V&aкuHsC vҥg{1~K-nhRgL k}[ Ⱥ*٩ coxPž^%^mO)P5 ˸(^n‡.xx^i)C-4B1Q6Z2⍦ Lt'4o u>\gA^K9ؘR6|5&CyIKUWy g')@PsqV^g$^tdg醖/%ќ /\!@$՚!kjruPZbs9| p? _#K3OSE۾۫*_6l[n5á]q͗W/¡z:iElKmGZuL,98k^:_b~a~ӟZƫ]>cλ61f*7Ir}xa+k[$hp 3J Ũ@} W `y CU*|!*l"s{B'U¡y%⽂ <片\4@J8pa *W I,t[#]/獰z0|$>EtWۭ[7ƵRy]6ky Q,x2=0I$!hp ͠r\!IŸntRIPfB%NldS!pj*܎v06?xQTS6r! F  1XَlH.>BxF3ԩEؘ!R5$`Ϯ^){}Om7W.[KO?9UDS&嬲K#/9 g>o% >~d?= GYTJ0f *DcLZs R'R,N޽'~vU=/͜ ͣYOeAZ;U AA"l1Q"qƶXĶ!(KmeK>_n[vM~]qN/NUdrxS!s0`Q""˼C8Wh+pGSE{ziT.5&E!䐮#ddEӧxil8$٠媰D٠ 5CDx;g4 f?|_{D :nk(>F{T g@<.=ƣF<Fj$P[lD6 Ə7xGj y/%޹QCS z' C+VuݳNmjY|6H,nM\=x5Tや@<Ggo\)q fgÃdNe0]^+LN8s +6w`8)[ޮv$GV10`9|@<2];<:h,Z9WwBh5PCE%R@f6l0w҈9;X+QdRZ[.܋_{ 7ݿYu'7LJ/TxM]o'wݻorۛK/lx L:3X{*:t$KO0D|κTsM5)s7Wӎ (&o<믿a'#'JM;8 |&U^+_r(_+vix1I׃qB < 8fSxuUG6-enlM[mit>Dbl){(_+< wh'i94Œ&R/N@ ɞCyq[>P~G_zd̒c嶴wk C`$SZ"HDJp#yX`̩+8x}y$H/ [HBv-sTNRۮH`q muOWrOBu)/B2kYQ*SMiژ^$,Vsl7$(()|=yX dg]z6RaHQ[eBEN?( vrJRQyS7{Gk \h!`AC( @ځSE/ -2~nWV3[xB3 O8p40pzs\$)UAO&#(nt21ϾŨxBWT #Ęgdq~ !\?Why s~P!b9]l~xԓ@%j:iPVvLlY?}rcz)PQ%:1ߏr#}Pi~_֑ײq5 hX]7<5+#I:~׎d$m~qnZ։pyqU ]^C2( 99sG)ULQ,r"C4="/B`U*pPC=8Yvl! 3>p6  +Yx@Ǜ[P0F2F2 d(*H(5Q6˕Zƭ}M)`8uPA&ptx[`Uzՙ hA  6%ɤ(x6> 6jB&~% }rCQ@⚸g_nbm7v;Ȏ %ӿ逦Z7Ӿy(m}֔qzrGnM`FCFDv \SRׯ8cPbEh;xW:?1t$G"]MYUŕneyw[OsWQ=֍< fkA|^Ba@ʽ(5x=I9y=hcRx)/Tיr~Jy,`ANvmB =T93=?~Ux8.|Rj-ݓvoXյ-I3%# qgC)yK+pG?߶}ݟ> <CrU>|ʼYWLU]MG6 xy6A:ʼRF9wܱ{}i{wU~+~W p(5YOAѐ d0wxr_Kpڨ / sʏ:gT?Iocll#kNJW6 -t`b^Q#'Ep IDATzWa,m!"# Sat2|ou\ p7&9I hȟa*__/BMxH8=@.x:^ᝇeRccݽBJް]1U>G r0_~꺐ˇ_ڃ$T_'HG!F#P;Fv-wR9"g]Jbb"Jt1CH_m~~bUӋs[Kӑ](OVOD) [[5_ZӃG$OKНL-rVkwT5 3y@|ca4AN9x^Y(`06<0n,S~ÊE/6E7klWzPHM3X[+L!0%JY SQB|Yk++P 3$ARyd$"u&m|Q[&3LG;ѻ@Tڱ<+PYyStgضL2&Ja1Gl7d̈́:u= '`AB ԘŒ$mf3GLXahWZI"Ep0>G%hg>ˀ" r]m0u CrZHռ~A{SwDgGF7QW) ><儮Ƙ' ik(xQ\:깭sN#:lq s8Xk=?> ǀ>yXw B4!j kʉOm\H%q؆KDGUp9̉7*J̗vΧv ]rE2pҐl(c{2uQI:'c_h[/Y_3 x ;r>6lbwkD$"8!~xAduP3WmGX=H11O9&̨(F+0`8J_1tЖ0=)(.$)o<tOH  Ɗ:k zӇ2߃BJ-<3fQŻ]P10*tf|liL-E"+`[Fm~0n \\g(wY7 h{N{,Զn~@rklVl|6D "GQGqJc CS+( iϲ6}2J,&Q]\PrH {j0p}|jځqkxU;g`L=>}(VK/7 DF.oq ~{,"'AsMF^*ܯ 1tW3>qѻOi[ ``瀒v؛A992_, _Hg>]>bo]zU'B1؋M|<#4bΙEB Cx<־ ӱO(OV42q/(+s]/i/]\c9ptfi^{FuPO 3sԮ)#Hd;7PG)*ulza'X^[vOfgvzzQֈ+#ϭ*Bɞ~ sPpo(ɜppϹu4O?yLUhL#=W…e6 w:^QMy,J&bQHF()z + q ƓG\ZxB%cO$Tox_?y+fa ??4^Zv~/&rTyޟ}쪠kJKiL[rϩt-Mo܏yPdxeRuUzpW3&J2(th-YՃ%9lY{@0TqxϥYZI 8[v<$Uҝ!93Pv^hG tANg}fo}M{띷5OO6Q5hf78-C^3$7DttEmlWkNGvxtbxI$\yۏFc{o~O߷ʫ/}%O2;v`cOttB$M rHƪ~YumpiéAR#Jl׶>'G7`,I[wwƪeb|Zl4ٳ"^œGVmV0Ψ#i^Q r"Ruf)Dm}~b'O MD **.`Ifsh׃*zajb()l5_M8|>-SApy4ƹ${"(8M Ciܐ 1UW8\*(?`=Gc8Z<^~3 Xh_j/εIW$fuerlJ`sP* .B]J3؅( (z0c3p)4m -28^C ίmf X怰y#֑9FiAXa0d!OĻxdA}!Up y((\+uqzDx&1 n.lm8tc-3]χWd:Ubre&ӱ2L>\SJ6hj5naqK-F8oL;r" w]#4O8}zl/9ۚKai$EI0l\["'y҆ໍm386/ vO$_MIyZ:G']A(~T*A1C1vSX55qߚPx]!\J)ڬUs5Ѯ{w#pD>^p;"g{ᚡv04Pi?;Y9Q,:j^Mn5_d/caW첌x <vPj#y75$VD0pp(aiP^s Gl!vcs^LREäkѝQ3s^ f8&"F7Cʟx0esAE/.PBPd],aڱ'ɐ(cw19WsMB<R.}}CAi7o޴/?*i'ֳ"\8iB _~~>x_#ͯۍG,lfRc(V!<C5V+2 ^V[%+wQqa8o6{v=[(KIj3jKcb#@QunCOТube',jнP8i@PY<|^0XbM?s>c 5V[TBu z>n" 5ƣX]iSv*¬lvĪZjpy~ iA]ˮk}(IO2XR{ʬ`/LeQ#Vfy^}kG{>9k!o^gAxZS\xU?Mg>T7(Qj,I,Fgib5G=j<Gҥ\[kn :9<$7`hw*|M{㥗~~;-Fvv~b^lK6"i'Pg Al,%lG+*nu6Mn[lImn5H%_Zh:^\o%s ŠUne7 `Ñ\v{DbkȲXYI4,cOپ,vDƍl<:M_޷Jx;eylhK[t^F>R Q"k:n+x0 [/slPZo yx陒Zfg'ڔX0ÇCYm(/xM=Ϯ$`Eɱ`B-e<{BYi3.Χl{ņF0n ұ];6`f$rP B !?΃r-~83TXh5q310dLdp{1旯¡ o3V]'I9X;t$jn+\GH^VR %Z x7403X!pJ`[wZ#0(D04xIMe{!/c-_΅3$aC yo0҂Gj^gKeD U.mێ2$JVIt5U .OqAК/ٍM-2=!Q$Q~Kɧ~VxBjc]j`Xd;&U4[GjkO@0N5މw 0F!gFݔľn2xq3}̍{c#aqh#:hZ{O*9f6;i ̀yО^jARNRnHuHL -z)Ǐج(DǐP4B$C(ӱDj)|>-YGa,AW$PkVA?m)^F ɋ>nb$Dҩ'FxZ[Ed^}XU @Hv9TbcE3k } )B>$z@N 9@' %nKe}.tT^knD$RQrպEBq $ } ,1J$[ :@gZ~bLs ӮH^Uum&wrԡ&0^Ϧӱhr' Sv}R˴!L8J=r Ԋ=)xD![kb, MS~dX808^ QTL= 4!:`I$50ˆ 8lᾝ<u?P`H!U3GCUHB'ߺ}{K'|O{c^bGHay*B0 ]4rvjncGu.CڤXB6%((#-$JJG 6h6B< xy;X5%Q _@(Da&~BKCd/ ͽ20xa0DQԞَ7o*jfZ񄌋c1i<~Y!aQ&"lusoĚ64]}<֎+vŘL75HBH79܉g" eQNR፦8h&J1EkJ02d|.;bԅ jc&Vfņǣv1bXJ9!.ΎOl~wo$h]B BM#9xQ`UO ӡf<1A3yQ֋MKԨ ܺ}JLVfxl)0$$L^q07lS2I^edtl 8WaYIqRY- [p_Aq`A=ݞӞy|ߌ9>ùvS5 /RuJ[_ @7&\x˩%r2dp@'TF ~3WONNRvnǸWJ)g<{SMc`9h,T_anyf։y:=5~,L#=؋J&APb~q] i}[ 9!*;eg OA4`qW1gooI1'ٍ1+9VG;G."Z+dž+dQg4V}ݷU9WZ$) MN,gOK+Mڮ|~j]~ɚxg};/.ˑ%ȇ@P$k RC{[Vs3p "ݻ2D0y $]ow(S9m" _;ʄzϞD!A~CW+[[R\jX`1ǡy gI"\qQXڇw@꒮ÑXnYq &\O{D1k{FA0::&CȌ͏`p E,Rd9hS5 p>MxQ:ar0\\! 9C"Tl\&"g\:D`(cxV)?K^g'8TQwJta;LL <"~ Ms<n#f|2aBVT F]cW;뭰L6ʟ^<'IƤ$\PvyFao%MXc0c]Bxݎ߭0Ch0K0WT 3)j~Ƶ RWT/.mePluN(w(h.t"9b|t#z2;G8X˗/?S rxkxc<\ί\ ]ցCPHVF:K.ro;zuᗬ;{=9![qDH>~l0ӿ뿮yÓ0z;c_0?EuuBEO܏9KVH @P2 {8:,NAifUU۽ LSuKyWa<&&`ҸFAXzVgAf*sJ_I Le Z.\:xw*H;E$OvM"uvRl5}~#[5EΟh`4q~$ FdX)A em!X]z-&މ-z.qE|j a5ߡtn#PgϮK_{W/̜Fn@v5~dr%=FQj1C mWQmp N^ŠpD /?/BONk#ũ@!e4]L=N2't SK 2d$tcsCe 6UXM!TB0LVcC sM o9Q?ƲVJ Bã-_4>+a"է}v((O|kĢ$2v7G0&Ƌ 3µKe1wxX3>8U5(5(GHiB2\ܠp{Bq.g5U#Fd*CĮIkCJ<@C<+͏SFb%y C9C$"$ 2P$s;kɭNӼ?{83=|پf|?NE4u~7;]Yj=Ճ̺̊<+<< fCs>0`(ш)EBd7BvUxגbw6:h{GP`(~O q?R"R| gU]+0RMU%l[=a)V\||A3Y+x!cΡH`&(;T!y1R3u#8IOPADkes SE%)#l*ΎfWp"7D硄(FG]| 䦩5g>j9oHTEutYõy_.;㈨luCxgEe)fMs =[/dUeG}[Seev.Ǎ{&T) .IǺXd0!} {$=c:J T*l {A7-Q cgؠ۳NMH&ɏc[.6??c]&ӡ F/s8^RjAwX,Vv1[lvgˇ6؛TyK+W$SvG--ԭ^ % <3J Xr4Wݩ퀐^`rpAخ?=l}QyajD a;=4 ݭ*br:ㅑB\ @zG5r[3,/3K:֓tdOK'qPe*p ! IDATp}BAԃ0-]0 H)g8RifcQ]e#JűTց԰i xغ{ݰ^ǟ7n߶wyGJ99#}EQbT/y1L2@ߧ9Z];} ~p]8k'"--p&&ijFtzKPf52lqhቇFOmD$ [*ZZ0di%ͺA&hyƠ4wBW's[Ζn7ё " $q""J"hkMa%xQ a%S9b#*%.=.FI(x){~>"TthD{)\H0 MnGC,00&lmU`㢨Z U kQDCw \ܩ`,֒ &珺˦+l*6ZF4cu"o.Բt en64iUa68DH;h0Ryzwg5"1k%5z *F)pMS=WvttRDdw3V@RցC(LӨm[@-Oݷ޶۷oۧ^}8od3wJB)nfmVJ3wn6v8en*쩅ޮVR\`C@%$Wk Dpai70Bi!L5pdKNWI ]^MGrytxh[` QVGmM f ha c'u 5>i`!9.R-\V* *ƧX8`Ahe?$ڬtJ*$I;-I7V!{C՞%Rk,<=9j]TT ͹[A/cJЅ)pdדZ,~P!0j09%zYjT曔4p<-;Yr*1f`\p0&yCL$]Y5lVQ8X# MaLE9Y 5l3Vd{;3`Ps/7WuQH^=c l[uݵppy4Ό%):?W/~Q0zĉg^}ƿ wq8<0,!ߏ7qY? ~$ Eu^炱{:莦jжGaZݙcP2&xQ:=#f[.ޜCҝhsʬǩuVé(x7CA7zB)o8&!ҩr癬Xߵ['4Fc%]+CwӆJr"q X88*ŮPmmO'bnބ˜׀C6b_H^oN<]!08ϼHcCZQWY!zL<{'ScP=C0UD7HB 6C7FXt*KC vBGP4 ,!:A8)`#Id1jɜzmt8&ΉW00SH:l y8j66[Ux< p9* $$|ʖ(뭰an0p{c%y 躱c1b+EknxFdl V g|u]Z1Fi_ҋg5Qz; e[sVצGNPYj:a0RA/5[Y>׬g{X`#NZ08Sm{U'Q(P  txƄ QFf9bgXΝ*{gmYgcSbVhc>5Ȃx6˜I o>&r?^#sN? Q*>w]H|g<  d*Ǜ>B6m~o8N1p3:f6:WŴaz~=9?g 7 /ٹf}GxK9BUd!}8CY}k^2`^ƛo_շ׍-`SV@h+kTz#ꉐMZ , _G8Lk.6^\|(2/6(ڪ` z~t"􋕳Hh!䱣؟Mػ.l3YKUrlƆ= ʉ S$^f41W[uKuZu^ |5*.F0ʭсmsȡ߳Յ5\G0GY5[EW8ՒVE^uƕ%Eؙ<8Sm3?6Hr@- !/ݬ܎u)٨֭ ^/$EWt#[GH34aa5H3i*I)8I㎳"WDA֊a.f WxTps8{h>;/޵(bɨ͜z?Hkb1ny56-(EkqZ4P¨C%Fn9 `MLjiy:^ԂFbw`k$řZ)J_{@fr#ngn-=sx'?E%Z"wVkA2`v7Qǚ"WPb'b͢[,;|:4J5i,5^Cco(Cd'7,& F} =bKv;p-X2%6a _gpX<$s@_bϳ@Pc>u#8=Wǀf87Լű>Y~×}]wl富w~Q !Dx5犢8nvbQ[TW@hbI&"BZ]:yu pb-t3YV/dS. ōI]]($$"ZlTJFM؏>9ш Fۖy e= 8Ɨg$<Õ~o:FaG z>?x{"^HJ`C"Ksɣ(\PuMkDHߗ*pxplQ_+2g8}D:aIHyb9<#~%B̸L53%Qn ٦H%Ԉε (EGgA9!( [ӕ%{ DO2Y\wG5?,;qB^V 4r ن|@&"Mt7IȜs N0L!BɀfO2>$vP_8nTF< >}b.;FꍮocR2XPⱏ`Hp أ"d/22-p<%/p`|x?J7`1M/ʷwqg^HY_Cq* d5UYW>3d>qVdPy K'yACMh(5@junE^oNWYp]]REq#гu>% NVk63:g14qa,}^-# ]x&9Yl}[x㰑 C eA-#ma̶j 5 ڂѡv) )a [;3 t A/J;N/ʗWj3/J)7CnaH>HLls Ѱc&qp0 p Z$HkLgmp.i߫ڶ%z05﹭Fl:%Ҙ٭'F`\^\2͔Vx8CǕ}5a4/#eUzۂ W:Sf]a}U{;֞l2]X GQMh2DR))1!Hx0E4bn R)yY?|EW3T 4Y"Jl!PΟ<!0ň3ZVGXtc{衊a.`n2o:^AZe}RƵ:o6sO7Vn5nPl7=5;cD L 9B58DR>w ~ ҿjYo4,:HWFakPF wcLQZ}=018q\(N||zPOFΫثw}ǚ5zq=Y dt>ˋ'z.#S$>Q]O`hJяs1T/tF2=ǨفF#z.PEL'ט) 35^?kq~|1\ 14t)pC*\:9 z>cJԞ#Ǩxpnnv#h-a%pM↜DZPm]Or[㞌k#0э#t8dp-H#M}:20@pNw!}uۖk f'W=j2Sl++S/S[,S0B 7ڤk 18#m"D٘(b "TmR*ks:0@L Id8)Pvg7|SWD Gąydax1M'E=o-rz<#{sڨǖD=|]jN#FaRh1Q0,쥗^Nx4g~$Nj3[VqTC㺥5 [Vi8S*2\ZP0vBD_lhf4k&֯\% ?`;?κ3*%̅!l49o24( vS \S~>Oo"\`4ְeB]j!ԃ0ސuzB\ Le핗R9.68a > Ee#}fOņ!E4u, kS8)"Ot1wv,hdWcu! sR\S,hQ(P{LOabDG߹0l˘xm5Z0׌ʼG8ʕ*"F{v:dRHup '鮊콸57V(1)l4lz}ڗ+e\ZQ{r`8L;/FY1t:|>Yk|s)a p-&CRe&hd'2:ZC(lMsŹJȒRlxSBi+ߑ IDAT\TXX1  C⼰Z3S$ XUXյ>k}V.j2hɍLIPHMeޒF<Ӈ7oذ1T2\k-|j %EM\Rp5Qdj:-S revZZ`!:Omc#ED4㚅j0 'i< zz #PJʟMgGJHTx\MNM&8kH^`ӂDXdjȠ8Үќ`)M`io[Qa`F`f{|ċ/L-qPR M 2-HIͲԩ/8.<lm&GNDŽ;6n7׽ tT`X3Iu`>&J"#L$*>h PtV0b+E][EV`AX3>pGu&։CFbn BTpL)k-f,1"04hBtBZCq:S({D8G}dx1g昔!B5R⻴]P`eJ7*%j zfzǙC%Ҡo#;7h &6 W8_Lwmz)* yׅQ w+7H5}F˲ &ҝpCpX9?dOQ"̚ś0{fvyΣHqe_~ zKb|e8&Bz4uTj 9~|y'G%UgWTVre f\g>@uz>pƟeZ Z@VޒO cB82HDC/ܟ -2&^LxܭgꞞ9VfZqnBqM:,vAqi6' 'z}s4i :Q àC[1L:F1 D~FO`@J#b : J)x ҕEH%[("mv)iۈK}O&ڈD:1FxPN*F]-.^/RIg-7ǎfM U;AøJKV(JaS, W-Ra=GyřaF$y9BBhZE$d x16tc܄yR5(`pZ=OEIR`5 qy<&0~rhS aӺ_x!V9- 2&"G"( ais}<1=ydG|WW{ ꫯ:[o,E}2=|#60nv?*K(v P6BGJwR-֊LN,.^ړZU8se5);ۉ47:֤YOh떖MsQZZaK:^;{6:)bEc99}suT|VN$PS|vBH(?:C+V-)UՍ$C-R'OX"k.pۂ]7W4Zf^hUz }TAFZ#3RUll`u)%yŦ3xl Hាh/+"_a 9BUw WyHQe4$!"t'ܨٝ_KE3n={"A~Vuuч봵NȗuñqbbZ)JM;㛚oޣE(Кa湰7xG4t>|Y7R|p .dI5 B1ARwVlME1ãް'ֆ>,-*sv+o7dFjZ\(I4M/g3_u7o_E7ؤv8aV,k<4TڮSh$]jaLIOt9c?]UQ) f+q8\2SnXS98{y\bF6rGl lk?~X|n'Q`·qJ`ۮ6 S ?0p>Kᮊw%/#},Kڮ|zmeگjl6[8^<|`Yex.=o]-'|1{)݈3@!Euu2nNȸҕ=."Ź'r+>\h4E굖k)`ҹG'O48r]_}˾;c_W_V{- ٴӋKO2fw|=͓v>X:H]\_[z1zgQTX{g$d0"&l%Exm3F*H$|<VXlmnunٶ6㑱ÒުP :u;;<fս+oY4/E^ۺ/@W0U9F;uեed~MƦC4[jx%bfqTa5+yMj)y z·(hnjь@H ʠHbFE mBE:z'(@HR 0$B#@{@ZF 4嬨`Ddv~v!Ÿ!| h 9+iWuFCY""2^R"F4[cʏf'|z$L|7^' m!onNHvgYX(Aiv3N׮\H5S/&S(|2&c%tV\TPF(;gFs_d-:ǸɀT8:Rw#QݟCookݰV;1 =u]a&jShV vj=Zg/>ݾ{WOp6 [i5 (qgF$8+";0BHW@鄲(j?)#)FH_fZe4Il hs>_ƼoY HwQW0G&%:Ja?̗橢c2}N|ɪ7b3Xz4p~[N們3NCWJ2hCB`<ԱάI k;6N-ۦr2`#t xVjX̽Or&tpȚLgMca|qL-V [_v#2fS7 "Ȗ apjݰ~C pc8e{d'h2%|'5OTu㽽#+7 %GV{i:O{SDTd$Gz {=-Y­PDfqn##r!: c 9xxx&/|^{ -K;W[Sr uѽcefc|908Ӊ%HDgebJ#i*6\8r=Y|"#y}Kp }IZrcO+oN}ƾt)n؝;wZ`\_MF-ѣK봺OК݁:+!\m0^э6߬eN?E٫/wv=A3)Y &NevRhYkL$8AV>~xj-t&b Ĥbi*HS7"& beX} Ҳ\(%9-8kcp̀2Ao"ކ HdW~if[r:)="&"H$ [&"&pSxB è֬ C`3u)iF}_TM $ 5x6fCs%Թ(AbxV˽E-XYPvGexHԛ1V⌮˳3ya7:t456K"5tǪۆh9PX:RxԝS]%NUmtp!ųv0>/r>8=k(۷[J;N iH6`bHKBFJSf5nc'|n3$Q'zc-3Hӿ;WFMX3ZY7+,Wk;wu /k&b$Oh5hdl݃( ٍްYcWګ}HAg3)|bjf׏>Gݷ0 z|g/t{EQD ZNf#vleͭ|\@0 P87`XlUmIMWv~XY/,;DYJ`¥G~RΊ8hohU=lI>94 EYҖ-Y9kHU[[l>^P/QR:`nWĩZ͜'=GV cV +c0i|`˙$/+5 сa`("CӤI Z 8|5},c21(]d*T9;DW2 cyxǿ<{&ЕbljsBƋ8>8d_R[.PVӥ ע :u %ksScDjy0:%;~{ho} _giiT^>efc[cJT8s)}XN~@8 tߓ{d@U91yvR!%/tCa1ku"GP1$@Ţe7lsj~prs+ҭ~=m p^.f Z?V/ X ֌C֨QUVn<ق'2:|Ɖ^Nۨ׶mp*'{0<v;vT暨y;:Sf;Wbڣ=|xgZ[ۨ߰e^dfqǬlXj=UGg+;Z3 ;WXd@j' [Uy+D~G z}= `ѵWL‚56[֬{%M A Nc èWAe*A0∄:?q KFݶw}ԘKB@TV%ޙe15(hS NsÒȹ瓩__ᆲ{C1. iN EM"=Z),iڳ1$Nl=]R_[?6kupsCW-pD8ӆI1̞3'6nZ?>: b ^E]'<~zd t._R-" 3yp.LdOI.[}B nƽ5=[\}^$%͕exsOe$ Z[[.USo^m3,*(2\ mjnՅAw~OՔ;(Vú®ۡFV 7K'ڍñk`4S8TU: 1KLUO=2\G@!&#a '!ŇYRN2,(ع ZNIh9,Á}o ,Y>w e.t/rd~ "l|(c8< Ze;~v;8rLbئZ *1ץ{07``Mֹ `TiU:+9ll+:!"cw'Oӝd/xsQ͇{QyTz>Ѿ|ޒ!Ecl2MCj*VƛNԶz1Tv7Fa ???TdˉuhS 3T3d#|>r<"CZG7?´kȨ֦ǧcy>s\!2iAWL2qL5sxgL%t7ơcN>vKl';[pZI^F6>.Z@dYkRS6Z,d^ʹIv>(d=<3`#vfmsg5gڍk;7?}D.6VQa.YRmB GrN-N5FjA"'=ݼYX%vЛx_KB3P'ؤbUPo^j1><55a*-Llle+Щ(m2&aUJT*&~T4A1 &HRWWa t"J)"C шW?sp5xUq4aX]ph9tMUEDIۚI= 8[ cQPR&TF3y*$:l傃B“%X]|>2k5 4CXݤ4.!H4{羙+^^ur@=wq=xHw2/ Wڸg͍=  B,á C(Ng z!"J4S@qBs9Di=a>x7O{罷k_P ӈI8&_?(*ƍK> gFI|26t#3 :"DTK .Ïl=[٣e4ZP`°,$:)\|Ws;}|Yd_LXӱ^wHF?Bf jƙ} b>[h1 ֆuZHmDޭ^ݎnښq-E?à 㫵R}`12 GUnD;S 32Qt v(enrn.Ut{f8UQ"\i%l@qvL`*`ϡ;g4xfvjzӵ@xkIY 2N g܍QgcR9oVCFZmr02Sw${v沷5r#kuy(?91RpV "0MouPp$4>G;)TDWK70[͖Y' !sHs dx.< Y2Td\^YW5-*ea`_#{uvʤ<Ɗ!PI+߲_~bPC ˽Qt  2{?e"7Z/SԢpʣGRPV54a3Ǭ Yn2 M· .% ;I}hB+(PҢÈīG0漇:6љwly9Eh aZ%(S^^=‹Vht@|pߴk;Yz+m(#;Yl^.5SSdREV^EZغlu9xiþP sGX>Տ(E#>CQ0tyt6x)LV:ۂbk kffraS0 Skuu-f.3{={r1bn,*PgD{f# j6e?kjr OA͂0Ç?+'OU4Ң h-oU b!"6[xJ}»Aw ݞ b%+Q< a&*nZv,CPݑrkuԢaW^ڻ|#aNEu| ΃QP⧸Jq<0QkIGʍ`c^iVGRey@0:.I@RDIr}7s_ox1 y;8r:Sv 'G`&0%{+Toͬ#\\"fPE2z:1\K:9RP1)^jV/V[Koܶ_Kg?+j1RdI )+|N)ZdB@{Y0VQICʛnKkDIjo~;:?~;m|huۉ8pDS Wp7[.S6fTJnm93-516 ť(N+_q8q ql5߷P5mҒ0Q}"`(-%xG,#>˪BHvƐf h xoΣd𕬭j\*dP`(bըY^׵, ^kY$z>B+ HYDнNҵ :z3wqk5_ ~0DalEN3Ͷ1,fݲdq.έ#VeBha`~qDO0Xz>}r.~s6:P-sz7"fsqnJx9,l ŗ7 ض0aV㿙!hCt1et?uj?|Ӿ!6d f,dP9x;11 e=p?Bv;|asp E/YWS ElخV Qe[9mi#Q׿h?֫ Q|m@͞ޑڽ٫|N[YvtxbqR ;ʡ %֛/< Vٙ}{xh;4jzy%v, thQa[JUjG !˱Px.׽ h'6k@% FQ\"^d"+>Yv0Q쁵[m^yEF?j3{D ty}b95`x L 7x+HjtƜj^1 Cș3Q#msS7~DڜYw!\{x(-OʼِX-er4D1`8F](rC sqpMbuM&U>أ5?g/}ɾ_zr wd}xq!6БAuʳ@qbjU @B#E?ť֐qD;@dN$c$ m) p60M7SESá(SlJPiIs9A),J][ "f^nS|WYݶO&v#'6GF- 9E;Kg[|(q[Pxs39ܣ:!:4bLӍD*_z,\۝6ȾDʰ+T8ԞUiYG-dvw}%7Qb/=FT$2C*J}?c3w`5cKJ6c_%e&0l8v;]1`"9cgzdwPRH %#2ԑ+þB~3^X}C=>z ɓ8`f " 杹 {+XR/ݻ+~& ȋ!MNյ}߶QȎ^HvWhxl5-u L9\F AEӵ˶8g-{ͷlzbV&jNjö0KZT]^ѱ}U䢪c<KA-rי8 Թ XfjEfӋ%ʚ]eKbt;1LP Üj)/g*]C5At aD>{j j,o&rp;!և͢ElzRϞTsZ0!|Rx`X  lUq'C)s햖 XLJd#) 7K%#F9j+U)O"Dx/"lbho*܂r[HB@Q KC9ƅ M3BT.0JQDWzt`2Dr.Ep׎ _U8lnT)B"Pڂ&rtt)vxxP(@LF+w_zIBEcpdV E "py((>%ɷ*2nhN;[oh?3%r_z)Cg Ԛ:g-AQƣIc`iX,)ı%-S.WuٟɟؖH.'374@Avy l2^Yn՜=8tnFϲaw_agWj`~/m:m1Mm;jgݶG1)ҏ EDF5@GQn#)+ҥEl ( ",`^M8J]c4l-mTmNH&u1v4mǴ@A'Ca7YQS4J\]E|pp0F''1\ y+˾UMĒu FZ8Z%QY#́,A6+@eDII?P<;2 *ʪ7:Na&I 5gI_FqTeB >| z2Aͺ;;Z,O# YM(@~ ngHC;)J6땢  G\~᎕scX-l2j8>X( 6ڃKOBJ 88?!8+4xKEsLQy0UD8 L3e9QBR(*^.1((oͽ-ɣRhN_qùqv0R/7Jz펊za\7UDssރCP'_Cc_^a1Q>Z*Vh~(k{shMjnځ=ޭ :DRTWyii(M3[;?aJZBi)j2 Y2(F. MFcpȍ΍0. uuK`]eaaON.C;+:Q\GJÃ?s {7{{p8l(6@s"ܰFyfLcfn(-RiѼ#FEq ⯆ 1rq uF vqGúě?!sq W;c Df;Yɪ:gaiK>ޥƳr^~ߌ"^츇21?8Tp `m oQqRKnJ+"_3 Y9$p(T:#K"GƋ:l&eܹ>C'JQ4ƑYbQ<MҦ=y|f.[w؅HٲZ׷~oh6ܳ~|پc_ѿ?r*_ۦLm'jȝbϭB2zDo׿h_tjv6@vZmhܬyt`сqJLC?cOepP9`~,lYKZMk;)!"}e,ִ-6lt8>DwKU@ ^)(:IwF:쒅'@e+z}zjLJ}׾&@D౎-Zq$|x0ե*zзsQő]q3-DO} #F0RD3I*QBo4h P1 alͰ%0\OH4 ~^5Xh18YZ, ؎pߡ]RsRDӍs)C@y)Ti9΍LiWx8: ; OZS Tt <5$Bi٩#" 2Jm{?f"繲յC*h 6h16kj@gjuAOa❻w_W{ }WPF:] IqV<)x{Dxư0D!ϗskڂK%v Cu2ŗ_VyǾo:D4Qv-me(.R6݌ IDATggxl}vK˟}FD5}7z1ninKD۝ ,7Վ8i[a5uv0okg>%740)¬a"V:=CAki$@C Mkc<6f9BaN yO\-à GZdY#N٩(qU^'4*G:N 2W3 L滢cܼyǘ` kew[-ytjɵdp ,| Cz F<=qPPzl޽ִri}@ =Ok-+Fz%x+IM2ō Ay!;[0/>fe5+2eS L n<;?D2<^cP&b U9 ?Rp^T'U`F%zr;-7k{wkw ';?o[oЎoܰ~~鯭w5evIw M "RH"DJAy"B$^%H(Bh$hu3=6nmW޺ә}v?t{{~^}qEMAdSk-عFܼu3~?'x_?7n?ol2hm'G8JaFqUxgbG+VZoeY_Z &eP̷9[Z :6ӨŠ׎!^1f;6v\qj]Պam;嬖nZ2a 7!d2Gŷ{/Y߳NTEF#ZKB'jh/bFu":H%V7y tJAB12`nk6)mKdAJ(\Bg(*f:s Q{mz X eq[Rd$t8h(cJR'~A+YѺBN; |>J6Sz@?nQ/h7wu3 ۭ͌[7c,EKAmݻ1ΡQSmO?\Z 6`aD'"*30_;sn|O(݀ gt 3)RHʶ7ށ9{f#գc05lͼ cJ}I[}-~'2^FONaVRQ=ʹ!gm abk ^FjC/+~=* T1Yb_l<%Xrû7弈զϠj6`fu3+tk6Z=U5CB2Xlg.+%2XĐu"Q(g) Ô㘕-#V]Dd(XacZ; 200.rkD]ĥ3UWa} jo{͑gQ\ۆLiٍ5NGS6ƧqP͡DةMs=: -6`kV(#gAxn@6{igtq{υ]wȚ}8䞷|Mgcq8Z׍Pc5'B֫Ǽ@'8X&-ϛi 2fϘϴAцaoi &;̋&9y뻜ck}%5Ox(NH6:eE#>#jY <ζ,uvUɴd9iF,|w(Pu4ɶ<2Z:,s*ebgTzL fz :Äa2*4+$#r<޽ ^ڬ &#u$sV<=~->_?3?ÿތgIkH=wN< a{;~x7b1,fKTFK`IN7OJH=m8GO+Y9{yU_@`rגݾű~'}]14Jz3cviȯ)зi%a-r&ֳ8,XV`V\g3cP6ၷGJî-'dY UX\tZEӒtX]BӶa\bǢq72|ZFjH%XD(KBPzQ.iTQM4F,a9wSPB~' `hY"ndc` |tYzLn6nRƮjZLT*[f72b+M{DWzvf\8𞁀<`ty $@FX,#we6 3"?* ospJ%e7/׸}VF/c08HN'7`]3aI3jw+4|ҋ%UElq ^GF̵`2;崴zaE- }0>>w_&5O'Py;otLM"5y&kgV,X2g]{\+ (>06r61Z5DhjS>OGY%)`sI;neƛoa|K_k}8{.^z<+g`,zMdn dyZsgvzجȑ#d1y9/Y~=3ahBJwlwb4&%2=gBҊ[٦~뭷ŗ_ȴ\h+&m=a0q-Gci@}ZEJ׸]{]37JJYQZ]FpE^Z6E7TJN\a V{gZ Q\a?v-eإHQg/K` E^uR-IOg8kHغ-Sb0(.LFkE{̹fib*2PYs(qr%v+QƠ8::L*߼{!>yϨ4L&\{F*08|BxZ5NduuIqzqu;`h&4щߏ/o_Wⵗ^(H,^l-VkyqI<|(6Y߃GCHH^`/bnSsG''1nux]S)\r&+Ō"XvVt>=THepjhJ?f_3DP1z0Ɨ]˶]"θ8kԊ]oc^ffڻAHZ1s۟-BĦ+&B:ZU3[vҳ"w"#u5;Fmg{^8&_6okaO[ϲEZN}/9OJO4R=߃Rݜ+6y%ps/\u8!"> n 0%{%.rUƹ,Q֡'} y2@Fz^^f/^Y :Gi;2d Dfŋ>M^"614i<-V<xa\\OOeӤ\;\F{X݄wM}ɳC"2)J򁡌$FN&ڤy~uE+,Q+s-(d.S7fV4;Ô G mfgҙӾ irȜwAaq`eq{^B77ď}SG?Qacg?l7?i0.4`Q;htџ̒y2<|gwxWXOmyvwߋ>o~ifnbX֍lQE ' ݐw4dwQ'NoI]ϝ.rO ѵ$iYY卮FVܾu*vkifq_NqIYQsܺ99fL&elV(7YFB(?˯gƠvČBjqJ!D"Ee]Ig>~$(Q_y KWl@ڮf:z&Gs"|AMlƐ¼XDYSR,aBxHEOnJ>km1JK{as\hk(5JRY~E # rગ\:E朸/CAϵ)eZjy0xk~>@U88^l <*5E =/hy,gt=p\f88Hnf[|_oč7LH BָT\zQE1)T+x"NP1ɱl}*wJUAQ`K}_ğ8Wj6`|V+sxLt<0g1|~Qoch}8z(һ8!8yLgm:kԄFkfg < +4e9 'JW̓S$&[_+(Ԅq"A,)D6.QގFJIiFM$Krʬ(C3뺬QhU/(= #uK8RZ:FJa0nf̜`a0ɸڅ;ޜ:)KW)HE'&4#ߥG{ndcgAgge ,e"Ȅd6猜`t{.,@Cdx9˘wd!2'3@ר9bǭfB'D900ę"?ͷZ wގw8::)ipZ|֌$-45? *_ǁj]6Cb?ef|&, ,B goPdE$ ;8<°VsSVaڌ˦)1ȓ{UF1){3m}̵Tc{k&.:kg/To hM3c;ƃȃSа6"frίvA[Ԁ1'{VWE|__?ǕjeC Q, *:rjz1zT Uc 80J(d"2q:%N"6FjUb'Y O?_oη1l4d[wK>v:'OF o݌Q A&ij؟a3ǡr$o]ᆳs(ddD 0F}" +iQ\2ݙG‹ݖ#+NR"hh61z8=AtDt뜏dl9^>=aFYyNFHKXNRL(p9ncng&!"]^?K Nbi%OS=Quɴ;Zӌ5q:Cyw( R1bɶb\iess V"&kgMXJK[w&KLJfinp"|rݑ,WY8KV {&뛍|*ߘA*. 7g<٩s\&LS|[ Z,ɈR_?'YV,ȯfv IDATmVhkN$`?8:ZRٓhe'ILe)-RfN1;ovXydzovW"E^mr|-$F!܏SC:4QT %r|]:0+c:ʘ&{r-c!E{{Lђ{*V$yMs'k5d!-8,1(ɀ gs~9^{-^x,(J6"U1Y-rLy2{`PO@8:8Ϙ#{,gX"se4,~o*+jki̛kk_[`7ۏY[-BD;(;C(Re휑MB9d2;9v}(dW=i鶽V.W^`evvT|O?vNf;<;M.m#^ynF~ =|qvƒ؜F˹pSaOY3l e|r_mʼYgGTI{_hnfRkؘS px 4%ZsSkȶ'a^qM1ԽlfP`h()E{js(vǹ$jcf2>o%(}>"aƕ૊k!m! %* o>2>[J| jR(g$\UD}]{y~UחWi3nbܹ\k$5% er1$nV),<## f0jLx6cݜHv{ܠkQoV>'5`7#JDD$i~'Y Y؀Rnδ`v4]lM~gwK< j0FуpL4/ʬ//W^_ .ŝbkh? %E< vMY3Ъ섒 _/3(E[Cvr<njs^&qTϜ %}u5* 緓L`tzg׀aU J\'eatUhNK4hk+Ms(삶k1&KF6F6RhJ̨l3'ϸɟUV T)FtWB78 Y#̜=9R4@ddo-R$ CӍj II|CJx l8Z4CcǸ7.iV]Ɠ_2QvxK C8F[f/#XGź q Y@鼁ܹa*7fs$ ש7Z}]L]Xݥg5n@7*|ax2S\mUnM9VGwcpg9b-CX1 5RTİۉ" Xf16N{Q^#K/isC&3_^o|y|EnUmOn[cztF}ǃMjqؾEc)78(I^ɄėCFHgDT:zGmٴPY F 36*>n;Iή31E0Da{b eۦL<3 +ǣy6M\`IўYC%kxKbkhMZ3&<"&7׾n݉?~!w7{FsI93p!VW`QqX(E 8Uc5nEq3a[֎1 W% Z֮^ V_x,Q/BO_'4O<}0fU<}(#TᦾlWqA(s;9o(n}΢3iǑ4eei2^K}޺pVgOG9Y72]OA7;zHg<틡b"ftq6=cMYL 8v0a5]2 a.<sΡ^7P͑sCV~7R7o뒁&2Sqӈ S[ 0jf豿da&0@RD xXxsZ8AoMY3 $e;e[ҵ"/R\]StpX95 W k #g:fs=޼ Ҳ46%ժ%?x;>/_`?'xP`B)d !|KCֳY7_9Kȧua)[@(6f9kzt6?=_\28\qx49{_ŻĿ{2>Oǿ? ~-ߊ?o~_w&{n|c;ݍ/܋wnqq_8{8,>-V-I噤ZuQ6u i/2 ?]W f|۵8q /zQ^]$BT=X5b+f'#"vqdZjNcͩ5I׬DVDæa[xR1 n. :MOq3. .(Ί}Xr5lrE"݌7[V mߡR|8(Q:ٖyB<*9 4o޹l$ ;X {bO!otkL#t?ȉ,XkFu`)r4Kfx0eL Y t'$ LKv_07Y],gn2?z%31< GҶ}_2g%k;MUf8ݞhjBWo!>JΛsA'zNӥA/">@MR~V f#äR%ŦJ:ΔV" Ъ[;aYtFzfYζ }l*KzF2hf2Z=9(]6 ?~76&j aݬqc|~F\? }fg݊W?8s'f 7QЭRF,v^lw8;/<Ų8q3xz}2n=7mf6~H:ρg1vҥgK֡;]YrXlTh2Q YggƠfhR N1./FqvVvJ{); l7o4b4ej;&#=LJG%J00A:ݸq3fY1tPࡹ:RY|Tq@is9g,}Ԣ)"Ky,$9#)AQEk#`Lc]áj3NAu" YP =Ք+RRnNLIh :v=('ʅkn 餴4)ll- TH^Ւ|ޡT%}v}N4M*#iWp2~(6xo/דY~gPWSaf〓T'D7}H CƯe{~D]J_?+ـfT24*ÃqC^p :nh_Ǯ֦I'n޾VN^]oqw B'?̧HUv`Kj["oė>x?x2 n?/n޹Ì<|8$`s&-Sr Ql,C!q² vn/XV nq&|ltJkQ |0%Nio=[pQ&,]h!j 2EX&Ԭ{t n,{N>Ius,ftXdbeUa xbAq0Y.ۙ3;,:Ε3P~. D,y q҆Vfl%8Ao/Yh%& b0dƏ# lY+3`s/X*Y,VfFg]G0CGV0?)YmZcV^V<(rV!bzxA'iH2Ҍ ӂG>YOEXcI5Ɯ)6 N67eaY+C9>8LdL _չen%;bLf8t#|{:R]W)2V"M/Sm#v;. \wq\gYs> b=ݛqp:E.Snxnyq.M!z@9DN]X6=˨YQ%"j}76Zv ghAd*ڢ203}OfFnLkd|,?J9 hM.yvaĸU"up$xqK0E&X I6Mg8MlE4T(\7ftG΋fyq,B:"ӺF0[[JWz{ uF<|^?~Nߎ|oFV$MxDb ?+z Z,=!HU}Q-M*i#פdewu)HHfJuBw3cPjC'ףd*^F_6|bЊ|ߎ?L)^\gd8"l'hw t)Gepn^c垸}imjkRe޽46,U+QJᆍ5`#<{ʅRδζ[1 hy0"RH]t{g.$-Ã4)fi%?έ`T72#{6>k](#!a0qNǍ[:euN#NG_r|wȹ?>9t0L:"F2vLgl}yB:0&J.*N>ҏiH/sJ{=HWWE(/F6"OaN"a1mqj ;^ufF?~eI;N|$+ep ͋ab G[mݎ' Pج#mдena* _OB'4hʎrwIy2!,ceʥx84-ʹ-'qf^)*rNylu{XyJcӬf3qR9F!*c]pp2.=zz,OgႾ,V5 hYCF1Kοσd AԒw<[$ p>:,A^ ;,uNOxk/Oc E MeƗEG\-j&/~jOlaVoԩthӘ'?DqwE{A2u2CC 2A_[Cs Z(xol2~N>'Qe}4>{7No1=KvKVmK6_&vuCU[E1FLy0g/gnUQ8i~e#2%s-\˪@QtFfW4v >\6dBxөnTڹ7gt2ڝդWIá*=bYN4 sW[YI%ث.EE5-qe<_U3IfvɄ=d5@2`y~G%EOW:9׶V0H/5niΆ`׬nɸN{-9mB=^z.%{cpM>\80w3Wg(X[̅kb|pni~-1=Z3 e+#,'ge?~3KEK|tv^^5b0⛫ x`w xEt .,&6vΙȍMv}崁D0M𯆶Zr)1)9UJFlLk$^SO7(;Suyʬ`>f|Q6L#k8.",vJ9yOɅMR~B<7y>eoR1I~g811Zp, 1.x_țvͽBRT B=2WRDtϑrJ#\/,*x=ܯ1"HQ$># *Z̪p~w(+ ~΀~ONoXC8{gN6kZ)e#מ~k7ַkId4t:닜Yb7oގ:YݰJmQi /sdl4wL{'VW6C˦ mf|^MT4 d黆 YEv Gc3YNcEӈA/ X5N }uO7۰|Y 87h` nkYyp790h{\Ҡ^3wt,JMsNY%gsv4WXN4"jB6d71ӔCF-bZEV ~(~" oxl0DɤmTYa ?s%jQ+\R6]= a9h'J_ً5'w87woɽu0(M?zLs/i9{!3_c389-{Ǐg{<}.@u}BkF(όX[W$d훷~(#E\dW,+z?r`Yhٓ'iw^ %x)̊h^Lw'xl] c#4)q-E(Ɉ{Ց~ƅx̚']\蕡WTdӋW\Kp+[Ϲ&K:7?lqNl^syRW2 |/apͳu[͘6>$W=Q1U*nS& S4nFe̅(>}I/)A)8Uy/w9'S618{a/sJ4?[L_ό:72ƍty;rf/Ra~!o.s/{&N5iHbA@7g4{TŪa/ۊø)V3b^ T2q %,'9WYY2l+v.x h,SoΛ>{f ;woRNFF6ZM<+.x\_[6q.ðD[1ArcH|> Uwn)2#O!]3QavHE̽_QKY0yPahE)D+z;j:5Z#OŸFX)5  bDl/(m)Qrm76r>c0J̯9yަ`!x݄3 :DRcsq=İ͆[5U/&/48[]/WQd{hJB`*]lW^|>>dTH7Y̺.W~^EK-(kbAHEZyH㣐W g4Z1=txf:Q FF'Kk i]0!33}p?\6خ4);3"W(&eu`*sfQ0հ%~\ H Þyyk*9Sw17# 3nFckI#$iHp6@W2 u֭u4NE.(zc_@14WyF+<~Q6z"8PZ4+D0˵}V䔬v4toJp.bO}:!U`¼-^>Z,}Ѽ15g^ pZXǟ^ɩU2T6d 5LG]+sH s p]qz{={q8/>+-߬.=_ς.$.W^k 1J*",dY3PȝsUtE=; cANr~9| rud!)a'X'g|l:JF'V^mBdjV7̥yV3cP3v9D&)bBdF2*B]ǽp |wGG%sϥ$|)Y rJG?N(NDE}$"6Pwwgց8:.^di/rڑfF~*Q8ZW0E;TW% vqy=lvݛ}㾧Ÿj`R{ Ο0j>9fJQL)$T ,a?WN1sUk)Ũp׿uZOTF"8)pF'|(:nbG(^Ɨݶ)nR1JpTU`BsLβGMb+򠨪Z:́9ϝN]ޜgo3QVԌ#u<]A\QkFg mHnQǚa~Lc>ӣO83 ;x8OB${iƊ"vʄz"%JK>+ctT@q(=n.[|;k. K4'Ӝ9Ia<}H5:8o)\qƭkJZMl)6Nkd:e:B=V.,Hbw^ae"ً hY8 u @p~6e>uiAƤ#Y1Qt||j(cG'7nd*]7z+>㓟x>#9.+W|`Yb1* $|'ez$>tw]C_O*./ս7-FQ'h9TOHjcdmIP44<㰐`6daA +H=Z~UKGs5p㔚kX3Up%5VZuMyA*i>=lY $R,G7y3;a'jeϓCP(Ή%\ۣXRvodSjJf ~b?,b0n%$.80 ҆wn)WW")810E%y=IjWt$T<ʼnY^"u0c8T=ypQZqFD^2pX+XֽWX|W7ŅJqk n0'^s7N8w ٫D✥"Z(*mFLdcՅnO:.}p"G4CRTnj1WVDK{z-nDd!.e[HfQ1!P 6y".ùQp]{$S)AFիhutߪ1 xT< g{nl8xX{f2mlsȈvZۨgIl륕A^AQd0{Db4hx VilVڽ.Jʙ4Ob<hKlM"QK: 7ƲhL =|qqgy0: $apo6 ܯ t>3[ChyPxݶGGY'e=6"ЍG٠e4^rA,pd  Ө'ExC{~w:'=pxr7x}S*YӾNYG&ͦ s- }׌a:1M~ZFB@u 3Kr8{Y0*p;rr^y!;1'̭LYlO$~\.\W>Oz4oA#9Z"4A NY=:8[GGi9þJ~VW2Jփl|<^ǖcZEQZds$ޓjw?_dV7_ gcO|qFfF5vG7t#ǶVOɹ@t,DgЗBGќr.^3I4{[Ʋ;l n;t^ꭠ[3v(tR^B[9l4^ʋ?{f j" VihM}L E)`%g[̸cS_]!Z`lmll^"ɠ~ Jh(baK9~*)b:> e+@Pf Ul[p1v6x@<@ uY49:"bh+贴hFνL4@{׍ZҼc̈26cՕeF㱒E-p75xnMS^7y3ݶb-4~9΀4]\Ϯ_y9#pzBE5m\Cr}enu+Zm_tj?Tpۯ)2 csM,^1?%[ө/OӬgy.1`6;t;e^''G);ǓQF\ a s|ܹ};^x!nݼYڈWtlh$AfO3rzz==j Ңa/,dsB&XeСy/$#<}v/S5?WF JsA"H>[y.b5kz¹뻖y~5)dI8ȅd@G yW {M5>cTJ)eȵTE{ q\& +nz0*HX u`yD{ϙ<\}aIyv~">yq̗WD&ɣK/œkxY\>5J E / c7ͯ1_ls߲;fߍ!/G7xrh/ŀV\˃643g;ǣJd]{>^URQAtgڷ٢оx)u1*H_:4GϘ5mБ,nFOOcPǵͰTøABMX7|;=?[{TkRœ ߞ(^>z.q'rxx;.8 5A`xs n M΂<sqq-s~y~q 5PxvAD- // 䁤rE*/lJN^Em=n4/yyE䭜Jw_^\4 q&M>~.Y2b^.TUA+tVs2O?-Q~~ri"`8Y9Ƅek`ZJ`Wq\_'H=lQԈ?<>inz)dMw*tk.fe9(]d L4{$Ue\I 9~VϟL l˹%aOeW(Wݢj9 RR뀑E~NEmSˋ m[' mz[L|9 뽝oNVE{,R>>{pxs[#9'ֆue<=gJZ_s_e7K=(YF?|YyrZO|G僿Ayw[56k<߹klV#!!8!b%ў̜/ MEX*lQ9 g'!HaD88R'O~gsP eb #Wl^@TGqh^ҽrA0PH//#u` 6B<*qnmZʓWٳ/Z/^~B4mwZ'5HN+_S> =/t%nӊrn#` H9TƋzsI9apt6;eȳ(681GYsdXG &T(ĆT^5ζ]C9˰{Iҡ="m!R F yNgO(K("%c_˳H::ls+0/z*cs0k 'э`|d-(Axp.Sxe֤wfM a\2mq.˼ ~sCd/C@HM`Ƅ$< 2 aH,gDJ$ݔ6Δ9Dw70 BSwCo7Ju+nܴON2U;HB~=QfaLhuy܋C9Sd§Ө7U/A'+-bV;VJ.nZ^~0XK'{ N!6Et`dmF"SjrOcZ,b(6EE-Ids$'ֹ5{ou:ѲI;~Z<W_<&va}IboO nWe^]d.֡[Fxvd4.o!E3}4=':? LWƺr!/0 o15=8N^eN(_|eh;lCئ^b޿:e5UYQðӗg?q[oAo

    |ɋ仯V %#p<pkW[Gg=5q#(kӮ V2Ƿ"yx/,D9LtxE<{V. w(GE6cZʰ)& 5K߼'͵I1x!ˆz{AdLx3q]$b~JCWkt-e-X7}>x?ٚ! P.{2EM<2@8f7Y6?8, Yx}5 fv}r/RBvq _/ ec2B(Yd8bPk6erx&@1glT{1EȠlL:ǁa:܋qjHPlcTID.co_G ラ}+ylZ."ƣ>eVgzCv}(!]q98Z,!Sݽ"+֕>HDZ}rq9F;w㑗Y-zEH+M7CMTTQe("LVZT\AܔxjY * Z<֌2۵)i Ȅ"%$ )Q";w=5YxeS确F/\¾~xi5!r `#N?4 `]ʺ[*9(9oUޭs$qfeܕa/o]~ۿZfs+9{C-Hp):Aѐ}OB|a "0ƺ o͓>n[@UKT-ĻcA o>IFYwP{5yejUPP ;^Oe,P7~l)Ǯ\n)#{r:<-Ox#ܔvWvkVY.ަ@ƇrbJ=&`$ ),xmf$*V=G|~x~[W%cxqؔzjH6(6Q㋋ZzpfPim> nZ=,qB̽-AvƗCrnZ!lf]Kwb֤%ToSJwpʧ"$nʽS$b8) WroE:0nl|Rt;wS!a5Tp{0 gF#!!Gޚy3<'J}pTȸ9ЂEye7H<4 CB+s`)G{ %)l4;x٫(<(П6hϠS!0 0vY%ЫV<& EnQ(l%0ɠ$n.<;/&xXT;qla2&Ycoy)5H6[ⰩeArfweMA#t"a)/,ڈe 3*oI5mI\͵ocPXGD ֏/^|5!}7͠*zڶuP73O ;,]ѡ7O_\ G< Z`3} kx+ЀILv_dr8bw^<DtϜ`("2iۮ J& ͢5 ~yz7VT q{/``IEmNw?|3Af{SFߜPSu3c2ޝjhk ϼ/\"gZk#6f:@ͭra@҃AޱP$J-hZM-n<ɐg >L3I|ehz+<菵=j59׈e_e^Ycg_QW1uN*dIlkaG_9'殞U믕0"VyĻҫL1\K$Cͻdf `p33p$X j. rnFX1~r??=3zp%O e>*Q-Xy6 VbW®|cxY:0a 8P<&|&γ0skX4&@텥VGs fqE8*)k'o7O6MuSWKÉgWZ":}r_͹}DO/lG|;L$9pV'` * Qzz N] VuFpPw/lYܫJ$8'? UjS%}nY7l([ۺId˜ 2Xϣd6[xmކ0ʬg4J LwAd|Wx)$JVin6t8lVJ* +2 Ax{xXiw50, 5~ h'6R!_q4>lQpSs6Ivz(H舶#Ӟz VFA4ӄ);Enna/<ƕ"1Vy NIY/E{AV)(K]IQAwWaY{(#mN6 uDCh%z* ;1c$Qx*tw/gZ9@!hkރq/"}׊BًEYoƻjT{pT_ӣk|T^sFf%,2*7Wy6̄㻛8l( 3V$cBZ_Pխb/;@_B-˛^_98zp30K|NM60(c7)*b9̵[߰ZkU;*DA-"ʨW~xE=( Nʶ/`?@cԱdhgS1zJ6h()BO 3>t޳@)*ʧW񚛷r_f<UPkI>{b=+U'aF:%o=- ?)vDE"(<־> Hr \,MG2Z_E/2pJcElKtS1:"xi `fn|(2组kG@nahoc9M\_.U 2 㩮4>:SdrN앻*wy{LѺghu"Tqf/yC'a}ִQT}7={X#9dmvyJ@*Wڔ7(Y{F(ׁ]IMS}MkL62t=q"yQ[>a{/gK3(dIo*Fx$?D~㠳KBLu{)gkL\=@`jb?^J! 9_##g]z_](:|u–eAo}~z5yeE]o^%^9|~,3;Sfm`J`p Ë|7}IL7x#5o;_4 ܏"RCi)DUa"8kk-gQb !mC QWW Vū7&5B X@o6Bzr2z l#l7 6P0%7@߶*3I IcSGOmW9NONʳG))}wëylƑ2&1>Z~\K/JFލQ3*W7a/=KboM pxO zeQv\_\f82N0~Mƈ޽Dž/_Ov/ĒH/E]w[~p2Aú+!isVuڷμ na`!}˗v '{gO(V{D0lyG<<>(GP:2]cQ@w;v tJۢp, V߲Lû:,NEVBCƕ$a|PFGꯗeyg0UONÇzqi Zkhb،ꤲwh&kx4'mTP(ekCr^|wL<ܺ*3|đѮ4m-q%}whgUa_]ƗAG,ۄL90őLkn@Ga:$YD3@`֝5y#It!W#G,Ez[68'Wq 7Qz2gn2*rv Dq#ze }U|RR#OgB& 69Vj]ʣ*,PfrS0}5,AŮ G5*oWCs/q6`ox,.ID+3Mm~ b& 8/<AʬO[ E5 6y0;/4-Q"yU "ru+BPy'lg0edV"TCwi[I 1 ^MV19J[Iag{28=.A\]JLk4q UY6!nCy-(WvNT6Lˮ1$}oL\XKI,5~=9 <bFK&΀t٧_=igES&3AYT%U}_mAj<=rX$A0BMU MzN3.l[Eķ ș~ekq%B[ǨucF3`oQϾTr9+(l,v>{FiO~5GY@8wl)(ԱY<;QٴUaYsv~ C&~c!Rf2 oM`@S?~Lݭ` ́QCaDmabSK-z͎F梴X wpzMG!\/qMzbpeWy=oZ՟fz/ckA0q m̈́Wz]BWBuYmxdЙǛ#8öfXJ()E^I)ZQ)~i{勗G?8Z>zL~_aF9$v~8byaX}3.t Fy7^: { k]h1+OYL+ޢ @5z<0B@&eXV|84~45`YUMFhFc ݼ|RY$Sz܋=e1źV 6+A?,ay]$ʔ( ZYiM(GeSJ&L.n+/6 8 epb":>: +GeCmtq^]''#Α:ף8L(?CL_#j=<U7u*72.viqrl<_̐a]1ٟ6q(bU4aNy=cQYSW^CrZ7nU[) 1,.>nXs̓ިY3TH5}QfU'(ݽYu1&J@89 ca?l;,c3ґN&ښK:ryMTۯNg3UB@2kPm8`)YQ"Uz`s/2&I'z2GE`lj (!!+2|nq$8n qûɲ8LlF*O/(oޮSچ`r1)nEJfpgxBa^˛r;(ǧm(~Q/ #f۫xǵ0#,1gIy :!o]-`ԡ 9r0P5'\{AO$ATۉBBwJmUn~]A㲜N駟wFUvkl 'Og$Ws{W١9&9_ 6Dx| =Jׇ*ɦ9Ȥ}^^{8I?WWzrQi-lD~-o:>޽yY$΃#8rXncY$VO_^=N-^M |mN *~ԫ0^7{^ihgoU#af'.S$AKN#} k>;ȡ`|Pmj}Ikz8>,#|n^~'?ofk@zEkq6N:7_~统-khCԙLhl}q쟻.˪q8v9mL4-{TiqӮN2Qz:v^=o6WKfD(cx`3LTUc^8-8`A*\QQ+FSaQ^YJc߯F_ (Ԅ ss}h XHMr~y/iݜ{QB-'lvϵ-~`(|cm xwԄMk31.tX0w& > @ ʹWܜ$ww/\o9 XT } jlIGG($8?;Uȥ>)ۥ>8q|ӄ\/)<<{W=y\0 P`;M8/A/ikF[@8Aisa((&7L%=׀PDC#w!zXTIDI7> 5`̴̼|ż~ѣܺu`T~rhsl=;5T~WKĻt"֚DvA(ͺsxY3g]Wv=K1|o}}" a-R\20 8H@nÑx 3;(*^MsWd$i%InTfk~ǚr8L?XMKY0./^ÎBDa<~rAY&iMbF!k++iq0g#PQ<8{nK[ʣ52ϾE)^d{ǧYQ~c ^EJuk݃%<m@=k8Dš*$3!vg[OpWCcZ$"Y/RS }~~vYD~j>/ϾYãTc{ү=~Gu(4u흾Io^1"!)h+>ps$2sXi쌓]@vEx[*LMC&a.sIvPnQ Mqb!_iCbVϸ8"O<2#Ϥ[6Mԉl'RZ Qޞe;w"z]owN3/N;ggN`,Wx%;XHy=W{yya2}'F Nrh3V$D+ԓnr2>(R,e?9Q2kiOZ?pi\E>%v"9$Q#FxTn.ʅjqM3Rkuͫ{xXNl6Q))m߰0ϋ&<֬k$eJ4hut~;<)5/Q'J9LfdZ `r8gn4eJϙSe=Txl)sYd}@fŸ~Ufɿ0G ~ HrKj/6f Bʹ!xbbڐ=8 gz]B'rWɧZ0OV? p9W"W0veфZQj+򆹩1.2#cԬ3.I·MݓdRZ@_9VԷ=8xﺁIm.pGLYvM65Z#Øv@ ƱeۜLJ'9X(gmIPꅩ)~8r2£W-fy>TV"e;@UDP P;Ton5i9yT}bQmtGo5Kp`peһƷ9$@V8x\~'n)P#2KQ;|Q߹Ҕ&8s(1(7NNQy9?wr\~@Ε̯ W$% 5űQ23v!c<)zX:% # e}?G""b0LOVOS}(k%9@y"񲏆$Q̵5ڒ5aǴo};j Ξw&!AW#2ߴl+Qu`_hO@?=jYdNwD;h}B(;L։I~imV w!vO!m0Okx~kgp0.**K|T~ͽR6ݶњy_ {e"m (^0t|c1aӨ~}S I< Fa$z&Gz;m㡣Ln<;ב=6!)Jwt;qk ߿ټBx+L_*oxݬ!ח"UAɧ?-ߖOIyw77y%C)pʴ[ x+f=k8zSr`i Ed9&2;=\w !?vRlnc%_òUoptƓT⳯F Nz_U(,G=1Oy`I`λ]5-D;_(Ȣ5qZ(\ꔌQk; VEjLTܳ(j딉pֈ~x\NպMa&@z5S t|X+ r0zP,dz7(Q"AR#?{;8؍m Z'2N_@jJVRoKT]E>;}VNv搚 iQjtpJ=[ST6㱋OV!HRw''yҦdS*q\1{OLJPÝFU>:g1'G9)69k֔@YNLWrW=3@ pzVN/' 2._(o?y<~aykI@B69>-Q$}ߚ\faΩ8 sG&}Cpރf?P\c<Nz>T3kCo^]9S#`o8ScX,ÿn]hi5 6uH2Z}ڡ#iL[ֿu_m}qX)7WX|oBok[Rb)wfkz]yl7ǟ|\.^֪18 ˈjNU\qY SJ2^WNi>LzӔ(|y{3ʰ+Lxq ~ϳ\-p.5dUOC@0JX r$R{]&AUӅlJE\O~#dKr^qAv՞uD`{0 UJOa.coSܫ}OQa}Oh~z*gmkma-`c qd6*,:>5QdhA;N5?EnU2B[6,"\6_&]n-Zsعv{{,s3бQr 1>={Vjјp>VaT:n2@Ǽ9a6E)cUQ>ʾwG_6xc>u'"@ q |0̧‡UB,kލaǁeLǡYe]*/Vk]]Wf6; ex/K@&]-P%Non0 {;ID~Q` 2NJw4.`PA`LM\nH8cT_OU*ZBxI4o\ X@DۄgyYiahQb`,Pvb-q9Ӭci(W^ \]gz?)O9/1/$|ʿ?E9ҢqfTQ g9fx֬N2Yd 4 `5#ͪ\^^c^252:1d Qዣتf.u4ƌ?04UA_kL;YmgV*ΧY0g-k =?Uxw&['?N3$M*?͍9P9OOGHC^^5k77a5! fZuCU|iõl8^]]ao.މP)QI ;^];FW0x6vSoko|;X^J<̔Gѽ6~~)2 ZVqRWUՕØW0[-eǟ̸]ޯz1eDhG^$␁=nГ5Tk!~sE`UVGOI٬DqޔˋҞt[QϮʋQy~pbZAsa,QrQD 85+Z۫1KBajH3GI?SBtFF9Xfq%.kq ^$cB au%AG*X?[mX+j7woco53?Y(RS6^29}TNsqSΟ~^ge>[__.oNJY};)ˍ19 Kv&lSa<-Ry'-oQ/.su:Boӟ=+_2ʓ^SʤX=@DA&I/3BˮQVbN6޳y4 jUT?aqGG!aQn(meNB7IqhNbI ]P2yR"\x9n.vlbcKXsZ%r[W73:Fk\\]6y"n*Qm|~E) 2i /`p8sEEY\]7Mk &0k~jU=>_yղ=Q:ɦ'BA CF/PCu x,k( =t{s'+Ev'v2*8T&{3ʲ) ##ʷ<ӓ@l7䄶REQ\'ts}ӽǍ&awtF>Fc,e=ʪeü <8 :<5NHQͦaKinekHUrEJA29vš"xW8^d}eX#|62Θ7޷wNһʳ&5lКHQ7.Z%Iԥ"t\<@>D(X7kDԃ֝?ʟWF؅.ϳp $P0Vkŀdsׂi<|(6e6"BCq+ϥ_l(l()_< y{ B]?|V^©Bg:m BBM!)F6w~PhC1M?61o*lVMIlp0VEO?a|tQO"|;oxk;LRдķŢ|ۃlg-dvap {'J%{j xΌɽZ6x/kxa>Nœg-V&Vw *CqN@)[{h<(5xﮱq>g׎/Yo~_J(ZA"DQƻ7\4"u]ʬ0]ߔŢ/[oI^ݕ$ww)9vVWe=\-wX04rzp8B1XYs(-"AȘVЙ0g?+}GO-?yyôËpDH%=e%4WG;/v.'eש!5xu$BO*ȭZ944oBuH 5!89eʴ@7',ἧ1|KNX灚5jrkj?y㍟+YI݄B[5bUŵ4rǯkEEҟI͙(G'f| C֭q̨o3^)Ty`^ܷUu7V{K Tc5h=g{K{C{{ 9DyY#UN;䜟Yư9UЀn7j4Nv͛KrP3;ZU& 8b4Y{ɐ8>7m>^3w)k,]=K XDsZl9<>.'ETJ46q4.˦T8i%m)~={GV!! !8 ^_x([㚸HV+\7e*$Ka*RŬ|/?я7ޏvb: ja65]KNApɆnVCkPS,JcB[o,?us{+.8Ӽ9DfK @zw[v6_ #FC5'{MPzw{jj]azʳ"IjHC]u<' 裣\_b H;q׎} zS{3@oxhCp'] N0$]sZ-^y i1 S PCeG_O)Hap?9 V+ƒJ\kO_漷}I%Ufu|xA_17|[~'O>+ް> TPQWaK~`1p?o+tY~9w&TOL!F:U1 ")Io r[sc&$g.ke[z#BQXx%Z[<T3: ?H}3>DJlUb崇'2e`koamlKRX$^gyXj2̋} ~uRk.ڶJsW"P\]` "x("ƣzN끗Wekf%0k0zCvϞa.=w3ڵy% k> n\n~FB n37ֱOO :W5dYnqyv /^&@]*J,kݕ'o?)'7?;5ԢBz&ٶm,ru}[./7 %gišf ?Rd\.fV%+r3[྾-ÓIi)C~mLmDACxՒ;)Q)Iæv^}i)DUw~]PuM<fZW_a~\+ld&ڃ1r (b|&YŠq}Dj!/%wUʮ =RCTuldrb~ptPF' g鋱"oD%uw@YI FJxTu5 s<1Ƈ!) b)vc%RgS Xe_pHd)r*u0GcXmcd3H)"2m/ücnX#H2gZdTȘi m):ujȺ8ZoB!̭qF/|p[o\xr<$VY'ޡ}wO3OIpo7~"}oz2d?_ھU VCqw_Gʮd{v4ޯq^o:HKьI]isLDTg;8kViy,S<+P[ 6*/-2>aA?`eBG@BY6Lٲ@G|ux|Q-res hўxX8yܳߜ6/&m!%́FYBư4ڪ=]3X)a.1D?I^Y͍i wB6.*g tXvE<:>,bc_ a/_QxE]pp팤,*nW;<*˹/7:Akv20NJAPI$۽rZy?/JFl6Qry-=;QNRy9H͗eD8 )`x(>q)ثv,֐n/<<uD17{OơI0k0E R(ގF6io_&MVq$l[a qu<*9%޶u=q.gYbVn(]ڇE/J)k<3DD(n9Qw26I3kə@]NNtEFeeYn޽'\_\EoDXy%ޚ7Ӽ򣸺G U_SW`9UBnwQ:h F)G0߇r UN${ee~W<!$Dp+a0ԈtvD0m*pb8W^dUJd0)V5q(YxgOd;Qr &g}\_&cӚxͰ=>UIArA #IR2{*B9PVٿjF?c22[cI5~StR[2eF<^~riJ.RcK^i`ZgAqt2d7f"ॉn:!׻APJFYŏD@X [ ۀOi$]P_ˋ U2 x8ymۤ^Ef[00ac:V%wj.%x  @&lyllxVݼP`=H=3Y"!VW-~qplڭ< 2bcrKh! (U8G<= v999 TB$(m=/Y!?EIx,y{9dmʵ]gMŋ ~'gg GQӘ݄崁mR _댍)z LIϘ ok5w2Hj JJ9Mf;Pn뀰d8l>g@<@HnT},x.ڵ~4ǡ}y?sK;eQbRql[>ߖoe?*e5/a-}y5 Y)!^'^):nKUI9+N2'2{7,[rK}aڨ ֚s+P}aP9~h{3.@tT 7>1LeDٝ;_?;Soq !Hv3@g1xlzFx縷pE5⺌w5#N \D[ýѥŖGN'OfM;2l7n*Ñ(,8o7`Oyjwl)|ZО2=#g# nE<v-Rz7?*R~~EӲ([ccuk[GtB#pMoSHp66 B|^ҫNYt 4eck78n{ⲜnG./\{t^)4M't4)tZҏn-WiU)\\}Uigs/`P;#N %묜!T{ } ~߾x/t/'vJ#v[CJю?͖mYհe[ѫ9ɩf\d(p+-2b`5+/Tn?w;""/J&G'vAHvu0<<gnS+;r~qL.ZjK9ڪn;{'؃8? NfWT<)kUX]xfuwmg4 Mҋ\Nx$ |ƙ+k;zY$7In8GN˧υj[^_])\ڝâw"y:Z:?CVRms[_ڬf<Xk Tc}V'0ڟ+cSlMi'Ul Xgd Ar6 G<6sA90:3s]^/rH+AtVdh<8~:=:hŹ qH;]:Y uCAΗ%KʜYrbRk.Pv"Y~=3b#eEk"ܻ '@(2 pkn n-RxO.KD(=}f뷬(T0 NXySy[hh"䜣mhmަwjͦGX![dSP!ȳ{9ہ|MPx9j Erjȱmب 3ٟ 0y>@'M Y/ggڼKr3ښ|4g}nz4gfhQ}v).U21I\uplMEn n"/ړ*<4рZ|9"+|oos#k!e"9dYz2>A ^%-X^dEn;(&k+r9``Ի 4H`,y0Zzf j1(0 \M(/!|ӌU0 7L=֖TiO"~p} A),6ME=g0o޼>>~rn`H>'**ﳫ( T!Cynm7]#hV(ɓ'QL^{-|CQ*ZKIaZ͔u<|(lmt3LK%~:FT\1a]ʨ>6ݛ6ƛRr2J!1 ~ abvˠ;0cD>f?;?I\QW(^"Ϥrٴ D85E{5q﹢()35r0v%t'P#m5.N$'~024I2խ`#MahUJnVRYND=ѷY?i,$yttj^1ɯm^{o~ x?kz^ "? kG6K}~Y I[WrV "-:Nܷi7bnja/01%F]5&7Xk%:N0=u?9PA0z/ ;NgHkøl/2[ɈdL5[ p|(tH^.8n@q0/pY!+($9EΙdD?ǣ 'Sio^wkO=K'OI֖|yEwBFBL=Vh(| !QaN􌜄@D0W}elTuБZ|ks8| uZ"H=G2O%r=ƮXD_6wwIyrxHԍ#֣WTLqMUX&‰/[)^\';y -!p og{I(( $ ,mtp=G3^>R$p^DV_L IDATs) 4&2A!t;X[Bth2EbfoNpqCll"G)`)bL)A4JxQ|W^)A^hǫ/2rs;QJƷlQbph?b=PyyV798F3FB$mrHG'"Ap[;e}Q/4vnPKzR ,e[6iX@:OȨ?ee杲\3\$g?x(#cqz죨}`@{"*ĈETh)$\q6ɒkU=AϰW#?&uq*}8]Iw :H;;֭[n̓)]nܾUw^{Dvk.3P %|y/ϳYۇ|gekg;;7"~#G[:N5޲"īt=RW'$S`]j(<44lgHgP;_(QS\he~\̎Yb--9e 0uIcF_{eJݻwˍ7.:FMRNPΝ;R}tt5)W˵2hiN'\t9A YM΍nљ'ϝ^qnZ) ~P22XЁgcp%E tװȉrhuљa<@]$״&T.]s&ͮ>wov)NaO`W gTOtR.k8-E)f z6 qؤ"E1 >PO19O #R :2JI"Wg?ãE(; -*!`)*}Q5]S*gU=axR4^xFAZuĸ/r*x3S6Fe#aE0ouvٹ,`Ed (npa:`3V<0ewq5'ce=*PpRʺǢ_pN_̾l 7˓'cr@Po`\yYzGFdqh'{`دjQƣgfѿk2{DOr4  }+<~ ۷H梁Y5UFT,0wg7CVg5dАg{><(2+sin_ԛĘ#7"Ge^F:?˫D}uqV;1E6ݯ)N.˵;EREc|ɇwRy=o}R"c؈l$2Fi3")' JaI]3L`FA0A\ZM^&ځ볏FpDti'nE[S%iZK`x%4×:YJ\BP]W`rE?u&ۂlJ86AƀnW:И/dw`j9I+J 0 FlmWܧ,j=8#ruSOr#)n)DihY9794!}\` @RwT.GG[wk_UP֖rqrj qt$$ ji=J<GJ E]n~D tQ^7_{-2n2 U)L)<̖7>20n:Vt ;DD)^^ \_ J9PT]&Lh[쬜; rzQdB3:'3p<g\DEaYRњ篘O??;< &<-G&"2ά!v,cwe9==)o|6?/MGY5cδvqq "W'ړ3#+W32Lд)B.ti8Y \Viq1F_*~e;lL0g.Xd :q.꺜 4otNTfe T(6]3M 9y,;2/$Ote _?n8lvt| b_JMYc.®i)]a ZkW8BD R nvyiBFy粧1B%b  0z }DWQ\H ]΀ό\1xJlo72CK0MWtڧvQ#A.)rr={'l NYZ+[U[uk2ڻ*w,c4:ȇxE3޵bL{.)T0qa{mRWj]˭Q<-!h1D$+={"!A93EWչg z]٫w j)c@D/wx䧡{.XWt70heo/c\g;t9~TF㭲WnyĀ^n)Ͳx\d1{_[3?_ݺSU֣`HH/ db%5D-KWEaODzU6vͽňv{mQ<~tr]}﨑e.4!k8/X:Gk1uosC@gh#1@Ȟ=l[K5)x3F+B|izn6Eq (5CC~1[gxqyIyWݗ9U*GE pr|wYGJcmP<'0&"XY@eD /Geg8 ,#K/֖ 0珎ShϽAP7Y2w@CN\3'kcn@ k8 W+Jj+Thgolln^â }ANN2e>x0}[8H YHl@2)4/U?2 2D- *p]޾q&cP*BQ $Wp7&A밧ITǸ-٥81A Ʀk8c{yl&Wlsx|i%Kgъٽq]v2v&yb@t^-y:cJ GFHheD#)KΙ]ҳLjJN,gZ5 gنg?όA?N4WA !'ocs#(($&œ1 \,ApLblH˭ =݊Į؂٦2`E&W0PQQSFH 6u6 devکIYYdrzq7QD# }QV'O1 ^1ryz^>?-|q}[ F2k&XoHM1XL3Ll?H).Bӊ0R}LF/ƿ"R}ppXPNOΒZթ{Y/ !$-.+͸lN!/tsW/h DalP.1LLLlTǪdx> bD |iЖ}TFxvwtk8Aic9krptP&֖y1(SjUђ`\p~1@]gu7E!tHA]}6|{[7l{i mQ[*:LC stnrY Wnp7]4;{U>}쟜?[uZPy‹<' =aeOe{Dнi[ޘr`Zt(r 7(ӊ9Z]_'GbZ?GwN~aFWL1+ LQRXxMoBzrYe"e4 K_0hYb0 $*^)Fd K!}EU*0}~K/W^~9´ kaHXm(8f(0WXtRϋ Yx#_"lqko^uZzzfrK⿍ͤ/YE;K6}+r%ݗ"ec<&2-{ "b$|A.JPZrnƒ芑6@[o^~3''wF@[x~ڍ2܊+Ny;=nDB:@V'[o%ynV#Ewѫ?< DqwCϵ=>8=ѦVZƠVHn跶@ɴ}a de8UpJ2 =j1"g8r'{r9'9Kτ>..P' ZL&Q ˰::8|Z&AȊ38mGgڤPgp5q3 &=)rfFv(A4AS cQՆ)(E#)AEIaLEXQC`5\󲽳#`Ѭn%0P 'MTw-xBGDWi٬*Z 1!$0JkzP_ܴщ$;VeEQ4tQ*[?a y}::8N9|F̪>hgyzVڼI@ f%4#ľDSuRvUXi_'q:Qǩf6Dk%b4Frõ/&z^޿-6)r>ڜ3$@ <* ٥Fxs QM1}YTLNQ_kbwJ H=?,>jxz/RNf/x MiqzF5>~x_q=i|%:[XXj2CCUjkLy2Fd2;R4kk-T/ן ?YkwwU\Bݾ"X2Qy;ɬ0zK<_{-d4C^HE2ŞwC>9`,YF-l|wSQfH^'׮IrЯSD\Ojt\Ti>| YtLNm0 e|Aj㼳L3Ʃfߵl 0ZT>_f(cMso<߯.ESy ,q3z\ }SddɖOdCn^4*EgyiͭAQHl S,̂ 3~GN$!n16y $o#`>zBO'%J:w*b ،8Ú ,FlUދ) DT?&*UQ}*\]7)Z{tu/߫W͠t/ч:{ofپt'ig$Lrl"Uh;Ady~Nr&ťE!D4]3gfm)|Vٴܼs;V[}x^+5ӛĶ(D9\,½ٞ {V`틔oFޚbk馄D!q=QƑX)@Hҁe ^ʠ2ΜWUI!hO$N%ҙ4RRJqYQT*D: )wA1ȥ5>/G|wS~?Jѿ7hcv^ z޽yNx3Xj{|(~½VdNn^~lߺH1:IqNߗi El S,1^W=FMî` "1a?9]Re4*%c%ml ezq^?f;wLvZ 2ͶC>425KhjFI?tzL]a#,jjWDdÚv|dbUNGl4A׬#z>sO}/IРn[bJ[7,0^dw2'}iow/0uˍvZJڣE .J9<:^j3>NNΊܓ ~$komfEIT9I3ZVy~"O&vv"Ϭ~1p tXcast!FYr6eoZ>IDZpR;[Oo9ա$ȲcCDW@Т^xAsQ2G ESiFuA7-MwUfgAɰ^ A IDATճ6l;2(2pj qzSef$Rkd =CdB;Mូ.=Ho%C'[{z:$eg̰OhWCzkc <ש3&:ȨI-ʅ2: k<ϣVmI/{hKՁA4nU\yBv=k?<3wv ҂f\(&e(y~~1dW{5-m0р!ۏ!!@1 a롙.$+Dtw}TH>C/V Cb.MhnS)x⫨-G@+>d2 Vz[oiJDF*R ܆0Y5D*MhU_9"4"WPixgd%to jƍ8Se0%& WBs%7-ꜟkǁn0xWVIO*MzUнE}YE6 NB{/kq_MAys"W>/p% 2 f>z+XC$єf0 愸7g}xD0fnθ PΝA߮Ed3`28_}vw}Eٻ?ͤ<Ә`O0EeN O=/ͭ~vvBsf- }mGkohuz] kaGuPDKO(zS2+C,r8[@$;=)R3Bxj{wllm9(GVm~Bh"`AhU 8WtY+ fF0޾; WR#zg X۴JyҫA1m:んS$VT(w??(_2]_v0">7ojIfI2KstƱdlomn_{o 񨬏1[q~<kP X2=)¬Q.;FwMQIxn 7xkEkF-iEbzZQAjYm J @`+ߡ#d6+{1Nɠ3zyãpF貤C f1~03'sϗ_xh[<r׽elomˋdV:ݓeV]Su.=Ș 35Eli_vFjX:Q7'"OBMNtbDfhZKls\"}gt՘]!U[R3 R-!{cV) v3mgKќ?&NP4xx|\=ziU]..Adox^1K"+ƔS7ըuu"sGk|,i*Fgvб`@;<eB$3?x1=#"e{]ѥ} ۽O*"i7YҸ!\8Wc8W+)qpO f(Ƚs?8"Qa=th9K?U"c6"0* )RY\7[KZ:EUNo8o8+I3 ojh9u⸏><||)??LPVTn^A":xܼ\mlF ܝC Q߾W66@! .̜<7b _tQ21FX;ݛ+nV,>̍6fIERʭ[߬g8k]*`c_OU=b 8vN {6=ó3=7g;<  G>d ٷrd(xfw6˭Zˇ}t_D?sZ?$NÍ7 ('ǂO@z "79Q%?*؏1NNL`g鲂~8 5jFXk"~:QdC @C M.P@~rY 7L0Γc ӛtv{4'¼+ gCZf>5 ' zm.Cm(0E}O\yHMAМNjuk'w T .㠁Āe&H y57 6c8I% qn"Ђq ~|׈R'{yBP‹?E2>0,׳2)Wi}*r{sz~xf 7aVS.O3D-Mȣ4ӓSt8y]ԼI 3À~hhGSa&~H.JFƽlE!Je)r[GI2x 9zb.F1zj3 9`-kvpq*oߐ|.{Y(`W,%.4sBaGRR`!bƀLȠlͰv]{uowxG㤕"}Fz Gie乯k3 E(?O~UjQ |,_,Af|Q.}'bs0MR89h8m (Jhx-Ziz8{NH>m_(\h0RGAQRٞe_Yu`qy?":ЁՕ4t*sE}HAJ%_ARaɿʣǏ˫V~IiyIf 8]/k7o׿+/A,;2*[;QEy?/GO+v'G;0J 4A]3o8lhI,0Ey~yǵ=%ޓ5U;(}ՠ~+t| c3J2niuБ"!"!^̆ qW<ن+bl_;c Ev+Ԅw/y';} "^;>W_ȊMM/z}\g+NSG8\ DX^=0@w8qh_۴ox]12$gs *5!7ZUg!'zr}08 0w~dd8Ga:3nbEIC*W]n=>OȳȈƏ)yn ad 2-`Z|a)n.0vyy1D?kkG6k'Esi54燎g_z饴\\/<= @ˮv|HdUj[vuxSY_PH&'39Wj,hM")qz_Jkmº~! {1tH˯ o7Q@_| IadY+[;׊B$]H#|{e6L=Ev_iwڛ5ULKjj5􉮲#)X^BQO H4 Jdyɚ$rH#Z69 Llnl:svj7#F .;;$UdHtwMo|xEP2k!3 2ZΆUȉA)KIǏ238xl,,VxЛCӭhe5:C,E{1^@UİZyG'G V$G|38)bP_iNK p&M6OLG<7xC;~Tv~7#"+ڲCQ';edz"kuLp Eg0y֒;ه7U=YmBj̈́t+:"x5mb˝oʮz `uF +aMCmiS@ňoDZ$j? 0ȽG6#'D"n΃``RreTKF%腌b'owˏ[o7?aya3Ғ 7o7o6l͸#c(?Or^Y̯*J'{s巊M'5R4'A} Ѡܸ|i:y/%uçizϲN}nim($*#miW(,*>c鏭'ox`xMw,K3~9=L7Ey2NᲤ^ IDATΟNw//:7mu8=wC;_r3DoI%aF,F巿{>ǍRlibBvm+*]c|K` o(Ҭl0ݫL/һ)3b2h>Z-Ō+>m gAOt0Rh}{7g`yrt?rָ\\]𔫲I0Ña&%z7Œ&Zȭvv9=9/Q1'SM)!OfgѧH'{h\1/)rImmQ*鏯/׿O_:t?!#FK^h!Id Hٺaf=ѓ%dGkTG)U*kWv5*`'++kӼ=;1tOJ=}z+=@>A&#.v#x0SKK-Дa(W_rxr3̽!hJCPkt 0[Lr~63C kW 3U\Ͽ*ZoTYn ׄ,#}t49xWWZ14;awѱў{4X !K91( &W5{~إqi*D uCN5caMkY l~ݗ  4hwGG].8ޫ`2)OUz0L #=\a2.d6Q/(vffbnOT9H@4{ !HW_V1+ng݉@y{WI+Na+\Ko\8OQ⥔绒 TD*\hEm|#W+2:C`%+hm~̿]' R(]C%5=" Dn &3^"\M+PsFe~n(5T7*Uwk%UZK3x78jZ~RyW_-&};)/b~vi=DAKi&25>XVWtfNf((/ )7hng<]@\Ttq^>~XN RM*Y3r&[tEѴj3g5w3 bS 8:: }%"ȝ^Ԛ4`ƀeA\TZfܯanM$w|&׳Ftd}fkcTHU5N"~KD¸Ku=죹µkGdр]@d%2 ^W6ТM/كU89ІpNj6oMSzٿ>iC'hd:#??+8cdE 9L ST,3Ɨ׋:=tڋB. XfԺ8-SY6tlD#ezioW>F`FDF{ wnr1˰)R*+n ,l5Z;O+2L;ݞy} 4䚂E#{EJ|dӋuO0A쾢W:8EWegz0 v-"?<߸3r.y)0ǡd-^Cg8YֳZ=lv+mt,՗MYhԁ oZh񒬆@Qƫ߫ @_^(ӓZXթ, #I"੶`,F/1*"&pM_ !צ’ªLV# iT4kHZ;(;gTGDa{E0ݬ+켼{e~X<\HZgu:J:DXaFi$5gA.@6(Mbx3؝nijտg)Vz"p΢~)zzen7>.kV]^'}\=W/s"jmnݨ>fKÇipZ~qR`mu8HW1(y-PKUlCDI{={q+Y7ZЬK 3/"4Z2e Wgšcdi*3 tF],LTk9#e_dN'}=; #LWtK<>,&ljykeUS+izKJ~Oq8)vEET32z{-Mk٪c'Q:?9/1/k=|~ =OBƺ1JУ¾HVt1+QEL ?/ˋw_كeW; TiKMLu7g:ř"d0 sOGߴAjV:-!(WN=tKiנXWٍFud[4PƁ[$` >^ Rcdy NL ]kF3LFsq%2zQe1 3$D73cP8љ}^N'_J%gO)Z/kp;ճF3++(i,/BIvq}ez^Ɠ 5STy,j8 Ӵ f #򜆣PZq̰~Q3ؓd0E`׮Q+%g3p+Kљ(sWc][*^;ge=  /<;ݞ/*$Ecsƽne jZ ,$°ڒЙ+#zejty5[ hK]0[[ZH=n-k戀ީL)⤀ WkϭYKxml4%m<_7X R%z5.=|<?_NO28raZGfL6RVS`ΰ7*SŻ{_Euɗz )砛 L}H~W|^Վi1ơ}AyzgCƐ\Ѩ5v$k^E*V3(`LP_W!pA+*fq,I lH=ݮ"2crpƬ_Bw) NG ndjn}zUA@&e{cNtq 77oG壏> .wCHgt-uvge3]h{A5/pQZ-Y8JE60Ry²dm惙!?ͽ v`Vk(i1| Jb}ơ uA>8:W\O3GΣ;5(d!:"3uE h}UZdw0EkZ ,Yw{A}Ytf5SQnN!U]6kEZ(pY,GU} ݺ«?NxſbQcH?] =Cyf ݝke2}Xk8R̃(DF1㪟f:xtU2jDim*0r"#үCV{\LkKR鑎\VCo^\PR Z0 ^NQ[g'pD"1*t>,¶cZmŸf Y!L躷5SLťZ1~s%;-hO->>v+v[kX}'7$ ]6,^>L\D!9'/{j[+:9b톎7(h_+SD{daVӇ:"gotE{>{L ?<+6jtuk5)5m}=Ðr~fE %u4ZpM-KmVyoުFY>Jc@Fên+ެUk魗eskXY`CkֲmJCvTP-*PZ-@6AGM8PAXmgeOYw8*㭭D|q4a\z0Dhxe;;:g;KOͨྲQKl7s%8SY5(w]F{ro\X3Rlƥ?wʂRw,tI֎zqA._s 6i|#|ǓTpSfqws~KuݹwADy­xwI~9 縿BSLg/y1~#̉k)'X-(`5ltPqwVu)kģ|V~r$QD 4XiCF_ JՕe8ovyXnجvރ0ZL"q[\,;;q_\XH' 532HCo]JjAkQ(֠V`f*hq3CE.ΝM`hTmvQ<ڠl9gfbLhTռJkc 0y}K8!N#aWN<ധm;'O`RQ8m*c 4rڃfpN֍K7*?X9{|2C* krC+ROʘNڡ)k1WWwVܕdbA0̾ |\bimHj ^Ψ OFUPq^^hX$:7Bv*֛S9~{9pJB̘K$^qkASKi-)rup0dpbD'cEW}a憳9z^.Rseb\b4ɰl\ gc!fV HgV߳P.I;=  lUoUM%Ș֙=O0kWza,>Z1ڻo|Qck.h<%é2\E<ֹM23Iv"'j)i}\=66.[;ZjZc~gr%W>Ϝ;8ۓ`#zHdx؛mʠa =HVQY]>?K壏~$kQD =%e'I=YQU+MŤjGRqըL5>$nRӝ.2VeV-=:P~r2,>$@lo3jʋDh<Qdt[Mа.s}MGG-5~bx?}.+l=%pzD1* "8Ǎ"jz>_TxXvix@u'Lt+gĩSgBQUE0U!S?V&NmuKEQ4`Y5ҵͲs8]%hed5ZEW'nu7eFzZ}`$G{yGR7ޡ`LK ڀH4k1e0-s6i9ALGeh:=Ze0|'Bi16:3yb͞`\ K{;_|1"2}WS!AbNneMbrνo\g)c Q[^X <1m 7&VkUHɃ9 IDATOya;U h/2-v0a⤢XP'ء'3Q4^\;K4q2FL}y2[%E`TM#錪&\a 7"^5`fH_\hB+gF6|15a;pM[6TvL-_fBeqݰ!-< R5tI2F +s 5)3g>v#ZKDb ] T33wYS^k>1}&x4L4jQ ed`A+ K^RU棵cVݎYpf\%+o-pL^:_x=M܍\hG f_{sI}-ρh,*ګ9с*ce>|isk8W518f\ژfsah9ILFL;ύ3 6#<f;~pTn^o9\(ظfx\ht\àO5CvRZZ|k4I7V;#ZHޚ'xҥ{Ͽ)_O>7zQ6kOq03:eQajcN2 F`K/Ph20`1E8f^`"|kkcWG-S'OT(.x>ry?*'U.A\㸇ot&4A9sm8T ^R vuMe}=h,37{Sկod=Y0p LsFfkELf1!n`0 cøl<χ04従VCU اLuT\H!kTՃ\FMS&pkC9-3zF ݎADbqbÂrE$p*UB wRTw&E4eg`;j zL)9-7Ċh z|v5~91aCT_1q2?pkwp͘b:.dpaKn9?Ў~3oZhrOغ'&أ _wx/V7k{1)iﯾc*Q,b>q; ?"vYxa:(p`CCh.^qk!d5i͏Vź֔a%︮Xb&qu#|9}|id"B7.ZF{D<7_3W挘`Tfg嬓N=T B8PNp;;'KD{{nRq9*CEK/W^~)6Ο;[.PΔgϖ3N'E%WWb=C`6S9s:LF mܯ)b>҃r IA0s A5O&>>Kgph(j}? eSjeA|Ƽz)+F lE)Jva|oPFH絋˥KoE)'Wf\X.֒ƕ@-"Wxv<a\K'Uiqj^ȅ=x]R֯D;9;r .ZPS^b'Geww+PzTcwzz(,-o|ŗ_-OM 𡴔|i- 6긠Yc%̧?bbdŚhc[^VV1cfB\((de-ӳ̕9r+oDmD_D(i'q "녽1as{b}*ϙU܈q+Ɠb GLJO0YW8ɳ^c7X% ~7A\|O =iuF+_]]ڛFC:3)L+ď~>C~K6;%t:A$SF)5-gF+d#cvD; 'SLYZ˦. );U{Dv6 f'ptIuGʌ:7;Ui>~KhrXWVXI&&`p[:DSOƹu7|3)w`!.V|:/UfKn0Qp,8hs5`E:w!*[ԔgUM30}>LWdjbjsf ^ƚVm-R9ֶ ~څuu?$vCi\B%wB5p^d)!̙l){٘`ZntW2L>Ц^_;V9N_B'l-IJ\^j|k  6Y5 _IP>\ybЏ:yYݩB98Zt\$nBAA2W>kҔcBTonN -0rP^K cW.]墺'tɪIf%">ݸNY+B'^+kudDZp&Z?.9ZEEkp+9}:,ݭ͝8}6v2IoIhL;)Eo .$YfM1e8k^Jx?+زG{B5Iz&P۾ݩ'ʛQSq''?Yzΐaϝ=]*-( +xS893-D(J0.;:A[egwћ$G"CEbG/5jZyJGÙ2=PV_?FyԹ28`ZoMA לb c$n.Z3pg{shO a pϽ܏YԖ!I8KFb=~޳] W;R$ge<)XS9:8)!YVo Gz~n!>[[RQlKxTi]q2m-WJͺ?ka6牱_X,& WX3((Ga&@, /SAͯ<Fl6SEZigD5bsijsF*|3@6<>B{oKY3O~w{r9웂 L!C8p0DK svVm"MC˼4R4l톋i!6Lk I0r6fl&bxvF:xio5o["yS^5FziÖkK;Y܈"w~|<`MKG'c 0ƀ&>!{iל(XlWB{f^-/Zl^U'ۨlvX*b ܔn`~spҦhA16Η')nqTpƸ(+00*1V.EcL ~R01l||ib5&49f:MÝr* U %e.-I* A ;^1_gN\g 8OAÜmlhc6LMrts2.u\z91U1,ppƜZ B ZI Cx_}ogMʊBdx8P)nx *osk3BW{^vޞ:s2 z{ k_RqL853G1ъJs˘r_B0T$Xݙ>Gvh{i/\yGdfXwa5E?:~ִebgdmM1-|j2]>J?|.momm4Aajoo?X4ҵ..gmMߺ\^Q,ܛt~6E䚦̭log/4<>oՂM$~rkGL0 y2ָ~0 =n4Y>z*HٟZ~#3aΦvoo9W/^ P%mPCg0i̧2׮\M$A6q7ĢD#FǰVq!CU*U:)A+rZԖEży˯◪"Ǟz2ڭ zTXB(AI"@Leue!!j7ll%gÄߟ.gDL~JZς~Ts*s7to24b3bvum-^; eG28PZdd6iKóB$7'O<pО0M?!2?01A n#2I?q24p^ C S=o3j' /\́hX1vڳi3~xܕ~?$,@3iVΤqA֫ >$`.c:91!oqW3sբIRya.F)\zI4.8\2WުM*MkyF^q(kiD>\L{2RSW6ZqzggkhpYώa$K++56D?~@Y7yQ*!g0>4Þr ú+֚lFy-}=a0`Ar9][bN;5,&7ZH3MdQM 1STmޣyhKy|ie3QL M/տXv98qۛegk-}eL㪘GeA_u3e쩌}Rybdpįlod;w|GnYn:vSdU:.'kuT[&뵳u/_`^OfQbF"œ} ~hh) N+cխa4ʞ7O{;q0UVN\.(3alJ="s⋡_91xX5E/VKZjd^C8%5 hjApfA0Uu1Zf>Eĸ'XLe8[3;:Suw0{X[Q)p#:$`us΄#'e wVCP˛R20b|U$+  뗞 TIڈJaAXV3Ј@0&e~6#$DXr؜ Xxi~(n*xL{8RrOu6~ 'RI_Ar p\yDEA5#4MLOd#X($29{: {hP )\(G9mU]-clg2a4Y:0eDžo I=A[snY~Y4GھiL|vy.㟩Yy"D)uq!Mս<]D=T<Ѯg|M;~aWiwy);k06hjk.b.>K@㹬m, ô**.Hs`XOy[r^ug&_b0Kx|+BWLd.Aen$i`X54xkAV ejfR;2+{"NWf4X$M0rZQU|G@IÜb&6[0mVS.077)ofUE\ed "rShF㲾U]/8X =߭p6Uվ?++ <<+TuCH"KaE昸LΥ=AҤ9S<rG<8yO[ 3v/|ϛW}B6d|HH {|j(+\G{X\YJ@ahTw6;.ر ۚkezЏҥ4=[Ξ|ƽmp6tzysw UsnqSgWW7'vsEv6AZqΒ3L +L;+&l 1c, A ?96Նh3gbuģ,,(|Mu9*mlmWg~'*_U=lS 8+}\w_hjr  P85ozU=Hk`ոЮ (As8;\;fˎZvQ!B啥D"y"a lWRϒt1kAc>ȗ;lw^l ୷*/\9s!'U1iB8B_uA"-`BʕerXT&qS|w~T ;0vP?&Q L.@ҿ'ZX~p !M@$_w"'E[kYwfBpZ;\HP䎶h3զ(KeXjs5?t:I>J_}DRJ,\-661ԷMp*ӽY}K6 FIWq}8m+"7%G|1&^€ ط`N, n.;bj po8)=>XeqS,f&%hzV ̶wyg?eM9TWz^pe;>&V&0U6髵yz2GfUs;YD^Ƭ"~+ӟ)~duA?-{Aǒ}9q̜EشV_Y'`wfyZ4ς[ c`Ri\;ù237S*&&^:8Ax3HIat=r}/{{Alru05=Gp^Zd58 ,Rm?cYsXhɶ(1p4[υw àR8bԤ3':˳P7N)cgM p%vW4>!'RЭc2U7k18yR@79w02oUZ-h^*t۝K?mViVT~|{`jR'E%mf+`s ; bS۴  Rߘ5FQ%xkjZ*sTq= ij:=[M\. !c(`a6umVpҲ*̍DQظQ IDATs[[,|K/&;qMD8 YfH!泳9DIe]5 3$~'89rL5P ,敃 'b1֣}Q`Y=變e\>i{ TֻLEj}ߋO0pISGV5ƞyӾ1jX-Cl?6g1Mv me흘?ow(Z=v~1LE<:ۗӺjnʂtC[kl m_~7'NMs-- -'픻v{}{1貴dCטhi^{"ՂtU44Yfʩ8\<x1%yؓ X*SY;\ d'/>8&72'{Tơ nJ8cyy%8H'kZ UgP*$U%*ڛf1?!VY\83F):AL)%ONSZ3X'(0G~o0).U._Xo jr7xR,7#>W3# əgudj:>|YPvvV9O>s}k7Lݸ?xH\j6(T^p8ɡ,{,nSLe\֮^ \ˣK\p(#$xyoOLo悖ӴVZu@R_Z XGtܺFMAM/wKNo1 58G6!(ӓe\~k-ʉ{UcMG'XFrʕspwvUrjM$}"Z%yq[ ~{BXS754%^%ﶌE` _ CFj$I{973I)GxR2 &[Wʛo^ʦW +kayn@翅r+ r 91ƅ1pJotrr6q5!eb~F Dxˋ/ #O|p6qvL.𚩋)6&gnU\CČ(qnscʙo.\0D ̘qkKvy^:aSy;&?o~qY\Y!B8w_" k#LȔAiNQGZf!}8b>zA$oD6%wt #4PJڢ wsMu_ˌJ&XS:1x^p+IZBL)2WTmbq=@ {#l43߸ H}c"j_heG8ip)߷Na:wn9pG|9yT\mxV{}E͏ڕy#)]q;?s9u屏>VGR HJUFf͍>ub˞ IJ.87 2CtmlTj!9؀{D0 |M8jv==qZa#<4i@%Z F,>roojq jU͘T>6I[֤F]&@ܒmIg89Gwj.7#G%lvf!t[n~tj0KָcYx?н_.Jaa=rU%ogq@;\@K/W^\j,Ep+dq,=wXR֟( !L0,/Az f?BknzX7%fYNO8OZŧzlך)KfBaL!)_p1(Vƒ"^\hOk1me-m⠦kjxkf8PkAcyfr_OY -Uls s೼č9ce7Vn#%pr^LP=bо"oHØKW/=dkO~r$.W R{SV2T|YeƓf>ڸ>͐FQ,8Xw'7ٲh/9 kcpVSYD 61r2``I[<55gYq[)Y GmlmqrVSԂͰvSR{n\„N2bks\y B=N+90Lko^b\\_ڀ4fáCUi-ky,Wvgń\}\z@"@"gxM+w q)TYCl^[(.].ߑ[AŽJ u|ɧRݫp {B$UzIdqj-a9}D]QY\UhY5dgNr[i0E"KG8ne2Șܹ9֔^v^=塇NO~o}ɲNʨ? SLnu_UeBw-)CS v^hߙxvvøP5TZX*#H Z9짦QVtXh+ HC1,%hqkW;PJkm]XʹMDfVOߤ|dGUTZ{nFbK% pdaUB1t"|rx|"~.޸ɝ:9*WT|j`ZMҋV3"ٰKY&@+ C@QJ,1b\4% ƒmcag0]riry/|O'Xf)lt(Z7sQfeg+W3S)D1Ko/̞~t!俖{2лa\ُI3>0㘎 ">>aD|K.4hoWÍ Y1+X€Wq}Q. N>7hx'aF|PomtWKD 8A=8ˁPEcc{#ZXpL^R8dX6s1^eH{ŬlUE ܞH`,K94Lֻ|9LM[WMNL(Oy4́kS ϕi`BIؘ`"M\R7c>L^j[~\|g=GxL/-Sj~_~ekc'8 h]ڰ֊#=me4o}='xΘ8*a1cKJs9xjl ca#DN{txhie8Odwo+st3#iY1އGe~f̞XF TB5N|a=ᳵпu TCJGå`*Yd_^0^U2TIJr?` X: ݯ\5M1v|֙_m?ڇ4M~4F;`>r>dw?Ԣ_y0V>cV{Q0Q~/\ J-o`CmyxA12+{mHb u֗\4;[(1V&Ҝy\?CgD mbh_ޭ{ t"_nxfp џ'pIAcl)\8sR%{fdvމ{+hj0B;ȳT ,Ѫ rt< 4:KNh0۫8R1٘ , 0#I'(0'<'r*l3CkV^Yf;qdg1ya``Qeè 4숶D$ɤuJLzٕ67T~VZS K?n.ZSMh8x ඼EAxVbWVvk3s,~Uh_JP1m-pcdk^@4A_J7 ^ՎC3b@A fl5>߻0>G1RldpyKj6ul77L!%|Kf۪GCy  K̀dvd`<}ɚ9i>o4Ӽ8@#wښ5zO5'QcuՔg T)y2x?2q/__-J)=cs~9#eF-nƔ u)ڒylr3+C0ǬΗh0)R:15Eh@ˌ9[_ d[> }c0 U+nmi|3 ~X D;辘4f!rpEfXޠ[b1j& 8͍I&SM)QÐurp M#-=FG'Zrc#4[a0]sq/fqzD8o5 yw![4 <( 1(\TrAjX3L 䵎֗WCgmidr3#@ciaZNf5esT,p׺?wr3ϖ3gϤ|K9s34'1`Xi a\{i,fg⎇\o:5#+++zj-=vqFh'B0YϦJAuڹ͞ߪuTCgL᯽f}&% JIS-R ?Z /Xf2;#CS- >Cmcig|@J%㕱5UVQFZ%d!2v.*VorZS]tD !hAZ?1cH8,K,?T:>]Ο;W b0gH=D'Ϧ+ۛ2cvSIV2&;}2G,G8"Mb^|;O[%\^"?tKƇt4 ! 1pc`1tݚD$.9݀ &a"-ӰK2Ž~Y\Zf!-B1sؙ#hx#e 6R6$ g\W/_ w2G$ sDG[c$Feg}*HFԥ]_d>"YRJE h800S^׾_|3ݭ#Ko\.?x2/GSO=Uh..Eʋ?(L /7fi0vUƗ_SU"+L/ A?6թ0 Jb/-9YX"4I^2|iCU-cUفlݬ%.:ݿ,T}&85(8yzY 4~>pgtX} {)Ŗɟ[p+TOY)`^m7 EsMٹT'lMIPU]0`g E <w/).`0I˔b(|Nl~7=S;  7*ZVkc\u{Qh<'d_6[5GGrɬo)oLExdFB3gh#gMrtiwґŌZ_--$+UοAr+WTn/??S#啗_-[ٙmL8WL2 \nJYH >&;zwW'?g(!ĽߤOcOiL#2 a F7L|B"Uwa'N/|}Zí[*&iժ4trblqT5Eb)YJZ 00N6;uyy)<"H6FW&| 0jBX5P4Ls9kP)>H *Hd|{%MDylM U[YiMqq07bo.ֱ.-/Gik ՞qf_6 K){#TsCߴa*O{A4Ɲ~f=Mj&= ˇ}c;8 2%/z-QX'0(uL;Z-9!h]LMoiѽk9GbPx}B烏nAWУ;WzF#D"Y:;6ž2/jq=8at`9`奕!UWBrn6 ӸDH:< cM}\XHn i:iÜOFEhӴx_\X45VFnMq,c5"UJxԴDMRqP29W א1 ʁrEzCm1UPD%as&m V܍ IDAT߼F7 C70*XcFR̓fR{eg6y;&VJ9a拼-3o4{2`0߫"!U 2!ah׮Qyŗ ϿG'>3哟ӟrbu,-/ϖG{|ǣz&BNf|<3tt^ h7:܏?0f^Zc)Ѡ ƽ풵M`AYb1BSU-ĺ1FZ,LU?@ G'㲾='0 V ڧeEEB9hys4u| g0&|n$Xe2lh}D!)&b֘iA)nF/R!DӪ_鉠0uI y7H?:%7xHex7i#aLJZǂem`ָ+٣5,QWkB&2rSN,O40zpi[M8.O P1 VLtq% ,M6r3i 1 LˊIƈabDcM J+x%1~aiXHXR[\P9c1Wylllf~.s/IպW2ᒇǔCnɄ2άuiV.L,?, d'u۳э椪 œ >NF{$G5+0n0BY&V>4%ܩ ^y|iORGe,+a! ÜU@rvK(ps«+'Boq1 xl"2$X2?\Մ x/hbv d49^,Ĉ]h89ikwAF޵^ :yDMم<:G9!>5tc2y^zRګ˛?|+W_Ɇp|~<|BQAyoƀ׀ly2W vpH6T3b4E&{{Rw7ӷ.Q._Bey#},3m;\u_/;qd4K%յDS cOxrfbٚL!T㬿Z#UbVa .pA  Qk``Zzu?,*|1r)33A|OK̺[!1ؐB 9p;"Ɔ)m~&jAl^b&5.퀷 }^!U%80Xv?wc{T63g{/R&?z7ԩܹ3g?3"Z*"0h+Э͐~ 0T3&;;gTHgEVc1N 1.q%sAEYZ7=yh؄*؈50OAGNkPw;N٫pj"fc"d Z o =H ya$&Ua~nHV1cAY]ڡan%2@-:%w1<&SqA;{Cm5JGVxXcEh2]p>2=9YR4j4<`=<$ :G?缳sB8?d xΡ{&(TesQBK[SݻKfSWmr1 Y^˃ԅ3W;hGM`<"f1bB\L|.[K~"[|կ~\[o'Hy>YN9"R`vyT9٥+=ٵ#Vz<޼t9Z Zj8Ξ?F!`΁U5E2. <)r7DƆSu"1Z!E~>;`0 '&ll~40*P3k7BOs.x3v<4FdhŅz?.mIupTfQ5B5 05+ ra2a0b._RN%ah0sk$2TW,u8&ʺs%)rI륝O%[2K+s NZ 5)*S5[:܆4j1#Vfg {l3Ӣuv1z\W W`0ifE亾GGטɘ}_^0w#rX󙤳K* f/W^X.\x81\]*?py?~Um;[#W2{M :&A~́] p>z mZ5 3;+T5poRf|+΢]@/ђU&_\+ɨptC?.slЕ^&ch3 S;cN\ ]<: f`sF̺`kJ(tc*Js.=:0;]4vt4T|g8v{atR9uib汾?e}*wk)$=M;䆯p xk2~ 42~rK3vš{΍gʹWFeaSek^T(zM/%8V>1Y2x TZ ʅID&V o}ਾgKsZ+~]2T7Ugui e7d쪂 Og8koPRF8M!c579KXSTQG)?ٳO塇*Lo6ЛoB sB( Mb;hfԉʏ~FW_J{O?]1\߿;n6N'>jۗ mgb^6kxj)]*`i46"8 ۛIYl ?[jcG;I]e#DەPM ·ᵁ(yN\DR6vlhb֬I;4>˲0ͭ'eqy9p4^ kѬ\gi۴qL~Fۛ9R$Ј;A$/[I13jL[ &;X AU|C`?Bsm4֖Wd-!Vyja,Ozq NLF&5aSͬ1,Msx k$9@hkqkEi6y۹;BE4YX0LtԾy9t`5E8)F) +?*ƿ*.\HUS帇CE)rihp1%VD=_],]x@<0~wH!x*O,YraF'D9́;T^{{/U^hna:y_d-. |Y=~T*v4:(pk?ģ1,&NZ֔4{zs֥0ʏ=XyGÔaP&!L0;FPe#|i4j04[2:ԊQ/QOsyjX54|{M1Fbn^>;x2UVNJMT pFFT1aB0X|ԋuhT:VkE%i啓I%Dt;K1-L-eutQ|ùr|Xʐb*M&eaA>BrfbSN:[67lak֋xr).N$`jWk)_cJKT^5 z̕4;U_8x+/mAj&>ĄՀh]DžOe\Hr -hp#ӊGk٧zJ&U_n$P)~oF״?&u;(C3´N9.;ۘZzu%0t~ejCg䊮8VP溎S?|WEsz鏗s/;{, n=y;sP=sX+/?K;Mn]g"(?0d@?x`4K 3aRpr0: WL:EG J ]YV)Ws%FT'L v;w|B%x$uT<+zikhwՠj|Z碑˺y`Ua\볠1 N2 Y߻ @2(L,OBwRų򷝗-C{zk'ͿNl{_MSP3ܮi;&~iolP ~b%hy/b:N8MHuxd%*Vl|+lKOuu#vӇ}_}[ePPC^ka-3kfuoȫElk "LWsN \~XݡYks3{! TFz+I4qX`Ġ1o*>w]_:x`T :T(2n2 D-7JlaT) F9$*3VAmAӶA+Me;&tF}^'n?xbsrcU0@a0mז!Ţ5~`W{o,j1pE(2jzleZm }%IX}񴗶#T?5Ʀ̹g=Yka{ FoX03Mz6{y7 -]2~ 'm 8κ=u}\??~;v|6ng1?^>8>7>UJy|7Stקsp?~|6i{ x{\gşw@`RJ^?0K[__쇷<-;~g}ܓ矝C7̧ c|\ϛuoۻ{~ߛl?^{:o>И? r^7_#k[׍}|w.ݔ5]'(7wrߜ}s݁'N.@7az6!Y3=uy5fk|;8t m|?v wڸ/|oVw<t~uc:{]{pl76麹8 ·vb~w,>7a3xo[JٰWn ?[C;/R>wPɧ;~yϗY{w=wO 2||꺽s|F[w?B<?;]s~wcUW~ ѹ[w-cW>wMIgw)//_߹O>UvOݍo=y+U+_.y_|}8h٨/^%@4qoϽ8;|_/1!upG_;Zp>J/s>[w9o'~~|~/p}޾X)dt 7;x# [4ɏp6*IDAT>p7U@}}=y:_#47?L~{}n~6 wm2AENiRM'r.e4riunSty_.|$`A @gٝ2;b)tNd2{H^  OekP~zw۽P|T7o!u2%|^캓uBAV"?[?xԪ%h%/P~FAlp#R'uOaY @$o|'G*Uޔ|.xB~1Kw,Ti8Xd+O}IAk;jQdːK"=X6Eg}~vVFۿ&5nrN^ DDKzqx=ۢO#!_|=ʫ>~k5\C(N~ @vݚŽ6Cb62<P{\рERM( fU'~{r25_AA ɉ<-龈zb}Y=-GT8IcKXPFKe?J](5i`hWD@՛Ĝ9.Cʳ8XW<9QXp>Nn/C<`hL1 jk|DFqY\HV,WMm8$W eʓ|23]&y^ϪUdg3c=c'C=v .4xdlO)zG@6KUJ|FnzN>>3$W31ڄ67zV/%-Gϓ'XK yu]6J=[ʏZ+XFhQdr3urcƦlrU$nOi \ydjW=9m?MR$ P.=?sD Iowr獳ppzdg7sJ`R&F xr;%o~ ^۞"|2bz6`NxWn$༒pS=R P_:~)"a(u>:|fz^cT8kq>i3\:JGf}'77xPgCtU-Go'5 ChW& ?q/k>5 ;!`~`F#W3"n"n_䭰v32[ʋ۵w.gu8xSzЅ6a-F=j<{"C p^GE|f܁*E ő]hP|2IE== 5W}AYhm/twDVK>ܱqI>"l syD)5&'0| ϘTB)\ffs!P cA>^̺P5諿ܦBeL![qy!,G X[n/^bK1sbkrŤ>U|(V$#fS {ck/M՛MޖגG&5~`ɯ>}TQJFD^i5E3*9._hR|t>J݃̕|WcPA ^pD 5=%˥=W[+̻ P㩻Gc֎k7c j$1ܣ+`=e!D\r3X}EK뫵8 cN&ڞpAW>Xjً\V{3}!c:ʜl?ʥlݳf7< P{ESVr>C]rW\P&5YMv[} a[." ]dʪ_QYܼFmjh[qSn=MSKLntK$1sRK5~Z'X 8<}og,b| EIkRH鴴?9 .K Mbqq>^Eǚ~ojorZܯv:<1۱{#b=Yⓟ^c+.ա/zqٖ!D@nX*?w촭ҮLwۯooyn[;NNK^:ʢ+_Pv1!zbn#Ӈ rè4ji^+6RѬSmy;څ&OEۜS݉y7ٮMMu0i_.ijŨբM[ײ?վdT׎0jlଛ I!d3:d[YEu]y3Vgg{Tdw_;K$;q2Ƌtiw!*lUai(դGNvn48'M z!d[so\0meӥ?dPUr0S~8E _Z Ehw8:|c\YR_rg{W:WAzq+ KǛz Lw`u?9-p=>lusr٨mZ @Dc"D @ aPÝV!@&Bz"D @ aPÝV!@&Bz"D @ aPÝV!@&Bz"D @ aPÝV!@&Bz"D @ aPÝV!@&Bz"D @ aPÝV!@&Bz"D @ aPÝV!@&Bz"D @ aPÝV!@&Bz"D @ aPÝV!@&Bz"D @ aPÝV!@&Bz"D @ aPÝV!@&Bz"D @ aPÝV!@&Bz"D @ aPÝV!@&Bz"D @ aPÝV!@&Bz"D @ aPÝVX&%no/HdX&9Zk+-S+f5Ukӻ,kz= 6R'Ic]6cU S+%(CL~&y-}T9>Efg:_\bTW}Cy춾 GC0`,C "?\iXě8*6\{vq>^EžhO˧iumD(]8V&kfq&kQߩQrʛxaZ]. &p*2[UZ*?j9o+dĹ||橺QG|Kַ$Y&օC&V/ LJ'ŬUf;aUY\l^LOS?Slr-1˳l3-ZsRwkolqS]17 oq [-QL?shN l-׻z<'I68:']+7<*8خ/KZdHYT1ru6+\=ĪSmoANf=悚,_'s7o'y]e/8 M݊9Dkva[np;B>:S]K!S˃8lo?,$ߖ2g[4O|]skIZҿcwp{/Rf{}hk" L_kUbs{n:mwrsӓ[Њv+kB%y.dyRI>v~1yns#FqZx  x s}A5o5#5^J,ю:bl~fzLiD] <-gtivd7"(fQ-nV\ˈx} yCfcrq:782Pa:i߰u,~H>#uӅHkHΖnvaqPKyQGAy駣."EL*?Gɉ\'u~RF"t܋VuÓt"߯6sͲwמ P;8 Dk;ﯳk :&zgf}W<$߃zcAvfh|lS?K,=_!Hfn {O:Uڢ?GvOKֳ+kUcʰpۯ% "@<@8íV[$cw b9(.xHV8?/H)}0F/|Kt-wM.5Pm>;|;/i; .!ɔAB>d]K<8 E !hxtuV')|x P?+4[fYY 8:d]8.Ngrvwr?\jb!('į~ yr8n:٧ddNvF L`#BdaJnT?r\D* [MFnn|  w쯽!:EKhmU"eC${Fmkh٧g=kfpۯ%a4@@#CQa&6 ~/K{6n|x#Y=Γ9&u"L"?xYFݲc}oy$%.wJGL IqC3gY[s"KG)meXl=[bЍY[ I]VII tuiX>o5oyyi84~v- @gpϐC8kf0sF9KB">b苙O:Id<TlG(~M}@1Ih8r=yЩBCp}?i%aOCz@w"K*Z^"?L,6qY81Uh*Σ(6,2ٶE|VWR.+DZS&^^L*)k-[Ⓢ[_'e_a:vke6uYcL~Tl{(\:ޢSZ6;;Mmc *:?Oe}߲n׉.^=GSf}SǙ>k8Xqd}rnů~)§1pV0ݐ+̶0U:gdMҖZV$nq,;5jM<prR.e5U"}F^Mǫ6^.1\~M{%uu[x\Ãc6ٛo_ae<}U9T  @4PkT@ G8Ԏ(@ Fm @p$Cb @PkT@ G8Ԏ(@ Fm @p$Cb @PkT@ G8Ԏ(@ Fm @p$Cb @PkT@ G8Ԏ(@ Fm @p$Cb @PkT@ G8Ԏ(@ Fm @p$Cb @PkT@ G8Ԏ(@ Fm @p$Cb @PkT@ G8Ԏ(@ Fm @p$Cb @PkT@ G8Ԏ(@ Fm @p$Cb @PkT@ G8Ԏ(@ Fm @p$Cb @PkT@ G8Ԏ(@ Fm @p$Cb @PkT@ G8Ԏ(@ Fm @p$Cb @PkT@ G8Ԏ(@ Fm @p$Cb @PkT@ G8Ԏ(@ SGDIENDB`RxPY-3.2.0/notebooks/reactivex.io/assets/js/000077500000000000000000000000001404130727200206775ustar00rootroot00000000000000RxPY-3.2.0/notebooks/reactivex.io/assets/js/ipython_notebook_toc.js000066400000000000000000000027371404130727200255050ustar00rootroot00000000000000// Converts integer to roman numeral function romanize(num) { var lookup = {M:1000,CM:900,D:500,CD:400,C:100,XC:90,L:50,XL:40,X:10,IX:9,V:5,IV:4,I:1}, roman = '', i; for ( i in lookup ) { while ( num >= lookup[i] ) { roman += i; num -= lookup[i]; } } return roman; } // Builds a

      Table of Contents from all in DOM function createTOC(){ var toc = ""; var level = 0; var levels = {} $('#toc').html(''); $(":header").each(function(i){ if (this.id=='tocheading'){return;} titleText = this.innerHTML; openLevel = this.tagName[1]; if (levels[openLevel]){ levels[openLevel] += 1; } else{ levels[openLevel] = 1; } if (openLevel > level) { toc += (new Array(openLevel - level + 1)).join('
        '); } else if (openLevel < level) { toc += (new Array(level - openLevel + 1)).join("
      "); for (i=level;i>openLevel;i--){levels[i]=0;} } level = parseInt(openLevel); if (this.id==''){this.id = this.innerHTML.replace(/ /g,"-")} var anchor = this.id; toc += '
    • ' + romanize(levels[openLevel].toString()) + '. ' + titleText + '
    • '; }); if (level) { toc += (new Array(level + 1)).join("
    "); } $('#toc').append(toc); }; // Executes the createToc function setTimeout(function(){createTOC();},100); // Rebuild to TOC every minute setInterval(function(){createTOC();},60000);RxPY-3.2.0/notebooks/reactivex.io/startup.py000066400000000000000000000067351404130727200210500ustar00rootroot00000000000000# Helpers. # Run this cell always after kernel restarts. All other cells are autonomous. from __future__ import print_function import rx import time import inspect import logging from random import randint from rx.testing import marbles logging.basicConfig(format="%(threadName)s:%(message)s") log = logging.getLogger("Rx") log.setLevel(logging.WARNING) sleep, now = time.sleep, time.time O = rx.Observable ts_glob = 0 # global start time def reset_start_time(show_doc_for=None, title=True, sleep=None): 'resets global start time and also prints doc strings' global ts_glob if sleep: log('main thread sleeping %ss' % sleep) time.sleep(sleep) ts_glob, d = time.time(), show_doc_for if title: if d: if title == True: title = d.__name__ header(title) if not d: return # print the function sig and doc if given # py2 compatible way: deco, fdef = inspect.getsource(d).split('def ', 1) fdef = 'def '.join((deco, fdef.split(')', 1)[0])) + '):' print ('module %s\n%s\n%s\n%s' % (d.__module__, fdef.strip(), (' ' + (d.__doc__ or 'n.a.').strip()), '-' * 80)) rst = reset_start_time def log(*msg): s = ' '.join([str(s) for s in msg]) print ('%s %s %s' % (dt(ts_glob), cur_thread(), s)) def header(msg): print ('\n\n%s %s %s\n' % ('=' * 10, msg, '=' * 10)) def rand(): return randint(100, 999) def to_int(s): return int(s) if s.isdigit() else s def dt(ts): # the time delta of now to given ts (in millis, 1 float) return str('%.1f' % ((time.time() - ts) * 1000)).rjust(6) class ItemGetter: 'allows to throw an object onto a format string' def __init__(self, obj): self.obj = obj def __getitem__(self, k, d=None): return getattr(self.obj, k, d) class Subscriber: def __init__(self, observed_stream, **kw): print ('') name = kw.get('name', str(hash(self))[-5:]) log('New subscription (%s) on stream' % str(name).strip(), hash(observed_stream)) self.ts = time.time() # tstart, for dts at events # no postifx after name, sometimes it ends with '\n': self.name = name def _on(self, what, v=''): print ('%s %s [%s] %s: %s -> %s' % ( dt(ts_glob), cur_thread(), what, dt(self.ts), v, self.name)) def on_next (self, v): return self._on('next', v) def on_error (self, v): return self._on('err ', v) def on_completed(self) : return self._on('cmpl', 'fin') def subs(src, **kw): # required for e.g. .multicast: obs = Subscriber(src, **kw) subscription = src.subscribe(obs) if kw.pop('return_subscriber', None): return subscription, obs return subscription # getting the current thread import threading threads = [] def cur_thread(): def _cur(): 'return a unique number for the current thread' n = threading.current_thread().name if 'Main' in n: return ' M' return '%5s' % ('T' + n.rsplit('-', 1)[-1]) # you could show all running threads via this: #threads = ' '.join([t.name for t in threading.enumerate()]) #return '%s of %s' % (_cur(), threads) return _cur() from rx.scheduler import new_thread_scheduler, timeout_scheduler from rx.subject import Subject from rx.testing import marbles, dump def marble_stream(s): return O.from_marbles(s).to_blocking() RxPY-3.2.0/project.cfg000066400000000000000000000006121404130727200145040ustar00rootroot00000000000000[project] name = Rx project = RxPY # Please make sure the version here remains the same as in rx/__init__.py version = 3.2.0 description = Reactive Extensions (Rx) for Python author = Dag Brattli author_email = dag@brattli.net copyright = 2013-2020, Dag Brattli, Microsoft Corp., and Contributors license = MIT License url = http://reactivex.io download_url = https://github.com/ReactiveX/RxPY RxPY-3.2.0/pytest.ini000066400000000000000000000000321404130727200144020ustar00rootroot00000000000000[pytest] testpaths = testsRxPY-3.2.0/requirements.txt000066400000000000000000000000271404130727200156410ustar00rootroot00000000000000pytest coveralls flake8RxPY-3.2.0/rx/000077500000000000000000000000001404130727200130075ustar00rootroot00000000000000RxPY-3.2.0/rx/__init__.py000066400000000000000000001075451404130727200151340ustar00rootroot00000000000000# pylint: disable=too-many-lines,redefined-outer-name,redefined-builtin from asyncio.futures import Future as _Future from typing import Iterable, Callable, Any, Optional, Union, Mapping from .core import Observable, pipe, typing from .internal.utils import alias # Please make sure the version here remains the same as in project.cfg __version__ = '3.2.0' def amb(*sources: Observable) -> Observable: """Propagates the observable sequence that emits first. .. marble:: :alt: amb ---8--6--9-----------| --1--2--3---5--------| ----------10-20-30---| [ amb() ] --1--2--3---5--------| Example: >>> winner = rx.amb(xs, ys, zs) Args: sources: Sequence of observables to monitor for first emission. Returns: An observable sequence that surfaces any of the given sequences, whichever emitted the first element. """ from .core.observable.amb import _amb return _amb(*sources) def case(mapper: Callable[[], Any], sources: Mapping, default_source: Optional[Union[Observable, _Future]] = None ) -> Observable: """Uses mapper to determine which source in sources to use. .. marble:: :alt: case --1---------------| a--1--2--3--4--| b--10-20-30---| [case(mapper, { 1: a, 2: b })] ---1--2--3--4--| Examples: >>> res = rx.case(mapper, { '1': obs1, '2': obs2 }) >>> res = rx.case(mapper, { '1': obs1, '2': obs2 }, obs0) Args: mapper: The function which extracts the value for to test in a case statement. sources: An object which has keys which correspond to the case statement labels. default_source: [Optional] The observable sequence or Future that will be run if the sources are not matched. If this is not provided, it defaults to :func:`empty`. Returns: An observable sequence which is determined by a case statement. """ from .core.observable.case import _case return _case(mapper, sources, default_source) def catch(*sources: Observable) -> Observable: """Continues observable sequences which are terminated with an exception by switching over to the next observable sequence. .. marble:: :alt: catch ---1---2---3-* a-7-8-| [ catch(a) ] ---1---2---3---7-8-| Examples: >>> res = rx.catch(xs, ys, zs) Args: sources: Sequence of observables. Returns: An observable sequence containing elements from consecutive observables from the sequence of sources until one of them terminates successfully. """ from .core.observable.catch import _catch_with_iterable return _catch_with_iterable(sources) def catch_with_iterable(sources: Iterable[Observable]) -> Observable: """Continues observable sequences that are terminated with an exception by switching over to the next observable sequence. .. marble:: :alt: catch ---1---2---3-* a-7-8-| [ catch(a) ] ---1---2---3---7-8-| Examples: >>> res = rx.catch([xs, ys, zs]) >>> res = rx.catch(src for src in [xs, ys, zs]) Args: sources: An Iterable of observables; thus, a generator can also be used here. Returns: An observable sequence containing elements from consecutive observables from the sequence of sources until one of them terminates successfully. """ from .core.observable.catch import _catch_with_iterable return _catch_with_iterable(sources) def create(subscribe: typing.Subscription) -> Observable: """Creates an observable sequence object from the specified subscription function. .. marble:: :alt: create [ create(a) ] ---1---2---3---4---| Args: subscribe: Subscription function. Returns: An observable sequence that can be subscribed to via the given subscription function. """ return Observable(subscribe) def combine_latest(*sources: Observable) -> Observable: """Merges the specified observable sequences into one observable sequence by creating a tuple whenever any of the observable sequences emits an element. .. marble:: :alt: combine_latest ---a-----b--c------| --1---2--------3---| [ combine_latest() ] ---a1-a2-b2-c2-c3--| Examples: >>> obs = rx.combine_latest(obs1, obs2, obs3) Args: sources: Sequence of observables. Returns: An observable sequence containing the result of combining elements from each source in given sequence. """ from .core.observable.combinelatest import _combine_latest return _combine_latest(*sources) def concat(*sources: Observable) -> Observable: """Concatenates all of the specified observable sequences. .. marble:: :alt: concat ---1--2--3--| --6--8--| [ concat() ] ---1--2--3----6--8-| Examples: >>> res = rx.concat(xs, ys, zs) Args: sources: Sequence of observables. Returns: An observable sequence that contains the elements of each source in the given sequence, in sequential order. """ from .core.observable.concat import _concat_with_iterable return _concat_with_iterable(sources) def concat_with_iterable(sources: Iterable[Observable]) -> Observable: """Concatenates all of the specified observable sequences. .. marble:: :alt: concat ---1--2--3--| --6--8--| [ concat() ] ---1--2--3----6--8-| Examples: >>> res = rx.concat_with_iterable([xs, ys, zs]) >>> res = rx.concat_with_iterable(for src in [xs, ys, zs]) Args: sources: An Iterable of observables; thus, a generator can also be used here. Returns: An observable sequence that contains the elements of each given sequence, in sequential order. """ from .core.observable.concat import _concat_with_iterable return _concat_with_iterable(sources) def defer(factory: Callable[[typing.Scheduler], Union[Observable, _Future]] ) -> Observable: """Returns an observable sequence that invokes the specified factory function whenever a new observer subscribes. .. marble:: :alt: defer [ defer(1,2,3) ] ---1--2--3--| ---1--2--3--| Example: >>> res = rx.defer(lambda: of(1, 2, 3)) Args: factory: Observable factory function to invoke for each observer which invokes :func:`subscribe() ` on the resulting sequence. Returns: An observable sequence whose observers trigger an invocation of the given factory function. """ from .core.observable.defer import _defer return _defer(factory) def empty(scheduler: Optional[typing.Scheduler] = None) -> Observable: """Returns an empty observable sequence. .. marble:: :alt: empty [ empty() ] --| Example: >>> obs = rx.empty() Args: scheduler: [Optional] Scheduler instance to send the termination call on. By default, this will use an instance of :class:`ImmediateScheduler `. Returns: An observable sequence with no elements. """ from .core.observable.empty import _empty return _empty(scheduler) def for_in(values: Iterable[Any], mapper: typing.Mapper) -> Observable: """Concatenates the observable sequences obtained by running the specified result mapper for each element in the specified values. .. marble:: :alt: for_in a--1--2-| b--10--20-| [for_in((a, b), lambda i: i+1)] ---2--3--11--21-| Note: This is just a wrapper for :func:`rx.concat(map(mapper, values)) ` Args: values: An Iterable of values to turn into an observable source. mapper: A function to apply to each item in the values list to turn it into an observable sequence; this should return instances of :class:`rx.Observable`. Returns: An observable sequence from the concatenated observable sequences. """ return concat_with_iterable(map(mapper, values)) def fork_join(*sources: Observable) -> Observable: """Wait for observables to complete and then combine last values they emitted into a tuple. Whenever any of that observables completes without emitting any value, result sequence will complete at that moment as well. .. marble:: :alt: fork_join ---a-----b--c---d-| --1---2------3-4---| -a---------b---| [ fork_join() ] --------------------d4b| Examples: >>> obs = rx.fork_join(obs1, obs2, obs3) Args: sources: Sequence of observables. Returns: An observable sequence containing the result of combining last element from each source in given sequence. """ from .core.observable.forkjoin import _fork_join return _fork_join(*sources) def from_callable(supplier: Callable[[], Any], scheduler: Optional[typing.Scheduler] = None ) -> Observable: """Returns an observable sequence that contains a single element generated by the given supplier, using the specified scheduler to send out observer messages. .. marble:: :alt: from_callable [ from_callable() ] --1--| Examples: >>> res = rx.from_callable(lambda: calculate_value()) >>> res = rx.from_callable(lambda: 1 / 0) # emits an error Args: supplier: Function which is invoked to obtain the single element. scheduler: [Optional] Scheduler instance to schedule the values on. If not specified, the default is to use an instance of :class:`CurrentThreadScheduler `. Returns: An observable sequence containing the single element obtained by invoking the given supplier function. """ from .core.observable.returnvalue import _from_callable return _from_callable(supplier, scheduler) def from_callback(func: Callable, mapper: Optional[typing.Mapper] = None ) -> Callable[[], Observable]: """Converts a callback function to an observable sequence. Args: func: Function with a callback as the last argument to convert to an Observable sequence. mapper: [Optional] A mapper which takes the arguments from the callback to produce a single item to yield on next. Returns: A function, when executed with the required arguments minus the callback, produces an Observable sequence with a single value of the arguments to the callback as a list. """ from .core.observable.fromcallback import _from_callback return _from_callback(func, mapper) def from_future(future: _Future) -> Observable: """Converts a Future to an Observable sequence .. marble:: :alt: from_future [ from_future() ] ------1| Args: future: A Python 3 compatible future. https://docs.python.org/3/library/asyncio-task.html#future http://www.tornadoweb.org/en/stable/concurrent.html#tornado.concurrent.Future Returns: An observable sequence which wraps the existing future success and failure. """ from .core.observable.fromfuture import _from_future return _from_future(future) def from_iterable(iterable: Iterable, scheduler: Optional[typing.Scheduler] = None) -> Observable: """Converts an iterable to an observable sequence. .. marble:: :alt: from_iterable [ from_iterable(1,2,3) ] ---1--2--3--| Example: >>> rx.from_iterable([1,2,3]) Args: iterable: An Iterable to change into an observable sequence. scheduler: [Optional] Scheduler instance to schedule the values on. If not specified, the default is to use an instance of :class:`CurrentThreadScheduler `. Returns: The observable sequence whose elements are pulled from the given iterable sequence. """ from .core.observable.fromiterable import from_iterable as from_iterable_ return from_iterable_(iterable, scheduler) from_ = alias('from_', 'Alias for :func:`rx.from_iterable`.', from_iterable) from_list = alias('from_list', 'Alias for :func:`rx.from_iterable`.', from_iterable) def from_marbles(string: str, timespan: typing.RelativeTime = 0.1, scheduler: Optional[typing.Scheduler] = None, lookup: Optional[Mapping] = None, error: Optional[Exception] = None ) -> Observable: """Convert a marble diagram string to a cold observable sequence, using an optional scheduler to enumerate the events. .. marble:: :alt: from_marbles [ from_marbles(-1-2-3-) ] -1-2-3-| Each character in the string will advance time by timespan (except for space). Characters that are not special (see the table below) will be interpreted as a value to be emitted. Numbers will be cast to int or float. Special characters: +------------+--------------------------------------------------------+ | :code:`-` | advance time by timespan | +------------+--------------------------------------------------------+ | :code:`#` | on_error() | +------------+--------------------------------------------------------+ | :code:`|` | on_completed() | +------------+--------------------------------------------------------+ | :code:`(` | open a group of marbles sharing the same timestamp | +------------+--------------------------------------------------------+ | :code:`)` | close a group of marbles | +------------+--------------------------------------------------------+ | :code:`,` | separate elements in a group | +------------+--------------------------------------------------------+ | | used to align multiple diagrams, does not advance time | +------------+--------------------------------------------------------+ In a group of elements, the position of the initial :code:`(` determines the timestamp at which grouped elements will be emitted. E.g. :code:`--(12,3,4)--` will emit 12, 3, 4 at 2 * timespan and then advance virtual time by 8 * timespan. Examples: >>> from_marbles('--1--(2,3)-4--|') >>> from_marbles('a--b--c-', lookup={'a': 1, 'b': 2, 'c': 3}) >>> from_marbles('a--b---#', error=ValueError('foo')) Args: string: String with marble diagram timespan: [Optional] Duration of each character in seconds. If not specified, defaults to :code:`0.1`. scheduler: [Optional] Scheduler to run the the input sequence on. If not specified, defaults to the subscribe scheduler if defined, else to an instance of :class:`NewThreadScheduler Observable: """Generates an observable sequence by iterating a state from an initial state until the condition fails. .. marble:: :alt: generate_with_relative_time [generate_with_relative_time()] -1-2-3-4-| Example: >>> res = rx.generate_with_relative_time(0, lambda x: True, lambda x: x + 1, lambda x: 0.5) Args: initial_state: Initial state. condition: Condition to terminate generation (upon returning :code:`False`). iterate: Iteration step function. time_mapper: Time mapper function to control the speed of values being produced each iteration, returning relative times, i.e. either a :class:`float` denoting seconds, or an instance of :class:`timedelta`. Returns: The generated sequence. """ from .core.observable.generatewithrelativetime import _generate_with_relative_time return _generate_with_relative_time(initial_state, condition, iterate, time_mapper) def generate(initial_state: Any, condition: typing.Predicate, iterate: typing.Mapper ) -> Observable: """Generates an observable sequence by running a state-driven loop producing the sequence's elements. .. marble:: :alt: generate [ generate() ] -1-2-3-4-| Example: >>> res = rx.generate(0, lambda x: x < 10, lambda x: x + 1) Args: initial_state: Initial state. condition: Condition to terminate generation (upon returning :code:`False`). iterate: Iteration step function. Returns: The generated sequence. """ from .core.observable.generate import _generate return _generate(initial_state, condition, iterate) def hot(string: str, timespan: typing.RelativeTime = 0.1, duetime: typing.AbsoluteOrRelativeTime = 0.0, scheduler: Optional[typing.Scheduler] = None, lookup: Optional[Mapping] = None, error: Optional[Exception] = None ) -> Observable: """Convert a marble diagram string to a hot observable sequence, using an optional scheduler to enumerate the events. .. marble:: :alt: hot [ from_marbles(-1-2-3-) ] -1-2-3-| -2-3-| Each character in the string will advance time by timespan (except for space). Characters that are not special (see the table below) will be interpreted as a value to be emitted. Numbers will be cast to int or float. Special characters: +------------+--------------------------------------------------------+ | :code:`-` | advance time by timespan | +------------+--------------------------------------------------------+ | :code:`#` | on_error() | +------------+--------------------------------------------------------+ | :code:`|` | on_completed() | +------------+--------------------------------------------------------+ | :code:`(` | open a group of elements sharing the same timestamp | +------------+--------------------------------------------------------+ | :code:`)` | close a group of elements | +------------+--------------------------------------------------------+ | :code:`,` | separate elements in a group | +------------+--------------------------------------------------------+ | | used to align multiple diagrams, does not advance time | +------------+--------------------------------------------------------+ In a group of elements, the position of the initial :code:`(` determines the timestamp at which grouped elements will be emitted. E.g. :code:`--(12,3,4)--` will emit 12, 3, 4 at 2 * timespan and then advance virtual time by 8 * timespan. Examples: >>> hot("--1--(2,3)-4--|") >>> hot("a--b--c-", lookup={'a': 1, 'b': 2, 'c': 3}) >>> hot("a--b---#", error=ValueError("foo")) Args: string: String with marble diagram timespan: [Optional] Duration of each character in seconds. If not specified, defaults to :code:`0.1`. duetime: [Optional] Absolute datetime or timedelta from now that determines when to start the emission of elements. scheduler: [Optional] Scheduler to run the the input sequence on. If not specified, defaults to an instance of :class:`NewThreadScheduler `. lookup: [Optional] A dict used to convert an element into a specified value. If not specified, defaults to :code:`{}`. error: [Optional] Exception that will be use in place of the :code:`#` symbol. If not specified, defaults to :code:`Exception('error')`. Returns: The observable sequence whose elements are pulled from the given marble diagram string. """ from .core.observable.marbles import hot as _hot return _hot(string, timespan, duetime, lookup=lookup, error=error, scheduler=scheduler) def if_then(condition: Callable[[], bool], then_source: Union[Observable, _Future], else_source: Union[None, Observable, _Future] = None ) -> Observable: """Determines whether an observable collection contains values. .. marble:: :alt: if_then ---1--2--3--| --6--8--| [ if_then() ] ---1--2--3--| Examples: >>> res = rx.if_then(condition, obs1) >>> res = rx.if_then(condition, obs1, obs2) Args: condition: The condition which determines if the then_source or else_source will be run. then_source: The observable sequence or :class:`Future` that will be run if the condition function returns :code:`True`. else_source: [Optional] The observable sequence or :class:`Future` that will be run if the condition function returns :code:`False`. If this is not provided, it defaults to :func:`empty() `. Returns: An observable sequence which is either the then_source or else_source. """ from .core.observable.ifthen import _if_then return _if_then(condition, then_source, else_source) def interval(period: typing.RelativeTime, scheduler: Optional[typing.Scheduler] = None) -> Observable: """Returns an observable sequence that produces a value after each period. .. marble:: :alt: interval [ interval() ] ---1---2---3---4---> Example: >>> res = rx.interval(1.0) Args: period: Period for producing the values in the resulting sequence (specified as a :class:`float` denoting seconds or an instance of :class:`timedelta`). scheduler: Scheduler to run the interval on. If not specified, an instance of :class:`TimeoutScheduler ` is used. Returns: An observable sequence that produces a value after each period. """ from .core.observable.interval import _interval return _interval(period, scheduler) def merge(*sources: Observable) -> Observable: """Merges all the observable sequences into a single observable sequence. .. marble:: :alt: merge ---1---2---3---4-| -a---b---c---d--| [ merge() ] -a-1-b-2-c-3-d-4-| Example: >>> res = rx.merge(obs1, obs2, obs3) Args: sources: Sequence of observables. Returns: The observable sequence that merges the elements of the observable sequences. """ from .core.observable.merge import _merge return _merge(*sources) def never() -> Observable: """Returns a non-terminating observable sequence, which can be used to denote an infinite duration (e.g. when using reactive joins). .. marble:: :alt: never [ never() ] --> Returns: An observable sequence whose observers will never get called. """ from .core.observable.never import _never return _never() def of(*args: Any) -> Observable: """This method creates a new observable sequence whose elements are taken from the arguments. .. marble:: :alt: of [ of(1,2,3) ] ---1--2--3--| Note: This is just a wrapper for :func:`rx.from_iterable(args) ` Example: >>> res = rx.of(1,2,3) Args: args: The variable number elements to emit from the observable. Returns: The observable sequence whose elements are pulled from the given arguments """ return from_iterable(args) def on_error_resume_next(*sources: Union[Observable, _Future]) -> Observable: """Continues an observable sequence that is terminated normally or by an exception with the next observable sequence. .. marble:: :alt: on_error_resume_next --1--2--* a--3--4--* b--6-| [on_error_resume_next(a,b)] --1--2----3--4----6-| Examples: >>> res = rx.on_error_resume_next(xs, ys, zs) Args: sources: Sequence of sources, each of which is expected to be an instance of either :class:`Observable` or :class:`Future`. Returns: An observable sequence that concatenates the source sequences, even if a sequence terminates with an exception. """ from .core.observable.onerrorresumenext import _on_error_resume_next return _on_error_resume_next(*sources) def range(start: int, stop: Optional[int] = None, step: Optional[int] = None, scheduler: Optional[typing.Scheduler] = None ) -> Observable: """Generates an observable sequence of integral numbers within a specified range, using the specified scheduler to send out observer messages. .. marble:: :alt: range [ range(4) ] --0--1--2--3--| Examples: >>> res = rx.range(10) >>> res = rx.range(0, 10) >>> res = rx.range(0, 10, 1) Args: start: The value of the first integer in the sequence. count: The number of sequential integers to generate. scheduler: [Optional] The scheduler to schedule the values on. If not specified, the default is to use an instance of :class:`CurrentThreadScheduler `. Returns: An observable sequence that contains a range of sequential integral numbers. """ from .core.observable.range import _range return _range(start, stop, step, scheduler) def return_value(value: Any, scheduler: Optional[typing.Scheduler] = None) -> Observable: """Returns an observable sequence that contains a single element, using the specified scheduler to send out observer messages. There is an alias called 'just'. .. marble:: :alt: return_value [ return_value(4) ] -4-| Examples: >>> res = rx.return_value(42) >>> res = rx.return_value(42, timeout_scheduler) Args: value: Single element in the resulting observable sequence. Returns: An observable sequence containing the single specified element. """ from .core.observable.returnvalue import _return_value return _return_value(value, scheduler) just = alias('just', 'Alias for :func:`rx.return_value`.', return_value) def repeat_value(value: Any = None, repeat_count: Optional[int] = None) -> Observable: """Generates an observable sequence that repeats the given element the specified number of times. .. marble:: :alt: repeat_value [ repeat_value(4) ] -4-4-4-4-> Examples: >>> res = rx.repeat_value(42) >>> res = rx.repeat_value(42, 4) Args: value: Element to repeat. repeat_count: [Optional] Number of times to repeat the element. If not specified, repeats indefinitely. Returns: An observable sequence that repeats the given element the specified number of times. """ from .core.observable.repeat import _repeat_value return _repeat_value(value, repeat_count) def start(func: Callable, scheduler: Optional[typing.Scheduler] = None) -> Observable: """Invokes the specified function asynchronously on the specified scheduler, surfacing the result through an observable sequence. .. marble:: :alt: start [ start(lambda i: return 4) ] -4-| -4-| Note: The function is called immediately, not during the subscription of the resulting sequence. Multiple subscriptions to the resulting sequence can observe the function's result. Example: >>> res = rx.start(lambda: pprint('hello')) >>> res = rx.start(lambda: pprint('hello'), rx.Scheduler.timeout) Args: func: Function to run asynchronously. scheduler: [Optional] Scheduler to run the function on. If not specified, defaults to an instance of :class:`TimeoutScheduler `. Returns: An observable sequence exposing the function's result value, or an exception. """ from .core.observable.start import _start return _start(func, scheduler) def start_async(function_async: Callable[[], _Future]) -> Observable: """Invokes the asynchronous function, surfacing the result through an observable sequence. .. marble:: :alt: start_async [ start_async() ] ------1| Args: function_async: Asynchronous function which returns a :class:`Future` to run. Returns: An observable sequence exposing the function's result value, or an exception. """ from .core.observable.startasync import _start_async return _start_async(function_async) def throw(exception: Exception, scheduler: Optional[typing.Scheduler] = None) -> Observable: """Returns an observable sequence that terminates with an exception, using the specified scheduler to send out the single OnError message. .. marble:: :alt: throw [ throw() ] -* Example: >>> res = rx.throw(Exception('Error')) Args: exception: An object used for the sequence's termination. scheduler: [Optional] Scheduler to schedule the error notification on. If not specified, the default is to use an instance of :class:`ImmediateScheduler `. Returns: The observable sequence that terminates exceptionally with the specified exception object. """ from .core.observable.throw import _throw return _throw(exception, scheduler) def timer(duetime: typing.AbsoluteOrRelativeTime, period: Optional[typing.RelativeTime] = None, scheduler: Optional[typing.Scheduler] = None) -> Observable: """Returns an observable sequence that produces a value after duetime has elapsed and then after each period. .. marble:: :alt: timer [ timer(2) ] --0-| Examples: >>> res = rx.timer(datetime(...)) >>> res = rx.timer(datetime(...), 0.1) >>> res = rx.timer(5.0) >>> res = rx.timer(5.0, 1.0) Args: duetime: Absolute (specified as a datetime object) or relative time (specified as a float denoting seconds or an instance of timedelta) at which to produce the first value. period: [Optional] Period to produce subsequent values (specified as a float denoting seconds or an instance of timedelta). If not specified, the resulting timer is not recurring. scheduler: [Optional] Scheduler to run the timer on. If not specified, the default is to use an instance of :class:`TimeoutScheduler `. Returns: An observable sequence that produces a value after due time has elapsed and then each period. """ from .core.observable.timer import _timer return _timer(duetime, period, scheduler) def to_async(func: Callable, scheduler: Optional[typing.Scheduler] = None) -> Callable: """Converts the function into an asynchronous function. Each invocation of the resulting asynchronous function causes an invocation of the original synchronous function on the specified scheduler. .. marble:: :alt: to_async [ to_async()() ] ------1| Examples: >>> res = rx.to_async(lambda x, y: x + y)(4, 3) >>> res = rx.to_async(lambda x, y: x + y, Scheduler.timeout)(4, 3) >>> res = rx.to_async(lambda x: log.debug(x), Scheduler.timeout)('hello') Args: func: Function to convert to an asynchronous function. scheduler: [Optional] Scheduler to run the function on. If not specified, defaults to an instance of :class:`TimeoutScheduler `. Returns: Asynchronous function. """ from .core.observable.toasync import _to_async return _to_async(func, scheduler) def using(resource_factory: Callable[[], typing.Disposable], observable_factory: Callable[[typing.Disposable], Observable] ) -> Observable: """Constructs an observable sequence that depends on a resource object, whose lifetime is tied to the resulting observable sequence's lifetime. Example: >>> res = rx.using(lambda: AsyncSubject(), lambda: s: s) Args: resource_factory: Factory function to obtain a resource object. observable_factory: Factory function to obtain an observable sequence that depends on the obtained resource. Returns: An observable sequence whose lifetime controls the lifetime of the dependent resource object. """ from .core.observable.using import _using return _using(resource_factory, observable_factory) def with_latest_from(*sources: Observable) -> Observable: """Merges the specified observable sequences into one observable sequence by creating a :class:`tuple` only when the first observable sequence produces an element. .. marble:: :alt: with_latest_from ---1---2---3----4-| --a-----b----c-d----| [with_latest_from() ] ---1,a-2,a-3,b--4,d-| Examples: >>> obs = rx.with_latest_from(obs1) >>> obs = rx.with_latest_from([obs1, obs2, obs3]) Args: sources: Sequence of observables. Returns: An observable sequence containing the result of combining elements of the sources into a :class:`tuple`. """ from .core.observable.withlatestfrom import _with_latest_from return _with_latest_from(*sources) def zip(*args: Observable) -> Observable: """Merges the specified observable sequences into one observable sequence by creating a :class:`tuple` whenever all of the observable sequences have produced an element at a corresponding index. .. marble:: :alt: zip --1--2---3-----4---| -a----b----c-d------| [ zip() ] --1,a-2,b--3,c-4,d-| Example: >>> res = rx.zip(obs1, obs2) Args: args: Observable sources to zip. Returns: An observable sequence containing the result of combining elements of the sources as a :class:`tuple`. """ from .core.observable.zip import _zip return _zip(*args) RxPY-3.2.0/rx/core/000077500000000000000000000000001404130727200137375ustar00rootroot00000000000000RxPY-3.2.0/rx/core/__init__.py000066400000000000000000000002521404130727200160470ustar00rootroot00000000000000# flake8: noqa from .pipe import pipe from .observable import Observable, ConnectableObservable from .observable import GroupedObservable from .observer import Observer RxPY-3.2.0/rx/core/abc/000077500000000000000000000000001404130727200144645ustar00rootroot00000000000000RxPY-3.2.0/rx/core/abc/__init__.py000066400000000000000000000003651404130727200166010ustar00rootroot00000000000000from .disposable import Disposable from .observable import Observable from .observer import Observer from .scheduler import Scheduler from .periodicscheduler import PeriodicScheduler from .startable import Startable from .subject import Subject RxPY-3.2.0/rx/core/abc/asyncobservable.py000066400000000000000000000003151404130727200202170ustar00rootroot00000000000000from abc import ABCMeta, abstractmethod class AsyncObservable(metaclass=ABCMeta): __slots__ = () @abstractmethod async def subscribe_async(self, observer): raise NotImplementedError RxPY-3.2.0/rx/core/abc/asyncobserver.py000066400000000000000000000010101404130727200177130ustar00rootroot00000000000000from abc import abstractmethod from .asyncobservable import AsyncObservable class AsyncObserver(AsyncObservable): """An asynchronous observable.""" __slots__ = () @abstractmethod async def on_next_async(self, value): return NotImplemented @abstractmethod async def on_error_async(self, error): return NotImplemented @abstractmethod async def on_completed_async(self): return NotImplemented async def subscribe_async(self, observer): return self RxPY-3.2.0/rx/core/abc/disposable.py000066400000000000000000000005651404130727200171710ustar00rootroot00000000000000from abc import ABC, abstractmethod class Disposable(ABC): """Disposable abstract base class. Untyped.""" @abstractmethod def dispose(self): raise NotImplementedError def __enter__(self): """Context management protocol.""" def __exit__(self, typ, value, traceback): """Context management protocol.""" self.dispose() RxPY-3.2.0/rx/core/abc/observable.py000066400000000000000000000003661404130727200171670ustar00rootroot00000000000000from abc import ABC, abstractmethod class Observable(ABC): """Observable abstract base class. Untyped.""" __slots__ = () @abstractmethod def subscribe(self, observer=None, *, scheduler=None): raise NotImplementedError RxPY-3.2.0/rx/core/abc/observer.py000066400000000000000000000005761404130727200166750ustar00rootroot00000000000000from abc import ABC, abstractmethod class Observer(ABC): """Observer abstract base class. Untyped.""" __slots__ = () @abstractmethod def on_next(self, value): raise NotImplementedError @abstractmethod def on_error(self, error): raise NotImplementedError @abstractmethod def on_completed(self): raise NotImplementedError RxPY-3.2.0/rx/core/abc/periodicscheduler.py000066400000000000000000000003561404130727200205370ustar00rootroot00000000000000from abc import ABC, abstractmethod class PeriodicScheduler(ABC): """PeriodicScheduler abstract base class. Untyped.""" @abstractmethod def schedule_periodic(self, period, action, state=None): return NotImplemented RxPY-3.2.0/rx/core/abc/scheduler.py000066400000000000000000000014621404130727200170170ustar00rootroot00000000000000from abc import ABC, abstractmethod class Scheduler(ABC): """Scheduler abstract base class. Untyped.""" @property @abstractmethod def now(self): return NotImplemented @abstractmethod def schedule(self, action, state=None): return NotImplemented @abstractmethod def schedule_relative(self, duetime, action, state=None): return NotImplemented @abstractmethod def schedule_absolute(self, duetime, action, state=None): return NotImplemented @classmethod @abstractmethod def to_seconds(cls, value): return NotImplemented @classmethod @abstractmethod def to_datetime(cls, value): return NotImplemented @classmethod @abstractmethod def to_timedelta(cls, value): return NotImplemented RxPY-3.2.0/rx/core/abc/startable.py000066400000000000000000000003471404130727200170230ustar00rootroot00000000000000from abc import ABC, abstractmethod class Startable(ABC): """Abstract base class for Thread- and Process-like objects.""" __slots__ = () @abstractmethod def start(self) -> None: raise NotImplementedError RxPY-3.2.0/rx/core/abc/subject.py000066400000000000000000000010741404130727200164770ustar00rootroot00000000000000from abc import abstractmethod from .observable import Observable from .observer import Observer class Subject(Observer, Observable): """Subject abstract base class. Untyped.""" __slots__ = () @abstractmethod def on_next(self, value): raise NotImplementedError @abstractmethod def on_error(self, error): raise NotImplementedError @abstractmethod def on_completed(self): raise NotImplementedError @abstractmethod def subscribe(self, observer=None, *, scheduler=None): raise NotImplementedError RxPY-3.2.0/rx/core/notification.py000066400000000000000000000112471404130727200170040ustar00rootroot00000000000000from abc import abstractmethod from rx.scheduler import ImmediateScheduler from .. import typing from .observer import Observer from .observable import Observable class Notification: """Represents a notification to an observer.""" def __init__(self) -> None: """Default constructor used by derived types.""" self.has_value = False self.value = None self.kind = '' def accept(self, on_next, on_error=None, on_completed=None): """Invokes the delegate corresponding to the notification or an observer and returns the produced result. Examples: >>> notification.accept(observer) >>> notification.accept(on_next, on_error, on_completed) Args: on_next: Delegate to invoke for an OnNext notification. on_error: [Optional] Delegate to invoke for an OnError notification. on_completed: [Optional] Delegate to invoke for an OnCompleted notification. Returns: Result produced by the observation.""" if isinstance(on_next, typing.Observer): return self._accept_observer(on_next) return self._accept(on_next, on_error, on_completed) @abstractmethod def _accept(self, on_next, on_error, on_completed): raise NotImplementedError @abstractmethod def _accept_observer(self, observer): raise NotImplementedError def to_observable(self, scheduler=None): """Returns an observable sequence with a single notification, using the specified scheduler, else the immediate scheduler. Args: scheduler: [Optional] Scheduler to send out the notification calls on. Returns: An observable sequence that surfaces the behavior of the notification upon subscription. """ scheduler = scheduler or ImmediateScheduler.singleton() def subscribe(observer, scheduler=None): def action(scheduler, state): self._accept_observer(observer) if self.kind == 'N': observer.on_completed() return scheduler.schedule(action) return Observable(subscribe) def equals(self, other): """Indicates whether this instance and a specified object are equal.""" other_string = '' if not other else str(other) return str(self) == other_string def __eq__(self, other): return self.equals(other) class OnNext(Notification): """Represents an OnNext notification to an observer.""" def __init__(self, value): """Constructs a notification of a new value.""" super(OnNext, self).__init__() self.value = value self.has_value = True self.kind = 'N' def _accept(self, on_next, on_error=None, on_completed=None): return on_next(self.value) def _accept_observer(self, observer): return observer.on_next(self.value) def __str__(self): val = self.value if isinstance(val, int): val = float(val) return "OnNext(%s)" % str(val) class OnError(Notification): """Represents an OnError notification to an observer.""" def __init__(self, exception): """Constructs a notification of an exception.""" super(OnError, self).__init__() self.exception = exception self.kind = 'E' def _accept(self, on_next, on_error, on_completed): return on_error(self.exception) def _accept_observer(self, observer): return observer.on_error(self.exception) def __str__(self): return "OnError(%s)" % str(self.exception) class OnCompleted(Notification): """Represents an OnCompleted notification to an observer.""" def __init__(self): """Constructs a notification of the end of a sequence.""" super(OnCompleted, self).__init__() self.kind = 'C' def _accept(self, on_next, on_error, on_completed): return on_completed() def _accept_observer(self, observer): return observer.on_completed() def __str__(self): return "OnCompleted()" def from_notifier(handler): """Creates an observer from a notification callback. Args: handler: Action that handles a notification. Returns: The observer object that invokes the specified handler using a notification corresponding to each message it receives. """ def _on_next(value): return handler(OnNext(value)) def _on_error(error): return handler(OnError(error)) def _on_completed(): return handler(OnCompleted()) return Observer(_on_next, _on_error, _on_completed) RxPY-3.2.0/rx/core/observable/000077500000000000000000000000001404130727200160635ustar00rootroot00000000000000RxPY-3.2.0/rx/core/observable/__init__.py000066400000000000000000000002151404130727200201720ustar00rootroot00000000000000from .observable import Observable from .connectableobservable import ConnectableObservable from .groupedobservable import GroupedObservable RxPY-3.2.0/rx/core/observable/amb.py000066400000000000000000000010341404130727200171720ustar00rootroot00000000000000from rx import never from rx import operators as _ from rx.core import Observable def _amb(*sources: Observable) -> Observable: """Propagates the observable sequence that reacts first. Example: >>> winner = amb(xs, ys, zs) Returns: An observable sequence that surfaces any of the given sequences, whichever reacted first. """ acc = never() def func(previous, current): return _.amb(previous)(current) for source in sources: acc = func(acc, source) return acc RxPY-3.2.0/rx/core/observable/case.py000066400000000000000000000012461404130727200173530ustar00rootroot00000000000000from typing import Optional, Union, Mapping, Callable, Any from asyncio import Future from rx import empty, defer, from_future from rx.core import Observable from rx.internal.utils import is_future def _case(mapper: Callable[[], Any], sources: Mapping, default_source: Optional[Union[Observable, Future]] = None ) -> Observable: default_source = default_source or empty() def factory(_) -> Observable: try: result = sources[mapper()] except KeyError: result = default_source result = from_future(result) if is_future(result) else result return result return defer(factory) RxPY-3.2.0/rx/core/observable/catch.py000066400000000000000000000042201404130727200175150ustar00rootroot00000000000000from typing import Iterable from rx.disposable import Disposable from rx.core import Observable from rx.disposable import SingleAssignmentDisposable, CompositeDisposable, SerialDisposable from rx.scheduler import CurrentThreadScheduler def _catch_with_iterable(sources: Iterable[Observable]) -> Observable: """Continues an observable sequence that is terminated by an exception with the next observable sequence. Examples: >>> res = catch([xs, ys, zs]) >>> res = rx.catch(src for src in [xs, ys, zs]) Args: sources: an Iterable of observables. Thus a generator is accepted. Returns: An observable sequence containing elements from consecutive source sequences until a source sequence terminates successfully. """ sources_ = iter(sources) def subscribe(observer, scheduler_=None): _scheduler = scheduler_ or CurrentThreadScheduler.singleton() subscription = SerialDisposable() cancelable = SerialDisposable() last_exception = None is_disposed = False def action(action1, state=None): def on_error(exn): nonlocal last_exception last_exception = exn cancelable.disposable = _scheduler.schedule(action) if is_disposed: return try: current = next(sources_) except StopIteration: if last_exception: observer.on_error(last_exception) else: observer.on_completed() except Exception as ex: # pylint: disable=broad-except observer.on_error(ex) else: d = SingleAssignmentDisposable() subscription.disposable = d d.disposable = current.subscribe_(observer.on_next, on_error, observer.on_completed, scheduler_) cancelable.disposable = _scheduler.schedule(action) def dispose(): nonlocal is_disposed is_disposed = True return CompositeDisposable(subscription, cancelable, Disposable(dispose)) return Observable(subscribe) RxPY-3.2.0/rx/core/observable/combinelatest.py000066400000000000000000000036311404130727200212710ustar00rootroot00000000000000from typing import Optional from rx.core import Observable, typing from rx.disposable import CompositeDisposable, SingleAssignmentDisposable def _combine_latest(*sources: Observable) -> Observable: """Merges the specified observable sequences into one observable sequence by creating a tuple whenever any of the observable sequences produces an element. Examples: >>> obs = combine_latest(obs1, obs2, obs3) Returns: An observable sequence containing the result of combining elements of the sources into a tuple. """ parent = sources[0] def subscribe(observer: typing.Observer, scheduler: Optional[typing.Scheduler] = None ) -> CompositeDisposable: n = len(sources) has_value = [False] * n has_value_all = [False] is_done = [False] * n values = [None] * n def _next(i): has_value[i] = True if has_value_all[0] or all(has_value): res = tuple(values) observer.on_next(res) elif all([x for j, x in enumerate(is_done) if j != i]): observer.on_completed() has_value_all[0] = all(has_value) def done(i): is_done[i] = True if all(is_done): observer.on_completed() subscriptions = [None] * n def func(i): subscriptions[i] = SingleAssignmentDisposable() def on_next(x): with parent.lock: values[i] = x _next(i) def on_completed(): with parent.lock: done(i) subscriptions[i].disposable = sources[i].subscribe_(on_next, observer.on_error, on_completed, scheduler) for idx in range(n): func(idx) return CompositeDisposable(subscriptions) return Observable(subscribe) RxPY-3.2.0/rx/core/observable/concat.py000066400000000000000000000027701404130727200177120ustar00rootroot00000000000000from typing import Iterable from rx.disposable import Disposable from rx.core import Observable from rx.disposable import SingleAssignmentDisposable, CompositeDisposable, SerialDisposable from rx.scheduler import CurrentThreadScheduler def _concat_with_iterable(sources: Iterable[Observable]) -> Observable: def subscribe(observer, scheduler_=None): _scheduler = scheduler_ or CurrentThreadScheduler.singleton() sources_ = iter(sources) subscription = SerialDisposable() cancelable = SerialDisposable() is_disposed = False def action(action1, state=None): nonlocal is_disposed if is_disposed: return def on_completed(): cancelable.disposable = _scheduler.schedule(action) try: current = next(sources_) except StopIteration: observer.on_completed() except Exception as ex: # pylint: disable=broad-except observer.on_error(ex) else: d = SingleAssignmentDisposable() subscription.disposable = d d.disposable = current.subscribe_(observer.on_next, observer.on_error, on_completed, scheduler_) cancelable.disposable = _scheduler.schedule(action) def dispose(): nonlocal is_disposed is_disposed = True return CompositeDisposable(subscription, cancelable, Disposable(dispose)) return Observable(subscribe) RxPY-3.2.0/rx/core/observable/connectableobservable.py000066400000000000000000000042721404130727200227640ustar00rootroot00000000000000from rx.disposable import Disposable from rx.disposable import CompositeDisposable from .observable import Observable class ConnectableObservable(Observable): """Represents an observable that can be connected and disconnected.""" def __init__(self, source, subject): self.subject = subject self.has_subscription = False self.subscription = None self.source = source super().__init__() def _subscribe_core(self, observer, scheduler=None): return self.subject.subscribe(observer, scheduler=scheduler) def connect(self, scheduler=None): """Connects the observable.""" if not self.has_subscription: self.has_subscription = True def dispose(): self.has_subscription = False subscription = self.source.subscribe(self.subject, scheduler=scheduler) self.subscription = CompositeDisposable(subscription, Disposable(dispose)) return self.subscription def auto_connect(self, subscriber_count=1): """Returns an observable sequence that stays connected to the source indefinitely to the observable sequence. Providing a subscriber_count will cause it to connect() after that many subscriptions occur. A subscriber_count of 0 will result in emissions firing immediately without waiting for subscribers. """ connectable_subscription = [None] count = [0] source = self is_connected = [False] if subscriber_count == 0: connectable_subscription[0] = source.connect() is_connected[0] = True def subscribe(observer, scheduler=None): count[0] += 1 should_connect = count[0] == subscriber_count and not is_connected[0] subscription = source.subscribe(observer) if should_connect: connectable_subscription[0] = source.connect(scheduler) is_connected[0] = True def dispose(): subscription.dispose() count[0] -= 1 is_connected[0] = False return Disposable(dispose) return Observable(subscribe) RxPY-3.2.0/rx/core/observable/defer.py000066400000000000000000000022031404130727200175170ustar00rootroot00000000000000from typing import Callable, Union from asyncio import Future from rx import throw, from_future from rx.core import Observable from rx.core.typing import Scheduler from rx.internal.utils import is_future def _defer(factory: Callable[[Scheduler], Union[Observable, Future]] ) -> Observable: """Returns an observable sequence that invokes the specified factory function whenever a new observer subscribes. Example: >>> res = defer(lambda: of(1, 2, 3)) Args: observable_factory: Observable factory function to invoke for each observer that subscribes to the resulting sequence. Returns: An observable sequence whose observers trigger an invocation of the given observable factory function. """ def subscribe(observer, scheduler=None): try: result = factory(scheduler) except Exception as ex: # By design. pylint: disable=W0703 return throw(ex).subscribe(observer) result = from_future(result) if is_future(result) else result return result.subscribe(observer, scheduler=scheduler) return Observable(subscribe) RxPY-3.2.0/rx/core/observable/empty.py000066400000000000000000000011221404130727200175670ustar00rootroot00000000000000from typing import Any, Optional from rx.core import typing, Observable from rx.scheduler import ImmediateScheduler def _empty(scheduler: Optional[typing.Scheduler] = None) -> Observable: def subscribe(observer: typing.Observer, scheduler_: Optional[typing.Scheduler] = None ) -> typing.Disposable: _scheduler = scheduler or scheduler_ or ImmediateScheduler.singleton() def action(_: typing.Scheduler, __: Any) -> None: observer.on_completed() return _scheduler.schedule(action) return Observable(subscribe) RxPY-3.2.0/rx/core/observable/forkjoin.py000066400000000000000000000037331404130727200202640ustar00rootroot00000000000000from typing import Optional from rx.core import Observable, typing from rx.disposable import CompositeDisposable, SingleAssignmentDisposable def _fork_join(*sources: Observable) -> Observable: """Wait for observables to complete and then combine last values they emitted into a tuple. Whenever any of that observables completes without emitting any value, result sequence will complete at that moment as well. Examples: >>> obs = rx.fork_join(obs1, obs2, obs3) Returns: An observable sequence containing the result of combining last element from each source in given sequence. """ parent = sources[0] def subscribe(observer: typing.Observer, scheduler: Optional[typing.Scheduler] = None ) -> CompositeDisposable: n = len(sources) values = [None] * n is_done = [False] * n has_value = [False] * n def done(i: int): is_done[i] = True if not has_value[i]: observer.on_completed() return if all(is_done): if all(has_value): observer.on_next(tuple(values)) observer.on_completed() else: observer.on_completed() subscriptions = [None] * n def _subscribe(i: int): subscriptions[i] = SingleAssignmentDisposable() def on_next(value): with parent.lock: values[i] = value has_value[i] = True def on_completed(): with parent.lock: done(i) subscriptions[i].disposable = sources[i].subscribe_( on_next, observer.on_error, on_completed, scheduler ) for i in range(n): _subscribe(i) return CompositeDisposable(subscriptions) return Observable(subscribe) RxPY-3.2.0/rx/core/observable/fromcallback.py000066400000000000000000000033521404130727200210600ustar00rootroot00000000000000from typing import Callable, Optional from rx.disposable import Disposable from rx.core import typing from rx.core import Observable from rx.core.typing import Mapper def _from_callback(func: Callable, mapper: Optional[Mapper] = None) -> Callable[[], Observable]: """Converts a callback function to an observable sequence. Args: func: Function with a callback as the last argument to convert to an Observable sequence. mapper: [Optional] A mapper which takes the arguments from the callback to produce a single item to yield on next. Returns: A function, when executed with the required arguments minus the callback, produces an Observable sequence with a single value of the arguments to the callback as a list. """ def function(*args): arguments = list(args) def subscribe(observer: typing.Observer, scheduler: Optional[typing.Scheduler] = None) -> typing.Disposable: def handler(*args): results = list(args) if mapper: try: results = mapper(args) except Exception as err: # pylint: disable=broad-except observer.on_error(err) return observer.on_next(results) else: if isinstance(results, list) and len(results) <= 1: observer.on_next(*results) else: observer.on_next(results) observer.on_completed() arguments.append(handler) func(*arguments) return Disposable() return Observable(subscribe) return function RxPY-3.2.0/rx/core/observable/fromfuture.py000066400000000000000000000024111404130727200206310ustar00rootroot00000000000000import asyncio from asyncio.futures import Future from typing import Optional from rx.disposable import Disposable from rx.core import typing from rx.core import Observable def _from_future(future: Future) -> Observable: """Converts a Future to an Observable sequence Args: future -- A Python 3 compatible future. https://docs.python.org/3/library/asyncio-task.html#future http://www.tornadoweb.org/en/stable/concurrent.html#tornado.concurrent.Future Returns: An Observable sequence which wraps the existing future success and failure. """ def subscribe(observer: typing.Observer, scheduler: Optional[typing.Scheduler] = None ) -> typing.Disposable: def done(future): try: value = future.result() except (Exception, asyncio.CancelledError) as ex: # pylint: disable=broad-except observer.on_error(ex) else: observer.on_next(value) observer.on_completed() future.add_done_callback(done) def dispose() -> None: if future and future.cancel: future.cancel() return Disposable(dispose) return Observable(subscribe) RxPY-3.2.0/rx/core/observable/fromiterable.py000066400000000000000000000030041404130727200211050ustar00rootroot00000000000000from typing import Iterable, Any, Optional from rx.core import Observable, typing from rx.scheduler import CurrentThreadScheduler from rx.disposable import CompositeDisposable, Disposable def from_iterable(iterable: Iterable, scheduler: Optional[typing.Scheduler] = None) -> Observable: """Converts an iterable to an observable sequence. Example: >>> from_iterable([1,2,3]) Args: iterable: A Python iterable scheduler: An optional scheduler to schedule the values on. Returns: The observable sequence whose elements are pulled from the given iterable sequence. """ def subscribe(observer: typing.Observer, scheduler_: Optional[typing.Scheduler] = None) -> typing.Disposable: _scheduler = scheduler or scheduler_ or CurrentThreadScheduler.singleton() iterator = iter(iterable) disposed = False def action(_: typing.Scheduler, __: Any = None) -> None: nonlocal disposed try: while not disposed: value = next(iterator) observer.on_next(value) except StopIteration: observer.on_completed() except Exception as error: # pylint: disable=broad-except observer.on_error(error) def dispose() -> None: nonlocal disposed disposed = True disp = Disposable(dispose) return CompositeDisposable(_scheduler.schedule(action), disp) return Observable(subscribe) RxPY-3.2.0/rx/core/observable/generate.py000066400000000000000000000025631404130727200202350ustar00rootroot00000000000000from typing import Any from rx.core import Observable from rx.core.typing import Mapper, Predicate from rx.scheduler import CurrentThreadScheduler from rx.disposable import MultipleAssignmentDisposable def _generate(initial_state: Any, condition: Predicate, iterate: Mapper ) -> Observable: def subscribe(observer, scheduler=None): scheduler = scheduler or CurrentThreadScheduler.singleton() first = True state = initial_state mad = MultipleAssignmentDisposable() def action(scheduler, state1=None): nonlocal first nonlocal state has_result = False result = None try: if first: first = False else: state = iterate(state) has_result = condition(state) if has_result: result = state except Exception as exception: # pylint: disable=broad-except observer.on_error(exception) return if has_result: observer.on_next(result) mad.disposable = scheduler.schedule(action) else: observer.on_completed() mad.disposable = scheduler.schedule(action) return mad return Observable(subscribe) RxPY-3.2.0/rx/core/observable/generatewithrelativetime.py000066400000000000000000000045541404130727200235460ustar00rootroot00000000000000from typing import Any, Callable from rx.core import Observable from rx.core.typing import Predicate, Mapper, RelativeTime from rx.scheduler import TimeoutScheduler from rx.disposable import MultipleAssignmentDisposable def _generate_with_relative_time(initial_state: Any, condition: Predicate, iterate: Mapper, time_mapper: Callable[[Any], RelativeTime] ) -> Observable: """Generates an observable sequence by iterating a state from an initial state until the condition fails. Example: res = source.generate_with_relative_time(0, lambda x: True, lambda x: x + 1, lambda x: 0.5) Args: initial_state: Initial state. condition: Condition to terminate generation (upon returning false). iterate: Iteration step function. time_mapper: Time mapper function to control the speed of values being produced each iteration, returning relative times, i.e. either floats denoting seconds or instances of timedelta. Returns: The generated sequence. """ def subscribe(observer, scheduler=None): scheduler = scheduler or TimeoutScheduler.singleton() mad = MultipleAssignmentDisposable() state = initial_state has_result = False result = None first = True time = None def action(scheduler, _): nonlocal state nonlocal has_result nonlocal result nonlocal first nonlocal time if has_result: observer.on_next(result) try: if first: first = False else: state = iterate(state) has_result = condition(state) if has_result: result = state time = time_mapper(state) except Exception as e: # pylint: disable=broad-except observer.on_error(e) return if has_result: mad.disposable = scheduler.schedule_relative(time, action) else: observer.on_completed() mad.disposable = scheduler.schedule_relative(0, action) return mad return Observable(subscribe) RxPY-3.2.0/rx/core/observable/groupedobservable.py000066400000000000000000000012531404130727200221500ustar00rootroot00000000000000from rx.disposable import CompositeDisposable from .observable import Observable class GroupedObservable(Observable): def __init__(self, key, underlying_observable, merged_disposable=None): super().__init__() self.key = key def subscribe(observer, scheduler=None): return CompositeDisposable(merged_disposable.disposable, underlying_observable.subscribe(observer, scheduler=scheduler)) self.underlying_observable = underlying_observable if not merged_disposable else Observable(subscribe) def _subscribe_core(self, observer, scheduler=None): return self.underlying_observable.subscribe(observer, scheduler=scheduler) RxPY-3.2.0/rx/core/observable/ifthen.py000066400000000000000000000027221404130727200177150ustar00rootroot00000000000000from typing import Callable, Union, cast from asyncio import Future import rx from rx.core import Observable from rx.core.typing import Scheduler from rx.internal.utils import is_future def _if_then(condition: Callable[[], bool], then_source: Union[Observable, Future], else_source: Union[None, Observable, Future] = None ) -> Observable: """Determines whether an observable collection contains values. Example: 1 - res = rx.if_then(condition, obs1) 2 - res = rx.if_then(condition, obs1, obs2) Args: condition: The condition which determines if the then_source or else_source will be run. then_source: The observable sequence or Promise that will be run if the condition function returns true. else_source: [Optional] The observable sequence or Promise that will be run if the condition function returns False. If this is not provided, it defaults to rx.empty Returns: An observable sequence which is either the then_source or else_source. """ else_source = else_source or rx.empty() then_source = rx.from_future(cast(Future, then_source)) if is_future(then_source) else then_source else_source = rx.from_future(cast(Future, else_source)) if is_future(else_source) else else_source def factory(_: Scheduler): return then_source if condition() else else_source return rx.defer(factory) RxPY-3.2.0/rx/core/observable/interval.py000066400000000000000000000004151404130727200202610ustar00rootroot00000000000000from typing import Optional from rx import timer from rx.core import Observable, typing def _interval(period: typing.RelativeTime, scheduler: Optional[typing.Scheduler] = None ) -> Observable: return timer(period, period, scheduler) RxPY-3.2.0/rx/core/observable/marbles.py000066400000000000000000000206121404130727200200630ustar00rootroot00000000000000from typing import List, Tuple, Optional, Mapping import re import threading from datetime import datetime, timedelta from rx import Observable from rx.core import notification from rx.disposable import CompositeDisposable, Disposable from rx.scheduler import NewThreadScheduler from rx.core.typing import RelativeTime, AbsoluteOrRelativeTime, Scheduler new_thread_scheduler = NewThreadScheduler() # tokens will be searched in the order below using pipe # group of elements: match any characters surrounded by () pattern_group = r"(\(.*?\))" # timespan: match one or multiple hyphens pattern_ticks = r"(-+)" # comma err: match any comma which is not in a group pattern_comma_error = r"(,)" # element: match | or # or one or more characters which are not - | # ( ) , pattern_element = r"(#|\||[^-,()#\|]+)" pattern = r'|'.join([ pattern_group, pattern_ticks, pattern_comma_error, pattern_element, ]) tokens = re.compile(pattern) def hot(string: str, timespan: RelativeTime = 0.1, duetime: AbsoluteOrRelativeTime = 0.0, lookup: Optional[Mapping] = None, error: Optional[Exception] = None, scheduler: Optional[Scheduler] = None ) -> Observable: _scheduler = scheduler or new_thread_scheduler if isinstance(duetime, datetime): duetime = duetime - _scheduler.now messages = parse( string, timespan=timespan, time_shift=duetime, lookup=lookup, error=error, raise_stopped=True, ) lock = threading.RLock() is_stopped = False observers = [] def subscribe(observer, scheduler=None): # should a hot observable already completed or on error # re-push on_completed/on_error at subscription time? if not is_stopped: with lock: observers.append(observer) def dispose(): with lock: try: observers.remove(observer) except ValueError: pass return Disposable(dispose) def create_action(notification): def action(scheduler, state=None): nonlocal is_stopped with lock: for observer in observers: notification.accept(observer) if notification.kind in ('C', 'E'): is_stopped = True return action for message in messages: timespan, notification = message action = create_action(notification) # Don't make closures within a loop _scheduler.schedule_relative(timespan, action) return Observable(subscribe) def from_marbles(string: str, timespan: RelativeTime = 0.1, lookup: Optional[Mapping] = None, error: Optional[Exception] = None, scheduler: Optional[Scheduler] = None ) -> Observable: messages = parse(string, timespan=timespan, lookup=lookup, error=error, raise_stopped=True) def subscribe(observer, scheduler_): _scheduler = scheduler or scheduler_ or new_thread_scheduler disp = CompositeDisposable() def schedule_msg(message): duetime, notification = message def action(*_, **__): notification.accept(observer) disp.add(_scheduler.schedule_relative(duetime, action)) for message in messages: # Don't make closures within a loop schedule_msg(message) return disp return Observable(subscribe) def parse(string: str, timespan: RelativeTime = 1.0, time_shift: RelativeTime = 0.0, lookup: Optional[Mapping] = None, error: Optional[Exception] = None, raise_stopped: bool = False ) -> List[Tuple[RelativeTime, notification.Notification]]: """Convert a marble diagram string to a list of messages. Each character in the string will advance time by timespan (exept for space). Characters that are not special (see the table below) will be interpreted as a value to be emitted. numbers will be cast to int or float. Special characters: +--------+--------------------------------------------------------+ | `-` | advance time by timespan | +--------+--------------------------------------------------------+ | `#` | on_error() | +--------+--------------------------------------------------------+ | `|` | on_completed() | +--------+--------------------------------------------------------+ | `(` | open a group of elements sharing the same timestamp | +--------+--------------------------------------------------------+ | `)` | close a group of elements | +--------+--------------------------------------------------------+ | `,` | separate elements in a group | +--------+--------------------------------------------------------+ | space | used to align multiple diagrams, does not advance time | +--------+--------------------------------------------------------+ In a group of elements, the position of the initial `(` determines the timestamp at which grouped elements will be emitted. E.g. `--(12,3,4)--` will emit 12, 3, 4 at 2 * timespan and then advance virtual time by 8 * timespan. Examples: >>> parse("--1--(2,3)-4--|") >>> parse("a--b--c-", lookup={'a': 1, 'b': 2, 'c': 3}) >>> parse("a--b---#", error=ValueError("foo")) Args: string: String with marble diagram timespan: [Optional] duration of each character in second. If not specified, defaults to 0.1s. lookup: [Optional] dict used to convert an element into a specified value. If not specified, defaults to {}. time_shift: [Optional] time used to delay every elements. If not specified, defaults to 0.0s. error: [Optional] exception that will be use in place of the # symbol. If not specified, defaults to Exception('error'). raise_finished: [optional] raise ValueError if elements are declared after on_completed or on_error symbol. Returns: A list of messages defined as a tuple of (timespan, notification). """ error = error or Exception('error') lookup = lookup or {} if isinstance(timespan, timedelta): timespan = timespan.total_seconds() if isinstance(time_shift, timedelta): time_shift = time_shift.total_seconds() string = string.replace(' ', '') # try to cast a string to an int, then to a float def try_number(element): try: return int(element) except ValueError: try: return float(element) except ValueError: return element def map_element(time, element): if element == '|': return (time, notification.OnCompleted()) elif element == '#': return (time, notification.OnError(error)) else: value = try_number(element) value = lookup.get(value, value) return (time, notification.OnNext(value)) is_stopped = False def check_stopped(element): nonlocal is_stopped if raise_stopped: if is_stopped: raise ValueError('Elements cannot be declared after a # or | symbol.') if element in ('#', '|'): is_stopped = True iframe = 0 messages = [] for results in tokens.findall(string): timestamp = iframe * timespan + time_shift group, ticks, comma_error, element = results if group: elements = group[1:-1].split(',') for elm in elements: check_stopped(elm) grp_messages = [map_element(timestamp, elm) for elm in elements if elm !=''] messages.extend(grp_messages) iframe += len(group) if ticks: iframe += len(ticks) if comma_error: raise ValueError("Comma is only allowed in group of elements.") if element: check_stopped(element) message = map_element(timestamp, element) messages.append(message) iframe += len(element) return messages RxPY-3.2.0/rx/core/observable/merge.py000066400000000000000000000002671404130727200175410ustar00rootroot00000000000000 import rx from rx import operators as ops from rx.core import Observable def _merge(*sources: Observable) -> Observable: return rx.from_iterable(sources).pipe(ops.merge_all()) RxPY-3.2.0/rx/core/observable/never.py000066400000000000000000000010401404130727200175470ustar00rootroot00000000000000from typing import Optional from rx.disposable import Disposable from rx.core import Observable, typing def _never() -> Observable: """Returns a non-terminating observable sequence, which can be used to denote an infinite duration (e.g. when using reactive joins). Returns: An observable sequence whose observers will never get called. """ def subscribe(observer: typing.Observer, scheduler: Optional[typing.Scheduler] = None) -> typing.Disposable: return Disposable() return Observable(subscribe) RxPY-3.2.0/rx/core/observable/observable.py000066400000000000000000000331021404130727200205600ustar00rootroot00000000000000# By design, pylint: disable=C0302 import threading import asyncio from typing import Any, Callable, Optional, Union, TypeVar, cast, overload from rx.disposable import Disposable from rx.scheduler import CurrentThreadScheduler from rx.scheduler.eventloop import AsyncIOScheduler from ..observer import AutoDetachObserver from .. import typing, abc A = TypeVar('A') B = TypeVar('B') C = TypeVar('C') D = TypeVar('D') E = TypeVar('E') F = TypeVar('F') G = TypeVar('G') class Observable(typing.Observable): """Observable base class. Represents a push-style collection, which you can :func:`pipe ` into :mod:`operators `.""" def __init__(self, subscribe: Optional[typing.Subscription] = None) -> None: """Creates an observable sequence object from the specified subscription function. Args: subscribe: [Optional] Subscription function """ super().__init__() self.lock = threading.RLock() self._subscribe = subscribe def _subscribe_core(self, observer: typing.Observer, scheduler: Optional[typing.Scheduler] = None ) -> typing.Disposable: return self._subscribe(observer, scheduler) if self._subscribe else Disposable() def subscribe(self, # pylint: disable=too-many-arguments,arguments-differ observer: Optional[Union[typing.Observer, typing.OnNext]] = None, on_error: Optional[typing.OnError] = None, on_completed: Optional[typing.OnCompleted] = None, on_next: Optional[typing.OnNext] = None, *, scheduler: Optional[typing.Scheduler] = None, ) -> typing.Disposable: """Subscribe an observer to the observable sequence. You may subscribe using an observer or callbacks, not both; if the first argument is an instance of :class:`Observer <..typing.Observer>` or if it has a (callable) attribute named :code:`on_next`, then any callback arguments will be ignored. Examples: >>> source.subscribe() >>> source.subscribe(observer) >>> source.subscribe(observer, scheduler=scheduler) >>> source.subscribe(on_next) >>> source.subscribe(on_next, on_error) >>> source.subscribe(on_next, on_error, on_completed) >>> source.subscribe(on_next, on_error, on_completed, scheduler=scheduler) Args: observer: [Optional] The object that is to receive notifications. on_error: [Optional] Action to invoke upon exceptional termination of the observable sequence. on_completed: [Optional] Action to invoke upon graceful termination of the observable sequence. on_next: [Optional] Action to invoke for each element in the observable sequence. scheduler: [Optional] The default scheduler to use for this subscription. Returns: Disposable object representing an observer's subscription to the observable sequence. """ if observer: if isinstance(observer, typing.Observer) \ or hasattr(observer, 'on_next') \ and callable(getattr(observer, 'on_next')): on_next = cast(typing.Observer, observer).on_next on_error = cast(typing.Observer, observer).on_error on_completed = cast(typing.Observer, observer).on_completed else: on_next = observer return self.subscribe_(on_next, on_error, on_completed, scheduler) def subscribe_(self, on_next: Optional[typing.OnNext] = None, on_error: Optional[typing.OnError] = None, on_completed: Optional[typing.OnCompleted] = None, scheduler: Optional[typing.Scheduler] = None ) -> typing.Disposable: """Subscribe callbacks to the observable sequence. Examples: >>> source.subscribe_(on_next) >>> source.subscribe_(on_next, on_error) >>> source.subscribe_(on_next, on_error, on_completed) Args: on_next: [Optional] Action to invoke for each element in the observable sequence. on_error: [Optional] Action to invoke upon exceptional termination of the observable sequence. on_completed: [Optional] Action to invoke upon graceful termination of the observable sequence. scheduler: [Optional] The scheduler to use for this subscription. Returns: Disposable object representing an observer's subscription to the observable sequence. """ auto_detach_observer = AutoDetachObserver(on_next, on_error, on_completed) def fix_subscriber(subscriber): """Fixes subscriber to make sure it returns a Disposable instead of None or a dispose function""" if not hasattr(subscriber, 'dispose'): subscriber = Disposable(subscriber) return subscriber def set_disposable(_: abc.Scheduler = None, __: Any = None): try: subscriber = self._subscribe_core(auto_detach_observer, scheduler) except Exception as ex: # By design. pylint: disable=W0703 if not auto_detach_observer.fail(ex): raise else: auto_detach_observer.subscription = fix_subscriber(subscriber) # Subscribe needs to set up the trampoline before for subscribing. # Actually, the first call to Subscribe creates the trampoline so # that it may assign its disposable before any observer executes # OnNext over the CurrentThreadScheduler. This enables single- # threaded cancellation # https://social.msdn.microsoft.com/Forums/en-US/eb82f593-9684-4e27- # 97b9-8b8886da5c33/whats-the-rationale-behind-how-currentthreadsche # dulerschedulerequired-behaves?forum=rx current_thread_scheduler = CurrentThreadScheduler.singleton() if current_thread_scheduler.schedule_required(): current_thread_scheduler.schedule(set_disposable) else: set_disposable() # Hide the identity of the auto detach observer return Disposable(auto_detach_observer.dispose) @overload def pipe(self, *operators: Callable[['Observable'], 'Observable'] ) -> 'Observable': # pylint: disable=no-self-use """Compose multiple operators left to right. Composes zero or more operators into a functional composition. The operators are composed from left to right. A composition of zero operators gives back the original source. Examples: >>> source.pipe() == source >>> source.pipe(f) == f(source) >>> source.pipe(g, f) == f(g(source)) >>> source.pipe(h, g, f) == f(g(h(source))) Args: operators: Sequence of operators. Returns: The composed observable. """ ... @overload def pipe(self) -> 'Observable': # pylint: disable=function-redefined, no-self-use ... # pylint: disable=pointless-statement @overload def pipe(self, op1: Callable[['Observable'], A]) -> A: # pylint: disable=function-redefined, no-self-use ... # pylint: disable=pointless-statement @overload def pipe(self, # pylint: disable=function-redefined, no-self-use op1: Callable[['Observable'], A], op2: Callable[[A], B]) -> B: ... # pylint: disable=pointless-statement @overload def pipe(self, # pylint: disable=function-redefined, no-self-use op1: Callable[['Observable'], A], op2: Callable[[A], B], op3: Callable[[B], C]) -> C: ... # pylint: disable=pointless-statement @overload def pipe(self, # pylint: disable=function-redefined, no-self-use op1: Callable[['Observable'], A], op2: Callable[[A], B], op3: Callable[[B], C], op4: Callable[[C], D]) -> D: ... # pylint: disable=pointless-statement @overload def pipe(self, # pylint: disable=function-redefined, no-self-use, too-many-arguments op1: Callable[['Observable'], A], op2: Callable[[A], B], op3: Callable[[B], C], op4: Callable[[C], D], op5: Callable[[D], E]) -> E: ... # pylint: disable=pointless-statement @overload def pipe(self, # pylint: disable=function-redefined, no-self-use, too-many-arguments op1: Callable[['Observable'], A], op2: Callable[[A], B], op3: Callable[[B], C], op4: Callable[[C], D], op5: Callable[[D], E], op6: Callable[[E], F]) -> F: ... # pylint: disable=pointless-statement @overload def pipe(self, # pylint: disable=function-redefined, no-self-use, too-many-arguments op1: Callable[['Observable'], A], op2: Callable[[A], B], op3: Callable[[B], C], op4: Callable[[C], D], op5: Callable[[D], E], op6: Callable[[E], F], op7: Callable[[F], G]) -> G: ... # pylint: disable=pointless-statement # pylint: disable=function-redefined def pipe(self, *operators: Callable[['Observable'], Any]) -> Any: """Compose multiple operators left to right. Composes zero or more operators into a functional composition. The operators are composed from left to right. A composition of zero operators gives back the original source. Examples: >>> source.pipe() == source >>> source.pipe(f) == f(source) >>> source.pipe(g, f) == f(g(source)) >>> source.pipe(h, g, f) == f(g(h(source))) Args: operators: Sequence of operators. Returns: The composed observable. """ from ..pipe import pipe return pipe(*operators)(self) def run(self) -> Any: """Run source synchronously. Subscribes to the observable source. Then blocks and waits for the observable source to either complete or error. Returns the last value emitted, or throws exception if any error occurred. Examples: >>> result = run(source) Raises: SequenceContainsNoElementsError: if observable completes (on_completed) without any values being emitted. Exception: raises exception if any error (on_error) occurred. Returns: The last element emitted from the observable. """ from ..run import run return run(self) def __await__(self) -> Any: """Awaits the given observable. Returns: The last item of the observable sequence. """ from ..operators.tofuture import _to_future loop = asyncio.get_event_loop() return iter(self.pipe(_to_future(scheduler=AsyncIOScheduler(loop=loop)))) def __add__(self, other) -> 'Observable': """Pythonic version of :func:`concat `. Example: >>> zs = xs + ys Args: other: The second observable sequence in the concatenation. Returns: Concatenated observable sequence. """ from rx import concat return concat(self, other) def __iadd__(self, other) -> 'Observable': """Pythonic use of :func:`concat `. Example: >>> xs += ys Args: other: The second observable sequence in the concatenation. Returns: Concatenated observable sequence. """ from rx import concat return concat(self, other) def __getitem__(self, key) -> 'Observable': """ Pythonic version of :func:`slice `. Slices the given observable using Python slice notation. The arguments to slice are `start`, `stop` and `step` given within brackets `[]` and separated by the colons `:`. It is basically a wrapper around the operators :func:`skip `, :func:`skip_last `, :func:`take `, :func:`take_last ` and :func:`filter `. The following diagram helps you remember how slices works with streams. Positive numbers are relative to the start of the events, while negative numbers are relative to the end (close) of the stream. .. code:: r---e---a---c---t---i---v---e---! 0 1 2 3 4 5 6 7 8 -8 -7 -6 -5 -4 -3 -2 -1 0 Examples: >>> result = source[1:10] >>> result = source[1:-2] >>> result = source[1:-1:2] Args: key: Slice object Returns: Sliced observable sequence. Raises: TypeError: If key is not of type :code:`int` or :code:`slice` """ if isinstance(key, slice): start, stop, step = key.start, key.stop, key.step elif isinstance(key, int): start, stop, step = key, key + 1, 1 else: raise TypeError('Invalid argument type.') from ..operators.slice import _slice return _slice(start, stop, step)(self) RxPY-3.2.0/rx/core/observable/onerrorresumenext.py000066400000000000000000000033411404130727200222440ustar00rootroot00000000000000from typing import Union from asyncio import Future import rx from rx.scheduler import CurrentThreadScheduler from rx.core import Observable from rx.disposable import CompositeDisposable, SingleAssignmentDisposable, SerialDisposable from rx.internal.utils import is_future def _on_error_resume_next(*sources: Union[Observable, Future]) -> Observable: """Continues an observable sequence that is terminated normally or by an exception with the next observable sequence. Examples: >>> res = rx.on_error_resume_next(xs, ys, zs) Returns: An observable sequence that concatenates the source sequences, even if a sequence terminates exceptionally. """ sources_ = iter(sources) def subscribe(observer, scheduler=None): scheduler = scheduler or CurrentThreadScheduler.singleton() subscription = SerialDisposable() cancelable = SerialDisposable() def action(scheduler, state=None): try: source = next(sources_) except StopIteration: observer.on_completed() return # Allow source to be a factory method taking an error source = source(state) if callable(source) else source current = rx.from_future(source) if is_future(source) else source d = SingleAssignmentDisposable() subscription.disposable = d def on_resume(state=None): scheduler.schedule(action, state) d.disposable = current.subscribe_(observer.on_next, on_resume, on_resume, scheduler) cancelable.disposable = scheduler.schedule(action) return CompositeDisposable(subscription, cancelable) return Observable(subscribe) RxPY-3.2.0/rx/core/observable/range.py000066400000000000000000000034771404130727200175440ustar00rootroot00000000000000from sys import maxsize from typing import Optional from rx.core import typing from rx.core import Observable from rx.scheduler import CurrentThreadScheduler from rx.disposable import MultipleAssignmentDisposable def _range(start: int, stop: Optional[int] = None, step: Optional[int] = None, scheduler: Optional[typing.Scheduler] = None ) -> Observable: """Generates an observable sequence of integral numbers within a specified range, using the specified scheduler to send out observer messages. Examples: >>> res = range(10) >>> res = range(0, 10) >>> res = range(0, 10, 1) Args: start: The value of the first integer in the sequence. count: The number of sequential integers to generate. scheduler: The scheduler to schedule the values on. Returns: An observable sequence that contains a range of sequential integral numbers. """ _stop: int = maxsize if stop is None else stop _step: int = 1 if step is None else step if step is None and stop is None: range_t = range(start) elif step is None: range_t = range(start, _stop) else: range_t = range(start, _stop, _step) def subscribe(observer, scheduler_: typing.Scheduler = None): nonlocal range_t _scheduler = scheduler or scheduler_ or CurrentThreadScheduler.singleton() sd = MultipleAssignmentDisposable() def action(scheduler, iterator): try: observer.on_next(next(iterator)) sd.disposable = _scheduler.schedule(action, state=iterator) except StopIteration: observer.on_completed() sd.disposable = _scheduler.schedule(action, iter(range_t)) return sd return Observable(subscribe) RxPY-3.2.0/rx/core/observable/repeat.py000066400000000000000000000014611404130727200177170ustar00rootroot00000000000000from typing import Any, Optional import rx from rx import operators as ops from rx.core import Observable def _repeat_value(value: Any = None, repeat_count: Optional[int] = None) -> Observable: """Generates an observable sequence that repeats the given element the specified number of times. Examples: 1 - res = repeat_value(42) 2 - res = repeat_value(42, 4) Args: value: Element to repeat. repeat_count: [Optional] Number of times to repeat the element. If not specified, repeats indefinitely. Returns: An observable sequence that repeats the given element the specified number of times. """ if repeat_count == -1: repeat_count = None xs = rx.return_value(value) return xs.pipe(ops.repeat(repeat_count)) RxPY-3.2.0/rx/core/observable/returnvalue.py000066400000000000000000000034011404130727200210070ustar00rootroot00000000000000from typing import Any, Callable, Optional from rx.core import typing from rx.core import Observable from rx.scheduler import CurrentThreadScheduler from rx.core.abc.scheduler import Scheduler def _return_value(value: Any, scheduler: Optional[typing.Scheduler] = None) -> Observable: """Returns an observable sequence that contains a single element, using the specified scheduler to send out observer messages. There is an alias called 'just'. Examples: >>> res = return(42) >>> res = return(42, rx.Scheduler.timeout) Args: value: Single element in the resulting observable sequence. Returns: An observable sequence containing the single specified element. """ def subscribe(observer: typing.Observer, scheduler_: Optional[typing.Scheduler] = None) -> typing.Disposable: _scheduler = scheduler or scheduler_ or CurrentThreadScheduler.singleton() def action(scheduler: typing.Scheduler, state: Any = None): observer.on_next(value) observer.on_completed() return _scheduler.schedule(action) return Observable(subscribe) def _from_callable(supplier: Callable[[], Any], scheduler: Optional[typing.Scheduler] = None) -> Observable: def subscribe(observer: typing.Observer, scheduler_: typing.Scheduler = None): _scheduler = scheduler or scheduler_ or CurrentThreadScheduler.singleton() def action(_: Scheduler, __: Any = None): nonlocal observer try: observer.on_next(supplier()) observer.on_completed() except Exception as e: # pylint: disable=broad-except observer.on_error(e) return _scheduler.schedule(action) return Observable(subscribe) RxPY-3.2.0/rx/core/observable/start.py000066400000000000000000000020221404130727200175660ustar00rootroot00000000000000from typing import Optional, Callable from rx import to_async from rx.core import Observable from rx.core.typing import Scheduler def _start(func: Callable, scheduler: Optional[Scheduler] = None) -> Observable: """Invokes the specified function asynchronously on the specified scheduler, surfacing the result through an observable sequence. Example: >>> res = rx.start(lambda: pprint('hello')) >>> res = rx.start(lambda: pprint('hello'), rx.Scheduler.timeout) Args: func: Function to run asynchronously. scheduler: [Optional] Scheduler to run the function on. If not specified, defaults to Scheduler.timeout. Remarks: The function is called immediately, not during the subscription of the resulting sequence. Multiple subscriptions to the resulting sequence can observe the function's result. Returns: An observable sequence exposing the function's result value, or an exception. """ return to_async(func, scheduler)() RxPY-3.2.0/rx/core/observable/startasync.py000066400000000000000000000005411404130727200206300ustar00rootroot00000000000000from typing import Callable from asyncio import Future from rx import throw, from_future from rx.core import Observable def _start_async(function_async: Callable[[], Future]) -> Observable: try: future = function_async() except Exception as ex: # pylint: disable=broad-except return throw(ex) return from_future(future) RxPY-3.2.0/rx/core/observable/throw.py000066400000000000000000000012501404130727200175760ustar00rootroot00000000000000from typing import Any, Optional from rx.core import typing from rx.core import Observable from rx.scheduler import ImmediateScheduler def _throw(exception: Exception, scheduler: Optional[typing.Scheduler] = None) -> Observable: exception = exception if isinstance(exception, Exception) else Exception(exception) def subscribe(observer: typing.Observer, scheduler: Optional[typing.Scheduler] = None) -> typing.Disposable: _scheduler = scheduler or ImmediateScheduler.singleton() def action(scheduler: typing.Scheduler, state: Any): observer.on_error(exception) return _scheduler.schedule(action) return Observable(subscribe) RxPY-3.2.0/rx/core/observable/timer.py000066400000000000000000000067141404130727200175650ustar00rootroot00000000000000from datetime import datetime from typing import Optional from rx.scheduler import TimeoutScheduler from rx.core import Observable, typing from rx.disposable import MultipleAssignmentDisposable def observable_timer_date(duetime, scheduler: Optional[typing.Scheduler] = None): def subscribe(observer, scheduler_=None): _scheduler = scheduler or scheduler_ or TimeoutScheduler.singleton() def action(scheduler, state): observer.on_next(0) observer.on_completed() return _scheduler.schedule_absolute(duetime, action) return Observable(subscribe) def observable_timer_duetime_and_period(duetime, period, scheduler: Optional[typing.Scheduler] = None) -> Observable: def subscribe(observer, scheduler_=None): _scheduler = scheduler or scheduler_ or TimeoutScheduler.singleton() nonlocal duetime if not isinstance(duetime, datetime): duetime = _scheduler.now + _scheduler.to_timedelta(duetime) p = max(0.0, _scheduler.to_seconds(period)) mad = MultipleAssignmentDisposable() dt = duetime count = 0 def action(scheduler, state): nonlocal dt nonlocal count if p > 0.0: now = scheduler.now dt = dt + scheduler.to_timedelta(p) if dt <= now: dt = now + scheduler.to_timedelta(p) observer.on_next(count) count += 1 mad.disposable = scheduler.schedule_absolute(dt, action) mad.disposable = _scheduler.schedule_absolute(dt, action) return mad return Observable(subscribe) def observable_timer_timespan(duetime: typing.RelativeTime, scheduler: Optional[typing.Scheduler] = None) -> Observable: def subscribe(observer, scheduler_=None): _scheduler = scheduler or scheduler_ or TimeoutScheduler.singleton() d = _scheduler.to_seconds(duetime) def action(scheduler, state): observer.on_next(0) observer.on_completed() if d <= 0.0: return _scheduler.schedule(action) return _scheduler.schedule_relative(d, action) return Observable(subscribe) def observable_timer_timespan_and_period(duetime: typing.RelativeTime, period: typing.RelativeTime, scheduler: Optional[typing.Scheduler] = None ) -> Observable: if duetime == period: def subscribe(observer, scheduler_=None): _scheduler = scheduler or scheduler_ or TimeoutScheduler.singleton() def action(count): observer.on_next(count) return count + 1 return _scheduler.schedule_periodic(period, action, state=0) return Observable(subscribe) return observable_timer_duetime_and_period(duetime, period, scheduler) def _timer(duetime: typing.AbsoluteOrRelativeTime, period: Optional[typing.RelativeTime] = None, scheduler: Optional[typing.Scheduler] = None ) -> Observable: if isinstance(duetime, datetime): if period is None: return observable_timer_date(duetime, scheduler) else: return observable_timer_duetime_and_period(duetime, period, scheduler) if period is None: return observable_timer_timespan(duetime, scheduler) return observable_timer_timespan_and_period(duetime, period, scheduler) RxPY-3.2.0/rx/core/observable/toasync.py000066400000000000000000000030041404130727200201120ustar00rootroot00000000000000from typing import Callable, Optional from rx import operators as ops from rx.core import Observable from rx.core.typing import Scheduler from rx.scheduler import TimeoutScheduler from rx.subject import AsyncSubject def _to_async(func: Callable, scheduler: Optional[Scheduler] = None ) -> Callable: """Converts the function into an asynchronous function. Each invocation of the resulting asynchronous function causes an invocation of the original synchronous function on the specified scheduler. Examples: res = rx.to_async(lambda x, y: x + y)(4, 3) res = rx.to_async(lambda x, y: x + y, Scheduler.timeout)(4, 3) res = rx.to_async(lambda x: log.debug(x), Scheduler.timeout)('hello') Args: func: Function to convert to an asynchronous function. scheduler: [Optional] Scheduler to run the function on. If not specified, defaults to Scheduler.timeout. Returns: Aynchronous function. """ _scheduler = scheduler or TimeoutScheduler.singleton() def wrapper(*args) -> Observable: subject = AsyncSubject() def action(scheduler, state): try: result = func(*args) except Exception as ex: # pylint: disable=broad-except subject.on_error(ex) return subject.on_next(result) subject.on_completed() _scheduler.schedule(action) return subject.pipe(ops.as_observable()) return wrapper RxPY-3.2.0/rx/core/observable/using.py000066400000000000000000000026751404130727200175740ustar00rootroot00000000000000from typing import Callable import rx from rx.core import Observable from rx.core import typing from rx.disposable import CompositeDisposable, Disposable def _using(resource_factory: Callable[[], typing.Disposable], observable_factory: Callable[[typing.Disposable], Observable] ) -> Observable: """Constructs an observable sequence that depends on a resource object, whose lifetime is tied to the resulting observable sequence's lifetime. Example: >>> res = rx.using(lambda: AsyncSubject(), lambda: s: s) Args: resource_factory: Factory function to obtain a resource object. observable_factory: Factory function to obtain an observable sequence that depends on the obtained resource. Returns: An observable sequence whose lifetime controls the lifetime of the dependent resource object. """ def subscribe(observer, scheduler=None): disp = Disposable() try: resource = resource_factory() if resource is not None: disp = resource source = observable_factory(resource) except Exception as exception: # pylint: disable=broad-except d = rx.throw(exception).subscribe(observer, scheduler=scheduler) return CompositeDisposable(d, disp) return CompositeDisposable(source.subscribe(observer, scheduler=scheduler), disp) return Observable(subscribe) RxPY-3.2.0/rx/core/observable/withlatestfrom.py000066400000000000000000000026631404130727200215200ustar00rootroot00000000000000from rx.disposable import CompositeDisposable, SingleAssignmentDisposable from rx.core import Observable from rx.internal.utils import NotSet def _with_latest_from(parent: Observable, *sources: Observable) -> Observable: NO_VALUE = NotSet() def subscribe(observer, scheduler=None): def subscribe_all(parent, *children): values = [NO_VALUE for _ in children] def subscribe_child(i, child): subscription = SingleAssignmentDisposable() def on_next(value): with parent.lock: values[i] = value subscription.disposable = child.subscribe_(on_next, observer.on_error, scheduler=scheduler) return subscription parent_subscription = SingleAssignmentDisposable() def on_next(value): with parent.lock: if NO_VALUE not in values: result = (value,) + tuple(values) observer.on_next(result) disp = parent.subscribe_(on_next, observer.on_error, observer.on_completed, scheduler) parent_subscription.disposable = disp children_subscription = [subscribe_child(i, child) for i, child in enumerate(children)] return [parent_subscription] + children_subscription return CompositeDisposable(subscribe_all(parent, *sources)) return Observable(subscribe) RxPY-3.2.0/rx/core/observable/zip.py000066400000000000000000000040061404130727200172370ustar00rootroot00000000000000from threading import RLock from typing import Optional, List from rx import from_future from rx.core import Observable, typing from rx.disposable import CompositeDisposable, SingleAssignmentDisposable from rx.internal.concurrency import synchronized from rx.internal.utils import is_future # pylint: disable=redefined-builtin def _zip(*args: Observable) -> Observable: """Merges the specified observable sequences into one observable sequence by creating a tuple whenever all of the observable sequences have produced an element at a corresponding index. Example: >>> res = zip(obs1, obs2) Args: args: Observable sources to zip. Returns: An observable sequence containing the result of combining elements of the sources as tuple. """ sources = list(args) def subscribe(observer: typing.Observer, scheduler: Optional[typing.Scheduler] = None) -> CompositeDisposable: n = len(sources) queues: List[List] = [[] for _ in range(n)] lock = RLock() @synchronized(lock) def next(i): if all([len(q) for q in queues]): try: queued_values = [x.pop(0) for x in queues] res = tuple(queued_values) except Exception as ex: # pylint: disable=broad-except observer.on_error(ex) return observer.on_next(res) subscriptions = [None] * n def func(i): source = sources[i] sad = SingleAssignmentDisposable() source = from_future(source) if is_future(source) else source def on_next(x): queues[i].append(x) next(i) sad.disposable = source.subscribe_(on_next, observer.on_error, observer.on_completed, scheduler) subscriptions[i] = sad for idx in range(n): func(idx) return CompositeDisposable(subscriptions) return Observable(subscribe) RxPY-3.2.0/rx/core/observer/000077500000000000000000000000001404130727200155665ustar00rootroot00000000000000RxPY-3.2.0/rx/core/observer/__init__.py000066400000000000000000000002641404130727200177010ustar00rootroot00000000000000from .observer import Observer from .scheduledobserver import ScheduledObserver from .observeonobserver import ObserveOnObserver from .autodetachobserver import AutoDetachObserver RxPY-3.2.0/rx/core/observer/autodetachobserver.py000066400000000000000000000031431404130727200220320ustar00rootroot00000000000000from typing import Any, Optional from rx.internal import noop, default_error from rx.disposable import SingleAssignmentDisposable from .. import typing class AutoDetachObserver(typing.Observer): def __init__(self, on_next: Optional[typing.OnNext] = None, on_error: Optional[typing.OnError] = None, on_completed: Optional[typing.OnCompleted] = None ) -> None: self._on_next = on_next or noop self._on_error = on_error or default_error self._on_completed = on_completed or noop self._subscription = SingleAssignmentDisposable() self.is_stopped = False def on_next(self, value: Any) -> None: if self.is_stopped: return self._on_next(value) def on_error(self, error) -> None: if self.is_stopped: return self.is_stopped = True try: self._on_error(error) finally: self.dispose() def on_completed(self) -> None: if self.is_stopped: return self.is_stopped = True try: self._on_completed() finally: self.dispose() def set_disposable(self, value: typing.Disposable): self._subscription.disposable = value subscription = property(fset=set_disposable) def dispose(self) -> None: self.is_stopped = True self._subscription.dispose() def fail(self, exn: Exception) -> bool: if self.is_stopped: return False self.is_stopped = True self._on_error(exn) return True RxPY-3.2.0/rx/core/observer/observeonobserver.py000066400000000000000000000007241404130727200217150ustar00rootroot00000000000000from typing import Any from .scheduledobserver import ScheduledObserver class ObserveOnObserver(ScheduledObserver): def _on_next_core(self, value: Any) -> None: super()._on_next_core(value) self.ensure_active() def _on_error_core(self, error: Exception) -> None: super()._on_error_core(error) self.ensure_active() def _on_completed_core(self) -> None: super()._on_completed_core() self.ensure_active() RxPY-3.2.0/rx/core/observer/observer.py000066400000000000000000000062141404130727200177720ustar00rootroot00000000000000from typing import Any, Callable, Optional from .. import typing from rx.internal import noop, default_error class Observer(typing.Observer, typing.Disposable): """Base class for implementations of the Observer class. This base class enforces the grammar of observers where OnError and OnCompleted are terminal messages. """ def __init__(self, on_next: Optional[typing.OnNext] = None, on_error: Optional[typing.OnError] = None, on_completed: Optional[typing.OnCompleted] = None ) -> None: self.is_stopped = False self._handler_on_next = on_next or noop self._handler_on_error = on_error or default_error self._handler_on_completed = on_completed or noop def on_next(self, value: Any) -> None: """Notify the observer of a new element in the sequence.""" if not self.is_stopped: self._on_next_core(value) def _on_next_core(self, value: Any) -> None: """For Subclassing purpose. This method is called by `on_next()` method until the observer is stopped. """ self._handler_on_next(value) def on_error(self, error: Exception) -> None: """Notify the observer that an exception has occurred. Args: error: The error that occurred. """ if not self.is_stopped: self.is_stopped = True self._on_error_core(error) def _on_error_core(self, error: Exception) -> None: """For Subclassing purpose. This method is called by `on_error()` method until the observer is stopped. """ self._handler_on_error(error) def on_completed(self) -> None: """Notifies the observer of the end of the sequence.""" if not self.is_stopped: self.is_stopped = True self._on_completed_core() def _on_completed_core(self) -> None: """For Subclassing purpose. This method is called by `on_completed()` method until the observer is stopped. """ self._handler_on_completed() def dispose(self) -> None: """Disposes the observer, causing it to transition to the stopped state.""" self.is_stopped = True def fail(self, exn: Exception) -> bool: if not self.is_stopped: self.is_stopped = True self._on_error_core(exn) return True return False def throw(self, error: Exception) -> None: import traceback traceback.print_stack() if error: raise error 1 / 0 # Raise division by zero def to_notifier(self) -> Callable: """Creates a notification callback from an observer. Returns the action that forwards its input notification to the underlying observer.""" def func(notifier): return notifier.accept(self) return func def as_observer(self) -> 'Observer': """Hides the identity of an observer. Returns an observer that hides the identity of the specified observer. """ return Observer(self.on_next, self.on_error, self.on_completed) RxPY-3.2.0/rx/core/observer/scheduledobserver.py000066400000000000000000000040301404130727200216450ustar00rootroot00000000000000import threading from typing import List, Any from rx.core import typing from rx.disposable import SerialDisposable from .observer import Observer class ScheduledObserver(Observer): def __init__(self, scheduler: typing.Scheduler, observer: typing.Observer) -> None: super().__init__() self.scheduler = scheduler self.observer = observer self.lock = threading.RLock() self.is_acquired = False self.has_faulted = False self.queue: List[typing.Action] = [] self.disposable = SerialDisposable() # Note to self: list append is thread safe # http://effbot.org/pyfaq/what-kinds-of-global-value-mutation-are-thread-safe.htm def _on_next_core(self, value: Any) -> None: def action(): self.observer.on_next(value) self.queue.append(action) def _on_error_core(self, error: Exception) -> None: def action(): self.observer.on_error(error) self.queue.append(action) def _on_completed_core(self) -> None: def action(): self.observer.on_completed() self.queue.append(action) def ensure_active(self) -> None: is_owner = False with self.lock: if not self.has_faulted and self.queue: is_owner = not self.is_acquired self.is_acquired = True if is_owner: self.disposable.disposable = self.scheduler.schedule(self.run) def run(self, scheduler: typing.Scheduler, state: typing.TState) -> None: parent = self with self.lock: if parent.queue: work = parent.queue.pop(0) else: parent.is_acquired = False return try: work() except Exception: with self.lock: parent.queue = [] parent.has_faulted = True raise self.scheduler.schedule(self.run) def dispose(self) -> None: super().dispose() self.disposable.dispose() RxPY-3.2.0/rx/core/operators/000077500000000000000000000000001404130727200157555ustar00rootroot00000000000000RxPY-3.2.0/rx/core/operators/__init__.py000066400000000000000000000000001404130727200200540ustar00rootroot00000000000000RxPY-3.2.0/rx/core/operators/all.py000066400000000000000000000006151404130727200171010ustar00rootroot00000000000000from typing import Callable from rx import operators as ops from rx.core import Observable, pipe from rx.core.typing import Predicate def _all(predicate: Predicate) -> Callable[[Observable], Observable]: filtering = ops.filter(lambda v: not predicate(v)) mapping = ops.map(lambda b: not b) some = ops.some() return pipe( filtering, some, mapping ) RxPY-3.2.0/rx/core/operators/amb.py000066400000000000000000000053271404130727200170750ustar00rootroot00000000000000from asyncio import Future from typing import cast, Any, Union from rx import from_future from rx.core import Observable, typing from rx.disposable import CompositeDisposable, SingleAssignmentDisposable from rx.internal.utils import is_future def _amb(right_source: Observable): if is_future(right_source): obs = from_future(cast(Future, right_source)) else: obs = cast(Observable, right_source) def amb(left_source: Observable): def subscribe(observer: typing.Observer, scheduler: typing.Scheduler = None) -> typing.Disposable: choice = [None] left_choice = 'L' right_choice = 'R' left_subscription = SingleAssignmentDisposable() right_subscription = SingleAssignmentDisposable() def choice_left(): if not choice[0]: choice[0] = left_choice right_subscription.dispose() def choice_right(): if not choice[0]: choice[0] = right_choice left_subscription.dispose() def on_next_left(value): with left_source.lock: choice_left() if choice[0] == left_choice: observer.on_next(value) def on_error_left(err): with left_source.lock: choice_left() if choice[0] == left_choice: observer.on_error(err) def on_completed_left(): with left_source.lock: choice_left() if choice[0] == left_choice: observer.on_completed() left_d = left_source.subscribe_(on_next_left, on_error_left, on_completed_left, scheduler) left_subscription.disposable = left_d def send_right(value: Any) -> None: with left_source.lock: choice_right() if choice[0] == right_choice: observer.on_next(value) def on_error_right(err: Exception) -> None: with left_source.lock: choice_right() if choice[0] == right_choice: observer.on_error(err) def on_completed_right() -> None: with left_source.lock: choice_right() if choice[0] == right_choice: observer.on_completed() right_d = obs.subscribe_(send_right, on_error_right, on_completed_right, scheduler) right_subscription.disposable = right_d return CompositeDisposable(left_subscription, right_subscription) return Observable(subscribe) return ambRxPY-3.2.0/rx/core/operators/asobservable.py000066400000000000000000000011501404130727200207740ustar00rootroot00000000000000from typing import Callable from rx.core import Observable def _as_observable() -> Callable[[Observable], Observable]: def as_observable(source: Observable) -> Observable: """Hides the identity of an observable sequence. Args: source: Observable source to hide identity from. Returns: An observable sequence that hides the identity of the source sequence. """ def subscribe(observer, scheduler=None): return source.subscribe(observer, scheduler=scheduler) return Observable(subscribe) return as_observable RxPY-3.2.0/rx/core/operators/average.py000066400000000000000000000030001404130727200177320ustar00rootroot00000000000000from typing import Callable, Optional from rx import operators from rx.core import Observable from rx.core.typing import Mapper class AverageValue(object): def __init__(self, sum, count): self.sum = sum self.count = count def _average(key_mapper: Optional[Mapper] = None) -> Callable[[Observable], Observable]: def average(source: Observable) -> Observable: """Partially applied average operator. Computes the average of an observable sequence of values that are in the sequence or obtained by invoking a transform function on each element of the input sequence if present. Examples: >>> res = average(source) Args: source: Source observable to average. Returns: An observable sequence containing a single element with the average of the sequence of values. """ if key_mapper: return source.pipe( operators.map(key_mapper), operators.average() ) def accumulator(prev, cur): return AverageValue(sum=prev.sum+cur, count=prev.count+1) def mapper(s): if s.count == 0: raise Exception('The input sequence was empty') return s.sum / float(s.count) seed = AverageValue(sum=0, count=0) return source.pipe( operators.scan(accumulator, seed), operators.last(), operators.map(mapper) ) return average RxPY-3.2.0/rx/core/operators/buffer.py000066400000000000000000000036501404130727200176040ustar00rootroot00000000000000from typing import Callable, Optional, Any from rx import operators as ops from rx.core import Observable, pipe def _buffer(boundaries: Observable) -> Callable[[Observable], Observable]: return pipe( ops.window(boundaries), ops.flat_map(pipe(ops.to_iterable(), ops.map(list))) ) def _buffer_when(closing_mapper: Callable[[], Observable]) -> Callable[[Observable], Observable]: return pipe( ops.window_when(closing_mapper), ops.flat_map(pipe(ops.to_iterable(), ops.map(list))) ) def _buffer_toggle(openings: Observable, closing_mapper: Callable[[Any], Observable] ) -> Callable[[Observable], Observable]: return pipe( ops.window_toggle(openings, closing_mapper), ops.flat_map(pipe(ops.to_iterable(), ops.map(list))) ) def _buffer_with_count(count: int, skip: Optional[int] = None) -> Callable[[Observable], Observable]: """Projects each element of an observable sequence into zero or more buffers which are produced based on element count information. Examples: >>> res = buffer_with_count(10)(xs) >>> res = buffer_with_count(10, 1)(xs) Args: count: Length of each buffer. skip: [Optional] Number of elements to skip between creation of consecutive buffers. If not provided, defaults to the count. Returns: A function that takes an observable source and returns an observable sequence of buffers. """ def buffer_with_count(source: Observable) -> Observable: nonlocal skip if skip is None: skip = count def mapper(value): return value.pipe(ops.to_iterable(), ops.map(list)) def predicate(value): return len(value) > 0 return source.pipe(ops.window_with_count(count, skip), ops.flat_map(mapper), ops.filter(predicate)) return buffer_with_count RxPY-3.2.0/rx/core/operators/bufferwithtime.py000066400000000000000000000010121404130727200213450ustar00rootroot00000000000000from typing import Callable, Optional from rx import operators as ops from rx.core import Observable, pipe, typing def _buffer_with_time(timespan: typing.RelativeTime, timeshift: Optional[typing.RelativeTime] = None, scheduler: Optional[typing.Scheduler] = None) -> Callable[[Observable], Observable]: if not timeshift: timeshift = timespan return pipe( ops.window_with_time(timespan, timeshift, scheduler), ops.flat_map(lambda x: x.pipe(ops.to_iterable())) ) RxPY-3.2.0/rx/core/operators/bufferwithtimeorcount.py000066400000000000000000000005411404130727200227650ustar00rootroot00000000000000from typing import Callable from rx import operators as ops from rx.core import Observable, pipe def _buffer_with_time_or_count(timespan, count, scheduler = None) -> Callable[[Observable], Observable]: return pipe( ops.window_with_time_or_count(timespan, count, scheduler), ops.flat_map(lambda x: x.pipe(ops.to_iterable())) ) RxPY-3.2.0/rx/core/operators/catch.py000066400000000000000000000046241404130727200174170ustar00rootroot00000000000000from typing import Callable, Union import rx from rx.core import Observable, typing from rx.disposable import SingleAssignmentDisposable, SerialDisposable from rx.internal.utils import is_future def catch_handler(source: Observable, handler: Callable[[Exception, Observable], Observable]) -> Observable: def subscribe(observer, scheduler=None): d1 = SingleAssignmentDisposable() subscription = SerialDisposable() subscription.disposable = d1 def on_error(exception): try: result = handler(exception, source) except Exception as ex: # By design. pylint: disable=W0703 observer.on_error(ex) return result = rx.from_future(result) if is_future(result) else result d = SingleAssignmentDisposable() subscription.disposable = d d.disposable = result.subscribe(observer, scheduler=scheduler) d1.disposable = source.subscribe_( observer.on_next, on_error, observer.on_completed, scheduler ) return subscription return Observable(subscribe) def _catch(handler: Union[Observable, Callable[[Exception, Observable], Observable]] ) -> Callable[[Observable], Observable]: def catch(source: Observable) -> Observable: """Continues an observable sequence that is terminated by an exception with the next observable sequence. Examples: >>> op = catch(ys) >>> op = catch(lambda ex, src: ys(ex)) Args: handler: Second observable sequence used to produce results when an error occurred in the first sequence, or an exception handler function that returns an observable sequence given the error and source observable that occurred in the first sequence. Returns: An observable sequence containing the first sequence's elements, followed by the elements of the handler sequence in case an exception occurred. """ if callable(handler): return catch_handler(source, handler) elif isinstance(handler, typing.Observable): return rx.catch(source, handler) else: raise TypeError('catch operator takes whether an Observable or a callable handler as argument.') return catch RxPY-3.2.0/rx/core/operators/combinelatest.py000066400000000000000000000013541404130727200211630ustar00rootroot00000000000000from typing import Any, Callable, Iterable, Union, List import rx from rx.core import Observable, typing def _combine_latest(*others: Observable) -> Callable[[Observable], Observable]: def combine_latest(source: Observable) -> Observable: """Merges the specified observable sequences into one observable sequence by creating a tuple whenever any of the observable sequences produces an element. Examples: >>> obs = combine_latest(source) Returns: An observable sequence containing the result of combining elements of the sources into a tuple. """ sources = (source,) + others return rx.combine_latest(*sources) return combine_latest RxPY-3.2.0/rx/core/operators/concat.py000066400000000000000000000011351404130727200175760ustar00rootroot00000000000000from typing import Callable import rx from rx.core import Observable def _concat(*sources: Observable) -> Callable[[Observable], Observable]: def concat(source: Observable) -> Observable: """Concatenates all the observable sequences. Examples: >>> op = concat(xs, ys, zs) Returns: An operator function that takes one or more observable sources and returns an observable sequence that contains the elements of each given sequence, in sequential order. """ return rx.concat(source, *sources) return concat RxPY-3.2.0/rx/core/operators/connectable/000077500000000000000000000000001404130727200202325ustar00rootroot00000000000000RxPY-3.2.0/rx/core/operators/connectable/__init__.py000066400000000000000000000000001404130727200223310ustar00rootroot00000000000000RxPY-3.2.0/rx/core/operators/connectable/refcount.py000066400000000000000000000020371404130727200224330ustar00rootroot00000000000000from typing import Callable from rx.disposable import Disposable from rx.core import ConnectableObservable, Observable def _ref_count() -> Callable[[ConnectableObservable], Observable]: """Returns an observable sequence that stays connected to the source as long as there is at least one subscription to the observable sequence. """ connectable_subscription = [None] count = [0] def ref_count(source: ConnectableObservable) -> Observable: def subscribe(observer, scheduler=None): count[0] += 1 should_connect = count[0] == 1 subscription = source.subscribe(observer, scheduler=scheduler) if should_connect: connectable_subscription[0] = source.connect(scheduler) def dispose(): subscription.dispose() count[0] -= 1 if not count[0]: connectable_subscription[0].dispose() return Disposable(dispose) return Observable(subscribe) return ref_countRxPY-3.2.0/rx/core/operators/contains.py000066400000000000000000000007631404130727200201530ustar00rootroot00000000000000from typing import Any, Callable, Optional from rx import operators as ops from rx.core import Observable, pipe from rx.core.typing import Comparer from rx.internal.basic import default_comparer def _contains(value: Any, comparer: Optional[Comparer] = None ) -> Callable[[Observable], Observable]: comparer_ = comparer or default_comparer filtering = ops.filter(lambda v: comparer_(v, value)) something = ops.some() return pipe(filtering, something) RxPY-3.2.0/rx/core/operators/count.py000066400000000000000000000006441404130727200174630ustar00rootroot00000000000000from typing import Callable, Optional from rx.core import Observable, pipe from rx.core.typing import Predicate from rx import operators as ops def _count(predicate: Optional[Predicate] = None) -> Callable[[Observable], Observable]: if predicate: filtering = ops.filter(predicate) return pipe(filtering, ops.count()) counter = ops.reduce(lambda n, _: n + 1, seed=0) return pipe(counter) RxPY-3.2.0/rx/core/operators/debounce.py000066400000000000000000000113711404130727200201160ustar00rootroot00000000000000from typing import Callable, Any from rx.core.typing import Disposable from rx.core import Observable, typing from rx.disposable import CompositeDisposable, SingleAssignmentDisposable, SerialDisposable from rx.scheduler import TimeoutScheduler def _debounce(duetime: typing.RelativeTime, scheduler=typing.Scheduler) -> Callable[[Observable], Observable]: def debounce(source: Observable) -> Observable: """Ignores values from an observable sequence which are followed by another value before duetime. Example: >>> res = debounce(source) Args: source: Source observable to debounce. Returns: An operator function that takes the source observable and returns the debounced observable sequence. """ def subscribe(observer, scheduler_=None) -> Disposable: _scheduler = scheduler or scheduler_ or TimeoutScheduler.singleton() cancelable = SerialDisposable() has_value = [False] value = [None] _id = [0] def on_next(x: Any) -> None: has_value[0] = True value[0] = x _id[0] += 1 current_id = _id[0] d = SingleAssignmentDisposable() cancelable.disposable = d def action(scheduler, state=None) -> None: if has_value[0] and _id[0] == current_id: observer.on_next(value[0]) has_value[0] = False d.disposable = _scheduler.schedule_relative(duetime, action) def on_error(exception: Exception) -> None: cancelable.dispose() observer.on_error(exception) has_value[0] = False _id[0] += 1 def on_completed() -> None: cancelable.dispose() if has_value[0]: observer.on_next(value[0]) observer.on_completed() has_value[0] = False _id[0] += 1 subscription = source.subscribe_(on_next, on_error, on_completed, scheduler=scheduler_) return CompositeDisposable(subscription, cancelable) return Observable(subscribe) return debounce def _throttle_with_mapper(throttle_duration_mapper: Callable[[Any], Observable]) -> Callable[[Observable], Observable]: def throttle_with_mapper(source: Observable) -> Observable: """Partially applied throttle_with_mapper operator. Ignores values from an observable sequence which are followed by another value within a computed throttle duration. Example: >>> obs = throttle_with_mapper(source) Args: source: The observable source to throttle. Returns: The throttled observable sequence. """ def subscribe(observer, scheduler=None) -> Disposable: cancelable = SerialDisposable() has_value = [False] value = [None] _id = [0] def on_next(x): throttle = None try: throttle = throttle_duration_mapper(x) except Exception as e: # pylint: disable=broad-except observer.on_error(e) return has_value[0] = True value[0] = x _id[0] += 1 current_id = _id[0] d = SingleAssignmentDisposable() cancelable.disposable = d def on_next(x: Any) -> None: if has_value[0] and _id[0] == current_id: observer.on_next(value[0]) has_value[0] = False d.dispose() def on_completed() -> None: if has_value[0] and _id[0] == current_id: observer.on_next(value[0]) has_value[0] = False d.dispose() d.disposable = throttle.subscribe_(on_next, observer.on_error, on_completed, scheduler=scheduler) def on_error(e) -> None: cancelable.dispose() observer.on_error(e) has_value[0] = False _id[0] += 1 def on_completed() -> None: cancelable.dispose() if has_value[0]: observer.on_next(value[0]) observer.on_completed() has_value[0] = False _id[0] += 1 subscription = source.subscribe_(on_next, on_error, on_completed, scheduler=scheduler) return CompositeDisposable(subscription, cancelable) return Observable(subscribe) return throttle_with_mapper RxPY-3.2.0/rx/core/operators/defaultifempty.py000066400000000000000000000023041404130727200213500ustar00rootroot00000000000000from typing import Any, Callable from rx.core import Observable from rx.core.typing import Disposable def _default_if_empty(default_value: Any = None) -> Callable[[Observable], Observable]: def default_if_empty(source: Observable) -> Observable: """Returns the elements of the specified sequence or the specified value in a singleton sequence if the sequence is empty. Examples: >>> obs = default_if_empty(source) Args: source: Source observable. Returns: An observable sequence that contains the specified default value if the source is empty otherwise, the elements of the source. """ def subscribe(observer, scheduler=None) -> Disposable: found = [False] def on_next(x: Any): found[0] = True observer.on_next(x) def on_completed(): if not found[0]: observer.on_next(default_value) observer.on_completed() return source.subscribe_(on_next, observer.on_error, on_completed, scheduler) return Observable(subscribe) return default_if_empty RxPY-3.2.0/rx/core/operators/delay.py000066400000000000000000000102551404130727200174300ustar00rootroot00000000000000from typing import Callable, Optional from datetime import datetime, timedelta from rx import operators as ops from rx.core import Observable, typing from rx.internal.constants import DELTA_ZERO from rx.disposable import CompositeDisposable, SerialDisposable, MultipleAssignmentDisposable from rx.scheduler import TimeoutScheduler class Timestamp(object): def __init__(self, value, timestamp): self.value = value self.timestamp = timestamp def observable_delay_timespan(source: Observable, duetime: typing.RelativeTime, scheduler: Optional[typing.Scheduler] = None) -> Observable: def subscribe(observer, scheduler_=None): nonlocal duetime _scheduler = scheduler or scheduler_ or TimeoutScheduler.singleton() if isinstance(duetime, datetime): duetime = _scheduler.to_datetime(duetime) - _scheduler.now else: duetime = _scheduler.to_timedelta(duetime) cancelable = SerialDisposable() exception = [None] active = [False] running = [False] queue = [] def on_next(notification): should_run = False with source.lock: if notification.value.kind == 'E': del queue[:] queue.append(notification) exception[0] = notification.value.exception should_run = not running[0] else: queue.append(Timestamp(value=notification.value, timestamp=notification.timestamp + duetime)) should_run = not active[0] active[0] = True if should_run: if exception[0]: observer.on_error(exception[0]) else: mad = MultipleAssignmentDisposable() cancelable.disposable = mad def action(scheduler, state): if exception[0]: return with source.lock: running[0] = True while True: result = None if queue and queue[0].timestamp <= scheduler.now: result = queue.pop(0).value if result: result.accept(observer) if not result: break should_continue = False recurse_duetime = 0 if queue: should_continue = True diff = queue[0].timestamp - scheduler.now zero = DELTA_ZERO if isinstance(diff, timedelta) else 0 recurse_duetime = max(zero, diff) else: active[0] = False ex = exception[0] running[0] = False if ex: observer.on_error(ex) elif should_continue: mad.disposable = scheduler.schedule_relative(recurse_duetime, action) mad.disposable = _scheduler.schedule_relative(duetime, action) subscription = source.pipe( ops.materialize(), ops.timestamp() ).subscribe_(on_next, scheduler=_scheduler) return CompositeDisposable(subscription, cancelable) return Observable(subscribe) def _delay(duetime: typing.RelativeTime, scheduler: Optional[typing.Scheduler] = None) -> Callable[[Observable], Observable]: def delay(source: Observable) -> Observable: """Time shifts the observable sequence. A partially applied delay operator function. Examples: >>> res = delay(source) Args: source: The observable sequence to delay. Returns: A time-shifted observable sequence. """ return observable_delay_timespan(source, duetime, scheduler) return delay RxPY-3.2.0/rx/core/operators/delaysubscription.py000066400000000000000000000015571404130727200221020ustar00rootroot00000000000000from typing import Callable, Optional import rx from rx import operators as ops from rx.core import Observable, typing def _delay_subscription(duetime: typing.AbsoluteOrRelativeTime, scheduler: Optional[typing.Scheduler] = None ) -> Callable[[Observable], Observable]: def delay_subscription(source: Observable) -> Observable: """Time shifts the observable sequence by delaying the subscription. Exampeles. >>> res = source.delay_subscription(5) Args: source: Source subscription to delay. Returns: Time-shifted sequence. """ def mapper(_) -> Observable: return rx.empty() return source.pipe( ops.delay_with_mapper(rx.timer(duetime, scheduler=scheduler), mapper) ) return delay_subscription RxPY-3.2.0/rx/core/operators/delaywithmapper.py000066400000000000000000000053301404130727200215270ustar00rootroot00000000000000from typing import Callable from rx.core import Observable, typing from rx.disposable import CompositeDisposable, SingleAssignmentDisposable, SerialDisposable def _delay_with_mapper(subscription_delay=None, delay_duration_mapper=None) -> Callable[[Observable], Observable]: def delay_with_mapper(source: Observable) -> Observable: """Time shifts the observable sequence based on a subscription delay and a delay mapper function for each element. Examples: >>> obs = delay_with_selector(source) Args: subscription_delay: [Optional] Sequence indicating the delay for the subscription to the source. delay_duration_mapper: [Optional] Selector function to retrieve a sequence indicating the delay for each given element. Returns: Time-shifted observable sequence. """ sub_delay, mapper = None, None if isinstance(subscription_delay, typing.Observable): mapper = delay_duration_mapper sub_delay = subscription_delay else: mapper = subscription_delay def subscribe(observer, scheduler=None): delays = CompositeDisposable() at_end = [False] def done(): if (at_end[0] and delays.length == 0): observer.on_completed() subscription = SerialDisposable() def start(): def on_next(x): try: delay = mapper(x) except Exception as error: observer.on_error(error) return d = SingleAssignmentDisposable() delays.add(d) def on_next(_): observer.on_next(x) delays.remove(d) done() def on_completed(): observer.on_next(x) delays.remove(d) done() d.disposable = delay.subscribe_(on_next, observer.on_error, on_completed, scheduler) def on_completed(): at_end[0] = True subscription.dispose() done() subscription.disposable = source.subscribe_(on_next, observer.on_error, on_completed, scheduler) if not sub_delay: start() else: subscription.disposable = sub_delay.subscribe_(lambda _: start(), observer.on_error, start) return CompositeDisposable(subscription, delays) return Observable(subscribe) return delay_with_mapperRxPY-3.2.0/rx/core/operators/dematerialize.py000066400000000000000000000014401404130727200211450ustar00rootroot00000000000000from typing import Callable from rx.core import Observable def _dematerialize() -> Callable[[Observable], Observable]: def dematerialize(source: Observable) -> Observable: """Partially applied dematerialize operator. Dematerializes the explicit notification values of an observable sequence as implicit notifications. Returns: An observable sequence exhibiting the behavior corresponding to the source sequence's notification values. """ def subscribe(observer, scheduler=None): def on_next(value): return value.accept(observer) return source.subscribe_(on_next, observer.on_error, observer.on_completed, scheduler) return Observable(subscribe) return dematerialize RxPY-3.2.0/rx/core/operators/distinct.py000066400000000000000000000040121404130727200201450ustar00rootroot00000000000000from typing import Callable, Optional from rx.core import Observable from rx.core.typing import Mapper, Comparer from rx.internal.basic import default_comparer def array_index_of_comparer(array, item, comparer): for i, a in enumerate(array): if comparer(a, item): return i return -1 class HashSet: def __init__(self, comparer): self.comparer = comparer self.set = [] def push(self, value): ret_value = array_index_of_comparer(self.set, value, self.comparer) == -1 if ret_value: self.set.append(value) return ret_value def _distinct(key_mapper: Optional[Mapper] = None, comparer: Optional[Comparer] = None ) -> Callable[[Observable], Observable]: comparer = comparer or default_comparer def distinct(source: Observable) -> Observable: """Returns an observable sequence that contains only distinct elements according to the key_mapper and the comparer. Usage of this operator should be considered carefully due to the maintenance of an internal lookup structure which can grow large. Examples: >>> res = obs = distinct(source) Args: source: Source observable to return distinct items from. Returns: An observable sequence only containing the distinct elements, based on a computed key value, from the source sequence. """ def subscribe(observer, scheduler=None): hashset = HashSet(comparer) def on_next(x): key = x if key_mapper: try: key = key_mapper(x) except Exception as ex: observer.on_error(ex) return hashset.push(key) and observer.on_next(x) return source.subscribe_(on_next, observer.on_error,observer.on_completed, scheduler) return Observable(subscribe) return distinct RxPY-3.2.0/rx/core/operators/distinctuntilchanged.py000066400000000000000000000046731404130727200225500ustar00rootroot00000000000000from typing import Callable, Optional from rx.core import Observable from rx.core.typing import Mapper, Comparer from rx.internal.basic import identity, default_comparer def _distinct_until_changed( key_mapper: Optional[Mapper] = None, comparer: Optional[Comparer] = None ) -> Callable[[Observable], Observable]: key_mapper = key_mapper or identity comparer = comparer or default_comparer def distinct_until_changed(source: Observable) -> Observable: """Returns an observable sequence that contains only distinct contiguous elements according to the key_mapper and the comparer. Examples: >>> op = distinct_until_changed(); >>> op = distinct_until_changed(lambda x: x.id) >>> op = distinct_until_changed(lambda x: x.id, lambda x, y: x == y) Args: key_mapper: [Optional] A function to compute the comparison key for each element. If not provided, it projects the value. comparer: [Optional] Equality comparer for computed key values. If not provided, defaults to an equality comparer function. Returns: An observable sequence only containing the distinct contiguous elements, based on a computed key value, from the source sequence. """ def subscribe(observer, scheduler=None): has_current_key = [False] current_key = [None] def on_next(value): comparer_equals = False try: key = key_mapper(value) except Exception as exception: # pylint: disable=broad-except observer.on_error(exception) return if has_current_key[0]: try: comparer_equals = comparer(current_key[0], key) except Exception as exception: # pylint: disable=broad-except observer.on_error(exception) return if not has_current_key[0] or not comparer_equals: has_current_key[0] = True current_key[0] = key observer.on_next(value) return source.subscribe_(on_next, observer.on_error, observer.on_completed, scheduler=scheduler) return Observable(subscribe) return distinct_until_changed RxPY-3.2.0/rx/core/operators/do.py000066400000000000000000000222101404130727200167260ustar00rootroot00000000000000from typing import Callable, Optional from rx.core import Observable, typing from rx.core.typing import Observer, Disposable from rx.disposable import CompositeDisposable def _do_action(on_next: Optional[typing.OnNext] = None, on_error: Optional[typing.OnError] = None, on_completed: Optional[typing.OnCompleted] = None ) -> Callable[[Observable], Observable]: def do_action(source: Observable) -> Observable: """Invokes an action for each element in the observable sequence and invokes an action on graceful or exceptional termination of the observable sequence. This method can be used for debugging, logging, etc. of query behavior by intercepting the message stream to run arbitrary actions for messages on the pipeline. Examples: >>> do_action(send)(observable) >>> do_action(on_next, on_error)(observable) >>> do_action(on_next, on_error, on_completed)(observable) Args: on_next: [Optional] Action to invoke for each element in the observable sequence. on_error: [Optional] Action to invoke on exceptional termination of the observable sequence. on_completed: [Optional] Action to invoke on graceful termination of the observable sequence. Returns: An observable source sequence with the side-effecting behavior applied. """ def subscribe(observer, scheduler=None): def _on_next(x): if not on_next: observer.on_next(x) else: try: on_next(x) except Exception as e: # pylint: disable=broad-except observer.on_error(e) observer.on_next(x) def _on_error(exception): if not on_error: observer.on_error(exception) else: try: on_error(exception) except Exception as e: # pylint: disable=broad-except observer.on_error(e) observer.on_error(exception) def _on_completed(): if not on_completed: observer.on_completed() else: try: on_completed() except Exception as e: # pylint: disable=broad-except observer.on_error(e) observer.on_completed() return source.subscribe_(_on_next, _on_error, _on_completed, scheduler) return Observable(subscribe) return do_action def do(observer: Observer) -> Callable[[Observable], Observable]: """Invokes an action for each element in the observable sequence and invokes an action on graceful or exceptional termination of the observable sequence. This method can be used for debugging, logging, etc. of query behavior by intercepting the message stream to run arbitrary actions for messages on the pipeline. >>> do(observer) Args: observer: Observer Returns: An operator function that takes the source observable and returns the source sequence with the side-effecting behavior applied. """ return _do_action(observer.on_next, observer.on_error, observer.on_completed) def do_after_next(source, after_next): """Invokes an action with each element after it has been emitted downstream. This can be helpful for debugging, logging, and other side effects. after_next -- Action to invoke on each element after it has been emitted """ def subscribe(observer, scheduler=None): def on_next(value): try: observer.on_next(value) after_next(value) except Exception as e: # pylint: disable=broad-except observer.on_error(e) return source.subscribe_(on_next, observer.on_error, observer.on_completed) return Observable(subscribe) def do_on_subscribe(source: Observable, on_subscribe): """Invokes an action on subscription. This can be helpful for debugging, logging, and other side effects on the start of an operation. Args: on_subscribe: Action to invoke on subscription """ def subscribe(observer, scheduler=None): on_subscribe() return source.subscribe_(observer.on_next, observer.on_error, observer.on_completed, scheduler) return Observable(subscribe) def do_on_dispose(source: Observable, on_dispose): """Invokes an action on disposal. This can be helpful for debugging, logging, and other side effects on the disposal of an operation. Args: on_dispose: Action to invoke on disposal """ class OnDispose(Disposable): def dispose(self) -> None: on_dispose() def subscribe(observer, scheduler=None): composite_disposable = CompositeDisposable() composite_disposable.add(OnDispose()) subscription = source.subscribe_(observer.on_next, observer.on_error, observer.on_completed, scheduler) composite_disposable.add(subscription) return composite_disposable return Observable(subscribe) def do_on_terminate(source, on_terminate): """Invokes an action on an on_complete() or on_error() event. This can be helpful for debugging, logging, and other side effects when completion or an error terminates an operation. on_terminate -- Action to invoke when on_complete or throw is called """ def subscribe(observer, scheduler=None): def on_completed(): try: on_terminate() except Exception as err: # pylint: disable=broad-except observer.on_error(err) else: observer.on_completed() def on_error(exception): try: on_terminate() except Exception as err: # pylint: disable=broad-except observer.on_error(err) else: observer.on_error(exception) return source.subscribe_(observer.on_next, on_error, on_completed, scheduler) return Observable(subscribe) def do_after_terminate(source, after_terminate): """Invokes an action after an on_complete() or on_error() event. This can be helpful for debugging, logging, and other side effects when completion or an error terminates an operation on_terminate -- Action to invoke after on_complete or throw is called """ def subscribe(observer, scheduler=None): def on_completed(): observer.on_completed() try: after_terminate() except Exception as err: # pylint: disable=broad-except observer.on_error(err) def on_error(exception): observer.on_error(exception) try: after_terminate() except Exception as err: # pylint: disable=broad-except observer.on_error(err) return source.subscribe(observer.on_next, on_error, on_completed, scheduler) return Observable(subscribe) def do_finally(finally_action: Callable) -> Callable[[Observable], Observable]: """Invokes an action after an on_complete(), on_error(), or disposal event occurs. This can be helpful for debugging, logging, and other side effects when completion, an error, or disposal terminates an operation. Note this operator will strive to execute the finally_action once, and prevent any redudant calls Args: finally_action -- Action to invoke after on_complete, on_error, or disposal is called """ class OnDispose(Disposable): def __init__(self, was_invoked): self.was_invoked = was_invoked def dispose(self) -> None: if not self.was_invoked[0]: finally_action() self.was_invoked[0] = True def partial(source: Observable) -> Observable: def subscribe(observer, scheduler=None): was_invoked = [False] def on_completed(): observer.on_completed() try: if not was_invoked[0]: finally_action() was_invoked[0] = True except Exception as err: # pylint: disable=broad-except observer.on_error(err) def on_error(exception): observer.on_error(exception) try: if not was_invoked[0]: finally_action() was_invoked[0] = True except Exception as err: # pylint: disable=broad-except observer.on_error(err) composite_disposable = CompositeDisposable() composite_disposable.add(OnDispose(was_invoked)) subscription = source.subscribe_(observer.on_next, on_error, on_completed, scheduler) composite_disposable.add(subscription) return composite_disposable return Observable(subscribe) return partial RxPY-3.2.0/rx/core/operators/dowhile.py000066400000000000000000000011701404130727200177610ustar00rootroot00000000000000from typing import Callable, Any from rx import operators as ops from rx.core import Observable def _do_while(condition: Callable[[Any], bool]) -> Callable[[Observable], Observable]: """Repeats source as long as condition holds emulating a do while loop. Args: condition: The condition which determines if the source will be repeated. Returns: An observable sequence which is repeated as long as the condition holds. """ def do_while(source: Observable) -> Observable: return source.pipe(ops.concat(source.pipe(ops.while_do(condition)))) return do_while RxPY-3.2.0/rx/core/operators/elementatordefault.py000066400000000000000000000021771404130727200222220ustar00rootroot00000000000000from typing import Any, Callable from rx.core import Observable from rx.internal.exceptions import ArgumentOutOfRangeException def _element_at_or_default(index, has_default=False, default_value=None): if index < 0: raise ArgumentOutOfRangeException() def element_at_or_default(source: Observable) -> Observable: def subscribe(observer, scheduler=None): i = [index] def on_next(x): found = False with source.lock: if i[0]: i[0] -= 1 else: found = True if found: observer.on_next(x) observer.on_completed() def on_completed(): if not has_default: observer.on_error(ArgumentOutOfRangeException()) else: observer.on_next(default_value) observer.on_completed() return source.subscribe_(on_next, observer.on_error, on_completed, scheduler) return Observable(subscribe) return element_at_or_default RxPY-3.2.0/rx/core/operators/exclusive.py000066400000000000000000000037601404130727200203440ustar00rootroot00000000000000from typing import Callable import rx from rx.core import Observable from rx.disposable import CompositeDisposable, SingleAssignmentDisposable from rx.internal.utils import is_future def _exclusive() -> Callable[[Observable], Observable]: """Performs a exclusive waiting for the first to finish before subscribing to another observable. Observables that come in between subscriptions will be dropped on the floor. Returns: An exclusive observable with only the results that happen when subscribed. """ def exclusive(source: Observable) -> Observable: def subscribe(observer, scheduler=None): has_current = [False] is_stopped = [False] m = SingleAssignmentDisposable() g = CompositeDisposable() g.add(m) def on_next(inner_source): if not has_current[0]: has_current[0] = True inner_source = rx.from_future(inner_source) if is_future(inner_source) else inner_source inner_subscription = SingleAssignmentDisposable() g.add(inner_subscription) def on_completed_inner(): g.remove(inner_subscription) has_current[0] = False if is_stopped[0] and len(g) == 1: observer.on_completed() inner_subscription.disposable = inner_source.subscribe_( observer.on_next, observer.on_error, on_completed_inner, scheduler ) def on_completed(): is_stopped[0] = True if not has_current[0] and len(g) == 1: observer.on_completed() m.disposable = source.subscribe_(on_next, observer.on_error, on_completed, scheduler) return g return Observable(subscribe) return exclusive RxPY-3.2.0/rx/core/operators/expand.py000066400000000000000000000047531404130727200176170ustar00rootroot00000000000000from typing import Callable from rx.core import Observable from rx.core.typing import Mapper from rx.disposable import SerialDisposable, CompositeDisposable, SingleAssignmentDisposable from rx.scheduler import ImmediateScheduler def _expand(mapper: Mapper) -> Callable[[Observable], Observable]: def expand(source: Observable) -> Observable: """Expands an observable sequence by recursively invoking mapper. Args: source: Source obserable to expand. Returns: An observable sequence containing all the elements produced by the recursive expansion. """ def subscribe(observer, scheduler=None): scheduler = scheduler or ImmediateScheduler.singleton() queue = [] m = SerialDisposable() d = CompositeDisposable(m) active_count = [0] is_acquired = [False] def ensure_active(): is_owner = False if queue: is_owner = not is_acquired[0] is_acquired[0] = True def action(scheduler, state): if queue: work = queue.pop(0) else: is_acquired[0] = False return sad = SingleAssignmentDisposable() d.add(sad) def on_next(value): observer.on_next(value) result = None try: result = mapper(value) except Exception as ex: observer.on_error(ex) return queue.append(result) active_count[0] += 1 ensure_active() def on_complete(): d.remove(sad) active_count[0] -= 1 if active_count[0] == 0: observer.on_completed() sad.disposable = work.subscribe_(on_next, observer.on_error, on_complete, scheduler) m.disposable = scheduler.schedule(action) if is_owner: m.disposable = scheduler.schedule(action) queue.append(source) active_count[0] += 1 ensure_active() return d return Observable(subscribe) return expand RxPY-3.2.0/rx/core/operators/filter.py000066400000000000000000000045751404130727200176270ustar00rootroot00000000000000from typing import Callable, Optional from rx.core import Observable from rx.core.typing import Predicate, PredicateIndexed, Scheduler, Observer, Disposable # pylint: disable=redefined-builtin def _filter(predicate: Predicate) -> Callable[[Observable], Observable]: def filter(source: Observable) -> Observable: """Partially applied filter operator. Filters the elements of an observable sequence based on a predicate. Example: >>> filter(source) Args: source: Source observable to filter. Returns: A filtered observable sequence. """ def subscribe(observer: Observer, scheduler: Optional[Scheduler]) -> Disposable: def on_next(value): try: should_run = predicate(value) except Exception as ex: # pylint: disable=broad-except observer.on_error(ex) return if should_run: observer.on_next(value) return source.subscribe_(on_next, observer.on_error, observer.on_completed, scheduler) return Observable(subscribe) return filter def _filter_indexed(predicate_indexed: Optional[PredicateIndexed] = None) -> Callable[[Observable], Observable]: def filter_indexed(source: Observable) -> Observable: """Partially applied indexed filter operator. Filters the elements of an observable sequence based on a predicate by incorporating the element's index. Example: >>> filter_indexed(source) Args: source: Source observable to filter. Returns: A filtered observable sequence. """ def subscribe(observer: Observer, scheduler: Optional[Scheduler]): count = 0 def on_next(value): nonlocal count try: should_run = predicate_indexed(value, count) except Exception as ex: # pylint: disable=broad-except observer.on_error(ex) return else: count += 1 if should_run: observer.on_next(value) return source.subscribe_(on_next, observer.on_error, observer.on_completed, scheduler) return Observable(subscribe) return filter_indexed RxPY-3.2.0/rx/core/operators/finallyaction.py000066400000000000000000000020551404130727200211650ustar00rootroot00000000000000from typing import Callable from rx.disposable import Disposable from rx.core import Observable def _finally_action(action: Callable) -> Callable[[Observable], Observable]: def finally_action(source: Observable) -> Observable: """Invokes a specified action after the source observable sequence terminates gracefully or exceptionally. Example: res = finally(source) Args: source: Observable sequence. Returns: An observable sequence with the action-invoking termination behavior applied. """ def subscribe(observer, scheduler=None): try: subscription = source.subscribe(observer, scheduler=scheduler) except Exception: action() raise def dispose(): try: subscription.dispose() finally: action() return Disposable(dispose) return Observable(subscribe) return finally_action RxPY-3.2.0/rx/core/operators/find.py000066400000000000000000000020601404130727200172450ustar00rootroot00000000000000from typing import Callable from rx.core import Observable from rx.core.typing import Predicate def _find_value(predicate: Predicate, yield_index) -> Callable[[Observable], Observable]: def find_value(source: Observable) -> Observable: def subscribe(observer, scheduler=None): i = [0] def on_next(x): should_run = False try: should_run = predicate(x, i, source) except Exception as ex: # pylint: disable=broad-except observer.on_error(ex) return if should_run: observer.on_next(i[0] if yield_index else x) observer.on_completed() else: i[0] += 1 def on_completed(): observer.on_next(-1 if yield_index else None) observer.on_completed() return source.subscribe_(on_next, observer.on_error, on_completed, scheduler) return Observable(subscribe) return find_value RxPY-3.2.0/rx/core/operators/first.py000066400000000000000000000020761404130727200174630ustar00rootroot00000000000000from typing import Callable, Optional from rx import operators as ops from rx.core import Observable, pipe from rx.core.typing import Predicate from .firstordefault import _first_or_default_async def _first(predicate: Optional[Predicate] = None) -> Callable[[Observable], Observable]: """Returns the first element of an observable sequence that satisfies the condition in the predicate if present else the first item in the sequence. Examples: >>> res = res = first()(source) >>> res = res = first(lambda x: x > 3)(source) Args: predicate -- [Optional] A predicate function to evaluate for elements in the source sequence. Returns: A function that takes an observable source and returns an observable sequence containing the first element in the observable sequence that satisfies the condition in the predicate if provided, else the first item in the sequence. """ if predicate: return pipe(ops.filter(predicate), ops.first()) return _first_or_default_async(False) RxPY-3.2.0/rx/core/operators/firstordefault.py000066400000000000000000000043261404130727200213710ustar00rootroot00000000000000from typing import Any, Callable, Optional from rx import operators as ops from rx.core import Observable, pipe from rx.core.typing import Predicate from rx.internal.exceptions import SequenceContainsNoElementsError def _first_or_default_async(has_default=False, default_value=None): def first_or_default_async(source: Observable) -> Observable: def subscribe(observer, scheduler=None): def on_next(x): observer.on_next(x) observer.on_completed() def on_completed(): if not has_default: observer.on_error(SequenceContainsNoElementsError()) else: observer.on_next(default_value) observer.on_completed() return source.subscribe_(on_next, observer.on_error, on_completed, scheduler) return Observable(subscribe) return first_or_default_async def _first_or_default(predicate: Optional[Predicate] = None, default_value: Any = None ) -> Callable[[Observable], Observable]: """Returns the first element of an observable sequence that satisfies the condition in the predicate, or a default value if no such element exists. Examples: >>> res = source.first_or_default() >>> res = source.first_or_default(lambda x: x > 3) >>> res = source.first_or_default(lambda x: x > 3, 0) >>> res = source.first_or_default(None, 0) Args: source -- Observable sequence. predicate -- [optional] A predicate function to evaluate for elements in the source sequence. default_value -- [Optional] The default value if no such element exists. If not specified, defaults to None. Returns: A function that takes an observable source and reutrn an observable sequence containing the first element in the observable sequence that satisfies the condition in the predicate, or a default value if no such element exists. """ if predicate: return pipe( ops.filter(predicate), ops.first_or_default(None, default_value) ) return _first_or_default_async(True, default_value) RxPY-3.2.0/rx/core/operators/flatmap.py000066400000000000000000000072001404130727200177520ustar00rootroot00000000000000import collections from typing import Callable, Optional from rx import from_, from_future, operators as ops from rx.core import Observable from rx.core.typing import Mapper, MapperIndexed from rx.internal.utils import is_future def _flat_map_internal(source, mapper=None, mapper_indexed=None): def projection(x, i): mapper_result = mapper(x) if mapper else mapper_indexed(x, i) if is_future(mapper_result): result = from_future(mapper_result) elif isinstance(mapper_result, collections.abc.Iterable): result = from_(mapper_result) else: result = mapper_result return result return source.pipe( ops.map_indexed(projection), ops.merge_all() ) def _flat_map(mapper: Optional[Mapper] = None) -> Callable[[Observable], Observable]: def flat_map(source: Observable) -> Observable: """One of the Following: Projects each element of an observable sequence to an observable sequence and merges the resulting observable sequences into one observable sequence. Example: >>> flat_map(source) Args: source: Source observable to flat map. Returns: An operator function that takes a source observable and returns an observable sequence whose elements are the result of invoking the one-to-many transform function on each element of the input sequence . """ if callable(mapper): ret = _flat_map_internal(source, mapper=mapper) else: ret = _flat_map_internal(source, mapper=lambda _: mapper) return ret return flat_map def _flat_map_indexed(mapper_indexed: Optional[MapperIndexed] = None) -> Callable[[Observable], Observable]: def flat_map_indexed(source: Observable) -> Observable: """One of the Following: Projects each element of an observable sequence to an observable sequence and merges the resulting observable sequences into one observable sequence. Example: >>> flat_map_indexed(source) Args: source: Source observable to flat map. Returns: An observable sequence whose elements are the result of invoking the one-to-many transform function on each element of the input sequence. """ if callable(mapper_indexed): ret = _flat_map_internal(source, mapper_indexed=mapper_indexed) else: ret = _flat_map_internal(source, mapper=lambda _: mapper_indexed) return ret return flat_map_indexed def _flat_map_latest(mapper: Mapper) -> Callable[[Observable], Observable]: def flat_map_latest(source: Observable) -> Observable: """Projects each element of an observable sequence into a new sequence of observable sequences by incorporating the element's index and then transforms an observable sequence of observable sequences into an observable sequence producing values only from the most recent observable sequence. Args: source: Source observable to flat map latest. Returns: An observable sequence whose elements are the result of invoking the transform function on each element of source producing an observable of Observable sequences and that at any point in time produces the elements of the most recent inner observable sequence that has been received. """ return source.pipe( ops.map(mapper), ops.switch_latest() ) return flat_map_latest RxPY-3.2.0/rx/core/operators/forkjoin.py000066400000000000000000000013171404130727200201520ustar00rootroot00000000000000from typing import Callable import rx from rx import Observable def _fork_join(*args: Observable) -> Callable[[Observable], Observable]: def fork_join(source: Observable) -> Observable: """Wait for observables to complete and then combine last values they emitted into a tuple. Whenever any of that observables completes without emitting any value, result sequence will complete at that moment as well. Examples: >>> obs = fork_join(source) Returns: An observable sequence containing the result of combining last element from each source in given sequence. """ return rx.fork_join(source, *args) return fork_join RxPY-3.2.0/rx/core/operators/groupby.py000066400000000000000000000010341404130727200200140ustar00rootroot00000000000000from typing import Callable, Optional import rx from rx import operators as ops from rx.core import Observable from rx.core.typing import Mapper from rx.subject import Subject def _group_by(key_mapper: Mapper, element_mapper: Optional[Mapper] = None, subject_mapper: Optional[Callable[[], Subject]] = None, ) -> Callable[[Observable], Observable]: def duration_mapper(_): return rx.never() return ops.group_by_until(key_mapper, element_mapper, duration_mapper, subject_mapper) RxPY-3.2.0/rx/core/operators/groupbyuntil.py000066400000000000000000000123451404130727200210770ustar00rootroot00000000000000from typing import Callable, Optional from collections import OrderedDict from rx import operators as ops from rx.core import Observable, GroupedObservable from rx.core.typing import Mapper from rx.subject import Subject from rx.disposable import CompositeDisposable, RefCountDisposable, SingleAssignmentDisposable from rx.internal.basic import identity def _group_by_until(key_mapper: Mapper, element_mapper: Optional[Mapper], duration_mapper: Callable[[GroupedObservable], Observable], subject_mapper: Optional[Callable[[], Subject]] = None, ) -> Callable[[Observable], Observable]: """Groups the elements of an observable sequence according to a specified key mapper function. A duration mapper function is used to control the lifetime of groups. When a group expires, it receives an OnCompleted notification. When a new element with the same key value as a reclaimed group occurs, the group will be reborn with a new lifetime request. Examples: >>> group_by_until(lambda x: x.id, None, lambda : rx.never()) >>> group_by_until(lambda x: x.id,lambda x: x.name, lambda grp: rx.never()) >>> group_by_until(lambda x: x.id, lambda x: x.name, lambda grp: rx.never(), lambda: ReplaySubject()) Args: key_mapper: A function to extract the key for each element. duration_mapper: A function to signal the expiration of a group. subject_mapper: A function that returns a subject used to initiate a grouped observable. Default mapper returns a Subject object. Returns: a sequence of observable groups, each of which corresponds to a unique key value, containing all elements that share that same key value. If a group's lifetime expires, a new group with the same key value can be created once an element with such a key value is encountered. """ element_mapper = element_mapper or identity default_subject_mapper = Subject subject_mapper = subject_mapper or default_subject_mapper def group_by_until(source: Observable) -> Observable: def subscribe(observer, scheduler=None): writers = OrderedDict() group_disposable = CompositeDisposable() ref_count_disposable = RefCountDisposable(group_disposable) def on_next(x): writer = None key = None try: key = key_mapper(x) except Exception as e: for wrt in writers.values(): wrt.on_error(e) observer.on_error(e) return fire_new_map_entry = False writer = writers.get(key) if not writer: try: writer = subject_mapper() except Exception as e: for wrt in writers.values(): wrt.on_error(e) observer.on_error(e) return writers[key] = writer fire_new_map_entry = True if fire_new_map_entry: group = GroupedObservable(key, writer, ref_count_disposable) duration_group = GroupedObservable(key, writer) try: duration = duration_mapper(duration_group) except Exception as e: for wrt in writers.values(): wrt.on_error(e) observer.on_error(e) return observer.on_next(group) sad = SingleAssignmentDisposable() group_disposable.add(sad) def expire(): if writers[key]: del writers[key] writer.on_completed() group_disposable.remove(sad) def on_next(value): pass def on_error(exn): for wrt in writers.values(): wrt.on_error(exn) observer.on_error(exn) def on_completed(): expire() sad.disposable = duration.pipe(ops.take(1)).subscribe_(on_next, on_error, on_completed, scheduler) try: element = element_mapper(x) except Exception as error: for wrt in writers.values(): wrt.on_error(error) observer.on_error(error) return writer.on_next(element) def on_error(ex): for wrt in writers.values(): wrt.on_error(ex) observer.on_error(ex) def on_completed(): for wrt in writers.values(): wrt.on_completed() observer.on_completed() group_disposable.add(source.subscribe_(on_next, on_error, on_completed, scheduler)) return ref_count_disposable return Observable(subscribe) return group_by_until RxPY-3.2.0/rx/core/operators/groupjoin.py000066400000000000000000000117111404130727200203440ustar00rootroot00000000000000import logging from typing import Callable, Any from collections import OrderedDict from rx import operators as ops from rx.core import Observable from rx.internal.utils import add_ref from rx.disposable import SingleAssignmentDisposable, RefCountDisposable, CompositeDisposable from rx.subject import Subject log = logging.getLogger("Rx") def _group_join(right: Observable, left_duration_mapper: Callable[[Any], Observable], right_duration_mapper: Callable[[Any], Observable], ) -> Callable[[Observable], Observable]: """Correlates the elements of two sequences based on overlapping durations, and groups the results. Args: right: The right observable sequence to join elements for. left_duration_mapper: A function to select the duration (expressed as an observable sequence) of each element of the left observable sequence, used to determine overlap. right_duration_mapper: A function to select the duration (expressed as an observable sequence) of each element of the right observable sequence, used to determine overlap. Returns: An observable sequence that contains elements combined into a tuple from source elements that have an overlapping duration. """ def nothing(_): return None def group_join(left: Observable) -> Observable: def subscribe(observer, scheduler=None): group = CompositeDisposable() rcd = RefCountDisposable(group) left_map = OrderedDict() right_map = OrderedDict() left_id = [0] right_id = [0] def on_next_left(value): subject = Subject() with left.lock: _id = left_id[0] left_id[0] += 1 left_map[_id] = subject try: result = (value, add_ref(subject, rcd)) except Exception as e: log.error("*** Exception: %s" % e) for left_value in left_map.values(): left_value.on_error(e) observer.on_error(e) return observer.on_next(result) for right_value in right_map.values(): subject.on_next(right_value) md = SingleAssignmentDisposable() group.add(md) def expire(): if _id in left_map: del left_map[_id] subject.on_completed() group.remove(md) try: duration = left_duration_mapper(value) except Exception as e: for left_value in left_map.values(): left_value.on_error(e) observer.on_error(e) return def on_error(error): for left_value in left_map.values(): left_value.on_error(error) observer.on_error(error) md.disposable = duration.pipe(ops.take(1)).subscribe_(nothing, on_error, expire, scheduler) def on_error_left(error): for left_value in left_map.values(): left_value.on_error(error) observer.on_error(error) group.add(left.subscribe_(on_next_left, on_error_left, observer.on_completed, scheduler)) def send_right(value): with left.lock: _id = right_id[0] right_id[0] += 1 right_map[_id] = value md = SingleAssignmentDisposable() group.add(md) def expire(): del right_map[_id] group.remove(md) try: duration = right_duration_mapper(value) except Exception as e: for left_value in left_map.values(): left_value.on_error(e) observer.on_error(e) return def on_error(error): with left.lock: for left_value in left_map.values(): left_value.on_error(error) observer.on_error(error) md.disposable = duration.pipe(ops.take(1)).subscribe_(nothing, on_error, expire, scheduler) with left.lock: for left_value in left_map.values(): left_value.on_next(value) def on_error_right(error): for left_value in left_map.values(): left_value.on_error(error) observer.on_error(error) group.add(right.subscribe_(send_right, on_error_right, scheduler=scheduler)) return rcd return Observable(subscribe) return group_join RxPY-3.2.0/rx/core/operators/ignoreelements.py000066400000000000000000000012441404130727200213500ustar00rootroot00000000000000from typing import Callable from rx.core import Observable from rx.internal import noop def _ignore_elements() -> Callable[[Observable], Observable]: """Ignores all elements in an observable sequence leaving only the termination messages. Returns: An empty observable {Observable} sequence that signals termination, successful or exceptional, of the source sequence. """ def ignore_elements(source: Observable) -> Observable: def subscribe(observer, scheduler=None): return source.subscribe_(noop, observer.on_error, observer.on_completed, scheduler) return Observable(subscribe) return ignore_elementsRxPY-3.2.0/rx/core/operators/isempty.py000066400000000000000000000006551404130727200200270ustar00rootroot00000000000000from typing import Callable from rx import operators as ops from rx.core import Observable, pipe def _is_empty() -> Callable[[Observable], Observable]: """Determines whether an observable sequence is empty. Returns: An observable sequence containing a single element determining whether the source sequence is empty. """ return pipe( ops.some(), ops.map(lambda b: not b) ) RxPY-3.2.0/rx/core/operators/join.py000066400000000000000000000074571404130727200173030ustar00rootroot00000000000000from typing import Callable, Any from collections import OrderedDict from rx.operators import take from rx.core import Observable from rx.internal import noop from rx.disposable import SingleAssignmentDisposable, CompositeDisposable def _join(right: Observable, left_duration_mapper: Callable[[Any], Observable], right_duration_mapper: Callable[[Any], Observable], ) -> Callable[[Observable], Observable]: def join(source: Observable) -> Observable: """Correlates the elements of two sequences based on overlapping durations. Args: source: Source observable. Return: An observable sequence that contains elements combined into a tuple from source elements that have an overlapping duration. """ left = source def subscribe(observer, scheduler=None): group = CompositeDisposable() left_done = False left_map = OrderedDict() left_id = 0 right_done = False right_map = OrderedDict() right_id = 0 def on_next_left(value): nonlocal left_id duration = None current_id = left_id left_id += 1 md = SingleAssignmentDisposable() left_map[current_id] = value group.add(md) def expire(): if current_id in left_map: del left_map[current_id] if not len(left_map) and left_done: observer.on_completed() return group.remove(md) try: duration = left_duration_mapper(value) except Exception as exception: observer.on_error(exception) return md.disposable = duration.pipe(take(1)).subscribe_(noop, observer.on_error, lambda: expire(), scheduler) for val in right_map.values(): result = (value, val) observer.on_next(result) def on_completed_left(): nonlocal left_done left_done = True if right_done or not len(left_map): observer.on_completed() group.add(left.subscribe_(on_next_left, observer.on_error, on_completed_left, scheduler)) def on_next_right(value): nonlocal right_id duration = None current_id = right_id right_id += 1 md = SingleAssignmentDisposable() right_map[current_id] = value group.add(md) def expire(): if current_id in right_map: del right_map[current_id] if not len(right_map) and right_done: observer.on_completed() return group.remove(md) try: duration = right_duration_mapper(value) except Exception as exception: observer.on_error(exception) return md.disposable = duration.pipe(take(1)).subscribe_(noop, observer.on_error, lambda: expire(), scheduler) for val in left_map.values(): result = (val, value) observer.on_next(result) def on_completed_right(): nonlocal right_done right_done = True if left_done or not len(right_map): observer.on_completed() group.add(right.subscribe_(on_next_right, observer.on_error, on_completed_right, scheduler)) return group return Observable(subscribe) return join RxPY-3.2.0/rx/core/operators/last.py000066400000000000000000000020431404130727200172710ustar00rootroot00000000000000from typing import Callable, Optional from rx import operators from rx.core import Observable from rx.core.typing import Predicate from .lastordefault import last_or_default_async def _last(predicate: Optional[Predicate] = None) -> Callable[[Observable], Observable]: def last(source: Observable) -> Observable: """Partially applied last operator. Returns the last element of an observable sequence that satisfies the condition in the predicate if specified, else the last element. Examples: >>> res = last(source) Args: source: Source observable to get last item from. Returns: An observable sequence containing the last element in the observable sequence that satisfies the condition in the predicate. """ if predicate: return source.pipe( operators.filter(predicate), operators.last() ) return last_or_default_async(source, False) return last RxPY-3.2.0/rx/core/operators/lastordefault.py000066400000000000000000000033661404130727200212100ustar00rootroot00000000000000from typing import Callable, Any, Optional from rx.core import Observable from rx.core.typing import Predicate from rx.internal.exceptions import SequenceContainsNoElementsError from rx import operators as ops def last_or_default_async(source: Observable, has_default: bool = False, default_value: Any = None ) -> Observable: def subscribe(observer, scheduler=None): value = [default_value] seen_value = [False] def on_next(x): value[0] = x seen_value[0] = True def on_completed(): if not seen_value[0] and not has_default: observer.on_error(SequenceContainsNoElementsError()) else: observer.on_next(value[0]) observer.on_completed() return source.subscribe_(on_next, observer.on_error, on_completed, scheduler) return Observable(subscribe) def _last_or_default(predicate: Optional[Predicate] = None, default_value: Any = None ) -> Callable[[Observable], Observable]: def last_or_default(source: Observable) -> Observable: """Return last or default element. Examples: >>> res = _last_or_default(source) Args: source: Observable sequence to get the last item from. Returns: Observable sequence containing the last element in the observable sequence. """ if predicate: return source.pipe( ops.filter(predicate), ops.last_or_default(None, default_value), ) return last_or_default_async(source, True, default_value) return last_or_default RxPY-3.2.0/rx/core/operators/map.py000066400000000000000000000033761404130727200171150ustar00rootroot00000000000000from typing import Callable, Any, Optional from rx.internal.basic import identity from rx.internal.utils import infinite from rx import operators as ops from rx.core import Observable, pipe from rx.core.typing import Mapper, MapperIndexed, Observer, Disposable, Scheduler # pylint: disable=redefined-builtin def _map(mapper: Optional[Mapper] = None) -> Callable[[Observable], Observable]: _mapper = mapper or identity def map(source: Observable) -> Observable: """Partially applied map operator. Project each element of an observable sequence into a new form by incorporating the element's index. Example: >>> map(source) Args: source: The observable source to transform. Returns: Returns an observable sequence whose elements are the result of invoking the transform function on each element of the source. """ def subscribe(obv: Observer, scheduler: Scheduler = None) -> Disposable: def on_next(value: Any) -> None: try: result = _mapper(value) except Exception as err: # pylint: disable=broad-except obv.on_error(err) else: obv.on_next(result) return source.subscribe_(on_next, obv.on_error, obv.on_completed, scheduler) return Observable(subscribe) return map def _map_indexed(mapper_indexed: Optional[MapperIndexed] = None) -> Callable[[Observable], Observable]: def _identity(value: Any, _: int) -> Any: return value _mapper_indexed = mapper_indexed or _identity return pipe( ops.zip_with_iterable(infinite()), ops.starmap_indexed(_mapper_indexed) ) RxPY-3.2.0/rx/core/operators/materialize.py000066400000000000000000000021731404130727200206400ustar00rootroot00000000000000from typing import Callable from rx.core import Observable from rx.core.notification import OnNext, OnError, OnCompleted def _materialize() -> Callable[[Observable], Observable]: def materialize(source: Observable) -> Observable: """Partially applied materialize operator. Materializes the implicit notifications of an observable sequence as explicit notification values. Args: source: Source observable to materialize. Returns: An observable sequence containing the materialized notification values from the source sequence. """ def subscribe(observer, scheduler=None): def on_next(value): observer.on_next(OnNext(value)) def on_error(exception): observer.on_next(OnError(exception)) observer.on_completed() def on_completed(): observer.on_next(OnCompleted()) observer.on_completed() return source.subscribe_(on_next, on_error, on_completed, scheduler) return Observable(subscribe) return materialize RxPY-3.2.0/rx/core/operators/max.py000066400000000000000000000015451404130727200171210ustar00rootroot00000000000000from typing import Callable, Optional from rx import operators as ops from rx.core import Observable, pipe from rx.core.typing import Comparer from rx.internal.basic import identity from .min import first_only def _max(comparer: Optional[Comparer] = None) -> Callable[[Observable], Observable]: """Returns the maximum value in an observable sequence according to the specified comparer. Examples: >>> op = max() >>> op = max(lambda x, y: x.value - y.value) Args: comparer: [Optional] Comparer used to compare elements. Returns: An operator function that takes an observable source and returns an observable sequence containing a single element with the maximum element in the source sequence. """ return pipe( ops.max_by(identity, comparer), ops.map(first_only) ) RxPY-3.2.0/rx/core/operators/maxby.py000066400000000000000000000016231404130727200174510ustar00rootroot00000000000000from typing import Callable, Optional from rx.core import Observable from rx.core.typing import Mapper, SubComparer from rx.internal.basic import default_sub_comparer from .minby import extrema_by def _max_by(key_mapper: Mapper, comparer: Optional[SubComparer] = None ) -> Callable[[Observable], Observable]: cmp = comparer or default_sub_comparer def max_by(source: Observable) -> Observable: """Partially applied max_by operator. Returns the elements in an observable sequence with the maximum key value. Examples: >>> res = max_by(source) Args: source: The source observable sequence to. Returns: An observable sequence containing a list of zero or more elements that have a maximum key value. """ return extrema_by(source, key_mapper, cmp) return max_by RxPY-3.2.0/rx/core/operators/merge.py000066400000000000000000000104611404130727200174300ustar00rootroot00000000000000from typing import Callable, Optional import rx from rx import from_future from rx.core import Observable from rx.disposable import CompositeDisposable, SingleAssignmentDisposable from rx.internal.concurrency import synchronized from rx.internal.utils import is_future def _merge(*sources: Observable, max_concurrent: Optional[int] = None ) -> Callable[[Observable], Observable]: def merge(source: Observable) -> Observable: """Merges an observable sequence of observable sequences into an observable sequence, limiting the number of concurrent subscriptions to inner sequences. Or merges two observable sequences into a single observable sequence. Examples: >>> res = merge(sources) Args: source: Source observable. Returns: The observable sequence that merges the elements of the inner sequences. """ if max_concurrent is None: sources_ = tuple([source]) + sources return rx.merge(*sources_) def subscribe(observer, scheduler=None): active_count = [0] group = CompositeDisposable() is_stopped = [False] queue = [] def subscribe(xs): subscription = SingleAssignmentDisposable() group.add(subscription) @synchronized(source.lock) def on_completed(): group.remove(subscription) if queue: s = queue.pop(0) subscribe(s) else: active_count[0] -= 1 if is_stopped[0] and active_count[0] == 0: observer.on_completed() on_next = synchronized(source.lock)(observer.on_next) on_error = synchronized(source.lock)(observer.on_error) subscription.disposable = xs.subscribe_(on_next, on_error, on_completed, scheduler) def on_next(inner_source): if active_count[0] < max_concurrent: active_count[0] += 1 subscribe(inner_source) else: queue.append(inner_source) def on_completed(): is_stopped[0] = True if active_count[0] == 0: observer.on_completed() group.add(source.subscribe_(on_next, observer.on_error, on_completed, scheduler)) return group return Observable(subscribe) return merge def _merge_all() -> Callable[[Observable], Observable]: def merge_all(source: Observable) -> Observable: """Partially applied merge_all operator. Merges an observable sequence of observable sequences into an observable sequence. Args: source: Source observable to merge. Returns: The observable sequence that merges the elements of the inner sequences. """ def subscribe(observer, scheduler=None): group = CompositeDisposable() is_stopped = [False] m = SingleAssignmentDisposable() group.add(m) def on_next(inner_source): inner_subscription = SingleAssignmentDisposable() group.add(inner_subscription) inner_source = from_future(inner_source) if is_future(inner_source) else inner_source @synchronized(source.lock) def on_completed(): group.remove(inner_subscription) if is_stopped[0] and len(group) == 1: observer.on_completed() on_next = synchronized(source.lock)(observer.on_next) on_error = synchronized(source.lock)(observer.on_error) subscription = inner_source.subscribe_(on_next, on_error, on_completed, scheduler) inner_subscription.disposable = subscription def on_completed(): is_stopped[0] = True if len(group) == 1: observer.on_completed() m.disposable = source.subscribe_(on_next, observer.on_error, on_completed, scheduler) return group return Observable(subscribe) return merge_all RxPY-3.2.0/rx/core/operators/min.py000066400000000000000000000020331404130727200171100ustar00rootroot00000000000000from typing import Callable, Optional, Sequence, Any from rx import operators as ops from rx.core import Observable, pipe from rx.core.typing import Comparer from rx.internal.basic import identity from rx.internal.exceptions import SequenceContainsNoElementsError def first_only(x: Sequence) -> Any: if not x: raise SequenceContainsNoElementsError() return x[0] def _min(comparer: Optional[Comparer] = None) -> Callable[[Observable], Observable]: """The `min` operator. Returns the minimum element in an observable sequence according to the optional comparer else a default greater than less than check. Examples: >>> res = source.min() >>> res = source.min(lambda x, y: x.value - y.value) Args: comparer: [Optional] Comparer used to compare elements. Returns: An observable sequence containing a single element with the minimum element in the source sequence. """ return pipe( ops.min_by(identity, comparer), ops.map(first_only) ) RxPY-3.2.0/rx/core/operators/minby.py000066400000000000000000000042151404130727200174470ustar00rootroot00000000000000from typing import Callable, Optional from rx.core import Observable from rx.core.typing import Mapper, SubComparer from rx.internal.basic import default_sub_comparer def extrema_by(source: Observable, key_mapper: Mapper, comparer: SubComparer ) -> Observable: def subscribe(observer, scheduler=None): has_value = [False] last_key = [None] items = [] def on_next(x): try: key = key_mapper(x) except Exception as ex: observer.on_error(ex) return comparison = 0 if not has_value[0]: has_value[0] = True last_key[0] = key else: try: comparison = comparer(key, last_key[0]) except Exception as ex1: observer.on_error(ex1) return if comparison > 0: last_key[0] = key items[:] = [] if comparison >= 0: items.append(x) def on_completed(): observer.on_next(items) observer.on_completed() return source.subscribe_(on_next, observer.on_error, on_completed, scheduler) return Observable(subscribe) def _min_by(key_mapper: Mapper, comparer: Optional[SubComparer] = None ) -> Callable[[Observable], Observable]: """The `min_by` operator. Returns the elements in an observable sequence with the minimum key value according to the specified comparer. Examples: >>> res = min_by(lambda x: x.value) >>> res = min_by(lambda x: x.value, lambda x, y: x - y) Args: key_mapper: Key mapper function. comparer: [Optional] Comparer used to compare key values. Returns: An observable sequence containing a list of zero or more elements that have a minimum key value. """ cmp: SubComparer = comparer or default_sub_comparer def min_by(source: Observable) -> Observable: return extrema_by(source, key_mapper, lambda x, y: - cmp(x, y)) return min_by RxPY-3.2.0/rx/core/operators/multicast.py000066400000000000000000000043611404130727200203400ustar00rootroot00000000000000from typing import Union, Callable, Optional from rx.core import Observable, ConnectableObservable from rx.core.typing import Subject, Mapper, Scheduler from rx.disposable import CompositeDisposable def _multicast(subject: Optional[Subject] = None, subject_factory: Optional[Callable[[Optional[Scheduler]], Subject]] = None, mapper: Optional[Callable[[ConnectableObservable], Observable]] = None ) -> Callable[[Observable], Union[Observable, ConnectableObservable]]: """Multicasts the source sequence notifications through an instantiated subject into all uses of the sequence within a mapper function. Each subscription to the resulting sequence causes a separate multicast invocation, exposing the sequence resulting from the mapper function's invocation. For specializations with fixed subject types, see Publish, PublishLast, and Replay. Examples: >>> res = multicast(observable) >>> res = multicast(subject_factory=lambda scheduler: Subject(), mapper=lambda x: x) Args: subject_factory: Factory function to create an intermediate subject through which the source sequence's elements will be multicast to the mapper function. subject: Subject to push source elements into. mapper: [Optional] Mapper function which can use the multicasted source sequence subject to the policies enforced by the created subject. Specified only if subject_factory" is a factory function. Returns: An observable sequence that contains the elements of a sequence produced by multicasting the source sequence within a mapper function. """ def multicast(source: Observable) -> Union[Observable, ConnectableObservable]: if subject_factory: def subscribe(observer, scheduler=None): connectable = source.pipe(_multicast(subject=subject_factory(scheduler))) subscription = mapper(connectable).subscribe(observer, scheduler=scheduler) return CompositeDisposable(subscription, connectable.connect(scheduler)) return Observable(subscribe) return ConnectableObservable(source, subject) return multicast RxPY-3.2.0/rx/core/operators/observeon.py000066400000000000000000000020421404130727200203270ustar00rootroot00000000000000from typing import Callable from rx.core import Observable from rx.core.typing import Scheduler from rx.core.observer import ObserveOnObserver def _observe_on(scheduler: Scheduler) -> Callable[[Observable], Observable]: def observe_on(source: Observable) -> Observable: """Wraps the source sequence in order to run its observer callbacks on the specified scheduler. This only invokes observer callbacks on a scheduler. In case the subscription and/or unsubscription actions have side-effects that require to be run on a scheduler, use subscribe_on. Args: source: Source observable. Returns: Returns the source sequence whose observations happen on the specified scheduler. """ def subscribe(observer, subscribe_scheduler=None): return source.subscribe(ObserveOnObserver(scheduler, observer), scheduler=subscribe_scheduler) return Observable(subscribe) return observe_on RxPY-3.2.0/rx/core/operators/onerrorresumenext.py000066400000000000000000000004641404130727200221410ustar00rootroot00000000000000from typing import Callable import rx from rx.core import Observable def _on_error_resume_next(second: Observable) -> Callable[[Observable], Observable]: def on_error_resume_next(source: Observable) -> Observable: return rx.on_error_resume_next(source, second) return on_error_resume_next RxPY-3.2.0/rx/core/operators/pairwise.py000066400000000000000000000025271404130727200201600ustar00rootroot00000000000000from typing import Callable from rx.core import Observable def _pairwise() -> Callable[[Observable], Observable]: def pairwise(source: Observable) -> Observable: """Partially applied pairwise operator. Returns a new observable that triggers on the second and subsequent triggerings of the input observable. The Nth triggering of the input observable passes the arguments from the N-1th and Nth triggering as a pair. The argument passed to the N-1th triggering is held in hidden internal state until the Nth triggering occurs. Returns: An observable that triggers on successive pairs of observations from the input observable as an array. """ def subscribe(observer, scheduler=None): has_previous = [False] previous = [None] def on_next(x): pair = None with source.lock: if has_previous[0]: pair = (previous[0], x) else: has_previous[0] = True previous[0] = x if pair: observer.on_next(pair) return source.subscribe_(on_next, observer.on_error, observer.on_completed) return Observable(subscribe) return pairwise RxPY-3.2.0/rx/core/operators/partition.py000066400000000000000000000052611404130727200203440ustar00rootroot00000000000000from typing import List, Callable from rx import operators as ops from rx.core import Observable from rx.core.typing import Predicate, PredicateIndexed def _partition(predicate: Predicate) -> Callable[[Observable], List[Observable]]: def partition(source: Observable) -> List[Observable]: """The partially applied `partition` operator. Returns two observables which partition the observations of the source by the given function. The first will trigger observations for those values for which the predicate returns true. The second will trigger observations for those values where the predicate returns false. The predicate is executed once for each subscribed observer. Both also propagate all error observations arising from the source and each completes when the source completes. Args: source: Source obserable to partition. Returns: A list of observables. The first triggers when the predicate returns True, and the second triggers when the predicate returns False. """ published = source.pipe( ops.publish(), ops.ref_count() ) return [ published.pipe(ops.filter(predicate)), published.pipe(ops.filter(lambda x: not predicate(x))) ] return partition def _partition_indexed(predicate_indexed: PredicateIndexed) -> Callable[[Observable], List[Observable]]: def partition_indexed(source: Observable) -> List[Observable]: """The partially applied indexed partition operator. Returns two observables which partition the observations of the source by the given function. The first will trigger observations for those values for which the predicate returns true. The second will trigger observations for those values where the predicate returns false. The predicate is executed once for each subscribed observer. Both also propagate all error observations arising from the source and each completes when the source completes. Args: source: Source observable to partition. Returns: A list of observables. The first triggers when the predicate returns True, and the second triggers when the predicate returns False. """ published = source.pipe( ops.publish(), ops.ref_count() ) return [ published.pipe(ops.filter_indexed(predicate_indexed)), published.pipe(ops.filter_indexed(predicate_indexed=lambda x, i: not predicate_indexed(x, i))) ] return partition_indexed RxPY-3.2.0/rx/core/operators/pluck.py000066400000000000000000000017201404130727200174450ustar00rootroot00000000000000from typing import Any, Callable from rx import operators as ops from rx.core import Observable def _pluck(key: Any) -> Callable[[Observable], Observable]: """Retrieves the value of a specified key using dict-like access (as in element[key]) from all elements in the Observable sequence. Args: key: The key to pluck. Returns a new Observable {Observable} sequence of key values. To pluck an attribute of each element, use pluck_attr. """ return ops.map(lambda x: x[key]) def _pluck_attr(prop: str) -> Callable[[Observable], Observable]: """Retrieves the value of a specified property (using getattr) from all elements in the Observable sequence. Args: property: The property to pluck. Returns a new Observable {Observable} sequence of property values. To pluck values using dict-like access (as in element[key]) on each element, use pluck. """ return ops.map(lambda x: getattr(x, prop)) RxPY-3.2.0/rx/core/operators/publish.py000066400000000000000000000034201404130727200177740ustar00rootroot00000000000000from typing import Callable, Optional from rx import operators as ops from rx.core import Observable, ConnectableObservable, pipe from rx.core.typing import Mapper from rx.subject import Subject def _publish(mapper: Optional[Mapper] = None) -> Callable[[Observable], ConnectableObservable]: """Returns an observable sequence that is the result of invoking the mapper on a connectable observable sequence that shares a single subscription to the underlying sequence. This operator is a specialization of Multicast using a regular Subject. Example: >>> res = publish() >>> res = publish(lambda x: x) mapper: [Optional] Selector function which can use the multicasted source sequence as many times as needed, without causing multiple subscriptions to the source sequence. Subscribers to the given source will receive all notifications of the source from the time of the subscription on. Returns: An observable sequence that contains the elements of a sequence produced by multicasting the source sequence within a mapper function. """ if mapper: return pipe(ops.multicast(subject_factory=lambda _: Subject(), mapper=mapper)) return pipe(ops.multicast(subject=Subject())) def _share() -> Callable[[Observable], Observable]: """Share a single subscription among multple observers. Returns a new Observable that multicasts (shares) the original Observable. As long as there is at least one Subscriber this Observable will be subscribed and emitting data. When all subscribers have unsubscribed it will unsubscribe from the source Observable. This is an alias for a composed publish() and ref_count(). """ return pipe(_publish(), ops.ref_count()) RxPY-3.2.0/rx/core/operators/publishvalue.py000066400000000000000000000010251404130727200210300ustar00rootroot00000000000000from typing import Any, Callable, Optional from rx import operators as ops from rx.core import Observable from rx.subject import BehaviorSubject from rx.core.typing import Mapper def _publish_value(initial_value: Any, mapper: Optional[Mapper] = None) -> Callable[[Observable], Observable]: if mapper: def subject_factory(scheduler): return BehaviorSubject(initial_value) return ops.multicast(subject_factory=subject_factory, mapper=mapper) return ops.multicast(BehaviorSubject(initial_value)) RxPY-3.2.0/rx/core/operators/reduce.py000066400000000000000000000023511404130727200175770ustar00rootroot00000000000000from typing import Any, Callable from rx import operators as ops from rx.internal.utils import NotSet from rx.core import Observable, pipe from rx.core.typing import Accumulator def _reduce(accumulator: Accumulator, seed: Any = NotSet) -> Callable[[Observable], Observable]: """Applies an accumulator function over an observable sequence, returning the result of the aggregation as a single element in the result sequence. The specified seed value is used as the initial accumulator value. For aggregation behavior with incremental intermediate results, see `scan()`. Examples: >>> res = reduce(lambda acc, x: acc + x) >>> res = reduce(lambda acc, x: acc + x, 0) Args: accumulator: An accumulator function to be invoked on each element. seed: Optional initial accumulator value. Returns: An operator function that takes an observable source and returns an observable sequence containing a single element with the final accumulator value. """ if seed is not NotSet: scanner = ops.scan(accumulator, seed=seed) return pipe(scanner, ops.last_or_default(default_value=seed)) return pipe(ops.scan(accumulator), ops.last()) RxPY-3.2.0/rx/core/operators/repeat.py000066400000000000000000000017741404130727200176200ustar00rootroot00000000000000import sys from typing import Callable, Optional import rx from rx.core import Observable from rx.internal.utils import infinite def _repeat(repeat_count: Optional[int] = None) -> Callable[[Observable], Observable]: if repeat_count is None: repeat_count = sys.maxsize def repeat(source: Observable) -> Observable: """Repeats the observable sequence a specified number of times. If the repeat count is not specified, the sequence repeats indefinitely. Examples: >>> repeated = source.repeat() >>> repeated = source.repeat(42) Args: source: The observable source to repeat. Returns: The observable sequence producing the elements of the given sequence repeatedly. """ if repeat_count is None: gen = infinite() else: gen = range(repeat_count) return rx.defer(lambda _: rx.concat_with_iterable(source for _ in gen)) return repeat RxPY-3.2.0/rx/core/operators/replay.py000066400000000000000000000040771404130727200176330ustar00rootroot00000000000000from typing import Union, Callable, Optional from rx import operators as ops from rx.core import Observable, ConnectableObservable, typing from rx.core.typing import Scheduler, Mapper from rx.subject import ReplaySubject def _replay(mapper: Optional[Mapper] = None, buffer_size: Optional[int] = None, window: Optional[typing.RelativeTime] = None, scheduler: Optional[Scheduler] = None ) -> Callable[[Observable], Union[Observable, ConnectableObservable]]: """Returns an observable sequence that is the result of invoking the mapper on a connectable observable sequence that shares a single subscription to the underlying sequence replaying notifications subject to a maximum time length for the replay buffer. This operator is a specialization of Multicast using a ReplaySubject. Examples: >>> res = replay(buffer_size=3) >>> res = replay(buffer_size=3, window=500) >>> res = replay(None, 3, 500) >>> res = replay(lambda x: x.take(6).repeat(), 3, 500) Args: mapper: [Optional] Selector function which can use the multicasted source sequence as many times as needed, without causing multiple subscriptions to the source sequence. Subscribers to the given source will receive all the notifications of the source subject to the specified replay buffer trimming policy. buffer_size: [Optional] Maximum element count of the replay buffer. window: [Optional] Maximum time length of the replay buffer. scheduler: [Optional] Scheduler the observers are invoked on. Returns: An observable sequence that contains the elements of a sequence produced by multicasting the source sequence within a mapper function. """ if mapper: def subject_factory(scheduler): return ReplaySubject(buffer_size, window, scheduler) return ops.multicast(subject_factory=subject_factory, mapper=mapper) return ops.multicast(ReplaySubject(buffer_size, window, scheduler)) RxPY-3.2.0/rx/core/operators/retry.py000066400000000000000000000017301404130727200174750ustar00rootroot00000000000000from typing import Callable, Optional import rx from rx.core import Observable from rx.internal.utils import infinite def _retry(retry_count: Optional[int] = None) -> Callable[[Observable], Observable]: """Repeats the source observable sequence the specified number of times or until it successfully terminates. If the retry count is not specified, it retries indefinitely. Examples: >>> retried = retry() >>> retried = retry(42) Args: retry_count: [Optional] Number of times to retry the sequence. If not provided, retry the sequence indefinitely. Returns: An observable sequence producing the elements of the given sequence repeatedly until it terminates successfully. """ if retry_count is None: gen = infinite() else: gen = range(retry_count) def retry(source: Observable) -> Observable: return rx.catch_with_iterable(source for _ in gen) return retry RxPY-3.2.0/rx/core/operators/sample.py000066400000000000000000000032111404130727200176050ustar00rootroot00000000000000from typing import Callable, Optional, Union import rx from rx.core import Observable, typing from rx.disposable import CompositeDisposable def sample_observable(source: Observable, sampler: Observable) -> Observable: def subscribe(observer, scheduler=None): at_end = [None] has_value = [None] value = [None] def sample_subscribe(x=None): if has_value[0]: has_value[0] = False observer.on_next(value[0]) if at_end[0]: observer.on_completed() def on_next(new_value): has_value[0] = True value[0] = new_value def on_completed(): at_end[0] = True return CompositeDisposable( source.subscribe_(on_next, observer.on_error, on_completed, scheduler), sampler.subscribe_(sample_subscribe, observer.on_error, sample_subscribe, scheduler) ) return Observable(subscribe) def _sample(sampler: Union[typing.RelativeTime, Observable], scheduler: Optional[typing.Scheduler] = None ) -> Callable[[Observable], Observable]: def sample(source: Observable) -> Observable: """Samples the observable sequence at each interval. Examples: >>> res = sample(source) Args: source: Source sequence to sample. Returns: Sampled observable sequence. """ if isinstance(sampler, typing.Observable): return sample_observable(source, sampler) else: return sample_observable(source, rx.interval(sampler, scheduler=scheduler)) return sample RxPY-3.2.0/rx/core/operators/scan.py000066400000000000000000000024661404130727200172630ustar00rootroot00000000000000from typing import Any, Callable from rx import defer, operators as ops from rx.internal.utils import NotSet from rx.core import Observable from rx.core.typing import Accumulator def _scan(accumulator: Accumulator, seed: Any = NotSet) -> Callable[[Observable], Observable]: has_seed = seed is not NotSet def scan(source: Observable) -> Observable: """Partially applied scan operator. Applies an accumulator function over an observable sequence and returns each intermediate result. Examples: >>> scanned = scan(source) Args: source: The observable source to scan. Returns: An observable sequence containing the accumulated values. """ def factory(scheduler): has_accumulation = False accumulation = None def projection(x): nonlocal has_accumulation nonlocal accumulation if has_accumulation: accumulation = accumulator(accumulation, x) else: accumulation = accumulator(seed, x) if has_seed else x has_accumulation = True return accumulation return source.pipe(ops.map(projection)) return defer(factory) return scan RxPY-3.2.0/rx/core/operators/sequenceequal.py000066400000000000000000000072501404130727200211730ustar00rootroot00000000000000from typing import Any, Callable, Optional import collections import rx from rx.core import Observable from rx.core.typing import Comparer from rx.disposable import CompositeDisposable from rx.internal import default_comparer def _sequence_equal(second: Observable, comparer: Optional[Comparer] = None ) -> Callable[[Observable], Observable]: comparer = comparer or default_comparer if isinstance(second, collections.abc.Iterable): second = rx.from_iterable(second) def sequence_equal(source: Observable) -> Observable: """Determines whether two sequences are equal by comparing the elements pairwise using a specified equality comparer. Examples: >>> res = sequence_equal([1,2,3]) >>> res = sequence_equal([{ "value": 42 }], lambda x, y: x.value == y.value) >>> res = sequence_equal(rx.return_value(42)) >>> res = sequence_equal(rx.return_value({ "value": 42 }), lambda x, y: x.value == y.value) Args: source: Source obserable to compare. Returns: An observable sequence that contains a single element which indicates whether both sequences are of equal length and their corresponding elements are equal according to the specified equality comparer. """ first = source def subscribe(observer, scheduler=None): donel = [False] doner = [False] ql = [] qr = [] def on_next1(x): if len(qr) > 0: v = qr.pop(0) try: equal = comparer(v, x) except Exception as e: observer.on_error(e) return if not equal: observer.on_next(False) observer.on_completed() elif doner[0]: observer.on_next(False) observer.on_completed() else: ql.append(x) def on_completed1(): donel[0] = True if not ql: if qr: observer.on_next(False) observer.on_completed() elif doner[0]: observer.on_next(True) observer.on_completed() def on_next2(x): if len(ql) > 0: v = ql.pop(0) try: equal = comparer(v, x) except Exception as exception: observer.on_error(exception) return if not equal: observer.on_next(False) observer.on_completed() elif donel[0]: observer.on_next(False) observer.on_completed() else: qr.append(x) def on_completed2(): doner[0] = True if not qr: if len(ql) > 0: observer.on_next(False) observer.on_completed() elif donel[0]: observer.on_next(True) observer.on_completed() subscription1 = first.subscribe_(on_next1, observer.on_error, on_completed1, scheduler) subscription2 = second.subscribe_(on_next2, observer.on_error, on_completed2, scheduler) return CompositeDisposable(subscription1, subscription2) return Observable(subscribe) return sequence_equal RxPY-3.2.0/rx/core/operators/single.py000066400000000000000000000016771404130727200176230ustar00rootroot00000000000000from typing import Callable, Optional from rx import operators as ops from rx.core import Observable, pipe from rx.core.typing import Predicate def _single(predicate: Optional[Predicate] = None) -> Callable[[Observable], Observable]: """Returns the only element of an observable sequence that satisfies the condition in the optional predicate, and reports an exception if there is not exactly one element in the observable sequence. Example: >>> res = single() >>> res = single(lambda x: x == 42) Args: predicate -- [Optional] A predicate function to evaluate for elements in the source sequence. Returns: An observable sequence containing the single element in the observable sequence that satisfies the condition in the predicate. """ if predicate: return pipe(ops.filter(predicate), ops.single()) else: return ops.single_or_default_async(False) RxPY-3.2.0/rx/core/operators/singleordefault.py000066400000000000000000000046021404130727200215200ustar00rootroot00000000000000from typing import Optional, Callable from rx import operators as ops from rx.core import Observable, pipe from rx.core.typing import Predicate, Any from rx.internal.exceptions import SequenceContainsNoElementsError def _single_or_default_async(has_default: bool = False, default_value: Any = None) -> Callable[[Observable], Observable]: def single_or_default_async(source: Observable) -> Observable: def subscribe(observer, scheduler=None): value = [default_value] seen_value = [False] def on_next(x): if seen_value[0]: observer.on_error(Exception('Sequence contains more than one element')) else: value[0] = x seen_value[0] = True def on_completed(): if not seen_value[0] and not has_default: observer.on_error(SequenceContainsNoElementsError()) else: observer.on_next(value[0]) observer.on_completed() return source.subscribe_(on_next, observer.on_error, on_completed, scheduler) return Observable(subscribe) return single_or_default_async def _single_or_default(predicate: Optional[Predicate] = None, default_value: Any = None) -> Callable[[Observable], Observable]: """Returns the only element of an observable sequence that matches the predicate, or a default value if no such element exists this method reports an exception if there is more than one element in the observable sequence. Examples: >>> res = single_or_default() >>> res = single_or_default(lambda x: x == 42) >>> res = single_or_default(lambda x: x == 42, 0) >>> res = single_or_default(None, 0) Args: predicate -- [Optional] A predicate function to evaluate for elements in the source sequence. default_value -- [Optional] The default value if the index is outside the bounds of the source sequence. Returns: An observable Sequence containing the single element in the observable sequence that satisfies the condition in the predicate, or a default value if no such element exists. """ if predicate: return pipe(ops.filter(predicate), ops.single_or_default(None, default_value)) else: return _single_or_default_async(True, default_value) RxPY-3.2.0/rx/core/operators/skip.py000066400000000000000000000021141404130727200172730ustar00rootroot00000000000000from typing import Callable from rx.core import Observable from rx.internal import ArgumentOutOfRangeException def _skip(count: int) -> Callable[[Observable], Observable]: if count < 0: raise ArgumentOutOfRangeException() def skip(source: Observable) -> Observable: """The skip operator. Bypasses a specified number of elements in an observable sequence and then returns the remaining elements. Args: source: The source observable. Returns: An observable sequence that contains the elements that occur after the specified index in the input sequence. """ def subscribe(observer, scheduler=None): remaining = count def on_next(value): nonlocal remaining if remaining <= 0: observer.on_next(value) else: remaining -= 1 return source.subscribe_(on_next, observer.on_error, observer.on_completed, scheduler) return Observable(subscribe) return skip RxPY-3.2.0/rx/core/operators/skiplast.py000066400000000000000000000024771404130727200201730ustar00rootroot00000000000000from typing import Callable from rx.core import Observable def _skip_last(count: int) -> Callable[[Observable], Observable]: def skip_last(source: Observable) -> Observable: """Bypasses a specified number of elements at the end of an observable sequence. This operator accumulates a queue with a length enough to store the first `count` elements. As more elements are received, elements are taken from the front of the queue and produced on the result sequence. This causes elements to be delayed. Args: count: Number of elements to bypass at the end of the source sequence. Returns: An observable sequence containing the source sequence elements except for the bypassed ones at the end. """ def subscribe(observer, scheduler=None): q = [] def on_next(value): front = None with source.lock: q.append(value) if len(q) > count: front = q.pop(0) if front is not None: observer.on_next(front) return source.subscribe_(on_next, observer.on_error, observer.on_completed, scheduler) return Observable(subscribe) return skip_last RxPY-3.2.0/rx/core/operators/skiplastwithtime.py000066400000000000000000000037351404130727200217440ustar00rootroot00000000000000from typing import Callable, Optional from rx.core import Observable, typing from rx.scheduler import TimeoutScheduler def _skip_last_with_time(duration: typing.RelativeTime, scheduler: Optional[typing.Scheduler] = None ) -> Callable[[Observable], Observable]: """Skips elements for the specified duration from the end of the observable source sequence. Example: >>> res = skip_last_with_time(5.0) This operator accumulates a queue with a length enough to store elements received during the initial duration window. As more elements are received, elements older than the specified duration are taken from the queue and produced on the result sequence. This causes elements to be delayed with duration. Args: duration: Duration for skipping elements from the end of the sequence. scheduler: Scheduler to use for time handling. Returns: An observable sequence with the elements skipped during the specified duration from the end of the source sequence. """ def skip_last_with_time(source: Observable) -> Observable: def subscribe(observer, scheduler_=None): nonlocal duration _scheduler = scheduler or scheduler_ or TimeoutScheduler.singleton() duration = _scheduler.to_timedelta(duration) q = [] def on_next(x): now = _scheduler.now q.append({"interval": now, "value": x}) while q and now - q[0]["interval"] >= duration: observer.on_next(q.pop(0)["value"]) def on_completed(): now = _scheduler.now while q and now - q[0]["interval"] >= duration: observer.on_next(q.pop(0)["value"]) observer.on_completed() return source.subscribe_(on_next, observer.on_error, on_completed, scheduler_) return Observable(subscribe) return skip_last_with_time RxPY-3.2.0/rx/core/operators/skipuntil.py000066400000000000000000000035261404130727200203570ustar00rootroot00000000000000from asyncio import Future from typing import cast, Callable, Union from rx import from_future from rx.core import Observable from rx.disposable import CompositeDisposable, SingleAssignmentDisposable from rx.internal.utils import is_future def _skip_until(other: Union[Observable, Future]) -> Callable[[Observable], Observable]: """Returns the values from the source observable sequence only after the other observable sequence produces a value. Args: other: The observable sequence that triggers propagation of elements of the source sequence. Returns: An observable sequence containing the elements of the source sequence starting from the point the other sequence triggered propagation. """ if is_future(other): obs = from_future(cast(Future, other)) else: obs = cast(Observable, other) def skip_until(source: Observable) -> Observable: def subscribe(observer, scheduler=None): is_open = [False] def on_next(left): if is_open[0]: observer.on_next(left) def on_completed(): if is_open[0]: observer.on_completed() subs = source.subscribe_(on_next, observer.on_error, on_completed, scheduler) subscriptions = CompositeDisposable(subs) right_subscription = SingleAssignmentDisposable() subscriptions.add(right_subscription) def on_next2(x): is_open[0] = True right_subscription.dispose() def on_completed2(): right_subscription.dispose() right_subscription.disposable = obs.subscribe_(on_next2, observer.on_error, on_completed2, scheduler) return subscriptions return Observable(subscribe) return skip_until RxPY-3.2.0/rx/core/operators/skipuntilwithtime.py000066400000000000000000000037071404130727200221330ustar00rootroot00000000000000from datetime import datetime from typing import Callable, Optional from rx.core import Observable, typing from rx.disposable import CompositeDisposable from rx.scheduler import TimeoutScheduler def _skip_until_with_time(start_time: typing.AbsoluteOrRelativeTime, scheduler: Optional[typing.Scheduler] = None ) -> Callable[[Observable], Observable]: def skip_until_with_time(source: Observable) -> Observable: """Skips elements from the observable source sequence until the specified start time. Errors produced by the source sequence are always forwarded to the result sequence, even if the error occurs before the start time. Examples: >>> res = source.skip_until_with_time(datetime) >>> res = source.skip_until_with_time(5.0) Args: start_time: Time to start taking elements from the source sequence. If this value is less than or equal to `datetime.utcnow`, no elements will be skipped. Returns: An observable sequence with the elements skipped until the specified start time. """ if isinstance(start_time, datetime): scheduler_method = 'schedule_absolute' else: scheduler_method = 'schedule_relative' def subscribe(observer, scheduler_=None): _scheduler = scheduler or scheduler_ or TimeoutScheduler.singleton() open = [False] def on_next(x): if open[0]: observer.on_next(x) subscription = source.subscribe_(on_next, observer.on_error, observer.on_completed, scheduler_) def action(scheduler, state): open[0] = True disp = getattr(_scheduler, scheduler_method)(start_time, action) return CompositeDisposable(disp, subscription) return Observable(subscribe) return skip_until_with_time RxPY-3.2.0/rx/core/operators/skipwhile.py000066400000000000000000000032611404130727200203300ustar00rootroot00000000000000from typing import Callable from rx import operators as ops from rx.core import Observable, typing, pipe def _skip_while(predicate: typing.Predicate) -> Callable[[Observable], Observable]: def skip_while(source: Observable) -> Observable: """Bypasses elements in an observable sequence as long as a specified condition is true and then returns the remaining elements. The element's index is used in the logic of the predicate function. Example: >>> skip_while(source) Args: source: The source observable to skip elements from. Returns: An observable sequence that contains the elements from the input sequence starting at the first element in the linear series that does not pass the test specified by predicate. """ def subscribe(observer, scheduler=None): running = False def on_next(value): nonlocal running if not running: try: running = not predicate(value) except Exception as exn: observer.on_error(exn) return if running: observer.on_next(value) return source.subscribe_(on_next, observer.on_error, observer.on_completed, scheduler) return Observable(subscribe) return skip_while def _skip_while_indexed(predicate: typing.PredicateIndexed) -> Callable[[Observable], Observable]: return pipe( ops.map_indexed(lambda x, i: (x, i)), ops.skip_while(lambda x: predicate(*x)), ops.map(lambda x: x[0]) ) RxPY-3.2.0/rx/core/operators/skipwithtime.py000066400000000000000000000036561404130727200210620ustar00rootroot00000000000000from typing import Callable, Optional from rx.core import Observable, typing from rx.disposable import CompositeDisposable from rx.scheduler import TimeoutScheduler def _skip_with_time(duration: typing.RelativeTime, scheduler: Optional[typing.Scheduler] = None ) -> Callable[[Observable], Observable]: def skip_with_time(source: Observable) -> Observable: """Skips elements for the specified duration from the start of the observable source sequence. Args: >>> res = skip_with_time(5.0) Specifying a zero value for duration doesn't guarantee no elements will be dropped from the start of the source sequence. This is a side-effect of the asynchrony introduced by the scheduler, where the action that causes callbacks from the source sequence to be forwarded may not execute immediately, despite the zero due time. Errors produced by the source sequence are always forwarded to the result sequence, even if the error occurs before the duration. Args: duration: Duration for skipping elements from the start of the sequence. Returns: An observable sequence with the elements skipped during the specified duration from the start of the source sequence. """ def subscribe(observer, scheduler_=None): _scheduler = scheduler or scheduler_ or TimeoutScheduler.singleton() open = [False] def action(scheduler, state): open[0] = True t = _scheduler.schedule_relative(duration, action) def on_next(x): if open[0]: observer.on_next(x) d = source.subscribe_(on_next, observer.on_error, observer.on_completed, scheduler_) return CompositeDisposable(t, d) return Observable(subscribe) return skip_with_time RxPY-3.2.0/rx/core/operators/slice.py000066400000000000000000000041631404130727200174320ustar00rootroot00000000000000from sys import maxsize from typing import Callable, Optional from rx import operators as ops from rx.core import Observable # pylint: disable=redefined-builtin def _slice(start: Optional[int] = None, stop: Optional[int] = None, step: Optional[int] = None ) -> Callable[[Observable], Observable]: _start: int = 0 if start is None else start _stop: int = maxsize if stop is None else stop _step: int = 1 if step is None else step pipeline = [] def slice(source: Observable) -> Observable: """The partially applied slice operator. Slices the given observable. It is basically a wrapper around the operators :func:`skip `, :func:`skip_last `, :func:`take `, :func:`take_last ` and :func:`filter `. The following diagram helps you remember how slices works with streams. Positive numbers are relative to the start of the events, while negative numbers are relative to the end (close) of the stream. .. code:: r---e---a---c---t---i---v---e---! 0 1 2 3 4 5 6 7 8 -8 -7 -6 -5 -4 -3 -2 -1 0 Examples: >>> result = source.slice(1, 10) >>> result = source.slice(1, -2) >>> result = source.slice(1, -1, 2) Args: source: Observable to slice Returns: A sliced observable sequence. """ if _stop >= 0: pipeline.append(ops.take(_stop)) if _start > 0: pipeline.append(ops.skip(_start)) elif _start < 0: pipeline.append(ops.take_last(-_start)) if _stop < 0: pipeline.append(ops.skip_last(-_stop)) if _step > 1: pipeline.append(ops.filter_indexed(lambda x, i: i % _step == 0)) elif _step < 0: # Reversing events is not supported raise TypeError('Negative step not supported.') return source.pipe(*pipeline) return slice RxPY-3.2.0/rx/core/operators/some.py000066400000000000000000000026151404130727200172760ustar00rootroot00000000000000from typing import Callable, Optional from rx import operators as ops from rx.core import Observable from rx.core.typing import Predicate def _some(predicate: Optional[Predicate] = None) -> Callable[[Observable], Observable]: def some(source: Observable) -> Observable: """Partially applied operator. Determines whether some element of an observable sequence satisfies a condition if present, else if some items are in the sequence. Example: >>> obs = some(source) Args: predicate -- A function to test each element for a condition. Returns: An observable sequence containing a single element determining whether some elements in the source sequence pass the test in the specified predicate if given, else if some items are in the sequence. """ def subscribe(observer, scheduler=None): def on_next(_): observer.on_next(True) observer.on_completed() def on_error(): observer.on_next(False) observer.on_completed() return source.subscribe_(on_next, observer.on_error, on_error, scheduler) if predicate: return source.pipe( ops.filter(predicate), _some(), ) return Observable(subscribe) return some RxPY-3.2.0/rx/core/operators/startswith.py000066400000000000000000000011271404130727200205440ustar00rootroot00000000000000from typing import Any, Callable import rx from rx.core import Observable def _start_with(*args: Any) -> Callable[[Observable], Observable]: def start_with(source: Observable) -> Observable: """Partially applied start_with operator. Prepends a sequence of values to an observable sequence. Example: >>> start_with(source) Returns: The source sequence prepended with the specified values. """ start = rx.from_iterable(args) sequence = [start, source] return rx.concat(*sequence) return start_with RxPY-3.2.0/rx/core/operators/statistics.py000066400000000000000000000035731404130727200205310ustar00rootroot00000000000000from rx.core import Observable import math def determine_median(sorted_list): if len(sorted_list) == 0: raise Exception("The input sequence was empty") if len(sorted_list) % 2 == 1: return sorted_list[int((len(sorted_list) + 1) / 2) - 1] else: median_1 = sorted_list[int((len(sorted_list) + 1) / 2) - 1] median_2 = sorted_list[int((len(sorted_list) + 1) / 2)] return float(median_1 + median_2) / 2.0 def median(source: Observable) -> Observable: """ Calculates the statistical median on numerical emissions. The sequence must be finite. """ return source.to_sorted_list().map(lambda l: determine_median(l)) def mode(source: Observable) -> Observable: """ Returns the most frequently emitted value (or "values" if they have the same number of occurrences). The sequence must be finite. """ return source.group_by(lambda v: v) \ .flat_map(lambda grp: grp.count().map(lambda ct: (grp.key, ct))) \ .to_sorted_list(lambda t: t[1], reverse=True) \ .flat_map(lambda l: Observable.from_(l).take_while(lambda t: t[1] == l[0][1])) \ .map(lambda t: t[0]) def variance(source: Observable) -> Observable: """ Returns the statistical variance of the numerical emissions. The sequence must be finite. """ squared_values = source.to_list() \ .flat_map(lambda l: Observable.from_(l).average().flat_map(lambda avg: Observable.from_(l).map(lambda i: i - avg))) \ .map(lambda i: i * i) \ .publish() \ .auto_connect(2) return Observable.zip(squared_values.sum(), squared_values.count(), lambda sum, ct: sum / (ct - 1)) def standard_deviation(source: Observable) -> Observable: """ Returns the standard deviation of the numerical emissions: The sequence must be finite. """ return source.variance().map(lambda i: math.sqrt(i)) RxPY-3.2.0/rx/core/operators/subscribeon.py000066400000000000000000000027201404130727200206460ustar00rootroot00000000000000from typing import Callable from rx.core import Observable from rx.core.typing import Scheduler from rx.disposable import SingleAssignmentDisposable, SerialDisposable, ScheduledDisposable def _subscribe_on(scheduler: Scheduler) -> Callable[[Observable], Observable]: def subscribe_on(source: Observable) -> Observable: """Subscribe on the specified scheduler. Wrap the source sequence in order to run its subscription and unsubscription logic on the specified scheduler. This operation is not commonly used; see the remarks section for more information on the distinction between subscribe_on and observe_on. This only performs the side-effects of subscription and unsubscription on the specified scheduler. In order to invoke observer callbacks on a scheduler, use observe_on. Args: source: The source observable.. Returns: The source sequence whose subscriptions and un-subscriptions happen on the specified scheduler. """ def subscribe(observer, _=None): m = SingleAssignmentDisposable() d = SerialDisposable() d.disposable = m def action(scheduler, state): d.disposable = ScheduledDisposable(scheduler, source.subscribe(observer)) m.disposable = scheduler.schedule(action) return d return Observable(subscribe) return subscribe_on RxPY-3.2.0/rx/core/operators/sum.py000066400000000000000000000006311404130727200171330ustar00rootroot00000000000000from typing import Callable, Optional from rx import operators as ops from rx.core import Observable, pipe from rx.core.typing import Mapper def _sum(key_mapper: Optional[Mapper] = None) -> Callable[[Observable], Observable]: if key_mapper: return pipe( ops.map(key_mapper), ops.sum() ) return ops.reduce(seed=0, accumulator=lambda prev, curr: prev + curr) RxPY-3.2.0/rx/core/operators/switchlatest.py000066400000000000000000000050361404130727200210510ustar00rootroot00000000000000from asyncio import Future from typing import cast, Any, Callable, Union from rx import from_future from rx.core import Observable from rx.disposable import CompositeDisposable, SingleAssignmentDisposable, SerialDisposable from rx.internal.utils import is_future def _switch_latest() -> Callable[[Observable], Observable]: def switch_latest(source: Observable) -> Observable: """Partially applied switch_latest operator. Transforms an observable sequence of observable sequences into an observable sequence producing values only from the most recent observable sequence. Returns: An observable sequence that at any point in time produces the elements of the most recent inner observable sequence that has been received. """ def subscribe(observer, scheduler=None): inner_subscription = SerialDisposable() has_latest = [False] is_stopped = [False] latest = [0] def on_next(inner_source: Union[Observable, Future]): nonlocal source d = SingleAssignmentDisposable() with source.lock: latest[0] += 1 _id = latest[0] has_latest[0] = True inner_subscription.disposable = d # Check if Future or Observable if is_future(inner_source): obs = from_future(cast(Future, inner_source)) else: obs = cast(Observable, inner_source) def on_next(x: Any) -> None: if latest[0] == _id: observer.on_next(x) def on_error(e: Exception) -> None: if latest[0] == _id: observer.on_error(e) def on_completed() -> None: if latest[0] == _id: has_latest[0] = False if is_stopped[0]: observer.on_completed() d.disposable = obs.subscribe_(on_next, on_error, on_completed, scheduler=scheduler) def on_completed() -> None: is_stopped[0] = True if not has_latest[0]: observer.on_completed() subscription = source.subscribe_(on_next, observer.on_error, on_completed, scheduler=scheduler) return CompositeDisposable(subscription, inner_subscription) return Observable(subscribe) return switch_latest RxPY-3.2.0/rx/core/operators/take.py000066400000000000000000000023031404130727200172510ustar00rootroot00000000000000from typing import Callable from rx import empty from rx.core import Observable from rx.internal import ArgumentOutOfRangeException def _take(count: int) -> Callable[[Observable], Observable]: if count < 0: raise ArgumentOutOfRangeException() def take(source: Observable) -> Observable: """Returns a specified number of contiguous elements from the start of an observable sequence. >>> take(source) Keyword arguments: count -- The number of elements to return. Returns an observable sequence that contains the specified number of elements from the start of the input sequence. """ if not count: return empty() def subscribe(observer, scheduler=None): remaining = count def on_next(value): nonlocal remaining if remaining > 0: remaining -= 1 observer.on_next(value) if not remaining: observer.on_completed() return source.subscribe_(on_next, observer.on_error, observer.on_completed, scheduler) return Observable(subscribe) return take RxPY-3.2.0/rx/core/operators/takelast.py000066400000000000000000000025061404130727200201420ustar00rootroot00000000000000from typing import Callable from rx.core import Observable def _take_last(count: int) -> Callable[[Observable], Observable]: def take_last(source: Observable) -> Observable: """Returns a specified number of contiguous elements from the end of an observable sequence. Example: >>> res = take_last(source) This operator accumulates a buffer with a length enough to store elements count elements. Upon completion of the source sequence, this buffer is drained on the result sequence. This causes the elements to be delayed. Args: source: Number of elements to take from the end of the source sequence. Returns: An observable sequence containing the specified number of elements from the end of the source sequence. """ def subscribe(observer, scheduler=None): q = [] def on_next(x): q.append(x) if len(q) > count: q.pop(0) def on_completed(): while q: observer.on_next(q.pop(0)) observer.on_completed() return source.subscribe_(on_next, observer.on_error, on_completed, scheduler) return Observable(subscribe) return take_last RxPY-3.2.0/rx/core/operators/takelastbuffer.py000066400000000000000000000025611404130727200213350ustar00rootroot00000000000000from typing import Callable from rx.core import Observable def _take_last_buffer(count: int) -> Callable[[Observable], Observable]: def take_last_buffer(source: Observable) -> Observable: """Returns an array with the specified number of contiguous elements from the end of an observable sequence. Example: >>> res = take_last(source) This operator accumulates a buffer with a length enough to store elements count elements. Upon completion of the source sequence, this buffer is drained on the result sequence. This causes the elements to be delayed. Args: source: Source observable to take elements from. Returns: An observable sequence containing a single list with the specified number of elements from the end of the source sequence. """ def subscribe(observer, scheduler=None): q = [] def on_next(x): with source.lock: q.append(x) if len(q) > count: q.pop(0) def on_completed(): observer.on_next(q) observer.on_completed() return source.subscribe_(on_next, observer.on_error, on_completed, scheduler) return Observable(subscribe) return take_last_buffer RxPY-3.2.0/rx/core/operators/takelastwithtime.py000066400000000000000000000040111404130727200217060ustar00rootroot00000000000000from typing import Callable, Optional from rx.core import Observable, typing from rx.scheduler import TimeoutScheduler def _take_last_with_time(duration: typing.RelativeTime, scheduler: Optional[typing.Scheduler] = None ) -> Callable[[Observable], Observable]: def take_last_with_time(source: Observable) -> Observable: """Returns elements within the specified duration from the end of the observable source sequence. Example: >>> res = take_last_with_time(source) This operator accumulates a queue with a length enough to store elements received during the initial duration window. As more elements are received, elements older than the specified duration are taken from the queue and produced on the result sequence. This causes elements to be delayed with duration. Args: duration: Duration for taking elements from the end of the sequence. Returns: An observable sequence with the elements taken during the specified duration from the end of the source sequence. """ def subscribe(observer, scheduler_=None): nonlocal duration _scheduler = scheduler or scheduler_ or TimeoutScheduler.singleton() duration = _scheduler.to_timedelta(duration) q = [] def on_next(x): now = _scheduler.now q.append({"interval": now, "value": x}) while q and now - q[0]["interval"] >= duration: q.pop(0) def on_completed(): now = _scheduler.now while q: _next = q.pop(0) if now - _next["interval"] <= duration: observer.on_next(_next["value"]) observer.on_completed() return source.subscribe_(on_next, observer.on_error, on_completed, scheduler_) return Observable(subscribe) return take_last_with_time RxPY-3.2.0/rx/core/operators/takeuntil.py000066400000000000000000000023661404130727200203360ustar00rootroot00000000000000from asyncio import Future from typing import cast, Callable, Union from rx import from_future from rx.internal import noop from rx.core import Observable from rx.disposable import CompositeDisposable from rx.internal.utils import is_future def _take_until(other: Union[Observable, Future]) -> Callable[[Observable], Observable]: if is_future(other): obs = from_future(cast(Future, other)) else: obs = cast(Observable, other) def take_until(source: Observable) -> Observable: """Returns the values from the source observable sequence until the other observable sequence produces a value. Args: source: The source observable sequence. Returns: An observable sequence containing the elements of the source sequence up to the point the other sequence interrupted further propagation. """ def subscribe(observer, scheduler=None): def on_completed(_): observer.on_completed() return CompositeDisposable( source.subscribe(observer), obs.subscribe_(on_completed, observer.on_error, noop, scheduler) ) return Observable(subscribe) return take_until RxPY-3.2.0/rx/core/operators/takeuntilwithtime.py000066400000000000000000000027111404130727200221030ustar00rootroot00000000000000from typing import Callable, Optional from datetime import datetime from rx.core import Observable, typing from rx.disposable import CompositeDisposable from rx.scheduler import TimeoutScheduler def _take_until_with_time(end_time: typing.AbsoluteOrRelativeTime, scheduler: Optional[typing.Scheduler] = None ) -> Callable[[Observable], Observable]: def take_until_with_time(source: Observable) -> Observable: """Takes elements for the specified duration until the specified end time, using the specified scheduler to run timers. Examples: >>> res = take_until_with_time(source) Args: source: Source observale to take elements from. Returns: An observable sequence with the elements taken until the specified end time. """ def subscribe(observer, scheduler_=None): _scheduler = scheduler or scheduler_ or TimeoutScheduler.singleton() if isinstance(end_time, datetime): scheduler_method = _scheduler.schedule_absolute else: scheduler_method = _scheduler.schedule_relative def action(scheduler, state): observer.on_completed() task = scheduler_method(end_time, action) return CompositeDisposable(task, source.subscribe(observer, scheduler=scheduler_)) return Observable(subscribe) return take_until_with_time RxPY-3.2.0/rx/core/operators/takewhile.py000066400000000000000000000062371404130727200203140ustar00rootroot00000000000000from typing import Any, Callable from rx.core import Observable from rx.core.typing import Predicate, PredicateIndexed def _take_while(predicate: Predicate, inclusive: bool = False) -> Callable[[Observable], Observable]: def take_while(source: Observable) -> Observable: """Returns elements from an observable sequence as long as a specified condition is true. The element's index is used in the logic of the predicate function. Example: >>> take_while(source) Args: source: The source observable to take from. Returns: An observable sequence that contains the elements from the input sequence that occur before the element at which the test no longer passes. """ def subscribe(observer, scheduler=None): running = True def on_next(value): nonlocal running with source.lock: if not running: return try: running = predicate(value) except Exception as exn: observer.on_error(exn) return if running: observer.on_next(value) else: if inclusive: observer.on_next(value) observer.on_completed() return source.subscribe_(on_next, observer.on_error, observer.on_completed, scheduler) return Observable(subscribe) return take_while def _take_while_indexed(predicate: PredicateIndexed, inclusive: bool = False) -> Callable[[Observable], Observable]: def take_while_indexed(source: Observable) -> Observable: """Returns elements from an observable sequence as long as a specified condition is true. The element's index is used in the logic of the predicate function. Example: >>> take_while(source) Args: source: Source observable to take from. Returns: An observable sequence that contains the elements from the input sequence that occur before the element at which the test no longer passes. """ def subscribe(observer, scheduler=None): running = True i = 0 def on_next(value: Any) -> None: nonlocal running, i with source.lock: if not running: return try: running = predicate(value, i) except Exception as exn: observer.on_error(exn) return else: i += 1 if running: observer.on_next(value) else: if inclusive: observer.on_next(value) observer.on_completed() return source.subscribe_(on_next, observer.on_error, observer.on_completed, scheduler) return Observable(subscribe) return take_while_indexed RxPY-3.2.0/rx/core/operators/takewithtime.py000066400000000000000000000030641404130727200210310ustar00rootroot00000000000000from typing import Callable, Optional from rx.core import Observable, typing from rx.disposable import CompositeDisposable from rx.scheduler import TimeoutScheduler def _take_with_time(duration: typing.RelativeTime, scheduler: Optional[typing.Scheduler] = None ) -> Callable[[Observable], Observable]: def take_with_time(source: Observable) -> Observable: """Takes elements for the specified duration from the start of the observable source sequence. Example: >>> res = take_with_time(source) This operator accumulates a queue with a length enough to store elements received during the initial duration window. As more elements are received, elements older than the specified duration are taken from the queue and produced on the result sequence. This causes elements to be delayed with duration. Args: source: Source observable to take elements from. Returns: An observable sequence with the elements taken during the specified duration from the start of the source sequence. """ def subscribe(observer, scheduler_=None): _scheduler = scheduler or scheduler_ or TimeoutScheduler.singleton() def action(scheduler, state): observer.on_completed() disp = _scheduler.schedule_relative(duration, action) return CompositeDisposable(disp, source.subscribe(observer, scheduler=scheduler_)) return Observable(subscribe) return take_with_time RxPY-3.2.0/rx/core/operators/throttlefirst.py000066400000000000000000000030461404130727200212470ustar00rootroot00000000000000from typing import Callable, Optional from rx.core import Observable, typing from rx.scheduler import TimeoutScheduler def _throttle_first(window_duration: typing.RelativeTime, scheduler: Optional[typing.Scheduler] = None ) -> Callable[[Observable], Observable]: def throttle_first(source: Observable) -> Observable: """Returns an observable that emits only the first item emitted by the source Observable during sequential time windows of a specifiedduration. Args: source: Source observable to throttle. Returns: An Observable that performs the throttle operation. """ def subscribe(observer, scheduler_=None): _scheduler = scheduler or scheduler_ or TimeoutScheduler.singleton() duration = _scheduler.to_timedelta(window_duration or 0.0) if duration <= _scheduler.to_timedelta(0): raise ValueError('window_duration cannot be less or equal zero.') last_on_next = [0] def on_next(x): emit = False now = _scheduler.now with source.lock: if not last_on_next[0] or now - last_on_next[0] >= duration: last_on_next[0] = now emit = True if emit: observer.on_next(x) return source.subscribe_(on_next, observer.on_error, observer.on_completed, scheduler=_scheduler) return Observable(subscribe) return throttle_first RxPY-3.2.0/rx/core/operators/timeinterval.py000066400000000000000000000023141404130727200210320ustar00rootroot00000000000000from typing import Callable, NamedTuple, Any, Optional from datetime import timedelta from rx import operators as ops from rx.core import Observable, typing from rx.scheduler import TimeoutScheduler class TimeInterval(NamedTuple): value: Any interval: timedelta def _time_interval(scheduler: Optional[typing.Scheduler] = None) -> Callable[[Observable], Observable]: def time_interval(source: Observable) -> Observable: """Records the time interval between consecutive values in an observable sequence. >>> res = time_interval(source) Return: An observable sequence with time interval information on values. """ def subscribe(observer, scheduler_): _scheduler = scheduler or scheduler_ or TimeoutScheduler.singleton() last = _scheduler.now def mapper(value): nonlocal last now = _scheduler.now span = now - last last = now return TimeInterval(value=value, interval=span) return source.pipe(ops.map(mapper)).subscribe(observer, scheduler_) return Observable(subscribe) return time_interval RxPY-3.2.0/rx/core/operators/timeout.py000066400000000000000000000056551404130727200200300ustar00rootroot00000000000000from asyncio import Future from datetime import datetime from typing import cast, Callable, Optional, Union from rx import from_future, throw from rx.core import Observable, typing from rx.disposable import CompositeDisposable, SingleAssignmentDisposable, SerialDisposable from rx.scheduler import TimeoutScheduler from rx.internal.utils import is_future def _timeout(duetime: typing.AbsoluteTime, other: Optional[Union[Observable, Future]] = None, scheduler: Optional[typing.Scheduler] = None ) -> Callable[[Observable], Observable]: other = other or throw(Exception("Timeout")) if is_future(other): obs = from_future(cast(Future, other)) else: obs = cast(Observable, other) def timeout(source: Observable) -> Observable: """Returns the source observable sequence or the other observable sequence if duetime elapses. Examples: >>> res = timeout(source) Args: source: Source observable to timeout Returns: An obserable sequence switching to the other sequence in case of a timeout. """ def subscribe(observer, scheduler_=None): _scheduler = scheduler or scheduler_ or TimeoutScheduler.singleton() if isinstance(duetime, datetime): scheduler_method = _scheduler.schedule_absolute else: scheduler_method = _scheduler.schedule_relative switched = [False] _id = [0] original = SingleAssignmentDisposable() subscription = SerialDisposable() timer = SerialDisposable() subscription.disposable = original def create_timer(): my_id = _id[0] def action(scheduler, state=None): switched[0] = (_id[0] == my_id) timer_wins = switched[0] if timer_wins: subscription.disposable = obs.subscribe(observer, scheduler=scheduler) timer.disposable = scheduler_method(duetime, action) create_timer() def on_next(value): send_wins = not switched[0] if send_wins: _id[0] += 1 observer.on_next(value) create_timer() def on_error(error): on_error_wins = not switched[0] if on_error_wins: _id[0] += 1 observer.on_error(error) def on_completed(): on_completed_wins = not switched[0] if on_completed_wins: _id[0] += 1 observer.on_completed() original.disposable = source.subscribe_(on_next, on_error, on_completed, scheduler_) return CompositeDisposable(subscription, timer) return Observable(subscribe) return timeout RxPY-3.2.0/rx/core/operators/timeoutwithmapper.py000066400000000000000000000072051404130727200221220ustar00rootroot00000000000000from typing import Callable, Optional, Any import rx from rx.core import Observable from rx.disposable import CompositeDisposable, SingleAssignmentDisposable, SerialDisposable def _timeout_with_mapper(first_timeout: Optional[Observable] = None, timeout_duration_mapper: Optional[Callable[[Any], Observable]] = None, other: Optional[Observable] = None ) -> Callable[[Observable], Observable]: """Returns the source observable sequence, switching to the other observable sequence if a timeout is signaled. res = timeout_with_mapper(rx.timer(500)) res = timeout_with_mapper(rx.timer(500), lambda x: rx.timer(200)) res = timeout_with_mapper(rx.timer(500), lambda x: rx.timer(200)), rx.return_value(42)) Args: first_timeout -- [Optional] Observable sequence that represents the timeout for the first element. If not provided, this defaults to rx.never(). timeout_duration_mapper -- [Optional] Selector to retrieve an observable sequence that represents the timeout between the current element and the next element. other -- [Optional] Sequence to return in case of a timeout. If not provided, this is set to rx.throw(). Returns: The source sequence switching to the other sequence in case of a timeout. """ first_timeout = first_timeout or rx.never() other = other or rx.throw(Exception('Timeout')) def timeout_with_mapper(source: Observable) -> Observable: def subscribe(observer, scheduler=None): subscription = SerialDisposable() timer = SerialDisposable() original = SingleAssignmentDisposable() subscription.disposable = original switched = False _id = [0] def set_timer(timeout: Observable) -> None: my_id = _id[0] def timer_wins(): return _id[0] == my_id d = SingleAssignmentDisposable() timer.disposable = d def on_next(x): if timer_wins(): subscription.disposable = other.subscribe(observer, scheduler=scheduler) d.dispose() def on_error(e): if timer_wins(): observer.on_error(e) def on_completed(): if timer_wins(): subscription.disposable = other.subscribe(observer) d.disposable = timeout.subscribe_(on_next, on_error, on_completed, scheduler) set_timer(first_timeout) def observer_wins(): res = not switched if res: _id[0] += 1 return res def on_next(x): if observer_wins(): observer.on_next(x) timeout = None try: timeout = timeout_duration_mapper(x) except Exception as e: observer.on_error(e) return set_timer(timeout) def on_error(error): if observer_wins(): observer.on_error(error) def on_completed(): if observer_wins(): observer.on_completed() original.disposable = source.subscribe_(on_next, on_error, on_completed, scheduler) return CompositeDisposable(subscription, timer) return Observable(subscribe) return timeout_with_mapper RxPY-3.2.0/rx/core/operators/timestamp.py000066400000000000000000000022041404130727200203300ustar00rootroot00000000000000from typing import Callable, NamedTuple, Any, Optional from datetime import datetime from rx import defer from rx.core import Observable, typing from rx.scheduler import TimeoutScheduler from rx import operators class Timestamp(NamedTuple): value: Any timestamp: datetime def _timestamp(scheduler: Optional[typing.Scheduler] = None) -> Callable[[Observable], Observable]: def timestamp(source: Observable) -> Observable: """Records the timestamp for each value in an observable sequence. Examples: >>> timestamp(source) Produces objects with attributes `value` and `timestamp`, where value is the original value. Args: source: Observable source to timestamp. Returns: An observable sequence with timestamp information on values. """ def factory(scheduler_=None): _scheduler = scheduler or scheduler_ or TimeoutScheduler.singleton() mapper = operators.map(lambda value: Timestamp(value=value, timestamp=_scheduler.now)) return source.pipe(mapper) return defer(factory) return timestamp RxPY-3.2.0/rx/core/operators/todict.py000066400000000000000000000031471404130727200176220ustar00rootroot00000000000000from typing import Callable, Any, Optional from rx.core import typing, Observable from rx.core.typing import Mapper def _to_dict(key_mapper: Mapper, element_mapper: Optional[Mapper] = None ) -> Callable[[Observable], Observable]: def to_dict(source: Observable) -> Observable: """Converts the observable sequence to a Map if it exists. Args: source: Source observable to convert. Returns: An observable sequence with a single value of a dictionary containing the values from the observable sequence. """ def subscribe(observer: typing.Observer, scheduler: Optional[typing.Scheduler] = None) -> typing.Disposable: m = dict() def on_next(x: Any) -> None: try: key = key_mapper(x) except Exception as ex: # pylint: disable=broad-except observer.on_error(ex) return element = x if element_mapper: try: element = element_mapper(x) except Exception as ex: # pylint: disable=broad-except observer.on_error(ex) return m[key] = element def on_completed() -> None: nonlocal m observer.on_next(m) m = dict() observer.on_completed() return source.subscribe_(on_next, observer.on_error, on_completed, scheduler) return Observable(subscribe) return to_dict RxPY-3.2.0/rx/core/operators/tofuture.py000066400000000000000000000034651404130727200202140ustar00rootroot00000000000000from typing import Callable, Optional from asyncio import Future from .. import typing from rx.core import Observable from rx.internal.exceptions import SequenceContainsNoElementsError def _to_future(future_ctor: Optional[Callable[[], Future]] = None, scheduler: Optional[typing.Scheduler] = None) -> Callable[[Observable], Future]: future_ctor = future_ctor or Future future = future_ctor() def to_future(source: Observable) -> Future: """Converts an existing observable sequence to a Future. If the observable emits a single item, then this item is set as the result of the future. If the observable emits a sequence of items, then the last emitted item is set as the result of the future. Example: future = rx.return_value(42).pipe(ops.to_future(asyncio.Future)) Args: future_ctor: [Optional] The constructor of the future. Returns: A future with the last value from the observable sequence. """ has_value = False last_value = None def on_next(value): nonlocal last_value nonlocal has_value last_value = value has_value = True def on_error(err): if not future.cancelled(): future.set_exception(err) def on_completed(): nonlocal last_value if not future.cancelled(): if has_value: future.set_result(last_value) else: future.set_exception(SequenceContainsNoElementsError()) last_value = None dis = source.subscribe_(on_next, on_error, on_completed, scheduler=scheduler) future.add_done_callback(lambda _: dis.dispose()) return future return to_future RxPY-3.2.0/rx/core/operators/toiterable.py000066400000000000000000000016151404130727200204640ustar00rootroot00000000000000from typing import Callable from rx.core import Observable def _to_iterable() -> Callable[[Observable], Observable]: def to_iterable(source: Observable) -> Observable: """Creates an iterable from an observable sequence. Returns: An observable sequence containing a single element with an iterable containing all the elements of the source sequence. """ def subscribe(observer, scheduler=None): nonlocal source queue = [] def on_next(item): queue.append(item) def on_completed(): nonlocal queue observer.on_next(queue) queue = [] observer.on_completed() return source.subscribe_(on_next, observer.on_error, on_completed, scheduler) return Observable(subscribe) return to_iterableRxPY-3.2.0/rx/core/operators/tomarbles.py000066400000000000000000000040221404130727200203150ustar00rootroot00000000000000from typing import List, Optional from rx.core import Observable from rx.core.typing import Scheduler, RelativeTime from rx.scheduler import NewThreadScheduler new_thread_scheduler = NewThreadScheduler() def _to_marbles(scheduler: Optional[Scheduler] = None, timespan: RelativeTime = 0.1): def to_marbles(source: Observable) -> Observable: """Convert an observable sequence into a marble diagram string. Args: timespan: [Optional] duration of each character in second. If not specified, defaults to 0.1s. scheduler: [Optional] The scheduler used to run the the input sequence on. Returns: Observable stream. """ def subscribe(observer, scheduler=None): scheduler = scheduler or new_thread_scheduler result: List[str] = [] last = scheduler.now def add_timespan(): nonlocal last now = scheduler.now diff = now - last last = now secs = scheduler.to_seconds(diff) dashes = "-" * int((secs + timespan / 2.0) * (1.0 / timespan)) result.append(dashes) def on_next(value): add_timespan() result.append(stringify(value)) def on_error(exception): add_timespan() result.append(stringify(exception)) observer.on_next("".join(n for n in result)) observer.on_completed() def on_completed(): add_timespan() result.append("|") observer.on_next("".join(n for n in result)) observer.on_completed() return source.subscribe_(on_next, on_error, on_completed) return Observable(subscribe) return to_marbles def stringify(value): """Utility for stringifying an event. """ string = str(value) if len(string) > 1: string = "(%s)" % string return string RxPY-3.2.0/rx/core/operators/toset.py000066400000000000000000000013201404130727200174610ustar00rootroot00000000000000from typing import Callable from rx.core import Observable def _to_set() -> Callable[[Observable], Observable]: """Converts the observable sequence to a set. Returns an observable sequence with a single value of a set containing the values from the observable sequence. """ def to_set(source: Observable) -> Observable: def subscribe(observer, scheduler=None): s = set() def on_completed(): nonlocal s observer.on_next(s) s = set() observer.on_completed() return source.subscribe_(s.add, observer.on_error, on_completed, scheduler) return Observable(subscribe) return to_setRxPY-3.2.0/rx/core/operators/whiledo.py000066400000000000000000000017221404130727200177640ustar00rootroot00000000000000from asyncio import Future from typing import cast, Callable, Union import itertools import rx from rx.core import Observable from rx.core.typing import Predicate from rx.internal.utils import is_future, infinite def _while_do(condition: Predicate) -> Callable[[Observable], Observable]: def while_do(source: Union[Observable, Future]) -> Observable: """Repeats source as long as condition holds emulating a while loop. Args: source: The observable sequence that will be run if the condition function returns true. Returns: An observable sequence which is repeated as long as the condition holds. """ if is_future(source): obs = rx.from_future(cast(Future, source)) else: obs = cast(Observable, source) it = itertools.takewhile(condition, (obs for _ in infinite())) return rx.concat_with_iterable(it) return while_do RxPY-3.2.0/rx/core/operators/window.py000066400000000000000000000103541404130727200176410ustar00rootroot00000000000000import logging from typing import Callable, Any from rx import empty from rx.core import Observable from rx.internal import noop from rx.internal.utils import add_ref from rx.disposable import SingleAssignmentDisposable, SerialDisposable, CompositeDisposable, RefCountDisposable from rx.subject import Subject from rx import operators as ops log = logging.getLogger("Rx") def _window_toggle(openings: Observable, closing_mapper: Callable[[Any], Observable] ) -> Callable[[Observable], Observable]: """Projects each element of an observable sequence into zero or more windows. Args: source: Source observable to project into windows. Returns: An observable sequence of windows. """ def window_toggle(source: Observable) -> Observable: def mapper(args): _, window = args return window return openings.pipe( ops.group_join( source, closing_mapper, lambda _: empty(), ), ops.map(mapper), ) return window_toggle def _window(boundaries: Observable) -> Callable[[Observable], Observable]: """Projects each element of an observable sequence into zero or more windows. Args: source: Source observable to project into windows. Returns: An observable sequence of windows. """ def window(source: Observable) -> Observable: def subscribe(observer, scheduler=None): window_subject = Subject() d = CompositeDisposable() r = RefCountDisposable(d) observer.on_next(add_ref(window_subject, r)) def on_next_window(x): window_subject.on_next(x) def on_error(err): window_subject.on_error(err) observer.on_error(err) def on_completed(): window_subject.on_completed() observer.on_completed() d.add(source.subscribe_(on_next_window, on_error, on_completed, scheduler)) def on_next_observer(w): nonlocal window_subject window_subject.on_completed() window_subject = Subject() observer.on_next(add_ref(window_subject, r)) d.add(boundaries.subscribe_(on_next_observer, on_error, on_completed, scheduler)) return r return Observable(subscribe) return window def _window_when(closing_mapper: Callable[[], Observable]) -> Callable[[Observable], Observable]: """Projects each element of an observable sequence into zero or more windows. Args: source: Source observable to project into windows. Returns: An observable sequence of windows. """ def window_when(source: Observable) -> Observable: def subscribe(observer, scheduler=None): m = SerialDisposable() d = CompositeDisposable(m) r = RefCountDisposable(d) window = Subject() observer.on_next(add_ref(window, r)) def on_next(value): window.on_next(value) def on_error(error): window.on_error(error) observer.on_error(error) def on_completed(): window.on_completed() observer.on_completed() d.add(source.subscribe_(on_next, on_error, on_completed, scheduler)) def create_window_on_completed(): try: window_close = closing_mapper() except Exception as exception: observer.on_error(exception) return def on_completed(): nonlocal window window.on_completed() window = Subject() observer.on_next(add_ref(window, r)) create_window_on_completed() m1 = SingleAssignmentDisposable() m.disposable = m1 m1.disposable = window_close.pipe(ops.take(1)).subscribe_(noop, on_error, on_completed, scheduler) create_window_on_completed() return r return Observable(subscribe) return window_when RxPY-3.2.0/rx/core/operators/windowwithcount.py000066400000000000000000000045051404130727200216070ustar00rootroot00000000000000from typing import Callable, Optional import logging from rx.core import Observable from rx.internal.utils import add_ref from rx.disposable import SingleAssignmentDisposable, RefCountDisposable from rx.internal.exceptions import ArgumentOutOfRangeException from rx.subject import Subject log = logging.getLogger("Rx") def _window_with_count(count: int, skip: Optional[int] = None) -> Callable[[Observable], Observable]: """Projects each element of an observable sequence into zero or more windows which are produced based on element count information. Examples: >>> window_with_count(10) >>> window_with_count(10, 1) Args: count: Length of each window. skip: [Optional] Number of elements to skip between creation of consecutive windows. If not specified, defaults to the count. Returns: An observable sequence of windows. """ if count <= 0: raise ArgumentOutOfRangeException() if skip is None: skip = count if skip <= 0: raise ArgumentOutOfRangeException() def window_with_count(source: Observable) -> Observable: def subscribe(observer, scheduler=None): m = SingleAssignmentDisposable() refCountDisposable = RefCountDisposable(m) n = [0] q = [] def create_window(): s = Subject() q.append(s) observer.on_next(add_ref(s, refCountDisposable)) create_window() def on_next(x): for item in q: item.on_next(x) c = n[0] - count + 1 if c >= 0 and c % skip == 0: s = q.pop(0) s.on_completed() n[0] += 1 if (n[0] % skip) == 0: create_window() def on_error(exception): while q: q.pop(0).on_error(exception) observer.on_error(exception) def on_completed(): while q: q.pop(0).on_completed() observer.on_completed() m.disposable = source.subscribe_(on_next, on_error, on_completed, scheduler) return refCountDisposable return Observable(subscribe) return window_with_count RxPY-3.2.0/rx/core/operators/windowwithtime.py000066400000000000000000000063141404130727200214150ustar00rootroot00000000000000from typing import Callable, Optional from datetime import timedelta from rx.core import Observable, typing from rx.scheduler import TimeoutScheduler from rx.internal.constants import DELTA_ZERO from rx.internal.utils import add_ref from rx.disposable import SingleAssignmentDisposable, CompositeDisposable, RefCountDisposable, SerialDisposable from rx.subject import Subject def _window_with_time(timespan: typing.RelativeTime, timeshift: Optional[typing.RelativeTime] = None, scheduler: Optional[typing.Scheduler] = None) -> Callable[[Observable], Observable]: if timeshift is None: timeshift = timespan if not isinstance(timespan, timedelta): timespan = timedelta(seconds=timespan) if not isinstance(timeshift, timedelta): timeshift = timedelta(seconds=timeshift) def window_with_time(source: Observable) -> Observable: def subscribe(observer, scheduler_=None): _scheduler = scheduler or scheduler_ or TimeoutScheduler.singleton() timer_d = SerialDisposable() next_shift = [timeshift] next_span = [timespan] total_time = [DELTA_ZERO] q = [] group_disposable = CompositeDisposable(timer_d) ref_count_disposable = RefCountDisposable(group_disposable) def create_timer(): m = SingleAssignmentDisposable() timer_d.disposable = m is_span = False is_shift = False if next_span[0] == next_shift[0]: is_span = True is_shift = True elif next_span[0] < next_shift[0]: is_span = True else: is_shift = True new_total_time = next_span[0] if is_span else next_shift[0] ts = new_total_time - total_time[0] total_time[0] = new_total_time if is_span: next_span[0] += timeshift if is_shift: next_shift[0] += timeshift def action(scheduler, state=None): s = None if is_shift: s = Subject() q.append(s) observer.on_next(add_ref(s, ref_count_disposable)) if is_span: s = q.pop(0) s.on_completed() create_timer() m.disposable = _scheduler.schedule_relative(ts, action) q.append(Subject()) observer.on_next(add_ref(q[0], ref_count_disposable)) create_timer() def on_next(x): for s in q: s.on_next(x) def on_error(e): for s in q: s.on_error(e) observer.on_error(e) def on_completed(): for s in q: s.on_completed() observer.on_completed() group_disposable.add(source.subscribe_(on_next, on_error, on_completed, scheduler_)) return ref_count_disposable return Observable(subscribe) return window_with_time RxPY-3.2.0/rx/core/operators/windowwithtimeorcount.py000066400000000000000000000051521404130727200230260ustar00rootroot00000000000000from typing import Callable, Optional from rx.core import Observable, typing from rx.scheduler import TimeoutScheduler from rx.internal.utils import add_ref from rx.disposable import SingleAssignmentDisposable, CompositeDisposable, RefCountDisposable, SerialDisposable from rx.subject import Subject def _window_with_time_or_count(timespan: typing.RelativeTime, count: int, scheduler: Optional[typing.Scheduler] = None ) -> Callable[[Observable], Observable]: def window_with_time_or_count(source: Observable) -> Observable: def subscribe(observer, scheduler_=None): _scheduler = scheduler or scheduler_ or TimeoutScheduler.singleton() n = [0] s = [None] timer_d = SerialDisposable() window_id = [0] group_disposable = CompositeDisposable(timer_d) ref_count_disposable = RefCountDisposable(group_disposable) def create_timer(_id): m = SingleAssignmentDisposable() timer_d.disposable = m def action(scheduler, state): if _id != window_id[0]: return n[0] = 0 window_id[0] += 1 new_id = window_id[0] s[0].on_completed() s[0] = Subject() observer.on_next(add_ref(s[0], ref_count_disposable)) create_timer(new_id) m.disposable = _scheduler.schedule_relative(timespan, action) s[0] = Subject() observer.on_next(add_ref(s[0], ref_count_disposable)) create_timer(0) def on_next(x): new_window = False new_id = 0 s[0].on_next(x) n[0] += 1 if n[0] == count: new_window = True n[0] = 0 window_id[0] += 1 new_id = window_id[0] s[0].on_completed() s[0] = Subject() observer.on_next(add_ref(s[0], ref_count_disposable)) if new_window: create_timer(new_id) def on_error(e): s[0].on_error(e) observer.on_error(e) def on_completed(): s[0].on_completed() observer.on_completed() group_disposable.add(source.subscribe_(on_next, on_error, on_completed, scheduler_)) return ref_count_disposable return Observable(subscribe) return window_with_time_or_count RxPY-3.2.0/rx/core/operators/withlatestfrom.py000066400000000000000000000014531404130727200214060ustar00rootroot00000000000000from typing import Callable import rx from rx.core import Observable def _with_latest_from(*sources: Observable) -> Callable[[Observable], Observable]: """With latest from operator. Merges the specified observable sequences into one observable sequence by creating a tuple only when the first observable sequence produces an element. The observables can be passed either as seperate arguments or as a list. Examples: >>> op = with_latest_from(obs1) >>> op = with_latest_from(obs1, obs2, obs3) Returns: An observable sequence containing the result of combining elements of the sources into a tuple. """ def with_latest_from(source: Observable) -> Observable: return rx.with_latest_from(source, *sources) return with_latest_from RxPY-3.2.0/rx/core/operators/zip.py000066400000000000000000000037111404130727200171330ustar00rootroot00000000000000from typing import Callable, Iterable import rx from rx.core import Observable # pylint: disable=redefined-builtin def _zip(*args: Observable) -> Callable[[Observable], Observable]: def zip(source: Observable) -> Observable: """Merges the specified observable sequences into one observable sequence by creating a tuple whenever all of the observable sequences have produced an element at a corresponding index. Example: >>> res = zip(source) Args: source: Source observable to zip. Returns: An observable sequence containing the result of combining elements of the sources as a tuple. """ return rx.zip(source, *args) return zip def _zip_with_iterable(seq: Iterable) -> Callable[[Observable], Observable]: def zip_with_iterable(source: Observable) -> Observable: """Merges the specified observable sequence and list into one observable sequence by creating a tuple whenever all of the observable sequences have produced an element at a corresponding index. Example >>> res = zip(source) Args: source: Source observable to zip. Returns: An observable sequence containing the result of combining elements of the sources as a tuple. """ first = source second = iter(seq) def subscribe(observer, scheduler=None): index = 0 def on_next(left): nonlocal index try: right = next(second) except StopIteration: observer.on_completed() else: result = (left, right) observer.on_next(result) return first.subscribe_(on_next, observer.on_error, observer.on_completed, scheduler) return Observable(subscribe) return zip_with_iterable RxPY-3.2.0/rx/core/pipe.py000066400000000000000000000060331404130727200152500ustar00rootroot00000000000000from typing import Callable, Any, TypeVar, overload from functools import reduce A = TypeVar('A') B = TypeVar('B') C = TypeVar('C') D = TypeVar('D') E = TypeVar('E') F = TypeVar('F') G = TypeVar('G') @overload def pipe(*operators: Callable[['Observable'], 'Observable']) -> Callable[['Observable'], 'Observable']: # type: ignore """Compose multiple operators left to right. Composes zero or more operators into a functional composition. The operators are composed to left to right. A composition of zero operators gives back the source. Examples: >>> pipe()(source) == source >>> pipe(f)(source) == f(source) >>> pipe(f, g)(source) == g(f(source)) >>> pipe(f, g, h)(source) == h(g(f(source))) ... Returns: The composed observable. """ ... @overload def pipe() -> Callable[[A], A]: # pylint: disable=function-redefined ... # pylint: disable=pointless-statement @overload def pipe(op1: Callable[[A], B]) -> Callable[[A], B]: # pylint: disable=function-redefined ... # pylint: disable=pointless-statement @overload def pipe(op1: Callable[[A], B], op2: Callable[[B], C]) -> Callable[[A], C]: # pylint: disable=function-redefined ... # pylint: disable=pointless-statement @overload def pipe(op1: Callable[[A], B], # pylint: disable=function-redefined op2: Callable[[B], C], op3: Callable[[C], D] ) -> Callable[[A], D]: ... # pylint: disable=pointless-statement @overload def pipe(op1: Callable[[A], B], # pylint: disable=function-redefined op2: Callable[[B], C], op3: Callable[[C], D], op4: Callable[[D], E] ) -> Callable[[A], E]: ... # pylint: disable=pointless-statement @overload def pipe(op1: Callable[[A], B], # pylint: disable=function-redefined op2: Callable[[B], C], op3: Callable[[C], D], op4: Callable[[D], E], op5: Callable[[E], F] ) -> Callable[[A], F]: ... # pylint: disable=pointless-statement @overload def pipe(op1: Callable[[A], B], # pylint: disable=function-redefined,too-many-arguments op2: Callable[[B], C], op3: Callable[[C], D], op4: Callable[[D], E], op5: Callable[[E], F], op6: Callable[[F], G] ) -> Callable[[A], G]: ... # pylint: disable=pointless-statement # pylint: disable=function-redefined def pipe(*operators: Callable[[Any], Any]) -> Callable[[Any], Any]: """Compose multiple operators left to right. Composes zero or more operators into a functional composition. The operators are composed to left to right. A composition of zero operators gives back the source. Examples: >>> pipe()(source) == source >>> pipe(f)(source) == f(source) >>> pipe(f, g)(source) == g(f(source)) >>> pipe(f, g, h)(source) == h(g(f(source))) ... Returns: The composed observable. """ def compose(source: Any) -> Any: return reduce(lambda obs, op: op(obs), operators, source) return compose RxPY-3.2.0/rx/core/run.py000066400000000000000000000032571404130727200151240ustar00rootroot00000000000000import threading from typing import Any, Optional from rx.internal.exceptions import SequenceContainsNoElementsError from rx.scheduler import NewThreadScheduler from .observable import Observable scheduler = NewThreadScheduler() def run(source: Observable) -> Any: """Run source synchronously. Subscribes to the observable source. Then blocks and waits for the observable source to either complete or error. Returns the last value emitted, or throws exception if any error occured. Examples: >>> result = run(source) Args: source: Observable source to run. Raises: SequenceContainsNoElementsError: if observable completes (on_completed) without any values being emitted. Exception: raises exception if any error (on_error) occured. Returns: The last element emitted from the observable. """ exception: Optional[Exception] = None latch = threading.Event() has_result = False result: Any = None done = False def on_next(value: Any) -> None: nonlocal result, has_result result = value has_result = True def on_error(error: Exception) -> None: nonlocal exception, done exception = error done = True latch.set() def on_completed() -> None: nonlocal done done = True latch.set() source.subscribe_(on_next, on_error, on_completed, scheduler=scheduler) while not done: latch.wait() if exception and isinstance(exception, Exception): raise exception # pylint: disable=raising-bad-type if not has_result: raise SequenceContainsNoElementsError return result RxPY-3.2.0/rx/core/typing.py000066400000000000000000000225351404130727200156320ustar00rootroot00000000000000from abc import abstractmethod from typing import Any, Callable, Generic, Optional, Tuple, TypeVar, Union from datetime import datetime, timedelta from threading import Thread from . import abc T_out = TypeVar('T_out', covariant=True) T_in = TypeVar('T_in', contravariant=True) TState = TypeVar('TState') # Can be anything T1 = TypeVar('T1') T2 = TypeVar('T2') Action = Callable[[], None] OnNext = Callable[[Any], None] OnError = Callable[[Exception], None] OnCompleted = Callable[[], None] Mapper = Callable[[T1], T2] MapperIndexed = Callable[[T1, int], T2] Predicate = Callable[[T1], bool] PredicateIndexed = Callable[[T1, int], bool] Comparer = Callable[[T1, T2], bool] SubComparer = Callable[[T1, T2], int] Accumulator = Callable[[TState, T1], TState] AbsoluteTime = Union[datetime, float] RelativeTime = Union[timedelta, float] AbsoluteOrRelativeTime = Union[datetime, timedelta, float] class Disposable(abc.Disposable): """Disposable abstract base class.""" __slots__ = () @abstractmethod def dispose(self) -> None: """Dispose the object: stop whatever we're doing and release all of the resources we might be using. """ raise NotImplementedError class Scheduler(abc.Scheduler): """Scheduler abstract base class.""" __slots__ = () @property @abstractmethod def now(self) -> datetime: """Represents a notion of time for this scheduler. Tasks being scheduled on a scheduler will adhere to the time denoted by this property. Returns: The scheduler's current time, as a datetime instance. """ return NotImplemented @abstractmethod def schedule(self, action: 'ScheduledAction', state: Optional[TState] = None ) -> Disposable: """Schedules an action to be executed. Args: action: Action to be executed. state: [Optional] state to be given to the action function. Returns: The disposable object used to cancel the scheduled action (best effort). """ return NotImplemented @abstractmethod def schedule_relative(self, duetime: RelativeTime, action: 'ScheduledAction', state: Optional[TState] = None ) -> Disposable: """Schedules an action to be executed after duetime. Args: duetime: Relative time after which to execute the action. action: Action to be executed. state: [Optional] state to be given to the action function. Returns: The disposable object used to cancel the scheduled action (best effort). """ return NotImplemented @abstractmethod def schedule_absolute(self, duetime: AbsoluteTime, action: 'ScheduledAction', state: Optional[TState] = None ) -> Disposable: """Schedules an action to be executed at duetime. Args: duetime: Absolute time at which to execute the action. action: Action to be executed. state: [Optional] state to be given to the action function. Returns: The disposable object used to cancel the scheduled action (best effort). """ return NotImplemented @classmethod @abstractmethod def to_seconds(cls, value: AbsoluteOrRelativeTime) -> float: """Converts time value to seconds. This method handles both absolute (datetime) and relative (timedelta) values. If the argument is already a float, it is simply returned unchanged. Args: value: the time value to convert to seconds. Returns: The value converted to seconds. """ return NotImplemented @classmethod @abstractmethod def to_datetime(cls, value: AbsoluteOrRelativeTime) -> datetime: """Converts time value to datetime. This method handles both absolute (float) and relative (timedelta) values. If the argument is already a datetime, it is simply returned unchanged. Args: value: the time value to convert to datetime. Returns: The value converted to datetime. """ return NotImplemented @classmethod @abstractmethod def to_timedelta(cls, value: AbsoluteOrRelativeTime) -> timedelta: """Converts time value to timedelta. This method handles both absolute (datetime) and relative (float) values. If the argument is already a timedelta, it is simply returned unchanged. If the argument is an absolute time, the result value will be the timedelta since the epoch, January 1st, 1970, 00:00:00. Args: value: the time value to convert to timedelta. Returns: The value converted to timedelta. """ return NotImplemented class PeriodicScheduler(abc.PeriodicScheduler): """PeriodicScheduler abstract base class.""" __slots__ = () @abstractmethod def schedule_periodic(self, period: RelativeTime, action: 'ScheduledPeriodicAction', state: Optional[TState] = None ) -> Disposable: """Schedules a periodic piece of work. Args: period: Period in seconds or timedelta for running the work periodically. action: Action to be executed. state: [Optional] Initial state passed to the action upon the first iteration. Returns: The disposable object used to cancel the scheduled recurring action (best effort). """ return NotImplemented ScheduledAction = Callable[[Scheduler, Optional[TState]], Optional[Disposable]] ScheduledPeriodicAction = Callable[[Optional[TState]], Optional[TState]] ScheduledSingleOrPeriodicAction = Union[ScheduledAction, ScheduledPeriodicAction] Startable = Union[abc.Startable, Thread] StartableTarget = Callable[..., None] StartableFactory = Callable[[StartableTarget], Startable] class Observer(Generic[T_in], abc.Observer): """Observer abstract base class An Observer is the entity that receives all emissions of a subscribed Observable. """ __slots__ = () @abstractmethod def on_next(self, value: T_in) -> None: """Notifies the observer of a new element in the sequence. Args: value: The received element. """ raise NotImplementedError @abstractmethod def on_error(self, error: Exception) -> None: """Notifies the observer that an exception has occurred. Args: error: The error that has occurred. """ raise NotImplementedError @abstractmethod def on_completed(self) -> None: """Notifies the observer of the end of the sequence.""" raise NotImplementedError class Observable(Generic[T_out], abc.Observable): """Observable abstract base class. Represents a push-style collection.""" __slots__ = () @abstractmethod def subscribe(self, observer: Observer[T_out] = None, *, scheduler: Scheduler = None ) -> Disposable: """Subscribe an observer to the observable sequence. Args: observer: [Optional] The object that is to receive notifications. scheduler: [Optional] The default scheduler to use for this subscription. Returns: Disposable object representing an observer's subscription to the observable sequence. """ raise NotImplementedError class Subject(Generic[T_in, T_out], abc.Subject): """Subject abstract base class. Represents an object that is both an observable sequence as well as an observer. """ __slots__ = () @abstractmethod def subscribe(self, observer: Observer[T_out] = None, *, scheduler: Scheduler = None ) -> Disposable: """Subscribe an observer to the observable sequence. Args: observer: [Optional] The object that is to receive notifications. scheduler: [Optional] The default scheduler to use for this subscription. Returns: Disposable object representing an observer's subscription to the observable sequence. """ raise NotImplementedError @abstractmethod def on_next(self, value: T_in) -> None: """Notifies the observer of a new element in the sequence. Args: value: The received element. """ raise NotImplementedError @abstractmethod def on_error(self, error: Exception) -> None: """Notifies the observer that an exception has occurred. Args: error: The error that has occurred. """ raise NotImplementedError @abstractmethod def on_completed(self) -> None: """Notifies the observer of the end of the sequence.""" raise NotImplementedError Subscription = Callable[[Observer, Optional[Scheduler]], Disposable] RxPY-3.2.0/rx/disposable/000077500000000000000000000000001404130727200151345ustar00rootroot00000000000000RxPY-3.2.0/rx/disposable/__init__.py000066400000000000000000000006521404130727200172500ustar00rootroot00000000000000from .disposable import Disposable from .booleandisposable import BooleanDisposable from .compositedisposable import CompositeDisposable from .singleassignmentdisposable import SingleAssignmentDisposable from .multipleassignmentdisposable import MultipleAssignmentDisposable from .serialdisposable import SerialDisposable from .refcountdisposable import RefCountDisposable from .scheduleddisposable import ScheduledDisposable RxPY-3.2.0/rx/disposable/booleandisposable.py000066400000000000000000000007121404130727200211730ustar00rootroot00000000000000from threading import RLock from rx.core.typing import Disposable class BooleanDisposable(Disposable): """Represents a Disposable that can be checked for status.""" def __init__(self): """Initializes a new instance of the BooleanDisposable class.""" self.is_disposed = False self.lock = RLock() super().__init__() def dispose(self): """Sets the status to disposed""" self.is_disposed = True RxPY-3.2.0/rx/disposable/compositedisposable.py000066400000000000000000000050641404130727200215630ustar00rootroot00000000000000from threading import RLock from rx.core.typing import Disposable class CompositeDisposable(Disposable): """Represents a group of disposable resources that are disposed together""" def __init__(self, *args): if args and isinstance(args[0], list): self.disposable = args[0] else: self.disposable = list(args) self.is_disposed = False self.lock = RLock() super(CompositeDisposable, self).__init__() def add(self, item): """Adds a disposable to the CompositeDisposable or disposes the disposable if the CompositeDisposable is disposed Args: item: Disposable to add.""" should_dispose = False with self.lock: if self.is_disposed: should_dispose = True else: self.disposable.append(item) if should_dispose: item.dispose() def remove(self, item): """Removes and disposes the first occurrence of a disposable from the CompositeDisposable.""" if self.is_disposed: return should_dispose = False with self.lock: if item in self.disposable: self.disposable.remove(item) should_dispose = True if should_dispose: item.dispose() return should_dispose def dispose(self): """Disposes all disposable in the group and removes them from the group.""" if self.is_disposed: return with self.lock: self.is_disposed = True current_disposable = self.disposable self.disposable = [] for disp in current_disposable: disp.dispose() def clear(self): """Removes and disposes all disposable from the CompositeDisposable, but does not dispose the CompositeDisposable.""" with self.lock: current_disposable = self.disposable self.disposable = [] for disposable in current_disposable: disposable.dispose() def contains(self, item): """Determines whether the CompositeDisposable contains a specific disposable. Args: item: Disposable to search for Returns: True if the disposable was found; otherwise, False""" return item in self.disposable def to_list(self): return self.disposable[:] def __len__(self): return len(self.disposable) @property def length(self): return len(self.disposable) RxPY-3.2.0/rx/disposable/disposable.py000066400000000000000000000020211404130727200176260ustar00rootroot00000000000000from typing import Optional from threading import RLock from rx.internal import noop from rx.core import typing class Disposable(typing.Disposable): """Main disposable class""" def __init__(self, action: Optional[typing.Action] = None) -> None: """Creates a disposable object that invokes the specified action when disposed. Args: action: Action to run during the first call to dispose. The action is guaranteed to be run at most once. Returns: The disposable object that runs the given action upon disposal. """ self.is_disposed = False self.action = action or noop self.lock = RLock() super().__init__() def dispose(self) -> None: """Performs the task of cleaning up resources.""" dispose = False with self.lock: if not self.is_disposed: dispose = True self.is_disposed = True if dispose: self.action() RxPY-3.2.0/rx/disposable/multipleassignmentdisposable.py000066400000000000000000000024371404130727200235060ustar00rootroot00000000000000from threading import RLock from rx.core.typing import Disposable class MultipleAssignmentDisposable(Disposable): """Represents a disposable resource whose underlying disposable resource can be replaced by another disposable resource.""" def __init__(self): self.current = None self.is_disposed = False self.lock = RLock() super().__init__() def get_disposable(self): return self.current def set_disposable(self, value): """If the MultipleAssignmentDisposable has already been disposed, assignment to this property causes immediate disposal of the given disposable object.""" with self.lock: should_dispose = self.is_disposed if not should_dispose: self.current = value if should_dispose and value is not None: value.dispose() disposable = property(get_disposable, set_disposable) def dispose(self): """Disposes the underlying disposable as well as all future replacements.""" old = None with self.lock: if not self.is_disposed: self.is_disposed = True old = self.current self.current = None if old is not None: old.dispose() RxPY-3.2.0/rx/disposable/refcountdisposable.py000066400000000000000000000044511404130727200214050ustar00rootroot00000000000000from threading import RLock from rx.disposable import Disposable from rx.core import typing class RefCountDisposable(typing.Disposable): """Represents a disposable resource that only disposes its underlying disposable resource when all dependent disposable objects have been disposed.""" class InnerDisposable(typing.Disposable): def __init__(self, parent) -> None: self.parent = parent self.is_disposed = False self.lock = RLock() def dispose(self) -> None: with self.lock: parent = self.parent self.parent = None parent.release() def __init__(self, disposable) -> None: """Initializes a new instance of the RefCountDisposable class with the specified disposable.""" self.underlying_disposable = disposable self.is_primary_disposed = False self.is_disposed = False self.lock = RLock() self.count = 0 super().__init__() def dispose(self) -> None: """Disposes the underlying disposable only when all dependent disposable have been disposed.""" if self.is_disposed: return underlying_disposable = None with self.lock: if not self.is_primary_disposed: self.is_primary_disposed = True if not self.count: self.is_disposed = True underlying_disposable = self.underlying_disposable if underlying_disposable is not None: underlying_disposable.dispose() def release(self) -> None: if self.is_disposed: return should_dispose = False with self.lock: self.count -= 1 if not self.count and self.is_primary_disposed: self.is_disposed = True should_dispose = True if should_dispose: self.underlying_disposable.dispose() @property def disposable(self) -> typing.Disposable: """Returns a dependent disposable that when disposed decreases the refcount on the underlying disposable.""" with self.lock: if self.is_disposed: return Disposable() self.count += 1 return self.InnerDisposable(self) RxPY-3.2.0/rx/disposable/scheduleddisposable.py000066400000000000000000000021211404130727200215100ustar00rootroot00000000000000from threading import RLock from rx.core.typing import Disposable class ScheduledDisposable(Disposable): """Represents a disposable resource whose disposal invocation will be scheduled on the specified Scheduler""" def __init__(self, scheduler, disposable) -> None: """Initializes a new instance of the ScheduledDisposable class that uses a Scheduler on which to dispose the disposable.""" self.scheduler = scheduler self.disposable = disposable self.is_disposed = False self.lock = RLock() super().__init__() def dispose(self) -> None: """Disposes the wrapped disposable on the provided scheduler.""" parent = self def action(scheduler, state): """Scheduled dispose action""" should_dispose = False with self.lock: if not parent.is_disposed: parent.is_disposed = True should_dispose = True if should_dispose: parent.disposable.dispose() self.scheduler.schedule(action) RxPY-3.2.0/rx/disposable/serialdisposable.py000066400000000000000000000033171404130727200210370ustar00rootroot00000000000000from threading import RLock from typing import Optional from rx.core import typing from rx.core.typing import Disposable class SerialDisposable(Disposable): """Represents a disposable resource whose underlying disposable resource can be replaced by another disposable resource, causing automatic disposal of the previous underlying disposable resource. """ def __init__(self) -> None: self.current: Optional[Disposable] = None self.is_disposed = False self.lock = RLock() super().__init__() def get_disposable(self) -> Optional[Disposable]: return self.current def set_disposable(self, value: typing.Disposable) -> None: """If the SerialDisposable has already been disposed, assignment to this property causes immediate disposal of the given disposable object. Assigning this property disposes the previous disposable object.""" old: Optional[Disposable] = None with self.lock: should_dispose = self.is_disposed if not should_dispose: old = self.current self.current = value if old is not None: old.dispose() if should_dispose and value is not None: value.dispose() disposable = property(get_disposable, set_disposable) def dispose(self) -> None: """Disposes the underlying disposable as well as all future replacements.""" old: Optional[Disposable] = None with self.lock: if not self.is_disposed: self.is_disposed = True old = self.current self.current = None if old is not None: old.dispose() RxPY-3.2.0/rx/disposable/singleassignmentdisposable.py000066400000000000000000000030111404130727200231210ustar00rootroot00000000000000from threading import RLock from rx.core.typing import Disposable class SingleAssignmentDisposable(Disposable): """Single assignment disposable. Represents a disposable resource which only allows a single assignment of its underlying disposable resource. If an underlying disposable resource has already been set, future attempts to set the underlying disposable resource will throw an Error.""" def __init__(self) -> None: """Initializes a new instance of the SingleAssignmentDisposable class. """ self.is_disposed = False self.current = None self.lock = RLock() super().__init__() def get_disposable(self): return self.current def set_disposable(self, value): if self.current: raise Exception('Disposable has already been assigned') old = None with self.lock: should_dispose = self.is_disposed if not should_dispose: old = self.current self.current = value if old: old.dispose() if should_dispose and value: value.dispose() disposable = property(get_disposable, set_disposable) def dispose(self) -> None: """Sets the status to disposed""" old = None with self.lock: if not self.is_disposed: self.is_disposed = True old = self.current self.current = None if old is not None: old.dispose() RxPY-3.2.0/rx/internal/000077500000000000000000000000001404130727200146235ustar00rootroot00000000000000RxPY-3.2.0/rx/internal/__init__.py000066400000000000000000000003741404130727200167400ustar00rootroot00000000000000from .priorityqueue import PriorityQueue from .basic import noop, default_error, default_comparer from .exceptions import SequenceContainsNoElementsError, ArgumentOutOfRangeException, DisposedException from . import concurrency from . import constants RxPY-3.2.0/rx/internal/basic.py000066400000000000000000000011141404130727200162530ustar00rootroot00000000000000from typing import Any from datetime import datetime # Defaults def noop(*args, **kw): """No operation. Returns nothing""" pass def identity(x: Any) -> Any: """Returns argument x""" return x def default_now() -> datetime: return datetime.utcnow() def default_comparer(x: Any, y: Any) -> bool: return x == y def default_sub_comparer(x, y): return x - y def default_key_serializer(x: Any) -> str: return str(x) def default_error(err) -> Exception: if isinstance(err, BaseException): raise err else: raise Exception(err) RxPY-3.2.0/rx/internal/concurrency.py000066400000000000000000000006621404130727200175330ustar00rootroot00000000000000from threading import Thread from rx.core.typing import StartableTarget def default_thread_factory(target: StartableTarget) -> Thread: return Thread(target=target, daemon=True) def synchronized(lock): """A decorator for synchronizing access to a given function.""" def wrapper(fn): def inner(*args, **kw): with lock: return fn(*args, **kw) return inner return wrapper RxPY-3.2.0/rx/internal/constants.py000066400000000000000000000001541404130727200172110ustar00rootroot00000000000000from datetime import datetime, timedelta DELTA_ZERO = timedelta(0) UTC_ZERO = datetime.utcfromtimestamp(0) RxPY-3.2.0/rx/internal/exceptions.py000066400000000000000000000017261404130727200173640ustar00rootroot00000000000000# Rx Exceptions class SequenceContainsNoElementsError(Exception): def __init__(self, msg=None): super(SequenceContainsNoElementsError, self).__init__(msg or "Sequence contains no elements") class ArgumentOutOfRangeException(ValueError): def __init__(self, msg=None): super(ArgumentOutOfRangeException, self).__init__(msg or "Argument out of range") class DisposedException(Exception): def __init__(self, msg=None): super(DisposedException, self).__init__(msg or "Object has been disposed") class ReEntracyException(Exception): def __init__(self, msg=None): super(ReEntracyException, self).__init__(msg or 'Re-entrancy detected') class CompletedException(Exception): def __init__(self, msg=None): super(CompletedException, self).__init__(msg or 'Observer completed') class WouldBlockException(Exception): def __init__(self, msg=None): super(WouldBlockException, self).__init__(msg or "Would block") RxPY-3.2.0/rx/internal/priorityqueue.py000066400000000000000000000026751404130727200201350ustar00rootroot00000000000000import heapq from sys import maxsize from typing import Generic, List, Tuple from rx.core.typing import T1 class PriorityQueue(Generic[T1]): """Priority queue for scheduling. Note that methods aren't thread-safe.""" MIN_COUNT = ~maxsize def __init__(self) -> None: self.items: List[Tuple[T1, int]] = [] self.count = PriorityQueue.MIN_COUNT # Monotonic increasing for sort stability def __len__(self): """Returns length of queue""" return len(self.items) def peek(self) -> T1: """Returns first item in queue without removing it""" return self.items[0][0] def dequeue(self) -> T1: """Returns and removes item with lowest priority from queue""" item: T1 = heapq.heappop(self.items)[0] if not self.items: self.count = PriorityQueue.MIN_COUNT return item def enqueue(self, item: T1) -> None: """Adds item to queue""" heapq.heappush(self.items, (item, self.count)) self.count += 1 def remove(self, item: T1) -> bool: """Remove given item from queue""" for index, _item in enumerate(self.items): if _item[0] == item: self.items.pop(index) heapq.heapify(self.items) return True return False def clear(self): """Remove all items from the queue.""" self.items = [] self.count = PriorityQueue.MIN_COUNT RxPY-3.2.0/rx/internal/utils.py000066400000000000000000000024741404130727200163440ustar00rootroot00000000000000from functools import update_wrapper from types import FunctionType from typing import cast, Any, Callable, Iterable from rx.disposable import CompositeDisposable def add_ref(xs, r): from rx.core import Observable def subscribe(observer, scheduler=None): return CompositeDisposable(r.disposable, xs.subscribe(observer)) return Observable(subscribe) def is_future(fut: Any) -> bool: return callable(getattr(fut, 'add_done_callback', None)) def infinite() -> Iterable[int]: n = 0 while True: yield n n += 1 def alias(name: str, doc: str, fun: Callable[..., Any]) -> Callable[..., Any]: # Adapted from https://stackoverflow.com/questions/13503079/how-to-create-a-copy-of-a-python-function# # See also help(type(lambda: 0)) _fun = cast(FunctionType, fun) args = (_fun.__code__, _fun.__globals__) kwargs = { 'name': name, 'argdefs': _fun.__defaults__, 'closure': _fun.__closure__ } alias = FunctionType(*args, **kwargs) # type: ignore alias = cast(FunctionType, update_wrapper(alias, _fun)) alias.__kwdefaults__ = _fun.__kwdefaults__ alias.__doc__ = doc return alias class NotSet: """Sentinel value.""" def __eq__(self, other): return self is other def __repr__(self): return 'NotSet' RxPY-3.2.0/rx/operators/000077500000000000000000000000001404130727200150255ustar00rootroot00000000000000RxPY-3.2.0/rx/operators/__init__.py000066400000000000000000003516231404130727200171500ustar00rootroot00000000000000# pylint: disable=too-many-lines,redefined-outer-name,redefined-builtin from asyncio import Future from typing import Callable, Union, Any, Iterable, List, Optional, cast, overload from datetime import timedelta, datetime from rx.internal.utils import NotSet from rx.core import Observable, ConnectableObservable, GroupedObservable, typing, pipe from rx.core.typing import Mapper, MapperIndexed, Predicate, PredicateIndexed, Comparer, Accumulator from rx.subject import Subject def all(predicate: Predicate) -> Callable[[Observable], Observable]: """Determines whether all elements of an observable sequence satisfy a condition. .. marble:: :alt: all --1--2--3--4--5-| [ all(i: i<10) ] ----------------true-| Example: >>> op = all(lambda value: value.length > 3) Args: predicate: A function to test each element for a condition. Returns: An operator function that takes an observable source and returns an observable sequence containing a single element determining whether all elements in the source sequence pass the test in the specified predicate. """ from rx.core.operators.all import _all return _all(predicate) def amb(right_source: Observable) -> Callable[[Observable], Observable]: """Propagates the observable sequence that reacts first. .. marble:: :alt: amb ---8--6--9-----------| --1--2--3---5--------| ----------10-20-30---| [ amb() ] --1--2--3---5--------| Example: >>> op = amb(ys) Returns: An operator function that takes an observable source and returns an observable sequence that surfaces any of the given sequences, whichever reacted first. """ from rx.core.operators.amb import _amb return _amb(right_source) def as_observable() -> Callable[[Observable], Observable]: """Hides the identity of an observable sequence. Returns: An operator function that takes an observable source and returns and observable sequence that hides the identity of the source sequence. """ from rx.core.operators.asobservable import _as_observable return _as_observable() def average(key_mapper: Optional[Mapper] = None) -> Callable[[Observable], Observable]: """The average operator. Computes the average of an observable sequence of values that are in the sequence or obtained by invoking a transform function on each element of the input sequence if present. .. marble:: :alt: average ---1--2--3--4----| [ average() ] -----------------2.5-| Examples: >>> op = average() >>> op = average(lambda x: x.value) Args: key_mapper: [Optional] A transform function to apply to each element. Returns: An operator function that takes an observable source and returns an observable sequence containing a single element with the average of the sequence of values. """ from rx.core.operators.average import _average return _average(key_mapper) def buffer(boundaries: Observable) -> Callable[[Observable], Observable]: """Projects each element of an observable sequence into zero or more buffers. .. marble:: :alt: buffer ---a-----b-----c--------| --1--2--3--4--5--6--7---| [ buffer() ] ---1-----2,3---4,5------| Examples: >>> res = buffer(rx.interval(1.0)) Args: boundaries: Observable sequence whose elements denote the creation and completion of buffers. Returns: A function that takes an observable source and returns an observable sequence of buffers. """ from rx.core.operators.buffer import _buffer return _buffer(boundaries) def buffer_when(closing_mapper: Callable[[], Observable]) -> Callable[[Observable], Observable]: """Projects each element of an observable sequence into zero or more buffers. .. marble:: :alt: buffer_when --------c-| --------c-| --------c-| ---1--2--3--4--5--6-------| [ buffer_when() ] +-------1,2-----3,4,5---6-| Examples: >>> res = buffer_when(lambda: rx.timer(0.5)) Args: closing_mapper: A function invoked to define the closing of each produced buffer. A buffer is started when the previous one is closed, resulting in non-overlapping buffers. The buffer is closed when one item is emitted or when the observable completes. Returns: A function that takes an observable source and returns an observable sequence of windows. """ from rx.core.operators.buffer import _buffer_when return _buffer_when(closing_mapper) def buffer_toggle(openings: Observable, closing_mapper: Callable[[Any], Observable] ) -> Callable[[Observable], Observable]: """Projects each element of an observable sequence into zero or more buffers. .. marble:: :alt: buffer_toggle ---a-----------b--------------| ---d--| --------e--| ----1--2--3--4--5--6--7--8----| [ buffer_toggle() ] ------1----------------5,6,7--| >>> res = buffer_toggle(rx.interval(0.5), lambda i: rx.timer(i)) Args: openings: Observable sequence whose elements denote the creation of buffers. closing_mapper: A function invoked to define the closing of each produced buffer. Value from openings Observable that initiated the associated buffer is provided as argument to the function. The buffer is closed when one item is emitted or when the observable completes. Returns: A function that takes an observable source and returns an observable sequence of windows. """ from rx.core.operators.buffer import _buffer_toggle return _buffer_toggle(openings, closing_mapper) def buffer_with_count(count: int, skip: Optional[int] = None) -> Callable[[Observable], Observable]: """Projects each element of an observable sequence into zero or more buffers which are produced based on element count information. .. marble:: :alt: buffer_with_count ----1-2-3-4-5-6------| [buffer_with_count(3)] --------1,2,3-4,5,6--| Examples: >>> res = buffer_with_count(10)(xs) >>> res = buffer_with_count(10, 1)(xs) Args: count: Length of each buffer. skip: [Optional] Number of elements to skip between creation of consecutive buffers. If not provided, defaults to the count. Returns: A function that takes an observable source and returns an observable sequence of buffers. """ from rx.core.operators.buffer import _buffer_with_count return _buffer_with_count(count, skip) def buffer_with_time(timespan: typing.RelativeTime, timeshift: Optional[typing.RelativeTime] = None, scheduler: Optional[typing.Scheduler] = None ) -> Callable[[Observable], Observable]: """Projects each element of an observable sequence into zero or more buffers which are produced based on timing information. .. marble:: :alt: buffer_with_time ---1-2-3-4-5-6-----| [buffer_with_time()] -------1,2,3-4,5,6-| Examples: >>> # non-overlapping segments of 1 second >>> res = buffer_with_time(1.0) >>> # segments of 1 second with time shift 0.5 seconds >>> res = buffer_with_time(1.0, 0.5) Args: timespan: Length of each buffer (specified as a float denoting seconds or an instance of timedelta). timeshift: [Optional] Interval between creation of consecutive buffers (specified as a float denoting seconds or an instance of timedelta). If not specified, the timeshift will be the same as the timespan argument, resulting in non-overlapping adjacent buffers. scheduler: [Optional] Scheduler to run the timer on. If not specified, the timeout scheduler is used Returns: An operator function that takes an observable source and returns an observable sequence of buffers. """ from rx.core.operators.bufferwithtime import _buffer_with_time return _buffer_with_time(timespan, timeshift, scheduler) def buffer_with_time_or_count(timespan, count, scheduler=None) -> Callable[[Observable], Observable]: """Projects each element of an observable sequence into a buffer that is completed when either it's full or a given amount of time has elapsed. .. marble:: :alt: buffer_with_time_or_count --1-2-3-4-5-6------| [ buffer() ] ------1,2,3-4,5,6--| Examples: >>> # 5s or 50 items in an array >>> res = source.buffer_with_time_or_count(5.0, 50) >>> # 5s or 50 items in an array >>> res = source.buffer_with_time_or_count(5.0, 50, Scheduler.timeout) Args: timespan: Maximum time length of a buffer. count: Maximum element count of a buffer. scheduler: [Optional] Scheduler to run buffering timers on. If not specified, the timeout scheduler is used. Returns: An operator function that takes an observable source and returns an observable sequence of buffers. """ from rx.core.operators.bufferwithtimeorcount import _buffer_with_time_or_count return _buffer_with_time_or_count(timespan, count, scheduler) def catch(handler: Union[Observable, Callable[[Exception, Observable], Observable]] ) -> Callable[[Observable], Observable]: """Continues an observable sequence that is terminated by an exception with the next observable sequence. .. marble:: :alt: catch ---1---2---3-* a-7-8-| [ catch(a) ] ---1---2---3---7-8-| Examples: >>> op = catch(ys) >>> op = catch(lambda ex, src: ys(ex)) Args: handler: Second observable sequence used to produce results when an error occurred in the first sequence, or an exception handler function that returns an observable sequence given the error and source observable that occurred in the first sequence. Returns: A function taking an observable source and returns an observable sequence containing the first sequence's elements, followed by the elements of the handler sequence in case an exception occurred. """ from rx.core.operators.catch import _catch return _catch(handler) def combine_latest(*others: Observable) -> Callable[[Observable], Observable]: """Merges the specified observable sequences into one observable sequence by creating a tuple whenever any of the observable sequences produces an element. .. marble:: :alt: combine_latest ---a-----b--c------| --1---2--------3---| [ combine_latest() ] ---a1-a2-b2-c2-c3--| Examples: >>> obs = combine_latest(other) >>> obs = combine_latest(obs1, obs2, obs3) Returns: An operator function that takes an observable sources and returns an observable sequence containing the result of combining elements of the sources into a tuple. """ from rx.core.operators.combinelatest import _combine_latest return _combine_latest(*others) def concat(*sources: Observable) -> Callable[[Observable], Observable]: """Concatenates all the observable sequences. .. marble:: :alt: concat ---1--2--3--| --6--8--| [ concat() ] ---1--2--3----6--8-| Examples: >>> op = concat(xs, ys, zs) Returns: An operator function that takes one or more observable sources and returns an observable sequence that contains the elements of each given sequence, in sequential order. """ from rx.core.operators.concat import _concat return _concat(*sources) def contains(value: Any, comparer: Optional[typing.Comparer] = None ) -> Callable[[Observable], Observable]: """Determines whether an observable sequence contains a specified element with an optional equality comparer. .. marble:: :alt: contains --1--2--3--4--| [ contains(3) ] --------------true-| Examples: >>> op = contains(42) >>> op = contains({ "value": 42 }, lambda x, y: x["value"] == y["value"]) Args: value: The value to locate in the source sequence. comparer: [Optional] An equality comparer to compare elements. Returns: A function that takes a source observable that returns an observable sequence containing a single element determining whether the source sequence contains an element that has the specified value. """ from rx.core.operators.contains import _contains return _contains(value, comparer) def count(predicate: Optional[typing.Predicate] = None) -> Callable[[Observable], Observable]: """Returns an observable sequence containing a value that represents how many elements in the specified observable sequence satisfy a condition if provided, else the count of items. .. marble:: :alt: count --1--2--3--4--| [ count(i: i>2) ] --------------2-| Examples: >>> op = count() >>> op = count(lambda x: x > 3) Args: predicate: A function to test each element for a condition. Returns: An operator function that takes an observable source and returns an observable sequence containing a single element with a number that represents how many elements in the input sequence satisfy the condition in the predicate function if provided, else the count of items in the sequence. """ from rx.core.operators.count import _count return _count(predicate) def debounce(duetime: typing.RelativeTime, scheduler: Optional[typing.Scheduler] = None ) -> Callable[[Observable], Observable]: """Ignores values from an observable sequence which are followed by another value before duetime. .. marble:: :alt: debounce --1--2-3-4--5------| [ debounce() ] ----1------4---5---| Example: >>> res = debounce(5.0) # 5 seconds Args: duetime: Duration of the throttle period for each value (specified as a float denoting seconds or an instance of timedelta). scheduler: Scheduler to debounce values on. Returns: An operator function that takes the source observable and returns the debounced observable sequence. """ from rx.core.operators.debounce import _debounce return _debounce(duetime, scheduler) throttle_with_timeout = debounce def default_if_empty(default_value: Any = None) -> Callable[[Observable], Observable]: """Returns the elements of the specified sequence or the specified value in a singleton sequence if the sequence is empty. .. marble:: :alt: default_if_empty ----------| [default_if_empty(42)] ----------42-| Examples: >>> res = obs = default_if_empty() >>> obs = default_if_empty(False) Args: default_value: The value to return if the sequence is empty. If not provided, this defaults to None. Returns: An operator function that takes an observable source and returns an observable sequence that contains the specified default value if the source is empty otherwise, the elements of the source. """ from rx.core.operators.defaultifempty import _default_if_empty return _default_if_empty(default_value) def delay_subscription(duetime: typing.AbsoluteOrRelativeTime, scheduler: Optional[typing.Scheduler] = None ) -> Callable[[Observable], Observable]: """Time shifts the observable sequence by delaying the subscription. .. marble:: :alt: delay_subscription ----1--2--3--4-----| [ delay() ] --------1--2--3--4-| Example: >>> res = delay_subscription(5.0) # 5s Args: duetime: Absolute or relative time to perform the subscription at. scheduler: Scheduler to delay subscription on. Returns: A function that take a source observable and returns a time-shifted observable sequence. """ from rx.core.operators.delaysubscription import _delay_subscription return _delay_subscription(duetime, scheduler=scheduler) def delay_with_mapper(subscription_delay=None, delay_duration_mapper=None ) -> Callable[[Observable], Observable]: """Time shifts the observable sequence based on a subscription delay and a delay mapper function for each element. .. marble:: :alt: delay_with_mapper ----1--2--3--4-----| [ delay() ] --------1--2--3--4-| Examples: >>> # with mapper only >>> res = source.delay_with_mapper(lambda x: Scheduler.timer(5.0)) >>> # with delay and mapper >>> res = source.delay_with_mapper(rx.timer(2.0), lambda x: rx.timer(x)) Args: subscription_delay: [Optional] Sequence indicating the delay for the subscription to the source. delay_duration_mapper: [Optional] Selector function to retrieve a sequence indicating the delay for each given element. Returns: A function that takes an observable source and returns a time-shifted observable sequence. """ from rx.core.operators.delaywithmapper import _delay_with_mapper return _delay_with_mapper(subscription_delay, delay_duration_mapper) def dematerialize() -> Callable[[Observable], Observable]: """Dematerialize operator. Dematerializes the explicit notification values of an observable sequence as implicit notifications. Returns: An observable sequence exhibiting the behavior corresponding to the source sequence's notification values. """ from rx.core.operators.dematerialize import _dematerialize return _dematerialize() def delay(duetime: typing.RelativeTime, scheduler: Optional[typing.Scheduler] = None ) -> Callable[[Observable], Observable]: """The delay operator. .. marble:: :alt: delay ----1--2--3--4-----| [ delay() ] --------1--2--3--4-| Time shifts the observable sequence by duetime. The relative time intervals between the values are preserved. Examples: >>> res = delay(timedelta(seconds=10)) >>> res = delay(5.0) Args: duetime: Relative time, specified as a float denoting seconds or an instance of timedelta, by which to shift the observable sequence. scheduler: [Optional] Scheduler to run the delay timers on. If not specified, the timeout scheduler is used. Returns: A partially applied operator function that takes the source observable and returns a time-shifted sequence. """ from rx.core.operators.delay import _delay return _delay(duetime, scheduler) def distinct(key_mapper: Optional[Mapper] = None, comparer: Optional[Comparer] = None ) -> Callable[[Observable], Observable]: """Returns an observable sequence that contains only distinct elements according to the key_mapper and the comparer. Usage of this operator should be considered carefully due to the maintenance of an internal lookup structure which can grow large. .. marble:: :alt: distinct -0-1-2-1-3-4-2-0---| [ distinct() ] -0-1-2---3-4-------| Examples: >>> res = obs = xs.distinct() >>> obs = xs.distinct(lambda x: x.id) >>> obs = xs.distinct(lambda x: x.id, lambda a,b: a == b) Args: key_mapper: [Optional] A function to compute the comparison key for each element. comparer: [Optional] Used to compare items in the collection. Returns: An operator function that takes an observable source and returns an observable sequence only containing the distinct elements, based on a computed key value, from the source sequence. """ from rx.core.operators.distinct import _distinct return _distinct(key_mapper, comparer) def distinct_until_changed(key_mapper: Optional[Mapper] = None, comparer: Optional[Comparer] = None ) -> Callable[[Observable], Observable]: """Returns an observable sequence that contains only distinct contiguous elements according to the key_mapper and the comparer. .. marble:: :alt: distinct_until_changed -0-1-1-2-3-1-2-2-3-| [ distinct() ] -0-1---2-3-1-2---3-| Examples: >>> op = distinct_until_changed(); >>> op = distinct_until_changed(lambda x: x.id) >>> op = distinct_until_changed(lambda x: x.id, lambda x, y: x == y) Args: key_mapper: [Optional] A function to compute the comparison key for each element. If not provided, it projects the value. comparer: [Optional] Equality comparer for computed key values. If not provided, defaults to an equality comparer function. Returns: An operator function that takes an observable source and returns an observable sequence only containing the distinct contiguous elements, based on a computed key value, from the source sequence. """ from rx.core.operators.distinctuntilchanged import _distinct_until_changed return _distinct_until_changed(key_mapper, comparer) def do(observer: typing.Observer) -> Callable[[Observable], Observable]: """Invokes an action for each element in the observable sequence and invokes an action on graceful or exceptional termination of the observable sequence. This method can be used for debugging, logging, etc. of query behavior by intercepting the message stream to run arbitrary actions for messages on the pipeline. .. marble:: :alt: do ----1---2---3---4---| [ do(i: foo()) ] ----1---2---3---4---| >>> do(observer) Args: observer: Observer Returns: An operator function that takes the source observable and returns the source sequence with the side-effecting behavior applied. """ from rx.core.operators.do import do as do_ return do_(observer) def do_action(on_next: Optional[typing.OnNext] = None, on_error: Optional[typing.OnError] = None, on_completed: Optional[typing.OnCompleted] = None ) -> Callable[[Observable], Observable]: """Invokes an action for each element in the observable sequence and invokes an action on graceful or exceptional termination of the observable sequence. This method can be used for debugging, logging, etc. of query behavior by intercepting the message stream to run arbitrary actions for messages on the pipeline. .. marble:: :alt: do_action ----1---2---3---4---| [do_action(i: foo())] ----1---2---3---4---| Examples: >>> do_action(send) >>> do_action(on_next, on_error) >>> do_action(on_next, on_error, on_completed) Args: on_next: [Optional] Action to invoke for each element in the observable sequence. on_error: [Optional] Action to invoke on exceptional termination of the observable sequence. on_completed: [Optional] Action to invoke on graceful termination of the observable sequence. Returns: An operator function that takes the source observable an returns the source sequence with the side-effecting behavior applied. """ from rx.core.operators.do import _do_action return _do_action(on_next, on_error, on_completed) def do_while(condition: Predicate) -> Callable[[Observable], Observable]: """Repeats source as long as condition holds emulating a do while loop. .. marble:: :alt: do_while --1--2--| [ do_while() ] --1--2--1--2--1--2--| Args: condition: The condition which determines if the source will be repeated. Returns: An observable sequence which is repeated as long as the condition holds. """ from rx.core.operators.dowhile import _do_while return _do_while(condition) def element_at(index: int) -> Callable[[Observable], Observable]: """Returns the element at a specified index in a sequence. .. marble:: :alt: element_at ----1---2---3---4---| [ element_at(2) ] ------------3-| Example: >>> res = source.element_at(5) Args: index: The zero-based index of the element to retrieve. Returns: An operator function that takes an observable source and returns an observable sequence that produces the element at the specified position in the source sequence. """ from rx.core.operators.elementatordefault import _element_at_or_default return _element_at_or_default(index, False) def element_at_or_default(index: int, default_value: Any = None) -> Callable[[Observable], Observable]: """Returns the element at a specified index in a sequence or a default value if the index is out of range. .. marble:: :alt: element_at_or_default --1---2---3---4-| [ element_at(6, a) ] ----------------a-| Example: >>> res = source.element_at_or_default(5) >>> res = source.element_at_or_default(5, 0) Args: index: The zero-based index of the element to retrieve. default_value: [Optional] The default value if the index is outside the bounds of the source sequence. Returns: A function that takes an observable source and returns an observable sequence that produces the element at the specified position in the source sequence, or a default value if the index is outside the bounds of the source sequence. """ from rx.core.operators.elementatordefault import _element_at_or_default return _element_at_or_default(index, True, default_value) def exclusive() -> Callable[[Observable], Observable]: """Performs a exclusive waiting for the first to finish before subscribing to another observable. Observables that come in between subscriptions will be dropped on the floor. .. marble:: :alt: exclusive -+---+-----+-------| +-7-8-9-| +-4-5-6-| +-1-2-3-| [ exclusive() ] ---1-2-3-----7-8-9-| Returns: An exclusive observable with only the results that happen when subscribed. """ from rx.core.operators.exclusive import _exclusive return _exclusive() def expand(mapper: Mapper) -> Callable[[Observable], Observable]: """Expands an observable sequence by recursively invoking mapper. Args: mapper: Mapper function to invoke for each produced element, resulting in another sequence to which the mapper will be invoked recursively again. Returns: An observable sequence containing all the elements produced by the recursive expansion. """ from rx.core.operators.expand import _expand return _expand(mapper) def filter(predicate: Predicate) -> Callable[[Observable], Observable]: """Filters the elements of an observable sequence based on a predicate. .. marble:: :alt: filter ----1---2---3---4---| [ filter(i: i>2) ] ------------3---4---| Example: >>> op = filter(lambda value: value < 10) Args: predicate: A function to test each source element for a condition. Returns: An operator function that takes an observable source and returns an observable sequence that contains elements from the input sequence that satisfy the condition. """ from rx.core.operators.filter import _filter return _filter(predicate) def filter_indexed(predicate_indexed: Optional[PredicateIndexed] = None) -> Callable[[Observable], Observable]: """Filters the elements of an observable sequence based on a predicate by incorporating the element's index. .. marble:: :alt: filter_indexed ----1---2---3---4---| [ filter(i,id: id>2)] ----------------4---| Example: >>> op = filter_indexed(lambda value, index: (value + index) < 10) Args: predicate: A function to test each source element for a condition; the second parameter of the function represents the index of the source element. Returns: An operator function that takes an observable source and returns an observable sequence that contains elements from the input sequence that satisfy the condition. """ from rx.core.operators.filter import _filter_indexed return _filter_indexed(predicate_indexed) def finally_action(action: Callable) -> Callable[[Observable], Observable]: """Invokes a specified action after the source observable sequence terminates gracefully or exceptionally. .. marble:: :alt: finally_action --1--2--3--4--| a-6-7-| [finally_action(a)] --1--2--3--4--6-7-| Example: >>> res = finally_action(lambda: print('sequence ended') Args: action: Action to invoke after the source observable sequence terminates. Returns: An operator function that takes an observable source and returns an observable sequence with the action-invoking termination behavior applied. """ from rx.core.operators.finallyaction import _finally_action return _finally_action(action) def find(predicate: Predicate) -> Callable[[Observable], Observable]: """Searches for an element that matches the conditions defined by the specified predicate, and returns the first occurrence within the entire Observable sequence. .. marble:: :alt: find --1--2--3--4--3--2--| [ find(3) ] --------3-| Args: predicate: The predicate that defines the conditions of the element to search for. Returns: An operator function that takes an observable source and returns an observable sequence with the first element that matches the conditions defined by the specified predicate, if found otherwise, None. """ from rx.core.operators.find import _find_value return _find_value(predicate, False) def find_index(predicate: Predicate) -> Callable[[Observable], Observable]: """Searches for an element that matches the conditions defined by the specified predicate, and returns an Observable sequence with the zero-based index of the first occurrence within the entire Observable sequence. .. marble:: :alt: find_index --1--2--3--4--3--2--| [ find_index(3) ] --------2-| Args: predicate: The predicate that defines the conditions of the element to search for. Returns: An operator function that takes an observable source and returns an observable sequence with the zero-based index of the first occurrence of an element that matches the conditions defined by match, if found; otherwise, -1. """ from rx.core.operators.find import _find_value return _find_value(predicate, True) def first(predicate: Optional[Predicate] = None) -> Callable[[Observable], Observable]: """Returns the first element of an observable sequence that satisfies the condition in the predicate if present else the first item in the sequence. .. marble:: :alt: first ---1---2---3---4----| [ first(i: i>1) ] -------2-| Examples: >>> res = res = first() >>> res = res = first(lambda x: x > 3) Args: predicate: [Optional] A predicate function to evaluate for elements in the source sequence. Returns: A function that takes an observable source and returns an observable sequence containing the first element in the observable sequence that satisfies the condition in the predicate if provided, else the first item in the sequence. """ from rx.core.operators.first import _first return _first(predicate) def first_or_default(predicate: Optional[Predicate] = None, default_value: Any = None ) -> Callable[[Observable], Observable]: """Returns the first element of an observable sequence that satisfies the condition in the predicate, or a default value if no such element exists. .. marble:: :alt: first_or_default --1--2--3--4-| [first(i: i>10, 42)] -------------42-| Examples: >>> res = first_or_default() >>> res = first_or_default(lambda x: x > 3) >>> res = first_or_default(lambda x: x > 3, 0) >>> res = first_or_default(None, 0) Args: predicate: [optional] A predicate function to evaluate for elements in the source sequence. default_value: [Optional] The default value if no such element exists. If not specified, defaults to None. Returns: A function that takes an observable source and returns an observable sequence containing the first element in the observable sequence that satisfies the condition in the predicate, or a default value if no such element exists. """ from rx.core.operators.firstordefault import _first_or_default return _first_or_default(predicate, default_value) def flat_map(mapper: Optional[Mapper] = None) -> Callable[[Observable], Observable]: """The flat_map operator. .. marble:: :alt: flat_map --1-2-3-| [ flat_map(range) ] --0-0-1-0-1-2-| One of the Following: Projects each element of an observable sequence to an observable sequence and merges the resulting observable sequences into one observable sequence. Example: >>> flat_map(lambda x: Observable.range(0, x)) Or: Projects each element of the source observable sequence to the other observable sequence and merges the resulting observable sequences into one observable sequence. Example: >>> flat_map(Observable.of(1, 2, 3)) Args: mapper: A transform function to apply to each element or an observable sequence to project each element from the source sequence onto. Returns: An operator function that takes a source observable and returns an observable sequence whose elements are the result of invoking the one-to-many transform function on each element of the input sequence. """ from rx.core.operators.flatmap import _flat_map return _flat_map(mapper) def flat_map_indexed(mapper_indexed: Optional[MapperIndexed] = None) -> Callable[[Observable], Observable]: """The `flat_map_indexed` operator. One of the Following: Projects each element of an observable sequence to an observable sequence and merges the resulting observable sequences into one observable sequence. .. marble:: :alt: flat_map_indexed --1-2-3-| [ flat_map(range) ] --0-0-1-0-1-2-| Example: >>> source.flat_map_indexed(lambda x, i: Observable.range(0, x)) Or: Projects each element of the source observable sequence to the other observable sequence and merges the resulting observable sequences into one observable sequence. Example: >>> source.flat_map_indexed(Observable.of(1, 2, 3)) Args: mapper_indexed: [Optional] A transform function to apply to each element or an observable sequence to project each element from the source sequence onto. Returns: An operator function that takes an observable source and returns an observable sequence whose elements are the result of invoking the one-to-many transform function on each element of the input sequence. """ from rx.core.operators.flatmap import _flat_map_indexed return _flat_map_indexed(mapper_indexed) def flat_map_latest(mapper: Mapper) -> Callable[[Observable], Observable]: """Projects each element of an observable sequence into a new sequence of observable sequences by incorporating the element's index and then transforms an observable sequence of observable sequences into an observable sequence producing values only from the most recent observable sequence. Args: mapper: A transform function to apply to each source element. The second parameter of the function represents the index of the source element. Returns: An operator function that takes an observable source and returns an observable sequence whose elements are the result of invoking the transform function on each element of source producing an observable of Observable sequences and that at any point in time produces the elements of the most recent inner observable sequence that has been received. """ from rx.core.operators.flatmap import _flat_map_latest return _flat_map_latest(mapper) def fork_join(*others: Observable) -> Callable[[Observable], Observable]: """Wait for observables to complete and then combine last values they emitted into a tuple. Whenever any of that observables completes without emitting any value, result sequence will complete at that moment as well. .. marble:: :alt: fork_join ---a-----b--c---d-| --1---2------3-4---| -a---------b---| [ fork_join() ] --------------------d4b| Examples: >>> res = fork_join(obs1) >>> res = fork_join(obs1, obs2, obs3) Returns: An operator function that takes an observable source and return an observable sequence containing the result of combining last element from each source in given sequence. """ from rx.core.operators.forkjoin import _fork_join return _fork_join(*others) def group_by(key_mapper: Mapper, element_mapper: Optional[Mapper] = None, subject_mapper: Optional[Callable[[], Subject]] = None, ) -> Callable[[Observable], Observable]: """Groups the elements of an observable sequence according to a specified key mapper function and comparer and selects the resulting elements by using a specified function. .. marble:: :alt: group_by --1--2--a--3--b--c-| [ group_by() ] -+-----+-----------| +a-----b--c-| +1--2-----3-------| Examples: >>> group_by(lambda x: x.id) >>> group_by(lambda x: x.id, lambda x: x.name) >>> group_by(lambda x: x.id, lambda x: x.name, lambda: ReplaySubject()) Keyword arguments: key_mapper: A function to extract the key for each element. element_mapper: [Optional] A function to map each source element to an element in an observable group. subject_mapper: A function that returns a subject used to initiate a grouped observable. Default mapper returns a Subject object. Returns: An operator function that takes an observable source and returns a sequence of observable groups, each of which corresponds to a unique key value, containing all elements that share that same key value. """ from rx.core.operators.groupby import _group_by return _group_by(key_mapper, element_mapper, subject_mapper) def group_by_until(key_mapper: Mapper, element_mapper: Optional[Mapper], duration_mapper: Callable[[GroupedObservable], Observable], subject_mapper: Optional[Callable[[], Subject]] = None, ) -> Callable[[Observable], Observable]: """Groups the elements of an observable sequence according to a specified key mapper function. A duration mapper function is used to control the lifetime of groups. When a group expires, it receives an OnCompleted notification. When a new element with the same key value as a reclaimed group occurs, the group will be reborn with a new lifetime request. .. marble:: :alt: group_by_until --1--2--a--3--b--c-| [ group_by_until() ] -+-----+-----------| +a-----b--c-| +1--2-----3-------| Examples: >>> group_by_until(lambda x: x.id, None, lambda : rx.never()) >>> group_by_until(lambda x: x.id, lambda x: x.name, lambda grp: rx.never()) >>> group_by_until(lambda x: x.id, lambda x: x.name, lambda grp: rx.never(), lambda: ReplaySubject()) Args: key_mapper: A function to extract the key for each element. element_mapper: A function to map each source element to an element in an observable group. duration_mapper: A function to signal the expiration of a group. subject_mapper: A function that returns a subject used to initiate a grouped observable. Default mapper returns a Subject object. Returns: An operator function that takes an observable source and returns a sequence of observable groups, each of which corresponds to a unique key value, containing all elements that share that same key value. If a group's lifetime expires, a new group with the same key value can be created once an element with such a key value is encountered. """ from rx.core.operators.groupbyuntil import _group_by_until return _group_by_until(key_mapper, element_mapper, duration_mapper, subject_mapper) def group_join(right: Observable, left_duration_mapper: Callable[[Any], Observable], right_duration_mapper: Callable[[Any], Observable] ) -> Callable[[Observable], Observable]: """Correlates the elements of two sequences based on overlapping durations, and groups the results. .. marble:: :alt: group_join -1---2----3---4----> --a--------b-----c-> [ group_join() ] --a1-a2----b3-b4-c4| Args: right: The right observable sequence to join elements for. left_duration_mapper: A function to select the duration (expressed as an observable sequence) of each element of the left observable sequence, used to determine overlap. right_duration_mapper: A function to select the duration (expressed as an observable sequence) of each element of the right observable sequence, used to determine overlap. Returns: An operator function that takes an observable source and returns an observable sequence that contains elements combined into a tuple from source elements that have an overlapping duration. """ from rx.core.operators.groupjoin import _group_join return _group_join(right, left_duration_mapper, right_duration_mapper) def ignore_elements() -> Callable[[Observable], Observable]: """Ignores all elements in an observable sequence leaving only the termination messages. .. marble:: :alt: ignore_elements ---1---2---3---4---| [ ignore_elements()] -------------------| Returns: An operator function that takes an observable source and returns an empty observable sequence that signals termination, successful or exceptional, of the source sequence. """ from rx.core.operators.ignoreelements import _ignore_elements return _ignore_elements() def is_empty() -> Callable[[Observable], Observable]: """Determines whether an observable sequence is empty. .. marble:: :alt: is_empty -------| [ is_empty() ] -------True-| Returns: An operator function that takes an observable source and returns an observable sequence containing a single element determining whether the source sequence is empty. """ from rx.core.operators.isempty import _is_empty return _is_empty() def join(right: Observable, left_duration_mapper: Callable[[Any], Observable], right_duration_mapper: Callable[[Any], Observable] ) -> Callable[[Observable], Observable]: """Correlates the elements of two sequences based on overlapping durations. .. marble:: :alt: join -1---2----3---4----> --a--------b-----c-> [ join() ] --a1-a2----b3-b4-c4| Args: right: The right observable sequence to join elements for. left_duration_mapper: A function to select the duration (expressed as an observable sequence) of each element of the left observable sequence, used to determine overlap. right_duration_mapper: A function to select the duration (expressed as an observable sequence) of each element of the right observable sequence, used to determine overlap. Return: An operator function that takes an observable source and returns an observable sequence that contains elements combined into a tuple from source elements that have an overlapping duration. """ from rx.core.operators.join import _join return _join(right, left_duration_mapper, right_duration_mapper) def last(predicate: Optional[Predicate] = None) -> Callable[[Observable], Observable]: """The last operator. Returns the last element of an observable sequence that satisfies the condition in the predicate if specified, else the last element. .. marble:: :alt: last ---1--2--3--4-| [ last() ] ------------4-| Examples: >>> op = last() >>> op = last(lambda x: x > 3) Args: predicate: [Optional] A predicate function to evaluate for elements in the source sequence. Returns: An operator function that takes an observable source and returns an observable sequence containing the last element in the observable sequence that satisfies the condition in the predicate. """ from rx.core.operators.last import _last return _last(predicate) def last_or_default(predicate: Optional[Predicate] = None, default_value: Any = None ) -> Callable[[Observable], Observable]: """The last_or_default operator. Returns the last element of an observable sequence that satisfies the condition in the predicate, or a default value if no such element exists. .. marble:: :alt: last ---1--2--3--4-| [last_or_default(8)] --------------8-| Examples: >>> res = last_or_default() >>> res = last_or_default(lambda x: x > 3) >>> res = last_or_default(lambda x: x > 3, 0) >>> res = last_or_default(None, 0) Args: predicate: [Optional] A predicate function to evaluate for elements in the source sequence. default_value: [Optional] The default value if no such element exists. If not specified, defaults to None. Returns: An operator function that takes an observable source and returns an observable sequence containing the last element in the observable sequence that satisfies the condition in the predicate, or a default value if no such element exists. """ from rx.core.operators.lastordefault import _last_or_default return _last_or_default(predicate, default_value) def map(mapper: Optional[Mapper] = None) -> Callable[[Observable], Observable]: """The map operator. Project each element of an observable sequence into a new form. .. marble:: :alt: map ---1---2---3---4---> [ map(i: i*2) ] ---2---4---6---8---> Example: >>> map(lambda value: value * 10) Args: mapper: A transform function to apply to each source element. Returns: A partially applied operator function that takes an observable source and returns an observable sequence whose elements are the result of invoking the transform function on each element of the source. """ from rx.core.operators.map import _map return _map(mapper) def map_indexed(mapper_indexed: Optional[MapperIndexed] = None) -> Callable[[Observable], Observable]: """Project each element of an observable sequence into a new form by incorporating the element's index. .. marble:: :alt: map_indexed ---1---2---3---4---> [ map(i,id: i*2) ] ---2---4---6---8---> Example: >>> ret = map_indexed(lambda value, index: value * value + index) Args: mapper_indexed: A transform function to apply to each source element. The second parameter of the function represents the index of the source element. Returns: A partially applied operator function that takes an observable source and returns an observable sequence whose elements are the result of invoking the transform function on each element of the source. """ from rx.core.operators.map import _map_indexed return _map_indexed(mapper_indexed) def materialize() -> Callable[[Observable], Observable]: """Materializes the implicit notifications of an observable sequence as explicit notification values. Returns: An operator function that takes an observable source and returns an observable sequence containing the materialized notification values from the source sequence. """ from rx.core.operators.materialize import _materialize return _materialize() def max(comparer: Optional[Comparer] = None) -> Callable[[Observable], Observable]: """Returns the maximum value in an observable sequence according to the specified comparer. .. marble:: :alt: max ---1--2--3--4-| [ max() ] --------------4-| Examples: >>> op = max() >>> op = max(lambda x, y: x.value - y.value) Args: comparer: [Optional] Comparer used to compare elements. Returns: A partially applied operator function that takes an observable source and returns an observable sequence containing a single element with the maximum element in the source sequence. """ from rx.core.operators.max import _max return _max(comparer) def max_by(key_mapper: Mapper, comparer: Optional[Comparer] = None ) -> Callable[[Observable], Observable]: """The max_by operator. Returns the elements in an observable sequence with the maximum key value according to the specified comparer. .. marble:: :alt: max_by ---1--2--3--4-| [ max_by() ] --------------4-| Examples: >>> res = max_by(lambda x: x.value) >>> res = max_by(lambda x: x.value, lambda x, y: x - y) Args: key_mapper: Key mapper function. comparer: [Optional] Comparer used to compare key values. Returns: A partially applied operator function that takes an observable source and return an observable sequence containing a list of zero or more elements that have a maximum key value. """ from rx.core.operators.maxby import _max_by return _max_by(key_mapper, comparer) def merge(*sources: Observable, max_concurrent: Optional[int] = None ) -> Callable[[Observable], Observable]: """Merges an observable sequence of observable sequences into an observable sequence, limiting the number of concurrent subscriptions to inner sequences. Or merges two observable sequences into a single observable sequence. .. marble:: :alt: merge ---1---2---3---4-| -a---b---c---d--| [ merge() ] -a-1-b-2-c-3-d-4-| Examples: >>> op = merge(max_concurrent=1) >>> op = merge(other_source) Args: max_concurrent: [Optional] Maximum number of inner observable sequences being subscribed to concurrently or the second observable sequence. Returns: An operator function that takes an observable source and returns the observable sequence that merges the elements of the inner sequences. """ from rx.core.operators.merge import _merge return _merge(*sources, max_concurrent=max_concurrent) def merge_all() -> Callable[[Observable], Observable]: """The merge_all operator. Merges an observable sequence of observable sequences into an observable sequence. .. marble:: :alt: merge_all ---1---2---3---4-| -a---b---c---d--| [ merge_all() ] -a-1-b-2-c-3-d-4-| Returns: A partially applied operator function that takes an observable source and returns the observable sequence that merges the elements of the inner sequences. """ from rx.core.operators.merge import _merge_all return _merge_all() def min(comparer: Optional[Comparer] = None) -> Callable[[Observable], Observable]: """The `min` operator. Returns the minimum element in an observable sequence according to the optional comparer else a default greater than less than check. .. marble:: :alt: min ---1--2--3--4-| [ min() ] --------------1-| Examples: >>> res = source.min() >>> res = source.min(lambda x, y: x.value - y.value) Args: comparer: [Optional] Comparer used to compare elements. Returns: An operator function that takes an observable source and returns an observable sequence containing a single element with the minimum element in the source sequence. """ from rx.core.operators.min import _min return _min(comparer) def min_by(key_mapper: Mapper, comparer: Optional[Comparer] = None ) -> Callable[[Observable], Observable]: """The `min_by` operator. Returns the elements in an observable sequence with the minimum key value according to the specified comparer. .. marble:: :alt: min_by ---1--2--3--4-| [ min_by() ] --------------1-| Examples: >>> res = min_by(lambda x: x.value) >>> res = min_by(lambda x: x.value, lambda x, y: x - y) Args: key_mapper: Key mapper function. comparer: [Optional] Comparer used to compare key values. Returns: An operator function that takes an observable source and reuturns an observable sequence containing a list of zero or more elements that have a minimum key value. """ from rx.core.operators.minby import _min_by return _min_by(key_mapper, comparer) def multicast(subject: Optional[typing.Subject] = None, subject_factory: Optional[Callable[[Optional[typing.Scheduler]], typing.Subject]] = None, mapper: Optional[Callable[[ConnectableObservable], Observable]] = None ) -> Callable[[Observable], Union[Observable, ConnectableObservable]]: """Multicasts the source sequence notifications through an instantiated subject into all uses of the sequence within a mapper function. Each subscription to the resulting sequence causes a separate multicast invocation, exposing the sequence resulting from the mapper function's invocation. For specializations with fixed subject types, see Publish, PublishLast, and Replay. Examples: >>> res = multicast(observable) >>> res = multicast(subject_factory=lambda scheduler: Subject(), mapper=lambda x: x) Args: subject_factory: Factory function to create an intermediate subject through which the source sequence's elements will be multicast to the mapper function. subject: Subject to push source elements into. mapper: [Optional] Mapper function which can use the multicasted source sequence subject to the policies enforced by the created subject. Specified only if subject_factory" is a factory function. Returns: An operator function that takes an observable source and returns an observable sequence that contains the elements of a sequence produced by multicasting the source sequence within a mapper function. """ from rx.core.operators.multicast import _multicast return _multicast(subject, subject_factory, mapper) def observe_on(scheduler: typing.Scheduler) -> Callable[[Observable], Observable]: """Wraps the source sequence in order to run its observer callbacks on the specified scheduler. Args: scheduler: Scheduler to notify observers on. This only invokes observer callbacks on a scheduler. In case the subscription and/or unsubscription actions have side-effects that require to be run on a scheduler, use subscribe_on. Returns: An operator function that takes an observable source and returns the source sequence whose observations happen on the specified scheduler. """ from rx.core.operators.observeon import _observe_on return _observe_on(scheduler) def on_error_resume_next(second: Observable) -> Callable[[Observable], Observable]: """Continues an observable sequence that is terminated normally or by an exception with the next observable sequence. .. marble:: :alt: on_error ---1--2--3--4-* e-a--b-| [ on_error(e) ] -1--2--3--4-a--b-| Keyword arguments: second: Second observable sequence used to produce results after the first sequence terminates. Returns: An observable sequence that concatenates the first and second sequence, even if the first sequence terminates exceptionally. """ from rx.core.operators.onerrorresumenext import _on_error_resume_next return _on_error_resume_next(second) def pairwise() -> Callable[[Observable], Observable]: """The pairwise operator. Returns a new observable that triggers on the second and subsequent triggerings of the input observable. The Nth triggering of the input observable passes the arguments from the N-1th and Nth triggering as a pair. The argument passed to the N-1th triggering is held in hidden internal state until the Nth triggering occurs. Returns: An operator function that takes an observable source and returns an observable that triggers on successive pairs of observations from the input observable as an array. """ from rx.core.operators.pairwise import _pairwise return _pairwise() def partition(predicate: Predicate) -> Callable[[Observable], List[Observable]]: """Returns two observables which partition the observations of the source by the given function. The first will trigger observations for those values for which the predicate returns true. The second will trigger observations for those values where the predicate returns false. The predicate is executed once for each subscribed observer. Both also propagate all error observations arising from the source and each completes when the source completes. .. marble:: :alt: partition ---1--2--3--4--| [ partition(even) ] ---1-----3-----| ------2-----4--| Args: predicate: The function to determine which output Observable will trigger a particular observation. Returns: An operator function that takes an observable source and returns a list of observables. The first triggers when the predicate returns True, and the second triggers when the predicate returns False. """ from rx.core.operators.partition import _partition return _partition(predicate) def partition_indexed(predicate_indexed: PredicateIndexed) -> Callable[[Observable], List[Observable]]: """The indexed partition operator. Returns two observables which partition the observations of the source by the given function. The first will trigger observations for those values for which the predicate returns true. The second will trigger observations for those values where the predicate returns false. The predicate is executed once for each subscribed observer. Both also propagate all error observations arising from the source and each completes when the source completes. .. marble:: :alt: partition_indexed ---1--2--3--4--| [ partition(even) ] ---1-----3-----| ------2-----4--| Args: predicate: The function to determine which output Observable will trigger a particular observation. Returns: A list of observables. The first triggers when the predicate returns True, and the second triggers when the predicate returns False. """ from rx.core.operators.partition import _partition_indexed return _partition_indexed(predicate_indexed) def pluck(key: Any) -> Callable[[Observable], Observable]: """Retrieves the value of a specified key using dict-like access (as in element[key]) from all elements in the Observable sequence. To pluck an attribute of each element, use pluck_attr. Args: key: The key to pluck. Returns: An operator function that takes an observable source and returns a new observable sequence of key values. """ from rx.core.operators.pluck import _pluck return _pluck(key) def pluck_attr(prop: str) -> Callable[[Observable], Observable]: """Retrieves the value of a specified property (using getattr) from all elements in the Observable sequence. To pluck values using dict-like access (as in element[key]) on each element, use pluck. Args: property: The property to pluck. Returns: An operator function that takes an observable source and returns a new observable sequence of property values. """ from rx.core.operators.pluck import _pluck_attr return _pluck_attr(prop) def publish(mapper: Optional[Mapper] = None) -> Callable[[Observable], ConnectableObservable]: """The `publish` operator. Returns an observable sequence that is the result of invoking the mapper on a connectable observable sequence that shares a single subscription to the underlying sequence. This operator is a specialization of Multicast using a regular Subject. Example: >>> res = publish() >>> res = publish(lambda x: x) Args: mapper: [Optional] Selector function which can use the multicasted source sequence as many times as needed, without causing multiple subscriptions to the source sequence. Subscribers to the given source will receive all notifications of the source from the time of the subscription on. Returns: An operator function that takes an observable source and returns an observable sequence that contains the elements of a sequence produced by multicasting the source sequence within a mapper function. """ from rx.core.operators.publish import _publish return _publish(mapper) def publish_value(initial_value: Any, mapper: Optional[Mapper] = None) -> Callable[[Observable], Observable]: """Returns an observable sequence that is the result of invoking the mapper on a connectable observable sequence that shares a single subscription to the underlying sequence and starts with initial_value. This operator is a specialization of Multicast using a BehaviorSubject. Examples: >>> res = source.publish_value(42) >>> res = source.publish_value(42, lambda x: x.map(lambda y: y * y)) Args: initial_value: Initial value received by observers upon subscription. mapper: [Optional] Optional mapper function which can use the multicasted source sequence as many times as needed, without causing multiple subscriptions to the source sequence. Subscribers to the given source will receive immediately receive the initial value, followed by all notifications of the source from the time of the subscription on. Returns: An operator function that takes an observable source and returns an observable sequence that contains the elements of a sequence produced by multicasting the source sequence within a mapper function. """ from rx.core.operators.publishvalue import _publish_value return _publish_value(initial_value, mapper) def reduce(accumulator: Accumulator, seed: Any = NotSet) -> Callable[[Observable], Observable]: """The reduce operator. Applies an accumulator function over an observable sequence, returning the result of the aggregation as a single element in the result sequence. The specified seed value is used as the initial accumulator value. For aggregation behavior with incremental intermediate results, see `scan`. .. marble:: :alt: reduce ---1--2--3--4--| [reduce(acc,i: acc+i)] ---------------10-| Examples: >>> res = reduce(lambda acc, x: acc + x) >>> res = reduce(lambda acc, x: acc + x, 0) Args: accumulator: An accumulator function to be invoked on each element. seed: Optional initial accumulator value. Returns: A partially applied operator function that takes an observable source and returns an observable sequence containing a single element with the final accumulator value. """ from rx.core.operators.reduce import _reduce return _reduce(accumulator, seed) def ref_count() -> Callable[[ConnectableObservable], Observable]: """Returns an observable sequence that stays connected to the source as long as there is at least one subscription to the observable sequence. """ from rx.core.operators.connectable.refcount import _ref_count return _ref_count() def repeat(repeat_count: Optional[int] = None) -> Callable[[Observable], Observable]: """Repeats the observable sequence a specified number of times. If the repeat count is not specified, the sequence repeats indefinitely. .. marble:: :alt: repeat -1--2-| [ repeat(3) ] -1--2--1--2--1--2-| Examples: >>> repeated = repeat() >>> repeated = repeat(42) Args: repeat_count: Number of times to repeat the sequence. If not provided, repeats the sequence indefinitely. Returns: An operator function that takes an observable sources and returns an observable sequence producing the elements of the given sequence repeatedly. """ from rx.core.operators.repeat import _repeat return _repeat(repeat_count) def replay(mapper: Optional[Mapper] = None, buffer_size: Optional[int] = None, window: Optional[typing.RelativeTime] = None, scheduler: Optional[typing.Scheduler] = None ) -> Callable[[Observable], Union[Observable, ConnectableObservable]]: """The `replay` operator. Returns an observable sequence that is the result of invoking the mapper on a connectable observable sequence that shares a single subscription to the underlying sequence replaying notifications subject to a maximum time length for the replay buffer. This operator is a specialization of Multicast using a ReplaySubject. Examples: >>> res = replay(buffer_size=3) >>> res = replay(buffer_size=3, window=0.5) >>> res = replay(None, 3, 0.5) >>> res = replay(lambda x: x.take(6).repeat(), 3, 0.5) Args: mapper: [Optional] Selector function which can use the multicasted source sequence as many times as needed, without causing multiple subscriptions to the source sequence. Subscribers to the given source will receive all the notifications of the source subject to the specified replay buffer trimming policy. buffer_size: [Optional] Maximum element count of the replay buffer. window: [Optional] Maximum time length of the replay buffer. scheduler: [Optional] Scheduler the observers are invoked on. Returns: An operator function that takes an observable source and returns an observable sequence that contains the elements of a sequence produced by multicasting the source sequence within a mapper function. """ from rx.core.operators.replay import _replay return _replay(mapper, buffer_size, window, scheduler=scheduler) def retry(retry_count: Optional[int] = None) -> Callable[[Observable], Observable]: """Repeats the source observable sequence the specified number of times or until it successfully terminates. If the retry count is not specified, it retries indefinitely. Examples: >>> retried = retry() >>> retried = retry(42) Args: retry_count: [Optional] Number of times to retry the sequence. If not provided, retry the sequence indefinitely. Returns: An observable sequence producing the elements of the given sequence repeatedly until it terminates successfully. """ from rx.core.operators.retry import _retry return _retry(retry_count) def sample(sampler: Union[typing.RelativeTime, Observable], scheduler: Optional[typing.Scheduler] = None ) -> Callable[[Observable], Observable]: """Samples the observable sequence at each interval. .. marble:: :alt: sample ---1-2-3-4------| [ sample(4) ] ----1---3---4---| Examples: >>> res = sample(sample_observable) # Sampler tick sequence >>> res = sample(5.0) # 5 seconds Args: sampler: Observable used to sample the source observable **or** time interval at which to sample (specified as a float denoting seconds or an instance of timedelta). scheduler: Scheduler to use only when a time interval is given. Returns: An operator function that takes an observable source and returns a sampled observable sequence. """ from rx.core.operators.sample import _sample return _sample(sampler, scheduler) def scan(accumulator: Accumulator, seed: Any = NotSet) -> Callable[[Observable], Observable]: """The scan operator. Applies an accumulator function over an observable sequence and returns each intermediate result. The optional seed value is used as the initial accumulator value. For aggregation behavior with no intermediate results, see `aggregate()` or `Observable()`. .. marble:: :alt: scan ----1--2--3--4-----| [scan(acc,i: acc+i)] ----1--3--6--10----| Examples: >>> scanned = source.scan(lambda acc, x: acc + x) >>> scanned = source.scan(lambda acc, x: acc + x, 0) Args: accumulator: An accumulator function to be invoked on each element. seed: [Optional] The initial accumulator value. Returns: A partially applied operator function that takes an observable source and returns an observable sequence containing the accumulated values. """ from rx.core.operators.scan import _scan return _scan(accumulator, seed) def sequence_equal(second: Observable, comparer: Optional[Comparer] = None ) -> Callable[[Observable], Observable]: """Determines whether two sequences are equal by comparing the elements pairwise using a specified equality comparer. .. marble:: :alt: scan -1--2--3--4----| ----1--2--3--4-| [ sequence_equal() ] ---------------True| Examples: >>> res = sequence_equal([1,2,3]) >>> res = sequence_equal([{ "value": 42 }], lambda x, y: x.value == y.value) >>> res = sequence_equal(rx.return_value(42)) >>> res = sequence_equal(rx.return_value({ "value": 42 }), lambda x, y: x.value == y.value) Args: second: Second observable sequence or array to compare. comparer: [Optional] Comparer used to compare elements of both sequences. No guarantees on order of comparer arguments. Returns: An operator function that takes an observable source and returns an observable sequence that contains a single element which indicates whether both sequences are of equal length and their corresponding elements are equal according to the specified equality comparer. """ from rx.core.operators.sequenceequal import _sequence_equal return _sequence_equal(second, comparer) def share() -> Callable[[Observable], Observable]: """Share a single subscription among multiple observers. This is an alias for a composed publish() and ref_count(). Returns: An operator function that takes an observable source and returns a new Observable that multicasts (shares) the original Observable. As long as there is at least one Subscriber this Observable will be subscribed and emitting data. When all subscribers have unsubscribed it will unsubscribe from the source Observable. """ from rx.core.operators.publish import _share return _share() def single(predicate: Optional[Predicate] = None) -> Callable[[Observable], Observable]: """The single operator. Returns the only element of an observable sequence that satisfies the condition in the optional predicate, and reports an exception if there is not exactly one element in the observable sequence. .. marble:: :alt: single ----1--2--3--4-----| [ single(3) ] ----------3--------| Example: >>> res = single() >>> res = single(lambda x: x == 42) Args: predicate: [Optional] A predicate function to evaluate for elements in the source sequence. Returns: An operator function that takes an observable source and returns an observable sequence containing the single element in the observable sequence that satisfies the condition in the predicate. """ from rx.core.operators.single import _single return _single(predicate) def single_or_default(predicate: Optional[Predicate] = None, default_value: Any = None ) -> Callable[[Observable], Observable]: """Returns the only element of an observable sequence that matches the predicate, or a default value if no such element exists this method reports an exception if there is more than one element in the observable sequence. .. marble:: :alt: single_or_default ----1--2--3--4--| [ single(8,42) ] ----------------42-| Examples: >>> res = single_or_default() >>> res = single_or_default(lambda x: x == 42) >>> res = single_or_default(lambda x: x == 42, 0) >>> res = single_or_default(None, 0) Args: predicate: [Optional] A predicate function to evaluate for elements in the source sequence. default_value: [Optional] The default value if the index is outside the bounds of the source sequence. Returns: An operator function that takes an observable source and returns an observable sequence containing the single element in the observable sequence that satisfies the condition in the predicate, or a default value if no such element exists. """ from rx.core.operators.singleordefault import _single_or_default return _single_or_default(predicate, default_value) def single_or_default_async(has_default: bool = False, default_value: Any = None ) -> Callable[[Observable], Observable]: from rx.core.operators.singleordefault import _single_or_default_async return _single_or_default_async(has_default, default_value) def skip(count: int) -> Callable[[Observable], Observable]: """The skip operator. Bypasses a specified number of elements in an observable sequence and then returns the remaining elements. .. marble:: :alt: skip ----1--2--3--4-----| [ skip(2) ] ----------3--4-----| Args: count: The number of elements to skip before returning the remaining elements. Returns: An operator function that takes an observable source and returns an observable sequence that contains the elements that occur after the specified index in the input sequence. """ from rx.core.operators.skip import _skip return _skip(count) def skip_last(count: int) -> Callable[[Observable], Observable]: """The skip_last operator. .. marble:: :alt: skip_last ----1--2--3--4-----| [ skip_last(1) ] -------1--2--3-----| Bypasses a specified number of elements at the end of an observable sequence. This operator accumulates a queue with a length enough to store the first `count` elements. As more elements are received, elements are taken from the front of the queue and produced on the result sequence. This causes elements to be delayed. Args: count: Number of elements to bypass at the end of the source sequence. Returns: An operator function that takes an observable source and returns an observable sequence containing the source sequence elements except for the bypassed ones at the end. """ from rx.core.operators.skiplast import _skip_last return _skip_last(count) def skip_last_with_time(duration: typing.RelativeTime, scheduler: typing.Scheduler = None ) -> Callable[[Observable], Observable]: """Skips elements for the specified duration from the end of the observable source sequence. Example: >>> res = skip_last_with_time(5.0) This operator accumulates a queue with a length enough to store elements received during the initial duration window. As more elements are received, elements older than the specified duration are taken from the queue and produced on the result sequence. This causes elements to be delayed with duration. Args: duration: Duration for skipping elements from the end of the sequence. scheduler: Scheduler to use for time handling. Returns: An observable sequence with the elements skipped during the specified duration from the end of the source sequence. """ from rx.core.operators.skiplastwithtime import _skip_last_with_time return _skip_last_with_time(duration, scheduler=scheduler) def skip_until(other: Observable) -> Callable[[Observable], Observable]: """Returns the values from the source observable sequence only after the other observable sequence produces a value. .. marble:: :alt: skip_until ----1--2--3--4-----| ---------1---------| [ skip_until() ] ----------3--4-----| Args: other: The observable sequence that triggers propagation of elements of the source sequence. Returns: An operator function that takes an observable source and returns an observable sequence containing the elements of the source sequence starting from the point the other sequence triggered propagation. """ from rx.core.operators.skipuntil import _skip_until return _skip_until(other) def skip_until_with_time(start_time: typing.AbsoluteOrRelativeTime, scheduler: Optional[typing.Scheduler] = None ) -> Callable[[Observable], Observable]: """Skips elements from the observable source sequence until the specified start time. Errors produced by the source sequence are always forwarded to the result sequence, even if the error occurs before the start time. .. marble:: :alt: skip_until ------1--2--3--4-------| [skip_until_with_time()] ------------3--4-------| Examples: >>> res = skip_until_with_time(datetime()) >>> res = skip_until_with_time(5.0) Args: start_time: Time to start taking elements from the source sequence. If this value is less than or equal to `datetime.utcnow()`, no elements will be skipped. Returns: An operator function that takes an observable source and returns an observable sequence with the elements skipped until the specified start time. """ from rx.core.operators.skipuntilwithtime import _skip_until_with_time return _skip_until_with_time(start_time, scheduler=scheduler) def skip_while(predicate: typing.Predicate) -> Callable[[Observable], Observable]: """The `skip_while` operator. Bypasses elements in an observable sequence as long as a specified condition is true and then returns the remaining elements. The element's index is used in the logic of the predicate function. .. marble:: :alt: skip_while ----1--2--3--4-----| [skip_while(i: i<3)] ----------3--4-----| Example: >>> skip_while(lambda value: value < 10) Args: predicate: A function to test each element for a condition; the second parameter of the function represents the index of the source element. Returns: An operator function that takes an observable source and returns an observable sequence that contains the elements from the input sequence starting at the first element in the linear series that does not pass the test specified by predicate. """ from rx.core.operators.skipwhile import _skip_while return _skip_while(predicate) def skip_while_indexed(predicate: typing.PredicateIndexed) -> Callable[[Observable], Observable]: """Bypasses elements in an observable sequence as long as a specified condition is true and then returns the remaining elements. The element's index is used in the logic of the predicate function. .. marble:: :alt: skip_while_indexed ----1--2--3--4-----| [skip_while(i: i<3)] ----------3--4-----| Example: >>> skip_while(lambda value, index: value < 10 or index < 10) Args: predicate: A function to test each element for a condition; the second parameter of the function represents the index of the source element. Returns: An operator function that takes an observable source and returns an observable sequence that contains the elements from the input sequence starting at the first element in the linear series that does not pass the test specified by predicate. """ from rx.core.operators.skipwhile import _skip_while_indexed return _skip_while_indexed(predicate) def skip_with_time(duration: typing.RelativeTime, scheduler: Optional[typing.Scheduler] = None ) -> Callable[[Observable], Observable]: """Skips elements for the specified duration from the start of the observable source sequence. .. marble:: :alt: skip_with_time ----1--2--3--4-----| [ skip_with_time() ] ----------3--4-----| Args: >>> res = skip_with_time(5.0) Specifying a zero value for duration doesn't guarantee no elements will be dropped from the start of the source sequence. This is a side-effect of the asynchrony introduced by the scheduler, where the action that causes callbacks from the source sequence to be forwarded may not execute immediately, despite the zero due time. Errors produced by the source sequence are always forwarded to the result sequence, even if the error occurs before the duration. Args: duration: Duration for skipping elements from the start of the sequence. Returns: An operator function that takes an observable source and returns an observable sequence with the elements skipped during the specified duration from the start of the source sequence. """ from rx.core.operators.skipwithtime import _skip_with_time return _skip_with_time(duration, scheduler=scheduler) def slice(start: Optional[int] = None, stop: Optional[int] = None, step: Optional[int] = None ) -> Callable[[Observable], Observable]: """The slice operator. Slices the given observable. It is basically a wrapper around the operators :func:`skip `, :func:`skip_last `, :func:`take `, :func:`take_last ` and :func:`filter `. .. marble:: :alt: slice ----1--2--3--4-----| [ slice(1, 2) ] -------2--3--------| Examples: >>> result = source.slice(1, 10) >>> result = source.slice(1, -2) >>> result = source.slice(1, -1, 2) Args: start: First element to take of skip last stop: Last element to take of skip last step: Takes every step element. Must be larger than zero Returns: An operator function that takes an observable source and returns a sliced observable sequence. """ from rx.core.operators.slice import _slice return _slice(start, stop, step) def some(predicate: Optional[Predicate] = None) -> Callable[[Observable], Observable]: """The some operator. Determines whether some element of an observable sequence satisfies a condition if present, else if some items are in the sequence. .. marble:: :alt: some ----1--2--3--4-----| [ some(i: i>3) ] -------------True--| Examples: >>> result = source.some() >>> result = source.some(lambda x: x > 3) Args: predicate: A function to test each element for a condition. Returns: An operator function that takes an observable source and returns an observable sequence containing a single element determining whether some elements in the source sequence pass the test in the specified predicate if given, else if some items are in the sequence. """ from rx.core.operators.some import _some return _some(predicate) def starmap(mapper: Optional[Mapper] = None) -> Callable[[Observable], Observable]: """The starmap operator. Unpack arguments grouped as tuple elements of an observable sequence and return an observable sequence of values by invoking the mapper function with star applied unpacked elements as positional arguments. Use instead of `map()` when the the arguments to the mapper is grouped as tuples and the mapper function takes multiple arguments. .. marble:: :alt: starmap -----1,2---3,4-----| [ starmap(add) ] -----3-----7-------| Example: >>> starmap(lambda x, y: x + y) Args: mapper: A transform function to invoke with unpacked elements as arguments. Returns: An operator function that takes an observable source and returns an observable sequence containing the results of invoking the mapper function with unpacked elements of the source. """ if mapper is None: return pipe() return pipe(map(lambda values: cast(Mapper, mapper)(*values))) def starmap_indexed(mapper: Optional[MapperIndexed] = None ) -> Callable[[Observable], Observable]: """Variant of :func:`starmap` which accepts an indexed mapper. .. marble:: :alt: starmap_indexed ---------1,2---3,4---------| [ starmap_indexed(sum) ] ---------3-----8-----------| Example: >>> starmap_indexed(lambda x, y, i: x + y + i) Args: mapper: A transform function to invoke with unpacked elements as arguments. Returns: An operator function that takes an observable source and returns an observable sequence containing the results of invoking the indexed mapper function with unpacked elements of the source. """ if mapper is None: return pipe() return pipe(map(lambda values: cast(MapperIndexed, mapper)(*values))) def start_with(*args: Any) -> Callable[[Observable], Observable]: """Prepends a sequence of values to an observable sequence. .. marble:: :alt: start_with -----1--2--3--4----| [ start_with(7,8) ] -7-8-1--2--3--4----| Example: >>> start_with(1, 2, 3) Returns: An operator function that takes a source observable and returns the source sequence prepended with the specified values. """ from rx.core.operators.startswith import _start_with return _start_with(*args) def subscribe_on(scheduler: typing.Scheduler) -> Callable[[Observable], Observable]: """Subscribe on the specified scheduler. Wrap the source sequence in order to run its subscription and unsubscription logic on the specified scheduler. This operation is not commonly used; see the remarks section for more information on the distinction between subscribe_on and observe_on. This only performs the side-effects of subscription and unsubscription on the specified scheduler. In order to invoke observer callbacks on a scheduler, use observe_on. Args: scheduler: Scheduler to perform subscription and unsubscription actions on. Returns: An operator function that takes an observable source and returns the source sequence whose subscriptions and un-subscriptions happen on the specified scheduler. """ from rx.core.operators.subscribeon import _subscribe_on return _subscribe_on(scheduler) def sum(key_mapper: Optional[Mapper] = None) -> Callable[[Observable], Observable]: """Computes the sum of a sequence of values that are obtained by invoking an optional transform function on each element of the input sequence, else if not specified computes the sum on each item in the sequence. .. marble:: :alt: sum -----1--2--3--4-| [ sum() ] ----------------10-| Examples: >>> res = sum() >>> res = sum(lambda x: x.value) Args: key_mapper: [Optional] A transform function to apply to each element. Returns: An operator function that takes a source observable and returns an observable sequence containing a single element with the sum of the values in the source sequence. """ from rx.core.operators.sum import _sum return _sum(key_mapper) def switch_latest() -> Callable[[Observable], Observable]: """The switch_latest operator. Transforms an observable sequence of observable sequences into an observable sequence producing values only from the most recent observable sequence. .. marble:: :alt: switch_latest -+------+----------| +--a--b--c-| +--1--2--3--4--| [ switch_latest() ] ----1--2---a--b--c-| Returns: A partially applied operator function that takes an observable source and returns the observable sequence that at any point in time produces the elements of the most recent inner observable sequence that has been received. """ from rx.core.operators.switchlatest import _switch_latest return _switch_latest() def take(count: int) -> Callable[[Observable], Observable]: """Returns a specified number of contiguous elements from the start of an observable sequence. .. marble:: :alt: take -----1--2--3--4----| [ take(2) ] -----1--2-| Example: >>> op = take(5) Args: count: The number of elements to return. Returns: An operator function that takes an observable source and returns an observable sequence that contains the specified number of elements from the start of the input sequence. """ from rx.core.operators.take import _take return _take(count) def take_last(count: int) -> Callable[[Observable], Observable]: """Returns a specified number of contiguous elements from the end of an observable sequence. .. marble:: :alt: take_last -1--2--3--4-| [ take_last(2) ] ------------3--4-| Example: >>> res = take_last(5) This operator accumulates a buffer with a length enough to store elements count elements. Upon completion of the source sequence, this buffer is drained on the result sequence. This causes the elements to be delayed. Args: count: Number of elements to take from the end of the source sequence. Returns: An operator function that takes an observable source and returns an observable sequence containing the specified number of elements from the end of the source sequence. """ from rx.core.operators.takelast import _take_last return _take_last(count) def take_last_buffer(count: int) -> Callable[[Observable], Observable]: """The `take_last_buffer` operator. Returns an array with the specified number of contiguous elements from the end of an observable sequence. .. marble:: :alt: take_last_buffer -----1--2--3--4-| [take_last_buffer(2)] ----------------3,4-| Example: >>> res = source.take_last(5) This operator accumulates a buffer with a length enough to store elements count elements. Upon completion of the source sequence, this buffer is drained on the result sequence. This causes the elements to be delayed. Args: count: Number of elements to take from the end of the source sequence. Returns: An operator function that takes an observable source and returns an observable sequence containing a single list with the specified number of elements from the end of the source sequence. """ from rx.core.operators.takelastbuffer import _take_last_buffer return _take_last_buffer(count) def take_last_with_time(duration: typing.RelativeTime, scheduler: Optional[typing.Scheduler] = None ) -> Callable[[Observable], Observable]: """Returns elements within the specified duration from the end of the observable source sequence. .. marble:: :alt: take_last_with_time -----1--2--3--4-| [take_last_with_time(3)] ----------------4-| Example: >>> res = take_last_with_time(5.0) This operator accumulates a queue with a length enough to store elements received during the initial duration window. As more elements are received, elements older than the specified duration are taken from the queue and produced on the result sequence. This causes elements to be delayed with duration. Args: duration: Duration for taking elements from the end of the sequence. Returns: An operator function that takes an observable source and returns an observable sequence with the elements taken during the specified duration from the end of the source sequence. """ from rx.core.operators.takelastwithtime import _take_last_with_time return _take_last_with_time(duration, scheduler=scheduler) def take_until(other: Observable) -> Callable[[Observable], Observable]: """Returns the values from the source observable sequence until the other observable sequence produces a value. .. marble:: :alt: take_until -----1--2--3--4----| -------------a-| [ take_until(2) ] -----1--2--3-------| Args: other: Observable sequence that terminates propagation of elements of the source sequence. Returns: An operator function that takes an observable source and returns as observable sequence containing the elements of the source sequence up to the point the other sequence interrupted further propagation. """ from rx.core.operators.takeuntil import _take_until return _take_until(other) def take_until_with_time(end_time: typing.AbsoluteOrRelativeTime, scheduler: Optional[typing.Scheduler] = None ) -> Callable[[Observable], Observable]: """Takes elements for the specified duration until the specified end time, using the specified scheduler to run timers. .. marble:: :alt: take_until_with_time -----1--2--3--4--------| [take_until_with_time()] -----1--2--3-----------| Examples: >>> res = take_until_with_time(dt, [optional scheduler]) >>> res = take_until_with_time(5.0, [optional scheduler]) Args: end_time: Time to stop taking elements from the source sequence. If this value is less than or equal to `datetime.utcnow()`, the result stream will complete immediately. scheduler: Scheduler to run the timer on. Returns: An operator function that takes an observable source and returns an observable sequence with the elements taken until the specified end time. """ from rx.core.operators.takeuntilwithtime import _take_until_with_time return _take_until_with_time(end_time, scheduler=scheduler) def take_while(predicate: Predicate, inclusive: bool = False) -> Callable[[Observable], Observable]: """Returns elements from an observable sequence as long as a specified condition is true. The element's index is used in the logic of the predicate function. .. marble:: :alt: take_while -----1--2--3--4----| [take_while(i: i<3)] -----1--2----------| Example: >>> take_while(lambda value: value < 10) Args: predicate: A function to test each element for a condition. inclusive: [Optional] When set to True the value that caused the predicate function to return False will also be emitted. If not specified, defaults to False. Returns: An operator function that takes an observable source and returns an observable sequence that contains the elements from the input sequence that occur before the element at which the test no longer passes. """ from rx.core.operators.takewhile import _take_while return _take_while(predicate, inclusive) def take_while_indexed(predicate: PredicateIndexed, inclusive: bool = False) -> Callable[[Observable], Observable]: """Returns elements from an observable sequence as long as a specified condition is true. The element's index is used in the logic of the predicate function. .. marble:: :alt: take_while_indexed --------1------2------3------4-------| [take_while_indexed(v, i: v<4 or i<3)] --------1------2---------------------| Example: >>> take_while_indexed(lambda value, index: value < 10 or index < 10) Args: predicate: A function to test each element for a condition; the second parameter of the function represents the index of the source element. inclusive: [Optional] When set to True the value that caused the predicate function to return False will also be emitted. If not specified, defaults to False. Returns: An observable sequence that contains the elements from the input sequence that occur before the element at which the test no longer passes. """ from rx.core.operators.takewhile import _take_while_indexed return _take_while_indexed(predicate, inclusive) def take_with_time(duration: typing.RelativeTime, scheduler: Optional[typing.Scheduler] = None ) -> Callable[[Observable], Observable]: """Takes elements for the specified duration from the start of the observable source sequence. .. marble:: :alt: take_with_time -----1--2--3--4----| [ take_with_time() ] -----1--2----------| Example: >>> res = take_with_time(5.0) This operator accumulates a queue with a length enough to store elements received during the initial duration window. As more elements are received, elements older than the specified duration are taken from the queue and produced on the result sequence. This causes elements to be delayed with duration. Args: duration: Duration for taking elements from the start of the sequence. Returns: An operator function that takes an observable source and returns an observable sequence with the elements taken during the specified duration from the start of the source sequence. """ from rx.core.operators.takewithtime import _take_with_time return _take_with_time(duration, scheduler=scheduler) def throttle_first(window_duration: typing.RelativeTime, scheduler: Optional[typing.Scheduler] = None ) -> Callable[[Observable], Observable]: """Returns an Observable that emits only the first item emitted by the source Observable during sequential time windows of a specified duration. Args: window_duration: time to wait before emitting another item after emitting the last item. Returns: An operator function that takes an observable source and returns an observable that performs the throttle operation. """ from rx.core.operators.throttlefirst import _throttle_first return _throttle_first(window_duration, scheduler) def throttle_with_mapper(throttle_duration_mapper: Callable[[Any], Observable]) -> Callable[[Observable], Observable]: """The throttle_with_mapper operator. Ignores values from an observable sequence which are followed by another value within a computed throttle duration. Example: >>> op = throttle_with_mapper(lambda x: rx.Scheduler.timer(x+x)) Args: throttle_duration_mapper: Mapper function to retrieve an observable sequence indicating the throttle duration for each given element. Returns: A partially applied operator function that takes an observable source and returns the throttled observable sequence. """ from rx.core.operators.debounce import _throttle_with_mapper return _throttle_with_mapper(throttle_duration_mapper) def timestamp(scheduler: Optional[typing.Scheduler] = None) -> Callable[[Observable], Observable]: """The timestamp operator. Records the timestamp for each value in an observable sequence. Examples: >>> timestamp() Produces objects with attributes `value` and `timestamp`, where value is the original value. Returns: A partially applied operator function that takes an observable source and returns an observable sequence with timestamp information on values. """ from rx.core.operators.timestamp import _timestamp return _timestamp(scheduler=scheduler) def timeout(duetime: typing.AbsoluteTime, other: Optional[Observable] = None, scheduler: Optional[typing.Scheduler] = None ) -> Callable[[Observable], Observable]: """Returns the source observable sequence or the other observable sequence if duetime elapses. .. marble:: :alt: timeout -1--2--------3--4--| o-6--7-| [ timeout(3,o) ] -1--2---6--7----------| Examples: >>> res = timeout(5.0) >>> res = timeout(datetime(), return_value(42)) >>> res = timeout(5.0, return_value(42)) Args: duetime: Absolute (specified as a datetime object) or relative time (specified as a float denoting seconds or an instance of timedetla) when a timeout occurs. other: Sequence to return in case of a timeout. If not specified, a timeout error throwing sequence will be used. scheduler: Returns: An operator function that takes and observable source and returns the source sequence switching to the other sequence in case of a timeout. """ from rx.core.operators.timeout import _timeout return _timeout(duetime, other, scheduler) def timeout_with_mapper(first_timeout: Optional[Observable] = None, timeout_duration_mapper: Optional[Callable[[Any], Observable]] = None, other: Optional[Observable] = None ) -> Callable[[Observable], Observable]: """Returns the source observable sequence, switching to the other observable sequence if a timeout is signaled. Examples: >>> res = timeout_with_mapper(rx.timer(0.5)) >>> res = timeout_with_mapper(rx.timer(0.5), lambda x: rx.timer(0.2)) >>> res = timeout_with_mapper(rx.timer(0.5), lambda x: rx.timer(0.2)), rx.return_value(42)) Args: first_timeout: [Optional] Observable sequence that represents the timeout for the first element. If not provided, this defaults to rx.never(). timeout_duration_mapper: [Optional] Selector to retrieve an observable sequence that represents the timeout between the current element and the next element. other: [Optional] Sequence to return in case of a timeout. If not provided, this is set to rx.throw(). Returns: An operator function that takes an observable source and returns the source sequence switching to the other sequence in case of a timeout. """ from rx.core.operators.timeoutwithmapper import _timeout_with_mapper return _timeout_with_mapper(first_timeout, timeout_duration_mapper, other) def time_interval(scheduler: Optional[typing.Scheduler] = None) -> Callable[[Observable], Observable]: """Records the time interval between consecutive values in an observable sequence. .. marble:: :alt: time_interval --1--2-----3---4--| [ time_interval() ] -----2-----5---5---| Examples: >>> res = time_interval() Return: An operator function that takes an observable source and returns an observable sequence with time interval information on values. """ from rx.core.operators.timeinterval import _time_interval return _time_interval(scheduler=scheduler) def to_dict(key_mapper: Mapper, element_mapper: Optional[Mapper] = None ) -> Callable[[Observable], Observable]: """Converts the observable sequence to a Map if it exists. Args: key_mapper: A function which produces the key for the dictionary. element_mapper: [Optional] An optional function which produces the element for the dictionary. If not present, defaults to the value from the observable sequence. Returns: An operator function that takes an observable source and returns an observable sequence with a single value of a dictionary containing the values from the observable sequence. """ from rx.core.operators.todict import _to_dict return _to_dict(key_mapper, element_mapper) def to_future(future_ctor: Optional[Callable[[], Future]] = None) -> Callable[[Observable], Future]: """Converts an existing observable sequence to a Future. Example: op = to_future(asyncio.Future); Args: future_ctor: [Optional] The constructor of the future. Returns: An operator function that takes an observable source and returns a future with the last value from the observable sequence. """ from rx.core.operators.tofuture import _to_future return _to_future(future_ctor) def to_iterable() -> Callable[[Observable], Observable]: """Creates an iterable from an observable sequence. There is also an alias called ``to_list``. Returns: An operator function that takes an obserable source and returns an observable sequence containing a single element with an iterable containing all the elements of the source sequence. """ from rx.core.operators.toiterable import _to_iterable return _to_iterable() to_list = to_iterable def to_marbles(timespan: typing.RelativeTime = 0.1, scheduler: Optional[typing.Scheduler] = None ) -> Callable[[Observable], Observable]: """Convert an observable sequence into a marble diagram string. Args: timespan: [Optional] duration of each character in second. If not specified, defaults to 0.1s. scheduler: [Optional] The scheduler used to run the the input sequence on. Returns: Observable stream. """ from rx.core.operators.tomarbles import _to_marbles return _to_marbles(scheduler=scheduler, timespan=timespan) def to_set() -> Callable[[Observable], Observable]: """Converts the observable sequence to a set. Returns: An operator function that takes an observable source and returns an observable sequence with a single value of a set containing the values from the observable sequence. """ from rx.core.operators.toset import _to_set return _to_set() def while_do(condition: Predicate) -> Callable[[Observable], Observable]: """Repeats source as long as condition holds emulating a while loop. Args: condition: The condition which determines if the source will be repeated. Returns: An operator function that takes an observable source and returns an observable sequence which is repeated as long as the condition holds. """ from rx.core.operators.whiledo import _while_do return _while_do(condition) def window(boundaries: Observable) -> Callable[[Observable], Observable]: """Projects each element of an observable sequence into zero or more windows. .. marble:: :alt: window ---a-----b-----c--------| ----1--2--3--4--5--6--7-| [ window(open) ] +--+-----+-----+--------| +5--6--7-| +3--4-| +1--2-| +--| Examples: >>> res = window(rx.interval(1.0)) Args: boundaries: Observable sequence whose elements denote the creation and completion of non-overlapping windows. Returns: An operator function that takes an observable source and returns an observable sequence of windows. """ from rx.core.operators.window import _window return _window(boundaries) def window_when(closing_mapper: Callable[[], Observable]) -> Callable[[Observable], Observable]: """Projects each element of an observable sequence into zero or more windows. .. marble:: :alt: window ------c| ------c| ------c| ----1--2--3--4--5-| [ window(close) ] +-----+-----+-----+| +4--5-| +2--3-| +----1| Examples: >>> res = window(lambda: rx.timer(0.5)) Args: closing_mapper: A function invoked to define the closing of each produced window. It defines the boundaries of the produced windows (a window is started when the previous one is closed, resulting in non-overlapping windows). Returns: An operator function that takes an observable source and returns an observable sequence of windows. """ from rx.core.operators.window import _window_when return _window_when(closing_mapper) def window_toggle(openings: Observable, closing_mapper: Callable[[Any], Observable] ) -> Callable[[Observable], Observable]: """Projects each element of an observable sequence into zero or more windows. .. marble:: :alt: window ---a-----------b------------| ---d--| --------e-| ----1--2--3--4--5--6--7--8--| [ window(open, close) ] ---+-----------+------------| +5--6--7| +1-| >>> res = window(rx.interval(0.5), lambda i: rx.timer(i)) Args: openings: Observable sequence whose elements denote the creation of windows. closing_mapper: A function invoked to define the closing of each produced window. Value from openings Observable that initiated the associated window is provided as argument to the function. Returns: An operator function that takes an observable source and returns an observable sequence of windows. """ from rx.core.operators.window import _window_toggle return _window_toggle(openings, closing_mapper) def window_with_count(count: int, skip: Optional[int] = None) -> Callable[[Observable], Observable]: """Projects each element of an observable sequence into zero or more windows which are produced based on element count information. .. marble:: :alt: window_with_count ---1-2-3---4-5-6---> [ window(3) ] --+-------+--------> +4-5-6-| +1-2-3-| Examples: >>> window_with_count(10) >>> window_with_count(10, 1) Args: count: Length of each window. skip: [Optional] Number of elements to skip between creation of consecutive windows. If not specified, defaults to the count. Returns: An observable sequence of windows. """ from rx.core.operators.windowwithcount import _window_with_count return _window_with_count(count, skip) def window_with_time(timespan: typing.RelativeTime, timeshift: Optional[typing.RelativeTime] = None, scheduler: Optional[typing.Scheduler] = None ) -> Callable[[Observable], Observable]: from rx.core.operators.windowwithtime import _window_with_time return _window_with_time(timespan, timeshift, scheduler) def window_with_time_or_count(timespan: typing.RelativeTime, count: int, scheduler: Optional[typing.Scheduler] = None ) -> Callable[[Observable], Observable]: from rx.core.operators.windowwithtimeorcount import _window_with_time_or_count return _window_with_time_or_count(timespan, count, scheduler) def with_latest_from(*sources: Observable) -> Callable[[Observable], Observable]: """The `with_latest_from` operator. Merges the specified observable sequences into one observable sequence by creating a tuple only when the first observable sequence produces an element. The observables can be passed either as separate arguments or as a list. .. marble:: :alt: with_latest_from ---1---2---3----4-| --a-----b----c-d----| [with_latest_from() ] ---1,a-2,a-3,b--4,d-| Examples: >>> op = with_latest_from(obs1) >>> op = with_latest_from([obs1, obs2, obs3]) Returns: An operator function that takes an observable source and returns an observable sequence containing the result of combining elements of the sources into a tuple. """ from rx.core.operators.withlatestfrom import _with_latest_from return _with_latest_from(*sources) def zip(*args: Observable) -> Callable[[Observable], Observable]: """Merges the specified observable sequences into one observable sequence by creating a tuple whenever all of the observable sequences have produced an element at a corresponding index. .. marble:: :alt: zip --1--2---3-----4---| -a----b----c-d------| [ zip() ] --1,a-2,b--3,c-4,d-| Example: >>> res = zip(obs1, obs2) Args: args: Observable sources to zip. Returns: An operator function that takes an observable source and returns an observable sequence containing the result of combining elements of the sources as a tuple. """ from rx.core.operators.zip import _zip return _zip(*args) def zip_with_iterable(second: Iterable) -> Callable[[Observable], Observable]: """Merges the specified observable sequence and list into one observable sequence by creating a tuple whenever all of the observable sequences have produced an element at a corresponding index. .. marble:: :alt: zip_with_iterable --1---2----3---4---| [ zip(a,b,c,b) ] --1,a-2,b--3,c-4,d-| Example >>> res = zip([1,2,3]) Args: second: Iterable to zip with the source observable.. Returns: An operator function that takes and observable source and returns an observable sequence containing the result of combining elements of the sources as a tuple. """ from rx.core.operators.zip import _zip_with_iterable return _zip_with_iterable(second) zip_with_list = zip_with_iterable RxPY-3.2.0/rx/py.typed000066400000000000000000000000001404130727200144740ustar00rootroot00000000000000RxPY-3.2.0/rx/scheduler/000077500000000000000000000000001404130727200147655ustar00rootroot00000000000000RxPY-3.2.0/rx/scheduler/__init__.py000066400000000000000000000010561404130727200171000ustar00rootroot00000000000000from .scheduleditem import ScheduledItem from .catchscheduler import CatchScheduler from .currentthreadscheduler import CurrentThreadScheduler from .eventloopscheduler import EventLoopScheduler from .historicalscheduler import HistoricalScheduler from .immediatescheduler import ImmediateScheduler from .newthreadscheduler import NewThreadScheduler from .threadpoolscheduler import ThreadPoolScheduler from .timeoutscheduler import TimeoutScheduler from .trampolinescheduler import TrampolineScheduler from .virtualtimescheduler import VirtualTimeScheduler RxPY-3.2.0/rx/scheduler/catchscheduler.py000066400000000000000000000141721404130727200203250ustar00rootroot00000000000000from datetime import datetime from typing import cast, Callable, Optional from rx.core import typing from rx.disposable import Disposable, SingleAssignmentDisposable from .periodicscheduler import PeriodicScheduler class CatchScheduler(PeriodicScheduler): def __init__(self, scheduler: typing.Scheduler, handler: Callable[[Exception], bool] ) -> None: """Wraps a scheduler, passed as constructor argument, adding exception handling for scheduled actions. The handler should return True to indicate it handled the exception successfully. Falsy return values will be taken to indicate that the exception should be escalated (raised by this scheduler). Args: scheduler: The scheduler to be wrapped. handler: Callable to handle exceptions raised by wrapped scheduler. """ super().__init__() self._scheduler: typing.Scheduler = scheduler self._handler: Callable[[Exception], bool] = handler self._recursive_original: Optional[typing.Scheduler] = None self._recursive_wrapper: Optional['CatchScheduler'] = None @property def now(self) -> datetime: """Represents a notion of time for this scheduler. Tasks being scheduled on a scheduler will adhere to the time denoted by this property. Returns: The scheduler's current time, as a datetime instance. """ return self._scheduler.now def schedule(self, action: typing.ScheduledAction, state: Optional[typing.TState] = None ) -> typing.Disposable: """Schedules an action to be executed. Args: action: Action to be executed. state: [Optional] state to be given to the action function. Returns: The disposable object used to cancel the scheduled action (best effort). """ action = self._wrap(action) return self._scheduler.schedule(action, state=state) def schedule_relative(self, duetime: typing.RelativeTime, action: typing.ScheduledAction, state: Optional[typing.TState] = None ) -> typing.Disposable: """Schedules an action to be executed after duetime. Args: duetime: Relative time after which to execute the action. action: Action to be executed. state: [Optional] state to be given to the action function. Returns: The disposable object used to cancel the scheduled action (best effort). """ action = self._wrap(action) return self._scheduler.schedule_relative(duetime, action, state=state) def schedule_absolute(self, duetime: typing.AbsoluteTime, action: typing.ScheduledAction, state: Optional[typing.TState] = None ) -> typing.Disposable: """Schedules an action to be executed at duetime. Args: duetime: Absolute time at which to execute the action. action: Action to be executed. state: [Optional] state to be given to the action function. Returns: The disposable object used to cancel the scheduled action (best effort). """ action = self._wrap(action) return self._scheduler.schedule_absolute(duetime, action, state=state) def schedule_periodic(self, period: typing.RelativeTime, action: typing.ScheduledPeriodicAction, state: Optional[typing.TState] = None ) -> typing.Disposable: """Schedules a periodic piece of work. Args: period: Period in seconds or timedelta for running the work periodically. action: Action to be executed. state: [Optional] Initial state passed to the action upon the first iteration. Returns: The disposable object used to cancel the scheduled recurring action (best effort). """ schedule_periodic = getattr(self._scheduler, 'schedule_periodic') if not callable(schedule_periodic): raise NotImplementedError disp: SingleAssignmentDisposable = SingleAssignmentDisposable() failed: bool = False def periodic(state: Optional[typing.TState] = None) -> Optional[typing.TState]: nonlocal failed if failed: return None try: return action(state) except Exception as ex: failed = True if not self._handler(ex): raise disp.dispose() return None scheduler = cast(PeriodicScheduler, self._scheduler) disp.disposable = scheduler.schedule_periodic(period, periodic, state=state) return disp def _clone(self, scheduler: typing.Scheduler) -> 'CatchScheduler': return CatchScheduler(scheduler, self._handler) def _wrap(self, action: typing.ScheduledAction) -> typing.ScheduledAction: parent = self def wrapped_action(self, state: Optional[typing.TState] ) -> Optional[typing.Disposable]: try: return action(parent._get_recursive_wrapper(self), state) except Exception as ex: if not parent._handler(ex): raise return Disposable() return wrapped_action def _get_recursive_wrapper(self, scheduler) -> 'CatchScheduler': if self._recursive_wrapper is None or self._recursive_original != scheduler: self._recursive_original = scheduler wrapper = self._clone(scheduler) wrapper._recursive_original = scheduler wrapper._recursive_wrapper = wrapper self._recursive_wrapper = wrapper return self._recursive_wrapper RxPY-3.2.0/rx/scheduler/currentthreadscheduler.py000066400000000000000000000047221404130727200221150ustar00rootroot00000000000000import logging from threading import current_thread, local, Thread from typing import MutableMapping from weakref import WeakKeyDictionary from .trampoline import Trampoline from .trampolinescheduler import TrampolineScheduler log = logging.getLogger('Rx') class CurrentThreadScheduler(TrampolineScheduler): """Represents an object that schedules units of work on the current thread. You should never schedule timeouts using the *CurrentThreadScheduler*, as that will block the thread while waiting. Each instance manages a number of trampolines (and queues), one for each thread that calls a *schedule* method. These trampolines are automatically garbage-collected when threads disappear, because they're stored in a weak key dictionary. """ _global: MutableMapping[ type, MutableMapping[Thread, 'CurrentThreadScheduler'] ] = WeakKeyDictionary() @classmethod def singleton(cls) -> 'CurrentThreadScheduler': """ Obtain a singleton instance for the current thread. Please note, if you pass this instance to another thread, it will effectively behave as if it were created by that other thread (separate trampoline and queue). Returns: The singleton *CurrentThreadScheduler* instance. """ thread = current_thread() class_map = CurrentThreadScheduler._global.get(cls) if class_map is None: class_map = WeakKeyDictionary() CurrentThreadScheduler._global[cls] = class_map try: self = class_map[thread] except KeyError: self = CurrentThreadSchedulerSingleton() class_map[thread] = self return self # pylint: disable=super-init-not-called def __init__(self): self._tramps: MutableMapping[Thread, Trampoline] = WeakKeyDictionary() def get_trampoline(self) -> Trampoline: thread = current_thread() tramp = self._tramps.get(thread) if tramp is None: tramp = Trampoline() self._tramps[thread] = tramp return tramp class _Local(local): def __init__(self) -> None: super().__init__() self.tramp = Trampoline() class CurrentThreadSchedulerSingleton(CurrentThreadScheduler): _local = _Local() # pylint: disable=super-init-not-called def __init__(self): pass def get_trampoline(self) -> Trampoline: return CurrentThreadSchedulerSingleton._local.tramp RxPY-3.2.0/rx/scheduler/eventloop/000077500000000000000000000000001404130727200170005ustar00rootroot00000000000000RxPY-3.2.0/rx/scheduler/eventloop/__init__.py000066400000000000000000000004541404130727200211140ustar00rootroot00000000000000from .asyncioscheduler import AsyncIOScheduler from .asynciothreadsafescheduler import AsyncIOThreadSafeScheduler from .eventletscheduler import EventletScheduler from .geventscheduler import GEventScheduler from .ioloopscheduler import IOLoopScheduler from .twistedscheduler import TwistedScheduler RxPY-3.2.0/rx/scheduler/eventloop/asyncioscheduler.py000066400000000000000000000074511404130727200227250ustar00rootroot00000000000000import logging import asyncio from datetime import datetime from typing import Optional from rx.core import typing from rx.disposable import CompositeDisposable, Disposable, SingleAssignmentDisposable from ..periodicscheduler import PeriodicScheduler log = logging.getLogger("Rx") class AsyncIOScheduler(PeriodicScheduler): """A scheduler that schedules work via the asyncio mainloop. This class does not use the asyncio threadsafe methods, if you need those please use the AsyncIOThreadSafeScheduler class.""" def __init__(self, loop: asyncio.AbstractEventLoop) -> None: """Create a new AsyncIOScheduler. Args: loop: Instance of asyncio event loop to use; typically, you would get this by asyncio.get_event_loop() """ super().__init__() self._loop: asyncio.AbstractEventLoop = loop def schedule(self, action: typing.ScheduledAction, state: Optional[typing.TState] = None ) -> typing.Disposable: """Schedules an action to be executed. Args: action: Action to be executed. state: [Optional] state to be given to the action function. Returns: The disposable object used to cancel the scheduled action (best effort). """ sad = SingleAssignmentDisposable() def interval() -> None: sad.disposable = self.invoke_action(action, state=state) handle = self._loop.call_soon(interval) def dispose() -> None: handle.cancel() return CompositeDisposable(sad, Disposable(dispose)) def schedule_relative(self, duetime: typing.RelativeTime, action: typing.ScheduledAction, state: Optional[typing.TState] = None ) -> typing.Disposable: """Schedules an action to be executed after duetime. Args: duetime: Relative time after which to execute the action. action: Action to be executed. state: [Optional] state to be given to the action function. Returns: The disposable object used to cancel the scheduled action (best effort). """ seconds = self.to_seconds(duetime) if seconds <= 0: return self.schedule(action, state) sad = SingleAssignmentDisposable() def interval() -> None: sad.disposable = self.invoke_action(action, state=state) handle = self._loop.call_later(seconds, interval) def dispose() -> None: handle.cancel() return CompositeDisposable(sad, Disposable(dispose)) def schedule_absolute(self, duetime: typing.AbsoluteTime, action: typing.ScheduledAction, state: Optional[typing.TState] = None ) -> typing.Disposable: """Schedules an action to be executed at duetime. Args: duetime: Absolute time at which to execute the action. action: Action to be executed. state: [Optional] state to be given to the action function. Returns: The disposable object used to cancel the scheduled action (best effort). """ duetime = self.to_datetime(duetime) return self.schedule_relative(duetime - self.now, action, state=state) @property def now(self) -> datetime: """Represents a notion of time for this scheduler. Tasks being scheduled on a scheduler will adhere to the time denoted by this property. Returns: The scheduler's current time, as a datetime instance. """ return self.to_datetime(self._loop.time()) RxPY-3.2.0/rx/scheduler/eventloop/asynciothreadsafescheduler.py000066400000000000000000000124361404130727200247530ustar00rootroot00000000000000import logging import asyncio from concurrent.futures import Future from typing import List, Optional from rx.core import typing from rx.disposable import CompositeDisposable, Disposable, SingleAssignmentDisposable from .asyncioscheduler import AsyncIOScheduler log = logging.getLogger("Rx") class AsyncIOThreadSafeScheduler(AsyncIOScheduler): """A scheduler that schedules work via the asyncio mainloop. This is a subclass of AsyncIOScheduler which uses the threadsafe asyncio methods. """ def __init__(self, loop: asyncio.AbstractEventLoop) -> None: """Create a new AsyncIOThreadSafeScheduler. Args: loop: Instance of asyncio event loop to use; typically, you would get this by asyncio.get_event_loop() """ super().__init__(loop) def schedule(self, action: typing.ScheduledAction, state: Optional[typing.TState] = None ) -> typing.Disposable: """Schedules an action to be executed. Args: action: Action to be executed. state: [Optional] state to be given to the action function. Returns: The disposable object used to cancel the scheduled action (best effort). """ sad = SingleAssignmentDisposable() def interval() -> None: sad.disposable = self.invoke_action(action, state=state) handle = self._loop.call_soon_threadsafe(interval) def dispose() -> None: if self._on_self_loop_or_not_running(): handle.cancel() return future: Future = Future() def cancel_handle() -> None: handle.cancel() future.set_result(0) self._loop.call_soon_threadsafe(cancel_handle) future.result() return CompositeDisposable(sad, Disposable(dispose)) def schedule_relative(self, duetime: typing.RelativeTime, action: typing.ScheduledAction, state: Optional[typing.TState] = None ) -> typing.Disposable: """Schedules an action to be executed after duetime. Args: duetime: Relative time after which to execute the action. action: Action to be executed. state: [Optional] state to be given to the action function. Returns: The disposable object used to cancel the scheduled action (best effort). """ seconds = self.to_seconds(duetime) if seconds <= 0: return self.schedule(action, state=state) sad = SingleAssignmentDisposable() def interval() -> None: sad.disposable = self.invoke_action(action, state=state) # the operations on the list used here are atomic, so there is no # need to protect its access with a lock handle: List[asyncio.Handle] = [] def stage2() -> None: handle.append(self._loop.call_later(seconds, interval)) handle.append(self._loop.call_soon_threadsafe(stage2)) def dispose() -> None: def do_cancel_handles(): try: handle.pop().cancel() handle.pop().cancel() except Exception: pass if self._on_self_loop_or_not_running(): do_cancel_handles() return future: Future = Future() def cancel_handle() -> None: do_cancel_handles() future.set_result(0) self._loop.call_soon_threadsafe(cancel_handle) future.result() return CompositeDisposable(sad, Disposable(dispose)) def schedule_absolute(self, duetime: typing.AbsoluteTime, action: typing.ScheduledAction, state: Optional[typing.TState] = None ) -> typing.Disposable: """Schedules an action to be executed at duetime. Args: duetime: Absolute time at which to execute the action. action: Action to be executed. state: [Optional] state to be given to the action function. Returns: The disposable object used to cancel the scheduled action (best effort). """ duetime = self.to_datetime(duetime) return self.schedule_relative(duetime - self.now, action, state=state) def _on_self_loop_or_not_running(self): """ Returns True if either self._loop is not running, or we're currently executing on self._loop. In both cases, waiting for a future to be resolved on the loop would result in a deadlock. """ if not self._loop.is_running(): return True current_loop = None try: # In python 3.7 there asyncio.get_running_loop() is prefered. current_loop = asyncio.get_event_loop() except RuntimeError: # If there is no loop in current thread at all, and it is not main # thread, we get error like: # RuntimeError: There is no current event loop in thread 'Thread-1' pass return self._loop == current_loop RxPY-3.2.0/rx/scheduler/eventloop/eventletscheduler.py000066400000000000000000000072471404130727200231110ustar00rootroot00000000000000import logging from datetime import datetime from typing import Any, Optional from rx.core import typing from rx.disposable import CompositeDisposable, Disposable, SingleAssignmentDisposable from ..periodicscheduler import PeriodicScheduler log = logging.getLogger("Rx") class EventletScheduler(PeriodicScheduler): """A scheduler that schedules work via the eventlet event loop. http://eventlet.net/ """ def __init__(self, eventlet: Any) -> None: """Create a new EventletScheduler. Args: eventlet: The eventlet module to use; typically, you would get this by import eventlet """ super().__init__() self._eventlet = eventlet def schedule(self, action: typing.ScheduledAction, state: Optional[typing.TState] = None ) -> typing.Disposable: """Schedules an action to be executed. Args: action: Action to be executed. state: [Optional] state to be given to the action function. Returns: The disposable object used to cancel the scheduled action (best effort). """ sad = SingleAssignmentDisposable() def interval() -> None: sad.disposable = self.invoke_action(action, state=state) timer = self._eventlet.spawn(interval) def dispose() -> None: timer.kill() return CompositeDisposable(sad, Disposable(dispose)) def schedule_relative(self, duetime: typing.RelativeTime, action: typing.ScheduledAction, state: Optional[typing.TState] = None ) -> typing.Disposable: """Schedules an action to be executed after duetime. Args: duetime: Relative time after which to execute the action. action: Action to be executed. state: [Optional] state to be given to the action function. Returns: The disposable object used to cancel the scheduled action (best effort). """ seconds = self.to_seconds(duetime) if seconds <= 0.0: return self.schedule(action, state=state) sad = SingleAssignmentDisposable() def interval() -> None: sad.disposable = self.invoke_action(action, state=state) timer = self._eventlet.spawn_after(seconds, interval) def dispose() -> None: timer.kill() return CompositeDisposable(sad, Disposable(dispose)) def schedule_absolute(self, duetime: typing.AbsoluteTime, action: typing.ScheduledAction, state: Optional[typing.TState] = None ) -> typing.Disposable: """Schedules an action to be executed at duetime. Args: duetime: Absolute time at which to execute the action. action: Action to be executed. state: [Optional] state to be given to the action function. Returns: The disposable object used to cancel the scheduled action (best effort). """ duetime = self.to_datetime(duetime) return self.schedule_relative(duetime - self.now, action, state=state) @property def now(self) -> datetime: """Represents a notion of time for this scheduler. Tasks being scheduled on a scheduler will adhere to the time denoted by this property. Returns: The scheduler's current time, as a datetime instance. """ return self.to_datetime(self._eventlet.hubs.get_hub().clock()) RxPY-3.2.0/rx/scheduler/eventloop/geventscheduler.py000066400000000000000000000072651404130727200225530ustar00rootroot00000000000000import logging from datetime import datetime from typing import Any, Optional from rx.core import typing from rx.disposable import CompositeDisposable, Disposable, SingleAssignmentDisposable from ..periodicscheduler import PeriodicScheduler log = logging.getLogger("Rx") class GEventScheduler(PeriodicScheduler): """A scheduler that schedules work via the GEvent event loop. http://www.gevent.org/ """ def __init__(self, gevent: Any) -> None: """Create a new GEventScheduler. Args: gevent: The gevent module to use; typically ,you would get this by import gevent """ super().__init__() self._gevent = gevent def schedule(self, action: typing.ScheduledAction, state: Optional[typing.TState] = None ) -> typing.Disposable: """Schedules an action to be executed. Args: action: Action to be executed. state: [Optional] state to be given to the action function. Returns: The disposable object used to cancel the scheduled action (best effort). """ sad = SingleAssignmentDisposable() def interval() -> None: sad.disposable = self.invoke_action(action, state=state) timer = self._gevent.spawn(interval) def dispose() -> None: timer.kill(block=False) return CompositeDisposable(sad, Disposable(dispose)) def schedule_relative(self, duetime: typing.RelativeTime, action: typing.ScheduledAction, state: Optional[typing.TState] = None ) -> typing.Disposable: """Schedules an action to be executed after duetime. Args: duetime: Relative time after which to execute the action. action: Action to be executed. state: [Optional] state to be given to the action function. Returns: The disposable object used to cancel the scheduled action (best effort). """ seconds = self.to_seconds(duetime) if seconds <= 0.0: return self.schedule(action, state=state) sad = SingleAssignmentDisposable() def interval() -> None: sad.disposable = self.invoke_action(action, state=state) log.debug("timeout: %s", seconds) timer = self._gevent.spawn_later(seconds, interval) def dispose() -> None: timer.kill(block=False) return CompositeDisposable(sad, Disposable(dispose)) def schedule_absolute(self, duetime: typing.AbsoluteTime, action: typing.ScheduledAction, state: Optional[typing.TState] = None ) -> typing.Disposable: """Schedules an action to be executed at duetime. Args: duetime: Absolute time at which to execute the action. action: Action to be executed. state: [Optional] state to be given to the action function. Returns: The disposable object used to cancel the scheduled action (best effort). """ duetime = self.to_datetime(duetime) return self.schedule_relative(duetime - self.now, action, state=state) @property def now(self) -> datetime: """Represents a notion of time for this scheduler. Tasks being scheduled on a scheduler will adhere to the time denoted by this property. Returns: The scheduler's current time, as a datetime instance. """ return self.to_datetime(self._gevent.get_hub().loop.now()) RxPY-3.2.0/rx/scheduler/eventloop/ioloopscheduler.py000066400000000000000000000077211404130727200225610ustar00rootroot00000000000000import logging from datetime import datetime from typing import Any, Optional from rx.core import typing from rx.disposable import CompositeDisposable, Disposable, SingleAssignmentDisposable from ..periodicscheduler import PeriodicScheduler log = logging.getLogger("Rx") class IOLoopScheduler(PeriodicScheduler): """A scheduler that schedules work via the Tornado I/O main event loop. Note, as of Tornado 6, this is just a wrapper around the asyncio loop. http://tornado.readthedocs.org/en/latest/ioloop.html""" def __init__(self, loop: Any) -> None: """Create a new IOLoopScheduler. Args: loop: The ioloop to use; typically, you would get this by tornado import ioloop; ioloop.IOLoop.current() """ super().__init__() self._loop = loop def schedule(self, action: typing.ScheduledAction, state: Optional[typing.TState] = None ) -> typing.Disposable: """Schedules an action to be executed. Args: action: Action to be executed. state: [Optional] state to be given to the action function. Returns: The disposable object used to cancel the scheduled action (best effort). """ sad = SingleAssignmentDisposable() disposed = False def interval() -> None: if not disposed: sad.disposable = self.invoke_action(action, state=state) self._loop.add_callback(interval) def dispose() -> None: nonlocal disposed disposed = True return CompositeDisposable(sad, Disposable(dispose)) def schedule_relative(self, duetime: typing.RelativeTime, action: typing.ScheduledAction, state: Optional[typing.TState] = None ) -> typing.Disposable: """Schedules an action to be executed after duetime. Args: duetime: Relative time after which to execute the action. action: Action to be executed. state: [Optional] state to be given to the action function. Returns: The disposable object used to cancel the scheduled action (best effort). """ seconds = self.to_seconds(duetime) if seconds <= 0.0: return self.schedule(action, state=state) sad = SingleAssignmentDisposable() def interval() -> None: sad.disposable = self.invoke_action(action, state=state) log.debug("timeout: %s", seconds) timer = self._loop.call_later(seconds, interval) def dispose() -> None: self._loop.remove_timeout(timer) self.\ _loop.remove_timeout(timer) return CompositeDisposable(sad, Disposable(dispose)) def schedule_absolute(self, duetime: typing.AbsoluteTime, action: typing.ScheduledAction, state: Optional[typing.TState] = None ) -> typing.Disposable: """Schedules an action to be executed at duetime. Args: duetime: Absolute time at which to execute the action. action: Action to be executed. state: [Optional] state to be given to the action function. Returns: The disposable object used to cancel the scheduled action (best effort). """ duetime = self.to_datetime(duetime) return self.schedule_relative(duetime - self.now, action, state=state) @property def now(self) -> datetime: """Represents a notion of time for this scheduler. Tasks being scheduled on a scheduler will adhere to the time denoted by this property. Returns: The scheduler's current time, as a datetime instance. """ return self.to_datetime(self._loop.time()) RxPY-3.2.0/rx/scheduler/eventloop/twistedscheduler.py000066400000000000000000000066101404130727200227370ustar00rootroot00000000000000import logging from datetime import datetime from typing import Any, Optional from rx.core import typing from rx.disposable import CompositeDisposable, Disposable, SingleAssignmentDisposable from ..periodicscheduler import PeriodicScheduler log = logging.getLogger("Rx") class TwistedScheduler(PeriodicScheduler): """A scheduler that schedules work via the Twisted reactor mainloop.""" def __init__(self, reactor: Any) -> None: """Create a new TwistedScheduler. Args: reactor: The reactor to use; typically, you would get this by from twisted.internet import reactor """ super().__init__() self._reactor = reactor def schedule(self, action: typing.ScheduledAction, state: Optional[typing.TState] = None ) -> typing.Disposable: """Schedules an action to be executed. Args: action: Action to be executed. state: [Optional] state to be given to the action function. Returns: The disposable object used to cancel the scheduled action (best effort). """ return self.schedule_relative(0.0, action, state=state) def schedule_relative(self, duetime: typing.RelativeTime, action: typing.ScheduledAction, state: Optional[typing.TState] = None ) -> typing.Disposable: """Schedules an action to be executed after duetime. Args: duetime: Relative time after which to execute the action. action: Action to be executed. state: [Optional] state to be given to the action function. Returns: The disposable object used to cancel the scheduled action (best effort). """ seconds = max(0.0, self.to_seconds(duetime)) sad = SingleAssignmentDisposable() def interval() -> None: sad.disposable = action(self, state) log.debug("timeout: %s", seconds) timer = self._reactor.callLater(seconds, interval) def dispose() -> None: if not timer.called: timer.cancel() return CompositeDisposable(sad, Disposable(dispose)) def schedule_absolute(self, duetime: typing.AbsoluteTime, action: typing.ScheduledAction, state: Optional[typing.TState] = None ) -> typing.Disposable: """Schedules an action to be executed at duetime. Args: duetime: Absolute time at which to execute the action. action: Action to be executed. state: [Optional] state to be given to the action function. Returns: The disposable object used to cancel the scheduled action (best effort). """ duetime = self.to_datetime(duetime) return self.schedule_relative(duetime - self.now, action, state=state) @property def now(self) -> datetime: """Represents a notion of time for this scheduler. Tasks being scheduled on a scheduler will adhere to the time denoted by this property. Returns: The scheduler's current time, as a datetime instance. """ return self.to_datetime(float(self._reactor.seconds())) RxPY-3.2.0/rx/scheduler/eventloopscheduler.py000066400000000000000000000166441404130727200212640ustar00rootroot00000000000000import logging import threading from collections import deque from datetime import timedelta from typing import Deque, Optional from rx.core import typing from rx.disposable import Disposable from rx.internal.concurrency import default_thread_factory from rx.internal.constants import DELTA_ZERO from rx.internal.exceptions import DisposedException from rx.internal.priorityqueue import PriorityQueue from .scheduleditem import ScheduledItem from .periodicscheduler import PeriodicScheduler log = logging.getLogger('Rx') class EventLoopScheduler(PeriodicScheduler, typing.Disposable): """Creates an object that schedules units of work on a designated thread.""" def __init__(self, thread_factory: Optional[typing.StartableFactory] = None, exit_if_empty: bool = False ) -> None: super().__init__() self._is_disposed = False self._thread_factory: typing.StartableFactory = thread_factory or default_thread_factory self._thread: Optional[typing.Startable] = None self._condition = threading.Condition(threading.Lock()) self._queue: PriorityQueue[ScheduledItem] = PriorityQueue() self._ready_list: Deque[ScheduledItem] = deque() self._exit_if_empty = exit_if_empty def schedule(self, action: typing.ScheduledAction, state: Optional[typing.TState] = None ) -> typing.Disposable: """Schedules an action to be executed. Args: action: Action to be executed. state: [Optional] state to be given to the action function. Returns: The disposable object used to cancel the scheduled action (best effort). """ return self.schedule_absolute(self.now, action, state=state) def schedule_relative(self, duetime: typing.RelativeTime, action: typing.ScheduledAction, state: Optional[typing.TState] = None ) -> typing.Disposable: """Schedules an action to be executed after duetime. Args: duetime: Relative time after which to execute the action. action: Action to be executed. state: [Optional] state to be given to the action function. Returns: The disposable object used to cancel the scheduled action (best effort). """ duetime = max(DELTA_ZERO, self.to_timedelta(duetime)) return self.schedule_absolute(self.now + duetime, action, state) def schedule_absolute(self, duetime: typing.AbsoluteTime, action: typing.ScheduledAction, state: Optional[typing.TState] = None ) -> typing.Disposable: """Schedules an action to be executed at duetime. Args: duetime: Absolute time at which to execute the action. action: Action to be executed. state: [Optional] state to be given to the action function. Returns: The disposable object used to cancel the scheduled action (best effort). """ if self._is_disposed: raise DisposedException() dt = self.to_datetime(duetime) si: ScheduledItem = ScheduledItem(self, state, action, dt) with self._condition: if dt <= self.now: self._ready_list.append(si) else: self._queue.enqueue(si) self._condition.notify() # signal that a new item is available self._ensure_thread() return Disposable(si.cancel) def schedule_periodic(self, period: typing.RelativeTime, action: typing.ScheduledPeriodicAction, state: Optional[typing.TState] = None ) -> typing.Disposable: """Schedules a periodic piece of work. Args: period: Period in seconds or timedelta for running the work periodically. action: Action to be executed. state: [Optional] Initial state passed to the action upon the first iteration. Returns: The disposable object used to cancel the scheduled recurring action (best effort). """ if self._is_disposed: raise DisposedException() return super().schedule_periodic(period, action, state=state) def _has_thread(self) -> bool: """Checks if there is an event loop thread running.""" with self._condition: return not self._is_disposed and self._thread is not None def _ensure_thread(self) -> None: """Ensures there is an event loop thread running. Should be called under the gate.""" if not self._thread: thread = self._thread_factory(self.run) self._thread = thread thread.start() def run(self) -> None: """Event loop scheduled on the designated event loop thread. The loop is suspended/resumed using the condition which gets notified by calls to Schedule or calls to dispose.""" ready: Deque[ScheduledItem] = deque() while True: with self._condition: # The notification could be because of a call to dispose. This # takes precedence over everything else: We'll exit the loop # immediately. Subsequent calls to Schedule won't ever create a # new thread. if self._is_disposed: return # Sort the ready_list (from recent calls for immediate schedule) # and the due subset of previously queued items. time = self.now while self._queue: due = self._queue.peek().duetime while self._ready_list and due > self._ready_list[0].duetime: ready.append(self._ready_list.popleft()) if due > time: break ready.append(self._queue.dequeue()) while self._ready_list: ready.append(self._ready_list.popleft()) # Execute the gathered actions while ready: item = ready.popleft() if not item.is_cancelled(): item.invoke() # Wait for next cycle, or if we're done let's exit if so configured with self._condition: if self._ready_list: continue elif self._queue: time = self.now item = self._queue.peek() seconds = (item.duetime - time).total_seconds() if seconds > 0: log.debug("timeout: %s", seconds) self._condition.wait(seconds) elif self._exit_if_empty: self._thread = None return else: self._condition.wait() def dispose(self) -> None: """Ends the thread associated with this scheduler. All remaining work in the scheduler queue is abandoned. """ with self._condition: if not self._is_disposed: self._is_disposed = True self._condition.notify() RxPY-3.2.0/rx/scheduler/historicalscheduler.py000066400000000000000000000026511404130727200214030ustar00rootroot00000000000000from datetime import datetime from rx.core import typing from .scheduler import UTC_ZERO from .virtualtimescheduler import VirtualTimeScheduler class HistoricalScheduler(VirtualTimeScheduler): """Provides a virtual time scheduler that uses datetime for absolute time and timedelta for relative time.""" def __init__(self, initial_clock: datetime = None) -> None: """Creates a new historical scheduler with the specified initial clock value. Args: initial_clock: Initial value for the clock. """ super().__init__(initial_clock or UTC_ZERO) @property def now(self) -> datetime: """Represents a notion of time for this scheduler. Tasks being scheduled on a scheduler will adhere to the time denoted by this property. Returns: The scheduler's current time, as a datetime instance. """ return self._clock @classmethod def add(cls, absolute: typing.AbsoluteTime, relative: typing.RelativeTime ) -> typing.AbsoluteTime: """Adds a relative time value to an absolute time value. Args: absolute: Absolute virtual time value. relative: Relative virtual time value to add. Returns: The resulting absolute virtual time sum value. """ return cls.to_datetime(absolute) + cls.to_timedelta(relative) RxPY-3.2.0/rx/scheduler/immediatescheduler.py000066400000000000000000000062231404130727200211770ustar00rootroot00000000000000from threading import Lock from typing import Optional, MutableMapping from weakref import WeakKeyDictionary from rx.core import typing from rx.internal.constants import DELTA_ZERO from rx.internal.exceptions import WouldBlockException from .scheduler import Scheduler class ImmediateScheduler(Scheduler): """Represents an object that schedules units of work to run immediately, on the current thread. You're not allowed to schedule timeouts using the ImmediateScheduler since that will block the current thread while waiting. Attempts to do so will raise a :class:`WouldBlockException`. """ _lock = Lock() _global: MutableMapping[type, 'ImmediateScheduler'] = WeakKeyDictionary() @classmethod def singleton(cls) -> 'ImmediateScheduler': with ImmediateScheduler._lock: try: self = ImmediateScheduler._global[cls] except KeyError: self = super().__new__(cls) ImmediateScheduler._global[cls] = self return self def __new__(cls) -> 'ImmediateScheduler': return cls.singleton() def schedule(self, action: typing.ScheduledAction, state: Optional[typing.TState] = None ) -> typing.Disposable: """Schedules an action to be executed. Args: action: Action to be executed. state: [Optional] state to be given to the action function. Returns: The disposable object used to cancel the scheduled action (best effort). """ return self.invoke_action(action, state) def schedule_relative(self, duetime: typing.RelativeTime, action: typing.ScheduledAction, state: Optional[typing.TState] = None ) -> typing.Disposable: """Schedules an action to be executed after duetime. Args: duetime: Relative time after which to execute the action. action: Action to be executed. state: [Optional] state to be given to the action function. Returns: The disposable object used to cancel the scheduled action (best effort). """ duetime = self.to_timedelta(duetime) if duetime > DELTA_ZERO: raise WouldBlockException() return self.invoke_action(action, state) def schedule_absolute(self, duetime: typing.AbsoluteTime, action: typing.ScheduledAction, state: Optional[typing.TState] = None ) -> typing.Disposable: """Schedules an action to be executed at duetime. Args: duetime: Absolute time at which to execute the action. action: Action to be executed. state: [Optional] state to be given to the action function. Returns: The disposable object used to cancel the scheduled action (best effort). """ duetime = self.to_datetime(duetime) return self.schedule_relative(duetime - self.now, action, state) RxPY-3.2.0/rx/scheduler/mainloop/000077500000000000000000000000001404130727200166035ustar00rootroot00000000000000RxPY-3.2.0/rx/scheduler/mainloop/__init__.py000066400000000000000000000003151404130727200207130ustar00rootroot00000000000000from .gtkscheduler import GtkScheduler from .tkinterscheduler import TkinterScheduler from .pygamescheduler import PyGameScheduler from .qtscheduler import QtScheduler from .wxscheduler import WxScheduler RxPY-3.2.0/rx/scheduler/mainloop/gtkscheduler.py000066400000000000000000000106241404130727200216440ustar00rootroot00000000000000from typing import cast, Any, Optional from rx.core import typing from rx.disposable import CompositeDisposable, Disposable, SingleAssignmentDisposable from ..periodicscheduler import PeriodicScheduler class GtkScheduler(PeriodicScheduler): """ A scheduler that schedules work via the GLib main loop used in GTK+ applications. See https://wiki.gnome.org/Projects/PyGObject """ def __init__(self, glib: Any) -> None: """Create a new GtkScheduler. Args: glib: The GLib module to use; typically, you would get this by import gi; gi.require_version('Gtk', '3.0'); from gi.repository import GLib """ super().__init__() self._glib = glib def _gtk_schedule(self, time: typing.AbsoluteOrRelativeTime, action: typing.ScheduledSingleOrPeriodicAction, state: Optional[typing.TState] = None, periodic: bool = False ) -> typing.Disposable: msecs = max(0, int(self.to_seconds(time) * 1000.0)) sad = SingleAssignmentDisposable() stopped = False def timer_handler(_) -> bool: if stopped: return False nonlocal state if periodic: state = cast(typing.ScheduledPeriodicAction, action)(state) else: sad.disposable = self.invoke_action(cast(typing.ScheduledAction, action), state=state) return periodic self._glib.timeout_add(msecs, timer_handler, None) def dispose() -> None: nonlocal stopped stopped = True return CompositeDisposable(sad, Disposable(dispose)) def schedule(self, action: typing.ScheduledAction, state: Optional[typing.TState] = None ) -> typing.Disposable: """Schedules an action to be executed. Args: action: Action to be executed. state: [Optional] state to be given to the action function. Returns: The disposable object used to cancel the scheduled action (best effort). """ return self._gtk_schedule(0.0, action, state) def schedule_relative(self, duetime: typing.RelativeTime, action: typing.ScheduledAction, state: Optional[typing.TState] = None ) -> typing.Disposable: """Schedules an action to be executed after duetime. Args: duetime: Relative time after which to execute the action. action: Action to be executed. state: [Optional] state to be given to the action function. Returns: The disposable object used to cancel the scheduled action (best effort). """ return self._gtk_schedule(duetime, action, state=state) def schedule_absolute(self, duetime: typing.AbsoluteTime, action: typing.ScheduledAction, state: Optional[typing.TState] = None ) -> typing.Disposable: """Schedules an action to be executed at duetime. Args: duetime: Absolute time at which to execute the action. action: Action to be executed. state: [Optional] state to be given to the action function. Returns: The disposable object used to cancel the scheduled action (best effort). """ duetime = self.to_datetime(duetime) return self._gtk_schedule(duetime - self.now, action, state=state) def schedule_periodic(self, period: typing.RelativeTime, action: typing.ScheduledPeriodicAction, state: Optional[typing.TState] = None ) -> typing.Disposable: """Schedules a periodic piece of work to be executed in the loop. Args: period: Period in seconds for running the work repeatedly. action: Action to be executed. state: [Optional] state to be given to the action function. Returns: The disposable object used to cancel the scheduled action (best effort). """ return self._gtk_schedule(period, action, state=state, periodic=True) RxPY-3.2.0/rx/scheduler/mainloop/pygamescheduler.py000066400000000000000000000070361404130727200223440ustar00rootroot00000000000000import logging import threading from typing import Any, Optional from rx.core import typing from rx.internal import PriorityQueue from rx.internal.constants import DELTA_ZERO from ..scheduleditem import ScheduledItem from ..periodicscheduler import PeriodicScheduler log = logging.getLogger("Rx") class PyGameScheduler(PeriodicScheduler): """A scheduler that schedules works for PyGame. Note that this class expects the caller to invoke run() repeatedly. http://www.pygame.org/docs/ref/time.html http://www.pygame.org/docs/ref/event.html""" def __init__(self, pygame: Any): """Create a new PyGameScheduler. Args: pygame: The PyGame module to use; typically, you would get this by import pygame """ super().__init__() self._pygame = pygame # TODO not used, refactor to actually use pygame? self._lock = threading.Lock() self._queue: PriorityQueue[ScheduledItem] = PriorityQueue() def schedule(self, action: typing.ScheduledAction, state: Optional[typing.TState] = None ) -> typing.Disposable: """Schedules an action to be executed. Args: action: Action to be executed. state: [Optional] state to be given to the action function. Returns: The disposable object used to cancel the scheduled action (best effort). """ log.debug("PyGameScheduler.schedule(state=%s)", state) return self.schedule_absolute(self.now, action, state=state) def schedule_relative(self, duetime: typing.RelativeTime, action: typing.ScheduledAction, state: Optional[typing.TState] = None ) -> typing.Disposable: """Schedules an action to be executed after duetime. Args: duetime: Relative time after which to execute the action. action: Action to be executed. state: [Optional] state to be given to the action function. Returns: The disposable object used to cancel the scheduled action (best effort). """ duetime = max(DELTA_ZERO, self.to_timedelta(duetime)) return self.schedule_absolute(self.now + duetime, action, state=state) def schedule_absolute(self, duetime: typing.AbsoluteTime, action: typing.ScheduledAction, state: Optional[typing.TState] = None ) -> typing.Disposable: """Schedules an action to be executed at duetime. Args: duetime: Absolute time at which to execute the action. action: Action to be executed. state: [Optional] state to be given to the action function. Returns: The disposable object used to cancel the scheduled action (best effort). """ duetime = self.to_datetime(duetime) si: ScheduledItem = ScheduledItem(self, state, action, duetime) with self._lock: self._queue.enqueue(si) return si.disposable def run(self) -> None: while self._queue: with self._lock: item: ScheduledItem = self._queue.peek() diff = item.duetime - self.now if diff > DELTA_ZERO: break item = self._queue.dequeue() if not item.is_cancelled(): item.invoke() RxPY-3.2.0/rx/scheduler/mainloop/qtscheduler.py000066400000000000000000000111041404130727200214750ustar00rootroot00000000000000import logging from datetime import timedelta from typing import Any, Optional, Set from rx.core import typing from rx.disposable import CompositeDisposable, Disposable, SingleAssignmentDisposable from ..periodicscheduler import PeriodicScheduler log = logging.getLogger(__name__) class QtScheduler(PeriodicScheduler): """A scheduler for a PyQt5/PySide2 event loop.""" def __init__(self, qtcore: Any): """Create a new QtScheduler. Args: qtcore: The QtCore instance to use; typically you would get this by either import PyQt5.QtCore or import PySide2.QtCore """ super().__init__() self._qtcore = qtcore timer_class: Any = self._qtcore.QTimer self._periodic_timers: Set[timer_class] = set() def schedule(self, action: typing.ScheduledAction, state: Optional[typing.TState] = None ) -> typing.Disposable: """Schedules an action to be executed. Args: action: Action to be executed. state: [Optional] state to be given to the action function. Returns: The disposable object used to cancel the scheduled action (best effort). """ return self.schedule_relative(0.0, action, state=state) def schedule_relative(self, duetime: typing.RelativeTime, action: typing.ScheduledAction, state: Optional[typing.TState] = None ) -> typing.Disposable: """Schedules an action to be executed after duetime. Args: duetime: Relative time after which to execute the action. action: Action to be executed. state: [Optional] state to be given to the action function. Returns: The disposable object used to cancel the scheduled action (best effort). """ msecs = max(0, int(self.to_seconds(duetime) * 1000.0)) sad = SingleAssignmentDisposable() is_disposed = False def invoke_action() -> None: if not is_disposed: sad.disposable = action(self, state) log.debug("relative timeout: %sms", msecs) # Use static method, let Qt C++ handle QTimer lifetime self._qtcore.QTimer.singleShot(msecs, invoke_action) def dispose() -> None: nonlocal is_disposed is_disposed = True return CompositeDisposable(sad, Disposable(dispose)) def schedule_absolute(self, duetime: typing.AbsoluteTime, action: typing.ScheduledAction, state: Optional[typing.TState] = None ) -> typing.Disposable: """Schedules an action to be executed at duetime. Args: duetime: Absolute time at which to execute the action. action: Action to be executed. state: [Optional] state to be given to the action function. Returns: The disposable object used to cancel the scheduled action (best effort). """ delta: timedelta = self.to_datetime(duetime) - self.now return self.schedule_relative(delta, action, state=state) def schedule_periodic(self, period: typing.RelativeTime, action: typing.ScheduledPeriodicAction, state: Optional[typing.TState] = None ) -> typing.Disposable: """Schedules a periodic piece of work to be executed in the loop. Args: period: Period in seconds for running the work repeatedly. action: Action to be executed. state: [Optional] state to be given to the action function. Returns: The disposable object used to cancel the scheduled action (best effort). """ msecs = max(0, int(self.to_seconds(period) * 1000.0)) sad = SingleAssignmentDisposable() def interval() -> None: nonlocal state state = action(state) log.debug("periodic timeout: %sms", msecs) timer = self._qtcore.QTimer() timer.setSingleShot(not period) timer.timeout.connect(interval) timer.setInterval(msecs) self._periodic_timers.add(timer) timer.start() def dispose() -> None: timer.stop() self._periodic_timers.remove(timer) timer.deleteLater() return CompositeDisposable(sad, Disposable(dispose)) RxPY-3.2.0/rx/scheduler/mainloop/tkinterscheduler.py000066400000000000000000000060031404130727200225330ustar00rootroot00000000000000from typing import Any, Optional from rx.core import typing from rx.disposable import CompositeDisposable, Disposable, SingleAssignmentDisposable from ..periodicscheduler import PeriodicScheduler class TkinterScheduler(PeriodicScheduler): """A scheduler that schedules work via the Tkinter main event loop. http://infohost.nmt.edu/tcc/help/pubs/tkinter/web/universal.html http://effbot.org/tkinterbook/widget.htm""" def __init__(self, root: Any) -> None: """Create a new TkinterScheduler. Args: root: The Tk instance to use; typically, you would get this by import tkinter; tkinter.Tk() """ super().__init__() self._root = root def schedule(self, action: typing.ScheduledAction, state: Optional[typing.TState] = None ) -> typing.Disposable: """Schedules an action to be executed. Args: action: Action to be executed. state: [Optional] state to be given to the action function. Returns: The disposable object used to cancel the scheduled action (best effort). """ return self.schedule_relative(0.0, action, state) def schedule_relative(self, duetime: typing.RelativeTime, action: typing.ScheduledAction, state: Optional[typing.TState] = None ) -> typing.Disposable: """Schedules an action to be executed after duetime. Args: duetime: Relative time after which to execute the action. action: Action to be executed. state: [Optional] state to be given to the action function. Returns: The disposable object used to cancel the scheduled action (best effort). """ sad = SingleAssignmentDisposable() def invoke_action() -> None: sad.disposable = self.invoke_action(action, state=state) msecs = max(0, int(self.to_seconds(duetime) * 1000.0)) timer = self._root.after(msecs, invoke_action) def dispose() -> None: self._root.after_cancel(timer) return CompositeDisposable(sad, Disposable(dispose)) def schedule_absolute(self, duetime: typing.AbsoluteTime, action: typing.ScheduledAction, state: Optional[typing.TState] = None ) -> typing.Disposable: """Schedules an action to be executed at duetime. Args: duetime: Absolute time at which to execute the action. action: Action to be executed. state: [Optional] state to be given to the action function. Returns: The disposable object used to cancel the scheduled action (best effort). """ duetime = self.to_datetime(duetime) return self.schedule_relative(duetime - self.now, action, state=state) RxPY-3.2.0/rx/scheduler/mainloop/wxscheduler.py000066400000000000000000000120441404130727200215130ustar00rootroot00000000000000import logging from typing import cast, Any, Optional, Set from rx.core import typing from rx.disposable import CompositeDisposable, Disposable, SingleAssignmentDisposable from ..periodicscheduler import PeriodicScheduler log = logging.getLogger("Rx") class WxScheduler(PeriodicScheduler): """A scheduler for a wxPython event loop.""" def __init__(self, wx: Any) -> None: """Create a new WxScheduler. Args: wx: The wx module to use; typically, you would get this by import wx """ super().__init__() self._wx = wx timer_class: Any = self._wx.Timer class Timer(timer_class): def __init__(self, callback) -> None: super().__init__() self.callback = callback def Notify(self): self.callback() self._timer_class = Timer self._timers: Set[Timer] = set() def cancel_all(self) -> None: """Cancel all scheduled actions. Should be called when destroying wx controls to prevent accessing dead wx objects in actions that might be pending. """ for timer in self._timers: timer.Stop() def _wxtimer_schedule(self, time: typing.AbsoluteOrRelativeTime, action: typing.ScheduledSingleOrPeriodicAction, state: Optional[typing.TState] = None, periodic: bool = False ) -> typing.Disposable: scheduler = self sad = SingleAssignmentDisposable() def interval() -> None: nonlocal state if periodic: state = cast(typing.ScheduledPeriodicAction, action)(state) else: sad.disposable = cast(typing.ScheduledAction, action)(scheduler, state) msecs = max(1, int(self.to_seconds(time) * 1000.0)) # Must be non-zero log.debug("timeout wx: %s", msecs) timer = self._timer_class(interval) timer.Start( msecs, self._wx.TIMER_CONTINUOUS if periodic else self._wx.TIMER_ONE_SHOT ) self._timers.add(timer) def dispose() -> None: timer.Stop() self._timers.remove(timer) return CompositeDisposable(sad, Disposable(dispose)) def schedule(self, action: typing.ScheduledAction, state: Optional[typing.TState] = None ) -> typing.Disposable: """Schedules an action to be executed. Args: action: Action to be executed. state: [Optional] state to be given to the action function. Returns: The disposable object used to cancel the scheduled action (best effort). """ return self._wxtimer_schedule(0.0, action, state=state) def schedule_relative(self, duetime: typing.RelativeTime, action: typing.ScheduledAction, state: Optional[typing.TState] = None ) -> typing.Disposable: """Schedules an action to be executed after duetime. Args: duetime: Relative time after which to execute the action. action: Action to be executed. state: [Optional] state to be given to the action function. Returns: The disposable object used to cancel the scheduled action (best effort). """ return self._wxtimer_schedule(duetime, action, state=state) def schedule_absolute(self, duetime: typing.AbsoluteTime, action: typing.ScheduledAction, state: Optional[typing.TState] = None ) -> typing.Disposable: """Schedules an action to be executed at duetime. Args: duetime: Absolute time at which to execute the action. action: Action to be executed. state: [Optional] state to be given to the action function. Returns: The disposable object used to cancel the scheduled action (best effort). """ duetime = self.to_datetime(duetime) return self._wxtimer_schedule(duetime - self.now, action, state=state) def schedule_periodic(self, period: typing.RelativeTime, action: typing.ScheduledPeriodicAction, state: Optional[typing.TState] = None ) -> typing.Disposable: """Schedules a periodic piece of work to be executed in the loop. Args: period: Period in seconds for running the work repeatedly. action: Action to be executed. state: [Optional] state to be given to the action function. Returns: The disposable object used to cancel the scheduled action (best effort). """ return self._wxtimer_schedule(period, action, state=state, periodic=True) RxPY-3.2.0/rx/scheduler/newthreadscheduler.py000066400000000000000000000102741404130727200212230ustar00rootroot00000000000000import logging import threading from datetime import datetime from typing import Optional from rx.core import typing from rx.disposable import Disposable from rx.internal.concurrency import default_thread_factory from .eventloopscheduler import EventLoopScheduler from .periodicscheduler import PeriodicScheduler log = logging.getLogger('Rx') class NewThreadScheduler(PeriodicScheduler): """Creates an object that schedules each unit of work on a separate thread. """ def __init__(self, thread_factory: Optional[typing.StartableFactory] = None) -> None: super().__init__() self.thread_factory: typing.StartableFactory = thread_factory or default_thread_factory def schedule(self, action: typing.ScheduledAction, state: Optional[typing.TState] = None ) -> typing.Disposable: """Schedules an action to be executed. Args: action: Action to be executed. state: [Optional] state to be given to the action function. Returns: The disposable object used to cancel the scheduled action (best effort). """ scheduler = EventLoopScheduler(thread_factory=self.thread_factory, exit_if_empty=True) return scheduler.schedule(action, state) def schedule_relative(self, duetime: typing.RelativeTime, action: typing.ScheduledAction, state: Optional[typing.TState] = None ) -> typing.Disposable: """Schedules an action to be executed after duetime. Args: duetime: Relative time after which to execute the action. action: Action to be executed. state: [Optional] state to be given to the action function. Returns: The disposable object used to cancel the scheduled action (best effort). """ scheduler = EventLoopScheduler(thread_factory=self.thread_factory, exit_if_empty=True) return scheduler.schedule_relative(duetime, action, state) def schedule_absolute(self, duetime: typing.AbsoluteTime, action: typing.ScheduledAction, state: Optional[typing.TState] = None ) -> typing.Disposable: """Schedules an action to be executed at duetime. Args: duetime: Absolute time at which to execute the action. action: Action to be executed. state: [Optional] state to be given to the action function. Returns: The disposable object used to cancel the scheduled action (best effort). """ dt = self.to_datetime(duetime) return self.schedule_relative(dt - self.now, action, state=state) def schedule_periodic(self, period: typing.RelativeTime, action: typing.ScheduledPeriodicAction, state: Optional[typing.TState] = None ) -> typing.Disposable: """Schedules a periodic piece of work. Args: period: Period in seconds or timedelta for running the work periodically. action: Action to be executed. state: [Optional] Initial state passed to the action upon the first iteration. Returns: The disposable object used to cancel the scheduled recurring action (best effort). """ seconds: float = self.to_seconds(period) timeout: float = seconds disposed: threading.Event = threading.Event() def run() -> None: nonlocal state, timeout while True: if timeout > 0.0: disposed.wait(timeout) if disposed.is_set(): return time: datetime = self.now state = action(state) timeout = seconds - (self.now - time).total_seconds() thread = self.thread_factory(run) thread.start() def dispose() -> None: disposed.set() return Disposable(dispose) RxPY-3.2.0/rx/scheduler/periodicscheduler.py000066400000000000000000000074251404130727200210440ustar00rootroot00000000000000from abc import abstractmethod from datetime import datetime from typing import Optional from rx.core import typing from rx.disposable import Disposable, MultipleAssignmentDisposable from .scheduler import Scheduler class PeriodicScheduler(Scheduler, typing.PeriodicScheduler): """Base class for the various periodic scheduler implementations in this package as well as the mainloop sub-package. """ def schedule_periodic(self, period: typing.RelativeTime, action: typing.ScheduledPeriodicAction, state: Optional[typing.TState] = None ) -> typing.Disposable: """Schedules a periodic piece of work. Args: period: Period in seconds or timedelta for running the work periodically. action: Action to be executed. state: [Optional] Initial state passed to the action upon the first iteration. Returns: The disposable object used to cancel the scheduled recurring action (best effort). """ disp: MultipleAssignmentDisposable = MultipleAssignmentDisposable() seconds: float = self.to_seconds(period) def periodic(scheduler: typing.Scheduler, state: Optional[typing.TState] = None ) -> Optional[Disposable]: if disp.is_disposed: return None now: datetime = scheduler.now try: state = action(state) except Exception: disp.dispose() raise time = seconds - (scheduler.now - now).total_seconds() disp.disposable = scheduler.schedule_relative(time, periodic, state=state) return None disp.disposable = self.schedule_relative(period, periodic, state=state) return disp @abstractmethod def schedule(self, action: typing.ScheduledAction, state: Optional[typing.TState] = None ) -> typing.Disposable: """Schedules an action to be executed. Args: action: Action to be executed. state: [Optional] state to be given to the action function. Returns: The disposable object used to cancel the scheduled action (best effort). """ return NotImplemented @abstractmethod def schedule_relative(self, duetime: typing.RelativeTime, action: typing.ScheduledAction, state: Optional[typing.TState] = None ) -> typing.Disposable: """Schedules an action to be executed after duetime. Args: duetime: Relative time after which to execute the action. action: Action to be executed. state: [Optional] state to be given to the action function. Returns: The disposable object used to cancel the scheduled action (best effort). """ return NotImplemented @abstractmethod def schedule_absolute(self, duetime: typing.AbsoluteTime, action: typing.ScheduledAction, state: Optional[typing.TState] = None ) -> typing.Disposable: """Schedules an action to be executed at duetime. Args: duetime: Absolute time at which to execute the action. action: Action to be executed. state: [Optional] state to be given to the action function. Returns: The disposable object used to cancel the scheduled action (best effort). """ return NotImplemented RxPY-3.2.0/rx/scheduler/scheduleditem.py000066400000000000000000000026671404130727200201710ustar00rootroot00000000000000from datetime import datetime from typing import Any, Optional from rx.core import typing from rx.disposable import SingleAssignmentDisposable from .scheduler import Scheduler class ScheduledItem(object): def __init__(self, scheduler: Scheduler, state: Optional[Any], action: typing.ScheduledAction, duetime: datetime ) -> None: self.scheduler: Scheduler = scheduler self.state: Optional[Any] = state self.action: typing.ScheduledAction = action self.duetime: datetime = duetime self.disposable: SingleAssignmentDisposable = SingleAssignmentDisposable() def invoke(self) -> None: ret = self.scheduler.invoke_action(self.action, state=self.state) self.disposable.disposable = ret def cancel(self) -> None: """Cancels the work item by disposing the resource returned by invoke_core as soon as possible.""" self.disposable.dispose() def is_cancelled(self) -> bool: return self.disposable.is_disposed def __lt__(self, other: 'ScheduledItem') -> bool: return self.duetime < other.duetime def __gt__(self, other: 'ScheduledItem') -> bool: return self.duetime > other.duetime def __eq__(self, other: Any) -> bool: try: return self.duetime == other.duetime except AttributeError: return NotImplemented RxPY-3.2.0/rx/scheduler/scheduler.py000066400000000000000000000126241404130727200173220ustar00rootroot00000000000000from abc import abstractmethod from datetime import datetime, timedelta from typing import Optional from rx.core import typing from rx.disposable import Disposable from rx.internal.basic import default_now from rx.internal.constants import UTC_ZERO class Scheduler(typing.Scheduler): """Base class for the various scheduler implementations in this package as well as the mainloop sub-package. This does not include an implementation of schedule_periodic, refer to PeriodicScheduler. """ @property def now(self) -> datetime: """Represents a notion of time for this scheduler. Tasks being scheduled on a scheduler will adhere to the time denoted by this property. Returns: The scheduler's current time, as a datetime instance. """ return default_now() @abstractmethod def schedule(self, action: typing.ScheduledAction, state: Optional[typing.TState] = None ) -> typing.Disposable: """Schedules an action to be executed. Args: action: Action to be executed. state: [Optional] state to be given to the action function. Returns: The disposable object used to cancel the scheduled action (best effort). """ return NotImplemented @abstractmethod def schedule_relative(self, duetime: typing.RelativeTime, action: typing.ScheduledAction, state: Optional[typing.TState] = None ) -> typing.Disposable: """Schedules an action to be executed after duetime. Args: duetime: Relative time after which to execute the action. action: Action to be executed. state: [Optional] state to be given to the action function. Returns: The disposable object used to cancel the scheduled action (best effort). """ return NotImplemented @abstractmethod def schedule_absolute(self, duetime: typing.AbsoluteTime, action: typing.ScheduledAction, state: Optional[typing.TState] = None ) -> typing.Disposable: """Schedules an action to be executed at duetime. Args: duetime: Absolute time at which to execute the action. action: Action to be executed. state: [Optional] state to be given to the action function. Returns: The disposable object used to cancel the scheduled action (best effort). """ return NotImplemented def invoke_action(self, action: typing.ScheduledAction, state: Optional[typing.TState] = None ) -> typing.Disposable: """Invoke the given given action. This is typically called by instances of ScheduledItem. Args: action: Action to be executed. state: [Optional] state to be given to the action function. Returns: The disposable object returned by the action, if any; or a new (no-op) disposable otherwise. """ ret = action(self, state) if isinstance(ret, typing.Disposable): return ret return Disposable() @classmethod def to_seconds(cls, value: typing.AbsoluteOrRelativeTime) -> float: """Converts time value to seconds. This method handles both absolute (datetime) and relative (timedelta) values. If the argument is already a float, it is simply returned unchanged. Args: value: the time value to convert to seconds. Returns: The value converted to seconds. """ if isinstance(value, datetime): value = value - UTC_ZERO if isinstance(value, timedelta): value = value.total_seconds() return value @classmethod def to_datetime(cls, value: typing.AbsoluteOrRelativeTime) -> datetime: """Converts time value to datetime. This method handles both absolute (float) and relative (timedelta) values. If the argument is already a datetime, it is simply returned unchanged. Args: value: the time value to convert to datetime. Returns: The value converted to datetime. """ if isinstance(value, timedelta): value = UTC_ZERO + value elif not isinstance(value, datetime): value = datetime.utcfromtimestamp(value) return value @classmethod def to_timedelta(cls, value: typing.AbsoluteOrRelativeTime) -> timedelta: """Converts time value to timedelta. This method handles both absolute (datetime) and relative (float) values. If the argument is already a timedelta, it is simply returned unchanged. If the argument is an absolute time, the result value will be the timedelta since the epoch, January 1st, 1970, 00:00:00. Args: value: the time value to convert to timedelta. Returns: The value converted to timedelta. """ if isinstance(value, datetime): value = value - UTC_ZERO elif not isinstance(value, timedelta): value = timedelta(seconds=value) return value RxPY-3.2.0/rx/scheduler/threadpoolscheduler.py000066400000000000000000000023031404130727200213750ustar00rootroot00000000000000from concurrent.futures import Future, ThreadPoolExecutor from typing import Optional from rx.core.abc import Startable from rx.core import typing from .newthreadscheduler import NewThreadScheduler class ThreadPoolScheduler(NewThreadScheduler): """A scheduler that schedules work via the thread pool.""" class ThreadPoolThread(Startable): """Wraps a concurrent future as a thread.""" def __init__(self, executor: ThreadPoolExecutor, target: typing.StartableTarget): self.executor: ThreadPoolExecutor = executor self.target: typing.StartableTarget = target self.future: Optional[Future] = None def start(self) -> None: self.future = self.executor.submit(self.target) def cancel(self) -> None: if self.future: self.future.cancel() def __init__(self, max_workers: Optional[int] = None) -> None: self.executor: ThreadPoolExecutor = ThreadPoolExecutor(max_workers=max_workers) def thread_factory(target: typing.StartableTarget): return self.ThreadPoolThread(self.executor, target) super().__init__(thread_factory) RxPY-3.2.0/rx/scheduler/timeoutscheduler.py000066400000000000000000000070001404130727200207210ustar00rootroot00000000000000from threading import Lock, Timer from typing import MutableMapping, Optional from weakref import WeakKeyDictionary from rx.core import typing from rx.disposable import CompositeDisposable, Disposable, SingleAssignmentDisposable from .periodicscheduler import PeriodicScheduler class TimeoutScheduler(PeriodicScheduler): """A scheduler that schedules work via a timed callback.""" _lock = Lock() _global: MutableMapping[type, 'TimeoutScheduler'] = WeakKeyDictionary() @classmethod def singleton(cls) -> 'TimeoutScheduler': with TimeoutScheduler._lock: try: self = TimeoutScheduler._global[cls] except KeyError: self = super().__new__(cls) TimeoutScheduler._global[cls] = self return self def __new__(cls) -> 'TimeoutScheduler': return cls.singleton() def schedule(self, action: typing.ScheduledAction, state: Optional[typing.TState] = None ) -> typing.Disposable: """Schedules an action to be executed. Args: action: Action to be executed. state: [Optional] state to be given to the action function. Returns: The disposable object used to cancel the scheduled action (best effort). """ sad = SingleAssignmentDisposable() def interval() -> None: sad.disposable = self.invoke_action(action, state) timer = Timer(0, interval) timer.setDaemon(True) timer.start() def dispose() -> None: timer.cancel() return CompositeDisposable(sad, Disposable(dispose)) def schedule_relative(self, duetime: typing.RelativeTime, action: typing.ScheduledAction, state: Optional[typing.TState] = None ) -> typing.Disposable: """Schedules an action to be executed after duetime. Args: duetime: Relative time after which to execute the action. action: Action to be executed. state: [Optional] state to be given to the action function. Returns: The disposable object used to cancel the scheduled action (best effort). """ seconds = self.to_seconds(duetime) if seconds <= 0.0: return self.schedule(action, state) sad = SingleAssignmentDisposable() def interval() -> None: sad.disposable = self.invoke_action(action, state) timer = Timer(seconds, interval) timer.setDaemon(True) timer.start() def dispose() -> None: timer.cancel() return CompositeDisposable(sad, Disposable(dispose)) def schedule_absolute(self, duetime: typing.AbsoluteTime, action: typing.ScheduledAction, state: Optional[typing.TState] = None ) -> typing.Disposable: """Schedules an action to be executed at duetime. Args: duetime: Absolute time at which to execute the action. action: Action to be executed. state: [Optional] state to be given to the action function. Returns: The disposable object used to cancel the scheduled action (best effort). """ duetime = self.to_datetime(duetime) return self.schedule_relative(duetime - self.now, action, state) RxPY-3.2.0/rx/scheduler/trampoline.py000066400000000000000000000034261404130727200175160ustar00rootroot00000000000000from collections import deque from threading import Condition, Lock from typing import Deque from rx.internal.priorityqueue import PriorityQueue from .scheduleditem import ScheduledItem class Trampoline(object): def __init__(self): self._idle: bool = True self._queue: PriorityQueue[ScheduledItem] = PriorityQueue() self._lock: Lock = Lock() self._condition: Condition = Condition(self._lock) def idle(self) -> bool: with self._lock: return self._idle def run(self, item: ScheduledItem) -> None: with self._lock: self._queue.enqueue(item) if self._idle: self._idle = False else: self._condition.notify() return try: self._run() finally: with self._lock: self._idle = True self._queue.clear() def _run(self) -> None: ready: Deque[ScheduledItem] = deque() while True: with self._lock: while len(self._queue) > 0: item: ScheduledItem = self._queue.peek() if item.duetime <= item.scheduler.now: self._queue.dequeue() ready.append(item) else: break while len(ready) > 0: item = ready.popleft() if not item.is_cancelled(): item.invoke() with self._lock: if len(self._queue) == 0: break item = self._queue.peek() seconds = (item.duetime - item.scheduler.now).total_seconds() if seconds > 0.0: self._condition.wait(seconds) RxPY-3.2.0/rx/scheduler/trampolinescheduler.py000066400000000000000000000073411404130727200214150ustar00rootroot00000000000000import logging from typing import Optional from rx.core import typing from rx.internal.constants import DELTA_ZERO from .scheduleditem import ScheduledItem from .scheduler import Scheduler from .trampoline import Trampoline log = logging.getLogger('Rx') class TrampolineScheduler(Scheduler): """Represents an object that schedules units of work on the trampoline. You should never schedule timeouts using the *TrampolineScheduler*, as it will block the thread while waiting. Each instance has its own trampoline (and queue), and you can schedule work on it from different threads. Beware though, that the first thread to call a *schedule* method while the trampoline is idle will then remain occupied until the queue is empty. """ def __init__(self) -> None: """Creates a scheduler that bounces its work off the trampoline.""" self._tramp = Trampoline() def get_trampoline(self) -> Trampoline: return self._tramp def schedule(self, action: typing.ScheduledAction, state: Optional[typing.TState] = None ) -> typing.Disposable: """Schedules an action to be executed. Args: action: Action to be executed. state: [Optional] state to be given to the action function. Returns: The disposable object used to cancel the scheduled action (best effort). """ return self.schedule_absolute(self.now, action, state=state) def schedule_relative(self, duetime: typing.RelativeTime, action: typing.ScheduledAction, state: Optional[typing.TState] = None ) -> typing.Disposable: """Schedules an action to be executed after duetime. Args: duetime: Relative time after which to execute the action. action: Action to be executed. state: [Optional] state to be given to the action function. Returns: The disposable object used to cancel the scheduled action (best effort). """ duetime = max(DELTA_ZERO, self.to_timedelta(duetime)) return self.schedule_absolute(self.now + duetime, action, state=state) def schedule_absolute(self, duetime: typing.AbsoluteTime, action: typing.ScheduledAction, state: Optional[typing.TState] = None ) -> typing.Disposable: """Schedules an action to be executed at duetime. Args: duetime: Absolute time at which to execute the action. action: Action to be executed. state: [Optional] state to be given to the action function. Returns: The disposable object used to cancel the scheduled action (best effort). """ dt = self.to_datetime(duetime) if dt > self.now: log.warning('Do not schedule blocking work!') item: ScheduledItem = ScheduledItem(self, state, action, dt) self.get_trampoline().run(item) return item.disposable def schedule_required(self) -> bool: """Test if scheduling is required. Gets a value indicating whether the caller must call a schedule method. If the trampoline is active, then it returns False; otherwise, if the trampoline is not active, then it returns True. """ return self.get_trampoline().idle() def ensure_trampoline(self, action): """Method for testing the TrampolineScheduler.""" if self.schedule_required(): return self.schedule(action) return action(self, None) RxPY-3.2.0/rx/scheduler/virtualtimescheduler.py000066400000000000000000000166341404130727200216150ustar00rootroot00000000000000import logging import threading from abc import abstractmethod from datetime import datetime from typing import Any, Optional from rx.internal import PriorityQueue, ArgumentOutOfRangeException from rx.core import typing from .periodicscheduler import PeriodicScheduler from .scheduleditem import ScheduledItem log = logging.getLogger("Rx") MAX_SPINNING = 100 class VirtualTimeScheduler(PeriodicScheduler): """Virtual Scheduler. This scheduler should work with either datetime/timespan or ticks as int/int""" def __init__(self, initial_clock=0) -> None: """Creates a new virtual time scheduler with the specified initial clock value. Args: initial_clock: Initial value for the clock. """ super().__init__() self._clock = initial_clock self._is_enabled = False self._lock: threading.Lock = threading.Lock() self._queue: PriorityQueue[ScheduledItem] = PriorityQueue() def _get_clock(self): with self._lock: return self._clock clock = property(fget=_get_clock) @property def now(self) -> datetime: """Represents a notion of time for this scheduler. Tasks being scheduled on a scheduler will adhere to the time denoted by this property. Returns: The scheduler's current time, as a datetime instance. """ return self.to_datetime(self._clock) def schedule(self, action: typing.ScheduledAction, state: Optional[typing.TState] = None ) -> typing.Disposable: """Schedules an action to be executed. Args: action: Action to be executed. state: [Optional] state to be given to the action function. Returns: The disposable object used to cancel the scheduled action (best effort). """ return self.schedule_absolute(self._clock, action, state=state) def schedule_relative(self, duetime: typing.RelativeTime, action: typing.ScheduledAction, state: Optional[typing.TState] = None ) -> typing.Disposable: """Schedules an action to be executed after duetime. Args: duetime: Relative time after which to execute the action. action: Action to be executed. state: [Optional] state to be given to the action function. Returns: The disposable object used to cancel the scheduled action (best effort). """ time: typing.AbsoluteTime = self.add(self._clock, self.to_seconds(duetime)) return self.schedule_absolute(time, action, state=state) def schedule_absolute(self, duetime: typing.AbsoluteTime, action: typing.ScheduledAction, state: Optional[typing.TState] = None ) -> typing.Disposable: """Schedules an action to be executed at duetime. Args: duetime: Absolute time at which to execute the action. action: Action to be executed. state: [Optional] state to be given to the action function. Returns: The disposable object used to cancel the scheduled action (best effort). """ dt = self.to_datetime(duetime) si: ScheduledItem = ScheduledItem(self, state, action, dt) with self._lock: self._queue.enqueue(si) return si.disposable def start(self) -> None: """Starts the virtual time scheduler.""" with self._lock: if self._is_enabled: return self._is_enabled = True spinning: int = 0 while True: with self._lock: if not self._is_enabled or not self._queue: break item: ScheduledItem = self._queue.dequeue() if item.duetime > self.now: if isinstance(self._clock, datetime): self._clock = item.duetime else: self._clock = self.to_seconds(item.duetime) spinning = 0 elif spinning > MAX_SPINNING: self._clock += 1.0 spinning = 0 if not item.is_cancelled(): item.invoke() spinning += 1 self.stop() def stop(self) -> None: """Stops the virtual time scheduler.""" with self._lock: self._is_enabled = False def advance_to(self, time: typing.AbsoluteTime) -> None: """Advances the schedulers clock to the specified absolute time, running all work til that point. Args: time: Absolute time to advance the schedulers clock to. """ dt: datetime = self.to_datetime(time) with self._lock: if self.now > dt: raise ArgumentOutOfRangeException() if self.now == dt or self._is_enabled: return self._is_enabled = True while True: with self._lock: if not self._is_enabled or not self._queue: break item: ScheduledItem = self._queue.peek() if item.duetime > dt: break if item.duetime > self.now: if isinstance(self._clock, datetime): self._clock = item.duetime else: self._clock = self.to_seconds(item.duetime) self._queue.dequeue() if not item.is_cancelled(): item.invoke() with self._lock: self._is_enabled = False if isinstance(self._clock, datetime): self._clock = dt else: self._clock = self.to_seconds(dt) def advance_by(self, time: typing.RelativeTime) -> None: """Advances the schedulers clock by the specified relative time, running all work scheduled for that timespan. Args: time: Relative time to advance the schedulers clock by. """ log.debug("VirtualTimeScheduler.advance_by(time=%s)", time) self.advance_to(self.add(self.now, self.to_timedelta(time))) def sleep(self, time: typing.RelativeTime) -> None: """Advances the schedulers clock by the specified relative time. Args: time: Relative time to advance the schedulers clock by. """ absolute = self.add(self.now, self.to_timedelta(time)) dt: datetime = self.to_datetime(absolute) if self.now > dt: raise ArgumentOutOfRangeException() with self._lock: if isinstance(self._clock, datetime): self._clock = dt else: self._clock = self.to_seconds(dt) @classmethod @abstractmethod def add(cls, absolute: typing.AbsoluteTime, relative: typing.RelativeTime ) -> typing.AbsoluteTime: """Adds a relative time value to an absolute time value. Args: absolute: Absolute virtual time value. relative: Relative virtual time value to add. Returns: The resulting absolute virtual time sum value. """ raise NotImplementedError RxPY-3.2.0/rx/subject/000077500000000000000000000000001404130727200144465ustar00rootroot00000000000000RxPY-3.2.0/rx/subject/__init__.py000066400000000000000000000002321404130727200165540ustar00rootroot00000000000000from .subject import Subject from .asyncsubject import AsyncSubject from .behaviorsubject import BehaviorSubject from .replaysubject import ReplaySubject RxPY-3.2.0/rx/subject/asyncsubject.py000066400000000000000000000047671404130727200175330ustar00rootroot00000000000000from typing import Any, Optional from rx.disposable import Disposable from rx.core import typing from .subject import Subject from .innersubscription import InnerSubscription class AsyncSubject(Subject): """Represents the result of an asynchronous operation. The last value before the close notification, or the error received through on_error, is sent to all subscribed observers.""" def __init__(self) -> None: """Creates a subject that can only receive one value and that value is cached for all future observations.""" super().__init__() self.value = None self.has_value = False def _subscribe_core(self, observer: typing.Observer, scheduler: Optional[typing.Scheduler] = None ) -> typing.Disposable: with self.lock: self.check_disposed() if not self.is_stopped: self.observers.append(observer) return InnerSubscription(self, observer) ex = self.exception has_value = self.has_value value = self.value if ex: observer.on_error(ex) elif has_value: observer.on_next(value) observer.on_completed() else: observer.on_completed() return Disposable() def _on_next_core(self, value: Any) -> None: """Remember the value. Upon completion, the most recently received value will be passed on to all subscribed observers. Args: value: The value to remember until completion """ with self.lock: self.value = value self.has_value = True def _on_completed_core(self) -> None: """Notifies all subscribed observers of the end of the sequence. The most recently received value, if any, will now be passed on to all subscribed observers.""" with self.lock: observers = self.observers.copy() self.observers.clear() value = self.value has_value = self.has_value if has_value: for observer in observers: observer.on_next(value) observer.on_completed() else: for observer in observers: observer.on_completed() def dispose(self) -> None: """Unsubscribe all observers and release resources.""" with self.lock: self.value = None super().dispose() RxPY-3.2.0/rx/subject/behaviorsubject.py000066400000000000000000000034251404130727200202030ustar00rootroot00000000000000from typing import Any from rx.disposable import Disposable from .subject import Subject from .innersubscription import InnerSubscription class BehaviorSubject(Subject): """Represents a value that changes over time. Observers can subscribe to the subject to receive the last (or initial) value and all subsequent notifications. """ def __init__(self, value) -> None: """Initializes a new instance of the BehaviorSubject class which creates a subject that caches its last value and starts with the specified value. Args: value: Initial value sent to observers when no other value has been received by the subject yet. """ super().__init__() self.value = value def _subscribe_core(self, observer, scheduler=None): with self.lock: self.check_disposed() if not self.is_stopped: self.observers.append(observer) observer.on_next(self.value) return InnerSubscription(self, observer) ex = self.exception if ex: observer.on_error(ex) else: observer.on_completed() return Disposable() def _on_next_core(self, value: Any) -> None: """Notifies all subscribed observers with the value.""" with self.lock: observers = self.observers.copy() self.value = value for observer in observers: observer.on_next(value) def dispose(self) -> None: """Release all resources. Releases all resources used by the current instance of the BehaviorSubject class and unsubscribe all observers. """ with self.lock: self.value = None super().dispose() RxPY-3.2.0/rx/subject/innersubscription.py000066400000000000000000000010061404130727200205750ustar00rootroot00000000000000import threading from rx.core import typing class InnerSubscription(typing.Disposable): def __init__(self, subject, observer): self.subject = subject self.observer = observer self.lock = threading.RLock() def dispose(self) -> None: with self.lock: if not self.subject.is_disposed and self.observer: if self.observer in self.subject.observers: self.subject.observers.remove(self.observer) self.observer = None RxPY-3.2.0/rx/subject/replaysubject.py000066400000000000000000000105771404130727200177060ustar00rootroot00000000000000import sys from datetime import datetime from typing import cast, Any, Optional, List, NamedTuple from datetime import timedelta from rx.core import typing from rx.scheduler import CurrentThreadScheduler from rx.core.observer.scheduledobserver import ScheduledObserver from .subject import Subject class RemovableDisposable(typing.Disposable): def __init__(self, subject, observer): self.subject = subject self.observer = observer def dispose(self): self.observer.dispose() if not self.subject.is_disposed and self.observer in self.subject.observers: self.subject.observers.remove(self.observer) class QueueItem(NamedTuple): interval: datetime value: Any class ReplaySubject(Subject): """Represents an object that is both an observable sequence as well as an observer. Each notification is broadcasted to all subscribed and future observers, subject to buffer trimming policies. """ def __init__(self, buffer_size: int = None, window: typing.RelativeTime = None, scheduler: Optional[typing.Scheduler] = None ) -> None: """Initializes a new instance of the ReplaySubject class with the specified buffer size, window and scheduler. Args: buffer_size: [Optional] Maximum element count of the replay buffer. window [Optional]: Maximum time length of the replay buffer. scheduler: [Optional] Scheduler the observers are invoked on. """ super().__init__() self.buffer_size = sys.maxsize if buffer_size is None else buffer_size self.scheduler = scheduler or CurrentThreadScheduler.singleton() self.window = timedelta.max if window is None else self.scheduler.to_timedelta(window) self.queue: List[QueueItem] = [] def _subscribe_core(self, observer: typing.Observer, scheduler: Optional[typing.Scheduler] = None ) -> typing.Disposable: so = ScheduledObserver(self.scheduler, observer) subscription = RemovableDisposable(self, so) with self.lock: self.check_disposed() self._trim(self.scheduler.now) self.observers.append(so) for item in self.queue: so.on_next(item.value) if self.exception is not None: so.on_error(self.exception) elif self.is_stopped: so.on_completed() so.ensure_active() return subscription def _trim(self, now: datetime): while len(self.queue) > self.buffer_size: self.queue.pop(0) while self.queue and (now - self.queue[0].interval) > self.window: self.queue.pop(0) def _on_next_core(self, value: Any) -> None: """Notifies all subscribed observers with the value.""" with self.lock: observers = self.observers.copy() now = self.scheduler.now self.queue.append(QueueItem(interval=now, value=value)) self._trim(now) for observer in observers: observer.on_next(value) for observer in observers: cast(ScheduledObserver, observer).ensure_active() def _on_error_core(self, error: Exception) -> None: """Notifies all subscribed observers with the exception.""" with self.lock: observers = self.observers.copy() self.observers.clear() self.exception = error now = self.scheduler.now self._trim(now) for observer in observers: observer.on_error(error) cast(ScheduledObserver, observer).ensure_active() def _on_completed_core(self) -> None: """Notifies all subscribed observers of the end of the sequence.""" with self.lock: observers = self.observers.copy() self.observers.clear() now = self.scheduler.now self._trim(now) for observer in observers: observer.on_completed() cast(ScheduledObserver, observer).ensure_active() def dispose(self) -> None: """Releases all resources used by the current instance of the ReplaySubject class and unsubscribe all observers.""" with self.lock: self.queue.clear() super().dispose() RxPY-3.2.0/rx/subject/subject.py000066400000000000000000000060441404130727200164630ustar00rootroot00000000000000import threading from typing import Any, List, Optional from rx.disposable import Disposable from rx.core import Observable, Observer, typing from rx.internal import DisposedException from .innersubscription import InnerSubscription class Subject(Observable, Observer, typing.Subject): """Represents an object that is both an observable sequence as well as an observer. Each notification is broadcasted to all subscribed observers. """ def __init__(self) -> None: super().__init__() self.is_disposed = False self.observers: List[typing.Observer] = [] self.exception: Optional[Exception] = None self.lock = threading.RLock() def check_disposed(self) -> None: if self.is_disposed: raise DisposedException() def _subscribe_core(self, observer: typing.Observer, scheduler: Optional[typing.Scheduler] = None ) -> typing.Disposable: with self.lock: self.check_disposed() if not self.is_stopped: self.observers.append(observer) return InnerSubscription(self, observer) if self.exception is not None: observer.on_error(self.exception) else: observer.on_completed() return Disposable() def on_next(self, value: Any) -> None: """Notifies all subscribed observers with the value. Args: value: The value to send to all subscribed observers. """ with self.lock: self.check_disposed() super().on_next(value) def _on_next_core(self, value: Any) -> None: with self.lock: observers = self.observers.copy() for observer in observers: observer.on_next(value) def on_error(self, error: Exception) -> None: """Notifies all subscribed observers with the exception. Args: error: The exception to send to all subscribed observers. """ with self.lock: self.check_disposed() super().on_error(error) def _on_error_core(self, error: Exception) -> None: with self.lock: observers = self.observers.copy() self.observers.clear() self.exception = error for observer in observers: observer.on_error(error) def on_completed(self) -> None: """Notifies all subscribed observers of the end of the sequence.""" with self.lock: self.check_disposed() super().on_completed() def _on_completed_core(self) -> None: with self.lock: observers = self.observers.copy() self.observers.clear() for observer in observers: observer.on_completed() def dispose(self) -> None: """Unsubscribe all observers and release resources.""" with self.lock: self.is_disposed = True self.observers = [] self.exception = None super().dispose() RxPY-3.2.0/rx/testing/000077500000000000000000000000001404130727200144645ustar00rootroot00000000000000RxPY-3.2.0/rx/testing/__init__.py000066400000000000000000000003071404130727200165750ustar00rootroot00000000000000from .testscheduler import TestScheduler from .reactivetest import OnNextPredicate, OnErrorPredicate, ReactiveTest, is_prime from .mockdisposable import MockDisposable from .recorded import Recorded RxPY-3.2.0/rx/testing/coldobservable.py000066400000000000000000000026461404130727200200340ustar00rootroot00000000000000from typing import List from rx.disposable import Disposable, CompositeDisposable from rx.core import Observable, typing from rx.scheduler import VirtualTimeScheduler from .subscription import Subscription class ColdObservable(Observable): def __init__(self, scheduler: VirtualTimeScheduler, messages) -> None: super().__init__() self.scheduler: VirtualTimeScheduler = scheduler self.messages = messages self.subscriptions: List[Subscription] = [] def _subscribe_core(self, observer=None, scheduler=None) -> typing.Disposable: self.subscriptions.append(Subscription(self.scheduler.clock)) index = len(self.subscriptions) - 1 disp = CompositeDisposable() def get_action(notification): def action(scheduler, state): notification.accept(observer) return Disposable() return action for message in self.messages: notification = message.value # Don't make closures within a loop action = get_action(notification) disp.add(self.scheduler.schedule_relative(message.time, action)) def dispose() -> None: start = self.subscriptions[index].subscribe end = self.scheduler.to_seconds(self.scheduler.now) self.subscriptions[index] = Subscription(start, end) disp.dispose() return Disposable(dispose) RxPY-3.2.0/rx/testing/hotobservable.py000066400000000000000000000031041404130727200176730ustar00rootroot00000000000000from typing import List from rx.disposable import Disposable from rx.core import Observable, typing from rx.scheduler import VirtualTimeScheduler from .recorded import Recorded from .subscription import Subscription class HotObservable(Observable): def __init__(self, scheduler: VirtualTimeScheduler, messages: List[Recorded]) -> None: super().__init__() self.scheduler: VirtualTimeScheduler = scheduler self.messages = messages self.subscriptions: List[Subscription] = [] self.observers: List[typing.Observer] = [] observable = self def get_action(notification): def action(scheduler, state): for observer in observable.observers[:]: notification.accept(observer) return Disposable() return action for message in self.messages: notification = message.value # Warning: Don't make closures within a loop action = get_action(notification) scheduler.schedule_absolute(message.time, action) def _subscribe_core(self, observer=None, scheduler=None) -> typing.Disposable: self.observers.append(observer) self.subscriptions.append(Subscription(self.scheduler.clock)) index = len(self.subscriptions) - 1 def dispose_action(): self.observers.remove(observer) start = self.subscriptions[index].subscribe end = self.scheduler.clock self.subscriptions[index] = Subscription(start, end) return Disposable(dispose_action) RxPY-3.2.0/rx/testing/marbles.py000066400000000000000000000110441404130727200164630ustar00rootroot00000000000000from typing import List, Tuple, Union, Dict from collections import namedtuple from contextlib import contextmanager from warnings import warn import rx from rx.core import Observable from rx.core.notification import Notification from rx.scheduler import NewThreadScheduler from rx.core.typing import Callable, RelativeTime from rx.testing import TestScheduler, Recorded, ReactiveTest from rx.core.observable.marbles import parse new_thread_scheduler = NewThreadScheduler() MarblesContext = namedtuple('MarblesContext', 'start, cold, hot, exp') @contextmanager def marbles_testing(timespan=1.0): """ Initialize a :class:`rx.testing.TestScheduler` and return a namedtuple containing the following functions that wrap its methods. :func:`cold()`: Parse a marbles string and return a cold observable :func:`hot()`: Parse a marbles string and return a hot observable :func:`start()`: Start the test scheduler, invoke the create function, subscribe to the resulting sequence, dispose the subscription and return the resulting records :func:`exp()`: Parse a marbles string and return a list of records Examples: >>> with marbles_testing() as (start, cold, hot, exp): ... obs = hot("-a-----b---c-|") ... ex = exp( "-a-----b---c-|") ... results = start(obs) ... assert results == ex The underlying test scheduler is initialized with the following parameters: - created time = 100.0s - subscribed = 200.0s - disposed = 1000.0s **IMPORTANT**: regarding :func:`hot()`, a marble declared as the first character will be skipped by the test scheduler. E.g. hot("a--b--") will only emit b. """ scheduler = TestScheduler() created = 100.0 disposed = 1000.0 subscribed = 200.0 start_called = False outside_of_context = False def check(): if outside_of_context: warn('context functions should not be called outside of ' 'with statement.', UserWarning, stacklevel=3, ) if start_called: warn('start() should only be called one time inside ' 'a with statement.', UserWarning, stacklevel=3, ) def test_start(create: Union[Observable, Callable[[], Observable]]) -> List[Recorded]: nonlocal start_called check() def default_create(): return create if isinstance(create, Observable): create_function = default_create else: create_function = create mock_observer = scheduler.start( create=create_function, created=created, subscribed=subscribed, disposed=disposed, ) start_called = True return mock_observer.messages def test_expected(string: str, lookup: Dict = None, error: Exception = None) -> List[Recorded]: messages = parse( string, timespan=timespan, time_shift=subscribed, lookup=lookup, error=error, ) return messages_to_records(messages) def test_cold(string: str, lookup: Dict = None, error: Exception = None) -> Observable: check() return rx.from_marbles( string, timespan=timespan, lookup=lookup, error=error, ) def test_hot(string: str, lookup: Dict = None, error: Exception = None) -> Observable: check() hot_obs = rx.hot( string, timespan=timespan, duetime=subscribed, lookup=lookup, error=error, scheduler=scheduler, ) return hot_obs try: yield MarblesContext(test_start, test_cold, test_hot, test_expected) finally: outside_of_context = True def messages_to_records(messages: List[Tuple[RelativeTime, Notification]] ) -> List[Recorded]: """ Helper function to convert messages returned by parse() to a list of Recorded. """ records = [] dispatcher = dict( N=lambda t, n: ReactiveTest.on_next(t, n.value), E=lambda t, n: ReactiveTest.on_error(t, n.exception), C=lambda t, n: ReactiveTest.on_completed(t) ) for message in messages: time, notification = message kind = notification.kind record = dispatcher[kind](time, notification) records.append(record) return records RxPY-3.2.0/rx/testing/mockdisposable.py000066400000000000000000000006401404130727200200350ustar00rootroot00000000000000from typing import List from rx.core import typing from rx.scheduler import VirtualTimeScheduler class MockDisposable: def __init__(self, scheduler: VirtualTimeScheduler): self.scheduler: VirtualTimeScheduler = scheduler self.disposes: List[typing.AbsoluteTime] = [] self.disposes.append(self.scheduler.clock) def dispose(self): self.disposes.append(self.scheduler.clock) RxPY-3.2.0/rx/testing/mockobserver.py000066400000000000000000000013751404130727200175450ustar00rootroot00000000000000from typing import Any, List from rx.core.typing import Observer from rx.core.notification import OnNext, OnError, OnCompleted from rx.scheduler import VirtualTimeScheduler from .recorded import Recorded class MockObserver(Observer): def __init__(self, scheduler: VirtualTimeScheduler) -> None: self.scheduler: VirtualTimeScheduler = scheduler self.messages: List[Recorded] = [] def on_next(self, value: Any) -> None: self.messages.append(Recorded(self.scheduler.clock, OnNext(value))) def on_error(self, error: Exception) -> None: self.messages.append(Recorded(self.scheduler.clock, OnError(error))) def on_completed(self) -> None: self.messages.append(Recorded(self.scheduler.clock, OnCompleted())) RxPY-3.2.0/rx/testing/reactivetest.py000066400000000000000000000035561404130727200175510ustar00rootroot00000000000000from typing import Any import math import types from rx.core.notification import OnNext, OnError, OnCompleted from .recorded import Recorded from .subscription import Subscription def is_prime(i: int) -> bool: """Tests if number is prime or not""" if i <= 1: return False _max = int(math.floor(math.sqrt(i))) for j in range(2, _max+1): if not i % j: return False return True # New predicate tests class OnNextPredicate: def __init__(self, predicate) -> None: self.predicate = predicate def __eq__(self, other): if other == self: return True if other is None: return False if other.kind != 'N': return False return self.predicate(other.value) class OnErrorPredicate: def __init__(self, predicate): self.predicate = predicate def __eq__(self, other): if other == self: return True if other is None: return False if other.kind != 'E': return False return self.predicate(other.exception) class ReactiveTest: created = 100 subscribed = 200 disposed = 1000 @staticmethod def on_next(ticks: int, value: Any) -> Recorded: if isinstance(value, types.FunctionType): return Recorded(ticks, OnNextPredicate(value)) return Recorded(ticks, OnNext(value)) @staticmethod def on_error(ticks: int, exception: Exception) -> Recorded: if isinstance(exception, types.FunctionType): return Recorded(ticks, OnErrorPredicate(exception)) return Recorded(ticks, OnError(exception)) @staticmethod def on_completed(ticks: int) -> Recorded: return Recorded(ticks, OnCompleted()) @staticmethod def subscribe(start: int, end: int) -> Subscription: return Subscription(start, end) RxPY-3.2.0/rx/testing/recorded.py000066400000000000000000000011561404130727200166300ustar00rootroot00000000000000from typing import Any from rx.internal.basic import default_comparer class Recorded: def __init__(self, time: int, value: Any, comparer=None): self.time = time self.value = value self.comparer = comparer or default_comparer def __eq__(self, other): """Returns true if a recorded value matches another recorded value""" time_match = self.time == other.time return time_match and self.comparer(self.value, other.value) equals = __eq__ def __repr__(self): return str(self) def __str__(self): return "%s@%s" % (self.value, self.time) RxPY-3.2.0/rx/testing/subscription.py000066400000000000000000000010661404130727200175650ustar00rootroot00000000000000import sys class Subscription(object): def __init__(self, start, end=None): self.subscribe = start self.unsubscribe = end or sys.maxsize def equals(self, other): return self.subscribe == other.subscribe and self.unsubscribe == other.unsubscribe def __eq__(self, other): return self.equals(other) def __repr__(self): return str(self) def __str__(self): unsubscribe = "Infinite" if self.unsubscribe == sys.maxsize else self.unsubscribe return "(%s, %s)" % (self.subscribe, unsubscribe) RxPY-3.2.0/rx/testing/testscheduler.py000066400000000000000000000120161404130727200177140ustar00rootroot00000000000000from typing import Callable, Any import rx from rx.disposable import Disposable from rx.core import Observable, typing from rx.scheduler import VirtualTimeScheduler from .coldobservable import ColdObservable from .hotobservable import HotObservable from .mockobserver import MockObserver from .reactivetest import ReactiveTest class TestScheduler(VirtualTimeScheduler): """Test time scheduler used for testing applications and libraries built using Reactive Extensions. All time, both absolute and relative is specified as integer ticks""" __test__ = False def schedule_absolute(self, duetime: typing.AbsoluteTime, action: Callable, state: Any = None) -> typing.Disposable: """Schedules an action to be executed at the specified virtual time. Args: duetime: Absolute virtual time at which to execute the action. action: Action to be executed. state: State passed to the action to be executed. Returns: Disposable object used to cancel the scheduled action (best effort). """ duetime = duetime if isinstance(duetime, float) else self.to_seconds(duetime) return super().schedule_absolute(duetime, action, state) @classmethod def add(cls, absolute, relative): """Adds a relative virtual time to an absolute virtual time value""" return absolute + relative def start(self, create=None, created=None, subscribed=None, disposed=None) -> MockObserver: # type: ignore """Starts the test scheduler and uses the specified virtual times to invoke the factory function, subscribe to the resulting sequence, and dispose the subscription. Args: create: Factory method to create an observable sequence. created: Virtual time at which to invoke the factory to create an observable sequence. subscribed: Virtual time at which to subscribe to the created observable sequence. disposed: Virtual time at which to dispose the subscription. Returns: Observer with timestamped recordings of notification messages that were received during the virtual time window when the subscription to the source sequence was active. """ # Defaults create = create or rx.never created = created or ReactiveTest.created subscribed = subscribed or ReactiveTest.subscribed disposed = disposed or ReactiveTest.disposed observer = self.create_observer() subscription = [None] source = [None] def action_create(scheduler, state): """Called at create time. Defaults to 100""" source[0] = create() return Disposable() self.schedule_absolute(created, action_create) def action_subscribe(scheduler, state): """Called at subscribe time. Defaults to 200""" subscription[0] = source[0].subscribe(observer, scheduler=scheduler) return Disposable() self.schedule_absolute(subscribed, action_subscribe) def action_dispose(scheduler, state): """Called at dispose time. Defaults to 1000""" subscription[0].dispose() return Disposable() self.schedule_absolute(disposed, action_dispose) super().start() return observer def create_hot_observable(self, *args) -> Observable: """Creates a hot observable using the specified timestamped notification messages either as a list or by multiple arguments. Args: messages: Notifications to surface through the created sequence at their specified absolute virtual times. Returns hot observable sequence that can be used to assert the timing of subscriptions and notifications. """ if args and isinstance(args[0], list): messages = args[0] else: messages = list(args) return HotObservable(self, messages) def create_cold_observable(self, *args) -> Observable: """Creates a cold observable using the specified timestamped notification messages either as an array or arguments. Args: args: Notifications to surface through the created sequence at their specified virtual time offsets from the sequence subscription time. Returns: Cold observable sequence that can be used to assert the timing of subscriptions and notifications. """ if args and isinstance(args[0], list): messages = args[0] else: messages = list(args) return ColdObservable(self, messages) def create_observer(self) -> MockObserver: """Creates an observer that records received notification messages and timestamps those. Return an Observer that can be used to assert the timing of received notifications. """ return MockObserver(self) RxPY-3.2.0/setup.cfg000066400000000000000000000001541404130727200141770ustar00rootroot00000000000000[egg_info] #tag_build = dev #tag_svn_revision = 1 [aliases] test = pytest [tool:pytest] testpaths = tests RxPY-3.2.0/setup.py000066400000000000000000000040671404130727200140770ustar00rootroot00000000000000#!/usr/bin/env python3 import sys try: from setuptools import setup except ImportError: from ez_setup import use_setuptools use_setuptools() from setuptools import setup from configparser import ConfigParser # General project metadata is stored in project.cfg with open('project.cfg') as project_file: config = ConfigParser() config.read_file(project_file) project_meta = dict(config.items('project')) # Populate the long_description field from README.rst with open('README.rst') as readme_file: project_meta['long_description'] = readme_file.read() needs_pytest = {'pytest', 'test', 'ptr'}.intersection(sys.argv) setup( **{key: project_meta[key] for key in ( 'name', 'version', 'description', 'long_description', 'author', 'author_email', 'license', 'url', 'download_url' )}, zip_safe=True, python_requires='>=3.6.0', # https://pypi.python.org/pypi?%3Aaction=list_classifiers classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Other Environment', 'Intended Audience :: Developers', 'License :: OSI Approved :: MIT License', 'Operating System :: OS Independent', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: Implementation :: CPython', 'Topic :: Software Development :: Libraries :: Python Modules' ], tests_require=['pytest>=4.6.1', 'pytest-asyncio>=0.10.0', 'coverage>=4.5.3'], package_data={'rx': ['py.typed']}, packages=['rx', 'rx.internal', 'rx.core', 'rx.core.abc', 'rx.core.operators', 'rx.core.operators.connectable', 'rx.core.observable', 'rx.core.observer', 'rx.scheduler', 'rx.scheduler.eventloop', 'rx.scheduler.mainloop', 'rx.operators', 'rx.disposable', 'rx.subject', 'rx.testing'], package_dir={'rx': 'rx'}, include_package_data=True ) RxPY-3.2.0/tests/000077500000000000000000000000001404130727200135205ustar00rootroot00000000000000RxPY-3.2.0/tests/__init__.py000066400000000000000000000000001404130727200156170ustar00rootroot00000000000000RxPY-3.2.0/tests/test_core/000077500000000000000000000000001404130727200155075ustar00rootroot00000000000000RxPY-3.2.0/tests/test_core/__init__.py000066400000000000000000000000001404130727200176060ustar00rootroot00000000000000RxPY-3.2.0/tests/test_core/test_notification.py000066400000000000000000000167461404130727200216240ustar00rootroot00000000000000from rx.core.typing import Observer from rx.testing import TestScheduler, ReactiveTest from rx.core.notification import OnNext, OnError, OnCompleted on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created def test_on_next_ctor_and_props(): n = OnNext(42) assert 'N' == n.kind assert n.has_value assert 42 == n.value assert not hasattr(n, "exception") def test_on_next_equality(): n1 = OnNext(42) n2 = OnNext(42) n3 = OnNext(24) n4 = OnCompleted() assert n1.equals(n1) assert n1.equals(n2) assert n2.equals(n1) assert not n1.equals(None) assert not n1.equals(n3) assert not n3.equals(n1) assert not n1.equals(n4) assert not n4.equals(n1) def test_on_next_tostring(): n1 = OnNext(42) assert "OnNext" in str(n1) assert "42" in str(n1) class CheckOnNextObserver(Observer): def __init__(self): super(CheckOnNextObserver, self).__init__() self.value = None def on_next(self, value): self.value = value return self.value def on_error(self, error): raise NotImplementedError def on_completed(self): def func(): raise NotImplementedError return func def test_on_next_accept_observer(): con = CheckOnNextObserver() n1 = OnNext(42) n1.accept(con) assert con.value == 42 class AcceptObserver(Observer): def __init__(self, on_next, on_error, on_completed): self._on_next = on_next self._on_error = on_error self._on_completed = on_completed def on_next(self, value): return self._on_next(value) def on_error(self, exception): return self._on_error(exception) def on_completed(self): return self._on_completed() def test_on_next_accept_observer_with_result(): n1 = OnNext(42) def on_next(x): return "OK" def on_error(err): assert False def on_completed(): assert False res = n1.accept(AcceptObserver(on_next, on_error, on_completed)) assert 'OK' == res def test_on_next_accept_action(): obs = [False] n1 = OnNext(42) def on_next(x): obs[0] = True return obs[0] def on_error(err): assert False def on_completed(): assert False n1.accept(on_next, on_error, on_completed) assert obs[0] def test_on_next_accept_action_with_result(): n1 = OnNext(42) def on_next(x): return "OK" def on_error(err): assert False def on_completed(): assert False res = n1.accept(on_next, on_error, on_completed) assert 'OK' == res def test_throw_ctor_and_props(): e = 'e' n = OnError(e) assert 'E'== n.kind assert not n.has_value assert e == n.exception def test_throw_equality(): ex1 = 'ex1' ex2 = 'ex2' n1 = OnError(ex1) n2 = OnError(ex1) n3 = OnError(ex2) n4 = OnCompleted() assert n1.equals(n1) assert n1.equals(n2) assert n2.equals(n1) assert not n1.equals(None) assert not n1.equals(n3) assert not n3.equals(n1) assert not n1.equals(n4) assert not n4.equals(n1) def test_throw_tostring(): ex = 'ex' n1 = OnError(ex) assert "OnError" in str(n1) assert "ex" in str(n1) class CheckOnErrorObserver(Observer): def __init__(self): super(CheckOnErrorObserver, self).__init__() self.error = None def on_next(value): raise NotImplementedError() def on_error(self, exception): self.error = exception def on_completed(self): raise NotImplementedError() def test_throw_accept_observer(): ex = 'ex' obs = CheckOnErrorObserver() n1 = OnError(ex) n1.accept(obs) assert ex == obs.error def test_throw_accept_observer_with_result(): ex = 'ex' n1 = OnError(ex) def on_next(x): assert False return None def on_error(ex): return "OK" def on_completed(): assert False return None res = n1.accept(AcceptObserver(on_next, on_error, on_completed)) assert 'OK' == res def test_throw_accept_action(): ex = 'ex' obs = [False] n1 = OnError(ex) def on_next(x): assert False return None def on_error(ex): obs[0] = True return obs[0] def on_completed(): assert False return None n1.accept(on_next, on_error, on_completed) assert obs[0] def test_throw_accept_action_with_result(): ex = 'ex' n1 = OnError(ex) def on_next(x): assert False return None def on_error(ex): return "OK" def on_completed(): assert False return None res = n1.accept(on_next, on_error, on_completed) assert 'OK' == res def test_close_ctor_and_props(): n = OnCompleted() assert 'C' == n.kind assert not n.has_value assert not hasattr(n, "exception") def test_close_equality(): n1 = OnCompleted() n2 = OnCompleted() n3 = OnNext(2) assert n1.equals(n1) assert n1.equals(n2) assert n2.equals(n1) assert not n1.equals(None) assert not n1.equals(n3) assert not n3.equals(n1) def test_close_tostring(): n1 = OnCompleted() assert 'OnCompleted' in str(n1) class CheckOnCompletedObserver(Observer): def __init__(self): super(CheckOnCompletedObserver, self).__init__() self.completed = False def on_next(self): raise NotImplementedError() def on_error(self): raise NotImplementedError() def on_completed(self): self.completed = True def test_close_accept_observer(): obs = CheckOnCompletedObserver() n1 = OnCompleted() n1.accept(obs) assert obs.completed def test_close_accept_observer_with_result(): n1 = OnCompleted() def on_next(x): assert False return None def on_error(err): assert False return None def on_completed(): return "OK" res = n1.accept(AcceptObserver(on_next, on_error, on_completed)) assert 'OK' == res def test_close_accept_action(): obs = [False] n1 = OnCompleted() def on_next(x): assert False return None def on_error(ex): assert False return None def on_completed(): obs[0] = True return obs[0] n1.accept(on_next, on_error, on_completed) assert obs[0] def test_close_accept_action_with_result(): n1 = OnCompleted() def on_next(x): assert False return None def on_error(ex): assert False return None def on_completed(): return "OK" res = n1.accept(on_next, on_error, on_completed) assert 'OK' == res def test_to_observable_empty(): scheduler = TestScheduler() def create(): return OnCompleted().to_observable(scheduler) res = scheduler.start(create) assert res.messages == [ReactiveTest.on_completed(200)] def test_to_observable_return(): scheduler = TestScheduler() def create(): return OnNext(42).to_observable(scheduler) res = scheduler.start(create) assert res.messages == [ReactiveTest.on_next(200, 42), ReactiveTest.on_completed(200)] def test_to_observable_on_error(): ex = 'ex' scheduler = TestScheduler() def create(): return OnError(ex).to_observable(scheduler) res = scheduler.start(create) assert res.messages == [ReactiveTest.on_error(200, ex)] RxPY-3.2.0/tests/test_core/test_observer.py000066400000000000000000000124211404130727200207470ustar00rootroot00000000000000from rx.core import Observer from rx.core.notification import OnNext, OnError, OnCompleted, from_notifier class MyObserver(Observer): def __init__(self): super().__init__() self.has_on_next = None self.has_on_completed = None self.has_on_error = None def _on_next_core(self, value): self.has_on_next = value def _on_error_core(self, error): self.has_on_error = error def _on_completed_core(self): self.has_on_completed = True def test_to_observer_notification_on_next(): i = 0 def next(n): assert i == 0 assert n.kind == 'N' assert n.value == 42 assert not hasattr(n, "exception") assert n.has_value from_notifier(next).on_next(42) def test_to_observer_notification_on_error(): ex = 'ex' i = 0 def next(n): assert i == 0 assert n.kind == 'E' assert n.exception == ex assert not n.has_value from_notifier(next).on_error(ex) def test_to_observer_notification_completed(): i = 0 def next(n): assert i == 0 assert n.kind == 'C' assert not n.has_value from_notifier(next).on_completed() def test_to_notifier_forwards(): obsn = MyObserver() obsn.to_notifier()(OnNext(42)) assert obsn.has_on_next == 42 ex = 'ex' obse = MyObserver() obse.to_notifier()(OnError(ex)) assert ex == obse.has_on_error obsc = MyObserver() obsc.to_notifier()(OnCompleted()) assert obsc.has_on_completed def test_create_on_next(): next = [False] def on_next(x): assert 42 == x next[0] = True res = Observer(on_next) res.on_next(42) assert next[0] return res.on_completed() def test_create_on_next_has_error(): ex = 'ex' next = [False] _e = None def on_next(x): assert 42 == x next[0] = True res = Observer(on_next) res.on_next(42) assert next[0] try: res.on_error(ex) assert False except Exception as e: e_ = e.args[0] assert ex == e_ def test_create_on_next_on_completed(): next = [False] completed = [False] def on_next(x): assert 42 == x next[0] = True return next[0] def on_completed(): completed[0] = True return completed[0] res = Observer(on_next, None, on_completed) res.on_next(42) assert next[0] assert not completed[0] res.on_completed() assert completed[0] def test_create_on_next_close_has_error(): e_ = None ex = 'ex' next = [False] completed = [False] def on_next(x): assert 42 == x next[0] = True def on_completed(): completed[0] = True res = Observer(on_next, None, on_completed) res.on_next(42) assert next[0] assert not completed[0] try: res.on_error(ex) assert False except Exception as e: e_ = e.args[0] assert ex == e_ assert not completed[0] def test_create_on_next_on_error(): ex = 'ex' next = [True] error = [False] def on_next(x): assert 42 == x next[0] = True def on_error(e): assert ex == e error[0] = True res = Observer(on_next, on_error) res.on_next(42) assert next[0] assert not error[0] res.on_error(ex) assert error[0] def test_create_on_next_throw_hit_completed(): ex = 'ex' next = [True] error = [False] def on_next(x): assert 42 == x next[0] = True def on_error(e): assert ex == e error[0] = True res = Observer(on_next, on_error) res.on_next(42) assert next[0] assert not error[0] res.on_completed() assert not error[0] def test_create_on_next_throw_close1(): ex = 'ex' next = [True] error = [False] completed = [False] def on_next(x): assert 42 == x next[0] = True def on_error(e): assert ex == e error[0] = True def on_completed(): completed[0] = True res = Observer(on_next, on_error, on_completed) res.on_next(42) assert next[0] assert not error[0] assert not completed[0] res.on_completed() assert completed[0] assert not error[0] def test_create_on_next_throw_close2(): ex = 'ex' next = [True] error = [False] completed = [False] def on_next(x): assert 42 == x next[0] = True def on_error(e): assert ex == e error[0] = True def on_completed(): completed[0] = True res = Observer(on_next, on_error, on_completed) res.on_next(42) assert next[0] assert not error[0] assert not completed[0] res.on_error(ex) assert not completed[0] assert error[0] def test_as_observer_hides(): obs = MyObserver() res = obs.as_observer() assert res != obs assert not isinstance(res, obs.__class__) def test_as_observer_forwards(): obsn = MyObserver() obsn.as_observer().on_next(42) assert obsn.has_on_next == 42 ex = 'ex' obse = MyObserver() obse.as_observer().on_error(ex) assert obse.has_on_error == ex obsc = MyObserver() obsc.as_observer().on_completed() assert obsc.has_on_completed if __name__ == '__main__': test_to_notifier_forwards() RxPY-3.2.0/tests/test_core/test_priorityqueue.py000066400000000000000000000065151404130727200220550ustar00rootroot00000000000000import unittest from rx.internal import PriorityQueue class TestItem(): __test__ = False def __init__(self, value, label=None): self.value = value self.label = label def __str__(self): if self.label: return "%s (%s)" % (self.value, self.label) else: return "%s" % self.value def __repr__(self): return str(self) def __eq__(self, other): return self.value == other.value # and self.label == other.label def __lt__(self, other): return self.value < other.value def __gt__(self, other): return self.value > other.value class TestPriorityQueue(unittest.TestCase): def test_priorityqueue_count(self): assert PriorityQueue.MIN_COUNT < 0 def test_priorityqueue_empty(self): """Must be empty on construction""" p = PriorityQueue() assert len(p) == 0 assert p.items == [] # Still empty after enqueue/dequeue p.enqueue(42) p.dequeue() assert len(p) == 0 def test_priorityqueue_length(self): """Test that length is n after n invocations""" p = PriorityQueue() assert len(p) == 0 for n in range(42): p.enqueue(n) assert len(p) == 42 p.dequeue() assert len(p) == 41 p.remove(10) assert len(p) == 40 for n in range(len(p)): p.dequeue() assert len(p) == 0 def test_priorityqueue_enqueue_dequeue(self): """Enqueue followed by dequeue should give the same result""" p = PriorityQueue() self.assertRaises(IndexError, p.dequeue) p.enqueue(42) p.enqueue(41) p.enqueue(43) assert([p.dequeue(), p.dequeue(), p.dequeue()] == [41, 42, 43]) def test_priorityqueue_sort_stability(self): """Items with same value should be returned in the order they were added""" p = PriorityQueue() p.enqueue(TestItem(43, "high")) p.enqueue(TestItem(42, "first")) p.enqueue(TestItem(42, "second")) p.enqueue(TestItem(42, "last")) p.enqueue(TestItem(41, "low")) assert len(p) == 5 assert p.dequeue() == TestItem(41, "low") assert p.dequeue() == TestItem(42, "first") assert p.dequeue() == TestItem(42, "second") assert p.dequeue() == TestItem(42, "last") assert p.dequeue() == TestItem(43, "high") def test_priorityqueue_remove(self): """Remove item from queue""" p = PriorityQueue() assert(p.remove(42) == False) p.enqueue(42) p.enqueue(41) p.enqueue(43) assert p.remove(42) == True assert [p.dequeue(), p.dequeue()] == [41, 43] p.enqueue(42) p.enqueue(41) p.enqueue(43) assert p.remove(41) == True assert [p.dequeue(), p.dequeue()] == [42, 43] p.enqueue(42) p.enqueue(41) p.enqueue(43) assert p.remove(43) == True assert [p.dequeue(), p.dequeue()] == [41, 42] def test_priorityqueue_peek(self): """Peek at first element in queue""" p = PriorityQueue() self.assertRaises(IndexError, p.peek) p.enqueue(42) assert p.peek() == 42 p.enqueue(41) assert p.peek() == 41 p.enqueue(43) assert p.peek() == 41 RxPY-3.2.0/tests/test_disposables/000077500000000000000000000000001404130727200170675ustar00rootroot00000000000000RxPY-3.2.0/tests/test_disposables/__init__.py000066400000000000000000000000001404130727200211660ustar00rootroot00000000000000RxPY-3.2.0/tests/test_disposables/test_disposable.py000066400000000000000000000133741404130727200226350ustar00rootroot00000000000000from rx.disposable import Disposable from rx.disposable import BooleanDisposable, SingleAssignmentDisposable from rx.disposable import CompositeDisposable, SerialDisposable from rx.disposable import RefCountDisposable def test_Disposable_create(): def action(): pass disp = Disposable(action) assert disp def test_Disposable_dispose(): disposed = [False] def action(): disposed[0] = True d = Disposable(action) assert not disposed[0] d.dispose() assert disposed[0] def test_emptydisposable(): d = Disposable() assert d d.dispose() def test_booleandisposable(): d = BooleanDisposable() assert not d.is_disposed d.dispose() assert d.is_disposed d.dispose() assert d.is_disposed def test_future_disposable_setnone(): d = SingleAssignmentDisposable() d.disposable = None assert d.disposable == None def test_futuredisposable_disposeafterset(): d = SingleAssignmentDisposable() disposed = [False] def action(): disposed[0] = True dd = Disposable(action) d.disposable = dd assert dd == d.disposable assert not disposed[0] d.dispose() assert disposed[0] d.dispose() assert disposed[0] def test_futuredisposable_disposebeforeset(): disposed = [False] def dispose(): disposed[0] = True d = SingleAssignmentDisposable() dd = Disposable(dispose) assert not disposed[0] d.dispose() assert not disposed[0] d.disposable = dd assert d.disposable == None assert disposed[0] d.dispose() assert disposed[0] def test_groupdisposable_contains(): d1 = Disposable() d2 = Disposable() g = CompositeDisposable(d1, d2) assert g.length == 2 assert g.contains(d1) assert g.contains(d2) def test_groupdisposable_add(): d1 = Disposable() d2 = Disposable() g = CompositeDisposable(d1) assert g.length == 1 assert g.contains(d1) g.add(d2) assert g.length == 2 assert g.contains(d2) def test_groupdisposable_addafterdispose(): disp1 = [False] disp2 = [False] def action1(): disp1[0] = True d1 = Disposable(action1) def action2(): disp2[0] = True d2 = Disposable(action2) g = CompositeDisposable(d1) assert g.length == 1 g.dispose() assert disp1[0] assert g.length == 0 g.add(d2) assert disp2[0] assert g.length == 0 def test_groupdisposable_remove(): disp1 = [False] disp2 = [False] def action1(): disp1[0] = True d1 = Disposable(action1) def action2(): disp2[0] = True d2 = Disposable(action2) g = CompositeDisposable(d1, d2) assert g.length == 2 assert g.contains(d1) assert g.contains(d2) assert g.remove(d1) assert g.length == 1 assert not g.contains(d1) assert g.contains(d2) assert disp1[0] assert g.remove(d2) assert not g.contains(d1) assert not g.contains(d2) assert disp2[0] disp3 = [False] def action3(): disp3[0] = True d3 = Disposable(action3) assert not g.remove(d3) assert not disp3[0] def test_groupdisposable_clear(): disp1 = [False] disp2 = [False] def action1(): disp1[0] = True d1 = Disposable(action1) def action2(): disp2[0] = True d2 = Disposable(action2) g = CompositeDisposable(d1, d2) assert g.length == 2 g.clear() assert disp1[0] assert disp2[0] assert not g.length disp3 = [False] def action3(): disp3[0] = True d3 = Disposable(action3) g.add(d3); assert not disp3[0] assert g.length == 1 def test_mutabledisposable_ctor_prop(): m = SerialDisposable() assert not m.disposable def test_mutabledisposable_replacebeforedispose(): disp1 = [False] disp2 = [False] m = SerialDisposable() def action1(): disp1[0] = True d1 = Disposable(action1) m.disposable = d1 assert d1 == m.disposable assert not disp1[0] def action2(): disp2[0] = True d2 = Disposable(action2) m.disposable = d2 assert d2 == m.disposable assert disp1[0] assert not disp2[0] def test_mutabledisposable_replaceafterdispose(): disp1 = [False] disp2 = [False] m = SerialDisposable() m.dispose() def action1(): disp1[0] = True d1 = Disposable(action1) m.disposable = d1 assert m.disposable == None assert disp1[0] def action2(): disp2[0] = True d2 = Disposable(action2) m.disposable = d2 assert m.disposable == None assert disp2[0] def test_mutabledisposable_dispose(): disp = [False] m = SerialDisposable() def action(): disp[0] = True d = Disposable(action) m.disposable = d assert d == m.disposable assert not disp[0] m.dispose() assert disp[0] assert m.disposable == None def test_refcountdisposable_singlereference(): d = BooleanDisposable() r = RefCountDisposable(d) assert not d.is_disposed r.dispose() assert d.is_disposed r.dispose() assert d.is_disposed def test_refcountdisposable_refcounting(): d = BooleanDisposable() r = RefCountDisposable(d) assert not d.is_disposed d1 = r.disposable d2 = r.disposable assert not d.is_disposed d1.dispose() assert not d.is_disposed d2.dispose() assert not d.is_disposed r.dispose() assert d.is_disposed d3 = r.disposable d3.dispose() def test_refcountdisposable_primarydisposesfirst(): d = BooleanDisposable() r = RefCountDisposable(d) assert not d.is_disposed; d1 = r.disposable d2 = r.disposable assert not d.is_disposed d1.dispose() assert not d.is_disposed r.dispose() assert not d.is_disposed d2.dispose() assert d.is_disposed RxPY-3.2.0/tests/test_integration/000077500000000000000000000000001404130727200171025ustar00rootroot00000000000000RxPY-3.2.0/tests/test_integration/test_concat_repeat.py000066400000000000000000000007541404130727200233300ustar00rootroot00000000000000import unittest from rx import operators as ops from rx.testing.marbles import marbles_testing class TestConcatIntegration(unittest.TestCase): def test_concat_repeat(self): with marbles_testing() as (start, cold, hot, exp): e1 = cold("-e11-e12|") e2 = cold("-e21-e22|") ex = exp("-e11-e12-e21-e22-e11-e12-e21-e22|") obs = e1.pipe(ops.concat(e2), ops.repeat(2)) results = start(obs) assert results == ex RxPY-3.2.0/tests/test_integration/test_group_reduce.py000066400000000000000000000014561404130727200232040ustar00rootroot00000000000000import unittest import rx from rx import operators as ops class TestGroupByReduce(unittest.TestCase): def test_groupby_count(self): res = [] counts = rx.from_(range(10)).pipe( ops.group_by(lambda i: 'even' if i % 2 == 0 else 'odd'), ops.flat_map(lambda i: i.pipe( ops.count(), ops.map(lambda ii: (i.key, ii)), )) ) counts.subscribe(on_next=res.append) assert res == [('even', 5), ('odd', 5)] def test_window_sum(self): res = [] rx.from_(range(6)).pipe( ops.window_with_count(count=3, skip=1), ops.flat_map(lambda i: i.pipe( ops.sum(), )), ).subscribe(on_next=res.append) assert res == [3, 6, 9, 12, 9, 5, 0] RxPY-3.2.0/tests/test_observable/000077500000000000000000000000001404130727200167035ustar00rootroot00000000000000RxPY-3.2.0/tests/test_observable/__init__.py000066400000000000000000000000001404130727200210020ustar00rootroot00000000000000RxPY-3.2.0/tests/test_observable/test_all.py000066400000000000000000000066401404130727200210720ustar00rootroot00000000000000import unittest from rx import operators as _ from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class TestAll(unittest.TestCase): def test_all_empty(self): scheduler = TestScheduler() msgs = [on_next(150, 1), on_completed(250)] xs = scheduler.create_hot_observable(msgs) def create(): return xs.pipe(_.all(lambda x: x > 0)) res = scheduler.start(create=create).messages assert res == [on_next(250, True), on_completed(250)] def test_all_return(self): scheduler = TestScheduler() msgs = [on_next(150, 1), on_next(210, 2), on_completed(250)] xs = scheduler.create_hot_observable(msgs) def create(): return xs.pipe(_.all(lambda x: x > 0)) res = scheduler.start(create=create).messages assert res == [on_next(250, True), on_completed(250)] def test_all_return_not_match(self): scheduler = TestScheduler() msgs = [on_next(150, 1), on_next(210, -2), on_completed(250)] xs = scheduler.create_hot_observable(msgs) def create(): return xs.pipe(_.all(lambda x: x > 0)) res = scheduler.start(create=create).messages assert res == [on_next(210, False), on_completed(210)] def test_all_some_none_match(self): scheduler = TestScheduler() msgs = [on_next(150, 1), on_next(210, -2), on_next(220, -3), on_next(230, -4), on_completed(250)] xs = scheduler.create_hot_observable(msgs) def create(): return xs.pipe(_.all(lambda x: x > 0)) res = scheduler.start(create=create).messages assert res == [on_next(210, False), on_completed(210)] def test_all_some_match(self): scheduler = TestScheduler() msgs = [on_next(150, 1), on_next(210, -2), on_next(220, 3), on_next(230, -4), on_completed(250)] xs = scheduler.create_hot_observable(msgs) def create(): return xs.pipe(_.all(lambda x: x > 0)) res = scheduler.start(create=create).messages assert res == [on_next(210, False), on_completed(210)] def test_all_some_all_match(self): scheduler = TestScheduler() msgs = [on_next(150, 1), on_next(210, 2), on_next(220, 3), on_next(230, 4), on_completed(250)] xs = scheduler.create_hot_observable(msgs) def create(): return xs.pipe(_.all(lambda x: x > 0)) res = scheduler.start(create=create).messages assert res == [on_next(250, True), on_completed(250)] def test_all_on_error(self): ex = 'ex' scheduler = TestScheduler() msgs = [on_next(150, 1), on_error(210, ex)] xs = scheduler.create_hot_observable(msgs) def create(): return xs.pipe(_.all(lambda x: x > 0)) res = scheduler.start(create=create).messages assert res == [on_error(210, ex)] def test_all_never(self): scheduler = TestScheduler() msgs = [on_next(150, 1)] xs = scheduler.create_hot_observable(msgs) def create(): return xs.pipe(_.all(lambda x: x > 0)) res = scheduler.start(create=create).messages assert res == [] if __name__ == '__main__': unittest.main() RxPY-3.2.0/tests/test_observable/test_amb.py000066400000000000000000000107101404130727200210520ustar00rootroot00000000000000import unittest import rx from rx import operators as ops from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class TestAmb(unittest.TestCase): def test_amb_never2(self): scheduler = TestScheduler() l = rx.never() r = rx.never() def create(): return l.pipe(ops.amb(r)) results = scheduler.start(create) assert results.messages == [] def test_amb_never3(self): scheduler = TestScheduler() n1 = rx.never() n2 = rx.never() n3 = rx.never() def create(): return rx.amb(n1, n2, n3) results = scheduler.start(create) assert results.messages == [] def test_amb_never_empty(self): scheduler = TestScheduler() r_msgs = [on_next(150, 1), on_completed(225)] n = rx.never() e = scheduler.create_hot_observable(r_msgs) def create(): return n.pipe(ops.amb(e)) results = scheduler.start(create) assert results.messages == [on_completed(225)] def test_amb_empty_never(self): scheduler = TestScheduler() r_msgs = [on_next(150, 1), on_completed(225)] n = rx.never() e = scheduler.create_hot_observable(r_msgs) def create(): return e.pipe(ops.amb(n)) results = scheduler.start(create) assert results.messages == [on_completed(225)] def test_amb_regular_should_dispose_loser(self): scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_next(210, 2), on_completed(240)] msgs2 = [on_next(150, 1), on_next(220, 3), on_completed(250)] source_not_disposed = [False] o1 = scheduler.create_hot_observable(msgs1) def action(): source_not_disposed[0] = True o2 = scheduler.create_hot_observable(msgs2).pipe( ops.do_action(on_next=action), ) def create(): return o1.pipe(ops.amb(o2)) results = scheduler.start(create) assert results.messages == [on_next(210, 2), on_completed(240)] assert(not source_not_disposed[0]) def test_amb_winner_throws(self): ex = 'ex' scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_next(210, 2), on_error(220, ex)] msgs2 = [on_next(150, 1), on_next(220, 3), on_completed(250)] source_not_disposed = [False] o1 = scheduler.create_hot_observable(msgs1) def action(): source_not_disposed[0] = True o2 = scheduler.create_hot_observable(msgs2).pipe( ops.do_action(on_next=action), ) def create(): return o1.pipe(ops.amb(o2)) results = scheduler.start(create) assert results.messages == [on_next(210, 2), on_error(220, ex)] assert(not source_not_disposed[0]) def test_amb_loser_throws(self): ex = 'ex' scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_next(220, 2), on_error(230, ex)] msgs2 = [on_next(150, 1), on_next(210, 3), on_completed(250)] source_not_disposed = [False] def action(): source_not_disposed[0] = True o1 = scheduler.create_hot_observable(msgs1).pipe( ops.do_action(on_next=action), ) o2 = scheduler.create_hot_observable(msgs2) def create(): return o1.pipe(ops.amb(o2)) results = scheduler.start(create) assert results.messages == [on_next(210, 3), on_completed(250)] assert(not source_not_disposed[0]) def test_amb_throws_before_election(self): ex = 'ex' scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_error(210, ex)] msgs2 = [on_next(150, 1), on_next(220, 3), on_completed(250)] source_not_disposed = [False] o1 = scheduler.create_hot_observable(msgs1) def action(): source_not_disposed[0] = True o2 = scheduler.create_hot_observable(msgs2).pipe( ops.do_action(on_next=action), ) def create(): return o1.pipe(ops.amb(o2)) results = scheduler.start(create) assert results.messages == [on_error(210, ex)] assert(not source_not_disposed[0]) RxPY-3.2.0/tests/test_observable/test_asobservable.py000066400000000000000000000053371404130727200227740ustar00rootroot00000000000000import unittest import rx from rx import operators as ops from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class TestAsObservable(unittest.TestCase): def test_as_observable_hides(self): some_observable = rx.empty() assert some_observable.pipe(ops.as_observable()) != some_observable def test_as_observable_never(self): scheduler = TestScheduler() def create(): return rx.never().pipe(ops.as_observable()) results = scheduler.start(create) assert results.messages == [] def test_as_observable_empty(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_completed(250)) def create(): return xs.pipe(ops.as_observable()) results = scheduler.start(create).messages self.assertEqual(1, len(results)) assert(results[0].value.kind == 'C' and results[0].time == 250) def test_as_observable_on_error(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_error(250, ex)) def create(): return xs.pipe(ops.as_observable()) results = scheduler.start(create).messages self.assertEqual(1, len(results)) assert(results[0].value.kind == 'E' and results[0].value.exception == ex and results[0].time == 250) def test_as_observable_Return(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_next(220, 2), on_completed(250)) def create(): return xs.pipe(ops.as_observable()) results = scheduler.start(create).messages self.assertEqual(2, len(results)) assert results[0].value.kind == 'N' and results[0].value.value == 2 and results[0].time == 220 assert results[1].value.kind == 'C' and results[1].time == 250 def test_as_observable_isnoteager(self): scheduler = TestScheduler() subscribed = [False] def subscribe(obs, scheduler=None): subscribed[0] = True disp = scheduler.create_hot_observable(on_next(150, 1), on_next(220, 2), on_completed(250)).subscribe(obs) def func(): return disp.dispose() return func xs = rx.create(subscribe) xs.pipe(ops.as_observable()) assert not subscribed[0] def create(): return xs.pipe(ops.as_observable()) scheduler.start(create) assert subscribed[0] RxPY-3.2.0/tests/test_observable/test_average.py000066400000000000000000000052711404130727200217330ustar00rootroot00000000000000import unittest from rx import operators as _ from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class TestAverage(unittest.TestCase): def test_average_int32_empty(self): scheduler = TestScheduler() msgs = [on_next(150, 1), on_completed(250)] xs = scheduler.create_hot_observable(msgs) res = scheduler.start(create=lambda: xs.pipe(_.average())).messages assert(len(res) == 1) assert(res[0].value.kind == 'E' and res[0].value.exception != None) assert(res[0].time == 250) def test_average_int32_return(self): scheduler = TestScheduler() msgs = [on_next(150, 1), on_next(210, 2), on_completed(250)] xs = scheduler.create_hot_observable(msgs) def create(): return xs.pipe(_.average()) res = scheduler.start(create=create).messages assert res == [on_next(250, 2.0), on_completed(250)] def test_average_int32_some(self): scheduler = TestScheduler() msgs = [on_next(150, 1), on_next(210, 3), on_next(220, 4), on_next(230, 2), on_completed(250)] xs = scheduler.create_hot_observable(msgs) def create(): return xs.pipe(_.average()) res = scheduler.start(create=create).messages assert res == [on_next(250, 3.0), on_completed(250)] def test_average_int32_on_error(self): ex = 'ex' scheduler = TestScheduler() msgs = [on_next(150, 1), on_error(210, ex)] xs = scheduler.create_hot_observable(msgs) def create(): return xs.pipe(_.average()) res = scheduler.start(create=create).messages assert res == [on_error(210, ex)] def test_average_int32_never(self): scheduler = TestScheduler() msgs = [on_next(150, 1)] xs = scheduler.create_hot_observable(msgs) def create(): return xs.pipe(_.average()) res = scheduler.start(create=create).messages assert res == [] def test_average_mapper_regular_int32(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(210, "b"), on_next(220, "fo"), on_next(230, "qux"), on_completed(240), ) def create(): return xs.pipe(_.average(len)) res = scheduler.start(create=create) assert res.messages == [on_next(240, 2.0), on_completed(240)] assert xs.subscriptions == [subscribe(200, 240)] RxPY-3.2.0/tests/test_observable/test_blocking/000077500000000000000000000000001404130727200215325ustar00rootroot00000000000000RxPY-3.2.0/tests/test_observable/test_blocking/__init__.py000066400000000000000000000000001404130727200236310ustar00rootroot00000000000000RxPY-3.2.0/tests/test_observable/test_blocking/test_blocking.py000066400000000000000000000027221404130727200247360ustar00rootroot00000000000000import unittest import pytest import rx from rx.internal.exceptions import SequenceContainsNoElementsError from rx import operators as ops from rx.testing import ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class RxException(Exception): pass # Helper function for raising exceptions within lambdas def _raise(ex): raise RxException(ex) class TestBlocking(unittest.TestCase): def test_run_empty(self): with pytest.raises(SequenceContainsNoElementsError): rx.empty().run() def test_run_error(self): with pytest.raises(RxException): rx.throw(RxException()).run() def test_run_just(self): result = rx.just(42).run() assert result == 42 def test_run_range(self): result = rx.range(42).run() assert result == 41 def test_run_range_to_iterable(self): result = rx.range(42).pipe(ops.to_iterable()).run() assert list(result) == list(range(42)) def test_run_from(self): result = rx.from_([1, 2, 3]).run() assert result == 3 def test_run_from_first(self): result = rx.from_([1, 2, 3]).pipe(ops.first()).run() assert result == 1 def test_run_of(self): result = rx.of(1, 2, 3).run() assert result == 3 RxPY-3.2.0/tests/test_observable/test_buffer.py000066400000000000000000000325721404130727200215760ustar00rootroot00000000000000import unittest from rx import operators as ops from rx.testing import TestScheduler, ReactiveTest from rx.testing.marbles import marbles_testing on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class TestBuffer(unittest.TestCase): def test_buffer_simple(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(90, 1), on_next(180, 2), on_next(250, 3), on_next(260, 4), on_next(310, 5), on_next(340, 6), on_next(410, 7), on_next(420, 8), on_next(470, 9), on_next(550, 10), on_completed(590) ) ys = scheduler.create_hot_observable( on_next(255, True), on_next(330, True), on_next(350, True), on_next(400, True), on_next(500, True), on_completed(900) ) def create(): return xs.pipe(ops.buffer(ys)) res = scheduler.start(create=create) assert [ on_next(255, lambda b: b == [3]), on_next(330, lambda b: b == [4, 5]), on_next(350, lambda b: b == [6]), on_next(400, lambda b: b == []), on_next(500, lambda b: b == [7, 8, 9]), on_next(590, lambda b: b == [10]), on_completed(590)] == res.messages assert xs.subscriptions == [ subscribe(200, 590)] assert ys.subscriptions == [ subscribe(200, 590)] def test_buffer_closeboundaries(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(90, 1), on_next(180, 2), on_next(250, 3), on_next(260, 4), on_next(310, 5), on_next(340, 6), on_next(410, 7), on_next(420, 8), on_next(470, 9), on_next(550, 10), on_completed(590) ) ys = scheduler.create_hot_observable( on_next(255, True), on_next(330, True), on_next(350, True), on_completed(400) ) def create(): return xs.pipe(ops.buffer(ys)) res = scheduler.start(create=create) assert [ on_next(255, lambda b: b == [3]), on_next(330, lambda b: b == [4, 5]), on_next(350, lambda b: b == [6]), on_next(400, lambda b: b == []), on_completed(400)] == res.messages assert xs.subscriptions == [ subscribe(200, 400)] assert ys.subscriptions == [ subscribe(200, 400)] def test_buffer_throwsource(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(90, 1), on_next(180, 2), on_next(250, 3), on_next(260, 4), on_next(310, 5), on_next(340, 6), on_next(380, 7), on_error(400, ex) ) ys = scheduler.create_hot_observable( on_next(255, True), on_next(330, True), on_next(350, True), on_completed(500) ) def create(): return xs.pipe(ops.buffer(ys)) res = scheduler.start(create=create) assert [ on_next(255, lambda b: b == [3]), on_next(330, lambda b: b == [4, 5]), on_next(350, lambda b: b == [6]), on_error(400, ex)] == res.messages assert xs.subscriptions == [ subscribe(200, 400)] assert ys.subscriptions == [ subscribe(200, 400)] def test_buffer_throwboundaries(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(90, 1), on_next(180, 2), on_next(250, 3), on_next(260, 4), on_next(310, 5), on_next(340, 6), on_next(410, 7), on_next(420, 8), on_next(470, 9), on_next(550, 10), on_completed(590) ) ys = scheduler.create_hot_observable( on_next(255, True), on_next(330, True), on_next(350, True), on_error(400, ex) ) def create(): return xs.pipe(ops.buffer(ys)) res = scheduler.start(create=create) assert [ on_next(255, lambda b: b == [3]), on_next(330, lambda b: b == [4, 5]), on_next(350, lambda b: b == [6]), on_error(400, ex)] == res.messages assert xs.subscriptions == [ subscribe(200, 400)] assert ys.subscriptions == [ subscribe(200, 400)] def test_when_closing_with_empty_observable(self): with marbles_testing(timespan=1.0) as (start, cold, hot, exp): def closing_mapper(): return cold('-----|') lookup = {'a': [1, 2], 'b': [3], 'c': [4, 5], 'd': [6], 'e': [7]} # -----| # -----| # -----| # -----| # -----| source = hot(' -1--2---3--4--5--6---7--|') expected = exp('-----a----b----c----d---(e,|)', lookup=lookup) # 012345678901234567890123456789 # 0 1 2 obs = source.pipe(ops.buffer_when(closing_mapper)) results = start(obs) assert results == expected def test_when_closing_only_on_first_item(self): with marbles_testing(timespan=1.0) as (start, cold, hot, exp): def closing_mapper(): return cold('-----1--2--3--4--|') lookup = {'a': [1, 2], 'b': [3], 'c': [4, 5], 'd': [6], 'e': [7]} # -----1--2--3--4--| # -----1--2--3--4--| # -----1--2--3--4--| # -----1--2--3--4--| source = hot(' -1--2---3--4--5--6---7--|') expected = exp('-----a----b----c----d---(e,|)', lookup=lookup) # 012345678901234567890123456789 # 0 1 2 obs = source.pipe(ops.buffer_when(closing_mapper)) results = start(obs) assert results == expected def test_when_source_on_error(self): class TestException(Exception): pass ex = TestException('test exception') with marbles_testing(timespan=1.0) as (start, cold, hot, exp): def closing_mapper(): return cold('-----1|') lookup = {'a': [1, 2], 'b': [3], 'c': [4, 5], 'd': [6], 'e': [7]} # -----1| # -----1| # -----1| # -----1| # -----1| source = hot(' -1--2---3--4--5--#', error=ex) expected = exp('-----a----b----c-#', lookup=lookup, error=ex) # 012345678901234567890123456789 # 0 1 2 obs = source.pipe(ops.buffer_when(closing_mapper)) results = start(obs) assert results == expected def test_when_closing_on_error(self): class TestException(Exception): pass ex = TestException('test exception') with marbles_testing(timespan=1.0) as (start, cold, hot, exp): def closing_mapper(): return cold('-----#', error=ex) # -----# source = hot(' -1--2---3--4--5--6---7--|') expected = exp('-----#', error=ex) # 012345678901234567890123456789 # 0 1 2 obs = source.pipe(ops.buffer_when(closing_mapper)) results = start(obs) assert results == expected def test_toggle_closing_with_empty_observable(self): with marbles_testing(timespan=1.0) as (start, cold, hot, exp): lookup = {'a': [2, 3], 'b': [5], 'c': [7], 'd': [8, 9]} openings = hot('---a-------b------c---d-------|') a = cold(' ------|') b = cold(' ----|') c = cold(' ---|') d = cold(' -----|') source = hot(' -1--2--3--4--5--6---7--8--9---|') expected = exp('---------a-----b-----c-----d--|', lookup=lookup) # 012345678901234567890123456789 # 0 1 2 closings = {'a': a, 'b': b, 'c': c, 'd': d} def closing_mapper(key): return closings[key] obs = source.pipe(ops.buffer_toggle(openings, closing_mapper)) results = start(obs) assert results == expected def test_toggle_closing_with_only_first_item(self): with marbles_testing(timespan=1.0) as (start, cold, hot, exp): lookup = {'a': [2, 3], 'b': [5], 'c': [7], 'd': [8, 9]} openings = hot('---a-------b------c---d-------|') a = cold(' ------1-2-|') b = cold(' ----1-2-|') c = cold(' ---1-2-|') d = cold(' -----1-2|') source = hot(' -1--2--3--4--5--6---7--8--9---|') expected = exp('---------a-----b-----c-----d--|', lookup=lookup) # 012345678901234567890123456789 # 0 1 2 closings = {'a': a, 'b': b, 'c': c, 'd': d} def closing_mapper(key): return closings[key] obs = source.pipe(ops.buffer_toggle(openings, closing_mapper)) results = start(obs) assert results == expected def test_toggle_source_on_error(self): class TestException(Exception): pass ex = TestException('test exception') with marbles_testing(timespan=1.0) as (start, cold, hot, exp): lookup = {'a': [2, 3], 'b': [5], 'c': [7], 'd': [8, 9]} openings = hot('---a-------b------c---d-------|') a = cold(' ------1-2-|') b = cold(' ----1-2-|') c = cold(' ---1-2-|') d = cold(' -----1-2|') source = hot(' -1--2--3--4--5--6--#', error=ex) expected = exp('---------a-----b---#', lookup=lookup, error=ex) # 012345678901234567890123456789 # 0 1 2 closings = {'a': a, 'b': b, 'c': c, 'd': d} def closing_mapper(key): return closings[key] obs = source.pipe(ops.buffer_toggle(openings, closing_mapper)) results = start(obs) assert results == expected def test_toggle_openings_on_error(self): class TestException(Exception): pass ex = TestException('test exception') with marbles_testing(timespan=1.0) as (start, cold, hot, exp): lookup = {'a': [2, 3], 'b': [5], 'c': [7], 'd': [8, 9]} openings = hot('---a-------b-----#', error=ex) a = cold(' ------1|') b = cold(' ----1|') c = cold(' ---1|') d = cold(' -----1|') source = hot(' -1--2--3--4--5--6---7--8--9---|') expected = exp('---------a-----b-#', lookup=lookup, error=ex) # 012345678901234567890123456789 # 0 1 2 closings = {'a': a, 'b': b, 'c': c, 'd': d} def closing_mapper(key): return closings[key] obs = source.pipe(ops.buffer_toggle(openings, closing_mapper)) results = start(obs) assert results == expected def test_toggle_closing_mapper_on_error(self): class TestException(Exception): pass ex = TestException('test exception') with marbles_testing(timespan=1.0) as (start, cold, hot, exp): lookup = {'a': [2, 3], 'b': [5], 'c': [7], 'd': [8, 9]} openings = hot('---a-------b------c---d-------|') a = cold(' ------1|') b = cold(' ---#', error=ex) c = cold(' ---1|') d = cold(' -----1|') source = hot(' -1--2--3--4--5--6---7--8--9---|') expected = exp('---------a----#', lookup=lookup, error=ex) # 012345678901234567890123456789 # 0 1 2 closings = {'a': a, 'b': b, 'c': c, 'd': d} def closing_mapper(key): return closings[key] obs = source.pipe(ops.buffer_toggle(openings, closing_mapper)) results = start(obs) assert results == expected RxPY-3.2.0/tests/test_observable/test_bufferwithcount.py000066400000000000000000000140451404130727200235360ustar00rootroot00000000000000import unittest from rx import operators as ops from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class RxException(Exception): pass # Helper function for raising exceptions within lambdas def _raise(ex): raise RxException(ex) def sequence_equal(arr1, arr2): if len(arr1) != len(arr2): return False for i in range(len(arr1)): if arr1[i] != arr2[i]: return False return True class TestBufferWithCount(unittest.TestCase): def test_buffer_with_count_basic(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(100, 1), on_next(210, 2), on_next(240, 3), on_next(280, 4), on_next(320, 5), on_next(350, 6), on_next(380, 7), on_next(420, 8), on_next(470, 9), on_completed(600)) def create(): return xs.pipe(ops.buffer_with_count(3, 2), ops.map(lambda x: str(x))) results = scheduler.start(create) assert results.messages == [ on_next(280, str([2, 3, 4])), on_next(350, str([4, 5, 6])), on_next(420, str([6, 7, 8])), on_next(600, str([8, 9])), on_completed(600)] assert xs.subscriptions == [subscribe(200, 600)] def test_buffer_with_count_disposed(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(100, 1), on_next(210, 2), on_next(240, 3), on_next(280, 4), on_next(320, 5), on_next(350, 6), on_next(380, 7), on_next(420, 8), on_next(470, 9), on_completed(600)) def create(): return xs.pipe(ops.buffer_with_count(3, 2), ops.map(lambda x: str(x))) results = scheduler.start(create, disposed=370) assert results.messages == [ on_next(280, str([2, 3, 4])), on_next(350, str([4, 5, 6]))] assert xs.subscriptions == [subscribe(200, 370)] def test_buffer_count_partial_window(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_next(210, 2), on_next(220, 3), on_next(230, 4), on_next(240, 5), on_completed(250)) def create(): return xs.pipe(ops.buffer_with_count(5)) results = scheduler.start(create).messages assert(2 == len(results)) assert(sequence_equal(results[0].value.value, [2, 3, 4, 5]) and results[0].time == 250) assert(results[1].value.kind == 'C' and results[1].time == 250) def test_buffer_count_full_windows(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_next(210, 2), on_next(220, 3), on_next(230, 4), on_next(240, 5), on_completed(250)) def create(): return xs.pipe(ops.buffer_with_count(2)) results = scheduler.start(create).messages self.assertEqual(3, len(results)) assert(sequence_equal(results[0].value.value, [2, 3]) and results[0].time == 220) assert(sequence_equal(results[1].value.value, [4, 5]) and results[1].time == 240) assert(results[2].value.kind == 'C' and results[2].time == 250) def test_buffer_count_full_and_partial_windows(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_next(210, 2), on_next(220, 3), on_next(230, 4), on_next(240, 5), on_completed(250)) def create(): return xs.pipe(ops.buffer_with_count(3)) results = scheduler.start(create).messages self.assertEqual(3, len(results)) assert(sequence_equal(results[0].value.value, [2, 3, 4]) and results[0].time == 230) assert(sequence_equal(results[1].value.value, [5]) and results[1].time == 250) assert(results[2].value.kind == 'C' and results[2].time == 250) def test_buffer_count_error(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_next(210, 2), on_next(220, 3), on_next(230, 4), on_next(240, 5), on_error(250, 'ex')) def create(): return xs.pipe(ops.buffer_with_count(5)) results = scheduler.start(create).messages self.assertEqual(1, len(results)) assert(results[0].value.kind == 'E' and results[0].time == 250) def test_buffer_count_skip_less(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_next(210, 2), on_next(220, 3), on_next(230, 4), on_next(240, 5), on_completed(250)) def create(): return xs.pipe(ops.buffer_with_count(3, 1)) results = scheduler.start(create).messages self.assertEqual(5, len(results)) assert(sequence_equal(results[0].value.value, [2, 3, 4]) and results[0].time == 230) assert(sequence_equal(results[1].value.value, [3, 4, 5]) and results[1].time == 240) assert(sequence_equal(results[2].value.value, [4, 5]) and results[2].time == 250) assert(sequence_equal(results[3].value.value, [5]) and results[3].time == 250) assert(results[4].value.kind == 'C' and results[4].time == 250) def test_buffer_count_skip_more(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_next(210, 2), on_next(220, 3), on_next(230, 4), on_next(240, 5), on_completed(250)) def create(): return xs.pipe(ops.buffer_with_count(2, 3)) results = scheduler.start(create).messages self.assertEqual(3, len(results)) assert(sequence_equal(results[0].value.value, [2, 3]) and results[0].time == 220) assert(sequence_equal(results[1].value.value, [5]) and results[1].time == 250) assert(results[2].value.kind == 'C' and results[2].time == 250) RxPY-3.2.0/tests/test_observable/test_bufferwithtime.py000066400000000000000000000076021404130727200233450ustar00rootroot00000000000000import unittest from rx import operators as ops from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class TestBufferWithCount(unittest.TestCase): def test_buffer_with_time_basic(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(100, 1), on_next(210, 2), on_next(240, 3), on_next(281, 4), on_next(321, 5), on_next(351, 6), on_next(381, 7), on_next(421, 8), on_next(471, 9), on_completed(600)) def create(): return xs.pipe( ops.buffer_with_time(100, 70), ops.map(lambda x: ",".join([str(a) for a in x])) ) results = scheduler.start(create) assert results.messages == [ on_next(300, "2,3,4"), on_next(370, "4,5,6"), on_next(440, "6,7,8"), on_next(510, "8,9"), on_next(580, ""), on_next(600, ""), on_completed(600)] assert xs.subscriptions == [subscribe(200, 600)] def test_buffer_with_time_error(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(100, 1), on_next(210, 2), on_next(240, 3), on_next(280, 4), on_next(320, 5), on_next(350, 6), on_next(380, 7), on_next(420, 8), on_next(470, 9), on_error(600, ex)) def create(): return xs.pipe( ops.buffer_with_time(100, 70), ops.map(lambda x: ",".join([str(a) for a in x])) ) results = scheduler.start(create) assert results.messages == [ on_next(300, "2,3,4"), on_next(370, "4,5,6"), on_next(440, "6,7,8"), on_next(510, "8,9"), on_next(580, ""), on_error(600, ex)] assert xs.subscriptions == [subscribe(200, 600)] def test_buffer_with_time_disposed(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(100, 1), on_next(210, 2), on_next(240, 3), on_next(281, 4), on_next(321, 5), on_next(351, 6), on_next(381, 7), on_next(421, 8), on_next(471, 9), on_completed(600)) def create(): return xs.pipe( ops.buffer_with_time(100, 70), ops.map(lambda x: ",".join([str(a) for a in x])) ) results = scheduler.start(create, disposed=370) assert results.messages == [on_next(300, "2,3,4")] assert xs.subscriptions == [subscribe(200, 370)] def test_buffer_with_time_basic_same(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(100, 1), on_next(210, 2), on_next(240, 3), on_next(280, 4), on_next(320, 5), on_next(350, 6), on_next(380, 7), on_next(420, 8), on_next(470, 9), on_completed(600)) def create(): return xs.pipe( ops.buffer_with_time(100), ops.map(lambda x: ",".join([str(a) for a in x])) ) results = scheduler.start(create) assert results.messages == [ on_next(300, "2,3,4"), on_next(400, "5,6,7"), on_next(500, "8,9"), on_next(600, ""), on_completed(600)] assert xs.subscriptions == [subscribe(200, 600)] RxPY-3.2.0/tests/test_observable/test_bufferwithtimeorcount.py000066400000000000000000000057431404130727200247630ustar00rootroot00000000000000import unittest import rx from rx import operators as ops from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class TestBufferWithCount(unittest.TestCase): def test_buffer_with_time_or_count_basic(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(205, 1), on_next(210, 2), on_next(240, 3), on_next(280, 4), on_next(320, 5), on_next(350, 6), on_next(370, 7), on_next(420, 8), on_next(470, 9), on_completed(600)) def create(): return xs.pipe(ops.buffer_with_time_or_count(70, 3), ops.map(lambda x: ",".join([str(a) for a in x]))) results = scheduler.start(create) assert results.messages == [on_next(240, "1,2,3"), on_next(310, "4"), on_next(370, "5,6,7"), on_next(440, "8"), on_next(510, "9"), on_next(580, ""), on_next(600, ""), on_completed(600)] assert xs.subscriptions == [subscribe(200, 600)] def test_buffer_with_time_or_count_error(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(205, 1), on_next(210, 2), on_next(240, 3), on_next(280, 4), on_next(320, 5), on_next(350, 6), on_next(370, 7), on_next(420, 8), on_next(470, 9), on_error(600, ex)) def create(): return xs.pipe(ops.buffer_with_time_or_count(70, 3), ops.map(lambda x: ",".join([str(a) for a in x]))) results = scheduler.start(create) assert results.messages == [on_next(240, "1,2,3"), on_next(310, "4"), on_next(370, "5,6,7"), on_next(440, "8"), on_next(510, "9"), on_next(580, ""), on_error(600, ex)] assert xs.subscriptions == [subscribe(200, 600)] def test_buffer_with_time_or_count_disposed(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(205, 1), on_next(210, 2), on_next(240, 3), on_next(280, 4), on_next(320, 5), on_next(350, 6), on_next(370, 7), on_next(420, 8), on_next(470, 9), on_completed(600)) def create(): return xs.pipe(ops.buffer_with_time_or_count(70, 3), ops.map(lambda x: ",".join([str(a) for a in x]))) results = scheduler.start(create, disposed=370) assert results.messages == [ on_next(240, "1,2,3"), on_next(310, "4"), on_next(370, "5,6,7")] assert xs.subscriptions == [subscribe(200, 370)] RxPY-3.2.0/tests/test_observable/test_case.py000066400000000000000000000144051404130727200212330ustar00rootroot00000000000000import unittest import rx from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class TestCase(unittest.TestCase): def test_case_one(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(210, 1), on_next(240, 2), on_next(270, 3), on_completed(300)) ys = scheduler.create_hot_observable(on_next(220, 11), on_next(250, 12), on_next(280, 13), on_completed(310)) zs = scheduler.create_hot_observable(on_next(230, 21), on_next(240, 22), on_next(290, 23), on_completed(320)) map = { 1: xs, 2: ys } def create(): return rx.case(lambda: 1, map, zs) results = scheduler.start(create) assert results.messages == [on_next(210, 1), on_next(240, 2), on_next(270, 3), on_completed(300)] assert xs.subscriptions == [subscribe(200, 300)] assert ys.subscriptions == [] assert zs.subscriptions == [] def test_case_two(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(210, 1), on_next(240, 2), on_next(270, 3), on_completed(300)) ys = scheduler.create_hot_observable(on_next(220, 11), on_next(250, 12), on_next(280, 13), on_completed(310)) zs = scheduler.create_hot_observable(on_next(230, 21), on_next(240, 22), on_next(290, 23), on_completed(320)) map = { 1: xs, 2: ys } def create(): return rx.case(lambda: 2, map, zs) results = scheduler.start(create) assert results.messages == [on_next(220, 11), on_next(250, 12), on_next(280, 13), on_completed(310)] assert xs.subscriptions == [] assert ys.subscriptions == [subscribe(200, 310)] assert zs.subscriptions == [] def test_case_three(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(210, 1), on_next(240, 2), on_next(270, 3), on_completed(300)) ys = scheduler.create_hot_observable(on_next(220, 11), on_next(250, 12), on_next(280, 13), on_completed(310)) zs = scheduler.create_hot_observable(on_next(230, 21), on_next(240, 22), on_next(290, 23), on_completed(320)) map = { 1: xs, 2: ys } def create(): return rx.case(lambda: 3, map, zs) results = scheduler.start(create) assert results.messages == [on_next(230, 21), on_next(240, 22), on_next(290, 23), on_completed(320)] assert xs.subscriptions == [] assert ys.subscriptions == [] assert zs.subscriptions == [subscribe(200, 320)] def test_case_on_error(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(210, 1), on_next(240, 2), on_next(270, 3), on_completed(300)) ys = scheduler.create_hot_observable(on_next(220, 11), on_next(250, 12), on_next(280, 13), on_completed(310)) zs = scheduler.create_hot_observable(on_next(230, 21), on_next(240, 22), on_next(290, 23), on_completed(320)) map = { 1: xs, 2: ys } def create(): def mapper(): raise Exception(ex) return rx.case(mapper, map, zs) results = scheduler.start(create) assert results.messages == [on_error(200, ex)] assert xs.subscriptions == [] assert ys.subscriptions == [] assert zs.subscriptions == [] def test_case_with_default_one(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(210, 1), on_next(240, 2), on_next(270, 3), on_completed(300)) ys = scheduler.create_hot_observable(on_next(220, 11), on_next(250, 12), on_next(280, 13), on_completed(310)) map = { 1: xs, 2: ys } def create(): return rx.case(lambda: 1, map) results = scheduler.start(create=create) assert results.messages == [on_next(210, 1), on_next(240, 2), on_next(270, 3), on_completed(300)] assert xs.subscriptions == [subscribe(200, 300)] assert ys.subscriptions == [] def test_case_with_default_two(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(210, 1), on_next(240, 2), on_next(270, 3), on_completed(300)) ys = scheduler.create_hot_observable(on_next(220, 11), on_next(250, 12), on_next(280, 13), on_completed(310)) map = { 1: xs, 2: ys } def create(): return rx.case(lambda: 2, map) results = scheduler.start(create=create) assert results.messages == [on_next(220, 11), on_next(250, 12), on_next(280, 13), on_completed(310)] assert xs.subscriptions == [] assert ys.subscriptions == [subscribe(200, 310)] def test_case_with_default_three(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(210, 1), on_next(240, 2), on_next(270, 3), on_completed(300)) ys = scheduler.create_hot_observable(on_next(220, 11), on_next(250, 12), on_next(280, 13), on_completed(310)) map = { 1: xs, 2: ys } def create(): return rx.case(lambda: 3, map) results = scheduler.start(create=create) assert results.messages == [on_completed(200)] assert xs.subscriptions == [] assert ys.subscriptions == [] def test_case_with_default_on_error(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(210, 1), on_next(240, 2), on_next(270, 3), on_completed(300)) ys = scheduler.create_hot_observable(on_next(220, 11), on_next(250, 12), on_next(280, 13), on_completed(310)) map = { 1: xs, 2: ys } def create(): def mapper(): raise Exception(ex) return rx.case(mapper, map) results = scheduler.start(create) assert results.messages == [on_error(200, ex)] assert xs.subscriptions == [] assert ys.subscriptions == [] RxPY-3.2.0/tests/test_observable/test_catch.py000066400000000000000000000205051404130727200214000ustar00rootroot00000000000000import unittest import rx from rx import operators as ops from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class TestCatch(unittest.TestCase): def test_catch_no_errors(self): scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_next(210, 2), on_next(220, 3), on_completed(230)] msgs2 = [on_next(240, 5), on_completed(250)] o1 = scheduler.create_hot_observable(msgs1) o2 = scheduler.create_hot_observable(msgs2) def create(): return o1.pipe(ops.catch(o2)) results = scheduler.start(create) assert results.messages == [on_next(210, 2), on_next(220, 3), on_completed(230)] def test_catch_never(self): scheduler = TestScheduler() msgs2 = [on_next(240, 5), on_completed(250)] o1 = rx.never() o2 = scheduler.create_hot_observable(msgs2) def create(): return o1.pipe(ops.catch(o2)) results = scheduler.start(create) assert results.messages == [] def test_catch_empty(self): scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_completed(230)] msgs2 = [on_next(240, 5), on_completed(250)] o1 = scheduler.create_hot_observable(msgs1) o2 = scheduler.create_hot_observable(msgs2) def create(): return o1.pipe(ops.catch(o2)) results = scheduler.start(create) assert results.messages == [on_completed(230)] def test_catch_return(self): scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_next(210, 2), on_completed(230)] msgs2 = [on_next(240, 5), on_completed(250)] o1 = scheduler.create_hot_observable(msgs1) o2 = scheduler.create_hot_observable(msgs2) def create(): return o1.pipe(ops.catch(o2)) results = scheduler.start(create) assert results.messages == [on_next(210, 2), on_completed(230)] def test_catch_error(self): ex = 'ex' scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_next(210, 2), on_next(220, 3), on_error(230, ex)] msgs2 = [on_next(240, 5), on_completed(250)] o1 = scheduler.create_hot_observable(msgs1) o2 = scheduler.create_hot_observable(msgs2) def create(): return o1.pipe(ops.catch(o2)) results = scheduler.start(create) assert results.messages == [on_next(210, 2), on_next(220, 3), on_next(240, 5), on_completed(250)] def test_catch_error_never(self): ex = 'ex' scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_next(210, 2), on_next(220, 3), on_error(230, ex)] o1 = scheduler.create_hot_observable(msgs1) o2 = rx.never() def create(): return o1.pipe(ops.catch(o2)) results = scheduler.start(create) assert results.messages == [on_next(210, 2), on_next(220, 3)] def test_catch_error_error(self): ex = 'ex' scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_next(210, 2), on_next(220, 3), on_error(230, 'ex1')] msgs2 = [on_next(240, 4), on_error(250, ex)] o1 = scheduler.create_hot_observable(msgs1) o2 = scheduler.create_hot_observable(msgs2) def create(): return o1.pipe(ops.catch(o2)) results = scheduler.start(create) assert results.messages == [on_next(210, 2), on_next(220, 3), on_next(240, 4), on_error(250, ex)] def test_catch_multiple(self): ex = 'ex' scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_next(210, 2), on_error(215, ex)] msgs2 = [on_next(220, 3), on_error(225, ex)] msgs3 = [on_next(230, 4), on_completed(235)] o1 = scheduler.create_hot_observable(msgs1) o2 = scheduler.create_hot_observable(msgs2) o3 = scheduler.create_hot_observable(msgs3) def create(): return rx.catch(o1, o2, o3) results = scheduler.start(create) assert results.messages == [on_next(210, 2), on_next(220, 3), on_next(230, 4), on_completed(235)] def test_catch_error_specific_caught(self): ex = 'ex' handler_called = [False] scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_next(210, 2), on_next(220, 3), on_error(230, ex)] msgs2 = [on_next(240, 4), on_completed(250)] o1 = scheduler.create_hot_observable(msgs1) o2 = scheduler.create_hot_observable(msgs2) def create(): def handler(e, source): handler_called[0] = True return o2 return o1.pipe(ops.catch(handler)) results = scheduler.start(create) assert results.messages == [on_next(210, 2), on_next(220, 3), on_next(240, 4), on_completed(250)] assert handler_called[0] def test_catch_error_specific_caught_immediate(self): ex = 'ex' handler_called = [False] scheduler = TestScheduler() msgs2 = [on_next(240, 4), on_completed(250)] o2 = scheduler.create_hot_observable(msgs2) def create(): def handler(e, source): handler_called[0] = True return o2 return rx.throw('ex').pipe(ops.catch(handler)) results = scheduler.start(create) assert results.messages == [on_next(240, 4), on_completed(250)] assert(handler_called[0]) def test_catch_handler_throws(self): ex = 'ex' ex2 = 'ex2' handler_called = [False] scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_next(210, 2), on_next(220, 3), on_error(230, ex)] o1 = scheduler.create_hot_observable(msgs1) def create(): def handler(e, source): handler_called[0] = True raise Exception(ex2) return o1.pipe(ops.catch(handler)) results = scheduler.start(create) assert results.messages == [on_next(210, 2), on_next(220, 3), on_error(230, ex2)] assert(handler_called[0]) def test_catch_nested_outer_catches(self): ex = 'ex' first_handler_called = [False] second_handler_called = [False] scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_next(210, 2), on_error(215, ex)] msgs2 = [on_next(220, 3), on_completed(225)] msgs3 = [on_next(220, 4), on_completed(225)] o1 = scheduler.create_hot_observable(msgs1) o2 = scheduler.create_hot_observable(msgs2) o3 = scheduler.create_hot_observable(msgs3) def create(): def handler1(e, source): first_handler_called[0] = True return o2 def handler2(e, source): second_handler_called[0] = True return o3 return o1.pipe(ops.catch(handler1), ops.catch(handler2)) results = scheduler.start(create) assert results.messages == [on_next(210, 2), on_next(220, 3), on_completed(225)] assert first_handler_called[0] assert not second_handler_called[0] def test_catch_throw_from_nested_catch(self): ex = 'ex' ex2 = 'ex' first_handler_called = [False] second_handler_called = [False] scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_next(210, 2), on_error(215, ex)] msgs2 = [on_next(220, 3), on_error(225, ex2)] msgs3 = [on_next(230, 4), on_completed(235)] o1 = scheduler.create_hot_observable(msgs1) o2 = scheduler.create_hot_observable(msgs2) o3 = scheduler.create_hot_observable(msgs3) def create(): def handler1(e, source): first_handler_called[0] = True assert(e == ex) return o2 def handler2(e, source): second_handler_called[0] = True assert(e == ex2) return o3 return o1.pipe( ops.catch(handler1), ops.catch(handler2)) results = scheduler.start(create) assert results.messages == [on_next(210, 2), on_next(220, 3), on_next(230, 4), on_completed(235)] assert(first_handler_called[0]) assert(second_handler_called[0]) RxPY-3.2.0/tests/test_observable/test_combinelatest.py000066400000000000000000000362161404130727200231550ustar00rootroot00000000000000import unittest import rx from rx import operators as ops from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class RxException(Exception): pass # Helper function for raising exceptions within lambdas def _raise(ex): raise RxException(ex) class TestCombineLatest(unittest.TestCase): def test_combine_latest_never_never(self): scheduler = TestScheduler() e1 = rx.never() e2 = rx.never() def create(): return e1.pipe( ops.combine_latest(e2), ops.map(sum), ) results = scheduler.start(create) assert results.messages == [] def test_combine_latest_never_empty(self): scheduler = TestScheduler() msgs = [on_next(150, 1), on_completed(210)] e1 = rx.never() e2 = scheduler.create_hot_observable(msgs) def create(): return e1.pipe( ops.combine_latest(e2), ops.map(sum), ) results = scheduler.start(create) assert results.messages == [] def test_combine_latest_empty_never(self): scheduler = TestScheduler() msgs = [on_next(150, 1), on_completed(210)] e1 = rx.never() e2 = scheduler.create_hot_observable(msgs) def create(): return e2.pipe( ops.combine_latest(e1), ops.map(sum), ) results = scheduler.start(create) assert results.messages == [] def test_combine_latest_empty_empty(self): scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_completed(210)] msgs2 = [on_next(150, 1), on_completed(210)] e1 = scheduler.create_hot_observable(msgs1) e2 = scheduler.create_hot_observable(msgs2) def create(): return e2.pipe( ops.combine_latest(e1), ops.map(sum), ) results = scheduler.start(create) assert results.messages == [on_completed(210)] def test_combine_latest_empty_return(self): scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_completed(210)] msgs2 = [on_next(150, 1), on_next(215, 2), on_completed(220)] e1 = scheduler.create_hot_observable(msgs1) e2 = scheduler.create_hot_observable(msgs2) def create(): return e1.pipe( ops.combine_latest(e2), ops.map(sum), ) results = scheduler.start(create) assert results.messages == [on_completed(215)] def test_combine_latest_return_empty(self): scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_completed(210)] msgs2 = [on_next(150, 1), on_next(215, 2), on_completed(220)] e1 = scheduler.create_hot_observable(msgs1) e2 = scheduler.create_hot_observable(msgs2) def create(): return e2.pipe( ops.combine_latest(e1), ops.map(sum), ) results = scheduler.start(create) assert results.messages == [on_completed(215)] def test_combine_latest_never_feturn(self): scheduler = TestScheduler() msgs = [on_next(150, 1), on_next(215, 2), on_completed(220)] e1 = scheduler.create_hot_observable(msgs) e2 = rx.never() def create(): return e1.pipe( ops.combine_latest(e2), ops.map(sum), ) results = scheduler.start(create) assert results.messages == [] def test_combine_latest_return_never(self): scheduler = TestScheduler() msgs = [on_next(150, 1), on_next(215, 2), on_completed(210)] e1 = scheduler.create_hot_observable(msgs) e2 = rx.never() def create(): return e2.pipe( ops.combine_latest(e1), ops.map(sum), ) results = scheduler.start(create) assert results.messages == [] def test_combine_latest_return_return(self): scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_next(215, 2), on_completed(230)] msgs2 = [on_next(150, 1), on_next(220, 3), on_completed(240)] e1 = scheduler.create_hot_observable(msgs1) e2 = scheduler.create_hot_observable(msgs2) def create(): return e1.pipe( ops.combine_latest(e2), ops.map(sum), ) results = scheduler.start(create) assert results.messages == [on_next(220, 2 + 3), on_completed(240)] def test_combine_latest_empty_error(self): ex = 'ex' scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_completed(230)] msgs2 = [on_next(150, 1), on_error(220, ex)] e1 = scheduler.create_hot_observable(msgs1) e2 = scheduler.create_hot_observable(msgs2) def create(): return e1.pipe( ops.combine_latest(e2), ops.map(sum), ) results = scheduler.start(create) assert results.messages == [on_error(220, ex)] def test_combine_latest_error_empty(self): ex = 'ex' scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_completed(230)] msgs2 = [on_next(150, 1), on_error(220, ex)] e1 = scheduler.create_hot_observable(msgs1) e2 = scheduler.create_hot_observable(msgs2) def create(): return e2.pipe( ops.combine_latest(e1), ops.map(sum), ) results = scheduler.start(create) assert results.messages == [on_error(220, ex)] def test_combine_latest_return_on_error(self): ex = 'ex' scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_next(210, 2), on_completed(230)] msgs2 = [on_next(150, 1), on_error(220, ex)] e1 = scheduler.create_hot_observable(msgs1) e2 = scheduler.create_hot_observable(msgs2) def create(): return e1.pipe( ops.combine_latest(e2), ops.map(sum), ) results = scheduler.start(create) assert results.messages == [on_error(220, ex)] def test_combine_latest_throw_return(self): ex = 'ex' scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_next(210, 2), on_completed(230)] msgs2 = [on_next(150, 1), on_error(220, ex)] e1 = scheduler.create_hot_observable(msgs1) e2 = scheduler.create_hot_observable(msgs2) def create(): return e2.pipe( ops.combine_latest(e1), ops.map(sum), ) results = scheduler.start(create) assert results.messages == [on_error(220, ex)] def test_combine_latest_throw_on_error(self): ex1 = 'ex1' ex2 = 'ex2' scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_error(220, ex1)] msgs2 = [on_next(150, 1), on_error(230, ex2)] e1 = scheduler.create_hot_observable(msgs1) e2 = scheduler.create_hot_observable(msgs2) def create(): return e1.pipe( ops.combine_latest(e2), ops.map(sum), ) results = scheduler.start(create) assert results.messages == [on_error(220, ex1)] def test_combine_latest_error_on_error(self): ex1 = 'ex1' ex2 = 'ex2' scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_next(210, 2), on_error(220, ex1)] msgs2 = [on_next(150, 1), on_error(230, ex2)] e1 = scheduler.create_hot_observable(msgs1) e2 = scheduler.create_hot_observable(msgs2) def create(): return e1.pipe( ops.combine_latest(e2), ops.map(sum), ) results = scheduler.start(create) assert results.messages == [on_error(220, ex1)] def test_combine_latest_throw_error(self): ex1 = 'ex1' ex2 = 'ex2' scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_next(210, 2), on_error(220, ex1)] msgs2 = [on_next(150, 1), on_error(230, ex2)] e1 = scheduler.create_hot_observable(msgs1) e2 = scheduler.create_hot_observable(msgs2) def create(): return e2.pipe( ops.combine_latest(e1), ops.map(sum), ) results = scheduler.start(create) assert results.messages == [on_error(220, ex1)] def test_combine_latest_never_on_error(self): ex = 'ex' scheduler = TestScheduler() msgs = [on_next(150, 1), on_error(220, ex)] e1 = rx.never() e2 = scheduler.create_hot_observable(msgs) def create(): return e1.pipe( ops.combine_latest(e2), ops.map(sum), ) results = scheduler.start(create) assert results.messages == [on_error(220, ex)] def test_combine_latest_throw_never(self): ex = 'ex' scheduler = TestScheduler() msgs = [on_next(150, 1), on_error(220, ex)] e1 = rx.never() e2 = scheduler.create_hot_observable(msgs) def create(): return e2.pipe( ops.combine_latest(e1), ops.map(sum), ) results = scheduler.start(create) assert results.messages == [on_error(220, ex)] def test_combine_latest_some_on_error(self): ex = 'ex' scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_next(215, 2), on_completed(230)] msgs2 = [on_next(150, 1), on_error(220, ex)] e1 = scheduler.create_hot_observable(msgs1) e2 = scheduler.create_hot_observable(msgs2) def create(): return e1.pipe( ops.combine_latest(e2), ops.map(sum), ) results = scheduler.start(create) assert results.messages == [on_error(220, ex)] def test_combine_latest_throw_some(self): ex = 'ex' scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_next(215, 2), on_completed(230)] msgs2 = [on_next(150, 1), on_error(220, ex)] e1 = scheduler.create_hot_observable(msgs1) e2 = scheduler.create_hot_observable(msgs2) def create(): return e2.pipe( ops.combine_latest(e1), ops.map(sum), ) results = scheduler.start(create) assert results.messages == [on_error(220, ex)] def test_combine_latest_throw_after_complete_left(self): ex = 'ex' scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_next(215, 2), on_completed(220)] msgs2 = [on_next(150, 1), on_error(230, ex)] e1 = scheduler.create_hot_observable(msgs1) e2 = scheduler.create_hot_observable(msgs2) def create(): return e1.pipe( ops.combine_latest(e2), ops.map(sum), ) results = scheduler.start(create) assert results.messages == [on_error(230, ex)] def test_combine_latest_throw_after_complete_right(self): ex = 'ex' scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_next(215, 2), on_completed(220)] msgs2 = [on_next(150, 1), on_error(230, ex)] e1 = scheduler.create_hot_observable(msgs1) e2 = scheduler.create_hot_observable(msgs2) def create(): return e2.pipe( ops.combine_latest(e1), ops.map(sum), ) results = scheduler.start(create) assert results.messages == [on_error(230, ex)] def test_combine_latest_interleaved_with_tail(self): scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_next(215, 2), on_next(225, 4), on_completed(230)] msgs2 = [on_next(150, 1), on_next(220, 3), on_next(230, 5), on_next(235, 6), on_next(240, 7), on_completed(250)] e1 = scheduler.create_hot_observable(msgs1) e2 = scheduler.create_hot_observable(msgs2) def create(): return e1.pipe( ops.combine_latest(e2), ops.map(sum), ) results = scheduler.start(create) assert results.messages == [ on_next(220, 2 + 3), on_next(225, 3 + 4), on_next(230, 4 + 5), on_next(235, 4 + 6), on_next(240, 4 + 7), on_completed(250)] def test_combine_latest_consecutive(self): scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_next(215, 2), on_next(225, 4), on_completed(230)] msgs2 = [on_next(150, 1), on_next(235, 6), on_next(240, 7), on_completed(250)] e1 = scheduler.create_hot_observable(msgs1) e2 = scheduler.create_hot_observable(msgs2) def create(): return e1.pipe( ops.combine_latest(e2), ops.map(sum), ) results = scheduler.start(create) assert results.messages == [on_next(235, 4 + 6), on_next(240, 4 + 7), on_completed(250)] def test_combine_latest_consecutive_end_with_error_left(self): ex = 'ex' scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_next(215, 2), on_next(225, 4), on_error(230, ex)] msgs2 = [on_next(150, 1), on_next(235, 6), on_next(240, 7), on_completed(250)] e1 = scheduler.create_hot_observable(msgs1) e2 = scheduler.create_hot_observable(msgs2) def create(): return e1.pipe( ops.combine_latest(e2), ops.map(sum), ) results = scheduler.start(create) assert results.messages == [on_error(230, ex)] def test_combine_latest_consecutive_end_with_error_right(self): ex = 'ex' scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_next(215, 2), on_next(225, 4), on_completed(230)] msgs2 = [on_next(150, 1), on_next(235, 6), on_next(240, 7), on_error(245, ex)] e1 = scheduler.create_hot_observable(msgs1) e2 = scheduler.create_hot_observable(msgs2) def create(): return e2.pipe( ops.combine_latest(e1), ops.map(sum), ) results = scheduler.start(create) assert results.messages == [on_next(235, 4 + 6), on_next(240, 4 + 7), on_error(245, ex)] def test_combine_latest_mapper_throws(self): ex = 'ex' scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_next(215, 2), on_completed(230)] msgs2 = [on_next(150, 1), on_next(220, 3), on_completed(240)] e1 = scheduler.create_hot_observable(msgs1) e2 = scheduler.create_hot_observable(msgs2) def create(): return e1.pipe( ops.combine_latest(e2), ops.map(lambda xy: _raise(ex)), ) results = scheduler.start(create) assert results.messages == [on_error(220, ex)] if __name__ == '__main__': unittest.main() RxPY-3.2.0/tests/test_observable/test_concat.py000066400000000000000000000206421404130727200215670ustar00rootroot00000000000000import unittest import rx from rx import operators as ops from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class RxException(Exception): pass # Helper function for raising exceptions within lambdas def _raise(ex): raise RxException(ex) class TestConcat(unittest.TestCase): def test_concat_empty_empty(self): scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_completed(230)] msgs2 = [on_next(150, 1), on_completed(250)] e1 = scheduler.create_hot_observable(msgs1) e2 = scheduler.create_hot_observable(msgs2) def create(): return e1.pipe(ops.concat(e2)) results = scheduler.start(create) assert results.messages == [on_completed(250)] def test_concat_empty_never(self): scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_completed(230)] e1 = scheduler.create_hot_observable(msgs1) e2 = rx.never() def create(): return e1.pipe(ops.concat(e2)) results = scheduler.start(create) assert results.messages == [] def test_concat_never_empty(self): scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_completed(230)] e1 = scheduler.create_hot_observable(msgs1) e2 = rx.never() def create(): return e2.pipe(ops.concat(e1)) results = scheduler.start(create) assert results.messages == [] def test_concat_never_never(self): scheduler = TestScheduler() e1 = rx.never() e2 = rx.never() def create(): return e1.pipe(ops.concat(e2)) results = scheduler.start(create) assert results.messages == [] def test_concat_empty_on_error(self): ex = 'ex' scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_completed(230)] msgs2 = [on_next(150, 1), on_error(250, ex)] e1 = scheduler.create_hot_observable(msgs1) e2 = scheduler.create_hot_observable(msgs2) def create(): return e1.pipe(ops.concat(e2)) results = scheduler.start(create) assert results.messages == [on_error(250, ex)] def test_concat_throw_empty(self): ex = 'ex' scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_error(230, ex)] msgs2 = [on_next(150, 1), on_completed(250)] e1 = scheduler.create_hot_observable(msgs1) e2 = scheduler.create_hot_observable(msgs2) def create(): return e1.pipe(ops.concat(e2)) results = scheduler.start(create) assert results.messages == [on_error(230, ex)] def test_concat_throw_on_error(self): ex = 'ex' scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_error(230, ex)] msgs2 = [on_next(150, 1), on_error(250, 'ex2')] e1 = scheduler.create_hot_observable(msgs1) e2 = scheduler.create_hot_observable(msgs2) def create(): return e1.pipe(ops.concat(e2)) results = scheduler.start(create) assert results.messages == [on_error(230, ex)] def test_concat_return_empty(self): scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_next(210, 2), on_completed(230)] msgs2 = [on_next(150, 1), on_completed(250)] e1 = scheduler.create_hot_observable(msgs1) e2 = scheduler.create_hot_observable(msgs2) def create(): return e1.pipe(ops.concat(e2)) results = scheduler.start(create) assert results.messages == [on_next(210, 2), on_completed(250)] def test_concat_empty_return(self): scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_completed(230)] msgs2 = [on_next(150, 1), on_next(240, 2), on_completed(250)] e1 = scheduler.create_hot_observable(msgs1) e2 = scheduler.create_hot_observable(msgs2) def create(): return e1.pipe(ops.concat(e2)) results = scheduler.start(create) assert results.messages == [on_next(240, 2), on_completed(250)] def test_concat_return_never(self): scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_next(210, 2), on_completed(230)] e1 = scheduler.create_hot_observable(msgs1) e2 = rx.never() def create(): return e1.pipe(ops.concat(e2)) results = scheduler.start(create) assert results.messages == [on_next(210, 2)] def test_concat_never_return(self): scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_next(210, 2), on_completed(230)] e1 = scheduler.create_hot_observable(msgs1) e2 = rx.never() def create(): return e2.pipe(ops.concat(e1)) results = scheduler.start(create) assert results.messages == [] def test_concat_return_return(self): scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_next(220, 2), on_completed(230)] msgs2 = [on_next(150, 1), on_next(240, 3), on_completed(250)] e1 = scheduler.create_hot_observable(msgs1) e2 = scheduler.create_hot_observable(msgs2) def create(): return e1.pipe(ops.concat(e2)) results = scheduler.start(create) assert results.messages == [on_next(220, 2), on_next(240, 3), on_completed(250)] def test_concat_throw_return(self): ex = 'ex' scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_error(230, ex)] msgs2 = [on_next(150, 1), on_next(240, 2), on_completed(250)] e1 = scheduler.create_hot_observable(msgs1) e2 = scheduler.create_hot_observable(msgs2) def create(): return e1.pipe(ops.concat(e2)) results = scheduler.start(create) assert results.messages == [on_error(230, ex)] def test_concat_return_on_error(self): ex = 'ex' scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_next(220, 2), on_completed(230)] msgs2 = [on_next(150, 1), on_error(250, ex)] e1 = scheduler.create_hot_observable(msgs1) e2 = scheduler.create_hot_observable(msgs2) def create(): return e1.pipe(ops.concat(e2)) results = scheduler.start(create) assert results.messages == [on_next(220, 2), on_error(250, ex)] def test_concat_some_data_some_data(self): scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_next(210, 2), on_next(220, 3), on_completed(225)] msgs2 = [on_next(150, 1), on_next(230, 4), on_next(240, 5), on_completed(250)] e1 = scheduler.create_hot_observable(msgs1) e2 = scheduler.create_hot_observable(msgs2) def create(): return e1.pipe(ops.concat(e2)) results = scheduler.start(create) assert results.messages == [on_next(210, 2), on_next( 220, 3), on_next(230, 4), on_next(240, 5), on_completed(250)] def test_concat_forward_scheduler(self): scheduler = TestScheduler() subscribe_schedulers = {'e1': 'unknown', 'e2': 'unknown'} def subscribe_e1(observer, scheduler='not_set'): subscribe_schedulers['e1'] = scheduler observer.on_completed() def subscribe_e2(observer, scheduler='not_set'): subscribe_schedulers['e2'] = scheduler observer.on_completed() e1 = rx.create(subscribe_e1) e2 = rx.create(subscribe_e2) stream = e1.pipe(ops.concat(e2)) stream.subscribe(scheduler=scheduler) scheduler.advance_to(1000) assert subscribe_schedulers['e1'] is scheduler assert subscribe_schedulers['e2'] is scheduler def test_concat_forward_none_scheduler(self): subscribe_schedulers = {'e1': 'unknown', 'e2': 'unknown'} def subscribe_e1(observer, scheduler='not_set'): subscribe_schedulers['e1'] = scheduler observer.on_completed() def subscribe_e2(observer, scheduler='not_set'): subscribe_schedulers['e2'] = scheduler observer.on_completed() e1 = rx.create(subscribe_e1) e2 = rx.create(subscribe_e2) stream = e1.pipe(ops.concat(e2)) stream.subscribe() assert subscribe_schedulers['e1'] is None assert subscribe_schedulers['e2'] is None RxPY-3.2.0/tests/test_observable/test_connectableobservable.py000066400000000000000000000154141404130727200246430ustar00rootroot00000000000000import unittest import rx from rx import operators as ops from rx.core import Observable from rx.core.typing import Observer from rx.testing import TestScheduler, ReactiveTest from rx.subject import Subject from rx.core import ConnectableObservable on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class MySubject(Observable, Observer): def __init__(self): super(MySubject, self).__init__() self.dispose_on_map = {} self.subscribe_count = 0 self.disposed = False def _subscribe_core(self, observer, scheduler=None): self.subscribe_count += 1 self.observer = observer class Duck: def __init__(self, this): self.this = this def dispose(self): self.this.disposed = True return Duck(self) def dispose_on(self, value, disposable): self.dispose_on_map[value] = disposable def on_next(self, value): self.observer.on_next(value) if value in self.dispose_on_map: self.dispose_on_map[value].dispose() def on_error(self, error): self.observer.on_error(error) def on_completed(self): self.observer.on_completed() class TestConnectableObservable(unittest.TestCase): def test_connectable_observable_creation(self): y = [0] s2 = Subject() co2 = ConnectableObservable(rx.return_value(1), s2) def on_next(x): y[0] = x co2.subscribe(on_next=on_next) self.assertNotEqual(1, y[0]) co2.connect() self.assertEqual(1, y[0]) def test_connectable_observable_connected(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(210, 1), on_next(220, 2), on_next(230, 3), on_next(240, 4), on_completed(250) ) subject = MySubject() conn = ConnectableObservable(xs, subject) disconnect = conn.connect(scheduler) res = scheduler.start(lambda: conn) assert res.messages == [ on_next(210, 1), on_next(220, 2), on_next(230, 3), on_next(240, 4), on_completed(250)] def test_connectable_observable_not_connected(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(210, 1), on_next(220, 2), on_next(230, 3), on_next(240, 4), on_completed(250) ) subject = MySubject() conn = ConnectableObservable(xs, subject) res = scheduler.start(lambda: conn) assert res.messages == [] def test_connectable_observable_disconnected(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(210, 1), on_next(220, 2), on_next(230, 3), on_next(240, 4), on_completed(250) ) subject = MySubject() conn = ConnectableObservable(xs, subject) disconnect = conn.connect(scheduler) disconnect.dispose() res = scheduler.start(lambda: conn) assert res.messages == [] def test_connectable_observable_disconnect_future(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(210, 1), on_next(220, 2), on_next(230, 3), on_next(240, 4), on_completed(250) ) subject = MySubject() conn = ConnectableObservable(xs, subject) subject.dispose_on(3, conn.connect()) res = scheduler.start(lambda: conn) assert res.messages == [ on_next(210, 1), on_next(220, 2), on_next(230, 3)] def test_connectable_observable_multiple_non_overlapped_connections(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(210, 1), on_next(220, 2), on_next(230, 3), on_next(240, 4), on_next(250, 5), on_next(260, 6), on_next(270, 7), on_next(280, 8), on_next(290, 9), on_completed(300) ) subject = Subject() conn = xs.pipe(ops.multicast(subject)) c1 = [None] def action10(scheduler, state): c1[0] = conn.connect(scheduler) scheduler.schedule_absolute(225, action10) def action11(scheduler, state): c1[0].dispose() scheduler.schedule_absolute(241, action11) def action12(scheduler, state): c1[0].dispose() # idempotency test scheduler.schedule_absolute(245, action12) def action13(scheduler, state): c1[0].dispose() # idempotency test scheduler.schedule_absolute(251, action13) def action14(scheduler, state): c1[0].dispose() # idempotency test scheduler.schedule_absolute(260, action14) c2 = [None] def action20(scheduler, state): c2[0] = conn.connect(scheduler) scheduler.schedule_absolute(249, action20) def action21(scheduler, state): c2[0].dispose() scheduler.schedule_absolute(255, action21) def action22(scheduler, state): c2[0].dispose() # idempotency test scheduler.schedule_absolute(265, action22) def action23(scheduler, state): c2[0].dispose() # idempotency test scheduler.schedule_absolute(280, action23) c3 = [None] def action30(scheduler, state): c3[0] = conn.connect(scheduler) scheduler.schedule_absolute(275, action30) def action31(scheduler, state): c3[0].dispose() scheduler.schedule_absolute(295, action31) res = scheduler.start(lambda: conn) assert res.messages == [ on_next(230, 3), on_next(240, 4), on_next(250, 5), on_next(280, 8), on_next(290, 9)] assert xs.subscriptions == [ subscribe(225, 241), subscribe(249, 255), subscribe(275, 295)] def test_connectable_observable_forward_scheduler(self): scheduler = TestScheduler() subscribe_scheduler = 'unknown' def subscribe(observer, scheduler=None): nonlocal subscribe_scheduler subscribe_scheduler = scheduler xs = rx.create(subscribe) subject = MySubject() conn = ConnectableObservable(xs, subject) conn.connect(scheduler) assert subscribe_scheduler is scheduler RxPY-3.2.0/tests/test_observable/test_contains.py000066400000000000000000000104571404130727200221410ustar00rootroot00000000000000import unittest from rx import operators as ops from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class TestContains(unittest.TestCase): def test_contains_empty(self): scheduler = TestScheduler() msgs = [on_next(150, 1), on_completed(250)] xs = scheduler.create_hot_observable(msgs) def create(): return xs.pipe(ops.contains(42)) res = scheduler.start(create=create).messages assert res == [on_next(250, False), on_completed(250)] def test_contains_return_positive(self): scheduler = TestScheduler() msgs = [on_next(150, 1), on_next(210, 2), on_completed(250)] xs = scheduler.create_hot_observable(msgs) def create(): return xs.pipe(ops.contains(2)) res = scheduler.start(create=create).messages assert res == [on_next(210, True), on_completed(210)] def test_contains_return_negative(self): scheduler = TestScheduler() msgs = [on_next(150, 1), on_next(210, 2), on_completed(250)] xs = scheduler.create_hot_observable(msgs) def create(): return xs.pipe(ops.contains(-2)) res = scheduler.start(create=create).messages assert res == [on_next(250, False), on_completed(250)] def test_contains_some_positive(self): scheduler = TestScheduler() msgs = [on_next(150, 1), on_next(210, 2), on_next(220, 3), on_next(230, 4), on_completed(250)] xs = scheduler.create_hot_observable(msgs) def create(): return xs.pipe(ops.contains(3)) res = scheduler.start(create=create).messages assert res == [on_next(220, True), on_completed(220)] def test_contains_some_negative(self): scheduler = TestScheduler() msgs = [on_next(150, 1), on_next(210, 2), on_next(220, 3), on_next(230, 4), on_completed(250)] xs = scheduler.create_hot_observable(msgs) def create(): return xs.pipe(ops.contains(-3)) res = scheduler.start(create=create).messages assert res == [on_next(250, False), on_completed(250)] def test_contains_on_error(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_error(210, ex)) def create(): return xs.pipe(ops.contains(42)) res = scheduler.start(create=create).messages assert res == [on_error(210, ex)] def test_contains_never(self): scheduler = TestScheduler() msgs = [on_next(150, 1)] xs = scheduler.create_hot_observable(msgs) def create(): return xs.pipe(ops.contains(42)) res = scheduler.start(create=create).messages assert res == [] def test_contains_comparer_throws(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_next(210, 2)) def create(): def comparer(a, b): raise Exception(ex) return xs.pipe(ops.contains(42, comparer)) res = scheduler.start(create=create).messages assert res == [on_error(210, ex)] def test_contains_comparer_contains_value(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(150, 1), on_next(210, 3), on_next(220, 4), on_next(230, 8), on_completed(250)) def create(): return xs.pipe(ops.contains(42, lambda a, b: a % 2 == b % 2)) res = scheduler.start(create=create).messages assert res == [on_next(220, True), on_completed(220)] def test_contains_comparer_does_not_contain_value(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(150, 1), on_next(210, 2), on_next(220, 4), on_next(230, 8), on_completed(250)) def create(): return xs.pipe(ops.contains(21, lambda a, b: a % 2 == b % 2)) res = scheduler.start(create=create).messages assert res == [on_next(250, False), on_completed(250)] RxPY-3.2.0/tests/test_observable/test_count.py000066400000000000000000000154651404130727200214570ustar00rootroot00000000000000import unittest from rx import operators as _ from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class TestCount(unittest.TestCase): def test_count_empty(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_completed(250)) res = scheduler.start(create=lambda: xs.pipe(_.count())).messages assert res == [on_next(250, 0), on_completed(250)] def test_count_empty_ii(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_next(210, 2), on_completed(250)) def create(): return xs.pipe(_.count()) res = scheduler.start(create=create).messages assert res == [on_next(250, 1), on_completed(250)] def test_count_some(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(150, 1), on_next(210, 2), on_next(220, 3), on_next(230, 4), on_completed(250)) res = scheduler.start(create=lambda: xs.pipe(_.count())).messages assert res == [on_next(250, 3), on_completed(250)] def test_count_on_error(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_error(210, ex)) res = scheduler.start(create=lambda: xs.pipe(_.count())).messages assert res == [on_error(210, ex)] def test_count_never(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1)) res = scheduler.start(create=lambda: xs.pipe(_.count())).messages assert res == [] def test_count_predicate_empty_true(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_completed(250)) def create(): return xs.pipe(_.count(lambda _: True)) res = scheduler.start(create=create) assert res.messages == [on_next(250, 0), on_completed(250)] assert xs.subscriptions == [subscribe(200, 250)] def test_count_predicate_empty_false(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_completed(250)) def create(): return xs.pipe(_.count(lambda _: False)) res = scheduler.start(create=create) assert res.messages == [on_next(250, 0), on_completed(250)] assert xs.subscriptions == [subscribe(200, 250)] def test_count_predicate_return_true(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_next(210, 2), on_completed(250)) def create(): return xs.pipe(_.count(lambda _: True)) res = scheduler.start(create=create) assert res.messages == [on_next(250, 1), on_completed(250)] assert xs.subscriptions == [subscribe(200, 250)] def test_count_predicate_return_false(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_next(210, 2), on_completed(250)) def create(): return xs.pipe(_.count(lambda _: False)) res = scheduler.start(create=create) assert res.messages == [on_next(250, 0), on_completed(250)] assert xs.subscriptions == [subscribe(200, 250)] def test_count_predicate_some_all(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(150, 1), on_next(210, 2), on_next(220, 3), on_next(230, 4), on_completed(250)) def create(): return xs.pipe(_.count(lambda x: x < 10)) res = scheduler.start(create=create) assert res.messages == [on_next(250, 3), on_completed(250)] assert xs.subscriptions == [subscribe(200, 250)] def test_count_predicate_some_none(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(150, 1), on_next(210, 2), on_next(220, 3), on_next(230, 4), on_completed(250)) def create(): return xs.pipe(_.count(lambda x: x > 10)) res = scheduler.start(create=create) assert res.messages == [on_next(250, 0), on_completed(250)] assert xs.subscriptions == [subscribe(200, 250)] def test_count_predicate_some_even(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(150, 1), on_next(210, 2), on_next(220, 3), on_next(230, 4), on_completed(250)) def create(): return xs.pipe(_.count(lambda x: x % 2 == 0)) res = scheduler.start(create=create) assert res.messages == [on_next(250, 2), on_completed(250)] assert xs.subscriptions == [subscribe(200, 250)] def test_count_predicate_throw_true(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_error(210, ex)) def create(): return xs.pipe(_.count(lambda _: True)) res = scheduler.start(create=create) assert res.messages == [on_error(210, ex)] assert xs.subscriptions == [subscribe(200, 210)] def test_count_predicate_throw_false(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_error(210, ex)) def create(): return xs.pipe(_.count(lambda _: False)) res = scheduler.start(create=create) assert res.messages == [on_error(210, ex)] assert xs.subscriptions == [subscribe(200, 210)] def test_count_predicate_never(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1)) def create(): return xs.pipe(_.count(lambda _: True)) res = scheduler.start(create=create) assert res.messages == [] assert xs.subscriptions == [subscribe(200, 1000)] def test_count_predicate_predicate_throws(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_next(210, 2), on_next(230, 3), on_completed(240)) def create(): def predicate(x): if x == 3: raise Exception(ex) else: return True return xs.pipe(_.count(predicate)) res = scheduler.start(create=create) assert res.messages == [on_error(230, ex)] assert xs.subscriptions == [subscribe(200, 230)] RxPY-3.2.0/tests/test_observable/test_create.py000066400000000000000000000077151404130727200215710ustar00rootroot00000000000000import unittest import rx from rx.disposable import Disposable from rx.testing import TestScheduler, ReactiveTest from rx.disposable import BooleanDisposable on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class RxException(Exception): pass # Helper function for raising exceptions within lambdas def _raise(ex): raise RxException(ex) class TestCreate(unittest.TestCase): def test_create_next(self): scheduler = TestScheduler() def _create(): def subscribe(o, scheduler=None): o.on_next(1) o.on_next(2) return lambda: None return rx.create(subscribe) results = scheduler.start(_create) assert results.messages == [on_next(200, 1), on_next(200, 2)] def test_create_completed(self): scheduler = TestScheduler() def _create(): def subscribe(o, scheduler=None): o.on_completed() o.on_next(100) o.on_error('ex') o.on_completed() return lambda: None return rx.create(subscribe) results = scheduler.start(_create) assert results.messages == [on_completed(200)] def test_create_error(self): scheduler = TestScheduler() ex = 'ex' def _create(): def subscribe(o, scheduler=None): o.on_error(ex) o.on_next(100) o.on_error('foo') o.on_completed() return lambda: None return rx.create(subscribe) results = scheduler.start(_create) assert results.messages == [on_error(200, ex)] def test_create_exception(self): with self.assertRaises(RxException): rx.create(lambda o, s: _raise('ex')).subscribe() def test_create_dispose(self): scheduler = TestScheduler() def _create(): def subscribe(o, scheduler=None): is_stopped = [False] o.on_next(1) o.on_next(2) def action1(scheduler, state): if not is_stopped[0]: return o.on_next(3) scheduler.schedule_relative(600, action1) def action2(scheduler, state): if not is_stopped[0]: return o.on_next(4) scheduler.schedule_relative(700, action2) def action3(scheduler, state): if not is_stopped[0]: return o.on_next(5) scheduler.schedule_relative(900, action3) def action4(scheduler, state): if not is_stopped[0]: return o.on_next(6) scheduler.schedule_relative(1100, action4) def dispose(): is_stopped[0] = True return dispose return rx.create(subscribe) results = scheduler.start(_create) assert results.messages == [on_next(200, 1), on_next(200, 2), on_next(800, 3), on_next(900, 4)] def test_create_observer_throws(self): def subscribe(o, scheduler=None): o.on_next(1) return lambda: None with self.assertRaises(RxException): rx.create(subscribe).subscribe(lambda x: _raise('ex')) def subscribe2(o, scheduler=None): o.on_error('exception') return lambda: None with self.assertRaises(RxException): rx.create(subscribe2).subscribe(on_error=lambda ex: _raise('ex')) def subscribe3(o, scheduler=None): o.on_completed() return lambda: None with self.assertRaises(RxException): rx.create(subscribe3).subscribe(on_completed=lambda: _raise('ex')) RxPY-3.2.0/tests/test_observable/test_debounce.py000066400000000000000000000276251404130727200221140ustar00rootroot00000000000000import unittest from rx import empty, never, throw, operators as _ from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class RxException(Exception): pass # Helper function for raising exceptions within lambdas def _raise(ex): raise RxException(ex) class TestDebounce(unittest.TestCase): def test_debounce_timespan_allpass(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_next(200, 2), on_next(250, 3), on_next( 300, 4), on_next(350, 5), on_next(400, 6), on_next(450, 7), on_next(500, 8), on_completed(550)) def create(): return xs.pipe(_.debounce(40)) results = scheduler.start(create) assert results.messages == [on_next(290, 3), on_next(340, 4), on_next( 390, 5), on_next(440, 6), on_next(490, 7), on_next(540, 8), on_completed(550)] def test_debounce_timespan_allpass_error_end(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_next(200, 2), on_next(250, 3), on_next( 300, 4), on_next(350, 5), on_next(400, 6), on_next(450, 7), on_next(500, 8), on_error(550, ex)) def create(): return xs.pipe(_.debounce(40)) results = scheduler.start(create) assert results.messages == [on_next(290, 3), on_next(340, 4), on_next( 390, 5), on_next(440, 6), on_next(490, 7), on_next(540, 8), on_error(550, ex)] def test_debounce_timespan_alldrop(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_next(200, 2), on_next(250, 3), on_next( 300, 4), on_next(350, 5), on_next(400, 6), on_next(450, 7), on_next(500, 8), on_completed(550)) def create(): return xs.pipe(_.debounce(60)) results = scheduler.start(create) assert results.messages == [on_next(550, 8), on_completed(550)] def test_debounce_timespan_alldrop_error_end(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_next(200, 2), on_next(250, 3), on_next( 300, 4), on_next(350, 5), on_next(400, 6), on_next(450, 7), on_next(500, 8), on_error(550, ex)) def create(): return xs.pipe(_.debounce(60)) results = scheduler.start(create) assert results.messages == [on_error(550, ex)] def test_debounce_timespan_some_drop(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_next(250, 2), on_next(350, 3), on_next( 370, 4), on_next(421, 5), on_next(480, 6), on_next(490, 7), on_next(500, 8), on_completed(600)) def create(): return xs.pipe(_.debounce(50)) results = scheduler.start(create) assert results.messages == [on_next(300, 2), on_next( 420, 4), on_next(471, 5), on_next(550, 8), on_completed(600)] def test_debounce_empty(self): scheduler = TestScheduler() def create(): return empty().pipe(_.debounce(10)) results = scheduler.start(create) assert results.messages == [on_completed(200)] def test_debounce_error(self): ex = 'ex' scheduler = TestScheduler() def create(): return throw(ex).pipe(_.debounce(10)) results = scheduler.start(create) assert results.messages == [on_error(200, ex)] def test_debounce_never(self): scheduler = TestScheduler() def create(): return never().pipe(_.debounce(10)) results = scheduler.start(create) assert results.messages == [] def test_debounce_duration_delay_behavior(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(150, -1), on_next(250, 0), on_next(280, 1), on_next(310, 2), on_next(350, 3), on_next(400, 4), on_completed(550)) ys = [scheduler.create_cold_observable(on_next(20, 42), on_next(25, 99)), scheduler.create_cold_observable(on_next(20, 42), on_next(25, 99)), scheduler.create_cold_observable( on_next(20, 42), on_next(25, 99)), scheduler.create_cold_observable(on_next(20, 42), on_next(25, 99)), scheduler.create_cold_observable(on_next(20, 42), on_next(25, 99))] def create(): def mapper(x): return ys[x] return xs.pipe(_.throttle_with_mapper(mapper)) results = scheduler.start(create) assert results.messages == [on_next(250 + 20, 0), on_next(280 + 20, 1), on_next(310 + 20, 2), on_next(350 + 20, 3), on_next(400 + 20, 4), on_completed(550)] assert xs.subscriptions == [subscribe(200, 550)] assert ys[0].subscriptions == [subscribe(250, 250 + 20)] assert ys[1].subscriptions == [subscribe(280, 280 + 20)] assert ys[2].subscriptions == [subscribe(310, 310 + 20)] assert ys[3].subscriptions == [subscribe(350, 350 + 20)] assert ys[4].subscriptions == [subscribe(400, 400 + 20)] def test_debounce_duration_throttle_behavior(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, -1), on_next(250, 0), on_next(280, 1), on_next(310, 2), on_next(350, 3), on_next(400, 4), on_completed(550)) ys = [scheduler.create_cold_observable(on_next(20, 42), on_next(25, 99)), scheduler.create_cold_observable(on_next(40, 42), on_next(45, 99)), scheduler.create_cold_observable( on_next(20, 42), on_next(25, 99)), scheduler.create_cold_observable(on_next(60, 42), on_next(65, 99)), scheduler.create_cold_observable(on_next(20, 42), on_next(25, 99))] def create(): def mapper(x): return ys[x] return xs.pipe(_.throttle_with_mapper(mapper)) results = scheduler.start(create) assert results.messages == [on_next(250 + 20, 0), on_next(310 + 20, 2), on_next(400 + 20, 4), on_completed(550)] assert xs.subscriptions == [subscribe(200, 550)] assert ys[0].subscriptions == [subscribe(250, 250 + 20)] assert ys[1].subscriptions == [subscribe(280, 310)] assert ys[2].subscriptions == [subscribe(310, 310 + 20)] assert ys[3].subscriptions == [subscribe(350, 400)] assert ys[4].subscriptions == [subscribe(400, 400 + 20)] def test_debounce_duration_early_completion(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(150, -1), on_next(250, 0), on_next(280, 1), on_next(310, 2), on_next(350, 3), on_next(400, 4), on_completed(410)) ys = [ scheduler.create_cold_observable( on_next(20, 42), on_next(25, 99)), scheduler.create_cold_observable( on_next(40, 42), on_next(45, 99)), scheduler.create_cold_observable( on_next(20, 42), on_next(25, 99)), scheduler.create_cold_observable( on_next(60, 42), on_next(65, 99)), scheduler.create_cold_observable( on_next(20, 42), on_next(25, 99)) ] def create(): def mapper(x): return ys[x] return xs.pipe(_.throttle_with_mapper(mapper)) results = scheduler.start(create) assert results.messages == [on_next(250 + 20, 0), on_next(310 + 20, 2), on_next(410, 4), on_completed(410)] assert xs.subscriptions == [subscribe(200, 410)] assert ys[0].subscriptions == [subscribe(250, 250 + 20)] assert ys[1].subscriptions == [subscribe(280, 310)] assert ys[2].subscriptions == [subscribe(310, 310 + 20)] assert ys[3].subscriptions == [subscribe(350, 400)] assert ys[4].subscriptions == [subscribe(400, 410)] def test_debounce_duration_inner_error(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_next( 250, 2), on_next(350, 3), on_next(450, 4), on_completed(550)) ex = 'ex' def create(): def mapper(x): if x < 4: return scheduler.create_cold_observable(on_next(x * 10, "Ignore"), on_next(x * 10 + 5, "Aargh!")) else: return scheduler.create_cold_observable(on_error(x * 10, ex)) return xs.pipe(_.throttle_with_mapper(mapper)) results = scheduler.start(create) assert results.messages == [on_next(250 + 2 * 10, 2), on_next(350 + 3 * 10, 3), on_error(450 + 4 * 10, ex)] assert xs.subscriptions == [subscribe(200, 490)] def test_debounce_duration_outer_error(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_next( 250, 2), on_next(350, 3), on_next(450, 4), on_error(460, ex)) def create(): def mapper(x): return scheduler.create_cold_observable(on_next(x * 10, "Ignore"), on_next(x * 10 + 5, "Aargh!")) return xs.pipe(_.throttle_with_mapper(mapper)) results = scheduler.start(create) assert results.messages == [on_next(250 + 2 * 10, 2), on_next(350 + 3 * 10, 3), on_error(460, ex)] assert xs.subscriptions == [subscribe(200, 460)] def test_debounce_duration_mapper_throws(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_next( 250, 2), on_next(350, 3), on_next(450, 4), on_completed(550)) def create(): def mapper(x): if x < 4: return scheduler.create_cold_observable(on_next(x * 10, "Ignore"), on_next(x * 10 + 5, "Aargh!")) else: _raise(ex) return xs.pipe(_.throttle_with_mapper(mapper)) results = scheduler.start(create) assert results.messages == [on_next(250 + 2 * 10, 2), on_next(350 + 3 * 10, 3), on_error(450, ex)] assert xs.subscriptions == [subscribe(200, 450)] def test_debounce_duration_inner_done_delay_behavior(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_next( 250, 2), on_next(350, 3), on_next(450, 4), on_completed(550)) def create(): def mapper(x): return scheduler.create_cold_observable(on_completed(x * 10)) return xs.pipe(_.throttle_with_mapper(mapper)) results = scheduler.start(create) assert results.messages == [ on_next(250 + 2 * 10, 2), on_next(350 + 3 * 10, 3), on_next(450 + 4 * 10, 4), on_completed(550)] assert xs.subscriptions == [subscribe(200, 550)] def test_debounce_duration_inner_done_throttle_behavior(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(150, 1), on_next(250, 2), on_next(280, 3), on_next(300, 4), on_next(400, 5), on_next(410, 6), on_completed(550)) def create(): def mapper(x): return scheduler.create_cold_observable(on_completed(x * 10)) return xs.pipe(_.throttle_with_mapper(mapper)) results = scheduler.start(create) assert results.messages == [on_next(250 + 2 * 10, 2), on_next(300 + 4 * 10, 4), on_next(410 + 6 * 10, 6), on_completed(550)] assert xs.subscriptions == [subscribe(200, 550)] RxPY-3.2.0/tests/test_observable/test_defaultifempty.py000066400000000000000000000042701404130727200233410ustar00rootroot00000000000000import unittest from rx import operators as ops from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class RxException(Exception): pass # Helper function for raising exceptions within lambdas def _raise(ex): raise RxException(ex) class TestDistinctUntilChanged(unittest.TestCase): def test_default_if_empty_non_empty1(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(280, 42), on_next(360, 43), on_completed(420)) def create(): return xs.pipe(ops.default_if_empty()) results = scheduler.start(create) assert results.messages == [on_next(280, 42), on_next(360, 43), on_completed(420)] assert xs.subscriptions == [subscribe(200, 420)] def test_default_if_empty_non_empty2(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(280, 42), on_next(360, 43), on_completed(420)) def create(): return xs.pipe(ops.default_if_empty(-1)) results = scheduler.start(create) assert results.messages == [on_next(280, 42), on_next(360, 43), on_completed(420)] assert xs.subscriptions == [subscribe(200, 420)] def test_default_if_empty_empty1(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_completed(420)) def create(): return xs.pipe(ops.default_if_empty(None)) results = scheduler.start(create) assert results.messages == [on_next(420, None), on_completed(420)] assert xs.subscriptions == [subscribe(200, 420)] def test_default_if_empty_empty2(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_completed(420)) def create(): return xs.pipe(ops.default_if_empty(-1)) results = scheduler.start(create) assert results.messages == [on_next(420, -1), on_completed(420)] assert xs.subscriptions == [subscribe(200, 420)] RxPY-3.2.0/tests/test_observable/test_defer.py000066400000000000000000000054251404130727200214070ustar00rootroot00000000000000import unittest import rx from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class RxException(Exception): pass # Helper function for raising exceptions within lambdas def _raise(ex): raise RxException(ex) class TestDefer(unittest.TestCase): def test_defer_complete(self): xs = [None] invoked = [0] scheduler = TestScheduler() def create(): def defer(scheduler): invoked[0] += 1 xs[0] = scheduler.create_cold_observable( on_next(100, scheduler.clock), on_completed(200) ) return xs[0] return rx.defer(defer) results = scheduler.start(create) assert results.messages == [on_next(300, 200), on_completed(400)] assert(1 == invoked[0]) assert xs[0].subscriptions == [subscribe(200, 400)] def test_defer_error(self): scheduler = TestScheduler() invoked = [0] xs = [None] ex = 'ex' def create(): def defer(scheduler): invoked[0] += 1 xs[0] = scheduler.create_cold_observable( on_next(100, scheduler.clock), on_error(200, ex)) return xs[0] return rx.defer(defer) results = scheduler.start(create) assert results.messages == [on_next(300, 200), on_error(400, ex)] assert (1 == invoked[0]) assert xs[0].subscriptions == [subscribe(200, 400)] def test_defer_dispose(self): scheduler = TestScheduler() invoked = [0] xs = [None] def create(): def defer(scheduler): invoked[0] += 1 xs[0] = scheduler.create_cold_observable( on_next(100, scheduler.clock), on_next(200, invoked[0]), on_next(1100, 1000)) return xs[0] return rx.defer(defer) results = scheduler.start(create) assert results.messages == [on_next(300, 200), on_next(400, 1)] assert(1 == invoked[0]) assert xs[0].subscriptions == [subscribe(200, 1000)] def test_defer_on_error(self): scheduler = TestScheduler() invoked = [0] ex = 'ex' def create(): def defer(scheduler): invoked[0] += 1 raise Exception(ex) return rx.defer(defer) results = scheduler.start(create) assert results.messages == [on_error(200, ex)] assert(1 == invoked[0]) RxPY-3.2.0/tests/test_observable/test_delay.py000066400000000000000000000157001404130727200214150ustar00rootroot00000000000000import logging import unittest from datetime import datetime from rx.operators import delay from rx.testing import TestScheduler, ReactiveTest FORMAT = '%(asctime)-15s %(threadName)s %(message)s' logging.basicConfig(filename='rx.log', format=FORMAT, level=logging.DEBUG) log = logging.getLogger('Rx') on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class RxException(Exception): pass # Helper function for raising exceptions within lambdas def _raise(ex): raise RxException(ex) class TestDelay(unittest.TestCase): def test_delay_timespan_simple1(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_next(250, 2), on_next(350, 3), on_next(450, 4), on_completed(550)) def create(): return xs.pipe(delay(100.0)) results = scheduler.start(create) assert results.messages == [on_next(350, 2), on_next(450, 3), on_next(550, 4), on_completed(650)] assert xs.subscriptions == [subscribe(200, 550)] def test_delay_datetime_offset_simple1_impl(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_next(250, 2), on_next(350, 3), on_next(450, 4), on_completed(550)) def create(): dt = datetime.utcfromtimestamp(300.0) return xs.pipe(delay(dt)) results = scheduler.start(create) assert results.messages == [on_next(350, 2), on_next(450, 3), on_next(550, 4), on_completed(650)] assert xs.subscriptions == [subscribe(200, 550)] def test_delay_timespan_simple2_impl(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_next(250, 2), on_next(350, 3), on_next(450, 4), on_completed(550)) def create(): return xs.pipe(delay(50)) results = scheduler.start(create) assert results.messages == [on_next(300, 2), on_next(400, 3), on_next(500, 4), on_completed(600)] assert xs.subscriptions == [subscribe(200, 550)] def test_delay_datetime_offset_simple2_impl(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_next(250, 2), on_next(350, 3), on_next(450, 4), on_completed(550)) def create(): return xs.pipe( delay(datetime.utcfromtimestamp(250)) ) results = scheduler.start(create) assert results.messages == [on_next(300, 2), on_next(400, 3), on_next(500, 4), on_completed(600)] assert xs.subscriptions == [subscribe(200, 550)] def test_delay_timespan_simple3_impl(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_next(250, 2), on_next(350, 3), on_next(450, 4), on_completed(550)) def create(): return xs.pipe(delay(150)) results = scheduler.start(create) assert results.messages == [on_next(400, 2), on_next(500, 3), on_next(600, 4), on_completed(700)] assert xs.subscriptions == [subscribe(200, 550)] def test_delay_datetime_offset_simple3_impl(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(150, 1), on_next(250, 2), on_next(350, 3), on_next(450, 4), on_completed(550)) def create(): return xs.pipe( delay(datetime.utcfromtimestamp(350)) ) results = scheduler.start(create) assert results.messages == [on_next(400, 2), on_next(500, 3), on_next(600, 4), on_completed(700)] assert xs.subscriptions == [subscribe(200, 550)] def test_delay_timespan_error1_impl(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(150, 1), on_next(250, 2), on_next(350, 3), on_next(450, 4), on_error(550, ex)) def create(): return xs.pipe(delay(50)) results = scheduler.start(create) assert results.messages == [on_next(300, 2), on_next(400, 3), on_next(500, 4), on_error(550, ex)] assert xs.subscriptions == [subscribe(200, 550)] def test_delay_datetime_offset_error1_impl(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(150, 1), on_next(250, 2), on_next(350, 3), on_next(450, 4), on_error(550, ex)) def create(): return xs.pipe( delay(datetime.utcfromtimestamp(250)) ) results = scheduler.start(create) assert results.messages == [on_next(300, 2), on_next(400, 3), on_next(500, 4), on_error(550, ex)] assert xs.subscriptions == [subscribe(200, 550)] def test_delay_timespan_error2_impl(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(150, 1), on_next(250, 2), on_next(350, 3), on_next(450, 4), on_error(550, ex)) def create(): return xs.pipe(delay(150)) results = scheduler.start(create) assert results.messages == [on_next(400, 2), on_next(500, 3), on_error(550, ex)] assert xs.subscriptions == [subscribe(200, 550)] def test_delay_datetime_offset_error2_impl(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(150, 1), on_next(250, 2), on_next(350, 3), on_next(450, 4), on_error(550, ex)) def create(): return xs.pipe(delay(datetime.utcfromtimestamp(350))) results = scheduler.start(create) assert results.messages == [on_next(400, 2), on_next(500, 3), on_error(550, ex)] assert xs.subscriptions == [subscribe(200, 550)] def test_delay_empty(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_completed(550)) def create(): return xs.pipe(delay(10)) results = scheduler.start(create) assert results.messages == [on_completed(560)] assert xs.subscriptions == [subscribe(200, 550)] def test_delay_error(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_error(550, ex)) def create(): return xs.pipe(delay(10.0)) results = scheduler.start(create) assert results.messages == [on_error(550, ex)] assert xs.subscriptions == [subscribe(200, 550)] def test_delay_never(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1)) def create(): return xs.pipe(delay(10)) results = scheduler.start(create) assert results.messages == [] assert xs.subscriptions == [subscribe(200, 1000)] RxPY-3.2.0/tests/test_observable/test_delaywithmapper.py000066400000000000000000000127541404130727200235240ustar00rootroot00000000000000import unittest from rx import operators as ops from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class RxException(Exception): pass # Helper function for raising exceptions within lambdas def _raise(ex): raise RxException(ex) class TestDelayWithMapper(unittest.TestCase): # Delay with mapper def test_delay_duration_simple1(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(150, 1), on_next(210, 10), on_next(220, 30), on_next(230, 50), on_next(240, 35), on_next(250, 20), on_completed(260)) def create(): def mapper(x): return scheduler.create_cold_observable(on_next(x, '!')) return xs.pipe(ops.delay_with_mapper(mapper)) results = scheduler.start(create) assert results.messages == [ on_next(210 + 10, 10), on_next(220 + 30, 30), on_next(250 + 20, 20), on_next(240 + 35, 35), on_next(230 + 50, 50), on_completed(280)] assert xs.subscriptions == [subscribe(200, 260)] def test_delay_duration_simple2(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(150, 1), on_next(210, 2), on_next(220, 3), on_next(230, 4), on_next(240, 5), on_next(250, 6), on_completed(300)) ys = scheduler.create_cold_observable(on_next(10, '!')) def create(): return xs.pipe(ops.delay_with_mapper(lambda _: ys)) results = scheduler.start(create) assert results.messages == [ on_next(210 + 10, 2), on_next(220 + 10, 3), on_next(230 + 10, 4), on_next(240 + 10, 5), on_next(250 + 10, 6), on_completed(300)] assert xs.subscriptions == [subscribe(200, 300)] assert ys.subscriptions == [ subscribe(210, 220), subscribe(220, 230), subscribe(230, 240), subscribe(240, 250), subscribe(250, 260)] def test_delay_duration_simple3(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(150, 1), on_next(210, 2), on_next(220, 3), on_next(230, 4), on_next(240, 5), on_next(250, 6), on_completed(300)) ys = scheduler.create_cold_observable(on_next(100, '!')) def create(): return xs.pipe(ops.delay_with_mapper(lambda _: ys)) results = scheduler.start(create) assert results.messages == [ on_next(210 + 100, 2), on_next(220 + 100, 3), on_next(230 + 100, 4), on_next(240 + 100, 5), on_next(250 + 100, 6), on_completed(350)] assert xs.subscriptions == [subscribe(200, 300)] assert ys.subscriptions == [ subscribe(210, 310), subscribe(220, 320), subscribe(230, 330), subscribe(240, 340), subscribe(250, 350)] def test_delay_duration_simple4_inner_empty(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(150, 1), on_next(210, 2), on_next(220, 3), on_next(230, 4), on_next(240, 5), on_next(250, 6), on_completed(300)) ys = scheduler.create_cold_observable(on_completed(100)) def create(): return xs.pipe(ops.delay_with_mapper(lambda _: ys)) results = scheduler.start(create) assert results.messages == [ on_next(210 + 100, 2), on_next(220 + 100, 3), on_next(230 + 100, 4), on_next(240 + 100, 5), on_next(250 + 100, 6), on_completed(350)] assert xs.subscriptions == [subscribe(200, 300)] assert ys.subscriptions == [ subscribe(210, 310), subscribe(220, 320), subscribe(230, 330), subscribe(240, 340), subscribe(250, 350)] def test_delay_duration_dispose1(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(150, 1), on_next(210, 2), on_next(220, 3), on_next(230, 4), on_next(240, 5), on_next(250, 6), on_completed(300)) ys = scheduler.create_cold_observable(on_next(200, '!')) def create(): return xs.pipe(ops.delay_with_mapper(lambda _: ys)) results = scheduler.start(create, disposed=425) assert results.messages == [on_next(210 + 200, 2), on_next(220 + 200, 3)] assert xs.subscriptions == [subscribe(200, 300)] assert ys.subscriptions == [ subscribe(210, 410), subscribe(220, 420), subscribe(230, 425), subscribe(240, 425), subscribe(250, 425)] def test_delay_duration_dispose2(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(150, 1), on_next(210, 2), on_next(400, 3), on_completed(500)) ys = scheduler.create_cold_observable(on_next(50, '!')) def create(): return xs.pipe(ops.delay_with_mapper(lambda _: ys)) results = scheduler.start(create, disposed=300) assert results.messages == [on_next(210 + 50, 2)] assert xs.subscriptions == [subscribe(200, 300)] assert ys.subscriptions == [subscribe(210, 260)] RxPY-3.2.0/tests/test_observable/test_distinct.py000066400000000000000000000067121404130727200221430ustar00rootroot00000000000000import math import unittest import rx from rx import operators as ops from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class RxException(Exception): pass # Helper function for raising exceptions within lambdas def _raise(ex): raise RxException(ex) class TestDistinctUntilChanged(unittest.TestCase): def test_distinct_defaultcomparer_all_distinct(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(280, 4), on_next(300, 2), on_next(350, 1), on_next(380, 3), on_next(400, 5), on_completed(420)) def create(): return xs.pipe(ops.distinct()) results = scheduler.start(create) assert results.messages == [on_next(280, 4), on_next(300, 2), on_next(350, 1), on_next(380, 3), on_next(400, 5), on_completed(420)] assert xs.subscriptions == [subscribe(200, 420)] def test_distinct_default_comparer_some_duplicates(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(280, 4), on_next(300, 2), on_next(350, 2), on_next(380, 3), on_next(400, 4), on_completed(420)) def create(): return xs.pipe(ops.distinct()) results = scheduler.start(create) assert results.messages == [on_next(280, 4), on_next(300, 2), on_next(380, 3), on_completed(420)] assert xs.subscriptions == [subscribe(200, 420)] def test_distinct_key_mapper_all_distinct(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(280, 8), on_next(300, 4), on_next(350, 2), on_next(380, 6), on_next(400, 10), on_completed(420)) def create(): def key_mapper(x): return x / 2 return xs.pipe(ops.distinct(key_mapper)) results = scheduler.start(create) assert results.messages == [on_next(280, 8), on_next(300, 4), on_next(350, 2), on_next(380, 6), on_next(400, 10), on_completed(420)] assert xs.subscriptions == [subscribe(200, 420)] def test_distinct_key_mapper_some_duplicates(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(280, 4), on_next(300, 2), on_next(350, 3), on_next(380, 7), on_next(400, 5), on_completed(420)) def create(): def key_mapper(x): return math.floor(x / 2.0) return xs.pipe(ops.distinct(key_mapper)) results = scheduler.start(create) assert results.messages == [on_next(280, 4), on_next(300, 2), on_next(380, 7), on_completed(420)] assert xs.subscriptions == [subscribe(200, 420)] def test_distinct_key_mapper_throws(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(280, 3), on_next(300, 2), on_next(350, 1), on_next(380, 0), on_next(400, 4), on_completed(420)) def create(): def key_mapper(x): if not x: raise Exception(ex) else: return math.floor(x / 2.0) return xs.pipe(ops.distinct(key_mapper)) results = scheduler.start(create) assert results.messages == [on_next(280, 3), on_next(350, 1), on_error(380, ex)] assert xs.subscriptions == [subscribe(200, 380)] RxPY-3.2.0/tests/test_observable/test_distinctuntilchanged.py000066400000000000000000000177171404130727200245400ustar00rootroot00000000000000import unittest import rx from rx import operators as ops from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class RxException(Exception): pass # Helper function for raising exceptions within lambdas def _raise(ex): raise RxException(ex) class TestDistinctUntilChanged(unittest.TestCase): def test_distinct_until_changed_never(self): scheduler = TestScheduler() def create(): return rx.never().pipe(ops.distinct_until_changed()) results = scheduler.start(create) assert results.messages == [] def test_distinct_until_changed_empty(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_completed(250)) def create(): return xs.pipe(ops.distinct_until_changed()) results = scheduler.start(create).messages self.assertEqual(1, len(results)) assert(results[0].value.kind == 'C' and results[0].time == 250) def test_distinct_until_changed_return(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_next(220, 2), on_completed(250)) def create(): return xs.pipe(ops.distinct_until_changed()) results = scheduler.start(create).messages self.assertEqual(2, len(results)) assert(results[0].value.kind == 'N' and results[0].time == 220 and results[0].value.value == 2) assert(results[1].value.kind == 'C' and results[1].time == 250) def test_distinct_until_changed_on_error(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_error(250, ex)) def create(): return xs.pipe(ops.distinct_until_changed()) results = scheduler.start(create).messages self.assertEqual(1, len(results)) assert(results[0].value.kind == 'E' and results[0].time == 250 and results[0].value.exception == ex) def test_distinct_until_changed_all_changes(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(150, 1), on_next(210, 2), on_next(220, 3), on_next(230, 4), on_next(240, 5), on_completed(250)) def create(): return xs.pipe(ops.distinct_until_changed()) results = scheduler.start(create).messages self.assertEqual(5, len(results)) assert(results[0].value.kind == 'N' and results[0].time == 210 and results[0].value.value == 2) assert(results[1].value.kind == 'N' and results[1].time == 220 and results[1].value.value == 3) assert(results[2].value.kind == 'N' and results[2].time == 230 and results[2].value.value == 4) assert(results[3].value.kind == 'N' and results[3].time == 240 and results[3].value.value == 5) assert(results[4].value.kind == 'C' and results[4].time == 250) def test_distinct_until_changed_all_same(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_next(210, 2), on_next( 220, 2), on_next(230, 2), on_next(240, 2), on_completed(250)) def create(): return xs.pipe(ops.distinct_until_changed()) results = scheduler.start(create).messages self.assertEqual(2, len(results)) assert(results[0].value.kind == 'N' and results[0].time == 210 and results[0].value.value == 2) assert(results[1].value.kind == 'C' and results[1].time == 250) def test_distinct_until_changed_some_changes(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_next(210, 2), on_next(215, 3), on_next( 220, 3), on_next(225, 2), on_next(230, 2), on_next(230, 1), on_next(240, 2), on_completed(250)) def create(): return xs.pipe(ops.distinct_until_changed()) results = scheduler.start(create).messages self.assertEqual(6, len(results)) assert(results[0].value.kind == 'N' and results[0].time == 210 and results[0].value.value == 2) assert(results[1].value.kind == 'N' and results[1].time == 215 and results[1].value.value == 3) assert(results[2].value.kind == 'N' and results[2].time == 225 and results[2].value.value == 2) assert(results[3].value.kind == 'N' and results[3].time == 230 and results[3].value.value == 1) assert(results[4].value.kind == 'N' and results[4].time == 240 and results[4].value.value == 2) assert(results[5].value.kind == 'C' and results[5].time == 250) def test_distinct_until_changed_comparer_all_equal(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_next(210, 2), on_next( 220, 3), on_next(230, 4), on_next(240, 5), on_completed(250)) def create(): return xs.pipe( ops.distinct_until_changed(comparer=lambda x, y: True) ) results = scheduler.start(create).messages self.assertEqual(2, len(results)) assert(results[0].value.kind == 'N' and results[0].time == 210 and results[0].value.value == 2) assert(results[1].value.kind == 'C' and results[1].time == 250) def test_distinct_until_changed_comparer_all_different(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_next(210, 2), on_next( 220, 2), on_next(230, 2), on_next(240, 2), on_completed(250)) def create(): return xs.pipe( ops.distinct_until_changed(comparer=lambda x, y: False) ) results = scheduler.start(create).messages self.assertEqual(5, len(results)) assert(results[0].value.kind == 'N' and results[0].time == 210 and results[0].value.value == 2) assert(results[1].value.kind == 'N' and results[1].time == 220 and results[1].value.value == 2) assert(results[2].value.kind == 'N' and results[2].time == 230 and results[2].value.value == 2) assert(results[3].value.kind == 'N' and results[3].time == 240 and results[3].value.value == 2) assert(results[4].value.kind == 'C' and results[4].time == 250) def test_distinct_until_changed_key_mapper_div2(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_next(210, 2), on_next( 220, 4), on_next(230, 3), on_next(240, 5), on_completed(250)) def create(): return xs.pipe(ops.distinct_until_changed(lambda x: x % 2)) results = scheduler.start(create).messages self.assertEqual(3, len(results)) assert(results[0].value.kind == 'N' and results[0].time == 210 and results[0].value.value == 2) assert(results[1].value.kind == 'N' and results[1].time == 230 and results[1].value.value == 3) assert(results[2].value.kind == 'C' and results[2].time == 250) def test_distinct_until_changed_key_mapper_throws(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_next(210, 2), on_completed(250)) def create(): return xs.pipe(ops.distinct_until_changed(lambda x: _raise(ex))) results = scheduler.start(create) assert results.messages == [on_error(210, ex)] def test_distinct_until_changed_comparer_throws(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_next(210, 2), on_next(220, 3), on_completed(250)) def create(): return xs.pipe( ops.distinct_until_changed(comparer=lambda x, y: _raise(ex)), ) results = scheduler.start(create) assert results.messages == [on_next(210, 2), on_error(220, ex)] RxPY-3.2.0/tests/test_observable/test_doaction.py000066400000000000000000000300061404130727200221130ustar00rootroot00000000000000import unittest import rx from rx import operators as _ from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class TestDo(unittest.TestCase): def test_do_should_see_all_values(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_next(210, 2), on_next( 220, 3), on_next(230, 4), on_next(240, 5), on_completed(250)) i = [0] sum = [2 + 3 + 4 + 5] def create(): def action(x): i[0] += 1 sum[0] -= x return sum[0] return xs.pipe(_.do_action(action)) scheduler.start(create) self.assertEqual(4, i[0]) self.assertEqual(0, sum[0]) def test_do_plain_action(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_next(210, 2), on_next( 220, 3), on_next(230, 4), on_next(240, 5), on_completed(250)) i = [0] def create(): def action(x): i[0] += 1 return i[0] return xs.pipe(_.do_action(action)) scheduler.start(create) self.assertEqual(4, i[0]) def test_do_next_completed(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_next(210, 2), on_next( 220, 3), on_next(230, 4), on_next(240, 5), on_completed(250)) i = [0] sum = [2 + 3 + 4 + 5] completed = [False] def create(): def on_next(x): i[0] += 1 sum[0] -= x def on_completed(): completed[0] = True return xs.pipe(_.do_action(on_next=on_next, on_completed=on_completed)) scheduler.start(create) self.assertEqual(4, i[0]) self.assertEqual(0, sum[0]) assert(completed[0]) def test_do_next_completed_never(self): scheduler = TestScheduler() i = [0] completed = False def create(): nonlocal completed def on_next(x): i[0] += 1 def on_completed(): nonlocal completed completed = True return rx.never().pipe( _.do_action(on_next=on_next, on_completed=on_completed), ) scheduler.start(create) self.assertEqual(0, i[0]) assert(not completed) def test_do_action_without_next(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_next(210, 2), on_completed(250)) completed = [False] def create(): def on_completed(): completed[0] = True return xs.pipe(_.do_action(on_completed=on_completed)) scheduler.start(create) assert(completed[0]) # def test_do_next_error(self): # ex = 'ex' # scheduler = TestScheduler() # xs = scheduler.create_hot_observable(on_next(150, 1), on_next(210, 2), on_next(220, 3), on_next(230, 4), on_next(240, 5), on_error(250, ex)) # i = [0] # sum = [2 + 3 + 4 + 5] # saw_error = False # scheduler.start(create) # return xs.do_action(function (x) { # i[0] += 1 # sum -= x # }, function (e) { # saw_error = e == ex # self.assertEqual(4, i) # self.assertEqual(0, sum) # assert(saw_error) # def test_do_next_error_not(self): # scheduler = TestScheduler() # xs = scheduler.create_hot_observable(on_next(150, 1), on_next(210, 2), on_next(220, 3), on_next(230, 4), on_next(240, 5), on_completed(250)) # i = [0] # sum = [2 + 3 + 4 + 5] # saw_error = False # scheduler.start(create) # return xs.do_action(function (x) { # i[0] += 1 # sum -= x # }, function (e) { # saw_error = True # self.assertEqual(4, i) # self.assertEqual(0, sum) # assert(not saw_error) # def test_do_next_error_completed(self): # scheduler = TestScheduler() # xs = scheduler.create_hot_observable(on_next(150, 1), on_next(210, 2), on_next(220, 3), on_next(230, 4), on_next(240, 5), on_completed(250)) # i = [0] # sum = [2 + 3 + 4 + 5] # saw_error = False # has_completed = False # scheduler.start(create) # return xs.do_action(function (x) { # i[0] += 1 # sum -= x # }, function (e) { # saw_error = True # }, function () { # has_completed = True # self.assertEqual(4, i) # self.assertEqual(0, sum) # assert(not saw_error) # assert(has_completed) # def test_do_next_error_completed_error(self): # ex = 'ex' # scheduler = TestScheduler() # xs = scheduler.create_hot_observable(on_next(150, 1), on_next(210, 2), on_next(220, 3), on_next(230, 4), on_next(240, 5), on_error(250, ex)) # i = [0] # sum = [2 + 3 + 4 + 5] # saw_error = False # has_completed = False # scheduler.start(create) # return xs.do_action(function (x) { # i[0] += 1 # sum -= x # }, function (e) { # saw_error = ex == e # }, function () { # has_completed = True # self.assertEqual(4, i) # self.assertEqual(0, sum) # assert(saw_error) # assert(not has_completed) # def test_do_next_error_completed_never(self): # scheduler = TestScheduler() # i = [0] # saw_error = False # has_completed = False # scheduler.start(create) # return rx.never().do_action(function (x) { # i[0] += 1 # }, function (e) { # saw_error = True # }, function () { # has_completed = True # self.assertEqual(0, i) # assert(not saw_error) # assert(not has_completed) # def test_Do_Observer_SomeDataWithError(self): # ex = 'ex' # scheduler = TestScheduler() # xs = scheduler.create_hot_observable(on_next(150, 1), on_next(210, 2), on_next(220, 3), on_next(230, 4), on_next(240, 5), on_error(250, ex)) # i = [0] # sum = [2 + 3 + 4 + 5] # saw_error = False # has_completed = False # scheduler.start(create) # return xs.do_action(Observer.create(function (x) { # i[0] += 1 # sum -= x # }, function (e) { # saw_error = e == ex # }, function () { # has_completed = True # })) # self.assertEqual(4, i) # self.assertEqual(0, sum) # assert(saw_error) # assert(not has_completed) # def test_do_observer_some_data_with_error(self): # scheduler = TestScheduler() # xs = scheduler.create_hot_observable(on_next(150, 1), on_next(210, 2), on_next(220, 3), on_next(230, 4), on_next(240, 5), on_completed(250)) # i = [0] # sum = [2 + 3 + 4 + 5] # saw_error = False # has_completed = False # scheduler.start(create) # return xs.do_action(Observer.create(function (x) { # i[0] += 1 # sum -= x # }, function (e) { # saw_error = True # }, function () { # has_completed = True # })) # self.assertEqual(4, i) # self.assertEqual(0, sum) # assert(not saw_error) # assert(has_completed) # def test_do1422_next_next_throws(self): # ex = 'ex' # scheduler = TestScheduler() # xs = scheduler.create_hot_observable(on_next(150, 1), on_next(210, 2), on_completed(250)) # results = scheduler.start(create) # return xs.do_action(function () { # raise Exception(ex) # assert results.messages == [on_error(210, ex)] # def test_do1422_next_completed_next_throws(self): # ex = 'ex' # scheduler = TestScheduler() # xs = scheduler.create_hot_observable(on_next(150, 1), on_next(210, 2), on_completed(250)) # results = scheduler.start(create) # return xs.do_action(function () { # throw ex # }, _undefined, function () { # assert results.messages == [on_error(210, ex)] # def test_do1422_next_completed_completed_throws(self): # ex = 'ex' # scheduler = TestScheduler() # xs = scheduler.create_hot_observable(on_next(150, 1), on_next(210, 2), on_completed(250)) # results = scheduler.start(create) # return xs.do_action(function () { }, _undefined, function () { # throw ex # assert results.messages == [on_next(210, 2), on_error(250, ex)] # def test_do1422_next_error_next_throws(self): # ex = 'ex' # scheduler = TestScheduler() # xs = scheduler.create_hot_observable(on_next(150, 1), on_next(210, 2), on_completed(250)) # results = scheduler.start(create) # return xs.do_action(function () { # raise Exception(ex) # }, function () { # assert results.messages == [on_error(210, ex)] # def test_Do1422_NextError_NextThrows(self): # var ex1, ex2, results, scheduler, xs # ex1 = 'ex1' # ex2 = 'ex2' # scheduler = TestScheduler() # xs = scheduler.create_hot_observable(on_next(150, 1), on_error(210, ex1)) # results = scheduler.start(create) # return xs.do_action(function () { }, function () { # raise Exception(ex)2 # assert results.messages == [on_error(210, ex2)] # def test_Do1422_NextErrorCompleted_NextThrows(self): # var ex, results, scheduler, xs # ex = 'ex' # scheduler = TestScheduler() # xs = scheduler.create_hot_observable(on_next(150, 1), on_next(210, 2), on_completed(250)) # results = scheduler.start(create) # return xs.do_action(function () { # raise Exception(ex) # }, function () { }, function () { # assert results.messages == [on_error(210, ex)] # def test_do1422_next_error_completed_error_throws(self): # var ex1, ex2, results, scheduler, xs # ex1 = 'ex1' # ex2 = 'ex2' # scheduler = TestScheduler() # xs = scheduler.create_hot_observable(on_next(150, 1), on_error(210, ex1)) # results = scheduler.start(create) # return xs.do_action(function () { }, function () { # raise Exception(ex)2 # }, function () { # assert results.messages == [on_error(210, ex2)] # def test_do1422_next_error_completed_completed_throws(self): # ex = 'ex' # scheduler = TestScheduler() # xs = scheduler.create_hot_observable(on_next(150, 1), on_next(210, 2), on_completed(250)) # results = scheduler.start(create) # return xs.do_action(function () { }, function () { }, function () { # raise Exception(ex) # assert results.messages == [on_next(210, 2), on_error(250, ex)] # def test_do1422_observer_next_throws(self): # ex = 'ex' # scheduler = TestScheduler() # xs = scheduler.create_hot_observable(on_next(150, 1), on_next(210, 2), on_completed(250)) # results = scheduler.start(create) # return xs.do_action(Observer.create(function () { # raise Exception(ex) # }, function () { }, function () { })) # assert results.messages == [on_error(210, ex)] # def test_do1422_observer_error_throws(self): # var ex1, ex2, results, scheduler, xs # ex1 = 'ex1' # ex2 = 'ex2' # scheduler = TestScheduler() # xs = scheduler.create_hot_observable(on_next(150, 1), on_error(210, ex1)) # results = scheduler.start(create) # return xs.do_action(Observer.create(function () { }, function () { # raise Exception(ex)2 # }, function () { })) # assert results.messages == [on_error(210, ex2)] # def test_do1422_observer_completed_throws(self): # var ex, results, scheduler, xs # ex = 'ex' # scheduler = TestScheduler() # xs = scheduler.create_hot_observable(on_next(150, 1), on_next(210, 2), on_completed(250)) # results = scheduler.start(create) # return xs.do_action(Observer.create(function () { }, function () { }, function () { # raise Exception(ex) # })) # assert results.messages == [on_next(210, 2), on_error(250, ex)] RxPY-3.2.0/tests/test_observable/test_dowhile.py000066400000000000000000000120731404130727200217520ustar00rootroot00000000000000import unittest from rx import operators as ops from rx.testing import TestScheduler, ReactiveTest class TestDoWhile(ReactiveTest, unittest.TestCase): def test_dowhile_always_false(self): scheduler = TestScheduler() xs = scheduler.create_cold_observable( self.on_next(50, 1), self.on_next(100, 2), self.on_next(150, 3), self.on_next(200, 4), self.on_completed(250)) def create(): return xs.pipe(ops.do_while(lambda _: False)) results = scheduler.start(create=create) assert results.messages == [ self.on_next(250, 1), self.on_next(300, 2), self.on_next(350, 3), self.on_next(400, 4), self.on_completed(450)] assert xs.subscriptions == [self.subscribe(200, 450)] def test_dowhile_always_true(self): scheduler = TestScheduler() xs = scheduler.create_cold_observable( self.on_next(50, 1), self.on_next(100, 2), self.on_next(150, 3), self.on_next(200, 4), self.on_completed(250)) def create(): return xs.pipe(ops.do_while(lambda _: True)) results = scheduler.start(create=create) assert results.messages == [ self.on_next(250, 1), self.on_next(300, 2), self.on_next(350, 3), self.on_next(400, 4), self.on_next(500, 1), self.on_next(550, 2), self.on_next(600, 3), self.on_next(650, 4), self.on_next(750, 1), self.on_next(800, 2), self.on_next(850, 3), self.on_next(900, 4)] assert xs.subscriptions == [ self.subscribe(200, 450), self.subscribe(450, 700), self.subscribe(700, 950), self.subscribe(950, 1000)] def test_dowhile_always_true_on_error(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_cold_observable(self.on_error(50, ex)) def create(): return xs.pipe(ops.do_while(lambda _: True)) results = scheduler.start(create=create) assert results.messages == [self.on_error(250, ex)] assert xs.subscriptions == [self.subscribe(200, 250)] def test_dowhile_always_true_infinite(self): scheduler = TestScheduler() xs = scheduler.create_cold_observable(self.on_next(50, 1)) def create(): return xs.pipe(ops.do_while(lambda _: True)) results = scheduler.start(create=create) assert results.messages == [ self.on_next(250, 1)] assert xs.subscriptions == [self.subscribe(200, 1000)] def test_dowhile_sometimes_true(self): scheduler = TestScheduler() xs = scheduler.create_cold_observable( self.on_next(50, 1), self.on_next(100, 2), self.on_next(150, 3), self.on_next(200, 4), self.on_completed(250)) n = [0] def create(): def condition(x): n[0] += 1 return n[0]<3 return xs.pipe(ops.do_while(condition)) results = scheduler.start(create=create) assert results.messages == [ self.on_next(250, 1), self.on_next(300, 2), self.on_next(350, 3), self.on_next(400, 4), self.on_next(500, 1), self.on_next(550, 2), self.on_next(600, 3), self.on_next(650, 4), self.on_next(750, 1), self.on_next(800, 2), self.on_next(850, 3), self.on_next(900, 4), self.on_completed(950)] assert xs.subscriptions == [ self.subscribe(200, 450), self.subscribe(450, 700), self.subscribe(700, 950)] def test_dowhile_sometimes_throws(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_cold_observable( self.on_next(50, 1), self.on_next(100, 2), self.on_next(150, 3), self.on_next(200, 4), self.on_completed(250)) n = [0] def create(): def condition(x): n[0] += 1 if n[0]<3: return True else: raise Exception(ex) return xs.pipe(ops.do_while(condition)) results = scheduler.start(create=create) assert results.messages == [ self.on_next(250, 1), self.on_next(300, 2), self.on_next(350, 3), self.on_next(400, 4), self.on_next(500, 1), self.on_next(550, 2), self.on_next(600, 3), self.on_next(650, 4), self.on_next(750, 1), self.on_next(800, 2), self.on_next(850, 3), self.on_next(900, 4), self.on_error(950, ex)] assert xs.subscriptions == [ self.subscribe(200, 450), self.subscribe(450, 700), self.subscribe(700, 950)] RxPY-3.2.0/tests/test_observable/test_elementat.py000066400000000000000000000100361404130727200222720ustar00rootroot00000000000000import unittest import rx from rx import operators as ops from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class TestElementAt(unittest.TestCase): def test_elementat_first(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(280, 42), on_next(360, 43), on_next(470, 44), on_completed(600)) def create(): return xs.pipe(ops.element_at(0)) results = scheduler.start(create=create) assert results.messages == [on_next(280, 42), on_completed(280)] assert xs.subscriptions == [subscribe(200, 280)] def test_elementat_other(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(280, 42), on_next(360, 43), on_next(470, 44), on_completed(600)) def create(): return xs.pipe(ops.element_at(2)) results = scheduler.start(create=create) assert results.messages == [on_next(470, 44), on_completed(470)] assert xs.subscriptions == [subscribe(200, 470)] def test_elementat_outofrange(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(280, 42), on_next(360, 43), on_next(470, 44), on_completed(600)) def create(): return xs.pipe(ops.element_at(3)) results = scheduler.start(create=create) self.assertEqual(1, len(results.messages)) self.assertEqual(600, results.messages[0].time) self.assertEqual('E', results.messages[0].value.kind) assert(results.messages[0].value.exception) def test_elementat_error(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(280, 42), on_next(360, 43), on_error(420, ex)) def create(): return xs.pipe(ops.element_at(3)) results = scheduler.start(create=create) assert results.messages == [on_error(420, ex)] assert xs.subscriptions == [subscribe(200, 420)] def test_element_at_or_default_first(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(280, 42), on_next(360, 43), on_next(470, 44), on_completed(600)) def create(): return xs.pipe(ops.element_at_or_default(0)) results = scheduler.start(create=create) assert results.messages == [on_next(280, 42), on_completed(280)] assert xs.subscriptions == [subscribe(200, 280)] def test_element_at_or_default_other(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(280, 42), on_next(360, 43), on_next(470, 44), on_completed(600)) def create(): return xs.pipe(ops.element_at_or_default(2)) results = scheduler.start(create=create) assert results.messages == [on_next(470, 44), on_completed(470)] assert xs.subscriptions == [subscribe(200, 470)] def test_element_at_or_default_outofrange(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(280, 42), on_next(360, 43), on_next(470, 44), on_completed(600)) def create(): return xs.pipe(ops.element_at_or_default(3, 0)) results = scheduler.start(create=create) assert results.messages == [on_next(600, 0), on_completed(600)] assert xs.subscriptions == [subscribe(200, 600)] def test_element_at_or_default_error(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(280, 42), on_next(360, 43), on_error(420, ex)) def create(): return xs.pipe(ops.element_at_or_default(3)) results = scheduler.start(create=create) assert results.messages == [on_error(420, ex)] assert xs.subscriptions == [subscribe(200, 420)] if __name__ == '__main__': unittest.main() RxPY-3.2.0/tests/test_observable/test_empty.py000066400000000000000000000023151404130727200214530ustar00rootroot00000000000000import unittest from rx import empty from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class RxException(Exception): pass # Helper function for raising exceptions within lambdas def _raise(ex): raise RxException(ex) class TestEmpty(unittest.TestCase): def test_empty_basic(self): scheduler = TestScheduler() def factory(): return empty() results = scheduler.start(factory) assert results.messages == [on_completed(200)] def test_empty_disposed(self): scheduler = TestScheduler() def factory(): return empty() results = scheduler.start(factory, disposed=200) assert results.messages == [] def test_empty_observer_throw_exception(self): scheduler = TestScheduler() xs = empty() xs.subscribe(lambda x: None, lambda ex: None, lambda: _raise('ex'), scheduler=scheduler) with self.assertRaises(RxException): scheduler.start() RxPY-3.2.0/tests/test_observable/test_expand.py000066400000000000000000000063271404130727200216030ustar00rootroot00000000000000import unittest from rx import operators as ops from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class RxException(Exception): pass # Helper function for raising exceptions within lambdas def _raise(ex): raise RxException(ex) class TestExpand(unittest.TestCase): def test_expand_empty(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_completed(300)) def create(): def mapper(): return scheduler.create_cold_observable(on_next(100, 1), on_next(200, 2), on_completed(300)) return xs.pipe(ops.expand(mapper)) results = scheduler.start(create) assert results.messages == [on_completed(300)] assert xs.subscriptions == [subscribe(200, 300)] def test_expand_error(self): scheduler = TestScheduler() ex = 'ex' xs = scheduler.create_hot_observable(on_error(300, ex)) def create(): def mapper(x): return scheduler.create_cold_observable(on_next(100 + x, 2 * x), on_next(200 + x, 3 * x), on_completed(300 + x)) return xs.pipe(ops.expand(mapper)) results = scheduler.start(create) assert results.messages == [on_error(300, ex)] assert xs.subscriptions == [subscribe(200, 300)] def test_expand_never(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable() def create(): def mapper(x): return scheduler.create_cold_observable(on_next(100 + x, 2 * x), on_next(200 + x, 3 * x), on_completed(300 + x)) return xs.pipe(ops.expand(mapper)) results = scheduler.start(create) assert results.messages == [] assert xs.subscriptions == [subscribe(200, 1000)] def test_expand_basic(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(550, 1), on_next(850, 2), on_completed(950)) def create(): def mapper(x): return scheduler.create_cold_observable(on_next(100, 2 * x), on_next(200, 3 * x), on_completed(300)) return xs.pipe(ops.expand(mapper)) results = scheduler.start(create) assert results.messages == [on_next(550, 1), on_next(650, 2), on_next(750, 3), on_next(750, 4), on_next(850, 2), on_next(850, 6), on_next(850, 6), on_next(850, 8), on_next(950, 9), on_next(950, 12), on_next(950, 4), on_next(950, 12), on_next(950, 12), on_next(950, 16)] assert xs.subscriptions == [subscribe(200, 950)] def test_expand_on_error(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(550, 1), on_next(850, 2), on_completed(950)) def create(): def mapper(x): raise Exception(ex) return xs.pipe(ops.expand(mapper)) results = scheduler.start(create) assert results.messages == [on_next(550, 1), on_error(550, ex)] assert xs.subscriptions == [subscribe(200, 550)] RxPY-3.2.0/tests/test_observable/test_filter.py000066400000000000000000000333231404130727200216050ustar00rootroot00000000000000import unittest from rx.testing import TestScheduler, ReactiveTest, is_prime from rx.disposable import SerialDisposable from rx.operators import filter, filter_indexed on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class RxException(Exception): pass # Helper function for raising exceptions within lambdas def _raise(ex): raise RxException(ex) def test_is_prime(): assert not is_prime(1) assert is_prime(2) assert is_prime(3) assert not is_prime(4) assert is_prime(5) assert not is_prime(6) class TestFilter(unittest.TestCase): def test_filter_complete(self): scheduler = TestScheduler() invoked = [0] xs = scheduler.create_hot_observable( on_next(110, 1), on_next(180, 2), on_next(230, 3), on_next(270, 4), on_next(340, 5), on_next(380, 6), on_next(390, 7), on_next(450, 8), on_next(470, 9), on_next(560, 10), on_next(580, 11), on_completed(600), on_next(610, 12), on_error(620, 'ex'), on_completed(630)) def create(): def predicate(x): invoked[0] += 1 return is_prime(x) return xs.pipe(filter(predicate)) results = scheduler.start(create) assert results.messages == [on_next(230, 3), on_next(340, 5), on_next(390, 7), on_next(580, 11), on_completed(600)] assert xs.subscriptions == [subscribe(200, 600)] assert invoked[0] == 9 def test_filter_true(self): scheduler = TestScheduler() invoked = [0] xs = scheduler.create_hot_observable( on_next(110, 1), on_next(180, 2), on_next(230, 3), on_next(270, 4), on_next(340, 5), on_next(380, 6), on_next(390, 7), on_next(450, 8), on_next(470, 9), on_next(560, 10), on_next(580, 11), on_completed(600)) def create(): def predicate(x): invoked[0] += 1 return True return xs.pipe(filter(predicate)) results = scheduler.start(create) assert results.messages == [on_next(230, 3), on_next(270, 4), on_next(340, 5), on_next(380, 6), on_next( 390, 7), on_next(450, 8), on_next(470, 9), on_next(560, 10), on_next(580, 11), on_completed(600)] assert xs.subscriptions == [subscribe(200, 600)] assert invoked[0] == 9 def test_filter_false(self): scheduler = TestScheduler() invoked = [0] xs = scheduler.create_hot_observable(on_next(110, 1), on_next(180, 2), on_next(230, 3), on_next(270, 4), on_next(340, 5), on_next( 380, 6), on_next(390, 7), on_next(450, 8), on_next(470, 9), on_next(560, 10), on_next(580, 11), on_completed(600)) def create(): def predicate(x): invoked[0] += 1 return False return xs.pipe(filter(predicate)) results = scheduler.start(create) assert results.messages == [on_completed(600)] assert xs.subscriptions == [subscribe(200, 600)] assert invoked[0] == 9 def test_filter_dispose(self): scheduler = TestScheduler() invoked = [0] xs = scheduler.create_hot_observable(on_next(110, 1), on_next(180, 2), on_next(230, 3), on_next(270, 4), on_next(340, 5), on_next( 380, 6), on_next(390, 7), on_next(450, 8), on_next(470, 9), on_next(560, 10), on_next(580, 11), on_completed(600)) def create(): def predicate(x): invoked[0] += 1 return is_prime(x) return xs.pipe(filter(predicate)) results = scheduler.start(create, disposed=400) assert results.messages == [ on_next(230, 3), on_next(340, 5), on_next(390, 7)] assert xs.subscriptions == [subscribe(200, 400)] assert invoked[0] == 5 def test_filter_error(self): scheduler = TestScheduler() invoked = [0] ex = 'ex' xs = scheduler.create_hot_observable( on_next(110, 1), on_next(180, 2), on_next(230, 3), on_next(270, 4), on_next(340, 5), on_next(380, 6), on_next(390, 7), on_next(450, 8), on_next(470, 9), on_next(560, 10), on_next(580, 11), on_error(600, ex), on_next(610, 12), on_error(620, 'ex'), on_completed(630)) def create(): def predicate(x): invoked[0] += 1 return is_prime(x) return xs.pipe(filter(predicate)) results = scheduler.start(create) assert results.messages == [on_next(230, 3), on_next( 340, 5), on_next(390, 7), on_next(580, 11), on_error(600, ex)] assert xs.subscriptions == [subscribe(200, 600)] assert(invoked[0] == 9) def test_filter_on_error(self): scheduler = TestScheduler() invoked = [0] ex = 'ex' xs = scheduler.create_hot_observable(on_next(110, 1), on_next(180, 2), on_next(230, 3), on_next(270, 4), on_next(340, 5), on_next(380, 6), on_next( 390, 7), on_next(450, 8), on_next(470, 9), on_next(560, 10), on_next(580, 11), on_completed(600), on_next(610, 12), on_error(620, 'ex'), on_completed(630)) def create(): def predicate(x): invoked[0] += 1 if x > 5: raise Exception(ex) return is_prime(x) return xs.pipe(filter(predicate)) results = scheduler.start(create) assert results.messages == [ on_next(230, 3), on_next(340, 5), on_error(380, ex)] assert xs.subscriptions == [subscribe(200, 380)] assert(invoked[0] == 4) def test_filter_dispose_in_predicate(self): scheduler = TestScheduler() invoked = [0] ys = [None] xs = scheduler.create_hot_observable(on_next(110, 1), on_next(180, 2), on_next(230, 3), on_next(270, 4), on_next(340, 5), on_next(380, 6), on_next( 390, 7), on_next(450, 8), on_next(470, 9), on_next(560, 10), on_next(580, 11), on_completed(600), on_next(610, 12), on_error(620, 'ex'), on_completed(630)) results = scheduler.create_observer() d = SerialDisposable() def action(scheduler, state): def predicate(x): invoked[0] += 1 if x == 8: d.dispose() return is_prime(x) ys[0] = xs.pipe(filter(predicate)) return ys[0] scheduler.schedule_absolute(created, action) def action1(scheduler, state): d.disposable = ys[0].subscribe(results) scheduler.schedule_absolute(subscribed, action1) def action2(scheduler, state): d.dispose() scheduler.schedule_absolute(disposed, action2) scheduler.start() assert results.messages == [ on_next(230, 3), on_next(340, 5), on_next(390, 7)] assert xs.subscriptions == [subscribe(200, 450)] assert(invoked[0] == 6) def test_filter_indexed_complete(self): scheduler = TestScheduler() invoked = [0] xs = scheduler.create_hot_observable(on_next(110, 1), on_next(180, 2), on_next(230, 3), on_next(270, 4), on_next(340, 5), on_next(380, 6), on_next( 390, 7), on_next(450, 8), on_next(470, 9), on_next(560, 10), on_next(580, 11), on_completed(600), on_next(610, 12), on_error(620, 'ex'), on_completed(630)) def create(): def predicate(x, index): invoked[0] += 1 return is_prime(x + index * 10) return xs.pipe(filter_indexed(predicate)) results = scheduler.start(create) assert results.messages == [ on_next(230, 3), on_next(390, 7), on_completed(600)] assert xs.subscriptions == [subscribe(200, 600)] assert(invoked[0] == 9) def test_filter_indexed_true(self): scheduler = TestScheduler() invoked = [0] xs = scheduler.create_hot_observable(on_next(110, 1), on_next(180, 2), on_next(230, 3), on_next(270, 4), on_next(340, 5), on_next( 380, 6), on_next(390, 7), on_next(450, 8), on_next(470, 9), on_next(560, 10), on_next(580, 11), on_completed(600)) def create(): def predicate(x, index): invoked[0] += 1 return True return xs.pipe(filter_indexed(predicate)) results = scheduler.start(create) assert results.messages == [on_next(230, 3), on_next(270, 4), on_next(340, 5), on_next(380, 6), on_next( 390, 7), on_next(450, 8), on_next(470, 9), on_next(560, 10), on_next(580, 11), on_completed(600)] assert xs.subscriptions == [subscribe(200, 600)] assert(invoked[0] == 9) def test_filter_indexed_false(self): scheduler = TestScheduler() invoked = [0] xs = scheduler.create_hot_observable(on_next(110, 1), on_next(180, 2), on_next(230, 3), on_next(270, 4), on_next(340, 5), on_next( 380, 6), on_next(390, 7), on_next(450, 8), on_next(470, 9), on_next(560, 10), on_next(580, 11), on_completed(600)) def create(): def predicate(x, index): invoked[0] += 1 return False return xs.pipe(filter_indexed(predicate)) results = scheduler.start(create) assert results.messages == [on_completed(600)] assert xs.subscriptions == [subscribe(200, 600)] assert(invoked[0] == 9) def test_filter_indexed_dispose(self): scheduler = TestScheduler() invoked = [0] xs = scheduler.create_hot_observable(on_next(110, 1), on_next(180, 2), on_next(230, 3), on_next(270, 4), on_next(340, 5), on_next( 380, 6), on_next(390, 7), on_next(450, 8), on_next(470, 9), on_next(560, 10), on_next(580, 11), on_completed(600)) def create(): def predicate(x, index): invoked[0] += 1 return is_prime(x + index * 10) return xs.pipe(filter_indexed(predicate)) results = scheduler.start(create, disposed=400) assert results.messages == [on_next(230, 3), on_next(390, 7)] assert xs.subscriptions == [subscribe(200, 400)] assert(invoked[0] == 5) def test_filter_indexed_error(self): scheduler = TestScheduler() invoked = [0] ex = 'ex' xs = scheduler.create_hot_observable(on_next(110, 1), on_next(180, 2), on_next(230, 3), on_next(270, 4), on_next(340, 5), on_next(380, 6), on_next( 390, 7), on_next(450, 8), on_next(470, 9), on_next(560, 10), on_next(580, 11), on_error(600, ex), on_next(610, 12), on_error(620, 'ex'), on_completed(630)) def create(): def predicate(x, index): invoked[0] += 1 return is_prime(x + index * 10) return xs.pipe(filter_indexed(predicate)) results = scheduler.start(create) assert results.messages == [ on_next(230, 3), on_next(390, 7), on_error(600, ex)] assert xs.subscriptions == [subscribe(200, 600)] assert(invoked[0] == 9) def test_filter_indexed_on_error(self): scheduler = TestScheduler() invoked = [0] ex = 'ex' xs = scheduler.create_hot_observable(on_next(110, 1), on_next(180, 2), on_next(230, 3), on_next(270, 4), on_next(340, 5), on_next(380, 6), on_next( 390, 7), on_next(450, 8), on_next(470, 9), on_next(560, 10), on_next(580, 11), on_completed(600), on_next(610, 12), on_error(620, 'ex'), on_completed(630)) def create(): def predicate(x, index): invoked[0] += 1 if x > 5: raise Exception(ex) return is_prime(x + index * 10) return xs.pipe(filter_indexed(predicate)) results = scheduler.start(create) assert results.messages == [on_next(230, 3), on_error(380, ex)] assert xs.subscriptions == [subscribe(200, 380)] assert(invoked[0] == 4) def test_filter_indexed_dispose_in_predicate(self): scheduler = TestScheduler() ys = [None] invoked = [0] xs = scheduler.create_hot_observable(on_next(110, 1), on_next(180, 2), on_next(230, 3), on_next(270, 4), on_next(340, 5), on_next(380, 6), on_next( 390, 7), on_next(450, 8), on_next(470, 9), on_next(560, 10), on_next(580, 11), on_completed(600), on_next(610, 12), on_error(620, 'ex'), on_completed(630)) results = scheduler.create_observer() d = SerialDisposable() def action1(scheduler, state): def predicate(x, index): invoked[0] += 1 if x == 8: d.dispose() return is_prime(x + index * 10) ys[0] = xs.pipe(filter_indexed(predicate)) scheduler.schedule_absolute(created, action1) def action2(scheduler, state): d.disposable = ys[0].subscribe(results) scheduler.schedule_absolute(subscribed, action2) def action3(scheduler, state): d.dispose() scheduler.schedule_absolute(disposed, action3) scheduler.start() assert results.messages == [on_next(230, 3), on_next(390, 7)] assert xs.subscriptions == [subscribe(200, 450)] assert invoked[0] == 6 RxPY-3.2.0/tests/test_observable/test_finally.py000066400000000000000000000051051404130727200217530ustar00rootroot00000000000000import unittest import rx from rx import operators as ops from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class TestFinally(unittest.TestCase): def test_finally_only_called_once_empty(self): invasserte_count = [0] def action(): invasserte_count[0] += 1 return invasserte_count some_observable = rx.empty().pipe(ops.finally_action(action)) d = some_observable.subscribe() d.dispose() d.dispose() self.assertEqual(1, invasserte_count[0]) def test_finally_empty(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_completed(250)) invasserted = [False] def create(): def action(): invasserted[0] = True return invasserted[0] return xs.pipe(ops.finally_action(action)) results = scheduler.start(create).messages self.assertEqual(1, len(results)) assert(results[0].value.kind == 'C' and results[0].time == 250) assert(invasserted[0]) def test_finally_return(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_next(210, 2), on_completed(250)) invasserted = [False] def create(): def action(): invasserted[0] = True return invasserted[0] return xs.pipe(ops.finally_action(action)) results = scheduler.start(create).messages self.assertEqual(2, len(results)) assert(results[0].value.kind == 'N' and results[0].time == 210 and results[0].value.value == 2) assert(results[1].value.kind == 'C' and results[1].time == 250) assert(invasserted[0]) def test_finally_on_error(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_error(250, ex)) invasserted = [False] def create(): def action(): invasserted[0] = True return invasserted[0] return xs.pipe(ops.finally_action(action)) results = scheduler.start(create).messages self.assertEqual(1, len(results)) assert(results[0].value.kind == 'E' and results[0].time == 250 and results[0].value.exception == ex) assert(invasserted[0]) RxPY-3.2.0/tests/test_observable/test_find.py000066400000000000000000000054631404130727200212440ustar00rootroot00000000000000import unittest from rx import operators as ops from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class TestFind(unittest.TestCase): def test_find_never(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(150, 1) ) def create(): return xs.pipe(ops.find(lambda x, i, s: True)) res = scheduler.start(create) assert res.messages == [] def test_find_empty(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(150, 1), on_completed(210) ) def create(): return xs.pipe(ops.find(lambda x, i, s: True)) res = scheduler.start(create) assert res.messages == [ on_next(210, None), on_completed(210)] def test_find_single(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(150, 1), on_next(210, 2), on_completed(220) ) def create(): return xs.pipe(ops.find(lambda x, i, s: x == 2)) res = scheduler.start(create) assert res.messages == [ on_next(210, 2), on_completed(210)] def test_find_notfound(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(150, 1), on_next(210, 2), on_completed(220) ) def create(): return xs.pipe(ops.find(lambda x, i, s: x == 3)) res = scheduler.start(create) assert res.messages == [ on_next(220, None), on_completed(220)] def test_find_Error(self): ex = Exception('error') scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(150, 1), on_next(210, 2), on_error(220, ex) ) def create(): return xs.pipe(ops.find(lambda x, i, s: x == 3)) res = scheduler.start(create) assert res.messages == [ on_error(220, ex)] def test_find_throws(self): ex = 'error' scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(150, 1), on_next(210, 2), on_completed(220) ) def create(): def predicate(x, i, source): raise Exception(ex) return xs.pipe(ops.find(predicate)) res = scheduler.start(create) assert res.messages == [ on_error(210, ex)] RxPY-3.2.0/tests/test_observable/test_first.py000066400000000000000000000075271404130727200214560ustar00rootroot00000000000000import unittest import rx from rx import operators as ops from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class TestFirst(unittest.TestCase): def test_first_async_empty(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_completed(250)) def create(): return xs.pipe(ops.first()) res = scheduler.start(create=create) assert [on_error(250, lambda e: e)] == res.messages assert xs.subscriptions == [subscribe(200, 250)] def test_first_async_one(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_next(210, 2), on_completed(250)) res = scheduler.start(lambda: xs.pipe(ops.first())) assert res.messages == [on_next(210, 2), on_completed(210)] assert xs.subscriptions == [subscribe(200, 210)] def test_first_async_many(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_next(210, 2), on_next(220, 3), on_completed(250)) res = scheduler.start(lambda: xs.pipe(ops.first())) assert res.messages == [on_next(210, 2), on_completed(210)] assert xs.subscriptions == [subscribe(200, 210)] def test_first_async_error(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_error(210, ex)) res = scheduler.start(lambda: xs.pipe(ops.first())) assert res.messages == [on_error(210, ex)] assert xs.subscriptions == [subscribe(200, 210)] def test_first_async_predicate(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_next(210, 2), on_next(220, 3), on_next(230, 4), on_next(240, 5), on_completed(250)) def create(): return xs.pipe(ops.first(lambda x: x % 2 == 1)) res = scheduler.start(create=create) assert res.messages == [on_next(220, 3), on_completed(220)] assert xs.subscriptions == [subscribe(200, 220)] def test_first_async_predicate_none(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_next(210, 2), on_next(220, 3), on_next(230, 4), on_next(240, 5), on_completed(250)) def create(): return xs.pipe(ops.first(lambda x: x > 10)) res = scheduler.start(create=create) assert [on_error(250, lambda e: e)] == res.messages assert xs.subscriptions == [subscribe(200, 250)] def test_first_async_predicate_on_error(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_next(210, 2), on_error(220, ex)) def create(): return xs.pipe(ops.first(lambda x: x % 2 == 1)) res = scheduler.start(create=create) assert res.messages == [on_error(220, ex)] assert xs.subscriptions == [subscribe(200, 220)] def test_first_async_predicate_throws(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_next(210, 2), on_next(220, 3), on_next(230, 4), on_next(240, 5), on_completed(250)) def create(): def predicate(x): if x < 4: return False else: raise Exception(ex) return xs.pipe(ops.first(predicate)) res = scheduler.start(create=create) assert res.messages == [on_error(230, ex)] assert xs.subscriptions == [subscribe(200, 230)] if __name__ == '__main__': unittest.main() RxPY-3.2.0/tests/test_observable/test_firstordefault.py000066400000000000000000000107371404130727200233610ustar00rootroot00000000000000import unittest import rx from rx import operators as ops from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class RxException(Exception): pass # Helper function for raising exceptions within lambdas def _raise(ex): raise RxException(ex) class TestFirst_or_default(unittest.TestCase): def test_first_or_default_async_empty(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_completed(250)) def create(): return xs.pipe(ops.first_or_default(None, 0)) res = scheduler.start(create=create) assert res.messages == [on_next(250, 0), on_completed(250)] assert xs.subscriptions == [subscribe(200, 250)] def test_first_or_default_async_one(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_next(210, 2), on_completed(250)) def create(): return xs.pipe(ops.first_or_default(None, 0)) res = scheduler.start(create=create) assert res.messages == [on_next(210, 2), on_completed(210)] assert xs.subscriptions == [subscribe(200, 210)] def test_first_or_default_async_many(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_next(210, 2), on_next(220, 3), on_completed(250)) def create(): return xs.pipe(ops.first_or_default(None, 0)) res = scheduler.start(create=create) assert res.messages == [on_next(210, 2), on_completed(210)] assert xs.subscriptions == [subscribe(200, 210)] def test_first_or_default_async_error(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_error(210, ex)) def create(): return xs.pipe(ops.first_or_default(None, 0)) res = scheduler.start(create=create) assert res.messages == [on_error(210, ex)] assert xs.subscriptions == [subscribe(200, 210)] def test_first_or_default_async_predicate(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_next(210, 2), on_next(220, 3), on_next(230, 4), on_next(240, 5), on_completed(250)) def create(): def predicate(x): return x % 2 == 1 return xs.pipe(ops.first_or_default(predicate, 0)) res = scheduler.start(create=create) assert res.messages == [on_next(220, 3), on_completed(220)] assert xs.subscriptions == [subscribe(200, 220)] def test_first_or_default_async_predicate_none(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_next(210, 2), on_next(220, 3), on_next(230, 4), on_next(240, 5), on_completed(250)) def create(): def predicate(x): return x > 10 return xs.pipe(ops.first_or_default(predicate, 0)) res = scheduler.start(create=create) assert res.messages == [on_next(250, 0), on_completed(250)] assert xs.subscriptions == [subscribe(200, 250)] def test_first_or_default_async_predicate_on_error(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_next(210, 2), on_error(220, ex)) def create(): def predicate(x): return x % 2 == 1 return xs.pipe(ops.first_or_default(predicate, 0)) res = scheduler.start(create=create) assert res.messages == [on_error(220, ex)] assert xs.subscriptions == [subscribe(200, 220)] def test_first_or_default_async_predicate_throws(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_next(210, 2), on_next(220, 3), on_next(230, 4), on_next(240, 5), on_completed(250)) def create(): def predicate(x): if x < 4: return False else: raise Exception(ex) return xs.pipe(ops.first_or_default(predicate, 0)) res = scheduler.start(create=create) assert res.messages == [on_error(230, ex)] assert xs.subscriptions == [subscribe(200, 230)] RxPY-3.2.0/tests/test_observable/test_flatmap.py000066400000000000000000000636721404130727200217560ustar00rootroot00000000000000import unittest import rx from rx import operators as ops from rx.testing import TestScheduler, ReactiveTest, is_prime on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class TestFlatMap(unittest.TestCase): def test_flat_map_then_complete_complete(self): scheduler = TestScheduler() xs = scheduler.create_cold_observable( on_next(100, 4), on_next(200, 2), on_next(300, 3), on_next(400, 1), on_completed(500)) ys = scheduler.create_cold_observable( on_next(50, "foo"), on_next(100, "bar"), on_next(150, "baz"), on_next(200, "qux"), on_completed(250)) def factory(): return xs.pipe(ops.flat_map(ys)) results = scheduler.start(factory) assert results.messages == [ on_next(350, "foo"), on_next(400, "bar"), on_next(450, "baz"), on_next(450, "foo"), on_next(500, "qux"), on_next(500, "bar"), on_next(550, "baz"), on_next(550, "foo"), on_next(600, "qux"), on_next(600, "bar"), on_next(650, "baz"), on_next(650, "foo"), on_next(700, "qux"), on_next(700, "bar"), on_next(750, "baz"), on_next(800, "qux"), on_completed(850)] assert xs.subscriptions == [subscribe(200, 700)] assert ys.subscriptions == [ subscribe(300, 550), subscribe(400, 650), subscribe(500, 750), subscribe(600, 850)] def test_flat_map_then_complete_complete_2(self): scheduler = TestScheduler() xs = scheduler.create_cold_observable( on_next(100, 4), on_next(200, 2), on_next(300, 3), on_next(400, 1), on_completed(700)) ys = scheduler.create_cold_observable( on_next(50, "foo"), on_next(100, "bar"), on_next(150, "baz"), on_next(200, "qux"), on_completed(250)) def factory(): return xs.pipe(ops.flat_map(ys)) results = scheduler.start(factory) assert results.messages == [ on_next(350, "foo"), on_next(400, "bar"), on_next(450, "baz"), on_next(450, "foo"), on_next(500, "qux"), on_next(500, "bar"), on_next(550, "baz"), on_next(550, "foo"), on_next(600, "qux"), on_next(600, "bar"), on_next(650, "baz"), on_next(650, "foo"), on_next(700, "qux"), on_next(700, "bar"), on_next(750, "baz"), on_next(800, "qux"), on_completed(900)] assert xs.subscriptions == [subscribe(200, 900)] assert ys.subscriptions == [ subscribe(300, 550), subscribe(400, 650), subscribe(500, 750), subscribe(600, 850)] def test_flat_map_then_never_complete(self): scheduler = TestScheduler() xs = scheduler.create_cold_observable( on_next(100, 4), on_next(200, 2), on_next(300, 3), on_next(400, 1), on_next(500, 5), on_next(700, 0)) ys = scheduler.create_cold_observable( on_next(50, "foo"), on_next(100, "bar"), on_next(150, "baz"), on_next(200, "qux"), on_completed(250)) results = scheduler.start(lambda: xs.pipe(ops.flat_map(ys))) assert results.messages == [ on_next(350, "foo"), on_next(400, "bar"), on_next(450, "baz"), on_next(450, "foo"), on_next(500, "qux"), on_next(500, "bar"), on_next(550, "baz"), on_next(550, "foo"), on_next(600, "qux"), on_next(600, "bar"), on_next(650, "baz"), on_next(650, "foo"), on_next(700, "qux"), on_next(700, "bar"), on_next(750, "baz"), on_next(750, "foo"), on_next(800, "qux"), on_next(800, "bar"), on_next(850, "baz"), on_next(900, "qux"), on_next(950, "foo")] assert xs.subscriptions == [subscribe(200, 1000)] assert ys.subscriptions == [ subscribe(300, 550), subscribe(400, 650), subscribe(500, 750), subscribe(600, 850), subscribe(700, 950), subscribe(900, 1000)] def test_flat_map_then_complete_never(self): scheduler = TestScheduler() xs = scheduler.create_cold_observable( on_next(100, 4), on_next(200, 2), on_next(300, 3), on_next(400, 1), on_completed(500)) ys = scheduler.create_cold_observable( on_next(50, "foo"), on_next(100, "bar"), on_next(150, "baz"), on_next(200, "qux")) results = scheduler.start(lambda: xs.pipe(ops.flat_map(ys))) assert results.messages == [ on_next(350, "foo"), on_next(400, "bar"), on_next(450, "baz"), on_next(450, "foo"), on_next(500, "qux"), on_next(500, "bar"), on_next(550, "baz"), on_next(550, "foo"), on_next(600, "qux"), on_next(600, "bar"), on_next(650, "baz"), on_next(650, "foo"), on_next(700, "qux"), on_next(700, "bar"), on_next(750, "baz"), on_next(800, "qux")] assert xs.subscriptions == [subscribe(200, 700)] assert ys.subscriptions == [ subscribe(300, 1000), subscribe(400, 1000), subscribe(500, 1000), subscribe(600, 1000)] def test_flat_map_then_complete_error(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_cold_observable( on_next(100, 4), on_next(200, 2), on_next(300, 3), on_next(400, 1), on_completed(500)) ys = scheduler.create_cold_observable( on_next(50, "foo"), on_next(100, "bar"), on_next(150, "baz"), on_next(200, "qux"), on_error(300, ex)) results = scheduler.start(lambda: xs.pipe(ops.flat_map(ys))) assert results.messages == [ on_next(350, "foo"), on_next(400, "bar"), on_next(450, "baz"), on_next(450, "foo"), on_next(500, "qux"), on_next(500, "bar"), on_next(550, "baz"), on_next(550, "foo"), on_error(600, ex)] assert xs.subscriptions == [subscribe(200, 600)] assert ys.subscriptions == [ subscribe(300, 600), subscribe(400, 600), subscribe(500, 600), subscribe(600, 600)] def test_flat_map_then_error_complete(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_cold_observable( on_next(100, 4), on_next(200, 2), on_next(300, 3), on_next(400, 1), on_error(500, ex)) ys = scheduler.create_cold_observable( on_next(50, "foo"), on_next(100, "bar"), on_next(150, "baz"), on_next(200, "qux"), on_completed(250)) results = scheduler.start(lambda: xs.pipe(ops.flat_map(ys))) assert results.messages == [ on_next(350, "foo"), on_next(400, "bar"), on_next(450, "baz"), on_next(450, "foo"), on_next(500, "qux"), on_next(500, "bar"), on_next(550, "baz"), on_next(550, "foo"), on_next(600, "qux"), on_next(600, "bar"), on_next(650, "baz"), on_next(650, "foo"), on_error(700, ex)] assert xs.subscriptions == [subscribe(200, 700)] assert ys.subscriptions == [ subscribe(300, 550), subscribe(400, 650), subscribe(500, 700), subscribe(600, 700)] def test_flat_map_then_error_error(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_cold_observable( on_next(100, 4), on_next(200, 2), on_next(300, 3), on_next(400, 1), on_error(500, ex)) ys = scheduler.create_cold_observable( on_next(50, "foo"), on_next(100, "bar"), on_next(150, "baz"), on_next(200, "qux"), on_error(250, ex)) results = scheduler.start(lambda: xs.pipe(ops.flat_map(ys))) assert results.messages == [ on_next(350, "foo"), on_next(400, "bar"), on_next(450, "baz"), on_next(450, "foo"), on_next(500, "qux"), on_next(500, "bar"), on_error(550, ex)] assert xs.subscriptions == [subscribe(200, 550)] assert ys.subscriptions == [ subscribe(300, 550), subscribe(400, 550), subscribe(500, 550)] def test_flat_map_complete(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(5, scheduler.create_cold_observable( on_error(1, 'ex1'))), on_next(105, scheduler.create_cold_observable( on_error(1, 'ex2'))), on_next(300, scheduler.create_cold_observable( on_next(10, 102), on_next(90, 103), on_next(110, 104), on_next(190, 105), on_next(440, 106), on_completed(460))), on_next(400, scheduler.create_cold_observable( on_next(180, 202), on_next(190, 203), on_completed(205))), on_next(550, scheduler.create_cold_observable( on_next(10, 301), on_next(50, 302), on_next(70, 303), on_next(260, 304), on_next(310, 305), on_completed(410))), on_next(750, scheduler.create_cold_observable( on_completed(40))), on_next(850, scheduler.create_cold_observable( on_next(80, 401), on_next(90, 402), on_completed(100))), on_completed(900)) def factory(): return xs.pipe(ops.flat_map(lambda x: x)) results = scheduler.start(factory) assert results.messages == [ on_next(310, 102), on_next(390, 103), on_next(410, 104), on_next(490, 105), on_next(560, 301), on_next(580, 202), on_next(590, 203), on_next(600, 302), on_next(620, 303), on_next(740, 106), on_next(810, 304), on_next(860, 305), on_next(930, 401), on_next(940, 402), on_completed(960)] assert xs.subscriptions == [subscribe(200, 900)] assert xs.messages[2].value.value.subscriptions == [ subscribe(300, 760)] assert xs.messages[3].value.value.subscriptions == [ subscribe(400, 605)] assert xs.messages[4].value.value.subscriptions == [ subscribe(550, 960)] assert xs.messages[5].value.value.subscriptions == [ subscribe(750, 790)] assert xs.messages[6].value.value.subscriptions == [ subscribe(850, 950)] def test_flat_map_complete_inner_not_complete(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(5, scheduler.create_cold_observable( on_error(1, 'ex1'))), on_next(105, scheduler.create_cold_observable( on_error(1, 'ex2'))), on_next(300, scheduler.create_cold_observable( on_next(10, 102), on_next(90, 103), on_next(110, 104), on_next(190, 105), on_next(440, 106), on_completed(460))), on_next(400, scheduler.create_cold_observable( on_next(180, 202), on_next(190, 203))), on_next(550, scheduler.create_cold_observable( on_next(10, 301), on_next(50, 302), on_next(70, 303), on_next(260, 304), on_next(310, 305), on_completed(410))), on_next(750, scheduler.create_cold_observable( on_completed(40))), on_next(850, scheduler.create_cold_observable( on_next(80, 401), on_next(90, 402), on_completed(100))), on_completed(900)) def factory(): return xs.pipe(ops.flat_map(lambda x: x)) results = scheduler.start(factory) assert results.messages == [ on_next(310, 102), on_next(390, 103), on_next(410, 104), on_next(490, 105), on_next(560, 301), on_next(580, 202), on_next(590, 203), on_next(600, 302), on_next(620, 303), on_next(740, 106), on_next(810, 304), on_next(860, 305), on_next(930, 401), on_next(940, 402)] assert xs.subscriptions == [subscribe(200, 900)] assert xs.messages[2].value.value.subscriptions == [ subscribe(300, 760)] assert xs.messages[3].value.value.subscriptions == [ subscribe(400, 1000)] assert xs.messages[4].value.value.subscriptions == [ subscribe(550, 960)] assert xs.messages[5].value.value.subscriptions == [ subscribe(750, 790)] assert xs.messages[6].value.value.subscriptions == [ subscribe(850, 950)] def test_flat_map_complete_outer_not_complete(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(5, scheduler.create_cold_observable( on_error(1, 'ex1'))), on_next(105, scheduler.create_cold_observable( on_error(1, 'ex2'))), on_next(300, scheduler.create_cold_observable( on_next(10, 102), on_next(90, 103), on_next(110, 104), on_next(190, 105), on_next(440, 106), on_completed(460))), on_next(400, scheduler.create_cold_observable( on_next(180, 202), on_next(190, 203), on_completed(205))), on_next(550, scheduler.create_cold_observable( on_next(10, 301), on_next(50, 302), on_next(70, 303), on_next(260, 304), on_next(310, 305), on_completed(410))), on_next(750, scheduler.create_cold_observable( on_completed(40))), on_next(850, scheduler.create_cold_observable( on_next(80, 401), on_next(90, 402), on_completed(100)))) def factory(): return xs.pipe(ops.flat_map(lambda x: x)) results = scheduler.start(factory) assert results.messages == [ on_next(310, 102), on_next(390, 103), on_next(410, 104), on_next(490, 105), on_next(560, 301), on_next(580, 202), on_next(590, 203), on_next(600, 302), on_next(620, 303), on_next(740, 106), on_next(810, 304), on_next(860, 305), on_next(930, 401), on_next(940, 402)] assert xs.subscriptions == [subscribe(200, 1000)] assert xs.messages[2].value.value.subscriptions == [ subscribe(300, 760)] assert xs.messages[3].value.value.subscriptions == [ subscribe(400, 605)] assert xs.messages[4].value.value.subscriptions == [ subscribe(550, 960)] assert xs.messages[5].value.value.subscriptions == [ subscribe(750, 790)] assert xs.messages[6].value.value.subscriptions == [ subscribe(850, 950)] def test_flat_map_error_outer(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(5, scheduler.create_cold_observable( on_error(1, 'ex1'))), on_next(105, scheduler.create_cold_observable( on_error(1, 'ex2'))), on_next(300, scheduler.create_cold_observable( on_next(10, 102), on_next(90, 103), on_next(110, 104), on_next(190, 105), on_next(440, 106), on_completed(460))), on_next(400, scheduler.create_cold_observable( on_next(180, 202), on_next(190, 203), on_completed(205))), on_next(550, scheduler.create_cold_observable( on_next(10, 301), on_next(50, 302), on_next(70, 303), on_next(260, 304), on_next(310, 305), on_completed(410))), on_next(750, scheduler.create_cold_observable( on_completed(40))), on_next(850, scheduler.create_cold_observable( on_next(80, 401), on_next(90, 402), on_completed(100))), on_error(900, ex)) def factory(): return xs.pipe(ops.flat_map(lambda x: x)) results = scheduler.start(factory) assert results.messages == [ on_next(310, 102), on_next(390, 103), on_next(410, 104), on_next(490, 105), on_next(560, 301), on_next(580, 202), on_next(590, 203), on_next(600, 302), on_next(620, 303), on_next(740, 106), on_next(810, 304), on_next(860, 305), on_error(900, ex)] assert xs.subscriptions == [subscribe(200, 900)] assert xs.messages[2].value.value.subscriptions == [ subscribe(300, 760)] assert xs.messages[3].value.value.subscriptions == [ subscribe(400, 605)] assert xs.messages[4].value.value.subscriptions == [ subscribe(550, 900)] assert xs.messages[5].value.value.subscriptions == [ subscribe(750, 790)] assert xs.messages[6].value.value.subscriptions == [ subscribe(850, 900)] def test_flat_map_error_inner(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(5, scheduler.create_cold_observable( on_error(1, 'ex1'))), on_next(105, scheduler.create_cold_observable( on_error(1, 'ex2'))), on_next(300, scheduler.create_cold_observable( on_next(10, 102), on_next(90, 103), on_next(110, 104), on_next(190, 105), on_next(440, 106), on_error(460, ex))), on_next(400, scheduler.create_cold_observable( on_next(180, 202), on_next(190, 203), on_completed(205))), on_next(550, scheduler.create_cold_observable( on_next(10, 301), on_next(50, 302), on_next(70, 303), on_next(260, 304), on_next(310, 305), on_completed(410))), on_next(750, scheduler.create_cold_observable( on_completed(40))), on_next(850, scheduler.create_cold_observable( on_next(80, 401), on_next(90, 402), on_completed(100))), on_completed(900)) def factory(): return xs.pipe(ops.flat_map(lambda x: x)) results = scheduler.start(factory) assert results.messages == [ on_next(310, 102), on_next(390, 103), on_next(410, 104), on_next(490, 105), on_next(560, 301), on_next(580, 202), on_next(590, 203), on_next(600, 302), on_next(620, 303), on_next(740, 106), on_error(760, ex)] assert xs.subscriptions == [subscribe(200, 760)] assert xs.messages[2].value.value.subscriptions == [ subscribe(300, 760)] assert xs.messages[3].value.value.subscriptions == [ subscribe(400, 605)] assert xs.messages[4].value.value.subscriptions == [ subscribe(550, 760)] assert xs.messages[5].value.value.subscriptions == [ subscribe(750, 760)] assert xs.messages[6].value.value.subscriptions == [] def test_flat_map_dispose(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(5, scheduler.create_cold_observable( on_error(1, 'ex1'))), on_next(105, scheduler.create_cold_observable( on_error(1, 'ex2'))), on_next(300, scheduler.create_cold_observable( on_next(10, 102), on_next(90, 103), on_next(110, 104), on_next(190, 105), on_next(440, 106), on_completed(460))), on_next(400, scheduler.create_cold_observable( on_next(180, 202), on_next(190, 203), on_completed(205))), on_next(550, scheduler.create_cold_observable( on_next(10, 301), on_next(50, 302), on_next(70, 303), on_next(260, 304), on_next(310, 305), on_completed(410))), on_next(750, scheduler.create_cold_observable( on_completed(40))), on_next(850, scheduler.create_cold_observable( on_next(80, 401), on_next(90, 402), on_completed(100))), on_completed(900)) def create(): return xs.pipe(ops.flat_map(lambda x: x)) results = scheduler.start(create, disposed=700) assert results.messages == [ on_next(310, 102), on_next(390, 103), on_next(410, 104), on_next(490, 105), on_next(560, 301), on_next(580, 202), on_next(590, 203), on_next(600, 302), on_next(620, 303)] assert xs.subscriptions == [subscribe(200, 700)] assert xs.messages[2].value.value.subscriptions == [ subscribe(300, 700)] assert xs.messages[3].value.value.subscriptions == [ subscribe(400, 605)] assert xs.messages[4].value.value.subscriptions == [ subscribe(550, 700)] assert xs.messages[5].value.value.subscriptions == [] assert xs.messages[6].value.value.subscriptions == [] def test_flat_map_on_error(self): invoked = [0] ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(5, scheduler.create_cold_observable( on_error(1, 'ex1'))), on_next(105, scheduler.create_cold_observable( on_error(1, 'ex2'))), on_next(300, scheduler.create_cold_observable( on_next(10, 102), on_next(90, 103), on_next(110, 104), on_next(190, 105), on_next(440, 106), on_completed(460))), on_next(400, scheduler.create_cold_observable( on_next(180, 202), on_next(190, 203), on_completed(205))), on_next(550, scheduler.create_cold_observable( on_next(10, 301), on_next(50, 302), on_next(70, 303), on_next(260, 304), on_next(310, 305), on_completed(410))), on_next(750, scheduler.create_cold_observable( on_completed(40))), on_next(850, scheduler.create_cold_observable( on_next(80, 401), on_next(90, 402), on_completed(100))), on_completed(900)) def factory(): def projection(x): invoked[0] += 1 if invoked[0] == 3: raise Exception(ex) return x return xs.pipe(ops.flat_map(projection)) results = scheduler.start(factory) assert results.messages == [ on_next(310, 102), on_next(390, 103), on_next(410, 104), on_next(490, 105), on_error(550, ex)] assert xs.subscriptions == [subscribe(200, 550)] assert xs.messages[2].value.value.subscriptions == [ subscribe(300, 550)] assert xs.messages[3].value.value.subscriptions == [ subscribe(400, 550)] assert xs.messages[4].value.value.subscriptions == [] assert xs.messages[5].value.value.subscriptions == [] assert xs.messages[6].value.value.subscriptions == [] def test_flat_map_use_function(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(210, 4), on_next(220, 3), on_next(250, 5), on_next(270, 1), on_completed(290)) def factory(): def projection(x): return rx.interval(10).pipe(ops.map_indexed(lambda a, b: x), ops.take(x)) return xs.pipe(ops.flat_map(projection)) results = scheduler.start(factory) assert results.messages == [ on_next(220, 4), on_next(230, 3), on_next(230, 4), on_next(240, 3), on_next(240, 4), on_next(250, 3), on_next(250, 4), on_next(260, 5), on_next(270, 5), on_next(280, 1), on_next(280, 5), on_next(290, 5), on_next(300, 5), on_completed(300)] assert xs.subscriptions == [subscribe(200, 290)] def test_flat_map_iterable_complete(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(210, 2), on_next(340, 4), on_next(420, 3), on_next(510, 2), on_completed(600) ) inners = [] def create(): def mapper(x): ys = [x] * x inners.append(ys) return ys return xs.pipe(ops.flat_map(mapper)) res = scheduler.start(create) assert res.messages == [ on_next(210, 2), on_next(210, 2), on_next(340, 4), on_next(340, 4), on_next(340, 4), on_next(340, 4), on_next(420, 3), on_next(420, 3), on_next(420, 3), on_next(510, 2), on_next(510, 2), on_completed(600)] assert xs.subscriptions == [ subscribe(200, 600)] assert(4 == len(inners)) if __name__ == '__main__': unittest.main() RxPY-3.2.0/tests/test_observable/test_flatmap_async.py000066400000000000000000000015331404130727200231370ustar00rootroot00000000000000import unittest import asyncio from rx import operators as ops from rx.subject import Subject from rx.scheduler.eventloop import AsyncIOScheduler class TestFlatMapAsync(unittest.TestCase): def test_flat_map_async(self): actual_next = None loop = asyncio.get_event_loop() scheduler = AsyncIOScheduler(loop=loop) def mapper(i): async def _mapper(i): return i + 1 return asyncio.ensure_future(_mapper(i)) def on_next(i): nonlocal actual_next actual_next = i async def test_flat_map(): x = Subject() x.pipe(ops.flat_map(mapper)).subscribe(on_next, scheduler=scheduler) x.on_next(10) await asyncio.sleep(0.1) loop.run_until_complete(test_flat_map()) assert actual_next == 11 RxPY-3.2.0/tests/test_observable/test_forin.py000066400000000000000000000026421404130727200214350ustar00rootroot00000000000000import unittest import rx from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class TestForIn(unittest.TestCase): def test_for_basic(self): scheduler = TestScheduler() def create(): def mapper(x): return scheduler.create_cold_observable( on_next(x * 100 + 10, x * 10 + 1), on_next(x * 100 + 20, x * 10 + 2), on_next(x * 100 + 30, x * 10 + 3), on_completed(x * 100 + 40)) return rx.for_in([1, 2, 3], mapper) results = scheduler.start(create=create) assert results.messages == [ on_next(310, 11), on_next(320, 12), on_next(330, 13), on_next(550, 21), on_next(560, 22), on_next(570, 23), on_next(890, 31), on_next(900, 32), on_next(910, 33), on_completed(920) ] def test_for_throws(self): ex = 'ex' scheduler = TestScheduler() def create(): def mapper(x): raise Exception(ex) return rx.for_in([1, 2, 3], mapper) results = scheduler.start(create=create) assert results.messages == [on_error(200, ex)] RxPY-3.2.0/tests/test_observable/test_forkjoin.py000066400000000000000000000166121404130727200221430ustar00rootroot00000000000000import unittest import rx from rx import operators as ops from rx.testing import ReactiveTest, TestScheduler on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class RxException(Exception): pass class TestForkJoin(unittest.TestCase): def test_fork_join_never_never(self): scheduler = TestScheduler() e1 = rx.never() e2 = rx.never() results = scheduler.start(lambda: rx.fork_join(e1, e2)) assert results.messages == [] def test_fork_join_never_empty(self): scheduler = TestScheduler() e1 = rx.never() e2 = rx.empty() results = scheduler.start(lambda: rx.fork_join(e1, e2)) assert results.messages == [on_completed(200)] def test_fork_join_never_non_empty(self): scheduler = TestScheduler() e1 = rx.never() e2 = scheduler.create_hot_observable([ on_next(150, 1), on_next(230, 2), on_completed(300) ]) results = scheduler.start(lambda: rx.fork_join(e1, e2)) assert results.messages == [] def test_fork_join_empty_empty(self): scheduler = TestScheduler() e1 = rx.empty() e2 = rx.empty() results = scheduler.start(lambda: rx.fork_join(e1, e2)) assert results.messages == [on_completed(200)] def test_fork_join_empty_non_empty(self): scheduler = TestScheduler() e1 = rx.empty() e2 = scheduler.create_hot_observable([ on_next(150, 1), on_next(230, 2), on_completed(300) ]) results = scheduler.start(lambda: rx.fork_join(e1, e2)) assert results.messages == [on_completed(200)] def test_fork_join_non_empty_non_empty_right_last(self): scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_next(215, 2), on_completed(230)] msgs2 = [on_next(150, 1), on_next(220, 3), on_completed(240)] e1 = scheduler.create_hot_observable(msgs1) e2 = scheduler.create_hot_observable(msgs2) results = scheduler.start(lambda: rx.fork_join(e1, e2)) assert results.messages == [on_next(240, (2, 3)), on_completed(240)] def test_fork_join_non_empty_non_empty_left_last(self): scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_next(215, 2), on_completed(300)] msgs2 = [on_next(150, 1), on_next(220, 3), on_completed(240)] e1 = scheduler.create_hot_observable(msgs1) e2 = scheduler.create_hot_observable(msgs2) results = scheduler.start(lambda: rx.fork_join(e1, e2)) assert results.messages == [on_next(300, (2, 3)), on_completed(300)] def test_fork_join_empty_error(self): ex = RxException() scheduler = TestScheduler() e1 = rx.empty() e2 = scheduler.create_hot_observable([ on_next(150, 1), on_next(230, 2), on_error(300, ex) ]) results = scheduler.start(lambda: rx.fork_join(e1, e2)) assert results.messages == [on_completed(200)] def test_fork_join_never_error(self): ex = RxException() scheduler = TestScheduler() e1 = rx.never() e2 = scheduler.create_hot_observable([ on_next(150, 1), on_next(230, 2), on_error(300, ex) ]) results = scheduler.start(lambda: rx.fork_join(e1, e2)) assert results.messages == [on_error(300, ex)] def test_fork_join_non_empty_error_left_last(self): ex = RxException() scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_next(250, 2), on_completed(330)] msgs2 = [on_next(150, 1), on_next(230, 2), on_error(300, ex)] e1 = scheduler.create_hot_observable(msgs1) e2 = scheduler.create_hot_observable(msgs2) results = scheduler.start(lambda: rx.fork_join(e1, e2)) assert results.messages == [on_error(300, ex)] def test_fork_join_non_empty_error_right_last(self): ex = RxException() scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_next(250, 2), on_completed(300)] msgs2 = [on_next(150, 1), on_next(230, 2), on_error(330, ex)] e1 = scheduler.create_hot_observable(msgs1) e2 = scheduler.create_hot_observable(msgs2) results = scheduler.start(lambda: rx.fork_join(e1, e2)) assert results.messages == [on_error(330, ex)] def test_fork_join_error_error_left_last(self): ex = RxException() scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_next(250, 2), on_error(340, ex)] msgs2 = [on_next(150, 1), on_next(230, 2), on_error(330, ex)] e1 = scheduler.create_hot_observable(msgs1) e2 = scheduler.create_hot_observable(msgs2) results = scheduler.start(lambda: rx.fork_join(e1, e2)) assert results.messages == [on_error(330, ex)] def test_fork_join_error_error_right_last(self): ex = RxException() scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_next(250, 2), on_error(340, ex)] msgs2 = [on_next(150, 1), on_next(230, 2), on_error(370, ex)] e1 = scheduler.create_hot_observable(msgs1) e2 = scheduler.create_hot_observable(msgs2) results = scheduler.start(lambda: rx.fork_join(e1, e2)) assert results.messages == [on_error(340, ex)] def test_fork_join_many(self): scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_next(210, 2), on_next(230, 3), on_next(300, 9), on_completed(500)] msgs2 = [on_next(150, 1), on_next(205, 3), on_next(220, 7), on_next(400, 3), on_completed(900)] msgs3 = [on_next(150, 1), on_next(250, 2), on_next(300, 3), on_next(400, 9), on_next(500, 2), on_completed(850)] msgs4 = [on_next(150, 1), on_next(400, 2), on_next(550, 10), on_next(560, 11), on_next(600, 3), on_completed(605)] msgs5 = [on_next(150, 1), on_next(201, 3), on_next(550, 10), on_next(560, 11), on_next(600, 3), on_next(900, 99), on_completed(905)] xs = [scheduler.create_hot_observable(x) for x in [msgs1, msgs2, msgs3, msgs4, msgs5]] results = scheduler.start(lambda: rx.fork_join(*xs)) assert results.messages == [on_next(905, (9, 3, 2, 3, 99)), on_completed(905)] def test_fork_join_many_ops(self): scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_next(210, 2), on_next(230, 3), on_next(300, 9), on_completed(500)] msgs2 = [on_next(150, 1), on_next(205, 3), on_next(220, 7), on_next(400, 3), on_completed(900)] msgs3 = [on_next(150, 1), on_next(250, 2), on_next(300, 3), on_next(400, 9), on_next(500, 2), on_completed(850)] msgs4 = [on_next(150, 1), on_next(400, 2), on_next(550, 10), on_next(560, 11), on_next(600, 3), on_completed(605)] msgs5 = [on_next(150, 1), on_next(201, 3), on_next(550, 10), on_next(560, 11), on_next(600, 3), on_next(900, 99), on_completed(905)] xs = [scheduler.create_hot_observable(x) for x in [msgs2, msgs3, msgs4, msgs5]] def create(): return scheduler.create_hot_observable(msgs1).pipe(ops.fork_join(*xs)) results = scheduler.start(create) assert results.messages == [on_next(905, (9, 3, 2, 3, 99)), on_completed(905)] RxPY-3.2.0/tests/test_observable/test_fromcallback.py000066400000000000000000000030111404130727200227270ustar00rootroot00000000000000import unittest import rx from rx.testing import ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class RxException(Exception): pass # Helper function for raising exceptions within lambdas def _raise(ex): raise RxException(ex) class TestFromCallback(unittest.TestCase): def test_from_callback(self): res = rx.from_callback(lambda cb: cb(True))() def on_next(r): self.assertEqual(r, True) def on_error(err): assert(False) def on_completed(): assert(True) res.subscribe(on_next, on_error, on_completed) def test_from_callback_single(self): res = rx.from_callback(lambda file, cb: cb(file))('file.txt') def on_next(r): self.assertEqual(r, 'file.txt') def on_error(err): assert(False) def on_completed(): assert(True) res.subscribe(on_next, on_error, on_completed) def test_from_node_callback_mapper(self): res = rx.from_callback( lambda f, s, t, cb: cb(f, s, t), lambda r: r[0] )(1, 2, 3) def on_next(r): self.assertEqual(r, 1) def on_error(err): assert(False) def on_completed(): assert(True) res.subscribe(on_next, on_error, on_completed) RxPY-3.2.0/tests/test_observable/test_fromfuture.py000066400000000000000000000051211404130727200225110ustar00rootroot00000000000000import asyncio from asyncio import Future import unittest import rx class TestFromFuture(unittest.TestCase): def test_future_success(self): loop = asyncio.get_event_loop() success = [False, True, False] @asyncio.coroutine def go(): future = Future() future.set_result(42) source = rx.from_future(future) def on_next(x): success[0] = x == 42 def on_error(err): success[1] = False def on_completed(): success[2] = True source.subscribe(on_next, on_error, on_completed) loop.run_until_complete(go()) assert(all(success)) def test_future_failure(self): loop = asyncio.get_event_loop() success = [True, False, True] @asyncio.coroutine def go(): error = Exception('woops') future = Future() future.set_exception(error) source = rx.from_future(future) def on_next(x): success[0] = False def on_error(err): success[1] = str(err) == str(error) def on_completed(): success[2] = False source.subscribe(on_next, on_error, on_completed) loop.run_until_complete(go()) assert all(success) def test_future_cancel(self): loop = asyncio.get_event_loop() success = [True, False, True] @asyncio.coroutine def go(): future = Future() source = rx.from_future(future) def on_next(x): success[0] = False def on_error(err): success[1] = type(err) == asyncio.CancelledError def on_completed(): success[2] = False source.subscribe(on_next, on_error, on_completed) future.cancel() loop.run_until_complete(go()) assert all(success) def test_future_dispose(self): loop = asyncio.get_event_loop() success = [True, True, True] @asyncio.coroutine def go(): future = Future() future.set_result(42) source = rx.from_future(future) def on_next(x): success[0] = False def on_error(err): success[1] = False def on_completed(): success[2] = False subscription = source.subscribe(on_next, on_error, on_completed) subscription.dispose() loop.run_until_complete(go()) assert all(success) RxPY-3.2.0/tests/test_observable/test_fromiterable.py000066400000000000000000000034301404130727200227670ustar00rootroot00000000000000import unittest import rx from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class RxException(Exception): pass # Helper function for raising exceptions within lambdas def _raise(ex): raise RxException(ex) class TestFromIterable(unittest.TestCase): def test_subscribe_to_iterable_finite(self): iterable_finite = [1, 2, 3, 4, 5] scheduler = TestScheduler() def create(): return rx.from_(iterable_finite) results = scheduler.start(create) assert results.messages == [ on_next(200, 1), on_next(200, 2), on_next(200, 3), on_next(200, 4), on_next(200, 5), on_completed(200)] def test_subscribe_to_iterable_empty(self): iterable_finite = [] scheduler = TestScheduler() def create(): return rx.from_(iterable_finite) results = scheduler.start(create) assert results.messages == [on_completed(200)] def test_double_subscribe_to_iterable(self): iterable_finite = [1, 2, 3] scheduler = TestScheduler() obs = rx.from_(iterable_finite) results = scheduler.start(lambda: rx.concat(obs, obs)) assert results.messages == [ on_next(200, 1), on_next(200, 2), on_next(200, 3), on_next(200, 1), on_next(200, 2), on_next(200, 3), on_completed(200)] def test_observer_throws(self): with self.assertRaises(RxException): rx.from_iterable([1, 2, 3]).subscribe(lambda x: _raise('ex')) RxPY-3.2.0/tests/test_observable/test_generate.py000066400000000000000000000054471404130727200221200ustar00rootroot00000000000000import unittest import rx from rx import operators as ops from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class RxException(Exception): pass # Helper function for raising exceptions within lambdas def _raise(ex): raise RxException(ex) class TestGenerate(unittest.TestCase): def test_generate_finite(self): scheduler = TestScheduler() def create(): return rx.generate(0, lambda x: x <= 3, lambda x: x + 1, ) results = scheduler.start(create) assert results.messages == [ on_next(200, 0), on_next(200, 1), on_next(200, 2), on_next(200, 3), on_completed(200)] def test_generate_throw_condition(self): scheduler = TestScheduler() ex = 'ex' def create(): return rx.generate(0, lambda x: _raise('ex'), lambda x: x + 1, ) results = scheduler.start(create) assert results.messages == [on_error(200, ex)] def test_generate_throw_iterate(self): scheduler = TestScheduler() ex = 'ex' def create(): return rx.generate(0, lambda x: True, lambda x: _raise(ex), ) results = scheduler.start(create) assert results.messages == [ on_next(200, 0), on_error(200, ex)] def test_generate_dispose(self): scheduler = TestScheduler() ex = 'ex' def create(): return rx.generate(0, lambda x: True, lambda x: x + 1, ) results = scheduler.start(create, disposed=200) assert results.messages == [] def test_generate_repeat(self): scheduler = TestScheduler() def create(): return rx.generate(0, lambda x: x <= 3, lambda x: x + 1, ) \ .pipe(ops.repeat(2)) results = scheduler.start(create) assert results.messages == [ on_next(200, 0), on_next(200, 1), on_next(200, 2), on_next(200, 3), on_next(200, 0), on_next(200, 1), on_next(200, 2), on_next(200, 3), on_completed(200)] RxPY-3.2.0/tests/test_observable/test_generatewithrelativetime.py000066400000000000000000000114151404130727200254170ustar00rootroot00000000000000import unittest import pytest import rx from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class RxException(Exception): pass # Helper function for raising exceptions within lambdas def _raise(ex): raise RxException(ex) class TestGenerateWithRelativeTime(unittest.TestCase): def test_generate_timespan_finite(self): scheduler = TestScheduler() def create(): return rx.generate_with_relative_time( 0, lambda x: x <= 3, lambda x: x + 1, lambda x: x + 1 ) results = scheduler.start(create) assert results.messages == [on_next(201, 0), on_next( 203, 1), on_next(206, 2), on_next(210, 3), on_completed(210)] def test_generate_timespan_throw_condition(self): ex = 'ex' scheduler = TestScheduler() def create(): return rx.generate_with_relative_time( 0, lambda x: _raise(ex), lambda x: x + 1, lambda x: x + 1 ) results = scheduler.start(create) assert results.messages == [on_error(200, ex)] def test_generate_timespan_throw_iterate(self): ex = 'ex' scheduler = TestScheduler() def create(): return rx.generate_with_relative_time( 0, lambda x: True, lambda x: _raise(ex), lambda x: x + 1 ) results = scheduler.start(create) assert results.messages == [on_next(201, 0), on_error(201, ex)] def test_generate_timespan_throw_timemapper(self): ex = 'ex' scheduler = TestScheduler() def create(): return rx.generate_with_relative_time( 0, lambda x: True, lambda x: x + 1, lambda x: _raise(ex) ) results = scheduler.start(create) assert results.messages == [on_error(200, ex)] def test_generate_timespan_dispose(self): scheduler = TestScheduler() def create(): return rx.generate_with_relative_time( 0, lambda x: True, lambda x: x + 1, lambda x: x + 1 ) results = scheduler.start(create, disposed=210) assert results.messages == [on_next(201, 0), on_next(203, 1), on_next(206, 2)] def test_generate_datetime_offset_finite(self): scheduler = TestScheduler() def create(): return rx.generate_with_relative_time( 0, lambda x: x <= 3, lambda x: x + 1, lambda x: x + 1 ) results = scheduler.start(create) assert results.messages == [ on_next(201, 0), on_next(203, 1), on_next(206, 2), on_next(210, 3), on_completed(210) ] def test_generate_datetime_offset_throw_condition(self): ex = 'ex' scheduler = TestScheduler() def create(): return rx.generate_with_relative_time( 0, lambda x: _raise(ex), lambda x: x + 1, lambda x: x + 1) results = scheduler.start(create) assert results.messages == [on_error(200, ex)] def test_generate_datetime_offset_throw_iterate(self): ex = 'ex' scheduler = TestScheduler() def create(): return rx.generate_with_relative_time( 0, lambda x: True, lambda x: _raise(ex), lambda x: x + 1) results = scheduler.start(create) assert results.messages == [on_next(201, 0), on_error(201, ex)] def test_generate_datetime_offset_throw_time_mapper(self): ex = 'ex' scheduler = TestScheduler() def create(): return rx.generate_with_relative_time( 0, lambda x: True, lambda x: x + 1, lambda x: _raise(ex) ) results = scheduler.start(create) assert results.messages == [on_error(200, ex)] def test_generate_datetime_offset_dispose(self): scheduler = TestScheduler() def create(): return rx.generate_with_relative_time( 0, lambda x: True, lambda x: x + 1, lambda x: x + 1 ) results = scheduler.start(create, disposed=210) assert results.messages == [on_next(201, 0), on_next(203, 1), on_next(206, 2)] RxPY-3.2.0/tests/test_observable/test_groupby.py000066400000000000000000000521331404130727200220070ustar00rootroot00000000000000import unittest import rx from rx import operators as ops from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class RxException(Exception): pass # Helper function for raising exceptions within lambdas def _raise(ex): raise RxException(ex) class TestGroupBy(unittest.TestCase): def test_group_by_with_key_comparer(self): scheduler = TestScheduler() key_invoked = [0] xs = scheduler.create_hot_observable( on_next(90, "error"), on_next(110, "error"), on_next(130, "error"), on_next(220, " foo"), on_next(240, " FoO "), on_next(270, "baR "), on_next(310, "foO "), on_next(350, " Baz "), on_next(360, " qux "), on_next(390, " bar"), on_next(420, " BAR "), on_next(470, "FOO "), on_next(480, "baz "), on_next(510, " bAZ "), on_next(530, " fOo "), on_completed(570), on_next(580, "error"), on_completed(600), on_error(650, 'ex')) def factory(): def key_mapper(x): key_invoked[0] += 1 return x.lower().strip() return xs.pipe( ops.group_by(key_mapper, lambda x: x), ops.map(lambda g: g.key), ) results = scheduler.start(factory) assert results.messages == [ on_next(220, "foo"), on_next(270, "bar"), on_next(350, "baz"), on_next(360, "qux"), on_completed(570)] assert xs.subscriptions == [subscribe(200, 570)] assert(key_invoked[0] == 12) def test_groupby_outer_complete(self): scheduler = TestScheduler() key_invoked = [0] ele_invoked = [0] xs = scheduler.create_hot_observable( on_next(90, "error"), on_next(110, "error"), on_next(130, "error"), on_next(220, " foo"), on_next(240, " FoO "), on_next(270, "baR "), on_next(310, "foO "), on_next(350, " Baz "), on_next(360, " qux "), on_next(390, " bar"), on_next(420, " BAR "), on_next(470, "FOO "), on_next(480, "baz "), on_next(510, " bAZ "), on_next(530, " fOo "), on_completed(570), on_next(580, "error"), on_completed(600), on_error(650, 'ex')) def factory(): def key_mapper(x): key_invoked[0] += 1 return x.lower().strip() def element_mapper(x): ele_invoked[0] += 1 return x[::-1] # Yes, this is reverse string in Python return xs.pipe( ops.group_by(key_mapper, element_mapper), ops.map(lambda g: g.key), ) results = scheduler.start(factory) assert results.messages == [ on_next(220, "foo"), on_next(270, "bar"), on_next(350, "baz"), on_next(360, "qux"), on_completed(570)] assert xs.subscriptions == [subscribe(200, 570)] assert key_invoked[0] == 12 assert ele_invoked[0] == 12 def test_group_by_outer_error(self): scheduler = TestScheduler() key_invoked = [0] ele_invoked = [0] ex = 'ex' xs = scheduler.create_hot_observable( on_next(90, "error"), on_next(110, "error"), on_next(130, "error"), on_next(220, " foo"), on_next(240, " FoO "), on_next(270, "baR "), on_next(310, "foO "), on_next(350, " Baz "), on_next(360, " qux "), on_next(390, " bar"), on_next(420, " BAR "), on_next(470, "FOO "), on_next(480, "baz "), on_next(510, " bAZ "), on_next(530, " fOo "), on_error(570, ex), on_next(580, "error"), on_completed(600), on_error(650, 'ex')) def factory(): def key_mapper(x): key_invoked[0] += 1 return x.lower().strip() def element_mapper(x): ele_invoked[0] += 1 return x[::-1] return xs.pipe( ops.group_by(key_mapper, element_mapper), ops.map(lambda g: g.key), ) results = scheduler.start(factory) assert results.messages == [ on_next(220, "foo"), on_next(270, "bar"), on_next(350, "baz"), on_next(360, "qux"), on_error(570, ex)] assert xs.subscriptions == [subscribe(200, 570)] assert(key_invoked[0] == 12) assert(ele_invoked[0] == 12) def test_group_by_outer_dispose(self): scheduler = TestScheduler() key_invoked = [0] ele_invoked = [0] xs = scheduler.create_hot_observable( on_next(90, "error"), on_next(110, "error"), on_next(130, "error"), on_next(220, " foo"), on_next(240, " FoO "), on_next(270, "baR "), on_next(310, "foO "), on_next(350, " Baz "), on_next(360, " qux "), on_next(390, " bar"), on_next(420, " BAR "), on_next(470, "FOO "), on_next(480, "baz "), on_next(510, " bAZ "), on_next(530, " fOo "), on_completed(570), on_next(580, "error"), on_completed(600), on_error(650, 'ex')) def factory(): def key_mapper(x): key_invoked[0] += 1 return x.lower().strip() def element_mapper(x): ele_invoked[0] += 1 return x[::-1] return xs.pipe( ops.group_by(key_mapper, element_mapper), ops.map(lambda g: g.key), ) results = scheduler.start(factory, disposed=355) assert results.messages == [ on_next(220, "foo"), on_next(270, "bar"), on_next(350, "baz")] assert xs.subscriptions == [subscribe(200, 355)] assert(key_invoked[0] == 5) assert(ele_invoked[0] == 5) def test_group_by_outer_key_on_error(self): scheduler = TestScheduler() key_invoked = [0] ele_invoked = [0] ex = 'ex' xs = scheduler.create_hot_observable( on_next(90, "error"), on_next(110, "error"), on_next(130, "error"), on_next(220, " foo"), on_next(240, " FoO "), on_next(270, "baR "), on_next(310, "foO "), on_next(350, " Baz "), on_next(360, " qux "), on_next(390, " bar"), on_next(420, " BAR "), on_next(470, "FOO "), on_next(480, "baz "), on_next(510, " bAZ "), on_next(530, " fOo "), on_completed(570), on_next(580, "error"), on_completed(600), on_error(650, 'ex')) def factory(): def key_mapper(x): key_invoked[0] += 1 if key_invoked[0] == 10: raise Exception(ex) return x.lower().strip() def element_mapper(x): ele_invoked[0] += 1 return x[::-1] return xs.pipe( ops.group_by(key_mapper, element_mapper), ops.map(lambda g: g.key), ) results = scheduler.start(factory) assert results.messages == [ on_next(220, "foo"), on_next(270, "bar"), on_next(350, "baz"), on_next(360, "qux"), on_error(480, ex)] assert xs.subscriptions == [subscribe(200, 480)] assert key_invoked[0] == 10 assert ele_invoked[0] == 9 def test_group_by_outer_ele_on_error(self): scheduler = TestScheduler() key_invoked = [0] ele_invoked = [0] ex = 'ex' xs = scheduler.create_hot_observable( on_next(90, "error"), on_next(110, "error"), on_next(130, "error"), on_next(220, " foo"), on_next(240, " FoO "), on_next(270, "baR "), on_next(310, "foO "), on_next(350, " Baz "), on_next(360, " qux "), on_next(390, " bar"), on_next(420, " BAR "), on_next(470, "FOO "), on_next(480, "baz "), on_next(510, " bAZ "), on_next(530, " fOo "), on_completed(570), on_next(580, "error"), on_completed(600), on_error(650, 'ex')) def factory(): def key_mapper(x): key_invoked[0] += 1 return x.lower().strip() def element_mapper(x): ele_invoked[0] += 1 if ele_invoked[0] == 10: raise Exception(ex) return x[::-1] return xs.pipe( ops.group_by(key_mapper, element_mapper), ops.map(lambda g: g.key), ) results = scheduler.start(factory) assert results.messages == [ on_next(220, "foo"), on_next(270, "bar"), on_next(350, "baz"), on_next(360, "qux"), on_error(480, ex)] assert xs.subscriptions == [subscribe(200, 480)] assert key_invoked[0] == 10 assert ele_invoked[0] == 10 def test_group_by_inner_complete(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(90, "error"), on_next(110, "error"), on_next(130, "error"), on_next(220, " foo"), on_next(240, " FoO "), on_next(270, "baR "), on_next(310, "foO "), on_next(350, " Baz "), on_next(360, " qux "), on_next(390, " bar"), on_next(420, " BAR "), on_next(470, "FOO "), on_next(480, "baz "), on_next(510, " bAZ "), on_next(530, " fOo "), on_completed(570), on_next(580, "error"), on_completed(600), on_error(650, 'ex')) c = { "outer_subscription": None, "inner_subscriptions": {}, "inners": {}, "results": {}, "outer": None } def action1(scheduler, state): c["outer"] = xs.pipe( ops.group_by(lambda x: x.lower().strip(), lambda x: x[::-1]), ) scheduler.schedule_absolute(created, action1) def action2(scheduler, state): def next(group): result = scheduler.create_observer() c["inners"][group.key] = group c["results"][group.key] = result def action21(scheduler, state): c["inner_subscriptions"][group.key] = group.subscribe(result, scheduler) scheduler.schedule_relative(100, action21) c["outer_subscription"] = c["outer"].subscribe(next, scheduler=scheduler) scheduler.schedule_absolute(subscribed, action2) def action3(scheduler, state): c["outer_subscription"].dispose() for sub in c["inner_subscriptions"].values(): sub.dispose() scheduler.schedule_absolute(disposed, action3) scheduler.start() assert(len(c["inners"]) == 4) assert c["results"]['foo'].messages == [ on_next(470, " OOF"), on_next(530, " oOf "), on_completed(570)] assert c["results"]['bar'].messages == [ on_next(390, "rab "), on_next(420, " RAB "), on_completed(570)] assert c["results"]['baz'].messages == [ on_next(480, " zab"), on_next(510, " ZAb "), on_completed(570)] assert c["results"]['qux'].messages == [ on_completed(570)] assert xs.subscriptions == [ subscribe(200, 570)] def test_group_by_inner_complete_all(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(90, "error"), on_next(110, "error"), on_next(130, "error"), on_next(220, " foo"), on_next(240, " FoO "), on_next(270, "baR "), on_next(310, "foO "), on_next(350, " Baz "), on_next(360, " qux "), on_next(390, " bar"), on_next(420, " BAR "), on_next(470, "FOO "), on_next(480, "baz "), on_next(510, " bAZ "), on_next(530, " fOo "), on_completed(570), on_next(580, "error"), on_completed(600), on_error(650, 'ex')) inners = {} inner_subscriptions = {} results = {} c = { "outer": None, "outer_subscription": None, "result": None } def action1(scheduler, state): c["outer"] = xs.pipe( ops.group_by( lambda x: x.lower().strip(), lambda x: x[::-1], )) return c["outer"] scheduler.schedule_absolute(created, action1) def action2(scheduler, state): def on_next(group): c["result"] = scheduler.create_observer() inners[group.key] = group results[group.key] = c["result"] inner_subscriptions[group.key] = group.subscribe(c["result"], scheduler) c["outer_subscription"] = c["outer"].subscribe(on_next, scheduler=scheduler) return c["outer_subscription"] scheduler.schedule_absolute(subscribed, action2) def action3(scheduler, state): c["outer_subscription"].dispose() for sub in inner_subscriptions.values(): sub.dispose() scheduler.schedule_absolute(disposed, action3) scheduler.start() assert(len(inners) == 4) assert results['foo'].messages == [ on_next(220, "oof "), on_next(240, " OoF "), on_next(310, " Oof"), on_next(470, " OOF"), on_next(530, " oOf "), on_completed(570)] assert results['bar'].messages == [ on_next(270, " Rab"), on_next(390, "rab "), on_next(420, " RAB "), on_completed(570)] assert results['baz'].messages == [ on_next(350, " zaB "), on_next(480, " zab"), on_next(510, " ZAb "), on_completed(570)] assert results['qux'].messages == [ on_next(360, " xuq "), on_completed(570)] assert xs.subscriptions == [ subscribe(200, 570)] def test_group_by_inner_error(self): ex = 'ex1' scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(90, "error"), on_next(110, "error"), on_next(130, "error"), on_next(220, " foo"), on_next(240, " FoO "), on_next(270, "baR "), on_next(310, "foO "), on_next(350, " Baz "), on_next(360, " qux "), on_next(390, " bar"), on_next(420, " BAR "), on_next(470, "FOO "), on_next(480, "baz "), on_next(510, " bAZ "), on_next(530, " fOo "), on_error(570, ex), on_next(580, "error"), on_completed(600), on_error(650, 'ex')) inner_subscriptions = {} inners = {} results = {} c = { "outer_subscription": None, "outer": None } def action1(scheduler, state): c["outer"] = xs.pipe( ops.group_by( lambda x: x.lower().strip(), lambda x: x[::-1], )) return c["outer"] scheduler.schedule_absolute(created, action1) def action2(scheduler, state): def on_next(group): result = scheduler.create_observer() inners[group.key] = group results[group.key] = result def action3(scheduler, state): inner_subscriptions[group.key] = group.subscribe(result, scheduler) scheduler.schedule_relative(100, action3) c["outer_subscription"] = c["outer"].subscribe(on_next, lambda e: None, scheduler=scheduler) return c["outer_subscription"] scheduler.schedule_absolute(subscribed, action2) def action4(scheduler, state): c["outer_subscription"].dispose() for sub in inner_subscriptions.values(): sub.dispose() scheduler.schedule_absolute(disposed, action4) scheduler.start() assert len(inners) == 4 assert results['foo'].messages == [ on_next(470, " OOF"), on_next(530, " oOf "), on_error(570, ex)] assert results['bar'].messages == [ on_next(390, "rab "), on_next(420, " RAB "), on_error(570, ex)] assert results['baz'].messages == [ on_next(480, " zab"), on_next(510, " ZAb "), on_error(570, ex)] assert results['qux'].messages == [ on_error(570, ex)] assert xs.subscriptions == [ subscribe(200, 570)] def test_group_by_with_merge(self): scheduler = TestScheduler() xs = [None] results = [None] def action1(scheduler, state): xs[0] = rx.from_iterable(["alpha", "apple", "beta", "bat", "gamma"]) \ .pipe(ops.group_by(lambda s: s[0]), ops.map(lambda xs: xs.pipe(ops.to_iterable(), ops.map(list))), ops.merge_all(), ) scheduler.schedule_absolute(created, action1) def action2(scheduler, state): results[0] = scheduler.create_observer() xs[0].subscribe(results[0], scheduler) scheduler.schedule_absolute(subscribed, action2) scheduler.start() assert results[0].messages == [ on_next(200, ["alpha", "apple"]), on_next(200, ["beta", "bat"]), on_next(200, ["gamma"]), on_completed(200)] def test_group_by_with_ReplaySubject(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(300, 1), on_next(310, 2), on_next(320, 3), on_next(320, 4), on_next(320, 5), on_next(320, 6), on_completed(1000), ) observer_groups = scheduler.create_observer() observer_odd = scheduler.create_observer() observer_even = scheduler.create_observer() def subscription(scheduler, state): source = xs.pipe( ops.group_by( key_mapper=lambda x: x % 2, element_mapper=None, subject_mapper=lambda: rx.subject.ReplaySubject(2), ) ) return source.subscribe(observer_groups, scheduler=scheduler) scheduler.schedule_absolute(290, subscription) scheduler.advance_to(500) # extract grouped observables from messages list groups = {m.value.value.key: m.value.value for m in observer_groups.messages if m.value.kind =='N'} def subscription_odd(scheduler, state): source = groups[1] return source.subscribe(observer_odd, scheduler=scheduler) def subscription_even(scheduler, state): source = groups[0] return source.subscribe(observer_even, scheduler=scheduler) scheduler.schedule_absolute(500, subscription_odd) scheduler.schedule_absolute(600, subscription_even) scheduler.advance_to(1100) # only the last 2 items of odd/even are received because the # ReplaySubject has been configured with a buffer size of 2 assert observer_odd.messages == [ on_next(500, 3), on_next(500, 5), on_completed(1000), ] assert observer_even.messages == [ on_next(600, 4), on_next(600, 6), on_completed(1000), ] if __name__ == '__main__': unittest.main() RxPY-3.2.0/tests/test_observable/test_groupjoin.py000066400000000000000000001114451404130727200223360ustar00rootroot00000000000000import unittest from datetime import timedelta import rx from rx import operators as ops from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class TimeSpan(object): @classmethod def from_ticks(cls, value): return value class TimeInterval(object): def __init__(self, value, interval): if isinstance(interval, timedelta): interval = int(interval.microseconds/1000) self.value = value self.interval = interval def __str__(self): return "%s@%s" % (self.value, self.interval) def equals(self, other): return other.interval == self.interval and other.value == self.value def get_hash_code(self): return self.value.get_hash_code() ^ self.interval.get_hash_code() def new_timer(l, t, scheduler): timer = scheduler.create_cold_observable(on_next(t, 0), on_completed(t)) l.append(timer) return timer class TestGroup_join(unittest.TestCase): def test_group_join_op_normal_i(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(210, TimeInterval(0, 10)), on_next(219, TimeInterval(1, 5)), on_next(240, TimeInterval(2, 10)), on_next(300, TimeInterval(3, 100)), on_next(310, TimeInterval(4, 80)), on_next(500, TimeInterval(5, 90)), on_next(700, TimeInterval(6, 25)), on_next(710, TimeInterval(7, 280)), on_next(720, TimeInterval(8, 100)), on_next(830, TimeInterval(9, 10)), on_completed(900) ) ys = scheduler.create_hot_observable( on_next(215, TimeInterval("hat", 20)), on_next(217, TimeInterval("bat", 1)), on_next(290, TimeInterval("wag", 200)), on_next(300, TimeInterval("pig", 10)), on_next(305, TimeInterval("cup", 50)), on_next(600, TimeInterval("yak", 90)), on_next(702, TimeInterval("tin", 20)), on_next(712, TimeInterval("man", 10)), on_next(722, TimeInterval("rat", 200)), on_next(732, TimeInterval("wig", 5)), on_completed(800) ) xsd = [] ysd = [] def create(): def mapper(x_yy): x, yy = x_yy return yy.pipe(ops.map(lambda y: '{}{}'.format(x.value, y.value))) return xs.pipe( ops.group_join( ys, lambda x: new_timer(xsd, x.interval, scheduler), lambda y: new_timer(ysd, y.interval, scheduler), ), ops.flat_map(mapper), ) res = scheduler.start(create=create) assert res.messages == [ on_next(215, "0hat"), on_next(217, "0bat"), on_next(219, "1hat"), on_next(300, "3wag"), on_next(300, "3pig"), on_next(305, "3cup"), on_next(310, "4wag"), on_next(310, "4pig"), on_next(310, "4cup"), on_next(702, "6tin"), on_next(710, "7tin"), on_next(712, "6man"), on_next(712, "7man"), on_next(720, "8tin"), on_next(720, "8man"), on_next(722, "6rat"), on_next(722, "7rat"), on_next(722, "8rat"), on_next(732, "7wig"), on_next(732, "8wig"), on_next(830, "9rat"), on_completed(990)] assert xs.subscriptions == [ subscribe(200, 900)] assert ys.subscriptions == [ subscribe(200, 800)] def test_group_join_op_normal_ii(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(210, TimeInterval(0, 10)), on_next(219, TimeInterval(1, 5)), on_next(240, TimeInterval(2, 10)), on_next(300, TimeInterval(3, 100)), on_next(310, TimeInterval(4, 80)), on_next(500, TimeInterval(5, 90)), on_next(700, TimeInterval(6, 25)), on_next(710, TimeInterval(7, 200)), on_next(720, TimeInterval(8, 100)), on_completed(721) ) ys = scheduler.create_hot_observable( on_next(215, TimeInterval("hat", (20))), on_next(217, TimeInterval("bat", (1))), on_next(290, TimeInterval("wag", (200))), on_next(300, TimeInterval("pig", (10))), on_next(305, TimeInterval("cup", (50))), on_next(600, TimeInterval("yak", (90))), on_next(702, TimeInterval("tin", (20))), on_next(712, TimeInterval("man", (10))), on_next(722, TimeInterval("rat", (200))), on_next(732, TimeInterval("wig", (5))), on_completed(990) ) xsd = [] ysd = [] def create(): def mapper(x_yy): x, yy = x_yy return yy.pipe(ops.map(lambda y: '{}{}'.format(x.value, y.value))) return xs.pipe( ops.group_join( ys, lambda x: new_timer(xsd, x.interval, scheduler), lambda y: new_timer(ysd, y.interval, scheduler), ), ops.flat_map(mapper), ) res = scheduler.start(create=create) assert res.messages == [ on_next(215, "0hat"), on_next(217, "0bat"), on_next(219, "1hat"), on_next(300, "3wag"), on_next(300, "3pig"), on_next(305, "3cup"), on_next(310, "4wag"), on_next(310, "4pig"), on_next(310, "4cup"), on_next(702, "6tin"), on_next(710, "7tin"), on_next(712, "6man"), on_next(712, "7man"), on_next(720, "8tin"), on_next(720, "8man"), on_next(722, "6rat"), on_next(722, "7rat"), on_next(722, "8rat"), on_next(732, "7wig"), on_next(732, "8wig"), on_completed(910)] assert xs.subscriptions == [ subscribe(200, 721)] assert ys.subscriptions == [ subscribe(200, 910)] def test_group_join_op_normal_iii(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(210, TimeInterval(0, 10)), on_next(219, TimeInterval(1, 5)), on_next(240, TimeInterval(2, 10)), on_next(300, TimeInterval(3, 100)), on_next(310, TimeInterval(4, 80)), on_next(500, TimeInterval(5, 90)), on_next(700, TimeInterval(6, 25)), on_next(710, TimeInterval(7, 280)), on_next(720, TimeInterval(8, 100)), on_next(830, TimeInterval(9, 10)), on_completed(900)) ys = scheduler.create_hot_observable( on_next(215, TimeInterval("hat", 20)), on_next(217, TimeInterval("bat", 1)), on_next(290, TimeInterval("wag", 200)), on_next(300, TimeInterval("pig", 10)), on_next(305, TimeInterval("cup", 50)), on_next(600, TimeInterval("yak", 90)), on_next(702, TimeInterval("tin", 20)), on_next(712, TimeInterval("man", 10)), on_next(722, TimeInterval("rat", 200)), on_next(732, TimeInterval("wig", 5)), on_completed(800)) def create(): def mapper(x_yy): x, yy = x_yy return yy.pipe(ops.map(lambda y: '{}{}'.format(x.value, y.value))) return xs.pipe( ops.group_join( ys, lambda x: rx.timer(x.interval).pipe(ops.filter(lambda _: False)), lambda y: rx.timer(y.interval).pipe(ops.filter(lambda _: False)), ), ops.flat_map(mapper), ) results = scheduler.start(create=create) assert results.messages == [ on_next(215, "0hat"), on_next(217, "0bat"), on_next(219, "1hat"), on_next(300, "3wag"), on_next(300, "3pig"), on_next(305, "3cup"), on_next(310, "4wag"), on_next(310, "4pig"), on_next(310, "4cup"), on_next(702, "6tin"), on_next(710, "7tin"), on_next(712, "6man"), on_next(712, "7man"), on_next(720, "8tin"), on_next(720, "8man"), on_next(722, "6rat"), on_next(722, "7rat"), on_next(722, "8rat"), on_next(732, "7wig"), on_next(732, "8wig"), on_next(830, "9rat"), on_completed(990)] def test_group_join_op_normal_iv(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(210, TimeInterval(0, TimeSpan.from_ticks(10))), on_next(219, TimeInterval(1, TimeSpan.from_ticks(5))), on_next(240, TimeInterval(2, TimeSpan.from_ticks(10))), on_next(300, TimeInterval(3, TimeSpan.from_ticks(100))), on_next(310, TimeInterval(4, TimeSpan.from_ticks(80))), on_next(500, TimeInterval(5, TimeSpan.from_ticks(90))), on_next(700, TimeInterval(6, TimeSpan.from_ticks(25))), on_next(710, TimeInterval(7, TimeSpan.from_ticks(200))), on_next(720, TimeInterval(8, TimeSpan.from_ticks(100))), on_completed(990)) ys = scheduler.create_hot_observable( on_next(215, TimeInterval("hat", TimeSpan.from_ticks(20))), on_next(217, TimeInterval("bat", TimeSpan.from_ticks(1))), on_next(290, TimeInterval("wag", TimeSpan.from_ticks(200))), on_next(300, TimeInterval("pig", TimeSpan.from_ticks(10))), on_next(305, TimeInterval("cup", TimeSpan.from_ticks(50))), on_next(600, TimeInterval("yak", TimeSpan.from_ticks(90))), on_next(702, TimeInterval("tin", TimeSpan.from_ticks(20))), on_next(712, TimeInterval("man", TimeSpan.from_ticks(10))), on_next(722, TimeInterval("rat", TimeSpan.from_ticks(200))), on_next(732, TimeInterval("wig", TimeSpan.from_ticks(5))), on_completed(980)) def create(): def mapper(x_yy): x, yy = x_yy return yy.pipe(ops.map(lambda y: '{}{}'.format(x.value, y.value))) return xs.pipe( ops.group_join( ys, lambda x: rx.timer(x.interval), lambda y: rx.timer(y.interval), ), ops.flat_map(mapper), ) results = scheduler.start(create=create) assert results.messages == [ on_next(215, "0hat"), on_next(217, "0bat"), on_next(219, "1hat"), on_next(300, "3wag"), on_next(300, "3pig"), on_next(305, "3cup"), on_next(310, "4wag"), on_next(310, "4pig"), on_next(310, "4cup"), on_next(702, "6tin"), on_next(710, "7tin"), on_next(712, "6man"), on_next(712, "7man"), on_next(720, "8tin"), on_next(720, "8man"), on_next(722, "6rat"), on_next(722, "7rat"), on_next(722, "8rat"), on_next(732, "7wig"), on_next(732, "8wig"), on_completed(990)] def test_group_join_op_normal_v(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(210, TimeInterval(0, TimeSpan.from_ticks(10))), on_next(219, TimeInterval(1, TimeSpan.from_ticks(5))), on_next(240, TimeInterval(2, TimeSpan.from_ticks(10))), on_next(300, TimeInterval(3, TimeSpan.from_ticks(100))), on_next(310, TimeInterval(4, TimeSpan.from_ticks(80))), on_next(500, TimeInterval(5, TimeSpan.from_ticks(90))), on_next(700, TimeInterval(6, TimeSpan.from_ticks(25))), on_next(710, TimeInterval(7, TimeSpan.from_ticks(200))), on_next(720, TimeInterval(8, TimeSpan.from_ticks(100))), on_completed(990)) ys = scheduler.create_hot_observable( on_next(215, TimeInterval("hat", TimeSpan.from_ticks(20))), on_next(217, TimeInterval("bat", TimeSpan.from_ticks(1))), on_next(290, TimeInterval("wag", TimeSpan.from_ticks(200))), on_next(300, TimeInterval("pig", TimeSpan.from_ticks(10))), on_next(305, TimeInterval("cup", TimeSpan.from_ticks(50))), on_next(600, TimeInterval("yak", TimeSpan.from_ticks(90))), on_next(702, TimeInterval("tin", TimeSpan.from_ticks(20))), on_next(712, TimeInterval("man", TimeSpan.from_ticks(10))), on_next(722, TimeInterval("rat", TimeSpan.from_ticks(200))), on_next(732, TimeInterval("wig", TimeSpan.from_ticks(5))), on_completed(900)) def create(): def mapper(x_yy): x, yy = x_yy return yy.pipe(ops.map(lambda y: '{}{}'.format(x.value, y.value))) return xs.pipe( ops.group_join( ys, lambda x: rx.timer(x.interval), lambda y: rx.timer(y.interval), ), ops.flat_map(mapper), ) results = scheduler.start(create=create) assert results.messages == [ on_next(215, "0hat"), on_next(217, "0bat"), on_next(219, "1hat"), on_next(300, "3wag"), on_next(300, "3pig"), on_next(305, "3cup"), on_next(310, "4wag"), on_next(310, "4pig"), on_next(310, "4cup"), on_next(702, "6tin"), on_next(710, "7tin"), on_next(712, "6man"), on_next(712, "7man"), on_next(720, "8tin"), on_next(720, "8man"), on_next(722, "6rat"), on_next(722, "7rat"), on_next(722, "8rat"), on_next(732, "7wig"), on_next(732, "8wig"), on_completed(990)] def test_group_join_op_normal_vi(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(210, TimeInterval(0, TimeSpan.from_ticks(10))), on_next(219, TimeInterval(1, TimeSpan.from_ticks(5))), on_next(240, TimeInterval(2, TimeSpan.from_ticks(10))), on_next(300, TimeInterval(3, TimeSpan.from_ticks(100))), on_next(310, TimeInterval(4, TimeSpan.from_ticks(80))), on_next(500, TimeInterval(5, TimeSpan.from_ticks(90))), on_next(700, TimeInterval(6, TimeSpan.from_ticks(25))), on_next(710, TimeInterval(7, TimeSpan.from_ticks(30))), on_next(720, TimeInterval(8, TimeSpan.from_ticks(200))), on_next(830, TimeInterval(9, TimeSpan.from_ticks(10))), on_completed(850)) ys = scheduler.create_hot_observable( on_next(215, TimeInterval("hat", TimeSpan.from_ticks(20))), on_next(217, TimeInterval("bat", TimeSpan.from_ticks(1))), on_next(290, TimeInterval("wag", TimeSpan.from_ticks(200))), on_next(300, TimeInterval("pig", TimeSpan.from_ticks(10))), on_next(305, TimeInterval("cup", TimeSpan.from_ticks(50))), on_next(600, TimeInterval("yak", TimeSpan.from_ticks(90))), on_next(702, TimeInterval("tin", TimeSpan.from_ticks(20))), on_next(712, TimeInterval("man", TimeSpan.from_ticks(10))), on_next(722, TimeInterval("rat", TimeSpan.from_ticks(20))), on_next(732, TimeInterval("wig", TimeSpan.from_ticks(5))), on_completed(900)) def create(): def mapper(x_yy): x, yy = x_yy return yy.pipe(ops.map(lambda y: '{}{}'.format(x.value, y.value))) return xs.pipe( ops.group_join( ys, lambda x: rx.timer(x.interval), lambda y: rx.timer(y.interval), ), ops.flat_map(mapper), ) results = scheduler.start(create=create) assert results.messages == [ on_next(215, "0hat"), on_next(217, "0bat"), on_next(219, "1hat"), on_next(300, "3wag"), on_next(300, "3pig"), on_next(305, "3cup"), on_next(310, "4wag"), on_next(310, "4pig"), on_next(310, "4cup"), on_next(702, "6tin"), on_next(710, "7tin"), on_next(712, "6man"), on_next(712, "7man"), on_next(720, "8tin"), on_next(720, "8man"), on_next(722, "6rat"), on_next(722, "7rat"), on_next(722, "8rat"), on_next(732, "7wig"), on_next(732, "8wig"), on_completed(920)] def test_group_join_op_normal_vii(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_completed(210)) ys = scheduler.create_hot_observable( on_next(215, TimeInterval("hat", TimeSpan.from_ticks(20))), on_next(217, TimeInterval("bat", TimeSpan.from_ticks(1))), on_next(290, TimeInterval("wag", TimeSpan.from_ticks(200))), on_next(300, TimeInterval("pig", TimeSpan.from_ticks(10))), on_next(305, TimeInterval("cup", TimeSpan.from_ticks(50))), on_next(600, TimeInterval("yak", TimeSpan.from_ticks(90))), on_next(702, TimeInterval("tin", TimeSpan.from_ticks(20))), on_next(712, TimeInterval("man", TimeSpan.from_ticks(10))), on_next(722, TimeInterval("rat", TimeSpan.from_ticks(20))), on_next(732, TimeInterval("wig", TimeSpan.from_ticks(5))), on_completed(900)) def create(): def mapper(x_yy): x, yy = x_yy return yy.pipe(ops.map(lambda y: '{}{}'.format(x.value, y.value))) return xs.pipe( ops.group_join( ys, lambda x: rx.timer(x.interval).pipe(ops.filter(lambda _: False)), lambda y: rx.timer(y.interval).pipe(ops.filter(lambda _: False)), ), ops.flat_map(mapper), ) results = scheduler.start(create=create) assert results.messages == [on_completed(210)] def test_group_join_op_normal_viii(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(210, TimeInterval(0, TimeSpan.from_ticks(200)))) ys = scheduler.create_hot_observable( on_next(220, TimeInterval("hat", TimeSpan.from_ticks(100))), on_completed(230)) def create(): def mapper(x_yy): x, yy = x_yy return yy.pipe(ops.map(lambda y: '{}{}'.format(x.value, y.value))) return xs.pipe( ops.group_join( ys, lambda x: rx.timer(x.interval), lambda y: rx.timer(y.interval), ), ops.flat_map(mapper), ) results = scheduler.start(create=create) assert results.messages == [on_next(220, "0hat")] def test_group_join_op_normal_ix(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(210, TimeInterval(0, TimeSpan.from_ticks(10))), on_next(219, TimeInterval(1, TimeSpan.from_ticks(5))), on_next(240, TimeInterval(2, TimeSpan.from_ticks(10))), on_next(300, TimeInterval(3, TimeSpan.from_ticks(100))), on_next(310, TimeInterval(4, TimeSpan.from_ticks(80))), on_next(500, TimeInterval(5, TimeSpan.from_ticks(90))), on_next(700, TimeInterval(6, TimeSpan.from_ticks(25))), on_next(710, TimeInterval(7, TimeSpan.from_ticks(300))), on_next(720, TimeInterval(8, TimeSpan.from_ticks(100))), on_next(830, TimeInterval(9, TimeSpan.from_ticks(10))), on_completed(900)) ys = scheduler.create_hot_observable( on_next(215, TimeInterval("hat", TimeSpan.from_ticks(20))), on_next(217, TimeInterval("bat", TimeSpan.from_ticks(1))), on_next(290, TimeInterval("wag", TimeSpan.from_ticks(200))), on_next(300, TimeInterval("pig", TimeSpan.from_ticks(10))), on_next(305, TimeInterval("cup", TimeSpan.from_ticks(50))), on_next(600, TimeInterval("yak", TimeSpan.from_ticks(90))), on_next(702, TimeInterval("tin", TimeSpan.from_ticks(20))), on_next(712, TimeInterval("man", TimeSpan.from_ticks(10))), on_next(722, TimeInterval("rat", TimeSpan.from_ticks(200))), on_next(732, TimeInterval("wig", TimeSpan.from_ticks(5))), on_completed(800)) def create(): def mapper(x_yy): x, yy = x_yy return yy.pipe(ops.map(lambda y: '{}{}'.format(x.value, y.value))) return xs.pipe( ops.group_join( ys, lambda x: rx.timer(x.interval), lambda y: rx.timer(y.interval), ), ops.flat_map(mapper), ) results = scheduler.start(create=create, disposed=713) assert results.messages == [ on_next(215, "0hat"), on_next(217, "0bat"), on_next(219, "1hat"), on_next(300, "3wag"), on_next(300, "3pig"), on_next(305, "3cup"), on_next(310, "4wag"), on_next(310, "4pig"), on_next(310, "4cup"), on_next(702, "6tin"), on_next(710, "7tin"), on_next(712, "6man"), on_next(712, "7man")] def test_group_join_op_error_i(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(210, TimeInterval(0, TimeSpan.from_ticks(10))), on_next(219, TimeInterval(1, TimeSpan.from_ticks(5))), on_next(240, TimeInterval(2, TimeSpan.from_ticks(10))), on_next(300, TimeInterval(3, TimeSpan.from_ticks(100))), on_error(310, ex)) ys = scheduler.create_hot_observable( on_next(215, TimeInterval("hat", TimeSpan.from_ticks(20))), on_next(217, TimeInterval("bat", TimeSpan.from_ticks(1))), on_next(290, TimeInterval("wag", TimeSpan.from_ticks(200))), on_next(300, TimeInterval("pig", TimeSpan.from_ticks(10))), on_next(305, TimeInterval("cup", TimeSpan.from_ticks(50))), on_next(600, TimeInterval("yak", TimeSpan.from_ticks(90))), on_next(702, TimeInterval("tin", TimeSpan.from_ticks(20))), on_next(712, TimeInterval("man", TimeSpan.from_ticks(10))), on_next(722, TimeInterval("rat", TimeSpan.from_ticks(200))), on_next(732, TimeInterval("wig", TimeSpan.from_ticks(5))), on_completed(800)) def create(): def mapper(x_yy): x, yy = x_yy return yy.pipe(ops.map(lambda y: '{}{}'.format(x.value, y.value))) return xs.pipe( ops.group_join( ys, lambda x: rx.timer(x.interval), lambda y: rx.timer(y.interval), ), ops.flat_map(mapper), ) results = scheduler.start(create=create) assert results.messages == [ on_next(215, "0hat"), on_next(217, "0bat"), on_next(219, "1hat"), on_next(300, "3wag"), on_next(300, "3pig"), on_next(305, "3cup"), on_error(310, ex)] def test_group_join_op_error_ii(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(210, TimeInterval(0, TimeSpan.from_ticks(10))), on_next(219, TimeInterval(1, TimeSpan.from_ticks(5))), on_next(240, TimeInterval(2, TimeSpan.from_ticks(10))), on_next(300, TimeInterval(3, TimeSpan.from_ticks(100))), on_next(310, TimeInterval(4, TimeSpan.from_ticks(80))), on_next(500, TimeInterval(5, TimeSpan.from_ticks(90))), on_next(700, TimeInterval(6, TimeSpan.from_ticks(25))), on_next(710, TimeInterval(7, TimeSpan.from_ticks(300))), on_next(720, TimeInterval(8, TimeSpan.from_ticks(100))), on_next(830, TimeInterval(9, TimeSpan.from_ticks(10))), on_completed(900)) ys = scheduler.create_hot_observable( on_next(215, TimeInterval("hat", TimeSpan.from_ticks(20))), on_next(217, TimeInterval("bat", TimeSpan.from_ticks(1))), on_next(290, TimeInterval("wag", TimeSpan.from_ticks(200))), on_next(300, TimeInterval("pig", TimeSpan.from_ticks(10))), on_next(305, TimeInterval("cup", TimeSpan.from_ticks(50))), on_next(600, TimeInterval("yak", TimeSpan.from_ticks(90))), on_next(702, TimeInterval("tin", TimeSpan.from_ticks(20))), on_next(712, TimeInterval("man", TimeSpan.from_ticks(10))), on_error(722, ex)) def create(): def mapper(x_yy): x, yy = x_yy return yy.pipe(ops.map(lambda y: '{}{}'.format(x.value, y.value))) return xs.pipe( ops.group_join( ys, lambda x: rx.timer(x.interval), lambda y: rx.timer(y.interval), ), ops.flat_map(mapper), ) results = scheduler.start(create=create) assert results.messages == [ on_next(215, "0hat"), on_next(217, "0bat"), on_next(219, "1hat"), on_next(300, "3wag"), on_next(300, "3pig"), on_next(305, "3cup"), on_next(310, "4wag"), on_next(310, "4pig"), on_next(310, "4cup"), on_next(702, "6tin"), on_next(710, "7tin"), on_next(712, "6man"), on_next(712, "7man"), on_next(720, "8tin"), on_next(720, "8man"), on_error(722, ex)] def test_group_join_op_error_iii(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(210, TimeInterval(0, TimeSpan.from_ticks(10))), on_next(219, TimeInterval(1, TimeSpan.from_ticks(5))), on_next(240, TimeInterval(2, TimeSpan.from_ticks(10))), on_next(300, TimeInterval(3, TimeSpan.from_ticks(100))), on_next(310, TimeInterval(4, TimeSpan.from_ticks(80))), on_next(500, TimeInterval(5, TimeSpan.from_ticks(90))), on_next(700, TimeInterval(6, TimeSpan.from_ticks(25))), on_next(710, TimeInterval(7, TimeSpan.from_ticks(300))), on_next(720, TimeInterval(8, TimeSpan.from_ticks(100))), on_next(830, TimeInterval(9, TimeSpan.from_ticks(10))), on_completed(900)) ys = scheduler.create_hot_observable( on_next(215, TimeInterval("hat", TimeSpan.from_ticks(20))), on_next(217, TimeInterval("bat", TimeSpan.from_ticks(1))), on_next(290, TimeInterval("wag", TimeSpan.from_ticks(200))), on_next(300, TimeInterval("pig", TimeSpan.from_ticks(10))), on_next(305, TimeInterval("cup", TimeSpan.from_ticks(50))), on_next(600, TimeInterval("yak", TimeSpan.from_ticks(90))), on_next(702, TimeInterval("tin", TimeSpan.from_ticks(20))), on_next(712, TimeInterval("man", TimeSpan.from_ticks(10))), on_next(722, TimeInterval("rat", TimeSpan.from_ticks(200))), on_next(732, TimeInterval("wig", TimeSpan.from_ticks(5))), on_completed(800)) def create(): def mapper(x_yy): x, yy = x_yy return yy.pipe(ops.map(lambda y: '{}{}'.format(x.value, y.value))) return xs.pipe( ops.group_join( ys, lambda x: rx.timer(x.interval).pipe( ops.flat_map(rx.throw(ex) if x.value == 6 else rx.empty())), lambda y: rx.timer(y.interval), ), ops.flat_map(mapper), ) results = scheduler.start(create=create) assert results.messages == [ on_next(215, "0hat"), on_next(217, "0bat"), on_next(219, "1hat"), on_next(300, "3wag"), on_next(300, "3pig"), on_next(305, "3cup"), on_next(310, "4wag"), on_next(310, "4pig"), on_next(310, "4cup"), on_next(702, "6tin"), on_next(710, "7tin"), on_next(712, "6man"), on_next(712, "7man"), on_next(720, "8tin"), on_next(720, "8man"), on_next(722, "6rat"), on_next(722, "7rat"), on_next(722, "8rat"), on_error(725, ex)] def test_group_join_op_error_iv(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(210, TimeInterval(0, TimeSpan.from_ticks(10))), on_next(219, TimeInterval(1, TimeSpan.from_ticks(5))), on_next(240, TimeInterval(2, TimeSpan.from_ticks(10))), on_next(300, TimeInterval(3, TimeSpan.from_ticks(100))), on_next(310, TimeInterval(4, TimeSpan.from_ticks(80))), on_next(500, TimeInterval(5, TimeSpan.from_ticks(90))), on_next(700, TimeInterval(6, TimeSpan.from_ticks(25))), on_next(710, TimeInterval(7, TimeSpan.from_ticks(300))), on_next(720, TimeInterval(8, TimeSpan.from_ticks(100))), on_next(830, TimeInterval(9, TimeSpan.from_ticks(10))), on_completed(900)) ys = scheduler.create_hot_observable( on_next(215, TimeInterval("hat", TimeSpan.from_ticks(20))), on_next(217, TimeInterval("bat", TimeSpan.from_ticks(1))), on_next(290, TimeInterval("wag", TimeSpan.from_ticks(200))), on_next(300, TimeInterval("pig", TimeSpan.from_ticks(10))), on_next(305, TimeInterval("cup", TimeSpan.from_ticks(50))), on_next(600, TimeInterval("yak", TimeSpan.from_ticks(90))), on_next(702, TimeInterval("tin", TimeSpan.from_ticks(19))), on_next(712, TimeInterval("man", TimeSpan.from_ticks(10))), on_next(722, TimeInterval("rat", TimeSpan.from_ticks(200))), on_next(732, TimeInterval("wig", TimeSpan.from_ticks(5))), on_completed(800)) def create(): def mapper(x_yy): x, yy = x_yy return yy.pipe(ops.map(lambda y: '{}{}'.format(x.value, y.value))) return xs.pipe( ops.group_join( ys, lambda x: rx.timer(x.interval), lambda y: rx.timer(y.interval).pipe( ops.flat_map(rx.throw(ex) if y.value == "tin" else rx.empty())), ), ops.flat_map(mapper), ) results = scheduler.start(create=create) assert results.messages == [ on_next(215, "0hat"), on_next(217, "0bat"), on_next(219, "1hat"), on_next(300, "3wag"), on_next(300, "3pig"), on_next(305, "3cup"), on_next(310, "4wag"), on_next(310, "4pig"), on_next(310, "4cup"), on_next(702, "6tin"), on_next(710, "7tin"), on_next(712, "6man"), on_next(712, "7man"), on_next(720, "8tin"), on_next(720, "8man"), on_error(721, ex)] def test_group_join_op_error_v(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(210, TimeInterval(0, TimeSpan.from_ticks(10))), on_next(219, TimeInterval(1, TimeSpan.from_ticks(5))), on_next(240, TimeInterval(2, TimeSpan.from_ticks(10))), on_next(300, TimeInterval(3, TimeSpan.from_ticks(100))), on_next(310, TimeInterval(4, TimeSpan.from_ticks(80))), on_next(500, TimeInterval(5, TimeSpan.from_ticks(90))), on_next(700, TimeInterval(6, TimeSpan.from_ticks(25))), on_next(710, TimeInterval(7, TimeSpan.from_ticks(300))), on_next(720, TimeInterval(8, TimeSpan.from_ticks(100))), on_next(830, TimeInterval(9, TimeSpan.from_ticks(10))), on_completed(900)) ys = scheduler.create_hot_observable( on_next(215, TimeInterval("hat", TimeSpan.from_ticks(20))), on_next(217, TimeInterval("bat", TimeSpan.from_ticks(1))), on_next(290, TimeInterval("wag", TimeSpan.from_ticks(200))), on_next(300, TimeInterval("pig", TimeSpan.from_ticks(10))), on_next(305, TimeInterval("cup", TimeSpan.from_ticks(50))), on_next(600, TimeInterval("yak", TimeSpan.from_ticks(90))), on_next(702, TimeInterval("tin", TimeSpan.from_ticks(20))), on_next(712, TimeInterval("man", TimeSpan.from_ticks(10))), on_next(722, TimeInterval("rat", TimeSpan.from_ticks(200))), on_next(732, TimeInterval("wig", TimeSpan.from_ticks(5))), on_completed(800)) def create(): def left_duration_mapper(x): if x.value >= 0: raise Exception(ex) else: return rx.empty() def mapper(x_yy): x, yy = x_yy return yy.pipe(ops.map(lambda y: '{}{}'.format(x.value, y.value))) return xs.pipe( ops.group_join( ys, left_duration_mapper, lambda y: rx.timer(y.interval), ), ops.flat_map(mapper), ) results = scheduler.start(create=create) assert results.messages == [on_error(210, ex)] def test_group_join_op_error_vi(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(210, TimeInterval(0, TimeSpan.from_ticks(10))), on_next(219, TimeInterval(1, TimeSpan.from_ticks(5))), on_next(240, TimeInterval(2, TimeSpan.from_ticks(10))), on_next(300, TimeInterval(3, TimeSpan.from_ticks(100))), on_next(310, TimeInterval(4, TimeSpan.from_ticks(80))), on_next(500, TimeInterval(5, TimeSpan.from_ticks(90))), on_next(700, TimeInterval(6, TimeSpan.from_ticks(25))), on_next(710, TimeInterval(7, TimeSpan.from_ticks(300))), on_next(720, TimeInterval(8, TimeSpan.from_ticks(100))), on_next(830, TimeInterval(9, TimeSpan.from_ticks(10))), on_completed(900) ) ys = scheduler.create_hot_observable( on_next(215, TimeInterval("hat", TimeSpan.from_ticks(20))), on_next(217, TimeInterval("bat", TimeSpan.from_ticks(1))), on_next(290, TimeInterval("wag", TimeSpan.from_ticks(200))), on_next(300, TimeInterval("pig", TimeSpan.from_ticks(10))), on_next(305, TimeInterval("cup", TimeSpan.from_ticks(50))), on_next(600, TimeInterval("yak", TimeSpan.from_ticks(90))), on_next(702, TimeInterval("tin", TimeSpan.from_ticks(20))), on_next(712, TimeInterval("man", TimeSpan.from_ticks(10))), on_next(722, TimeInterval("rat", TimeSpan.from_ticks(200))), on_next(732, TimeInterval("wig", TimeSpan.from_ticks(5))), on_completed(800) ) def create(): def right_duration_mapper(y): if len(y.value) >= 0: raise Exception(ex) else: return rx.empty() def mapper(x_yy): x, yy = x_yy return yy.pipe(ops.map(lambda y: '{}{}'.format(x.value, y.value))) return xs.pipe( ops.group_join( ys, lambda x: rx.timer(x.interval), right_duration_mapper, ), ops.flat_map(mapper), ) results = scheduler.start(create=create) assert results.messages == [on_error(215, ex)] if __name__ == '__main__': unittest.main() RxPY-3.2.0/tests/test_observable/test_ifthen.py000066400000000000000000000120111404130727200215640ustar00rootroot00000000000000import unittest import rx from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class TestIf_then(unittest.TestCase): def test_if_true(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(210, 1), on_next(250, 2), on_completed(300)) ys = scheduler.create_hot_observable(on_next(310, 3), on_next(350, 4), on_completed(400)) def create(): return rx.if_then(lambda: True, xs, ys) results = scheduler.start(create=create) assert results.messages == [on_next(210, 1), on_next(250, 2), on_completed(300)] assert xs.subscriptions == [subscribe(200, 300)] assert ys.subscriptions == [] def test_if_false(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(210, 1), on_next(250, 2), on_completed(300)) ys = scheduler.create_hot_observable(on_next(310, 3), on_next(350, 4), on_completed(400)) def create(): return rx.if_then(lambda: False, xs, ys) results = scheduler.start(create=create) assert results.messages == [on_next(310, 3), on_next(350, 4), on_completed(400)] assert xs.subscriptions == [] assert ys.subscriptions == [subscribe(200, 400)] def test_if_on_error(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(210, 1), on_next(250, 2), on_completed(300)) ys = scheduler.create_hot_observable(on_next(310, 3), on_next(350, 4), on_completed(400)) def create(): def condition(): raise Exception(ex) return rx.if_then(condition, xs, ys) results = scheduler.start(create=create) assert results.messages == [on_error(200, ex)] assert xs.subscriptions == [] assert ys.subscriptions == [] def test_if_dispose(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(210, 1), on_next(250, 2)) ys = scheduler.create_hot_observable(on_next(310, 3), on_next(350, 4), on_completed(400)) def create(): return rx.if_then(lambda: True, xs, ys) results = scheduler.start(create=create) assert results.messages == [on_next(210, 1), on_next(250, 2)] assert xs.subscriptions == [subscribe(200, 1000)] assert ys.subscriptions == [] def test_if_default_completed(self): b = [False] scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(110, 1), on_next(220, 2), on_next(330, 3), on_completed(440)) def action(scheduler, state): b[0] = True scheduler.schedule_absolute(150, action) def create(): def condition(): return b[0] return rx.if_then(condition, xs) results = scheduler.start(create) assert results.messages == [on_next(220, 2), on_next(330, 3), on_completed(440)] assert xs.subscriptions == [subscribe(200, 440)] def test_if_default_error(self): ex = 'ex' b = [False] scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(110, 1), on_next(220, 2), on_next(330, 3), on_error(440, ex)) def action(scheduler, state): b[0] = True scheduler.schedule_absolute(150, action) def create(): def condition(): return b[0] return rx.if_then(condition, xs) results = scheduler.start(create) assert results.messages == [on_next(220, 2), on_next(330, 3), on_error(440, ex)] assert xs.subscriptions == [subscribe(200, 440)] def test_if_default_never(self): b = [False] scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(110, 1), on_next(220, 2), on_next(330, 3)) def action(scheduler, state): b[0] = True scheduler.schedule_absolute(150, action) def create(): def condition(): return b[0] return rx.if_then(condition, xs) results = scheduler.start(create) assert results.messages == [on_next(220, 2), on_next(330, 3)] assert xs.subscriptions == [subscribe(200, 1000)] def test_if_default_other(self): b = [True] scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(110, 1), on_next(220, 2), on_next(330, 3), on_error(440, 'ex')) def action(scheduler, state): b[0] = False scheduler.schedule_absolute(150, action) def create(): def condition(): return b[0] return rx.if_then(condition, xs) results = scheduler.start(create) assert results.messages == [on_completed(200)] assert xs.subscriptions == [] RxPY-3.2.0/tests/test_observable/test_ignoreelements.py000066400000000000000000000037431404130727200233430ustar00rootroot00000000000000import unittest from rx import operators as ops from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class TestIgnoreElements(unittest.TestCase): def test_ignore_values_basic(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(180, 1), on_next(210, 2), on_next(250, 3), on_next(270, 4), on_next(310, 5), on_next(360, 6), on_next(380, 7), on_next(410, 8), on_next(590, 9)) results = scheduler.start(create=lambda: xs.pipe(ops.ignore_elements())) assert results.messages == [] assert xs.subscriptions == [subscribe(200, 1000)] def test_ignore_values_completed(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(180, 1), on_next(210, 2), on_next(250, 3), on_next(270, 4), on_next(310, 5), on_next(360, 6), on_next(380, 7), on_next(410, 8), on_next(590, 9), on_completed(610)) results = scheduler.start(create=lambda: xs.pipe(ops.ignore_elements())) assert results.messages == [on_completed(610)] assert xs.subscriptions == [subscribe(200, 610)] def test_ignore_values_error(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(180, 1), on_next(210, 2), on_next(250, 3), on_next(270, 4), on_next(310, 5), on_next(360, 6), on_next(380, 7), on_next(410, 8), on_next(590, 9), on_error(610, ex)) results = scheduler.start(create=lambda: xs.pipe(ops.ignore_elements())) assert results.messages == [on_error(610, ex)] assert xs.subscriptions == [subscribe(200, 610)] RxPY-3.2.0/tests/test_observable/test_interval.py000066400000000000000000000043311404130727200221410ustar00rootroot00000000000000import unittest import rx from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class RxException(Exception): pass # Helper function for raising exceptions within lambdas def _raise(ex): raise RxException(ex) class TestTimeInterval(unittest.TestCase): def test_interval_timespan_basic(self): scheduler = TestScheduler() def create(): return rx.interval(100) results = scheduler.start(create) assert results.messages == [ on_next(300, 0), on_next(400, 1), on_next(500, 2), on_next(600, 3), on_next(700, 4), on_next(800, 5), on_next(900, 6)] #def test_interval_timespan_zero(self): # scheduler = TestScheduler() # def create(): # return rx.interval(0) # results = scheduler.start(create, disposed=210) # assert results.messages == [ # on_next(201, 0), on_next(202, 1), on_next(203, 2), # on_next(204, 3), on_next(205, 4), on_next(206, 5), # on_next(207, 6), on_next(208, 7), on_next(209, 8)] #def test_interval_timespan_negative(self): # scheduler = TestScheduler() # def create(): # return rx.interval(-1) # results = scheduler.start(create, disposed=210) # assert results.messages == [ # on_next(201, 0), on_next(202, 1), on_next(203, 2), # on_next(204, 3), on_next(205, 4), on_next(206, 5), # on_next(207, 6), on_next(208, 7), on_next(209, 8)] def test_interval_timespan_disposed(self): scheduler = TestScheduler() def create(): return rx.interval(1000) results = scheduler.start(create) assert results.messages == [] def test_interval_timespan_observer_throws(self): scheduler = TestScheduler() xs = rx.interval(1) xs.subscribe(lambda x: _raise("ex"), scheduler=scheduler) with self.assertRaises(RxException): scheduler.start() RxPY-3.2.0/tests/test_observable/test_isempty.py000066400000000000000000000035721404130727200220150ustar00rootroot00000000000000import unittest from rx import operators as ops from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class TestIsEmpty(unittest.TestCase): def test_is_empty_empty(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_completed(250)) def create(): return xs.pipe(ops.is_empty()) res = scheduler.start(create=create).messages assert res == [on_next(250, True), on_completed(250)] assert xs.subscriptions == [subscribe(200, 250)] def test_is_empty_return(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_next(210, 2), on_completed(250)) def create(): return xs.pipe(ops.is_empty()) res = scheduler.start(create=create).messages assert res == [on_next(210, False), on_completed(210)] assert xs.subscriptions == [subscribe(200, 210)] def test_is_empty_on_error(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_error(210, ex)) def create(): return xs.pipe(ops.is_empty()) res = scheduler.start(create=create).messages assert res == [on_error(210, ex)] assert xs.subscriptions == [subscribe(200, 210)] def test_is_empty_never(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1)) def create(): return xs.pipe(ops.is_empty()) res = scheduler.start(create=create).messages assert res == [] assert xs.subscriptions == [subscribe(200, 1000)] RxPY-3.2.0/tests/test_observable/test_join.py000066400000000000000000001004051404130727200212530ustar00rootroot00000000000000import unittest from datetime import timedelta import rx from rx import operators as ops from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class TimeSpan(object): def from_ticks(self, value): return value class TimeInterval(object): def __init__(self, value, interval): if isinstance(interval, timedelta): interval = int(interval.microseconds/1000) self.value = value self.interval = interval def __str__(self): return "%s@%s" % (self.value, self.interval) def equals(self, other): return other.interval == self.interval and other.value == self.value def get_hash_code(self): return self.value.get_hash_code() ^ self.interval.get_hash_code() class TestJoin(unittest.TestCase): def test_join_op_normal_i(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(210, TimeInterval(0, 10)), on_next(219, TimeInterval(1, 5)), on_next(240, TimeInterval(2, 10)), on_next(300, TimeInterval(3, 100)), on_next(310, TimeInterval(4, 80)), on_next(500, TimeInterval(5, 90)), on_next(700, TimeInterval(6, 25)), on_next(710, TimeInterval(7, 300)), on_next(720, TimeInterval(8, 100)), on_next(830, TimeInterval(9, 10)), on_completed(900)) ys = scheduler.create_hot_observable( on_next(215, TimeInterval("hat", 20)), on_next(217, TimeInterval("bat", 1)), on_next(290, TimeInterval("wag", 200)), on_next(300, TimeInterval("pig", 10)), on_next(305, TimeInterval("cup", 50)), on_next(600, TimeInterval("yak", 90)), on_next(702, TimeInterval("tin", 20)), on_next(712, TimeInterval("man", 10)), on_next(722, TimeInterval("rat", 200)), on_next(732, TimeInterval("wig", 5)), on_completed(800)) def create(): def mapper(xy): x, y = xy return "{}{}".format(x.value, y.value) return xs.pipe( ops.join( ys, lambda x: rx.timer(x.interval), lambda y: rx.timer(y.interval), ), ops.map(mapper), ) results = scheduler.start(create=create) assert results.messages == [ on_next(215, "0hat"), on_next(217, "0bat"), on_next(219, "1hat"), on_next(300, "3wag"), on_next(300, "3pig"), on_next(305, "3cup"), on_next(310, "4wag"), on_next(310, "4pig"), on_next(310, "4cup"), on_next(702, "6tin"), on_next(710, "7tin"), on_next(712, "6man"), on_next(712, "7man"), on_next(720, "8tin"), on_next(720, "8man"), on_next(722, "6rat"), on_next(722, "7rat"), on_next(722, "8rat"), on_next(732, "7wig"), on_next(732, "8wig"), on_next(830, "9rat"), on_completed(900)] def test_join_op_normal_ii(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(210, TimeInterval(0, 10)), on_next(219, TimeInterval(1, 5)), on_next(240, TimeInterval(2, 10)), on_next(300, TimeInterval(3, 100)), on_next(310, TimeInterval(4, 80)), on_next(500, TimeInterval(5, 90)), on_next(700, TimeInterval(6, 25)), on_next(710, TimeInterval(7, 200)), on_next(720, TimeInterval(8, 100)), on_completed(721)) ys = scheduler.create_hot_observable( on_next(215, TimeInterval("hat", 20)), on_next(217, TimeInterval("bat", 1)), on_next(290, TimeInterval("wag", 200)), on_next(300, TimeInterval("pig", 10)), on_next(305, TimeInterval("cup", 50)), on_next(600, TimeInterval("yak", 90)), on_next(702, TimeInterval("tin", 20)), on_next(712, TimeInterval("man", 10)), on_next(722, TimeInterval("rat", 200)), on_next(732, TimeInterval("wig", 5)), on_completed(990)) def create(): def mapper(xy): x, y = xy return "{}{}".format(x.value, y.value) return xs.pipe( ops.join( ys, lambda x: rx.timer(x.interval), lambda y: rx.timer(y.interval), ), ops.map(mapper), ) results = scheduler.start(create=create) assert results.messages == [ on_next(215, "0hat"), on_next(217, "0bat"), on_next(219, "1hat"), on_next(300, "3wag"), on_next(300, "3pig"), on_next(305, "3cup"), on_next(310, "4wag"), on_next(310, "4pig"), on_next(310, "4cup"), on_next(702, "6tin"), on_next(710, "7tin"), on_next(712, "6man"), on_next(712, "7man"), on_next(720, "8tin"), on_next(720, "8man"), on_next(722, "6rat"), on_next(722, "7rat"), on_next(722, "8rat"), on_next(732, "7wig"), on_next(732, "8wig"), on_completed(910)] def test_join_op_normal_iii(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(210, TimeInterval(0, 10)), on_next(219, TimeInterval(1, 5)), on_next(240, TimeInterval(2, 10)), on_next(300, TimeInterval(3, 100)), on_next(310, TimeInterval(4, 80)), on_next(500, TimeInterval(5, 90)), on_next(700, TimeInterval(6, 25)), on_next(710, TimeInterval(7, 300)), on_next(720, TimeInterval(8, 100)), on_next(830, TimeInterval(9, 10)), on_completed(900)) ys = scheduler.create_hot_observable( on_next(215, TimeInterval("hat", 20)), on_next(217, TimeInterval("bat", 1)), on_next(290, TimeInterval("wag", 200)), on_next(300, TimeInterval("pig", 10)), on_next(305, TimeInterval("cup", 50)), on_next(600, TimeInterval("yak", 90)), on_next(702, TimeInterval("tin", 20)), on_next(712, TimeInterval("man", 10)), on_next(722, TimeInterval("rat", 200)), on_next(732, TimeInterval("wig", 5)), on_completed(800)) def create(): def mapper(xy): x, y = xy return "{}{}".format(x.value, y.value) return xs.pipe( ops.join( ys, lambda x: rx.timer(x.interval).pipe(ops.filter(lambda _: False)), lambda y: rx.timer(y.interval).pipe(ops.filter(lambda _: False)), ), ops.map(mapper), ) results = scheduler.start(create=create) assert results.messages == [ on_next(215, "0hat"), on_next(217, "0bat"), on_next(219, "1hat"), on_next(300, "3wag"), on_next(300, "3pig"), on_next(305, "3cup"), on_next(310, "4wag"), on_next(310, "4pig"), on_next(310, "4cup"), on_next(702, "6tin"), on_next(710, "7tin"), on_next(712, "6man"), on_next(712, "7man"), on_next(720, "8tin"), on_next(720, "8man"), on_next(722, "6rat"), on_next(722, "7rat"), on_next(722, "8rat"), on_next(732, "7wig"), on_next(732, "8wig"), on_next(830, "9rat"), on_completed(900)] def test_join_op_normal_iv(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(210, TimeInterval(0, 10)), on_next(219, TimeInterval(1, 5)), on_next(240, TimeInterval(2, 10)), on_next(300, TimeInterval(3, 100)), on_next(310, TimeInterval(4, 80)), on_next(500, TimeInterval(5, 90)), on_next(700, TimeInterval(6, 25)), on_next(710, TimeInterval(7, 200)), on_next(720, TimeInterval(8, 100)), on_completed(990)) ys = scheduler.create_hot_observable( on_next(215, TimeInterval("hat", 20)), on_next(217, TimeInterval("bat", 1)), on_next(290, TimeInterval("wag", 200)), on_next(300, TimeInterval("pig", 10)), on_next(305, TimeInterval("cup", 50)), on_next(600, TimeInterval("yak", 90)), on_next(702, TimeInterval("tin", 20)), on_next(712, TimeInterval("man", 10)), on_next(722, TimeInterval("rat", 200)), on_next(732, TimeInterval("wig", 5)), on_completed(980)) def create(): def mapper(xy): x, y = xy return "{}{}".format(x.value, y.value) return xs.pipe( ops.join( ys, lambda x: rx.timer(x.interval), lambda y: rx.timer(y.interval), ), ops.map(mapper), ) results = scheduler.start(create=create) assert results.messages == [ on_next(215, "0hat"), on_next(217, "0bat"), on_next(219, "1hat"), on_next(300, "3wag"), on_next(300, "3pig"), on_next(305, "3cup"), on_next(310, "4wag"), on_next(310, "4pig"), on_next(310, "4cup"), on_next(702, "6tin"), on_next(710, "7tin"), on_next(712, "6man"), on_next(712, "7man"), on_next(720, "8tin"), on_next(720, "8man"), on_next(722, "6rat"), on_next(722, "7rat"), on_next(722, "8rat"), on_next(732, "7wig"), on_next(732, "8wig"), on_completed(980)] def test_join_op_normal_v(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(210, TimeInterval(0, 10)), on_next(219, TimeInterval(1, 5)), on_next(240, TimeInterval(2, 10)), on_next(300, TimeInterval(3, 100)), on_next(310, TimeInterval(4, 80)), on_next(500, TimeInterval(5, 90)), on_next(700, TimeInterval(6, 25)), on_next(710, TimeInterval(7, 200)), on_next(720, TimeInterval(8, 100)), on_completed(990)) ys = scheduler.create_hot_observable( on_next(215, TimeInterval("hat", 20)), on_next(217, TimeInterval("bat", 1)), on_next(290, TimeInterval("wag", 200)), on_next(300, TimeInterval("pig", 10)), on_next(305, TimeInterval("cup", 50)), on_next(600, TimeInterval("yak", 90)), on_next(702, TimeInterval("tin", 20)), on_next(712, TimeInterval("man", 10)), on_next(722, TimeInterval("rat", 200)), on_next(732, TimeInterval("wig", 5)), on_completed(900)) def create(): def mapper(xy): x, y = xy return "{}{}".format(x.value, y.value) return xs.pipe( ops.join( ys, lambda x: rx.timer(x.interval), lambda y: rx.timer(y.interval), ), ops.map(mapper), ) results = scheduler.start(create=create) assert results.messages == [ on_next(215, "0hat"), on_next(217, "0bat"), on_next(219, "1hat"), on_next(300, "3wag"), on_next(300, "3pig"), on_next(305, "3cup"), on_next(310, "4wag"), on_next(310, "4pig"), on_next(310, "4cup"), on_next(702, "6tin"), on_next(710, "7tin"), on_next(712, "6man"), on_next(712, "7man"), on_next(720, "8tin"), on_next(720, "8man"), on_next(722, "6rat"), on_next(722, "7rat"), on_next(722, "8rat"), on_next(732, "7wig"), on_next(732, "8wig"), on_completed(922)] def test_join_op_normal_vi(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(210, TimeInterval(0, 10)), on_next(219, TimeInterval(1, 5)), on_next(240, TimeInterval(2, 10)), on_next(300, TimeInterval(3, 100)), on_next(310, TimeInterval(4, 80)), on_next(500, TimeInterval(5, 90)), on_next(700, TimeInterval(6, 25)), on_next(710, TimeInterval(7, 30)), on_next(720, TimeInterval(8, 200)), on_next(830, TimeInterval(9, 10)), on_completed(850)) ys = scheduler.create_hot_observable( on_next(215, TimeInterval("hat", 20)), on_next(217, TimeInterval("bat", 1)), on_next(290, TimeInterval("wag", 200)), on_next(300, TimeInterval("pig", 10)), on_next(305, TimeInterval("cup", 50)), on_next(600, TimeInterval("yak", 90)), on_next(702, TimeInterval("tin", 20)), on_next(712, TimeInterval("man", 10)), on_next(722, TimeInterval("rat", 20)), on_next(732, TimeInterval("wig", 5)), on_completed(900)) def create(): def mapper(xy): x, y = xy return "{}{}".format(x.value, y.value) return xs.pipe( ops.join( ys, lambda x: rx.timer(x.interval), lambda y: rx.timer(y.interval), ), ops.map(mapper), ) results = scheduler.start(create=create) assert results.messages == [ on_next(215, "0hat"), on_next(217, "0bat"), on_next(219, "1hat"), on_next(300, "3wag"), on_next(300, "3pig"), on_next(305, "3cup"), on_next(310, "4wag"), on_next(310, "4pig"), on_next(310, "4cup"), on_next(702, "6tin"), on_next(710, "7tin"), on_next(712, "6man"), on_next(712, "7man"), on_next(720, "8tin"), on_next(720, "8man"), on_next(722, "6rat"), on_next(722, "7rat"), on_next(722, "8rat"), on_next(732, "7wig"), on_next(732, "8wig"), on_completed(900)] def test_join_op_normal_vii(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(210, TimeInterval(0, 10)), on_next(219, TimeInterval(1, 5)), on_next(240, TimeInterval(2, 10)), on_next(300, TimeInterval(3, 100)), on_next(310, TimeInterval(4, 80)), on_next(500, TimeInterval(5, 90)), on_next(700, TimeInterval(6, 25)), on_next(710, TimeInterval(7, 300)), on_next(720, TimeInterval(8, 100)), on_next(830, TimeInterval(9, 10)), on_completed(900)) ys = scheduler.create_hot_observable( on_next(215, TimeInterval("hat", 20)), on_next(217, TimeInterval("bat", 1)), on_next(290, TimeInterval("wag", 200)), on_next(300, TimeInterval("pig", 10)), on_next(305, TimeInterval("cup", 50)), on_next(600, TimeInterval("yak", 90)), on_next(702, TimeInterval("tin", 20)), on_next(712, TimeInterval("man", 10)), on_next(722, TimeInterval("rat", 200)), on_next(732, TimeInterval("wig", 5)), on_completed(800)) def create(): def mapper(xy): x, y = xy return "{}{}".format(x.value, y.value) return xs.pipe( ops.join( ys, lambda x: rx.timer(x.interval), lambda y: rx.timer(y.interval), ), ops.map(mapper), ) results = scheduler.start(create, disposed=713) assert results.messages == [ on_next(215, "0hat"), on_next(217, "0bat"), on_next(219, "1hat"), on_next(300, "3wag"), on_next(300, "3pig"), on_next(305, "3cup"), on_next(310, "4wag"), on_next(310, "4pig"), on_next(310, "4cup"), on_next(702, "6tin"), on_next(710, "7tin"), on_next(712, "6man"), on_next(712, "7man")] def test_join_op_error_i(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(210, TimeInterval(0, 10)), on_next(219, TimeInterval(1, 5)), on_next(240, TimeInterval(2, 10)), on_next(300, TimeInterval(3, 100)), on_error(310, ex)) ys = scheduler.create_hot_observable( on_next(215, TimeInterval("hat", 20)), on_next(217, TimeInterval("bat", 1)), on_next(290, TimeInterval("wag", 200)), on_next(300, TimeInterval("pig", 10)), on_next(305, TimeInterval("cup", 50)), on_next(600, TimeInterval("yak", 90)), on_next(702, TimeInterval("tin", 20)), on_next(712, TimeInterval("man", 10)), on_next(722, TimeInterval("rat", 200)), on_next(732, TimeInterval("wig", 5)), on_completed(800)) def create(): def mapper(xy): x, y = xy return "{}{}".format(x.value, y.value) return xs.pipe( ops.join( ys, lambda x: rx.timer(x.interval), lambda y: rx.timer(y.interval), ), ops.map(mapper), ) results = scheduler.start(create=create, disposed=713) assert results.messages == [ on_next(215, "0hat"), on_next(217, "0bat"), on_next(219, "1hat"), on_next(300, "3wag"), on_next(300, "3pig"), on_next(305, "3cup"), on_error(310, ex)] def test_join_op_error_ii(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(210, TimeInterval(0, 10)), on_next(219, TimeInterval(1, 5)), on_next(240, TimeInterval(2, 10)), on_next(300, TimeInterval(3, 100)), on_next(310, TimeInterval(4, 80)), on_next(500, TimeInterval(5, 90)), on_next(700, TimeInterval(6, 25)), on_next(710, TimeInterval(7, 300)), on_next(720, TimeInterval(8, 100)), on_next(830, TimeInterval(9, 10)), on_completed(900)) ys = scheduler.create_hot_observable( on_next(215, TimeInterval("hat", 20)), on_next(217, TimeInterval("bat", 1)), on_next(290, TimeInterval("wag", 200)), on_next(300, TimeInterval("pig", 10)), on_next(305, TimeInterval("cup", 50)), on_next(600, TimeInterval("yak", 90)), on_next(702, TimeInterval("tin", 20)), on_next(712, TimeInterval("man", 10)), on_error(722, ex)) def create(): def mapper(xy): x, y = xy return "{}{}".format(x.value, y.value) return xs.pipe( ops.join( ys, lambda x: rx.timer(x.interval), lambda y: rx.timer(y.interval), ), ops.map(mapper), ) results = scheduler.start(create=create) assert results.messages == [ on_next(215, "0hat"), on_next(217, "0bat"), on_next(219, "1hat"), on_next(300, "3wag"), on_next(300, "3pig"), on_next(305, "3cup"), on_next(310, "4wag"), on_next(310, "4pig"), on_next(310, "4cup"), on_next(702, "6tin"), on_next(710, "7tin"), on_next(712, "6man"), on_next(712, "7man"), on_next(720, "8tin"), on_next(720, "8man"), on_error(722, ex)] def test_join_op_error_iii(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(210, TimeInterval(0, 10)), on_next(219, TimeInterval(1, 5)), on_next(240, TimeInterval(2, 10)), on_next(300, TimeInterval(3, 100)), on_next(310, TimeInterval(4, 80)), on_next(500, TimeInterval(5, 90)), on_next(700, TimeInterval(6, 25)), on_next(710, TimeInterval(7, 300)), on_next(720, TimeInterval(8, 100)), on_next(830, TimeInterval(9, 10)), on_completed(900)) ys = scheduler.create_hot_observable( on_next(215, TimeInterval("hat", 20)), on_next(217, TimeInterval("bat", 1)), on_next(290, TimeInterval("wag", 200)), on_next(300, TimeInterval("pig", 10)), on_next(305, TimeInterval("cup", 50)), on_next(600, TimeInterval("yak", 90)), on_next(702, TimeInterval("tin", 20)), on_next(712, TimeInterval("man", 10)), on_next(722, TimeInterval("rat", 200)), on_next(732, TimeInterval("wig", 5)), on_completed(800)) def create(): def mapper(xy): x, y = xy return "{}{}".format(x.value, y.value) return xs.pipe( ops.join( ys, lambda x: rx.timer(x.interval).pipe(ops.flat_map(rx.throw(ex) if x.value == 6 else rx.empty())), lambda y: rx.timer(y.interval), ), ops.map(mapper), ) results = scheduler.start(create=create) assert results.messages == [ on_next(215, "0hat"), on_next(217, "0bat"), on_next(219, "1hat"), on_next(300, "3wag"), on_next(300, "3pig"), on_next(305, "3cup"), on_next(310, "4wag"), on_next(310, "4pig"), on_next(310, "4cup"), on_next(702, "6tin"), on_next(710, "7tin"), on_next(712, "6man"), on_next(712, "7man"), on_next(720, "8tin"), on_next(720, "8man"), on_next(722, "6rat"), on_next(722, "7rat"), on_next(722, "8rat"), on_error(725, ex)] def test_join_op_error_iv(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(210, TimeInterval(0, 10)), on_next(219, TimeInterval(1, 5)), on_next(240, TimeInterval(2, 10)), on_next(300, TimeInterval(3, 100)), on_next(310, TimeInterval(4, 80)), on_next(500, TimeInterval(5, 90)), on_next(700, TimeInterval(6, 25)), on_next(710, TimeInterval(7, 300)), on_next(720, TimeInterval(8, 100)), on_next(830, TimeInterval(9, 10)), on_completed(900)) ys = scheduler.create_hot_observable( on_next(215, TimeInterval("hat", 20)), on_next(217, TimeInterval("bat", 1)), on_next(290, TimeInterval("wag", 200)), on_next(300, TimeInterval("pig", 10)), on_next(305, TimeInterval("cup", 50)), on_next(600, TimeInterval("yak", 90)), on_next(702, TimeInterval("tin", 19)), on_next(712, TimeInterval("man", 10)), on_next(722, TimeInterval("rat", 200)), on_next(732, TimeInterval("wig", 5)), on_completed(800)) def create(): def mapper(xy): x, y = xy return "{}{}".format(x.value, y.value) return xs.pipe( ops.join( ys, lambda x: rx.timer(x.interval), lambda y: rx.timer(y.interval).pipe(ops.flat_map(rx.throw(ex) if y.value == "tin" else rx.empty())), ), ops.map(mapper), ) results = scheduler.start(create=create) assert results.messages == [ on_next(215, "0hat"), on_next(217, "0bat"), on_next(219, "1hat"), on_next(300, "3wag"), on_next(300, "3pig"), on_next(305, "3cup"), on_next(310, "4wag"), on_next(310, "4pig"), on_next(310, "4cup"), on_next(702, "6tin"), on_next(710, "7tin"), on_next(712, "6man"), on_next(712, "7man"), on_next(720, "8tin"), on_next(720, "8man"), on_error(721, ex)] def test_join_op_error_v(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(210, TimeInterval(0, 10)), on_next(219, TimeInterval(1, 5)), on_next(240, TimeInterval(2, 10)), on_next(300, TimeInterval(3, 100)), on_next(310, TimeInterval(4, 80)), on_next(500, TimeInterval(5, 90)), on_next(700, TimeInterval(6, 25)), on_next(710, TimeInterval(7, 300)), on_next(720, TimeInterval(8, 100)), on_next(830, TimeInterval(9, 10)), on_completed(900)) ys = scheduler.create_hot_observable( on_next(215, TimeInterval("hat", 20)), on_next(217, TimeInterval("bat", 1)), on_next(290, TimeInterval("wag", 200)), on_next(300, TimeInterval("pig", 10)), on_next(305, TimeInterval("cup", 50)), on_next(600, TimeInterval("yak", 90)), on_next(702, TimeInterval("tin", 20)), on_next(712, TimeInterval("man", 10)), on_next(722, TimeInterval("rat", 200)), on_next(732, TimeInterval("wig", 5)), on_completed(800)) def create(): def left_duration_mapper(x): if x.value >= 0: raise Exception(ex) else: return rx.empty() def mapper(xy): x, y = xy return "{}{}".format(x.value, y.value) return xs.pipe( ops.join( ys, left_duration_mapper, lambda y: rx.timer(y.interval), ), ops.map(mapper), ) results = scheduler.start(create=create) assert results.messages == [on_error(210, ex)] def test_join_op_error_vi(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(210, TimeInterval(0, 10)), on_next(219, TimeInterval(1, 5)), on_next(240, TimeInterval(2, 10)), on_next(300, TimeInterval(3, 100)), on_next(310, TimeInterval(4, 80)), on_next(500, TimeInterval(5, 90)), on_next(700, TimeInterval(6, 25)), on_next(710, TimeInterval(7, 300)), on_next(720, TimeInterval(8, 100)), on_next(830, TimeInterval(9, 10)), on_completed(900)) ys = scheduler.create_hot_observable( on_next(215, TimeInterval("hat", 20)), on_next(217, TimeInterval("bat", 1)), on_next(290, TimeInterval("wag", 200)), on_next(300, TimeInterval("pig", 10)), on_next(305, TimeInterval("cup", 50)), on_next(600, TimeInterval("yak", 90)), on_next(702, TimeInterval("tin", 20)), on_next(712, TimeInterval("man", 10)), on_next(722, TimeInterval("rat", 200)), on_next(732, TimeInterval("wig", 5)), on_completed(800)) def create(): def right_duration_mapper(y): if len(y.value) >= 0: raise Exception(ex) else: return rx.empty() def mapper(xy): x, y = xy return "{}{}".format(x.value, y.value) return xs.pipe( ops.join( ys, lambda x: rx.timer(x.interval), right_duration_mapper, ), ops.map(mapper), ) results = scheduler.start(create=create) assert results.messages == [on_error(215, ex)] def test_join_op_forward_scheduler(self): scheduler = TestScheduler() subscribe_schedulers = { 'x': 'unknown', 'y': 'unknown', 'duration_x': 'unknown', 'duration_y': 'unknown', } def subscribe_x(observer, scheduler='not_set'): subscribe_schedulers['x'] = scheduler # need to push one element to trigger duration mapper observer.on_next('foo') def subscribe_y(observer, scheduler='not_set'): subscribe_schedulers['y'] = scheduler # need to push one element to trigger duration mapper observer.on_next('bar') def subscribe_duration_x(observer, scheduler='not_set'): subscribe_schedulers['duration_x'] = scheduler def subscribe_duration_y(observer, scheduler='not_set'): subscribe_schedulers['duration_y'] = scheduler xs = rx.create(subscribe_x) ys = rx.create(subscribe_y) duration_x = rx.create(subscribe_duration_x) duration_y = rx.create(subscribe_duration_y) def create(): return xs.pipe( ops.join( ys, lambda x: duration_x, lambda y: duration_y, ), ) results = scheduler.start(create=create) assert subscribe_schedulers['x'] is scheduler assert subscribe_schedulers['y'] is scheduler assert subscribe_schedulers['duration_x'] is scheduler assert subscribe_schedulers['duration_y'] is scheduler def test_join_op_forward_scheduler_None(self): subscribe_schedulers = { 'x': 'unknown', 'y': 'unknown', 'duration_x': 'unknown', 'duration_y': 'unknown', } def subscribe_x(observer, scheduler='not_set'): subscribe_schedulers['x'] = scheduler # need to push one element to trigger duration mapper observer.on_next('foo') def subscribe_y(observer, scheduler='not_set'): subscribe_schedulers['y'] = scheduler # need to push one element to trigger duration mapper observer.on_next('bar') def subscribe_duration_x(observer, scheduler='not_set'): subscribe_schedulers['duration_x'] = scheduler def subscribe_duration_y(observer, scheduler='not_set'): subscribe_schedulers['duration_y'] = scheduler xs = rx.create(subscribe_x) ys = rx.create(subscribe_y) duration_x = rx.create(subscribe_duration_x) duration_y = rx.create(subscribe_duration_y) stream = xs.pipe( ops.join( ys, lambda x: duration_x, lambda y: duration_y, ), ) stream.subscribe() assert subscribe_schedulers['x'] is None assert subscribe_schedulers['y'] is None assert subscribe_schedulers['duration_x'] is None assert subscribe_schedulers['duration_y'] is None RxPY-3.2.0/tests/test_observable/test_last.py000066400000000000000000000106701404130727200212630ustar00rootroot00000000000000import unittest from rx import operators as _ from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class RxException(Exception): pass # Helper function for raising exceptions within lambdas def _raise(ex): raise RxException(ex) class TestLast(unittest.TestCase): def test_last_async_empty(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_completed(250)) def create(): return xs.pipe(_.last()) res = scheduler.start(create=create) def predicate(e): return e is not None assert [on_error(250, predicate)] == res.messages assert xs.subscriptions == [subscribe(200, 250)] def test_last_async_one(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(150, 1), on_next(210, 2), on_completed(250)) def create(): return xs.pipe(_.last()) res = scheduler.start(create=create) assert res.messages == [on_next(250, 2), on_completed(250)] assert xs.subscriptions == [subscribe(200, 250)] def test_last_async_many(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(150, 1), on_next(210, 2), on_next(220, 3), on_completed(250)) def create(): return xs.pipe(_.last()) res = scheduler.start(create=create) assert res.messages == [on_next(250, 3), on_completed(250)] assert xs.subscriptions == [subscribe(200, 250)] def test_last_async_error(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_error(210, ex)) def create(): return xs.pipe(_.last()) res = scheduler.start(create=create) assert res.messages == [on_error(210, ex)] assert xs.subscriptions == [subscribe(200, 210)] def test_last_async_predicate(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(150, 1), on_next(210, 2), on_next(220, 3), on_next(230, 4), on_next(240, 5), on_completed(250)) def create(): def predicate(x): return x % 2 == 1 return xs.pipe(_.last(predicate)) res = scheduler.start(create=create) assert res.messages == [on_next(250, 5), on_completed(250)] assert xs.subscriptions == [subscribe(200, 250)] def test_last_async_predicate_none(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(150, 1), on_next(210, 2), on_next(220, 3), on_next(230, 4), on_next(240, 5), on_completed(250)) def create(): def predicate(x): return x > 10 return xs.pipe(_.last(predicate)) res = scheduler.start(create=create) def predicate(e): return e is not None assert [on_error(250, predicate)] == res.messages assert xs.subscriptions == [subscribe(200, 250)] def test_last_async_predicate_on_error(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_error(210, ex)) def create(): def predicate(x): return x % 2 == 1 return xs.pipe(_.last(predicate)) res = scheduler.start(create=create) assert res.messages == [on_error(210, ex)] assert xs.subscriptions == [subscribe(200, 210)] def test_last_async_predicate_throws(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(150, 1), on_next(210, 2), on_next(220, 3), on_next(230, 4), on_next(240, 5), on_completed(250)) def create(): def predicate(x): if x < 4: return x % 2 == 1 else: raise Exception(ex) return xs.pipe(_.last(predicate)) res = scheduler.start(create=create) assert res.messages == [on_error(230, ex)] assert xs.subscriptions == [subscribe(200, 230)] RxPY-3.2.0/tests/test_observable/test_lastordefault.py000066400000000000000000000111011404130727200231570ustar00rootroot00000000000000import unittest from rx import operators as _ from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class RxException(Exception): pass # Helper function for raising exceptions within lambdas def _raise(ex): raise RxException(ex) class TestLastOrDefault(unittest.TestCase): def test_last_or_default_async_empty(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(150, 1), on_completed(250)) def create(): return xs.pipe(_.last_or_default(None, 0)) res = scheduler.start(create=create) assert res.messages == [on_next(250, 0), on_completed(250)] assert xs.subscriptions == [subscribe(200, 250)] def test_last_or_default_async(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(150, 1), on_next(210, 2), on_completed(250)) def create(): return xs.pipe(_.last_or_default(None, 0)) res = scheduler.start(create=create) assert res.messages == [on_next(250, 2), on_completed(250)] assert xs.subscriptions == [subscribe(200, 250)] def test_last_or_default_async_many(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(150, 1), on_next(210, 2), on_next(220, 3), on_completed(250)) def create(): return xs.pipe(_.last_or_default(None, 0)) res = scheduler.start(create=create) assert res.messages == [on_next(250, 3), on_completed(250)] assert xs.subscriptions == [subscribe(200, 250)] def test_last_or_default_async_error(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_error(210, ex)) def create(): return xs.pipe(_.last_or_default(None, 0)) res = scheduler.start(create=create) assert res.messages == [on_error(210, ex)] assert xs.subscriptions == [subscribe(200, 210)] def test_last_or_default_async_predicate(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(150, 1), on_next(210, 2), on_next(220, 3), on_next(230, 4), on_next(240, 5), on_completed(250)) def create(): def predicate(x): return x % 2 == 1 return xs.pipe(_.last_or_default(predicate, 0)) res = scheduler.start(create=create) assert res.messages == [on_next(250, 5), on_completed(250)] assert xs.subscriptions == [subscribe(200, 250)] def test_last_or_default_async_Predicate_none(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(150, 1), on_next(210, 2), on_next(220, 3), on_next(230, 4), on_next(240, 5), on_completed(250)) def create(): def predicate(x): return x > 10 return xs.pipe(_.last_or_default(predicate, 0)) res = scheduler.start(create=create) assert res.messages == [on_next(250, 0), on_completed(250)] assert xs.subscriptions == [subscribe(200, 250)] def test_last_or_default_async_Predicate_on_error(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_error(210, ex)) def create(): def predicate(x): return x > 10 return xs.pipe(_.last_or_default(predicate, 0)) res = scheduler.start(create=create) assert res.messages == [on_error(210, ex)] assert xs.subscriptions == [subscribe(200, 210)] def test_last_or_default_async_predicate_throws(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(150, 1), on_next(210, 2), on_next(220, 3), on_next(230, 4), on_next(240, 5), on_completed(250)) def create(): def predicate(x): if x < 4: return x % 2 == 1 else: raise Exception(ex) return xs.pipe(_.last_or_default(predicate, 0)) res = scheduler.start(create=create) assert res.messages == [on_error(230, ex)] assert xs.subscriptions == [subscribe(200, 230)] RxPY-3.2.0/tests/test_observable/test_map.py000066400000000000000000000303431404130727200210740ustar00rootroot00000000000000import unittest from rx import Observable, return_value, throw, empty, create from rx.testing import TestScheduler, ReactiveTest from rx.disposable import SerialDisposable from rx.operators import map, map_indexed on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class RxException(Exception): pass # Helper function for raising exceptions within lambdas def _raise(ex): raise RxException(ex) class TestSelect(unittest.TestCase): def test_map_throws(self): mapper = map(lambda x: x) with self.assertRaises(RxException): return_value(1).pipe( mapper ).subscribe(lambda x: _raise("ex")) with self.assertRaises(RxException): throw('ex').pipe( mapper ).subscribe(on_error=lambda ex: _raise(ex)) with self.assertRaises(RxException): empty().pipe( mapper ).subscribe(lambda x: x, lambda ex: ex, lambda: _raise('ex')) def subscribe(observer, scheduler=None): _raise('ex') with self.assertRaises(RxException): create(subscribe).pipe( map(lambda x: x) ).subscribe() def test_map_disposeinsidemapper(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(100, 1), on_next(200, 2), on_next(500, 3), on_next(600, 4)) results = scheduler.create_observer() d = SerialDisposable() invoked = [0] def projection(x, *args, **kw): invoked[0] += 1 if scheduler.clock > 400: d.dispose() return x d.disposable = xs.pipe( map(projection) ).subscribe(results, scheduler) def action(scheduler, state): return d.dispose() scheduler.schedule_absolute(ReactiveTest.disposed, action) scheduler.start() assert results.messages == [on_next(100, 1), on_next(200, 2)] assert xs.subscriptions == [ReactiveTest.subscribe(0, 500)] assert invoked[0] == 3 def test_map_completed(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(180, 1), on_next(210, 2), on_next(240, 3), on_next(290, 4), on_next(350, 5), on_completed(400), on_next(410, -1), on_completed(420), on_error(430, 'ex')) invoked = [0] def factory(): def projection(x): invoked[0] += 1 return x + 1 return xs.pipe(map(projection)) results = scheduler.start(factory) assert results.messages == [on_next(210, 3), on_next( 240, 4), on_next(290, 5), on_next(350, 6), on_completed(400)] assert xs.subscriptions == [ReactiveTest.subscribe(200, 400)] assert invoked[0] == 4 def test_map_default_mapper(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(180, 1), on_next(210, 2), on_next(240, 3), on_next(290, 4), on_next(350, 5), on_completed(400), on_next(410, -1), on_completed(420), on_error(430, 'ex')) def factory(): return xs.pipe(map()) results = scheduler.start(factory) assert results.messages == [ on_next(210, 2), on_next(240, 3), on_next(290, 4), on_next(350, 5), on_completed(400)] assert xs.subscriptions == [ReactiveTest.subscribe(200, 400)] def test_map_completed_two(self): for i in range(100): scheduler = TestScheduler() invoked = [0] xs = scheduler.create_hot_observable(on_next(180, 1), on_next(210, 2), on_next(240, 3), on_next( 290, 4), on_next(350, 5), on_completed(400), on_next(410, -1), on_completed(420), on_error(430, 'ex')) def factory(): def projection(x): invoked[0] += 1 return x + 1 return xs.pipe(map(projection)) results = scheduler.start(factory) assert results.messages == [on_next(210, 3), on_next( 240, 4), on_next(290, 5), on_next(350, 6), on_completed(400)] assert xs.subscriptions == [subscribe(200, 400)] assert invoked[0] == 4 def test_map_not_completed(self): scheduler = TestScheduler() invoked = [0] xs = scheduler.create_hot_observable(on_next(180, 1), on_next( 210, 2), on_next(240, 3), on_next(290, 4), on_next(350, 5)) def factory(): def projection(x): invoked[0] += 1 return x + 1 return xs.pipe(map(projection)) results = scheduler.start(factory) assert results.messages == [on_next(210, 3), on_next(240, 4), on_next(290, 5), on_next(350, 6)] assert xs.subscriptions == [subscribe(200, 1000)] assert invoked[0] == 4 def test_map_error(self): scheduler = TestScheduler() ex = 'ex' invoked = [0] xs = scheduler.create_hot_observable(on_next(180, 1), on_next(210, 2), on_next(240, 3), on_next( 290, 4), on_next(350, 5), on_error(400, ex), on_next(410, -1), on_completed(420), on_error(430, 'ex')) def factory(): def projection(x): invoked[0] += 1 return x + 1 return xs.pipe(map(projection)) results = scheduler.start(factory) assert results.messages == [on_next(210, 3), on_next( 240, 4), on_next(290, 5), on_next(350, 6), on_error(400, ex)] assert xs.subscriptions == [subscribe(200, 400)] assert invoked[0] == 4 def test_map_mapper_throws(self): scheduler = TestScheduler() invoked = [0] ex = 'ex' xs = scheduler.create_hot_observable( on_next(180, 1), on_next(210, 2), on_next(240, 3), on_next(290, 4), on_next(350, 5), on_completed(400), on_next(410, -1), on_completed(420), on_error(430, 'ex')) def factory(): def projection(x): invoked[0] += 1 if invoked[0] == 3: raise Exception(ex) return x + 1 return xs.pipe(map(projection)) results = scheduler.start(factory) assert results.messages == [on_next(210, 3), on_next(240, 4), on_error(290, ex)] assert xs.subscriptions == [subscribe(200, 290)] assert invoked[0] == 3 def test_map_with_index_throws(self): with self.assertRaises(RxException): mapper = map_indexed(lambda x, index: x) return return_value(1).pipe( mapper ).subscribe(lambda x: _raise('ex')) with self.assertRaises(RxException): return throw('ex').pipe( mapper ).subscribe(lambda x: x, lambda ex: _raise(ex)) with self.assertRaises(RxException): return empty().pipe( mapper ).subscribe(lambda x: x, lambda ex: None, lambda: _raise('ex')) with self.assertRaises(RxException): return create(lambda o, s: _raise('ex')).pipe( mapper ).subscribe() def test_map_with_index_dispose_inside_mapper(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(100, 4), on_next(200, 3), on_next(500, 2), on_next(600, 1)) invoked = [0] results = scheduler.create_observer() d = SerialDisposable() def projection(x, index): invoked[0] += 1 if scheduler.clock > 400: d.dispose() return x + index * 10 d.disposable = xs.pipe(map_indexed(projection)).subscribe(results) def action(scheduler, state): return d.dispose() scheduler.schedule_absolute(disposed, action) scheduler.start() assert results.messages == [on_next(100, 4), on_next(200, 13)] assert xs.subscriptions == [subscribe(0, 500)] assert invoked[0] == 3 def test_map_with_index_completed(self): scheduler = TestScheduler() invoked = [0] xs = scheduler.create_hot_observable(on_next(180, 5), on_next(210, 4), on_next(240, 3), on_next( 290, 2), on_next(350, 1), on_completed(400), on_next(410, -1), on_completed(420), on_error(430, 'ex')) def factory(): def projection(x, index): invoked[0] += 1 return (x + 1) + (index * 10) return xs.pipe(map_indexed(projection)) results = scheduler.start(factory) assert results.messages == [on_next(210, 5), on_next( 240, 14), on_next(290, 23), on_next(350, 32), on_completed(400)] assert xs.subscriptions == [subscribe(200, 400)] assert invoked[0] == 4 def test_map_with_index_default_mapper(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(180, 5), on_next(210, 4), on_next(240, 3), on_next(290, 2), on_next(350, 1), on_completed(400), on_next(410, -1), on_completed(420), on_error(430, 'ex')) def factory(): return xs.pipe(map_indexed()) results = scheduler.start(factory) assert results.messages == [ on_next(210, 4), on_next(240, 3), on_next(290, 2), on_next(350, 1), on_completed(400)] assert xs.subscriptions == [subscribe(200, 400)] def test_map_with_index_not_completed(self): scheduler = TestScheduler() invoked = [0] xs = scheduler.create_hot_observable(on_next(180, 5), on_next( 210, 4), on_next(240, 3), on_next(290, 2), on_next(350, 1)) def factory(): def projection(x, index): invoked[0] += 1 return (x + 1) + (index * 10) return xs.pipe(map_indexed(projection)) results = scheduler.start(factory) assert results.messages == [on_next(210, 5), on_next(240, 14), on_next(290, 23), on_next(350, 32)] assert xs.subscriptions == [subscribe(200, 1000)] assert invoked[0] == 4 def test_map_with_index_error(self): scheduler = TestScheduler() ex = 'ex' invoked = [0] xs = scheduler.create_hot_observable(on_next(180, 5), on_next(210, 4), on_next(240, 3), on_next( 290, 2), on_next(350, 1), on_error(400, ex), on_next(410, -1), on_completed(420), on_error(430, 'ex')) def factory(): def projection(x, index): invoked[0] += 1 return (x + 1) + (index * 10) return xs.pipe(map_indexed(projection)) results = scheduler.start(factory) assert results.messages == [on_next(210, 5), on_next( 240, 14), on_next(290, 23), on_next(350, 32), on_error(400, ex)] assert xs.subscriptions == [subscribe(200, 400)] assert invoked[0] == 4 def test_map_with_index_mapper_throws(self): scheduler = TestScheduler() invoked = [0] ex = 'ex' xs = scheduler.create_hot_observable(on_next(180, 5), on_next(210, 4), on_next(240, 3), on_next( 290, 2), on_next(350, 1), on_completed(400), on_next(410, -1), on_completed(420), on_error(430, 'ex')) def factory(): def projection(x, index): invoked[0] += 1 if invoked[0] == 3: raise Exception(ex) return (x + 1) + (index * 10) return xs.pipe(map_indexed(projection)) results = scheduler.start(factory) assert results.messages == [on_next(210, 5), on_next(240, 14), on_error(290, ex)] assert xs.subscriptions == [subscribe(200, 290)] assert invoked[0] == 3 if __name__ == '__main__': unittest.main() RxPY-3.2.0/tests/test_observable/test_marbles.py000066400000000000000000000475651404130727200217620ustar00rootroot00000000000000import unittest import rx from rx.core import notification from rx.core.observable.marbles import parse from rx.testing import TestScheduler from rx.testing.reactivetest import ReactiveTest import datetime def mess_on_next(time, value): return (time, notification.OnNext(value)) def mess_on_error(time, error): return (time, notification.OnError(error)) def mess_on_completed(time): return (time, notification.OnCompleted()) class TestParse(unittest.TestCase): def test_parse_just_on_error(self): string = "#" results = parse(string) expected = [mess_on_error(0.0, Exception('error'))] assert results == expected def test_parse_just_on_error_specified(self): string = "#" ex = Exception('Foo') results = parse(string, error=ex) expected = [mess_on_error(0.0, ex)] assert results == expected def test_parse_just_on_completed(self): string = "|" results = parse(string) expected = [mess_on_completed(0.0)] assert results == expected def test_parse_just_on_next(self): string = "a" results = parse(string) expected = [mess_on_next(0.0, 'a')] assert results == expected def test_parse_marble_timespan(self): string = "a--b---c" " 012345678901234567890" ts = 0.1 results = parse(string, timespan=ts) expected = [ mess_on_next(0 * ts, 'a'), mess_on_next(3 * ts, 'b'), mess_on_next(7 * ts, 'c'), ] assert results == expected def test_parse_marble_timedelta(self): string = "a--b---c" " 012345678901234567890" ts = 0.1 results = parse(string, timespan=datetime.timedelta(seconds=ts)) expected = [ mess_on_next(0 * ts, 'a'), mess_on_next(3 * ts, 'b'), mess_on_next(7 * ts, 'c'), ] assert results == expected def test_parse_marble_multiple_digits(self): string = "-ab-cde--" " 012345678901234567890" results = parse(string) expected = [ mess_on_next(1.0, 'ab'), mess_on_next(4.0, 'cde'), ] assert results == expected def test_parse_marble_multiple_digits_int(self): string = "-1-22-333-" " 012345678901234567890" results = parse(string) expected = [ mess_on_next(1.0, 1), mess_on_next(3.0, 22), mess_on_next(6.0, 333), ] assert results == expected def test_parse_marble_multiple_digits_float(self): string = "-1.0--2.345--6.7e8-" " 012345678901234567890" results = parse(string) expected = [ mess_on_next(1.0, float('1.0')), mess_on_next(6.0, float('2.345')), mess_on_next(13.0, float('6.7e8')), ] assert results == expected def test_parse_marble_completed(self): string = "-ab-c--|" " 012345678901234567890" results = parse(string) expected = [ mess_on_next(1.0, 'ab'), mess_on_next(4.0, 'c'), mess_on_completed(7.0), ] assert results == expected def test_parse_marble_with_error(self): string = "-a-b-c--#--" " 012345678901234567890" ex = Exception('ex') results = parse(string, error=ex) expected = [ mess_on_next(1.0, 'a'), mess_on_next(3.0, 'b'), mess_on_next(5.0, 'c'), mess_on_error(8.0, ex), ] assert results == expected def test_parse_marble_with_space(self): string = " -a b- c- de |" " 01 23 45 67 8901234567890" results = parse(string) expected = [ mess_on_next(1.0, 'ab'), mess_on_next(4.0, 'c'), mess_on_next(6.0, 'de'), mess_on_completed(8.0), ] assert results == expected def test_parse_marble_with_group(self): string = "-x(ab,12,1.5)-c--(de)-|" " 012345678901234567890123" " 0 1 2 " results = parse(string) expected = [ mess_on_next(1.0, 'x'), mess_on_next(2.0, 'ab'), mess_on_next(2.0, 12), mess_on_next(2.0, float('1.5')), mess_on_next(14.0, 'c'), mess_on_next(17.0, 'de'), mess_on_completed(22.0), ] assert results == expected def test_parse_marble_lookup(self): string = "-ab-c-12-3-|" " 012345678901234567890" lookup = { 'ab': 'aabb', 'c': 'cc', 12: '1122', 3: 33, } results = parse(string, lookup=lookup) expected = [ mess_on_next(1.0, 'aabb'), mess_on_next(4.0, 'cc'), mess_on_next(6.0, '1122'), mess_on_next(9.0, 33), mess_on_completed(11.0), ] assert results == expected def test_parse_marble_time_shift(self): string = "-ab----c-d-|" " 012345678901234567890" offset = 10.0 results = parse(string, time_shift=offset) expected = [ mess_on_next(1.0 + offset, 'ab'), mess_on_next(7.0 + offset, 'c'), mess_on_next(9.0 + offset, 'd'), mess_on_completed(11.0 + offset), ] assert results == expected def test_parse_marble_raise_with_elements_after_error(self): string = "-a-b-c--#-1-" " 012345678901234567890" with self.assertRaises(ValueError): parse(string, raise_stopped=True) def test_parse_marble_raise_with_elements_after_completed(self): string = "-a-b-c--|-1-" " 012345678901234567890" with self.assertRaises(ValueError): parse(string, raise_stopped=True) def test_parse_marble_raise_with_elements_after_completed_group(self): string = "-a-b-c--(|,1)-" " 012345678901234567890" with self.assertRaises(ValueError): parse(string, raise_stopped=True) def test_parse_marble_raise_with_elements_after_error_group(self): string = "-a-b-c--(#,1)-" " 012345678901234567890" with self.assertRaises(ValueError): parse(string, raise_stopped=True) class TestFromMarble(unittest.TestCase): def create_factory(self, observable): def create(): return observable return create def test_from_marbles_on_error(self): string = "#" obs = rx.from_marbles(string) scheduler = TestScheduler() results = scheduler.start(self.create_factory(obs)).messages expected = [ReactiveTest.on_error(200.0, Exception('error'))] assert results == expected def test_from_marbles_on_error_specified(self): string = "#" ex = Exception('Foo') obs = rx.from_marbles(string, error=ex) scheduler = TestScheduler() results = scheduler.start(self.create_factory(obs)).messages expected = [ReactiveTest.on_error(200.0, ex)] assert results == expected def test_from_marbles_on_complete(self): string = "|" obs = rx.from_marbles(string) scheduler = TestScheduler() results = scheduler.start(self.create_factory(obs)).messages expected = [ReactiveTest.on_completed(200.0)] assert results == expected def test_from_marbles_on_next(self): string = "a" obs = rx.from_marbles(string) scheduler = TestScheduler() results = scheduler.start(self.create_factory(obs)).messages expected = [ReactiveTest.on_next(200.0, 'a')] assert results == expected def test_from_marbles_timespan(self): string = "a--b---c" " 012345678901234567890" ts = 0.5 obs = rx.from_marbles(string, timespan=ts) scheduler = TestScheduler() results = scheduler.start(self.create_factory(obs)).messages expected = [ ReactiveTest.on_next(0 * ts + 200.0, 'a'), ReactiveTest.on_next(3 * ts + 200.0, 'b'), ReactiveTest.on_next(7 * ts + 200.0, 'c'), ] assert results == expected def test_from_marbles_marble_completed(self): string = "-ab-c--|" " 012345678901234567890" obs = rx.from_marbles(string) scheduler = TestScheduler() results = scheduler.start(self.create_factory(obs)).messages expected = [ ReactiveTest.on_next(200.1, 'ab'), ReactiveTest.on_next(200.4, 'c'), ReactiveTest.on_completed(200.7), ] assert results == expected def test_from_marbles_marble_with_error(self): string = "-ab-c--#--" " 012345678901234567890" ex = Exception('ex') obs = rx.from_marbles(string, error=ex) scheduler = TestScheduler() results = scheduler.start(self.create_factory(obs)).messages expected = [ ReactiveTest.on_next(200.1, 'ab'), ReactiveTest.on_next(200.4, 'c'), ReactiveTest.on_error(200.7, ex), ] assert results == expected def test_from_marbles_marble_with_consecutive_symbols(self): string = "-ab(12)#--" " 012345678901234567890" ex = Exception('ex') obs = rx.from_marbles(string, error=ex) scheduler = TestScheduler() results = scheduler.start(self.create_factory(obs)).messages expected = [ ReactiveTest.on_next(200.1, 'ab'), ReactiveTest.on_next(200.3, 12), ReactiveTest.on_error(200.7, ex), ] assert results == expected def test_from_marbles_marble_with_space(self): string = " -a b- c- - |" " 01 23 45 6 78901234567890" obs = rx.from_marbles(string) scheduler = TestScheduler() results = scheduler.start(self.create_factory(obs)).messages expected = [ ReactiveTest.on_next(200.1, 'ab'), ReactiveTest.on_next(200.4, 'c'), ReactiveTest.on_completed(200.7), ] assert results == expected def test_from_marbles_marble_with_group(self): string = "-(ab)-c-(12.5,def)--(6,|)" " 012345678901234567890" obs = rx.from_marbles(string) scheduler = TestScheduler() results = scheduler.start(self.create_factory(obs)).messages expected = [ ReactiveTest.on_next(200.1, 'ab'), ReactiveTest.on_next(200.6, 'c'), ReactiveTest.on_next(200.8, str(12.5)), ReactiveTest.on_next(200.8, 'def'), ReactiveTest.on_next(202.0, 6), ReactiveTest.on_completed(202.0), ] assert results == expected def test_from_marbles_marble_lookup(self): string = "-ab-c-12-3-|" " 012345678901234567890" lookup = { 'ab': 'aabb', 'c': 'cc', 12: '1122', 3: 33, } obs = rx.from_marbles(string, lookup=lookup) scheduler = TestScheduler() results = scheduler.start(self.create_factory(obs)).messages expected = [ ReactiveTest.on_next(200.1, 'aabb'), ReactiveTest.on_next(200.4, 'cc'), ReactiveTest.on_next(200.6, '1122'), ReactiveTest.on_next(200.9, 33), ReactiveTest.on_completed(201.1), ] assert results == expected def test_from_marbles_reuse(self): string = "a--b---c--|" " 012345678901234567890" obs = rx.from_marbles(string) scheduler = TestScheduler() results = scheduler.start(self.create_factory(obs)).messages expected = [ ReactiveTest.on_next(200.0, 'a'), ReactiveTest.on_next(200.3, 'b'), ReactiveTest.on_next(200.7, 'c'), ReactiveTest.on_completed(201.0), ] assert results == expected scheduler = TestScheduler() results = scheduler.start(self.create_factory(obs)).messages expected = [ ReactiveTest.on_next(200.0, 'a'), ReactiveTest.on_next(200.3, 'b'), ReactiveTest.on_next(200.7, 'c'), ReactiveTest.on_completed(201.0), ] assert results == expected scheduler = TestScheduler() results = scheduler.start(self.create_factory(obs)).messages expected = [ ReactiveTest.on_next(200.0, 'a'), ReactiveTest.on_next(200.3, 'b'), ReactiveTest.on_next(200.7, 'c'), ReactiveTest.on_completed(201.0), ] assert results == expected class TestHot(unittest.TestCase): def create_factory(self, observable): def create(): return observable return create def test_hot_on_error(self): string = "#" scheduler = TestScheduler() obs = rx.hot(string, 0.1, 200.1, scheduler=scheduler) results = scheduler.start(self.create_factory(obs)).messages expected = [ReactiveTest.on_error(200.1, Exception('error'))] assert results == expected def test_hot_on_error_specified(self): string = "#" ex = Exception('Foo') scheduler = TestScheduler() obs = rx.hot(string, 0.1, 200.1, error=ex, scheduler=scheduler) results = scheduler.start(self.create_factory(obs)).messages expected = [ReactiveTest.on_error(200.1, ex)] assert results == expected def test_hot_on_complete(self): string = "|" scheduler = TestScheduler() obs = rx.hot(string, 0.1, 200.1, scheduler=scheduler) results = scheduler.start(self.create_factory(obs)).messages expected = [ReactiveTest.on_completed(200.1)] assert results == expected def test_hot_on_next(self): string = "a" scheduler = TestScheduler() obs = rx.hot(string, 0.1, 200.1, scheduler=scheduler) results = scheduler.start(self.create_factory(obs)).messages expected = [ReactiveTest.on_next(200.1, 'a')] assert results == expected def test_hot_skipped_at_200(self): string = "a" scheduler = TestScheduler() obs = rx.hot(string, 0.1, 200.0, scheduler=scheduler) results = scheduler.start(self.create_factory(obs)).messages expected = [] assert results == expected def test_hot_timespan(self): string = "-a-b---c" " 012345678901234567890" ts = 0.5 scheduler = TestScheduler() obs = rx.hot(string, ts, 200.0, scheduler=scheduler) results = scheduler.start(self.create_factory(obs)).messages expected = [ ReactiveTest.on_next(1 * ts + 200.0, 'a'), ReactiveTest.on_next(3 * ts + 200.0, 'b'), ReactiveTest.on_next(7 * ts + 200.0, 'c'), ] assert results == expected def test_hot_marble_completed(self): string = "-ab-c--|" " 012345678901234567890" scheduler = TestScheduler() obs = rx.hot(string, 0.1, 200.0, scheduler=scheduler) results = scheduler.start(self.create_factory(obs)).messages expected = [ ReactiveTest.on_next(200.1, 'ab'), ReactiveTest.on_next(200.4, 'c'), ReactiveTest.on_completed(200.7), ] assert results == expected def test_hot_marble_with_error(self): string = "-ab-c--#--" " 012345678901234567890" ex = Exception('ex') scheduler = TestScheduler() obs = rx.hot(string, 0.1, 200.0, error=ex, scheduler=scheduler) results = scheduler.start(self.create_factory(obs)).messages expected = [ ReactiveTest.on_next(200.1, 'ab'), ReactiveTest.on_next(200.4, 'c'), ReactiveTest.on_error(200.7, ex), ] assert results == expected def test_hot_marble_with_consecutive_symbols(self): string = "-ab(12)#--" " 012345678901234567890" ex = Exception('ex') scheduler = TestScheduler() obs = rx.hot(string, 0.1, 200.0, error=ex, scheduler=scheduler) results = scheduler.start(self.create_factory(obs)).messages expected = [ ReactiveTest.on_next(200.1, 'ab'), ReactiveTest.on_next(200.3, 12), ReactiveTest.on_error(200.7, ex), ] assert results == expected def test_hot_marble_with_space(self): string = " -a b- c- - |" " 01 23 45 6 78901234567890" scheduler = TestScheduler() obs = rx.hot(string, 0.1, 200.0, scheduler=scheduler) results = scheduler.start(self.create_factory(obs)).messages expected = [ ReactiveTest.on_next(200.1, 'ab'), ReactiveTest.on_next(200.4, 'c'), ReactiveTest.on_completed(200.7), ] assert results == expected def test_hot_marble_with_group(self): string = "-(ab)-c-(12.5,def)--(6,|)" " 01234567890123456789012345" scheduler = TestScheduler() obs = rx.hot(string, 0.1, 200.0, scheduler=scheduler) results = scheduler.start(self.create_factory(obs)).messages expected = [ ReactiveTest.on_next(200.1, 'ab'), ReactiveTest.on_next(200.6, 'c'), ReactiveTest.on_next(200.8, str(12.5)), ReactiveTest.on_next(200.8, 'def'), ReactiveTest.on_next(202.0, 6), ReactiveTest.on_completed(202.0), ] assert results == expected def test_hot_marble_lookup(self): string = "-ab-c-12-3-|" " 012345678901234567890" lookup = { 'ab': 'aabb', 'c': 'cc', 12: '1122', 3: 33, } scheduler = TestScheduler() obs = rx.hot(string, 0.1, 200.0, lookup=lookup, scheduler=scheduler) results = scheduler.start(self.create_factory(obs)).messages expected = [ ReactiveTest.on_next(200.1, 'aabb'), ReactiveTest.on_next(200.4, 'cc'), ReactiveTest.on_next(200.6, '1122'), ReactiveTest.on_next(200.9, 33), ReactiveTest.on_completed(201.1), ] assert results == expected def test_hot_marble_with_datetime(self): string = "-ab-c--|" " 012345678901234567890" scheduler = TestScheduler() duetime = scheduler.now + datetime.timedelta(seconds=300.0) obs = rx.hot(string, 0.1, duetime, scheduler=scheduler) results = scheduler.start(self.create_factory(obs)).messages expected = [ ReactiveTest.on_next(300.1, 'ab'), ReactiveTest.on_next(300.4, 'c'), ReactiveTest.on_completed(300.7), ] assert results == expected def test_hot_marble_with_timedelta(self): string = "-ab-c--|" " 012345678901234567890" scheduler = TestScheduler() duetime = datetime.timedelta(seconds=300.0) obs = rx.hot(string, 0.1, duetime, scheduler=scheduler) results = scheduler.start(self.create_factory(obs)).messages expected = [ ReactiveTest.on_next(300.1, 'ab'), ReactiveTest.on_next(300.4, 'c'), ReactiveTest.on_completed(300.7), ] assert results == expected RxPY-3.2.0/tests/test_observable/test_materialize.py000066400000000000000000000077221404130727200226320ustar00rootroot00000000000000import unittest import rx from rx import operators as _ from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class TestMaterialize(unittest.TestCase): def test_materialize_never(self): scheduler = TestScheduler() def create(): return rx.never().pipe(_.materialize()) results = scheduler.start(create) assert results.messages == [] def test_materialize_empty(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_completed(250)) def create(): return xs.pipe(_.materialize()) results = scheduler.start(create).messages assert(len(results) == 2) assert(results[0].value.kind == 'N' and results[0].value.value.kind == 'C' and results[0].time == 250) assert(results[1].value.kind == 'C' and results[1].time == 250) def test_materialize_return(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(150, 1), on_next(210, 2), on_completed(250)) def create(): return xs.pipe(_.materialize()) results = scheduler.start(create).messages assert(len(results) == 3) assert(results[0].value.kind == 'N' and results[0].value.value.kind == 'N' and results[0].value.value.value == 2 and results[0].time == 210) assert(results[1].value.kind == 'N' and results[1].value.value.kind == 'C' and results[1].time == 250) assert(results[2].value.kind == 'C' and results[1].time == 250) def test_materialize_on_error(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_error(250, ex)) def create(): return xs.pipe(_.materialize()) results = scheduler.start(create).messages assert(len(results) == 2) assert(results[0].value.kind == 'N' and results[0].value.value.kind == 'E' and results[0].value.value.exception == ex) assert(results[1].value.kind == 'C') def test_materialize_dematerialize_never(self): scheduler = TestScheduler() def create(): return rx.never().pipe(_.materialize(), _.dematerialize()) results = scheduler.start(create) assert results.messages == [] def test_materialize_dematerialize_empty(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_completed(250)) def create(): return xs.pipe(_.materialize(), _.dematerialize()) results = scheduler.start(create).messages assert(len(results) == 1) assert(results[0].value.kind == 'C' and results[0].time == 250) def test_materialize_dematerialize_return(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(150, 1), on_next(210, 2), on_completed(250)) def create(): return xs.pipe(_.materialize(), _.dematerialize()) results = scheduler.start(create).messages assert(len(results) == 2) assert(results[0].value.kind == 'N' and results[0].value.value == 2 and results[0].time == 210) assert(results[1].value.kind == 'C') def test_materialize_dematerialize_on_error(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_error(250, ex)) def create(): return xs.pipe(_.materialize(), _.dematerialize()) results = scheduler.start(create).messages assert(len(results) == 1) assert(results[0].value.kind == 'E' and results[0].value.exception == ex and results[0].time == 250) if __name__ == '__main__': unittest.main() RxPY-3.2.0/tests/test_observable/test_max.py000066400000000000000000000127221404130727200211050ustar00rootroot00000000000000import unittest from rx import operators as ops from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class TestMax(unittest.TestCase): def test_max_int32_empty(self): scheduler = TestScheduler() msgs = [on_next(150, 1), on_completed(250)] xs = scheduler.create_hot_observable(msgs) def create(): return xs.pipe(ops.max()) res = scheduler.start(create=create).messages self.assertEqual(1, len(res)) assert(res[0].value.kind == 'E' and res[0].value.exception is not None) assert(res[0].time == 250) def test_max_int32_return(self): scheduler = TestScheduler() msgs = [on_next(150, 1), on_next(210, 2), on_completed(250)] xs = scheduler.create_hot_observable(msgs) def create(): return xs.pipe(ops.max()) res = scheduler.start(create=create).messages assert res == [on_next(250, 2), on_completed(250)] def test_max_int32_some(self): scheduler = TestScheduler() msgs = [on_next(150, 1), on_next(210, 3), on_next(220, 4), on_next(230, 2), on_completed(250)] xs = scheduler.create_hot_observable(msgs) def create(): return xs.pipe(ops.max()) res = scheduler.start(create=create).messages assert res == [on_next(250, 4), on_completed(250)] def test_max_int32_on_error(self): ex = 'ex' scheduler = TestScheduler() msgs = [on_next(150, 1), on_error(210, ex)] xs = scheduler.create_hot_observable(msgs) def create(): return xs.pipe(ops.max()) res = scheduler.start(create=create).messages assert res == [on_error(210, ex)] def test_max_int32_never(self): scheduler = TestScheduler() msgs = [on_next(150, 1)] xs = scheduler.create_hot_observable(msgs) def create(): return xs.pipe(ops.max()) res = scheduler.start(create=create).messages assert res == [] def test_max_of_t_comparer_empty(self): scheduler = TestScheduler() msgs = [on_next(150, 1), on_completed(250)] def reverse_comparer(a, b): if a > b: return -1 if a < b: return 1 return 0 xs = scheduler.create_hot_observable(msgs) def create(): return xs.pipe(ops.max(reverse_comparer)) res = scheduler.start(create=create).messages self.assertEqual(1, len(res)) assert(res[0].value.kind == 'E' and res[0].value.exception is not None) assert(res[0].time == 250) def test_max_of_t_comparer_return(self): scheduler = TestScheduler() msgs = [on_next(150, 'z'), on_next(210, 'a'), on_completed(250)] def reverse_comparer(a, b): if a > b: return -1 if a < b: return 1 return 0 xs = scheduler.create_hot_observable(msgs) def create(): return xs.pipe(ops.max(reverse_comparer)) res = scheduler.start(create=create).messages assert res == [on_next(250, 'a'), on_completed(250)] def test_max_of_t_comparer_some(self): scheduler = TestScheduler() msgs = [on_next(150, 'z'), on_next(210, 'b'), on_next(220, 'c'), on_next(230, 'a'), on_completed(250)] def reverse_comparer(a, b): if a > b: return -1 if a < b: return 1 return 0 xs = scheduler.create_hot_observable(msgs) def create(): return xs.pipe(ops.max(reverse_comparer)) res = scheduler.start(create=create).messages assert res == [on_next(250, 'a'), on_completed(250)] def test_max_of_t_comparer_on_error(self): ex = 'ex' scheduler = TestScheduler() msgs = [on_next(150, 'z'), on_error(210, ex)] def reverse_comparer(a, b): if a > b: return -1 if a < b: return 1 return 0 xs = scheduler.create_hot_observable(msgs) def create(): return xs.pipe(ops.max(reverse_comparer)) res = scheduler.start(create=create).messages assert res == [on_error(210, ex)] def test_max_of_t_comparer_never(self): scheduler = TestScheduler() msgs = [on_next(150, 'z')] def reverse_comparer(a, b): if a > b: return -1 if a < b: return 1 return 0 xs = scheduler.create_hot_observable(msgs) def create(): return xs.pipe(ops.max(reverse_comparer)) res = scheduler.start(create=create).messages assert res == [] def test_max_of_t_comparer_throws(self): ex = 'ex' scheduler = TestScheduler() msgs = [on_next(150, 'z'), on_next(210, 'b'), on_next(220, 'c'), on_next(230, 'a'), on_completed(250)] def reverse_comparer(a, b): raise Exception(ex) xs = scheduler.create_hot_observable(msgs) def create(): return xs.pipe(ops.max(reverse_comparer)) res = scheduler.start(create=create).messages assert res == [on_error(220, ex)] RxPY-3.2.0/tests/test_observable/test_maxby.py000066400000000000000000000257501404130727200214450ustar00rootroot00000000000000import unittest from rx import operators as ops from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class TestMaxBy(unittest.TestCase): def test_maxby_empty(self): scheduler = TestScheduler() msgs = [ on_next(150, {"key": 1, "value": 'z'}), on_completed(250) ] xs = scheduler.create_hot_observable(msgs) def create(): def mapper(x): return x["key"] return xs.pipe(ops.max_by(mapper)) res = scheduler.start(create=create).messages self.assertEqual(2, len(res)) self.assertEqual(0, len(res[0].value.value)) assert(res[1].value.kind == 'C' and res[1].time == 250) def test_maxby_return(self): scheduler = TestScheduler() msgs = [ on_next(150, { "key": 1, "value": 'z' }), on_next(210, { "key": 2, "value": 'a' }), on_completed(250) ] xs = scheduler.create_hot_observable(msgs) def create(): def mapper(x): return x["key"] return xs.pipe(ops.max_by(mapper)) res = scheduler.start(create=create).messages self.assertEqual(2, len(res)) assert(res[0].value.kind == 'N') self.assertEqual(1, len(res[0].value.value)) self.assertEqual(2, res[0].value.value[0]["key"]) self.assertEqual('a', res[0].value.value[0]["value"]) assert(res[1].value.kind == 'C' and res[1].time == 250) def test_maxby_some(self): scheduler = TestScheduler() msgs = [ on_next(150, { "key": 1, "value": 'z' }), on_next(210, { "key": 3, "value": 'b' }), on_next(220, { "key": 4, "value": 'c' }), on_next(230, { "key": 2, "value": 'a' }), on_completed(250) ] xs = scheduler.create_hot_observable(msgs) def create(): def mapper(x): return x["key"] return xs.pipe(ops.max_by(mapper)) res = scheduler.start(create=create).messages self.assertEqual(2, len(res)) assert(res[0].value.kind == 'N') self.assertEqual(1, len(res[0].value.value[0]["value"])) self.assertEqual(4, res[0].value.value[0]["key"]) self.assertEqual('c', res[0].value.value[0]["value"]) assert(res[1].value.kind == 'C' and res[1].time == 250) def test_maxby_multiple(self): scheduler = TestScheduler() msgs = [ on_next(150, { "key": 1, "value": 'z' }), on_next(210, { "key": 3, "value": 'b' }), on_next(215, { "key": 2, "value": 'd' }), on_next(220, { "key": 3, "value": 'c' }), on_next(225, { "key": 2, "value": 'y' }), on_next(230, { "key": 4, "value": 'a' }), on_next(235, { "key": 4, "value": 'r' }), on_completed(250) ] xs = scheduler.create_hot_observable(msgs) def create(): return xs.pipe(ops.max_by(lambda x: x["key"])) res = scheduler.start(create=create).messages self.assertEqual(2, len(res)) assert(res[0].value.kind == 'N') self.assertEqual(2, len(res[0].value.value)) self.assertEqual(4, res[0].value.value[0]["key"]) self.assertEqual('a', res[0].value.value[0]["value"]) self.assertEqual(4, res[0].value.value[1]["key"]) self.assertEqual('r', res[0].value.value[1]["value"]) assert(res[1].value.kind == 'C' and res[1].time == 250) def test_maxby_on_error(self): ex = 'ex' scheduler = TestScheduler() msgs = [ on_next(150, { "key": 1, "value": 'z' }), on_error(210, ex) ] xs = scheduler.create_hot_observable(msgs) def create(): return xs.pipe(ops.max_by(lambda x: x["key"])) res = scheduler.start(create=create).messages assert res == [on_error(210, ex)] def test_maxby_never(self): scheduler = TestScheduler() msgs = [ on_next(150, { "key": 1, "value": 'z' }) ] xs = scheduler.create_hot_observable(msgs) def create(): return xs.pipe(ops.max_by(lambda x: x["key"])) res = scheduler.start(create=create).messages assert res == [] # def test_MaxBy_Comparer_Empty(): # var msgs, res, reverseComparer, scheduler, xs # scheduler = TestScheduler() # msgs = [ # on_next(150, { # key: 1, # value: 'z' # }), # on_completed(250) # ] # reverseComparer = function (a, b) { # if (a > b) { # return -1 # } # if (a < b) { # return 1 # } # return 0 # } # xs = scheduler.create_hot_observable(msgs) # res = scheduler.start(create=create) # return xs.max_by(function (x) { # return x.key # }, reverseComparer) # }).messages # self.assertEqual(2, res.length) # self.assertEqual(0, res[0].value.value.length) # assert(res[1].value.kind == 'C' and res[1].time == 250) # def test_MaxBy_Comparer_Return(): # var msgs, res, reverseComparer, scheduler, xs # scheduler = TestScheduler() # msgs = [ # on_next(150, { # key: 1, # value: 'z' # }), on_next(210, { # key: 2, # value: 'a' # }), on_completed(250) # ] # reverseComparer = function (a, b) { # if (a > b) { # return -1 # } # if (a < b) { # return 1 # } # return 0 # } # xs = scheduler.create_hot_observable(msgs) # res = scheduler.start(create=create) # return xs.max_by(function (x) { # return x.key # }, reverseComparer) # }).messages # self.assertEqual(2, res.length) # assert(res[0].value.kind == 'N') # self.assertEqual(1, res[0].value.value.length) # self.assertEqual(2, res[0].value.value[0].key) # self.assertEqual('a', res[0].value.value[0].value) # assert(res[1].value.kind == 'C' and res[1].time == 250) # def test_MaxBy_Comparer_Some(): # var msgs, res, reverseComparer, scheduler, xs # scheduler = TestScheduler() # msgs = [ # on_next(150, { # key: 1, # value: 'z' # }), on_next(210, { # key: 3, # value: 'b' # }), on_next(220, { # key: 4, # value: 'c' # }), on_next(230, { # key: 2, # value: 'a' # }), on_completed(250) # ] # reverseComparer = function (a, b) { # if (a > b) { # return -1 # } # if (a < b) { # return 1 # } # return 0 # } # xs = scheduler.create_hot_observable(msgs) # res = scheduler.start(create=create) # return xs.max_by(function (x) { # return x.key # }, reverseComparer) # }).messages # self.assertEqual(2, res.length) # assert(res[0].value.kind == 'N') # self.assertEqual(1, res[0].value.value.length) # equal(2, res[0].value.value[0].key) # self.assertEqual('a', res[0].value.value[0].value) # assert(res[1].value.kind == 'C' and res[1].time == 250) # def test_MaxBy_Comparer_Throw(): # var ex, msgs, res, reverseComparer, scheduler, xs # ex = 'ex' # scheduler = TestScheduler() # msgs = [ # on_next(150, { # key: 1, # value: 'z' # }), on_error(210, ex) # ] # reverseComparer = function (a, b) { # if (a > b) { # return -1 # } # if (a < b) { # return 1 # } # return 0 # } # xs = scheduler.create_hot_observable(msgs) # res = scheduler.start(create=create) # return xs.max_by(function (x) { # return x.key # }, reverseComparer) # }).messages # assert res == [on_error(210, ex)] # def test_MaxBy_Comparer_Never(): # var msgs, res, reverseComparer, scheduler, xs # scheduler = TestScheduler() # msgs = [ # on_next(150, { # key: 1, # value: 'z' # }) # ] # reverseComparer = function (a, b) { # if (a > b) { # return -1 # } # if (a < b) { # return 1 # } # return 0 # } # xs = scheduler.create_hot_observable(msgs) # res = scheduler.start(create=create) # return xs.max_by(function (x) { # return x.key # }, reverseComparer) # }).messages # assert res == [] # def test_MaxBy_SelectorThrows(): # var ex, msgs, res, reverseComparer, scheduler, xs # ex = 'ex' # scheduler = TestScheduler() # msgs = [ # on_next(150, { # key: 1, # value: 'z' # }), on_next(210, { # key: 3, # value: 'b' # }), on_next(220, { # key: 2, # value: 'c' # }), on_next(230, { # key: 4, # value: 'a' # }), on_completed(250) # ] # reverseComparer = function (a, b) { # if (a > b) { # return -1 # } # if (a < b) { # return 1 # } # return 0 # } # xs = scheduler.create_hot_observable(msgs) # res = scheduler.start(create=create) # return xs.max_by(function (x) { # throw ex # }, reverseComparer) # }).messages # assert res == [on_error(210, ex)] def test_maxby_comparerthrows(self): ex = 'ex' scheduler = TestScheduler() msgs = [ on_next(150, { "key": 1, "value": 'z' }), on_next(210, { "key": 3, "value": 'b' }), on_next(220, { "key": 2, "value": 'c' }), on_next(230, { "key": 4, "value": 'a' }), on_completed(250) ] def reverse_comparer(a, b): raise Exception(ex) xs = scheduler.create_hot_observable(msgs) def create(): return xs.pipe(ops.max_by(lambda x: x["key"], reverse_comparer)) res = scheduler.start(create=create).messages assert res == [on_error(220, ex)] RxPY-3.2.0/tests/test_observable/test_merge.py000066400000000000000000000512761404130727200214260ustar00rootroot00000000000000import unittest import pytest import rx from rx import operators as ops from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class TestMerge(unittest.TestCase): def test_merge_never2(self): scheduler = TestScheduler() n1 = rx.never() n2 = rx.never() def create(): return rx.merge(n1, n2) results = scheduler.start(create) assert results.messages == [] def test_merge_never3(self): scheduler = TestScheduler() n1 = rx.never() n2 = rx.never() n3 = rx.never() def create(): return rx.merge(n1, n2, n3) results = scheduler.start(create) assert results.messages == [] def test_merge_empty2(self): scheduler = TestScheduler() e1 = rx.empty() e2 = rx.empty() def create(): return rx.merge(e1, e2) results = scheduler.start(create) assert results.messages == [on_completed(200)] def test_merge_empty3(self): scheduler = TestScheduler() e1 = rx.empty() e2 = rx.empty() e3 = rx.empty() def create(): return rx.merge(e1, e2, e3) results = scheduler.start(create) assert results.messages == [on_completed(200)] def test_merge_empty_delayed2_right_last(self): scheduler = TestScheduler() l_msgs = [on_next(150, 1), on_completed(240)] r_msgs = [on_next(150, 1), on_completed(250)] e1 = scheduler.create_hot_observable(l_msgs) e2 = scheduler.create_hot_observable(r_msgs) def create(): return rx.merge(e1, e2) results = scheduler.start(create) assert results.messages == [on_completed(250)] def test_merge_empty_delayed2_left_last(self): scheduler = TestScheduler() l_msgs = [on_next(150, 1), on_completed(250)] r_msgs = [on_next(150, 1), on_completed(240)] e1 = scheduler.create_hot_observable(l_msgs) e2 = scheduler.create_hot_observable(r_msgs) def create(): return rx.merge(e1, e2) results = scheduler.start(create) assert results.messages == [on_completed(250)] def test_merge_empty_delayed3_middle_last(self): scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_completed(245)] msgs2 = [on_next(150, 1), on_completed(250)] msgs3 = [on_next(150, 1), on_completed(240)] e1 = scheduler.create_hot_observable(msgs1) e2 = scheduler.create_hot_observable(msgs2) e3 = scheduler.create_hot_observable(msgs3) def create(): return rx.merge(e1, e2, e3) results = scheduler.start(create) assert results.messages == [on_completed(250)] def test_merge_empty_never(self): scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_completed(245)] e1 = scheduler.create_hot_observable(msgs1) n1 = rx.never() def create(): return rx.merge(e1, n1) results = scheduler.start(create) assert results.messages == [] def test_merge_never_empty(self): scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_completed(245)] e1 = scheduler.create_hot_observable(msgs1) n1 = rx.never() def create(): return rx.merge(n1, e1) results = scheduler.start(create) assert results.messages == [] def test_merge_return_never(self): scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_next(210, 2), on_completed(245)] r1 = scheduler.create_hot_observable(msgs1) n1 = rx.never() def create(): return rx.merge(r1, n1) results = scheduler.start(create) assert results.messages == [on_next(210, 2)] def test_merge_never_return(self): scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_next(210, 2), on_completed(245)] r1 = scheduler.create_hot_observable(msgs1) n1 = rx.never() def create(): return rx.merge(n1, r1) results = scheduler.start(create) assert results.messages == [on_next(210, 2)] def test_merge_error_never(self): ex = 'ex' scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_next(210, 2), on_error(245, ex)] e1 = scheduler.create_hot_observable(msgs1) n1 = rx.never() def create(): return rx.merge(e1, n1) results = scheduler.start(create) assert results.messages == [on_next(210, 2), on_error(245, ex)] def test_merge_never_error(self): ex = 'ex' scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_next(210, 2), on_error(245, ex)] e1 = scheduler.create_hot_observable(msgs1) n1 = rx.never() def create(): return rx.merge(n1, e1) results = scheduler.start(create) assert results.messages == [on_next(210, 2), on_error(245, ex)] def test_merge_empty_return(self): scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_completed(245)] msgs2 = [on_next(150, 1), on_next(210, 2), on_completed(250)] e1 = scheduler.create_hot_observable(msgs1) r1 = scheduler.create_hot_observable(msgs2) def create(): return rx.merge(e1, r1) results = scheduler.start(create) assert results.messages == [on_next(210, 2), on_completed(250)] def test_merge_return_empty(self): scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_completed(245)] msgs2 = [on_next(150, 1), on_next(210, 2), on_completed(250)] e1 = scheduler.create_hot_observable(msgs1) r1 = scheduler.create_hot_observable(msgs2) def create(): return rx.merge(r1, e1) results = scheduler.start(create) assert results.messages == [on_next(210, 2), on_completed(250)] def test_merge_lots2(self): scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_next(210, 2), on_next(220, 4), on_next(230, 6), on_next(240, 8), on_completed(245)] msgs2 = [on_next(150, 1), on_next(215, 3), on_next(225, 5), on_next(235, 7), on_next(245, 9), on_completed(250)] o1 = scheduler.create_hot_observable(msgs1) o2 = scheduler.create_hot_observable(msgs2) def create(): return rx.merge(o1, o2) results = scheduler.start(create).messages assert(len(results) == 9) for i, result in enumerate(results[:-1]): assert(result.value.kind == 'N') assert(result.time == 210 + i * 5) assert(result.value.value == i + 2) assert(results[8].value.kind == 'C' and results[8].time == 250) def test_merge_lots3(self): scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_next(210, 2), on_next(225, 5), on_next(240, 8), on_completed(245)] msgs2 = [on_next(150, 1), on_next(215, 3), on_next(230, 6), on_next(245, 9), on_completed(250)] msgs3 = [on_next(150, 1), on_next(220, 4), on_next(235, 7), on_completed(240)] o1 = scheduler.create_hot_observable(msgs1) o2 = scheduler.create_hot_observable(msgs2) o3 = scheduler.create_hot_observable(msgs3) def create(): return rx.merge(o1, o2, o3) results = scheduler.start(create).messages assert(len(results) == 9) for i, result in enumerate(results[:-1]): assert(results[i].value.kind == 'N' and results[i].time == 210 + i * 5 and results[i].value.value == i + 2) assert(results[8].value.kind == 'C' and results[8].time == 250) def test_merge_error_left(self): ex = 'ex' scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_next(210, 2), on_error(245, ex)] msgs2 = [on_next(150, 1), on_next(215, 3), on_completed(250)] o1 = scheduler.create_hot_observable(msgs1) o2 = scheduler.create_hot_observable(msgs2) def create(): return rx.merge(o1, o2) results = scheduler.start(create) assert results.messages == [on_next(210, 2), on_next(215, 3), on_error(245, ex)] def test_merge_error_causes_disposal(self): ex = 'ex' scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_error(210, ex)] msgs2 = [on_next(150, 1), on_next(220, 1), on_completed(250)] source_not_disposed = [False] o1 = scheduler.create_hot_observable(msgs1) def action(): source_not_disposed[0] = True o2 = scheduler.create_hot_observable(msgs2).pipe(ops.do_action(on_next=action)) def create(): return rx.merge(o1, o2) results = scheduler.start(create) assert results.messages == [on_error(210, ex)] assert(not source_not_disposed[0]) def test_merge_observable_of_observable_data(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(300, scheduler.create_cold_observable(on_next(10, 101), on_next(20, 102), on_next(110, 103), on_next(120, 104), on_next(210, 105), on_next(220, 106), on_completed(230))), on_next(400, scheduler.create_cold_observable( on_next(10, 201), on_next(20, 202), on_next(30, 203), on_next(40, 200), on_completed(50))), on_next(500, scheduler.create_cold_observable(on_next(10, 301), on_next(20, 302), on_next(30, 303), on_next(40, 304), on_next(120, 305), on_completed(150))), on_completed(600)) def create(): return xs.pipe(ops.merge_all()) results = scheduler.start(create) assert results.messages == [on_next(310, 101), on_next(320, 102), on_next(410, 103), on_next(410, 201), on_next(420, 104), on_next(420, 202), on_next(430, 203), on_next( 440, 200), on_next(510, 105), on_next(510, 301), on_next(520, 106), on_next(520, 302), on_next(530, 303), on_next(540, 304), on_next(620, 305), on_completed(650)] def test_merge_observable_of_observable_data_non_overlapped(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(300, scheduler.create_cold_observable(on_next(10, 101), on_next(20, 102), on_completed(230))), on_next(400, scheduler.create_cold_observable(on_next(10, 201), on_next( 20, 202), on_next(30, 203), on_next(40, 200), on_completed(50))), on_next(500, scheduler.create_cold_observable(on_next(10, 301), on_next(20, 302), on_next(30, 303), on_next(40, 304), on_completed(50))), on_completed(600)) def create(): return xs.pipe(ops.merge_all()) results = scheduler.start(create) assert results.messages == [on_next(310, 101), on_next(320, 102), on_next(410, 201), on_next(420, 202), on_next( 430, 203), on_next(440, 200), on_next(510, 301), on_next(520, 302), on_next(530, 303), on_next(540, 304), on_completed(600)] def test_merge_observable_of_observable_inner_throws(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(300, scheduler.create_cold_observable(on_next(10, 101), on_next(20, 102), on_completed(230))), on_next(400, scheduler.create_cold_observable(on_next(10, 201), on_next( 20, 202), on_next(30, 203), on_next(40, 200), on_error(50, ex))), on_next(500, scheduler.create_cold_observable(on_next(10, 301), on_next(20, 302), on_next(30, 303), on_next(40, 304), on_completed(50))), on_completed(600)) def create(): return xs.pipe(ops.merge_all()) results = scheduler.start(create) assert results.messages == [on_next(310, 101), on_next(320, 102), on_next( 410, 201), on_next(420, 202), on_next(430, 203), on_next(440, 200), on_error(450, ex)] def test_merge_observable_of_observable_outer_throws(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(300, scheduler.create_cold_observable(on_next(10, 101), on_next(20, 102), on_completed(230))), on_next( 400, scheduler.create_cold_observable(on_next(10, 201), on_next(20, 202), on_next(30, 203), on_next(40, 200), on_completed(50))), on_error(500, ex)) def create(): return xs.pipe(ops.merge_all()) results = scheduler.start(create) assert results.messages == [on_next(310, 101), on_next(320, 102), on_next( 410, 201), on_next(420, 202), on_next(430, 203), on_next(440, 200), on_error(500, ex)] def test_mergeconcat_basic(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(210, scheduler.create_cold_observable( on_next(50, 1), on_next(100, 2), on_next(120, 3), on_completed(140))), on_next(260, scheduler.create_cold_observable( on_next(20, 4), on_next(70, 5), on_completed(200))), on_next(270, scheduler.create_cold_observable( on_next(10, 6), on_next(90, 7), on_next(110, 8), on_completed(130))), on_next(320, scheduler.create_cold_observable( on_next(210, 9), on_next(240, 10), on_completed(300))), on_completed(400)) def create(): return xs.pipe(ops.merge(max_concurrent=2)) results = scheduler.start(create) assert results.messages == [ on_next(260, 1), on_next(280, 4), on_next(310, 2), on_next(330, 3), on_next(330, 5), on_next(360, 6), on_next(440, 7), on_next(460, 8), on_next(670, 9), on_next(700, 10), on_completed(760)] assert xs.subscriptions == [subscribe(200, 400)] def test_mergeconcat_basic_long(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(210, scheduler.create_cold_observable( on_next(50, 1), on_next(100, 2), on_next(120, 3), on_completed(140))), on_next(260, scheduler.create_cold_observable( on_next(20, 4), on_next(70, 5), on_completed(300))), on_next(270, scheduler.create_cold_observable( on_next(10, 6), on_next(90, 7), on_next(110, 8), on_completed(130))), on_next(320, scheduler.create_cold_observable( on_next(210, 9), on_next(240, 10), on_completed(300))), on_completed(400)) def create(): return xs.pipe(ops.merge(max_concurrent=2)) results = scheduler.start(create) assert results.messages == [ on_next(260, 1), on_next(280, 4), on_next(310, 2), on_next(330, 3), on_next(330, 5), on_next(360, 6), on_next(440, 7), on_next(460, 8), on_next(690, 9), on_next(720, 10), on_completed(780)] assert xs.subscriptions == [subscribe(200, 400)] def test_mergeconcat_basic_wide(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(210, scheduler.create_cold_observable( on_next(50, 1), on_next(100, 2), on_next(120, 3), on_completed(140))), on_next(260, scheduler.create_cold_observable( on_next(20, 4), on_next(70, 5), on_completed(300))), on_next(270, scheduler.create_cold_observable( on_next(10, 6), on_next(90, 7), on_next(110, 8), on_completed(130))), on_next(420, scheduler.create_cold_observable( on_next(210, 9), on_next(240, 10), on_completed(300))), on_completed(450)) def create(): return xs.pipe(ops.merge(max_concurrent=3)) results = scheduler.start(create) assert results.messages == [on_next(260, 1), on_next(280, 4), on_next(280, 6), on_next(310, 2), on_next( 330, 3), on_next(330, 5), on_next(360, 7), on_next(380, 8), on_next(630, 9), on_next(660, 10), on_completed(720)] assert xs.subscriptions == [subscribe(200, 450)] def test_mergeconcat_basic_late(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(210, scheduler.create_cold_observable(on_next(50, 1), on_next(100, 2), on_next(120, 3), on_completed(140))), on_next(260, scheduler.create_cold_observable(on_next(20, 4), on_next(70, 5), on_completed( 300))), on_next(270, scheduler.create_cold_observable(on_next(10, 6), on_next(90, 7), on_next(110, 8), on_completed(130))), on_next(420, scheduler.create_cold_observable(on_next(210, 9), on_next(240, 10), on_completed(300))), on_completed(750)) def create(): return xs.pipe(ops.merge(max_concurrent=3)) results = scheduler.start(create) assert results.messages == [on_next(260, 1), on_next(280, 4), on_next(280, 6), on_next(310, 2), on_next( 330, 3), on_next(330, 5), on_next(360, 7), on_next(380, 8), on_next(630, 9), on_next(660, 10), on_completed(750)] assert xs.subscriptions == [subscribe(200, 750)] def test_mergeconcat_disposed(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(210, scheduler.create_cold_observable( on_next(50, 1), on_next(100, 2), on_next(120, 3), on_completed(140))), on_next(260, scheduler.create_cold_observable( on_next(20, 4), on_next(70, 5), on_completed(200))), on_next(270, scheduler.create_cold_observable( on_next(10, 6), on_next(90, 7), on_next(110, 8), on_completed(130))), on_next(320, scheduler.create_cold_observable( on_next(210, 9), on_next(240, 10), on_completed(300))), on_completed(400) ) def create(): return xs.pipe(ops.merge(max_concurrent=2)) results = scheduler.start(create, disposed=450) assert results.messages == [ on_next(260, 1), on_next(280, 4), on_next(310, 2), on_next(330, 3), on_next(330, 5), on_next(360, 6), on_next(440, 7)] assert xs.subscriptions == [subscribe(200, 400)] def test_mergeconcat_outererror(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(210, scheduler.create_cold_observable(on_next(50, 1), on_next(100, 2), on_next(120, 3), on_completed(140))), on_next(260, scheduler.create_cold_observable(on_next(20, 4), on_next(70, 5), on_completed( 200))), on_next(270, scheduler.create_cold_observable(on_next(10, 6), on_next(90, 7), on_next(110, 8), on_completed(130))), on_next(320, scheduler.create_cold_observable(on_next(210, 9), on_next(240, 10), on_completed(300))), on_error(400, ex)) def create(): return xs.pipe(ops.merge(max_concurrent=2)) results = scheduler.start(create) assert results.messages == [on_next(260, 1), on_next(280, 4), on_next( 310, 2), on_next(330, 3), on_next(330, 5), on_next(360, 6), on_error(400, ex)] assert xs.subscriptions == [subscribe(200, 400)] def test_mergeconcat_innererror(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(210, scheduler.create_cold_observable( on_next(50, 1), on_next(100, 2), on_next(120, 3), on_completed(140))), on_next(260, scheduler.create_cold_observable( on_next(20, 4), on_next(70, 5), on_completed(200))), on_next(270, scheduler.create_cold_observable( on_next(10, 6), on_next(90, 7), on_next(110, 8), on_error(140, ex))), on_next(320, scheduler.create_cold_observable( on_next(210, 9), on_next(240, 10), on_completed(300))), on_completed(400) ) def create(): return xs.pipe(ops.merge(max_concurrent=2)) results = scheduler.start(create) assert results.messages == [ on_next(260, 1), on_next(280, 4), on_next(310, 2), on_next(330, 3), on_next(330, 5), on_next(360, 6), on_next(440, 7), on_next(460, 8), on_error(490, ex)] assert xs.subscriptions == [subscribe(200, 400)] def test_merge_112233(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(250, 1), on_next(300, 2), on_next(350, 3), on_completed(360)) ys = scheduler.create_hot_observable( on_next(250, 1), on_next(300, 2), on_next(320, 3), on_completed(340)) def create(): return xs.pipe(ops.merge(ys)) results = scheduler.start(create) assert results.messages == [ on_next(250, 1), on_next(250, 1), on_next(300, 2), on_next(300, 2), on_next(320, 3), on_next(350, 3), on_completed(360)] assert xs.subscriptions == [subscribe(200, 360)] RxPY-3.2.0/tests/test_observable/test_min.py000066400000000000000000000110141404130727200210740ustar00rootroot00000000000000import unittest from rx import operators as ops from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class TestMin(unittest.TestCase): def test_min_int32_empty(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_completed(250)) def create(): return xs.pipe(ops.min()) res = scheduler.start(create=create).messages assert(1 == len(res)) assert(res[0].value.kind == 'E' and res[0].value.exception) assert(res[0].time == 250) def test_min_int32_return(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(150, 1), on_next(210, 2), on_completed(250)) res = scheduler.start(create=lambda: xs.pipe(ops.min())).messages assert res == [on_next(250, 2), on_completed(250)] def test_min_int32_some(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(150, 1), on_next(210, 2), on_next(220, 3), on_next(230, 4), on_completed(250)) res = scheduler.start(create=lambda: xs.pipe(ops.min())).messages assert res == [on_next(250, 2), on_completed(250)] def test_min_int32_on_error(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_error(210, ex)) res = scheduler.start(create=lambda: xs.pipe(ops.min())).messages assert res == [on_error(210, ex)] def test_min_int32_never(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1)) res = scheduler.start(create=lambda: xs.pipe(ops.min())).messages assert res == [] def test_min_of_t_comparer_empty(self): scheduler = TestScheduler() def comparer(a, b): if a > b: return -1 if a == b: return 0 return 1 xs = scheduler.create_hot_observable(on_next(150, 'a'), on_completed(250)) def create(): return xs.pipe(ops.min(comparer)) res = scheduler.start(create=create).messages self.assertEqual(1, len(res)) assert(res[0].value.kind == 'E' and res[0].value.exception is not None) assert(res[0].time == 250) def test_min_of_t_comparer_empty_ii(self): scheduler = TestScheduler() def comparer(a, b): if a > b: return -1 if a == b: return 0 return 1 xs = scheduler.create_hot_observable( on_next(150, 'z'), on_next(210, "b"), on_next(220, "c"), on_next(230, "a"), on_completed(250)) def create(): return xs.pipe(ops.min(comparer)) res = scheduler.start(create=create).messages assert res == [on_next(250, "c"), on_completed(250)] def test_min_of_t_comparer_on_error(self): ex = 'ex' scheduler = TestScheduler() def comparer(a, b): if a > b: return -1 if a == b: return 0 return 1 xs = scheduler.create_hot_observable(on_next(150, 'z'), on_error(210, ex)) def create(): return xs.pipe(ops.min(comparer)) res = scheduler.start(create=create).messages assert res == [on_error(210, ex)] def test_min_of_t_comparer_never(self): scheduler = TestScheduler() def comparer(a, b): if a > b: return -1 if a == b: return 0 return 1 xs = scheduler.create_hot_observable(on_next(150, 'z')) def create(): return xs.pipe(ops.min(comparer)) res = scheduler.start(create=create).messages assert res == [] def test_min_of_t_comparer_throws(self): ex = 'ex' scheduler = TestScheduler() def comparer(a, b): raise Exception(ex) xs = scheduler.create_hot_observable( on_next(150, 'z'), on_next(210, "b"), on_next(220, "c"), on_next(230, "a"), on_completed(250)) def create(): return xs.pipe(ops.min(comparer)) res = scheduler.start(create=create).messages assert res == [on_error(220, ex)] RxPY-3.2.0/tests/test_observable/test_minby.py000066400000000000000000000247501404130727200214420ustar00rootroot00000000000000import unittest from rx import operators as ops from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class RxException(Exception): pass # Helper function for raising exceptions within lambdas def _raise(ex): raise RxException(ex) class TestMinBy(unittest.TestCase): def test_min_by_empty(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(150, {"key": 1, "value": 'z'}), on_completed(250)) def create(): return xs.pipe(ops.min_by(lambda x: x["key"])) res = scheduler.start(create=create).messages assert(2 == len(res)) assert(0 == len(res[0].value.value)) assert(res[1].value.kind == 'C' and res[1].time == 250) def test_min_by_return(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(150, {"key": 1, "value": 'z'}), on_next(210, {"key": 2, "value": 'a'}), on_completed(250)) def create(): return xs.pipe(ops.min_by(lambda x: x["key"])) res = scheduler.start(create=create).messages self.assertEqual(2, len(res)) assert(res[0].value.kind == 'N') self.assertEqual(1, len(res[0].value.value)) self.assertEqual(2, res[0].value.value[0]["key"]) self.assertEqual('a', res[0].value.value[0]["value"]) assert(res[1].value.kind == 'C' and res[1].time == 250) def test_min_by_some(self): scheduler = TestScheduler() msgs = [ on_next(150, { "key": 1, "value": 'z' }), on_next(210, { "key": 3, "value": 'b' }), on_next(220, { "key": 2, "value": 'c' }), on_next(230, { "key": 4, "value": 'a' }), on_completed(250) ] xs = scheduler.create_hot_observable(msgs) def create(): return xs.pipe(ops.min_by(lambda x: x["key"])) res = scheduler.start(create=create).messages self.assertEqual(2, len(res)) assert(res[0].value.kind == 'N') self.assertEqual(1, len(res[0].value.value)) self.assertEqual(2, res[0].value.value[0]["key"]) self.assertEqual('c', res[0].value.value[0]["value"]) assert(res[1].value.kind == 'C' and res[1].time == 250) def test_min_by_multiple(self): scheduler = TestScheduler() msgs = [ on_next(150, { "key": 1, "value": 'z' }), on_next(210, { "key": 3, "value": 'b' }), on_next(215, { "key": 2, "value": 'd' }), on_next(220, { "key": 3, "value": 'c' }), on_next(225, { "key": 2, "value": 'y' }), on_next(230, { "key": 4, "value": 'a' }), on_next(235, { "key": 4, "value": 'r' }), on_completed(250) ] xs = scheduler.create_hot_observable(msgs) def create(): return xs.pipe(ops.min_by(lambda x: x["key"])) res = scheduler.start(create=create).messages self.assertEqual(2, len(res)) assert(res[0].value.kind == 'N') self.assertEqual(2, len(res[0].value.value)) self.assertEqual(2, res[0].value.value[0]["key"]) self.assertEqual('d', res[0].value.value[0]["value"]) self.assertEqual(2, res[0].value.value[1]["key"]) self.assertEqual('y', res[0].value.value[1]["value"]) assert(res[1].value.kind == 'C' and res[1].time == 250) def test_min_by_on_error(self): ex = 'ex' scheduler = TestScheduler() msgs = [ on_next(150, { "key": 1, "value": 'z' }), on_error(210, ex) ] xs = scheduler.create_hot_observable(msgs) def create(): return xs.pipe(ops.min_by(lambda x: x["key"])) res = scheduler.start(create=create).messages assert res == [on_error(210, ex)] def test_min_by_never(self): scheduler = TestScheduler() msgs = [ on_next(150, { "key": 1, "value": 'z' }) ] xs = scheduler.create_hot_observable(msgs) def create(): return xs.pipe(ops.min_by(lambda x: x["key"])) res = scheduler.start(create=create).messages assert res == [] def test_min_by_comparer_empty(self): scheduler = TestScheduler() msgs = [ on_next(150, { "key": 1, "value": 'z' }), on_completed(250) ] def reverse_comparer(a, b): if a > b: return -1 if a == b: return 0 return 1 xs = scheduler.create_hot_observable(msgs) def create(): return xs.pipe(ops.min_by(lambda x: x["key"], reverse_comparer)) res = scheduler.start(create=create).messages self.assertEqual(2, len(res)) self.assertEqual(0, len(res[0].value.value)) assert(res[1].value.kind == 'C' and res[1].time == 250) def test_min_by_comparer_return(self): scheduler = TestScheduler() msgs = [ on_next(150, { "key": 1, "value": 'z' }), on_next(210, { "key": 2, "value": 'a' }), on_completed(250) ] def reverse_comparer(a, b): if a > b: return -1 if a == b: return 0 return 1 xs = scheduler.create_hot_observable(msgs) def create(): return xs.pipe(ops.min_by(lambda x: x["key"], reverse_comparer)) res = scheduler.start(create=create).messages self.assertEqual(2, len(res)) assert(res[0].value.kind == 'N') self.assertEqual(1, len(res[0].value.value)) self.assertEqual(2, res[0].value.value[0]["key"]) self.assertEqual('a', res[0].value.value[0]["value"]) assert(res[1].value.kind == 'C' and res[1].time == 250) def test_min_by_comparer_some(self): scheduler = TestScheduler() msgs = [ on_next(150, { "key": 1, "value": 'z' }), on_next(210, { "key": 3, "value": 'b' }), on_next(220, { "key": 20, "value": 'c' }), on_next(230, { "key": 4, "value": 'a' }), on_completed(250) ] def reverse_comparer(a, b): if a > b: return -1 if a == b: return 0 return 1 xs = scheduler.create_hot_observable(msgs) def create(): return xs.pipe(ops.min_by(lambda x: x["key"], reverse_comparer)) res = scheduler.start(create=create).messages self.assertEqual(2, len(res)) assert(res[0].value.kind == 'N') self.assertEqual(1, len(res[0].value.value)) self.assertEqual(20, res[0].value.value[0]["key"]) self.assertEqual('c', res[0].value.value[0]["value"]) assert(res[1].value.kind == 'C' and res[1].time == 250) def test_min_by_comparer_on_error(self): ex = 'ex' scheduler = TestScheduler() msgs = [ on_next(150, { "key": 1, "value": 'z' }), on_error(210, ex) ] def reverse_comparer(a, b): if a > b: return -1 if a == b: return 0 return 1 def create(): return xs.pipe(ops.min_by(lambda x: x["key"], reverse_comparer)) xs = scheduler.create_hot_observable(msgs) res = scheduler.start(create=create).messages assert res == [on_error(210, ex)] def test_min_by_comparer_never(self): scheduler = TestScheduler() msgs = [ on_next(150, { "key": 1, "value": 'z' }) ] def reverse_comparer(a, b): if a > b: return -1 if a == b: return 0 return 1 xs = scheduler.create_hot_observable(msgs) def create(): return xs.pipe(ops.min_by(lambda x: x["key"], reverse_comparer)) res = scheduler.start(create=create).messages assert res == [] def test_min_by_mapper_throws(self): ex = 'ex' scheduler = TestScheduler() msgs = [ on_next(150, { "key": 1, "value": 'z' }), on_next(210, { "key": 3, "value": 'b' }), on_next(220, { "key": 2, "value": 'c' }), on_next(230, { "key": 4, "value": 'a' }), on_completed(250) ] def reverse_comparer(a, b): if a > b: return -1 if a == b: return 0 return 1 xs = scheduler.create_hot_observable(msgs) def create(): return xs.pipe(ops.min_by(lambda x: _raise(ex), reverse_comparer)) res = scheduler.start(create=create).messages assert res == [on_error(210, ex)] def test_min_by_comparer_throws(self): ex = 'ex' scheduler = TestScheduler() msgs = [ on_next(150, { "key": 1, "value": 'z' }), on_next(210, { "key": 3, "value": 'b' }), on_next(220, { "key": 2, "value": 'c' }), on_next(230, { "key": 4, "value": 'a' }), on_completed(250) ] def reverse_comparer(a, b): _raise(ex) xs = scheduler.create_hot_observable(msgs) def create(): return xs.pipe(ops.min_by(lambda x: x["key"], reverse_comparer)) res = scheduler.start(create=create).messages assert res == [on_error(220, ex)] RxPY-3.2.0/tests/test_observable/test_multicast.py000066400000000000000000000263041404130727200223260ustar00rootroot00000000000000import unittest import pytest from rx import operators as ops from rx.subject import Subject from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class TestMulticast(unittest.TestCase): def test_multicast_hot_1(self): scheduler = TestScheduler() s = Subject() xs = scheduler.create_hot_observable( on_next(40, 0), on_next(90, 1), on_next(150, 2), on_next(210, 3), on_next(240, 4), on_next(270, 5), on_next(330, 6), on_next(340, 7), on_completed(390)) obv = scheduler.create_observer() d1 = [None] d2 = [None] c = [None] def action(scheduler, state): c[0] = xs.pipe(ops.multicast(s)) scheduler.schedule_absolute(50, action) def action0(scheduler, state): d1[0] = c[0].subscribe(obv, scheduler) scheduler.schedule_absolute(100, action0) def action1(scheduler, state): d2[0] = c[0].connect(scheduler) scheduler.schedule_absolute(200, action1) def action2(scheduler, state): d1[0].dispose() scheduler.schedule_absolute(300, action2) scheduler.start() assert obv.messages == [ on_next(210, 3), on_next(240, 4), on_next(270, 5)] assert xs.subscriptions == [subscribe(200, 390)] def test_multicast_hot_2(self): c = [None] d1 = [None] d2 = [None] scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(40, 0), on_next(90, 1), on_next(150, 2), on_next(210, 3), on_next(240, 4), on_next(270, 5), on_next(330, 6), on_next(340, 7), on_completed(390)) s = Subject() o = scheduler.create_observer() def action0(scheduler, state): c[0] = xs.pipe(ops.multicast(s)) scheduler.schedule_absolute(50, action0) def action1(scheduler, state): d2[0] = c[0].connect(scheduler) scheduler.schedule_absolute(100, action1) def action2(scheduler, state): d1[0] = c[0].subscribe(o, scheduler) scheduler.schedule_absolute(200, action2) def action3(scheduler, state): return d1[0].dispose() scheduler.schedule_absolute(300, action3) scheduler.start() assert o.messages == [on_next(210, 3), on_next(240, 4), on_next(270, 5)] assert xs.subscriptions == [subscribe(100, 390)] def test_multicast_hot_21(self): c = [None] d1 = [None] d2 = [None] scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(40, 0), on_next(90, 1), on_next(150, 2), on_next(210, 3), on_next(240, 4), on_next(270, 5), on_next(330, 6), on_next(340, 7), on_completed(390)) s = Subject() o = scheduler.create_observer() def action0(scheduler, state): c[0] = xs.pipe(ops.multicast(s)) scheduler.schedule_absolute(50, action0) def action1(scheduler, state): d2[0] = c[0].connect(scheduler) scheduler.schedule_absolute(100, action1) def action2(scheduler, state): d1[0] = c[0].subscribe(o) scheduler.schedule_absolute(200, action2) def action3(scheduler, state): return d1[0].dispose() scheduler.schedule_absolute(300, action3) scheduler.start() assert o.messages == [on_next(210, 3), on_next(240, 4), on_next(270, 5)] assert xs.subscriptions == [subscribe(100, 390)] def test_multicast_hot_3(self): c = [None] d1 = [None] d2 = [None] scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(40, 0), on_next(90, 1), on_next(150, 2), on_next(210, 3), on_next(240, 4), on_next(270, 5), on_next(330, 6), on_next(340, 7), on_completed(390)) s = Subject() o = scheduler.create_observer() def action0(scheduler, state): c[0] = xs.pipe(ops.multicast(s)) scheduler.schedule_absolute(50, action0) def action1(scheduler, state): d2[0] = c[0].connect(scheduler) scheduler.schedule_absolute(100, action1) def action2(scheduler, state): d1[0] = c[0].subscribe(o) scheduler.schedule_absolute(200, action2) def action3(scheduler, state): d2[0].dispose() scheduler.schedule_absolute(300, action3) def action4(scheduler, state): d2[0] = c[0].connect(scheduler) scheduler.schedule_absolute(335, action4) scheduler.start() assert o.messages == [on_next(210, 3), on_next(240, 4), on_next(270, 5), on_next(340, 7), on_completed(390)] assert xs.subscriptions == [subscribe(100, 300), subscribe(335, 390)] def test_multicast_hot_4(self): c = [None] d1 = [None] d2 = [None] ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(40, 0), on_next(90, 1), on_next(150, 2), on_next(210, 3), on_next(240, 4), on_next(270, 5), on_next(330, 6), on_next(340, 7), on_error(390, ex)) s = Subject() o = scheduler.create_observer() def action0(scheduler, state): c[0] = xs.pipe(ops.multicast(s)) scheduler.schedule_absolute(50, action0) def action1(scheduler, state): d2[0] = c[0].connect(scheduler) scheduler.schedule_absolute(100, action1) def action2(scheduler, state): d1[0] = c[0].subscribe(o, scheduler) scheduler.schedule_absolute(200, action2) def action3(scheduler, state): d2[0].dispose() scheduler.schedule_absolute(300, action3) def action4(scheduler, state): d2[0] = c[0].connect(scheduler) scheduler.schedule_absolute(335, action4) scheduler.start() assert o.messages == [on_next(210, 3), on_next(240, 4), on_next(270, 5), on_next(340, 7), on_error(390, ex)] assert xs.subscriptions == [subscribe(100, 300), subscribe(335, 390)] def test_multicast_hot_5(self): c = [None] d1 = [None] d2 = [None] ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(40, 0), on_next(90, 1), on_next(150, 2), on_next(210, 3), on_next(240, 4), on_next(270, 5), on_next(330, 6), on_next(340, 7), on_error(390, ex)) s = Subject() o = scheduler.create_observer() def action0(scheduler, state): c[0] = xs.pipe(ops.multicast(s)) scheduler.schedule_absolute(50, action0) def action1(scheduler, state): d2[0] = c[0].connect(scheduler) scheduler.schedule_absolute(100, action1) def action2(scheduler, state): d1[0] = c[0].subscribe(o, scheduler) scheduler.schedule_absolute(400, action2) scheduler.start() assert o.messages == [on_error(400, ex)] assert xs.subscriptions == [subscribe(100, 390)] def test_multicast_hot_6(self): c = [None] d1 = [None] d2 = [None] ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(40, 0), on_next(90, 1), on_next(150, 2), on_next(210, 3), on_next(240, 4), on_next(270, 5), on_next(330, 6), on_next(340, 7), on_completed(390)) s = Subject() o = scheduler.create_observer() def action0(scheduler, state): c[0] = xs.pipe(ops.multicast(s)) scheduler.schedule_absolute(50, action0) def action1(scheduler, state): d2[0] = c[0].connect(scheduler) scheduler.schedule_absolute(100, action1) def action2(scheduler, state): d1[0] = c[0].subscribe(o, scheduler) scheduler.schedule_absolute(400, action2) scheduler.start() assert o.messages == [on_completed(400)] assert xs.subscriptions == [subscribe(100, 390)] def test_multicast_cold_completed(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(40, 0), on_next(90, 1), on_next(150, 2), on_next(210, 3), on_next(240, 4), on_next(270, 5), on_next(330, 6), on_next(340, 7), on_completed(390)) def create(): def subject_factory(scheduler): return Subject() def mapper(ys): return ys return xs.pipe(ops.multicast(subject_factory=subject_factory, mapper=mapper)) results = scheduler.start(create) assert results.messages == [on_next(210, 3), on_next(240, 4), on_next(270, 5), on_next(330, 6), on_next(340, 7), on_completed(390)] assert xs.subscriptions == [subscribe(200, 390)] def test_multicast_cold_error(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(40, 0), on_next(90, 1), on_next(150, 2), on_next(210, 3), on_next(240, 4), on_next(270, 5), on_next(330, 6), on_next(340, 7), on_error(390, ex)) def create(): def subject_factory(scheduler): return Subject() def mapper(ys): return ys return xs.pipe(ops.multicast(subject_factory=subject_factory, mapper=mapper)) results = scheduler.start(create) assert results.messages == [on_next(210, 3), on_next(240, 4), on_next(270, 5), on_next(330, 6), on_next(340, 7), on_error(390, ex)] assert xs.subscriptions == [subscribe(200, 390)] def test_multicast_cold_dispose(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(40, 0), on_next(90, 1), on_next(150, 2), on_next(210, 3), on_next(240, 4), on_next(270, 5), on_next(330, 6), on_next(340, 7)) def create(): def subject_factory(scheduler): return Subject() def mapper(ys): return ys return xs.pipe(ops.multicast(subject_factory=subject_factory, mapper=mapper)) results = scheduler.start(create) assert results.messages == [on_next(210, 3), on_next(240, 4), on_next(270, 5), on_next(330, 6), on_next(340, 7)] assert xs.subscriptions == [subscribe(200, 1000)] def test_multicast_cold_zip(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(40, 0), on_next(90, 1), on_next(150, 2), on_next(210, 3), on_next(240, 4), on_next(270, 5), on_next(330, 6), on_next(340, 7), on_completed(390)) def create(): def subject_factory(scheduler): return Subject() def mapper(ys): return ys.pipe( ops.zip(ys), ops.map(sum), ) return xs.pipe(ops.multicast(subject_factory=subject_factory, mapper=mapper)) results = scheduler.start(create) assert results.messages == [on_next(210, 6), on_next(240, 8), on_next(270, 10), on_next(330, 12), on_next(340, 14), on_completed(390)] assert xs.subscriptions == [subscribe(200, 390)] RxPY-3.2.0/tests/test_observable/test_never.py000066400000000000000000000011261404130727200214330ustar00rootroot00000000000000import unittest from rx import never from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class TestNever(unittest.TestCase): def test_never_basic(self): scheduler = TestScheduler() xs = never() results = scheduler.create_observer() xs.subscribe(results) scheduler.start() assert results.messages == [] RxPY-3.2.0/tests/test_observable/test_observeon.py000066400000000000000000000052711404130727200223230ustar00rootroot00000000000000import unittest import rx from rx import operators as ops from rx.scheduler import ImmediateScheduler from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class TestObserveOn(unittest.TestCase): def test_observe_on_normal(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(150, 1), on_next(210, 2), on_completed(250) ) def create(): return xs.pipe(ops.observe_on(scheduler)) results = scheduler.start(create) assert results.messages == [on_next(210, 2), on_completed(250)] assert xs.subscriptions == [subscribe(200, 250)] def test_observe_on_error(self): scheduler = TestScheduler() ex = 'ex' xs = scheduler.create_hot_observable( on_next(150, 1), on_error(210, ex) ) def create(): return xs.pipe(ops.observe_on(scheduler)) results = scheduler.start(create) assert results.messages == [on_error(210, ex)] assert xs.subscriptions == [subscribe(200, 210)] def test_observe_on_empty(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(150, 1), on_completed(250) ) def create(): return xs.pipe(ops.observe_on(scheduler)) results = scheduler.start(create) assert results.messages == [on_completed(250)] assert xs.subscriptions == [subscribe(200, 250)] def test_observe_on_never(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(150, 1) ) def create(): return xs.pipe(ops.observe_on(scheduler)) results = scheduler.start(create) assert results.messages == [] assert xs.subscriptions == [subscribe(200, 1000)] def test_observe_on_forward_subscribe_scheduler(self): scheduler = ImmediateScheduler() expected_subscribe_scheduler = ImmediateScheduler() actual_subscribe_scheduler = None def subscribe(observer, scheduler): nonlocal actual_subscribe_scheduler actual_subscribe_scheduler = scheduler observer.on_completed() xs = rx.create(subscribe) xs.pipe(ops.observe_on(scheduler)).subscribe( scheduler=expected_subscribe_scheduler) assert expected_subscribe_scheduler == actual_subscribe_scheduler RxPY-3.2.0/tests/test_observable/test_of.py000066400000000000000000000024371404130727200207260ustar00rootroot00000000000000import unittest import rx from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class TestOf(unittest.TestCase): def test_of(self): results = [] rx.of(1, 2, 3, 4, 5).subscribe(results.append) assert(str([1, 2, 3, 4, 5]) == str(results)) def test_of_empty(self): results = [] rx.of().subscribe(results.append) assert(len(results) == 0) def teest_of_with_scheduler(self): scheduler = TestScheduler() def create(): return rx.of(1, 2, 3, 4, 5) results = scheduler.start(create=create) assert results.messages == [ on_next(201, 1), on_next(202, 2), on_next(203, 3), on_next(204, 4), on_next(205, 5), on_completed(206)] def teest_of_with_scheduler_empty(self): scheduler = TestScheduler() def create(): return rx.of(scheduler=scheduler) results = scheduler.start(create=create) assert results.messages == [ on_completed(201)] RxPY-3.2.0/tests/test_observable/test_onerrorresumenext.py000066400000000000000000000134131404130727200241240ustar00rootroot00000000000000import unittest import rx from rx import operators as ops from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class TestOnErrorResumeNext(unittest.TestCase): def test_on_error_resume_next_no_errors(self): scheduler = TestScheduler() msgs1 = [ on_next(150, 1), on_next(210, 2), on_next(220, 3), on_completed(230)] msgs2 = [ on_next(240, 4), on_completed(250)] o1 = scheduler.create_hot_observable(msgs1) o2 = scheduler.create_hot_observable(msgs2) def create(): return o1.pipe(ops.on_error_resume_next(o2)) results = scheduler.start(create) assert results.messages == [ on_next(210, 2), on_next(220, 3), on_next(240, 4), on_completed(250)] def test_on_error_resume_next_error(self): scheduler = TestScheduler() msgs1 = [ on_next(150, 1), on_next(210, 2), on_next(220, 3), on_error(230, 'ex')] msgs2 = [ on_next(240, 4), on_completed(250)] o1 = scheduler.create_hot_observable(msgs1) o2 = scheduler.create_hot_observable(msgs2) def create(): return o1.pipe(ops.on_error_resume_next(o2)) results = scheduler.start(create) assert results.messages == [ on_next(210, 2), on_next(220, 3), on_next(240, 4), on_completed(250)] def test_on_error_resume_next_error_multiple(self): scheduler = TestScheduler() msgs1 = [ on_next(150, 1), on_next(210, 2), on_error(220, 'ex')] msgs2 = [ on_next(230, 4), on_error(240, 'ex')] msgs3 = [on_completed(250)] o1 = scheduler.create_hot_observable(msgs1) o2 = scheduler.create_hot_observable(msgs2) o3 = scheduler.create_hot_observable(msgs3) def create(): return rx.on_error_resume_next(o1, o2, o3) results = scheduler.start(create) assert results.messages == [ on_next(210, 2), on_next(230, 4), on_completed(250)] def test_on_error_resume_next_empty_return_throw_and_more(self): scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_completed(205)] msgs2 = [on_next(215, 2), on_completed(220)] msgs3 = [on_next(225, 3), on_next(230, 4), on_completed(235)] msgs4 = [on_error(240, 'ex')] msgs5 = [on_next(245, 5), on_completed(250)] o1 = scheduler.create_hot_observable(msgs1) o2 = scheduler.create_hot_observable(msgs2) o3 = scheduler.create_hot_observable(msgs3) o4 = scheduler.create_hot_observable(msgs4) o5 = scheduler.create_hot_observable(msgs5) def create(): return rx.on_error_resume_next(o1, o2, o3, o4, o5) results = scheduler.start(create) assert results.messages == [ on_next(215, 2), on_next(225, 3), on_next(230, 4), on_next(245, 5), on_completed(250)] def test_on_error_resume_next_empty_return_throw_and_more_ii(self): ex = 'ex' scheduler = TestScheduler() msgs1 = [ on_next(150, 1), on_next(210, 2), on_completed(220)] msgs2 = [on_error(230, ex)] o1 = scheduler.create_hot_observable(msgs1) o2 = scheduler.create_hot_observable(msgs2) def create(): return o1.pipe(ops.on_error_resume_next(o2)) results = scheduler.start(create) assert results.messages == [on_next(210, 2), on_completed(230)] def test_on_error_resume_next_single_source_throws(self): ex = 'ex' scheduler = TestScheduler() msgs1 = [on_error(230, ex)] o1 = scheduler.create_hot_observable(msgs1) def create(): return rx.on_error_resume_next(o1) results = scheduler.start(create) assert results.messages == [on_completed(230)] def test_on_error_resume_next_end_with_never(self): scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_next(210, 2), on_completed(220)] o1 = scheduler.create_hot_observable(msgs1) o2 = rx.never() def create(): return rx.on_error_resume_next(o1, o2) results = scheduler.start(create) assert results.messages == [on_next(210, 2)] def test_on_error_resume_next_start_with_never(self): scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_next(210, 2), on_completed(220)] o1 = rx.never() o2 = scheduler.create_hot_observable(msgs1) def create(): return rx.on_error_resume_next(o1, o2) results = scheduler.start(create) assert results.messages == [] def test_on_error_resume_next_start_with_factory(self): scheduler = TestScheduler() msgs1 = [ on_next(150, 1), on_next(210, 2), on_next(220, 3), on_error(230, 'ex')] o1 = scheduler.create_hot_observable(msgs1) def factory(ex): assert(ex == "ex") msgs2 = [on_next(240, 4), on_completed(250)] o2 = scheduler.create_hot_observable(msgs2) return o2 def create(): return rx.on_error_resume_next(o1, factory) results = scheduler.start(create) assert results.messages == [ on_next(210, 2), on_next(220, 3), on_next(240, 4), on_completed(250)] RxPY-3.2.0/tests/test_observable/test_pairwise.py000066400000000000000000000071351404130727200221450ustar00rootroot00000000000000import unittest from rx import operators as ops from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class TestPairwise(unittest.TestCase): def test_pairwise_empty(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(180, 5), on_completed(210) ) def create(): return xs.pipe(ops.pairwise()) results = scheduler.start(create) assert results.messages == [ on_completed(210)] assert xs.subscriptions == [ subscribe(200, 210)] def test_pairwise_single(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(180, 5), on_next(210, 4), on_completed(220) ) def create(): return xs.pipe(ops.pairwise()) results = scheduler.start(create) assert results.messages == [on_completed(220)] assert xs.subscriptions == [subscribe(200, 220)] def test_pairwise_completed(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(180, 5), on_next(210, 4), on_next(240, 3), on_next(290, 2), on_next(350, 1), on_completed(360) ) def create(): return xs.pipe(ops.pairwise()) results = scheduler.start(create) assert results.messages == [ on_next(240, (4, 3)), on_next(290, (3, 2)), on_next(350, (2, 1)), on_completed(360)] assert xs.subscriptions == [subscribe(200, 360)] def test_pairwise_not_completed(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(180, 5), on_next(210, 4), on_next(240, 3), on_next(290, 2), on_next(350, 1)) def create(): return xs.pipe(ops.pairwise()) results = scheduler.start(create) assert results.messages == [ on_next(240, (4, 3)), on_next(290, (3, 2)), on_next(350, (2, 1))] assert xs.subscriptions == [ subscribe(200, 1000)] def test_pairwise_error(self): error = Exception() scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(180, 5), on_next(210, 4), on_next(240, 3), on_error(290, error), on_next(350, 1), on_completed(360)) def create(): return xs.pipe(ops.pairwise()) results = scheduler.start(create) assert results.messages == [ on_next(240, (4, 3)), on_error(290, error)] assert xs.subscriptions == [ subscribe(200, 290)] def test_pairwise_disposed(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(180, 5), on_next(210, 4), on_next(240, 3), on_next(290, 2), on_next(350, 1), on_completed(360)) def create(): return xs.pipe(ops.pairwise()) results = scheduler.start(create, disposed=280) assert results.messages == [ on_next(240, (4, 3))] assert xs.subscriptions == [ subscribe(200, 280)] RxPY-3.2.0/tests/test_observable/test_partition.py000066400000000000000000000230541404130727200223310ustar00rootroot00000000000000import unittest import rx from rx import operators as ops from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created def is_even(num): return +num % 2 == 0 class TestPartition(unittest.TestCase): def test_partition_empty(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(180, 5), on_completed(210) ) subscription1 = [None] subscription2 = [None] observables = [] results1 = scheduler.create_observer() results2 = scheduler.create_observer() def action0(scheduler, state): observables.extend(xs.pipe(ops.partition(is_even))) scheduler.schedule_absolute(ReactiveTest.created, action0) def action1(scheduler, state): subscription1[0] = observables[0].subscribe(results1) subscription2[0] = observables[1].subscribe(results2) scheduler.schedule_absolute(ReactiveTest.subscribed, action1) def action2(scheduler, state): subscription1[0].dispose() subscription2[0].dispose() scheduler.schedule_absolute(ReactiveTest.disposed, action2) scheduler.start() assert results1.messages == [ on_completed(210)] assert results2.messages == [ on_completed(210)] assert xs.subscriptions == [ subscribe(200, 210)] def test_partition_single(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(180, 5), on_next(210, 4), on_completed(220) ) observables = [] subscription1 = [None] subscription2 = [None] results1 = scheduler.create_observer() results2 = scheduler.create_observer() def action0(scheduler, state): observables.extend(xs.pipe(ops.partition(is_even))) scheduler.schedule_absolute(ReactiveTest.created, action0) def action1(scheduler, state): subscription1[0] = observables[0].subscribe(results1) subscription2[0] = observables[1].subscribe(results2) scheduler.schedule_absolute(ReactiveTest.subscribed, action1) def action2(scheduler, state): subscription1[0].dispose() subscription2[0].dispose() scheduler.schedule_absolute(ReactiveTest.disposed, action2) scheduler.start() assert results1.messages == [ on_next(210, 4), on_completed(220)] assert results2.messages == [ on_completed(220)] assert xs.subscriptions == [ subscribe(200, 220)] def test_partition_each(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(180, 5), on_next(210, 4), on_next(220, 3), on_completed(230) ) observables = [] subscription1 = [None] subscription2 = [None] results1 = scheduler.create_observer() results2 = scheduler.create_observer() def action0(scheduler, state): observables.extend(xs.pipe(ops.partition(is_even))) scheduler.schedule_absolute(ReactiveTest.created, action0) def action1(scheduler, state): subscription1[0] = observables[0].subscribe(results1) subscription2[0] = observables[1].subscribe(results2) scheduler.schedule_absolute(ReactiveTest.subscribed, action1) def action2(scheduler, state): subscription1[0].dispose() subscription2[0].dispose() scheduler.schedule_absolute(ReactiveTest.disposed, action2) scheduler.start() assert results1.messages == [ on_next(210, 4), on_completed(230)] assert results2.messages == [ on_next(220, 3), on_completed(230)] assert xs.subscriptions == [ subscribe(200, 230)] def test_partition_completed(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(180, 5), on_next(210, 4), on_next(240, 3), on_next(290, 2), on_next(350, 1), on_completed(360) ) observables = [] subscription1 = [None] subscription2 = [None] results1 = scheduler.create_observer() results2 = scheduler.create_observer() def action0(scheduler, state): observables.extend(xs.pipe(ops.partition(is_even))) scheduler.schedule_absolute(ReactiveTest.created, action0) def action1(scheduler, state): subscription1[0] = observables[0].subscribe(results1) subscription2[0] = observables[1].subscribe(results2) scheduler.schedule_absolute(ReactiveTest.subscribed, action1) def action2(scheduler, state): subscription1[0].dispose() subscription2[0].dispose() scheduler.schedule_absolute(ReactiveTest.disposed, action2) scheduler.start() assert results1.messages == [ on_next(210, 4), on_next(290, 2), on_completed(360)] assert results2.messages == [ on_next(240, 3), on_next(350, 1), on_completed(360)] assert xs.subscriptions == [ subscribe(200, 360)] def test_partition_not_completed(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(180, 5), on_next(210, 4), on_next(240, 3), on_next(290, 2), on_next(350, 1) ) observables = [] subscription1 = [None] subscription2 = [None] results1 = scheduler.create_observer() results2 = scheduler.create_observer() def action0(scheduler, state): observables.extend(xs.pipe(ops.partition(is_even))) scheduler.schedule_absolute(ReactiveTest.created, action0) def action1(scheduler, state): subscription1[0] = observables[0].subscribe(results1) subscription2[0] = observables[1].subscribe(results2) scheduler.schedule_absolute(ReactiveTest.subscribed, action1) def action2(scheduler, state): subscription1[0].dispose() subscription2[0].dispose() scheduler.schedule_absolute(ReactiveTest.disposed, action2) scheduler.start() assert results1.messages == [ on_next(210, 4), on_next(290, 2)] assert results2.messages == [ on_next(240, 3), on_next(350, 1)] assert xs.subscriptions == [ subscribe(200, 1000)] def test_partition_error(self): error = Exception() scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(180, 5), on_next(210, 4), on_next(240, 3), on_error(290, error), on_next(350, 1), on_completed(360) ) observables = [] subscription1 = [None] subscription2 = [None] results1 = scheduler.create_observer() results2 = scheduler.create_observer() def action0(scheduler, state): observables.extend(xs.pipe(ops.partition(is_even))) scheduler.schedule_absolute(ReactiveTest.created, action0) def action1(scheduler, state): subscription1[0] = observables[0].subscribe(results1) subscription2[0] = observables[1].subscribe(results2) scheduler.schedule_absolute(ReactiveTest.subscribed, action1) def action2(scheduler, state): subscription1[0].dispose() subscription2[0].dispose() scheduler.schedule_absolute(ReactiveTest.disposed, action2) scheduler.start() assert results1.messages == [ on_next(210, 4), on_error(290, error)] assert results2.messages == [ on_next(240, 3), on_error(290, error)] assert xs.subscriptions == [ subscribe(200, 290)] def test_partition_disposed(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(180, 5), on_next(210, 4), on_next(240, 3), on_next(290, 2), on_next(350, 1), on_completed(360) ) observables = [] subscription1 = [None] subscription2 = [None] results1 = scheduler.create_observer() results2 = scheduler.create_observer() def action0(scheduler, state): observables.extend(xs.pipe(ops.partition(is_even))) scheduler.schedule_absolute(ReactiveTest.created, action0) def action1(scheduler, state): subscription1[0] = observables[0].subscribe(results1) subscription2[0] = observables[1].subscribe(results2) scheduler.schedule_absolute(ReactiveTest.subscribed, action1) def action2(scheduler, state): subscription1[0].dispose() subscription2[0].dispose() scheduler.schedule_absolute(280, action2) scheduler.start() assert results1.messages == [ on_next(210, 4)] assert results2.messages == [ on_next(240, 3)] assert xs.subscriptions == [ subscribe(200, 280)] RxPY-3.2.0/tests/test_observable/test_pluck.py000066400000000000000000000041111404130727200214270ustar00rootroot00000000000000import unittest from rx import operators as ops from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class TestPluck(unittest.TestCase): def test_pluck_completed(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(180, {"prop": 1}), on_next(210, {"prop": 2}), on_next(240, {"prop": 3}), on_next(290, {"prop": 4}), on_next(350, {"prop": 5}), on_completed(400), on_next(410, {"prop": -1}), on_completed(420), on_error(430, Exception('ex')) ) results = scheduler.start(create=lambda: xs.pipe(ops.pluck('prop'))) assert results.messages == [ on_next(210, 2), on_next(240, 3), on_next(290, 4), on_next(350, 5), on_completed(400)] assert xs.subscriptions == [subscribe(200, 400)] class TestPluckAttr(unittest.TestCase): def test_pluck_attr_completed(self): scheduler = TestScheduler() class DummyClass: def __init__(self, prop): self.prop = prop xs = scheduler.create_hot_observable( on_next(180, DummyClass(1)), on_next(210, DummyClass(2)), on_next(240, DummyClass(3)), on_next(290, DummyClass(4)), on_next(350, DummyClass(5)), on_completed(400), on_next(410, DummyClass(-1)), on_completed(420), on_error(430, Exception('ex')) ) results = scheduler.start(create=lambda: xs.pipe(ops.pluck_attr('prop'))) assert results.messages == [ on_next(210, 2), on_next(240, 3), on_next(290, 4), on_next(350, 5), on_completed(400)] assert xs.subscriptions == [subscribe(200, 400)] RxPY-3.2.0/tests/test_observable/test_publish.py000066400000000000000000000406131404130727200217660ustar00rootroot00000000000000import unittest import rx from rx import operators as ops from rx.core import ConnectableObservable, Observable from rx.core.typing import Observer from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class RxException(Exception): pass # Helper function for raising exceptions within lambdas def _raise(ex): raise RxException(ex) class MySubject(Observable, Observer): def __init__(self): super(MySubject, self).__init__() self.dispose_on_map = {} self.subscribe_count = 0 self.disposed = False self.observer = None def _subscribe_core(self, observer, scheduler=None): self.subscribe_count += 1 self.observer = observer class Duck: def __init__(self, this): self.this = this def dispose(self): self.this.disposed = True return Duck(self) def dispose_on(self, value, disposable): self.dispose_on_map[value] = disposable def on_next(self, value): self.observer.on_next(value) if value in self.dispose_on_map: self.dispose_on_map[value].dispose() def on_error(self, error): self.observer.on_error(error) def on_completed(self): self.observer.on_completed() class TestPublish(unittest.TestCase): def test_publish_cold_zip(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(40, 0), on_next(90, 1), on_next(150, 2), on_next(210, 3), on_next(240, 4), on_next(270, 5), on_next(330, 6), on_next(340, 7), on_completed(390) ) def create(): def mapper(ys): return ys.pipe( ops.zip(ys), ops.map(sum), ) return xs.pipe(ops.publish(mapper=mapper)) results = scheduler.start(create) assert results.messages == [on_next(210, 6), on_next(240, 8), on_next(270, 10), on_next(330, 12), on_next(340, 14), on_completed(390)] assert xs.subscriptions == [subscribe(200, 390)] def test_ref_count_connects_on_first(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(210, 1), on_next(220, 2), on_next(230, 3), on_next(240, 4), on_completed(250) ) subject = MySubject() conn = ConnectableObservable(xs, subject) def create(): return conn.pipe(ops.ref_count()) res = scheduler.start(create) assert res.messages == [ on_next(210, 1), on_next(220, 2), on_next(230, 3), on_next(240, 4), on_completed(250)] assert subject.disposed def test_ref_count_notconnected(self): disconnected = [False] count = [0] def factory(scheduler): count[0] += 1 def create(obs, scheduler=None): def func(): disconnected[0] = True return func return rx.create(create) xs = rx.defer(factory) subject = MySubject() conn = ConnectableObservable(xs, subject) refd = conn.pipe(ops.ref_count()) dis1 = refd.subscribe() self.assertEqual(1, count[0]) self.assertEqual(1, subject.subscribe_count) assert not disconnected[0] dis2 = refd.subscribe() self.assertEqual(1, count[0]) self.assertEqual(2, subject.subscribe_count) assert not disconnected[0] dis1.dispose() assert not disconnected[0] dis2.dispose() assert disconnected[0] disconnected[0] = False dis3 = refd.subscribe() self.assertEqual(2, count[0]) self.assertEqual(3, subject.subscribe_count) assert not disconnected[0] dis3.dispose() assert disconnected[0] def test_publish_basic(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(110, 7), on_next(220, 3), on_next(280, 4), on_next(290, 1), on_next(340, 8), on_next(360, 5), on_next(370, 6), on_next(390, 7), on_next(410, 13), on_next(430, 2), on_next(450, 9), on_next(520, 11), on_next(560, 20), on_completed(600) ) ys = [None] subscription = [None] connection = [None] results = scheduler.create_observer() def action0(scheduler, state): ys[0] = xs.pipe(ops.publish()) scheduler.schedule_absolute(created, action0) def action1(scheduler, state): subscription[0] = ys[0].subscribe(results) scheduler.schedule_absolute(subscribed, action1) def action2(scheduler, state): subscription[0].dispose() scheduler.schedule_absolute(disposed, action2) def action3(scheduler, state): connection[0] = ys[0].connect() scheduler.schedule_absolute(300, action3) def action4(scheduler, state): connection[0].dispose() scheduler.schedule_absolute(400, action4) def action5(scheduler, state): connection[0] = ys[0].connect() scheduler.schedule_absolute(500, action5) def action6(scheduler, state): connection[0].dispose() scheduler.schedule_absolute(550, action6) def action7(scheduler, state): connection[0] = ys[0].connect() scheduler.schedule_absolute(650, action7) def action8(scheduler, state): connection[0].dispose() scheduler.schedule_absolute(800, action8) scheduler.start() assert results.messages == [ on_next(340, 8), on_next(360, 5), on_next(370, 6), on_next(390, 7), on_next(520, 11)] assert xs.subscriptions == [subscribe(300, 400), subscribe(500, 550), subscribe(650, 800)] def test_publish_error(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(110, 7), on_next(220, 3), on_next(280, 4), on_next(290, 1), on_next(340, 8), on_next(360, 5), on_next(370, 6), on_next(390, 7), on_next(410, 13), on_next(430, 2), on_next(450, 9), on_next(520, 11), on_next(560, 20), on_error(600, ex)) ys = [None] subscription = [None] connection = [None] results = scheduler.create_observer() def action0(scheduler, state): ys[0] = xs.pipe(ops.publish()) scheduler.schedule_absolute(created, action0) def action1(scheduler, state): subscription[0] = ys[0].subscribe(results) scheduler.schedule_absolute(subscribed, action1) def action2(scheduler, state): subscription[0].dispose() scheduler.schedule_absolute(disposed, action2) def action3(scheduler, state): connection[0] = ys[0].connect() scheduler.schedule_absolute(300, action3) def action4(scheduler, state): connection[0].dispose() scheduler.schedule_absolute(400, action4) def action5(scheduler, state): connection[0] = ys[0].connect() scheduler.schedule_absolute(500, action5) def action6(scheduler, state): connection[0].dispose() scheduler.schedule_absolute(800, action6) scheduler.start() assert results.messages == [ on_next(340, 8), on_next(360, 5), on_next(370, 6), on_next(390, 7), on_next(520, 11), on_next(560, 20), on_error(600, ex)] assert xs.subscriptions == [subscribe(300, 400), subscribe(500, 600)] def test_publish_complete(self): connection = [None] subscription = [None] ys = [None] scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(110, 7), on_next(220, 3), on_next(280, 4), on_next(290, 1), on_next(340, 8), on_next(360, 5), on_next(370, 6), on_next(390, 7), on_next(410, 13), on_next(430, 2), on_next(450, 9), on_next(520, 11), on_next(560, 20), on_completed(600)) results = scheduler.create_observer() def action0(scheduler, state): ys[0] = xs.pipe(ops.publish()) scheduler.schedule_absolute(created, action0) def action1(scheduler, state): subscription[0] = ys[0].subscribe(results) scheduler.schedule_absolute(subscribed, action1) def action2(scheduler, state): subscription[0].dispose() scheduler.schedule_absolute(disposed, action2) def action3(scheduler, state): connection[0] = ys[0].connect() scheduler.schedule_absolute(300, action3) def action4(scheduler, state): connection[0].dispose() scheduler.schedule_absolute(400, action4) def action5(scheduler, state): connection[0] = ys[0].connect() scheduler.schedule_absolute(500, action5) def action6(scheduler, state): connection[0].dispose() scheduler.schedule_absolute(800, action6) scheduler.start() assert results.messages == [ on_next(340, 8), on_next(360, 5), on_next(370, 6), on_next(390, 7), on_next(520, 11), on_next(560, 20), on_completed(600)] assert xs.subscriptions == [subscribe(300, 400), subscribe(500, 600)] def test_publish_dispose(self): connection = [None] subscription = [None] ys = [None] scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(110, 7), on_next(220, 3), on_next(280, 4), on_next(290, 1), on_next(340, 8), on_next(360, 5), on_next(370, 6), on_next(390, 7), on_next(410, 13), on_next(430, 2), on_next(450, 9), on_next(520, 11), on_next(560, 20), on_completed(600)) results = scheduler.create_observer() def action0(scheduler, state): ys[0] = xs.pipe(ops.publish()) scheduler.schedule_absolute(created, action0) def action1(scheduler, state): subscription[0] = ys[0].subscribe(results) scheduler.schedule_absolute(subscribed, action1) def action2(scheduler, state): subscription[0].dispose() scheduler.schedule_absolute(350, action2) def action3(scheduler, state): connection[0] = ys[0].connect() scheduler.schedule_absolute(300, action3) def action4(scheduler, state): connection[0].dispose() scheduler.schedule_absolute(400, action4) def action5(scheduler, state): connection[0] = ys[0].connect() scheduler.schedule_absolute(500, action5) def action6(scheduler, state): connection[0].dispose() scheduler.schedule_absolute(550, action6) def action7(scheduler, state): connection[0] = ys[0].connect() scheduler.schedule_absolute(650, action7) def action8(scheduler, state): connection[0].dispose() scheduler.schedule_absolute(800, action8) scheduler.start() assert results.messages == [on_next(340, 8)] assert xs.subscriptions == [ subscribe(300, 400), subscribe(500, 550), subscribe(650, 800)] def test_publish_multipleconnections(self): xs = rx.never() ys = xs.pipe(ops.publish()) connection1 = ys.connect() connection2 = ys.connect() assert(connection1 == connection2) connection1.dispose() connection2.dispose() connection3 = ys.connect() assert(connection1 != connection3) connection3.dispose() def test_publish_lambda_zip_complete(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(110, 7), on_next(220, 3), on_next(280, 4), on_next(290, 1), on_next(340, 8), on_next(360, 5), on_next(370, 6), on_next(390, 7), on_next(410, 13), on_next(430, 2), on_next(450, 9), on_next(520, 11), on_next(560, 20), on_completed(600)) def create(): def mapper(_xs): return _xs.pipe( ops.zip(_xs.pipe(ops.skip(1))), ops.map(sum), ) return xs.pipe(ops.publish(mapper)) results = scheduler.start(create) assert results.messages == [ on_next(280, 7), on_next(290, 5), on_next(340, 9), on_next(360, 13), on_next(370, 11), on_next(390, 13), on_next(410, 20), on_next(430, 15), on_next(450, 11), on_next(520, 20), on_next(560, 31), on_completed(600)] assert xs.subscriptions == [subscribe(200, 600)] def test_publish_lambda_zip_error(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(110, 7), on_next(220, 3), on_next(280, 4), on_next(290, 1), on_next(340, 8), on_next(360, 5), on_next(370, 6), on_next(390, 7), on_next(410, 13), on_next(430, 2), on_next(450, 9), on_next(520, 11), on_next(560, 20), on_error(600, ex)) def create(): def mapper(_xs): return _xs.pipe( ops.zip(_xs.pipe(ops.skip(1))), ops.map(sum), ) return xs.pipe(ops.publish(mapper)) results = scheduler.start(create) assert results.messages == [ on_next(280, 7), on_next(290, 5), on_next(340, 9), on_next(360, 13), on_next(370, 11), on_next(390, 13), on_next(410, 20), on_next(430, 15), on_next(450, 11), on_next(520, 20), on_next(560, 31), on_error(600, ex)] assert xs.subscriptions == [subscribe(200, 600)] def test_publish_lambda_zip_dispose(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(110, 7), on_next(220, 3), on_next(280, 4), on_next(290, 1), on_next(340, 8), on_next(360, 5), on_next(370, 6), on_next(390, 7), on_next(410, 13), on_next(430, 2), on_next(450, 9), on_next(520, 11), on_next(560, 20), on_completed(600)) def create(): def mapper(_xs): return _xs.pipe( ops.zip(_xs.pipe(ops.skip(1))), ops.map(sum) ) return xs.pipe(ops.publish(mapper)) results = scheduler.start(create, disposed=470) assert results.messages == [ on_next(280, 7), on_next(290, 5), on_next(340, 9), on_next(360, 13), on_next(370, 11), on_next(390, 13), on_next(410, 20), on_next(430, 15), on_next(450, 11)] assert xs.subscriptions == [subscribe(200, 470)] RxPY-3.2.0/tests/test_observable/test_publishvalue.py000066400000000000000000000316441404130727200230270ustar00rootroot00000000000000import unittest import rx from rx import operators as ops from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class TestPublishValue(unittest.TestCase): def test_publishwithinitialvalue_basic(self): subscription = [None] connection = [None] ys = [None] scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(110, 7), on_next(220, 3), on_next(280, 4), on_next(290, 1), on_next(340, 8), on_next(360, 5), on_next(370, 6), on_next(390, 7), on_next(410, 13), on_next(430, 2), on_next(450, 9), on_next(520, 11), on_next(560, 20), on_completed(600)) results = scheduler.create_observer() def action0(scheduler, state): ys[0] = xs.pipe(ops.publish_value(1979)) scheduler.schedule_absolute(created, action0) def action1(scheduler, state): subscription[0] = ys[0].subscribe(results) scheduler.schedule_absolute(subscribed, action1) def action2(scheduler, state): subscription[0].dispose() scheduler.schedule_absolute(disposed, action2) def action3(scheduler, state): connection[0] = ys[0].connect() scheduler.schedule_absolute(300, action3) def action4(scheduler, state): connection[0].dispose() scheduler.schedule_absolute(400, action4) def action5(scheduler, state): connection[0] = ys[0].connect() scheduler.schedule_absolute(500, action5) def action6(scheduler, state): connection[0].dispose() scheduler.schedule_absolute(550, action6) def action7(scheduler, state): connection[0] = ys[0].connect() scheduler.schedule_absolute(650, action7) def action8(scheduler, state): connection[0].dispose() scheduler.schedule_absolute(800, action8) scheduler.start() assert results.messages == [ on_next(200, 1979), on_next(340, 8), on_next(360, 5), on_next(370, 6), on_next(390, 7), on_next(520, 11)] assert xs.subscriptions == [ subscribe(300, 400), subscribe(500, 550), subscribe(650, 800)] def test_publish_with_initial_value_error(self): connection = [None] subscription = [None] ys = [None] ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(110, 7), on_next(220, 3), on_next(280, 4), on_next(290, 1), on_next(340, 8), on_next(360, 5), on_next(370, 6), on_next(390, 7), on_next(410, 13), on_next(430, 2), on_next(450, 9), on_next(520, 11), on_next(560, 20), on_error(600, ex)) results = scheduler.create_observer() def action0(scheduler, state): ys[0] = xs.pipe(ops.publish_value(1979)) scheduler.schedule_absolute(created, action0) def action1(scheduler, state): subscription[0] = ys[0].subscribe(results) scheduler.schedule_absolute(subscribed, action1) def action2(scheduler, state): subscription[0].dispose() scheduler.schedule_absolute(disposed, action2) def action3(scheduler, state): connection[0] = ys[0].connect() scheduler.schedule_absolute(300, action3) def action4(scheduler, state): connection[0].dispose() scheduler.schedule_absolute(400, action4) def action5(scheduler, state): connection[0] = ys[0].connect() scheduler.schedule_absolute(500, action5) def action6(scheduler, state): connection[0].dispose() scheduler.schedule_absolute(800, action6) scheduler.start() assert results.messages == [ on_next(200, 1979), on_next(340, 8), on_next(360, 5), on_next(370, 6), on_next(390, 7), on_next(520, 11), on_next(560, 20), on_error(600, ex)] assert xs.subscriptions == [subscribe(300, 400), subscribe(500, 600)] def test_publish_with_initial_value_complete(self): connection = [None] subscription = [None] ys = [None] scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(110, 7), on_next(220, 3), on_next(280, 4), on_next(290, 1), on_next(340, 8), on_next(360, 5), on_next(370, 6), on_next(390, 7), on_next(410, 13), on_next(430, 2), on_next(450, 9), on_next(520, 11), on_next(560, 20), on_completed(600)) results = scheduler.create_observer() def action0(scheduler, state): ys[0] = xs.pipe(ops.publish_value(1979)) scheduler.schedule_absolute(created, action0) def action1(scheduler, state): subscription[0] = ys[0].subscribe(results) scheduler.schedule_absolute(subscribed, action1) def action2(scheduler, state): subscription[0].dispose() scheduler.schedule_absolute(disposed, action2) def action3(scheduler, state): connection[0] = ys[0].connect() scheduler.schedule_absolute(300, action3) def action4(scheduler, state): connection[0].dispose() scheduler.schedule_absolute(400, action4) def action5(scheduler, state): connection[0] = ys[0].connect() scheduler.schedule_absolute(500, action5) def action6(scheduler, state): connection[0].dispose() scheduler.schedule_absolute(800, action6) scheduler.start() assert results.messages == [ on_next(200, 1979), on_next(340, 8), on_next(360, 5), on_next(370, 6), on_next(390, 7), on_next(520, 11), on_next(560, 20), on_completed(600)] assert xs.subscriptions == [subscribe(300, 400), subscribe(500, 600)] def test_publish_with_initial_value_dispose(self): connection = [None] subscription = [None] ys = [None] scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(110, 7), on_next(220, 3), on_next(280, 4), on_next(290, 1), on_next(340, 8), on_next(360, 5), on_next(370, 6), on_next(390, 7), on_next(410, 13), on_next(430, 2), on_next(450, 9), on_next(520, 11), on_next(560, 20), on_completed(600)) results = scheduler.create_observer() def action0(scheduler, state): ys[0] = xs.pipe(ops.publish_value(1979)) scheduler.schedule_absolute(created, action0) def action1(scheduler, state): subscription[0] = ys[0].subscribe(results) scheduler.schedule_absolute(subscribed, action1) def action2(scheduler, state): subscription[0].dispose() scheduler.schedule_absolute(350, action2) def action3(scheduler, state): connection[0] = ys[0].connect() scheduler.schedule_absolute(300, action3) def action4(scheduler, state): connection[0].dispose() scheduler.schedule_absolute(400, action4) def action5(scheduler, state): connection[0] = ys[0].connect() scheduler.schedule_absolute(500, action5) def action6(scheduler, state): connection[0].dispose() scheduler.schedule_absolute(550, action6) def action7(scheduler, state): connection[0] = ys[0].connect() scheduler.schedule_absolute(650, action7) def action8(scheduler, state): connection[0].dispose() scheduler.schedule_absolute(800, action8) scheduler.start() assert results.messages == [ on_next(200, 1979), on_next(340, 8)] assert xs.subscriptions == [ subscribe(300, 400), subscribe(500, 550), subscribe(650, 800)] def test_publish_with_initial_value_multiple_connections(self): xs = rx.never() ys = xs.pipe(ops.publish_value(1979)) connection1 = ys.connect() connection2 = ys.connect() assert(connection1 == connection2) connection1.dispose() connection2.dispose() connection3 = ys.connect() assert(connection1 != connection3) def test_publish_with_initial_value_lambda_zip_complete(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(110, 7), on_next(220, 3), on_next(280, 4), on_next(290, 1), on_next(340, 8), on_next(360, 5), on_next(370, 6), on_next(390, 7), on_next(410, 13), on_next(430, 2), on_next(450, 9), on_next(520, 11), on_next(560, 20), on_completed(600)) def create(): def mapper(_xs): return _xs.pipe( ops.zip(_xs.pipe(ops.skip(1))), ops.map(sum), ) return xs.pipe(ops.publish_value(1979, mapper)) results = scheduler.start(create) assert results.messages == [ on_next(220, 1982), on_next(280, 7), on_next(290, 5), on_next(340, 9), on_next(360, 13), on_next(370, 11), on_next(390, 13), on_next(410, 20), on_next(430, 15), on_next(450, 11), on_next(520, 20), on_next(560, 31), on_completed(600)] assert xs.subscriptions == [subscribe(200, 600)] def test_publish_with_initial_value_lambda_zip_error(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(110, 7), on_next(220, 3), on_next(280, 4), on_next(290, 1), on_next(340, 8), on_next(360, 5), on_next(370, 6), on_next(390, 7), on_next(410, 13), on_next(430, 2), on_next(450, 9), on_next(520, 11), on_next(560, 20), on_error(600, ex)) def create(): def mapper(_xs): return _xs.pipe( ops.zip(_xs.pipe(ops.skip(1))), ops.map(sum), ) return xs.pipe(ops.publish_value(1979, mapper)) results = scheduler.start(create) assert results.messages == [ on_next(220, 1982), on_next(280, 7), on_next(290, 5), on_next(340, 9), on_next(360, 13), on_next(370, 11), on_next(390, 13), on_next(410, 20), on_next(430, 15), on_next(450, 11), on_next(520, 20), on_next(560, 31), on_error(600, ex)] assert xs.subscriptions == [subscribe(200, 600)] def test_publish_with_initial_value_lambda_zip_dispose(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(110, 7), on_next(220, 3), on_next(280, 4), on_next(290, 1), on_next(340, 8), on_next(360, 5), on_next(370, 6), on_next(390, 7), on_next(410, 13), on_next(430, 2), on_next(450, 9), on_next(520, 11), on_next(560, 20), on_completed(600)) def create(): def mapper(_xs): return _xs.pipe( ops.zip(_xs.pipe(ops.skip(1))), ops.map(sum), ) return xs.pipe(ops.publish_value(1979, mapper)) results = scheduler.start(create, disposed=470) assert results.messages == [ on_next(220, 1982), on_next(280, 7), on_next(290, 5), on_next(340, 9), on_next(360, 13), on_next(370, 11), on_next(390, 13), on_next(410, 20), on_next(430, 15), on_next(450, 11)] assert xs.subscriptions == [subscribe(200, 470)] RxPY-3.2.0/tests/test_observable/test_range.py000066400000000000000000000056741404130727200214240ustar00rootroot00000000000000import unittest import rx from rx import operators as ops from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class TestRange(unittest.TestCase): def test_range_zero(self): scheduler = TestScheduler() def create(): return rx.range(0, 0) results = scheduler.start(create) assert results.messages == [on_completed(200)] def test_range_one(self): scheduler = TestScheduler() def create(): return rx.range(0, 1) results = scheduler.start(create) assert results.messages == [on_next(200, 0), on_completed(200)] def test_range_five(self): scheduler = TestScheduler() def create(): return rx.range(10, 15) results = scheduler.start(create) assert results.messages == [on_next(200, 10), on_next(200, 11), on_next(200, 12), on_next(200, 13), on_next(200, 14), on_completed(200)] def test_range_dispose(self): scheduler = TestScheduler() def create(): return rx.range(-10, 5) results = scheduler.start(create, disposed=200) assert results.messages == [] def test_range_double_subscribe(self): scheduler = TestScheduler() obs = rx.range(1, 4) results = scheduler.start(lambda: obs.pipe(ops.concat(obs))) assert results.messages == [on_next(200, 1), on_next(200, 2), on_next(200, 3), on_next(200, 1), on_next(200, 2), on_next(200, 3), on_completed(200)] def test_range_only_start(self): scheduler = TestScheduler() def create(): return rx.range(5) results = scheduler.start(create) assert results.messages == [on_next(200, 0), on_next(200, 1), on_next(200, 2), on_next(200, 3), on_next(200, 4), on_completed(200)] def test_range_step_also(self): scheduler = TestScheduler() def create(): return rx.range(0, 10, 2) results = scheduler.start(create) assert results.messages == [on_next(200, 0), on_next(200, 2), on_next(200, 4), on_next(200, 6), on_next(200, 8), on_completed(200)] RxPY-3.2.0/tests/test_observable/test_reduce.py000066400000000000000000000114761404130727200215740ustar00rootroot00000000000000import unittest import rx from rx import operators as ops from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class TestReduce(unittest.TestCase): def test_reduce_with_seed_empty(self): scheduler = TestScheduler() msgs = [on_next(150, 1), on_completed(250)] xs = scheduler.create_hot_observable(msgs) def create(): return xs.pipe(ops.reduce(lambda acc, x: acc + x, 42)) res = scheduler.start(create=create).messages assert res == [on_next(250, 42), on_completed(250)] def test_reduce_with_seed_return(self): scheduler = TestScheduler() msgs = [on_next(150, 1), on_next(210, 24), on_completed(250)] xs = scheduler.create_hot_observable(msgs) def create(): return xs.pipe(ops.reduce(accumulator=lambda acc, x: acc + x, seed=42)) res = scheduler.start(create=create).messages assert res == [on_next(250, 42 + 24), on_completed(250)] def test_reduce_with_seed_on_error(self): ex = 'ex' scheduler = TestScheduler() msgs = [on_next(150, 1), on_error(210, ex)] xs = scheduler.create_hot_observable(msgs) def create(): return xs.pipe(ops.reduce(accumulator=lambda acc, x: acc + x, seed=42)) res = scheduler.start(create=create).messages assert res == [on_error(210, ex)] def test_reduce_with_seed_never(self): scheduler = TestScheduler() msgs = [on_next(150, 1)] xs = scheduler.create_hot_observable(msgs) def create(): return xs.pipe(ops.reduce(accumulator=lambda acc, x: acc + x, seed=42)) res = scheduler.start(create=create).messages assert res == [] def test_reduce_with_seed_range(self): scheduler = TestScheduler() msgs = [ on_next(150, 1), on_next(210, 0), on_next(220, 1), on_next(230, 2), on_next(240, 3), on_next(250, 4), on_completed(260)] xs = scheduler.create_hot_observable(msgs) def create(): return xs.pipe(ops.reduce(accumulator=lambda acc, x: acc + x, seed=42)) res = scheduler.start(create=create).messages assert res == [on_next(260, 10 + 42), on_completed(260)] def test_reduce_without_seed_empty(self): scheduler = TestScheduler() msgs = [on_next(150, 1), on_completed(250)] xs = scheduler.create_hot_observable(msgs) def create(): return xs.pipe(ops.reduce(accumulator=lambda acc, x: acc + x)) res = scheduler.start(create=create).messages assert len(res) == 1 assert res[0].value.kind == 'E' and res[0].value.exception is not None assert res[0].time == 250 def test_reduce_without_seed_return(self): scheduler = TestScheduler() msgs = [on_next(150, 1), on_next(210, 24), on_completed(250)] def create(): return xs.pipe(ops.reduce(accumulator=lambda acc, x: acc + x)) xs = scheduler.create_hot_observable(msgs) res = scheduler.start(create=create).messages assert res == [on_next(250, 24), on_completed(250)] def test_reduce_without_seed_on_error(self): ex = 'ex' scheduler = TestScheduler() msgs = [on_next(150, 1), on_error(210, ex)] xs = scheduler.create_hot_observable(msgs) def create(): return xs.pipe(ops.reduce(lambda acc, x: acc + x)) res = scheduler.start(create=create).messages assert res == [on_error(210, ex)] def test_reduce_without_seed_never(self): scheduler = TestScheduler() msgs = [on_next(150, 1)] xs = scheduler.create_hot_observable(msgs) def create(): return xs.pipe(ops.reduce(lambda acc, x: acc + x)) res = scheduler.start(create=create).messages assert res == [] def test_reduce_without_seed_range(self): scheduler = TestScheduler() msgs = [ on_next(150, 1), on_next(210, 0), on_next(220, 1), on_next(230, 2), on_next(240, 3), on_next(250, 4), on_completed(260)] xs = scheduler.create_hot_observable(msgs) def create(): return xs.pipe(ops.reduce(lambda acc, x: acc + x)) res = scheduler.start(create=create).messages assert res == [on_next(260, 10), on_completed(260)] def test_reduce_seed_none_does_not_crash(self): rx.empty().pipe( ops.reduce(lambda acc, v: v, seed=None) ).subscribe() if __name__ == '__main__': unittest.main() RxPY-3.2.0/tests/test_observable/test_repeat.py000066400000000000000000000173511404130727200216030ustar00rootroot00000000000000import unittest import rx from rx import operators as ops from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class RxException(Exception): pass # Helper function for raising exceptions within lambdas def _raise(ex): raise RxException(ex) class TestRepeat(unittest.TestCase): def test_repeat_value_count_zero(self): scheduler = TestScheduler() def create(): return rx.repeat_value(42, 0) results = scheduler.start(create) assert results.messages == [on_completed(200)] def test_repeat_value_count_one(self): scheduler = TestScheduler() def create(): return rx.repeat_value(42, 1) results = scheduler.start(create) assert results.messages == [on_next(200, 42), on_completed(200)] def test_repeat_value_count_ten(self): scheduler = TestScheduler() def create(): return rx.repeat_value(42, 10) results = scheduler.start(create) assert results.messages == [on_next(200, 42), on_next(200, 42), on_next(200, 42), on_next(200, 42), on_next( 200, 42), on_next(200, 42), on_next(200, 42), on_next(200, 42), on_next(200, 42), on_next(200, 42), on_completed(200)] def test_repeat_value_count_dispose(self): scheduler = TestScheduler() def create(): return rx.repeat_value(42, 10) results = scheduler.start(create, disposed=200) assert results.messages == [] def test_repeat_value(self): scheduler = TestScheduler() def create(): return rx.repeat_value(42, -1) results = scheduler.start(create, disposed=201) assert results.messages[:6] == [on_next(200, 42), on_next(200, 42), on_next( 200, 42), on_next(200, 42), on_next(200, 42), on_next(200, 42)] def test_repeat_observable_basic(self): scheduler = TestScheduler() xs = scheduler.create_cold_observable(on_next(100, 1), on_next(200, 3), on_next(150, 2), on_completed(250)) results = scheduler.start(lambda: xs.pipe(ops.repeat())) assert results.messages == [on_next(300, 1), on_next(350, 2), on_next(400, 3), on_next(550, 1), on_next(600, 2), on_next(650, 3), on_next(800, 1), on_next(850, 2), on_next(900, 3)] assert xs.subscriptions == [subscribe(200, 450), subscribe(450, 700), subscribe(700, 950), subscribe(950, 1000)] def test_repeat_observable_infinite(self): scheduler = TestScheduler() xs = scheduler.create_cold_observable(on_next(100, 1), on_next(150, 2), on_next(200, 3)) results = scheduler.start(lambda: xs.pipe(ops.repeat())) assert results.messages == [on_next(300, 1), on_next(350, 2), on_next(400, 3)] assert xs.subscriptions == [subscribe(200, 1000)] def test_repeat_observable_error(self): results = None scheduler = TestScheduler() ex = 'ex' xs = scheduler.create_cold_observable(on_next(100, 1), on_next(150, 2), on_next(200, 3), on_error(250, ex)) results = scheduler.start(lambda: xs.pipe(ops.repeat())) assert results.messages == [on_next(300, 1), on_next(350, 2), on_next(400, 3), on_error(450, ex)] assert xs.subscriptions == [subscribe(200, 450)] def test_repeat_observable_throws(self): scheduler1 = TestScheduler() xs = rx.return_value(11).pipe(ops.repeat()) xs.subscribe(lambda x: _raise('ex'), scheduler=scheduler1) with self.assertRaises(RxException): scheduler1.start() scheduler2 = TestScheduler() ys = rx.throw('ex').pipe(ops.repeat()) ys.subscribe(lambda ex: _raise('ex'), scheduler=scheduler2) with self.assertRaises(Exception): scheduler2.start() scheduler3 = TestScheduler() zs = rx.return_value(1).pipe(ops.repeat()) d = zs.subscribe(on_completed=lambda: _raise('ex'), scheduler=scheduler3) scheduler3.schedule_absolute(210, lambda sc, st: d.dispose()) scheduler3.start() # scheduler4 = TestScheduler() # xss = Observable.create(lambda o: _raise('ex')).repeat() # with self.assertRaises(RxException): # xss.subscribe(scheduler=scheduler4) def test_repeat_observable_repeat_count_basic(self): scheduler = TestScheduler() xs = scheduler.create_cold_observable(on_next(5, 1), on_next(10, 2), on_next(15, 3), on_completed(20)) results = scheduler.start(lambda: xs.pipe(ops.repeat(3))) assert results.messages == [on_next(205, 1), on_next(210, 2), on_next(215, 3), on_next(225, 1), on_next( 230, 2), on_next(235, 3), on_next(245, 1), on_next(250, 2), on_next(255, 3), on_completed(260)] assert xs.subscriptions == [subscribe(200, 220), subscribe(220, 240), subscribe(240, 260)] def test_repeat_observable_repeat_count_dispose(self): scheduler = TestScheduler() xs = scheduler.create_cold_observable(on_next(5, 1), on_next(10, 2), on_next(15, 3), on_completed(20)) results = scheduler.start(lambda: xs.pipe(ops.repeat(3)), disposed=231) assert results.messages == [on_next(205, 1), on_next(210, 2), on_next(215, 3), on_next(225, 1), on_next(230, 2)] assert xs.subscriptions == [subscribe(200, 220), subscribe(220, 231)] def test_repeat_observable_repeat_count_infinite(self): scheduler = TestScheduler() xs = scheduler.create_cold_observable(on_next(100, 1), on_next(150, 2), on_next(200, 3)) results = scheduler.start(lambda: xs.pipe(ops.repeat(3))) assert results.messages == [on_next(300, 1), on_next(350, 2), on_next(400, 3)] assert xs.subscriptions == [subscribe(200, 1000)] def test_repeat_observable_repeat_count_error(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_cold_observable(on_next(100, 1), on_next(150, 2), on_next(200, 3), on_error(250, ex)) results = scheduler.start(lambda: xs.pipe(ops.repeat(3))) assert results.messages == [on_next(300, 1), on_next(350, 2), on_next(400, 3), on_error(450, ex)] assert xs.subscriptions == [subscribe(200, 450)] def test_repeat_observable_repeat_count_throws(self): scheduler1 = TestScheduler() xs = rx.return_value(1).pipe(ops.repeat(3)) xs.subscribe(lambda x: _raise('ex'), scheduler=scheduler1) with self.assertRaises(RxException): scheduler1.start() scheduler2 = TestScheduler() ys = rx.throw('ex1').pipe(ops.repeat(3)) ys.subscribe(on_error=lambda ex: _raise('ex2'), scheduler=scheduler2) with self.assertRaises(RxException): scheduler2.start() # scheduler3 = TestScheduler() # zs = rx.return_value(1).repeat(100) # d = zs.subscribe(on_completed=lambda: _raise('ex3'), scheduler=scheduler3) # scheduler3.schedule_absolute(10, lambda sc, st: d.dispose()) # scheduler3.start() # xss = Observable.create(lambda o: _raise('ex4')).repeat(3) # with self.assertRaises(RxException): # xss.subscribe() RxPY-3.2.0/tests/test_observable/test_replay.py000066400000000000000000000542261404130727200216210ustar00rootroot00000000000000import unittest import rx from rx import operators as ops from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class TestReplay(unittest.TestCase): def test_replay_count_basic(self): connection = [None] subscription = [None] ys = [None] scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(110, 7), on_next(220, 3), on_next(280, 4), on_next(290, 1), on_next(340, 8), on_next(360, 5), on_next( 370, 6), on_next(390, 7), on_next(410, 13), on_next(430, 2), on_next(450, 9), on_next(520, 11), on_next(560, 20), on_completed(600)) results = scheduler.create_observer() def action0(scheduler, state): ys[0] = xs.pipe(ops.replay(buffer_size=3, scheduler=scheduler)) scheduler.schedule_absolute(created, action0) def action1(scheduler, state): subscription[0] = ys[0].subscribe(results, scheduler) scheduler.schedule_absolute(450, action1) def action2(scheduler, state): subscription[0].dispose() scheduler.schedule_absolute(disposed, action2) def action3(scheduler, state): connection[0] = ys[0].connect(scheduler) scheduler.schedule_absolute(300, action3) def action4(scheduler, state): connection[0].dispose() scheduler.schedule_absolute(400, action4) def action5(scheduler, state): connection[0] = ys[0].connect(scheduler) scheduler.schedule_absolute(500, action5) def action6(scheduler, state): connection[0].dispose() scheduler.schedule_absolute(550, action6) def action7(scheduler, state): connection[0] = ys[0].connect(scheduler) scheduler.schedule_absolute(650, action7) def action8(scheduler, state): connection[0].dispose() scheduler.schedule_absolute(800, action8) scheduler.start() assert results.messages == [on_next(450, 5), on_next(450, 6), on_next(450, 7), on_next(520, 11)] assert xs.subscriptions == [subscribe(300, 400), subscribe(500, 550), subscribe(650, 800)] def test_replay_count_error(self): connection = [None] subscription = [None] ys = [None] ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(110, 7), on_next(220, 3), on_next(280, 4), on_next(290, 1), on_next(340, 8), on_next(360, 5), on_next( 370, 6), on_next(390, 7), on_next(410, 13), on_next(430, 2), on_next(450, 9), on_next(520, 11), on_next(560, 20), on_error(600, ex)) results = scheduler.create_observer() def action0(scheduler, state): ys[0] = xs.pipe(ops.replay(buffer_size=3, scheduler=scheduler)) scheduler.schedule_absolute(created, action0) def action1(scheduler, state): subscription[0] = ys[0].subscribe(results) scheduler.schedule_absolute(450, action1) def action2(scheduler, state): subscription[0].dispose() scheduler.schedule_absolute(disposed, action2) def action3(scheduler, state): connection[0] = ys[0].connect(scheduler) scheduler.schedule_absolute(300, action3) def action4(scheduler, state): connection[0].dispose() scheduler.schedule_absolute(400, action4) def action5(scheduler, state): connection[0] = ys[0].connect(scheduler) scheduler.schedule_absolute(500, action5) def action6(scheduler, state): connection[0].dispose() scheduler.schedule_absolute(800, action6) scheduler.start() assert results.messages == [on_next(450, 5), on_next(450, 6), on_next( 450, 7), on_next(520, 11), on_next(560, 20), on_error(600, ex)] assert xs.subscriptions == [subscribe(300, 400), subscribe(500, 600)] def test_replay_count_complete(self): connection = [None] subscription = [None] ys = [None] scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(110, 7), on_next(220, 3), on_next(280, 4), on_next(290, 1), on_next(340, 8), on_next(360, 5), on_next( 370, 6), on_next(390, 7), on_next(410, 13), on_next(430, 2), on_next(450, 9), on_next(520, 11), on_next(560, 20), on_completed(600)) results = scheduler.create_observer() def action0(scheduler, state): ys[0] = xs.pipe(ops.replay(buffer_size=3, scheduler=scheduler)) scheduler.schedule_absolute(created, action0) def action1(scehduler, state): subscription[0] = ys[0].subscribe(results) scheduler.schedule_absolute(450, action1) def action2(scheduler, state): subscription[0].dispose() scheduler.schedule_absolute(disposed, action2) def action3(scheduler, state): connection[0] = ys[0].connect(scheduler) scheduler.schedule_absolute(300, action3) def action4(scheduler, state): connection[0].dispose() scheduler.schedule_absolute(400, action4) def action5(scheduler, state): connection[0] = ys[0].connect(scheduler) scheduler.schedule_absolute(500, action5) def action(scheduler, state): connection[0].dispose() scheduler.schedule_absolute(800, action) scheduler.start() assert results.messages == [on_next(450, 5), on_next(450, 6), on_next( 450, 7), on_next(520, 11), on_next(560, 20), on_completed(600)] assert xs.subscriptions == [subscribe(300, 400), subscribe(500, 600)] def test_replay_count_dispose(self): connection = [None] subscription = [None] ys = [None] scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(110, 7), on_next(220, 3), on_next(280, 4), on_next(290, 1), on_next(340, 8), on_next(360, 5), on_next( 370, 6), on_next(390, 7), on_next(410, 13), on_next(430, 2), on_next(450, 9), on_next(520, 11), on_next(560, 20), on_completed(600)) results = scheduler.create_observer() def action0(scheduler, state): ys[0] = xs.pipe(ops.replay(buffer_size=3, scheduler=scheduler)) scheduler.schedule_absolute(created, action0) def action1(scheduler, state): subscription[0] = ys[0].subscribe(results) scheduler.schedule_absolute(450, action1) def action2(scheduler, state): subscription[0].dispose() scheduler.schedule_absolute(475, action2) def action3(scheduler, state): connection[0] = ys[0].connect(scheduler) scheduler.schedule_absolute(300, action3) def action4(scheduler, state): connection[0].dispose() scheduler.schedule_absolute(400, action4) def action5(scheduler, state): connection[0] = ys[0].connect(scheduler) scheduler.schedule_absolute(500, action5) def action6(scheduler, state): connection[0].dispose() scheduler.schedule_absolute(550, action6) def action7(scheduler, state): connection[0] = ys[0].connect(scheduler) scheduler.schedule_absolute(650, action7) def action8(scheduler, state): connection[0].dispose() scheduler.schedule_absolute(800, action8) scheduler.start() assert results.messages == [on_next(450, 5), on_next(450, 6), on_next(450, 7)] assert xs.subscriptions == [subscribe(300, 400), subscribe(500, 550), subscribe(650, 800)] def test_replay_count_multiple_connections(self): xs = rx.never() ys = xs.pipe(ops.replay(None, 3)) connection1 = ys.connect() connection2 = ys.connect() assert connection1 == connection2 connection1.dispose() connection2.dispose() connection3 = ys.connect() assert connection1 != connection3 # def test_replay_count_lambda_zip_complete(self): # scheduler = TestScheduler() # xs = scheduler.create_hot_observable(on_next(110, 7), on_next(220, 3), on_next(280, 4), on_next(290, 1), on_next(340, 8), on_next(360, 5), on_next(370, 6), on_next(390, 7), on_next(410, 13), on_next(430, 2), on_next(450, 9), on_next(520, 11), on_next(560, 20), on_completed(600)) # def action(): # def mapper(_xs): # return _xs.take(6).repeat() # return xs.replay(mapper, 3, scheduler=scheduler) # results = scheduler.start(action, disposed=610) # assert results.messages == [on_next(220, 3), on_next(280, 4), on_next(290, 1), on_next(340, 8), on_next(360, 5), on_next(370, 6), on_next(370, 8), on_next(370, 5), on_next(370, 6), on_next(390, 7), on_next(410, 13), on_next(430, 2), on_next(430, 7), on_next(430, 13), on_next(430, 2), on_next(450, 9), on_next(520, 11), on_next(560, 20), on_next(560, 9), on_next(560, 11), on_next(560, 20), on_next(600, 9), on_next(600, 11), on_next(600, 20), on_next(600, 9), on_next(600, 11), on_next(600, 20)] # assert xs.subscriptions == [subscribe(200, 600)] # def test_replay_count_lambda_zip_error(self): # ex = 'ex' # scheduler = TestScheduler() # xs = scheduler.create_hot_observable(on_next(110, 7), on_next(220, 3), on_next(280, 4), on_next(290, 1), on_next(340, 8), on_next(360, 5), on_next(370, 6), on_next(390, 7), on_next(410, 13), on_next(430, 2), on_next(450, 9), on_next(520, 11), on_next(560, 20), on_error(600, ex)) # def create(): # def mapper(_xs): # return _xs.take(6).repeat() # return xs.replay(mapper, 3, None) # results = scheduler.start(create) # assert results.messages == [on_next(221, 3), on_next(281, 4), on_next(291, 1), on_next(341, 8), on_next(361, 5), on_next(371, 6), on_next(372, 8), on_next(373, 5), on_next(374, 6), on_next(391, 7), on_next(411, 13), on_next(431, 2), on_next(432, 7), on_next(433, 13), on_next(434, 2), on_next(450, 9), on_next(520, 11), on_next(560, 20), on_next(562, 9), on_next(563, 11), on_next(564, 20), on_error(600, ex)] # assert xs.subscriptions == [subscribe(200, 600)] # def test_replay_count_lambda_zip_dispose(self): # scheduler = TestScheduler() # xs = scheduler.create_hot_observable(on_next(110, 7), on_next(220, 3), on_next(280, 4), on_next(290, 1), on_next(340, 8), on_next(360, 5), on_next(370, 6), on_next(390, 7), on_next(410, 13), on_next(430, 2), on_next(450, 9), on_next(520, 11), on_next(560, 20), on_completed(600)) # def create(): # def mapper(_xs): # return _xs.take(6).repeat() # return xs.replay(mapper, 3, None) # results = scheduler.start(create, disposed=470) # assert results.messages == [on_next(221, 3), on_next(281, 4), on_next(291, 1), on_next(341, 8), on_next(361, 5), on_next(371, 6), on_next(372, 8), on_next(373, 5), on_next(374, 6), on_next(391, 7), on_next(411, 13), on_next(431, 2), on_next(432, 7), on_next(433, 13), on_next(434, 2), on_next(450, 9)] # assert xs.subscriptions == [subscribe(200, 470)] def test_replay_time_basic(self): subscription = [None] connection = [None] ys = [None] scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(110, 7), on_next(220, 3), on_next(280, 4), on_next(290, 1), on_next(340, 8), on_next(360, 5), on_next( 370, 6), on_next(390, 7), on_next(410, 13), on_next(430, 2), on_next(450, 9), on_next(520, 11), on_next(560, 20), on_completed(600)) results = scheduler.create_observer() def action0(scheduler, state): ys[0] = xs.pipe(ops.replay(window=150, scheduler=scheduler)) scheduler.schedule_absolute(created, action0) def action1(scheduler, state): subscription[0] = ys[0].subscribe(results) scheduler.schedule_absolute(450, action1) def action2(scheduler, state): subscription[0].dispose() scheduler.schedule_absolute(disposed, action2) def action3(scheduler, state): connection[0] = ys[0].connect(scheduler) scheduler.schedule_absolute(300, action3) def action4(scheduler, state): connection[0].dispose() scheduler.schedule_absolute(400, action4) def action5(scheduler, state): connection[0] = ys[0].connect(scheduler) scheduler.schedule_absolute(500, action5) def action6(scheduler, state): connection[0].dispose() scheduler.schedule_absolute(550, action6) def action7(scheduler, state): connection[0] = ys[0].connect(scheduler) scheduler.schedule_absolute(650, action7) def action8(scheduler, state): connection[0].dispose() scheduler.schedule_absolute(800, action8) scheduler.start() assert results.messages == [on_next(450, 8), on_next( 450, 5), on_next(450, 6), on_next(450, 7), on_next(520, 11)] assert xs.subscriptions == [subscribe(300, 400), subscribe(500, 550), subscribe(650, 800)] def test_replay_time_error(self): subscription = [None] connection = [None] ys = [None] ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(110, 7), on_next(220, 3), on_next(280, 4), on_next(290, 1), on_next(340, 8), on_next(360, 5), on_next( 370, 6), on_next(390, 7), on_next(410, 13), on_next(430, 2), on_next(450, 9), on_next(520, 11), on_next(560, 20), on_error(600, ex)) results = scheduler.create_observer() def action0(scheduler, state): ys[0] = xs.pipe(ops.replay(window=75, scheduler=scheduler)) scheduler.schedule_absolute(created, action0) def action1(scheduler, state): subscription[0] = ys[0].subscribe(results, scheduler) scheduler.schedule_absolute(450, action1) def action2(scheduler, state): subscription[0].dispose() scheduler.schedule_absolute(disposed, action2) def action3(scheduler, state): connection[0] = ys[0].connect(scheduler) scheduler.schedule_absolute(300, action3) def action4(scheduler, state): connection[0].dispose() scheduler.schedule_absolute(400, action4) def action5(scheduler, state): connection[0] = ys[0].connect(scheduler) scheduler.schedule_absolute(500, action5) def action6(scheduler, state): connection[0].dispose() scheduler.schedule_absolute(800, action6) scheduler.start() assert results.messages == [on_next(450, 7), on_next(520, 11), on_next(560, 20), on_error(600, ex)] assert xs.subscriptions == [subscribe(300, 400), subscribe(500, 600)] def test_replay_time_complete(self): subscription = [None] connection = [None] ys = [None] scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(110, 7), on_next(220, 3), on_next(280, 4), on_next(290, 1), on_next(340, 8), on_next(360, 5), on_next( 370, 6), on_next(390, 7), on_next(410, 13), on_next(430, 2), on_next(450, 9), on_next(520, 11), on_next(560, 20), on_completed(600)) results = scheduler.create_observer() def action0(scheduler, state): ys[0] = xs.pipe(ops.replay(window=85, scheduler=scheduler)) scheduler.schedule_absolute(created, action0) def action1(scheduler, state): subscription[0] = ys[0].subscribe(results, scheduler) scheduler.schedule_absolute(450, action1) def action2(scheduler, state): subscription[0].dispose() scheduler.schedule_absolute(disposed, action2) def action3(scheduler, state): connection[0] = ys[0].connect(scheduler) scheduler.schedule_absolute(300, action3) def action4(scheduler, state): connection[0].dispose() scheduler.schedule_absolute(400, action4) def action5(scheduler, state): connection[0] = ys[0].connect(scheduler) scheduler.schedule_absolute(500, action5) def action6(scheduler, state): connection[0].dispose() scheduler.schedule_absolute(800, action6) scheduler.start() assert results.messages == [on_next(450, 6), on_next( 450, 7), on_next(520, 11), on_next(560, 20), on_completed(600)] assert xs.subscriptions == [subscribe(300, 400), subscribe(500, 600)] def test_replay_time_dispose(self): subscription = [None] connection = [None] ys = [None] scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(110, 7), on_next(220, 3), on_next(280, 4), on_next(290, 1), on_next(340, 8), on_next(360, 5), on_next( 370, 6), on_next(390, 7), on_next(410, 13), on_next(430, 2), on_next(450, 9), on_next(520, 11), on_next(560, 20), on_completed(600)) results = scheduler.create_observer() def action0(scheduler, state): ys[0] = xs.pipe(ops.replay(window=100, scheduler=scheduler)) scheduler.schedule_absolute(created, action0) def action1(scheduler, state): subscription[0] = ys[0].subscribe(results, scheduler) scheduler.schedule_absolute(450, action1) def action2(scheduler, state): subscription[0].dispose() scheduler.schedule_absolute(475, action2) def action3(scheduler, state): connection[0] = ys[0].connect(scheduler) scheduler.schedule_absolute(300, action3) def action4(scheduler, state): connection[0].dispose() scheduler.schedule_absolute(400, action4) def action5(scheduler, state): connection[0] = ys[0].connect(scheduler) scheduler.schedule_absolute(500, action5) def action6(scheduler, state): connection[0].dispose() scheduler.schedule_absolute(550, action6) def action7(scheduler, state): connection[0] = ys[0].connect(scheduler) scheduler.schedule_absolute(650, action7) def action8(scheduler, state): connection[0].dispose() scheduler.schedule_absolute(800, action8) scheduler.start() assert results.messages == [on_next(450, 5), on_next(450, 6), on_next(450, 7)] assert xs.subscriptions == [subscribe(300, 400), subscribe(500, 550), subscribe(650, 800)] def test_replay_time_multiple_connections(self): xs = rx.never() ys = xs.pipe(ops.replay(window=100)) connection1 = ys.connect() connection2 = ys.connect() assert connection1 == connection2 connection1.dispose() connection2.dispose() connection3 = ys.connect() assert connection1 != connection3 # def test_replay_time_lambda_zip_complete(self): # scheduler = TestScheduler() # xs = scheduler.create_hot_observable( # on_next(110, 7), on_next(220, 3), on_next(280, 4), # on_next(290, 1), on_next(340, 8), on_next(360, 5), # on_next(370, 6), on_next(390, 7), on_next(410, 13), # on_next(430, 2), on_next(450, 9), on_next(520, 11), # on_next(560, 20), on_completed(600)) # def create(): # def mapper(_xs): # return _xs.pipe( # ops.take(6), # ops.repeat() # ) # return xs.pipe(ops.replay(mapper, None, 50)) # results = scheduler.start(create, disposed=610) # assert results.messages == [ # on_next(220, 3), on_next(280, 4), on_next(290, 1), # on_next(340, 8), on_next(360, 5), on_next(370, 6), # on_next(370, 8), on_next(370, 5), on_next(370, 6), # on_next(390, 7), on_next(410, 13), on_next(430, 2), # on_next(430, 7), on_next(430, 13), on_next(430, 2), # on_next(450, 9), on_next(520, 11), on_next(560, 20), # on_next(560, 11), on_next(560, 20), on_next(600, 20), # on_next(600, 20), on_next(600, 20), on_next(600, 20)] # assert xs.subscriptions == [subscribe(200, 600)] # def test_replay_time_lambda_zip_error(self): # ex = 'ex' # scheduler = TestScheduler() # xs = scheduler.create_hot_observable(on_next(110, 7), on_next(220, 3), on_next(280, 4), on_next(290, 1), on_next(340, 8), on_next(360, 5), on_next(370, 6), on_next(390, 7), on_next(410, 13), on_next(430, 2), on_next(450, 9), on_next(520, 11), on_next(560, 20), on_error(600, ex)) # def create(): # def mapper(_xs): # return _xs.take(6).repeat() # return xs.replay(mapper, None, 50) # results = scheduler.start(create) # assert results.messages == [on_next(221, 3), on_next(281, 4), on_next(291, 1), on_next(341, 8), on_next(361, 5), on_next(371, 6), on_next(372, 8), on_next(373, 5), on_next(374, 6), on_next(391, 7), on_next(411, 13), on_next(431, 2), on_next(432, 7), on_next(433, 13), on_next(434, 2), on_next(450, 9), on_next(520, 11), on_next(560, 20), on_next(562, 11), on_next(563, 20), on_error(600, ex)] # assert xs.subscriptions == [subscribe(200, 600)] # def test_replay_time_lambda_zip_dispose(self): # scheduler = TestScheduler() # xs = scheduler.create_hot_observable(on_next(110, 7), on_next(220, 3), on_next(280, 4), on_next(290, 1), on_next(340, 8), on_next(360, 5), on_next(370, 6), on_next(390, 7), on_next(410, 13), on_next(430, 2), on_next(450, 9), on_next(520, 11), on_next(560, 20), on_completed(600)) # def create(): # def mapper(_xs): # return _xs.take(6).repeat() # return xs.replay(mapper, None, 50) # results = scheduler.start(create, disposed=470) # assert results.messages == [on_next(221, 3), on_next(281, 4), on_next(291, 1), on_next(341, 8), on_next(361, 5), on_next(371, 6), on_next(372, 8), on_next(373, 5), on_next(374, 6), on_next(391, 7), on_next(411, 13), on_next(431, 2), on_next(432, 7), on_next(433, 13), on_next(434, 2), on_next(450, 9)] # assert xs.subscriptions == [subscribe(200, 470)] RxPY-3.2.0/tests/test_observable/test_retry.py000066400000000000000000000140741404130727200214670ustar00rootroot00000000000000import unittest import pytest import rx from rx import operators as ops from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class RxException(Exception): pass # Helper function for raising exceptions within lambdas def _raise(ex): raise RxException(ex) class TestRetry(unittest.TestCase): def test_retry_observable_basic(self): scheduler = TestScheduler() xs = scheduler.create_cold_observable( on_next(100, 1), on_next(150, 2), on_next(200, 3), on_completed(250)) results = scheduler.start(lambda: xs.pipe(ops.retry())) assert results.messages == [ on_next(300, 1), on_next(350, 2), on_next(400, 3), on_completed(450)] assert xs.subscriptions == [subscribe(200, 450)] def test_retry_observable_infinite(self): scheduler = TestScheduler() xs = scheduler.create_cold_observable( on_next(100, 1), on_next(150, 2), on_next(200, 3)) results = scheduler.start(lambda: xs.pipe(ops.retry())) assert results.messages == [ on_next(300, 1), on_next(350, 2), on_next(400, 3)] assert xs.subscriptions == [subscribe(200, 1000)] def test_retry_observable_error(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_cold_observable( on_next(100, 1), on_next(150, 2), on_next(200, 3), on_error(250, ex)) results = scheduler.start(lambda: xs.pipe(ops.retry()), disposed=1100) assert results.messages == [ on_next(300, 1), on_next(350, 2), on_next(400, 3), on_next(550, 1), on_next(600, 2), on_next(650, 3), on_next(800, 1), on_next(850, 2), on_next(900, 3), on_next(1050, 1)] assert xs.subscriptions == [ subscribe(200, 450), subscribe(450, 700), subscribe(700, 950), subscribe(950, 1100)] def test_retry_observable_throws(self): scheduler1 = TestScheduler() xs = rx.return_value(1).pipe(ops.retry()) xs.subscribe(lambda x: _raise('ex'), scheduler=scheduler1) with pytest.raises(RxException): scheduler1.start() scheduler2 = TestScheduler() ys = rx.throw('ex').pipe(ops.retry()) d = ys.subscribe(on_error=lambda ex: _raise('ex'), scheduler=scheduler2) scheduler2.schedule_absolute(210, lambda sc, st: d.dispose()) scheduler2.start() scheduler3 = TestScheduler() zs = rx.return_value(1).pipe(ops.retry()) zs.subscribe(on_completed=lambda: _raise('ex'), scheduler=scheduler3) with pytest.raises(RxException): scheduler3.start() def test_retry_observable_retry_count_basic(self): scheduler = TestScheduler() ex = 'ex' xs = scheduler.create_cold_observable( on_next(5, 1), on_next(10, 2), on_next(15, 3), on_error(20, ex)) results = scheduler.start(lambda: xs.pipe(ops.retry(3))) assert results.messages == [ on_next(205, 1), on_next(210, 2), on_next(215, 3), on_next(225, 1), on_next(230, 2), on_next(235, 3), on_next(245, 1), on_next(250, 2), on_next(255, 3), on_error(260, ex)] assert xs.subscriptions == [ subscribe(200, 220), subscribe(220, 240), subscribe(240, 260)] def test_retry_observable_retry_count_dispose(self): scheduler = TestScheduler() ex = 'ex' xs = scheduler.create_cold_observable( on_next(5, 1), on_next(10, 2), on_next(15, 3), on_error(20, ex)) results = scheduler.start(lambda: xs.pipe(ops.retry(3)), disposed=231) assert results.messages == [ on_next(205, 1), on_next(210, 2), on_next(215, 3), on_next(225, 1), on_next(230, 2)] assert xs.subscriptions == [subscribe(200, 220), subscribe(220, 231)] def test_retry_observable_retry_count_dispose_ii(self): scheduler = TestScheduler() xs = scheduler.create_cold_observable( on_next(100, 1), on_next(150, 2), on_next(200, 3)) results = scheduler.start(lambda: xs.pipe(ops.retry(3))) assert results.messages == [ on_next(300, 1), on_next(350, 2), on_next(400, 3)] assert xs.subscriptions == [subscribe(200, 1000)] def test_retry_observable_retry_count_dispose_iii(self): scheduler = TestScheduler() xs = scheduler.create_cold_observable( on_next(100, 1), on_next(150, 2), on_next(200, 3), on_completed(250)) results = scheduler.start(lambda: xs.pipe(ops.retry(3))) assert results.messages == [ on_next(300, 1), on_next(350, 2), on_next(400, 3), on_completed(450)] assert xs.subscriptions == [subscribe(200, 450)] def test_retry_observable_retry_count_throws(self): scheduler1 = TestScheduler() xs = rx.return_value(1).pipe(ops.retry(3)) xs.subscribe(lambda x: _raise('ex'), scheduler=scheduler1) self.assertRaises(RxException, scheduler1.start) scheduler2 = TestScheduler() ys = rx.throw('ex').pipe(ops.retry(100)) d = ys.subscribe(on_error=lambda ex: _raise('ex'), scheduler=scheduler2) def dispose(_, __): d.dispose() scheduler2.schedule_absolute(0, dispose) scheduler2.start() scheduler3 = TestScheduler() zs = rx.return_value(1).pipe(ops.retry(100)) zs.subscribe(on_completed=lambda: _raise('ex'), scheduler=scheduler3) with pytest.raises(RxException): scheduler3.start() xss = rx.create(lambda o: _raise('ex')).pipe(ops.retry(100)) with pytest.raises(Exception): xss.subscribe() if __name__ == '__main__': unittest.main() RxPY-3.2.0/tests/test_observable/test_returnvalue.py000066400000000000000000000042701404130727200226730ustar00rootroot00000000000000import unittest import rx from rx.testing import TestScheduler, ReactiveTest from rx.disposable import SerialDisposable on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class RxException(Exception): pass # Helper function for raising exceptions within lambdas def _raise(ex): raise RxException(ex) class TestReturnValue(unittest.TestCase): def test_return_basic(self): scheduler = TestScheduler() def factory(): return rx.return_value(42) results = scheduler.start(factory) assert results.messages == [ on_next(200, 42), on_completed(200)] def test_return_disposed(self): scheduler = TestScheduler() def factory(): return rx.return_value(42) results = scheduler.start(factory, disposed=200) assert results.messages == [] def test_return_disposed_after_next(self): scheduler = TestScheduler() d = SerialDisposable() xs = rx.return_value(42) results = scheduler.create_observer() def action(scheduler, state): def on_next(x): d.dispose() results.on_next(x) def on_error(e): results.on_error(e) def on_completed(): results.on_completed() d.disposable = xs.subscribe(on_next, on_error, on_completed, scheduler=scheduler) return d.disposable scheduler.schedule_absolute(100, action) scheduler.start() assert results.messages == [on_next(100, 42)] def test_return_observer_throws(self): scheduler1 = TestScheduler() xs = rx.return_value(1) xs.subscribe(lambda x: _raise('ex'), scheduler=scheduler1) self.assertRaises(RxException, scheduler1.start) scheduler2 = TestScheduler() ys = rx.return_value(1) ys.subscribe(lambda x: x, lambda ex: ex, lambda: _raise('ex'), scheduler=scheduler2) self.assertRaises(RxException, scheduler2.start) RxPY-3.2.0/tests/test_observable/test_sample.py000066400000000000000000000043051404130727200215770ustar00rootroot00000000000000import unittest import rx from rx import operators as ops from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class RxException(Exception): pass # Helper function for raising exceptions within lambdas def _raise(ex): raise RxException(ex) class TestSample(unittest.TestCase): def test_sample_regular(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_next(210, 2), on_next(230, 3), on_next( 260, 4), on_next(300, 5), on_next(350, 6), on_next(380, 7), on_completed(390)) def create(): return xs.pipe(ops.sample(50)) results = scheduler.start(create) assert results.messages == [on_next(250, 3), on_next( 300, 5), on_next(350, 6), on_next(400, 7), on_completed(400)] def test_sample_error_in_flight(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_next(210, 2), on_next( 230, 3), on_next(260, 4), on_next(300, 5), on_next(310, 6), on_error(330, ex)) def create(): return xs.pipe(ops.sample(50)) results = scheduler.start(create) assert results.messages == [on_next(250, 3), on_next(300, 5), on_error(330, ex)] def test_sample_empty(self): scheduler = TestScheduler() def create(): return rx.empty().pipe(ops.sample(0)) results = scheduler.start(create) assert results.messages == [on_completed(200)] def test_sample_error(self): ex = 'ex' scheduler = TestScheduler() def create(): return rx.throw(ex).pipe(ops.sample(0)) results = scheduler.start(create) assert results.messages == [on_error(200, ex)] def test_sample_never(self): scheduler = TestScheduler() def create(): return rx.never().pipe(ops.sample(1)) results = scheduler.start(create) assert results.messages == [] RxPY-3.2.0/tests/test_observable/test_scan.py000066400000000000000000000135441404130727200212470ustar00rootroot00000000000000import unittest from rx import never, operators as _ from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class TestScan(unittest.TestCase): def test_scan_seed_never(self): scheduler = TestScheduler() seed = 42 def create(): def func(acc, x): return acc + x return never().pipe(_.scan(seed=seed, accumulator=func)) results = scheduler.start(create) assert results.messages == [] def test_scan_seed_empty(self): scheduler = TestScheduler() seed = 42 xs = scheduler.create_hot_observable(on_next(150, 1), on_completed(250)) def create(): return xs.pipe(_.scan(lambda acc, x: acc + x, seed=seed)) results = scheduler.start(create).messages assert(len(results) == 1) assert(results[0].value.kind == 'C' and results[0].time == 250) def test_scan_seed_return(self): scheduler = TestScheduler() seed = 42 xs = scheduler.create_hot_observable(on_next(150, 1), on_next(220, 2), on_completed(250)) def create(): return xs.pipe(_.scan(lambda acc, x: acc + x, seed=seed)) results = scheduler.start(create).messages assert(len(results) == 2) assert(results[0].value.kind == 'N' and results[0].value.value == seed + 2 and results[0].time == 220) assert(results[1].value.kind == 'C' and results[1].time == 250) def test_scan_seed_on_error(self): ex = 'ex' scheduler = TestScheduler() seed = 42 xs = scheduler.create_hot_observable(on_next(150, 1), on_error(250, ex)) def create(): return xs.pipe(_.scan(seed, lambda acc, x: acc + x)) results = scheduler.start(create).messages assert(len(results) == 1) assert(results[0].value.kind == 'E' and results[0].value.exception == ex and results[0].time == 250) def test_scan_seed_somedata(self): scheduler = TestScheduler() seed = 1 xs = scheduler.create_hot_observable(on_next(150, 1), on_next(210, 2), on_next( 220, 3), on_next(230, 4), on_next(240, 5), on_completed(250)) def create(): return xs.pipe(_.scan(lambda acc, x: acc + x, seed=seed)) results = scheduler.start(create).messages assert(len(results) == 5) assert(results[0].value.kind == 'N' and results[0].value.value == seed + 2 and results[0].time == 210) assert(results[1].value.kind == 'N' and results[1].value.value == seed + 2 + 3 and results[1].time == 220) assert(results[2].value.kind == 'N' and results[2].value.value == seed + 2 + 3 + 4 and results[2].time == 230) assert(results[3].value.kind == 'N' and results[3].value.value == seed + 2 + 3 + 4 + 5 and results[3].time == 240) assert(results[4].value.kind == 'C' and results[4].time == 250) def test_scan_noseed_never(self): scheduler = TestScheduler() def create(): return never().pipe(_.scan(lambda acc, x: acc + x)) results = scheduler.start(create) assert results.messages == [] def test_scan_noseed_empty(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_completed(250)) def create(): return xs.pipe(_.scan(lambda acc, x: acc + x)) results = scheduler.start(create).messages assert len(results) == 1 assert results[0].value.kind == 'C' and results[0].time == 250 def test_scan_noseed_return(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_next(220, 2), on_completed(250)) def create(): def func(acc, x): if acc is None: acc = 0 return acc + x return xs.pipe(_.scan(accumulator=func)) results = scheduler.start(create).messages assert len(results) == 2 assert results[0].value.kind == 'N' and results[0].time == 220 and results[0].value.value == 2 assert results[1].value.kind == 'C' and results[1].time == 250 def test_scan_noseed_on_error(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_error(250, ex)) def create(): def func(acc, x): if acc is None: acc = 0 return acc + x return xs.pipe(_.scan(func)) results = scheduler.start(create).messages assert len(results) == 1 assert results[0].value.kind == 'E' and results[0].time == 250 and results[0].value.exception == ex def test_scan_noseed_somedata(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_next(210, 2), on_next( 220, 3), on_next(230, 4), on_next(240, 5), on_completed(250)) def create(): def func(acc, x): if acc is None: acc = 0 return acc + x return xs.pipe(_.scan(func)) results = scheduler.start(create).messages assert len(results) == 5 assert results[0].value.kind == 'N' and results[0].time == 210 and results[0].value.value == 2 assert results[1].value.kind == 'N' and results[1].time == 220 and results[1].value.value == 2 + 3 assert results[2].value.kind == 'N' and results[2].time == 230 and results[2].value.value == 2 + 3 + 4 assert results[3].value.kind == 'N' and results[3].time == 240 and results[3].value.value == 2 + 3 + 4 + 5 assert results[4].value.kind == 'C' and results[4].time == 250 RxPY-3.2.0/tests/test_observable/test_sequenceequal.py000066400000000000000000000361171404130727200231640ustar00rootroot00000000000000import unittest import rx from rx import operators as ops from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class TestSequenceEqual(unittest.TestCase): def test_sequence_equal_equal(self): scheduler = TestScheduler() msgs1 = [on_next(110, 1), on_next(190, 2), on_next(240, 3), on_next(290, 4), on_next(310, 5), on_next(340, 6), on_next(450, 7), on_completed(510)] msgs2 = [on_next(90, 1), on_next(270, 3), on_next(280, 4), on_next(300, 5), on_next(330, 6), on_next(340, 7), on_completed(720)] xs = scheduler.create_hot_observable(msgs1) ys = scheduler.create_hot_observable(msgs2) results = scheduler.start(lambda: xs.pipe(ops.sequence_equal(ys))) assert results.messages == [on_next(720, True), on_completed(720)] assert xs.subscriptions == [subscribe(200, 510)] assert ys.subscriptions == [subscribe(200, 720)] def test_sequence_equal_equal_sym(self): scheduler = TestScheduler() msgs1 = [on_next(110, 1), on_next(190, 2), on_next(240, 3), on_next(290, 4), on_next(310, 5), on_next(340, 6), on_next(450, 7), on_completed(510)] msgs2 = [on_next(90, 1), on_next(270, 3), on_next(280, 4), on_next(300, 5), on_next(330, 6), on_next(340, 7), on_completed(720)] xs = scheduler.create_hot_observable(msgs1) ys = scheduler.create_hot_observable(msgs2) results = scheduler.start(lambda: ys.pipe(ops.sequence_equal(xs))) assert results.messages == [on_next(720, True), on_completed(720)] assert xs.subscriptions == [subscribe(200, 510)] assert ys.subscriptions == [subscribe(200, 720)] def test_sequence_equal_not_equal_left(self): scheduler = TestScheduler() msgs1 = [on_next(110, 1), on_next(190, 2), on_next(240, 3), on_next(290, 4), on_next(310, 0), on_next(340, 6), on_next(450, 7), on_completed(510)] msgs2 = [on_next(90, 1), on_next(270, 3), on_next(280, 4), on_next(300, 5), on_next(330, 6), on_next(340, 7), on_completed(720)] xs = scheduler.create_hot_observable(msgs1) ys = scheduler.create_hot_observable(msgs2) results = scheduler.start(lambda: xs.pipe(ops.sequence_equal(ys))) assert results.messages == [on_next(310, False), on_completed(310)] assert xs.subscriptions == [subscribe(200, 310)] assert ys.subscriptions == [subscribe(200, 310)] def test_sequence_equal_not_equal_left_sym(self): scheduler = TestScheduler() msgs1 = [on_next(110, 1), on_next(190, 2), on_next(240, 3), on_next(290, 4), on_next(310, 0), on_next(340, 6), on_next(450, 7), on_completed(510)] msgs2 = [on_next(90, 1), on_next(270, 3), on_next(280, 4), on_next(300, 5), on_next(330, 6), on_next(340, 7), on_completed(720)] xs = scheduler.create_hot_observable(msgs1) ys = scheduler.create_hot_observable(msgs2) results = scheduler.start(lambda: ys.pipe(ops.sequence_equal(xs))) assert results.messages == [on_next(310, False), on_completed(310)] assert xs.subscriptions == [subscribe(200, 310)] assert ys.subscriptions == [subscribe(200, 310)] def test_sequence_equal_not_equal_right(self): scheduler = TestScheduler() msgs1 = [on_next(110, 1), on_next(190, 2), on_next(240, 3), on_next(290, 4), on_next(310, 5), on_next(340, 6), on_next(450, 7), on_completed(510)] msgs2 = [on_next(90, 1), on_next(270, 3), on_next(280, 4), on_next(300, 5), on_next(330, 6), on_next(340, 7), on_next(350, 8)] xs = scheduler.create_hot_observable(msgs1) ys = scheduler.create_hot_observable(msgs2) results = scheduler.start(lambda: xs.pipe(ops.sequence_equal(ys))) assert results.messages == [on_next(510, False), on_completed(510)] assert xs.subscriptions == [subscribe(200, 510)] assert ys.subscriptions == [subscribe(200, 510)] def test_sequence_equal_not_equal_right_sym(self): scheduler = TestScheduler() msgs1 = [on_next(110, 1), on_next(190, 2), on_next(240, 3), on_next(290, 4), on_next(310, 5), on_next(340, 6), on_next(450, 7), on_completed(510)] msgs2 = [on_next(90, 1), on_next(270, 3), on_next(280, 4), on_next(300, 5), on_next(330, 6), on_next(340, 7), on_next(350, 8)] xs = scheduler.create_hot_observable(msgs1) ys = scheduler.create_hot_observable(msgs2) results = scheduler.start(lambda: ys.pipe(ops.sequence_equal(xs))) assert results.messages == [on_next(510, False), on_completed(510)] assert xs.subscriptions == [subscribe(200, 510)] assert ys.subscriptions == [subscribe(200, 510)] def test_sequence_equal_not_equal_2(self): scheduler = TestScheduler() msgs1 = [on_next(110, 1), on_next(190, 2), on_next(240, 3), on_next(290, 4), on_next(310, 5), on_next(340, 6), on_next(450, 7), on_next(490, 8), on_next(520, 9), on_next(580, 10), on_next(600, 11)] msgs2 = [on_next(90, 1), on_next(270, 3), on_next(280, 4), on_next(300, 5), on_next(330, 6), on_next(340, 7), on_next(350, 9), on_next(400, 9), on_next(410, 10), on_next(490, 11), on_next(550, 12), on_next(560, 13)] xs = scheduler.create_hot_observable(msgs1) ys = scheduler.create_hot_observable(msgs2) results = scheduler.start(create=lambda: xs.pipe(ops.sequence_equal(ys))) assert results.messages == [on_next(490, False), on_completed(490)] assert xs.subscriptions == [subscribe(200, 490)] assert ys.subscriptions == [subscribe(200, 490)] def test_sequence_equal_not_equal_2_sym(self): scheduler = TestScheduler() msgs1 = [on_next(110, 1), on_next(190, 2), on_next(240, 3), on_next(290, 4), on_next(310, 5), on_next(340, 6), on_next(450, 7), on_next(490, 8), on_next(520, 9), on_next(580, 10), on_next(600, 11)] msgs2 = [on_next(90, 1), on_next(270, 3), on_next(280, 4), on_next(300, 5), on_next(330, 6), on_next(340, 7), on_next(350, 9), on_next(400, 9), on_next(410, 10), on_next(490, 11), on_next(550, 12), on_next(560, 13)] xs = scheduler.create_hot_observable(msgs1) ys = scheduler.create_hot_observable(msgs2) results = scheduler.start(create=lambda: ys.pipe(ops.sequence_equal(xs))) assert results.messages == [on_next(490, False), on_completed(490)] assert xs.subscriptions == [subscribe(200, 490)] assert ys.subscriptions == [subscribe(200, 490)] def test_sequence_equal_not_equal_3(self): scheduler = TestScheduler() msgs1 = [on_next(110, 1), on_next(190, 2), on_next(240, 3), on_next(290, 4), on_next(310, 5), on_completed(330)] msgs2 = [on_next(90, 1), on_next(270, 3), on_next(400, 4), on_completed(420)] xs = scheduler.create_hot_observable(msgs1) ys = scheduler.create_hot_observable(msgs2) results = scheduler.start(create=lambda: xs.pipe(ops.sequence_equal(ys))) assert results.messages == [on_next(420, False), on_completed(420)] assert xs.subscriptions == [subscribe(200, 330)] assert ys.subscriptions == [subscribe(200, 420)] def test_sequence_equal_not_equal_3_sym(self): scheduler = TestScheduler() msgs1 = [on_next(110, 1), on_next(190, 2), on_next(240, 3), on_next(290, 4), on_next(310, 5), on_completed(330)] msgs2 = [on_next(90, 1), on_next(270, 3), on_next(400, 4), on_completed(420)] xs = scheduler.create_hot_observable(msgs1) ys = scheduler.create_hot_observable(msgs2) results = scheduler.start(create=lambda: ys.pipe(ops.sequence_equal(xs))) assert results.messages == [on_next(420, False), on_completed(420)] assert xs.subscriptions == [subscribe(200, 330)] assert ys.subscriptions == [subscribe(200, 420)] def test_sequence_equal_comparer_throws(self): ex = 'ex' scheduler = TestScheduler() msgs1 = [on_next(110, 1), on_next(190, 2), on_next(240, 3), on_next(290, 4), on_next(310, 5), on_completed(330)] msgs2 = [on_next(90, 1), on_next(270, 3), on_next(400, 4), on_completed(420)] xs = scheduler.create_hot_observable(msgs1) ys = scheduler.create_hot_observable(msgs2) def create(): def comparer(a, b): raise Exception(ex) return xs.pipe(ops.sequence_equal(ys, comparer)) results = scheduler.start(create=create) assert results.messages == [on_error(270, ex)] assert xs.subscriptions == [subscribe(200, 270)] assert ys.subscriptions == [subscribe(200, 270)] def test_sequence_equal_comparer_throws_sym(self): ex = 'ex' scheduler = TestScheduler() msgs1 = [on_next(110, 1), on_next(190, 2), on_next(240, 3), on_next(290, 4), on_next(310, 5), on_completed(330)] msgs2 = [on_next(90, 1), on_next(270, 3), on_next(400, 4), on_completed(420)] xs = scheduler.create_hot_observable(msgs1) ys = scheduler.create_hot_observable(msgs2) def create(): def comparer(a, b): raise Exception(ex) return ys.pipe(ops.sequence_equal(xs, comparer)) results = scheduler.start(create=create) assert results.messages == [on_error(270, ex)] assert xs.subscriptions == [subscribe(200, 270)] assert ys.subscriptions == [subscribe(200, 270)] def test_sequence_equal_not_equal_4(self): scheduler = TestScheduler() msgs1 = [on_next(250, 1), on_completed(300)] msgs2 = [on_next(290, 1), on_next(310, 2), on_completed(350)] xs = scheduler.create_hot_observable(msgs1) ys = scheduler.create_hot_observable(msgs2) results = scheduler.start(create=lambda: xs.pipe(ops.sequence_equal(ys))) assert results.messages == [on_next(310, False), on_completed(310)] assert xs.subscriptions == [subscribe(200, 300)] assert ys.subscriptions == [subscribe(200, 310)] def test_sequence_equal_not_equal_4_sym(self): scheduler = TestScheduler() msgs1 = [on_next(250, 1), on_completed(300)] msgs2 = [on_next(290, 1), on_next(310, 2), on_completed(350)] xs = scheduler.create_hot_observable(msgs1) ys = scheduler.create_hot_observable(msgs2) results = scheduler.start(create=lambda: ys.pipe(ops.sequence_equal(xs))) assert results.messages == [on_next(310, False), on_completed(310)] assert xs.subscriptions == [subscribe(200, 300)] assert ys.subscriptions == [subscribe(200, 310)] def test_sequenceequal_iterable_equal(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(110, 1), on_next(190, 2), on_next(240, 3), on_next(290, 4), on_next(310, 5), on_next(340, 6), on_next(450, 7), on_completed(510)) def create(): return xs.pipe(ops.sequence_equal([3, 4, 5, 6, 7])) res = scheduler.start(create=create) assert res.messages == [on_next(510, True), on_completed(510)] assert xs.subscriptions == [subscribe(200, 510)] def test_sequenceequal_iterable_notequal_elements(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(110, 1), on_next(190, 2), on_next(240, 3), on_next(290, 4), on_next(310, 5), on_next(340, 6), on_next(450, 7), on_completed(510)) def create(): return xs.pipe(ops.sequence_equal([3, 4, 9, 6, 7])) res = scheduler.start(create=create) assert res.messages == [on_next(310, False), on_completed(310)] assert xs.subscriptions == [subscribe(200, 310)] def test_sequenceequal_iterable_comparer_equal(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(110, 1), on_next(190, 2), on_next(240, 3), on_next(290, 4), on_next(310, 5), on_next(340, 6), on_next(450, 7), on_completed(510)) def create(): def comparer(x, y): return x % 2 == y % 2 return xs.pipe(ops.sequence_equal([3 - 2, 4, 5, 6 + 42, 7 - 6], comparer)) res = scheduler.start(create=create) assert res.messages == [on_next(510, True), on_completed(510)] assert xs.subscriptions == [subscribe(200, 510)] def test_sequenceequal_iterable_comparer_notequal(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(110, 1), on_next(190, 2), on_next(240, 3), on_next(290, 4), on_next(310, 5), on_next(340, 6), on_next(450, 7), on_completed(510)) def create(): def comparer(x, y): return x % 2 == y % 2 return xs.pipe(ops.sequence_equal([3 - 2, 4, 5 + 9, 6 + 42, 7 - 6], comparer)) res = scheduler.start(create=create) assert res.messages == [on_next(310, False), on_completed(310)] assert xs.subscriptions == [subscribe(200, 310)] def test_sequenceequal_iterable_comparer_throws(self): def on_error_comparer(value, exn): def comparer(x, y): if x == value: raise Exception(exn) return x == y return comparer ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(110, 1), on_next(190, 2), on_next(240, 3), on_next(290, 4), on_next(310, 5), on_next(340, 6), on_next(450, 7), on_completed(510)) def create(): return xs.pipe(ops.sequence_equal([3, 4, 5, 6, 7], on_error_comparer(5, ex))) res = scheduler.start(create=create) assert res.messages == [on_error(310, ex)] assert xs.subscriptions == [subscribe(200, 310)] def test_sequenceequal_iterable_notequal_toolong(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(110, 1), on_next(190, 2), on_next(240, 3), on_next(290, 4), on_next(310, 5), on_next(340, 6), on_next(450, 7), on_completed(510)) def create(): return xs.pipe(ops.sequence_equal([3, 4, 5, 6, 7, 8])) res = scheduler.start(create=create) assert res.messages == [on_next(510, False), on_completed(510)] assert xs.subscriptions == [subscribe(200, 510)] def test_sequenceequal_iterable_notequal_tooshort(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(110, 1), on_next(190, 2), on_next(240, 3), on_next(290, 4), on_next(310, 5), on_next(340, 6), on_next(450, 7), on_completed(510)) def create(): return xs.pipe(ops.sequence_equal([3, 4, 5, 6])) res = scheduler.start(create=create) assert res.messages == [on_next(450, False), on_completed(450)] assert xs.subscriptions == [subscribe(200, 450)] def test_sequenceequal_iterable_on_error(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(110, 1), on_next(190, 2), on_next(240, 3), on_next(290, 4), on_error(310, ex)) def create(): return xs.pipe(ops.sequence_equal([3, 4])) res = scheduler.start(create=create) assert res.messages == [on_error(310, ex)] assert xs.subscriptions == [subscribe(200, 310)] RxPY-3.2.0/tests/test_observable/test_single.py000066400000000000000000000116161404130727200216020ustar00rootroot00000000000000import unittest from rx import operators as ops from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class RxException(Exception): pass # Helper function for raising exceptions within lambdas def _raise(ex): raise RxException(ex) class TestSingle(unittest.TestCase): def test_single_async_empty(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_completed(250)) def create(): return xs.pipe(ops.single()) res = scheduler.start(create=create) def predicate(e): return e is not None assert [on_error(250, predicate)] == res.messages assert xs.subscriptions == [subscribe(200, 250)] def test_single_async_one(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_next(210, 2), on_completed(250)) def create(): return xs.pipe(ops.single()) res = scheduler.start(create=create) assert res.messages == [on_next(250, 2), on_completed(250)] assert xs.subscriptions == [subscribe(200, 250)] def test_single_async_many(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_next(210, 2), on_next(220, 3), on_completed(250)) def create(): return xs.pipe(ops.single()) res = scheduler.start(create=create) def predicate(e): return not e is None assert [on_error(220, predicate)] == res.messages assert xs.subscriptions == [subscribe(200, 220)] def test_single_async_error(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_error(210, ex)) def create(): return xs.pipe(ops.single()) res = scheduler.start(create=create) assert res.messages == [on_error(210, ex)] assert xs.subscriptions == [subscribe(200, 210)] def test_single_async_predicate(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_next(210, 2), on_next(220, 3), on_next(230, 4), on_next(240, 5), on_completed(250)) def create(): def predicate(x): return x % 2 == 1 return xs.pipe(ops.single(predicate)) res = scheduler.start(create=create) def predicate(e): return not e is None assert [on_error(240, predicate)] == res.messages assert xs.subscriptions == [subscribe(200, 240)] def test_single_async_predicate_empty(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_completed(250)) def create(): def predicate(x): return x % 2 == 1 return xs.pipe(ops.single(predicate)) res = scheduler.start(create=create) def predicate(e): return not e is None assert [on_error(250, predicate)] == res.messages assert xs.subscriptions == [subscribe(200, 250)] def test_single_async_predicate_one(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_next(210, 2), on_next(220, 3), on_next(230, 4), on_next(240, 5), on_completed(250)) def create(): def predicate(x): return x == 4 return xs.pipe(ops.single(predicate)) res = scheduler.start(create=create) assert res.messages == [on_next(250, 4), on_completed(250)] assert xs.subscriptions == [subscribe(200, 250)] def test_single_async_predicate_on_error(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_error(210, ex)) def create(): def predicate(x): return x > 10 return xs.pipe(ops.single(predicate)) res = scheduler.start(create=create) assert res.messages == [on_error(210, ex)] assert xs.subscriptions == [subscribe(200, 210)] def test_single_async_predicate_throws(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_next(210, 2), on_next(220, 3), on_next(230, 4), on_next(240, 5), on_completed(250)) def create(): def predicate(x): if x < 4: return False else: raise Exception(ex) return xs.pipe(ops.single(predicate)) res = scheduler.start(create=create) assert res.messages == [on_error(230, ex)] assert xs.subscriptions == [subscribe(200, 230)] RxPY-3.2.0/tests/test_observable/test_singleordefault.py000066400000000000000000000132461404130727200235110ustar00rootroot00000000000000import unittest from rx import operators as ops from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class RxException(Exception): pass # Helper function for raising exceptions within lambdas def _raise(ex): raise RxException(ex) class TestSingleOrDefault(unittest.TestCase): def test_single_or_default_async_empty(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_completed(250)) def create(): return xs.pipe(ops.single_or_default(None, 0)) res = scheduler.start(create=create) assert res.messages == [on_next(250, 0), on_completed(250)] assert xs.subscriptions == [subscribe(200, 250)] def test_single_or_default_async_one(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_next(210, 2), on_completed(250)) def create(): return xs.pipe(ops.single_or_default(None, 0)) res = scheduler.start(create=create) assert res.messages == [on_next(250, 2), on_completed(250)] assert xs.subscriptions == [subscribe(200, 250)] def test_single_or_default_async_many(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_next(210, 2), on_next(220, 3), on_completed(250)) def create(): return xs.pipe(ops.single_or_default(None, 0)) res = scheduler.start(create=create) def predicate(e): return not e is None #assert res.messages == [on_error(220, predicate)] assert [on_error(220, predicate)] == res.messages assert xs.subscriptions == [subscribe(200, 220)] def test_single_or_default_async_error(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_error(210, ex)) def create(): return xs.pipe(ops.single_or_default(None, 0)) res = scheduler.start(create=create) assert res.messages == [on_error(210, ex)] assert xs.subscriptions == [subscribe(200, 210)] def test_single_or_default_async_predicate(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_next(210, 2), on_next(220, 3), on_next(230, 4), on_next(240, 5), on_completed(250)) def create(): def predicate(x): return x % 2 == 1 return xs.pipe(ops.single_or_default(predicate, 0)) res = scheduler.start(create=create) def predicate(e): return not e is None assert [on_error(240, predicate)] == res.messages assert xs.subscriptions == [subscribe(200, 240)] def test_single_or_default_async_predicate_empty(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_completed(250)) def create(): def predicate(x): return x % 2 == 1 return xs.pipe(ops.single_or_default(predicate, 0)) res = scheduler.start(create=create) assert res.messages == [on_next(250, 0), on_completed(250)] assert xs.subscriptions == [subscribe(200, 250)] def test_single_or_default_async_predicate_one(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_next(210, 2), on_next(220, 3), on_next(230, 4), on_next(240, 5), on_completed(250)) def create(): def predicate(x): return x == 4 return xs.pipe(ops.single_or_default(predicate, 0)) res = scheduler.start(create=create) assert res.messages == [on_next(250, 4), on_completed(250)] assert xs.subscriptions == [subscribe(200, 250)] def test_single_or_default_async_predicate_none(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_next(210, 2), on_next(220, 3), on_next(230, 4), on_next(240, 5), on_completed(250)) def create(): def predicate(x): return x > 10 return xs.pipe(ops.single_or_default(predicate, 0)) res = scheduler.start(create=create) assert res.messages == [on_next(250, 0), on_completed(250)] assert xs.subscriptions == [subscribe(200, 250)] def test_single_or_default_async_predicate_on_error(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_error(210, ex)) def create(): def predicate(x): return x > 10 return xs.pipe(ops.single_or_default(predicate, 0)) res = scheduler.start(create=create) assert res.messages == [on_error(210, ex)] assert xs.subscriptions == [subscribe(200, 210)] def test_single_or_default_async_predicate_throws(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_next(210, 2), on_next(220, 3), on_next(230, 4), on_next(240, 5), on_completed(250)) def create(): def predicate(x): if x < 4: return False else: raise Exception(ex) return xs.pipe(ops.single_or_default(predicate, 0)) res = scheduler.start(create=create) assert res.messages == [on_error(230, ex)] assert xs.subscriptions == [subscribe(200, 230)] RxPY-3.2.0/tests/test_observable/test_skip.py000066400000000000000000000210141404130727200212600ustar00rootroot00000000000000import unittest from rx import operators as ops from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class TestSkip(unittest.TestCase): def test_skip_complete_after(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(70, 6), on_next(150, 4), on_next(210, 9), on_next(230, 13), on_next(270, 7), on_next(280, 1), on_next(300, -1), on_next(310, 3), on_next(340, 8), on_next(370, 11), on_next(410, 15), on_next(415, 16), on_next(460, 72), on_next(510, 76), on_next(560, 32), on_next(570, -100), on_next(580, -3), on_next(590, 5), on_next(630, 10), on_completed(690)) def create(): return xs.pipe(ops.skip(20)) results = scheduler.start(create) assert results.messages == [on_completed(690)] assert xs.subscriptions == [subscribe(200, 690)] def test_skip_complete_same(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(70, 6), on_next(150, 4), on_next(210, 9), on_next(230, 13), on_next(270, 7), on_next(280, 1), on_next(300, -1), on_next(310, 3), on_next(340, 8), on_next(370, 11), on_next(410, 15), on_next(415, 16), on_next(460, 72), on_next(510, 76), on_next(560, 32), on_next(570, -100), on_next(580, -3), on_next(590, 5), on_next(630, 10), on_completed(690)) def create(): return xs.pipe(ops.skip(17)) results = scheduler.start(create) assert results.messages == [on_completed(690)] assert xs.subscriptions == [subscribe(200, 690)] def test_skip_complete_before(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(70, 6), on_next(150, 4), on_next(210, 9), on_next(230, 13), on_next(270, 7), on_next(280, 1), on_next(300, -1), on_next(310, 3), on_next(340, 8), on_next(370, 11), on_next(410, 15), on_next(415, 16), on_next(460, 72), on_next(510, 76), on_next(560, 32), on_next(570, -100), on_next(580, -3), on_next(590, 5), on_next(630, 10), on_completed(690)) def create(): return xs.pipe(ops.skip(10)) results = scheduler.start(create) assert results.messages == [ on_next(460, 72), on_next(510, 76), on_next(560, 32), on_next(570, -100), on_next(580, -3), on_next(590, 5), on_next(630, 10), on_completed(690)] assert xs.subscriptions == [subscribe(200, 690)] def test_skip_Complete_zero(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(70, 6), on_next(150, 4), on_next(210, 9), on_next(230, 13), on_next(270, 7), on_next(280, 1), on_next(300, -1), on_next(310, 3), on_next(340, 8), on_next(370, 11), on_next(410, 15), on_next(415, 16), on_next(460, 72), on_next(510, 76), on_next(560, 32), on_next(570, -100), on_next(580, -3), on_next(590, 5), on_next(630, 10), on_completed(690)) def create(): return xs.pipe(ops.skip(0)) results = scheduler.start(create) assert results.messages == [ on_next(210, 9), on_next(230, 13), on_next(270, 7), on_next(280, 1), on_next(300, -1), on_next(310, 3), on_next(340, 8), on_next(370, 11), on_next(410, 15), on_next(415, 16), on_next(460, 72), on_next(510, 76), on_next(560, 32), on_next(570, -100), on_next(580, -3), on_next(590, 5), on_next(630, 10), on_completed(690)] assert xs.subscriptions == [subscribe(200, 690)] def test_skip_error_after(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(70, 6), on_next(150, 4), on_next(210, 9), on_next(230, 13), on_next(270, 7), on_next(280, 1), on_next(300, -1), on_next(310, 3), on_next(340, 8), on_next(370, 11), on_next(410, 15), on_next(415, 16), on_next(460, 72), on_next(510, 76), on_next(560, 32), on_next(570, -100), on_next(580, -3), on_next(590, 5), on_next(630, 10), on_error(690, ex)) def create(): return xs.pipe(ops.skip(20)) results = scheduler.start(create) assert results.messages == [on_error(690, ex)] assert xs.subscriptions == [subscribe(200, 690)] def test_skip_error_same(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(70, 6), on_next(150, 4), on_next(210, 9), on_next(230, 13), on_next(270, 7), on_next(280, 1), on_next(300, -1), on_next(310, 3), on_next(340, 8), on_next(370, 11), on_next(410, 15), on_next(415, 16), on_next(460, 72), on_next(510, 76), on_next(560, 32), on_next(570, -100), on_next(580, -3), on_next(590, 5), on_next(630, 10), on_error(690, ex)) def create(): return xs.pipe(ops.skip(17)) results = scheduler.start(create) assert results.messages == [on_error(690, ex)] assert xs.subscriptions == [subscribe(200, 690)] def test_skip_error_before(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(70, 6), on_next(150, 4), on_next(210, 9), on_next(230, 13), on_next(270, 7), on_next(280, 1), on_next(300, -1), on_next(310, 3), on_next(340, 8), on_next(370, 11), on_next(410, 15), on_next(415, 16), on_next(460, 72), on_next(510, 76), on_next(560, 32), on_next(570, -100), on_next(580, -3), on_next(590, 5), on_next(630, 10), on_error(690, ex)) def create(): return xs.pipe(ops.skip(3)) results = scheduler.start(create) assert results.messages == [ on_next(280, 1), on_next(300, -1), on_next(310, 3), on_next(340, 8), on_next(370, 11), on_next(410, 15), on_next(415, 16), on_next(460, 72), on_next(510, 76), on_next(560, 32), on_next(570, -100), on_next(580, -3), on_next(590, 5), on_next(630, 10), on_error(690, ex)] assert xs.subscriptions == [subscribe(200, 690)] def test_skip_dispose_before(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(70, 6), on_next(150, 4), on_next(210, 9), on_next(230, 13), on_next(270, 7), on_next(280, 1), on_next(300, -1), on_next(310, 3), on_next(340, 8), on_next(370, 11), on_next(410, 15), on_next(415, 16), on_next(460, 72), on_next(510, 76), on_next(560, 32), on_next(570, -100), on_next(580, -3), on_next(590, 5), on_next(630, 10)) def create(): return xs.pipe(ops.skip(3)) results = scheduler.start(create, disposed=250) assert results.messages == [] assert xs.subscriptions == [subscribe(200, 250)] def test_skip_dispose_after(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(70, 6), on_next(150, 4), on_next(210, 9), on_next(230, 13), on_next(270, 7), on_next(280, 1), on_next(300, -1), on_next(310, 3), on_next(340, 8), on_next(370, 11), on_next(410, 15), on_next(415, 16), on_next(460, 72), on_next(510, 76), on_next(560, 32), on_next(570, -100), on_next(580, -3), on_next(590, 5), on_next(630, 10)) def create(): return xs.pipe(ops.skip(3)) results = scheduler.start(create, disposed=400) assert results.messages == [ on_next(280, 1), on_next(300, -1), on_next(310, 3), on_next(340, 8), on_next(370, 11)] assert xs.subscriptions == [subscribe(200, 400)] if __name__ == '__main__': unittest.main() RxPY-3.2.0/tests/test_observable/test_skiplast.py000066400000000000000000000154671404130727200221630ustar00rootroot00000000000000import unittest from rx import operators as ops from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class TestSkipLast(unittest.TestCase): def test_skip_last_zero_completed(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(180, 1), on_next(210, 2), on_next(250, 3), on_next(270, 4), on_next(310, 5), on_next(360, 6), on_next(380, 7), on_next(410, 8), on_next(590, 9), on_completed(650)) def create(): return xs.pipe(ops.skip_last(0)) results = scheduler.start(create) assert results.messages == [ on_next(210, 2), on_next(250, 3), on_next(270, 4), on_next(310, 5), on_next(360, 6), on_next(380, 7), on_next(410, 8), on_next(590, 9), on_completed(650)] assert xs.subscriptions == [subscribe(200, 650)] def test_skip_last_zero_error(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(180, 1), on_next(210, 2), on_next(250, 3), on_next(270, 4), on_next(310, 5), on_next(360, 6), on_next(380, 7), on_next(410, 8), on_next(590, 9), on_error(650, ex)) def create(): return xs.pipe(ops.skip_last(0)) results = scheduler.start(create) assert results.messages == [ on_next(210, 2), on_next(250, 3), on_next(270, 4), on_next(310, 5), on_next(360, 6), on_next(380, 7), on_next(410, 8), on_next(590, 9), on_error(650, ex)] assert xs.subscriptions == [subscribe(200, 650)] def test_skip_last_zero_disposed(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(180, 1), on_next(210, 2), on_next(250, 3), on_next(270, 4), on_next(310, 5), on_next(360, 6), on_next(380, 7), on_next(410, 8), on_next(590, 9)) def create(): return xs.pipe(ops.skip_last(0)) results = scheduler.start(create) assert results.messages == [ on_next(210, 2), on_next(250, 3), on_next(270, 4), on_next(310, 5), on_next(360, 6), on_next(380, 7), on_next(410, 8), on_next(590, 9)] assert xs.subscriptions == [subscribe(200, 1000)] def test_skip_last_one_completed(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(180, 1), on_next(210, 2), on_next(250, 3), on_next(270, 4), on_next(310, 5), on_next(360, 6), on_next(380, 7), on_next(410, 8), on_next(590, 9), on_completed(650)) def create(): return xs.pipe(ops.skip_last(1)) results = scheduler.start(create) assert results.messages == [ on_next(250, 2), on_next(270, 3), on_next(310, 4), on_next(360, 5), on_next(380, 6), on_next(410, 7), on_next(590, 8), on_completed(650)] assert xs.subscriptions == [subscribe(200, 650)] def test_skip_last_one_error(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(180, 1), on_next(210, 2), on_next(250, 3), on_next(270, 4), on_next(310, 5), on_next(360, 6), on_next(380, 7), on_next(410, 8), on_next(590, 9), on_error(650, ex)) def create(): return xs.pipe(ops.skip_last(1)) results = scheduler.start(create) assert results.messages == [ on_next(250, 2), on_next(270, 3), on_next(310, 4), on_next(360, 5), on_next(380, 6), on_next(410, 7), on_next(590, 8), on_error(650, ex)] assert xs.subscriptions == [subscribe(200, 650)] def test_skip_last_one_disposed(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(180, 1), on_next(210, 2), on_next(250, 3), on_next(270, 4), on_next(310, 5), on_next(360, 6), on_next(380, 7), on_next(410, 8), on_next(590, 9)) def create(): return xs.pipe(ops.skip_last(1)) results = scheduler.start(create) assert results.messages == [ on_next(250, 2), on_next(270, 3), on_next(310, 4), on_next(360, 5), on_next(380, 6), on_next(410, 7), on_next(590, 8)] assert xs.subscriptions == [subscribe(200, 1000)] def test_skip_last_three_completed(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(180, 1), on_next(210, 2), on_next(250, 3), on_next(270, 4), on_next(310, 5), on_next(360, 6), on_next(380, 7), on_next(410, 8), on_next(590, 9), on_completed(650)) def create(): return xs.pipe(ops.skip_last(3)) results = scheduler.start(create) assert results.messages == [ on_next(310, 2), on_next(360, 3), on_next(380, 4), on_next(410, 5), on_next(590, 6), on_completed(650)] assert xs.subscriptions == [subscribe(200, 650)] def test_skip_last_three_error(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(180, 1), on_next(210, 2), on_next(250, 3), on_next(270, 4), on_next(310, 5), on_next(360, 6), on_next(380, 7), on_next(410, 8), on_next(590, 9), on_error(650, ex)) def create(): return xs.pipe(ops.skip_last(3)) results = scheduler.start(create) assert results.messages == [ on_next(310, 2), on_next(360, 3), on_next(380, 4), on_next(410, 5), on_next(590, 6), on_error(650, ex)] assert xs.subscriptions == [subscribe(200, 650)] def test_skip_last_three_disposed(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(180, 1), on_next(210, 2), on_next(250, 3), on_next(270, 4), on_next(310, 5), on_next(360, 6), on_next(380, 7), on_next(410, 8), on_next(590, 9)) def create(): return xs.pipe(ops.skip_last(3)) results = scheduler.start(create) assert results.messages == [ on_next(310, 2), on_next(360, 3), on_next(380, 4), on_next(410, 5), on_next(590, 6)] assert xs.subscriptions == [subscribe(200, 1000)] RxPY-3.2.0/tests/test_observable/test_skiplastwithtime.py000066400000000000000000000065451404130727200237330ustar00rootroot00000000000000import unittest import rx from rx import operators as ops from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class TestSkipLastWithTime(unittest.TestCase): def test_skiplast_zero1(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(210, 1), on_next(220, 2), on_completed(230)) def create(): return xs.pipe(ops.skip_last_with_time(0)) res = scheduler.start(create) assert res.messages == [on_next(210, 1), on_next(220, 2), on_completed(230)] assert xs.subscriptions == [subscribe(200, 230)] def test_skiplast_zero2(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(210, 1), on_next(220, 2), on_next(230, 3), on_completed(230)) def create(): return xs.pipe(ops.skip_last_with_time(0)) res = scheduler.start(create) assert res.messages == [on_next(210, 1), on_next(220, 2), on_next(230, 3), on_completed(230)] assert xs.subscriptions == [subscribe(200, 230)] def test_skiplast_some1(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(210, 1), on_next(220, 2), on_next(230, 3), on_completed(230)) def create(): return xs.pipe(ops.skip_last_with_time(15)) res = scheduler.start(create) assert res.messages == [on_next(230, 1), on_completed(230)] assert xs.subscriptions == [subscribe(200, 230)] def test_skiplast_some2(self): scheduler = TestScheduler() def create(): return xs.pipe(ops.skip_last_with_time(45)) xs = scheduler.create_hot_observable(on_next(210, 1), on_next(220, 2), on_next(230, 3), on_next(240, 4), on_next(250, 5), on_next(260, 6), on_next(270, 7), on_next(280, 8), on_next(290, 9), on_completed(300)) res = scheduler.start(create) assert res.messages == [on_next(260, 1), on_next(270, 2), on_next(280, 3), on_next(290, 4), on_next(300, 5), on_completed(300)] assert xs.subscriptions == [subscribe(200, 300)] def test_skiplast_all(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(210, 1), on_next(220, 2), on_completed(230)) def create(): return xs.pipe(ops.skip_last_with_time(45)) res = scheduler.start(create) assert res.messages == [on_completed(230)] assert xs.subscriptions == [subscribe(200, 230)] def test_skiplast_error(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_error(210, ex)) def create(): return xs.pipe(ops.skip_last_with_time(45)) res = scheduler.start(create) assert res.messages == [on_error(210, ex)] assert xs.subscriptions == [subscribe(200, 210)] def test_skiplast_never(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable() def create(): return xs.pipe(ops.skip_last_with_time(50)) res = scheduler.start(create) assert res.messages == [] assert xs.subscriptions == [subscribe(200, 1000)] RxPY-3.2.0/tests/test_observable/test_skipuntil.py000066400000000000000000000107371404130727200223460ustar00rootroot00000000000000import unittest import rx from rx import operators as ops from rx.core import Observable from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class TestSkipUntil(unittest.TestCase): def test_skip_until_somedata_next(self): scheduler = TestScheduler() l_msgs = [on_next(150, 1), on_next(210, 2), on_next(220, 3), on_next(230, 4), on_next(240, 5), on_completed(250)] r_msgs = [on_next(150, 1), on_next(225, 99), on_completed(230)] l = scheduler.create_hot_observable(l_msgs) r = scheduler.create_hot_observable(r_msgs) def create(): return l.pipe(ops.skip_until(r)) results = scheduler.start(create) assert results.messages == [on_next(230, 4), on_next(240, 5), on_completed(250)] def test_skip_until_somedata_error(self): scheduler = TestScheduler() ex = 'ex' l_msgs = [on_next(150, 1), on_next(210, 2), on_next(220, 3), on_next(230, 4), on_next(240, 5), on_completed(250)] r_msgs = [on_next(150, 1), on_error(225, ex)] l = scheduler.create_hot_observable(l_msgs) r = scheduler.create_hot_observable(r_msgs) def create(): return l.pipe(ops.skip_until(r)) results = scheduler.start(create) assert results.messages == [on_error(225, ex)] def test_skip_until_somedata_empty(self): scheduler = TestScheduler() l_msgs = [on_next(150, 1), on_next(210, 2), on_next(220, 3), on_next(230, 4), on_next(240, 5), on_completed(250)] r_msgs = [on_next(150, 1), on_completed(225)] l = scheduler.create_hot_observable(l_msgs) r = scheduler.create_hot_observable(r_msgs) def create(): return l.pipe(ops.skip_until(r)) results = scheduler.start(create) assert results.messages == [] def test_skip_until_never_next(self): scheduler = TestScheduler() r_msgs = [on_next(150, 1), on_next(225, 2), on_completed(250)] l = rx.never() r = scheduler.create_hot_observable(r_msgs) def create(): return l.pipe(ops.skip_until(r)) results = scheduler.start(create) assert results.messages == [] def test_skip_until_never_error(self): ex = 'ex' scheduler = TestScheduler() r_msgs = [on_next(150, 1), on_error(225, ex)] l = rx.never() r = scheduler.create_hot_observable(r_msgs) def create(): return l.pipe(ops.skip_until(r)) results = scheduler.start(create) assert results.messages == [on_error(225, ex)] def test_skip_until_somedata_never(self): scheduler = TestScheduler() l_msgs = [on_next(150, 1), on_next(210, 2), on_next(220, 3), on_next(230, 4), on_next(240, 5), on_completed(250)] l = scheduler.create_hot_observable(l_msgs) r = rx.never() def create(): return l.pipe(ops.skip_until(r)) results = scheduler.start(create) assert results.messages == [] def test_skip_until_never_empty(self): scheduler = TestScheduler() r_msgs = [on_next(150, 1), on_completed(225)] l = rx.never() r = scheduler.create_hot_observable(r_msgs) def create(): return l.pipe(ops.skip_until(r)) results = scheduler.start(create) assert results.messages == [] def test_skip_until_never_never(self): scheduler = TestScheduler() l = rx.never() r = rx.never() def create(): return l.pipe(ops.skip_until(r)) results = scheduler.start(create) assert results.messages == [] def test_skip_until_has_completed_causes_disposal(self): scheduler = TestScheduler() l_msgs = [on_next(150, 1), on_next(210, 2), on_next(220, 3), on_next(230, 4), on_next(240, 5), on_completed(250)] disposed = [False] l = scheduler.create_hot_observable(l_msgs) def subscribe(observer, scheduler=None): disposed[0] = True r = Observable(subscribe) def create(): return l.pipe(ops.skip_until(r)) results = scheduler.start(create) assert results.messages == [] assert(disposed[0]) RxPY-3.2.0/tests/test_observable/test_skipuntilwithtime.py000066400000000000000000000070141404130727200241130ustar00rootroot00000000000000import unittest from datetime import datetime from rx import operators as ops from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class TestSkipUntilWithTIme(unittest.TestCase): def test_skipuntil_zero(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(210, 1), on_next(220, 2), on_completed(230)) def create(): return xs.pipe(ops.skip_until_with_time(datetime.utcfromtimestamp(0))) res = scheduler.start(create) assert res.messages == [on_next(210, 1), on_next(220, 2), on_completed(230)] assert xs.subscriptions == [subscribe(200, 230)] def test_skipuntil_late(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(210, 1), on_next(220, 2), on_completed(230)) def create(): return xs.pipe(ops.skip_until_with_time(datetime.utcfromtimestamp(250))) res = scheduler.start(create) assert res.messages == [on_completed(230)] assert xs.subscriptions == [subscribe(200, 230)] def test_skipuntil_error(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_error(210, ex)) def create(): return xs.pipe(ops.skip_until_with_time(datetime.utcfromtimestamp(250))) res = scheduler.start(create) assert res.messages == [on_error(210, ex)] assert xs.subscriptions == [subscribe(200, 210)] def test_skipuntil_never(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable() def create(): return xs.pipe(ops.skip_until_with_time(datetime.utcfromtimestamp(250))) res = scheduler.start(create) assert res.messages == [] assert xs.subscriptions == [subscribe(200, 1000)] def test_skipuntil_twice1(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(210, 1), on_next(220, 2), on_next(230, 3), on_next(240, 4), on_next(250, 5), on_next(260, 6), on_completed(270)) def create(): return xs.pipe( ops.skip_until_with_time(datetime.utcfromtimestamp(215)), ops.skip_until_with_time(datetime.utcfromtimestamp(230)), ) res = scheduler.start(create) assert res.messages == [ on_next(240, 4), on_next(250, 5), on_next(260, 6), on_completed(270)] assert xs.subscriptions == [subscribe(200, 270)] def test_skipuntil_twice2(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(210, 1), on_next(220, 2), on_next(230, 3), on_next(240, 4), on_next(250, 5), on_next(260, 6), on_completed(270)) def create(): return xs.pipe( ops.skip_until_with_time(datetime.utcfromtimestamp(230)), ops.skip_until_with_time(datetime.utcfromtimestamp(215)), ) res = scheduler.start(create) assert res.messages == [on_next(240, 4), on_next(250, 5), on_next(260, 6), on_completed(270)] assert xs.subscriptions == [subscribe(200, 270)] RxPY-3.2.0/tests/test_observable/test_skipwhile.py000066400000000000000000000177351404130727200223300ustar00rootroot00000000000000import unittest from rx import operators as ops from rx.testing import TestScheduler, ReactiveTest, is_prime on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class TestSkipWhile(unittest.TestCase): def test_skip_while_complete_before(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(90, -1), on_next(110, -1), on_next(210, 2), on_next(260, 5), on_next(290, 13), on_next(320, 3), on_completed(330), on_next(350, 7), on_next(390, 4), on_next(410, 17), on_next(450, 8), on_next(500, 23), on_completed(600)) invoked = [0] def create(): def predicate(x): invoked[0] += 1 return is_prime(x) return xs.pipe(ops.skip_while(predicate)) results = scheduler.start(create) assert results.messages == [on_completed(330)] assert xs.subscriptions == [subscribe(200, 330)] assert(invoked[0] == 4) def test_skip_while_complete_after(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(90, -1), on_next(110, -1), on_next(210, 2), on_next(260, 5), on_next(290, 13), on_next(320, 3), on_next(350, 7), on_next(390, 4), on_next(410, 17), on_next(450, 8), on_next(500, 23), on_completed(600)) invoked = [0] def create(): def predicate(x): invoked[0] += 1 return is_prime(x) return xs.pipe(ops.skip_while(predicate)) results = scheduler.start(create) assert results.messages == [on_next(390, 4), on_next( 410, 17), on_next(450, 8), on_next(500, 23), on_completed(600)] assert xs.subscriptions == [subscribe(200, 600)] assert(invoked[0] == 6) def test_skip_while_error_before(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(90, -1), on_next(110, -1), on_next(210, 2), on_next(260, 5), on_error(270, ex), on_next(290, 13), on_next(320, 3), on_next(350, 7), on_next(390, 4), on_next(410, 17), on_next(450, 8), on_next(500, 23), on_completed(600)) invoked = [0] def create(): def predicate(x): invoked[0] += 1 return is_prime(x) return xs.pipe(ops.skip_while(predicate)) results = scheduler.start(create) assert results.messages == [on_error(270, ex)] assert xs.subscriptions == [subscribe(200, 270)] assert(invoked[0] == 2) def test_skip_while_error_after(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(90, -1), on_next(110, -1), on_next(210, 2), on_next(260, 5), on_next(290, 13), on_next(320, 3), on_next(350, 7), on_next(390, 4), on_next(410, 17), on_next(450, 8), on_next(500, 23), on_error(600, ex)) invoked = [0] def create(): def predicate(x): invoked[0] += 1 return is_prime(x) return xs.pipe(ops.skip_while(predicate)) results = scheduler.start(create) assert results.messages == [on_next(390, 4), on_next( 410, 17), on_next(450, 8), on_next(500, 23), on_error(600, ex)] assert xs.subscriptions == [subscribe(200, 600)] assert(invoked[0] == 6) def test_skip_while_dispose_before(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(90, -1), on_next(110, -1), on_next(210, 2), on_next(260, 5), on_next(290, 13), on_next(320, 3), on_next(350, 7), on_next(390, 4), on_next(410, 17), on_next(450, 8), on_next(500, 23), on_completed(600)) invoked = [0] def create(): def predicate(x): invoked[0] += 1 return is_prime(x) return xs.pipe(ops.skip_while(predicate)) results = scheduler.start(create, disposed=300) assert results.messages == [] assert xs.subscriptions == [subscribe(200, 300)] assert(invoked[0] == 3) def test_skip_while_dispose_after(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(90, -1), on_next(110, -1), on_next(210, 2), on_next(260, 5), on_next(290, 13), on_next(320, 3), on_next(350, 7), on_next(390, 4), on_next(410, 17), on_next(450, 8), on_next(500, 23), on_completed(600)) invoked = [0] def create(): def predicate(x): invoked[0] += 1 return is_prime(x) return xs.pipe(ops.skip_while(predicate)) results = scheduler.start(create, disposed=470) assert results.messages == [on_next(390, 4), on_next(410, 17), on_next(450, 8)] assert xs.subscriptions == [subscribe(200, 470)] assert(invoked[0] == 6) def test_skip_while_zero(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(90, -1), on_next(110, -1), on_next(205, 100), on_next(210, 2), on_next(260, 5), on_next(290, 13), on_next(320, 3), on_next(350, 7), on_next(390, 4), on_next(410, 17), on_next(450, 8), on_next(500, 23), on_completed(600)) invoked = [0] def create(): def predicate(x): invoked[0] += 1 return is_prime(x) return xs.pipe(ops.skip_while(predicate)) results = scheduler.start(create) assert results.messages == [ on_next(205, 100), on_next(210, 2), on_next(260, 5), on_next(290, 13), on_next(320, 3), on_next(350, 7), on_next(390, 4), on_next(410, 17), on_next(450, 8), on_next(500, 23), on_completed(600)] assert xs.subscriptions == [subscribe(200, 600)] assert(invoked[0] == 1) def test_skip_while_on_error(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(90, -1), on_next(110, -1), on_next(210, 2), on_next(260, 5), on_next(290, 13), on_next(320, 3), on_next(350, 7), on_next(390, 4), on_next(410, 17), on_next(450, 8), on_next(500, 23), on_completed(600)) ex = 'ex' invoked = [0] def create(): def predicate(x): invoked[0] += 1 if invoked[0] == 3: raise Exception(ex) return is_prime(x) return xs.pipe(ops.skip_while(predicate)) results = scheduler.start(create) assert results.messages == [on_error(290, ex)] assert xs.subscriptions == [subscribe(200, 290)] assert invoked[0] == 3 def test_skip_while_index(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(90, -1), on_next(110, -1), on_next(210, 2), on_next(260, 5), on_next(290, 13), on_next(320, 3), on_next(350, 7), on_next(390, 4), on_next(410, 17), on_next(450, 8), on_next(500, 23), on_completed(600)) def create(): def predicate(x, i): return i < 5 return xs.pipe(ops.skip_while_indexed(predicate)) results = scheduler.start(create) assert results.messages == [ on_next(390, 4), on_next(410, 17), on_next(450, 8), on_next(500, 23), on_completed(600) ] assert xs.subscriptions == [subscribe(200, 600)] RxPY-3.2.0/tests/test_observable/test_skipwithtime.py000066400000000000000000000067721404130727200230510ustar00rootroot00000000000000import unittest from rx import operators as ops from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class TestSkipWithTime(unittest.TestCase): def test_skip_zero(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(210, 1), on_next(220, 2), on_completed(230)) def create(): return xs.pipe(ops.skip_with_time(0)) res = scheduler.start(create) assert res.messages == [on_next(210, 1), on_next(220, 2), on_completed(230)] assert xs.subscriptions == [subscribe(200, 230)] def test_skip_some(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(210, 1), on_next(220, 2), on_completed(230)) def create(): return xs.pipe(ops.skip_with_time(15)) res = scheduler.start(create) assert res.messages == [on_next(220, 2), on_completed(230)] assert xs.subscriptions == [subscribe(200, 230)] def test_skip_late(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(210, 1), on_next(220, 2), on_completed(230)) def create(): return xs.pipe(ops.skip_with_time(50)) res = scheduler.start(create) assert res.messages == [on_completed(230)] assert xs.subscriptions == [subscribe(200, 230)] def test_skip_error(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_error(210, ex)) def create(): return xs.pipe(ops.skip_with_time(50)) res = scheduler.start(create) assert res.messages == [on_error(210, ex)] assert xs.subscriptions == [subscribe(200, 210)] def test_skip_never(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable() def create(): return xs.pipe(ops.skip_with_time(50)) res = scheduler.start(create) assert res.messages == [] assert xs.subscriptions == [subscribe(200, 1000)] def test_skip_twice1(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(210, 1), on_next(220, 2), on_next(230, 3), on_next(240, 4), on_next(250, 5), on_next(260, 6), on_completed(270)) def create(): return xs.pipe( ops.skip_with_time(15), ops.skip_with_time(30), ) res = scheduler.start(create) assert res.messages == [on_next(240, 4), on_next(250, 5), on_next(260, 6), on_completed(270)] assert xs.subscriptions == [subscribe(200, 270)] def test_skip_twice2(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(210, 1), on_next(220, 2), on_next(230, 3), on_next(240, 4), on_next(250, 5), on_next(260, 6), on_completed(270)) def create(): return xs.pipe( ops.skip_with_time(30), ops.skip_with_time(15), ) res = scheduler.start(create) assert res.messages == [on_next(240, 4), on_next(250, 5), on_next(260, 6), on_completed(270)] assert xs.subscriptions == [subscribe(200, 270)] RxPY-3.2.0/tests/test_observable/test_slice.py000066400000000000000000000167401404130727200214230ustar00rootroot00000000000000import unittest from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class TestSlice(unittest.TestCase): def test_slice_empty(self): scheduler = TestScheduler() msgs = [on_next(150, 1), on_completed(250)] xs = scheduler.create_hot_observable(msgs) def create(): return xs[1:42] res = scheduler.start(create=create).messages assert res == [on_completed(250)] def test_slice_same(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(70, -2), on_next(150, -1), on_next(210, 0), on_next(230, 1), on_next(270, 2), on_next(280, 3), on_next(300, 4), on_next(310, 5), on_next(340, 6), on_next(370, 7), on_next(410, 8), on_next(415, 9), on_completed(690)) def create(): return xs[0:10] results = scheduler.start(create) assert results.messages == [ on_next(210, 0), on_next(230, 1), on_next(270, 2), on_next(280, 3), on_next(300, 4), on_next(310, 5), on_next(340, 6), on_next(370, 7), on_next(410, 8), on_next(415, 9), on_completed(415)] assert xs.subscriptions == [subscribe(200, 415)] def test_slice_same_noop(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(70, -2), on_next(150, -1), on_next(210, 0), on_next(230, 1), on_next(270, 2), on_next(280, 3), on_next(300, 4), on_next(310, 5), on_next(340, 6), on_next(370, 7), on_next(410, 8), on_next(415, 9), on_completed(690)) def create(): return xs[:] results = scheduler.start(create) assert results.messages == [ on_next(210, 0), on_next(230, 1), on_next(270, 2), on_next(280, 3), on_next(300, 4), on_next(310, 5), on_next(340, 6), on_next(370, 7), on_next(410, 8), on_next(415, 9), on_completed(690)] assert xs.subscriptions == [subscribe(200, 690)] def test_slice_skip_first(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(70, -2), on_next(150, -1), on_next(210, 0), on_next(230, 1), on_next(270, 2), on_next(280, 3), on_next(300, 4), on_next(310, 5), on_next(340, 6), on_next(370, 7), on_next(410, 8), on_next(415, 9), on_completed(690)) def create(): return xs[2:] results = scheduler.start(create) assert results.messages == [ on_next(270, 2), on_next(280, 3), on_next(300, 4), on_next(310, 5), on_next(340, 6), on_next(370, 7), on_next(410, 8), on_next(415, 9), on_completed(690)] assert xs.subscriptions == [subscribe(200, 690)] def test_slice_skip_last(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(70, -2), on_next(150, -1), on_next(210, 0), on_next(230, 1), on_next(270, 2), on_next(280, 3), on_next(300, 4), on_next(310, 5), on_next(340, 6), on_next(370, 7), on_next(410, 8), on_next(415, 9), on_completed(690)) def create(): return xs[:-2] results = scheduler.start(create) assert results.messages == [ on_next(270, 0), on_next(280, 1), on_next(300, 2), on_next(310, 3), on_next(340, 4), on_next(370, 5), on_next(410, 6), on_next(415, 7), on_completed(690)] assert xs.subscriptions == [subscribe(200, 690)] def test_slice_take_last(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(70, -2), on_next(150, -1), on_next(210, 0), on_next(230, 1), on_next(270, 2), on_next(280, 3), on_next(300, 4), on_next(310, 5), on_next(340, 6), on_next(370, 7), on_next(410, 8), on_next(415, 9), on_completed(690)) def create(): return xs[-2:] results = scheduler.start(create) assert results.messages == [ on_next(690, 8), on_next(690, 9), on_completed(690)] assert xs.subscriptions == [subscribe(200, 690)] def test_slice_take_first(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(70, -2), on_next(150, -1), on_next(210, 0), on_next(230, 1), on_next(270, 2), on_next(280, 3), on_next(300, 4), on_next(310, 5), on_next(340, 6), on_next(370, 7), on_next(410, 8), on_next(415, 9), on_completed(690)) def create(): return xs[:2] results = scheduler.start(create) assert results.messages == [ on_next(210, 0), on_next(230, 1), on_completed(230)] assert xs.subscriptions == [subscribe(200, 230)] def test_slice_take_last_skip_all(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(70, -2), on_next(150, -1), on_next(210, 0), on_next(230, 1), on_next(270, 2), on_next(280, 3), on_next(300, 4), on_next(310, 5), on_next(340, 6), on_next(370, 7), on_next(410, 8), on_next(415, 9), on_completed(690)) def create(): return xs[-2:0] results = scheduler.start(create) assert results.messages == [ on_completed(200)] def test_slice_step_2(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(70, -2), on_next(150, -1), on_next(210, 0), on_next(230, 1), on_next(270, 2), on_next(280, 3), on_next(300, 4), on_next(310, 5), on_next(340, 6), on_next(370, 7), on_next(410, 8), on_next(415, 9), on_completed(690)) def create(): return xs[0:10:2] results = scheduler.start(create) assert results.messages == [ on_next(210, 0), on_next(270, 2), on_next(300, 4), on_next(340, 6), on_next(410, 8), on_completed(415)] assert xs.subscriptions == [subscribe(200, 415)] RxPY-3.2.0/tests/test_observable/test_some.py000066400000000000000000000106431404130727200212630ustar00rootroot00000000000000import unittest from rx import operators as ops from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class TestSome(unittest.TestCase): def test_some_empty(self): scheduler = TestScheduler() msgs = [on_next(150, 1), on_completed(250)] xs = scheduler.create_hot_observable(msgs) def create(): return xs.pipe(ops.some()) res = scheduler.start(create=create).messages assert res == [on_next(250, False), on_completed(250)] def test_some_return(self): scheduler = TestScheduler() msgs = [on_next(150, 1), on_next(210, 2), on_completed(250)] xs = scheduler.create_hot_observable(msgs) def create(): return xs.pipe(ops.some()) res = scheduler.start(create=create).messages assert res == [on_next(210, True), on_completed(210)] def test_some_on_error(self): ex = 'ex' scheduler = TestScheduler() msgs = [on_next(150, 1), on_error(210, ex)] xs = scheduler.create_hot_observable(msgs) def create(): return xs.pipe(ops.some()) res = scheduler.start(create=create).messages assert res == [on_error(210, ex)] def test_some_never(self): scheduler = TestScheduler() msgs = [on_next(150, 1)] xs = scheduler.create_hot_observable(msgs) def create(): return xs.pipe(ops.some()) res = scheduler.start(create=create).messages assert res == [] def test_some_predicate_empty(self): scheduler = TestScheduler() msgs = [on_next(150, 1), on_completed(250)] xs = scheduler.create_hot_observable(msgs) def create(): return xs.pipe(ops.some(lambda x: x > 0)) res = scheduler.start(create=create).messages assert res == [on_next(250, False), on_completed(250)] def test_some_predicate_return(self): scheduler = TestScheduler() msgs = [on_next(150, 1), on_next(210, 2), on_completed(250)] xs = scheduler.create_hot_observable(msgs) def create(): return xs.pipe(ops.some(lambda x: x > 0)) res = scheduler.start(create=create).messages assert res == [on_next(210, True), on_completed(210)] def test_some_predicate_return_not_match(self): scheduler = TestScheduler() msgs = [on_next(150, 1), on_next(210, -2), on_completed(250)] xs = scheduler.create_hot_observable(msgs) def create(): return xs.pipe(ops.some(lambda x: x > 0)) res = scheduler.start(create=create).messages assert res == [on_next(250, False), on_completed(250)] def test_some_predicate_some_none_match(self): scheduler = TestScheduler() msgs = [on_next(150, 1), on_next(210, -2), on_next(220, -3), on_next(230, -4), on_completed(250)] xs = scheduler.create_hot_observable(msgs) def create(): return xs.pipe(ops.some(lambda x: x > 0)) res = scheduler.start(create=create).messages assert res == [on_next(250, False), on_completed(250)] def test_some_predicate_some_match(self): scheduler = TestScheduler() msgs = [on_next(150, 1), on_next(210, -2), on_next(220, 3), on_next(230, -4), on_completed(250)] xs = scheduler.create_hot_observable(msgs) def create(): return xs.pipe(ops.some(lambda x: x > 0)) res = scheduler.start(create=create).messages assert res == [on_next(220, True), on_completed(220)] def test_some_predicate_on_error(self): ex = 'ex' scheduler = TestScheduler() msgs = [on_next(150, 1), on_error(210, ex)] xs = scheduler.create_hot_observable(msgs) def create(): return xs.pipe(ops.some(lambda x: x > 0)) res = scheduler.start(create=create).messages assert res == [on_error(210, ex)] def test_some_predicate_never(self): scheduler = TestScheduler() msgs = [on_next(150, 1)] xs = scheduler.create_hot_observable(msgs) def create(): return xs.pipe(ops.some(lambda x: x > 0)) res = scheduler.start(create=create).messages assert res == [] RxPY-3.2.0/tests/test_observable/test_starmap.py000066400000000000000000000261711404130727200217720ustar00rootroot00000000000000import unittest from rx import return_value, throw, empty, create from rx.testing import TestScheduler, ReactiveTest from rx.disposable import SerialDisposable from rx import operators as ops on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class RxException(Exception): pass # Helper function for raising exceptions within lambdas def _raise(ex): raise RxException(ex) class TestSelect(unittest.TestCase): def test_starmap_never(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable() invoked = [0] def factory(): def mapper(x, y): invoked[0] += 1 return x + y return xs.pipe(ops.starmap(mapper)) results = scheduler.start(factory) assert results.messages == [] assert xs.subscriptions == [ReactiveTest.subscribe(200, 1000)] assert invoked[0] == 0 def test_starmap_empty(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( # 100 create # 200 subscribe on_completed(300), ) invoked = [0] def factory(): def mapper(x, y): invoked[0] += 1 return x + y return xs.pipe(ops.starmap(mapper)) results = scheduler.start(factory) assert results.messages == [on_completed(300)] assert xs.subscriptions == [ReactiveTest.subscribe(200, 300)] assert invoked[0] == 0 def test_starmap_subscription_error(self): mapper = ops.starmap(lambda x, y: (x, y)) with self.assertRaises(RxException): return_value((1, 10)).pipe( mapper ).subscribe(lambda x: _raise("ex")) with self.assertRaises(RxException): throw('ex').pipe( mapper ).subscribe(on_error=lambda ex: _raise(ex)) with self.assertRaises(RxException): empty().pipe( mapper ).subscribe(lambda x: x, lambda ex: ex, lambda: _raise('ex')) def subscribe(observer, scheduler=None): _raise('ex') with self.assertRaises(RxException): create(subscribe).pipe( mapper ).subscribe() def test_starmap_dispose_inside_mapper(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( # 100 create on_next(110, (1, 10)), # 200 subscribe on_next(210, (2, 20)), on_next(310, (3, 30)), on_next(410, (4, 40))) results = scheduler.create_observer() d = SerialDisposable() invoked = [0] def mapper(x, y): invoked[0] += 1 if scheduler._clock > 250: d.dispose() return x + y d.disposable = xs.pipe( ops.starmap(mapper) ).subscribe(results, scheduler) def action(scheduler, state): return d.dispose() scheduler.schedule_absolute(ReactiveTest.disposed, action) scheduler.start() assert results.messages == [ on_next(110, 11), on_next(210, 22)] assert xs.subscriptions == [ReactiveTest.subscribe(0, 310)] assert invoked[0] == 3 def test_starmap_completed(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( # 100 create on_next(180, (1, 10)), # 200 subscribe on_next(210, (2, 20)), on_next(240, (3, 30)), on_next(290, (4, 40)), on_next(350, (5, 50)), on_completed(400), on_next(410, (-1, -10)), on_completed(420), on_error(430, 'ex')) invoked = [0] def factory(): def mapper(x, y): invoked[0] += 1 return x + y return xs.pipe(ops.starmap(mapper)) results = scheduler.start(factory) assert results.messages == [ on_next(210, 22), on_next(240, 33), on_next(290, 44), on_next(350, 55), on_completed(400)] assert xs.subscriptions == [ReactiveTest.subscribe(200, 400)] assert invoked[0] == 4 def test_starmap_not_completed(self): scheduler = TestScheduler() invoked = [0] xs = scheduler.create_hot_observable( # 100 create on_next(180, (1, 10)), # 200 subscribe on_next(210, (2, 20)), on_next(240, (3, 30)), on_next(290, (4, 40)), on_next(350, (5, 50))) def factory(): def mapper(x, y): invoked[0] += 1 return x + y return xs.pipe(ops.starmap(mapper)) results = scheduler.start(factory) assert results.messages == [ on_next(210, 22), on_next(240, 33), on_next(290, 44), on_next(350, 55)] assert xs.subscriptions == [subscribe(200, 1000)] assert invoked[0] == 4 def test_starmap_no_mapper(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( # 100 create on_next(180, (1, 10)), # 200 subscribe on_next(210, (2, 20)), on_next(240, (3, 30)), on_next(290, (4, 40)), on_next(350, (5, 50)), on_completed(400), on_next(410, (-1, -10)), on_completed(420), on_error(430, 'ex')) def factory(): return xs.pipe(ops.starmap()) results = scheduler.start(factory) assert results.messages == [ on_next(210, (2, 20)), on_next(240, (3, 30)), on_next(290, (4, 40)), on_next(350, (5, 50)), on_completed(400)] assert xs.subscriptions == [ReactiveTest.subscribe(200, 400)] def test_starmap_mapper_with_one_element(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( # 100 create on_next(180, (1,)), # 200 subscribe on_next(210, (2,)), on_next(240, (3,)), on_next(290, (4,)), on_next(350, (5,)), on_completed(400), on_next(410, (-1,)), on_completed(420), on_error(430, 'ex')) invoked = [0] def factory(): def mapper(x): invoked[0] += 1 return x * 10 return xs.pipe(ops.starmap(mapper)) results = scheduler.start(factory) assert results.messages == [ on_next(210, 20), on_next(240, 30), on_next(290, 40), on_next(350, 50), on_completed(400)] assert xs.subscriptions == [ReactiveTest.subscribe(200, 400)] assert invoked[0] == 4 def test_starmap_mapper_with_three_elements(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( # 100 create on_next(180, (1, 10, 100)), # 200 subscribe on_next(210, (2, 20, 200)), on_next(240, (3, 30, 300)), on_next(290, (4, 40, 400)), on_next(350, (5, 50, 500)), on_completed(400), on_next(410, (-1, -10, -100)), on_completed(420), on_error(430, 'ex')) invoked = [0] def factory(): def mapper(x, y, z): invoked[0] += 1 return x + y + z return xs.pipe(ops.starmap(mapper)) results = scheduler.start(factory) assert results.messages == [ on_next(210, 222), on_next(240, 333), on_next(290, 444), on_next(350, 555), on_completed(400)] assert xs.subscriptions == [ReactiveTest.subscribe(200, 400)] assert invoked[0] == 4 def test_starmap_mapper_with_args(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( # 100 create on_next(180, (1, 10)), # 200 subscribe on_next(210, (2, 20)), on_next(240, (3, 30)), on_next(290, (4, 40)), on_next(350, (5, 50)), on_completed(400), on_next(410, (-1, -10)), on_completed(420), on_error(430, 'ex')) invoked = [0] def factory(): def mapper(*args): invoked[0] += 1 return sum(args) return xs.pipe(ops.starmap(mapper)) results = scheduler.start(factory) assert results.messages == [ on_next(210, 22), on_next(240, 33), on_next(290, 44), on_next(350, 55), on_completed(400)] assert xs.subscriptions == [ReactiveTest.subscribe(200, 400)] assert invoked[0] == 4 def test_starmap_error(self): scheduler = TestScheduler() ex = 'ex' invoked = [0] xs = scheduler.create_hot_observable( # 100 create on_next(180, (1, 10)), # 200 subscribe on_next(210, (2, 20)), on_next(240, (3, 30)), on_next(290, (4, 40)), on_next(350, (5, 50)), on_error(400, ex), on_next(410, (-1, -10)), on_completed(420), on_error(430, ex)) def factory(): def mapper(x, y): invoked[0] += 1 return x + y return xs.pipe(ops.starmap(mapper)) results = scheduler.start(factory) assert results.messages == [ on_next(210, 22), on_next(240, 33), on_next(290, 44), on_next(350, 55), on_error(400, ex)] assert xs.subscriptions == [subscribe(200, 400)] assert invoked[0] == 4 def test_starmap_mapper_error(self): scheduler = TestScheduler() invoked = [0] ex = 'ex' xs = scheduler.create_hot_observable( # 100 create on_next(180, (1, 10)), # 200 subscribe on_next(210, (2, 20)), on_next(240, (3, 30)), on_next(290, (4, 40)), on_next(350, (5, 50)), on_completed(400), on_next(410, (-1, -10)), on_completed(420), on_error(430, ex)) def factory(): def mapper(x, y): invoked[0] += 1 if invoked[0] == 3: raise Exception(ex) return x + y return xs.pipe(ops.starmap(mapper)) results = scheduler.start(factory) assert results.messages == [ on_next(210, 22), on_next(240, 33), on_error(290, ex)] assert xs.subscriptions == [subscribe(200, 290)] assert invoked[0] == 3 if __name__ == '__main__': unittest.main() RxPY-3.2.0/tests/test_observable/test_start.py000066400000000000000000000046061404130727200214570ustar00rootroot00000000000000import unittest import asyncio from asyncio import Future import rx from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class TestStart(unittest.TestCase): def test_start_async(self): loop = asyncio.get_event_loop() success = [False] @asyncio.coroutine def go(): def func(): future = Future() future.set_result(42) return future source = rx.start_async(func) def on_next(x): success[0] = (42 == x) source.subscribe(on_next) loop.run_until_complete(go()) assert(all(success)) def test_start_async_error(self): loop = asyncio.get_event_loop() success = [False] @asyncio.coroutine def go(): def func(): future = Future() future.set_exception(Exception(str(42))) return future source = rx.start_async(func) def on_error(ex): success[0] = (str(42) == str(ex)) source.subscribe(on_error=on_error) loop.run_until_complete(go()) assert(all(success)) def test_start_action2(self): scheduler = TestScheduler() done = [False] def create(): def func(): done[0] = True return rx.start(func, scheduler) res = scheduler.start(create) assert res.messages == [ on_next(200, None), on_completed(200)] assert(done) def test_start_func2(self): scheduler = TestScheduler() def create(): def func(): return 1 return rx.start(func, scheduler) res = scheduler.start(create) assert res.messages == [ on_next(200, 1), on_completed(200)] def test_start_funcerror(self): ex = Exception() scheduler = TestScheduler() def create(): def func(): raise ex return rx.start(func, scheduler) res = scheduler.start(create) assert res.messages == [ on_error(200, ex)] RxPY-3.2.0/tests/test_observable/test_startwith.py000066400000000000000000000043651404130727200223550ustar00rootroot00000000000000import unittest from rx import operators as _ from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class TestStartWith(unittest.TestCase): def test_start_with(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_next(220, 2), on_completed(250)) def create(): return xs.pipe(_.start_with(1)) results = scheduler.start(create) assert results.messages == [on_next(200, 1), on_next(220, 2), on_completed(250)] # def test_start_with_scheduler(self): # scheduler = TestScheduler() # xs = scheduler.create_hot_observable(on_next(150, 1), on_next(220, 2), on_completed(250)) # def create(): # return xs.pipe(_.start_with(scheduler) # results = scheduler.start(create) # assert results.messages == [on_next(220, 2), on_completed(250)] def test_start_with_scheduler_and_arg(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_next(220, 2), on_completed(250)) def create(): return xs.pipe(_.start_with(42)) results = scheduler.start(create) assert results.messages == [on_next(200, 42), on_next(220, 2), on_completed(250)] def test_start_with_immediate_scheduler_and_arg(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_next(220, 2), on_completed(250)) def create(): return xs.pipe(_.start_with(42)) results = scheduler.start(create) assert results.messages == [on_next(200, 42), on_next(220, 2), on_completed(250)] def test_start_with_scheduler_keyword_and_arg(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_next(220, 2), on_completed(250)) def create(): return xs.pipe(_.start_with(42)) results = scheduler.start(create) assert results.messages == [on_next(200, 42), on_next(220, 2), on_completed(250)] RxPY-3.2.0/tests/test_observable/test_subscribeon.py000066400000000000000000000041661404130727200226410ustar00rootroot00000000000000import unittest import rx from rx import operators as ops from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class TestSubscribeOn(unittest.TestCase): def test_subscribe_on_normal(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(150, 1), on_next(210, 2), on_completed(250) ) def create(): return xs.pipe(ops.subscribe_on(scheduler)) results = scheduler.start(create) assert results.messages == [on_next(210, 2), on_completed(250)] assert xs.subscriptions == [subscribe(200, 250)] def test_subscribe_on_error(self): scheduler = TestScheduler() ex = 'ex' xs = scheduler.create_hot_observable( on_next(150, 1), on_error(210, ex) ) def create(): return xs.pipe(ops.subscribe_on(scheduler)) results = scheduler.start(create) assert results.messages == [on_error(210, ex)] assert xs.subscriptions == [subscribe(200, 210)] def test_subscribe_on_empty(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(150, 1), on_completed(250) ) def create(): return xs.pipe(ops.subscribe_on(scheduler)) results = scheduler.start(create) assert results.messages == [on_completed(250)] assert xs.subscriptions == [subscribe(200, 250)] def test_subscribe_on_never(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(150, 1) ) def create(): return xs.pipe(ops.subscribe_on(scheduler)) results = scheduler.start(create) assert results.messages == [] assert xs.subscriptions == [subscribe(200, 1000)] if __name__ == '__main__': unittest.main() RxPY-3.2.0/tests/test_observable/test_sum.py000066400000000000000000000050251404130727200211220ustar00rootroot00000000000000import unittest from rx import operators as ops from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class TestSum(unittest.TestCase): def test_sum_int32_empty(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_completed(250)) def create(): return xs.pipe(ops.sum()) res = scheduler.start(create=create).messages assert res == [on_next(250, 0), on_completed(250)] def test_sum_int32_return(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(150, 1), on_next(210, 2), on_completed(250)) def create(): return xs.pipe(ops.sum()) res = scheduler.start(create=create).messages assert res == [on_next(250, 2), on_completed(250)] def test_sum_int32_some(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(150, 1), on_next(210, 2), on_next(220, 3), on_next(230, 4), on_completed(250)) def create(): return xs.pipe(ops.sum()) res = scheduler.start(create=create).messages assert res == [on_next(250, 2 + 3 + 4), on_completed(250)] def test_sum_int32_on_error(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_error(210, ex)) def create(): return xs.pipe(ops.sum()) res = scheduler.start(create=create).messages assert res == [on_error(210, ex)] def test_sum_int32_never(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1)) def create(): return xs.pipe(ops.sum()) res = scheduler.start(create=create).messages assert res == [] def test_sum_mapper_regular_int32(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(210, "fo"), on_next(220, "b"), on_next(230, "qux"), on_completed(240)) def create(): return xs.pipe(ops.sum(lambda x: len(x))) res = scheduler.start(create=create) assert res.messages == [on_next(240, 6), on_completed(240)] assert xs.subscriptions == [subscribe(200, 240)] RxPY-3.2.0/tests/test_observable/test_switchlatest.py000066400000000000000000000117221404130727200230350ustar00rootroot00000000000000import unittest from rx import operators as ops from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class TestSwitch(unittest.TestCase): def test_switch_data(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(300, scheduler.create_cold_observable( on_next(10, 101), on_next(20, 102), on_next(110, 103), on_next(120, 104), on_next(210, 105), on_next(220, 106), on_completed(230))), on_next(400, scheduler.create_cold_observable( on_next(10, 201), on_next(20, 202), on_next(30, 203), on_next(40, 204), on_completed(50))), on_next(500, scheduler.create_cold_observable( on_next(10, 301), on_next(20, 302), on_next(30, 303), on_next(40, 304), on_completed(150))), on_completed(600)) def create(): return xs.pipe(ops.switch_latest()) results = scheduler.start(create) assert results.messages == [ on_next(310, 101), on_next(320, 102), on_next(410, 201), on_next(420, 202), on_next(430, 203), on_next(440, 204), on_next(510, 301), on_next(520, 302), on_next(530, 303), on_next(540, 304), on_completed(650)] def test_switch_inner_throws(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(300, scheduler.create_cold_observable( on_next(10, 101), on_next(20, 102), on_next(110, 103), on_next(120, 104), on_next(210, 105), on_next(220, 106), on_completed(230))), on_next(400, scheduler.create_cold_observable( on_next(10, 201), on_next(20, 202), on_next(30, 203), on_next(40, 204), on_error(50, ex))), on_next(500, scheduler.create_cold_observable( on_next(10, 301), on_next(20, 302), on_next(30, 303), on_next(40, 304), on_completed(150))), on_completed(600)) def create(): return xs.pipe(ops.switch_latest()) results = scheduler.start(create) assert results.messages == [ on_next(310, 101), on_next(320, 102), on_next(410, 201), on_next(420, 202), on_next(430, 203), on_next(440, 204), on_error(450, ex)] def test_switch_outer_throws(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(300, scheduler.create_cold_observable( on_next(10, 101), on_next(20, 102), on_next(110, 103), on_next(120, 104), on_next(210, 105), on_next(220, 106), on_completed(230))), on_next(400, scheduler.create_cold_observable( on_next(10, 201), on_next(20, 202), on_next(30, 203), on_next(40, 204), on_completed(50))), on_error(500, ex)) def create(): return xs.pipe(ops.switch_latest()) results = scheduler.start(create) assert results.messages == [ on_next(310, 101), on_next(320, 102), on_next(410, 201), on_next(420, 202), on_next(430, 203), on_next(440, 204), on_error(500, ex)] def test_switch_no_inner(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_completed(500)) def create(): return xs.pipe(ops.switch_latest()) results = scheduler.start(create) assert results.messages == [on_completed(500)] def test_switch_inner_completes(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(300, scheduler.create_cold_observable( on_next(10, 101), on_next(20, 102), on_next(110, 103), on_next(120, 104), on_next(210, 105), on_next(220, 106), on_completed(230))), on_completed(540)) def create(): return xs.pipe(ops.switch_latest()) results = scheduler.start(create) assert results.messages == [ on_next(310, 101), on_next(320, 102), on_next(410, 103), on_next(420, 104), on_next(510, 105), on_next(520, 106), on_completed(540)] RxPY-3.2.0/tests/test_observable/test_take.py000066400000000000000000000212431404130727200212420ustar00rootroot00000000000000import unittest from rx import operators as ops from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class TestTake(unittest.TestCase): def test_take_complete_after(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(70, 6), on_next(150, 4), on_next(210, 9), on_next(230, 13), on_next(270, 7), on_next(280, 1), on_next(300, -1), on_next(310, 3), on_next(340, 8), on_next(370, 11), on_next(410, 15), on_next(415, 16), on_next(460, 72), on_next(510, 76), on_next(560, 32), on_next(570, -100), on_next(580, -3), on_next(590, 5), on_next(630, 10), on_completed(690)) def create(): return xs.pipe(ops.take(20)) results = scheduler.start(create) assert results.messages == [ on_next(210, 9), on_next(230, 13), on_next(270, 7), on_next(280, 1), on_next(300, -1), on_next(310, 3), on_next(340, 8), on_next(370, 11), on_next(410, 15), on_next(415, 16), on_next(460, 72), on_next(510, 76), on_next(560, 32), on_next(570, -100), on_next(580, -3), on_next(590, 5), on_next(630, 10), on_completed(690)] assert xs.subscriptions == [subscribe(200, 690)] def test_take_complete_same(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(70, 6), on_next(150, 4), on_next(210, 9), on_next(230, 13), on_next(270, 7), on_next(280, 1), on_next(300, -1), on_next(310, 3), on_next(340, 8), on_next(370, 11), on_next(410, 15), on_next(415, 16), on_next(460, 72), on_next(510, 76), on_next(560, 32), on_next(570, -100), on_next(580, -3), on_next(590, 5), on_next(630, 10), on_completed(690)) def create(): return xs.pipe(ops.take(17)) results = scheduler.start(create) assert results.messages == [ on_next(210, 9), on_next(230, 13), on_next(270, 7), on_next(280, 1), on_next(300, -1), on_next(310, 3), on_next(340, 8), on_next(370, 11), on_next(410, 15), on_next(415, 16), on_next(460, 72), on_next(510, 76), on_next(560, 32), on_next(570, -100), on_next(580, -3), on_next(590, 5), on_next(630, 10), on_completed(630)] assert xs.subscriptions == [subscribe(200, 630)] def test_take_complete_before(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(70, 6), on_next(150, 4), on_next(210, 9), on_next(230, 13), on_next(270, 7), on_next(280, 1), on_next(300, -1), on_next(310, 3), on_next(340, 8), on_next(370, 11), on_next(410, 15), on_next(415, 16), on_next(460, 72), on_next(510, 76), on_next(560, 32), on_next(570, -100), on_next(580, -3), on_next(590, 5), on_next(630, 10), on_completed(690)) def create(): return xs.pipe(ops.take(10)) results = scheduler.start(create) assert results.messages == [ on_next(210, 9), on_next(230, 13), on_next(270, 7), on_next(280, 1), on_next(300, -1), on_next(310, 3), on_next(340, 8), on_next(370, 11), on_next(410, 15), on_next(415, 16), on_completed(415)] assert xs.subscriptions == [subscribe(200, 415)] def test_take_error_after(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(70, 6), on_next(150, 4), on_next(210, 9), on_next(230, 13), on_next(270, 7), on_next(280, 1), on_next(300, -1), on_next(310, 3), on_next(340, 8), on_next(370, 11), on_next(410, 15), on_next(415, 16), on_next(460, 72), on_next(510, 76), on_next(560, 32), on_next(570, -100), on_next(580, -3), on_next(590, 5), on_next(630, 10), on_error(690, ex)) def create(): return xs.pipe(ops.take(20)) results = scheduler.start(create) assert results.messages == [ on_next(210, 9), on_next(230, 13), on_next(270, 7), on_next(280, 1), on_next(300, -1), on_next(310, 3), on_next(340, 8), on_next(370, 11), on_next(410, 15), on_next(415, 16), on_next(460, 72), on_next(510, 76), on_next(560, 32), on_next(570, -100), on_next(580, -3), on_next(590, 5), on_next(630, 10), on_error(690, ex)] assert xs.subscriptions == [subscribe(200, 690)] def test_take_error_same(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(70, 6), on_next(150, 4), on_next(210, 9), on_next(230, 13), on_next(270, 7), on_next(280, 1), on_next(300, -1), on_next(310, 3), on_next(340, 8), on_next(370, 11), on_next(410, 15), on_next(415, 16), on_next(460, 72), on_next(510, 76), on_next(560, 32), on_next(570, -100), on_next(580, -3), on_next(590, 5), on_next(630, 10), on_error(690, 'ex')) def create(): return xs.pipe(ops.take(17)) results = scheduler.start(create) assert results.messages == [ on_next(210, 9), on_next(230, 13), on_next(270, 7), on_next(280, 1), on_next(300, -1), on_next(310, 3), on_next(340, 8), on_next(370, 11), on_next(410, 15), on_next(415, 16), on_next(460, 72), on_next(510, 76), on_next(560, 32), on_next(570, -100), on_next(580, -3), on_next(590, 5), on_next(630, 10), on_completed(630)] assert xs.subscriptions == [subscribe(200, 630)] def test_take_error_before(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(70, 6), on_next(150, 4), on_next(210, 9), on_next(230, 13), on_next(270, 7), on_next(280, 1), on_next(300, -1), on_next(310, 3), on_next(340, 8), on_next(370, 11), on_next(410, 15), on_next(415, 16), on_next(460, 72), on_next(510, 76), on_next(560, 32), on_next(570, -100), on_next(580, -3), on_next(590, 5), on_next(630, 10), on_error(690, 'ex')) def create(): return xs.pipe(ops.take(3)) results = scheduler.start(create) assert results.messages == [ on_next(210, 9), on_next(230, 13), on_next(270, 7), on_completed(270)] assert xs.subscriptions == [subscribe(200, 270)] def test_take_dispose_before(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(70, 6), on_next(150, 4), on_next(210, 9), on_next(230, 13), on_next(270, 7), on_next(280, 1), on_next(300, -1), on_next(310, 3), on_next(340, 8), on_next(370, 11), on_next(410, 15), on_next(415, 16), on_next(460, 72), on_next(510, 76), on_next(560, 32), on_next(570, -100), on_next(580, -3), on_next(590, 5), on_next(630, 10)) def create(): return xs.pipe(ops.take(3)) results = scheduler.start(create, disposed=250) assert results.messages == [on_next(210, 9), on_next(230, 13)] assert xs.subscriptions == [subscribe(200, 250)] def test_take_dispose_after(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(70, 6), on_next(150, 4), on_next(210, 9), on_next(230, 13), on_next(270, 7), on_next(280, 1), on_next(300, -1), on_next(310, 3), on_next(340, 8), on_next(370, 11), on_next(410, 15), on_next(415, 16), on_next(460, 72), on_next(510, 76), on_next(560, 32), on_next(570, -100), on_next(580, -3), on_next(590, 5), on_next(630, 10)) def create(): return xs.pipe(ops.take(3)) results = scheduler.start(create, disposed=400) assert results.messages == [ on_next(210, 9), on_next(230, 13), on_next(270, 7), on_completed(270)] assert xs.subscriptions == [subscribe(200, 270)] RxPY-3.2.0/tests/test_observable/test_takelast.py000066400000000000000000000130341404130727200221250ustar00rootroot00000000000000import unittest from rx import operators as ops from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class TestTakeLast(unittest.TestCase): def test_take_last_zero_completed(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(180, 1), on_next(210, 2), on_next(250, 3), on_next(270, 4), on_next(310, 5), on_next(360, 6), on_next(380, 7), on_next(410, 8), on_next(590, 9), on_completed(650)) def create(): return xs.pipe(ops.take_last(0)) results = scheduler.start(create) assert results.messages == [on_completed(650)] assert xs.subscriptions == [subscribe(200, 650)] def test_take_last_zero_error(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(180, 1), on_next(210, 2), on_next(250, 3), on_next(270, 4), on_next(310, 5), on_next(360, 6), on_next(380, 7), on_next(410, 8), on_next(590, 9), on_error(650, ex)) def create(): return xs.pipe(ops.take_last(0)) results = scheduler.start(create) assert results.messages == [on_error(650, ex)] assert xs.subscriptions == [subscribe(200, 650)] def test_take_last_zero_disposed(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(180, 1), on_next(210, 2), on_next(250, 3), on_next(270, 4), on_next(310, 5), on_next(360, 6), on_next(380, 7), on_next(410, 8), on_next(590, 9)) def create(): return xs.pipe(ops.take_last(0)) results = scheduler.start(create) assert results.messages == [] assert xs.subscriptions == [subscribe(200, 1000)] def test_take_last_one_completed(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(180, 1), on_next(210, 2), on_next(250, 3), on_next(270, 4), on_next(310, 5), on_next(360, 6), on_next(380, 7), on_next(410, 8), on_next(590, 9), on_completed(650)) def create(): return xs.pipe(ops.take_last(1)) results = scheduler.start(create) assert results.messages == [on_next(650, 9), on_completed(650)] assert xs.subscriptions == [subscribe(200, 650)] def test_take_last_one_error(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(180, 1), on_next(210, 2), on_next(250, 3), on_next(270, 4), on_next(310, 5), on_next(360, 6), on_next(380, 7), on_next(410, 8), on_next(590, 9), on_error(650, ex)) def create(): return xs.pipe(ops.take_last(1)) results = scheduler.start(create) assert results.messages == [on_error(650, ex)] assert xs.subscriptions == [subscribe(200, 650)] def test_take_last_One_disposed(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(180, 1), on_next(210, 2), on_next(250, 3), on_next(270, 4), on_next(310, 5), on_next(360, 6), on_next(380, 7), on_next(410, 8), on_next(590, 9)) def create(): return xs.pipe(ops.take_last(1)) results = scheduler.start(create) assert results.messages == [] assert xs.subscriptions == [subscribe(200, 1000)] def test_take_last_three_completed(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(180, 1), on_next(210, 2), on_next(250, 3), on_next(270, 4), on_next(310, 5), on_next(360, 6), on_next(380, 7), on_next(410, 8), on_next(590, 9), on_completed(650)) def create(): return xs.pipe(ops.take_last(3)) results = scheduler.start(create) assert results.messages == [ on_next(650, 7), on_next(650, 8), on_next(650, 9), on_completed(650)] assert xs.subscriptions == [subscribe(200, 650)] def test_Take_last_three_error(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(180, 1), on_next(210, 2), on_next(250, 3), on_next(270, 4), on_next(310, 5), on_next(360, 6), on_next(380, 7), on_next(410, 8), on_next(590, 9), on_error(650, ex)) def create(): return xs.pipe(ops.take_last(3)) results = scheduler.start(create) assert results.messages == [on_error(650, ex)] assert xs.subscriptions == [subscribe(200, 650)] def test_Take_last_three_disposed(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(180, 1), on_next(210, 2), on_next(250, 3), on_next(270, 4), on_next(310, 5), on_next(360, 6), on_next(380, 7), on_next(410, 8), on_next(590, 9)) def create(): return xs.pipe(ops.take_last(3)) results = scheduler.start(create) assert results.messages == [] assert xs.subscriptions == [subscribe(200, 1000)] RxPY-3.2.0/tests/test_observable/test_takelastbuffer.py000066400000000000000000000131621404130727200233210ustar00rootroot00000000000000import unittest from rx import operators as ops from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class TestTakeLastBuffer(unittest.TestCase): def test_take_last_buffer_zero_completed(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(180, 1), on_next(210, 2), on_next(250, 3), on_next(270, 4), on_next(310, 5), on_next(360, 6), on_next(380, 7), on_next(410, 8), on_next(590, 9), on_completed(650)) def create(): return xs.pipe(ops.take_last_buffer(0)) res = scheduler.start(create) def predicate(lst): return len(lst) == 0 assert [on_next(650, predicate), on_completed(650)] == res.messages assert xs.subscriptions == [subscribe(200, 650)] def test_take_last_buffer_zero_error(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(180, 1), on_next(210, 2), on_next(250, 3), on_next(270, 4), on_next(310, 5), on_next(360, 6), on_next(380, 7), on_next(410, 8), on_next(590, 9), on_error(650, ex)) def create(): return xs.pipe(ops.take_last_buffer(0)) res = scheduler.start(create) assert res.messages == [on_error(650, ex)] assert xs.subscriptions == [subscribe(200, 650)] def test_take_last_buffer_zero_disposed(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(180, 1), on_next(210, 2), on_next(250, 3), on_next(270, 4), on_next(310, 5), on_next(360, 6), on_next(380, 7), on_next(410, 8), on_next(590, 9)) def create(): return xs.pipe(ops.take_last_buffer(0)) res = scheduler.start(create) assert res.messages == [] assert xs.subscriptions == [subscribe(200, 1000)] def test_take_last_buffer_one_completed(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(180, 1), on_next(210, 2), on_next(250, 3), on_next(270, 4), on_next(310, 5), on_next(360, 6), on_next(380, 7), on_next(410, 8), on_next(590, 9), on_completed(650)) def create(): return xs.pipe(ops.take_last_buffer(1)) res = scheduler.start(create) def predicate(lst): return lst == [9] assert [on_next(650, predicate), on_completed(650)] == res.messages assert xs.subscriptions == [subscribe(200, 650)] def test_take_last_buffer_one_error(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(180, 1), on_next(210, 2), on_next(250, 3), on_next(270, 4), on_next(310, 5), on_next(360, 6), on_next(380, 7), on_next(410, 8), on_next(590, 9), on_error(650, ex)) def create(): return xs.pipe(ops.take_last_buffer(1)) res = scheduler.start(create) assert res.messages == [on_error(650, ex)] assert xs.subscriptions == [subscribe(200, 650)] def test_take_last_buffer_one_disposed(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(180, 1), on_next(210, 2), on_next(250, 3), on_next(270, 4), on_next(310, 5), on_next(360, 6), on_next(380, 7), on_next(410, 8), on_next(590, 9)) def create(): return xs.pipe(ops.take_last_buffer(1)) res = scheduler.start(create) assert res.messages == [] assert xs.subscriptions == [subscribe(200, 1000)] def test_take_last_buffer_three_completed(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(180, 1), on_next(210, 2), on_next(250, 3), on_next(270, 4), on_next(310, 5), on_next(360, 6), on_next(380, 7), on_next(410, 8), on_next(590, 9), on_completed(650)) def create(): return xs.pipe(ops.take_last_buffer(3)) res = scheduler.start(create) def predicate(lst): return lst == [7, 8, 9] assert [on_next(650, predicate), on_completed(650)] == res.messages assert xs.subscriptions == [subscribe(200, 650)] # def test_Take_last_buffer_Three_Error(): # var ex, res, scheduler, xs # ex = 'ex' # scheduler = TestScheduler() # xs = scheduler.create_hot_observable(on_next(180, 1), on_next(210, 2), on_next(250, 3), on_next(270, 4), on_next(310, 5), on_next(360, 6), on_next(380, 7), on_next(410, 8), on_next(590, 9), on_error(650, ex)) # res = scheduler.start(create) # return xs.pipe(ops.take_last_buffer(3)) # assert res.messages == [on_error(650, ex)] # assert xs.subscriptions == [subscribe(200, 650)] # def test_Take_last_buffer_Three_Disposed(): # var res, scheduler, xs # scheduler = TestScheduler() # xs = scheduler.create_hot_observable(on_next(180, 1), on_next(210, 2), on_next(250, 3), on_next(270, 4), on_next(310, 5), on_next(360, 6), on_next(380, 7), on_next(410, 8), on_next(590, 9)) # res = scheduler.start(create) # return xs.pipe(ops.take_last_buffer(3)) # assert res.messages == [] # assert xs.subscriptions == [subscribe(200, 1000)] RxPY-3.2.0/tests/test_observable/test_takelastwithtime.py000066400000000000000000000106561404130727200237070ustar00rootroot00000000000000import unittest from rx import operators as ops from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class TestTakeLast(unittest.TestCase): def test_takelast_zero1(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(210, 1), on_next(220, 2), on_completed(230)) def create(): return xs.pipe(ops.take_last_with_time(0)) res = scheduler.start(create) assert res.messages == [on_completed(230)] assert xs.subscriptions == [subscribe(200, 230)] def test_takelast_zero2(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(210, 1), on_next(220, 2), on_next(230, 3), on_completed(230)) def create(): return xs.pipe(ops.take_last_with_time(0)) res = scheduler.start(create) assert res.messages == [on_completed(230)] assert xs.subscriptions == [subscribe(200, 230)] def test_takelast_some1(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(210, 1), on_next(220, 2), on_next(230, 3), on_completed(240)) def create(): return xs.pipe(ops.take_last_with_time(25)) res = scheduler.start(create) assert res.messages == [on_next(240, 2), on_next(240, 3), on_completed(240)] assert xs.subscriptions == [subscribe(200, 240)] def test_takelast_some2(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(210, 1), on_next(220, 2), on_next(230, 3), on_completed(300)) def create(): return xs.pipe(ops.take_last_with_time(25)) res = scheduler.start(create) assert res.messages == [on_completed(300)] assert xs.subscriptions == [subscribe(200, 300)] def test_takelast_some3(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(210, 1), on_next(220, 2), on_next(230, 3), on_next(240, 4), on_next(250, 5), on_next(260, 6), on_next(270, 7), on_next(280, 8), on_next(290, 9), on_completed(300)) def create(): return xs.pipe(ops.take_last_with_time(45)) res = scheduler.start(create) assert res.messages == [ on_next(300, 6), on_next(300, 7), on_next(300, 8), on_next(300, 9), on_completed(300)] assert xs.subscriptions == [subscribe(200, 300)] def test_takelast_some4(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(210, 1), on_next(240, 2), on_next(250, 3), on_next(280, 4), on_next(290, 5), on_next(300, 6), on_completed(350)) def create(): return xs.pipe(ops.take_last_with_time(25)) res = scheduler.start(create) assert res.messages == [on_completed(350)] assert xs.subscriptions == [subscribe(200, 350)] def test_takelast_all(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(210, 1), on_next(220, 2), on_completed(230)) def create(): return xs.pipe(ops.take_last_with_time(50)) res = scheduler.start(create) assert res.messages == [on_next(230, 1), on_next(230, 2), on_completed(230)] assert xs.subscriptions == [subscribe(200, 230)] def test_takelast_error(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_error(210, ex)) def create(): return xs.pipe(ops.take_last_with_time(50)) res = scheduler.start(create) assert res.messages == [on_error(210, ex)] assert xs.subscriptions == [subscribe(200, 210)] def test_takelast_never(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable() def create(): return xs.pipe(ops.take_last_with_time(50)) res = scheduler.start(create) assert res.messages == [] assert xs.subscriptions == [subscribe(200, 1000)] RxPY-3.2.0/tests/test_observable/test_takeuntil.py000066400000000000000000000143311404130727200223160ustar00rootroot00000000000000import unittest import rx from rx import operators as ops from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class RxException(Exception): pass # Helper function for raising exceptions within lambdas def _raise(ex): raise RxException(ex) class TestTakeUntil(unittest.TestCase): def test_take_until_preempt_somedata_next(self): scheduler = TestScheduler() l_msgs = [on_next(150, 1), on_next(210, 2), on_next(220, 3), on_next(230, 4), on_next(240, 5), on_completed(250)] r_msgs = [on_next(150, 1), on_next(225, 99), on_completed(230)] l = scheduler.create_hot_observable(l_msgs) r = scheduler.create_hot_observable(r_msgs) def create(): return l.pipe(ops.take_until(r)) results = scheduler.start(create) assert results.messages == [on_next(210, 2), on_next(220, 3), on_completed(225)] def test_take_until_preempt_somedata_error(self): ex = 'ex' scheduler = TestScheduler() l_msgs = [on_next(150, 1), on_next(210, 2), on_next(220, 3), on_next(230, 4), on_next(240, 5), on_completed(250)] r_msgs = [on_next(150, 1), on_error(225, ex)] l = scheduler.create_hot_observable(l_msgs) r = scheduler.create_hot_observable(r_msgs) def create(): return l.pipe(ops.take_until(r)) results = scheduler.start(create) assert results.messages == [on_next(210, 2), on_next(220, 3), on_error(225, ex)] def test_take_until_nopreempt_somedata_empty(self): scheduler = TestScheduler() l_msgs = [on_next(150, 1), on_next(210, 2), on_next(220, 3), on_next(230, 4), on_next(240, 5), on_completed(250)] r_msgs = [on_next(150, 1), on_completed(225)] l = scheduler.create_hot_observable(l_msgs) r = scheduler.create_hot_observable(r_msgs) def create(): return l.pipe(ops.take_until(r)) results = scheduler.start(create) assert results.messages == [on_next(210, 2), on_next( 220, 3), on_next(230, 4), on_next(240, 5), on_completed(250)] def test_take_until_nopreempt_somedata_never(self): scheduler = TestScheduler() l_msgs = [on_next(150, 1), on_next(210, 2), on_next(220, 3), on_next(230, 4), on_next(240, 5), on_completed(250)] l = scheduler.create_hot_observable(l_msgs) r = rx.never() def create(): return l.pipe(ops.take_until(r)) results = scheduler.start(create) assert results.messages == [on_next(210, 2), on_next( 220, 3), on_next(230, 4), on_next(240, 5), on_completed(250)] def test_take_until_preempt_never_next(self): scheduler = TestScheduler() r_msgs = [on_next(150, 1), on_next(225, 2), on_completed(250)] l = rx.never() r = scheduler.create_hot_observable(r_msgs) def create(): return l.pipe(ops.take_until(r)) results = scheduler.start(create) assert results.messages == [on_completed(225)] def test_take_until_preempt_never_error(self): ex = 'ex' scheduler = TestScheduler() r_msgs = [on_next(150, 1), on_error(225, ex)] l = rx.never() r = scheduler.create_hot_observable(r_msgs) def create(): return l.pipe(ops.take_until(r)) results = scheduler.start(create) assert results.messages == [on_error(225, ex)] def test_take_until_nopreempt_never_empty(self): scheduler = TestScheduler() r_msgs = [on_next(150, 1), on_completed(225)] l = rx.never() r = scheduler.create_hot_observable(r_msgs) def create(): return l.pipe(ops.take_until(r)) results = scheduler.start(create) assert results.messages == [] def test_take_until_nopreempt_never_never(self): scheduler = TestScheduler() l = rx.never() r = rx.never() def create(): return l.pipe(ops.take_until(r)) results = scheduler.start(create) assert results.messages == [] def test_take_until_preempt_beforefirstproduced(self): scheduler = TestScheduler() l_msgs = [on_next(150, 1), on_next(230, 2), on_completed(240)] r_msgs = [on_next(150, 1), on_next(210, 2), on_completed(220)] l = scheduler.create_hot_observable(l_msgs) r = scheduler.create_hot_observable(r_msgs) def create(): return l.pipe(ops.take_until(r)) results = scheduler.start(create) assert results.messages == [on_completed(210)] def test_take_until_preempt_beforefirstproduced_remain_silent_and_proper_disposed(self): scheduler = TestScheduler() l_msgs = [on_next(150, 1), on_error(215, 'ex'), on_completed(240)] r_msgs = [on_next(150, 1), on_next(210, 2), on_completed(220)] source_not_disposed = [False] def action(): source_not_disposed[0] = True l = scheduler.create_hot_observable(l_msgs).pipe(ops.do_action(on_next=action)) r = scheduler.create_hot_observable(r_msgs) def create(): return l.pipe(ops.take_until(r)) results = scheduler.start(create) assert results.messages == [on_completed(210)] assert(not source_not_disposed[0]) def test_take_until_nopreempt_afterlastproduced_proper_disposed_signal(self): scheduler = TestScheduler() l_msgs = [on_next(150, 1), on_next(230, 2), on_completed(240)] r_msgs = [on_next(150, 1), on_next(250, 2), on_completed(260)] signal_not_disposed = [False] l = scheduler.create_hot_observable(l_msgs) def action(): signal_not_disposed[0] = True r = scheduler.create_hot_observable(r_msgs).pipe(ops.do_action(on_next=action)) def create(): return l.pipe(ops.take_until(r)) results = scheduler.start(create) assert results.messages == [on_next(230, 2), on_completed(240)] assert(not signal_not_disposed[0]) RxPY-3.2.0/tests/test_observable/test_takeuntilwithtime.py000066400000000000000000000076001404130727200240720ustar00rootroot00000000000000from datetime import datetime import unittest from rx import operators as ops from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class RxException(Exception): pass # Helper function for raising exceptions within lambdas def _raise(ex): raise RxException(ex) class TestTakeUntilWithTime(unittest.TestCase): def test_takeuntil_zero(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(210, 1), on_next(220, 2), on_completed(230)) def create(): return xs.pipe(ops.take_until_with_time(datetime.utcfromtimestamp(0))) res = scheduler.start(create) assert res.messages == [on_completed(200)] assert xs.subscriptions == [subscribe(200, 200)] def test_takeuntil_late(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(210, 1), on_next(220, 2), on_completed(230) ) def create(): dt = datetime.utcfromtimestamp(250) return xs.pipe(ops.take_until_with_time(dt)) res = scheduler.start(create) assert res.messages == [on_next(210, 1), on_next(220, 2), on_completed(230)] assert xs.subscriptions == [subscribe(200, 230)] def test_takeuntil_error(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_error(210, ex)) def create(): dt = datetime.utcfromtimestamp(250) return xs.pipe(ops.take_until_with_time(dt)) res = scheduler.start(create) assert res.messages == [on_error(210, ex)] assert xs.subscriptions == [subscribe(200, 210)] def test_takeuntil_never(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable() def create(): dt = datetime.utcfromtimestamp(250) return xs.pipe(ops.take_until_with_time(dt)) res = scheduler.start(create) assert res.messages == [on_completed(250)] assert xs.subscriptions == [subscribe(200, 250)] def test_takeuntil_twice1(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(210, 1), on_next(220, 2), on_next(230, 3), on_next(240, 4), on_next(250, 5), on_next(260, 6), on_completed(270) ) def create(): dt235 = datetime.utcfromtimestamp(235) dt255 = datetime.utcfromtimestamp(255) return xs.pipe( ops.take_until_with_time(dt255), ops.take_until_with_time(dt235), ) res = scheduler.start(create) assert res.messages == [on_next(210, 1), on_next(220, 2), on_next(230, 3), on_completed(235)] assert xs.subscriptions == [subscribe(200, 235)] def test_takeuntil_twice2(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(210, 1), on_next(220, 2), on_next(230, 3), on_next(240, 4), on_next(250, 5), on_next(260, 6), on_completed(270) ) def create(): dt235 = datetime.utcfromtimestamp(235) dt255 = datetime.utcfromtimestamp(255) return xs.pipe( ops.take_until_with_time(dt235), ops.take_until_with_time(dt255), ) res = scheduler.start(create) assert res.messages == [on_next(210, 1), on_next(220, 2), on_next(230, 3), on_completed(235)] assert xs.subscriptions == [subscribe(200, 235)] RxPY-3.2.0/tests/test_observable/test_takewhile.py000066400000000000000000000240671404130727200223020ustar00rootroot00000000000000import unittest import rx from rx import operators as ops from rx.testing import TestScheduler, ReactiveTest, is_prime on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class TestTakeWhile(unittest.TestCase): def test_take_while_complete_Before(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(90, -1), on_next(110, -1), on_next(210, 2), on_next(260, 5), on_next(290, 13), on_next( 320, 3), on_completed(330), on_next(350, 7), on_next(390, 4), on_next(410, 17), on_next(450, 8), on_next(500, 23), on_completed(600)) invoked = 0 def factory(): def predicate(x): nonlocal invoked invoked += 1 return is_prime(x) return xs.pipe(ops.take_while(predicate)) results = scheduler.start(factory) assert results.messages == [on_next(210, 2), on_next( 260, 5), on_next(290, 13), on_next(320, 3), on_completed(330)] assert xs.subscriptions == [subscribe(200, 330)] assert(invoked == 4) def test_take_while_complete_after(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(90, -1), on_next(110, -1), on_next(210, 2), on_next(260, 5), on_next(290, 13), on_next( 320, 3), on_next(350, 7), on_next(390, 4), on_next(410, 17), on_next(450, 8), on_next(500, 23), on_completed(600)) invoked = 0 def factory(): def predicate(x): nonlocal invoked invoked += 1 return is_prime(x) return xs.pipe(ops.take_while(predicate)) results = scheduler.start(factory) assert results.messages == [on_next(210, 2), on_next(260, 5), on_next( 290, 13), on_next(320, 3), on_next(350, 7), on_completed(390)] assert xs.subscriptions == [subscribe(200, 390)] assert(invoked == 6) def test_take_while_error_before(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(90, -1), on_next(110, -1), on_next(210, 2), on_next(260, 5), on_error( 270, ex), on_next(290, 13), on_next(320, 3), on_next(350, 7), on_next(390, 4), on_next(410, 17), on_next(450, 8), on_next(500, 23)) invoked = 0 def factory(): def predicate(x): nonlocal invoked invoked += 1 return is_prime(x) return xs.pipe(ops.take_while(predicate)) results = scheduler.start(factory) assert results.messages == [on_next(210, 2), on_next(260, 5), on_error(270, ex)] assert xs.subscriptions == [subscribe(200, 270)] assert(invoked == 2) def test_take_while_error_after(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(90, -1), on_next(110, -1), on_next(210, 2), on_next(260, 5), on_next(290, 13), on_next( 320, 3), on_next(350, 7), on_next(390, 4), on_next(410, 17), on_next(450, 8), on_next(500, 23), on_error(600, 'ex')) invoked = 0 def factory(): def predicate(x): nonlocal invoked invoked += 1 return is_prime(x) return xs.pipe(ops.take_while(predicate)) results = scheduler.start(factory) assert results.messages == [on_next(210, 2), on_next(260, 5), on_next( 290, 13), on_next(320, 3), on_next(350, 7), on_completed(390)] assert xs.subscriptions == [subscribe(200, 390)] assert(invoked == 6) def test_take_while_dispose_before(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(90, -1), on_next(110, -1), on_next(210, 2), on_next(260, 5), on_next(290, 13), on_next( 320, 3), on_next(350, 7), on_next(390, 4), on_next(410, 17), on_next(450, 8), on_next(500, 23), on_completed(600)) invoked = 0 def create(): def predicate(x): nonlocal invoked invoked += 1 return is_prime(x) return xs.pipe(ops.take_while(predicate)) results = scheduler.start(create, disposed=300) assert results.messages == [on_next(210, 2), on_next(260, 5), on_next(290, 13)] assert xs.subscriptions == [subscribe(200, 300)] assert(invoked == 3) def test_take_while_dispose_after(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(90, -1), on_next(110, -1), on_next(210, 2), on_next(260, 5), on_next(290, 13), on_next( 320, 3), on_next(350, 7), on_next(390, 4), on_next(410, 17), on_next(450, 8), on_next(500, 23), on_completed(600)) invoked = 0 def create(): def predicate(x): nonlocal invoked invoked += 1 return is_prime(x) return xs.pipe(ops.take_while(predicate)) results = scheduler.start(create, disposed=400) assert results.messages == [on_next(210, 2), on_next(260, 5), on_next( 290, 13), on_next(320, 3), on_next(350, 7), on_completed(390)] assert xs.subscriptions == [subscribe(200, 390)] assert(invoked == 6) def test_take_while_zero(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(90, -1), on_next(110, -1), on_next(205, 100), on_next(210, 2), on_next(260, 5), on_next( 290, 13), on_next(320, 3), on_next(350, 7), on_next(390, 4), on_next(410, 17), on_next(450, 8), on_next(500, 23), on_completed(600)) invoked = 0 def create(): def predicate(x): nonlocal invoked invoked += 1 return is_prime(x) return xs.pipe(ops.take_while(predicate)) results = scheduler.start(create, disposed=300) assert results.messages == [on_completed(205)] assert xs.subscriptions == [subscribe(200, 205)] assert (invoked == 1) def test_take_while_on_error(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(90, -1), on_next(110, -1), on_next(210, 2), on_next(260, 5), on_next(290, 13), on_next( 320, 3), on_next(350, 7), on_next(390, 4), on_next(410, 17), on_next(450, 8), on_next(500, 23), on_completed(600)) invoked = 0 def factory(): def predicate(x): nonlocal invoked invoked += 1 if invoked == 3: raise Exception(ex) return is_prime(x) return xs.pipe(ops.take_while(predicate)) results = scheduler.start(factory) assert results.messages == [on_next(210, 2), on_next(260, 5), on_error(290, ex)] assert xs.subscriptions == [subscribe(200, 290)] assert(invoked == 3) def test_take_while_index(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(90, -1), on_next(110, -1), on_next(205, 100), on_next(210, 2), on_next(260, 5), on_next( 290, 13), on_next(320, 3), on_next(350, 7), on_next(390, 4), on_next(410, 17), on_next(450, 8), on_next(500, 23), on_completed(600)) def factory(): return xs.pipe(ops.take_while_indexed(lambda x, i: i < 5)) results = scheduler.start(factory) assert results.messages == [on_next(205, 100), on_next(210, 2), on_next( 260, 5), on_next(290, 13), on_next(320, 3), on_completed(350)] assert xs.subscriptions == [subscribe(200, 350)] def test_take_while_index_inclusive_false(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(90, -1), on_next(110, -1), on_next(205, 100), on_next(210, 2), on_next(260, 5), on_next( 290, 13), on_next(320, 3), on_next(350, 7), on_next(390, 4), on_next(410, 17), on_next(450, 8), on_next(500, 23), on_completed(600)) def factory(): return xs.pipe(ops.take_while_indexed(lambda x, i: i < 5, inclusive=False)) results = scheduler.start(factory) assert results.messages == [on_next(205, 100), on_next(210, 2), on_next( 260, 5), on_next(290, 13), on_next(320, 3), on_completed(350)] assert xs.subscriptions == [subscribe(200, 350)] def test_take_while_index_inclusive_true(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(90, -1), on_next(110, -1), on_next(205, 100), on_next(210, 2), on_next(260, 5), on_next( 290, 13), on_next(320, 3), on_next(350, 7), on_next(390, 4), on_next(410, 17), on_next(450, 8), on_next(500, 23), on_completed(600)) def factory_inclusive(): return xs.pipe(ops.take_while_indexed(lambda x, i: i < 4, inclusive=True)) results_inclusive = scheduler.start(factory_inclusive) assert results_inclusive.messages == [on_next(205, 100), on_next(210, 2), on_next( 260, 5), on_next(290, 13), on_next(320, 3), on_completed(320)] assert xs.subscriptions == [subscribe(200, 320)] def test_take_while_complete_after_inclusive_true(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(90, -1), on_next(110, -1), on_next(210, 2), on_next(260, 5), on_next(290, 13), on_next( 320, 3), on_next(350, 7), on_next(390, 4), on_next(410, 17), on_next(450, 8), on_next(500, 23), on_completed(600)) invoked = 0 def factory(): def predicate(x): nonlocal invoked invoked += 1 return is_prime(x) return xs.pipe(ops.take_while(predicate, inclusive=True)) results = scheduler.start(factory) assert results.messages == [on_next(210, 2), on_next(260, 5), on_next( 290, 13), on_next(320, 3), on_next(350, 7), on_next(390, 4), on_completed(390)] assert xs.subscriptions == [subscribe(200, 390)] assert(invoked == 6) RxPY-3.2.0/tests/test_observable/test_takewithtime.py000066400000000000000000000073061404130727200230210ustar00rootroot00000000000000import unittest from rx import operators as ops from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class TestTakeWithTime(unittest.TestCase): def test_take_zero(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(210, 1), on_next(220, 2), on_completed(230)) def create(): return xs.pipe(ops.take_with_time(0)) res = scheduler.start(create) assert res.messages == [on_completed(200)] assert xs.subscriptions == [subscribe(200, 200)] def test_take_some(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(210, 1), on_next(220, 2), on_next(230, 3), on_completed(240)) def create(): return xs.pipe(ops.take_with_time(25)) res = scheduler.start(create) assert res.messages == [on_next(210, 1), on_next(220, 2), on_completed(225)] assert xs.subscriptions == [subscribe(200, 225)] def test_take_late(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(210, 1), on_next(220, 2), on_completed(230)) def create(): return xs.pipe(ops.take_with_time(50)) res = scheduler.start(create) assert res.messages == [on_next(210, 1), on_next(220, 2), on_completed(230)] assert xs.subscriptions == [subscribe(200, 230)] def test_take_Error(self): scheduler = TestScheduler() ex = 'ex' xs = scheduler.create_hot_observable(on_error(210, ex)) def create(): return xs.pipe(ops.take_with_time(50)) res = scheduler.start(create) assert res.messages == [on_error(210, ex)] assert xs.subscriptions == [subscribe(200, 210)] def test_take_never(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable() def create(): return xs.pipe(ops.take_with_time(50)) res = scheduler.start(create) assert res.messages == [on_completed(250)] assert xs.subscriptions == [subscribe(200, 250)] def test_take_twice1(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(210, 1), on_next(220, 2), on_next(230, 3), on_next(240, 4), on_next(250, 5), on_next(260, 6), on_completed(270)) def create(): return xs.pipe( ops.take_with_time(55), ops.take_with_time(35), ) res = scheduler.start(create) assert res.messages == [ on_next(210, 1), on_next(220, 2), on_next(230, 3), on_completed(235)] assert xs.subscriptions == [subscribe(200, 235)] def test_take_twice2(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(210, 1), on_next(220, 2), on_next(230, 3), on_next(240, 4), on_next(250, 5), on_next(260, 6), on_completed(270)) def create(): return xs.pipe( ops.take_with_time(35), ops.take_with_time(55), ) res = scheduler.start(create) assert res.messages == [ on_next(210, 1), on_next(220, 2), on_next(230, 3), on_completed(235)] assert xs.subscriptions == [subscribe(200, 235)] RxPY-3.2.0/tests/test_observable/test_throttlefirst.py000066400000000000000000000064761404130727200232460ustar00rootroot00000000000000import unittest from rx import operators as ops from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class RxException(Exception): pass # Helper function for raising exceptions within lambdas def _raise(ex): raise RxException(ex) class TestThrottleFirst(unittest.TestCase): def test_throttle_first_completed(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(150, 1), on_next(210, 2), on_next(250, 3), on_next(310, 4), on_next(350, 5), on_next(410, 6), on_next(450, 7), on_completed(500) ) def create(): return xs.pipe(ops.throttle_first(200)) results = scheduler.start(create=create) assert results.messages == [ on_next(210, 2), on_next(410, 6), on_completed(500)] assert xs.subscriptions == [ subscribe(200, 500)] def test_throttle_first_never(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(150, 1) ) def create(): return xs.pipe(ops.throttle_first(200)) results = scheduler.start(create=create) assert results.messages == [] assert xs.subscriptions == [ subscribe(200, 1000)] def test_throttle_first_empty(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(150, 1), on_completed(500) ) def create(): return xs.pipe(ops.throttle_first(200)) results = scheduler.start(create=create) assert results.messages == [ on_completed(500)] assert xs.subscriptions == [ subscribe(200, 500)] def test_throttle_first_error(self): error = RxException() scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(150, 1), on_next(210, 2), on_next(250, 3), on_next(310, 4), on_next(350, 5), on_error(410, error), on_next(450, 7), on_completed(500) ) def create(): return xs.pipe(ops.throttle_first(200)) results = scheduler.start(create=create) assert results.messages == [ on_next(210, 2), on_error(410, error)] assert xs.subscriptions == [ subscribe(200, 410)] def test_throttle_first_no_end(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(150, 1), on_next(210, 2), on_next(250, 3), on_next(310, 4), on_next(350, 5), on_next(410, 6), on_next(450, 7) ) def create(): return xs.pipe(ops.throttle_first(200)) results = scheduler.start(create=create) assert results.messages == [ on_next(210, 2), on_next(410, 6)] assert xs.subscriptions == [ subscribe(200, 1000)] RxPY-3.2.0/tests/test_observable/test_throw.py000066400000000000000000000023271404130727200214630ustar00rootroot00000000000000import unittest from rx import throw from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class RxException(Exception): pass # Helper function for raising exceptions within lambdas def _raise(ex): raise RxException(ex) class TestThrow(unittest.TestCase): def test_throw_exception_basic(self): scheduler = TestScheduler() ex = 'ex' def factory(): return throw(ex) results = scheduler.start(factory) assert results.messages == [on_error(200, ex)] def test_throw_disposed(self): scheduler = TestScheduler() def factory(): return throw('ex') results = scheduler.start(factory, disposed=200) assert results.messages == [] def test_throw_observer_throws(self): scheduler = TestScheduler() xs = throw('ex') xs.subscribe(lambda x: None, lambda ex: _raise('ex'), lambda: None, scheduler=scheduler) self.assertRaises(RxException, scheduler.start) RxPY-3.2.0/tests/test_observable/test_timeinterval.py000066400000000000000000000054631404130727200230270ustar00rootroot00000000000000import unittest from datetime import timedelta import rx from rx import operators as ops from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class TimeInterval(object): def __init__(self, value, interval): if isinstance(interval, timedelta): interval = int(interval.seconds) # FIXME: Must fix when tests run at fraction of seconds. self.value = value self.interval = interval def __str__(self): return "%s@%s" % (self.value, self.interval) def equals(self, other): return other.interval == self.interval and other.value == self.value class TestTimeInterval(unittest.TestCase): def test_time_interval_regular(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(150, 1), on_next(210, 2), on_next(230, 3), on_next(260, 4), on_next(300, 5), on_next(350, 6), on_completed(400) ) def create(): def mapper(x): return TimeInterval(x.value, x.interval) return xs.pipe( ops.time_interval(), ops.map(mapper), ) results = scheduler.start(create) assert results.messages == [ on_next(210, TimeInterval(2, 10)), on_next(230, TimeInterval(3, 20)), on_next(260, TimeInterval(4, 30)), on_next(300, TimeInterval(5, 40)), on_next(350, TimeInterval(6, 50)), on_completed(400)] def test_time_interval_empty(self): scheduler = TestScheduler() def create(): return rx.empty().pipe(ops.time_interval()) results = scheduler.start(create) assert results.messages == [on_completed(200)] def test_time_interval_error(self): ex = 'ex' scheduler = TestScheduler() def create(): return rx.throw(ex).pipe(ops.time_interval()) results = scheduler.start(create) assert results.messages == [on_error(200, ex)] def test_time_interval_never(self): scheduler = TestScheduler() def create(): return rx.never().pipe(ops.time_interval()) results = scheduler.start(create) assert results.messages == [] def test_time_interval_default_scheduler(self): import datetime import time xs = rx.of(1, 2).pipe( ops.time_interval(), ops.pluck_attr('interval'), ) l = [] d = xs.subscribe(l.append) time.sleep(0.1) self.assertEqual(len(l), 2) [self.assertIsInstance(el, datetime.timedelta) for el in l] RxPY-3.2.0/tests/test_observable/test_timeout.py000066400000000000000000000215751404130727200220140ustar00rootroot00000000000000import unittest from datetime import datetime import rx from rx import operators as ops from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class RxException(Exception): pass # Helper function for raising exceptions within lambdas def _raise(ex): raise RxException(ex) class TestTimeout(unittest.TestCase): def test_timeout_in_time(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_next(210, 2), on_next(230, 3), on_next(260, 4), on_next(300, 5), on_next(350, 6), on_completed(400)) def create(): return xs.pipe(ops.timeout(500, None)) results = scheduler.start(create) assert results.messages == [on_next(210, 2), on_next(230, 3), on_next(260, 4), on_next(300, 5), on_next(350, 6), on_completed(400)] def test_timeout_out_of_time(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(150, 1), on_next(210, 2), on_next(230, 3), on_next(260, 4), on_next(300, 5), on_next(350, 6), on_completed(400)) def create(): return xs.pipe(ops.timeout(205)) results = scheduler.start(create) assert results.messages == [on_next(210, 2), on_next(230, 3), on_next(260, 4), on_next(300, 5), on_next(350, 6), on_completed(400)] def test_timeout_timeout_occurs_1(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(70, 1), on_next(130, 2), on_next(310, 3), on_next(400, 4), on_completed(500)) ys = scheduler.create_cold_observable(on_next(50, -1), on_next(200, -2), on_next(310, -3), on_completed(320)) def create(): return xs.pipe(ops.timeout(100, ys)) results = scheduler.start(create) assert results.messages == [on_next(350, -1), on_next(500, -2), on_next(610, -3), on_completed(620)] assert xs.subscriptions == [subscribe(200, 300)] assert ys.subscriptions == [subscribe(300, 620)] def test_timeout_timeout_occurs_2(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(70, 1), on_next(130, 2), on_next(240, 3), on_next(310, 4), on_next(430, 5), on_completed(500)) ys = scheduler.create_cold_observable(on_next(50, -1), on_next(200, -2), on_next(310, -3), on_completed(320)) def create(): return xs.pipe(ops.timeout(100, ys)) results = scheduler.start(create) assert results.messages == [on_next(240, 3), on_next(310, 4), on_next(460, -1), on_next(610, -2), on_next(720, -3), on_completed(730)] assert xs.subscriptions == [subscribe(200, 410)] assert ys.subscriptions == [subscribe(410, 730)] def test_timeout_timeout_occurs_never(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(70, 1), on_next(130, 2), on_next(240, 3), on_next(310, 4), on_next(430, 5), on_completed(500)) ys = scheduler.create_cold_observable() def create(): return xs.pipe(ops.timeout(100, ys)) results = scheduler.start(create) assert results.messages == [on_next(240, 3), on_next(310, 4)] assert xs.subscriptions == [subscribe(200, 410)] assert ys.subscriptions == [subscribe(410, 1000)] def test_timeout_timeout_occurs_completed(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_completed(500)) ys = scheduler.create_cold_observable(on_next(100, -1)) def create(): return xs.pipe(ops.timeout(100, ys)) results = scheduler.start(create) assert results.messages == [on_next(400, -1)] assert xs.subscriptions == [subscribe(200, 300)] assert ys.subscriptions == [subscribe(300, 1000)] def test_timeout_timeout_occurs_error(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_error(500, 'ex')) ys = scheduler.create_cold_observable(on_next(100, -1)) def create(): return xs.pipe(ops.timeout(100, ys)) results = scheduler.start(create) assert results.messages == [on_next(400, -1)] assert xs.subscriptions == [subscribe(200, 300)] assert ys.subscriptions == [subscribe(300, 1000)] def test_timeout_timeout_not_occurs_completed(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_completed(250)) ys = scheduler.create_cold_observable(on_next(100, -1)) def create(): return xs.pipe(ops.timeout(100, ys)) results = scheduler.start(create) assert results.messages == [on_completed(250)] assert xs.subscriptions == [subscribe(200, 250)] assert ys.subscriptions == [] def test_timeout_timeout_not_occurs_error(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_error(250, ex)) ys = scheduler.create_cold_observable(on_next(100, -1)) def create(): return xs.pipe(ops.timeout(100, ys)) results = scheduler.start(create) assert results.messages == [on_error(250, ex)] assert xs.subscriptions == [subscribe(200, 250)] assert ys.subscriptions == [] def test_timeout_timeout_does_not_occur(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(70, 1), on_next(130, 2), on_next(240, 3), on_next(320, 4), on_next(410, 5), on_completed(500)) ys = scheduler.create_cold_observable(on_next(50, -1), on_next(200, -2), on_next(310, -3), on_completed(320)) def create(): return xs.pipe(ops.timeout(100, ys)) results = scheduler.start(create) assert results.messages == [on_next(240, 3), on_next(320, 4), on_next(410, 5), on_completed(500)] assert xs.subscriptions == [subscribe(200, 500)] assert ys.subscriptions == [] def test_timeout_datetime_offset_timeout_occurs(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(410, 1)) ys = scheduler.create_cold_observable(on_next(100, -1)) def create(): return xs.pipe(ops.timeout(datetime.utcfromtimestamp(400), ys)) results = scheduler.start(create) assert results.messages == [on_next(500, -1)] assert xs.subscriptions == [subscribe(200, 400)] assert ys.subscriptions == [subscribe(400, 1000)] def test_timeout_datetime_offset_timeout_does_not_occur_completed(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(310, 1), on_completed(390)) ys = scheduler.create_cold_observable(on_next(100, -1)) def create(): return xs.pipe(ops.timeout(datetime.utcfromtimestamp(400), ys)) results = scheduler.start(create) assert results.messages == [on_next(310, 1), on_completed(390)] assert xs.subscriptions == [subscribe(200, 390)] assert ys.subscriptions == [] def test_timeout_datetime_offset_timeout_does_not_occur_error(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(310, 1), on_error(390, ex)) ys = scheduler.create_cold_observable(on_next(100, -1)) def create(): return xs.pipe(ops.timeout(datetime.utcfromtimestamp(400), ys)) results = scheduler.start(create) assert results.messages == [on_next(310, 1), on_error(390, ex)] assert xs.subscriptions == [subscribe(200, 390)] assert ys.subscriptions == [] def test_timeout_datetime_offset_timeout_occur_2(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(310, 1), on_next(350, 2), on_next(420, 3), on_completed(450)) ys = scheduler.create_cold_observable(on_next(100, -1)) def create(): return xs.pipe(ops.timeout(datetime.utcfromtimestamp(400), ys)) results = scheduler.start(create) assert results.messages == [on_next(310, 1), on_next(350, 2), on_next(500, -1)] assert xs.subscriptions == [subscribe(200, 400)] assert ys.subscriptions == [subscribe(400, 1000)] def test_timeout_datetime_offset_timeout_occur_3(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(310, 1), on_next(350, 2), on_next(420, 3), on_completed(450)) ys = scheduler.create_cold_observable() def create(): return xs.pipe(ops.timeout(datetime.utcfromtimestamp(400), ys)) results = scheduler.start(create) assert results.messages == [on_next(310, 1), on_next(350, 2)] assert xs.subscriptions == [subscribe(200, 400)] assert ys.subscriptions == [subscribe(400, 1000)] RxPY-3.2.0/tests/test_observable/test_timeoutwithmapper.py000066400000000000000000000160631404130727200241110ustar00rootroot00000000000000import unittest import rx from rx import operators as ops from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class RxException(Exception): pass # Helper function for raising exceptions within lambdas def _raise(ex): raise RxException(ex) class TestTimeoutWithSelector(unittest.TestCase): def test_timeout_duration_simple_never(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(310, 1), on_next(350, 2), on_next(420, 3), on_completed(450) ) ys = scheduler.create_cold_observable() def create(): return xs.pipe(ops.timeout_with_mapper(ys, lambda _: ys)) results = scheduler.start(create) assert results.messages == [ on_next(310, 1), on_next(350, 2), on_next(420, 3), on_completed(450)] assert xs.subscriptions == [ subscribe(200, 450)] assert ys.subscriptions == [ subscribe(200, 310), subscribe(310, 350), subscribe(350, 420), subscribe(420, 450)] def test_timeout_duration_simple_timeoutfirst(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(310, 1), on_next(350, 2), on_next(420, 3), on_completed(450) ) ys = scheduler.create_cold_observable( on_next(100, 'boo!') ) zs = scheduler.create_cold_observable() def create(): return xs.pipe(ops.timeout_with_mapper(ys, lambda _: zs)) results = scheduler.start(create) self.assertEqual(1, len(results.messages)) assert(results.messages[0].time == 300 and results.messages[0].value.exception) assert xs.subscriptions == [subscribe(200, 300)] assert ys.subscriptions == [subscribe(200, 300)] assert zs.subscriptions == [] def test_timeout_duration_simple_timeout_later(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(310, 1), on_next(350, 2), on_next(420, 3), on_completed(450)) ys = scheduler.create_cold_observable() zs = scheduler.create_cold_observable(on_next(50, 'boo!')) def create(): return xs.pipe(ops.timeout_with_mapper(ys, lambda _: zs)) results = scheduler.start(create) self.assertEqual(3, len(results.messages)) assert(on_next(310, 1).equals(results.messages[0])) assert(on_next(350, 2).equals(results.messages[1])) assert(results.messages[2].time == 400 and results.messages[2].value.exception) assert xs.subscriptions == [subscribe(200, 400)] assert ys.subscriptions == [subscribe(200, 310)] assert zs.subscriptions == [subscribe(310, 350), subscribe(350, 400)] def test_timeout_duration_simple_timeout_by_completion(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(310, 1), on_next(350, 2), on_next(420, 3), on_completed(450)) ys = scheduler.create_cold_observable() zs = scheduler.create_cold_observable(on_completed(50)) def create(): return xs.pipe(ops.timeout_with_mapper(ys, lambda _: zs)) results = scheduler.start(create) self.assertEqual(3, len(results.messages)) assert(on_next(310, 1).equals(results.messages[0])) assert(on_next(350, 2).equals(results.messages[1])) assert(results.messages[2].time == 400 and results.messages[2].value.exception) assert xs.subscriptions == [subscribe(200, 400)] assert ys.subscriptions == [subscribe(200, 310)] assert zs.subscriptions == [subscribe(310, 350), subscribe(350, 400)] def test_timeout_duration_simple_timeout_by_completion2(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(310, 1), on_next(350, 2), on_next(420, 3), on_completed(450)) ys = scheduler.create_cold_observable() zs = scheduler.create_cold_observable() def create(): def mapper(x): if x < 3: return zs else: raise Exception(ex) return xs.pipe(ops.timeout_with_mapper(ys, mapper)) results = scheduler.start(create) assert results.messages == [ on_next(310, 1), on_next(350, 2), on_next(420, 3), on_error(420, ex)] assert xs.subscriptions == [subscribe(200, 420)] assert ys.subscriptions == [subscribe(200, 310)] assert zs.subscriptions == [subscribe(310, 350), subscribe(350, 420)] def test_timeout_duration_simple_inner_throws(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(310, 1), on_next(350, 2), on_next(420, 3), on_completed(450)) ys = scheduler.create_cold_observable() zs = scheduler.create_cold_observable(on_error(50, ex)) def create(): return xs.pipe(ops.timeout_with_mapper(ys, lambda _: zs)) results = scheduler.start(create) assert results.messages == [on_next(310, 1), on_next(350, 2), on_error(400, ex)] assert xs.subscriptions == [subscribe(200, 400)] assert ys.subscriptions == [subscribe(200, 310)] assert zs.subscriptions == [subscribe(310, 350), subscribe(350, 400)] def test_timeout_duration_simple_first_throws(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(310, 1), on_next(350, 2), on_next(420, 3), on_completed(450)) ys = scheduler.create_cold_observable(on_error(50, ex)) zs = scheduler.create_cold_observable() def create(): return xs.pipe(ops.timeout_with_mapper(ys, lambda _: zs)) results = scheduler.start(create) assert results.messages == [on_error(250, ex)] assert xs.subscriptions == [subscribe(200, 250)] assert ys.subscriptions == [subscribe(200, 250)] assert zs.subscriptions == [] def test_timeout_duration_simple_source_throws(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(310, 1), on_next(350, 2), on_next(420, 3), on_error(450, ex)) ys = scheduler.create_cold_observable() zs = scheduler.create_cold_observable() def create(): return xs.pipe(ops.timeout_with_mapper(ys, lambda _: zs)) results = scheduler.start(create) assert results.messages == [on_next(310, 1), on_next(350, 2), on_next(420, 3), on_error(450, ex)] assert xs.subscriptions == [subscribe(200, 450)] assert ys.subscriptions == [subscribe(200, 310)] assert zs.subscriptions == [subscribe(310, 350), subscribe(350, 420), subscribe(420, 450)] RxPY-3.2.0/tests/test_observable/test_timer.py000066400000000000000000000071341404130727200214410ustar00rootroot00000000000000import unittest import rx from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class RxException(Exception): pass # Helper function for raising exceptions within lambdas def _raise(ex): raise RxException(ex) class TestTimer(unittest.TestCase): def test_oneshot_timer_date_basic(self): scheduler = TestScheduler() date = scheduler.to_datetime(250.0) def create(): return rx.timer(duetime=date) results = scheduler.start(create) assert results.messages == [on_next(250.0, 0), on_completed(250.0)] def test_oneshot_timer_date_passed(self): scheduler = TestScheduler() date = scheduler.to_datetime(90.0) def create(): return rx.timer(date) results = scheduler.start(create) assert results.messages == [on_next(200, 0), on_completed(200)] def test_oneshot_timer_date_disposed(self): scheduler = TestScheduler() date = scheduler.to_datetime(1010.0) def create(): return rx.timer(date) results = scheduler.start(create) assert results.messages == [] def test_oneshot_timer_date_observer_throws(self): scheduler = TestScheduler() date = scheduler.to_datetime(250.0) xs = rx.timer(date) xs.subscribe(lambda x: _raise("ex"), scheduler=scheduler) self.assertRaises(RxException, scheduler.start) def test_oneshot_timer_timespan_basic(self): scheduler = TestScheduler() def create(): return rx.timer(duetime=300) results = scheduler.start(create) assert results.messages == [on_next(500, 0), on_completed(500)] def test_oneshot_timer_timespan_zero(self): scheduler = TestScheduler() def create(): return rx.timer(0) results = scheduler.start(create) assert results.messages == [on_next(200, 0), on_completed(200)] def test_oneshot_timer_timespan_negative(self): scheduler = TestScheduler() def create(): return rx.timer(-1) results = scheduler.start(create) assert results.messages == [on_next(200, 0), on_completed(200)] def test_oneshot_timer_timespan_disposed(self): scheduler = TestScheduler() def create(): return rx.timer(1000) results = scheduler.start(create) assert results.messages == [] def test_oneshot_timer_timespan_observer_throws(self): scheduler1 = TestScheduler() xs = rx.timer(11) xs.subscribe(lambda x: _raise("ex"), scheduler=scheduler1) self.assertRaises(RxException, scheduler1.start) scheduler2 = TestScheduler() ys = rx.timer(1, period=None) ys.subscribe(on_completed=lambda: _raise("ex"), scheduler=scheduler2) self.assertRaises(RxException, scheduler2.start) def test_periodic_timer_basic(self): scheduler = TestScheduler() def create(): return rx.timer(duetime=300, period=400) results = scheduler.start(create) assert results.messages == [on_next(500, 0), on_next(900, 1)] def test_periodic_timer_equal_time_and_period(self): scheduler = TestScheduler() def create(): return rx.timer(duetime=300, period=300) results = scheduler.start(create) assert results.messages == [on_next(500, 0), on_next(800, 1)] RxPY-3.2.0/tests/test_observable/test_timestamp.py000066400000000000000000000047221404130727200223240ustar00rootroot00000000000000import unittest from datetime import datetime import rx from rx import operators as ops from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class Timestamp(object): def __init__(self, value, timestamp): if isinstance(timestamp, datetime): timestamp = timestamp - datetime.utcfromtimestamp(0) timestamp = int(timestamp.seconds) # FIXME: Must fix when tests run at fraction of seconds. self.value = value self.timestamp = timestamp def __str__(self): return "%s@%s" % (self.value, self.timestamp) def equals(self, other): return other.timestamp == self.timestamp and other.value == self.value class TestTimeInterval(unittest.TestCase): def test_timestamp_regular(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(150, 1), on_next(210, 2), on_next(230, 3), on_next(260, 4), on_next(300, 5), on_next(350, 6), on_completed(400)) def create(): def mapper(x): return Timestamp(x.value, x.timestamp) return xs.pipe( ops.timestamp(), ops.map(mapper), ) results = scheduler.start(create) assert results.messages == [ on_next(210, Timestamp(2, 210)), on_next(230, Timestamp(3, 230)), on_next(260, Timestamp(4, 260)), on_next(300, Timestamp(5, 300)), on_next(350, Timestamp(6, 350)), on_completed(400) ] def test_timestamp_empty(self): scheduler = TestScheduler() def create(): return rx.empty().pipe(ops.timestamp()) results = scheduler.start(create) assert results.messages == [on_completed(200)] def test_timestamp_error(self): ex = 'ex' scheduler = TestScheduler() def create(): return rx.throw(ex).pipe(ops.timestamp()) results = scheduler.start(create) assert results.messages == [on_error(200, ex)] def test_timestamp_never(self): scheduler = TestScheduler() def create(): return rx.never().pipe(ops.timestamp()) results = scheduler.start(create) assert results.messages == [] RxPY-3.2.0/tests/test_observable/test_toasync.py000066400000000000000000000101401404130727200217700ustar00rootroot00000000000000import unittest import rx from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class TestToAsync(unittest.TestCase): def test_to_async_context(self): class Context: def __init__(self): self.value = 42 def func(self, x): return self.value + x scheduler = TestScheduler() def create(): context = Context() return rx.to_async(context.func, scheduler)(42) res = scheduler.start(create) assert res.messages == [ on_next(200, 84), on_completed(200)] def test_to_async0(self): scheduler = TestScheduler() def create(): def func(): return 0 return rx.to_async(func, scheduler)() res = scheduler.start(create) assert res.messages == [ on_next(200, 0), on_completed(200)] def test_to_async1(self): scheduler = TestScheduler() def create(): def func(x): return x return rx.to_async(func, scheduler)(1) res = scheduler.start(create) assert res.messages == [ on_next(200, 1), on_completed(200)] def test_to_async2(self): scheduler = TestScheduler() def create(): def func(x, y): return x + y return rx.to_async(func, scheduler)(1, 2) res = scheduler.start(create) assert res.messages == [ on_next(200, 3), on_completed(200)] def test_to_async3(self): scheduler = TestScheduler() def create(): def func(x, y, z): return x + y + z return rx.to_async(func, scheduler)(1, 2, 3) res = scheduler.start(create) assert res.messages == [ on_next(200, 6), on_completed(200)] def test_to_async4(self): scheduler = TestScheduler() def create(): def func(a, b, c, d): return a + b + c + d return rx.to_async(func, scheduler)(1, 2, 3, 4) res = scheduler.start(create) assert res.messages == [ on_next(200, 10), on_completed(200)] def test_to_async_error0(self): ex = Exception() scheduler = TestScheduler() def create(): def func(): raise ex return rx.to_async(func, scheduler)() res = scheduler.start(create) assert res.messages == [ on_error(200, ex)] def test_to_async_error1(self): ex = Exception() scheduler = TestScheduler() def create(): def func(a): raise ex return rx.to_async(func, scheduler)(1) res = scheduler.start(create) assert res.messages == [ on_error(200, ex)] def test_to_async_error2(self): ex = Exception() scheduler = TestScheduler() def create(): def func(a, b): raise ex return rx.to_async(func, scheduler)(1, 2) res = scheduler.start(create) assert res.messages == [ on_error(200, ex)] def test_to_async_error3(self): ex = Exception() scheduler = TestScheduler() def create(): def func(a, b, c): raise ex return rx.to_async(func, scheduler)(1, 2, 3) res = scheduler.start(create) assert res.messages == [ on_error(200, ex)] def test_to_async_error4(self): ex = Exception() scheduler = TestScheduler() def create(): def func(a, b, c, d): raise ex return rx.to_async(func, scheduler)(1, 2, 3, 4) res = scheduler.start(create) assert res.messages == [ on_error(200, ex)] RxPY-3.2.0/tests/test_observable/test_todict.py000066400000000000000000000070561404130727200216120ustar00rootroot00000000000000import unittest import rx from rx import operators as ops from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class TestToDict(unittest.TestCase): def test_to_dict_completed(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(110, 1), on_next(220, 2), on_next(330, 3), on_next(440, 4), on_next(550, 5), on_completed(660) ) def create(): return xs.pipe(ops.to_dict(lambda x: x * 2, lambda x: x * 4)) res = scheduler.start(create) assert res.messages == [ on_next(660, {4: 8, 6: 12, 8: 16, 10: 20}), on_completed(660)] assert xs.subscriptions == [ subscribe(200, 660)] def test_to_dict_error(self): scheduler = TestScheduler() ex = Exception() xs = scheduler.create_hot_observable( on_next(110, 1), on_next(220, 2), on_next(330, 3), on_next(440, 4), on_next(550, 5), on_error(660, ex) ) def create(): return xs.pipe(ops.to_dict(lambda x: x * 2, lambda x: x * 4)) res = scheduler.start(create) assert res.messages == [ on_error(660, ex)] assert xs.subscriptions == [ subscribe(200, 660)] def test_to_dict_keymapperthrows(self): scheduler = TestScheduler() ex = Exception() xs = scheduler.create_hot_observable( on_next(110, 1), on_next(220, 2), on_next(330, 3), on_next(440, 4), on_next(550, 5), on_completed(600) ) def create(): def key_mapper(x): if x < 4: return x * 2 else: raise ex return xs.pipe(ops.to_dict(key_mapper, lambda x: x * 4)) res = scheduler.start(create) assert res.messages == [ on_error(440, ex)] assert xs.subscriptions == [ subscribe(200, 440)] def test_to_dict_elementmapperthrows(self): scheduler = TestScheduler() ex = Exception() xs = scheduler.create_hot_observable( on_next(110, 1), on_next(220, 2), on_next(330, 3), on_next(440, 4), on_next(550, 5), on_completed(600) ) def value_mapper(x): if x < 4: return x * 4 else: raise ex def create(): return xs.pipe(ops.to_dict(lambda x: x * 2 , value_mapper)) res = scheduler.start(create) assert res.messages == [ on_error(440, ex)] assert xs.subscriptions == [ subscribe(200, 440)] def test_to_dict_disposed(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(110, 1), on_next(220, 2), on_next(330, 3), on_next(440, 4), on_next(550, 5) ) def create(): return xs.pipe(ops.to_dict(lambda x: x * 2, lambda x: x * 4)) res = scheduler.start(create) assert res.messages == [] assert xs.subscriptions == [ subscribe(200, 1000)] RxPY-3.2.0/tests/test_observable/test_tofuture.py000066400000000000000000000061621404130727200221760ustar00rootroot00000000000000 import unittest import asyncio import rx import rx.operators as ops from rx.internal.exceptions import SequenceContainsNoElementsError from rx.testing import ReactiveTest from rx.subject import Subject on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class TestToFuture(unittest.TestCase): def test_await_success(self): loop = asyncio.get_event_loop() result = None async def go(): nonlocal result source = rx.return_value(42) result = await source loop.run_until_complete(go()) assert result == 42 def test_await_success_on_sequence(self): loop = asyncio.get_event_loop() result = None async def go(): nonlocal result source = rx.from_([40, 41, 42]) result = await source loop.run_until_complete(go()) assert result == 42 def test_await_error(self): loop = asyncio.get_event_loop() error = Exception("error") result = None async def go(): nonlocal result source = rx.throw(error) try: result = await source except Exception as ex: result = ex loop.run_until_complete(go()) assert result == error def test_await_empty_observable(self): loop = asyncio.get_event_loop() result = None async def go(): nonlocal result source = rx.empty() result = await source self.assertRaises(SequenceContainsNoElementsError, loop.run_until_complete, go()) def test_await_with_delay(self): loop = asyncio.get_event_loop() result = None async def go(): nonlocal result source = rx.return_value(42).pipe(ops.delay(0.1)) result = await source loop.run_until_complete(go()) assert result == 42 def test_cancel(self): loop = asyncio.get_event_loop() async def go(): source = rx.return_value(42) fut = next(source.__await__()) # This used to raise an InvalidStateError before we got # support for cancellation. fut.cancel() await fut self.assertRaises(asyncio.CancelledError, loop.run_until_complete, go()) def test_dispose_on_cancel(self): loop = asyncio.get_event_loop() sub = Subject() async def using_sub(): # Since the subject never completes, this await statement # will never be complete either. We wait forever. await rx.using(lambda: sub, lambda s: s) async def go(): await asyncio.wait_for(using_sub(), 0.1) self.assertRaises(asyncio.TimeoutError, loop.run_until_complete, go()) # When we cancel the future (due to the time-out), the future # automatically disposes the underlying subject. self.assertTrue(sub.is_disposed) RxPY-3.2.0/tests/test_observable/test_toiterable.py000066400000000000000000000041341404130727200224500ustar00rootroot00000000000000import unittest from rx import operators as ops from rx.testing import TestScheduler, ReactiveTest class TestToArray(ReactiveTest, unittest.TestCase): def test_toiterable_completed(self): scheduler = TestScheduler() msgs = [ self.on_next(110, 1), self.on_next(220, 2), self.on_next(330, 3), self.on_next(440, 4), self.on_next(550, 5), self.on_completed(660)] xs = scheduler.create_hot_observable(msgs) def create(): return xs.pipe( ops.to_iterable(), ops.map(list), ) results = scheduler.start(create=create).messages assert len(results) == 2 assert results[0].time == 660 assert results[0].value.kind == 'N' assert results[0].value.value == [2, 3, 4, 5] assert self.on_completed(660).equals(results[1]) assert xs.subscriptions == [self.subscribe(200, 660)] def test_toiterableerror(self): ex = 'ex' scheduler = TestScheduler() msgs = [ self.on_next(110, 1), self.on_next(220, 2), self.on_next(330, 3), self.on_next(440, 4), self.on_next(550, 5), self.on_error(660, ex)] xs = scheduler.create_hot_observable(msgs) def create(): return xs.pipe(ops.to_iterable()) results = scheduler.start(create=create).messages assert results == [self.on_error(660, ex)] assert xs.subscriptions == [self.subscribe(200, 660)] def test_toiterabledisposed(self): scheduler = TestScheduler() msgs = [ self.on_next(110, 1), self.on_next(220, 2), self.on_next(330, 3), self.on_next(440, 4), self.on_next(550, 5)] xs = scheduler.create_hot_observable(msgs) def create(): return xs.pipe(ops.to_iterable()) results = scheduler.start(create=create).messages assert results == [] assert xs.subscriptions == [self.subscribe(200, 1000)] RxPY-3.2.0/tests/test_observable/test_toset.py000066400000000000000000000036301404130727200214540ustar00rootroot00000000000000import unittest import rx from rx import operators as ops from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class TestToDict(unittest.TestCase): def test_to_set_completed(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(110, 1), on_next(220, 2), on_next(330, 3), on_next(440, 4), on_next(550, 5), on_completed(660) ) def create(): return xs.pipe(ops.to_set()) results = scheduler.start(create) assert results.messages == [on_next(660, set([2, 3, 4, 5])), on_completed(660)] assert xs.subscriptions == [ subscribe(200, 660)] def test_to_set_error(self): error = Exception() scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(110, 1), on_next(220, 2), on_next(330, 3), on_next(440, 4), on_next(550, 5), on_error(660, error) ) results = scheduler.start(lambda: xs.pipe(ops.to_set())) assert results.messages == [ on_error(660, error)] assert xs.subscriptions == [ subscribe(200, 660)] def test_to_set_disposed(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(110, 1), on_next(220, 2), on_next(330, 3), on_next(440, 4), on_next(550, 5) ) results = scheduler.start(lambda: xs.pipe(ops.to_set())) assert results.messages == [] assert xs.subscriptions == [subscribe(200, 1000)] RxPY-3.2.0/tests/test_observable/test_using.py000066400000000000000000000136451404130727200214520ustar00rootroot00000000000000import unittest import rx from rx.testing import TestScheduler, ReactiveTest, MockDisposable on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class RxException(Exception): pass # Helper function for raising exceptions within lambdas def _raise(ex): raise RxException(ex) class TestUsing(unittest.TestCase): def test_using_null(self): disp = [None] xs = [None] _d = [None] scheduler = TestScheduler() dispose_invoked = [0] create_invoked = [0] def create(): def create_resources(): dispose_invoked[0] += 1 disp[0] = None return disp[0] def create_observable(d): _d[0] = d create_invoked[0] += 1 xs[0] = scheduler.create_cold_observable( on_next(100, scheduler.clock), on_completed(200)) return xs[0] return rx.using(create_resources, create_observable) results = scheduler.start(create) assert (disp[0] == _d[0]) assert results.messages == [on_next(300, 200), on_completed(400)] assert (1 == create_invoked[0]) assert (1 == dispose_invoked[0]) assert xs[0].subscriptions == [subscribe(200, 400)] assert (disp[0] is None) def test_using_complete(self): disp = [None] xs = [None] _d = [None] scheduler = TestScheduler() dispose_invoked = [0] create_invoked = [0] def create(): def create_resource(): dispose_invoked[0] += 1 disp[0] = MockDisposable(scheduler) return disp[0] def create_observable(d): _d[0] = d create_invoked[0] += 1 xs[0] = scheduler.create_cold_observable( on_next(100, scheduler.clock), on_completed(200)) return xs[0] return rx.using(create_resource, create_observable) results = scheduler.start(create) assert disp == _d assert results.messages == [on_next(300, 200), on_completed(400)] assert create_invoked[0] == 1 assert dispose_invoked[0] == 1 assert xs[0].subscriptions == [subscribe(200, 400)] disp[0].disposes = [200, 400] def test_using_error(self): scheduler = TestScheduler() dispose_invoked = [0] create_invoked = [0] ex = 'ex' disp = [None] xs = [None] _d = [None] def create(): def create_resource(): dispose_invoked[0] += 1 disp[0] = MockDisposable(scheduler) return disp[0] def create_observable(d): _d[0] = d create_invoked[0] += 1 xs[0] = scheduler.create_cold_observable( on_next(100, scheduler.clock), on_error(200, ex)) return xs[0] return rx.using(create_resource, create_observable) results = scheduler.start(create) assert disp[0] == _d[0] assert results.messages == [on_next(300, 200), on_error(400, ex)] assert create_invoked[0] == 1 assert dispose_invoked[0] == 1 assert xs[0].subscriptions == [subscribe(200, 400)] assert disp[0].disposes == [200, 400] def test_using_dispose(self): disp = [None] xs = [None] _d = [None] scheduler = TestScheduler() dispose_invoked = [0] create_invoked = [0] def create(): def create_resource(): dispose_invoked[0] += 1 disp[0] = MockDisposable(scheduler) return disp[0] def create_observable(d): _d[0] = d create_invoked[0] += 1 xs[0] = scheduler.create_cold_observable( on_next(100, scheduler.clock), on_next(1000, scheduler.clock + 1)) return xs[0] return rx.using(create_resource, create_observable) results = scheduler.start(create) assert disp[0] == _d[0] assert results.messages == [on_next(300, 200)] assert 1 == create_invoked[0] assert 1 == dispose_invoked[0] assert xs[0].subscriptions == [subscribe(200, 1000)] assert disp[0].disposes == [200, 1000] def test_using_throw_resource_mapper(self): scheduler = TestScheduler() dispose_invoked = [0] create_invoked = [0] ex = 'ex' def create(): def create_resource(): dispose_invoked[0] += 1 raise _raise(ex) def create_observable(d): create_invoked[0] += 1 return rx.never() return rx.using(create_resource, create_observable) results = scheduler.start(create) assert results.messages == [on_error(200, ex)] assert(0 == create_invoked[0]) assert(1 == dispose_invoked[0]) def test_using_throw_resource_usage(self): scheduler = TestScheduler() dispose_invoked = [0] create_invoked = [0] disp = [None] ex = 'ex' def create(): def create_resource(): dispose_invoked[0] += 1 disp[0] = MockDisposable(scheduler) return disp[0] def create_observable(d): create_invoked[0] += 1 _raise(ex) return rx.using(create_resource, create_observable) results = scheduler.start(create) assert results.messages == [on_error(200, ex)] assert create_invoked[0] == 1 assert dispose_invoked[0] == 1 assert disp[0].disposes == [200, 200] RxPY-3.2.0/tests/test_observable/test_while_do.py000066400000000000000000000105751404130727200221160ustar00rootroot00000000000000import unittest import rx from rx import operators as ops from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class TestWhile(unittest.TestCase): def test_while_always_false(self): scheduler = TestScheduler() xs = scheduler.create_cold_observable(on_next(50, 1), on_next( 100, 2), on_next(150, 3), on_next(200, 4), on_completed(250)) def create(): return xs.pipe(ops.while_do(lambda _: False)) results = scheduler.start(create) assert results.messages == [on_completed(200)] assert xs.subscriptions == [] def test_while_always_true(self): scheduler = TestScheduler() xs = scheduler.create_cold_observable(on_next(50, 1), on_next( 100, 2), on_next(150, 3), on_next(200, 4), on_completed(250)) def create(): return xs.pipe(ops.while_do(lambda _: True)) results = scheduler.start(create) assert results.messages == [on_next(250, 1), on_next(300, 2), on_next(350, 3), on_next(400, 4), on_next(500, 1), on_next( 550, 2), on_next(600, 3), on_next(650, 4), on_next(750, 1), on_next(800, 2), on_next(850, 3), on_next(900, 4)] assert xs.subscriptions == [subscribe(200, 450), subscribe(450, 700), subscribe(700, 950), subscribe(950, 1000)] def test_while_always_true_on_error(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_cold_observable(on_error(50, ex)) def create(): return xs.pipe(ops.while_do(lambda _: True)) results = scheduler.start(create) assert results.messages == [on_error(250, ex)] assert xs.subscriptions == [subscribe(200, 250)] def test_while_always_true_infinite(self): scheduler = TestScheduler() xs = scheduler.create_cold_observable(on_next(50, 1)) def create(): return xs.pipe(ops.while_do(lambda _: True)) results = scheduler.start(create) assert results.messages == [on_next(250, 1)] assert xs.subscriptions == [subscribe(200, 1000)] def test_dowhile_always_true_infinite_with_create(self): scheduler = TestScheduler() n = [0] def create(): def predicate(x): n[0] += 1 return n[0] < 100 def subscribe(o, scheduler=None): o.on_next(1) o.on_completed() return lambda: None return rx.create(subscribe).pipe(ops.while_do(predicate)) results = scheduler.start(create=create) assert results.messages == [on_next(200, 1) for _ in range(99)] + [on_completed(200)] def test_while_sometimes_true(self): scheduler = TestScheduler() xs = scheduler.create_cold_observable(on_next(50, 1), on_next( 100, 2), on_next(150, 3), on_next(200, 4), on_completed(250)) n = [0] def create(): def predicate(x): n[0] += 1 return n[0] < 3 return xs.pipe(ops.while_do(predicate)) results = scheduler.start(create) assert results.messages == [on_next(250, 1), on_next(300, 2), on_next(350, 3), on_next( 400, 4), on_next(500, 1), on_next(550, 2), on_next(600, 3), on_next(650, 4), on_completed(700)] assert xs.subscriptions == [subscribe(200, 450), subscribe(450, 700)] def test_while_sometimes_throws(self): scheduler = TestScheduler() xs = scheduler.create_cold_observable(on_next(50, 1), on_next( 100, 2), on_next(150, 3), on_next(200, 4), on_completed(250)) n = [0] ex = 'ex' def create(): def predicate(x): n[0] += 1 if n[0] < 3: return True else: raise Exception(ex) return xs.pipe(ops.while_do(predicate)) results = scheduler.start(create) assert results.messages == [on_next(250, 1), on_next(300, 2), on_next(350, 3), on_next( 400, 4), on_next(500, 1), on_next(550, 2), on_next(600, 3), on_next(650, 4), on_error(700, ex)] assert xs.subscriptions == [subscribe(200, 450), subscribe(450, 700)] RxPY-3.2.0/tests/test_observable/test_window.py000066400000000000000000000420421404130727200216250ustar00rootroot00000000000000import unittest import rx from rx import operators as ops from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class TestWindow(unittest.TestCase): def test_window_when_basic(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(90, 1), on_next(180, 2), on_next(250, 3), on_next(260, 4), on_next(310, 5), on_next(340, 6), on_next(410, 7), on_next(420, 8), on_next(470, 9), on_next(550, 10), on_completed(590)) window = [1] def create(): def closing(): curr = window[0] window[0] += 1 return rx.timer(curr * 100) def mapper(w, i): return w.pipe(ops.map(lambda x: str(i) + ' ' + str(x))) return xs.pipe( ops.window_when(closing), ops.map_indexed(mapper), ops.merge_all(), ) results = scheduler.start(create=create) assert results.messages == [ on_next(250, "0 3"), on_next(260, "0 4"), on_next(310, "1 5"), on_next(340, "1 6"), on_next(410, "1 7"), on_next(420, "1 8"), on_next(470, "1 9"), on_next(550, "2 10"), on_completed(590)] assert xs.subscriptions == [subscribe(200, 590)] def test_window_when_dispose(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(90, 1), on_next(180, 2), on_next(250, 3), on_next(260, 4), on_next(310, 5), on_next(340, 6), on_next(410, 7), on_next(420, 8), on_next(470, 9), on_next(550, 10), on_completed(590)) window = [1] def create(): def closing(): curr = window[0] window[0] += 1 return rx.timer(curr * 100) def mapper(w, i): return w.pipe(ops.map(lambda x: str(i) + ' ' + str(x))) return xs.pipe( ops.window_when(closing), ops.map_indexed(mapper), ops.merge_all(), ) results = scheduler.start(create=create, disposed=400) assert results.messages == [ on_next(250, "0 3"), on_next(260, "0 4"), on_next(310, "1 5"), on_next(340, "1 6")] assert xs.subscriptions == [subscribe(200, 400)] def test_window_when_error(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(90, 1), on_next(180, 2), on_next(250, 3), on_next(260, 4), on_next(310, 5), on_next(340, 6), on_next(410, 7), on_next(420, 8), on_next(470, 9), on_next(550, 10), on_error(590, ex)) window = [1] def create(): def closing(): curr = window[0] window[0] += 1 return rx.timer(curr * 100) def mapper(w, i): return w.pipe(ops.map(lambda x: str(i) + ' ' + str(x))) return xs.pipe( ops.window_when(closing), ops.map_indexed(mapper), ops.merge_all(), ) results = scheduler.start(create=create) assert results.messages == [ on_next(250, "0 3"), on_next(260, "0 4"), on_next(310, "1 5"), on_next(340, "1 6"), on_next(410, "1 7"), on_next(420, "1 8"), on_next(470, "1 9"), on_next(550, "2 10"), on_error(590, ex)] assert xs.subscriptions == [subscribe(200, 590)] def test_window_when_on_error(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(90, 1), on_next(180, 2), on_next(250, 3), on_next(260, 4), on_next(310, 5), on_next(340, 6), on_next(410, 7), on_next(420, 8), on_next(470, 9), on_next(550, 10), on_completed(590)) def create(): def closing(): raise Exception(ex) def mapper(w, i): return w.pipe(ops.map(lambda x: str(i) + ' ' + str(x))) return xs.pipe( ops.window_when(closing), ops.map_indexed(mapper), ops.merge_all(), ) results = scheduler.start(create=create) assert results.messages == [on_error(200, ex)] assert xs.subscriptions == [subscribe(200, 200)] def test_window_when_window_close_error(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(90, 1), on_next(180, 2), on_next(250, 3), on_next(260, 4), on_next(310, 5), on_next(340, 6), on_next(410, 7), on_next(420, 8), on_next(470, 9), on_next(550, 10), on_completed(590)) def create(): def closing(): return rx.throw(ex) def mapper(w, i): return w.pipe(ops.map(lambda x: str(i) + ' ' + str(x))) return xs.pipe( ops.window_when(closing), ops.map_indexed(mapper), ops.merge_all(), ) results = scheduler.start(create=create) assert results.messages == [on_error(200, ex)] assert xs.subscriptions == [subscribe(200, 200)] def test_window_toggle_basic(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(90, 1), on_next(180, 2), on_next(250, 3), on_next(260, 4), on_next(310, 5), on_next(340, 6), on_next(410, 7), on_next(420, 8), on_next(470, 9), on_next(550, 10), on_completed(590)) ys = scheduler.create_hot_observable(on_next(255, 50), on_next( 330, 100), on_next(350, 50), on_next(400, 90), on_completed(900)) def create(): def closing(x): return rx.timer(x) def mapper(w, i): return w.pipe(ops.map(lambda x: str(i) + ' ' + str(x))) return xs.pipe( ops.window_toggle(ys, closing), ops.map_indexed(mapper), ops.merge_all(), ) results = scheduler.start(create=create) assert results.messages == [ on_next(260, "0 4"), on_next(340, "1 6"), on_next(410, "1 7"), on_next(410, "3 7"), on_next(420, "1 8"), on_next(420, "3 8"), on_next(470, "3 9"), on_completed(900)] assert xs.subscriptions == [subscribe(200, 590)] assert ys.subscriptions == [subscribe(200, 900)] def test_window_toggle_on_error(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(90, 1), on_next(180, 2), on_next(250, 3), on_next(260, 4), on_next(310, 5), on_next(340, 6), on_next(410, 7), on_next(420, 8), on_next(470, 9), on_next(550, 10), on_completed(590)) ys = scheduler.create_hot_observable(on_next(255, 50), on_next( 330, 100), on_next(350, 50), on_next(400, 90), on_completed(900)) def create(): def closing(x): raise Exception(ex) def mapper(w, i): return w.pipe(ops.map(lambda x: str(i) + ' ' + str(x))) return xs.pipe( ops.window_toggle(ys, closing), ops.map_indexed(mapper), ops.merge_all(), ) results = scheduler.start(create=create) assert results.messages == [on_error(255, ex)] assert xs.subscriptions == [subscribe(200, 255)] assert ys.subscriptions == [subscribe(200, 255)] def test_window_toggle_dispose(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(90, 1), on_next(180, 2), on_next(250, 3), on_next(260, 4), on_next(310, 5), on_next(340, 6), on_next(410, 7), on_next(420, 8), on_next(470, 9), on_next(550, 10), on_completed(590)) ys = scheduler.create_hot_observable(on_next(255, 50), on_next( 330, 100), on_next(350, 50), on_next(400, 90), on_completed(900)) def create(): def closing(x): return rx.timer(x) def mapper(w, i): return w.pipe(ops.map(lambda x: str(i) + ' ' + str(x))) return xs.pipe( ops.window_toggle(ys, closing), ops.map_indexed(mapper), ops.merge_all(), ) results = scheduler.start(create=create, disposed=415) assert results.messages == [ on_next(260, "0 4"), on_next(340, "1 6"), on_next(410, "1 7"), on_next(410, "3 7")] assert xs.subscriptions == [subscribe(200, 415)] assert ys.subscriptions == [subscribe(200, 415)] def test_window_toggle_data_error(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(90, 1), on_next(180, 2), on_next(250, 3), on_next(260, 4), on_next(310, 5), on_next(340, 6), on_next(410, 7), on_error(415, ex)) ys = scheduler.create_hot_observable(on_next(255, 50), on_next( 330, 100), on_next(350, 50), on_next(400, 90), on_completed(900)) def create(): def closing(x): return rx.timer(x) def mapper(w, i): return w.pipe(ops.map(lambda x: str(i) + ' ' + str(x))) return xs.pipe( ops.window_toggle(ys, closing), ops.map_indexed(mapper), ops.merge_all(), ) results = scheduler.start(create=create) assert results.messages == [ on_next(260, "0 4"), on_next(340, "1 6"), on_next(410, "1 7"), on_next(410, "3 7"), on_error(415, ex)] assert xs.subscriptions == [subscribe(200, 415)] assert ys.subscriptions == [subscribe(200, 415)] def test_window_toggle_window_error(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(90, 1), on_next(180, 2), on_next(250, 3), on_next(260, 4), on_next(310, 5), on_next(340, 6), on_next(410, 7), on_next(420, 8), on_next(470, 9), on_next(550, 10), on_completed(590)) ys = scheduler.create_hot_observable(on_next(255, 50), on_next( 330, 100), on_next(350, 50), on_next(400, 90), on_error(415, ex)) def create(): def closing(x): return rx.timer(x) def mapper(w, i): return w.pipe(ops.map(lambda x: str(i) + ' ' + str(x))) return xs.pipe( ops.window_toggle(ys, closing), ops.map_indexed(mapper), ops.merge_all(), ) results = scheduler.start(create=create) assert results.messages == [ on_next(260, "0 4"), on_next(340, "1 6"), on_next(410, "1 7"), on_next(410, "3 7"), on_error(415, ex)] assert xs.subscriptions == [subscribe(200, 415)] assert ys.subscriptions == [subscribe(200, 415)] def test_window_simple(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(90, 1), on_next(180, 2), on_next(250, 3), on_next(260, 4), on_next(310, 5), on_next(340, 6), on_next(410, 7), on_next(420, 8), on_next(470, 9), on_next(550, 10), on_completed(590) ) ys = scheduler.create_hot_observable( on_next(255, True), on_next(330, True), on_next(350, True), on_next(400, True), on_next(500, True), on_completed(900) ) def create(): def mapper(w, i): return w.pipe(ops.map(lambda x: str(i) + ' ' + str(x))) return xs.pipe( ops.window(ys), ops.map_indexed(mapper), ops.merge_all(), ) res = scheduler.start(create=create) assert res.messages == [ on_next(250, "0 3"), on_next(260, "1 4"), on_next(310, "1 5"), on_next(340, "2 6"), on_next(410, "4 7"), on_next(420, "4 8"), on_next(470, "4 9"), on_next(550, "5 10"), on_completed(590)] assert xs.subscriptions == [ subscribe(200, 590)] assert ys.subscriptions == [ subscribe(200, 590)] def test_window_close_boundaries(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(90, 1), on_next(180, 2), on_next(250, 3), on_next(260, 4), on_next(310, 5), on_next(340, 6), on_next(410, 7), on_next(420, 8), on_next(470, 9), on_next(550, 10), on_completed(590) ) ys = scheduler.create_hot_observable( on_next(255, True), on_next(330, True), on_next(350, True), on_completed(400) ) def create(): def mapper(w, i): return w.pipe(ops.map(lambda x: str(i) + ' ' + str(x))) return xs.pipe( ops.window(ys), ops.map_indexed(mapper), ops.merge_all(), ) res = scheduler.start(create=create) assert res.messages == [ on_next(250, "0 3"), on_next(260, "1 4"), on_next(310, "1 5"), on_next(340, "2 6"), on_completed(400)] assert xs.subscriptions == [ subscribe(200, 400)] assert ys.subscriptions == [ subscribe(200, 400)] def test_window_throwSource(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(90, 1), on_next(180, 2), on_next(250, 3), on_next(260, 4), on_next(310, 5), on_next(340, 6), on_next(380, 7), on_error(400, ex) ) ys = scheduler.create_hot_observable( on_next(255, True), on_next(330, True), on_next(350, True), on_completed(500) ) def create(): def mapper(w, i): return w.pipe(ops.map(lambda x: str(i) + ' ' + str(x))) return xs.pipe( ops.window(ys), ops.map_indexed(mapper), ops.merge_all(), ) res = scheduler.start(create=create) assert res.messages == [ on_next(250, "0 3"), on_next(260, "1 4"), on_next(310, "1 5"), on_next(340, "2 6"), on_next(380, "3 7"), on_error(400, ex)] assert xs.subscriptions == [ subscribe(200, 400)] assert ys.subscriptions == [ subscribe(200, 400)] def test_window_throw_boundaries(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(90, 1), on_next(180, 2), on_next(250, 3), on_next(260, 4), on_next(310, 5), on_next(340, 6), on_next(410, 7), on_next(420, 8), on_next(470, 9), on_next(550, 10), on_completed(590) ) ys = scheduler.create_hot_observable( on_next(255, True), on_next(330, True), on_next(350, True), on_error(400, ex) ) def create(): def mapper(w, i): return w.pipe(ops.map(lambda x: str(i) + ' ' + str(x))) return xs.pipe( ops.window(ys), ops.map_indexed(mapper), ops.merge_all(), ) res = scheduler.start(create=create) assert res.messages == [ on_next(250, "0 3"), on_next(260, "1 4"), on_next(310, "1 5"), on_next(340, "2 6"), on_error(400, ex)] assert xs.subscriptions == [ subscribe(200, 400)] assert ys.subscriptions == [ subscribe(200, 400)] if __name__ == '__main__': unittest.main() RxPY-3.2.0/tests/test_observable/test_windowwithcount.py000066400000000000000000000060161404130727200235730ustar00rootroot00000000000000import unittest from rx import operators as ops from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class TestWindowWithCount(unittest.TestCase): def test_window_with_count_basic(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(100, 1), on_next(210, 2), on_next(240, 3), on_next(280, 4), on_next(320, 5), on_next(350, 6), on_next(380, 7), on_next(420, 8), on_next(470, 9), on_completed(600)) def create(): def proj(w, i): return w.pipe(ops.map(lambda x: str(i) + ' ' + str(x))) return xs.pipe(ops.window_with_count(3, 2), ops.map_indexed(proj), ops.merge_all()) results = scheduler.start(create) assert results.messages == [on_next(210, "0 2"), on_next(240, "0 3"), on_next(280, "0 4"), on_next(280, "1 4"), on_next(320, "1 5"), on_next(350, "1 6"), on_next(350, "2 6"), on_next(380, "2 7"), on_next(420, "2 8"), on_next(420, "3 8"), on_next(470, "3 9"), on_completed(600)] assert xs.subscriptions == [subscribe(200, 600)] def test_window_with_count_disposed(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(100, 1), on_next(210, 2), on_next(240, 3), on_next(280, 4), on_next(320, 5), on_next(350, 6), on_next(380, 7), on_next(420, 8), on_next(470, 9), on_completed(600)) def create(): def proj(w, i): return w.pipe(ops.map(lambda x: str(i) + ' ' + str(x))) return xs.pipe(ops.window_with_count(3, 2), ops.map_indexed(proj), ops.merge_all()) results = scheduler.start(create, disposed=370) assert results.messages == [on_next(210, "0 2"), on_next(240, "0 3"), on_next(280, "0 4"), on_next(280, "1 4"), on_next(320, "1 5"), on_next(350, "1 6"), on_next(350, "2 6")] assert xs.subscriptions == [subscribe(200, 370)] def test_window_with_count_error(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(100, 1), on_next(210, 2), on_next(240, 3), on_next(280, 4), on_next(320, 5), on_next(350, 6), on_next(380, 7), on_next(420, 8), on_next(470, 9), on_error(600, ex)) def create(): def mapper(w, i): def mapping(x): return "%s %s" % (i, x) return w.pipe(ops.map(mapping)) return xs.pipe(ops.window_with_count(3, 2), ops.map_indexed(mapper), ops.merge_all()) results = scheduler.start(create) assert results.messages == [on_next(210, "0 2"), on_next(240, "0 3"), on_next(280, "0 4"), on_next(280, "1 4"), on_next(320, "1 5"), on_next(350, "1 6"), on_next(350, "2 6"), on_next(380, "2 7"), on_next(420, "2 8"), on_next(420, "3 8"), on_next(470, "3 9"), on_error(600, ex)] assert xs.subscriptions == [subscribe(200, 600)] RxPY-3.2.0/tests/test_observable/test_windowwithtime.py000066400000000000000000000151331404130727200234010ustar00rootroot00000000000000import unittest import rx from rx import operators as ops from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class TestWindowWithTime(unittest.TestCase): def test_window_time_basic(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(150, 1), on_next(210, 2), on_next(240, 3), on_next(270, 4), on_next(320, 5), on_next(360, 6), on_next(390, 7), on_next(410, 8), on_next(460, 9), on_next(470, 10), on_completed(490) ) def create(): def mapper(ys, i): def proj(y): return "%s %s" % (i, y) return ys.pipe(ops.map(proj), ops.concat(rx.return_value('%s end' % i))) return xs.pipe(ops.window_with_time(100), ops.map_indexed(mapper), ops.merge_all()) results = scheduler.start(create) assert results.messages == [ on_next(210, "0 2"), on_next(240, "0 3"), on_next(270, "0 4"), on_next(300, "0 end"), on_next(320, "1 5"), on_next(360, "1 6"), on_next(390, "1 7"), on_next(400, "1 end"), on_next(410, "2 8"), on_next(460, "2 9"), on_next(470, "2 10"), on_next(490, "2 end"), on_completed(490)] assert xs.subscriptions == [subscribe(200, 490)] def test_window_time_basic_both(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(150, 1), on_next(210, 2), on_next(240, 3), on_next(270, 4), on_next(320, 5), on_next(360, 6), on_next(390, 7), on_next(410, 8), on_next(460, 9), on_next(470, 10), on_completed(490) ) def create(): def mapper(ys, i): def proj(y): return "%s %s" % (i, y) return ys.pipe(ops.map(proj), ops.concat(rx.return_value('%s end' % i))) return xs.pipe(ops.window_with_time(100, 50), ops.map_indexed(mapper), ops.merge_all()) results = scheduler.start(create) assert results.messages == [on_next(210, "0 2"), on_next(240, "0 3"), on_next(270, "0 4"), on_next(270, "1 4"), on_next(300, "0 end"), on_next(320, "1 5"), on_next(320, "2 5"), on_next(350, "1 end"), on_next(360, "2 6"), on_next(360, "3 6"), on_next(390, "2 7"), on_next( 390, "3 7"), on_next(400, "2 end"), on_next(410, "3 8"), on_next(410, "4 8"), on_next(450, "3 end"), on_next(460, "4 9"), on_next(460, "5 9"), on_next(470, "4 10"), on_next(470, "5 10"), on_next(490, "4 end"), on_next(490, "5 end"), on_completed(490)] assert xs.subscriptions == [subscribe(200, 490)] def test_window_with_time_basic(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(100, 1), on_next(210, 2), on_next(240, 3), on_next(280, 4), on_next( 320, 5), on_next(350, 6), on_next(380, 7), on_next(420, 8), on_next(470, 9), on_completed(600)) def create(): def mapper(w, i): return w.pipe(ops.map(lambda x: "%s %s" % (i, x))) return xs.pipe(ops.window_with_time(100, 70), ops.map_indexed(mapper), ops.merge_all()) results = scheduler.start(create) assert results.messages == [on_next(210, "0 2"), on_next(240, "0 3"), on_next(280, "0 4"), on_next(280, "1 4"), on_next(320, "1 5"), on_next( 350, "1 6"), on_next(350, "2 6"), on_next(380, "2 7"), on_next(420, "2 8"), on_next(420, "3 8"), on_next(470, "3 9"), on_completed(600)] assert xs.subscriptions == [subscribe(200, 600)] def test_window_with_time_error(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(100, 1), on_next(210, 2), on_next(240, 3), on_next(280, 4), on_next( 320, 5), on_next(350, 6), on_next(380, 7), on_next(420, 8), on_next(470, 9), on_error(600, ex)) def create(): def mapper(w, i): return w.pipe(ops.map(lambda x: "%s %s" % (i, x))) return xs.pipe(ops.window_with_time(100, 70), ops.map_indexed(mapper), ops.merge_all()) results = scheduler.start(create) assert results.messages == [on_next(210, "0 2"), on_next(240, "0 3"), on_next(280, "0 4"), on_next(280, "1 4"), on_next(320, "1 5"), on_next( 350, "1 6"), on_next(350, "2 6"), on_next(380, "2 7"), on_next(420, "2 8"), on_next(420, "3 8"), on_next(470, "3 9"), on_error(600, ex)] assert xs.subscriptions == [subscribe(200, 600)] def test_Window_with_time_disposed(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(100, 1), on_next(210, 2), on_next(240, 3), on_next(280, 4), on_next( 320, 5), on_next(350, 6), on_next(380, 7), on_next(420, 8), on_next(470, 9), on_completed(600)) def create(): def mapper(w, i): return w.pipe(ops.map(lambda x: "%s %s" % (i, x))) return xs.pipe(ops.window_with_time(100, 70), ops.map_indexed(mapper), ops.merge_all()) results = scheduler.start(create, disposed=370) assert results.messages == [on_next(210, "0 2"), on_next(240, "0 3"), on_next(280, "0 4"), on_next( 280, "1 4"), on_next(320, "1 5"), on_next(350, "1 6"), on_next(350, "2 6")] assert xs.subscriptions == [subscribe(200, 370)] def test_window_with_time_basic_same(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(100, 1), on_next(210, 2), on_next(240, 3), on_next(280, 4), on_next( 320, 5), on_next(350, 6), on_next(380, 7), on_next(420, 8), on_next(470, 9), on_completed(600)) def create(): def mapper(w, i): return w.pipe(ops.map(lambda x: "%s %s" % (i, x))) return xs.pipe(ops.window_with_time(100), ops.map_indexed(mapper), ops.merge_all()) results = scheduler.start(create) assert results.messages == [on_next(210, "0 2"), on_next(240, "0 3"), on_next(280, "0 4"), on_next( 320, "1 5"), on_next(350, "1 6"), on_next(380, "1 7"), on_next(420, "2 8"), on_next(470, "2 9"), on_completed(600)] assert xs.subscriptions == [subscribe(200, 600)] RxPY-3.2.0/tests/test_observable/test_windowwithtimeorcount.py000066400000000000000000000062241404130727200250140ustar00rootroot00000000000000import unittest import rx from rx import operators as ops from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class TestWindowWithTime(unittest.TestCase): def test_window_with_time_or_count_basic(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(205, 1), on_next(210, 2), on_next(240, 3), on_next(280, 4), on_next(320, 5), on_next(350, 6), on_next(370, 7), on_next(420, 8), on_next(470, 9), on_completed(600)) def create(): def projection(w, i): def inner_proj(x): return "%s %s" % (i, x) return w.pipe(ops.map(inner_proj)) return xs.pipe(ops.window_with_time_or_count(70, 3), ops.map_indexed(projection), ops.merge_all()) results = scheduler.start(create) assert results.messages == [on_next(205, "0 1"), on_next(210, "0 2"), on_next(240, "0 3"), on_next(280, "1 4"), on_next(320, "2 5"), on_next(350, "2 6"), on_next(370, "2 7"), on_next(420, "3 8"), on_next(470, "4 9"), on_completed(600)] assert xs.subscriptions == [subscribe(200, 600)] def test_window_with_time_or_count_error(self): ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(205, 1), on_next(210, 2), on_next(240, 3), on_next(280, 4), on_next(320, 5), on_next(350, 6), on_next(370, 7), on_next(420, 8), on_next(470, 9), on_error(600, ex)) def create(): def projection(w, i): def inner_proj(x): return "%s %s" % (i, x) return w.pipe(ops.map(inner_proj)) return xs.pipe(ops.window_with_time_or_count(70, 3), ops.map_indexed(projection), ops.merge_all()) results = scheduler.start(create) assert results.messages == [on_next(205, "0 1"), on_next(210, "0 2"), on_next(240, "0 3"), on_next(280, "1 4"), on_next(320, "2 5"), on_next(350, "2 6"), on_next(370, "2 7"), on_next(420, "3 8"), on_next(470, "4 9"), on_error(600, ex)] assert xs.subscriptions == [subscribe(200, 600)] def test_window_with_time_or_count_disposed(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable(on_next(205, 1), on_next(210, 2), on_next(240, 3), on_next(280, 4), on_next(320, 5), on_next(350, 6), on_next(370, 7), on_next(420, 8), on_next(470, 9), on_completed(600)) def create(): def projection(w, i): def inner_proj(x): return "%s %s" % (i, x) return w.pipe(ops.map(inner_proj)) return xs.pipe(ops.window_with_time_or_count(70, 3), ops.map_indexed(projection), ops.merge_all()) results = scheduler.start(create, disposed=370) assert results.messages == [on_next(205, "0 1"), on_next(210, "0 2"), on_next(240, "0 3"), on_next(280, "1 4"), on_next(320, "2 5"), on_next(350, "2 6"), on_next(370, "2 7")] assert xs.subscriptions == [subscribe(200, 370)] RxPY-3.2.0/tests/test_observable/test_withlatestfrom.py000077500000000000000000000373541404130727200234070ustar00rootroot00000000000000import unittest import rx from rx import operators as ops from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class RxException(Exception): pass # Helper function for raising exceptions within lambdas def _raise(ex): raise RxException(ex) class TestWithLatestFrom(unittest.TestCase): def test_with_latest_from_never_never(self): scheduler = TestScheduler() e1 = rx.never() e2 = rx.never() def create(): return e1.pipe( ops.with_latest_from(e2), ops.map(sum), ) results = scheduler.start(create) assert results.messages == [] def test_with_latest_from_never_empty(self): scheduler = TestScheduler() msgs = [on_next(150, 1), on_completed(210)] e1 = rx.never() e2 = scheduler.create_hot_observable(msgs) def create(): return e1.pipe( ops.with_latest_from(e2), ops.map(sum), ) results = scheduler.start(create) assert results.messages == [] def test_with_latest_from_empty_never(self): scheduler = TestScheduler() msgs = [on_next(150, 1), on_completed(210)] e1 = rx.never() e2 = scheduler.create_hot_observable(msgs) def create(): return e2.pipe( ops.with_latest_from(e1), ops.map(sum), ) results = scheduler.start(create) assert results.messages == [on_completed(210)] def test_with_latest_from_empty_empty(self): scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_completed(210)] msgs2 = [on_next(150, 1), on_completed(210)] e1 = scheduler.create_hot_observable(msgs1) e2 = scheduler.create_hot_observable(msgs2) def create(): return e2.pipe( ops.with_latest_from(e1), ops.map(sum), ) results = scheduler.start(create) assert results.messages == [on_completed(210)] def test_with_latest_from_empty_return(self): scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_completed(210)] msgs2 = [on_next(150, 1), on_next(215, 2), on_completed(220)] e1 = scheduler.create_hot_observable(msgs1) e2 = scheduler.create_hot_observable(msgs2) def create(): return e1.pipe( ops.with_latest_from(e2), ops.map(sum), ) results = scheduler.start(create) assert results.messages == [on_completed(210)] def test_with_latest_from_return_empty(self): scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_completed(210)] msgs2 = [on_next(150, 1), on_next(215, 2), on_completed(220)] e1 = scheduler.create_hot_observable(msgs1) e2 = scheduler.create_hot_observable(msgs2) def create(): return e2.pipe( ops.with_latest_from(e1), ops.map(sum), ) results = scheduler.start(create) assert results.messages == [on_completed(220)] def test_with_latest_from_never_return(self): scheduler = TestScheduler() msgs = [on_next(150, 1), on_next(215, 2), on_completed(220)] e1 = scheduler.create_hot_observable(msgs) e2 = rx.never() def create(): return e1.pipe( ops.with_latest_from(e2), ops.map(sum), ) results = scheduler.start(create) assert results.messages == [on_completed(220)] def test_with_latest_from_return_never(self): scheduler = TestScheduler() msgs = [on_next(150, 1), on_next(215, 2), on_completed(210)] e1 = scheduler.create_hot_observable(msgs) e2 = rx.never() def create(): return e2.pipe( ops.with_latest_from(e1), ops.map(sum), ) results = scheduler.start(create) assert results.messages == [] def test_with_latest_from_return_return(self): scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_next(215, 2), on_completed(230)] msgs2 = [on_next(150, 1), on_next(220, 3), on_completed(240)] e1 = scheduler.create_hot_observable(msgs1) e2 = scheduler.create_hot_observable(msgs2) def create(): return e1.pipe( ops.with_latest_from(e2), ops.map(sum), ) results = scheduler.start(create) assert results.messages == [on_completed(230)] def test_with_latest_from_empty_error(self): ex = 'ex' scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_completed(230)] msgs2 = [on_next(150, 1), on_error(220, ex)] e1 = scheduler.create_hot_observable(msgs1) e2 = scheduler.create_hot_observable(msgs2) def create(): return e1.pipe( ops.with_latest_from(e2), ops.map(sum), ) results = scheduler.start(create) assert results.messages == [on_error(220, ex)] def test_with_latest_from_error_empty(self): ex = 'ex' scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_completed(230)] msgs2 = [on_next(150, 1), on_error(220, ex)] e1 = scheduler.create_hot_observable(msgs1) e2 = scheduler.create_hot_observable(msgs2) def create(): return e2.pipe( ops.with_latest_from(e1), ops.map(sum), ) results = scheduler.start(create) assert results.messages == [on_error(220, ex)] def test_with_latest_from_return_on_error(self): ex = 'ex' scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_next(210, 2), on_completed(230)] msgs2 = [on_next(150, 1), on_error(220, ex)] e1 = scheduler.create_hot_observable(msgs1) e2 = scheduler.create_hot_observable(msgs2) def create(): return e1.pipe( ops.with_latest_from(e2), ops.map(sum), ) results = scheduler.start(create) assert results.messages == [on_error(220, ex)] def test_with_latest_from_throw_return(self): ex = 'ex' scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_next(210, 2), on_completed(230)] msgs2 = [on_next(150, 1), on_error(220, ex)] e1 = scheduler.create_hot_observable(msgs1) e2 = scheduler.create_hot_observable(msgs2) def create(): return e2.pipe( ops.with_latest_from(e1), ops.map(sum), ) results = scheduler.start(create) assert results.messages == [on_error(220, ex)] def test_with_latest_from_throw_on_error(self): ex1 = 'ex1' ex2 = 'ex2' scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_error(220, ex1)] msgs2 = [on_next(150, 1), on_error(230, ex2)] e1 = scheduler.create_hot_observable(msgs1) e2 = scheduler.create_hot_observable(msgs2) def create(): return e1.pipe( ops.with_latest_from(e2), ops.map(sum), ) results = scheduler.start(create) assert results.messages == [on_error(220, ex1)] def test_with_latest_from_error_on_error(self): ex1 = 'ex1' ex2 = 'ex2' scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_next(210, 2), on_error(220, ex1)] msgs2 = [on_next(150, 1), on_error(230, ex2)] e1 = scheduler.create_hot_observable(msgs1) e2 = scheduler.create_hot_observable(msgs2) def create(): return e1.pipe( ops.with_latest_from(e2), ops.map(sum), ) results = scheduler.start(create) assert results.messages == [on_error(220, ex1)] def test_with_latest_from_throw_error(self): ex1 = 'ex1' ex2 = 'ex2' scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_next(210, 2), on_error(220, ex1)] msgs2 = [on_next(150, 1), on_error(230, ex2)] e1 = scheduler.create_hot_observable(msgs1) e2 = scheduler.create_hot_observable(msgs2) def create(): return e2.pipe( ops.with_latest_from(e1), ops.map(sum), ) results = scheduler.start(create) assert results.messages == [on_error(220, ex1)] def test_with_latest_from_never_on_error(self): ex = 'ex' scheduler = TestScheduler() msgs = [on_next(150, 1), on_error(220, ex)] e1 = rx.never() e2 = scheduler.create_hot_observable(msgs) def create(): return e1.pipe( ops.with_latest_from(e2), ops.map(sum), ) results = scheduler.start(create) assert results.messages == [on_error(220, ex)] def test_with_latest_from_throw_never(self): ex = 'ex' scheduler = TestScheduler() msgs = [on_next(150, 1), on_error(220, ex)] e1 = rx.never() e2 = scheduler.create_hot_observable(msgs) def create(): return e2.pipe( ops.with_latest_from(e1), ops.map(sum), ) results = scheduler.start(create) assert results.messages == [on_error(220, ex)] def test_with_latest_from_some_on_error(self): ex = 'ex' scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_next(215, 2), on_completed(230)] msgs2 = [on_next(150, 1), on_error(220, ex)] e1 = scheduler.create_hot_observable(msgs1) e2 = scheduler.create_hot_observable(msgs2) def create(): return e1.pipe( ops.with_latest_from(e2), ops.map(sum), ) results = scheduler.start(create) assert results.messages == [on_error(220, ex)] def test_with_latest_from_throw_some(self): ex = 'ex' scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_next(215, 2), on_completed(230)] msgs2 = [on_next(150, 1), on_error(220, ex)] e1 = scheduler.create_hot_observable(msgs1) e2 = scheduler.create_hot_observable(msgs2) def create(): return e2.pipe( ops.with_latest_from(e1), ops.map(sum), ) results = scheduler.start(create) assert results.messages == [on_error(220, ex)] def test_with_latest_from_no_throw_after_complete_left(self): ex = 'ex' scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_next(215, 2), on_completed(220)] msgs2 = [on_next(150, 1), on_error(230, ex)] e1 = scheduler.create_hot_observable(msgs1) e2 = scheduler.create_hot_observable(msgs2) def create(): return e1.pipe( ops.with_latest_from(e2), ops.map(sum), ) results = scheduler.start(create) assert results.messages == [on_completed(220)] def test_with_latest_from_throw_after_complete_right(self): ex = 'ex' scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_next(215, 2), on_completed(220)] msgs2 = [on_next(150, 1), on_error(230, ex)] e1 = scheduler.create_hot_observable(msgs1) e2 = scheduler.create_hot_observable(msgs2) def create(): return e2.pipe( ops.with_latest_from(e1), ops.map(sum), ) results = scheduler.start(create) assert results.messages == [on_error(230, ex)] def test_with_latest_from_interleaved_with_tail(self): scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_next(215, 2), on_next(225, 4), on_completed(230)] msgs2 = [on_next(150, 1), on_next(220, 3), on_next(230, 5), on_next(235, 6), on_next(240, 7), on_completed(250)] e1 = scheduler.create_hot_observable(msgs1) e2 = scheduler.create_hot_observable(msgs2) def create(): return e1.pipe( ops.with_latest_from(e2), ops.map(sum), ) results = scheduler.start(create) assert results.messages == [on_next(225, 3 + 4), on_completed(230)] def test_with_latest_from_consecutive(self): scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_next(215, 2), on_next(225, 4), on_completed(230)] msgs2 = [on_next(150, 1), on_next(235, 6), on_next(240, 7), on_completed(250)] e1 = scheduler.create_hot_observable(msgs1) e2 = scheduler.create_hot_observable(msgs2) def create(): return e1.pipe( ops.with_latest_from(e2), ops.map(sum), ) results = scheduler.start(create) assert results.messages == [on_completed(230)] def test_with_latest_from_consecutive_end_with_error_left(self): ex = 'ex' scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_next(215, 2), on_next(225, 4), on_error(230, ex)] msgs2 = [on_next(150, 1), on_next(235, 6), on_next(240, 7), on_completed(250)] e1 = scheduler.create_hot_observable(msgs1) e2 = scheduler.create_hot_observable(msgs2) def create(): return e1.pipe( ops.with_latest_from(e2), ops.map(sum), ) results = scheduler.start(create) assert results.messages == [on_error(230, ex)] def test_with_latest_from_consecutive_end_with_error_right(self): ex = 'ex' scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_next(215, 2), on_next(225, 4), on_completed(230)] msgs2 = [on_next(150, 1), on_next(235, 6), on_next(240, 7), on_error(245, ex)] e1 = scheduler.create_hot_observable(msgs1) e2 = scheduler.create_hot_observable(msgs2) def create(): return e2.pipe( ops.with_latest_from(e1), ops.map(sum), ) results = scheduler.start(create) assert results.messages == [on_next(235, 4 + 6), on_next(240, 4 + 7), on_error(245, ex)] def test_with_latest_from_mapper_throws(self): ex = 'ex' scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_next(225, 2), on_completed(230)] msgs2 = [on_next(150, 1), on_next(220, 3), on_completed(240)] e1 = scheduler.create_hot_observable(msgs1) e2 = scheduler.create_hot_observable(msgs2) def create(): return e1.pipe( ops.with_latest_from(e2), ops.map(lambda xy: _raise(ex)), ) results = scheduler.start(create) assert results.messages == [on_error(225, ex)] def test_with_latest_from_repeat_last_left_value(self): scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_next(215, 2), on_next(225, 4), on_next(230, 5), on_completed(235)] msgs2 = [on_next(150, 1), on_next(220, 3), on_completed(250)] e1 = scheduler.create_hot_observable(msgs1) e2 = scheduler.create_hot_observable(msgs2) def create(): return e1.pipe( ops.with_latest_from(e2), ops.map(sum), ) results = scheduler.start(create) assert results.messages == [on_next(225, 3 + 4), on_next(230, 3 + 5), on_completed(235)] if __name__ == '__main__': unittest.main() RxPY-3.2.0/tests/test_observable/test_zip.py000066400000000000000000000356171404130727200211320ustar00rootroot00000000000000import unittest import rx from rx import operators as ops from rx.testing import TestScheduler, ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class TestZip(unittest.TestCase): def test_zip_never_never(self): scheduler = TestScheduler() o1 = rx.never() o2 = rx.never() def create(): return o1.pipe(ops.zip(o2)) results = scheduler.start(create) assert results.messages == [] def test_zip_never_empty(self): scheduler = TestScheduler() msgs = [on_next(150, 1), on_completed(210)] o1 = rx.never() o2 = scheduler.create_hot_observable(msgs) def create(): return o1.pipe(ops.zip(o2)) results = scheduler.start(create) assert results.messages == [on_completed(210)] def test_zip_empty_empty(self): scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_completed(210)] msgs2 = [on_next(150, 1), on_completed(210)] e1 = scheduler.create_hot_observable(msgs1) e2 = scheduler.create_hot_observable(msgs2) def create(): return e1.pipe( ops.zip(e2), ops.map(sum)) results = scheduler.start(create) assert results.messages == [on_completed(210)] def test_zip_empty_non_empty(self): scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_completed(210)] msgs2 = [on_next(150, 1), on_next(215, 2), on_completed(220)] e1 = scheduler.create_hot_observable(msgs1) e2 = scheduler.create_hot_observable(msgs2) def create(): return e1.pipe( ops.zip(e2), ops.map(sum)) results = scheduler.start(create) assert results.messages == [on_completed(210)] def test_zip_non_empty_empty(self): scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_completed(210)] msgs2 = [on_next(150, 1), on_next(215, 2), on_completed(220)] e1 = scheduler.create_hot_observable(msgs1) e2 = scheduler.create_hot_observable(msgs2) def create(): return e2.pipe( ops.zip(e1), ops.map(sum)) results = scheduler.start(create) assert results.messages == [on_completed(210)] def test_zip_never_non_empty(self): scheduler = TestScheduler() msgs = [on_next(150, 1), on_next(215, 2), on_completed(220)] e1 = scheduler.create_hot_observable(msgs) e2 = rx.never() def create(): return e2.pipe( ops.zip(e1), ops.map(sum)) results = scheduler.start(create) assert results.messages == [on_completed(220)] def test_zip_non_empty_never(self): scheduler = TestScheduler() msgs = [on_next(150, 1), on_next(215, 2), on_completed(220)] e1 = scheduler.create_hot_observable(msgs) e2 = rx.never() def create(): return e1.pipe( ops.zip(e2), ops.map(sum)) results = scheduler.start(create) assert results.messages == [on_completed(220)] def test_zip_non_empty_non_empty(self): scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_next(215, 2), on_completed(230)] msgs2 = [on_next(150, 1), on_next(220, 3), on_completed(240)] e1 = scheduler.create_hot_observable(msgs1) e2 = scheduler.create_hot_observable(msgs2) def create(): return e1.pipe( ops.zip(e2), ops.map(sum)) results = scheduler.start(create) assert results.messages == [on_next(220, 2 + 3), on_completed(230)] def test_zip_empty_error(self): ex = 'ex' scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_completed(230)] msgs2 = [on_next(150, 1), on_error(220, ex)] e1 = scheduler.create_hot_observable(msgs1) e2 = scheduler.create_hot_observable(msgs2) def create(): return e1.pipe( ops.zip(e2), ops.map(sum)) results = scheduler.start(create) assert results.messages == [on_error(220, ex)] def test_zip_error_empty(self): ex = 'ex' scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_completed(230)] msgs2 = [on_next(150, 1), on_error(220, ex)] e1 = scheduler.create_hot_observable(msgs1) e2 = scheduler.create_hot_observable(msgs2) def create(): return e2.pipe( ops.zip(e1), ops.map(sum)) results = scheduler.start(create) assert results.messages == [on_error(220, ex)] def test_zip_never_error(self): ex = 'ex' scheduler = TestScheduler() msgs2 = [on_next(150, 1), on_error(220, ex)] e1 = rx.never() e2 = scheduler.create_hot_observable(msgs2) def create(): return e1.pipe( ops.zip(e2), ops.map(sum)) results = scheduler.start(create) assert results.messages == [on_error(220, ex)] def test_zip_error_never(self): ex = 'ex' scheduler = TestScheduler() msgs2 = [on_next(150, 1), on_error(220, ex)] e1 = rx.never() e2 = scheduler.create_hot_observable(msgs2) def create(): return e2.pipe( ops.zip(e1), ops.map(sum)) results = scheduler.start(create) assert results.messages == [on_error(220, ex)] def test_zip_error_error(self): ex1 = 'ex1' ex2 = 'ex2' scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_error(230, ex1)] msgs2 = [on_next(150, 1), on_error(220, ex2)] e1 = scheduler.create_hot_observable(msgs1) e2 = scheduler.create_hot_observable(msgs2) def create(): return e2.pipe( ops.zip(e1), ops.map(sum)) results = scheduler.start(create) assert results.messages == [on_error(220, ex2)] def test_zip_some_error(self): ex = 'ex' scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_next(215, 2), on_completed(230)] msgs2 = [on_next(150, 1), on_error(220, ex)] e1 = scheduler.create_hot_observable(msgs1) e2 = scheduler.create_hot_observable(msgs2) def create(): return e1.pipe( ops.zip(e2), ops.map(sum)) results = scheduler.start(create) assert results.messages == [on_error(220, ex)] def test_zip_error_some(self): ex = 'ex' scheduler = TestScheduler() msgs1 = [on_next(150, 1), on_next(215, 2), on_completed(230)] msgs2 = [on_next(150, 1), on_error(220, ex)] e1 = scheduler.create_hot_observable(msgs1) e2 = scheduler.create_hot_observable(msgs2) def create(): return e2.pipe( ops.zip(e1), ops.map(sum)) results = scheduler.start(create) assert results.messages == [on_error(220, ex)] def test_zip_some_data_asymmetric1(self): scheduler = TestScheduler() def msgs1_factory(): results = [] for i in range(5): results.append(on_next(205 + i * 5, i)) return results msgs1 = msgs1_factory() def msgs2_factory(): results = [] for i in range(10): results.append(on_next(205 + i * 8, i)) return results msgs2 = msgs2_factory() length = min(len(msgs1), len(msgs2)) e1 = scheduler.create_hot_observable(msgs1) e2 = scheduler.create_hot_observable(msgs2) def create(): return e1.pipe( ops.zip(e2), ops.map(sum)) results = scheduler.start(create).messages assert (length == len(results)) for i in range(length): _sum = msgs1[i].value.value + msgs2[i].value.value time = max(msgs1[i].time, msgs2[i].time) assert (results[i].value.kind == 'N' and results[i].time == time and results[i].value.value == _sum) def test_zip_some_data_asymmetric2(self): scheduler = TestScheduler() def msgs1_factory(): results = [] for i in range(10): results.append(on_next(205 + i * 5, i)) return results msgs1 = msgs1_factory() def msgs2_factory(): results = [] for i in range(5): results.append(on_next(205 + i * 8, i)) return results msgs2 = msgs2_factory() length = min(len(msgs1), len(msgs2)) e1 = scheduler.create_hot_observable(msgs1) e2 = scheduler.create_hot_observable(msgs2) def create(): return e1.pipe( ops.zip(e2), ops.map(sum)) results = scheduler.start(create).messages assert (length == len(results)) for i in range(length): _sum = msgs1[i].value.value + msgs2[i].value.value time = max(msgs1[i].time, msgs2[i].time) assert (results[i].value.kind == 'N' and results[i].time == time and results[i].value.value == _sum) def test_zip_some_data_symmetric(self): scheduler = TestScheduler() def msgs1_factory(): results = [] for i in range(10): results.append(on_next(205 + i * 5, i)) return results msgs1 = msgs1_factory() def msgs2_factory(): results = [] for i in range(10): results.append(on_next(205 + i * 8, i)) return results msgs2 = msgs2_factory() length = min(len(msgs1), len(msgs2)) e1 = scheduler.create_hot_observable(msgs1) e2 = scheduler.create_hot_observable(msgs2) def create(): return e1.pipe( ops.zip(e2), ops.map(sum)) results = scheduler.start(create).messages assert (length == len(results)) for i in range(length): _sum = msgs1[i].value.value + msgs2[i].value.value time = max(msgs1[i].time, msgs2[i].time) assert (results[i].value.kind == 'N' and results[i].time == time and results[i].value.value == _sum) def test_zip_with_iterable_never_empty(self): scheduler = TestScheduler() n1 = scheduler.create_hot_observable(on_next(150, 1)) n2 = [] def create(): return n1.pipe( ops.zip_with_iterable(n2), ops.map(sum)) results = scheduler.start(create) assert results.messages == [] assert n1.subscriptions == [subscribe(200, 1000)] def test_zip_with_iterable_empty_empty(self): scheduler = TestScheduler() n1 = scheduler.create_hot_observable(on_next(150, 1), on_completed(210)) n2 = [] def create(): return n1.pipe( ops.zip_with_iterable(n2), ops.map(sum)) results = scheduler.start(create) assert results.messages == [on_completed(210)] assert n1.subscriptions == [subscribe(200, 210)] def test_zip_with_iterable_empty_non_empty(self): scheduler = TestScheduler() n1 = scheduler.create_hot_observable(on_next(150, 1), on_completed(210)) n2 = [2] def create(): return n1.pipe( ops.zip_with_iterable(n2), ops.map(sum)) results = scheduler.start(create) assert results.messages == [on_completed(210)] assert n1.subscriptions == [subscribe(200, 210)] def test_zip_with_iterable_non_empty_empty(self): scheduler = TestScheduler() n1 = scheduler.create_hot_observable( on_next(150, 1), on_next(215, 2), on_completed(220)) n2 = [] def create(): return n1.pipe( ops.zip_with_iterable(n2), ops.map(sum)) results = scheduler.start(create) assert results.messages == [on_completed(215)] assert n1.subscriptions == [subscribe(200, 215)] def test_zip_with_iterable_never_non_empty(self): scheduler = TestScheduler() n1 = scheduler.create_hot_observable(on_next(150, 1)) n2 = [2] def create(): return n1.pipe( ops.zip_with_iterable(n2), ops.map(sum)) results = scheduler.start(create) assert results.messages == [] assert n1.subscriptions == [subscribe(200, 1000)] def test_zip_with_iterable_non_empty_non_empty(self): scheduler = TestScheduler() n1 = scheduler.create_hot_observable( on_next(150, 1), on_next(215, 2), on_completed(230)) n2 = [3] def create(): return n1.pipe( ops.zip_with_iterable(n2), ops.map(sum)) results = scheduler.start(create) assert results.messages == [on_next(215, 2 + 3), on_completed(230)] assert n1.subscriptions == [subscribe(200, 230)] def test_zip_with_iterable_error_empty(self): ex = 'ex' scheduler = TestScheduler() n1 = scheduler.create_hot_observable(on_next(150, 1), on_error(220, ex)) n2 = [] def create(): return n1.pipe( ops.zip_with_iterable(n2), ops.map(sum)) results = scheduler.start(create) assert results.messages == [on_error(220, ex)] assert n1.subscriptions == [subscribe(200, 220)] def test_zip_with_iterable_error_some(self): ex = 'ex' scheduler = TestScheduler() n1 = scheduler.create_hot_observable(on_next(150, 1), on_error(220, ex)) n2 = [2] def create(): return n1.pipe( ops.zip_with_iterable(n2), ops.map(sum)) results = scheduler.start(create) assert results.messages == [on_error(220, ex)] assert n1.subscriptions == [subscribe(200, 220)] def test_zip_with_iterable_some_data_both_sides(self): scheduler = TestScheduler() n1 = scheduler.create_hot_observable( on_next(150, 1), on_next(210, 2), on_next(220, 3), on_next(230, 4), on_next(240, 5)) n2 = [5, 4, 3, 2] def create(): return n1.pipe( ops.zip_with_iterable(n2), ops.map(sum)) results = scheduler.start(create) assert results.messages == [on_next(210, 7), on_next(220, 7), on_next(230, 7), on_next(240, 7)] assert n1.subscriptions == [subscribe(200, 1000)] RxPY-3.2.0/tests/test_observable_multiple.py000066400000000000000000000041321404130727200211700ustar00rootroot00000000000000from rx.testing import ReactiveTest on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class RxException(Exception): pass # Helper function for raising exceptions within lambdas def _raise(ex): raise RxException(ex) # test("Rx.Observable.catchException() does not lose subscription to underlying observable", 12, function () { # var subscribes = 0, # unsubscribes = 0, # tracer = Rx.Observable.create(function (observer) { ++subscribes return function () { ++unsubscribes } , # s # // Try it without catchException() # s = tracer.subscribe() # strictEqual(subscribes, 1, "1 subscribes") # strictEqual(unsubscribes, 0, "0 unsubscribes") # s.dispose() # strictEqual(subscribes, 1, "After dispose: 1 subscribes") # strictEqual(unsubscribes, 1, "After dispose: 1 unsubscribes") # // Now try again with catchException(Observable): # subscribes = unsubscribes = 0 # s = tracer.catchException(Rx.rx.never()).subscribe() # strictEqual(subscribes, 1, "catchException(Observable): 1 subscribes") # strictEqual(unsubscribes, 0, "catchException(Observable): 0 unsubscribes") # s.dispose() # strictEqual(subscribes, 1, "catchException(Observable): After dispose: 1 subscribes") # strictEqual(unsubscribes, 1, "catchException(Observable): After dispose: 1 unsubscribes") # // And now try again with catchException(function()): # subscribes = unsubscribes = 0 # s = tracer.catchException(function () { return Rx.rx.never() .subscribe() # strictEqual(subscribes, 1, "catchException(function): 1 subscribes") # strictEqual(unsubscribes, 0, "catchException(function): 0 unsubscribes") # s.dispose() # strictEqual(subscribes, 1, "catchException(function): After dispose: 1 subscribes") # strictEqual(unsubscribes, 1, "catchException(function): After dispose: 1 unsubscribes") // this one FAILS (unsubscribes is 0) # RxPY-3.2.0/tests/test_observable_time.py000066400000000000000000000177271404130727200203110ustar00rootroot00000000000000import logging from rx.testing import ReactiveTest FORMAT = '%(asctime)-15s %(threadName)s %(message)s' logging.basicConfig(filename='rx.log', format=FORMAT, level=logging.DEBUG) log = logging.getLogger('Rx') on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class RxException(Exception): pass # Helper function for raising exceptions within lambdas def _raise(ex): raise RxException(ex) # // TakeLastBuffer # def test_takeLastBuffer_with_time_Zero1(): # var res, scheduler, xs # scheduler = TestScheduler() # xs = scheduler.create_hot_observable(on_next(210, 1), on_next(220, 2), on_completed(230)) # res = scheduler.start(create) # return xs.takeLastBuffer_with_time(0, scheduler) # res.messages.assert_equal(on_next(230, function (lst) { # return lst.length === 0 # }), on_completed(230)) # assert xs.subscriptions == [subscribe(200, 230)] # def test_takeLastBuffer_with_time_Zero2(): # var res, scheduler, xs # scheduler = TestScheduler() # xs = scheduler.create_hot_observable(on_next(210, 1), on_next(220, 2), on_next(230, 3), on_completed(230)) # res = scheduler.start(create) # return xs.takeLastBuffer_with_time(0, scheduler) # res.messages.assert_equal(on_next(230, function (lst) { # return lst.length === 0 # }), on_completed(230)) # assert xs.subscriptions == [subscribe(200, 230)] # function arrayEqual(arr1, arr2) { # if (arr1.length != arr2.length) return false # for (var i = 0, len = arr1.length i < len i++) { # if (arr1[i] != arr2[i]) return false # } # return true # } # def test_takeLastBuffer_with_time_Some1(): # var res, scheduler, xs # scheduler = TestScheduler() # xs = scheduler.create_hot_observable(on_next(210, 1), on_next(220, 2), on_next(230, 3), on_completed(240)) # res = scheduler.start(create) # return xs.takeLastBuffer_with_time(25, scheduler) # res.messages.assert_equal(on_next(240, function (lst) { # return arrayEqual(lst, [2, 3]) # }), on_completed(240)) # assert xs.subscriptions == [subscribe(200, 240)] # def test_takeLastBuffer_with_time_Some2(): # var res, scheduler, xs # scheduler = TestScheduler() # xs = scheduler.create_hot_observable(on_next(210, 1), on_next(220, 2), on_next(230, 3), on_completed(300)) # res = scheduler.start(create) # return xs.takeLastBuffer_with_time(25, scheduler) # res.messages.assert_equal(on_next(300, function (lst) { # return lst.length === 0 # }), on_completed(300)) # assert xs.subscriptions == [subscribe(200, 300)] # def test_takeLastBuffer_with_time_Some3(): # var res, scheduler, xs # scheduler = TestScheduler() # xs = scheduler.create_hot_observable(on_next(210, 1), on_next(220, 2), on_next(230, 3), on_next(240, 4), on_next(250, 5), on_next(260, 6), on_next(270, 7), on_next(280, 8), on_next(290, 9), on_completed(300)) # res = scheduler.start(create) # return xs.takeLastBuffer_with_time(45, scheduler) # res.messages.assert_equal(on_next(300, function (lst) { # return arrayEqual(lst, [6, 7, 8, 9]) # }), on_completed(300)) # assert xs.subscriptions == [subscribe(200, 300)] # def test_takeLastBuffer_with_time_Some4(): # var res, scheduler, xs # scheduler = TestScheduler() # xs = scheduler.create_hot_observable(on_next(210, 1), on_next(240, 2), on_next(250, 3), on_next(280, 4), on_next(290, 5), on_next(300, 6), on_completed(350)) # res = scheduler.start(create) # return xs.takeLastBuffer_with_time(25, scheduler) # res.messages.assert_equal(on_next(350, function (lst) { # return lst.length === 0 # }), on_completed(350)) # assert xs.subscriptions == [subscribe(200, 350)] # def test_takeLastBuffer_with_time_All(): # var res, scheduler, xs # scheduler = TestScheduler() # xs = scheduler.create_hot_observable(on_next(210, 1), on_next(220, 2), on_completed(230)) # res = scheduler.start(create) # return xs.takeLastBuffer_with_time(50, scheduler) # res.messages.assert_equal(on_next(230, function (lst) { # return arrayEqual(lst, [1, 2]) # }), on_completed(230)) # assert xs.subscriptions == [subscribe(200, 230)] # def test_takeLastBuffer_with_time_Error(): # var ex, res, scheduler, xs # scheduler = TestScheduler() # ex = 'ex' # xs = scheduler.create_hot_observable(on_error(210, ex)) # res = scheduler.start(create) # return xs.takeLastBuffer_with_time(50, scheduler) # assert res.messages == [on_error(210, ex)] # assert xs.subscriptions == [subscribe(200, 210)] # def test_takeLastBuffer_with_time_Never(): # var res, scheduler, xs # scheduler = TestScheduler() # xs = scheduler.create_hot_observable() # res = scheduler.start(create) # return xs.takeLastBuffer_with_time(50, scheduler) # assert res.messages == [] # assert xs.subscriptions == [subscribe(200, 1000)] # def test_Take_Zero(): # var res, scheduler, xs # scheduler = TestScheduler() # xs = scheduler.create_hot_observable(on_next(210, 1), on_next(220, 2), on_completed(230)) # res = scheduler.start(create) # return xs.takeWithTime(0, scheduler) # assert res.messages == [on_completed(201)] # assert xs.subscriptions == [subscribe(200, 201)] # def test_Take_Some(): # var res, scheduler, xs # scheduler = TestScheduler() # xs = scheduler.create_hot_observable(on_next(210, 1), on_next(220, 2), on_next(230, 3), on_completed(240)) # res = scheduler.start(create) # return xs.takeWithTime(25, scheduler) # assert res.messages == [on_next(210, 1), on_next(220, 2), on_completed(225)] # assert xs.subscriptions == [subscribe(200, 225)] # def test_Take_Late(): # var res, scheduler, xs # scheduler = TestScheduler() # xs = scheduler.create_hot_observable(on_next(210, 1), on_next(220, 2), on_completed(230)) # res = scheduler.start(create) # return xs.takeWithTime(50, scheduler) # assert res.messages == [on_next(210, 1), on_next(220, 2), on_completed(230)] # assert xs.subscriptions == [subscribe(200, 230)] # def test_Take_Error(): # var ex, res, scheduler, xs # scheduler = TestScheduler() # ex = 'ex' # xs = scheduler.create_hot_observable(on_error(210, ex)) # res = scheduler.start(create) # return xs.takeWithTime(50, scheduler) # assert res.messages == [on_error(210, ex)] # assert xs.subscriptions == [subscribe(200, 210)] # def test_Take_Never(): # var res, scheduler, xs # scheduler = TestScheduler() # xs = scheduler.create_hot_observable() # res = scheduler.start(create) # return xs.takeWithTime(50, scheduler) # assert res.messages == [on_completed(250)] # assert xs.subscriptions == [subscribe(200, 250)] # def test_Take_Twice1(): # var res, scheduler, xs # scheduler = TestScheduler() # xs = scheduler.create_hot_observable(on_next(210, 1), on_next(220, 2), on_next(230, 3), on_next(240, 4), on_next(250, 5), on_next(260, 6), on_completed(270)) # res = scheduler.start(create) # return xs.takeWithTime(55, scheduler).takeWithTime(35, scheduler) # assert res.messages == [on_next(210, 1), on_next(220, 2), on_next(230, 3), on_completed(235)] # assert xs.subscriptions == [subscribe(200, 235)] # def test_Take_Twice2(): # var res, scheduler, xs # scheduler = TestScheduler() # xs = scheduler.create_hot_observable(on_next(210, 1), on_next(220, 2), on_next(230, 3), on_next(240, 4), on_next(250, 5), on_next(260, 6), on_completed(270)) # res = scheduler.start(create) # return xs.takeWithTime(35, scheduler).takeWithTime(55, scheduler) # assert res.messages == [on_next(210, 1), on_next(220, 2), on_next(230, 3), on_completed(235)] # assert xs.subscriptions == [subscribe(200, 235)] # // TakeLast RxPY-3.2.0/tests/test_scheduler/000077500000000000000000000000001404130727200165355ustar00rootroot00000000000000RxPY-3.2.0/tests/test_scheduler/__init__.py000066400000000000000000000000001404130727200206340ustar00rootroot00000000000000RxPY-3.2.0/tests/test_scheduler/test_catchscheduler.py000066400000000000000000000203531404130727200231320ustar00rootroot00000000000000import unittest from datetime import timedelta from rx.scheduler import CatchScheduler, VirtualTimeScheduler class MyException(Exception): pass class CatchSchedulerTestScheduler(VirtualTimeScheduler): def __init__(self, initial_clock=0.0): super().__init__(initial_clock) self.exc = None def add(self, absolute, relative): return absolute + relative def _wrap(self, action): def _action(scheduler, state=None): ret = None try: ret = action(scheduler, state) except MyException as e: self.exc = e finally: return ret return _action def schedule_absolute(self, duetime, action, state=None): action = self._wrap(action) return super().schedule_absolute(duetime, action, state=state) class TestCatchScheduler(unittest.TestCase): def test_catch_now(self): wrapped = CatchSchedulerTestScheduler() scheduler = CatchScheduler(wrapped, lambda ex: True) diff = scheduler.now - wrapped.now assert abs(diff) < timedelta(milliseconds=1) def test_catch_now_units(self): wrapped = CatchSchedulerTestScheduler() scheduler = CatchScheduler(wrapped, lambda ex: True) diff = scheduler.now wrapped.sleep(0.1) diff = scheduler.now - diff assert timedelta(milliseconds=80) < diff < timedelta(milliseconds=180) def test_catch_schedule(self): ran = False handled = False def action(scheduler, state): nonlocal ran ran = True def handler(_): nonlocal handled handled = True return True wrapped = CatchSchedulerTestScheduler() scheduler = CatchScheduler(wrapped, handler) scheduler.schedule(action) wrapped.start() assert ran is True assert handled is False assert wrapped.exc is None def test_catch_schedule_relative(self): ran = False handled = False def action(scheduler, state): nonlocal ran ran = True def handler(_): nonlocal handled handled = True return True wrapped = CatchSchedulerTestScheduler() scheduler = CatchScheduler(wrapped, handler) scheduler.schedule_relative(0.1, action) wrapped.start() assert ran is True assert handled is False assert wrapped.exc is None def test_catch_schedule_absolute(self): ran = False handled = False def action(scheduler, state): nonlocal ran ran = True def handler(_): nonlocal handled handled = True return True wrapped = CatchSchedulerTestScheduler() scheduler = CatchScheduler(wrapped, handler) scheduler.schedule_absolute(0.1, action) wrapped.start() assert ran is True assert handled is False assert wrapped.exc is None def test_catch_schedule_error_handled(self): ran = False handled = False def action(scheduler, state): nonlocal ran ran = True raise MyException() def handler(_): nonlocal handled handled = True return True wrapped = CatchSchedulerTestScheduler() scheduler = CatchScheduler(wrapped, handler) scheduler.schedule(action) wrapped.start() assert ran is True assert handled is True assert wrapped.exc is None def test_catch_schedule_error_unhandled(self): ran = False handled = False def action(scheduler, state): nonlocal ran ran = True raise MyException() def handler(_): nonlocal handled handled = True return False wrapped = CatchSchedulerTestScheduler() scheduler = CatchScheduler(wrapped, handler) scheduler.schedule(action) wrapped.start() assert ran is True assert handled is True assert isinstance(wrapped.exc, MyException) def test_catch_schedule_nested(self): ran = False handled = False def inner(scheduler, state): nonlocal ran ran = True def outer(scheduler, state): scheduler.schedule(inner) def handler(_): nonlocal handled handled = True return True wrapped = CatchSchedulerTestScheduler() scheduler = CatchScheduler(wrapped, handler) scheduler.schedule(outer) wrapped.start() assert ran is True assert handled is False assert wrapped.exc is None def test_catch_schedule_nested_error_handled(self): ran = False handled = False def inner(scheduler, state): nonlocal ran ran = True raise MyException() def outer(scheduler, state): scheduler.schedule(inner) def handler(_): nonlocal handled handled = True return True wrapped = CatchSchedulerTestScheduler() scheduler = CatchScheduler(wrapped, handler) scheduler.schedule(outer) wrapped.start() assert ran is True assert handled is True assert wrapped.exc is None def test_catch_schedule_nested_error_unhandled(self): ran = False handled = False def inner(scheduler, state): nonlocal ran ran = True raise MyException() def outer(scheduler, state): scheduler.schedule(inner) def handler(_): nonlocal handled handled = True return False wrapped = CatchSchedulerTestScheduler() scheduler = CatchScheduler(wrapped, handler) scheduler.schedule(outer) wrapped.start() assert ran is True assert handled is True assert isinstance(wrapped.exc, MyException) def test_catch_schedule_periodic(self): period = 0.05 counter = 3 handled = False def action(state): nonlocal counter if state: counter -= 1 return state - 1 if counter == 0: disp.dispose() def handler(_): nonlocal handled handled = True return True wrapped = CatchSchedulerTestScheduler() scheduler = CatchScheduler(wrapped, handler) disp = scheduler.schedule_periodic(period, action, counter) wrapped.start() assert counter == 0 assert handled is False assert wrapped.exc is None def test_catch_schedule_periodic_error_handled(self): period = 0.05 counter = 3 handled = False def action(state): nonlocal counter if state: counter -= 1 return state - 1 if counter == 0: raise MyException() def handler(_): nonlocal handled handled = True disp.dispose() return True wrapped = CatchSchedulerTestScheduler() scheduler = CatchScheduler(wrapped, handler) disp = scheduler.schedule_periodic(period, action, counter) wrapped.start() assert counter == 0 assert handled is True assert wrapped.exc is None def test_catch_schedule_periodic_error_unhandled(self): period = 0.05 counter = 3 handled = False def action(state): nonlocal counter if state: counter -= 1 return state - 1 if counter == 0: raise MyException() def handler(_): nonlocal handled handled = True disp.dispose() return False wrapped = CatchSchedulerTestScheduler() scheduler = CatchScheduler(wrapped, handler) disp = scheduler.schedule_periodic(period, action, counter) wrapped.start() assert counter == 0 assert handled is True assert isinstance(wrapped.exc, MyException) RxPY-3.2.0/tests/test_scheduler/test_currentthreadscheduler.py000066400000000000000000000157221404130727200247260ustar00rootroot00000000000000import pytest import unittest import threading from datetime import timedelta from time import sleep from rx.scheduler import CurrentThreadScheduler from rx.internal.basic import default_now class TestCurrentThreadScheduler(unittest.TestCase): def test_currentthread_singleton(self): scheduler = [ CurrentThreadScheduler(), CurrentThreadScheduler.singleton(), CurrentThreadScheduler.singleton() ] assert scheduler[0] is not scheduler[1] assert scheduler[1] is scheduler[2] gate = [threading.Semaphore(0), threading.Semaphore(0)] scheduler = [None, None] def run(idx): scheduler[idx] = CurrentThreadScheduler.singleton() gate[idx].release() for idx in (0, 1): threading.Thread(target=run, args=(idx,)).start() gate[idx].acquire() assert scheduler[0] is not None assert scheduler[1] is not None assert scheduler[0] is not scheduler[1] def test_currentthread_extend(self): class MyScheduler(CurrentThreadScheduler): pass scheduler = [ MyScheduler(), MyScheduler.singleton(), MyScheduler.singleton(), CurrentThreadScheduler.singleton(), ] assert scheduler[0] is not scheduler[1] assert scheduler[1] is scheduler[2] assert scheduler[1] is not scheduler[3] def test_currentthread_now(self): scheduler = CurrentThreadScheduler() diff = scheduler.now - default_now() assert abs(diff) < timedelta(milliseconds=5) def test_currentthread_now_units(self): scheduler = CurrentThreadScheduler() diff = scheduler.now sleep(1.1) diff = scheduler.now - diff assert timedelta(milliseconds=1000) < diff < timedelta(milliseconds=1300) def test_currentthread_schedule(self): scheduler = CurrentThreadScheduler() ran = False def action(scheduler, state=None): nonlocal ran ran = True scheduler.schedule(action) assert ran is True def test_currentthread_schedule_block(self): scheduler = CurrentThreadScheduler() ran = False def action(scheduler, state=None): nonlocal ran ran = True t = scheduler.now scheduler.schedule_relative(0.2, action) t = scheduler.now - t assert ran is True assert t >= timedelta(seconds=0.2) def test_currentthread_schedule_error(self): scheduler = CurrentThreadScheduler() class MyException(Exception): pass def action(scheduler, state=None): raise MyException() with pytest.raises(MyException): scheduler.schedule(action) def test_currentthread_schedule_nested(self): scheduler = CurrentThreadScheduler() ran = False def action(scheduler, state=None): def inner_action(scheduler, state=None): nonlocal ran ran = True return scheduler.schedule(inner_action) scheduler.schedule(action) assert ran is True def test_currentthread_schedule_nested_order(self): scheduler = CurrentThreadScheduler() tests = [] def outer(scheduler, state=None): def action1(scheduler, state=None): tests.append(1) def action2(scheduler, state=None): tests.append(2) CurrentThreadScheduler().schedule(action2) CurrentThreadScheduler().schedule(action1) def action3(scheduler, state=None): tests.append(3) CurrentThreadScheduler().schedule(action3) scheduler.ensure_trampoline(outer) assert tests == [1, 2, 3] def test_currentthread_singleton_schedule_nested_order(self): scheduler = CurrentThreadScheduler.singleton() tests = [] def outer(scheduler, state=None): def action1(scheduler, state=None): tests.append(1) def action2(scheduler, state=None): tests.append(2) scheduler.schedule(action2) scheduler.schedule(action1) def action3(scheduler, state=None): tests.append(3) scheduler.schedule(action3) scheduler.ensure_trampoline(outer) assert tests == [1, 3, 2] def test_currentthread_ensuretrampoline(self): scheduler = CurrentThreadScheduler() ran1, ran2 = False, False def outer_action(scheduer, state=None): def action1(scheduler, state=None): nonlocal ran1 ran1 = True scheduler.schedule(action1) def action2(scheduler, state=None): nonlocal ran2 ran2 = True return scheduler.schedule(action2) scheduler.ensure_trampoline(outer_action) assert ran1 is True assert ran2 is True def test_currentthread_ensuretrampoline_nested(self): scheduler = CurrentThreadScheduler() ran1, ran2 = False, False def outer_action(scheduler, state): def inner_action1(scheduler, state): nonlocal ran1 ran1 = True scheduler.schedule(inner_action1) def inner_action2(scheduler, state): nonlocal ran2 ran2 = True return scheduler.schedule(inner_action2) scheduler.ensure_trampoline(outer_action) assert ran1 is True assert ran2 is True def test_currentthread_ensuretrampoline_and_cancel(self): scheduler = CurrentThreadScheduler() ran1, ran2 = False, False def outer_action(scheduler, state): def inner_action1(scheduler, state): nonlocal ran1 ran1 = True def inner_action2(scheduler, state): nonlocal ran2 ran2 = True d = scheduler.schedule(inner_action2) d.dispose() return scheduler.schedule(inner_action1) scheduler.ensure_trampoline(outer_action) assert ran1 is True assert ran2 is False def test_currentthread_ensuretrampoline_and_canceltimed(self): scheduler = CurrentThreadScheduler() ran1, ran2 = False, False def outer_action(scheduler, state): def inner_action1(scheduler, state): nonlocal ran1 ran1 = True def inner_action2(scheduler, state): nonlocal ran2 ran2 = True t = scheduler.now + timedelta(seconds=0.5) d = scheduler.schedule_absolute(t, inner_action2) d.dispose() return scheduler.schedule(inner_action1) scheduler.ensure_trampoline(outer_action) assert ran1 is True assert ran2 is False RxPY-3.2.0/tests/test_scheduler/test_eventloop/000077500000000000000000000000001404130727200216075ustar00rootroot00000000000000RxPY-3.2.0/tests/test_scheduler/test_eventloop/__init__.py000066400000000000000000000000001404130727200237060ustar00rootroot00000000000000RxPY-3.2.0/tests/test_scheduler/test_eventloop/test_asyncioscheduler.py000066400000000000000000000044471404130727200265750ustar00rootroot00000000000000import unittest import asyncio from datetime import datetime, timedelta from rx.scheduler.eventloop import AsyncIOScheduler class TestAsyncIOScheduler(unittest.TestCase): def test_asyncio_schedule_now(self): loop = asyncio.get_event_loop() scheduler = AsyncIOScheduler(loop) diff = scheduler.now - datetime.utcfromtimestamp(loop.time()) assert abs(diff) < timedelta(milliseconds=1) def test_asyncio_schedule_now_units(self): loop = asyncio.get_event_loop() scheduler = AsyncIOScheduler(loop) diff = scheduler.now yield from asyncio.sleep(0.1, loop=loop) diff = scheduler.now - diff assert timedelta(milliseconds=80) < diff < timedelta(milliseconds=180) def test_asyncio_schedule_action(self): loop = asyncio.get_event_loop() @asyncio.coroutine def go(): scheduler = AsyncIOScheduler(loop) ran = False def action(scheduler, state): nonlocal ran ran = True scheduler.schedule(action) yield from asyncio.sleep(0.1, loop=loop) assert ran is True loop.run_until_complete(go()) def test_asyncio_schedule_action_due(self): loop = asyncio.get_event_loop() @asyncio.coroutine def go(): scheduler = AsyncIOScheduler(loop) starttime = loop.time() endtime = None def action(scheduler, state): nonlocal endtime endtime = loop.time() scheduler.schedule_relative(0.2, action) yield from asyncio.sleep(0.3, loop=loop) assert endtime is not None diff = endtime - starttime assert diff > 0.18 loop.run_until_complete(go()) def test_asyncio_schedule_action_cancel(self): loop = asyncio.get_event_loop() @asyncio.coroutine def go(): ran = False scheduler = AsyncIOScheduler(loop) def action(scheduler, state): nonlocal ran ran = True d = scheduler.schedule_relative(0.05, action) d.dispose() yield from asyncio.sleep(0.3, loop=loop) assert ran is False loop.run_until_complete(go()) RxPY-3.2.0/tests/test_scheduler/test_eventloop/test_asynciothreadsafescheduler.py000066400000000000000000000116721404130727200306220ustar00rootroot00000000000000import unittest import asyncio import threading from datetime import datetime, timedelta from rx.scheduler.eventloop import AsyncIOThreadSafeScheduler class TestAsyncIOThreadSafeScheduler(unittest.TestCase): def test_asyncio_threadsafe_schedule_now(self): loop = asyncio.get_event_loop() scheduler = AsyncIOThreadSafeScheduler(loop) diff = scheduler.now - datetime.utcfromtimestamp(loop.time()) assert abs(diff) < timedelta(milliseconds=1) def test_asyncio_threadsafe_schedule_now_units(self): loop = asyncio.get_event_loop() scheduler = AsyncIOThreadSafeScheduler(loop) diff = scheduler.now yield from asyncio.sleep(0.1, loop=loop) diff = scheduler.now - diff assert timedelta(milliseconds=80) < diff < timedelta(milliseconds=180) def test_asyncio_threadsafe_schedule_action(self): loop = asyncio.get_event_loop() @asyncio.coroutine def go(): scheduler = AsyncIOThreadSafeScheduler(loop) ran = False def action(scheduler, state): nonlocal ran ran = True def schedule(): scheduler.schedule(action) threading.Thread(target=schedule).start() yield from asyncio.sleep(0.1, loop=loop) assert ran is True loop.run_until_complete(go()) def test_asyncio_threadsafe_schedule_action_due(self): loop = asyncio.get_event_loop() @asyncio.coroutine def go(): scheduler = AsyncIOThreadSafeScheduler(loop) starttime = loop.time() endtime = None def action(scheduler, state): nonlocal endtime endtime = loop.time() def schedule(): scheduler.schedule_relative(0.2, action) threading.Thread(target=schedule).start() yield from asyncio.sleep(0.3, loop=loop) assert endtime is not None diff = endtime - starttime assert diff > 0.18 loop.run_until_complete(go()) def test_asyncio_threadsafe_schedule_action_cancel(self): loop = asyncio.get_event_loop() @asyncio.coroutine def go(): ran = False scheduler = AsyncIOThreadSafeScheduler(loop) def action(scheduler, state): nonlocal ran ran = True def schedule(): d = scheduler.schedule_relative(0.05, action) d.dispose() threading.Thread(target=schedule).start() yield from asyncio.sleep(0.3, loop=loop) assert ran is False loop.run_until_complete(go()) def cancel_same_thread_common(self, test_body): update_state = { 'ran': False, 'dispose_completed': False } def action(scheduler, state): update_state['ran'] = True # Make the actual test body run in deamon thread, so that in case of # failure it doesn't hang indefinitely. def thread_target(): loop = asyncio.new_event_loop() scheduler = AsyncIOThreadSafeScheduler(loop) test_body(scheduler, action, update_state) @asyncio.coroutine def go(): yield from asyncio.sleep(0.2, loop=loop) loop.run_until_complete(go()) thread = threading.Thread(target=thread_target) thread.daemon = True thread.start() thread.join(0.3) assert update_state['dispose_completed'] is True assert update_state['ran'] is False def test_asyncio_threadsafe_cancel_non_relative_same_thread(self): def test_body(scheduler, action, update_state): d = scheduler.schedule(action) # Test case when dispose is called on thread on which loop is not # yet running, and non-relative schedele is used. d.dispose() update_state['dispose_completed'] = True self.cancel_same_thread_common(test_body) def test_asyncio_threadsafe_schedule_action_cancel_same_thread(self): def test_body(scheduler, action, update_state): d = scheduler.schedule_relative(0.05, action) # Test case when dispose is called on thread on which loop is not # yet running, and relative schedule is used. d.dispose() update_state['dispose_completed'] = True self.cancel_same_thread_common(test_body) def test_asyncio_threadsafe_schedule_action_cancel_same_loop(self): def test_body(scheduler, action, update_state): d = scheduler.schedule_relative(0.1, action) def do_dispose(): d.dispose() update_state['dispose_completed'] = True # Test case when dispose is called in loop's callback. scheduler._loop.call_soon(do_dispose) self.cancel_same_thread_common(test_body) RxPY-3.2.0/tests/test_scheduler/test_eventloop/test_eventletscheduler.py000066400000000000000000000042411404130727200267460ustar00rootroot00000000000000import pytest import unittest from datetime import datetime, timedelta from time import sleep from rx.scheduler.eventloop import EventletScheduler eventlet = pytest.importorskip("eventlet") class TestEventletScheduler(unittest.TestCase): def test_eventlet_schedule_now(self): scheduler = EventletScheduler(eventlet) hub = eventlet.hubs.get_hub() diff = scheduler.now - datetime.utcfromtimestamp(hub.clock()) assert abs(diff) < timedelta(milliseconds=1) def test_eventlet_schedule_now_units(self): scheduler = EventletScheduler(eventlet) diff = scheduler.now sleep(0.1) diff = scheduler.now - diff assert timedelta(milliseconds=80) < diff < timedelta(milliseconds=180) def test_eventlet_schedule_action(self): scheduler = EventletScheduler(eventlet) ran = False def action(scheduler, state): nonlocal ran ran = True scheduler.schedule(action) eventlet.sleep(0.1) assert ran is True def test_eventlet_schedule_action_due(self): scheduler = EventletScheduler(eventlet) starttime = datetime.now() endtime = None def action(scheduler, state): nonlocal endtime endtime = datetime.now() scheduler.schedule_relative(0.2, action) eventlet.sleep(0.3) assert endtime is not None diff = endtime - starttime assert diff > timedelta(seconds=0.18) def test_eventlet_schedule_action_cancel(self): scheduler = EventletScheduler(eventlet) ran = False def action(scheduler, state): nonlocal ran ran = True d = scheduler.schedule_relative(1.0, action) d.dispose() eventlet.sleep(0.01) assert ran is False def test_eventlet_schedule_action_periodic(self): scheduler = EventletScheduler(eventlet) period = 0.05 counter = 3 def action(state): nonlocal counter if counter: counter -= 1 scheduler.schedule_periodic(period, action) eventlet.sleep(0.3) assert counter == 0 RxPY-3.2.0/tests/test_scheduler/test_eventloop/test_geventscheduler.py000066400000000000000000000033651404130727200264160ustar00rootroot00000000000000import pytest import unittest from datetime import datetime, timedelta from rx.scheduler.eventloop import GEventScheduler gevent = pytest.importorskip("gevent") class TestGEventScheduler(unittest.TestCase): def test_gevent_schedule_now(self): scheduler = GEventScheduler(gevent) hub = gevent.get_hub() diff = scheduler.now - datetime.utcfromtimestamp(hub.loop.now()) assert abs(diff) < timedelta(milliseconds=1) def test_gevent_schedule_now_units(self): scheduler = GEventScheduler(gevent) diff = scheduler.now gevent.sleep(0.1) diff = scheduler.now - diff assert timedelta(milliseconds=80) < diff < timedelta(milliseconds=180) def test_gevent_schedule_action(self): scheduler = GEventScheduler(gevent) ran = False def action(scheduler, state): nonlocal ran ran = True scheduler.schedule(action) gevent.sleep(0.1) assert ran is True def test_gevent_schedule_action_due(self): scheduler = GEventScheduler(gevent) starttime = datetime.now() endtime = None def action(scheduler, state): nonlocal endtime endtime = datetime.now() scheduler.schedule_relative(0.2, action) gevent.sleep(0.3) assert endtime is not None diff = endtime - starttime assert diff > timedelta(seconds=0.18) def test_gevent_schedule_action_cancel(self): scheduler = GEventScheduler(gevent) ran = False def action(scheduler, state): nonlocal ran ran = True d = scheduler.schedule_relative(0.01, action) d.dispose() gevent.sleep(0.1) assert ran is False RxPY-3.2.0/tests/test_scheduler/test_eventloop/test_ioloopscheduler.py000066400000000000000000000042341404130727200264230ustar00rootroot00000000000000import pytest import unittest from datetime import datetime, timedelta from time import sleep tornado = pytest.importorskip("tornado") from tornado import ioloop from rx.scheduler.eventloop import IOLoopScheduler class TestIOLoopScheduler(unittest.TestCase): def test_ioloop_schedule_now(self): loop = ioloop.IOLoop.instance() scheduler = IOLoopScheduler(loop) diff = scheduler.now - datetime.utcfromtimestamp(loop.time()) assert abs(diff) < timedelta(milliseconds=1) def test_ioloop_schedule_now_units(self): loop = ioloop.IOLoop.instance() scheduler = IOLoopScheduler(loop) diff = scheduler.now sleep(0.1) diff = scheduler.now - diff assert timedelta(milliseconds=80) < diff < timedelta(milliseconds=180) def test_ioloop_schedule_action(self): loop = ioloop.IOLoop.instance() scheduler = IOLoopScheduler(loop) ran = False def action(scheduler, state): nonlocal ran ran = True scheduler.schedule(action) def done(): assert ran is True loop.stop() loop.call_later(0.1, done) loop.start() def test_ioloop_schedule_action_due(self): loop = ioloop.IOLoop.instance() scheduler = IOLoopScheduler(loop) starttime = loop.time() endtime = None def action(scheduler, state): nonlocal endtime endtime = loop.time() scheduler.schedule_relative(0.2, action) def done(): assert endtime is not None diff = endtime - starttime assert diff > 0.18 loop.stop() loop.call_later(0.3, done) loop.start() def test_ioloop_schedule_action_cancel(self): loop = ioloop.IOLoop.instance() ran = False scheduler = IOLoopScheduler(loop) def action(scheduler, state): nonlocal ran ran = True d = scheduler.schedule_relative(0.01, action) d.dispose() def done(): assert ran is False loop.stop() loop.call_later(0.1, done) loop.start() RxPY-3.2.0/tests/test_scheduler/test_eventloop/test_twistedscheduler.py000066400000000000000000000043351404130727200266070ustar00rootroot00000000000000import pytest from datetime import datetime, timedelta from time import sleep twisted = pytest.importorskip("twisted") from twisted.internet import reactor, defer from twisted.trial import unittest from rx.scheduler.eventloop import TwistedScheduler class TestTwistedScheduler(unittest.TestCase): def test_twisted_schedule_now(self): scheduler = TwistedScheduler(reactor) diff = scheduler.now - datetime.utcfromtimestamp(float(reactor.seconds())) assert abs(diff) < timedelta(milliseconds=1) def test_twisted_schedule_now_units(self): scheduler = TwistedScheduler(reactor) diff = scheduler.now sleep(0.1) diff = scheduler.now - diff assert timedelta(milliseconds=80) < diff < timedelta(milliseconds=180) @defer.inlineCallbacks def test_twisted_schedule_action(self): scheduler = TwistedScheduler(reactor) promise = defer.Deferred() ran = False def action(scheduler, state): nonlocal ran ran = True def done(): promise.callback('Done') scheduler.schedule(action) reactor.callLater(0.1, done) yield promise assert ran is True @defer.inlineCallbacks def test_twisted_schedule_action_due(self): scheduler = TwistedScheduler(reactor) promise = defer.Deferred() starttime = reactor.seconds() endtime = None def action(scheduler, state): nonlocal endtime endtime = reactor.seconds() def done(): promise.callback('Done') scheduler.schedule_relative(0.2, action) reactor.callLater(0.3, done) yield promise diff = endtime - starttime assert diff > 0.18 @defer.inlineCallbacks def test_twisted_schedule_action_cancel(self): scheduler = TwistedScheduler(reactor) promise = defer.Deferred() ran = False def action(scheduler, state): nonlocal ran ran = True def done(): promise.callback('Done') d = scheduler.schedule_relative(0.01, action) d.dispose() reactor.callLater(0.1, done) yield promise assert ran is False RxPY-3.2.0/tests/test_scheduler/test_eventloopscheduler.py000066400000000000000000000171301404130727200240620ustar00rootroot00000000000000import pytest import unittest import threading from datetime import timedelta from time import sleep from rx.scheduler import EventLoopScheduler from rx.internal import DisposedException from rx.internal.basic import default_now class TestEventLoopScheduler(unittest.TestCase): def test_event_loop_now(self): scheduler = EventLoopScheduler() diff = scheduler.now - default_now() assert abs(diff) < timedelta(milliseconds=5) def test_event_loop_now_units(self): scheduler = EventLoopScheduler() diff = scheduler.now sleep(1.1) diff = scheduler.now - diff assert timedelta(milliseconds=1000) < diff < timedelta(milliseconds=1300) def test_event_loop_schedule_action(self): scheduler = EventLoopScheduler(exit_if_empty=True) ran = False gate = threading.Semaphore(0) def action(scheduler, state): nonlocal ran ran = True gate.release() scheduler.schedule(action) gate.acquire() assert ran is True # There is no guarantee that the event-loop thread ends before the # test thread is re-scheduled, give it some time to always run. sleep(0.1) assert scheduler._has_thread() is False def test_event_loop_different_thread(self): thread_id = None scheduler = EventLoopScheduler(exit_if_empty=True) gate = threading.Semaphore(0) def action(scheduler, state): nonlocal thread_id thread_id = threading.current_thread().ident gate.release() scheduler.schedule(action) gate.acquire() # There is no guarantee that the event-loop thread ends before the # test thread is re-scheduled, give it some time to always run. sleep(0.1) assert thread_id != threading.current_thread().ident assert scheduler._has_thread() is False def test_event_loop_schedule_ordered_actions(self): scheduler = EventLoopScheduler(exit_if_empty=True) gate = threading.Semaphore(0) result = [] scheduler.schedule(lambda s, t: result.append(1)) def action(scheduler, state): result.append(2) gate.release() scheduler.schedule(action) gate.acquire() # There is no guarantee that the event-loop thread ends before the # test thread is re-scheduled, give it some time to always run. sleep(0.1) assert result == [1, 2] assert scheduler._has_thread() is False def test_event_loop_schedule_ordered_actions_due(self): scheduler = EventLoopScheduler(exit_if_empty=True) gate = threading.Semaphore(0) result = [] def action(scheduler, state): result.append(3) gate.release() scheduler.schedule_relative(0.4, action) scheduler.schedule_relative(0.2, lambda s, t: result.append(2)) scheduler.schedule(lambda s, t: result.append(1)) gate.acquire() # There is no guarantee that the event-loop thread ends before the # test thread is re-scheduled, give it some time to always run. sleep(0.1) assert result == [1, 2, 3] assert scheduler._has_thread() is False def test_event_loop_schedule_ordered_actions_due_mixed(self): scheduler = EventLoopScheduler(exit_if_empty=True) gate = threading.Semaphore(0) result = [] def action(scheduler, state): result.append(1) scheduler.schedule_relative(0.2, action3) scheduler.schedule(action2) def action2(scheduler, state): result.append(2) def action3(scheduler, state): result.append(3) gate.release() scheduler.schedule(action) gate.acquire() # There is no guarantee that the event-loop thread ends before the # test thread is re-scheduled, give it some time to always run. sleep(0.1) assert result == [1, 2, 3] assert scheduler._has_thread() is False def test_event_loop_schedule_action_relative_due(self): scheduler = EventLoopScheduler(exit_if_empty=True) gate = threading.Semaphore(0) starttime = default_now() endtime = None def action(scheduler, state): nonlocal endtime endtime = default_now() gate.release() scheduler.schedule_relative(timedelta(milliseconds=200), action) gate.acquire() # There is no guarantee that the event-loop thread ends before the # test thread is re-scheduled, give it some time to always run. sleep(0.1) diff = endtime - starttime assert diff > timedelta(milliseconds=180) assert scheduler._has_thread() is False def test_event_loop_schedule_action_absolute_due(self): scheduler = EventLoopScheduler(exit_if_empty=True) gate = threading.Semaphore(0) starttime = default_now() endtime = None def action(scheduler, state): nonlocal endtime endtime = default_now() gate.release() scheduler.schedule_absolute(scheduler.now, action) gate.acquire() # There is no guarantee that the event-loop thread ends before the # test thread is re-scheduled, give it some time to always run. sleep(0.1) diff = endtime - starttime assert diff < timedelta(milliseconds=180) assert scheduler._has_thread() is False def test_eventloop_schedule_action_periodic(self): scheduler = EventLoopScheduler(exit_if_empty=False) gate = threading.Semaphore(0) period = 0.05 counter = 3 def action(state): nonlocal counter if state: counter -= 1 return state - 1 if counter == 0: gate.release() disp = scheduler.schedule_periodic(period, action, counter) def dispose(scheduler, state): disp.dispose() gate.release() gate.acquire() assert counter == 0 assert scheduler._has_thread() is True scheduler.schedule(dispose) gate.acquire() assert scheduler._has_thread() is True sleep(period) scheduler.dispose() sleep(period) assert scheduler._has_thread() is False def test_eventloop_schedule_dispose(self): scheduler = EventLoopScheduler(exit_if_empty=False) scheduler.dispose() ran = False def action(scheduler, state): nonlocal ran ran = True with pytest.raises(DisposedException): scheduler.schedule(action) assert ran is False assert scheduler._has_thread() is False def test_eventloop_schedule_absolute_dispose(self): scheduler = EventLoopScheduler(exit_if_empty=False) scheduler.dispose() ran = False def action(scheduler, state): nonlocal ran ran = True with pytest.raises(DisposedException): scheduler.schedule_absolute(scheduler.now, action) assert ran is False assert scheduler._has_thread() is False def test_eventloop_schedule_periodic_dispose_error(self): scheduler = EventLoopScheduler(exit_if_empty=False) scheduler.dispose() ran = False def action(scheduler, state): nonlocal ran ran = True with pytest.raises(DisposedException): scheduler.schedule_periodic(0.1, action) assert ran is False assert scheduler._has_thread() is False RxPY-3.2.0/tests/test_scheduler/test_historicalscheduler.py000066400000000000000000000233731404130727200242160ustar00rootroot00000000000000import unittest from datetime import datetime, timedelta from rx.scheduler import HistoricalScheduler from rx.internal.constants import UTC_ZERO def assert_equals(first, second): if len(first) != len(second): print("len(%d) != len(%d)" % (len(first), len(second))) assert False for i in range(len(first)): f = first[i] s = second[i] if hasattr(f, "assert_equals") and hasattr(s, "assert_equals"): assert f.assert_equals(s) else: assert f == s def time(days): dt = datetime(year=1979, month=10, day=31, hour=4, minute=30, second=15) dt = dt + timedelta(days=days) return dt def from_days(days): return timedelta(days=days) class Timestamped(object): def __init__(self, value, timestamp): self.value = value self.timestamp = timestamp def assert_equals(self, other): if not other: return False return other.value == self.value and other.timestamp == self.timestamp def __str__(self): return "(%s, %s)" % (self.value, self.timestamp) def __repr__(self): return str(self) class TestHistoricalScheduler(unittest.TestCase): def test_ctor(self): s = HistoricalScheduler() self.assertEqual(UTC_ZERO, s.clock) self.assertEqual(False, s._is_enabled) def test_start_stop(self): s = HistoricalScheduler() list = [] s.schedule_absolute(time(0), lambda sc, st: list.append(Timestamped(1, s.now))) s.schedule_absolute(time(1), lambda sc, st: list.append(Timestamped(2, s.now))) s.schedule_absolute(time(2), lambda sc, st: s.stop()) s.schedule_absolute(time(3), lambda sc, st: list.append(Timestamped(3, s.now))) s.schedule_absolute(time(4), lambda sc, st: s.stop()) s.schedule_absolute(time(5), lambda sc, st: s.start()) s.schedule_absolute(time(6), lambda sc, st: list.append(Timestamped(4, s.now))) s.start() self.assertEqual(time(2), s.now) self.assertEqual(time(2), s.clock) s.start() self.assertEqual(time(4), s.now) self.assertEqual(time(4), s.clock) s.start() self.assertEqual(time(6), s.now) self.assertEqual(time(6), s.clock) s.start() self.assertEqual(time(6), s.now) self.assertEqual(time(6), s.clock) assert_equals(list, [ Timestamped(1, time(0)), Timestamped(2, time(1)), Timestamped(3, time(3)), Timestamped(4, time(6)) ]) def test_order(self): s = HistoricalScheduler() list = [] s.schedule_absolute(time(2), lambda a, b: list.append(Timestamped(2, s.now))) s.schedule_absolute(time(3), lambda a, b: list.append(Timestamped(3, s.now))) s.schedule_absolute(time(1), lambda a, b: list.append(Timestamped(0, s.now))) s.schedule_absolute(time(1), lambda a, b: list.append(Timestamped(1, s.now))) s.start() assert_equals(list, [ Timestamped(0, time(1)), Timestamped(1, time(1)), Timestamped(2, time(2)), Timestamped(3, time(3)) ]) def test_cancellation(self): s = HistoricalScheduler() list = [] d = s.schedule_absolute(time(2), lambda a, b: list.append(Timestamped(2, s.now))) def action(scheduler, state): list.append(Timestamped(0, s.now)) d.dispose() s.schedule_absolute(time(1), action) s.start() assert_equals(list, [ Timestamped(0, time(1)) ]) def test_advance_to(self): s = HistoricalScheduler() list = [] s.schedule_absolute(time(0), lambda a, b: list.append(Timestamped(0, s.now))) s.schedule_absolute(time(1), lambda a, b: list.append(Timestamped(1, s.now))) s.schedule_absolute(time(2), lambda a, b: list.append(Timestamped(2, s.now))) s.schedule_absolute(time(10), lambda a, b: list.append(Timestamped(10, s.now))) s.schedule_absolute(time(11), lambda a, b: list.append(Timestamped(11, s.now))) s.advance_to(time(8)) self.assertEqual(time(8), s.now) self.assertEqual(time(8), s.clock) assert_equals(list, [ Timestamped(0, time(0)), Timestamped(1, time(1)), Timestamped(2, time(2)) ]) s.advance_to(time(8)) self.assertEqual(time(8), s.now) self.assertEqual(time(8), s.clock) assert_equals(list, [ Timestamped(0, time(0)), Timestamped(1, time(1)), Timestamped(2, time(2)) ]) s.schedule_absolute(time(7), lambda a, b: list.append(Timestamped(7, s.now))) s.schedule_absolute(time(8), lambda a, b: list.append(Timestamped(8, s.now))) self.assertEqual(time(8), s.now) self.assertEqual(time(8), s.clock) assert_equals(list, [ Timestamped(0, time(0)), Timestamped(1, time(1)), Timestamped(2, time(2)) ]) s.advance_to(time(10)) self.assertEqual(time(10), s.now) self.assertEqual(time(10), s.clock) assert_equals(list, [ Timestamped(0, time(0)), Timestamped(1, time(1)), Timestamped(2, time(2)), Timestamped(7, time(8)), Timestamped(8, time(8)), Timestamped(10, time(10)) ]) s.advance_to(time(100)) self.assertEqual(time(100), s.now) self.assertEqual(time(100), s.clock) assert_equals(list, [ Timestamped(0, time(0)), Timestamped(1, time(1)), Timestamped(2, time(2)), Timestamped(7, time(8)), Timestamped(8, time(8)), Timestamped(10, time(10)), Timestamped(11, time(11)) ]) def test_advance_by(self): s = HistoricalScheduler() list = [] s.schedule_absolute(time(0), lambda a, b: list.append(Timestamped(0, s.now))) s.schedule_absolute(time(1), lambda a, b: list.append(Timestamped(1, s.now))) s.schedule_absolute(time(2), lambda a, b: list.append(Timestamped(2, s.now))) s.schedule_absolute(time(10), lambda a, b: list.append(Timestamped(10, s.now))) s.schedule_absolute(time(11), lambda a, b: list.append(Timestamped(11, s.now))) s.advance_by(time(8) - s.now) self.assertEqual(time(8), s.now) self.assertEqual(time(8), s.clock) assert_equals(list, [ Timestamped(0, time(0)), Timestamped(1, time(1)), Timestamped(2, time(2)) ]) s.schedule_absolute(time(7), lambda a, b: list.append(Timestamped(7, s.now))) s.schedule_absolute(time(8), lambda a, b: list.append(Timestamped(8, s.now))) self.assertEqual(time(8), s.now) self.assertEqual(time(8), s.clock) assert_equals(list, [ Timestamped(0, time(0)), Timestamped(1, time(1)), Timestamped(2, time(2)) ]) s.advance_by(timedelta(0)) self.assertEqual(time(8), s.now) self.assertEqual(time(8), s.clock) assert_equals(list, [ Timestamped(0, time(0)), Timestamped(1, time(1)), Timestamped(2, time(2)) ]) s.advance_by(from_days(2)) self.assertEqual(time(10), s.now) self.assertEqual(time(10), s.clock) assert_equals(list, [ Timestamped(0, time(0)), Timestamped(1, time(1)), Timestamped(2, time(2)), Timestamped(7, time(8)), Timestamped(8, time(8)), Timestamped(10, time(10)) ]) s.advance_by(from_days(90)) self.assertEqual(time(100), s.now) self.assertEqual(time(100), s.clock) assert_equals(list, [ Timestamped(0, time(0)), Timestamped(1, time(1)), Timestamped(2, time(2)), Timestamped(7, time(8)), Timestamped(8, time(8)), Timestamped(10, time(10)), Timestamped(11, time(11)) ]) def test_is_enabled(self): s = HistoricalScheduler() self.assertEqual(False, s._is_enabled) def action(scheduler, state): self.assertEqual(True, s._is_enabled) s.stop() self.assertEqual(False, s._is_enabled) s.schedule(action) self.assertEqual(False, s._is_enabled) s.start() self.assertEqual(False, s._is_enabled) def test_sleep1(self): now = datetime(year=1983, month=2, day=11, hour=12) s = HistoricalScheduler(now) s.sleep(from_days(1)) self.assertEqual(now + from_days(1), s.clock) def test_sleep2(self): s = HistoricalScheduler() n = [0] def action(scheduler, state): s.sleep(timedelta(3 * 6000)) n[0] += 1 s.schedule_absolute(s.now + timedelta(6000), action) s.schedule_absolute(s.now + timedelta(6000), action) s.advance_to(s.now + timedelta(5 * 6000)) self.assertEqual(2, n[0]) def test_schedule_relative_with_timedelta(self): s = HistoricalScheduler() n = 0 def action(scheduler, state): nonlocal n n += 1 s.schedule_relative(timedelta(2), action) s.advance_by(timedelta(1)) self.assertEqual(n, 0) s.advance_by(timedelta(1)) self.assertEqual(n, 1) def test_schedule_relative_with_float(self): s = HistoricalScheduler() n = 0 def action(scheduler, state): nonlocal n n += 1 s.schedule_relative(1.0, action) s.advance_by(0.5) self.assertEqual(n, 0) s.advance_by(0.5) self.assertEqual(n, 1) RxPY-3.2.0/tests/test_scheduler/test_immediatescheduler.py000066400000000000000000000120351404130727200240040ustar00rootroot00000000000000import pytest import unittest import threading from datetime import timedelta from time import sleep from rx.disposable import Disposable from rx.scheduler import ImmediateScheduler from rx.internal.basic import default_now from rx.internal.constants import DELTA_ZERO from rx.internal.exceptions import WouldBlockException class TestImmediateScheduler(unittest.TestCase): def test_immediate_singleton(self): scheduler = [ ImmediateScheduler(), ImmediateScheduler.singleton() ] assert scheduler[0] is scheduler[1] gate = [threading.Semaphore(0), threading.Semaphore(0)] scheduler = [None, None] def run(idx): scheduler[idx] = ImmediateScheduler() gate[idx].release() for idx in (0, 1): threading.Thread(target=run, args=(idx,)).start() gate[idx].acquire() assert scheduler[0] is not None assert scheduler[1] is not None assert scheduler[0] is scheduler[1] def test_immediate_extend(self): class MyScheduler(ImmediateScheduler): pass scheduler = [ MyScheduler(), MyScheduler.singleton(), ImmediateScheduler.singleton(), ] assert scheduler[0] is scheduler[1] assert scheduler[0] is not scheduler[2] def test_immediate_now(self): scheduler = ImmediateScheduler() diff = scheduler.now - default_now() assert abs(diff) <= timedelta(milliseconds=1) def test_immediate_now_units(self): scheduler = ImmediateScheduler() diff = scheduler.now sleep(1.1) diff = scheduler.now - diff assert timedelta(milliseconds=1000) < diff < timedelta(milliseconds=1300) def test_immediate_scheduleaction(self): scheduler = ImmediateScheduler() ran = False def action(scheduler, state=None): nonlocal ran ran = True scheduler.schedule(action) assert ran def test_immediate_schedule_action_error(self): scheduler = ImmediateScheduler() class MyException(Exception): pass def action(scheduler, state=None): raise MyException() with pytest.raises(MyException): return scheduler.schedule(action) def test_immediate_schedule_action_due_error(self): scheduler = ImmediateScheduler() ran = False def action(scheduler, state=None): nonlocal ran ran = True with pytest.raises(WouldBlockException): scheduler.schedule_relative(0.1, action) assert ran is False def test_immediate_simple1(self): scheduler = ImmediateScheduler() xx = 0 def action(scheduler, state=None): nonlocal xx xx = state return Disposable() scheduler.schedule(action, 42) assert xx == 42 def test_immediate_simple2(self): scheduler = ImmediateScheduler() xx = 0 def action(scheduler, state=None): nonlocal xx xx = state return Disposable() scheduler.schedule_absolute(default_now(), action, 42) assert xx == 42 def test_immediate_simple3(self): scheduler = ImmediateScheduler() xx = 0 def action(scheduler, state=None): nonlocal xx xx = state return Disposable() scheduler.schedule_relative(DELTA_ZERO, action, 42) assert xx == 42 def test_immediate_recursive1(self): scheduler = ImmediateScheduler() xx = 0 yy = 0 def action(scheduler, state=None): nonlocal xx xx = state def inner_action(scheduler, state=None): nonlocal yy yy = state return Disposable() return scheduler.schedule(inner_action, 43) scheduler.schedule(action, 42) assert xx == 42 assert yy == 43 def test_immediate_recursive2(self): scheduler = ImmediateScheduler() xx = 0 yy = 0 def action(scheduler, state=None): nonlocal xx xx = state def inner_action(scheduler, state=None): nonlocal yy yy = state return Disposable() return scheduler.schedule_absolute(default_now(), inner_action, 43) scheduler.schedule_absolute(default_now(), action, 42) assert xx == 42 assert yy == 43 def test_immediate_recursive3(self): scheduler = ImmediateScheduler() xx = 0 yy = 0 def action(scheduler, state=None): nonlocal xx xx = state def inner_action(scheduler, state): nonlocal yy yy = state return Disposable() return scheduler.schedule_relative(DELTA_ZERO, inner_action, 43) scheduler.schedule_relative(DELTA_ZERO, action, 42) assert xx == 42 assert yy == 43 RxPY-3.2.0/tests/test_scheduler/test_mainloop/000077500000000000000000000000001404130727200214125ustar00rootroot00000000000000RxPY-3.2.0/tests/test_scheduler/test_mainloop/__init__.py000066400000000000000000000000001404130727200235110ustar00rootroot00000000000000RxPY-3.2.0/tests/test_scheduler/test_mainloop/test_gtkscheduler.py000066400000000000000000000076771404130727200255300ustar00rootroot00000000000000import pytest import unittest import os import threading from datetime import timedelta from time import sleep from rx.scheduler.mainloop import GtkScheduler from rx.internal.basic import default_now gi = pytest.importorskip('gi') gi.require_version('Gtk', '3.0') from gi.repository import GLib, Gtk # Removing GNOME_DESKTOP_SESSION_ID from environment # prevents QtScheduler test from failing with message # Gtk-ERROR **: GTK+ 2.x symbols detected. # Using GTK+ 2.x and GTK+ 3 in the same process is not supported if 'GNOME_DESKTOP_SESSION_ID' in os.environ: del os.environ['GNOME_DESKTOP_SESSION_ID'] class TestGtkScheduler(unittest.TestCase): def test_gtk_schedule_now(self): scheduler = GtkScheduler(GLib) diff = scheduler.now - default_now() assert abs(diff) < timedelta(milliseconds=1) def test_gtk_schedule_now_units(self): scheduler = GtkScheduler(GLib) diff = scheduler.now sleep(0.1) diff = scheduler.now - diff assert timedelta(milliseconds=80) < diff < timedelta(milliseconds=180) def test_gtk_schedule_action(self): scheduler = GtkScheduler(GLib) gate = threading.Semaphore(0) ran = False def action(scheduler, state): nonlocal ran ran = True scheduler.schedule(action) def done(data): Gtk.main_quit() gate.release() return False GLib.timeout_add(50, done, None) Gtk.main() gate.acquire() assert ran is True def test_gtk_schedule_action_relative(self): scheduler = GtkScheduler(GLib) gate = threading.Semaphore(0) starttime = default_now() endtime = None def action(scheduler, state): nonlocal endtime endtime = default_now() scheduler.schedule_relative(0.1, action) def done(data): Gtk.main_quit() gate.release() return False GLib.timeout_add(200, done, None) Gtk.main() gate.acquire() assert endtime is not None diff = endtime - starttime assert diff > timedelta(milliseconds=80) def test_gtk_schedule_action_absolute(self): scheduler = GtkScheduler(GLib) gate = threading.Semaphore(0) starttime = default_now() endtime = None def action(scheduler, state): nonlocal endtime endtime = default_now() due = scheduler.now + timedelta(milliseconds=100) scheduler.schedule_absolute(due, action) def done(data): Gtk.main_quit() gate.release() return False GLib.timeout_add(200, done, None) Gtk.main() gate.acquire() assert endtime is not None diff = endtime - starttime assert diff > timedelta(milliseconds=80) def test_gtk_schedule_action_cancel(self): ran = False scheduler = GtkScheduler(GLib) gate = threading.Semaphore(0) def action(scheduler, state): nonlocal ran ran = True d = scheduler.schedule_relative(0.1, action) d.dispose() def done(data): Gtk.main_quit() gate.release() return False GLib.timeout_add(200, done, None) Gtk.main() gate.acquire() assert ran is False def test_gtk_schedule_action_periodic(self): scheduler = GtkScheduler(GLib) gate = threading.Semaphore(0) period = 0.05 counter = 3 def action(state): nonlocal counter if state: counter -= 1 return state - 1 scheduler.schedule_periodic(period, action, counter) def done(data): Gtk.main_quit() gate.release() return False GLib.timeout_add(300, done, None) Gtk.main() gate.acquire() assert counter == 0 RxPY-3.2.0/tests/test_scheduler/test_mainloop/test_pygamescheduler.py000066400000000000000000000046031404130727200262070ustar00rootroot00000000000000import pytest import unittest from datetime import timedelta from time import sleep from rx.scheduler.mainloop import PyGameScheduler from rx.internal.basic import default_now pygame = pytest.importorskip("pygame") class TestPyGameScheduler(unittest.TestCase): def test_pygame_schedule_now(self): scheduler = PyGameScheduler(pygame) diff = scheduler.now - default_now() assert abs(diff) < timedelta(milliseconds=1) def test_pygame_schedule_now_units(self): scheduler = PyGameScheduler(pygame) diff = scheduler.now sleep(0.1) diff = scheduler.now - diff assert timedelta(milliseconds=80) < diff < timedelta(milliseconds=180) def test_pygame_schedule_action(self): scheduler = PyGameScheduler(pygame) ran = False def action(scheduler, state): nonlocal ran ran = True scheduler.schedule(action) scheduler.run() assert ran is True def test_pygame_schedule_action_due_relative(self): scheduler = PyGameScheduler(pygame) starttime = default_now() endtime = None def action(scheduler, state): nonlocal endtime endtime = default_now() scheduler.schedule_relative(0.1, action) scheduler.run() assert endtime is None sleep(0.2) scheduler.run() assert endtime is not None diff = endtime - starttime assert diff > timedelta(milliseconds=180) def test_pygame_schedule_action_due_absolute(self): scheduler = PyGameScheduler(pygame) starttime = default_now() endtime = None def action(scheduler, state): nonlocal endtime endtime = default_now() scheduler.schedule_absolute(starttime + timedelta(seconds=0.1), action) scheduler.run() assert endtime is None sleep(0.2) scheduler.run() assert endtime is not None diff = endtime - starttime assert diff > timedelta(milliseconds=180) def test_pygame_schedule_action_cancel(self): scheduler = PyGameScheduler(pygame) ran = False def action(scheduler, state): nonlocal ran ran = True d = scheduler.schedule_relative(0.1, action) d.dispose() sleep(0.2) scheduler.run() assert ran is False RxPY-3.2.0/tests/test_scheduler/test_mainloop/test_qtscheduler_pyqt5.py000066400000000000000000000103551404130727200265140ustar00rootroot00000000000000import pytest import threading from datetime import timedelta from time import sleep QtCore = pytest.importorskip('PyQt5.QtCore') from rx.scheduler.mainloop import QtScheduler from rx.internal.basic import default_now @pytest.fixture(scope="module") def app(): # share qt application among all tests app = QtCore.QCoreApplication([]) yield app # teardown class TestQtSchedulerPyQt5: def test_pyqt5_schedule_now(self): scheduler = QtScheduler(QtCore) diff = scheduler.now - default_now() assert abs(diff) < timedelta(milliseconds=1) def test_pyqt5_schedule_now_units(self): scheduler = QtScheduler(QtCore) diff = scheduler.now sleep(0.1) diff = scheduler.now - diff assert timedelta(milliseconds=80) < diff < timedelta(milliseconds=180) def test_pyqt5_schedule_action(self, app): scheduler = QtScheduler(QtCore) gate = threading.Semaphore(0) ran = False def action(scheduler, state): nonlocal ran ran = True scheduler.schedule(action) def done(): app.quit() gate.release() QtCore.QTimer.singleShot(50, done) app.exec_() gate.acquire() assert ran is True def test_pyqt5_schedule_action_due_relative(self, app): scheduler = QtScheduler(QtCore) gate = threading.Semaphore(0) starttime = default_now() endtime = None def action(scheduler, state): nonlocal endtime endtime = default_now() scheduler.schedule_relative(0.2, action) def done(): app.quit() gate.release() QtCore.QTimer.singleShot(300, done) app.exec_() gate.acquire() assert endtime is not None diff = endtime - starttime assert diff > timedelta(milliseconds=180) def test_pyqt5_schedule_action_due_absolute(self, app): scheduler = QtScheduler(QtCore) gate = threading.Semaphore(0) starttime = default_now() endtime = None def action(scheduler, state): nonlocal endtime endtime = default_now() scheduler.schedule_absolute(starttime + timedelta(seconds=0.2), action) def done(): app.quit() gate.release() QtCore.QTimer.singleShot(300, done) app.exec_() gate.acquire() assert endtime is not None diff = endtime - starttime assert diff > timedelta(milliseconds=180) def test_pyqt5_schedule_action_cancel(self, app): ran = False scheduler = QtScheduler(QtCore) gate = threading.Semaphore(0) def action(scheduler, state): nonlocal ran ran = True d = scheduler.schedule_relative(0.1, action) d.dispose() def done(): app.quit() gate.release() QtCore.QTimer.singleShot(300, done) app.exec_() gate.acquire() assert ran is False def test_pyqt5_schedule_action_periodic(self, app): scheduler = QtScheduler(QtCore) gate = threading.Semaphore(0) period = 0.050 counter = 3 def action(state): nonlocal counter if state: counter -= 1 return state - 1 scheduler.schedule_periodic(period, action, counter) def done(): app.quit() gate.release() QtCore.QTimer.singleShot(300, done) app.exec_() gate.acquire() assert counter == 0 def test_pyqt5_schedule_periodic_cancel(self, app): scheduler = QtScheduler(QtCore) gate = threading.Semaphore(0) period = 0.05 counter = 3 def action(state): nonlocal counter if state: counter -= 1 return state - 1 disp = scheduler.schedule_periodic(period, action, counter) def dispose(): disp.dispose() QtCore.QTimer.singleShot(100, dispose) def done(): app.quit() gate.release() QtCore.QTimer.singleShot(300, done) app.exec_() gate.acquire() assert 0 < counter < 3 RxPY-3.2.0/tests/test_scheduler/test_mainloop/test_qtscheduler_pyside2.py000066400000000000000000000104031404130727200270030ustar00rootroot00000000000000import pytest import threading from datetime import timedelta from time import sleep QtCore = pytest.importorskip('PySide2.QtCore') from rx.scheduler.mainloop import QtScheduler from rx.internal.basic import default_now @pytest.fixture(scope="module") def app(): # share qt application among all tests app = QtCore.QCoreApplication([]) yield app # teardown class TestQtSchedulerPySide2(): def test_pyside2_schedule_now(self): scheduler = QtScheduler(QtCore) diff = scheduler.now - default_now() assert abs(diff) < timedelta(milliseconds=1) def test_pyside2_schedule_now_units(self): scheduler = QtScheduler(QtCore) diff = scheduler.now sleep(0.1) diff = scheduler.now - diff assert timedelta(milliseconds=80) < diff < timedelta(milliseconds=180) def test_pyside2_schedule_action(self, app): scheduler = QtScheduler(QtCore) gate = threading.Semaphore(0) ran = False def action(scheduler, state): nonlocal ran ran = True scheduler.schedule(action) def done(): app.quit() gate.release() QtCore.QTimer.singleShot(50, done) app.exec_() gate.acquire() assert ran is True def test_pyside2_schedule_action_due_relative(self, app): scheduler = QtScheduler(QtCore) gate = threading.Semaphore(0) starttime = default_now() endtime = None def action(scheduler, state): nonlocal endtime endtime = default_now() scheduler.schedule_relative(0.2, action) def done(): app.quit() gate.release() QtCore.QTimer.singleShot(300, done) app.exec_() gate.acquire() assert endtime is not None diff = endtime - starttime assert diff > timedelta(milliseconds=180) def test_pyside2_schedule_action_due_absolute(self, app): scheduler = QtScheduler(QtCore) gate = threading.Semaphore(0) starttime = default_now() endtime = None def action(scheduler, state): nonlocal endtime endtime = default_now() scheduler.schedule_absolute(starttime + timedelta(seconds=0.2), action) def done(): app.quit() gate.release() QtCore.QTimer.singleShot(300, done) app.exec_() gate.acquire() assert endtime is not None diff = endtime - starttime assert diff > timedelta(milliseconds=180) def test_pyside2_schedule_action_cancel(self, app): ran = False scheduler = QtScheduler(QtCore) gate = threading.Semaphore(0) def action(scheduler, state): nonlocal ran ran = True d = scheduler.schedule_relative(0.1, action) d.dispose() def done(): app.quit() gate.release() QtCore.QTimer.singleShot(300, done) app.exec_() gate.acquire() assert ran is False def test_pyside2_schedule_action_periodic(self, app): scheduler = QtScheduler(QtCore) gate = threading.Semaphore(0) period = 0.050 counter = 3 def action(state): nonlocal counter if state: counter -= 1 return state - 1 scheduler.schedule_periodic(period, action, counter) def done(): app.quit() gate.release() QtCore.QTimer.singleShot(300, done) app.exec_() gate.acquire() assert counter == 0 def test_pyside2_schedule_periodic_cancel(self, app): scheduler = QtScheduler(QtCore) gate = threading.Semaphore(0) period = 0.05 counter = 3 def action(state): nonlocal counter if state: counter -= 1 return state - 1 disp = scheduler.schedule_periodic(period, action, counter) def dispose(): disp.dispose() QtCore.QTimer.singleShot(100, dispose) def done(): app.quit() gate.release() QtCore.QTimer.singleShot(300, done) app.exec_() gate.acquire() assert 0 < counter < 3 RxPY-3.2.0/tests/test_scheduler/test_mainloop/test_tkinterscheduler.py000066400000000000000000000051541404130727200264070ustar00rootroot00000000000000import pytest import unittest from datetime import timedelta from time import sleep from rx.scheduler.mainloop import TkinterScheduler from rx.internal.basic import default_now tkinter = pytest.importorskip("tkinter") try: root = tkinter.Tk() root.withdraw() # Don't actually draw anything display = True except Exception: display = False @pytest.mark.skipif("display == False") class TestTkinterScheduler(unittest.TestCase): def test_tkinter_schedule_now(self): scheduler = TkinterScheduler(root) res = scheduler.now - default_now() assert abs(res) <= timedelta(milliseconds=1) def test_tkinter_schedule_now_units(self): scheduler = TkinterScheduler(root) diff = scheduler.now sleep(1.1) diff = scheduler.now - diff assert timedelta(milliseconds=1000) < diff < timedelta(milliseconds=1300) def test_tkinter_schedule_action(self): scheduler = TkinterScheduler(root) ran = False def action(scheduler, state): nonlocal ran ran = True scheduler.schedule(action) def done(): assert ran is True root.quit() root.after_idle(done) root.mainloop() def test_tkinter_schedule_action_due(self): scheduler = TkinterScheduler(root) starttime = default_now() endtime = None def action(scheduler, state): nonlocal endtime endtime = default_now() scheduler.schedule_relative(0.2, action) def done(): root.quit() assert endtime is not None diff = endtime - starttime assert diff > timedelta(milliseconds=180) root.after(300, done) root.mainloop() def test_tkinter_schedule_action_cancel(self): ran = False scheduler = TkinterScheduler(root) def action(scheduler, state): nonlocal ran ran = True d = scheduler.schedule_relative(0.1, action) d.dispose() def done(): root.quit() assert ran is False root.after(300, done) root.mainloop() def test_tkinter_schedule_action_periodic(self): scheduler = TkinterScheduler(root) period = 0.050 counter = 3 def action(state): nonlocal counter if state: counter -= 1 return state - 1 scheduler.schedule_periodic(period, action, counter) def done(): root.quit() assert counter == 0 root.after(300, done) root.mainloop() RxPY-3.2.0/tests/test_scheduler/test_mainloop/test_wxscheduler.py000066400000000000000000000067451404130727200253740ustar00rootroot00000000000000from datetime import timedelta from time import sleep import unittest import pytest from rx.scheduler.mainloop import WxScheduler from rx.internal.basic import default_now wx = pytest.importorskip('wx') def make_app(): app = wx.App() wx.Frame(None) # We need this for some reason, or the loop won't run return app class AppExit(wx.Timer): def __init__(self, app) -> None: super().__init__() self.app = app def Notify(self): self.app.ExitMainLoop() class TestWxScheduler(unittest.TestCase): def test_wx_schedule_now(self): scheduler = WxScheduler(wx) diff = scheduler.now - default_now() assert abs(diff) < timedelta(milliseconds=1) def test_wx_schedule_now_units(self): scheduler = WxScheduler(wx) diff = scheduler.now sleep(0.1) diff = scheduler.now - diff assert timedelta(milliseconds=80) < diff < timedelta(milliseconds=180) def test_wx_schedule_action(self): app = make_app() exit = AppExit(app) scheduler = WxScheduler(wx) ran = False def action(scheduler, state): nonlocal ran ran = True scheduler.schedule(action) exit.Start(100, wx.TIMER_ONE_SHOT) app.MainLoop() scheduler.cancel_all() assert ran is True def test_wx_schedule_action_relative(self): app = make_app() exit = AppExit(app) scheduler = WxScheduler(wx) starttime = default_now() endtime = None def action(scheduler, state): nonlocal endtime endtime = default_now() scheduler.schedule_relative(0.1, action) exit.Start(200, wx.TIMER_ONE_SHOT) app.MainLoop() scheduler.cancel_all() assert endtime is not None diff = endtime - starttime assert timedelta(milliseconds=80) < diff < timedelta(milliseconds=180) def test_wx_schedule_action_absolute(self): app = make_app() exit = AppExit(app) scheduler = WxScheduler(wx) starttime = default_now() endtime = None def action(scheduler, state): nonlocal endtime endtime = default_now() due = scheduler.now + timedelta(milliseconds=100) scheduler.schedule_absolute(due, action) exit.Start(200, wx.TIMER_ONE_SHOT) app.MainLoop() scheduler.cancel_all() assert endtime is not None diff = endtime - starttime assert timedelta(milliseconds=80) < diff < timedelta(milliseconds=180) def test_wx_schedule_action_cancel(self): app = make_app() exit = AppExit(app) scheduler = WxScheduler(wx) ran = False def action(scheduler, state): nonlocal ran ran = True d = scheduler.schedule_relative(0.1, action) d.dispose() exit.Start(200, wx.TIMER_ONE_SHOT) app.MainLoop() scheduler.cancel_all() assert ran is False def test_wx_schedule_action_periodic(self): app = make_app() exit = AppExit(app) scheduler = WxScheduler(wx) period = 0.05 counter = 3 def action(state): nonlocal counter if state: counter -= 1 return state - 1 scheduler.schedule_periodic(period, action, counter) exit.Start(500, wx.TIMER_ONE_SHOT) app.MainLoop() scheduler.cancel_all() assert counter == 0 RxPY-3.2.0/tests/test_scheduler/test_newthreadscheduler.py000066400000000000000000000051341404130727200240310ustar00rootroot00000000000000import unittest import threading from datetime import timedelta from time import sleep from rx.scheduler import NewThreadScheduler from rx.internal.basic import default_now class TestNewThreadScheduler(unittest.TestCase): def test_new_thread_now(self): scheduler = NewThreadScheduler() diff = scheduler.now - default_now() assert abs(diff) < timedelta(milliseconds=5) def test_new_thread_now_units(self): scheduler = NewThreadScheduler() diff = scheduler.now sleep(1.1) diff = scheduler.now - diff assert timedelta(milliseconds=1000) < diff < timedelta(milliseconds=1300) def test_new_thread_schedule_action(self): scheduler = NewThreadScheduler() ran = False def action(scheduler, state): nonlocal ran ran = True scheduler.schedule(action) sleep(0.1) assert ran is True def test_new_thread_schedule_action_due(self): scheduler = NewThreadScheduler() starttime = default_now() endtime = None def action(scheduler, state): nonlocal endtime endtime = default_now() scheduler.schedule_relative(timedelta(milliseconds=200), action) sleep(0.4) assert endtime is not None diff = endtime - starttime assert diff > timedelta(milliseconds=180) def test_new_thread_schedule_action_cancel(self): ran = False scheduler = NewThreadScheduler() def action(scheduler, state): nonlocal ran ran = True d = scheduler.schedule_relative(timedelta(milliseconds=1), action) d.dispose() sleep(0.1) assert ran is False def test_new_thread_schedule_periodic(self): scheduler = NewThreadScheduler() gate = threading.Semaphore(0) period = 0.05 counter = 3 def action(state): nonlocal counter if state: counter -= 1 return state - 1 if counter == 0: gate.release() scheduler.schedule_periodic(period, action, counter) gate.acquire() assert counter == 0 def test_new_thread_schedule_periodic_cancel(self): scheduler = NewThreadScheduler() period = 0.1 counter = 4 def action(state): nonlocal counter if state: counter -= 1 return state - 1 disp = scheduler.schedule_periodic(period, action, counter) sleep(0.4) disp.dispose() assert 0 <= counter < 4 RxPY-3.2.0/tests/test_scheduler/test_scheduleditem.py000066400000000000000000000043101404130727200227630ustar00rootroot00000000000000import unittest from datetime import timedelta from typing import Optional from rx.core import typing from rx.disposable import Disposable from rx.internal.basic import default_now from rx.scheduler.scheduleditem import ScheduledItem from rx.scheduler.scheduler import Scheduler class ScheduledItemTestScheduler(Scheduler): def __init__(self): super() self.action = None self.state = None self.disposable = None def invoke_action(self, action, state): self.action = action self.state = state self.disposable = super().invoke_action(action, state) return self.disposable def schedule(self, action, state): pass def schedule_relative(self, duetime, action, state): pass def schedule_absolute(self, duetime, action, state): pass class TestScheduledItem(unittest.TestCase): def test_scheduleditem_invoke(self): scheduler = ScheduledItemTestScheduler() disposable = Disposable() state = 42 ran = False def action(scheduler, state): nonlocal ran ran = True return disposable item = ScheduledItem(scheduler, state, action, default_now()) item.invoke() assert ran is True assert item.disposable.disposable is disposable assert scheduler.disposable is disposable assert scheduler.state is state assert scheduler.action is action def test_scheduleditem_cancel(self): scheduler = ScheduledItemTestScheduler() item = ScheduledItem(scheduler, None, lambda s, t: None, default_now()) item.cancel() assert item.disposable.is_disposed assert item.is_cancelled() def test_scheduleditem_compare(self): scheduler = ScheduledItemTestScheduler() duetime1 = default_now() duetime2 = duetime1 + timedelta(seconds=1) item1 = ScheduledItem(scheduler, None, lambda s, t: None, duetime1) item2 = ScheduledItem(scheduler, None, lambda s, t: None, duetime2) item3 = ScheduledItem(scheduler, None, lambda s, t: None, duetime1) assert item1 < item2 assert item2 > item3 assert item1 == item3 RxPY-3.2.0/tests/test_scheduler/test_scheduler.py000066400000000000000000000017231404130727200221270ustar00rootroot00000000000000import unittest from datetime import timedelta from rx.internal.constants import DELTA_ZERO, UTC_ZERO from rx.scheduler.scheduler import Scheduler class TestScheduler(unittest.TestCase): def test_base_to_seconds(self): val = Scheduler.to_seconds(0.0) assert val == 0.0 val = Scheduler.to_seconds(DELTA_ZERO) assert val == 0.0 val = Scheduler.to_seconds(UTC_ZERO) assert val == 0.0 def test_base_to_datetime(self): val = Scheduler.to_datetime(0.0) assert val == UTC_ZERO val = Scheduler.to_datetime(DELTA_ZERO) assert val == UTC_ZERO val = Scheduler.to_datetime(UTC_ZERO) assert val == UTC_ZERO def test_base_to_timedelta(self): val = Scheduler.to_timedelta(0.0) assert val == DELTA_ZERO val = Scheduler.to_timedelta(DELTA_ZERO) assert val == DELTA_ZERO val = Scheduler.to_timedelta(UTC_ZERO) assert val == DELTA_ZERO RxPY-3.2.0/tests/test_scheduler/test_threadpoolscheduler.py000066400000000000000000000045761404130727200242220ustar00rootroot00000000000000import unittest import threading from datetime import timedelta from time import sleep from rx.scheduler import ThreadPoolScheduler from rx.internal.basic import default_now thread_pool_scheduler = ThreadPoolScheduler() class TestThreadPoolScheduler(unittest.TestCase): def test_threadpool_now(self): scheduler = ThreadPoolScheduler() diff = scheduler.now - default_now() assert abs(diff) < timedelta(milliseconds=5) def test_threadpool_now_units(self): scheduler = ThreadPoolScheduler() diff = scheduler.now sleep(1.1) diff = scheduler.now - diff assert timedelta(milliseconds=1000) < diff < timedelta(milliseconds=1300) def test_schedule_action(self): ident = threading.current_thread().ident evt = threading.Event() nt = thread_pool_scheduler def action(scheduler, state): assert ident != threading.current_thread().ident evt.set() nt.schedule(action) evt.wait() def test_schedule_action_due_relative(self): ident = threading.current_thread().ident evt = threading.Event() nt = thread_pool_scheduler def action(scheduler, state): assert ident != threading.current_thread().ident evt.set() nt.schedule_relative(timedelta(milliseconds=200), action) evt.wait() def test_schedule_action_due_0(self): ident = threading.current_thread().ident evt = threading.Event() nt = thread_pool_scheduler def action(scheduler, state): assert ident != threading.current_thread().ident evt.set() nt.schedule_relative(0.1, action) evt.wait() def test_schedule_action_absolute(self): ident = threading.current_thread().ident evt = threading.Event() nt = thread_pool_scheduler def action(scheduler, state): assert ident != threading.current_thread().ident evt.set() nt.schedule_absolute(default_now() + timedelta(milliseconds=100), action) evt.wait() def test_schedule_action_cancel(self): nt = thread_pool_scheduler ran = False def action(scheduler, state): nonlocal ran ran = True d = nt.schedule_relative(0.05, action) d.dispose() sleep(0.1) assert ran is False RxPY-3.2.0/tests/test_scheduler/test_timeoutscheduler.py000066400000000000000000000051721404130727200235400ustar00rootroot00000000000000import unittest import threading from datetime import timedelta from time import sleep from rx.scheduler import TimeoutScheduler from rx.internal.basic import default_now class TestTimeoutScheduler(unittest.TestCase): def test_timeout_singleton(self): scheduler = [ TimeoutScheduler(), TimeoutScheduler.singleton() ] assert scheduler[0] is scheduler[1] gate = [threading.Semaphore(0), threading.Semaphore(0)] scheduler = [None, None] def run(idx): scheduler[idx] = TimeoutScheduler() gate[idx].release() for idx in (0, 1): threading.Thread(target=run, args=(idx,)).start() gate[idx].acquire() assert scheduler[0] is not None assert scheduler[1] is not None assert scheduler[0] is scheduler[1] def test_timeout_extend(self): class MyScheduler(TimeoutScheduler): pass scheduler = [ MyScheduler(), MyScheduler.singleton(), TimeoutScheduler.singleton(), ] assert scheduler[0] is scheduler[1] assert scheduler[0] is not scheduler[2] def test_timeout_now(self): scheduler = TimeoutScheduler() diff = scheduler.now - default_now() assert abs(diff) < timedelta(milliseconds=5) def test_timeout_now_units(self): scheduler = TimeoutScheduler() diff = scheduler.now sleep(1.1) diff = scheduler.now - diff assert timedelta(milliseconds=1000) < diff < timedelta(milliseconds=1300) def test_timeout_schedule_action(self): scheduler = TimeoutScheduler() ran = False def action(scheduler, state): nonlocal ran ran = True scheduler.schedule(action) sleep(0.1) assert ran is True def test_timeout_schedule_action_due(self): scheduler = TimeoutScheduler() starttime = default_now() endtime = None def action(scheduler, state): nonlocal endtime endtime = default_now() scheduler.schedule_relative(timedelta(milliseconds=200), action) sleep(0.4) assert endtime is not None diff = endtime - starttime assert diff > timedelta(milliseconds=180) def test_timeout_schedule_action_cancel(self): ran = False scheduler = TimeoutScheduler() def action(scheduler, state): nonlocal ran ran = True d = scheduler.schedule_relative(timedelta(milliseconds=300), action) d.dispose() sleep(0.1) assert ran is False RxPY-3.2.0/tests/test_scheduler/test_trampolinescheduler.py000066400000000000000000000121651404130727200242240ustar00rootroot00000000000000import pytest import unittest from datetime import timedelta from time import sleep from rx.scheduler import TrampolineScheduler from rx.internal.basic import default_now class TestTrampolineScheduler(unittest.TestCase): def test_trampoline_now(self): scheduler = TrampolineScheduler() diff = scheduler.now - default_now() assert abs(diff) < timedelta(milliseconds=1) def test_trampoline_now_units(self): scheduler = TrampolineScheduler() diff = scheduler.now sleep(1.1) diff = scheduler.now - diff assert timedelta(milliseconds=1000) < diff < timedelta(milliseconds=1300) def test_trampoline_schedule(self): scheduler = TrampolineScheduler() ran = False def action(scheduler, state=None): nonlocal ran ran = True scheduler.schedule(action) assert ran is True def test_trampoline_schedule_block(self): scheduler = TrampolineScheduler() ran = False def action(scheduler, state=None): nonlocal ran ran = True t = scheduler.now scheduler.schedule_relative(0.2, action) t = scheduler.now - t assert ran is True assert t >= timedelta(seconds=0.2) def test_trampoline_schedule_error(self): scheduler = TrampolineScheduler() class MyException(Exception): pass def action(scheduler, state=None): raise MyException() with pytest.raises(MyException): scheduler.schedule(action) def test_trampoline_schedule_nested(self): scheduler = TrampolineScheduler() ran = False def action(scheduler, state=None): def inner_action(scheduler, state=None): nonlocal ran ran = True return scheduler.schedule(inner_action) scheduler.schedule(action) assert ran is True def test_trampoline_schedule_nested_order(self): scheduler = TrampolineScheduler() tests = [] def outer(scheduler, state=None): def action1(scheduler, state=None): tests.append(1) def action2(scheduler, state=None): tests.append(2) TrampolineScheduler().schedule(action2) TrampolineScheduler().schedule(action1) def action3(scheduler, state=None): tests.append(3) scheduler3 = TrampolineScheduler() scheduler3.schedule(action3) scheduler.ensure_trampoline(outer) assert tests == [1, 2, 3] def test_trampoline_ensuretrampoline(self): scheduler = TrampolineScheduler() ran1, ran2 = False, False def outer_action(scheduer, state=None): def action1(scheduler, state=None): nonlocal ran1 ran1 = True scheduler.schedule(action1) def action2(scheduler, state=None): nonlocal ran2 ran2 = True return scheduler.schedule(action2) scheduler.ensure_trampoline(outer_action) assert ran1 is True assert ran2 is True def test_trampoline_ensuretrampoline_nested(self): scheduler = TrampolineScheduler() ran1, ran2 = False, False def outer_action(scheduler, state): def inner_action1(scheduler, state): nonlocal ran1 ran1 = True scheduler.ensure_trampoline(inner_action1) def inner_action2(scheduler, state): nonlocal ran2 ran2 = True return scheduler.ensure_trampoline(inner_action2) scheduler.ensure_trampoline(outer_action) assert ran1 is True assert ran2 is True def test_trampoline_ensuretrampoline_and_cancel(self): scheduler = TrampolineScheduler() ran1, ran2 = False, False def outer_action(scheduler, state): def inner_action1(scheduler, state): nonlocal ran1 ran1 = True def inner_action2(scheduler, state): nonlocal ran2 ran2 = True d = scheduler.schedule(inner_action2) d.dispose() return scheduler.schedule(inner_action1) scheduler.ensure_trampoline(outer_action) assert ran1 is True assert ran2 is False def test_trampoline_ensuretrampoline_and_canceltimed(self): scheduler = TrampolineScheduler() ran1, ran2 = False, False def outer_action(scheduler, state): def inner_action1(scheduler, state): nonlocal ran1 ran1 = True def inner_action2(scheduler, state): nonlocal ran2 ran2 = True t = scheduler.now + timedelta(seconds=0.5) d = scheduler.schedule_absolute(t, inner_action2) d.dispose() return scheduler.schedule(inner_action1) scheduler.ensure_trampoline(outer_action) assert ran1 is True assert ran2 is False RxPY-3.2.0/tests/test_scheduler/test_virtualtimescheduler.py000066400000000000000000000041371404130727200244170ustar00rootroot00000000000000import pytest import unittest from rx.scheduler import VirtualTimeScheduler from rx.internal import ArgumentOutOfRangeException from rx.internal.constants import DELTA_ZERO, UTC_ZERO class VirtualSchedulerTestScheduler(VirtualTimeScheduler): def add(self, absolute, relative): return absolute + relative class TestVirtualTimeScheduler(unittest.TestCase): def test_virtual_now_noarg(self): scheduler = VirtualSchedulerTestScheduler() assert scheduler.clock == 0.0 assert scheduler.now == UTC_ZERO def test_virtual_now_float(self): scheduler = VirtualSchedulerTestScheduler(0.0) assert scheduler.clock == 0.0 assert scheduler.now == UTC_ZERO def test_virtual_now_timedelta(self): scheduler = VirtualSchedulerTestScheduler(DELTA_ZERO) assert scheduler.clock == DELTA_ZERO assert scheduler.now == UTC_ZERO def test_virtual_now_datetime(self): scheduler = VirtualSchedulerTestScheduler(UTC_ZERO) assert scheduler.clock == UTC_ZERO assert scheduler.now == UTC_ZERO def test_virtual_schedule_action(self): scheduler = VirtualSchedulerTestScheduler() ran = False def action(scheduler, state): nonlocal ran ran = True scheduler.schedule(action) scheduler.start() assert ran is True def test_virtual_schedule_action_error(self): scheduler = VirtualSchedulerTestScheduler() class MyException(Exception): pass def action(scheduler, state): raise MyException() with pytest.raises(MyException): scheduler.schedule(action) scheduler.start() def test_virtual_schedule_sleep_error(self): scheduler = VirtualSchedulerTestScheduler() with pytest.raises(ArgumentOutOfRangeException): scheduler.sleep(-1) def test_virtual_schedule_advance_clock_error(self): scheduler = VirtualSchedulerTestScheduler() with pytest.raises(ArgumentOutOfRangeException): scheduler.advance_to(scheduler._clock - 1) RxPY-3.2.0/tests/test_subject/000077500000000000000000000000001404130727200162165ustar00rootroot00000000000000RxPY-3.2.0/tests/test_subject/__init__.py000066400000000000000000000000001404130727200203150ustar00rootroot00000000000000RxPY-3.2.0/tests/test_subject/test_asyncsubject.py000066400000000000000000000265111404130727200223310ustar00rootroot00000000000000import pytest from rx.testing import TestScheduler, ReactiveTest from rx.subject import AsyncSubject from rx.internal.exceptions import DisposedException on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class RxException(Exception): pass # Helper function for raising exceptions within lambdas def _raise(ex): raise RxException(ex) def test_infinite(): subject = [None] subscription = [None] subscription1 = [None] subscription2 = [None] subscription3 = [None] scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(70, 1), on_next(110, 2), on_next(220, 3), on_next(270, 4), on_next(340, 5), on_next(410, 6), on_next(520, 7), on_next(630, 8), on_next(710, 9), on_next(870, 10), on_next(940, 11), on_next(1020, 12) ) results1 = scheduler.create_observer() results2 = scheduler.create_observer() results3 = scheduler.create_observer() def action1(scheduler, state=None): subject[0] = AsyncSubject() scheduler.schedule_absolute(100, action1) def action2(scheduler, state=None): subscription[0] = xs.subscribe(subject[0]) scheduler.schedule_absolute(200, action2) def action3(scheduler, state=None): subscription[0].dispose() scheduler.schedule_absolute(1000, action3) def action4(scheduler, state=None): subscription1[0] = subject[0].subscribe(results1) scheduler.schedule_absolute(300, action4) def action5(scheduler, state=None): subscription2[0] = subject[0].subscribe(results2) scheduler.schedule_absolute(400, action5) def action6(scheduler, state=None): subscription3[0] = subject[0].subscribe(results3) scheduler.schedule_absolute(900, action6) def action7(scheduler, state=None): subscription1[0].dispose() scheduler.schedule_absolute(600, action7) def action8(scheduler, state=None): subscription2[0].dispose() scheduler.schedule_absolute(700, action8) def action9(scheduler, state=None): subscription1[0].dispose() scheduler.schedule_absolute(800, action9) def action10(scheduler, state=None): subscription3[0].dispose() scheduler.schedule_absolute(950, action10) scheduler.start() assert results1.messages == [] assert results2.messages == [] assert results3.messages == [] def test_finite(): subject = [None] subscription = [None] subscription1 = [None] subscription2 = [None] subscription3 = [None] scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(70, 1), on_next(110, 2), on_next(220, 3), on_next(270, 4), on_next(340, 5), on_next(410, 6), on_next(520, 7), on_completed(630), on_next(640, 9), on_completed(650), on_error(660, 'ex') ) results1 = scheduler.create_observer() results2 = scheduler.create_observer() results3 = scheduler.create_observer() def action1(scheduler, state=None): subject[0] = AsyncSubject() scheduler.schedule_absolute(100, action1) def action2(scheduler, state=None): subscription[0] = xs.subscribe(subject[0]) scheduler.schedule_absolute(200, action2) def action3(scheduler, state=None): subscription[0].dispose() scheduler.schedule_absolute(1000, action3) def action4(scheduler, state=None): subscription1[0] = subject[0].subscribe(results1) scheduler.schedule_absolute(300, action4) def action5(scheduler, state=None): subscription2[0] = subject[0].subscribe(results2) scheduler.schedule_absolute(400, action5) def action6(scheduler, state=None): subscription3[0] = subject[0].subscribe(results3) scheduler.schedule_absolute(900, action6) def action7(scheduler, state=None): subscription1[0].dispose() scheduler.schedule_absolute(600, action7) def action8(scheduler, state=None): subscription2[0].dispose() scheduler.schedule_absolute(700, action8) def action9(scheduler, state=None): subscription1[0].dispose() scheduler.schedule_absolute(800, action9) def action10(scheduler, state=None): subscription3[0].dispose() scheduler.schedule_absolute(950, action10) scheduler.start() assert results1.messages == [] assert results2.messages == [on_next(630, 7), on_completed(630)] assert results3.messages == [on_next(900, 7), on_completed(900)] def test_error(): subject = [None] subscription = [None] subscription1 = [None] subscription2 = [None] subscription3 = [None] ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(70, 1), on_next(110, 2), on_next(220, 3), on_next(270, 4), on_next(340, 5), on_next(410, 6), on_next(520, 7), on_error(630, ex), on_next(640, 9), on_completed(650), on_error(660, 'ex2') ) results1 = scheduler.create_observer() results2 = scheduler.create_observer() results3 = scheduler.create_observer() def action(scheduler, state=None): subject[0] = AsyncSubject() scheduler.schedule_absolute(100, action) def action1(scheduler, state=None): subscription[0] = xs.subscribe(subject[0]) scheduler.schedule_absolute(200, action1) def action2(scheduler, state=None): subscription[0].dispose() scheduler.schedule_absolute(1000, action2) def action3(scheduler, state=None): subscription1[0] = subject[0].subscribe(results1) scheduler.schedule_absolute(300, action3) def action4(scheduler, state=None): subscription2[0] = subject[0].subscribe(results2) scheduler.schedule_absolute(400, action4) def action5(scheduler, state=None): subscription3[0] = subject[0].subscribe(results3) scheduler.schedule_absolute(900, action5) def action6(scheduler, state=None): subscription1[0].dispose() scheduler.schedule_absolute(600, action6) def action7(scheduler, state=None): subscription2[0].dispose() scheduler.schedule_absolute(700, action7) def action8(scheduler, state=None): subscription1[0].dispose() scheduler.schedule_absolute(800, action8) def action9(scheduler, state=None): subscription3[0].dispose() scheduler.schedule_absolute(950, action9) scheduler.start() assert results1.messages == [] assert results2.messages == [on_error(630, ex)] assert results3.messages == [on_error(900, ex)] def test_canceled(): subject = [None] subscription = [None] subscription1 = [None] subscription2 = [None] subscription3 = [None] scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_completed(630), on_next(640, 9), on_completed(650), on_error(660, 'ex') ) results1 = scheduler.create_observer() results2 = scheduler.create_observer() results3 = scheduler.create_observer() def action1(scheduler, state=None): subject[0] = AsyncSubject() scheduler.schedule_absolute(100, action1) def action2(scheduler, state=None): subscription[0] = xs.subscribe(subject[0]) scheduler.schedule_absolute(200, action2) def action3(scheduler, state=None): subscription[0].dispose() scheduler.schedule_absolute(1000, action3) def action4(scheduler, state=None): subscription1[0] = subject[0].subscribe(results1) scheduler.schedule_absolute(300, action4) def action5(scheduler, state=None): subscription2[0] = subject[0].subscribe(results2) scheduler.schedule_absolute(400, action5) def action6(scheduler, state=None): subscription3[0] = subject[0].subscribe(results3) scheduler.schedule_absolute(900, action6) def action7(scheduler, state=None): subscription1[0].dispose() scheduler.schedule_absolute(600, action7) def action8(scheduler, state=None): subscription2[0].dispose() scheduler.schedule_absolute(700, action8) def action9(scheduler, state=None): subscription1[0].dispose() scheduler.schedule_absolute(800, action9) def action10(scheduler, state=None): subscription3[0].dispose() scheduler.schedule_absolute(950, action10) scheduler.start() assert results1.messages == [] assert results2.messages == [on_completed(630)] assert results3.messages == [on_completed(900)] def test_subject_disposed(): subject = [None] subscription1 = [None] subscription2 = [None] subscription3 = [None] scheduler = TestScheduler() results1 = scheduler.create_observer() results2 = scheduler.create_observer() results3 = scheduler.create_observer() def action1(scheduler, state=None): subject[0] = AsyncSubject() scheduler.schedule_absolute(100, action1) def action2(scheduler, state=None): subscription1[0] = subject[0].subscribe(results1) scheduler.schedule_absolute(200, action2) def action3(scheduler, state=None): subscription2[0] = subject[0].subscribe(results2) scheduler.schedule_absolute(300, action3) def action4(scheduler, state=None): subscription3[0] = subject[0].subscribe(results3) scheduler.schedule_absolute(400, action4) def action5(scheduler, state=None): subscription1[0].dispose() scheduler.schedule_absolute(500, action5) def action6(scheduler, state=None): subject[0].dispose() scheduler.schedule_absolute(600, action6) def action7(scheduler, state=None): subscription2[0].dispose() scheduler.schedule_absolute(700, action7) def action8(scheduler, state=None): subscription3[0].dispose() scheduler.schedule_absolute(800, action8) def action9(scheduler, state=None): subject[0].on_next(1) scheduler.schedule_absolute(150, action9) def action10(scheduler, state=None): subject[0].on_next(2) scheduler.schedule_absolute(250, action10) def action11(scheduler, state=None): subject[0].on_next(3) scheduler.schedule_absolute(350, action11) def action12(scheduler, state=None): subject[0].on_next(4) scheduler.schedule_absolute(450, action12) def action13(scheduler, state=None): subject[0].on_next(5) scheduler.schedule_absolute(550, action13) def action14(scheduler, state=None): with pytest.raises(DisposedException): subject[0].on_next(6) scheduler.schedule_absolute(650, action14) def action15(scheduler, state=None): with pytest.raises(DisposedException): subject[0].on_completed() scheduler.schedule_absolute(750, action15) def action16(scheduler, state=None): with pytest.raises(DisposedException): subject[0].on_error('ex') scheduler.schedule_absolute(850, action16) def action17(scheduler, state=None): with pytest.raises(DisposedException): subject[0].subscribe(None) scheduler.schedule_absolute(950, action17) scheduler.start() assert results1.messages == [] assert results2.messages == [] assert results3.messages == [] RxPY-3.2.0/tests/test_subject/test_behaviorsubject.py000066400000000000000000000305341404130727200230130ustar00rootroot00000000000000import pytest from rx.testing import TestScheduler, ReactiveTest from rx.subject import BehaviorSubject from rx.internal.exceptions import DisposedException on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class RxException(Exception): pass # Helper function for raising exceptions within lambdas def _raise(ex): raise RxException(ex) def test_infinite(): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(70, 1), on_next(110, 2), on_next(220, 3), on_next(270, 4), on_next(340, 5), on_next(410, 6), on_next(520, 7), on_next(630, 8), on_next(710, 9), on_next(870, 10), on_next(940, 11), on_next(1020, 12) ) subject = [None] subscription = [None] subscription1 = [None] subscription2 = [None] subscription3 = [None] results1 = scheduler.create_observer() results2 = scheduler.create_observer() results3 = scheduler.create_observer() def action1(scheduler, state=None): subject[0] = BehaviorSubject(100) scheduler.schedule_absolute(100, action1) def action2(scheduler, state=None): subscription[0] = xs.subscribe(subject[0]) scheduler.schedule_absolute(200, action2) def action3(scheduler, state=None): subscription[0].dispose() scheduler.schedule_absolute(1000, action3) def action4(scheduler, state=None): subscription1[0] = subject[0].subscribe(results1) scheduler.schedule_absolute(300, action4) def action5(scheduler, state=None): subscription2[0] = subject[0].subscribe(results2) scheduler.schedule_absolute(400, action5) def action6(scheduler, state=None): subscription3[0] = subject[0].subscribe(results3) scheduler.schedule_absolute(900, action6) def action7(scheduler, state=None): subscription1[0].dispose() scheduler.schedule_absolute(600, action7) def action8(scheduler, state=None): subscription2[0].dispose() scheduler.schedule_absolute(700, action8) def action9(scheduler, state=None): subscription1[0].dispose() scheduler.schedule_absolute(800, action9) def action10(scheduler, state=None): subscription3[0].dispose() scheduler.schedule_absolute(950, action10) scheduler.start() assert results1.messages == [ on_next(300, 4), on_next(340, 5), on_next(410, 6), on_next(520, 7)] assert results2.messages == [ on_next(400, 5), on_next(410, 6), on_next(520, 7), on_next(630, 8)] assert results3.messages == [ on_next(900, 10), on_next(940, 11)] def test_finite(): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(70, 1), on_next(110, 2), on_next(220, 3), on_next(270, 4), on_next(340, 5), on_next(410, 6), on_next(520, 7), on_completed(630), on_next(640, 9), on_completed(650), on_error(660, RxException()) ) subject = [None] subscription = [None] subscription1 = [None] subscription2 = [None] subscription3 = [None] results1 = scheduler.create_observer() results2 = scheduler.create_observer() results3 = scheduler.create_observer() def action1(scheduler, state=None): subject[0] = BehaviorSubject(100) scheduler.schedule_absolute(100, action1) def action2(scheduler, state=None): subscription[0] = xs.subscribe(subject[0]) scheduler.schedule_absolute(200, action2) def action3(scheduler, state=None): subscription[0].dispose() scheduler.schedule_absolute(1000, action3) def action4(scheduler, state=None): subscription1[0] = subject[0].subscribe(results1) scheduler.schedule_absolute(300, action4) def action5(scheduler, state=None): subscription2[0] = subject[0].subscribe(results2) scheduler.schedule_absolute(400, action5) def action6(scheduler, state=None): subscription3[0] = subject[0].subscribe(results3) scheduler.schedule_absolute(900, action6) def action7(scheduler, state=None): subscription1[0].dispose() scheduler.schedule_absolute(600, action7) def action8(scheduler, state=None): subscription2[0].dispose() scheduler.schedule_absolute(700, action8) def action9(scheduler, state=None): subscription1[0].dispose() scheduler.schedule_absolute(800, action9) def action10(scheduler, state=None): subscription3[0].dispose() scheduler.schedule_absolute(950, action10) scheduler.start() assert results1.messages == [ on_next(300, 4), on_next(340, 5), on_next(410, 6), on_next(520, 7)] assert results2.messages == [ on_next(400, 5), on_next(410, 6), on_next(520, 7), on_completed(630)] assert results3.messages == [ on_completed(900)] def test_error(): scheduler = TestScheduler() ex = RxException() xs = scheduler.create_hot_observable( on_next(70, 1), on_next(110, 2), on_next(220, 3), on_next(270, 4), on_next(340, 5), on_next(410, 6), on_next(520, 7), on_error(630, ex), on_next(640, 9), on_completed(650), on_error(660, RxException()) ) subject = [None] subscription = [None] subscription1 = [None] subscription2 = [None] subscription3 = [None] results1 = scheduler.create_observer() results2 = scheduler.create_observer() results3 = scheduler.create_observer() def action1(scheduler, state=None): subject[0] = BehaviorSubject(100) scheduler.schedule_absolute(100, action1) def action2(scheduler, state=None): subscription[0] = xs.subscribe(subject[0]) scheduler.schedule_absolute(200, action2) def action3(scheduler, state=None): subscription[0].dispose() scheduler.schedule_absolute(1000, action3) def action4(scheduler, state=None): subscription1[0] = subject[0].subscribe(results1) scheduler.schedule_absolute(300, action4) def action5(scheduler, state=None): subscription2[0] = subject[0].subscribe(results2) scheduler.schedule_absolute(400, action5) def action6(scheduler, state=None): subscription3[0] = subject[0].subscribe(results3) scheduler.schedule_absolute(900, action6) def action7(scheduler, state=None): subscription1[0].dispose() scheduler.schedule_absolute(600, action7) def action8(scheduler, state=None): subscription2[0].dispose() scheduler.schedule_absolute(700, action8) def action9(scheduler, state=None): subscription1[0].dispose() scheduler.schedule_absolute(800, action9) def action10(scheduler, state=None): subscription3[0].dispose() scheduler.schedule_absolute(950, action10) scheduler.start() assert results1.messages == [ on_next(300, 4), on_next(340, 5), on_next(410, 6), on_next(520, 7)] assert results2.messages == [ on_next(400, 5), on_next(410, 6), on_next(520, 7), on_error(630, ex)] assert results3.messages == [ on_error(900, ex)] def test_canceled(): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_completed(630), on_next(640, 9), on_completed(650), on_error(660, RxException()) ) subject = [None] subscription = [None] subscription1 = [None] subscription2 = [None] subscription3 = [None] results1 = scheduler.create_observer() results2 = scheduler.create_observer() results3 = scheduler.create_observer() def action1(scheduler, state=None): subject[0] = BehaviorSubject(100) scheduler.schedule_absolute(100, action1) def action2(scheduler, state=None): subscription[0] = xs.subscribe(subject[0]) scheduler.schedule_absolute(200, action2) def action3(scheduler, state=None): subscription[0].dispose() scheduler.schedule_absolute(1000, action3) def action4(scheduler, state=None): subscription1[0] = subject[0].subscribe(results1) scheduler.schedule_absolute(300, action4) def action5(scheduler, state=None): subscription2[0] = subject[0].subscribe(results2) scheduler.schedule_absolute(400, action5) def action6(scheduler, state=None): subscription3[0] = subject[0].subscribe(results3) scheduler.schedule_absolute(900, action6) def action7(scheduler, state=None): subscription1[0].dispose() scheduler.schedule_absolute(600, action7) def action8(scheduler, state=None): subscription2[0].dispose() scheduler.schedule_absolute(700, action8) def action9(scheduler, state=None): subscription1[0].dispose() scheduler.schedule_absolute(800, action9) def action10(scheduler, state=None): subscription3[0].dispose() scheduler.schedule_absolute(950, action10) scheduler.start() assert results1.messages == [ on_next(300, 100)] assert results2.messages == [ on_next(400, 100), on_completed(630)] assert results3.messages == [ on_completed(900)] def test_subject_disposed(): scheduler = TestScheduler() subject = [None] results1 = scheduler.create_observer() subscription1 = [None] results2 = scheduler.create_observer() subscription2 = [None] results3 = scheduler.create_observer() subscription3 = [None] def action1(scheduler, state=None): subject[0] = BehaviorSubject(0) scheduler.schedule_absolute(100, action1) def action2(scheduler, state=None): subscription1[0] = subject[0].subscribe(results1) scheduler.schedule_absolute(200, action2) def action3(scheduler, state=None): subscription2[0] = subject[0].subscribe(results2) scheduler.schedule_absolute(300, action3) def action4(scheduler, state=None): subscription3[0] = subject[0].subscribe(results3) scheduler.schedule_absolute(400, action4) def action5(scheduler, state=None): subscription1[0].dispose() scheduler.schedule_absolute(500, action5) def action6(scheduler, state=None): subject[0].dispose() scheduler.schedule_absolute(600, action6) def action7(scheduler, state=None): subscription2[0].dispose() scheduler.schedule_absolute(700, action7) def action8(scheduler, state=None): subscription3[0].dispose() scheduler.schedule_absolute(800, action8) def action9(scheduler, state=None): subject[0].on_next(1) scheduler.schedule_absolute(150, action9) def action10(scheduler, state=None): subject[0].on_next(2) scheduler.schedule_absolute(250, action10) def action11(scheduler, state=None): subject[0].on_next(3) scheduler.schedule_absolute(350, action11) def action12(scheduler, state=None): subject[0].on_next(4) scheduler.schedule_absolute(450, action12) def action13(scheduler, state=None): subject[0].on_next(5) scheduler.schedule_absolute(550, action13) def action14(scheduler, state=None): with pytest.raises(DisposedException): subject[0].on_next(6) scheduler.schedule_absolute(650, action14) def action15(scheduler, state=None): with pytest.raises(DisposedException): subject[0].on_completed() scheduler.schedule_absolute(750, action15) def action16(scheduler, state=None): with pytest.raises(DisposedException): subject[0].on_error(RxException()) scheduler.schedule_absolute(850, action16) def action17(scheduler, state=None): with pytest.raises(DisposedException): subject[0].subscribe(None) scheduler.schedule_absolute(950, action17) scheduler.start() assert results1.messages == [ on_next(200, 1), on_next(250, 2), on_next(350, 3), on_next(450, 4)] assert results2.messages == [ on_next(300, 2), on_next(350, 3), on_next(450, 4), on_next(550, 5)] assert results3.messages == [ on_next(400, 3), on_next(450, 4), on_next(550, 5)] RxPY-3.2.0/tests/test_subject/test_replaysubject.py000066400000000000000000000412531404130727200225100ustar00rootroot00000000000000import sys import pytest from rx.testing import TestScheduler, ReactiveTest from rx.subject import ReplaySubject from rx.internal.exceptions import DisposedException on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class RxException(Exception): pass # Helper function for raising exceptions within lambdas def _raise(ex): raise RxException(ex) def test_infinite(): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(70, 1), on_next(110, 2), on_next(220, 3), on_next(270, 4), on_next(340, 5), on_next(410, 6), on_next(520, 7), on_next(630, 8), on_next(710, 9), on_next(870, 10), on_next(940, 11), on_next(1020, 12) ) subject = [None] subscription = [None] subscription1 = [None] subscription2 = [None] subscription3 = [None] results1 = scheduler.create_observer() results2 = scheduler.create_observer() results3 = scheduler.create_observer() def action1(scheduler, state=None): subject[0] = ReplaySubject(3, 100, scheduler) scheduler.schedule_absolute(100, action1) def action2(scheduler, state=None): subscription[0] = xs.subscribe(subject[0]) scheduler.schedule_absolute(200, action2) def action3(scheduler, state=None): subscription[0].dispose() scheduler.schedule_absolute(1000, action3) def action4(scheduler, state=None): subscription1[0] = subject[0].subscribe(results1) scheduler.schedule_absolute(300, action4) def action5(scheduler, state=None): subscription2[0] = subject[0].subscribe(results2) scheduler.schedule_absolute(400, action5) def action6(scheduler, state=None): subscription3[0] = subject[0].subscribe(results3) scheduler.schedule_absolute(900, action6) def action7(scheduler, state=None): subscription1[0].dispose() scheduler.schedule_absolute(600, action7) def action8(scheduler, state=None): subscription2[0].dispose() scheduler.schedule_absolute(700, action8) def action9(scheduler, state=None): subscription1[0].dispose() scheduler.schedule_absolute(800, action9) def action10(scheduler, state=None): subscription3[0].dispose() scheduler.schedule_absolute(950, action10) scheduler.start() assert results1.messages == [ on_next(300, 3), on_next(300, 4), on_next(340, 5), on_next(410, 6), on_next(520, 7)] assert results2.messages == [ on_next(400, 5), on_next(410, 6), on_next(520, 7), on_next(630, 8)] assert results3.messages == [ on_next(900, 10), on_next(940, 11)] def test_infinite2(): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(70, 1), on_next(110, 2), on_next(220, 3), on_next(270, 4), on_next(280, -1), on_next(290, -2), on_next(340, 5), on_next(410, 6), on_next(520, 7), on_next(630, 8), on_next(710, 9), on_next(870, 10), on_next(940, 11), on_next(1020, 12) ) subject = [None] subscription = [None] subscription1 = [None] subscription2 = [None] subscription3 = [None] results1 = scheduler.create_observer() results2 = scheduler.create_observer() results3 = scheduler.create_observer() def action1(scheduler, state=None): subject[0] = ReplaySubject(3, 100, scheduler) scheduler.schedule_absolute(100, action1) def action2(scheduler, state=None): subscription[0] = xs.subscribe(subject[0]) scheduler.schedule_absolute(200, action2) def action3(scheduler, state=None): subscription[0].dispose() scheduler.schedule_absolute(1000, action3) def action4(scheduler, state=None): subscription1[0] = subject[0].subscribe(results1) scheduler.schedule_absolute(300, action4) def action5(scheduler, state=None): subscription2[0] = subject[0].subscribe(results2) scheduler.schedule_absolute(400, action5) def action6(scheduler, state=None): subscription3[0] = subject[0].subscribe(results3) scheduler.schedule_absolute(900, action6) def action7(scheduler, state=None): subscription1[0].dispose() scheduler.schedule_absolute(600, action7) def action8(scheduler, state=None): subscription2[0].dispose() scheduler.schedule_absolute(700, action8) def action9(scheduler, state=None): subscription1[0].dispose() scheduler.schedule_absolute(800, action9) def action10(scheduler, state=None): subscription3[0].dispose() scheduler.schedule_absolute(950, action10) scheduler.start() assert results1.messages == [ on_next(300, 4), on_next(300, -1), on_next(300, -2), on_next(340, 5), on_next(410, 6), on_next(520, 7)] assert results2.messages == [ on_next(400, 5), on_next(410, 6), on_next(520, 7), on_next(630, 8)] assert results3.messages == [ on_next(900, 10), on_next(940, 11)] def test_finite(): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(70, 1), on_next(110, 2), on_next(220, 3), on_next(270, 4), on_next(340, 5), on_next(410, 6), on_next(520, 7), on_completed(630), on_next(640, 9), on_completed(650), on_error(660, 'ex') ) subject = [None] subscription = [None] subscription1 = [None] subscription2 = [None] subscription3 = [None] results1 = scheduler.create_observer() results2 = scheduler.create_observer() results3 = scheduler.create_observer() def action1(scheduler, state=None): subject[0] = ReplaySubject(3, 100, scheduler) scheduler.schedule_absolute(100, action1) def action3(scheduler, state=None): subscription[0] = xs.subscribe(subject[0]) scheduler.schedule_absolute(200, action3) def action4(scheduler, state=None): subscription[0].dispose() scheduler.schedule_absolute(1000, action4) def action5(scheduler, state=None): subscription1[0] = subject[0].subscribe(results1) scheduler.schedule_absolute(300, action5) def action6(scheduler, state=None): subscription2[0] = subject[0].subscribe(results2) scheduler.schedule_absolute(400, action6) def action7(scheduler, state=None): subscription3[0] = subject[0].subscribe(results3) scheduler.schedule_absolute(900, action7) def action8(scheduler, state=None): subscription1[0].dispose() scheduler.schedule_absolute(600, action8) def action9(scheduler, state=None): subscription2[0].dispose() scheduler.schedule_absolute(700, action9) def action10(scheduler, state=None): subscription1[0].dispose() scheduler.schedule_absolute(800, action10) def action11(scheduler, state=None): subscription3[0].dispose() scheduler.schedule_absolute(950, action11) scheduler.start() assert results1.messages == [ on_next(300, 3), on_next(300, 4), on_next(340, 5), on_next(410, 6), on_next(520, 7)] assert results2.messages == [ on_next(400, 5), on_next(410, 6), on_next(520, 7), on_completed(630)] assert results3.messages == [ on_completed(900)] def test_error(): scheduler = TestScheduler() ex = RxException('ex') xs = scheduler.create_hot_observable( on_next(70, 1), on_next(110, 2), on_next(220, 3), on_next(270, 4), on_next(340, 5), on_next(410, 6), on_next(520, 7), on_error(630, ex), on_next(640, 9), on_completed(650), on_error(660, RxException('ex')) ) subject = [None] subscription = [None] subscription1 = [None] subscription2 = [None] subscription3 = [None] results1 = scheduler.create_observer() results2 = scheduler.create_observer() results3 = scheduler.create_observer() def action1(scheduler, state=None): subject[0] = ReplaySubject(3, 100, scheduler) scheduler.schedule_absolute(100, action1) def action2(scheduler, state=None): subscription[0] = xs.subscribe(subject[0]) scheduler.schedule_absolute(200, action2) def action3(scheduler, state=None): subscription[0].dispose() scheduler.schedule_absolute(1000, action3) def action4(scheduler, state=None): subscription1[0] = subject[0].subscribe(results1) scheduler.schedule_absolute(300, action4) def action5(scheduler, state=None): subscription2[0] = subject[0].subscribe(results2) scheduler.schedule_absolute(400, action5) def action6(scheduler, state=None): subscription3[0] = subject[0].subscribe(results3) scheduler.schedule_absolute(900, action6) def action7(scheduler, state=None): subscription1[0].dispose() scheduler.schedule_absolute(600, action7) def action8(scheduler, state=None): subscription2[0].dispose() scheduler.schedule_absolute(700, action8) def action9(scheduler, state=None): subscription1[0].dispose() scheduler.schedule_absolute(800, action9) def action10(scheduler, state=None): subscription3[0].dispose() scheduler.schedule_absolute(950, action10) scheduler.start() assert results1.messages == [ on_next(300, 3), on_next(300, 4), on_next(340, 5), on_next(410, 6), on_next(520, 7)] assert results2.messages == [ on_next(400, 5), on_next(410, 6), on_next(520, 7), on_error(630, ex)] assert results3.messages == [ on_error(900, ex)] def test_canceled(): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_completed(630), on_next(640, 9), on_completed(650), on_error(660, RxException()) ) subject = [None] subscription = [None] subscription1 = [None] subscription2 = [None] subscription3 = [None] results1 = scheduler.create_observer() results2 = scheduler.create_observer() results3 = scheduler.create_observer() def action1(scheduler, state=None): subject[0] = ReplaySubject(3, 100, scheduler) scheduler.schedule_absolute(100, action1) def action2(scheduler, state=None): subscription[0] = xs.subscribe(subject[0]) scheduler.schedule_absolute(200, action2) def action3(scheduler, state=None): subscription[0].dispose() scheduler.schedule_absolute(1000, action3) def action4(scheduler, state=None): subscription1[0] = subject[0].subscribe(results1) scheduler.schedule_absolute(300, action4) def action5(scheduler, state=None): subscription2[0] = subject[0].subscribe(results2) scheduler.schedule_absolute(400, action5) def action6(scheduler, state=None): subscription3[0] = subject[0].subscribe(results3) scheduler.schedule_absolute(900, action6) def action7(scheduler, state=None): subscription1[0].dispose() scheduler.schedule_absolute(600, action7) def action8(scheduler, state=None): subscription2[0].dispose() scheduler.schedule_absolute(700, action8) def action9(scheduler, state=None): subscription1[0].dispose() scheduler.schedule_absolute(800, action9) def action10(scheduler, state=None): subscription3[0].dispose() scheduler.schedule_absolute(950, action10) scheduler.start() assert results1.messages == [] assert results2.messages == [ on_completed(630)] assert results3.messages == [ on_completed(900)] def test_subject_disposed(): scheduler = TestScheduler() subject = [None] subscription1 = [None] subscription2 = [None] subscription3 = [None] results1 = scheduler.create_observer() results2 = scheduler.create_observer() results3 = scheduler.create_observer() def action1(scheduler, state=None): subject[0] = ReplaySubject(scheduler=scheduler) scheduler.schedule_absolute(100, action1) def action2(scheduler, state=None): subscription1[0] = subject[0].subscribe(results1) scheduler.schedule_absolute(200, action2) def action3(scheduler, state=None): subscription2[0] = subject[0].subscribe(results2) scheduler.schedule_absolute(300, action3) def action4(scheduler, state=None): subscription3[0] = subject[0].subscribe(results3) scheduler.schedule_absolute(400, action4) def action5(scheduler, state=None): subscription1[0].dispose() scheduler.schedule_absolute(500, action5) def action6(scheduler, state=None): subject[0].dispose() scheduler.schedule_absolute(600, action6) def action7(scheduler, state=None): subscription2[0].dispose() scheduler.schedule_absolute(700, action7) def action8(scheduler, state=None): subscription3[0].dispose() scheduler.schedule_absolute(800, action8) def action9(scheduler, state=None): subject[0].on_next(1) scheduler.schedule_absolute(150, action9) def action10(scheduler, state=None): subject[0].on_next(2) scheduler.schedule_absolute(250, action10) def action11(scheduler, state=None): subject[0].on_next(3) scheduler.schedule_absolute(350, action11) def action12(scheduler, state=None): subject[0].on_next(4) scheduler.schedule_absolute(450, action12) def action13(scheduler, state=None): subject[0].on_next(5) scheduler.schedule_absolute(550, action13) def action14(scheduler, state=None): with pytest.raises(DisposedException): subject[0].on_next(6) scheduler.schedule_absolute(650, action14) def action15(scheduler, state=None): with pytest.raises(DisposedException): subject[0].on_completed() scheduler.schedule_absolute(750, action15) def action16(scheduler, state=None): with pytest.raises(DisposedException): subject[0].on_error(Exception()) scheduler.schedule_absolute(850, action16) def action17(scheduler, state=None): with pytest.raises(DisposedException): subject[0].subscribe(None) scheduler.schedule_absolute(950, action17) scheduler.start() assert results1.messages == [ on_next(200, 1), on_next(250, 2), on_next(350, 3), on_next(450, 4)] assert results2.messages == [ on_next(300, 1), on_next(300, 2), on_next(350, 3), on_next(450, 4), on_next(550, 5)] assert results3.messages == [ on_next(400, 1), on_next(400, 2), on_next(400, 3), on_next(450, 4), on_next(550, 5)] def test_replay_subject_dies_out(): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(70, 1), on_next(110, 2), on_next(220, 3), on_next(270, 4), on_next(340, 5), on_next(410, 6), on_next(520, 7), on_completed(580) ) subject = [None] results1 = scheduler.create_observer() results2 = scheduler.create_observer() results3 = scheduler.create_observer() results4 = scheduler.create_observer() def action1(scheduler, state=None): subject[0] = ReplaySubject(sys.maxsize, 100, scheduler) scheduler.schedule_absolute(100, action1) def action2(scheduler, state=None): xs.subscribe(subject[0]) scheduler.schedule_absolute(200, action2) def action3(scheduler, state=None): subject[0].subscribe(results1) scheduler.schedule_absolute(300, action3) def action4(scheduler, state=None): subject[0].subscribe(results2) scheduler.schedule_absolute(400, action4) def action5(scheduler, state=None): subject[0].subscribe(results3) scheduler.schedule_absolute(600, action5) def action6(scheduler, state=None): subject[0].subscribe(results4) scheduler.schedule_absolute(900, action6) scheduler.start() assert results1.messages == [ on_next(300, 3), on_next(300, 4), on_next(340, 5), on_next(410, 6), on_next(520, 7), on_completed(580)] assert results2.messages == [ on_next(400, 5), on_next(410, 6), on_next(520, 7), on_completed(580)] assert results3.messages == [ on_next(600, 7), on_completed(600)] assert results4.messages == [ on_completed(900)] RxPY-3.2.0/tests/test_subject/test_subject.py000066400000000000000000000214221404130727200212670ustar00rootroot00000000000000from rx.testing import TestScheduler, ReactiveTest from rx.subject import Subject on_next = ReactiveTest.on_next on_completed = ReactiveTest.on_completed on_error = ReactiveTest.on_error subscribe = ReactiveTest.subscribe subscribed = ReactiveTest.subscribed disposed = ReactiveTest.disposed created = ReactiveTest.created class RxException(Exception): pass # Helper function for raising exceptions within lambdas def _raise(ex): raise RxException(ex) def test_infinite(): subscription = [None] subscription1 = [None] subscription2 = [None] subscription3 = [None] s = [None] scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(70, 1), on_next(110, 2), on_next(220, 3), on_next(270, 4), on_next(340, 5), on_next(410, 6), on_next(520, 7), on_next(630, 8), on_next(710, 9), on_next(870, 10), on_next(940, 11), on_next(1020, 12) ) results1 = scheduler.create_observer() results2 = scheduler.create_observer() results3 = scheduler.create_observer() def action1(scheduler, state=None): s[0] = Subject() scheduler.schedule_absolute(100, action1) def action2(scheduler, state=None): subscription[0] = xs.subscribe(s[0]) scheduler.schedule_absolute(200, action2) def action3(scheduler, state=None): subscription[0].dispose() scheduler.schedule_absolute(1000, action3) def action4(scheduler, state=None): subscription1[0] = s[0].subscribe(results1) scheduler.schedule_absolute(300, action4) def action5(scheduler, state=None): subscription2[0] = s[0].subscribe(results2) scheduler.schedule_absolute(400, action5) def action6(scheduler, state=None): subscription3[0] = s[0].subscribe(results3) scheduler.schedule_absolute(900, action6) def action7(scheduler, state=None): subscription1[0].dispose() scheduler.schedule_absolute(600, action7) def action8(scheduler, state=None): subscription2[0].dispose() scheduler.schedule_absolute(700, action8) def action9(scheduler, state=None): subscription1[0].dispose() scheduler.schedule_absolute(800, action9) def action10(scheduler, state=None): subscription3[0].dispose() scheduler.schedule_absolute(950, action10) scheduler.start() assert results1.messages == [ on_next(340, 5), on_next(410, 6), on_next(520, 7)] assert results2.messages == [ on_next(410, 6), on_next(520, 7), on_next(630, 8)] assert results3.messages == [ on_next(940, 11)] def test_finite(): scheduler = TestScheduler() subscription = [None] subscription1 = [None] subscription2 = [None] subscription3 = [None] s = [None] xs = scheduler.create_hot_observable( on_next(70, 1), on_next(110, 2), on_next(220, 3), on_next(270, 4), on_next(340, 5), on_next(410, 6), on_next(520, 7), on_completed(630), on_next(640, 9), on_completed(650), on_error(660, 'error') ) results1 = scheduler.create_observer() results2 = scheduler.create_observer() results3 = scheduler.create_observer() def action1(scheduler, state=None): s[0] = Subject() scheduler.schedule_absolute(100, action1) def action2(scheduler, state=None): subscription[0] = xs.subscribe(s[0]) scheduler.schedule_absolute(200, action2) def action3(scheduler, state=None): subscription[0].dispose() scheduler.schedule_absolute(1000, action3) def action4(scheduler, state=None): subscription1[0] = s[0].subscribe(results1) scheduler.schedule_absolute(300, action4) def action5(scheduler, state=None): subscription2[0] = s[0].subscribe(results2) scheduler.schedule_absolute(400, action5) def action6(scheduler, state=None): subscription3[0] = s[0].subscribe(results3) scheduler.schedule_absolute(900, action6) def action7(scheduler, state=None): subscription1[0].dispose() scheduler.schedule_absolute(600, action7) def action8(scheduler, state=None): subscription2[0].dispose() scheduler.schedule_absolute(700, action8) def action9(scheduler, state=None): subscription1[0].dispose() scheduler.schedule_absolute(800, action9) def action10(scheduler, state=None): subscription3[0].dispose() scheduler.schedule_absolute(950, action10) scheduler.start() assert results1.messages == [ on_next(340, 5), on_next(410, 6), on_next(520, 7)] assert results2.messages == [ on_next(410, 6), on_next(520, 7), on_completed(630)] assert results3.messages == [ on_completed(900)] def test_error(): s = [None] subscription = [None] subscription1 = [None] subscription2 = [None] subscription3 = [None] ex = 'ex' scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(70, 1), on_next(110, 2), on_next(220, 3), on_next(270, 4), on_next(340, 5), on_next(410, 6), on_next(520, 7), on_error(630, ex), on_next(640, 9), on_completed(650), on_error(660, 'foo') ) results1 = scheduler.create_observer() results2 = scheduler.create_observer() results3 = scheduler.create_observer() def action(scheduler, state=None): s[0] = Subject() scheduler.schedule_absolute(100, action) def action1(scheduler, state=None): subscription[0] = xs.subscribe(s[0]) scheduler.schedule_absolute(200, action1) def action2(scheduler, state=None): subscription[0].dispose() scheduler.schedule_absolute(1000, action2) def action3(scheduler, state=None): subscription1[0] = s[0].subscribe(results1) scheduler.schedule_absolute(300, action3) def action4(scheduler, state=None): subscription2[0] = s[0].subscribe(results2) scheduler.schedule_absolute(400, action4) def action5(scheduler, state=None): subscription3[0] = s[0].subscribe(results3) scheduler.schedule_absolute(900, action5) def action6(scheduler, state=None): subscription1[0].dispose() scheduler.schedule_absolute(600, action6) def action7(scheduler, state=None): subscription2[0].dispose() scheduler.schedule_absolute(700, action7) def action8(scheduler, state=None): subscription1[0].dispose() scheduler.schedule_absolute(800, action8) def action9(scheduler, state=None): subscription3[0].dispose() scheduler.schedule_absolute(950, action9) scheduler.start() assert results1.messages == [on_next(340, 5), on_next(410, 6), on_next(520, 7)] assert results2.messages == [on_next(410, 6), on_next(520, 7), on_error(630, ex)] assert results3.messages == [on_error(900, ex)] def test_canceled(): s = [None] subscription = [None] subscription1 = [None] subscription2 = [None] subscription3 = [None] scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_completed(630), on_next(640, 9), on_completed(650), on_error(660, 'ex') ) results1 = scheduler.create_observer() results2 = scheduler.create_observer() results3 = scheduler.create_observer() def action1(scheduler, state=None): s[0] = Subject() scheduler.schedule_absolute(100, action1) def action2(scheduler, state=None): subscription[0] = xs.subscribe(s[0]) scheduler.schedule_absolute(200, action2) def action3(scheduler, state=None): subscription[0].dispose() scheduler.schedule_absolute(1000, action3) def action4(scheduler, state=None): subscription1[0] = s[0].subscribe(results1) scheduler.schedule_absolute(300, action4) def action5(scheduler, state=None): subscription2[0] = s[0].subscribe(results2) scheduler.schedule_absolute(400, action5) def action6(scheduler, state=None): subscription3[0] = s[0].subscribe(results3) scheduler.schedule_absolute(900, action6) def action7(scheduler, state=None): subscription1[0].dispose() scheduler.schedule_absolute(600, action7) def action8(scheduler, state=None): subscription2[0].dispose() scheduler.schedule_absolute(700, action8) def action9(scheduler, state=None): subscription1[0].dispose() scheduler.schedule_absolute(800, action9) def action10(scheduler, state=None): subscription3[0].dispose() scheduler.schedule_absolute(950, action10) scheduler.start() assert results1.messages == [] assert results2.messages == [on_completed(630)] assert results3.messages == [on_completed(900)] RxPY-3.2.0/tests/test_testing/000077500000000000000000000000001404130727200162345ustar00rootroot00000000000000RxPY-3.2.0/tests/test_testing/__init__.py000066400000000000000000000000001404130727200203330ustar00rootroot00000000000000RxPY-3.2.0/tests/test_testing/test_marbles.py000066400000000000000000000140311404130727200212710ustar00rootroot00000000000000import unittest from rx.testing.marbles import marbles_testing from rx.testing.reactivetest import ReactiveTest #from rx.scheduler import timeout_scheduler, new_thread_scheduler # marble sequences to test: #tested_marbles = '0-1-(10)|', '0|', '(10)-(20)|', '(abc)-|' # class TestFromToMarbles(unittest.TestCase): # def test_new_thread_scheduler(self): # stream = Observable.from_marbles(marbles) # result = stream.to_blocking().to_marbles() # self.assertEqual(result, expected) # 'this is the default scheduler' # self._run_test(tested_marbles, new_thread_scheduler) # def test_timeout_scheduler(self): # self._run_test(tested_marbles, timeout_scheduler) # def test_timeout_new_thread_scheduler(self): # self._run_test(tested_marbles, timeout_scheduler, new_thread_scheduler) # def test_new_thread_scheduler_timeout(self): # self._run_test(tested_marbles, new_thread_scheduler, timeout_scheduler) # def test_timeout_testscheduler(self): # '''the test scheduler uses virtual time => `to_marbles` does not # see the original delays. # ''' # expected = [t.replace('-', '') for t in tested_marbles] # self._run_test(expected, timeout_scheduler, TestScheduler()) # def test_newthread_testscheduler(self): # '''the test scheduler uses virtual time => `to_marbles` does not # see the original delays. # ''' # expected = [t.replace('-', '') for t in tested_marbles] # self._run_test(expected, new_thread_scheduler, TestScheduler()) class TestTestContext(unittest.TestCase): def test_start_with_cold_never(self): with marbles_testing() as (start, cold, hot, exp): obs = cold("----") " 012345678901234567890" def create(): return obs results = start(create) expected = [] assert results == expected def test_start_with_cold_empty(self): with marbles_testing() as (start, cold, hot, exp): obs = cold("------|") " 012345678901234567890" def create(): return obs results = start(create) expected = [ReactiveTest.on_completed(206)] assert results == expected def test_start_with_cold_normal(self): with marbles_testing() as (start, cold, hot, exp): obs = cold("12--3-|") " 012345678901234567890" def create(): return obs results = start(create) expected = [ ReactiveTest.on_next(200.0, 12), ReactiveTest.on_next(204.0, 3), ReactiveTest.on_completed(206.0), ] assert results == expected def test_start_with_cold_no_create_function(self): with marbles_testing() as (start, cold, hot, exp): obs = cold("12--3-|") " 012345678901234567890" results = start(obs) expected = [ ReactiveTest.on_next(200.0, 12), ReactiveTest.on_next(204.0, 3), ReactiveTest.on_completed(206.0), ] assert results == expected def test_start_with_hot_never(self): with marbles_testing() as (start, cold, hot, exp): obs = hot("------") " 012345678901234567890" def create(): return obs results = start(create) expected = [] assert results == expected def test_start_with_hot_empty(self): with marbles_testing() as (start, cold, hot, exp): obs = hot("---|") " 012345678901234567890" def create(): return obs results = start(create) expected = [ReactiveTest.on_completed(203.0), ] assert results == expected def test_start_with_hot_normal(self): with marbles_testing() as (start, cold, hot, exp): obs = hot("-12--3-|") " 012345678901234567890" def create(): return obs results = start(create) expected = [ ReactiveTest.on_next(201.0, 12), ReactiveTest.on_next(205.0, 3), ReactiveTest.on_completed(207.0), ] assert results == expected def test_exp(self): with marbles_testing() as (start, cold, hot, exp): results = exp("12--3--4--5-|") " 012345678901234567890" expected = [ ReactiveTest.on_next(200.0, 12), ReactiveTest.on_next(204.0, 3), ReactiveTest.on_next(207.0, 4), ReactiveTest.on_next(210.0, 5), ReactiveTest.on_completed(212.0), ] assert results == expected def test_start_with_hot_and_exp(self): with marbles_testing() as (start, cold, hot, exp): obs = hot(" --3--4--5-|") expected = exp("--3--4--5-|") " 012345678901234567890" def create(): return obs results = start(create) assert results == expected def test_start_with_cold_and_exp(self): with marbles_testing() as (start, cold, hot, exp): obs = cold(" 12--3--4--5-|") expected = exp(" 12--3--4--5-|") " 012345678901234567890" def create(): return obs results = start(create) assert results == expected def test_start_with_cold_and_exp_group(self): with marbles_testing() as (start, cold, hot, exp): obs = cold(" 12--(3,6.5)----(5,#)") expected = exp(" 12--(3,6.5)----(5,#)") " 012345678901234567890" def create(): return obs results = start(create) assert results == expected RxPY-3.2.0/tests/test_version.py000066400000000000000000000014461404130727200166230ustar00rootroot00000000000000import unittest from configparser import ConfigParser from os.path import abspath, dirname, join from re import match class VersionTest(unittest.TestCase): def test_version(self): root = abspath(join(dirname(__file__), '..')) with open(join(root, 'project.cfg')) as project_file: config = ConfigParser() config.read_file(project_file) project_meta = dict(config.items('project')) version = None with open(join(root, 'rx', '__init__.py')) as init_py: for line in init_py: version = match('\\s*__version__\\s*=\\s*[\'"](.*)[\'"]', line) if version is not None: version = version.group(1) break assert project_meta['version'] == version