diff --git a/.appveyor.yml b/.appveyor.yml deleted file mode 100644 index 4c2a756b4..000000000 --- a/.appveyor.yml +++ /dev/null @@ -1,40 +0,0 @@ -# credit: https://packaging.python.org/guides/supporting-windows-using-appveyor/ - -environment: - matrix: - - PYTHON: "C:\\Python27" - PYTHON_VERSION: "py27-x86" - - PYTHON: "C:\\Python33" - PYTHON_VERSION: "py33-x86" - - PYTHON: "C:\\Python34" - PYTHON_VERSION: "py34-x86" - - PYTHON: "C:\\Python35" - PYTHON_VERSION: "py35-x86" - - PYTHON: "C:\\Python36" - PYTHON_VERSION: "py36-x86" - - PYTHON: "C:\\Python27-x64" - PYTHON_VERSION: "py27-x64" - - PYTHON: "C:\\Python33-x64" - PYTHON_VERSION: "py33-x64" - - PYTHON: "C:\\Python34-x64" - PYTHON_VERSION: "py34-x64" - - PYTHON: "C:\\Python35-x64" - PYTHON_VERSION: "py35-x64" - - PYTHON: "C:\\Python36-x64" - PYTHON_VERSION: "py36-x64" - -install: - - "%PYTHON%\\python.exe -m pip install wheel" - - "%PYTHON%\\python.exe -m pip install -r requirements.txt" - - "%PYTHON%\\python.exe -m pip install flake8" - - "%PYTHON%\\python.exe -m pip install -r test_requirements.txt" - -build: off - -test_script: - - "%PYTHON%\\python.exe -m flake8 slackclient" - - "%PYTHON%\\python.exe -m pytest --cov-report= --cov=slackclient tests" - -# maybe `after_test:`? -on_success: - - "%PYTHON%\\python.exe -m codecov -e win-%PYTHON_VERSION%" \ No newline at end of file diff --git a/.coveragerc b/.coveragerc deleted file mode 100644 index 8d395b7e3..000000000 --- a/.coveragerc +++ /dev/null @@ -1,13 +0,0 @@ -[run] -branch = True -source = slackclient - -[report] -exclude_lines = - if self.debug: - pragma: no cover - raise NotImplementedError - if __name__ == .__main__.: -ignore_errors = True -omit = - tests/* \ No newline at end of file diff --git a/.flake8 b/.flake8 index 51b50a046..308e748f4 100644 --- a/.flake8 +++ b/.flake8 @@ -1,2 +1,2 @@ [flake8] -max-line-length = 100 \ No newline at end of file +max-line-length = 125 \ No newline at end of file diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 000000000..d6547ce30 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,2 @@ +# change black settings +108955b601e768fd56696be903fc8b471c73ebf7 diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 000000000..4a08579c2 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,12 @@ +# Salesforce Open Source project configuration +# Learn more: https://github.com/salesforce/oss-template +#ECCN:Open Source +#GUSINFO:Open Source,Open Source Workflow + +# @slackapi/slack-platform-python +# are code reviewers for all changes in this repo. +* @slackapi/slack-platform-python + +# @slackapi/developer-education +# are code reviewers for changes in the `/docs` directory. +/docs/ @slackapi/developer-education diff --git a/.github/ISSUE_TEMPLATE/01_question.md b/.github/ISSUE_TEMPLATE/01_question.md new file mode 100644 index 000000000..66971561d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/01_question.md @@ -0,0 +1,51 @@ +--- +name: SDK Question +about: Submit a question about this SDK +title: (Set a clear title describing your question) +labels: "untriaged" +assignees: "" +--- + +(Describe your issue and goal here) + +### Reproducible in: + +```bash +pip freeze | grep slack +python --version +sw_vers && uname -v # or `ver` +``` + +#### The Slack SDK version + +(Paste the output of `pip freeze | grep slack`) + +#### Python runtime version + +(Paste the output of `python --version`) + +#### OS info + +(Paste the output of `sw_vers && uname -v` on macOS/Linux or `ver` on Windows OS) + +#### Steps to reproduce: + +(Share the commands to run, source code, and project settings (e.g., pyproject.toml)) + +1. +2. +3. + +### Expected result: + +(Tell what you expected to happen) + +### Actual result: + +(Tell what actually happened with logs, screenshots) + +### Requirements + +For general questions/issues about Slack API platform or its server-side, could you submit questions at https://my.slack.com/help/requests/new instead. :bow: + +Please read the [Contributing guidelines](https://github.com/slackapi/python-slack-sdk/blob/main/.github/contributing.md) and [Code of Conduct](https://slackhq.github.io/code-of-conduct) before creating this issue or pull request. By submitting, you are agreeing to those rules. diff --git a/.github/ISSUE_TEMPLATE/02_enhancement.md b/.github/ISSUE_TEMPLATE/02_enhancement.md new file mode 100644 index 000000000..33c349f68 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/02_enhancement.md @@ -0,0 +1,25 @@ +--- +name: SDK Enhancement / Feature Request +about: Submit an enhancement/feature request +title: (Set a clear title describing your idea) +labels: "untriaged" +assignees: "" +--- + +(Describe your issue and goal here) + +### Category (place an `x` in each of the `[ ]`) + +- [ ] **slack_sdk.web.WebClient (sync/async)** (Web API client) +- [ ] **slack_sdk.webhook.WebhookClient (sync/async)** (Incoming Webhook, response_url sender) +- [ ] **slack_sdk.models** (UI component builders) +- [ ] **slack_sdk.oauth** (OAuth Flow Utilities) +- [ ] **slack_sdk.socket_mode** (Socket Mode client) +- [ ] **slack_sdk.audit_logs** (Audit Logs API client) +- [ ] **slack_sdk.scim** (SCIM API client) +- [ ] **slack_sdk.rtm** (RTM client) +- [ ] **slack_sdk.signature** (Request Signature Verifier) + +### Requirements + +Please read the [Contributing guidelines](https://github.com/slackapi/python-slack-sdk/blob/main/.github/contributing.md) and [Code of Conduct](https://slackhq.github.io/code-of-conduct) before creating this issue or pull request. By submitting, you are agreeing to those rules. diff --git a/.github/ISSUE_TEMPLATE/03_document.md b/.github/ISSUE_TEMPLATE/03_document.md new file mode 100644 index 000000000..822bc2ee4 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/03_document.md @@ -0,0 +1,17 @@ +--- +name: SDK Document +about: Submit an issue on documents +title: (Set a clear title describing your idea) +labels: "untriaged" +assignees: "" +--- + +(Describe your issue and goal here) + +### The page URLs + +- https://docs.slack.dev/tools/python-slack-sdk/ + +### Requirements + +Please read the [Contributing guidelines](https://github.com/slackapi/python-slack-sdk/blob/main/.github/contributing.md) and [Code of Conduct](https://slackhq.github.io/code-of-conduct) before creating this issue or pull request. By submitting, you are agreeing to those rules. diff --git a/.github/ISSUE_TEMPLATE/04_bug.md b/.github/ISSUE_TEMPLATE/04_bug.md new file mode 100644 index 000000000..fd1fc5718 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/04_bug.md @@ -0,0 +1,51 @@ +--- +name: SDK Bug +about: Report the SDK bug +title: (Set a clear title describing the issue) +labels: "untriaged" +assignees: "" +--- + +(Filling out the following details about bugs will help us solve your issue sooner.) + +### Reproducible in: + +```bash +pip freeze | grep slack +python --version +sw_vers && uname -v # or `ver` +``` + +#### The Slack SDK version + +(Paste the output of `pip freeze | grep slack`) + +#### Python runtime version + +(Paste the output of `python --version`) + +#### OS info + +(Paste the output of `sw_vers && uname -v` on macOS/Linux or `ver` on Windows OS) + +#### Steps to reproduce: + +(Share the commands to run, source code, and project settings (e.g., pyproject.toml)) + +1. +2. +3. + +### Expected result: + +(Tell what you expected to happen) + +### Actual result: + +(Tell what actually happened with logs, screenshots) + +### Requirements + +For general questions/issues about Slack API platform or its server-side, could you submit questions at https://my.slack.com/help/requests/new instead. :bow: + +Please read the [Contributing guidelines](https://github.com/slackapi/python-slack-sdk/blob/main/.github/contributing.md) and [Code of Conduct](https://slackhq.github.io/code-of-conduct) before creating this issue or pull request. By submitting, you are agreeing to those rules. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 000000000..1925abae7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,7 @@ +blank_issues_enabled: false +contact_links: + - name: Slack Platform Customer Support + url: https://my.slack.com/help/requests/new + about: | + This issue tracker is a place to track bugs, feature requests, and questions on this SDK side. + If you have a general question on how to use the Slack platform, please get in touch with our customer support agents first via either /feedback in your Slack workspace or the help page link here. diff --git a/.github/contributing.md b/.github/contributing.md index 614140b03..f15e0c46a 100644 --- a/.github/contributing.md +++ b/.github/contributing.md @@ -7,26 +7,29 @@ well. There are many ways you can contribute! :heart: ### Bug Reports and Fixes :bug: -- If you find a bug, please search for it in the [Issues](https://github.com/slackapi/python-slackclient/issues), and if it isn't already tracked, - [create a new issue](https://github.com/slackapi/python-slackclient/issues/new). Fill out the "Bug Report" section of the issue template. Even if an Issue is closed, feel free to comment and add details, it will still - be reviewed. -- Issues that have already been identified as a bug (note: able to reproduce) will be labelled `bug`. -- If you'd like to submit a fix for a bug, [send a Pull Request](#creating_a_pull_request) and mention the Issue number. - - Include tests that isolate the bug and verifies that it was fixed. + +- If you find a bug, please search for it in the [Issues](https://github.com/slackapi/python-slack-sdk/issues), and if it isn't already tracked, + [create a new issue](https://github.com/slackapi/python-slack-sdk/issues/new). Fill out the "Bug Report" section of the issue template. Even if an Issue is closed, feel free to comment and add details, it will still + be reviewed. +- Issues that have already been identified as a bug (note: able to reproduce) will be labelled `bug`. +- If you'd like to submit a fix for a bug, [send a Pull Request](#creating_a_pull_request) and mention the Issue number. +- Include tests that isolate the bug and verifies that it was fixed. ### New Features :bulb: -- If you'd like to add new functionality to this project, describe the problem you want to solve in a [new Issue](https://github.com/slackapi/python-slackclient/issues/new). -- Issues that have been identified as a feature request will be labelled `enhancement`. -- If you'd like to implement the new feature, please wait for feedback from the project - maintainers before spending too much time writing the code. In some cases, `enhancement`s may - not align well with the project objectives at the time. + +- If you'd like to add new functionality to this project, describe the problem you want to solve in a [new Issue](https://github.com/slackapi/python-slack-sdk/issues/new). +- Issues that have been identified as a feature request will be labelled `enhancement`. +- If you'd like to implement the new feature, please wait for feedback from the project + maintainers before spending too much time writing the code. In some cases, `enhancement`s may + not align well with the project objectives at the time. ### Tests :mag:, Documentation :books:, Miscellaneous :sparkles: -- If you'd like to improve the tests, you want to make the documentation clearer, you have an - alternative implementation of something that may have advantages over the way its currently - done, or you have any other change, we would be happy to hear about it! - - If its a trivial change, go ahead and [send a Pull Request](#creating_a_pull_request) with the changes you have in mind. - - If not, [open an Issue](https://github.com/slackapi/python-slackclient/issues/new) to discuss the idea first. + +- If you'd like to improve the tests, you want to make the documentation clearer, you have an + alternative implementation of something that may have advantages over the way its currently + done, or you have any other change, we would be happy to hear about it! +- If its a trivial change, go ahead and [send a Pull Request](#creating_a_pull_request) with the changes you have in mind. +- If not, [open an Issue](https://github.com/slackapi/python-slack-sdk/issues/new) to discuss the idea first. If you're new to our project and looking for some way to make your first contribution, look for Issues labelled `good first contribution`. @@ -35,8 +38,8 @@ Issues labelled `good first contribution`. For your contribution to be accepted: -- [x] You must have signed the [Contributor License Agreement (CLA)](https://cla-assistant.io/slackapi/python-slackclient). -- [x] The test suite must be complete and pass. +- [x] You must have signed the [Contributor License Agreement (CLA)](https://cla.salesforce.com/sign-cla). +- [x] The test suite must be complete and pass (see the [Maintainer's Guide](./maintainers_guide.md) for details on how to run the tests). - [x] The changes must be approved by code review. - [x] Commits should be atomic and messages must be descriptive. Related issues should be mentioned by Issue number. @@ -52,9 +55,9 @@ If the contribution doesn't meet the above criteria, you may fail our automated 3. :herb: Create a new branch and check it out. 4. :crystal_ball: Make your changes and commit them locally. Magic happens here! 5. :arrow_heading_up: Push your new branch to your fork. (e.g. `git push username fix-issue-16`). -6. :inbox_tray: Open a Pull Request on github.com from your new branch on your fork to `master` in this +6. :inbox_tray: Open a Pull Request on github.com from your new branch on your fork to `main` in this repository. ## Maintainers -There are more details about processes and workflow in the [Maintainer's Guide](./maintainers_guide.md). \ No newline at end of file +There are more details about processes and workflow in the [Maintainer's Guide](./maintainers_guide.md). diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..ac86badc1 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,16 @@ +# 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: "pip" + directory: "/" + schedule: + interval: "monthly" + open-pull-requests-limit: 5 + ignore: + - dependency-name: "black" + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "monthly" diff --git a/.github/issue_template.md b/.github/issue_template.md index a39638a65..33b393830 100644 --- a/.github/issue_template.md +++ b/.github/issue_template.md @@ -1,48 +1,43 @@ -### Description +(Describe your issue and goal here) -Describe your issue here. +### Reproducible in: -### What type of issue is this? (place an `x` in one of the `[ ]`) -- [ ] bug -- [ ] enhancement (feature request) -- [ ] question -- [ ] documentation related -- [ ] testing related -- [ ] discussion +```bash +pip freeze | grep slack +python --version +sw_vers && uname -v # or `ver` +``` -### Requirements (place an `x` in each of the `[ ]`) -* [ ] I've read and understood the [Contributing guidelines](https://github.com/slackapi/python-slackclient/blob/master/.github/contributing.md) and have done my best effort to follow them. -* [ ] I've read and agree to the [Code of Conduct](https://slackhq.github.io/code-of-conduct). -* [ ] I've searched for any related issues and avoided creating a duplicate issue. +#### The Slack SDK version ---- +(Paste the output of `pip freeze | grep slack`) -### Bug Report +#### Python runtime version -Filling out the following details about bugs will help us solve your issue sooner. +(Paste the output of `python --version`) -#### Reproducible in: +#### OS info -slackclient version: - -python version: - -OS version(s): +(Paste the output of `sw_vers && uname -v` on macOS/Linux or `ver` on Windows OS) #### Steps to reproduce: +(Share the commands to run, source code, and project settings (e.g., pyproject.toml)) + 1. 2. 3. -#### Expected result: +### Expected result: + +(Tell what you expected to happen) -What you expected to happen +### Actual result: -#### Actual result: +(Tell what actually happened with logs, screenshots) -What actually happened +### Requirements -#### Attachments: +For general questions/issues about Slack API platform or its server-side, could you submit questions at https://my.slack.com/help/requests/new instead. :bow: -Logs, screenshots, screencast, sample project, funny gif, etc. \ No newline at end of file +Please read the [Contributing guidelines](https://github.com/slackapi/python-slack-sdk/blob/main/.github/contributing.md) and [Code of Conduct](https://slackhq.github.io/code-of-conduct) before creating this issue or pull request. By submitting, you are agreeing to those rules. diff --git a/.github/maintainers_guide.md b/.github/maintainers_guide.md index c1f6a2223..ccda1607f 100644 --- a/.github/maintainers_guide.md +++ b/.github/maintainers_guide.md @@ -8,65 +8,249 @@ this project. If you use this package within your own software as is but don't p ### Python (and friends) -Not surprisingly, you will need to have Python installed on your system to work on this package. We support non-EOL, -stable versions of CPython. The current supported versions are listed in the CI configurations (e.g. `.travis.yml`). -At a minimum, you should have the latest version of Python 2 and the latest version of Python 3 to develop against. -It's tricky to set up a system that has more than that, so you can lean on the CI servers to test changes on the -in-between versions for you. +We recommend using [pyenv](https://github.com/pyenv/pyenv) for Python runtime management. If you use macOS, follow the following steps: -You should also make sure you have the latest versions of `pip`, `setuptools`, `virtualenv`, `wheel`, `twine` and -[`tox`](https://tox.readthedocs.io/en/latest/) installed with your version of Python. +```sh +brew update +brew install pyenv +``` -On macOS, the easiest way to install these tools is by using [Homebrew](https://brew.sh/) and installing the `python` -and `python3` packages. Some of the above packages are preinstalled and you can install the remaining on your own: -`pip install virtualenv wheel twine tox && pip3 install virtualenv twine tox`. +You can hook `pyenv` into your shell automatically by running `pyenv init` and following the instructions. + +Install necessary Python runtimes for development/testing. It is not necessary +to install all the various Python versions we test in [continuous integration on +GitHub Actions](https://github.com/slackapi/python-slack-sdk/blob/main/.github/workflows/tests.yml), +but make sure you are running at least one version that we execute our tests in +locally so that you can run the tests yourself. + +```sh +$ pyenv install -l | grep -v "-e[conda|stackless|pypy]" + +$ pyenv install 3.9.6 # select the latest patch version +$ pyenv local 3.9.6 + +$ pyenv versions + system + 3.6.10 + 3.7.7 +* 3.9.6 (set by /path-to-python-slack-sdk/.python-version) + +$ pyenv rehash +``` + +Then, you can create a new [Virtual Environment](https://docs.python.org/3/tutorial/venv.html) specific to the Python version you just installed by running: + +```sh +python -m venv env_3.9.6 +source env_3.9.6/bin/activate +``` + +At this point you have a clean, Python-version-specific environment "activated" for +use just for this project. All `python` and `pip` commands run in your shell +from this point on run in the context of this virtual environment. You can +deactivate the virtual environment by running `deactivate`; it is recommended to +do so after you are done working in this project. To come back to development +work for this project again in the future, `cd` into this project directory and +run `source env_3.9.6/bin/activate` again. + +The last step is to install this project's dependencies and run all unit tests; to do so, you can run + +```sh +./scripts/run_validation.sh +``` + +Also check out [how +we configure GitHub Actions to install dependencies for this project for use in +our continuous integration](https://github.com/slackapi/python-slack-sdk/blob/v3.17.0/.github/workflows/ci-build.yml#L28-L32). ## Tasks +### Formatting + +This project uses code formatting tools to maintain consistent style. You can format the codebase by running: + +```sh +./scripts/format.sh +``` + ### Testing -Tox is used to run the test suite across multiple isolated versions of Python. It is configured in `tox.ini` to -run all the supported versions of Python, but when you invoke it, you should only select the versions you have on your -system. For example, on a system with Python 2.7.13 and Python 3.6.1, you would run the tests using the following -command: `tox -e flake8,py27,py36` (flake8 is a quality analysis tool). +#### Unit Tests + +When you make changes to this SDK, please write unit tests verifying if the changes work as you expected. You can easily run all the tests and formatting/linter with the below scripts. + +Run all the unit tests, code linter, and code analyzer: + +```sh +./scripts/run_validation.sh +``` + +Run all the unit tests (no linter nor code analyzer): + +```sh +./scripts/run_unit_tests.sh +``` + +Run a specific unit test: + +```sh +./scripts/run_unit_tests.sh tests/web/test_web_client.py +``` + +You can rely on GitHub Actions builds for running the tests on a variety of Python runtimes. + +#### Integration Tests with Real Slack APIs + +This project also has integration tests that verify the SDK works with the Slack API platform. As a preparation, you need to set [the required env variables](https://github.com/slackapi/python-slack-sdk/blob/main/integration_tests/env_variable_names.py) properly. You don't need to setup all of them if you just want to run some of the tests. Commonly, `SLACK_SDK_TEST_BOT_TOKEN` and `SLACK_SDK_TEST_USER_TOKEN` are used for running `WebClient` tests. + +Run all integration tests: + +```sh +./scripts/run_integration_tests.sh +``` + +Run a specific integration test: + +```sh +./scripts/run_integration_tests.sh integration_tests/web/test_async_web_client.py +``` + +#### Develop Locally + +If you want to test the package locally you can. + +1. Build the package locally + - Run + ```sh + scripts/build_pypi_package.sh + ``` + - This will create a `.whl` file in the `./dist` folder +2. Use the built package + - Example `/dist/slack_sdk-1.2.3-py2.py3-none-any.whl` was created + - From anywhere on your machine you can install this package to a project with + ```sh + pip install /dist/slack_sdk-1.2.3-py2.py3-none-any.whl + ``` + - It is also possible to include `slack_sdk @ file:////dist/slack_sdk-1.2.3-py2.py3-none-any.whl` in a [requirements.txt](https://pip.pypa.io/en/stable/user_guide/#requirements-files) file ### Generating Documentation -The documentation is generated from the source and templates in the `docs-src` directory. The generated documentation -gets committed to the repo in `docs` and also published to a GitHub Pages website. +See [`/docs/README`](https://github.com/slackapi/python-slack-sdk/blob/main/docs/README.md) for information on editing documentation pages. + +The API reference is generated from a script. You can generate and preview the **API _reference_ documents for `slack_sdk` package modules** by running: -You can generate the documentation by running `tox -e docs`. +```sh +./scripts/generate_api_docs.sh +``` ### Releasing -1. Create the commit for the release: - * Bump the version number in adherence to [Semantic Versioning](http://semver.org/) in `slackclient/version.py`. - * Commit with a message including the new version number. For example `1.0.6`. +#### test.pypi.org deployment + +[TestPyPI](https://test.pypi.org/) is a separate instance of the Python Package +Index that allows you to try distribution tools and processes without affecting +the real index. This is particularly useful when making changes related to the +package configuration itself, for example, modifications to the `pyproject.toml` file. + +You can deploy this project to TestPyPI using GitHub Actions. + +To deploy using GitHub Actions: + +1. Push your changes to a branch or tag +2. Navigate to +3. Click on "Run workflow" +4. Select your branch or tag from the dropdown +5. Click "Run workflow" to build and deploy your branch to TestPyPI + +Alternatively, you can deploy from your local machine with: + +```sh +./scripts/deploy_to_test_pypi.sh +``` + +#### Development Deployment + +Deploying a new version of this library to PyPI is triggered by publishing a GitHub Release. +Before creating a new release, ensure that everything on a stable branch has +landed, then [run the tests](#unit-tests). -2. Distribute the release - * Build the distribtuions: `python setup.py sdist bdist_wheel`. This will create artifacts in the `dist` directory. - * Publish to PyPI: `twine upload dist/*`. You must have access to the credentials to publish. - * Create a GitHub Release. You will select the commit with updated version number (e.g. `1.0.6`) to assiociate with - the tag, and name the tag after this version (e.g. `1.0.6`). This will also serve as a Changelog for the project. - Add a description of changes to the Release. Mention Issue and PR #'s and @-mention contributors. +1. Create the commit for the release + 1. Use the latest supported Python version. Using a [virtual environment](#python-and-friends) is recommended. + 2. In `slack_sdk/version.py` bump the version number in adherence to [Semantic Versioning](http://semver.org/) and [Developmental Release](https://peps.python.org/pep-0440/#developmental-releases). + - Example: if the current version is `1.2.3`, a proper development bump would be `1.2.4.dev0` + - `.dev` will indicate to pip that this is a [Development Release](https://peps.python.org/pep-0440/#developmental-releases) + - Note that the `dev` version can be bumped in development releases: `1.2.4.dev0` -> `1.2.4.dev1` + 3. Build the docs with `./scripts/generate_api_docs.sh`. + 4. Commit with a message including the new version number. For example `1.2.4.dev0` & push the commit to a branch where the development release will live (create it if it does not exist) + 1. `git checkout -b future-release` + 2. `git add --all` (review files with `git status` before committing) + 3. `git commit -m 'chore(release): version 1.2.4.dev0'` + 4. `git push -u origin future-release` +2. Create a new GitHub Release + 1. Navigate to the [Releases page](https://github.com/slackapi/python-slack-sdk/releases). + 2. Click the "Draft a new release" button. + 3. Set the "Target" to the feature branch with the development changes. + 4. Click "Tag: Select tag" + 5. Input a new tag name manually. The tag name must match the version in `slack_sdk/version.py` prefixed with "v" (e.g., if version is `1.2.4.dev0`, enter `v1.2.4.dev0`) + 6. Click the "Create a new tag" button. This won't create your tag immediately. + 7. Click the "Generate release notes" button. + 8. The release name should match the tag name! + 9. Edit the resulting notes to ensure they have decent messaging that is understandable by non-contributors, but each commit should still have its own line. + 10. Set this release as a pre-release. + 11. Publish the release by clicking the "Publish release" button! +3. Navigate to the [release workflow run](https://github.com/slackapi/python-slack-sdk/actions/workflows/pypi-release.yml). You will need to approve the deployment! +4. After a few minutes, the corresponding version will be available on . +5. (Slack Internal) Communicate the release internally -3. (Slack Internal) Communicate the release internally. Include a link to the GitHub Release. +#### Production Deployment -4. Announce on Slack Team dev4slack in #slack-api +Deploying a new version of this library to PyPI is triggered by publishing a GitHub Release. +Before creating a new release, ensure that everything on the `main` branch since +the last tag is in a releasable state! At a minimum, [run the tests](#unit-tests). -5. (Slack Internal) Tweet? Not necessary for patch updates, might be needed for minor updates, definitely needed for - major updates. Include a link to the GitHub Release. +1. Create the commit for the release + 1. Use the latest supported Python version. Using a [virtual environment](#python-and-friends) is recommended. + 2. In `slack_sdk/version.py` bump the version number in adherence to [Semantic Versioning](http://semver.org/) and the [Versioning](#versioning-and-tags) section. + 3. Build the docs with `./scripts/generate_api_docs.sh`. + 4. Commit with a message including the new version number. For example `1.2.3` & push the commit to a branch and create a PR to sanity check. + 1. `git checkout -b 1.2.3-release` + 2. `git add --all` (review files with `git status` before committing) + 3. `git commit -m 'chore(release): version 1.2.3'` + 4. `git push -u origin 1.2.3-release` + 5. Add relevant labels to the PR and add the PR to a GitHub Milestone. + 6. Merge in release PR after getting an approval from at least one maintainer. +2. Create a new GitHub Release + 1. Navigate to the [Releases page](https://github.com/slackapi/python-slack-sdk/releases). + 2. Click the "Draft a new release" button. + 3. Set the "Target" to the `main` branch. + 4. Click "Tag: Select tag" + 5. Input a new tag name manually. The tag name must match the version in `slack_sdk/version.py` prefixed with "v" (e.g., if version is `1.2.3`, enter `v1.2.3`) + 6. Click the "Create a new tag" button. This won't create your tag immediately. + 7. Click the "Generate release notes" button. + 8. The release name should match the tag name! + 9. Edit the resulting notes to ensure they have decent messaging that is understandable by non-contributors, but each commit should still have its own line. + 10. Include a link to the current GitHub Milestone. + 11. Ensure the "latest release" checkbox is checked to mark this as the latest stable release. + 12. Publish the release by clicking the "Publish release" button! +3. Navigate to the [release workflow run](https://github.com/slackapi/python-slack-sdk/actions/workflows/pypi-release.yml). You will need to approve the deployment! +4. After a few minutes, the corresponding version will be available on . +5. Close the current GitHub Milestone and create one for the next patch version. +6. (Slack Internal) Communicate the release internally + - Include a link to the GitHub release +7. (Slack Internal) Tweet by @SlackAPI + - Not necessary for patch updates, might be needed for minor updates, + definitely needed for major updates. Include a link to the GitHub release ## Workflow ### Versioning and Tags -This project uses semantic versioning, expressed through the numbering scheme of +This project uses [Semantic Versioning](http://semver.org/), expressed through the numbering scheme of [PEP-0440](https://www.python.org/dev/peps/pep-0440/). ### Branches -`master` is where active development occurs. Long running named feature branches are occasionally created for +The `main` branch is where active development occurs. Long running named feature branches are occasionally created for collaboration on a feature that has a large scope (because everyone cannot push commits to another person's open Pull Request). At some point in the future after a major version increment, there may be maintenance branches for older major versions. @@ -75,26 +259,19 @@ versions. Labels are used to run issues through an organized workflow. Here are the basic definitions: -* `bug`: A confirmed bug report. A bug is considered confirmed when reproduction steps have been - documented and the issue has been reproduced. -* `enhancement`: A feature request for something this package might not already do. -* `docs`: An issue that is purely about documentation work. -* `tests`: An issue that is purely about testing work. -* `needs feedback`: An issue that may have claimed to be a bug but was not reproducible, or was otherwise missing some information. -* `discussion`: An issue that is purely meant to hold a discussion. Typically the maintainers are looking for feedback in this issues. -* `question`: An issue that is like a support request because the user's usage was not correct. -* `semver:major|minor|patch`: Metadata about how resolving this issue would affect the version number. -* `security`: An issue that has special consideration for security reasons. -* `good first contribution`: An issue that has a well-defined relatively-small scope, with clear expectations. It helps when the testing approach is also known. -* `duplicate`: An issue that is functionally the same as another issue. Apply this only if you've linked the other issue by number. +- `bug`: A confirmed bug report. A bug is considered confirmed when reproduction steps have been + documented and the issue has been reproduced. +- `enhancement`: A feature request for something this package might not already do. +- `docs`: An issue that is purely about documentation work. +- `tests`: An issue that is purely about testing work. +- `question`: An issue that is like a support request because the user's usage was not correct. **Triage** is the process of taking new issues that aren't yet "seen" and marking them with a basic level of information -with labels. An issue should have **one** of the following labels applied: `bug`, `enhancement`, `question`, -`needs feedback`, `docs`, `tests`, or `discussion`. +with labels. An issue should have **one** of the following labels applied: `bug`, `enhancement`, `question`, `docs`, `tests`, or `discussion`. Issues are closed when a resolution has been reached. If for any reason a closed issue seems relevant once again, reopening is great and better than creating a duplicate issue. ## Everything else -When in doubt, find the other maintainers and ask. \ No newline at end of file +When in doubt, find the other maintainers and ask. diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index ce8640a6b..ae9cb65ab 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,8 +1,28 @@ -### Summary +## Summary -Describe the goal of this PR. Mention any related Issue numbers. + -### Requirements (place an `x` in each `[ ]`) +### Testing -* [ ] I've read and understood the [Contributing Guidelines](https://github.com/slackapi/python-slackclient/blob/master/.github/contributing.md) and have done my best effort to follow them. -* [ ] I've read and agree to the [Code of Conduct](https://slackhq.github.io/code-of-conduct). \ No newline at end of file + + +### Category + +- [ ] **slack_sdk.web.WebClient (sync/async)** (Web API client) +- [ ] **slack_sdk.webhook.WebhookClient (sync/async)** (Incoming Webhook, response_url sender) +- [ ] **slack_sdk.socket_mode** (Socket Mode client) +- [ ] **slack_sdk.signature** (Request Signature Verifier) +- [ ] **slack_sdk.oauth** (OAuth Flow Utilities) +- [ ] **slack_sdk.models** (UI component builders) +- [ ] **slack_sdk.scim** (SCIM API client) +- [ ] **slack_sdk.audit_logs** (Audit Logs API client) +- [ ] **slack_sdk.rtm_v2** (RTM client) +- [ ] `/docs` (Documents) +- [ ] `/tutorial` (PythOnBoardingBot tutorial) +- [ ] `tests`/`integration_tests` (Automated tests for this library) + +## Requirements + +- [ ] I've read and understood the [Contributing Guidelines](https://github.com/slackapi/python-slack-sdk/blob/main/.github/contributing.md) and have done my best effort to follow them. +- [ ] I've read and agree to the [Code of Conduct](https://slackhq.github.io/code-of-conduct). +- [ ] I've run `python3 -m venv .venv && source .venv/bin/activate && ./scripts/run_validation.sh` after making the changes. diff --git a/.github/release.yml b/.github/release.yml new file mode 100644 index 000000000..b2574b7cc --- /dev/null +++ b/.github/release.yml @@ -0,0 +1,24 @@ +# https://docs.github.com/en/repositories/releasing-projects-on-github/automatically-generated-release-notes#configuring-automatically-generated-release-notes +changelog: + categories: + - title: 🚀 Enhancements + labels: + - enhancement + - title: 🐛 Bug Fixes + labels: + - bug + - title: 📚 Documentation + labels: + - docs + - title: 🤖 Build + labels: + - build + - title: 🧪 Testing/Code Health + labels: + - code health + - title: 🔒 Security + labels: + - security + - title: 📦 Other changes + labels: + - "*" diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml new file mode 100644 index 000000000..c0fd021f0 --- /dev/null +++ b/.github/workflows/ci-build.yml @@ -0,0 +1,134 @@ +name: Python CI + +on: + push: + branches: + - main + pull_request: + schedule: + - cron: "0 0 * * *" + workflow_dispatch: + +env: + LATEST_SUPPORTED_PY: "3.14" + +jobs: + lint: + name: Lint + runs-on: ubuntu-latest + timeout-minutes: 5 + permissions: + contents: read + steps: + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + with: + persist-credentials: false + - name: Set up Python ${{ env.LATEST_SUPPORTED_PY }} + uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 + with: + python-version: ${{ env.LATEST_SUPPORTED_PY }} + - name: Run lint verification + run: ./scripts/lint.sh + + typecheck: + name: Typecheck + runs-on: ubuntu-latest + timeout-minutes: 5 + permissions: + contents: read + steps: + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + with: + persist-credentials: false + - name: Set up Python ${{ env.LATEST_SUPPORTED_PY }} + uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 + with: + python-version: ${{ env.LATEST_SUPPORTED_PY }} + - name: Run mypy verification + run: ./scripts/run_mypy.sh + + unittest: + name: Unit tests + runs-on: ubuntu-22.04 + timeout-minutes: 15 + strategy: + fail-fast: false + matrix: + python-version: + - "3.14" + - "3.13" + - "3.12" + - "3.11" + - "3.10" + - "3.9" + - "3.8" + - "3.7" + - "pypy3.10" + - "pypy3.11" + permissions: + contents: read + env: + CI_LARGE_SOCKET_MODE_PAYLOAD_TESTING_DISABLED: "1" + FORCE_COLOR: "1" + steps: + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + with: + persist-credentials: false + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 + with: + python-version: ${{ matrix.python-version }} + cache: pip + - name: Install dependencies + run: | + pip install -U pip + pip install -r requirements/testing.txt + pip install -r requirements/optional.txt + - name: Run tests + run: | + PYTHONPATH=$PWD:$PYTHONPATH pytest --cov-report=xml --cov=slack_sdk/ --junitxml=reports/test_report.xml tests/ + - name: Run tests for SQLAlchemy v1.4 (backward-compatibility) + run: | + # Install v1.4 for testing + pip install "SQLAlchemy>=1.4,<2" + PYTHONPATH=$PWD:$PYTHONPATH pytest tests/slack_sdk/oauth/installation_store/test_sqlalchemy.py + PYTHONPATH=$PWD:$PYTHONPATH pytest tests/slack_sdk/oauth/state_store/test_sqlalchemy.py + - name: Upload test results to Codecov + if: ${{ !cancelled() }} + uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2 + with: + directory: ./reports/ + fail_ci_if_error: true + flags: ${{ matrix.python-version }} + report_type: test_results + token: ${{ secrets.CODECOV_TOKEN }} + verbose: true + - name: Upload test coverage to Codecov (only with latest supported version) + if: startsWith(matrix.python-version, env.LATEST_SUPPORTED_PY) + uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2 + with: + fail_ci_if_error: true + # Run validation generates the coverage file + files: ./coverage.xml + report_type: coverage + token: ${{ secrets.CODECOV_TOKEN }} + verbose: true + + notifications: + name: Regression notifications + runs-on: ubuntu-latest + needs: + - lint + - typecheck + - unittest + if: ${{ !success() && github.ref == 'refs/heads/main' && github.event_name != 'workflow_dispatch' }} + steps: + - name: Send notifications of failing tests + uses: slackapi/slack-github-action@91efab103c0de0a537f72a35f6b8cda0ee76bf0a # v2.1.1 + with: + errors: true + webhook: ${{ secrets.SLACK_REGRESSION_FAILURES_WEBHOOK_URL }} + webhook-type: webhook-trigger + payload: | + action_url: "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" + repository: "${{ github.repository }}" diff --git a/.github/workflows/pypi-release.yml b/.github/workflows/pypi-release.yml new file mode 100644 index 000000000..89a18c827 --- /dev/null +++ b/.github/workflows/pypi-release.yml @@ -0,0 +1,87 @@ +name: Upload A Release to pypi.org or test.pypi.org + +on: + release: + types: + - published + workflow_dispatch: + inputs: + dry_run: + description: "Dry run (build only, do not publish)" + required: false + type: boolean + +jobs: + release-build: + runs-on: ubuntu-latest + permissions: + contents: read + + steps: + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + with: + ref: ${{ github.event.release.tag_name || github.ref }} + persist-credentials: false + + - name: Set up Python + uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 + with: + python-version: "3.x" + + - name: Build release distributions + run: | + scripts/build_pypi_package.sh + + - name: Persist dist folder + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 + with: + name: release-dist + path: dist/ + + test-pypi-publish: + runs-on: ubuntu-latest + needs: + - release-build + # Run this job for workflow_dispatch events when dry_run input is not 'true' + # Note: The comparison is against a string value 'true' since GitHub Actions inputs are strings + if: github.event_name == 'workflow_dispatch' && github.event.inputs.dry_run != 'true' + environment: + name: testpypi + permissions: + id-token: write + + steps: + - name: Retrieve dist folder + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 + with: + name: release-dist + path: dist/ + + - name: Publish release distributions to test.pypi.org + # Using OIDC for PyPI publishing (no API tokens needed) + # See: https://docs.github.com/en/actions/how-tos/secure-your-work/security-harden-deployments/oidc-in-pypi + uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0 + with: + repository-url: https://test.pypi.org/legacy/ + + pypi-publish: + runs-on: ubuntu-latest + needs: + - release-build + if: github.event_name == 'release' + environment: + name: pypi + permissions: + id-token: write + + steps: + - name: Retrieve dist folder + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 + with: + name: release-dist + path: dist/ + + - name: Publish release distributions to pypi.org + # Using OIDC for PyPI publishing (no API tokens needed) + # See: https://docs.github.com/en/actions/how-tos/secure-your-work/security-harden-deployments/oidc-in-pypi + uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0 diff --git a/.github/workflows/triage-issues.yml b/.github/workflows/triage-issues.yml new file mode 100644 index 000000000..cf13d3afc --- /dev/null +++ b/.github/workflows/triage-issues.yml @@ -0,0 +1,32 @@ +# This workflow uses the following github action to automate +# management of stale issues and prs in this repo: +# https://github.com/marketplace/actions/close-stale-issues + +name: Close stale issues and PRs + +on: + workflow_dispatch: + schedule: + - cron: "0 0 * * 1" + +jobs: + stale: + runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write + steps: + - uses: actions/stale@997185467fa4f803885201cee163a9f38240193d # v10.1.1 + with: + days-before-issue-stale: 30 + days-before-issue-close: 10 + days-before-pr-stale: -1 + days-before-pr-close: -1 + stale-issue-label: auto-triage-stale + stale-issue-message: 👋 It looks like this issue has been open for 30 days with no activity. We'll mark this as stale for now, and wait 10 days for an update or for further comment before closing this issue out. If you think this issue needs to be prioritized, please comment to get the thread going again! Maintainers also review issues marked as stale on a regular basis and comment or adjust status if the issue needs to be reprioritized. + close-issue-message: As this issue has been inactive for more than one month, we will be closing it. Thank you to all the participants! If you would like to raise a related issue, please create a new issue which includes your specific details and references this issue number. + exempt-issue-labels: auto-triage-skip + exempt-all-milestones: true + remove-stale-when-updated: true + enable-statistics: true + operations-per-run: 60 diff --git a/.gitignore b/.gitignore index 3cdc89899..5f316a341 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,7 @@ # general things to ignore build/ dist/ -docs/_sources/ -docs/.doctrees +.eggs/ *.egg-info/ *.egg *.py[cod] @@ -11,13 +10,32 @@ __pycache__/ *~ # virtualenv -env/ -venv/ +env*/ +venv*/ +.venv*/ +.env*/ # codecov / coverage -.coverage +.coverage* cov_* +coverage.xml +reports/ # due to using tox and pytest .tox .cache +.pytest_cache/ +.python-version +pip +.mypy_cache/ + +# JetBrains PyCharm settings +.idea/ + +tmp.txt +.DS_Store +logs/ + +.pytype/ +*.db +.env* diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index e7343fe25..000000000 --- a/.travis.yml +++ /dev/null @@ -1,19 +0,0 @@ -sudo: false -dist: trusty -language: python -python: - - "2.7" - - "3.3" - - "3.4" - - "3.5" - - "3.6" -cache: pip -install: - - travis_retry pip install -r requirements.txt - - travis_retry pip install flake8 - - travis_retry pip install -r test_requirements.txt -script: - - flake8 slackclient - - py.test --cov-report= --cov=slackclient tests -after_success: - - codecov -e $TRAVIS_PYTHON_VERSION diff --git a/.vscode/settings.json b/.vscode/settings.json index 42212ddd1..08726984d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,9 @@ // Place your settings in this file to overwrite default and user settings. { "python.linting.pylintEnabled": false, - "python.linting.flake8Enabled": true + "python.linting.flake8Enabled": true, + "python.venvPath": "${workspaceFolder}/env", + "python.formatting.provider": "black", + "editor.formatOnSave": true, + "python.linting.enabled": true, } \ No newline at end of file diff --git a/LICENSE b/LICENSE index 73da6e975..a63845785 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2015-2016 Slack Technologies, Inc +Copyright (c) 2015- Slack Technologies, LLC Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/MANIFEST.in b/MANIFEST.in index 1aba38f67..4c24f8ecf 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1 +1 @@ -include LICENSE +include README.md LICENSE slack/py.typed slack_sdk/py.typed diff --git a/README.md b/README.md new file mode 100644 index 000000000..2d0638e7d --- /dev/null +++ b/README.md @@ -0,0 +1,305 @@ +

Python Slack SDK

+ +

+ + Tests + + Codecov + + Pepy Total Downloads +
+ + PyPI - Version + + Python Versions + + Documentation +

+ +The Slack platform offers several APIs to build apps. Each Slack API delivers part of the capabilities from the platform, so that you can pick just those that fit for your needs. This SDK offers a corresponding package for each of Slack’s APIs. They are small and powerful when used independently, and work seamlessly when used together, too. + +**Comprehensive documentation on using the Slack Python can be found at [https://docs.slack.dev/tools/python-slack-sdk/](https://docs.slack.dev/tools/python-slack-sdk/)** + +--- + +Whether you're building a custom app for your team, or integrating a third party service into your Slack workflows, Slack Developer Kit for Python allows you to leverage the flexibility of Python to get your project up and running as quickly as possible. + +The **Python Slack SDK** allows interaction with: + +- `slack_sdk.web`: for calling the [Web API methods][api-methods] +- `slack_sdk.webhook`: for utilizing the [Incoming Webhooks](https://docs.slack.dev/messaging/sending-messages-using-incoming-webhooks/) and [`response_url`s in payloads](https://docs.slack.dev/interactivity/handling-user-interaction/#message_responses) +- `slack_sdk.signature`: for [verifying incoming requests from the Slack API server](https://docs.slack.dev/authentication/verifying-requests-from-slack/) +- `slack_sdk.socket_mode`: for receiving and sending messages over [Socket Mode](https://docs.slack.dev/apis/events-api/using-socket-mode/) connections +- `slack_sdk.audit_logs`: for utilizing [Audit Logs APIs](https://docs.slack.dev/admins/audit-logs-api/) +- `slack_sdk.scim`: for utilizing [SCIM APIs](https://docs.slack.dev/admins/scim-api/) +- `slack_sdk.oauth`: for implementing the [Slack OAuth flow](https://docs.slack.dev/authentication/installing-with-oauth/) +- `slack_sdk.models`: for constructing [Block Kit](https://docs.slack.dev/block-kit/) UI components using easy-to-use builders +- `slack_sdk.rtm`: for utilizing the [RTM API][rtm-docs] + +If you want to use our [Events API][events-docs] and Interactivity features, please check the [Bolt for Python][bolt-python] library. Details on the Tokens and Authentication can be found in our [Auth Guide](https://docs.slack.dev/tools/python-slack-sdk/installation/). + +## slackclient is in maintenance mode + +Are you looking for [slackclient](https://pypi.org/project/slackclient/)? The slackclient project is in maintenance mode now and this [`slack_sdk`](https://pypi.org/project/slack-sdk/) is the successor. If you have time to make a migration to slack_sdk v3, please follow [our migration guide](https://docs.slack.dev/tools/python-slack-sdk/v3-migration/) to ensure your app continues working after updating. + +## Table of contents + +* [Requirements](#requirements) +* [Installation](#installation) +* [Getting started tutorial](#getting-started-tutorial) +* [Basic Usage of the Web Client](#basic-usage-of-the-web-client) + * [Sending a message to Slack](#sending-a-message-to-slack) + * [Uploading files to Slack](#uploading-files-to-slack) +* [Async usage](#async-usage) + * [WebClient as a script](#asyncwebclient-in-a-script) + * [WebClient in a framework](#asyncwebclient-in-a-framework) +* [Advanced Options](#advanced-options) + * [SSL](#ssl) + * [Proxy](#proxy) + * [DNS performance](#dns-performance) + * [Example](#example) +* [Migrating from v1](#migrating-from-v1) +* [Support](#support) +* [Development](#development) + +### Requirements + +--- + +This library requires Python 3.7 and above. If you're unsure how to check what version of Python you're on, you can check it using the following: + +> **Note:** You may need to use `python3` before your commands to ensure you use the correct Python path. e.g. `python3 --version` + +```bash +python --version + +-- or -- + +python3 --version +``` + +### Installation + +We recommend using [PyPI][pypi] to install the Slack Developer Kit for Python. + +```bash +$ pip install slack_sdk +``` + +### Getting started tutorial + +--- + +We've created this [tutorial](https://github.com/slackapi/python-slack-sdk/tree/main/tutorial) to build a basic Slack app in less than 10 minutes. It requires some general programming knowledge, and Python basics. It focuses on the interacting with the Slack Web API and RTM API. Use it to give you an idea of how to use this SDK. + +**[Read the tutorial to get started!](https://github.com/slackapi/python-slack-sdk/tree/main/tutorial)** + +### Basic Usage of the Web Client + +--- + +Slack provide a Web API that gives you the ability to build applications that interact with Slack in a variety of ways. This Development Kit is a module based wrapper that makes interaction with that API easier. We have a basic example here with some of the more common uses but a full list of the available methods are available [here][api-methods]. More detailed examples can be found in [our guide](https://docs.slack.dev/tools/python-slack-sdk/web/). + +#### Sending a message to Slack + +One of the most common use-cases is sending a message to Slack. If you want to send a message as your app, or as a user, this method can do both. In our examples, we specify the channel name, however it is recommended to use the `channel_id` where possible. Also, if your app's bot user is not in a channel yet, invite the bot user before running the code snippet (or add `chat:write.public` to Bot Token Scopes for posting in any public channels). + +```python +import os +from slack_sdk import WebClient +from slack_sdk.errors import SlackApiError + +client = WebClient(token=os.environ['SLACK_BOT_TOKEN']) + +try: + response = client.chat_postMessage(channel='#random', text="Hello world!") + assert response["message"]["text"] == "Hello world!" +except SlackApiError as e: + # You will get a SlackApiError if "ok" is False + assert e.response["ok"] is False + assert e.response["error"] # str like 'invalid_auth', 'channel_not_found' + print(f"Got an error: {e.response['error']}") + # Also receive a corresponding status_code + assert isinstance(e.response.status_code, int) + print(f"Received a response status_code: {e.response.status_code}") +``` + +Here we also ensure that the response back from Slack is a successful one and that the message is the one we sent by using the `assert` statement. + +#### Uploading files to Slack + +We've changed the process for uploading files to Slack to be much easier and straight forward. You can now just include a path to the file directly in the API call and upload it that way. + +```python +import os +from slack_sdk import WebClient +from slack_sdk.errors import SlackApiError + +client = WebClient(token=os.environ['SLACK_BOT_TOKEN']) + +try: + filepath="./tmp.txt" + response = client.files_upload_v2(channel='C0123456789', file=filepath) + assert response["file"] # the uploaded file +except SlackApiError as e: + # You will get a SlackApiError if "ok" is False + assert e.response["ok"] is False + assert e.response["error"] # str like 'invalid_auth', 'channel_not_found' + print(f"Got an error: {e.response['error']}") +``` + +More details on the `files_upload_v2` method can be found [here][files_upload_v2]. + +### Async usage + +`AsyncWebClient` in this SDK requires [AIOHttp][aiohttp] under the hood for asynchronous requests. + +#### AsyncWebClient in a script + +```python +import asyncio +import os +from slack_sdk.web.async_client import AsyncWebClient +from slack_sdk.errors import SlackApiError + +client = AsyncWebClient(token=os.environ['SLACK_BOT_TOKEN']) + +async def post_message(): + try: + response = await client.chat_postMessage(channel='#random', text="Hello world!") + assert response["message"]["text"] == "Hello world!" + except SlackApiError as e: + assert e.response["ok"] is False + assert e.response["error"] # str like 'invalid_auth', 'channel_not_found' + print(f"Got an error: {e.response['error']}") + +asyncio.run(post_message()) +``` + +#### AsyncWebClient in a framework + +If you are using a framework invoking the asyncio event loop like : sanic/jupyter notebook/etc. + +```python +import os +from slack_sdk.web.async_client import AsyncWebClient +from slack_sdk.errors import SlackApiError + +client = AsyncWebClient(token=os.environ['SLACK_BOT_TOKEN']) +# Define this as an async function +async def send_to_slack(channel, text): + try: + # Don't forget to have await as the client returns asyncio.Future + response = await client.chat_postMessage(channel=channel, text=text) + assert response["message"]["text"] == text + except SlackApiError as e: + assert e.response["ok"] is False + assert e.response["error"] # str like 'invalid_auth', 'channel_not_found' + raise e + +from aiohttp import web + +async def handle_requests(request: web.Request) -> web.Response: + text = 'Hello World!' + if 'text' in request.query: + text = "\t".join(request.query.getall("text")) + try: + await send_to_slack(channel="#random", text=text) + return web.json_response(data={'message': 'Done!'}) + except SlackApiError as e: + return web.json_response(data={'message': f"Failed due to {e.response['error']}"}) + + +if __name__ == "__main__": + app = web.Application() + app.add_routes([web.get("/", handle_requests)]) + # e.g., http://localhost:3000/?text=foo&text=bar + web.run_app(app, host="0.0.0.0", port=3000) +``` + +### Advanced Options + +#### SSL + +You can provide a custom SSL context or disable verification by passing the `ssl` option, supported by both the RTM and the Web client. + +For async requests, see the [AIOHttp SSL documentation](https://docs.aiohttp.org/en/stable/client_advanced.html#ssl-control-for-tcp-sockets). + +For sync requests, see the [urllib SSL documentation](https://docs.python.org/3/library/urllib.request.html#urllib.request.urlopen). + +#### Proxy + +A proxy is supported when making async requests, pass the `proxy` option, supported by both the RTM and the Web client. + +For async requests, see [AIOHttp Proxy documentation](https://docs.aiohttp.org/en/stable/client_advanced.html#proxy-support). + +For sync requests, setting either `HTTPS_PROXY` env variable or the `proxy` option works. + +#### DNS performance + +Using the async client and looking for a performance boost? Installing the optional dependencies (aiodns) may help speed up DNS resolving by the client. We've included it as an extra called "optional": +```bash +$ pip install slack_sdk[optional] +``` + +#### Example + +```python +import os +from slack_sdk import WebClient +from ssl import SSLContext + +sslcert = SSLContext() +# pip3 install proxy.py +# proxy --port 9000 --log-level d +proxyinfo = "http://localhost:9000" + +client = WebClient( + token=os.environ['SLACK_BOT_TOKEN'], + ssl=sslcert, + proxy=proxyinfo +) +response = client.chat_postMessage(channel="#random", text="Hello World!") +print(response) +``` + +### Migrating from v2 + +If you're migrating from slackclient v2.x of slack_sdk to v3.x, Please follow our migration guide to ensure your app continues working after updating. + +**[Check out the Migration Guide here!](https://docs.slack.dev/tools/python-slack-sdk/v3-migration/)** + +### Migrating from v1 + +If you're migrating from v1.x of slackclient to v2.x, Please follow our migration guide to ensure your app continues working after updating. + +**[Check out the Migration Guide here!](https://github.com/slackapi/python-slack-sdk/wiki/Migrating-to-2.x)** + +### Support + +--- + +If you get stuck, we’re here to help. The following are the best ways to get assistance working through your issue: + +Use our [Github Issue Tracker][gh-issues] for reporting bugs or requesting features. +Visit the [Slack Community][slack-community] for getting help using Slack Developer Kit for Python or just generally bond with your fellow Slack developers. + +### Contributing + +We welcome contributions from everyone! Please check out our +[Contributor's Guide](.github/contributing.md) for how to contribute in a +helpful and collaborative way. + + + +[slackclientv1]: https://github.com/slackapi/python-slackclient/tree/v1 +[api-methods]: https://docs.slack.dev/reference/methods +[rtm-docs]: https://docs.slack.dev/legacy/legacy-rtm-api/ +[events-docs]: https://docs.slack.dev/apis/events-api/ +[bolt-python]: https://github.com/slackapi/bolt-python +[pypi]: https://pypi.org/ +[gh-issues]: https://github.com/slackapi/python-slack-sdk/issues +[slack-community]: https://slackcommunity.com/ +[files_upload_v2]: https://github.com/slackapi/python-slack-sdk/releases/tag/v3.19.0 +[aiohttp]: https://aiohttp.readthedocs.io/ + diff --git a/README.rst b/README.rst deleted file mode 100644 index f9b98c439..000000000 --- a/README.rst +++ /dev/null @@ -1,316 +0,0 @@ -python-slackclient -=================== - -A basic client for Slack.com, which can optionally connect to the Slack Real Time Messaging (RTM) API. - -|build-status| |windows-build-status| |codecov| |doc-status| |pypi-version| |python-version| - -.. |build-status| image:: https://travis-ci.org/slackapi/python-slackclient.svg?branch=master - :target: https://travis-ci.org/slackapi/python-slackclient -.. |windows-build-status| image:: https://ci.appveyor.com/api/projects/status/github/slackapi/python-slackclient?branch=master&svg=true - :target: https://ci.appveyor.com/project/aoberoi/python-slackclient -.. |codecov| image:: https://codecov.io/gh/slackapi/python-slackclient/branch/master/graph/badge.svg - :target: https://codecov.io/gh/slackapi/python-slackclient -.. |doc-status| image:: https://readthedocs.org/projects/python-slackclient/badge/?version=latest - :target: http://python-slackclient.readthedocs.io/en/latest/?badge=latest -.. |pypi-version| image:: https://badge.fury.io/py/slackclient.svg - :target: https://pypi.python.org/pypi/slackclient -.. |python-version| image:: https://img.shields.io/pypi/pyversions/slackclient.svg - :target: https://pypi.python.org/pypi/slackclient - -Overview --------- - -Whether you're building a custom app for your team, or integrating a third party -service into your Slack workflows, Slack Developer Kit for Python allows you to leverage the flexibility -of Python to get your project up and running as quickly as possible. - - -Requirements and Installation -****************************** - -Slack Developer Kit for Python currently works with Python 2.7 (watch for Python 3 support in the future), and requires `PyPI` to install -dependencies. Of course, since you probably installed this module with `PyPI`, this is not a problem. - -We recommend using `PyPI `_ to install Slack Developer Kit for Python - -.. code-block:: bash - - pip install slackclient - -Of course, if you prefer doing things the hard way, you can always implement Slack Developer Kit for Python -by pulling down the source code directly into your project: - -.. code-block:: bash - - git clone https://github.com/slackapi/python-slackclient.git - pip install -r requirements.txt - -Documentation --------------- - -For comprehensive method information and usage examples, see the `full documentation `_. - -Getting Help -------------- - -If you get stuck, we’re here to help. The following are the best ways to get assistance working through your issue: - -- Use our `Github Issue Tracker `_ for reporting bugs or requesting features. -- Visit the `dev4slack channel `_ for getting help using Slack Developer Kit for Python or just generally bond with your fellow Slack developers. - -Basic Usage ------------- -The Slack Web API allows you to build applications that interact with Slack in more complex ways than the integrations -we provide out of the box. - -This package is a modular wrapper designed to make Slack `Web API `_ calls simpler and easier for your -app. Provided below are examples of how to interact with commonly used API endpoints, but this is by no means -a complete list. Review the full list of available methods `here `_. - -See `Tokens & Authentication ` for API token handling best practices. - -Sending a message -******************** -The primary use of Slack is sending messages. Whether you're sending a message -to a user or to a channel, this method handles both. - -To send a message to a channel, use the channel's ID. For IMs, use the user's ID. - -.. code-block:: python - - from slackclient import SlackClient - - slack_token = os.environ["SLACK_API_TOKEN"] - sc = SlackClient(slack_token) - - sc.api_call( - "chat.postMessage", - channel="#python", - text="Hello from Python! :tada:" - ) - -There are some unique options specific to sending IMs, so be sure to read the **channels** -section of the `chat.postMessage `_ -page for a full list of formatting and authorship options. - -Sending an ephemeral message, which is only visible to an assigned user in a specified channel, is nearly the same -as sending a regular message, but with an additional ``user`` parameter. - -.. code-block:: python - - from slackclient import SlackClient - - slack_token = os.environ["SLACK_API_TOKEN"] - sc = SlackClient(slack_token) - - sc.api_call( - "chat.postEphemeral", - channel="#python", - text="Hello from Python! :tada:", - user="U0XXXXXXX" - ) - -See `chat.postEphemeral `_ for more info. - - -Replying to messages and creating threads -***************************************** -Threaded messages are just like regular messages, except thread replies are grouped together to provide greater context -to the user. You can reply to a thread or start a new threaded conversation by simply passing the original message's ``ts`` -ID in the ``thread_ts`` attribute when posting a message. If you're replying to a threaded message, you'll pass the `thread_ts` -ID of the message you're replying to. - -A channel or DM conversation is a nearly linear timeline of messages exchanged between people, bots, and apps. -When one of these messages is replied to, it becomes the parent of a thread. By default, threaded replies do not -appear directly in the channel, instead relegated to a kind of forked timeline descending from the parent message. - -.. code-block:: python - - from slackclient import SlackClient - - slack_token = os.environ["SLACK_API_TOKEN"] - sc = SlackClient(slack_token) - - sc.api_call( - "chat.postMessage", - channel="#python", - text="Hello from Python! :tada:", - thread_ts="1476746830.000003" - ) - - -By default, ``reply_broadcast`` is set to ``False``. To indicate your reply is germane to all members of a channel, -set the ``reply_broadcast`` boolean parameter to ``True``. - -.. code-block:: python - - from slackclient import SlackClient - - slack_token = os.environ["SLACK_API_TOKEN"] - sc = SlackClient(slack_token) - - sc.api_call( - "chat.postMessage", - channel="#python", - text="Hello from Python! :tada:", - thread_ts="1476746830.000003", - reply_broadcast=True - ) - - -**Note:** While threaded messages may contain attachments and message buttons, when your reply is broadcast to the -channel, it'll actually be a reference to your reply, not the reply itself. -So, when appearing in the channel, it won't contain any attachments or message buttons. Also note that updates and -deletion of threaded replies works the same as regular messages. - -See the `Threading messages together `_ -article for more information. - - -Deleting a message -******************** -Sometimes you need to delete things. - -.. code-block:: python - - from slackclient import SlackClient - - slack_token = os.environ["SLACK_API_TOKEN"] - sc = SlackClient(slack_token) - - sc.api_call( - "chat.delete", - channel="C0XXXXXX", - ts="1476745373.000002" - ) - -See `chat.delete `_ for more info. - -Adding or removing an emoji reaction -**************************************** -You can quickly respond to any message on Slack with an emoji reaction. Reactions -can be used for any purpose: voting, checking off to-do items, showing excitement — and just for fun. - -This method adds a reaction (emoji) to an item (``file``, ``file comment``, ``channel message``, ``group message``, or ``direct message``). One of file, file_comment, or the combination of channel and timestamp must be specified. - -.. code-block:: python - - from slackclient import SlackClient - - slack_token = os.environ["SLACK_API_TOKEN"] - sc = SlackClient(slack_token) - - sc.api_call( - "reactions.add", - channel="C0XXXXXXX", - name="thumbsup", - timestamp="1234567890.123456" - ) - -Removing an emoji reaction is basically the same format, but you'll use ``reactions.remove`` instead of ``reactions.add`` - -.. code-block:: python - - sc.api_call( - "reactions.remove", - channel="C0XXXXXXX", - name="thumbsup", - timestamp="1234567890.123456" - ) - - -See `reactions.add `_ and `reactions.remove `_ for more info. - -Getting a list of channels -****************************** -At some point, you'll want to find out what channels are available to your app. This is how you get that list. - -**Note:** This call requires the ``channels:read`` scope. - -.. code-block:: python - - from slackclient import SlackClient - - slack_token = os.environ["SLACK_API_TOKEN"] - sc = SlackClient(slack_token) - - sc.api_call("channels.list") - -Archived channels are included by default. You can exclude them by passing ``exclude_archived=1`` to your request. - -.. code-block:: python - - from slackclient import SlackClient - - slack_token = os.environ["SLACK_API_TOKEN"] - sc = SlackClient(slack_token) - - sc.api_call( - "channels.list", - exclude_archived=1 - ) - -See `channels.list `_ for more info. - -Getting a channel's info -************************* -Once you have the ID for a specific channel, you can fetch information about that channel. - -.. code-block:: python - - from slackclient import SlackClient - - slack_token = os.environ["SLACK_API_TOKEN"] - sc = SlackClient(slack_token) - - sc.api_call( - "channels.info", - channel="C0XXXXXXX" - ) - -See `channels.info `_ for more info. - -Joining a channel -******************** -Channels are the social hub of most Slack teams. Here's how you hop into one: - -.. code-block:: python - - from slackclient import SlackClient - - slack_token = os.environ["SLACK_API_TOKEN"] - sc = SlackClient(slack_token) - - sc.api_call( - "channels.join", - channel="C0XXXXXXY" - ) - -If you are already in the channel, the response is slightly different. -``already_in_channel`` will be true, and a limited ``channel`` object will be returned. Bot users cannot join a channel on their own, they need to be invited by another user. - -See `channels.join `_ for more info. - -Leaving a channel -******************** -Maybe you've finished up all the business you had in a channel, or maybe you -joined one by accident. This is how you leave a channel. - -.. code-block:: python - - from slackclient import SlackClient - - slack_token = os.environ["SLACK_API_TOKEN"] - sc = SlackClient(slack_token) - - sc.api_call( - "channels.leave", - channel="C0XXXXXXX" - ) - -See `channels.leave `_ for more info. - -Additional Information -******************************************************************************************** -For comprehensive method information and usage examples, see the `full documentation`_. diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 000000000..5568e5e6b --- /dev/null +++ b/codecov.yml @@ -0,0 +1,8 @@ +coverage: + status: + project: + default: + threshold: 2.0% + patch: + default: + target: 50% diff --git a/docs-src/.gitignore b/docs-src/.gitignore deleted file mode 100644 index e35d8850c..000000000 --- a/docs-src/.gitignore +++ /dev/null @@ -1 +0,0 @@ -_build diff --git a/docs-src/Makefile b/docs-src/Makefile deleted file mode 100644 index ecbc9e80a..000000000 --- a/docs-src/Makefile +++ /dev/null @@ -1,225 +0,0 @@ -# Makefile for Sphinx documentation -# - -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -PAPER = -BUILDDIR = ../docs/ - -# Internal variables. -PAPEROPT_a4 = -D latex_paper_size=a4 -PAPEROPT_letter = -D latex_paper_size=letter -ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . -# the i18n builder cannot share the environment and doctrees with the others -I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . - -.PHONY: help -help: - @echo "Please use \`make ' where is one of" - @echo " html to make standalone HTML files" - @echo " dirhtml to make HTML files named index.html in directories" - @echo " singlehtml to make a single large HTML file" - @echo " pickle to make pickle files" - @echo " json to make JSON files" - @echo " htmlhelp to make HTML files and a HTML help project" - @echo " qthelp to make HTML files and a qthelp project" - @echo " applehelp to make an Apple Help Book" - @echo " devhelp to make HTML files and a Devhelp project" - @echo " epub to make an epub" - @echo " epub3 to make an epub3" - @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" - @echo " latexpdf to make LaTeX files and run them through pdflatex" - @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" - @echo " text to make text files" - @echo " man to make manual pages" - @echo " texinfo to make Texinfo files" - @echo " info to make Texinfo files and run them through makeinfo" - @echo " gettext to make PO message catalogs" - @echo " changes to make an overview of all changed/added/deprecated items" - @echo " xml to make Docutils-native XML files" - @echo " pseudoxml to make pseudoxml-XML files for display purposes" - @echo " linkcheck to check all external links for integrity" - @echo " doctest to run all doctests embedded in the documentation (if enabled)" - @echo " coverage to run coverage check of the documentation (if enabled)" - @echo " dummy to check syntax errors of document sources" - -.PHONY: clean -clean: - rm -rf $(BUILDDIR)/* - -.PHONY: html -html: - $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." - -.PHONY: dirhtml -dirhtml: - $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." - -.PHONY: singlehtml -singlehtml: - $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml - @echo - @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." - -.PHONY: pickle -pickle: - $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle - @echo - @echo "Build finished; now you can process the pickle files." - -.PHONY: json -json: - $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json - @echo - @echo "Build finished; now you can process the JSON files." - -.PHONY: htmlhelp -htmlhelp: - $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp - @echo - @echo "Build finished; now you can run HTML Help Workshop with the" \ - ".hhp project file in $(BUILDDIR)/htmlhelp." - -.PHONY: qthelp -qthelp: - $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp - @echo - @echo "Build finished; now you can run "qcollectiongenerator" with the" \ - ".qhcp project file in $(BUILDDIR)/qthelp, like this:" - @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/python-slackclient.qhcp" - @echo "To view the help file:" - @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/python-slackclient.qhc" - -.PHONY: applehelp -applehelp: - $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp - @echo - @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." - @echo "N.B. You won't be able to view it unless you put it in" \ - "~/Library/Documentation/Help or install it in your application" \ - "bundle." - -.PHONY: devhelp -devhelp: - $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp - @echo - @echo "Build finished." - @echo "To view the help file:" - @echo "# mkdir -p $$HOME/.local/share/devhelp/python-slackclient" - @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/python-slackclient" - @echo "# devhelp" - -.PHONY: epub -epub: - $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub - @echo - @echo "Build finished. The epub file is in $(BUILDDIR)/epub." - -.PHONY: epub3 -epub3: - $(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3 - @echo - @echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3." - -.PHONY: latex -latex: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo - @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." - @echo "Run \`make' in that directory to run these through (pdf)latex" \ - "(use \`make latexpdf' here to do that automatically)." - -.PHONY: latexpdf -latexpdf: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through pdflatex..." - $(MAKE) -C $(BUILDDIR)/latex all-pdf - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -.PHONY: latexpdfja -latexpdfja: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through platex and dvipdfmx..." - $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -.PHONY: text -text: - $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text - @echo - @echo "Build finished. The text files are in $(BUILDDIR)/text." - -.PHONY: man -man: - $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man - @echo - @echo "Build finished. The manual pages are in $(BUILDDIR)/man." - -.PHONY: texinfo -texinfo: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo - @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." - @echo "Run \`make' in that directory to run these through makeinfo" \ - "(use \`make info' here to do that automatically)." - -.PHONY: info -info: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo "Running Texinfo files through makeinfo..." - make -C $(BUILDDIR)/texinfo info - @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." - -.PHONY: gettext -gettext: - $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale - @echo - @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." - -.PHONY: changes -changes: - $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes - @echo - @echo "The overview file is in $(BUILDDIR)/changes." - -.PHONY: linkcheck -linkcheck: - $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck - @echo - @echo "Link check complete; look for any errors in the above output " \ - "or in $(BUILDDIR)/linkcheck/output.txt." - -.PHONY: doctest -doctest: - $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest - @echo "Testing of doctests in the sources finished, look at the " \ - "results in $(BUILDDIR)/doctest/output.txt." - -.PHONY: coverage -coverage: - $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage - @echo "Testing of coverage in the sources finished, look at the " \ - "results in $(BUILDDIR)/coverage/python.txt." - -.PHONY: xml -xml: - $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml - @echo - @echo "Build finished. The XML files are in $(BUILDDIR)/xml." - -.PHONY: pseudoxml -pseudoxml: - $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml - @echo - @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." - -.PHONY: dummy -dummy: - $(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy - @echo - @echo "Build finished. Dummy builder generates no files." diff --git a/docs-src/_themes/slack/conf.py b/docs-src/_themes/slack/conf.py deleted file mode 100644 index bc81579bb..000000000 --- a/docs-src/_themes/slack/conf.py +++ /dev/null @@ -1,342 +0,0 @@ -# -*- coding: utf-8 -*- -# -# python-slackclient documentation build configuration file, created by -# sphinx-quickstart on Mon Jun 27 17:36:09 2016. -# -# This file is execfile()d with the current directory set to its -# containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -# -import os -import sys -sys.path.insert(0, os.path.abspath('../')) - -# -- General configuration ------------------------------------------------ - -# If your documentation needs a minimal Sphinx version, state it here. -# -# needs_sphinx = '1.0' - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. -extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.coverage', -] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['../../_templates'] - -# The suffix(es) of source filenames. -# You can specify multiple suffix as a list of string: -# -# source_suffix = ['.rst', '.md'] -source_suffix = '.rst' - -# The encoding of source files. -# -# source_encoding = 'utf-8-sig' - -# The master toctree document. -master_doc = 'index' - -# General information about the project. -project = u'Slack Developer Kit for Python' -copyright = u'2015–2016 Slack Technologies, Inc. and contributors' -author = u'Slack Technologies, Inc. and contributors' - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The short X.Y version. -version = u'1.0' -# The full version, including alpha/beta/rc tags. -release = u'1.0.1' - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -# -# This is also used if you do content translation via gettext catalogs. -# Usually you set "language" from the command line for these cases. -language = None - -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -# -# today = '' -# -# Else, today_fmt is used as the format for a strftime call. -# -# today_fmt = '%B %d, %Y' - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -# This patterns also effect to html_static_path and html_extra_path -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] - -# The reST default role (used for this markup: `text`) to use for all -# documents. -# -# default_role = None - -# If true, '()' will be appended to :func: etc. cross-reference text. -# -# add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -# -# add_module_names = True - -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -# -# show_authors = False - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'emacs' - -# A list of ignored prefixes for module index sorting. -# modindex_common_prefix = [] - -# If true, keep warnings as "system message" paragraphs in the built documents. -# keep_warnings = False - -# If true, `todo` and `todoList` produce output, else they produce nothing. -todo_include_todos = False - - -# -- Options for HTML output ---------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -# -html_theme = "slack" -html_theme_path = ["../../_themes", ] - -highlight_language = "python" - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -# -# html_theme_options = {} - -# Add any paths that contain custom themes here, relative to this directory. -# html_theme_path = [] - -# The name for this set of Sphinx documents. -# " v documentation" by default. -# -# html_title = u'python-slackclient v1.0.1' - -# A shorter title for the navigation bar. Default is the same as html_title. -# -# html_short_title = None - -# The name of an image file (relative to this directory) to place at the top -# of the sidebar. -# -# html_logo = None - -# The name of an image file (relative to this directory) to use as a favicon of -# the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -# -# html_favicon = None - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['static'] - -html_context = { - 'css_files': ['static/pygments.css'], -} - -# Add any extra paths that contain custom files (such as robots.txt or -# .htaccess) here, relative to this directory. These files are copied -# directly to the root of the documentation. -# -# html_extra_path = [] - -# If not None, a 'Last updated on:' timestamp is inserted at every page -# bottom, using the given strftime format. -# The empty string is equivalent to '%b %d, %Y'. -# -# html_last_updated_fmt = None - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -# -# html_use_smartypants = True - -# Custom sidebar templates, maps document names to template names. -# -# html_sidebars = {} - -# Additional templates that should be rendered to pages, maps page names to -# template names. -# -# html_additional_pages = {} - -# If false, no module index is generated. -# -# html_domain_indices = True - -# If false, no index is generated. -# -# html_use_index = True - -# If true, the index is split into individual pages for each letter. -# -# html_split_index = False - -# If true, links to the reST sources are added to the pages. -# -# html_show_sourcelink = True - -# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -# -# html_show_sphinx = True - -# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -# -# html_show_copyright = True - -# If true, an OpenSearch description file will be output, and all pages will -# contain a tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -# -# html_use_opensearch = '' - -# This is the file name suffix for HTML files (e.g. ".xhtml"). -# html_file_suffix = None - -# Language to be used for generating the HTML full-text search index. -# Sphinx supports the following languages: -# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' -# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr', 'zh' -# -# html_search_language = 'en' - -# A dictionary with options for the search language support, empty by default. -# 'ja' uses this config value. -# 'zh' user can custom change `jieba` dictionary path. -# -# html_search_options = {'type': 'default'} - -# The name of a javascript file (relative to the configuration directory) that -# implements a search results scorer. If empty, the default will be used. -# -# html_search_scorer = 'scorer.js' - -# Output file base name for HTML help builder. -htmlhelp_basename = 'python-slackclientdoc' - -# -- Options for LaTeX output --------------------------------------------- - -latex_elements = { - # The paper size ('letterpaper' or 'a4paper'). - # - # 'papersize': 'letterpaper', - - # The font size ('10pt', '11pt' or '12pt'). - # - # 'pointsize': '10pt', - - # Additional stuff for the LaTeX preamble. - # - # 'preamble': '', - - # Latex figure (float) alignment - # - # 'figure_align': 'htbp', -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, -# author, documentclass [howto, manual, or own class]). -latex_documents = [ - (master_doc, 'python-slackclient.tex', u'python-slackclient Documentation', - u'Ryan Huber, Jeff Ammons', 'manual'), -] - -# The name of an image file (relative to this directory) to place at the top of -# the title page. -# -# latex_logo = None - -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -# -# latex_use_parts = False - -# If true, show page references after internal links. -# -# latex_show_pagerefs = False - -# If true, show URL addresses after external links. -# -# latex_show_urls = False - -# Documents to append as an appendix to all manuals. -# -# latex_appendices = [] - -# If false, no module index is generated. -# -# latex_domain_indices = True - - -# -- Options for manual page output --------------------------------------- - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - (master_doc, 'python-slackclient', u'python-slackclient Documentation', - [author], 1) -] - -# If true, show URL addresses after external links. -# -# man_show_urls = False - - -# -- Options for Texinfo output ------------------------------------------- - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - (master_doc, 'python-slackclient', u'python-slackclient Documentation', - author, 'python-slackclient', 'A basic client for Slack.com, which can optionally connect to the Slack Real Time Messaging (RTM) API.', - 'Miscellaneous'), -] - -# Documents to append as an appendix to all manuals. -# -# texinfo_appendices = [] - -# If false, no module index is generated. -# -# texinfo_domain_indices = True - -# How to display URL addresses: 'footnote', 'no', or 'inline'. -# -# texinfo_show_urls = 'footnote' - -# If true, do not generate a @detailmenu in the "Top" node's menu. -# -# texinfo_no_detailmenu = False diff --git a/docs-src/_themes/slack/layout.html b/docs-src/_themes/slack/layout.html deleted file mode 100644 index c81593793..000000000 --- a/docs-src/_themes/slack/layout.html +++ /dev/null @@ -1,84 +0,0 @@ - - - - - {{ metatags }} - - {%- block htmltitle %} - {{ title|striptags|e + " — "|safe + project|e }} - {%- endblock %} - - {%- macro css() %} - - - - - - - - - {%- endmacro %} - - {{ css() }} - {%- block linktags %} - - - {%- endblock %} - - - - - {%- block header %} -
- - - - - - - {{ project }} - - -
- {% endblock %} - -
-
- - - - - -
-
{{ title }}
-
- {%- block body %} - {{ body }} - {% endblock %} -
-
-
- - -
-
- -
-

Copyright © 2015–2016 Slack Technologies, Inc. and contributors

-
- - - - - - diff --git a/docs-src/_themes/slack/localtoc.html b/docs-src/_themes/slack/localtoc.html deleted file mode 100644 index 9c79c046a..000000000 --- a/docs-src/_themes/slack/localtoc.html +++ /dev/null @@ -1,4 +0,0 @@ -
Table of Contents?
-
    diff --git a/docs-src/_themes/slack/relations.html b/docs-src/_themes/slack/relations.html deleted file mode 100644 index e3faec04a..000000000 --- a/docs-src/_themes/slack/relations.html +++ /dev/null @@ -1,19 +0,0 @@ -{# - basic/relations.html - ~~~~~~~~~~~~~~~~~~~~ - - Sphinx sidebar template: relation links. - - :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. - :license: BSD, see LICENSE for details. -#} -{%- if prev %} -

    {{ _('Previous topic') }}

    -

    {{ prev.title }}

    -{%- endif %} -{%- if next %} -

    {{ _('Next topic') }}

    -

    {{ next.title }}

    -{%- endif %} diff --git a/docs-src/_themes/slack/sidebar.html b/docs-src/_themes/slack/sidebar.html deleted file mode 100644 index e3db567e3..000000000 --- a/docs-src/_themes/slack/sidebar.html +++ /dev/null @@ -1,15 +0,0 @@ -{{ toctree(maxdepth=-1, collapse=False,includehidden=True) }} - - diff --git a/docs-src/_themes/slack/static/default.css_t b/docs-src/_themes/slack/static/default.css_t deleted file mode 100644 index dc7388797..000000000 --- a/docs-src/_themes/slack/static/default.css_t +++ /dev/null @@ -1,50 +0,0 @@ -.section h1, .section .headerlink { - display: none; -} - -.section-title { - font-size: 2rem; - line-height: 2.5rem; - letter-spacing: -1px; - font-weight: 700; - font-family: Slack-Lato, appleLogo, sans-serif; - margin: 0 0 1rem; -} - -nav#api_nav .toctree-l1 { - margin-bottom: 1.5rem; -} - -nav#api_nav #api_sections ul li.toctree-l1>a { - color: #3aa3e3; - letter-spacing: 0; - font-size: .75rem; - font-weight: 700; - font-family: Slack-Lato, appleLogo, sans-serif; - text-transform: uppercase; - border: none; - padding: 0; -} - -nav#api_nav #api_sections ul li.toctree-l2 a { - color: #555459; - text-transform: none; - font-weight: inherit; - padding: 0; - color: #555459; - display: block; - text-overflow: ellipsis; - overflow: hidden; - white-space: nowrap; - font-size: .9375rem!important; -} - -nav#api_nav #api_sections ul li.toctree-l2 a:hover { - background-color: #edf7fd; - border-color: #dcf0fb -} - -nav#api_nav #footer #footer_nav a { - border: none; - padding: 0; -} diff --git a/docs-src/_themes/slack/static/pygments.css_t b/docs-src/_themes/slack/static/pygments.css_t deleted file mode 100644 index a94b170ff..000000000 --- a/docs-src/_themes/slack/static/pygments.css_t +++ /dev/null @@ -1,60 +0,0 @@ -.highlight .hll { background-color: #ffffcc } -.highlight { background: #ffffff; } -.highlight .c { color: #999988; font-style: italic } /* Comment */ -.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */ -.highlight .k { font-weight: bold } /* Keyword */ -.highlight .o { font-weight: bold } /* Operator */ -.highlight .cm { color: #999988; font-style: italic } /* Comment.Multiline */ -.highlight .cp { color: #999999; font-weight: bold } /* Comment.Preproc */ -.highlight .c1 { color: #999988; font-style: italic } /* Comment.Single */ -.highlight .cs { color: #999999; font-weight: bold; font-style: italic } /* Comment.Special */ -.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */ -.highlight .ge { font-style: italic } /* Generic.Emph */ -.highlight .gr { color: #aa0000 } /* Generic.Error */ -.highlight .gh { color: #999999 } /* Generic.Heading */ -.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */ -.highlight .go { color: #888888 } /* Generic.Output */ -.highlight .gp { color: #555555 } /* Generic.Prompt */ -.highlight .gs { font-weight: bold } /* Generic.Strong */ -.highlight .gu { color: #aaaaaa } /* Generic.Subheading */ -.highlight .gt { color: #aa0000 } /* Generic.Traceback */ -.highlight .kc { font-weight: bold } /* Keyword.Constant */ -.highlight .kd { font-weight: bold } /* Keyword.Declaration */ -.highlight .kn { font-weight: bold } /* Keyword.Namespace */ -.highlight .kp { font-weight: bold } /* Keyword.Pseudo */ -.highlight .kr { font-weight: bold } /* Keyword.Reserved */ -.highlight .kt { color: #445588; font-weight: bold } /* Keyword.Type */ -.highlight .m { color: #009999 } /* Literal.Number */ -.highlight .s { color: #bb8844 } /* Literal.String */ -.highlight .na { color: #008080 } /* Name.Attribute */ -.highlight .nb { color: #999999 } /* Name.Builtin */ -.highlight .nc { color: #445588; font-weight: bold } /* Name.Class */ -.highlight .no { color: #008080 } /* Name.Constant */ -.highlight .ni { color: #800080 } /* Name.Entity */ -.highlight .ne { color: #990000; font-weight: bold } /* Name.Exception */ -.highlight .nf { color: #990000; font-weight: bold } /* Name.Function */ -.highlight .nn { color: #555555 } /* Name.Namespace */ -.highlight .nt { color: #000080 } /* Name.Tag */ -.highlight .nv { color: #008080 } /* Name.Variable */ -.highlight .ow { font-weight: bold } /* Operator.Word */ -.highlight .w { color: #bbbbbb } /* Text.Whitespace */ -.highlight .mf { color: #009999 } /* Literal.Number.Float */ -.highlight .mh { color: #009999 } /* Literal.Number.Hex */ -.highlight .mi { color: #009999 } /* Literal.Number.Integer */ -.highlight .mo { color: #009999 } /* Literal.Number.Oct */ -.highlight .sb { color: #bb8844 } /* Literal.String.Backtick */ -.highlight .sc { color: #bb8844 } /* Literal.String.Char */ -.highlight .sd { color: #bb8844 } /* Literal.String.Doc */ -.highlight .s2 { color: #bb8844 } /* Literal.String.Double */ -.highlight .se { color: #bb8844 } /* Literal.String.Escape */ -.highlight .sh { color: #bb8844 } /* Literal.String.Heredoc */ -.highlight .si { color: #bb8844 } /* Literal.String.Interpol */ -.highlight .sx { color: #bb8844 } /* Literal.String.Other */ -.highlight .sr { color: #808000 } /* Literal.String.Regex */ -.highlight .s1 { color: #bb8844 } /* Literal.String.Single */ -.highlight .ss { color: #bb8844 } /* Literal.String.Symbol */ -.highlight .bp { color: #999999 } /* Name.Builtin.Pseudo */ -.highlight .vc { color: #008080 } /* Name.Variable.Class */ -.highlight .vg { color: #008080 } /* Name.Variable.Global */ -.highlight .vi { color: #008080 } /* Name.Variable.Instance */ -.highlight .il { color: #009999 } /* Literal.Number.Integer.Long */ diff --git a/docs-src/_themes/slack/test.html b/docs-src/_themes/slack/test.html deleted file mode 100644 index 257cc5642..000000000 --- a/docs-src/_themes/slack/test.html +++ /dev/null @@ -1 +0,0 @@ -foo diff --git a/docs-src/_themes/slack/theme.conf b/docs-src/_themes/slack/theme.conf deleted file mode 100644 index b13de3cbb..000000000 --- a/docs-src/_themes/slack/theme.conf +++ /dev/null @@ -1,6 +0,0 @@ -[theme] -inherit = default -stylesheet = default.css -show_sphinx = False - -[options] diff --git a/docs-src/about.rst b/docs-src/about.rst deleted file mode 100644 index 17fad157f..000000000 --- a/docs-src/about.rst +++ /dev/null @@ -1,18 +0,0 @@ -============================================== -About -============================================== - -|product_name| -************** - - -Access the Slack Platform from your Python app. |product_name| lets you build on the Slack Web APIs pythonically. - -|product_name| is proudly maintained with 💖 by the Slack Developer Tools team - -- `License`_ -- `Code of Conduct`_ -- `Contributing`_ -- `Contributor License Agreement`_ - -.. include:: metadata.rst \ No newline at end of file diff --git a/docs-src/auth.rst b/docs-src/auth.rst deleted file mode 100644 index a78700352..000000000 --- a/docs-src/auth.rst +++ /dev/null @@ -1,144 +0,0 @@ -============================================== -Tokens & Authentication -============================================== -.. _handling-tokens: - -Handling tokens and other sensitive data ----------------------------------------- -Slack tokens are the keys to your—or your customers’—teams. Keep them secret. Keep them safe. One way to do that is to never explicitly hardcode them. - -Try to avoid this when possible: - -.. code-block:: python - - token = 'xoxb-abc-1232' - -If you commit this code to GitHub, the world gains access to this token’s team. Rather, we recommend you pass tokens in as environment variables, or persist them in a database that is accessed at runtime. You can add a token to the environment by starting your app as: - -.. code-block:: python - - SLACK_BOT_TOKEN="xoxb-abc-1232" python myapp.py - -Then in your code retrieve the key with: - -.. code-block:: python - - import os - SLACK_BOT_TOKEN = os.environ["SLACK_BOT_TOKEN"] - -You can use the same technique for other kinds of sensitive data that ne’er-do-wells could use in nefarious ways, including - -- Incoming webhook URLs -- Slash command verification tokens -- App client ids and client secrets - -For additional information, please see our `Safely Storing Credentials `_ page. - -Test Tokens ------------------------ -During development (prior to implementing OAuth) you can use a test token provided by the `Test Token Generator `_. These tokens provide access to your private data and that of your team. - -**Tester tokens are not intended to replace OAuth 2.0 tokens.** Once your app is ready for users, replace this token with a proper OAuth token implementation. - -**Never share test tokens with other users or applications. Do not publish test tokens in public code repositories.** - -The OAuth flow -------------------------- -Authentication for Slack's APIs is done using OAuth, so you'll want to read up on `OAuth `_. - -In order to implement OAuth in your app, you will need to include a web server. In this example, we'll use `Flask `_. - -As mentioned above, we're setting the app tokens and other configs in environment variables and pulling them into global variables. - -Depending on what actions your app will need to perform, you'll need different OAuth permission scopes. Review the available scopes `here `_. - -.. code-block:: python - - import os - from flask import Flask, request - from slackclient import SlackClient - - client_id = os.environ["SLACK_CLIENT_ID"] - client_secret = os.environ["SLACK_CLIENT_SECRET"] - oauth_scope = os.environ["SLACK_BOT_SCOPE"] - - app = Flask(__name__) - -**The OAuth initiation link:** - -To begin the OAuth flow, you'll need to provide the user with a link to Slack's OAuth page, -This directs the user to Slack's OAuth acceptance page, where the user will review and accept -or refuse the permissions your app is requesting as defined by the requested scope(s). - -For the best user experience, use the `Add to Slack button `_ to direct users to approve your application for access or `Sign in with Slack `_ to log users in. - -.. code-block:: python - - @app.route("/begin_auth", methods=["GET"]) - def pre_install(): - return ''' - - Add to Slack - - '''.format(oauth_scope, client_id) - -**The OAuth completion page** - -Once the user has agreed to the permissions you've requested on Slack's OAuth -page, Slack will redirect the user to your auth completion page. Included in this -redirect is a ``code`` query string param which you’ll use to request access -tokens from the ``oauth.access`` endpoint. - -Generally, Web API requests require a valid OAuth token, but there are a few endpoints -which do not require a token. ``oauth.access`` is one example of this. Since this -is the endpoint you'll use to retrieve the tokens for later API requests, -an empty string ``""`` is acceptable for this request. - -.. code-block:: python - - @app.route("/finish_auth", methods=["GET", "POST"]) - def post_install(): - - # Retrieve the auth code from the request params - auth_code = request.args['code'] - - # An empty string is a valid token for this request - sc = SlackClient("") - - # Request the auth tokens from Slack - auth_response = sc.api_call( - "oauth.access", - client_id=client_id, - client_secret=client_secret, - code=auth_code - ) - -A successful request to ``oauth.access`` will yield two tokens: A ``user`` -token and a ``bot`` token. The ``user`` token ``auth_response['access_token']`` -is used to make requests on behalf of the authorizing user and the ``bot`` -token ``auth_response['bot']['bot_access_token']`` is for making requests -on behalf of your app's bot user. - -If your Slack app includes a bot user, upon approval the JSON response will contain -an additional node containing an access token to be specifically used for your bot -user, within the context of the approving team. - -When you use Web API methods on behalf of your bot user, you should use this bot -user access token instead of the top-level access token granted to your application. - -.. code-block:: python - - # Save the bot token to an environmental variable or to your data store - # for later use - os.environ["SLACK_USER_TOKEN"] = auth_response['access_token'] - os.environ["SLACK_BOT_TOKEN"] = auth_response['bot']['bot_access_token'] - - # Don't forget to let the user know that auth has succeeded! - return "Auth complete!" - -Once your user has completed the OAuth flow, you'll be able to use the provided -tokens to make a variety of Web API calls on behalf of the user and your app's bot user. - -See the :ref:`Web API usage ` section of this documentation for usage examples. - -.. include:: metadata.rst diff --git a/docs-src/basic_usage.rst b/docs-src/basic_usage.rst deleted file mode 100644 index d26a5077c..000000000 --- a/docs-src/basic_usage.rst +++ /dev/null @@ -1,313 +0,0 @@ -.. _web-api-examples: - -============================================== -Basic Usage -============================================== -The Slack Web API allows you to build applications that interact with Slack in more complex ways than the integrations -we provide out of the box. - -This package is a modular wrapper designed to make Slack `Web API`_ calls simpler and easier for your -app. Provided below are examples of how to interact with commonly used API endpoints, but this is by no means -a complete list. Review the full list of available methods `here `_. - -See :ref:`Tokens & Authentication ` for API token handling best practices. - --------- - -Sending a message ------------------------ -The primary use of Slack is sending messages. Whether you're sending a message -to a user or to a channel, this method handles both. - -To send a message to a channel, use the channel's ID. For IMs, use the user's ID. - -.. code-block:: python - - from slackclient import SlackClient - - slack_token = os.environ["SLACK_API_TOKEN"] - sc = SlackClient(slack_token) - - sc.api_call( - "chat.postMessage", - channel="#python", - text="Hello from Python! :tada:" - ) - -There are some unique options specific to sending IMs, so be sure to read the **channels** -section of the `chat.postMessage `_ -page for a full list of formatting and authorship options. - -Sending an ephemeral message, which is only visible to an assigned user in a specified channel, is nearly the same -as sending a regular message, but with an additional ``user`` parameter. - -.. code-block:: python - - from slackclient import SlackClient - - slack_token = os.environ["SLACK_API_TOKEN"] - sc = SlackClient(slack_token) - - sc.api_call( - "chat.postEphemeral", - channel="#python", - text="Hello from Python! :tada:", - user="U0XXXXXXX" - ) - -See `chat.postEphemeral `_ for more info. - --------- - -Replying to messages and creating threads ------------------------ -Threaded messages are just like regular messages, except thread replies are grouped together to provide greater context -to the user. You can reply to a thread or start a new threaded conversation by simply passing the original message's ``ts`` -ID in the ``thread_ts`` attribute when posting a message. If you're replying to a threaded message, you'll pass the `thread_ts` -ID of the message you're replying to. - -A channel or DM conversation is a nearly linear timeline of messages exchanged between people, bots, and apps. -When one of these messages is replied to, it becomes the parent of a thread. By default, threaded replies do not -appear directly in the channel, instead relegated to a kind of forked timeline descending from the parent message. - -.. code-block:: python - - from slackclient import SlackClient - - slack_token = os.environ["SLACK_API_TOKEN"] - sc = SlackClient(slack_token) - - sc.api_call( - "chat.postMessage", - channel="#python", - text="Hello from Python! :tada:", - thread_ts="1476746830.000003" - ) - - -By default, ``reply_broadcast`` is set to ``False``. To indicate your reply is germane to all members of a channel, -set the ``reply_broadcast`` boolean parameter to ``True``. - -.. code-block:: python - - from slackclient import SlackClient - - slack_token = os.environ["SLACK_API_TOKEN"] - sc = SlackClient(slack_token) - - sc.api_call( - "chat.postMessage", - channel="#python", - text="Hello from Python! :tada:", - thread_ts="1476746830.000003", - reply_broadcast=True - ) - - -**Note:** While threaded messages may contain attachments and message buttons, when your reply is broadcast to the -channel, it'll actually be a reference to your reply, not the reply itself. -So, when appearing in the channel, it won't contain any attachments or message buttons. Also note that updates and -deletion of threaded replies works the same as regular messages. - -See the `Threading messages together `_ -article for more information. - - --------- - -Updating the content of a message ----------------------------------- -Let's say you have a bot which posts the status of a request. When that request -is updated, you'll want to update the message to reflect it's state. Or your user -might want to fix a typo or change some wording. This is how you'll make those changes. - -.. code-block:: python - - from slackclient import SlackClient - - slack_token = os.environ["SLACK_API_TOKEN"] - sc = SlackClient(slack_token) - - sc.api_call( - "chat.update", - ts="1476746830.000003", - channel="C0XXXXXX", - text="Hello from Python! :tada:" - ) - -See `chat.update `_ for formatting options -and some special considerations when calling this with a bot user. - --------- - -Deleting a message -------------------- -Sometimes you need to delete things. - -.. code-block:: python - - from slackclient import SlackClient - - slack_token = os.environ["SLACK_API_TOKEN"] - sc = SlackClient(slack_token) - - sc.api_call( - "chat.delete", - channel="C0XXXXXX", - ts="1476745373.000002" - ) - -See `chat.delete `_ for more info. - --------- - -Adding or removing an emoji reaction ---------------------------------------- -You can quickly respond to any message on Slack with an emoji reaction. Reactions -can be used for any purpose: voting, checking off to-do items, showing excitement — and just for fun. - -This method adds a reaction (emoji) to an item (``file``, ``file comment``, ``channel message``, ``group message``, or ``direct message``). One of file, file_comment, or the combination of channel and timestamp must be specified. - -.. code-block:: python - - from slackclient import SlackClient - - slack_token = os.environ["SLACK_API_TOKEN"] - sc = SlackClient(slack_token) - - sc.api_call( - "reactions.add", - channel="C0XXXXXXX", - name="thumbsup", - timestamp="1234567890.123456" - ) - -Removing an emoji reaction is basically the same format, but you'll use ``reactions.remove`` instead of ``reactions.add`` - -.. code-block:: python - - sc.api_call( - "reactions.remove", - channel="C0XXXXXXX", - name="thumbsup", - timestamp="1234567890.123456" - ) - - -See `reactions.add `_ and `reactions.remove `_ for more info. - --------- - -Getting a list of channels ---------------------------- -At some point, you'll want to find out what channels are available to your app. This is how you get that list. - -**Note:** This call requires the ``channels:read`` scope. - -.. code-block:: python - - from slackclient import SlackClient - - slack_token = os.environ["SLACK_API_TOKEN"] - sc = SlackClient(slack_token) - - sc.api_call("channels.list") - -Archived channels are included by default. You can exclude them by passing ``exclude_archived=1`` to your request. - - -.. code-block:: python - - from slackclient import SlackClient - - slack_token = os.environ["SLACK_API_TOKEN"] - sc = SlackClient(slack_token) - - sc.api_call( - "channels.list", - exclude_archived=1 - ) - -See `channels.list `_ for more info. - --------- - -Getting a channel's info -------------------------- -Once you have the ID for a specific channel, you can fetch information about that channel. - -.. code-block:: python - - from slackclient import SlackClient - - slack_token = os.environ["SLACK_API_TOKEN"] - sc = SlackClient(slack_token) - - sc.api_call( - "channels.info", - channel="C0XXXXXXX" - ) - -See `channels.info `_ for more info. - --------- - -Joining a channel ------------------- -Channels are the social hub of most Slack teams. Here's how you hop into one: - -.. code-block:: python - - from slackclient import SlackClient - - slack_token = os.environ["SLACK_API_TOKEN"] - sc = SlackClient(slack_token) - - sc.api_call( - "channels.join", - channel="C0XXXXXXY" - ) - -If you are already in the channel, the response is slightly different. -``already_in_channel`` will be true, and a limited ``channel`` object will be returned. Bot users cannot join a channel on their own, they need to be invited by another user. - -See `channels.join `_ for more info. - --------- - -Leaving a channel ------------------- -Maybe you've finished up all the business you had in a channel, or maybe you -joined one by accident. This is how you leave a channel. - -.. code-block:: python - - from slackclient import SlackClient - - slack_token = os.environ["SLACK_API_TOKEN"] - sc = SlackClient(slack_token) - - sc.api_call( - "channels.leave", - channel="C0XXXXXXX" - ) - -See `channels.leave `_ for more info. - --------- - -Get a list of team members ------------------------------- - -.. code-block:: python - - from slackclient import SlackClient - - slack_token = os.environ["SLACK_API_TOKEN"] - sc = SlackClient(slack_token) - - sc.api_call("users.list") - -See `users.list `_ for more info. - -.. include:: metadata.rst diff --git a/docs-src/changelog.rst b/docs-src/changelog.rst deleted file mode 100644 index 732f28542..000000000 --- a/docs-src/changelog.rst +++ /dev/null @@ -1,93 +0,0 @@ -============================================== -Changelog -============================================== - -v1.0.9 (2017-8-31) -------------------- - - - Fixed rtm_send_message ID bug introduced in 1.0.8 - -v1.0.8 (2017-8-31) -------------------- - - - Added rtm.connect support - -v1.0.7 (2017-08-02) -------------------- - - - Fixes an issue where connecting over RTM to large teams may result in “Websocket URL expired” errors - - A load of packaging improvements - -v1.0.6 (2017-06-12) -------------------- - - - Added proxy support (thanks @timfeirg!) - - Tidied up docs (thanks @schlueter!) - - Added tox settings for Python 3 testing (thanks @cclauss!) - -v1.0.5 (2017-1-23) -------------------- - - - Allow RTM Channel.send_message to reply to a thread - - Index users by ID instead of Name (non-breaking change) - - Added timeout to api calls. - - Fixed a typo about token access in auth.rst, thanks @kelvintaywl! - - Added Message Threads to the docs - -v1.0.4 (2016-12-15) -------------------- - - - fixed the ability to search for a user by ID - -v1.0.3 (2016-12-13) -------------------- - - - fixed an issue causing RTM connections to fail for large teams - -v1.0.2 (2016-09-22) -------------------- - - - removed unused ping counter - - fixed contributor guidelines links - - updated documentation - - Fix bug preventing API calls requiring a file ID - - Removes files from api_calls before JSON encoding, so the request is properly formatted - -v1.0.1 (2016-03-25) -------------------- - - - fix for __eq__ comparison in channels using '#' in channel name - - added copyright info to the LICENSE file - -v1.0.0 (2016-02-28) -------------------- - - - the ``api_call`` function now returns a decoded JSON object, rather than a JSON encoded string - - some ``api_call`` calls now call actions on the parent server object: - - ``im.open`` - - ``mpim.open``, ``groups.create``, ``groups.createChild`` - - ``channels.create``, `channels.join`` - -v0.18.0 (2016-02-21) --------------------- - - - Moves to use semver for versioning - - Adds support for private groups and MPDMs - - Switches to use requests instead of urllib - - Gets Travis CI integration working - - Fixes some formatting issues so the code will work for python 2.6 - - Cleans up some unused imports, some PEP-8 fixes and a couple bad default args fixes - -v0.17 (2016-02-15) ------------------- - - - Fixes the server so that it doesn't add duplicate users or channels to its internal lists, https://github.com/slackapi/python-slackclient/commit/0cb4bcd6e887b428e27e8059b6278b86ee661aaa - - README updates: - - Updates the URLs pointing to Slack docs for configuring authentication, https://github.com/slackapi/python-slackclient/commit/7d01515cebc80918a29100b0e4793790eb83e7b9 - - s/channnels/channels, https://github.com/slackapi/python-slackclient/commit/d45285d2f1025899dcd65e259624ee73771f94bb - - Adds users to the local cache when they join the team, https://github.com/slackapi/python-slackclient/commit/f7bb8889580cc34471ba1ddc05afc34d1a5efa23 - - Fixes urllib py 2/3 compatibility, https://github.com/slackapi/python-slackclient/commit/1046cc2375a85a22e94573e2aad954ba7287c886 - - - -.. include:: metadata.rst diff --git a/docs-src/conf.py b/docs-src/conf.py deleted file mode 100644 index 37a401194..000000000 --- a/docs-src/conf.py +++ /dev/null @@ -1,338 +0,0 @@ -# -*- coding: utf-8 -*- -# -# python-slackclient documentation build configuration file, created by -# sphinx-quickstart on Mon Jun 27 17:36:09 2016. -# -# This file is execfile()d with the current directory set to its -# containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -# -import os -import sys -sys.path.insert(0, os.path.abspath('../')) - -# -- General configuration ------------------------------------------------ - -# If your documentation needs a minimal Sphinx version, state it here. -# -# needs_sphinx = '1.0' - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. -extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.coverage', -] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# The suffix(es) of source filenames. -# You can specify multiple suffix as a list of string: -# -# source_suffix = ['.rst', '.md'] -source_suffix = '.rst' - -# The encoding of source files. -# -# source_encoding = 'utf-8-sig' - -# The master toctree document. -master_doc = 'index' - -# General information about the project. -project = u'python-slackclient' -copyright = u'2016, Ryan Huber, Jeff Ammons' -author = u'Ryan Huber, Jeff Ammons' - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The short X.Y version. -version = u'1.0' -# The full version, including alpha/beta/rc tags. -release = u'1.0.1' - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -# -# This is also used if you do content translation via gettext catalogs. -# Usually you set "language" from the command line for these cases. -language = None - -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -# -# today = '' -# -# Else, today_fmt is used as the format for a strftime call. -# -# today_fmt = '%B %d, %Y' - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -# This patterns also effect to html_static_path and html_extra_path -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] - -# The reST default role (used for this markup: `text`) to use for all -# documents. -# -# default_role = None - -# If true, '()' will be appended to :func: etc. cross-reference text. -# -# add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -# -# add_module_names = True - -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -# -# show_authors = False - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - -# A list of ignored prefixes for module index sorting. -# modindex_common_prefix = [] - -# If true, keep warnings as "system message" paragraphs in the built documents. -# keep_warnings = False - -# If true, `todo` and `todoList` produce output, else they produce nothing. -todo_include_todos = False - - -# -- Options for HTML output ---------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -# -import sphinx_rtd_theme # noqa -html_theme = "sphinx_rtd_theme" - -html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -# -# html_theme_options = {} - -# Add any paths that contain custom themes here, relative to this directory. -# html_theme_path = [] - -# The name for this set of Sphinx documents. -# " v documentation" by default. -# -# html_title = u'python-slackclient v1.0.1' - -# A shorter title for the navigation bar. Default is the same as html_title. -# -# html_short_title = None - -# The name of an image file (relative to this directory) to place at the top -# of the sidebar. -# -# html_logo = None - -# The name of an image file (relative to this directory) to use as a favicon of -# the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -# -# html_favicon = None - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -# html_static_path = ['_static'] - -# Add any extra paths that contain custom files (such as robots.txt or -# .htaccess) here, relative to this directory. These files are copied -# directly to the root of the documentation. -# -# html_extra_path = [] - -# If not None, a 'Last updated on:' timestamp is inserted at every page -# bottom, using the given strftime format. -# The empty string is equivalent to '%b %d, %Y'. -# -# html_last_updated_fmt = None - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -# -# html_use_smartypants = True - -# Custom sidebar templates, maps document names to template names. -# -# html_sidebars = {} - -# Additional templates that should be rendered to pages, maps page names to -# template names. -# -# html_additional_pages = {} - -# If false, no module index is generated. -# -# html_domain_indices = True - -# If false, no index is generated. -# -# html_use_index = True - -# If true, the index is split into individual pages for each letter. -# -# html_split_index = False - -# If true, links to the reST sources are added to the pages. -# -# html_show_sourcelink = True - -# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -# -# html_show_sphinx = True - -# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -# -# html_show_copyright = True - -# If true, an OpenSearch description file will be output, and all pages will -# contain a tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -# -# html_use_opensearch = '' - -# This is the file name suffix for HTML files (e.g. ".xhtml"). -# html_file_suffix = None - -# Language to be used for generating the HTML full-text search index. -# Sphinx supports the following languages: -# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' -# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr', 'zh' -# -# html_search_language = 'en' - -# A dictionary with options for the search language support, empty by default. -# 'ja' uses this config value. -# 'zh' user can custom change `jieba` dictionary path. -# -# html_search_options = {'type': 'default'} - -# The name of a javascript file (relative to the configuration directory) that -# implements a search results scorer. If empty, the default will be used. -# -# html_search_scorer = 'scorer.js' - -# Output file base name for HTML help builder. -htmlhelp_basename = 'python-slackclientdoc' - -# -- Options for LaTeX output --------------------------------------------- - -latex_elements = { - # The paper size ('letterpaper' or 'a4paper'). - # - # 'papersize': 'letterpaper', - - # The font size ('10pt', '11pt' or '12pt'). - # - # 'pointsize': '10pt', - - # Additional stuff for the LaTeX preamble. - # - # 'preamble': '', - - # Latex figure (float) alignment - # - # 'figure_align': 'htbp', -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, -# author, documentclass [howto, manual, or own class]). -latex_documents = [ - (master_doc, 'python-slackclient.tex', u'python-slackclient Documentation', - u'Ryan Huber, Jeff Ammons', 'manual'), -] - -# The name of an image file (relative to this directory) to place at the top of -# the title page. -# -# latex_logo = None - -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -# -# latex_use_parts = False - -# If true, show page references after internal links. -# -# latex_show_pagerefs = False - -# If true, show URL addresses after external links. -# -# latex_show_urls = False - -# Documents to append as an appendix to all manuals. -# -# latex_appendices = [] - -# If false, no module index is generated. -# -# latex_domain_indices = True - - -# -- Options for manual page output --------------------------------------- - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - (master_doc, 'python-slackclient', u'python-slackclient Documentation', - [author], 1) -] - -# If true, show URL addresses after external links. -# -# man_show_urls = False - - -# -- Options for Texinfo output ------------------------------------------- - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - (master_doc, 'python-slackclient', u'python-slackclient Documentation', - author, 'python-slackclient', 'A basic client for Slack.com, which can optionally connect to the Slack Real Time Messaging (RTM) API.', - 'Miscellaneous'), -] - -# Documents to append as an appendix to all manuals. -# -# texinfo_appendices = [] - -# If false, no module index is generated. -# -# texinfo_domain_indices = True - -# How to display URL addresses: 'footnote', 'no', or 'inline'. -# -# texinfo_show_urls = 'footnote' - -# If true, do not generate a @detailmenu in the "Top" node's menu. -# -# texinfo_no_detailmenu = False diff --git a/docs-src/faq.rst b/docs-src/faq.rst deleted file mode 100644 index 44b25e3e2..000000000 --- a/docs-src/faq.rst +++ /dev/null @@ -1,63 +0,0 @@ -============================================== -Frequently Asked Questions -============================================== - -What even is |product_name| and why should I care? -************************************************** - -|product_name| is a wrapper around commonly accessed parts of the Slack Platform. It provides basic mechanisms for -using the Slack Web API from within your Python app. - -On the other hand, |product_name| does not provide access to the RTM or Events bot-building APIs, although we anticipate -adding support for these in the future. - -OMG I found a bug! -****************** - -Well, poop. Take a deep breath, and then let us know on the `Issue Tracker`_. If you're feeling particularly ambitious, -why not submit a `pull request`_ with a bug fix? - -Hey, there's a feature missing! -******************************* - -There's always something more that could be added! You can let us know in the `Issue Tracker`_ to start a discussion -around the proposed feature, that's a good start. If you're feeling particularly ambitious, why not write the feature -yourself, and submit a `pull request`_! We love feedback and we love help and we don't bite. Much. - -I'd like to contribute...but how? -********************************* - -What an excellent question. First of all, please have a look at our general `contributing guidelines`_. We'll wait for -you here. - -All done? Great! While we're super excited to incorporate your new feature into |product_name|, there are a -couple of things we want to make sure you've given thought to. - -- Please write unit tests for your new code. But don't **just** aim to increase the test coverage, rather, we expect you - to have written **thoughtful** tests that ensure your new feature will continue to work as expected, and to help future - contributors to ensure they don't break it! - -- Please document your new feature. Think about **concrete use cases** for your feature, and add a section to the - appropriate document, including a **complete** sample program that demonstrates your feature. Don't forget to update - the changelog in ``changelog.rst``! - -Including these two items with your pull request will totally make our day—and, more importantly, your future users' days! - -On that note... - -How do I compile the documentation? -*********************************** - -This project's documentation is generated with `Sphinx `_. If you are editing one of the many -reStructuredText files in the ``docs-src`` folder, you'll need to rebuild the documentation. It is recommended to run -the following steps inside a ``virtualenv`` environment. - -.. code-block:: bash - - tox -e docs - - -Do be sure to add the ``docs`` folder and its contents to your pull request! - - -.. include:: metadata.rst diff --git a/docs-src/index.rst b/docs-src/index.rst deleted file mode 100644 index 4ac972474..000000000 --- a/docs-src/index.rst +++ /dev/null @@ -1,48 +0,0 @@ -.. toctree:: - :hidden: - - self - auth - basic_usage - real_time_messaging - faq - changelog - about - -============================================== -|product_name| -============================================== - -Whether you're building a custom app for your team, or integrating a third party -service into your Slack workflows, |product_name| allows you to leverage the flexibility -of Python to get your project up and running as quickly as possible. - -Requirements and Installation -****************************** - -|product_name| currently works with Python 2.7 (watch for Python 3 support in the future), and requires `PyPI` to install -dependencies. Of course, since you probably installed this module with `PyPI`, this is not a problem. - -We recommend using `PyPI `_ to install |product_name| - -.. code-block:: bash - - pip install slackclient - -Of course, if you prefer doing things the hard way, you can always implement |product_name| -by pulling down the source code directly into your project: - -.. code-block:: bash - - git clone https://github.com/slackapi/python-slackclient.git - pip install -r requirements.txt - -Getting Help -************ - -If you get stuck, we’re here to help. The following are the best ways to get assistance working through your issue: - -- Use our `Github Issue Tracker `_ for reporting bugs or requesting features. -- Visit the `dev4slack channel `_ for getting help using |product_name| or just generally bond with your fellow Slack developers. - -.. include:: metadata.rst diff --git a/docs-src/make.bat b/docs-src/make.bat deleted file mode 100644 index e66ba12ac..000000000 --- a/docs-src/make.bat +++ /dev/null @@ -1,281 +0,0 @@ -@ECHO OFF - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build -) -set BUILDDIR=_build -set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . -set I18NSPHINXOPTS=%SPHINXOPTS% . -if NOT "%PAPER%" == "" ( - set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% - set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% -) - -if "%1" == "" goto help - -if "%1" == "help" ( - :help - echo.Please use `make ^` where ^ is one of - echo. html to make standalone HTML files - echo. dirhtml to make HTML files named index.html in directories - echo. singlehtml to make a single large HTML file - echo. pickle to make pickle files - echo. json to make JSON files - echo. htmlhelp to make HTML files and a HTML help project - echo. qthelp to make HTML files and a qthelp project - echo. devhelp to make HTML files and a Devhelp project - echo. epub to make an epub - echo. epub3 to make an epub3 - echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter - echo. text to make text files - echo. man to make manual pages - echo. texinfo to make Texinfo files - echo. gettext to make PO message catalogs - echo. changes to make an overview over all changed/added/deprecated items - echo. xml to make Docutils-native XML files - echo. pseudoxml to make pseudoxml-XML files for display purposes - echo. linkcheck to check all external links for integrity - echo. doctest to run all doctests embedded in the documentation if enabled - echo. coverage to run coverage check of the documentation if enabled - echo. dummy to check syntax errors of document sources - goto end -) - -if "%1" == "clean" ( - for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i - del /q /s %BUILDDIR%\* - goto end -) - - -REM Check if sphinx-build is available and fallback to Python version if any -%SPHINXBUILD% 1>NUL 2>NUL -if errorlevel 9009 goto sphinx_python -goto sphinx_ok - -:sphinx_python - -set SPHINXBUILD=python -m sphinx.__init__ -%SPHINXBUILD% 2> nul -if errorlevel 9009 ( - echo. - echo.The 'sphinx-build' command was not found. Make sure you have Sphinx - echo.installed, then set the SPHINXBUILD environment variable to point - echo.to the full path of the 'sphinx-build' executable. Alternatively you - echo.may add the Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.http://sphinx-doc.org/ - exit /b 1 -) - -:sphinx_ok - - -if "%1" == "html" ( - %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/html. - goto end -) - -if "%1" == "dirhtml" ( - %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. - goto end -) - -if "%1" == "singlehtml" ( - %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. - goto end -) - -if "%1" == "pickle" ( - %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can process the pickle files. - goto end -) - -if "%1" == "json" ( - %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can process the JSON files. - goto end -) - -if "%1" == "htmlhelp" ( - %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can run HTML Help Workshop with the ^ -.hhp project file in %BUILDDIR%/htmlhelp. - goto end -) - -if "%1" == "qthelp" ( - %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can run "qcollectiongenerator" with the ^ -.qhcp project file in %BUILDDIR%/qthelp, like this: - echo.^> qcollectiongenerator %BUILDDIR%\qthelp\python-slackclient.qhcp - echo.To view the help file: - echo.^> assistant -collectionFile %BUILDDIR%\qthelp\python-slackclient.ghc - goto end -) - -if "%1" == "devhelp" ( - %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. - goto end -) - -if "%1" == "epub" ( - %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The epub file is in %BUILDDIR%/epub. - goto end -) - -if "%1" == "epub3" ( - %SPHINXBUILD% -b epub3 %ALLSPHINXOPTS% %BUILDDIR%/epub3 - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The epub3 file is in %BUILDDIR%/epub3. - goto end -) - -if "%1" == "latex" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "latexpdf" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - cd %BUILDDIR%/latex - make all-pdf - cd %~dp0 - echo. - echo.Build finished; the PDF files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "latexpdfja" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - cd %BUILDDIR%/latex - make all-pdf-ja - cd %~dp0 - echo. - echo.Build finished; the PDF files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "text" ( - %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The text files are in %BUILDDIR%/text. - goto end -) - -if "%1" == "man" ( - %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The manual pages are in %BUILDDIR%/man. - goto end -) - -if "%1" == "texinfo" ( - %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. - goto end -) - -if "%1" == "gettext" ( - %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The message catalogs are in %BUILDDIR%/locale. - goto end -) - -if "%1" == "changes" ( - %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes - if errorlevel 1 exit /b 1 - echo. - echo.The overview file is in %BUILDDIR%/changes. - goto end -) - -if "%1" == "linkcheck" ( - %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck - if errorlevel 1 exit /b 1 - echo. - echo.Link check complete; look for any errors in the above output ^ -or in %BUILDDIR%/linkcheck/output.txt. - goto end -) - -if "%1" == "doctest" ( - %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest - if errorlevel 1 exit /b 1 - echo. - echo.Testing of doctests in the sources finished, look at the ^ -results in %BUILDDIR%/doctest/output.txt. - goto end -) - -if "%1" == "coverage" ( - %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage - if errorlevel 1 exit /b 1 - echo. - echo.Testing of coverage in the sources finished, look at the ^ -results in %BUILDDIR%/coverage/python.txt. - goto end -) - -if "%1" == "xml" ( - %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The XML files are in %BUILDDIR%/xml. - goto end -) - -if "%1" == "pseudoxml" ( - %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. - goto end -) - -if "%1" == "dummy" ( - %SPHINXBUILD% -b dummy %ALLSPHINXOPTS% %BUILDDIR%/dummy - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. Dummy builder generates no files. - goto end -) - -:end diff --git a/docs-src/metadata.rst b/docs-src/metadata.rst deleted file mode 100644 index 4d3ec4062..000000000 --- a/docs-src/metadata.rst +++ /dev/null @@ -1,19 +0,0 @@ -.. Site settings -.. |product_name| replace:: Slack Developer Kit for Python -.. |email| replace:: opensource@slack.com -.. |repo_name| replace:: python-slackclient -.. |github_username| replace:: SlackAPI -.. |twitter_username| replace:: SlackAPI - -.. _dev4slack channel: http://https://dev4slack.slack.com/archives/sdk-python-slackclient -.. _Issue Tracker: http://github.com/SlackAPI/python-slackclient/issues -.. _pull request: http://github.com/SlackAPI/python-slackclient/pulls -.. _Python RTMBot: https://slackapi.github.io/python-rtmbot -.. _License: https://github.com/SlackAPI/python-slackclient/blob/master/LICENSE -.. _Code of Conduct: https://github.com/SlackAPI/python-slackclient/blob/master/CODE_OF_CONDUCT.md -.. _Contributing: https://github.com/SlackAPI/python-slackclient/blob/master/CONTRIBUTING.md -.. _contributing guidelines: https://github.com/SlackAPI/python-slackclient/blob/master/CONTRIBUTING.md -.. _Contributor License Agreement: https://docs.google.com/a/slack-corp.com/forms/d/e/1FAIpQLSfzjVoCM7ohBnjWf7eDYQxzti1EPpinsIJQA5RAUBwJKRUQHg/viewform -.. _Real Time Messaging API: https://api.slack.com/rtm -.. _Web API: https://api.slack.com/web - diff --git a/docs-src/real_time_messaging.rst b/docs-src/real_time_messaging.rst deleted file mode 100644 index 068d5aaa6..000000000 --- a/docs-src/real_time_messaging.rst +++ /dev/null @@ -1,127 +0,0 @@ -.. _real-time-messaging: - -============================================== -Real Time Messaging -============================================== -The `Real Time Messaging API`_ is a WebSocket-based API that allows you to -receive events from Slack in real time and send messages as users. - -If you prefer events to be pushed to you instead, we recommend using the -HTTP-based `Events API `_ instead. -Most event types supported by the RTM API are also available -in `the Events API `_. - -See :ref:`Tokens & Authentication ` for API token handling best practices. - -Connecting to the Real Time Messaging API ------------------------------------------- -:: - - from slackclient import SlackClient - - slack_token = os.environ["SLACK_API_TOKEN"] - sc = SlackClient(slack_token) - - if sc.rtm_connect(): - while True: - print sc.rtm_read() - time.sleep(1) - else: - print "Connection Failed" - -If you connect successfully the first event received will be a hello: -:: - - { - u'type': u'hello' - } - -If there was a problem connecting an error will be returned, including a descriptive error message: -:: - - { - u'type': u'error', - u'error': { - u'code': 1, - u'msg': u'Socket URL has expired' - } - } - -rtm.start vs rtm.connect ---------------------------- - -If you expect your app to be used on large teams, we recommend starting the RTM client with `rtm.connect` rather than the default connection method for this client, `rtm.start`. -`rtm.connect` provides a lighter initial connection payload, without the team's channel and user information included. You'll need to request channel and user info via -the Web API separately. - -To do this, simply pass `with_team_state=False` into the `rtm_connect` call, like so: -:: - - from slackclient import SlackClient - - slack_token = os.environ["SLACK_API_TOKEN"] - sc = SlackClient(slack_token) - - if sc.rtm_connect(with_team_state=False): - while True: - print sc.rtm_read() - time.sleep(1) - else: - print "Connection Failed" - - -See the `rtm.start docs `_ and the `rtm.connect docs`_ -for more details. - - -RTM Events -------------- -:: - - { - u'type': u'message', - u'ts': u'1358878749.000002', - u'user': u'U023BECGF', - u'text': u'Hello' - } - -See `RTM Events `_ for a complete list of events. - -Sending messages via the RTM API ---------------------------------- -You can send a message to Slack by sending JSON over the websocket connection. - -:: - - from slackclient import SlackClient - - slack_token = os.environ["SLACK_API_TOKEN"] - sc = SlackClient(slack_token) - - sc.rtm_send_message("welcome-test", "test") - -You can send a message to a private group or direct message channel in the same -way, but using a Group ID (``C024BE91L``) or DM channel ID (``D024BE91L``). - -You can send a message in reply to a thread using the ``thread`` argument, and -optionally broadcast that message back to the channel by setting -``reply_broadcast`` to ``True``. - -:: - - from slackclient import SlackClient - - slack_token = os.environ["SLACK_API_TOKEN"] - sc = SlackClient(slack_token) - - sc.rtm_send_message("welcome-test", "test", "1482960137.003543", True) - -See `Threading messages `_ -for more details on using threads. - -The RTM API only supports posting messages with `basic formatting `_. -It does not support attachments or other message formatting modes. - - To post a more complex message as a user, see :ref:`Web API usage `. - -.. include:: metadata.rst diff --git a/docs/.buildinfo b/docs/.buildinfo deleted file mode 100644 index d6e8f587e..000000000 --- a/docs/.buildinfo +++ /dev/null @@ -1,4 +0,0 @@ -# Sphinx build info version 1 -# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. -config: 59efe32fc6b880b69f0bafedda8f3fec -tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/docs/_static/ajax-loader.gif b/docs/_static/ajax-loader.gif deleted file mode 100644 index 61faf8cab..000000000 Binary files a/docs/_static/ajax-loader.gif and /dev/null differ diff --git a/docs/_static/basic.css b/docs/_static/basic.css deleted file mode 100644 index 6df76b0a6..000000000 --- a/docs/_static/basic.css +++ /dev/null @@ -1,639 +0,0 @@ -/* - * basic.css - * ~~~~~~~~~ - * - * Sphinx stylesheet -- basic theme. - * - * :copyright: Copyright 2007-2017 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * - */ - -/* -- main layout ----------------------------------------------------------- */ - -div.clearer { - clear: both; -} - -/* -- relbar ---------------------------------------------------------------- */ - -div.related { - width: 100%; - font-size: 90%; -} - -div.related h3 { - display: none; -} - -div.related ul { - margin: 0; - padding: 0 0 0 10px; - list-style: none; -} - -div.related li { - display: inline; -} - -div.related li.right { - float: right; - margin-right: 5px; -} - -/* -- sidebar --------------------------------------------------------------- */ - -div.sphinxsidebarwrapper { - padding: 10px 5px 0 10px; -} - -div.sphinxsidebar { - float: left; - width: 230px; - margin-left: -100%; - font-size: 90%; - word-wrap: break-word; - overflow-wrap : break-word; -} - -div.sphinxsidebar ul { - list-style: none; -} - -div.sphinxsidebar ul ul, -div.sphinxsidebar ul.want-points { - margin-left: 20px; - list-style: square; -} - -div.sphinxsidebar ul ul { - margin-top: 0; - margin-bottom: 0; -} - -div.sphinxsidebar form { - margin-top: 10px; -} - -div.sphinxsidebar input { - border: 1px solid #98dbcc; - font-family: sans-serif; - font-size: 1em; -} - -div.sphinxsidebar #searchbox input[type="text"] { - width: 170px; -} - -img { - border: 0; - max-width: 100%; -} - -/* -- search page ----------------------------------------------------------- */ - -ul.search { - margin: 10px 0 0 20px; - padding: 0; -} - -ul.search li { - padding: 5px 0 5px 20px; - background-image: url(file.png); - background-repeat: no-repeat; - background-position: 0 7px; -} - -ul.search li a { - font-weight: bold; -} - -ul.search li div.context { - color: #888; - margin: 2px 0 0 30px; - text-align: left; -} - -ul.keywordmatches li.goodmatch a { - font-weight: bold; -} - -/* -- index page ------------------------------------------------------------ */ - -table.contentstable { - width: 90%; - margin-left: auto; - margin-right: auto; -} - -table.contentstable p.biglink { - line-height: 150%; -} - -a.biglink { - font-size: 1.3em; -} - -span.linkdescr { - font-style: italic; - padding-top: 5px; - font-size: 90%; -} - -/* -- general index --------------------------------------------------------- */ - -table.indextable { - width: 100%; -} - -table.indextable td { - text-align: left; - vertical-align: top; -} - -table.indextable ul { - margin-top: 0; - margin-bottom: 0; - list-style-type: none; -} - -table.indextable > tbody > tr > td > ul { - padding-left: 0em; -} - -table.indextable tr.pcap { - height: 10px; -} - -table.indextable tr.cap { - margin-top: 10px; - background-color: #f2f2f2; -} - -img.toggler { - margin-right: 3px; - margin-top: 3px; - cursor: pointer; -} - -div.modindex-jumpbox { - border-top: 1px solid #ddd; - border-bottom: 1px solid #ddd; - margin: 1em 0 1em 0; - padding: 0.4em; -} - -div.genindex-jumpbox { - border-top: 1px solid #ddd; - border-bottom: 1px solid #ddd; - margin: 1em 0 1em 0; - padding: 0.4em; -} - -/* -- domain module index --------------------------------------------------- */ - -table.modindextable td { - padding: 2px; - border-collapse: collapse; -} - -/* -- general body styles --------------------------------------------------- */ - -div.body p, div.body dd, div.body li, div.body blockquote { - -moz-hyphens: auto; - -ms-hyphens: auto; - -webkit-hyphens: auto; - hyphens: auto; -} - -a.headerlink { - visibility: hidden; -} - -h1:hover > a.headerlink, -h2:hover > a.headerlink, -h3:hover > a.headerlink, -h4:hover > a.headerlink, -h5:hover > a.headerlink, -h6:hover > a.headerlink, -dt:hover > a.headerlink, -caption:hover > a.headerlink, -p.caption:hover > a.headerlink, -div.code-block-caption:hover > a.headerlink { - visibility: visible; -} - -div.body p.caption { - text-align: inherit; -} - -div.body td { - text-align: left; -} - -.first { - margin-top: 0 !important; -} - -p.rubric { - margin-top: 30px; - font-weight: bold; -} - -img.align-left, .figure.align-left, object.align-left { - clear: left; - float: left; - margin-right: 1em; -} - -img.align-right, .figure.align-right, object.align-right { - clear: right; - float: right; - margin-left: 1em; -} - -img.align-center, .figure.align-center, object.align-center { - display: block; - margin-left: auto; - margin-right: auto; -} - -.align-left { - text-align: left; -} - -.align-center { - text-align: center; -} - -.align-right { - text-align: right; -} - -/* -- sidebars -------------------------------------------------------------- */ - -div.sidebar { - margin: 0 0 0.5em 1em; - border: 1px solid #ddb; - padding: 7px 7px 0 7px; - background-color: #ffe; - width: 40%; - float: right; -} - -p.sidebar-title { - font-weight: bold; -} - -/* -- topics ---------------------------------------------------------------- */ - -div.topic { - border: 1px solid #ccc; - padding: 7px 7px 0 7px; - margin: 10px 0 10px 0; -} - -p.topic-title { - font-size: 1.1em; - font-weight: bold; - margin-top: 10px; -} - -/* -- admonitions ----------------------------------------------------------- */ - -div.admonition { - margin-top: 10px; - margin-bottom: 10px; - padding: 7px; -} - -div.admonition dt { - font-weight: bold; -} - -div.admonition dl { - margin-bottom: 0; -} - -p.admonition-title { - margin: 0px 10px 5px 0px; - font-weight: bold; -} - -div.body p.centered { - text-align: center; - margin-top: 25px; -} - -/* -- tables ---------------------------------------------------------------- */ - -table.docutils { - border: 0; - border-collapse: collapse; -} - -table caption span.caption-number { - font-style: italic; -} - -table caption span.caption-text { -} - -table.docutils td, table.docutils th { - padding: 1px 8px 1px 5px; - border-top: 0; - border-left: 0; - border-right: 0; - border-bottom: 1px solid #aaa; -} - -table.footnote td, table.footnote th { - border: 0 !important; -} - -th { - text-align: left; - padding-right: 5px; -} - -table.citation { - border-left: solid 1px gray; - margin-left: 1px; -} - -table.citation td { - border-bottom: none; -} - -/* -- figures --------------------------------------------------------------- */ - -div.figure { - margin: 0.5em; - padding: 0.5em; -} - -div.figure p.caption { - padding: 0.3em; -} - -div.figure p.caption span.caption-number { - font-style: italic; -} - -div.figure p.caption span.caption-text { -} - -/* -- field list styles ----------------------------------------------------- */ - -table.field-list td, table.field-list th { - border: 0 !important; -} - -.field-list ul { - margin: 0; - padding-left: 1em; -} - -.field-list p { - margin: 0; -} - -.field-name { - -moz-hyphens: manual; - -ms-hyphens: manual; - -webkit-hyphens: manual; - hyphens: manual; -} - -/* -- other body styles ----------------------------------------------------- */ - -ol.arabic { - list-style: decimal; -} - -ol.loweralpha { - list-style: lower-alpha; -} - -ol.upperalpha { - list-style: upper-alpha; -} - -ol.lowerroman { - list-style: lower-roman; -} - -ol.upperroman { - list-style: upper-roman; -} - -dl { - margin-bottom: 15px; -} - -dd p { - margin-top: 0px; -} - -dd ul, dd table { - margin-bottom: 10px; -} - -dd { - margin-top: 3px; - margin-bottom: 10px; - margin-left: 30px; -} - -dt:target, .highlighted { - background-color: #fbe54e; -} - -dl.glossary dt { - font-weight: bold; - font-size: 1.1em; -} - -.optional { - font-size: 1.3em; -} - -.sig-paren { - font-size: larger; -} - -.versionmodified { - font-style: italic; -} - -.system-message { - background-color: #fda; - padding: 5px; - border: 3px solid red; -} - -.footnote:target { - background-color: #ffa; -} - -.line-block { - display: block; - margin-top: 1em; - margin-bottom: 1em; -} - -.line-block .line-block { - margin-top: 0; - margin-bottom: 0; - margin-left: 1.5em; -} - -.guilabel, .menuselection { - font-family: sans-serif; -} - -.accelerator { - text-decoration: underline; -} - -.classifier { - font-style: oblique; -} - -abbr, acronym { - border-bottom: dotted 1px; - cursor: help; -} - -/* -- code displays --------------------------------------------------------- */ - -pre { - overflow: auto; - overflow-y: hidden; /* fixes display issues on Chrome browsers */ -} - -span.pre { - -moz-hyphens: none; - -ms-hyphens: none; - -webkit-hyphens: none; - hyphens: none; -} - -td.linenos pre { - padding: 5px 0px; - border: 0; - background-color: transparent; - color: #aaa; -} - -table.highlighttable { - margin-left: 0.5em; -} - -table.highlighttable td { - padding: 0 0.5em 0 0.5em; -} - -div.code-block-caption { - padding: 2px 5px; - font-size: small; -} - -div.code-block-caption code { - background-color: transparent; -} - -div.code-block-caption + div > div.highlight > pre { - margin-top: 0; -} - -div.code-block-caption span.caption-number { - padding: 0.1em 0.3em; - font-style: italic; -} - -div.code-block-caption span.caption-text { -} - -div.literal-block-wrapper { - padding: 1em 1em 0; -} - -div.literal-block-wrapper div.highlight { - margin: 0; -} - -code.descname { - background-color: transparent; - font-weight: bold; - font-size: 1.2em; -} - -code.descclassname { - background-color: transparent; -} - -code.xref, a code { - background-color: transparent; - font-weight: bold; -} - -h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { - background-color: transparent; -} - -.viewcode-link { - float: right; -} - -.viewcode-back { - float: right; - font-family: sans-serif; -} - -div.viewcode-block:target { - margin: -1px -10px; - padding: 0 10px; -} - -/* -- math display ---------------------------------------------------------- */ - -img.math { - vertical-align: middle; -} - -div.body div.math p { - text-align: center; -} - -span.eqno { - float: right; -} - -span.eqno a.headerlink { - position: relative; - left: 0px; - z-index: 1; -} - -div.math:hover a.headerlink { - visibility: visible; -} - -/* -- printout stylesheet --------------------------------------------------- */ - -@media print { - div.document, - div.documentwrapper, - div.bodywrapper { - margin: 0 !important; - width: 100%; - } - - div.sphinxsidebar, - div.related, - div.footer, - #top-link { - display: none; - } -} \ No newline at end of file diff --git a/docs/_static/classic.css b/docs/_static/classic.css deleted file mode 100644 index 22fa0bde7..000000000 --- a/docs/_static/classic.css +++ /dev/null @@ -1,261 +0,0 @@ -/* - * classic.css_t - * ~~~~~~~~~~~~~ - * - * Sphinx stylesheet -- classic theme. - * - * :copyright: Copyright 2007-2017 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * - */ - -@import url("basic.css"); - -/* -- page layout ----------------------------------------------------------- */ - -body { - font-family: sans-serif; - font-size: 100%; - background-color: #11303d; - color: #000; - margin: 0; - padding: 0; -} - -div.document { - background-color: #1c4e63; -} - -div.documentwrapper { - float: left; - width: 100%; -} - -div.bodywrapper { - margin: 0 0 0 230px; -} - -div.body { - background-color: #ffffff; - color: #000000; - padding: 0 20px 30px 20px; -} - -div.footer { - color: #ffffff; - width: 100%; - padding: 9px 0 9px 0; - text-align: center; - font-size: 75%; -} - -div.footer a { - color: #ffffff; - text-decoration: underline; -} - -div.related { - background-color: #133f52; - line-height: 30px; - color: #ffffff; -} - -div.related a { - color: #ffffff; -} - -div.sphinxsidebar { -} - -div.sphinxsidebar h3 { - font-family: 'Trebuchet MS', sans-serif; - color: #ffffff; - font-size: 1.4em; - font-weight: normal; - margin: 0; - padding: 0; -} - -div.sphinxsidebar h3 a { - color: #ffffff; -} - -div.sphinxsidebar h4 { - font-family: 'Trebuchet MS', sans-serif; - color: #ffffff; - font-size: 1.3em; - font-weight: normal; - margin: 5px 0 0 0; - padding: 0; -} - -div.sphinxsidebar p { - color: #ffffff; -} - -div.sphinxsidebar p.topless { - margin: 5px 10px 10px 10px; -} - -div.sphinxsidebar ul { - margin: 10px; - padding: 0; - color: #ffffff; -} - -div.sphinxsidebar a { - color: #98dbcc; -} - -div.sphinxsidebar input { - border: 1px solid #98dbcc; - font-family: sans-serif; - font-size: 1em; -} - - - -/* -- hyperlink styles ------------------------------------------------------ */ - -a { - color: #355f7c; - text-decoration: none; -} - -a:visited { - color: #355f7c; - text-decoration: none; -} - -a:hover { - text-decoration: underline; -} - - - -/* -- body styles ----------------------------------------------------------- */ - -div.body h1, -div.body h2, -div.body h3, -div.body h4, -div.body h5, -div.body h6 { - font-family: 'Trebuchet MS', sans-serif; - background-color: #f2f2f2; - font-weight: normal; - color: #20435c; - border-bottom: 1px solid #ccc; - margin: 20px -20px 10px -20px; - padding: 3px 0 3px 10px; -} - -div.body h1 { margin-top: 0; font-size: 200%; } -div.body h2 { font-size: 160%; } -div.body h3 { font-size: 140%; } -div.body h4 { font-size: 120%; } -div.body h5 { font-size: 110%; } -div.body h6 { font-size: 100%; } - -a.headerlink { - color: #c60f0f; - font-size: 0.8em; - padding: 0 4px 0 4px; - text-decoration: none; -} - -a.headerlink:hover { - background-color: #c60f0f; - color: white; -} - -div.body p, div.body dd, div.body li, div.body blockquote { - text-align: justify; - line-height: 130%; -} - -div.admonition p.admonition-title + p { - display: inline; -} - -div.admonition p { - margin-bottom: 5px; -} - -div.admonition pre { - margin-bottom: 5px; -} - -div.admonition ul, div.admonition ol { - margin-bottom: 5px; -} - -div.note { - background-color: #eee; - border: 1px solid #ccc; -} - -div.seealso { - background-color: #ffc; - border: 1px solid #ff6; -} - -div.topic { - background-color: #eee; -} - -div.warning { - background-color: #ffe4e4; - border: 1px solid #f66; -} - -p.admonition-title { - display: inline; -} - -p.admonition-title:after { - content: ":"; -} - -pre { - padding: 5px; - background-color: #eeffcc; - color: #333333; - line-height: 120%; - border: 1px solid #ac9; - border-left: none; - border-right: none; -} - -code { - background-color: #ecf0f3; - padding: 0 1px 0 1px; - font-size: 0.95em; -} - -th { - background-color: #ede; -} - -.warning code { - background: #efc2c2; -} - -.note code { - background: #d6d6d6; -} - -.viewcode-back { - font-family: sans-serif; -} - -div.viewcode-block:target { - background-color: #f4debf; - border-top: 1px solid #ac9; - border-bottom: 1px solid #ac9; -} - -div.code-block-caption { - color: #efefef; - background-color: #1c4e63; -} \ No newline at end of file diff --git a/docs/_static/comment-bright.png b/docs/_static/comment-bright.png deleted file mode 100644 index 15e27edb1..000000000 Binary files a/docs/_static/comment-bright.png and /dev/null differ diff --git a/docs/_static/comment-close.png b/docs/_static/comment-close.png deleted file mode 100644 index 4d91bcf57..000000000 Binary files a/docs/_static/comment-close.png and /dev/null differ diff --git a/docs/_static/comment.png b/docs/_static/comment.png deleted file mode 100644 index dfbc0cbd5..000000000 Binary files a/docs/_static/comment.png and /dev/null differ diff --git a/docs/_static/default.css b/docs/_static/default.css deleted file mode 100644 index bf8f9173e..000000000 --- a/docs/_static/default.css +++ /dev/null @@ -1,50 +0,0 @@ -.section h1, .section .headerlink { - display: none; -} - -.section-title { - font-size: 2rem; - line-height: 2.5rem; - letter-spacing: -1px; - font-weight: 700; - font-family: Slack-Lato, appleLogo, sans-serif; - margin: 0 0 1rem; -} - -nav#api_nav .toctree-l1 { - margin-bottom: 1.5rem; -} - -nav#api_nav #api_sections ul li.toctree-l1>a { - color: #3aa3e3; - letter-spacing: 0; - font-size: .75rem; - font-weight: 700; - font-family: Slack-Lato, appleLogo, sans-serif; - text-transform: uppercase; - border: none; - padding: 0; -} - -nav#api_nav #api_sections ul li.toctree-l2 a { - color: #555459; - text-transform: none; - font-weight: inherit; - padding: 0; - color: #555459; - display: block; - text-overflow: ellipsis; - overflow: hidden; - white-space: nowrap; - font-size: .9375rem!important; -} - -nav#api_nav #api_sections ul li.toctree-l2 a:hover { - background-color: #edf7fd; - border-color: #dcf0fb -} - -nav#api_nav #footer #footer_nav a { - border: none; - padding: 0; -} \ No newline at end of file diff --git a/docs/_static/doctools.js b/docs/_static/doctools.js deleted file mode 100644 index 565497723..000000000 --- a/docs/_static/doctools.js +++ /dev/null @@ -1,287 +0,0 @@ -/* - * doctools.js - * ~~~~~~~~~~~ - * - * Sphinx JavaScript utilities for all documentation. - * - * :copyright: Copyright 2007-2017 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * - */ - -/** - * select a different prefix for underscore - */ -$u = _.noConflict(); - -/** - * make the code below compatible with browsers without - * an installed firebug like debugger -if (!window.console || !console.firebug) { - var names = ["log", "debug", "info", "warn", "error", "assert", "dir", - "dirxml", "group", "groupEnd", "time", "timeEnd", "count", "trace", - "profile", "profileEnd"]; - window.console = {}; - for (var i = 0; i < names.length; ++i) - window.console[names[i]] = function() {}; -} - */ - -/** - * small helper function to urldecode strings - */ -jQuery.urldecode = function(x) { - return decodeURIComponent(x).replace(/\+/g, ' '); -}; - -/** - * small helper function to urlencode strings - */ -jQuery.urlencode = encodeURIComponent; - -/** - * This function returns the parsed url parameters of the - * current request. Multiple values per key are supported, - * it will always return arrays of strings for the value parts. - */ -jQuery.getQueryParameters = function(s) { - if (typeof s == 'undefined') - s = document.location.search; - var parts = s.substr(s.indexOf('?') + 1).split('&'); - var result = {}; - for (var i = 0; i < parts.length; i++) { - var tmp = parts[i].split('=', 2); - var key = jQuery.urldecode(tmp[0]); - var value = jQuery.urldecode(tmp[1]); - if (key in result) - result[key].push(value); - else - result[key] = [value]; - } - return result; -}; - -/** - * highlight a given string on a jquery object by wrapping it in - * span elements with the given class name. - */ -jQuery.fn.highlightText = function(text, className) { - function highlight(node) { - if (node.nodeType == 3) { - var val = node.nodeValue; - var pos = val.toLowerCase().indexOf(text); - if (pos >= 0 && !jQuery(node.parentNode).hasClass(className)) { - var span = document.createElement("span"); - span.className = className; - span.appendChild(document.createTextNode(val.substr(pos, text.length))); - node.parentNode.insertBefore(span, node.parentNode.insertBefore( - document.createTextNode(val.substr(pos + text.length)), - node.nextSibling)); - node.nodeValue = val.substr(0, pos); - } - } - else if (!jQuery(node).is("button, select, textarea")) { - jQuery.each(node.childNodes, function() { - highlight(this); - }); - } - } - return this.each(function() { - highlight(this); - }); -}; - -/* - * backward compatibility for jQuery.browser - * This will be supported until firefox bug is fixed. - */ -if (!jQuery.browser) { - jQuery.uaMatch = function(ua) { - ua = ua.toLowerCase(); - - var match = /(chrome)[ \/]([\w.]+)/.exec(ua) || - /(webkit)[ \/]([\w.]+)/.exec(ua) || - /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) || - /(msie) ([\w.]+)/.exec(ua) || - ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || - []; - - return { - browser: match[ 1 ] || "", - version: match[ 2 ] || "0" - }; - }; - jQuery.browser = {}; - jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true; -} - -/** - * Small JavaScript module for the documentation. - */ -var Documentation = { - - init : function() { - this.fixFirefoxAnchorBug(); - this.highlightSearchWords(); - this.initIndexTable(); - - }, - - /** - * i18n support - */ - TRANSLATIONS : {}, - PLURAL_EXPR : function(n) { return n == 1 ? 0 : 1; }, - LOCALE : 'unknown', - - // gettext and ngettext don't access this so that the functions - // can safely bound to a different name (_ = Documentation.gettext) - gettext : function(string) { - var translated = Documentation.TRANSLATIONS[string]; - if (typeof translated == 'undefined') - return string; - return (typeof translated == 'string') ? translated : translated[0]; - }, - - ngettext : function(singular, plural, n) { - var translated = Documentation.TRANSLATIONS[singular]; - if (typeof translated == 'undefined') - return (n == 1) ? singular : plural; - return translated[Documentation.PLURALEXPR(n)]; - }, - - addTranslations : function(catalog) { - for (var key in catalog.messages) - this.TRANSLATIONS[key] = catalog.messages[key]; - this.PLURAL_EXPR = new Function('n', 'return +(' + catalog.plural_expr + ')'); - this.LOCALE = catalog.locale; - }, - - /** - * add context elements like header anchor links - */ - addContextElements : function() { - $('div[id] > :header:first').each(function() { - $('\u00B6'). - attr('href', '#' + this.id). - attr('title', _('Permalink to this headline')). - appendTo(this); - }); - $('dt[id]').each(function() { - $('\u00B6'). - attr('href', '#' + this.id). - attr('title', _('Permalink to this definition')). - appendTo(this); - }); - }, - - /** - * workaround a firefox stupidity - * see: https://bugzilla.mozilla.org/show_bug.cgi?id=645075 - */ - fixFirefoxAnchorBug : function() { - if (document.location.hash) - window.setTimeout(function() { - document.location.href += ''; - }, 10); - }, - - /** - * highlight the search words provided in the url in the text - */ - highlightSearchWords : function() { - var params = $.getQueryParameters(); - var terms = (params.highlight) ? params.highlight[0].split(/\s+/) : []; - if (terms.length) { - var body = $('div.body'); - if (!body.length) { - body = $('body'); - } - window.setTimeout(function() { - $.each(terms, function() { - body.highlightText(this.toLowerCase(), 'highlighted'); - }); - }, 10); - $('') - .appendTo($('#searchbox')); - } - }, - - /** - * init the domain index toggle buttons - */ - initIndexTable : function() { - var togglers = $('img.toggler').click(function() { - var src = $(this).attr('src'); - var idnum = $(this).attr('id').substr(7); - $('tr.cg-' + idnum).toggle(); - if (src.substr(-9) == 'minus.png') - $(this).attr('src', src.substr(0, src.length-9) + 'plus.png'); - else - $(this).attr('src', src.substr(0, src.length-8) + 'minus.png'); - }).css('display', ''); - if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) { - togglers.click(); - } - }, - - /** - * helper function to hide the search marks again - */ - hideSearchWords : function() { - $('#searchbox .highlight-link').fadeOut(300); - $('span.highlighted').removeClass('highlighted'); - }, - - /** - * make the url absolute - */ - makeURL : function(relativeURL) { - return DOCUMENTATION_OPTIONS.URL_ROOT + '/' + relativeURL; - }, - - /** - * get the current relative url - */ - getCurrentURL : function() { - var path = document.location.pathname; - var parts = path.split(/\//); - $.each(DOCUMENTATION_OPTIONS.URL_ROOT.split(/\//), function() { - if (this == '..') - parts.pop(); - }); - var url = parts.join('/'); - return path.substring(url.lastIndexOf('/') + 1, path.length - 1); - }, - - initOnKeyListeners: function() { - $(document).keyup(function(event) { - var activeElementType = document.activeElement.tagName; - // don't navigate when in search box or textarea - if (activeElementType !== 'TEXTAREA' && activeElementType !== 'INPUT' && activeElementType !== 'SELECT') { - switch (event.keyCode) { - case 37: // left - var prevHref = $('link[rel="prev"]').prop('href'); - if (prevHref) { - window.location.href = prevHref; - return false; - } - case 39: // right - var nextHref = $('link[rel="next"]').prop('href'); - if (nextHref) { - window.location.href = nextHref; - return false; - } - } - } - }); - } -}; - -// quick alias for translations -_ = Documentation.gettext; - -$(document).ready(function() { - Documentation.init(); -}); \ No newline at end of file diff --git a/docs/_static/down-pressed.png b/docs/_static/down-pressed.png deleted file mode 100644 index 5756c8cad..000000000 Binary files a/docs/_static/down-pressed.png and /dev/null differ diff --git a/docs/_static/down.png b/docs/_static/down.png deleted file mode 100644 index 1b3bdad2c..000000000 Binary files a/docs/_static/down.png and /dev/null differ diff --git a/docs/_static/file.png b/docs/_static/file.png deleted file mode 100644 index a858a410e..000000000 Binary files a/docs/_static/file.png and /dev/null differ diff --git a/docs/_static/jquery-3.1.0.js b/docs/_static/jquery-3.1.0.js deleted file mode 100644 index f2fc27478..000000000 --- a/docs/_static/jquery-3.1.0.js +++ /dev/null @@ -1,10074 +0,0 @@ -/*eslint-disable no-unused-vars*/ -/*! - * jQuery JavaScript Library v3.1.0 - * https://jquery.com/ - * - * Includes Sizzle.js - * https://sizzlejs.com/ - * - * Copyright jQuery Foundation and other contributors - * Released under the MIT license - * https://jquery.org/license - * - * Date: 2016-07-07T21:44Z - */ -( function( global, factory ) { - - "use strict"; - - if ( typeof module === "object" && typeof module.exports === "object" ) { - - // For CommonJS and CommonJS-like environments where a proper `window` - // is present, execute the factory and get jQuery. - // For environments that do not have a `window` with a `document` - // (such as Node.js), expose a factory as module.exports. - // This accentuates the need for the creation of a real `window`. - // e.g. var jQuery = require("jquery")(window); - // See ticket #14549 for more info. - module.exports = global.document ? - factory( global, true ) : - function( w ) { - if ( !w.document ) { - throw new Error( "jQuery requires a window with a document" ); - } - return factory( w ); - }; - } else { - factory( global ); - } - -// Pass this if window is not defined yet -} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) { - -// Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1 -// throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode -// arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common -// enough that all such attempts are guarded in a try block. -"use strict"; - -var arr = []; - -var document = window.document; - -var getProto = Object.getPrototypeOf; - -var slice = arr.slice; - -var concat = arr.concat; - -var push = arr.push; - -var indexOf = arr.indexOf; - -var class2type = {}; - -var toString = class2type.toString; - -var hasOwn = class2type.hasOwnProperty; - -var fnToString = hasOwn.toString; - -var ObjectFunctionString = fnToString.call( Object ); - -var support = {}; - - - - function DOMEval( code, doc ) { - doc = doc || document; - - var script = doc.createElement( "script" ); - - script.text = code; - doc.head.appendChild( script ).parentNode.removeChild( script ); - } -/* global Symbol */ -// Defining this global in .eslintrc would create a danger of using the global -// unguarded in another place, it seems safer to define global only for this module - - - -var - version = "3.1.0", - - // Define a local copy of jQuery - jQuery = function( selector, context ) { - - // The jQuery object is actually just the init constructor 'enhanced' - // Need init if jQuery is called (just allow error to be thrown if not included) - return new jQuery.fn.init( selector, context ); - }, - - // Support: Android <=4.0 only - // Make sure we trim BOM and NBSP - rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, - - // Matches dashed string for camelizing - rmsPrefix = /^-ms-/, - rdashAlpha = /-([a-z])/g, - - // Used by jQuery.camelCase as callback to replace() - fcamelCase = function( all, letter ) { - return letter.toUpperCase(); - }; - -jQuery.fn = jQuery.prototype = { - - // The current version of jQuery being used - jquery: version, - - constructor: jQuery, - - // The default length of a jQuery object is 0 - length: 0, - - toArray: function() { - return slice.call( this ); - }, - - // Get the Nth element in the matched element set OR - // Get the whole matched element set as a clean array - get: function( num ) { - return num != null ? - - // Return just the one element from the set - ( num < 0 ? this[ num + this.length ] : this[ num ] ) : - - // Return all the elements in a clean array - slice.call( this ); - }, - - // Take an array of elements and push it onto the stack - // (returning the new matched element set) - pushStack: function( elems ) { - - // Build a new jQuery matched element set - var ret = jQuery.merge( this.constructor(), elems ); - - // Add the old object onto the stack (as a reference) - ret.prevObject = this; - - // Return the newly-formed element set - return ret; - }, - - // Execute a callback for every element in the matched set. - each: function( callback ) { - return jQuery.each( this, callback ); - }, - - map: function( callback ) { - return this.pushStack( jQuery.map( this, function( elem, i ) { - return callback.call( elem, i, elem ); - } ) ); - }, - - slice: function() { - return this.pushStack( slice.apply( this, arguments ) ); - }, - - first: function() { - return this.eq( 0 ); - }, - - last: function() { - return this.eq( -1 ); - }, - - eq: function( i ) { - var len = this.length, - j = +i + ( i < 0 ? len : 0 ); - return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] ); - }, - - end: function() { - return this.prevObject || this.constructor(); - }, - - // For internal use only. - // Behaves like an Array's method, not like a jQuery method. - push: push, - sort: arr.sort, - splice: arr.splice -}; - -jQuery.extend = jQuery.fn.extend = function() { - var options, name, src, copy, copyIsArray, clone, - target = arguments[ 0 ] || {}, - i = 1, - length = arguments.length, - deep = false; - - // Handle a deep copy situation - if ( typeof target === "boolean" ) { - deep = target; - - // Skip the boolean and the target - target = arguments[ i ] || {}; - i++; - } - - // Handle case when target is a string or something (possible in deep copy) - if ( typeof target !== "object" && !jQuery.isFunction( target ) ) { - target = {}; - } - - // Extend jQuery itself if only one argument is passed - if ( i === length ) { - target = this; - i--; - } - - for ( ; i < length; i++ ) { - - // Only deal with non-null/undefined values - if ( ( options = arguments[ i ] ) != null ) { - - // Extend the base object - for ( name in options ) { - src = target[ name ]; - copy = options[ name ]; - - // Prevent never-ending loop - if ( target === copy ) { - continue; - } - - // Recurse if we're merging plain objects or arrays - if ( deep && copy && ( jQuery.isPlainObject( copy ) || - ( copyIsArray = jQuery.isArray( copy ) ) ) ) { - - if ( copyIsArray ) { - copyIsArray = false; - clone = src && jQuery.isArray( src ) ? src : []; - - } else { - clone = src && jQuery.isPlainObject( src ) ? src : {}; - } - - // Never move original objects, clone them - target[ name ] = jQuery.extend( deep, clone, copy ); - - // Don't bring in undefined values - } else if ( copy !== undefined ) { - target[ name ] = copy; - } - } - } - } - - // Return the modified object - return target; -}; - -jQuery.extend( { - - // Unique for each copy of jQuery on the page - expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ), - - // Assume jQuery is ready without the ready module - isReady: true, - - error: function( msg ) { - throw new Error( msg ); - }, - - noop: function() {}, - - isFunction: function( obj ) { - return jQuery.type( obj ) === "function"; - }, - - isArray: Array.isArray, - - isWindow: function( obj ) { - return obj != null && obj === obj.window; - }, - - isNumeric: function( obj ) { - - // As of jQuery 3.0, isNumeric is limited to - // strings and numbers (primitives or objects) - // that can be coerced to finite numbers (gh-2662) - var type = jQuery.type( obj ); - return ( type === "number" || type === "string" ) && - - // parseFloat NaNs numeric-cast false positives ("") - // ...but misinterprets leading-number strings, particularly hex literals ("0x...") - // subtraction forces infinities to NaN - !isNaN( obj - parseFloat( obj ) ); - }, - - isPlainObject: function( obj ) { - var proto, Ctor; - - // Detect obvious negatives - // Use toString instead of jQuery.type to catch host objects - if ( !obj || toString.call( obj ) !== "[object Object]" ) { - return false; - } - - proto = getProto( obj ); - - // Objects with no prototype (e.g., `Object.create( null )`) are plain - if ( !proto ) { - return true; - } - - // Objects with prototype are plain iff they were constructed by a global Object function - Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor; - return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString; - }, - - isEmptyObject: function( obj ) { - - /* eslint-disable no-unused-vars */ - // See https://github.com/eslint/eslint/issues/6125 - var name; - - for ( name in obj ) { - return false; - } - return true; - }, - - type: function( obj ) { - if ( obj == null ) { - return obj + ""; - } - - // Support: Android <=2.3 only (functionish RegExp) - return typeof obj === "object" || typeof obj === "function" ? - class2type[ toString.call( obj ) ] || "object" : - typeof obj; - }, - - // Evaluates a script in a global context - globalEval: function( code ) { - DOMEval( code ); - }, - - // Convert dashed to camelCase; used by the css and data modules - // Support: IE <=9 - 11, Edge 12 - 13 - // Microsoft forgot to hump their vendor prefix (#9572) - camelCase: function( string ) { - return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); - }, - - nodeName: function( elem, name ) { - return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); - }, - - each: function( obj, callback ) { - var length, i = 0; - - if ( isArrayLike( obj ) ) { - length = obj.length; - for ( ; i < length; i++ ) { - if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { - break; - } - } - } else { - for ( i in obj ) { - if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { - break; - } - } - } - - return obj; - }, - - // Support: Android <=4.0 only - trim: function( text ) { - return text == null ? - "" : - ( text + "" ).replace( rtrim, "" ); - }, - - // results is for internal usage only - makeArray: function( arr, results ) { - var ret = results || []; - - if ( arr != null ) { - if ( isArrayLike( Object( arr ) ) ) { - jQuery.merge( ret, - typeof arr === "string" ? - [ arr ] : arr - ); - } else { - push.call( ret, arr ); - } - } - - return ret; - }, - - inArray: function( elem, arr, i ) { - return arr == null ? -1 : indexOf.call( arr, elem, i ); - }, - - // Support: Android <=4.0 only, PhantomJS 1 only - // push.apply(_, arraylike) throws on ancient WebKit - merge: function( first, second ) { - var len = +second.length, - j = 0, - i = first.length; - - for ( ; j < len; j++ ) { - first[ i++ ] = second[ j ]; - } - - first.length = i; - - return first; - }, - - grep: function( elems, callback, invert ) { - var callbackInverse, - matches = [], - i = 0, - length = elems.length, - callbackExpect = !invert; - - // Go through the array, only saving the items - // that pass the validator function - for ( ; i < length; i++ ) { - callbackInverse = !callback( elems[ i ], i ); - if ( callbackInverse !== callbackExpect ) { - matches.push( elems[ i ] ); - } - } - - return matches; - }, - - // arg is for internal usage only - map: function( elems, callback, arg ) { - var length, value, - i = 0, - ret = []; - - // Go through the array, translating each of the items to their new values - if ( isArrayLike( elems ) ) { - length = elems.length; - for ( ; i < length; i++ ) { - value = callback( elems[ i ], i, arg ); - - if ( value != null ) { - ret.push( value ); - } - } - - // Go through every key on the object, - } else { - for ( i in elems ) { - value = callback( elems[ i ], i, arg ); - - if ( value != null ) { - ret.push( value ); - } - } - } - - // Flatten any nested arrays - return concat.apply( [], ret ); - }, - - // A global GUID counter for objects - guid: 1, - - // Bind a function to a context, optionally partially applying any - // arguments. - proxy: function( fn, context ) { - var tmp, args, proxy; - - if ( typeof context === "string" ) { - tmp = fn[ context ]; - context = fn; - fn = tmp; - } - - // Quick check to determine if target is callable, in the spec - // this throws a TypeError, but we will just return undefined. - if ( !jQuery.isFunction( fn ) ) { - return undefined; - } - - // Simulated bind - args = slice.call( arguments, 2 ); - proxy = function() { - return fn.apply( context || this, args.concat( slice.call( arguments ) ) ); - }; - - // Set the guid of unique handler to the same of original handler, so it can be removed - proxy.guid = fn.guid = fn.guid || jQuery.guid++; - - return proxy; - }, - - now: Date.now, - - // jQuery.support is not used in Core but other projects attach their - // properties to it so it needs to exist. - support: support -} ); - -if ( typeof Symbol === "function" ) { - jQuery.fn[ Symbol.iterator ] = arr[ Symbol.iterator ]; -} - -// Populate the class2type map -jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ), -function( i, name ) { - class2type[ "[object " + name + "]" ] = name.toLowerCase(); -} ); - -function isArrayLike( obj ) { - - // Support: real iOS 8.2 only (not reproducible in simulator) - // `in` check used to prevent JIT error (gh-2145) - // hasOwn isn't used here due to false negatives - // regarding Nodelist length in IE - var length = !!obj && "length" in obj && obj.length, - type = jQuery.type( obj ); - - if ( type === "function" || jQuery.isWindow( obj ) ) { - return false; - } - - return type === "array" || length === 0 || - typeof length === "number" && length > 0 && ( length - 1 ) in obj; -} -var Sizzle = -/*! - * Sizzle CSS Selector Engine v2.3.0 - * https://sizzlejs.com/ - * - * Copyright jQuery Foundation and other contributors - * Released under the MIT license - * http://jquery.org/license - * - * Date: 2016-01-04 - */ -(function( window ) { - -var i, - support, - Expr, - getText, - isXML, - tokenize, - compile, - select, - outermostContext, - sortInput, - hasDuplicate, - - // Local document vars - setDocument, - document, - docElem, - documentIsHTML, - rbuggyQSA, - rbuggyMatches, - matches, - contains, - - // Instance-specific data - expando = "sizzle" + 1 * new Date(), - preferredDoc = window.document, - dirruns = 0, - done = 0, - classCache = createCache(), - tokenCache = createCache(), - compilerCache = createCache(), - sortOrder = function( a, b ) { - if ( a === b ) { - hasDuplicate = true; - } - return 0; - }, - - // Instance methods - hasOwn = ({}).hasOwnProperty, - arr = [], - pop = arr.pop, - push_native = arr.push, - push = arr.push, - slice = arr.slice, - // Use a stripped-down indexOf as it's faster than native - // https://jsperf.com/thor-indexof-vs-for/5 - indexOf = function( list, elem ) { - var i = 0, - len = list.length; - for ( ; i < len; i++ ) { - if ( list[i] === elem ) { - return i; - } - } - return -1; - }, - - booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped", - - // Regular expressions - - // http://www.w3.org/TR/css3-selectors/#whitespace - whitespace = "[\\x20\\t\\r\\n\\f]", - - // http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier - identifier = "(?:\\\\.|[\\w-]|[^\0-\\xa0])+", - - // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors - attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace + - // Operator (capture 2) - "*([*^$|!~]?=)" + whitespace + - // "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]" - "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + whitespace + - "*\\]", - - pseudos = ":(" + identifier + ")(?:\\((" + - // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: - // 1. quoted (capture 3; capture 4 or capture 5) - "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + - // 2. simple (capture 6) - "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + - // 3. anything else (capture 2) - ".*" + - ")\\)|)", - - // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter - rwhitespace = new RegExp( whitespace + "+", "g" ), - rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ), - - rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), - rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ), - - rattributeQuotes = new RegExp( "=" + whitespace + "*([^\\]'\"]*?)" + whitespace + "*\\]", "g" ), - - rpseudo = new RegExp( pseudos ), - ridentifier = new RegExp( "^" + identifier + "$" ), - - matchExpr = { - "ID": new RegExp( "^#(" + identifier + ")" ), - "CLASS": new RegExp( "^\\.(" + identifier + ")" ), - "TAG": new RegExp( "^(" + identifier + "|[*])" ), - "ATTR": new RegExp( "^" + attributes ), - "PSEUDO": new RegExp( "^" + pseudos ), - "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace + - "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace + - "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), - "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), - // For use in libraries implementing .is() - // We use this for POS matching in `select` - "needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + - whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) - }, - - rinputs = /^(?:input|select|textarea|button)$/i, - rheader = /^h\d$/i, - - rnative = /^[^{]+\{\s*\[native \w/, - - // Easily-parseable/retrievable ID or TAG or CLASS selectors - rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, - - rsibling = /[+~]/, - - // CSS escapes - // http://www.w3.org/TR/CSS21/syndata.html#escaped-characters - runescape = new RegExp( "\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig" ), - funescape = function( _, escaped, escapedWhitespace ) { - var high = "0x" + escaped - 0x10000; - // NaN means non-codepoint - // Support: Firefox<24 - // Workaround erroneous numeric interpretation of +"0x" - return high !== high || escapedWhitespace ? - escaped : - high < 0 ? - // BMP codepoint - String.fromCharCode( high + 0x10000 ) : - // Supplemental Plane codepoint (surrogate pair) - String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); - }, - - // CSS string/identifier serialization - // https://drafts.csswg.org/cssom/#common-serializing-idioms - rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\x80-\uFFFF\w-]/g, - fcssescape = function( ch, asCodePoint ) { - if ( asCodePoint ) { - - // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER - if ( ch === "\0" ) { - return "\uFFFD"; - } - - // Control characters and (dependent upon position) numbers get escaped as code points - return ch.slice( 0, -1 ) + "\\" + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " "; - } - - // Other potentially-special ASCII characters get backslash-escaped - return "\\" + ch; - }, - - // Used for iframes - // See setDocument() - // Removing the function wrapper causes a "Permission Denied" - // error in IE - unloadHandler = function() { - setDocument(); - }, - - disabledAncestor = addCombinator( - function( elem ) { - return elem.disabled === true; - }, - { dir: "parentNode", next: "legend" } - ); - -// Optimize for push.apply( _, NodeList ) -try { - push.apply( - (arr = slice.call( preferredDoc.childNodes )), - preferredDoc.childNodes - ); - // Support: Android<4.0 - // Detect silently failing push.apply - arr[ preferredDoc.childNodes.length ].nodeType; -} catch ( e ) { - push = { apply: arr.length ? - - // Leverage slice if possible - function( target, els ) { - push_native.apply( target, slice.call(els) ); - } : - - // Support: IE<9 - // Otherwise append directly - function( target, els ) { - var j = target.length, - i = 0; - // Can't trust NodeList.length - while ( (target[j++] = els[i++]) ) {} - target.length = j - 1; - } - }; -} - -function Sizzle( selector, context, results, seed ) { - var m, i, elem, nid, match, groups, newSelector, - newContext = context && context.ownerDocument, - - // nodeType defaults to 9, since context defaults to document - nodeType = context ? context.nodeType : 9; - - results = results || []; - - // Return early from calls with invalid selector or context - if ( typeof selector !== "string" || !selector || - nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) { - - return results; - } - - // Try to shortcut find operations (as opposed to filters) in HTML documents - if ( !seed ) { - - if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) { - setDocument( context ); - } - context = context || document; - - if ( documentIsHTML ) { - - // If the selector is sufficiently simple, try using a "get*By*" DOM method - // (excepting DocumentFragment context, where the methods don't exist) - if ( nodeType !== 11 && (match = rquickExpr.exec( selector )) ) { - - // ID selector - if ( (m = match[1]) ) { - - // Document context - if ( nodeType === 9 ) { - if ( (elem = context.getElementById( m )) ) { - - // Support: IE, Opera, Webkit - // TODO: identify versions - // getElementById can match elements by name instead of ID - if ( elem.id === m ) { - results.push( elem ); - return results; - } - } else { - return results; - } - - // Element context - } else { - - // Support: IE, Opera, Webkit - // TODO: identify versions - // getElementById can match elements by name instead of ID - if ( newContext && (elem = newContext.getElementById( m )) && - contains( context, elem ) && - elem.id === m ) { - - results.push( elem ); - return results; - } - } - - // Type selector - } else if ( match[2] ) { - push.apply( results, context.getElementsByTagName( selector ) ); - return results; - - // Class selector - } else if ( (m = match[3]) && support.getElementsByClassName && - context.getElementsByClassName ) { - - push.apply( results, context.getElementsByClassName( m ) ); - return results; - } - } - - // Take advantage of querySelectorAll - if ( support.qsa && - !compilerCache[ selector + " " ] && - (!rbuggyQSA || !rbuggyQSA.test( selector )) ) { - - if ( nodeType !== 1 ) { - newContext = context; - newSelector = selector; - - // qSA looks outside Element context, which is not what we want - // Thanks to Andrew Dupont for this workaround technique - // Support: IE <=8 - // Exclude object elements - } else if ( context.nodeName.toLowerCase() !== "object" ) { - - // Capture the context ID, setting it first if necessary - if ( (nid = context.getAttribute( "id" )) ) { - nid = nid.replace( rcssescape, fcssescape ); - } else { - context.setAttribute( "id", (nid = expando) ); - } - - // Prefix every selector in the list - groups = tokenize( selector ); - i = groups.length; - while ( i-- ) { - groups[i] = "#" + nid + " " + toSelector( groups[i] ); - } - newSelector = groups.join( "," ); - - // Expand context for sibling selectors - newContext = rsibling.test( selector ) && testContext( context.parentNode ) || - context; - } - - if ( newSelector ) { - try { - push.apply( results, - newContext.querySelectorAll( newSelector ) - ); - return results; - } catch ( qsaError ) { - } finally { - if ( nid === expando ) { - context.removeAttribute( "id" ); - } - } - } - } - } - } - - // All others - return select( selector.replace( rtrim, "$1" ), context, results, seed ); -} - -/** - * Create key-value caches of limited size - * @returns {function(string, object)} Returns the Object data after storing it on itself with - * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) - * deleting the oldest entry - */ -function createCache() { - var keys = []; - - function cache( key, value ) { - // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) - if ( keys.push( key + " " ) > Expr.cacheLength ) { - // Only keep the most recent entries - delete cache[ keys.shift() ]; - } - return (cache[ key + " " ] = value); - } - return cache; -} - -/** - * Mark a function for special use by Sizzle - * @param {Function} fn The function to mark - */ -function markFunction( fn ) { - fn[ expando ] = true; - return fn; -} - -/** - * Support testing using an element - * @param {Function} fn Passed the created element and returns a boolean result - */ -function assert( fn ) { - var el = document.createElement("fieldset"); - - try { - return !!fn( el ); - } catch (e) { - return false; - } finally { - // Remove from its parent by default - if ( el.parentNode ) { - el.parentNode.removeChild( el ); - } - // release memory in IE - el = null; - } -} - -/** - * Adds the same handler for all of the specified attrs - * @param {String} attrs Pipe-separated list of attributes - * @param {Function} handler The method that will be applied - */ -function addHandle( attrs, handler ) { - var arr = attrs.split("|"), - i = arr.length; - - while ( i-- ) { - Expr.attrHandle[ arr[i] ] = handler; - } -} - -/** - * Checks document order of two siblings - * @param {Element} a - * @param {Element} b - * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b - */ -function siblingCheck( a, b ) { - var cur = b && a, - diff = cur && a.nodeType === 1 && b.nodeType === 1 && - a.sourceIndex - b.sourceIndex; - - // Use IE sourceIndex if available on both nodes - if ( diff ) { - return diff; - } - - // Check if b follows a - if ( cur ) { - while ( (cur = cur.nextSibling) ) { - if ( cur === b ) { - return -1; - } - } - } - - return a ? 1 : -1; -} - -/** - * Returns a function to use in pseudos for input types - * @param {String} type - */ -function createInputPseudo( type ) { - return function( elem ) { - var name = elem.nodeName.toLowerCase(); - return name === "input" && elem.type === type; - }; -} - -/** - * Returns a function to use in pseudos for buttons - * @param {String} type - */ -function createButtonPseudo( type ) { - return function( elem ) { - var name = elem.nodeName.toLowerCase(); - return (name === "input" || name === "button") && elem.type === type; - }; -} - -/** - * Returns a function to use in pseudos for :enabled/:disabled - * @param {Boolean} disabled true for :disabled; false for :enabled - */ -function createDisabledPseudo( disabled ) { - // Known :disabled false positives: - // IE: *[disabled]:not(button, input, select, textarea, optgroup, option, menuitem, fieldset) - // not IE: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable - return function( elem ) { - - // Check form elements and option elements for explicit disabling - return "label" in elem && elem.disabled === disabled || - "form" in elem && elem.disabled === disabled || - - // Check non-disabled form elements for fieldset[disabled] ancestors - "form" in elem && elem.disabled === false && ( - // Support: IE6-11+ - // Ancestry is covered for us - elem.isDisabled === disabled || - - // Otherwise, assume any non-