mirror of
https://github.com/python-telegram-bot/python-telegram-bot.git
synced 2024-12-23 06:50:29 +01:00
Merge remote-tracking branch 'origin/master' into default_parse_mode
This commit is contained in:
commit
b83376a3fe
25 changed files with 455 additions and 75 deletions
2
.github/CONTRIBUTING.rst
vendored
2
.github/CONTRIBUTING.rst
vendored
|
@ -25,7 +25,7 @@ Setting things up
|
|||
|
||||
.. code-block:: bash
|
||||
|
||||
$ sudo pip install -r requirements.txt -r requirements-dev.txt
|
||||
$ pip install -r requirements.txt -r requirements-dev.txt
|
||||
|
||||
|
||||
5. Install pre-commit hooks:
|
||||
|
|
|
@ -1,9 +1,16 @@
|
|||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: "[BUG]"
|
||||
labels: 'bug :bug:'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
Thanks for reporting issues of python-telegram-bot!
|
||||
|
||||
Use this template to notify us if you found a bug, or if you want to request a new feature.
|
||||
If you're looking for help with programming your bot using our library, feel free to ask your
|
||||
questions in out telegram group at: https://t.me/pythontelegrambotgroup
|
||||
Use this template to notify us if you found a bug.
|
||||
|
||||
To make it easier for us to help you please enter detailed information below.
|
||||
|
24
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
24
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
|
@ -0,0 +1,24 @@
|
|||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: "[FEATURE]"
|
||||
labels: enhancement
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
#### Is your feature request related to a problem? Please describe.
|
||||
A clear and concise description of what the problem is.
|
||||
Ex. *I want to do X, but there is no way to do it.*
|
||||
|
||||
#### Describe the solution you'd like
|
||||
A clear and concise description of what you want to happen.
|
||||
Ex. *I think it would be nice if you would add feature Y so it will make it easier.*
|
||||
|
||||
#### Describe alternatives you've considered
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
Ex. *I considered Z, but that didn't work because...*
|
||||
|
||||
#### Additional context
|
||||
Add any other context or screenshots about the feature request here.
|
||||
Ex. *Here's a photo of my cat!*
|
29
.github/ISSUE_TEMPLATE/question.md
vendored
Normal file
29
.github/ISSUE_TEMPLATE/question.md
vendored
Normal file
|
@ -0,0 +1,29 @@
|
|||
---
|
||||
name: Question
|
||||
about: Get help with errors or general questions
|
||||
title: "[QUESTION]"
|
||||
labels: 'question :question:'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
Hey there, you have a question? We are happy to answer. Please make sure no similar question was opened already.
|
||||
|
||||
The following template is a suggestion how you can report an issue you run into whilst using our library. If you just want to ask a question, feel free to delete everything; just make sure you have a describing title :)
|
||||
|
||||
Please mind that there is also a users' Telegram group at https://t.me/pythontelegrambotgroup for questions about the library. Questions asked there might be answered quicker than here. In case you are unable to join our group due to Telegram restrictions, you can use our IRC channel at https://webchat.freenode.net/?channels=##python-telegram-bot to participate in the group.
|
||||
-->
|
||||
|
||||
### Issue I am facing
|
||||
Please describe the issue here in as much detail as possible
|
||||
|
||||
### Traceback to the issue
|
||||
```
|
||||
put it here
|
||||
```
|
||||
|
||||
### Related part of your code
|
||||
```python
|
||||
put it here
|
||||
```
|
14
.github/workflows/example_notifier.yml
vendored
Normal file
14
.github/workflows/example_notifier.yml
vendored
Normal file
|
@ -0,0 +1,14 @@
|
|||
name: Warning maintainers
|
||||
on:
|
||||
pull_request:
|
||||
paths: examples/**
|
||||
jobs:
|
||||
job:
|
||||
runs-on: ubuntu-latest
|
||||
name: about example change
|
||||
steps:
|
||||
- name: running the check
|
||||
uses: Poolitzer/notifier-action@master
|
||||
with:
|
||||
notify-message: Hey there. Relax, I am just a little warning for the maintainers to release directly after merging your PR, otherwise we have broken examples and people might get confused :)
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
92
.github/workflows/test.yml
vendored
Normal file
92
.github/workflows/test.yml
vendored
Normal file
|
@ -0,0 +1,92 @@
|
|||
name: Testing your PR
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
pytest:
|
||||
name: pytest
|
||||
runs-on: ${{matrix.os}}
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: [2.7, 3.5, 3.6, 3.7]
|
||||
os: [ubuntu-latest, windows-latest]
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
python-version: 3.7
|
||||
test-build: True
|
||||
test-pre-commit: True
|
||||
- os: windows-latest
|
||||
python-version: 3.7
|
||||
test-build: True
|
||||
fail-fast: False
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Initialize vendored libs
|
||||
run:
|
||||
git submodule update --init --recursive
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -W ignore -m pip install --upgrade pip
|
||||
python -W ignore -m pip install -U codecov pytest-cov
|
||||
python -W ignore -m pip install -r requirements.txt
|
||||
python -W ignore -m pip install -r requirements-dev.txt
|
||||
|
||||
- name: Test with pytest
|
||||
run: |
|
||||
pytest -v -m nocoverage
|
||||
nocov_exit=$?
|
||||
pytest -v -m "not nocoverage" --cov
|
||||
cov_exit=$?
|
||||
global_exit=$(( nocov_exit > cov_exit ? nocov_exit : cov_exit ))
|
||||
exit ${global_exit}
|
||||
env:
|
||||
JOB_INDEX: ${{ strategy.job-index }}
|
||||
BOTS: W3sidG9rZW4iOiAiNjk2MTg4NzMyOkFBR1Z3RUtmSEhsTmpzY3hFRE5LQXdraEdzdFpfa28xbUMwIiwgInBheW1lbnRfcHJvdmlkZXJfdG9rZW4iOiAiMjg0Njg1MDYzOlRFU1Q6WldGaU1UUmxNbVF5TnpNeSIsICJib3RfbmFtZSI6ICJQVEIgdGVzdHMgb24gVHJhdmlzIHVzaW5nIENQeXRob24gMi43IiwgImJvdF91c2VybmFtZSI6ICJAcHRiX3RyYXZpc19jcHl0aG9uXzI3X2JvdCJ9LCB7InRva2VuIjogIjY3MTQ2ODg4NjpBQUdQR2ZjaVJJQlVORmU4MjR1SVZkcTdKZTNfWW5BVE5HdyIsICJwYXltZW50X3Byb3ZpZGVyX3Rva2VuIjogIjI4NDY4NTA2MzpURVNUOlpHWXdPVGxrTXpNeE4yWTIiLCAiYm90X25hbWUiOiAiUFRCIHRlc3RzIG9uIFRyYXZpcyB1c2luZyBDUHl0aG9uIDMuNCIsICJib3RfdXNlcm5hbWUiOiAiQHB0Yl90cmF2aXNfY3B5dGhvbl8zNF9ib3QifSwgeyJ0b2tlbiI6ICI2MjkzMjY1Mzg6QUFGUnJaSnJCN29CM211ekdzR0pYVXZHRTVDUXpNNUNVNG8iLCAicGF5bWVudF9wcm92aWRlcl90b2tlbiI6ICIyODQ2ODUwNjM6VEVTVDpNbU01WVdKaFl6a3hNMlUxIiwgImJvdF9uYW1lIjogIlBUQiB0ZXN0cyBvbiBUcmF2aXMgdXNpbmcgQ1B5dGhvbiAzLjUiLCAiYm90X3VzZXJuYW1lIjogIkBwdGJfdHJhdmlzX2NweXRob25fMzVfYm90In0sIHsidG9rZW4iOiAiNjQwMjA4OTQzOkFBRmhCalFwOXFtM1JUeFN6VXBZekJRakNsZS1Kano1aGNrIiwgInBheW1lbnRfcHJvdmlkZXJfdG9rZW4iOiAiMjg0Njg1MDYzOlRFU1Q6WXpoa1pUZzFOamMxWXpWbCIsICJib3RfbmFtZSI6ICJQVEIgdGVzdHMgb24gVHJhdmlzIHVzaW5nIENQeXRob24gMy42IiwgImJvdF91c2VybmFtZSI6ICJAcHRiX3RyYXZpc19jcHl0aG9uXzM2X2JvdCJ9LCB7InRva2VuIjogIjY5NTEwNDA4ODpBQUhmenlsSU9qU0lJUy1lT25JMjB5MkUyMEhvZEhzZnotMCIsICJwYXltZW50X3Byb3ZpZGVyX3Rva2VuIjogIjI4NDY4NTA2MzpURVNUOk9HUTFNRGd3WmpJd1pqRmwiLCAiYm90X25hbWUiOiAiUFRCIHRlc3RzIG9uIFRyYXZpcyB1c2luZyBDUHl0aG9uIDMuNyIsICJib3RfdXNlcm5hbWUiOiAiQHB0Yl90cmF2aXNfY3B5dGhvbl8zN19ib3QifSwgeyJ0b2tlbiI6ICI2OTE0MjM1NTQ6QUFGOFdrakNaYm5IcVBfaTZHaFRZaXJGRWxackdhWU9oWDAiLCAicGF5bWVudF9wcm92aWRlcl90b2tlbiI6ICIyODQ2ODUwNjM6VEVTVDpZamM1TlRoaU1tUXlNV1ZoIiwgImJvdF9uYW1lIjogIlBUQiB0ZXN0cyBvbiBUcmF2aXMgdXNpbmcgUHlQeSAyLjciLCAiYm90X3VzZXJuYW1lIjogIkBwdGJfdHJhdmlzX3B5cHlfMjdfYm90In0sIHsidG9rZW4iOiAiNjg0MzM5OTg0OkFBRk1nRUVqcDAxcjVyQjAwN3lDZFZOc2c4QWxOc2FVLWNjIiwgInBheW1lbnRfcHJvdmlkZXJfdG9rZW4iOiAiMjg0Njg1MDYzOlRFU1Q6TVRBek1UWTNNR1V5TmpnMCIsICJib3RfbmFtZSI6ICJQVEIgdGVzdHMgb24gVHJhdmlzIHVzaW5nIFB5UHkgMy41IiwgImJvdF91c2VybmFtZSI6ICJAcHRiX3RyYXZpc19weXB5XzM1X2JvdCJ9LCB7InRva2VuIjogIjY5MDA5MTM0NzpBQUZMbVI1cEFCNVljcGVfbU9oN3pNNEpGQk9oMHozVDBUbyIsICJwYXltZW50X3Byb3ZpZGVyX3Rva2VuIjogIjI4NDY4NTA2MzpURVNUOlpEaGxOekU1TURrd1lXSmkiLCAiYm90X25hbWUiOiAiUFRCIHRlc3RzIG9uIEFwcFZleW9yIHVzaW5nIENQeXRob24gMy40IiwgImJvdF91c2VybmFtZSI6ICJAcHRiX2FwcHZleW9yX2NweXRob25fMzRfYm90In0sIHsidG9rZW4iOiAiNjk0MzA4MDUyOkFBRUIyX3NvbkNrNTVMWTlCRzlBTy1IOGp4aVBTNTVvb0JBIiwgInBheW1lbnRfcHJvdmlkZXJfdG9rZW4iOiAiMjg0Njg1MDYzOlRFU1Q6WW1aaVlXWm1NakpoWkdNeSIsICJib3RfbmFtZSI6ICJQVEIgdGVzdHMgb24gQXBwVmV5b3IgdXNpbmcgQ1B5dGhvbiAyLjciLCAiYm90X3VzZXJuYW1lIjogIkBwdGJfYXBwdmV5b3JfY3B5dGhvbl8yN19ib3QifV0=
|
||||
TEST_BUILD: ${{ matrix.test-build }}
|
||||
TEST_PRE_COMMIT: ${{ matrix.test-pre-commit }}
|
||||
shell: bash --noprofile --norc {0}
|
||||
|
||||
- name: Submit coverage
|
||||
run: |
|
||||
if [ "$CODECOV_TOKEN" != "" ]; then
|
||||
codecov -F github -t $CODECOV_TOKEN --name "${{ matrix.os }}-${{ matrix.python-version }}"
|
||||
fi
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
shell: bash
|
||||
test_official:
|
||||
name: test-official
|
||||
runs-on: ${{matrix.os}}
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: [3.7]
|
||||
os: [ubuntu-latest]
|
||||
fail-fast: False
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Initialize vendored libs
|
||||
run:
|
||||
git submodule update --init --recursive
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -W ignore -m pip install --upgrade pip
|
||||
python -W ignore -m pip install -r requirements.txt
|
||||
python -W ignore -m pip install -r requirements-dev.txt
|
||||
- name: Compare to official api
|
||||
run: |
|
||||
pytest -v tests/test_official.py
|
||||
exit $?
|
||||
env:
|
||||
TEST_OFFICIAL: "true"
|
||||
shell: bash --noprofile --norc {0}
|
||||
|
|
@ -26,6 +26,7 @@ The following wonderful people contributed directly or indirectly to this projec
|
|||
- `d-qoi <https://github.com/d-qoi>`_
|
||||
- `daimajia <https://github.com/daimajia>`_
|
||||
- `Daniel Reed <https://github.com/nmlorg>`_
|
||||
- `Dmitry Grigoryev <https://github.com/icecom-dg>`_
|
||||
- `Ehsan Online <https://github.com/ehsanonline>`_
|
||||
- `Eli Gao <https://github.com/eligao>`_
|
||||
- `Emilio Molinari <https://github.com/xates>`_
|
||||
|
|
|
@ -192,11 +192,12 @@ You can get help in several ways:
|
|||
|
||||
1. We have a vibrant community of developers helping each other in our `Telegram group <https://telegram.me/pythontelegrambotgroup>`_. Join us!
|
||||
|
||||
2. Our `Wiki pages <https://github.com/python-telegram-bot/python-telegram-bot/wiki/>`_ offer a growing amount of resources.
|
||||
2. Report bugs, request new features or ask questions by `creating an issue <https://github.com/python-telegram-bot/python-telegram-bot/issues/new/choose>`_.
|
||||
|
||||
3. You can ask for help on Stack Overflow using the `python-telegram-bot tag <https://stackoverflow.com/questions/tagged/python-telegram-bot>`_.
|
||||
3. Our `Wiki pages <https://github.com/python-telegram-bot/python-telegram-bot/wiki/>`_ offer a growing amount of resources.
|
||||
|
||||
4. You can even ask for help on Stack Overflow using the `python-telegram-bot tag <https://stackoverflow.com/questions/tagged/python-telegram-bot>`_.
|
||||
|
||||
4. As last resort, the developers are ready to help you with `serious issues <https://github.com/python-telegram-bot/python-telegram-bot/issues/new>`_.
|
||||
|
||||
|
||||
============
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Examples
|
||||
|
||||
In this folder there are small examples to show what a bot written with `python-telegram-bot` looks like. Some bots focus on one specific aspect of the Telegram Bot API while others focus on one of the mechanics of this library. Except for the [`echobot.py`](#pure-api) example, they all use the high-level framework this library provides with the [`telegram.ext`](https://python-telegram-bot.readthedocs.io/en/latest/telegram.ext.html) submodule.
|
||||
In this folder are small examples to show what a bot written with `python-telegram-bot` looks like. Some bots focus on one specific aspect of the Telegram Bot API while others focus on one of the mechanics of this library. Except for the [`echobot.py`](#pure-api) example, they all use the high-level framework this library provides with the [`telegram.ext`](https://python-telegram-bot.readthedocs.io/en/latest/telegram.ext.html) submodule.
|
||||
|
||||
All examples are licensed under the [CC0 License](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/LICENSE.txt) and are therefore fully dedicated to the public domain. You can use them as the base for your own bots without worrying about copyrights.
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s
|
|||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def msg(bot, update):
|
||||
def msg(update, context):
|
||||
# If we received any passport data
|
||||
passport_data = update.message.passport_data
|
||||
if passport_data:
|
||||
|
@ -77,9 +77,9 @@ def msg(bot, update):
|
|||
actual_file.download()
|
||||
|
||||
|
||||
def error(bot, update, error):
|
||||
def error(update, context):
|
||||
"""Log Errors caused by Updates."""
|
||||
logger.warning('Update "%s" caused error "%s"', update, error)
|
||||
logger.warning('Update "%s" caused error "%s"', update, context.error)
|
||||
|
||||
|
||||
def main():
|
||||
|
|
|
@ -42,7 +42,7 @@ def start_with_shipping_callback(update, context):
|
|||
currency = "USD"
|
||||
# price in dollars
|
||||
price = 1
|
||||
# price * 100 so as to include 2 d.p.
|
||||
# price * 100 so as to include 2 decimal points
|
||||
# check https://core.telegram.org/bots/payments#supported-currencies for more details
|
||||
prices = [LabeledPrice("Test", price * 100)]
|
||||
|
||||
|
@ -66,7 +66,7 @@ def start_without_shipping_callback(update, context):
|
|||
currency = "USD"
|
||||
# price in dollars
|
||||
price = 1
|
||||
# price * 100 so as to include 2 d.p.
|
||||
# price * 100 so as to include 2 decimal points
|
||||
prices = [LabeledPrice("Test", price * 100)]
|
||||
|
||||
# optionally pass need_name=True, need_phone_number=True,
|
||||
|
|
|
@ -17,7 +17,8 @@
|
|||
"""Constants in the Telegram network.
|
||||
|
||||
The following constants were extracted from the
|
||||
`Telegram Bots FAQ <https://core.telegram.org/bots/faq>`_.
|
||||
`Telegram Bots FAQ <https://core.telegram.org/bots/faq>`_ and
|
||||
`Telegram Bots API <https://core.telegram.org/bots/api>`_.
|
||||
|
||||
Attributes:
|
||||
MAX_MESSAGE_LENGTH (:obj:`int`): 4096
|
||||
|
@ -25,6 +26,7 @@ Attributes:
|
|||
SUPPORTED_WEBHOOK_PORTS (List[:obj:`int`]): [443, 80, 88, 8443]
|
||||
MAX_FILESIZE_DOWNLOAD (:obj:`int`): In bytes (20MB)
|
||||
MAX_FILESIZE_UPLOAD (:obj:`int`): In bytes (50MB)
|
||||
MAX_PHOTOSIZE_UPLOAD (:obj:`int`): In bytes (10MB)
|
||||
MAX_MESSAGES_PER_SECOND_PER_CHAT (:obj:`int`): `1`. Telegram may allow short bursts that go
|
||||
over this limit, but eventually you'll begin receiving 429 errors.
|
||||
MAX_MESSAGES_PER_SECOND (:obj:`int`): 30
|
||||
|
@ -47,6 +49,7 @@ MAX_CAPTION_LENGTH = 1024
|
|||
SUPPORTED_WEBHOOK_PORTS = [443, 80, 88, 8443]
|
||||
MAX_FILESIZE_DOWNLOAD = int(20E6) # (20MB)
|
||||
MAX_FILESIZE_UPLOAD = int(50E6) # (50MB)
|
||||
MAX_PHOTOSIZE_UPLOAD = int(10E6) # (10MB)
|
||||
MAX_MESSAGES_PER_SECOND_PER_CHAT = 1
|
||||
MAX_MESSAGES_PER_SECOND = 30
|
||||
MAX_MESSAGES_PER_MINUTE_PER_GROUP = 20
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
|
||||
import logging
|
||||
import warnings
|
||||
from threading import Lock
|
||||
|
||||
from telegram import Update
|
||||
from telegram.ext import (Handler, CallbackQueryHandler, InlineQueryHandler,
|
||||
|
@ -37,8 +38,7 @@ class _ConversationTimeoutContext(object):
|
|||
class ConversationHandler(Handler):
|
||||
"""
|
||||
A handler to hold a conversation with a single user by managing four collections of other
|
||||
handlers. Note that neither posts in Telegram Channels, nor group interactions with multiple
|
||||
users are managed by instances of this class.
|
||||
handlers.
|
||||
|
||||
The first collection, a ``list`` named :attr:`entry_points`, is used to initiate the
|
||||
conversation, for example with a :class:`telegram.ext.CommandHandler` or
|
||||
|
@ -184,7 +184,9 @@ class ConversationHandler(Handler):
|
|||
self.map_to_parent = map_to_parent
|
||||
|
||||
self.timeout_jobs = dict()
|
||||
self._timeout_jobs_lock = Lock()
|
||||
self.conversations = dict()
|
||||
self._conversations_lock = Lock()
|
||||
|
||||
self.logger = logging.getLogger(__name__)
|
||||
|
||||
|
@ -262,7 +264,8 @@ class ConversationHandler(Handler):
|
|||
return None
|
||||
|
||||
key = self._get_key(update)
|
||||
state = self.conversations.get(key)
|
||||
with self._conversations_lock:
|
||||
state = self.conversations.get(key)
|
||||
|
||||
# Resolve promises
|
||||
if isinstance(state, tuple) and len(state) == 2 and isinstance(state[1], Promise):
|
||||
|
@ -281,7 +284,8 @@ class ConversationHandler(Handler):
|
|||
if res is None and old_state is None:
|
||||
res = self.END
|
||||
self.update_state(res, key)
|
||||
state = self.conversations.get(key)
|
||||
with self._conversations_lock:
|
||||
state = self.conversations.get(key)
|
||||
else:
|
||||
handlers = self.states.get(self.WAITING, [])
|
||||
for handler in handlers:
|
||||
|
@ -340,15 +344,22 @@ class ConversationHandler(Handler):
|
|||
|
||||
"""
|
||||
conversation_key, handler, check_result = check_result
|
||||
new_state = handler.handle_update(update, dispatcher, check_result, context)
|
||||
timeout_job = self.timeout_jobs.pop(conversation_key, None)
|
||||
|
||||
if timeout_job is not None:
|
||||
timeout_job.schedule_removal()
|
||||
if self.conversation_timeout and new_state != self.END:
|
||||
self.timeout_jobs[conversation_key] = dispatcher.job_queue.run_once(
|
||||
self._trigger_timeout, self.conversation_timeout,
|
||||
context=_ConversationTimeoutContext(conversation_key, update, dispatcher))
|
||||
with self._timeout_jobs_lock:
|
||||
# Remove the old timeout job (if present)
|
||||
timeout_job = self.timeout_jobs.pop(conversation_key, None)
|
||||
|
||||
if timeout_job is not None:
|
||||
timeout_job.schedule_removal()
|
||||
|
||||
new_state = handler.handle_update(update, dispatcher, check_result, context)
|
||||
|
||||
with self._timeout_jobs_lock:
|
||||
if self.conversation_timeout and new_state != self.END:
|
||||
# Add the new timeout job
|
||||
self.timeout_jobs[conversation_key] = dispatcher.job_queue.run_once(
|
||||
self._trigger_timeout, self.conversation_timeout,
|
||||
context=_ConversationTimeoutContext(conversation_key, update, dispatcher))
|
||||
|
||||
if isinstance(self.map_to_parent, dict) and new_state in self.map_to_parent:
|
||||
self.update_state(self.END, conversation_key)
|
||||
|
@ -358,33 +369,42 @@ class ConversationHandler(Handler):
|
|||
|
||||
def update_state(self, new_state, key):
|
||||
if new_state == self.END:
|
||||
if key in self.conversations:
|
||||
# If there is no key in conversations, nothing is done.
|
||||
del self.conversations[key]
|
||||
if self.persistent:
|
||||
self.persistence.update_conversation(self.name, key, None)
|
||||
with self._conversations_lock:
|
||||
if key in self.conversations:
|
||||
# If there is no key in conversations, nothing is done.
|
||||
del self.conversations[key]
|
||||
if self.persistent:
|
||||
self.persistence.update_conversation(self.name, key, None)
|
||||
|
||||
elif isinstance(new_state, Promise):
|
||||
self.conversations[key] = (self.conversations.get(key), new_state)
|
||||
if self.persistent:
|
||||
self.persistence.update_conversation(self.name, key,
|
||||
(self.conversations.get(key), new_state))
|
||||
with self._conversations_lock:
|
||||
self.conversations[key] = (self.conversations.get(key), new_state)
|
||||
if self.persistent:
|
||||
self.persistence.update_conversation(self.name, key,
|
||||
(self.conversations.get(key), new_state))
|
||||
|
||||
elif new_state is not None:
|
||||
self.conversations[key] = new_state
|
||||
if self.persistent:
|
||||
self.persistence.update_conversation(self.name, key, new_state)
|
||||
with self._conversations_lock:
|
||||
self.conversations[key] = new_state
|
||||
if self.persistent:
|
||||
self.persistence.update_conversation(self.name, key, new_state)
|
||||
|
||||
def _trigger_timeout(self, context, job=None):
|
||||
self.logger.debug('conversation timeout was triggered!')
|
||||
|
||||
# Backward compatibility with bots that do not use CallbackContext
|
||||
if isinstance(context, CallbackContext):
|
||||
context = context.job.context
|
||||
else:
|
||||
context = job.context
|
||||
job = context.job
|
||||
|
||||
context = job.context
|
||||
|
||||
with self._timeout_jobs_lock:
|
||||
found_job = self.timeout_jobs[context.conversation_key]
|
||||
if found_job is not job:
|
||||
# The timeout has been canceled in handle_update
|
||||
return
|
||||
del self.timeout_jobs[context.conversation_key]
|
||||
|
||||
del self.timeout_jobs[context.conversation_key]
|
||||
handlers = self.states.get(self.TIMEOUT, [])
|
||||
for handler in handlers:
|
||||
check = handler.check_update(context.update)
|
||||
|
|
|
@ -909,6 +909,39 @@ officedocument.wordprocessingml.document")``-
|
|||
return message.from_user.language_code and any(
|
||||
[message.from_user.language_code.startswith(x) for x in self.lang])
|
||||
|
||||
class msg_in(BaseFilter):
|
||||
"""Filters messages to only allow those whose text/caption appears in a given list.
|
||||
|
||||
Examples:
|
||||
A simple usecase is to allow only messages that were send by a custom
|
||||
:class:`telegram.ReplyKeyboardMarkup`::
|
||||
|
||||
buttons = ['Start', 'Settings', 'Back']
|
||||
markup = ReplyKeyboardMarkup.from_column(buttons)
|
||||
...
|
||||
MessageHandler(Filters.msg_in(buttons), callback_method)
|
||||
|
||||
Args:
|
||||
list_ (List[:obj:`str`]): Which messages to allow through. Only exact matches
|
||||
are allowed.
|
||||
caption (:obj:`bool`): Optional. Whether the caption should be used instead of text.
|
||||
Default is ``False``.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, list_, caption=False):
|
||||
self.list_ = list_
|
||||
self.caption = caption
|
||||
self.name = 'Filters.msg_in({!r}, caption={!r})'.format(self.list_, self.caption)
|
||||
|
||||
def filter(self, message):
|
||||
if self.caption:
|
||||
txt = message.caption
|
||||
else:
|
||||
txt = message.text
|
||||
|
||||
return txt in self.list_
|
||||
|
||||
class _UpdateType(BaseFilter):
|
||||
update_filter = True
|
||||
|
||||
|
|
|
@ -42,7 +42,8 @@ class JobQueue(object):
|
|||
Attributes:
|
||||
_queue (:obj:`PriorityQueue`): The queue that holds the Jobs.
|
||||
bot (:class:`telegram.Bot`): The bot instance that should be passed to the jobs.
|
||||
DEPRECATED: Use set_dispatcher instead.
|
||||
DEPRECATED: Use :attr:`set_dispatcher` instead.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, bot=None):
|
||||
|
@ -68,6 +69,13 @@ class JobQueue(object):
|
|||
self._running = False
|
||||
|
||||
def set_dispatcher(self, dispatcher):
|
||||
"""Set the dispatcher to be used by this JobQueue. Use this instead of passing a
|
||||
:class:`telegram.Bot` to the JobQueue, which is deprecated.
|
||||
|
||||
Args:
|
||||
dispatcher (:class:`telegram.ext.Dispatcher`): The dispatcher.
|
||||
|
||||
"""
|
||||
self._dispatcher = dispatcher
|
||||
|
||||
def _put(self, job, next_t=None, last_t=None):
|
||||
|
|
|
@ -742,13 +742,14 @@ class Message(TelegramObject):
|
|||
self._quote(kwargs)
|
||||
return self.bot.send_poll(self.chat_id, *args, **kwargs)
|
||||
|
||||
def forward(self, chat_id, disable_notification=False):
|
||||
def forward(self, chat_id, *args, **kwargs):
|
||||
"""Shortcut for::
|
||||
|
||||
bot.forward_message(chat_id=chat_id,
|
||||
from_chat_id=update.message.chat_id,
|
||||
disable_notification=disable_notification,
|
||||
message_id=update.message.message_id)
|
||||
message_id=update.message.message_id,
|
||||
*args,
|
||||
**kwargs)
|
||||
|
||||
Returns:
|
||||
:class:`telegram.Message`: On success, instance representing the message forwarded.
|
||||
|
@ -757,8 +758,9 @@ class Message(TelegramObject):
|
|||
return self.bot.forward_message(
|
||||
chat_id=chat_id,
|
||||
from_chat_id=self.chat_id,
|
||||
disable_notification=disable_notification,
|
||||
message_id=self.message_id)
|
||||
message_id=self.message_id,
|
||||
*args,
|
||||
**kwargs)
|
||||
|
||||
def edit_text(self, *args, **kwargs):
|
||||
"""Shortcut for::
|
||||
|
|
|
@ -17,11 +17,10 @@
|
|||
# You should have received a copy of the GNU Lesser Public License
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
"""Provide a bot to tests"""
|
||||
import json
|
||||
import base64
|
||||
import os
|
||||
import random
|
||||
import sys
|
||||
|
||||
from platform import python_implementation
|
||||
|
||||
# Provide some public fallbacks so it's easy for contributors to run tests on their local machine
|
||||
# These bots are only able to talk in our test chats, so they are quite useless for other
|
||||
|
@ -32,32 +31,42 @@ FALLBACKS = [
|
|||
'payment_provider_token': '284685063:TEST:NjQ0NjZlNzI5YjJi',
|
||||
'chat_id': '675666224',
|
||||
'super_group_id': '-1001493296829',
|
||||
'channel_id': '@pythontelegrambottests'
|
||||
'channel_id': '@pythontelegrambottests',
|
||||
'bot_name': 'PTB tests fallback 1',
|
||||
'bot_username': '@ptb_fallback_1_bot'
|
||||
}, {
|
||||
'token': '558194066:AAEEylntuKSLXj9odiv3TnX7Z5KY2J3zY3M',
|
||||
'payment_provider_token': '284685063:TEST:YjEwODQwMTFmNDcy',
|
||||
'chat_id': '675666224',
|
||||
'super_group_id': '-1001493296829',
|
||||
'channel_id': '@pythontelegrambottests'
|
||||
'channel_id': '@pythontelegrambottests',
|
||||
'bot_name': 'PTB tests fallback 2',
|
||||
'bot_username': '@ptb_fallback_2_bot'
|
||||
}
|
||||
]
|
||||
|
||||
GITHUB_ACTION = os.getenv('GITHUB_ACTION', None)
|
||||
BOTS = os.getenv('BOTS', None)
|
||||
JOB_INDEX = os.getenv('JOB_INDEX', None)
|
||||
if GITHUB_ACTION is not None and BOTS is not None and JOB_INDEX is not None:
|
||||
BOTS = json.loads(base64.b64decode(BOTS).decode('utf-8'))
|
||||
JOB_INDEX = int(JOB_INDEX)
|
||||
|
||||
|
||||
def get(name, fallback):
|
||||
full_name = '{0}_{1}_{2[0]}{2[1]}'.format(name, python_implementation(),
|
||||
sys.version_info).upper()
|
||||
# First try full_names such as
|
||||
# TOKEN_CPYTHON_33
|
||||
# CHAT_ID_PYPY_27
|
||||
val = os.getenv(full_name)
|
||||
if val:
|
||||
return val
|
||||
# Then try short names
|
||||
# TOKEN
|
||||
# CHAT_ID
|
||||
# If we have TOKEN, PAYMENT_PROVIDER_TOKEN, CHAT_ID, SUPER_GROUP_ID,
|
||||
# CHANNEL_ID, BOT_NAME, or BOT_USERNAME in the environment, then use that
|
||||
val = os.getenv(name.upper())
|
||||
if val:
|
||||
return val
|
||||
|
||||
# If we're running as a github action then fetch bots from the repo secrets
|
||||
if GITHUB_ACTION is not None and BOTS is not None and JOB_INDEX is not None:
|
||||
try:
|
||||
return BOTS[JOB_INDEX][name]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
# Otherwise go with the fallback
|
||||
return fallback
|
||||
|
||||
|
|
|
@ -38,6 +38,11 @@ TRAVIS = os.getenv('TRAVIS', False)
|
|||
if TRAVIS:
|
||||
pytest_plugins = ['tests.travis_fold']
|
||||
|
||||
GITHUB_ACTION = os.getenv('GITHUB_ACTION', False)
|
||||
|
||||
if GITHUB_ACTION:
|
||||
pytest_plugins = ['tests.plugin_github_group']
|
||||
|
||||
# THIS KEY IS OBVIOUSLY COMPROMISED
|
||||
# DO NOT USE IN PRODUCTION!
|
||||
PRIVATE_KEY = b"-----BEGIN RSA PRIVATE KEY-----\r\nMIIEowIBAAKCAQEA0AvEbNaOnfIL3GjB8VI4M5IaWe+GcK8eSPHkLkXREIsaddum\r\nwPBm/+w8lFYdnY+O06OEJrsaDtwGdU//8cbGJ/H/9cJH3dh0tNbfszP7nTrQD+88\r\nydlcYHzClaG8G+oTe9uEZSVdDXj5IUqR0y6rDXXb9tC9l+oSz+ShYg6+C4grAb3E\r\nSTv5khZ9Zsi/JEPWStqNdpoNuRh7qEYc3t4B/a5BH7bsQENyJSc8AWrfv+drPAEe\r\njQ8xm1ygzWvJp8yZPwOIYuL+obtANcoVT2G2150Wy6qLC0bD88Bm40GqLbSazueC\r\nRHZRug0B9rMUKvKc4FhG4AlNzBCaKgIcCWEqKwIDAQABAoIBACcIjin9d3Sa3S7V\r\nWM32JyVF3DvTfN3XfU8iUzV7U+ZOswA53eeFM04A/Ly4C4ZsUNfUbg72O8Vd8rg/\r\n8j1ilfsYpHVvphwxaHQlfIMa1bKCPlc/A6C7b2GLBtccKTbzjARJA2YWxIaqk9Nz\r\nMjj1IJK98i80qt29xRnMQ5sqOO3gn2SxTErvNchtBiwOH8NirqERXig8VCY6fr3n\r\nz7ZImPU3G/4qpD0+9ULrt9x/VkjqVvNdK1l7CyAuve3D7ha3jPMfVHFtVH5gqbyp\r\nKotyIHAyD+Ex3FQ1JV+H7DkP0cPctQiss7OiO9Zd9C1G2OrfQz9el7ewAPqOmZtC\r\nKjB3hUECgYEA/4MfKa1cvaCqzd3yUprp1JhvssVkhM1HyucIxB5xmBcVLX2/Kdhn\r\nhiDApZXARK0O9IRpFF6QVeMEX7TzFwB6dfkyIePsGxputA5SPbtBlHOvjZa8omMl\r\nEYfNa8x/mJkvSEpzvkWPascuHJWv1cEypqphu/70DxubWB5UKo/8o6cCgYEA0HFy\r\ncgwPMB//nltHGrmaQZPFT7/Qgl9ErZT3G9S8teWY4o4CXnkdU75tBoKAaJnpSfX3\r\nq8VuRerF45AFhqCKhlG4l51oW7TUH50qE3GM+4ivaH5YZB3biwQ9Wqw+QyNLAh/Q\r\nnS4/Wwb8qC9QuyEgcCju5lsCaPEXZiZqtPVxZd0CgYEAshBG31yZjO0zG1TZUwfy\r\nfN3euc8mRgZpSdXIHiS5NSyg7Zr8ZcUSID8jAkJiQ3n3OiAsuq1MGQ6kNa582kLT\r\nFPQdI9Ea8ahyDbkNR0gAY9xbM2kg/Gnro1PorH9PTKE0ekSodKk1UUyNrg4DBAwn\r\nqE6E3ebHXt/2WmqIbUD653ECgYBQCC8EAQNX3AFegPd1GGxU33Lz4tchJ4kMCNU0\r\nN2NZh9VCr3nTYjdTbxsXU8YP44CCKFG2/zAO4kymyiaFAWEOn5P7irGF/JExrjt4\r\nibGy5lFLEq/HiPtBjhgsl1O0nXlwUFzd7OLghXc+8CPUJaz5w42unqT3PBJa40c3\r\nQcIPdQKBgBnSb7BcDAAQ/Qx9juo/RKpvhyeqlnp0GzPSQjvtWi9dQRIu9Pe7luHc\r\nm1Img1EO1OyE3dis/rLaDsAa2AKu1Yx6h85EmNjavBqP9wqmFa0NIQQH8fvzKY3/\r\nP8IHY6009aoamLqYaexvrkHVq7fFKiI6k8myMJ6qblVNFv14+KXU\r\n-----END RSA PRIVATE KEY-----" # noqa: E501
|
||||
|
|
77
tests/plugin_github_group.py
Normal file
77
tests/plugin_github_group.py
Normal file
|
@ -0,0 +1,77 @@
|
|||
#!/usr/bin/env python
|
||||
#
|
||||
# A library that provides a Python interface to the Telegram Bot API
|
||||
# Copyright (C) 2015-2018
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser Public License
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
import _pytest.config
|
||||
import pytest
|
||||
|
||||
fold_plugins = {'_cov': 'Coverage report', 'flaky': 'Flaky report'}
|
||||
|
||||
|
||||
def terminal_summary_wrapper(original, plugin_name):
|
||||
text = fold_plugins[plugin_name]
|
||||
|
||||
def pytest_terminal_summary(terminalreporter):
|
||||
terminalreporter.write('##[group] {}\n'.format(text))
|
||||
original(terminalreporter)
|
||||
terminalreporter.write('##[endgroup]')
|
||||
|
||||
return pytest_terminal_summary
|
||||
|
||||
|
||||
@pytest.mark.trylast
|
||||
def pytest_configure(config):
|
||||
for hookimpl in config.pluginmanager.hook.pytest_terminal_summary._nonwrappers:
|
||||
if hookimpl.plugin_name in fold_plugins.keys():
|
||||
hookimpl.function = terminal_summary_wrapper(hookimpl.function,
|
||||
hookimpl.plugin_name)
|
||||
|
||||
|
||||
terminal = None
|
||||
previous_name = None
|
||||
|
||||
|
||||
def _get_name(location):
|
||||
if location[0].startswith('tests/'):
|
||||
return location[0][6:]
|
||||
return location[0]
|
||||
|
||||
|
||||
@pytest.mark.trylast
|
||||
def pytest_itemcollected(item):
|
||||
item._nodeid = item._nodeid.split('::', 1)[1]
|
||||
|
||||
|
||||
@pytest.hookimpl(hookwrapper=True, tryfirst=True)
|
||||
def pytest_runtest_protocol(item, nextitem):
|
||||
# This is naughty but pytests' own plugins does something similar too, so who cares
|
||||
global terminal
|
||||
if terminal is None:
|
||||
terminal = _pytest.config.create_terminal_writer(item.config)
|
||||
|
||||
global previous_name
|
||||
|
||||
name = _get_name(item.location)
|
||||
|
||||
if previous_name is None or previous_name != name:
|
||||
previous_name = name
|
||||
terminal.write('\n##[group] {}'.format(name))
|
||||
|
||||
yield
|
||||
|
||||
if nextitem is None or _get_name(nextitem.location) != name:
|
||||
terminal.write('\n##[endgroup]')
|
|
@ -114,13 +114,14 @@ class TestBot(object):
|
|||
@pytest.mark.timeout(10)
|
||||
def test_delete_message(self, bot, chat_id):
|
||||
message = bot.send_message(chat_id, text='will be deleted')
|
||||
time.sleep(2)
|
||||
|
||||
assert bot.delete_message(chat_id=chat_id, message_id=message.message_id) is True
|
||||
|
||||
@flaky(3, 1)
|
||||
@pytest.mark.timeout(10)
|
||||
def test_delete_message_old_message(self, bot, chat_id):
|
||||
with pytest.raises(TelegramError, match='Message to delete not found'):
|
||||
with pytest.raises(BadRequest):
|
||||
# Considering that the first message is old enough
|
||||
bot.delete_message(chat_id=chat_id, message_id=1)
|
||||
|
||||
|
|
|
@ -613,6 +613,53 @@ class TestConversationHandler(object):
|
|||
assert handler.conversations.get((self.group.id, user1.id)) is None
|
||||
assert not self.is_timeout
|
||||
|
||||
def test_conversation_timeout_cancel_conflict(self, dp, bot, user1):
|
||||
# Start state machine, wait half the timeout,
|
||||
# then call a callback that takes more than the timeout
|
||||
# t=0 /start (timeout=.5)
|
||||
# t=.25 /slowbrew (sleep .5)
|
||||
# | t=.5 original timeout (should not execute)
|
||||
# | t=.75 /slowbrew returns (timeout=1.25)
|
||||
# t=1.25 timeout
|
||||
|
||||
def slowbrew(_bot, update):
|
||||
sleep(0.25)
|
||||
# Let's give to the original timeout a chance to execute
|
||||
dp.job_queue.tick()
|
||||
sleep(0.25)
|
||||
# By returning None we do not override the conversation state so
|
||||
# we can see if the timeout has been executed
|
||||
|
||||
states = self.states
|
||||
states[self.THIRSTY].append(CommandHandler('slowbrew', slowbrew))
|
||||
states.update({ConversationHandler.TIMEOUT: [
|
||||
MessageHandler(None, self.passout2)
|
||||
]})
|
||||
|
||||
handler = ConversationHandler(entry_points=self.entry_points, states=states,
|
||||
fallbacks=self.fallbacks, conversation_timeout=0.5)
|
||||
dp.add_handler(handler)
|
||||
|
||||
# CommandHandler timeout
|
||||
message = Message(0, user1, None, self.group, text='/start',
|
||||
entities=[MessageEntity(type=MessageEntity.BOT_COMMAND, offset=0,
|
||||
length=len('/start'))],
|
||||
bot=bot)
|
||||
dp.process_update(Update(update_id=0, message=message))
|
||||
sleep(0.25)
|
||||
dp.job_queue.tick()
|
||||
message.text = '/slowbrew'
|
||||
message.entities[0].length = len('/slowbrew')
|
||||
dp.process_update(Update(update_id=0, message=message))
|
||||
dp.job_queue.tick()
|
||||
assert handler.conversations.get((self.group.id, user1.id)) is not None
|
||||
assert not self.is_timeout
|
||||
|
||||
sleep(0.5)
|
||||
dp.job_queue.tick()
|
||||
assert handler.conversations.get((self.group.id, user1.id)) is None
|
||||
assert self.is_timeout
|
||||
|
||||
def test_per_message_warning_is_only_shown_once(self, recwarn):
|
||||
ConversationHandler(
|
||||
entry_points=self.entry_points,
|
||||
|
|
|
@ -604,6 +604,16 @@ class TestFilters(object):
|
|||
update.message.from_user.language_code = 'da'
|
||||
assert f(update)
|
||||
|
||||
def test_msg_in_filter(self, update):
|
||||
update.message.text = 'test'
|
||||
update.message.caption = 'caption'
|
||||
|
||||
assert Filters.msg_in(['test'])(update)
|
||||
assert Filters.msg_in(['caption'], caption=True)(update)
|
||||
|
||||
assert not Filters.msg_in(['test'], caption=True)(update)
|
||||
assert not Filters.msg_in(['caption'])(update)
|
||||
|
||||
def test_and_filters(self, update):
|
||||
update.message.text = 'test'
|
||||
update.message.forward_date = datetime.datetime.now()
|
||||
|
|
|
@ -40,6 +40,8 @@ def job_queue(bot, _dp):
|
|||
|
||||
|
||||
@pytest.mark.skipif(os.getenv('APPVEYOR'), reason="On Appveyor precise timings are not accurate.")
|
||||
@pytest.mark.skipif(os.getenv('GITHUB_ACTIONS', False) and os.name == 'nt',
|
||||
reason="On windows precise timings are not accurate.")
|
||||
@flaky(10, 1) # Timings aren't quite perfect
|
||||
class TestJobQueue(object):
|
||||
result = 0
|
||||
|
|
|
@ -26,6 +26,8 @@ import telegram.ext.messagequeue as mq
|
|||
|
||||
|
||||
@pytest.mark.skipif(os.getenv('APPVEYOR'), reason="On Appveyor precise timings are not accurate.")
|
||||
@pytest.mark.skipif(os.getenv('GITHUB_ACTIONS', False) and os.name == 'nt',
|
||||
reason="On windows precise timings are not accurate.")
|
||||
class TestDelayQueue(object):
|
||||
N = 128
|
||||
burst_limit = 30
|
||||
|
|
|
@ -17,8 +17,6 @@
|
|||
# You should have received a copy of the GNU Lesser Public License
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
import os
|
||||
import sys
|
||||
from platform import python_implementation
|
||||
|
||||
import pytest
|
||||
|
||||
|
@ -30,17 +28,12 @@ def call_pre_commit_hook(hook_id):
|
|||
|
||||
@pytest.mark.nocoverage
|
||||
@pytest.mark.parametrize('hook_id', argvalues=('yapf', 'flake8', 'pylint'))
|
||||
@pytest.mark.skipif(not (os.getenv('TRAVIS') or os.getenv('APPVEYOR')), reason='Not running in CI')
|
||||
@pytest.mark.skipif(not sys.version_info[:2] == (3, 6) or python_implementation() != 'CPython',
|
||||
reason='Only running pre-commit-hooks on newest tested python version, '
|
||||
'as they are slow and consistent across platforms.')
|
||||
@pytest.mark.skipif(not os.getenv('TEST_PRE_COMMIT', False), reason='TEST_PRE_COMMIT not enabled')
|
||||
def test_pre_commit_hook(hook_id):
|
||||
assert call_pre_commit_hook(hook_id) == 0 # pragma: no cover
|
||||
|
||||
|
||||
@pytest.mark.nocoverage
|
||||
@pytest.mark.skipif(
|
||||
not sys.version_info[:2] in ((3, 6), (2, 7)) or python_implementation() != 'CPython',
|
||||
reason='Only testing build on 2.7 and 3.6')
|
||||
@pytest.mark.skipif(not os.getenv('TEST_BUILD', False), reason='TEST_BUILD not enabled')
|
||||
def test_build():
|
||||
assert os.system('python setup.py bdist_dumb') == 0 # pragma: no cover
|
||||
|
|
Loading…
Reference in a new issue