pax_global_header00006660000000000000000000000064142644617540014527gustar00rootroot0000000000000052 comment=7187453c1a829c3f7ceeadae722f2d1d13f6b51e RxPY-4.0.4/000077500000000000000000000000001426446175400123765ustar00rootroot00000000000000RxPY-4.0.4/.coveragerc000066400000000000000000000003011426446175400145110ustar00rootroot00000000000000[report] omit = */python?.?/* */site-packages/nose/* exclude_lines = pragma: no cover return NotImplemented raise NotImplementedError \.\.\. [xml] output = coverage.xml RxPY-4.0.4/.flake8000066400000000000000000000001401426446175400135440ustar00rootroot00000000000000[flake8] ignore = D203,W503 exclude = .git,__pycache__,docs/source/conf.py,old,build,dist,tests RxPY-4.0.4/.gitattributes000066400000000000000000000007171426446175400152760ustar00rootroot00000000000000# 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-4.0.4/.github/000077500000000000000000000000001426446175400137365ustar00rootroot00000000000000RxPY-4.0.4/.github/ISSUE_TEMPLATE/000077500000000000000000000000001426446175400161215ustar00rootroot00000000000000RxPY-4.0.4/.github/ISSUE_TEMPLATE/bug_report.md000066400000000000000000000012101426446175400206050ustar00rootroot00000000000000--- name: Bug report about: Create a report to help us improve title: '' labels: '' assignees: '' --- **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior: **Expected behavior** A clear and concise description of what you expected to happen. **Code or Screenshots** If applicable, add a minimal and self contained code example or screenshots to help explain your problem. ```python def foo(self) -> str: return 3 ``` **Additional context** Add any other context about the problem here. - OS [e.g. Windows] - RxPY version [e.g 4.0.0] - Python version [e.g. 3.10.2] RxPY-4.0.4/.github/ISSUE_TEMPLATE/feature_request.md000066400000000000000000000012741426446175400216520ustar00rootroot00000000000000--- name: Feature request about: Suggest an idea for this project title: '' labels: '' assignees: '' --- **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] **Describe the solution you'd like** A clear and concise description of what you want to happen, and also include a minimal code example if applicable. ```python def foo(self) -> str: return 3 ``` **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered. **Additional context** Add any other context or screenshots about the feature request here. RxPY-4.0.4/.github/lock.yml000066400000000000000000000021771426446175400154200ustar00rootroot00000000000000# 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-4.0.4/.github/workflows/000077500000000000000000000000001426446175400157735ustar00rootroot00000000000000RxPY-4.0.4/.github/workflows/python-package.yml000066400000000000000000000045221426446175400214330ustar00rootroot00000000000000name: Python package on: [push, pull_request] jobs: code-quality: 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: Cache Poetry uses: actions/cache@v2 with: path: | ~/.cache/pypoetry key: poetry-cache-${{ runner.os }}-${{ hashFiles('**/poetry.lock') }} - name: Install dependencies run: | pip install poetry poetry install - name: Code checks run: | poetry run pre-commit run --all-files --show-diff-on-failure build: strategy: matrix: platform: [ubuntu-latest, macos-latest, windows-latest] python-version: [3.7, 3.8, 3.9, "3.10", pypy-3.8] 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: Cache Poetry uses: actions/cache@v2 with: path: | ~/.cache/pypoetry ~/Library/Caches/pypoetry C:\Users\*\AppData\Local\pypoetry\Cache key: poetry-cache-${{ runner.os }}-${{ hashFiles('**/poetry.lock') }} - name: Install dependencies run: | pip install poetry poetry config installer.parallel false poetry install - name: Test with pytest run: | poetry run pytest -n auto 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: Cache Poetry uses: actions/cache@v2 with: path: | ~/.cache/pypoetry key: poetry-cache-${{ runner.os }}-${{ hashFiles('**/poetry.lock') }} - name: Install dependencies run: | pip install poetry poetry install - name: Run coverage tests run: | poetry run coverage run -m pytest - name: Coveralls env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} COVERALLS_SERVICE_NAME: github run: | poetry run coveralls RxPY-4.0.4/.github/workflows/python-publish.yml000066400000000000000000000015771426446175400215150ustar00rootroot00000000000000name: Publish Package on: release: types: [created] jobs: publish: runs-on: ubuntu-latest name: "Publish library" steps: - name: Check out uses: actions/checkout@v3 with: token: "${{ secrets.GITHUB_TOKEN }}" fetch-depth: 0 - name: Setup Python Env uses: actions/setup-python@v3 with: python-version: '3.9' - name: Install dependencies run: pip install poetry dunamai - name: Set version run: | RX_VERSION=$(dunamai from any --no-metadata --style pep440) poetry version $RX_VERSION echo "__version__ = \"$RX_VERSION\"" > reactivex/_version.py - name: Build package run: poetry build - name: Release to PyPI run: | poetry publish -u __token__ -p ${{ secrets.PYPI_API_TOKEN }} || echo 'Version exists'RxPY-4.0.4/.gitignore000066400000000000000000000013741426446175400143730ustar00rootroot00000000000000*.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 .ionide/ .python-version __pycache__RxPY-4.0.4/.pre-commit-config.yaml000066400000000000000000000016261426446175400166640ustar00rootroot00000000000000repos: - hooks: - args: - --remove-all-unused-imports - --in-place id: autoflake repo: https://github.com/humitos/mirrors-autoflake rev: v1.1 - hooks: - id: isort repo: https://github.com/timothycrosley/isort rev: 5.10.1 - hooks: - id: black repo: https://github.com/psf/black rev: 22.3.0 - hooks: - id: flake8 exclude: (^docs/|^examples/|^notebooks/|^tests/) repo: https://gitlab.com/pycqa/flake8 rev: 3.9.2 - hooks: - id: pyright name: pyright entry: pyright language: node pass_filenames: false types: [python] additional_dependencies: ["pyright@1.1.254"] repo: local - hooks: - id: mypy exclude: (^docs/|^examples/|^notebooks/|^tests/|^reactivex/operators/_\w.*\.py$) repo: https://github.com/pre-commit/mirrors-mypy rev: v0.942 RxPY-4.0.4/.pylintrc000066400000000000000000000002071426446175400142420ustar00rootroot00000000000000[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-4.0.4/LICENSE000066400000000000000000000021301426446175400133770ustar00rootroot00000000000000# The MIT License Copyright 2013-2022, 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-4.0.4/README.rst000066400000000000000000000105371426446175400140730ustar00rootroot00000000000000=============================== The ReactiveX for Python (RxPY) =============================== .. image:: https://github.com/ReactiveX/RxPY/workflows/Python%20package/badge.svg :target: https://github.com/ReactiveX/RxPY/actions :alt: Build Status .. image:: https://img.shields.io/coveralls/ReactiveX/RxPY.svg :target: https://coveralls.io/github/ReactiveX/RxPY :alt: Coverage Status .. image:: https://img.shields.io/pypi/v/reactivex.svg :target: https://pypi.org/project/reactivex/ :alt: PyPY Package Version .. 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* ReactiveX for Python v4 ----------------------- For v3.X please go to the `v3 branch `_. ReactiveX for Python v4.x runs on `Python `_ 3.7 or above. To install: .. code:: console pip3 install reactivex About ReactiveX --------------- ReactiveX for Python (RxPY) is a library 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 reactivex as rx from reactivex 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 ReactiveX ------------------ Read the `documentation `_ to learn the principles of ReactiveX and get the complete reference of the available operators. If you need to migrate code from RxPY v1.x or v3.x, read the `migration `_ section. There is also a list of third party documentation available `here `_. Community ---------- Join the conversation on GitHub `Discussions `_! if you have any questions or suggestions. Differences from .NET and RxJS ------------------------------ ReactiveX for Python 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 Rx.NET and RxJava in terms of threading and blocking operators. ReactiveX for Python follows `PEP 8 `_, so all function and method names are ``snake_cased`` i.e 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 ReactiveX for Python 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). Development ----------- This project is managed using `Poetry `_. Code is formatted using `Black `_, `isort `_. Code is statically type checked using `pyright `_ and `mypy `_. If you want to take advantage of the default VSCode integration, then first configure Poetry to make its virtual environment in the repository: .. code:: console poetry config virtualenvs.in-project true After cloning the repository, activate the tooling: .. code:: console poetry install poetry run pre-commit install Run unit tests: .. code:: console poetry run pytest Run code checks (manually): .. code:: console poetry run pre-commit run --all-files RxPY-4.0.4/authors.txt000066400000000000000000000002071426446175400146230ustar00rootroot00000000000000ReactiveX for Python maintainers Dag Brattli @dbrattli Erik Kemperman, @erikkemperman Jérémie Fache, @jcafhe Romain Picard, @MainRo RxPY-4.0.4/changes.md000066400000000000000000000132701426446175400143330ustar00rootroot00000000000000# 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-4.0.4/docs/000077500000000000000000000000001426446175400133265ustar00rootroot00000000000000RxPY-4.0.4/docs/.rstcheck.cfg000066400000000000000000000006661426446175400157030ustar00rootroot00000000000000[rstcheck] ignore_directives= ignore_roles= ignore_messages=(Unknown directive type "autoclass".|No directive entry for "autoclass" in module "docutils.parsers.rst.languages.en".|Unknown directive type "automodule".|Unknown directive type "autofunction".|No directive entry for "autofunction" in module "docutils.parsers.rst.languages.en".|No directive entry for "automodule" in module "docutils.parsers.rst.languages.en".) report=warningRxPY-4.0.4/docs/Makefile000066400000000000000000000011311426446175400147620ustar00rootroot00000000000000# 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-4.0.4/docs/additional_reading.rst000066400000000000000000000032741426446175400176670ustar00rootroot00000000000000.. _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-4.0.4/docs/conf.py000066400000000000000000000133151426446175400146300ustar00rootroot00000000000000# -*- 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 sys from distutils.command.config import config import guzzle_sphinx_theme import tomli from dunamai import Version root = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) sys.path.insert(0, root) # -- Project information ----------------------------------------------------- # General project metadata is stored in pyproject.toml with open(os.path.join(root, "pyproject.toml"), "rb") as f: config = tomli.load(f) project_meta = config["tool"]["poetry"] print(project_meta) project = project_meta["name"] author = project_meta["authors"][0] description = project_meta["description"] url = project_meta["homepage"] title = project + " Documentation" _version = Version.from_git() # The full version, including alpha/beta/rc tags release = _version.serialize(metadata=False) # The short X.Y.Z version version = _version.base # -- 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__", } # 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-4.0.4/docs/contributing.rst000066400000000000000000000011101426446175400165600ustar00rootroot00000000000000Contributing ============= 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-4.0.4/docs/get_started.rst000066400000000000000000000351111426446175400163660ustar00rootroot00000000000000.. 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 reactivex 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 reactivex 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 reactivex 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 reactivex 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 reactivex 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 reactivex from reactivex import operators as ops def length_more_than_5(): return rx.pipe( ops.map(lambda s: len(s)), ops.filter(lambda i: i >= 5), ) reactivex.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 reactivex 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 reactivex.create(subscribe) return _lowercase reactivex.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 reactivex from reactivex.scheduler import ThreadPoolScheduler from reactivex 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 reactivex.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 reactivex.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 reactivex.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 Enter key to exit\n") **OUTPUT:** .. code:: console Press Enter 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 reactivex import reactivex.operators as ops from reactivex.subject import Subject from reactivex.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 reactivex.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-4.0.4/docs/index.rst000066400000000000000000000011401426446175400151630ustar00rootroot00000000000000.. 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. ReactiveX for Python (RxPY) =========================== ReactiveX for Python (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-4.0.4/docs/installation.rst000066400000000000000000000006041426446175400165610ustar00rootroot00000000000000.. Installation Installation ============ ReactiveX for Python (RxPY) v4.x runs on `Python `__ 3. To install: .. code:: console pip3 install reactivex RxPY v3.x runs on `Python `__ 3. To install RxPY: .. code:: console pip3 install rx For Python 2.x you need to use version 1.6 .. code:: console pip install rx==1.6.1 RxPY-4.0.4/docs/license.rst000066400000000000000000000021461426446175400155050ustar00rootroot00000000000000The MIT License =============== Copyright 2013-2022, 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-4.0.4/docs/migration.rst000066400000000000000000000177461426446175400160700ustar00rootroot00000000000000.. _migration: Migration v4 ============ ReactiveX for Python v4 is an evolution of RxPY v3 to modernize it to current Python standards: - Project main module renamed from ``rx`` to ``reactivex``. This is done to give it a unique name different from the obsolete `Reactive Extensions (RxPY) `_ - Generic type annotations. Code now type checks with pyright / pylance at strict settings. It also mostly type checks with mypy. Mypy should eventually catch up. - The ``pipe`` function has been renamed to ``compose``. There is now a new function ``pipe`` that works similar to the ``pipe`` method. - RxPY is now a modern Python project using ``pyproject.toml`` instead of ``setup.py``, and using modern tools such as Poetry, Black formatter and isort. .. code:: python import reactivex as rx from reactivex 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))) Migration v3 ============ 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* | *reactivex.scheduler* | +-----------------------+-------------------------+ | *rx.disposables* | *rx.disposable* | +-----------------------+-------------------------+ | *rx.subjects* | *rx.subject* | +-----------------------+-------------------------+ Furthermore, the package formerly known as *rx.concurrency.mainloopscheduler* has been split into two parts, *reactivex.scheduler.mainloop* and *reactivex.scheduler.eventloop*. RxPY-4.0.4/docs/operators.rst000066400000000000000000000334111426446175400161000ustar00rootroot00000000000000.. _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-4.0.4/docs/rationale.rst000066400000000000000000000025001426446175400160330ustar00rootroot00000000000000.. 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-4.0.4/docs/reference.rst000066400000000000000000000004441426446175400160200ustar00rootroot00000000000000.. reference: Reference ========== .. toctree:: :name: reference Observable Factory Observable Subject Scheduler Operators Typing RxPY-4.0.4/docs/reference_observable.rst000066400000000000000000000001451426446175400202220ustar00rootroot00000000000000.. _reference_observable: Observable =========== .. autoclass:: reactivex.Observable :members: RxPY-4.0.4/docs/reference_observable_factory.rst000066400000000000000000000001651426446175400217530ustar00rootroot00000000000000.. _reference_observable_factory: Observable Factory ===================== .. automodule:: reactivex :members: RxPY-4.0.4/docs/reference_operators.rst000066400000000000000000000001411426446175400201100ustar00rootroot00000000000000.. _reference_operators: Operators ========= .. automodule:: reactivex.operators :members: RxPY-4.0.4/docs/reference_scheduler.rst000066400000000000000000000012651426446175400200600ustar00rootroot00000000000000.. _reference_scheduler: Schedulers =========== .. automodule:: reactivex.scheduler :members: CatchScheduler, CurrentThreadScheduler, EventLoopScheduler, HistoricalScheduler, ImmediateScheduler, NewThreadScheduler, ThreadPoolScheduler, TimeoutScheduler, TrampolineScheduler, VirtualTimeScheduler .. automodule:: reactivex.scheduler.eventloop :members: AsyncIOScheduler, AsyncIOThreadSafeScheduler, EventletScheduler, GEventScheduler, IOLoopScheduler, TwistedScheduler .. automodule:: reactivex.scheduler.mainloop :members: GtkScheduler, PyGameScheduler, QtScheduler, TkinterScheduler, WxScheduler RxPY-4.0.4/docs/reference_subject.rst000066400000000000000000000004341426446175400175360ustar00rootroot00000000000000.. _reference_subject: Subject ======== .. autoclass:: reactivex.subject.Subject :members: .. autoclass:: reactivex.subject.BehaviorSubject :members: .. autoclass:: reactivex.subject.ReplaySubject :members: .. autoclass:: reactivex.subject.AsyncSubject :members: RxPY-4.0.4/docs/reference_typing.rst000066400000000000000000000001261426446175400174070ustar00rootroot00000000000000.. _reference_typing: Typing ======= .. automodule:: reactivex.typing :members: RxPY-4.0.4/docs/requirements.txt000066400000000000000000000001741426446175400166140ustar00rootroot00000000000000sphinx>=2.0 sphinx-autodoc-typehints>=1.10.3 guzzle_sphinx_theme>=0.7.11 sphinxcontrib_dooble>=1.0 tomli>=2.0 dunamai>=1.9.0RxPY-4.0.4/examples/000077500000000000000000000000001426446175400142145ustar00rootroot00000000000000RxPY-4.0.4/examples/asyncio/000077500000000000000000000000001426446175400156615ustar00rootroot00000000000000RxPY-4.0.4/examples/asyncio/await.py000066400000000000000000000004451426446175400173430ustar00rootroot00000000000000import asyncio import reactivex stream = reactivex.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-4.0.4/examples/asyncio/toasyncgenerator.py000066400000000000000000000036361426446175400216320ustar00rootroot00000000000000import asyncio from asyncio import Future from typing import Any, Coroutine, List, TypeVar import reactivex from reactivex import Notification, Observable from reactivex import operators as ops from reactivex.scheduler.eventloop import AsyncIOScheduler _T = TypeVar("_T") def to_async_generator(sentinel: Any = None) -> Coroutine[Any, Any, Future[Any]]: loop = asyncio.get_event_loop() future = loop.create_future() notifications: List[Notification[Any]] = [] def _to_async_generator(source: Observable[_T]): 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: _T) -> None: """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) async def gen(): """Generator producing futures""" nonlocal future loop.call_soon(feeder) future = Future() return future return gen return _to_async_generator async def go(loop): scheduler = AsyncIOScheduler(loop) xs = reactivex.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 = await 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-4.0.4/examples/asyncio/toasynciterator.py000066400000000000000000000037461426446175400214770ustar00rootroot00000000000000import asyncio from asyncio import Future import reactivex from reactivex import Observable from reactivex import operators as ops from reactivex.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 = reactivex.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-4.0.4/examples/autocomplete/000077500000000000000000000000001426446175400167155ustar00rootroot00000000000000RxPY-4.0.4/examples/autocomplete/autocomplete.js000066400000000000000000000013471426446175400217610ustar00rootroot00000000000000(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-4.0.4/examples/autocomplete/autocomplete.py000066400000000000000000000060321426446175400217710ustar00rootroot00000000000000""" 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 asyncio import Future from typing import Any, Dict, Union from tornado import ioloop from tornado.escape import json_decode from tornado.httpclient import AsyncHTTPClient, HTTPResponse from tornado.httputil import url_concat from tornado.web import Application, RequestHandler, StaticFileHandler, url from tornado.websocket import WebSocketHandler from reactivex import operators as ops from reactivex.scheduler.eventloop import IOLoopScheduler from reactivex.subject import Subject scheduler = IOLoopScheduler(ioloop.IOLoop.current()) def search_wikipedia(term: str) -> Future[HTTPResponse]: """Search Wikipedia for a given term""" url = "http://en.wikipedia.org/w/api.php" params: Dict[str, str] = {"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, *args: Any): 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[Dict[str, str]] = 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: HTTPResponse) -> None: self.write_message(x.body) def on_error(ex: Exception) -> None: print(ex) searcher.subscribe( on_next=send_response, on_error=on_error, scheduler=scheduler ) def on_message(self, message: Union[bytes, str]): 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-4.0.4/examples/autocomplete/autocomplete_asyncio.py000066400000000000000000000061541426446175400235230ustar00rootroot00000000000000""" 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 asyncio import os from asyncio import Future from typing import Dict, Union from tornado.escape import json_decode from tornado.httpclient import AsyncHTTPClient, HTTPResponse from tornado.httputil import url_concat from tornado.platform.asyncio import AsyncIOMainLoop from tornado.web import Application, RequestHandler, StaticFileHandler, url from tornado.websocket import WebSocketHandler from reactivex import operators as ops from reactivex.scheduler.eventloop import AsyncIOScheduler from reactivex.subject import Subject def search_wikipedia(term: str) -> Future[HTTPResponse]: """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[Dict[str, str]] = 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: HTTPResponse) -> None: self.write_message(x.body) def on_error(ex: Exception): print(ex) searcher.subscribe( on_next=send_response, on_error=on_error, scheduler=scheduler ) def on_message(self, message: Union[bytes, str]): 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-4.0.4/examples/autocomplete/bottle_autocomplete.py000066400000000000000000000045001426446175400233400ustar00rootroot00000000000000""" 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 """ import json import gevent import requests from bottle import Bottle, abort, request from geventwebsocket import WebSocketError from geventwebsocket.handler import WebSocketHandler from reactivex.scheduler.eventloop import GEventScheduler from reactivex.subject import Subject 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, scheduler=scheduler) # Pause for 750ms .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-4.0.4/examples/autocomplete/index.html000066400000000000000000000024341426446175400207150ustar00rootroot00000000000000 Rx for Python Rocks!
    RxPY-4.0.4/examples/chess/000077500000000000000000000000001426446175400153215ustar00rootroot00000000000000RxPY-4.0.4/examples/chess/chess.py000066400000000000000000000051171426446175400170040ustar00rootroot00000000000000import sys from os.path import dirname, join import pygame from reactivex import operators as ops from reactivex.scheduler.mainloop import PyGameScheduler from reactivex.subject import Subject # 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-4.0.4/examples/chess/chess_bishop_black.png000066400000000000000000000041171426446175400216370ustar00rootroot00000000000000PNG  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-4.0.4/examples/chess/chess_bishop_white.png000066400000000000000000000037451426446175400217110ustar00rootroot00000000000000PNG  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-4.0.4/examples/chess/chess_king_black.png000066400000000000000000000037361426446175400213110ustar00rootroot00000000000000PNG  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-4.0.4/examples/chess/chess_king_white.png000066400000000000000000000036171426446175400213530ustar00rootroot00000000000000PNG  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-4.0.4/examples/chess/chess_knight_black.png000066400000000000000000000033711426446175400216400ustar00rootroot00000000000000PNG  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-4.0.4/examples/chess/chess_knight_white.png000066400000000000000000000032461426446175400217050ustar00rootroot00000000000000PNG  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-4.0.4/examples/chess/chess_pawn_black.png000066400000000000000000000041561426446175400213230ustar00rootroot00000000000000PNG  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-4.0.4/examples/chess/chess_pawn_white.png000066400000000000000000000040061426446175400213610ustar00rootroot00000000000000PNG  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-4.0.4/examples/chess/chess_queen_black.png000066400000000000000000000040051426446175400214640ustar00rootroot00000000000000PNG  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-4.0.4/examples/chess/chess_queen_white.png000066400000000000000000000037001426446175400215310ustar00rootroot00000000000000PNG  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-4.0.4/examples/chess/chess_rook_black.png000066400000000000000000000026161426446175400213270ustar00rootroot00000000000000PNG  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-4.0.4/examples/chess/chess_rook_white.png000066400000000000000000000040771426446175400213760ustar00rootroot00000000000000PNG  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-4.0.4/examples/errors/000077500000000000000000000000001426446175400155305ustar00rootroot00000000000000RxPY-4.0.4/examples/errors/failing.py000066400000000000000000000012651426446175400175170ustar00rootroot00000000000000""" 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 reactivex from reactivex import operators as ops def failing(x): x = int(x) if not x % 2: raise Exception("Error") return x def main(): xs = reactivex.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-4.0.4/examples/konamicode/000077500000000000000000000000001426446175400163255ustar00rootroot00000000000000RxPY-4.0.4/examples/konamicode/index.html000066400000000000000000000170051426446175400203250ustar00rootroot00000000000000 Rx for Python Rocks!

    Enter the Konami Code

    BA

    RxPY-4.0.4/examples/konamicode/konamicode.js000066400000000000000000000005301426446175400207720ustar00rootroot00000000000000$(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-4.0.4/examples/konamicode/konamicode.py000066400000000000000000000040001426446175400210020ustar00rootroot00000000000000import os from typing import Dict, Union from tornado import ioloop from tornado.escape import json_decode from tornado.web import Application, RequestHandler, StaticFileHandler, url from tornado.websocket import WebSocketHandler from reactivex import operators as ops from reactivex.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[Dict[str, int]] = 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(on_next=lambda x: self.write_message("Konami!")) def on_message(self, message: Union[str, bytes]): 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(int(port)) ioloop.IOLoop.current().start() if __name__ == "__main__": main() RxPY-4.0.4/examples/marbles/000077500000000000000000000000001426446175400156415ustar00rootroot00000000000000RxPY-4.0.4/examples/marbles/frommarbles_error.py000066400000000000000000000005751426446175400217440ustar00rootroot00000000000000import reactivex from reactivex import operators as ops """ Specify the error to be raised in place of the # symbol. """ err = ValueError("I don't like 5!") src0 = reactivex.from_marbles("12-----4-----67--|", timespan=0.2) src1 = reactivex.from_marbles("----3----5-# ", timespan=0.2, error=err) source = reactivex.merge(src0, src1).pipe(ops.do_action(print)) source.run() RxPY-4.0.4/examples/marbles/frommarbles_flatmap.py000066400000000000000000000010231426446175400222240ustar00rootroot00000000000000import reactivex from reactivex import operators as ops a = reactivex.cold(" ---a0---a1----------------a2-| ") b = reactivex.cold(" ---b1---b2---| ") c = reactivex.cold(" ---c1---c2---| ") d = reactivex.cold(" -----d1---d2---|") e1 = reactivex.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-4.0.4/examples/marbles/frommarbles_lookup.py000066400000000000000000000010101426446175400221050ustar00rootroot00000000000000import reactivex import reactivex.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 = reactivex.cold("a---b----c----|", timespan=0.01, lookup=lookup0) source1 = reactivex.cold("---x---y---z--|", timespan=0.01, lookup=lookup1) observable = reactivex.merge(source0, source1).pipe(ops.to_iterable()) elements = observable.run() print("received {}".format(list(elements))) RxPY-4.0.4/examples/marbles/frommarbles_merge.py000066400000000000000000000006071426446175400217060ustar00rootroot00000000000000import reactivex from reactivex import operators as ops """ simple example that merges two cold observables. """ source0 = reactivex.cold("a-----d---1--------4-|", timespan=0.01) source1 = reactivex.cold("--b-c-------2---3-| ", timespan=0.01) observable = reactivex.merge(source0, source1).pipe(ops.to_iterable()) elements = observable.run() print("received {}".format(list(elements))) RxPY-4.0.4/examples/marbles/hot_datetime.py000066400000000000000000000007211426446175400206610ustar00rootroot00000000000000import datetime import reactivex import reactivex.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 = reactivex.hot("10--11--12--13--(14,|)", timespan=0.2, duetime=duetime) source = hot.pipe(ops.do_action(print)) source.run() RxPY-4.0.4/examples/marbles/testing_debounce.py000066400000000000000000000012401426446175400215310ustar00rootroot00000000000000from reactivex import operators as ops from reactivex.testing.marbles import marbles_testing """ Tests debounceTime from reactivexjs 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-4.0.4/examples/marbles/testing_flatmap.py000066400000000000000000000017671426446175400214070ustar00rootroot00000000000000from reactivex import operators as ops from reactivex.testing.marbles import marbles_testing """ Tests MergeMap from reactivexjs 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-4.0.4/examples/marbles/tomarbles.py000066400000000000000000000007171426446175400202100ustar00rootroot00000000000000import reactivex from reactivex import operators as ops source0 = reactivex.cold("a-----d---1--------4-|", timespan=0.1) source1 = reactivex.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 = reactivex.merge(source0, source1).pipe(ops.to_marbles(timespan=0.1)) diagram = observable.run() print('got "{}"'.format(diagram)) RxPY-4.0.4/examples/parallel/000077500000000000000000000000001426446175400160105ustar00rootroot00000000000000RxPY-4.0.4/examples/parallel/timer.py000066400000000000000000000007601426446175400175050ustar00rootroot00000000000000import concurrent.futures import time import reactivex from reactivex import operators as ops seconds = [5, 1, 2, 4, 3] def sleep(tm: float) -> float: time.sleep(tm) return tm def output(result: str) -> None: print("%d seconds" % result) with concurrent.futures.ProcessPoolExecutor(5) as executor: reactivex.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-4.0.4/examples/statistics/000077500000000000000000000000001426446175400164065ustar00rootroot00000000000000RxPY-4.0.4/examples/statistics/statistics.py000066400000000000000000000037531426446175400211620ustar00rootroot00000000000000import math from typing import Any from reactivex import Observable 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[Any]) -> Observable[Any]: """ 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-4.0.4/examples/timeflies/000077500000000000000000000000001426446175400161755ustar00rootroot00000000000000RxPY-4.0.4/examples/timeflies/timeflies_gtk.py000066400000000000000000000032101426446175400213710ustar00rootroot00000000000000import gi from gi.repository import Gdk, GLib, Gtk import reactivex from reactivex import operators as ops from reactivex.scheduler.mainloop import GtkScheduler from reactivex.subject import Subject gi.require_version("Gtk", "3.0") 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) reactivex.from_(text).pipe( mapper, labeler, ).subscribe(on_next, on_error=print, scheduler=scheduler) window.show_all() Gtk.main() if __name__ == "__main__": main() RxPY-4.0.4/examples/timeflies/timeflies_qt.py000066400000000000000000000032101426446175400212300ustar00rootroot00000000000000import sys import reactivex from reactivex import operators as ops from reactivex.scheduler.mainloop import QtScheduler from reactivex.subject import Subject try: from PySide2 import QtCore from PySide2.QtWidgets import QApplication, QLabel, QWidget except ImportError: try: from PyQt5 import QtCore from PyQt5.QtWidgets import QApplication, QLabel, QWidget 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)) reactivex.from_(text).pipe( mapper, labeler, ).subscribe(on_next, on_error=print, scheduler=scheduler) sys.exit(app.exec_()) if __name__ == "__main__": main() RxPY-4.0.4/examples/timeflies/timeflies_tkinter.py000066400000000000000000000024761426446175400223010ustar00rootroot00000000000000import tkinter from tkinter import Event, Frame, Label, Tk from typing import Any, Tuple import reactivex from reactivex import Observable from reactivex import operators as ops from reactivex.scheduler.mainloop import TkinterScheduler from reactivex.subject import Subject def main() -> None: root = Tk() root.title("Rx for Python rocks") scheduler = TkinterScheduler(root) mousemoves: Subject[Event[Any]] = Subject() frame = Frame(root, width=600, height=600) frame.bind("", mousemoves.on_next) text = "TIME FLIES LIKE AN ARROW" def on_next(info: Tuple[tkinter.Label, "Event[Frame]", int]) -> None: label, ev, i = info label.place(x=ev.x + i * 12 + 15, y=ev.y) def label2stream( label: tkinter.Label, index: int ) -> Observable[Tuple[tkinter.Label, "Event[Frame]", int]]: return mousemoves.pipe( ops.map(lambda ev: (label, ev, index)), ops.delay(index * 0.1), ) def char2label(char: str) -> Label: return Label(frame, text=char, borderwidth=0, padx=0, pady=0) reactivex.from_(text).pipe( ops.map(char2label), ops.flat_map_indexed(label2stream), ).subscribe(on_next, on_error=print, scheduler=scheduler) frame.pack() root.mainloop() if __name__ == "__main__": main() RxPY-4.0.4/examples/timeflies/timeflies_wx.py000066400000000000000000000027611426446175400212540ustar00rootroot00000000000000import wx import reactivex from reactivex import operators as ops from reactivex.scheduler.mainloop import WxScheduler from reactivex.subject import Subject 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) reactivex.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-4.0.4/notebooks/000077500000000000000000000000001426446175400144015ustar00rootroot00000000000000RxPY-4.0.4/notebooks/Getting Started.ipynb000066400000000000000000000277341426446175400204510ustar00rootroot00000000000000{ "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 reactivex" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Importing the Rx module" ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "collapsed": true }, "outputs": [], "source": [ "import reactivex\n", "from reactivex import operators as ops\n", "from reactivex import 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": 23, "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[int]):\n", " def on_next(self, value: int):\n", " print(\"Got: %s\" % value)\n", "\n", " def on_error(self, error: Exception):\n", " print(\"Got error: %s\" % error)\n", "\n", " def on_completed(self):\n", " print(\"Sequence completed\")\n", "\n", "xs = reactivex.from_iterable(range(10))\n", "d = xs.subscribe(MyObserver())" ] }, { "cell_type": "code", "execution_count": 4, "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 = reactivex.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": 9, "metadata": { "collapsed": false, "scrolled": true }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1\n", "3\n", "5\n", "7\n", "9\n" ] } ], "source": [ "xs = reactivex.from_(range(10))\n", "d = xs.pipe(\n", " ops.filter(\n", " lambda x: x % 2\n", " )).subscribe(print)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Transforming a sequence" ] }, { "cell_type": "code", "execution_count": 10, "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 = reactivex.from_(range(10))\n", "d = xs.pipe(\n", " ops.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": 12, "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 = reactivex.from_(range(10, 20, 2))\n", "d = xs.pipe(\n", " ops.map_indexed(\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": 13, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1\n", "a\n", "b\n", "c\n", "d\n", "e\n", "2\n", "3\n", "4\n" ] } ], "source": [ "xs = reactivex.range(1, 5)\n", "ys = reactivex.from_(\"abcde\")\n", "zs = xs.pipe(ops.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 function to. The methods are `from_marbles()` and `to_marbles()`.\n", "\n", "Examples:\n", "1. `res = reactivex.from_marbles(\"1-2-3-|\")`\n", "2. `res = reactivex.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": 17, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "['a', 'b', 'c']" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "xs = reactivex.from_marbles(\"a-b-c-|\")\n", "xs.pipe(ops.to_list()).run()" ] }, { "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": 24, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 24, "metadata": {}, "output_type": "execute_result" }, { "name": "stdout", "output_type": "stream", "text": [ "error\n" ] } ], "source": [ "xs = reactivex.from_marbles(\"1-2-3-#-\")\n", "ys = reactivex.from_marbles(\"1-2-3-4-5\")\n", "xs.pipe(ops.merge(ys)).subscribe(on_error=print)" ] }, { "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": 16, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Got: 42\n" ] } ], "source": [ "from reactivex.subject import Subject\n", "\n", "stream = Subject[int]()\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.10.0" } }, "nbformat": 4, "nbformat_minor": 0 } RxPY-4.0.4/notebooks/reactivex.io/000077500000000000000000000000001426446175400170015ustar00rootroot00000000000000RxPY-4.0.4/notebooks/reactivex.io/A Decision Tree of Observable Operators. Part I - Creation.ipynb000066400000000000000000001165431426446175400322050ustar00rootroot00000000000000{ "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 = reactivex.Observable.return(42)\n", " res = reactivex.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 = reactivex.Observable.start(lambda: pprint('hello'))\n", " res = reactivex.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 = reactivex.Observable.from_iterable([1,2,3])\n", " 2 - res = reactivex.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 = reactivex.Observable.repeat(42)\n", " 2 - res = reactivex.Observable.repeat(42, 4)\n", " 3 - res = reactivex.Observable.repeat(42, 4, Rx.Scheduler.timeout)\n", " 4 - res = reactivex.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 = reactivex.Observable.generate(0,\n", " lambda x: x < 10,\n", " lambda x: x + 1,\n", " lambda x: x)\n", " 2 - res = reactivex.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 = reactivex.Observable.defer(lambda: reactivex.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 = reactivex.Observable.if(condition, obs1)\n", " 2 - res = reactivex.Observable.if(condition, obs1, obs2)\n", " 3 - res = reactivex.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 reactivex.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 = reactivex.Observable.range(0, 10)\n", " 2 - res = reactivex.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 = reactivex.Observable.interval(1000)\n", " 2 - res = reactivex.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 = reactivex.empty()\n", " 2 - res = reactivex.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 = reactivex.throw(Exception('Error'))\n", " 2 - res = reactivex.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-4.0.4/notebooks/reactivex.io/Marble Diagrams.ipynb000066400000000000000000000107571426446175400227700ustar00rootroot00000000000000{ "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 = reactivex.Observable.from_string(\"1-2-3-|\")\n", " 2 - res = reactivex.Observable.from_string(\"1-(42)-3-|\")\n", " 3 - res = reactivex.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-4.0.4/notebooks/reactivex.io/Part II - Combination.ipynb000066400000000000000000000773571426446175400236170ustar00rootroot00000000000000{ "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 = reactivex.Observable.merge(xs, ys, zs)\n", " 2 - merged = reactivex.Observable.merge([xs, ys, zs])\n", " 3 - merged = reactivex.Observable.merge(scheduler, xs, ys, zs)\n", " 4 - merged = reactivex.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-4.0.4/notebooks/reactivex.io/Part III - Transformation.ipynb000066400000000000000000000600301426446175400244500ustar00rootroot00000000000000{ "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-4.0.4/notebooks/reactivex.io/Part IV - Grouping, Buffering, Delaying, misc.ipynb000066400000000000000000001345571426446175400277050ustar00rootroot00000000000000{ "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-4.0.4/notebooks/reactivex.io/Part V - Consolidating Streams.ipynb000066400000000000000000001447721426446175400254510ustar00rootroot00000000000000{ "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, reactivex.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-4.0.4/notebooks/reactivex.io/Part VI - Entirety Operations.ipynb000066400000000000000000000707621426446175400252720ustar00rootroot00000000000000{ "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-4.0.4/notebooks/reactivex.io/Part VII - Meta Operations.ipynb000066400000000000000000001462321426446175400244620ustar00rootroot00000000000000{ "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, reactivex.scheduler.ImmediateScheduler())\n", "d = subs(s.subscribe_on(reactivex.scheduler.TimeoutScheduler()), name='SimpleSubs')\n", "\n", "sleep(0.1)\n", "\n", "header('Custom Subscription Side Effects')\n", "\n", "from reactivex.scheduler.newthreadscheduler import NewThreadScheduler\n", "from reactivex.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 reactivex.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-4.0.4/notebooks/reactivex.io/Part VIII - Hot & Cold.ipynb000066400000000000000000001126321426446175400233000ustar00rootroot00000000000000{ "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-4.0.4/notebooks/reactivex.io/RxPy - Basic Usage and Mechanics.ipynb000066400000000000000000000132641426446175400255160ustar00rootroot00000000000000{ "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-4.0.4/notebooks/reactivex.io/assets/000077500000000000000000000000001426446175400203035ustar00rootroot00000000000000RxPY-4.0.4/notebooks/reactivex.io/assets/Article about Publish - Refcount.ipynb000066400000000000000000000156261426446175400273100ustar00rootroot00000000000000{ "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-4.0.4/notebooks/reactivex.io/assets/img/000077500000000000000000000000001426446175400210575ustar00rootroot00000000000000RxPY-4.0.4/notebooks/reactivex.io/assets/img/publishConnect.png000066400000000000000000007373711426446175400245670ustar00rootroot00000000000000PNG  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-4.0.4/notebooks/reactivex.io/assets/img/threading.png000066400000000000000000013000431426446175400235330ustar00rootroot00000000000000PNG  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-4.0.4/notebooks/reactivex.io/assets/js/000077500000000000000000000000001426446175400207175ustar00rootroot00000000000000RxPY-4.0.4/notebooks/reactivex.io/assets/js/ipython_notebook_toc.js000066400000000000000000000027371426446175400255250ustar00rootroot00000000000000// 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-4.0.4/notebooks/reactivex.io/startup.py000066400000000000000000000065361426446175400210670ustar00rootroot00000000000000# Helpers. # Run this cell always after kernel restarts. All other cells are autonomous. from __future__ import print_function import inspect import logging # getting the current thread import threading import time from random import randint import reactivex logging.basicConfig(format="%(threadName)s:%(message)s") log = logging.getLogger("Rx") log.setLevel(logging.WARNING) sleep, now = time.sleep, time.time O = reactivex.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 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() def marble_stream(s): return O.from_marbles(s).to_blocking() RxPY-4.0.4/poetry.lock000066400000000000000000001405111426446175400145740ustar00rootroot00000000000000[[package]] name = "atomicwrites" version = "1.4.0" description = "Atomic file writes." category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "attrs" version = "21.4.0" description = "Classes Without Boilerplate" category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [package.extras] dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit", "cloudpickle"] docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "cloudpickle"] tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "cloudpickle"] [[package]] name = "autoflake" version = "1.4" description = "Removes unused imports and unused variables" category = "dev" optional = false python-versions = "*" [package.dependencies] pyflakes = ">=1.1.0" [[package]] name = "black" version = "22.1.0" description = "The uncompromising code formatter." category = "dev" optional = false python-versions = ">=3.6.2" [package.dependencies] click = ">=8.0.0" mypy-extensions = ">=0.4.3" pathspec = ">=0.9.0" platformdirs = ">=2" tomli = ">=1.1.0" typed-ast = {version = ">=1.4.2", markers = "python_version < \"3.8\" and implementation_name == \"cpython\""} typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""} [package.extras] colorama = ["colorama (>=0.4.3)"] d = ["aiohttp (>=3.7.4)"] jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "certifi" version = "2021.10.8" description = "Python package for providing Mozilla's CA Bundle." category = "dev" optional = false python-versions = "*" [[package]] name = "cfgv" version = "3.3.1" description = "Validate configuration and produce human readable error messages." category = "dev" optional = false python-versions = ">=3.6.1" [[package]] name = "charset-normalizer" version = "2.0.12" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." category = "dev" optional = false python-versions = ">=3.5.0" [package.extras] unicode_backport = ["unicodedata2"] [[package]] name = "click" version = "8.0.4" description = "Composable command line interface toolkit" category = "dev" optional = false python-versions = ">=3.6" [package.dependencies] colorama = {version = "*", markers = "platform_system == \"Windows\""} importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} [[package]] name = "colorama" version = "0.4.4" description = "Cross-platform colored terminal text." category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "coverage" version = "6.3.2" description = "Code coverage measurement for Python" category = "dev" optional = false python-versions = ">=3.7" [package.extras] toml = ["tomli"] [[package]] name = "coveralls" version = "3.3.1" description = "Show coverage stats online via coveralls.io" category = "dev" optional = false python-versions = ">= 3.5" [package.dependencies] coverage = ">=4.1,<6.0.0 || >6.1,<6.1.1 || >6.1.1,<7.0" docopt = ">=0.6.1" requests = ">=1.0.0" [package.extras] yaml = ["PyYAML (>=3.10)"] [[package]] name = "distlib" version = "0.3.4" description = "Distribution utilities" category = "dev" optional = false python-versions = "*" [[package]] name = "docopt" version = "0.6.2" description = "Pythonic argument parser, that will make you smile" category = "dev" optional = false python-versions = "*" [[package]] name = "dunamai" version = "1.9.0" description = "Dynamic version generation" category = "dev" optional = false python-versions = ">=3.5,<4.0" [package.dependencies] importlib-metadata = {version = ">=1.6.0", markers = "python_version < \"3.8\""} packaging = ">=20.9" [[package]] name = "execnet" version = "1.9.0" description = "execnet: rapid multi-Python deployment" category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [package.extras] testing = ["pre-commit"] [[package]] name = "filelock" version = "3.6.0" description = "A platform independent file lock." category = "dev" optional = false python-versions = ">=3.7" [package.extras] docs = ["furo (>=2021.8.17b43)", "sphinx (>=4.1)", "sphinx-autodoc-typehints (>=1.12)"] testing = ["covdefaults (>=1.2.0)", "coverage (>=4)", "pytest (>=4)", "pytest-cov", "pytest-timeout (>=1.4.2)"] [[package]] name = "flake8" version = "4.0.1" description = "the modular source code checker: pep8 pyflakes and co" category = "dev" optional = false python-versions = ">=3.6" [package.dependencies] importlib-metadata = {version = "<4.3", markers = "python_version < \"3.8\""} mccabe = ">=0.6.0,<0.7.0" pycodestyle = ">=2.8.0,<2.9.0" pyflakes = ">=2.4.0,<2.5.0" [[package]] name = "identify" version = "2.4.11" description = "File identification library for Python" category = "dev" optional = false python-versions = ">=3.7" [package.extras] license = ["ukkonen"] [[package]] name = "idna" version = "3.3" description = "Internationalized Domain Names in Applications (IDNA)" category = "dev" optional = false python-versions = ">=3.5" [[package]] name = "importlib-metadata" version = "4.2.0" description = "Read metadata from Python packages" category = "dev" optional = false python-versions = ">=3.6" [package.dependencies] typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} zipp = ">=0.5" [package.extras] docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] [[package]] name = "iniconfig" version = "1.1.1" description = "iniconfig: brain-dead simple config-ini parsing" category = "dev" optional = false python-versions = "*" [[package]] name = "isort" version = "5.10.1" description = "A Python utility / library to sort Python imports." category = "dev" optional = false python-versions = ">=3.6.1,<4.0" [package.extras] pipfile_deprecated_finder = ["pipreqs", "requirementslib"] requirements_deprecated_finder = ["pipreqs", "pip-api"] colors = ["colorama (>=0.4.3,<0.5.0)"] plugins = ["setuptools"] [[package]] name = "mccabe" version = "0.6.1" description = "McCabe checker, plugin for flake8" category = "dev" optional = false python-versions = "*" [[package]] name = "mypy" version = "0.931" description = "Optional static typing for Python" category = "dev" optional = false python-versions = ">=3.6" [package.dependencies] mypy-extensions = ">=0.4.3" tomli = ">=1.1.0" typed-ast = {version = ">=1.4.0,<2", markers = "python_version < \"3.8\""} typing-extensions = ">=3.10" [package.extras] dmypy = ["psutil (>=4.0)"] python2 = ["typed-ast (>=1.4.0,<2)"] [[package]] name = "mypy-extensions" version = "0.4.3" description = "Experimental type system extensions for programs checked with the mypy typechecker." category = "dev" optional = false python-versions = "*" [[package]] name = "nodeenv" version = "1.6.0" description = "Node.js virtual environment builder" category = "dev" optional = false python-versions = "*" [[package]] name = "packaging" version = "21.3" description = "Core utilities for Python packages" category = "dev" optional = false python-versions = ">=3.6" [package.dependencies] pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" [[package]] name = "pathspec" version = "0.9.0" description = "Utility library for gitignore style pattern matching of file paths." category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" [[package]] name = "platformdirs" version = "2.5.1" description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." category = "dev" optional = false python-versions = ">=3.7" [package.extras] docs = ["Sphinx (>=4)", "furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)"] test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"] [[package]] name = "pluggy" version = "1.0.0" description = "plugin and hook calling mechanisms for python" category = "dev" optional = false python-versions = ">=3.6" [package.dependencies] importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} [package.extras] dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] [[package]] name = "pre-commit" version = "2.17.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." category = "dev" optional = false python-versions = ">=3.6.1" [package.dependencies] cfgv = ">=2.0.0" identify = ">=1.0.0" importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} nodeenv = ">=0.11.1" pyyaml = ">=5.1" toml = "*" virtualenv = ">=20.0.8" [[package]] name = "py" version = "1.11.0" description = "library with cross-python path, ini-parsing, io, code, log facilities" category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "pycodestyle" version = "2.8.0" description = "Python style guide checker" category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "pyflakes" version = "2.4.0" description = "passive checker of Python programs" category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "pyparsing" version = "3.0.7" description = "Python parsing module" category = "dev" optional = false python-versions = ">=3.6" [package.extras] diagrams = ["jinja2", "railroad-diagrams"] [[package]] name = "pyright" version = "0.0.13" description = "Command line wrapper for pyright" category = "dev" optional = false python-versions = ">=3" [package.dependencies] nodeenv = ">=1.6.0" typing-extensions = {version = ">=3.7", markers = "python_version < \"3.8\""} [package.extras] all = ["twine (>=3.4.1)"] dev = ["twine (>=3.4.1)"] [[package]] name = "pytest" version = "7.0.1" description = "pytest: simple powerful testing with Python" category = "dev" optional = false python-versions = ">=3.6" [package.dependencies] atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} attrs = ">=19.2.0" colorama = {version = "*", markers = "sys_platform == \"win32\""} importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} iniconfig = "*" packaging = "*" pluggy = ">=0.12,<2.0" py = ">=1.8.2" tomli = ">=1.0.0" [package.extras] testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] [[package]] name = "pytest-asyncio" version = "0.18.1" description = "Pytest support for asyncio" category = "dev" optional = false python-versions = ">=3.7" [package.dependencies] pytest = ">=6.1.0" typing-extensions = {version = ">=3.7.2", markers = "python_version < \"3.8\""} [package.extras] testing = ["coverage (==6.2)", "hypothesis (>=5.7.1)", "flaky (>=3.5.0)", "mypy (==0.931)"] [[package]] name = "pytest-forked" version = "1.4.0" description = "run tests in isolated forked subprocesses" category = "dev" optional = false python-versions = ">=3.6" [package.dependencies] py = "*" pytest = ">=3.10" [[package]] name = "pytest-xdist" version = "2.5.0" description = "pytest xdist plugin for distributed testing and loop-on-failing modes" category = "dev" optional = false python-versions = ">=3.6" [package.dependencies] execnet = ">=1.1" pytest = ">=6.2.0" pytest-forked = "*" [package.extras] psutil = ["psutil (>=3.0)"] setproctitle = ["setproctitle"] testing = ["filelock"] [[package]] name = "pyyaml" version = "6.0" description = "YAML parser and emitter for Python" category = "dev" optional = false python-versions = ">=3.6" [[package]] name = "requests" version = "2.27.1" description = "Python HTTP for Humans." category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" [package.dependencies] certifi = ">=2017.4.17" charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""} idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""} urllib3 = ">=1.21.1,<1.27" [package.extras] socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"] [[package]] name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "toml" version = "0.10.2" description = "Python Library for Tom's Obvious, Minimal Language" category = "dev" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "tomli" version = "2.0.1" description = "A lil' TOML parser" category = "dev" optional = false python-versions = ">=3.7" [[package]] name = "typed-ast" version = "1.5.2" description = "a fork of Python 2 and 3 ast modules with type comment support" category = "dev" optional = false python-versions = ">=3.6" [[package]] name = "typing-extensions" version = "4.1.1" description = "Backported and Experimental Type Hints for Python 3.6+" category = "main" optional = false python-versions = ">=3.6" [[package]] name = "urllib3" version = "1.26.8" description = "HTTP library with thread-safe connection pooling, file post, and more." category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" [package.extras] brotli = ["brotlipy (>=0.6.0)"] secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] name = "virtualenv" version = "20.13.2" description = "Virtual Python Environment builder" category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" [package.dependencies] distlib = ">=0.3.1,<1" filelock = ">=3.2,<4" importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} platformdirs = ">=2,<3" six = ">=1.9.0,<2" [package.extras] docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=21.3)"] testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)", "packaging (>=20.0)"] [[package]] name = "zipp" version = "3.7.0" description = "Backport of pathlib-compatible object wrapper for zip files" category = "dev" optional = false python-versions = ">=3.7" [package.extras] docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] [metadata] lock-version = "1.1" python-versions = ">= 3.7, < 3.11" content-hash = "b7c131a52cd9cb1d14dadc1b8f1b0d9044ae592674cd9918f227b993a5474de9" [metadata.files] atomicwrites = [ {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, ] attrs = [ {file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"}, {file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"}, ] autoflake = [ {file = "autoflake-1.4.tar.gz", hash = "sha256:61a353012cff6ab94ca062823d1fb2f692c4acda51c76ff83a8d77915fba51ea"}, ] black = [ {file = "black-22.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1297c63b9e1b96a3d0da2d85d11cd9bf8664251fd69ddac068b98dc4f34f73b6"}, {file = "black-22.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2ff96450d3ad9ea499fc4c60e425a1439c2120cbbc1ab959ff20f7c76ec7e866"}, {file = "black-22.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e21e1f1efa65a50e3960edd068b6ae6d64ad6235bd8bfea116a03b21836af71"}, {file = "black-22.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2f69158a7d120fd641d1fa9a921d898e20d52e44a74a6fbbcc570a62a6bc8ab"}, {file = "black-22.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:228b5ae2c8e3d6227e4bde5920d2fc66cc3400fde7bcc74f480cb07ef0b570d5"}, {file = "black-22.1.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b1a5ed73ab4c482208d20434f700d514f66ffe2840f63a6252ecc43a9bc77e8a"}, {file = "black-22.1.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35944b7100af4a985abfcaa860b06af15590deb1f392f06c8683b4381e8eeaf0"}, {file = "black-22.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:7835fee5238fc0a0baf6c9268fb816b5f5cd9b8793423a75e8cd663c48d073ba"}, {file = "black-22.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dae63f2dbf82882fa3b2a3c49c32bffe144970a573cd68d247af6560fc493ae1"}, {file = "black-22.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fa1db02410b1924b6749c245ab38d30621564e658297484952f3d8a39fce7e8"}, {file = "black-22.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:c8226f50b8c34a14608b848dc23a46e5d08397d009446353dad45e04af0c8e28"}, {file = "black-22.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2d6f331c02f0f40aa51a22e479c8209d37fcd520c77721c034517d44eecf5912"}, {file = "black-22.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:742ce9af3086e5bd07e58c8feb09dbb2b047b7f566eb5f5bc63fd455814979f3"}, {file = "black-22.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:fdb8754b453fb15fad3f72cd9cad3e16776f0964d67cf30ebcbf10327a3777a3"}, {file = "black-22.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5660feab44c2e3cb24b2419b998846cbb01c23c7fe645fee45087efa3da2d61"}, {file = "black-22.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:6f2f01381f91c1efb1451998bd65a129b3ed6f64f79663a55fe0e9b74a5f81fd"}, {file = "black-22.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:efbadd9b52c060a8fc3b9658744091cb33c31f830b3f074422ed27bad2b18e8f"}, {file = "black-22.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8871fcb4b447206904932b54b567923e5be802b9b19b744fdff092bd2f3118d0"}, {file = "black-22.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ccad888050f5393f0d6029deea2a33e5ae371fd182a697313bdbd835d3edaf9c"}, {file = "black-22.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07e5c049442d7ca1a2fc273c79d1aecbbf1bc858f62e8184abe1ad175c4f7cc2"}, {file = "black-22.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:373922fc66676133ddc3e754e4509196a8c392fec3f5ca4486673e685a421321"}, {file = "black-22.1.0-py3-none-any.whl", hash = "sha256:3524739d76b6b3ed1132422bf9d82123cd1705086723bc3e235ca39fd21c667d"}, {file = "black-22.1.0.tar.gz", hash = "sha256:a7c0192d35635f6fc1174be575cb7915e92e5dd629ee79fdaf0dcfa41a80afb5"}, ] certifi = [ {file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"}, {file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"}, ] cfgv = [ {file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"}, {file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"}, ] charset-normalizer = [ {file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"}, {file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"}, ] click = [ {file = "click-8.0.4-py3-none-any.whl", hash = "sha256:6a7a62563bbfabfda3a38f3023a1db4a35978c0abd76f6c9605ecd6554d6d9b1"}, {file = "click-8.0.4.tar.gz", hash = "sha256:8458d7b1287c5fb128c90e23381cf99dcde74beaf6c7ff6384ce84d6fe090adb"}, ] colorama = [ {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, ] coverage = [ {file = "coverage-6.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9b27d894748475fa858f9597c0ee1d4829f44683f3813633aaf94b19cb5453cf"}, {file = "coverage-6.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:37d1141ad6b2466a7b53a22e08fe76994c2d35a5b6b469590424a9953155afac"}, {file = "coverage-6.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9987b0354b06d4df0f4d3e0ec1ae76d7ce7cbca9a2f98c25041eb79eec766f1"}, {file = "coverage-6.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:26e2deacd414fc2f97dd9f7676ee3eaecd299ca751412d89f40bc01557a6b1b4"}, {file = "coverage-6.3.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4dd8bafa458b5c7d061540f1ee9f18025a68e2d8471b3e858a9dad47c8d41903"}, {file = "coverage-6.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:46191097ebc381fbf89bdce207a6c107ac4ec0890d8d20f3360345ff5976155c"}, {file = "coverage-6.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6f89d05e028d274ce4fa1a86887b071ae1755082ef94a6740238cd7a8178804f"}, {file = "coverage-6.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:58303469e9a272b4abdb9e302a780072c0633cdcc0165db7eec0f9e32f901e05"}, {file = "coverage-6.3.2-cp310-cp310-win32.whl", hash = "sha256:2fea046bfb455510e05be95e879f0e768d45c10c11509e20e06d8fcaa31d9e39"}, {file = "coverage-6.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:a2a8b8bcc399edb4347a5ca8b9b87e7524c0967b335fbb08a83c8421489ddee1"}, {file = "coverage-6.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:f1555ea6d6da108e1999b2463ea1003fe03f29213e459145e70edbaf3e004aaa"}, {file = "coverage-6.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5f4e1edcf57ce94e5475fe09e5afa3e3145081318e5fd1a43a6b4539a97e518"}, {file = "coverage-6.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7a15dc0a14008f1da3d1ebd44bdda3e357dbabdf5a0b5034d38fcde0b5c234b7"}, {file = "coverage-6.3.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21b7745788866028adeb1e0eca3bf1101109e2dc58456cb49d2d9b99a8c516e6"}, {file = "coverage-6.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:8ce257cac556cb03be4a248d92ed36904a59a4a5ff55a994e92214cde15c5bad"}, {file = "coverage-6.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b0be84e5a6209858a1d3e8d1806c46214e867ce1b0fd32e4ea03f4bd8b2e3359"}, {file = "coverage-6.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:acf53bc2cf7282ab9b8ba346746afe703474004d9e566ad164c91a7a59f188a4"}, {file = "coverage-6.3.2-cp37-cp37m-win32.whl", hash = "sha256:8bdde1177f2311ee552f47ae6e5aa7750c0e3291ca6b75f71f7ffe1f1dab3dca"}, {file = "coverage-6.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:b31651d018b23ec463e95cf10070d0b2c548aa950a03d0b559eaa11c7e5a6fa3"}, {file = "coverage-6.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:07e6db90cd9686c767dcc593dff16c8c09f9814f5e9c51034066cad3373b914d"}, {file = "coverage-6.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2c6dbb42f3ad25760010c45191e9757e7dce981cbfb90e42feef301d71540059"}, {file = "coverage-6.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c76aeef1b95aff3905fb2ae2d96e319caca5b76fa41d3470b19d4e4a3a313512"}, {file = "coverage-6.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8cf5cfcb1521dc3255d845d9dca3ff204b3229401994ef8d1984b32746bb45ca"}, {file = "coverage-6.3.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8fbbdc8d55990eac1b0919ca69eb5a988a802b854488c34b8f37f3e2025fa90d"}, {file = "coverage-6.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ec6bc7fe73a938933d4178c9b23c4e0568e43e220aef9472c4f6044bfc6dd0f0"}, {file = "coverage-6.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:9baff2a45ae1f17c8078452e9e5962e518eab705e50a0aa8083733ea7d45f3a6"}, {file = "coverage-6.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fd9e830e9d8d89b20ab1e5af09b32d33e1a08ef4c4e14411e559556fd788e6b2"}, {file = "coverage-6.3.2-cp38-cp38-win32.whl", hash = "sha256:f7331dbf301b7289013175087636bbaf5b2405e57259dd2c42fdcc9fcc47325e"}, {file = "coverage-6.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:68353fe7cdf91f109fc7d474461b46e7f1f14e533e911a2a2cbb8b0fc8613cf1"}, {file = "coverage-6.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b78e5afb39941572209f71866aa0b206c12f0109835aa0d601e41552f9b3e620"}, {file = "coverage-6.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4e21876082ed887baed0146fe222f861b5815455ada3b33b890f4105d806128d"}, {file = "coverage-6.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34626a7eee2a3da12af0507780bb51eb52dca0e1751fd1471d0810539cefb536"}, {file = "coverage-6.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1ebf730d2381158ecf3dfd4453fbca0613e16eaa547b4170e2450c9707665ce7"}, {file = "coverage-6.3.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd6fe30bd519694b356cbfcaca9bd5c1737cddd20778c6a581ae20dc8c04def2"}, {file = "coverage-6.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:96f8a1cb43ca1422f36492bebe63312d396491a9165ed3b9231e778d43a7fca4"}, {file = "coverage-6.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:dd035edafefee4d573140a76fdc785dc38829fe5a455c4bb12bac8c20cfc3d69"}, {file = "coverage-6.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5ca5aeb4344b30d0bec47481536b8ba1181d50dbe783b0e4ad03c95dc1296684"}, {file = "coverage-6.3.2-cp39-cp39-win32.whl", hash = "sha256:f5fa5803f47e095d7ad8443d28b01d48c0359484fec1b9d8606d0e3282084bc4"}, {file = "coverage-6.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:9548f10d8be799551eb3a9c74bbf2b4934ddb330e08a73320123c07f95cc2d92"}, {file = "coverage-6.3.2-pp36.pp37.pp38-none-any.whl", hash = "sha256:18d520c6860515a771708937d2f78f63cc47ab3b80cb78e86573b0a760161faf"}, {file = "coverage-6.3.2.tar.gz", hash = "sha256:03e2a7826086b91ef345ff18742ee9fc47a6839ccd517061ef8fa1976e652ce9"}, ] coveralls = [ {file = "coveralls-3.3.1-py2.py3-none-any.whl", hash = "sha256:f42015f31d386b351d4226389b387ae173207058832fbf5c8ec4b40e27b16026"}, {file = "coveralls-3.3.1.tar.gz", hash = "sha256:b32a8bb5d2df585207c119d6c01567b81fba690c9c10a753bfe27a335bfc43ea"}, ] distlib = [ {file = "distlib-0.3.4-py2.py3-none-any.whl", hash = "sha256:6564fe0a8f51e734df6333d08b8b94d4ea8ee6b99b5ed50613f731fd4089f34b"}, {file = "distlib-0.3.4.zip", hash = "sha256:e4b58818180336dc9c529bfb9a0b58728ffc09ad92027a3f30b7cd91e3458579"}, ] docopt = [ {file = "docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"}, ] dunamai = [ {file = "dunamai-1.9.0-py3-none-any.whl", hash = "sha256:89213a9c1176313cdfda7091a15c6d94be8c17b25e57a8c1e2ecf10d902b25d2"}, {file = "dunamai-1.9.0.tar.gz", hash = "sha256:284dc9acbd3d6b749440332e46164f64b207c56eaf4af412d856cf9f08978932"}, ] execnet = [ {file = "execnet-1.9.0-py2.py3-none-any.whl", hash = "sha256:a295f7cc774947aac58dde7fdc85f4aa00c42adf5d8f5468fc630c1acf30a142"}, {file = "execnet-1.9.0.tar.gz", hash = "sha256:8f694f3ba9cc92cab508b152dcfe322153975c29bda272e2fd7f3f00f36e47c5"}, ] filelock = [ {file = "filelock-3.6.0-py3-none-any.whl", hash = "sha256:f8314284bfffbdcfa0ff3d7992b023d4c628ced6feb957351d4c48d059f56bc0"}, {file = "filelock-3.6.0.tar.gz", hash = "sha256:9cd540a9352e432c7246a48fe4e8712b10acb1df2ad1f30e8c070b82ae1fed85"}, ] flake8 = [ {file = "flake8-4.0.1-py2.py3-none-any.whl", hash = "sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d"}, {file = "flake8-4.0.1.tar.gz", hash = "sha256:806e034dda44114815e23c16ef92f95c91e4c71100ff52813adf7132a6ad870d"}, ] identify = [ {file = "identify-2.4.11-py2.py3-none-any.whl", hash = "sha256:fd906823ed1db23c7a48f9b176a1d71cb8abede1e21ebe614bac7bdd688d9213"}, {file = "identify-2.4.11.tar.gz", hash = "sha256:2986942d3974c8f2e5019a190523b0b0e2a07cb8e89bf236727fb4b26f27f8fd"}, ] idna = [ {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, ] importlib-metadata = [ {file = "importlib_metadata-4.2.0-py3-none-any.whl", hash = "sha256:057e92c15bc8d9e8109738a48db0ccb31b4d9d5cfbee5a8670879a30be66304b"}, {file = "importlib_metadata-4.2.0.tar.gz", hash = "sha256:b7e52a1f8dec14a75ea73e0891f3060099ca1d8e6a462a4dff11c3e119ea1b31"}, ] iniconfig = [ {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, ] isort = [ {file = "isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"}, {file = "isort-5.10.1.tar.gz", hash = "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"}, ] mccabe = [ {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, ] mypy = [ {file = "mypy-0.931-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3c5b42d0815e15518b1f0990cff7a705805961613e701db60387e6fb663fe78a"}, {file = "mypy-0.931-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c89702cac5b302f0c5d33b172d2b55b5df2bede3344a2fbed99ff96bddb2cf00"}, {file = "mypy-0.931-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:300717a07ad09525401a508ef5d105e6b56646f7942eb92715a1c8d610149714"}, {file = "mypy-0.931-cp310-cp310-win_amd64.whl", hash = "sha256:7b3f6f557ba4afc7f2ce6d3215d5db279bcf120b3cfd0add20a5d4f4abdae5bc"}, {file = "mypy-0.931-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:1bf752559797c897cdd2c65f7b60c2b6969ffe458417b8d947b8340cc9cec08d"}, {file = "mypy-0.931-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4365c60266b95a3f216a3047f1d8e3f895da6c7402e9e1ddfab96393122cc58d"}, {file = "mypy-0.931-cp36-cp36m-win_amd64.whl", hash = "sha256:1b65714dc296a7991000b6ee59a35b3f550e0073411ac9d3202f6516621ba66c"}, {file = "mypy-0.931-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e839191b8da5b4e5d805f940537efcaa13ea5dd98418f06dc585d2891d228cf0"}, {file = "mypy-0.931-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:50c7346a46dc76a4ed88f3277d4959de8a2bd0a0fa47fa87a4cde36fe247ac05"}, {file = "mypy-0.931-cp37-cp37m-win_amd64.whl", hash = "sha256:d8f1ff62f7a879c9fe5917b3f9eb93a79b78aad47b533911b853a757223f72e7"}, {file = "mypy-0.931-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f9fe20d0872b26c4bba1c1be02c5340de1019530302cf2dcc85c7f9fc3252ae0"}, {file = "mypy-0.931-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1b06268df7eb53a8feea99cbfff77a6e2b205e70bf31743e786678ef87ee8069"}, {file = "mypy-0.931-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8c11003aaeaf7cc2d0f1bc101c1cc9454ec4cc9cb825aef3cafff8a5fdf4c799"}, {file = "mypy-0.931-cp38-cp38-win_amd64.whl", hash = "sha256:d9d2b84b2007cea426e327d2483238f040c49405a6bf4074f605f0156c91a47a"}, {file = "mypy-0.931-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ff3bf387c14c805ab1388185dd22d6b210824e164d4bb324b195ff34e322d166"}, {file = "mypy-0.931-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5b56154f8c09427bae082b32275a21f500b24d93c88d69a5e82f3978018a0266"}, {file = "mypy-0.931-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8ca7f8c4b1584d63c9a0f827c37ba7a47226c19a23a753d52e5b5eddb201afcd"}, {file = "mypy-0.931-cp39-cp39-win_amd64.whl", hash = "sha256:74f7eccbfd436abe9c352ad9fb65872cc0f1f0a868e9d9c44db0893440f0c697"}, {file = "mypy-0.931-py3-none-any.whl", hash = "sha256:1171f2e0859cfff2d366da2c7092b06130f232c636a3f7301e3feb8b41f6377d"}, {file = "mypy-0.931.tar.gz", hash = "sha256:0038b21890867793581e4cb0d810829f5fd4441aa75796b53033af3aa30430ce"}, ] mypy-extensions = [ {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, ] nodeenv = [ {file = "nodeenv-1.6.0-py2.py3-none-any.whl", hash = "sha256:621e6b7076565ddcacd2db0294c0381e01fd28945ab36bcf00f41c5daf63bef7"}, {file = "nodeenv-1.6.0.tar.gz", hash = "sha256:3ef13ff90291ba2a4a7a4ff9a979b63ffdd00a464dbe04acf0ea6471517a4c2b"}, ] packaging = [ {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, ] pathspec = [ {file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"}, {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"}, ] platformdirs = [ {file = "platformdirs-2.5.1-py3-none-any.whl", hash = "sha256:bcae7cab893c2d310a711b70b24efb93334febe65f8de776ee320b517471e227"}, {file = "platformdirs-2.5.1.tar.gz", hash = "sha256:7535e70dfa32e84d4b34996ea99c5e432fa29a708d0f4e394bbcb2a8faa4f16d"}, ] pluggy = [ {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, ] pre-commit = [ {file = "pre_commit-2.17.0-py2.py3-none-any.whl", hash = "sha256:725fa7459782d7bec5ead072810e47351de01709be838c2ce1726b9591dad616"}, {file = "pre_commit-2.17.0.tar.gz", hash = "sha256:c1a8040ff15ad3d648c70cc3e55b93e4d2d5b687320955505587fd79bbaed06a"}, ] py = [ {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, ] pycodestyle = [ {file = "pycodestyle-2.8.0-py2.py3-none-any.whl", hash = "sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20"}, {file = "pycodestyle-2.8.0.tar.gz", hash = "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f"}, ] pyflakes = [ {file = "pyflakes-2.4.0-py2.py3-none-any.whl", hash = "sha256:3bb3a3f256f4b7968c9c788781e4ff07dce46bdf12339dcda61053375426ee2e"}, {file = "pyflakes-2.4.0.tar.gz", hash = "sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c"}, ] pyparsing = [ {file = "pyparsing-3.0.7-py3-none-any.whl", hash = "sha256:a6c06a88f252e6c322f65faf8f418b16213b51bdfaece0524c1c1bc30c63c484"}, {file = "pyparsing-3.0.7.tar.gz", hash = "sha256:18ee9022775d270c55187733956460083db60b37d0d0fb357445f3094eed3eea"}, ] pyright = [ {file = "pyright-0.0.13-py3-none-any.whl", hash = "sha256:520458c87dc456ed0d405a08a565066aaf5b76281d23861759cfaa3c73f5bda0"}, {file = "pyright-0.0.13.tar.gz", hash = "sha256:ca97f9121927847d0aea5d2e757a0bf6d3c25c116c90a3ce6263b4167a12dfc4"}, ] pytest = [ {file = "pytest-7.0.1-py3-none-any.whl", hash = "sha256:9ce3ff477af913ecf6321fe337b93a2c0dcf2a0a1439c43f5452112c1e4280db"}, {file = "pytest-7.0.1.tar.gz", hash = "sha256:e30905a0c131d3d94b89624a1cc5afec3e0ba2fbdb151867d8e0ebd49850f171"}, ] pytest-asyncio = [ {file = "pytest-asyncio-0.18.1.tar.gz", hash = "sha256:c43fcdfea2335dd82ffe0f2774e40285ddfea78a8e81e56118d47b6a90fbb09e"}, {file = "pytest_asyncio-0.18.1-py3-none-any.whl", hash = "sha256:c9ec48e8bbf5cc62755e18c4d8bc6907843ec9c5f4ac8f61464093baeba24a7e"}, ] pytest-forked = [ {file = "pytest-forked-1.4.0.tar.gz", hash = "sha256:8b67587c8f98cbbadfdd804539ed5455b6ed03802203485dd2f53c1422d7440e"}, {file = "pytest_forked-1.4.0-py3-none-any.whl", hash = "sha256:bbbb6717efc886b9d64537b41fb1497cfaf3c9601276be8da2cccfea5a3c8ad8"}, ] pytest-xdist = [ {file = "pytest-xdist-2.5.0.tar.gz", hash = "sha256:4580deca3ff04ddb2ac53eba39d76cb5dd5edeac050cb6fbc768b0dd712b4edf"}, {file = "pytest_xdist-2.5.0-py3-none-any.whl", hash = "sha256:6fe5c74fec98906deb8f2d2b616b5c782022744978e7bd4695d39c8f42d0ce65"}, ] pyyaml = [ {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"}, {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"}, {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"}, {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"}, {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, ] requests = [ {file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"}, {file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"}, ] six = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] toml = [ {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, ] tomli = [ {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] typed-ast = [ {file = "typed_ast-1.5.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:183b183b7771a508395d2cbffd6db67d6ad52958a5fdc99f450d954003900266"}, {file = "typed_ast-1.5.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:676d051b1da67a852c0447621fdd11c4e104827417bf216092ec3e286f7da596"}, {file = "typed_ast-1.5.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc2542e83ac8399752bc16e0b35e038bdb659ba237f4222616b4e83fb9654985"}, {file = "typed_ast-1.5.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:74cac86cc586db8dfda0ce65d8bcd2bf17b58668dfcc3652762f3ef0e6677e76"}, {file = "typed_ast-1.5.2-cp310-cp310-win_amd64.whl", hash = "sha256:18fe320f354d6f9ad3147859b6e16649a0781425268c4dde596093177660e71a"}, {file = "typed_ast-1.5.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:31d8c6b2df19a777bc8826770b872a45a1f30cfefcfd729491baa5237faae837"}, {file = "typed_ast-1.5.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:963a0ccc9a4188524e6e6d39b12c9ca24cc2d45a71cfdd04a26d883c922b4b78"}, {file = "typed_ast-1.5.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0eb77764ea470f14fcbb89d51bc6bbf5e7623446ac4ed06cbd9ca9495b62e36e"}, {file = "typed_ast-1.5.2-cp36-cp36m-win_amd64.whl", hash = "sha256:294a6903a4d087db805a7656989f613371915fc45c8cc0ddc5c5a0a8ad9bea4d"}, {file = "typed_ast-1.5.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:26a432dc219c6b6f38be20a958cbe1abffcc5492821d7e27f08606ef99e0dffd"}, {file = "typed_ast-1.5.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7407cfcad702f0b6c0e0f3e7ab876cd1d2c13b14ce770e412c0c4b9728a0f88"}, {file = "typed_ast-1.5.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f30ddd110634c2d7534b2d4e0e22967e88366b0d356b24de87419cc4410c41b7"}, {file = "typed_ast-1.5.2-cp37-cp37m-win_amd64.whl", hash = "sha256:8c08d6625bb258179b6e512f55ad20f9dfef019bbfbe3095247401e053a3ea30"}, {file = "typed_ast-1.5.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:90904d889ab8e81a956f2c0935a523cc4e077c7847a836abee832f868d5c26a4"}, {file = "typed_ast-1.5.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bbebc31bf11762b63bf61aaae232becb41c5bf6b3461b80a4df7e791fabb3aca"}, {file = "typed_ast-1.5.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c29dd9a3a9d259c9fa19d19738d021632d673f6ed9b35a739f48e5f807f264fb"}, {file = "typed_ast-1.5.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:58ae097a325e9bb7a684572d20eb3e1809802c5c9ec7108e85da1eb6c1a3331b"}, {file = "typed_ast-1.5.2-cp38-cp38-win_amd64.whl", hash = "sha256:da0a98d458010bf4fe535f2d1e367a2e2060e105978873c04c04212fb20543f7"}, {file = "typed_ast-1.5.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:33b4a19ddc9fc551ebabca9765d54d04600c4a50eda13893dadf67ed81d9a098"}, {file = "typed_ast-1.5.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1098df9a0592dd4c8c0ccfc2e98931278a6c6c53cb3a3e2cf7e9ee3b06153344"}, {file = "typed_ast-1.5.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42c47c3b43fe3a39ddf8de1d40dbbfca60ac8530a36c9b198ea5b9efac75c09e"}, {file = "typed_ast-1.5.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f290617f74a610849bd8f5514e34ae3d09eafd521dceaa6cf68b3f4414266d4e"}, {file = "typed_ast-1.5.2-cp39-cp39-win_amd64.whl", hash = "sha256:df05aa5b241e2e8045f5f4367a9f6187b09c4cdf8578bb219861c4e27c443db5"}, {file = "typed_ast-1.5.2.tar.gz", hash = "sha256:525a2d4088e70a9f75b08b3f87a51acc9cde640e19cc523c7e41aa355564ae27"}, ] typing-extensions = [ {file = "typing_extensions-4.1.1-py3-none-any.whl", hash = "sha256:21c85e0fe4b9a155d0799430b0ad741cdce7e359660ccbd8b530613e8df88ce2"}, {file = "typing_extensions-4.1.1.tar.gz", hash = "sha256:1a9462dcc3347a79b1f1c0271fbe79e844580bb598bafa1ed208b94da3cdcd42"}, ] urllib3 = [ {file = "urllib3-1.26.8-py2.py3-none-any.whl", hash = "sha256:000ca7f471a233c2251c6c7023ee85305721bfdf18621ebff4fd17a8653427ed"}, {file = "urllib3-1.26.8.tar.gz", hash = "sha256:0e7c33d9a63e7ddfcb86780aac87befc2fbddf46c58dbb487e0855f7ceec283c"}, ] virtualenv = [ {file = "virtualenv-20.13.2-py2.py3-none-any.whl", hash = "sha256:e7b34c9474e6476ee208c43a4d9ac1510b041c68347eabfe9a9ea0c86aa0a46b"}, {file = "virtualenv-20.13.2.tar.gz", hash = "sha256:01f5f80744d24a3743ce61858123488e91cb2dd1d3bdf92adaf1bba39ffdedf0"}, ] zipp = [ {file = "zipp-3.7.0-py3-none-any.whl", hash = "sha256:b47250dd24f92b7dd6a0a8fc5244da14608f3ca90a5efcd37a3b1642fac9a375"}, {file = "zipp-3.7.0.tar.gz", hash = "sha256:9f50f446828eb9d45b267433fd3e9da8d801f614129124863f9c51ebceafb87d"}, ] RxPY-4.0.4/pyproject.toml000066400000000000000000000042031426446175400153110ustar00rootroot00000000000000[tool.poetry] name = "reactivex" version = "0.0.0" # NOTE: will be updated by publish script description = "ReactiveX (Rx) for Python" readme = "README.rst" authors = ["Dag Brattli "] license = "MIT License" homepage = "http://reactivex.io" repository = "https://github.com/ReactiveX/RxPY" documentation = "https://rxpy.readthedocs.io/en/latest/" 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.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: Implementation :: CPython", "Topic :: Software Development :: Libraries :: Python Modules", ] packages = [ { include = "reactivex" }, { include = "reactivex/py.typed" } ] [tool.poetry.dependencies] python = ">= 3.7, < 4.0" typing-extensions = "^4.1.1" [tool.poetry.dev-dependencies] pytest-asyncio = "^0.18.1" pytest = "^7.0.1" coverage = "^6.3.2" pytest-xdist = "^2.5.0" black = "^22.1.0" isort = "^5.10.1" pyright = "^0.0.13" mypy = "^0.931" flake8 = "^4.0.1" coveralls = "^3.3.1" pre-commit = "^2.17.0" autoflake = "^1.4" dunamai = "^1.9.0" [tool.black] line-length = 88 target_version = ['py39'] include = '\.py$' [tool.isort] profile = "black" line_length=88 # corresponds to -w flag multi_line_output=3 # corresponds to -m flag include_trailing_comma=true # corresponds to -tc flag skip_glob = '^((?!py$).)*$' # isort all Python files float_to_top=true [tool.mypy] python_version = "3.9" follow_imports = "silent" files = ["reactivex"] exclude = ["reactivex/operators/_\\w.*\\.py$"] # mypy will eventually catch up disallow_any_generics = true disallow_untyped_defs = true [tool.pytest.ini_options] testpaths = ["tests"] asyncio_mode = "strict" #addopts = "-n auto" [build-system] requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" RxPY-4.0.4/pyrightconfig.json000066400000000000000000000003001426446175400161360ustar00rootroot00000000000000{ "include": [ "reactivex" ], "exclude": [ "tests" ], "reportImportCycles": false, "reportMissingImports": false, "pythonVersion": "3.9", "typeCheckingMode": "strict" }RxPY-4.0.4/reactivex/000077500000000000000000000000001426446175400143705ustar00rootroot00000000000000RxPY-4.0.4/reactivex/__init__.py000066400000000000000000001155141426446175400165100ustar00rootroot00000000000000# pylint: disable=too-many-lines,redefined-outer-name,redefined-builtin from asyncio import Future from typing import ( Any, Callable, Iterable, Mapping, Optional, Tuple, TypeVar, Union, overload, ) from . import abc, typing from ._version import __version__ from .internal.utils import alias from .notification import Notification from .observable import ConnectableObservable, GroupedObservable, Observable from .observer import Observer from .pipe import compose, pipe from .subject import Subject _T = TypeVar("_T") _T1 = TypeVar("_T1") _T2 = TypeVar("_T2") _TKey = TypeVar("_TKey") _TState = TypeVar("_TState") _A = TypeVar("_A") _B = TypeVar("_B") _C = TypeVar("_C") _D = TypeVar("_D") _E = TypeVar("_E") _F = TypeVar("_F") _G = TypeVar("_G") def amb(*sources: Observable[_T]) -> Observable[_T]: """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 = reactivex.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 .observable.amb import amb_ as amb_ return amb_(*sources) def case( mapper: Callable[[], _TKey], sources: Mapping[_TKey, Observable[_T]], default_source: Optional[Union[Observable[_T], "Future[_T]"]] = None, ) -> Observable[_T]: """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 = reactivex.case(mapper, { '1': obs1, '2': obs2 }) >>> res = reactivex.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 .observable.case import case_ return case_(mapper, sources, default_source) def catch(*sources: Observable[_T]) -> Observable[_T]: """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 = reactivex.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 .observable.catch import catch_with_iterable_ return catch_with_iterable_(sources) def catch_with_iterable(sources: Iterable[Observable[_T]]) -> Observable[_T]: """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 = reactivex.catch([xs, ys, zs]) >>> res = reactivex.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 .observable.catch import catch_with_iterable_ return catch_with_iterable_(sources) def create(subscribe: typing.Subscription[_T]) -> Observable[_T]: """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) @overload def combine_latest( __a: Observable[_A], __b: Observable[_B] ) -> Observable[Tuple[_A, _B]]: ... @overload def combine_latest( __a: Observable[_A], __b: Observable[_B], __c: Observable[_C] ) -> Observable[Tuple[_A, _B, _C]]: ... @overload def combine_latest( __a: Observable[_A], __b: Observable[_B], __c: Observable[_C], __d: Observable[_D] ) -> Observable[Tuple[_A, _B, _C, _D]]: ... def combine_latest(*__sources: Observable[Any]) -> Observable[Any]: """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 .observable.combinelatest import combine_latest_ return combine_latest_(*__sources) def concat(*sources: Observable[_T]) -> Observable[_T]: """Concatenates all of the specified observable sequences. .. marble:: :alt: concat ---1--2--3--| --6--8--| [ concat() ] ---1--2--3----6--8-| Examples: >>> res = reactivex.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 .observable.concat import concat_with_iterable_ return concat_with_iterable_(sources) def concat_with_iterable(sources: Iterable[Observable[_T]]) -> Observable[_T]: """Concatenates all of the specified observable sequences. .. marble:: :alt: concat ---1--2--3--| --6--8--| [ concat() ] ---1--2--3----6--8-| Examples: >>> res = reactivex.concat_with_iterable([xs, ys, zs]) >>> res = reactivex.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 .observable.concat import concat_with_iterable_ return concat_with_iterable_(sources) def defer( factory: Callable[[abc.SchedulerBase], Union[Observable[_T], "Future[_T]"]] ) -> Observable[_T]: """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 = reactivex.defer(lambda scheduler: of(1, 2, 3)) Args: factory: Observable factory function to invoke for each observer which invokes :func:`subscribe() ` on the resulting sequence. The factory takes a single argument, the scheduler used. Returns: An observable sequence whose observers trigger an invocation of the given factory function. """ from .observable.defer import defer_ return defer_(factory) def empty(scheduler: Optional[abc.SchedulerBase] = None) -> Observable[Any]: """Returns an empty observable sequence. .. marble:: :alt: empty [ empty() ] --| Example: >>> obs = reactivex.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 .observable.empty import empty_ return empty_(scheduler) def for_in( values: Iterable[_T1], mapper: typing.Mapper[_T1, Observable[_T2]] ) -> Observable[_T2]: """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:`reactivex.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:`reactivex.Observable`. Returns: An observable sequence from the concatenated observable sequences. """ mapped: Iterable[Observable[_T2]] = map(mapper, values) return concat_with_iterable(mapped) @overload def fork_join(__a: Observable[_A], __b: Observable[_B]) -> Observable[Tuple[_A, _B]]: ... @overload def fork_join( __a: Observable[_A], __b: Observable[_B], __c: Observable[_C] ) -> Observable[Tuple[_A, _B, _C]]: ... @overload def fork_join( __a: Observable[_A], __b: Observable[_B], __c: Observable[_C], __d: Observable[_D] ) -> Observable[Tuple[_A, _B, _C, _D]]: ... @overload def fork_join( __a: Observable[_A], __b: Observable[_B], __c: Observable[_C], __d: Observable[_D], __e: Observable[_E], ) -> Observable[Tuple[_A, _B, _C, _D, _E]]: ... def fork_join(*sources: Observable[Any]) -> Observable[Any]: """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 = reactivex.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 .observable.forkjoin import fork_join_ return fork_join_(*sources) def from_callable( supplier: Callable[[], _T], scheduler: Optional[abc.SchedulerBase] = None ) -> Observable[_T]: """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 = reactivex.from_callable(lambda: calculate_value()) >>> res = reactivex.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 .observable.returnvalue import from_callable_ return from_callable_(supplier, scheduler) def from_callback( func: Callable[..., Callable[..., None]], mapper: Optional[typing.Mapper[Any, Any]] = None, ) -> Callable[[], Observable[Any]]: """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 .observable.fromcallback import from_callback_ return from_callback_(func, mapper) def from_future(future: "Future[_T]") -> Observable[_T]: """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 Returns: An observable sequence which wraps the existing future success and failure. """ from .observable.fromfuture import from_future_ return from_future_(future) def from_iterable( iterable: Iterable[_T], scheduler: Optional[abc.SchedulerBase] = None ) -> Observable[_T]: """Converts an iterable to an observable sequence. .. marble:: :alt: from_iterable [ from_iterable(1,2,3) ] ---1--2--3--| Example: >>> reactivex.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 .observable.fromiterable import from_iterable_ as from_iterable_ return from_iterable_(iterable, scheduler) from_ = alias("from_", "Alias for :func:`reactivex.from_iterable`.", from_iterable) from_list = alias( "from_list", "Alias for :func:`reactivex.from_iterable`.", from_iterable ) def from_marbles( string: str, timespan: typing.RelativeTime = 0.1, scheduler: Optional[abc.SchedulerBase] = None, lookup: Optional[Mapping[Union[str, float], Any]] = None, error: Optional[Exception] = None, ) -> Observable[Any]: """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[_TState]: """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 = reactivex.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 .observable.generatewithrelativetime import generate_with_relative_time_ return generate_with_relative_time_(initial_state, condition, iterate, time_mapper) def generate( initial_state: _TState, condition: typing.Predicate[_TState], iterate: typing.Mapper[_TState, _TState], ) -> Observable[_TState]: """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 = reactivex.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 .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[abc.SchedulerBase] = None, lookup: Optional[Mapping[Union[str, float], Any]] = None, error: Optional[Exception] = None, ) -> Observable[Any]: """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 .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[_T], "Future[_T]"], else_source: Union[None, Observable[_T], "Future[_T]"] = None, ) -> Observable[_T]: """Determines whether an observable collection contains values. .. marble:: :alt: if_then ---1--2--3--| --6--8--| [ if_then() ] ---1--2--3--| Examples: >>> res = reactivex.if_then(condition, obs1) >>> res = reactivex.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 .observable.ifthen import if_then_ return if_then_(condition, then_source, else_source) def interval( period: typing.RelativeTime, scheduler: Optional[abc.SchedulerBase] = None ) -> Observable[int]: """Returns an observable sequence that produces a value after each period. .. marble:: :alt: interval [ interval() ] ---1---2---3---4---> Example: >>> res = reactivex.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 .observable.interval import interval_ return interval_(period, scheduler) def merge(*sources: Observable[Any]) -> Observable[Any]: """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 = reactivex.merge(obs1, obs2, obs3) Args: sources: Sequence of observables. Returns: The observable sequence that merges the elements of the observable sequences. """ from .observable.merge import merge_ return merge_(*sources) def never() -> Observable[Any]: """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 .observable.never import never_ return never_() def of(*args: _T) -> Observable[_T]: """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:`reactivex.from_iterable(args) ` Example: >>> res = reactivex.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[_T], "Future[_T]", Callable[[Optional[Exception]], Observable[_T]] ] ) -> Observable[_T]: """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 = reactivex.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 .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[abc.SchedulerBase] = None, ) -> Observable[int]: """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 = reactivex.range(10) >>> res = reactivex.range(0, 10) >>> res = reactivex.range(0, 10, 1) Args: start: The value of the first integer in the sequence. stop: [Optional] Generate number up to (exclusive) the stop value. Default is `sys.maxsize`. step: [Optional] The step to be used (default is 1). 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 .observable.range import range_ return range_(start, stop, step, scheduler) def return_value( value: _T, scheduler: Optional[abc.SchedulerBase] = None ) -> Observable[_T]: """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 = reactivex.return_value(42) >>> res = reactivex.return_value(42, timeout_scheduler) Args: value: Single element in the resulting observable sequence. Returns: An observable sequence containing the single specified element. """ from .observable.returnvalue import return_value_ return return_value_(value, scheduler) just = alias("just", "Alias for :func:`reactivex.return_value`.", return_value) def repeat_value(value: _T, repeat_count: Optional[int] = None) -> Observable[_T]: """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 = reactivex.repeat_value(42) >>> res = reactivex.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 .observable.repeat import repeat_value_ return repeat_value_(value, repeat_count) def start( func: Callable[[], _T], scheduler: Optional[abc.SchedulerBase] = None ) -> Observable[_T]: """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 = reactivex.start(lambda: pprint('hello')) >>> res = reactivex.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 .observable.start import start_ return start_(func, scheduler) def start_async(function_async: Callable[[], "Future[_T]"]) -> Observable[_T]: """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 .observable.startasync import start_async_ return start_async_(function_async) def throw( exception: Union[str, Exception], scheduler: Optional[abc.SchedulerBase] = None ) -> Observable[Any]: """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 = reactivex.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 .observable.throw import throw_ return throw_(exception, scheduler) def timer( duetime: typing.AbsoluteOrRelativeTime, period: Optional[typing.RelativeTime] = None, scheduler: Optional[abc.SchedulerBase] = None, ) -> Observable[int]: """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 = reactivex.timer(datetime(...)) >>> res = reactivex.timer(datetime(...), 0.1) >>> res = reactivex.timer(5.0) >>> res = reactivex.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 .observable.timer import timer_ return timer_(duetime, period, scheduler) def to_async( func: Callable[..., _T], scheduler: Optional[abc.SchedulerBase] = None ) -> Callable[..., Observable[_T]]: """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 = reactivex.to_async(lambda x, y: x + y)(4, 3) >>> res = reactivex.to_async(lambda x, y: x + y, Scheduler.timeout)(4, 3) >>> res = reactivex.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 .observable.toasync import to_async_ return to_async_(func, scheduler) def using( resource_factory: Callable[[], abc.DisposableBase], observable_factory: Callable[[abc.DisposableBase], Observable[_T]], ) -> Observable[_T]: """Constructs an observable sequence that depends on a resource object, whose lifetime is tied to the resulting observable sequence's lifetime. Example: >>> res = reactivex.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 .observable.using import using_ return using_(resource_factory, observable_factory) def with_latest_from(*sources: Observable[Any]) -> Observable[Tuple[Any, ...]]: """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 .observable.withlatestfrom import with_latest_from_ return with_latest_from_(*sources) def zip(*args: Observable[Any]) -> Observable[Tuple[Any, ...]]: """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 .observable.zip import zip_ return zip_(*args) __all__ = [ "abc", "amb", "case", "catch", "catch_with_iterable", "create", "combine_latest", "compose", "concat", "concat_with_iterable", "ConnectableObservable", "defer", "empty", "fork_join", "from_callable", "from_callback", "from_future", "from_iterable", "GroupedObservable", "never", "Notification", "on_error_resume_next", "of", "Observable", "Observer", "return_value", "pipe", "range", "repeat_value", "Subject", "start", "start_async", "throw", "timer", "typing", "to_async", "using", "with_latest_from", "zip", "__version__", ] RxPY-4.0.4/reactivex/_version.py000066400000000000000000000001111426446175400165570ustar00rootroot00000000000000__version__ = "0.0.0" # NOTE: version will be written by publish script RxPY-4.0.4/reactivex/abc/000077500000000000000000000000001426446175400151155ustar00rootroot00000000000000RxPY-4.0.4/reactivex/abc/__init__.py000066400000000000000000000011221426446175400172220ustar00rootroot00000000000000from .disposable import DisposableBase from .observable import ObservableBase, Subscription from .observer import ObserverBase, OnCompleted, OnError, OnNext from .periodicscheduler import PeriodicSchedulerBase from .scheduler import ScheduledAction, SchedulerBase from .startable import StartableBase from .subject import SubjectBase __all__ = [ "DisposableBase", "ObserverBase", "ObservableBase", "OnCompleted", "OnError", "OnNext", "SchedulerBase", "PeriodicSchedulerBase", "SubjectBase", "Subscription", "ScheduledAction", "StartableBase", ] RxPY-4.0.4/reactivex/abc/disposable.py000066400000000000000000000014721426446175400176200ustar00rootroot00000000000000from __future__ import annotations from abc import ABC, abstractmethod from types import TracebackType from typing import Optional, Type class DisposableBase(ABC): """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 def __enter__(self) -> DisposableBase: """Context management protocol.""" return self def __exit__( self, exctype: Optional[Type[BaseException]], excinst: Optional[BaseException], exctb: Optional[TracebackType], ) -> None: """Context management protocol.""" self.dispose() __all__ = ["DisposableBase"] RxPY-4.0.4/reactivex/abc/observable.py000066400000000000000000000024661426446175400176230ustar00rootroot00000000000000from abc import ABC, abstractmethod from typing import Callable, Generic, Optional, TypeVar, Union from .disposable import DisposableBase from .observer import ObserverBase, OnCompleted, OnError, OnNext from .scheduler import SchedulerBase _T_out = TypeVar("_T_out", covariant=True) class ObservableBase(Generic[_T_out], ABC): """Observable abstract base class. Represents a push-style collection.""" __slots__ = () @abstractmethod def subscribe( self, on_next: Optional[Union[OnNext[_T_out], ObserverBase[_T_out]]] = None, on_error: Optional[OnError] = None, on_completed: Optional[OnCompleted] = None, *, scheduler: Optional[SchedulerBase] = None, ) -> DisposableBase: """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 Subscription = Callable[[ObserverBase[_T_out], Optional[SchedulerBase]], DisposableBase] __all__ = ["ObservableBase", "Subscription"] RxPY-4.0.4/reactivex/abc/observer.py000066400000000000000000000021761426446175400173240ustar00rootroot00000000000000from abc import ABC, abstractmethod from typing import Callable, Generic, TypeVar _T = TypeVar("_T") _T_in = TypeVar("_T_in", contravariant=True) OnNext = Callable[[_T], None] OnError = Callable[[Exception], None] OnCompleted = Callable[[], None] class ObserverBase(Generic[_T_in], ABC): """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 __all__ = ["ObserverBase", "OnNext", "OnError", "OnCompleted"] RxPY-4.0.4/reactivex/abc/periodicscheduler.py000066400000000000000000000025001426446175400211610ustar00rootroot00000000000000from abc import ABC, abstractmethod from typing import Callable, Optional, TypeVar, Union from .disposable import DisposableBase from .scheduler import RelativeTime, ScheduledAction _TState = TypeVar("_TState") # Can be anything ScheduledPeriodicAction = Callable[[Optional[_TState]], Optional[_TState]] ScheduledSingleOrPeriodicAction = Union[ ScheduledAction[_TState], ScheduledPeriodicAction[_TState] ] class PeriodicSchedulerBase(ABC): """PeriodicScheduler abstract base class.""" __slots__ = () @abstractmethod def schedule_periodic( self, period: RelativeTime, action: ScheduledPeriodicAction[_TState], state: Optional[_TState] = None, ) -> DisposableBase: """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 __all__ = [ "PeriodicSchedulerBase", "ScheduledPeriodicAction", "ScheduledSingleOrPeriodicAction", "RelativeTime", ] RxPY-4.0.4/reactivex/abc/scheduler.py000066400000000000000000000104121426446175400174430ustar00rootroot00000000000000from abc import ABC, abstractmethod from datetime import datetime, timedelta from typing import Callable, Optional, TypeVar, Union from .disposable import DisposableBase _TState = TypeVar("_TState") # Can be anything AbsoluteTime = Union[datetime, float] RelativeTime = Union[timedelta, float] AbsoluteOrRelativeTime = Union[datetime, timedelta, float] ScheduledAction = Callable[ ["SchedulerBase", Optional[_TState]], Optional[DisposableBase], ] class SchedulerBase(ABC): """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[_TState], state: Optional[_TState] = None ) -> DisposableBase: """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[_TState], state: Optional[_TState] = None, ) -> DisposableBase: """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[_TState], state: Optional[_TState] = None, ) -> DisposableBase: """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 __all__ = [ "SchedulerBase", "AbsoluteTime", "RelativeTime", "AbsoluteOrRelativeTime", "ScheduledAction", ] RxPY-4.0.4/reactivex/abc/startable.py000066400000000000000000000004121426446175400174450ustar00rootroot00000000000000from abc import ABC, abstractmethod class StartableBase(ABC): """Abstract base class for Thread- and Process-like objects.""" __slots__ = () @abstractmethod def start(self) -> None: raise NotImplementedError __all__ = ["StartableBase"] RxPY-4.0.4/reactivex/abc/subject.py000066400000000000000000000035631426446175400171350ustar00rootroot00000000000000from abc import abstractmethod from typing import Optional, TypeVar, Union from .disposable import DisposableBase from .observable import ObservableBase from .observer import ObserverBase, OnCompleted, OnError, OnNext from .scheduler import SchedulerBase _T = TypeVar("_T") class SubjectBase(ObserverBase[_T], ObservableBase[_T]): """Subject abstract base class. Represents an object that is both an observable sequence as well as an observer. """ __slots__ = () @abstractmethod def subscribe( self, on_next: Optional[Union[OnNext[_T], ObserverBase[_T]]] = None, on_error: Optional[OnError] = None, on_completed: Optional[OnCompleted] = None, *, scheduler: Optional[SchedulerBase] = None, ) -> DisposableBase: """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) -> 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 __all__ = ["SubjectBase"] RxPY-4.0.4/reactivex/disposable/000077500000000000000000000000001426446175400165155ustar00rootroot00000000000000RxPY-4.0.4/reactivex/disposable/__init__.py000066400000000000000000000012221426446175400206230ustar00rootroot00000000000000from .booleandisposable import BooleanDisposable from .compositedisposable import CompositeDisposable from .disposable import Disposable from .multipleassignmentdisposable import MultipleAssignmentDisposable from .refcountdisposable import RefCountDisposable from .scheduleddisposable import ScheduledDisposable from .serialdisposable import SerialDisposable from .singleassignmentdisposable import SingleAssignmentDisposable __all__ = [ "BooleanDisposable", "CompositeDisposable", "Disposable", "MultipleAssignmentDisposable", "RefCountDisposable", "ScheduledDisposable", "SerialDisposable", "SingleAssignmentDisposable", ] RxPY-4.0.4/reactivex/disposable/booleandisposable.py000066400000000000000000000007421426446175400225570ustar00rootroot00000000000000from threading import RLock from reactivex.abc import DisposableBase class BooleanDisposable(DisposableBase): """Represents a Disposable that can be checked for status.""" def __init__(self) -> None: """Initializes a new instance of the BooleanDisposable class.""" self.is_disposed = False self.lock = RLock() super().__init__() def dispose(self) -> None: """Sets the status to disposed""" self.is_disposed = True RxPY-4.0.4/reactivex/disposable/compositedisposable.py000066400000000000000000000054001426446175400231360ustar00rootroot00000000000000from threading import RLock from typing import Any, List from reactivex import abc class CompositeDisposable(abc.DisposableBase): """Represents a group of disposable resources that are disposed together""" def __init__(self, *args: Any): if args and isinstance(args[0], list): self.disposable: List[abc.DisposableBase] = args[0] else: self.disposable = list(args) self.is_disposed = False self.lock = RLock() super(CompositeDisposable, self).__init__() def add(self, item: abc.DisposableBase) -> None: """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: abc.DisposableBase) -> bool: """Removes and disposes the first occurrence of a disposable from the CompositeDisposable.""" if self.is_disposed: return False 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) -> None: """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) -> None: """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: abc.DisposableBase) -> bool: """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) -> List[abc.DisposableBase]: return self.disposable[:] def __len__(self) -> int: return len(self.disposable) @property def length(self) -> int: return len(self.disposable) RxPY-4.0.4/reactivex/disposable/disposable.py000066400000000000000000000021541426446175400212160ustar00rootroot00000000000000from threading import RLock from typing import Optional from reactivex import typing from reactivex.abc import DisposableBase from reactivex.internal import noop from reactivex.typing import Action class Disposable(DisposableBase): """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 = 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-4.0.4/reactivex/disposable/multipleassignmentdisposable.py000066400000000000000000000026411426446175400250640ustar00rootroot00000000000000from threading import RLock from typing import Optional from reactivex.abc import DisposableBase class MultipleAssignmentDisposable(DisposableBase): """Represents a disposable resource whose underlying disposable resource can be replaced by another disposable resource.""" def __init__(self) -> None: self.current: Optional[DisposableBase] = None self.is_disposed = False self.lock = RLock() super().__init__() def get_disposable(self) -> Optional[DisposableBase]: return self.current def set_disposable(self, value: DisposableBase) -> None: """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) -> None: """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-4.0.4/reactivex/disposable/refcountdisposable.py000066400000000000000000000046641426446175400227740ustar00rootroot00000000000000from threading import RLock from typing import Optional from reactivex.abc import DisposableBase from .disposable import Disposable class RefCountDisposable(DisposableBase): """Represents a disposable resource that only disposes its underlying disposable resource when all dependent disposable objects have been disposed.""" class InnerDisposable(DisposableBase): def __init__(self, parent: "RefCountDisposable") -> None: self.parent: Optional[RefCountDisposable] = parent self.is_disposed = False self.lock = RLock() def dispose(self) -> None: with self.lock: parent = self.parent self.parent = None if parent is not None: parent.release() def __init__(self, disposable: DisposableBase) -> 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) -> DisposableBase: """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-4.0.4/reactivex/disposable/scheduleddisposable.py000066400000000000000000000021751426446175400231020ustar00rootroot00000000000000from threading import RLock from typing import Any from reactivex import abc from .singleassignmentdisposable import SingleAssignmentDisposable class ScheduledDisposable(abc.DisposableBase): """Represents a disposable resource whose disposal invocation will be scheduled on the specified Scheduler""" def __init__( self, scheduler: abc.SchedulerBase, disposable: abc.DisposableBase ) -> None: """Initializes a new instance of the ScheduledDisposable class that uses a Scheduler on which to dispose the disposable.""" self.scheduler = scheduler self.disposable = SingleAssignmentDisposable() self.disposable.disposable = disposable self.lock = RLock() super().__init__() @property def is_disposed(self) -> bool: return self.disposable.is_disposed def dispose(self) -> None: """Disposes the wrapped disposable on the provided scheduler.""" def action(scheduler: abc.SchedulerBase, state: Any) -> None: """Scheduled dispose action""" self.disposable.dispose() self.scheduler.schedule(action) RxPY-4.0.4/reactivex/disposable/serialdisposable.py000066400000000000000000000033211426446175400224130ustar00rootroot00000000000000from threading import RLock from typing import Optional from reactivex import abc class SerialDisposable(abc.DisposableBase): """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[abc.DisposableBase] = None self.is_disposed = False self.lock = RLock() super().__init__() def get_disposable(self) -> Optional[abc.DisposableBase]: return self.current def set_disposable(self, value: abc.DisposableBase) -> 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[abc.DisposableBase] = 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[abc.DisposableBase] = 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-4.0.4/reactivex/disposable/singleassignmentdisposable.py000066400000000000000000000030731426446175400245120ustar00rootroot00000000000000from threading import RLock from typing import Optional from reactivex.abc import DisposableBase class SingleAssignmentDisposable(DisposableBase): """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: bool = False self.current: Optional[DisposableBase] = None self.lock = RLock() super().__init__() def get_disposable(self) -> Optional[DisposableBase]: return self.current def set_disposable(self, value: DisposableBase) -> None: if self.current: raise Exception("Disposable has already been assigned") with self.lock: should_dispose = self.is_disposed if not should_dispose: self.current = value if self.is_disposed and value: value.dispose() disposable = property(get_disposable, set_disposable) def dispose(self) -> None: """Sets the status to disposed""" old: Optional[DisposableBase] = 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-4.0.4/reactivex/internal/000077500000000000000000000000001426446175400162045ustar00rootroot00000000000000RxPY-4.0.4/reactivex/internal/__init__.py000066400000000000000000000013321426446175400203140ustar00rootroot00000000000000from .basic import default_comparer, default_error, noop from .concurrency import default_thread_factory, synchronized from .constants import DELTA_ZERO, UTC_ZERO from .exceptions import ( ArgumentOutOfRangeException, DisposedException, SequenceContainsNoElementsError, ) from .priorityqueue import PriorityQueue from .utils import NotSet, add_ref, alias, infinite __all__ = [ "add_ref", "alias", "ArgumentOutOfRangeException", "DisposedException", "default_comparer", "default_error", "infinite", "noop", "NotSet", "SequenceContainsNoElementsError", "concurrency", "DELTA_ZERO", "UTC_ZERO", "synchronized", "default_thread_factory", "PriorityQueue", ] RxPY-4.0.4/reactivex/internal/basic.py000066400000000000000000000012161426446175400176370ustar00rootroot00000000000000from datetime import datetime from typing import Any, NoReturn, TypeVar, Union _T = TypeVar("_T") def noop(*args: Any, **kw: Any) -> None: """No operation. Returns nothing""" def identity(x: _T) -> _T: """Returns argument x""" return x def default_now() -> datetime: return datetime.utcnow() def default_comparer(x: _T, y: _T) -> bool: return x == y def default_sub_comparer(x: Any, y: Any) -> Any: return x - y def default_key_serializer(x: Any) -> str: return str(x) def default_error(err: Union[Exception, str]) -> NoReturn: if isinstance(err, BaseException): raise err raise Exception(err) RxPY-4.0.4/reactivex/internal/concurrency.py000066400000000000000000000012631426446175400211120ustar00rootroot00000000000000from threading import RLock, Thread from typing import Any, Callable, TypeVar from typing_extensions import ParamSpec from reactivex.typing import StartableTarget _T = TypeVar("_T") _P = ParamSpec("_P") def default_thread_factory(target: StartableTarget) -> Thread: return Thread(target=target, daemon=True) def synchronized(lock: RLock) -> Callable[[Callable[_P, _T]], Callable[_P, _T]]: """A decorator for synchronizing access to a given function.""" def wrapper(fn: Callable[_P, _T]) -> Callable[_P, _T]: def inner(*args: _P.args, **kw: _P.kwargs) -> Any: with lock: return fn(*args, **kw) return inner return wrapper RxPY-4.0.4/reactivex/internal/constants.py000066400000000000000000000001541426446175400205720ustar00rootroot00000000000000from datetime import datetime, timedelta DELTA_ZERO = timedelta(0) UTC_ZERO = datetime.utcfromtimestamp(0) RxPY-4.0.4/reactivex/internal/exceptions.py000066400000000000000000000017531426446175400207450ustar00rootroot00000000000000# Rx Exceptions from typing import Optional class SequenceContainsNoElementsError(Exception): def __init__(self, msg: Optional[str] = None): super().__init__(msg or "Sequence contains no elements") class ArgumentOutOfRangeException(ValueError): def __init__(self, msg: Optional[str] = None): super(ArgumentOutOfRangeException, self).__init__( msg or "Argument out of range" ) class DisposedException(Exception): def __init__(self, msg: Optional[str] = None): super().__init__(msg or "Object has been disposed") class ReEntracyException(Exception): def __init__(self, msg: Optional[str] = None): super().__init__(msg or "Re-entrancy detected") class CompletedException(Exception): def __init__(self, msg: Optional[str] = None): super().__init__(msg or "Observer completed") class WouldBlockException(Exception): def __init__(self, msg: Optional[str] = None): super().__init__(msg or "Would block") RxPY-4.0.4/reactivex/internal/priorityqueue.py000066400000000000000000000027231426446175400215100ustar00rootroot00000000000000import heapq from sys import maxsize from typing import Generic, List, Tuple, TypeVar _T1 = TypeVar("_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) -> int: """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) -> None: """Remove all items from the queue.""" self.items = [] self.count = PriorityQueue.MIN_COUNT RxPY-4.0.4/reactivex/internal/utils.py000066400000000000000000000033611426446175400177210ustar00rootroot00000000000000from functools import update_wrapper from types import FunctionType from typing import TYPE_CHECKING, Any, Callable, Iterable, Optional, TypeVar, cast from typing_extensions import ParamSpec from reactivex import abc from reactivex.disposable import CompositeDisposable from reactivex.disposable.refcountdisposable import RefCountDisposable if TYPE_CHECKING: from reactivex import Observable _T = TypeVar("_T") _P = ParamSpec("_P") def add_ref(xs: "Observable[_T]", r: RefCountDisposable) -> "Observable[_T]": from reactivex import Observable def subscribe( observer: abc.ObserverBase[Any], scheduler: Optional[abc.SchedulerBase] = None ) -> abc.DisposableBase: return CompositeDisposable(r.disposable, xs.subscribe(observer)) return Observable(subscribe) def infinite() -> Iterable[int]: n = 0 while True: yield n n += 1 def alias(name: str, doc: str, fun: Callable[_P, _T]) -> Callable[_P, _T]: # 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_ = update_wrapper(alias_, _fun) alias_.__kwdefaults__ = _fun.__kwdefaults__ alias_.__doc__ = doc alias_.__annotations__ = _fun.__annotations__ return cast(Callable[_P, _T], alias_) class NotSet: """Sentinel value.""" def __eq__(self, other: Any) -> bool: return self is other def __repr__(self) -> str: return "NotSet" __all__ = ["add_ref", "infinite", "alias", "NotSet"] RxPY-4.0.4/reactivex/notification.py000066400000000000000000000142531426446175400174350ustar00rootroot00000000000000from abc import abstractmethod from typing import Any, Callable, Generic, Optional, TypeVar, Union from reactivex import abc, typing from reactivex.scheduler import ImmediateScheduler from .observable import Observable from .observer import Observer _T = TypeVar("_T") class Notification(Generic[_T]): """Represents a notification to an observer.""" def __init__(self) -> None: """Default constructor used by derived types.""" self.has_value = False self.value: Optional[_T] = None self.kind: str = "" def accept( self, on_next: Union[typing.OnNext[_T], abc.ObserverBase[_T]], on_error: Optional[typing.OnError] = None, on_completed: Optional[typing.OnCompleted] = None, ) -> 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, abc.ObserverBase): return self._accept_observer(on_next) return self._accept(on_next, on_error, on_completed) @abstractmethod def _accept( self, on_next: typing.OnNext[_T], on_error: Optional[typing.OnError], on_completed: Optional[typing.OnCompleted], ) -> None: raise NotImplementedError @abstractmethod def _accept_observer(self, observer: abc.ObserverBase[_T]) -> None: raise NotImplementedError def to_observable( self, scheduler: Optional[abc.SchedulerBase] = None ) -> abc.ObservableBase[_T]: """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: abc.ObserverBase[_T], scheduler: Optional[abc.SchedulerBase] = None, ) -> abc.DisposableBase: def action(scheduler: abc.SchedulerBase, state: Any) -> None: self._accept_observer(observer) if self.kind == "N": observer.on_completed() __scheduler = scheduler or _scheduler return __scheduler.schedule(action) return Observable(subscribe) def equals(self, other: "Notification[_T]") -> bool: """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: Any) -> bool: return self.equals(other) class OnNext(Notification[_T]): """Represents an OnNext notification to an observer.""" def __init__(self, value: _T) -> None: """Constructs a notification of a new value.""" super(OnNext, self).__init__() self.value: _T = value self.has_value: bool = True self.kind: str = "N" def _accept( self, on_next: typing.OnNext[_T], on_error: Optional[typing.OnError] = None, on_completed: Optional[typing.OnCompleted] = None, ) -> None: return on_next(self.value) def _accept_observer(self, observer: abc.ObserverBase[_T]) -> None: return observer.on_next(self.value) def __str__(self) -> str: val: Any = self.value if isinstance(val, int): val = float(val) return "OnNext(%s)" % str(val) class OnError(Notification[_T]): """Represents an OnError notification to an observer.""" def __init__(self, error: Union[Exception, str]) -> None: """Constructs a notification of an exception.""" super(OnError, self).__init__() self.exception: Exception = ( error if isinstance(error, Exception) else Exception(error) ) self.kind = "E" def _accept( self, on_next: typing.OnNext[_T], on_error: Optional[typing.OnError], on_completed: Optional[typing.OnCompleted], ) -> None: return on_error(self.exception) if on_error else None def _accept_observer(self, observer: abc.ObserverBase[_T]) -> None: return observer.on_error(self.exception) def __str__(self) -> str: return "OnError(%s)" % str(self.exception) class OnCompleted(Notification[_T]): """Represents an OnCompleted notification to an observer.""" def __init__(self) -> None: """Constructs a notification of the end of a sequence.""" super(OnCompleted, self).__init__() self.kind = "C" def _accept( self, on_next: typing.OnNext[_T], on_error: Optional[typing.OnError], on_completed: Optional[typing.OnCompleted], ) -> None: return on_completed() if on_completed else None def _accept_observer(self, observer: abc.ObserverBase[_T]) -> None: return observer.on_completed() def __str__(self) -> str: return "OnCompleted()" def from_notifier(handler: Callable[[Notification[_T]], None]) -> Observer[_T]: """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: _T) -> None: return handler(OnNext(value)) def _on_error(error: Exception) -> None: return handler(OnError(error)) def _on_completed() -> None: return handler(OnCompleted()) return Observer(_on_next, _on_error, _on_completed) RxPY-4.0.4/reactivex/observable/000077500000000000000000000000001426446175400165145ustar00rootroot00000000000000RxPY-4.0.4/reactivex/observable/__init__.py000066400000000000000000000003251426446175400206250ustar00rootroot00000000000000from .connectableobservable import ConnectableObservable from .groupedobservable import GroupedObservable from .observable import Observable __all__ = ["Observable", "ConnectableObservable", "GroupedObservable"] RxPY-4.0.4/reactivex/observable/amb.py000066400000000000000000000012451426446175400176270ustar00rootroot00000000000000from typing import TypeVar from reactivex import Observable, never from reactivex import operators as _ _T = TypeVar("_T") def amb_(*sources: Observable[_T]) -> Observable[_T]: """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: Observable[_T] = never() def func(previous: Observable[_T], current: Observable[_T]) -> Observable[_T]: return _.amb(previous)(current) for source in sources: acc = func(acc, source) return acc __all__ = ["amb_"] RxPY-4.0.4/reactivex/observable/case.py000066400000000000000000000015751426446175400200110ustar00rootroot00000000000000from asyncio import Future from typing import Callable, Mapping, Optional, TypeVar, Union from reactivex import Observable, abc, defer, empty, from_future _Key = TypeVar("_Key") _T = TypeVar("_T") def case_( mapper: Callable[[], _Key], sources: Mapping[_Key, Observable[_T]], default_source: Optional[Union[Observable[_T], "Future[_T]"]] = None, ) -> Observable[_T]: default_source_: Union[Observable[_T], "Future[_T]"] = default_source or empty() def factory(_: abc.SchedulerBase) -> Observable[_T]: try: result: Union[Observable[_T], "Future[_T]"] = sources[mapper()] except KeyError: result = default_source_ if isinstance(result, Future): result_: Observable[_T] = from_future(result) else: result_ = result return result_ return defer(factory) __all__ = ["case_"] RxPY-4.0.4/reactivex/observable/catch.py000066400000000000000000000050071426446175400201520ustar00rootroot00000000000000from typing import Any, Iterable, Optional, TypeVar from reactivex import Observable, abc from reactivex.disposable import ( CompositeDisposable, Disposable, SerialDisposable, SingleAssignmentDisposable, ) from reactivex.scheduler import CurrentThreadScheduler _T = TypeVar("_T") def catch_with_iterable_(sources: Iterable[Observable[_T]]) -> Observable[_T]: """Continues an observable sequence that is terminated by an exception with the next observable sequence. Examples: >>> res = catch([xs, ys, zs]) >>> res = reactivex.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: abc.ObserverBase[_T], scheduler_: Optional[abc.SchedulerBase] = None ) -> abc.DisposableBase: _scheduler = scheduler_ or CurrentThreadScheduler.singleton() subscription = SerialDisposable() cancelable = SerialDisposable() last_exception = None is_disposed = False def action(scheduler: abc.SchedulerBase, state: Any = None) -> None: def on_error(exn: Exception) -> None: 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=scheduler_, ) cancelable.disposable = _scheduler.schedule(action) def dispose() -> None: nonlocal is_disposed is_disposed = True return CompositeDisposable(subscription, cancelable, Disposable(dispose)) return Observable(subscribe) __all__ = ["catch_with_iterable_"] RxPY-4.0.4/reactivex/observable/combinelatest.py000066400000000000000000000042571426446175400217270ustar00rootroot00000000000000from typing import Any, List, Optional, Tuple from reactivex import Observable, abc from reactivex.disposable import CompositeDisposable, SingleAssignmentDisposable def combine_latest_(*sources: Observable[Any]) -> Observable[Tuple[Any, ...]]: """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: abc.ObserverBase[Any], scheduler: Optional[abc.SchedulerBase] = None ) -> CompositeDisposable: n = len(sources) has_value = [False] * n has_value_all = [False] is_done = [False] * n values = [None] * n def _next(i: Any) -> None: 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: Any) -> None: is_done[i] = True if all(is_done): observer.on_completed() subscriptions: List[Optional[SingleAssignmentDisposable]] = [None] * n def func(i: int) -> None: subscriptions[i] = SingleAssignmentDisposable() def on_next(x: Any) -> None: with parent.lock: values[i] = x _next(i) def on_completed() -> None: with parent.lock: done(i) subscription = subscriptions[i] assert subscription subscription.disposable = sources[i].subscribe( on_next, observer.on_error, on_completed, scheduler=scheduler ) for idx in range(n): func(idx) return CompositeDisposable(subscriptions) return Observable(subscribe) __all__ = ["combine_latest_"] RxPY-4.0.4/reactivex/observable/concat.py000066400000000000000000000035341426446175400203420ustar00rootroot00000000000000from typing import Any, Iterable, Optional, TypeVar from reactivex import Observable, abc from reactivex.disposable import ( CompositeDisposable, Disposable, SerialDisposable, SingleAssignmentDisposable, ) from reactivex.scheduler import CurrentThreadScheduler _T = TypeVar("_T") def concat_with_iterable_(sources: Iterable[Observable[_T]]) -> Observable[_T]: def subscribe( observer: abc.ObserverBase[_T], scheduler_: Optional[abc.SchedulerBase] = None ) -> abc.DisposableBase: _scheduler = scheduler_ or CurrentThreadScheduler.singleton() sources_ = iter(sources) subscription = SerialDisposable() cancelable = SerialDisposable() is_disposed = False def action(scheduler: abc.SchedulerBase, state: Any = None) -> None: nonlocal is_disposed if is_disposed: return def on_completed() -> None: 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=scheduler_, ) cancelable.disposable = _scheduler.schedule(action) def dispose() -> None: nonlocal is_disposed is_disposed = True return CompositeDisposable(subscription, cancelable, Disposable(dispose)) return Observable(subscribe) __all__ = ["concat_with_iterable_"] RxPY-4.0.4/reactivex/observable/connectableobservable.py000066400000000000000000000053041426446175400234120ustar00rootroot00000000000000from typing import List, Optional, TypeVar from reactivex import abc from reactivex.disposable import CompositeDisposable, Disposable from .observable import Observable _T = TypeVar("_T") class ConnectableObservable(Observable[_T]): """Represents an observable that can be connected and disconnected.""" def __init__(self, source: abc.ObservableBase[_T], subject: abc.SubjectBase[_T]): self.subject = subject self.has_subscription = False self.subscription: Optional[abc.DisposableBase] = None self.source = source super().__init__() def _subscribe_core( self, observer: abc.ObserverBase[_T], scheduler: Optional[abc.SchedulerBase] = None, ) -> abc.DisposableBase: return self.subject.subscribe(observer, scheduler=scheduler) def connect( self, scheduler: Optional[abc.SchedulerBase] = None ) -> Optional[abc.DisposableBase]: """Connects the observable.""" if not self.has_subscription: self.has_subscription = True def dispose() -> None: 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: int = 1) -> Observable[_T]: """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: List[Optional[abc.DisposableBase]] = [None] count = [0] source = self is_connected = [False] if subscriber_count == 0: connectable_subscription[0] = source.connect() is_connected[0] = True def subscribe( observer: abc.ObserverBase[_T], scheduler: Optional[abc.SchedulerBase] = None, ) -> abc.DisposableBase: 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() -> None: subscription.dispose() count[0] -= 1 is_connected[0] = False return Disposable(dispose) return Observable(subscribe) RxPY-4.0.4/reactivex/observable/defer.py000066400000000000000000000026001426446175400201510ustar00rootroot00000000000000from asyncio import Future from typing import Callable, Optional, TypeVar, Union from reactivex import Observable, abc, from_future, throw from reactivex.scheduler import ImmediateScheduler _T = TypeVar("_T") def defer_( factory: Callable[[abc.SchedulerBase], Union[Observable[_T], "Future[_T]"]] ) -> Observable[_T]: """Returns an observable sequence that invokes the specified factory function whenever a new observer subscribes. Example: >>> res = defer(lambda scheduler: of(1, 2, 3)) Args: observable_factory: Observable factory function to invoke for each observer that subscribes to the resulting sequence. The factory takes a single argument, the scheduler used. Returns: An observable sequence whose observers trigger an invocation of the given observable factory function. """ def subscribe( observer: abc.ObserverBase[_T], scheduler: Optional[abc.SchedulerBase] = None ) -> abc.DisposableBase: try: result = factory(scheduler or ImmediateScheduler.singleton()) except Exception as ex: # By design. pylint: disable=W0703 return throw(ex).subscribe(observer) result = from_future(result) if isinstance(result, Future) else result return result.subscribe(observer, scheduler=scheduler) return Observable(subscribe) __all__ = ["defer_"] RxPY-4.0.4/reactivex/observable/empty.py000066400000000000000000000011501426446175400202210ustar00rootroot00000000000000from typing import Any, Optional from reactivex import Observable, abc from reactivex.scheduler import ImmediateScheduler def empty_(scheduler: Optional[abc.SchedulerBase] = None) -> Observable[Any]: def subscribe( observer: abc.ObserverBase[Any], scheduler_: Optional[abc.SchedulerBase] = None ) -> abc.DisposableBase: _scheduler = scheduler or scheduler_ or ImmediateScheduler.singleton() def action(_: abc.SchedulerBase, __: Any) -> None: observer.on_completed() return _scheduler.schedule(action) return Observable(subscribe) __all__ = ["empty_"] RxPY-4.0.4/reactivex/observable/forkjoin.py000066400000000000000000000042151426446175400207110ustar00rootroot00000000000000from typing import Any, List, Optional, Tuple, cast from reactivex import Observable, abc from reactivex.disposable import CompositeDisposable, SingleAssignmentDisposable def fork_join_(*sources: Observable[Any]) -> Observable[Tuple[Any, ...]]: """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 = reactivex.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: abc.ObserverBase[Tuple[Any, ...]], scheduler: Optional[abc.SchedulerBase] = None, ) -> abc.DisposableBase: n = len(sources) values = [None] * n is_done = [False] * n has_value = [False] * n def done(i: int) -> None: 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: List[SingleAssignmentDisposable] = [ cast(SingleAssignmentDisposable, None) ] * n def _subscribe(i: int) -> None: subscriptions[i] = SingleAssignmentDisposable() def on_next(value: Any) -> None: with parent.lock: values[i] = value has_value[i] = True def on_completed() -> None: with parent.lock: done(i) subscriptions[i].disposable = sources[i].subscribe( on_next, observer.on_error, on_completed, scheduler=scheduler ) for i in range(n): _subscribe(i) return CompositeDisposable(subscriptions) return Observable(subscribe) __all__ = ["fork_join_"] RxPY-4.0.4/reactivex/observable/fromcallback.py000066400000000000000000000035261426446175400215140ustar00rootroot00000000000000from typing import Any, Callable, Optional from reactivex import Observable, abc, typing from reactivex.disposable import Disposable def from_callback_( func: Callable[..., Callable[..., None]], mapper: Optional[typing.Mapper[Any, Any]] = None, ) -> Callable[[], Observable[Any]]: """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: Any) -> Observable[Any]: arguments = list(args) def subscribe( observer: abc.ObserverBase[Any], scheduler: Optional[abc.SchedulerBase] = None, ) -> abc.DisposableBase: def handler(*args: Any) -> None: 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 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 __all__ = ["from_callback_"] RxPY-4.0.4/reactivex/observable/fromfuture.py000066400000000000000000000025751426446175400212750ustar00rootroot00000000000000import asyncio from asyncio import Future from typing import Any, Optional, TypeVar, cast from reactivex import Observable, abc from reactivex.disposable import Disposable _T = TypeVar("_T") def from_future_(future: "Future[_T]") -> Observable[_T]: """Converts a Future to an Observable sequence Args: future -- A Python 3 compatible future. https://docs.python.org/3/library/asyncio-task.html#future Returns: An Observable sequence which wraps the existing future success and failure. """ def subscribe( observer: abc.ObserverBase[Any], scheduler: Optional[abc.SchedulerBase] = None ) -> abc.DisposableBase: def done(future: "Future[_T]") -> None: try: value: Any = future.result() except Exception as ex: observer.on_error(ex) except asyncio.CancelledError as ex: # pylint: disable=broad-except # asyncio.CancelledError is a BaseException, so need to cast observer.on_error(cast(Exception, ex)) else: observer.on_next(value) observer.on_completed() future.add_done_callback(done) def dispose() -> None: if future: future.cancel() return Disposable(dispose) return Observable(subscribe) __all__ = ["from_future_"] RxPY-4.0.4/reactivex/observable/fromiterable.py000066400000000000000000000031641426446175400215450ustar00rootroot00000000000000from typing import Any, Iterable, Optional, TypeVar from reactivex import Observable, abc from reactivex.disposable import CompositeDisposable, Disposable from reactivex.scheduler import CurrentThreadScheduler _T = TypeVar("_T") def from_iterable_( iterable: Iterable[_T], scheduler: Optional[abc.SchedulerBase] = None ) -> Observable[_T]: """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: abc.ObserverBase[_T], scheduler_: Optional[abc.SchedulerBase] = None ) -> abc.DisposableBase: _scheduler = scheduler or scheduler_ or CurrentThreadScheduler.singleton() iterator = iter(iterable) disposed = False def action(_: abc.SchedulerBase, __: 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) __all__ = ["from_iterable_"] RxPY-4.0.4/reactivex/observable/generate.py000066400000000000000000000031571426446175400206660ustar00rootroot00000000000000from typing import Any, Optional, TypeVar, cast from reactivex import Observable, abc, typing from reactivex.disposable import MultipleAssignmentDisposable from reactivex.scheduler import CurrentThreadScheduler _TState = TypeVar("_TState") def generate_( initial_state: _TState, condition: typing.Predicate[_TState], iterate: typing.Mapper[_TState, _TState], ) -> Observable[_TState]: def subscribe( observer: abc.ObserverBase[_TState], scheduler: Optional[abc.SchedulerBase] = None, ) -> abc.DisposableBase: scheduler = scheduler or CurrentThreadScheduler.singleton() first = True state = initial_state mad = MultipleAssignmentDisposable() def action(scheduler: abc.SchedulerBase, state1: Any = None) -> None: nonlocal first nonlocal state has_result = False result: _TState = cast(_TState, 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) __all__ = ["generate_"] RxPY-4.0.4/reactivex/observable/generatewithrelativetime.py000066400000000000000000000052271426446175400241750ustar00rootroot00000000000000from typing import Any, Callable, Optional, TypeVar, cast from reactivex import Observable, abc from reactivex.disposable import MultipleAssignmentDisposable from reactivex.scheduler import TimeoutScheduler from reactivex.typing import Mapper, Predicate, RelativeTime _TState = TypeVar("_TState") def generate_with_relative_time_( initial_state: _TState, condition: Predicate[_TState], iterate: Mapper[_TState, _TState], time_mapper: Callable[[_TState], RelativeTime], ) -> Observable[_TState]: """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: abc.ObserverBase[_TState], scheduler: Optional[abc.SchedulerBase] = None, ) -> abc.DisposableBase: scheduler = scheduler or TimeoutScheduler.singleton() mad = MultipleAssignmentDisposable() state = initial_state has_result = False result: _TState = cast(_TState, None) first = True time: Optional[RelativeTime] = None def action(scheduler: abc.SchedulerBase, _: Any) -> None: 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: assert time mad.disposable = scheduler.schedule_relative(time, action) else: observer.on_completed() mad.disposable = scheduler.schedule_relative(0, action) return mad return Observable(subscribe) __all__ = ["generate_with_relative_time_"] RxPY-4.0.4/reactivex/observable/groupedobservable.py000066400000000000000000000024041426446175400226000ustar00rootroot00000000000000from typing import Generic, Optional, TypeVar from reactivex import abc from reactivex.disposable import CompositeDisposable, Disposable, RefCountDisposable from .observable import Observable _T = TypeVar("_T") _TKey = TypeVar("_TKey") class GroupedObservable(Generic[_TKey, _T], Observable[_T]): def __init__( self, key: _TKey, underlying_observable: Observable[_T], merged_disposable: Optional[RefCountDisposable] = None, ): super().__init__() self.key = key def subscribe( observer: abc.ObserverBase[_T], scheduler: Optional[abc.SchedulerBase] = None, ) -> abc.DisposableBase: return CompositeDisposable( merged_disposable.disposable if merged_disposable else Disposable(), underlying_observable.subscribe(observer, scheduler=scheduler), ) self.underlying_observable = ( underlying_observable if not merged_disposable else Observable(subscribe) ) def _subscribe_core( self, observer: abc.ObserverBase[_T], scheduler: Optional[abc.SchedulerBase] = None, ) -> abc.DisposableBase: return self.underlying_observable.subscribe(observer, scheduler=scheduler) RxPY-4.0.4/reactivex/observable/ifthen.py000066400000000000000000000032031426446175400203410ustar00rootroot00000000000000from asyncio import Future from typing import Callable, TypeVar, Union import reactivex from reactivex import Observable, abc _T = TypeVar("_T") def if_then_( condition: Callable[[], bool], then_source: Union[Observable[_T], "Future[_T]"], else_source: Union[None, Observable[_T], "Future[_T]"] = None, ) -> Observable[_T]: """Determines whether an observable collection contains values. Example: 1 - res = reactivex.if_then(condition, obs1) 2 - res = reactivex.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 reactivex.empty Returns: An observable sequence which is either the then_source or else_source. """ else_source_: Union[Observable[_T], "Future[_T]"] = else_source or reactivex.empty() then_source = ( reactivex.from_future(then_source) if isinstance(then_source, Future) else then_source ) else_source_ = ( reactivex.from_future(else_source_) if isinstance(else_source_, Future) else else_source_ ) def factory(_: abc.SchedulerBase) -> Union[Observable[_T], "Future[_T]"]: return then_source if condition() else else_source_ return reactivex.defer(factory) __all__ = ["if_then_"] RxPY-4.0.4/reactivex/observable/interval.py000066400000000000000000000004171426446175400207140ustar00rootroot00000000000000from typing import Optional from reactivex import Observable, abc, timer, typing def interval_( period: typing.RelativeTime, scheduler: Optional[abc.SchedulerBase] = None ) -> Observable[int]: return timer(period, period, scheduler) __all__ = ["interval_"] RxPY-4.0.4/reactivex/observable/marbles.py000066400000000000000000000220461426446175400205170ustar00rootroot00000000000000import re import threading from datetime import datetime, timedelta from typing import Any, List, Mapping, Optional, Tuple, Union from reactivex import Notification, Observable, abc, notification, typing from reactivex.disposable import CompositeDisposable, Disposable from reactivex.scheduler import NewThreadScheduler 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: typing.RelativeTime = 0.1, duetime: typing.AbsoluteOrRelativeTime = 0.0, lookup: Optional[Mapping[Union[str, float], Any]] = None, error: Optional[Exception] = None, scheduler: Optional[abc.SchedulerBase] = None, ) -> Observable[Any]: _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: List[abc.ObserverBase[Any]] = [] def subscribe( observer: abc.ObserverBase[Any], scheduler: Optional[abc.SchedulerBase] = None ) -> abc.DisposableBase: # 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() -> None: with lock: try: observers.remove(observer) except ValueError: pass return Disposable(dispose) def create_action(notification: Notification[Any]) -> typing.ScheduledAction[Any]: def action(scheduler: abc.SchedulerBase, state: Any = None) -> 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: typing.RelativeTime = 0.1, lookup: Optional[Mapping[Union[str, float], Any]] = None, error: Optional[Exception] = None, scheduler: Optional[abc.SchedulerBase] = None, ) -> Observable[Any]: messages = parse( string, timespan=timespan, lookup=lookup, error=error, raise_stopped=True ) def subscribe( observer: abc.ObserverBase[Any], scheduler_: Optional[abc.SchedulerBase] = None ) -> abc.DisposableBase: _scheduler = scheduler or scheduler_ or new_thread_scheduler disp = CompositeDisposable() def schedule_msg( message: Tuple[typing.RelativeTime, Notification[Any]] ) -> None: duetime, notification = message def action(scheduler: abc.SchedulerBase, state: Any = None) -> None: 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: typing.RelativeTime = 1.0, time_shift: typing.RelativeTime = 0.0, lookup: Optional[Mapping[Union[str, float], Any]] = None, error: Optional[Exception] = None, raise_stopped: bool = False, ) -> List[Tuple[typing.RelativeTime, notification.Notification[Any]]]: """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: str) -> Union[float, str]: try: return int(element) except ValueError: try: return float(element) except ValueError: return element def map_element( time: typing.RelativeTime, element: str ) -> Tuple[typing.RelativeTime, Notification[Any]]: 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: str) -> None: 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: List[Tuple[typing.RelativeTime, Notification[Any]]] = [] 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-4.0.4/reactivex/observable/merge.py000066400000000000000000000004341426446175400201660ustar00rootroot00000000000000from typing import TypeVar import reactivex from reactivex import Observable from reactivex import operators as ops _T = TypeVar("_T") def merge_(*sources: Observable[_T]) -> Observable[_T]: return reactivex.from_iterable(sources).pipe(ops.merge_all()) __all__ = ["merge_"] RxPY-4.0.4/reactivex/observable/never.py000066400000000000000000000011351426446175400202050ustar00rootroot00000000000000from typing import Any, Optional from reactivex import Observable, abc from reactivex.disposable import Disposable def never_() -> Observable[Any]: """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: abc.ObserverBase[Any], scheduler: Optional[abc.SchedulerBase] = None ) -> abc.DisposableBase: return Disposable() return Observable(subscribe) __all__ = ["never_"] RxPY-4.0.4/reactivex/observable/observable.py000066400000000000000000000264221426446175400212200ustar00rootroot00000000000000# By design, pylint: disable=C0302 from __future__ import annotations import asyncio import threading from typing import Any, Callable, Generator, Optional, TypeVar, Union, cast, overload from reactivex import abc from reactivex.disposable import Disposable from reactivex.scheduler import CurrentThreadScheduler from reactivex.scheduler.eventloop import AsyncIOScheduler from ..observer import AutoDetachObserver _A = TypeVar("_A") _B = TypeVar("_B") _C = TypeVar("_C") _D = TypeVar("_D") _E = TypeVar("_E") _F = TypeVar("_F") _G = TypeVar("_G") _T_out = TypeVar("_T_out", covariant=True) class Observable(abc.ObservableBase[_T_out]): """Observable base class. Represents a push-style collection, which you can :func:`pipe ` into :mod:`operators `.""" def __init__(self, subscribe: Optional[abc.Subscription[_T_out]] = 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: abc.ObserverBase[_T_out], scheduler: Optional[abc.SchedulerBase] = None, ) -> abc.DisposableBase: return self._subscribe(observer, scheduler) if self._subscribe else Disposable() def subscribe( self, on_next: Optional[ Union[abc.ObserverBase[_T_out], abc.OnNext[_T_out], None] ] = None, on_error: Optional[abc.OnError] = None, on_completed: Optional[abc.OnCompleted] = None, *, scheduler: Optional[abc.SchedulerBase] = None, ) -> abc.DisposableBase: """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 <..abc.ObserverBase>` 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 ( isinstance(on_next, abc.ObserverBase) or hasattr(on_next, "on_next") and callable(getattr(on_next, "on_next")) ): obv = cast(abc.ObserverBase[_T_out], on_next) on_next = obv.on_next on_error = obv.on_error on_completed = obv.on_completed auto_detach_observer: AutoDetachObserver[_T_out] = AutoDetachObserver( on_next, on_error, on_completed ) def fix_subscriber( subscriber: Union[abc.DisposableBase, Callable[[], None]] ) -> abc.DisposableBase: """Fixes subscriber to make sure it returns a Disposable instead of None or a dispose function""" if isinstance(subscriber, abc.DisposableBase) or hasattr( subscriber, "dispose" ): # Note: cast can be avoided using Protocols (Python 3.9) return cast(abc.DisposableBase, subscriber) return Disposable(subscriber) def set_disposable( _: Optional[abc.SchedulerBase] = None, __: Any = None ) -> 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, __op1: Callable[[Observable[_T_out]], _A]) -> _A: ... @overload def pipe( self, __op1: Callable[[Observable[_T_out]], _A], __op2: Callable[[_A], _B], ) -> _B: ... @overload def pipe( self, __op1: Callable[[Observable[_T_out]], _A], __op2: Callable[[_A], _B], __op3: Callable[[_B], _C], ) -> _C: ... @overload def pipe( self, __op1: Callable[[Observable[_T_out]], _A], __op2: Callable[[_A], _B], __op3: Callable[[_B], _C], __op4: Callable[[_C], _D], ) -> _D: ... @overload def pipe( self, __op1: Callable[[Observable[_T_out]], _A], __op2: Callable[[_A], _B], __op3: Callable[[_B], _C], __op4: Callable[[_C], _D], __op5: Callable[[_D], _E], ) -> _E: ... @overload def pipe( self, __op1: Callable[[Observable[_T_out]], _A], __op2: Callable[[_A], _B], __op3: Callable[[_B], _C], __op4: Callable[[_C], _D], __op5: Callable[[_D], _E], __op6: Callable[[_E], _F], ) -> _F: ... @overload def pipe( self, __op1: Callable[[Observable[_T_out]], _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: ... def pipe(self, *operators: Callable[[Any], 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 as pipe_ return pipe_(self, *operators) 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) -> Generator[Any, None, _T_out]: """Awaits the given observable. Returns: The last item of the observable sequence. """ from ..operators._tofuture import to_future_ loop = asyncio.get_event_loop() future: asyncio.Future[_T_out] = self.pipe( to_future_(scheduler=AsyncIOScheduler(loop=loop)) ) return future.__await__() def __add__(self, other: Observable[_T_out]) -> Observable[_T_out]: """Pythonic version of :func:`concat `. Example: >>> zs = xs + ys Args: other: The second observable sequence in the concatenation. Returns: Concatenated observable sequence. """ from reactivex import concat return concat(self, other) def __iadd__(self, other: Observable[_T_out]) -> "Observable[_T_out]": """Pythonic use of :func:`concat `. Example: >>> xs += ys Args: other: The second observable sequence in the concatenation. Returns: Concatenated observable sequence. """ from reactivex import concat return concat(self, other) def __getitem__(self, key: Union[slice, int]) -> Observable[_T_out]: """ 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 else: start, stop, step = key, key + 1, 1 from ..operators._slice import slice_ return slice_(start, stop, step)(self) __all__ = ["Observable"] RxPY-4.0.4/reactivex/observable/onerrorresumenext.py000066400000000000000000000042561426446175400227030ustar00rootroot00000000000000from asyncio import Future from typing import Callable, Optional, TypeVar, Union import reactivex from reactivex import Observable, abc from reactivex.disposable import ( CompositeDisposable, SerialDisposable, SingleAssignmentDisposable, ) from reactivex.scheduler import CurrentThreadScheduler _T = TypeVar("_T") def on_error_resume_next_( *sources: Union[ Observable[_T], "Future[_T]", Callable[[Optional[Exception]], Observable[_T]] ] ) -> Observable[_T]: """Continues an observable sequence that is terminated normally or by an exception with the next observable sequence. Examples: >>> res = reactivex.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: abc.ObserverBase[_T], scheduler: Optional[abc.SchedulerBase] = None ) -> abc.DisposableBase: scheduler = scheduler or CurrentThreadScheduler.singleton() subscription = SerialDisposable() cancelable = SerialDisposable() def action( scheduler: abc.SchedulerBase, state: Optional[Exception] = None ) -> 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 = ( reactivex.from_future(source) if isinstance(source, Future) else source ) d = SingleAssignmentDisposable() subscription.disposable = d def on_resume(state: Optional[Exception] = None) -> None: scheduler.schedule(action, state) d.disposable = current.subscribe( observer.on_next, on_resume, on_resume, scheduler=scheduler ) cancelable.disposable = scheduler.schedule(action) return CompositeDisposable(subscription, cancelable) return Observable(subscribe) __all__ = ["on_error_resume_next_"] RxPY-4.0.4/reactivex/observable/range.py000066400000000000000000000041471426446175400201700ustar00rootroot00000000000000from sys import maxsize from typing import Iterator, Optional from reactivex import Observable, abc from reactivex.disposable import MultipleAssignmentDisposable from reactivex.scheduler import CurrentThreadScheduler def range_( start: int, stop: Optional[int] = None, step: Optional[int] = None, scheduler: Optional[abc.SchedulerBase] = None, ) -> Observable[int]: """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. stop: [Optional] Generate number up to (exclusive) the stop value. Default is `sys.maxsize`. step: [Optional] The step to be used (default is 1). 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: abc.ObserverBase[int], scheduler_: Optional[abc.SchedulerBase] = None ) -> abc.DisposableBase: nonlocal range_t _scheduler = scheduler or scheduler_ or CurrentThreadScheduler.singleton() sd = MultipleAssignmentDisposable() def action( scheduler: abc.SchedulerBase, iterator: Optional[Iterator[int]] ) -> None: try: assert iterator 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) __all__ = ["range_"] RxPY-4.0.4/reactivex/observable/repeat.py000066400000000000000000000015721426446175400203530ustar00rootroot00000000000000from typing import Optional, TypeVar import reactivex from reactivex import Observable from reactivex import operators as ops _T = TypeVar("_T") def repeat_value_(value: _T, repeat_count: Optional[int] = None) -> Observable[_T]: """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 = reactivex.return_value(value) return xs.pipe(ops.repeat(repeat_count)) __all__ = ["repeat_value_"] RxPY-4.0.4/reactivex/observable/returnvalue.py000066400000000000000000000036151426446175400214470ustar00rootroot00000000000000from typing import Any, Callable, Optional, TypeVar from reactivex import Observable, abc from reactivex.scheduler import CurrentThreadScheduler _T = TypeVar("_T") def return_value_( value: _T, scheduler: Optional[abc.SchedulerBase] = None ) -> Observable[_T]: """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: abc.ObserverBase[_T], scheduler_: Optional[abc.SchedulerBase] = None ) -> abc.DisposableBase: _scheduler = scheduler or scheduler_ or CurrentThreadScheduler.singleton() def action(scheduler: abc.SchedulerBase, state: Any = None) -> None: observer.on_next(value) observer.on_completed() return _scheduler.schedule(action) return Observable(subscribe) def from_callable_( supplier: Callable[[], _T], scheduler: Optional[abc.SchedulerBase] = None ) -> Observable[_T]: def subscribe( observer: abc.ObserverBase[_T], scheduler_: Optional[abc.SchedulerBase] = None ) -> abc.DisposableBase: _scheduler = scheduler or scheduler_ or CurrentThreadScheduler.singleton() def action(_: abc.SchedulerBase, __: Any = None) -> 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) __all__ = ["return_value_", "from_callable_"] RxPY-4.0.4/reactivex/observable/start.py000066400000000000000000000021031426446175400202170ustar00rootroot00000000000000from typing import Callable, Optional, TypeVar from reactivex import Observable, abc, to_async _T = TypeVar("_T") def start_( func: Callable[[], _T], scheduler: Optional[abc.SchedulerBase] = None ) -> Observable[_T]: """Invokes the specified function asynchronously on the specified scheduler, surfacing the result through an observable sequence. Example: >>> res = reactivex.start(lambda: pprint('hello')) >>> res = reactivex.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)() __all__ = ["start_"] RxPY-4.0.4/reactivex/observable/startasync.py000066400000000000000000000006311426446175400212610ustar00rootroot00000000000000from asyncio import Future from typing import Callable, TypeVar from reactivex import Observable, from_future, throw _T = TypeVar("_T") def start_async_(function_async: Callable[[], "Future[_T]"]) -> Observable[_T]: try: future = function_async() except Exception as ex: # pylint: disable=broad-except return throw(ex) return from_future(future) __all__ = ["start_async_"] RxPY-4.0.4/reactivex/observable/throw.py000066400000000000000000000013621426446175400202330ustar00rootroot00000000000000from typing import Any, Optional, Union from reactivex import Observable, abc from reactivex.scheduler import ImmediateScheduler def throw_( exception: Union[str, Exception], scheduler: Optional[abc.SchedulerBase] = None ) -> Observable[Any]: exception_ = exception if isinstance(exception, Exception) else Exception(exception) def subscribe( observer: abc.ObserverBase[Any], scheduler: Optional[abc.SchedulerBase] = None ) -> abc.DisposableBase: _scheduler = scheduler or ImmediateScheduler.singleton() def action(scheduler: abc.SchedulerBase, state: Any) -> None: observer.on_error(exception_) return _scheduler.schedule(action) return Observable(subscribe) __all__ = ["throw_"] RxPY-4.0.4/reactivex/observable/timer.py000066400000000000000000000106221426446175400202070ustar00rootroot00000000000000from datetime import datetime from typing import Any, Optional from reactivex import Observable, abc, typing from reactivex.disposable import MultipleAssignmentDisposable from reactivex.scheduler import TimeoutScheduler from reactivex.scheduler.periodicscheduler import PeriodicScheduler def observable_timer_date( duetime: typing.AbsoluteTime, scheduler: Optional[abc.SchedulerBase] = None ) -> Observable[int]: def subscribe( observer: abc.ObserverBase[int], scheduler_: Optional[abc.SchedulerBase] = None ) -> abc.DisposableBase: _scheduler: abc.SchedulerBase = ( scheduler or scheduler_ or TimeoutScheduler.singleton() ) def action(scheduler: abc.SchedulerBase, state: Any) -> None: observer.on_next(0) observer.on_completed() return _scheduler.schedule_absolute(duetime, action) return Observable(subscribe) def observable_timer_duetime_and_period( duetime: typing.AbsoluteOrRelativeTime, period: typing.AbsoluteOrRelativeTime, scheduler: Optional[abc.SchedulerBase] = None, ) -> Observable[int]: def subscribe( observer: abc.ObserverBase[int], scheduler_: Optional[abc.SchedulerBase] = None ) -> abc.DisposableBase: _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: abc.SchedulerBase, state: Any) -> None: 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[abc.SchedulerBase] = None ) -> Observable[int]: def subscribe( observer: abc.ObserverBase[int], scheduler_: Optional[abc.SchedulerBase] = None ) -> abc.DisposableBase: _scheduler = scheduler or scheduler_ or TimeoutScheduler.singleton() d = _scheduler.to_seconds(duetime) def action(scheduler: abc.SchedulerBase, state: Any) -> None: 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[abc.SchedulerBase] = None, ) -> Observable[int]: if duetime == period: def subscribe( observer: abc.ObserverBase[int], scheduler_: Optional[abc.SchedulerBase] = None, ) -> abc.DisposableBase: _scheduler: abc.SchedulerBase = ( scheduler or scheduler_ or TimeoutScheduler.singleton() ) def action(count: Optional[int] = None) -> Optional[int]: if count is not None: observer.on_next(count) return count + 1 return None if not isinstance(_scheduler, PeriodicScheduler): raise ValueError("Sceduler must be PeriodicScheduler") 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[abc.SchedulerBase] = None, ) -> Observable[int]: 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) __all__ = ["timer_"] RxPY-4.0.4/reactivex/observable/toasync.py000066400000000000000000000032361426446175400205520ustar00rootroot00000000000000from typing import Any, Callable, Optional, TypeVar from reactivex import Observable, abc from reactivex import operators as ops from reactivex.scheduler import TimeoutScheduler from reactivex.subject import AsyncSubject _T = TypeVar("_T") def to_async_( func: Callable[..., _T], scheduler: Optional[abc.SchedulerBase] = None ) -> Callable[..., Observable[_T]]: """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 = reactivex.to_async(lambda x, y: x + y)(4, 3) res = reactivex.to_async(lambda x, y: x + y, Scheduler.timeout)(4, 3) res = reactivex.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: Any) -> Observable[_T]: subject: AsyncSubject[_T] = AsyncSubject() def action(scheduler: abc.SchedulerBase, state: Any = None) -> None: 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 __all__ = ["to_async_"] RxPY-4.0.4/reactivex/observable/using.py000066400000000000000000000032051426446175400202130ustar00rootroot00000000000000from typing import Callable, Optional, TypeVar import reactivex from reactivex import Observable, abc from reactivex.disposable import CompositeDisposable, Disposable _T = TypeVar("_T") def using_( resource_factory: Callable[[], abc.DisposableBase], observable_factory: Callable[[abc.DisposableBase], Observable[_T]], ) -> Observable[_T]: """Constructs an observable sequence that depends on a resource object, whose lifetime is tied to the resulting observable sequence's lifetime. Example: >>> res = reactivex.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: abc.ObserverBase[_T], scheduler: Optional[abc.SchedulerBase] = None ) -> abc.DisposableBase: disp: abc.DisposableBase = 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 = reactivex.throw(exception).subscribe(observer, scheduler=scheduler) return CompositeDisposable(d, disp) return CompositeDisposable( source.subscribe(observer, scheduler=scheduler), disp ) return Observable(subscribe) __all__ = ["using_"] RxPY-4.0.4/reactivex/observable/withlatestfrom.py000066400000000000000000000037031426446175400221450ustar00rootroot00000000000000from typing import Any, List, Optional, Tuple from reactivex import Observable, abc from reactivex.disposable import CompositeDisposable, SingleAssignmentDisposable from reactivex.internal.utils import NotSet def with_latest_from_( parent: Observable[Any], *sources: Observable[Any] ) -> Observable[Tuple[Any, ...]]: NO_VALUE = NotSet() def subscribe( observer: abc.ObserverBase[Any], scheduler: Optional[abc.SchedulerBase] = None ) -> abc.DisposableBase: def subscribeall( parent: Observable[Any], *children: Observable[Any] ) -> List[SingleAssignmentDisposable]: values = [NO_VALUE for _ in children] def subscribechild( i: int, child: Observable[Any] ) -> SingleAssignmentDisposable: subscription = SingleAssignmentDisposable() def on_next(value: Any) -> None: 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: Any) -> None: with parent.lock: if NO_VALUE not in values: result = (value,) + tuple(values) observer.on_next(result) children_subscription = [ subscribechild(i, child) for i, child in enumerate(children) ] disp = parent.subscribe( on_next, observer.on_error, observer.on_completed, scheduler=scheduler ) parent_subscription.disposable = disp return [parent_subscription] + children_subscription return CompositeDisposable(subscribeall(parent, *sources)) return Observable(subscribe) __all__ = ["with_latest_from_"] RxPY-4.0.4/reactivex/observable/zip.py000066400000000000000000000054141426446175400176740ustar00rootroot00000000000000from asyncio import Future from threading import RLock from typing import Any, List, Optional, Tuple from reactivex import Observable, abc, from_future from reactivex.disposable import CompositeDisposable, SingleAssignmentDisposable from reactivex.internal import synchronized def zip_(*args: Observable[Any]) -> Observable[Tuple[Any, ...]]: """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: abc.ObserverBase[Any], scheduler: Optional[abc.SchedulerBase] = None ) -> CompositeDisposable: n = len(sources) queues: List[List[Any]] = [[] for _ in range(n)] lock = RLock() is_completed = [False] * n @synchronized(lock) def next_(i: int) -> None: 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) # after sending the zipped values, complete the observer if at least one # upstream observable is completed and its queue has length zero if any( ( done for queue, done in zip(queues, is_completed) if len(queue) == 0 ) ): observer.on_completed() def completed(i: int) -> None: is_completed[i] = True if len(queues[i]) == 0: observer.on_completed() subscriptions: List[Optional[abc.DisposableBase]] = [None] * n def func(i: int) -> None: source: Observable[Any] = sources[i] if isinstance(source, Future): source = from_future(source) sad = SingleAssignmentDisposable() def on_next(x: Any) -> None: queues[i].append(x) next_(i) sad.disposable = source.subscribe( on_next, observer.on_error, lambda: completed(i), scheduler=scheduler ) subscriptions[i] = sad for idx in range(n): func(idx) return CompositeDisposable(subscriptions) return Observable(subscribe) __all__ = ["zip_"] RxPY-4.0.4/reactivex/observer/000077500000000000000000000000001426446175400162175ustar00rootroot00000000000000RxPY-4.0.4/reactivex/observer/__init__.py000066400000000000000000000004141426446175400203270ustar00rootroot00000000000000from .autodetachobserver import AutoDetachObserver from .observeonobserver import ObserveOnObserver from .observer import Observer from .scheduledobserver import ScheduledObserver __all__ = ["AutoDetachObserver", "ObserveOnObserver", "Observer", "ScheduledObserver"] RxPY-4.0.4/reactivex/observer/autodetachobserver.py000066400000000000000000000032561426446175400224700ustar00rootroot00000000000000from typing import Optional, TypeVar from reactivex.disposable import SingleAssignmentDisposable from reactivex.internal import default_error, noop from .. import abc, typing _T_in = TypeVar("_T_in", contravariant=True) class AutoDetachObserver(abc.ObserverBase[_T_in]): def __init__( self, on_next: Optional[typing.OnNext[_T_in]] = 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: _T_in) -> None: if self.is_stopped: return self._on_next(value) def on_error(self, error: Exception) -> 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: abc.DisposableBase) -> None: 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-4.0.4/reactivex/observer/observeonobserver.py000066400000000000000000000007561426446175400223530ustar00rootroot00000000000000from typing import TypeVar from .scheduledobserver import ScheduledObserver _T = TypeVar("_T") class ObserveOnObserver(ScheduledObserver[_T]): def _on_next_core(self, value: _T) -> 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-4.0.4/reactivex/observer/observer.py000066400000000000000000000067001426446175400204230ustar00rootroot00000000000000from __future__ import annotations from typing import TYPE_CHECKING, Callable, Optional, TypeVar from reactivex import abc from reactivex.internal.basic import default_error, noop from reactivex.typing import OnCompleted, OnError, OnNext _T_in = TypeVar("_T_in", contravariant=True) if TYPE_CHECKING: from reactivex.notification import Notification else: class Notification: pass class Observer(abc.ObserverBase[_T_in], abc.DisposableBase): """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[OnNext[_T_in]] = None, on_error: Optional[OnError] = None, on_completed: Optional[OnCompleted] = None, ) -> None: self.is_stopped = False self._handler_on_next: OnNext[_T_in] = on_next or noop self._handler_on_error: OnError = on_error or default_error self._handler_on_completed: OnCompleted = on_completed or noop def on_next(self, value: _T_in) -> 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: _T_in) -> 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() raise error def to_notifier(self) -> Callable[[Notification[_T_in]], None]: """Creates a notification callback from an observer. Returns the action that forwards its input notification to the underlying observer.""" def func(notifier: Notification[_T_in]) -> None: return notifier.accept(self) return func def as_observer(self) -> abc.ObserverBase[_T_in]: """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-4.0.4/reactivex/observer/scheduledobserver.py000066400000000000000000000042151426446175400223030ustar00rootroot00000000000000import threading from typing import Any, List, TypeVar from reactivex import abc, typing from reactivex.disposable import SerialDisposable from .observer import Observer _T_in = TypeVar("_T_in", contravariant=True) class ScheduledObserver(Observer[_T_in]): def __init__( self, scheduler: abc.SchedulerBase, observer: abc.ObserverBase[_T_in] ) -> 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() -> None: self.observer.on_next(value) self.queue.append(action) def _on_error_core(self, error: Exception) -> None: def action() -> None: self.observer.on_error(error) self.queue.append(action) def _on_completed_core(self) -> None: def action() -> None: 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: abc.SchedulerBase, state: Any) -> 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-4.0.4/reactivex/operators/000077500000000000000000000000001426446175400164065ustar00rootroot00000000000000RxPY-4.0.4/reactivex/operators/__init__.py000066400000000000000000003731671426446175400205400ustar00rootroot00000000000000# pylint: disable=too-many-lines,redefined-builtin,import-outside-toplevel from asyncio import Future from typing import ( TYPE_CHECKING, Any, Callable, Dict, Iterable, List, Optional, Set, Tuple, Type, TypeVar, Union, cast, overload, ) from reactivex import ( ConnectableObservable, GroupedObservable, Notification, Observable, abc, compose, typing, ) from reactivex.internal.basic import identity from reactivex.internal.utils import NotSet from reactivex.subject import Subject from reactivex.typing import ( Accumulator, Comparer, Mapper, MapperIndexed, Predicate, PredicateIndexed, ) _T = TypeVar("_T") _T1 = TypeVar("_T1") _T2 = TypeVar("_T2") _TKey = TypeVar("_TKey") _TState = TypeVar("_TState") _TValue = TypeVar("_TValue") _TRight = TypeVar("_TRight") _TLeft = TypeVar("_TLeft") _A = TypeVar("_A") _B = TypeVar("_B") _C = TypeVar("_C") _D = TypeVar("_D") def all(predicate: Predicate[_T]) -> Callable[[Observable[_T]], Observable[bool]]: """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 ._all import all_ return all_(predicate) def amb(right_source: Observable[_T]) -> Callable[[Observable[_T]], Observable[_T]]: """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 ._amb import amb_ return amb_(right_source) def as_observable() -> Callable[[Observable[_T]], Observable[_T]]: """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 ._asobservable import as_observable_ return as_observable_() def average( key_mapper: Optional[Mapper[_T, float]] = None ) -> Callable[[Observable[_T]], Observable[float]]: """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 ._average import average_ return average_(key_mapper) def buffer( boundaries: Observable[Any], ) -> Callable[[Observable[_T]], Observable[List[_T]]]: """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(reactivex.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 ._buffer import buffer_ return buffer_(boundaries) def buffer_when( closing_mapper: Callable[[], Observable[Any]] ) -> Callable[[Observable[_T]], Observable[List[_T]]]: """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: reactivex.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 ._buffer import buffer_when_ return buffer_when_(closing_mapper) def buffer_toggle( openings: Observable[Any], closing_mapper: Callable[[Any], Observable[Any]] ) -> Callable[[Observable[_T]], Observable[List[_T]]]: """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(reactivex.interval(0.5), lambda i: reactivex.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 ._buffer import buffer_toggle_ return buffer_toggle_(openings, closing_mapper) def buffer_with_count( count: int, skip: Optional[int] = None ) -> Callable[[Observable[_T]], Observable[List[_T]]]: """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 ._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[abc.SchedulerBase] = None, ) -> Callable[[Observable[_T]], Observable[List[_T]]]: """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 ._bufferwithtime import buffer_with_time_ return buffer_with_time_(timespan, timeshift, scheduler) def buffer_with_time_or_count( timespan: typing.RelativeTime, count: int, scheduler: Optional[abc.SchedulerBase] = None, ) -> Callable[[Observable[_T]], Observable[List[_T]]]: """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 ._bufferwithtimeorcount import buffer_with_time_or_count_ return buffer_with_time_or_count_(timespan, count, scheduler) def catch( handler: Union[ Observable[_T], Callable[[Exception, Observable[_T]], Observable[_T]] ] ) -> Callable[[Observable[_T]], Observable[_T]]: """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 ._catch import catch_ return catch_(handler) def combine_latest( *others: Observable[Any], ) -> Callable[[Observable[Any]], Observable[Any]]: """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 ._combinelatest import combine_latest_ return combine_latest_(*others) def concat(*sources: Observable[_T]) -> Callable[[Observable[_T]], Observable[_T]]: """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 ._concat import concat_ return concat_(*sources) def contains( value: _T, comparer: Optional[typing.Comparer[_T]] = None ) -> Callable[[Observable[_T]], Observable[bool]]: """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 ._contains import contains_ return contains_(value, comparer) def count( predicate: Optional[typing.Predicate[_T]] = None, ) -> Callable[[Observable[_T]], Observable[int]]: """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 ._count import count_ return count_(predicate) def debounce( duetime: typing.RelativeTime, scheduler: Optional[abc.SchedulerBase] = None ) -> Callable[[Observable[_T]], Observable[_T]]: """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 ._debounce import debounce_ return debounce_(duetime, scheduler) throttle_with_timeout = debounce @overload def default_if_empty( default_value: _T, ) -> Callable[[Observable[_T]], Observable[_T]]: ... @overload def default_if_empty() -> Callable[[Observable[_T]], Observable[Optional[_T]]]: ... def default_if_empty( default_value: Any = None, ) -> Callable[[Observable[Any]], Observable[Any]]: """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 ._defaultifempty import default_if_empty_ return default_if_empty_(default_value) def delay_subscription( duetime: typing.AbsoluteOrRelativeTime, scheduler: Optional[abc.SchedulerBase] = None, ) -> Callable[[Observable[_T]], Observable[_T]]: """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 ._delaysubscription import delay_subscription_ return delay_subscription_(duetime, scheduler=scheduler) def delay_with_mapper( subscription_delay: Union[ Observable[Any], typing.Mapper[Any, Observable[Any]], None, ] = None, delay_duration_mapper: Optional[typing.Mapper[_T, Observable[Any]]] = None, ) -> Callable[[Observable[_T]], Observable[_T]]: """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( reactivex.timer(2.0), lambda x: reactivex.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 ._delaywithmapper import delay_with_mapper_ return delay_with_mapper_(subscription_delay, delay_duration_mapper) def dematerialize() -> Callable[[Observable[Notification[_T]]], Observable[_T]]: """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 ._dematerialize import dematerialize_ return dematerialize_() def delay( duetime: typing.RelativeTime, scheduler: Optional[abc.SchedulerBase] = None ) -> Callable[[Observable[_T]], Observable[_T]]: """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 ._delay import delay_ return delay_(duetime, scheduler) def distinct( key_mapper: Optional[Mapper[_T, _TKey]] = None, comparer: Optional[Comparer[_TKey]] = None, ) -> Callable[[Observable[_T]], Observable[_T]]: """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 ._distinct import distinct_ return distinct_(key_mapper, comparer) def distinct_until_changed( key_mapper: Optional[Mapper[_T, _TKey]] = None, comparer: Optional[Comparer[_TKey]] = None, ) -> Callable[[Observable[_T]], Observable[_T]]: """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 ._distinctuntilchanged import distinct_until_changed_ return distinct_until_changed_(key_mapper, comparer) def do(observer: abc.ObserverBase[_T]) -> Callable[[Observable[_T]], Observable[_T]]: """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 ._do import do_ return do_(observer) def do_action( on_next: Optional[typing.OnNext[_T]] = None, on_error: Optional[typing.OnError] = None, on_completed: Optional[typing.OnCompleted] = None, ) -> Callable[[Observable[_T]], Observable[_T]]: """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 ._do import do_action_ return do_action_(on_next, on_error, on_completed) def do_while( condition: Predicate[Observable[_T]], ) -> Callable[[Observable[_T]], Observable[_T]]: """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 ._dowhile import do_while_ return do_while_(condition) def element_at(index: int) -> Callable[[Observable[_T]], Observable[_T]]: """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 ._elementatordefault import element_at_or_default_ return element_at_or_default_(index, False) def element_at_or_default( index: int, default_value: Optional[_T] = None ) -> Callable[[Observable[_T]], Observable[_T]]: """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 ._elementatordefault import element_at_or_default_ return element_at_or_default_(index, True, default_value) def exclusive() -> Callable[[Observable[Observable[_T]]], Observable[_T]]: """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 ._exclusive import exclusive_ return exclusive_() def expand( mapper: typing.Mapper[_T, Observable[_T]] ) -> Callable[[Observable[_T]], Observable[_T]]: """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 ._expand import expand_ return expand_(mapper) def filter(predicate: Predicate[_T]) -> Callable[[Observable[_T]], Observable[_T]]: """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 ._filter import filter_ return filter_(predicate) def filter_indexed( predicate_indexed: Optional[PredicateIndexed[_T]] = None, ) -> Callable[[Observable[_T]], Observable[_T]]: """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 ._filter import filter_indexed_ return filter_indexed_(predicate_indexed) def finally_action(action: typing.Action) -> Callable[[Observable[_T]], Observable[_T]]: """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 ._finallyaction import finally_action_ return finally_action_(action) def find( predicate: Callable[[_T, int, Observable[_T]], bool] ) -> Callable[[Observable[_T]], Observable[Union[_T, None]]]: """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 ._find import find_value_ return cast( Callable[[Observable[_T]], Observable[Union[_T, None]]], find_value_(predicate, False), ) def find_index( predicate: Callable[[_T, int, Observable[_T]], bool] ) -> Callable[[Observable[_T]], Observable[Union[int, None]]]: """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 ._find import find_value_ return cast( Callable[[Observable[_T]], Observable[Union[int, None]]], find_value_(predicate, True), ) def first( predicate: Optional[Predicate[_T]] = None, ) -> Callable[[Observable[_T]], Observable[_T]]: """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 ._first import first_ return first_(predicate) def first_or_default( predicate: Optional[Predicate[_T]] = None, default_value: Optional[_T] = None ) -> Callable[[Observable[_T]], Observable[_T]]: """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 ._firstordefault import first_or_default_ return first_or_default_(predicate, default_value) @overload def flat_map( mapper: Optional[Iterable[_T2]] = None, ) -> Callable[[Observable[Any]], Observable[_T2]]: ... @overload def flat_map( mapper: Optional[Observable[_T2]] = None, ) -> Callable[[Observable[Any]], Observable[_T2]]: ... @overload def flat_map( mapper: Optional[Mapper[_T1, Iterable[_T2]]] = None ) -> Callable[[Observable[_T1]], Observable[_T2]]: ... @overload def flat_map( mapper: Optional[Mapper[_T1, Observable[_T2]]] = None ) -> Callable[[Observable[_T1]], Observable[_T2]]: ... def flat_map( mapper: Optional[Any] = None, ) -> Callable[[Observable[Any]], Observable[Any]]: """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 ._flatmap import flat_map_ return flat_map_(mapper) @overload def flat_map_indexed( mapper_indexed: Optional[Iterable[_T2]] = None, ) -> Callable[[Observable[Any]], Observable[_T2]]: ... @overload def flat_map_indexed( mapper_indexed: Optional[Observable[_T2]] = None, ) -> Callable[[Observable[Any]], Observable[_T2]]: ... @overload def flat_map_indexed( mapper_indexed: Optional[MapperIndexed[_T1, Iterable[_T2]]] = None ) -> Callable[[Observable[_T1]], Observable[_T2]]: ... @overload def flat_map_indexed( mapper_indexed: Optional[MapperIndexed[_T1, Observable[_T2]]] = None ) -> Callable[[Observable[_T1]], Observable[_T2]]: ... def flat_map_indexed( mapper_indexed: Any = None, ) -> Callable[[Observable[Any]], Observable[Any]]: """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 ._flatmap import flat_map_indexed_ return flat_map_indexed_(mapper_indexed) def flat_map_latest( mapper: Mapper[_T1, Union[Observable[_T2], "Future[_T2]"]] ) -> Callable[[Observable[_T1]], Observable[_T2]]: """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 ._flatmap import flat_map_latest_ return flat_map_latest_(mapper) def fork_join( *others: Observable[Any], ) -> Callable[[Observable[Any]], Observable[Tuple[Any, ...]]]: """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 ._forkjoin import fork_join_ return fork_join_(*others) def group_by( key_mapper: Mapper[_T, _TKey], element_mapper: Optional[Mapper[_T, _TValue]] = None, subject_mapper: Optional[Callable[[], Subject[_TValue]]] = None, ) -> Callable[[Observable[_T]], Observable[GroupedObservable[_TKey, _TValue]]]: """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 ._groupby import group_by_ return group_by_(key_mapper, element_mapper, subject_mapper) def group_by_until( key_mapper: Mapper[_T, _TKey], element_mapper: Optional[Mapper[_T, _TValue]], duration_mapper: Callable[[GroupedObservable[_TKey, _TValue]], Observable[Any]], subject_mapper: Optional[Callable[[], Subject[_TValue]]] = None, ) -> Callable[[Observable[_T]], Observable[GroupedObservable[_TKey, _TValue]]]: """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 : reactivex.never()) >>> group_by_until( lambda x: x.id, lambda x: x.name, lambda grp: reactivex.never() ) >>> group_by_until( lambda x: x.id, lambda x: x.name, lambda grp: reactivex.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 ._groupbyuntil import group_by_until_ return group_by_until_(key_mapper, element_mapper, duration_mapper, subject_mapper) def group_join( right: Observable[_TRight], left_duration_mapper: Callable[[_TLeft], Observable[Any]], right_duration_mapper: Callable[[_TRight], Observable[Any]], ) -> Callable[[Observable[_TLeft]], Observable[Tuple[_TLeft, Observable[_TRight]]]]: """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 ._groupjoin import group_join_ return group_join_(right, left_duration_mapper, right_duration_mapper) def ignore_elements() -> Callable[[Observable[_T]], Observable[_T]]: """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 ._ignoreelements import ignore_elements_ return ignore_elements_() def is_empty() -> Callable[[Observable[Any]], Observable[bool]]: """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 ._isempty import is_empty_ return is_empty_() def join( right: Observable[_T2], left_duration_mapper: Callable[[Any], Observable[Any]], right_duration_mapper: Callable[[Any], Observable[Any]], ) -> Callable[[Observable[_T1]], Observable[Tuple[_T1, _T2]]]: """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 ._join import join_ return join_(right, left_duration_mapper, right_duration_mapper) def last( predicate: Optional[Predicate[_T]] = None, ) -> Callable[[Observable[_T]], Observable[_T]]: """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 ._last import last_ return last_(predicate) @overload def last_or_default() -> Callable[[Observable[_T]], Observable[Optional[_T]]]: ... @overload def last_or_default( default_value: _T, ) -> Callable[[Observable[_T]], Observable[_T]]: ... @overload def last_or_default( default_value: _T, predicate: Predicate[_T], ) -> Callable[[Observable[_T]], Observable[_T]]: ... def last_or_default( default_value: Any = None, predicate: Optional[Predicate[_T]] = None, ) -> Callable[[Observable[_T]], Observable[Any]]: """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 ._lastordefault import last_or_default return last_or_default(default_value, predicate) def map( mapper: Optional[Mapper[_T1, _T2]] = None ) -> Callable[[Observable[_T1]], Observable[_T2]]: """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 ._map import map_ return map_(mapper) def map_indexed( mapper_indexed: Optional[MapperIndexed[_T1, _T2]] = None ) -> Callable[[Observable[_T1]], Observable[_T2]]: """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 ._map import map_indexed_ return map_indexed_(mapper_indexed) def materialize() -> Callable[[Observable[_T]], Observable[Notification[_T]]]: """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 ._materialize import materialize return materialize() def max( comparer: Optional[Comparer[_T]] = None, ) -> Callable[[Observable[_T]], Observable[_T]]: """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 ._max import max_ return max_(comparer) def max_by( key_mapper: Mapper[_T, _TKey], comparer: Optional[Comparer[_TKey]] = None ) -> Callable[[Observable[_T]], Observable[List[_T]]]: """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 ._maxby import max_by_ return max_by_(key_mapper, comparer) def merge( *sources: Observable[Any], max_concurrent: Optional[int] = None ) -> Callable[[Observable[Any]], Observable[Any]]: """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 ._merge import merge_ return merge_(*sources, max_concurrent=max_concurrent) def merge_all() -> Callable[[Observable[Observable[_T]]], Observable[_T]]: """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 ._merge import merge_all_ return merge_all_() def min( comparer: Optional[Comparer[_T]] = None, ) -> Callable[[Observable[_T]], Observable[_T]]: """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 ._min import min_ return min_(comparer) def min_by( key_mapper: Mapper[_T, _TKey], comparer: Optional[Comparer[_TKey]] = None ) -> Callable[[Observable[_T]], Observable[List[_T]]]: """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 ._minby import min_by_ return min_by_(key_mapper, comparer) @overload def multicast() -> Callable[[Observable[_T]], ConnectableObservable[_T]]: ... @overload def multicast( subject: abc.SubjectBase[_T], ) -> Callable[[Observable[_T]], ConnectableObservable[_T]]: ... @overload def multicast( *, subject_factory: Callable[[Optional[abc.SchedulerBase]], abc.SubjectBase[_T]], mapper: Optional[Callable[[Observable[_T]], Observable[_T2]]] = None, ) -> Callable[[Observable[_T]], Observable[_T2]]: ... def multicast( subject: Optional[abc.SubjectBase[_T]] = None, *, subject_factory: Optional[ Callable[[Optional[abc.SchedulerBase]], abc.SubjectBase[_T]] ] = None, mapper: Optional[Callable[[Observable[_T]], Observable[_T2]]] = None, ) -> Callable[[Observable[_T]], Union[Observable[_T2], ConnectableObservable[_T]]]: """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 ._multicast import multicast_ return multicast_(subject, subject_factory=subject_factory, mapper=mapper) def observe_on( scheduler: abc.SchedulerBase, ) -> Callable[[Observable[_T]], Observable[_T]]: """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 ._observeon import observe_on_ return observe_on_(scheduler) def on_error_resume_next( second: Observable[_T], ) -> Callable[[Observable[_T]], Observable[_T]]: """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 ._onerrorresumenext import on_error_resume_next_ return on_error_resume_next_(second) def pairwise() -> Callable[[Observable[_T]], Observable[Tuple[_T, _T]]]: """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 ._pairwise import pairwise_ return pairwise_() def partition( predicate: Predicate[_T], ) -> Callable[[Observable[_T]], List[Observable[_T]]]: """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 ._partition import partition_ return partition_(predicate) def partition_indexed( predicate_indexed: PredicateIndexed[_T], ) -> Callable[[Observable[_T]], List[Observable[_T]]]: """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 ._partition import partition_indexed_ return partition_indexed_(predicate_indexed) def pluck( key: _TKey, ) -> Callable[[Observable[Dict[_TKey, _TValue]]], Observable[_TValue]]: """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 ._pluck import pluck_ return pluck_(key) def pluck_attr(prop: str) -> Callable[[Observable[Any]], Observable[Any]]: """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 ._pluck import pluck_attr_ return pluck_attr_(prop) @overload def publish() -> Callable[[Observable[_T1]], ConnectableObservable[_T1]]: ... @overload def publish( mapper: Mapper[Observable[_T1], Observable[_T2]], ) -> Callable[[Observable[_T1]], Observable[_T2]]: ... def publish( mapper: Optional[Mapper[Observable[_T1], Observable[_T2]]] = None, ) -> Callable[[Observable[_T1]], Union[Observable[_T2], ConnectableObservable[_T1]]]: """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 ._publish import publish_ return publish_(mapper) @overload def publish_value( initial_value: _T1, ) -> Callable[[Observable[_T1]], ConnectableObservable[_T1]]: ... @overload def publish_value( initial_value: _T1, mapper: Mapper[Observable[_T1], Observable[_T2]], ) -> Callable[[Observable[_T1]], Observable[_T2]]: ... def publish_value( initial_value: _T1, mapper: Optional[Mapper[Observable[_T1], Observable[_T2]]] = None, ) -> Callable[[Observable[_T1]], Union[Observable[_T2], ConnectableObservable[_T1]]]: """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 ._publishvalue import publish_value_ return publish_value_(initial_value, mapper) @overload def reduce( accumulator: Accumulator[_TState, _T] ) -> Callable[[Observable[_T]], Observable[_T]]: ... @overload def reduce( accumulator: Accumulator[_TState, _T], seed: _TState ) -> Callable[[Observable[_T]], Observable[_TState]]: ... def reduce( accumulator: Accumulator[_TState, _T], seed: Union[_TState, Type[NotSet]] = NotSet ) -> Callable[[Observable[_T]], Observable[Any]]: """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 ._reduce import reduce_ return reduce_(accumulator, seed) def ref_count() -> Callable[[ConnectableObservable[_T]], Observable[_T]]: """Returns an observable sequence that stays connected to the source as long as there is at least one subscription to the observable sequence. """ from .connectable._refcount import ref_count_ return ref_count_() def repeat( repeat_count: Optional[int] = None, ) -> Callable[[Observable[_T]], Observable[_T]]: """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 ._repeat import repeat_ return repeat_(repeat_count) @overload def replay( buffer_size: Optional[int] = None, window: Optional[typing.RelativeTime] = None, *, scheduler: Optional[abc.SchedulerBase] = None, ) -> Callable[[Observable[_T1]], ConnectableObservable[_T1]]: ... @overload def replay( buffer_size: Optional[int] = None, window: Optional[typing.RelativeTime] = None, *, mapper: Optional[Mapper[Observable[_T1], Observable[_T2]]], scheduler: Optional[abc.SchedulerBase] = None, ) -> Callable[[Observable[_T1]], Observable[_T2]]: ... def replay( buffer_size: Optional[int] = None, window: Optional[typing.RelativeTime] = None, *, mapper: Optional[Mapper[Observable[_T1], Observable[_T2]]] = None, scheduler: Optional[abc.SchedulerBase] = None, ) -> Callable[[Observable[_T1]], Union[Observable[_T2], ConnectableObservable[_T1]]]: """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 ._replay import replay_ return replay_(mapper, buffer_size, window, scheduler=scheduler) def retry( retry_count: Optional[int] = None, ) -> Callable[[Observable[_T]], Observable[_T]]: """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 ._retry import retry_ return retry_(retry_count) def sample( sampler: Union[typing.RelativeTime, Observable[Any]], scheduler: Optional[abc.SchedulerBase] = None, ) -> Callable[[Observable[_T]], Observable[_T]]: """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 ._sample import sample_ return sample_(sampler, scheduler) @overload def scan( accumulator: Accumulator[_T, _T] ) -> Callable[[Observable[_T]], Observable[_T]]: ... @overload def scan( accumulator: Accumulator[_TState, _T], seed: Union[_TState, Type[NotSet]] ) -> Callable[[Observable[_T]], Observable[_TState]]: ... def scan( accumulator: Accumulator[_TState, _T], seed: Union[_TState, Type[NotSet]] = NotSet ) -> Callable[[Observable[_T]], Observable[_TState]]: """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 ._scan import scan_ return scan_(accumulator, seed) def sequence_equal( second: Union[Observable[_T], Iterable[_T]], comparer: Optional[Comparer[_T]] = None ) -> Callable[[Observable[_T]], Observable[bool]]: """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(reactivex.return_value(42)) >>> res = sequence_equal( reactivex.return_value({ "value": 42 }), lambda x, y: x.value == y.value) Args: second: Second observable sequence or iterable 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 ._sequenceequal import sequence_equal_ return sequence_equal_(second, comparer) def share() -> Callable[[Observable[_T]], Observable[_T]]: """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 ._publish import share_ return share_() def single( predicate: Optional[Predicate[_T]] = None, ) -> Callable[[Observable[_T]], Observable[_T]]: """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 ._single import single_ return single_(predicate) def single_or_default( predicate: Optional[Predicate[_T]] = None, default_value: Any = None ) -> Callable[[Observable[_T]], Observable[_T]]: """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 ._singleordefault import single_or_default_ return single_or_default_(predicate, default_value) def single_or_default_async( has_default: bool = False, default_value: _T = None ) -> Callable[[Observable[_T]], Observable[_T]]: from ._singleordefault import single_or_default_async_ return single_or_default_async_(has_default, default_value) def skip(count: int) -> Callable[[Observable[_T]], Observable[_T]]: """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 ._skip import skip_ return skip_(count) def skip_last(count: int) -> Callable[[Observable[_T]], Observable[_T]]: """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 ._skiplast import skip_last_ return skip_last_(count) def skip_last_with_time( duration: typing.RelativeTime, scheduler: Optional[abc.SchedulerBase] = None ) -> Callable[[Observable[_T]], Observable[_T]]: """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 ._skiplastwithtime import skip_last_with_time_ return skip_last_with_time_(duration, scheduler=scheduler) def skip_until( other: Union[Observable[Any], "Future[Any]"] ) -> Callable[[Observable[_T]], Observable[_T]]: """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 ._skipuntil import skip_until_ return skip_until_(other) def skip_until_with_time( start_time: typing.AbsoluteOrRelativeTime, scheduler: Optional[abc.SchedulerBase] = None, ) -> Callable[[Observable[_T]], Observable[_T]]: """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 ._skipuntilwithtime import skip_until_with_time_ return skip_until_with_time_(start_time, scheduler=scheduler) def skip_while( predicate: typing.Predicate[_T], ) -> Callable[[Observable[_T]], Observable[_T]]: """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 ._skipwhile import skip_while_ return skip_while_(predicate) def skip_while_indexed( predicate: typing.PredicateIndexed[_T], ) -> Callable[[Observable[_T]], Observable[_T]]: """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 ._skipwhile import skip_while_indexed_ return skip_while_indexed_(predicate) def skip_with_time( duration: typing.RelativeTime, scheduler: Optional[abc.SchedulerBase] = None ) -> Callable[[Observable[_T]], Observable[_T]]: """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 ._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[_T]], Observable[_T]]: """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 ._slice import slice_ return slice_(start, stop, step) def some( predicate: Optional[Predicate[_T]] = None, ) -> Callable[[Observable[_T]], Observable[bool]]: """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 ._some import some_ return some_(predicate) @overload def starmap( mapper: Callable[[_A, _B], _T] ) -> Callable[[Observable[Tuple[_A, _B]]], Observable[_T]]: ... @overload def starmap( mapper: Callable[[_A, _B, _C], _T] ) -> Callable[[Observable[Tuple[_A, _B, _C]]], Observable[_T]]: ... @overload def starmap( mapper: Callable[[_A, _B, _C, _D], _T] ) -> Callable[[Observable[Tuple[_A, _B, _C, _D]]], Observable[_T]]: ... def starmap( mapper: Optional[Callable[..., Any]] = None ) -> Callable[[Observable[Any]], Observable[Any]]: """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 compose(identity) def starred(values: Tuple[Any, ...]) -> Any: assert mapper # mypy is paranoid return mapper(*values) return compose(map(starred)) @overload def starmap_indexed( mapper: Callable[[_A, int], _T] ) -> Callable[[Observable[_A]], Observable[_T]]: ... @overload def starmap_indexed( mapper: Callable[[_A, _B, int], _T] ) -> Callable[[Observable[Tuple[_A, _B]]], Observable[_T]]: ... @overload def starmap_indexed( mapper: Callable[[_A, _B, _C, int], _T] ) -> Callable[[Observable[Tuple[_A, _B, _C]]], Observable[_T]]: ... @overload def starmap_indexed( mapper: Callable[[_A, _B, _C, _D, int], _T] ) -> Callable[[Observable[Tuple[_A, _B, _C, _D]]], Observable[_T]]: ... def starmap_indexed( mapper: Optional[Callable[..., Any]] = None ) -> Callable[[Observable[Any]], Observable[Any]]: """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. """ from ._map import map_ if mapper is None: return compose(identity) def starred(values: Tuple[Any, ...]) -> Any: assert mapper # mypy is paranoid return mapper(*values) return compose(map_(starred)) def start_with(*args: _T) -> Callable[[Observable[_T]], Observable[_T]]: """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 ._startswith import start_with_ return start_with_(*args) def subscribe_on( scheduler: abc.SchedulerBase, ) -> Callable[[Observable[_T]], Observable[_T]]: """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 ._subscribeon import subscribe_on_ return subscribe_on_(scheduler) @overload def sum() -> Callable[[Observable[float]], Observable[float]]: ... @overload def sum(key_mapper: Mapper[_T, float]) -> Callable[[Observable[_T]], Observable[float]]: ... def sum( key_mapper: Optional[Mapper[Any, float]] = None ) -> Callable[[Observable[Any]], Observable[float]]: """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 ._sum import sum_ return sum_(key_mapper) def switch_latest() -> Callable[ [Observable[Union[Observable[_T], "Future[_T]"]]], Observable[_T] ]: """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 ._switchlatest import switch_latest_ return switch_latest_() def take(count: int) -> Callable[[Observable[_T]], Observable[_T]]: """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 ._take import take_ return take_(count) def take_last(count: int) -> Callable[[Observable[_T]], Observable[_T]]: """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 ._takelast import take_last_ return take_last_(count) def take_last_buffer(count: int) -> Callable[[Observable[_T]], Observable[List[_T]]]: """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 ._takelastbuffer import take_last_buffer_ return take_last_buffer_(count) def take_last_with_time( duration: typing.RelativeTime, scheduler: Optional[abc.SchedulerBase] = None ) -> Callable[[Observable[_T]], Observable[_T]]: """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 ._takelastwithtime import take_last_with_time_ return take_last_with_time_(duration, scheduler=scheduler) def take_until(other: Observable[Any]) -> Callable[[Observable[_T]], Observable[_T]]: """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 ._takeuntil import take_until_ return take_until_(other) def take_until_with_time( end_time: typing.AbsoluteOrRelativeTime, scheduler: Optional[abc.SchedulerBase] = None, ) -> Callable[[Observable[_T]], Observable[_T]]: """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 ._takeuntilwithtime import take_until_with_time_ return take_until_with_time_(end_time, scheduler=scheduler) def take_while( predicate: Predicate[_T], inclusive: bool = False ) -> Callable[[Observable[_T]], Observable[_T]]: """Returns elements from an observable sequence as long as a specified condition is true. .. 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 ._takewhile import take_while_ return take_while_(predicate, inclusive) def take_while_indexed( predicate: PredicateIndexed[_T], inclusive: bool = False ) -> Callable[[Observable[_T]], Observable[_T]]: """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 ._takewhile import take_while_indexed_ return take_while_indexed_(predicate, inclusive) def take_with_time( duration: typing.RelativeTime, scheduler: Optional[abc.SchedulerBase] = None ) -> Callable[[Observable[_T]], Observable[_T]]: """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 ._takewithtime import take_with_time_ return take_with_time_(duration, scheduler=scheduler) def throttle_first( window_duration: typing.RelativeTime, scheduler: Optional[abc.SchedulerBase] = None ) -> Callable[[Observable[_T]], Observable[_T]]: """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 ._throttlefirst import throttle_first_ return throttle_first_(window_duration, scheduler) def throttle_with_mapper( throttle_duration_mapper: Callable[[Any], Observable[Any]] ) -> Callable[[Observable[_T]], Observable[_T]]: """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 ._debounce import throttle_with_mapper_ return throttle_with_mapper_(throttle_duration_mapper) if TYPE_CHECKING: from ._timestamp import Timestamp def timestamp( scheduler: Optional[abc.SchedulerBase] = None, ) -> Callable[[Observable[_T]], Observable["Timestamp[_T]"]]: """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 ._timestamp import timestamp_ return timestamp_(scheduler=scheduler) def timeout( duetime: typing.AbsoluteOrRelativeTime, other: Optional[Observable[_T]] = None, scheduler: Optional[abc.SchedulerBase] = None, ) -> Callable[[Observable[_T]], Observable[_T]]: """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 ._timeout import timeout_ return timeout_(duetime, other, scheduler) def timeout_with_mapper( first_timeout: Optional[Observable[Any]] = None, timeout_duration_mapper: Optional[Callable[[_T], Observable[Any]]] = None, other: Optional[Observable[_T]] = None, ) -> Callable[[Observable[_T]], Observable[_T]]: """Returns the source observable sequence, switching to the other observable sequence if a timeout is signaled. Examples: >>> res = timeout_with_mapper(reactivex.timer(0.5)) >>> res = timeout_with_mapper( reactivex.timer(0.5), lambda x: reactivex.timer(0.2) ) >>> res = timeout_with_mapper( reactivex.timer(0.5), lambda x: reactivex.timer(0.2), reactivex.return_value(42) ) Args: first_timeout: [Optional] Observable sequence that represents the timeout for the first element. If not provided, this defaults to reactivex.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 reactivex.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 ._timeoutwithmapper import timeout_with_mapper_ return timeout_with_mapper_(first_timeout, timeout_duration_mapper, other) if TYPE_CHECKING: from ._timeinterval import TimeInterval def time_interval( scheduler: Optional[abc.SchedulerBase] = None, ) -> Callable[[Observable[_T]], Observable["TimeInterval[_T]"]]: """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 ._timeinterval import time_interval_ return time_interval_(scheduler=scheduler) def to_dict( key_mapper: Mapper[_T, _TKey], element_mapper: Optional[Mapper[_T, _TValue]] = None ) -> Callable[[Observable[_T]], Observable[Dict[_TKey, _TValue]]]: """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 ._todict import to_dict_ return to_dict_(key_mapper, element_mapper) def to_future( future_ctor: Optional[Callable[[], "Future[_T]"]] = None ) -> Callable[[Observable[_T]], "Future[_T]"]: """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 ._tofuture import to_future_ return to_future_(future_ctor) def to_iterable() -> Callable[[Observable[_T]], Observable[List[_T]]]: """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 ._toiterable import to_iterable_ return to_iterable_() to_list = to_iterable def to_marbles( timespan: typing.RelativeTime = 0.1, scheduler: Optional[abc.SchedulerBase] = None ) -> Callable[[Observable[Any]], Observable[str]]: """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 ._tomarbles import to_marbles return to_marbles(scheduler=scheduler, timespan=timespan) def to_set() -> Callable[[Observable[_T]], Observable[Set[_T]]]: """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 ._toset import to_set_ return to_set_() def while_do( condition: Predicate[Observable[_T]], ) -> Callable[[Observable[_T]], Observable[_T]]: """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 ._whiledo import while_do_ return while_do_(condition) def window( boundaries: Observable[Any], ) -> Callable[[Observable[_T]], Observable[Observable[_T]]]: """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(reactivex.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 ._window import window_ return window_(boundaries) def window_when( closing_mapper: Callable[[], Observable[Any]] ) -> Callable[[Observable[_T]], Observable[Observable[_T]]]: """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: reactivex.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 ._window import window_when_ return window_when_(closing_mapper) def window_toggle( openings: Observable[Any], closing_mapper: Callable[[Any], Observable[Any]] ) -> Callable[[Observable[_T]], Observable[Observable[_T]]]: """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(reactivex.interval(0.5), lambda i: reactivex.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 ._window import window_toggle_ return window_toggle_(openings, closing_mapper) def window_with_count( count: int, skip: Optional[int] = None ) -> Callable[[Observable[_T]], Observable[Observable[_T]]]: """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 ._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[abc.SchedulerBase] = None, ) -> Callable[[Observable[_T]], Observable[Observable[_T]]]: from ._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[abc.SchedulerBase] = None, ) -> Callable[[Observable[_T]], Observable[Observable[_T]]]: from ._windowwithtimeorcount import window_with_time_or_count_ return window_with_time_or_count_(timespan, count, scheduler) def with_latest_from( *sources: Observable[Any], ) -> Callable[[Observable[Any]], Observable[Any]]: """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 ._withlatestfrom import with_latest_from_ return with_latest_from_(*sources) def zip(*args: Observable[Any]) -> Callable[[Observable[Any]], Observable[Any]]: """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 ._zip import zip_ return zip_(*args) def zip_with_iterable( second: Iterable[_T2], ) -> Callable[[Observable[_T1]], Observable[Tuple[_T1, _T2]]]: """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 ._zip import zip_with_iterable_ return zip_with_iterable_(second) zip_with_list = zip_with_iterable __all__ = [ "all", "amb", "as_observable", "average", "buffer", "buffer_when", "buffer_toggle", "buffer_with_count", "buffer_with_time", "buffer_with_time_or_count", "catch", "combine_latest", "concat", "contains", "count", "debounce", "throttle_with_timeout", "default_if_empty", "delay_subscription", "delay_with_mapper", "dematerialize", "delay", "distinct", "distinct_until_changed", "do", "do_action", "do_while", "element_at", "element_at_or_default", "exclusive", "expand", "filter", "filter_indexed", "finally_action", "find", "find_index", "first", "first_or_default", "flat_map", "flat_map_indexed", "flat_map_latest", "fork_join", "group_by", "group_by_until", "group_join", "ignore_elements", "is_empty", "join", "last", "last_or_default", "map", "map_indexed", "materialize", "max", "max_by", "merge", "merge_all", "min", "min_by", "multicast", "observe_on", "on_error_resume_next", "pairwise", "partition", "partition_indexed", "pluck", "pluck_attr", "publish", "publish_value", "reduce", "ref_count", "repeat", "replay", "retry", "sample", "scan", "sequence_equal", "share", "single", "single_or_default", "single_or_default_async", "skip", "skip_last", "skip_last_with_time", "skip_until", "skip_until_with_time", "skip_while", "skip_while_indexed", "skip_with_time", "slice", "some", "starmap", "starmap_indexed", "start_with", "subscribe_on", "sum", "switch_latest", "take", "take_last", "take_last_buffer", "take_last_with_time", "take_until", "take_until_with_time", "take_while", "take_while_indexed", "take_with_time", "throttle_first", "throttle_with_mapper", "timestamp", "timeout", "timeout_with_mapper", "time_interval", "to_dict", "to_future", "to_iterable", "to_list", "to_marbles", "to_set", "while_do", "window", "window_when", "window_toggle", "window_with_count", "window_with_time", "window_with_time_or_count", "with_latest_from", "zip", "zip_with_list", "zip_with_iterable", "zip_with_list", ] RxPY-4.0.4/reactivex/operators/_all.py000066400000000000000000000007611426446175400176730ustar00rootroot00000000000000from typing import Callable, TypeVar from reactivex import Observable, compose from reactivex import operators as ops from reactivex.typing import Predicate _T = TypeVar("_T") def all_(predicate: Predicate[_T]) -> Callable[[Observable[_T]], Observable[bool]]: def filter(v: _T): return not predicate(v) def mapping(b: bool) -> bool: return not b return compose( ops.filter(filter), ops.some(), ops.map(mapping), ) __all__ = ["all_"] RxPY-4.0.4/reactivex/operators/_amb.py000066400000000000000000000060041426446175400176560ustar00rootroot00000000000000from asyncio import Future from typing import Callable, List, Optional, TypeVar, Union from reactivex import Observable, abc, from_future from reactivex.disposable import CompositeDisposable, SingleAssignmentDisposable _T = TypeVar("_T") def amb_( right_source: Union[Observable[_T], "Future[_T]"] ) -> Callable[[Observable[_T]], Observable[_T]]: if isinstance(right_source, Future): obs: Observable[_T] = from_future(right_source) else: obs = right_source def amb(left_source: Observable[_T]) -> Observable[_T]: def subscribe( observer: abc.ObserverBase[_T], scheduler: Optional[abc.SchedulerBase] = None, ) -> abc.DisposableBase: choice: List[Optional[str]] = [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: _T) -> None: with left_source.lock: choice_left() if choice[0] == left_choice: observer.on_next(value) def on_error_left(err: Exception) -> None: with left_source.lock: choice_left() if choice[0] == left_choice: observer.on_error(err) def on_completed_left() -> None: 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=scheduler ) left_subscription.disposable = left_d def send_right(value: _T) -> 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=scheduler ) right_subscription.disposable = right_d return CompositeDisposable(left_subscription, right_subscription) return Observable(subscribe) return amb __all__ = ["amb_"] RxPY-4.0.4/reactivex/operators/_asobservable.py000066400000000000000000000014651426446175400215750ustar00rootroot00000000000000from typing import Callable, Optional, TypeVar from reactivex import Observable, abc _T = TypeVar("_T") def as_observable_() -> Callable[[Observable[_T]], Observable[_T]]: def as_observable(source: Observable[_T]) -> Observable[_T]: """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: abc.ObserverBase[_T], scheduler: Optional[abc.SchedulerBase] = None, ) -> abc.DisposableBase: return source.subscribe(observer, scheduler=scheduler) return Observable(subscribe) return as_observable __all__ = ["as_observable_"] RxPY-4.0.4/reactivex/operators/_average.py000066400000000000000000000032251426446175400205330ustar00rootroot00000000000000from dataclasses import dataclass from typing import Any, Callable, Optional, TypeVar, cast from reactivex import Observable, operators, typing _T = TypeVar("_T") @dataclass class AverageValue: sum: float count: int def average_( key_mapper: Optional[typing.Mapper[_T, float]] = None, ) -> Callable[[Observable[_T]], Observable[float]]: def average(source: Observable[Any]) -> Observable[float]: """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. """ key_mapper_: typing.Mapper[_T, float] = key_mapper or ( lambda x: float(cast(Any, x)) ) def accumulator(prev: AverageValue, cur: float) -> AverageValue: return AverageValue(sum=prev.sum + cur, count=prev.count + 1) def mapper(s: AverageValue) -> float: if s.count == 0: raise Exception("The input sequence was empty") return s.sum / float(s.count) seed = AverageValue(sum=0, count=0) ret = source.pipe( operators.map(key_mapper_), operators.scan(accumulator, seed), operators.last(), operators.map(mapper), ) return ret return average __all__ = ["average_"] RxPY-4.0.4/reactivex/operators/_buffer.py000066400000000000000000000042361426446175400203750ustar00rootroot00000000000000from typing import Any, Callable, List, Optional, TypeVar from reactivex import Observable, compose from reactivex import operators as ops _T = TypeVar("_T") def buffer_( boundaries: Observable[Any], ) -> Callable[[Observable[_T]], Observable[List[_T]]]: return compose( ops.window(boundaries), ops.flat_map(ops.to_list()), ) def buffer_when_( closing_mapper: Callable[[], Observable[Any]] ) -> Callable[[Observable[_T]], Observable[List[_T]]]: return compose( ops.window_when(closing_mapper), ops.flat_map(ops.to_list()), ) def buffer_toggle_( openings: Observable[Any], closing_mapper: Callable[[Any], Observable[Any]] ) -> Callable[[Observable[_T]], Observable[List[_T]]]: return compose( ops.window_toggle(openings, closing_mapper), ops.flat_map(ops.to_list()), ) def buffer_with_count_( count: int, skip: Optional[int] = None ) -> Callable[[Observable[_T]], Observable[List[_T]]]: """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[_T]) -> Observable[List[_T]]: nonlocal skip if skip is None: skip = count def mapper(value: Observable[_T]) -> Observable[List[_T]]: return value.pipe( ops.to_list(), ) def predicate(value: List[_T]) -> bool: return len(value) > 0 return source.pipe( ops.window_with_count(count, skip), ops.flat_map(mapper), ops.filter(predicate), ) return buffer_with_count __all__ = ["buffer_", "buffer_with_count_", "buffer_when_", "buffer_toggle_"] RxPY-4.0.4/reactivex/operators/_bufferwithtime.py000066400000000000000000000011531426446175400221430ustar00rootroot00000000000000from typing import Callable, List, Optional, TypeVar from reactivex import Observable, abc, compose from reactivex import operators as ops from reactivex import typing _T = TypeVar("_T") def buffer_with_time_( timespan: typing.RelativeTime, timeshift: Optional[typing.RelativeTime] = None, scheduler: Optional[abc.SchedulerBase] = None, ) -> Callable[[Observable[_T]], Observable[List[_T]]]: if not timeshift: timeshift = timespan return compose( ops.window_with_time(timespan, timeshift, scheduler), ops.flat_map(ops.to_list()), ) __all__ = ["buffer_with_time_"] RxPY-4.0.4/reactivex/operators/_bufferwithtimeorcount.py000066400000000000000000000010551426446175400235560ustar00rootroot00000000000000from typing import Callable, List, Optional, TypeVar from reactivex import Observable, abc, compose from reactivex import operators as ops from reactivex import typing _T = TypeVar("_T") def buffer_with_time_or_count_( timespan: typing.RelativeTime, count: int, scheduler: Optional[abc.SchedulerBase] = None, ) -> Callable[[Observable[_T]], Observable[List[_T]]]: return compose( ops.window_with_time_or_count(timespan, count, scheduler), ops.flat_map(ops.to_iterable()), ) __all__ = ["buffer_with_time_or_count_"] RxPY-4.0.4/reactivex/operators/_catch.py000066400000000000000000000050171426446175400202040ustar00rootroot00000000000000from asyncio import Future from typing import Callable, Optional, TypeVar, Union import reactivex from reactivex import Observable, abc from reactivex.disposable import SerialDisposable, SingleAssignmentDisposable _T = TypeVar("_T") def catch_handler( source: Observable[_T], handler: Callable[[Exception, Observable[_T]], Union[Observable[_T], "Future[_T]"]], ) -> Observable[_T]: def subscribe( observer: abc.ObserverBase[_T], scheduler: Optional[abc.SchedulerBase] = None ) -> abc.DisposableBase: d1 = SingleAssignmentDisposable() subscription = SerialDisposable() subscription.disposable = d1 def on_error(exception: Exception) -> None: try: result = handler(exception, source) except Exception as ex: # By design. pylint: disable=W0703 observer.on_error(ex) return result = ( reactivex.from_future(result) if isinstance(result, Future) 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=scheduler ) return subscription return Observable(subscribe) def catch_( handler: Union[ Observable[_T], Callable[[Exception, Observable[_T]], Observable[_T]] ] ) -> Callable[[Observable[_T]], Observable[_T]]: def catch(source: Observable[_T]) -> Observable[_T]: """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) else: return reactivex.catch(source, handler) return catch __all__ = ["catch_"] RxPY-4.0.4/reactivex/operators/_combinelatest.py000066400000000000000000000014361426446175400217540ustar00rootroot00000000000000from typing import Any, Callable import reactivex from reactivex import Observable def combine_latest_( *others: Observable[Any], ) -> Callable[[Observable[Any]], Observable[Any]]: def combine_latest(source: Observable[Any]) -> Observable[Any]: """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 reactivex.combine_latest(*sources) return combine_latest __all__ = ["combine_latest_"] RxPY-4.0.4/reactivex/operators/_concat.py000066400000000000000000000012671426446175400203740ustar00rootroot00000000000000from typing import Callable, TypeVar import reactivex from reactivex import Observable _T = TypeVar("_T") def concat_(*sources: Observable[_T]) -> Callable[[Observable[_T]], Observable[_T]]: def concat(source: Observable[_T]) -> Observable[_T]: """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 reactivex.concat(source, *sources) return concat __all__ = ["concat_"] RxPY-4.0.4/reactivex/operators/_contains.py000066400000000000000000000011051426446175400207320ustar00rootroot00000000000000from typing import Callable, Optional, TypeVar from reactivex import Observable, compose from reactivex import operators as ops from reactivex import typing from reactivex.internal.basic import default_comparer _T = TypeVar("_T") def contains_( value: _T, comparer: Optional[typing.Comparer[_T]] = None ) -> Callable[[Observable[_T]], Observable[bool]]: comparer_ = comparer or default_comparer def predicate(v: _T) -> bool: return comparer_(v, value) return compose( ops.filter(predicate), ops.some(), ) __all__ = ["contains_"] RxPY-4.0.4/reactivex/operators/_count.py000066400000000000000000000010561426446175400202510ustar00rootroot00000000000000from typing import Callable, Optional, TypeVar from reactivex import Observable, compose from reactivex import operators as ops from reactivex.typing import Predicate _T = TypeVar("_T") def count_( predicate: Optional[Predicate[_T]] = None, ) -> Callable[[Observable[_T]], Observable[int]]: if predicate: return compose( ops.filter(predicate), ops.count(), ) def reducer(n: int, _: _T) -> int: return n + 1 counter = ops.reduce(reducer, seed=0) return counter __all__ = ["count_"] RxPY-4.0.4/reactivex/operators/_debounce.py000066400000000000000000000126721426446175400207130ustar00rootroot00000000000000from typing import Any, Callable, List, Optional, TypeVar, cast from reactivex import Observable, abc, typing from reactivex.disposable import ( CompositeDisposable, SerialDisposable, SingleAssignmentDisposable, ) from reactivex.scheduler import TimeoutScheduler _T = TypeVar("_T") def debounce_( duetime: typing.RelativeTime, scheduler: Optional[abc.SchedulerBase] ) -> Callable[[Observable[_T]], Observable[_T]]: def debounce(source: Observable[_T]) -> Observable[_T]: """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: abc.ObserverBase[_T], scheduler_: Optional[abc.SchedulerBase] = None, ) -> abc.DisposableBase: _scheduler = scheduler or scheduler_ or TimeoutScheduler.singleton() cancelable = SerialDisposable() has_value = [False] value: List[_T] = [cast(_T, None)] _id: List[int] = [0] def on_next(x: _T) -> None: has_value[0] = True value[0] = x _id[0] += 1 current_id = _id[0] d = SingleAssignmentDisposable() cancelable.disposable = d def action(scheduler: abc.SchedulerBase, state: Any = 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[Any]] ) -> Callable[[Observable[_T]], Observable[_T]]: def throttle_with_mapper(source: Observable[_T]) -> Observable[_T]: """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: abc.ObserverBase[_T], scheduler: Optional[abc.SchedulerBase] = None, ) -> abc.DisposableBase: cancelable = SerialDisposable() has_value: bool = False value: _T = cast(_T, None) _id = [0] def on_next(x: _T) -> None: nonlocal value, has_value throttle = None try: throttle = throttle_duration_mapper(x) except Exception as e: # pylint: disable=broad-except observer.on_error(e) return has_value = True value = x _id[0] += 1 current_id = _id[0] d = SingleAssignmentDisposable() cancelable.disposable = d def on_next(x: Any) -> None: nonlocal has_value if has_value and _id[0] == current_id: observer.on_next(value) has_value = False d.dispose() def on_completed() -> None: nonlocal has_value if has_value and _id[0] == current_id: observer.on_next(value) has_value = False d.dispose() d.disposable = throttle.subscribe( on_next, observer.on_error, on_completed, scheduler=scheduler ) def on_error(e: Exception) -> None: nonlocal has_value cancelable.dispose() observer.on_error(e) has_value = False _id[0] += 1 def on_completed() -> None: nonlocal has_value cancelable.dispose() if has_value: observer.on_next(value) observer.on_completed() has_value = 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 __all__ = ["debounce_", "throttle_with_mapper_"] RxPY-4.0.4/reactivex/operators/_defaultifempty.py000066400000000000000000000026601426446175400221450ustar00rootroot00000000000000from typing import Callable, Optional, TypeVar from reactivex import Observable, abc _T = TypeVar("_T") def default_if_empty_( default_value: Optional[_T] = None, ) -> Callable[[Observable[_T]], Observable[Optional[_T]]]: def default_if_empty(source: Observable[_T]) -> Observable[Optional[_T]]: """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: abc.ObserverBase[Optional[_T]], scheduler: Optional[abc.SchedulerBase] = None, ) -> abc.DisposableBase: found = [False] def on_next(x: _T): 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=scheduler ) return Observable(subscribe) return default_if_empty __all__ = ["default_if_empty_"] RxPY-4.0.4/reactivex/operators/_delay.py000066400000000000000000000111621426446175400202160ustar00rootroot00000000000000from datetime import datetime from typing import Any, Callable, List, Optional, TypeVar from reactivex import Notification, Observable, abc from reactivex import operators as ops from reactivex import typing from reactivex.disposable import ( CompositeDisposable, MultipleAssignmentDisposable, SerialDisposable, ) from reactivex.internal.constants import DELTA_ZERO from reactivex.notification import OnError from reactivex.scheduler import TimeoutScheduler from ._timestamp import Timestamp _T = TypeVar("_T") def observable_delay_timespan( source: Observable[_T], duetime: typing.RelativeTime, scheduler: Optional[abc.SchedulerBase] = None, ) -> Observable[_T]: def subscribe( observer: abc.ObserverBase[_T], scheduler_: Optional[abc.SchedulerBase] = 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: Optional[Exception] = None active = [False] running = [False] queue: List[Timestamp[Notification[_T]]] = [] def on_next(notification: Timestamp[Notification[_T]]) -> None: nonlocal exception should_run = False with source.lock: if isinstance(notification.value, OnError): del queue[:] queue.append(notification) exception = 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: observer.on_error(exception) else: mad = MultipleAssignmentDisposable() cancelable.disposable = mad def action(scheduler: abc.SchedulerBase, state: Any = None): if exception: 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: typing.RelativeTime = 0 if queue: should_continue = True diff = queue[0].timestamp - scheduler.now recurse_duetime = max(DELTA_ZERO, diff) else: active[0] = False ex = exception 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[abc.SchedulerBase] = None ) -> Callable[[Observable[_T]], Observable[_T]]: def delay(source: Observable[_T]) -> Observable[_T]: """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 __all__ = ["delay_"] RxPY-4.0.4/reactivex/operators/_delaysubscription.py000066400000000000000000000017421426446175400226660ustar00rootroot00000000000000from typing import Any, Callable, Optional, TypeVar import reactivex from reactivex import Observable, abc from reactivex import operators as ops from reactivex import typing _T = TypeVar("_T") def delay_subscription_( duetime: typing.AbsoluteOrRelativeTime, scheduler: Optional[abc.SchedulerBase] = None, ) -> Callable[[Observable[_T]], Observable[_T]]: def delay_subscription(source: Observable[_T]) -> Observable[_T]: """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(_: Any) -> Observable[_T]: return reactivex.empty() return source.pipe( ops.delay_with_mapper(reactivex.timer(duetime, scheduler=scheduler), mapper) ) return delay_subscription __all__ = ["delay_subscription_"] RxPY-4.0.4/reactivex/operators/_delaywithmapper.py000066400000000000000000000066671426446175400223350ustar00rootroot00000000000000from typing import Any, Callable, Optional, TypeVar, Union from reactivex import Observable, abc, typing from reactivex.disposable import ( CompositeDisposable, SerialDisposable, SingleAssignmentDisposable, ) _T = TypeVar("_T") def delay_with_mapper_( subscription_delay: Union[ Observable[Any], typing.Mapper[Any, Observable[Any]], None, ] = None, delay_duration_mapper: Optional[typing.Mapper[_T, Observable[Any]]] = None, ) -> Callable[[Observable[_T]], Observable[_T]]: def delay_with_mapper(source: Observable[_T]) -> Observable[_T]: """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: Optional[Observable[Any]] = None mapper: Optional[typing.Mapper[Any, Observable[Any]]] = None if isinstance(subscription_delay, abc.ObservableBase): mapper = delay_duration_mapper sub_delay = subscription_delay else: mapper = subscription_delay def subscribe( observer: abc.ObserverBase[_T], scheduler: Optional[abc.SchedulerBase] = None, ) -> abc.DisposableBase: 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: _T) -> None: try: assert mapper delay = mapper(x) except Exception as error: # pylint: disable=broad-except observer.on_error(error) return d = SingleAssignmentDisposable() delays.add(d) def on_next(_: Any) -> None: observer.on_next(x) delays.remove(d) done() def on_completed() -> None: observer.on_next(x) delays.remove(d) done() d.disposable = delay.subscribe( on_next, observer.on_error, on_completed, scheduler=scheduler ) def on_completed() -> None: at_end[0] = True subscription.dispose() done() subscription.disposable = source.subscribe( on_next, observer.on_error, on_completed, scheduler=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_mapper __all__ = ["delay_with_mapper_"] RxPY-4.0.4/reactivex/operators/_dematerialize.py000066400000000000000000000021031426446175400217320ustar00rootroot00000000000000from typing import Callable, Optional, TypeVar from reactivex import Notification, Observable, abc _T = TypeVar("_T") def dematerialize_() -> Callable[[Observable[Notification[_T]]], Observable[_T]]: def dematerialize(source: Observable[Notification[_T]]) -> Observable[_T]: """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: abc.ObserverBase[_T], scheduler: Optional[abc.SchedulerBase] = None, ): def on_next(value: Notification[_T]) -> None: return value.accept(observer) return source.subscribe( on_next, observer.on_error, observer.on_completed, scheduler=scheduler ) return Observable(subscribe) return dematerialize __all__ = ["dematerialize_"] RxPY-4.0.4/reactivex/operators/_distinct.py000066400000000000000000000046661426446175400207540ustar00rootroot00000000000000from typing import Callable, Generic, List, Optional, TypeVar, cast from reactivex import Observable, abc, typing from reactivex.internal.basic import default_comparer _T = TypeVar("_T") _TKey = TypeVar("_TKey") def array_index_of_comparer( array: List[_TKey], item: _TKey, comparer: typing.Comparer[_TKey] ): for i, a in enumerate(array): if comparer(a, item): return i return -1 class HashSet(Generic[_TKey]): def __init__(self, comparer: typing.Comparer[_TKey]): self.comparer = comparer self.set: List[_TKey] = [] def push(self, value: _TKey): 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[typing.Mapper[_T, _TKey]] = None, comparer: Optional[typing.Comparer[_TKey]] = None, ) -> Callable[[Observable[_T]], Observable[_T]]: comparer_ = comparer or default_comparer def distinct(source: Observable[_T]) -> Observable[_T]: """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: abc.ObserverBase[_T], scheduler: Optional[abc.SchedulerBase] = None, ) -> abc.DisposableBase: hashset = HashSet(comparer_) def on_next(x: _T) -> None: key = cast(_TKey, x) if key_mapper: try: key = key_mapper(x) except Exception as ex: observer.on_error(ex) return if hashset.push(key): observer.on_next(x) return source.subscribe( on_next, observer.on_error, observer.on_completed, scheduler=scheduler ) return Observable(subscribe) return distinct __all__ = ["distinct_"] RxPY-4.0.4/reactivex/operators/_distinctuntilchanged.py000066400000000000000000000054651426446175400233400ustar00rootroot00000000000000from typing import Callable, Optional, TypeVar, cast from reactivex import Observable, abc from reactivex.internal.basic import default_comparer, identity from reactivex.typing import Comparer, Mapper _T = TypeVar("_T") _TKey = TypeVar("_TKey") def distinct_until_changed_( key_mapper: Optional[Mapper[_T, _TKey]] = None, comparer: Optional[Comparer[_TKey]] = None, ) -> Callable[[Observable[_T]], Observable[_T]]: key_mapper_ = key_mapper or cast(Callable[[_T], _TKey], identity) comparer_ = comparer or default_comparer def distinct_until_changed(source: Observable[_T]) -> Observable[_T]: """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: abc.ObserverBase[_T], scheduler: Optional[abc.SchedulerBase] = None, ) -> abc.DisposableBase: has_current_key = False current_key: _TKey = cast(_TKey, None) def on_next(value: _T) -> None: nonlocal has_current_key, current_key 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: try: comparer_equals = comparer_(current_key, key) except Exception as exception: # pylint: disable=broad-except observer.on_error(exception) return if not has_current_key or not comparer_equals: has_current_key = True current_key = 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 __all__ = ["distinct_until_changed_"] RxPY-4.0.4/reactivex/operators/_do.py000066400000000000000000000250241426446175400175240ustar00rootroot00000000000000from typing import Any, Callable, List, Optional, TypeVar from reactivex import Observable, abc, typing from reactivex.disposable import CompositeDisposable _T = TypeVar("_T") def do_action_( on_next: Optional[typing.OnNext[_T]] = None, on_error: Optional[typing.OnError] = None, on_completed: Optional[typing.OnCompleted] = None, ) -> Callable[[Observable[_T]], Observable[_T]]: def do_action(source: Observable[_T]) -> Observable[_T]: """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: abc.ObserverBase[_T], scheduler: Optional[abc.SchedulerBase] = None, ) -> abc.DisposableBase: def _on_next(x: _T) -> None: 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: Exception) -> None: 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() -> None: 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=scheduler ) return Observable(subscribe) return do_action def do_(observer: abc.ObserverBase[_T]) -> Callable[[Observable[_T]], Observable[_T]]: """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: Observable[_T], after_next: typing.OnNext[_T]): """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: abc.ObserverBase[_T], scheduler: Optional[abc.SchedulerBase] = None ) -> abc.DisposableBase: def on_next(value: _T): 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[Any], on_subscribe: typing.Action): """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: abc.ObserverBase[Any], scheduler: Optional[abc.SchedulerBase] = None ) -> abc.DisposableBase: on_subscribe() return source.subscribe( observer.on_next, observer.on_error, observer.on_completed, scheduler=scheduler, ) return Observable(subscribe) def do_on_dispose(source: Observable[Any], on_dispose: typing.Action): """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(abc.DisposableBase): def dispose(self) -> None: on_dispose() def subscribe( observer: abc.ObserverBase[Any], scheduler: Optional[abc.SchedulerBase] = None ) -> abc.DisposableBase: composite_disposable = CompositeDisposable() composite_disposable.add(OnDispose()) subscription = source.subscribe( observer.on_next, observer.on_error, observer.on_completed, scheduler=scheduler, ) composite_disposable.add(subscription) return composite_disposable return Observable(subscribe) def do_on_terminate(source: Observable[Any], on_terminate: typing.Action): """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: abc.ObserverBase[Any], scheduler: Optional[abc.SchedulerBase] = None ) -> abc.DisposableBase: 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: 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=scheduler ) return Observable(subscribe) def do_after_terminate(source: Observable[Any], after_terminate: typing.Action): """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: abc.ObserverBase[Any], scheduler: Optional[abc.SchedulerBase] = None ) -> abc.DisposableBase: 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: Exception) -> None: 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=scheduler ) return Observable(subscribe) def do_finally( finally_action: typing.Action, ) -> Callable[[Observable[_T]], Observable[_T]]: """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(abc.DisposableBase): def __init__(self, was_invoked: List[bool]): 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[_T]) -> Observable[_T]: def subscribe( observer: abc.ObserverBase[_T], scheduler: Optional[abc.SchedulerBase] = None, ) -> abc.DisposableBase: 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: 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=scheduler ) composite_disposable.add(subscription) return composite_disposable return Observable(subscribe) return partial __all__ = [ "do_", "do_action_", "do_after_next", "do_finally", "do_on_dispose", "do_on_subscribe", "do_on_terminate", "do_after_terminate", ] RxPY-4.0.4/reactivex/operators/_dowhile.py000066400000000000000000000014611426446175400205540ustar00rootroot00000000000000from typing import Callable, TypeVar from reactivex import Observable from reactivex import operators as ops _T = TypeVar("_T") def do_while_( condition: Callable[[Observable[_T]], bool] ) -> Callable[[Observable[_T]], Observable[_T]]: """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[_T]) -> Observable[_T]: return source.pipe( ops.concat( source.pipe( ops.while_do(condition), ), ) ) return do_while __all__ = ["do_while_"] RxPY-4.0.4/reactivex/operators/_elementatordefault.py000066400000000000000000000030121426446175400227770ustar00rootroot00000000000000from typing import Callable, Optional, TypeVar, cast from reactivex import Observable, abc from reactivex.internal.exceptions import ArgumentOutOfRangeException _T = TypeVar("_T") def element_at_or_default_( index: int, has_default: bool = False, default_value: Optional[_T] = None ) -> Callable[[Observable[_T]], Observable[_T]]: if index < 0: raise ArgumentOutOfRangeException() def element_at_or_default(source: Observable[_T]) -> Observable[_T]: def subscribe( observer: abc.ObserverBase[_T], scheduler: Optional[abc.SchedulerBase] = None, ) -> abc.DisposableBase: index_ = index def on_next(x: _T) -> None: nonlocal index_ found = False with source.lock: if index_: index_ -= 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(cast(_T, default_value)) observer.on_completed() return source.subscribe( on_next, observer.on_error, on_completed, scheduler=scheduler ) return Observable(subscribe) return element_at_or_default __all__ = ["element_at_or_default_"] RxPY-4.0.4/reactivex/operators/_exclusive.py000066400000000000000000000046571426446175400211420ustar00rootroot00000000000000from asyncio import Future from typing import Callable, Optional, TypeVar, Union import reactivex from reactivex import Observable, abc from reactivex.disposable import CompositeDisposable, SingleAssignmentDisposable _T = TypeVar("_T") def exclusive_() -> Callable[[Observable[Observable[_T]]], Observable[_T]]: """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[_T]]) -> Observable[_T]: def subscribe( observer: abc.ObserverBase[_T], scheduler: Optional[abc.SchedulerBase] = None, ) -> abc.DisposableBase: has_current = [False] is_stopped = [False] m = SingleAssignmentDisposable() g = CompositeDisposable() g.add(m) def on_next(inner_source: Union[Observable[_T], "Future[_T]"]) -> None: if not has_current[0]: has_current[0] = True inner_source = ( reactivex.from_future(inner_source) if isinstance(inner_source, Future) 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=scheduler, ) def on_completed() -> None: 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=scheduler ) return g return Observable(subscribe) return exclusive __all__ = ["exclusive_"] RxPY-4.0.4/reactivex/operators/_expand.py000066400000000000000000000060061426446175400204000ustar00rootroot00000000000000from typing import Any, Callable, List, Optional, TypeVar from reactivex import Observable, abc, typing from reactivex.disposable import ( CompositeDisposable, SerialDisposable, SingleAssignmentDisposable, ) from reactivex.scheduler import ImmediateScheduler _T = TypeVar("_T") def expand_( mapper: typing.Mapper[_T, Observable[_T]] ) -> Callable[[Observable[_T]], Observable[_T]]: def expand(source: Observable[_T]) -> Observable[_T]: """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: abc.ObserverBase[_T], scheduler: Optional[abc.SchedulerBase] = None, ) -> abc.DisposableBase: scheduler = scheduler or ImmediateScheduler.singleton() queue: List[Observable[_T]] = [] m = SerialDisposable() d = CompositeDisposable(m) active_count = 0 is_acquired = False def ensure_active(): nonlocal is_acquired is_owner = False if queue: is_owner = not is_acquired is_acquired = True def action(scheduler: abc.SchedulerBase, state: Any = None): nonlocal is_acquired, active_count if queue: work = queue.pop(0) else: is_acquired = False return sad = SingleAssignmentDisposable() d.add(sad) def on_next(value: _T) -> None: nonlocal active_count observer.on_next(value) result = None try: result = mapper(value) except Exception as ex: observer.on_error(ex) return queue.append(result) active_count += 1 ensure_active() def on_complete() -> None: nonlocal active_count d.remove(sad) active_count -= 1 if active_count == 0: observer.on_completed() sad.disposable = work.subscribe( on_next, observer.on_error, on_complete, scheduler=scheduler ) m.disposable = scheduler.schedule(action) if is_owner: m.disposable = scheduler.schedule(action) queue.append(source) active_count += 1 ensure_active() return d return Observable(subscribe) return expand __all__ = ["expand_"] RxPY-4.0.4/reactivex/operators/_filter.py000066400000000000000000000053671426446175400204170ustar00rootroot00000000000000from typing import Callable, Optional, TypeVar from reactivex import Observable, abc from reactivex.typing import Predicate, PredicateIndexed _T = TypeVar("_T") # pylint: disable=redefined-builtin def filter_(predicate: Predicate[_T]) -> Callable[[Observable[_T]], Observable[_T]]: def filter(source: Observable[_T]) -> Observable[_T]: """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: abc.ObserverBase[_T], scheduler: Optional[abc.SchedulerBase] ) -> abc.DisposableBase: def on_next(value: _T): 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=scheduler ) return Observable(subscribe) return filter def filter_indexed_( predicate_indexed: Optional[PredicateIndexed[_T]] = None, ) -> Callable[[Observable[_T]], Observable[_T]]: def filter_indexed(source: Observable[_T]) -> Observable[_T]: """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: abc.ObserverBase[_T], scheduler: Optional[abc.SchedulerBase] ): count = 0 def on_next(value: _T): nonlocal count should_run = True if predicate_indexed: 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=scheduler ) return Observable(subscribe) return filter_indexed __all__ = ["filter_", "filter_indexed_"] RxPY-4.0.4/reactivex/operators/_finallyaction.py000066400000000000000000000024261426446175400217570ustar00rootroot00000000000000from typing import Callable, Optional, TypeVar from reactivex import Observable, abc, typing from reactivex.disposable import Disposable _T = TypeVar("_T") def finally_action_( action: typing.Action, ) -> Callable[[Observable[_T]], Observable[_T]]: def finally_action(source: Observable[_T]) -> Observable[_T]: """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: abc.ObserverBase[_T], scheduler: Optional[abc.SchedulerBase] = None, ) -> abc.DisposableBase: 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 __all__ = ["finally_action_"] RxPY-4.0.4/reactivex/operators/_find.py000066400000000000000000000026311426446175400200410ustar00rootroot00000000000000from typing import Callable, Optional, TypeVar, Union from reactivex import Observable, abc _T = TypeVar("_T") def find_value_( predicate: Callable[[_T, int, Observable[_T]], bool], yield_index: bool ) -> Callable[[Observable[_T]], Observable[Union[_T, int, None]]]: def find_value(source: Observable[_T]) -> Observable[Union[_T, int, None]]: def subscribe( observer: abc.ObserverBase[Union[_T, int, None]], scheduler: Optional[abc.SchedulerBase] = None, ) -> abc.DisposableBase: index = 0 def on_next(x: _T) -> None: nonlocal index should_run = False try: should_run = predicate(x, index, source) except Exception as ex: # pylint: disable=broad-except observer.on_error(ex) return if should_run: observer.on_next(index if yield_index else x) observer.on_completed() else: index += 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=scheduler ) return Observable(subscribe) return find_value __all__ = ["find_value_"] RxPY-4.0.4/reactivex/operators/_first.py000066400000000000000000000022301426446175400202430ustar00rootroot00000000000000from typing import Callable, Optional, TypeVar from reactivex import Observable, compose from reactivex import operators as ops from reactivex.typing import Predicate from ._firstordefault import first_or_default_async_ _T = TypeVar("_T") def first_( predicate: Optional[Predicate[_T]] = None, ) -> Callable[[Observable[_T]], Observable[_T]]: """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 compose(ops.filter(predicate), ops.first()) return first_or_default_async_(False) __all__ = ["first_"] RxPY-4.0.4/reactivex/operators/_firstordefault.py000066400000000000000000000047751426446175400221710ustar00rootroot00000000000000from typing import Callable, Optional, TypeVar, cast from reactivex import Observable, abc, compose from reactivex import operators as ops from reactivex.internal.exceptions import SequenceContainsNoElementsError from reactivex.typing import Predicate _T = TypeVar("_T") def first_or_default_async_( has_default: bool = False, default_value: Optional[_T] = None ) -> Callable[[Observable[_T]], Observable[_T]]: def first_or_default_async(source: Observable[_T]) -> Observable[_T]: def subscribe( observer: abc.ObserverBase[_T], scheduler: Optional[abc.SchedulerBase] = None, ): def on_next(x: _T): observer.on_next(x) observer.on_completed() def on_completed(): if not has_default: observer.on_error(SequenceContainsNoElementsError()) else: observer.on_next(cast(_T, default_value)) observer.on_completed() return source.subscribe( on_next, observer.on_error, on_completed, scheduler=scheduler ) return Observable(subscribe) return first_or_default_async def first_or_default_( predicate: Optional[Predicate[_T]] = None, default_value: Optional[_T] = None ) -> Callable[[Observable[_T]], Observable[_T]]: """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 compose(ops.filter(predicate), ops.first_or_default(None, default_value)) return first_or_default_async_(True, default_value) __all__ = ["first_or_default_", "first_or_default_async_"] RxPY-4.0.4/reactivex/operators/_flatmap.py000066400000000000000000000103171426446175400205450ustar00rootroot00000000000000from asyncio import Future from typing import Any, Callable, Optional, TypeVar, Union, cast from reactivex import Observable, from_, from_future from reactivex import operators as ops from reactivex.internal.basic import identity from reactivex.typing import Mapper, MapperIndexed _T1 = TypeVar("_T1") _T2 = TypeVar("_T2") def _flat_map_internal( source: Observable[_T1], mapper: Optional[Mapper[_T1, Any]] = None, mapper_indexed: Optional[MapperIndexed[_T1, Any]] = None, ) -> Observable[Any]: def projection(x: _T1, i: int) -> Observable[Any]: mapper_result: Any = ( mapper(x) if mapper else mapper_indexed(x, i) if mapper_indexed else identity ) if isinstance(mapper_result, Future): result: Observable[Any] = from_future(cast("Future[Any]", mapper_result)) elif isinstance(mapper_result, Observable): result = mapper_result else: result = from_(mapper_result) return result return source.pipe( ops.map_indexed(projection), ops.merge_all(), ) def flat_map_( mapper: Optional[Mapper[_T1, Observable[_T2]]] = None ) -> Callable[[Observable[_T1]], Observable[_T2]]: def flat_map(source: Observable[_T1]) -> Observable[_T2]: """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[Any] = None, ) -> Callable[[Observable[Any]], Observable[Any]]: def flat_map_indexed(source: Observable[Any]) -> Observable[Any]: """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[_T1, Union[Observable[_T2], "Future[_T2]"]] ) -> Callable[[Observable[_T1]], Observable[_T2]]: def flat_map_latest(source: Observable[_T1]) -> Observable[_T2]: """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 __all__ = ["flat_map_", "flat_map_latest_", "flat_map_indexed_"] RxPY-4.0.4/reactivex/operators/_forkjoin.py000066400000000000000000000015131426446175400207400ustar00rootroot00000000000000from typing import Any, Callable, Tuple import reactivex from reactivex import Observable def fork_join_( *args: Observable[Any], ) -> Callable[[Observable[Any]], Observable[Tuple[Any, ...]]]: def fork_join(source: Observable[Any]) -> Observable[Tuple[Any, ...]]: """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 reactivex.fork_join(source, *args) return fork_join __all__ = ["fork_join_"] RxPY-4.0.4/reactivex/operators/_groupby.py000066400000000000000000000015241426446175400206100ustar00rootroot00000000000000from typing import Any, Callable, Optional, TypeVar from reactivex import GroupedObservable, Observable, typing from reactivex.subject import Subject _T = TypeVar("_T") _TKey = TypeVar("_TKey") _TValue = TypeVar("_TValue") # pylint: disable=import-outside-toplevel def group_by_( key_mapper: typing.Mapper[_T, _TKey], element_mapper: Optional[typing.Mapper[_T, _TValue]] = None, subject_mapper: Optional[Callable[[], Subject[_TValue]]] = None, ) -> Callable[[Observable[_T]], Observable[GroupedObservable[_TKey, _TValue]]]: from reactivex import operators as ops def duration_mapper(_: GroupedObservable[Any, Any]) -> Observable[Any]: import reactivex return reactivex.never() return ops.group_by_until( key_mapper, element_mapper, duration_mapper, subject_mapper ) __all__ = ["group_by_"] RxPY-4.0.4/reactivex/operators/_groupbyuntil.py000066400000000000000000000142441426446175400216670ustar00rootroot00000000000000from collections import OrderedDict from typing import Any, Callable, Optional, TypeVar, cast from reactivex import GroupedObservable, Observable, abc from reactivex import operators as ops from reactivex.disposable import ( CompositeDisposable, RefCountDisposable, SingleAssignmentDisposable, ) from reactivex.internal.basic import identity from reactivex.subject import Subject from reactivex.typing import Mapper _T = TypeVar("_T") _TKey = TypeVar("_TKey") _TValue = TypeVar("_TValue") def group_by_until_( key_mapper: Mapper[_T, _TKey], element_mapper: Optional[Mapper[_T, _TValue]], duration_mapper: Callable[[GroupedObservable[_TKey, _TValue]], Observable[Any]], subject_mapper: Optional[Callable[[], Subject[_TValue]]] = None, ) -> Callable[[Observable[_T]], Observable[GroupedObservable[_TKey, _TValue]]]: """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 : reactivex.never()) >>> group_by_until( lambda x: x.id,lambda x: x.name, lambda grp: reactivex.never() ) >>> group_by_until( lambda x: x.id, lambda x: x.name, lambda grp: reactivex.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 cast(Mapper[_T, _TValue], identity) default_subject_mapper: Callable[[], Subject[_TValue]] = lambda: Subject() subject_mapper_ = subject_mapper or default_subject_mapper def group_by_until( source: Observable[_T], ) -> Observable[GroupedObservable[_TKey, _TValue]]: def subscribe( observer: abc.ObserverBase[GroupedObservable[_TKey, _TValue]], scheduler: Optional[abc.SchedulerBase] = None, ) -> abc.DisposableBase: writers: OrderedDict[_TKey, Subject[_TValue]] = OrderedDict() group_disposable = CompositeDisposable() ref_count_disposable = RefCountDisposable(group_disposable) def on_next(x: _T) -> None: 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[_TKey, _TValue] = GroupedObservable( key, writer, ref_count_disposable ) duration_group: GroupedObservable[_TKey, Any] = 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() -> None: if writers[key]: del writers[key] writer.on_completed() group_disposable.remove(sad) def on_next(value: Any) -> None: pass def on_error(exn: Exception) -> None: for wrt in writers.values(): wrt.on_error(exn) observer.on_error(exn) def on_completed() -> None: expire() sad.disposable = duration.pipe( ops.take(1), ).subscribe(on_next, on_error, on_completed, scheduler=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: Exception) -> None: for wrt in writers.values(): wrt.on_error(ex) observer.on_error(ex) def on_completed() -> None: for wrt in writers.values(): wrt.on_completed() observer.on_completed() group_disposable.add( source.subscribe(on_next, on_error, on_completed, scheduler=scheduler) ) return ref_count_disposable return Observable(subscribe) return group_by_until __all__ = ["group_by_until_"] RxPY-4.0.4/reactivex/operators/_groupjoin.py000066400000000000000000000133761426446175400211450ustar00rootroot00000000000000import logging from collections import OrderedDict from typing import Any, Callable, Optional, Tuple, TypeVar from reactivex import Observable, abc from reactivex import operators as ops from reactivex.disposable import ( CompositeDisposable, RefCountDisposable, SingleAssignmentDisposable, ) from reactivex.internal import add_ref from reactivex.subject import Subject _TLeft = TypeVar("_TLeft") _TRight = TypeVar("_TRight") log = logging.getLogger("Rx") def group_join_( right: Observable[_TRight], left_duration_mapper: Callable[[_TLeft], Observable[Any]], right_duration_mapper: Callable[[_TRight], Observable[Any]], ) -> Callable[[Observable[_TLeft]], Observable[Tuple[_TLeft, Observable[_TRight]]]]: """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(_: Any) -> None: return None def group_join( left: Observable[_TLeft], ) -> Observable[Tuple[_TLeft, Observable[_TRight]]]: def subscribe( observer: abc.ObserverBase[Tuple[_TLeft, Observable[_TRight]]], scheduler: Optional[abc.SchedulerBase] = None, ) -> abc.DisposableBase: group = CompositeDisposable() rcd = RefCountDisposable(group) left_map: OrderedDict[int, Subject[_TRight]] = OrderedDict() right_map: OrderedDict[int, _TRight] = OrderedDict() left_id = [0] right_id = [0] def on_next_left(value: _TLeft) -> None: subject: Subject[_TRight] = 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: Exception) -> Any: 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=scheduler ) def on_error_left(error: Exception) -> None: 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=scheduler, ) ) def send_right(value: _TRight) -> None: 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: Exception): 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=scheduler ) with left.lock: for left_value in left_map.values(): left_value.on_next(value) def on_error_right(error: Exception) -> None: 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 __all__ = ["group_join_"] RxPY-4.0.4/reactivex/operators/_ignoreelements.py000066400000000000000000000016411426446175400221410ustar00rootroot00000000000000from typing import Callable, Optional, TypeVar from reactivex import Observable, abc from reactivex.internal import noop _T = TypeVar("_T") def ignore_elements_() -> Callable[[Observable[_T]], Observable[_T]]: """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[_T]) -> Observable[_T]: def subscribe( observer: abc.ObserverBase[_T], scheduler: Optional[abc.SchedulerBase] = None, ) -> abc.DisposableBase: return source.subscribe( noop, observer.on_error, observer.on_completed, scheduler=scheduler ) return Observable(subscribe) return ignore_elements __all__ = ["ignore_elements_"] RxPY-4.0.4/reactivex/operators/_isempty.py000066400000000000000000000010261426446175400206100ustar00rootroot00000000000000from typing import Any, Callable from reactivex import Observable, compose from reactivex import operators as ops def is_empty_() -> Callable[[Observable[Any]], Observable[bool]]: """Determines whether an observable sequence is empty. Returns: An observable sequence containing a single element determining whether the source sequence is empty. """ def mapper(b: bool) -> bool: return not b return compose( ops.some(), ops.map(mapper), ) __all__ = ["is_empty_"] RxPY-4.0.4/reactivex/operators/_join.py000066400000000000000000000107601426446175400200620ustar00rootroot00000000000000from collections import OrderedDict from typing import Any, Callable, Optional, Tuple, TypeVar from reactivex import Observable, abc from reactivex.disposable import CompositeDisposable, SingleAssignmentDisposable from reactivex.internal import noop from reactivex.operators import take _T1 = TypeVar("_T1") _T2 = TypeVar("_T2") def join_( right: Observable[_T2], left_duration_mapper: Callable[[Any], Observable[Any]], right_duration_mapper: Callable[[Any], Observable[Any]], ) -> Callable[[Observable[_T1]], Observable[Tuple[_T1, _T2]]]: def join(source: Observable[_T1]) -> Observable[Tuple[_T1, _T2]]: """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: abc.ObserverBase[Tuple[_T1, _T2]], scheduler: Optional[abc.SchedulerBase] = None, ) -> abc.DisposableBase: group = CompositeDisposable() left_done = False left_map: OrderedDict[int, _T1] = OrderedDict() left_id = 0 right_done = False right_map: OrderedDict[int, _T2] = OrderedDict() right_id = 0 def on_next_left(value: _T1): 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() 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=scheduler ) for val in right_map.values(): result = (value, val) observer.on_next(result) def on_completed_left() -> None: 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=scheduler, ) ) def on_next_right(value: _T2): 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() 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=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=scheduler, ) ) return group return Observable(subscribe) return join __all__ = ["join_"] RxPY-4.0.4/reactivex/operators/_last.py000066400000000000000000000021621426446175400200630ustar00rootroot00000000000000from typing import Any, Callable, Optional, TypeVar from reactivex import Observable, operators from reactivex.typing import Predicate from ._lastordefault import last_or_default_async _T = TypeVar("_T") def last_( predicate: Optional[Predicate[_T]] = None, ) -> Callable[[Observable[_T]], Observable[Any]]: def last(source: Observable[_T]) -> Observable[Any]: """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 __all__ = ["last_"] RxPY-4.0.4/reactivex/operators/_lastordefault.py000066400000000000000000000036601426446175400217750ustar00rootroot00000000000000from typing import Any, Callable, Optional, TypeVar from reactivex import Observable, abc from reactivex import operators as ops from reactivex import typing from reactivex.internal.exceptions import SequenceContainsNoElementsError _T = TypeVar("_T") def last_or_default_async( source: Observable[_T], has_default: bool = False, default_value: Optional[_T] = None, ) -> Observable[Optional[_T]]: def subscribe( observer: abc.ObserverBase[Optional[_T]], scheduler: Optional[abc.SchedulerBase] = None, ): value = [default_value] seen_value = [False] def on_next(x: _T) -> None: 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=scheduler ) return Observable(subscribe) def last_or_default( default_value: Optional[_T] = None, predicate: Optional[typing.Predicate[_T]] = None ) -> Callable[[Observable[_T]], Observable[Any]]: def last_or_default(source: Observable[Any]) -> Observable[Any]: """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(default_value), ) return last_or_default_async(source, True, default_value) return last_or_default __all__ = ["last_or_default", "last_or_default_async"] RxPY-4.0.4/reactivex/operators/_map.py000066400000000000000000000041001426446175400176670ustar00rootroot00000000000000from typing import Callable, Optional, TypeVar, cast from reactivex import Observable, abc, compose from reactivex import operators as ops from reactivex import typing from reactivex.internal.basic import identity from reactivex.internal.utils import infinite from reactivex.typing import Mapper, MapperIndexed _T1 = TypeVar("_T1") _T2 = TypeVar("_T2") def map_( mapper: Optional[Mapper[_T1, _T2]] = None ) -> Callable[[Observable[_T1]], Observable[_T2]]: _mapper = mapper or cast(Mapper[_T1, _T2], identity) def map(source: Observable[_T1]) -> Observable[_T2]: """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: abc.ObserverBase[_T2], scheduler: Optional[abc.SchedulerBase] = None ) -> abc.DisposableBase: def on_next(value: _T1) -> 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=scheduler ) return Observable(subscribe) return map def map_indexed_( mapper_indexed: Optional[MapperIndexed[_T1, _T2]] = None ) -> Callable[[Observable[_T1]], Observable[_T2]]: def _identity(value: _T1, _: int) -> _T2: return cast(_T2, value) _mapper_indexed = mapper_indexed or cast(typing.MapperIndexed[_T1, _T2], _identity) return compose( ops.zip_with_iterable(infinite()), ops.starmap_indexed(_mapper_indexed), ) __all__ = ["map_", "map_indexed_"] RxPY-4.0.4/reactivex/operators/_materialize.py000066400000000000000000000026571426446175400214370ustar00rootroot00000000000000from typing import Callable, Optional, TypeVar from reactivex import Observable, abc from reactivex.notification import Notification, OnCompleted, OnError, OnNext _T = TypeVar("_T") def materialize() -> Callable[[Observable[_T]], Observable[Notification[_T]]]: def materialize(source: Observable[_T]) -> Observable[Notification[_T]]: """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: abc.ObserverBase[Notification[_T]], scheduler: Optional[abc.SchedulerBase] = None, ): def on_next(value: _T) -> None: observer.on_next(OnNext(value)) def on_error(error: Exception) -> None: observer.on_next(OnError(error)) observer.on_completed() def on_completed() -> None: observer.on_next(OnCompleted()) observer.on_completed() return source.subscribe( on_next, on_error, on_completed, scheduler=scheduler ) return Observable(subscribe) return materialize __all__ = ["materialize"] RxPY-4.0.4/reactivex/operators/_max.py000066400000000000000000000017431426446175400177110ustar00rootroot00000000000000from typing import Callable, Optional, TypeVar, cast from reactivex import Observable, compose from reactivex import operators as ops from reactivex.internal.basic import identity from reactivex.typing import Comparer from ._min import first_only _T = TypeVar("_T") def max_( comparer: Optional[Comparer[_T]] = None, ) -> Callable[[Observable[_T]], Observable[_T]]: """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 compose( ops.max_by(cast(Callable[[_T], _T], identity), comparer), ops.map(first_only), ) __all__ = ["max_"] RxPY-4.0.4/reactivex/operators/_maxby.py000066400000000000000000000017721426446175400202460ustar00rootroot00000000000000from typing import Callable, List, Optional, TypeVar from reactivex import Observable, typing from reactivex.internal.basic import default_sub_comparer from ._minby import extrema_by _T = TypeVar("_T") _TKey = TypeVar("_TKey") def max_by_( key_mapper: typing.Mapper[_T, _TKey], comparer: Optional[typing.SubComparer[_TKey]] = None, ) -> Callable[[Observable[_T]], Observable[List[_T]]]: cmp = comparer or default_sub_comparer def max_by(source: Observable[_T]) -> Observable[List[_T]]: """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 __all__ = ["max_by_"] RxPY-4.0.4/reactivex/operators/_merge.py000066400000000000000000000121011426446175400202110ustar00rootroot00000000000000from asyncio import Future from typing import Callable, List, Optional, TypeVar, Union import reactivex from reactivex import Observable, abc, from_future, typing from reactivex.disposable import CompositeDisposable, SingleAssignmentDisposable from reactivex.internal import synchronized _T = TypeVar("_T") def merge_( *sources: Observable[_T], max_concurrent: Optional[int] = None ) -> Callable[[Observable[Observable[_T]]], Observable[_T]]: def merge(source: Observable[Observable[_T]]) -> Observable[_T]: """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 reactivex.merge(*sources_) def subscribe( observer: abc.ObserverBase[_T], scheduler: Optional[abc.SchedulerBase] = None, ): active_count = [0] group = CompositeDisposable() is_stopped = [False] queue: List[Observable[_T]] = [] def subscribe(xs: Observable[_T]): 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=scheduler ) def on_next(inner_source: Observable[_T]) -> None: assert max_concurrent 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=scheduler ) ) return group return Observable(subscribe) return merge def merge_all_() -> Callable[[Observable[Observable[_T]]], Observable[_T]]: def merge_all(source: Observable[Observable[_T]]) -> Observable[_T]: """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: abc.ObserverBase[_T], scheduler: Optional[abc.SchedulerBase] = None, ): group = CompositeDisposable() is_stopped = [False] m = SingleAssignmentDisposable() group.add(m) def on_next(inner_source: Union[Observable[_T], "Future[_T]"]): inner_subscription = SingleAssignmentDisposable() group.add(inner_subscription) inner_source = ( from_future(inner_source) if isinstance(inner_source, Future) 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: typing.OnNext[_T] = 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=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=scheduler ) return group return Observable(subscribe) return merge_all __all__ = ["merge_", "merge_all_"] RxPY-4.0.4/reactivex/operators/_min.py000066400000000000000000000022251426446175400177030ustar00rootroot00000000000000from typing import Callable, List, Optional, TypeVar, cast from reactivex import Observable, compose from reactivex import operators as ops from reactivex.internal.basic import identity from reactivex.internal.exceptions import SequenceContainsNoElementsError from reactivex.typing import Comparer _T = TypeVar("_T") def first_only(x: List[_T]) -> _T: if not x: raise SequenceContainsNoElementsError() return x[0] def min_( comparer: Optional[Comparer[_T]] = None, ) -> Callable[[Observable[_T]], Observable[_T]]: """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 compose( ops.min_by(cast(Callable[[_T], _T], identity), comparer), ops.map(first_only), ) __all__ = ["min_"] RxPY-4.0.4/reactivex/operators/_minby.py000066400000000000000000000047051426446175400202430ustar00rootroot00000000000000from typing import Callable, List, Optional, TypeVar, cast from reactivex import Observable, abc, typing from reactivex.internal.basic import default_sub_comparer _T = TypeVar("_T") _TKey = TypeVar("_TKey") def extrema_by( source: Observable[_T], key_mapper: typing.Mapper[_T, _TKey], comparer: typing.SubComparer[_TKey], ) -> Observable[List[_T]]: def subscribe( observer: abc.ObserverBase[List[_T]], scheduler: Optional[abc.SchedulerBase] = None, ) -> abc.DisposableBase: has_value = False last_key: _TKey = cast(_TKey, None) items: List[_T] = [] def on_next(x: _T) -> None: nonlocal has_value, last_key try: key = key_mapper(x) except Exception as ex: observer.on_error(ex) return comparison = 0 if not has_value: has_value = True last_key = key else: try: comparison = comparer(key, last_key) except Exception as ex1: observer.on_error(ex1) return if comparison > 0: last_key = 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=scheduler ) return Observable(subscribe) def min_by_( key_mapper: typing.Mapper[_T, _TKey], comparer: Optional[typing.SubComparer[_TKey]] = None, ) -> Callable[[Observable[_T]], Observable[List[_T]]]: """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 = comparer or default_sub_comparer def min_by(source: Observable[_T]) -> Observable[List[_T]]: return extrema_by(source, key_mapper, lambda x, y: -cmp(x, y)) return min_by __all__ = ["min_by_"] RxPY-4.0.4/reactivex/operators/_multicast.py000066400000000000000000000055731426446175400211360ustar00rootroot00000000000000from typing import Callable, Optional, TypeVar, Union from reactivex import ConnectableObservable, Observable, abc from reactivex import operators as ops from reactivex.disposable import CompositeDisposable _TSource = TypeVar("_TSource") _TResult = TypeVar("_TResult") def multicast_( subject: Optional[abc.SubjectBase[_TSource]] = None, *, subject_factory: Optional[ Callable[[Optional[abc.SchedulerBase]], abc.SubjectBase[_TSource]] ] = None, mapper: Optional[Callable[[Observable[_TSource]], Observable[_TResult]]] = None, ) -> Callable[ [Observable[_TSource]], Union[Observable[_TResult], ConnectableObservable[_TSource]] ]: """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[_TSource], ) -> Union[Observable[_TResult], ConnectableObservable[_TSource]]: if subject_factory: def subscribe( observer: abc.ObserverBase[_TResult], scheduler: Optional[abc.SchedulerBase] = None, ) -> abc.DisposableBase: assert subject_factory connectable = source.pipe( ops.multicast(subject=subject_factory(scheduler)) ) assert mapper subscription = mapper(connectable).subscribe( observer, scheduler=scheduler ) return CompositeDisposable(subscription, connectable.connect(scheduler)) return Observable(subscribe) if not subject: raise ValueError("multicast: Subject cannot be None") ret: ConnectableObservable[_TSource] = ConnectableObservable(source, subject) return ret return multicast RxPY-4.0.4/reactivex/operators/_observeon.py000066400000000000000000000022741426446175400211260ustar00rootroot00000000000000from typing import Callable, Optional, TypeVar from reactivex import Observable, abc from reactivex.observer import ObserveOnObserver _T = TypeVar("_T") def observe_on_( scheduler: abc.SchedulerBase, ) -> Callable[[Observable[_T]], Observable[_T]]: def observe_on(source: Observable[_T]) -> Observable[_T]: """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: abc.ObserverBase[_T], subscribe_scheduler: Optional[abc.SchedulerBase] = None, ): return source.subscribe( ObserveOnObserver(scheduler, observer), scheduler=subscribe_scheduler ) return Observable(subscribe) return observe_on __all__ = ["observe_on_"] RxPY-4.0.4/reactivex/operators/_onerrorresumenext.py000066400000000000000000000006431426446175400227300ustar00rootroot00000000000000from typing import Callable, TypeVar import reactivex from reactivex import Observable _T = TypeVar("_T") def on_error_resume_next_( second: Observable[_T], ) -> Callable[[Observable[_T]], Observable[_T]]: def on_error_resume_next(source: Observable[_T]) -> Observable[_T]: return reactivex.on_error_resume_next(source, second) return on_error_resume_next __all__ = ["on_error_resume_next_"] RxPY-4.0.4/reactivex/operators/_pairwise.py000066400000000000000000000032061426446175400207430ustar00rootroot00000000000000from typing import Callable, Optional, Tuple, TypeVar, cast from reactivex import Observable, abc _T = TypeVar("_T") def pairwise_() -> Callable[[Observable[_T]], Observable[Tuple[_T, _T]]]: def pairwise(source: Observable[_T]) -> Observable[Tuple[_T, _T]]: """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: abc.ObserverBase[Tuple[_T, _T]], scheduler: Optional[abc.SchedulerBase] = None, ) -> abc.DisposableBase: has_previous = False previous: _T = cast(_T, None) def on_next(x: _T) -> None: nonlocal has_previous, previous pair = None with source.lock: if has_previous: pair = (previous, x) else: has_previous = True previous = x if pair: observer.on_next(pair) return source.subscribe(on_next, observer.on_error, observer.on_completed) return Observable(subscribe) return pairwise __all__ = ["pairwise_"] RxPY-4.0.4/reactivex/operators/_partition.py000066400000000000000000000057151426446175400211400ustar00rootroot00000000000000from typing import Callable, List, TypeVar from reactivex import Observable from reactivex import operators as ops from reactivex.typing import Predicate, PredicateIndexed _T = TypeVar("_T") def partition_( predicate: Predicate[_T], ) -> Callable[[Observable[_T]], List[Observable[_T]]]: def partition(source: Observable[_T]) -> List[Observable[_T]]: """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. """ def not_predicate(x: _T) -> bool: return not predicate(x) published = source.pipe( ops.publish(), ops.ref_count(), ) return [ published.pipe(ops.filter(predicate)), published.pipe(ops.filter(not_predicate)), ] return partition def partition_indexed_( predicate_indexed: PredicateIndexed[_T], ) -> Callable[[Observable[_T]], List[Observable[_T]]]: def partition_indexed(source: Observable[_T]) -> List[Observable[_T]]: """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. """ def not_predicate_indexed(x: _T, i: int) -> bool: return not predicate_indexed(x, i) published = source.pipe( ops.publish(), ops.ref_count(), ) return [ published.pipe(ops.filter_indexed(predicate_indexed)), published.pipe(ops.filter_indexed(not_predicate_indexed)), ] return partition_indexed __all__ = ["partition_", "partition_indexed_"] RxPY-4.0.4/reactivex/operators/_pluck.py000066400000000000000000000022701426446175400202360ustar00rootroot00000000000000from typing import Any, Callable, Dict, TypeVar from reactivex import Observable from reactivex import operators as ops _TKey = TypeVar("_TKey") _TValue = TypeVar("_TValue") def pluck_( key: _TKey, ) -> Callable[[Observable[Dict[_TKey, _TValue]]], Observable[_TValue]]: """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. """ def mapper(x: Dict[_TKey, _TValue]) -> _TValue: return x[key] return ops.map(mapper) def pluck_attr_(prop: str) -> Callable[[Observable[Any]], Observable[Any]]: """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)) __all__ = ["pluck_", "pluck_attr_"] RxPY-4.0.4/reactivex/operators/_publish.py000066400000000000000000000043001426446175400205620ustar00rootroot00000000000000from typing import Callable, Optional, TypeVar, Union from reactivex import ConnectableObservable, Observable, abc, compose from reactivex import operators as ops from reactivex.subject import Subject from reactivex.typing import Mapper _TSource = TypeVar("_TSource") _TResult = TypeVar("_TResult") def publish_( mapper: Optional[Mapper[Observable[_TSource], Observable[_TResult]]] = None, ) -> Callable[ [Observable[_TSource]], Union[Observable[_TResult], ConnectableObservable[_TSource]] ]: """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: def factory(scheduler: Optional[abc.SchedulerBase] = None) -> Subject[_TSource]: return Subject() return ops.multicast(subject_factory=factory, mapper=mapper) subject: Subject[_TSource] = Subject() return ops.multicast(subject=subject) def share_() -> Callable[[Observable[_TSource]], Observable[_TSource]]: """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 compose( ops.publish(), ops.ref_count(), ) __all__ = ["publish_", "share_"] RxPY-4.0.4/reactivex/operators/_publishvalue.py000066400000000000000000000015561426446175400216310ustar00rootroot00000000000000from typing import Callable, Optional, TypeVar, Union, cast from reactivex import ConnectableObservable, Observable, abc from reactivex import operators as ops from reactivex.subject import BehaviorSubject from reactivex.typing import Mapper _T1 = TypeVar("_T1") _T2 = TypeVar("_T2") def publish_value_( initial_value: _T1, mapper: Optional[Mapper[Observable[_T1], Observable[_T2]]] = None, ) -> Callable[[Observable[_T1]], Union[Observable[_T2], ConnectableObservable[_T1]]]: if mapper: def subject_factory( scheduler: Optional[abc.SchedulerBase] = None, ) -> BehaviorSubject[_T1]: return BehaviorSubject(initial_value) return ops.multicast(subject_factory=subject_factory, mapper=mapper) subject = BehaviorSubject(cast(_T2, initial_value)) return ops.multicast(subject) __all__ = ["publish_value_"] RxPY-4.0.4/reactivex/operators/_reduce.py000066400000000000000000000030421426446175400203650ustar00rootroot00000000000000from typing import Any, Callable, Type, TypeVar, Union, cast from reactivex import Observable, compose from reactivex import operators as ops from reactivex.internal.utils import NotSet from reactivex.typing import Accumulator _T = TypeVar("_T") _TState = TypeVar("_TState") def reduce_( accumulator: Accumulator[_TState, _T], seed: Union[_TState, Type[NotSet]] = NotSet ) -> Callable[[Observable[_T]], Observable[Any]]: """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: seed_: _TState = cast(_TState, seed) scanner = ops.scan(accumulator, seed=seed_) return compose( scanner, ops.last_or_default(default_value=seed_), ) return compose( ops.scan(cast(Accumulator[_T, _T], accumulator)), ops.last(), ) __all__ = ["reduce_"] RxPY-4.0.4/reactivex/operators/_repeat.py000066400000000000000000000021711426446175400204000ustar00rootroot00000000000000import sys from typing import Callable, Optional, TypeVar import reactivex from reactivex import Observable from reactivex.internal.utils import infinite _T = TypeVar("_T") def repeat_( repeat_count: Optional[int] = None, ) -> Callable[[Observable[_T]], Observable[_T]]: if repeat_count is None: repeat_count = sys.maxsize def repeat(source: Observable[_T]) -> Observable[_T]: """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 reactivex.defer( lambda _: reactivex.concat_with_iterable(source for _ in gen) ) return repeat __all = ["repeat"] RxPY-4.0.4/reactivex/operators/_replay.py000066400000000000000000000045761426446175400204270ustar00rootroot00000000000000from typing import Callable, Optional, TypeVar, Union from reactivex import ConnectableObservable, Observable, abc from reactivex import operators as ops from reactivex import typing from reactivex.subject import ReplaySubject from reactivex.typing import Mapper _TSource = TypeVar("_TSource") _TResult = TypeVar("_TResult") def replay_( mapper: Optional[Mapper[Observable[_TSource], Observable[_TResult]]] = None, buffer_size: Optional[int] = None, window: Optional[typing.RelativeTime] = None, scheduler: Optional[abc.SchedulerBase] = None, ) -> Callable[ [Observable[_TSource]], Union[Observable[_TResult], ConnectableObservable[_TSource]] ]: """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: Optional[abc.SchedulerBase] = None, ) -> ReplaySubject[_TSource]: return ReplaySubject(buffer_size, window, scheduler) return ops.multicast(subject_factory=subject_factory, mapper=mapper) rs: ReplaySubject[_TSource] = ReplaySubject(buffer_size, window, scheduler) return ops.multicast(subject=rs) __all__ = ["replay_"] RxPY-4.0.4/reactivex/operators/_retry.py000066400000000000000000000020731426446175400202660ustar00rootroot00000000000000from typing import Callable, Optional, TypeVar import reactivex from reactivex import Observable from reactivex.internal.utils import infinite _T = TypeVar("_T") def retry_( retry_count: Optional[int] = None, ) -> Callable[[Observable[_T]], Observable[_T]]: """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[_T]) -> Observable[_T]: return reactivex.catch_with_iterable(source for _ in gen) return retry __all__ = ["retry_"] RxPY-4.0.4/reactivex/operators/_sample.py000066400000000000000000000041131426446175400203770ustar00rootroot00000000000000from typing import Any, Callable, Optional, TypeVar, Union, cast import reactivex from reactivex import Observable, abc, typing from reactivex.disposable import CompositeDisposable _T = TypeVar("_T") def sample_observable( source: Observable[_T], sampler: Observable[Any] ) -> Observable[_T]: def subscribe( observer: abc.ObserverBase[_T], scheduler: Optional[abc.SchedulerBase] = None ): at_end = False has_value = False value: _T = cast(_T, None) def sample_subscribe(_: Any = None) -> None: nonlocal has_value if has_value: has_value = False observer.on_next(value) if at_end: observer.on_completed() def on_next(new_value: _T): nonlocal has_value, value has_value = True value = new_value def on_completed(): nonlocal at_end at_end = True return CompositeDisposable( source.subscribe( on_next, observer.on_error, on_completed, scheduler=scheduler ), sampler.subscribe( sample_subscribe, observer.on_error, sample_subscribe, scheduler=scheduler, ), ) return Observable(subscribe) def sample_( sampler: Union[typing.RelativeTime, Observable[Any]], scheduler: Optional[abc.SchedulerBase] = None, ) -> Callable[[Observable[_T]], Observable[_T]]: def sample(source: Observable[_T]) -> Observable[_T]: """Samples the observable sequence at each interval. Examples: >>> res = sample(source) Args: source: Source sequence to sample. Returns: Sampled observable sequence. """ if isinstance(sampler, abc.ObservableBase): return sample_observable(source, sampler) else: return sample_observable( source, reactivex.interval(sampler, scheduler=scheduler) ) return sample __all__ = ["sample_"] RxPY-4.0.4/reactivex/operators/_scan.py000066400000000000000000000033041426446175400200430ustar00rootroot00000000000000from typing import Callable, Type, TypeVar, Union, cast from reactivex import Observable, abc, defer from reactivex import operators as ops from reactivex.internal.utils import NotSet from reactivex.typing import Accumulator _T = TypeVar("_T") _TState = TypeVar("_TState") def scan_( accumulator: Accumulator[_TState, _T], seed: Union[_TState, Type[NotSet]] = NotSet ) -> Callable[[Observable[_T]], Observable[_TState]]: has_seed = seed is not NotSet def scan(source: Observable[_T]) -> Observable[_TState]: """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: abc.SchedulerBase) -> Observable[_TState]: has_accumulation = False accumulation: _TState = cast(_TState, None) def projection(x: _T) -> _TState: nonlocal has_accumulation nonlocal accumulation if has_accumulation: accumulation = accumulator(accumulation, x) else: accumulation = ( accumulator(cast(_TState, seed), x) if has_seed else cast(_TState, x) ) has_accumulation = True return accumulation return source.pipe(ops.map(projection)) return defer(factory) return scan __all__ = ["scan_"] RxPY-4.0.4/reactivex/operators/_sequenceequal.py000066400000000000000000000100631426446175400217570ustar00rootroot00000000000000from typing import Callable, Iterable, List, Optional, TypeVar, Union import reactivex from reactivex import Observable, abc, typing from reactivex.disposable import CompositeDisposable from reactivex.internal import default_comparer _T = TypeVar("_T") def sequence_equal_( second: Union[Observable[_T], Iterable[_T]], comparer: Optional[typing.Comparer[_T]] = None, ) -> Callable[[Observable[_T]], Observable[bool]]: comparer_ = comparer or default_comparer second_ = ( reactivex.from_iterable(second) if isinstance(second, Iterable) else second ) def sequence_equal(source: Observable[_T]) -> Observable[bool]: """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(reactivex.return_value(42)) >>> res = sequence_equal( reactivex.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: abc.ObserverBase[bool], scheduler: Optional[abc.SchedulerBase] = None, ): donel = [False] doner = [False] ql: List[_T] = [] qr: List[_T] = [] def on_next1(x: _T) -> None: 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() -> None: 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: _T): 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=scheduler ) subscription2 = second_.subscribe( on_next2, observer.on_error, on_completed2, scheduler=scheduler ) return CompositeDisposable(subscription1, subscription2) return Observable(subscribe) return sequence_equal __all__ = ["sequence_equal_"] RxPY-4.0.4/reactivex/operators/_single.py000066400000000000000000000020301426446175400203730ustar00rootroot00000000000000from typing import Callable, Optional, TypeVar from reactivex import Observable, compose from reactivex import operators as ops from reactivex.typing import Predicate _T = TypeVar("_T") def single_( predicate: Optional[Predicate[_T]] = None, ) -> Callable[[Observable[_T]], Observable[_T]]: """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 compose(ops.filter(predicate), ops.single()) else: return ops.single_or_default_async(False) __all__ = ["single_"] RxPY-4.0.4/reactivex/operators/_singleordefault.py000066400000000000000000000054301426446175400223100ustar00rootroot00000000000000from typing import Callable, Optional, TypeVar, cast from reactivex import Observable, abc, compose from reactivex import operators as ops from reactivex.internal.exceptions import SequenceContainsNoElementsError from reactivex.typing import Predicate _T = TypeVar("_T") def single_or_default_async_( has_default: bool = False, default_value: Optional[_T] = None ) -> Callable[[Observable[_T]], Observable[_T]]: def single_or_default_async(source: Observable[_T]) -> Observable[_T]: def subscribe( observer: abc.ObserverBase[_T], scheduler: Optional[abc.SchedulerBase] = None, ): value = cast(_T, default_value) seen_value = False def on_next(x: _T): nonlocal value, seen_value if seen_value: observer.on_error( Exception("Sequence contains more than one element") ) else: value = x seen_value = True def on_completed(): if not seen_value and not has_default: observer.on_error(SequenceContainsNoElementsError()) else: observer.on_next(value) observer.on_completed() return source.subscribe( on_next, observer.on_error, on_completed, scheduler=scheduler ) return Observable(subscribe) return single_or_default_async def single_or_default_( predicate: Optional[Predicate[_T]] = None, default_value: _T = None ) -> Callable[[Observable[_T]], Observable[_T]]: """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 compose( ops.filter(predicate), ops.single_or_default(None, default_value) ) else: return single_or_default_async_(True, default_value) __all__ = ["single_or_default_", "single_or_default_async_"] RxPY-4.0.4/reactivex/operators/_skip.py000066400000000000000000000024641426446175400200730ustar00rootroot00000000000000from typing import Callable, Optional, TypeVar from reactivex import Observable, abc from reactivex.internal import ArgumentOutOfRangeException _T = TypeVar("_T") def skip_(count: int) -> Callable[[Observable[_T]], Observable[_T]]: if count < 0: raise ArgumentOutOfRangeException() def skip(source: Observable[_T]) -> Observable[_T]: """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: abc.ObserverBase[_T], scheduler: Optional[abc.SchedulerBase] = None, ): remaining = count def on_next(value: _T) -> None: nonlocal remaining if remaining <= 0: observer.on_next(value) else: remaining -= 1 return source.subscribe( on_next, observer.on_error, observer.on_completed, scheduler=scheduler ) return Observable(subscribe) return skip __all__ = ["skip_"] RxPY-4.0.4/reactivex/operators/_skiplast.py000066400000000000000000000030661426446175400207560ustar00rootroot00000000000000from typing import Callable, List, Optional, TypeVar from reactivex import Observable, abc _T = TypeVar("_T") def skip_last_(count: int) -> Callable[[Observable[_T]], Observable[_T]]: def skip_last(source: Observable[_T]) -> Observable[_T]: """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: abc.ObserverBase[_T], scheduler: Optional[abc.SchedulerBase] = None, ): q: List[_T] = [] def on_next(value: _T) -> None: 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=scheduler ) return Observable(subscribe) return skip_last __all__ = ["skip_last_"] RxPY-4.0.4/reactivex/operators/_skiplastwithtime.py000066400000000000000000000044601426446175400225300ustar00rootroot00000000000000from typing import Any, Callable, Dict, List, Optional, TypeVar from reactivex import Observable, abc, typing from reactivex.scheduler import TimeoutScheduler _T = TypeVar("_T") def skip_last_with_time_( duration: typing.RelativeTime, scheduler: Optional[abc.SchedulerBase] = None ) -> Callable[[Observable[_T]], Observable[_T]]: """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[_T]) -> Observable[_T]: def subscribe( observer: abc.ObserverBase[_T], scheduler_: Optional[abc.SchedulerBase] = None, ) -> abc.DisposableBase: nonlocal duration _scheduler: abc.SchedulerBase = ( scheduler or scheduler_ or TimeoutScheduler.singleton() ) duration = _scheduler.to_timedelta(duration) q: List[Dict[str, Any]] = [] def on_next(x: _T) -> None: 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() -> None: 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=_scheduler ) return Observable(subscribe) return skip_last_with_time __all__ = ["skip_last_with_time_"] RxPY-4.0.4/reactivex/operators/_skipuntil.py000066400000000000000000000041251426446175400211430ustar00rootroot00000000000000from asyncio import Future from typing import Any, Callable, Optional, TypeVar, Union from reactivex import Observable, abc, from_future from reactivex.disposable import CompositeDisposable, SingleAssignmentDisposable _T = TypeVar("_T") def skip_until_( other: Union[Observable[Any], "Future[Any]"] ) -> Callable[[Observable[_T]], Observable[_T]]: """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 isinstance(other, Future): obs: Observable[Any] = from_future(other) else: obs = other def skip_until(source: Observable[_T]) -> Observable[_T]: def subscribe( observer: abc.ObserverBase[_T], scheduler: Optional[abc.SchedulerBase] = None, ): is_open = [False] def on_next(left: _T) -> None: if is_open[0]: observer.on_next(left) def on_completed() -> None: if is_open[0]: observer.on_completed() subs = source.subscribe( on_next, observer.on_error, on_completed, scheduler=scheduler ) subscriptions = CompositeDisposable(subs) right_subscription = SingleAssignmentDisposable() subscriptions.add(right_subscription) def on_next2(x: Any) -> None: 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=scheduler ) return subscriptions return Observable(subscribe) return skip_until __all__ = ["skip_until_"] RxPY-4.0.4/reactivex/operators/_skipuntilwithtime.py000066400000000000000000000043141426446175400227160ustar00rootroot00000000000000from datetime import datetime from typing import Any, Callable, Optional, TypeVar from reactivex import Observable, abc, typing from reactivex.disposable import CompositeDisposable from reactivex.scheduler import TimeoutScheduler _T = TypeVar("_T") def skip_until_with_time_( start_time: typing.AbsoluteOrRelativeTime, scheduler: Optional[abc.SchedulerBase] = None, ) -> Callable[[Observable[_T]], Observable[_T]]: def skip_until_with_time(source: Observable[_T]) -> Observable[_T]: """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: abc.ObserverBase[_T], scheduler_: Optional[abc.SchedulerBase] = None, ): _scheduler = scheduler or scheduler_ or TimeoutScheduler.singleton() open = [False] def on_next(x: _T) -> None: if open[0]: observer.on_next(x) subscription = source.subscribe( on_next, observer.on_error, observer.on_completed, scheduler=scheduler_ ) def action(scheduler: abc.SchedulerBase, state: Any): open[0] = True disp = getattr(_scheduler, scheduler_method)(start_time, action) return CompositeDisposable(disp, subscription) return Observable(subscribe) return skip_until_with_time __all__ = ["skip_until_with_time_"] RxPY-4.0.4/reactivex/operators/_skipwhile.py000066400000000000000000000042351426446175400211220ustar00rootroot00000000000000from typing import Callable, Optional, Tuple, TypeVar from reactivex import Observable, abc, compose from reactivex import operators as ops from reactivex import typing _T = TypeVar("_T") def skip_while_( predicate: typing.Predicate[_T], ) -> Callable[[Observable[_T]], Observable[_T]]: def skip_while(source: Observable[_T]) -> Observable[_T]: """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: abc.ObserverBase[_T], scheduler: Optional[abc.SchedulerBase] = None, ): running = False def on_next(value: _T): 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=scheduler ) return Observable(subscribe) return skip_while def skip_while_indexed_( predicate: typing.PredicateIndexed[_T], ) -> Callable[[Observable[_T]], Observable[_T]]: def indexer(x: _T, i: int) -> Tuple[_T, int]: return (x, i) def skipper(x: Tuple[_T, int]) -> bool: return predicate(*x) def mapper(x: Tuple[_T, int]) -> _T: return x[0] return compose( ops.map_indexed(indexer), ops.skip_while(skipper), ops.map(mapper), ) __all__ = ["skip_while_", "skip_while_indexed_"] RxPY-4.0.4/reactivex/operators/_skipwithtime.py000066400000000000000000000042541426446175400216450ustar00rootroot00000000000000from typing import Any, Callable, Optional, TypeVar from reactivex import Observable, abc, typing from reactivex.disposable import CompositeDisposable from reactivex.scheduler import TimeoutScheduler _T = TypeVar("_T") def skip_with_time_( duration: typing.RelativeTime, scheduler: Optional[abc.SchedulerBase] = None ) -> Callable[[Observable[_T]], Observable[_T]]: def skip_with_time(source: Observable[_T]) -> Observable[_T]: """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: abc.ObserverBase[_T], scheduler_: Optional[abc.SchedulerBase] = None, ): _scheduler = scheduler or scheduler_ or TimeoutScheduler.singleton() open = [False] def action(scheduler: abc.SchedulerBase, state: Any) -> None: open[0] = True t = _scheduler.schedule_relative(duration, action) def on_next(x: _T): if open[0]: observer.on_next(x) d = source.subscribe( on_next, observer.on_error, observer.on_completed, scheduler=scheduler_ ) return CompositeDisposable(t, d) return Observable(subscribe) return skip_with_time __all__ = ["skip_with_time_"] RxPY-4.0.4/reactivex/operators/_slice.py000066400000000000000000000044071426446175400202230ustar00rootroot00000000000000from sys import maxsize from typing import Any, Callable, List, Optional, TypeVar from reactivex import Observable from reactivex import operators as ops _T = TypeVar("_T") # pylint: disable=redefined-builtin def slice_( start: Optional[int] = None, stop: Optional[int] = None, step: Optional[int] = None ) -> Callable[[Observable[_T]], Observable[_T]]: _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: List[Callable[[Observable[Any]], Observable[Any]]] = [] def slice(source: Observable[_T]) -> Observable[_T]: """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 __all__ = ["slice_"] RxPY-4.0.4/reactivex/operators/_some.py000066400000000000000000000031701426446175400200630ustar00rootroot00000000000000from typing import Callable, Optional, TypeVar from reactivex import Observable, abc from reactivex import operators as ops from reactivex.typing import Predicate _T = TypeVar("_T") def some_( predicate: Optional[Predicate[_T]] = None, ) -> Callable[[Observable[_T]], Observable[bool]]: def some(source: Observable[_T]) -> Observable[bool]: """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: abc.ObserverBase[bool], scheduler: Optional[abc.SchedulerBase] = None, ): def on_next(_: _T): 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=scheduler ) if predicate: return source.pipe( ops.filter(predicate), some_(), ) return Observable(subscribe) return some __all__ = ["some_"] RxPY-4.0.4/reactivex/operators/_startswith.py000066400000000000000000000012611426446175400213330ustar00rootroot00000000000000from typing import Callable, TypeVar import reactivex from reactivex import Observable _T = TypeVar("_T") def start_with_(*args: _T) -> Callable[[Observable[_T]], Observable[_T]]: def start_with(source: Observable[_T]) -> Observable[_T]: """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 = reactivex.from_iterable(args) sequence = [start, source] return reactivex.concat(*sequence) return start_with __all__ = ["start_with_"] RxPY-4.0.4/reactivex/operators/_subscribeon.py000066400000000000000000000033201426446175400214330ustar00rootroot00000000000000from typing import Any, Callable, Optional, TypeVar from reactivex import Observable, abc from reactivex.disposable import ( ScheduledDisposable, SerialDisposable, SingleAssignmentDisposable, ) _T = TypeVar("_T") def subscribe_on_( scheduler: abc.SchedulerBase, ) -> Callable[[Observable[_T]], Observable[_T]]: def subscribe_on(source: Observable[_T]) -> Observable[_T]: """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: abc.ObserverBase[_T], _: Optional[abc.SchedulerBase] = None ): m = SingleAssignmentDisposable() d = SerialDisposable() d.disposable = m def action(scheduler: abc.SchedulerBase, state: Optional[Any] = None): d.disposable = ScheduledDisposable( scheduler, source.subscribe(observer) ) m.disposable = scheduler.schedule(action) return d return Observable(subscribe) return subscribe_on __all__ = ["subscribe_on_"] RxPY-4.0.4/reactivex/operators/_sum.py000066400000000000000000000007771426446175400177360ustar00rootroot00000000000000from typing import Any, Callable, Optional from reactivex import Observable, compose from reactivex import operators as ops from reactivex.typing import Mapper def sum_( key_mapper: Optional[Mapper[Any, float]] = None ) -> Callable[[Observable[Any]], Observable[float]]: if key_mapper: return compose(ops.map(key_mapper), ops.sum()) def accumulator(prev: float, cur: float) -> float: return prev + cur return ops.reduce(seed=0, accumulator=accumulator) __all__ = ["sum_"] RxPY-4.0.4/reactivex/operators/_switchlatest.py000066400000000000000000000055321426446175400216420ustar00rootroot00000000000000from asyncio import Future from typing import Any, Callable, Optional, TypeVar, Union from reactivex import Observable, abc, from_future from reactivex.disposable import ( CompositeDisposable, SerialDisposable, SingleAssignmentDisposable, ) _T = TypeVar("_T") def switch_latest_() -> Callable[ [Observable[Union[Observable[_T], "Future[_T]"]]], Observable[_T] ]: def switch_latest( source: Observable[Union[Observable[_T], "Future[_T]"]] ) -> Observable[_T]: """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: abc.ObserverBase[_T], scheduler: Optional[abc.SchedulerBase] = None, ) -> abc.DisposableBase: inner_subscription = SerialDisposable() has_latest = [False] is_stopped = [False] latest = [0] def on_next(inner_source: Union[Observable[_T], "Future[_T]"]) -> None: 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 isinstance(inner_source, Future): obs = from_future(inner_source) else: obs = 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 __all__ = ["switch_latest_"] RxPY-4.0.4/reactivex/operators/_take.py000066400000000000000000000026351426446175400200510ustar00rootroot00000000000000from typing import Callable, Optional, TypeVar from reactivex import Observable, abc, empty from reactivex.internal import ArgumentOutOfRangeException _T = TypeVar("_T") def take_(count: int) -> Callable[[Observable[_T]], Observable[_T]]: if count < 0: raise ArgumentOutOfRangeException() def take(source: Observable[_T]) -> Observable[_T]: """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: abc.ObserverBase[_T], scheduler: Optional[abc.SchedulerBase] = None, ): remaining = count def on_next(value: _T) -> None: 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=scheduler ) return Observable(subscribe) return take __all__ = ["take_"] RxPY-4.0.4/reactivex/operators/_takelast.py000066400000000000000000000031231426446175400207260ustar00rootroot00000000000000from typing import Callable, List, Optional, TypeVar from reactivex import Observable, abc _T = TypeVar("_T") def take_last_(count: int) -> Callable[[Observable[_T]], Observable[_T]]: def take_last(source: Observable[_T]) -> Observable[_T]: """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: abc.ObserverBase[_T], scheduler: Optional[abc.SchedulerBase] = None, ) -> abc.DisposableBase: q: List[_T] = [] def on_next(x: _T) -> None: 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=scheduler ) return Observable(subscribe) return take_last __all__ = ["take_last_"] RxPY-4.0.4/reactivex/operators/_takelastbuffer.py000066400000000000000000000032371426446175400221260ustar00rootroot00000000000000from typing import Callable, List, Optional, TypeVar from reactivex import Observable, abc _T = TypeVar("_T") def take_last_buffer_(count: int) -> Callable[[Observable[_T]], Observable[List[_T]]]: def take_last_buffer(source: Observable[_T]) -> Observable[List[_T]]: """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: abc.ObserverBase[List[_T]], scheduler: Optional[abc.SchedulerBase] = None, ) -> abc.DisposableBase: q: List[_T] = [] def on_next(x: _T) -> None: with source.lock: q.append(x) if len(q) > count: q.pop(0) def on_completed() -> None: observer.on_next(q) observer.on_completed() return source.subscribe( on_next, observer.on_error, on_completed, scheduler=scheduler ) return Observable(subscribe) return take_last_buffer __all__ = ["take_last_buffer_"] RxPY-4.0.4/reactivex/operators/_takelastwithtime.py000066400000000000000000000044421426446175400225060ustar00rootroot00000000000000from typing import Any, Callable, Dict, List, Optional, TypeVar from reactivex import Observable, abc, typing from reactivex.scheduler import TimeoutScheduler _T = TypeVar("_T") def take_last_with_time_( duration: typing.RelativeTime, scheduler: Optional[abc.SchedulerBase] = None ) -> Callable[[Observable[_T]], Observable[_T]]: def take_last_with_time(source: Observable[_T]) -> Observable[_T]: """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: abc.ObserverBase[_T], scheduler_: Optional[abc.SchedulerBase] = None, ) -> abc.DisposableBase: nonlocal duration _scheduler = scheduler or scheduler_ or TimeoutScheduler.singleton() duration = _scheduler.to_timedelta(duration) q: List[Dict[str, Any]] = [] def on_next(x: _T) -> None: 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=scheduler_ ) return Observable(subscribe) return take_last_with_time __all__ = ["take_last_with_time_"] RxPY-4.0.4/reactivex/operators/_takeuntil.py000066400000000000000000000027531426446175400211260ustar00rootroot00000000000000from asyncio import Future from typing import Callable, Optional, TypeVar, Union from reactivex import Observable, abc, from_future from reactivex.disposable import CompositeDisposable from reactivex.internal import noop _T = TypeVar("_T") def take_until_( other: Union[Observable[_T], "Future[_T]"] ) -> Callable[[Observable[_T]], Observable[_T]]: if isinstance(other, Future): obs: Observable[_T] = from_future(other) else: obs = other def take_until(source: Observable[_T]) -> Observable[_T]: """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: abc.ObserverBase[_T], scheduler: Optional[abc.SchedulerBase] = None, ) -> abc.DisposableBase: def on_completed(_: _T) -> None: observer.on_completed() return CompositeDisposable( source.subscribe(observer, scheduler=scheduler), obs.subscribe( on_completed, observer.on_error, noop, scheduler=scheduler ), ) return Observable(subscribe) return take_until __all__ = ["take_until_"] RxPY-4.0.4/reactivex/operators/_takeuntilwithtime.py000066400000000000000000000032521426446175400226740ustar00rootroot00000000000000from datetime import datetime from typing import Any, Callable, Optional, TypeVar from reactivex import Observable, abc, typing from reactivex.disposable import CompositeDisposable from reactivex.scheduler import TimeoutScheduler _T = TypeVar("_T") def take_until_with_time_( end_time: typing.AbsoluteOrRelativeTime, scheduler: Optional[abc.SchedulerBase] = None, ) -> Callable[[Observable[_T]], Observable[_T]]: def take_until_with_time(source: Observable[_T]) -> Observable[_T]: """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: abc.ObserverBase[_T], scheduler_: Optional[abc.SchedulerBase] = None, ) -> abc.DisposableBase: _scheduler = scheduler or scheduler_ or TimeoutScheduler.singleton() def action(scheduler: abc.SchedulerBase, state: Any = None): observer.on_completed() if isinstance(end_time, datetime): task = _scheduler.schedule_absolute(end_time, action) else: task = _scheduler.schedule_relative(end_time, action) return CompositeDisposable( task, source.subscribe(observer, scheduler=scheduler_) ) return Observable(subscribe) return take_until_with_time __all__ = ["take_until_with_time_"] RxPY-4.0.4/reactivex/operators/_takewhile.py000066400000000000000000000070341426446175400211000ustar00rootroot00000000000000from typing import Any, Callable, Optional, TypeVar from reactivex import Observable, abc from reactivex.typing import Predicate, PredicateIndexed _T = TypeVar("_T") def take_while_( predicate: Predicate[_T], inclusive: bool = False ) -> Callable[[Observable[_T]], Observable[_T]]: def take_while(source: Observable[_T]) -> Observable[_T]: """Returns elements from an observable sequence as long as a specified condition is true. 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: abc.ObserverBase[_T], scheduler: Optional[abc.SchedulerBase] = None, ) -> abc.DisposableBase: running = True def on_next(value: _T): 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=scheduler ) return Observable(subscribe) return take_while def take_while_indexed_( predicate: PredicateIndexed[_T], inclusive: bool = False ) -> Callable[[Observable[_T]], Observable[_T]]: def take_while_indexed(source: Observable[_T]) -> Observable[_T]: """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: abc.ObserverBase[_T], scheduler: Optional[abc.SchedulerBase] = None, ) -> abc.DisposableBase: 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=scheduler ) return Observable(subscribe) return take_while_indexed __all__ = ["take_while_", "take_while_indexed_"] RxPY-4.0.4/reactivex/operators/_takewithtime.py000066400000000000000000000034721426446175400216240ustar00rootroot00000000000000from typing import Any, Callable, Optional, TypeVar from reactivex import Observable, abc, typing from reactivex.disposable import CompositeDisposable from reactivex.scheduler import TimeoutScheduler _T = TypeVar("_T") def take_with_time_( duration: typing.RelativeTime, scheduler: Optional[abc.SchedulerBase] = None ) -> Callable[[Observable[_T]], Observable[_T]]: def take_with_time(source: Observable[_T]) -> Observable[_T]: """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: abc.ObserverBase[_T], scheduler_: Optional[abc.SchedulerBase] = None, ) -> abc.DisposableBase: _scheduler = scheduler or scheduler_ or TimeoutScheduler.singleton() def action(scheduler: abc.SchedulerBase, state: Any = None): observer.on_completed() disp = _scheduler.schedule_relative(duration, action) return CompositeDisposable( disp, source.subscribe(observer, scheduler=scheduler_) ) return Observable(subscribe) return take_with_time __all__ = ["take_with_time_"] RxPY-4.0.4/reactivex/operators/_throttlefirst.py000066400000000000000000000035371426446175400220440ustar00rootroot00000000000000from datetime import datetime from typing import Callable, Optional, TypeVar from reactivex import Observable, abc, typing from reactivex.scheduler import TimeoutScheduler _T = TypeVar("_T") def throttle_first_( window_duration: typing.RelativeTime, scheduler: Optional[abc.SchedulerBase] = None ) -> Callable[[Observable[_T]], Observable[_T]]: def throttle_first(source: Observable[_T]) -> Observable[_T]: """Returns an observable that emits only the first item emitted by the source Observable during sequential time windows of a specified duration. Args: source: Source observable to throttle. Returns: An Observable that performs the throttle operation. """ def subscribe( observer: abc.ObserverBase[_T], scheduler_: Optional[abc.SchedulerBase] = None, ) -> abc.DisposableBase: _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: Optional[datetime] = None def on_next(x: _T) -> None: nonlocal last_on_next emit = False now = _scheduler.now with source.lock: if not last_on_next or now - last_on_next >= duration: last_on_next = 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 __all__ = ["throttle_first_"] RxPY-4.0.4/reactivex/operators/_timeinterval.py000066400000000000000000000030211426446175400216160ustar00rootroot00000000000000from dataclasses import dataclass from datetime import timedelta from typing import Callable, Generic, Optional, TypeVar from reactivex import Observable, abc from reactivex import operators as ops from reactivex.scheduler import TimeoutScheduler _T = TypeVar("_T") @dataclass class TimeInterval(Generic[_T]): value: _T interval: timedelta def time_interval_( scheduler: Optional[abc.SchedulerBase] = None, ) -> Callable[[Observable[_T]], Observable[TimeInterval[_T]]]: def time_interval(source: Observable[_T]) -> Observable[TimeInterval[_T]]: """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: abc.ObserverBase[TimeInterval[_T]], scheduler_: Optional[abc.SchedulerBase] = None, ) -> abc.DisposableBase: _scheduler = scheduler or scheduler_ or TimeoutScheduler.singleton() last = _scheduler.now def mapper(value: _T) -> TimeInterval[_T]: 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=_scheduler ) return Observable(subscribe) return time_interval RxPY-4.0.4/reactivex/operators/_timeout.py000066400000000000000000000062571426446175400206170ustar00rootroot00000000000000from asyncio import Future from datetime import datetime from typing import Any, Callable, Optional, TypeVar, Union from reactivex import Observable, abc, from_future, throw, typing from reactivex.disposable import ( CompositeDisposable, SerialDisposable, SingleAssignmentDisposable, ) from reactivex.scheduler import TimeoutScheduler _T = TypeVar("_T") def timeout_( duetime: typing.AbsoluteOrRelativeTime, other: Optional[Union[Observable[_T], "Future[_T]"]] = None, scheduler: Optional[abc.SchedulerBase] = None, ) -> Callable[[Observable[_T]], Observable[_T]]: other = other or throw(Exception("Timeout")) if isinstance(other, Future): obs = from_future(other) else: obs = other def timeout(source: Observable[_T]) -> Observable[_T]: """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: abc.ObserverBase[_T], scheduler_: Optional[abc.SchedulerBase] = None, ) -> abc.DisposableBase: _scheduler = scheduler or scheduler_ or TimeoutScheduler.singleton() switched = [False] _id = [0] original = SingleAssignmentDisposable() subscription = SerialDisposable() timer = SerialDisposable() subscription.disposable = original def create_timer() -> None: my_id = _id[0] def action(scheduler: abc.SchedulerBase, state: Any = None): switched[0] = _id[0] == my_id timer_wins = switched[0] if timer_wins: subscription.disposable = obs.subscribe( observer, scheduler=scheduler ) if isinstance(duetime, datetime): timer.disposable = _scheduler.schedule_absolute(duetime, action) else: timer.disposable = _scheduler.schedule_relative(duetime, action) create_timer() def on_next(value: _T) -> None: send_wins = not switched[0] if send_wins: _id[0] += 1 observer.on_next(value) create_timer() def on_error(error: Exception) -> None: on_error_wins = not switched[0] if on_error_wins: _id[0] += 1 observer.on_error(error) def on_completed() -> None: 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=scheduler_ ) return CompositeDisposable(subscription, timer) return Observable(subscribe) return timeout RxPY-4.0.4/reactivex/operators/_timeoutwithmapper.py000066400000000000000000000104701426446175400227100ustar00rootroot00000000000000from typing import Any, Callable, Optional, TypeVar import reactivex from reactivex import Observable, abc from reactivex.disposable import ( CompositeDisposable, SerialDisposable, SingleAssignmentDisposable, ) _T = TypeVar("_T") def timeout_with_mapper_( first_timeout: Optional[Observable[_T]] = None, timeout_duration_mapper: Optional[Callable[[Any], Observable[Any]]] = None, other: Optional[Observable[_T]] = None, ) -> Callable[[Observable[_T]], Observable[_T]]: """Returns the source observable sequence, switching to the other observable sequence if a timeout is signaled. res = timeout_with_mapper(reactivex.timer(500)) res = timeout_with_mapper(reactivex.timer(500), lambda x: reactivex.timer(200)) res = timeout_with_mapper( reactivex.timer(500), lambda x: reactivex.timer(200)), reactivex.return_value(42) ) Args: first_timeout -- [Optional] Observable sequence that represents the timeout for the first element. If not provided, this defaults to reactivex.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 reactivex.throw(). Returns: The source sequence switching to the other sequence in case of a timeout. """ first_timeout_ = first_timeout or reactivex.never() other_ = other or reactivex.throw(Exception("Timeout")) def timeout_with_mapper(source: Observable[_T]) -> Observable[_T]: def subscribe( observer: abc.ObserverBase[_T], scheduler: Optional[abc.SchedulerBase] = None, ) -> abc.DisposableBase: subscription = SerialDisposable() timer = SerialDisposable() original = SingleAssignmentDisposable() subscription.disposable = original switched = False _id = [0] def set_timer(timeout: Observable[Any]) -> None: my_id = _id[0] def timer_wins(): return _id[0] == my_id d = SingleAssignmentDisposable() timer.disposable = d def on_next(x: Any) -> None: if timer_wins(): subscription.disposable = other_.subscribe( observer, scheduler=scheduler ) d.dispose() def on_error(e: Exception) -> None: if timer_wins(): observer.on_error(e) def on_completed() -> None: if timer_wins(): subscription.disposable = other_.subscribe(observer) d.disposable = timeout.subscribe( on_next, on_error, on_completed, scheduler=scheduler ) set_timer(first_timeout_) def observer_wins(): res = not switched if res: _id[0] += 1 return res def on_next(x: _T) -> None: if observer_wins(): observer.on_next(x) timeout = None try: timeout = ( timeout_duration_mapper(x) if timeout_duration_mapper else reactivex.never() ) except Exception as e: observer.on_error(e) return set_timer(timeout) def on_error(error: Exception) -> None: if observer_wins(): observer.on_error(error) def on_completed() -> None: if observer_wins(): observer.on_completed() original.disposable = source.subscribe( on_next, on_error, on_completed, scheduler=scheduler ) return CompositeDisposable(subscription, timer) return Observable(subscribe) return timeout_with_mapper __all__ = ["timeout_with_mapper_"] RxPY-4.0.4/reactivex/operators/_timestamp.py000066400000000000000000000025111426446175400211210ustar00rootroot00000000000000from dataclasses import dataclass from datetime import datetime from typing import Any, Callable, Generic, Optional, TypeVar from reactivex import Observable, abc, defer, operators from reactivex.scheduler import TimeoutScheduler _T = TypeVar("_T") @dataclass class Timestamp(Generic[_T]): value: _T timestamp: datetime def timestamp_( scheduler: Optional[abc.SchedulerBase] = None, ) -> Callable[[Observable[_T]], Observable[Timestamp[_T]]]: def timestamp(source: Observable[Any]) -> Observable[Timestamp[_T]]: """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_: Optional[abc.SchedulerBase] = None): _scheduler = scheduler or scheduler_ or TimeoutScheduler.singleton() def mapper(value: _T) -> Timestamp[_T]: return Timestamp(value=value, timestamp=_scheduler.now) return source.pipe(operators.map(mapper)) return defer(factory) return timestamp __all__ = ["timestamp_"] RxPY-4.0.4/reactivex/operators/_todict.py000066400000000000000000000037031426446175400204100ustar00rootroot00000000000000from typing import Callable, Dict, Optional, TypeVar, cast from reactivex import Observable, abc from reactivex.typing import Mapper _T = TypeVar("_T") _TKey = TypeVar("_TKey") _TValue = TypeVar("_TValue") def to_dict_( key_mapper: Mapper[_T, _TKey], element_mapper: Optional[Mapper[_T, _TValue]] = None ) -> Callable[[Observable[_T]], Observable[Dict[_TKey, _TValue]]]: def to_dict(source: Observable[_T]) -> Observable[Dict[_TKey, _TValue]]: """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: abc.ObserverBase[Dict[_TKey, _TValue]], scheduler: Optional[abc.SchedulerBase] = None, ) -> abc.DisposableBase: m: Dict[_TKey, _TValue] = dict() def on_next(x: _T) -> None: try: key = key_mapper(x) except Exception as ex: # pylint: disable=broad-except observer.on_error(ex) return if element_mapper: try: element = element_mapper(x) except Exception as ex: # pylint: disable=broad-except observer.on_error(ex) return else: element = cast(_TValue, x) m[key] = cast(_TValue, 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=scheduler ) return Observable(subscribe) return to_dict __all__ = ["to_dict_"] RxPY-4.0.4/reactivex/operators/_tofuture.py000066400000000000000000000040151426446175400207740ustar00rootroot00000000000000import asyncio from asyncio import Future from typing import Callable, Optional, TypeVar, cast from reactivex import Observable, abc from reactivex.internal.exceptions import SequenceContainsNoElementsError _T = TypeVar("_T") def to_future_( future_ctor: Optional[Callable[[], "Future[_T]"]] = None, scheduler: Optional[abc.SchedulerBase] = None, ) -> Callable[[Observable[_T]], "Future[_T]"]: future_ctor_: Callable[[], "Future[_T]"] = ( future_ctor or asyncio.get_event_loop().create_future ) future: "Future[_T]" = future_ctor_() def to_future(source: Observable[_T]) -> "Future[_T]": """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 = reactivex.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 = cast(_T, None) def on_next(value: _T): nonlocal last_value nonlocal has_value last_value = value has_value = True def on_error(err: Exception): 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 __all__ = ["to_future_"] RxPY-4.0.4/reactivex/operators/_toiterable.py000066400000000000000000000022221426446175400212470ustar00rootroot00000000000000from typing import Callable, List, Optional, TypeVar from reactivex import Observable, abc _T = TypeVar("_T") def to_iterable_() -> Callable[[Observable[_T]], Observable[List[_T]]]: def to_iterable(source: Observable[_T]) -> Observable[List[_T]]: """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: abc.ObserverBase[List[_T]], scheduler: Optional[abc.SchedulerBase] = None, ): nonlocal source queue: List[_T] = [] def on_next(item: _T): 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=scheduler ) return Observable(subscribe) return to_iterable __all__ = ["to_iterable_"] RxPY-4.0.4/reactivex/operators/_tomarbles.py000066400000000000000000000044121426446175400211100ustar00rootroot00000000000000from typing import Any, List, Optional from reactivex import Observable, abc from reactivex.scheduler import NewThreadScheduler from reactivex.typing import RelativeTime new_thread_scheduler = NewThreadScheduler() def to_marbles( timespan: RelativeTime = 0.1, scheduler: Optional[abc.SchedulerBase] = None ): def to_marbles(source: Observable[Any]) -> Observable[str]: """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: abc.ObserverBase[str], scheduler: Optional[abc.SchedulerBase] = 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) timespan_ = scheduler.to_seconds(timespan) dashes = "-" * int((secs + timespan_ / 2.0) * (1.0 / timespan_)) result.append(dashes) def on_next(value: Any) -> None: add_timespan() result.append(stringify(value)) def on_error(exception: Exception) -> None: 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: Any) -> str: """Utility for stringifying an event.""" string = str(value) if len(string) > 1: string = "(%s)" % string return string __all__ = ["stringify"] RxPY-4.0.4/reactivex/operators/_toset.py000066400000000000000000000017441426446175400202630ustar00rootroot00000000000000from typing import Callable, Optional, Set, TypeVar from reactivex import Observable, abc _T = TypeVar("_T") def to_set_() -> Callable[[Observable[_T]], Observable[Set[_T]]]: """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[_T]) -> Observable[Set[_T]]: def subscribe( observer: abc.ObserverBase[Set[_T]], scheduler: Optional[abc.SchedulerBase] = None, ) -> abc.DisposableBase: s: Set[_T] = set() def on_completed() -> None: nonlocal s observer.on_next(s) s = set() observer.on_completed() return source.subscribe( s.add, observer.on_error, on_completed, scheduler=scheduler ) return Observable(subscribe) return to_set __all__ = ["to_set_"] RxPY-4.0.4/reactivex/operators/_whiledo.py000066400000000000000000000020571426446175400205560ustar00rootroot00000000000000import itertools from asyncio import Future from typing import Callable, TypeVar, Union import reactivex from reactivex import Observable from reactivex.internal.utils import infinite from reactivex.typing import Predicate _T = TypeVar("_T") def while_do_( condition: Predicate[Observable[_T]], ) -> Callable[[Observable[_T]], Observable[_T]]: def while_do(source: Union[Observable[_T], "Future[_T]"]) -> Observable[_T]: """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 isinstance(source, Future): obs = reactivex.from_future(source) else: obs = source it = itertools.takewhile(condition, (obs for _ in infinite())) return reactivex.concat_with_iterable(it) return while_do __all__ = ["while_do_"] RxPY-4.0.4/reactivex/operators/_window.py000066400000000000000000000120701426446175400204260ustar00rootroot00000000000000import logging from typing import Any, Callable, Optional, Tuple, TypeVar from reactivex import Observable, abc, empty from reactivex import operators as ops from reactivex.disposable import ( CompositeDisposable, RefCountDisposable, SerialDisposable, SingleAssignmentDisposable, ) from reactivex.internal import add_ref, noop from reactivex.subject import Subject log = logging.getLogger("Rx") _T = TypeVar("_T") def window_toggle_( openings: Observable[Any], closing_mapper: Callable[[Any], Observable[Any]] ) -> Callable[[Observable[_T]], Observable[Observable[_T]]]: """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[_T]) -> Observable[Observable[_T]]: def mapper(args: Tuple[Any, Observable[_T]]): _, 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[Any], ) -> Callable[[Observable[_T]], Observable[Observable[_T]]]: """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[_T]) -> Observable[Observable[_T]]: def subscribe( observer: abc.ObserverBase[Observable[_T]], scheduler: Optional[abc.SchedulerBase] = None, ) -> abc.DisposableBase: window_subject: Subject[_T] = Subject() d = CompositeDisposable() r = RefCountDisposable(d) observer.on_next(add_ref(window_subject, r)) def on_next_window(x: _T) -> None: window_subject.on_next(x) def on_error(err: Exception) -> None: window_subject.on_error(err) observer.on_error(err) def on_completed() -> None: window_subject.on_completed() observer.on_completed() d.add( source.subscribe( on_next_window, on_error, on_completed, scheduler=scheduler ) ) def on_next_observer(w: Observable[_T]): 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=scheduler ) ) return r return Observable(subscribe) return window def window_when_( closing_mapper: Callable[[], Observable[Any]] ) -> Callable[[Observable[_T]], Observable[Observable[_T]]]: """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[_T]) -> Observable[Observable[_T]]: def subscribe( observer: abc.ObserverBase[Observable[_T]], scheduler: Optional[abc.SchedulerBase] = None, ): m = SerialDisposable() d = CompositeDisposable(m) r = RefCountDisposable(d) window: Subject[_T] = Subject() observer.on_next(add_ref(window, r)) def on_next(value: _T) -> None: window.on_next(value) def on_error(error: Exception) -> None: window.on_error(error) observer.on_error(error) def on_completed() -> None: window.on_completed() observer.on_completed() d.add( source.subscribe(on_next, on_error, on_completed, scheduler=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=scheduler ) create_window_on_completed() return r return Observable(subscribe) return window_when __all__ = ["window_", "window_when_", "window_toggle_"] RxPY-4.0.4/reactivex/operators/_windowwithcount.py000066400000000000000000000052121426446175400223730ustar00rootroot00000000000000import logging from typing import Callable, List, Optional, TypeVar from reactivex import Observable, abc from reactivex.disposable import RefCountDisposable, SingleAssignmentDisposable from reactivex.internal import ArgumentOutOfRangeException, add_ref from reactivex.subject import Subject log = logging.getLogger("Rx") _T = TypeVar("_T") def window_with_count_( count: int, skip: Optional[int] = None ) -> Callable[[Observable[_T]], Observable[Observable[_T]]]: """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() skip_ = skip if skip is not None else count if skip_ <= 0: raise ArgumentOutOfRangeException() def window_with_count(source: Observable[_T]) -> Observable[Observable[_T]]: def subscribe( observer: abc.ObserverBase[Observable[_T]], scheduler: Optional[abc.SchedulerBase] = None, ): m = SingleAssignmentDisposable() refCountDisposable = RefCountDisposable(m) n = [0] q: List[Subject[_T]] = [] def create_window(): s: Subject[_T] = Subject() q.append(s) observer.on_next(add_ref(s, refCountDisposable)) create_window() def on_next(x: _T) -> None: 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: Exception) -> None: while q: q.pop(0).on_error(exception) observer.on_error(exception) def on_completed() -> None: while q: q.pop(0).on_completed() observer.on_completed() m.disposable = source.subscribe( on_next, on_error, on_completed, scheduler=scheduler ) return refCountDisposable return Observable(subscribe) return window_with_count __all__ = ["window_with_count_"] RxPY-4.0.4/reactivex/operators/_windowwithtime.py000066400000000000000000000074231426446175400222070ustar00rootroot00000000000000from datetime import timedelta from typing import Any, Callable, List, Optional, TypeVar from reactivex import Observable, abc, typing from reactivex.disposable import ( CompositeDisposable, RefCountDisposable, SerialDisposable, SingleAssignmentDisposable, ) from reactivex.internal import DELTA_ZERO, add_ref, synchronized from reactivex.scheduler import TimeoutScheduler from reactivex.subject import Subject _T = TypeVar("_T") def window_with_time_( timespan: typing.RelativeTime, timeshift: Optional[typing.RelativeTime] = None, scheduler: Optional[abc.SchedulerBase] = None, ) -> Callable[[Observable[_T]], Observable[Observable[_T]]]: 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[_T]) -> Observable[Observable[_T]]: def subscribe( observer: abc.ObserverBase[Observable[_T]], scheduler_: Optional[abc.SchedulerBase] = None, ): _scheduler = scheduler or scheduler_ or TimeoutScheduler.singleton() timer_d = SerialDisposable() next_shift = [timeshift] next_span = [timespan] total_time = [DELTA_ZERO] queue: List[Subject[_T]] = [] 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 @synchronized(source.lock) def action(scheduler: abc.SchedulerBase, state: Any = None): s: Optional[Subject[_T]] = None if is_shift: s = Subject() queue.append(s) observer.on_next(add_ref(s, ref_count_disposable)) if is_span: s = queue.pop(0) s.on_completed() create_timer() m.disposable = _scheduler.schedule_relative(ts, action) queue.append(Subject()) observer.on_next(add_ref(queue[0], ref_count_disposable)) create_timer() def on_next(x: _T) -> None: with source.lock: for s in queue: s.on_next(x) @synchronized(source.lock) def on_error(e: Exception) -> None: for s in queue: s.on_error(e) observer.on_error(e) @synchronized(source.lock) def on_completed() -> None: for s in queue: s.on_completed() observer.on_completed() group_disposable.add( source.subscribe(on_next, on_error, on_completed, scheduler=scheduler_) ) return ref_count_disposable return Observable(subscribe) return window_with_time __all__ = ["window_with_time_"] RxPY-4.0.4/reactivex/operators/_windowwithtimeorcount.py000066400000000000000000000060511426446175400236150ustar00rootroot00000000000000from typing import Any, Callable, Optional, TypeVar from reactivex import Observable, abc, typing from reactivex.disposable import ( CompositeDisposable, RefCountDisposable, SerialDisposable, SingleAssignmentDisposable, ) from reactivex.internal import add_ref from reactivex.scheduler import TimeoutScheduler from reactivex.subject import Subject _T = TypeVar("_T") def window_with_time_or_count_( timespan: typing.RelativeTime, count: int, scheduler: Optional[abc.SchedulerBase] = None, ) -> Callable[[Observable[_T]], Observable[Observable[_T]]]: def window_with_time_or_count(source: Observable[_T]) -> Observable[Observable[_T]]: def subscribe( observer: abc.ObserverBase[Observable[_T]], scheduler_: Optional[abc.SchedulerBase] = None, ) -> abc.DisposableBase: _scheduler = scheduler or scheduler_ or TimeoutScheduler.singleton() n: int = 0 s: Subject[_T] = Subject() timer_d = SerialDisposable() window_id = 0 group_disposable = CompositeDisposable(timer_d) ref_count_disposable = RefCountDisposable(group_disposable) def create_timer(_id: int): nonlocal n, s, window_id m = SingleAssignmentDisposable() timer_d.disposable = m def action(scheduler: abc.SchedulerBase, state: Any = None): nonlocal n, s, window_id if _id != window_id: return n = 0 window_id += 1 new_id = window_id s.on_completed() s = Subject() observer.on_next(add_ref(s, ref_count_disposable)) create_timer(new_id) m.disposable = _scheduler.schedule_relative(timespan, action) observer.on_next(add_ref(s, ref_count_disposable)) create_timer(0) def on_next(x: _T) -> None: nonlocal n, s, window_id new_window = False new_id = 0 s.on_next(x) n += 1 if n == count: new_window = True n = 0 window_id += 1 new_id = window_id s.on_completed() s = Subject() observer.on_next(add_ref(s, ref_count_disposable)) if new_window: create_timer(new_id) def on_error(e: Exception) -> None: s.on_error(e) observer.on_error(e) def on_completed() -> None: s.on_completed() observer.on_completed() group_disposable.add( source.subscribe(on_next, on_error, on_completed, scheduler=scheduler_) ) return ref_count_disposable return Observable(subscribe) return window_with_time_or_count __all__ = ["window_with_time_or_count_"] RxPY-4.0.4/reactivex/operators/_withlatestfrom.py000066400000000000000000000016021426446175400221720ustar00rootroot00000000000000from typing import Any, Callable import reactivex from reactivex import Observable def with_latest_from_( *sources: Observable[Any], ) -> Callable[[Observable[Any]], Observable[Any]]: """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[Any]) -> Observable[Any]: return reactivex.with_latest_from(source, *sources) return with_latest_from __all__ = ["with_latest_from_"] RxPY-4.0.4/reactivex/operators/_zip.py000066400000000000000000000045171426446175400177300ustar00rootroot00000000000000from typing import Any, Callable, Iterable, Optional, Tuple, TypeVar import reactivex from reactivex import Observable, abc _T = TypeVar("_T") _TOther = TypeVar("_TOther") def zip_( *args: Observable[Any], ) -> Callable[[Observable[Any]], Observable[Tuple[Any, ...]]]: def _zip(source: Observable[Any]) -> Observable[Tuple[Any, ...]]: """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 reactivex.zip(source, *args) return _zip def zip_with_iterable_( seq: Iterable[_TOther], ) -> Callable[[Observable[_T]], Observable[Tuple[_T, _TOther]]]: def zip_with_iterable(source: Observable[_T]) -> Observable[Tuple[_T, _TOther]]: """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: abc.ObserverBase[Tuple[_T, _TOther]], scheduler: Optional[abc.SchedulerBase] = None, ): index = 0 def on_next(left: _T) -> None: 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=scheduler ) return Observable(subscribe) return zip_with_iterable __all__ = ["zip_", "zip_with_iterable_"] RxPY-4.0.4/reactivex/operators/connectable/000077500000000000000000000000001426446175400206635ustar00rootroot00000000000000RxPY-4.0.4/reactivex/operators/connectable/__init__.py000066400000000000000000000000001426446175400227620ustar00rootroot00000000000000RxPY-4.0.4/reactivex/operators/connectable/_refcount.py000066400000000000000000000026131426446175400232230ustar00rootroot00000000000000from typing import Callable, Optional, TypeVar from reactivex import ConnectableObservable, Observable, abc from reactivex.disposable import Disposable _T = TypeVar("_T") def ref_count_() -> Callable[[ConnectableObservable[_T]], Observable[_T]]: """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: Optional[abc.DisposableBase] = None count = 0 def ref_count(source: ConnectableObservable[_T]) -> Observable[_T]: def subscribe( observer: abc.ObserverBase[_T], scheduler: Optional[abc.SchedulerBase] = None, ) -> abc.DisposableBase: nonlocal connectable_subscription, count count += 1 should_connect = count == 1 subscription = source.subscribe(observer, scheduler=scheduler) if should_connect: connectable_subscription = source.connect(scheduler) def dispose() -> None: nonlocal connectable_subscription, count subscription.dispose() count -= 1 if not count and connectable_subscription: connectable_subscription.dispose() return Disposable(dispose) return Observable(subscribe) return ref_count __all__ = ["ref_count_"] RxPY-4.0.4/reactivex/pipe.py000066400000000000000000000102511426446175400156760ustar00rootroot00000000000000from functools import reduce from typing import Any, Callable, TypeVar, overload _A = TypeVar("_A") _B = TypeVar("_B") _C = TypeVar("_C") _D = TypeVar("_D") _E = TypeVar("_E") _F = TypeVar("_F") _G = TypeVar("_G") _H = TypeVar("_H") _T = TypeVar("_T") _J = TypeVar("_J") @overload def compose(__op1: Callable[[_A], _B]) -> Callable[[_A], _B]: ... @overload def compose(__op1: Callable[[_A], _B], __op2: Callable[[_B], _C]) -> Callable[[_A], _C]: ... @overload def compose( __op1: Callable[[_A], _B], __op2: Callable[[_B], _C], __op3: Callable[[_C], _D], ) -> Callable[[_A], _D]: ... @overload def compose( __op1: Callable[[_A], _B], __op2: Callable[[_B], _C], __op3: Callable[[_C], _D], __op4: Callable[[_D], _E], ) -> Callable[[_A], _E]: ... @overload def compose( __op1: Callable[[_A], _B], __op2: Callable[[_B], _C], __op3: Callable[[_C], _D], __op4: Callable[[_D], _E], __op5: Callable[[_E], _F], ) -> Callable[[_A], _F]: ... @overload def compose( __op1: Callable[[_A], _B], __op2: Callable[[_B], _C], __op3: Callable[[_C], _D], __op4: Callable[[_D], _E], __op5: Callable[[_E], _F], __op6: Callable[[_F], _G], ) -> Callable[[_A], _G]: ... def compose(*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 @overload def pipe(__value: _A) -> _A: ... @overload def pipe(__value: _A, __fn1: Callable[[_A], _B]) -> _B: ... @overload def pipe( __value: _A, __fn1: Callable[[_A], _B], __fn2: Callable[[_B], _C], ) -> _C: ... @overload def pipe( __value: _A, __fn1: Callable[[_A], _B], __fn2: Callable[[_B], _C], __fn3: Callable[[_C], _D], ) -> _D: ... @overload def pipe( __value: _A, __fn1: Callable[[_A], _B], __fn2: Callable[[_B], _C], __fn3: Callable[[_C], _D], __fn4: Callable[[_D], _E], ) -> _E: ... @overload def pipe( __value: _A, __fn1: Callable[[_A], _B], __fn2: Callable[[_B], _C], __fn3: Callable[[_C], _D], __fn4: Callable[[_D], _E], __fn5: Callable[[_E], _F], ) -> _F: ... @overload def pipe( __value: _A, __fn1: Callable[[_A], _B], __fn2: Callable[[_B], _C], __fn3: Callable[[_C], _D], __fn4: Callable[[_D], _E], __fn5: Callable[[_E], _F], __fn6: Callable[[_F], _G], ) -> _G: ... @overload def pipe( __value: _A, __fn1: Callable[[_A], _B], __fn2: Callable[[_B], _C], __fn3: Callable[[_C], _D], __fn4: Callable[[_D], _E], __fn5: Callable[[_E], _F], __fn6: Callable[[_F], _G], __fn7: Callable[[_G], _H], ) -> _H: ... @overload def pipe( __value: _A, __fn1: Callable[[_A], _B], __fn2: Callable[[_B], _C], __fn3: Callable[[_C], _D], __fn4: Callable[[_D], _E], __fn5: Callable[[_E], _F], __fn6: Callable[[_F], _G], __fn7: Callable[[_G], _H], __fn8: Callable[[_H], _T], ) -> _T: ... @overload def pipe( __value: _A, __fn1: Callable[[_A], _B], __fn2: Callable[[_B], _C], __fn3: Callable[[_C], _D], __fn4: Callable[[_D], _E], __fn5: Callable[[_E], _F], __fn6: Callable[[_F], _G], __fn7: Callable[[_G], _H], __fn8: Callable[[_H], _T], __fn9: Callable[[_T], _J], ) -> _J: ... def pipe(__value: Any, *fns: Callable[[Any], Any]) -> Any: """Functional pipe (`|>`) Allows the use of function argument on the left side of the function. Example: >>> pipe(x, fn) == __fn(x) # Same as x |> fn >>> pipe(x, fn, gn) == gn(fn(x)) # Same as x |> fn |> gn ... """ return compose(*fns)(__value) __all__ = ["pipe", "compose"] RxPY-4.0.4/reactivex/py.typed000066400000000000000000000000001426446175400160550ustar00rootroot00000000000000RxPY-4.0.4/reactivex/run.py000066400000000000000000000032551426446175400155530ustar00rootroot00000000000000import threading from typing import Optional, TypeVar, cast from reactivex.internal.exceptions import SequenceContainsNoElementsError from reactivex.scheduler import NewThreadScheduler from .observable import Observable scheduler = NewThreadScheduler() _T = TypeVar("_T") def run(source: Observable[_T]) -> _T: """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: _T = cast(_T, None) done = False def on_next(value: _T) -> 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: raise cast(Exception, exception) if not has_result: raise SequenceContainsNoElementsError return result RxPY-4.0.4/reactivex/scheduler/000077500000000000000000000000001426446175400163465ustar00rootroot00000000000000RxPY-4.0.4/reactivex/scheduler/__init__.py000066400000000000000000000015301426446175400204560ustar00rootroot00000000000000from .catchscheduler import CatchScheduler from .currentthreadscheduler import CurrentThreadScheduler from .eventloopscheduler import EventLoopScheduler from .historicalscheduler import HistoricalScheduler from .immediatescheduler import ImmediateScheduler from .newthreadscheduler import NewThreadScheduler from .scheduleditem import ScheduledItem from .threadpoolscheduler import ThreadPoolScheduler from .timeoutscheduler import TimeoutScheduler from .trampolinescheduler import TrampolineScheduler from .virtualtimescheduler import VirtualTimeScheduler __all__ = [ "CatchScheduler", "CurrentThreadScheduler", "EventLoopScheduler", "HistoricalScheduler", "ImmediateScheduler", "NewThreadScheduler", "ScheduledItem", "ThreadPoolScheduler", "TimeoutScheduler", "TrampolineScheduler", "VirtualTimeScheduler", ] RxPY-4.0.4/reactivex/scheduler/catchscheduler.py000066400000000000000000000137621426446175400217120ustar00rootroot00000000000000from datetime import datetime from typing import Callable, Optional, TypeVar, cast from reactivex import abc, typing from reactivex.abc.scheduler import SchedulerBase from reactivex.disposable import Disposable, SingleAssignmentDisposable from .periodicscheduler import PeriodicScheduler _TState = TypeVar("_TState") class CatchScheduler(PeriodicScheduler): def __init__( self, scheduler: abc.SchedulerBase, 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: abc.SchedulerBase = scheduler self._handler: Callable[[Exception], bool] = handler self._recursive_original: Optional[abc.SchedulerBase] = 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[_TState], state: Optional[_TState] = None ) -> abc.DisposableBase: """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[_TState], state: Optional[_TState] = None, ) -> abc.DisposableBase: """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[_TState], state: Optional[_TState] = None, ) -> abc.DisposableBase: """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[_TState], state: Optional[_TState] = None, ) -> abc.DisposableBase: """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[_TState] = None) -> Optional[_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: abc.SchedulerBase) -> "CatchScheduler": return CatchScheduler(scheduler, self._handler) def _wrap( self, action: typing.ScheduledAction[_TState] ) -> typing.ScheduledAction[_TState]: parent = self def wrapped_action( self: abc.SchedulerBase, state: Optional[_TState] ) -> Optional[abc.DisposableBase]: 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: SchedulerBase) -> "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-4.0.4/reactivex/scheduler/currentthreadscheduler.py000066400000000000000000000051351426446175400234750ustar00rootroot00000000000000import logging from threading import Thread, current_thread, local 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_: MutableMapping[ Thread, "CurrentThreadScheduler" ] = WeakKeyDictionary() CurrentThreadScheduler._global[cls] = class_map_ else: class_map_ = 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) -> None: 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) -> None: pass def get_trampoline(self) -> Trampoline: return CurrentThreadSchedulerSingleton._local.tramp RxPY-4.0.4/reactivex/scheduler/eventloop/000077500000000000000000000000001426446175400203615ustar00rootroot00000000000000RxPY-4.0.4/reactivex/scheduler/eventloop/__init__.py000066400000000000000000000007241426446175400224750ustar00rootroot00000000000000from .asyncioscheduler import AsyncIOScheduler from .asynciothreadsafescheduler import AsyncIOThreadSafeScheduler from .eventletscheduler import EventletScheduler from .geventscheduler import GEventScheduler from .ioloopscheduler import IOLoopScheduler from .twistedscheduler import TwistedScheduler __all__ = [ "AsyncIOScheduler", "AsyncIOThreadSafeScheduler", "EventletScheduler", "GEventScheduler", "IOLoopScheduler", "TwistedScheduler", ] RxPY-4.0.4/reactivex/scheduler/eventloop/asyncioscheduler.py000066400000000000000000000073161426446175400243060ustar00rootroot00000000000000import asyncio import logging from datetime import datetime from typing import Optional, TypeVar from reactivex import abc, typing from reactivex.disposable import ( CompositeDisposable, Disposable, SingleAssignmentDisposable, ) from ..periodicscheduler import PeriodicScheduler _TState = TypeVar("_TState") 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[_TState], state: Optional[_TState] = None ) -> abc.DisposableBase: """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[_TState], state: Optional[_TState] = None, ) -> abc.DisposableBase: """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[_TState], state: Optional[_TState] = None, ) -> abc.DisposableBase: """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-4.0.4/reactivex/scheduler/eventloop/asynciothreadsafescheduler.py000066400000000000000000000116421426446175400263320ustar00rootroot00000000000000import asyncio import logging from concurrent.futures import Future from typing import List, Optional, TypeVar from reactivex import abc, typing from reactivex.disposable import ( CompositeDisposable, Disposable, SingleAssignmentDisposable, ) from .asyncioscheduler import AsyncIOScheduler _TState = TypeVar("_TState") 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 schedule( self, action: typing.ScheduledAction[_TState], state: Optional[_TState] = None ) -> abc.DisposableBase: """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[int]" = 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[_TState], state: Optional[_TState] = None, ) -> abc.DisposableBase: """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() -> None: try: handle.pop().cancel() handle.pop().cancel() except Exception: pass if self._on_self_loop_or_not_running(): do_cancel_handles() return future: "Future[int]" = 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[_TState], state: Optional[_TState] = None, ) -> abc.DisposableBase: """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) -> bool: """ 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-4.0.4/reactivex/scheduler/eventloop/eventletscheduler.py000066400000000000000000000071141426446175400244630ustar00rootroot00000000000000import logging from datetime import datetime from typing import Any, Optional, TypeVar from reactivex import abc, typing from reactivex.disposable import ( CompositeDisposable, Disposable, SingleAssignmentDisposable, ) from ..periodicscheduler import PeriodicScheduler _TState = TypeVar("_TState") 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[_TState], state: Optional[_TState] = None ) -> abc.DisposableBase: """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[_TState], state: Optional[_TState] = None, ) -> abc.DisposableBase: """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[_TState], state: Optional[_TState] = None, ) -> abc.DisposableBase: """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-4.0.4/reactivex/scheduler/eventloop/geventscheduler.py000066400000000000000000000071641426446175400241320ustar00rootroot00000000000000import logging from datetime import datetime from typing import Any, Optional, TypeVar from reactivex import abc, typing from reactivex.disposable import ( CompositeDisposable, Disposable, SingleAssignmentDisposable, ) from ..periodicscheduler import PeriodicScheduler _TState = TypeVar("_TState") 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[_TState], state: Optional[_TState] = None ) -> abc.DisposableBase: """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[_TState], state: Optional[_TState] = None, ) -> abc.DisposableBase: """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[_TState], state: Optional[_TState] = None, ) -> abc.DisposableBase: """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-4.0.4/reactivex/scheduler/eventloop/ioloopscheduler.py000066400000000000000000000075451426446175400241460ustar00rootroot00000000000000import logging from datetime import datetime from typing import Any, Optional, TypeVar from reactivex import abc, typing from reactivex.disposable import ( CompositeDisposable, Disposable, SingleAssignmentDisposable, ) from ..periodicscheduler import PeriodicScheduler _TState = TypeVar("_TState") 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[_TState], state: Optional[_TState] = None ) -> abc.DisposableBase: """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[_TState], state: Optional[_TState] = None, ) -> abc.DisposableBase: """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[_TState], state: Optional[_TState] = None, ) -> abc.DisposableBase: """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-4.0.4/reactivex/scheduler/eventloop/twistedscheduler.py000066400000000000000000000064551426446175400243270ustar00rootroot00000000000000import logging from datetime import datetime from typing import Any, Optional, TypeVar from reactivex import abc, typing from reactivex.disposable import ( CompositeDisposable, Disposable, SingleAssignmentDisposable, ) from ..periodicscheduler import PeriodicScheduler _TState = TypeVar("_TState") 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[_TState], state: Optional[_TState] = None ) -> abc.DisposableBase: """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[_TState], state: Optional[_TState] = None, ) -> abc.DisposableBase: """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[_TState], state: Optional[_TState] = None, ) -> abc.DisposableBase: """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-4.0.4/reactivex/scheduler/eventloopscheduler.py000066400000000000000000000163751426446175400226460ustar00rootroot00000000000000import logging import threading from collections import deque from typing import Deque, Optional, TypeVar from reactivex import abc, typing from reactivex.disposable import Disposable from reactivex.internal.concurrency import default_thread_factory from reactivex.internal.constants import DELTA_ZERO from reactivex.internal.exceptions import DisposedException from reactivex.internal.priorityqueue import PriorityQueue from .periodicscheduler import PeriodicScheduler from .scheduleditem import ScheduledItem log = logging.getLogger("Rx") _TState = TypeVar("_TState") class EventLoopScheduler(PeriodicScheduler, abc.DisposableBase): """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[_TState], state: Optional[_TState] = None ) -> abc.DisposableBase: """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[_TState], state: Optional[_TState] = None, ) -> abc.DisposableBase: """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[_TState], state: Optional[_TState] = None, ) -> abc.DisposableBase: """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[_TState], state: Optional[_TState] = None, ) -> abc.DisposableBase: """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-4.0.4/reactivex/scheduler/historicalscheduler.py000066400000000000000000000011541426446175400227610ustar00rootroot00000000000000from datetime import datetime from typing import Optional 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: Optional[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) RxPY-4.0.4/reactivex/scheduler/immediatescheduler.py000066400000000000000000000060611426446175400225600ustar00rootroot00000000000000from threading import Lock from typing import MutableMapping, Optional, TypeVar from weakref import WeakKeyDictionary from reactivex import abc, typing from reactivex.internal.constants import DELTA_ZERO from reactivex.internal.exceptions import WouldBlockException from .scheduler import Scheduler _TState = TypeVar("_TState") 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[_TState], state: Optional[_TState] = None ) -> abc.DisposableBase: """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[_TState], state: Optional[_TState] = None, ) -> abc.DisposableBase: """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[_TState], state: Optional[_TState] = None, ) -> abc.DisposableBase: """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-4.0.4/reactivex/scheduler/mainloop/000077500000000000000000000000001426446175400201645ustar00rootroot00000000000000RxPY-4.0.4/reactivex/scheduler/mainloop/__init__.py000066400000000000000000000005051426446175400222750ustar00rootroot00000000000000from .gtkscheduler import GtkScheduler from .pygamescheduler import PyGameScheduler from .qtscheduler import QtScheduler from .tkinterscheduler import TkinterScheduler from .wxscheduler import WxScheduler __all__ = [ "GtkScheduler", "PyGameScheduler", "QtScheduler", "TkinterScheduler", "WxScheduler", ] RxPY-4.0.4/reactivex/scheduler/mainloop/gtkscheduler.py000066400000000000000000000104571426446175400232310ustar00rootroot00000000000000from typing import Any, Optional, TypeVar, cast from reactivex import abc, typing from reactivex.disposable import ( CompositeDisposable, Disposable, SingleAssignmentDisposable, ) from ..periodicscheduler import PeriodicScheduler _TState = TypeVar("_TState") 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[_TState], state: Optional[_TState] = None, periodic: bool = False, ) -> abc.DisposableBase: msecs = max(0, int(self.to_seconds(time) * 1000.0)) sad = SingleAssignmentDisposable() stopped = False def timer_handler(_: Any) -> bool: if stopped: return False nonlocal state if periodic: state = cast(typing.ScheduledPeriodicAction[_TState], action)(state) else: sad.disposable = self.invoke_action( cast(typing.ScheduledAction[_TState], 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[_TState], state: Optional[_TState] = None ) -> abc.DisposableBase: """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[_TState], state: Optional[_TState] = None, ) -> abc.DisposableBase: """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[_TState], state: Optional[_TState] = None, ) -> abc.DisposableBase: """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[_TState], state: Optional[_TState] = None, ) -> abc.DisposableBase: """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-4.0.4/reactivex/scheduler/mainloop/pygamescheduler.py000066400000000000000000000066721426446175400237320ustar00rootroot00000000000000import logging import threading from typing import Any, Optional, TypeVar from reactivex import abc, typing from reactivex.internal import PriorityQueue from reactivex.internal.constants import DELTA_ZERO from ..periodicscheduler import PeriodicScheduler from ..scheduleditem import ScheduledItem _TState = TypeVar("_TState") 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[_TState], state: Optional[_TState] = None ) -> abc.DisposableBase: """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[_TState], state: Optional[_TState] = None, ) -> abc.DisposableBase: """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[_TState], state: Optional[_TState] = None, ) -> abc.DisposableBase: """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-4.0.4/reactivex/scheduler/mainloop/qtscheduler.py000066400000000000000000000105741426446175400230700ustar00rootroot00000000000000import logging from datetime import timedelta from typing import Any, Optional, Set, TypeVar from reactivex import abc, typing from reactivex.disposable import ( CompositeDisposable, Disposable, SingleAssignmentDisposable, ) from ..periodicscheduler import PeriodicScheduler _TState = TypeVar("_TState") 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 self._periodic_timers: Set[Any] = set() def schedule( self, action: typing.ScheduledAction[_TState], state: Optional[_TState] = None ) -> abc.DisposableBase: """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[_TState], state: Optional[_TState] = None, ) -> abc.DisposableBase: """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[_TState], state: Optional[_TState] = None, ) -> abc.DisposableBase: """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[_TState], state: Optional[_TState] = None, ) -> abc.DisposableBase: """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-4.0.4/reactivex/scheduler/mainloop/tkinterscheduler.py000066400000000000000000000056531426446175400241260ustar00rootroot00000000000000from typing import Any, Optional, TypeVar from reactivex import abc, typing from reactivex.disposable import ( CompositeDisposable, Disposable, SingleAssignmentDisposable, ) from ..periodicscheduler import PeriodicScheduler _TState = TypeVar("_TState") 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[_TState], state: Optional[_TState] = None ) -> abc.DisposableBase: """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[_TState], state: Optional[_TState] = None, ) -> abc.DisposableBase: """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[_TState], state: Optional[_TState] = None, ) -> abc.DisposableBase: """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-4.0.4/reactivex/scheduler/mainloop/wxscheduler.py000066400000000000000000000126221426446175400230760ustar00rootroot00000000000000import logging from typing import Any, Optional, Set, TypeVar, cast from reactivex import abc, typing from reactivex.disposable import ( CompositeDisposable, Disposable, SingleAssignmentDisposable, ) from ..periodicscheduler import PeriodicScheduler _TState = TypeVar("_TState") 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: typing.Action) -> None: super().__init__() # type: ignore self.callback = callback def Notify(self) -> None: 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() # type: ignore def _wxtimer_schedule( self, time: typing.AbsoluteOrRelativeTime, action: typing.ScheduledSingleOrPeriodicAction[_TState], state: Optional[_TState] = None, periodic: bool = False, ) -> abc.DisposableBase: scheduler = self sad = SingleAssignmentDisposable() def interval() -> None: nonlocal state if periodic: state = cast(typing.ScheduledPeriodicAction[_TState], action)(state) else: sad.disposable = cast(typing.ScheduledAction[_TState], 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) # A timer can only be used from the main thread if self._wx.IsMainThread(): timer.Start(msecs, oneShot=not periodic) # type: ignore else: self._wx.CallAfter(timer.Start, msecs, oneShot=not periodic) # type: ignore self._timers.add(timer) def dispose() -> None: timer.Stop() # type: ignore self._timers.remove(timer) return CompositeDisposable(sad, Disposable(dispose)) def schedule( self, action: typing.ScheduledAction[_TState], state: Optional[_TState] = None ) -> abc.DisposableBase: """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() is_disposed = False def invoke_action() -> None: if not is_disposed: sad.disposable = action(self, state) self._wx.CallAfter(invoke_action) def dispose() -> None: nonlocal is_disposed is_disposed = True return CompositeDisposable(sad, Disposable(dispose)) def schedule_relative( self, duetime: typing.RelativeTime, action: typing.ScheduledAction[_TState], state: Optional[_TState] = None, ) -> abc.DisposableBase: """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[_TState], state: Optional[_TState] = None, ) -> abc.DisposableBase: """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[_TState], state: Optional[_TState] = None, ) -> abc.DisposableBase: """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-4.0.4/reactivex/scheduler/newthreadscheduler.py000066400000000000000000000101471426446175400226030ustar00rootroot00000000000000import logging import threading from datetime import datetime from typing import Optional, TypeVar from reactivex import abc, typing from reactivex.disposable import Disposable from reactivex.internal.concurrency import default_thread_factory from .eventloopscheduler import EventLoopScheduler from .periodicscheduler import PeriodicScheduler _TState = TypeVar("_TState") 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[_TState], state: Optional[_TState] = None ) -> abc.DisposableBase: """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[_TState], state: Optional[_TState] = None, ) -> abc.DisposableBase: """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[_TState], state: Optional[_TState] = None, ) -> abc.DisposableBase: """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[_TState], state: Optional[_TState] = None, ) -> abc.DisposableBase: """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-4.0.4/reactivex/scheduler/periodicscheduler.py000066400000000000000000000035261426446175400224230ustar00rootroot00000000000000from datetime import datetime from typing import Optional, TypeVar from reactivex import abc, typing from reactivex.disposable import Disposable, MultipleAssignmentDisposable from .scheduler import Scheduler _TState = TypeVar("_TState") class PeriodicScheduler(Scheduler, abc.PeriodicSchedulerBase): """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[_TState], state: Optional[_TState] = None, ) -> abc.DisposableBase: """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: abc.SchedulerBase, state: Optional[_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 RxPY-4.0.4/reactivex/scheduler/scheduleditem.py000066400000000000000000000027141426446175400215430ustar00rootroot00000000000000from datetime import datetime from typing import Any, Optional, TypeVar from reactivex import abc from reactivex.disposable import SingleAssignmentDisposable from .scheduler import Scheduler _TState = TypeVar("_TState") class ScheduledItem(object): def __init__( self, scheduler: Scheduler, state: Optional[_TState], action: abc.ScheduledAction[_TState], duetime: datetime, ) -> None: self.scheduler: Scheduler = scheduler self.state: Optional[Any] = state self.action: abc.ScheduledAction[_TState] = 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-4.0.4/reactivex/scheduler/scheduler.py000066400000000000000000000123761426446175400207070ustar00rootroot00000000000000from abc import abstractmethod from datetime import datetime, timedelta from typing import Optional, TypeVar from reactivex import abc, typing from reactivex.disposable import Disposable from reactivex.internal.basic import default_now from reactivex.internal.constants import UTC_ZERO _TState = TypeVar("_TState") class Scheduler(abc.SchedulerBase): """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: abc.ScheduledAction[_TState], state: Optional[_TState] = None ) -> abc.DisposableBase: """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: abc.ScheduledAction[_TState], state: Optional[_TState] = None, ) -> abc.DisposableBase: """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: abc.ScheduledAction[_TState], state: Optional[_TState] = None, ) -> abc.DisposableBase: """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: abc.ScheduledAction[_TState], state: Optional[_TState] = None ) -> abc.DisposableBase: """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, abc.DisposableBase): 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-4.0.4/reactivex/scheduler/threadpoolscheduler.py000066400000000000000000000023461426446175400227650ustar00rootroot00000000000000from concurrent.futures import Future, ThreadPoolExecutor from typing import Any, Optional from reactivex import abc, typing from .newthreadscheduler import NewThreadScheduler class ThreadPoolScheduler(NewThreadScheduler): """A scheduler that schedules work via the thread pool.""" class ThreadPoolThread(abc.StartableBase): """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[Any]"] = 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, ) -> ThreadPoolScheduler.ThreadPoolThread: return self.ThreadPoolThread(self.executor, target) super().__init__(thread_factory) RxPY-4.0.4/reactivex/scheduler/timeoutscheduler.py000066400000000000000000000066741426446175400223220ustar00rootroot00000000000000from threading import Lock, Timer from typing import MutableMapping, Optional, TypeVar from weakref import WeakKeyDictionary from reactivex import abc, typing from reactivex.disposable import ( CompositeDisposable, Disposable, SingleAssignmentDisposable, ) from .periodicscheduler import PeriodicScheduler _TState = TypeVar("_TState") 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: abc.ScheduledAction[_TState], state: Optional[_TState] = None ) -> abc.DisposableBase: """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.daemon = True timer.start() def dispose() -> None: timer.cancel() return CompositeDisposable(sad, Disposable(dispose)) def schedule_relative( self, duetime: typing.RelativeTime, action: abc.ScheduledAction[_TState], state: Optional[_TState] = None, ) -> abc.DisposableBase: """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.daemon = True timer.start() def dispose() -> None: timer.cancel() return CompositeDisposable(sad, Disposable(dispose)) def schedule_absolute( self, duetime: typing.AbsoluteTime, action: abc.ScheduledAction[_TState], state: Optional[_TState] = None, ) -> abc.DisposableBase: """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) __all__ = ["TimeoutScheduler"] RxPY-4.0.4/reactivex/scheduler/trampoline.py000066400000000000000000000034671426446175400211040ustar00rootroot00000000000000from collections import deque from threading import Condition, Lock from typing import Deque from reactivex.internal.priorityqueue import PriorityQueue from .scheduleditem import ScheduledItem class Trampoline: def __init__(self) -> None: 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) __all__ = ["Trampoline"] RxPY-4.0.4/reactivex/scheduler/trampolinescheduler.py000066400000000000000000000073141426446175400227760ustar00rootroot00000000000000import logging from typing import Optional, TypeVar from reactivex import abc, typing from reactivex.abc.disposable import DisposableBase from reactivex.abc.scheduler import ScheduledAction from reactivex.internal.constants import DELTA_ZERO from .scheduleditem import ScheduledItem from .scheduler import Scheduler from .trampoline import Trampoline _TState = TypeVar("_TState") 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: self._tramp = Trampoline() def get_trampoline(self) -> Trampoline: return self._tramp def schedule( self, action: abc.ScheduledAction[_TState], state: Optional[_TState] = None ) -> abc.DisposableBase: """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: abc.ScheduledAction[_TState], state: Optional[_TState] = None, ) -> abc.DisposableBase: """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: abc.ScheduledAction[_TState], state: Optional[_TState] = None, ) -> abc.DisposableBase: """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: ScheduledAction[_TState] ) -> Optional[DisposableBase]: """Method for testing the TrampolineScheduler.""" if self.schedule_required(): return self.schedule(action) return action(self, None) RxPY-4.0.4/reactivex/scheduler/virtualtimescheduler.py000066400000000000000000000167631426446175400232010ustar00rootroot00000000000000import logging import threading from datetime import datetime, timedelta from typing import Any, Optional, TypeVar from reactivex import abc, typing from reactivex.abc.scheduler import AbsoluteTime from reactivex.internal import ArgumentOutOfRangeException, PriorityQueue from .periodicscheduler import PeriodicScheduler from .scheduleditem import ScheduledItem log = logging.getLogger("Rx") MAX_SPINNING = 100 _TState = TypeVar("_TState") class VirtualTimeScheduler(PeriodicScheduler): """Virtual Scheduler. This scheduler should work with either datetime/timespan or ticks as int/int""" def __init__(self, initial_clock: typing.AbsoluteTime = 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) -> AbsoluteTime: 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[_TState], state: Optional[_TState] = None ) -> abc.DisposableBase: """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: abc.ScheduledAction[_TState], state: Optional[_TState] = None, ) -> abc.DisposableBase: """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, duetime) return self.schedule_absolute(time, action, state=state) def schedule_absolute( self, duetime: typing.AbsoluteTime, action: abc.ScheduledAction[_TState], state: Optional[_TState] = None, ) -> abc.DisposableBase: """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) -> Any: """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: if isinstance(self._clock, datetime): self.clock += timedelta(microseconds=1000) else: 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 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-4.0.4/reactivex/subject/000077500000000000000000000000001426446175400160275ustar00rootroot00000000000000RxPY-4.0.4/reactivex/subject/__init__.py000066400000000000000000000003451426446175400201420ustar00rootroot00000000000000from .asyncsubject import AsyncSubject from .behaviorsubject import BehaviorSubject from .replaysubject import ReplaySubject from .subject import Subject __all__ = ["Subject", "AsyncSubject", "BehaviorSubject", "ReplaySubject"] RxPY-4.0.4/reactivex/subject/asyncsubject.py000066400000000000000000000050111426446175400210730ustar00rootroot00000000000000from typing import Optional, TypeVar, cast from .. import abc from ..disposable import Disposable from .innersubscription import InnerSubscription from .subject import Subject _T = TypeVar("_T") class AsyncSubject(Subject[_T]): """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: _T = cast(_T, None) self.has_value: bool = False def _subscribe_core( self, observer: abc.ObserverBase[_T], scheduler: Optional[abc.SchedulerBase] = None, ) -> abc.DisposableBase: 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: _T) -> 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 = cast(_T, None) super().dispose() RxPY-4.0.4/reactivex/subject/behaviorsubject.py000066400000000000000000000037151426446175400215660ustar00rootroot00000000000000from typing import Optional, TypeVar, cast from .. import abc from ..disposable import Disposable from .innersubscription import InnerSubscription from .subject import Subject _T = TypeVar("_T") class BehaviorSubject(Subject[_T]): """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: _T) -> 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: _T = value def _subscribe_core( self, observer: abc.ObserverBase[_T], scheduler: Optional[abc.SchedulerBase] = None, ) -> abc.DisposableBase: 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: _T) -> 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 = cast(_T, None) super().dispose() RxPY-4.0.4/reactivex/subject/innersubscription.py000066400000000000000000000012771426446175400221700ustar00rootroot00000000000000import threading from typing import TYPE_CHECKING, Optional, TypeVar from .. import abc if TYPE_CHECKING: from .subject import Subject _T = TypeVar("_T") class InnerSubscription(abc.DisposableBase): def __init__( self, subject: "Subject[_T]", observer: Optional[abc.ObserverBase[_T]] = None ): 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-4.0.4/reactivex/subject/replaysubject.py000066400000000000000000000110021426446175400212470ustar00rootroot00000000000000import sys from collections import deque from datetime import datetime, timedelta from typing import Any, Deque, NamedTuple, Optional, TypeVar, cast from reactivex.observer.scheduledobserver import ScheduledObserver from reactivex.scheduler import CurrentThreadScheduler from .. import abc, typing from ..observer import Observer from .subject import Subject _T = TypeVar("_T") class RemovableDisposable(abc.DisposableBase): def __init__(self, subject: Subject[_T], observer: Observer[_T]): self.subject = subject self.observer = observer def dispose(self) -> None: 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[_T]): """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: Optional[int] = None, window: Optional[typing.RelativeTime] = None, scheduler: Optional[abc.SchedulerBase] = 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: Deque[QueueItem] = deque() def _subscribe_core( self, observer: abc.ObserverBase[_T], scheduler: Optional[abc.SchedulerBase] = None, ) -> abc.DisposableBase: 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) -> None: while len(self.queue) > self.buffer_size: self.queue.popleft() while self.queue and (now - self.queue[0].interval) > self.window: self.queue.popleft() def _on_next_core(self, value: _T) -> 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[_T], 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[_T], 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[_T], 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-4.0.4/reactivex/subject/subject.py000066400000000000000000000061141426446175400200420ustar00rootroot00000000000000import threading from typing import List, Optional, TypeVar from .. import abc from ..disposable import Disposable from ..internal import DisposedException from ..observable import Observable from ..observer import Observer from .innersubscription import InnerSubscription _T = TypeVar("_T") class Subject(Observable[_T], Observer[_T], abc.SubjectBase[_T]): """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[abc.ObserverBase[_T]] = [] 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: abc.ObserverBase[_T], scheduler: Optional[abc.SchedulerBase] = None, ) -> abc.DisposableBase: 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: _T) -> 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: _T) -> 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-4.0.4/reactivex/testing/000077500000000000000000000000001426446175400160455ustar00rootroot00000000000000RxPY-4.0.4/reactivex/testing/__init__.py000066400000000000000000000005441426446175400201610ustar00rootroot00000000000000from .mockdisposable import MockDisposable from .reactivetest import OnErrorPredicate, OnNextPredicate, ReactiveTest, is_prime from .recorded import Recorded from .testscheduler import TestScheduler __all__ = [ "MockDisposable", "OnErrorPredicate", "OnNextPredicate", "ReactiveTest", "Recorded", "TestScheduler", "is_prime", ] RxPY-4.0.4/reactivex/testing/coldobservable.py000066400000000000000000000036271426446175400214150ustar00rootroot00000000000000from typing import Any, List, Optional, TypeVar from reactivex import Notification, Observable, abc from reactivex.disposable import CompositeDisposable, Disposable from reactivex.scheduler import VirtualTimeScheduler from .recorded import Recorded from .subscription import Subscription _T = TypeVar("_T") class ColdObservable(Observable[_T]): def __init__( self, scheduler: VirtualTimeScheduler, messages: List[Recorded[_T]] ) -> None: super().__init__() self.scheduler = scheduler self.messages = messages self.subscriptions: List[Subscription] = [] def _subscribe_core( self, observer: Optional[abc.ObserverBase[_T]] = None, scheduler: Optional[abc.SchedulerBase] = None, ) -> abc.DisposableBase: self.subscriptions.append(Subscription(self.scheduler.clock)) index = len(self.subscriptions) - 1 disp = CompositeDisposable() def get_action(notification: Notification[_T]) -> abc.ScheduledAction[_T]: def action( scheduler: abc.SchedulerBase, state: Any = None ) -> abc.DisposableBase: if observer: notification.accept(observer) return Disposable() return action for message in self.messages: notification = message.value if not isinstance(notification, Notification): raise ValueError("Must be notification") # 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, int(end)) disp.dispose() return Disposable(dispose) RxPY-4.0.4/reactivex/testing/hotobservable.py000066400000000000000000000040411426446175400212550ustar00rootroot00000000000000from typing import Any, List, Optional, TypeVar from reactivex import Observable, abc from reactivex.disposable import Disposable from reactivex.notification import Notification from reactivex.scheduler import VirtualTimeScheduler from .recorded import Recorded from .subscription import Subscription _T = TypeVar("_T") class HotObservable(Observable[_T]): def __init__( self, scheduler: VirtualTimeScheduler, messages: List[Recorded[_T]] ) -> None: super().__init__() self.scheduler = scheduler self.messages = messages self.subscriptions: List[Subscription] = [] self.observers: List[abc.ObserverBase[_T]] = [] observable = self def get_action(notification: Notification[_T]) -> abc.ScheduledAction[_T]: def action(scheduler: abc.SchedulerBase, state: Any) -> abc.DisposableBase: for observer in observable.observers[:]: notification.accept(observer) return Disposable() return action for message in self.messages: notification = message.value if not isinstance(notification, Notification): raise ValueError("Must be notification") # Warning: Don't make closures within a loop action = get_action(notification) scheduler.schedule_absolute(message.time, action) def _subscribe_core( self, observer: Optional[abc.ObserverBase[_T]] = None, scheduler: Optional[abc.SchedulerBase] = None, ) -> abc.DisposableBase: if observer: self.observers.append(observer) self.subscriptions.append(Subscription(self.scheduler.clock)) index = len(self.subscriptions) - 1 def dispose_action() -> None: if observer: self.observers.remove(observer) start = self.subscriptions[index].subscribe end = self.scheduler.clock self.subscriptions[index] = Subscription(start, end) return Disposable(dispose_action) RxPY-4.0.4/reactivex/testing/marbles.py000066400000000000000000000132021426446175400200420ustar00rootroot00000000000000from contextlib import contextmanager from typing import Any, Dict, Generator, List, NamedTuple, Optional, Tuple, Union from warnings import warn import reactivex from reactivex import Observable, typing from reactivex.notification import Notification, OnError, OnNext from reactivex.observable.marbles import parse from reactivex.scheduler import NewThreadScheduler from reactivex.typing import Callable, RelativeTime from .reactivetest import ReactiveTest from .recorded import Recorded from .testscheduler import TestScheduler new_thread_scheduler = NewThreadScheduler() class MarblesContext(NamedTuple): start: Callable[ [Union[Observable[Any], Callable[[], Observable[Any]]]], List[Recorded[Any]] ] cold: Callable[ [str, Optional[Dict[Union[str, float], Any]], Optional[Exception]], Observable[Any], ] hot: Callable[ [str, Optional[Dict[Union[str, float], Any]], Optional[Exception]], Observable[Any], ] exp: Callable[ [str, Optional[Dict[Union[str, float], Any]], Optional[Exception]], List[Recorded[Any]], ] @contextmanager def marbles_testing( timespan: RelativeTime = 1.0, ) -> Generator[MarblesContext, None, None]: """ 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() -> None: 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[Any], Callable[[], Observable[Any]]] ) -> List[Recorded[Any]]: nonlocal start_called check() if isinstance(create, Observable): create_ = create def default_create() -> Observable[Any]: return create_ 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: Optional[Dict[Union[str, float], Any]] = None, error: Optional[Exception] = None, ) -> List[Recorded[Any]]: messages = parse( string, timespan=timespan, time_shift=subscribed, lookup=lookup, error=error, ) return messages_to_records(messages) def test_cold( string: str, lookup: Optional[Dict[Union[str, float], Any]] = None, error: Optional[Exception] = None, ) -> Observable[Any]: check() return reactivex.from_marbles( string, timespan=timespan, lookup=lookup, error=error, ) def test_hot( string: str, lookup: Optional[Dict[Union[str, float], Any]] = None, error: Optional[Exception] = None, ) -> Observable[Any]: check() hot_obs: Observable[Any] = reactivex.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[typing.RelativeTime, Notification[Any]]] ) -> List[Recorded[Any]]: """ Helper function to convert messages returned by parse() to a list of Recorded. """ records: List[Recorded[Any]] = [] for message in messages: time, notification = message if isinstance(time, float): time_ = int(time) else: time_ = time.microseconds // 1000 if isinstance(notification, OnNext): record = ReactiveTest.on_next(time_, notification.value) elif isinstance(notification, OnError): record = ReactiveTest.on_error(time_, notification.exception) else: record = ReactiveTest.on_completed(time_) records.append(record) return records RxPY-4.0.4/reactivex/testing/mockdisposable.py000066400000000000000000000006641426446175400214240ustar00rootroot00000000000000from typing import List from reactivex import abc, typing from reactivex.scheduler import VirtualTimeScheduler class MockDisposable(abc.DisposableBase): def __init__(self, scheduler: VirtualTimeScheduler): self.scheduler = scheduler self.disposes: List[typing.AbsoluteTime] = [] self.disposes.append(self.scheduler.clock) def dispose(self) -> None: self.disposes.append(self.scheduler.clock) RxPY-4.0.4/reactivex/testing/mockobserver.py000066400000000000000000000014141426446175400211200ustar00rootroot00000000000000from typing import List, TypeVar from reactivex import abc from reactivex.notification import OnCompleted, OnError, OnNext from reactivex.scheduler import VirtualTimeScheduler from .recorded import Recorded _T = TypeVar("_T") class MockObserver(abc.ObserverBase[_T]): def __init__(self, scheduler: VirtualTimeScheduler) -> None: self.scheduler = scheduler self.messages: List[Recorded[_T]] = [] def on_next(self, value: _T) -> 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-4.0.4/reactivex/testing/reactivetest.py000066400000000000000000000040451426446175400211240ustar00rootroot00000000000000import math import types from typing import Any, Generic, TypeVar, Union from reactivex import typing from reactivex.notification import OnCompleted, OnError, OnNext from .recorded import Recorded from .subscription import Subscription _T = TypeVar("_T") 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(Generic[_T]): def __init__(self, predicate: typing.Predicate[_T]) -> None: self.predicate = predicate def __eq__(self, other: Any) -> bool: if other == self: return True if other is None: return False if other.kind != "N": return False return self.predicate(other.value) class OnErrorPredicate(Generic[_T]): def __init__(self, predicate: typing.Predicate[_T]): self.predicate = predicate def __eq__(self, other: Any) -> bool: 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: _T) -> Recorded[_T]: if isinstance(value, types.FunctionType): return Recorded(ticks, OnNextPredicate(value)) return Recorded(ticks, OnNext(value)) @staticmethod def on_error(ticks: int, error: Union[Exception, str]) -> Recorded[Any]: if isinstance(error, types.FunctionType): return Recorded(ticks, OnErrorPredicate(error)) return Recorded(ticks, OnError(error)) @staticmethod def on_completed(ticks: int) -> Recorded[Any]: return Recorded(ticks, OnCompleted()) @staticmethod def subscribe(start: int, end: int) -> Subscription: return Subscription(start, end) RxPY-4.0.4/reactivex/testing/recorded.py000066400000000000000000000021071426446175400202060ustar00rootroot00000000000000from typing import TYPE_CHECKING, Any, Generic, TypeVar, Union, cast from reactivex import Notification if TYPE_CHECKING: from .reactivetest import OnErrorPredicate, OnNextPredicate _T = TypeVar("_T") class Recorded(Generic[_T]): def __init__( self, time: int, value: Union[Notification[_T], "OnNextPredicate[_T]", "OnErrorPredicate[_T]"], # comparer: Optional[typing.Comparer[_T]] = None, ): self.time = time self.value = value # self.comparer = comparer or default_comparer def __eq__(self, other: Any) -> bool: """Returns true if a recorded value matches another recorded value""" if isinstance(other, Recorded): other_ = cast(Recorded[_T], other) time_match = self.time == other_.time if not time_match: return False return self.value == other_.value return False equals = __eq__ def __repr__(self) -> str: return str(self) def __str__(self) -> str: return "%s@%s" % (self.value, self.time) RxPY-4.0.4/reactivex/testing/subscription.py000066400000000000000000000012751426446175400211500ustar00rootroot00000000000000import sys from typing import Any, Optional class Subscription: def __init__(self, start: int, end: Optional[int] = None): self.subscribe = start self.unsubscribe = end or sys.maxsize def equals(self, other: Any) -> bool: return ( self.subscribe == other.subscribe and self.unsubscribe == other.unsubscribe ) def __eq__(self, other: Any) -> bool: return self.equals(other) def __repr__(self) -> str: return str(self) def __str__(self) -> str: unsubscribe = ( "Infinite" if self.unsubscribe == sys.maxsize else self.unsubscribe ) return "(%s, %s)" % (self.subscribe, unsubscribe) RxPY-4.0.4/reactivex/testing/testscheduler.py000066400000000000000000000134361426446175400213040ustar00rootroot00000000000000from typing import Any, Callable, List, Optional, TypeVar, Union, cast import reactivex from reactivex import Observable, abc, typing from reactivex.disposable import Disposable from reactivex.scheduler import VirtualTimeScheduler from reactivex.testing.recorded import Recorded from .coldobservable import ColdObservable from .hotobservable import HotObservable from .mockobserver import MockObserver from .reactivetest import ReactiveTest _T = TypeVar("_T") _TState = TypeVar("_TState") 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: typing.ScheduledAction[_TState], state: _TState = None, ) -> abc.DisposableBase: """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) def start( self, create: Optional[Callable[[], Observable[_T]]] = None, created: Optional[float] = None, subscribed: Optional[float] = None, disposed: Optional[float] = None, ) -> MockObserver[_T]: """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 created = created or ReactiveTest.created subscribed = subscribed or ReactiveTest.subscribed disposed = disposed or ReactiveTest.disposed observer = self.create_observer() subscription: Optional[abc.DisposableBase] = None source: Optional[abc.ObservableBase[_T]] = None def action_create( scheduler: abc.SchedulerBase, state: Any = None ) -> abc.DisposableBase: """Called at create time. Defaults to 100""" nonlocal source source = create() if create is not None else reactivex.never() return Disposable() self.schedule_absolute(created, action_create) def action_subscribe( scheduler: abc.SchedulerBase, state: Any = None ) -> abc.DisposableBase: """Called at subscribe time. Defaults to 200""" nonlocal subscription if source: subscription = source.subscribe(observer, scheduler=scheduler) return Disposable() self.schedule_absolute(subscribed, action_subscribe) def action_dispose( scheduler: abc.SchedulerBase, state: Any = None ) -> abc.DisposableBase: """Called at dispose time. Defaults to 1000""" if subscription: subscription.dispose() return Disposable() self.schedule_absolute(disposed, action_dispose) super().start() return observer def create_hot_observable( self, *args: Union[Recorded[_T], List[Recorded[_T]]] ) -> HotObservable[_T]: """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 = cast(List[Recorded[_T]], list(args)) return HotObservable(self, messages) def create_cold_observable( self, *args: Union[Recorded[_T], List[Recorded[_T]]] ) -> ColdObservable[_T]: """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 = cast(List[Recorded[_T]], list(args)) return ColdObservable(self, messages) def create_observer(self) -> MockObserver[Any]: """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-4.0.4/reactivex/typing.py000066400000000000000000000025271426446175400162620ustar00rootroot00000000000000from threading import Thread from typing import Callable, TypeVar, Union from .abc.observable import Subscription from .abc.observer import OnCompleted, OnError, OnNext from .abc.periodicscheduler import ( ScheduledPeriodicAction, ScheduledSingleOrPeriodicAction, ) from .abc.scheduler import ( AbsoluteOrRelativeTime, AbsoluteTime, RelativeTime, ScheduledAction, ) from .abc.startable import StartableBase _TState = TypeVar("_TState") _T1 = TypeVar("_T1") _T2 = TypeVar("_T2") Action = Callable[[], None] Mapper = Callable[[_T1], _T2] MapperIndexed = Callable[[_T1, int], _T2] Predicate = Callable[[_T1], bool] PredicateIndexed = Callable[[_T1, int], bool] Comparer = Callable[[_T1, _T1], bool] SubComparer = Callable[[_T1, _T1], int] Accumulator = Callable[[_TState, _T1], _TState] Startable = Union[StartableBase, Thread] StartableTarget = Callable[..., None] StartableFactory = Callable[[StartableTarget], Startable] __all__ = [ "Accumulator", "AbsoluteTime", "AbsoluteOrRelativeTime", "Comparer", "Mapper", "MapperIndexed", "OnNext", "OnError", "OnCompleted", "Predicate", "PredicateIndexed", "RelativeTime", "SubComparer", "ScheduledPeriodicAction", "ScheduledSingleOrPeriodicAction", "ScheduledAction", "Startable", "StartableTarget", "Subscription", ] RxPY-4.0.4/setup.cfg000066400000000000000000000003741426446175400142230ustar00rootroot00000000000000[egg_info] #tag_build = dev #tag_svn_revision = 1 [aliases] test = pytest [tool:pytest] testpaths = tests asyncio_mode = strict [flake8] max-line-length = 88 [isort] profile = black src_paths=rx [mypy] python_version = 3.9 follow_imports = silent RxPY-4.0.4/tests/000077500000000000000000000000001426446175400135405ustar00rootroot00000000000000RxPY-4.0.4/tests/__init__.py000066400000000000000000000000001426446175400156370ustar00rootroot00000000000000RxPY-4.0.4/tests/test_core/000077500000000000000000000000001426446175400155275ustar00rootroot00000000000000RxPY-4.0.4/tests/test_core/__init__.py000066400000000000000000000000001426446175400176260ustar00rootroot00000000000000RxPY-4.0.4/tests/test_core/test_notification.py000066400000000000000000000171731426446175400216370ustar00rootroot00000000000000from typing import Any from reactivex.abc import ObserverBase from reactivex.notification import OnCompleted, OnError, OnNext from reactivex.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 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(ObserverBase): 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(ObserverBase): 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 == str(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(ObserverBase[Any]): def __init__(self): super(CheckOnErrorObserver, self).__init__() self.error = None def on_next(self, value: Any) -> None: raise NotImplementedError() def on_error(self, error: Exception) -> None: self.error = str(error) def on_completed(self) -> None: 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(ObserverBase): 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-4.0.4/tests/test_core/test_observer.py000066400000000000000000000124511426446175400207720ustar00rootroot00000000000000from reactivex import Observer from reactivex.notification import OnCompleted, OnError, OnNext, 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: Exception): self.has_on_error = str(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 str(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-4.0.4/tests/test_core/test_priorityqueue.py000066400000000000000000000065171426446175400220770ustar00rootroot00000000000000import unittest from reactivex.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-4.0.4/tests/test_disposables/000077500000000000000000000000001426446175400171075ustar00rootroot00000000000000RxPY-4.0.4/tests/test_disposables/__init__.py000066400000000000000000000000001426446175400212060ustar00rootroot00000000000000RxPY-4.0.4/tests/test_disposables/test_disposable.py000066400000000000000000000133561426446175400226550ustar00rootroot00000000000000from reactivex.disposable import ( BooleanDisposable, CompositeDisposable, Disposable, RefCountDisposable, SerialDisposable, SingleAssignmentDisposable, ) 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-4.0.4/tests/test_integration/000077500000000000000000000000001426446175400171225ustar00rootroot00000000000000RxPY-4.0.4/tests/test_integration/test_concat_repeat.py000066400000000000000000000007721426446175400233500ustar00rootroot00000000000000import unittest from reactivex import operators as ops from reactivex.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-4.0.4/tests/test_integration/test_group_reduce.py000066400000000000000000000016331426446175400232210ustar00rootroot00000000000000import unittest import reactivex from reactivex import operators as ops class TestGroupByReduce(unittest.TestCase): def test_groupby_count(self): res = [] counts = reactivex.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 = [] reactivex.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-4.0.4/tests/test_observable/000077500000000000000000000000001426446175400167235ustar00rootroot00000000000000RxPY-4.0.4/tests/test_observable/__init__.py000066400000000000000000000000001426446175400210220ustar00rootroot00000000000000RxPY-4.0.4/tests/test_observable/test_all.py000066400000000000000000000072041426446175400211070ustar00rootroot00000000000000import unittest from reactivex import operators as _ from reactivex.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 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-4.0.4/tests/test_observable/test_amb.py000066400000000000000000000107421426446175400210770ustar00rootroot00000000000000import unittest import reactivex from reactivex import operators as ops from reactivex.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 TestAmb(unittest.TestCase): def test_amb_never2(self): scheduler = TestScheduler() l = reactivex.never() r = reactivex.never() def create(): return l.pipe(ops.amb(r)) results = scheduler.start(create) assert results.messages == [] def test_amb_never3(self): scheduler = TestScheduler() n1 = reactivex.never() n2 = reactivex.never() n3 = reactivex.never() def create(): return reactivex.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 = reactivex.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 = reactivex.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-4.0.4/tests/test_observable/test_asobservable.py000066400000000000000000000056431426446175400230140ustar00rootroot00000000000000import unittest import reactivex from reactivex import operators as ops from reactivex.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 TestAsObservable(unittest.TestCase): def test_as_observable_hides(self): some_observable = reactivex.empty() assert some_observable.pipe(ops.as_observable()) != some_observable def test_as_observable_never(self): scheduler = TestScheduler() def create(): return reactivex.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 str(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 = reactivex.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-4.0.4/tests/test_observable/test_average.py000066400000000000000000000054031426446175400217500ustar00rootroot00000000000000import unittest from reactivex import operators as _ from reactivex.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 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-4.0.4/tests/test_observable/test_blocking/000077500000000000000000000000001426446175400215525ustar00rootroot00000000000000RxPY-4.0.4/tests/test_observable/test_blocking/__init__.py000066400000000000000000000000001426446175400236510ustar00rootroot00000000000000RxPY-4.0.4/tests/test_observable/test_blocking/test_blocking.py000066400000000000000000000030461426446175400247560ustar00rootroot00000000000000import unittest import pytest import reactivex from reactivex import operators as ops from reactivex.internal.exceptions import SequenceContainsNoElementsError from reactivex.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): reactivex.empty().run() def test_run_error(self): with pytest.raises(RxException): reactivex.throw(RxException()).run() def test_run_just(self): result = reactivex.just(42).run() assert result == 42 def test_run_range(self): result = reactivex.range(42).run() assert result == 41 def test_run_range_to_iterable(self): result = reactivex.range(42).pipe(ops.to_iterable()).run() assert list(result) == list(range(42)) def test_run_from(self): result = reactivex.from_([1, 2, 3]).run() assert result == 3 def test_run_from_first(self): result = reactivex.from_([1, 2, 3]).pipe(ops.first()).run() assert result == 1 def test_run_of(self): result = reactivex.of(1, 2, 3).run() assert result == 3 RxPY-4.0.4/tests/test_observable/test_buffer.py000066400000000000000000000325271426446175400216160ustar00rootroot00000000000000import unittest from reactivex import operators as ops from reactivex.testing import ReactiveTest, TestScheduler from reactivex.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-4.0.4/tests/test_observable/test_bufferwithcount.py000066400000000000000000000152521426446175400235570ustar00rootroot00000000000000import unittest from reactivex import operators as ops from reactivex.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 # 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-4.0.4/tests/test_observable/test_bufferwithtime.py000066400000000000000000000077321426446175400233710ustar00rootroot00000000000000import unittest from reactivex import operators as ops from reactivex.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 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-4.0.4/tests/test_observable/test_bufferwithtimeorcount.py000066400000000000000000000063751426446175400250050ustar00rootroot00000000000000import unittest from reactivex import operators as ops from reactivex.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 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-4.0.4/tests/test_observable/test_case.py000066400000000000000000000154531426446175400212570ustar00rootroot00000000000000import unittest import reactivex from reactivex.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 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 reactivex.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 reactivex.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 reactivex.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 reactivex.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 reactivex.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 reactivex.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 reactivex.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 reactivex.case(mapper, map) results = scheduler.start(create) assert results.messages == [on_error(200, ex)] assert xs.subscriptions == [] assert ys.subscriptions == [] RxPY-4.0.4/tests/test_observable/test_catch.py000066400000000000000000000213611426446175400214210ustar00rootroot00000000000000import unittest import reactivex from reactivex import operators as ops from reactivex.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 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 = reactivex.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 = reactivex.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 reactivex.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 reactivex.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 str(e) == ex return o2 def handler2(e, source): second_handler_called[0] = True assert str(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-4.0.4/tests/test_observable/test_combinelatest.py000066400000000000000000000365001426446175400231710ustar00rootroot00000000000000import unittest import reactivex from reactivex import operators as ops from reactivex.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 # 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 = reactivex.never() e2 = reactivex.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 = reactivex.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 = reactivex.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 = reactivex.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 = reactivex.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 = reactivex.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 = reactivex.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-4.0.4/tests/test_observable/test_concat.py000066400000000000000000000211021426446175400215770ustar00rootroot00000000000000import unittest import reactivex from reactivex import operators as ops from reactivex.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 # Helper function for raising exceptions within lambdas def _raise(error: str): raise RxException(error) 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 = reactivex.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 = reactivex.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 = reactivex.never() e2 = reactivex.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 = reactivex.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 = reactivex.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 = reactivex.create(subscribe_e1) e2 = reactivex.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 = reactivex.create(subscribe_e1) e2 = reactivex.create(subscribe_e2) stream = e1.pipe(ops.concat(e2)) stream.subscribe() assert subscribe_schedulers["e1"] is None assert subscribe_schedulers["e2"] is None RxPY-4.0.4/tests/test_observable/test_connectableobservable.py000066400000000000000000000154751426446175400246720ustar00rootroot00000000000000import unittest import reactivex from reactivex import ConnectableObservable, Observable from reactivex import operators as ops from reactivex.abc import ObserverBase from reactivex.subject import Subject from reactivex.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 MySubject(Observable, ObserverBase): 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) -> None: 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(reactivex.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 = reactivex.create(subscribe) subject = MySubject() conn = ConnectableObservable(xs, subject) conn.connect(scheduler) assert subscribe_scheduler is scheduler RxPY-4.0.4/tests/test_observable/test_contains.py000066400000000000000000000107701426446175400221570ustar00rootroot00000000000000import unittest from reactivex import operators as ops from reactivex.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 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-4.0.4/tests/test_observable/test_count.py000066400000000000000000000157031426446175400214720ustar00rootroot00000000000000import unittest from reactivex import operators as _ from reactivex.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 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-4.0.4/tests/test_observable/test_create.py000066400000000000000000000100031426446175400215710ustar00rootroot00000000000000import unittest import reactivex from reactivex.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 # 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 reactivex.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 reactivex.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 reactivex.create(subscribe) results = scheduler.start(_create) assert results.messages == [on_error(200, ex)] def test_create_exception(self): with self.assertRaises(RxException): reactivex.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 reactivex.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): reactivex.create(subscribe).subscribe(lambda x: _raise("ex")) def subscribe2(o, scheduler=None): o.on_error("exception") return lambda: None with self.assertRaises(RxException): reactivex.create(subscribe2).subscribe(on_error=lambda ex: _raise("ex")) def subscribe3(o, scheduler=None): o.on_completed() return lambda: None with self.assertRaises(RxException): reactivex.create(subscribe3).subscribe(on_completed=lambda: _raise("ex")) RxPY-4.0.4/tests/test_observable/test_debounce.py000066400000000000000000000326331426446175400221270ustar00rootroot00000000000000import unittest from reactivex import empty, never from reactivex import operators as _ from reactivex import throw from reactivex.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 # 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-4.0.4/tests/test_observable/test_defaultifempty.py000066400000000000000000000045401426446175400233610ustar00rootroot00000000000000import unittest from typing import Optional from reactivex import Observable from reactivex import operators as ops from reactivex.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 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() -> Observable[Optional[int]]: 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-4.0.4/tests/test_observable/test_defer.py000066400000000000000000000055111426446175400214230ustar00rootroot00000000000000import unittest import reactivex from reactivex.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 # 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 reactivex.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 reactivex.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 reactivex.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 reactivex.defer(defer) results = scheduler.start(create) assert results.messages == [on_error(200, ex)] assert 1 == invoked[0] RxPY-4.0.4/tests/test_observable/test_delay.py000066400000000000000000000176241426446175400214440ustar00rootroot00000000000000import logging import unittest from datetime import datetime from reactivex.operators import delay from reactivex.testing import ReactiveTest, TestScheduler 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-4.0.4/tests/test_observable/test_delaywithmapper.py000066400000000000000000000137651426446175400235470ustar00rootroot00000000000000import unittest from reactivex import operators as ops from reactivex.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 # 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-4.0.4/tests/test_observable/test_distinct.py000066400000000000000000000102131426446175400221520ustar00rootroot00000000000000import math import unittest from reactivex import operators as ops from reactivex.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 # 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-4.0.4/tests/test_observable/test_distinctuntilchanged.py000066400000000000000000000223721426446175400245510ustar00rootroot00000000000000import unittest import reactivex from reactivex import operators as ops from reactivex.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 # 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 reactivex.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 str(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-4.0.4/tests/test_observable/test_doaction.py000066400000000000000000000304441426446175400221410ustar00rootroot00000000000000import unittest import reactivex from reactivex import operators as _ from reactivex.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 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 reactivex.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) # def 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 reactivex.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-4.0.4/tests/test_observable/test_dowhile.py000066400000000000000000000122651426446175400217750ustar00rootroot00000000000000import unittest from reactivex import operators as ops from reactivex.testing import ReactiveTest, TestScheduler 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-4.0.4/tests/test_observable/test_elementat.py000066400000000000000000000103241426446175400223120ustar00rootroot00000000000000import unittest from reactivex import operators as ops from reactivex.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 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-4.0.4/tests/test_observable/test_empty.py000066400000000000000000000023621426446175400214750ustar00rootroot00000000000000import unittest from reactivex import empty from reactivex.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 # 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-4.0.4/tests/test_observable/test_expand.py000066400000000000000000000072651426446175400216250ustar00rootroot00000000000000import unittest from reactivex import operators as ops from reactivex.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 # 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-4.0.4/tests/test_observable/test_filter.py000066400000000000000000000373261426446175400216340ustar00rootroot00000000000000import unittest from reactivex import Observable from reactivex.disposable import SerialDisposable from reactivex.operators import filter, filter_indexed from reactivex.testing import ReactiveTest, TestScheduler, 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 RxException(Exception): pass 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() -> Observable[int]: def predicate(x: int) -> bool: 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() -> Observable[int]: def predicate(x: int) -> bool: 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() -> Observable[int]: def predicate(x: int) -> bool: 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: int) -> bool: 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: int) -> bool: 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: int) -> bool: 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-4.0.4/tests/test_observable/test_finally.py000066400000000000000000000053271426446175400220010ustar00rootroot00000000000000import unittest import reactivex from reactivex import operators as ops from reactivex.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 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 = reactivex.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 str(results[0].value.exception) == ex ) assert invasserted[0] RxPY-4.0.4/tests/test_observable/test_find.py000066400000000000000000000051101426446175400212510ustar00rootroot00000000000000import unittest from reactivex import operators as ops from reactivex.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 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-4.0.4/tests/test_observable/test_first.py000066400000000000000000000102301426446175400214570ustar00rootroot00000000000000import unittest from reactivex import operators as ops from reactivex.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 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-4.0.4/tests/test_observable/test_firstordefault.py000066400000000000000000000114341426446175400233740ustar00rootroot00000000000000import unittest from reactivex import operators as ops from reactivex.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 # 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-4.0.4/tests/test_observable/test_flatmap.py000066400000000000000000000720111426446175400217610ustar00rootroot00000000000000import unittest import reactivex from reactivex import operators as ops from reactivex.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 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 reactivex.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-4.0.4/tests/test_observable/test_flatmap_async.py000066400000000000000000000017561426446175400231660ustar00rootroot00000000000000import asyncio import unittest from reactivex import operators as ops from reactivex.scheduler.eventloop import AsyncIOScheduler from reactivex.subject import Subject 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: int): async def _mapper(i: int): return i + 1 return asyncio.ensure_future(_mapper(i)) def on_next(i: int): nonlocal actual_next actual_next = i def on_error(ex): print("Error", ex) async def test_flat_map(): x: Subject[int] = Subject() x.pipe(ops.flat_map(mapper)).subscribe( on_next, on_error, scheduler=scheduler ) x.on_next(10) await asyncio.sleep(0.1) loop.run_until_complete(test_flat_map()) assert actual_next == 11 RxPY-4.0.4/tests/test_observable/test_forin.py000066400000000000000000000031571426446175400214570ustar00rootroot00000000000000import unittest import reactivex from reactivex.observable.observable import Observable from reactivex.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 TestForIn(unittest.TestCase): def test_for_basic(self): scheduler = TestScheduler() def create(): def mapper(x: int) -> Observable[int]: 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 reactivex.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: int): raise Exception(ex) return reactivex.for_in([1, 2, 3], mapper) results = scheduler.start(create=create) assert results.messages == [on_error(200, ex)] RxPY-4.0.4/tests/test_observable/test_forkjoin.py000066400000000000000000000203531426446175400221600ustar00rootroot00000000000000import unittest import reactivex from reactivex import operators as ops from reactivex.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 = reactivex.never() e2 = reactivex.never() results = scheduler.start(lambda: reactivex.fork_join(e1, e2)) assert results.messages == [] def test_fork_join_never_empty(self): scheduler = TestScheduler() e1 = reactivex.never() e2 = reactivex.empty() results = scheduler.start(lambda: reactivex.fork_join(e1, e2)) assert results.messages == [on_completed(200)] def test_fork_join_never_non_empty(self): scheduler = TestScheduler() e1 = reactivex.never() e2 = scheduler.create_hot_observable( [on_next(150, 1), on_next(230, 2), on_completed(300)] ) results = scheduler.start(lambda: reactivex.fork_join(e1, e2)) assert results.messages == [] def test_fork_join_empty_empty(self): scheduler = TestScheduler() e1 = reactivex.empty() e2 = reactivex.empty() results = scheduler.start(lambda: reactivex.fork_join(e1, e2)) assert results.messages == [on_completed(200)] def test_fork_join_empty_non_empty(self): scheduler = TestScheduler() e1 = reactivex.empty() e2 = scheduler.create_hot_observable( [on_next(150, 1), on_next(230, 2), on_completed(300)] ) results = scheduler.start(lambda: reactivex.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: reactivex.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: reactivex.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 = reactivex.empty() e2 = scheduler.create_hot_observable( [on_next(150, 1), on_next(230, 2), on_error(300, ex)] ) results = scheduler.start(lambda: reactivex.fork_join(e1, e2)) assert results.messages == [on_completed(200)] def test_fork_join_never_error(self): ex = RxException() scheduler = TestScheduler() e1 = reactivex.never() e2 = scheduler.create_hot_observable( [on_next(150, 1), on_next(230, 2), on_error(300, ex)] ) results = scheduler.start(lambda: reactivex.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: reactivex.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: reactivex.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: reactivex.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: reactivex.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: reactivex.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-4.0.4/tests/test_observable/test_fromcallback.py000066400000000000000000000030321426446175400227520ustar00rootroot00000000000000import unittest import reactivex from reactivex.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 = reactivex.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 = reactivex.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 = reactivex.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-4.0.4/tests/test_observable/test_fromfuture.py000066400000000000000000000050361426446175400225360ustar00rootroot00000000000000import asyncio import unittest from asyncio import Future import reactivex class TestFromFuture(unittest.TestCase): def test_future_success(self): loop = asyncio.get_event_loop() success = [False, True, False] async def go(): future = Future() future.set_result(42) source = reactivex.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] async def go(): error = Exception("woops") future = Future() future.set_exception(error) source = reactivex.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] async def go(): future = Future() source = reactivex.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] async def go(): future = Future() future.set_result(42) source = reactivex.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-4.0.4/tests/test_observable/test_fromiterable.py000066400000000000000000000036161426446175400230150ustar00rootroot00000000000000import unittest import reactivex from reactivex.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 # 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 reactivex.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 reactivex.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 = reactivex.from_(iterable_finite) results = scheduler.start(lambda: reactivex.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): reactivex.from_iterable([1, 2, 3]).subscribe(lambda x: _raise("ex")) RxPY-4.0.4/tests/test_observable/test_generate.py000066400000000000000000000052511426446175400221310ustar00rootroot00000000000000import unittest import reactivex from reactivex import operators as ops from reactivex.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 # 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 reactivex.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 reactivex.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 reactivex.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 reactivex.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 reactivex.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-4.0.4/tests/test_observable/test_generatewithrelativetime.py000066400000000000000000000107671426446175400254500ustar00rootroot00000000000000import unittest import reactivex from reactivex.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 # 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 reactivex.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 reactivex.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 reactivex.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 reactivex.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 reactivex.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 reactivex.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 reactivex.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 reactivex.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 reactivex.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 reactivex.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-4.0.4/tests/test_observable/test_groupby.py000066400000000000000000000521101426446175400220220ustar00rootroot00000000000000import unittest import reactivex from reactivex import operators as ops from reactivex.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 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] = reactivex.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: reactivex.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-4.0.4/tests/test_observable/test_groupjoin.py000066400000000000000000001131161426446175400223530ustar00rootroot00000000000000import unittest from datetime import timedelta import reactivex from reactivex import operators as ops from reactivex.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 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: reactivex.timer(x.interval).pipe( ops.filter(lambda _: False) ), lambda y: reactivex.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: reactivex.timer(x.interval), lambda y: reactivex.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: reactivex.timer(x.interval), lambda y: reactivex.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: reactivex.timer(x.interval), lambda y: reactivex.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: reactivex.timer(x.interval).pipe( ops.filter(lambda _: False) ), lambda y: reactivex.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: reactivex.timer(x.interval), lambda y: reactivex.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: reactivex.timer(x.interval), lambda y: reactivex.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: reactivex.timer(x.interval), lambda y: reactivex.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: reactivex.timer(x.interval), lambda y: reactivex.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: reactivex.timer(x.interval).pipe( ops.flat_map( reactivex.throw(ex) if x.value == 6 else reactivex.empty() ) ), lambda y: reactivex.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: reactivex.timer(x.interval), lambda y: reactivex.timer(y.interval).pipe( ops.flat_map( reactivex.throw(ex) if y.value == "tin" else reactivex.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 reactivex.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: reactivex.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 reactivex.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: reactivex.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-4.0.4/tests/test_observable/test_ifthen.py000066400000000000000000000125011426446175400216100ustar00rootroot00000000000000import unittest import reactivex from reactivex.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 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 reactivex.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 reactivex.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 reactivex.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 reactivex.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 reactivex.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 reactivex.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 reactivex.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 reactivex.if_then(condition, xs) results = scheduler.start(create) assert results.messages == [on_completed(200)] assert xs.subscriptions == [] RxPY-4.0.4/tests/test_observable/test_ignoreelements.py000066400000000000000000000042721426446175400233610ustar00rootroot00000000000000import unittest from reactivex import operators as ops from reactivex.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 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-4.0.4/tests/test_observable/test_interval.py000066400000000000000000000044721426446175400221670ustar00rootroot00000000000000import unittest import reactivex from reactivex.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 # 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 reactivex.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 reactivex.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 reactivex.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 reactivex.interval(1000) results = scheduler.start(create) assert results.messages == [] def test_interval_timespan_observer_throws(self): scheduler = TestScheduler() xs = reactivex.interval(1) xs.subscribe(lambda x: _raise("ex"), scheduler=scheduler) with self.assertRaises(RxException): scheduler.start() RxPY-4.0.4/tests/test_observable/test_isempty.py000066400000000000000000000036421426446175400220330ustar00rootroot00000000000000import unittest from reactivex import operators as ops from reactivex.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 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-4.0.4/tests/test_observable/test_join.py000066400000000000000000001021011426446175400212660ustar00rootroot00000000000000import unittest from datetime import timedelta import reactivex from reactivex import operators as ops from reactivex.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 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: reactivex.timer(x.interval), lambda y: reactivex.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: reactivex.timer(x.interval), lambda y: reactivex.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: reactivex.timer(x.interval).pipe( ops.filter(lambda _: False) ), lambda y: reactivex.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: reactivex.timer(x.interval), lambda y: reactivex.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: reactivex.timer(x.interval), lambda y: reactivex.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: reactivex.timer(x.interval), lambda y: reactivex.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: reactivex.timer(x.interval), lambda y: reactivex.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: reactivex.timer(x.interval), lambda y: reactivex.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: reactivex.timer(x.interval), lambda y: reactivex.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: reactivex.timer(x.interval).pipe( ops.flat_map( reactivex.throw(ex) if x.value == 6 else reactivex.empty() ) ), lambda y: reactivex.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: reactivex.timer(x.interval), lambda y: reactivex.timer(y.interval).pipe( ops.flat_map( reactivex.throw(ex) if y.value == "tin" else reactivex.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 reactivex.empty() def mapper(xy): x, y = xy return "{}{}".format(x.value, y.value) return xs.pipe( ops.join( ys, left_duration_mapper, lambda y: reactivex.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 reactivex.empty() def mapper(xy): x, y = xy return "{}{}".format(x.value, y.value) return xs.pipe( ops.join( ys, lambda x: reactivex.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 = reactivex.create(subscribe_x) ys = reactivex.create(subscribe_y) duration_x = reactivex.create(subscribe_duration_x) duration_y = reactivex.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 = reactivex.create(subscribe_x) ys = reactivex.create(subscribe_y) duration_x = reactivex.create(subscribe_duration_x) duration_y = reactivex.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-4.0.4/tests/test_observable/test_last.py000066400000000000000000000111301426446175400212730ustar00rootroot00000000000000import unittest from reactivex import operators as _ from reactivex.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 # 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-4.0.4/tests/test_observable/test_lastordefault.py000066400000000000000000000113771426446175400232160ustar00rootroot00000000000000import unittest from reactivex import operators as _ from reactivex.observable.observable import Observable from reactivex.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 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() -> Observable[int]: return xs.pipe(_.last_or_default(default_value=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() -> Observable[int]: return xs.pipe(_.last_or_default(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(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(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() -> Observable[int]: def predicate(x: int) -> bool: return x % 2 == 1 return xs.pipe(_.last_or_default(0, 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_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: int) -> bool: return x > 10 return xs.pipe(_.last_or_default(0, predicate)) 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: int) -> bool: return x > 10 return xs.pipe(_.last_or_default(0, predicate)) 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: int) -> bool: if x < 4: return x % 2 == 1 else: raise Exception(ex) return xs.pipe(_.last_or_default(0, predicate)) res = scheduler.start(create=create) assert res.messages == [on_error(230, ex)] assert xs.subscriptions == [subscribe(200, 230)] RxPY-4.0.4/tests/test_observable/test_map.py000066400000000000000000000325341426446175400211200ustar00rootroot00000000000000import unittest from reactivex import create, empty, return_value, throw from reactivex.disposable import SerialDisposable from reactivex.operators import map, map_indexed from reactivex.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 # 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-4.0.4/tests/test_observable/test_marbles.py000066400000000000000000000470101426446175400217630ustar00rootroot00000000000000import datetime import unittest import reactivex from reactivex import notification from reactivex.observable.marbles import parse from reactivex.testing import TestScheduler from reactivex.testing.reactivetest import ReactiveTest 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 = reactivex.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 = reactivex.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 = reactivex.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 = reactivex.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 = reactivex.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 = reactivex.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 = reactivex.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 = reactivex.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 = reactivex.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 = reactivex.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 = reactivex.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 = reactivex.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 = reactivex.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 = reactivex.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 = reactivex.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 = reactivex.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 = reactivex.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 = reactivex.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 = reactivex.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 = reactivex.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 = reactivex.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 = reactivex.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 = reactivex.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 = reactivex.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 = reactivex.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 = reactivex.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-4.0.4/tests/test_observable/test_materialize.py000066400000000000000000000104051426446175400226420ustar00rootroot00000000000000import unittest import reactivex from reactivex import operators as _ from reactivex.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 TestMaterialize(unittest.TestCase): def test_materialize_never(self): scheduler = TestScheduler() def create(): return reactivex.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 str(results[0].value.value.exception) == ex ) assert results[1].value.kind == "C" def test_materialize_dematerialize_never(self): scheduler = TestScheduler() def create(): return reactivex.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 str(results[0].value.exception) == ex and results[0].time == 250 ) if __name__ == "__main__": unittest.main() RxPY-4.0.4/tests/test_observable/test_max.py000066400000000000000000000132031426446175400211200ustar00rootroot00000000000000import unittest from reactivex import operators as ops from reactivex.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 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-4.0.4/tests/test_observable/test_maxby.py000066400000000000000000000256201426446175400214610ustar00rootroot00000000000000import unittest from reactivex import operators as ops from reactivex.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 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-4.0.4/tests/test_observable/test_merge.py000066400000000000000000000645561426446175400214530ustar00rootroot00000000000000import unittest import reactivex from reactivex import operators as ops from reactivex.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 TestMerge(unittest.TestCase): def test_merge_never2(self): scheduler = TestScheduler() n1 = reactivex.never() n2 = reactivex.never() def create(): return reactivex.merge(n1, n2) results = scheduler.start(create) assert results.messages == [] def test_merge_never3(self): scheduler = TestScheduler() n1 = reactivex.never() n2 = reactivex.never() n3 = reactivex.never() def create(): return reactivex.merge(n1, n2, n3) results = scheduler.start(create) assert results.messages == [] def test_merge_empty2(self): scheduler = TestScheduler() e1 = reactivex.empty() e2 = reactivex.empty() def create(): return reactivex.merge(e1, e2) results = scheduler.start(create) assert results.messages == [on_completed(200)] def test_merge_empty3(self): scheduler = TestScheduler() e1 = reactivex.empty() e2 = reactivex.empty() e3 = reactivex.empty() def create(): return reactivex.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 reactivex.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 reactivex.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 reactivex.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 = reactivex.never() def create(): return reactivex.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 = reactivex.never() def create(): return reactivex.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 = reactivex.never() def create(): return reactivex.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 = reactivex.never() def create(): return reactivex.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 = reactivex.never() def create(): return reactivex.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 = reactivex.never() def create(): return reactivex.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 reactivex.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 reactivex.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 reactivex.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 reactivex.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 reactivex.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 reactivex.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-4.0.4/tests/test_observable/test_min.py000066400000000000000000000112161426446175400211200ustar00rootroot00000000000000import unittest from reactivex import operators as ops from reactivex.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 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-4.0.4/tests/test_observable/test_minby.py000066400000000000000000000225331426446175400214570ustar00rootroot00000000000000import unittest from reactivex import operators as ops from reactivex.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 # 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-4.0.4/tests/test_observable/test_multicast.py000066400000000000000000000341071426446175400223460ustar00rootroot00000000000000import unittest from typing import Any, List, Optional from reactivex import abc from reactivex import operators as ops from reactivex.observable.connectableobservable import ConnectableObservable from reactivex.subject import Subject from reactivex.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 TestMulticast(unittest.TestCase): def test_multicast_hot_1(self): scheduler = TestScheduler() s: Subject[int] = 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: List[Optional[abc.DisposableBase]] = [None] d2: List[Optional[abc.DisposableBase]] = [None] c: List[Optional[ConnectableObservable[int]]] = [None] def action(scheduler: abc.SchedulerBase, state: Any = None): c[0] = xs.pipe(ops.multicast(s)) scheduler.schedule_absolute(50, action) def action0(scheduler: abc.SchedulerBase, state: Any = None): assert c[0] d1[0] = c[0].subscribe(obv, scheduler=scheduler) scheduler.schedule_absolute(100, action0) def action1(scheduler: abc.SchedulerBase, state: Any = None): assert c[0] d2[0] = c[0].connect(scheduler) scheduler.schedule_absolute(200, action1) def action2(scheduler: abc.SchedulerBase, state: Any = None): assert d1[0] 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: abc.SchedulerBase, state: Any = None): c[0] = xs.pipe(ops.multicast(s)) scheduler.schedule_absolute(50, action0) def action1(scheduler: abc.SchedulerBase, state: Any = None): d2[0] = c[0].connect(scheduler) scheduler.schedule_absolute(100, action1) def action2(scheduler: abc.SchedulerBase, state: Any = None): d1[0] = c[0].subscribe(o, scheduler) scheduler.schedule_absolute(200, action2) def action3(scheduler: abc.SchedulerBase, state: Any = None): 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: abc.SchedulerBase, state: Any = None): c[0] = xs.pipe(ops.multicast(s)) scheduler.schedule_absolute(50, action0) def action1(scheduler: abc.SchedulerBase, state: Any = None): d2[0] = c[0].connect(scheduler) scheduler.schedule_absolute(100, action1) def action2(scheduler: abc.SchedulerBase, state: Any = None): d1[0] = c[0].subscribe(o) scheduler.schedule_absolute(200, action2) def action3(scheduler: abc.SchedulerBase, state: Any = None): 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: abc.SchedulerBase, state: Any = None): c[0] = xs.pipe(ops.multicast(s)) scheduler.schedule_absolute(50, action0) def action1(scheduler: abc.SchedulerBase, state: Any = None): d2[0] = c[0].connect(scheduler) scheduler.schedule_absolute(100, action1) def action2(scheduler: abc.SchedulerBase, state: Any = None): d1[0] = c[0].subscribe(o) scheduler.schedule_absolute(200, action2) def action3(scheduler: abc.SchedulerBase, state: Any = None): d2[0].dispose() scheduler.schedule_absolute(300, action3) def action4(scheduler: abc.SchedulerBase, state: Any = None): 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: abc.SchedulerBase, state: Any = None): c[0] = xs.pipe(ops.multicast(s)) scheduler.schedule_absolute(50, action0) def action1(scheduler: abc.SchedulerBase, state: Any = None): d2[0] = c[0].connect(scheduler) scheduler.schedule_absolute(100, action1) def action2(scheduler: abc.SchedulerBase, state: Any = None): d1[0] = c[0].subscribe(o, scheduler) scheduler.schedule_absolute(200, action2) def action3(scheduler: abc.SchedulerBase, state: Any = None): d2[0].dispose() scheduler.schedule_absolute(300, action3) def action4(scheduler: abc.SchedulerBase, state: Any = None): 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: abc.SchedulerBase, state: Any = None): c[0] = xs.pipe(ops.multicast(s)) scheduler.schedule_absolute(50, action0) def action1(scheduler: abc.SchedulerBase, state: Any = None): d2[0] = c[0].connect(scheduler) scheduler.schedule_absolute(100, action1) def action2(scheduler: abc.SchedulerBase, state: Any = None): 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: abc.SchedulerBase, state: Any = None): c[0] = xs.pipe(ops.multicast(s)) scheduler.schedule_absolute(50, action0) def action1(scheduler: abc.SchedulerBase, state: Any = None): d2[0] = c[0].connect(scheduler) scheduler.schedule_absolute(100, action1) def action2(scheduler: abc.SchedulerBase, state: Any = None): 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-4.0.4/tests/test_observable/test_never.py000066400000000000000000000011441426446175400214530ustar00rootroot00000000000000import unittest from reactivex import never from reactivex.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 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-4.0.4/tests/test_observable/test_observeon.py000066400000000000000000000052701426446175400223420ustar00rootroot00000000000000import unittest import reactivex from reactivex import operators as ops from reactivex.scheduler import ImmediateScheduler from reactivex.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 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 = reactivex.create(subscribe) xs.pipe(ops.observe_on(scheduler)).subscribe( scheduler=expected_subscribe_scheduler ) assert expected_subscribe_scheduler == actual_subscribe_scheduler RxPY-4.0.4/tests/test_observable/test_of.py000066400000000000000000000025041426446175400207410ustar00rootroot00000000000000import unittest import reactivex from reactivex.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 TestOf(unittest.TestCase): def test_of(self): results = [] reactivex.of(1, 2, 3, 4, 5).subscribe(results.append) assert str([1, 2, 3, 4, 5]) == str(results) def test_of_empty(self): results = [] reactivex.of().subscribe(results.append) assert len(results) == 0 def teest_of_with_scheduler(self): scheduler = TestScheduler() def create(): return reactivex.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 reactivex.of(scheduler=scheduler) results = scheduler.start(create=create) assert results.messages == [on_completed(201)] RxPY-4.0.4/tests/test_observable/test_onerrorresumenext.py000066400000000000000000000131701426446175400241440ustar00rootroot00000000000000import unittest import reactivex from reactivex import operators as ops from reactivex.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 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 reactivex.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 reactivex.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 reactivex.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 = reactivex.never() def create(): return reactivex.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 = reactivex.never() o2 = scheduler.create_hot_observable(msgs1) def create(): return reactivex.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: Exception): assert str(ex) == "ex" msgs2 = [on_next(240, 4), on_completed(250)] o2 = scheduler.create_hot_observable(msgs2) return o2 def create(): return reactivex.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-4.0.4/tests/test_observable/test_pairwise.py000066400000000000000000000067741426446175400221750ustar00rootroot00000000000000import unittest from reactivex import operators as ops from reactivex.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 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-4.0.4/tests/test_observable/test_partition.py000066400000000000000000000222541426446175400223520ustar00rootroot00000000000000import unittest from reactivex import operators as ops from reactivex.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 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-4.0.4/tests/test_observable/test_pluck.py000066400000000000000000000041521426446175400214540ustar00rootroot00000000000000import unittest from reactivex import operators as ops from reactivex.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 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-4.0.4/tests/test_observable/test_publish.py000066400000000000000000000407461426446175400220150ustar00rootroot00000000000000import unittest import reactivex from reactivex import ConnectableObservable, Observable from reactivex import operators as ops from reactivex.abc import ObserverBase from reactivex.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 # Helper function for raising exceptions within lambdas def _raise(ex): raise RxException(ex) class MySubject(Observable, ObserverBase): 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 reactivex.create(create) xs = reactivex.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 = reactivex.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-4.0.4/tests/test_observable/test_publishvalue.py000066400000000000000000000320521426446175400230410ustar00rootroot00000000000000import unittest import reactivex from reactivex import operators as ops from reactivex.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 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 = reactivex.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-4.0.4/tests/test_observable/test_range.py000066400000000000000000000053231426446175400214330ustar00rootroot00000000000000import unittest import reactivex from reactivex import operators as ops from reactivex.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 TestRange(unittest.TestCase): def test_range_zero(self): scheduler = TestScheduler() def create(): return reactivex.range(0, 0) results = scheduler.start(create) assert results.messages == [on_completed(200)] def test_range_one(self): scheduler = TestScheduler() def create(): return reactivex.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 reactivex.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 reactivex.range(-10, 5) results = scheduler.start(create, disposed=200) assert results.messages == [] def test_range_double_subscribe(self): scheduler = TestScheduler() obs = reactivex.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 reactivex.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 reactivex.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-4.0.4/tests/test_observable/test_reduce.py000066400000000000000000000115301426446175400216030ustar00rootroot00000000000000import unittest import reactivex from reactivex import operators as ops from reactivex.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 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): reactivex.empty().pipe(ops.reduce(lambda acc, v: v, seed=None)).subscribe() if __name__ == "__main__": unittest.main() RxPY-4.0.4/tests/test_observable/test_repeat.py000066400000000000000000000202541426446175400216170ustar00rootroot00000000000000import unittest import reactivex from reactivex import operators as ops from reactivex.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 # 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 reactivex.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 reactivex.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 reactivex.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 reactivex.repeat_value(42, 10) results = scheduler.start(create, disposed=200) assert results.messages == [] def test_repeat_value(self): scheduler = TestScheduler() def create(): return reactivex.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 = reactivex.return_value(11).pipe(ops.repeat()) xs.subscribe(lambda x: _raise("ex"), scheduler=scheduler1) with self.assertRaises(RxException): scheduler1.start() scheduler2 = TestScheduler() ys = reactivex.throw("ex").pipe(ops.repeat()) ys.subscribe(lambda ex: _raise("ex"), scheduler=scheduler2) with self.assertRaises(Exception): scheduler2.start() scheduler3 = TestScheduler() zs = reactivex.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 = reactivex.return_value(1).pipe(ops.repeat(3)) xs.subscribe(lambda x: _raise("ex"), scheduler=scheduler1) with self.assertRaises(RxException): scheduler1.start() scheduler2 = TestScheduler() ys = reactivex.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 = reactivex.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-4.0.4/tests/test_observable/test_replay.py000066400000000000000000000625521426446175400216420ustar00rootroot00000000000000import unittest import reactivex from reactivex import operators as ops from reactivex.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 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 = reactivex.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 = reactivex.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.pipe(ops.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.pipe(ops.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-4.0.4/tests/test_observable/test_retry.py000066400000000000000000000145121426446175400215040ustar00rootroot00000000000000import unittest import pytest import reactivex from reactivex import operators as ops from reactivex.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 # 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 = reactivex.return_value(1).pipe(ops.retry()) xs.subscribe(lambda x: _raise("ex"), scheduler=scheduler1) with pytest.raises(RxException): scheduler1.start() scheduler2 = TestScheduler() ys = reactivex.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 = reactivex.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 = reactivex.return_value(1).pipe(ops.retry(3)) xs.subscribe(lambda x: _raise("ex"), scheduler=scheduler1) self.assertRaises(RxException, scheduler1.start) scheduler2 = TestScheduler() ys = reactivex.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 = reactivex.return_value(1).pipe(ops.retry(100)) zs.subscribe(on_completed=lambda: _raise("ex"), scheduler=scheduler3) with pytest.raises(RxException): scheduler3.start() xss = reactivex.create(lambda o: _raise("ex")).pipe(ops.retry(100)) with pytest.raises(Exception): xss.subscribe() if __name__ == "__main__": unittest.main() RxPY-4.0.4/tests/test_observable/test_returnvalue.py000066400000000000000000000044131426446175400227120ustar00rootroot00000000000000import unittest import reactivex from reactivex.disposable import SerialDisposable from reactivex.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 # 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 reactivex.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 reactivex.return_value(42) results = scheduler.start(factory, disposed=200) assert results.messages == [] def test_return_disposed_after_next(self): scheduler = TestScheduler() d = SerialDisposable() xs = reactivex.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 = reactivex.return_value(1) xs.subscribe(lambda x: _raise("ex"), scheduler=scheduler1) self.assertRaises(RxException, scheduler1.start) scheduler2 = TestScheduler() ys = reactivex.return_value(1) ys.subscribe( lambda x: x, lambda ex: ex, lambda: _raise("ex"), scheduler=scheduler2 ) self.assertRaises(RxException, scheduler2.start) RxPY-4.0.4/tests/test_observable/test_sample.py000066400000000000000000000047321426446175400216230ustar00rootroot00000000000000import unittest import reactivex from reactivex import operators as ops from reactivex.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 # 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 reactivex.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 reactivex.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 reactivex.never().pipe(ops.sample(1)) results = scheduler.start(create) assert results.messages == [] RxPY-4.0.4/tests/test_observable/test_scan.py000066400000000000000000000154171426446175400212700ustar00rootroot00000000000000import unittest from reactivex import Observable, never from reactivex import operators as _ from reactivex.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 TestScan(unittest.TestCase): def test_scan_seed_never(self): scheduler = TestScheduler() seed = 42 def create(): def func(acc: int, x: int) -> int: 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() -> Observable[int]: return xs.pipe(_.scan(lambda acc, x: acc + x, seed)) results = scheduler.start(create).messages assert len(results) == 1 assert ( results[0].value.kind == "E" and str(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() -> Observable[int]: 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() -> Observable[int]: 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() -> Observable[int]: def func(acc: int, x: int) -> int: 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() -> Observable[int]: def func(acc: int, x: int) -> int: 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 str(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-4.0.4/tests/test_observable/test_sequenceequal.py000066400000000000000000000440461426446175400232040ustar00rootroot00000000000000import unittest from reactivex import operators as ops from reactivex.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 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-4.0.4/tests/test_observable/test_single.py000066400000000000000000000123031426446175400216140ustar00rootroot00000000000000import unittest from reactivex import operators as ops from reactivex.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 # 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-4.0.4/tests/test_observable/test_singleordefault.py000066400000000000000000000140571426446175400235320ustar00rootroot00000000000000import unittest from reactivex import operators as ops from reactivex.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 # 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-4.0.4/tests/test_observable/test_skip.py000066400000000000000000000240651426446175400213110ustar00rootroot00000000000000import unittest from reactivex import operators as ops from reactivex.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 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-4.0.4/tests/test_observable/test_skiplast.py000066400000000000000000000176251426446175400222010ustar00rootroot00000000000000import unittest from reactivex import operators as ops from reactivex.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 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-4.0.4/tests/test_observable/test_skiplastwithtime.py000066400000000000000000000073251426446175400237500ustar00rootroot00000000000000import unittest from reactivex import operators as ops from reactivex.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 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-4.0.4/tests/test_observable/test_skipuntil.py000066400000000000000000000115441426446175400223630ustar00rootroot00000000000000import unittest import reactivex from reactivex import Observable from reactivex import operators as ops from reactivex.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 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 = reactivex.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 = reactivex.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 = reactivex.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 = reactivex.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 = reactivex.never() r = reactivex.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-4.0.4/tests/test_observable/test_skipuntilwithtime.py000066400000000000000000000072421426446175400241360ustar00rootroot00000000000000import unittest from datetime import datetime from reactivex import operators as ops from reactivex.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 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-4.0.4/tests/test_observable/test_skipwhile.py000066400000000000000000000217571426446175400223470ustar00rootroot00000000000000import unittest from reactivex import operators as ops from reactivex.testing import ReactiveTest, TestScheduler, 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-4.0.4/tests/test_observable/test_skipwithtime.py000066400000000000000000000073741426446175400230700ustar00rootroot00000000000000import unittest from reactivex import operators as ops from reactivex.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 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-4.0.4/tests/test_observable/test_slice.py000066400000000000000000000171431426446175400214410ustar00rootroot00000000000000import unittest from reactivex.observable.observable import Observable from reactivex.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 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() -> Observable[int]: 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-4.0.4/tests/test_observable/test_some.py000066400000000000000000000110401426446175400212730ustar00rootroot00000000000000import unittest from reactivex import operators as ops from reactivex.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 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-4.0.4/tests/test_observable/test_starmap.py000066400000000000000000000262771426446175400220210ustar00rootroot00000000000000import unittest from reactivex import create, empty from reactivex import operators as ops from reactivex import return_value, throw from reactivex.disposable import SerialDisposable from reactivex.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 # 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-4.0.4/tests/test_observable/test_start.py000066400000000000000000000045141426446175400214750ustar00rootroot00000000000000import asyncio import unittest from asyncio import Future import reactivex from reactivex.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 TestStart(unittest.TestCase): def test_start_async(self): loop = asyncio.get_event_loop() success = [False] async def go(): def func(): future = Future() future.set_result(42) return future source = reactivex.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] async def go(): def func(): future = Future() future.set_exception(Exception(str(42))) return future source = reactivex.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 reactivex.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 reactivex.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 reactivex.start(func, scheduler) res = scheduler.start(create) assert res.messages == [on_error(200, ex)] RxPY-4.0.4/tests/test_observable/test_startwith.py000066400000000000000000000047541426446175400223770ustar00rootroot00000000000000import unittest from reactivex import operators as _ from reactivex.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 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-4.0.4/tests/test_observable/test_subscribeon.py000066400000000000000000000041461426446175400226570ustar00rootroot00000000000000import unittest from reactivex import operators as ops from reactivex.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 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-4.0.4/tests/test_observable/test_sum.py000066400000000000000000000051561426446175400211470ustar00rootroot00000000000000import unittest from reactivex import operators as ops from reactivex.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 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-4.0.4/tests/test_observable/test_switchlatest.py000066400000000000000000000140611426446175400230540ustar00rootroot00000000000000import unittest from reactivex import operators as ops from reactivex.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 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-4.0.4/tests/test_observable/test_take.py000066400000000000000000000245631426446175400212720ustar00rootroot00000000000000import unittest from reactivex import operators as ops from reactivex.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 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-4.0.4/tests/test_observable/test_takelast.py000066400000000000000000000142421426446175400221470ustar00rootroot00000000000000import unittest from reactivex import operators as ops from reactivex.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 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-4.0.4/tests/test_observable/test_takelastbuffer.py000066400000000000000000000141251426446175400233410ustar00rootroot00000000000000import unittest from reactivex import operators as ops from reactivex.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 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-4.0.4/tests/test_observable/test_takelastwithtime.py000066400000000000000000000110671426446175400237240ustar00rootroot00000000000000import unittest from reactivex import operators as ops from reactivex.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 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-4.0.4/tests/test_observable/test_takeuntil.py000066400000000000000000000155451426446175400223460ustar00rootroot00000000000000import unittest import reactivex from reactivex import operators as ops from reactivex.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 TestTakeUntil(unittest.TestCase): def test_take_until_preempt_somedata_next(self): scheduler = TestScheduler() left_msgs = [ on_next(150, 1), on_next(210, 2), on_next(220, 3), on_next(230, 4), on_next(240, 5), on_completed(250), ] right_msgs = [on_next(150, 1), on_next(225, 99), on_completed(230)] left = scheduler.create_hot_observable(left_msgs) right = scheduler.create_hot_observable(right_msgs) def create(): return left.pipe(ops.take_until(right)) 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() left_msgs = [ on_next(150, 1), on_next(210, 2), on_next(220, 3), on_next(230, 4), on_next(240, 5), on_completed(250), ] right_msgs = [on_next(150, 1), on_error(225, ex)] left = scheduler.create_hot_observable(left_msgs) right = scheduler.create_hot_observable(right_msgs) def create(): return left.pipe(ops.take_until(right)) 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() left_msgs = [ on_next(150, 1), on_next(210, 2), on_next(220, 3), on_next(230, 4), on_next(240, 5), on_completed(250), ] right_msgs = [on_next(150, 1), on_completed(225)] left = scheduler.create_hot_observable(left_msgs) right = scheduler.create_hot_observable(right_msgs) def create(): return left.pipe(ops.take_until(right)) 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() left_msgs = [ on_next(150, 1), on_next(210, 2), on_next(220, 3), on_next(230, 4), on_next(240, 5), on_completed(250), ] left = scheduler.create_hot_observable(left_msgs) right = reactivex.never() def create(): return left.pipe(ops.take_until(right)) 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() right_msgs = [on_next(150, 1), on_next(225, 2), on_completed(250)] left = reactivex.never() right = scheduler.create_hot_observable(right_msgs) def create(): return left.pipe(ops.take_until(right)) results = scheduler.start(create) assert results.messages == [on_completed(225)] def test_take_until_preempt_never_error(self): ex = "ex" scheduler = TestScheduler() right_msgs = [on_next(150, 1), on_error(225, ex)] left = reactivex.never() right = scheduler.create_hot_observable(right_msgs) def create(): return left.pipe(ops.take_until(right)) results = scheduler.start(create) assert results.messages == [on_error(225, ex)] def test_take_until_nopreempt_never_empty(self): scheduler = TestScheduler() right_msgs = [on_next(150, 1), on_completed(225)] left = reactivex.never() right = scheduler.create_hot_observable(right_msgs) def create(): return left.pipe(ops.take_until(right)) results = scheduler.start(create) assert results.messages == [] def test_take_until_nopreempt_never_never(self): scheduler = TestScheduler() left = reactivex.never() right = reactivex.never() def create(): return left.pipe(ops.take_until(right)) results = scheduler.start(create) assert results.messages == [] def test_take_until_preempt_beforefirstproduced(self): scheduler = TestScheduler() left_msgs = [on_next(150, 1), on_next(230, 2), on_completed(240)] right_msgs = [on_next(150, 1), on_next(210, 2), on_completed(220)] l = scheduler.create_hot_observable(left_msgs) r = scheduler.create_hot_observable(right_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() left_msgs = [on_next(150, 1), on_error(215, "ex"), on_completed(240)] right_msgs = [on_next(150, 1), on_next(210, 2), on_completed(220)] source_not_disposed = [False] def action(): source_not_disposed[0] = True left = scheduler.create_hot_observable(left_msgs).pipe( ops.do_action(on_next=action) ) right = scheduler.create_hot_observable(right_msgs) def create(): return left.pipe(ops.take_until(right)) 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() left_msgs = [on_next(150, 1), on_next(230, 2), on_completed(240)] right_msgs = [on_next(150, 1), on_next(250, 2), on_completed(260)] signal_not_disposed = [False] left = scheduler.create_hot_observable(left_msgs) def action(): signal_not_disposed[0] = True right = scheduler.create_hot_observable(right_msgs).pipe( ops.do_action(on_next=action) ) def create(): return left.pipe(ops.take_until(right)) results = scheduler.start(create) assert results.messages == [on_next(230, 2), on_completed(240)] assert not signal_not_disposed[0] RxPY-4.0.4/tests/test_observable/test_takeuntilwithtime.py000066400000000000000000000077431426446175400241220ustar00rootroot00000000000000import unittest from datetime import datetime from reactivex import operators as ops from reactivex.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 # 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-4.0.4/tests/test_observable/test_takewhile.py000066400000000000000000000305311426446175400223130ustar00rootroot00000000000000import unittest from reactivex import operators as ops from reactivex.testing import ReactiveTest, TestScheduler, 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-4.0.4/tests/test_observable/test_takewithtime.py000066400000000000000000000074561426446175400230470ustar00rootroot00000000000000import unittest from reactivex import operators as ops from reactivex.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 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-4.0.4/tests/test_observable/test_throttlefirst.py000066400000000000000000000062101426446175400232500ustar00rootroot00000000000000import unittest from reactivex import operators as ops from reactivex.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 # 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-4.0.4/tests/test_observable/test_throw.py000066400000000000000000000023731426446175400215040ustar00rootroot00000000000000import unittest from reactivex import throw from reactivex.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 # 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-4.0.4/tests/test_observable/test_timeinterval.py000066400000000000000000000057411426446175400230460ustar00rootroot00000000000000import unittest from datetime import timedelta import reactivex from reactivex import operators as ops from reactivex.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 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 reactivex.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 reactivex.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 reactivex.never().pipe(ops.time_interval()) results = scheduler.start(create) assert results.messages == [] def test_time_interval_default_scheduler(self): import datetime import time xs = reactivex.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-4.0.4/tests/test_observable/test_timeout.py000066400000000000000000000235351426446175400220320ustar00rootroot00000000000000import unittest from datetime import datetime from reactivex import operators as ops from reactivex.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 # 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-4.0.4/tests/test_observable/test_timeoutwithmapper.py000066400000000000000000000163261426446175400241330ustar00rootroot00000000000000import unittest from reactivex import operators as ops from reactivex.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 # 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-4.0.4/tests/test_observable/test_timer.py000066400000000000000000000072761426446175400214700ustar00rootroot00000000000000import unittest import reactivex from reactivex.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 # 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 reactivex.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 reactivex.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 reactivex.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 = reactivex.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 reactivex.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 reactivex.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 reactivex.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 reactivex.timer(1000) results = scheduler.start(create) assert results.messages == [] def test_oneshot_timer_timespan_observer_throws(self): scheduler1 = TestScheduler() xs = reactivex.timer(11) xs.subscribe(lambda x: _raise("ex"), scheduler=scheduler1) self.assertRaises(RxException, scheduler1.start) scheduler2 = TestScheduler() ys = reactivex.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 reactivex.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 reactivex.timer(duetime=300, period=300) results = scheduler.start(create) assert results.messages == [on_next(500, 0), on_next(800, 1)] RxPY-4.0.4/tests/test_observable/test_timestamp.py000066400000000000000000000051541426446175400223440ustar00rootroot00000000000000import unittest from datetime import datetime import reactivex from reactivex import operators as ops from reactivex.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 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 reactivex.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 reactivex.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 reactivex.never().pipe(ops.timestamp()) results = scheduler.start(create) assert results.messages == [] RxPY-4.0.4/tests/test_observable/test_toasync.py000066400000000000000000000077521426446175400220270ustar00rootroot00000000000000import unittest import reactivex from reactivex.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 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 reactivex.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 reactivex.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 reactivex.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 reactivex.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 reactivex.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 reactivex.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 reactivex.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 reactivex.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 reactivex.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 reactivex.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 reactivex.to_async(func, scheduler)(1, 2, 3, 4) res = scheduler.start(create) assert res.messages == [on_error(200, ex)] RxPY-4.0.4/tests/test_observable/test_todict.py000066400000000000000000000067261426446175400216350ustar00rootroot00000000000000import unittest from reactivex import operators as ops from reactivex.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 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-4.0.4/tests/test_observable/test_tofuture.py000066400000000000000000000063331426446175400222160ustar00rootroot00000000000000import asyncio import unittest import reactivex import reactivex.operators as ops from reactivex.internal.exceptions import SequenceContainsNoElementsError from reactivex.subject import Subject from reactivex.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 TestToFuture(unittest.TestCase): def test_await_success(self): loop = asyncio.get_event_loop() result = None async def go(): nonlocal result source = reactivex.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 = reactivex.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 = reactivex.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 = reactivex.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 = reactivex.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 = reactivex.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 reactivex.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-4.0.4/tests/test_observable/test_toiterable.py000066400000000000000000000041701426446175400224700ustar00rootroot00000000000000import unittest from reactivex import operators as ops from reactivex.testing import ReactiveTest, TestScheduler 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-4.0.4/tests/test_observable/test_toset.py000066400000000000000000000035671426446175400215050ustar00rootroot00000000000000import unittest from reactivex import operators as ops from reactivex.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 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-4.0.4/tests/test_observable/test_using.py000066400000000000000000000137761426446175400214770ustar00rootroot00000000000000import unittest import reactivex from reactivex.testing import MockDisposable, 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 # 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 reactivex.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 reactivex.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 reactivex.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 reactivex.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 reactivex.never() return reactivex.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 reactivex.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-4.0.4/tests/test_observable/test_while_do.py000066400000000000000000000120751426446175400221330ustar00rootroot00000000000000import unittest import reactivex from reactivex import operators as ops from reactivex.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 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 reactivex.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-4.0.4/tests/test_observable/test_window.py000066400000000000000000000442061426446175400216510ustar00rootroot00000000000000import unittest import reactivex from reactivex import operators as ops from reactivex.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 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 reactivex.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 reactivex.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 reactivex.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 reactivex.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 reactivex.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 reactivex.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 reactivex.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 reactivex.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-4.0.4/tests/test_observable/test_windowwithcount.py000066400000000000000000000076321426446175400236200ustar00rootroot00000000000000import unittest from reactivex import operators as ops from reactivex.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 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-4.0.4/tests/test_observable/test_windowwithtime.py000066400000000000000000000201161426446175400234160ustar00rootroot00000000000000import unittest import reactivex from reactivex import operators as ops from reactivex.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 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(reactivex.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(reactivex.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-4.0.4/tests/test_observable/test_windowwithtimeorcount.py000066400000000000000000000101061426446175400250260ustar00rootroot00000000000000import unittest from reactivex import operators as ops from reactivex.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 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-4.0.4/tests/test_observable/test_withlatestfrom.py000077500000000000000000000377161426446175400234310ustar00rootroot00000000000000import unittest import reactivex from reactivex import operators as ops from reactivex.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 # Helper function for raising exceptions within lambdas def _raise(ex: str) -> None: raise RxException(ex) class TestWithLatestFrom(unittest.TestCase): def test_with_latest_from_never_never(self): scheduler = TestScheduler() e1 = reactivex.never() e2 = reactivex.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 = reactivex.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 = reactivex.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 = reactivex.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 = reactivex.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 = reactivex.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 = reactivex.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-4.0.4/tests/test_observable/test_zip.py000066400000000000000000000366071426446175400211520ustar00rootroot00000000000000import unittest import reactivex from reactivex import operators as ops from reactivex.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 TestZip(unittest.TestCase): def test_zip_never_never(self): scheduler = TestScheduler() o1 = reactivex.never() o2 = reactivex.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 = reactivex.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 = reactivex.never() def create(): return e2.pipe(ops.zip(e1), ops.map(sum)) results = scheduler.start(create) assert results.messages == [] 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 = reactivex.never() def create(): return e1.pipe(ops.zip(e2), ops.map(sum)) results = scheduler.start(create) assert results.messages == [] 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_non_empty_non_empty_sequential(self): scheduler = TestScheduler() msgs1 = [on_next(210, 1), on_next(215, 2), on_completed(230)] msgs2 = [on_next(240, 1), on_next(245, 3), on_completed(250)] e1 = scheduler.create_cold_observable(msgs1) e2 = scheduler.create_cold_observable(msgs2) def create(): return e1.pipe(ops.zip(e2), ops.map(sum)) results = scheduler.start(create) assert results.messages == [ on_next(200 + 240, 1 + 1), on_next(200 + 245, 2 + 3), on_completed(200 + 245), ] def test_zip_non_empty_partial_sequential(self): scheduler = TestScheduler() msgs1 = [on_next(210, 1), on_next(215, 2), on_completed(230)] msgs2 = [on_next(240, 1), on_completed(250)] e1 = scheduler.create_cold_observable(msgs1) e2 = scheduler.create_cold_observable(msgs2) def create(): return e1.pipe(ops.zip(e2), ops.map(sum)) results = scheduler.start(create) assert results.messages == [on_next(200 + 240, 1 + 1), on_completed(200 + 250)] 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 = reactivex.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 = reactivex.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-4.0.4/tests/test_scheduler/000077500000000000000000000000001426446175400165555ustar00rootroot00000000000000RxPY-4.0.4/tests/test_scheduler/__init__.py000066400000000000000000000000001426446175400206540ustar00rootroot00000000000000RxPY-4.0.4/tests/test_scheduler/test_catchscheduler.py000066400000000000000000000203601426446175400231500ustar00rootroot00000000000000import unittest from datetime import timedelta from reactivex.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-4.0.4/tests/test_scheduler/test_currentthreadscheduler.py000066400000000000000000000157411426446175400247470ustar00rootroot00000000000000import threading import unittest from datetime import timedelta from time import sleep import pytest from reactivex.internal.basic import default_now from reactivex.scheduler import CurrentThreadScheduler 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-4.0.4/tests/test_scheduler/test_eventloop/000077500000000000000000000000001426446175400216275ustar00rootroot00000000000000RxPY-4.0.4/tests/test_scheduler/test_eventloop/__init__.py000066400000000000000000000000001426446175400237260ustar00rootroot00000000000000RxPY-4.0.4/tests/test_scheduler/test_eventloop/test_asyncioscheduler.py000066400000000000000000000046231426446175400266110ustar00rootroot00000000000000import asyncio import os import unittest from datetime import datetime, timedelta import pytest from reactivex.scheduler.eventloop import AsyncIOScheduler CI = os.getenv("CI") is not None class TestAsyncIOScheduler(unittest.TestCase): @pytest.mark.skipif(CI, reason="Flaky test in GitHub Actions") 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=2) # NOTE: may take 1 ms in CI @pytest.mark.skipif(CI, reason="Test is flaky in GitHub Actions") def test_asyncio_schedule_now_units(self): loop = asyncio.get_event_loop() scheduler = AsyncIOScheduler(loop) diff = scheduler.now yield from asyncio.sleep(0.1) diff = scheduler.now - diff assert timedelta(milliseconds=80) < diff < timedelta(milliseconds=180) def test_asyncio_schedule_action(self): loop = asyncio.get_event_loop() async def go(): scheduler = AsyncIOScheduler(loop) ran = False def action(scheduler, state): nonlocal ran ran = True scheduler.schedule(action) await asyncio.sleep(0.1) assert ran is True loop.run_until_complete(go()) def test_asyncio_schedule_action_due(self): loop = asyncio.get_event_loop() async 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) await asyncio.sleep(0.3) 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() async def go(): ran = False scheduler = AsyncIOScheduler(loop) def action(scheduler, state): nonlocal ran ran = True d = scheduler.schedule_relative(0.05, action) d.dispose() await asyncio.sleep(0.3) assert ran is False loop.run_until_complete(go()) RxPY-4.0.4/tests/test_scheduler/test_eventloop/test_asynciothreadsafescheduler.py000066400000000000000000000116701426446175400306400ustar00rootroot00000000000000import asyncio import os import threading import unittest from datetime import datetime, timedelta import pytest from reactivex.scheduler.eventloop import AsyncIOThreadSafeScheduler CI = os.getenv("CI") is not None class TestAsyncIOThreadSafeScheduler(unittest.TestCase): @pytest.mark.skipif(CI, reason="Flaky test in GitHub Actions") 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=2) @pytest.mark.skipif(CI, reason="Flaky test in GitHub Actions") 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) diff = scheduler.now - diff assert timedelta(milliseconds=80) < diff < timedelta(milliseconds=180) def test_asyncio_threadsafe_schedule_action(self): loop = asyncio.get_event_loop() async 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() await asyncio.sleep(0.1) assert ran is True loop.run_until_complete(go()) def test_asyncio_threadsafe_schedule_action_due(self): loop = asyncio.get_event_loop() async 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() await asyncio.sleep(0.3) 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() async 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() await asyncio.sleep(0.3) 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) async def go(): await asyncio.sleep(0.2) 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-4.0.4/tests/test_scheduler/test_eventloop/test_eventletscheduler.py000066400000000000000000000045271426446175400267750ustar00rootroot00000000000000import os import unittest from datetime import datetime, timedelta from time import sleep import pytest from reactivex.scheduler.eventloop import EventletScheduler eventlet = pytest.importorskip("eventlet") CI = os.getenv("CI") is not None class TestEventletScheduler(unittest.TestCase): @pytest.mark.skipif(CI, reason="Flaky test in GitHub Actions") 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) @pytest.mark.skipif(CI, reason="Flaky test in GitHub Actions") 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-4.0.4/tests/test_scheduler/test_eventloop/test_geventscheduler.py000066400000000000000000000033721426446175400264340ustar00rootroot00000000000000import unittest from datetime import datetime, timedelta import pytest from reactivex.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-4.0.4/tests/test_scheduler/test_eventloop/test_ioloopscheduler.py000066400000000000000000000042611426446175400264430ustar00rootroot00000000000000import unittest from datetime import datetime, timedelta from time import sleep import pytest from reactivex.scheduler.eventloop import IOLoopScheduler tornado = pytest.importorskip("tornado") from tornado import ioloop # isort: skip 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-4.0.4/tests/test_scheduler/test_eventloop/test_twistedscheduler.py000066400000000000000000000044001426446175400266200ustar00rootroot00000000000000from datetime import datetime, timedelta from time import sleep import pytest from reactivex.scheduler.eventloop import TwistedScheduler twisted = pytest.importorskip("twisted") from twisted.internet import defer, reactor # isort: skip from twisted.trial import unittest # isort: skip 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-4.0.4/tests/test_scheduler/test_eventloopscheduler.py000066400000000000000000000173331426446175400241070ustar00rootroot00000000000000import os import threading import unittest from datetime import timedelta from time import sleep import pytest from reactivex.internal import DisposedException from reactivex.internal.basic import default_now from reactivex.scheduler import EventLoopScheduler CI = os.getenv("CI") is not None class TestEventLoopScheduler(unittest.TestCase): def test_event_loop_now(self): scheduler = EventLoopScheduler() diff = scheduler.now - default_now() assert abs(diff) < timedelta(milliseconds=5) @pytest.mark.skipif(CI, reason="Flaky test in GitHub Actions") 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-4.0.4/tests/test_scheduler/test_historicalscheduler.py000066400000000000000000000240501426446175400242270ustar00rootroot00000000000000import unittest from datetime import datetime, timedelta from reactivex.internal.constants import UTC_ZERO from reactivex.scheduler import HistoricalScheduler 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-4.0.4/tests/test_scheduler/test_immediatescheduler.py000066400000000000000000000123171426446175400240270ustar00rootroot00000000000000import os import threading import unittest from datetime import timedelta from time import sleep import pytest from reactivex.disposable import Disposable from reactivex.internal.basic import default_now from reactivex.internal.constants import DELTA_ZERO from reactivex.internal.exceptions import WouldBlockException from reactivex.scheduler import ImmediateScheduler CI = os.getenv("CI") is not None 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] @pytest.mark.skipif(CI, reason="Flaky test in GitHub Actions") def test_immediate_now(self): scheduler = ImmediateScheduler() diff = scheduler.now - default_now() assert abs(diff) <= timedelta(milliseconds=1) @pytest.mark.skipif(CI, reason="Flaky test in GitHub Actions") 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-4.0.4/tests/test_scheduler/test_mainloop/000077500000000000000000000000001426446175400214325ustar00rootroot00000000000000RxPY-4.0.4/tests/test_scheduler/test_mainloop/__init__.py000066400000000000000000000000001426446175400235310ustar00rootroot00000000000000RxPY-4.0.4/tests/test_scheduler/test_mainloop/test_gtkscheduler.py000066400000000000000000000077341426446175400255420ustar00rootroot00000000000000import os import threading import unittest from datetime import timedelta from time import sleep import pytest from reactivex.internal.basic import default_now from reactivex.scheduler.mainloop import GtkScheduler gi = pytest.importorskip("gi") from gi.repository import GLib, Gtk # isort: skip gi.require_version("Gtk", "3.0") # 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-4.0.4/tests/test_scheduler/test_mainloop/test_pygamescheduler.py000066400000000000000000000046171426446175400262340ustar00rootroot00000000000000import unittest from datetime import timedelta from time import sleep import pytest from reactivex.internal.basic import default_now from reactivex.scheduler.mainloop import PyGameScheduler 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-4.0.4/tests/test_scheduler/test_mainloop/test_qtscheduler_pyqt5.py000066400000000000000000000103721426446175400265330ustar00rootroot00000000000000import threading from datetime import timedelta from time import sleep import pytest from reactivex.internal.basic import default_now from reactivex.scheduler.mainloop import QtScheduler QtCore = pytest.importorskip("PyQt5.QtCore") @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-4.0.4/tests/test_scheduler/test_mainloop/test_qtscheduler_pyside2.py000066400000000000000000000104161426446175400270270ustar00rootroot00000000000000import threading from datetime import timedelta from time import sleep import pytest from reactivex.internal.basic import default_now from reactivex.scheduler.mainloop import QtScheduler QtCore = pytest.importorskip("PySide2.QtCore") @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-4.0.4/tests/test_scheduler/test_mainloop/test_tkinterscheduler.py000066400000000000000000000054531426446175400264310ustar00rootroot00000000000000import os import unittest from datetime import timedelta from time import sleep import pytest from reactivex.internal.basic import default_now from reactivex.scheduler.mainloop import TkinterScheduler tkinter = pytest.importorskip("tkinter") try: root = tkinter.Tk() root.withdraw() # Don't actually draw anything display = True except Exception: display = False CI = os.getenv("CI") is not None @pytest.mark.skipif("display == False") class TestTkinterScheduler(unittest.TestCase): @pytest.mark.skipif(CI, reason="Flaky test in GitHub Actions") def test_tkinter_schedule_now(self): scheduler = TkinterScheduler(root) res = scheduler.now - default_now() assert abs(res) <= timedelta(milliseconds=1) @pytest.mark.skipif(CI, reason="Flaky test in GitHub Actions") 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-4.0.4/tests/test_scheduler/test_mainloop/test_wxscheduler.py000066400000000000000000000067621426446175400254130ustar00rootroot00000000000000import unittest from datetime import timedelta from time import sleep import pytest from reactivex.internal.basic import default_now from reactivex.scheduler.mainloop import WxScheduler 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-4.0.4/tests/test_scheduler/test_newthreadscheduler.py000066400000000000000000000053601426446175400240520ustar00rootroot00000000000000import os import threading import unittest from datetime import timedelta from time import sleep import pytest from reactivex.internal.basic import default_now from reactivex.scheduler import NewThreadScheduler CI = os.getenv("CI") is not None class TestNewThreadScheduler(unittest.TestCase): def test_new_thread_now(self): scheduler = NewThreadScheduler() diff = scheduler.now - default_now() assert abs(diff) < timedelta(milliseconds=5) @pytest.mark.skipif(CI, reason="Flaky test in GitHub Actions") 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.2) 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: int): 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: int): 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-4.0.4/tests/test_scheduler/test_scheduleditem.py000066400000000000000000000042531426446175400230110ustar00rootroot00000000000000import unittest from datetime import timedelta from reactivex.disposable import Disposable from reactivex.internal.basic import default_now from reactivex.scheduler.scheduleditem import ScheduledItem from reactivex.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-4.0.4/tests/test_scheduler/test_scheduler.py000066400000000000000000000017011426446175400221430ustar00rootroot00000000000000import unittest from reactivex.internal.constants import DELTA_ZERO, UTC_ZERO from reactivex.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-4.0.4/tests/test_scheduler/test_threadpoolscheduler.py000066400000000000000000000050071426446175400242300ustar00rootroot00000000000000import os import threading import unittest from datetime import timedelta from time import sleep import pytest from reactivex.internal.basic import default_now from reactivex.scheduler import ThreadPoolScheduler thread_pool_scheduler = ThreadPoolScheduler() CI = os.getenv("CI") is not None class TestThreadPoolScheduler(unittest.TestCase): @pytest.mark.skipif(CI, reason="Flaky test in GitHub Actions") 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-4.0.4/tests/test_scheduler/test_timeoutscheduler.py000066400000000000000000000054451426446175400235630ustar00rootroot00000000000000import os import threading import unittest from datetime import timedelta from time import sleep import pytest from reactivex.internal.basic import default_now from reactivex.scheduler import TimeoutScheduler CI = os.getenv("CI") is not None 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] @pytest.mark.skipif(CI, reason="Flaky test in GitHub Actions") def test_timeout_now(self): scheduler = TimeoutScheduler() diff = scheduler.now - default_now() assert abs(diff) < timedelta(milliseconds=1) @pytest.mark.skipif(CI, reason="Flaky test in GitHub Actions") 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-4.0.4/tests/test_scheduler/test_trampolinescheduler.py000066400000000000000000000124641426446175400242460ustar00rootroot00000000000000import os import unittest from datetime import timedelta from time import sleep import pytest from reactivex.internal.basic import default_now from reactivex.scheduler import TrampolineScheduler CI = os.getenv("CI") is not None class TestTrampolineScheduler(unittest.TestCase): @pytest.mark.skipif(CI, reason="Flaky test in GitHub Actions") def test_trampoline_now(self): scheduler = TrampolineScheduler() diff = scheduler.now - default_now() assert abs(diff) < timedelta(milliseconds=1) @pytest.mark.skipif(CI, reason="Flaky test in GitHub Actions") 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-4.0.4/tests/test_scheduler/test_virtualtimescheduler.py000066400000000000000000000041631426446175400244360ustar00rootroot00000000000000import unittest import pytest from reactivex.internal import ArgumentOutOfRangeException from reactivex.internal.constants import DELTA_ZERO, UTC_ZERO from reactivex.scheduler import VirtualTimeScheduler 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-4.0.4/tests/test_subject/000077500000000000000000000000001426446175400162365ustar00rootroot00000000000000RxPY-4.0.4/tests/test_subject/__init__.py000066400000000000000000000000001426446175400203350ustar00rootroot00000000000000RxPY-4.0.4/tests/test_subject/test_asyncsubject.py000066400000000000000000000265761426446175400223640ustar00rootroot00000000000000import pytest from reactivex.internal.exceptions import DisposedException from reactivex.subject import AsyncSubject from reactivex.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 # 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-4.0.4/tests/test_subject/test_behaviorsubject.py000066400000000000000000000305771426446175400230420ustar00rootroot00000000000000import pytest from reactivex.internal.exceptions import DisposedException from reactivex.subject import BehaviorSubject from reactivex.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 # 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-4.0.4/tests/test_subject/test_replaysubject.py000066400000000000000000000413731426446175400225330ustar00rootroot00000000000000import sys import pytest from reactivex.internal.exceptions import DisposedException from reactivex.subject import ReplaySubject from reactivex.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 # 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-4.0.4/tests/test_subject/test_subject.py000066400000000000000000000212751426446175400213150ustar00rootroot00000000000000from reactivex.subject import Subject from reactivex.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 # 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-4.0.4/tests/test_testing/000077500000000000000000000000001426446175400162545ustar00rootroot00000000000000RxPY-4.0.4/tests/test_testing/__init__.py000066400000000000000000000000001426446175400203530ustar00rootroot00000000000000RxPY-4.0.4/tests/test_testing/test_marbles.py000066400000000000000000000137641426446175400213250ustar00rootroot00000000000000import unittest from reactivex.testing.marbles import marbles_testing from reactivex.testing.reactivetest import ReactiveTest # from reactivex.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-4.0.4/tests/test_version.py000066400000000000000000000002221426446175400166320ustar00rootroot00000000000000import unittest from reactivex import __version__ class VersionTest(unittest.TestCase): def test_version(self): assert __version__