pax_global_header 0000666 0000000 0000000 00000000064 15257723452 0014526 g ustar 00root root 0000000 0000000 52 comment=e55304ea17c160514ea3daad4a33ea7cfc9d61d4
freenub-0.1.2/ 0000775 0000000 0000000 00000000000 15257723452 0013154 5 ustar 00root root 0000000 0000000 freenub-0.1.2/.all-contributorsrc 0000664 0000000 0000000 00000000443 15257723452 0017006 0 ustar 00root root 0000000 0000000 {
"projectName": "freenub",
"projectOwner": "bdraco",
"repoType": "github",
"repoHost": "https://github.com",
"files": [
"README.md"
],
"imageSize": 80,
"commit": true,
"commitConvention": "angular",
"contributors": [],
"contributorsPerLine": 7,
"skipCi": true
}
freenub-0.1.2/.codespellrc 0000664 0000000 0000000 00000000057 15257723452 0015456 0 ustar 00root root 0000000 0000000 [codespell]
ignore-words-list = socio-economic
freenub-0.1.2/.copier-answers.yml 0000664 0000000 0000000 00000001057 15257723452 0016721 0 ustar 00root root 0000000 0000000 # 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
freenub-0.1.2/.editorconfig 0000664 0000000 0000000 00000000444 15257723452 0015633 0 ustar 00root root 0000000 0000000 # 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
freenub-0.1.2/.github/ 0000775 0000000 0000000 00000000000 15257723452 0014514 5 ustar 00root root 0000000 0000000 freenub-0.1.2/.github/CODEOWNERS 0000664 0000000 0000000 00000000217 15257723452 0016107 0 ustar 00root root 0000000 0000000 * @seba-aln @kleewho @Xavrax @jguz-pubnub @parfeon
.github/* @parfeon @seba-aln @kleewho @Xavrax @jguz-pubnub
README.md @techwritermat
freenub-0.1.2/.github/CODE_OF_CONDUCT.md 0000664 0000000 0000000 00000012056 15257723452 0017317 0 ustar 00root root 0000000 0000000 # 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.
freenub-0.1.2/.github/FUNDING.yml 0000664 0000000 0000000 00000000023 15257723452 0016324 0 ustar 00root root 0000000 0000000 github: ["bdraco"]
freenub-0.1.2/.github/ISSUE_TEMPLATE/ 0000775 0000000 0000000 00000000000 15257723452 0016677 5 ustar 00root root 0000000 0000000 freenub-0.1.2/.github/ISSUE_TEMPLATE/1-bug-report.yml 0000664 0000000 0000000 00000003525 15257723452 0021653 0 ustar 00root root 0000000 0000000 name: 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!
freenub-0.1.2/.github/ISSUE_TEMPLATE/2-feature-request.yml 0000664 0000000 0000000 00000003606 15257723452 0022707 0 ustar 00root root 0000000 0000000 name: 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!
freenub-0.1.2/.github/ISSUE_TEMPLATE/config.yml 0000664 0000000 0000000 00000000600 15257723452 0020663 0 ustar 00root root 0000000 0000000 # 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.
freenub-0.1.2/.github/PULL_REQUEST_TEMPLATE.md 0000664 0000000 0000000 00000003454 15257723452 0020323 0 ustar 00root root 0000000 0000000
### 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.
freenub-0.1.2/.github/dependabot.yml 0000664 0000000 0000000 00000001344 15257723452 0017346 0 ustar 00root root 0000000 0000000 # To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "monthly"
commit-message:
prefix: "chore(ci): "
groups:
github-actions:
patterns:
- "*"
- package-ecosystem: "pip" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "weekly"
freenub-0.1.2/.github/labels.toml 0000664 0000000 0000000 00000003675 15257723452 0016666 0 ustar 00root root 0000000 0000000 [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."
freenub-0.1.2/.github/workflows/ 0000775 0000000 0000000 00000000000 15257723452 0016551 5 ustar 00root root 0000000 0000000 freenub-0.1.2/.github/workflows/ci.yml 0000664 0000000 0000000 00000007123 15257723452 0017672 0 ustar 00root root 0000000 0000000 name: 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@v7
- uses: actions/setup-python@v7
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@v7
with:
fetch-depth: 0
- uses: wagoid/commitlint-github-action@v6.2.1
test:
strategy:
fail-fast: false
matrix:
python-version:
- "3.11"
- "3.12"
- "3.13"
- "3.14"
- "3.15"
os:
- ubuntu-latest
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v7
- name: Install poetry
run: pipx install poetry
- name: Set up Python
uses: actions/setup-python@v7
id: setup-python
with:
python-version: ${{ matrix.python-version }}
cache: poetry
allow-prereleases: true
- 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@v7
with:
token: ${{ secrets.CODECOV_TOKEN }}
# PRs and non-main pushes: validate the PSR config with a read-only dry run.
# No environment and no elevated permissions, so PR runs can never enter the
# release environment (avoiding approval-gate hangs) or hold OIDC/write tokens.
release_dry_run:
needs:
- test
- lint
- commitlint
if: github.ref_name != 'main'
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0
ref: ${{ github.ref }}
- name: Create local branch name
env:
BRANCH: ${{ github.head_ref || github.ref_name }}
run: git switch -C "$BRANCH"
- name: Test release
uses: python-semantic-release/python-semantic-release@v10.7.0
with:
no_operation_mode: true
# main only: real release + publish. The environment and OIDC/write
# permissions live here so they are never granted on PR runs.
release:
needs:
- test
- lint
- commitlint
if: github.ref_name == 'main'
runs-on: ubuntu-latest
environment: release
concurrency:
group: release-${{ github.ref }}
cancel-in-progress: false
permissions:
id-token: write
contents: write
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0
ref: ${{ github.ref }}
- name: Create local branch name
env:
BRANCH: ${{ github.ref_name }}
run: git switch -C "$BRANCH"
- name: Release
uses: python-semantic-release/python-semantic-release@v10.7.0
id: release
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 }}
freenub-0.1.2/.github/workflows/issue-manager.yml 0000664 0000000 0000000 00000001340 15257723452 0022032 0 ustar 00root root 0000000 0000000 name: 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.8.1
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."
}
}
freenub-0.1.2/.github/workflows/poetry-upgrade.yml 0000664 0000000 0000000 00000000340 15257723452 0022240 0 ustar 00root root 0000000 0000000 name: Upgrader
on:
workflow_dispatch:
schedule:
- cron: "37 10 7 * *"
jobs:
upgrade:
uses: browniebroke/github-actions/.github/workflows/poetry-upgrade.yml@v2
secrets:
gh_pat: ${{ secrets.GH_PAT }}
freenub-0.1.2/.gitignore 0000664 0000000 0000000 00000004553 15257723452 0015153 0 ustar 00root root 0000000 0000000 # 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
freenub-0.1.2/.gitpod.yml 0000664 0000000 0000000 00000000306 15257723452 0015242 0 ustar 00root root 0000000 0000000 tasks:
- command: |
pip install poetry
PIP_USER=false poetry install
- command: |
pip install pre-commit
pre-commit install
PIP_USER=false pre-commit install-hooks
freenub-0.1.2/.idea/ 0000775 0000000 0000000 00000000000 15257723452 0014134 5 ustar 00root root 0000000 0000000 freenub-0.1.2/.idea/freenub.iml 0000664 0000000 0000000 00000000515 15257723452 0016266 0 ustar 00root root 0000000 0000000
freenub-0.1.2/.idea/watcherTasks.xml 0000664 0000000 0000000 00000005253 15257723452 0017326 0 ustar 00root root 0000000 0000000
freenub-0.1.2/.idea/workspace.xml 0000664 0000000 0000000 00000002736 15257723452 0016664 0 ustar 00root root 0000000 0000000
freenub-0.1.2/.pre-commit-config.yaml 0000664 0000000 0000000 00000002376 15257723452 0017445 0 ustar 00root root 0000000 0000000 # 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: [pre-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: v4.19.0
hooks:
- id: commitizen
stages: [commit-msg]
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.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: 2.5.1
hooks:
- id: poetry-check
- repo: https://github.com/pre-commit/mirrors-prettier
rev: v4.0.0-alpha.8
hooks:
- id: prettier
args: ["--tab-width", "2"]
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.16.9
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
- id: ruff-format
freenub-0.1.2/.pubnub.yml 0000664 0000000 0000000 00000054200 15257723452 0015251 0 ustar 00root root 0000000 0000000 name: 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
freenub-0.1.2/CHANGELOG.md 0000664 0000000 0000000 00000011164 15257723452 0014770 0 ustar 00root root 0000000 0000000 # Changelog
## v0.1.2 (2026-10-02)
### Bug fixes
- Re-announce pnconnectedcategory after reconnect() ([`cea7403`](https://github.com/bdraco/freenub/commit/cea7403430df877c00bdb00cad9caafa180ad3b1))
## v0.1.1 (2026-10-02)
### Bug fixes
- Honour enable_presence_heartbeat in asynciosubscriptionmanager ([`6e20961`](https://github.com/bdraco/freenub/commit/6e209615d70275c0f4163cdc52812ceeb8b02d02))
## v0.1.0 (2024-07-15)
### Bug fixes
- 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
### Features
- 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
### Build system
- Add custom gha large runner ([`f6b7e1d`](https://github.com/bdraco/freenub/commit/f6b7e1ddb9ef1e5ee31a7af473c6a0e35ef29938))
- Flake linting runs once before tests and not after all python versions ([`484df21`](https://github.com/bdraco/freenub/commit/484df21c6717e3a95475a139335cd40a7db4c711))
- Fix syntax error in job pre-condition ([`9cffc1f`](https://github.com/bdraco/freenub/commit/9cffc1ff2ab7df9e07f9edd9178af3ef867632d3))
- Improve tests and validation workflows ([`9cffc1f`](https://github.com/bdraco/freenub/commit/9cffc1ff2ab7df9e07f9edd9178af3ef867632d3))
- Revert changelogs of not released version ([`0f31919`](https://github.com/bdraco/freenub/commit/0f31919c51fba3faa9126e45a2580b0a2874b9f5))
- Integrate with release notifications ([`6beedc6`](https://github.com/bdraco/freenub/commit/6beedc60da504e71312f11563f303046c31e468d))
- Switch deployment to github actions ([`1ea3b5c`](https://github.com/bdraco/freenub/commit/1ea3b5c83cb7cfcd0eb82dbf73c668e1649c813f))
### Testing
- Migrate tests to github actions ([`342c981`](https://github.com/bdraco/freenub/commit/342c981bfcc99f4163fe6867abee1081ea92890b))
- Migrate tests to github actions ([`342c981`](https://github.com/bdraco/freenub/commit/342c981bfcc99f4163fe6867abee1081ea92890b))
### Bug fixes
- Allow empty 'message' field in filemessageresult ([`554e72e`](https://github.com/bdraco/freenub/commit/554e72ef29a1d93fe099cbcd322c7ce1104f85c1))
- Remove unwanted output while calling `fetch_messages` ([`84479c5`](https://github.com/bdraco/freenub/commit/84479c5d721891b0a53d04c1f14175f1a71d2add))
- Remove unwanted output while calling `fetch_messages` ([`84479c5`](https://github.com/bdraco/freenub/commit/84479c5d721891b0a53d04c1f14175f1a71d2add))
- Adapt acceptance testing code to the updated version of the testing infrastructure. ([`13e4fce`](https://github.com/bdraco/freenub/commit/13e4fce4a9f0fc8d7d3d27f595d0a19b57cd76a6))
- Mock server http interface has been changed, hence ([`13e4fce`](https://github.com/bdraco/freenub/commit/13e4fce4a9f0fc8d7d3d27f595d0a19b57cd76a6))
- Run tests for pr as well ([`13e4fce`](https://github.com/bdraco/freenub/commit/13e4fce4a9f0fc8d7d3d27f595d0a19b57cd76a6))
### Documentation
- Fix badge link ([`930d7b6`](https://github.com/bdraco/freenub/commit/930d7b6de77d3269d08c7e584e767c2f13ca004a))
- Fix changelogs ([`f6bd5ea`](https://github.com/bdraco/freenub/commit/f6bd5eae9ef29e731ce3ffc509ae28a3053f3b11))
- Change release version ([`970c28d`](https://github.com/bdraco/freenub/commit/970c28db5df0cf19116bb2fcefef625a42f7f134))
freenub-0.1.2/CONTRIBUTING.md 0000664 0000000 0000000 00000007377 15257723452 0015423 0 ustar 00root root 0000000 0000000 # 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
freenub-0.1.2/DEVELOPER.md 0000664 0000000 0000000 00000006000 15257723452 0015017 0 ustar 00root root 0000000 0000000 # 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.
freenub-0.1.2/LICENSE 0000664 0000000 0000000 00000002503 15257723452 0014161 0 ustar 00root root 0000000 0000000 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
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
freenub-0.1.2/README.md 0000664 0000000 0000000 00000004772 15257723452 0014445 0 ustar 00root root 0000000 0000000 # freenub
---
**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.
freenub-0.1.2/bandit.yml 0000664 0000000 0000000 00000000020 15257723452 0015130 0 ustar 00root root 0000000 0000000 skips: ["B101"]
freenub-0.1.2/commitlint.config.mjs 0000664 0000000 0000000 00000000362 15257723452 0017313 0 ustar 00root root 0000000 0000000 export 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],
},
};
freenub-0.1.2/examples/ 0000775 0000000 0000000 00000000000 15257723452 0014772 5 ustar 00root root 0000000 0000000 freenub-0.1.2/examples/__init__.py 0000664 0000000 0000000 00000000305 15257723452 0017101 0 ustar 00root root 0000000 0000000 from pubnub.pnconfiguration import PNConfiguration
pnconf = PNConfiguration()
pnconf.subscribe_key = "demo"
pnconf.publish_key = "demo"
pnconf.enable_subscribe = False
pnconf.user_id = "user_id"
freenub-0.1.2/examples/crypto.py 0000664 0000000 0000000 00000003435 15257723452 0016671 0 ustar 00root root 0000000 0000000 from 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")
freenub-0.1.2/examples/entities.py 0000664 0000000 0000000 00000010305 15257723452 0017167 0 ustar 00root root 0000000 0000000 import 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__}")
freenub-0.1.2/examples/logger.py 0000664 0000000 0000000 00000000554 15257723452 0016627 0 ustar 00root root 0000000 0000000 import 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()
freenub-0.1.2/examples/native_threads/ 0000775 0000000 0000000 00000000000 15257723452 0017772 5 ustar 00root root 0000000 0000000 freenub-0.1.2/examples/native_threads/check.py 0000664 0000000 0000000 00000004464 15257723452 0021431 0 ustar 00root root 0000000 0000000 from 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()
freenub-0.1.2/examples/native_threads/http/ 0000775 0000000 0000000 00000000000 15257723452 0020751 5 ustar 00root root 0000000 0000000 freenub-0.1.2/examples/native_threads/http/app.py 0000664 0000000 0000000 00000005325 15257723452 0022110 0 ustar 00root root 0000000 0000000 import 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)
freenub-0.1.2/examples/native_threads/publish.py 0000664 0000000 0000000 00000001144 15257723452 0022012 0 ustar 00root root 0000000 0000000 # 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()
freenub-0.1.2/examples/pubnub_asyncio/ 0000775 0000000 0000000 00000000000 15257723452 0020012 5 ustar 00root root 0000000 0000000 freenub-0.1.2/examples/pubnub_asyncio/fastapi/ 0000775 0000000 0000000 00000000000 15257723452 0021441 5 ustar 00root root 0000000 0000000 freenub-0.1.2/examples/pubnub_asyncio/fastapi/main.py 0000664 0000000 0000000 00000002052 15257723452 0022736 0 ustar 00root root 0000000 0000000 import 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()
freenub-0.1.2/examples/pubnub_asyncio/fastapi/requirements.txt 0000664 0000000 0000000 00000000010 15257723452 0024714 0 ustar 00root root 0000000 0000000 fastapi
freenub-0.1.2/examples/pubnub_asyncio/http/ 0000775 0000000 0000000 00000000000 15257723452 0020771 5 ustar 00root root 0000000 0000000 freenub-0.1.2/examples/pubnub_asyncio/http/app.py 0000664 0000000 0000000 00000011201 15257723452 0022116 0 ustar 00root root 0000000 0000000 import 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()
freenub-0.1.2/examples/pubnub_asyncio/http/requirements.txt 0000664 0000000 0000000 00000000025 15257723452 0024252 0 ustar 00root root 0000000 0000000 aiohttp
aiohttp_cors
freenub-0.1.2/poetry.lock 0000664 0000000 0000000 00000362110 15257723452 0015353 0 ustar 00root root 0000000 0000000 # This file is automatically @generated by Poetry 2.5.1 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"
groups = ["main"]
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"
attrs = ">=17.3.0"
frozenlist = ">=1.1.1"
multidict = ">=4.5,<7.0"
yarl = ">=1.0,<2.0"
[package.extras]
speedups = ["Brotli ; platform_python_implementation == \"CPython\"", "aiodns ; sys_platform == \"linux\" or sys_platform == \"darwin\"", "brotlicffi ; platform_python_implementation != \"CPython\""]
[[package]]
name = "aiosignal"
version = "1.3.1"
description = "aiosignal: a list of registered asynchronous callbacks"
optional = false
python-versions = ">=3.7"
groups = ["main"]
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 = "attrs"
version = "23.2.0"
description = "Classes Without Boilerplate"
optional = false
python-versions = ">=3.7"
groups = ["main"]
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) ; platform_python_implementation == \"CPython\" and python_version >= \"3.8\"", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.8\""]
tests-no-zope = ["attrs[tests-mypy]", "cloudpickle ; platform_python_implementation == \"CPython\"", "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.*"
groups = ["dev"]
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"
groups = ["dev"]
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"
groups = ["main"]
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 ; python_version < \"3.12\""]
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"
groups = ["main"]
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"
groups = ["main"]
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"
groups = ["dev"]
markers = "sys_platform == \"win32\""
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"
groups = ["dev"]
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.extras]
toml = ["tomli ; python_full_version <= \"3.11.0a6\""]
[[package]]
name = "frozenlist"
version = "1.4.1"
description = "A list-like structure which implements collections.abc.MutableSequence"
optional = false
python-versions = ">=3.8"
groups = ["main"]
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"
groups = ["main", "dev"]
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"
groups = ["dev"]
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"
groups = ["main", "dev"]
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"
groups = ["dev"]
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 = "*"
groups = ["dev"]
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.*"
groups = ["dev"]
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) ; python_version < \"3.0\"", "pytest (>=5.0) ; python_version >= \"3.0\"", "pytest-cov", "pytest-html (>=1.19.0)", "ruff ; python_version >= \"3.7\"", "tox (>=2.8,<4.0)", "twine (>=1.13.0)", "virtualenv (<20.22.0) ; python_version <= \"3.6\"", "virtualenv (>=20.0.0) ; python_version > \"3.6\""]
docs = ["Sphinx (>=1.6)", "sphinx-bootstrap-theme (>=0.6.0)"]
testing = ["pytest (<5.0) ; python_version < \"3.0\"", "pytest (>=5.0) ; python_version >= \"3.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"
groups = ["dev"]
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.*"
groups = ["main"]
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 = "pygments"
version = "2.21.0"
description = "Pygments is a syntax highlighting package written in Python."
optional = false
python-versions = ">=3.9"
groups = ["dev"]
files = [
{file = "pygments-2.21.0-py3-none-any.whl", hash = "sha256:2363c69b61c4a97c838da3b130dcd6468f4848992b21a82f2a63ec34377137d9"},
{file = "pygments-2.21.0.tar.gz", hash = "sha256:610ca751c9bc2492b38eb9a38a7fbc93edbbb2d7182edaf34e66ae493dee5c8c"},
]
[package.extras]
windows-terminal = ["colorama (>=0.4.6)"]
[[package]]
name = "pytest"
version = "8.4.2"
description = "pytest: simple powerful testing with Python"
optional = false
python-versions = ">=3.9"
groups = ["dev"]
files = [
{file = "pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79"},
{file = "pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01"},
]
[package.dependencies]
colorama = {version = ">=0.4", markers = "sys_platform == \"win32\""}
iniconfig = ">=1"
packaging = ">=20"
pluggy = ">=1.5,<2"
pygments = ">=2.7.2"
[package.extras]
dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"]
[[package]]
name = "pytest-asyncio"
version = "0.23.7"
description = "Pytest support for asyncio"
optional = false
python-versions = ">=3.8"
groups = ["dev"]
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"
groups = ["dev"]
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.3"
description = "YAML parser and emitter for Python"
optional = false
python-versions = ">=3.8"
groups = ["dev"]
files = [
{file = "PyYAML-6.0.3-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:c2514fceb77bc5e7a2f7adfaa1feb2fb311607c9cb518dbc378688ec73d8292f"},
{file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c57bb8c96f6d1808c030b1687b9b5fb476abaa47f0db9c0101f5e9f394e97f4"},
{file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:efd7b85f94a6f21e4932043973a7ba2613b059c4a000551892ac9f1d11f5baf3"},
{file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22ba7cfcad58ef3ecddc7ed1db3409af68d023b7f940da23c6c2a1890976eda6"},
{file = "PyYAML-6.0.3-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:6344df0d5755a2c9a276d4473ae6b90647e216ab4757f8426893b5dd2ac3f369"},
{file = "PyYAML-6.0.3-cp38-cp38-win32.whl", hash = "sha256:3ff07ec89bae51176c0549bc4c63aa6202991da2d9a6129d7aef7f1407d3f295"},
{file = "PyYAML-6.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:5cf4e27da7e3fbed4d6c3d8e797387aaad68102272f8f9752883bc32d61cb87b"},
{file = "pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b"},
{file = "pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956"},
{file = "pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8"},
{file = "pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198"},
{file = "pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b"},
{file = "pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0"},
{file = "pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69"},
{file = "pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e"},
{file = "pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c"},
{file = "pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e"},
{file = "pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824"},
{file = "pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c"},
{file = "pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00"},
{file = "pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d"},
{file = "pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a"},
{file = "pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4"},
{file = "pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b"},
{file = "pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf"},
{file = "pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196"},
{file = "pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0"},
{file = "pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28"},
{file = "pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c"},
{file = "pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc"},
{file = "pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e"},
{file = "pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea"},
{file = "pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5"},
{file = "pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b"},
{file = "pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd"},
{file = "pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8"},
{file = "pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1"},
{file = "pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c"},
{file = "pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5"},
{file = "pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6"},
{file = "pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6"},
{file = "pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be"},
{file = "pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26"},
{file = "pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c"},
{file = "pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb"},
{file = "pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac"},
{file = "pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310"},
{file = "pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7"},
{file = "pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788"},
{file = "pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5"},
{file = "pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764"},
{file = "pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35"},
{file = "pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac"},
{file = "pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3"},
{file = "pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3"},
{file = "pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba"},
{file = "pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c"},
{file = "pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702"},
{file = "pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c"},
{file = "pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065"},
{file = "pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65"},
{file = "pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9"},
{file = "pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b"},
{file = "pyyaml-6.0.3-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:b865addae83924361678b652338317d1bd7e79b1f4596f96b96c77a5a34b34da"},
{file = "pyyaml-6.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c3355370a2c156cffb25e876646f149d5d68f5e0a3ce86a5084dd0b64a994917"},
{file = "pyyaml-6.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3c5677e12444c15717b902a5798264fa7909e41153cdf9ef7ad571b704a63dd9"},
{file = "pyyaml-6.0.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5ed875a24292240029e4483f9d4a4b8a1ae08843b9c54f43fcc11e404532a8a5"},
{file = "pyyaml-6.0.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0150219816b6a1fa26fb4699fb7daa9caf09eb1999f3b70fb6e786805e80375a"},
{file = "pyyaml-6.0.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fa160448684b4e94d80416c0fa4aac48967a969efe22931448d853ada8baf926"},
{file = "pyyaml-6.0.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:27c0abcb4a5dac13684a37f76e701e054692a9b2d3064b70f5e4eb54810553d7"},
{file = "pyyaml-6.0.3-cp39-cp39-win32.whl", hash = "sha256:1ebe39cb5fc479422b83de611d14e2c0d3bb2a18bbcb01f229ab3cfbd8fee7a0"},
{file = "pyyaml-6.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:2e71d11abed7344e42a8849600193d15b6def118602c4c176f748e4583246007"},
{file = "pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f"},
]
[[package]]
name = "requests"
version = "2.34.2"
description = "Python HTTP for Humans."
optional = false
python-versions = ">=3.10"
groups = ["main"]
files = [
{file = "requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0"},
{file = "requests-2.34.2.tar.gz", hash = "sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed"},
]
[package.dependencies]
certifi = ">=2023.5.7"
charset_normalizer = ">=2,<4"
idna = ">=2.5,<4"
urllib3 = ">=1.26,<3"
[package.extras]
socks = ["PySocks (>=1.5.6,!=1.5.7)"]
use-chardet-on-py3 = ["chardet (>=3.0.2,<8)"]
[[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.*"
groups = ["dev"]
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 = "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"
groups = ["main", "dev"]
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) ; os_name != \"nt\" and python_version < \"3\" and platform_python_implementation == \"CPython\"", "brotli (>=1.0.9) ; python_version >= \"3\" and platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; (os_name != \"nt\" or python_version >= \"3\") and platform_python_implementation != \"CPython\"", "brotlipy (>=0.6.0) ; os_name == \"nt\" and python_version < \"3\""]
secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress ; python_version == \"2.7\"", "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"
groups = ["dev"]
files = [
{file = "vcrpy-6.0.1-py2.py3-none-any.whl", hash = "sha256:621c3fb2d6bd8aa9f87532c688e4575bcbbde0c0afeb5ebdb7e14cac409edfdd"},
{file = "vcrpy-6.0.1.tar.gz", hash = "sha256:9e023fee7f892baa0bbda2f7da7c8ac51165c1c6e38ff8688683a12a4bde9278"},
]
[package.dependencies]
PyYAML = "*"
urllib3 = {version = "<2", markers = "platform_python_implementation == \"PyPy\""}
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"
groups = ["dev"]
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"
groups = ["main", "dev"]
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.1"
python-versions = ">=3.11"
content-hash = "f7f5dca936456ab8d93c3f217e4dd4536a9dda60798b8c7dae55745d50195de8"
freenub-0.1.2/pubnub/ 0000775 0000000 0000000 00000000000 15257723452 0014447 5 ustar 00root root 0000000 0000000 freenub-0.1.2/pubnub/__init__.py 0000664 0000000 0000000 00000001064 15257723452 0016561 0 ustar 00root root 0000000 0000000 import logging
import os
PUBNUB_ROOT = os.path.dirname(os.path.abspath(__file__))
__version__ = "0.1.2"
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)
freenub-0.1.2/pubnub/builders.py 0000664 0000000 0000000 00000003721 15257723452 0016635 0 ustar 00root root 0000000 0000000 from 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)
freenub-0.1.2/pubnub/callbacks.py 0000664 0000000 0000000 00000001505 15257723452 0016741 0 ustar 00root root 0000000 0000000 from 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
freenub-0.1.2/pubnub/crypto.py 0000664 0000000 0000000 00000010431 15257723452 0016340 0 ustar 00root root 0000000 0000000 import 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
freenub-0.1.2/pubnub/crypto_core.py 0000664 0000000 0000000 00000000272 15257723452 0017352 0 ustar 00root root 0000000 0000000 from abc import abstractmethod
class PubNubCrypto:
@abstractmethod
def encrypt(self, key, msg):
pass
@abstractmethod
def decrypt(self, key, msg):
pass
freenub-0.1.2/pubnub/dtos.py 0000664 0000000 0000000 00000001724 15257723452 0015776 0 ustar 00root root 0000000 0000000 class 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
freenub-0.1.2/pubnub/endpoints/ 0000775 0000000 0000000 00000000000 15257723452 0016452 5 ustar 00root root 0000000 0000000 freenub-0.1.2/pubnub/endpoints/__init__.py 0000664 0000000 0000000 00000000000 15257723452 0020551 0 ustar 00root root 0000000 0000000 freenub-0.1.2/pubnub/endpoints/access/ 0000775 0000000 0000000 00000000000 15257723452 0017713 5 ustar 00root root 0000000 0000000 freenub-0.1.2/pubnub/endpoints/access/__init__.py 0000664 0000000 0000000 00000000000 15257723452 0022012 0 ustar 00root root 0000000 0000000 freenub-0.1.2/pubnub/endpoints/access/audit.py 0000664 0000000 0000000 00000004212 15257723452 0021372 0 ustar 00root root 0000000 0000000 from 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"
freenub-0.1.2/pubnub/endpoints/access/grant.py 0000664 0000000 0000000 00000012120 15257723452 0021374 0 ustar 00root root 0000000 0000000 from 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"
freenub-0.1.2/pubnub/endpoints/access/grant_token.py 0000664 0000000 0000000 00000007023 15257723452 0022602 0 ustar 00root root 0000000 0000000 from 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)
freenub-0.1.2/pubnub/endpoints/access/revoke_token.py 0000664 0000000 0000000 00000002314 15257723452 0022760 0 ustar 00root root 0000000 0000000 from 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"
freenub-0.1.2/pubnub/endpoints/channel_groups/ 0000775 0000000 0000000 00000000000 15257723452 0021461 5 ustar 00root root 0000000 0000000 freenub-0.1.2/pubnub/endpoints/channel_groups/__init__.py 0000664 0000000 0000000 00000000000 15257723452 0023560 0 ustar 00root root 0000000 0000000 freenub-0.1.2/pubnub/endpoints/channel_groups/add_channel_to_channel_group.py 0000664 0000000 0000000 00000004122 15257723452 0027660 0 ustar 00root root 0000000 0000000 from 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"
freenub-0.1.2/pubnub/endpoints/channel_groups/list_channels_in_channel_group.py 0000664 0000000 0000000 00000003505 15257723452 0030256 0 ustar 00root root 0000000 0000000 from 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"
freenub-0.1.2/pubnub/endpoints/channel_groups/remove_channel_from_channel_group.py 0000664 0000000 0000000 00000004166 15257723452 0030756 0 ustar 00root root 0000000 0000000 from 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"
freenub-0.1.2/pubnub/endpoints/channel_groups/remove_channel_group.py 0000664 0000000 0000000 00000003236 15257723452 0026240 0 ustar 00root root 0000000 0000000 from 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"
freenub-0.1.2/pubnub/endpoints/endpoint.py 0000664 0000000 0000000 00000023470 15257723452 0020652 0 ustar 00root root 0000000 0000000 import 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
freenub-0.1.2/pubnub/endpoints/entities/ 0000775 0000000 0000000 00000000000 15257723452 0020276 5 ustar 00root root 0000000 0000000 freenub-0.1.2/pubnub/endpoints/entities/__init__.py 0000664 0000000 0000000 00000000000 15257723452 0022375 0 ustar 00root root 0000000 0000000 freenub-0.1.2/pubnub/endpoints/entities/endpoint.py 0000664 0000000 0000000 00000014102 15257723452 0022466 0 ustar 00root root 0000000 0000000 import 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
freenub-0.1.2/pubnub/endpoints/entities/membership/ 0000775 0000000 0000000 00000000000 15257723452 0022431 5 ustar 00root root 0000000 0000000 freenub-0.1.2/pubnub/endpoints/entities/membership/__init__.py 0000664 0000000 0000000 00000000000 15257723452 0024530 0 ustar 00root root 0000000 0000000 freenub-0.1.2/pubnub/endpoints/entities/membership/add_memberships.py 0000664 0000000 0000000 00000006315 15257723452 0026136 0 ustar 00root root 0000000 0000000 from 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
freenub-0.1.2/pubnub/endpoints/entities/membership/fetch_memberships.py 0000664 0000000 0000000 00000004112 15257723452 0026470 0 ustar 00root root 0000000 0000000 from 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
freenub-0.1.2/pubnub/endpoints/entities/membership/remove_memberships.py 0000664 0000000 0000000 00000006126 15257723452 0026703 0 ustar 00root root 0000000 0000000 from 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
freenub-0.1.2/pubnub/endpoints/entities/membership/update_memberships.py 0000664 0000000 0000000 00000006345 15257723452 0026673 0 ustar 00root root 0000000 0000000 from 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
freenub-0.1.2/pubnub/endpoints/entities/space/ 0000775 0000000 0000000 00000000000 15257723452 0021371 5 ustar 00root root 0000000 0000000 freenub-0.1.2/pubnub/endpoints/entities/space/__init__.py 0000664 0000000 0000000 00000000000 15257723452 0023470 0 ustar 00root root 0000000 0000000 freenub-0.1.2/pubnub/endpoints/entities/space/create_space.py 0000664 0000000 0000000 00000004075 15257723452 0024367 0 ustar 00root root 0000000 0000000 from 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
freenub-0.1.2/pubnub/endpoints/entities/space/fetch_space.py 0000664 0000000 0000000 00000002023 15257723452 0024204 0 ustar 00root root 0000000 0000000 from 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
freenub-0.1.2/pubnub/endpoints/entities/space/fetch_spaces.py 0000664 0000000 0000000 00000001671 15257723452 0024377 0 ustar 00root root 0000000 0000000 from 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
freenub-0.1.2/pubnub/endpoints/entities/space/remove_space.py 0000664 0000000 0000000 00000001662 15257723452 0024420 0 ustar 00root root 0000000 0000000 from 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
freenub-0.1.2/pubnub/endpoints/entities/space/update_space.py 0000664 0000000 0000000 00000004075 15257723452 0024406 0 ustar 00root root 0000000 0000000 from 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
freenub-0.1.2/pubnub/endpoints/entities/user/ 0000775 0000000 0000000 00000000000 15257723452 0021254 5 ustar 00root root 0000000 0000000 freenub-0.1.2/pubnub/endpoints/entities/user/__init__.py 0000664 0000000 0000000 00000000000 15257723452 0023353 0 ustar 00root root 0000000 0000000 freenub-0.1.2/pubnub/endpoints/entities/user/create_user.py 0000664 0000000 0000000 00000004273 15257723452 0024135 0 ustar 00root root 0000000 0000000 from 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
freenub-0.1.2/pubnub/endpoints/entities/user/fetch_user.py 0000664 0000000 0000000 00000002016 15257723452 0023754 0 ustar 00root root 0000000 0000000 from 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
freenub-0.1.2/pubnub/endpoints/entities/user/fetch_users.py 0000664 0000000 0000000 00000001611 15257723452 0024137 0 ustar 00root root 0000000 0000000 from 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
freenub-0.1.2/pubnub/endpoints/entities/user/remove_user.py 0000664 0000000 0000000 00000001655 15257723452 0024170 0 ustar 00root root 0000000 0000000 from 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
freenub-0.1.2/pubnub/endpoints/entities/user/update_user.py 0000664 0000000 0000000 00000004273 15257723452 0024154 0 ustar 00root root 0000000 0000000 from 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
freenub-0.1.2/pubnub/endpoints/fetch_messages.py 0000664 0000000 0000000 00000014062 15257723452 0022007 0 ustar 00root root 0000000 0000000 import 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"
freenub-0.1.2/pubnub/endpoints/file_operations/ 0000775 0000000 0000000 00000000000 15257723452 0021634 5 ustar 00root root 0000000 0000000 freenub-0.1.2/pubnub/endpoints/file_operations/__init__.py 0000664 0000000 0000000 00000000000 15257723452 0023733 0 ustar 00root root 0000000 0000000 freenub-0.1.2/pubnub/endpoints/file_operations/delete_file.py 0000664 0000000 0000000 00000002630 15257723452 0024450 0 ustar 00root root 0000000 0000000 from 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"
freenub-0.1.2/pubnub/endpoints/file_operations/download_file.py 0000664 0000000 0000000 00000005001 15257723452 0025010 0 ustar 00root root 0000000 0000000 from 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"
)
freenub-0.1.2/pubnub/endpoints/file_operations/download_file_asyncio.py 0000664 0000000 0000000 00000001632 15257723452 0026543 0 ustar 00root root 0000000 0000000 from 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
freenub-0.1.2/pubnub/endpoints/file_operations/fetch_upload_details.py 0000664 0000000 0000000 00000002644 15257723452 0026356 0 ustar 00root root 0000000 0000000 from 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"
freenub-0.1.2/pubnub/endpoints/file_operations/file_based_endpoint.py 0000664 0000000 0000000 00000000704 15257723452 0026164 0 ustar 00root root 0000000 0000000 from 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
freenub-0.1.2/pubnub/endpoints/file_operations/get_file_url.py 0000664 0000000 0000000 00000003737 15257723452 0024660 0 ustar 00root root 0000000 0000000 from 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"
freenub-0.1.2/pubnub/endpoints/file_operations/list_files.py 0000664 0000000 0000000 00000002031 15257723452 0024337 0 ustar 00root root 0000000 0000000 from 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"
freenub-0.1.2/pubnub/endpoints/file_operations/publish_file_message.py 0000664 0000000 0000000 00000006036 15257723452 0026364 0 ustar 00root root 0000000 0000000 from 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"
freenub-0.1.2/pubnub/endpoints/file_operations/send_file.py 0000664 0000000 0000000 00000010722 15257723452 0024140 0 ustar 00root root 0000000 0000000 from 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"
)
freenub-0.1.2/pubnub/endpoints/file_operations/send_file_asyncio.py 0000664 0000000 0000000 00000003742 15257723452 0025671 0 ustar 00root root 0000000 0000000 import 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
freenub-0.1.2/pubnub/endpoints/history.py 0000664 0000000 0000000 00000006232 15257723452 0020530 0 ustar 00root root 0000000 0000000 from 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"
freenub-0.1.2/pubnub/endpoints/history_delete.py 0000664 0000000 0000000 00000003150 15257723452 0022046 0 ustar 00root root 0000000 0000000 from 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"
freenub-0.1.2/pubnub/endpoints/message_actions/ 0000775 0000000 0000000 00000000000 15257723452 0021616 5 ustar 00root root 0000000 0000000 freenub-0.1.2/pubnub/endpoints/message_actions/__init__.py 0000664 0000000 0000000 00000000000 15257723452 0023715 0 ustar 00root root 0000000 0000000 freenub-0.1.2/pubnub/endpoints/message_actions/add_message_action.py 0000664 0000000 0000000 00000005200 15257723452 0025756 0 ustar 00root root 0000000 0000000 from 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)
freenub-0.1.2/pubnub/endpoints/message_actions/get_message_actions.py 0000664 0000000 0000000 00000004427 15257723452 0026202 0 ustar 00root root 0000000 0000000 from 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"
freenub-0.1.2/pubnub/endpoints/message_actions/remove_message_action.py 0000664 0000000 0000000 00000004305 15257723452 0026530 0 ustar 00root root 0000000 0000000 from 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)
freenub-0.1.2/pubnub/endpoints/message_count.py 0000664 0000000 0000000 00000004120 15257723452 0021655 0 ustar 00root root 0000000 0000000 from 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"
freenub-0.1.2/pubnub/endpoints/mixins.py 0000664 0000000 0000000 00000001542 15257723452 0020335 0 ustar 00root root 0000000 0000000 from 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
freenub-0.1.2/pubnub/endpoints/objects_v2/ 0000775 0000000 0000000 00000000000 15257723452 0020512 5 ustar 00root root 0000000 0000000 freenub-0.1.2/pubnub/endpoints/objects_v2/__init__.py 0000664 0000000 0000000 00000000000 15257723452 0022611 0 ustar 00root root 0000000 0000000 freenub-0.1.2/pubnub/endpoints/objects_v2/channel/ 0000775 0000000 0000000 00000000000 15257723452 0022122 5 ustar 00root root 0000000 0000000 freenub-0.1.2/pubnub/endpoints/objects_v2/channel/__init__.py 0000664 0000000 0000000 00000000000 15257723452 0024221 0 ustar 00root root 0000000 0000000 freenub-0.1.2/pubnub/endpoints/objects_v2/channel/get_all_channels.py 0000664 0000000 0000000 00000001720 15257723452 0025756 0 ustar 00root root 0000000 0000000 from 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
freenub-0.1.2/pubnub/endpoints/objects_v2/channel/get_channel.py 0000664 0000000 0000000 00000002072 15257723452 0024744 0 ustar 00root root 0000000 0000000 from 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
freenub-0.1.2/pubnub/endpoints/objects_v2/channel/remove_channel.py 0000664 0000000 0000000 00000001766 15257723452 0025473 0 ustar 00root root 0000000 0000000 from 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
freenub-0.1.2/pubnub/endpoints/objects_v2/channel/set_channel.py 0000664 0000000 0000000 00000003210 15257723452 0024753 0 ustar 00root root 0000000 0000000 from 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
freenub-0.1.2/pubnub/endpoints/objects_v2/members/ 0000775 0000000 0000000 00000000000 15257723452 0022144 5 ustar 00root root 0000000 0000000 freenub-0.1.2/pubnub/endpoints/objects_v2/members/__init__.py 0000664 0000000 0000000 00000000000 15257723452 0024243 0 ustar 00root root 0000000 0000000 freenub-0.1.2/pubnub/endpoints/objects_v2/members/get_channel_members.py 0000664 0000000 0000000 00000002437 15257723452 0026505 0 ustar 00root root 0000000 0000000 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 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
freenub-0.1.2/pubnub/endpoints/objects_v2/members/manage_channel_members.py 0000664 0000000 0000000 00000004014 15257723452 0027147 0 ustar 00root root 0000000 0000000 from 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
freenub-0.1.2/pubnub/endpoints/objects_v2/members/remove_channel_members.py 0000664 0000000 0000000 00000003313 15257723452 0027215 0 ustar 00root root 0000000 0000000 from 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
freenub-0.1.2/pubnub/endpoints/objects_v2/members/set_channel_members.py 0000664 0000000 0000000 00000003241 15257723452 0026513 0 ustar 00root root 0000000 0000000 from 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
freenub-0.1.2/pubnub/endpoints/objects_v2/memberships/ 0000775 0000000 0000000 00000000000 15257723452 0023030 5 ustar 00root root 0000000 0000000 freenub-0.1.2/pubnub/endpoints/objects_v2/memberships/__init__.py 0000664 0000000 0000000 00000000000 15257723452 0025127 0 ustar 00root root 0000000 0000000 freenub-0.1.2/pubnub/endpoints/objects_v2/memberships/get_memberships.py 0000664 0000000 0000000 00000002406 15257723452 0026561 0 ustar 00root root 0000000 0000000 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 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
freenub-0.1.2/pubnub/endpoints/objects_v2/memberships/manage_memberships.py 0000664 0000000 0000000 00000004442 15257723452 0027234 0 ustar 00root root 0000000 0000000 from 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
freenub-0.1.2/pubnub/endpoints/objects_v2/memberships/remove_memberships.py 0000664 0000000 0000000 00000003503 15257723452 0027276 0 ustar 00root root 0000000 0000000 from 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
freenub-0.1.2/pubnub/endpoints/objects_v2/memberships/set_memberships.py 0000664 0000000 0000000 00000003440 15257723452 0026574 0 ustar 00root root 0000000 0000000 from 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
freenub-0.1.2/pubnub/endpoints/objects_v2/objects_endpoint.py 0000664 0000000 0000000 00000013142 15257723452 0024416 0 ustar 00root root 0000000 0000000 import 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
freenub-0.1.2/pubnub/endpoints/objects_v2/uuid/ 0000775 0000000 0000000 00000000000 15257723452 0021460 5 ustar 00root root 0000000 0000000 freenub-0.1.2/pubnub/endpoints/objects_v2/uuid/__init__.py 0000664 0000000 0000000 00000000000 15257723452 0023557 0 ustar 00root root 0000000 0000000 freenub-0.1.2/pubnub/endpoints/objects_v2/uuid/get_all_uuid.py 0000664 0000000 0000000 00000001654 15257723452 0024475 0 ustar 00root root 0000000 0000000 from 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
freenub-0.1.2/pubnub/endpoints/objects_v2/uuid/get_uuid.py 0000664 0000000 0000000 00000002027 15257723452 0023640 0 ustar 00root root 0000000 0000000 from 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
freenub-0.1.2/pubnub/endpoints/objects_v2/uuid/remove_uuid.py 0000664 0000000 0000000 00000001706 15257723452 0024361 0 ustar 00root root 0000000 0000000 from 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
freenub-0.1.2/pubnub/endpoints/objects_v2/uuid/set_uuid.py 0000664 0000000 0000000 00000003656 15257723452 0023665 0 ustar 00root root 0000000 0000000 from 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
freenub-0.1.2/pubnub/endpoints/presence/ 0000775 0000000 0000000 00000000000 15257723452 0020256 5 ustar 00root root 0000000 0000000 freenub-0.1.2/pubnub/endpoints/presence/__init__.py 0000664 0000000 0000000 00000000000 15257723452 0022355 0 ustar 00root root 0000000 0000000 freenub-0.1.2/pubnub/endpoints/presence/get_state.py 0000664 0000000 0000000 00000004344 15257723452 0022614 0 ustar 00root root 0000000 0000000 from 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"
freenub-0.1.2/pubnub/endpoints/presence/heartbeat.py 0000664 0000000 0000000 00000004220 15257723452 0022565 0 ustar 00root root 0000000 0000000 from 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"
freenub-0.1.2/pubnub/endpoints/presence/here_now.py 0000664 0000000 0000000 00000004375 15257723452 0022447 0 ustar 00root root 0000000 0000000 from 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"
freenub-0.1.2/pubnub/endpoints/presence/leave.py 0000664 0000000 0000000 00000004167 15257723452 0021734 0 ustar 00root root 0000000 0000000 from 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"
freenub-0.1.2/pubnub/endpoints/presence/set_state.py 0000664 0000000 0000000 00000006130 15257723452 0022623 0 ustar 00root root 0000000 0000000 from 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"
freenub-0.1.2/pubnub/endpoints/presence/where_now.py 0000664 0000000 0000000 00000002604 15257723452 0022627 0 ustar 00root root 0000000 0000000 from 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"
freenub-0.1.2/pubnub/endpoints/pubsub/ 0000775 0000000 0000000 00000000000 15257723452 0017752 5 ustar 00root root 0000000 0000000 freenub-0.1.2/pubnub/endpoints/pubsub/__init__.py 0000664 0000000 0000000 00000000000 15257723452 0022051 0 ustar 00root root 0000000 0000000 freenub-0.1.2/pubnub/endpoints/pubsub/fire.py 0000664 0000000 0000000 00000010146 15257723452 0021253 0 ustar 00root root 0000000 0000000 from 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"
freenub-0.1.2/pubnub/endpoints/pubsub/publish.py 0000664 0000000 0000000 00000011617 15257723452 0022000 0 ustar 00root root 0000000 0000000 from 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"
freenub-0.1.2/pubnub/endpoints/pubsub/subscribe.py 0000664 0000000 0000000 00000005300 15257723452 0022303 0 ustar 00root root 0000000 0000000 from 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"
freenub-0.1.2/pubnub/endpoints/push/ 0000775 0000000 0000000 00000000000 15257723452 0017431 5 ustar 00root root 0000000 0000000 freenub-0.1.2/pubnub/endpoints/push/__init__.py 0000664 0000000 0000000 00000000000 15257723452 0021530 0 ustar 00root root 0000000 0000000 freenub-0.1.2/pubnub/endpoints/push/add_channels_to_push.py 0000664 0000000 0000000 00000006514 15257723452 0024155 0 ustar 00root root 0000000 0000000 from 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"
freenub-0.1.2/pubnub/endpoints/push/list_push_provisions.py 0000664 0000000 0000000 00000006243 15257723452 0024315 0 ustar 00root root 0000000 0000000 from 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"
freenub-0.1.2/pubnub/endpoints/push/remove_channels_from_push.py 0000664 0000000 0000000 00000006547 15257723452 0025251 0 ustar 00root root 0000000 0000000 from 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"
freenub-0.1.2/pubnub/endpoints/push/remove_device.py 0000664 0000000 0000000 00000006056 15257723452 0022626 0 ustar 00root root 0000000 0000000 from 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"
freenub-0.1.2/pubnub/endpoints/signal.py 0000664 0000000 0000000 00000003115 15257723452 0020301 0 ustar 00root root 0000000 0000000 from 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"
freenub-0.1.2/pubnub/endpoints/time.py 0000664 0000000 0000000 00000001520 15257723452 0017760 0 ustar 00root root 0000000 0000000 from 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"
freenub-0.1.2/pubnub/enums.py 0000664 0000000 0000000 00000010115 15257723452 0016146 0 ustar 00root root 0000000 0000000 from 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
freenub-0.1.2/pubnub/errors.py 0000664 0000000 0000000 00000006227 15257723452 0016344 0 ustar 00root root 0000000 0000000 PNERR_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"
freenub-0.1.2/pubnub/event_engine/ 0000775 0000000 0000000 00000000000 15257723452 0017115 5 ustar 00root root 0000000 0000000 freenub-0.1.2/pubnub/event_engine/__init__.py 0000664 0000000 0000000 00000000000 15257723452 0021214 0 ustar 00root root 0000000 0000000 freenub-0.1.2/pubnub/event_engine/dispatcher.py 0000664 0000000 0000000 00000003132 15257723452 0021614 0 ustar 00root root 0000000 0000000 from 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]
freenub-0.1.2/pubnub/event_engine/manage_effects.py 0000664 0000000 0000000 00000027310 15257723452 0022421 0 ustar 00root root 0000000 0000000 import 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)
freenub-0.1.2/pubnub/event_engine/models/ 0000775 0000000 0000000 00000000000 15257723452 0020400 5 ustar 00root root 0000000 0000000 freenub-0.1.2/pubnub/event_engine/models/__init__.py 0000664 0000000 0000000 00000000000 15257723452 0022477 0 ustar 00root root 0000000 0000000 freenub-0.1.2/pubnub/event_engine/models/effects.py 0000664 0000000 0000000 00000004600 15257723452 0022371 0 ustar 00root root 0000000 0000000 from 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
freenub-0.1.2/pubnub/event_engine/models/events.py 0000664 0000000 0000000 00000004737 15257723452 0022271 0 ustar 00root root 0000000 0000000 from 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
freenub-0.1.2/pubnub/event_engine/models/states.py 0000664 0000000 0000000 00000045332 15257723452 0022264 0 ustar 00root root 0000000 0000000 from 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)
freenub-0.1.2/pubnub/event_engine/statemachine.py 0000664 0000000 0000000 00000007623 15257723452 0022144 0 ustar 00root root 0000000 0000000 """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}")
freenub-0.1.2/pubnub/exceptions.py 0000664 0000000 0000000 00000001225 15257723452 0017202 0 ustar 00root root 0000000 0000000 class 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
freenub-0.1.2/pubnub/features.py 0000664 0000000 0000000 00000000772 15257723452 0016645 0 ustar 00root root 0000000 0000000 from 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
freenub-0.1.2/pubnub/managers.py 0000664 0000000 0000000 00000047177 15257723452 0016636 0 ustar 00root root 0000000 0000000 import 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)
if subscribe_operation.timetoken is not None:
self._timetoken = subscribe_operation.timetoken
# reconnect() clears the announcement latch by default, so a new
# subscription re-announces PNConnectedCategory on its own.
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(announce_status=False)
def adapt_state_builder(self, state_operation):
self._subscription_state.adapt_state_builder(state_operation)
self.reconnect(announce_status=False)
@abstractmethod
def reconnect(self, announce_status=True):
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)
freenub-0.1.2/pubnub/models/ 0000775 0000000 0000000 00000000000 15257723452 0015732 5 ustar 00root root 0000000 0000000 freenub-0.1.2/pubnub/models/__init__.py 0000664 0000000 0000000 00000000000 15257723452 0020031 0 ustar 00root root 0000000 0000000 freenub-0.1.2/pubnub/models/consumer/ 0000775 0000000 0000000 00000000000 15257723452 0017565 5 ustar 00root root 0000000 0000000 freenub-0.1.2/pubnub/models/consumer/__init__.py 0000664 0000000 0000000 00000000000 15257723452 0021664 0 ustar 00root root 0000000 0000000 freenub-0.1.2/pubnub/models/consumer/access_manager.py 0000664 0000000 0000000 00000014163 15257723452 0023077 0 ustar 00root root 0000000 0000000 """
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
freenub-0.1.2/pubnub/models/consumer/channel_group.py 0000664 0000000 0000000 00000001030 15257723452 0022755 0 ustar 00root root 0000000 0000000 class 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)
freenub-0.1.2/pubnub/models/consumer/common.py 0000664 0000000 0000000 00000001041 15257723452 0021423 0 ustar 00root root 0000000 0000000 class 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)
freenub-0.1.2/pubnub/models/consumer/entities/ 0000775 0000000 0000000 00000000000 15257723452 0021411 5 ustar 00root root 0000000 0000000 freenub-0.1.2/pubnub/models/consumer/entities/__init__.py 0000664 0000000 0000000 00000000000 15257723452 0023510 0 ustar 00root root 0000000 0000000 freenub-0.1.2/pubnub/models/consumer/entities/membership.py 0000664 0000000 0000000 00000001620 15257723452 0024115 0 ustar 00root root 0000000 0000000 from 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"]]
freenub-0.1.2/pubnub/models/consumer/entities/page.py 0000664 0000000 0000000 00000001351 15257723452 0022677 0 ustar 00root root 0000000 0000000 from 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))
freenub-0.1.2/pubnub/models/consumer/entities/result.py 0000664 0000000 0000000 00000000671 15257723452 0023305 0 ustar 00root root 0000000 0000000 from 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)
freenub-0.1.2/pubnub/models/consumer/entities/space.py 0000664 0000000 0000000 00000002120 15257723452 0023051 0 ustar 00root root 0000000 0000000 from 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
freenub-0.1.2/pubnub/models/consumer/entities/user.py 0000664 0000000 0000000 00000002073 15257723452 0022743 0 ustar 00root root 0000000 0000000 from 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
freenub-0.1.2/pubnub/models/consumer/file.py 0000664 0000000 0000000 00000003250 15257723452 0021056 0 ustar 00root root 0000000 0000000 class 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
freenub-0.1.2/pubnub/models/consumer/history.py 0000664 0000000 0000000 00000007276 15257723452 0021654 0 ustar 00root root 0000000 0000000 class 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}"
)
freenub-0.1.2/pubnub/models/consumer/message_actions.py 0000664 0000000 0000000 00000003003 15257723452 0023277 0 ustar 00root root 0000000 0000000 class 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"
freenub-0.1.2/pubnub/models/consumer/message_count.py 0000664 0000000 0000000 00000000545 15257723452 0022777 0 ustar 00root root 0000000 0000000 class 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}"
freenub-0.1.2/pubnub/models/consumer/objects_v2/ 0000775 0000000 0000000 00000000000 15257723452 0021625 5 ustar 00root root 0000000 0000000 freenub-0.1.2/pubnub/models/consumer/objects_v2/__init__.py 0000664 0000000 0000000 00000000000 15257723452 0023724 0 ustar 00root root 0000000 0000000 freenub-0.1.2/pubnub/models/consumer/objects_v2/channel.py 0000664 0000000 0000000 00000002310 15257723452 0023603 0 ustar 00root root 0000000 0000000 from 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}"
freenub-0.1.2/pubnub/models/consumer/objects_v2/channel_members.py 0000664 0000000 0000000 00000003625 15257723452 0025327 0 ustar 00root root 0000000 0000000 from 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
freenub-0.1.2/pubnub/models/consumer/objects_v2/memberships.py 0000664 0000000 0000000 00000004424 15257723452 0024521 0 ustar 00root root 0000000 0000000 from 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}"
freenub-0.1.2/pubnub/models/consumer/objects_v2/page.py 0000664 0000000 0000000 00000001375 15257723452 0023121 0 ustar 00root root 0000000 0000000 from 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
freenub-0.1.2/pubnub/models/consumer/objects_v2/sort.py 0000664 0000000 0000000 00000002054 15257723452 0023167 0 ustar 00root root 0000000 0000000 from 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()
freenub-0.1.2/pubnub/models/consumer/objects_v2/uuid.py 0000664 0000000 0000000 00000002252 15257723452 0023146 0 ustar 00root root 0000000 0000000 from 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}"
freenub-0.1.2/pubnub/models/consumer/pn_error_data.py 0000664 0000000 0000000 00000000352 15257723452 0022756 0 ustar 00root root 0000000 0000000 class PNErrorData:
def __init__(self, information, exception):
assert isinstance(information, str)
assert isinstance(exception, Exception)
self.information = information
self.exception = exception
freenub-0.1.2/pubnub/models/consumer/presence.py 0000664 0000000 0000000 00000012735 15257723452 0021753 0 ustar 00root root 0000000 0000000 class 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
freenub-0.1.2/pubnub/models/consumer/pubsub.py 0000664 0000000 0000000 00000007012 15257723452 0021437 0 ustar 00root root 0000000 0000000 from 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
freenub-0.1.2/pubnub/models/consumer/push.py 0000664 0000000 0000000 00000001057 15257723452 0021121 0 ustar 00root root 0000000 0000000 class 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
)
freenub-0.1.2/pubnub/models/consumer/signal.py 0000664 0000000 0000000 00000000516 15257723452 0021416 0 ustar 00root root 0000000 0000000 class 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
freenub-0.1.2/pubnub/models/consumer/time.py 0000664 0000000 0000000 00000001034 15257723452 0021073 0 ustar 00root root 0000000 0000000 from 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)
freenub-0.1.2/pubnub/models/consumer/v3/ 0000775 0000000 0000000 00000000000 15257723452 0020115 5 ustar 00root root 0000000 0000000 freenub-0.1.2/pubnub/models/consumer/v3/__init__.py 0000664 0000000 0000000 00000000000 15257723452 0022214 0 ustar 00root root 0000000 0000000 freenub-0.1.2/pubnub/models/consumer/v3/access_manager.py 0000664 0000000 0000000 00000001105 15257723452 0023417 0 ustar 00root root 0000000 0000000 """
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
freenub-0.1.2/pubnub/models/consumer/v3/channel.py 0000664 0000000 0000000 00000001666 15257723452 0022110 0 ustar 00root root 0000000 0000000 from 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
freenub-0.1.2/pubnub/models/consumer/v3/group.py 0000664 0000000 0000000 00000001111 15257723452 0021615 0 ustar 00root root 0000000 0000000 from 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
freenub-0.1.2/pubnub/models/consumer/v3/pn_resource.py 0000664 0000000 0000000 00000002025 15257723452 0023012 0 ustar 00root root 0000000 0000000 class 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
freenub-0.1.2/pubnub/models/consumer/v3/space.py 0000664 0000000 0000000 00000001747 15257723452 0021573 0 ustar 00root root 0000000 0000000 from 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
freenub-0.1.2/pubnub/models/consumer/v3/user.py 0000664 0000000 0000000 00000001734 15257723452 0021452 0 ustar 00root root 0000000 0000000 from 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
freenub-0.1.2/pubnub/models/consumer/v3/uuid.py 0000664 0000000 0000000 00000001203 15257723452 0021431 0 ustar 00root root 0000000 0000000 from 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
freenub-0.1.2/pubnub/models/server/ 0000775 0000000 0000000 00000000000 15257723452 0017240 5 ustar 00root root 0000000 0000000 freenub-0.1.2/pubnub/models/server/__init__.py 0000664 0000000 0000000 00000000000 15257723452 0021337 0 ustar 00root root 0000000 0000000 freenub-0.1.2/pubnub/models/server/subscribe.py 0000664 0000000 0000000 00000006602 15257723452 0021577 0 ustar 00root root 0000000 0000000 class 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"]))
freenub-0.1.2/pubnub/models/subscription_item.py 0000664 0000000 0000000 00000000254 15257723452 0022047 0 ustar 00root root 0000000 0000000 class SubscriptionItem:
def __init__(self, name=None, state=None):
self.name = name
self.state = state
def __str__(self):
return self.name
freenub-0.1.2/pubnub/pnconfiguration.py 0000664 0000000 0000000 00000011275 15257723452 0020234 0 ustar 00root root 0000000 0000000 from 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
freenub-0.1.2/pubnub/pubnub.py 0000664 0000000 0000000 00000042375 15257723452 0016327 0 ustar 00root root 0000000 0000000 import 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):
# A recovered connection is reported as PNReconnectedCategory,
# so the subscribe loop is restarted without arming the
# PNConnectedCategory announcement.
subscription_manager.reconnect(announce_status=False)
pn_status = PNStatus()
pn_status.category = PNStatusCategory.PNReconnectedCategory
pn_status.error = False
# Still required: the latch is already clear if no subscribe has
# succeeded yet, which is the case when the connection was down
# from the start.
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, announce_status=True):
self._should_stop = False
if announce_status:
# Clear the one-shot latch so the next successful subscribe
# announces PNConnectedCategory again. Callers that only restart
# the loop (adapt_unsubscribe_builder, adapt_state_builder) pass
# announce_status=False.
self._subscription_status_announced = 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:
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()
freenub-0.1.2/pubnub/pubnub_asyncio.py 0000664 0000000 0000000 00000067462 15257723452 0020060 0 ustar 00root root 0000000 0000000 import 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):
# A recovered connection is reported as PNReconnectedCategory,
# so the subscribe loop is restarted without arming the
# PNConnectedCategory announcement.
subscription_manager.reconnect(announce_status=False)
pn_status = PNStatus()
pn_status.category = PNStatusCategory.PNReconnectedCategory
pn_status.error = False
# Still required: the latch is already clear if no subscribe has
# succeeded yet, which is the case when the connection was down
# from the start.
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, announce_status=True):
# TODO: method is synchronized in Java
self._should_stop = False
if announce_status:
# Clear the one-shot latch so the next successful subscribe
# announces PNConnectedCategory again. Callers that only restart
# the loop (adapt_unsubscribe_builder, adapt_state_builder) pass
# announce_status=False.
self._subscription_status_announced = False
self._subscribe_loop_task = asyncio.ensure_future(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:
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()
freenub-0.1.2/pubnub/pubnub_core.py 0000664 0000000 0000000 00000050463 15257723452 0017334 0 ustar 00root root 0000000 0000000 import 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
freenub-0.1.2/pubnub/request_handlers/ 0000775 0000000 0000000 00000000000 15257723452 0020017 5 ustar 00root root 0000000 0000000 freenub-0.1.2/pubnub/request_handlers/__init__.py 0000664 0000000 0000000 00000000000 15257723452 0022116 0 ustar 00root root 0000000 0000000 freenub-0.1.2/pubnub/request_handlers/base.py 0000664 0000000 0000000 00000000623 15257723452 0021304 0 ustar 00root root 0000000 0000000 from 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
freenub-0.1.2/pubnub/request_handlers/requests_handler.py 0000664 0000000 0000000 00000027633 15257723452 0023754 0 ustar 00root root 0000000 0000000 import 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
freenub-0.1.2/pubnub/structures.py 0000664 0000000 0000000 00000006237 15257723452 0017254 0 ustar 00root root 0000000 0000000 from .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
freenub-0.1.2/pubnub/utils.py 0000664 0000000 0000000 00000020304 15257723452 0016160 0 ustar 00root root 0000000 0000000 import 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
freenub-0.1.2/pubnub/workers.py 0000664 0000000 0000000 00000016773 15257723452 0016533 0 ustar 00root root 0000000 0000000 import 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)
freenub-0.1.2/pyproject.toml 0000664 0000000 0000000 00000006546 15257723452 0016103 0 ustar 00root root 0000000 0000000 [tool.poetry]
name = "freenub"
version = "0.1.2"
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.11',
'Programming Language :: Python :: 3.12',
'Programming Language :: Python :: 3.13',
'Programming Language :: Python :: 3.14',
'Programming Language :: Python :: 3.15',
'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.11"
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"
allow_zero_version = true
[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 = "py311"
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"
freenub-0.1.2/renovate.json 0000664 0000000 0000000 00000000101 15257723452 0015662 0 ustar 00root root 0000000 0000000 {
"extends": ["github>browniebroke/renovate-configs:python"]
}
freenub-0.1.2/requirements-dev.txt 0000664 0000000 0000000 00000000163 15257723452 0017214 0 ustar 00root root 0000000 0000000 pyyaml
pytest-cov
pycryptodomex
flake8
pytest
pytest-asyncio
aiohttp
requests
cbor2
behave
vcrpy
urllib3<2
busypie
freenub-0.1.2/scripts/ 0000775 0000000 0000000 00000000000 15257723452 0014643 5 ustar 00root root 0000000 0000000 freenub-0.1.2/scripts/install.sh 0000775 0000000 0000000 00000000071 15257723452 0016646 0 ustar 00root root 0000000 0000000 #!/usr/bin/env bash
pip install -r requirements-dev.txt
freenub-0.1.2/scripts/run-tests.py 0000775 0000000 0000000 00000001116 15257723452 0017163 0 ustar 00root root 0000000 0000000 #!/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)
freenub-0.1.2/setup.cfg 0000664 0000000 0000000 00000000125 15257723452 0014773 0 ustar 00root root 0000000 0000000 [tool:pytest]
norecursedirs = benchmarks
[flake8]
max-line-length = 120
ignore=E402
freenub-0.1.2/templates/ 0000775 0000000 0000000 00000000000 15257723452 0015152 5 ustar 00root root 0000000 0000000 freenub-0.1.2/templates/CHANGELOG.md.j2 0000664 0000000 0000000 00000001235 15257723452 0017276 0 ustar 00root root 0000000 0000000 # 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 #}
freenub-0.1.2/tests/ 0000775 0000000 0000000 00000000000 15257723452 0014316 5 ustar 00root root 0000000 0000000 freenub-0.1.2/tests/__init__.py 0000664 0000000 0000000 00000000000 15257723452 0016415 0 ustar 00root root 0000000 0000000 freenub-0.1.2/tests/test_init.py 0000664 0000000 0000000 00000000327 15257723452 0016674 0 ustar 00root root 0000000 0000000 from 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]
freenub-0.1.2/tests/test_presence_heartbeat_flag.py 0000664 0000000 0000000 00000030123 15257723452 0022542 0 ustar 00root root 0000000 0000000 """`config.enable_presence_heartbeat` on the asyncio subscription manager.
The flag is False by default. `NativeSubscriptionManager.reconnect` checks it
before registering the presence heartbeat timer;
`AsyncioSubscriptionManager.reconnect` must do the same, otherwise every
`PubNubAsyncio` application issues presence transactions it did not ask for and
receives their failures as error statuses.
Both clients are driven through stubbed endpoints so no network traffic is
involved.
"""
import asyncio
import pytest
import pytest_asyncio
import pubnub.pubnub as pubnub_native
import pubnub.pubnub_asyncio as pubnub_asyncio
from pubnub.callbacks import SubscribeCallback
from pubnub.enums import PNHeartbeatNotificationOptions, PNStatusCategory
from pubnub.errors import PNERR_CLIENT_TIMEOUT, PNERR_REQUEST_CANCELLED
from pubnub.exceptions import PubNubException
from pubnub.models.consumer.common import PNStatus
from pubnub.pnconfiguration import PNConfiguration
from pubnub.pubnub import PubNub
from pubnub.pubnub_asyncio import (
AsyncioEnvelope,
PubNubAsyncio,
PubNubAsyncioException,
)
CHANNEL = "test-channel"
# Presence timeout and heartbeat interval, shortened from the defaults of 300
# and 280 so a test can observe several fires.
PRESENCE_TIMEOUT = 20
HEARTBEAT_INTERVAL = 0.1
# How long the tests that assert an *absence* wait before concluding nothing
# fired. Tests that assert a heartbeat did happen poll instead; see
# `wait_for_calls`.
HEARTBEAT_WINDOW = 0.35
# Time for the subscribe loop to get as far as its first request.
SETTLE = 0.05
def make_config(enable_presence_heartbeat=False):
config = PNConfiguration()
config.subscribe_key = "test-sub-key"
config.publish_key = "test-pub-key"
config.uuid = "test-uuid"
config.enable_presence_heartbeat = enable_presence_heartbeat
config.set_presence_timeout_with_custom_interval(
PRESENCE_TIMEOUT, HEARTBEAT_INTERVAL
)
return config
class StatusRecorder(SubscribeCallback):
def __init__(self):
self.categories = []
def status(self, pubnub, status):
self.categories.append(status.category)
def message(self, pubnub, message):
pass
def presence(self, pubnub, presence):
pass
class ParkedSubscribeStub:
"""A subscribe long poll that never answers.
Keeps the subscribe loop out of the way: the subscription state is
populated by `adapt_subscribe_builder`, which is all the heartbeat needs.
"""
calls = 0
def __init__(self, pubnub):
type(self).calls += 1
self._pubnub = pubnub
def channels(self, channels):
return self
def channel_groups(self, groups):
return self
def timetoken(self, timetoken):
return self
def region(self, region):
return self
def filter_expression(self, expression):
return self
async def future(self):
try:
await asyncio.Event().wait()
except asyncio.CancelledError:
status = PNStatus()
status.error = True
status.category = PNStatusCategory.PNCancelledCategory
status.error_data = PubNubException(pn_error=PNERR_REQUEST_CANCELLED)
return PubNubAsyncioException(result=None, status=status)
def pn_async(self, callback):
return ParkedNativeCall()
class ParkedNativeCall:
def __init__(self):
self.is_executed = False
self.is_canceled = False
def cancel(self):
self.is_canceled = True
def heartbeat_success():
status = PNStatus()
status.error = False
status.category = PNStatusCategory.PNAcknowledgmentCategory
return AsyncioEnvelope(result=None, status=status)
def heartbeat_timeout():
status = PNStatus()
status.error = True
status.category = PNStatusCategory.PNTimeoutCategory
status.error_data = PubNubException(pn_error=PNERR_CLIENT_TIMEOUT)
return PubNubAsyncioException(result=None, status=status)
class HeartbeatStub:
"""Counts presence heartbeat requests instead of sending them."""
calls = 0
response = staticmethod(heartbeat_success)
def __init__(self, pubnub):
self._pubnub = pubnub
def channels(self, channels):
return self
def channel_groups(self, groups):
return self
def state(self, state):
return self
def cancellation_event(self, event):
return self
async def future(self):
type(self).calls += 1
return type(self).response()
def pn_async(self, callback):
type(self).calls += 1
envelope = type(self).response()
callback(envelope.result, envelope.status)
return ParkedNativeCall()
async def wait_for_calls(minimum, timeout=2.0):
"""Wait for `minimum` heartbeat requests rather than for a fixed window."""
loop = asyncio.get_running_loop()
deadline = loop.time() + timeout
while HeartbeatStub.calls < minimum:
assert loop.time() < deadline, (
f"only {HeartbeatStub.calls} heartbeats in {timeout}s"
)
await asyncio.sleep(0.01)
def assert_subscribe_loop_alive(pubnub):
"""A negative heartbeat assertion means nothing if the subscribe loop died.
`_start_subscribe_loop` is fire-and-forget, so a stub that no longer matches
`Subscribe` kills it without reaching any listener, and every `calls == 0` or
`categories == []` below then passes vacuously. It also dies holding
`_subscription_lock`, which leaves the next loop blocked rather than failed,
so check that the loop is parked on a request and not merely undead.
"""
manager = pubnub._subscription_manager
loop_task = manager._subscribe_loop_task
if loop_task is not None and loop_task.done() and not loop_task.cancelled():
assert loop_task.exception() is None, (
f"subscribe loop raised: {loop_task.exception()!r}"
)
request = manager._subscribe_request_task
assert request is not None and not request.done(), (
"subscribe loop is not parked on a request"
)
@pytest.fixture
def stub_endpoints(monkeypatch):
ParkedSubscribeStub.calls = 0
HeartbeatStub.calls = 0
HeartbeatStub.response = staticmethod(heartbeat_success)
monkeypatch.setattr(pubnub_asyncio, "Subscribe", ParkedSubscribeStub)
monkeypatch.setattr(pubnub_asyncio, "Heartbeat", HeartbeatStub)
monkeypatch.setattr(pubnub_native, "Subscribe", ParkedSubscribeStub)
monkeypatch.setattr(pubnub_native, "Heartbeat", HeartbeatStub)
return HeartbeatStub
@pytest_asyncio.fixture
async def async_client_factory(stub_endpoints):
clients = []
def build(enable_presence_heartbeat=False):
pubnub = PubNubAsyncio(make_config(enable_presence_heartbeat))
recorder = StatusRecorder()
pubnub.add_listener(recorder)
clients.append(pubnub)
return pubnub, recorder
try:
yield build
finally:
for pubnub in clients:
await pubnub.stop()
# --- the timer is only registered when the flag is set ------------------
@pytest.mark.asyncio
async def test_subscribe_registers_no_heartbeat_timer_by_default(
async_client_factory,
):
pubnub, _ = async_client_factory()
assert pubnub.config.enable_presence_heartbeat is False
pubnub.subscribe().channels([CHANNEL]).execute()
await asyncio.sleep(SETTLE)
assert_subscribe_loop_alive(pubnub)
assert pubnub._subscription_manager._heartbeat_periodic_callback is None
@pytest.mark.asyncio
async def test_subscribe_registers_heartbeat_timer_when_enabled(
async_client_factory,
):
pubnub, _ = async_client_factory(enable_presence_heartbeat=True)
pubnub.subscribe().channels([CHANNEL]).execute()
await asyncio.sleep(SETTLE)
callback = pubnub._subscription_manager._heartbeat_periodic_callback
assert callback is not None
assert callback._running is True
@pytest.mark.asyncio
async def test_truthy_flag_registers_heartbeat_timer(async_client_factory):
"""Opting in with a truthy non-bool works.
`enable_presence_heartbeat` is a plain attribute with no validation
(`pubnub/pnconfiguration.py:35`), so a value parsed from the environment or
from JSON arrives as `1` or `"true"`. An identity check against `True` would
read those as opting out and disable the heartbeat with no way to tell from
the outside.
"""
pubnub, _ = async_client_factory()
pubnub.config.enable_presence_heartbeat = 1
pubnub.subscribe().channels([CHANNEL]).execute()
await wait_for_calls(1)
assert pubnub._subscription_manager._heartbeat_periodic_callback is not None
@pytest.mark.asyncio
async def test_reconnect_registers_no_heartbeat_timer_by_default(
async_client_factory,
):
"""`reconnect()` is reached from unsubscribe and set-state too."""
pubnub, _ = async_client_factory()
pubnub.subscribe().channels([CHANNEL]).execute()
await asyncio.sleep(SETTLE)
pubnub.reconnect()
await asyncio.sleep(SETTLE)
assert_subscribe_loop_alive(pubnub)
assert pubnub._subscription_manager._heartbeat_periodic_callback is None
# --- no presence transactions are issued --------------------------------
@pytest.mark.asyncio
async def test_no_heartbeat_requests_sent_by_default(async_client_factory):
pubnub, _ = async_client_factory()
pubnub.subscribe().channels([CHANNEL]).execute()
await asyncio.sleep(HEARTBEAT_WINDOW)
assert_subscribe_loop_alive(pubnub)
assert HeartbeatStub.calls == 0
@pytest.mark.asyncio
async def test_heartbeat_requests_sent_when_enabled(async_client_factory):
"""The flag still turns the heartbeat on."""
pubnub, _ = async_client_factory(enable_presence_heartbeat=True)
pubnub.subscribe().channels([CHANNEL]).execute()
await wait_for_calls(1)
assert HeartbeatStub.calls >= 1
def test_native_client_sends_no_heartbeat_requests_by_default(stub_endpoints):
"""The behaviour the asyncio manager is being brought in line with."""
pubnub = PubNub(make_config())
try:
pubnub.subscribe().channels([CHANNEL]).execute()
assert ParkedSubscribeStub.calls >= 1, "subscribe loop issued no request"
assert pubnub._subscription_manager._heartbeat_periodic_callback is None
assert HeartbeatStub.calls == 0
finally:
pubnub._subscription_manager.stop()
# --- no error statuses from a disabled subsystem ------------------------
@pytest.mark.asyncio
async def test_heartbeat_failure_not_announced_by_default(async_client_factory):
"""A heartbeat timeout reached listeners as `PNTimeoutCategory`.
With `heartbeat_notification_options` at its default of FAILURES, a failed
heartbeat is announced verbatim. In a steady-state subscribed session the
heartbeat is the only thing on the asyncio path that announces
`PNTimeoutCategory`: the subscribe loop restarts silently on its own long
poll timeouts. An application reading that as a lost connection acts on a
failure that never happened.
"""
HeartbeatStub.response = staticmethod(heartbeat_timeout)
pubnub, recorder = async_client_factory()
pubnub.subscribe().channels([CHANNEL]).execute()
await asyncio.sleep(HEARTBEAT_WINDOW)
assert_subscribe_loop_alive(pubnub)
assert recorder.categories == []
@pytest.mark.asyncio
async def test_heartbeat_failure_announced_when_enabled(async_client_factory):
"""Announcement is unchanged for an application that opted in."""
HeartbeatStub.response = staticmethod(heartbeat_timeout)
pubnub, recorder = async_client_factory(enable_presence_heartbeat=True)
assert (
pubnub.config.heartbeat_notification_options
is PNHeartbeatNotificationOptions.FAILURES
)
pubnub.subscribe().channels([CHANNEL]).execute()
await wait_for_calls(1)
assert PNStatusCategory.PNTimeoutCategory in recorder.categories
@pytest.mark.asyncio
async def test_heartbeat_success_not_announced_when_enabled(async_client_factory):
"""FAILURES verbosity stays silent on success, so one timeout looks like one event."""
pubnub, recorder = async_client_factory(enable_presence_heartbeat=True)
pubnub.subscribe().channels([CHANNEL]).execute()
await wait_for_calls(1)
assert HeartbeatStub.calls >= 1
assert recorder.categories == []
freenub-0.1.2/tests/test_subscription_status_announcement.py 0000664 0000000 0000000 00000051310 15257723452 0024630 0 ustar 00root root 0000000 0000000 """Connection status announcement across reconnect().
`SubscriptionManager._subscription_status_announced` is a one-shot latch that
guards the only place `PNConnectedCategory` is announced. `reconnect()` must
clear it, otherwise a caller that reconnects gets a working subscribe loop and
no further connection status for the lifetime of the manager.
`reconnect()` also doubles as the internal restart primitive for
`adapt_unsubscribe_builder` and `adapt_state_builder`. Those pass
`announce_status=False`, so a partial unsubscribe or a `set_state()` restarts
the loop without reporting a connection that never dropped.
Both managers are driven through a stubbed `Subscribe` endpoint so no network
traffic is involved.
"""
import asyncio
import pytest
import pytest_asyncio
import pubnub.pubnub as pubnub_native
import pubnub.pubnub_asyncio as pubnub_asyncio
from pubnub.callbacks import SubscribeCallback
from pubnub.dtos import UnsubscribeOperation
from pubnub.enums import PNStatusCategory
from pubnub.errors import PNERR_REQUEST_CANCELLED
from pubnub.exceptions import PubNubException
from pubnub.models.consumer.common import PNStatus
from pubnub.pnconfiguration import PNConfiguration
from pubnub.pubnub import PubNub
from pubnub.pubnub_asyncio import (
AsyncioEnvelope,
PubNubAsyncio,
PubNubAsyncioException,
)
CHANNEL = "test-channel"
OTHER_CHANNEL = "other-channel"
# Time for the asyncio subscribe loop to restart itself, where there is no
# response to key off. Every await in that path is on an already-resolved
# future. Response delivery is signalled rather than timed; see `async_client`.
SETTLE = 0.05
def make_config():
config = PNConfiguration()
config.subscribe_key = "test-sub-key"
config.publish_key = "test-pub-key"
config.uuid = "test-uuid"
# Keeps the leave request off the unsubscribe path; this suite never
# exercises the network.
config.suppress_leave_events = True
return config
def empty_subscribe_payload(timetoken=1000):
"""The shape `SubscribeEnvelope.from_json` expects, with no messages."""
return {"t": {"t": str(timetoken), "r": 1}, "m": []}
def success_status():
status = PNStatus()
status.error = False
status.category = PNStatusCategory.PNAcknowledgmentCategory
return status
def cancelled_status():
status = PNStatus()
status.error = True
status.category = PNStatusCategory.PNCancelledCategory
status.error_data = PubNubException(pn_error=PNERR_REQUEST_CANCELLED)
return status
class StatusRecorder(SubscribeCallback):
def __init__(self):
self.categories = []
def status(self, pubnub, status):
self.categories.append(status.category)
def message(self, pubnub, message):
pass
def presence(self, pubnub, presence):
pass
class AsyncioSubscribeStub:
"""Stands in for `Subscribe` on the asyncio path.
`future()` parks on a queue until a test hands it a response, which is what
a real long poll does. Cancellation is translated into a
`PNCancelledCategory` envelope the way `PubNubAsyncio.request_future` does.
"""
queue = None
request_count = 0
def __init__(self, pubnub):
self._pubnub = pubnub
def channels(self, channels):
return self
def channel_groups(self, groups):
return self
def timetoken(self, timetoken):
return self
def region(self, region):
return self
def filter_expression(self, expression):
return self
async def future(self):
type(self).request_count += 1
try:
return await type(self).queue.get()
except asyncio.CancelledError:
return PubNubAsyncioException(result=None, status=cancelled_status())
@pytest_asyncio.fixture
async def asyncio_subscribe(monkeypatch):
# Async so the queue is built inside the running loop. On Python 3.9
# `asyncio.Queue()` binds `get_event_loop()` at construction, and a queue
# built from a sync fixture belongs to a different loop than the test, so
# `queue.get()` awaits a foreign future and raises.
AsyncioSubscribeStub.queue = asyncio.Queue()
AsyncioSubscribeStub.request_count = 0
monkeypatch.setattr(pubnub_asyncio, "Subscribe", AsyncioSubscribeStub)
return AsyncioSubscribeStub
@pytest_asyncio.fixture
async def async_client(asyncio_subscribe):
pubnub = PubNubAsyncio(make_config())
recorder = StatusRecorder()
pubnub.add_listener(recorder)
# Every assertion in this suite is about what the manager did with a
# response, so the stub signals from the manager side: an event set when
# the subscribe stub picks a response off the queue would fire before
# `_handle_endpoint_call` had announced anything.
manager = pubnub._subscription_manager
handled = asyncio.Event()
handle_endpoint_call = manager._handle_endpoint_call
def signal_when_handled(raw_result, status):
handle_endpoint_call(raw_result, status)
handled.set()
manager._handle_endpoint_call = signal_when_handled
asyncio_subscribe.handled = handled
try:
yield pubnub, recorder, asyncio_subscribe
finally:
await pubnub.stop()
# `stop()` cancels only the newest loop task; let the superseded ones
# see their cancellation before the loop closes under them.
await asyncio.sleep(0)
async def deliver_subscribe_response(stub, timetoken=1000):
"""Hand the parked subscribe loop one successful response.
Returns once the manager has finished with it, so a missed wake-up fails as
a timeout rather than as a wrong-categories assertion.
"""
stub.handled.clear()
stub.queue.put_nowait(
AsyncioEnvelope(
result=empty_subscribe_payload(timetoken), status=success_status()
)
)
await asyncio.wait_for(stub.handled.wait(), timeout=2)
class NativeCallStub:
"""The handle `NativeSubscriptionManager._stop_subscribe_loop` inspects."""
def __init__(self):
self.is_executed = False
self.is_canceled = False
def cancel(self):
self.is_canceled = True
class NativeSubscribeStub:
"""Stands in for `Subscribe` on the native path.
`pn_async` is non-blocking on the real endpoint: it hands the request to a
worker and returns. The stub records the callback instead of firing it, so
a test decides when the long poll answers, on the calling thread.
"""
pending = None
request_count = 0
def __init__(self, pubnub):
self._pubnub = pubnub
def channels(self, channels):
return self
def channel_groups(self, groups):
return self
def timetoken(self, timetoken):
return self
def region(self, region):
return self
def filter_expression(self, expression):
return self
def pn_async(self, callback):
type(self).request_count += 1
call = NativeCallStub()
type(self).pending.append((call, callback))
return call
@pytest.fixture
def native_subscribe(monkeypatch):
NativeSubscribeStub.pending = []
NativeSubscribeStub.request_count = 0
monkeypatch.setattr(pubnub_native, "Subscribe", NativeSubscribeStub)
return NativeSubscribeStub
@pytest.fixture
def native_client(native_subscribe):
pubnub = PubNub(make_config())
recorder = StatusRecorder()
pubnub.add_listener(recorder)
try:
yield pubnub, recorder, native_subscribe
finally:
pubnub._subscription_manager.stop()
def deliver_native_response(stub, timetoken=1000):
"""Answer the outstanding subscribe call with a successful response."""
call, callback = stub.pending.pop()
# Anything still queued behind it was superseded by a restart of the loop.
stub.pending.clear()
call.is_executed = True
callback(empty_subscribe_payload(timetoken), success_status())
# --- asyncio manager ----------------------------------------------------
@pytest.mark.asyncio
async def test_subscribe_announces_connected_once(async_client):
pubnub, recorder, stub = async_client
pubnub.subscribe().channels([CHANNEL]).execute()
await deliver_subscribe_response(stub)
assert recorder.categories == [PNStatusCategory.PNConnectedCategory]
@pytest.mark.asyncio
async def test_connected_not_repeated_per_subscribe_response(async_client):
"""The latch still suppresses one announcement per long-poll iteration."""
pubnub, recorder, stub = async_client
pubnub.subscribe().channels([CHANNEL]).execute()
await deliver_subscribe_response(stub, timetoken=1000)
await deliver_subscribe_response(stub, timetoken=1001)
await deliver_subscribe_response(stub, timetoken=1002)
assert recorder.categories == [PNStatusCategory.PNConnectedCategory]
@pytest.mark.asyncio
async def test_reconnect_announces_connected_again(async_client):
pubnub, recorder, stub = async_client
pubnub.subscribe().channels([CHANNEL]).execute()
await deliver_subscribe_response(stub, timetoken=1000)
assert recorder.categories == [PNStatusCategory.PNConnectedCategory]
pubnub.reconnect()
await asyncio.sleep(SETTLE)
# Nothing is announced at the moment reconnect() is called; the status
# rides on the first successful subscribe response after it.
assert recorder.categories == [PNStatusCategory.PNConnectedCategory]
await deliver_subscribe_response(stub, timetoken=1001)
assert recorder.categories == [
PNStatusCategory.PNConnectedCategory,
PNStatusCategory.PNConnectedCategory,
]
@pytest.mark.asyncio
async def test_reconnect_announces_connected_once_not_per_iteration(async_client):
pubnub, recorder, stub = async_client
pubnub.subscribe().channels([CHANNEL]).execute()
await deliver_subscribe_response(stub, timetoken=1000)
pubnub.reconnect()
await asyncio.sleep(SETTLE)
await deliver_subscribe_response(stub, timetoken=1001)
await deliver_subscribe_response(stub, timetoken=1002)
await deliver_subscribe_response(stub, timetoken=1003)
assert recorder.categories == [
PNStatusCategory.PNConnectedCategory,
PNStatusCategory.PNConnectedCategory,
]
@pytest.mark.asyncio
async def test_disconnect_reconnect_cycle_announces_connected(async_client):
"""`disconnect()` / `reconnect()` is the cycle that produced no events."""
pubnub, recorder, stub = async_client
pubnub.subscribe().channels([CHANNEL]).execute()
await deliver_subscribe_response(stub, timetoken=1000)
pubnub._subscription_manager.disconnect()
await asyncio.sleep(SETTLE)
pubnub._subscription_manager.reconnect()
await asyncio.sleep(SETTLE)
await deliver_subscribe_response(stub, timetoken=1001)
assert recorder.categories == [
PNStatusCategory.PNConnectedCategory,
PNStatusCategory.PNConnectedCategory,
]
@pytest.mark.asyncio
async def test_on_reconnect_announces_reconnected_without_duplicate(async_client):
"""`on_reconnect` suppresses the connected status in favour of its own.
It sets the latch back to True straight after calling `reconnect()`, which
is dead code unless `reconnect()` clears it. With the reset in place that
assignment becomes load-bearing: the reconnection path must still announce
`PNReconnectedCategory` alone.
"""
pubnub, recorder, stub = async_client
pubnub.subscribe().channels([CHANNEL]).execute()
await deliver_subscribe_response(stub, timetoken=1000)
recorder.categories.clear()
pubnub._subscription_manager._reconnection_listener.on_reconnect()
await asyncio.sleep(SETTLE)
await deliver_subscribe_response(stub, timetoken=1001)
assert recorder.categories == [PNStatusCategory.PNReconnectedCategory]
@pytest.mark.asyncio
async def test_partial_unsubscribe_announces_nothing(async_client):
"""`adapt_unsubscribe_builder` restarts the loop without re-announcing.
Dropping one of two channels leaves a live subscription. The channels that
remain were already connected, so there is no new subscription to report.
"""
pubnub, recorder, stub = async_client
pubnub.subscribe().channels([CHANNEL, OTHER_CHANNEL]).execute()
await deliver_subscribe_response(stub, timetoken=1000)
recorder.categories.clear()
pubnub._subscription_manager.adapt_unsubscribe_builder(
UnsubscribeOperation(channels=[OTHER_CHANNEL], channel_groups=[])
)
await asyncio.sleep(SETTLE)
await deliver_subscribe_response(stub, timetoken=1001)
assert recorder.categories == []
@pytest.mark.asyncio
async def test_unsubscribe_all_then_subscribe_announces_connected(async_client):
"""A full unsubscribe drops the subscription, so the next one is new.
`adapt_unsubscribe_builder` passes `announce_status=False` and
`_start_subscribe_loop` returns early with no channels, so the latch is
left set. Only the `reconnect()` on the subscribe path clears it again.
"""
pubnub, recorder, stub = async_client
pubnub.subscribe().channels([CHANNEL]).execute()
await deliver_subscribe_response(stub, timetoken=1000)
pubnub.unsubscribe_all()
await asyncio.sleep(SETTLE)
recorder.categories.clear()
pubnub.subscribe().channels([OTHER_CHANNEL]).execute()
await deliver_subscribe_response(stub, timetoken=1001)
assert recorder.categories == [PNStatusCategory.PNConnectedCategory]
@pytest.mark.asyncio
async def test_set_state_announces_nothing(async_client):
"""`SetState.custom_params` calls `adapt_state_builder` on every request.
Writing presence state is not a connection event, so the restart it
triggers must not re-announce.
"""
pubnub, recorder, stub = async_client
pubnub.subscribe().channels([CHANNEL]).execute()
await deliver_subscribe_response(stub, timetoken=1000)
recorder.categories.clear()
pubnub.set_state().channels([CHANNEL]).state({"mood": "ok"}).custom_params()
await asyncio.sleep(SETTLE)
await deliver_subscribe_response(stub, timetoken=1001)
assert recorder.categories == []
@pytest.mark.asyncio
async def test_reconnect_restarts_the_subscribe_loop(async_client):
"""The loop the status is meant to describe really does restart."""
pubnub, recorder, stub = async_client
pubnub.subscribe().channels([CHANNEL]).execute()
await deliver_subscribe_response(stub, timetoken=1000)
before = stub.request_count
pubnub.reconnect()
await asyncio.sleep(SETTLE)
assert stub.request_count > before
# --- native manager -----------------------------------------------------
def test_native_subscribe_announces_connected_once(native_client):
pubnub, recorder, stub = native_client
pubnub.subscribe().channels([CHANNEL]).execute()
deliver_native_response(stub)
assert recorder.categories == [PNStatusCategory.PNConnectedCategory]
def test_native_connected_not_repeated_per_subscribe_response(native_client):
pubnub, recorder, stub = native_client
pubnub.subscribe().channels([CHANNEL]).execute()
deliver_native_response(stub, timetoken=1000)
deliver_native_response(stub, timetoken=1001)
deliver_native_response(stub, timetoken=1002)
assert recorder.categories == [PNStatusCategory.PNConnectedCategory]
def test_native_reconnect_announces_connected_again(native_client):
pubnub, recorder, stub = native_client
pubnub.subscribe().channels([CHANNEL]).execute()
deliver_native_response(stub, timetoken=1000)
assert recorder.categories == [PNStatusCategory.PNConnectedCategory]
pubnub.reconnect()
assert recorder.categories == [PNStatusCategory.PNConnectedCategory]
deliver_native_response(stub, timetoken=1001)
assert recorder.categories == [
PNStatusCategory.PNConnectedCategory,
PNStatusCategory.PNConnectedCategory,
]
def test_native_reconnect_announces_connected_once_not_per_iteration(native_client):
pubnub, recorder, stub = native_client
pubnub.subscribe().channels([CHANNEL]).execute()
deliver_native_response(stub, timetoken=1000)
pubnub.reconnect()
deliver_native_response(stub, timetoken=1001)
deliver_native_response(stub, timetoken=1002)
deliver_native_response(stub, timetoken=1003)
assert recorder.categories == [
PNStatusCategory.PNConnectedCategory,
PNStatusCategory.PNConnectedCategory,
]
def test_native_disconnect_reconnect_cycle_announces_connected(native_client):
pubnub, recorder, stub = native_client
pubnub.subscribe().channels([CHANNEL]).execute()
deliver_native_response(stub, timetoken=1000)
pubnub._subscription_manager.disconnect()
pubnub._subscription_manager.reconnect()
deliver_native_response(stub, timetoken=1001)
assert recorder.categories == [
PNStatusCategory.PNConnectedCategory,
PNStatusCategory.PNConnectedCategory,
]
def test_native_on_reconnect_announces_reconnected_without_duplicate(native_client):
pubnub, recorder, stub = native_client
pubnub.subscribe().channels([CHANNEL]).execute()
deliver_native_response(stub, timetoken=1000)
recorder.categories.clear()
pubnub._subscription_manager._reconnection_listener.on_reconnect()
deliver_native_response(stub, timetoken=1001)
assert recorder.categories == [PNStatusCategory.PNReconnectedCategory]
class SynchronousNativeSubscribeStub(NativeSubscribeStub):
"""Answers one subscribe call on the calling thread.
The real handler answers on a worker thread
(`request_handlers/requests_handler.py:113`), so a response can land while
the caller of `reconnect()` is still running. This stub makes that ordering
reachable without depending on thread timing.
"""
answer_synchronously = False
def pn_async(self, callback):
if not type(self).answer_synchronously:
return super().pn_async(callback)
type(self).answer_synchronously = False
type(self).request_count += 1
call = NativeCallStub()
call.is_executed = True
callback(empty_subscribe_payload(), success_status())
return call
@pytest.fixture
def synchronous_native_client(monkeypatch):
SynchronousNativeSubscribeStub.pending = []
SynchronousNativeSubscribeStub.request_count = 0
SynchronousNativeSubscribeStub.answer_synchronously = False
monkeypatch.setattr(pubnub_native, "Subscribe", SynchronousNativeSubscribeStub)
pubnub = PubNub(make_config())
recorder = StatusRecorder()
pubnub.add_listener(recorder)
try:
yield pubnub, recorder, SynchronousNativeSubscribeStub
finally:
pubnub._subscription_manager.stop()
def test_native_on_reconnect_response_inside_the_window(synchronous_native_client):
"""`on_reconnect` must not arm the announcement it is about to suppress.
It reports PNReconnectedCategory and sets the latch itself. If the restart
cleared the latch first, a subscribe response arriving before that
assignment would announce PNConnectedCategory as well.
"""
pubnub, recorder, stub = synchronous_native_client
pubnub.subscribe().channels([CHANNEL]).execute()
deliver_native_response(stub, timetoken=1000)
recorder.categories.clear()
stub.answer_synchronously = True
pubnub._subscription_manager._reconnection_listener.on_reconnect()
assert recorder.categories == [PNStatusCategory.PNReconnectedCategory]
def test_native_partial_unsubscribe_announces_nothing(native_client):
pubnub, recorder, stub = native_client
pubnub.subscribe().channels([CHANNEL, OTHER_CHANNEL]).execute()
deliver_native_response(stub, timetoken=1000)
recorder.categories.clear()
pubnub._subscription_manager.adapt_unsubscribe_builder(
UnsubscribeOperation(channels=[OTHER_CHANNEL], channel_groups=[])
)
deliver_native_response(stub, timetoken=1001)
assert recorder.categories == []
def test_native_unsubscribe_all_then_subscribe_announces_connected(native_client):
pubnub, recorder, stub = native_client
pubnub.subscribe().channels([CHANNEL]).execute()
deliver_native_response(stub, timetoken=1000)
pubnub.unsubscribe_all()
recorder.categories.clear()
pubnub.subscribe().channels([OTHER_CHANNEL]).execute()
deliver_native_response(stub, timetoken=1001)
assert recorder.categories == [PNStatusCategory.PNConnectedCategory]
def test_native_set_state_announces_nothing(native_client):
pubnub, recorder, stub = native_client
pubnub.subscribe().channels([CHANNEL]).execute()
deliver_native_response(stub, timetoken=1000)
recorder.categories.clear()
pubnub.set_state().channels([CHANNEL]).state({"mood": "ok"}).custom_params()
deliver_native_response(stub, timetoken=1001)
assert recorder.categories == []
def test_native_reconnect_restarts_the_subscribe_loop(native_client):
pubnub, recorder, stub = native_client
pubnub.subscribe().channels([CHANNEL]).execute()
deliver_native_response(stub, timetoken=1000)
before = stub.request_count
pubnub.reconnect()
assert stub.request_count > before