pax_global_header00006660000000000000000000000064146452320300014511gustar00rootroot0000000000000052 comment=e3f3dd06e2002a65177f166308a1d61e85829167 bdraco-freenub-69809e8/000077500000000000000000000000001464523203000146655ustar00rootroot00000000000000bdraco-freenub-69809e8/.all-contributorsrc000066400000000000000000000004431464523203000205170ustar00rootroot00000000000000{ "projectName": "freenub", "projectOwner": "bdraco", "repoType": "github", "repoHost": "https://github.com", "files": [ "README.md" ], "imageSize": 80, "commit": true, "commitConvention": "angular", "contributors": [], "contributorsPerLine": 7, "skipCi": true } bdraco-freenub-69809e8/.codespellrc000066400000000000000000000000571464523203000171670ustar00rootroot00000000000000[codespell] ignore-words-list = socio-economic bdraco-freenub-69809e8/.copier-answers.yml000066400000000000000000000010571464523203000204320ustar00rootroot00000000000000# Changes here will be overwritten by Copier _commit: 54cda6a _src_path: gh:browniebroke/pypackage-template add_me_as_contributor: false copyright_year: '2024' documentation: false email: nick@koston.org full_name: J. Nick Koston github_username: bdraco has_cli: false initial_commit: true is_django_package: false open_source_license: MIT package_name: freenub project_name: freenub project_short_description: This is a fork of pubnub when it still had an MIT license project_slug: freenub run_poetry_install: true setup_github: true setup_pre_commit: true bdraco-freenub-69809e8/.editorconfig000066400000000000000000000004441464523203000173440ustar00rootroot00000000000000# http://editorconfig.org root = true [*] indent_style = space indent_size = 4 trim_trailing_whitespace = true insert_final_newline = true charset = utf-8 end_of_line = lf [*.bat] indent_style = tab end_of_line = crlf [LICENSE] insert_final_newline = false [Makefile] indent_style = tab bdraco-freenub-69809e8/.github/000077500000000000000000000000001464523203000162255ustar00rootroot00000000000000bdraco-freenub-69809e8/.github/CODEOWNERS000066400000000000000000000002171464523203000176200ustar00rootroot00000000000000* @seba-aln @kleewho @Xavrax @jguz-pubnub @parfeon .github/* @parfeon @seba-aln @kleewho @Xavrax @jguz-pubnub README.md @techwritermat bdraco-freenub-69809e8/.github/CODE_OF_CONDUCT.md000066400000000000000000000120561464523203000210300ustar00rootroot00000000000000# Contributor Covenant Code of Conduct ## Our Pledge We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. ## Our Standards Examples of behavior that contributes to a positive environment for our community include: - Demonstrating empathy and kindness toward other people - Being respectful of differing opinions, viewpoints, and experiences - Giving and gracefully accepting constructive feedback - Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience - Focusing on what is best not just for us as individuals, but for the overall community Examples of unacceptable behavior include: - The use of sexualized language or imagery, and sexual attention or advances of any kind - Trolling, insulting or derogatory comments, and personal or political attacks - Public or private harassment - Publishing others' private information, such as a physical or email address, without their explicit permission - Other conduct which could reasonably be considered inappropriate in a professional setting ## Enforcement Responsibilities Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. ## Scope This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting @bdraco. All complaints will be reviewed and investigated promptly and fairly. All community leaders are obligated to respect the privacy and security of the reporter of any incident. ## Enforcement Guidelines Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: ### 1. Correction **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. ### 2. Warning **Community Impact**: A violation through a single incident or series of actions. **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. ### 3. Temporary Ban **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. ### 4. Permanent Ban **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. **Consequence**: A permanent ban from any sort of public interaction within the community. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0, available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity). [homepage]: https://www.contributor-covenant.org For answers to common questions about this code of conduct, see the FAQ at https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations. bdraco-freenub-69809e8/.github/FUNDING.yml000066400000000000000000000000231464523203000200350ustar00rootroot00000000000000github: ["bdraco"] bdraco-freenub-69809e8/.github/ISSUE_TEMPLATE/000077500000000000000000000000001464523203000204105ustar00rootroot00000000000000bdraco-freenub-69809e8/.github/ISSUE_TEMPLATE/1-bug-report.yml000066400000000000000000000035251464523203000233640ustar00rootroot00000000000000name: Bug report description: Create a report to help us improve labels: [bug] body: - type: textarea id: description attributes: label: Describe the bug description: A clear and concise description of what the bug is. placeholder: Describe the bug validations: required: true - type: textarea id: reproduce attributes: label: To Reproduce description: Steps to reproduce the behavior. placeholder: To Reproduce validations: required: true - type: textarea id: context attributes: label: Additional context description: Add any other context about the problem here. placeholder: Additional context - type: input id: version attributes: label: Version description: Version of the project. placeholder: Version validations: required: true - type: input id: platform attributes: label: Platform description: Platform where the bug was found. placeholder: "Example: Windows 11 / macOS 12.0.1 / Ubuntu 20.04" validations: required: true - type: checkboxes id: terms attributes: label: Code of Conduct description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/bdraco/freenub/blob/main/.github/CODE_OF_CONDUCT.md). options: - label: I agree to follow this project's Code of Conduct. required: true - type: checkboxes id: no-duplicate attributes: label: No Duplicate description: Please check [existing issues](https://github.com/bdraco/freenub/issues) to avoid duplicates. options: - label: I have checked existing issues to avoid duplicates. required: true - type: markdown attributes: value: 👋 Have a great day and thank you for the bug report! bdraco-freenub-69809e8/.github/ISSUE_TEMPLATE/2-feature-request.yml000066400000000000000000000036061464523203000244200ustar00rootroot00000000000000name: Feature request description: Suggest an idea for this project labels: [enhancement] body: - type: textarea id: description attributes: label: Is your feature request related to a problem? Please describe. description: A clear and concise description of what the problem is. value: I'm always frustrated when validations: required: true - type: textarea id: solution attributes: label: Describe alternatives you've considered description: A clear and concise description of any alternative solutions or features you've considered. placeholder: Describe alternatives you've considered validations: required: true - type: textarea id: context attributes: label: Additional context description: Add any other context or screenshots about the feature request here. placeholder: Additional context - type: checkboxes id: terms attributes: label: Code of Conduct description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/bdraco/freenub/blob/main/.github/CODE_OF_CONDUCT.md). options: - label: I agree to follow this project's Code of Conduct required: true - type: checkboxes id: willing attributes: label: Are you willing to resolve this issue by submitting a Pull Request? description: Remember that first-time contributors are welcome! 🙌 options: - label: Yes, I have the time, and I know how to start. - label: Yes, I have the time, but I don't know how to start. I would need guidance. - label: No, I don't have the time, although I believe I could do it if I had the time... - label: No, I don't have the time and I wouldn't even know how to start. - type: markdown attributes: value: 👋 Have a great day and thank you for the feature request! bdraco-freenub-69809e8/.github/ISSUE_TEMPLATE/config.yml000066400000000000000000000006001464523203000223740ustar00rootroot00000000000000# Disabling blank issues to ensure all necessary information is provided # Users should use the provided templates for specific issues # For general questions, please refer to the contact links section blank_issues_enabled: false contact_links: - name: Questions url: https://github.com/bdraco/freenub/discussions/categories/q-a about: Please ask and answer questions here. bdraco-freenub-69809e8/.github/PULL_REQUEST_TEMPLATE.md000066400000000000000000000034541464523203000220340ustar00rootroot00000000000000 ### Description of change ### Pull-Request Checklist - [ ] Code is up-to-date with the `main` branch - [ ] This pull request follows the [contributing guidelines](https://github.com/bdraco/freenub/blob/main/CONTRIBUTING.md). - [ ] This pull request links relevant issues as `Fixes #0000` - [ ] There are new or updated unit tests validating the change - [ ] Documentation has been updated to reflect this change - [ ] The new commits follow conventions outlined in the [conventional commit spec](https://www.conventionalcommits.org/en/v1.0.0/), such as "fix(api): prevent racing of requests". > - If pre-commit.ci is failing, try `pre-commit run -a` for further information. > - If CI / test is failing, try `poetry run pytest` for further information. bdraco-freenub-69809e8/.github/labels.toml000066400000000000000000000036751464523203000203770ustar00rootroot00000000000000[breaking] color = "ffcc00" name = "breaking" description = "Breaking change." [bug] color = "d73a4a" name = "bug" description = "Something isn't working" [dependencies] color = "0366d6" name = "dependencies" description = "Pull requests that update a dependency file" [github_actions] color = "000000" name = "github_actions" description = "Update of github actions" [documentation] color = "1bc4a5" name = "documentation" description = "Improvements or additions to documentation" [duplicate] color = "cfd3d7" name = "duplicate" description = "This issue or pull request already exists" [enhancement] color = "a2eeef" name = "enhancement" description = "New feature or request" ["good first issue"] color = "7057ff" name = "good first issue" description = "Good for newcomers" ["help wanted"] color = "008672" name = "help wanted" description = "Extra attention is needed" [invalid] color = "e4e669" name = "invalid" description = "This doesn't seem right" [nochangelog] color = "555555" name = "nochangelog" description = "Exclude pull requests from changelog" [question] color = "d876e3" name = "question" description = "Further information is requested" [removed] color = "e99695" name = "removed" description = "Removed piece of functionalities." [tests] color = "bfd4f2" name = "tests" description = "CI, CD and testing related changes" [wontfix] color = "ffffff" name = "wontfix" description = "This will not be worked on" [discussion] color = "c2e0c6" name = "discussion" description = "Some discussion around the project" [hacktoberfest] color = "ffa663" name = "hacktoberfest" description = "Good issues for Hacktoberfest" [answered] color = "0ee2b6" name = "answered" description = "Automatically closes as answered after a delay" [waiting] color = "5f7972" name = "waiting" description = "Automatically closes if no answer after a delay" [fund] color = "0E8A16" name = "fund" description = "Add a section linking to polar.sh for funding the issue." bdraco-freenub-69809e8/.github/workflows/000077500000000000000000000000001464523203000202625ustar00rootroot00000000000000bdraco-freenub-69809e8/.github/workflows/ci.yml000066400000000000000000000052771464523203000214130ustar00rootroot00000000000000name: CI on: push: branches: - main pull_request: concurrency: group: ${{ github.head_ref || github.run_id }} cancel-in-progress: true jobs: lint: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: 3.x - uses: pre-commit/action@v3.0.1 # Make sure commit messages follow the conventional commits convention: # https://www.conventionalcommits.org commitlint: name: Lint Commit Messages runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - uses: wagoid/commitlint-github-action@v6.0.1 test: strategy: fail-fast: false matrix: python-version: - "3.9" - "3.10" - "3.11" - "3.12" os: - ubuntu-latest runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - name: Install poetry run: pipx install poetry - name: Set up Python uses: actions/setup-python@v5 id: setup-python with: python-version: ${{ matrix.python-version }} cache: poetry - name: Install Dependencies run: poetry install shell: bash - name: Test with Pytest run: poetry run pytest shell: bash - name: Upload coverage to Codecov uses: codecov/codecov-action@v4 with: token: ${{ secrets.CODECOV_TOKEN }} release: needs: - test - lint - commitlint runs-on: ubuntu-latest environment: release concurrency: release permissions: id-token: write contents: write steps: - uses: actions/checkout@v4 with: fetch-depth: 0 ref: ${{ github.head_ref || github.ref_name }} # Do a dry run of PSR - name: Test release uses: python-semantic-release/python-semantic-release@v9.8.1 if: github.ref_name != 'main' with: root_options: --noop # On main branch: actual PSR + upload to PyPI & GitHub - name: Release uses: python-semantic-release/python-semantic-release@v9.8.1 id: release if: github.ref_name == 'main' with: github_token: ${{ secrets.GITHUB_TOKEN }} - name: Publish package distributions to PyPI uses: pypa/gh-action-pypi-publish@release/v1 if: steps.release.outputs.released == 'true' - name: Publish package distributions to GitHub Releases uses: python-semantic-release/upload-to-gh-release@main if: steps.release.outputs.released == 'true' with: github_token: ${{ secrets.GITHUB_TOKEN }} bdraco-freenub-69809e8/.github/workflows/issue-manager.yml000066400000000000000000000013401464523203000235430ustar00rootroot00000000000000name: Issue Manager on: schedule: - cron: "0 0 * * *" issue_comment: types: - created issues: types: - labeled pull_request_target: types: - labeled workflow_dispatch: jobs: issue-manager: runs-on: ubuntu-latest steps: - uses: tiangolo/issue-manager@0.5.0 with: token: ${{ secrets.GITHUB_TOKEN }} config: > { "answered": { "message": "Assuming the original issue was solved, it will be automatically closed now." }, "waiting": { "message": "Automatically closing. To re-open, please provide the additional information requested." } } bdraco-freenub-69809e8/.github/workflows/poetry-upgrade.yml000066400000000000000000000003401464523203000237510ustar00rootroot00000000000000name: Upgrader on: workflow_dispatch: schedule: - cron: "37 10 7 * *" jobs: upgrade: uses: browniebroke/github-actions/.github/workflows/poetry-upgrade.yml@v1 secrets: gh_pat: ${{ secrets.GH_PAT }} bdraco-freenub-69809e8/.gitignore000066400000000000000000000045531464523203000166640ustar00rootroot00000000000000# Created by .ignore support plugin (hsz.mobi) ### Python template # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python env/ src/ build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ share/python-wheels/ *.egg-info/ .installed.cfg *.egg MANIFEST *.egg-info/ .installed.cfg *.egg # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .nox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover *.py,cover .hypothesis/ .pytest_cache/ cover/ *,cover .hypothesis/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py db.sqlite3 db.sqlite3-journal # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder .pybuilder/ target/ # Jupyter Notebook .ipynb_checkpoints # IPython profile_default/ ipython_config.py # pyenv # For a library or package, you might want to ignore these files since the code is # intended to run in multiple environments; otherwise, check them in: # .python-version # pipenv # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. # However, in case of collaboration, if having platform-specific dependencies or dependencies # having no cross-platform support, pipenv may install dependencies that don't work, or not # install all needed dependencies. #Pipfile.lock # PEP 582; used by e.g. github.com/David-OConnor/pyflow __pypackages__/ # Celery stuff celerybeat-schedule celerybeat.pid # SageMath parsed files *.sage.py # Environments .env .venv env/ venv/ ENV/ env.bak/ venv.bak/ # Spyder freenub settings .spyderproject .spyproject # Rope freenub settings .ropeproject # mkdocs documentation /site # mypy .mypy_cache/ .dmypy.json dmypy.json # Pyre type checker .pyre/ # pytype static type analyzer .pytype/ # Cython debug symbols cython_debug/ target/ #Ipython Notebook .ipynb_checkpoints # pyenv .python-version # Development Environment .idea .vscode .DS_Store # Twisted _trial_temp # jupyter dev notebook PubNubTwisted.ipynb # GitHub Actions # ################## .github/.release bdraco-freenub-69809e8/.gitpod.yml000066400000000000000000000003061464523203000167530ustar00rootroot00000000000000tasks: - command: | pip install poetry PIP_USER=false poetry install - command: | pip install pre-commit pre-commit install PIP_USER=false pre-commit install-hooks bdraco-freenub-69809e8/.idea/000077500000000000000000000000001464523203000156455ustar00rootroot00000000000000bdraco-freenub-69809e8/.idea/freenub.iml000066400000000000000000000005151464523203000177770ustar00rootroot00000000000000 bdraco-freenub-69809e8/.idea/watcherTasks.xml000066400000000000000000000052531464523203000210370ustar00rootroot00000000000000 bdraco-freenub-69809e8/.idea/workspace.xml000066400000000000000000000027361464523203000203750ustar00rootroot00000000000000 bdraco-freenub-69809e8/.pre-commit-config.yaml000066400000000000000000000023611464523203000211500ustar00rootroot00000000000000# See https://pre-commit.com for more information # See https://pre-commit.com/hooks.html for more hooks exclude: "CHANGELOG.md|.copier-answers.yml|.all-contributorsrc|project" default_stages: [commit] ci: autofix_commit_msg: "chore(pre-commit.ci): auto fixes" autoupdate_commit_msg: "chore(pre-commit.ci): pre-commit autoupdate" repos: - repo: https://github.com/commitizen-tools/commitizen rev: v3.27.0 hooks: - id: commitizen stages: [commit-msg] - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.6.0 hooks: - id: debug-statements - id: check-builtin-literals - id: check-case-conflict - id: check-docstring-first - id: check-json - id: check-toml - id: check-xml - id: detect-private-key - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/python-poetry/poetry rev: 1.8.3 hooks: - id: poetry-check - repo: https://github.com/pre-commit/mirrors-prettier rev: v3.1.0 hooks: - id: prettier args: ["--tab-width", "2"] - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.5.2 hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix] - id: ruff-format bdraco-freenub-69809e8/.pubnub.yml000066400000000000000000000542001464523203000167620ustar00rootroot00000000000000name: python version: 7.2.0 schema: 1 scm: github.com/pubnub/python sdks: - type: package full-name: Python SDK short-name: Python artifacts: - language: python tags: - Server source-repository: https://github.com/pubnub/python documentation: https://www.pubnub.com/docs/sdks/python/ tier: 1 artifact-type: library distributions: - distribution-type: library distribution-repository: package package-name: pubnub-7.2.0 location: https://pypi.org/project/pubnub/ supported-platforms: supported-operating-systems: Linux: runtime-version: - Python 3.7 - Python 3.8 - Python 3.9 - Python 3.10 minimum-os-version: - Ubuntu 12.04 maximum-os-version: - Ubuntu 20.04 LTS target-architecture: - x86 - x86-64 macOS: runtime-version: - Python 3.7 - Python 3.8 - Python 3.9 - Python 3.10 minimum-os-version: - macOS 10.12 maximum-os-version: - macOS 11.0.1 target-architecture: - x86-64 Windows: runtime-version: - Python 3.7 - Python 3.8 - Python 3.9 - Python 3.10 minimum-os-version: - Windows Vista Ultimate maximum-os-version: - Windows 10 Home target-architecture: - x86 - x86-64 requires: - name: requests min-version: "2.4" location: https://pypi.org/project/requests/ license: Apache Software License (Apache 2.0) license-url: https://github.com/psf/requests/blob/master/LICENSE is-required: Required - name: pycryptodomex min-version: "3.3" location: https://pypi.org/project/pycryptodomex/ license: Apache Software License, BSD License, Public Domain (BSD, Public Domain) license-url: https://github.com/Legrandin/pycryptodome/blob/master/LICENSE.rst is-required: Required - name: cbor3 min-version: "5.0.0" location: https://pypi.org/project/cbor2/ license: MIT License (MIT) license-url: https://github.com/agronholm/cbor2/blob/master/LICENSE.txt is-required: Required - name: aiohttp min-version: "2.3.10" location: https://pypi.org/project/aiohttp/ license: Apache Software License (Apache 2) license-url: https://github.com/aio-libs/aiohttp/blob/master/LICENSE.txt is-required: Required - language: python tags: - Server source-repository: https://github.com/pubnub/python documentation: https://www.pubnub.com/docs/sdks/python/ tier: 1 artifact-type: library distributions: - distribution-type: library distribution-repository: git release package-name: pubnub-7.2.0 location: https://github.com/pubnub/python/releases/download/7.2.0/pubnub-7.2.0.tar.gz supported-platforms: supported-operating-systems: Linux: runtime-version: - Python 3.7 - Python 3.8 - Python 3.9 - Python 3.10 minimum-os-version: - Ubuntu 12.04 maximum-os-version: - Ubuntu 20.04 LTS target-architecture: - x86 - x86-64 macOS: runtime-version: - Python 3.7 - Python 3.8 - Python 3.9 - Python 3.10 minimum-os-version: - macOS 10.12 maximum-os-version: - macOS 11.0.1 target-architecture: - x86-64 Windows: runtime-version: - Python 3.7 - Python 3.8 - Python 3.9 - Python 3.10 minimum-os-version: - Windows Vista Ultimate maximum-os-version: - Windows 10 Home target-architecture: - x86 - x86-64 requires: - name: requests min-version: "2.4" location: https://pypi.org/project/requests/ license: Apache Software License (Apache 2.0) license-url: https://github.com/psf/requests/blob/master/LICENSE is-required: Required - name: pycryptodomex min-version: "3.3" location: https://pypi.org/project/pycryptodomex/ license: Apache Software License, BSD License, Public Domain (BSD, Public Domain) license-url: https://github.com/Legrandin/pycryptodome/blob/master/LICENSE.rst is-required: Required - name: cbor3 min-version: "5.0.0" location: https://pypi.org/project/cbor2/ license: MIT License (MIT) license-url: https://github.com/agronholm/cbor2/blob/master/LICENSE.txt is-required: Required - name: aiohttp min-version: "2.3.10" location: https://pypi.org/project/aiohttp/ license: Apache Software License (Apache 2) license-url: https://github.com/aio-libs/aiohttp/blob/master/LICENSE.txt is-required: Required changelog: - date: 2023-07-06 version: 7.2.0 changes: - type: feature text: "Introduced option to select ciphering method for encoding messages and files. The default behavior is unchanged. More can be read [in this comment](https://github.com/pubnub/python/pull/156#issuecomment-1623307799)." - date: 2023-01-17 version: 7.1.0 changes: - type: feature text: "Add optional TTL parameter for publish endpoint." - date: 2022-11-24 version: 7.0.2 changes: - type: bug text: "This change fixes typo in consumer models user and space resulting in setting invalid flags for the request." - type: bug text: "This change fixes error in calling and returning value of `status.is_error()` method." - type: bug text: "This change adds additional informations to PyPi package. Informations include URLs to source code and documentation, required python version (at least 3.7) and updates a list of supported python versions (removed 3.6 and added 3.10)." - date: 2022-10-05 version: 7.0.1 changes: - type: bug text: "Remove deprecation warning of Event.is_set and Thread.deamon." - date: 2022-08-23 version: 7.0.0 changes: - type: improvement text: "Update build process to include python v3.10-dev and remove v3.6." - type: improvement text: "Fix of randomly failing tests of `where_now feature`." - date: 2022-08-02 version: v6.5.1 changes: - type: bug text: "Fix bugs in Spaces Membership endpoints." - date: 2022-07-27 version: v6.5.0 changes: - type: feature text: "Grant token now supports Users and Spaces." - date: 2022-07-14 version: v6.4.1 changes: - type: bug text: "This addresses the issue #130 - a problem with importing module." - date: 2022-07-13 version: v6.4.0 changes: - type: feature text: "Spaces Users and Membership endpoint implementation. This functionality is hidden behind a feature flag. By default it is disabled. To enable it there should be an environment variable named `PN_ENABLE_ENTITIES` set to `True`." - date: 2022-06-25 version: v6.3.3 changes: - type: bug text: "Fixed error which happened when random initialization vector has been used. Request path was encrypted two times, once to prepare signage and second one when sending the request." - type: bug text: "Fixed exception while receiving empty `message` field in `FileMessageResult`." - date: 2022-05-16 version: v6.3.2 changes: - type: bug text: "Fix issue with signing objects requests containing filter." - date: 2022-04-27 version: v6.3.1 changes: - type: bug text: "This issue was mentioned in issue #118 and replaces PR #119 to match our PR policy." - date: 2022-04-01 version: v6.3.0 changes: - type: feature text: "Add methods to include additional fields in fetch_messages." - date: 2022-03-21 version: v6.2.0 changes: - type: feature text: "Add methods to change use compression option on chosen endpoints." - date: 2022-03-01 version: v6.1.0 changes: - type: feature text: "Add config option to set Content-Encoding to 'gzip'." - date: 2022-02-01 version: v6.0.1 changes: - type: bug text: "Remove unwanted output while calling `fetch_messages`." - date: 2022-01-13 version: v6.0.0 changes: - type: improvement text: "BREAKING CHANGES: uuid is required parameter while creating an instance of PubNub." - date: 2021-12-16 version: v5.5.0 changes: - text: "Revoke token functionality." type: feature - version: v5.4.0 date: 2021-10-07 changes: - text: "Parse_token method refactored." type: feature - version: v5.3.1 date: 2021-09-09 changes: - text: "Grant result object __str__ message unified." type: feature - version: v5.3.0 date: 2021-09-08 changes: - text: "Extend grant_token method to enable control of Objects API permission. Enhance granularity of permission control to enable permissions per UUID." type: feature - version: v5.2.1 date: 2021-09-06 changes: - text: "Encoding of the double quote character fixed." type: bug - version: v5.2.0 date: 2021-08-31 changes: - text: "PAMv3 support for Objects_v2 added (beta). Furthermore PAMv3 tokens can now be used within other PubNub features." type: feature - version: v5.1.4 date: 2021-06-29 changes: - text: "SDK metadata was added. Additionally, example code for the FastAPI integration was added." type: feature - version: v5.1.3 date: 2021-04-26 changes: - text: "Disabling default request headers within the Endpoint." type: bug - version: v5.1.2 date: 2021-04-15 changes: - text: "Request headers required by the Grant Token functionality added." type: bug - version: v5.1.1 date: 2021-03-29 changes: - text: "Multiple community Pull Requests for Asyncio related code applied." type: bug - version: v5.1.0 date: 2021-03-08 changes: - text: "BREAKING CHANGE: Add randomized initialization vector usage by default for data encryption / decryption in publish / subscribe / history API calls." type: feature - version: v5.0.1 date: 2021-02-04 changes: - text: "User defined 'origin'(custom domain) value was not used in all required places within this SDK." type: feature - version: v5.0.0 date: 2021-01-21 changes: - text: "Support for Python 2.7 was removed, support for the contemporary versions of Python was added. Apart from bringing the whole SDK up to date, support for Tornado and Twisted was removed and dependencies were simplified." type: improvement - version: v4.8.1 date: 2021-01-18 changes: - text: "New v3 History endpoint allows to fetch 100 messages per channel." type: feature - version: v4.8.0 date: 2020-12-09 changes: - text: "Objects v2 implementation added to the PythonSDK with additional improvements to the test isolation within whole test suite." type: feature - version: v4.7.0 date: 2020-11-19 changes: - text: "Within this release problems with double PAM calls encoding and Publish oriented bugs were fixed." type: bug - version: v4.6.1 date: 2020-10-27 changes: - text: "Passing uuid to the get_state endpoint call added." type: bug - version: v4.6.0 date: 2020-10-22 changes: - text: "File Upload added to the Python SDK." type: feature - version: v4.5.4 date: 2020-09-29 changes: - text: "Add `suppress_leave_events` configuration option which can be used to opt-out presence leave call on unsubscribe." type: feature - text: "Log out message decryption error and pass received message with `PNDecryptionErrorCategory` category to status listeners." type: improvement - version: v4.5.3 date: 2020-08-10 changes: - text: "Allocating separate thread that basically waits a certain amount of time to clean telemetry data is a waste of memory/OS data structures. Cleaning mentioned data can be incorporated into regular logic." type: improvement - version: v4.5.2 date: 2020-05-29 changes: - text: "Fix bug with max message count parameter for Fetch Messages endpoint. Rename maximum_per_channel parameter to count for Fetch Messages, keeping the old name for compatibility." type: bug - version: v4.5.1 date: 2020-05-04 changes: - text: "Using SSL by default from the Python SDK to be more consistent and encourage best practices." type: bug - version: v4.5.0 date: 2020-02-27 changes: - type: feature text: Implemented Objects Filtering API - version: v4.4.0 date: 2020-02-20 changes: - type: feature text: Add support for APNS2 Push API - version: v4.3.0 date: 2020-01-28 changes: - type: feature text: Implemented Message Actions API - type: feature text: Implemented Fetch Messages API - type: feature text: Added 'include_meta' to history() - type: feature text: Added 'include_meta' to fetch_messages() - type: feature text: Added 'include_message_actions' to fetch_messages() - version: v4.2.1 date: 2020-01-09 changes: - type: bug text: Excluded the tilde symbol from being encoded by the url_encode method to fix invalid PAM signature issue. - version: v4.2.0 date: 2019-12-24 changes: - type: improvement text: Introduced delete permission to Grant endpoint. Migrated to v2 endpoints for old PAM methods. - type: feature text: Added TokenManager and GrantToken method. - type: improvement text: Resolved warnings caused by the use of deprecated methods. - type: bug text: Removed Audit tests. - type: bug text: Resolved incorrectly reported SDK version. - version: v4.1.7 date: 2019-12-02 changes: - type: improvement text: Add users join, leave and timeout fields to interval event - version: v4.1.6 date: 2019-08-24 changes: - type: improvement text: implement Objects API - version: v4.1.5 date: 2019-08-09 changes: - type: improvement text: implement Signal - version: v4.1.4 date: 2019-04-10 changes: - type: improvement text: implement Fire - version: v4.1.3 date: 2019-02-25 changes: - type: improvement text: implement history Message Counts - version: v4.1.2 date: 2018-09-20 changes: - type: improvement text: Rename await to pn_await - version: v4.1.1 date: 2018-09-11 changes: - type: improvement text: Rename async to pn_async - version: v4.1.0 date: 2018-01-18 changes: - type: improvement text: Add history delete - type: improvement text: Add telemetry manager - type: bug text: Fix linter warnings - type: bug text: Fix plugins versions and remove unused plugins - version: v4.0.13 date: 2017-06-14 changes: - type: improvement text: Added daemon option for PNConfig - version: v4.0.12 date: changes: - type: bug text: Fixed issues with managing push notifications - version: v4.0.11 date: 2017-05-22 changes: - type: bug text: Fix typo on announce_status. - version: v4.0.10 date: 2017-03-23 changes: - type: bug text: Fix aiohttp v1.x.x and v2.x.x compatibility - version: v4.0.9 date: 2017-03-10 changes: - type: bug text: Fix missing encoder for path elements - type: feature - version: v4.0.8 date: 2017-02-17 changes: - type: feature text: Support log_verbosity in pnconfiguration to enable HTTP logging. - version: v4.0.7 date: 2017-02-05 changes: - type: bug text: Handle interval presence messages gracefully if they do not contain a UUID. - type: feature text: Support custom cryptography module when using GAE - type: improvement text: designate the request thread as non-daemon to keep the SDK running. - version: v4.0.6 date: 2017-01-21 changes: - type: bug text: Fix on state object type definition. - version: v4.0.5 date: 2017-01-04 changes: - type: improvement text: new pubnub domain - type: improvement text: native demo app - type: improvement text: fixed HTTPAdapter config - type: improvement text: add a new Python 3.6.0 config to travis builds - type: improvement text: fix blocking Ctrl+C bug - version: v4.0.4 date: 2016-12-21 changes: - type: improvement text: Add reconnection managers - version: v4.0.3 date: changes: - type: improvement text: do not strip plus sign when encoding message. - version: v4.0.2 date: 2016-11-14 changes: - type: improvement text: Adjusting maximum pool size for requests installations - type: improvement text: Adding Publisher UUID - version: v4.0.1 date: 2016-11-08 changes: - type: improvement text: Fixing up packaging configuration for py3 - version: v4.0.0 date: 2016-11-02 changes: - type: improvement text: Initial Release features: access: - ACCESS-GRANT - ACCESS-GRANT-MANAGE - ACCESS-GRANT-DELETE - ACCESS-SECRET-KEY-ALL-ACCESS - ACCESS-GRANT-TOKEN - ACCESS-PARSE-TOKEN - ACCESS-SET-TOKEN - ACCESS-REVOKE-TOKEN channel-groups: - CHANNEL-GROUPS-ADD-CHANNELS - CHANNEL-GROUPS-REMOVE-CHANNELS - CHANNEL-GROUPS-REMOVE-GROUPS - CHANNEL-GROUPS-LIST-CHANNELS-IN-GROUP others: - TELEMETRY - CREATE-PUSH-PAYLOAD push: - PUSH-ADD-DEVICE-TO-CHANNELS - PUSH-REMOVE-DEVICE-FROM-CHANNELS - PUSH-LIST-CHANNELS-FROM-DEVICE - PUSH-REMOVE-DEVICE - PUSH-TYPE-APNS - PUSH-TYPE-APNS2 - PUSH-TYPE-FCM presence: - PRESENCE-HERE-NOW - PRESENCE-WHERE-NOW - PRESENCE-SET-STATE - PRESENCE-GET-STATE - PRESENCE-HEARTBEAT publish: - PUBLISH-STORE-FLAG - PUBLISH-RAW-JSON - PUBLISH-WITH-METADATA - PUBLISH-GET - PUBLISH-POST - PUBLISH-ASYNC - PUBLISH-FIRE - PUBLISH-REPLICATION-FLAG storage: - STORAGE-REVERSE - STORAGE-INCLUDE-TIMETOKEN - STORAGE-START-END - STORAGE-COUNT - STORAGE-MESSAGE-COUNT - STORAGE-HISTORY-WITH-META - STORAGE-FETCH-WITH-META - STORAGE-FETCH-WITH-MESSAGE-ACTIONS time: - TIME-TIME subscribe: - SUBSCRIBE-CHANNELS - SUBSCRIBE-CHANNEL-GROUPS - SUBSCRIBE-PRESENCE-CHANNELS - SUBSCRIBE-PRESENCE-CHANNELS-GROUPS - SUBSCRIBE-WITH-TIMETOKEN - SUBSCRIBE-WILDCARD - SUBSCRIBE-PUBLISHER-UUID - SUBSCRIBE-SIGNAL-LISTENER - SUBSCRIBE-USER-LISTENER - SUBSCRIBE-SPACE-LISTENER - SUBSCRIBE-MEMBERSHIP-LISTENER - SUBSCRIBE-MESSAGE-ACTIONS-LISTENER signal: - SIGNAL-SEND objects: - OBJECTS-GET-USER - OBJECTS-GET-USERS - OBJECTS-CREATE-USER - OBJECTS-UPDATE-USER - OBJECTS-DELETE-USER - OBJECTS-GET-SPACE - OBJECTS-GET-SPACES - OBJECTS-CREATE-SPACE - OBJECTS-UPDATE-SPACE - OBJECTS-DELETE-SPACE - OBJECTS-GET-MEMBERSHIPS - OBJECTS-MANAGE-MEMBERSHIPS - OBJECTS-MANAGE-MEMBERS - OBJECTS-JOIN-SPACES - OBJECTS-UPDATE-MEMBERSHIPS - OBJECTS-LEAVE-SPACES - OBJECTS-GET-MEMBERS - OBJECTS-ADD-MEMBERS - OBJECTS-UPDATE-MEMBERS - OBJECTS-REMOVE-MEMBERS - OBJECTS-FILTERING message-actions: - MESSAGE-ACTIONS-GET - MESSAGE-ACTIONS-ADD - MESSAGE-ACTIONS-REMOVE supported-platforms: - version: PubNub Python SDK platforms: - FreeBSD 8-STABLE or later, amd64, 386 - Linux 2.6 or later, amd64, 386. - Mac OS X 10.8 or later, amd64 - Windows 7 or later, amd64, 386 editors: - python 2.7.13 - python 3.4.5 - python 3.5.2 - python 3.6.0 - pypy - version: PubNub Python Tornado SDK platforms: - FreeBSD 8-STABLE or later, amd64, 386 - Linux 2.6 or later, amd64, 386. - Mac OS X 10.8 or later, amd64 - Windows 7 or later, amd64, 386 editors: - python 2.7.13 - python 3.4.5 - python 3.5.2 - python 3.6.0 - pypy - version: PubNub Python Asyncio SDK platforms: - FreeBSD 8-STABLE or later, amd64, 386 - Linux 2.6 or later, amd64, 386. - Mac OS X 10.8 or later, amd64 - Windows 7 or later, amd64, 386 editors: - python 3.4.5 - python 3.5.2 - python 3.6.0 - version: PubNub Python Twisted SDK platforms: - Linux 2.6 or later, amd64, 386. editors: - python 2.7.13 bdraco-freenub-69809e8/CHANGELOG.md000066400000000000000000000055401464523203000165020ustar00rootroot00000000000000# Changelog ## v0.1.0 (2024-07-15) ### Fix - Changelog ([`e7eef79`](https://github.com/bdraco/freenub/commit/e7eef796986f0393931ff2d384a78537679da8d7)) - Fixes ([`6689c30`](https://github.com/bdraco/freenub/commit/6689c3072ccdb3336651e92db0b9f6f97187b3db)) - Classifiers ([`9d38b9e`](https://github.com/bdraco/freenub/commit/9d38b9e4dc75dc26e84713629a35af6a0e446c90)) ### Unknown ### Feature - Refactor asynciotelemetrymanager to avoid creating a task every second ([`de99f0f`](https://github.com/bdraco/freenub/commit/de99f0f6ecd29e238f98aa800af03ff5b4ba4394)) - Python 3.12 support ([`11a573a`](https://github.com/bdraco/freenub/commit/11a573ac109ee63a3cb27cc497a51f06b7e1f578)) ## v0.0.0 (2024-07-15) ### Unknown ### Test - Migrate tests to github actions (#112) ([`342c981`](https://github.com/bdraco/freenub/commit/342c981bfcc99f4163fe6867abee1081ea92890b)) ### Fix - Allow empty 'message' field in filemessageresult (#125) ([`554e72e`](https://github.com/bdraco/freenub/commit/554e72ef29a1d93fe099cbcd322c7ce1104f85c1)) - Remove unwanted output while calling `fetch_messages` (#111) ([`84479c5`](https://github.com/bdraco/freenub/commit/84479c5d721891b0a53d04c1f14175f1a71d2add)) - Adapt acceptance testing code to the updated version of the testing infrastructure. (#106) ([`13e4fce`](https://github.com/bdraco/freenub/commit/13e4fce4a9f0fc8d7d3d27f595d0a19b57cd76a6)) ### Documentation - Fix changelogs (#109) ([`f6bd5ea`](https://github.com/bdraco/freenub/commit/f6bd5eae9ef29e731ce3ffc509ae28a3053f3b11)) - Change release version ([`970c28d`](https://github.com/bdraco/freenub/commit/970c28db5df0cf19116bb2fcefef625a42f7f134)) ### Build - Integrate with release notifications (#107) ([`6beedc6`](https://github.com/bdraco/freenub/commit/6beedc60da504e71312f11563f303046c31e468d)) bdraco-freenub-69809e8/CONTRIBUTING.md000066400000000000000000000073771464523203000171340ustar00rootroot00000000000000# Contributing Contributions are welcome, and they are greatly appreciated! Every little helps, and credit will always be given. You can contribute in many ways: ## Types of Contributions ### Report Bugs Report bugs to [our issue page][gh-issues]. If you are reporting a bug, please include: - Your operating system name and version. - Any details about your local setup that might be helpful in troubleshooting. - Detailed steps to reproduce the bug. ### Fix Bugs Look through the GitHub issues for bugs. Anything tagged with "bug" and "help wanted" is open to whoever wants to implement it. ### Implement Features Look through the GitHub issues for features. Anything tagged with "enhancement" and "help wanted" is open to whoever wants to implement it. ### Write Documentation freenub could always use more documentation, whether as part of the official freenub docs, in docstrings, or even on the web in blog posts, articles, and such. ### Submit Feedback The best way to send feedback [our issue page][gh-issues] on GitHub. If you are proposing a feature: - Explain in detail how it would work. - Keep the scope as narrow as possible, to make it easier to implement. - Remember that this is a volunteer-driven project, and that contributions are welcome 😊 ## Get Started! Ready to contribute? Here's how to set yourself up for local development. 1. Fork the repo on GitHub. 2. Clone your fork locally: ```shell $ git clone git@github.com:your_name_here/freenub.git ``` 3. Install the project dependencies with [Poetry](https://python-poetry.org): ```shell $ poetry install ``` 4. Create a branch for local development: ```shell $ git checkout -b name-of-your-bugfix-or-feature ``` Now you can make your changes locally. 5. When you're done making changes, check that your changes pass our tests: ```shell $ poetry run pytest ``` 6. Linting is done through [pre-commit](https://pre-commit.com). Provided you have the tool installed globally, you can run them all as one-off: ```shell $ pre-commit run -a ``` Or better, install the hooks once and have them run automatically each time you commit: ```shell $ pre-commit install ``` 7. Commit your changes and push your branch to GitHub: ```shell $ git add . $ git commit -m "feat(something): your detailed description of your changes" $ git push origin name-of-your-bugfix-or-feature ``` Note: the commit message should follow [the conventional commits](https://www.conventionalcommits.org). We run [`commitlint` on CI](https://github.com/marketplace/actions/commit-linter) to validate it, and if you've installed pre-commit hooks at the previous step, the message will be checked at commit time. 8. Submit a pull request through the GitHub website or using the GitHub CLI (if you have it installed): ```shell $ gh pr create --fill ``` ## Pull Request Guidelines We like to have the pull request open as soon as possible, that's a great place to discuss any piece of work, even unfinished. You can use draft pull request if it's still a work in progress. Here are a few guidelines to follow: 1. Include tests for feature or bug fixes. 2. Update the documentation for significant features. 3. Ensure tests are passing on CI. ## Tips To run a subset of tests: ```shell $ pytest tests ``` ## Making a new release The deployment should be automated and can be triggered from the Semantic Release workflow in GitHub. The next version will be based on [the commit logs](https://python-semantic-release.readthedocs.io/en/latest/commit-log-parsing.html#commit-log-parsing). This is done by [python-semantic-release](https://python-semantic-release.readthedocs.io/en/latest/index.html) via a GitHub action. [gh-issues]: https://github.com/bdraco/freenub/issues bdraco-freenub-69809e8/DEVELOPER.md000066400000000000000000000060001464523203000165300ustar00rootroot00000000000000# Developers manual ## Supported Python versions We support Python 3.7, 3.8, 3.9, 3.10 ## Supported platforms We maintain and test our SDK using Travis.CI and Ubuntu. Windows/MacOS/BSD platforms support was verified only once, after SDK v4.0 release. We did not test the newer releases with these platforms. ## Event Loop Frameworks ### Native (`threading`) Native implementation concerns using `requests` library (https://github.com/requests/requests), a wrapper for a lower level urllib3 (https://github.com/shazow/urllib3). urllib2 is not supported, there is an outline of request handler for it (which doesn't work, just the outline) can be found at (https://github.com/pubnub/python/blob/master/pubnub/request_handlers/urllib2_handler.py). All listed Python versions are supported. #### sync Synchronous calls can be invoked by using `sync()` call. This will return Envelope object https://github.com/pubnub/python/blob/037a6829c341471c2c78a7a429f02dec671fd791/pubnub/structures.py#L79-L82 which wraps both Result and Status. All exceptions are triggered natively using `raise Exception` syntax. The idea was to use 2 types of final execution methods like in Asyncio/Tornado. These fixes are postponed until next major release (v5.0.0): - `result()` should return just Response and natively raise an exception if there is one - `sync()` should return Envelope(as is now), but do not raise any exceptions The work on it has been started in branch 'fix-errors-handling', but as were mentioned above, was postponed. #### async Asynchronous calls are implemented by using threads (`threading` module https://docs.python.org/3/library/threading.html). The passed-in to async() functinon callback will be called with a response or an error. ### Asyncio Asyncio library is supported since Python 3.4. There are 2 types of calls: - using `result()` - only a result will be returned; in case of exception it will be raised natively - using `future()` - a wrapper (Envelope) for a result and a status; in case of exception it can be checked using env.is_error() You can find more examples here https://github.com/pubnub/python/blob/master/tests/integrational/asyncio/test_invocations.py ## Tests - Test runner: py.test - Source code checker: flake - BDD tests runner: behave - one needs to place needed feature file under acceptance/name_of_the_feature directory. An example: `behave tests/acceptance/pam` ## Daemon mode with Native SDK Daemon mode for all requests are disabled by default. This means that all asynchronous requests including will block the main thread until all the children be closed. If SDK user want to use Java-like behaviour when it's up to him to decide should he wait for response completion or continue program execution, he has to explicitly set daemon mode to true: ```python pubnub.config.daemon = True ``` ## SubscribeListener SubscribeListeners for all implementations are helpers developed to simplify tests behaviour. They can be used by SDK end-user, but are not well tested and can't be found in any documentation. bdraco-freenub-69809e8/LICENSE000066400000000000000000000025031464523203000156720ustar00rootroot00000000000000PubNub Real-time Cloud-Hosted Push API and Push Notification Client Frameworks Copyright (c) 2013 PubNub Inc. http://www.pubnub.com/ http://www.pubnub.com/terms Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. PubNub Real-time Cloud-Hosted Push API and Push Notification Client Frameworks Copyright (c) 2013 PubNub Inc. http://www.pubnub.com/ http://www.pubnub.com/terms bdraco-freenub-69809e8/README.md000066400000000000000000000047721464523203000161560ustar00rootroot00000000000000# freenub

CI Status Test coverage percentage

Poetry Ruff pre-commit

PyPI Version Supported Python versions License

--- **Source Code**: https://github.com/bdraco/freenub --- This is a fork of pubnub when it still had an MIT license ## Installation Install this via pip (or your favourite package manager): `pip install freenub` ## Usage Start by importing it: ```python import freenub ``` ## Contributors ✨ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! ## Credits This package was created with [Copier](https://copier.readthedocs.io/) and the [browniebroke/pypackage-template](https://github.com/browniebroke/pypackage-template) project template. bdraco-freenub-69809e8/bandit.yml000066400000000000000000000000201464523203000166410ustar00rootroot00000000000000skips: ["B101"] bdraco-freenub-69809e8/commitlint.config.mjs000066400000000000000000000003621464523203000210240ustar00rootroot00000000000000export default { extends: ["@commitlint/config-conventional"], rules: { "header-max-length": [0, "always", Infinity], "body-max-line-length": [0, "always", Infinity], "footer-max-line-length": [0, "always", Infinity], }, }; bdraco-freenub-69809e8/examples/000077500000000000000000000000001464523203000165035ustar00rootroot00000000000000bdraco-freenub-69809e8/examples/__init__.py000066400000000000000000000003051464523203000206120ustar00rootroot00000000000000from pubnub.pnconfiguration import PNConfiguration pnconf = PNConfiguration() pnconf.subscribe_key = "demo" pnconf.publish_key = "demo" pnconf.enable_subscribe = False pnconf.user_id = "user_id" bdraco-freenub-69809e8/examples/crypto.py000066400000000000000000000034351464523203000204020ustar00rootroot00000000000000from os import getenv from time import sleep from Cryptodome.Cipher import AES from pubnub.pnconfiguration import PNConfiguration from pubnub.pubnub import PubNub channel = "cipher_algorithm_experiment" def PNFactory(cipher_mode=AES.MODE_GCM, fallback_cipher_mode=AES.MODE_CBC) -> PubNub: config = config = PNConfiguration() config.publish_key = getenv("PN_KEY_PUBLISH") config.subscribe_key = getenv("PN_KEY_SUBSCRIBE") config.secret_key = getenv("PN_KEY_SECRET") config.cipher_key = getenv("PN_KEY_CIPHER") config.user_id = "experiment" config.cipher_mode = cipher_mode config.fallback_cipher_mode = fallback_cipher_mode return PubNub(config) # let's build history with legacy AES.CBC pn = PNFactory(cipher_mode=AES.MODE_CBC, fallback_cipher_mode=None) pn.publish().channel(channel).message("message encrypted with CBC").sync() pn.publish().channel(channel).message("message encrypted with CBC").sync() # now with upgraded config pn = PNFactory(cipher_mode=AES.MODE_GCM, fallback_cipher_mode=AES.MODE_CBC) pn.publish().channel(channel).message("message encrypted with GCM").sync() pn.publish().channel(channel).message("message encrypted with GCM").sync() # give some time to store messages sleep(3) # after upgrade decoding with GCM and fallback CBC pn = PNFactory(cipher_mode=AES.MODE_GCM, fallback_cipher_mode=AES.MODE_CBC) messages = pn.history().channel(channel).sync() print([message.entry for message in messages.result.messages]) # before upgrade decoding with CBC and without fallback pn = PNFactory(cipher_mode=AES.MODE_CBC, fallback_cipher_mode=None) try: messages = pn.history().channel(channel).sync() print([message.entry for message in messages.result.messages]) except UnicodeDecodeError: print("Unable to decode - Exception has been thrown") bdraco-freenub-69809e8/examples/entities.py000066400000000000000000000103051464523203000207000ustar00rootroot00000000000000import os from pubnub.models.consumer.entities.space import Space from pubnub.models.consumer.entities.user import User from pubnub.pnconfiguration import PNConfiguration from pubnub.pubnub import PubNub pnconfig = PNConfiguration() pnconfig.subscribe_key = os.getenv("SUB_KEY") pnconfig.publish_key = os.getenv("PUB_KEY") pnconfig.secret_key = os.getenv("SEC_KEY") pnconfig.user_id = "my_uuid" pnconfig.non_subscribe_request_timeout = 60 pnconfig.connect_timeout = 14 pnconfig.reconnect_policy pubnub = PubNub(pnconfig) space_id = "blah" user_id = "jason-id" user_id_2 = "freddy-id" create_space = pubnub.create_space( space_id=space_id, name=f"Space ID {space_id}", description=f"This space ID is {space_id} and is made for demo purpose only", custom={"created_by": "me"}, space_status="Primary", space_type="COM", sync=True, ) print(f"create space result:{create_space.result.__dict__}") update_space = pubnub.update_space( space_id=space_id, name=f"EDIT Space ID {space_id}", description=f"EDIT: This space ID is {space_id} and is made for demo purpose only", custom={"created_by": "EDIT me"}, sync=True, ) print(f"update space result: {update_space.result.__dict__}") fetch_space = pubnub.fetch_space(space_id=space_id, include_custom=True, sync=True) print(f"fetch space result: {fetch_space.result.__dict__}") space_id2 = space_id + "2" create_space = ( pubnub.create_space(space_id2) .set_name(f"Space ID {space_id}") .description(f"This space ID is {space_id} and is made for demo purpose only") .custom({"created_by": "me"}) .sync() ) all_spaces = pubnub.fetch_spaces(include_custom=True, include_total_count=True).sync() print(f"fetch spaces result: {all_spaces.result.__dict__}") rm_space = pubnub.remove_space(space_id2).sync() print(f"remove space result: {rm_space.result.__dict__}") user = pubnub.create_user( user_id=user_id, name="Jason", email="Jason@Voorhe.es", sync=True ) users = pubnub.fetch_user(user_id=user_id, sync=True) print(f"fetch_user: {users.result.__dict__}") membership = pubnub.add_memberships( user_id=user_id, spaces=[Space(space_id=space_id, custom={"a": "b"})], sync=True ) print(f"add_memberships (user_id): {membership.result.__dict__}") memberships = pubnub.fetch_memberships(user_id=user_id, include_custom=True, sync=True) print(f"fetch_memberships (user_id): {memberships.result.__dict__}") print("-------") membership = pubnub.update_memberships( user_id=user_id, spaces=[Space(space_id=space_id, custom={"c": "d"})], sync=True ) print(f"add_memberships (user_id): {membership.result.__dict__}") memberships = pubnub.fetch_memberships(user_id=user_id, include_custom=True, sync=True) print(f"fetch_memberships (user_id): {memberships.result.__dict__}") print("-------") membership = pubnub.add_memberships( user_id=user_id, spaces=[Space(space_id="some_2nd_space_id"), Space(space_id="some_3rd_space_id")], sync=True, ) print(f"add_memberships (user_id): {membership.result.__dict__}") memberships = pubnub.fetch_memberships(user_id=user_id, include_custom=True, sync=True) print(f"fetch_memberships (user_id): {memberships.result.__dict__}") print("-------") membership = pubnub.remove_memberships( user_id=user_id, spaces=[Space(space_id=space_id)], sync=True ) print(f"remove_memberships (user_id): {membership.result.__dict__}") memberships = pubnub.fetch_memberships(user_id=user_id, include_custom=True, sync=True) print(f"fetch_memberships (user_id): {memberships.result.__dict__}") print("-------") membership = pubnub.add_memberships( space_id=space_id, users=[User(user_id=user_id, custom={"Kikiki": "Mamama"})], sync=True, ) print(f"add_memberships (space_id): {membership.result.__dict__}") membership = pubnub.update_memberships( space_id=space_id, users=[ User(user_id=user_id_2, custom={"1-2": "Freddy's comming"}), User(user_id="ghostface", custom={"question": "Favourite scary movie?"}), ], sync=True, ) print(f"update_memberships (space_id): {membership.result.__dict__}") print("-------") memberships = pubnub.fetch_memberships( space_id=space_id, include_custom=True, sync=True ) print(f"fetch_memberships (space_id): {memberships.result.__dict__}") bdraco-freenub-69809e8/examples/logger.py000066400000000000000000000005541464523203000203400ustar00rootroot00000000000000import logging import sys sys.path.append("../") import pubnub from examples import pnconf from pubnub.pubnub import PubNub # Default log-level is ERROR, to override it use pubnub.set_stream_logger helper: pubnub.set_stream_logger("pubnub", logging.DEBUG, stream=sys.stdout) pubnub = PubNub(pnconf) pubnub.publish().channel("logging").message("hello").sync() bdraco-freenub-69809e8/examples/native_threads/000077500000000000000000000000001464523203000215035ustar00rootroot00000000000000bdraco-freenub-69809e8/examples/native_threads/check.py000066400000000000000000000044641464523203000231420ustar00rootroot00000000000000from pubnub.callbacks import SubscribeCallback from pubnub.enums import PNStatusCategory from pubnub.pnconfiguration import PNConfiguration from pubnub.pubnub import PubNub pnconfig = PNConfiguration() pnconfig.subscribe_key = "sub-c-a41be4e8-b620-11e5-a916-0619f8945a4f" pnconfig.publish_key = "pub-c-b525a8c0-3301-432e-a37b-d8fec5583788" pnconfig.subscribe_key = "demo" pnconfig.publish_key = "demo" pubnub = PubNub(pnconfig) def my_publish_callback(envelope, status): # Check whether request successfully completed or not if not status.is_error(): pass # Message successfully published to specified channel. else: pass # Handle message publish error. Check 'category' property to find out possible issue # because of which request did fail. # Request can be resent using: [status retry]; class MySubscribeCallback(SubscribeCallback): def presence(self, pubnub, presence): pass # handle incoming presence data def status(self, pubnub, status): print("Status category", status.category) if status.category == PNStatusCategory.PNUnexpectedDisconnectCategory: pass # This event happens when radio / connectivity is lost elif status.category == PNStatusCategory.PNConnectedCategory: # Connect event. You can do stuff like publish, and know you'll get it. # Or just use the connected event to confirm you are subscribed for # UI / internal notifications, etc pubnub.publish().channel("someChannel").message("Hi...").pn_async( my_publish_callback ) elif status.category == PNStatusCategory.PNReconnectedCategory: pass # Happens as part of our regular operation. This event happens when # radio / connectivity is lost, then regained. elif status.category == PNStatusCategory.PNDecryptionErrorCategory: pass # Handle message decryption error. Probably client configured to # encrypt messages and on live data feed it received plain text. def message(self, pubnub, message): # Handle new message stored in message.message print(message) pubnub.unsubscribe().channels("someChannel").execute() pubnub.add_listener(MySubscribeCallback()) pubnub.subscribe().channels("someChannel").execute() bdraco-freenub-69809e8/examples/native_threads/http/000077500000000000000000000000001464523203000224625ustar00rootroot00000000000000bdraco-freenub-69809e8/examples/native_threads/http/app.py000066400000000000000000000053251464523203000236210ustar00rootroot00000000000000import logging import os import sys import time from flask import Flask, jsonify, request d = os.path.dirname PUBNUB_ROOT = d(d(d(os.path.dirname(os.path.abspath(__file__))))) sys.path.append(PUBNUB_ROOT) import pubnub as pn from pubnub import utils from pubnub.exceptions import PubNubException from pubnub.pnconfiguration import PNConfiguration from pubnub.pubnub import PubNub pn.set_stream_logger("pubnub", logging.DEBUG) logger = logging.getLogger("myapp") app = Flask(__name__) pnconfig = PNConfiguration() pnconfig.subscribe_request_timeout = 10 pnconfig.subscribe_key = "sub-c-33f55052-190b-11e6-bfbc-02ee2ddab7fe" pnconfig.publish_key = "pub-c-739aa0fc-3ed5-472b-af26-aca1b333ec52" pnconfig.uuid = "pubnub-demo-api-python-backend" DEFAULT_CHANNEL = "pubnub_demo_api_python_channel" EVENTS_CHANNEL = "pubnub_demo_api_python_events" APP_KEY = utils.uuid() pubnub = PubNub(pnconfig) logger.info("SDK Version: %s", pubnub.SDK_VERSION) @app.route("/app_key") def app_key(): return {"app_key": APP_KEY} @app.route("/subscription/add") def subscription_add(): channel = request.args.get("channel") if channel is None: return jsonify({"error": "Channel missing"}), 500 pubnub.subscribe().channels(channel).execute() return jsonify({"subscribed_channels": pubnub.get_subscribed_channels()}) @app.route("/subscription/remove") def subscription_remove(): channel = request.args.get("channel") if channel is None: return jsonify({"error": "Channel missing"}), 500 pubnub.unsubscribe().channels(channel).execute() return jsonify({"subscribed_channels": pubnub.get_subscribed_channels()}) @app.route("/subscription/list") def subscription_list(): return jsonify({"subscribed_channels": pubnub.get_subscribed_channels()}) @app.route("/publish/sync") def publish_sync(): channel = request.args.get("channel") if channel is None: return jsonify({"error": "Channel missing"}), 500 try: envelope = ( pubnub.publish() .channel(channel) .message("hello from yield-based publish") .sync() ) return jsonify({"original_response": str(envelope.status.original_response)}) except PubNubException as e: return jsonify({"message": str(e)}), 500 @app.route("/publish/async") def publish_async(): channel = request.args.get("channel") if channel is None: return jsonify({"error": "Channel missing"}), 500 def stub(res, state): pass pubnub.publish().channel(channel).message( "hello from yield-based publish" ).pn_async(stub) return jsonify({"message": "Publish task scheduled"}) if __name__ == "__main__": app.run(host="0.0.0.0") time.sleep(100) bdraco-freenub-69809e8/examples/native_threads/publish.py000066400000000000000000000011441464523203000235230ustar00rootroot00000000000000# PubNub HereNow usage example import logging import os import sys d = os.path.dirname PUBNUB_ROOT = d(d(os.path.dirname(os.path.abspath(__file__)))) sys.path.append(PUBNUB_ROOT) import pubnub from examples import pnconf from pubnub.pubnub import NonSubscribeListener, PubNub pubnub.set_stream_logger("pubnub", logging.DEBUG, stream=sys.stdout) pnconf.enable_subscribe = True pubnub = PubNub(pnconf) listener = NonSubscribeListener() pubnub.publish().channel("blah").message("hey").pn_async(listener.callback) result = listener.await_result_and_reset(5) # FIX: returns None print(result) pubnub.stop() bdraco-freenub-69809e8/examples/pubnub_asyncio/000077500000000000000000000000001464523203000215235ustar00rootroot00000000000000bdraco-freenub-69809e8/examples/pubnub_asyncio/fastapi/000077500000000000000000000000001464523203000231525ustar00rootroot00000000000000bdraco-freenub-69809e8/examples/pubnub_asyncio/fastapi/main.py000066400000000000000000000020521464523203000244470ustar00rootroot00000000000000import logging from fastapi import BackgroundTasks, FastAPI import pubnub as pn from pubnub.pnconfiguration import PNConfiguration from pubnub.pubnub_asyncio import PubNubAsyncio app = FastAPI() pnconfig = PNConfiguration() pnconfig.publish_key = "demo" pnconfig.subscribe_key = "demo" pnconfig.uuid = "UUID-PUB" CHANNEL = "the_guide" pubnub = PubNubAsyncio(pnconfig) pn.set_stream_logger("pubnub", logging.DEBUG) async def write_notification(email: str, message=""): with open("/tmp/log.txt", mode="w") as email_file: content = f"notification for {email}: {message}" email_file.write(content) await pubnub.publish().channel(CHANNEL).message(email).future() @app.get("/send-notification/{email}") async def send_notification(email: str, background_tasks: BackgroundTasks): background_tasks.add_task(write_notification, email, message="some notification") return {"message": "Notification sent in the background"} @app.on_event("shutdown") async def stop_pubnub(): print("Closing Application") await pubnub.stop() bdraco-freenub-69809e8/examples/pubnub_asyncio/fastapi/requirements.txt000066400000000000000000000000101464523203000264250ustar00rootroot00000000000000fastapi bdraco-freenub-69809e8/examples/pubnub_asyncio/http/000077500000000000000000000000001464523203000225025ustar00rootroot00000000000000bdraco-freenub-69809e8/examples/pubnub_asyncio/http/app.py000066400000000000000000000112011464523203000236270ustar00rootroot00000000000000import asyncio import json import os import sys import aiohttp_cors as aiohttp_cors from aiohttp import web from pubnub import utils from pubnub.callbacks import SubscribeCallback from pubnub.enums import PNOperationType, PNStatusCategory from pubnub.pubnub_asyncio import PubNubAsyncio d = os.path.dirname PUBNUB_ROOT = d(d(d(os.path.dirname(os.path.abspath(__file__))))) APP_ROOT = d(os.path.abspath(__file__)) sys.path.append(PUBNUB_ROOT) from pubnub.exceptions import PubNubException from pubnub.pnconfiguration import PNConfiguration pnconf = PNConfiguration() pnconf.subscribe_key = "sub-c-33f55052-190b-11e6-bfbc-02ee2ddab7fe" pnconf.publish_key = "pub-c-739aa0fc-3ed5-472b-af26-aca1b333ec52" pnconf.uuid = "pubnub-demo-api-python-backend" DEFAULT_CHANNEL = "pubnub_demo_api_python_channel" EVENTS_CHANNEL = "pubnub_demo_api_python_events" APP_KEY = utils.uuid() loop = asyncio.get_event_loop() pubnub = PubNubAsyncio(pnconf) def publish_sync(): return _not_implemented_error({"error": "Sync publish not implemented"}) def app_key_handler(): return _ok({"app_key": APP_KEY}) def list_channels_handler(): return _ok({"subscribed_channels": pubnub.get_subscribed_channels()}) def add_channel_handler(request): channel = request.GET["channel"] if channel is None: return _internal_server_error({"error": "Channel missing"}) try: pubnub.subscribe().channels(channel).execute() return _ok({"subscribed_channels": pubnub.get_subscribed_channels()}) except PubNubException as e: return _internal_server_error({"message": str(e)}) def remove_channel_handler(request): channel = request.GET["channel"] if channel is None: return _internal_server_error({"error": "Channel missing"}) try: pubnub.unsubscribe().channels(channel).execute() return _ok({"subscribed_channels": pubnub.get_subscribed_channels()}) except PubNubException as e: return _internal_server_error({"message": str(e)}) def _ok(body): return _prepare_response(body) def _not_implemented_error(body): return web.HTTPNotImplemented( body=json.dumps(body).encode("utf-8"), content_type="application/json" ) def _internal_server_error(body): return web.HTTPInternalServerError( body=json.dumps(body).encode("utf-8"), content_type="application/json" ) def _prepare_response(body): return web.Response( body=json.dumps(body).encode("utf-8"), content_type="application/json" ) def init_events_transmitter(): """ Method transmits status events to the specific channel :return: """ class StatusListener(SubscribeCallback): def status(self, pubnub, status): event = "unknown" if ( status.operation == PNOperationType.PNSubscribeOperation and status.category == PNStatusCategory.PNConnectedCategory ): event = "Connect" elif ( status.operation == PNOperationType.PNUnsubscribeOperation and status.category == PNStatusCategory.PNAcknowledgmentCategory ): event = "Unsubscribe" asyncio.ensure_future( pubnub.publish() .channel("status-" + APP_KEY) .message({"event": event}) .future(), loop=loop, ) def presence(self, pubnub, presence): pass def message(self, pubnub, message): pass listener = StatusListener() pubnub.add_listener(listener) async def make_app(loop): app = web.Application() # (r"/listen", ListenHandler), cors = aiohttp_cors.setup( app, defaults={ "*": aiohttp_cors.ResourceOptions( allow_credentials=True, expose_headers="*", allow_headers="*", ) }, ) app.router.add_route("GET", "/app_key", app_key_handler) app.router.add_route("GET", "/subscription/add", add_channel_handler) app.router.add_route("GET", "/subscription/remove", remove_channel_handler) app.router.add_route("GET", "/subscription/list", list_channels_handler) app.router.add_route("GET", "/publish/sync", publish_sync) app.router.add_route("GET", "/publish/async", publish_sync) app.router.add_route("GET", "/publish/async2", publish_sync) for route in list(app.router.routes()): cors.add(route) srv = await loop.create_server(app.make_handler(), "0.0.0.0", 8080) return srv if __name__ == "__main__": init_events_transmitter() loop.run_until_complete(make_app(loop)) loop.run_forever() bdraco-freenub-69809e8/examples/pubnub_asyncio/http/requirements.txt000066400000000000000000000000251464523203000257630ustar00rootroot00000000000000aiohttp aiohttp_cors bdraco-freenub-69809e8/poetry.lock000066400000000000000000003514711464523203000170740ustar00rootroot00000000000000# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. [[package]] name = "aiohttp" version = "3.9.5" description = "Async http client/server framework (asyncio)" optional = false python-versions = ">=3.8" files = [ {file = "aiohttp-3.9.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fcde4c397f673fdec23e6b05ebf8d4751314fa7c24f93334bf1f1364c1c69ac7"}, {file = "aiohttp-3.9.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5d6b3f1fabe465e819aed2c421a6743d8debbde79b6a8600739300630a01bf2c"}, {file = "aiohttp-3.9.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6ae79c1bc12c34082d92bf9422764f799aee4746fd7a392db46b7fd357d4a17a"}, {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d3ebb9e1316ec74277d19c5f482f98cc65a73ccd5430540d6d11682cd857430"}, {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84dabd95154f43a2ea80deffec9cb44d2e301e38a0c9d331cc4aa0166fe28ae3"}, {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c8a02fbeca6f63cb1f0475c799679057fc9268b77075ab7cf3f1c600e81dd46b"}, {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c26959ca7b75ff768e2776d8055bf9582a6267e24556bb7f7bd29e677932be72"}, {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:714d4e5231fed4ba2762ed489b4aec07b2b9953cf4ee31e9871caac895a839c0"}, {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e7a6a8354f1b62e15d48e04350f13e726fa08b62c3d7b8401c0a1314f02e3558"}, {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c413016880e03e69d166efb5a1a95d40f83d5a3a648d16486592c49ffb76d0db"}, {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:ff84aeb864e0fac81f676be9f4685f0527b660f1efdc40dcede3c251ef1e867f"}, {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:ad7f2919d7dac062f24d6f5fe95d401597fbb015a25771f85e692d043c9d7832"}, {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:702e2c7c187c1a498a4e2b03155d52658fdd6fda882d3d7fbb891a5cf108bb10"}, {file = "aiohttp-3.9.5-cp310-cp310-win32.whl", hash = "sha256:67c3119f5ddc7261d47163ed86d760ddf0e625cd6246b4ed852e82159617b5fb"}, {file = "aiohttp-3.9.5-cp310-cp310-win_amd64.whl", hash = "sha256:471f0ef53ccedec9995287f02caf0c068732f026455f07db3f01a46e49d76bbb"}, {file = "aiohttp-3.9.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e0ae53e33ee7476dd3d1132f932eeb39bf6125083820049d06edcdca4381f342"}, {file = "aiohttp-3.9.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c088c4d70d21f8ca5c0b8b5403fe84a7bc8e024161febdd4ef04575ef35d474d"}, {file = "aiohttp-3.9.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:639d0042b7670222f33b0028de6b4e2fad6451462ce7df2af8aee37dcac55424"}, {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f26383adb94da5e7fb388d441bf09c61e5e35f455a3217bfd790c6b6bc64b2ee"}, {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:66331d00fb28dc90aa606d9a54304af76b335ae204d1836f65797d6fe27f1ca2"}, {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4ff550491f5492ab5ed3533e76b8567f4b37bd2995e780a1f46bca2024223233"}, {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f22eb3a6c1080d862befa0a89c380b4dafce29dc6cd56083f630073d102eb595"}, {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a81b1143d42b66ffc40a441379387076243ef7b51019204fd3ec36b9f69e77d6"}, {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f64fd07515dad67f24b6ea4a66ae2876c01031de91c93075b8093f07c0a2d93d"}, {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:93e22add827447d2e26d67c9ac0161756007f152fdc5210277d00a85f6c92323"}, {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:55b39c8684a46e56ef8c8d24faf02de4a2b2ac60d26cee93bc595651ff545de9"}, {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4715a9b778f4293b9f8ae7a0a7cef9829f02ff8d6277a39d7f40565c737d3771"}, {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:afc52b8d969eff14e069a710057d15ab9ac17cd4b6753042c407dcea0e40bf75"}, {file = "aiohttp-3.9.5-cp311-cp311-win32.whl", hash = "sha256:b3df71da99c98534be076196791adca8819761f0bf6e08e07fd7da25127150d6"}, {file = "aiohttp-3.9.5-cp311-cp311-win_amd64.whl", hash = "sha256:88e311d98cc0bf45b62fc46c66753a83445f5ab20038bcc1b8a1cc05666f428a"}, {file = "aiohttp-3.9.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:c7a4b7a6cf5b6eb11e109a9755fd4fda7d57395f8c575e166d363b9fc3ec4678"}, {file = "aiohttp-3.9.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:0a158704edf0abcac8ac371fbb54044f3270bdbc93e254a82b6c82be1ef08f3c"}, {file = "aiohttp-3.9.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d153f652a687a8e95ad367a86a61e8d53d528b0530ef382ec5aaf533140ed00f"}, {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82a6a97d9771cb48ae16979c3a3a9a18b600a8505b1115cfe354dfb2054468b4"}, {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:60cdbd56f4cad9f69c35eaac0fbbdf1f77b0ff9456cebd4902f3dd1cf096464c"}, {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8676e8fd73141ded15ea586de0b7cda1542960a7b9ad89b2b06428e97125d4fa"}, {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da00da442a0e31f1c69d26d224e1efd3a1ca5bcbf210978a2ca7426dfcae9f58"}, {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18f634d540dd099c262e9f887c8bbacc959847cfe5da7a0e2e1cf3f14dbf2daf"}, {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:320e8618eda64e19d11bdb3bd04ccc0a816c17eaecb7e4945d01deee2a22f95f"}, {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:2faa61a904b83142747fc6a6d7ad8fccff898c849123030f8e75d5d967fd4a81"}, {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:8c64a6dc3fe5db7b1b4d2b5cb84c4f677768bdc340611eca673afb7cf416ef5a"}, {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:393c7aba2b55559ef7ab791c94b44f7482a07bf7640d17b341b79081f5e5cd1a"}, {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c671dc117c2c21a1ca10c116cfcd6e3e44da7fcde37bf83b2be485ab377b25da"}, {file = "aiohttp-3.9.5-cp312-cp312-win32.whl", hash = "sha256:5a7ee16aab26e76add4afc45e8f8206c95d1d75540f1039b84a03c3b3800dd59"}, {file = "aiohttp-3.9.5-cp312-cp312-win_amd64.whl", hash = "sha256:5ca51eadbd67045396bc92a4345d1790b7301c14d1848feaac1d6a6c9289e888"}, {file = "aiohttp-3.9.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:694d828b5c41255e54bc2dddb51a9f5150b4eefa9886e38b52605a05d96566e8"}, {file = "aiohttp-3.9.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0605cc2c0088fcaae79f01c913a38611ad09ba68ff482402d3410bf59039bfb8"}, {file = "aiohttp-3.9.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4558e5012ee03d2638c681e156461d37b7a113fe13970d438d95d10173d25f78"}, {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dbc053ac75ccc63dc3a3cc547b98c7258ec35a215a92bd9f983e0aac95d3d5b"}, {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4109adee842b90671f1b689901b948f347325045c15f46b39797ae1bf17019de"}, {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6ea1a5b409a85477fd8e5ee6ad8f0e40bf2844c270955e09360418cfd09abac"}, {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3c2890ca8c59ee683fd09adf32321a40fe1cf164e3387799efb2acebf090c11"}, {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3916c8692dbd9d55c523374a3b8213e628424d19116ac4308e434dbf6d95bbdd"}, {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8d1964eb7617907c792ca00b341b5ec3e01ae8c280825deadbbd678447b127e1"}, {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d5ab8e1f6bee051a4bf6195e38a5c13e5e161cb7bad83d8854524798bd9fcd6e"}, {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:52c27110f3862a1afbcb2af4281fc9fdc40327fa286c4625dfee247c3ba90156"}, {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:7f64cbd44443e80094309875d4f9c71d0401e966d191c3d469cde4642bc2e031"}, {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8b4f72fbb66279624bfe83fd5eb6aea0022dad8eec62b71e7bf63ee1caadeafe"}, {file = "aiohttp-3.9.5-cp38-cp38-win32.whl", hash = "sha256:6380c039ec52866c06d69b5c7aad5478b24ed11696f0e72f6b807cfb261453da"}, {file = "aiohttp-3.9.5-cp38-cp38-win_amd64.whl", hash = "sha256:da22dab31d7180f8c3ac7c7635f3bcd53808f374f6aa333fe0b0b9e14b01f91a"}, {file = "aiohttp-3.9.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:1732102949ff6087589408d76cd6dea656b93c896b011ecafff418c9661dc4ed"}, {file = "aiohttp-3.9.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c6021d296318cb6f9414b48e6a439a7f5d1f665464da507e8ff640848ee2a58a"}, {file = "aiohttp-3.9.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:239f975589a944eeb1bad26b8b140a59a3a320067fb3cd10b75c3092405a1372"}, {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b7b30258348082826d274504fbc7c849959f1989d86c29bc355107accec6cfb"}, {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd2adf5c87ff6d8b277814a28a535b59e20bfea40a101db6b3bdca7e9926bc24"}, {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e9a3d838441bebcf5cf442700e3963f58b5c33f015341f9ea86dcd7d503c07e2"}, {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e3a1ae66e3d0c17cf65c08968a5ee3180c5a95920ec2731f53343fac9bad106"}, {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9c69e77370cce2d6df5d12b4e12bdcca60c47ba13d1cbbc8645dd005a20b738b"}, {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0cbf56238f4bbf49dab8c2dc2e6b1b68502b1e88d335bea59b3f5b9f4c001475"}, {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d1469f228cd9ffddd396d9948b8c9cd8022b6d1bf1e40c6f25b0fb90b4f893ed"}, {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:45731330e754f5811c314901cebdf19dd776a44b31927fa4b4dbecab9e457b0c"}, {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:3fcb4046d2904378e3aeea1df51f697b0467f2aac55d232c87ba162709478c46"}, {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8cf142aa6c1a751fcb364158fd710b8a9be874b81889c2bd13aa8893197455e2"}, {file = "aiohttp-3.9.5-cp39-cp39-win32.whl", hash = "sha256:7b179eea70833c8dee51ec42f3b4097bd6370892fa93f510f76762105568cf09"}, {file = "aiohttp-3.9.5-cp39-cp39-win_amd64.whl", hash = "sha256:38d80498e2e169bc61418ff36170e0aad0cd268da8b38a17c4cf29d254a8b3f1"}, {file = "aiohttp-3.9.5.tar.gz", hash = "sha256:edea7d15772ceeb29db4aff55e482d4bcfb6ae160ce144f2682de02f6d693551"}, ] [package.dependencies] aiosignal = ">=1.1.2" async-timeout = {version = ">=4.0,<5.0", markers = "python_version < \"3.11\""} attrs = ">=17.3.0" frozenlist = ">=1.1.1" multidict = ">=4.5,<7.0" yarl = ">=1.0,<2.0" [package.extras] speedups = ["Brotli", "aiodns", "brotlicffi"] [[package]] name = "aiosignal" version = "1.3.1" description = "aiosignal: a list of registered asynchronous callbacks" optional = false python-versions = ">=3.7" files = [ {file = "aiosignal-1.3.1-py3-none-any.whl", hash = "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17"}, {file = "aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc"}, ] [package.dependencies] frozenlist = ">=1.1.0" [[package]] name = "async-timeout" version = "4.0.3" description = "Timeout context manager for asyncio programs" optional = false python-versions = ">=3.7" files = [ {file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"}, {file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"}, ] [[package]] name = "attrs" version = "23.2.0" description = "Classes Without Boilerplate" optional = false python-versions = ">=3.7" files = [ {file = "attrs-23.2.0-py3-none-any.whl", hash = "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1"}, {file = "attrs-23.2.0.tar.gz", hash = "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30"}, ] [package.extras] cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] dev = ["attrs[tests]", "pre-commit"] docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] tests = ["attrs[tests-no-zope]", "zope-interface"] tests-mypy = ["mypy (>=1.6)", "pytest-mypy-plugins"] tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist[psutil]"] [[package]] name = "behave" version = "1.2.6" description = "behave is behaviour-driven development, Python style" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" files = [ {file = "behave-1.2.6-py2.py3-none-any.whl", hash = "sha256:ebda1a6c9e5bfe95c5f9f0a2794e01c7098b3dde86c10a95d8621c5907ff6f1c"}, {file = "behave-1.2.6.tar.gz", hash = "sha256:b9662327aa53294c1351b0a9c369093ccec1d21026f050c3bd9b3e5cccf81a86"}, ] [package.dependencies] parse = ">=1.8.2" parse-type = ">=0.4.2" six = ">=1.11" [package.extras] develop = ["coverage", "invoke (>=0.21.0)", "modernize (>=0.5)", "path.py (>=8.1.2)", "pathlib", "pycmd", "pylint", "pytest (>=3.0)", "pytest-cov", "tox"] docs = ["sphinx (>=1.6)", "sphinx-bootstrap-theme (>=0.6)"] [[package]] name = "busypie" version = "0.5.1" description = "Easy and expressive busy-waiting for Python" optional = false python-versions = ">=3.6" files = [ {file = "busypie-0.5.1-py3-none-any.whl", hash = "sha256:0f7d6a3e549357a8d59087985919af11276cd11a6be162dadd7f8d60a4718a86"}, {file = "busypie-0.5.1.tar.gz", hash = "sha256:5a6ec2a60973a9708a2b6aada5d9904ab73636f2432f19db2940c811af559d02"}, ] [[package]] name = "cbor2" version = "5.6.4" description = "CBOR (de)serializer with extensive tag support" optional = false python-versions = ">=3.8" files = [ {file = "cbor2-5.6.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c40c68779a363f47a11ded7b189ba16767391d5eae27fac289e7f62b730ae1fc"}, {file = "cbor2-5.6.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c0625c8d3c487e509458459de99bf052f62eb5d773cc9fc141c6a6ea9367726d"}, {file = "cbor2-5.6.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:de7137622204168c3a57882f15dd09b5135bda2bcb1cf8b56b58d26b5150dfca"}, {file = "cbor2-5.6.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3545e1e62ec48944b81da2c0e0a736ca98b9e4653c2365cae2f10ae871e9113"}, {file = "cbor2-5.6.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d6749913cd00a24eba17406a0bfc872044036c30a37eb2fcde7acfd975317e8a"}, {file = "cbor2-5.6.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:57db966ab08443ee54b6f154f72021a41bfecd4ba897fe108728183ad8784a2a"}, {file = "cbor2-5.6.4-cp310-cp310-win_amd64.whl", hash = "sha256:380e0c7f4db574dcd86e6eee1b0041863b0aae7efd449d49b0b784cf9a481b9b"}, {file = "cbor2-5.6.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5c763d50a1714e0356b90ad39194fc8ef319356b89fb001667a2e836bfde88e3"}, {file = "cbor2-5.6.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:58a7ac8861857a9f9b0de320a4808a2a5f68a2599b4c14863e2748d5a4686c99"}, {file = "cbor2-5.6.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7d715b2f101730335e84a25fe0893e2b6adf049d6d44da123bf243b8c875ffd8"}, {file = "cbor2-5.6.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f53a67600038cb9668720b309fdfafa8c16d1a02570b96d2144d58d66774318"}, {file = "cbor2-5.6.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f898bab20c4f42dca3688c673ff97c2f719b1811090430173c94452603fbcf13"}, {file = "cbor2-5.6.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5e5d50fb9f47d295c1b7f55592111350424283aff4cc88766c656aad0300f11f"}, {file = "cbor2-5.6.4-cp311-cp311-win_amd64.whl", hash = "sha256:7f9d867dcd814ab8383ad132eb4063e2b69f6a9f688797b7a8ca34a4eadb3944"}, {file = "cbor2-5.6.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e0860ca88edf8aaec5461ce0e498eb5318f1bcc70d93f90091b7a1f1d351a167"}, {file = "cbor2-5.6.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c38a0ed495a63a8bef6400158746a9cb03c36f89aeed699be7ffebf82720bf86"}, {file = "cbor2-5.6.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c8d8c2f208c223a61bed48dfd0661694b891e423094ed30bac2ed75032142aa"}, {file = "cbor2-5.6.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24cd2ce6136e1985da989e5ba572521023a320dcefad5d1fff57fba261de80ca"}, {file = "cbor2-5.6.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7facce04aed2bf69ef43bdffb725446fe243594c2451921e89cc305bede16f02"}, {file = "cbor2-5.6.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f9c8ee0d89411e5e039a4f3419befe8b43c0dd8746eedc979e73f4c06fe0ef97"}, {file = "cbor2-5.6.4-cp312-cp312-win_amd64.whl", hash = "sha256:9b45d554daa540e2f29f1747df9f08f8d98ade65a67b1911791bc193d33a5923"}, {file = "cbor2-5.6.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0a5cb2c16687ccd76b38cfbfdb34468ab7d5635fb92c9dc5e07831c1816bd0a9"}, {file = "cbor2-5.6.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6f985f531f7495527153c4f66c8c143e4cf8a658ec9e87b14bc5438e0a8d0911"}, {file = "cbor2-5.6.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9d9c7b4bd7c3ea7e5587d4f1bbe073b81719530ddadb999b184074f064896e2"}, {file = "cbor2-5.6.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64d06184dcdc275c389fee3cd0ea80b5e1769763df15f93ecd0bf4c281817365"}, {file = "cbor2-5.6.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e9ba7116f201860fb4c3e80ef36be63851ec7e4a18af70fea22d09cab0b000bf"}, {file = "cbor2-5.6.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:341468ae58bdedaa05c907ab16e90dd0d5c54d7d1e66698dfacdbc16a31e815b"}, {file = "cbor2-5.6.4-cp38-cp38-win_amd64.whl", hash = "sha256:bcb4994be1afcc81f9167c220645d878b608cae92e19f6706e770f9bc7bbff6c"}, {file = "cbor2-5.6.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:41c43abffe217dce70ae51c7086530687670a0995dfc90cc35f32f2cf4d86392"}, {file = "cbor2-5.6.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:227a7e68ba378fe53741ed892b5b03fe472b5bd23ef26230a71964accebf50a2"}, {file = "cbor2-5.6.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13521b7c9a0551fcc812d36afd03fc554fa4e1b193659bb5d4d521889aa81154"}, {file = "cbor2-5.6.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f4816d290535d20c7b7e2663b76da5b0deb4237b90275c202c26343d8852b8a"}, {file = "cbor2-5.6.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1e98d370106821335efcc8fbe4136ea26b4747bf29ca0e66512b6c4f6f5cc59f"}, {file = "cbor2-5.6.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:68743a18e16167ff37654a29321f64f0441801dba68359c82dc48173cc6c87e1"}, {file = "cbor2-5.6.4-cp39-cp39-win_amd64.whl", hash = "sha256:7ba5e9c6ed17526d266a1116c045c0941f710860c5f2495758df2e0d848c1b6d"}, {file = "cbor2-5.6.4-py3-none-any.whl", hash = "sha256:fe411c4bf464f5976605103ebcd0f60b893ac3e4c7c8d8bc8f4a0cb456e33c60"}, {file = "cbor2-5.6.4.tar.gz", hash = "sha256:1c533c50dde86bef1c6950602054a0ffa3c376e8b0e20c7b8f5b108793f6983e"}, ] [package.extras] benchmarks = ["pytest-benchmark (==4.0.0)"] doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme (>=1.3.0)", "typing-extensions"] test = ["coverage (>=7)", "hypothesis", "pytest"] [[package]] name = "certifi" version = "2024.7.4" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ {file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"}, {file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"}, ] [[package]] name = "charset-normalizer" version = "3.3.2" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7.0" files = [ {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, ] [[package]] name = "colorama" version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] [[package]] name = "coverage" version = "7.6.0" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.8" files = [ {file = "coverage-7.6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dff044f661f59dace805eedb4a7404c573b6ff0cdba4a524141bc63d7be5c7fd"}, {file = "coverage-7.6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a8659fd33ee9e6ca03950cfdcdf271d645cf681609153f218826dd9805ab585c"}, {file = "coverage-7.6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7792f0ab20df8071d669d929c75c97fecfa6bcab82c10ee4adb91c7a54055463"}, {file = "coverage-7.6.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d4b3cd1ca7cd73d229487fa5caca9e4bc1f0bca96526b922d61053ea751fe791"}, {file = "coverage-7.6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7e128f85c0b419907d1f38e616c4f1e9f1d1b37a7949f44df9a73d5da5cd53c"}, {file = "coverage-7.6.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a94925102c89247530ae1dab7dc02c690942566f22e189cbd53579b0693c0783"}, {file = "coverage-7.6.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:dcd070b5b585b50e6617e8972f3fbbee786afca71b1936ac06257f7e178f00f6"}, {file = "coverage-7.6.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d50a252b23b9b4dfeefc1f663c568a221092cbaded20a05a11665d0dbec9b8fb"}, {file = "coverage-7.6.0-cp310-cp310-win32.whl", hash = "sha256:0e7b27d04131c46e6894f23a4ae186a6a2207209a05df5b6ad4caee6d54a222c"}, {file = "coverage-7.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:54dece71673b3187c86226c3ca793c5f891f9fc3d8aa183f2e3653da18566169"}, {file = "coverage-7.6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c7b525ab52ce18c57ae232ba6f7010297a87ced82a2383b1afd238849c1ff933"}, {file = "coverage-7.6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bea27c4269234e06f621f3fac3925f56ff34bc14521484b8f66a580aacc2e7d"}, {file = "coverage-7.6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed8d1d1821ba5fc88d4a4f45387b65de52382fa3ef1f0115a4f7a20cdfab0e94"}, {file = "coverage-7.6.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01c322ef2bbe15057bc4bf132b525b7e3f7206f071799eb8aa6ad1940bcf5fb1"}, {file = "coverage-7.6.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03cafe82c1b32b770a29fd6de923625ccac3185a54a5e66606da26d105f37dac"}, {file = "coverage-7.6.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0d1b923fc4a40c5832be4f35a5dab0e5ff89cddf83bb4174499e02ea089daf57"}, {file = "coverage-7.6.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4b03741e70fb811d1a9a1d75355cf391f274ed85847f4b78e35459899f57af4d"}, {file = "coverage-7.6.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a73d18625f6a8a1cbb11eadc1d03929f9510f4131879288e3f7922097a429f63"}, {file = "coverage-7.6.0-cp311-cp311-win32.whl", hash = "sha256:65fa405b837060db569a61ec368b74688f429b32fa47a8929a7a2f9b47183713"}, {file = "coverage-7.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:6379688fb4cfa921ae349c76eb1a9ab26b65f32b03d46bb0eed841fd4cb6afb1"}, {file = "coverage-7.6.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f7db0b6ae1f96ae41afe626095149ecd1b212b424626175a6633c2999eaad45b"}, {file = "coverage-7.6.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bbdf9a72403110a3bdae77948b8011f644571311c2fb35ee15f0f10a8fc082e8"}, {file = "coverage-7.6.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cc44bf0315268e253bf563f3560e6c004efe38f76db03a1558274a6e04bf5d5"}, {file = "coverage-7.6.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:da8549d17489cd52f85a9829d0e1d91059359b3c54a26f28bec2c5d369524807"}, {file = "coverage-7.6.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0086cd4fc71b7d485ac93ca4239c8f75732c2ae3ba83f6be1c9be59d9e2c6382"}, {file = "coverage-7.6.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1fad32ee9b27350687035cb5fdf9145bc9cf0a094a9577d43e909948ebcfa27b"}, {file = "coverage-7.6.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:044a0985a4f25b335882b0966625270a8d9db3d3409ddc49a4eb00b0ef5e8cee"}, {file = "coverage-7.6.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:76d5f82213aa78098b9b964ea89de4617e70e0d43e97900c2778a50856dac605"}, {file = "coverage-7.6.0-cp312-cp312-win32.whl", hash = "sha256:3c59105f8d58ce500f348c5b56163a4113a440dad6daa2294b5052a10db866da"}, {file = "coverage-7.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:ca5d79cfdae420a1d52bf177de4bc2289c321d6c961ae321503b2ca59c17ae67"}, {file = "coverage-7.6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d39bd10f0ae453554798b125d2f39884290c480f56e8a02ba7a6ed552005243b"}, {file = "coverage-7.6.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:beb08e8508e53a568811016e59f3234d29c2583f6b6e28572f0954a6b4f7e03d"}, {file = "coverage-7.6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2e16f4cd2bc4d88ba30ca2d3bbf2f21f00f382cf4e1ce3b1ddc96c634bc48ca"}, {file = "coverage-7.6.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6616d1c9bf1e3faea78711ee42a8b972367d82ceae233ec0ac61cc7fec09fa6b"}, {file = "coverage-7.6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad4567d6c334c46046d1c4c20024de2a1c3abc626817ae21ae3da600f5779b44"}, {file = "coverage-7.6.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d17c6a415d68cfe1091d3296ba5749d3d8696e42c37fca5d4860c5bf7b729f03"}, {file = "coverage-7.6.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:9146579352d7b5f6412735d0f203bbd8d00113a680b66565e205bc605ef81bc6"}, {file = "coverage-7.6.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:cdab02a0a941af190df8782aafc591ef3ad08824f97850b015c8c6a8b3877b0b"}, {file = "coverage-7.6.0-cp38-cp38-win32.whl", hash = "sha256:df423f351b162a702c053d5dddc0fc0ef9a9e27ea3f449781ace5f906b664428"}, {file = "coverage-7.6.0-cp38-cp38-win_amd64.whl", hash = "sha256:f2501d60d7497fd55e391f423f965bbe9e650e9ffc3c627d5f0ac516026000b8"}, {file = "coverage-7.6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7221f9ac9dad9492cecab6f676b3eaf9185141539d5c9689d13fd6b0d7de840c"}, {file = "coverage-7.6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ddaaa91bfc4477d2871442bbf30a125e8fe6b05da8a0015507bfbf4718228ab2"}, {file = "coverage-7.6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4cbe651f3904e28f3a55d6f371203049034b4ddbce65a54527a3f189ca3b390"}, {file = "coverage-7.6.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:831b476d79408ab6ccfadaaf199906c833f02fdb32c9ab907b1d4aa0713cfa3b"}, {file = "coverage-7.6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46c3d091059ad0b9c59d1034de74a7f36dcfa7f6d3bde782c49deb42438f2450"}, {file = "coverage-7.6.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:4d5fae0a22dc86259dee66f2cc6c1d3e490c4a1214d7daa2a93d07491c5c04b6"}, {file = "coverage-7.6.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:07ed352205574aad067482e53dd606926afebcb5590653121063fbf4e2175166"}, {file = "coverage-7.6.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:49c76cdfa13015c4560702574bad67f0e15ca5a2872c6a125f6327ead2b731dd"}, {file = "coverage-7.6.0-cp39-cp39-win32.whl", hash = "sha256:482855914928c8175735a2a59c8dc5806cf7d8f032e4820d52e845d1f731dca2"}, {file = "coverage-7.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:543ef9179bc55edfd895154a51792b01c017c87af0ebaae092720152e19e42ca"}, {file = "coverage-7.6.0-pp38.pp39.pp310-none-any.whl", hash = "sha256:6fe885135c8a479d3e37a7aae61cbd3a0fb2deccb4dda3c25f92a49189f766d6"}, {file = "coverage-7.6.0.tar.gz", hash = "sha256:289cc803fa1dc901f84701ac10c9ee873619320f2f9aff38794db4a4a0268d51"}, ] [package.dependencies] tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} [package.extras] toml = ["tomli"] [[package]] name = "exceptiongroup" version = "1.2.2" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" files = [ {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, ] [package.extras] test = ["pytest (>=6)"] [[package]] name = "frozenlist" version = "1.4.1" description = "A list-like structure which implements collections.abc.MutableSequence" optional = false python-versions = ">=3.8" files = [ {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f9aa1878d1083b276b0196f2dfbe00c9b7e752475ed3b682025ff20c1c1f51ac"}, {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:29acab3f66f0f24674b7dc4736477bcd4bc3ad4b896f5f45379a67bce8b96868"}, {file = "frozenlist-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:74fb4bee6880b529a0c6560885fce4dc95936920f9f20f53d99a213f7bf66776"}, {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:590344787a90ae57d62511dd7c736ed56b428f04cd8c161fcc5e7232c130c69a"}, {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:068b63f23b17df8569b7fdca5517edef76171cf3897eb68beb01341131fbd2ad"}, {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c849d495bf5154cd8da18a9eb15db127d4dba2968d88831aff6f0331ea9bd4c"}, {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9750cc7fe1ae3b1611bb8cfc3f9ec11d532244235d75901fb6b8e42ce9229dfe"}, {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9b2de4cf0cdd5bd2dee4c4f63a653c61d2408055ab77b151c1957f221cabf2a"}, {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0633c8d5337cb5c77acbccc6357ac49a1770b8c487e5b3505c57b949b4b82e98"}, {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:27657df69e8801be6c3638054e202a135c7f299267f1a55ed3a598934f6c0d75"}, {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:f9a3ea26252bd92f570600098783d1371354d89d5f6b7dfd87359d669f2109b5"}, {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:4f57dab5fe3407b6c0c1cc907ac98e8a189f9e418f3b6e54d65a718aaafe3950"}, {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e02a0e11cf6597299b9f3bbd3f93d79217cb90cfd1411aec33848b13f5c656cc"}, {file = "frozenlist-1.4.1-cp310-cp310-win32.whl", hash = "sha256:a828c57f00f729620a442881cc60e57cfcec6842ba38e1b19fd3e47ac0ff8dc1"}, {file = "frozenlist-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:f56e2333dda1fe0f909e7cc59f021eba0d2307bc6f012a1ccf2beca6ba362439"}, {file = "frozenlist-1.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a0cb6f11204443f27a1628b0e460f37fb30f624be6051d490fa7d7e26d4af3d0"}, {file = "frozenlist-1.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b46c8ae3a8f1f41a0d2ef350c0b6e65822d80772fe46b653ab6b6274f61d4a49"}, {file = "frozenlist-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fde5bd59ab5357e3853313127f4d3565fc7dad314a74d7b5d43c22c6a5ed2ced"}, {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:722e1124aec435320ae01ee3ac7bec11a5d47f25d0ed6328f2273d287bc3abb0"}, {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2471c201b70d58a0f0c1f91261542a03d9a5e088ed3dc6c160d614c01649c106"}, {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c757a9dd70d72b076d6f68efdbb9bc943665ae954dad2801b874c8c69e185068"}, {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f146e0911cb2f1da549fc58fc7bcd2b836a44b79ef871980d605ec392ff6b0d2"}, {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f9c515e7914626b2a2e1e311794b4c35720a0be87af52b79ff8e1429fc25f19"}, {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c302220494f5c1ebeb0912ea782bcd5e2f8308037b3c7553fad0e48ebad6ad82"}, {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:442acde1e068288a4ba7acfe05f5f343e19fac87bfc96d89eb886b0363e977ec"}, {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:1b280e6507ea8a4fa0c0a7150b4e526a8d113989e28eaaef946cc77ffd7efc0a"}, {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:fe1a06da377e3a1062ae5fe0926e12b84eceb8a50b350ddca72dc85015873f74"}, {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:db9e724bebd621d9beca794f2a4ff1d26eed5965b004a97f1f1685a173b869c2"}, {file = "frozenlist-1.4.1-cp311-cp311-win32.whl", hash = "sha256:e774d53b1a477a67838a904131c4b0eef6b3d8a651f8b138b04f748fccfefe17"}, {file = "frozenlist-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:fb3c2db03683b5767dedb5769b8a40ebb47d6f7f45b1b3e3b4b51ec8ad9d9825"}, {file = "frozenlist-1.4.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1979bc0aeb89b33b588c51c54ab0161791149f2461ea7c7c946d95d5f93b56ae"}, {file = "frozenlist-1.4.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cc7b01b3754ea68a62bd77ce6020afaffb44a590c2289089289363472d13aedb"}, {file = "frozenlist-1.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c9c92be9fd329ac801cc420e08452b70e7aeab94ea4233a4804f0915c14eba9b"}, {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c3894db91f5a489fc8fa6a9991820f368f0b3cbdb9cd8849547ccfab3392d86"}, {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ba60bb19387e13597fb059f32cd4d59445d7b18b69a745b8f8e5db0346f33480"}, {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8aefbba5f69d42246543407ed2461db31006b0f76c4e32dfd6f42215a2c41d09"}, {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:780d3a35680ced9ce682fbcf4cb9c2bad3136eeff760ab33707b71db84664e3a"}, {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9acbb16f06fe7f52f441bb6f413ebae6c37baa6ef9edd49cdd567216da8600cd"}, {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:23b701e65c7b36e4bf15546a89279bd4d8675faabc287d06bbcfac7d3c33e1e6"}, {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:3e0153a805a98f5ada7e09826255ba99fb4f7524bb81bf6b47fb702666484ae1"}, {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:dd9b1baec094d91bf36ec729445f7769d0d0cf6b64d04d86e45baf89e2b9059b"}, {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:1a4471094e146b6790f61b98616ab8e44f72661879cc63fa1049d13ef711e71e"}, {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5667ed53d68d91920defdf4035d1cdaa3c3121dc0b113255124bcfada1cfa1b8"}, {file = "frozenlist-1.4.1-cp312-cp312-win32.whl", hash = "sha256:beee944ae828747fd7cb216a70f120767fc9f4f00bacae8543c14a6831673f89"}, {file = "frozenlist-1.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:64536573d0a2cb6e625cf309984e2d873979709f2cf22839bf2d61790b448ad5"}, {file = "frozenlist-1.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:20b51fa3f588ff2fe658663db52a41a4f7aa6c04f6201449c6c7c476bd255c0d"}, {file = "frozenlist-1.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:410478a0c562d1a5bcc2f7ea448359fcb050ed48b3c6f6f4f18c313a9bdb1826"}, {file = "frozenlist-1.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c6321c9efe29975232da3bd0af0ad216800a47e93d763ce64f291917a381b8eb"}, {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48f6a4533887e189dae092f1cf981f2e3885175f7a0f33c91fb5b7b682b6bab6"}, {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6eb73fa5426ea69ee0e012fb59cdc76a15b1283d6e32e4f8dc4482ec67d1194d"}, {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fbeb989b5cc29e8daf7f976b421c220f1b8c731cbf22b9130d8815418ea45887"}, {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32453c1de775c889eb4e22f1197fe3bdfe457d16476ea407472b9442e6295f7a"}, {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:693945278a31f2086d9bf3df0fe8254bbeaef1fe71e1351c3bd730aa7d31c41b"}, {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:1d0ce09d36d53bbbe566fe296965b23b961764c0bcf3ce2fa45f463745c04701"}, {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:3a670dc61eb0d0eb7080890c13de3066790f9049b47b0de04007090807c776b0"}, {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:dca69045298ce5c11fd539682cff879cc1e664c245d1c64da929813e54241d11"}, {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a06339f38e9ed3a64e4c4e43aec7f59084033647f908e4259d279a52d3757d09"}, {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b7f2f9f912dca3934c1baec2e4585a674ef16fe00218d833856408c48d5beee7"}, {file = "frozenlist-1.4.1-cp38-cp38-win32.whl", hash = "sha256:e7004be74cbb7d9f34553a5ce5fb08be14fb33bc86f332fb71cbe5216362a497"}, {file = "frozenlist-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:5a7d70357e7cee13f470c7883a063aae5fe209a493c57d86eb7f5a6f910fae09"}, {file = "frozenlist-1.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bfa4a17e17ce9abf47a74ae02f32d014c5e9404b6d9ac7f729e01562bbee601e"}, {file = "frozenlist-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b7e3ed87d4138356775346e6845cccbe66cd9e207f3cd11d2f0b9fd13681359d"}, {file = "frozenlist-1.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c99169d4ff810155ca50b4da3b075cbde79752443117d89429595c2e8e37fed8"}, {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edb678da49d9f72c9f6c609fbe41a5dfb9a9282f9e6a2253d5a91e0fc382d7c0"}, {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6db4667b187a6742b33afbbaf05a7bc551ffcf1ced0000a571aedbb4aa42fc7b"}, {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55fdc093b5a3cb41d420884cdaf37a1e74c3c37a31f46e66286d9145d2063bd0"}, {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82e8211d69a4f4bc360ea22cd6555f8e61a1bd211d1d5d39d3d228b48c83a897"}, {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89aa2c2eeb20957be2d950b85974b30a01a762f3308cd02bb15e1ad632e22dc7"}, {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9d3e0c25a2350080e9319724dede4f31f43a6c9779be48021a7f4ebde8b2d742"}, {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7268252af60904bf52c26173cbadc3a071cece75f873705419c8681f24d3edea"}, {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:0c250a29735d4f15321007fb02865f0e6b6a41a6b88f1f523ca1596ab5f50bd5"}, {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:96ec70beabbd3b10e8bfe52616a13561e58fe84c0101dd031dc78f250d5128b9"}, {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:23b2d7679b73fe0e5a4560b672a39f98dfc6f60df63823b0a9970525325b95f6"}, {file = "frozenlist-1.4.1-cp39-cp39-win32.whl", hash = "sha256:a7496bfe1da7fb1a4e1cc23bb67c58fab69311cc7d32b5a99c2007b4b2a0e932"}, {file = "frozenlist-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:e6a20a581f9ce92d389a8c7d7c3dd47c81fd5d6e655c8dddf341e14aa48659d0"}, {file = "frozenlist-1.4.1-py3-none-any.whl", hash = "sha256:04ced3e6a46b4cfffe20f9ae482818e34eba9b5fb0ce4056e4cc9b6e212d09b7"}, {file = "frozenlist-1.4.1.tar.gz", hash = "sha256:c037a86e8513059a2613aaba4d817bb90b9d9b6b69aace3ce9c877e8c8ed402b"}, ] [[package]] name = "idna" version = "3.7" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.5" files = [ {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, ] [[package]] name = "iniconfig" version = "2.0.0" description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.7" files = [ {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] [[package]] name = "multidict" version = "6.0.5" description = "multidict implementation" optional = false python-versions = ">=3.7" files = [ {file = "multidict-6.0.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:228b644ae063c10e7f324ab1ab6b548bdf6f8b47f3ec234fef1093bc2735e5f9"}, {file = "multidict-6.0.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:896ebdcf62683551312c30e20614305f53125750803b614e9e6ce74a96232604"}, {file = "multidict-6.0.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:411bf8515f3be9813d06004cac41ccf7d1cd46dfe233705933dd163b60e37600"}, {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d147090048129ce3c453f0292e7697d333db95e52616b3793922945804a433c"}, {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:215ed703caf15f578dca76ee6f6b21b7603791ae090fbf1ef9d865571039ade5"}, {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c6390cf87ff6234643428991b7359b5f59cc15155695deb4eda5c777d2b880f"}, {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21fd81c4ebdb4f214161be351eb5bcf385426bf023041da2fd9e60681f3cebae"}, {file = "multidict-6.0.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3cc2ad10255f903656017363cd59436f2111443a76f996584d1077e43ee51182"}, {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6939c95381e003f54cd4c5516740faba40cf5ad3eeff460c3ad1d3e0ea2549bf"}, {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:220dd781e3f7af2c2c1053da9fa96d9cf3072ca58f057f4c5adaaa1cab8fc442"}, {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:766c8f7511df26d9f11cd3a8be623e59cca73d44643abab3f8c8c07620524e4a"}, {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:fe5d7785250541f7f5019ab9cba2c71169dc7d74d0f45253f8313f436458a4ef"}, {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c1c1496e73051918fcd4f58ff2e0f2f3066d1c76a0c6aeffd9b45d53243702cc"}, {file = "multidict-6.0.5-cp310-cp310-win32.whl", hash = "sha256:7afcdd1fc07befad18ec4523a782cde4e93e0a2bf71239894b8d61ee578c1319"}, {file = "multidict-6.0.5-cp310-cp310-win_amd64.whl", hash = "sha256:99f60d34c048c5c2fabc766108c103612344c46e35d4ed9ae0673d33c8fb26e8"}, {file = "multidict-6.0.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f285e862d2f153a70586579c15c44656f888806ed0e5b56b64489afe4a2dbfba"}, {file = "multidict-6.0.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:53689bb4e102200a4fafa9de9c7c3c212ab40a7ab2c8e474491914d2305f187e"}, {file = "multidict-6.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:612d1156111ae11d14afaf3a0669ebf6c170dbb735e510a7438ffe2369a847fd"}, {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7be7047bd08accdb7487737631d25735c9a04327911de89ff1b26b81745bd4e3"}, {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de170c7b4fe6859beb8926e84f7d7d6c693dfe8e27372ce3b76f01c46e489fcf"}, {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04bde7a7b3de05732a4eb39c94574db1ec99abb56162d6c520ad26f83267de29"}, {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85f67aed7bb647f93e7520633d8f51d3cbc6ab96957c71272b286b2f30dc70ed"}, {file = "multidict-6.0.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:425bf820055005bfc8aa9a0b99ccb52cc2f4070153e34b701acc98d201693733"}, {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d3eb1ceec286eba8220c26f3b0096cf189aea7057b6e7b7a2e60ed36b373b77f"}, {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7901c05ead4b3fb75113fb1dd33eb1253c6d3ee37ce93305acd9d38e0b5f21a4"}, {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:e0e79d91e71b9867c73323a3444724d496c037e578a0e1755ae159ba14f4f3d1"}, {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:29bfeb0dff5cb5fdab2023a7a9947b3b4af63e9c47cae2a10ad58394b517fddc"}, {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e030047e85cbcedbfc073f71836d62dd5dadfbe7531cae27789ff66bc551bd5e"}, {file = "multidict-6.0.5-cp311-cp311-win32.whl", hash = "sha256:2f4848aa3baa109e6ab81fe2006c77ed4d3cd1e0ac2c1fbddb7b1277c168788c"}, {file = "multidict-6.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:2faa5ae9376faba05f630d7e5e6be05be22913782b927b19d12b8145968a85ea"}, {file = "multidict-6.0.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:51d035609b86722963404f711db441cf7134f1889107fb171a970c9701f92e1e"}, {file = "multidict-6.0.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cbebcd5bcaf1eaf302617c114aa67569dd3f090dd0ce8ba9e35e9985b41ac35b"}, {file = "multidict-6.0.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2ffc42c922dbfddb4a4c3b438eb056828719f07608af27d163191cb3e3aa6cc5"}, {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ceb3b7e6a0135e092de86110c5a74e46bda4bd4fbfeeb3a3bcec79c0f861e450"}, {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:79660376075cfd4b2c80f295528aa6beb2058fd289f4c9252f986751a4cd0496"}, {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e4428b29611e989719874670fd152b6625500ad6c686d464e99f5aaeeaca175a"}, {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d84a5c3a5f7ce6db1f999fb9438f686bc2e09d38143f2d93d8406ed2dd6b9226"}, {file = "multidict-6.0.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:76c0de87358b192de7ea9649beb392f107dcad9ad27276324c24c91774ca5271"}, {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:79a6d2ba910adb2cbafc95dad936f8b9386e77c84c35bc0add315b856d7c3abb"}, {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:92d16a3e275e38293623ebf639c471d3e03bb20b8ebb845237e0d3664914caef"}, {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:fb616be3538599e797a2017cccca78e354c767165e8858ab5116813146041a24"}, {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:14c2976aa9038c2629efa2c148022ed5eb4cb939e15ec7aace7ca932f48f9ba6"}, {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:435a0984199d81ca178b9ae2c26ec3d49692d20ee29bc4c11a2a8d4514c67eda"}, {file = "multidict-6.0.5-cp312-cp312-win32.whl", hash = "sha256:9fe7b0653ba3d9d65cbe7698cca585bf0f8c83dbbcc710db9c90f478e175f2d5"}, {file = "multidict-6.0.5-cp312-cp312-win_amd64.whl", hash = "sha256:01265f5e40f5a17f8241d52656ed27192be03bfa8764d88e8220141d1e4b3556"}, {file = "multidict-6.0.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:19fe01cea168585ba0f678cad6f58133db2aa14eccaf22f88e4a6dccadfad8b3"}, {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6bf7a982604375a8d49b6cc1b781c1747f243d91b81035a9b43a2126c04766f5"}, {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:107c0cdefe028703fb5dafe640a409cb146d44a6ae201e55b35a4af8e95457dd"}, {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:403c0911cd5d5791605808b942c88a8155c2592e05332d2bf78f18697a5fa15e"}, {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aeaf541ddbad8311a87dd695ed9642401131ea39ad7bc8cf3ef3967fd093b626"}, {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e4972624066095e52b569e02b5ca97dbd7a7ddd4294bf4e7247d52635630dd83"}, {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d946b0a9eb8aaa590df1fe082cee553ceab173e6cb5b03239716338629c50c7a"}, {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b55358304d7a73d7bdf5de62494aaf70bd33015831ffd98bc498b433dfe5b10c"}, {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:a3145cb08d8625b2d3fee1b2d596a8766352979c9bffe5d7833e0503d0f0b5e5"}, {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:d65f25da8e248202bd47445cec78e0025c0fe7582b23ec69c3b27a640dd7a8e3"}, {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c9bf56195c6bbd293340ea82eafd0071cb3d450c703d2c93afb89f93b8386ccc"}, {file = "multidict-6.0.5-cp37-cp37m-win32.whl", hash = "sha256:69db76c09796b313331bb7048229e3bee7928eb62bab5e071e9f7fcc4879caee"}, {file = "multidict-6.0.5-cp37-cp37m-win_amd64.whl", hash = "sha256:fce28b3c8a81b6b36dfac9feb1de115bab619b3c13905b419ec71d03a3fc1423"}, {file = "multidict-6.0.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:76f067f5121dcecf0d63a67f29080b26c43c71a98b10c701b0677e4a065fbd54"}, {file = "multidict-6.0.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b82cc8ace10ab5bd93235dfaab2021c70637005e1ac787031f4d1da63d493c1d"}, {file = "multidict-6.0.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5cb241881eefd96b46f89b1a056187ea8e9ba14ab88ba632e68d7a2ecb7aadf7"}, {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8e94e6912639a02ce173341ff62cc1201232ab86b8a8fcc05572741a5dc7d93"}, {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09a892e4a9fb47331da06948690ae38eaa2426de97b4ccbfafbdcbe5c8f37ff8"}, {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55205d03e8a598cfc688c71ca8ea5f66447164efff8869517f175ea632c7cb7b"}, {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37b15024f864916b4951adb95d3a80c9431299080341ab9544ed148091b53f50"}, {file = "multidict-6.0.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2a1dee728b52b33eebff5072817176c172050d44d67befd681609b4746e1c2e"}, {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:edd08e6f2f1a390bf137080507e44ccc086353c8e98c657e666c017718561b89"}, {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:60d698e8179a42ec85172d12f50b1668254628425a6bd611aba022257cac1386"}, {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:3d25f19500588cbc47dc19081d78131c32637c25804df8414463ec908631e453"}, {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:4cc0ef8b962ac7a5e62b9e826bd0cd5040e7d401bc45a6835910ed699037a461"}, {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:eca2e9d0cc5a889850e9bbd68e98314ada174ff6ccd1129500103df7a94a7a44"}, {file = "multidict-6.0.5-cp38-cp38-win32.whl", hash = "sha256:4a6a4f196f08c58c59e0b8ef8ec441d12aee4125a7d4f4fef000ccb22f8d7241"}, {file = "multidict-6.0.5-cp38-cp38-win_amd64.whl", hash = "sha256:0275e35209c27a3f7951e1ce7aaf93ce0d163b28948444bec61dd7badc6d3f8c"}, {file = "multidict-6.0.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e7be68734bd8c9a513f2b0cfd508802d6609da068f40dc57d4e3494cefc92929"}, {file = "multidict-6.0.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1d9ea7a7e779d7a3561aade7d596649fbecfa5c08a7674b11b423783217933f9"}, {file = "multidict-6.0.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ea1456df2a27c73ce51120fa2f519f1bea2f4a03a917f4a43c8707cf4cbbae1a"}, {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf590b134eb70629e350691ecca88eac3e3b8b3c86992042fb82e3cb1830d5e1"}, {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5c0631926c4f58e9a5ccce555ad7747d9a9f8b10619621f22f9635f069f6233e"}, {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dce1c6912ab9ff5f179eaf6efe7365c1f425ed690b03341911bf4939ef2f3046"}, {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0868d64af83169e4d4152ec612637a543f7a336e4a307b119e98042e852ad9c"}, {file = "multidict-6.0.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:141b43360bfd3bdd75f15ed811850763555a251e38b2405967f8e25fb43f7d40"}, {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7df704ca8cf4a073334e0427ae2345323613e4df18cc224f647f251e5e75a527"}, {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6214c5a5571802c33f80e6c84713b2c79e024995b9c5897f794b43e714daeec9"}, {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:cd6c8fca38178e12c00418de737aef1261576bd1b6e8c6134d3e729a4e858b38"}, {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:e02021f87a5b6932fa6ce916ca004c4d441509d33bbdbeca70d05dff5e9d2479"}, {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ebd8d160f91a764652d3e51ce0d2956b38efe37c9231cd82cfc0bed2e40b581c"}, {file = "multidict-6.0.5-cp39-cp39-win32.whl", hash = "sha256:04da1bb8c8dbadf2a18a452639771951c662c5ad03aefe4884775454be322c9b"}, {file = "multidict-6.0.5-cp39-cp39-win_amd64.whl", hash = "sha256:d6f6d4f185481c9669b9447bf9d9cf3b95a0e9df9d169bbc17e363b7d5487755"}, {file = "multidict-6.0.5-py3-none-any.whl", hash = "sha256:0d63c74e3d7ab26de115c49bffc92cc77ed23395303d496eae515d4204a625e7"}, {file = "multidict-6.0.5.tar.gz", hash = "sha256:f7e301075edaf50500f0b341543c41194d8df3ae5caf4702f2095f3ca73dd8da"}, ] [[package]] name = "packaging" version = "24.1" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" files = [ {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, ] [[package]] name = "parse" version = "1.20.2" description = "parse() is the opposite of format()" optional = false python-versions = "*" files = [ {file = "parse-1.20.2-py2.py3-none-any.whl", hash = "sha256:967095588cb802add9177d0c0b6133b5ba33b1ea9007ca800e526f42a85af558"}, {file = "parse-1.20.2.tar.gz", hash = "sha256:b41d604d16503c79d81af5165155c0b20f6c8d6c559efa66b4b695c3e5a0a0ce"}, ] [[package]] name = "parse-type" version = "0.6.2" description = "Simplifies to build parse types based on the parse module" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*" files = [ {file = "parse_type-0.6.2-py2.py3-none-any.whl", hash = "sha256:06d39a8b70fde873eb2a131141a0e79bb34a432941fb3d66fad247abafc9766c"}, {file = "parse_type-0.6.2.tar.gz", hash = "sha256:79b1f2497060d0928bc46016793f1fca1057c4aacdf15ef876aa48d75a73a355"}, ] [package.dependencies] parse = {version = ">=1.18.0", markers = "python_version >= \"3.0\""} six = ">=1.15" [package.extras] develop = ["build (>=0.5.1)", "coverage (>=4.4)", "pylint", "pytest (<5.0)", "pytest (>=5.0)", "pytest-cov", "pytest-html (>=1.19.0)", "ruff", "tox (>=2.8,<4.0)", "twine (>=1.13.0)", "virtualenv (<20.22.0)", "virtualenv (>=20.0.0)"] docs = ["Sphinx (>=1.6)", "sphinx-bootstrap-theme (>=0.6.0)"] testing = ["pytest (<5.0)", "pytest (>=5.0)", "pytest-html (>=1.19.0)"] [[package]] name = "pluggy" version = "1.5.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" files = [ {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, ] [package.extras] dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] [[package]] name = "pycryptodomex" version = "3.20.0" description = "Cryptographic library for Python" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ {file = "pycryptodomex-3.20.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:645bd4ca6f543685d643dadf6a856cc382b654cc923460e3a10a49c1b3832aeb"}, {file = "pycryptodomex-3.20.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ff5c9a67f8a4fba4aed887216e32cbc48f2a6fb2673bb10a99e43be463e15913"}, {file = "pycryptodomex-3.20.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:8ee606964553c1a0bc74057dd8782a37d1c2bc0f01b83193b6f8bb14523b877b"}, {file = "pycryptodomex-3.20.0-cp27-cp27m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7805830e0c56d88f4d491fa5ac640dfc894c5ec570d1ece6ed1546e9df2e98d6"}, {file = "pycryptodomex-3.20.0-cp27-cp27m-musllinux_1_1_aarch64.whl", hash = "sha256:bc3ee1b4d97081260d92ae813a83de4d2653206967c4a0a017580f8b9548ddbc"}, {file = "pycryptodomex-3.20.0-cp27-cp27m-win32.whl", hash = "sha256:8af1a451ff9e123d0d8bd5d5e60f8e3315c3a64f3cdd6bc853e26090e195cdc8"}, {file = "pycryptodomex-3.20.0-cp27-cp27m-win_amd64.whl", hash = "sha256:cbe71b6712429650e3883dc81286edb94c328ffcd24849accac0a4dbcc76958a"}, {file = "pycryptodomex-3.20.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:76bd15bb65c14900d98835fcd10f59e5e0435077431d3a394b60b15864fddd64"}, {file = "pycryptodomex-3.20.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:653b29b0819605fe0898829c8ad6400a6ccde096146730c2da54eede9b7b8baa"}, {file = "pycryptodomex-3.20.0-cp27-cp27mu-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62a5ec91388984909bb5398ea49ee61b68ecb579123694bffa172c3b0a107079"}, {file = "pycryptodomex-3.20.0-cp27-cp27mu-musllinux_1_1_aarch64.whl", hash = "sha256:108e5f1c1cd70ffce0b68739c75734437c919d2eaec8e85bffc2c8b4d2794305"}, {file = "pycryptodomex-3.20.0-cp35-abi3-macosx_10_9_universal2.whl", hash = "sha256:59af01efb011b0e8b686ba7758d59cf4a8263f9ad35911bfe3f416cee4f5c08c"}, {file = "pycryptodomex-3.20.0-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:82ee7696ed8eb9a82c7037f32ba9b7c59e51dda6f105b39f043b6ef293989cb3"}, {file = "pycryptodomex-3.20.0-cp35-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91852d4480a4537d169c29a9d104dda44094c78f1f5b67bca76c29a91042b623"}, {file = "pycryptodomex-3.20.0-cp35-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bca649483d5ed251d06daf25957f802e44e6bb6df2e8f218ae71968ff8f8edc4"}, {file = "pycryptodomex-3.20.0-cp35-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e186342cfcc3aafaad565cbd496060e5a614b441cacc3995ef0091115c1f6c5"}, {file = "pycryptodomex-3.20.0-cp35-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:25cd61e846aaab76d5791d006497134602a9e451e954833018161befc3b5b9ed"}, {file = "pycryptodomex-3.20.0-cp35-abi3-musllinux_1_1_i686.whl", hash = "sha256:9c682436c359b5ada67e882fec34689726a09c461efd75b6ea77b2403d5665b7"}, {file = "pycryptodomex-3.20.0-cp35-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:7a7a8f33a1f1fb762ede6cc9cbab8f2a9ba13b196bfaf7bc6f0b39d2ba315a43"}, {file = "pycryptodomex-3.20.0-cp35-abi3-win32.whl", hash = "sha256:c39778fd0548d78917b61f03c1fa8bfda6cfcf98c767decf360945fe6f97461e"}, {file = "pycryptodomex-3.20.0-cp35-abi3-win_amd64.whl", hash = "sha256:2a47bcc478741b71273b917232f521fd5704ab4b25d301669879e7273d3586cc"}, {file = "pycryptodomex-3.20.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:1be97461c439a6af4fe1cf8bf6ca5936d3db252737d2f379cc6b2e394e12a458"}, {file = "pycryptodomex-3.20.0-pp27-pypy_73-win32.whl", hash = "sha256:19764605feea0df966445d46533729b645033f134baeb3ea26ad518c9fdf212c"}, {file = "pycryptodomex-3.20.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f2e497413560e03421484189a6b65e33fe800d3bd75590e6d78d4dfdb7accf3b"}, {file = "pycryptodomex-3.20.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e48217c7901edd95f9f097feaa0388da215ed14ce2ece803d3f300b4e694abea"}, {file = "pycryptodomex-3.20.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d00fe8596e1cc46b44bf3907354e9377aa030ec4cd04afbbf6e899fc1e2a7781"}, {file = "pycryptodomex-3.20.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:88afd7a3af7ddddd42c2deda43d53d3dfc016c11327d0915f90ca34ebda91499"}, {file = "pycryptodomex-3.20.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d3584623e68a5064a04748fb6d76117a21a7cb5eaba20608a41c7d0c61721794"}, {file = "pycryptodomex-3.20.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0daad007b685db36d977f9de73f61f8da2a7104e20aca3effd30752fd56f73e1"}, {file = "pycryptodomex-3.20.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5dcac11031a71348faaed1f403a0debd56bf5404232284cf8c761ff918886ebc"}, {file = "pycryptodomex-3.20.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:69138068268127cd605e03438312d8f271135a33140e2742b417d027a0539427"}, {file = "pycryptodomex-3.20.0.tar.gz", hash = "sha256:7a710b79baddd65b806402e14766c721aee8fb83381769c27920f26476276c1e"}, ] [[package]] name = "pytest" version = "8.2.2" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" files = [ {file = "pytest-8.2.2-py3-none-any.whl", hash = "sha256:c434598117762e2bd304e526244f67bf66bbd7b5d6cf22138be51ff661980343"}, {file = "pytest-8.2.2.tar.gz", hash = "sha256:de4bb8104e201939ccdc688b27a89a7be2079b22e2bd2b07f806b6ba71117977"}, ] [package.dependencies] colorama = {version = "*", markers = "sys_platform == \"win32\""} exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} iniconfig = "*" packaging = "*" pluggy = ">=1.5,<2.0" tomli = {version = ">=1", markers = "python_version < \"3.11\""} [package.extras] dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] [[package]] name = "pytest-asyncio" version = "0.23.7" description = "Pytest support for asyncio" optional = false python-versions = ">=3.8" files = [ {file = "pytest_asyncio-0.23.7-py3-none-any.whl", hash = "sha256:009b48127fbe44518a547bddd25611551b0e43ccdbf1e67d12479f569832c20b"}, {file = "pytest_asyncio-0.23.7.tar.gz", hash = "sha256:5f5c72948f4c49e7db4f29f2521d4031f1c27f86e57b046126654083d4770268"}, ] [package.dependencies] pytest = ">=7.0.0,<9" [package.extras] docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"] testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"] [[package]] name = "pytest-cov" version = "5.0.0" description = "Pytest plugin for measuring coverage." optional = false python-versions = ">=3.8" files = [ {file = "pytest-cov-5.0.0.tar.gz", hash = "sha256:5837b58e9f6ebd335b0f8060eecce69b662415b16dc503883a02f45dfeb14857"}, {file = "pytest_cov-5.0.0-py3-none-any.whl", hash = "sha256:4f0764a1219df53214206bf1feea4633c3b558a2925c8b59f144f682861ce652"}, ] [package.dependencies] coverage = {version = ">=5.2.1", extras = ["toml"]} pytest = ">=4.6" [package.extras] testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"] [[package]] name = "pyyaml" version = "6.0.1" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.6" files = [ {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, ] [[package]] name = "requests" version = "2.32.3" description = "Python HTTP for Humans." optional = false python-versions = ">=3.8" files = [ {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, ] [package.dependencies] certifi = ">=2017.4.17" charset-normalizer = ">=2,<4" idna = ">=2.5,<4" urllib3 = ">=1.21.1,<3" [package.extras] socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" files = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] [[package]] name = "tomli" version = "2.0.1" description = "A lil' TOML parser" optional = false python-versions = ">=3.7" files = [ {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] [[package]] name = "urllib3" version = "1.26.19" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" files = [ {file = "urllib3-1.26.19-py2.py3-none-any.whl", hash = "sha256:37a0344459b199fce0e80b0d3569837ec6b6937435c5244e7fd73fa6006830f3"}, {file = "urllib3-1.26.19.tar.gz", hash = "sha256:3e3d753a8618b86d7de333b4223005f68720bcd6a7d2bcb9fbd2229ec7c1e429"}, ] [package.extras] brotli = ["brotli (==1.0.9)", "brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] name = "vcrpy" version = "6.0.1" description = "Automatically mock your HTTP interactions to simplify and speed up testing" optional = false python-versions = ">=3.8" files = [ {file = "vcrpy-6.0.1.tar.gz", hash = "sha256:9e023fee7f892baa0bbda2f7da7c8ac51165c1c6e38ff8688683a12a4bde9278"}, ] [package.dependencies] PyYAML = "*" urllib3 = {version = "<2", markers = "platform_python_implementation == \"PyPy\" or python_version < \"3.10\""} wrapt = "*" yarl = "*" [package.extras] tests = ["Werkzeug (==2.0.3)", "aiohttp", "boto3", "httplib2", "httpx", "pytest", "pytest-aiohttp", "pytest-asyncio", "pytest-cov", "pytest-httpbin", "requests (>=2.22.0)", "tornado", "urllib3"] [[package]] name = "wrapt" version = "1.16.0" description = "Module for decorators, wrappers and monkey patching." optional = false python-versions = ">=3.6" files = [ {file = "wrapt-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ffa565331890b90056c01db69c0fe634a776f8019c143a5ae265f9c6bc4bd6d4"}, {file = "wrapt-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e4fdb9275308292e880dcbeb12546df7f3e0f96c6b41197e0cf37d2826359020"}, {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb2dee3874a500de01c93d5c71415fcaef1d858370d405824783e7a8ef5db440"}, {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2a88e6010048489cda82b1326889ec075a8c856c2e6a256072b28eaee3ccf487"}, {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac83a914ebaf589b69f7d0a1277602ff494e21f4c2f743313414378f8f50a4cf"}, {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:73aa7d98215d39b8455f103de64391cb79dfcad601701a3aa0dddacf74911d72"}, {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:807cc8543a477ab7422f1120a217054f958a66ef7314f76dd9e77d3f02cdccd0"}, {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bf5703fdeb350e36885f2875d853ce13172ae281c56e509f4e6eca049bdfb136"}, {file = "wrapt-1.16.0-cp310-cp310-win32.whl", hash = "sha256:f6b2d0c6703c988d334f297aa5df18c45e97b0af3679bb75059e0e0bd8b1069d"}, {file = "wrapt-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:decbfa2f618fa8ed81c95ee18a387ff973143c656ef800c9f24fb7e9c16054e2"}, {file = "wrapt-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1a5db485fe2de4403f13fafdc231b0dbae5eca4359232d2efc79025527375b09"}, {file = "wrapt-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:75ea7d0ee2a15733684badb16de6794894ed9c55aa5e9903260922f0482e687d"}, {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a452f9ca3e3267cd4d0fcf2edd0d035b1934ac2bd7e0e57ac91ad6b95c0c6389"}, {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:43aa59eadec7890d9958748db829df269f0368521ba6dc68cc172d5d03ed8060"}, {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72554a23c78a8e7aa02abbd699d129eead8b147a23c56e08d08dfc29cfdddca1"}, {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d2efee35b4b0a347e0d99d28e884dfd82797852d62fcd7ebdeee26f3ceb72cf3"}, {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:6dcfcffe73710be01d90cae08c3e548d90932d37b39ef83969ae135d36ef3956"}, {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:eb6e651000a19c96f452c85132811d25e9264d836951022d6e81df2fff38337d"}, {file = "wrapt-1.16.0-cp311-cp311-win32.whl", hash = "sha256:66027d667efe95cc4fa945af59f92c5a02c6f5bb6012bff9e60542c74c75c362"}, {file = "wrapt-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:aefbc4cb0a54f91af643660a0a150ce2c090d3652cf4052a5397fb2de549cd89"}, {file = "wrapt-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5eb404d89131ec9b4f748fa5cfb5346802e5ee8836f57d516576e61f304f3b7b"}, {file = "wrapt-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9090c9e676d5236a6948330e83cb89969f433b1943a558968f659ead07cb3b36"}, {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94265b00870aa407bd0cbcfd536f17ecde43b94fb8d228560a1e9d3041462d73"}, {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2058f813d4f2b5e3a9eb2eb3faf8f1d99b81c3e51aeda4b168406443e8ba809"}, {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98b5e1f498a8ca1858a1cdbffb023bfd954da4e3fa2c0cb5853d40014557248b"}, {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:14d7dc606219cdd7405133c713f2c218d4252f2a469003f8c46bb92d5d095d81"}, {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:49aac49dc4782cb04f58986e81ea0b4768e4ff197b57324dcbd7699c5dfb40b9"}, {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:418abb18146475c310d7a6dc71143d6f7adec5b004ac9ce08dc7a34e2babdc5c"}, {file = "wrapt-1.16.0-cp312-cp312-win32.whl", hash = "sha256:685f568fa5e627e93f3b52fda002c7ed2fa1800b50ce51f6ed1d572d8ab3e7fc"}, {file = "wrapt-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:dcdba5c86e368442528f7060039eda390cc4091bfd1dca41e8046af7c910dda8"}, {file = "wrapt-1.16.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d462f28826f4657968ae51d2181a074dfe03c200d6131690b7d65d55b0f360f8"}, {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a33a747400b94b6d6b8a165e4480264a64a78c8a4c734b62136062e9a248dd39"}, {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3646eefa23daeba62643a58aac816945cadc0afaf21800a1421eeba5f6cfb9c"}, {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ebf019be5c09d400cf7b024aa52b1f3aeebeff51550d007e92c3c1c4afc2a40"}, {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:0d2691979e93d06a95a26257adb7bfd0c93818e89b1406f5a28f36e0d8c1e1fc"}, {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:1acd723ee2a8826f3d53910255643e33673e1d11db84ce5880675954183ec47e"}, {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:bc57efac2da352a51cc4658878a68d2b1b67dbe9d33c36cb826ca449d80a8465"}, {file = "wrapt-1.16.0-cp36-cp36m-win32.whl", hash = "sha256:da4813f751142436b075ed7aa012a8778aa43a99f7b36afe9b742d3ed8bdc95e"}, {file = "wrapt-1.16.0-cp36-cp36m-win_amd64.whl", hash = "sha256:6f6eac2360f2d543cc875a0e5efd413b6cbd483cb3ad7ebf888884a6e0d2e966"}, {file = "wrapt-1.16.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a0ea261ce52b5952bf669684a251a66df239ec6d441ccb59ec7afa882265d593"}, {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bd2d7ff69a2cac767fbf7a2b206add2e9a210e57947dd7ce03e25d03d2de292"}, {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9159485323798c8dc530a224bd3ffcf76659319ccc7bbd52e01e73bd0241a0c5"}, {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a86373cf37cd7764f2201b76496aba58a52e76dedfaa698ef9e9688bfd9e41cf"}, {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:73870c364c11f03ed072dda68ff7aea6d2a3a5c3fe250d917a429c7432e15228"}, {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b935ae30c6e7400022b50f8d359c03ed233d45b725cfdd299462f41ee5ffba6f"}, {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:db98ad84a55eb09b3c32a96c576476777e87c520a34e2519d3e59c44710c002c"}, {file = "wrapt-1.16.0-cp37-cp37m-win32.whl", hash = "sha256:9153ed35fc5e4fa3b2fe97bddaa7cbec0ed22412b85bcdaf54aeba92ea37428c"}, {file = "wrapt-1.16.0-cp37-cp37m-win_amd64.whl", hash = "sha256:66dfbaa7cfa3eb707bbfcd46dab2bc6207b005cbc9caa2199bcbc81d95071a00"}, {file = "wrapt-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1dd50a2696ff89f57bd8847647a1c363b687d3d796dc30d4dd4a9d1689a706f0"}, {file = "wrapt-1.16.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:44a2754372e32ab315734c6c73b24351d06e77ffff6ae27d2ecf14cf3d229202"}, {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e9723528b9f787dc59168369e42ae1c3b0d3fadb2f1a71de14531d321ee05b0"}, {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbed418ba5c3dce92619656802cc5355cb679e58d0d89b50f116e4a9d5a9603e"}, {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:941988b89b4fd6b41c3f0bfb20e92bd23746579736b7343283297c4c8cbae68f"}, {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6a42cd0cfa8ffc1915aef79cb4284f6383d8a3e9dcca70c445dcfdd639d51267"}, {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ca9b6085e4f866bd584fb135a041bfc32cab916e69f714a7d1d397f8c4891ca"}, {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5e49454f19ef621089e204f862388d29e6e8d8b162efce05208913dde5b9ad6"}, {file = "wrapt-1.16.0-cp38-cp38-win32.whl", hash = "sha256:c31f72b1b6624c9d863fc095da460802f43a7c6868c5dda140f51da24fd47d7b"}, {file = "wrapt-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:490b0ee15c1a55be9c1bd8609b8cecd60e325f0575fc98f50058eae366e01f41"}, {file = "wrapt-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9b201ae332c3637a42f02d1045e1d0cccfdc41f1f2f801dafbaa7e9b4797bfc2"}, {file = "wrapt-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2076fad65c6736184e77d7d4729b63a6d1ae0b70da4868adeec40989858eb3fb"}, {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5cd603b575ebceca7da5a3a251e69561bec509e0b46e4993e1cac402b7247b8"}, {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b47cfad9e9bbbed2339081f4e346c93ecd7ab504299403320bf85f7f85c7d46c"}, {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8212564d49c50eb4565e502814f694e240c55551a5f1bc841d4fcaabb0a9b8a"}, {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5f15814a33e42b04e3de432e573aa557f9f0f56458745c2074952f564c50e664"}, {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db2e408d983b0e61e238cf579c09ef7020560441906ca990fe8412153e3b291f"}, {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:edfad1d29c73f9b863ebe7082ae9321374ccb10879eeabc84ba3b69f2579d537"}, {file = "wrapt-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed867c42c268f876097248e05b6117a65bcd1e63b779e916fe2e33cd6fd0d3c3"}, {file = "wrapt-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:eb1b046be06b0fce7249f1d025cd359b4b80fc1c3e24ad9eca33e0dcdb2e4a35"}, {file = "wrapt-1.16.0-py3-none-any.whl", hash = "sha256:6906c4100a8fcbf2fa735f6059214bb13b97f75b1a61777fcf6432121ef12ef1"}, {file = "wrapt-1.16.0.tar.gz", hash = "sha256:5f370f952971e7d17c7d1ead40e49f32345a7f7a5373571ef44d800d06b1899d"}, ] [[package]] name = "yarl" version = "1.9.4" description = "Yet another URL library" optional = false python-versions = ">=3.7" files = [ {file = "yarl-1.9.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a8c1df72eb746f4136fe9a2e72b0c9dc1da1cbd23b5372f94b5820ff8ae30e0e"}, {file = "yarl-1.9.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a3a6ed1d525bfb91b3fc9b690c5a21bb52de28c018530ad85093cc488bee2dd2"}, {file = "yarl-1.9.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c38c9ddb6103ceae4e4498f9c08fac9b590c5c71b0370f98714768e22ac6fa66"}, {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d9e09c9d74f4566e905a0b8fa668c58109f7624db96a2171f21747abc7524234"}, {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8477c1ee4bd47c57d49621a062121c3023609f7a13b8a46953eb6c9716ca392"}, {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5ff2c858f5f6a42c2a8e751100f237c5e869cbde669a724f2062d4c4ef93551"}, {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:357495293086c5b6d34ca9616a43d329317feab7917518bc97a08f9e55648455"}, {file = "yarl-1.9.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54525ae423d7b7a8ee81ba189f131054defdb122cde31ff17477951464c1691c"}, {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:801e9264d19643548651b9db361ce3287176671fb0117f96b5ac0ee1c3530d53"}, {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e516dc8baf7b380e6c1c26792610230f37147bb754d6426462ab115a02944385"}, {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:7d5aaac37d19b2904bb9dfe12cdb08c8443e7ba7d2852894ad448d4b8f442863"}, {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:54beabb809ffcacbd9d28ac57b0db46e42a6e341a030293fb3185c409e626b8b"}, {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bac8d525a8dbc2a1507ec731d2867025d11ceadcb4dd421423a5d42c56818541"}, {file = "yarl-1.9.4-cp310-cp310-win32.whl", hash = "sha256:7855426dfbddac81896b6e533ebefc0af2f132d4a47340cee6d22cac7190022d"}, {file = "yarl-1.9.4-cp310-cp310-win_amd64.whl", hash = "sha256:848cd2a1df56ddbffeb375535fb62c9d1645dde33ca4d51341378b3f5954429b"}, {file = "yarl-1.9.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:35a2b9396879ce32754bd457d31a51ff0a9d426fd9e0e3c33394bf4b9036b099"}, {file = "yarl-1.9.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c7d56b293cc071e82532f70adcbd8b61909eec973ae9d2d1f9b233f3d943f2c"}, {file = "yarl-1.9.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d8a1c6c0be645c745a081c192e747c5de06e944a0d21245f4cf7c05e457c36e0"}, {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b3c1ffe10069f655ea2d731808e76e0f452fc6c749bea04781daf18e6039525"}, {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:549d19c84c55d11687ddbd47eeb348a89df9cb30e1993f1b128f4685cd0ebbf8"}, {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7409f968456111140c1c95301cadf071bd30a81cbd7ab829169fb9e3d72eae9"}, {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e23a6d84d9d1738dbc6e38167776107e63307dfc8ad108e580548d1f2c587f42"}, {file = "yarl-1.9.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d8b889777de69897406c9fb0b76cdf2fd0f31267861ae7501d93003d55f54fbe"}, {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:03caa9507d3d3c83bca08650678e25364e1843b484f19986a527630ca376ecce"}, {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4e9035df8d0880b2f1c7f5031f33f69e071dfe72ee9310cfc76f7b605958ceb9"}, {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:c0ec0ed476f77db9fb29bca17f0a8fcc7bc97ad4c6c1d8959c507decb22e8572"}, {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:ee04010f26d5102399bd17f8df8bc38dc7ccd7701dc77f4a68c5b8d733406958"}, {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:49a180c2e0743d5d6e0b4d1a9e5f633c62eca3f8a86ba5dd3c471060e352ca98"}, {file = "yarl-1.9.4-cp311-cp311-win32.whl", hash = "sha256:81eb57278deb6098a5b62e88ad8281b2ba09f2f1147c4767522353eaa6260b31"}, {file = "yarl-1.9.4-cp311-cp311-win_amd64.whl", hash = "sha256:d1d2532b340b692880261c15aee4dc94dd22ca5d61b9db9a8a361953d36410b1"}, {file = "yarl-1.9.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0d2454f0aef65ea81037759be5ca9947539667eecebca092733b2eb43c965a81"}, {file = "yarl-1.9.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:44d8ffbb9c06e5a7f529f38f53eda23e50d1ed33c6c869e01481d3fafa6b8142"}, {file = "yarl-1.9.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:aaaea1e536f98754a6e5c56091baa1b6ce2f2700cc4a00b0d49eca8dea471074"}, {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3777ce5536d17989c91696db1d459574e9a9bd37660ea7ee4d3344579bb6f129"}, {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fc5fc1eeb029757349ad26bbc5880557389a03fa6ada41703db5e068881e5f2"}, {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea65804b5dc88dacd4a40279af0cdadcfe74b3e5b4c897aa0d81cf86927fee78"}, {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa102d6d280a5455ad6a0f9e6d769989638718e938a6a0a2ff3f4a7ff8c62cc4"}, {file = "yarl-1.9.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09efe4615ada057ba2d30df871d2f668af661e971dfeedf0c159927d48bbeff0"}, {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:008d3e808d03ef28542372d01057fd09168419cdc8f848efe2804f894ae03e51"}, {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:6f5cb257bc2ec58f437da2b37a8cd48f666db96d47b8a3115c29f316313654ff"}, {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:992f18e0ea248ee03b5a6e8b3b4738850ae7dbb172cc41c966462801cbf62cf7"}, {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:0e9d124c191d5b881060a9e5060627694c3bdd1fe24c5eecc8d5d7d0eb6faabc"}, {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3986b6f41ad22988e53d5778f91855dc0399b043fc8946d4f2e68af22ee9ff10"}, {file = "yarl-1.9.4-cp312-cp312-win32.whl", hash = "sha256:4b21516d181cd77ebd06ce160ef8cc2a5e9ad35fb1c5930882baff5ac865eee7"}, {file = "yarl-1.9.4-cp312-cp312-win_amd64.whl", hash = "sha256:a9bd00dc3bc395a662900f33f74feb3e757429e545d831eef5bb280252631984"}, {file = "yarl-1.9.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:63b20738b5aac74e239622d2fe30df4fca4942a86e31bf47a81a0e94c14df94f"}, {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7d7f7de27b8944f1fee2c26a88b4dabc2409d2fea7a9ed3df79b67277644e17"}, {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c74018551e31269d56fab81a728f683667e7c28c04e807ba08f8c9e3bba32f14"}, {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ca06675212f94e7a610e85ca36948bb8fc023e458dd6c63ef71abfd482481aa5"}, {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5aef935237d60a51a62b86249839b51345f47564208c6ee615ed2a40878dccdd"}, {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2b134fd795e2322b7684155b7855cc99409d10b2e408056db2b93b51a52accc7"}, {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d25039a474c4c72a5ad4b52495056f843a7ff07b632c1b92ea9043a3d9950f6e"}, {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f7d6b36dd2e029b6bcb8a13cf19664c7b8e19ab3a58e0fefbb5b8461447ed5ec"}, {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:957b4774373cf6f709359e5c8c4a0af9f6d7875db657adb0feaf8d6cb3c3964c"}, {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:d7eeb6d22331e2fd42fce928a81c697c9ee2d51400bd1a28803965883e13cead"}, {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6a962e04b8f91f8c4e5917e518d17958e3bdee71fd1d8b88cdce74dd0ebbf434"}, {file = "yarl-1.9.4-cp37-cp37m-win32.whl", hash = "sha256:f3bc6af6e2b8f92eced34ef6a96ffb248e863af20ef4fde9448cc8c9b858b749"}, {file = "yarl-1.9.4-cp37-cp37m-win_amd64.whl", hash = "sha256:ad4d7a90a92e528aadf4965d685c17dacff3df282db1121136c382dc0b6014d2"}, {file = "yarl-1.9.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ec61d826d80fc293ed46c9dd26995921e3a82146feacd952ef0757236fc137be"}, {file = "yarl-1.9.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8be9e837ea9113676e5754b43b940b50cce76d9ed7d2461df1af39a8ee674d9f"}, {file = "yarl-1.9.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bef596fdaa8f26e3d66af846bbe77057237cb6e8efff8cd7cc8dff9a62278bbf"}, {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d47552b6e52c3319fede1b60b3de120fe83bde9b7bddad11a69fb0af7db32f1"}, {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84fc30f71689d7fc9168b92788abc977dc8cefa806909565fc2951d02f6b7d57"}, {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4aa9741085f635934f3a2583e16fcf62ba835719a8b2b28fb2917bb0537c1dfa"}, {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:206a55215e6d05dbc6c98ce598a59e6fbd0c493e2de4ea6cc2f4934d5a18d130"}, {file = "yarl-1.9.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07574b007ee20e5c375a8fe4a0789fad26db905f9813be0f9fef5a68080de559"}, {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5a2e2433eb9344a163aced6a5f6c9222c0786e5a9e9cac2c89f0b28433f56e23"}, {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6ad6d10ed9b67a382b45f29ea028f92d25bc0bc1daf6c5b801b90b5aa70fb9ec"}, {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:6fe79f998a4052d79e1c30eeb7d6c1c1056ad33300f682465e1b4e9b5a188b78"}, {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a825ec844298c791fd28ed14ed1bffc56a98d15b8c58a20e0e08c1f5f2bea1be"}, {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8619d6915b3b0b34420cf9b2bb6d81ef59d984cb0fde7544e9ece32b4b3043c3"}, {file = "yarl-1.9.4-cp38-cp38-win32.whl", hash = "sha256:686a0c2f85f83463272ddffd4deb5e591c98aac1897d65e92319f729c320eece"}, {file = "yarl-1.9.4-cp38-cp38-win_amd64.whl", hash = "sha256:a00862fb23195b6b8322f7d781b0dc1d82cb3bcac346d1e38689370cc1cc398b"}, {file = "yarl-1.9.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:604f31d97fa493083ea21bd9b92c419012531c4e17ea6da0f65cacdcf5d0bd27"}, {file = "yarl-1.9.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8a854227cf581330ffa2c4824d96e52ee621dd571078a252c25e3a3b3d94a1b1"}, {file = "yarl-1.9.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ba6f52cbc7809cd8d74604cce9c14868306ae4aa0282016b641c661f981a6e91"}, {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6327976c7c2f4ee6816eff196e25385ccc02cb81427952414a64811037bbc8b"}, {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8397a3817d7dcdd14bb266283cd1d6fc7264a48c186b986f32e86d86d35fbac5"}, {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e0381b4ce23ff92f8170080c97678040fc5b08da85e9e292292aba67fdac6c34"}, {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23d32a2594cb5d565d358a92e151315d1b2268bc10f4610d098f96b147370136"}, {file = "yarl-1.9.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ddb2a5c08a4eaaba605340fdee8fc08e406c56617566d9643ad8bf6852778fc7"}, {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:26a1dc6285e03f3cc9e839a2da83bcbf31dcb0d004c72d0730e755b33466c30e"}, {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:18580f672e44ce1238b82f7fb87d727c4a131f3a9d33a5e0e82b793362bf18b4"}, {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:29e0f83f37610f173eb7e7b5562dd71467993495e568e708d99e9d1944f561ec"}, {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:1f23e4fe1e8794f74b6027d7cf19dc25f8b63af1483d91d595d4a07eca1fb26c"}, {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:db8e58b9d79200c76956cefd14d5c90af54416ff5353c5bfd7cbe58818e26ef0"}, {file = "yarl-1.9.4-cp39-cp39-win32.whl", hash = "sha256:c7224cab95645c7ab53791022ae77a4509472613e839dab722a72abe5a684575"}, {file = "yarl-1.9.4-cp39-cp39-win_amd64.whl", hash = "sha256:824d6c50492add5da9374875ce72db7a0733b29c2394890aef23d533106e2b15"}, {file = "yarl-1.9.4-py3-none-any.whl", hash = "sha256:928cecb0ef9d5a7946eb6ff58417ad2fe9375762382f1bf5c55e61645f2c43ad"}, {file = "yarl-1.9.4.tar.gz", hash = "sha256:566db86717cf8080b99b58b083b773a908ae40f06681e87e589a976faf8246bf"}, ] [package.dependencies] idna = ">=2.0" multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.8" content-hash = "7b7c8f00039ca373510a870745c9289a8abc17b479d165ddac8ba30560cbe6ad" bdraco-freenub-69809e8/pubnub/000077500000000000000000000000001464523203000161605ustar00rootroot00000000000000bdraco-freenub-69809e8/pubnub/__init__.py000066400000000000000000000010641464523203000202720ustar00rootroot00000000000000import logging import os PUBNUB_ROOT = os.path.dirname(os.path.abspath(__file__)) __version__ = "0.1.0" def set_stream_logger( name="pubnub", level=logging.ERROR, format_string=None, stream=None ): if format_string is None: format_string = "%(asctime)s %(name)s [%(levelname)s] %(message)s" logger = logging.getLogger(name) logger.setLevel(level) handler = logging.StreamHandler(stream) handler.setLevel(level) formatter = logging.Formatter(format_string) handler.setFormatter(formatter) logger.addHandler(handler) bdraco-freenub-69809e8/pubnub/builders.py000066400000000000000000000037211464523203000203460ustar00rootroot00000000000000from abc import ABCMeta, abstractmethod from . import utils from .dtos import SubscribeOperation, UnsubscribeOperation class PubSubBuilder: __metaclass__ = ABCMeta def __init__(self, subscription_manager): self._subscription_manager = subscription_manager self._channel_subscriptions = [] self._channel_group_subscriptions = [] # TODO: make the 'channel' alias def channels(self, channels_list): utils.extend_list(self._channel_subscriptions, channels_list) return self def channel_groups(self, channel_groups_list): utils.extend_list(self._channel_group_subscriptions, channel_groups_list) return self @abstractmethod def execute(self): pass class SubscribeBuilder(PubSubBuilder): def __init__(self, subscription_manager): super().__init__(subscription_manager) self._presence_enabled = False self._timetoken = 0 def with_presence(self): self._presence_enabled = True return self def with_timetoken(self, timetoken): self._timetoken = timetoken return self def channel_subscriptions(self): return self._channel_subscriptions def channel_group_subscriptions(self): return self._channel_group_subscriptions def execute(self): subscribe_operation = SubscribeOperation( channels=self._channel_subscriptions, channel_groups=self._channel_group_subscriptions, timetoken=self._timetoken, presence_enabled=self._presence_enabled, ) self._subscription_manager.adapt_subscribe_builder(subscribe_operation) class UnsubscribeBuilder(PubSubBuilder): def execute(self): unsubscribe_operation = UnsubscribeOperation( channels=self._channel_subscriptions, channel_groups=self._channel_group_subscriptions, ) self._subscription_manager.adapt_unsubscribe_builder(unsubscribe_operation) bdraco-freenub-69809e8/pubnub/callbacks.py000066400000000000000000000015051464523203000204520ustar00rootroot00000000000000from abc import ABCMeta, abstractmethod class PNCallback: @abstractmethod def on_response(self, x, status): pass class SubscribeCallback: __metaclass__ = ABCMeta @abstractmethod def status(self, pubnub, status): pass @abstractmethod def message(self, pubnub, message): pass @abstractmethod def presence(self, pubnub, presence): pass def signal(self, pubnub, signal): pass def channel(self, pubnub, channel): pass def uuid(self, pubnub, uuid): pass def membership(self, pubnub, membership): pass def message_action(self, pubnub, message_action): pass def file(self, pubnub, file_message): pass class ReconnectionCallback: @abstractmethod def on_reconnect(self): pass bdraco-freenub-69809e8/pubnub/crypto.py000066400000000000000000000104311464523203000200510ustar00rootroot00000000000000import hashlib import json import random from base64 import decodebytes, encodebytes from Cryptodome.Cipher import AES from Cryptodome.Util.Padding import pad, unpad from pubnub.crypto_core import PubNubCrypto Initial16bytes = "0123456789012345" class PubNubCryptodome(PubNubCrypto): mode = AES.MODE_CBC fallback_mode = None def __init__(self, pubnub_config): self.pubnub_configuration = pubnub_config self.mode = pubnub_config.cipher_mode self.fallback_mode = pubnub_config.fallback_cipher_mode def encrypt(self, key, msg, use_random_iv=False): secret = self.get_secret(key) initialization_vector = self.get_initialization_vector(use_random_iv) cipher = AES.new( bytes(secret[0:32], "utf-8"), self.mode, bytes(initialization_vector, "utf-8"), ) encrypted_message = cipher.encrypt(self.pad(msg.encode("utf-8"))) msg_with_iv = self.append_random_iv( encrypted_message, use_random_iv, bytes(initialization_vector, "utf-8") ) return encodebytes(msg_with_iv).decode("utf-8").replace("\n", "") def decrypt(self, key, msg, use_random_iv=False): secret = self.get_secret(key) decoded_message = decodebytes(msg.encode("utf-8")) initialization_vector, extracted_message = self.extract_random_iv( decoded_message, use_random_iv ) cipher = AES.new(bytes(secret[0:32], "utf-8"), self.mode, initialization_vector) try: plain = self.depad((cipher.decrypt(extracted_message)).decode("utf-8")) except UnicodeDecodeError as e: if not self.fallback_mode: raise e cipher = AES.new( bytes(secret[0:32], "utf-8"), self.fallback_mode, initialization_vector ) plain = self.depad((cipher.decrypt(extracted_message)).decode("utf-8")) try: return json.loads(plain) except Exception: return plain def append_random_iv(self, message, use_random_iv, initialization_vector): if self.pubnub_configuration.use_random_initialization_vector or use_random_iv: return initialization_vector + message else: return message def extract_random_iv(self, message, use_random_iv): if self.pubnub_configuration.use_random_initialization_vector or use_random_iv: return message[0:16], message[16:] else: return bytes(Initial16bytes, "utf-8"), message def get_initialization_vector(self, use_random_iv): if self.pubnub_configuration.use_random_initialization_vector or use_random_iv: return f"{random.randint(0, 9999999999999999):016}" else: return Initial16bytes def pad(self, msg, block_size=16): padding = block_size - (len(msg) % block_size) return msg + (chr(padding) * padding).encode("utf-8") def depad(self, msg): return msg[0 : -ord(msg[-1])] def get_secret(self, key): return hashlib.sha256(key.encode("utf-8")).hexdigest() class PubNubFileCrypto(PubNubCryptodome): def encrypt(self, key, file): secret = self.get_secret(key) initialization_vector = self.get_initialization_vector(use_random_iv=True) cipher = AES.new( bytes(secret[0:32], "utf-8"), self.mode, bytes(initialization_vector, "utf-8"), ) initialization_vector = bytes(initialization_vector, "utf-8") return self.append_random_iv( cipher.encrypt(pad(file, 16)), use_random_iv=True, initialization_vector=initialization_vector, ) def decrypt(self, key, file): secret = self.get_secret(key) initialization_vector, extracted_file = self.extract_random_iv( file, use_random_iv=True ) try: cipher = AES.new( bytes(secret[0:32], "utf-8"), self.mode, initialization_vector ) result = unpad(cipher.decrypt(extracted_file), 16) except ValueError: cipher = AES.new( bytes(secret[0:32], "utf-8"), self.fallback_mode, initialization_vector ) result = unpad(cipher.decrypt(extracted_file), 16) return result bdraco-freenub-69809e8/pubnub/crypto_core.py000066400000000000000000000002721464523203000210630ustar00rootroot00000000000000from abc import abstractmethod class PubNubCrypto: @abstractmethod def encrypt(self, key, msg): pass @abstractmethod def decrypt(self, key, msg): pass bdraco-freenub-69809e8/pubnub/dtos.py000066400000000000000000000017241464523203000175070ustar00rootroot00000000000000class SubscribeOperation: def __init__( self, channels=None, channel_groups=None, presence_enabled=None, timetoken=None ): assert isinstance(channels, (list, tuple)) assert isinstance(channel_groups, (list, tuple)) assert isinstance(presence_enabled, bool) assert isinstance(timetoken, int) self.channels = channels self.channel_groups = channel_groups self.presence_enabled = presence_enabled self.timetoken = timetoken class UnsubscribeOperation: def __init__(self, channels=None, channel_groups=None): assert isinstance(channels, (list, tuple)) assert isinstance(channel_groups, (list, tuple)) self.channels = channels self.channel_groups = channel_groups class StateOperation: def __init__(self, channels=None, channel_groups=None, state=None): self.channels = channels self.channel_groups = channel_groups self.state = state bdraco-freenub-69809e8/pubnub/endpoints/000077500000000000000000000000001464523203000201635ustar00rootroot00000000000000bdraco-freenub-69809e8/pubnub/endpoints/__init__.py000066400000000000000000000000001464523203000222620ustar00rootroot00000000000000bdraco-freenub-69809e8/pubnub/endpoints/access/000077500000000000000000000000001464523203000214245ustar00rootroot00000000000000bdraco-freenub-69809e8/pubnub/endpoints/access/__init__.py000066400000000000000000000000001464523203000235230ustar00rootroot00000000000000bdraco-freenub-69809e8/pubnub/endpoints/access/audit.py000066400000000000000000000042121464523203000231030ustar00rootroot00000000000000from pubnub import utils from pubnub.endpoints.endpoint import Endpoint from pubnub.enums import HttpMethod, PNOperationType from pubnub.models.consumer.access_manager import PNAccessManagerAuditResult class Audit(Endpoint): AUDIT_PATH = "/v2/auth/audit/sub-key/%s" def __init__(self, pubnub): Endpoint.__init__(self, pubnub) self._auth_keys = [] self._channels = [] self._groups = [] self._read = None self._write = None self._manage = None self._ttl = None self._sort_params = True def auth_keys(self, auth_keys): utils.extend_list(self._auth_keys, auth_keys) return self def channels(self, channels): utils.extend_list(self._channels, channels) return self def channel_groups(self, channel_groups): utils.extend_list(self._groups, channel_groups) return self def custom_params(self): params = {} if len(self._auth_keys) > 0: params["auth"] = utils.join_items_and_encode(self._auth_keys) if len(self._channels) > 0: params["channel"] = utils.join_items_and_encode(self._channels) if len(self._groups) > 0: params["channel-group"] = utils.join_items_and_encode(self._groups) return params def build_path(self): return Audit.AUDIT_PATH % self.pubnub.config.subscribe_key def http_method(self): return HttpMethod.GET def validate_params(self): self.validate_subscribe_key() self.validate_secret_key() def create_response(self, envelope): return PNAccessManagerAuditResult.from_json(envelope["payload"]) def is_auth_required(self): return False def affected_channels(self): return self._channels def affected_channels_groups(self): return self._groups def request_timeout(self): return self.pubnub.config.non_subscribe_request_timeout def connect_timeout(self): return self.pubnub.config.connect_timeout def operation_type(self): return PNOperationType.PNAccessManagerAudit def name(self): return "Grant" bdraco-freenub-69809e8/pubnub/endpoints/access/grant.py000066400000000000000000000121431464523203000231120ustar00rootroot00000000000000from pubnub import utils from pubnub.endpoints.endpoint import Endpoint from pubnub.enums import HttpMethod, PNOperationType from pubnub.errors import PNERR_PAM_INVALID_ARGUMENTS, PNERR_PAM_NO_FLAGS from pubnub.exceptions import PubNubException from pubnub.models.consumer.access_manager import PNAccessManagerGrantResult class Grant(Endpoint): GRANT_PATH = "/v2/auth/grant/sub-key/%s" def __init__(self, pubnub): Endpoint.__init__(self, pubnub) self._auth_keys = [] self._channels = [] self._groups = [] self._uuids = [] self._read = None self._write = None self._manage = None self._delete = None self._ttl = None self._get = None self._update = None self._join = None self._sort_params = True def get(self, flag): self._get = flag return self def update(self, flag): self._update = flag return self def join(self, flag): self._join = flag return self def uuids(self, uuids): utils.extend_list(self._uuids, uuids) return self def auth_keys(self, auth_keys): utils.extend_list(self._auth_keys, auth_keys) return self def channels(self, channels): utils.extend_list(self._channels, channels) return self def channel_groups(self, channel_groups): utils.extend_list(self._groups, channel_groups) return self def read(self, flag): self._read = flag return self def write(self, flag): self._write = flag return self def manage(self, flag): self._manage = flag return self def delete(self, flag): self._delete = flag return self def ttl(self, ttl): self._ttl = ttl return self def encoded_params(self): params = {} if self._auth_keys: params["auth"] = utils.join_items_and_encode(self._auth_keys) if self._channels: params["channel"] = utils.join_channels(self._channels) if self._groups: params["channel-group"] = utils.join_items_and_encode(self._groups) return params def custom_params(self): params = {} if self._read is not None: params["r"] = "1" if self._read is True else "0" if self._write is not None: params["w"] = "1" if self._write is True else "0" if self._manage is not None: params["m"] = "1" if self._manage is True else "0" if self._delete is not None: params["d"] = "1" if self._delete is True else "0" if self._get is not None: params["g"] = "1" if self._get is True else "0" if self._update is not None: params["u"] = "1" if self._update is True else "0" if self._join is not None: params["j"] = "1" if self._join is True else "0" if self._auth_keys: params["auth"] = utils.join_items(self._auth_keys) if self._channels: params["channel"] = utils.join_items(self._channels) if self._groups: params["channel-group"] = utils.join_items(self._groups) if self._uuids: params["target-uuid"] = utils.join_items(self._uuids) if self._ttl is not None: params["ttl"] = str(int(self._ttl)) return params def build_path(self): return Grant.GRANT_PATH % self.pubnub.config.subscribe_key def http_method(self): return HttpMethod.GET def validate_params(self): self.validate_subscribe_key() self.validate_secret_key() self.validate_publish_key() # self.validate_channels_and_groups() if self._channels and self._groups and self._uuids: raise PubNubException( pn_error=PNERR_PAM_INVALID_ARGUMENTS, errormsg="Grants for channels or channelGroups can't be changed together with grants for UUIDs", ) if self._uuids and not self._auth_keys: raise PubNubException( pn_error=PNERR_PAM_INVALID_ARGUMENTS, errormsg="UUIDs grant management require " "providing non empty authKeys", ) if ( self._write is None and self._read is None and self._manage is None and self._get is None and self._update is None and self._join is None ): raise PubNubException(pn_error=PNERR_PAM_NO_FLAGS) def create_response(self, envelope): return PNAccessManagerGrantResult.from_json(envelope["payload"]) def is_auth_required(self): return False def affected_channels(self): return self._channels def affected_channels_groups(self): return self._groups def request_timeout(self): return self.pubnub.config.non_subscribe_request_timeout def connect_timeout(self): return self.pubnub.config.connect_timeout def operation_type(self): return PNOperationType.PNAccessManagerGrant def name(self): return "Grant" bdraco-freenub-69809e8/pubnub/endpoints/access/grant_token.py000066400000000000000000000070231464523203000243130ustar00rootroot00000000000000from pubnub import utils from pubnub.endpoints.endpoint import Endpoint from pubnub.enums import HttpMethod, PNOperationType from pubnub.errors import PNERR_INVALID_META, PNERR_RESOURCES_MISSING, PNERR_TTL_MISSING from pubnub.exceptions import PubNubException from pubnub.models.consumer.v3.access_manager import PNGrantTokenResult class GrantToken(Endpoint): GRANT_TOKEN_PATH = "/v3/pam/%s/grant" def __init__(self, pubnub): Endpoint.__init__(self, pubnub) self._ttl = None self._meta = None self._authorized_uuid = None self._channels = [] self._groups = [] self._uuids = [] self._sort_params = True def ttl(self, ttl): self._ttl = ttl return self def meta(self, meta): self._meta = meta return self def authorized_uuid(self, uuid): self._authorized_uuid = uuid return self def authorized_user(self, user): self._authorized_uuid = user return self def spaces(self, spaces): self._channels = spaces return self def users(self, users): self._uuids = users return self def channels(self, channels): self._channels = channels return self def groups(self, groups): self._groups = groups return self def uuids(self, uuids): self._uuids = uuids return self def custom_params(self): return {} def build_data(self): params = {"ttl": int(self._ttl)} permissions = {} resources = {} patterns = {} utils.parse_resources(self._channels, "channels", resources, patterns) utils.parse_resources(self._groups, "groups", resources, patterns) utils.parse_resources(self._uuids, "uuids", resources, patterns) utils.parse_resources(self._uuids, "users", resources, patterns) utils.parse_resources(self._channels, "spaces", resources, patterns) permissions["resources"] = resources permissions["patterns"] = patterns if self._meta: if isinstance(self._meta, dict): permissions["meta"] = self._meta else: raise PubNubException(pn_error=PNERR_INVALID_META) else: permissions["meta"] = {} if self._authorized_uuid: permissions["uuid"] = self._authorized_uuid params["permissions"] = permissions return utils.write_value_as_string(params) def build_path(self): return GrantToken.GRANT_TOKEN_PATH % self.pubnub.config.subscribe_key def http_method(self): return HttpMethod.POST def validate_params(self): self.validate_subscribe_key() self.validate_secret_key() self.validate_ttl() self.validate_resources() def create_response(self, envelope): return PNGrantTokenResult.from_json(envelope["data"]) def is_auth_required(self): return False def request_timeout(self): return self.pubnub.config.non_subscribe_request_timeout def connect_timeout(self): return self.pubnub.config.connect_timeout def operation_type(self): return PNOperationType.PNAccessManagerGrantToken def name(self): return "Grant Token" def validate_resources(self): if not any((self._channels, self._groups, self._uuids)): raise PubNubException(pn_error=PNERR_RESOURCES_MISSING) def validate_ttl(self): if not self._ttl: raise PubNubException(pn_error=PNERR_TTL_MISSING) bdraco-freenub-69809e8/pubnub/endpoints/access/revoke_token.py000066400000000000000000000023141464523203000244710ustar00rootroot00000000000000from pubnub import utils from pubnub.endpoints.endpoint import Endpoint from pubnub.enums import HttpMethod, PNOperationType from pubnub.models.consumer.v3.access_manager import PNRevokeTokenResult class RevokeToken(Endpoint): REVOKE_TOKEN_PATH = "/v3/pam/%s/grant/%s" def __init__(self, pubnub, token): Endpoint.__init__(self, pubnub) self.token = token def validate_params(self): self.validate_subscribe_key() self.validate_secret_key() def create_response(self, envelope): return PNRevokeTokenResult(envelope) def is_auth_required(self): return False def request_timeout(self): return self.pubnub.config.non_subscribe_request_timeout def connect_timeout(self): return self.pubnub.config.connect_timeout def http_method(self): return HttpMethod.DELETE def custom_params(self): return {} def build_path(self): return RevokeToken.REVOKE_TOKEN_PATH % ( self.pubnub.config.subscribe_key, utils.url_encode(self.token), ) def operation_type(self): return PNOperationType.PNAccessManagerRevokeToken def name(self): return "RevokeToken" bdraco-freenub-69809e8/pubnub/endpoints/channel_groups/000077500000000000000000000000001464523203000231725ustar00rootroot00000000000000bdraco-freenub-69809e8/pubnub/endpoints/channel_groups/__init__.py000066400000000000000000000000001464523203000252710ustar00rootroot00000000000000bdraco-freenub-69809e8/pubnub/endpoints/channel_groups/add_channel_to_channel_group.py000066400000000000000000000041221464523203000313710ustar00rootroot00000000000000from pubnub import utils from pubnub.endpoints.endpoint import Endpoint from pubnub.enums import HttpMethod, PNOperationType from pubnub.errors import PNERR_CHANNELS_MISSING, PNERR_GROUP_MISSING from pubnub.exceptions import PubNubException from pubnub.models.consumer.channel_group import PNChannelGroupsAddChannelResult class AddChannelToChannelGroup(Endpoint): # /v1/channel-registration/sub-key//channel-group/?add=ch1,ch2 ADD_PATH = "/v1/channel-registration/sub-key/%s/channel-group/%s" def __init__(self, pubnub): Endpoint.__init__(self, pubnub) self._channels = [] self._channel_group = None def channels(self, channels): if isinstance(channels, (list, tuple)): self._channels.extend(channels) else: self._channels.extend(utils.split_items(channels)) return self def channel_group(self, channel_group): self._channel_group = channel_group return self def custom_params(self): return {"add": utils.join_items(self._channels)} def build_path(self): return AddChannelToChannelGroup.ADD_PATH % ( self.pubnub.config.subscribe_key, utils.url_encode(self._channel_group), ) def http_method(self): return HttpMethod.GET def validate_params(self): self.validate_subscribe_key() if len(self._channels) == 0: raise PubNubException(pn_error=PNERR_CHANNELS_MISSING) if not isinstance(self._channel_group, str) or len(self._channel_group) == 0: raise PubNubException(pn_error=PNERR_GROUP_MISSING) def is_auth_required(self): return True def create_response(self, envelope): return PNChannelGroupsAddChannelResult() def request_timeout(self): return self.pubnub.config.non_subscribe_request_timeout def connect_timeout(self): return self.pubnub.config.connect_timeout def operation_type(self): return PNOperationType.PNAddChannelsToGroupOperation def name(self): return "AddChannelToChannelGroup" bdraco-freenub-69809e8/pubnub/endpoints/channel_groups/list_channels_in_channel_group.py000066400000000000000000000035051464523203000317670ustar00rootroot00000000000000from pubnub import utils from pubnub.endpoints.endpoint import Endpoint from pubnub.enums import HttpMethod, PNOperationType from pubnub.errors import PNERR_GROUP_MISSING from pubnub.exceptions import PubNubException from pubnub.models.consumer.channel_group import PNChannelGroupsListResult class ListChannelsInChannelGroup(Endpoint): # /v1/channel-registration/sub-key//channel-group/ LIST_PATH = "/v1/channel-registration/sub-key/%s/channel-group/%s" def __init__(self, pubnub): Endpoint.__init__(self, pubnub) self._channel_group = None def channel_group(self, channel_group): self._channel_group = channel_group return self def custom_params(self): return {} def build_path(self): return ListChannelsInChannelGroup.LIST_PATH % ( self.pubnub.config.subscribe_key, utils.url_encode(self._channel_group), ) def http_method(self): return HttpMethod.GET def validate_params(self): self.validate_subscribe_key() if not isinstance(self._channel_group, str) or len(self._channel_group) == 0: raise PubNubException(pn_error=PNERR_GROUP_MISSING) def create_response(self, envelope): if "payload" in envelope and "channels" in envelope["payload"]: return PNChannelGroupsListResult(envelope["payload"]["channels"]) else: return PNChannelGroupsListResult([]) def is_auth_required(self): return True def request_timeout(self): return self.pubnub.config.non_subscribe_request_timeout def connect_timeout(self): return self.pubnub.config.connect_timeout def operation_type(self): return PNOperationType.PNChannelsForGroupOperation def name(self): return "ListChannelsInChannelGroup" bdraco-freenub-69809e8/pubnub/endpoints/channel_groups/remove_channel_from_channel_group.py000066400000000000000000000041661464523203000324670ustar00rootroot00000000000000from pubnub import utils from pubnub.endpoints.endpoint import Endpoint from pubnub.enums import HttpMethod, PNOperationType from pubnub.errors import PNERR_CHANNELS_MISSING, PNERR_GROUP_MISSING from pubnub.exceptions import PubNubException from pubnub.models.consumer.channel_group import PNChannelGroupsRemoveChannelResult class RemoveChannelFromChannelGroup(Endpoint): # /v1/channel-registration/sub-key//channel-group/?remove=ch1,ch2 REMOVE_PATH = "/v1/channel-registration/sub-key/%s/channel-group/%s" def __init__(self, pubnub): Endpoint.__init__(self, pubnub) self._channels = [] self._channel_group = None def channels(self, channels): if isinstance(channels, (list, tuple)): self._channels.extend(channels) else: self._channels.extend(utils.split_items(channels)) return self def channel_group(self, channel_group): self._channel_group = channel_group return self def custom_params(self): return {"remove": utils.join_items(self._channels)} def build_path(self): return RemoveChannelFromChannelGroup.REMOVE_PATH % ( self.pubnub.config.subscribe_key, utils.url_encode(self._channel_group), ) def http_method(self): return HttpMethod.GET def validate_params(self): self.validate_subscribe_key() if len(self._channels) == 0: raise PubNubException(pn_error=PNERR_CHANNELS_MISSING) if not isinstance(self._channel_group, str) or len(self._channel_group) == 0: raise PubNubException(pn_error=PNERR_GROUP_MISSING) def is_auth_required(self): return True def create_response(self, envelope): return PNChannelGroupsRemoveChannelResult() def request_timeout(self): return self.pubnub.config.non_subscribe_request_timeout def connect_timeout(self): return self.pubnub.config.connect_timeout def operation_type(self): return PNOperationType.PNRemoveChannelsFromGroupOperation def name(self): return "RemoveChannelToChannelGroup" bdraco-freenub-69809e8/pubnub/endpoints/channel_groups/remove_channel_group.py000066400000000000000000000032361464523203000277510ustar00rootroot00000000000000from pubnub import utils from pubnub.endpoints.endpoint import Endpoint from pubnub.enums import HttpMethod, PNOperationType from pubnub.errors import PNERR_GROUP_MISSING from pubnub.exceptions import PubNubException from pubnub.models.consumer.channel_group import PNChannelGroupsRemoveGroupResult class RemoveChannelGroup(Endpoint): # /v1/channel-registration/sub-key//channel-group//remove REMOVE_PATH = "/v1/channel-registration/sub-key/%s/channel-group/%s/remove" def __init__(self, pubnub): Endpoint.__init__(self, pubnub) self._channel_group = None def channel_group(self, channel_group): self._channel_group = channel_group return self def custom_params(self): return {} def build_path(self): return RemoveChannelGroup.REMOVE_PATH % ( self.pubnub.config.subscribe_key, utils.url_encode(self._channel_group), ) def http_method(self): return HttpMethod.GET def validate_params(self): self.validate_subscribe_key() if not isinstance(self._channel_group, str) or len(self._channel_group) == 0: raise PubNubException(pn_error=PNERR_GROUP_MISSING) def is_auth_required(self): return True def create_response(self, envelope): return PNChannelGroupsRemoveGroupResult() def request_timeout(self): return self.pubnub.config.non_subscribe_request_timeout def connect_timeout(self): return self.pubnub.config.connect_timeout def operation_type(self): return PNOperationType.PNRemoveGroupOperation def name(self): return "RemoveChannelGroup" bdraco-freenub-69809e8/pubnub/endpoints/endpoint.py000066400000000000000000000234701464523203000223630ustar00rootroot00000000000000import logging import zlib from abc import ABCMeta, abstractmethod from pubnub import utils from pubnub.enums import HttpMethod, PNStatusCategory from pubnub.errors import ( PNERR_CHANNEL_MISSING, PNERR_CHANNEL_OR_GROUP_MISSING, PNERR_FILE_ID_MISSING, PNERR_FILE_NAME_MISSING, PNERR_FILE_OBJECT_MISSING, PNERR_PUBLISH_KEY_MISSING, PNERR_SECRET_KEY_MISSING, PNERR_SUBSCRIBE_KEY_MISSING, ) from pubnub.exceptions import PubNubException from pubnub.models.consumer.common import PNStatus from pubnub.models.consumer.pn_error_data import PNErrorData from pubnub.structures import RequestOptions, ResponseInfo logger = logging.getLogger("pubnub") class Endpoint: SERVER_RESPONSE_SUCCESS = 200 SERVER_RESPONSE_FORBIDDEN = 403 SERVER_RESPONSE_BAD_REQUEST = 400 __metaclass__ = ABCMeta _path = None def __init__(self, pubnub): self.pubnub = pubnub self._cancellation_event = None self._sort_params = False self._use_compression = self.pubnub.config.should_compress def cancellation_event(self, event): self._cancellation_event = event return self @abstractmethod def build_path(self): pass @abstractmethod def custom_params(self): raise NotImplementedError def build_data(self): return None @abstractmethod def http_method(self): pass @abstractmethod def validate_params(self): pass @abstractmethod def create_response(self, endpoint): pass @abstractmethod def operation_type(self): raise NotImplementedError @abstractmethod def name(self): pass @abstractmethod def request_timeout(self): pass @abstractmethod def connect_timeout(self): pass def is_auth_required(self): raise NotImplementedError def affected_channels(self): return None def affected_channels_groups(self): return None def allow_redirects(self): return True def use_base_path(self): return True def is_compressable(self): return False def request_headers(self): headers = {} if self.__compress_request(): headers["Content-Encoding"] = "gzip" if self.http_method() == HttpMethod.POST: headers["Content-type"] = "application/json" return headers def build_file_upload_request(self): return def non_json_response(self): return False def encoded_params(self): return {} def get_path(self): if not self._path: self._path = self.build_path() return self._path def options(self): data = self.build_data() if data and self.__compress_request(): data = zlib.compress(data.encode("utf-8"), level=2) return RequestOptions( path=self.get_path(), params_callback=self.build_params_callback(), method=self.http_method(), request_timeout=self.request_timeout(), connect_timeout=self.connect_timeout(), create_response=self.create_response, create_status=self.create_status, create_exception=self.create_exception, operation_type=self.operation_type(), data=data, files=self.build_file_upload_request(), sort_arguments=self._sort_params, allow_redirects=self.allow_redirects(), use_base_path=self.use_base_path(), request_headers=self.request_headers(), non_json_response=self.non_json_response(), ) def sync(self): self.validate_params() envelope = self.pubnub.request_sync(self.options()) if envelope.status.is_error(): raise envelope.status.error_data.exception return envelope def prepare_options(self): return self.pubnub.prepare_options(self.options()) def pn_async(self, callback): try: self.validate_params() options = self.options() except PubNubException as e: callback( None, self.create_status( PNStatusCategory.PNBadRequestCategory, None, None, e ), ) return def callback_wrapper(envelope): callback(envelope.result, envelope.status) return self.pubnub.request_async( endpoint_name=self.name(), endpoint_call_options=options, callback=callback_wrapper, # REVIEW: include self._cancellation_event into options? cancellation_event=self._cancellation_event, ) def result(self): def handler(): self.validate_params() return self.options() return self.pubnub.request_result( options_func=handler, cancellation_event=self._cancellation_event ) def future(self): def handler(): self.validate_params() return self.options() return self.pubnub.request_future( options_func=handler, cancellation_event=self._cancellation_event ) def deferred(self): def handler(): self.validate_params() return self.options() return self.pubnub.request_deferred( options_func=handler, cancellation_event=self._cancellation_event ) def build_params_callback(self): def callback(params_to_merge): custom_params = self.custom_params() custom_params.update(params_to_merge) custom_params["pnsdk"] = self.pubnub.sdk_name custom_params["uuid"] = self.pubnub.uuid for ( query_key, query_value, ) in self.pubnub._telemetry_manager.operation_latencies().items(): custom_params[query_key] = query_value if self.is_auth_required(): if self.pubnub._get_token(): custom_params["auth"] = self.pubnub._get_token() elif self.pubnub.config.auth_key: custom_params["auth"] = self.pubnub.config.auth_key if self.pubnub.config.secret_key: utils.sign_request( self, self.pubnub, custom_params, self.http_method(), self.build_data(), ) custom_params.update(self.encoded_params()) # reassign since pnsdk should be signed unencoded custom_params["pnsdk"] = utils.url_encode(self.pubnub.sdk_name) return custom_params return callback def validate_subscribe_key(self): if ( self.pubnub.config.subscribe_key is None or len(self.pubnub.config.subscribe_key) == 0 ): raise PubNubException(pn_error=PNERR_SUBSCRIBE_KEY_MISSING) def validate_secret_key(self): if ( self.pubnub.config.secret_key is None or len(self.pubnub.config.secret_key) == 0 ): raise PubNubException(pn_error=PNERR_SECRET_KEY_MISSING) def validate_channel(self): if self._channel is None or len(self._channel) == 0: raise PubNubException(pn_error=PNERR_CHANNEL_MISSING) def validate_channels_and_groups(self): if len(self._channels) == 0 and len(self._groups) == 0: raise PubNubException(pn_error=PNERR_CHANNEL_OR_GROUP_MISSING) def validate_publish_key(self): if ( self.pubnub.config.publish_key is None or len(self.pubnub.config.publish_key) == 0 ): raise PubNubException(pn_error=PNERR_PUBLISH_KEY_MISSING) def validate_file_object(self): if not self._file_object: raise PubNubException(pn_error=PNERR_FILE_OBJECT_MISSING) def validate_file_name(self): if not self._file_name: raise PubNubException(pn_error=PNERR_FILE_NAME_MISSING) def validate_file_id(self): if not self._file_id: raise PubNubException(pn_error=PNERR_FILE_ID_MISSING) def create_status(self, category, response, response_info, exception): if response_info is not None: assert isinstance(response_info, ResponseInfo) pn_status = PNStatus() if response is None or exception is not None: pn_status.error = True if response is not None: pn_status.original_response = response if exception is not None: pn_status.error_data = PNErrorData(str(exception), exception) if response_info is not None: pn_status.status_code = response_info.status_code pn_status.tls_enabled = response_info.tls_enabled pn_status.origin = response_info.origin pn_status.uuid = response_info.uuid pn_status.auth_key = response_info.auth_key pn_status.client_request = response_info.client_request pn_status.client_response = response_info.client_response pn_status.operation = self.operation_type() pn_status.category = category pn_status.affected_channels = self.affected_channels() pn_status.affected_groups = self.affected_channels_groups() return pn_status """ Used by asyncio and tornado clients to build exceptions The only difference with create_status() method is that a status is wrapped with an exception and also contains this exception inside as 'status.error_data.exception' """ def create_exception(self, category, response, response_info, exception): status = self.create_status(category, response, response_info, exception) exception.status = status return exception def __compress_request(self): return self.is_compressable() and self._use_compression bdraco-freenub-69809e8/pubnub/endpoints/entities/000077500000000000000000000000001464523203000220075ustar00rootroot00000000000000bdraco-freenub-69809e8/pubnub/endpoints/entities/__init__.py000066400000000000000000000000001464523203000241060ustar00rootroot00000000000000bdraco-freenub-69809e8/pubnub/endpoints/entities/endpoint.py000066400000000000000000000141021464523203000241770ustar00rootroot00000000000000import logging from abc import ABCMeta from pubnub import utils from pubnub.endpoints.endpoint import Endpoint from pubnub.errors import PNERR_SPACE_MISSING, PNERR_USER_ID_MISSING from pubnub.exceptions import PubNubException from pubnub.models.consumer.entities.page import Next, Previous logger = logging.getLogger("pubnub") class EntitiesEndpoint(Endpoint): __metaclass__ = ABCMeta def __init__(self, pubnub): Endpoint.__init__(self, pubnub) def is_auth_required(self): return True def connect_timeout(self): return self.pubnub.config.connect_timeout def request_timeout(self): return self.pubnub.config.non_subscribe_request_timeout def validate_params(self): self.validate_subscribe_key() self.validate_specific_params() def validate_specific_params(self): pass def encoded_params(self): params = {} if isinstance(self, ListEndpoint): if self._filter: params["filter"] = utils.url_encode(str(self._filter)) return params def custom_params(self): params = {} inclusions = [] if isinstance(self, IncludeCustomEndpoint): if self._include_custom: inclusions.append("custom") if isinstance(self, UserIDIncludeEndpoint): if self._uuid_details_level: if self._uuid_details_level == UserIDIncludeEndpoint.USER_ID: inclusions.append("user_id") elif ( self._uuid_details_level == UserIDIncludeEndpoint.USER_ID_WITH_CUSTOM ): inclusions.append("user_id.custom") if isinstance(self, SpaceIDIncludeEndpoint): if self._space_details_level: if self._space_details_level == SpaceIDIncludeEndpoint.CHANNEL: inclusions.append("space") elif ( self._space_details_level == SpaceIDIncludeEndpoint.CHANNEL_WITH_CUSTOM ): inclusions.append("space.custom") if isinstance(self, ListEndpoint): if self._filter: params["filter"] = str(self._filter) if self._limit: params["limit"] = int(self._limit) if self._include_total_count: params["count"] = bool(self._include_total_count) if self._sort_keys: joined_sort_params_array = [] for sort_key in self._sort_keys: joined_sort_params_array.append( f"{sort_key.key_str()}:{sort_key.dir_str()}" ) params["sort"] = ",".join(joined_sort_params_array) if self._page: if isinstance(self._page, Next): params["start"] = self._page.hash elif isinstance(self._page, Previous): params["end"] = self._page.hash else: raise ValueError() if len(inclusions) > 0: params["include"] = ",".join(inclusions) return params class CustomAwareEndpoint: __metaclass__ = ABCMeta def __init__(self): self._custom = None def custom(self, custom): self._custom = dict(custom) return self class SpaceEndpoint: __metaclass__ = ABCMeta def __init__(self): self._space_id = None def space_id(self, space): self._space_id = str(space) return self def _validate_space_id(self): if self._space_id is None or len(self._space_id) == 0: raise PubNubException(pn_error=PNERR_SPACE_MISSING) class UserEndpoint: __metaclass__ = ABCMeta def __init__(self): self._user_id = None def user_id(self, user_id): self._user_id = str(user_id) return self def _effective_user_id(self): if self._user_id is not None: return self._user_id else: return self.pubnub.config.user_id def _validate_user_id(self): if self._effective_user_id() is None or len(self._effective_user_id()) == 0: raise PubNubException(pn_error=PNERR_USER_ID_MISSING) class UsersEndpoint: __metaclass__ = ABCMeta def __init__(self): self._users = None def users(self, users): self._users = users return self class SpacesEndpoint: __metaclass__ = ABCMeta def __init__(self): self._spaces = None def spaces(self, spaces): self._spaces = spaces return self class ListEndpoint: __metaclass__ = ABCMeta def __init__(self): self._limit = None self._filter = None self._include_total_count = None self._sort_keys = None self._page = None def limit(self, limit): self._limit = int(limit) return self def filter(self, filter): self._filter = str(filter) return self def include_total_count(self, include_total_count): self._include_total_count = bool(include_total_count) return self def sort(self, *sort_keys): self._sort_keys = sort_keys return self def page(self, page): self._page = page return self class IncludeCustomEndpoint: __metaclass__ = ABCMeta def __init__(self): self._include_custom = None def include_custom(self, include_custom): self._include_custom = bool(include_custom) return self class UserIDIncludeEndpoint: __metaclass__ = ABCMeta USER_ID = 1 USER_ID_WITH_CUSTOM = 2 def __init__(self): self._user_id_details_level = None def include_user_id(self, user_id_details_level): self._user_id_details_level = user_id_details_level return self class SpaceIDIncludeEndpoint: __metaclass__ = ABCMeta SPACE = 1 SPACE_WITH_CUSTOM = 2 def __init__(self): self._space_details_level = None def include_space(self, space_details_level): self._space_details_level = space_details_level return self bdraco-freenub-69809e8/pubnub/endpoints/entities/membership/000077500000000000000000000000001464523203000241425ustar00rootroot00000000000000bdraco-freenub-69809e8/pubnub/endpoints/entities/membership/__init__.py000066400000000000000000000000001464523203000262410ustar00rootroot00000000000000bdraco-freenub-69809e8/pubnub/endpoints/entities/membership/add_memberships.py000066400000000000000000000063151464523203000276470ustar00rootroot00000000000000from pubnub import utils from pubnub.endpoints.entities.endpoint import ( EntitiesEndpoint, IncludeCustomEndpoint, SpaceEndpoint, SpacesEndpoint, UserEndpoint, UsersEndpoint, ) from pubnub.enums import HttpMethod, PNOperationType from pubnub.errors import ( PNERR_INVALID_SPACE, PNERR_INVALID_USER, PNERR_SPACE_MISSING, PNERR_USER_ID_MISSING, ) from pubnub.exceptions import PubNubException from pubnub.models.consumer.entities.membership import ( PNMembershipsResult, PNSpaceMembershipsResult, ) from pubnub.models.consumer.entities.space import Space from pubnub.models.consumer.entities.user import User class AddSpaceMembers( EntitiesEndpoint, SpaceEndpoint, UsersEndpoint, IncludeCustomEndpoint ): MEMBERSHIP_PATH = "/v2/objects/%s/channels/%s/uuids" def __init__(self, pubnub): EntitiesEndpoint.__init__(self, pubnub) IncludeCustomEndpoint.__init__(self) SpaceEndpoint.__init__(self) UsersEndpoint.__init__(self) def validate_specific_params(self): if self._space_id is None or len(self._space_id) == 0: raise PubNubException(pn_error=PNERR_SPACE_MISSING) self._users = list(self._users) if not all(isinstance(user, User) for user in self._users): raise PubNubException(pn_error=PNERR_INVALID_USER) def build_path(self): return AddSpaceMembers.MEMBERSHIP_PATH % ( self.pubnub.config.subscribe_key, self._space_id, ) def build_data(self): users = [user.to_payload_dict() for user in self._users] payload = {"set": users, "delete": []} return utils.write_value_as_string(payload) def create_response(self, envelope): return PNSpaceMembershipsResult(envelope) def operation_type(self): return PNOperationType.PNAddSpaceUsersOperation def name(self): return "Add Space Users" def http_method(self): return HttpMethod.PATCH class AddUserSpaces(EntitiesEndpoint, UserEndpoint, SpacesEndpoint): MEMBERSHIP_PATH = "/v2/objects/%s/uuids/%s/channels" def __init__(self, pubnub): EntitiesEndpoint.__init__(self, pubnub) UserEndpoint.__init__(self) SpacesEndpoint.__init__(self) def validate_specific_params(self): if self._user_id is None or len(self._user_id) == 0: raise PubNubException(pn_error=PNERR_USER_ID_MISSING) self._spaces = list(self._spaces) if not all(isinstance(space, Space) for space in self._spaces): raise PubNubException(pn_error=PNERR_INVALID_SPACE) def build_path(self): return AddUserSpaces.MEMBERSHIP_PATH % ( self.pubnub.config.subscribe_key, self._user_id, ) def build_data(self): spaces = [space.to_payload_dict() for space in self._spaces] payload = {"set": spaces, "delete": []} return utils.write_value_as_string(payload) def create_response(self, envelope): return PNMembershipsResult(envelope) def operation_type(self): return PNOperationType.PNAddUserSpacesOperation def name(self): return "Add User Spaces" def http_method(self): return HttpMethod.PATCH bdraco-freenub-69809e8/pubnub/endpoints/entities/membership/fetch_memberships.py000066400000000000000000000041121464523203000302010ustar00rootroot00000000000000from pubnub.endpoints.entities.endpoint import ( EntitiesEndpoint, IncludeCustomEndpoint, ListEndpoint, SpaceEndpoint, UserEndpoint, ) from pubnub.enums import HttpMethod, PNOperationType from pubnub.models.consumer.entities.membership import ( PNSpaceMembershipsResult, PNUserMembershipsResult, ) class FetchUserMemberships( EntitiesEndpoint, IncludeCustomEndpoint, UserEndpoint, ListEndpoint ): MEMBERSHIP_PATH = "/v2/objects/%s/uuids/%s/channels" def __init__(self, pubnub): EntitiesEndpoint.__init__(self, pubnub) ListEndpoint.__init__(self) IncludeCustomEndpoint.__init__(self) UserEndpoint.__init__(self) def build_path(self): return FetchUserMemberships.MEMBERSHIP_PATH % ( self.pubnub.config.subscribe_key, self._user_id, ) def validate_specific_params(self): self._validate_user_id() def create_response(self, envelope): return PNUserMembershipsResult(envelope) def operation_type(self): return PNOperationType.PNFetchUserMembershipsOperation def name(self): return "Fetch User Memberships" def http_method(self): return HttpMethod.GET class FetchSpaceMemberships(EntitiesEndpoint, IncludeCustomEndpoint, SpaceEndpoint): MEMBERSHIP_PATH = "/v2/objects/%s/channels/%s/uuids" def __init__(self, pubnub): EntitiesEndpoint.__init__(self, pubnub) ListEndpoint.__init__(self) IncludeCustomEndpoint.__init__(self) UserEndpoint.__init__(self) def build_path(self): return FetchSpaceMemberships.MEMBERSHIP_PATH % ( self.pubnub.config.subscribe_key, self._space_id, ) def validate_specific_params(self): self._validate_space_id() def create_response(self, envelope): return PNSpaceMembershipsResult(envelope) def operation_type(self): return PNOperationType.PNFetchSpaceMembershipsOperation def name(self): return "Fetch Space Memberships" def http_method(self): return HttpMethod.GET bdraco-freenub-69809e8/pubnub/endpoints/entities/membership/remove_memberships.py000066400000000000000000000061261464523203000304140ustar00rootroot00000000000000from pubnub import utils from pubnub.endpoints.entities.endpoint import ( EntitiesEndpoint, SpaceEndpoint, SpacesEndpoint, UserEndpoint, UsersEndpoint, ) from pubnub.enums import HttpMethod, PNOperationType from pubnub.errors import ( PNERR_INVALID_SPACE, PNERR_INVALID_USER, PNERR_SPACE_MISSING, PNERR_USER_ID_MISSING, ) from pubnub.exceptions import PubNubException from pubnub.models.consumer.entities.membership import PNMembershipsResult from pubnub.models.consumer.entities.space import Space from pubnub.models.consumer.entities.user import User class RemoveSpaceMembers(EntitiesEndpoint, SpaceEndpoint, UsersEndpoint): MEMBERSHIP_PATH = "/v2/objects/%s/uuids/%s/channels" def __init__(self, pubnub): EntitiesEndpoint.__init__(self, pubnub) SpaceEndpoint.__init__(self) UsersEndpoint.__init__(self) def validate_specific_params(self): if self._space_id is None or len(self._space_id) == 0: raise PubNubException(pn_error=PNERR_SPACE_MISSING) self._users = list(self._users) if not all(isinstance(user, User) for user in self._users): raise PubNubException(pn_error=PNERR_INVALID_USER) def build_path(self): return RemoveSpaceMembers.MEMBERSHIP_PATH % ( self.pubnub.config.subscribe_key, self.pubnub.uuid, ) def build_data(self): users = [user.to_payload_dict() for user in self._users] payload = {"set": [], "delete": users} return utils.write_value_as_string(payload) def create_response(self, envelope): return PNMembershipsResult(envelope) def operation_type(self): return PNOperationType.PNRemoveSpaceUsersOperation def name(self): return "Remove Space Users" def http_method(self): return HttpMethod.PATCH class RemoveUserSpaces(EntitiesEndpoint, UserEndpoint, SpacesEndpoint): MEMBERSHIP_PATH = "/v2/objects/%s/uuids/%s/channels" def __init__(self, pubnub): EntitiesEndpoint.__init__(self, pubnub) UserEndpoint.__init__(self) SpacesEndpoint.__init__(self) def validate_specific_params(self): if self._user_id is None or len(self._user_id) == 0: raise PubNubException(pn_error=PNERR_USER_ID_MISSING) self._spaces = list(self._spaces) if not all(isinstance(space, Space) for space in self._spaces): raise PubNubException(pn_error=PNERR_INVALID_SPACE) def build_path(self): return RemoveUserSpaces.MEMBERSHIP_PATH % ( self.pubnub.config.subscribe_key, self._user_id, ) def build_data(self): spaces = [space.to_payload_dict() for space in self._spaces] payload = {"set": [], "delete": spaces} return utils.write_value_as_string(payload) def create_response(self, envelope): return PNMembershipsResult(envelope) def operation_type(self): return PNOperationType.PNRemoveUserSpacesOperation def name(self): return "Remove User Spaces" def http_method(self): return HttpMethod.PATCH bdraco-freenub-69809e8/pubnub/endpoints/entities/membership/update_memberships.py000066400000000000000000000063451464523203000304040ustar00rootroot00000000000000from pubnub import utils from pubnub.endpoints.entities.endpoint import ( EntitiesEndpoint, IncludeCustomEndpoint, SpaceEndpoint, SpacesEndpoint, UserEndpoint, UsersEndpoint, ) from pubnub.enums import HttpMethod, PNOperationType from pubnub.errors import ( PNERR_INVALID_SPACE, PNERR_INVALID_USER, PNERR_SPACE_MISSING, PNERR_USER_ID_MISSING, ) from pubnub.exceptions import PubNubException from pubnub.models.consumer.entities.membership import ( PNMembershipsResult, PNSpaceMembershipsResult, ) from pubnub.models.consumer.entities.space import Space from pubnub.models.consumer.entities.user import User class UpdateSpaceMembers( EntitiesEndpoint, SpaceEndpoint, UsersEndpoint, IncludeCustomEndpoint ): MEMBERSHIP_PATH = "/v2/objects/%s/channels/%s/uuids" def __init__(self, pubnub): EntitiesEndpoint.__init__(self, pubnub) IncludeCustomEndpoint.__init__(self) SpaceEndpoint.__init__(self) UsersEndpoint.__init__(self) def validate_specific_params(self): if self._space_id is None or len(self._space_id) == 0: raise PubNubException(pn_error=PNERR_SPACE_MISSING) self._users = list(self._users) if not all(isinstance(user, User) for user in self._users): raise PubNubException(pn_error=PNERR_INVALID_USER) def build_path(self): return UpdateSpaceMembers.MEMBERSHIP_PATH % ( self.pubnub.config.subscribe_key, self._space_id, ) def build_data(self): users = [user.to_payload_dict() for user in self._users] payload = {"set": users, "delete": []} return utils.write_value_as_string(payload) def create_response(self, envelope): return PNSpaceMembershipsResult(envelope) def operation_type(self): return PNOperationType.PNUpdateSpaceUsersOperation def name(self): return "Update Space Users" def http_method(self): return HttpMethod.PATCH class UpdateUserSpaces(EntitiesEndpoint, UserEndpoint, SpacesEndpoint): MEMBERSHIP_PATH = "/v2/objects/%s/uuids/%s/channels" def __init__(self, pubnub): EntitiesEndpoint.__init__(self, pubnub) UserEndpoint.__init__(self) SpacesEndpoint.__init__(self) def validate_specific_params(self): if self._user_id is None or len(self._user_id) == 0: raise PubNubException(pn_error=PNERR_USER_ID_MISSING) self._spaces = list(self._spaces) if not all(isinstance(space, Space) for space in self._spaces): raise PubNubException(pn_error=PNERR_INVALID_SPACE) def build_path(self): return UpdateUserSpaces.MEMBERSHIP_PATH % ( self.pubnub.config.subscribe_key, self._user_id, ) def build_data(self): spaces = [space.to_payload_dict() for space in self._spaces] payload = {"set": spaces, "delete": []} return utils.write_value_as_string(payload) def create_response(self, envelope): return PNMembershipsResult(envelope) def operation_type(self): return PNOperationType.PNUpdateUserSpacesOperation def name(self): return "Update User Spaces" def http_method(self): return HttpMethod.PATCH bdraco-freenub-69809e8/pubnub/endpoints/entities/space/000077500000000000000000000000001464523203000231025ustar00rootroot00000000000000bdraco-freenub-69809e8/pubnub/endpoints/entities/space/__init__.py000066400000000000000000000000001464523203000252010ustar00rootroot00000000000000bdraco-freenub-69809e8/pubnub/endpoints/entities/space/create_space.py000066400000000000000000000040751464523203000261000ustar00rootroot00000000000000from pubnub.endpoints.entities.endpoint import ( CustomAwareEndpoint, EntitiesEndpoint, IncludeCustomEndpoint, SpaceEndpoint, ) from pubnub.enums import HttpMethod, PNOperationType from pubnub.models.consumer.entities.space import PNCreateSpaceResult from pubnub.utils import write_value_as_string class CreateSpace( EntitiesEndpoint, SpaceEndpoint, IncludeCustomEndpoint, CustomAwareEndpoint ): CREATE_SPACE_PATH = "/v2/objects/%s/channels/%s" def __init__(self, pubnub): EntitiesEndpoint.__init__(self, pubnub) SpaceEndpoint.__init__(self) CustomAwareEndpoint.__init__(self) IncludeCustomEndpoint.__init__(self) self._name = None self._description = None self._status = None self._type = None def space_status(self, space_status): self._status = space_status self._include_status = True return self def space_type(self, space_type): self._type = space_type self._include_type = True return self def set_name(self, name): self._name = str(name) return self def description(self, description): self._description = str(description) return self def validate_specific_params(self): self._validate_space_id() def build_path(self): return CreateSpace.CREATE_SPACE_PATH % ( self.pubnub.config.subscribe_key, self._space_id, ) def build_data(self): payload = { "name": self._name, "description": self._description, "custom": self._custom, } if self._status: payload["status"] = self._status if self._type: payload["type"] = self._type return write_value_as_string(payload) def create_response(self, envelope): return PNCreateSpaceResult(envelope) def operation_type(self): return PNOperationType.PNCreateSpaceOperation def name(self): return "Create space" def http_method(self): return HttpMethod.PATCH bdraco-freenub-69809e8/pubnub/endpoints/entities/space/fetch_space.py000066400000000000000000000020231464523203000257150ustar00rootroot00000000000000from pubnub.endpoints.entities.endpoint import ( EntitiesEndpoint, IncludeCustomEndpoint, SpaceEndpoint, ) from pubnub.enums import HttpMethod, PNOperationType from pubnub.models.consumer.entities.space import PNFetchSpaceResult class FetchSpace(EntitiesEndpoint, SpaceEndpoint, IncludeCustomEndpoint): FETCH_SPACE_PATH = "/v2/objects/%s/channels/%s" def __init__(self, pubnub): EntitiesEndpoint.__init__(self, pubnub) SpaceEndpoint.__init__(self) IncludeCustomEndpoint.__init__(self) def build_path(self): return FetchSpace.FETCH_SPACE_PATH % ( self.pubnub.config.subscribe_key, self._space_id, ) def validate_specific_params(self): self._validate_space_id() def create_response(self, envelope): return PNFetchSpaceResult(envelope) def operation_type(self): return PNOperationType.PNFetchSpaceOperation def name(self): return "Fetch Space" def http_method(self): return HttpMethod.GET bdraco-freenub-69809e8/pubnub/endpoints/entities/space/fetch_spaces.py000066400000000000000000000016711464523203000261100ustar00rootroot00000000000000from pubnub.endpoints.entities.endpoint import ( EntitiesEndpoint, IncludeCustomEndpoint, ListEndpoint, ) from pubnub.enums import HttpMethod, PNOperationType from pubnub.models.consumer.entities.space import PNFetchSpacesResult class FetchSpaces(EntitiesEndpoint, ListEndpoint, IncludeCustomEndpoint): FETCH_SPACES_PATH = "/v2/objects/%s/channels" inclusions = ["status", "type"] def __init__(self, pubnub): EntitiesEndpoint.__init__(self, pubnub) ListEndpoint.__init__(self) IncludeCustomEndpoint.__init__(self) def build_path(self): return FetchSpaces.FETCH_SPACES_PATH % self.pubnub.config.subscribe_key def create_response(self, envelope): return PNFetchSpacesResult(envelope) def operation_type(self): return PNOperationType.PNFetchSpacesOperation def name(self): return "Fetch Spaces" def http_method(self): return HttpMethod.GET bdraco-freenub-69809e8/pubnub/endpoints/entities/space/remove_space.py000066400000000000000000000016621464523203000261310ustar00rootroot00000000000000from pubnub.endpoints.entities.endpoint import EntitiesEndpoint, SpaceEndpoint from pubnub.enums import HttpMethod, PNOperationType from pubnub.models.consumer.entities.space import PNRemoveSpaceResult class RemoveSpace(EntitiesEndpoint, SpaceEndpoint): REMOVE_SPACE_PATH = "/v2/objects/%s/channels/%s" def __init__(self, pubnub): EntitiesEndpoint.__init__(self, pubnub) SpaceEndpoint.__init__(self) def build_path(self): return RemoveSpace.REMOVE_SPACE_PATH % ( self.pubnub.config.subscribe_key, self._space_id, ) def validate_specific_params(self): self._validate_space_id() def create_response(self, envelope): return PNRemoveSpaceResult(envelope) def operation_type(self): return PNOperationType.PNRemoveSpaceOperation def name(self): return "Remove Space" def http_method(self): return HttpMethod.DELETE bdraco-freenub-69809e8/pubnub/endpoints/entities/space/update_space.py000066400000000000000000000040751464523203000261170ustar00rootroot00000000000000from pubnub.endpoints.entities.endpoint import ( CustomAwareEndpoint, EntitiesEndpoint, IncludeCustomEndpoint, SpaceEndpoint, ) from pubnub.enums import HttpMethod, PNOperationType from pubnub.models.consumer.entities.space import PNUpdateSpaceResult from pubnub.utils import write_value_as_string class UpdateSpace( EntitiesEndpoint, SpaceEndpoint, IncludeCustomEndpoint, CustomAwareEndpoint ): UPDATE_SPACE_PATH = "/v2/objects/%s/channels/%s" def __init__(self, pubnub): EntitiesEndpoint.__init__(self, pubnub) SpaceEndpoint.__init__(self) CustomAwareEndpoint.__init__(self) IncludeCustomEndpoint.__init__(self) self._name = None self._description = None self._status = None self._type = None def space_status(self, space_status): self._status = space_status self._include_status = True return self def space_type(self, space_type): self._type = space_type self._include_type = True return self def set_name(self, name): self._name = str(name) return self def description(self, description): self._description = str(description) return self def validate_specific_params(self): self._validate_space_id() def build_path(self): return UpdateSpace.UPDATE_SPACE_PATH % ( self.pubnub.config.subscribe_key, self._space_id, ) def build_data(self): payload = { "name": self._name, "description": self._description, "custom": self._custom, } if self._status: payload["status"] = self._status if self._type: payload["type"] = self._type return write_value_as_string(payload) def create_response(self, envelope): return PNUpdateSpaceResult(envelope) def operation_type(self): return PNOperationType.PNUpdateSpaceOperation def name(self): return "Updatea space" def http_method(self): return HttpMethod.PATCH bdraco-freenub-69809e8/pubnub/endpoints/entities/user/000077500000000000000000000000001464523203000227655ustar00rootroot00000000000000bdraco-freenub-69809e8/pubnub/endpoints/entities/user/__init__.py000066400000000000000000000000001464523203000250640ustar00rootroot00000000000000bdraco-freenub-69809e8/pubnub/endpoints/entities/user/create_user.py000066400000000000000000000042731464523203000256460ustar00rootroot00000000000000from pubnub.endpoints.entities.endpoint import ( CustomAwareEndpoint, EntitiesEndpoint, IncludeCustomEndpoint, UserEndpoint, ) from pubnub.enums import HttpMethod, PNOperationType from pubnub.models.consumer.entities.user import PNCreateUserResult from pubnub.utils import write_value_as_string class CreateUser( EntitiesEndpoint, UserEndpoint, IncludeCustomEndpoint, CustomAwareEndpoint ): CREATE_USER_PATH = "/v2/objects/%s/uuids/%s" def __init__(self, pubnub): EntitiesEndpoint.__init__(self, pubnub) UserEndpoint.__init__(self) IncludeCustomEndpoint.__init__(self) CustomAwareEndpoint.__init__(self) self._name = None self._email = None self._external_id = None self._profile_url = None def user_status(self, user_status): self._status = user_status self._include_status = True return self def user_type(self, user_type): self._type = user_type self._include_type = True return self def set_name(self, name): self._name = str(name) return self def email(self, email): self._email = str(email) return self def external_id(self, external_id): self._external_id = str(external_id) return self def profile_url(self, profile_url): self._profile_url = str(profile_url) return self def build_path(self): return CreateUser.CREATE_USER_PATH % ( self.pubnub.config.subscribe_key, self._effective_user_id(), ) def build_data(self): payload = { "name": self._name, "email": self._email, "externalId": self._external_id, "profileUrl": self._profile_url, "custom": self._custom, } return write_value_as_string(payload) def validate_specific_params(self): self._validate_user_id() def create_response(self, envelope): return PNCreateUserResult(envelope) def operation_type(self): return PNOperationType.PNCreateUserOperation def name(self): return "Create User" def http_method(self): return HttpMethod.PATCH bdraco-freenub-69809e8/pubnub/endpoints/entities/user/fetch_user.py000066400000000000000000000020161464523203000254650ustar00rootroot00000000000000from pubnub.endpoints.entities.endpoint import ( EntitiesEndpoint, IncludeCustomEndpoint, UserEndpoint, ) from pubnub.enums import HttpMethod, PNOperationType from pubnub.models.consumer.entities.user import PNFetchUserResult class FetchUser(EntitiesEndpoint, UserEndpoint, IncludeCustomEndpoint): FETCH_USER_PATH = "/v2/objects/%s/uuids/%s" def __init__(self, pubnub): EntitiesEndpoint.__init__(self, pubnub) UserEndpoint.__init__(self) IncludeCustomEndpoint.__init__(self) def build_path(self): return FetchUser.FETCH_USER_PATH % ( self.pubnub.config.subscribe_key, self._effective_user_id(), ) def validate_specific_params(self): self._validate_user_id() def create_response(self, envelope): return PNFetchUserResult(envelope) def operation_type(self): return PNOperationType.PNFetchUserOperation def name(self): return "Fetch User" def http_method(self): return HttpMethod.GET bdraco-freenub-69809e8/pubnub/endpoints/entities/user/fetch_users.py000066400000000000000000000016111464523203000256500ustar00rootroot00000000000000from pubnub.endpoints.entities.endpoint import ( EntitiesEndpoint, IncludeCustomEndpoint, ListEndpoint, ) from pubnub.enums import HttpMethod, PNOperationType from pubnub.models.consumer.entities.user import PNFetchUsersResult class FetchUsers(EntitiesEndpoint, ListEndpoint, IncludeCustomEndpoint): FETCH_USERS_PATH = "/v2/objects/%s/uuids" def __init__(self, pubnub): EntitiesEndpoint.__init__(self, pubnub) ListEndpoint.__init__(self) IncludeCustomEndpoint.__init__(self) def build_path(self): return FetchUsers.FETCH_USERS_PATH % self.pubnub.config.subscribe_key def create_response(self, envelope): return PNFetchUsersResult(envelope) def operation_type(self): return PNOperationType.PNFetchUsersOperation def name(self): return "Fetch Users" def http_method(self): return HttpMethod.GET bdraco-freenub-69809e8/pubnub/endpoints/entities/user/remove_user.py000066400000000000000000000016551464523203000257010ustar00rootroot00000000000000from pubnub.endpoints.entities.endpoint import EntitiesEndpoint, UserEndpoint from pubnub.enums import HttpMethod, PNOperationType from pubnub.models.consumer.entities.user import PNRemoveUserResult class RemoveUser(EntitiesEndpoint, UserEndpoint): REMOVE_USER_PATH = "/v2/objects/%s/uuids/%s" def __init__(self, pubnub): EntitiesEndpoint.__init__(self, pubnub) UserEndpoint.__init__(self) def build_path(self): return RemoveUser.REMOVE_USER_PATH % ( self.pubnub.config.subscribe_key, self._effective_user_id(), ) def validate_specific_params(self): self._validate_user_id() def create_response(self, envelope): return PNRemoveUserResult(envelope) def operation_type(self): return PNOperationType.PNRemoveUserOperation def name(self): return "Remove User" def http_method(self): return HttpMethod.DELETE bdraco-freenub-69809e8/pubnub/endpoints/entities/user/update_user.py000066400000000000000000000042731464523203000256650ustar00rootroot00000000000000from pubnub.endpoints.entities.endpoint import ( CustomAwareEndpoint, EntitiesEndpoint, IncludeCustomEndpoint, UserEndpoint, ) from pubnub.enums import HttpMethod, PNOperationType from pubnub.models.consumer.entities.user import PNUpdateUserResult from pubnub.utils import write_value_as_string class UpdateUser( EntitiesEndpoint, UserEndpoint, IncludeCustomEndpoint, CustomAwareEndpoint ): UPDATE_USER_PATH = "/v2/objects/%s/uuids/%s" def __init__(self, pubnub): EntitiesEndpoint.__init__(self, pubnub) UserEndpoint.__init__(self) IncludeCustomEndpoint.__init__(self) CustomAwareEndpoint.__init__(self) self._name = None self._email = None self._external_id = None self._profile_url = None def user_status(self, user_status): self._status = user_status self._include_status = True return self def user_type(self, user_type): self._type = user_type self._include_type = True return self def set_name(self, name): self._name = str(name) return self def email(self, email): self._email = str(email) return self def external_id(self, external_id): self._external_id = str(external_id) return self def profile_url(self, profile_url): self._profile_url = str(profile_url) return self def build_path(self): return UpdateUser.UPDATE_USER_PATH % ( self.pubnub.config.subscribe_key, self._effective_user_id(), ) def build_data(self): payload = { "name": self._name, "email": self._email, "externalId": self._external_id, "profileUrl": self._profile_url, "custom": self._custom, } return write_value_as_string(payload) def validate_specific_params(self): self._validate_user_id() def create_response(self, envelope): return PNUpdateUserResult(envelope) def operation_type(self): return PNOperationType.PNUpdateUserOperation def name(self): return "Update User" def http_method(self): return HttpMethod.PATCH bdraco-freenub-69809e8/pubnub/endpoints/fetch_messages.py000066400000000000000000000140621464523203000235200ustar00rootroot00000000000000import logging from pubnub import utils from pubnub.endpoints.endpoint import Endpoint from pubnub.enums import HttpMethod, PNOperationType from pubnub.errors import ( PNERR_CHANNEL_MISSING, PNERR_HISTORY_MESSAGE_ACTIONS_MULTIPLE_CHANNELS, ) from pubnub.exceptions import PubNubException from pubnub.models.consumer.history import PNFetchMessagesResult logger = logging.getLogger("pubnub") class FetchMessages(Endpoint): FETCH_MESSAGES_PATH = "/v3/history/sub-key/%s/channel/%s" FETCH_MESSAGES_WITH_ACTIONS_PATH = "/v3/history-with-actions/sub-key/%s/channel/%s" SINGLE_CHANNEL_MAX_MESSAGES = 100 DEFAULT_SINGLE_CHANNEL_MESSAGES = 100 MULTIPLE_CHANNELS_MAX_MESSAGES = 25 DEFAULT_MULTIPLE_CHANNELS_MESSAGES = 25 MAX_MESSAGES_ACTIONS = 25 DEFAULT_MESSAGES_ACTIONS = 25 def __init__(self, pubnub): Endpoint.__init__(self, pubnub) self._channels = [] self._start = None self._end = None self._count = None self._include_meta = None self._include_message_actions = None self._include_message_type = None self._include_uuid = None def channels(self, channels): utils.extend_list(self._channels, channels) return self def count(self, count): assert isinstance(count, int) self._count = count return self def maximum_per_channel(self, maximum_per_channel): return self.count(maximum_per_channel) def start(self, start): assert isinstance(start, int) self._start = start return self def end(self, end): assert isinstance(end, int) self._end = end return self def include_meta(self, include_meta): assert isinstance(include_meta, bool) self._include_meta = include_meta return self def include_message_actions(self, include_message_actions): assert isinstance(include_message_actions, bool) self._include_message_actions = include_message_actions return self def include_message_type(self, include_message_type): assert isinstance(include_message_type, bool) self._include_message_type = include_message_type return self def include_uuid(self, include_uuid): assert isinstance(include_uuid, bool) self._include_uuid = include_uuid return self def custom_params(self): params = {"max": int(self._count)} if self._start is not None: params["start"] = str(self._start) if self._end is not None: params["end"] = str(self._end) if self._include_meta is not None: params["include_meta"] = "true" if self._include_meta else "false" if self._include_message_type is not None: params["include_message_type"] = ( "true" if self._include_message_type else "false" ) if self.include_message_actions and self._include_uuid is not None: params["include_uuid"] = "true" if self._include_uuid else "false" return params def build_path(self): if self._include_message_actions is False: return FetchMessages.FETCH_MESSAGES_PATH % ( self.pubnub.config.subscribe_key, utils.join_channels(self._channels), ) else: return FetchMessages.FETCH_MESSAGES_WITH_ACTIONS_PATH % ( self.pubnub.config.subscribe_key, utils.url_encode(self._channels[0]), ) def http_method(self): return HttpMethod.GET def is_auth_required(self): return True def validate_params(self): self.validate_subscribe_key() if self._channels is None or len(self._channels) == 0: raise PubNubException(pn_error=PNERR_CHANNEL_MISSING) if self._include_meta is None: self._include_meta = False if self._include_message_actions is None: self._include_message_actions = False if not self._include_message_actions: if len(self._channels) == 1: if self._count is None or self._count < 1: self._count = FetchMessages.DEFAULT_SINGLE_CHANNEL_MESSAGES logger.info("count param defaulting to %d", self._count) elif self._count > FetchMessages.SINGLE_CHANNEL_MAX_MESSAGES: self._count = FetchMessages.DEFAULT_SINGLE_CHANNEL_MESSAGES logger.info("count param defaulting to %d", self._count) else: if self._count is None or self._count < 1: self._count = FetchMessages.DEFAULT_MULTIPLE_CHANNELS_MESSAGES logger.info("count param defaulting to %d", self._count) elif self._count > FetchMessages.MULTIPLE_CHANNELS_MAX_MESSAGES: self._count = FetchMessages.DEFAULT_MULTIPLE_CHANNELS_MESSAGES logger.info("count param defaulting to %d", self._count) else: if len(self._channels) > 1: raise PubNubException( pn_error=PNERR_HISTORY_MESSAGE_ACTIONS_MULTIPLE_CHANNELS ) if ( self._count is None or self._count < 1 or self._count > FetchMessages.MAX_MESSAGES_ACTIONS ): self._count = FetchMessages.DEFAULT_MESSAGES_ACTIONS logger.info("count param defaulting to %d", self._count) def create_response(self, envelope): # pylint: disable=W0221 return PNFetchMessagesResult.from_json( json_input=envelope, include_message_actions=self._include_message_actions, start_timetoken=self._start, end_timetoken=self._end, ) def request_timeout(self): return self.pubnub.config.non_subscribe_request_timeout def connect_timeout(self): return self.pubnub.config.connect_timeout def operation_type(self): return PNOperationType.PNFetchMessagesOperation def name(self): return "Fetch messages" bdraco-freenub-69809e8/pubnub/endpoints/file_operations/000077500000000000000000000000001464523203000233455ustar00rootroot00000000000000bdraco-freenub-69809e8/pubnub/endpoints/file_operations/__init__.py000066400000000000000000000000001464523203000254440ustar00rootroot00000000000000bdraco-freenub-69809e8/pubnub/endpoints/file_operations/delete_file.py000066400000000000000000000026301464523203000261610ustar00rootroot00000000000000from pubnub import utils from pubnub.endpoints.file_operations.file_based_endpoint import FileOperationEndpoint from pubnub.enums import HttpMethod, PNOperationType from pubnub.models.consumer.file import PNDeleteFileResult class DeleteFile(FileOperationEndpoint): DELETE_FILE_URL = "/v1/files/%s/channels/%s/files/%s/%s" def __init__(self, pubnub): FileOperationEndpoint.__init__(self, pubnub) self._file_id = None self._file_name = None def build_path(self): return DeleteFile.DELETE_FILE_URL % ( self.pubnub.config.subscribe_key, utils.url_encode(self._channel), self._file_id, self._file_name, ) def file_id(self, file_id): self._file_id = file_id return self def file_name(self, file_name): self._file_name = file_name return self def http_method(self): return HttpMethod.DELETE def custom_params(self): return {} def is_auth_required(self): return True def validate_params(self): self.validate_subscribe_key() self.validate_channel() self.validate_file_name() self.validate_file_id() def create_response(self, envelope): return PNDeleteFileResult(envelope) def operation_type(self): return PNOperationType.PNDeleteFileOperation def name(self): return "Delete file" bdraco-freenub-69809e8/pubnub/endpoints/file_operations/download_file.py000066400000000000000000000050011464523203000265210ustar00rootroot00000000000000from pubnub.crypto import PubNubFileCrypto from pubnub.endpoints.file_operations.file_based_endpoint import FileOperationEndpoint from pubnub.endpoints.file_operations.get_file_url import GetFileDownloadUrl from pubnub.enums import HttpMethod, PNOperationType from pubnub.models.consumer.file import PNDownloadFileResult from pubnub.request_handlers.requests_handler import RequestsRequestHandler class DownloadFileNative(FileOperationEndpoint): def __init__(self, pubnub): FileOperationEndpoint.__init__(self, pubnub) self._file_id = None self._file_name = None self._pubnub = pubnub self._download_data = None self._cipher_key = None def cipher_key(self, cipher_key): self._cipher_key = cipher_key return self def build_path(self): return self._download_data.result.file_url def http_method(self): return HttpMethod.GET def is_auth_required(self): return False def custom_params(self): return {} def file_id(self, file_id): self._file_id = file_id return self def file_name(self, file_name): self._file_name = file_name return self def decrypt_payload(self, data): return PubNubFileCrypto(self._pubnub.config).decrypt( self._cipher_key or self._pubnub.config.cipher_key, data ) def validate_params(self): self.validate_subscribe_key() self.validate_channel() self.validate_file_name() self.validate_file_id() def create_response(self, envelope): if self._cipher_key or self._pubnub.config.cipher_key: return PNDownloadFileResult(self.decrypt_payload(envelope.content)) else: return PNDownloadFileResult(envelope.content) def non_json_response(self): return True def operation_type(self): return PNOperationType.PNDownloadFileAction def use_base_path(self): return False def build_params_callback(self): return lambda a: {} def name(self): return "Downloading file" def sync(self): self._download_data = ( GetFileDownloadUrl(self._pubnub) .channel(self._channel) .file_name(self._file_name) .file_id(self._file_id) .sync() ) return super().sync() def pn_async(self, callback): return RequestsRequestHandler(self._pubnub).async_file_based_operation( self.sync, callback, "File Download" ) bdraco-freenub-69809e8/pubnub/endpoints/file_operations/download_file_asyncio.py000066400000000000000000000016321464523203000302540ustar00rootroot00000000000000from pubnub.endpoints.file_operations.download_file import DownloadFileNative from pubnub.endpoints.file_operations.get_file_url import GetFileDownloadUrl from pubnub.models.consumer.file import PNDownloadFileResult class DownloadFileAsyncio(DownloadFileNative): def create_response(self, envelope, data=None): if self._cipher_key or self._pubnub.config.cipher_key: data = self.decrypt_payload(data) return PNDownloadFileResult(data) async def future(self): self._download_data = ( await GetFileDownloadUrl(self._pubnub) .channel(self._channel) .file_name(self._file_name) .file_id(self._file_id) .future() ) downloaded_file = await super().future() return downloaded_file async def result(self): response_envelope = await self.future() return response_envelope.result bdraco-freenub-69809e8/pubnub/endpoints/file_operations/fetch_upload_details.py000066400000000000000000000026441464523203000300670ustar00rootroot00000000000000from pubnub import utils from pubnub.endpoints.file_operations.file_based_endpoint import FileOperationEndpoint from pubnub.enums import HttpMethod, PNOperationType from pubnub.models.consumer.file import PNFetchFileUploadS3DataResult class FetchFileUploadS3Data(FileOperationEndpoint): GENERATE_FILE_UPLOAD_DATA = "/v1/files/%s/channels/%s/generate-upload-url" def __init__(self, pubnub): FileOperationEndpoint.__init__(self, pubnub) self._file_name = None def build_path(self): return FetchFileUploadS3Data.GENERATE_FILE_UPLOAD_DATA % ( self.pubnub.config.subscribe_key, utils.url_encode(self._channel), ) def build_data(self): params = {"name": self._file_name} return utils.write_value_as_string(params) def http_method(self): return HttpMethod.POST def custom_params(self): return {} def is_auth_required(self): return True def file_name(self, file_name): self._file_name = file_name return self def validate_params(self): self.validate_subscribe_key() self.validate_channel() self.validate_file_name() def create_response(self, envelope): return PNFetchFileUploadS3DataResult(envelope) def operation_type(self): return PNOperationType.PNFetchFileUploadS3DataAction def name(self): return "Fetch file upload S3 data" bdraco-freenub-69809e8/pubnub/endpoints/file_operations/file_based_endpoint.py000066400000000000000000000007041464523203000276750ustar00rootroot00000000000000from pubnub.endpoints.endpoint import Endpoint class FileOperationEndpoint(Endpoint): def __init__(self, pubnub): Endpoint.__init__(self, pubnub) self._channel = None def channel(self, channel): self._channel = channel return self def request_timeout(self): return self.pubnub.config.non_subscribe_request_timeout def connect_timeout(self): return self.pubnub.config.connect_timeout bdraco-freenub-69809e8/pubnub/endpoints/file_operations/get_file_url.py000066400000000000000000000037371464523203000263710ustar00rootroot00000000000000from pubnub import utils from pubnub.endpoints.file_operations.file_based_endpoint import FileOperationEndpoint from pubnub.enums import HttpMethod, PNOperationType from pubnub.models.consumer.file import PNGetFileDownloadURLResult class GetFileDownloadUrl(FileOperationEndpoint): GET_FILE_DOWNLOAD_URL = "/v1/files/%s/channels/%s/files/%s/%s" def __init__(self, pubnub, file_name=None, file_id=None): FileOperationEndpoint.__init__(self, pubnub) self._file_id = file_id self._file_name = file_name def build_path(self): return GetFileDownloadUrl.GET_FILE_DOWNLOAD_URL % ( self.pubnub.config.subscribe_key, utils.url_encode(self._channel), self._file_id, self._file_name, ) def get_complete_url(self): endpoint_options = self.options() endpoint_options.merge_params_in(self.custom_params()) query_params = "?" + endpoint_options.query_string return ( self.pubnub.config.scheme_extended() + self.pubnub.base_origin + self.build_path() + query_params ) def file_id(self, file_id): self._file_id = file_id return self def file_name(self, file_name): self._file_name = file_name return self def http_method(self): return HttpMethod.GET def custom_params(self): return {} def is_auth_required(self): return True def non_json_response(self): return True def validate_params(self): self.validate_subscribe_key() self.validate_channel() self.validate_file_id() self.validate_file_name() def create_response(self, envelope, data=None): return PNGetFileDownloadURLResult(envelope) def operation_type(self): return PNOperationType.PNGetFileDownloadURLAction def allow_redirects(self): return False def name(self): return "Get file download url" bdraco-freenub-69809e8/pubnub/endpoints/file_operations/list_files.py000066400000000000000000000020311464523203000260500ustar00rootroot00000000000000from pubnub import utils from pubnub.endpoints.file_operations.file_based_endpoint import FileOperationEndpoint from pubnub.enums import HttpMethod, PNOperationType from pubnub.models.consumer.file import PNGetFilesResult class ListFiles(FileOperationEndpoint): LIST_FILES_URL = "/v1/files/%s/channels/%s/files" def __init__(self, pubnub): FileOperationEndpoint.__init__(self, pubnub) def build_path(self): return ListFiles.LIST_FILES_URL % ( self.pubnub.config.subscribe_key, utils.url_encode(self._channel), ) def http_method(self): return HttpMethod.GET def custom_params(self): return {} def is_auth_required(self): return True def validate_params(self): self.validate_subscribe_key() self.validate_channel() def create_response(self, envelope): return PNGetFilesResult(envelope) def operation_type(self): return PNOperationType.PNGetFilesAction def name(self): return "List files" bdraco-freenub-69809e8/pubnub/endpoints/file_operations/publish_file_message.py000066400000000000000000000060361464523203000300750ustar00rootroot00000000000000from pubnub import utils from pubnub.endpoints.file_operations.file_based_endpoint import FileOperationEndpoint from pubnub.endpoints.mixins import TimeTokenOverrideMixin from pubnub.enums import HttpMethod, PNOperationType from pubnub.models.consumer.file import PNPublishFileMessageResult class PublishFileMessage(FileOperationEndpoint, TimeTokenOverrideMixin): PUBLISH_FILE_MESSAGE = "/v1/files/publish-file/%s/%s/0/%s/0/%s" def __init__(self, pubnub): super().__init__(pubnub) self._file_id = None self._file_name = None self._pubnub = pubnub self._message = None self._should_store = None self._ttl = 0 self._meta = None self._cipher_key = None self._replicate = None self._ptto = None def meta(self, meta): self._meta = meta return self def should_store(self, should_store): self._should_store = bool(should_store) return self def cipher_key(self, cipher_key): self._cipher_key = cipher_key return self def message(self, message): self._message = message return self def file_id(self, file_id): self._file_id = file_id return self def ttl(self, ttl): self._ttl = ttl return self def file_name(self, file_name): self._file_name = file_name return self def _encrypt_message(self, message): if self._cipher_key or self._pubnub.config.cipher_key: return self._pubnub.config.crypto.encrypt( self._cipher_key or self._pubnub.config.cipher_key, utils.write_value_as_string(message), ) else: return message def _build_message(self): message = { "message": self._message, "file": {"id": self._file_id, "name": self._file_name}, } return self._encrypt_message(message) def build_path(self): message = self._build_message() return PublishFileMessage.PUBLISH_FILE_MESSAGE % ( self.pubnub.config.publish_key, self.pubnub.config.subscribe_key, utils.url_encode(self._channel), utils.url_write(message), ) def http_method(self): return HttpMethod.GET def custom_params(self): params = TimeTokenOverrideMixin.custom_params(self) params.update( { "meta": utils.url_write(self._meta), "ttl": self._ttl, "store": 1 if self._should_store else 0, } ) return params def is_auth_required(self): return True def validate_params(self): self.validate_subscribe_key() self.validate_channel() self.validate_file_name() self.validate_file_id() def create_response(self, envelope): return PNPublishFileMessageResult(envelope) def operation_type(self): return PNOperationType.PNSendFileAction def name(self): return "Sending file upload notification" bdraco-freenub-69809e8/pubnub/endpoints/file_operations/send_file.py000066400000000000000000000107221464523203000256510ustar00rootroot00000000000000from pubnub.crypto import PubNubFileCrypto from pubnub.endpoints.file_operations.fetch_upload_details import FetchFileUploadS3Data from pubnub.endpoints.file_operations.file_based_endpoint import FileOperationEndpoint from pubnub.endpoints.file_operations.publish_file_message import PublishFileMessage from pubnub.endpoints.mixins import TimeTokenOverrideMixin from pubnub.enums import HttpMethod, PNOperationType from pubnub.models.consumer.file import PNSendFileResult from pubnub.request_handlers.requests_handler import RequestsRequestHandler class SendFileNative(FileOperationEndpoint, TimeTokenOverrideMixin): def __init__(self, pubnub): super().__init__(pubnub) self._file_name = None self._pubnub = pubnub self._file_upload_envelope = None self._message = None self._should_store = None self._ttl = 0 self._meta = None self._cipher_key = None self._file_object = None self._replicate = None self._ptto = None def file_object(self, fd): self._file_object = fd return self def build_params_callback(self): return lambda a: {} def build_path(self): return self._file_upload_envelope.result.data["url"] def encrypt_payload(self): if self._cipher_key or self._pubnub.config.cipher_key: try: payload = self._file_object.read() except AttributeError: payload = self._file_object return PubNubFileCrypto(self._pubnub.config).encrypt( self._cipher_key or self._pubnub.config.cipher_key, payload ) else: return self._file_object def build_file_upload_request(self): file = self.encrypt_payload() multipart_body = {} for form_field in self._file_upload_envelope.result.data["form_fields"]: multipart_body[form_field["key"]] = (None, form_field["value"]) multipart_body["file"] = (self._file_name, file, None) return multipart_body def http_method(self): return HttpMethod.POST def use_compression(self, compress=True): self._use_compression = bool(compress) return self def is_compressable(self): return True def custom_params(self): return {} def validate_params(self): self.validate_subscribe_key() self.validate_channel() self.validate_file_object() self.validate_file_name() def use_base_path(self): return False def non_json_response(self): return True def is_auth_required(self): return False def should_store(self, should_store): self._should_store = bool(should_store) return self def ttl(self, ttl): self._ttl = ttl return self def meta(self, meta): self._meta = meta return self def message(self, message): self._message = message return self def file_name(self, file_name): self._file_name = file_name return self def cipher_key(self, cipher_key): self._cipher_key = cipher_key return self def create_response(self, envelope, data=None): return PNSendFileResult(envelope, self._file_upload_envelope) def operation_type(self): return PNOperationType.PNSendFileAction def request_headers(self): return {} def name(self): return "Send file to S3" def sync(self): self._file_upload_envelope = ( FetchFileUploadS3Data(self._pubnub) .channel(self._channel) .file_name(self._file_name) .sync() ) response_envelope = super().sync() publish_file_response = ( PublishFileMessage(self._pubnub) .channel(self._channel) .meta(self._meta) .message(self._message) .file_id(response_envelope.result.file_id) .file_name(response_envelope.result.name) .should_store(self._should_store) .ttl(self._ttl) .replicate(self._replicate) .ptto(self._ptto) .cipher_key(self._cipher_key) .sync() ) response_envelope.result.timestamp = publish_file_response.result.timestamp return response_envelope def pn_async(self, callback): return RequestsRequestHandler(self._pubnub).async_file_based_operation( self.sync, callback, "File Download" ) bdraco-freenub-69809e8/pubnub/endpoints/file_operations/send_file_asyncio.py000066400000000000000000000037421464523203000274020ustar00rootroot00000000000000import aiohttp from pubnub.endpoints.file_operations.fetch_upload_details import FetchFileUploadS3Data from pubnub.endpoints.file_operations.publish_file_message import PublishFileMessage from pubnub.endpoints.file_operations.send_file import SendFileNative class AsyncioSendFile(SendFileNative): def build_file_upload_request(self): file = self.encrypt_payload() form_data = aiohttp.FormData() for form_field in self._file_upload_envelope.result.data["form_fields"]: form_data.add_field( form_field["key"], form_field["value"], content_type="multipart/form-data", ) form_data.add_field( "file", file, filename=self._file_name, content_type="application/octet-stream", ) return form_data def options(self): request_options = super(SendFileNative, self).options() request_options.data = request_options.files return request_options async def future(self): self._file_upload_envelope = ( await FetchFileUploadS3Data(self._pubnub) .channel(self._channel) .file_name(self._file_name) .future() ) response_envelope = await super(SendFileNative, self).future() publish_file_response = ( await PublishFileMessage(self._pubnub) .channel(self._channel) .meta(self._meta) .message(self._message) .file_id(response_envelope.result.file_id) .file_name(response_envelope.result.name) .should_store(self._should_store) .ttl(self._ttl) .cipher_key(self._cipher_key) .future() ) response_envelope.result.timestamp = publish_file_response.result.timestamp return response_envelope async def result(self): response_envelope = await self.future() return response_envelope.result bdraco-freenub-69809e8/pubnub/endpoints/history.py000066400000000000000000000062321464523203000222410ustar00rootroot00000000000000from pubnub import utils from pubnub.endpoints.endpoint import Endpoint from pubnub.enums import HttpMethod, PNOperationType from pubnub.models.consumer.history import PNHistoryResult class History(Endpoint): HISTORY_PATH = "/v2/history/sub-key/%s/channel/%s" MAX_COUNT = 100 def __init__(self, pubnub): Endpoint.__init__(self, pubnub) self._channel = None self._start = None self._end = None self._reverse = None self._count = None self._include_timetoken = None self._include_meta = None def channel(self, channel): self._channel = channel return self def start(self, start): assert isinstance(start, int) self._start = start return self def end(self, end): assert isinstance(end, int) self._end = end return self def reverse(self, reverse): assert isinstance(reverse, bool) self._reverse = reverse return self def count(self, count): assert isinstance(count, int) self._count = count return self def include_timetoken(self, include_timetoken): assert isinstance(include_timetoken, bool) self._include_timetoken = include_timetoken return self def include_meta(self, include_meta): assert isinstance(include_meta, bool) self._include_meta = include_meta return self def custom_params(self): params = {} if self._start is not None: params["start"] = str(self._start) if self._end is not None: params["end"] = str(self._end) if self._count is not None and 0 < self._count <= History.MAX_COUNT: params["count"] = str(self._count) else: params["count"] = "100" if self._reverse is not None: params["reverse"] = "true" if self._reverse else "false" if self._include_timetoken is not None: params["include_token"] = "true" if self._include_timetoken else "false" if self._include_meta is not None: params["include_meta"] = "true" if self._include_meta else "false" return params def build_path(self): return History.HISTORY_PATH % ( self.pubnub.config.subscribe_key, utils.url_encode(self._channel), ) def http_method(self): return HttpMethod.GET def is_auth_required(self): return True def validate_params(self): self.validate_subscribe_key() self.validate_channel() def create_response(self, envelope): return PNHistoryResult.from_json( json_input=envelope, crypto=self.pubnub.config.crypto, include_timetoken=self._include_timetoken, include_meta=self._include_meta, cipher=self.pubnub.config.cipher_key, ) def request_timeout(self): return self.pubnub.config.non_subscribe_request_timeout def connect_timeout(self): return self.pubnub.config.connect_timeout def operation_type(self): return PNOperationType.PNHistoryOperation def name(self): return "History" bdraco-freenub-69809e8/pubnub/endpoints/history_delete.py000066400000000000000000000031501464523203000235570ustar00rootroot00000000000000from pubnub import utils from pubnub.endpoints.endpoint import Endpoint from pubnub.enums import HttpMethod, PNOperationType class HistoryDelete(Endpoint): # pylint: disable=W0612 HISTORY_DELETE_PATH = "/v3/history/sub-key/%s/channel/%s" def __init__(self, pubnub): Endpoint.__init__(self, pubnub) self._channel = None self._start = None self._end = None def channel(self, channel): self._channel = channel return self def start(self, start): self._start = start return self def end(self, end): self._end = end return self def custom_params(self): params = {} if self._start is not None: params["start"] = str(self._start) if self._end is not None: params["end"] = str(self._end) return params def build_path(self): return HistoryDelete.HISTORY_DELETE_PATH % ( self.pubnub.config.subscribe_key, utils.url_encode(self._channel), ) def http_method(self): return HttpMethod.DELETE def is_auth_required(self): return True def validate_params(self): self.validate_subscribe_key() self.validate_channel() def create_response(self, endpoint): return {} def request_timeout(self): return self.pubnub.config.non_subscribe_request_timeout def connect_timeout(self): return self.pubnub.config.connect_timeout def operation_type(self): return PNOperationType.PNHistoryDeleteOperation def name(self): return "History delete" bdraco-freenub-69809e8/pubnub/endpoints/message_actions/000077500000000000000000000000001464523203000233275ustar00rootroot00000000000000bdraco-freenub-69809e8/pubnub/endpoints/message_actions/__init__.py000066400000000000000000000000001464523203000254260ustar00rootroot00000000000000bdraco-freenub-69809e8/pubnub/endpoints/message_actions/add_message_action.py000066400000000000000000000052001464523203000274670ustar00rootroot00000000000000from pubnub import utils from pubnub.endpoints.endpoint import Endpoint from pubnub.enums import HttpMethod, PNOperationType from pubnub.errors import ( PNERR_MESSAGE_ACTION_MISSING, PNERR_MESSAGE_ACTION_TYPE_MISSING, PNERR_MESSAGE_ACTION_VALUE_MISSING, PNERR_MESSAGE_TIMETOKEN_MISSING, ) from pubnub.exceptions import PubNubException from pubnub.models.consumer.message_actions import PNAddMessageActionResult class AddMessageAction(Endpoint): ADD_MESSAGE_ACTION_PATH = "/v1/message-actions/%s/channel/%s/message/%s" def __init__(self, pubnub): Endpoint.__init__(self, pubnub) self._channel = None self._message_action = None def channel(self, channel): self._channel = str(channel) return self def message_action(self, message_action): self._message_action = message_action return self def custom_params(self): return {} def build_data(self): params = { "type": self._message_action.type, "value": self._message_action.value, } return utils.write_value_as_string(params) def build_path(self): return AddMessageAction.ADD_MESSAGE_ACTION_PATH % ( self.pubnub.config.subscribe_key, utils.url_encode(self._channel), self._message_action.message_timetoken, ) def http_method(self): return HttpMethod.POST def validate_params(self): self.validate_subscribe_key() self.validate_channel() self.validate_message_action() def create_response(self, envelope): # pylint: disable=W0221 return PNAddMessageActionResult(envelope["data"]) def is_auth_required(self): return True def request_timeout(self): return self.pubnub.config.non_subscribe_request_timeout def connect_timeout(self): return self.pubnub.config.connect_timeout def operation_type(self): return PNOperationType.PNAddMessageAction def name(self): return "Add message action" def validate_message_action(self): if self._message_action is None: raise PubNubException(pn_error=PNERR_MESSAGE_ACTION_MISSING) if self._message_action.message_timetoken is None: raise PubNubException(pn_error=PNERR_MESSAGE_TIMETOKEN_MISSING) if self._message_action.type is None or len(self._message_action.type) == 0: raise PubNubException(pn_error=PNERR_MESSAGE_ACTION_TYPE_MISSING) if self._message_action.value is None or len(self._message_action.value) == 0: raise PubNubException(pn_error=PNERR_MESSAGE_ACTION_VALUE_MISSING) bdraco-freenub-69809e8/pubnub/endpoints/message_actions/get_message_actions.py000066400000000000000000000044271464523203000277130ustar00rootroot00000000000000from pubnub import utils from pubnub.endpoints.endpoint import Endpoint from pubnub.enums import HttpMethod, PNOperationType from pubnub.models.consumer.message_actions import ( PNGetMessageActionsResult, PNMessageAction, ) class GetMessageActions(Endpoint): GET_MESSAGE_ACTIONS_PATH = "/v1/message-actions/%s/channel/%s" MAX_LIMIT = 100 def __init__(self, pubnub): Endpoint.__init__(self, pubnub) self._channel = None self._start = None self._end = None self._limit = GetMessageActions.MAX_LIMIT def channel(self, channel): self._channel = str(channel) return self def start(self, start): assert isinstance(start, str) self._start = start return self def end(self, end): assert isinstance(end, str) self._end = end return self def limit(self, limit): assert isinstance(limit, str) self._limit = limit return self def custom_params(self): params = {} if self._start is not None: params["start"] = self._start if self._end is not None and self._start is None: params["end"] = self._end if self._limit != GetMessageActions.MAX_LIMIT: params["limit"] = self._limit return params def build_path(self): return GetMessageActions.GET_MESSAGE_ACTIONS_PATH % ( self.pubnub.config.subscribe_key, utils.url_encode(self._channel), ) def http_method(self): return HttpMethod.GET def is_auth_required(self): return True def validate_params(self): self.validate_subscribe_key() self.validate_channel() def create_response(self, envelope): # pylint: disable=W0221 result = envelope result["actions"] = [] for action in result["data"]: result["actions"].append(PNMessageAction(action)) return PNGetMessageActionsResult(result) def request_timeout(self): return self.pubnub.config.non_subscribe_request_timeout def connect_timeout(self): return self.pubnub.config.connect_timeout def operation_type(self): return PNOperationType.PNGetMessageActions def name(self): return "Get message actions" bdraco-freenub-69809e8/pubnub/endpoints/message_actions/remove_message_action.py000066400000000000000000000043051464523203000302410ustar00rootroot00000000000000from pubnub import utils from pubnub.endpoints.endpoint import Endpoint from pubnub.enums import HttpMethod, PNOperationType from pubnub.errors import ( PNERR_MESSAGE_ACTION_TIMETOKEN_MISSING, PNERR_MESSAGE_TIMETOKEN_MISSING, ) from pubnub.exceptions import PubNubException class RemoveMessageAction(Endpoint): REMOVE_MESSAGE_ACTION_PATH = ( "/v1/message-actions/%s/channel/%s/message/%s/action/%s" ) def __init__(self, pubnub): Endpoint.__init__(self, pubnub) self._channel = None self._message_timetoken = None self._action_timetoken = None def channel(self, channel): self._channel = str(channel) return self def message_timetoken(self, message_timetoken): self._message_timetoken = message_timetoken return self def action_timetoken(self, action_timetoken): self._action_timetoken = action_timetoken return self def custom_params(self): return {} def build_data(self): return None def build_path(self): return RemoveMessageAction.REMOVE_MESSAGE_ACTION_PATH % ( self.pubnub.config.subscribe_key, utils.url_encode(self._channel), self._message_timetoken, self._action_timetoken, ) def http_method(self): return HttpMethod.DELETE def validate_params(self): self.validate_subscribe_key() self.validate_channel() self.validate_timetokens() def create_response(self, envelope): # pylint: disable=W0221 return {} def is_auth_required(self): return True def request_timeout(self): return self.pubnub.config.non_subscribe_request_timeout def connect_timeout(self): return self.pubnub.config.connect_timeout def operation_type(self): return PNOperationType.PNDeleteMessageAction def name(self): return "Remove message action" def validate_timetokens(self): if self._message_timetoken is None: raise PubNubException(pn_error=PNERR_MESSAGE_TIMETOKEN_MISSING) if self._action_timetoken is None: raise PubNubException(pn_error=PNERR_MESSAGE_ACTION_TIMETOKEN_MISSING) bdraco-freenub-69809e8/pubnub/endpoints/message_count.py000066400000000000000000000041201464523203000233660ustar00rootroot00000000000000from pubnub import utils from pubnub.endpoints.endpoint import Endpoint from pubnub.enums import HttpMethod, PNOperationType from pubnub.exceptions import PubNubException from pubnub.models.consumer.message_count import PNMessageCountResult class MessageCount(Endpoint): MESSAGE_COUNT_PATH = "/v3/history/sub-key/%s/message-counts/%s" def __init__(self, pubnub): Endpoint.__init__(self, pubnub) self._channel = [] self._channels_timetoken = [] def channel(self, channel): utils.extend_list(self._channel, channel) return self def channel_timetokens(self, timetokens): timetokens = [str(item) for item in timetokens] utils.extend_list(self._channels_timetoken, timetokens) return self def custom_params(self): params = {} if len(self._channels_timetoken) > 0: if len(self._channels_timetoken) > 1: params["channelsTimetoken"] = utils.join_items(self._channels_timetoken) else: params["timetoken"] = self._channels_timetoken[0] return params def build_path(self): return MessageCount.MESSAGE_COUNT_PATH % ( self.pubnub.config.subscribe_key, utils.join_channels(self._channel), ) def http_method(self): return HttpMethod.GET def is_auth_required(self): return True def validate_params(self): self.validate_subscribe_key() self.validate_channel() if len(self._channels_timetoken) != len(self._channel): raise PubNubException( "The number of channels and the number of timetokens do not match." ) def create_response(self, result): # pylint: disable=W0221 return PNMessageCountResult(result) def request_timeout(self): return self.pubnub.config.non_subscribe_request_timeout def connect_timeout(self): return self.pubnub.config.connect_timeout def operation_type(self): return PNOperationType.PNMessageCountOperation def name(self): return "Message Count" bdraco-freenub-69809e8/pubnub/endpoints/mixins.py000066400000000000000000000015421464523203000220460ustar00rootroot00000000000000from pubnub.errors import PNERR_UUID_MISSING from pubnub.exceptions import PubNubException class UUIDValidatorMixin: def validate_uuid(self): if self._uuid is None or not isinstance(self._uuid, str): raise PubNubException(pn_error=PNERR_UUID_MISSING) class TimeTokenOverrideMixin: def replicate(self, replicate): self._replicate = replicate return self def ptto(self, timetoken): if timetoken: assert isinstance(timetoken, int) self._ptto = timetoken return self def custom_params(self): params = {} if self._replicate is not None: if self._replicate: params["norep"] = "false" else: params["norep"] = "true" if self._ptto: params["ptto"] = self._ptto return params bdraco-freenub-69809e8/pubnub/endpoints/objects_v2/000077500000000000000000000000001464523203000222235ustar00rootroot00000000000000bdraco-freenub-69809e8/pubnub/endpoints/objects_v2/__init__.py000066400000000000000000000000001464523203000243220ustar00rootroot00000000000000bdraco-freenub-69809e8/pubnub/endpoints/objects_v2/channel/000077500000000000000000000000001464523203000236335ustar00rootroot00000000000000bdraco-freenub-69809e8/pubnub/endpoints/objects_v2/channel/__init__.py000066400000000000000000000000001464523203000257320ustar00rootroot00000000000000bdraco-freenub-69809e8/pubnub/endpoints/objects_v2/channel/get_all_channels.py000066400000000000000000000017201464523203000274670ustar00rootroot00000000000000from pubnub.endpoints.objects_v2.objects_endpoint import ( IncludeCustomEndpoint, ListEndpoint, ObjectsEndpoint, ) from pubnub.enums import HttpMethod, PNOperationType from pubnub.models.consumer.objects_v2.channel import PNGetAllChannelMetadataResult class GetAllChannels(ObjectsEndpoint, ListEndpoint, IncludeCustomEndpoint): GET_ALL_CHANNELS_PATH = "/v2/objects/%s/channels" def __init__(self, pubnub): ObjectsEndpoint.__init__(self, pubnub) ListEndpoint.__init__(self) IncludeCustomEndpoint.__init__(self) def build_path(self): return GetAllChannels.GET_ALL_CHANNELS_PATH % self.pubnub.config.subscribe_key def create_response(self, envelope): return PNGetAllChannelMetadataResult(envelope) def operation_type(self): return PNOperationType.PNGetAllChannelMetadataOperation def name(self): return "Get all Channels" def http_method(self): return HttpMethod.GET bdraco-freenub-69809e8/pubnub/endpoints/objects_v2/channel/get_channel.py000066400000000000000000000020721464523203000264550ustar00rootroot00000000000000from pubnub.endpoints.objects_v2.objects_endpoint import ( ChannelEndpoint, IncludeCustomEndpoint, ObjectsEndpoint, ) from pubnub.enums import HttpMethod, PNOperationType from pubnub.models.consumer.objects_v2.channel import PNGetChannelMetadataResult class GetChannel(ObjectsEndpoint, ChannelEndpoint, IncludeCustomEndpoint): GET_CHANNEL_PATH = "/v2/objects/%s/channels/%s" def __init__(self, pubnub): ObjectsEndpoint.__init__(self, pubnub) ChannelEndpoint.__init__(self) IncludeCustomEndpoint.__init__(self) def build_path(self): return GetChannel.GET_CHANNEL_PATH % ( self.pubnub.config.subscribe_key, self._channel, ) def validate_specific_params(self): self._validate_channel() def create_response(self, envelope): return PNGetChannelMetadataResult(envelope) def operation_type(self): return PNOperationType.PNGetChannelMetadataOperation def name(self): return "Get Channel" def http_method(self): return HttpMethod.GET bdraco-freenub-69809e8/pubnub/endpoints/objects_v2/channel/remove_channel.py000066400000000000000000000017661464523203000272040ustar00rootroot00000000000000from pubnub.endpoints.objects_v2.objects_endpoint import ( ChannelEndpoint, ObjectsEndpoint, ) from pubnub.enums import HttpMethod, PNOperationType from pubnub.models.consumer.objects_v2.channel import PNRemoveChannelMetadataResult class RemoveChannel(ObjectsEndpoint, ChannelEndpoint): REMOVE_CHANNEL_PATH = "/v2/objects/%s/channels/%s" def __init__(self, pubnub): ObjectsEndpoint.__init__(self, pubnub) ChannelEndpoint.__init__(self) def build_path(self): return RemoveChannel.REMOVE_CHANNEL_PATH % ( self.pubnub.config.subscribe_key, self._channel, ) def validate_specific_params(self): self._validate_channel() def create_response(self, envelope): return PNRemoveChannelMetadataResult(envelope) def operation_type(self): return PNOperationType.PNRemoveChannelMetadataOperation def name(self): return "Remove Channel" def http_method(self): return HttpMethod.DELETE bdraco-freenub-69809e8/pubnub/endpoints/objects_v2/channel/set_channel.py000066400000000000000000000032101464523203000264640ustar00rootroot00000000000000from pubnub import utils from pubnub.endpoints.objects_v2.objects_endpoint import ( ChannelEndpoint, CustomAwareEndpoint, IncludeCustomEndpoint, ObjectsEndpoint, ) from pubnub.enums import HttpMethod, PNOperationType from pubnub.models.consumer.objects_v2.channel import PNSetChannelMetadataResult class SetChannel( ObjectsEndpoint, ChannelEndpoint, IncludeCustomEndpoint, CustomAwareEndpoint ): SET_CHANNEL_PATH = "/v2/objects/%s/channels/%s" def __init__(self, pubnub): ObjectsEndpoint.__init__(self, pubnub) ChannelEndpoint.__init__(self) CustomAwareEndpoint.__init__(self) IncludeCustomEndpoint.__init__(self) self._name = None self._description = None def set_name(self, name): self._name = str(name) return self def description(self, description): self._description = str(description) return self def validate_specific_params(self): self._validate_channel() def build_path(self): return SetChannel.SET_CHANNEL_PATH % ( self.pubnub.config.subscribe_key, self._channel, ) def build_data(self): payload = { "name": self._name, "description": self._description, "custom": self._custom, } return utils.write_value_as_string(payload) def create_response(self, envelope): return PNSetChannelMetadataResult(envelope) def operation_type(self): return PNOperationType.PNSetChannelMetadataOperation def name(self): return "Set UUID" def http_method(self): return HttpMethod.PATCH bdraco-freenub-69809e8/pubnub/endpoints/objects_v2/members/000077500000000000000000000000001464523203000236555ustar00rootroot00000000000000bdraco-freenub-69809e8/pubnub/endpoints/objects_v2/members/__init__.py000066400000000000000000000000001464523203000257540ustar00rootroot00000000000000bdraco-freenub-69809e8/pubnub/endpoints/objects_v2/members/get_channel_members.py000066400000000000000000000024371464523203000302160ustar00rootroot00000000000000from pubnub.endpoints.objects_v2.objects_endpoint import ( ChannelEndpoint, IncludeCustomEndpoint, ListEndpoint, ObjectsEndpoint, UUIDIncludeEndpoint, ) from pubnub.enums import HttpMethod, PNOperationType from pubnub.models.consumer.objects_v2.channel_members import PNGetChannelMembersResult class GetChannelMembers( ObjectsEndpoint, ChannelEndpoint, ListEndpoint, IncludeCustomEndpoint, UUIDIncludeEndpoint, ): GET_CHANNEL_MEMBERS_PATH = "/v2/objects/%s/channels/%s/uuids" def __init__(self, pubnub): ObjectsEndpoint.__init__(self, pubnub) ChannelEndpoint.__init__(self) ListEndpoint.__init__(self) IncludeCustomEndpoint.__init__(self) UUIDIncludeEndpoint.__init__(self) def build_path(self): return GetChannelMembers.GET_CHANNEL_MEMBERS_PATH % ( self.pubnub.config.subscribe_key, self._channel, ) def validate_specific_params(self): self._validate_channel() def create_response(self, envelope): return PNGetChannelMembersResult(envelope) def operation_type(self): return PNOperationType.PNGetChannelMembersOperation def name(self): return "Get Channel Members" def http_method(self): return HttpMethod.GET bdraco-freenub-69809e8/pubnub/endpoints/objects_v2/members/manage_channel_members.py000066400000000000000000000040141464523203000306600ustar00rootroot00000000000000from pubnub import utils from pubnub.endpoints.objects_v2.objects_endpoint import ( ChannelEndpoint, IncludeCustomEndpoint, ListEndpoint, ObjectsEndpoint, UUIDIncludeEndpoint, ) from pubnub.enums import HttpMethod, PNOperationType from pubnub.models.consumer.objects_v2.channel_members import ( PNManageChannelMembersResult, ) class ManageChannelMembers( ObjectsEndpoint, ChannelEndpoint, ListEndpoint, IncludeCustomEndpoint, UUIDIncludeEndpoint, ): MANAGE_CHANNELS_MEMBERS_PATH = "/v2/objects/%s/channels/%s/uuids" def __init__(self, pubnub): ObjectsEndpoint.__init__(self, pubnub) ChannelEndpoint.__init__(self) ListEndpoint.__init__(self) IncludeCustomEndpoint.__init__(self) UUIDIncludeEndpoint.__init__(self) self._uuids_to_set = [] self._uuids_to_remove = [] def set(self, uuids_to_set): self._uuids_to_set = list(uuids_to_set) return self def remove(self, uuids_to_remove): self._uuids_to_remove = list(uuids_to_remove) return self def validate_specific_params(self): self._validate_channel() def build_path(self): return ManageChannelMembers.MANAGE_CHANNELS_MEMBERS_PATH % ( self.pubnub.config.subscribe_key, self._channel, ) def build_data(self): uuids_to_set = [] uuids_to_remove = [] for uuid in self._uuids_to_set: uuids_to_set.append(uuid.to_payload_dict()) for uuid in self._uuids_to_remove: uuids_to_remove.append(uuid.to_payload_dict()) payload = {"set": uuids_to_set, "delete": uuids_to_remove} return utils.write_value_as_string(payload) def create_response(self, envelope): return PNManageChannelMembersResult(envelope) def operation_type(self): return PNOperationType.PNManageChannelMembersOperation def name(self): return "Manage Channels Members" def http_method(self): return HttpMethod.PATCH bdraco-freenub-69809e8/pubnub/endpoints/objects_v2/members/remove_channel_members.py000066400000000000000000000033131464523203000307260ustar00rootroot00000000000000from pubnub import utils from pubnub.endpoints.objects_v2.objects_endpoint import ( ChannelEndpoint, IncludeCustomEndpoint, ListEndpoint, ObjectsEndpoint, UUIDIncludeEndpoint, ) from pubnub.enums import HttpMethod, PNOperationType from pubnub.models.consumer.objects_v2.channel_members import ( PNRemoveChannelMembersResult, ) class RemoveChannelMembers( ObjectsEndpoint, ChannelEndpoint, ListEndpoint, IncludeCustomEndpoint, UUIDIncludeEndpoint, ): REMOVE_CHANNEL_MEMBERS_PATH = "/v2/objects/%s/channels/%s/uuids" def __init__(self, pubnub): ObjectsEndpoint.__init__(self, pubnub) ListEndpoint.__init__(self) ChannelEndpoint.__init__(self) IncludeCustomEndpoint.__init__(self) UUIDIncludeEndpoint.__init__(self) self._uuids = [] def uuids(self, uuids): self._uuids = list(uuids) return self def build_path(self): return RemoveChannelMembers.REMOVE_CHANNEL_MEMBERS_PATH % ( self.pubnub.config.subscribe_key, self._channel, ) def build_data(self): uuids_to_delete = [] for uuid in self._uuids: uuids_to_delete.append(uuid.to_payload_dict()) payload = {"set": [], "delete": uuids_to_delete} return utils.write_value_as_string(payload) def validate_specific_params(self): self._validate_channel() def create_response(self, envelope): return PNRemoveChannelMembersResult(envelope) def operation_type(self): return PNOperationType.PNRemoveChannelMembersOperation def name(self): return "Remove Channel Members" def http_method(self): return HttpMethod.PATCH bdraco-freenub-69809e8/pubnub/endpoints/objects_v2/members/set_channel_members.py000066400000000000000000000032411464523203000302240ustar00rootroot00000000000000from pubnub import utils from pubnub.endpoints.objects_v2.objects_endpoint import ( ChannelEndpoint, IncludeCustomEndpoint, ListEndpoint, ObjectsEndpoint, UUIDIncludeEndpoint, ) from pubnub.enums import HttpMethod, PNOperationType from pubnub.models.consumer.objects_v2.channel_members import PNSetChannelMembersResult class SetChannelMembers( ObjectsEndpoint, ChannelEndpoint, ListEndpoint, IncludeCustomEndpoint, UUIDIncludeEndpoint, ): SET_CHANNEL_MEMBERS_PATH = "/v2/objects/%s/channels/%s/uuids" def __init__(self, pubnub): ObjectsEndpoint.__init__(self, pubnub) ListEndpoint.__init__(self) ChannelEndpoint.__init__(self) IncludeCustomEndpoint.__init__(self) UUIDIncludeEndpoint.__init__(self) self._uuids = [] def uuids(self, uuids): self._uuids = list(uuids) return self def validate_specific_params(self): self._validate_channel() def build_path(self): return SetChannelMembers.SET_CHANNEL_MEMBERS_PATH % ( self.pubnub.config.subscribe_key, self._channel, ) def build_data(self): uuids_to_set = [] for uuid in self._uuids: uuids_to_set.append(uuid.to_payload_dict()) payload = {"set": uuids_to_set, "delete": []} return utils.write_value_as_string(payload) def create_response(self, envelope): return PNSetChannelMembersResult(envelope) def operation_type(self): return PNOperationType.PNSetChannelMembersOperation def name(self): return "Set Channel Members" def http_method(self): return HttpMethod.PATCH bdraco-freenub-69809e8/pubnub/endpoints/objects_v2/memberships/000077500000000000000000000000001464523203000245415ustar00rootroot00000000000000bdraco-freenub-69809e8/pubnub/endpoints/objects_v2/memberships/__init__.py000066400000000000000000000000001464523203000266400ustar00rootroot00000000000000bdraco-freenub-69809e8/pubnub/endpoints/objects_v2/memberships/get_memberships.py000066400000000000000000000024061464523203000302720ustar00rootroot00000000000000from pubnub.endpoints.objects_v2.objects_endpoint import ( ChannelIncludeEndpoint, IncludeCustomEndpoint, ListEndpoint, ObjectsEndpoint, UuidEndpoint, ) from pubnub.enums import HttpMethod, PNOperationType from pubnub.models.consumer.objects_v2.memberships import PNGetMembershipsResult class GetMemberships( ObjectsEndpoint, UuidEndpoint, ListEndpoint, IncludeCustomEndpoint, ChannelIncludeEndpoint, ): GET_MEMBERSHIPS_PATH = "/v2/objects/%s/uuids/%s/channels" def __init__(self, pubnub): ObjectsEndpoint.__init__(self, pubnub) UuidEndpoint.__init__(self) ListEndpoint.__init__(self) IncludeCustomEndpoint.__init__(self) ChannelIncludeEndpoint.__init__(self) def build_path(self): return GetMemberships.GET_MEMBERSHIPS_PATH % ( self.pubnub.config.subscribe_key, self._effective_uuid(), ) def validate_specific_params(self): self._validate_uuid() def create_response(self, envelope): return PNGetMembershipsResult(envelope) def operation_type(self): return PNOperationType.PNGetMembershipsOperation def name(self): return "Get Memberships" def http_method(self): return HttpMethod.GET bdraco-freenub-69809e8/pubnub/endpoints/objects_v2/memberships/manage_memberships.py000066400000000000000000000044421464523203000307450ustar00rootroot00000000000000from pubnub import utils from pubnub.endpoints.objects_v2.objects_endpoint import ( ChannelIncludeEndpoint, IncludeCustomEndpoint, ListEndpoint, ObjectsEndpoint, UuidEndpoint, ) from pubnub.enums import HttpMethod, PNOperationType from pubnub.models.consumer.objects_v2.memberships import PNManageMembershipsResult class ManageMemberships( ObjectsEndpoint, UuidEndpoint, ListEndpoint, IncludeCustomEndpoint, ChannelIncludeEndpoint, ): MANAGE_MEMBERSHIPS_PATH = "/v2/objects/%s/uuids/%s/channels" def __init__(self, pubnub): ObjectsEndpoint.__init__(self, pubnub) UuidEndpoint.__init__(self) ListEndpoint.__init__(self) IncludeCustomEndpoint.__init__(self) ChannelIncludeEndpoint.__init__(self) self._channel_memberships_to_set = [] self._channel_memberships_to_remove = [] def set(self, channel_memberships_to_set): self._channel_memberships_to_set = list(channel_memberships_to_set) return self def remove(self, channel_memberships_to_remove): self._channel_memberships_to_remove = list(channel_memberships_to_remove) return self def validate_specific_params(self): self._validate_uuid() def build_path(self): return ManageMemberships.MANAGE_MEMBERSHIPS_PATH % ( self.pubnub.config.subscribe_key, self._effective_uuid(), ) def build_data(self): channel_memberships_to_set = [] channel_memberships_to_remove = [] for channel_membership in self._channel_memberships_to_set: channel_memberships_to_set.append(channel_membership.to_payload_dict()) for channel_membership in self._channel_memberships_to_remove: channel_memberships_to_remove.append(channel_membership.to_payload_dict()) payload = { "set": channel_memberships_to_set, "delete": channel_memberships_to_remove, } return utils.write_value_as_string(payload) def create_response(self, envelope): return PNManageMembershipsResult(envelope) def operation_type(self): return PNOperationType.PNManageMembershipsOperation def name(self): return "Manage Memberships" def http_method(self): return HttpMethod.PATCH bdraco-freenub-69809e8/pubnub/endpoints/objects_v2/memberships/remove_memberships.py000066400000000000000000000035031464523203000310070ustar00rootroot00000000000000from pubnub import utils from pubnub.endpoints.objects_v2.objects_endpoint import ( ChannelIncludeEndpoint, IncludeCustomEndpoint, ListEndpoint, ObjectsEndpoint, UuidEndpoint, ) from pubnub.enums import HttpMethod, PNOperationType from pubnub.models.consumer.objects_v2.memberships import PNRemoveMembershipsResult class RemoveMemberships( ObjectsEndpoint, UuidEndpoint, ListEndpoint, IncludeCustomEndpoint, ChannelIncludeEndpoint, ): REMOVE_MEMBERSHIPS_PATH = "/v2/objects/%s/uuids/%s/channels" def __init__(self, pubnub): ObjectsEndpoint.__init__(self, pubnub) ListEndpoint.__init__(self) UuidEndpoint.__init__(self) IncludeCustomEndpoint.__init__(self) ChannelIncludeEndpoint.__init__(self) self._channel_memberships = [] def channel_memberships(self, channel_memberships): self._channel_memberships = list(channel_memberships) return self def build_path(self): return RemoveMemberships.REMOVE_MEMBERSHIPS_PATH % ( self.pubnub.config.subscribe_key, self._effective_uuid(), ) def build_data(self): channel_memberships_to_delete = [] for channel_membership in self._channel_memberships: channel_memberships_to_delete.append(channel_membership.to_payload_dict()) payload = {"set": [], "delete": channel_memberships_to_delete} return utils.write_value_as_string(payload) def validate_specific_params(self): self._validate_uuid() def create_response(self, envelope): return PNRemoveMembershipsResult(envelope) def operation_type(self): return PNOperationType.PNRemoveMembershipsOperation def name(self): return "Remove Memberships" def http_method(self): return HttpMethod.PATCH bdraco-freenub-69809e8/pubnub/endpoints/objects_v2/memberships/set_memberships.py000066400000000000000000000034401464523203000303050ustar00rootroot00000000000000from pubnub import utils from pubnub.endpoints.objects_v2.objects_endpoint import ( ChannelIncludeEndpoint, IncludeCustomEndpoint, ListEndpoint, ObjectsEndpoint, UuidEndpoint, ) from pubnub.enums import HttpMethod, PNOperationType from pubnub.models.consumer.objects_v2.memberships import PNSetMembershipsResult class SetMemberships( ObjectsEndpoint, ListEndpoint, IncludeCustomEndpoint, ChannelIncludeEndpoint, UuidEndpoint, ): SET_MEMBERSHIP_PATH = "/v2/objects/%s/uuids/%s/channels" def __init__(self, pubnub): ObjectsEndpoint.__init__(self, pubnub) UuidEndpoint.__init__(self) ListEndpoint.__init__(self) IncludeCustomEndpoint.__init__(self) ChannelIncludeEndpoint.__init__(self) self._channel_memberships = [] def channel_memberships(self, channel_memberships): self._channel_memberships = list(channel_memberships) return self def validate_specific_params(self): self._validate_uuid() def build_path(self): return SetMemberships.SET_MEMBERSHIP_PATH % ( self.pubnub.config.subscribe_key, self._effective_uuid(), ) def build_data(self): channel_memberships_to_set = [] for channel_membership in self._channel_memberships: channel_memberships_to_set.append(channel_membership.to_payload_dict()) payload = {"set": channel_memberships_to_set, "delete": []} return utils.write_value_as_string(payload) def create_response(self, envelope): return PNSetMembershipsResult(envelope) def operation_type(self): return PNOperationType.PNSetMembershipsOperation def name(self): return "Set Memberships" def http_method(self): return HttpMethod.PATCH bdraco-freenub-69809e8/pubnub/endpoints/objects_v2/objects_endpoint.py000066400000000000000000000131421464523203000261270ustar00rootroot00000000000000import logging from abc import ABCMeta from pubnub import utils from pubnub.endpoints.endpoint import Endpoint from pubnub.errors import PNERR_CHANNEL_MISSING, PNERR_UUID_MISSING from pubnub.exceptions import PubNubException from pubnub.models.consumer.objects_v2.page import Next, Previous logger = logging.getLogger("pubnub") class ObjectsEndpoint(Endpoint): __metaclass__ = ABCMeta def __init__(self, pubnub): Endpoint.__init__(self, pubnub) def is_auth_required(self): return True def connect_timeout(self): return self.pubnub.config.connect_timeout def request_timeout(self): return self.pubnub.config.non_subscribe_request_timeout def validate_params(self): self.validate_subscribe_key() self.validate_specific_params() def validate_specific_params(self): pass def encoded_params(self): params = {} if isinstance(self, ListEndpoint): if self._filter: params["filter"] = utils.url_encode(str(self._filter)) return params def custom_params(self): params = {} inclusions = [] if isinstance(self, IncludeCustomEndpoint): if self._include_custom: inclusions.append("custom") if isinstance(self, UUIDIncludeEndpoint): if self._uuid_details_level: if self._uuid_details_level == UUIDIncludeEndpoint.UUID: inclusions.append("uuid") elif self._uuid_details_level == UUIDIncludeEndpoint.UUID_WITH_CUSTOM: inclusions.append("uuid.custom") if isinstance(self, ChannelIncludeEndpoint): if self._channel_details_level: if self._channel_details_level == ChannelIncludeEndpoint.CHANNEL: inclusions.append("channel") elif ( self._channel_details_level == ChannelIncludeEndpoint.CHANNEL_WITH_CUSTOM ): inclusions.append("channel.custom") if isinstance(self, ListEndpoint): if self._filter: params["filter"] = str(self._filter) if self._limit: params["limit"] = int(self._limit) if self._include_total_count: params["count"] = bool(self._include_total_count) if self._sort_keys: joined_sort_params_array = [] for sort_key in self._sort_keys: joined_sort_params_array.append( f"{sort_key.key_str()}:{sort_key.dir_str()}" ) params["sort"] = ",".join(joined_sort_params_array) if self._page: if isinstance(self._page, Next): params["start"] = self._page.hash elif isinstance(self._page, Previous): params["end"] = self._page.hash else: raise ValueError() if len(inclusions) > 0: params["include"] = ",".join(inclusions) return params class CustomAwareEndpoint: __metaclass__ = ABCMeta def __init__(self): self._custom = None def custom(self, custom): self._custom = dict(custom) return self class ChannelEndpoint: __metaclass__ = ABCMeta def __init__(self): self._channel = None def channel(self, channel): self._channel = str(channel) return self def _validate_channel(self): if self._channel is None or len(self._channel) == 0: raise PubNubException(pn_error=PNERR_CHANNEL_MISSING) class UuidEndpoint: __metaclass__ = ABCMeta def __init__(self): self._uuid = None def uuid(self, uuid): self._uuid = str(uuid) return self def _effective_uuid(self): if self._uuid is not None: return self._uuid else: return self.pubnub.config.uuid def _validate_uuid(self): if self._effective_uuid() is None or len(self._effective_uuid()) == 0: raise PubNubException(pn_error=PNERR_UUID_MISSING) class ListEndpoint: __metaclass__ = ABCMeta def __init__(self): self._limit = None self._filter = None self._include_total_count = None self._sort_keys = None self._page = None def limit(self, limit): self._limit = int(limit) return self def filter(self, filter): self._filter = str(filter) return self def include_total_count(self, include_total_count): self._include_total_count = bool(include_total_count) return self def sort(self, *sort_keys): self._sort_keys = sort_keys return self def page(self, page): self._page = page return self class IncludeCustomEndpoint: __metaclass__ = ABCMeta def __init__(self): self._include_custom = None def include_custom(self, include_custom): self._include_custom = bool(include_custom) return self class UUIDIncludeEndpoint: __metaclass__ = ABCMeta UUID = 1 UUID_WITH_CUSTOM = 2 def __init__(self): self._uuid_details_level = None def include_uuid(self, uuid_details_level): self._uuid_details_level = uuid_details_level return self class ChannelIncludeEndpoint: __metaclass__ = ABCMeta CHANNEL = 1 CHANNEL_WITH_CUSTOM = 2 def __init__(self): self._channel_details_level = None def include_channel(self, channel_details_level): self._channel_details_level = channel_details_level return self bdraco-freenub-69809e8/pubnub/endpoints/objects_v2/uuid/000077500000000000000000000000001464523203000231715ustar00rootroot00000000000000bdraco-freenub-69809e8/pubnub/endpoints/objects_v2/uuid/__init__.py000066400000000000000000000000001464523203000252700ustar00rootroot00000000000000bdraco-freenub-69809e8/pubnub/endpoints/objects_v2/uuid/get_all_uuid.py000066400000000000000000000016541464523203000262060ustar00rootroot00000000000000from pubnub.endpoints.objects_v2.objects_endpoint import ( IncludeCustomEndpoint, ListEndpoint, ObjectsEndpoint, ) from pubnub.enums import HttpMethod, PNOperationType from pubnub.models.consumer.objects_v2.uuid import PNGetAllUUIDMetadataResult class GetAllUuid(ObjectsEndpoint, ListEndpoint, IncludeCustomEndpoint): GET_ALL_UID_PATH = "/v2/objects/%s/uuids" def __init__(self, pubnub): ObjectsEndpoint.__init__(self, pubnub) ListEndpoint.__init__(self) IncludeCustomEndpoint.__init__(self) def build_path(self): return GetAllUuid.GET_ALL_UID_PATH % self.pubnub.config.subscribe_key def create_response(self, envelope): return PNGetAllUUIDMetadataResult(envelope) def operation_type(self): return PNOperationType.PNGetAllUuidMetadataOperation def name(self): return "Get all UUIDs" def http_method(self): return HttpMethod.GET bdraco-freenub-69809e8/pubnub/endpoints/objects_v2/uuid/get_uuid.py000066400000000000000000000020271464523203000253510ustar00rootroot00000000000000from pubnub.endpoints.objects_v2.objects_endpoint import ( IncludeCustomEndpoint, ObjectsEndpoint, UuidEndpoint, ) from pubnub.enums import HttpMethod, PNOperationType from pubnub.models.consumer.objects_v2.uuid import PNGetUUIDMetadataResult class GetUuid(ObjectsEndpoint, UuidEndpoint, IncludeCustomEndpoint): GET_UID_PATH = "/v2/objects/%s/uuids/%s" def __init__(self, pubnub): ObjectsEndpoint.__init__(self, pubnub) UuidEndpoint.__init__(self) IncludeCustomEndpoint.__init__(self) def build_path(self): return GetUuid.GET_UID_PATH % ( self.pubnub.config.subscribe_key, self._effective_uuid(), ) def validate_specific_params(self): self._validate_uuid() def create_response(self, envelope): return PNGetUUIDMetadataResult(envelope) def operation_type(self): return PNOperationType.PNGetUuidMetadataOperation def name(self): return "Get UUID" def http_method(self): return HttpMethod.GET bdraco-freenub-69809e8/pubnub/endpoints/objects_v2/uuid/remove_uuid.py000066400000000000000000000017061464523203000260720ustar00rootroot00000000000000from pubnub.endpoints.objects_v2.objects_endpoint import ObjectsEndpoint, UuidEndpoint from pubnub.enums import HttpMethod, PNOperationType from pubnub.models.consumer.objects_v2.uuid import PNRemoveUUIDMetadataResult class RemoveUuid(ObjectsEndpoint, UuidEndpoint): REMOVE_UID_PATH = "/v2/objects/%s/uuids/%s" def __init__(self, pubnub): ObjectsEndpoint.__init__(self, pubnub) UuidEndpoint.__init__(self) def build_path(self): return RemoveUuid.REMOVE_UID_PATH % ( self.pubnub.config.subscribe_key, self._effective_uuid(), ) def validate_specific_params(self): self._validate_uuid() def create_response(self, envelope): return PNRemoveUUIDMetadataResult(envelope) def operation_type(self): return PNOperationType.PNRemoveUuidMetadataOperation def name(self): return "Remove UUID" def http_method(self): return HttpMethod.DELETE bdraco-freenub-69809e8/pubnub/endpoints/objects_v2/uuid/set_uuid.py000066400000000000000000000036561464523203000253760ustar00rootroot00000000000000from pubnub import utils from pubnub.endpoints.objects_v2.objects_endpoint import ( CustomAwareEndpoint, IncludeCustomEndpoint, ObjectsEndpoint, UuidEndpoint, ) from pubnub.enums import HttpMethod, PNOperationType from pubnub.models.consumer.objects_v2.uuid import PNSetUUIDMetadataResult class SetUuid( ObjectsEndpoint, UuidEndpoint, IncludeCustomEndpoint, CustomAwareEndpoint ): SET_UID_PATH = "/v2/objects/%s/uuids/%s" def __init__(self, pubnub): ObjectsEndpoint.__init__(self, pubnub) UuidEndpoint.__init__(self) IncludeCustomEndpoint.__init__(self) CustomAwareEndpoint.__init__(self) self._name = None self._email = None self._external_id = None self._profile_url = None def set_name(self, name): self._name = str(name) return self def email(self, email): self._email = str(email) return self def external_id(self, external_id): self._external_id = str(external_id) return self def profile_url(self, profile_url): self._profile_url = str(profile_url) return self def build_path(self): return SetUuid.SET_UID_PATH % ( self.pubnub.config.subscribe_key, self._effective_uuid(), ) def build_data(self): payload = { "name": self._name, "email": self._email, "externalId": self._external_id, "profileUrl": self._profile_url, "custom": self._custom, } return utils.write_value_as_string(payload) def validate_specific_params(self): self._validate_uuid() def create_response(self, envelope): return PNSetUUIDMetadataResult(envelope) def operation_type(self): return PNOperationType.PNSetUuidMetadataOperation def name(self): return "Set UUID" def http_method(self): return HttpMethod.PATCH bdraco-freenub-69809e8/pubnub/endpoints/presence/000077500000000000000000000000001464523203000217675ustar00rootroot00000000000000bdraco-freenub-69809e8/pubnub/endpoints/presence/__init__.py000066400000000000000000000000001464523203000240660ustar00rootroot00000000000000bdraco-freenub-69809e8/pubnub/endpoints/presence/get_state.py000066400000000000000000000043441464523203000243250ustar00rootroot00000000000000from pubnub import utils from pubnub.endpoints.endpoint import Endpoint from pubnub.endpoints.mixins import UUIDValidatorMixin from pubnub.enums import HttpMethod, PNOperationType from pubnub.models.consumer.presence import PNGetStateResult class GetState(Endpoint, UUIDValidatorMixin): # /v2/presence/sub-key//channel//uuid//data?state= GET_STATE_PATH = "/v2/presence/sub-key/%s/channel/%s/uuid/%s" def __init__(self, pubnub): Endpoint.__init__(self, pubnub) self._channels = [] self._groups = [] self._uuid = self.pubnub.uuid def channels(self, channels): utils.extend_list(self._channels, channels) return self def uuid(self, uuid): self._uuid = uuid return self def channel_groups(self, channel_groups): utils.extend_list(self._groups, channel_groups) return self def custom_params(self): params = {} if len(self._groups) > 0: params["channel-group"] = utils.join_items(self._groups) return params def build_path(self): return GetState.GET_STATE_PATH % ( self.pubnub.config.subscribe_key, utils.join_channels(self._channels), utils.url_encode(self._uuid), ) def http_method(self): return HttpMethod.GET def validate_params(self): self.validate_subscribe_key() self.validate_channels_and_groups() self.validate_uuid() def create_response(self, envelope): if len(self._channels) == 1 and len(self._groups) == 0: channels = {self._channels[0]: envelope["payload"]} else: channels = envelope["payload"]["channels"] return PNGetStateResult(channels) def is_auth_required(self): return True def affected_channels(self): return self._channels def affected_channels_groups(self): return self._groups def request_timeout(self): return self.pubnub.config.non_subscribe_request_timeout def connect_timeout(self): return self.pubnub.config.connect_timeout def operation_type(self): return PNOperationType.PNGetState def name(self): return "GetState" bdraco-freenub-69809e8/pubnub/endpoints/presence/heartbeat.py000066400000000000000000000042201464523203000242760ustar00rootroot00000000000000from pubnub import utils from pubnub.endpoints.endpoint import Endpoint from pubnub.enums import HttpMethod, PNOperationType from pubnub.errors import PNERR_CHANNEL_OR_GROUP_MISSING from pubnub.exceptions import PubNubException class Heartbeat(Endpoint): # /v2/presence/sub-key//channel//heartbeat?uuid= HEARTBEAT_PATH = "/v2/presence/sub-key/%s/channel/%s/heartbeat" def __init__(self, pubnub): super().__init__(pubnub) self._channels = [] self._groups = [] self._state = None def channels(self, channels): utils.extend_list(self._channels, channels) return self def channel_groups(self, channel_groups): utils.extend_list(self._groups, channel_groups) return self def state(self, state): self._state = state return self def http_method(self): return HttpMethod.GET def validate_params(self): self.validate_subscribe_key() if len(self._channels) == 0 and len(self._groups) == 0: raise PubNubException(pn_error=PNERR_CHANNEL_OR_GROUP_MISSING) def build_path(self): channels = utils.join_channels(self._channels) return Heartbeat.HEARTBEAT_PATH % (self.pubnub.config.subscribe_key, channels) def custom_params(self): params = {"heartbeat": str(self.pubnub.config.presence_timeout)} if len(self._groups) > 0: params["channel-group"] = utils.join_items(self._groups) if self._state is not None and len(self._state) > 0: params["state"] = utils.url_write(self._state) return params def create_response(self, envelope): return True def is_auth_required(self): return True def affected_channels(self): return None def affected_channels_groups(self): return None def request_timeout(self): return self.pubnub.config.non_subscribe_request_timeout def connect_timeout(self): return self.pubnub.config.connect_timeout def operation_type(self): return PNOperationType.PNHeartbeatOperation def name(self): return "Heartbeat" bdraco-freenub-69809e8/pubnub/endpoints/presence/here_now.py000066400000000000000000000043751464523203000241600ustar00rootroot00000000000000from pubnub import utils from pubnub.endpoints.endpoint import Endpoint from pubnub.enums import HttpMethod, PNOperationType from pubnub.models.consumer.presence import PNHereNowResult class HereNow(Endpoint): HERE_NOW_PATH = "/v2/presence/sub-key/%s/channel/%s" HERE_NOW_GLOBAL_PATH = "/v2/presence/sub-key/%s" def __init__(self, pubnub): Endpoint.__init__(self, pubnub) self._channels = [] self._channel_groups = [] self._include_state = False self._include_uuids = True def channels(self, channels): utils.extend_list(self._channels, channels) return self def channel_groups(self, channel_groups): utils.extend_list(self._channel_groups, channel_groups) return self def include_state(self, should_include_state): self._include_state = should_include_state return self def include_uuids(self, include_uuids): self._include_uuids = include_uuids return self def custom_params(self): params = {} if len(self._channel_groups) > 0: params["channel-group"] = utils.join_items_and_encode(self._channel_groups) if self._include_state: params["state"] = "1" if not self._include_uuids: params["disable_uuids"] = "1" return params def build_path(self): if len(self._channels) == 0 and len(self._channel_groups) == 0: return HereNow.HERE_NOW_GLOBAL_PATH % self.pubnub.config.subscribe_key else: return HereNow.HERE_NOW_PATH % ( self.pubnub.config.subscribe_key, utils.join_channels(self._channels), ) def http_method(self): return HttpMethod.GET def validate_params(self): self.validate_subscribe_key() def is_auth_required(self): return True def create_response(self, envelope): return PNHereNowResult.from_json(envelope, self._channels) def request_timeout(self): return self.pubnub.config.non_subscribe_request_timeout def connect_timeout(self): return self.pubnub.config.connect_timeout def operation_type(self): return PNOperationType.PNHereNowOperation def name(self): return "HereNow" bdraco-freenub-69809e8/pubnub/endpoints/presence/leave.py000066400000000000000000000041671464523203000234450ustar00rootroot00000000000000from pubnub import utils from pubnub.endpoints.endpoint import Endpoint from pubnub.enums import HttpMethod, PNOperationType from pubnub.errors import PNERR_CHANNEL_OR_GROUP_MISSING from pubnub.exceptions import PubNubException class Leave(Endpoint): # /v2/presence/sub-key//channel//leave?uuid= LEAVE_PATH = "/v2/presence/sub-key/%s/channel/%s/leave" def __init__(self, pubnub): Endpoint.__init__(self, pubnub) self._channels = [] self._groups = [] def channels(self, channels): if isinstance(channels, (list, tuple)): self._channels.extend(channels) else: self._channels.extend(utils.split_items(channels)) return self def channel_groups(self, channel_groups): if isinstance(channel_groups, (list, tuple)): self._groups.extend(channel_groups) else: self._groups.extend(utils.split_items(channel_groups)) return self def custom_params(self): params = {} if len(self._groups) > 0: params["channel-group"] = utils.join_items(self._groups) return params def build_path(self): return Leave.LEAVE_PATH % ( self.pubnub.config.subscribe_key, utils.join_channels(self._channels), ) def http_method(self): return HttpMethod.GET def validate_params(self): self.validate_subscribe_key() if len(self._channels) == 0 and len(self._groups) == 0: raise PubNubException(pn_error=PNERR_CHANNEL_OR_GROUP_MISSING) def create_response(self, envelope): return envelope def is_auth_required(self): return True def affected_channels(self): return self._channels def affected_channels_groups(self): return self._groups def request_timeout(self): return self.pubnub.config.non_subscribe_request_timeout def connect_timeout(self): return self.pubnub.config.connect_timeout def operation_type(self): return PNOperationType.PNUnsubscribeOperation def name(self): return "Leave" bdraco-freenub-69809e8/pubnub/endpoints/presence/set_state.py000066400000000000000000000061301464523203000243340ustar00rootroot00000000000000from pubnub import utils from pubnub.dtos import StateOperation from pubnub.endpoints.endpoint import Endpoint from pubnub.enums import HttpMethod, PNOperationType from pubnub.errors import ( PNERR_STATE_MISSING, PNERR_STATE_SETTER_FOR_GROUPS_NOT_SUPPORTED_YET, ) from pubnub.exceptions import PubNubException from pubnub.models.consumer.presence import PNSetStateResult class SetState(Endpoint): # /v2/presence/sub-key//channel//uuid//data?state= SET_STATE_PATH = "/v2/presence/sub-key/%s/channel/%s/uuid/%s/data" def __init__(self, pubnub, subscription_manager=None): Endpoint.__init__(self, pubnub) self._subscription_manager = subscription_manager self._channels = [] self._groups = [] self._state = None def channels(self, channels): utils.extend_list(self._channels, channels) return self def channel_groups(self, channel_groups): utils.extend_list(self._groups, channel_groups) return self def state(self, state): self._state = state return self def encoded_params(self): return {"state": utils.url_write(self._state)} def custom_params(self): if self._subscription_manager is not None: self._subscription_manager.adapt_state_builder( StateOperation( channels=self._channels, channel_groups=self._groups, state=self._state, ) ) params = {"state": utils.write_value_as_string(self._state)} if len(self._groups) > 0: params["channel-group"] = utils.join_items_and_encode(self._groups) return params def build_path(self): return SetState.SET_STATE_PATH % ( self.pubnub.config.subscribe_key, utils.join_channels(self._channels), utils.url_encode(self.pubnub.uuid), ) def http_method(self): return HttpMethod.GET def validate_params(self): self.validate_subscribe_key() self.validate_channels_and_groups() if len(self._channels) == 0 and len(self._groups) > 0: raise PubNubException( pn_error=PNERR_STATE_SETTER_FOR_GROUPS_NOT_SUPPORTED_YET ) if self._state is None or not isinstance(self._state, dict): raise PubNubException(pn_error=PNERR_STATE_MISSING) def create_response(self, envelope): if "status" in envelope and envelope["status"] == 200: return PNSetStateResult(envelope["payload"]) else: return envelope def is_auth_required(self): return True def affected_channels(self): return self._channels def affected_channels_groups(self): return self._groups def request_timeout(self): return self.pubnub.config.non_subscribe_request_timeout def connect_timeout(self): return self.pubnub.config.connect_timeout def operation_type(self): return PNOperationType.PNSetStateOperation def name(self): return "SetState" bdraco-freenub-69809e8/pubnub/endpoints/presence/where_now.py000066400000000000000000000026041464523203000243400ustar00rootroot00000000000000from pubnub import utils from pubnub.endpoints.endpoint import Endpoint from pubnub.endpoints.mixins import UUIDValidatorMixin from pubnub.enums import HttpMethod, PNOperationType from pubnub.models.consumer.presence import PNWhereNowResult class WhereNow(Endpoint, UUIDValidatorMixin): # /v2/presence/sub-key//uuid/ WHERE_NOW_PATH = "/v2/presence/sub-key/%s/uuid/%s" def __init__(self, pubnub): Endpoint.__init__(self, pubnub) self._uuid = pubnub.config.uuid def uuid(self, uuid): self._uuid = uuid return self def custom_params(self): return {} def build_path(self): return WhereNow.WHERE_NOW_PATH % ( self.pubnub.config.subscribe_key, utils.url_encode(self._uuid), ) def http_method(self): return HttpMethod.GET def validate_params(self): self.validate_subscribe_key() self.validate_uuid() def is_auth_required(self): return True def create_response(self, envelope): return PNWhereNowResult.from_json(envelope) def request_timeout(self): return self.pubnub.config.non_subscribe_request_timeout def connect_timeout(self): return self.pubnub.config.connect_timeout def operation_type(self): return PNOperationType.PNWhereNowOperation def name(self): return "WhereNow" bdraco-freenub-69809e8/pubnub/endpoints/pubsub/000077500000000000000000000000001464523203000214635ustar00rootroot00000000000000bdraco-freenub-69809e8/pubnub/endpoints/pubsub/__init__.py000066400000000000000000000000001464523203000235620ustar00rootroot00000000000000bdraco-freenub-69809e8/pubnub/endpoints/pubsub/fire.py000066400000000000000000000101461464523203000227640ustar00rootroot00000000000000from pubnub import utils from pubnub.endpoints.endpoint import Endpoint from pubnub.enums import HttpMethod, PNOperationType from pubnub.errors import PNERR_MESSAGE_MISSING from pubnub.exceptions import PubNubException from pubnub.models.consumer.pubsub import PNFireResult class Fire(Endpoint): # /publish//////[?argument(s)] FIRE_GET_PATH = "/publish/%s/%s/0/%s/%s/%s" FIRE_POST_PATH = "/publish/%s/%s/0/%s/%s" def __init__(self, pubnub): Endpoint.__init__(self, pubnub) self._channel = None self._message = None self._use_post = None self._meta = None def channel(self, channel): self._channel = str(channel) return self def message(self, message): self._message = message return self def use_post(self, use_post): self._use_post = bool(use_post) return self def is_compressable(self): return True def use_compression(self, compress=True): self._use_compression = bool(compress) return self def meta(self, meta): self._meta = meta return self def build_data(self): if self._use_post is True: cipher = self.pubnub.config.cipher_key if cipher is not None: return ( '"' + self.pubnub.config.crypto.encrypt( cipher, utils.write_value_as_string(self._message) ) + '"' ) else: return utils.write_value_as_string(self._message) else: return None def custom_params(self): params = {} if self._meta is not None: params["meta"] = utils.write_value_as_string(self._meta) params["store"] = "0" params["norep"] = "1" if self.pubnub.config.auth_key is not None: params["auth"] = utils.url_encode(self.pubnub.config.auth_key) return params def build_path(self): if self._use_post: return Fire.FIRE_POST_PATH % ( self.pubnub.config.publish_key, self.pubnub.config.subscribe_key, utils.url_encode(self._channel), 0, ) else: cipher = self.pubnub.config.cipher_key stringified_message = utils.write_value_as_string(self._message) if cipher is not None: stringified_message = ( '"' + self.pubnub.config.crypto.encrypt(cipher, stringified_message) + '"' ) stringified_message = utils.url_encode(stringified_message) return Fire.FIRE_GET_PATH % ( self.pubnub.config.publish_key, self.pubnub.config.subscribe_key, utils.url_encode(self._channel), 0, stringified_message, ) def http_method(self): if self._use_post is True: return HttpMethod.POST else: return HttpMethod.GET def validate_params(self): self.validate_channel() if self._message is None: raise PubNubException(pn_error=PNERR_MESSAGE_MISSING) self.validate_subscribe_key() self.validate_publish_key() def create_response(self, envelope): """ :param envelope: an already serialized json response :return: """ if envelope is None: return None timetoken = int(envelope[2]) res = PNFireResult(envelope, timetoken) return res def is_auth_required(self): return True def affected_channels(self): return None def affected_channels_groups(self): return None def request_timeout(self): return self.pubnub.config.non_subscribe_request_timeout def connect_timeout(self): return self.pubnub.config.connect_timeout def operation_type(self): return PNOperationType.PNFireOperation def name(self): return "Fire" bdraco-freenub-69809e8/pubnub/endpoints/pubsub/publish.py000066400000000000000000000116171464523203000235110ustar00rootroot00000000000000from pubnub import utils from pubnub.endpoints.endpoint import Endpoint from pubnub.endpoints.mixins import TimeTokenOverrideMixin from pubnub.enums import HttpMethod, PNOperationType from pubnub.errors import PNERR_MESSAGE_MISSING from pubnub.exceptions import PubNubException from pubnub.models.consumer.pubsub import PNPublishResult class Publish(Endpoint, TimeTokenOverrideMixin): # /publish//////[?argument(s)] PUBLISH_GET_PATH = "/publish/%s/%s/0/%s/%s/%s" PUBLISH_POST_PATH = "/publish/%s/%s/0/%s/%s" def __init__(self, pubnub): super().__init__(pubnub) self._channel = None self._message = None self._should_store = None self._use_post = None self._meta = None self._replicate = None self._ptto = None self._ttl = None def channel(self, channel): self._channel = str(channel) return self def message(self, message): self._message = message return self def use_post(self, use_post): self._use_post = bool(use_post) return self def use_compression(self, compress=True): self._use_compression = bool(compress) return self def is_compressable(self): return True def should_store(self, should_store): self._should_store = bool(should_store) return self def meta(self, meta): self._meta = meta return self def ttl(self, ttl): self._ttl = ttl return self def build_data(self): if self._use_post is True: cipher = self.pubnub.config.cipher_key if cipher is not None: return ( '"' + self.pubnub.config.crypto.encrypt( cipher, utils.write_value_as_string(self._message) ) + '"' ) else: return utils.write_value_as_string(self._message) else: return None def encoded_params(self): if self._meta: return {"meta": utils.url_write(self._meta)} else: return {} def custom_params(self): params = TimeTokenOverrideMixin.custom_params(self) if self._ttl: params["ttl"] = self._ttl if self._meta: params["meta"] = utils.write_value_as_string(self._meta) if self._should_store is not None: if self._should_store: params["store"] = "1" else: params["store"] = "0" # REVIEW: should auth key be assigned here? if self.pubnub.config.auth_key is not None: params["auth"] = utils.url_encode(self.pubnub.config.auth_key) return params def build_path(self): if self._use_post: return Publish.PUBLISH_POST_PATH % ( self.pubnub.config.publish_key, self.pubnub.config.subscribe_key, utils.url_encode(self._channel), 0, ) else: cipher = self.pubnub.config.cipher_key stringified_message = utils.write_value_as_string(self._message) if cipher is not None: stringified_message = ( '"' + self.pubnub.config.crypto.encrypt(cipher, stringified_message) + '"' ) stringified_message = utils.url_encode(stringified_message) return Publish.PUBLISH_GET_PATH % ( self.pubnub.config.publish_key, self.pubnub.config.subscribe_key, utils.url_encode(self._channel), 0, stringified_message, ) def http_method(self): if self._use_post is True: return HttpMethod.POST else: return HttpMethod.GET def validate_params(self): self.validate_channel() if self._message is None: raise PubNubException(pn_error=PNERR_MESSAGE_MISSING) self.validate_subscribe_key() self.validate_publish_key() def create_response(self, envelope): """ :param envelope: an already serialized json response :return: """ if envelope is None: return None timetoken = int(envelope[2]) res = PNPublishResult(envelope, timetoken) return res def is_auth_required(self): return True def affected_channels(self): return None def affected_channels_groups(self): return None def request_timeout(self): return self.pubnub.config.non_subscribe_request_timeout def connect_timeout(self): return self.pubnub.config.connect_timeout def operation_type(self): return PNOperationType.PNPublishOperation def name(self): return "Publish" bdraco-freenub-69809e8/pubnub/endpoints/pubsub/subscribe.py000066400000000000000000000053001464523203000240140ustar00rootroot00000000000000from pubnub import utils from pubnub.endpoints.endpoint import Endpoint from pubnub.enums import HttpMethod, PNOperationType from pubnub.errors import PNERR_CHANNEL_OR_GROUP_MISSING from pubnub.exceptions import PubNubException class Subscribe(Endpoint): # /subscribe//// SUBSCRIBE_PATH = "/v2/subscribe/%s/%s/0" def __init__(self, pubnub): super().__init__(pubnub) self._channels = [] self._groups = [] self._region = None self._filter_expression = None self._timetoken = None self._with_presence = None def channels(self, channels): utils.extend_list(self._channels, channels) return self def channel_groups(self, groups): utils.extend_list(self._groups, groups) return self def timetoken(self, timetoken): self._timetoken = timetoken return self def filter_expression(self, expr): self._filter_expression = expr return self def region(self, region): self._region = region return self def http_method(self): return HttpMethod.GET def validate_params(self): self.validate_subscribe_key() if len(self._channels) == 0 and len(self._groups) == 0: raise PubNubException(pn_error=PNERR_CHANNEL_OR_GROUP_MISSING) def build_path(self): channels = utils.join_channels(self._channels) return Subscribe.SUBSCRIBE_PATH % (self.pubnub.config.subscribe_key, channels) def custom_params(self): params = {} if len(self._groups) > 0: params["channel-group"] = utils.join_items_and_encode(self._groups) if self._filter_expression is not None and len(self._filter_expression) > 0: params["filter-expr"] = utils.url_encode(self._filter_expression) if self._timetoken is not None: params["tt"] = str(self._timetoken) if self._region is not None: params["tr"] = self._region if not self.pubnub.config.heartbeat_default_values: params["heartbeat"] = self.pubnub.config.presence_timeout return params def create_response(self, envelope): return envelope def is_auth_required(self): return True def affected_channels(self): return self._channels def affected_channels_groups(self): return self._groups def request_timeout(self): return self.pubnub.config.subscribe_request_timeout def connect_timeout(self): return self.pubnub.config.connect_timeout def operation_type(self): return PNOperationType.PNSubscribeOperation def name(self): return "Subscribe" bdraco-freenub-69809e8/pubnub/endpoints/push/000077500000000000000000000000001464523203000211425ustar00rootroot00000000000000bdraco-freenub-69809e8/pubnub/endpoints/push/__init__.py000066400000000000000000000000001464523203000232410ustar00rootroot00000000000000bdraco-freenub-69809e8/pubnub/endpoints/push/add_channels_to_push.py000066400000000000000000000065141464523203000256660ustar00rootroot00000000000000from pubnub import utils from pubnub.endpoints.endpoint import Endpoint from pubnub.enums import HttpMethod, PNOperationType, PNPushEnvironment, PNPushType from pubnub.errors import ( PNERR_CHANNEL_MISSING, PNERR_PUSH_DEVICE_MISSING, PNERR_PUSH_TOPIC_MISSING, PNERROR_PUSH_TYPE_MISSING, ) from pubnub.exceptions import PubNubException from pubnub.models.consumer.push import PNPushAddChannelResult class AddChannelsToPush(Endpoint): # v1/push/sub-key/{subKey}/devices/{pushToken} ADD_PATH = "/v1/push/sub-key/%s/devices/%s" # v2/push/sub-key/{subKey}/devices-apns2/{deviceApns2} ADD_PATH_APNS2 = "/v2/push/sub-key/%s/devices-apns2/%s" def __init__(self, pubnub): Endpoint.__init__(self, pubnub) self._channels = None self._device_id = None self._push_type = None self._topic = None self._environment = None def channels(self, channels): self._channels = channels return self def device_id(self, device_id): self._device_id = device_id return self def push_type(self, push_type): self._push_type = push_type return self def topic(self, topic): self._topic = topic return self def environment(self, environment): self._environment = environment return self def custom_params(self): params = {} params["add"] = utils.join_items(self._channels) if self._push_type != PNPushType.APNS2: params["type"] = utils.push_type_to_string(self._push_type) else: if self._environment is None: self._environment = PNPushEnvironment.DEVELOPMENT params["environment"] = self._environment params["topic"] = self._topic return params def build_path(self): if self._push_type != PNPushType.APNS2: return AddChannelsToPush.ADD_PATH % ( self.pubnub.config.subscribe_key, self._device_id, ) else: return AddChannelsToPush.ADD_PATH_APNS2 % ( self.pubnub.config.subscribe_key, self._device_id, ) def http_method(self): return HttpMethod.GET def validate_params(self): self.validate_subscribe_key() if not isinstance(self._channels, list) or len(self._channels) == 0: raise PubNubException(pn_error=PNERR_CHANNEL_MISSING) if not isinstance(self._device_id, str) or len(self._device_id) == 0: raise PubNubException(pn_error=PNERR_PUSH_DEVICE_MISSING) if self._push_type is None: raise PubNubException(pn_error=PNERROR_PUSH_TYPE_MISSING) if self._push_type == PNPushType.APNS2: if not isinstance(self._topic, str) or len(self._topic) == 0: raise PubNubException(pn_error=PNERR_PUSH_TOPIC_MISSING) def create_response(self, envelope): return PNPushAddChannelResult() def is_auth_required(self): return True def request_timeout(self): return self.pubnub.config.non_subscribe_request_timeout def connect_timeout(self): return self.pubnub.config.connect_timeout def operation_type(self): return PNOperationType.PNAddPushNotificationsOnChannelsOperation def name(self): return "AddChannelsToPush" bdraco-freenub-69809e8/pubnub/endpoints/push/list_push_provisions.py000066400000000000000000000062431464523203000260260ustar00rootroot00000000000000from pubnub import utils from pubnub.endpoints.endpoint import Endpoint from pubnub.enums import HttpMethod, PNOperationType, PNPushEnvironment, PNPushType from pubnub.errors import ( PNERR_PUSH_DEVICE_MISSING, PNERR_PUSH_TOPIC_MISSING, PNERROR_PUSH_TYPE_MISSING, ) from pubnub.exceptions import PubNubException from pubnub.models.consumer.push import PNPushListProvisionsResult class ListPushProvisions(Endpoint): # v1/push/sub-key/{subKey}/devices/{pushToken} LIST_PATH = "/v1/push/sub-key/%s/devices/%s" # v2/push/sub-key/{subKey}/devices-apns2/{deviceApns2} LIST_PATH_APNS2 = "/v2/push/sub-key/%s/devices-apns2/%s" def __init__(self, pubnub): Endpoint.__init__(self, pubnub) self._device_id = None self._push_type = None self._topic = None self._environment = None def device_id(self, device_id): self._device_id = device_id return self def push_type(self, push_type): self._push_type = push_type return self def topic(self, topic): self._topic = topic return self def environment(self, environment): self._environment = environment return self def custom_params(self): params = {} if self._push_type != PNPushType.APNS2: params["type"] = utils.push_type_to_string(self._push_type) else: if self._environment is None: self._environment = PNPushEnvironment.DEVELOPMENT params["environment"] = self._environment params["topic"] = self._topic return params def build_path(self): if self._push_type != PNPushType.APNS2: return ListPushProvisions.LIST_PATH % ( self.pubnub.config.subscribe_key, self._device_id, ) else: return ListPushProvisions.LIST_PATH_APNS2 % ( self.pubnub.config.subscribe_key, self._device_id, ) def http_method(self): return HttpMethod.GET def validate_params(self): self.validate_subscribe_key() if not isinstance(self._device_id, str) or len(self._device_id) == 0: raise PubNubException(pn_error=PNERR_PUSH_DEVICE_MISSING) if self._push_type is None: raise PubNubException(pn_error=PNERROR_PUSH_TYPE_MISSING) if self._push_type == PNPushType.APNS2: if not isinstance(self._topic, str) or len(self._topic) == 0: raise PubNubException(pn_error=PNERR_PUSH_TOPIC_MISSING) def create_response(self, channels): if channels is not None and len(channels) > 0 and isinstance(channels, list): return PNPushListProvisionsResult(channels) else: return PNPushListProvisionsResult([]) def is_auth_required(self): return True def request_timeout(self): return self.pubnub.config.non_subscribe_request_timeout def connect_timeout(self): return self.pubnub.config.connect_timeout def operation_type(self): return PNOperationType.PNPushNotificationEnabledChannelsOperation def name(self): return "ListPushProvisions" bdraco-freenub-69809e8/pubnub/endpoints/push/remove_channels_from_push.py000066400000000000000000000065471464523203000267620ustar00rootroot00000000000000from pubnub import utils from pubnub.endpoints.endpoint import Endpoint from pubnub.enums import HttpMethod, PNOperationType, PNPushEnvironment, PNPushType from pubnub.errors import ( PNERR_CHANNEL_MISSING, PNERR_PUSH_DEVICE_MISSING, PNERR_PUSH_TOPIC_MISSING, PNERROR_PUSH_TYPE_MISSING, ) from pubnub.exceptions import PubNubException from pubnub.models.consumer.push import PNPushRemoveChannelResult class RemoveChannelsFromPush(Endpoint): # v1/push/sub-key/{subKey}/devices/{pushToken} REMOVE_PATH = "/v1/push/sub-key/%s/devices/%s" # v2/push/sub-key/{subKey}/devices-apns2/{deviceApns2} REMOVE_PATH_APNS2 = "/v2/push/sub-key/%s/devices-apns2/%s" def __init__(self, pubnub): Endpoint.__init__(self, pubnub) self._channels = None self._device_id = None self._push_type = None self._topic = None self._environment = None def channels(self, channels): self._channels = channels return self def device_id(self, device_id): self._device_id = device_id return self def push_type(self, push_type): self._push_type = push_type return self def topic(self, topic): self._topic = topic return self def environment(self, environment): self._environment = environment return self def custom_params(self): params = {"remove": utils.join_items(self._channels)} if self._push_type != PNPushType.APNS2: params["type"] = utils.push_type_to_string(self._push_type) else: if self._environment is None: self._environment = PNPushEnvironment.DEVELOPMENT params["environment"] = self._environment params["topic"] = self._topic return params def build_path(self): if self._push_type != PNPushType.APNS2: return RemoveChannelsFromPush.REMOVE_PATH % ( self.pubnub.config.subscribe_key, self._device_id, ) else: return RemoveChannelsFromPush.REMOVE_PATH_APNS2 % ( self.pubnub.config.subscribe_key, self._device_id, ) def http_method(self): return HttpMethod.GET def validate_params(self): self.validate_subscribe_key() if not isinstance(self._channels, list) or len(self._channels) == 0: raise PubNubException(pn_error=PNERR_CHANNEL_MISSING) if not isinstance(self._device_id, str) or len(self._device_id) == 0: raise PubNubException(pn_error=PNERR_PUSH_DEVICE_MISSING) if self._push_type is None: raise PubNubException(pn_error=PNERROR_PUSH_TYPE_MISSING) if self._push_type == PNPushType.APNS2: if not isinstance(self._topic, str) or len(self._topic) == 0: raise PubNubException(pn_error=PNERR_PUSH_TOPIC_MISSING) def create_response(self, envelope): return PNPushRemoveChannelResult() def is_auth_required(self): return True def request_timeout(self): return self.pubnub.config.non_subscribe_request_timeout def connect_timeout(self): return self.pubnub.config.connect_timeout def operation_type(self): return PNOperationType.PNRemovePushNotificationsFromChannelsOperation def name(self): return "RemoveChannelsFromPush" bdraco-freenub-69809e8/pubnub/endpoints/push/remove_device.py000066400000000000000000000060561464523203000243370ustar00rootroot00000000000000from pubnub import utils from pubnub.endpoints.endpoint import Endpoint from pubnub.enums import HttpMethod, PNOperationType, PNPushEnvironment, PNPushType from pubnub.errors import ( PNERR_PUSH_DEVICE_MISSING, PNERR_PUSH_TOPIC_MISSING, PNERROR_PUSH_TYPE_MISSING, ) from pubnub.exceptions import PubNubException from pubnub.models.consumer.push import PNPushRemoveAllChannelsResult class RemoveDeviceFromPush(Endpoint): # v1/push/sub-key/{subKey}/devices/{pushToken}/remove REMOVE_PATH = "/v1/push/sub-key/%s/devices/%s/remove" # v2/push/sub-key/{subKey}/devices-apns2/{deviceApns2}/remove REMOVE_PATH_APNS2 = "/v2/push/sub-key/%s/devices-apns2/%s/remove" def __init__(self, pubnub): Endpoint.__init__(self, pubnub) self._device_id = None self._push_type = None self._topic = None self._environment = None def device_id(self, device_id): self._device_id = device_id return self def push_type(self, push_type): self._push_type = push_type return self def topic(self, topic): self._topic = topic return self def environment(self, environment): self._environment = environment return self def custom_params(self): params = {} if self._push_type != PNPushType.APNS2: params["type"] = utils.push_type_to_string(self._push_type) else: if self._environment is None: self._environment = PNPushEnvironment.DEVELOPMENT params["environment"] = self._environment params["topic"] = self._topic return params def build_path(self): if self._push_type != PNPushType.APNS2: return RemoveDeviceFromPush.REMOVE_PATH % ( self.pubnub.config.subscribe_key, self._device_id, ) else: return RemoveDeviceFromPush.REMOVE_PATH_APNS2 % ( self.pubnub.config.subscribe_key, self._device_id, ) def http_method(self): return HttpMethod.GET def validate_params(self): self.validate_subscribe_key() if not isinstance(self._device_id, str) or len(self._device_id) == 0: raise PubNubException(pn_error=PNERR_PUSH_DEVICE_MISSING) if self._push_type is None: raise PubNubException(pn_error=PNERROR_PUSH_TYPE_MISSING) if self._push_type == PNPushType.APNS2: if not isinstance(self._topic, str) or len(self._topic) == 0: raise PubNubException(pn_error=PNERR_PUSH_TOPIC_MISSING) def create_response(self, envelope): return PNPushRemoveAllChannelsResult() def is_auth_required(self): return True def request_timeout(self): return self.pubnub.config.non_subscribe_request_timeout def connect_timeout(self): return self.pubnub.config.connect_timeout def operation_type(self): return PNOperationType.PNRemoveAllPushNotificationsOperation def name(self): return "RemoveDeviceFromPush" bdraco-freenub-69809e8/pubnub/endpoints/signal.py000066400000000000000000000031151464523203000220120ustar00rootroot00000000000000from pubnub import utils from pubnub.endpoints.endpoint import Endpoint from pubnub.enums import HttpMethod, PNOperationType from pubnub.models.consumer.signal import PNSignalResult class Signal(Endpoint): SIGNAL_PATH = "/signal/%s/%s/0/%s/0/%s" def __init__(self, pubnub): Endpoint.__init__(self, pubnub) self._channel = None self._message = None def channel(self, channel): self._channel = str(channel) return self def message(self, message): self._message = message return self def build_path(self): stringified_message = utils.write_value_as_string(self._message) msg = utils.url_encode(stringified_message) return Signal.SIGNAL_PATH % ( self.pubnub.config.publish_key, self.pubnub.config.subscribe_key, utils.url_encode(self._channel), msg, ) def custom_params(self): return {} def http_method(self): return HttpMethod.GET def is_auth_required(self): return True def validate_params(self): self.validate_subscribe_key() self.validate_publish_key() self.validate_channel() def create_response(self, result): # pylint: disable=W0221 return PNSignalResult(result) def request_timeout(self): return self.pubnub.config.non_subscribe_request_timeout def connect_timeout(self): return self.pubnub.config.connect_timeout def operation_type(self): return PNOperationType.PNSignalOperation def name(self): return "Signal" bdraco-freenub-69809e8/pubnub/endpoints/time.py000066400000000000000000000015201464523203000214710ustar00rootroot00000000000000from pubnub.endpoints.endpoint import Endpoint from pubnub.enums import HttpMethod, PNOperationType from pubnub.models.consumer.time import PNTimeResponse class Time(Endpoint): TIME_PATH = "/time/0" def custom_params(self): return {} def build_path(self): return Time.TIME_PATH def http_method(self): return HttpMethod.GET def validate_params(self): pass def is_auth_required(self): return False def create_response(self, envelope): return PNTimeResponse(envelope) def request_timeout(self): return self.pubnub.config.non_subscribe_request_timeout def connect_timeout(self): return self.pubnub.config.connect_timeout def operation_type(self): return PNOperationType.PNTimeOperation def name(self): return "Time" bdraco-freenub-69809e8/pubnub/enums.py000066400000000000000000000101151464523203000176570ustar00rootroot00000000000000from enum import Enum class HttpMethod: GET = 1 POST = 2 DELETE = 3 PATCH = 4 @classmethod def string(cls, method): if method == cls.GET: return "GET" elif method == cls.POST: return "POST" elif method == cls.DELETE: return "DELETE" elif method == cls.PATCH: return "PATCH" class PNStatusCategory: PNUnknownCategory = 1 PNAcknowledgmentCategory = 2 PNAccessDeniedCategory = 3 PNTimeoutCategory = 4 PNNetworkIssuesCategory = 5 PNConnectedCategory = 6 PNReconnectedCategory = 7 PNDisconnectedCategory = 8 PNUnexpectedDisconnectCategory = 9 PNCancelledCategory = 10 PNBadRequestCategory = 11 PNMalformedFilterExpressionCategory = 12 PNMalformedResponseCategory = 13 PNDecryptionErrorCategory = 14 PNTLSConnectionFailedCategory = 15 PNTLSUntrustedCertificateCategory = 16 PNInternalExceptionCategory = 17 class PNOperationType: PNSubscribeOperation = 1 PNUnsubscribeOperation = 2 PNPublishOperation = 3 PNHistoryOperation = 4 PNWhereNowOperation = 5 PNHeartbeatOperation = 6 PNSetStateOperation = 7 PNAddChannelsToGroupOperation = 8 PNRemoveChannelsFromGroupOperation = 9 PNChannelGroupsOperation = 10 PNRemoveGroupOperation = 11 PNChannelsForGroupOperation = 12 PNPushNotificationEnabledChannelsOperation = 13 PNAddPushNotificationsOnChannelsOperation = 14 PNRemovePushNotificationsFromChannelsOperation = 15 PNRemoveAllPushNotificationsOperation = 16 PNTimeOperation = 17 PNHereNowOperation = 18 PNGetState = 19 PNAccessManagerAudit = 20 PNAccessManagerGrant = 21 PNAccessManagerRevoke = 22 PNHistoryDeleteOperation = 23 PNMessageCountOperation = 24 PNFireOperation = 25 PNSignalOperation = 26 PNAccessManagerRevokeToken = 40 PNAccessManagerGrantToken = 41 PNAddMessageAction = 42 PNGetMessageActions = 43 PNDeleteMessageAction = 44 PNFetchMessagesOperation = 45 PNGetFilesAction = 46 PNDeleteFileOperation = 47 PNGetFileDownloadURLAction = 48 PNFetchFileUploadS3DataAction = 49 PNDownloadFileAction = 50 PNSendFileAction = 51 PNSendFileNotification = 52 PNSetUuidMetadataOperation = 53 PNGetUuidMetadataOperation = 54 PNRemoveUuidMetadataOperation = 55 PNGetAllUuidMetadataOperation = 56 PNSetChannelMetadataOperation = 57 PNGetChannelMetadataOperation = 58 PNRemoveChannelMetadataOperation = 59 PNGetAllChannelMetadataOperation = 60 PNSetChannelMembersOperation = 61 PNGetChannelMembersOperation = 62 PNRemoveChannelMembersOperation = 63 PNManageChannelMembersOperation = 64 PNSetMembershipsOperation = 65 PNGetMembershipsOperation = 66 PNRemoveMembershipsOperation = 67 PNManageMembershipsOperation = 68 PNCreateSpaceOperation = 69 PNUpdateSpaceOperation = 70 PNFetchSpaceOperation = 71 PNFetchSpacesOperation = 72 PNRemoveSpaceOperation = 73 PNCreateUserOperation = 74 PNUpdateUserOperation = 75 PNFetchUserOperation = 76 PNFetchUsersOperation = 77 PNRemoveUserOperation = 78 PNAddUserSpacesOperation = 79 PNAddSpaceUsersOperation = 80 PNUpdateUserSpacesOperation = 81 PNUpdateSpaceUsersOperation = 82 PNRemoveUserSpacesOperation = 81 PNRemoveSpaceUsersOperation = 82 PNFetchUserMembershipsOperation = 85 PNFetchSpaceMembershipsOperation = 86 class PNHeartbeatNotificationOptions: NONE = 1 FAILURES = 2 ALL = 3 class PNReconnectionPolicy: NONE = 1 LINEAR = 2 EXPONENTIAL = 3 class PNPushType: APNS = 1 MPNS = 2 GCM = 3 APNS2 = 4 class PNResourceType: CHANNEL = "channel" GROUP = "group" USER = "user" SPACE = "space" class PNMatchType: RESOURCE = "resource" PATTERN = "pattern" class PNPushEnvironment: DEVELOPMENT = "development" PRODUCTION = "production" class PAMPermissions(Enum): READ = 1 WRITE = 2 MANAGE = 4 DELETE = 8 CREATE = 16 GET = 32 UPDATE = 64 JOIN = 128 bdraco-freenub-69809e8/pubnub/errors.py000066400000000000000000000062271464523203000200550ustar00rootroot00000000000000PNERR_CLIENT_TIMEOUT = "Client Timeout" PNERR__TIMEOUT = "Timeout Occurred" PNERR_REQUEST_CANCELLED = "HTTP Client Error" # TODO: clarify to not confuse with 4xx and 5xx http erros PNERR_HTTP_ERROR = "HTTP Error" PNERR_CONNECTION_ERROR = "Connection Error" PNERR_TOO_MANY_REDIRECTS_ERROR = "Too many redirects" # For 5xx server responses PNERR_SERVER_ERROR = "HTTP Server Error" # For 4xx server responses PNERR_CLIENT_ERROR = "HTTP Client Error" PNERR_UNKNOWN_ERROR = "Unknown Error" PNERR_CHANNEL_MISSING = "Channel missing" PNERR_CHANNELS_MISSING = "Channels missing" PNERR_GROUP_MISSING = "Channel group missing" PNERR_MESSAGE_MISSING = "Message missing" PNERR_SUBSCRIBE_KEY_MISSING = "Subscribe key not configured" PNERR_SECRET_KEY_MISSING = "Secret key is not configured" PNERR_PUBLISH_KEY_MISSING = "Publish key not configured" PNERR_PUBLISH_META_WRONG_TYPE = "Publish meta should be dict" PNERR_DEFERRED_NOT_IMPLEMENTED = ( "Deferred endpoint call is not implemented by this platform" ) PNERR_JSON_DECODING_FAILED = "JSON decoding failed" PNERR_JSON_NOT_SERIALIZABLE = "Trying to publish not JSON serializable object" PNERR_CHANNEL_OR_GROUP_MISSING = "Channel or group missing" PNERR_STATE_MISSING = "State missing or not a dict" PNERR_UUID_MISSING = "uuid missing or not a string" PNERR_STATE_SETTER_FOR_GROUPS_NOT_SUPPORTED_YET = ( "State setter for channel groups is not supported yet" ) PNERR_PUSH_DEVICE_MISSING = "Device ID is missing for push operation" PNERROR_PUSH_TYPE_MISSING = "Push Type is missing" PNERR_PAM_NO_FLAGS = "At least one flag should be specified" PNERR_PAM_INVALID_ARGUMENTS = "Invalid arguments" PNERR_RESOURCES_MISSING = "Resources missing" PNERR_TTL_MISSING = "TTL missing" PNERR_INVALID_META = "Invalid meta parameter" PNERR_INVALID_ACCESS_TOKEN = "Invalid access token" PNERR_MESSAGE_ACTION_MISSING = "Message action is missing" PNERR_MESSAGE_ACTION_TYPE_MISSING = "Message action type is missing" PNERR_MESSAGE_ACTION_VALUE_MISSING = "Message action value is missing" PNERR_MESSAGE_TIMETOKEN_MISSING = "Message timetoken is missing" PNERR_MESSAGE_ACTION_TIMETOKEN_MISSING = "Message action timetoken is missing" PNERR_HISTORY_MESSAGE_ACTIONS_MULTIPLE_CHANNELS = ( "History can return message action data for a single channel only. " "Either pass a single channel or disable the include_message_action" "s flag. " ) PNERR_PUSH_TOPIC_MISSING = ( "Push notification topic is missing. Required only if push type is APNS2." ) PNERR_FILE_OBJECT_MISSING = "File object is missing." PNERR_FILE_NAME_MISSING = "File name is missing." PNERR_FILE_ID_MISSING = "File id is missing." PNERR_SPACE_MISSING = "Space missing" PNERR_SPACES_MISSING = "Spaces missing" PNERR_USER_ID_MISSING = "user_id missing or not a string" PNERR_USER_SPACE_PAIRS_MISSING = "User/Space pair is missing" PNERR_MISUSE_OF_USER_AND_USERS = "user_id and users should not be used together" PNERR_MISUSE_OF_SPACE_AND_SPACES = "space_id and spaces should not be used together" PNERR_MISUSE_OF_USER_AND_SPACE = "user_id and space_id should not be used together" PNERR_INVALID_USER = "Provided user is not valid instance of User" PNERR_INVALID_SPACE = "Provided space is not valid instance of Space" bdraco-freenub-69809e8/pubnub/event_engine/000077500000000000000000000000001464523203000206265ustar00rootroot00000000000000bdraco-freenub-69809e8/pubnub/event_engine/__init__.py000066400000000000000000000000001464523203000227250ustar00rootroot00000000000000bdraco-freenub-69809e8/pubnub/event_engine/dispatcher.py000066400000000000000000000031321464523203000233250ustar00rootroot00000000000000from pubnub.event_engine import manage_effects from pubnub.event_engine.models import effects class Dispatcher: _pubnub = None _managed_effects_factory = None def __init__(self, event_engine) -> None: self._event_engine = event_engine self._managed_effects = {} self._effect_emitter = manage_effects.EmitEffect() def set_pn(self, pubnub_instance): self._pubnub = pubnub_instance self._effect_emitter.set_pn(pubnub_instance) def dispatch_effect(self, effect: effects.PNEffect): if not self._managed_effects_factory: self._managed_effects_factory = manage_effects.ManagedEffectFactory( self._pubnub, self._event_engine ) if isinstance(effect, effects.PNEmittableEffect): self.emit_effect(effect) elif isinstance(effect, effects.PNManageableEffect): self.dispatch_managed_effect(effect) elif isinstance(effect, effects.PNCancelEffect): self.dispatch_cancel_effect(effect) def emit_effect(self, effect: effects.PNEffect): self._effect_emitter.emit(effect) def dispatch_managed_effect(self, effect: effects.PNEffect): managed_effect = self._managed_effects_factory.create(effect) managed_effect.run() self._managed_effects[effect.__class__.__name__] = managed_effect def dispatch_cancel_effect(self, effect: effects.PNEffect): if effect.cancel_effect in self._managed_effects: self._managed_effects[effect.cancel_effect].stop() del self._managed_effects[effect.cancel_effect] bdraco-freenub-69809e8/pubnub/event_engine/manage_effects.py000066400000000000000000000273101464523203000241320ustar00rootroot00000000000000import asyncio import logging import math from queue import SimpleQueue from typing import Union from pubnub.endpoints.pubsub.subscribe import Subscribe from pubnub.enums import PNReconnectionPolicy from pubnub.event_engine.models import effects, events from pubnub.models.consumer.common import PNStatus from pubnub.models.consumer.pubsub import PNMessageResult from pubnub.models.server.subscribe import SubscribeMessage from pubnub.pubnub import PubNub class ManagedEffect: pubnub: PubNub = None event_engine = None effect: Union[effects.PNManageableEffect, effects.PNCancelEffect] stop_event = None def set_pn(self, pubnub: PubNub): self.pubnub = pubnub def __init__( self, pubnub_instance, event_engine_instance, effect: Union[effects.PNManageableEffect, effects.PNCancelEffect], ) -> None: self.effect = effect self.event_engine = event_engine_instance self.pubnub = pubnub_instance def run(self): pass def run_async(self): pass def stop(self): logging.debug(f"stop called on {self.__class__.__name__}") if self.stop_event: logging.debug( f"stop_event({id(self.stop_event)}).set() called on {self.__class__.__name__}" ) self.stop_event.set() def get_new_stop_event(self): event = asyncio.Event() logging.debug( f"creating new stop_event({id(event)}) for {self.__class__.__name__}" ) return event class ManageHandshakeEffect(ManagedEffect): def run(self): channels = self.effect.channels groups = self.effect.groups if hasattr(self.pubnub, "event_loop"): self.stop_event = self.get_new_stop_event() loop: asyncio.AbstractEventLoop = self.pubnub.event_loop if loop.is_running(): loop.create_task( self.handshake_async(channels, groups, self.stop_event) ) else: loop.run_until_complete( self.handshake_async(channels, groups, self.stop_event) ) else: # TODO: the synchronous way pass async def handshake_async(self, channels, groups, stop_event): request = ( Subscribe(self.pubnub) .channels(channels) .channel_groups(groups) .cancellation_event(stop_event) ) handshake = await request.future() if handshake.status.error: logging.warning(f"Handshake failed: {handshake.status.error_data.__dict__}") handshake_failure = events.HandshakeFailureEvent( handshake.status.error_data, 1 ) self.event_engine.trigger(handshake_failure) else: cursor = handshake.result["t"] timetoken = cursor["t"] region = cursor["r"] handshake_success = events.HandshakeSuccessEvent(timetoken, region) self.event_engine.trigger(handshake_success) class ManagedReceiveMessagesEffect(ManagedEffect): effect: effects.ReceiveMessagesEffect def run(self): channels = self.effect.channels groups = self.effect.groups timetoken = self.effect.timetoken region = self.effect.region if hasattr(self.pubnub, "event_loop"): self.stop_event = self.get_new_stop_event() loop: asyncio.AbstractEventLoop = self.pubnub.event_loop if loop.is_running(): loop.create_task( self.receive_messages_async(channels, groups, timetoken, region) ) else: loop.run_until_complete( self.receive_messages_async(channels, groups, timetoken, region) ) else: # TODO: the synchronous way pass async def receive_messages_async(self, channels, groups, timetoken, region): subscribe = Subscribe(self.pubnub) if channels: subscribe.channels(channels) if groups: subscribe.channel_groups(groups) if timetoken: subscribe.timetoken(timetoken) if region: subscribe.region(region) subscribe.cancellation_event(self.stop_event) response = await subscribe.future() if response and response.result: if not response.status.error: cursor = response.result["t"] timetoken = cursor["t"] region = cursor["r"] messages = response.result["m"] recieve_success = events.ReceiveSuccessEvent( timetoken, region=region, messages=messages ) self.event_engine.trigger(recieve_success) self.stop_event.set() class ManagedReconnectEffect(ManagedEffect): effect: effects.ReconnectEffect reconnection_policy: PNReconnectionPolicy give_up_event: events.PNFailureEvent failure_event: events.PNFailureEvent success_event: events.PNCursorEvent def __init__( self, pubnub_instance, event_engine_instance, effect: Union[effects.PNManageableEffect, effects.PNCancelEffect], ) -> None: super().__init__(pubnub_instance, event_engine_instance, effect) self.reconnection_policy = pubnub_instance.config.reconnect_policy self.interval = pubnub_instance.config.RECONNECTION_INTERVAL self.min_backoff = pubnub_instance.config.RECONNECTION_MIN_EXPONENTIAL_BACKOFF self.max_backoff = pubnub_instance.config.RECONNECTION_MAX_EXPONENTIAL_BACKOFF def calculate_reconnection_delay(self, attempt): if not attempt: attempt = 1 if self.reconnection_policy is PNReconnectionPolicy.LINEAR: delay = self.interval elif self.reconnection_policy is PNReconnectionPolicy.EXPONENTIAL: delay = int(math.pow(2, attempt - 5 * math.floor((attempt - 1) / 5)) - 1) return delay def run(self): if self.reconnection_policy is PNReconnectionPolicy.NONE: self.event_engine.trigger( self.give_up_event( reason=self.effect.reason, attempt=self.effect.attempts ) ) else: attempt = self.effect.attempts delay = self.calculate_reconnection_delay(attempt) logging.warning(f"will reconnect in {delay}s") if hasattr(self.pubnub, "event_loop"): loop: asyncio.AbstractEventLoop = self.pubnub.event_loop if loop.is_running(): self.delayed_reconnect_coro = loop.create_task( self.delayed_reconnect_async(delay, attempt) ) else: self.delayed_reconnect_coro = loop.run_until_complete( self.delayed_reconnect_async(delay, attempt) ) else: # TODO: the synchronous way pass async def delayed_reconnect_async(self, delay, attempt): self.stop_event = self.get_new_stop_event() await asyncio.sleep(delay) request = ( Subscribe(self.pubnub) .channels(self.effect.channels) .channel_groups(self.effect.groups) .cancellation_event(self.stop_event) ) if self.effect.timetoken: request.timetoken(self.effect.timetoken) if self.effect.region: request.region(self.effect.region) reconnect = await request.future() if reconnect.status.error: logging.warning(f"Reconnect failed: {reconnect.status.error_data.__dict__}") reconnect_failure = self.failure_event(reconnect.status.error_data, attempt) self.event_engine.trigger(reconnect_failure) else: cursor = reconnect.result["t"] timetoken = cursor["t"] region = cursor["r"] reconnect_success = self.success_event(timetoken, region) self.event_engine.trigger(reconnect_success) def stop(self): logging.debug(f"stop called on {self.__class__.__name__}") if self.stop_event: logging.debug( f"stop_event({id(self.stop_event)}).set() called on {self.__class__.__name__}" ) self.stop_event.set() if self.delayed_reconnect_coro: try: self.delayed_reconnect_coro.cancel() except asyncio.exceptions.CancelledError: pass class ManagedHandshakeReconnectEffect(ManagedReconnectEffect): def __init__( self, pubnub_instance, event_engine_instance, effect: Union[effects.PNManageableEffect, effects.PNCancelEffect], ) -> None: self.give_up_event = events.HandshakeReconnectGiveupEvent self.failure_event = events.HandshakeReconnectFailureEvent self.success_event = events.HandshakeReconnectSuccessEvent super().__init__(pubnub_instance, event_engine_instance, effect) class ManagedReceiveReconnectEffect(ManagedReconnectEffect): def __init__( self, pubnub_instance, event_engine_instance, effect: Union[effects.PNManageableEffect, effects.PNCancelEffect], ) -> None: self.give_up_event = events.HandshakeReconnectGiveupEvent self.failure_event = events.HandshakeReconnectFailureEvent self.success_event = events.HandshakeReconnectSuccessEvent super().__init__(pubnub_instance, event_engine_instance, effect) class ManagedEffectFactory: _managed_effects = { effects.HandshakeEffect.__name__: ManageHandshakeEffect, effects.ReceiveMessagesEffect.__name__: ManagedReceiveMessagesEffect, effects.HandshakeReconnectEffect.__name__: ManagedHandshakeReconnectEffect, } def __init__(self, pubnub_instance, event_engine_instance) -> None: self._pubnub = pubnub_instance self._event_engine = event_engine_instance def create(self, effect: ManagedEffect): if effect.__class__.__name__ not in self._managed_effects: # TODO replace below with raise unsupported managed effect exception return ManagedEffect(self._pubnub, self._event_engine, effect) return self._managed_effects[effect.__class__.__name__]( self._pubnub, self._event_engine, effect ) class EmitEffect: pubnub: PubNub def set_pn(self, pubnub: PubNub): self.pubnub = pubnub self.queue = SimpleQueue def emit(self, effect: effects.PNEmittableEffect): if isinstance(effect, effects.EmitMessagesEffect): self.emit_message(effect) if isinstance(effect, effects.EmitStatusEffect): self.emit_status(effect) def emit_message(self, effect: effects.EmitMessagesEffect): for message in effect.messages: subscribe_message = SubscribeMessage().from_json(message) pn_message_result = PNMessageResult( message=subscribe_message.payload, subscription=subscribe_message.subscription_match, channel=subscribe_message.channel, timetoken=int(message["p"]["t"]), user_metadata=subscribe_message.publish_metadata, publisher=subscribe_message.issuing_client_id, ) self.pubnub._subscription_manager._listener_manager.announce_message( pn_message_result ) def emit_status(self, effect: effects.EmitStatusEffect): pn_status = PNStatus() pn_status.category = effect.status pn_status.error = False self.pubnub._subscription_manager._listener_manager.announce_status(pn_status) bdraco-freenub-69809e8/pubnub/event_engine/models/000077500000000000000000000000001464523203000221115ustar00rootroot00000000000000bdraco-freenub-69809e8/pubnub/event_engine/models/__init__.py000066400000000000000000000000001464523203000242100ustar00rootroot00000000000000bdraco-freenub-69809e8/pubnub/event_engine/models/effects.py000066400000000000000000000046001464523203000241020ustar00rootroot00000000000000from typing import Union from pubnub.enums import PNStatusCategory from pubnub.exceptions import PubNubException class PNEffect: pass class PNManageableEffect(PNEffect): pass class PNCancelEffect(PNEffect): cancel_effect: str class HandshakeEffect(PNManageableEffect): def __init__( self, channels: Union[None, list[str]] = None, groups: Union[None, list[str]] = None, ) -> None: super().__init__() self.channels = channels self.groups = groups class CancelHandshakeEffect(PNCancelEffect): cancel_effect = HandshakeEffect.__name__ class ReceiveMessagesEffect(PNManageableEffect): def __init__( self, channels: Union[None, list[str]] = None, groups: Union[None, list[str]] = None, timetoken: Union[None, str] = None, region: Union[None, int] = None, ) -> None: super().__init__() self.channels = channels self.groups = groups self.timetoken = timetoken self.region = region class CancelReceiveMessagesEffect(PNCancelEffect): cancel_effect = ReceiveMessagesEffect.__name__ class ReconnectEffect(PNManageableEffect): def __init__( self, channels: Union[None, list[str]] = None, groups: Union[None, list[str]] = None, timetoken: Union[None, str] = None, region: Union[None, int] = None, attempts: Union[None, int] = None, reason: Union[None, PubNubException] = None, ) -> None: self.channels = channels self.groups = groups self.attempts = attempts self.reason = reason self.timetoken = timetoken self.region = region class HandshakeReconnectEffect(ReconnectEffect): pass class CancelHandshakeReconnectEffect(PNCancelEffect): cancel_effect = HandshakeReconnectEffect.__name__ class ReceiveReconnectEffect(ReconnectEffect): pass class CancelReceiveReconnectEffect(PNCancelEffect): cancel_effect = ReceiveReconnectEffect.__name__ class PNEmittableEffect(PNEffect): pass class EmitMessagesEffect(PNEmittableEffect): def __init__(self, messages: Union[None, list[str]]) -> None: super().__init__() self.messages = messages class EmitStatusEffect(PNEmittableEffect): def __init__(self, status: Union[None, PNStatusCategory]) -> None: super().__init__() self.status = status bdraco-freenub-69809e8/pubnub/event_engine/models/events.py000066400000000000000000000047371464523203000240020ustar00rootroot00000000000000from typing import Optional from pubnub.exceptions import PubNubException class PNEvent: def get_name(self) -> str: return self.__class__.__name__ class PNFailureEvent(PNEvent): def __init__(self, reason: PubNubException, attempt: int) -> None: self.reason = reason self.attempt = attempt super().__init__() class PNCursorEvent(PNEvent): def __init__(self, timetoken: str, region: Optional[int] = None) -> None: self.timetoken = timetoken self.region = region class PNChannelGroupsEvent(PNEvent): def __init__(self, channels: list[str], groups: list[str]) -> None: self.channels = channels self.groups = groups class SubscriptionChangedEvent(PNChannelGroupsEvent): def __init__(self, channels: list[str], groups: list[str]) -> None: PNChannelGroupsEvent.__init__(self, channels, groups) class SubscriptionRestoredEvent(PNCursorEvent, PNChannelGroupsEvent): def __init__( self, timetoken: str, channels: list[str], groups: list[str], region: Optional[int] = None, ) -> None: PNCursorEvent.__init__(self, timetoken, region) PNChannelGroupsEvent.__init__(self, channels, groups) class HandshakeSuccessEvent(PNCursorEvent): def __init__(self, timetoken: str, region: Optional[int] = None) -> None: super().__init__(timetoken, region) class HandshakeFailureEvent(PNFailureEvent): pass class HandshakeReconnectSuccessEvent(PNCursorEvent): pass class HandshakeReconnectFailureEvent(PNFailureEvent): pass class HandshakeReconnectGiveupEvent(PNFailureEvent): pass class HandshakeReconnectRetryEvent(PNEvent): pass class ReceiveSuccessEvent(PNCursorEvent): def __init__( self, timetoken: str, messages: list, region: Optional[int] = None ) -> None: PNCursorEvent.__init__(self, timetoken, region) self.messages = messages class ReceiveFailureEvent(PNFailureEvent): pass class ReceiveReconnectSuccessEvent(PNCursorEvent): def __init__( self, timetoken: str, messages: list, region: Optional[int] = None ) -> None: PNCursorEvent.__init__(self, timetoken, region) self.messages = messages class ReceiveReconnectFailureEvent(PNFailureEvent): pass class ReceiveReconnectGiveupEvent(PNFailureEvent): pass class ReceiveReconnectRetryEvent(PNEvent): pass class DisconnectEvent(PNEvent): pass class ReconnectEvent(PNEvent): pass bdraco-freenub-69809e8/pubnub/event_engine/models/states.py000066400000000000000000000453321464523203000237750ustar00rootroot00000000000000from typing import Union from pubnub.enums import PNStatusCategory from pubnub.event_engine.models import effects, events from pubnub.event_engine.models.effects import PNEffect from pubnub.exceptions import PubNubException class PNContext(dict): channels: list groups: list region: int timetoken: str attempt: int reason: PubNubException def update(self, context): super().update(context.__dict__) class PNState: _context: PNContext def __init__(self, context: PNContext) -> None: self._context = context self._transitions = dict def on(self, event: events.PNEvent, context: PNContext): return self._transitions[event.get_name()](event, context) def on_enter(self, context: Union[None, PNContext]): pass def on_exit(self): pass def get_context(self) -> PNContext: return self._context class PNTransition: context: PNContext state: PNState effect: Union[None, list[PNEffect]] def __init__( self, state: PNState, context: Union[None, PNContext] = None, effect: Union[None, list[PNEffect]] = None, ) -> None: self.context = context self.state = state self.effect = effect class UnsubscribedState(PNState): def __init__(self, context: PNContext) -> None: super().__init__(context) self._context.attempt = 0 self._transitions = { events.SubscriptionChangedEvent.__name__: self.subscription_changed, events.SubscriptionRestoredEvent.__name__: self.subscription_restored, } def subscription_changed( self, event: events.SubscriptionChangedEvent, context: PNContext ) -> PNTransition: self._context.update(context) self._context.channels = event.channels self._context.groups = event.groups self._context.timetoken = 0 return PNTransition(state=HandshakingState, context=self._context) def subscription_restored( self, event: events.SubscriptionRestoredEvent, context: PNContext ) -> PNTransition: self._context.update(context) self._context.channels = event.channels self._context.groups = event.groups self._context.timetoken = event.timetoken self._context.region = event.region return PNTransition(state=ReceivingState, context=self._context) class HandshakingState(PNState): def __init__(self, context: PNContext) -> None: super().__init__(context) self._transitions = { events.HandshakeFailureEvent.__name__: self.reconnecting, events.DisconnectEvent.__name__: self.disconnect, events.HandshakeSuccessEvent.__name__: self.handshaking_success, events.SubscriptionRestoredEvent.__name__: self.subscription_restored, events.SubscriptionChangedEvent.__name__: self.subscription_changed, } def on_enter(self, context: Union[None, PNContext]): self._context.update(context) super().on_enter(self._context) return effects.HandshakeEffect(self._context.channels, self._context.groups) def on_exit(self): super().on_exit() return effects.CancelHandshakeEffect() def subscription_changed( self, event: events.SubscriptionChangedEvent, context: PNContext ) -> PNTransition: self._context.update(context) self._context.channels = event.channels self._context.groups = event.groups self._context.timetoken = 0 return PNTransition(state=HandshakingState, context=self._context) def subscription_restored( self, event: events.SubscriptionRestoredEvent, context: PNContext ) -> PNTransition: self._context.update(context) self._context.channels = event.channels self._context.groups = event.groups self._context.timetoken = event.timetoken self._context.region = event.region return PNTransition(state=ReceivingState, context=self._context) def reconnecting( self, event: events.HandshakeFailureEvent, context: PNContext ) -> PNTransition: self._context.update(context) self._context.attempt = event.attempt self._context.reason = event.reason return PNTransition(state=HandshakeReconnectingState, context=self._context) def disconnect( self, event: events.DisconnectEvent, context: PNContext ) -> PNTransition: self._context.update(context) self._context.timetoken = 0 return PNTransition(state=HandshakeStoppedState, context=self._context) def handshaking_success( self, event: events.HandshakeSuccessEvent, context: PNContext ) -> PNTransition: self._context.update(context) self._context.timetoken = event.timetoken self._context.region = event.region return PNTransition( state=ReceivingState, context=self._context, effect=effects.EmitStatusEffect(PNStatusCategory.PNConnectedCategory), ) class HandshakeReconnectingState(PNState): def __init__(self, context: PNContext) -> None: super().__init__(context) self._transitions = { events.DisconnectEvent.__name__: self.disconnect, events.HandshakeReconnectGiveupEvent.__name__: self.give_up, events.HandshakeReconnectSuccessEvent.__name__: self.success, events.SubscriptionRestoredEvent.__name__: self.subscription_restored, events.HandshakeReconnectFailureEvent.__name__: self.handshake_reconnect, events.SubscriptionChangedEvent.__name__: self.subscription_changed, } def on_enter(self, context: Union[None, PNContext]): self._context.update(context) super().on_enter(self._context) return effects.HandshakeReconnectEffect( self._context.channels, self._context.groups, attempts=self._context.attempt, reason=self._context.reason, ) def on_exit(self): super().on_exit() return effects.CancelHandshakeReconnectEffect() def disconnect( self, event: events.DisconnectEvent, context: PNContext ) -> PNTransition: self._context.update(context) return PNTransition(state=HandshakeStoppedState, context=self._context) def subscription_changed( self, event: events.SubscriptionChangedEvent, context: PNContext ) -> PNTransition: self._context.update(context) self._context.channels = event.channels self._context.groups = event.groups self._context.timetoken = 0 return PNTransition(state=HandshakeReconnectingState, context=self._context) def handshake_reconnect( self, event: events.HandshakeReconnectFailureEvent, context: PNContext ) -> PNTransition: self._context.update(context) self._context.attempt = event.attempt + 1 self._context.reason = event.reason return PNTransition(state=HandshakeReconnectingState, context=self._context) def give_up( self, event: events.HandshakeReconnectGiveupEvent, context: PNContext ) -> PNTransition: self._context.update(context) self._context.attempt = event.attempt self._context.reason = event.reason return PNTransition(state=HandshakeFailedState, context=self._context) def subscription_restored( self, event: events.SubscriptionRestoredEvent, context: PNContext ) -> PNTransition: self._context.update(context) self._context.channels = event.channels self._context.groups = event.groups self._context.timetoken = event.timetoken self._context.region = event.region return PNTransition(state=ReceivingState, context=self._context) def success( self, event: events.HandshakeReconnectSuccessEvent, context: PNContext ) -> PNTransition: self._context.update(context) self._context.timetoken = event.timetoken self._context.region = event.region return PNTransition( state=ReceivingState, context=self._context, effect=effects.EmitStatusEffect( PNStatusCategory.PNConnectedCategory, ), ) class HandshakeFailedState(PNState): def __init__(self, context: PNContext) -> None: super().__init__(context) self._transitions = { events.SubscriptionChangedEvent.__name__: self.subscription_changed, events.ReconnectEvent.__name__: self.reconnect, events.SubscriptionRestoredEvent.__name__: self.subscription_restored, } def subscription_changed( self, event: events.SubscriptionChangedEvent, context: PNContext ) -> PNTransition: self._context.update(context) self._context.channels = event.channels self._context.groups = event.groups self._context.timetoken = 0 return PNTransition(state=HandshakingState, context=self._context) def reconnect( self, event: events.ReconnectEvent, context: PNContext ) -> PNTransition: self._context.update(context) return PNTransition(state=HandshakingState, context=self._context) def subscription_restored( self, event: events.SubscriptionRestoredEvent, context: PNContext ) -> PNTransition: self._context.update(context) self._context.channels = event.channels self._context.groups = event.groups self._context.timetoken = event.timetoken self._context.region = event.region return PNTransition(state=ReceivingState, context=self._context) class HandshakeStoppedState(PNState): def __init__(self, context: PNContext) -> None: super().__init__(context) self._context.attempt = 0 self._transitions = {events.ReconnectEvent.__name__: self.reconnect} def reconnect( self, event: events.ReconnectEvent, context: PNContext ) -> PNTransition: self._context.update(context) return PNTransition(state=HandshakeReconnectingState, context=self._context) class ReceivingState(PNState): def __init__(self, context: PNContext) -> None: super().__init__(context) self._context.attempt = 0 self._transitions = { events.SubscriptionChangedEvent.__name__: self.subscription_changed, events.SubscriptionRestoredEvent.__name__: self.subscription_restored, events.ReceiveSuccessEvent.__name__: self.receiving_success, events.ReceiveFailureEvent.__name__: self.receiving_failure, events.DisconnectEvent.__name__: self.disconnect, events.ReconnectEvent.__name__: self.reconnect, } def on_enter(self, context: Union[None, PNContext]): super().on_enter(context) return effects.ReceiveMessagesEffect( context.channels, context.groups, timetoken=self._context.timetoken, region=self._context.region, ) def on_exit(self): super().on_exit() return effects.CancelReceiveMessagesEffect() def subscription_changed( self, event: events.SubscriptionChangedEvent, context: PNContext ) -> PNTransition: self._context.update(context) self._context.channels = event.channels self._context.groups = event.groups self._context.timetoken = 0 return PNTransition(state=self.__class__, context=self._context) def subscription_restored( self, event: events.SubscriptionRestoredEvent, context: PNContext ) -> PNTransition: self._context.update(context) self._context.channels = event.channels self._context.groups = event.groups self._context.timetoken = event.timetoken self._context.region = event.region return PNTransition(state=self.__class__, context=self._context) def receiving_success( self, event: events.ReceiveSuccessEvent, context: PNContext ) -> PNTransition: self._context.update(context) self._context.timetoken = event.timetoken self._context.region = event.region return PNTransition( state=self.__class__, context=self._context, effect=[ effects.EmitMessagesEffect(messages=event.messages), effects.EmitStatusEffect(PNStatusCategory.PNConnectedCategory), ], ) def receiving_failure( self, event: events.ReceiveFailureEvent, context: PNContext ) -> PNTransition: self._context.update(context) self._context.reason = event.reason return PNTransition(state=ReceiveReconnectingState, context=self._context) def disconnect( self, event: events.DisconnectEvent, context: PNContext ) -> PNTransition: self._context.update(context) return PNTransition( state=ReceiveStoppedState, context=self._context, effect=effects.EmitStatusEffect(PNStatusCategory.PNDisconnectedCategory), ) def reconnect( self, event: events.ReconnectEvent, context: PNContext ) -> PNTransition: self._context.update(context) return PNTransition(state=ReceivingState, context=self._context) class ReceiveReconnectingState(PNState): def __init__(self, context: PNContext) -> None: super().__init__(context) self._transitions = { events.ReceiveReconnectFailureEvent.__name__: self.reconnect_failure, events.SubscriptionChangedEvent.__name__: self.subscription_changed, events.DisconnectEvent.__name__: self.disconnect, events.ReceiveReconnectGiveupEvent.__name__: self.give_up, events.ReceiveReconnectSuccessEvent.__name__: self.reconnect_success, events.SubscriptionRestoredEvent.__name__: self.subscription_restored, } def on_enter(self, context: Union[None, PNContext]): self._context.update(context) super().on_enter(self._context) return effects.ReceiveReconnectEffect( self._context.channels, self._context.groups, self._context.timetoken, self._context.region, self._context.attempt, self._context.reason, ) def on_exit(self): super().on_exit() return effects.CancelReceiveReconnectEffect() def reconnect_failure( self, event: events.ReceiveReconnectFailureEvent, context: PNContext ) -> PNTransition: self._context.update(context) self._context.attempt = event.attempt + 1 self._context.reason = event.reason return PNTransition(state=ReceiveReconnectingState, context=self._context) def subscription_changed( self, event: events.SubscriptionChangedEvent, context: PNContext ) -> PNTransition: self._context.update(context) self._context.channels = event.channels self._context.groups = event.groups self._context.timetoken = 0 return PNTransition(state=ReceiveReconnectingState, context=self._context) def disconnect( self, event: events.DisconnectEvent, context: PNContext ) -> PNTransition: self._context.update(context) return PNTransition(state=ReceiveStoppedState, context=self._context) def give_up( self, event: events.ReceiveReconnectGiveupEvent, context: PNContext ) -> PNTransition: self._context.update(context) self._context.reason = event.reason self._context.attempt = event.attempt return PNTransition( state=ReceiveFailedState, context=self._context, effect=effects.EmitStatusEffect(PNStatusCategory.PNDisconnectedCategory), ) def reconnect_success( self, event: events.ReceiveReconnectSuccessEvent, context: PNContext ) -> PNTransition: self._context.update(context) self._context.timetoken = event.timetoken self._context.region = event.region return PNTransition( state=ReceivingState, context=self._context, effect=[ effects.EmitMessagesEffect(event.messages), effects.EmitStatusEffect(PNStatusCategory.PNConnectedCategory), ], ) def subscription_restored( self, event: events.SubscriptionRestoredEvent, context: PNContext ) -> PNTransition: self._context.update(context) self._context.channels = event.channels self._context.groups = event.groups self._context.timetoken = event.timetoken self._context.region = event.region return PNTransition(state=ReceiveReconnectingState, context=self._context) class ReceiveFailedState(PNState): def __init__(self, context: PNContext) -> None: super().__init__(context) self._transitions = { events.ReceiveReconnectRetryEvent.__name__: self.reconnect_retry, events.SubscriptionChangedEvent.__name__: self.subscription_changed, events.SubscriptionRestoredEvent.__name__: self.subscription_restored, events.ReconnectEvent.__name__: self.reconnect, } def reconnect_retry( self, event: events.ReceiveReconnectRetryEvent, context: PNContext ) -> PNTransition: self._context.update(context) return PNTransition(state=ReceiveReconnectingState, context=self._context) def subscription_changed( self, event: events.SubscriptionChangedEvent, context: PNContext ) -> PNTransition: self._context.update(context) self._context.channels = event.channels self._context.groups = event.groups self._context.timetoken = 0 return PNTransition(state=ReceivingState, context=self._context) def reconnect( self, event: events.ReconnectEvent, context: PNContext ) -> PNTransition: self._context.update(context) return PNTransition(state=ReceivingState, context=self._context) def subscription_restored( self, event: events.SubscriptionRestoredEvent, context: PNContext ) -> PNTransition: self._context.update(context) self._context.channels = event.channels self._context.groups = event.groups self._context.timetoken = event.timetoken self._context.region = event.region return PNTransition(state=ReceivingState, context=self._context) class ReceiveStoppedState(PNState): def __init__(self, context: PNContext) -> None: super().__init__(context) self._context.attempt = 0 self._transitions = {events.ReconnectEvent.__name__: self.reconnect} def reconnect( self, event: events.ReconnectEvent, context: PNContext ) -> PNTransition: self._context.update(context) return PNTransition(state=ReceiveReconnectingState, context=self._context) bdraco-freenub-69809e8/pubnub/event_engine/statemachine.py000066400000000000000000000076231464523203000236550ustar00rootroot00000000000000"""StateMachine.""" import logging from typing import Optional from pubnub.event_engine.dispatcher import Dispatcher from pubnub.event_engine.models import effects, events, states class StateMachine: _current_state: states.PNState _context: states.PNContext _effect_list: list[effects.PNEffect] _enabled: bool def __init__( self, initial_state: states.PNState, dispatcher_class: Optional[Dispatcher] = None, ) -> None: self._context = states.PNContext() self._current_state = initial_state(self._context) self._listeners = {} self._effect_list = [] if dispatcher_class is None: dispatcher_class = Dispatcher self._dispatcher = dispatcher_class(self) self._enabled = True def get_state_name(self): return self._current_state.__class__.__name__ def get_context(self) -> states.PNContext: return self._current_state._context def get_dispatcher(self) -> Dispatcher: return self._dispatcher def trigger(self, event: events.PNEvent) -> states.PNTransition: logging.debug( f"Triggered {event.__class__.__name__}({event.__dict__}) on {self.get_state_name()}" ) if not self._enabled: logging.error("EventEngine is not enabled") return False if event.get_name() in self._current_state._transitions: self._effect_list.clear() effect = self._current_state.on_exit() logging.debug(f"On exit effect: {effect.__class__.__name__}") if effect: self._effect_list.append(effect) transition: states.PNTransition = self._current_state.on( event, self._context ) self._current_state = transition.state(self._current_state.get_context()) self._context = transition.context if transition.effect: if isinstance(transition.effect, list): logging.debug("unpacking list") for effect in transition.effect: logging.debug(f"Transition effect: {effect.__class__.__name__}") self._effect_list.append(effect) else: logging.debug( f"Transition effect: {transition.effect.__class__.__name__}" ) self._effect_list.append(transition.effect) effect = self._current_state.on_enter(self._context) if effect: logging.debug(f"On enter effect: {effect.__class__.__name__}") self._effect_list.append(effect) else: # we're ignoring events unhandled logging.debug( f"unhandled event?? {event.__class__.__name__} in {self._current_state.__class__.__name__}" ) self.stop() self.dispatch_effects() def dispatch_effects(self): for effect in self._effect_list: logging.debug(f"dispatching {effect.__class__.__name__} {id(effect)}") self._dispatcher.dispatch_effect(effect) self._effect_list.clear() def stop(self): self._enabled = False if __name__ == "__main__": machine = StateMachine(states.UnsubscribedState) logging.debug(f"machine initialized. Current state: {machine.get_state_name()}") effect = machine.trigger( events.SubscriptionChangedEvent(channels=["fail"], groups=[]) ) machine.add_listener( effects.PNEffect, lambda x: logging.debug(f"Catch All Logger: {effect.__dict__}"), ) machine.add_listener( effects.EmitMessagesEffect, ) effect = machine.trigger(events.DisconnectEvent()) logging.debug( f"SubscriptionChangedEvent triggered with channels=[`fail`]. Curr state: {machine.get_state_name()}" ) logging.debug(f"effect queue: {machine._effect_list}") bdraco-freenub-69809e8/pubnub/exceptions.py000066400000000000000000000012251464523203000207130ustar00rootroot00000000000000class PubNubException(Exception): def __init__(self, errormsg="", status_code=0, pn_error=None, status=None): self._errormsg = errormsg self._status_code = status_code self._pn_error = pn_error self.status = status if len(str(errormsg)) > 0 and int(status_code) > 0: msg = str(pn_error) + " (" + str(status_code) + "): " + str(errormsg) elif len(str(errormsg)) > 0: msg = str(pn_error) + ": " + str(errormsg) else: msg = str(pn_error) super().__init__(msg) @property def _status(self): raise DeprecationWarning return self.status bdraco-freenub-69809e8/pubnub/features.py000066400000000000000000000007721464523203000203560ustar00rootroot00000000000000from os import getenv from pubnub.exceptions import PubNubException flags = {"PN_ENABLE_ENTITIES": getenv("PN_ENABLE_ENTITIES", False)} def feature_flag(flag): def not_implemented(*args, **kwargs): raise PubNubException(errormsg="This feature is not enabled") def inner(method): if flag not in flags.keys(): raise PubNubException(errormsg="Flag not supported") if not flags[flag]: return not_implemented return method return inner bdraco-freenub-69809e8/pubnub/managers.py000066400000000000000000000467461464523203000203500ustar00rootroot00000000000000import base64 import copy import logging import math import time from abc import ABCMeta, abstractmethod from cbor2 import loads from . import utils from .callbacks import ReconnectionCallback, SubscribeCallback from .dtos import SubscribeOperation, UnsubscribeOperation from .enums import PNOperationType, PNReconnectionPolicy, PNStatusCategory from .errors import PNERR_INVALID_ACCESS_TOKEN from .exceptions import PubNubException from .models.consumer.common import PNStatus from .models.server.subscribe import SubscribeEnvelope from .models.subscription_item import SubscriptionItem logger = logging.getLogger("pubnub") class PublishSequenceManager: def __init__(self, provided_max_sequence): self.max_sequence = provided_max_sequence self.next_sequence = 0 @abstractmethod def get_next_sequence(self): if self.max_sequence == self.next_sequence: self.next_sequence = 1 else: self.next_sequence += 1 return self.next_sequence class BasePathManager: MAX_SUBDOMAIN = 20 DEFAULT_SUBDOMAIN = "pubsub" DEFAULT_BASE_PATH = "pubnub.com" def __init__(self, initial_config): self.config = initial_config self._current_subdomain = 1 def get_base_path(self): if self.config.origin: return self.config.origin else: return f"{BasePathManager.DEFAULT_SUBDOMAIN}.{BasePathManager.DEFAULT_BASE_PATH}" class ReconnectionManager: INTERVAL = 3 MINEXPONENTIALBACKOFF = 1 MAXEXPONENTIALBACKOFF = 32 def __init__(self, pubnub): self._pubnub = pubnub self._callback = None self._timer = None self._timer_interval = None self._connection_errors = 1 def set_reconnection_listener(self, reconnection_callback): assert isinstance(reconnection_callback, ReconnectionCallback) self._callback = reconnection_callback def _recalculate_interval(self): if self._pubnub.config.reconnect_policy == PNReconnectionPolicy.EXPONENTIAL: self._timer_interval = int(math.pow(2, self._connection_errors) - 1) if self._timer_interval > self.MAXEXPONENTIALBACKOFF: self._timer_interval = self.MINEXPONENTIALBACKOFF self._connection_errors = 1 logger.debug( "timerInterval > MAXEXPONENTIALBACKOFF at: %s" % utils.datetime_now() ) elif self._timer_interval < 1: self._timer_interval = self.MINEXPONENTIALBACKOFF logger.debug( "timerInterval = %d at: %s" % (self._timer_interval, utils.datetime_now()) ) else: self._timer_interval = self.INTERVAL @abstractmethod def start_polling(self): pass def _stop_heartbeat_timer(self): if self._timer is not None: self._timer.stop() self._timer = None class StateManager: def __init__(self): self._channels = {} self._groups = {} self._presence_channels = {} self._presence_groups = {} def is_empty(self): return ( len(self._channels) == 0 and len(self._groups) == 0 and len(self._presence_channels) == 0 and len(self._presence_groups) == 0 ) def subscribed_to_the_only_channel(self): return ( len(self._channels) == 1 and len(self._groups) == 0 and len(self._presence_channels) == 0 and len(self._presence_groups) == 0 ) def prepare_channel_list(self, include_presence): return StateManager._prepare_membership_list( self._channels, self._presence_channels, include_presence ) def prepare_channel_group_list(self, include_presence): return StateManager._prepare_membership_list( self._groups, self._presence_groups, include_presence ) def adapt_subscribe_builder(self, subscribe_operation): for channel in subscribe_operation.channels: self._channels[channel] = SubscriptionItem(name=channel) if subscribe_operation.presence_enabled: self._presence_channels[channel] = SubscriptionItem(name=channel) for group in subscribe_operation.channel_groups: self._groups[group] = SubscriptionItem(name=group) if subscribe_operation.presence_enabled: self._presence_groups[group] = SubscriptionItem(name=group) def adapt_unsubscribe_builder(self, unsubscribe_operation): for channel in unsubscribe_operation.channels: self._channels.pop(channel, None) if channel in self._presence_channels: self._presence_channels.pop(channel, None) for group in unsubscribe_operation.channel_groups: self._groups.pop(group) if group in self._presence_groups: self._presence_groups.pop(group) def adapt_state_builder(self, state_operation): for channel in state_operation.channels: subscribed_channel = self._channels.get(channel) if subscribed_channel is not None: subscribed_channel.state = state_operation.state for group in state_operation.channel_groups: subscribed_group = self._channels.get(group) if subscribed_group is not None: subscribed_group.state = state_operation.state def state_payload(self): state = {} for channel in self._channels.values(): if channel.state is not None: state[channel.name] = channel.state for group in self._groups.values(): if group.state is not None: state[group.name] = group.state return state @staticmethod def _prepare_membership_list(data_storage, presence_storage, include_presence): response = [] for item in data_storage.values(): response.append(item.name) if include_presence: for item in presence_storage.values(): response.append(item.name + "-pnpres") return response class ListenerManager: def __init__(self, pubnub_instance): self._pubnub = pubnub_instance self._listeners = [] def add_listener(self, listener): assert isinstance(listener, SubscribeCallback) self._listeners.append(listener) def remove_listener(self, listener): assert isinstance(listener, SubscribeCallback) self._listeners.remove(listener) def announce_status(self, status): for callback in self._listeners: callback.status(self._pubnub, status) def announce_message(self, message): for callback in self._listeners: callback.message(self._pubnub, message) def announce_signal(self, signal): for callback in self._listeners: callback.signal(self._pubnub, signal) def announce_channel(self, channel): for callback in self._listeners: callback.channel(self._pubnub, channel) def announce_uuid(self, uuid): for callback in self._listeners: callback.uuid(self._pubnub, uuid) def announce_membership(self, membership): for callback in self._listeners: callback.membership(self._pubnub, membership) def announce_message_action(self, message_action): for callback in self._listeners: callback.message_action(self._pubnub, message_action) def announce_presence(self, presence): for callback in self._listeners: callback.presence(self._pubnub, presence) def announce_file_message(self, file_message): for callback in self._listeners: callback.file(self._pubnub, file_message) class SubscriptionManager: __metaclass__ = ABCMeta HEARTBEAT_INTERVAL_MULTIPLIER = 1000 def __init__(self, pubnub_instance): self._pubnub = pubnub_instance self._subscription_status_announced = False self._subscription_state = StateManager() self._listener_manager = ListenerManager(self._pubnub) self._timetoken = 0 self._region = None self._should_stop = False self._subscribe_request_task = None self._heartbeat_call = None @abstractmethod def _start_worker(self): pass @abstractmethod def _set_consumer_event(self): pass @abstractmethod def _message_queue_put(self, message): pass @abstractmethod def _start_subscribe_loop(self): pass @abstractmethod def _stop_subscribe_loop(self): pass @abstractmethod def _stop_heartbeat_timer(self): pass @abstractmethod def _perform_heartbeat_loop(self): pass @abstractmethod def _send_leave(self, unsubscribe_operation): pass def add_listener(self, listener): self._listener_manager.add_listener(listener) def remove_listener(self, listener): self._listener_manager.remove_listener(listener) def get_subscribed_channels(self): return self._subscription_state.prepare_channel_list(False) def get_subscribed_channel_groups(self): return self._subscription_state.prepare_channel_group_list(False) def unsubscribe_all(self): self.adapt_unsubscribe_builder( UnsubscribeOperation( channels=self._subscription_state.prepare_channel_list(False), channel_groups=self._subscription_state.prepare_channel_group_list( False ), ) ) def adapt_subscribe_builder(self, subscribe_operation): assert isinstance(subscribe_operation, SubscribeOperation) self._subscription_state.adapt_subscribe_builder(subscribe_operation) self._subscription_status_announced = False if subscribe_operation.timetoken is not None: self._timetoken = subscribe_operation.timetoken self.reconnect() def adapt_unsubscribe_builder(self, unsubscribe_operation): assert isinstance(unsubscribe_operation, UnsubscribeOperation) self._subscription_state.adapt_unsubscribe_builder(unsubscribe_operation) if not self._pubnub.config.suppress_leave_events: self._send_leave(unsubscribe_operation) if self._subscription_state.is_empty(): self._region = None self._timetoken = 0 self.reconnect() def adapt_state_builder(self, state_operation): self._subscription_state.adapt_state_builder(state_operation) self.reconnect() @abstractmethod def reconnect(self): pass def stop(self): self._should_stop = True self._stop_subscribe_loop() self._stop_heartbeat_timer() self._set_consumer_event() def _handle_endpoint_call(self, raw_result, status): assert isinstance(status, PNStatus) if not self._subscription_status_announced: pn_status = PNStatus() pn_status.category = PNStatusCategory.PNConnectedCategory pn_status.status_code = status.status_code pn_status.auth_key = status.auth_key pn_status.operation = status.operation pn_status.client_request = status.client_request pn_status.origin = status.origin pn_status.tls_enabled = status.tls_enabled pn_status.affected_channels = status.affected_channels pn_status.affected_groups = status.affected_groups self._subscription_status_announced = True self._listener_manager.announce_status(pn_status) result = SubscribeEnvelope.from_json(raw_result) only_channel = self._subscription_state.subscribed_to_the_only_channel() if result.messages is not None and len(result.messages) > 0: for message in result.messages: if only_channel: message.only_channel_subscription = True self._message_queue_put(message) self._timetoken = int(result.metadata.timetoken) self._region = int(result.metadata.region) # TODO: make abstract def _register_heartbeat_timer(self): self._stop_heartbeat_timer() class TelemetryManager: TIMESTAMP_DIVIDER = 1000 MAXIMUM_LATENCY_DATA_AGE = 60 CLEAN_UP_INTERVAL = 1 CLEAN_UP_INTERVAL_MULTIPLIER = 1000 def __init__(self): self.latencies = {} @abstractmethod def _start_clean_up_timer(self): pass @abstractmethod def _stop_clean_up_timer(self): pass def operation_latencies(self): operation_latencies = {} for endpoint_name, endpoint_latencies in self.latencies.items(): latency_key = "l_" + endpoint_name endpoint_average_latency = self.average_latency_from_data( endpoint_latencies ) if endpoint_average_latency > 0: operation_latencies[latency_key] = endpoint_average_latency return operation_latencies def clean_up_telemetry_data(self): current_timestamp = time.time() copy_latencies = copy.deepcopy(self.latencies) for endpoint_name, endpoint_latencies in copy_latencies.items(): for latency_information in endpoint_latencies: if ( current_timestamp - latency_information["timestamp"] > self.MAXIMUM_LATENCY_DATA_AGE ): self.latencies[endpoint_name].remove(latency_information) if len(self.latencies[endpoint_name]) == 0: del self.latencies[endpoint_name] def store_latency(self, latency, operation_type): if operation_type != PNOperationType.PNSubscribeOperation and latency > 0: endpoint_name = self.endpoint_name_for_operation(operation_type) store_timestamp = time.time() if endpoint_name not in self.latencies: self.latencies[endpoint_name] = [] latency_entry = { "timestamp": store_timestamp, "latency": latency, } self.latencies[endpoint_name].append(latency_entry) @staticmethod def average_latency_from_data(endpoint_latencies): total_latency = 0 for latency_data in endpoint_latencies: total_latency += latency_data["latency"] return total_latency / len(endpoint_latencies) @staticmethod def endpoint_name_for_operation(operation_type): endpoint = { PNOperationType.PNPublishOperation: "pub", PNOperationType.PNFireOperation: "pub", PNOperationType.PNHistoryOperation: "hist", PNOperationType.PNHistoryDeleteOperation: "hist", PNOperationType.PNMessageCountOperation: "mc", PNOperationType.PNUnsubscribeOperation: "pres", PNOperationType.PNWhereNowOperation: "pres", PNOperationType.PNHereNowOperation: "pres", PNOperationType.PNGetState: "pres", PNOperationType.PNSetStateOperation: "pres", PNOperationType.PNHeartbeatOperation: "pres", PNOperationType.PNAddChannelsToGroupOperation: "cg", PNOperationType.PNRemoveChannelsFromGroupOperation: "cg", PNOperationType.PNChannelGroupsOperation: "cg", PNOperationType.PNChannelsForGroupOperation: "cg", PNOperationType.PNRemoveGroupOperation: "cg", PNOperationType.PNAddPushNotificationsOnChannelsOperation: "push", PNOperationType.PNPushNotificationEnabledChannelsOperation: "push", PNOperationType.PNRemoveAllPushNotificationsOperation: "push", PNOperationType.PNRemovePushNotificationsFromChannelsOperation: "push", PNOperationType.PNAccessManagerAudit: "pam", PNOperationType.PNAccessManagerGrant: "pam", PNOperationType.PNAccessManagerRevoke: "pam", PNOperationType.PNTimeOperation: "pam", PNOperationType.PNAccessManagerGrantToken: "pamv3", PNOperationType.PNAccessManagerRevokeToken: "pamv3", PNOperationType.PNSignalOperation: "sig", PNOperationType.PNSetUuidMetadataOperation: "obj", PNOperationType.PNGetUuidMetadataOperation: "obj", PNOperationType.PNRemoveUuidMetadataOperation: "obj", PNOperationType.PNGetAllUuidMetadataOperation: "obj", PNOperationType.PNSetChannelMetadataOperation: "obj", PNOperationType.PNGetChannelMetadataOperation: "obj", PNOperationType.PNRemoveChannelMetadataOperation: "obj", PNOperationType.PNGetAllChannelMetadataOperation: "obj", PNOperationType.PNSetChannelMembersOperation: "obj", PNOperationType.PNGetChannelMembersOperation: "obj", PNOperationType.PNRemoveChannelMembersOperation: "obj", PNOperationType.PNManageChannelMembersOperation: "obj", PNOperationType.PNSetMembershipsOperation: "obj", PNOperationType.PNGetMembershipsOperation: "obj", PNOperationType.PNRemoveMembershipsOperation: "obj", PNOperationType.PNManageMembershipsOperation: "obj", PNOperationType.PNAddMessageAction: "msga", PNOperationType.PNGetMessageActions: "msga", PNOperationType.PNDeleteMessageAction: "msga", PNOperationType.PNGetFilesAction: "file", PNOperationType.PNDeleteFileOperation: "file", PNOperationType.PNGetFileDownloadURLAction: "file", PNOperationType.PNFetchFileUploadS3DataAction: "file", PNOperationType.PNDownloadFileAction: "file", PNOperationType.PNSendFileAction: "file", }[operation_type] return endpoint class TokenManager: def __init__(self): self.token = None def set_token(self, token): self.token = token def get_token(self): return self.token @classmethod def parse_token(cls, token): token = cls.unwrap_token(token) parsed_token = { "version": token["v"], "timestamp": token["t"], "ttl": token["ttl"], "authorized_uuid": token.get("uuid"), "resources": {}, "patterns": {}, "meta": token["meta"], } perm_type_name_mapping = {"res": "resources", "pat": "patterns"} for resource_type in perm_type_name_mapping: for resource in token[resource_type]: if resource == "uuid": parsed_token[perm_type_name_mapping[resource_type]]["uuids"] = ( utils.parse_pam_permissions(token[resource_type][resource]) ) elif resource == "grp": parsed_token[perm_type_name_mapping[resource_type]]["groups"] = ( utils.parse_pam_permissions(token[resource_type][resource]) ) elif resource == "chan": parsed_token[perm_type_name_mapping[resource_type]]["channels"] = ( utils.parse_pam_permissions(token[resource_type][resource]) ) return parsed_token @staticmethod def unwrap_token(token): token = token.replace("_", "/").replace("-", "+") byte_array = base64.b64decode(token) try: unwrapped_obj = loads(byte_array) decoded_obj = utils.decode_utf8_dict(unwrapped_obj) return decoded_obj except Exception: raise PubNubException(pn_error=PNERR_INVALID_ACCESS_TOKEN) bdraco-freenub-69809e8/pubnub/models/000077500000000000000000000000001464523203000174435ustar00rootroot00000000000000bdraco-freenub-69809e8/pubnub/models/__init__.py000066400000000000000000000000001464523203000215420ustar00rootroot00000000000000bdraco-freenub-69809e8/pubnub/models/consumer/000077500000000000000000000000001464523203000212765ustar00rootroot00000000000000bdraco-freenub-69809e8/pubnub/models/consumer/__init__.py000066400000000000000000000000001464523203000233750ustar00rootroot00000000000000bdraco-freenub-69809e8/pubnub/models/consumer/access_manager.py000066400000000000000000000141631464523203000246100ustar00rootroot00000000000000""" Possible responses of PAM request """ class _PAMResult: def __init__( self, level, subscribe_key, channels, groups, uuids, ttl=None, r=None, w=None, m=None, d=None, ): self.level = level self.subscribe_key = subscribe_key self.channels = channels self.groups = groups self.uuids = uuids self.ttl = ttl self.read_enabled = r self.write_enabled = w self.manage_enabled = m self.delete_enabled = d @classmethod def from_json(cls, json_input): constructed_channels = {} constructed_groups = {} constructed_uuids = {} # only extract ttl, others are to be fetched on per uuid level r, w, m, d, g, u, j, ttl = fetch_permissions(json_input) if "channel" in json_input: channel_name = json_input["channel"] constructed_auth_keys = {} for auth_key_name, value in json_input["auths"].items(): constructed_auth_keys[auth_key_name] = PNAccessManagerKeyData.from_json( value ) constructed_channels[channel_name] = PNAccessManagerChannelData( name=channel_name, auth_keys=constructed_auth_keys, ttl=ttl ) if "channel-group" in json_input: if isinstance(json_input["channel-group"], str): group_name = json_input["channel-group"] constructed_auth_keys = {} for auth_key_name, value in json_input["auths"].items(): constructed_auth_keys[auth_key_name] = ( PNAccessManagerKeyData.from_json(value) ) constructed_groups[group_name] = PNAccessManagerChannelGroupData( name=group_name, auth_keys=constructed_auth_keys, ttl=ttl ) if "channel-groups" in json_input: if isinstance(json_input["channel-groups"], str): group_name = json_input["channel-groups"] constructed_auth_keys = {} for auth_key_name, value in json_input["auths"].items(): constructed_auth_keys[auth_key_name] = ( PNAccessManagerKeyData.from_json(value) ) constructed_groups[group_name] = PNAccessManagerChannelGroupData( name=group_name, auth_keys=constructed_auth_keys, ttl=ttl ) if isinstance(json_input["channel-groups"], dict): for group_name, value in json_input["channel-groups"].items(): constructed_groups[group_name] = ( PNAccessManagerChannelGroupData.from_json(group_name, value) ) if "channels" in json_input: for channel_name, value in json_input["channels"].items(): constructed_channels[channel_name] = ( PNAccessManagerChannelData.from_json(channel_name, value) ) if "uuids" in json_input: for uuid, value in json_input["uuids"].items(): constructed_uuids[uuid] = PNAccessManagerUuidsData.from_json( uuid, value ) return cls( level=json_input["level"], subscribe_key=json_input["subscribe_key"], channels=constructed_channels, groups=constructed_groups, uuids=constructed_uuids, r=r, w=w, m=m, d=d, ttl=ttl, ) class PNAccessManagerResult(_PAMResult): def __str__(self): return "Permissions are valid for %d minutes" % self.ttl or 0 class PNAccessManagerAuditResult(PNAccessManagerResult): pass class PNAccessManagerGrantResult(PNAccessManagerResult): pass class _PAMEntityData: def __init__( self, name, auth_keys=None, r=None, w=None, m=None, d=None, g=None, u=None, j=None, ttl=None, ): self.name = name self.auth_keys = auth_keys self.read_enabled = r self.write_enabled = w self.manage_enabled = m self.delete_enabled = d self.get = g self.update = u self.join = j self.ttl = ttl @classmethod def from_json(cls, name, json_input): r, w, m, d, g, u, j, ttl = fetch_permissions(json_input) constructed_auth_keys = {} if "auths" in json_input: for auth_key, value in json_input["auths"].items(): constructed_auth_keys[auth_key] = PNAccessManagerKeyData.from_json( value ) return cls(name, constructed_auth_keys, r, w, m, d, g, u, j, ttl) class PNAccessManagerChannelData(_PAMEntityData): pass class PNAccessManagerChannelGroupData(_PAMEntityData): pass class PNAccessManagerUuidsData(_PAMEntityData): pass class PNAccessManagerKeyData: def __init__(self, r, w, m, d, g, u, j, ttl=None): self.read_enabled = r self.write_enabled = w self.manage_enabled = m self.delete_enabled = d self.get = g self.update = u self.join = j self.ttl = ttl @classmethod def from_json(cls, json_input): r, w, m, d, g, u, j, ttl = fetch_permissions(json_input) return PNAccessManagerKeyData(r, w, m, d, g, u, j, ttl) def fetch_permissions(json_input): r = None w = None m = None d = None g = None u = None j = None ttl = None if "r" in json_input: r = json_input["r"] == 1 if "w" in json_input: w = json_input["w"] == 1 if "m" in json_input: m = json_input["m"] == 1 if "d" in json_input: d = json_input["d"] == 1 if "g" in json_input: g = json_input["g"] == 1 if "u" in json_input: u = json_input["u"] == 1 if "j" in json_input: j = json_input["j"] == 1 if "ttl" in json_input: ttl = json_input["ttl"] return r, w, m, d, g, u, j, ttl bdraco-freenub-69809e8/pubnub/models/consumer/channel_group.py000066400000000000000000000010301464523203000244660ustar00rootroot00000000000000class PNChannelGroupsAddChannelResult: def __str__(self): return "Channel successfully added" class PNChannelGroupsRemoveChannelResult: def __str__(self): return "Channel successfully removed" class PNChannelGroupsRemoveGroupResult: def __str__(self): return "Group successfully removed" class PNChannelGroupsListResult: def __init__(self, channels): self.channels = channels def __str__(self): return "Group contains following channels: %s" % ", ".join(self.channels) bdraco-freenub-69809e8/pubnub/models/consumer/common.py000066400000000000000000000010411464523203000231340ustar00rootroot00000000000000class PNStatus: def __init__(self): self.category = None self.error_data = None self.error = None self.status_code = None self.operation = None self.tls_enabled = None self.uuid = None self.auth_key = None self.origin = None self.client_request = None self.client_response = None self.original_response = None self.affected_channels = None self.affected_groups = None def is_error(self): return bool(self.error) bdraco-freenub-69809e8/pubnub/models/consumer/entities/000077500000000000000000000000001464523203000231225ustar00rootroot00000000000000bdraco-freenub-69809e8/pubnub/models/consumer/entities/__init__.py000066400000000000000000000000001464523203000252210ustar00rootroot00000000000000bdraco-freenub-69809e8/pubnub/models/consumer/entities/membership.py000066400000000000000000000016201464523203000256260ustar00rootroot00000000000000from pubnub.models.consumer.entities.result import PNEntityPageableResult class PNMembershipsResult(PNEntityPageableResult): _description = "Set Memberships: %s" def __init__(self, result): super().__init__(result) self.status = result["status"] def rename_channel(result): result["space"] = result.pop("channel") return result def rename_uuid(result): result["user"] = result.pop("uuid") return result class PNUserMembershipsResult(PNMembershipsResult): def __init__(self, result): super().__init__(result) self.data = [ PNMembershipsResult.rename_channel(space) for space in result["data"] ] class PNSpaceMembershipsResult(PNMembershipsResult): def __init__(self, result): super().__init__(result) self.data = [PNMembershipsResult.rename_uuid(user) for user in result["data"]] bdraco-freenub-69809e8/pubnub/models/consumer/entities/page.py000066400000000000000000000013511464523203000244100ustar00rootroot00000000000000from abc import ABCMeta class PNPage: __metaclass__ = ABCMeta def __init__(self, hash): self._hash = str(hash) @property def hash(self): return self._hash @classmethod def builder(cls, value): if value is None: return None return cls(value) class Next(PNPage): def __init__(self, hash): super().__init__(hash) class Previous(PNPage): def __init__(self, hash): super().__init__(hash) class PNPageable: __metaclass__ = ABCMeta def __init__(self, result): self.total_count = result.get("totalCount", None) self.next = Next.builder(result.get("next", None)) self.prev = Previous.builder(result.get("prev", None)) bdraco-freenub-69809e8/pubnub/models/consumer/entities/result.py000066400000000000000000000006711464523203000250160ustar00rootroot00000000000000from pubnub.models.consumer.objects_v2.page import PNPageable class PNEntityResult: def __init__(self, result): self.data = result["data"] self.status = result["status"] def __str__(self): return self._description % self.data class PNEntityPageableResult(PNEntityResult, PNPageable): def __init__(self, result): PNEntityResult.__init__(self, result) PNPageable.__init__(self, result) bdraco-freenub-69809e8/pubnub/models/consumer/entities/space.py000066400000000000000000000021201464523203000245620ustar00rootroot00000000000000from typing import Optional from pubnub.models.consumer.entities.result import ( PNEntityPageableResult, PNEntityResult, ) class PNCreateSpaceResult(PNEntityResult): _description = "Create Space: %s" class PNUpdateSpaceResult(PNEntityResult): _description = "Update Space: %s" class PNFetchSpaceResult(PNEntityResult): _description = "Fetch Space: %s" class PNRemoveSpaceResult(PNEntityResult): _description = "Remove Space: %s" class PNFetchSpacesResult(PNEntityPageableResult): _description = "Fetch Spaces: %s" class PNSpaceResult(PNEntityResult): def __str__(self): return f"Space {self.event} event with data: {self.data}" class Space: space_id: str custom: Optional[dict] def __init__(self, space_id=None, **kwargs): self.space_id = space_id if "custom" in kwargs.keys(): self.custom = kwargs["custom"] def to_payload_dict(self): result = {"channel": {"id": str(self.space_id)}} if "custom" in self.__dict__.keys(): result["custom"] = self.custom return result bdraco-freenub-69809e8/pubnub/models/consumer/entities/user.py000066400000000000000000000020731464523203000244540ustar00rootroot00000000000000from typing import Optional from pubnub.models.consumer.entities.result import ( PNEntityPageableResult, PNEntityResult, ) class PNCreateUserResult(PNEntityResult): _description = "Create User: %s" class PNUpdateUserResult(PNEntityResult): _description = "Update User: %s" class PNFetchUserResult(PNEntityResult): _description = "Fetch User: %s" class PNRemoveUserResult(PNEntityResult): _description = "Remove User: %s" class PNFetchUsersResult(PNEntityPageableResult): _description = "Fetch Users: %s" class PNUserResult(PNEntityResult): def __str__(self): return f"UUID {self.event} event with data: {self.data}" class User: user_id: str custom: Optional[dict] def __init__(self, user_id=None, **kwargs): self.user_id = user_id if "custom" in kwargs.keys(): self.custom = kwargs["custom"] def to_payload_dict(self): result = {"uuid": {"id": str(self.user_id)}} if "custom" in self.__dict__.keys(): result["custom"] = self.custom return result bdraco-freenub-69809e8/pubnub/models/consumer/file.py000066400000000000000000000032501464523203000225670ustar00rootroot00000000000000class PNGetFilesResult: def __init__(self, result): self.data = result["data"] self.count = result.get("count", None) self.next = result.get("next", None) self.prev = result.get("prev", None) def __str__(self): return "Get files success with data: %s" % self.data class PNDeleteFileResult: def __init__(self, result): self.status = result["status"] def __str__(self): return "Delete files success with status: %s" % self.status class PNGetFileDownloadURLResult: def __init__(self, result, data=None): self.file_url = result.headers["Location"] def __str__(self): return "Get file URL success with status: %s" % self.status class PNFetchFileUploadS3DataResult: def __init__(self, result): self.name = result["data"]["name"] self.file_id = result["data"]["id"] self.data = result["file_upload_request"] def __str__(self): return "Fetch file upload S3 data success with status: %s" % self.status class PNDownloadFileResult: def __init__(self, result): self.data = result def __str__(self): return "Downloading file success with status: %s" % self.status class PNSendFileResult: def __init__(self, result, file_upload_data): self.name = file_upload_data.result.name self.file_id = file_upload_data.result.file_id def __str__(self): return "Sending file success with status: %s" % self.status class PNPublishFileMessageResult: def __init__(self, result): self.timestamp = result[2] def __str__(self): return "Sending file notification success with status: %s" % self.status bdraco-freenub-69809e8/pubnub/models/consumer/history.py000066400000000000000000000072761464523203000233650ustar00rootroot00000000000000class PNHistoryResult: def __init__(self, messages, start_timetoken, end_timetoken): self.messages = messages self.start_timetoken = start_timetoken self.end_timetoken = end_timetoken def __str__(self): return "History result for range %d..%d" % ( self.start_timetoken, self.end_timetoken, ) @classmethod def from_json( cls, json_input, crypto, include_timetoken=False, include_meta=False, cipher=None, ): start_timetoken = json_input[1] end_timetoken = json_input[2] raw_items = json_input[0] messages = [] for item in raw_items: if ( (include_timetoken or include_meta) and isinstance(item, dict) and "message" in item ): message = PNHistoryItemResult(item["message"], crypto) if include_timetoken and "timetoken" in item: message.timetoken = item["timetoken"] if include_meta and "meta" in item: message.meta = item["meta"] else: message = PNHistoryItemResult(item, crypto) if cipher is not None: message.decrypt(cipher) messages.append(message) return PNHistoryResult( messages=messages, start_timetoken=start_timetoken, end_timetoken=end_timetoken, ) class PNHistoryItemResult: def __init__(self, entry, crypto, timetoken=None, meta=None): self.timetoken = timetoken self.meta = meta self.entry = entry self.crypto = crypto def __str__(self): return f"History item with tt: {self.timetoken} and content: {self.entry}" def decrypt(self, cipher_key): self.entry = self.crypto.decrypt(cipher_key, self.entry) class PNFetchMessagesResult: def __init__(self, channels, start_timetoken, end_timetoken): self.channels = channels self.start_timetoken = start_timetoken self.end_timetoken = end_timetoken def __str__(self): return "Fetch messages result for range %d..%d" % ( self.start_timetoken, self.end_timetoken, ) @classmethod def from_json( cls, json_input, include_message_actions=False, start_timetoken=None, end_timetoken=None, ): channels = {} for key, entry in json_input["channels"].items(): channels[key] = [] for item in entry: message = PNFetchMessageItem(item["message"], item["timetoken"]) if "uuid" in item: message.uuid = item["uuid"] if "message_type" in item: message.message_type = item["message_type"] if "meta" in item: message.meta = item["meta"] if include_message_actions: if "actions" in item: message.actions = item["actions"] else: message.actions = {} channels[key].append(message) return PNFetchMessagesResult( channels=channels, start_timetoken=start_timetoken, end_timetoken=end_timetoken, ) class PNFetchMessageItem: def __init__(self, message, timetoken, meta=None, actions=None): self.message = message self.meta = meta self.timetoken = timetoken self.actions = actions def __str__(self): return ( f"Fetch message item with tt: {self.timetoken} and content: {self.message}" ) bdraco-freenub-69809e8/pubnub/models/consumer/message_actions.py000066400000000000000000000030031464523203000250100ustar00rootroot00000000000000class PNMessageAction: def __init__(self, message_action=None): if message_action is not None: self.type = message_action["type"] self.value = message_action["value"] self.message_timetoken = message_action["messageTimetoken"] self.uuid = message_action["uuid"] self.action_timetoken = message_action["actionTimetoken"] else: self.type = None self.value = None self.message_timetoken = None self.uuid = None self.action_timetoken = None def __str__(self): return f"Message action with tt: {self.action_timetoken} for uuid {self.uuid} with value {self.value} " class PNGetMessageActionsResult: def __init__(self, result): """ Representation of get message actions server response :param result: result of get message actions operation """ self._result = result self.actions = result["actions"] def __str__(self): return "Get message actions success" class PNAddMessageActionResult(PNMessageAction): def __init__(self, message_action): super().__init__(message_action) class PNRemoveMessageActionResult: def __init__(self, result): """ s Representation of remove message actions server response :param result: result of remove message actions operation """ self._result = result def __str__(self): return "Remove message actions success" bdraco-freenub-69809e8/pubnub/models/consumer/message_count.py000066400000000000000000000005451464523203000245100ustar00rootroot00000000000000class PNMessageCountResult: def __init__(self, result): """ Representation of message count server response :param result: result of message count operation """ self._result = result self.channels = result["channels"] def __str__(self): return f"Message count for channels: {self.channels}" bdraco-freenub-69809e8/pubnub/models/consumer/objects_v2/000077500000000000000000000000001464523203000233365ustar00rootroot00000000000000bdraco-freenub-69809e8/pubnub/models/consumer/objects_v2/__init__.py000066400000000000000000000000001464523203000254350ustar00rootroot00000000000000bdraco-freenub-69809e8/pubnub/models/consumer/objects_v2/channel.py000066400000000000000000000023101464523203000253140ustar00rootroot00000000000000from pubnub.models.consumer.objects_v2.page import PNPageable class PNSetChannelMetadataResult: def __init__(self, result): self.data = result["data"] self.status = result["status"] def __str__(self): return "Set Channel metatdata: %s" % self.data class PNGetChannelMetadataResult: def __init__(self, result): self.data = result["data"] self.status = result["status"] def __str__(self): return "Get Channel metatdata: %s" % self.data class PNRemoveChannelMetadataResult: def __init__(self, result): self.data = result["data"] self.status = result["status"] def __str__(self): return "Get Channel metatdata: %s" % self.data class PNGetAllChannelMetadataResult(PNPageable): def __init__(self, result): PNPageable.__init__(self, result) self.data = result["data"] self.status = result["status"] def __str__(self): return "Get all Channel metatdata: %s" % self.data class PNChannelMetadataResult: def __init__(self, event, data): self.data = data self.event = event def __str__(self): return f"Channel {self.event} event with data: {self.data}" bdraco-freenub-69809e8/pubnub/models/consumer/objects_v2/channel_members.py000066400000000000000000000036251464523203000270400ustar00rootroot00000000000000from abc import ABCMeta, abstractmethod from pubnub.models.consumer.objects_v2.page import PNPageable class PNUUID: __metaclass__ = ABCMeta def __init__(self, uuid): self._uuid = uuid @staticmethod def uuid(uuid): return JustUUID(uuid) @staticmethod def uuid_with_custom(uuid, custom): return UUIDWithCustom(uuid, custom) @abstractmethod def to_payload_dict(self): return None class JustUUID(PNUUID): def to_payload_dict(self): return {"uuid": {"id": str(self._uuid)}} class UUIDWithCustom(PNUUID): def __init__(self, uuid, custom): PNUUID.__init__(self, uuid) self._custom = custom def to_payload_dict(self): return {"uuid": {"id": str(self._uuid)}, "custom": dict(self._custom)} class PNSetChannelMembersResult(PNPageable): def __init__(self, result): PNPageable.__init__(self, result) self.data = result["data"] self.status = result["status"] def __str__(self): return "Set Channel Members metatdata: %s" % self.data class PNGetChannelMembersResult(PNPageable): def __init__(self, result): PNPageable.__init__(self, result) self.data = result["data"] self.status = result["status"] def __str__(self): return "Get Channel Members metatdata: %s" % self.data class PNRemoveChannelMembersResult(PNPageable): def __init__(self, result): PNPageable.__init__(self, result) self.data = result["data"] self.status = result["status"] def __str__(self): return "Remove Channel Members metatdata: %s" % self.data class PNManageChannelMembersResult(PNPageable): def __init__(self, result): PNPageable.__init__(self, result) self.data = result["data"] self.status = result["status"] def __str__(self): return "Manage Channel Members metatdata: %s" % self.data bdraco-freenub-69809e8/pubnub/models/consumer/objects_v2/memberships.py000066400000000000000000000044241464523203000262320ustar00rootroot00000000000000from abc import ABCMeta, abstractmethod from pubnub.models.consumer.objects_v2.page import PNPageable class PNChannelMembership: __metaclass__ = ABCMeta def __init__(self, channel): self._channel = channel @staticmethod def channel(channel): return JustChannel(channel) @staticmethod def channel_with_custom(channel, custom): return ChannelWithCustom(channel, custom) @abstractmethod def to_payload_dict(self): return None class JustChannel(PNChannelMembership): def __init__(self, channel): PNChannelMembership.__init__(self, channel) def to_payload_dict(self): return {"channel": {"id": str(self._channel)}} class ChannelWithCustom(PNChannelMembership): def __init__(self, channel, custom): PNChannelMembership.__init__(self, channel) self._custom = custom def to_payload_dict(self): return {"channel": {"id": str(self._channel)}, "custom": dict(self._custom)} class PNSetMembershipsResult(PNPageable): def __init__(self, result): PNPageable.__init__(self, result) self.data = result["data"] self.status = result["status"] def __str__(self): return "Set Memberships metatdata: %s" % self.data class PNGetMembershipsResult(PNPageable): def __init__(self, result): PNPageable.__init__(self, result) self.data = result["data"] self.status = result["status"] def __str__(self): return "Get Memberships metatdata: %s" % self.data class PNRemoveMembershipsResult(PNPageable): def __init__(self, result): PNPageable.__init__(self, result) self.data = result["data"] self.status = result["status"] def __str__(self): return "Remove Memberships metatdata: %s" % self.data class PNManageMembershipsResult(PNPageable): def __init__(self, result): PNPageable.__init__(self, result) self.data = result["data"] self.status = result["status"] def __str__(self): return "Manage Channel Members metatdata: %s" % self.data class PNMembershipResult: def __init__(self, event, data): self.data = data self.event = event def __str__(self): return f"Membership {self.event} event with data: {self.data}" bdraco-freenub-69809e8/pubnub/models/consumer/objects_v2/page.py000066400000000000000000000013751464523203000246320ustar00rootroot00000000000000from abc import ABCMeta class PNPage: __metaclass__ = ABCMeta def __init__(self, hash): self._hash = str(hash) @property def hash(self): return self._hash class Next(PNPage): def __init__(self, hash): PNPage.__init__(self, hash) class Previous(PNPage): def __init__(self, hash): PNPage.__init__(self, hash) class PNPageable: __metaclass__ = ABCMeta def __init__(self, result): self.total_count = result.get("totalCount", None) if result.get("next", None): self.next = Next(result["next"]) else: self.next = None if result.get("prev", None): self.prev = Previous(result["prev"]) else: self.prev = None bdraco-freenub-69809e8/pubnub/models/consumer/objects_v2/sort.py000066400000000000000000000020541464523203000247000ustar00rootroot00000000000000from enum import Enum class PNSortKeyValue(Enum): ID = 1 NAME = 2 UPDATED = 3 class PNSortDirection(Enum): ASC = 1 DESC = 2 class PNSortKey: def __init__(self, sort_key_value, direction): self._sort_key_value = sort_key_value self._direction = direction @staticmethod def asc(sort_key_value): return PNSortKey(sort_key_value, PNSortDirection.ASC) @staticmethod def desc(sort_key_value): return PNSortKey(sort_key_value, PNSortDirection.DESC) def key_str(self): if self._sort_key_value == PNSortKeyValue.ID: return "id" elif self._sort_key_value == PNSortKeyValue.NAME: return "name" elif self._sort_key_value == PNSortKeyValue.UPDATED: return "updated" else: raise ValueError() def dir_str(self): if self._direction == PNSortDirection.ASC: return "asc" elif self._direction == PNSortDirection.DESC: return "desc" else: raise ValueError() bdraco-freenub-69809e8/pubnub/models/consumer/objects_v2/uuid.py000066400000000000000000000022521464523203000246570ustar00rootroot00000000000000from pubnub.models.consumer.objects_v2.page import PNPageable class PNSetUUIDMetadataResult: def __init__(self, result): self.data = result["data"] self.status = result["status"] def __str__(self): return "Set UUID metatdata: %s" % self.data class PNGetUUIDMetadataResult: def __init__(self, result): self.data = result["data"] self.status = result["status"] def __str__(self): return "Get UUID metatdata: %s" % self.data class PNRemoveUUIDMetadataResult: def __init__(self, result): self.data = result["data"] self.status = result["status"] def __str__(self): return "Get UUID metatdata: %s" % self.data class PNGetAllUUIDMetadataResult(PNPageable): def __init__(self, result): PNPageable.__init__(self, result) self.data = result["data"] self.status = result["status"] def __str__(self): return "Get all UUID metatdata: %s" % self.data class PNUUIDMetadataResult: def __init__(self, event, data): self.data = data self.event = event def __str__(self): return f"UUID {self.event} event with data: {self.data}" bdraco-freenub-69809e8/pubnub/models/consumer/pn_error_data.py000066400000000000000000000003521464523203000244670ustar00rootroot00000000000000class PNErrorData: def __init__(self, information, exception): assert isinstance(information, str) assert isinstance(exception, Exception) self.information = information self.exception = exception bdraco-freenub-69809e8/pubnub/models/consumer/presence.py000066400000000000000000000127351464523203000234640ustar00rootroot00000000000000class PNHereNowResult: def __init__(self, total_channels, total_occupancy, channels): assert isinstance(total_channels, int) assert isinstance(total_occupancy, int) self.total_channels = total_channels self.total_occupancy = total_occupancy self.channels = channels def __str__(self): return "HereNow Result total occupancy: %d, total channels: %d" % ( self.total_occupancy, self.total_channels, ) @classmethod def from_json(cls, envelope, channel_names): # multiple if "payload" in envelope and isinstance(envelope["payload"], dict): json_input = envelope["payload"] channels = [] if len(json_input["channels"]) > 0: for channel_name, raw_data in json_input["channels"].items(): channels.append( PNHereNowChannelData.from_json(channel_name, raw_data) ) return PNHereNowResult( total_channels=int(json_input["total_channels"]), total_occupancy=int(json_input["total_occupancy"]), channels=channels, ) elif len(channel_names) == 1: return PNHereNowResult( total_channels=1, total_occupancy=int(json_input["total_occupancy"]), channels=[PNHereNowChannelData(channel_names[0], 0, [])], ) else: return PNHereNowResult( total_channels=int(json_input["total_channels"]), total_occupancy=int(json_input["total_occupancy"]), channels={}, ) # empty elif "occupancy" in envelope and int(envelope["occupancy"]) == 0: return PNHereNowResult( total_channels=1, total_occupancy=int(envelope["occupancy"]), channels=[PNHereNowChannelData(channel_names[0], 0, [])], ) # single elif "uuids" in envelope and isinstance(envelope["uuids"], list): occupants = [] for user in envelope["uuids"]: if isinstance(user, str): occupants.append(PNHereNowOccupantsData(user, None)) else: state = user["state"] if "state" in user else None occupants.append(PNHereNowOccupantsData(user["uuid"], state)) return PNHereNowResult( total_channels=1, total_occupancy=int(envelope["occupancy"]), channels=[ PNHereNowChannelData( channel_name=channel_names[0], occupancy=envelope["occupancy"], occupants=occupants, ) ], ) else: return PNHereNowResult( total_channels=1, total_occupancy=int(envelope["occupancy"]), channels=[ PNHereNowChannelData( channel_name=channel_names[0], occupancy=envelope["occupancy"], occupants=[], ) ], ) class PNHereNowChannelData: def __init__(self, channel_name, occupancy, occupants): self.channel_name = channel_name self.occupancy = occupancy self.occupants = occupants def __str__(self): return "HereNow Channel Data for channel '%s': occupancy: %d, occupants: %d" % ( self.channel_name, self.occupancy, self.occupants, ) @classmethod def from_json(cls, name, json_input): if "uuids" in json_input: occupants = [] for user in json_input["uuids"]: if isinstance(user, dict) and len(user) > 0: if "state" in user: occupants.append( PNHereNowOccupantsData(user["uuid"], user["state"]) ) else: occupants.append(PNHereNowOccupantsData(user["uuid"], None)) else: occupants.append(PNHereNowOccupantsData(user, None)) else: occupants = None return PNHereNowChannelData( channel_name=name, occupancy=int(json_input["occupancy"]), occupants=occupants, ) class PNHereNowOccupantsData: def __init__(self, uuid, state): self.uuid = uuid self.state = state def __str__(self): return f"HereNow Occupants Data for '{self.uuid}': {self.state}" class PNWhereNowResult: def __init__(self, channels): assert isinstance(channels, (list, tuple)) self.channels = channels def __str__(self): return "User is currently subscribed to %s" % ", ".join(self.channels) @classmethod def from_json(cls, json_input): return PNWhereNowResult(json_input["payload"]["channels"]) class PNSetStateResult: def __init__(self, state): assert isinstance(state, dict) self.state = state def __str__(self): return "New state %s successfully set" % self.state class PNGetStateResult: def __init__(self, channels): assert isinstance(channels, dict) self.channels = channels def __str__(self): return "Current state is %s" % self.channels bdraco-freenub-69809e8/pubnub/models/consumer/pubsub.py000066400000000000000000000070121464523203000231500ustar00rootroot00000000000000from pubnub.models.consumer.message_actions import PNMessageAction class PNMessageResult: def __init__( self, message, subscription, channel, timetoken, user_metadata=None, publisher=None, ): if subscription is not None: assert isinstance(subscription, str) if channel is not None: assert isinstance(channel, str) if publisher is not None: assert isinstance(publisher, str) assert isinstance(timetoken, int) if user_metadata is not None: assert isinstance(user_metadata, object) self.message = message # DEPRECATED: subscribed_channel and actual_channel properties are deprecated # self.subscribed_channel = subscribed_channel <= now known as subscription # self.actual_channel = actual_channel <= now known as channel self.channel = channel self.subscription = subscription self.timetoken = timetoken self.user_metadata = user_metadata self.publisher = publisher class PNSignalMessageResult(PNMessageResult): pass class PNFileMessageResult(PNMessageResult): def __init__( self, message, subscription, channel, timetoken, publisher, file_url, file_id, file_name, ): super().__init__(message, subscription, channel, timetoken, publisher=publisher) self.file_url = file_url self.file_id = file_id self.file_name = file_name class PNPresenceEventResult: def __init__( self, event, uuid, timestamp, occupancy, subscription, channel, timetoken, state, join, leave, timeout, user_metadata=None, ): assert isinstance(event, str) assert isinstance(timestamp, int) assert isinstance(occupancy, int) assert isinstance(channel, str) assert isinstance(timetoken, int) if user_metadata is not None: assert isinstance(user_metadata, object) if state is not None: assert isinstance(state, dict) self.event = event self.uuid = uuid self.timestamp = timestamp self.occupancy = occupancy self.state = state self.join = join self.leave = leave self.timeout = timeout # DEPRECATED: subscribed_channel and actual_channel properties are deprecated # self.subscribed_channel = subscribed_channel <= now known as subscription # self.actual_channel = actual_channel <= now known as channel self.subscription = subscription self.channel = channel self.timetoken = timetoken self.user_metadata = user_metadata class PNMessageActionResult(PNMessageAction): def __init__(self, result): super().__init__(result) class PNPublishResult: def __init__(self, envelope, timetoken): """ Representation of publish server response :param timetoken: of publish operation """ self.timetoken = timetoken def __str__(self): return "Publish success with timetoken %s" % self.timetoken class PNFireResult: def __init__(self, envelope, timetoken): """ Representation of fire server response :param timetoken: of fire operation """ self.timetoken = timetoken def __str__(self): return "Fire success with timetoken %s" % self.timetoken bdraco-freenub-69809e8/pubnub/models/consumer/push.py000066400000000000000000000010571464523203000226320ustar00rootroot00000000000000class PNPushAddChannelResult: def __str__(self): return "Channel successfully added" class PNPushRemoveChannelResult: def __str__(self): return "Channel successfully removed" class PNPushRemoveAllChannelsResult: def __str__(self): return "All channels successfully removed" class PNPushListProvisionsResult: def __init__(self, channels): self.channels = channels def __str__(self): return "Push notification enabled on following channels: %s" % ", ".join( self.channels ) bdraco-freenub-69809e8/pubnub/models/consumer/signal.py000066400000000000000000000005161464523203000231270ustar00rootroot00000000000000class PNSignalResult: def __init__(self, result): """ Representation of signal server response :param result: result of signal operation """ self.timetoken = result[2] self._result = result def __str__(self): return "Signal success with timetoken %s" % self.timetoken bdraco-freenub-69809e8/pubnub/models/consumer/time.py000066400000000000000000000010341464523203000226040ustar00rootroot00000000000000from datetime import date class PNTimeResponse: MULTIPLIER = 10000000 def __init__(self, server_response): assert isinstance(server_response, list) self.server_response = server_response self.value_as_string = str(server_response[0]) self.value_as_int = server_response[0] def __int__(self): return self.value_as_int def __str__(self): return self.value_as_string def date_time(self): return date.fromtimestamp(self.value_as_int / PNTimeResponse.MULTIPLIER) bdraco-freenub-69809e8/pubnub/models/consumer/v3/000077500000000000000000000000001464523203000216265ustar00rootroot00000000000000bdraco-freenub-69809e8/pubnub/models/consumer/v3/__init__.py000066400000000000000000000000001464523203000237250ustar00rootroot00000000000000bdraco-freenub-69809e8/pubnub/models/consumer/v3/access_manager.py000066400000000000000000000011051464523203000251300ustar00rootroot00000000000000""" Possible responses of PAMv3 request """ class _PAMv3Result: def __init__(self, token): self.token = token @classmethod def from_json(cls, json_input): return cls(token=json_input["token"]) class PNGrantTokenResult(_PAMv3Result): def __str__(self): return "Grant token: %s" % (self.token) def get_token(self): return self.token class PNRevokeTokenResult: def __init__(self, result): self.status = result["status"] def __str__(self): return "Revoke token success with status: %s" % self.status bdraco-freenub-69809e8/pubnub/models/consumer/v3/channel.py000066400000000000000000000016661464523203000236210ustar00rootroot00000000000000from pubnub.models.consumer.v3.pn_resource import PNResource class Channel(PNResource): def __init__(self, resource_name=None, resource_pattern=None): super().__init__(resource_name, resource_pattern) @staticmethod def id(channel_id): channel = Channel(resource_name=channel_id) return channel @staticmethod def pattern(channel_pattern): channel = Channel(resource_pattern=channel_pattern) return channel def read(self): self._read = True return self def manage(self): self._manage = True return self def write(self): self._write = True return self def delete(self): self._delete = True return self def get(self): self._get = True return self def update(self): self._update = True return self def join(self): self._join = True return self bdraco-freenub-69809e8/pubnub/models/consumer/v3/group.py000066400000000000000000000011111464523203000233260ustar00rootroot00000000000000from pubnub.models.consumer.v3.pn_resource import PNResource class Group(PNResource): def __init__(self, resource_name=None, resource_pattern=None): super().__init__(resource_name, resource_pattern) @staticmethod def id(group_id): group = Group(resource_name=group_id) return group @staticmethod def pattern(group_pattern): group = Group(resource_pattern=group_pattern) return group def read(self): self._read = True return self def manage(self): self._manage = True return self bdraco-freenub-69809e8/pubnub/models/consumer/v3/pn_resource.py000066400000000000000000000020251464523203000245230ustar00rootroot00000000000000class PNResource: def __init__(self, resource_name=None, resource_pattern=None): self._resource_name = resource_name self._resource_pattern = resource_pattern self._read = False self._write = False self._create = False self._manage = False self._delete = False self._get = False self._update = False self._join = False def is_pattern_resource(self): return self._resource_pattern is not None def get_id(self): if self.is_pattern_resource(): return self._resource_pattern return self._resource_name def is_read(self): return self._read def is_write(self): return self._write def is_create(self): return self._create def is_manage(self): return self._manage def is_delete(self): return self._delete def is_get(self): return self._get def is_update(self): return self._update def is_join(self): return self._join bdraco-freenub-69809e8/pubnub/models/consumer/v3/space.py000066400000000000000000000017471464523203000233040ustar00rootroot00000000000000from pubnub.models.consumer.v3.pn_resource import PNResource class Space(PNResource): def __init__(self, resource_name=None, resource_pattern=None): super().__init__(resource_name, resource_pattern) @staticmethod def id(space_id): space = Space(resource_name=space_id) return space @staticmethod def pattern(space_pattern): space = Space(resource_pattern=space_pattern) return space def read(self): self._read = True return self def write(self): self._write = True return self def create(self): self._create = True return self def manage(self): self._manage = True return self def delete(self): self._delete = True return self def get(self): self._get = True return self def update(self): self._update = True return self def join(self): self._join = True return self bdraco-freenub-69809e8/pubnub/models/consumer/v3/user.py000066400000000000000000000017341464523203000231630ustar00rootroot00000000000000from pubnub.models.consumer.v3.pn_resource import PNResource class User(PNResource): def __init__(self, resource_name=None, resource_pattern=None): super().__init__(resource_name, resource_pattern) @staticmethod def id(user_id): user = User(resource_name=user_id) return user @staticmethod def pattern(user_pattern): user = User(resource_pattern=user_pattern) return user def read(self): self._read = True return self def write(self): self._write = True return self def create(self): self._create = True return self def manage(self): self._manage = True return self def delete(self): self._delete = True return self def get(self): self._get = True return self def update(self): self._update = True return self def join(self): self._join = True return self bdraco-freenub-69809e8/pubnub/models/consumer/v3/uuid.py000066400000000000000000000012031464523203000231420ustar00rootroot00000000000000from pubnub.models.consumer.v3.pn_resource import PNResource class UUID(PNResource): def __init__(self, resource_name=None, resource_pattern=None): super().__init__(resource_name, resource_pattern) @staticmethod def id(user_id): user = UUID(resource_name=user_id) return user @staticmethod def pattern(user_pattern): user = UUID(resource_pattern=user_pattern) return user def delete(self): self._delete = True return self def get(self): self._get = True return self def update(self): self._update = True return self bdraco-freenub-69809e8/pubnub/models/server/000077500000000000000000000000001464523203000207515ustar00rootroot00000000000000bdraco-freenub-69809e8/pubnub/models/server/__init__.py000066400000000000000000000000001464523203000230500ustar00rootroot00000000000000bdraco-freenub-69809e8/pubnub/models/server/subscribe.py000066400000000000000000000066021464523203000233100ustar00rootroot00000000000000class SubscribeEnvelope: def __init__(self, messages=None, metadata=None): assert isinstance(messages, (list, None)) assert isinstance(metadata, (SubscribeMetadata, None)) self.messages = messages self.metadata = metadata @classmethod def from_json(cls, json_input): messages = [] for raw_message in json_input["m"]: messages.append(SubscribeMessage.from_json(raw_message)) metadata = SubscribeMetadata.from_json(json_input["t"]) return SubscribeEnvelope(messages, metadata) class SubscribeMessage: def __init__(self): self.shard = None self.subscription_match = None self.channel = None self.payload = None self.flags = None self.issuing_client_id = None self.subscribe_key = None self.origination_timetoken = None self.publish_metadata = None self.only_channel_subscription = False self.type = 0 @classmethod def from_json(cls, json_input): message = SubscribeMessage() if "a" in json_input: message.shard = json_input["a"] if "b" in json_input: message.subscription_match = json_input["b"] message.channel = json_input["c"] message.payload = json_input["d"] message.flags = json_input["f"] if "i" in json_input: message.issuing_client_id = json_input["i"] message.subscribe_key = json_input["k"] if "o" in json_input: message.origination_timetoken = json_input["o"] message.publish_metadata = PublishMetadata.from_json(json_input["p"]) if "e" in json_input: message.type = json_input["e"] return message class SubscribeMetadata: def __init__(self, timetoken=None, region=None): self.timetoken = timetoken self.region = region @classmethod def from_json(cls, json_input): assert isinstance(json_input, dict) assert "r" in json_input assert "t" in json_input return SubscribeMetadata(json_input["t"], json_input["r"]) class PresenceEnvelope: def __init__(self, action, uuid, occupancy, timestamp, data=None): assert isinstance(action, str) assert isinstance(occupancy, int) assert isinstance(timestamp, int) if data is not None: assert isinstance(data, dict) self.action = action self.uuid = uuid self.occupancy = occupancy self.timestamp = timestamp self.data = data @classmethod def extract_value(cls, json, key): if key in json: return json[key] else: return None @classmethod def from_json_payload(cls, json): return PresenceEnvelope( action=cls.extract_value(json, "action"), uuid=cls.extract_value(json, "uuid"), occupancy=cls.extract_value(json, "occupancy"), timestamp=cls.extract_value(json, "timestamp"), data=cls.extract_value(json, "data"), ) class PublishMetadata: def __init__(self, publish_timetoken=None, region=None): self.publish_timetoken = publish_timetoken self.region = region @classmethod def from_json(cls, json_input): assert "r" in json_input assert "t" in json_input return PublishMetadata(int(json_input["t"]), int(json_input["r"])) bdraco-freenub-69809e8/pubnub/models/subscription_item.py000066400000000000000000000002541464523203000235600ustar00rootroot00000000000000class SubscriptionItem: def __init__(self, name=None, state=None): self.name = name self.state = state def __str__(self): return self.name bdraco-freenub-69809e8/pubnub/pnconfiguration.py000066400000000000000000000112751464523203000217450ustar00rootroot00000000000000from Cryptodome.Cipher import AES from pubnub.enums import PNHeartbeatNotificationOptions, PNReconnectionPolicy from pubnub.exceptions import PubNubException class PNConfiguration: DEFAULT_PRESENCE_TIMEOUT = 300 DEFAULT_HEARTBEAT_INTERVAL = 280 ALLOWED_AES_MODES = [AES.MODE_CBC, AES.MODE_GCM] RECONNECTION_INTERVAL = 3 RECONNECTION_MIN_EXPONENTIAL_BACKOFF = 1 RECONNECTION_MAX_EXPONENTIAL_BACKOFF = 32 def __init__(self): # TODO: add validation self._uuid = None self.origin = "ps.pndsn.com" self.ssl = True self.non_subscribe_request_timeout = 10 self.subscribe_request_timeout = 310 self.connect_timeout = 10 self.subscribe_key = None self.publish_key = None self.secret_key = None self.cipher_key = None self._cipher_mode = AES.MODE_CBC self._fallback_cipher_mode = None self.auth_key = None self.filter_expression = None self.enable_subscribe = True self.crypto_instance = None self.file_crypto_instance = None self.log_verbosity = False self.enable_presence_heartbeat = False self.heartbeat_notification_options = PNHeartbeatNotificationOptions.FAILURES self.reconnect_policy = PNReconnectionPolicy.NONE self.maximum_reconnection_retries = -1 # -1 means unlimited/ 0 means no retries self.daemon = False self.use_random_initialization_vector = True self.suppress_leave_events = False self.should_compress = False self.heartbeat_default_values = True self._presence_timeout = PNConfiguration.DEFAULT_PRESENCE_TIMEOUT self._heartbeat_interval = PNConfiguration.DEFAULT_HEARTBEAT_INTERVAL def validate(self): PNConfiguration.validate_not_empty_string(self.uuid) def validate_not_empty_string(value: str): assert ( value and isinstance(value, str) and value.strip() != "" ), "UUID missing or invalid type" def scheme(self): if self.ssl: return "https" else: return "http" def scheme_extended(self): return self.scheme() + "://" def scheme_and_host(self): return self.scheme_extended() + self.origin def set_presence_timeout_with_custom_interval(self, timeout, interval): self.heartbeat_default_values = False self._presence_timeout = timeout self._heartbeat_interval = interval def set_presence_timeout(self, timeout): self.set_presence_timeout_with_custom_interval(timeout, (timeout / 2) - 1) @property def cipher_mode(self): return self._cipher_mode @cipher_mode.setter def cipher_mode(self, cipher_mode): if cipher_mode not in self.ALLOWED_AES_MODES: raise PubNubException("Cipher mode not supported") if cipher_mode is not self._cipher_mode: self._cipher_mode = cipher_mode self.crypto_instance = None @property def fallback_cipher_mode(self): return self._fallback_cipher_mode @fallback_cipher_mode.setter def fallback_cipher_mode(self, fallback_cipher_mode): if fallback_cipher_mode not in self.ALLOWED_AES_MODES: raise PubNubException("Cipher mode not supported") if fallback_cipher_mode is not self._fallback_cipher_mode: self._fallback_cipher_mode = fallback_cipher_mode self.crypto_instance = None @property def crypto(self): if self.crypto_instance is None: self._init_cryptodome() return self.crypto_instance def _init_cryptodome(self): from .crypto import PubNubCryptodome self.crypto_instance = PubNubCryptodome(self) def _init_file_crypto(self): from .crypto import PubNubFileCrypto self.file_crypto_instance = PubNubFileCrypto(self) @property def file_crypto(self): if not self.file_crypto_instance: self._init_file_crypto() return self.file_crypto_instance @property def port(self): return 443 if self.ssl == "https" else 80 @property def presence_timeout(self): return self._presence_timeout @property def heartbeat_interval(self): return self._heartbeat_interval # TODO: set log level # TODO: set log level @property def uuid(self): return self._uuid @uuid.setter def uuid(self, uuid): PNConfiguration.validate_not_empty_string(uuid) self._uuid = uuid @property def user_id(self): return self._uuid @user_id.setter def user_id(self, user_id): PNConfiguration.validate_not_empty_string(user_id) self._uuid = user_id bdraco-freenub-69809e8/pubnub/pubnub.py000066400000000000000000000407611464523203000200350ustar00rootroot00000000000000import copy import logging import threading from queue import Empty, Queue from threading import Event from . import utils from .callbacks import ReconnectionCallback, SubscribeCallback from .endpoints.presence.heartbeat import Heartbeat from .endpoints.presence.leave import Leave from .endpoints.pubsub.subscribe import Subscribe from .enums import ( PNHeartbeatNotificationOptions, PNOperationType, PNReconnectionPolicy, PNStatusCategory, ) from .managers import ( PublishSequenceManager, ReconnectionManager, SubscriptionManager, TelemetryManager, ) from .models.consumer.common import PNStatus from .pnconfiguration import PNConfiguration from .pubnub_core import PubNubCore from .request_handlers.base import BaseRequestHandler from .request_handlers.requests_handler import RequestsRequestHandler from .structures import PlatformOptions from .workers import SubscribeMessageWorker logger = logging.getLogger("pubnub") class PubNub(PubNubCore): """PubNub Python API""" def __init__(self, config): assert isinstance(config, PNConfiguration) PubNubCore.__init__(self, config) self._request_handler = RequestsRequestHandler(self) if self.config.enable_subscribe: self._subscription_manager = NativeSubscriptionManager(self) self._publish_sequence_manager = PublishSequenceManager(PubNubCore.MAX_SEQUENCE) self._telemetry_manager = NativeTelemetryManager() def sdk_platform(self): return "" def set_request_handler(self, handler): assert isinstance(handler, BaseRequestHandler) self._request_handler = handler def request_sync(self, endpoint_call_options): platform_options = PlatformOptions(self.headers, self.config) self.merge_in_params(endpoint_call_options) if self.config.log_verbosity: print(endpoint_call_options) return self._request_handler.sync_request( platform_options, endpoint_call_options ) def request_async( self, endpoint_name, endpoint_call_options, callback, cancellation_event ): platform_options = PlatformOptions(self.headers, self.config) self.merge_in_params(endpoint_call_options) if self.config.log_verbosity: print(endpoint_call_options) return self._request_handler.async_request( endpoint_name, platform_options, endpoint_call_options, callback, cancellation_event, ) def merge_in_params(self, options): params_to_merge_in = {} if options.operation_type == PNOperationType.PNPublishOperation: params_to_merge_in["seqn"] = ( self._publish_sequence_manager.get_next_sequence() ) options.merge_params_in(params_to_merge_in) def stop(self): if self._subscription_manager is not None: self._subscription_manager.stop() else: raise Exception("Subscription manager is not enabled for this instance") def request_deferred(self, options_func): raise NotImplementedError def request_future(self, *args, **kwargs): raise NotImplementedError class NativeReconnectionManager(ReconnectionManager): def __init__(self, pubnub): super().__init__(pubnub) def _register_heartbeat_timer(self): self.stop_heartbeat_timer() self._recalculate_interval() self._timer = threading.Timer(self._timer_interval, self._call_time) self._timer.daemon = True self._timer.start() def _call_time(self): self._pubnub.time().pn_async(self._call_time_callback) def _call_time_callback(self, resp, status): if not status.is_error(): self._connection_errors = 1 self.stop_heartbeat_timer() self._callback.on_reconnect() logger.debug( "reconnection manager stop due success time endpoint call: %s" % utils.datetime_now() ) elif self._pubnub.config.reconnect_policy == PNReconnectionPolicy.EXPONENTIAL: logger.debug("reconnect interval increment at: %s" % utils.datetime_now()) self.stop_heartbeat_timer() self._connection_errors += 1 self._register_heartbeat_timer() elif self._pubnub.config.reconnect_policy == PNReconnectionPolicy.LINEAR: self.stop_heartbeat_timer() self._connection_errors += 1 self._register_heartbeat_timer() def start_polling(self): if self._pubnub.config.reconnect_policy == PNReconnectionPolicy.NONE: logger.warning( "reconnection policy is disabled, please handle reconnection manually." ) return logger.debug("reconnection manager start at: %s" % utils.datetime_now()) self._register_heartbeat_timer() def stop_heartbeat_timer(self): if self._timer is not None: self._timer.cancel() class NativePublishSequenceManager(PublishSequenceManager): def __init__(self, provided_max_sequence): super().__init__(provided_max_sequence) self._lock = threading.Lock() def get_next_sequence(self): with self._lock: if self.max_sequence == self.next_sequence: self.next_sequence = 1 else: self.next_sequence += 1 return self.next_sequence class NativeSubscriptionManager(SubscriptionManager): def __init__(self, pubnub_instance): subscription_manager = self self._message_queue = Queue() self._consumer_event = threading.Event() self._subscribe_call = None self._heartbeat_periodic_callback = None self._reconnection_manager = NativeReconnectionManager(pubnub_instance) super().__init__(pubnub_instance) self._start_worker() class NativeReconnectionCallback(ReconnectionCallback): def on_reconnect(self): subscription_manager.reconnect() pn_status = PNStatus() pn_status.category = PNStatusCategory.PNReconnectedCategory pn_status.error = False subscription_manager._subscription_status_announced = True subscription_manager._listener_manager.announce_status(pn_status) self._reconnection_listener = NativeReconnectionCallback() self._reconnection_manager.set_reconnection_listener( self._reconnection_listener ) def _send_leave(self, unsubscribe_operation): def leave_callback(result, status): self._listener_manager.announce_status(status) Leave(self._pubnub).channels(unsubscribe_operation.channels).channel_groups( unsubscribe_operation.channel_groups ).pn_async(leave_callback) def _register_heartbeat_timer(self): super()._register_heartbeat_timer() self._perform_heartbeat_loop() self._heartbeat_periodic_callback = NativePeriodicCallback( self._perform_heartbeat_loop, self._pubnub.config.heartbeat_interval ) if not self._should_stop: self._heartbeat_periodic_callback.start() def _perform_heartbeat_loop(self): if self._heartbeat_call is not None: # TODO: cancel call pass state_payload = self._subscription_state.state_payload() presence_channels = self._subscription_state.prepare_channel_list(False) presence_groups = self._subscription_state.prepare_channel_group_list(False) if len(presence_channels) == 0 and len(presence_groups) == 0: return def heartbeat_callback(raw_result, status): heartbeat_verbosity = self._pubnub.config.heartbeat_notification_options if status.is_error(): if heartbeat_verbosity in ( PNHeartbeatNotificationOptions.ALL, PNHeartbeatNotificationOptions.FAILURES, ): self._listener_manager.announce_status(status) else: if heartbeat_verbosity == PNHeartbeatNotificationOptions.ALL: self._listener_manager.announce_status(status) try: ( Heartbeat(self._pubnub) .channels(presence_channels) .channel_groups(presence_groups) .state(state_payload) .pn_async(heartbeat_callback) ) except Exception as e: logger.error("Heartbeat request failed: %s" % e) def _stop_heartbeat_timer(self): if self._heartbeat_periodic_callback is not None: self._heartbeat_periodic_callback.stop() def _set_consumer_event(self): self._consumer_event.set() self._message_queue_put(None) def _message_queue_put(self, message): self._message_queue.put(message) def reconnect(self): self._should_stop = False self._start_subscribe_loop() # Check the instance flag to determine if we want to perform the presence heartbeat # This is False by default if self._pubnub.config.enable_presence_heartbeat is True: self._register_heartbeat_timer() def disconnect(self): self._should_stop = True self._stop_heartbeat_timer() self._stop_subscribe_loop() def _start_worker(self): consumer = NativeSubscribeMessageWorker( self._pubnub, self._listener_manager, self._message_queue, self._consumer_event, ) self._consumer_thread = threading.Thread( target=consumer.run, name="SubscribeMessageWorker" ) self._consumer_thread.daemon = True self._consumer_thread.start() def _start_subscribe_loop(self): self._stop_subscribe_loop() combined_channels = self._subscription_state.prepare_channel_list(True) combined_groups = self._subscription_state.prepare_channel_group_list(True) if len(combined_channels) == 0 and len(combined_groups) == 0: return def callback(raw_result, status): """SubscribeEndpoint callback""" if status.is_error(): if status and status.category == PNStatusCategory.PNCancelledCategory: return if ( status.category is PNStatusCategory.PNTimeoutCategory and not self._should_stop ): self._start_subscribe_loop() return logger.error( "Exception in subscribe loop: %s" % str(status.error_data.exception) ) if ( status and status.category == PNStatusCategory.PNAccessDeniedCategory ): status.operation = PNOperationType.PNUnsubscribeOperation self._listener_manager.announce_status(status) self.unsubscribe_all() self.disconnect() return self._listener_manager.announce_status(status) self._reconnection_manager.start_polling() self.disconnect() else: self._handle_endpoint_call(raw_result, status) self._start_subscribe_loop() try: self._subscribe_call = ( Subscribe(self._pubnub) .channels(combined_channels) .channel_groups(combined_groups) .timetoken(self._timetoken) .region(self._region) .filter_expression(self._pubnub.config.filter_expression) .pn_async(callback) ) except Exception as e: logger.error("Subscribe request failed: %s" % e) def _stop_subscribe_loop(self): sc = self._subscribe_call if sc is not None and not sc.is_executed and not sc.is_canceled: sc.cancel() class NativePeriodicCallback: def __init__(self, callback, callback_time): self._callback = callback self._callback_time = callback_time self._running = False self._timeout = None def start(self): self._running = True self._schedule_next() def stop(self): self._running = False if self._timeout is not None: self._timeout.cancel() self._timeout = None def _run(self): if not self._running: return try: self._callback() except Exception: # TODO: handle the exception pass finally: self._schedule_next() def _schedule_next(self): self._timeout = threading.Timer(self._callback_time, self._run) self._timer.daemon = True self._timeout.start() class NativeSubscribeMessageWorker(SubscribeMessageWorker): def _take_message(self): while not self._event.is_set(): try: # TODO: get rid of 1s timeout msg = self._queue.get(True, 1) if msg is not None: self._process_incoming_payload(msg) self._queue.task_done() except Empty: continue except Exception as e: # TODO: move to finally self._queue.task_done() self._event.set() logger.error("take message interrupted: %s" % str(e)) raise class SubscribeListener(SubscribeCallback): def __init__(self): self.connected = False self.connected_event = Event() self.disconnected_event = Event() self.presence_queue = Queue() self.message_queue = Queue() self.channel_queue = Queue() self.uuid_queue = Queue() self.membership_queue = Queue() def status(self, pubnub, status): if utils.is_subscribed_event(status) and not self.connected_event.is_set(): self.connected_event.set() elif ( utils.is_unsubscribed_event(status) and not self.disconnected_event.is_set() ): self.disconnected_event.set() def message(self, pubnub, message): self.message_queue.put(message) def presence(self, pubnub, presence): self.presence_queue.put(presence) def wait_for_connect(self): if not self.connected_event.is_set(): self.connected_event.wait() else: raise Exception("the instance is already connected") def channel(self, pubnub, channel): self.channel_queue.put(channel) def uuid(self, pubnub, uuid): self.uuid_queue.put(uuid) def membership(self, pubnub, membership): self.membership_queue.put(membership) def wait_for_disconnect(self): if not self.disconnected_event.is_set(): self.disconnected_event.wait() else: raise Exception("the instance is already disconnected") def wait_for_message_on(self, *channel_names): channel_names = list(channel_names) while True: env = self.message_queue.get() self.message_queue.task_done() if env.channel in channel_names: return env else: continue def wait_for_presence_on(self, *channel_names): channel_names = list(channel_names) while True: env = self.presence_queue.get() self.presence_queue.task_done() if env.channel in channel_names: return env else: continue class NonSubscribeListener: def __init__(self): self.result = None self.status = None self.done_event = Event() def callback(self, result, status): self.result = result self.status = status self.done_event.set() def pn_await(self, timeout=5): """Returns False if a timeout happened, otherwise True""" return self.done_event.wait(timeout) def await_result(self, timeout=5): self.pn_await(timeout) return self.result def await_result_and_reset(self, timeout=5): self.pn_await(timeout) cp = copy.copy(self.result) self.reset() return cp def reset(self): self.result = None self.status = None self.done_event.clear() class NativeTelemetryManager(TelemetryManager): def store_latency(self, latency, operation_type): super().store_latency(latency, operation_type) self.clean_up_telemetry_data() bdraco-freenub-69809e8/pubnub/pubnub_asyncio.py000066400000000000000000000655411464523203000215650ustar00rootroot00000000000000import asyncio import json import logging import math import time import urllib from asyncio import Event, Queue, Semaphore import aiohttp from yarl import URL from pubnub import utils from pubnub.callbacks import ReconnectionCallback, SubscribeCallback from pubnub.dtos import SubscribeOperation, UnsubscribeOperation from pubnub.endpoints.presence.heartbeat import Heartbeat from pubnub.endpoints.presence.leave import Leave from pubnub.endpoints.pubsub.subscribe import Subscribe from pubnub.enums import ( PNHeartbeatNotificationOptions, PNOperationType, PNReconnectionPolicy, PNStatusCategory, ) from pubnub.errors import ( PNERR_CLIENT_ERROR, PNERR_CLIENT_TIMEOUT, PNERR_JSON_DECODING_FAILED, PNERR_REQUEST_CANCELLED, PNERR_SERVER_ERROR, ) from pubnub.event_engine.models import events, states from pubnub.event_engine.statemachine import StateMachine from pubnub.managers import ( PublishSequenceManager, ReconnectionManager, SubscriptionManager, TelemetryManager, ) from pubnub.models.consumer.common import PNStatus from pubnub.pubnub_core import PubNubCore from pubnub.structures import RequestOptions, ResponseInfo from pubnub.workers import SubscribeMessageWorker from .exceptions import PubNubException logger = logging.getLogger("pubnub") class PubNubAsyncio(PubNubCore): """ PubNub Python SDK for asyncio framework """ def __init__(self, config, custom_event_loop=None, subscription_manager=None): super().__init__(config) self.event_loop = custom_event_loop or asyncio.get_event_loop() self._connector = None self._session = None self._connector = aiohttp.TCPConnector(verify_ssl=True, loop=self.event_loop) if not subscription_manager: subscription_manager = AsyncioSubscriptionManager if self.config.enable_subscribe: self._subscription_manager = subscription_manager(self) self._publish_sequence_manager = AsyncioPublishSequenceManager( self.event_loop, PubNubCore.MAX_SEQUENCE ) self._telemetry_manager = AsyncioTelemetryManager() def __del__(self): pass if self.event_loop.is_running(): self.event_loop.create_task(self.close_session()) async def close_pending_tasks(self, tasks): await asyncio.gather(*tasks) await asyncio.sleep(0.1) async def create_session(self): self._session = aiohttp.ClientSession( loop=self.event_loop, timeout=aiohttp.ClientTimeout(connect=self.config.connect_timeout), connector=self._connector, ) async def close_session(self): if self._session is not None: await self._session.close() async def set_connector(self, cn): await self._session.close() self._connector = cn self._session = aiohttp.ClientSession( loop=self.event_loop, timeout=aiohttp.ClientTimeout(connect=self.config.connect_timeout), connector=self._connector, ) async def stop(self): await self.close_session() if self._subscription_manager: self._subscription_manager.stop() def sdk_platform(self): return "-Asyncio" def request_sync(self, *args): raise NotImplementedError def request_deferred(self, *args): raise NotImplementedError async def request_result(self, options_func, cancellation_event): envelope = await self._request_helper(options_func, cancellation_event) return envelope.result async def request_future(self, options_func, cancellation_event): try: res = await self._request_helper(options_func, cancellation_event) return res except PubNubException as e: return PubNubAsyncioException(result=None, status=e.status) except asyncio.TimeoutError: return PubNubAsyncioException( result=None, status=options_func().create_status( PNStatusCategory.PNTimeoutCategory, None, None, exception=PubNubException(pn_error=PNERR_CLIENT_TIMEOUT), ), ) except asyncio.CancelledError: return PubNubAsyncioException( result=None, status=options_func().create_status( PNStatusCategory.PNCancelledCategory, None, None, exception=PubNubException(pn_error=PNERR_REQUEST_CANCELLED), ), ) except Exception as e: return PubNubAsyncioException( result=None, status=options_func().create_status( PNStatusCategory.PNUnknownCategory, None, None, e ), ) async def _request_helper(self, options_func, cancellation_event): """ Query string should be provided as a manually serialized and encoded string. :param options_func: :param cancellation_event: :return: """ if cancellation_event is not None: assert isinstance(cancellation_event, Event) options = options_func() assert isinstance(options, RequestOptions) create_response = options.create_response create_status = options.create_status create_exception = options.create_exception params_to_merge_in = {} if options.operation_type == PNOperationType.PNPublishOperation: params_to_merge_in[ "seqn" ] = await self._publish_sequence_manager.get_next_sequence() options.merge_params_in(params_to_merge_in) if options.use_base_path: url = utils.build_url( self.config.scheme(), self.base_origin, options.path, options.query_string, ) else: url = utils.build_url( scheme="", origin="", path=options.path, params=options.query_string ) url = URL(url, encoded=True) logger.debug(f"{options.method_string} {url} {options.data}") if options.request_headers: request_headers = {**self.headers, **options.request_headers} else: request_headers = self.headers try: if not self._session: await self.create_session() start_timestamp = time.time() response = await asyncio.wait_for( self._session.request( options.method_string, url, headers=request_headers, data=options.data if options.data else None, allow_redirects=options.allow_redirects, ), options.request_timeout, ) except (asyncio.TimeoutError, asyncio.CancelledError): raise except Exception as e: logger.error("session.request exception: %s" % str(e)) raise if not options.non_json_response: body = await response.text() else: if isinstance(response.content, bytes): body = ( response.content ) # TODO: simplify this logic within the v5 release else: body = await response.read() if cancellation_event is not None and cancellation_event.is_set(): return response_info = None status_category = PNStatusCategory.PNUnknownCategory if response: request_url = urllib.parse.urlparse(str(response.url)) query = urllib.parse.parse_qs(request_url.query) uuid = None auth_key = None if "uuid" in query and len(query["uuid"]) > 0: uuid = query["uuid"][0] if "auth_key" in query and len(query["auth_key"]) > 0: auth_key = query["auth_key"][0] response_info = ResponseInfo( status_code=response.status, tls_enabled="https" == request_url.scheme, origin=request_url.hostname, uuid=uuid, auth_key=auth_key, client_request=None, client_response=response, ) # if body is not None and len(body) > 0 and not options.non_json_response: if body is not None and len(body) > 0: if options.non_json_response: data = body else: try: data = json.loads(body) except ValueError: if response.status == 599 and len(body) > 0: data = body else: raise except TypeError: try: data = json.loads(body.decode("utf-8")) except ValueError: raise create_exception( category=status_category, response=response, response_info=response_info, exception=PubNubException( pn_error=PNERR_JSON_DECODING_FAILED, errormsg="json decode error", ), ) else: data = "N/A" logger.debug(data) if response.status not in (200, 307, 204): if response.status >= 500: err = PNERR_SERVER_ERROR else: err = PNERR_CLIENT_ERROR if response.status == 403: status_category = PNStatusCategory.PNAccessDeniedCategory if response.status == 400: status_category = PNStatusCategory.PNBadRequestCategory raise create_exception( category=status_category, response=data, response_info=response_info, exception=PubNubException( errormsg=data, pn_error=err, status_code=response.status ), ) else: self._telemetry_manager.store_latency( time.time() - start_timestamp, options.operation_type ) return AsyncioEnvelope( result=create_response(data) if not options.non_json_response else create_response(response, data), status=create_status( PNStatusCategory.PNAcknowledgmentCategory, data, response_info, None ), ) class AsyncioReconnectionManager(ReconnectionManager): def __init__(self, pubnub): self._task = None super().__init__(pubnub) async def _register_heartbeat_timer(self): while True: self._recalculate_interval() await asyncio.sleep(self._timer_interval) logger.debug("reconnect loop at: %s" % utils.datetime_now()) try: await self._pubnub.time().future() self._connection_errors = 1 self._callback.on_reconnect() break except Exception: if ( self._pubnub.config.reconnect_policy == PNReconnectionPolicy.EXPONENTIAL ): logger.debug( "reconnect interval increment at: %s" % utils.datetime_now() ) self._connection_errors += 1 def start_polling(self): if self._pubnub.config.reconnect_policy == PNReconnectionPolicy.NONE: logger.warning( "reconnection policy is disabled, please handle reconnection manually." ) return self._task = asyncio.ensure_future(self._register_heartbeat_timer()) def stop_polling(self): if self._task is not None and not self._task.cancelled(): self._task.cancel() class AsyncioPublishSequenceManager(PublishSequenceManager): def __init__(self, ioloop, provided_max_sequence): super().__init__(provided_max_sequence) self._lock = asyncio.Lock() self._event_loop = ioloop async def get_next_sequence(self): async with self._lock: if self.max_sequence == self.next_sequence: self.next_sequence = 1 else: self.next_sequence += 1 return self.next_sequence class AsyncioSubscriptionManager(SubscriptionManager): def __init__(self, pubnub_instance): subscription_manager = self self._message_worker = None self._message_queue = Queue() self._subscription_lock = Semaphore(1) self._subscribe_loop_task = None self._heartbeat_periodic_callback = None self._reconnection_manager = AsyncioReconnectionManager(pubnub_instance) super().__init__(pubnub_instance) self._start_worker() class AsyncioReconnectionCallback(ReconnectionCallback): def on_reconnect(self): subscription_manager.reconnect() pn_status = PNStatus() pn_status.category = PNStatusCategory.PNReconnectedCategory pn_status.error = False subscription_manager._subscription_status_announced = True subscription_manager._listener_manager.announce_status(pn_status) self._reconnection_listener = AsyncioReconnectionCallback() self._reconnection_manager.set_reconnection_listener( self._reconnection_listener ) def _set_consumer_event(self): if not self._message_worker.cancelled(): self._message_worker.cancel() def _message_queue_put(self, message): self._message_queue.put_nowait(message) def _start_worker(self): consumer = AsyncioSubscribeMessageWorker( self._pubnub, self._listener_manager, self._message_queue, None ) self._message_worker = asyncio.ensure_future( consumer.run(), loop=self._pubnub.event_loop ) def reconnect(self): # TODO: method is synchronized in Java self._should_stop = False self._subscribe_loop_task = asyncio.ensure_future(self._start_subscribe_loop()) self._register_heartbeat_timer() def disconnect(self): # TODO: method is synchronized in Java self._should_stop = True self._stop_heartbeat_timer() self._stop_subscribe_loop() def stop(self): super().stop() self._reconnection_manager.stop_polling() if self._subscribe_loop_task and not self._subscribe_loop_task.cancelled(): self._subscribe_loop_task.cancel() async def _start_subscribe_loop(self): self._stop_subscribe_loop() await self._subscription_lock.acquire() combined_channels = self._subscription_state.prepare_channel_list(True) combined_groups = self._subscription_state.prepare_channel_group_list(True) if len(combined_channels) == 0 and len(combined_groups) == 0: self._subscription_lock.release() return self._subscribe_request_task = asyncio.ensure_future( Subscribe(self._pubnub) .channels(combined_channels) .channel_groups(combined_groups) .timetoken(self._timetoken) .region(self._region) .filter_expression(self._pubnub.config.filter_expression) .future() ) e = await self._subscribe_request_task if self._subscribe_request_task.cancelled(): self._subscription_lock.release() return if e.is_error(): if e.status and e.status.category == PNStatusCategory.PNCancelledCategory: self._subscription_lock.release() return if e.status and e.status.category == PNStatusCategory.PNTimeoutCategory: asyncio.ensure_future(self._start_subscribe_loop()) self._subscription_lock.release() return logger.error("Exception in subscribe loop: %s" % str(e)) if ( e.status and e.status.category == PNStatusCategory.PNAccessDeniedCategory ): e.status.operation = PNOperationType.PNUnsubscribeOperation # TODO: raise error self._listener_manager.announce_status(e.status) self._reconnection_manager.start_polling() self._subscription_lock.release() self.disconnect() return else: self._handle_endpoint_call(e.result, e.status) self._subscription_lock.release() self._subscribe_loop_task = asyncio.ensure_future( self._start_subscribe_loop() ) self._subscription_lock.release() def _stop_subscribe_loop(self): if ( self._subscribe_request_task is not None and not self._subscribe_request_task.cancelled() ): self._subscribe_request_task.cancel() def _stop_heartbeat_timer(self): if self._heartbeat_periodic_callback is not None: self._heartbeat_periodic_callback.stop() def _register_heartbeat_timer(self): super()._register_heartbeat_timer() self._heartbeat_periodic_callback = AsyncioPeriodicCallback( self._perform_heartbeat_loop, self._pubnub.config.heartbeat_interval * 1000, self._pubnub.event_loop, ) if not self._should_stop: self._heartbeat_periodic_callback.start() async def _perform_heartbeat_loop(self): if self._heartbeat_call is not None: # TODO: cancel call pass cancellation_event = Event() state_payload = self._subscription_state.state_payload() presence_channels = self._subscription_state.prepare_channel_list(False) presence_groups = self._subscription_state.prepare_channel_group_list(False) if len(presence_channels) == 0 and len(presence_groups) == 0: return try: heartbeat_call = ( Heartbeat(self._pubnub) .channels(presence_channels) .channel_groups(presence_groups) .state(state_payload) .cancellation_event(cancellation_event) .future() ) envelope = await heartbeat_call heartbeat_verbosity = self._pubnub.config.heartbeat_notification_options if envelope.status.is_error(): if heartbeat_verbosity in ( PNHeartbeatNotificationOptions.ALL, PNHeartbeatNotificationOptions.FAILURES, ): self._listener_manager.announce_status(envelope.status) else: if heartbeat_verbosity == PNHeartbeatNotificationOptions.ALL: self._listener_manager.announce_status(envelope.status) except PubNubAsyncioException: pass # TODO: check correctness # if e.status is not None and e.status.category == PNStatusCategory.PNTimeoutCategory: # self._start_subscribe_loop() # else: # self._listener_manager.announce_status(e.status) finally: cancellation_event.set() def _send_leave(self, unsubscribe_operation): asyncio.ensure_future(self._send_leave_helper(unsubscribe_operation)) async def _send_leave_helper(self, unsubscribe_operation): envelope = ( await Leave(self._pubnub) .channels(unsubscribe_operation.channels) .channel_groups(unsubscribe_operation.channel_groups) .future() ) self._listener_manager.announce_status(envelope.status) class EventEngineSubscriptionManager(SubscriptionManager): event_engine: StateMachine loop: asyncio.AbstractEventLoop def __init__(self, pubnub_instance): self.event_engine = StateMachine(states.UnsubscribedState) self.event_engine.get_dispatcher().set_pn(pubnub_instance) self.loop = asyncio.new_event_loop() super().__init__(pubnub_instance) def stop(self): self.event_engine.stop() def adapt_subscribe_builder(self, subscribe_operation: SubscribeOperation): if not isinstance(subscribe_operation, SubscribeOperation): raise PubNubException("Invalid Subscribe Operation") if subscribe_operation.timetoken: subscription_event = events.SubscriptionRestoredEvent( channels=subscribe_operation.channels, groups=subscribe_operation.channel_groups, timetoken=subscribe_operation.timetoken, ) else: subscription_event = events.SubscriptionChangedEvent( channels=subscribe_operation.channels, groups=subscribe_operation.channel_groups, ) self.event_engine.trigger(subscription_event) def adapt_unsubscribe_builder(self, unsubscribe_operation): if not isinstance(unsubscribe_operation, UnsubscribeOperation): raise PubNubException("Invalid Unsubscribe Operation") event = events.SubscriptionChangedEvent(None, None) self.event_engine.trigger(event) class AsyncioSubscribeMessageWorker(SubscribeMessageWorker): async def run(self): await self._take_message() async def _take_message(self): while True: try: msg = await self._queue.get() if msg is not None: self._process_incoming_payload(msg) self._queue.task_done() except asyncio.CancelledError: logger.debug("Message Worker cancelled") break except Exception as e: logger.error("take message interrupted: %s" % str(e)) raise class AsyncioPeriodicCallback: def __init__(self, callback, callback_time, event_loop): self._callback = callback self._callback_time = callback_time self._event_loop = event_loop self._next_timeout = None self._running = False self._timeout = None def start(self): self._running = True self._next_timeout = self._event_loop.time() self._schedule_next() def stop(self): self._running = False if self._timeout is not None: self._timeout.cancel() self._timeout = None def _run(self): if not self._running: return try: asyncio.ensure_future(self._callback()) except Exception: raise finally: self._schedule_next() def _schedule_next(self): current_time = self._event_loop.time() if self._next_timeout <= current_time: callback_time_sec = self._callback_time / 1000.0 self._next_timeout += ( math.floor((current_time - self._next_timeout) / callback_time_sec) + 1 ) * callback_time_sec self._timeout = self._event_loop.call_at(self._next_timeout, self._run) class AsyncioEnvelope: def __init__(self, result, status): self.result = result self.status = status @staticmethod def is_error(): return False class PubNubAsyncioException(Exception): def __init__(self, result, status): self.result = result self.status = status def __str__(self): return str(self.status.error_data.exception) @staticmethod def is_error(): return True def value(self): return self.status.error_data.exception class SubscribeListener(SubscribeCallback): def __init__(self): self.connected = False self.connected_event = Event() self.disconnected_event = Event() self.presence_queue = Queue() self.message_queue = Queue() self.error_queue = Queue() def status(self, pubnub, status): if utils.is_subscribed_event(status) and not self.connected_event.is_set(): self.connected_event.set() elif ( utils.is_unsubscribed_event(status) and not self.disconnected_event.is_set() ): self.disconnected_event.set() elif status.is_error(): self.error_queue.put_nowait(status.error_data.exception) def message(self, pubnub, message): self.message_queue.put_nowait(message) def presence(self, pubnub, presence): self.presence_queue.put_nowait(presence) async def _wait_for(self, coro): scc_task = asyncio.ensure_future(coro) err_task = asyncio.ensure_future(self.error_queue.get()) await asyncio.wait([scc_task, err_task], return_when=asyncio.FIRST_COMPLETED) if err_task.done() and not scc_task.done(): if not scc_task.cancelled(): scc_task.cancel() raise err_task.result() else: if not err_task.cancelled(): err_task.cancel() return scc_task.result() async def wait_for_connect(self): if not self.connected_event.is_set(): await self._wait_for(self.connected_event.wait()) else: raise Exception("instance is already connected") async def wait_for_disconnect(self): if not self.disconnected_event.is_set(): await self._wait_for(self.disconnected_event.wait()) else: raise Exception("instance is already disconnected") async def wait_for_message_on(self, *channel_names): channel_names = list(channel_names) while True: try: env = await self._wait_for(self.message_queue.get()) if env.channel in channel_names: return env else: continue finally: self.message_queue.task_done() async def wait_for_presence_on(self, *channel_names): channel_names = list(channel_names) while True: try: env = await self._wait_for(self.presence_queue.get()) if env.channel in channel_names: return env else: continue finally: self.presence_queue.task_done() class AsyncioTelemetryManager(TelemetryManager): def __init__(self): TelemetryManager.__init__(self) self.loop = asyncio.get_event_loop() self._schedule_next_cleanup() def _schedule_next_cleanup(self): self._timer = self.loop.call_later( self.CLEAN_UP_INTERVAL * self.CLEAN_UP_INTERVAL_MULTIPLIER / 1000, self._clean_up_schedule_next, ) def _clean_up_schedule_next(self): self.clean_up_telemetry_data() self._schedule_next_cleanup() def _stop_clean_up_timer(self): self._timer.cancel() bdraco-freenub-69809e8/pubnub/pubnub_core.py000066400000000000000000000504631464523203000210450ustar00rootroot00000000000000import logging import time from abc import ABCMeta, abstractmethod from pubnub.endpoints.entities.membership.add_memberships import ( AddSpaceMembers, AddUserSpaces, ) from pubnub.endpoints.entities.membership.fetch_memberships import ( FetchSpaceMemberships, FetchUserMemberships, ) from pubnub.endpoints.entities.membership.remove_memberships import ( RemoveSpaceMembers, RemoveUserSpaces, ) from pubnub.endpoints.entities.membership.update_memberships import ( UpdateSpaceMembers, UpdateUserSpaces, ) from pubnub.endpoints.entities.space.create_space import CreateSpace from pubnub.endpoints.entities.space.fetch_space import FetchSpace from pubnub.endpoints.entities.space.fetch_spaces import FetchSpaces from pubnub.endpoints.entities.space.remove_space import RemoveSpace from pubnub.endpoints.entities.space.update_space import UpdateSpace from pubnub.endpoints.entities.user.create_user import CreateUser from pubnub.endpoints.entities.user.fetch_user import FetchUser from pubnub.endpoints.entities.user.fetch_users import FetchUsers from pubnub.endpoints.entities.user.remove_user import RemoveUser from pubnub.endpoints.entities.user.update_user import UpdateUser from pubnub.errors import PNERR_MISUSE_OF_USER_AND_SPACE, PNERR_USER_SPACE_PAIRS_MISSING from pubnub.exceptions import PubNubException from pubnub.features import feature_flag from .builders import SubscribeBuilder, UnsubscribeBuilder from .endpoints.access.audit import Audit from .endpoints.access.grant import Grant from .endpoints.access.grant_token import GrantToken from .endpoints.access.revoke_token import RevokeToken from .endpoints.channel_groups.add_channel_to_channel_group import ( AddChannelToChannelGroup, ) from .endpoints.channel_groups.list_channels_in_channel_group import ( ListChannelsInChannelGroup, ) from .endpoints.channel_groups.remove_channel_from_channel_group import ( RemoveChannelFromChannelGroup, ) from .endpoints.channel_groups.remove_channel_group import RemoveChannelGroup from .endpoints.fetch_messages import FetchMessages from .endpoints.file_operations.delete_file import DeleteFile from .endpoints.file_operations.download_file import DownloadFileNative from .endpoints.file_operations.fetch_upload_details import FetchFileUploadS3Data from .endpoints.file_operations.get_file_url import GetFileDownloadUrl from .endpoints.file_operations.list_files import ListFiles from .endpoints.file_operations.publish_file_message import PublishFileMessage from .endpoints.file_operations.send_file import SendFileNative from .endpoints.history import History from .endpoints.history_delete import HistoryDelete from .endpoints.message_actions.add_message_action import AddMessageAction from .endpoints.message_actions.get_message_actions import GetMessageActions from .endpoints.message_actions.remove_message_action import RemoveMessageAction from .endpoints.message_count import MessageCount from .endpoints.objects_v2.channel.get_all_channels import GetAllChannels from .endpoints.objects_v2.channel.get_channel import GetChannel from .endpoints.objects_v2.channel.remove_channel import RemoveChannel from .endpoints.objects_v2.channel.set_channel import SetChannel from .endpoints.objects_v2.members.get_channel_members import GetChannelMembers from .endpoints.objects_v2.members.manage_channel_members import ManageChannelMembers from .endpoints.objects_v2.members.remove_channel_members import RemoveChannelMembers from .endpoints.objects_v2.members.set_channel_members import SetChannelMembers from .endpoints.objects_v2.memberships.get_memberships import GetMemberships from .endpoints.objects_v2.memberships.manage_memberships import ManageMemberships from .endpoints.objects_v2.memberships.remove_memberships import RemoveMemberships from .endpoints.objects_v2.memberships.set_memberships import SetMemberships from .endpoints.objects_v2.uuid.get_all_uuid import GetAllUuid from .endpoints.objects_v2.uuid.get_uuid import GetUuid from .endpoints.objects_v2.uuid.remove_uuid import RemoveUuid from .endpoints.objects_v2.uuid.set_uuid import SetUuid from .endpoints.presence.get_state import GetState from .endpoints.presence.heartbeat import Heartbeat from .endpoints.presence.here_now import HereNow from .endpoints.presence.set_state import SetState from .endpoints.presence.where_now import WhereNow from .endpoints.pubsub.fire import Fire from .endpoints.pubsub.publish import Publish from .endpoints.push.add_channels_to_push import AddChannelsToPush from .endpoints.push.list_push_provisions import ListPushProvisions from .endpoints.push.remove_channels_from_push import RemoveChannelsFromPush from .endpoints.push.remove_device import RemoveDeviceFromPush from .endpoints.signal import Signal from .endpoints.time import Time from .managers import BasePathManager, TelemetryManager, TokenManager logger = logging.getLogger("pubnub") class PubNubCore: """A base class for PubNub Python API implementations""" SDK_VERSION = "7.2.0" SDK_NAME = "PubNub-Python" TIMESTAMP_DIVIDER = 1000 MAX_SEQUENCE = 65535 __metaclass__ = ABCMeta _plugins = [] def __init__(self, config): self.config = config self.config.validate() self.headers = {"User-Agent": self.sdk_name} self._subscription_manager = None self._publish_sequence_manager = None self._telemetry_manager = TelemetryManager() self._base_path_manager = BasePathManager(config) self._token_manager = TokenManager() @property def base_origin(self): return self._base_path_manager.get_base_path() @property def sdk_name(self): return f"{PubNubCore.SDK_NAME}{self.sdk_platform()}/{PubNubCore.SDK_VERSION}" @abstractmethod def sdk_platform(self): pass @property def uuid(self): return self.config.uuid def add_listener(self, listener): self._validate_subscribe_manager_enabled() return self._subscription_manager.add_listener(listener) def remove_listener(self, listener): self._validate_subscribe_manager_enabled() return self._subscription_manager.remove_listener(listener) def get_subscribed_channels(self): self._validate_subscribe_manager_enabled() return self._subscription_manager.get_subscribed_channels() def get_subscribed_channel_groups(self): self._validate_subscribe_manager_enabled() return self._subscription_manager.get_subscribed_channel_groups() def add_channel_to_channel_group(self): return AddChannelToChannelGroup(self) def remove_channel_from_channel_group(self): return RemoveChannelFromChannelGroup(self) def list_channels_in_channel_group(self): return ListChannelsInChannelGroup(self) def remove_channel_group(self): return RemoveChannelGroup(self) def subscribe(self): return SubscribeBuilder(self._subscription_manager) def unsubscribe(self): return UnsubscribeBuilder(self._subscription_manager) def unsubscribe_all(self): return self._subscription_manager.unsubscribe_all() def reconnect(self): return self._subscription_manager.reconnect() def heartbeat(self): return Heartbeat(self) def set_state(self): return SetState(self, self._subscription_manager) def get_state(self): return GetState(self) def here_now(self): return HereNow(self) def where_now(self): return WhereNow(self) def publish(self): return Publish(self) def grant(self): return Grant(self) def grant_token(self): return GrantToken(self) def revoke_token(self, token): return RevokeToken(self, token) def audit(self): return Audit(self) # Push Related methods def list_push_channels(self): return ListPushProvisions(self) def add_channels_to_push(self): return AddChannelsToPush(self) def remove_channels_from_push(self): return RemoveChannelsFromPush(self) def remove_device_from_push(self): return RemoveDeviceFromPush(self) def history(self): return History(self) def message_counts(self): return MessageCount(self) def fire(self): return Fire(self) def signal(self): return Signal(self) def set_uuid_metadata(self): return SetUuid(self) def get_uuid_metadata(self): return GetUuid(self) def remove_uuid_metadata(self): return RemoveUuid(self) def get_all_uuid_metadata(self): return GetAllUuid(self) def set_channel_metadata(self): return SetChannel(self) def get_channel_metadata(self): return GetChannel(self) def remove_channel_metadata(self): return RemoveChannel(self) def get_all_channel_metadata(self): return GetAllChannels(self) def set_channel_members(self): return SetChannelMembers(self) def get_channel_members(self): return GetChannelMembers(self) def remove_channel_members(self): return RemoveChannelMembers(self) def manage_channel_members(self): return ManageChannelMembers(self) def set_memberships(self): return SetMemberships(self) def get_memberships(self): return GetMemberships(self) def manage_memberships(self): return ManageMemberships(self) def fetch_messages(self): return FetchMessages(self) def add_message_action(self): return AddMessageAction(self) def get_message_actions(self): return GetMessageActions(self) def remove_message_action(self): return RemoveMessageAction(self) def time(self): return Time(self) def delete_messages(self): return HistoryDelete(self) def parse_token(self, token): return self._token_manager.parse_token(token) def set_token(self, token): self._token_manager.set_token(token) def _get_token(self): return self._token_manager.get_token() def send_file(self): if not self.sdk_platform(): return SendFileNative(self) elif "Asyncio" in self.sdk_platform(): from .endpoints.file_operations.send_file_asyncio import AsyncioSendFile return AsyncioSendFile(self) else: raise NotImplementedError def download_file(self): if not self.sdk_platform(): return DownloadFileNative(self) elif "Asyncio" in self.sdk_platform(): from .endpoints.file_operations.download_file_asyncio import ( DownloadFileAsyncio, ) return DownloadFileAsyncio(self) else: raise NotImplementedError def list_files(self): return ListFiles(self) def get_file_url(self): return GetFileDownloadUrl(self) def delete_file(self): return DeleteFile(self) def _fetch_file_upload_s3_data(self): return FetchFileUploadS3Data(self) def publish_file_message(self): return PublishFileMessage(self) def decrypt(self, cipher_key, file): return self.config.file_crypto.decrypt(cipher_key, file) def encrypt(self, cipher_key, file): return self.config.file_crypto.encrypt(cipher_key, file) @staticmethod def timestamp(): return int(time.time()) def _validate_subscribe_manager_enabled(self): if self._subscription_manager is None: raise Exception("Subscription manager is not enabled for this instance") """ Entities code -- all of methods bellow should be decorated with pubnub.features.feature_flag """ @feature_flag("PN_ENABLE_ENTITIES") def create_space( self, space_id, name=None, description=None, custom=None, space_type=None, space_status=None, sync=None, ): space = CreateSpace(self).space_id(space_id) if name is not None: space.set_name(name) if description is not None: space.description(description) if custom is not None: space.custom(custom) if space_status is not None: space.space_status(space_status) if space_type is not None: space.space_type(space_type) if sync: return space.sync() return space @feature_flag("PN_ENABLE_ENTITIES") def update_space( self, space_id, name=None, description=None, custom=None, space_type=None, space_status=None, sync=None, ): space = UpdateSpace(self).space_id(space_id) if name is not None: space.set_name(name) if description is not None: space.description(description) if custom is not None: space.custom(custom) if space_status is not None: space.space_status(space_status) if space_type is not None: space.space_type(space_type) if sync: return space.sync() return space @feature_flag("PN_ENABLE_ENTITIES") def remove_space(self, space_id, sync=None): remove_space = RemoveSpace(self).space_id(space_id) if sync: return remove_space.sync() return remove_space @feature_flag("PN_ENABLE_ENTITIES") def fetch_space(self, space_id, include_custom=None, sync=None): space = FetchSpace(self).space_id(space_id) if include_custom is not None: space.include_custom(include_custom) if sync: return space.sync() return space @feature_flag("PN_ENABLE_ENTITIES") def fetch_spaces( self, limit=None, page=None, filter=None, sort=None, include_total_count=None, include_custom=None, sync=None, ): spaces = FetchSpaces(self) if limit is not None: spaces.limit(limit) if page is not None: spaces.page(page) if filter is not None: spaces.filter(filter) if sort is not None: spaces.sort(sort) if include_total_count is not None: spaces.include_total_count(include_total_count) if include_custom is not None: spaces.include_custom(include_custom) if sync: return spaces.sync() return spaces @feature_flag("PN_ENABLE_ENTITIES") def create_user( self, user_id, name=None, email=None, custom=None, user_type=None, user_status=None, sync=None, ): user = CreateUser(self).user_id(user_id) if name is not None: user.set_name(name) if email is not None: user.email(email) if custom is not None: user.custom(custom) if user_status is not None: user.user_status(user_status) if user_type is not None: user.user_type(user_type) if sync: return user.sync() return user @feature_flag("PN_ENABLE_ENTITIES") def update_user( self, user_id, name=None, email=None, custom=None, user_type=None, user_status=None, sync=None, ): user = UpdateUser(self).user_id(user_id) if name is not None: user.set_name(name) if email is not None: user.email(email) if custom is not None: user.custom(custom) if user_status is not None: user.user_status(user_status) if user_type is not None: user.user_type(user_type) if sync: return user.sync() return user @feature_flag("PN_ENABLE_ENTITIES") def remove_user(self, user_id, sync=None): user = RemoveUser(self).user_id(user_id) if sync: return user.sync() return user @feature_flag("PN_ENABLE_ENTITIES") def fetch_user(self, user_id, include_custom=None, sync=None): user = FetchUser(self).user_id(user_id) if include_custom is not None: user.include_custom(include_custom) if sync: return user.sync() return user @feature_flag("PN_ENABLE_ENTITIES") def fetch_users( self, limit=None, page=None, filter=None, sort=None, include_total_count=None, include_custom=None, sync=None, ): users = FetchUsers(self) if limit is not None: users.limit(limit) if page is not None: users.page(page) if filter is not None: users.filter(filter) if sort is not None: users.sort(sort) if include_total_count is not None: users.include_total_count(include_total_count) if include_custom is not None: users.include_custom(include_custom) if sync: return users.sync() return users @feature_flag("PN_ENABLE_ENTITIES") def add_memberships( self, user_id: str = None, users: list = None, space_id: str = None, spaces: list = None, sync=None, ): if user_id and space_id: raise (PubNubException(pn_error=PNERR_MISUSE_OF_USER_AND_SPACE)) if user_id and spaces: membership = AddUserSpaces(self).user_id(user_id).spaces(spaces) elif space_id and users: membership = AddSpaceMembers(self).space_id(space_id).users(users) else: raise (PubNubException(pn_error=PNERR_USER_SPACE_PAIRS_MISSING)) if sync: return membership.sync() return membership @feature_flag("PN_ENABLE_ENTITIES") def update_memberships( self, user_id: str = None, users: list = None, space_id: str = None, spaces: list = None, sync=None, ): if user_id and space_id: raise (PubNubException(pn_error=PNERR_MISUSE_OF_USER_AND_SPACE)) if user_id and spaces: membership = UpdateUserSpaces(self).user_id(user_id).spaces(spaces) elif space_id and users: membership = UpdateSpaceMembers(self).space_id(space_id).users(users) else: raise (PubNubException(pn_error=PNERR_USER_SPACE_PAIRS_MISSING)) if sync: return membership.sync() return membership def remove_memberships(self, **kwargs): if len(kwargs) == 0: return RemoveMemberships(self) if "user_id" in kwargs.keys() and "space_id" in kwargs.keys(): raise (PubNubException(pn_error=PNERR_MISUSE_OF_USER_AND_SPACE)) if kwargs["user_id"] and kwargs["spaces"]: membership = ( RemoveUserSpaces(self) .user_id(kwargs["user_id"]) .spaces(kwargs["spaces"]) ) elif kwargs["space_id"] and kwargs["users"]: membership = ( RemoveSpaceMembers(self) .space_id(kwargs["space_id"]) .users(kwargs["users"]) ) else: raise (PubNubException(pn_error=PNERR_USER_SPACE_PAIRS_MISSING)) if kwargs["sync"]: return membership.sync() return membership @feature_flag("PN_ENABLE_ENTITIES") def fetch_memberships( self, user_id: str = None, space_id: str = None, limit=None, page=None, filter=None, sort=None, include_total_count=None, include_custom=None, sync=None, ): if user_id and space_id: raise (PubNubException(pn_error=PNERR_MISUSE_OF_USER_AND_SPACE)) if user_id: memberships = FetchUserMemberships(self).user_id(user_id) elif space_id: memberships = FetchSpaceMemberships(self).space_id(space_id) else: raise (PubNubException(pn_error=PNERR_USER_SPACE_PAIRS_MISSING)) if limit: memberships.limit(limit) if page: memberships.page(page) if filter: memberships.filter(filter) if sort: memberships.sort(sort) if include_total_count: memberships.include_total_count(include_total_count) if include_custom: memberships.include_custom(include_custom) if sync: return memberships.sync() return memberships bdraco-freenub-69809e8/pubnub/request_handlers/000077500000000000000000000000001464523203000215305ustar00rootroot00000000000000bdraco-freenub-69809e8/pubnub/request_handlers/__init__.py000066400000000000000000000000001464523203000236270ustar00rootroot00000000000000bdraco-freenub-69809e8/pubnub/request_handlers/base.py000066400000000000000000000006231464523203000230150ustar00rootroot00000000000000from abc import ABCMeta, abstractmethod class BaseRequestHandler: __metaclass__ = ABCMeta @abstractmethod def sync_request(self, platform_options, endpoint_call_options): pass @abstractmethod def async_request( self, endpoint_name, platform_options, endpoint_call_options, callback, cancellation_event, ): pass bdraco-freenub-69809e8/pubnub/request_handlers/requests_handler.py000066400000000000000000000276331464523203000254650ustar00rootroot00000000000000import json # noqa # pylint: disable=W0611 import logging import threading import urllib import requests from requests import Session from requests.adapters import HTTPAdapter from pubnub import utils from pubnub.enums import PNStatusCategory from pubnub.errors import ( PNERR_CLIENT_ERROR, PNERR_CLIENT_TIMEOUT, PNERR_CONNECTION_ERROR, PNERR_HTTP_ERROR, PNERR_SERVER_ERROR, PNERR_TOO_MANY_REDIRECTS_ERROR, PNERR_UNKNOWN_ERROR, ) from pubnub.exceptions import PubNubException from pubnub.request_handlers.base import BaseRequestHandler from pubnub.structures import Envelope, PlatformOptions, RequestOptions, ResponseInfo try: from json.decoder import JSONDecodeError except ImportError: JSONDecodeError = ValueError logger = logging.getLogger("pubnub") class RequestsRequestHandler(BaseRequestHandler): """PubNub Python SDK Native requests handler based on `requests` HTTP library.""" ENDPOINT_THREAD_COUNTER = 0 def __init__(self, pubnub): self.session = Session() self.session.mount( "http://%s" % pubnub.config.origin, HTTPAdapter(max_retries=1, pool_maxsize=500), ) self.session.mount( "https://%s" % pubnub.config.origin, HTTPAdapter(max_retries=1, pool_maxsize=500), ) self.session.mount( "http://%s/v2/subscribe" % pubnub.config.origin, HTTPAdapter(pool_maxsize=500), ) self.session.mount( "https://%s/v2/subscribe" % pubnub.config.origin, HTTPAdapter(pool_maxsize=500), ) self.pubnub = pubnub def sync_request(self, platform_options, endpoint_call_options): return self._build_envelope(platform_options, endpoint_call_options) def async_request( self, endpoint_name, platform_options, endpoint_call_options, callback, cancellation_event, ): call = Call() if cancellation_event is None: cancellation_event = threading.Event() def callback_to_invoke_in_separate_thread(): try: envelope = self._build_envelope(platform_options, endpoint_call_options) if cancellation_event is not None and cancellation_event.is_set(): # Since there are no way to affect on ongoing request it's response will # be just ignored on cancel call return callback(envelope) except PubNubException as e: logger.error("Async request PubNubException. %s" % str(e)) callback( Envelope( result=None, status=endpoint_call_options.create_status( category=PNStatusCategory.PNBadRequestCategory, response=None, response_info=None, exception=e, ), ) ) except Exception as e: logger.error("Async request Exception. %s" % str(e)) callback( Envelope( result=None, status=endpoint_call_options.create_status( category=PNStatusCategory.PNInternalExceptionCategory, response=None, response_info=None, exception=e, ), ) ) finally: call.executed_cb() self.execute_callback_in_separate_thread( callback_to_invoke_in_separate_thread, endpoint_name, call, cancellation_event, ) def execute_callback_in_separate_thread( self, callback_to_invoke_in_another_thread, operation_name, call_obj, cancellation_event, ): client = AsyncHTTPClient(callback_to_invoke_in_another_thread) thread = threading.Thread( target=client.run, name="Thread-%s-%d" % (operation_name, ++RequestsRequestHandler.ENDPOINT_THREAD_COUNTER), ) thread.daemon = self.pubnub.config.daemon thread.start() call_obj.thread = thread call_obj.cancellation_event = cancellation_event return call_obj def async_file_based_operation( self, func, callback, operation_name, cancellation_event=None ): call = Call() if cancellation_event is None: cancellation_event = threading.Event() def callback_to_invoke_in_separate_thread(): try: envelope = func() callback(envelope.result, envelope.status) except Exception as e: logger.error("Async file upload request Exception. %s" % str(e)) callback(Envelope(result=None, status=e)) finally: call.executed_cb() self.execute_callback_in_separate_thread( callback_to_invoke_in_separate_thread, operation_name, call, cancellation_event, ) return call def _build_envelope(self, p_options, e_options): """A wrapper for _invoke_url to separate request logic""" status_category = PNStatusCategory.PNUnknownCategory response_info = None url_base_path = self.pubnub.base_origin if e_options.use_base_path else None try: res = self._invoke_request(p_options, e_options, url_base_path) except PubNubException as e: if e._pn_error is PNERR_CONNECTION_ERROR: status_category = PNStatusCategory.PNUnexpectedDisconnectCategory elif e._pn_error is PNERR_CLIENT_TIMEOUT: status_category = PNStatusCategory.PNTimeoutCategory return Envelope( result=None, status=e_options.create_status( category=status_category, response=None, response_info=response_info, exception=e, ), ) if res is not None: url = urllib.parse.urlparse(res.url) query = urllib.parse.parse_qs(url.query) uuid = None auth_key = None if "uuid" in query and len(query["uuid"]) > 0: uuid = query["uuid"][0] if "auth_key" in query and len(query["auth_key"]) > 0: auth_key = query["auth_key"][0] response_info = ResponseInfo( status_code=res.status_code, tls_enabled="https" == url.scheme, origin=url.hostname, uuid=uuid, auth_key=auth_key, client_request=res.request, ) if not res.ok: if res.status_code == 403: status_category = PNStatusCategory.PNAccessDeniedCategory if res.status_code == 400: status_category = PNStatusCategory.PNBadRequestCategory if res.text is None: text = "N/A" else: text = res.text if res.status_code >= 500: err = PNERR_SERVER_ERROR else: err = PNERR_CLIENT_ERROR try: response = res.json() except JSONDecodeError: response = None return Envelope( result=None, status=e_options.create_status( category=status_category, response=response, response_info=response_info, exception=PubNubException( pn_error=err, errormsg=text, status_code=res.status_code ), ), ) else: if e_options.non_json_response: response = res else: response = res.json() return Envelope( result=e_options.create_response(response), status=e_options.create_status( category=PNStatusCategory.PNAcknowledgmentCategory, response=response, response_info=response_info, exception=None, ), ) def _invoke_request(self, p_options, e_options, base_origin): assert isinstance(p_options, PlatformOptions) assert isinstance(e_options, RequestOptions) if base_origin: url = p_options.pn_config.scheme() + "://" + base_origin + e_options.path else: url = e_options.path if e_options.request_headers: request_headers = {**p_options.headers, **e_options.request_headers} else: request_headers = p_options.headers args = { "method": e_options.method_string, "headers": request_headers, "url": url, "params": e_options.query_string, "timeout": (e_options.connect_timeout, e_options.request_timeout), "allow_redirects": e_options.allow_redirects, } if e_options.is_post() or e_options.is_patch(): args["data"] = e_options.data args["files"] = e_options.files logger.debug( "%s %s %s" % ( e_options.method_string, utils.build_url( p_options.pn_config.scheme(), base_origin, e_options.path, e_options.query_string, ), e_options.data, ) ) else: logger.debug( "%s %s" % ( e_options.method_string, utils.build_url( p_options.pn_config.scheme(), base_origin, e_options.path, e_options.query_string, ), ) ) try: res = self.session.request(**args) logger.debug("GOT %s" % res.text) except requests.exceptions.ConnectionError as e: raise PubNubException(pn_error=PNERR_CONNECTION_ERROR, errormsg=str(e)) except requests.exceptions.HTTPError as e: raise PubNubException(pn_error=PNERR_HTTP_ERROR, errormsg=str(e)) except requests.exceptions.Timeout as e: raise PubNubException(pn_error=PNERR_CLIENT_TIMEOUT, errormsg=str(e)) except requests.exceptions.TooManyRedirects as e: raise PubNubException( pn_error=PNERR_TOO_MANY_REDIRECTS_ERROR, errormsg=str(e) ) except Exception as e: raise PubNubException(pn_error=PNERR_UNKNOWN_ERROR, errormsg=str(e)) return res class AsyncHTTPClient: """A wrapper for threaded calls""" def __init__(self, callback_to_invoke): self._callback_to_invoke = callback_to_invoke def run(self): self._callback_to_invoke() class Call: """ A platform dependent representation of async PubNub method call """ def __init__(self): self.thread = None self.cancellation_event = None self.is_executed = False self.is_canceled = False def cancel(self): """ Set Event flag to stop thread on timeout. This will not stop thread immediately, it will stopped only after ongoing request will be finished :return: nothing """ if self.cancellation_event is not None: self.cancellation_event.set() self.is_canceled = True def join(self): if isinstance(self.thread, threading.Thread): self.thread.join() def executed_cb(self): self.is_executed = True bdraco-freenub-69809e8/pubnub/structures.py000066400000000000000000000062371464523203000207650ustar00rootroot00000000000000from .enums import HttpMethod class RequestOptions: def __init__( self, path, params_callback, method, request_timeout, connect_timeout, create_response, create_status, create_exception, operation_type, data=None, sort_arguments=False, allow_redirects=True, use_base_path=None, files=None, request_headers=None, non_json_response=False, ): assert len(path) > 0 assert callable(params_callback) assert isinstance(method, int) assert isinstance(request_timeout, int) assert isinstance(connect_timeout, int) if not ( method is HttpMethod.GET or method is HttpMethod.POST or method is HttpMethod.DELETE or method is HttpMethod.PATCH ): raise AssertionError() self.params = None self.path = path self.params_callback = params_callback self._method = method self.request_timeout = request_timeout self.connect_timeout = connect_timeout # TODO: rename 'data' => 'body' self.data = data self.files = files self.body = data self.sort_params = sort_arguments self.create_response = create_response self.create_status = create_status self.create_exception = create_exception self.operation_type = operation_type self.allow_redirects = allow_redirects self.use_base_path = use_base_path self.request_headers = request_headers self.non_json_response = non_json_response def merge_params_in(self, params_to_merge_in): self.params = self.params_callback(params_to_merge_in) @property def method_string(self): return HttpMethod.string(self._method) def is_post(self): return self._method is HttpMethod.POST def is_patch(self): return self._method is HttpMethod.PATCH def query_list(self): """All query keys and values should be already encoded inside a build_params() method""" s = [] for k, v in self.params.items(): s.append(str(k) + "=" + str(v)) if self.sort_params: return sorted(s) else: return s @property def query_string(self): return str("&".join(self.query_list())) def __str__(self): return f"path: {self.path}, qs: {self.query_string}" class PlatformOptions: def __init__(self, headers, pn_config): self.headers = headers self.pn_config = pn_config class ResponseInfo: def __init__( self, status_code, tls_enabled, origin, uuid, auth_key, client_request, client_response=None, ): self.status_code = status_code self.tls_enabled = tls_enabled self.origin = origin self.uuid = uuid self.auth_key = auth_key self.client_request = client_request self.client_response = client_response class Envelope: def __init__(self, result, status): self.result = result self.status = status bdraco-freenub-69809e8/pubnub/utils.py000066400000000000000000000203041464523203000176710ustar00rootroot00000000000000import datetime import hmac import json import threading import urllib import uuid as u from hashlib import sha256 from .enums import ( HttpMethod, PAMPermissions, PNOperationType, PNPushType, PNStatusCategory, ) from .errors import PNERR_JSON_NOT_SERIALIZABLE from .exceptions import PubNubException from .models.consumer.common import PNStatus def get_data_for_user(data): try: if "message" in data and "payload" in data: return {"message": data["message"], "payload": data["payload"]} else: return data except TypeError: return data def write_value_as_string(data): try: return json.dumps(data) except TypeError: raise PubNubException(pn_error=PNERR_JSON_NOT_SERIALIZABLE) def url_encode(data): return urllib.parse.quote(data, safe="~").replace("+", "%2B") def url_write(data): """Just wraps url_encode(write_value_as_string())""" return url_encode(write_value_as_string(data)) def uuid(): return str(u.uuid4()) def split_items(items_string): if len(items_string) == 0: return [] else: return items_string.split(",") def join_items(items_list): return ",".join(items_list) def join_items_and_encode(items_list): return ",".join(url_encode(x) for x in items_list) def join_channels(items_list): if len(items_list) == 0: return "," else: return join_items_and_encode(items_list) def extend_list(existing_items, new_items): if isinstance(new_items, str): existing_items.extend(split_items(new_items)) else: existing_items.extend(new_items) def build_url(scheme, origin, path, params={}): return urllib.parse.urlunsplit((scheme, origin, path, params, "")) def synchronized(func): func.__lock__ = threading.Lock() def synced_func(*args, **kws): with func.__lock__: return func(*args, **kws) return synced_func def is_subscribed_event(status): assert isinstance(status, PNStatus) return status.category == PNStatusCategory.PNConnectedCategory def is_unsubscribed_event(status): assert isinstance(status, PNStatus) return ( status.category == PNStatusCategory.PNAcknowledgmentCategory and status.operation == PNOperationType.PNUnsubscribeOperation ) def prepare_pam_arguments(unsorted_params): sorted_keys = sorted(unsorted_params) stringified_arguments = "" i = 0 for key in sorted_keys: if i != 0: stringified_arguments += "&" stringified_arguments += key + "=" + pam_encode(str(unsorted_params[key])) i += 1 return stringified_arguments def pam_encode(s_url): # !'()*~ encoded = url_encode(s_url) if encoded is not None: encoded = ( encoded.replace("*", "%2A") .replace("!", "%21") .replace("'", "%27") .replace("(", "%28") .replace(")", "%29") .replace("[", "%5B") .replace("]", "%5D") .replace("~", "%7E") ) return encoded def sign_sha256(secret, sign_input): from base64 import urlsafe_b64encode sign = urlsafe_b64encode( hmac.new(secret.encode("utf-8"), sign_input.encode("utf-8"), sha256).digest() ) return sign.decode("utf-8") def push_type_to_string(push_type): if push_type == PNPushType.APNS: return "apns" elif push_type == PNPushType.GCM: return "gcm" elif push_type == PNPushType.MPNS: return "mpns" else: return "" def strip_right(text, suffix): if not text.endswith(suffix): return text return text[: len(text) - len(suffix)] def datetime_now(): return datetime.datetime.now().strftime("%I:%M%p on %B %d, %Y") def sign_request(endpoint, pn, custom_params, method, body): custom_params["timestamp"] = str(pn.timestamp()) request_url = endpoint.get_path() encoded_query_string = prepare_pam_arguments(custom_params) is_v2_signature = not ( request_url.startswith("/publish") and method == HttpMethod.POST ) signed_input = "" if not is_v2_signature: signed_input += pn.config.subscribe_key + "\n" signed_input += pn.config.publish_key + "\n" signed_input += request_url + "\n" signed_input += encoded_query_string else: signed_input += HttpMethod.string(method).upper() + "\n" signed_input += pn.config.publish_key + "\n" signed_input += request_url + "\n" signed_input += encoded_query_string + "\n" if body is not None: signed_input += body signature = sign_sha256(pn.config.secret_key, signed_input) if is_v2_signature: signature = signature.rstrip("=") signature = "v2." + signature custom_params["signature"] = signature def parse_resources(resource_list, resource_set_name, resources, patterns): if resource_list: for pn_resource in resource_list: resource_object = {} if pn_resource.is_pattern_resource(): determined_object = patterns else: determined_object = resources if resource_set_name in determined_object: determined_object[resource_set_name][pn_resource.get_id()] = ( calculate_bitmask(pn_resource) ) else: resource_object[pn_resource.get_id()] = calculate_bitmask(pn_resource) determined_object[resource_set_name] = resource_object if resource_set_name not in resources: resources[resource_set_name] = {} if resource_set_name not in patterns: patterns[resource_set_name] = {} def calculate_bitmask(pn_resource): bit_sum = 0 if pn_resource.is_read(): bit_sum += PAMPermissions.READ.value if pn_resource.is_write(): bit_sum += PAMPermissions.WRITE.value if pn_resource.is_manage(): bit_sum += PAMPermissions.MANAGE.value if pn_resource.is_delete(): bit_sum += PAMPermissions.DELETE.value if pn_resource.is_create(): bit_sum += PAMPermissions.CREATE.value if pn_resource.is_get(): bit_sum += PAMPermissions.GET.value if pn_resource.is_update(): bit_sum += PAMPermissions.UPDATE.value if pn_resource.is_join(): bit_sum += PAMPermissions.JOIN.value return bit_sum def decode_utf8_dict(dic): if isinstance(dic, bytes): return dic.decode("utf-8") elif isinstance(dic, dict): new_dic = {} for key in dic: new_key = key if isinstance(key, bytes): new_key = key.decode("UTF-8") if new_key == "sig" and isinstance(dic[key], bytes): new_dic[new_key] = dic[key] else: new_dic[new_key] = decode_utf8_dict(dic[key]) return new_dic elif isinstance(dic, list): new_l = [] for e in dic: new_l.append(decode_utf8_dict(e)) return new_l else: return dic def has_permission(perms, perm): return (perms & perm) == perm def has_read_permission(perms): return has_permission(perms, PAMPermissions.READ.value) def has_write_permission(perms): return has_permission(perms, PAMPermissions.WRITE.value) def has_delete_permission(perms): return has_permission(perms, PAMPermissions.DELETE.value) def has_manage_permission(perms): return has_permission(perms, PAMPermissions.MANAGE.value) def has_get_permission(perms): return has_permission(perms, PAMPermissions.GET.value) def has_update_permission(perms): return has_permission(perms, PAMPermissions.UPDATE.value) def has_join_permission(perms): return has_permission(perms, PAMPermissions.JOIN.value) def parse_pam_permissions(resource): new_res = {} for res_name, perms in resource.items(): new_res[res_name] = { "read": has_read_permission(perms), "write": has_write_permission(perms), "manage": has_manage_permission(perms), "delete": has_delete_permission(perms), "get": has_get_permission(perms), "update": has_update_permission(perms), "join": has_join_permission(perms), } return new_res bdraco-freenub-69809e8/pubnub/workers.py000066400000000000000000000167731464523203000202440ustar00rootroot00000000000000import logging from abc import abstractmethod from .endpoints.file_operations.get_file_url import GetFileDownloadUrl from .enums import PNOperationType, PNStatusCategory from .models.consumer.common import PNStatus from .models.consumer.objects_v2.channel import PNChannelMetadataResult from .models.consumer.objects_v2.memberships import PNMembershipResult from .models.consumer.objects_v2.uuid import PNUUIDMetadataResult from .models.consumer.pn_error_data import PNErrorData from .models.consumer.pubsub import ( PNFileMessageResult, PNMessageActionResult, PNMessageResult, PNPresenceEventResult, PNSignalMessageResult, ) from .models.server.subscribe import PresenceEnvelope, SubscribeMessage from .utils import strip_right logger = logging.getLogger("pubnub") class SubscribeMessageWorker: TYPE_MESSAGE = 0 TYPE_SIGNAL = 1 TYPE_OBJECT = 2 TYPE_MESSAGE_ACTION = 3 TYPE_FILE_MESSAGE = 4 def __init__( self, pubnub_instance, listener_manager_instance, queue_instance, event ): # assert isinstance(pubnub_instnace, PubNubCore) # assert isinstance(listener_manager_instance, ListenerManager) # assert isinstance(queue_instance, utils.Queue) self._pubnub = pubnub_instance self._listener_manager = listener_manager_instance self._queue = queue_instance self._is_running = None self._event = event def run(self): self._take_message() @abstractmethod def _take_message(self): pass def _get_url_for_file_event_message(self, channel, extracted_message): return ( GetFileDownloadUrl(self._pubnub) .channel(channel) .file_name(extracted_message["file"]["name"]) .file_id(extracted_message["file"]["id"]) .get_complete_url() ) def _process_message(self, message_input): if self._pubnub.config.cipher_key is None: return message_input else: try: return self._pubnub.config.crypto.decrypt( self._pubnub.config.cipher_key, message_input ) except Exception as exception: logger.warning( 'could not decrypt message: "%s", due to error %s' % (message_input, str(exception)) ) pn_status = PNStatus() pn_status.category = PNStatusCategory.PNDecryptionErrorCategory pn_status.error_data = PNErrorData(str(exception), exception) pn_status.error = True pn_status.operation = PNOperationType.PNSubscribeOperation self._listener_manager.announce_status(pn_status) return message_input def _process_incoming_payload(self, message): assert isinstance(message, SubscribeMessage) channel = message.channel subscription_match = message.subscription_match publish_meta_data = message.publish_metadata if channel is not None and channel == subscription_match: subscription_match = None if "-pnpres" in message.channel: presence_payload = PresenceEnvelope.from_json_payload(message.payload) stripped_presence_channel = None stripped_presence_subscription = None if channel is not None: stripped_presence_channel = strip_right(channel, "-pnpres") if subscription_match is not None: stripped_presence_subscription = strip_right( subscription_match, "-pnpres" ) pn_presence_event_result = PNPresenceEventResult( event=presence_payload.action, channel=stripped_presence_channel, subscription=stripped_presence_subscription, timetoken=publish_meta_data.publish_timetoken, occupancy=presence_payload.occupancy, uuid=presence_payload.uuid, timestamp=presence_payload.timestamp, state=presence_payload.data, join=message.payload.get("join", None), leave=message.payload.get("leave", None), timeout=message.payload.get("timeout", None), ) self._listener_manager.announce_presence(pn_presence_event_result) elif message.type == SubscribeMessageWorker.TYPE_OBJECT: if message.payload["type"] == "channel": channel_result = PNChannelMetadataResult( event=message.payload["event"], data=message.payload["data"] ) self._listener_manager.announce_channel(channel_result) elif message.payload["type"] == "uuid": uuid_result = PNUUIDMetadataResult( event=message.payload["event"], data=message.payload["data"] ) self._listener_manager.announce_uuid(uuid_result) elif message.payload["type"] == "membership": membership_result = PNMembershipResult( event=message.payload["event"], data=message.payload["data"] ) self._listener_manager.announce_membership(membership_result) elif message.type == SubscribeMessageWorker.TYPE_FILE_MESSAGE: extracted_message = self._process_message(message.payload) download_url = self._get_url_for_file_event_message( channel, extracted_message ) pn_file_result = PNFileMessageResult( message=extracted_message.get("message"), channel=channel, subscription=subscription_match, timetoken=publish_meta_data.publish_timetoken, publisher=message.issuing_client_id, file_url=download_url, file_id=extracted_message["file"]["id"], file_name=extracted_message["file"]["name"], ) self._listener_manager.announce_file_message(pn_file_result) else: extracted_message = self._process_message(message.payload) publisher = message.issuing_client_id if extracted_message is None: logger.debug("unable to parse payload on #processIncomingMessages") if message.type == SubscribeMessageWorker.TYPE_SIGNAL: pn_signal_result = PNSignalMessageResult( message=extracted_message, channel=channel, subscription=subscription_match, timetoken=publish_meta_data.publish_timetoken, publisher=publisher, ) self._listener_manager.announce_signal(pn_signal_result) elif message.type == SubscribeMessageWorker.TYPE_MESSAGE_ACTION: message_action = extracted_message["data"] if "uuid" not in message_action: message_action["uuid"] = publisher message_action_result = PNMessageActionResult(message_action) self._listener_manager.announce_message_action(message_action_result) else: pn_message_result = PNMessageResult( message=extracted_message, channel=channel, subscription=subscription_match, timetoken=publish_meta_data.publish_timetoken, publisher=publisher, ) self._listener_manager.announce_message(pn_message_result) bdraco-freenub-69809e8/pyproject.toml000066400000000000000000000065641464523203000176140ustar00rootroot00000000000000[tool.poetry] name = "freenub" version = "0.1.0" description = "This is a fork of pubnub when it still had an MIT license" authors = ["J. Nick Koston "] license = "MIT" readme = "README.md" repository = "https://github.com/bdraco/freenub" classifiers = [ 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', 'Programming Language :: Python', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', 'Programming Language :: Python :: 3.12', 'Programming Language :: Python :: Implementation :: CPython', 'License :: OSI Approved :: MIT License', 'Operating System :: OS Independent', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Software Development :: Libraries :: Python Modules', ] packages = [ { include = "pubnub" }, ] [tool.poetry.urls] "Bug Tracker" = "https://github.com/bdraco/freenub/issues" "Changelog" = "https://github.com/bdraco/freenub/blob/main/CHANGELOG.md" [tool.poetry.dependencies] python = "^3.8" pycryptodomex = ">=3.3" requests = ">=2.4" cbor2 = "^5.6.4" aiohttp = "^3.9.5" [tool.poetry.group.dev.dependencies] pytest = "^8.0.0" pytest-cov = "^5.0.0" pyyaml = "^6.0.1" pytest-asyncio = "^0.23.7" behave = "^1.2.6" vcrpy = "^6.0.1" urllib3 = "<2" busypie = "^0.5.1" [tool.semantic_release] version_toml = ["pyproject.toml:tool.poetry.version"] version_variables = [ "pubnub/__init__.py:__version__", ] build_command = "pip install poetry && poetry build" [tool.semantic_release.changelog] exclude_commit_patterns = [ "chore*", "ci*", ] [tool.semantic_release.changelog.environment] keep_trailing_newline = true [tool.semantic_release.branches.main] match = "main" [tool.semantic_release.branches.noop] match = "(?!main$)" prerelease = true [tool.pytest.ini_options] addopts = """\ -v -Wdefault --cov=pubnub --cov-report=term --cov-report=xml """ [tool.coverage.run] branch = true [tool.coverage.report] exclude_lines = [ "pragma: no cover", "@overload", "if TYPE_CHECKING", "raise NotImplementedError", 'if __name__ == "__main__":', ] [tool.ruff] target-version = "py38" line-length = 88 [tool.ruff.lint] ignore = [ "D203", # 1 blank line required before class docstring "D212", # Multi-line docstring summary should start at the first line "D100", # Missing docstring in public module "D104", # Missing docstring in public package "D107", # Missing docstring in `__init__` "D401", # First line of docstring should be in imperative mood "C414", "C413", ] select = [ "I", # isort ] [tool.ruff.lint.per-file-ignores] "tests/**/*" = [ "D100", "D101", "D102", "D103", "D104", "S101", ] "setup.py" = ["D100"] "conftest.py" = ["D100"] [tool.ruff.lint.isort] known-first-party = ["pubnub", "tests"] [tool.mypy] check_untyped_defs = true disallow_any_generics = true disallow_incomplete_defs = true disallow_untyped_defs = true mypy_path = "src/" no_implicit_optional = true show_error_codes = true warn_unreachable = true warn_unused_ignores = true exclude = [ 'setup.py', ] [[tool.mypy.overrides]] module = "tests.*" allow_untyped_defs = true [build-system] requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" bdraco-freenub-69809e8/renovate.json000066400000000000000000000001011464523203000173730ustar00rootroot00000000000000{ "extends": ["github>browniebroke/renovate-configs:python"] } bdraco-freenub-69809e8/requirements-dev.txt000066400000000000000000000001631464523203000207250ustar00rootroot00000000000000pyyaml pytest-cov pycryptodomex flake8 pytest pytest-asyncio aiohttp requests cbor2 behave vcrpy urllib3<2 busypie bdraco-freenub-69809e8/scripts/000077500000000000000000000000001464523203000163545ustar00rootroot00000000000000bdraco-freenub-69809e8/scripts/install.sh000077500000000000000000000000711464523203000203570ustar00rootroot00000000000000#!/usr/bin/env bash pip install -r requirements-dev.txt bdraco-freenub-69809e8/scripts/run-tests.py000077500000000000000000000011161464523203000206740ustar00rootroot00000000000000#!/usr/bin/env python # Don't run tests from the root repo dir. # We want to ensure we're importing from the installed # binary package not from the CWD. import os from subprocess import check_call _dname = os.path.dirname REPO_ROOT = _dname(_dname(os.path.abspath(__file__))) os.chdir(os.path.join(REPO_ROOT)) tcmn = "py.test tests --cov=pubnub --ignore=tests/manual/" fcmn = "flake8 --exclude=scripts/,src/,.cache,.git,.idea,.tox,._trial_temp/,venv/ --ignore F811,E402" def run(command): return check_call(command, shell=True) run(tcmn) # moved to separate action # run(fcmn) bdraco-freenub-69809e8/setup.cfg000066400000000000000000000001251464523203000165040ustar00rootroot00000000000000[tool:pytest] norecursedirs = benchmarks [flake8] max-line-length = 120 ignore=E402 bdraco-freenub-69809e8/templates/000077500000000000000000000000001464523203000166635ustar00rootroot00000000000000bdraco-freenub-69809e8/templates/CHANGELOG.md.j2000066400000000000000000000012351464523203000210070ustar00rootroot00000000000000# Changelog {%- for version, release in context.history.released.items() %} ## {{ version.as_tag() }} ({{ release.tagged_date.strftime("%Y-%m-%d") }}) {%- for category, commits in release["elements"].items() %} {# Category title: Breaking, Fix, Documentation #} ### {{ category | capitalize }} {# List actual changes in the category #} {%- for commit in commits %} {% if commit is not none and commit.descriptions is defined %} - {{ commit.descriptions[0] | capitalize }} ([`{{ commit.short_hash }}`]({{ commit.hexsha | commit_hash_url }})) {% endif %} {%- endfor %}{# for commit #} {%- endfor %}{# for category, commits #} {%- endfor %}{# for version, release #} bdraco-freenub-69809e8/tests/000077500000000000000000000000001464523203000160275ustar00rootroot00000000000000bdraco-freenub-69809e8/tests/__init__.py000066400000000000000000000000001464523203000201260ustar00rootroot00000000000000bdraco-freenub-69809e8/tests/test_init.py000066400000000000000000000003271464523203000204050ustar00rootroot00000000000000from Cryptodome.Cipher import AES import pubnub from pubnub.pnconfiguration import PNConfiguration def test_x(): assert 1 + 1 == 2 assert PNConfiguration.ALLOWED_AES_MODES == [AES.MODE_CBC, AES.MODE_GCM]