├── .coveragerc ├── .flake8 ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── lock.yml └── workflows │ ├── python-package.yml │ └── python-publish.yml ├── .gitignore ├── .pre-commit-config.yaml ├── .pylintrc ├── LICENSE ├── README.rst ├── authors.txt ├── changes.md ├── docs ├── .rstcheck.cfg ├── Makefile ├── additional_reading.rst ├── conf.py ├── contributing.rst ├── get_started.rst ├── index.rst ├── installation.rst ├── license.rst ├── migration.rst ├── operators.rst ├── rationale.rst ├── reference.rst ├── reference_observable.rst ├── reference_observable_factory.rst ├── reference_operators.rst ├── reference_scheduler.rst ├── reference_subject.rst ├── reference_typing.rst ├── requirements.txt └── testing.rst ├── examples ├── asyncio │ ├── await.py │ ├── toasyncgenerator.py │ └── toasynciterator.py ├── autocomplete │ ├── autocomplete.js │ ├── autocomplete.py │ ├── autocomplete_asyncio.py │ ├── bottle_autocomplete.py │ └── index.html ├── chess │ ├── chess.py │ ├── chess_bishop_black.png │ ├── chess_bishop_white.png │ ├── chess_king_black.png │ ├── chess_king_white.png │ ├── chess_knight_black.png │ ├── chess_knight_white.png │ ├── chess_pawn_black.png │ ├── chess_pawn_white.png │ ├── chess_queen_black.png │ ├── chess_queen_white.png │ ├── chess_rook_black.png │ └── chess_rook_white.png ├── errors │ └── failing.py ├── konamicode │ ├── index.html │ ├── konamicode.js │ └── konamicode.py ├── marbles │ ├── frommarbles_error.py │ ├── frommarbles_flatmap.py │ ├── frommarbles_lookup.py │ ├── frommarbles_merge.py │ ├── hot_datetime.py │ ├── testing_debounce.py │ ├── testing_flatmap.py │ └── tomarbles.py ├── parallel │ └── timer.py ├── statistics │ └── statistics.py └── timeflies │ ├── timeflies_gtk.py │ ├── timeflies_qt.py │ ├── timeflies_tkinter.py │ └── timeflies_wx.py ├── notebooks ├── Getting Started.ipynb └── reactivex.io │ ├── A Decision Tree of Observable Operators. Part I - Creation.ipynb │ ├── Marble Diagrams.ipynb │ ├── Part II - Combination.ipynb │ ├── Part III - Transformation.ipynb │ ├── Part IV - Grouping, Buffering, Delaying, misc.ipynb │ ├── Part V - Consolidating Streams.ipynb │ ├── Part VI - Entirety Operations.ipynb │ ├── Part VII - Meta Operations.ipynb │ ├── Part VIII - Hot & Cold.ipynb │ ├── RxPy - Basic Usage and Mechanics.ipynb │ ├── assets │ ├── Article about Publish - Refcount.ipynb │ ├── img │ │ ├── publishConnect.png │ │ └── threading.png │ └── js │ │ └── ipython_notebook_toc.js │ └── startup.py ├── poetry.lock ├── pyproject.toml ├── pyrightconfig.json ├── reactivex ├── __init__.py ├── _version.py ├── abc │ ├── __init__.py │ ├── disposable.py │ ├── observable.py │ ├── observer.py │ ├── periodicscheduler.py │ ├── scheduler.py │ ├── startable.py │ └── subject.py ├── disposable │ ├── __init__.py │ ├── booleandisposable.py │ ├── compositedisposable.py │ ├── disposable.py │ ├── multipleassignmentdisposable.py │ ├── refcountdisposable.py │ ├── scheduleddisposable.py │ ├── serialdisposable.py │ └── singleassignmentdisposable.py ├── internal │ ├── __init__.py │ ├── basic.py │ ├── concurrency.py │ ├── constants.py │ ├── exceptions.py │ ├── priorityqueue.py │ └── utils.py ├── notification.py ├── observable │ ├── __init__.py │ ├── amb.py │ ├── case.py │ ├── catch.py │ ├── combinelatest.py │ ├── concat.py │ ├── connectableobservable.py │ ├── defer.py │ ├── empty.py │ ├── forkjoin.py │ ├── fromcallback.py │ ├── fromfuture.py │ ├── fromiterable.py │ ├── generate.py │ ├── generatewithrelativetime.py │ ├── groupedobservable.py │ ├── ifthen.py │ ├── interval.py │ ├── marbles.py │ ├── merge.py │ ├── never.py │ ├── observable.py │ ├── onerrorresumenext.py │ ├── range.py │ ├── repeat.py │ ├── returnvalue.py │ ├── start.py │ ├── startasync.py │ ├── throw.py │ ├── timer.py │ ├── toasync.py │ ├── using.py │ ├── withlatestfrom.py │ └── zip.py ├── observer │ ├── __init__.py │ ├── autodetachobserver.py │ ├── observeonobserver.py │ ├── observer.py │ └── scheduledobserver.py ├── operators │ ├── __init__.py │ ├── _all.py │ ├── _amb.py │ ├── _asobservable.py │ ├── _average.py │ ├── _buffer.py │ ├── _bufferwithtime.py │ ├── _bufferwithtimeorcount.py │ ├── _catch.py │ ├── _combinelatest.py │ ├── _concat.py │ ├── _contains.py │ ├── _count.py │ ├── _debounce.py │ ├── _defaultifempty.py │ ├── _delay.py │ ├── _delaysubscription.py │ ├── _delaywithmapper.py │ ├── _dematerialize.py │ ├── _distinct.py │ ├── _distinctuntilchanged.py │ ├── _do.py │ ├── _dowhile.py │ ├── _elementatordefault.py │ ├── _exclusive.py │ ├── _expand.py │ ├── _filter.py │ ├── _finallyaction.py │ ├── _find.py │ ├── _first.py │ ├── _firstordefault.py │ ├── _flatmap.py │ ├── _forkjoin.py │ ├── _groupby.py │ ├── _groupbyuntil.py │ ├── _groupjoin.py │ ├── _ignoreelements.py │ ├── _isempty.py │ ├── _join.py │ ├── _last.py │ ├── _lastordefault.py │ ├── _map.py │ ├── _materialize.py │ ├── _max.py │ ├── _maxby.py │ ├── _merge.py │ ├── _min.py │ ├── _minby.py │ ├── _multicast.py │ ├── _observeon.py │ ├── _onerrorresumenext.py │ ├── _pairwise.py │ ├── _partition.py │ ├── _pluck.py │ ├── _publish.py │ ├── _publishvalue.py │ ├── _reduce.py │ ├── _repeat.py │ ├── _replay.py │ ├── _retry.py │ ├── _sample.py │ ├── _scan.py │ ├── _sequenceequal.py │ ├── _single.py │ ├── _singleordefault.py │ ├── _skip.py │ ├── _skiplast.py │ ├── _skiplastwithtime.py │ ├── _skipuntil.py │ ├── _skipuntilwithtime.py │ ├── _skipwhile.py │ ├── _skipwithtime.py │ ├── _slice.py │ ├── _some.py │ ├── _startswith.py │ ├── _subscribeon.py │ ├── _sum.py │ ├── _switchlatest.py │ ├── _take.py │ ├── _takelast.py │ ├── _takelastbuffer.py │ ├── _takelastwithtime.py │ ├── _takeuntil.py │ ├── _takeuntilwithtime.py │ ├── _takewhile.py │ ├── _takewithtime.py │ ├── _throttlefirst.py │ ├── _timeinterval.py │ ├── _timeout.py │ ├── _timeoutwithmapper.py │ ├── _timestamp.py │ ├── _todict.py │ ├── _tofuture.py │ ├── _toiterable.py │ ├── _tomarbles.py │ ├── _toset.py │ ├── _whiledo.py │ ├── _window.py │ ├── _windowwithcount.py │ ├── _windowwithtime.py │ ├── _windowwithtimeorcount.py │ ├── _withlatestfrom.py │ ├── _zip.py │ └── connectable │ │ ├── __init__.py │ │ └── _refcount.py ├── pipe.py ├── py.typed ├── run.py ├── scheduler │ ├── __init__.py │ ├── catchscheduler.py │ ├── currentthreadscheduler.py │ ├── eventloop │ │ ├── __init__.py │ │ ├── asyncioscheduler.py │ │ ├── asynciothreadsafescheduler.py │ │ ├── eventletscheduler.py │ │ ├── geventscheduler.py │ │ ├── ioloopscheduler.py │ │ └── twistedscheduler.py │ ├── eventloopscheduler.py │ ├── historicalscheduler.py │ ├── immediatescheduler.py │ ├── mainloop │ │ ├── __init__.py │ │ ├── gtkscheduler.py │ │ ├── pygamescheduler.py │ │ ├── qtscheduler.py │ │ ├── tkinterscheduler.py │ │ └── wxscheduler.py │ ├── newthreadscheduler.py │ ├── periodicscheduler.py │ ├── scheduleditem.py │ ├── scheduler.py │ ├── threadpoolscheduler.py │ ├── timeoutscheduler.py │ ├── trampoline.py │ ├── trampolinescheduler.py │ └── virtualtimescheduler.py ├── subject │ ├── __init__.py │ ├── asyncsubject.py │ ├── behaviorsubject.py │ ├── innersubscription.py │ ├── replaysubject.py │ └── subject.py ├── testing │ ├── __init__.py │ ├── coldobservable.py │ ├── hotobservable.py │ ├── marbles.py │ ├── mockdisposable.py │ ├── mockobserver.py │ ├── reactivetest.py │ ├── recorded.py │ ├── subscription.py │ └── testscheduler.py └── typing.py ├── setup.cfg └── tests ├── __init__.py ├── test_core ├── __init__.py ├── test_notification.py ├── test_observer.py └── test_priorityqueue.py ├── test_disposables ├── __init__.py └── test_disposable.py ├── test_integration ├── test_concat_repeat.py └── test_group_reduce.py ├── test_observable ├── __init__.py ├── test_all.py ├── test_amb.py ├── test_asobservable.py ├── test_average.py ├── test_blocking │ ├── __init__.py │ └── test_blocking.py ├── test_buffer.py ├── test_bufferwithcount.py ├── test_bufferwithtime.py ├── test_bufferwithtimeorcount.py ├── test_case.py ├── test_catch.py ├── test_combinelatest.py ├── test_concat.py ├── test_concatmap.py ├── test_connectableobservable.py ├── test_contains.py ├── test_count.py ├── test_create.py ├── test_debounce.py ├── test_defaultifempty.py ├── test_defer.py ├── test_delay.py ├── test_delaywithmapper.py ├── test_distinct.py ├── test_distinctuntilchanged.py ├── test_doaction.py ├── test_dowhile.py ├── test_elementat.py ├── test_empty.py ├── test_expand.py ├── test_filter.py ├── test_finally.py ├── test_find.py ├── test_first.py ├── test_firstordefault.py ├── test_flatmap.py ├── test_flatmap_async.py ├── test_forin.py ├── test_forkjoin.py ├── test_fromcallback.py ├── test_fromfuture.py ├── test_fromiterable.py ├── test_generate.py ├── test_generatewithrelativetime.py ├── test_groupby.py ├── test_groupjoin.py ├── test_ifthen.py ├── test_ignoreelements.py ├── test_interval.py ├── test_isempty.py ├── test_join.py ├── test_last.py ├── test_lastordefault.py ├── test_map.py ├── test_marbles.py ├── test_materialize.py ├── test_max.py ├── test_maxby.py ├── test_merge.py ├── test_min.py ├── test_minby.py ├── test_multicast.py ├── test_never.py ├── test_observeon.py ├── test_of.py ├── test_onerrorresumenext.py ├── test_pairwise.py ├── test_partition.py ├── test_pluck.py ├── test_publish.py ├── test_publishvalue.py ├── test_range.py ├── test_reduce.py ├── test_repeat.py ├── test_replay.py ├── test_retry.py ├── test_returnvalue.py ├── test_sample.py ├── test_scan.py ├── test_sequenceequal.py ├── test_single.py ├── test_singleordefault.py ├── test_skip.py ├── test_skiplast.py ├── test_skiplastwithtime.py ├── test_skipuntil.py ├── test_skipuntilwithtime.py ├── test_skipwhile.py ├── test_skipwithtime.py ├── test_slice.py ├── test_some.py ├── test_starmap.py ├── test_start.py ├── test_startwith.py ├── test_subscribeon.py ├── test_sum.py ├── test_switchlatest.py ├── test_switchmap.py ├── test_switchmapindexed.py ├── test_take.py ├── test_takelast.py ├── test_takelastbuffer.py ├── test_takelastwithtime.py ├── test_takeuntil.py ├── test_takeuntilwithtime.py ├── test_takewhile.py ├── test_takewithtime.py ├── test_throttlefirst.py ├── test_throw.py ├── test_timeinterval.py ├── test_timeout.py ├── test_timeoutwithmapper.py ├── test_timer.py ├── test_timestamp.py ├── test_toasync.py ├── test_todict.py ├── test_tofuture.py ├── test_toiterable.py ├── test_toset.py ├── test_using.py ├── test_while_do.py ├── test_window.py ├── test_windowwithcount.py ├── test_windowwithtime.py ├── test_windowwithtimeorcount.py ├── test_withlatestfrom.py └── test_zip.py ├── test_scheduler ├── __init__.py ├── test_catchscheduler.py ├── test_currentthreadscheduler.py ├── test_eventloop │ ├── __init__.py │ ├── test_asyncioscheduler.py │ ├── test_asynciothreadsafescheduler.py │ ├── test_eventletscheduler.py │ ├── test_geventscheduler.py │ ├── test_ioloopscheduler.py │ └── test_twistedscheduler.py ├── test_eventloopscheduler.py ├── test_historicalscheduler.py ├── test_immediatescheduler.py ├── test_mainloop │ ├── __init__.py │ ├── test_gtkscheduler.py │ ├── test_pygamescheduler.py │ ├── test_qtscheduler_pyqt5.py │ ├── test_qtscheduler_pyside2.py │ ├── test_tkinterscheduler.py │ └── test_wxscheduler.py ├── test_newthreadscheduler.py ├── test_scheduleditem.py ├── test_scheduler.py ├── test_threadpoolscheduler.py ├── test_timeoutscheduler.py ├── test_trampolinescheduler.py └── test_virtualtimescheduler.py ├── test_subject ├── __init__.py ├── test_asyncsubject.py ├── test_behaviorsubject.py ├── test_replaysubject.py └── test_subject.py ├── test_testing ├── __init__.py └── test_marbles.py └── test_version.py /.coveragerc: -------------------------------------------------------------------------------- 1 | [report] 2 | omit = 3 | */python?.?/* 4 | */site-packages/nose/* 5 | exclude_lines = 6 | pragma: no cover 7 | return NotImplemented 8 | raise NotImplementedError 9 | \.\.\. 10 | [xml] 11 | output = coverage.xml 12 | -------------------------------------------------------------------------------- /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | ignore = D203,W503 3 | exclude = .git,__pycache__,docs/source/conf.py,old,build,dist,tests 4 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Set the default behavior, in case people don't have core.autocrlf set. 2 | * text=auto 3 | 4 | # Explicitly declare text files you want to always be normalized and converted 5 | # to native line endings on checkout. 6 | *.py text 7 | *.js text 8 | *.xml text 9 | *.yml text 10 | *.md text 11 | 12 | # Declare files that will always have CRLF line endings on checkout. 13 | *.sln text eol=crlf 14 | *.pyproj text eol=crlf 15 | 16 | # Denote all files that are truly binary and should not be modified. 17 | *.png binary 18 | *.jpg binary -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 16 | **Expected behavior** 17 | A clear and concise description of what you expected to happen. 18 | 19 | **Code or Screenshots** 20 | If applicable, add a minimal and self contained code example or screenshots to help explain your problem. 21 | 22 | ```python 23 | def foo(self) -> str: 24 | return 3 25 | ``` 26 | 27 | **Additional context** 28 | Add any other context about the problem here. 29 | 30 | - OS [e.g. Windows] 31 | - RxPY version [e.g 4.0.0] 32 | - Python version [e.g. 3.10.2] 33 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen, and also include a minimal code example if applicable. 15 | 16 | ```python 17 | def foo(self) -> str: 18 | return 3 19 | ``` 20 | 21 | **Describe alternatives you've considered** 22 | A clear and concise description of any alternative solutions or features you've considered. 23 | 24 | **Additional context** 25 | Add any other context or screenshots about the feature request here. 26 | -------------------------------------------------------------------------------- /.github/lock.yml: -------------------------------------------------------------------------------- 1 | # Configuration for Lock Threads - https://github.com/dessant/lock-threads 2 | 3 | # Number of days of inactivity before a closed issue or pull request is locked 4 | daysUntilLock: 365 5 | 6 | # Skip issues and pull requests created before a given timestamp. Timestamp must 7 | # follow ISO 8601 (`YYYY-MM-DD`). Set to `false` to disable 8 | skipCreatedBefore: false 9 | 10 | # Issues and pull requests with these labels will be ignored. Set to `[]` to disable 11 | exemptLabels: [] 12 | 13 | # Label to add before locking, such as `outdated`. Set to `false` to disable 14 | lockLabel: false 15 | 16 | # Comment to post before locking. Set to `false` to disable 17 | lockComment: > 18 | This thread has been automatically locked since there has not been 19 | any recent activity after it was closed. Please open a new issue for 20 | related bugs. 21 | 22 | # Assign `resolved` as the reason for locking. Set to `false` to disable 23 | setLockReason: true 24 | 25 | # Limit to only `issues` or `pulls` 26 | # only: issues 27 | 28 | # Optionally, specify configuration settings just for `issues` or `pulls` 29 | # issues: 30 | # exemptLabels: 31 | # - help-wanted 32 | # lockLabel: outdated 33 | 34 | # pulls: 35 | # daysUntilLock: 30 36 | 37 | # Repository to extend settings from 38 | # _extends: repo 39 | -------------------------------------------------------------------------------- /.github/workflows/python-publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish Package 2 | 3 | on: 4 | release: 5 | types: [created] 6 | 7 | jobs: 8 | publish: 9 | runs-on: ubuntu-latest 10 | name: "Publish library" 11 | steps: 12 | - name: Check out 13 | uses: actions/checkout@v4 14 | with: 15 | token: "${{ secrets.GITHUB_TOKEN }}" 16 | fetch-depth: 0 17 | 18 | - name: Setup Python Env 19 | uses: actions/setup-python@v5 20 | with: 21 | python-version: "3.12" 22 | 23 | - name: Install dependencies 24 | run: pip install poetry dunamai 25 | 26 | - name: Set version 27 | run: | 28 | RX_VERSION=$(dunamai from any --no-metadata --style pep440) 29 | poetry version $RX_VERSION 30 | echo "__version__ = \"$RX_VERSION\"" > reactivex/_version.py 31 | 32 | - name: Build package 33 | run: poetry build 34 | 35 | - name: Release to PyPI 36 | run: | 37 | poetry publish -u __token__ -p ${{ secrets.PYPI_API_TOKEN }} || echo 'Version exists' -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | 3 | # C extensions 4 | *.so 5 | 6 | # Packages 7 | *.egg 8 | *.egg-info 9 | dist 10 | build 11 | eggs 12 | .eggs 13 | parts 14 | bin 15 | var 16 | sdist 17 | develop-eggs 18 | .installed.cfg 19 | lib 20 | lib64 21 | *tmp_dir 22 | 23 | # Installer logs 24 | pip-log.txt 25 | 26 | # Unit test / coverage reports 27 | .coverage 28 | .tox 29 | nosetests.xml 30 | coverage.xml 31 | htmlcov 32 | .mypy_cache 33 | .pytest_cache 34 | 35 | # Virtual env 36 | venv 37 | 38 | # Translations 39 | *.mo 40 | 41 | # Mr Developer 42 | .mr.developer.cfg 43 | .project 44 | .pydevproject 45 | 46 | # Visual studio 47 | *.suo 48 | *.DotSettings.user 49 | TestResults/ 50 | 51 | # Log files 52 | *.log 53 | Rx.pyperf 54 | *.coverage 55 | UpgradeLog.htm 56 | TestResults/Rx.TE.Tests.mdf 57 | TestResults/Rx.TE.Tests_log.ldf 58 | *.user 59 | 60 | # Cloud9 61 | .c9 62 | 63 | # PyCharm 64 | .idea 65 | .zedstate 66 | 67 | # Spyder IDE 68 | .spyproject/ 69 | 70 | .ipynb_checkpoints 71 | .cache/ 72 | .vscode/ 73 | .noseids 74 | _build 75 | 76 | # Mac OS 77 | .DS_Store 78 | 79 | # Type checkers 80 | .pyre 81 | 82 | .ionide/ 83 | 84 | .python-version 85 | __pycache__ -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - hooks: 3 | - args: 4 | - --remove-all-unused-imports 5 | - --in-place 6 | id: autoflake 7 | repo: https://github.com/PyCQA/autoflake 8 | rev: v2.3.1 9 | - hooks: 10 | - id: isort 11 | repo: https://github.com/timothycrosley/isort 12 | rev: 5.13.2 13 | - hooks: 14 | - id: black 15 | repo: https://github.com/psf/black 16 | rev: 24.10.0 17 | - hooks: 18 | - id: flake8 19 | exclude: (^docs/|^examples/|^notebooks/|^tests/) 20 | repo: https://github.com/PyCQA/flake8 21 | rev: 7.1.1 22 | - hooks: 23 | - id: pyright 24 | name: pyright 25 | entry: pyright 26 | language: node 27 | pass_filenames: false 28 | types: [python] 29 | additional_dependencies: ["pyright@1.1.388"] 30 | repo: local 31 | - hooks: 32 | - id: mypy 33 | exclude: (^docs/|^examples/|^notebooks/|^tests/|^reactivex/operators/_\w.*\.py$) 34 | repo: https://github.com/pre-commit/mirrors-mypy 35 | rev: v1.13.0 36 | 37 | -------------------------------------------------------------------------------- /.pylintrc: -------------------------------------------------------------------------------- 1 | [BASIC] 2 | 3 | good-names=n,x,xs,ys,zs,ex,obv,obs,T_in,T_out,do,_ 4 | 5 | [MESSAGES CONTROL] 6 | 7 | disable=missing-docstring,unused-argument,invalid-name -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | # The MIT License 2 | 3 | Copyright 2013-2022, Dag Brattli, Microsoft Corp., and Contributors. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /authors.txt: -------------------------------------------------------------------------------- 1 | ReactiveX for Python maintainers 2 | 3 | Dag Brattli @dbrattli 4 | Erik Kemperman, @erikkemperman 5 | Jérémie Fache, @jcafhe 6 | Romain Picard, @MainRo 7 | -------------------------------------------------------------------------------- /docs/.rstcheck.cfg: -------------------------------------------------------------------------------- 1 | [rstcheck] 2 | ignore_directives= 3 | ignore_roles= 4 | 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".) 5 | report=warning -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SPHINXPROJ = RxPY 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -------------------------------------------------------------------------------- /docs/additional_reading.rst: -------------------------------------------------------------------------------- 1 | .. _additional_reading: 2 | 3 | Additional Reading 4 | ================== 5 | 6 | Open Material 7 | ------------- 8 | 9 | The RxPY source repository contains `example notebooks 10 | `_. 11 | 12 | The official ReactiveX website contains additional tutorials and documentation: 13 | 14 | * `Introduction `_ 15 | * `Tutorials `_ 16 | * `Operators `_ 17 | 18 | Several commercial contents have their associated example code available freely: 19 | 20 | * `Packt Reactive Programming in Python `_ 21 | 22 | RxPY 3.0.0 has removed support for backpressure here are the known community projects supporting backpressure: 23 | 24 | * `rxbackpressure rxpy extension `_ 25 | * `rxpy_backpressure observer decorators `_ 26 | 27 | Commercial Material 28 | -------------------- 29 | 30 | **O\'Reilly Video** 31 | 32 | O'Reilly has published the video *Reactive Python for Data Science* which is 33 | available on both the `O\'Reilly Store 34 | `_ as well as `O\'Reilly 35 | Safari `_. 36 | This video teaches RxPY from scratch with applications towards data science, but 37 | should be helpful for anyone seeking to learn RxPY and reactive programming. 38 | 39 | **Packt Video** 40 | 41 | Packt has published the video *Reactive Programming in Python*, available on 42 | `Packt store 43 | `_. 44 | This video teaches how to write reactive GUI and network applications. 45 | 46 | -------------------------------------------------------------------------------- /docs/contributing.rst: -------------------------------------------------------------------------------- 1 | Contributing 2 | ============= 3 | 4 | You can contribute by reviewing and sending feedback on code checkins, 5 | suggesting and trying out new features as they are implemented, register issues 6 | and help us verify fixes as they are checked in, as well as submit code fixes or 7 | code contributions of your own. 8 | 9 | The main repository is at `ReactiveX/RxPY `_. 10 | Please register any issues to `ReactiveX/RxPY/issues `_. 11 | 12 | Please submit any pull requests against the 13 | `master `_ branch. 14 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. RxPY documentation master file, created by 2 | sphinx-quickstart on Sat Feb 17 23:30:45 2018. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | ReactiveX for Python (RxPY) 7 | =========================== 8 | 9 | ReactiveX for Python (RxPY) is a library for composing asynchronous and 10 | event-based programs using observable collections and pipable query 11 | operators in Python. 12 | 13 | .. toctree:: 14 | :maxdepth: 2 15 | 16 | installation 17 | rationale 18 | get_started 19 | testing 20 | migration 21 | operators 22 | additional_reading 23 | reference 24 | contributing 25 | license 26 | 27 | 28 | -------------------------------------------------------------------------------- /docs/installation.rst: -------------------------------------------------------------------------------- 1 | .. Installation 2 | 3 | Installation 4 | ============ 5 | 6 | ReactiveX for Python (RxPY) v4.x runs on `Python 7 | `__ 3. To install: 8 | 9 | .. code:: console 10 | 11 | pip3 install reactivex 12 | 13 | RxPY v3.x runs on `Python `__ 3. To install RxPY: 14 | 15 | .. code:: console 16 | 17 | pip3 install rx 18 | 19 | For Python 2.x you need to use version 1.6 20 | 21 | .. code:: console 22 | 23 | pip install rx==1.6.1 24 | -------------------------------------------------------------------------------- /docs/license.rst: -------------------------------------------------------------------------------- 1 | The MIT License 2 | =============== 3 | 4 | Copyright 2013-2022, Dag Brattli, Microsoft Corp., and Contributors. 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy of 7 | this software and associated documentation files (the "Software"), to deal in 8 | the Software without restriction, including without limitation the rights to 9 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 10 | the Software, and to permit persons to whom the Software is furnished to do so, 11 | subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 18 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 19 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 20 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 21 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/rationale.rst: -------------------------------------------------------------------------------- 1 | .. Rationale 2 | 3 | Rationale 4 | ========== 5 | 6 | Reactive Extensions for Python (RxPY) is a set of libraries for composing 7 | asynchronous and event-based programs using observable sequences and pipable 8 | query operators in Python. Using Rx, developers represent asynchronous data 9 | streams with Observables, query asynchronous data streams using operators, and 10 | parameterize concurrency in data/event streams using Schedulers. 11 | 12 | Using Rx, you can represent multiple asynchronous data streams (that come from 13 | diverse sources, e.g., stock quote, Tweets, computer events, web service 14 | requests, etc.), and subscribe to the event stream using the Observer object. 15 | The Observable notifies the subscribed Observer instance whenever an event 16 | occurs. You can put various transformations in-between the source Observable and 17 | the consuming Observer as well. 18 | 19 | Because Observable sequences are data streams, you can query them using standard 20 | query operators implemented as functions that can be chained with the pipe 21 | operator. Thus you can filter, map, reduce, compose and perform time-based 22 | operations on multiple events easily by using these operators. In 23 | addition, there are a number of other reactive stream specific operators that 24 | allow powerful queries to be written. Cancellation, exceptions, and 25 | synchronization are also handled gracefully by using dedicated operators. 26 | -------------------------------------------------------------------------------- /docs/reference.rst: -------------------------------------------------------------------------------- 1 | .. reference: 2 | 3 | Reference 4 | ========== 5 | 6 | 7 | .. toctree:: 8 | :name: reference 9 | 10 | Observable Factory 11 | Observable 12 | Subject 13 | Scheduler 14 | Operators 15 | Typing 16 | -------------------------------------------------------------------------------- /docs/reference_observable.rst: -------------------------------------------------------------------------------- 1 | .. _reference_observable: 2 | 3 | Observable 4 | =========== 5 | 6 | .. autoclass:: reactivex.Observable 7 | :members: 8 | -------------------------------------------------------------------------------- /docs/reference_observable_factory.rst: -------------------------------------------------------------------------------- 1 | .. _reference_observable_factory: 2 | 3 | Observable Factory 4 | ===================== 5 | 6 | .. automodule:: reactivex 7 | :members: 8 | -------------------------------------------------------------------------------- /docs/reference_operators.rst: -------------------------------------------------------------------------------- 1 | .. _reference_operators: 2 | 3 | Operators 4 | ========= 5 | 6 | .. automodule:: reactivex.operators 7 | :members: 8 | -------------------------------------------------------------------------------- /docs/reference_scheduler.rst: -------------------------------------------------------------------------------- 1 | .. _reference_scheduler: 2 | 3 | Schedulers 4 | =========== 5 | 6 | .. automodule:: reactivex.scheduler 7 | :members: CatchScheduler, CurrentThreadScheduler, EventLoopScheduler, 8 | HistoricalScheduler, ImmediateScheduler, NewThreadScheduler, 9 | ThreadPoolScheduler, TimeoutScheduler, TrampolineScheduler, 10 | VirtualTimeScheduler 11 | 12 | .. automodule:: reactivex.scheduler.eventloop 13 | :members: AsyncIOScheduler, AsyncIOThreadSafeScheduler, EventletScheduler, 14 | GEventScheduler, IOLoopScheduler, TwistedScheduler 15 | 16 | .. automodule:: reactivex.scheduler.mainloop 17 | :members: GtkScheduler, PyGameScheduler, QtScheduler, 18 | TkinterScheduler, WxScheduler 19 | -------------------------------------------------------------------------------- /docs/reference_subject.rst: -------------------------------------------------------------------------------- 1 | .. _reference_subject: 2 | 3 | Subject 4 | ======== 5 | 6 | .. autoclass:: reactivex.subject.Subject 7 | :members: 8 | 9 | .. autoclass:: reactivex.subject.BehaviorSubject 10 | :members: 11 | 12 | .. autoclass:: reactivex.subject.ReplaySubject 13 | :members: 14 | 15 | .. autoclass:: reactivex.subject.AsyncSubject 16 | :members: 17 | -------------------------------------------------------------------------------- /docs/reference_typing.rst: -------------------------------------------------------------------------------- 1 | .. _reference_typing: 2 | 3 | Typing 4 | ======= 5 | 6 | .. automodule:: reactivex.typing 7 | :members: 8 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | sphinx>=2.0 2 | sphinx-autodoc-typehints>=1.10.3 3 | guzzle_sphinx_theme>=0.7.11 4 | sphinxcontrib_dooble>=1.0 5 | tomli>=2.0 6 | dunamai>=1.9.0 -------------------------------------------------------------------------------- /examples/asyncio/await.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | import reactivex 4 | 5 | stream = reactivex.just("Hello, world!") 6 | 7 | 8 | async def hello_world(): 9 | n = await stream 10 | print(n) 11 | 12 | 13 | loop = asyncio.get_event_loop() 14 | # Blocking call which returns when the hello_world() coroutine is done 15 | loop.run_until_complete(hello_world()) 16 | loop.close() 17 | -------------------------------------------------------------------------------- /examples/autocomplete/autocomplete.js: -------------------------------------------------------------------------------- 1 | (function (global, $, undefined) { 2 | function main() { 3 | var $input = $('#textInput'), 4 | $results = $('#results'); 5 | var ws = new WebSocket("ws://localhost:8080/ws"); 6 | 7 | $input.keyup(function(ev) { 8 | var msg = { term: ev.target.value }; 9 | ws.send(JSON.stringify(msg)); 10 | }); 11 | 12 | ws.onmessage = function(msg) { 13 | var data = JSON.parse(msg.data); 14 | var res = data[1]; 15 | 16 | // Append the results 17 | $results.empty(); 18 | 19 | $.each(res, function (_, value) { 20 | $('
  • ' + value + '
  • ').appendTo($results); 21 | }); 22 | $results.show(); 23 | } 24 | } 25 | main(); 26 | }(window, jQuery)); -------------------------------------------------------------------------------- /examples/autocomplete/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Rx for Python Rocks! 10 | 11 | 12 | 13 | 14 |
    15 | 19 |
    20 |
    21 |
    22 | 23 | 24 |
    25 | 28 |
    29 |
    30 |
    31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /examples/chess/chess_bishop_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ReactiveX/RxPY/7747af34f3e1fbac2496231e3c2edf56ff704051/examples/chess/chess_bishop_black.png -------------------------------------------------------------------------------- /examples/chess/chess_bishop_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ReactiveX/RxPY/7747af34f3e1fbac2496231e3c2edf56ff704051/examples/chess/chess_bishop_white.png -------------------------------------------------------------------------------- /examples/chess/chess_king_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ReactiveX/RxPY/7747af34f3e1fbac2496231e3c2edf56ff704051/examples/chess/chess_king_black.png -------------------------------------------------------------------------------- /examples/chess/chess_king_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ReactiveX/RxPY/7747af34f3e1fbac2496231e3c2edf56ff704051/examples/chess/chess_king_white.png -------------------------------------------------------------------------------- /examples/chess/chess_knight_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ReactiveX/RxPY/7747af34f3e1fbac2496231e3c2edf56ff704051/examples/chess/chess_knight_black.png -------------------------------------------------------------------------------- /examples/chess/chess_knight_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ReactiveX/RxPY/7747af34f3e1fbac2496231e3c2edf56ff704051/examples/chess/chess_knight_white.png -------------------------------------------------------------------------------- /examples/chess/chess_pawn_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ReactiveX/RxPY/7747af34f3e1fbac2496231e3c2edf56ff704051/examples/chess/chess_pawn_black.png -------------------------------------------------------------------------------- /examples/chess/chess_pawn_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ReactiveX/RxPY/7747af34f3e1fbac2496231e3c2edf56ff704051/examples/chess/chess_pawn_white.png -------------------------------------------------------------------------------- /examples/chess/chess_queen_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ReactiveX/RxPY/7747af34f3e1fbac2496231e3c2edf56ff704051/examples/chess/chess_queen_black.png -------------------------------------------------------------------------------- /examples/chess/chess_queen_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ReactiveX/RxPY/7747af34f3e1fbac2496231e3c2edf56ff704051/examples/chess/chess_queen_white.png -------------------------------------------------------------------------------- /examples/chess/chess_rook_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ReactiveX/RxPY/7747af34f3e1fbac2496231e3c2edf56ff704051/examples/chess/chess_rook_black.png -------------------------------------------------------------------------------- /examples/chess/chess_rook_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ReactiveX/RxPY/7747af34f3e1fbac2496231e3c2edf56ff704051/examples/chess/chess_rook_white.png -------------------------------------------------------------------------------- /examples/errors/failing.py: -------------------------------------------------------------------------------- 1 | """ 2 | This example shows how you can retry subscriptions that might 3 | sometime fail with an error. 4 | 5 | Note: you cannot use ref_count() in this case since that would 6 | make publish() re-subscribe the cold-observable and it would start 7 | looping forever. 8 | """ 9 | 10 | import time 11 | 12 | import reactivex 13 | from reactivex import operators as ops 14 | 15 | 16 | def failing(x): 17 | x = int(x) 18 | if not x % 2: 19 | raise Exception("Error") 20 | return x 21 | 22 | 23 | def main(): 24 | xs = reactivex.from_marbles("1-2-3-4-5-6-7-9-|").pipe(ops.publish()) 25 | xs.pipe(ops.map(failing), ops.retry()).subscribe(print) 26 | 27 | xs.connect() # Must connect. Cannot use ref_count() with publish() 28 | 29 | time.sleep(5) 30 | 31 | 32 | if __name__ == "__main__": 33 | main() 34 | -------------------------------------------------------------------------------- /examples/konamicode/konamicode.js: -------------------------------------------------------------------------------- 1 | $(function () { 2 | var result = $('#result'); 3 | var ws = new WebSocket("ws://localhost:8080/ws"); 4 | 5 | $(document).keyup(function(ev) { 6 | msg = { keycode: ev.keyCode }; 7 | ws.send(JSON.stringify(msg)); 8 | }); 9 | 10 | ws.onmessage = function(msg) { 11 | result.html(msg.data).show().fadeOut(2000); // print the result 12 | }; 13 | }); -------------------------------------------------------------------------------- /examples/marbles/frommarbles_error.py: -------------------------------------------------------------------------------- 1 | import reactivex 2 | from reactivex import operators as ops 3 | 4 | """ 5 | Specify the error to be raised in place of the # symbol. 6 | """ 7 | 8 | err = ValueError("I don't like 5!") 9 | 10 | src0 = reactivex.from_marbles("12-----4-----67--|", timespan=0.2) 11 | src1 = reactivex.from_marbles("----3----5-# ", timespan=0.2, error=err) 12 | 13 | source = reactivex.merge(src0, src1).pipe(ops.do_action(print)) 14 | source.run() 15 | -------------------------------------------------------------------------------- /examples/marbles/frommarbles_flatmap.py: -------------------------------------------------------------------------------- 1 | import reactivex 2 | from reactivex import operators as ops 3 | 4 | a = reactivex.cold(" ---a0---a1----------------a2-| ") 5 | b = reactivex.cold(" ---b1---b2---| ") 6 | c = reactivex.cold(" ---c1---c2---| ") 7 | d = reactivex.cold(" -----d1---d2---|") 8 | e1 = reactivex.cold("a--b--------c-----d-------| ") 9 | 10 | observableLookup = {"a": a, "b": b, "c": c, "d": d} 11 | 12 | source = e1.pipe( 13 | ops.flat_map(lambda value: observableLookup[value]), 14 | ops.do_action(lambda v: print(v)), 15 | ) 16 | 17 | source.run() 18 | -------------------------------------------------------------------------------- /examples/marbles/frommarbles_lookup.py: -------------------------------------------------------------------------------- 1 | import reactivex 2 | import reactivex.operators as ops 3 | 4 | """ 5 | Use a dictionnary to convert elements declared in the marbles diagram to 6 | the specified values. 7 | """ 8 | 9 | lookup0 = {"a": 1, "b": 3, "c": 5} 10 | lookup1 = {"x": 2, "y": 4, "z": 6} 11 | source0 = reactivex.cold("a---b----c----|", timespan=0.01, lookup=lookup0) 12 | source1 = reactivex.cold("---x---y---z--|", timespan=0.01, lookup=lookup1) 13 | 14 | observable = reactivex.merge(source0, source1).pipe(ops.to_iterable()) 15 | elements = observable.run() 16 | print("received {}".format(list(elements))) 17 | -------------------------------------------------------------------------------- /examples/marbles/frommarbles_merge.py: -------------------------------------------------------------------------------- 1 | import reactivex 2 | from reactivex import operators as ops 3 | 4 | """ 5 | simple example that merges two cold observables. 6 | """ 7 | 8 | source0 = reactivex.cold("a-----d---1--------4-|", timespan=0.01) 9 | source1 = reactivex.cold("--b-c-------2---3-| ", timespan=0.01) 10 | 11 | observable = reactivex.merge(source0, source1).pipe(ops.to_iterable()) 12 | elements = observable.run() 13 | print("received {}".format(list(elements))) 14 | -------------------------------------------------------------------------------- /examples/marbles/hot_datetime.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | import reactivex 4 | import reactivex.operators as ops 5 | 6 | """ 7 | Delay the emission of elements to the specified datetime. 8 | """ 9 | 10 | now = datetime.datetime.now(datetime.timezone.utc) 11 | dt = datetime.timedelta(seconds=3.0) 12 | duetime = now + dt 13 | 14 | print( 15 | "{} -> now\n" 16 | "{} -> start of emission in {}s".format(now, duetime, dt.total_seconds()) 17 | ) 18 | 19 | hot = reactivex.hot("10--11--12--13--(14,|)", timespan=0.2, duetime=duetime) 20 | 21 | source = hot.pipe(ops.do_action(print)) 22 | source.run() 23 | -------------------------------------------------------------------------------- /examples/marbles/testing_debounce.py: -------------------------------------------------------------------------------- 1 | from reactivex import operators as ops 2 | from reactivex.testing.marbles import marbles_testing 3 | 4 | """ 5 | Tests debounceTime from reactivexjs 6 | https://github.com/ReactiveX/rxjs/blob/master/spec/operators/debounceTime-spec.ts 7 | 8 | it should delay all element by the specified time 9 | """ 10 | with marbles_testing(timespan=1.0) as (start, cold, hot, exp): 11 | 12 | e1 = cold("-a--------b------c----|") 13 | ex = exp("------a--------b------(c,|)") 14 | expected = ex 15 | 16 | def create(): 17 | return e1.pipe( 18 | ops.debounce(5), 19 | ) 20 | 21 | results = start(create) 22 | assert results == expected 23 | 24 | print("debounce: results vs expected") 25 | for r, e in zip(results, expected): 26 | print(r, e) 27 | -------------------------------------------------------------------------------- /examples/marbles/testing_flatmap.py: -------------------------------------------------------------------------------- 1 | from reactivex import operators as ops 2 | from reactivex.testing.marbles import marbles_testing 3 | 4 | """ 5 | Tests MergeMap from reactivexjs 6 | https://github.com/ReactiveX/rxjs/blob/master/spec/operators/mergeMap-spec.ts 7 | 8 | it should flat_map many regular interval inners 9 | """ 10 | with marbles_testing(timespan=1.0) as context: 11 | start, cold, hot, exp = context 12 | 13 | a = cold(" ----a---a----a----(a,|) ") 14 | b = cold(" ----1----b----(b,|) ") 15 | c = cold(" -------c---c---c----c---(c,|)") 16 | d = cold(" -------(d,|) ") 17 | e1 = hot("-a---b-----------c-------d------------| ") 18 | ex = exp("-----a---(a,1)(a,b)(a,b)c---c---(c,d)c---(c,|)") 19 | expected = ex 20 | 21 | observableLookup = {"a": a, "b": b, "c": c, "d": d} 22 | 23 | obs = e1.pipe(ops.flat_map(lambda value: observableLookup[value])) 24 | 25 | results = start(obs) 26 | assert results == expected 27 | 28 | print("flat_map: results vs expected") 29 | for r, e in zip(results, expected): 30 | print(r, e) 31 | -------------------------------------------------------------------------------- /examples/marbles/tomarbles.py: -------------------------------------------------------------------------------- 1 | import reactivex 2 | from reactivex import operators as ops 3 | 4 | source0 = reactivex.cold("a-----d---1--------4-|", timespan=0.1) 5 | source1 = reactivex.cold("--b-c-------2---3-| ", timespan=0.1) 6 | 7 | print("to_marbles() is a blocking operator, we need to wait for completion...") 8 | print('expecting "a-b-c-d---1-2---3--4-|"') 9 | observable = reactivex.merge(source0, source1).pipe(ops.to_marbles(timespan=0.1)) 10 | diagram = observable.run() 11 | print('got "{}"'.format(diagram)) 12 | -------------------------------------------------------------------------------- /examples/parallel/timer.py: -------------------------------------------------------------------------------- 1 | import concurrent.futures 2 | import time 3 | 4 | import reactivex 5 | from reactivex import operators as ops 6 | 7 | seconds = [5, 1, 2, 4, 3] 8 | 9 | 10 | def sleep(tm: float) -> float: 11 | time.sleep(tm) 12 | return tm 13 | 14 | 15 | def output(result: str) -> None: 16 | print("%d seconds" % result) 17 | 18 | 19 | with concurrent.futures.ProcessPoolExecutor(5) as executor: 20 | reactivex.from_(seconds).pipe( 21 | ops.flat_map(lambda s: executor.submit(sleep, s)) 22 | ).subscribe(output) 23 | 24 | # 1 seconds 25 | # 2 seconds 26 | # 3 seconds 27 | # 4 seconds 28 | # 5 seconds 29 | -------------------------------------------------------------------------------- /examples/timeflies/timeflies_gtk.py: -------------------------------------------------------------------------------- 1 | import gi 2 | from gi.repository import Gdk, GLib, Gtk 3 | 4 | import reactivex 5 | from reactivex import operators as ops 6 | from reactivex.scheduler.mainloop import GtkScheduler 7 | from reactivex.subject import Subject 8 | 9 | gi.require_version("Gtk", "3.0") 10 | 11 | 12 | class Window(Gtk.Window): 13 | def __init__(self): 14 | super().__init__() 15 | self.resize(600, 600) 16 | 17 | self.add_events(Gdk.EventMask.POINTER_MOTION_MASK) 18 | self.connect("motion-notify-event", self.on_mouse_move) 19 | 20 | self.mousemove = Subject() 21 | 22 | def on_mouse_move(self, widget, event): 23 | self.mousemove.on_next((event.x, event.y)) 24 | 25 | 26 | def main(): 27 | scheduler = GtkScheduler(GLib) 28 | scrolled_window = Gtk.ScrolledWindow() 29 | 30 | window = Window() 31 | window.connect("delete-event", Gtk.main_quit) 32 | 33 | container = Gtk.Fixed() 34 | 35 | scrolled_window.add(container) 36 | window.add(scrolled_window) 37 | text = "TIME FLIES LIKE AN ARROW" 38 | 39 | def on_next(info): 40 | label, (x, y), i = info 41 | container.move(label, x + i * 12 + 15, y) 42 | label.show() 43 | 44 | def handle_label(label, i): 45 | delayer = ops.delay(i * 0.100) 46 | mapper = ops.map(lambda xy: (label, xy, i)) 47 | 48 | return window.mousemove.pipe( 49 | delayer, 50 | mapper, 51 | ) 52 | 53 | def make_label(char): 54 | label = Gtk.Label(label=char) 55 | container.put(label, 0, 0) 56 | label.hide() 57 | return label 58 | 59 | mapper = ops.map(make_label) 60 | labeler = ops.flat_map_indexed(handle_label) 61 | 62 | reactivex.from_(text).pipe( 63 | mapper, 64 | labeler, 65 | ).subscribe(on_next, on_error=print, scheduler=scheduler) 66 | 67 | window.show_all() 68 | 69 | Gtk.main() 70 | 71 | 72 | if __name__ == "__main__": 73 | main() 74 | -------------------------------------------------------------------------------- /examples/timeflies/timeflies_qt.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | import reactivex 4 | from reactivex import operators as ops 5 | from reactivex.scheduler.mainloop import QtScheduler 6 | from reactivex.subject import Subject 7 | 8 | try: 9 | from PySide2 import QtCore 10 | from PySide2.QtWidgets import QApplication, QLabel, QWidget 11 | except ImportError: 12 | try: 13 | from PyQt5 import QtCore 14 | from PyQt5.QtWidgets import QApplication, QLabel, QWidget 15 | except ImportError: 16 | raise ImportError("Please ensure either PySide2 or PyQt5 is available!") 17 | 18 | 19 | class Window(QWidget): 20 | def __init__(self): 21 | QWidget.__init__(self) 22 | self.setWindowTitle("Rx for Python rocks") 23 | self.resize(600, 600) 24 | self.setMouseTracking(True) 25 | 26 | # This Subject is used to transmit mouse moves to labels 27 | self.mousemove = Subject() 28 | 29 | def mouseMoveEvent(self, event): 30 | self.mousemove.on_next((event.x(), event.y())) 31 | 32 | 33 | def main(): 34 | app = QApplication(sys.argv) 35 | scheduler = QtScheduler(QtCore) 36 | 37 | window = Window() 38 | window.show() 39 | 40 | text = "TIME FLIES LIKE AN ARROW" 41 | 42 | def on_next(info): 43 | label, (x, y), i = info 44 | label.move(x + i * 12 + 15, y) 45 | label.show() 46 | 47 | def handle_label(label, i): 48 | delayer = ops.delay(i * 0.100) 49 | mapper = ops.map(lambda xy: (label, xy, i)) 50 | 51 | return window.mousemove.pipe( 52 | delayer, 53 | mapper, 54 | ) 55 | 56 | labeler = ops.flat_map_indexed(handle_label) 57 | mapper = ops.map(lambda c: QLabel(c, window)) 58 | 59 | reactivex.from_(text).pipe( 60 | mapper, 61 | labeler, 62 | ).subscribe(on_next, on_error=print, scheduler=scheduler) 63 | 64 | sys.exit(app.exec_()) 65 | 66 | 67 | if __name__ == "__main__": 68 | main() 69 | -------------------------------------------------------------------------------- /examples/timeflies/timeflies_tkinter.py: -------------------------------------------------------------------------------- 1 | import tkinter 2 | from tkinter import Event, Frame, Label, Tk 3 | from typing import Any, Tuple 4 | 5 | import reactivex 6 | from reactivex import Observable 7 | from reactivex import operators as ops 8 | from reactivex.scheduler.mainloop import TkinterScheduler 9 | from reactivex.subject import Subject 10 | 11 | 12 | def main() -> None: 13 | root = Tk() 14 | root.title("Rx for Python rocks") 15 | scheduler = TkinterScheduler(root) 16 | 17 | mousemoves: Subject[Event[Any]] = Subject() 18 | 19 | frame = Frame(root, width=600, height=600) 20 | frame.bind("", mousemoves.on_next) 21 | 22 | text = "TIME FLIES LIKE AN ARROW" 23 | 24 | def on_next(info: Tuple[tkinter.Label, "Event[Frame]", int]) -> None: 25 | label, ev, i = info 26 | label.place(x=ev.x + i * 12 + 15, y=ev.y) 27 | 28 | def label2stream( 29 | label: tkinter.Label, index: int 30 | ) -> Observable[Tuple[tkinter.Label, "Event[Frame]", int]]: 31 | 32 | return mousemoves.pipe( 33 | ops.map(lambda ev: (label, ev, index)), 34 | ops.delay(index * 0.1), 35 | ) 36 | 37 | def char2label(char: str) -> Label: 38 | return Label(frame, text=char, borderwidth=0, padx=0, pady=0) 39 | 40 | reactivex.from_(text).pipe( 41 | ops.map(char2label), 42 | ops.flat_map_indexed(label2stream), 43 | ).subscribe(on_next, on_error=print, scheduler=scheduler) 44 | 45 | frame.pack() 46 | root.mainloop() 47 | 48 | 49 | if __name__ == "__main__": 50 | main() 51 | -------------------------------------------------------------------------------- /examples/timeflies/timeflies_wx.py: -------------------------------------------------------------------------------- 1 | import wx 2 | 3 | import reactivex 4 | from reactivex import operators as ops 5 | from reactivex.scheduler.mainloop import WxScheduler 6 | from reactivex.subject import Subject 7 | 8 | 9 | class Frame(wx.Frame): 10 | def __init__(self): 11 | super(Frame, self).__init__(None) 12 | self.SetTitle("Rx for Python rocks") 13 | self.SetSize((600, 600)) 14 | 15 | # This Subject is used to transmit mouse moves to labels 16 | self.mousemove = Subject() 17 | 18 | self.Bind(wx.EVT_MOTION, self.OnMotion) 19 | 20 | def OnMotion(self, event): 21 | self.mousemove.on_next((event.GetX(), event.GetY())) 22 | 23 | 24 | def main(): 25 | app = wx.App() 26 | scheduler = WxScheduler(wx) 27 | 28 | app.TopWindow = frame = Frame() 29 | frame.Show() 30 | 31 | text = "TIME FLIES LIKE AN ARROW" 32 | 33 | def on_next(info): 34 | label, (x, y), i = info 35 | label.Move(x + i * 12 + 15, y) 36 | label.Show() 37 | 38 | def handle_label(label, i): 39 | delayer = ops.delay(i * 0.100) 40 | mapper = ops.map(lambda xy: (label, xy, i)) 41 | 42 | return frame.mousemove.pipe( 43 | delayer, 44 | mapper, 45 | ) 46 | 47 | def make_label(char): 48 | label = wx.StaticText(frame, label=char) 49 | label.Hide() 50 | return label 51 | 52 | mapper = ops.map(make_label) 53 | labeler = ops.flat_map_indexed(handle_label) 54 | 55 | reactivex.from_(text).pipe( 56 | mapper, 57 | labeler, 58 | ).subscribe(on_next, on_error=print, scheduler=scheduler) 59 | 60 | frame.Bind(wx.EVT_CLOSE, lambda e: (scheduler.cancel_all(), e.Skip())) 61 | app.MainLoop() 62 | 63 | 64 | if __name__ == "__main__": 65 | main() 66 | -------------------------------------------------------------------------------- /notebooks/reactivex.io/assets/img/publishConnect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ReactiveX/RxPY/7747af34f3e1fbac2496231e3c2edf56ff704051/notebooks/reactivex.io/assets/img/publishConnect.png -------------------------------------------------------------------------------- /notebooks/reactivex.io/assets/img/threading.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ReactiveX/RxPY/7747af34f3e1fbac2496231e3c2edf56ff704051/notebooks/reactivex.io/assets/img/threading.png -------------------------------------------------------------------------------- /notebooks/reactivex.io/assets/js/ipython_notebook_toc.js: -------------------------------------------------------------------------------- 1 | // Converts integer to roman numeral 2 | function romanize(num) { 3 | 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}, 4 | roman = '', 5 | i; 6 | for ( i in lookup ) { 7 | while ( num >= lookup[i] ) { 8 | roman += i; 9 | num -= lookup[i]; 10 | } 11 | } 12 | return roman; 13 | } 14 | 15 | // Builds a
      Table of Contents from all in DOM 16 | function createTOC(){ 17 | var toc = ""; 18 | var level = 0; 19 | var levels = {} 20 | $('#toc').html(''); 21 | 22 | $(":header").each(function(i){ 23 | if (this.id=='tocheading'){return;} 24 | 25 | titleText = this.innerHTML; 26 | openLevel = this.tagName[1]; 27 | 28 | if (levels[openLevel]){ 29 | levels[openLevel] += 1; 30 | } else{ 31 | levels[openLevel] = 1; 32 | } 33 | 34 | if (openLevel > level) { 35 | toc += (new Array(openLevel - level + 1)).join('
        '); 36 | } else if (openLevel < level) { 37 | toc += (new Array(level - openLevel + 1)).join("
      "); 38 | for (i=level;i>openLevel;i--){levels[i]=0;} 39 | } 40 | 41 | level = parseInt(openLevel); 42 | 43 | 44 | if (this.id==''){this.id = this.innerHTML.replace(/ /g,"-")} 45 | var anchor = this.id; 46 | 47 | toc += '
    • ' + romanize(levels[openLevel].toString()) + '. ' + titleText 48 | + '
    • '; 49 | 50 | }); 51 | 52 | 53 | if (level) { 54 | toc += (new Array(level + 1)).join("
    "); 55 | } 56 | 57 | 58 | $('#toc').append(toc); 59 | 60 | }; 61 | 62 | // Executes the createToc function 63 | setTimeout(function(){createTOC();},100); 64 | 65 | // Rebuild to TOC every minute 66 | setInterval(function(){createTOC();},60000); -------------------------------------------------------------------------------- /pyrightconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": [ 3 | "reactivex" 4 | ], 5 | "exclude": [ 6 | "tests" 7 | ], 8 | "reportImportCycles": false, 9 | "reportMissingImports": false, 10 | "pythonVersion": "3.9", 11 | "typeCheckingMode": "strict" 12 | } -------------------------------------------------------------------------------- /reactivex/_version.py: -------------------------------------------------------------------------------- 1 | __version__ = "0.0.0" # NOTE: version will be written by publish script 2 | -------------------------------------------------------------------------------- /reactivex/abc/__init__.py: -------------------------------------------------------------------------------- 1 | from .disposable import DisposableBase 2 | from .observable import ObservableBase, Subscription 3 | from .observer import ObserverBase, OnCompleted, OnError, OnNext 4 | from .periodicscheduler import PeriodicSchedulerBase 5 | from .scheduler import ScheduledAction, SchedulerBase 6 | from .startable import StartableBase 7 | from .subject import SubjectBase 8 | 9 | __all__ = [ 10 | "DisposableBase", 11 | "ObserverBase", 12 | "ObservableBase", 13 | "OnCompleted", 14 | "OnError", 15 | "OnNext", 16 | "SchedulerBase", 17 | "PeriodicSchedulerBase", 18 | "SubjectBase", 19 | "Subscription", 20 | "ScheduledAction", 21 | "StartableBase", 22 | ] 23 | -------------------------------------------------------------------------------- /reactivex/abc/disposable.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from abc import ABC, abstractmethod 4 | from types import TracebackType 5 | from typing import Optional, Type 6 | 7 | 8 | class DisposableBase(ABC): 9 | """Disposable abstract base class.""" 10 | 11 | __slots__ = () 12 | 13 | @abstractmethod 14 | def dispose(self) -> None: 15 | """Dispose the object: stop whatever we're doing and release all of the 16 | resources we might be using. 17 | """ 18 | raise NotImplementedError 19 | 20 | def __enter__(self) -> DisposableBase: 21 | """Context management protocol.""" 22 | return self 23 | 24 | def __exit__( 25 | self, 26 | exctype: Optional[Type[BaseException]], 27 | excinst: Optional[BaseException], 28 | exctb: Optional[TracebackType], 29 | ) -> None: 30 | """Context management protocol.""" 31 | self.dispose() 32 | 33 | 34 | __all__ = ["DisposableBase"] 35 | -------------------------------------------------------------------------------- /reactivex/abc/observable.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from typing import Callable, Generic, Optional, TypeVar, Union 3 | 4 | from .disposable import DisposableBase 5 | from .observer import ObserverBase, OnCompleted, OnError, OnNext 6 | from .scheduler import SchedulerBase 7 | 8 | _T_out = TypeVar("_T_out", covariant=True) 9 | 10 | 11 | class ObservableBase(Generic[_T_out], ABC): 12 | """Observable abstract base class. 13 | 14 | Represents a push-style collection.""" 15 | 16 | __slots__ = () 17 | 18 | @abstractmethod 19 | def subscribe( 20 | self, 21 | on_next: Optional[Union[OnNext[_T_out], ObserverBase[_T_out]]] = None, 22 | on_error: Optional[OnError] = None, 23 | on_completed: Optional[OnCompleted] = None, 24 | *, 25 | scheduler: Optional[SchedulerBase] = None, 26 | ) -> DisposableBase: 27 | """Subscribe an observer to the observable sequence. 28 | 29 | Args: 30 | observer: [Optional] The object that is to receive 31 | notifications. 32 | scheduler: [Optional] The default scheduler to use for this 33 | subscription. 34 | 35 | Returns: 36 | Disposable object representing an observer's subscription 37 | to the observable sequence. 38 | """ 39 | 40 | raise NotImplementedError 41 | 42 | 43 | Subscription = Callable[[ObserverBase[_T_out], Optional[SchedulerBase]], DisposableBase] 44 | 45 | __all__ = ["ObservableBase", "Subscription"] 46 | -------------------------------------------------------------------------------- /reactivex/abc/observer.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from typing import Callable, Generic, TypeVar 3 | 4 | _T = TypeVar("_T") 5 | _T_in = TypeVar("_T_in", contravariant=True) 6 | 7 | OnNext = Callable[[_T], None] 8 | OnError = Callable[[Exception], None] 9 | OnCompleted = Callable[[], None] 10 | 11 | 12 | class ObserverBase(Generic[_T_in], ABC): 13 | """Observer abstract base class 14 | 15 | An Observer is the entity that receives all emissions of a 16 | subscribed Observable. 17 | """ 18 | 19 | __slots__ = () 20 | 21 | @abstractmethod 22 | def on_next(self, value: _T_in) -> None: 23 | """Notifies the observer of a new element in the sequence. 24 | 25 | Args: 26 | value: The received element. 27 | """ 28 | 29 | raise NotImplementedError 30 | 31 | @abstractmethod 32 | def on_error(self, error: Exception) -> None: 33 | """Notifies the observer that an exception has occurred. 34 | 35 | Args: 36 | error: The error that has occurred. 37 | """ 38 | 39 | raise NotImplementedError 40 | 41 | @abstractmethod 42 | def on_completed(self) -> None: 43 | """Notifies the observer of the end of the sequence.""" 44 | 45 | raise NotImplementedError 46 | 47 | 48 | __all__ = ["ObserverBase", "OnNext", "OnError", "OnCompleted"] 49 | -------------------------------------------------------------------------------- /reactivex/abc/periodicscheduler.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from typing import Callable, Optional, TypeVar, Union 3 | 4 | from .disposable import DisposableBase 5 | from .scheduler import RelativeTime, ScheduledAction 6 | 7 | _TState = TypeVar("_TState") # Can be anything 8 | 9 | ScheduledPeriodicAction = Callable[[Optional[_TState]], Optional[_TState]] 10 | ScheduledSingleOrPeriodicAction = Union[ 11 | ScheduledAction[_TState], ScheduledPeriodicAction[_TState] 12 | ] 13 | 14 | 15 | class PeriodicSchedulerBase(ABC): 16 | """PeriodicScheduler abstract base class.""" 17 | 18 | __slots__ = () 19 | 20 | @abstractmethod 21 | def schedule_periodic( 22 | self, 23 | period: RelativeTime, 24 | action: ScheduledPeriodicAction[_TState], 25 | state: Optional[_TState] = None, 26 | ) -> DisposableBase: 27 | """Schedules a periodic piece of work. 28 | 29 | Args: 30 | period: Period in seconds or timedelta for running the 31 | work periodically. 32 | action: Action to be executed. 33 | state: [Optional] Initial state passed to the action upon 34 | the first iteration. 35 | 36 | Returns: 37 | The disposable object used to cancel the scheduled 38 | recurring action (best effort). 39 | """ 40 | 41 | return NotImplemented 42 | 43 | 44 | __all__ = [ 45 | "PeriodicSchedulerBase", 46 | "ScheduledPeriodicAction", 47 | "ScheduledSingleOrPeriodicAction", 48 | "RelativeTime", 49 | ] 50 | -------------------------------------------------------------------------------- /reactivex/abc/startable.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | 3 | 4 | class StartableBase(ABC): 5 | """Abstract base class for Thread- and Process-like objects.""" 6 | 7 | __slots__ = () 8 | 9 | @abstractmethod 10 | def start(self) -> None: 11 | raise NotImplementedError 12 | 13 | 14 | __all__ = ["StartableBase"] 15 | -------------------------------------------------------------------------------- /reactivex/disposable/__init__.py: -------------------------------------------------------------------------------- 1 | from .booleandisposable import BooleanDisposable 2 | from .compositedisposable import CompositeDisposable 3 | from .disposable import Disposable 4 | from .multipleassignmentdisposable import MultipleAssignmentDisposable 5 | from .refcountdisposable import RefCountDisposable 6 | from .scheduleddisposable import ScheduledDisposable 7 | from .serialdisposable import SerialDisposable 8 | from .singleassignmentdisposable import SingleAssignmentDisposable 9 | 10 | __all__ = [ 11 | "BooleanDisposable", 12 | "CompositeDisposable", 13 | "Disposable", 14 | "MultipleAssignmentDisposable", 15 | "RefCountDisposable", 16 | "ScheduledDisposable", 17 | "SerialDisposable", 18 | "SingleAssignmentDisposable", 19 | ] 20 | -------------------------------------------------------------------------------- /reactivex/disposable/booleandisposable.py: -------------------------------------------------------------------------------- 1 | from threading import RLock 2 | 3 | from reactivex.abc import DisposableBase 4 | 5 | 6 | class BooleanDisposable(DisposableBase): 7 | """Represents a Disposable that can be checked for status.""" 8 | 9 | def __init__(self) -> None: 10 | """Initializes a new instance of the BooleanDisposable class.""" 11 | 12 | self.is_disposed = False 13 | self.lock = RLock() 14 | 15 | super().__init__() 16 | 17 | def dispose(self) -> None: 18 | """Sets the status to disposed""" 19 | 20 | self.is_disposed = True 21 | -------------------------------------------------------------------------------- /reactivex/disposable/disposable.py: -------------------------------------------------------------------------------- 1 | from threading import RLock 2 | from typing import Optional 3 | 4 | from reactivex import typing 5 | from reactivex.abc import DisposableBase 6 | from reactivex.internal import noop 7 | from reactivex.typing import Action 8 | 9 | 10 | class Disposable(DisposableBase): 11 | """Main disposable class""" 12 | 13 | def __init__(self, action: Optional[typing.Action] = None) -> None: 14 | """Creates a disposable object that invokes the specified 15 | action when disposed. 16 | 17 | Args: 18 | action: Action to run during the first call to dispose. 19 | The action is guaranteed to be run at most once. 20 | 21 | Returns: 22 | The disposable object that runs the given action upon 23 | disposal. 24 | """ 25 | 26 | self.is_disposed = False 27 | self.action: Action = action or noop 28 | 29 | self.lock = RLock() 30 | 31 | super().__init__() 32 | 33 | def dispose(self) -> None: 34 | """Performs the task of cleaning up resources.""" 35 | 36 | dispose = False 37 | with self.lock: 38 | if not self.is_disposed: 39 | dispose = True 40 | self.is_disposed = True 41 | 42 | if dispose: 43 | self.action() 44 | -------------------------------------------------------------------------------- /reactivex/disposable/multipleassignmentdisposable.py: -------------------------------------------------------------------------------- 1 | from threading import RLock 2 | from typing import Optional 3 | 4 | from reactivex.abc import DisposableBase 5 | 6 | 7 | class MultipleAssignmentDisposable(DisposableBase): 8 | """Represents a disposable resource whose underlying disposable 9 | resource can be replaced by another disposable resource.""" 10 | 11 | def __init__(self) -> None: 12 | self.current: Optional[DisposableBase] = None 13 | self.is_disposed = False 14 | self.lock = RLock() 15 | 16 | super().__init__() 17 | 18 | def get_disposable(self) -> Optional[DisposableBase]: 19 | return self.current 20 | 21 | def set_disposable(self, value: DisposableBase) -> None: 22 | """If the MultipleAssignmentDisposable has already been 23 | disposed, assignment to this property causes immediate disposal 24 | of the given disposable object.""" 25 | 26 | with self.lock: 27 | should_dispose = self.is_disposed 28 | if not should_dispose: 29 | self.current = value 30 | 31 | if should_dispose: 32 | value.dispose() 33 | 34 | disposable = property(get_disposable, set_disposable) 35 | 36 | def dispose(self) -> None: 37 | """Disposes the underlying disposable as well as all future 38 | replacements.""" 39 | 40 | old = None 41 | 42 | with self.lock: 43 | if not self.is_disposed: 44 | self.is_disposed = True 45 | old = self.current 46 | self.current = None 47 | 48 | if old is not None: 49 | old.dispose() 50 | -------------------------------------------------------------------------------- /reactivex/disposable/scheduleddisposable.py: -------------------------------------------------------------------------------- 1 | from threading import RLock 2 | from typing import Any 3 | 4 | from reactivex import abc 5 | 6 | from .singleassignmentdisposable import SingleAssignmentDisposable 7 | 8 | 9 | class ScheduledDisposable(abc.DisposableBase): 10 | """Represents a disposable resource whose disposal invocation will 11 | be scheduled on the specified Scheduler""" 12 | 13 | def __init__( 14 | self, scheduler: abc.SchedulerBase, disposable: abc.DisposableBase 15 | ) -> None: 16 | """Initializes a new instance of the ScheduledDisposable class 17 | that uses a Scheduler on which to dispose the disposable.""" 18 | 19 | self.scheduler = scheduler 20 | self.disposable = SingleAssignmentDisposable() 21 | self.disposable.disposable = disposable 22 | self.lock = RLock() 23 | 24 | super().__init__() 25 | 26 | @property 27 | def is_disposed(self) -> bool: 28 | return self.disposable.is_disposed 29 | 30 | def dispose(self) -> None: 31 | """Disposes the wrapped disposable on the provided scheduler.""" 32 | 33 | def action(scheduler: abc.SchedulerBase, state: Any) -> None: 34 | """Scheduled dispose action""" 35 | 36 | self.disposable.dispose() 37 | 38 | self.scheduler.schedule(action) 39 | -------------------------------------------------------------------------------- /reactivex/disposable/serialdisposable.py: -------------------------------------------------------------------------------- 1 | from threading import RLock 2 | from typing import Optional 3 | 4 | from reactivex import abc 5 | 6 | 7 | class SerialDisposable(abc.DisposableBase): 8 | """Represents a disposable resource whose underlying disposable 9 | resource can be replaced by another disposable resource, causing 10 | automatic disposal of the previous underlying disposable resource. 11 | """ 12 | 13 | def __init__(self) -> None: 14 | self.current: Optional[abc.DisposableBase] = None 15 | self.is_disposed = False 16 | self.lock = RLock() 17 | 18 | super().__init__() 19 | 20 | def get_disposable(self) -> Optional[abc.DisposableBase]: 21 | return self.current 22 | 23 | def set_disposable(self, value: abc.DisposableBase) -> None: 24 | """If the SerialDisposable has already been disposed, assignment 25 | to this property causes immediate disposal of the given 26 | disposable object. Assigning this property disposes the previous 27 | disposable object.""" 28 | 29 | old: Optional[abc.DisposableBase] = None 30 | 31 | with self.lock: 32 | should_dispose = self.is_disposed 33 | if not should_dispose: 34 | old = self.current 35 | self.current = value 36 | 37 | if old is not None: 38 | old.dispose() 39 | 40 | if should_dispose: 41 | value.dispose() 42 | 43 | disposable = property(get_disposable, set_disposable) 44 | 45 | def dispose(self) -> None: 46 | """Disposes the underlying disposable as well as all future 47 | replacements.""" 48 | 49 | old: Optional[abc.DisposableBase] = None 50 | 51 | with self.lock: 52 | if not self.is_disposed: 53 | self.is_disposed = True 54 | old = self.current 55 | self.current = None 56 | 57 | if old is not None: 58 | old.dispose() 59 | -------------------------------------------------------------------------------- /reactivex/disposable/singleassignmentdisposable.py: -------------------------------------------------------------------------------- 1 | from threading import RLock 2 | from typing import Optional 3 | 4 | from reactivex.abc import DisposableBase 5 | 6 | 7 | class SingleAssignmentDisposable(DisposableBase): 8 | """Single assignment disposable. 9 | 10 | Represents a disposable resource which only allows a single 11 | assignment of its underlying disposable resource. If an underlying 12 | disposable resource has already been set, future attempts to set the 13 | underlying disposable resource will throw an Error.""" 14 | 15 | def __init__(self) -> None: 16 | """Initializes a new instance of the SingleAssignmentDisposable 17 | class. 18 | """ 19 | self.is_disposed: bool = False 20 | self.current: Optional[DisposableBase] = None 21 | self.lock = RLock() 22 | 23 | super().__init__() 24 | 25 | def get_disposable(self) -> Optional[DisposableBase]: 26 | return self.current 27 | 28 | def set_disposable(self, value: DisposableBase) -> None: 29 | if self.current: 30 | raise Exception("Disposable has already been assigned") 31 | 32 | with self.lock: 33 | should_dispose = self.is_disposed 34 | if not should_dispose: 35 | self.current = value 36 | 37 | if self.is_disposed and value: 38 | value.dispose() 39 | 40 | disposable = property(get_disposable, set_disposable) 41 | 42 | def dispose(self) -> None: 43 | """Sets the status to disposed""" 44 | old: Optional[DisposableBase] = None 45 | 46 | with self.lock: 47 | if not self.is_disposed: 48 | self.is_disposed = True 49 | old = self.current 50 | self.current = None 51 | 52 | if old is not None: 53 | old.dispose() 54 | -------------------------------------------------------------------------------- /reactivex/internal/__init__.py: -------------------------------------------------------------------------------- 1 | from .basic import default_comparer, default_error, noop 2 | from .concurrency import default_thread_factory, synchronized 3 | from .constants import DELTA_ZERO, UTC_ZERO 4 | from .exceptions import ( 5 | ArgumentOutOfRangeException, 6 | DisposedException, 7 | SequenceContainsNoElementsError, 8 | ) 9 | from .priorityqueue import PriorityQueue 10 | from .utils import NotSet, add_ref, alias, infinite 11 | 12 | __all__ = [ 13 | "add_ref", 14 | "alias", 15 | "ArgumentOutOfRangeException", 16 | "DisposedException", 17 | "default_comparer", 18 | "default_error", 19 | "infinite", 20 | "noop", 21 | "NotSet", 22 | "SequenceContainsNoElementsError", 23 | "concurrency", 24 | "DELTA_ZERO", 25 | "UTC_ZERO", 26 | "synchronized", 27 | "default_thread_factory", 28 | "PriorityQueue", 29 | ] 30 | -------------------------------------------------------------------------------- /reactivex/internal/basic.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime, timezone 2 | from typing import Any, NoReturn, TypeVar, Union 3 | 4 | _T = TypeVar("_T") 5 | 6 | 7 | def noop(*args: Any, **kw: Any) -> None: 8 | """No operation. Returns nothing""" 9 | 10 | 11 | def identity(x: _T) -> _T: 12 | """Returns argument x""" 13 | return x 14 | 15 | 16 | def default_now() -> datetime: 17 | return datetime.now(timezone.utc) 18 | 19 | 20 | def default_comparer(x: _T, y: _T) -> bool: 21 | return x == y 22 | 23 | 24 | def default_sub_comparer(x: Any, y: Any) -> Any: 25 | return x - y 26 | 27 | 28 | def default_key_serializer(x: Any) -> str: 29 | return str(x) 30 | 31 | 32 | def default_error(err: Union[Exception, str]) -> NoReturn: 33 | if isinstance(err, BaseException): 34 | raise err 35 | 36 | raise Exception(err) 37 | -------------------------------------------------------------------------------- /reactivex/internal/concurrency.py: -------------------------------------------------------------------------------- 1 | from threading import RLock, Thread 2 | from typing import Any, Callable, TypeVar 3 | 4 | from typing_extensions import ParamSpec 5 | 6 | from reactivex.typing import StartableTarget 7 | 8 | _T = TypeVar("_T") 9 | _P = ParamSpec("_P") 10 | 11 | 12 | def default_thread_factory(target: StartableTarget) -> Thread: 13 | return Thread(target=target, daemon=True) 14 | 15 | 16 | def synchronized(lock: RLock) -> Callable[[Callable[_P, _T]], Callable[_P, _T]]: 17 | """A decorator for synchronizing access to a given function.""" 18 | 19 | def wrapper(fn: Callable[_P, _T]) -> Callable[_P, _T]: 20 | def inner(*args: _P.args, **kw: _P.kwargs) -> Any: 21 | with lock: 22 | return fn(*args, **kw) 23 | 24 | return inner 25 | 26 | return wrapper 27 | -------------------------------------------------------------------------------- /reactivex/internal/constants.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime, timedelta, timezone 2 | 3 | DELTA_ZERO = timedelta(0) 4 | UTC_ZERO = datetime.fromtimestamp(0, tz=timezone.utc) 5 | -------------------------------------------------------------------------------- /reactivex/internal/exceptions.py: -------------------------------------------------------------------------------- 1 | # Rx Exceptions 2 | 3 | 4 | from typing import Optional 5 | 6 | 7 | class SequenceContainsNoElementsError(Exception): 8 | def __init__(self, msg: Optional[str] = None): 9 | super().__init__(msg or "Sequence contains no elements") 10 | 11 | 12 | class ArgumentOutOfRangeException(ValueError): 13 | def __init__(self, msg: Optional[str] = None): 14 | super(ArgumentOutOfRangeException, self).__init__( 15 | msg or "Argument out of range" 16 | ) 17 | 18 | 19 | class DisposedException(Exception): 20 | def __init__(self, msg: Optional[str] = None): 21 | super().__init__(msg or "Object has been disposed") 22 | 23 | 24 | class ReEntracyException(Exception): 25 | def __init__(self, msg: Optional[str] = None): 26 | super().__init__(msg or "Re-entrancy detected") 27 | 28 | 29 | class CompletedException(Exception): 30 | def __init__(self, msg: Optional[str] = None): 31 | super().__init__(msg or "Observer completed") 32 | 33 | 34 | class WouldBlockException(Exception): 35 | def __init__(self, msg: Optional[str] = None): 36 | super().__init__(msg or "Would block") 37 | -------------------------------------------------------------------------------- /reactivex/internal/priorityqueue.py: -------------------------------------------------------------------------------- 1 | import heapq 2 | from sys import maxsize 3 | from typing import Generic, List, Tuple, TypeVar 4 | 5 | _T1 = TypeVar("_T1") 6 | 7 | 8 | class PriorityQueue(Generic[_T1]): 9 | """Priority queue for scheduling. Note that methods aren't thread-safe.""" 10 | 11 | MIN_COUNT = ~maxsize 12 | 13 | def __init__(self) -> None: 14 | self.items: List[Tuple[_T1, int]] = [] 15 | self.count = PriorityQueue.MIN_COUNT # Monotonic increasing for sort stability 16 | 17 | def __len__(self) -> int: 18 | """Returns length of queue""" 19 | 20 | return len(self.items) 21 | 22 | def peek(self) -> _T1: 23 | """Returns first item in queue without removing it""" 24 | return self.items[0][0] 25 | 26 | def dequeue(self) -> _T1: 27 | """Returns and removes item with lowest priority from queue""" 28 | 29 | item: _T1 = heapq.heappop(self.items)[0] 30 | if not self.items: 31 | self.count = PriorityQueue.MIN_COUNT 32 | return item 33 | 34 | def enqueue(self, item: _T1) -> None: 35 | """Adds item to queue""" 36 | 37 | heapq.heappush(self.items, (item, self.count)) 38 | self.count += 1 39 | 40 | def remove(self, item: _T1) -> bool: 41 | """Remove given item from queue""" 42 | 43 | for index, _item in enumerate(self.items): 44 | if _item[0] == item: 45 | self.items.pop(index) 46 | heapq.heapify(self.items) 47 | return True 48 | 49 | return False 50 | 51 | def clear(self) -> None: 52 | """Remove all items from the queue.""" 53 | self.items = [] 54 | self.count = PriorityQueue.MIN_COUNT 55 | -------------------------------------------------------------------------------- /reactivex/internal/utils.py: -------------------------------------------------------------------------------- 1 | from functools import update_wrapper 2 | from types import FunctionType 3 | from typing import TYPE_CHECKING, Any, Callable, Iterable, Optional, TypeVar, cast 4 | 5 | from typing_extensions import ParamSpec 6 | 7 | from reactivex import abc 8 | from reactivex.disposable import CompositeDisposable 9 | from reactivex.disposable.refcountdisposable import RefCountDisposable 10 | 11 | if TYPE_CHECKING: 12 | from reactivex import Observable 13 | 14 | _T = TypeVar("_T") 15 | _P = ParamSpec("_P") 16 | 17 | 18 | def add_ref(xs: "Observable[_T]", r: RefCountDisposable) -> "Observable[_T]": 19 | from reactivex import Observable 20 | 21 | def subscribe( 22 | observer: abc.ObserverBase[Any], scheduler: Optional[abc.SchedulerBase] = None 23 | ) -> abc.DisposableBase: 24 | return CompositeDisposable(r.disposable, xs.subscribe(observer)) 25 | 26 | return Observable(subscribe) 27 | 28 | 29 | def infinite() -> Iterable[int]: 30 | n = 0 31 | while True: 32 | yield n 33 | n += 1 34 | 35 | 36 | def alias(name: str, doc: str, fun: Callable[_P, _T]) -> Callable[_P, _T]: 37 | # Adapted from 38 | # https://stackoverflow.com/questions/13503079/how-to-create-a-copy-of-a-python-function# 39 | # See also help(type(lambda: 0)) 40 | _fun = cast(FunctionType, fun) 41 | args = (_fun.__code__, _fun.__globals__) 42 | kwargs = {"name": name, "argdefs": _fun.__defaults__, "closure": _fun.__closure__} 43 | alias_ = FunctionType(*args, **kwargs) # type: ignore 44 | alias_ = update_wrapper(alias_, _fun) # type: ignore 45 | alias_.__kwdefaults__ = _fun.__kwdefaults__ # type: ignore 46 | alias_.__doc__ = doc 47 | alias_.__annotations__ = _fun.__annotations__ 48 | return cast(Callable[_P, _T], alias_) 49 | 50 | 51 | class NotSet: 52 | """Sentinel value.""" 53 | 54 | def __eq__(self, other: Any) -> bool: 55 | return self is other 56 | 57 | def __repr__(self) -> str: 58 | return "NotSet" 59 | 60 | 61 | __all__ = ["add_ref", "infinite", "alias", "NotSet"] 62 | -------------------------------------------------------------------------------- /reactivex/observable/__init__.py: -------------------------------------------------------------------------------- 1 | from .connectableobservable import ConnectableObservable 2 | from .groupedobservable import GroupedObservable 3 | from .observable import Observable 4 | 5 | __all__ = ["Observable", "ConnectableObservable", "GroupedObservable"] 6 | -------------------------------------------------------------------------------- /reactivex/observable/amb.py: -------------------------------------------------------------------------------- 1 | from typing import TypeVar 2 | 3 | from reactivex import Observable, never 4 | from reactivex import operators as _ 5 | 6 | _T = TypeVar("_T") 7 | 8 | 9 | def amb_(*sources: Observable[_T]) -> Observable[_T]: 10 | """Propagates the observable sequence that reacts first. 11 | 12 | Example: 13 | >>> winner = amb(xs, ys, zs) 14 | 15 | Returns: 16 | An observable sequence that surfaces any of the given sequences, 17 | whichever reacted first. 18 | """ 19 | 20 | acc: Observable[_T] = never() 21 | 22 | def func(previous: Observable[_T], current: Observable[_T]) -> Observable[_T]: 23 | return _.amb(previous)(current) 24 | 25 | for source in sources: 26 | acc = func(acc, source) 27 | 28 | return acc 29 | 30 | 31 | __all__ = ["amb_"] 32 | -------------------------------------------------------------------------------- /reactivex/observable/case.py: -------------------------------------------------------------------------------- 1 | from asyncio import Future 2 | from typing import Callable, Mapping, Optional, TypeVar, Union 3 | 4 | from reactivex import Observable, abc, defer, empty, from_future 5 | 6 | _Key = TypeVar("_Key") 7 | _T = TypeVar("_T") 8 | 9 | 10 | def case_( 11 | mapper: Callable[[], _Key], 12 | sources: Mapping[_Key, Observable[_T]], 13 | default_source: Optional[Union[Observable[_T], "Future[_T]"]] = None, 14 | ) -> Observable[_T]: 15 | 16 | default_source_: Union[Observable[_T], "Future[_T]"] = default_source or empty() 17 | 18 | def factory(_: abc.SchedulerBase) -> Observable[_T]: 19 | try: 20 | result: Union[Observable[_T], "Future[_T]"] = sources[mapper()] 21 | except KeyError: 22 | result = default_source_ 23 | 24 | if isinstance(result, Future): 25 | 26 | result_: Observable[_T] = from_future(result) 27 | else: 28 | result_ = result 29 | 30 | return result_ 31 | 32 | return defer(factory) 33 | 34 | 35 | __all__ = ["case_"] 36 | -------------------------------------------------------------------------------- /reactivex/observable/defer.py: -------------------------------------------------------------------------------- 1 | from asyncio import Future 2 | from typing import Callable, Optional, TypeVar, Union 3 | 4 | from reactivex import Observable, abc, from_future, throw 5 | from reactivex.scheduler import ImmediateScheduler 6 | 7 | _T = TypeVar("_T") 8 | 9 | 10 | def defer_( 11 | factory: Callable[[abc.SchedulerBase], Union[Observable[_T], "Future[_T]"]] 12 | ) -> Observable[_T]: 13 | """Returns an observable sequence that invokes the specified factory 14 | function whenever a new observer subscribes. 15 | 16 | Example: 17 | >>> res = defer(lambda scheduler: of(1, 2, 3)) 18 | 19 | Args: 20 | observable_factory: Observable factory function to invoke for 21 | each observer that subscribes to the resulting sequence. The 22 | factory takes a single argument, the scheduler used. 23 | 24 | Returns: 25 | An observable sequence whose observers trigger an invocation 26 | of the given observable factory function. 27 | """ 28 | 29 | def subscribe( 30 | observer: abc.ObserverBase[_T], scheduler: Optional[abc.SchedulerBase] = None 31 | ) -> abc.DisposableBase: 32 | try: 33 | result = factory(scheduler or ImmediateScheduler.singleton()) 34 | except Exception as ex: # By design. pylint: disable=W0703 35 | return throw(ex).subscribe(observer) 36 | 37 | result = from_future(result) if isinstance(result, Future) else result 38 | return result.subscribe(observer, scheduler=scheduler) 39 | 40 | return Observable(subscribe) 41 | 42 | 43 | __all__ = ["defer_"] 44 | -------------------------------------------------------------------------------- /reactivex/observable/empty.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Optional 2 | 3 | from reactivex import Observable, abc 4 | from reactivex.scheduler import ImmediateScheduler 5 | 6 | 7 | def empty_(scheduler: Optional[abc.SchedulerBase] = None) -> Observable[Any]: 8 | def subscribe( 9 | observer: abc.ObserverBase[Any], scheduler_: Optional[abc.SchedulerBase] = None 10 | ) -> abc.DisposableBase: 11 | 12 | _scheduler = scheduler or scheduler_ or ImmediateScheduler.singleton() 13 | 14 | def action(_: abc.SchedulerBase, __: Any) -> None: 15 | observer.on_completed() 16 | 17 | return _scheduler.schedule(action) 18 | 19 | return Observable(subscribe) 20 | 21 | 22 | __all__ = ["empty_"] 23 | -------------------------------------------------------------------------------- /reactivex/observable/fromcallback.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Callable, Optional 2 | 3 | from reactivex import Observable, abc, typing 4 | from reactivex.disposable import Disposable 5 | 6 | 7 | def from_callback_( 8 | func: Callable[..., Callable[..., None]], 9 | mapper: Optional[typing.Mapper[Any, Any]] = None, 10 | ) -> Callable[[], Observable[Any]]: 11 | """Converts a callback function to an observable sequence. 12 | 13 | Args: 14 | func: Function with a callback as the last argument to 15 | convert to an Observable sequence. 16 | mapper: [Optional] A mapper which takes the arguments 17 | from the callback to produce a single item to yield on next. 18 | 19 | Returns: 20 | A function, when executed with the required arguments minus 21 | the callback, produces an Observable sequence with a single value of 22 | the arguments to the callback as a list. 23 | """ 24 | 25 | def function(*args: Any) -> Observable[Any]: 26 | arguments = list(args) 27 | 28 | def subscribe( 29 | observer: abc.ObserverBase[Any], 30 | scheduler: Optional[abc.SchedulerBase] = None, 31 | ) -> abc.DisposableBase: 32 | def handler(*args: Any) -> None: 33 | results = list(args) 34 | if mapper: 35 | try: 36 | results = mapper(args) 37 | except Exception as err: # pylint: disable=broad-except 38 | observer.on_error(err) 39 | return 40 | 41 | observer.on_next(results) 42 | else: 43 | if len(results) <= 1: 44 | observer.on_next(*results) 45 | else: 46 | observer.on_next(results) 47 | 48 | observer.on_completed() 49 | 50 | arguments.append(handler) 51 | func(*arguments) 52 | return Disposable() 53 | 54 | return Observable(subscribe) 55 | 56 | return function 57 | 58 | 59 | __all__ = ["from_callback_"] 60 | -------------------------------------------------------------------------------- /reactivex/observable/fromfuture.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from asyncio import Future 3 | from typing import Any, Optional, TypeVar, cast 4 | 5 | from reactivex import Observable, abc 6 | from reactivex.disposable import Disposable 7 | 8 | _T = TypeVar("_T") 9 | 10 | 11 | def from_future_(future: "Future[_T]") -> Observable[_T]: 12 | """Converts a Future to an Observable sequence 13 | 14 | Args: 15 | future -- A Python 3 compatible future. 16 | https://docs.python.org/3/library/asyncio-task.html#future 17 | 18 | Returns: 19 | An Observable sequence which wraps the existing future success 20 | and failure. 21 | """ 22 | 23 | def subscribe( 24 | observer: abc.ObserverBase[Any], scheduler: Optional[abc.SchedulerBase] = None 25 | ) -> abc.DisposableBase: 26 | def done(future: "Future[_T]") -> None: 27 | try: 28 | value: Any = future.result() 29 | except Exception as ex: 30 | observer.on_error(ex) 31 | except asyncio.CancelledError as ex: # pylint: disable=broad-except 32 | # asyncio.CancelledError is a BaseException, so need to cast 33 | observer.on_error(cast(Exception, ex)) 34 | else: 35 | observer.on_next(value) 36 | observer.on_completed() 37 | 38 | future.add_done_callback(done) 39 | 40 | def dispose() -> None: 41 | if future: 42 | future.cancel() 43 | 44 | return Disposable(dispose) 45 | 46 | return Observable(subscribe) 47 | 48 | 49 | __all__ = ["from_future_"] 50 | -------------------------------------------------------------------------------- /reactivex/observable/fromiterable.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Iterable, Optional, TypeVar 2 | 3 | from reactivex import Observable, abc 4 | from reactivex.disposable import CompositeDisposable, Disposable 5 | from reactivex.scheduler import CurrentThreadScheduler 6 | 7 | _T = TypeVar("_T") 8 | 9 | 10 | def from_iterable_( 11 | iterable: Iterable[_T], scheduler: Optional[abc.SchedulerBase] = None 12 | ) -> Observable[_T]: 13 | """Converts an iterable to an observable sequence. 14 | 15 | Example: 16 | >>> from_iterable([1,2,3]) 17 | 18 | Args: 19 | iterable: A Python iterable 20 | scheduler: An optional scheduler to schedule the values on. 21 | 22 | Returns: 23 | The observable sequence whose elements are pulled from the 24 | given iterable sequence. 25 | """ 26 | 27 | def subscribe( 28 | observer: abc.ObserverBase[_T], scheduler_: Optional[abc.SchedulerBase] = None 29 | ) -> abc.DisposableBase: 30 | _scheduler = scheduler or scheduler_ or CurrentThreadScheduler.singleton() 31 | iterator = iter(iterable) 32 | disposed = False 33 | 34 | def action(_: abc.SchedulerBase, __: Any = None) -> None: 35 | nonlocal disposed 36 | 37 | try: 38 | while not disposed: 39 | value = next(iterator) 40 | observer.on_next(value) 41 | except StopIteration: 42 | observer.on_completed() 43 | except Exception as error: # pylint: disable=broad-except 44 | observer.on_error(error) 45 | 46 | def dispose() -> None: 47 | nonlocal disposed 48 | disposed = True 49 | 50 | disp = Disposable(dispose) 51 | return CompositeDisposable(_scheduler.schedule(action), disp) 52 | 53 | return Observable(subscribe) 54 | 55 | 56 | __all__ = ["from_iterable_"] 57 | -------------------------------------------------------------------------------- /reactivex/observable/generate.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Optional, TypeVar, cast 2 | 3 | from reactivex import Observable, abc, typing 4 | from reactivex.disposable import MultipleAssignmentDisposable 5 | from reactivex.scheduler import CurrentThreadScheduler 6 | 7 | _TState = TypeVar("_TState") 8 | 9 | 10 | def generate_( 11 | initial_state: _TState, 12 | condition: typing.Predicate[_TState], 13 | iterate: typing.Mapper[_TState, _TState], 14 | ) -> Observable[_TState]: 15 | def subscribe( 16 | observer: abc.ObserverBase[_TState], 17 | scheduler: Optional[abc.SchedulerBase] = None, 18 | ) -> abc.DisposableBase: 19 | scheduler = scheduler or CurrentThreadScheduler.singleton() 20 | first = True 21 | state = initial_state 22 | mad = MultipleAssignmentDisposable() 23 | 24 | def action(scheduler: abc.SchedulerBase, state1: Any = None) -> None: 25 | nonlocal first 26 | nonlocal state 27 | 28 | has_result = False 29 | result: _TState = cast(_TState, None) 30 | 31 | try: 32 | if first: 33 | first = False 34 | else: 35 | state = iterate(state) 36 | 37 | has_result = condition(state) 38 | if has_result: 39 | result = state 40 | 41 | except Exception as exception: # pylint: disable=broad-except 42 | observer.on_error(exception) 43 | return 44 | 45 | if has_result: 46 | observer.on_next(result) 47 | mad.disposable = scheduler.schedule(action) 48 | else: 49 | observer.on_completed() 50 | 51 | mad.disposable = scheduler.schedule(action) 52 | return mad 53 | 54 | return Observable(subscribe) 55 | 56 | 57 | __all__ = ["generate_"] 58 | -------------------------------------------------------------------------------- /reactivex/observable/groupedobservable.py: -------------------------------------------------------------------------------- 1 | from typing import Generic, Optional, TypeVar 2 | 3 | from reactivex import abc 4 | from reactivex.disposable import CompositeDisposable, Disposable, RefCountDisposable 5 | 6 | from .observable import Observable 7 | 8 | _T = TypeVar("_T") 9 | _TKey = TypeVar("_TKey") 10 | 11 | 12 | class GroupedObservable(Generic[_TKey, _T], Observable[_T]): 13 | def __init__( 14 | self, 15 | key: _TKey, 16 | underlying_observable: Observable[_T], 17 | merged_disposable: Optional[RefCountDisposable] = None, 18 | ): 19 | super().__init__() 20 | self.key = key 21 | 22 | def subscribe( 23 | observer: abc.ObserverBase[_T], 24 | scheduler: Optional[abc.SchedulerBase] = None, 25 | ) -> abc.DisposableBase: 26 | return CompositeDisposable( 27 | merged_disposable.disposable if merged_disposable else Disposable(), 28 | underlying_observable.subscribe(observer, scheduler=scheduler), 29 | ) 30 | 31 | self.underlying_observable: Observable[_T] = ( 32 | underlying_observable if not merged_disposable else Observable(subscribe) 33 | ) 34 | 35 | def _subscribe_core( 36 | self, 37 | observer: abc.ObserverBase[_T], 38 | scheduler: Optional[abc.SchedulerBase] = None, 39 | ) -> abc.DisposableBase: 40 | return self.underlying_observable.subscribe(observer, scheduler=scheduler) 41 | -------------------------------------------------------------------------------- /reactivex/observable/ifthen.py: -------------------------------------------------------------------------------- 1 | from asyncio import Future 2 | from typing import Callable, TypeVar, Union 3 | 4 | import reactivex 5 | from reactivex import Observable, abc 6 | 7 | _T = TypeVar("_T") 8 | 9 | 10 | def if_then_( 11 | condition: Callable[[], bool], 12 | then_source: Union[Observable[_T], "Future[_T]"], 13 | else_source: Union[None, Observable[_T], "Future[_T]"] = None, 14 | ) -> Observable[_T]: 15 | """Determines whether an observable collection contains values. 16 | 17 | Example: 18 | 1 - res = reactivex.if_then(condition, obs1) 19 | 2 - res = reactivex.if_then(condition, obs1, obs2) 20 | 21 | Args: 22 | condition: The condition which determines if the then_source or 23 | else_source will be run. 24 | then_source: The observable sequence or Promise that 25 | will be run if the condition function returns true. 26 | else_source: [Optional] The observable sequence or 27 | Promise that will be run if the condition function returns 28 | False. If this is not provided, it defaults to 29 | reactivex.empty 30 | 31 | Returns: 32 | An observable sequence which is either the then_source or 33 | else_source. 34 | """ 35 | 36 | else_source_: Union[Observable[_T], "Future[_T]"] = else_source or reactivex.empty() 37 | 38 | then_source = ( 39 | reactivex.from_future(then_source) 40 | if isinstance(then_source, Future) 41 | else then_source 42 | ) 43 | else_source_ = ( 44 | reactivex.from_future(else_source_) 45 | if isinstance(else_source_, Future) 46 | else else_source_ 47 | ) 48 | 49 | def factory(_: abc.SchedulerBase) -> Union[Observable[_T], "Future[_T]"]: 50 | return then_source if condition() else else_source_ 51 | 52 | return reactivex.defer(factory) 53 | 54 | 55 | __all__ = ["if_then_"] 56 | -------------------------------------------------------------------------------- /reactivex/observable/interval.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from reactivex import Observable, abc, timer, typing 4 | 5 | 6 | def interval_( 7 | period: typing.RelativeTime, scheduler: Optional[abc.SchedulerBase] = None 8 | ) -> Observable[int]: 9 | 10 | return timer(period, period, scheduler) 11 | 12 | 13 | __all__ = ["interval_"] 14 | -------------------------------------------------------------------------------- /reactivex/observable/merge.py: -------------------------------------------------------------------------------- 1 | from typing import TypeVar 2 | 3 | import reactivex 4 | from reactivex import Observable 5 | from reactivex import operators as ops 6 | 7 | _T = TypeVar("_T") 8 | 9 | 10 | def merge_(*sources: Observable[_T]) -> Observable[_T]: 11 | return reactivex.from_iterable(sources).pipe(ops.merge_all()) 12 | 13 | 14 | __all__ = ["merge_"] 15 | -------------------------------------------------------------------------------- /reactivex/observable/never.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Optional 2 | 3 | from reactivex import Observable, abc 4 | from reactivex.disposable import Disposable 5 | 6 | 7 | def never_() -> Observable[Any]: 8 | """Returns a non-terminating observable sequence, which can be used 9 | to denote an infinite duration (e.g. when using reactive joins). 10 | 11 | Returns: 12 | An observable sequence whose observers will never get called. 13 | """ 14 | 15 | def subscribe( 16 | observer: abc.ObserverBase[Any], scheduler: Optional[abc.SchedulerBase] = None 17 | ) -> abc.DisposableBase: 18 | return Disposable() 19 | 20 | return Observable(subscribe) 21 | 22 | 23 | __all__ = ["never_"] 24 | -------------------------------------------------------------------------------- /reactivex/observable/repeat.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, TypeVar 2 | 3 | import reactivex 4 | from reactivex import Observable 5 | from reactivex import operators as ops 6 | 7 | _T = TypeVar("_T") 8 | 9 | 10 | def repeat_value_(value: _T, repeat_count: Optional[int] = None) -> Observable[_T]: 11 | """Generates an observable sequence that repeats the given element 12 | the specified number of times. 13 | 14 | Examples: 15 | 1 - res = repeat_value(42) 16 | 2 - res = repeat_value(42, 4) 17 | 18 | Args: 19 | value: Element to repeat. 20 | repeat_count: [Optional] Number of times to repeat the element. 21 | If not specified, repeats indefinitely. 22 | 23 | Returns: 24 | An observable sequence that repeats the given element the 25 | specified number of times. 26 | """ 27 | 28 | if repeat_count == -1: 29 | repeat_count = None 30 | 31 | xs = reactivex.return_value(value) 32 | return xs.pipe(ops.repeat(repeat_count)) 33 | 34 | 35 | __all__ = ["repeat_value_"] 36 | -------------------------------------------------------------------------------- /reactivex/observable/start.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, Optional, TypeVar 2 | 3 | from reactivex import Observable, abc, to_async 4 | 5 | _T = TypeVar("_T") 6 | 7 | 8 | def start_( 9 | func: Callable[[], _T], scheduler: Optional[abc.SchedulerBase] = None 10 | ) -> Observable[_T]: 11 | """Invokes the specified function asynchronously on the specified 12 | scheduler, surfacing the result through an observable sequence. 13 | 14 | Example: 15 | >>> res = reactivex.start(lambda: pprint('hello')) 16 | >>> res = reactivex.start(lambda: pprint('hello'), rx.Scheduler.timeout) 17 | 18 | Args: 19 | func: Function to run asynchronously. 20 | scheduler: [Optional] Scheduler to run the function on. If 21 | not specified, defaults to Scheduler.timeout. 22 | 23 | Remarks: 24 | The function is called immediately, not during the subscription 25 | of the resulting sequence. Multiple subscriptions to the 26 | resulting sequence can observe the function's result. 27 | 28 | Returns: 29 | An observable sequence exposing the function's result value, 30 | or an exception. 31 | """ 32 | 33 | return to_async(func, scheduler)() 34 | 35 | 36 | __all__ = ["start_"] 37 | -------------------------------------------------------------------------------- /reactivex/observable/startasync.py: -------------------------------------------------------------------------------- 1 | from asyncio import Future 2 | from typing import Callable, TypeVar 3 | 4 | from reactivex import Observable, from_future, throw 5 | 6 | _T = TypeVar("_T") 7 | 8 | 9 | def start_async_(function_async: Callable[[], "Future[_T]"]) -> Observable[_T]: 10 | try: 11 | future = function_async() 12 | except Exception as ex: # pylint: disable=broad-except 13 | return throw(ex) 14 | 15 | return from_future(future) 16 | 17 | 18 | __all__ = ["start_async_"] 19 | -------------------------------------------------------------------------------- /reactivex/observable/throw.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Optional, Union 2 | 3 | from reactivex import Observable, abc 4 | from reactivex.scheduler import ImmediateScheduler 5 | 6 | 7 | def throw_( 8 | exception: Union[str, Exception], scheduler: Optional[abc.SchedulerBase] = None 9 | ) -> Observable[Any]: 10 | exception_ = exception if isinstance(exception, Exception) else Exception(exception) 11 | 12 | def subscribe( 13 | observer: abc.ObserverBase[Any], scheduler: Optional[abc.SchedulerBase] = None 14 | ) -> abc.DisposableBase: 15 | _scheduler = scheduler or ImmediateScheduler.singleton() 16 | 17 | def action(scheduler: abc.SchedulerBase, state: Any) -> None: 18 | observer.on_error(exception_) 19 | 20 | return _scheduler.schedule(action) 21 | 22 | return Observable(subscribe) 23 | 24 | 25 | __all__ = ["throw_"] 26 | -------------------------------------------------------------------------------- /reactivex/observable/toasync.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Callable, Optional, TypeVar 2 | 3 | from reactivex import Observable, abc 4 | from reactivex import operators as ops 5 | from reactivex.scheduler import TimeoutScheduler 6 | from reactivex.subject import AsyncSubject 7 | 8 | _T = TypeVar("_T") 9 | 10 | 11 | def to_async_( 12 | func: Callable[..., _T], scheduler: Optional[abc.SchedulerBase] = None 13 | ) -> Callable[..., Observable[_T]]: 14 | """Converts the function into an asynchronous function. Each 15 | invocation of the resulting asynchronous function causes an 16 | invocation of the original synchronous function on the specified 17 | scheduler. 18 | 19 | Examples: 20 | res = reactivex.to_async(lambda x, y: x + y)(4, 3) 21 | res = reactivex.to_async(lambda x, y: x + y, Scheduler.timeout)(4, 3) 22 | res = reactivex.to_async(lambda x: log.debug(x), Scheduler.timeout)('hello') 23 | 24 | Args: 25 | func: Function to convert to an asynchronous function. 26 | scheduler: [Optional] Scheduler to run the function on. If not 27 | specified, defaults to Scheduler.timeout. 28 | 29 | Returns: 30 | Aynchronous function. 31 | """ 32 | 33 | _scheduler = scheduler or TimeoutScheduler.singleton() 34 | 35 | def wrapper(*args: Any) -> Observable[_T]: 36 | subject: AsyncSubject[_T] = AsyncSubject() 37 | 38 | def action(scheduler: abc.SchedulerBase, state: Any = None) -> None: 39 | try: 40 | result = func(*args) 41 | except Exception as ex: # pylint: disable=broad-except 42 | subject.on_error(ex) 43 | return 44 | 45 | subject.on_next(result) 46 | subject.on_completed() 47 | 48 | _scheduler.schedule(action) 49 | return subject.pipe(ops.as_observable()) 50 | 51 | return wrapper 52 | 53 | 54 | __all__ = ["to_async_"] 55 | -------------------------------------------------------------------------------- /reactivex/observable/using.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, Optional, TypeVar 2 | 3 | import reactivex 4 | from reactivex import Observable, abc 5 | from reactivex.disposable import CompositeDisposable, Disposable 6 | 7 | _T = TypeVar("_T") 8 | 9 | 10 | def using_( 11 | resource_factory: Callable[[], Optional[abc.DisposableBase]], 12 | observable_factory: Callable[[Optional[abc.DisposableBase]], Observable[_T]], 13 | ) -> Observable[_T]: 14 | """Constructs an observable sequence that depends on a resource 15 | object, whose lifetime is tied to the resulting observable 16 | sequence's lifetime. 17 | 18 | Example: 19 | >>> res = reactivex.using(lambda: AsyncSubject(), lambda: s: s) 20 | 21 | Args: 22 | resource_factory: Factory function to obtain a resource object. 23 | observable_factory: Factory function to obtain an observable 24 | sequence that depends on the obtained resource. 25 | 26 | Returns: 27 | An observable sequence whose lifetime controls the lifetime 28 | of the dependent resource object. 29 | """ 30 | 31 | def subscribe( 32 | observer: abc.ObserverBase[_T], scheduler: Optional[abc.SchedulerBase] = None 33 | ) -> abc.DisposableBase: 34 | disp: abc.DisposableBase = Disposable() 35 | 36 | try: 37 | resource = resource_factory() 38 | if resource is not None: 39 | disp = resource 40 | 41 | source = observable_factory(resource) 42 | except Exception as exception: # pylint: disable=broad-except 43 | d = reactivex.throw(exception).subscribe(observer, scheduler=scheduler) 44 | return CompositeDisposable(d, disp) 45 | 46 | return CompositeDisposable( 47 | source.subscribe(observer, scheduler=scheduler), disp 48 | ) 49 | 50 | return Observable(subscribe) 51 | 52 | 53 | __all__ = ["using_"] 54 | -------------------------------------------------------------------------------- /reactivex/observer/__init__.py: -------------------------------------------------------------------------------- 1 | from .autodetachobserver import AutoDetachObserver 2 | from .observeonobserver import ObserveOnObserver 3 | from .observer import Observer 4 | from .scheduledobserver import ScheduledObserver 5 | 6 | __all__ = ["AutoDetachObserver", "ObserveOnObserver", "Observer", "ScheduledObserver"] 7 | -------------------------------------------------------------------------------- /reactivex/observer/autodetachobserver.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, TypeVar 2 | 3 | from reactivex.disposable import SingleAssignmentDisposable 4 | from reactivex.internal import default_error, noop 5 | 6 | from .. import abc, typing 7 | 8 | _T_in = TypeVar("_T_in", contravariant=True) 9 | 10 | 11 | class AutoDetachObserver(abc.ObserverBase[_T_in]): 12 | def __init__( 13 | self, 14 | on_next: Optional[typing.OnNext[_T_in]] = None, 15 | on_error: Optional[typing.OnError] = None, 16 | on_completed: Optional[typing.OnCompleted] = None, 17 | ) -> None: 18 | self._on_next = on_next or noop 19 | self._on_error = on_error or default_error 20 | self._on_completed = on_completed or noop 21 | 22 | self._subscription = SingleAssignmentDisposable() 23 | self.is_stopped = False 24 | 25 | def on_next(self, value: _T_in) -> None: 26 | if self.is_stopped: 27 | return 28 | self._on_next(value) 29 | 30 | def on_error(self, error: Exception) -> None: 31 | if self.is_stopped: 32 | return 33 | self.is_stopped = True 34 | 35 | try: 36 | self._on_error(error) 37 | finally: 38 | self.dispose() 39 | 40 | def on_completed(self) -> None: 41 | if self.is_stopped: 42 | return 43 | self.is_stopped = True 44 | 45 | try: 46 | self._on_completed() 47 | finally: 48 | self.dispose() 49 | 50 | def set_disposable(self, value: abc.DisposableBase) -> None: 51 | self._subscription.disposable = value 52 | 53 | subscription = property(fset=set_disposable) 54 | 55 | def dispose(self) -> None: 56 | self.is_stopped = True 57 | self._subscription.dispose() 58 | 59 | def fail(self, exn: Exception) -> bool: 60 | if self.is_stopped: 61 | return False 62 | 63 | self.is_stopped = True 64 | self._on_error(exn) 65 | return True 66 | -------------------------------------------------------------------------------- /reactivex/observer/observeonobserver.py: -------------------------------------------------------------------------------- 1 | from typing import TypeVar 2 | 3 | from .scheduledobserver import ScheduledObserver 4 | 5 | _T = TypeVar("_T") 6 | 7 | 8 | class ObserveOnObserver(ScheduledObserver[_T]): 9 | def _on_next_core(self, value: _T) -> None: 10 | super()._on_next_core(value) 11 | self.ensure_active() 12 | 13 | def _on_error_core(self, error: Exception) -> None: 14 | super()._on_error_core(error) 15 | self.ensure_active() 16 | 17 | def _on_completed_core(self) -> None: 18 | super()._on_completed_core() 19 | self.ensure_active() 20 | -------------------------------------------------------------------------------- /reactivex/operators/_all.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, TypeVar 2 | 3 | from reactivex import Observable, compose 4 | from reactivex import operators as ops 5 | from reactivex.typing import Predicate 6 | 7 | _T = TypeVar("_T") 8 | 9 | 10 | def all_(predicate: Predicate[_T]) -> Callable[[Observable[_T]], Observable[bool]]: 11 | def filter(v: _T): 12 | return not predicate(v) 13 | 14 | def mapping(b: bool) -> bool: 15 | return not b 16 | 17 | return compose( 18 | ops.filter(filter), 19 | ops.some(), 20 | ops.map(mapping), 21 | ) 22 | 23 | 24 | __all__ = ["all_"] 25 | -------------------------------------------------------------------------------- /reactivex/operators/_asobservable.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, Optional, TypeVar 2 | 3 | from reactivex import Observable, abc 4 | 5 | _T = TypeVar("_T") 6 | 7 | 8 | def as_observable_() -> Callable[[Observable[_T]], Observable[_T]]: 9 | def as_observable(source: Observable[_T]) -> Observable[_T]: 10 | """Hides the identity of an observable sequence. 11 | 12 | Args: 13 | source: Observable source to hide identity from. 14 | 15 | Returns: 16 | An observable sequence that hides the identity of the 17 | source sequence. 18 | """ 19 | 20 | def subscribe( 21 | observer: abc.ObserverBase[_T], 22 | scheduler: Optional[abc.SchedulerBase] = None, 23 | ) -> abc.DisposableBase: 24 | return source.subscribe(observer, scheduler=scheduler) 25 | 26 | return Observable(subscribe) 27 | 28 | return as_observable 29 | 30 | 31 | __all__ = ["as_observable_"] 32 | -------------------------------------------------------------------------------- /reactivex/operators/_average.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import Any, Callable, Optional, TypeVar, cast 3 | 4 | from reactivex import Observable, operators, typing 5 | 6 | _T = TypeVar("_T") 7 | 8 | 9 | @dataclass 10 | class AverageValue: 11 | sum: float 12 | count: int 13 | 14 | 15 | def average_( 16 | key_mapper: Optional[typing.Mapper[_T, float]] = None, 17 | ) -> Callable[[Observable[_T]], Observable[float]]: 18 | def average(source: Observable[Any]) -> Observable[float]: 19 | """Partially applied average operator. 20 | 21 | Computes the average of an observable sequence of values that 22 | are in the sequence or obtained by invoking a transform 23 | function on each element of the input sequence if present. 24 | 25 | Examples: 26 | >>> res = average(source) 27 | 28 | Args: 29 | source: Source observable to average. 30 | 31 | Returns: 32 | An observable sequence containing a single element with the 33 | average of the sequence of values. 34 | """ 35 | 36 | key_mapper_: typing.Mapper[_T, float] = key_mapper or ( 37 | lambda x: float(cast(Any, x)) 38 | ) 39 | 40 | def accumulator(prev: AverageValue, cur: float) -> AverageValue: 41 | return AverageValue(sum=prev.sum + cur, count=prev.count + 1) 42 | 43 | def mapper(s: AverageValue) -> float: 44 | if s.count == 0: 45 | raise Exception("The input sequence was empty") 46 | 47 | return s.sum / float(s.count) 48 | 49 | seed = AverageValue(sum=0, count=0) 50 | 51 | ret = source.pipe( 52 | operators.map(key_mapper_), 53 | operators.scan(accumulator, seed), 54 | operators.last(), 55 | operators.map(mapper), 56 | ) 57 | return ret 58 | 59 | return average 60 | 61 | 62 | __all__ = ["average_"] 63 | -------------------------------------------------------------------------------- /reactivex/operators/_bufferwithtime.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, List, Optional, TypeVar 2 | 3 | from reactivex import Observable, abc, compose 4 | from reactivex import operators as ops 5 | from reactivex import typing 6 | 7 | _T = TypeVar("_T") 8 | 9 | 10 | def buffer_with_time_( 11 | timespan: typing.RelativeTime, 12 | timeshift: Optional[typing.RelativeTime] = None, 13 | scheduler: Optional[abc.SchedulerBase] = None, 14 | ) -> Callable[[Observable[_T]], Observable[List[_T]]]: 15 | if not timeshift: 16 | timeshift = timespan 17 | 18 | return compose( 19 | ops.window_with_time(timespan, timeshift, scheduler), 20 | ops.flat_map(ops.to_list()), 21 | ) 22 | 23 | 24 | __all__ = ["buffer_with_time_"] 25 | -------------------------------------------------------------------------------- /reactivex/operators/_bufferwithtimeorcount.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, List, Optional, TypeVar 2 | 3 | from reactivex import Observable, abc, compose 4 | from reactivex import operators as ops 5 | from reactivex import typing 6 | 7 | _T = TypeVar("_T") 8 | 9 | 10 | def buffer_with_time_or_count_( 11 | timespan: typing.RelativeTime, 12 | count: int, 13 | scheduler: Optional[abc.SchedulerBase] = None, 14 | ) -> Callable[[Observable[_T]], Observable[List[_T]]]: 15 | return compose( 16 | ops.window_with_time_or_count(timespan, count, scheduler), 17 | ops.flat_map(ops.to_iterable()), 18 | ) 19 | 20 | 21 | __all__ = ["buffer_with_time_or_count_"] 22 | -------------------------------------------------------------------------------- /reactivex/operators/_combinelatest.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Callable 2 | 3 | import reactivex 4 | from reactivex import Observable 5 | 6 | 7 | def combine_latest_( 8 | *others: Observable[Any], 9 | ) -> Callable[[Observable[Any]], Observable[Any]]: 10 | def combine_latest(source: Observable[Any]) -> Observable[Any]: 11 | """Merges the specified observable sequences into one 12 | observable sequence by creating a tuple whenever any 13 | of the observable sequences produces an element. 14 | 15 | Examples: 16 | >>> obs = combine_latest(source) 17 | 18 | Returns: 19 | An observable sequence containing the result of combining 20 | elements of the sources into a tuple. 21 | """ 22 | 23 | sources = (source,) + others 24 | 25 | return reactivex.combine_latest(*sources) 26 | 27 | return combine_latest 28 | 29 | 30 | __all__ = ["combine_latest_"] 31 | -------------------------------------------------------------------------------- /reactivex/operators/_concat.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, TypeVar 2 | 3 | import reactivex 4 | from reactivex import Observable 5 | 6 | _T = TypeVar("_T") 7 | 8 | 9 | def concat_(*sources: Observable[_T]) -> Callable[[Observable[_T]], Observable[_T]]: 10 | def concat(source: Observable[_T]) -> Observable[_T]: 11 | """Concatenates all the observable sequences. 12 | 13 | Examples: 14 | >>> op = concat(xs, ys, zs) 15 | 16 | Returns: 17 | An operator function that takes one or more observable sources and 18 | returns an observable sequence that contains the elements of 19 | each given sequence, in sequential order. 20 | """ 21 | return reactivex.concat(source, *sources) 22 | 23 | return concat 24 | 25 | 26 | __all__ = ["concat_"] 27 | -------------------------------------------------------------------------------- /reactivex/operators/_contains.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, Optional, TypeVar 2 | 3 | from reactivex import Observable, compose 4 | from reactivex import operators as ops 5 | from reactivex import typing 6 | from reactivex.internal.basic import default_comparer 7 | 8 | _T = TypeVar("_T") 9 | 10 | 11 | def contains_( 12 | value: _T, comparer: Optional[typing.Comparer[_T]] = None 13 | ) -> Callable[[Observable[_T]], Observable[bool]]: 14 | comparer_ = comparer or default_comparer 15 | 16 | def predicate(v: _T) -> bool: 17 | return comparer_(v, value) 18 | 19 | return compose( 20 | ops.filter(predicate), 21 | ops.some(), 22 | ) 23 | 24 | 25 | __all__ = ["contains_"] 26 | -------------------------------------------------------------------------------- /reactivex/operators/_count.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, Optional, TypeVar 2 | 3 | from reactivex import Observable, compose 4 | from reactivex import operators as ops 5 | from reactivex.typing import Predicate 6 | 7 | _T = TypeVar("_T") 8 | 9 | 10 | def count_( 11 | predicate: Optional[Predicate[_T]] = None, 12 | ) -> Callable[[Observable[_T]], Observable[int]]: 13 | 14 | if predicate: 15 | return compose( 16 | ops.filter(predicate), 17 | ops.count(), 18 | ) 19 | 20 | def reducer(n: int, _: _T) -> int: 21 | return n + 1 22 | 23 | counter = ops.reduce(reducer, seed=0) 24 | return counter 25 | 26 | 27 | __all__ = ["count_"] 28 | -------------------------------------------------------------------------------- /reactivex/operators/_defaultifempty.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, Optional, TypeVar 2 | 3 | from reactivex import Observable, abc 4 | 5 | _T = TypeVar("_T") 6 | 7 | 8 | def default_if_empty_( 9 | default_value: Optional[_T] = None, 10 | ) -> Callable[[Observable[_T]], Observable[Optional[_T]]]: 11 | def default_if_empty(source: Observable[_T]) -> Observable[Optional[_T]]: 12 | """Returns the elements of the specified sequence or the 13 | specified value in a singleton sequence if the sequence is 14 | empty. 15 | 16 | Examples: 17 | >>> obs = default_if_empty(source) 18 | 19 | Args: 20 | source: Source observable. 21 | 22 | Returns: 23 | An observable sequence that contains the specified default 24 | value if the source is empty otherwise, the elements of the 25 | source. 26 | """ 27 | 28 | def subscribe( 29 | observer: abc.ObserverBase[Optional[_T]], 30 | scheduler: Optional[abc.SchedulerBase] = None, 31 | ) -> abc.DisposableBase: 32 | found = [False] 33 | 34 | def on_next(x: _T): 35 | found[0] = True 36 | observer.on_next(x) 37 | 38 | def on_completed(): 39 | if not found[0]: 40 | observer.on_next(default_value) 41 | observer.on_completed() 42 | 43 | return source.subscribe( 44 | on_next, observer.on_error, on_completed, scheduler=scheduler 45 | ) 46 | 47 | return Observable(subscribe) 48 | 49 | return default_if_empty 50 | 51 | 52 | __all__ = ["default_if_empty_"] 53 | -------------------------------------------------------------------------------- /reactivex/operators/_delaysubscription.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Callable, Optional, TypeVar 2 | 3 | import reactivex 4 | from reactivex import Observable, abc 5 | from reactivex import operators as ops 6 | from reactivex import typing 7 | 8 | _T = TypeVar("_T") 9 | 10 | 11 | def delay_subscription_( 12 | duetime: typing.AbsoluteOrRelativeTime, 13 | scheduler: Optional[abc.SchedulerBase] = None, 14 | ) -> Callable[[Observable[_T]], Observable[_T]]: 15 | def delay_subscription(source: Observable[_T]) -> Observable[_T]: 16 | """Time shifts the observable sequence by delaying the subscription. 17 | 18 | Exampeles. 19 | >>> res = source.delay_subscription(5) 20 | 21 | Args: 22 | source: Source subscription to delay. 23 | 24 | Returns: 25 | Time-shifted sequence. 26 | """ 27 | 28 | def mapper(_: Any) -> Observable[_T]: 29 | return reactivex.empty() 30 | 31 | return source.pipe( 32 | ops.delay_with_mapper(reactivex.timer(duetime, scheduler=scheduler), mapper) 33 | ) 34 | 35 | return delay_subscription 36 | 37 | 38 | __all__ = ["delay_subscription_"] 39 | -------------------------------------------------------------------------------- /reactivex/operators/_dematerialize.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, Optional, TypeVar 2 | 3 | from reactivex import Notification, Observable, abc 4 | 5 | _T = TypeVar("_T") 6 | 7 | 8 | def dematerialize_() -> Callable[[Observable[Notification[_T]]], Observable[_T]]: 9 | def dematerialize(source: Observable[Notification[_T]]) -> Observable[_T]: 10 | """Partially applied dematerialize operator. 11 | 12 | Dematerializes the explicit notification values of an 13 | observable sequence as implicit notifications. 14 | 15 | Returns: 16 | An observable sequence exhibiting the behavior 17 | corresponding to the source sequence's notification values. 18 | """ 19 | 20 | def subscribe( 21 | observer: abc.ObserverBase[_T], 22 | scheduler: Optional[abc.SchedulerBase] = None, 23 | ): 24 | def on_next(value: Notification[_T]) -> None: 25 | return value.accept(observer) 26 | 27 | return source.subscribe( 28 | on_next, observer.on_error, observer.on_completed, scheduler=scheduler 29 | ) 30 | 31 | return Observable(subscribe) 32 | 33 | return dematerialize 34 | 35 | 36 | __all__ = ["dematerialize_"] 37 | -------------------------------------------------------------------------------- /reactivex/operators/_dowhile.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, TypeVar 2 | 3 | from reactivex import Observable 4 | from reactivex import operators as ops 5 | 6 | _T = TypeVar("_T") 7 | 8 | 9 | def do_while_( 10 | condition: Callable[[Observable[_T]], bool] 11 | ) -> Callable[[Observable[_T]], Observable[_T]]: 12 | """Repeats source as long as condition holds emulating a do while 13 | loop. 14 | 15 | Args: 16 | condition: The condition which determines if the source will be 17 | repeated. 18 | 19 | Returns: 20 | An observable sequence which is repeated as long 21 | as the condition holds. 22 | """ 23 | 24 | def do_while(source: Observable[_T]) -> Observable[_T]: 25 | return source.pipe( 26 | ops.concat( 27 | source.pipe( 28 | ops.while_do(condition), 29 | ), 30 | ) 31 | ) 32 | 33 | return do_while 34 | 35 | 36 | __all__ = ["do_while_"] 37 | -------------------------------------------------------------------------------- /reactivex/operators/_elementatordefault.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, Optional, TypeVar, cast 2 | 3 | from reactivex import Observable, abc 4 | from reactivex.internal.exceptions import ArgumentOutOfRangeException 5 | 6 | _T = TypeVar("_T") 7 | 8 | 9 | def element_at_or_default_( 10 | index: int, has_default: bool = False, default_value: Optional[_T] = None 11 | ) -> Callable[[Observable[_T]], Observable[_T]]: 12 | if index < 0: 13 | raise ArgumentOutOfRangeException() 14 | 15 | def element_at_or_default(source: Observable[_T]) -> Observable[_T]: 16 | def subscribe( 17 | observer: abc.ObserverBase[_T], 18 | scheduler: Optional[abc.SchedulerBase] = None, 19 | ) -> abc.DisposableBase: 20 | index_ = index 21 | 22 | def on_next(x: _T) -> None: 23 | nonlocal index_ 24 | found = False 25 | with source.lock: 26 | if index_: 27 | index_ -= 1 28 | else: 29 | found = True 30 | 31 | if found: 32 | observer.on_next(x) 33 | observer.on_completed() 34 | 35 | def on_completed(): 36 | if not has_default: 37 | observer.on_error(ArgumentOutOfRangeException()) 38 | else: 39 | observer.on_next(cast(_T, default_value)) 40 | observer.on_completed() 41 | 42 | return source.subscribe( 43 | on_next, observer.on_error, on_completed, scheduler=scheduler 44 | ) 45 | 46 | return Observable(subscribe) 47 | 48 | return element_at_or_default 49 | 50 | 51 | __all__ = ["element_at_or_default_"] 52 | -------------------------------------------------------------------------------- /reactivex/operators/_finallyaction.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, Optional, TypeVar 2 | 3 | from reactivex import Observable, abc, typing 4 | from reactivex.disposable import Disposable 5 | 6 | _T = TypeVar("_T") 7 | 8 | 9 | def finally_action_( 10 | action: typing.Action, 11 | ) -> Callable[[Observable[_T]], Observable[_T]]: 12 | def finally_action(source: Observable[_T]) -> Observable[_T]: 13 | """Invokes a specified action after the source observable 14 | sequence terminates gracefully or exceptionally. 15 | 16 | Example: 17 | res = finally(source) 18 | 19 | Args: 20 | source: Observable sequence. 21 | 22 | Returns: 23 | An observable sequence with the action-invoking termination 24 | behavior applied. 25 | """ 26 | 27 | def subscribe( 28 | observer: abc.ObserverBase[_T], 29 | scheduler: Optional[abc.SchedulerBase] = None, 30 | ) -> abc.DisposableBase: 31 | try: 32 | subscription = source.subscribe(observer, scheduler=scheduler) 33 | except Exception: 34 | action() 35 | raise 36 | 37 | def dispose(): 38 | try: 39 | subscription.dispose() 40 | finally: 41 | action() 42 | 43 | return Disposable(dispose) 44 | 45 | return Observable(subscribe) 46 | 47 | return finally_action 48 | 49 | 50 | __all__ = ["finally_action_"] 51 | -------------------------------------------------------------------------------- /reactivex/operators/_find.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, Optional, TypeVar, Union 2 | 3 | from reactivex import Observable, abc 4 | 5 | _T = TypeVar("_T") 6 | 7 | 8 | def find_value_( 9 | predicate: Callable[[_T, int, Observable[_T]], bool], yield_index: bool 10 | ) -> Callable[[Observable[_T]], Observable[Union[_T, int, None]]]: 11 | def find_value(source: Observable[_T]) -> Observable[Union[_T, int, None]]: 12 | def subscribe( 13 | observer: abc.ObserverBase[Union[_T, int, None]], 14 | scheduler: Optional[abc.SchedulerBase] = None, 15 | ) -> abc.DisposableBase: 16 | index = 0 17 | 18 | def on_next(x: _T) -> None: 19 | nonlocal index 20 | should_run = False 21 | try: 22 | should_run = predicate(x, index, source) 23 | except Exception as ex: # pylint: disable=broad-except 24 | observer.on_error(ex) 25 | return 26 | 27 | if should_run: 28 | observer.on_next(index if yield_index else x) 29 | observer.on_completed() 30 | else: 31 | index += 1 32 | 33 | def on_completed(): 34 | observer.on_next(-1 if yield_index else None) 35 | observer.on_completed() 36 | 37 | return source.subscribe( 38 | on_next, observer.on_error, on_completed, scheduler=scheduler 39 | ) 40 | 41 | return Observable(subscribe) 42 | 43 | return find_value 44 | 45 | 46 | __all__ = ["find_value_"] 47 | -------------------------------------------------------------------------------- /reactivex/operators/_first.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, Optional, TypeVar 2 | 3 | from reactivex import Observable, compose 4 | from reactivex import operators as ops 5 | from reactivex.typing import Predicate 6 | 7 | from ._firstordefault import first_or_default_async_ 8 | 9 | _T = TypeVar("_T") 10 | 11 | 12 | def first_( 13 | predicate: Optional[Predicate[_T]] = None, 14 | ) -> Callable[[Observable[_T]], Observable[_T]]: 15 | """Returns the first element of an observable sequence that 16 | satisfies the condition in the predicate if present else the first 17 | item in the sequence. 18 | 19 | Examples: 20 | >>> res = res = first()(source) 21 | >>> res = res = first(lambda x: x > 3)(source) 22 | 23 | Args: 24 | predicate -- [Optional] A predicate function to evaluate for 25 | elements in the source sequence. 26 | 27 | Returns: 28 | A function that takes an observable source and returns an 29 | observable sequence containing the first element in the 30 | observable sequence that satisfies the condition in the predicate if 31 | provided, else the first item in the sequence. 32 | """ 33 | 34 | if predicate: 35 | return compose(ops.filter(predicate), ops.first()) 36 | 37 | return first_or_default_async_(False) 38 | 39 | 40 | __all__ = ["first_"] 41 | -------------------------------------------------------------------------------- /reactivex/operators/_forkjoin.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Callable, Tuple 2 | 3 | import reactivex 4 | from reactivex import Observable 5 | 6 | 7 | def fork_join_( 8 | *args: Observable[Any], 9 | ) -> Callable[[Observable[Any]], Observable[Tuple[Any, ...]]]: 10 | def fork_join(source: Observable[Any]) -> Observable[Tuple[Any, ...]]: 11 | """Wait for observables to complete and then combine last values 12 | they emitted into a tuple. Whenever any of that observables 13 | completes without emitting any value, result sequence will 14 | complete at that moment as well. 15 | 16 | Examples: 17 | >>> obs = fork_join(source) 18 | 19 | Returns: 20 | An observable sequence containing the result of combining 21 | last element from each source in given sequence. 22 | """ 23 | return reactivex.fork_join(source, *args) 24 | 25 | return fork_join 26 | 27 | 28 | __all__ = ["fork_join_"] 29 | -------------------------------------------------------------------------------- /reactivex/operators/_groupby.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Callable, Optional, TypeVar 2 | 3 | from reactivex import GroupedObservable, Observable, typing 4 | from reactivex.subject import Subject 5 | 6 | _T = TypeVar("_T") 7 | _TKey = TypeVar("_TKey") 8 | _TValue = TypeVar("_TValue") 9 | 10 | # pylint: disable=import-outside-toplevel 11 | 12 | 13 | def group_by_( 14 | key_mapper: typing.Mapper[_T, _TKey], 15 | element_mapper: Optional[typing.Mapper[_T, _TValue]] = None, 16 | subject_mapper: Optional[Callable[[], Subject[_TValue]]] = None, 17 | ) -> Callable[[Observable[_T]], Observable[GroupedObservable[_TKey, _TValue]]]: 18 | from reactivex import operators as ops 19 | 20 | def duration_mapper(_: GroupedObservable[Any, Any]) -> Observable[Any]: 21 | import reactivex 22 | 23 | return reactivex.never() 24 | 25 | return ops.group_by_until( 26 | key_mapper, element_mapper, duration_mapper, subject_mapper 27 | ) 28 | 29 | 30 | __all__ = ["group_by_"] 31 | -------------------------------------------------------------------------------- /reactivex/operators/_ignoreelements.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, Optional, TypeVar 2 | 3 | from reactivex import Observable, abc 4 | from reactivex.internal import noop 5 | 6 | _T = TypeVar("_T") 7 | 8 | 9 | def ignore_elements_() -> Callable[[Observable[_T]], Observable[_T]]: 10 | """Ignores all elements in an observable sequence leaving only the 11 | termination messages. 12 | 13 | Returns: 14 | An empty observable {Observable} sequence that signals 15 | termination, successful or exceptional, of the source sequence. 16 | """ 17 | 18 | def ignore_elements(source: Observable[_T]) -> Observable[_T]: 19 | def subscribe( 20 | observer: abc.ObserverBase[_T], 21 | scheduler: Optional[abc.SchedulerBase] = None, 22 | ) -> abc.DisposableBase: 23 | return source.subscribe( 24 | noop, observer.on_error, observer.on_completed, scheduler=scheduler 25 | ) 26 | 27 | return Observable(subscribe) 28 | 29 | return ignore_elements 30 | 31 | 32 | __all__ = ["ignore_elements_"] 33 | -------------------------------------------------------------------------------- /reactivex/operators/_isempty.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Callable 2 | 3 | from reactivex import Observable, compose 4 | from reactivex import operators as ops 5 | 6 | 7 | def is_empty_() -> Callable[[Observable[Any]], Observable[bool]]: 8 | """Determines whether an observable sequence is empty. 9 | 10 | Returns: 11 | An observable sequence containing a single element 12 | determining whether the source sequence is empty. 13 | """ 14 | 15 | def mapper(b: bool) -> bool: 16 | return not b 17 | 18 | return compose( 19 | ops.some(), 20 | ops.map(mapper), 21 | ) 22 | 23 | 24 | __all__ = ["is_empty_"] 25 | -------------------------------------------------------------------------------- /reactivex/operators/_last.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Callable, Optional, TypeVar 2 | 3 | from reactivex import Observable, operators 4 | from reactivex.typing import Predicate 5 | 6 | from ._lastordefault import last_or_default_async 7 | 8 | _T = TypeVar("_T") 9 | 10 | 11 | def last_( 12 | predicate: Optional[Predicate[_T]] = None, 13 | ) -> Callable[[Observable[_T]], Observable[Any]]: 14 | def last(source: Observable[_T]) -> Observable[Any]: 15 | """Partially applied last operator. 16 | 17 | Returns the last element of an observable sequence that 18 | satisfies the condition in the predicate if specified, else 19 | the last element. 20 | 21 | Examples: 22 | >>> res = last(source) 23 | 24 | Args: 25 | source: Source observable to get last item from. 26 | 27 | Returns: 28 | An observable sequence containing the last element in the 29 | observable sequence that satisfies the condition in the 30 | predicate. 31 | """ 32 | 33 | if predicate: 34 | return source.pipe( 35 | operators.filter(predicate), 36 | operators.last(), 37 | ) 38 | 39 | return last_or_default_async(source, False) 40 | 41 | return last 42 | 43 | 44 | __all__ = ["last_"] 45 | -------------------------------------------------------------------------------- /reactivex/operators/_materialize.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, Optional, TypeVar 2 | 3 | from reactivex import Observable, abc 4 | from reactivex.notification import Notification, OnCompleted, OnError, OnNext 5 | 6 | _T = TypeVar("_T") 7 | 8 | 9 | def materialize() -> Callable[[Observable[_T]], Observable[Notification[_T]]]: 10 | def materialize(source: Observable[_T]) -> Observable[Notification[_T]]: 11 | """Partially applied materialize operator. 12 | 13 | Materializes the implicit notifications of an observable 14 | sequence as explicit notification values. 15 | 16 | Args: 17 | source: Source observable to materialize. 18 | 19 | Returns: 20 | An observable sequence containing the materialized 21 | notification values from the source sequence. 22 | """ 23 | 24 | def subscribe( 25 | observer: abc.ObserverBase[Notification[_T]], 26 | scheduler: Optional[abc.SchedulerBase] = None, 27 | ): 28 | def on_next(value: _T) -> None: 29 | observer.on_next(OnNext(value)) 30 | 31 | def on_error(error: Exception) -> None: 32 | observer.on_next(OnError(error)) 33 | observer.on_completed() 34 | 35 | def on_completed() -> None: 36 | observer.on_next(OnCompleted()) 37 | observer.on_completed() 38 | 39 | return source.subscribe( 40 | on_next, on_error, on_completed, scheduler=scheduler 41 | ) 42 | 43 | return Observable(subscribe) 44 | 45 | return materialize 46 | 47 | 48 | __all__ = ["materialize"] 49 | -------------------------------------------------------------------------------- /reactivex/operators/_max.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, Optional, TypeVar, cast 2 | 3 | from reactivex import Observable, compose 4 | from reactivex import operators as ops 5 | from reactivex.internal.basic import identity 6 | from reactivex.typing import Comparer 7 | 8 | from ._min import first_only 9 | 10 | _T = TypeVar("_T") 11 | 12 | 13 | def max_( 14 | comparer: Optional[Comparer[_T]] = None, 15 | ) -> Callable[[Observable[_T]], Observable[_T]]: 16 | """Returns the maximum value in an observable sequence according to 17 | the specified comparer. 18 | 19 | Examples: 20 | >>> op = max() 21 | >>> op = max(lambda x, y: x.value - y.value) 22 | 23 | Args: 24 | comparer: [Optional] Comparer used to compare elements. 25 | 26 | Returns: 27 | An operator function that takes an observable source and returns 28 | an observable sequence containing a single element with the 29 | maximum element in the source sequence. 30 | """ 31 | return compose( 32 | ops.max_by(cast(Callable[[_T], _T], identity), comparer), 33 | ops.map(first_only), 34 | ) 35 | 36 | 37 | __all__ = ["max_"] 38 | -------------------------------------------------------------------------------- /reactivex/operators/_maxby.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, List, Optional, TypeVar 2 | 3 | from reactivex import Observable, typing 4 | from reactivex.internal.basic import default_sub_comparer 5 | 6 | from ._minby import extrema_by 7 | 8 | _T = TypeVar("_T") 9 | _TKey = TypeVar("_TKey") 10 | 11 | 12 | def max_by_( 13 | key_mapper: typing.Mapper[_T, _TKey], 14 | comparer: Optional[typing.SubComparer[_TKey]] = None, 15 | ) -> Callable[[Observable[_T]], Observable[List[_T]]]: 16 | 17 | cmp = comparer or default_sub_comparer 18 | 19 | def max_by(source: Observable[_T]) -> Observable[List[_T]]: 20 | """Partially applied max_by operator. 21 | 22 | Returns the elements in an observable sequence with the maximum 23 | key value. 24 | 25 | Examples: 26 | >>> res = max_by(source) 27 | 28 | Args: 29 | source: The source observable sequence to. 30 | 31 | Returns: 32 | An observable sequence containing a list of zero or more 33 | elements that have a maximum key value. 34 | """ 35 | return extrema_by(source, key_mapper, cmp) 36 | 37 | return max_by 38 | 39 | 40 | __all__ = ["max_by_"] 41 | -------------------------------------------------------------------------------- /reactivex/operators/_min.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, List, Optional, TypeVar, cast 2 | 3 | from reactivex import Observable, compose 4 | from reactivex import operators as ops 5 | from reactivex.internal.basic import identity 6 | from reactivex.internal.exceptions import SequenceContainsNoElementsError 7 | from reactivex.typing import Comparer 8 | 9 | _T = TypeVar("_T") 10 | 11 | 12 | def first_only(x: List[_T]) -> _T: 13 | if not x: 14 | raise SequenceContainsNoElementsError() 15 | 16 | return x[0] 17 | 18 | 19 | def min_( 20 | comparer: Optional[Comparer[_T]] = None, 21 | ) -> Callable[[Observable[_T]], Observable[_T]]: 22 | """The `min` operator. 23 | 24 | Returns the minimum element in an observable sequence according to 25 | the optional comparer else a default greater than less than check. 26 | 27 | Examples: 28 | >>> res = source.min() 29 | >>> res = source.min(lambda x, y: x.value - y.value) 30 | 31 | Args: 32 | comparer: [Optional] Comparer used to compare elements. 33 | 34 | Returns: 35 | An observable sequence containing a single element 36 | with the minimum element in the source sequence. 37 | """ 38 | return compose( 39 | ops.min_by(cast(Callable[[_T], _T], identity), comparer), 40 | ops.map(first_only), 41 | ) 42 | 43 | 44 | __all__ = ["min_"] 45 | -------------------------------------------------------------------------------- /reactivex/operators/_observeon.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, Optional, TypeVar 2 | 3 | from reactivex import Observable, abc 4 | from reactivex.observer import ObserveOnObserver 5 | 6 | _T = TypeVar("_T") 7 | 8 | 9 | def observe_on_( 10 | scheduler: abc.SchedulerBase, 11 | ) -> Callable[[Observable[_T]], Observable[_T]]: 12 | def observe_on(source: Observable[_T]) -> Observable[_T]: 13 | """Wraps the source sequence in order to run its observer 14 | callbacks on the specified scheduler. 15 | 16 | This only invokes observer callbacks on a scheduler. In case 17 | the subscription and/or unsubscription actions have 18 | side-effects that require to be run on a scheduler, use 19 | subscribe_on. 20 | 21 | Args: 22 | source: Source observable. 23 | 24 | 25 | Returns: 26 | Returns the source sequence whose observations happen on 27 | the specified scheduler. 28 | """ 29 | 30 | def subscribe( 31 | observer: abc.ObserverBase[_T], 32 | subscribe_scheduler: Optional[abc.SchedulerBase] = None, 33 | ): 34 | return source.subscribe( 35 | ObserveOnObserver(scheduler, observer), scheduler=subscribe_scheduler 36 | ) 37 | 38 | return Observable(subscribe) 39 | 40 | return observe_on 41 | 42 | 43 | __all__ = ["observe_on_"] 44 | -------------------------------------------------------------------------------- /reactivex/operators/_onerrorresumenext.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, TypeVar 2 | 3 | import reactivex 4 | from reactivex import Observable 5 | 6 | _T = TypeVar("_T") 7 | 8 | 9 | def on_error_resume_next_( 10 | second: Observable[_T], 11 | ) -> Callable[[Observable[_T]], Observable[_T]]: 12 | def on_error_resume_next(source: Observable[_T]) -> Observable[_T]: 13 | return reactivex.on_error_resume_next(source, second) 14 | 15 | return on_error_resume_next 16 | 17 | 18 | __all__ = ["on_error_resume_next_"] 19 | -------------------------------------------------------------------------------- /reactivex/operators/_pairwise.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, Optional, Tuple, TypeVar, cast 2 | 3 | from reactivex import Observable, abc 4 | 5 | _T = TypeVar("_T") 6 | 7 | 8 | def pairwise_() -> Callable[[Observable[_T]], Observable[Tuple[_T, _T]]]: 9 | def pairwise(source: Observable[_T]) -> Observable[Tuple[_T, _T]]: 10 | """Partially applied pairwise operator. 11 | 12 | Returns a new observable that triggers on the second and 13 | subsequent triggerings of the input observable. The Nth 14 | triggering of the input observable passes the arguments from 15 | the N-1th and Nth triggering as a pair. The argument passed to 16 | the N-1th triggering is held in hidden internal state until the 17 | Nth triggering occurs. 18 | 19 | Returns: 20 | An observable that triggers on successive pairs of 21 | observations from the input observable as an array. 22 | """ 23 | 24 | def subscribe( 25 | observer: abc.ObserverBase[Tuple[_T, _T]], 26 | scheduler: Optional[abc.SchedulerBase] = None, 27 | ) -> abc.DisposableBase: 28 | has_previous = False 29 | previous: _T = cast(_T, None) 30 | 31 | def on_next(x: _T) -> None: 32 | nonlocal has_previous, previous 33 | pair = None 34 | 35 | with source.lock: 36 | if has_previous: 37 | pair = (previous, x) 38 | else: 39 | has_previous = True 40 | 41 | previous = x 42 | 43 | if pair: 44 | observer.on_next(pair) 45 | 46 | return source.subscribe(on_next, observer.on_error, observer.on_completed) 47 | 48 | return Observable(subscribe) 49 | 50 | return pairwise 51 | 52 | 53 | __all__ = ["pairwise_"] 54 | -------------------------------------------------------------------------------- /reactivex/operators/_pluck.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Callable, Dict, TypeVar 2 | 3 | from reactivex import Observable 4 | from reactivex import operators as ops 5 | 6 | _TKey = TypeVar("_TKey") 7 | _TValue = TypeVar("_TValue") 8 | 9 | 10 | def pluck_( 11 | key: _TKey, 12 | ) -> Callable[[Observable[Dict[_TKey, _TValue]]], Observable[_TValue]]: 13 | """Retrieves the value of a specified key using dict-like access (as in 14 | element[key]) from all elements in the Observable sequence. 15 | 16 | Args: 17 | key: The key to pluck. 18 | 19 | Returns a new Observable {Observable} sequence of key values. 20 | 21 | To pluck an attribute of each element, use pluck_attr. 22 | """ 23 | 24 | def mapper(x: Dict[_TKey, _TValue]) -> _TValue: 25 | return x[key] 26 | 27 | return ops.map(mapper) 28 | 29 | 30 | def pluck_attr_(prop: str) -> Callable[[Observable[Any]], Observable[Any]]: 31 | """Retrieves the value of a specified property (using getattr) from 32 | all elements in the Observable sequence. 33 | 34 | Args: 35 | property: The property to pluck. 36 | 37 | Returns a new Observable {Observable} sequence of property values. 38 | 39 | To pluck values using dict-like access (as in element[key]) on each 40 | element, use pluck. 41 | """ 42 | 43 | return ops.map(lambda x: getattr(x, prop)) 44 | 45 | 46 | __all__ = ["pluck_", "pluck_attr_"] 47 | -------------------------------------------------------------------------------- /reactivex/operators/_publishvalue.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, Optional, TypeVar, Union 2 | 3 | from reactivex import ConnectableObservable, Observable, abc 4 | from reactivex import operators as ops 5 | from reactivex.subject import BehaviorSubject 6 | from reactivex.typing import Mapper 7 | 8 | _T1 = TypeVar("_T1") 9 | _T2 = TypeVar("_T2") 10 | 11 | 12 | def publish_value_( 13 | initial_value: _T1, 14 | mapper: Optional[Mapper[Observable[_T1], Observable[_T2]]] = None, 15 | ) -> Union[ 16 | Callable[[Observable[_T1]], ConnectableObservable[_T1]], 17 | Callable[[Observable[_T1]], Observable[_T2]], 18 | ]: 19 | if mapper: 20 | 21 | def subject_factory( 22 | scheduler: Optional[abc.SchedulerBase] = None, 23 | ) -> BehaviorSubject[_T1]: 24 | return BehaviorSubject(initial_value) 25 | 26 | return ops.multicast(subject_factory=subject_factory, mapper=mapper) 27 | 28 | subject = BehaviorSubject(initial_value) 29 | return ops.multicast(subject) 30 | 31 | 32 | __all__ = ["publish_value_"] 33 | -------------------------------------------------------------------------------- /reactivex/operators/_reduce.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Callable, Type, TypeVar, Union, cast 2 | 3 | from reactivex import Observable, compose 4 | from reactivex import operators as ops 5 | from reactivex.internal.utils import NotSet 6 | from reactivex.typing import Accumulator 7 | 8 | _T = TypeVar("_T") 9 | _TState = TypeVar("_TState") 10 | 11 | 12 | def reduce_( 13 | accumulator: Accumulator[_TState, _T], seed: Union[_TState, Type[NotSet]] = NotSet 14 | ) -> Callable[[Observable[_T]], Observable[Any]]: 15 | """Applies an accumulator function over an observable sequence, 16 | returning the result of the aggregation as a single element in the 17 | result sequence. The specified seed value is used as the initial 18 | accumulator value. 19 | 20 | For aggregation behavior with incremental intermediate results, see 21 | `scan()`. 22 | 23 | Examples: 24 | >>> res = reduce(lambda acc, x: acc + x) 25 | >>> res = reduce(lambda acc, x: acc + x, 0) 26 | 27 | Args: 28 | accumulator: An accumulator function to be 29 | invoked on each element. 30 | seed: Optional initial accumulator value. 31 | 32 | Returns: 33 | An operator function that takes an observable source and returns 34 | an observable sequence containing a single element with the 35 | final accumulator value. 36 | """ 37 | if seed is not NotSet: 38 | seed_: _TState = cast(_TState, seed) 39 | scanner = ops.scan(accumulator, seed=seed_) 40 | return compose( 41 | scanner, 42 | ops.last_or_default(default_value=seed_), 43 | ) 44 | 45 | return compose( 46 | ops.scan(cast(Accumulator[_T, _T], accumulator)), 47 | ops.last(), 48 | ) 49 | 50 | 51 | __all__ = ["reduce_"] 52 | -------------------------------------------------------------------------------- /reactivex/operators/_repeat.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, Optional, TypeVar 2 | 3 | import reactivex 4 | from reactivex import Observable 5 | from reactivex.internal.utils import infinite 6 | 7 | _T = TypeVar("_T") 8 | 9 | 10 | def repeat_( 11 | repeat_count: Optional[int] = None, 12 | ) -> Callable[[Observable[_T]], Observable[_T]]: 13 | def repeat(source: Observable[_T]) -> Observable[_T]: 14 | """Repeats the observable sequence a specified number of times. 15 | If the repeat count is not specified, the sequence repeats 16 | indefinitely. 17 | 18 | Examples: 19 | >>> repeated = source.repeat() 20 | >>> repeated = source.repeat(42) 21 | 22 | Args: 23 | source: The observable source to repeat. 24 | 25 | Returns: 26 | The observable sequence producing the elements of the given 27 | sequence repeatedly. 28 | """ 29 | 30 | if repeat_count is None: 31 | gen = infinite() 32 | else: 33 | gen = range(repeat_count) 34 | 35 | return reactivex.defer( 36 | lambda _: reactivex.concat_with_iterable(source for _ in gen) 37 | ) 38 | 39 | return repeat 40 | 41 | 42 | __all = ["repeat"] 43 | -------------------------------------------------------------------------------- /reactivex/operators/_retry.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, Optional, TypeVar 2 | 3 | import reactivex 4 | from reactivex import Observable 5 | from reactivex.internal.utils import infinite 6 | 7 | _T = TypeVar("_T") 8 | 9 | 10 | def retry_( 11 | retry_count: Optional[int] = None, 12 | ) -> Callable[[Observable[_T]], Observable[_T]]: 13 | """Repeats the source observable sequence the specified number of 14 | times or until it successfully terminates. If the retry count is 15 | not specified, it retries indefinitely. 16 | 17 | Examples: 18 | >>> retried = retry() 19 | >>> retried = retry(42) 20 | 21 | Args: 22 | retry_count: [Optional] Number of times to retry the sequence. 23 | If not provided, retry the sequence indefinitely. 24 | 25 | Returns: 26 | An observable sequence producing the elements of the given 27 | sequence repeatedly until it terminates successfully. 28 | """ 29 | 30 | if retry_count is None: 31 | gen = infinite() 32 | else: 33 | gen = range(retry_count) 34 | 35 | def retry(source: Observable[_T]) -> Observable[_T]: 36 | return reactivex.catch_with_iterable(source for _ in gen) 37 | 38 | return retry 39 | 40 | 41 | __all__ = ["retry_"] 42 | -------------------------------------------------------------------------------- /reactivex/operators/_scan.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, Type, TypeVar, Union, cast 2 | 3 | from reactivex import Observable, abc, defer 4 | from reactivex import operators as ops 5 | from reactivex.internal.utils import NotSet 6 | from reactivex.typing import Accumulator 7 | 8 | _T = TypeVar("_T") 9 | _TState = TypeVar("_TState") 10 | 11 | 12 | def scan_( 13 | accumulator: Accumulator[_TState, _T], seed: Union[_TState, Type[NotSet]] = NotSet 14 | ) -> Callable[[Observable[_T]], Observable[_TState]]: 15 | has_seed = seed is not NotSet 16 | 17 | def scan(source: Observable[_T]) -> Observable[_TState]: 18 | """Partially applied scan operator. 19 | 20 | Applies an accumulator function over an observable sequence and 21 | returns each intermediate result. 22 | 23 | Examples: 24 | >>> scanned = scan(source) 25 | 26 | Args: 27 | source: The observable source to scan. 28 | 29 | Returns: 30 | An observable sequence containing the accumulated values. 31 | """ 32 | 33 | def factory(scheduler: abc.SchedulerBase) -> Observable[_TState]: 34 | has_accumulation = False 35 | accumulation: _TState = cast(_TState, None) 36 | 37 | def projection(x: _T) -> _TState: 38 | nonlocal has_accumulation 39 | nonlocal accumulation 40 | 41 | if has_accumulation: 42 | accumulation = accumulator(accumulation, x) 43 | else: 44 | accumulation = ( 45 | accumulator(cast(_TState, seed), x) 46 | if has_seed 47 | else cast(_TState, x) 48 | ) 49 | has_accumulation = True 50 | 51 | return accumulation 52 | 53 | return source.pipe(ops.map(projection)) 54 | 55 | return defer(factory) 56 | 57 | return scan 58 | 59 | 60 | __all__ = ["scan_"] 61 | -------------------------------------------------------------------------------- /reactivex/operators/_single.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, Optional, TypeVar, cast 2 | 3 | from reactivex import Observable, compose 4 | from reactivex import operators as ops 5 | from reactivex.typing import Predicate 6 | 7 | _T = TypeVar("_T") 8 | 9 | 10 | def single_( 11 | predicate: Optional[Predicate[_T]] = None, 12 | ) -> Callable[[Observable[_T]], Observable[_T]]: 13 | """Returns the only element of an observable sequence that satisfies the 14 | condition in the optional predicate, and reports an exception if there 15 | is not exactly one element in the observable sequence. 16 | 17 | Example: 18 | >>> res = single() 19 | >>> res = single(lambda x: x == 42) 20 | 21 | Args: 22 | predicate -- [Optional] A predicate function to evaluate for 23 | elements in the source sequence. 24 | 25 | Returns: 26 | An observable sequence containing the single element in the 27 | observable sequence that satisfies the condition in the predicate. 28 | """ 29 | 30 | if predicate: 31 | return compose(ops.filter(predicate), ops.single()) 32 | else: 33 | return cast( 34 | Callable[[Observable[_T]], Observable[_T]], 35 | ops.single_or_default_async(False), 36 | ) 37 | 38 | 39 | __all__ = ["single_"] 40 | -------------------------------------------------------------------------------- /reactivex/operators/_skip.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, Optional, TypeVar 2 | 3 | from reactivex import Observable, abc 4 | from reactivex.internal import ArgumentOutOfRangeException 5 | 6 | _T = TypeVar("_T") 7 | 8 | 9 | def skip_(count: int) -> Callable[[Observable[_T]], Observable[_T]]: 10 | if count < 0: 11 | raise ArgumentOutOfRangeException() 12 | 13 | def skip(source: Observable[_T]) -> Observable[_T]: 14 | """The skip operator. 15 | 16 | Bypasses a specified number of elements in an observable sequence 17 | and then returns the remaining elements. 18 | 19 | Args: 20 | source: The source observable. 21 | 22 | Returns: 23 | An observable sequence that contains the elements that occur 24 | after the specified index in the input sequence. 25 | """ 26 | 27 | def subscribe( 28 | observer: abc.ObserverBase[_T], 29 | scheduler: Optional[abc.SchedulerBase] = None, 30 | ): 31 | remaining = count 32 | 33 | def on_next(value: _T) -> None: 34 | nonlocal remaining 35 | 36 | if remaining <= 0: 37 | observer.on_next(value) 38 | else: 39 | remaining -= 1 40 | 41 | return source.subscribe( 42 | on_next, observer.on_error, observer.on_completed, scheduler=scheduler 43 | ) 44 | 45 | return Observable(subscribe) 46 | 47 | return skip 48 | 49 | 50 | __all__ = ["skip_"] 51 | -------------------------------------------------------------------------------- /reactivex/operators/_skiplast.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, List, Optional, TypeVar 2 | 3 | from reactivex import Observable, abc 4 | 5 | _T = TypeVar("_T") 6 | 7 | 8 | def skip_last_(count: int) -> Callable[[Observable[_T]], Observable[_T]]: 9 | def skip_last(source: Observable[_T]) -> Observable[_T]: 10 | """Bypasses a specified number of elements at the end of an 11 | observable sequence. 12 | 13 | This operator accumulates a queue with a length enough to store 14 | the first `count` elements. As more elements are received, 15 | elements are taken from the front of the queue and produced on 16 | the result sequence. This causes elements to be delayed. 17 | 18 | Args: 19 | count: Number of elements to bypass at the end of the 20 | source sequence. 21 | 22 | Returns: 23 | An observable sequence containing the source sequence 24 | elements except for the bypassed ones at the end. 25 | """ 26 | 27 | def subscribe( 28 | observer: abc.ObserverBase[_T], 29 | scheduler: Optional[abc.SchedulerBase] = None, 30 | ): 31 | q: List[_T] = [] 32 | 33 | def on_next(value: _T) -> None: 34 | front = None 35 | with source.lock: 36 | q.append(value) 37 | if len(q) > count: 38 | front = q.pop(0) 39 | 40 | if front is not None: 41 | observer.on_next(front) 42 | 43 | return source.subscribe( 44 | on_next, observer.on_error, observer.on_completed, scheduler=scheduler 45 | ) 46 | 47 | return Observable(subscribe) 48 | 49 | return skip_last 50 | 51 | 52 | __all__ = ["skip_last_"] 53 | -------------------------------------------------------------------------------- /reactivex/operators/_some.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, Optional, TypeVar 2 | 3 | from reactivex import Observable, abc 4 | from reactivex import operators as ops 5 | from reactivex.typing import Predicate 6 | 7 | _T = TypeVar("_T") 8 | 9 | 10 | def some_( 11 | predicate: Optional[Predicate[_T]] = None, 12 | ) -> Callable[[Observable[_T]], Observable[bool]]: 13 | def some(source: Observable[_T]) -> Observable[bool]: 14 | """Partially applied operator. 15 | 16 | Determines whether some element of an observable sequence satisfies a 17 | condition if present, else if some items are in the sequence. 18 | 19 | Example: 20 | >>> obs = some(source) 21 | 22 | Args: 23 | predicate -- A function to test each element for a condition. 24 | 25 | Returns: 26 | An observable sequence containing a single element 27 | determining whether some elements in the source sequence 28 | pass the test in the specified predicate if given, else if 29 | some items are in the sequence. 30 | """ 31 | 32 | def subscribe( 33 | observer: abc.ObserverBase[bool], 34 | scheduler: Optional[abc.SchedulerBase] = None, 35 | ): 36 | def on_next(_: _T): 37 | observer.on_next(True) 38 | observer.on_completed() 39 | 40 | def on_error(): 41 | observer.on_next(False) 42 | observer.on_completed() 43 | 44 | return source.subscribe( 45 | on_next, observer.on_error, on_error, scheduler=scheduler 46 | ) 47 | 48 | if predicate: 49 | return source.pipe( 50 | ops.filter(predicate), 51 | some_(), 52 | ) 53 | 54 | return Observable(subscribe) 55 | 56 | return some 57 | 58 | 59 | __all__ = ["some_"] 60 | -------------------------------------------------------------------------------- /reactivex/operators/_startswith.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, TypeVar 2 | 3 | import reactivex 4 | from reactivex import Observable 5 | 6 | _T = TypeVar("_T") 7 | 8 | 9 | def start_with_(*args: _T) -> Callable[[Observable[_T]], Observable[_T]]: 10 | def start_with(source: Observable[_T]) -> Observable[_T]: 11 | """Partially applied start_with operator. 12 | 13 | Prepends a sequence of values to an observable sequence. 14 | 15 | Example: 16 | >>> start_with(source) 17 | 18 | Returns: 19 | The source sequence prepended with the specified values. 20 | """ 21 | start = reactivex.from_iterable(args) 22 | sequence = [start, source] 23 | return reactivex.concat(*sequence) 24 | 25 | return start_with 26 | 27 | 28 | __all__ = ["start_with_"] 29 | -------------------------------------------------------------------------------- /reactivex/operators/_subscribeon.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Callable, Optional, TypeVar 2 | 3 | from reactivex import Observable, abc 4 | from reactivex.disposable import ( 5 | ScheduledDisposable, 6 | SerialDisposable, 7 | SingleAssignmentDisposable, 8 | ) 9 | 10 | _T = TypeVar("_T") 11 | 12 | 13 | def subscribe_on_( 14 | scheduler: abc.SchedulerBase, 15 | ) -> Callable[[Observable[_T]], Observable[_T]]: 16 | def subscribe_on(source: Observable[_T]) -> Observable[_T]: 17 | """Subscribe on the specified scheduler. 18 | 19 | Wrap the source sequence in order to run its subscription and 20 | unsubscription logic on the specified scheduler. This operation 21 | is not commonly used; see the remarks section for more 22 | information on the distinction between subscribe_on and 23 | observe_on. 24 | 25 | This only performs the side-effects of subscription and 26 | unsubscription on the specified scheduler. In order to invoke 27 | observer callbacks on a scheduler, use observe_on. 28 | 29 | Args: 30 | source: The source observable.. 31 | 32 | Returns: 33 | The source sequence whose subscriptions and 34 | un-subscriptions happen on the specified scheduler. 35 | """ 36 | 37 | def subscribe( 38 | observer: abc.ObserverBase[_T], _: Optional[abc.SchedulerBase] = None 39 | ): 40 | m = SingleAssignmentDisposable() 41 | d = SerialDisposable() 42 | d.disposable = m 43 | 44 | def action(scheduler: abc.SchedulerBase, state: Optional[Any] = None): 45 | d.disposable = ScheduledDisposable( 46 | scheduler, source.subscribe(observer) 47 | ) 48 | 49 | m.disposable = scheduler.schedule(action) 50 | return d 51 | 52 | return Observable(subscribe) 53 | 54 | return subscribe_on 55 | 56 | 57 | __all__ = ["subscribe_on_"] 58 | -------------------------------------------------------------------------------- /reactivex/operators/_sum.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Callable, Optional 2 | 3 | from reactivex import Observable, compose 4 | from reactivex import operators as ops 5 | from reactivex.typing import Mapper 6 | 7 | 8 | def sum_( 9 | key_mapper: Optional[Mapper[Any, float]] = None 10 | ) -> Callable[[Observable[Any]], Observable[float]]: 11 | if key_mapper: 12 | return compose(ops.map(key_mapper), ops.sum()) 13 | 14 | def accumulator(prev: float, cur: float) -> float: 15 | return prev + cur 16 | 17 | return ops.reduce(seed=0, accumulator=accumulator) 18 | 19 | 20 | __all__ = ["sum_"] 21 | -------------------------------------------------------------------------------- /reactivex/operators/_take.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, Optional, TypeVar 2 | 3 | from reactivex import Observable, abc, empty 4 | from reactivex.internal import ArgumentOutOfRangeException 5 | 6 | _T = TypeVar("_T") 7 | 8 | 9 | def take_(count: int) -> Callable[[Observable[_T]], Observable[_T]]: 10 | if count < 0: 11 | raise ArgumentOutOfRangeException() 12 | 13 | def take(source: Observable[_T]) -> Observable[_T]: 14 | """Returns a specified number of contiguous elements from the start of 15 | an observable sequence. 16 | 17 | >>> take(source) 18 | 19 | Keyword arguments: 20 | count -- The number of elements to return. 21 | 22 | Returns an observable sequence that contains the specified number of 23 | elements from the start of the input sequence. 24 | """ 25 | 26 | if not count: 27 | return empty() 28 | 29 | def subscribe( 30 | observer: abc.ObserverBase[_T], 31 | scheduler: Optional[abc.SchedulerBase] = None, 32 | ): 33 | remaining = count 34 | 35 | def on_next(value: _T) -> None: 36 | nonlocal remaining 37 | 38 | if remaining > 0: 39 | remaining -= 1 40 | observer.on_next(value) 41 | if not remaining: 42 | observer.on_completed() 43 | 44 | return source.subscribe( 45 | on_next, observer.on_error, observer.on_completed, scheduler=scheduler 46 | ) 47 | 48 | return Observable(subscribe) 49 | 50 | return take 51 | 52 | 53 | __all__ = ["take_"] 54 | -------------------------------------------------------------------------------- /reactivex/operators/_takelast.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, List, Optional, TypeVar 2 | 3 | from reactivex import Observable, abc 4 | 5 | _T = TypeVar("_T") 6 | 7 | 8 | def take_last_(count: int) -> Callable[[Observable[_T]], Observable[_T]]: 9 | def take_last(source: Observable[_T]) -> Observable[_T]: 10 | """Returns a specified number of contiguous elements from the end of an 11 | observable sequence. 12 | 13 | Example: 14 | >>> res = take_last(source) 15 | 16 | This operator accumulates a buffer with a length enough to store 17 | elements count elements. Upon completion of the source sequence, this 18 | buffer is drained on the result sequence. This causes the elements to be 19 | delayed. 20 | 21 | Args: 22 | source: Number of elements to take from the end of the source 23 | sequence. 24 | 25 | Returns: 26 | An observable sequence containing the specified number of elements 27 | from the end of the source sequence. 28 | """ 29 | 30 | def subscribe( 31 | observer: abc.ObserverBase[_T], 32 | scheduler: Optional[abc.SchedulerBase] = None, 33 | ) -> abc.DisposableBase: 34 | q: List[_T] = [] 35 | 36 | def on_next(x: _T) -> None: 37 | q.append(x) 38 | if len(q) > count: 39 | q.pop(0) 40 | 41 | def on_completed(): 42 | while q: 43 | observer.on_next(q.pop(0)) 44 | observer.on_completed() 45 | 46 | return source.subscribe( 47 | on_next, observer.on_error, on_completed, scheduler=scheduler 48 | ) 49 | 50 | return Observable(subscribe) 51 | 52 | return take_last 53 | 54 | 55 | __all__ = ["take_last_"] 56 | -------------------------------------------------------------------------------- /reactivex/operators/_takelastbuffer.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, List, Optional, TypeVar 2 | 3 | from reactivex import Observable, abc 4 | 5 | _T = TypeVar("_T") 6 | 7 | 8 | def take_last_buffer_(count: int) -> Callable[[Observable[_T]], Observable[List[_T]]]: 9 | def take_last_buffer(source: Observable[_T]) -> Observable[List[_T]]: 10 | """Returns an array with the specified number of contiguous 11 | elements from the end of an observable sequence. 12 | 13 | Example: 14 | >>> res = take_last(source) 15 | 16 | This operator accumulates a buffer with a length enough to 17 | store elements count elements. Upon completion of the source 18 | sequence, this buffer is drained on the result sequence. This 19 | causes the elements to be delayed. 20 | 21 | Args: 22 | source: Source observable to take elements from. 23 | 24 | Returns: 25 | An observable sequence containing a single list with the 26 | specified number of elements from the end of the source 27 | sequence. 28 | """ 29 | 30 | def subscribe( 31 | observer: abc.ObserverBase[List[_T]], 32 | scheduler: Optional[abc.SchedulerBase] = None, 33 | ) -> abc.DisposableBase: 34 | q: List[_T] = [] 35 | 36 | def on_next(x: _T) -> None: 37 | with source.lock: 38 | q.append(x) 39 | if len(q) > count: 40 | q.pop(0) 41 | 42 | def on_completed() -> None: 43 | observer.on_next(q) 44 | observer.on_completed() 45 | 46 | return source.subscribe( 47 | on_next, observer.on_error, on_completed, scheduler=scheduler 48 | ) 49 | 50 | return Observable(subscribe) 51 | 52 | return take_last_buffer 53 | 54 | 55 | __all__ = ["take_last_buffer_"] 56 | -------------------------------------------------------------------------------- /reactivex/operators/_takeuntil.py: -------------------------------------------------------------------------------- 1 | from asyncio import Future 2 | from typing import Callable, Optional, TypeVar, Union 3 | 4 | from reactivex import Observable, abc, from_future 5 | from reactivex.disposable import CompositeDisposable 6 | from reactivex.internal import noop 7 | 8 | _T = TypeVar("_T") 9 | 10 | 11 | def take_until_( 12 | other: Union[Observable[_T], "Future[_T]"] 13 | ) -> Callable[[Observable[_T]], Observable[_T]]: 14 | if isinstance(other, Future): 15 | obs: Observable[_T] = from_future(other) 16 | else: 17 | obs = other 18 | 19 | def take_until(source: Observable[_T]) -> Observable[_T]: 20 | """Returns the values from the source observable sequence until 21 | the other observable sequence produces a value. 22 | 23 | Args: 24 | source: The source observable sequence. 25 | 26 | Returns: 27 | An observable sequence containing the elements of the source 28 | sequence up to the point the other sequence interrupted 29 | further propagation. 30 | """ 31 | 32 | def subscribe( 33 | observer: abc.ObserverBase[_T], 34 | scheduler: Optional[abc.SchedulerBase] = None, 35 | ) -> abc.DisposableBase: 36 | def on_completed(_: _T) -> None: 37 | observer.on_completed() 38 | 39 | return CompositeDisposable( 40 | source.subscribe(observer, scheduler=scheduler), 41 | obs.subscribe( 42 | on_completed, observer.on_error, noop, scheduler=scheduler 43 | ), 44 | ) 45 | 46 | return Observable(subscribe) 47 | 48 | return take_until 49 | 50 | 51 | __all__ = ["take_until_"] 52 | -------------------------------------------------------------------------------- /reactivex/operators/_takeuntilwithtime.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from typing import Any, Callable, Optional, TypeVar 3 | 4 | from reactivex import Observable, abc, typing 5 | from reactivex.disposable import CompositeDisposable 6 | from reactivex.scheduler import TimeoutScheduler 7 | 8 | _T = TypeVar("_T") 9 | 10 | 11 | def take_until_with_time_( 12 | end_time: typing.AbsoluteOrRelativeTime, 13 | scheduler: Optional[abc.SchedulerBase] = None, 14 | ) -> Callable[[Observable[_T]], Observable[_T]]: 15 | def take_until_with_time(source: Observable[_T]) -> Observable[_T]: 16 | """Takes elements for the specified duration until the specified end 17 | time, using the specified scheduler to run timers. 18 | 19 | Examples: 20 | >>> res = take_until_with_time(source) 21 | 22 | Args: 23 | source: Source observale to take elements from. 24 | 25 | Returns: 26 | An observable sequence with the elements taken 27 | until the specified end time. 28 | """ 29 | 30 | def subscribe( 31 | observer: abc.ObserverBase[_T], 32 | scheduler_: Optional[abc.SchedulerBase] = None, 33 | ) -> abc.DisposableBase: 34 | _scheduler = scheduler or scheduler_ or TimeoutScheduler.singleton() 35 | 36 | def action(scheduler: abc.SchedulerBase, state: Any = None): 37 | observer.on_completed() 38 | 39 | if isinstance(end_time, datetime): 40 | task = _scheduler.schedule_absolute(end_time, action) 41 | else: 42 | task = _scheduler.schedule_relative(end_time, action) 43 | 44 | return CompositeDisposable( 45 | task, source.subscribe(observer, scheduler=scheduler_) 46 | ) 47 | 48 | return Observable(subscribe) 49 | 50 | return take_until_with_time 51 | 52 | 53 | __all__ = ["take_until_with_time_"] 54 | -------------------------------------------------------------------------------- /reactivex/operators/_takewithtime.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Callable, Optional, TypeVar 2 | 3 | from reactivex import Observable, abc, typing 4 | from reactivex.disposable import CompositeDisposable 5 | from reactivex.scheduler import TimeoutScheduler 6 | 7 | _T = TypeVar("_T") 8 | 9 | 10 | def take_with_time_( 11 | duration: typing.RelativeTime, scheduler: Optional[abc.SchedulerBase] = None 12 | ) -> Callable[[Observable[_T]], Observable[_T]]: 13 | def take_with_time(source: Observable[_T]) -> Observable[_T]: 14 | """Takes elements for the specified duration from the start of 15 | the observable source sequence. 16 | 17 | Example: 18 | >>> res = take_with_time(source) 19 | 20 | This operator accumulates a queue with a length enough to store 21 | elements received during the initial duration window. As more 22 | elements are received, elements older than the specified 23 | duration are taken from the queue and produced on the result 24 | sequence. This causes elements to be delayed with duration. 25 | 26 | Args: 27 | source: Source observable to take elements from. 28 | 29 | Returns: 30 | An observable sequence with the elements taken during the 31 | specified duration from the start of the source sequence. 32 | """ 33 | 34 | def subscribe( 35 | observer: abc.ObserverBase[_T], 36 | scheduler_: Optional[abc.SchedulerBase] = None, 37 | ) -> abc.DisposableBase: 38 | _scheduler = scheduler or scheduler_ or TimeoutScheduler.singleton() 39 | 40 | def action(scheduler: abc.SchedulerBase, state: Any = None): 41 | observer.on_completed() 42 | 43 | disp = _scheduler.schedule_relative(duration, action) 44 | return CompositeDisposable( 45 | disp, source.subscribe(observer, scheduler=scheduler_) 46 | ) 47 | 48 | return Observable(subscribe) 49 | 50 | return take_with_time 51 | 52 | 53 | __all__ = ["take_with_time_"] 54 | -------------------------------------------------------------------------------- /reactivex/operators/_throttlefirst.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from typing import Callable, Optional, TypeVar 3 | 4 | from reactivex import Observable, abc, typing 5 | from reactivex.scheduler import TimeoutScheduler 6 | 7 | _T = TypeVar("_T") 8 | 9 | 10 | def throttle_first_( 11 | window_duration: typing.RelativeTime, scheduler: Optional[abc.SchedulerBase] = None 12 | ) -> Callable[[Observable[_T]], Observable[_T]]: 13 | def throttle_first(source: Observable[_T]) -> Observable[_T]: 14 | """Returns an observable that emits only the first item emitted 15 | by the source Observable during sequential time windows of a 16 | specified duration. 17 | 18 | Args: 19 | source: Source observable to throttle. 20 | 21 | Returns: 22 | An Observable that performs the throttle operation. 23 | """ 24 | 25 | def subscribe( 26 | observer: abc.ObserverBase[_T], 27 | scheduler_: Optional[abc.SchedulerBase] = None, 28 | ) -> abc.DisposableBase: 29 | _scheduler = scheduler or scheduler_ or TimeoutScheduler.singleton() 30 | 31 | duration = _scheduler.to_timedelta(window_duration or 0.0) 32 | if duration <= _scheduler.to_timedelta(0): 33 | raise ValueError("window_duration cannot be less or equal zero.") 34 | last_on_next: Optional[datetime] = None 35 | 36 | def on_next(x: _T) -> None: 37 | nonlocal last_on_next 38 | emit = False 39 | now = _scheduler.now 40 | 41 | with source.lock: 42 | if not last_on_next or now - last_on_next >= duration: 43 | last_on_next = now 44 | emit = True 45 | if emit: 46 | observer.on_next(x) 47 | 48 | return source.subscribe( 49 | on_next, observer.on_error, observer.on_completed, scheduler=_scheduler 50 | ) 51 | 52 | return Observable(subscribe) 53 | 54 | return throttle_first 55 | 56 | 57 | __all__ = ["throttle_first_"] 58 | -------------------------------------------------------------------------------- /reactivex/operators/_timeinterval.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from datetime import timedelta 3 | from typing import Callable, Generic, Optional, TypeVar 4 | 5 | from reactivex import Observable, abc 6 | from reactivex import operators as ops 7 | from reactivex.scheduler import TimeoutScheduler 8 | 9 | _T = TypeVar("_T") 10 | 11 | 12 | @dataclass 13 | class TimeInterval(Generic[_T]): 14 | value: _T 15 | interval: timedelta 16 | 17 | 18 | def time_interval_( 19 | scheduler: Optional[abc.SchedulerBase] = None, 20 | ) -> Callable[[Observable[_T]], Observable[TimeInterval[_T]]]: 21 | def time_interval(source: Observable[_T]) -> Observable[TimeInterval[_T]]: 22 | """Records the time interval between consecutive values in an 23 | observable sequence. 24 | 25 | >>> res = time_interval(source) 26 | 27 | Return: 28 | An observable sequence with time interval information on 29 | values. 30 | """ 31 | 32 | def subscribe( 33 | observer: abc.ObserverBase[TimeInterval[_T]], 34 | scheduler_: Optional[abc.SchedulerBase] = None, 35 | ) -> abc.DisposableBase: 36 | _scheduler = scheduler or scheduler_ or TimeoutScheduler.singleton() 37 | last = _scheduler.now 38 | 39 | def mapper(value: _T) -> TimeInterval[_T]: 40 | nonlocal last 41 | 42 | now = _scheduler.now 43 | span = now - last 44 | last = now 45 | return TimeInterval(value=value, interval=span) 46 | 47 | return source.pipe(ops.map(mapper)).subscribe( 48 | observer, scheduler=_scheduler 49 | ) 50 | 51 | return Observable(subscribe) 52 | 53 | return time_interval 54 | -------------------------------------------------------------------------------- /reactivex/operators/_timestamp.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from datetime import datetime 3 | from typing import Callable, Generic, Optional, TypeVar 4 | 5 | from reactivex import Observable, abc, defer, operators 6 | from reactivex.scheduler import TimeoutScheduler 7 | 8 | _T = TypeVar("_T") 9 | 10 | 11 | @dataclass 12 | class Timestamp(Generic[_T]): 13 | value: _T 14 | timestamp: datetime 15 | 16 | 17 | def timestamp_( 18 | scheduler: Optional[abc.SchedulerBase] = None, 19 | ) -> Callable[[Observable[_T]], Observable[Timestamp[_T]]]: 20 | def timestamp(source: Observable[_T]) -> Observable[Timestamp[_T]]: 21 | """Records the timestamp for each value in an observable sequence. 22 | 23 | Examples: 24 | >>> timestamp(source) 25 | 26 | Produces objects with attributes `value` and `timestamp`, where 27 | value is the original value. 28 | 29 | Args: 30 | source: Observable source to timestamp. 31 | 32 | Returns: 33 | An observable sequence with timestamp information on values. 34 | """ 35 | 36 | def factory(scheduler_: Optional[abc.SchedulerBase] = None): 37 | _scheduler = scheduler or scheduler_ or TimeoutScheduler.singleton() 38 | 39 | def mapper(value: _T) -> Timestamp[_T]: 40 | return Timestamp(value=value, timestamp=_scheduler.now) 41 | 42 | return source.pipe(operators.map(mapper)) 43 | 44 | return defer(factory) 45 | 46 | return timestamp 47 | 48 | 49 | __all__ = ["timestamp_"] 50 | -------------------------------------------------------------------------------- /reactivex/operators/_toiterable.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, List, Optional, TypeVar 2 | 3 | from reactivex import Observable, abc 4 | 5 | _T = TypeVar("_T") 6 | 7 | 8 | def to_iterable_() -> Callable[[Observable[_T]], Observable[List[_T]]]: 9 | def to_iterable(source: Observable[_T]) -> Observable[List[_T]]: 10 | """Creates an iterable from an observable sequence. 11 | 12 | Returns: 13 | An observable sequence containing a single element with an 14 | iterable containing all the elements of the source 15 | sequence. 16 | """ 17 | 18 | def subscribe( 19 | observer: abc.ObserverBase[List[_T]], 20 | scheduler: Optional[abc.SchedulerBase] = None, 21 | ): 22 | nonlocal source 23 | 24 | queue: List[_T] = [] 25 | 26 | def on_next(item: _T): 27 | queue.append(item) 28 | 29 | def on_completed(): 30 | nonlocal queue 31 | observer.on_next(queue) 32 | queue = [] 33 | observer.on_completed() 34 | 35 | return source.subscribe( 36 | on_next, observer.on_error, on_completed, scheduler=scheduler 37 | ) 38 | 39 | return Observable(subscribe) 40 | 41 | return to_iterable 42 | 43 | 44 | __all__ = ["to_iterable_"] 45 | -------------------------------------------------------------------------------- /reactivex/operators/_toset.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, Optional, Set, TypeVar 2 | 3 | from reactivex import Observable, abc 4 | 5 | _T = TypeVar("_T") 6 | 7 | 8 | def to_set_() -> Callable[[Observable[_T]], Observable[Set[_T]]]: 9 | """Converts the observable sequence to a set. 10 | 11 | Returns an observable sequence with a single value of a set 12 | containing the values from the observable sequence. 13 | """ 14 | 15 | def to_set(source: Observable[_T]) -> Observable[Set[_T]]: 16 | def subscribe( 17 | observer: abc.ObserverBase[Set[_T]], 18 | scheduler: Optional[abc.SchedulerBase] = None, 19 | ) -> abc.DisposableBase: 20 | s: Set[_T] = set() 21 | 22 | def on_completed() -> None: 23 | nonlocal s 24 | observer.on_next(s) 25 | s = set() 26 | observer.on_completed() 27 | 28 | return source.subscribe( 29 | s.add, observer.on_error, on_completed, scheduler=scheduler 30 | ) 31 | 32 | return Observable(subscribe) 33 | 34 | return to_set 35 | 36 | 37 | __all__ = ["to_set_"] 38 | -------------------------------------------------------------------------------- /reactivex/operators/_whiledo.py: -------------------------------------------------------------------------------- 1 | import itertools 2 | from asyncio import Future 3 | from typing import Callable, TypeVar, Union 4 | 5 | import reactivex 6 | from reactivex import Observable 7 | from reactivex.internal.utils import infinite 8 | from reactivex.typing import Predicate 9 | 10 | _T = TypeVar("_T") 11 | 12 | 13 | def while_do_( 14 | condition: Predicate[Observable[_T]], 15 | ) -> Callable[[Observable[_T]], Observable[_T]]: 16 | def while_do(source: Union[Observable[_T], "Future[_T]"]) -> Observable[_T]: 17 | """Repeats source as long as condition holds emulating a while 18 | loop. 19 | 20 | Args: 21 | source: The observable sequence that will be run if the 22 | condition function returns true. 23 | 24 | Returns: 25 | An observable sequence which is repeated as long as the 26 | condition holds. 27 | """ 28 | if isinstance(source, Future): 29 | obs = reactivex.from_future(source) 30 | else: 31 | obs = source 32 | it = itertools.takewhile(condition, (obs for _ in infinite())) 33 | return reactivex.concat_with_iterable(it) 34 | 35 | return while_do 36 | 37 | 38 | __all__ = ["while_do_"] 39 | -------------------------------------------------------------------------------- /reactivex/operators/_withlatestfrom.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Callable 2 | 3 | import reactivex 4 | from reactivex import Observable 5 | 6 | 7 | def with_latest_from_( 8 | *sources: Observable[Any], 9 | ) -> Callable[[Observable[Any]], Observable[Any]]: 10 | """With latest from operator. 11 | 12 | Merges the specified observable sequences into one observable 13 | sequence by creating a tuple only when the first 14 | observable sequence produces an element. The observables can be 15 | passed either as seperate arguments or as a list. 16 | 17 | Examples: 18 | >>> op = with_latest_from(obs1) 19 | >>> op = with_latest_from(obs1, obs2, obs3) 20 | 21 | Returns: 22 | An observable sequence containing the result of combining 23 | elements of the sources into a tuple. 24 | """ 25 | 26 | def with_latest_from(source: Observable[Any]) -> Observable[Any]: 27 | return reactivex.with_latest_from(source, *sources) 28 | 29 | return with_latest_from 30 | 31 | 32 | __all__ = ["with_latest_from_"] 33 | -------------------------------------------------------------------------------- /reactivex/operators/connectable/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ReactiveX/RxPY/7747af34f3e1fbac2496231e3c2edf56ff704051/reactivex/operators/connectable/__init__.py -------------------------------------------------------------------------------- /reactivex/operators/connectable/_refcount.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, Optional, TypeVar 2 | 3 | from reactivex import ConnectableObservable, Observable, abc 4 | from reactivex.disposable import Disposable 5 | 6 | _T = TypeVar("_T") 7 | 8 | 9 | def ref_count_() -> Callable[[ConnectableObservable[_T]], Observable[_T]]: 10 | """Returns an observable sequence that stays connected to the 11 | source as long as there is at least one subscription to the 12 | observable sequence. 13 | """ 14 | 15 | connectable_subscription: Optional[abc.DisposableBase] = None 16 | count = 0 17 | 18 | def ref_count(source: ConnectableObservable[_T]) -> Observable[_T]: 19 | def subscribe( 20 | observer: abc.ObserverBase[_T], 21 | scheduler: Optional[abc.SchedulerBase] = None, 22 | ) -> abc.DisposableBase: 23 | nonlocal connectable_subscription, count 24 | 25 | count += 1 26 | should_connect = count == 1 27 | subscription = source.subscribe(observer, scheduler=scheduler) 28 | if should_connect: 29 | connectable_subscription = source.connect(scheduler) 30 | 31 | def dispose() -> None: 32 | nonlocal connectable_subscription, count 33 | 34 | subscription.dispose() 35 | count -= 1 36 | if not count and connectable_subscription: 37 | connectable_subscription.dispose() 38 | 39 | return Disposable(dispose) 40 | 41 | return Observable(subscribe) 42 | 43 | return ref_count 44 | 45 | 46 | __all__ = ["ref_count_"] 47 | -------------------------------------------------------------------------------- /reactivex/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ReactiveX/RxPY/7747af34f3e1fbac2496231e3c2edf56ff704051/reactivex/py.typed -------------------------------------------------------------------------------- /reactivex/run.py: -------------------------------------------------------------------------------- 1 | import threading 2 | from typing import Optional, TypeVar, cast 3 | 4 | from reactivex.internal.exceptions import SequenceContainsNoElementsError 5 | from reactivex.scheduler import NewThreadScheduler 6 | 7 | from .observable import Observable 8 | 9 | scheduler = NewThreadScheduler() 10 | 11 | _T = TypeVar("_T") 12 | 13 | 14 | def run(source: Observable[_T]) -> _T: 15 | """Run source synchronously. 16 | 17 | Subscribes to the observable source. Then blocks and waits for the 18 | observable source to either complete or error. Returns the 19 | last value emitted, or throws exception if any error occured. 20 | 21 | Examples: 22 | >>> result = run(source) 23 | 24 | Args: 25 | source: Observable source to run. 26 | 27 | Raises: 28 | SequenceContainsNoElementsError: if observable completes 29 | (on_completed) without any values being emitted. 30 | Exception: raises exception if any error (on_error) occured. 31 | 32 | Returns: 33 | The last element emitted from the observable. 34 | """ 35 | exception: Optional[Exception] = None 36 | latch = threading.Event() 37 | has_result = False 38 | result: _T = cast(_T, None) 39 | done = False 40 | 41 | def on_next(value: _T) -> None: 42 | nonlocal result, has_result 43 | result = value 44 | has_result = True 45 | 46 | def on_error(error: Exception) -> None: 47 | nonlocal exception, done 48 | 49 | exception = error 50 | done = True 51 | latch.set() 52 | 53 | def on_completed() -> None: 54 | nonlocal done 55 | done = True 56 | latch.set() 57 | 58 | source.subscribe(on_next, on_error, on_completed, scheduler=scheduler) 59 | 60 | while not done: 61 | latch.wait() 62 | 63 | if exception: 64 | raise cast(Exception, exception) 65 | 66 | if not has_result: 67 | raise SequenceContainsNoElementsError 68 | 69 | return result 70 | -------------------------------------------------------------------------------- /reactivex/scheduler/__init__.py: -------------------------------------------------------------------------------- 1 | from .catchscheduler import CatchScheduler 2 | from .currentthreadscheduler import CurrentThreadScheduler 3 | from .eventloopscheduler import EventLoopScheduler 4 | from .historicalscheduler import HistoricalScheduler 5 | from .immediatescheduler import ImmediateScheduler 6 | from .newthreadscheduler import NewThreadScheduler 7 | from .scheduleditem import ScheduledItem 8 | from .threadpoolscheduler import ThreadPoolScheduler 9 | from .timeoutscheduler import TimeoutScheduler 10 | from .trampolinescheduler import TrampolineScheduler 11 | from .virtualtimescheduler import VirtualTimeScheduler 12 | 13 | __all__ = [ 14 | "CatchScheduler", 15 | "CurrentThreadScheduler", 16 | "EventLoopScheduler", 17 | "HistoricalScheduler", 18 | "ImmediateScheduler", 19 | "NewThreadScheduler", 20 | "ScheduledItem", 21 | "ThreadPoolScheduler", 22 | "TimeoutScheduler", 23 | "TrampolineScheduler", 24 | "VirtualTimeScheduler", 25 | ] 26 | -------------------------------------------------------------------------------- /reactivex/scheduler/eventloop/__init__.py: -------------------------------------------------------------------------------- 1 | from .asyncioscheduler import AsyncIOScheduler 2 | from .asynciothreadsafescheduler import AsyncIOThreadSafeScheduler 3 | from .eventletscheduler import EventletScheduler 4 | from .geventscheduler import GEventScheduler 5 | from .ioloopscheduler import IOLoopScheduler 6 | from .twistedscheduler import TwistedScheduler 7 | 8 | __all__ = [ 9 | "AsyncIOScheduler", 10 | "AsyncIOThreadSafeScheduler", 11 | "EventletScheduler", 12 | "GEventScheduler", 13 | "IOLoopScheduler", 14 | "TwistedScheduler", 15 | ] 16 | -------------------------------------------------------------------------------- /reactivex/scheduler/historicalscheduler.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from typing import Optional 3 | 4 | from .scheduler import UTC_ZERO 5 | from .virtualtimescheduler import VirtualTimeScheduler 6 | 7 | 8 | class HistoricalScheduler(VirtualTimeScheduler): 9 | """Provides a virtual time scheduler that uses datetime for absolute time 10 | and timedelta for relative time.""" 11 | 12 | def __init__(self, initial_clock: Optional[datetime] = None) -> None: 13 | """Creates a new historical scheduler with the specified initial clock 14 | value. 15 | 16 | Args: 17 | initial_clock: Initial value for the clock. 18 | """ 19 | 20 | super().__init__(initial_clock or UTC_ZERO) 21 | -------------------------------------------------------------------------------- /reactivex/scheduler/mainloop/__init__.py: -------------------------------------------------------------------------------- 1 | from .gtkscheduler import GtkScheduler 2 | from .pygamescheduler import PyGameScheduler 3 | from .qtscheduler import QtScheduler 4 | from .tkinterscheduler import TkinterScheduler 5 | from .wxscheduler import WxScheduler 6 | 7 | __all__ = [ 8 | "GtkScheduler", 9 | "PyGameScheduler", 10 | "QtScheduler", 11 | "TkinterScheduler", 12 | "WxScheduler", 13 | ] 14 | -------------------------------------------------------------------------------- /reactivex/scheduler/periodicscheduler.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from typing import Optional, TypeVar 3 | 4 | from reactivex import abc, typing 5 | from reactivex.disposable import Disposable, MultipleAssignmentDisposable 6 | 7 | from .scheduler import Scheduler 8 | 9 | _TState = TypeVar("_TState") 10 | 11 | 12 | class PeriodicScheduler(Scheduler, abc.PeriodicSchedulerBase): 13 | """Base class for the various periodic scheduler implementations in this 14 | package as well as the mainloop sub-package. 15 | """ 16 | 17 | def schedule_periodic( 18 | self, 19 | period: typing.RelativeTime, 20 | action: typing.ScheduledPeriodicAction[_TState], 21 | state: Optional[_TState] = None, 22 | ) -> abc.DisposableBase: 23 | """Schedules a periodic piece of work. 24 | 25 | Args: 26 | period: Period in seconds or timedelta for running the 27 | work periodically. 28 | action: Action to be executed. 29 | state: [Optional] Initial state passed to the action upon 30 | the first iteration. 31 | 32 | Returns: 33 | The disposable object used to cancel the scheduled 34 | recurring action (best effort). 35 | """ 36 | 37 | disp: MultipleAssignmentDisposable = MultipleAssignmentDisposable() 38 | seconds: float = self.to_seconds(period) 39 | 40 | def periodic( 41 | scheduler: abc.SchedulerBase, state: Optional[_TState] = None 42 | ) -> Optional[Disposable]: 43 | if disp.is_disposed: 44 | return None 45 | 46 | now: datetime = scheduler.now 47 | 48 | try: 49 | state = action(state) 50 | except Exception: 51 | disp.dispose() 52 | raise 53 | 54 | time = seconds - (scheduler.now - now).total_seconds() 55 | disp.disposable = scheduler.schedule_relative(time, periodic, state=state) 56 | 57 | return None 58 | 59 | disp.disposable = self.schedule_relative(period, periodic, state=state) 60 | return disp 61 | -------------------------------------------------------------------------------- /reactivex/scheduler/scheduleditem.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from typing import Any, Optional 3 | 4 | from reactivex import abc 5 | from reactivex.disposable import SingleAssignmentDisposable 6 | 7 | from .scheduler import Scheduler 8 | 9 | 10 | class ScheduledItem(object): 11 | def __init__( 12 | self, 13 | scheduler: Scheduler, 14 | state: Optional[Any], 15 | action: abc.ScheduledAction[Any], 16 | duetime: datetime, 17 | ) -> None: 18 | self.scheduler: Scheduler = scheduler 19 | self.state: Optional[Any] = state 20 | self.action: abc.ScheduledAction[Any] = action 21 | self.duetime: datetime = duetime 22 | self.disposable: SingleAssignmentDisposable = SingleAssignmentDisposable() 23 | 24 | def invoke(self) -> None: 25 | ret = self.scheduler.invoke_action(self.action, state=self.state) 26 | self.disposable.disposable = ret 27 | 28 | def cancel(self) -> None: 29 | """Cancels the work item by disposing the resource returned by 30 | invoke_core as soon as possible.""" 31 | 32 | self.disposable.dispose() 33 | 34 | def is_cancelled(self) -> bool: 35 | return self.disposable.is_disposed 36 | 37 | def __lt__(self, other: "ScheduledItem") -> bool: 38 | return self.duetime < other.duetime 39 | 40 | def __gt__(self, other: "ScheduledItem") -> bool: 41 | return self.duetime > other.duetime 42 | 43 | def __eq__(self, other: Any) -> bool: 44 | try: 45 | return self.duetime == other.duetime 46 | except AttributeError: 47 | return NotImplemented 48 | -------------------------------------------------------------------------------- /reactivex/scheduler/threadpoolscheduler.py: -------------------------------------------------------------------------------- 1 | from concurrent.futures import Future, ThreadPoolExecutor 2 | from typing import Any, Optional 3 | 4 | from reactivex import abc, typing 5 | 6 | from .newthreadscheduler import NewThreadScheduler 7 | 8 | 9 | class ThreadPoolScheduler(NewThreadScheduler): 10 | """A scheduler that schedules work via the thread pool.""" 11 | 12 | class ThreadPoolThread(abc.StartableBase): 13 | """Wraps a concurrent future as a thread.""" 14 | 15 | def __init__( 16 | self, executor: ThreadPoolExecutor, target: typing.StartableTarget 17 | ): 18 | self.executor: ThreadPoolExecutor = executor 19 | self.target: typing.StartableTarget = target 20 | self.future: Optional["Future[Any]"] = None 21 | 22 | def start(self) -> None: 23 | self.future = self.executor.submit(self.target) 24 | 25 | def cancel(self) -> None: 26 | if self.future: 27 | self.future.cancel() 28 | 29 | def __init__(self, max_workers: Optional[int] = None) -> None: 30 | self.executor: ThreadPoolExecutor = ThreadPoolExecutor(max_workers=max_workers) 31 | 32 | def thread_factory( 33 | target: typing.StartableTarget, 34 | ) -> ThreadPoolScheduler.ThreadPoolThread: 35 | return self.ThreadPoolThread(self.executor, target) 36 | 37 | super().__init__(thread_factory) 38 | -------------------------------------------------------------------------------- /reactivex/scheduler/trampoline.py: -------------------------------------------------------------------------------- 1 | from collections import deque 2 | from threading import Condition, Lock 3 | from typing import Deque 4 | 5 | from reactivex.internal.priorityqueue import PriorityQueue 6 | 7 | from .scheduleditem import ScheduledItem 8 | 9 | 10 | class Trampoline: 11 | def __init__(self) -> None: 12 | self._idle: bool = True 13 | self._queue: PriorityQueue[ScheduledItem] = PriorityQueue() 14 | self._lock: Lock = Lock() 15 | self._condition: Condition = Condition(self._lock) 16 | 17 | def idle(self) -> bool: 18 | with self._lock: 19 | return self._idle 20 | 21 | def run(self, item: ScheduledItem) -> None: 22 | with self._lock: 23 | self._queue.enqueue(item) 24 | if self._idle: 25 | self._idle = False 26 | else: 27 | self._condition.notify() 28 | return 29 | try: 30 | self._run() 31 | finally: 32 | with self._lock: 33 | self._idle = True 34 | self._queue.clear() 35 | 36 | def _run(self) -> None: 37 | ready: Deque[ScheduledItem] = deque() 38 | while True: 39 | with self._lock: 40 | while len(self._queue) > 0: 41 | item: ScheduledItem = self._queue.peek() 42 | if item.duetime <= item.scheduler.now: 43 | self._queue.dequeue() 44 | ready.append(item) 45 | else: 46 | break 47 | 48 | while len(ready) > 0: 49 | item = ready.popleft() 50 | if not item.is_cancelled(): 51 | item.invoke() 52 | 53 | with self._lock: 54 | if len(self._queue) == 0: 55 | break 56 | item = self._queue.peek() 57 | seconds = (item.duetime - item.scheduler.now).total_seconds() 58 | if seconds > 0.0: 59 | self._condition.wait(seconds) 60 | 61 | 62 | __all__ = ["Trampoline"] 63 | -------------------------------------------------------------------------------- /reactivex/subject/__init__.py: -------------------------------------------------------------------------------- 1 | from .asyncsubject import AsyncSubject 2 | from .behaviorsubject import BehaviorSubject 3 | from .replaysubject import ReplaySubject 4 | from .subject import Subject 5 | 6 | __all__ = ["Subject", "AsyncSubject", "BehaviorSubject", "ReplaySubject"] 7 | -------------------------------------------------------------------------------- /reactivex/subject/innersubscription.py: -------------------------------------------------------------------------------- 1 | import threading 2 | from typing import TYPE_CHECKING, Optional, TypeVar 3 | 4 | from .. import abc 5 | 6 | if TYPE_CHECKING: 7 | from .subject import Subject 8 | 9 | _T = TypeVar("_T") 10 | 11 | 12 | class InnerSubscription(abc.DisposableBase): 13 | def __init__( 14 | self, subject: "Subject[_T]", observer: Optional[abc.ObserverBase[_T]] = None 15 | ): 16 | self.subject = subject 17 | self.observer = observer 18 | 19 | self.lock = threading.RLock() 20 | 21 | def dispose(self) -> None: 22 | with self.lock: 23 | if not self.subject.is_disposed and self.observer: 24 | if self.observer in self.subject.observers: 25 | self.subject.observers.remove(self.observer) 26 | self.observer = None 27 | -------------------------------------------------------------------------------- /reactivex/testing/__init__.py: -------------------------------------------------------------------------------- 1 | from .mockdisposable import MockDisposable 2 | from .reactivetest import OnErrorPredicate, OnNextPredicate, ReactiveTest, is_prime 3 | from .recorded import Recorded 4 | from .testscheduler import TestScheduler 5 | 6 | __all__ = [ 7 | "MockDisposable", 8 | "OnErrorPredicate", 9 | "OnNextPredicate", 10 | "ReactiveTest", 11 | "Recorded", 12 | "TestScheduler", 13 | "is_prime", 14 | ] 15 | -------------------------------------------------------------------------------- /reactivex/testing/mockdisposable.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | from reactivex import abc, typing 4 | from reactivex.scheduler import VirtualTimeScheduler 5 | 6 | 7 | class MockDisposable(abc.DisposableBase): 8 | def __init__(self, scheduler: VirtualTimeScheduler): 9 | self.scheduler = scheduler 10 | self.disposes: List[typing.AbsoluteTime] = [] 11 | self.disposes.append(self.scheduler.clock) 12 | 13 | def dispose(self) -> None: 14 | self.disposes.append(self.scheduler.clock) 15 | -------------------------------------------------------------------------------- /reactivex/testing/mockobserver.py: -------------------------------------------------------------------------------- 1 | from typing import List, TypeVar 2 | 3 | from reactivex import abc 4 | from reactivex.notification import OnCompleted, OnError, OnNext 5 | from reactivex.scheduler import VirtualTimeScheduler 6 | 7 | from .recorded import Recorded 8 | 9 | _T = TypeVar("_T") 10 | 11 | 12 | class MockObserver(abc.ObserverBase[_T]): 13 | def __init__(self, scheduler: VirtualTimeScheduler) -> None: 14 | self.scheduler = scheduler 15 | self.messages: List[Recorded[_T]] = [] 16 | 17 | def on_next(self, value: _T) -> None: 18 | self.messages.append(Recorded(self.scheduler.clock, OnNext(value))) 19 | 20 | def on_error(self, error: Exception) -> None: 21 | self.messages.append(Recorded(self.scheduler.clock, OnError(error))) 22 | 23 | def on_completed(self) -> None: 24 | self.messages.append(Recorded(self.scheduler.clock, OnCompleted())) 25 | -------------------------------------------------------------------------------- /reactivex/testing/recorded.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING, Any, Generic, TypeVar, Union, cast 2 | 3 | from reactivex import Notification 4 | 5 | if TYPE_CHECKING: 6 | from .reactivetest import OnErrorPredicate, OnNextPredicate 7 | 8 | 9 | _T = TypeVar("_T") 10 | 11 | 12 | class Recorded(Generic[_T]): 13 | def __init__( 14 | self, 15 | time: int, 16 | value: Union[Notification[_T], "OnNextPredicate[_T]", "OnErrorPredicate[_T]"], 17 | # comparer: Optional[typing.Comparer[_T]] = None, 18 | ): 19 | self.time = time 20 | self.value = value 21 | # self.comparer = comparer or default_comparer 22 | 23 | def __eq__(self, other: Any) -> bool: 24 | """Returns true if a recorded value matches another recorded value""" 25 | 26 | if isinstance(other, Recorded): 27 | other_ = cast(Recorded[_T], other) 28 | time_match = self.time == other_.time 29 | if not time_match: 30 | return False 31 | return self.value == other_.value 32 | 33 | return False 34 | 35 | equals = __eq__ 36 | 37 | def __repr__(self) -> str: 38 | return str(self) 39 | 40 | def __str__(self) -> str: 41 | return "%s@%s" % (self.value, self.time) 42 | -------------------------------------------------------------------------------- /reactivex/testing/subscription.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from typing import Any, Optional 3 | 4 | 5 | class Subscription: 6 | def __init__(self, start: int, end: Optional[int] = None): 7 | self.subscribe = start 8 | self.unsubscribe = end or sys.maxsize 9 | 10 | def equals(self, other: Any) -> bool: 11 | return ( 12 | self.subscribe == other.subscribe and self.unsubscribe == other.unsubscribe 13 | ) 14 | 15 | def __eq__(self, other: Any) -> bool: 16 | return self.equals(other) 17 | 18 | def __repr__(self) -> str: 19 | return str(self) 20 | 21 | def __str__(self) -> str: 22 | unsubscribe = ( 23 | "Infinite" if self.unsubscribe == sys.maxsize else self.unsubscribe 24 | ) 25 | return "(%s, %s)" % (self.subscribe, unsubscribe) 26 | -------------------------------------------------------------------------------- /reactivex/typing.py: -------------------------------------------------------------------------------- 1 | from threading import Thread 2 | from typing import Callable, TypeVar, Union 3 | 4 | from .abc.observable import Subscription 5 | from .abc.observer import OnCompleted, OnError, OnNext 6 | from .abc.periodicscheduler import ( 7 | ScheduledPeriodicAction, 8 | ScheduledSingleOrPeriodicAction, 9 | ) 10 | from .abc.scheduler import ( 11 | AbsoluteOrRelativeTime, 12 | AbsoluteTime, 13 | RelativeTime, 14 | ScheduledAction, 15 | ) 16 | from .abc.startable import StartableBase 17 | 18 | _TState = TypeVar("_TState") 19 | _T1 = TypeVar("_T1") 20 | _T2 = TypeVar("_T2") 21 | 22 | Action = Callable[[], None] 23 | 24 | Mapper = Callable[[_T1], _T2] 25 | MapperIndexed = Callable[[_T1, int], _T2] 26 | Predicate = Callable[[_T1], bool] 27 | PredicateIndexed = Callable[[_T1, int], bool] 28 | Comparer = Callable[[_T1, _T1], bool] 29 | SubComparer = Callable[[_T1, _T1], int] 30 | Accumulator = Callable[[_TState, _T1], _TState] 31 | 32 | 33 | Startable = Union[StartableBase, Thread] 34 | StartableTarget = Callable[..., None] 35 | StartableFactory = Callable[[StartableTarget], Startable] 36 | 37 | __all__ = [ 38 | "Accumulator", 39 | "AbsoluteTime", 40 | "AbsoluteOrRelativeTime", 41 | "Comparer", 42 | "Mapper", 43 | "MapperIndexed", 44 | "OnNext", 45 | "OnError", 46 | "OnCompleted", 47 | "Predicate", 48 | "PredicateIndexed", 49 | "RelativeTime", 50 | "SubComparer", 51 | "ScheduledPeriodicAction", 52 | "ScheduledSingleOrPeriodicAction", 53 | "ScheduledAction", 54 | "Startable", 55 | "StartableTarget", 56 | "Subscription", 57 | ] 58 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [egg_info] 2 | #tag_build = dev 3 | #tag_svn_revision = 1 4 | 5 | [aliases] 6 | test = pytest 7 | 8 | [tool:pytest] 9 | testpaths = tests 10 | asyncio_mode = strict 11 | 12 | [flake8] 13 | max-line-length = 88 14 | 15 | [isort] 16 | profile = black 17 | src_paths=rx 18 | 19 | [mypy] 20 | python_version = 3.9 21 | follow_imports = silent 22 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ReactiveX/RxPY/7747af34f3e1fbac2496231e3c2edf56ff704051/tests/__init__.py -------------------------------------------------------------------------------- /tests/test_core/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ReactiveX/RxPY/7747af34f3e1fbac2496231e3c2edf56ff704051/tests/test_core/__init__.py -------------------------------------------------------------------------------- /tests/test_disposables/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ReactiveX/RxPY/7747af34f3e1fbac2496231e3c2edf56ff704051/tests/test_disposables/__init__.py -------------------------------------------------------------------------------- /tests/test_integration/test_concat_repeat.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from reactivex import operators as ops 4 | from reactivex.testing.marbles import marbles_testing 5 | 6 | 7 | class TestConcatIntegration(unittest.TestCase): 8 | def test_concat_repeat(self): 9 | with marbles_testing() as (start, cold, hot, exp): 10 | e1 = cold("-e11-e12|") 11 | e2 = cold("-e21-e22|") 12 | ex = exp("-e11-e12-e21-e22-e11-e12-e21-e22|") 13 | 14 | obs = e1.pipe(ops.concat(e2), ops.repeat(2)) 15 | 16 | results = start(obs) 17 | assert results == ex 18 | -------------------------------------------------------------------------------- /tests/test_integration/test_group_reduce.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import reactivex 4 | from reactivex import operators as ops 5 | 6 | 7 | class TestGroupByReduce(unittest.TestCase): 8 | def test_groupby_count(self): 9 | res = [] 10 | counts = reactivex.from_(range(10)).pipe( 11 | ops.group_by(lambda i: "even" if i % 2 == 0 else "odd"), 12 | ops.flat_map( 13 | lambda i: i.pipe( 14 | ops.count(), 15 | ops.map(lambda ii: (i.key, ii)), 16 | ) 17 | ), 18 | ) 19 | 20 | counts.subscribe(on_next=res.append) 21 | assert res == [("even", 5), ("odd", 5)] 22 | 23 | def test_window_sum(self): 24 | res = [] 25 | reactivex.from_(range(6)).pipe( 26 | ops.window_with_count(count=3, skip=1), 27 | ops.flat_map( 28 | lambda i: i.pipe( 29 | ops.sum(), 30 | ) 31 | ), 32 | ).subscribe(on_next=res.append) 33 | 34 | assert res == [3, 6, 9, 12, 9, 5, 0] 35 | -------------------------------------------------------------------------------- /tests/test_observable/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ReactiveX/RxPY/7747af34f3e1fbac2496231e3c2edf56ff704051/tests/test_observable/__init__.py -------------------------------------------------------------------------------- /tests/test_observable/test_blocking/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ReactiveX/RxPY/7747af34f3e1fbac2496231e3c2edf56ff704051/tests/test_observable/test_blocking/__init__.py -------------------------------------------------------------------------------- /tests/test_observable/test_blocking/test_blocking.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import pytest 4 | 5 | import reactivex 6 | from reactivex import operators as ops 7 | from reactivex.internal.exceptions import SequenceContainsNoElementsError 8 | from reactivex.testing import ReactiveTest 9 | 10 | on_next = ReactiveTest.on_next 11 | on_completed = ReactiveTest.on_completed 12 | on_error = ReactiveTest.on_error 13 | subscribe = ReactiveTest.subscribe 14 | subscribed = ReactiveTest.subscribed 15 | disposed = ReactiveTest.disposed 16 | created = ReactiveTest.created 17 | 18 | 19 | class RxException(Exception): 20 | pass 21 | 22 | 23 | # Helper function for raising exceptions within lambdas 24 | def _raise(ex): 25 | raise RxException(ex) 26 | 27 | 28 | class TestBlocking(unittest.TestCase): 29 | def test_run_empty(self): 30 | with pytest.raises(SequenceContainsNoElementsError): 31 | reactivex.empty().run() 32 | 33 | def test_run_error(self): 34 | with pytest.raises(RxException): 35 | reactivex.throw(RxException()).run() 36 | 37 | def test_run_just(self): 38 | result = reactivex.just(42).run() 39 | assert result == 42 40 | 41 | def test_run_range(self): 42 | result = reactivex.range(42).run() 43 | assert result == 41 44 | 45 | def test_run_range_to_iterable(self): 46 | result = reactivex.range(42).pipe(ops.to_iterable()).run() 47 | assert list(result) == list(range(42)) 48 | 49 | def test_run_from(self): 50 | result = reactivex.from_([1, 2, 3]).run() 51 | assert result == 3 52 | 53 | def test_run_from_first(self): 54 | result = reactivex.from_([1, 2, 3]).pipe(ops.first()).run() 55 | assert result == 1 56 | 57 | def test_run_of(self): 58 | result = reactivex.of(1, 2, 3).run() 59 | assert result == 3 60 | -------------------------------------------------------------------------------- /tests/test_observable/test_empty.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from reactivex import empty 4 | from reactivex.testing import ReactiveTest, TestScheduler 5 | 6 | on_next = ReactiveTest.on_next 7 | on_completed = ReactiveTest.on_completed 8 | on_error = ReactiveTest.on_error 9 | subscribe = ReactiveTest.subscribe 10 | subscribed = ReactiveTest.subscribed 11 | disposed = ReactiveTest.disposed 12 | created = ReactiveTest.created 13 | 14 | 15 | class RxException(Exception): 16 | pass 17 | 18 | 19 | # Helper function for raising exceptions within lambdas 20 | def _raise(ex): 21 | raise RxException(ex) 22 | 23 | 24 | class TestEmpty(unittest.TestCase): 25 | def test_empty_basic(self): 26 | scheduler = TestScheduler() 27 | 28 | def factory(): 29 | return empty() 30 | 31 | results = scheduler.start(factory) 32 | 33 | assert results.messages == [on_completed(200)] 34 | 35 | def test_empty_disposed(self): 36 | scheduler = TestScheduler() 37 | 38 | def factory(): 39 | return empty() 40 | 41 | results = scheduler.start(factory, disposed=200) 42 | assert results.messages == [] 43 | 44 | def test_empty_observer_throw_exception(self): 45 | scheduler = TestScheduler() 46 | xs = empty() 47 | xs.subscribe( 48 | lambda x: None, lambda ex: None, lambda: _raise("ex"), scheduler=scheduler 49 | ) 50 | 51 | with self.assertRaises(RxException): 52 | scheduler.start() 53 | -------------------------------------------------------------------------------- /tests/test_observable/test_flatmap_async.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import unittest 3 | 4 | from reactivex import operators as ops 5 | from reactivex.scheduler.eventloop import AsyncIOScheduler 6 | from reactivex.subject import Subject 7 | 8 | 9 | class TestFlatMapAsync(unittest.TestCase): 10 | def test_flat_map_async(self): 11 | actual_next = None 12 | loop = asyncio.get_event_loop() 13 | scheduler = AsyncIOScheduler(loop=loop) 14 | 15 | def mapper(i: int): 16 | async def _mapper(i: int): 17 | return i + 1 18 | 19 | return asyncio.ensure_future(_mapper(i)) 20 | 21 | def on_next(i: int): 22 | nonlocal actual_next 23 | actual_next = i 24 | 25 | def on_error(ex): 26 | print("Error", ex) 27 | 28 | async def test_flat_map(): 29 | x: Subject[int] = Subject() 30 | x.pipe(ops.flat_map(mapper)).subscribe( 31 | on_next, on_error, scheduler=scheduler 32 | ) 33 | x.on_next(10) 34 | await asyncio.sleep(0.1) 35 | 36 | loop.run_until_complete(test_flat_map()) 37 | assert actual_next == 11 38 | -------------------------------------------------------------------------------- /tests/test_observable/test_forin.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import reactivex 4 | from reactivex.observable.observable import Observable 5 | from reactivex.testing import ReactiveTest, TestScheduler 6 | 7 | on_next = ReactiveTest.on_next 8 | on_completed = ReactiveTest.on_completed 9 | on_error = ReactiveTest.on_error 10 | subscribe = ReactiveTest.subscribe 11 | subscribed = ReactiveTest.subscribed 12 | disposed = ReactiveTest.disposed 13 | created = ReactiveTest.created 14 | 15 | 16 | class TestForIn(unittest.TestCase): 17 | def test_for_basic(self): 18 | scheduler = TestScheduler() 19 | 20 | def create(): 21 | def mapper(x: int) -> Observable[int]: 22 | return scheduler.create_cold_observable( 23 | on_next(x * 100 + 10, x * 10 + 1), 24 | on_next(x * 100 + 20, x * 10 + 2), 25 | on_next(x * 100 + 30, x * 10 + 3), 26 | on_completed(x * 100 + 40), 27 | ) 28 | 29 | return reactivex.for_in([1, 2, 3], mapper) 30 | 31 | results = scheduler.start(create=create) 32 | assert results.messages == [ 33 | on_next(310, 11), 34 | on_next(320, 12), 35 | on_next(330, 13), 36 | on_next(550, 21), 37 | on_next(560, 22), 38 | on_next(570, 23), 39 | on_next(890, 31), 40 | on_next(900, 32), 41 | on_next(910, 33), 42 | on_completed(920), 43 | ] 44 | 45 | def test_for_throws(self): 46 | ex = "ex" 47 | scheduler = TestScheduler() 48 | 49 | def create(): 50 | def mapper(x: int): 51 | raise Exception(ex) 52 | 53 | return reactivex.for_in([1, 2, 3], mapper) 54 | 55 | results = scheduler.start(create=create) 56 | assert results.messages == [on_error(200, ex)] 57 | -------------------------------------------------------------------------------- /tests/test_observable/test_fromcallback.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import reactivex 4 | from reactivex.testing import ReactiveTest 5 | 6 | on_next = ReactiveTest.on_next 7 | on_completed = ReactiveTest.on_completed 8 | on_error = ReactiveTest.on_error 9 | subscribe = ReactiveTest.subscribe 10 | subscribed = ReactiveTest.subscribed 11 | disposed = ReactiveTest.disposed 12 | created = ReactiveTest.created 13 | 14 | 15 | class RxException(Exception): 16 | pass 17 | 18 | 19 | # Helper function for raising exceptions within lambdas 20 | def _raise(ex): 21 | raise RxException(ex) 22 | 23 | 24 | class TestFromCallback(unittest.TestCase): 25 | def test_from_callback(self): 26 | res = reactivex.from_callback(lambda cb: cb(True))() 27 | 28 | def on_next(r): 29 | self.assertEqual(r, True) 30 | 31 | def on_error(err): 32 | assert False 33 | 34 | def on_completed(): 35 | assert True 36 | 37 | res.subscribe(on_next, on_error, on_completed) 38 | 39 | def test_from_callback_single(self): 40 | res = reactivex.from_callback(lambda file, cb: cb(file))("file.txt") 41 | 42 | def on_next(r): 43 | self.assertEqual(r, "file.txt") 44 | 45 | def on_error(err): 46 | assert False 47 | 48 | def on_completed(): 49 | assert True 50 | 51 | res.subscribe(on_next, on_error, on_completed) 52 | 53 | def test_from_node_callback_mapper(self): 54 | res = reactivex.from_callback(lambda f, s, t, cb: cb(f, s, t), lambda r: r[0])( 55 | 1, 2, 3 56 | ) 57 | 58 | def on_next(r): 59 | self.assertEqual(r, 1) 60 | 61 | def on_error(err): 62 | assert False 63 | 64 | def on_completed(): 65 | assert True 66 | 67 | res.subscribe(on_next, on_error, on_completed) 68 | -------------------------------------------------------------------------------- /tests/test_observable/test_never.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from reactivex import never 4 | from reactivex.testing import ReactiveTest, TestScheduler 5 | 6 | on_next = ReactiveTest.on_next 7 | on_completed = ReactiveTest.on_completed 8 | on_error = ReactiveTest.on_error 9 | subscribe = ReactiveTest.subscribe 10 | subscribed = ReactiveTest.subscribed 11 | disposed = ReactiveTest.disposed 12 | created = ReactiveTest.created 13 | 14 | 15 | class TestNever(unittest.TestCase): 16 | def test_never_basic(self): 17 | scheduler = TestScheduler() 18 | xs = never() 19 | results = scheduler.create_observer() 20 | xs.subscribe(results) 21 | scheduler.start() 22 | assert results.messages == [] 23 | -------------------------------------------------------------------------------- /tests/test_observable/test_of.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import reactivex 4 | from reactivex.testing import ReactiveTest, TestScheduler 5 | 6 | on_next = ReactiveTest.on_next 7 | on_completed = ReactiveTest.on_completed 8 | on_error = ReactiveTest.on_error 9 | subscribe = ReactiveTest.subscribe 10 | subscribed = ReactiveTest.subscribed 11 | disposed = ReactiveTest.disposed 12 | created = ReactiveTest.created 13 | 14 | 15 | class TestOf(unittest.TestCase): 16 | def test_of(self): 17 | results = [] 18 | 19 | reactivex.of(1, 2, 3, 4, 5).subscribe(results.append) 20 | 21 | assert str([1, 2, 3, 4, 5]) == str(results) 22 | 23 | def test_of_empty(self): 24 | results = [] 25 | 26 | reactivex.of().subscribe(results.append) 27 | 28 | assert len(results) == 0 29 | 30 | def teest_of_with_scheduler(self): 31 | scheduler = TestScheduler() 32 | 33 | def create(): 34 | return reactivex.of(1, 2, 3, 4, 5) 35 | 36 | results = scheduler.start(create=create) 37 | 38 | assert results.messages == [ 39 | on_next(201, 1), 40 | on_next(202, 2), 41 | on_next(203, 3), 42 | on_next(204, 4), 43 | on_next(205, 5), 44 | on_completed(206), 45 | ] 46 | 47 | def teest_of_with_scheduler_empty(self): 48 | scheduler = TestScheduler() 49 | 50 | def create(): 51 | return reactivex.of(scheduler=scheduler) 52 | 53 | results = scheduler.start(create=create) 54 | 55 | assert results.messages == [on_completed(201)] 56 | -------------------------------------------------------------------------------- /tests/test_observable/test_throw.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from reactivex import throw 4 | from reactivex.testing import ReactiveTest, TestScheduler 5 | 6 | on_next = ReactiveTest.on_next 7 | on_completed = ReactiveTest.on_completed 8 | on_error = ReactiveTest.on_error 9 | subscribe = ReactiveTest.subscribe 10 | subscribed = ReactiveTest.subscribed 11 | disposed = ReactiveTest.disposed 12 | created = ReactiveTest.created 13 | 14 | 15 | class RxException(Exception): 16 | pass 17 | 18 | 19 | # Helper function for raising exceptions within lambdas 20 | def _raise(ex): 21 | raise RxException(ex) 22 | 23 | 24 | class TestThrow(unittest.TestCase): 25 | def test_throw_exception_basic(self): 26 | scheduler = TestScheduler() 27 | ex = "ex" 28 | 29 | def factory(): 30 | return throw(ex) 31 | 32 | results = scheduler.start(factory) 33 | assert results.messages == [on_error(200, ex)] 34 | 35 | def test_throw_disposed(self): 36 | scheduler = TestScheduler() 37 | 38 | def factory(): 39 | return throw("ex") 40 | 41 | results = scheduler.start(factory, disposed=200) 42 | assert results.messages == [] 43 | 44 | def test_throw_observer_throws(self): 45 | scheduler = TestScheduler() 46 | xs = throw("ex") 47 | xs.subscribe( 48 | lambda x: None, lambda ex: _raise("ex"), lambda: None, scheduler=scheduler 49 | ) 50 | 51 | self.assertRaises(RxException, scheduler.start) 52 | -------------------------------------------------------------------------------- /tests/test_scheduler/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ReactiveX/RxPY/7747af34f3e1fbac2496231e3c2edf56ff704051/tests/test_scheduler/__init__.py -------------------------------------------------------------------------------- /tests/test_scheduler/test_eventloop/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ReactiveX/RxPY/7747af34f3e1fbac2496231e3c2edf56ff704051/tests/test_scheduler/test_eventloop/__init__.py -------------------------------------------------------------------------------- /tests/test_scheduler/test_eventloop/test_geventscheduler.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from datetime import datetime, timedelta, timezone 3 | 4 | import pytest 5 | 6 | from reactivex.scheduler.eventloop import GEventScheduler 7 | 8 | gevent = pytest.importorskip("gevent") 9 | 10 | 11 | class TestGEventScheduler(unittest.TestCase): 12 | def test_gevent_schedule_now(self): 13 | scheduler = GEventScheduler(gevent) 14 | hub = gevent.get_hub() 15 | diff = scheduler.now - datetime.fromtimestamp(hub.loop.now(), tz=timezone.utc) 16 | assert abs(diff) < timedelta(milliseconds=1) 17 | 18 | def test_gevent_schedule_now_units(self): 19 | scheduler = GEventScheduler(gevent) 20 | diff = scheduler.now 21 | gevent.sleep(0.1) 22 | diff = scheduler.now - diff 23 | assert timedelta(milliseconds=80) < diff < timedelta(milliseconds=180) 24 | 25 | def test_gevent_schedule_action(self): 26 | scheduler = GEventScheduler(gevent) 27 | ran = False 28 | 29 | def action(scheduler, state): 30 | nonlocal ran 31 | ran = True 32 | 33 | scheduler.schedule(action) 34 | 35 | gevent.sleep(0.1) 36 | assert ran is True 37 | 38 | def test_gevent_schedule_action_due(self): 39 | scheduler = GEventScheduler(gevent) 40 | starttime = datetime.now() 41 | endtime = None 42 | 43 | def action(scheduler, state): 44 | nonlocal endtime 45 | endtime = datetime.now() 46 | 47 | scheduler.schedule_relative(0.2, action) 48 | 49 | gevent.sleep(0.3) 50 | assert endtime is not None 51 | diff = endtime - starttime 52 | assert diff > timedelta(seconds=0.18) 53 | 54 | def test_gevent_schedule_action_cancel(self): 55 | scheduler = GEventScheduler(gevent) 56 | ran = False 57 | 58 | def action(scheduler, state): 59 | nonlocal ran 60 | ran = True 61 | 62 | d = scheduler.schedule_relative(0.01, action) 63 | d.dispose() 64 | 65 | gevent.sleep(0.1) 66 | assert ran is False 67 | -------------------------------------------------------------------------------- /tests/test_scheduler/test_mainloop/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ReactiveX/RxPY/7747af34f3e1fbac2496231e3c2edf56ff704051/tests/test_scheduler/test_mainloop/__init__.py -------------------------------------------------------------------------------- /tests/test_scheduler/test_scheduler.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from reactivex.internal.constants import DELTA_ZERO, UTC_ZERO 4 | from reactivex.scheduler.scheduler import Scheduler 5 | 6 | 7 | class TestScheduler(unittest.TestCase): 8 | def test_base_to_seconds(self): 9 | val = Scheduler.to_seconds(0.0) 10 | assert val == 0.0 11 | val = Scheduler.to_seconds(DELTA_ZERO) 12 | assert val == 0.0 13 | val = Scheduler.to_seconds(UTC_ZERO) 14 | assert val == 0.0 15 | 16 | def test_base_to_datetime(self): 17 | val = Scheduler.to_datetime(0.0) 18 | assert val == UTC_ZERO 19 | val = Scheduler.to_datetime(DELTA_ZERO) 20 | assert val == UTC_ZERO 21 | val = Scheduler.to_datetime(UTC_ZERO) 22 | assert val == UTC_ZERO 23 | 24 | def test_base_to_timedelta(self): 25 | val = Scheduler.to_timedelta(0.0) 26 | assert val == DELTA_ZERO 27 | val = Scheduler.to_timedelta(DELTA_ZERO) 28 | assert val == DELTA_ZERO 29 | val = Scheduler.to_timedelta(UTC_ZERO) 30 | assert val == DELTA_ZERO 31 | -------------------------------------------------------------------------------- /tests/test_subject/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ReactiveX/RxPY/7747af34f3e1fbac2496231e3c2edf56ff704051/tests/test_subject/__init__.py -------------------------------------------------------------------------------- /tests/test_testing/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ReactiveX/RxPY/7747af34f3e1fbac2496231e3c2edf56ff704051/tests/test_testing/__init__.py -------------------------------------------------------------------------------- /tests/test_version.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from reactivex import __version__ 4 | 5 | 6 | class VersionTest(unittest.TestCase): 7 | def test_version(self): 8 | assert __version__ 9 | --------------------------------------------------------------------------------