Skip to content

Commit

Permalink
Backport and improvements (#601)
Browse files Browse the repository at this point in the history
* Backport RedisStorage, deep-linking
* Allow prereleases for aioredis
* Bump dependencies
* Correctly skip Redis tests on Windows
* Reformat tests code and bump Makefile
  • Loading branch information
JrooTJunior committed Jun 14, 2021
1 parent 32bc051 commit 83d6ab4
Show file tree
Hide file tree
Showing 43 changed files with 996 additions and 319 deletions.
15 changes: 14 additions & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ jobs:
virtualenvs-create: true
virtualenvs-in-project: true

- name: Setup redis
if: ${{ matrix.os != 'windows-latest' }}
uses: shogo82148/actions-setup-redis@v1
with:
redis-version: 6

- name: Load cached venv
id: cached-poetry-dependencies
uses: actions/cache@v2
Expand All @@ -64,7 +70,14 @@ jobs:
run: |
poetry run black --check --diff aiogram tests
- name: Run tests
- name: Run tests (with Redis)
if: ${{ matrix.os != 'windows-latest' }}
run: |
poetry run pytest --cov=aiogram --cov-config .coveragerc --cov-report=xml --redis redis://localhost:6379/0
- name: Run tests (without Redis)
# Redis can't be used on GitHub Windows Runners
if: ${{ matrix.os == 'windows-latest' }}
run: |
poetry run pytest --cov=aiogram --cov-config .coveragerc --cov-report=xml
Expand Down
59 changes: 22 additions & 37 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,14 @@ base_python := python3
py := poetry run
python := $(py) python

package_dir := aiogram
tests_dir := tests
scripts_dir := scripts
code_dir := $(package_dir) $(tests_dir) $(scripts_dir)
reports_dir := reports

redis_connection := redis://localhost:6379

.PHONY: help
help:
@echo "======================================================================================="
Expand All @@ -17,22 +23,17 @@ help:
@echo " clean: Delete temporary files"
@echo ""
@echo "Code quality:"
@echo " isort: Run isort tool"
@echo " black: Run black tool"
@echo " flake8: Run flake8 tool"
@echo " flake8-report: Run flake8 with HTML reporting"
@echo " mypy: Run mypy tool"
@echo " mypy-report: Run mypy tool with HTML reporting"
@echo " lint: Run isort, black, flake8 and mypy tools"
@echo " lint: Lint code by isort, black, flake8 and mypy tools"
@echo " reformat: Reformat code by isort and black tools"
@echo ""
@echo "Tests:"
@echo " test: Run tests"
@echo " test-coverage: Run tests with HTML reporting (results + coverage)"
@echo " test-coverage-report: Open coverage report in default system web browser"
@echo ""
@echo "Documentation:"
@echo " docs: Build docs"
@echo " docs-serve: Serve docs for local development"
@echo " docs: Build docs"
@echo " docs-serve: Serve docs for local development"
@echo " docs-prepare-reports: Move all HTML reports to docs dir"
@echo ""
@echo "Project"
Expand Down Expand Up @@ -65,46 +66,30 @@ clean:
# Code quality
# =================================================================================================

.PHONY: isort
isort:
$(py) isort aiogram tests scripts

.PHONY: black
black:
$(py) black aiogram tests scripts

.PHONY: flake8
flake8:
$(py) flake8 aiogram

.PHONY: flake8-report
flake8-report:
mkdir -p $(reports_dir)/flake8
$(py) flake8 --format=html --htmldir=$(reports_dir)/flake8 aiogram

.PHONY: mypy
mypy:
$(py) mypy aiogram

.PHONY: mypy-report
mypy-report:
$(py) mypy aiogram --html-report $(reports_dir)/typechecking

.PHONY: lint
lint: isort black flake8 mypy
lint:
$(py) isort --check-only $(code_dir)
$(py) black --check --diff $(code_dir)
$(py) flake8 $(code_dir)
$(py) mypy $(package_dir)

.PHONY: reformat
reformat:
$(py) black $(code_dir)
$(py) isort $(code_dir)

# =================================================================================================
# Tests
# =================================================================================================

.PHONY: test
test:
$(py) pytest --cov=aiogram --cov-config .coveragerc tests/
$(py) pytest --cov=aiogram --cov-config .coveragerc tests/ --redis $(redis_connection)

.PHONY: test-coverage
test-coverage:
mkdir -p $(reports_dir)/tests/
$(py) pytest --cov=aiogram --cov-config .coveragerc --html=$(reports_dir)/tests/index.html tests/
$(py) pytest --cov=aiogram --cov-config .coveragerc --html=$(reports_dir)/tests/index.html tests/ --redis $(redis_connection)

.PHONY: test-coverage-report
test-coverage-report:
Expand Down
6 changes: 6 additions & 0 deletions aiogram/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
from .dispatcher.dispatcher import Dispatcher
from .dispatcher.middlewares.base import BaseMiddleware
from .dispatcher.router import Router
from .utils.text_decorations import html_decoration as _html_decoration
from .utils.text_decorations import markdown_decoration as _markdown_decoration

try:
import uvloop as _uvloop
Expand All @@ -15,6 +17,8 @@
pass

F = MagicFilter()
html = _html_decoration
md = _markdown_decoration

__all__ = (
"__api_version__",
Expand All @@ -29,6 +33,8 @@
"filters",
"handler",
"F",
"html",
"md",
)

__version__ = "3.0.0-alpha.8"
Expand Down
43 changes: 31 additions & 12 deletions aiogram/dispatcher/dispatcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import contextvars
import warnings
from asyncio import CancelledError, Future, Lock
from typing import Any, AsyncGenerator, Dict, Optional, Union, cast
from typing import Any, AsyncGenerator, Dict, Optional, Union

from .. import loggers
from ..client.bot import Bot
Expand All @@ -13,7 +13,6 @@
from ..utils.exceptions.base import TelegramAPIError
from .event.bases import UNHANDLED, SkipHandler
from .event.telegram import TelegramEventObserver
from .fsm.context import FSMContext
from .fsm.middleware import FSMContextMiddleware
from .fsm.storage.base import BaseStorage
from .fsm.storage.memory import MemoryStorage
Expand All @@ -32,7 +31,7 @@ def __init__(
self,
storage: Optional[BaseStorage] = None,
fsm_strategy: FSMStrategy = FSMStrategy.USER_IN_CHAT,
isolate_events: bool = True,
isolate_events: bool = False,
**kwargs: Any,
) -> None:
super(Dispatcher, self).__init__(**kwargs)
Expand Down Expand Up @@ -255,7 +254,9 @@ async def _process_update(
)
return True # because update was processed but unsuccessful

async def _polling(self, bot: Bot, polling_timeout: int = 30, **kwargs: Any) -> None:
async def _polling(
self, bot: Bot, polling_timeout: int = 30, handle_as_tasks: bool = True, **kwargs: Any
) -> None:
"""
Internal polling process
Expand All @@ -264,7 +265,11 @@ async def _polling(self, bot: Bot, polling_timeout: int = 30, **kwargs: Any) ->
:return:
"""
async for update in self._listen_updates(bot, polling_timeout=polling_timeout):
await self._process_update(bot=bot, update=update, **kwargs)
handle_update = self._process_update(bot=bot, update=update, **kwargs)
if handle_as_tasks:
asyncio.create_task(handle_update)
else:
await handle_update

async def _feed_webhook_update(self, bot: Bot, update: Update, **kwargs: Any) -> Any:
"""
Expand Down Expand Up @@ -342,11 +347,15 @@ def process_response(task: Future[Any]) -> None:

return None

async def start_polling(self, *bots: Bot, polling_timeout: int = 10, **kwargs: Any) -> None:
async def start_polling(
self, *bots: Bot, polling_timeout: int = 10, handle_as_tasks: bool = True, **kwargs: Any
) -> None:
"""
Polling runner
:param bots:
:param polling_timeout:
:param handle_as_tasks:
:param kwargs:
:return:
"""
Expand All @@ -363,7 +372,12 @@ async def start_polling(self, *bots: Bot, polling_timeout: int = 10, **kwargs: A
"Run polling for bot @%s id=%d - %r", user.username, bot.id, user.full_name
)
coro_list.append(
self._polling(bot=bot, polling_timeout=polling_timeout, **kwargs)
self._polling(
bot=bot,
handle_as_tasks=handle_as_tasks,
polling_timeout=polling_timeout,
**kwargs,
)
)
await asyncio.gather(*coro_list)
finally:
Expand All @@ -372,22 +386,27 @@ async def start_polling(self, *bots: Bot, polling_timeout: int = 10, **kwargs: A
loggers.dispatcher.info("Polling stopped")
await self.emit_shutdown(**workflow_data)

def run_polling(self, *bots: Bot, polling_timeout: int = 30, **kwargs: Any) -> None:
def run_polling(
self, *bots: Bot, polling_timeout: int = 30, handle_as_tasks: bool = True, **kwargs: Any
) -> None:
"""
Run many bots with polling
:param bots: Bot instances
:param polling_timeout: Poling timeout
:param handle_as_tasks: Run task for each event and no wait result
:param kwargs: contextual data
:return:
"""
try:
return asyncio.run(
self.start_polling(*bots, **kwargs, polling_timeout=polling_timeout)
self.start_polling(
*bots,
**kwargs,
polling_timeout=polling_timeout,
handle_as_tasks=handle_as_tasks,
)
)
except (KeyboardInterrupt, SystemExit): # pragma: no cover
# Allow to graceful shutdown
pass

def current_state(self, chat_id: int, user_id: int) -> FSMContext:
return cast(FSMContext, self.fsm.resolve_context(chat_id=chat_id, user_id=user_id))

0 comments on commit 83d6ab4

Please sign in to comment.