mirror of
https://github.com/aiogram/aiogram.git
synced 2025-12-06 07:50:32 +00:00
Migrate to hatchling (#1095)
* Migrate to hatchling instead of poetry, ruff instead of flake8 * Migrate to hatchling instead of poetry, ruff instead of flake8 * Update tests suite * venv? * -m venv? * Change dependencies * Remove venv * Change mypy config * Added changelog * Mark uvloop incompatible with pypy * Update release script * Use internal caching for dependencies * Re-disable cov branches * Added contributing guide
This commit is contained in:
parent
04ccb390d5
commit
f4ce4431f9
58 changed files with 799 additions and 3001 deletions
|
|
@ -1,6 +0,0 @@
|
||||||
[report]
|
|
||||||
exclude_lines =
|
|
||||||
pragma: no cover
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
@abstractmethod
|
|
||||||
@overload
|
|
||||||
12
.flake8
12
.flake8
|
|
@ -1,12 +0,0 @@
|
||||||
[flake8]
|
|
||||||
max-line-length = 99
|
|
||||||
select = C,E,F,W,B,B950
|
|
||||||
ignore = E501,W503,E203
|
|
||||||
exclude =
|
|
||||||
.git
|
|
||||||
build
|
|
||||||
dist
|
|
||||||
venv
|
|
||||||
docs
|
|
||||||
*.egg-info
|
|
||||||
experiment.py
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
name: Deploy
|
name: "Deploy"
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
|
@ -17,16 +17,12 @@ jobs:
|
||||||
with:
|
with:
|
||||||
python-version: "3.10"
|
python-version: "3.10"
|
||||||
|
|
||||||
- name: Install and configure Poetry
|
- name: Install build dependencies
|
||||||
uses: snok/install-poetry@v1
|
run: python -m pip install --upgrade build
|
||||||
with:
|
|
||||||
version: 1.2.1
|
- name: Build source distribution
|
||||||
virtualenvs-create: false
|
run: python -m build .
|
||||||
installer-parallel: true
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
run: |
|
|
||||||
poetry build
|
|
||||||
- name: Try install wheel
|
- name: Try install wheel
|
||||||
run: |
|
run: |
|
||||||
pip install -U virtualenv
|
pip install -U virtualenv
|
||||||
|
|
@ -53,17 +49,17 @@ jobs:
|
||||||
name: dist
|
name: dist
|
||||||
path: dist
|
path: dist
|
||||||
|
|
||||||
# - name: Publish a Python distribution to Test PyPI
|
# - name: Publish a Python distribution to Test PyPI
|
||||||
# uses: pypa/gh-action-pypi-publish@master
|
# uses: pypa/gh-action-pypi-publish@master
|
||||||
## if: github.event.action != 'published'
|
## if: github.event.action != 'published'
|
||||||
# with:
|
# with:
|
||||||
# user: __token__
|
# user: __token__
|
||||||
# password: ${{ secrets.PYPI_TEST_TOKEN }}
|
# password: ${{ secrets.PYPI_TEST_TOKEN }}
|
||||||
# repository_url: https://test.pypi.org/legacy/
|
# repository_url: https://test.pypi.org/legacy/
|
||||||
|
|
||||||
- name: Publish a Python distribution to PyPI
|
- name: Publish a Python distribution to PyPI
|
||||||
uses: pypa/gh-action-pypi-publish@master
|
uses: pypa/gh-action-pypi-publish@master
|
||||||
# if: github.event.action == 'published'
|
# if: github.event.action == 'published'
|
||||||
with:
|
with:
|
||||||
user: __token__
|
user: __token__
|
||||||
password: ${{ secrets.PYPI_TOKEN }}
|
password: ${{ secrets.PYPI_TOKEN }}
|
||||||
61
.github/workflows/tests.yml
vendored
61
.github/workflows/tests.yml
vendored
|
|
@ -8,11 +8,7 @@ on:
|
||||||
- ".github/workflows/tests.yml"
|
- ".github/workflows/tests.yml"
|
||||||
- "aiogram/**"
|
- "aiogram/**"
|
||||||
- "tests/**"
|
- "tests/**"
|
||||||
- ".coveragerc"
|
|
||||||
- ".flake8"
|
|
||||||
- "codecov.yaml"
|
- "codecov.yaml"
|
||||||
- "mypy.ini"
|
|
||||||
- "poetry.lock"
|
|
||||||
- "pyproject.toml"
|
- "pyproject.toml"
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
|
|
@ -21,11 +17,6 @@ on:
|
||||||
- ".github/workflows/tests.yml"
|
- ".github/workflows/tests.yml"
|
||||||
- "aiogram/**"
|
- "aiogram/**"
|
||||||
- "tests/**"
|
- "tests/**"
|
||||||
- ".coveragerc"
|
|
||||||
- ".flake8"
|
|
||||||
- "codecov.yaml"
|
|
||||||
- "mypy.ini"
|
|
||||||
- "poetry.lock"
|
|
||||||
- "pyproject.toml"
|
- "pyproject.toml"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|
@ -67,30 +58,25 @@ jobs:
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@master
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Set up Python ${{ matrix.python-version }} on ${{ matrix.os }}
|
- name: Set up Python ${{ matrix.python-version }} on ${{ matrix.os }}
|
||||||
uses: actions/setup-python@v4
|
uses: actions/setup-python@v4
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
|
cache: "pip"
|
||||||
|
cache-dependency-path: pyproject.toml
|
||||||
|
|
||||||
- name: Install and configure Poetry
|
- name: Install project dependencies
|
||||||
uses: snok/install-poetry@v1
|
|
||||||
if: "env.IS_PYPY == 'false' || env.IS_WINDOWS == 'false'"
|
|
||||||
with:
|
|
||||||
version: 1.2.1
|
|
||||||
virtualenvs-create: true
|
|
||||||
virtualenvs-in-project: true
|
|
||||||
installer-parallel: true
|
|
||||||
|
|
||||||
- name: Install and configure Poetry (PyPy on Windows)
|
|
||||||
if: "env.IS_PYPY == 'true' && env.IS_WINDOWS == 'true'"
|
|
||||||
run: |
|
run: |
|
||||||
set -eu
|
pip install -e .[dev,test,redis,proxy,i18n,fast]
|
||||||
pip install "poetry==1.2.1"
|
|
||||||
poetry config virtualenvs.create true
|
- name: Lint code
|
||||||
poetry config virtualenvs.in-project true
|
if: "env.IS_PYPY == 'false'"
|
||||||
poetry config installer.parallel true
|
run: |
|
||||||
|
ruff --format=github aiogram examples
|
||||||
|
mypy aiogram
|
||||||
|
black --check --diff aiogram tests
|
||||||
|
|
||||||
- name: Setup redis
|
- name: Setup redis
|
||||||
if: ${{ env.IS_WINDOWS == 'false' }}
|
if: ${{ env.IS_WINDOWS == 'false' }}
|
||||||
|
|
@ -98,33 +84,12 @@ jobs:
|
||||||
with:
|
with:
|
||||||
redis-version: 6
|
redis-version: 6
|
||||||
|
|
||||||
- name: Load cached venv
|
|
||||||
id: cached-poetry-dependencies
|
|
||||||
uses: actions/cache@v2
|
|
||||||
with:
|
|
||||||
path: .venv
|
|
||||||
key: venv-${{ matrix.os }}-${{ matrix.python-version }}-${{ hashFiles('**/poetry.lock') }}-${{ secrets.CACHE_VERSION }}
|
|
||||||
|
|
||||||
- name: Install project dependencies
|
|
||||||
if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true'
|
|
||||||
run: |
|
|
||||||
flags=""
|
|
||||||
[[ "$IS_PYPY" == "false" ]] && flags="$flags -E fast"
|
|
||||||
poetry install --no-interaction -E redis -E proxy -E i18n $flags
|
|
||||||
|
|
||||||
- name: Lint code
|
|
||||||
if: "env.IS_PYPY == 'false'"
|
|
||||||
run: |
|
|
||||||
poetry run flake8 aiogram
|
|
||||||
poetry run mypy aiogram
|
|
||||||
poetry run black --check --diff aiogram tests
|
|
||||||
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: |
|
run: |
|
||||||
flags=""
|
flags=""
|
||||||
[[ "$IS_PYPY" == "false" ]] && flags="$flags --cov=aiogram --cov-config .coveragerc --cov-report=xml"
|
[[ "$IS_PYPY" == "false" ]] && flags="$flags --cov=aiogram --cov-config .coveragerc --cov-report=xml"
|
||||||
[[ "$IS_WINDOWS" == "false" ]] && flags="$flags --redis redis://localhost:6379/0"
|
[[ "$IS_WINDOWS" == "false" ]] && flags="$flags --redis redis://localhost:6379/0"
|
||||||
poetry run pytest $flags
|
pytest $flags
|
||||||
|
|
||||||
- name: Upload coverage data
|
- name: Upload coverage data
|
||||||
if: "env.IS_PYPY == 'false'"
|
if: "env.IS_PYPY == 'false'"
|
||||||
|
|
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -13,7 +13,7 @@ dist/
|
||||||
site/
|
site/
|
||||||
*.egg-info/
|
*.egg-info/
|
||||||
*.egg
|
*.egg
|
||||||
aiogram/_meta.py
|
.ruff_cache
|
||||||
|
|
||||||
.mypy_cache
|
.mypy_cache
|
||||||
.pytest_cache
|
.pytest_cache
|
||||||
|
|
|
||||||
|
|
@ -19,34 +19,8 @@ repos:
|
||||||
- id: black
|
- id: black
|
||||||
files: &files '^(aiogram|tests|examples)'
|
files: &files '^(aiogram|tests|examples)'
|
||||||
|
|
||||||
- repo: https://github.com/pre-commit/mirrors-isort
|
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||||
rev: v5.10.1
|
rev: 'v0.0.215'
|
||||||
hooks:
|
hooks:
|
||||||
- id: isort
|
- id: ruff
|
||||||
additional_dependencies: [ toml ]
|
args: [ "--force-exclude" ]
|
||||||
files: *files
|
|
||||||
|
|
||||||
- repo: https://gitlab.com/pycqa/flake8
|
|
||||||
rev: 3.9.2
|
|
||||||
hooks:
|
|
||||||
- id: flake8
|
|
||||||
args: [ '--config=.flake8' ]
|
|
||||||
files: *files
|
|
||||||
|
|
||||||
- repo: https://github.com/floatingpurr/sync_with_poetry
|
|
||||||
rev: 0.2.0
|
|
||||||
hooks:
|
|
||||||
- id: sync_with_poetry
|
|
||||||
|
|
||||||
- repo: https://github.com/python-poetry/poetry
|
|
||||||
rev: '1.2.1'
|
|
||||||
hooks:
|
|
||||||
- id: poetry-check
|
|
||||||
- id: poetry-lock
|
|
||||||
args: [ "--no-update" ]
|
|
||||||
- id: poetry-export
|
|
||||||
args: [ "-f", "requirements.txt", "--without-hashes", "-o", "requirements/base.txt" ]
|
|
||||||
- id: poetry-export
|
|
||||||
args: [ "-f", "requirements.txt", "--without-hashes", "-o", "requirements/docs.txt",
|
|
||||||
"-E", "fast", "-E", "redis", "-E", "proxy", "-E", "i18n",
|
|
||||||
"--with", "docs" ]
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
version: 2
|
version: 2
|
||||||
|
|
||||||
python:
|
python:
|
||||||
version: "3.8"
|
version: "3.10"
|
||||||
install:
|
install:
|
||||||
- method: pip
|
- method: pip
|
||||||
path: .
|
path: .
|
||||||
- requirements: requirements/docs.txt
|
extra_requirements:
|
||||||
|
- docs
|
||||||
|
- redis
|
||||||
|
|
||||||
sphinx:
|
sphinx:
|
||||||
configuration: docs/conf.py
|
configuration: docs/conf.py
|
||||||
|
|
|
||||||
1
CHANGES/1095.misc.rst
Normal file
1
CHANGES/1095.misc.rst
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Updated package metadata, moved build internals from Poetry to Hatch, added contributing guides.
|
||||||
1
CONTRIBUTING.md
Normal file
1
CONTRIBUTING.md
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Please read the [Contributing](https://docs.aiogram.dev/en/dev-3.x/contributing/) guidelines in the documentation site.
|
||||||
79
Makefile
79
Makefile
|
|
@ -1,8 +1,4 @@
|
||||||
.DEFAULT_GOAL := help
|
.DEFAULT_GOAL := lint
|
||||||
|
|
||||||
base_python := python3
|
|
||||||
py := poetry run
|
|
||||||
python := $(py) python
|
|
||||||
|
|
||||||
package_dir := aiogram
|
package_dir := aiogram
|
||||||
tests_dir := tests
|
tests_dir := tests
|
||||||
|
|
@ -13,43 +9,10 @@ reports_dir := reports
|
||||||
|
|
||||||
redis_connection := redis://localhost:6379
|
redis_connection := redis://localhost:6379
|
||||||
|
|
||||||
.PHONY: help
|
|
||||||
help:
|
|
||||||
@echo "======================================================================================="
|
|
||||||
@echo " aiogram build tools "
|
|
||||||
@echo "======================================================================================="
|
|
||||||
@echo "Environment:"
|
|
||||||
@echo " help: Show this message"
|
|
||||||
@echo " install: Install development dependencies"
|
|
||||||
@echo " clean: Delete temporary files"
|
|
||||||
@echo ""
|
|
||||||
@echo "Code quality:"
|
|
||||||
@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-prepare-reports: Move all HTML reports to docs dir"
|
|
||||||
@echo ""
|
|
||||||
@echo "Project"
|
|
||||||
@echo " build: Run tests build package and docs"
|
|
||||||
@echo ""
|
|
||||||
|
|
||||||
# =================================================================================================
|
# =================================================================================================
|
||||||
# Environment
|
# Environment
|
||||||
# =================================================================================================
|
# =================================================================================================
|
||||||
|
|
||||||
.PHONY: install
|
|
||||||
install:
|
|
||||||
poetry install --all-extras
|
|
||||||
$(py) pre-commit install
|
|
||||||
|
|
||||||
.PHONY: clean
|
.PHONY: clean
|
||||||
clean:
|
clean:
|
||||||
rm -rf `find . -name __pycache__`
|
rm -rf `find . -name __pycache__`
|
||||||
|
|
@ -68,16 +31,15 @@ clean:
|
||||||
|
|
||||||
.PHONY: lint
|
.PHONY: lint
|
||||||
lint:
|
lint:
|
||||||
$(py) isort --check-only $(code_dir)
|
isort --check-only $(code_dir)
|
||||||
$(py) black --check --diff $(code_dir)
|
black --check --diff $(code_dir)
|
||||||
$(py) flake8 $(code_dir)
|
ruff $(package_dir)
|
||||||
$(py) mypy $(package_dir)
|
mypy $(package_dir)
|
||||||
# TODO: wemake-python-styleguide
|
|
||||||
|
|
||||||
.PHONY: reformat
|
.PHONY: reformat
|
||||||
reformat:
|
reformat:
|
||||||
$(py) black $(code_dir)
|
black $(code_dir)
|
||||||
$(py) isort $(code_dir)
|
isort $(code_dir)
|
||||||
|
|
||||||
# =================================================================================================
|
# =================================================================================================
|
||||||
# Tests
|
# Tests
|
||||||
|
|
@ -88,17 +50,17 @@ test-run-services:
|
||||||
|
|
||||||
.PHONY: test
|
.PHONY: test
|
||||||
test: test-run-services
|
test: test-run-services
|
||||||
$(py) pytest --cov=aiogram --cov-config .coveragerc tests/ --redis $(redis_connection)
|
pytest --cov=aiogram --cov-config .coveragerc tests/ --redis $(redis_connection)
|
||||||
|
|
||||||
.PHONY: test-coverage
|
.PHONY: test-coverage
|
||||||
test-coverage: test-run-services
|
test-coverage: test-run-services
|
||||||
mkdir -p $(reports_dir)/tests/
|
mkdir -p $(reports_dir)/tests/
|
||||||
$(py) pytest --cov=aiogram --cov-config .coveragerc --html=$(reports_dir)/tests/index.html tests/ --redis $(redis_connection)
|
pytest --cov=aiogram --cov-config .coveragerc --html=$(reports_dir)/tests/index.html tests/ --redis $(redis_connection)
|
||||||
$(py) coverage html -d $(reports_dir)/coverage
|
coverage html -d $(reports_dir)/coverage
|
||||||
|
|
||||||
.PHONY: test-coverage-view
|
.PHONY: test-coverage-view
|
||||||
test-coverage-view:
|
test-coverage-view:
|
||||||
$(py) coverage html -d $(reports_dir)/coverage
|
coverage html -d $(reports_dir)/coverage
|
||||||
python -c "import webbrowser; webbrowser.open('file://$(shell pwd)/reports/coverage/index.html')"
|
python -c "import webbrowser; webbrowser.open('file://$(shell pwd)/reports/coverage/index.html')"
|
||||||
|
|
||||||
# =================================================================================================
|
# =================================================================================================
|
||||||
|
|
@ -117,7 +79,7 @@ docs-gettext:
|
||||||
|
|
||||||
docs-serve:
|
docs-serve:
|
||||||
#rm -rf docs/_build
|
#rm -rf docs/_build
|
||||||
$(py) sphinx-autobuild --watch aiogram/ --watch CHANGELOG.rst --watch README.rst docs/ docs/_build/ $(OPTS)
|
sphinx-autobuild --watch aiogram/ --watch CHANGELOG.rst --watch README.rst docs/ docs/_build/ $(OPTS)
|
||||||
.PHONY: docs-serve
|
.PHONY: docs-serve
|
||||||
|
|
||||||
$(locale_targets): docs-serve-%:
|
$(locale_targets): docs-serve-%:
|
||||||
|
|
@ -129,15 +91,13 @@ $(locale_targets): docs-serve-%:
|
||||||
# =================================================================================================
|
# =================================================================================================
|
||||||
|
|
||||||
.PHONY: build
|
.PHONY: build
|
||||||
build: clean flake8-report mypy-report test-coverage
|
build: clean
|
||||||
mkdir -p site/simple
|
hatch build
|
||||||
poetry build
|
|
||||||
mv dist site/simple/aiogram
|
|
||||||
|
|
||||||
.PHONY: bump
|
.PHONY: bump
|
||||||
bump:
|
bump:
|
||||||
poetry version $(args)
|
hatch version $(args)
|
||||||
$(python) scripts/bump_versions.py
|
python scripts/bump_versions.py
|
||||||
|
|
||||||
.PHONY: towncrier-build
|
.PHONY: towncrier-build
|
||||||
towncrier-build:
|
towncrier-build:
|
||||||
|
|
@ -160,10 +120,3 @@ release:
|
||||||
git add .
|
git add .
|
||||||
git commit -m "Release $(shell poetry version -s)"
|
git commit -m "Release $(shell poetry version -s)"
|
||||||
git tag v$(shell poetry version -s)
|
git tag v$(shell poetry version -s)
|
||||||
|
|
||||||
_poetry_export_args := --format requirements.txt --without-hashes
|
|
||||||
|
|
||||||
.PHONY: export-requirements
|
|
||||||
export-requirements:
|
|
||||||
poetry export $(_poetry_export_args) --output requirements/base.txt
|
|
||||||
poetry export $(_poetry_export_args) --output requirements/docs.txt -E fast -E redis -E proxy -E i18n --with docs
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,9 @@
|
||||||
from aiogram.dispatcher.flags import FlagGenerator
|
from contextlib import suppress
|
||||||
|
|
||||||
|
from aiogram.dispatcher.flags import FlagGenerator
|
||||||
|
from . import methods
|
||||||
|
from . import types
|
||||||
|
from . import enums
|
||||||
from .client import session
|
from .client import session
|
||||||
from .client.bot import Bot
|
from .client.bot import Bot
|
||||||
from .dispatcher.dispatcher import Dispatcher
|
from .dispatcher.dispatcher import Dispatcher
|
||||||
|
|
@ -9,12 +13,10 @@ from .utils.magic_filter import MagicFilter
|
||||||
from .utils.text_decorations import html_decoration as html
|
from .utils.text_decorations import html_decoration as html
|
||||||
from .utils.text_decorations import markdown_decoration as md
|
from .utils.text_decorations import markdown_decoration as md
|
||||||
|
|
||||||
try:
|
with suppress(ImportError):
|
||||||
import uvloop as _uvloop
|
import uvloop as _uvloop
|
||||||
|
|
||||||
_uvloop.install()
|
_uvloop.install()
|
||||||
except ImportError: # pragma: no cover
|
|
||||||
pass
|
|
||||||
|
|
||||||
F = MagicFilter()
|
F = MagicFilter()
|
||||||
flags = FlagGenerator()
|
flags = FlagGenerator()
|
||||||
|
|
@ -24,6 +26,7 @@ __all__ = (
|
||||||
"__version__",
|
"__version__",
|
||||||
"types",
|
"types",
|
||||||
"methods",
|
"methods",
|
||||||
|
"enums",
|
||||||
"Bot",
|
"Bot",
|
||||||
"session",
|
"session",
|
||||||
"Dispatcher",
|
"Dispatcher",
|
||||||
|
|
|
||||||
|
|
@ -310,10 +310,9 @@ class Bot(ContextInstanceMixin["Bot"]):
|
||||||
if isinstance(destination, (str, pathlib.Path)):
|
if isinstance(destination, (str, pathlib.Path)):
|
||||||
await self.__download_file(destination=destination, stream=stream)
|
await self.__download_file(destination=destination, stream=stream)
|
||||||
return None
|
return None
|
||||||
else:
|
return await self.__download_file_binary_io(
|
||||||
return await self.__download_file_binary_io(
|
destination=destination, seek=seek, stream=stream
|
||||||
destination=destination, seek=seek, stream=stream
|
)
|
||||||
)
|
|
||||||
finally:
|
finally:
|
||||||
if close_stream:
|
if close_stream:
|
||||||
await stream.aclose()
|
await stream.aclose()
|
||||||
|
|
|
||||||
|
|
@ -48,18 +48,22 @@ def _retrieve_basic(basic: _ProxyBasic) -> Dict[str, Any]:
|
||||||
username = proxy_auth.login
|
username = proxy_auth.login
|
||||||
password = proxy_auth.password
|
password = proxy_auth.password
|
||||||
|
|
||||||
return dict(
|
return {
|
||||||
proxy_type=proxy_type,
|
"proxy_type": proxy_type,
|
||||||
host=host,
|
"host": host,
|
||||||
port=port,
|
"port": port,
|
||||||
username=username,
|
"username": username,
|
||||||
password=password,
|
"password": password,
|
||||||
rdns=True,
|
"rdns": True,
|
||||||
)
|
}
|
||||||
|
|
||||||
|
|
||||||
def _prepare_connector(chain_or_plain: _ProxyType) -> Tuple[Type["TCPConnector"], Dict[str, Any]]:
|
def _prepare_connector(chain_or_plain: _ProxyType) -> Tuple[Type["TCPConnector"], Dict[str, Any]]:
|
||||||
from aiohttp_socks import ChainProxyConnector, ProxyConnector, ProxyInfo # type: ignore
|
from aiohttp_socks import ( # type: ignore
|
||||||
|
ChainProxyConnector,
|
||||||
|
ProxyConnector,
|
||||||
|
ProxyInfo,
|
||||||
|
)
|
||||||
|
|
||||||
# since tuple is Iterable(compatible with _ProxyChain) object, we assume that
|
# since tuple is Iterable(compatible with _ProxyChain) object, we assume that
|
||||||
# user wants chained proxies if tuple is a pair of string(url) and BasicAuth
|
# user wants chained proxies if tuple is a pair of string(url) and BasicAuth
|
||||||
|
|
@ -74,7 +78,7 @@ def _prepare_connector(chain_or_plain: _ProxyType) -> Tuple[Type["TCPConnector"]
|
||||||
for basic in chain_or_plain:
|
for basic in chain_or_plain:
|
||||||
infos.append(ProxyInfo(**_retrieve_basic(basic)))
|
infos.append(ProxyInfo(**_retrieve_basic(basic)))
|
||||||
|
|
||||||
return ChainProxyConnector, dict(proxy_infos=infos)
|
return ChainProxyConnector, {"proxy_infos": infos}
|
||||||
|
|
||||||
|
|
||||||
class AiohttpSession(BaseSession):
|
class AiohttpSession(BaseSession):
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,17 @@ import json
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
from types import TracebackType
|
from types import TracebackType
|
||||||
from typing import TYPE_CHECKING, Any, AsyncGenerator, Callable, Final, Optional, Type, Union, cast
|
from typing import (
|
||||||
|
TYPE_CHECKING,
|
||||||
|
Any,
|
||||||
|
AsyncGenerator,
|
||||||
|
Callable,
|
||||||
|
Final,
|
||||||
|
Optional,
|
||||||
|
Type,
|
||||||
|
Union,
|
||||||
|
cast,
|
||||||
|
)
|
||||||
|
|
||||||
from pydantic import ValidationError
|
from pydantic import ValidationError
|
||||||
|
|
||||||
|
|
@ -165,8 +175,7 @@ class BaseSession(abc.ABC):
|
||||||
return str(round(value.timestamp()))
|
return str(round(value.timestamp()))
|
||||||
if isinstance(value, Enum):
|
if isinstance(value, Enum):
|
||||||
return self.prepare_value(value.value)
|
return self.prepare_value(value.value)
|
||||||
else:
|
return str(value)
|
||||||
return str(value)
|
|
||||||
|
|
||||||
def clean_json(self, value: Any) -> Any:
|
def clean_json(self, value: Any) -> Any:
|
||||||
"""
|
"""
|
||||||
|
|
@ -174,7 +183,7 @@ class BaseSession(abc.ABC):
|
||||||
"""
|
"""
|
||||||
if isinstance(value, list):
|
if isinstance(value, list):
|
||||||
return [self.clean_json(v) for v in value if v is not None]
|
return [self.clean_json(v) for v in value if v is not None]
|
||||||
elif isinstance(value, dict):
|
if isinstance(value, dict):
|
||||||
return {k: self.clean_json(v) for k, v in value.items() if v is not None}
|
return {k: self.clean_json(v) for k, v in value.items() if v is not None}
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,8 @@ class TelegramAPIServer:
|
||||||
file: str
|
file: str
|
||||||
"""Files URL"""
|
"""Files URL"""
|
||||||
is_local: bool = False
|
is_local: bool = False
|
||||||
"""Mark this server is in `local mode <https://core.telegram.org/bots/api#using-a-local-bot-api-server>`_."""
|
"""Mark this server is
|
||||||
|
in `local mode <https://core.telegram.org/bots/api#using-a-local-bot-api-server>`_."""
|
||||||
wrap_local_file: FilesPathWrapper = BareFilesPathWrapper()
|
wrap_local_file: FilesPathWrapper = BareFilesPathWrapper()
|
||||||
"""Callback to wrap files path in local mode"""
|
"""Callback to wrap files path in local mode"""
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -107,13 +107,13 @@ class Dispatcher(Router):
|
||||||
return self.fsm.storage
|
return self.fsm.storage
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def parent_router(self) -> None:
|
def parent_router(self) -> Optional[Router]:
|
||||||
"""
|
"""
|
||||||
Dispatcher has no parent router and can't be included to any other routers or dispatchers
|
Dispatcher has no parent router and can't be included to any other routers or dispatchers
|
||||||
|
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
return None
|
return None # noqa: RET501
|
||||||
|
|
||||||
@parent_router.setter
|
@parent_router.setter
|
||||||
def parent_router(self, value: Router) -> None:
|
def parent_router(self, value: Router) -> None:
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,8 @@ class EventObserver:
|
||||||
"""
|
"""
|
||||||
Simple events observer
|
Simple events observer
|
||||||
|
|
||||||
Is used for managing events is not related with Telegram (For example startup/shutdown processes)
|
Is used for managing events is not related with Telegram
|
||||||
|
(For example startup/shutdown processes)
|
||||||
|
|
||||||
Handlers can be registered via decorator or method
|
Handlers can be registered via decorator or method
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -94,7 +94,7 @@ def extract_flags(handler: Union["HandlerObject", Dict[str, Any]]) -> Dict[str,
|
||||||
handler = handler["handler"]
|
handler = handler["handler"]
|
||||||
if not hasattr(handler, "flags"):
|
if not hasattr(handler, "flags"):
|
||||||
return {}
|
return {}
|
||||||
return handler.flags # type: ignore
|
return handler.flags
|
||||||
|
|
||||||
|
|
||||||
def get_flag(
|
def get_flag(
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,11 @@
|
||||||
import functools
|
import functools
|
||||||
from typing import Any, Callable, Dict, List, Optional, Sequence, Union, overload
|
from typing import Any, Callable, Dict, List, Optional, Sequence, Union, overload
|
||||||
|
|
||||||
from aiogram.dispatcher.event.bases import MiddlewareEventType, MiddlewareType, NextMiddlewareType
|
from aiogram.dispatcher.event.bases import (
|
||||||
|
MiddlewareEventType,
|
||||||
|
MiddlewareType,
|
||||||
|
NextMiddlewareType,
|
||||||
|
)
|
||||||
from aiogram.dispatcher.event.handler import CallbackType
|
from aiogram.dispatcher.event.handler import CallbackType
|
||||||
from aiogram.types import TelegramObject
|
from aiogram.types import TelegramObject
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -101,7 +101,7 @@ class Router:
|
||||||
if observer.handlers and update_name not in skip_events:
|
if observer.handlers and update_name not in skip_events:
|
||||||
handlers_in_use.add(update_name)
|
handlers_in_use.add(update_name)
|
||||||
|
|
||||||
return list(sorted(handlers_in_use))
|
return list(sorted(handlers_in_use)) # NOQA: C413
|
||||||
|
|
||||||
async def propagate_event(self, update_type: str, event: TelegramObject, **kwargs: Any) -> Any:
|
async def propagate_event(self, update_type: str, event: TelegramObject, **kwargs: Any) -> Any:
|
||||||
kwargs.update(event_router=self)
|
kwargs.update(event_router=self)
|
||||||
|
|
|
||||||
|
|
@ -120,6 +120,7 @@ class ClientDecodeError(AiogramError):
|
||||||
original_type = type(self.original)
|
original_type = type(self.original)
|
||||||
return (
|
return (
|
||||||
f"{self.message}\n"
|
f"{self.message}\n"
|
||||||
f"Caused from error: {original_type.__module__}.{original_type.__name__}: {self.original}\n"
|
f"Caused from error: "
|
||||||
|
f"{original_type.__module__}.{original_type.__name__}: {self.original}\n"
|
||||||
f"Content: {self.data}"
|
f"Content: {self.data}"
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,8 @@ class Filter(ABC):
|
||||||
|
|
||||||
def update_handler_flags(self, flags: Dict[str, Any]) -> None:
|
def update_handler_flags(self, flags: Dict[str, Any]) -> None:
|
||||||
"""
|
"""
|
||||||
Also if you want to extend handler flags with using this filter you should implement this method
|
Also if you want to extend handler flags with using this filter
|
||||||
|
you should implement this method
|
||||||
|
|
||||||
:param flags: existing flags, can be updated directly
|
:param flags: existing flags, can be updated directly
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,17 @@ from __future__ import annotations
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from fractions import Fraction
|
from fractions import Fraction
|
||||||
from typing import TYPE_CHECKING, Any, ClassVar, Dict, Literal, Optional, Type, TypeVar, Union
|
from typing import (
|
||||||
|
TYPE_CHECKING,
|
||||||
|
Any,
|
||||||
|
ClassVar,
|
||||||
|
Dict,
|
||||||
|
Literal,
|
||||||
|
Optional,
|
||||||
|
Type,
|
||||||
|
TypeVar,
|
||||||
|
Union,
|
||||||
|
)
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
from magic_filter import MagicFilter
|
from magic_filter import MagicFilter
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ class _MemberStatusMarker:
|
||||||
result = self.name.upper()
|
result = self.name.upper()
|
||||||
if self.is_member is not None:
|
if self.is_member is not None:
|
||||||
result = ("+" if self.is_member else "-") + result
|
result = ("+" if self.is_member else "-") + result
|
||||||
return result
|
return result # noqa: RET504
|
||||||
|
|
||||||
def __pos__(self: MarkerT) -> MarkerT:
|
def __pos__(self: MarkerT) -> MarkerT:
|
||||||
return type(self)(name=self.name, is_member=True)
|
return type(self)(name=self.name, is_member=True)
|
||||||
|
|
@ -38,7 +38,8 @@ class _MemberStatusMarker:
|
||||||
if isinstance(other, _MemberStatusGroupMarker):
|
if isinstance(other, _MemberStatusGroupMarker):
|
||||||
return other | self
|
return other | self
|
||||||
raise TypeError(
|
raise TypeError(
|
||||||
f"unsupported operand type(s) for |: {type(self).__name__!r} and {type(other).__name__!r}"
|
f"unsupported operand type(s) for |: "
|
||||||
|
f"{type(self).__name__!r} and {type(other).__name__!r}"
|
||||||
)
|
)
|
||||||
|
|
||||||
__ror__ = __or__
|
__ror__ = __or__
|
||||||
|
|
@ -52,7 +53,8 @@ class _MemberStatusMarker:
|
||||||
if isinstance(other, _MemberStatusGroupMarker):
|
if isinstance(other, _MemberStatusGroupMarker):
|
||||||
return _MemberStatusTransition(old=old, new=other)
|
return _MemberStatusTransition(old=old, new=other)
|
||||||
raise TypeError(
|
raise TypeError(
|
||||||
f"unsupported operand type(s) for >>: {type(self).__name__!r} and {type(other).__name__!r}"
|
f"unsupported operand type(s) for >>: "
|
||||||
|
f"{type(self).__name__!r} and {type(other).__name__!r}"
|
||||||
)
|
)
|
||||||
|
|
||||||
def __lshift__(
|
def __lshift__(
|
||||||
|
|
@ -64,7 +66,8 @@ class _MemberStatusMarker:
|
||||||
if isinstance(other, _MemberStatusGroupMarker):
|
if isinstance(other, _MemberStatusGroupMarker):
|
||||||
return _MemberStatusTransition(old=other, new=new)
|
return _MemberStatusTransition(old=other, new=new)
|
||||||
raise TypeError(
|
raise TypeError(
|
||||||
f"unsupported operand type(s) for <<: {type(self).__name__!r} and {type(other).__name__!r}"
|
f"unsupported operand type(s) for <<: "
|
||||||
|
f"{type(self).__name__!r} and {type(other).__name__!r}"
|
||||||
)
|
)
|
||||||
|
|
||||||
def __hash__(self) -> int:
|
def __hash__(self) -> int:
|
||||||
|
|
@ -89,10 +92,11 @@ class _MemberStatusGroupMarker:
|
||||||
) -> MarkerGroupT:
|
) -> MarkerGroupT:
|
||||||
if isinstance(other, _MemberStatusMarker):
|
if isinstance(other, _MemberStatusMarker):
|
||||||
return type(self)(*self.statuses, other)
|
return type(self)(*self.statuses, other)
|
||||||
elif isinstance(other, _MemberStatusGroupMarker):
|
if isinstance(other, _MemberStatusGroupMarker):
|
||||||
return type(self)(*self.statuses, *other.statuses)
|
return type(self)(*self.statuses, *other.statuses)
|
||||||
raise TypeError(
|
raise TypeError(
|
||||||
f"unsupported operand type(s) for |: {type(self).__name__!r} and {type(other).__name__!r}"
|
f"unsupported operand type(s) for |: "
|
||||||
|
f"{type(self).__name__!r} and {type(other).__name__!r}"
|
||||||
)
|
)
|
||||||
|
|
||||||
def __rshift__(
|
def __rshift__(
|
||||||
|
|
@ -103,7 +107,8 @@ class _MemberStatusGroupMarker:
|
||||||
if isinstance(other, _MemberStatusGroupMarker):
|
if isinstance(other, _MemberStatusGroupMarker):
|
||||||
return _MemberStatusTransition(old=self, new=other)
|
return _MemberStatusTransition(old=self, new=other)
|
||||||
raise TypeError(
|
raise TypeError(
|
||||||
f"unsupported operand type(s) for >>: {type(self).__name__!r} and {type(other).__name__!r}"
|
f"unsupported operand type(s) for >>: "
|
||||||
|
f"{type(self).__name__!r} and {type(other).__name__!r}"
|
||||||
)
|
)
|
||||||
|
|
||||||
def __lshift__(
|
def __lshift__(
|
||||||
|
|
@ -114,7 +119,8 @@ class _MemberStatusGroupMarker:
|
||||||
if isinstance(other, _MemberStatusGroupMarker):
|
if isinstance(other, _MemberStatusGroupMarker):
|
||||||
return _MemberStatusTransition(old=other, new=self)
|
return _MemberStatusTransition(old=other, new=self)
|
||||||
raise TypeError(
|
raise TypeError(
|
||||||
f"unsupported operand type(s) for <<: {type(self).__name__!r} and {type(other).__name__!r}"
|
f"unsupported operand type(s) for <<: "
|
||||||
|
f"{type(self).__name__!r} and {type(other).__name__!r}"
|
||||||
)
|
)
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
|
|
@ -124,10 +130,7 @@ class _MemberStatusGroupMarker:
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def check(self, *, member: ChatMember) -> bool:
|
def check(self, *, member: ChatMember) -> bool:
|
||||||
for status in self.statuses:
|
return any(status.check(member=member) for status in self.statuses)
|
||||||
if status.check(member=member):
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
class _MemberStatusTransition:
|
class _MemberStatusTransition:
|
||||||
|
|
|
||||||
|
|
@ -181,7 +181,7 @@ class Command(Filter):
|
||||||
await self.validate_mention(bot=bot, command=command)
|
await self.validate_mention(bot=bot, command=command)
|
||||||
command = self.validate_command(command)
|
command = self.validate_command(command)
|
||||||
command = self.do_magic(command=command)
|
command = self.do_magic(command=command)
|
||||||
return command
|
return command # noqa: RET504
|
||||||
|
|
||||||
def do_magic(self, command: CommandObject) -> Any:
|
def do_magic(self, command: CommandObject) -> Any:
|
||||||
if not self.magic:
|
if not self.magic:
|
||||||
|
|
|
||||||
|
|
@ -84,7 +84,7 @@ class Text(Filter):
|
||||||
@classmethod
|
@classmethod
|
||||||
def _validate_constraints(cls, **values: Any) -> None:
|
def _validate_constraints(cls, **values: Any) -> None:
|
||||||
# Validate that only one text filter type is presented
|
# Validate that only one text filter type is presented
|
||||||
used_args = set(key for key, value in values.items() if value is not None)
|
used_args = {key for key, value in values.items() if value is not None}
|
||||||
if len(used_args) < 1:
|
if len(used_args) < 1:
|
||||||
raise ValueError(f"Filter should contain one of arguments: {set(values.keys())}")
|
raise ValueError(f"Filter should contain one of arguments: {set(values.keys())}")
|
||||||
if len(used_args) > 1:
|
if len(used_args) > 1:
|
||||||
|
|
@ -133,5 +133,4 @@ class Text(Filter):
|
||||||
def prepare_text(self, text: str) -> str:
|
def prepare_text(self, text: str) -> str:
|
||||||
if self.ignore_case:
|
if self.ignore_case:
|
||||||
return str(text).lower()
|
return str(text).lower()
|
||||||
else:
|
return str(text)
|
||||||
return str(text)
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,12 @@ from typing import Any, Awaitable, Callable, Dict, Optional, cast
|
||||||
from aiogram import Bot
|
from aiogram import Bot
|
||||||
from aiogram.dispatcher.middlewares.base import BaseMiddleware
|
from aiogram.dispatcher.middlewares.base import BaseMiddleware
|
||||||
from aiogram.fsm.context import FSMContext
|
from aiogram.fsm.context import FSMContext
|
||||||
from aiogram.fsm.storage.base import DEFAULT_DESTINY, BaseEventIsolation, BaseStorage, StorageKey
|
from aiogram.fsm.storage.base import (
|
||||||
|
DEFAULT_DESTINY,
|
||||||
|
BaseEventIsolation,
|
||||||
|
BaseStorage,
|
||||||
|
StorageKey,
|
||||||
|
)
|
||||||
from aiogram.fsm.strategy import FSMStrategy, apply_strategy
|
from aiogram.fsm.strategy import FSMStrategy, apply_strategy
|
||||||
from aiogram.types import TelegramObject
|
from aiogram.types import TelegramObject
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,12 @@ from typing import Any, AsyncGenerator, DefaultDict, Dict, Hashable, Optional
|
||||||
|
|
||||||
from aiogram import Bot
|
from aiogram import Bot
|
||||||
from aiogram.fsm.state import State
|
from aiogram.fsm.state import State
|
||||||
from aiogram.fsm.storage.base import BaseEventIsolation, BaseStorage, StateType, StorageKey
|
from aiogram.fsm.storage.base import (
|
||||||
|
BaseEventIsolation,
|
||||||
|
BaseStorage,
|
||||||
|
StateType,
|
||||||
|
StorageKey,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,16 @@ from __future__ import annotations
|
||||||
|
|
||||||
import abc
|
import abc
|
||||||
import secrets
|
import secrets
|
||||||
from typing import TYPE_CHECKING, Any, Dict, Generator, Generic, Optional, TypeVar, Union
|
from typing import (
|
||||||
|
TYPE_CHECKING,
|
||||||
|
Any,
|
||||||
|
Dict,
|
||||||
|
Generator,
|
||||||
|
Generic,
|
||||||
|
Optional,
|
||||||
|
TypeVar,
|
||||||
|
Union,
|
||||||
|
)
|
||||||
|
|
||||||
from pydantic import BaseConfig, BaseModel, Extra, root_validator
|
from pydantic import BaseConfig, BaseModel, Extra, root_validator
|
||||||
from pydantic.generics import GenericModel
|
from pydantic.generics import GenericModel
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,9 @@ from .audio import Audio
|
||||||
from .base import UNSET, TelegramObject
|
from .base import UNSET, TelegramObject
|
||||||
from .bot_command import BotCommand
|
from .bot_command import BotCommand
|
||||||
from .bot_command_scope import BotCommandScope
|
from .bot_command_scope import BotCommandScope
|
||||||
from .bot_command_scope_all_chat_administrators import BotCommandScopeAllChatAdministrators
|
from .bot_command_scope_all_chat_administrators import (
|
||||||
|
BotCommandScopeAllChatAdministrators,
|
||||||
|
)
|
||||||
from .bot_command_scope_all_group_chats import BotCommandScopeAllGroupChats
|
from .bot_command_scope_all_group_chats import BotCommandScopeAllGroupChats
|
||||||
from .bot_command_scope_all_private_chats import BotCommandScopeAllPrivateChats
|
from .bot_command_scope_all_private_chats import BotCommandScopeAllPrivateChats
|
||||||
from .bot_command_scope_chat import BotCommandScopeChat
|
from .bot_command_scope_chat import BotCommandScopeChat
|
||||||
|
|
@ -110,7 +112,9 @@ from .passport_element_error_front_side import PassportElementErrorFrontSide
|
||||||
from .passport_element_error_reverse_side import PassportElementErrorReverseSide
|
from .passport_element_error_reverse_side import PassportElementErrorReverseSide
|
||||||
from .passport_element_error_selfie import PassportElementErrorSelfie
|
from .passport_element_error_selfie import PassportElementErrorSelfie
|
||||||
from .passport_element_error_translation_file import PassportElementErrorTranslationFile
|
from .passport_element_error_translation_file import PassportElementErrorTranslationFile
|
||||||
from .passport_element_error_translation_files import PassportElementErrorTranslationFiles
|
from .passport_element_error_translation_files import (
|
||||||
|
PassportElementErrorTranslationFiles,
|
||||||
|
)
|
||||||
from .passport_element_error_unspecified import PassportElementErrorUnspecified
|
from .passport_element_error_unspecified import PassportElementErrorUnspecified
|
||||||
from .passport_file import PassportFile
|
from .passport_file import PassportFile
|
||||||
from .photo_size import PhotoSize
|
from .photo_size import PhotoSize
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,11 @@ from typing import TYPE_CHECKING, Any, List, Optional, Union
|
||||||
|
|
||||||
from pydantic import Field
|
from pydantic import Field
|
||||||
|
|
||||||
from aiogram.utils.text_decorations import TextDecoration, html_decoration, markdown_decoration
|
from aiogram.utils.text_decorations import (
|
||||||
|
TextDecoration,
|
||||||
|
html_decoration,
|
||||||
|
markdown_decoration,
|
||||||
|
)
|
||||||
|
|
||||||
from ..enums import ContentType
|
from ..enums import ContentType
|
||||||
from .base import UNSET, TelegramObject
|
from .base import UNSET, TelegramObject
|
||||||
|
|
@ -2465,7 +2469,7 @@ class Message(TelegramObject):
|
||||||
**kwargs,
|
**kwargs,
|
||||||
)
|
)
|
||||||
|
|
||||||
def send_copy(
|
def send_copy( # noqa: C901
|
||||||
self: Message,
|
self: Message,
|
||||||
chat_id: Union[str, int],
|
chat_id: Union[str, int],
|
||||||
disable_notification: Optional[bool] = None,
|
disable_notification: Optional[bool] = None,
|
||||||
|
|
@ -2538,7 +2542,7 @@ class Message(TelegramObject):
|
||||||
|
|
||||||
if self.text:
|
if self.text:
|
||||||
return SendMessage(text=text, entities=entities, **kwargs)
|
return SendMessage(text=text, entities=entities, **kwargs)
|
||||||
elif self.audio:
|
if self.audio:
|
||||||
return SendAudio(
|
return SendAudio(
|
||||||
audio=self.audio.file_id,
|
audio=self.audio.file_id,
|
||||||
caption=text,
|
caption=text,
|
||||||
|
|
@ -2548,29 +2552,29 @@ class Message(TelegramObject):
|
||||||
caption_entities=entities,
|
caption_entities=entities,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
)
|
)
|
||||||
elif self.animation:
|
if self.animation:
|
||||||
return SendAnimation(
|
return SendAnimation(
|
||||||
animation=self.animation.file_id, caption=text, caption_entities=entities, **kwargs
|
animation=self.animation.file_id, caption=text, caption_entities=entities, **kwargs
|
||||||
)
|
)
|
||||||
elif self.document:
|
if self.document:
|
||||||
return SendDocument(
|
return SendDocument(
|
||||||
document=self.document.file_id, caption=text, caption_entities=entities, **kwargs
|
document=self.document.file_id, caption=text, caption_entities=entities, **kwargs
|
||||||
)
|
)
|
||||||
elif self.photo:
|
if self.photo:
|
||||||
return SendPhoto(
|
return SendPhoto(
|
||||||
photo=self.photo[-1].file_id, caption=text, caption_entities=entities, **kwargs
|
photo=self.photo[-1].file_id, caption=text, caption_entities=entities, **kwargs
|
||||||
)
|
)
|
||||||
elif self.sticker:
|
if self.sticker:
|
||||||
return SendSticker(sticker=self.sticker.file_id, **kwargs)
|
return SendSticker(sticker=self.sticker.file_id, **kwargs)
|
||||||
elif self.video:
|
if self.video:
|
||||||
return SendVideo(
|
return SendVideo(
|
||||||
video=self.video.file_id, caption=text, caption_entities=entities, **kwargs
|
video=self.video.file_id, caption=text, caption_entities=entities, **kwargs
|
||||||
)
|
)
|
||||||
elif self.video_note:
|
if self.video_note:
|
||||||
return SendVideoNote(video_note=self.video_note.file_id, **kwargs)
|
return SendVideoNote(video_note=self.video_note.file_id, **kwargs)
|
||||||
elif self.voice:
|
if self.voice:
|
||||||
return SendVoice(voice=self.voice.file_id, **kwargs)
|
return SendVoice(voice=self.voice.file_id, **kwargs)
|
||||||
elif self.contact:
|
if self.contact:
|
||||||
return SendContact(
|
return SendContact(
|
||||||
phone_number=self.contact.phone_number,
|
phone_number=self.contact.phone_number,
|
||||||
first_name=self.contact.first_name,
|
first_name=self.contact.first_name,
|
||||||
|
|
@ -2578,7 +2582,7 @@ class Message(TelegramObject):
|
||||||
vcard=self.contact.vcard,
|
vcard=self.contact.vcard,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
)
|
)
|
||||||
elif self.venue:
|
if self.venue:
|
||||||
return SendVenue(
|
return SendVenue(
|
||||||
latitude=self.venue.location.latitude,
|
latitude=self.venue.location.latitude,
|
||||||
longitude=self.venue.location.longitude,
|
longitude=self.venue.location.longitude,
|
||||||
|
|
@ -2588,20 +2592,20 @@ class Message(TelegramObject):
|
||||||
foursquare_type=self.venue.foursquare_type,
|
foursquare_type=self.venue.foursquare_type,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
)
|
)
|
||||||
elif self.location:
|
if self.location:
|
||||||
return SendLocation(
|
return SendLocation(
|
||||||
latitude=self.location.latitude, longitude=self.location.longitude, **kwargs
|
latitude=self.location.latitude, longitude=self.location.longitude, **kwargs
|
||||||
)
|
)
|
||||||
elif self.poll:
|
if self.poll:
|
||||||
return SendPoll(
|
return SendPoll(
|
||||||
question=self.poll.question,
|
question=self.poll.question,
|
||||||
options=[option.text for option in self.poll.options],
|
options=[option.text for option in self.poll.options],
|
||||||
**kwargs,
|
**kwargs,
|
||||||
)
|
)
|
||||||
elif self.dice: # Dice value can't be controlled
|
if self.dice: # Dice value can't be controlled
|
||||||
return SendDice(**kwargs)
|
return SendDice(**kwargs)
|
||||||
else:
|
|
||||||
raise TypeError("This type of message can't be copied.")
|
raise TypeError("This type of message can't be copied.")
|
||||||
|
|
||||||
def copy_to(
|
def copy_to(
|
||||||
self,
|
self,
|
||||||
|
|
@ -3066,9 +3070,10 @@ class Message(TelegramObject):
|
||||||
if self.chat.type in ("private", "group"):
|
if self.chat.type in ("private", "group"):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if not self.chat.username or force_private:
|
chat_value = (
|
||||||
chat_value = f"c/{self.chat.shifted_id}"
|
f"c/{self.chat.shifted_id}"
|
||||||
else:
|
if not self.chat.username or force_private
|
||||||
chat_value = self.chat.username
|
else self.chat.username
|
||||||
|
)
|
||||||
|
|
||||||
return f"https://t.me/{chat_value}/{self.message_id}"
|
return f"https://t.me/{chat_value}/{self.message_id}"
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ def check_signature(token: str, hash: str, **kwargs: Any) -> bool:
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
secret = hashlib.sha256(token.encode("utf-8"))
|
secret = hashlib.sha256(token.encode("utf-8"))
|
||||||
check_string = "\n".join(map(lambda k: f"{k}={kwargs[k]}", sorted(kwargs)))
|
check_string = "\n".join(f"{k}={kwargs[k]}" for k in sorted(kwargs))
|
||||||
hmac_string = hmac.new(
|
hmac_string = hmac.new(
|
||||||
secret.digest(), check_string.encode("utf-8"), digestmod=hashlib.sha256
|
secret.digest(), check_string.encode("utf-8"), digestmod=hashlib.sha256
|
||||||
).hexdigest()
|
).hexdigest()
|
||||||
|
|
|
||||||
|
|
@ -77,4 +77,7 @@ class Backoff:
|
||||||
self._next_delay = self.min_delay
|
self._next_delay = self.min_delay
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return f"Backoff(tryings={self._counter}, current_delay={self._current_delay}, next_delay={self._next_delay})"
|
return (
|
||||||
|
f"Backoff(tryings={self._counter}, current_delay={self._current_delay}, "
|
||||||
|
f"next_delay={self._next_delay})"
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,8 @@ class ChatActionSender:
|
||||||
Provides simply to use context manager.
|
Provides simply to use context manager.
|
||||||
|
|
||||||
Technically sender start background task with infinity loop which works
|
Technically sender start background task with infinity loop which works
|
||||||
until action will be finished and sends the `chat action <https://core.telegram.org/bots/api#sendchataction>`_
|
until action will be finished and sends the
|
||||||
|
`chat action <https://core.telegram.org/bots/api#sendchataction>`_
|
||||||
every 5 seconds.
|
every 5 seconds.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
@ -110,7 +111,7 @@ class ChatActionSender:
|
||||||
async with self._lock:
|
async with self._lock:
|
||||||
if not self.running:
|
if not self.running:
|
||||||
return
|
return
|
||||||
if not self._close_event.is_set():
|
if not self._close_event.is_set(): # pragma: no branches
|
||||||
self._close_event.set()
|
self._close_event.set()
|
||||||
await self._closed_event.wait()
|
await self._closed_event.wait()
|
||||||
self._task = None
|
self._task = None
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,8 @@ class KeyboardBuilder(Generic[ButtonType]):
|
||||||
"""
|
"""
|
||||||
if not isinstance(row, list):
|
if not isinstance(row, list):
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"Row {row!r} should be type 'List[{self._button_type.__name__}]' not type {type(row).__name__}"
|
f"Row {row!r} should be type 'List[{self._button_type.__name__}]' "
|
||||||
|
f"not type {type(row).__name__}"
|
||||||
)
|
)
|
||||||
if len(row) > MAX_WIDTH:
|
if len(row) > MAX_WIDTH:
|
||||||
raise ValueError(f"Row {row!r} is too long (MAX_WIDTH={MAX_WIDTH})")
|
raise ValueError(f"Row {row!r} is too long (MAX_WIDTH={MAX_WIDTH})")
|
||||||
|
|
@ -114,7 +115,8 @@ class KeyboardBuilder(Generic[ButtonType]):
|
||||||
count = 0
|
count = 0
|
||||||
if not isinstance(markup, list):
|
if not isinstance(markup, list):
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"Markup should be type 'List[List[{self._button_type.__name__}]]' not type {type(markup).__name__!r}"
|
f"Markup should be type 'List[List[{self._button_type.__name__}]]' "
|
||||||
|
f"not type {type(markup).__name__!r}"
|
||||||
)
|
)
|
||||||
for row in markup:
|
for row in markup:
|
||||||
self._validate_row(row)
|
self._validate_row(row)
|
||||||
|
|
@ -206,7 +208,8 @@ class KeyboardBuilder(Generic[ButtonType]):
|
||||||
|
|
||||||
By default, when the sum of passed sizes is lower than buttons count the last
|
By default, when the sum of passed sizes is lower than buttons count the last
|
||||||
one size will be used for tail of the markup.
|
one size will be used for tail of the markup.
|
||||||
If repeat=True is passed - all sizes will be cycled when available more buttons count than all sizes
|
If repeat=True is passed - all sizes will be cycled when available more buttons
|
||||||
|
count than all sizes
|
||||||
|
|
||||||
:param sizes:
|
:param sizes:
|
||||||
:param repeat:
|
:param repeat:
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,9 @@ class AsFilterResultOperation(BaseOperation):
|
||||||
self.name = name
|
self.name = name
|
||||||
|
|
||||||
def resolve(self, value: Any, initial_value: Any) -> Any:
|
def resolve(self, value: Any, initial_value: Any) -> Any:
|
||||||
if value:
|
if not value:
|
||||||
return {self.name: value}
|
return None
|
||||||
|
return {self.name: value}
|
||||||
|
|
||||||
|
|
||||||
class MagicFilter(_MagicFilter):
|
class MagicFilter(_MagicFilter):
|
||||||
|
|
|
||||||
|
|
@ -81,13 +81,12 @@ class TextDecoration(ABC):
|
||||||
:param entities: Array of MessageEntities
|
:param entities: Array of MessageEntities
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
result = "".join(
|
return "".join(
|
||||||
self._unparse_entities(
|
self._unparse_entities(
|
||||||
add_surrogates(text),
|
add_surrogates(text),
|
||||||
sorted(entities, key=lambda item: item.offset) if entities else [],
|
sorted(entities, key=lambda item: item.offset) if entities else [],
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return result
|
|
||||||
|
|
||||||
def _unparse_entities(
|
def _unparse_entities(
|
||||||
self,
|
self,
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,10 @@ class WebAppUser(TelegramObject):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
id: int
|
id: int
|
||||||
"""A unique identifier for the user or bot. This number may have more than 32 significant bits and some programming languages may have difficulty/silent defects in interpreting it. It has at most 52 significant bits, so a 64-bit integer or a double-precision float type is safe for storing this identifier."""
|
"""A unique identifier for the user or bot. This number may have more than 32 significant bits
|
||||||
|
and some programming languages may have difficulty/silent defects in interpreting it.
|
||||||
|
It has at most 52 significant bits, so a 64-bit integer or a double-precision float type
|
||||||
|
is safe for storing this identifier."""
|
||||||
is_bot: Optional[bool] = None
|
is_bot: Optional[bool] = None
|
||||||
"""True, if this user is a bot. Returns in the receiver field only."""
|
"""True, if this user is a bot. Returns in the receiver field only."""
|
||||||
first_name: str
|
first_name: str
|
||||||
|
|
@ -29,24 +32,32 @@ class WebAppUser(TelegramObject):
|
||||||
language_code: Optional[str] = None
|
language_code: Optional[str] = None
|
||||||
"""IETF language tag of the user's language. Returns in user field only."""
|
"""IETF language tag of the user's language. Returns in user field only."""
|
||||||
photo_url: Optional[str] = None
|
photo_url: Optional[str] = None
|
||||||
"""URL of the user’s profile photo. The photo can be in .jpeg or .svg formats. Only returned for Web Apps launched from the attachment menu."""
|
"""URL of the user’s profile photo. The photo can be in .jpeg or .svg formats.
|
||||||
|
Only returned for Web Apps launched from the attachment menu."""
|
||||||
|
|
||||||
|
|
||||||
class WebAppInitData(TelegramObject):
|
class WebAppInitData(TelegramObject):
|
||||||
"""
|
"""
|
||||||
This object contains data that is transferred to the Web App when it is opened. It is empty if the Web App was launched from a keyboard button.
|
This object contains data that is transferred to the Web App when it is opened.
|
||||||
|
It is empty if the Web App was launched from a keyboard button.
|
||||||
|
|
||||||
Source: https://core.telegram.org/bots/webapps#webappinitdata
|
Source: https://core.telegram.org/bots/webapps#webappinitdata
|
||||||
"""
|
"""
|
||||||
|
|
||||||
query_id: Optional[str] = None
|
query_id: Optional[str] = None
|
||||||
"""A unique identifier for the Web App session, required for sending messages via the answerWebAppQuery method."""
|
"""A unique identifier for the Web App session, required for sending messages
|
||||||
|
via the answerWebAppQuery method."""
|
||||||
user: Optional[WebAppUser] = None
|
user: Optional[WebAppUser] = None
|
||||||
"""An object containing data about the current user."""
|
"""An object containing data about the current user."""
|
||||||
receiver: Optional[WebAppUser] = None
|
receiver: Optional[WebAppUser] = None
|
||||||
"""An object containing data about the chat partner of the current user in the chat where the bot was launched via the attachment menu. Returned only for Web Apps launched via the attachment menu."""
|
"""An object containing data about the chat partner of the current user in the chat where
|
||||||
|
the bot was launched via the attachment menu.
|
||||||
|
Returned only for Web Apps launched via the attachment menu."""
|
||||||
start_param: Optional[str] = None
|
start_param: Optional[str] = None
|
||||||
"""The value of the startattach parameter, passed via link. Only returned for Web Apps when launched from the attachment menu via link. The value of the start_param parameter will also be passed in the GET-parameter tgWebAppStartParam, so the Web App can load the correct interface right away."""
|
"""The value of the startattach parameter, passed via link.
|
||||||
|
Only returned for Web Apps when launched from the attachment menu via link.
|
||||||
|
The value of the start_param parameter will also be passed in the GET-parameter
|
||||||
|
tgWebAppStartParam, so the Web App can load the correct interface right away."""
|
||||||
auth_date: datetime
|
auth_date: datetime
|
||||||
"""Unix time when the form was opened."""
|
"""Unix time when the form was opened."""
|
||||||
hash: str
|
hash: str
|
||||||
|
|
|
||||||
|
|
@ -88,7 +88,8 @@ class BaseRequestHandler(ABC):
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
:param dispatcher: instance of :class:`aiogram.dispatcher.dispatcher.Dispatcher`
|
:param dispatcher: instance of :class:`aiogram.dispatcher.dispatcher.Dispatcher`
|
||||||
:param handle_in_background: immediately respond to the Telegram instead of waiting end of handler process
|
:param handle_in_background: immediately respond to the Telegram instead of
|
||||||
|
waiting end of handler process
|
||||||
"""
|
"""
|
||||||
self.dispatcher = dispatcher
|
self.dispatcher = dispatcher
|
||||||
self.handle_in_background = handle_in_background
|
self.handle_in_background = handle_in_background
|
||||||
|
|
@ -166,7 +167,8 @@ class SimpleRequestHandler(BaseRequestHandler):
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
:param dispatcher: instance of :class:`aiogram.dispatcher.dispatcher.Dispatcher`
|
:param dispatcher: instance of :class:`aiogram.dispatcher.dispatcher.Dispatcher`
|
||||||
:param handle_in_background: immediately respond to the Telegram instead of waiting end of handler process
|
:param handle_in_background: immediately respond to the Telegram instead of
|
||||||
|
waiting end of handler process
|
||||||
:param bot: instance of :class:`aiogram.client.bot.Bot`
|
:param bot: instance of :class:`aiogram.client.bot.Bot`
|
||||||
"""
|
"""
|
||||||
super().__init__(dispatcher=dispatcher, handle_in_background=handle_in_background, **data)
|
super().__init__(dispatcher=dispatcher, handle_in_background=handle_in_background, **data)
|
||||||
|
|
@ -184,7 +186,8 @@ class SimpleRequestHandler(BaseRequestHandler):
|
||||||
|
|
||||||
class TokenBasedRequestHandler(BaseRequestHandler):
|
class TokenBasedRequestHandler(BaseRequestHandler):
|
||||||
"""
|
"""
|
||||||
Handler that supports multiple bots, the context will be resolved from path variable 'bot_token'
|
Handler that supports multiple bots, the context will be resolved
|
||||||
|
from path variable 'bot_token'
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
|
|
@ -196,7 +199,8 @@ class TokenBasedRequestHandler(BaseRequestHandler):
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
:param dispatcher: instance of :class:`aiogram.dispatcher.dispatcher.Dispatcher`
|
:param dispatcher: instance of :class:`aiogram.dispatcher.dispatcher.Dispatcher`
|
||||||
:param handle_in_background: immediately respond to the Telegram instead of waiting end of handler process
|
:param handle_in_background: immediately respond to the Telegram instead of
|
||||||
|
waiting end of handler process
|
||||||
:param bot_settings: kwargs that will be passed to new Bot instance
|
:param bot_settings: kwargs that will be passed to new Bot instance
|
||||||
"""
|
"""
|
||||||
super().__init__(dispatcher=dispatcher, handle_in_background=handle_in_background, **data)
|
super().__init__(dispatcher=dispatcher, handle_in_background=handle_in_background, **data)
|
||||||
|
|
|
||||||
213
docs/contributing.rst
Normal file
213
docs/contributing.rst
Normal file
|
|
@ -0,0 +1,213 @@
|
||||||
|
============
|
||||||
|
Contributing
|
||||||
|
============
|
||||||
|
|
||||||
|
You're welcome to contribute to aiogram!
|
||||||
|
|
||||||
|
*aiogram* is an open-source project, and anyone can contribute to it in any possible way
|
||||||
|
|
||||||
|
|
||||||
|
Developing
|
||||||
|
==========
|
||||||
|
|
||||||
|
Before making any changes in the framework code, it is necessary to fork the project and clone
|
||||||
|
the project to your PC and know how to do a pull-request.
|
||||||
|
|
||||||
|
How to work with pull-request you can read in the `GitHub docs <https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request>`_
|
||||||
|
|
||||||
|
Also in due to this project is written in Python, you will need Python to be installed
|
||||||
|
(is recommended to use latest Python versions, but any version starting from 3.8 can be used)
|
||||||
|
|
||||||
|
|
||||||
|
Use virtualenv
|
||||||
|
--------------
|
||||||
|
|
||||||
|
You can create a virtual environment in a directory using :code:`venv` module (it should be pre-installed by default):
|
||||||
|
|
||||||
|
.. code-block::bash
|
||||||
|
|
||||||
|
python -m venv .venv
|
||||||
|
|
||||||
|
This action will create a :code:`.venv` directory with the Python binaries and then you will
|
||||||
|
be able to install packages into that isolated environment.
|
||||||
|
|
||||||
|
|
||||||
|
Activate the environment
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
Linux/ macOS:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
source .venv/bin/activate
|
||||||
|
|
||||||
|
Windows PoweShell
|
||||||
|
|
||||||
|
.. code-block:: powershell
|
||||||
|
|
||||||
|
.\.venv\Scripts\Activate.ps1
|
||||||
|
|
||||||
|
To check it worked, use described command, it should show the :code:`pip` location inside
|
||||||
|
the isolated environment
|
||||||
|
|
||||||
|
Linux, macOS:
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
which pip
|
||||||
|
|
||||||
|
Windows PowerShell
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
Get-Command pip
|
||||||
|
|
||||||
|
Also make you shure you have the latest pip version in your virtual environment to avoid
|
||||||
|
errors on next steps:
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
|
||||||
|
|
||||||
|
Setup project
|
||||||
|
-------------
|
||||||
|
|
||||||
|
After activating the environment install `aiogram` from sources and their dependencies:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
pip install -e ."[dev,test,docs,fast,redis,proxy,i18n]"
|
||||||
|
|
||||||
|
It will install :code:`aiogram` in editable mode into your virtual environment and all dependencies.
|
||||||
|
|
||||||
|
Making changes in code
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
At this point you can make any changes in the code that you want, it can be any fixes,
|
||||||
|
implementing new features or experimenting.
|
||||||
|
|
||||||
|
|
||||||
|
Format the code (code-style)
|
||||||
|
----------------------------
|
||||||
|
|
||||||
|
Note that this project is Black-formatted, so you should follow that code-style,
|
||||||
|
too be sure You're correctly doing this let's reformat the code automatically:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
black aiogram tests examples
|
||||||
|
isort aiogram tests examples
|
||||||
|
|
||||||
|
|
||||||
|
Run tests
|
||||||
|
---------
|
||||||
|
|
||||||
|
All changes should be tested:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
pytest tests
|
||||||
|
|
||||||
|
Also if you are doing something with Redis-storage, you will need to test everything works with Redis:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
pytest --redis redis://<host>:<port>/<db> tests
|
||||||
|
|
||||||
|
Docs
|
||||||
|
----
|
||||||
|
|
||||||
|
We are using `Sphinx` to render docs in different languages, all sources located in `docs` directory,
|
||||||
|
you can change the sources and to test it you can start live-preview server and look what you are doing:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
sphinx-autobuild --watch aiogram/ docs/ docs/_build/
|
||||||
|
|
||||||
|
|
||||||
|
Docs translations
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
Translation of the documentation is very necessary and cannot be done without the help of the
|
||||||
|
community from all over the world, so you are welcome to translate the documentation
|
||||||
|
into different languages.
|
||||||
|
|
||||||
|
Before start, let's up to date all texts:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
cd docs
|
||||||
|
make gettext
|
||||||
|
sphinx-intl update -p _build/gettext -l <language_code>
|
||||||
|
|
||||||
|
Change the :code:`<language_code>` in example below to the target language code, after that
|
||||||
|
you can modify texts inside :code:`docs/locale/<language_code>/LC_MESSAGES` as :code:`*.po` files
|
||||||
|
by using any text-editor or specialized utilites for GNU Gettext,
|
||||||
|
for example via `poedit <https://poedit.net/>`_.
|
||||||
|
|
||||||
|
To view results:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
sphinx-autobuild --watch aiogram/ docs/ docs/_build/ -D language=<language_code>
|
||||||
|
|
||||||
|
|
||||||
|
Describe changes
|
||||||
|
----------------
|
||||||
|
|
||||||
|
Describe your changes in one or more sentences so that bot developers know what's changed
|
||||||
|
in their favorite framework - create `<code>.<category>.rst` file and write the description.
|
||||||
|
|
||||||
|
:code:`<code>` is Issue or Pull-request number, after release link to this issue will
|
||||||
|
be published to the *Changelog* page.
|
||||||
|
|
||||||
|
:code:`<category>` is a changes category marker, it can be one of:
|
||||||
|
|
||||||
|
- :code:`feature` - when you are implementing new feature
|
||||||
|
- :code:`bugfix` - when you fix a bug
|
||||||
|
- :code:`doc` - when you improve the docs
|
||||||
|
- :code:`removal` - when you remove something from the framework
|
||||||
|
- :code:`misc` - when changed something inside the Core or project configuration
|
||||||
|
|
||||||
|
If you have troubles with changing category feel free to ask Core-contributors to help with choosing it.
|
||||||
|
|
||||||
|
Complete
|
||||||
|
--------
|
||||||
|
|
||||||
|
After you have made all your changes, publish them to the repository and create a pull request
|
||||||
|
as mentioned at the beginning of the article and wait for a review of these changes.
|
||||||
|
|
||||||
|
|
||||||
|
Star on GitHub
|
||||||
|
==============
|
||||||
|
|
||||||
|
You can "star" repository on GitHub - https://github.com/aiogram/aiogram (click the star button at the top right)
|
||||||
|
|
||||||
|
Adding stars makes it easier for other people to find this project and understand how useful it is.
|
||||||
|
|
||||||
|
Guides
|
||||||
|
======
|
||||||
|
|
||||||
|
You can write guides how to develop Bots on top of aiogram and publish it into YouTube, Medium,
|
||||||
|
GitHub Books, any Courses platform or any other platform that you know.
|
||||||
|
|
||||||
|
This will help more people learn about the framework and learn how to use it
|
||||||
|
|
||||||
|
|
||||||
|
Take answers
|
||||||
|
============
|
||||||
|
|
||||||
|
The developers is always asks for any question in our chats or any other platforms like GitHub Discussions,
|
||||||
|
StackOverflow and others, feel free to answer to this questions.
|
||||||
|
|
||||||
|
Funding
|
||||||
|
=======
|
||||||
|
|
||||||
|
The development of the project is free and not financed by commercial organizations,
|
||||||
|
it is my personal initiative (`@JRootJunior <https://t.me/JRootJunior>`_) and
|
||||||
|
I am engaged in the development of the project in my free time.
|
||||||
|
|
||||||
|
So, if you want to financially support the project, or, for example, give me a pizza or a beer,
|
||||||
|
you can do it on `OpenCollective <https://opencollective.com/aiogram>`_
|
||||||
|
or `Patreon <https://www.patreon.com/aiogram>`_.
|
||||||
|
|
@ -16,3 +16,4 @@ Contents
|
||||||
dispatcher/index
|
dispatcher/index
|
||||||
utils/index
|
utils/index
|
||||||
changelog
|
changelog
|
||||||
|
contributing
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,12 @@ from aiogram import Bot, Dispatcher, F, Router, html
|
||||||
from aiogram.filters import Command
|
from aiogram.filters import Command
|
||||||
from aiogram.fsm.context import FSMContext
|
from aiogram.fsm.context import FSMContext
|
||||||
from aiogram.fsm.state import State, StatesGroup
|
from aiogram.fsm.state import State, StatesGroup
|
||||||
from aiogram.types import KeyboardButton, Message, ReplyKeyboardMarkup, ReplyKeyboardRemove
|
from aiogram.types import (
|
||||||
|
KeyboardButton,
|
||||||
|
Message,
|
||||||
|
ReplyKeyboardMarkup,
|
||||||
|
ReplyKeyboardRemove,
|
||||||
|
)
|
||||||
|
|
||||||
form_router = Router()
|
form_router = Router()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ async def command_add_bot(message: Message, command: CommandObject, bot: Bot) ->
|
||||||
return message.answer("Invalid token")
|
return message.answer("Invalid token")
|
||||||
await new_bot.delete_webhook(drop_pending_updates=True)
|
await new_bot.delete_webhook(drop_pending_updates=True)
|
||||||
await new_bot.set_webhook(OTHER_BOTS_URL.format(bot_token=command.args))
|
await new_bot.set_webhook(OTHER_BOTS_URL.format(bot_token=command.args))
|
||||||
await message.answer(f"Bot @{bot_user.username} successful added")
|
return await message.answer(f"Bot @{bot_user.username} successful added")
|
||||||
|
|
||||||
|
|
||||||
async def on_startup(dispatcher: Dispatcher, bot: Bot):
|
async def on_startup(dispatcher: Dispatcher, bot: Bot):
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,6 @@ async def send_message_handler(request: Request):
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return json_response({"ok": False, "err": "Unauthorized"}, status=401)
|
return json_response({"ok": False, "err": "Unauthorized"}, status=401)
|
||||||
|
|
||||||
print(data)
|
|
||||||
reply_markup = None
|
reply_markup = None
|
||||||
if data["with_webview"] == "1":
|
if data["with_webview"] == "1":
|
||||||
reply_markup = InlineKeyboardMarkup(
|
reply_markup = InlineKeyboardMarkup(
|
||||||
|
|
|
||||||
37
mypy.ini
37
mypy.ini
|
|
@ -1,37 +0,0 @@
|
||||||
[mypy]
|
|
||||||
plugins = pydantic.mypy
|
|
||||||
python_version = 3.8
|
|
||||||
show_error_codes = True
|
|
||||||
show_error_context = True
|
|
||||||
pretty = True
|
|
||||||
ignore_missing_imports = False
|
|
||||||
warn_unused_configs = True
|
|
||||||
disallow_subclassing_any = True
|
|
||||||
disallow_any_generics = True
|
|
||||||
disallow_untyped_calls = True
|
|
||||||
disallow_untyped_defs = True
|
|
||||||
disallow_incomplete_defs = True
|
|
||||||
check_untyped_defs = True
|
|
||||||
disallow_untyped_decorators = True
|
|
||||||
no_implicit_optional = True
|
|
||||||
warn_redundant_casts = True
|
|
||||||
warn_unused_ignores = True
|
|
||||||
warn_return_any = True
|
|
||||||
follow_imports_for_stubs = True
|
|
||||||
namespace_packages = True
|
|
||||||
show_absolute_path = True
|
|
||||||
|
|
||||||
[mypy-aiofiles]
|
|
||||||
ignore_missing_imports = True
|
|
||||||
|
|
||||||
[mypy-async_lru]
|
|
||||||
ignore_missing_imports = True
|
|
||||||
|
|
||||||
[mypy-uvloop]
|
|
||||||
ignore_missing_imports = True
|
|
||||||
|
|
||||||
[mypy-redis.*]
|
|
||||||
ignore_missing_imports = True
|
|
||||||
|
|
||||||
[mypy-babel.*]
|
|
||||||
ignore_missing_imports = True
|
|
||||||
2486
poetry.lock
generated
2486
poetry.lock
generated
File diff suppressed because it is too large
Load diff
323
pyproject.toml
323
pyproject.toml
|
|
@ -1,18 +1,19 @@
|
||||||
[tool.poetry]
|
[build-system]
|
||||||
|
requires = ["hatchling"]
|
||||||
|
build-backend = "hatchling.build"
|
||||||
|
|
||||||
|
[project]
|
||||||
name = "aiogram"
|
name = "aiogram"
|
||||||
version = "3.0.0-beta.7"
|
description = 'Modern and fully asynchronous framework for Telegram Bot API'
|
||||||
description = "Modern and fully asynchronous framework for Telegram Bot API"
|
readme = "README.rst"
|
||||||
|
requires-python = ">=3.8"
|
||||||
|
license = "MIT"
|
||||||
authors = [
|
authors = [
|
||||||
"Alex Root Junior <jroot.junior@gmail.com>",
|
{ name = "Alex Root Junior", email = "jroot.junior@gmail.com" },
|
||||||
]
|
]
|
||||||
maintainers = [
|
maintainers = [
|
||||||
"Alex Root Junior <jroot.junior@gmail.com>",
|
{ name = "Alex Root Junior", email = "jroot.junior@gmail.com" },
|
||||||
]
|
]
|
||||||
license = "MIT"
|
|
||||||
readme = "README.rst"
|
|
||||||
homepage = "https://aiogram.dev/"
|
|
||||||
documentation = "https://docs.aiogram.dev/"
|
|
||||||
repository = "https://github.com/aiogram/aiogram/"
|
|
||||||
keywords = [
|
keywords = [
|
||||||
"telegram",
|
"telegram",
|
||||||
"bot",
|
"bot",
|
||||||
|
|
@ -38,77 +39,245 @@ classifiers = [
|
||||||
"Topic :: Software Development :: Libraries :: Python Modules",
|
"Topic :: Software Development :: Libraries :: Python Modules",
|
||||||
"Topic :: Communications :: Chat",
|
"Topic :: Communications :: Chat",
|
||||||
]
|
]
|
||||||
packages = [
|
dependencies = [
|
||||||
{ include = "aiogram" }
|
"magic-filter~=1.0.9",
|
||||||
|
"aiohttp~=3.8.3",
|
||||||
|
"pydantic~=1.10.4",
|
||||||
|
"aiofiles~=22.1.0",
|
||||||
|
"certifi>=2022.9.24",
|
||||||
|
]
|
||||||
|
dynamic = ["version"]
|
||||||
|
|
||||||
|
[tool.hatch.version]
|
||||||
|
path = "aiogram/__init__.py"
|
||||||
|
|
||||||
|
[project.optional-dependencies]
|
||||||
|
fast = [
|
||||||
|
"uvloop>=0.17.0; (sys_platform == 'darwin' or sys_platform == 'linux') and platform_python_implementation != 'PyPy'",
|
||||||
|
]
|
||||||
|
redis = [
|
||||||
|
"redis~=4.3.4",
|
||||||
|
]
|
||||||
|
proxy = [
|
||||||
|
"aiohttp-socks~=0.7.1",
|
||||||
|
]
|
||||||
|
i18n = [
|
||||||
|
"Babel~=2.9.1",
|
||||||
|
]
|
||||||
|
test = [
|
||||||
|
"pytest~=7.1.3",
|
||||||
|
"pytest-html~=3.1.1",
|
||||||
|
"pytest-asyncio~=0.19.0",
|
||||||
|
"pytest-lazy-fixture~=0.6.3",
|
||||||
|
"pytest-mock~=3.9.0",
|
||||||
|
"pytest-mypy~=0.10.0",
|
||||||
|
"pytest-cov~=4.0.0",
|
||||||
|
"pytest-aiohttp~=1.0.4",
|
||||||
|
"aresponses~=2.1.6",
|
||||||
|
]
|
||||||
|
docs = [
|
||||||
|
"Sphinx~=5.2.3",
|
||||||
|
"sphinx-intl~=2.0.1",
|
||||||
|
"sphinx-autobuild~=2021.3.14",
|
||||||
|
"sphinx-copybutton~=0.5.0",
|
||||||
|
"furo~=2022.9.29",
|
||||||
|
"sphinx-prompt~=1.5.0",
|
||||||
|
"Sphinx-Substitution-Extensions~=2022.2.16",
|
||||||
|
"towncrier~=22.8.0",
|
||||||
|
"pygments~=2.4",
|
||||||
|
"pymdown-extensions~=9.6",
|
||||||
|
"markdown-include~=0.7.0",
|
||||||
|
"Pygments~=2.13.0",
|
||||||
|
"sphinxcontrib-towncrier~=0.3.1a3",
|
||||||
|
]
|
||||||
|
dev = [
|
||||||
|
"black~=22.8",
|
||||||
|
"isort~=5.11",
|
||||||
|
"ruff~=0.0.215",
|
||||||
|
"mypy~=0.981",
|
||||||
|
"toml~=0.10.2",
|
||||||
|
"pre-commit~=2.20.0",
|
||||||
|
"packaging~=21.3",
|
||||||
|
"typing-extensions~=4.3.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[project.urls]
|
||||||
|
Homepage = "https://aiogram.dev/"
|
||||||
|
Documentation = "https://docs.aiogram.dev/"
|
||||||
|
Repository = "https://github.com/aiogram/aiogram/"
|
||||||
|
|
||||||
[tool.poetry.dependencies]
|
[tool.hatch.envs.default]
|
||||||
python = "^3.8"
|
features = [
|
||||||
magic-filter = "^1.0.9"
|
"dev",
|
||||||
aiohttp = "^3.8.3"
|
"fast",
|
||||||
pydantic = "^1.10.2"
|
"redis",
|
||||||
aiofiles = "^22.1.0"
|
"proxy",
|
||||||
# Fast
|
"i18n",
|
||||||
uvloop = { version = "^0.17.0", markers = "sys_platform == 'darwin' or sys_platform == 'linux'", optional = true }
|
]
|
||||||
# i18n
|
post-install-commands = [
|
||||||
Babel = { version = "^2.9.1", optional = true }
|
"pre-commit install",
|
||||||
# Proxy
|
]
|
||||||
aiohttp-socks = { version = "^0.7.1", optional = true }
|
|
||||||
# Redis
|
[tool.hatch.envs.default.scripts]
|
||||||
redis = { version = "^4.3.4", optional = true }
|
reformat = [
|
||||||
certifi = "^2022.9.24"
|
"black aiogram tests",
|
||||||
|
"isort aiogram tests",
|
||||||
|
]
|
||||||
|
lint = "ruff aiogram"
|
||||||
|
|
||||||
|
[tool.hatch.envs.docs]
|
||||||
|
features = [
|
||||||
|
"fast",
|
||||||
|
"redis",
|
||||||
|
"proxy",
|
||||||
|
"i18n",
|
||||||
|
"docs",
|
||||||
|
]
|
||||||
|
[tool.hatch.envs.docs.scripts]
|
||||||
|
serve = "sphinx-autobuild --watch aiogram/ --watch CHANGELOG.rst --watch README.rst docs/ docs/_build/ {args}"
|
||||||
|
|
||||||
|
[tool.hatch.envs.dev]
|
||||||
|
python = "3.10"
|
||||||
|
features = [
|
||||||
|
"dev",
|
||||||
|
"fast",
|
||||||
|
"redis",
|
||||||
|
"proxy",
|
||||||
|
"i18n",
|
||||||
|
"test",
|
||||||
|
]
|
||||||
|
extra-dependencies = [
|
||||||
|
"butcher @ git+https://github.com/aiogram/butcher.git@v0.1.9"
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.hatch.envs.test]
|
||||||
|
features = [
|
||||||
|
"fast",
|
||||||
|
"redis",
|
||||||
|
"proxy",
|
||||||
|
"i18n",
|
||||||
|
"test",
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.hatch.envs.test.scripts]
|
||||||
|
cov = [
|
||||||
|
"pytest --cov-config pyproject.toml --cov=aiogram --html=reports/py{matrix:python}/tests/index.html {args}",
|
||||||
|
"coverage html -d reports/py{matrix:python}/coverage",
|
||||||
|
]
|
||||||
|
cov-redis = [
|
||||||
|
"pytest --cov-config pyproject.toml --cov=aiogram --html=reports/py{matrix:python}/tests/index.html --redis {env:REDIS_DNS:'redis://localhost:6379'} {args}",
|
||||||
|
"coverage html -d reports/py{matrix:python}/coverage",
|
||||||
|
]
|
||||||
|
view-cov = "google-chrome-stable reports/py{matrix:python}/coverage/index.html"
|
||||||
|
|
||||||
|
|
||||||
[tool.poetry.group.docs.dependencies]
|
[[tool.hatch.envs.test.matrix]]
|
||||||
Sphinx = "^5.2.3"
|
python = ["38", "39", "310", "311"]
|
||||||
sphinx-intl = "^2.0.1"
|
|
||||||
sphinx-autobuild = "^2021.3.14"
|
[tool.ruff]
|
||||||
sphinx-copybutton = "^0.5.0"
|
line-length = 99
|
||||||
furo = "^2022.9.29"
|
select = [
|
||||||
sphinx-prompt = "^1.5.0"
|
# "C", # TODO: mccabe - code complecity
|
||||||
Sphinx-Substitution-Extensions = "^2022.2.16"
|
"C4",
|
||||||
towncrier = "^22.8.0"
|
"E",
|
||||||
pygments = "^2.4"
|
"F",
|
||||||
pymdown-extensions = "^9.6"
|
"T10",
|
||||||
markdown-include = "^0.7.0"
|
"T20",
|
||||||
Pygments = "^2.13.0"
|
"Q",
|
||||||
sphinxcontrib-towncrier = "^0.3.1a3"
|
"RET",
|
||||||
|
]
|
||||||
|
ignore = [
|
||||||
|
"F401"
|
||||||
|
]
|
||||||
|
src = ["aiogram", "tests"]
|
||||||
|
exclude = [
|
||||||
|
".git",
|
||||||
|
"build",
|
||||||
|
"dist",
|
||||||
|
"venv",
|
||||||
|
".venv",
|
||||||
|
"docs",
|
||||||
|
"tests",
|
||||||
|
"dev",
|
||||||
|
"scripts",
|
||||||
|
"*.egg-info",
|
||||||
|
]
|
||||||
|
target-version = "py310"
|
||||||
|
|
||||||
|
[tool.ruff.isort]
|
||||||
|
known-first-party = [
|
||||||
|
"aiogram",
|
||||||
|
"finite_state_machine",
|
||||||
|
"handlers",
|
||||||
|
"routes",
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.ruff.per-file-ignores]
|
||||||
|
"aiogram/client/bot.py" = ["E501"]
|
||||||
|
"aiogram/types/*" = ["E501"]
|
||||||
|
"aiogram/methods/*" = ["E501"]
|
||||||
|
"aiogram/enums/*" = ["E501"]
|
||||||
|
|
||||||
|
|
||||||
[tool.poetry.group.test.dependencies]
|
[tool.pytest.ini_options]
|
||||||
pytest = "^7.1.3"
|
asyncio_mode = "auto"
|
||||||
pytest-html = "^3.1.1"
|
testpaths = [
|
||||||
pytest-asyncio = "^0.19.0"
|
"tests",
|
||||||
pytest-lazy-fixture = "^0.6.3"
|
]
|
||||||
pytest-mock = "^3.9.0"
|
|
||||||
pytest-mypy = "^0.10.0"
|
|
||||||
pytest-cov = "^4.0.0"
|
|
||||||
pytest-aiohttp = "^1.0.4"
|
|
||||||
aresponses = "^2.1.6"
|
|
||||||
|
|
||||||
|
[tool.coverage.run]
|
||||||
|
branch = false
|
||||||
|
parallel = true
|
||||||
|
omit = [
|
||||||
|
"aiogram/__about__.py",
|
||||||
|
]
|
||||||
|
|
||||||
[tool.poetry.group.dev.dependencies]
|
[tool.coverage.report]
|
||||||
black = "^22.8.0"
|
exclude_lines = [
|
||||||
isort = "^5.10.1"
|
"if __name__ == .__main__.:",
|
||||||
flake8 = "^5.0.4"
|
"pragma: no cover",
|
||||||
mypy = "^0.981"
|
"if TYPE_CHECKING:",
|
||||||
toml = "^0.10.2"
|
"@abstractmethod",
|
||||||
pre-commit = "^2.20.0"
|
"@overload",
|
||||||
packaging = "^21.3"
|
]
|
||||||
typing-extensions = "^4.3.0"
|
|
||||||
butcher = { git = "https://github.com/aiogram/butcher.git", rev = "v0.1.8", python = "3.10" }
|
|
||||||
|
|
||||||
|
[tool.mypy]
|
||||||
|
plugins = "pydantic.mypy"
|
||||||
|
python_version = "3.8"
|
||||||
|
show_error_codes = true
|
||||||
|
show_error_context = true
|
||||||
|
pretty = true
|
||||||
|
ignore_missing_imports = false
|
||||||
|
warn_unused_configs = true
|
||||||
|
disallow_subclassing_any = true
|
||||||
|
disallow_any_generics = true
|
||||||
|
disallow_untyped_calls = true
|
||||||
|
disallow_untyped_defs = true
|
||||||
|
disallow_incomplete_defs = true
|
||||||
|
check_untyped_defs = true
|
||||||
|
disallow_untyped_decorators = true
|
||||||
|
no_implicit_optional = true
|
||||||
|
warn_redundant_casts = true
|
||||||
|
warn_unused_ignores = true
|
||||||
|
warn_return_any = true
|
||||||
|
follow_imports_for_stubs = true
|
||||||
|
namespace_packages = true
|
||||||
|
show_absolute_path = true
|
||||||
|
|
||||||
[tool.poetry.extras]
|
[[tool.mypy.overrides]]
|
||||||
fast = ["uvloop"]
|
module = [
|
||||||
redis = ["redis"]
|
"aiofiles",
|
||||||
proxy = ["aiohttp-socks"]
|
"async_lru",
|
||||||
i18n = ["Babel"]
|
"uvloop",
|
||||||
|
"redis.*",
|
||||||
|
"babel.*",
|
||||||
|
]
|
||||||
|
ignore_missing_imports = true
|
||||||
|
disallow_untyped_defs = true
|
||||||
|
|
||||||
[tool.black]
|
[tool.black]
|
||||||
line-length = 99
|
line-length = 99
|
||||||
target-version = ['py38', 'py39', 'py310']
|
target-version = ['py38', 'py39', 'py310', 'py311']
|
||||||
exclude = '''
|
exclude = '''
|
||||||
(
|
(
|
||||||
\.eggs
|
\.eggs
|
||||||
|
|
@ -122,21 +291,7 @@ exclude = '''
|
||||||
'''
|
'''
|
||||||
|
|
||||||
[tool.isort]
|
[tool.isort]
|
||||||
multi_line_output = 3
|
profile = "black"
|
||||||
include_trailing_comma = true
|
|
||||||
force_grid_wrap = 0
|
|
||||||
use_parentheses = true
|
|
||||||
line_length = 99
|
|
||||||
known_third_party = [
|
|
||||||
"aiofiles",
|
|
||||||
"aiohttp",
|
|
||||||
"aiohttp_socks",
|
|
||||||
"aresponses",
|
|
||||||
"packaging",
|
|
||||||
"pkg_resources",
|
|
||||||
"pydantic",
|
|
||||||
"pytest"
|
|
||||||
]
|
|
||||||
|
|
||||||
[tool.towncrier]
|
[tool.towncrier]
|
||||||
package = "aiogram"
|
package = "aiogram"
|
||||||
|
|
@ -172,7 +327,3 @@ showcontent = true
|
||||||
directory = "misc"
|
directory = "misc"
|
||||||
name = "Misc"
|
name = "Misc"
|
||||||
showcontent = true
|
showcontent = true
|
||||||
|
|
||||||
[build-system]
|
|
||||||
requires = ["poetry-core"]
|
|
||||||
build-backend = "poetry.core.masonry.api"
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
[pytest]
|
|
||||||
asyncio_mode = auto
|
|
||||||
testpaths =
|
|
||||||
tests
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
aiofiles==22.1.0 ; python_version >= "3.8" and python_version < "4.0"
|
|
||||||
aiohttp==3.8.3 ; python_version >= "3.8" and python_version < "4.0"
|
|
||||||
aiosignal==1.2.0 ; python_version >= "3.8" and python_version < "4.0"
|
|
||||||
async-timeout==4.0.2 ; python_version >= "3.8" and python_version < "4.0"
|
|
||||||
attrs==22.1.0 ; python_version >= "3.8" and python_version < "4.0"
|
|
||||||
babel==2.10.3 ; python_version >= "3.8" and python_version < "4.0"
|
|
||||||
certifi==2022.9.24 ; python_version >= "3.8" and python_version < "4.0"
|
|
||||||
charset-normalizer==2.1.1 ; python_version >= "3.8" and python_version < "4.0"
|
|
||||||
frozenlist==1.3.1 ; python_version >= "3.8" and python_version < "4.0"
|
|
||||||
idna==3.4 ; python_version >= "3.8" and python_version < "4.0"
|
|
||||||
magic-filter==1.0.9 ; python_version >= "3.8" and python_version < "4.0"
|
|
||||||
multidict==6.0.2 ; python_version >= "3.8" and python_version < "4.0"
|
|
||||||
pydantic==1.10.2 ; python_version >= "3.8" and python_version < "4.0"
|
|
||||||
pytz==2022.5 ; python_version >= "3.8" and python_version < "4.0"
|
|
||||||
typing-extensions==4.4.0 ; python_version >= "3.8" and python_version < "4.0"
|
|
||||||
yarl==1.8.1 ; python_version >= "3.8" and python_version < "4.0"
|
|
||||||
|
|
@ -1,65 +0,0 @@
|
||||||
aiofiles==22.1.0 ; python_version >= "3.8" and python_version < "4.0"
|
|
||||||
aiohttp-socks==0.7.1 ; python_version >= "3.8" and python_version < "4.0"
|
|
||||||
aiohttp==3.8.3 ; python_version >= "3.8" and python_version < "4.0"
|
|
||||||
aiosignal==1.2.0 ; python_version >= "3.8" and python_version < "4.0"
|
|
||||||
alabaster==0.7.12 ; python_version >= "3.8" and python_version < "4.0"
|
|
||||||
async-timeout==4.0.2 ; python_version >= "3.8" and python_version < "4.0"
|
|
||||||
attrs==22.1.0 ; python_version >= "3.8" and python_version < "4.0"
|
|
||||||
babel==2.10.3 ; python_version >= "3.8" and python_version < "4.0"
|
|
||||||
beautifulsoup4==4.11.1 ; python_version >= "3.8" and python_version < "4.0"
|
|
||||||
certifi==2022.9.24 ; python_version >= "3.8" and python_version < "4.0"
|
|
||||||
charset-normalizer==2.1.1 ; python_version >= "3.8" and python_version < "4.0"
|
|
||||||
click-default-group==1.2.2 ; python_version >= "3.8" and python_version < "4.0"
|
|
||||||
click==8.1.3 ; python_version >= "3.8" and python_version < "4.0"
|
|
||||||
colorama==0.4.6 ; python_version >= "3.8" and python_version < "4.0"
|
|
||||||
deprecated==1.2.13 ; python_version >= "3.8" and python_version < "4.0"
|
|
||||||
docutils==0.19 ; python_version >= "3.8" and python_version < "4.0"
|
|
||||||
frozenlist==1.3.1 ; python_version >= "3.8" and python_version < "4.0"
|
|
||||||
furo==2022.9.29 ; python_version >= "3.8" and python_version < "4.0"
|
|
||||||
idna==3.4 ; python_version >= "3.8" and python_version < "4.0"
|
|
||||||
imagesize==1.4.1 ; python_version >= "3.8" and python_version < "4.0"
|
|
||||||
importlib-metadata==5.0.0 ; python_version >= "3.8" and python_version < "3.10"
|
|
||||||
incremental==22.10.0 ; python_version >= "3.8" and python_version < "4.0"
|
|
||||||
jinja2==3.1.2 ; python_version >= "3.8" and python_version < "4.0"
|
|
||||||
livereload==2.6.3 ; python_version >= "3.8" and python_version < "4.0"
|
|
||||||
magic-filter==1.0.9 ; python_version >= "3.8" and python_version < "4.0"
|
|
||||||
markdown-include==0.7.0 ; python_version >= "3.8" and python_version < "4.0"
|
|
||||||
markdown==3.4.1 ; python_version >= "3.8" and python_version < "4.0"
|
|
||||||
markupsafe==2.1.1 ; python_version >= "3.8" and python_version < "4.0"
|
|
||||||
multidict==6.0.2 ; python_version >= "3.8" and python_version < "4.0"
|
|
||||||
packaging==21.3 ; python_version >= "3.8" and python_version < "4.0"
|
|
||||||
pydantic==1.10.2 ; python_version >= "3.8" and python_version < "4.0"
|
|
||||||
pygments==2.13.0 ; python_version >= "3.8" and python_version < "4.0"
|
|
||||||
pymdown-extensions==9.7 ; python_version >= "3.8" and python_version < "4.0"
|
|
||||||
pyparsing==3.0.9 ; python_version >= "3.8" and python_version < "4.0"
|
|
||||||
python-socks[asyncio]==2.0.3 ; python_version >= "3.8" and python_version < "4.0"
|
|
||||||
pytz==2022.5 ; python_version >= "3.8" and python_version < "4.0"
|
|
||||||
redis==4.3.4 ; python_version >= "3.8" and python_version < "4.0"
|
|
||||||
requests==2.28.1 ; python_version >= "3.8" and python_version < "4"
|
|
||||||
setuptools==65.5.0 ; python_version >= "3.8" and python_version < "4.0"
|
|
||||||
six==1.16.0 ; python_version >= "3.8" and python_version < "4.0"
|
|
||||||
snowballstemmer==2.2.0 ; python_version >= "3.8" and python_version < "4.0"
|
|
||||||
soupsieve==2.3.2.post1 ; python_version >= "3.8" and python_version < "4.0"
|
|
||||||
sphinx-autobuild==2021.3.14 ; python_version >= "3.8" and python_version < "4.0"
|
|
||||||
sphinx-basic-ng==1.0.0b1 ; python_version >= "3.8" and python_version < "4.0"
|
|
||||||
sphinx-copybutton==0.5.0 ; python_version >= "3.8" and python_version < "4.0"
|
|
||||||
sphinx-intl==2.0.1 ; python_version >= "3.8" and python_version < "4.0"
|
|
||||||
sphinx-prompt==1.5.0 ; python_version >= "3.8" and python_version < "4.0"
|
|
||||||
sphinx-substitution-extensions==2022.2.16 ; python_version >= "3.8" and python_version < "4.0"
|
|
||||||
sphinx==5.3.0 ; python_version >= "3.8" and python_version < "4.0"
|
|
||||||
sphinxcontrib-applehelp==1.0.2 ; python_version >= "3.8" and python_version < "4.0"
|
|
||||||
sphinxcontrib-devhelp==1.0.2 ; python_version >= "3.8" and python_version < "4.0"
|
|
||||||
sphinxcontrib-htmlhelp==2.0.0 ; python_version >= "3.8" and python_version < "4.0"
|
|
||||||
sphinxcontrib-jsmath==1.0.1 ; python_version >= "3.8" and python_version < "4.0"
|
|
||||||
sphinxcontrib-qthelp==1.0.3 ; python_version >= "3.8" and python_version < "4.0"
|
|
||||||
sphinxcontrib-serializinghtml==1.1.5 ; python_version >= "3.8" and python_version < "4.0"
|
|
||||||
sphinxcontrib-towncrier==0.3.1a3 ; python_version >= "3.8" and python_version < "4.0"
|
|
||||||
tomli==2.0.1 ; python_version >= "3.8" and python_version < "4.0"
|
|
||||||
tornado==6.2 ; python_version >= "3.8" and python_version < "4.0"
|
|
||||||
towncrier==22.8.0 ; python_version >= "3.8" and python_version < "4.0"
|
|
||||||
typing-extensions==4.4.0 ; python_version >= "3.8" and python_version < "4.0"
|
|
||||||
urllib3==1.26.12 ; python_version >= "3.8" and python_version < "4"
|
|
||||||
uvloop==0.17.0 ; python_version >= "3.8" and python_version < "4.0" and sys_platform == "darwin" or python_version >= "3.8" and python_version < "4.0" and sys_platform == "linux"
|
|
||||||
wrapt==1.14.1 ; python_version >= "3.8" and python_version < "4.0"
|
|
||||||
yarl==1.8.1 ; python_version >= "3.8" and python_version < "4.0"
|
|
||||||
zipp==3.10.0 ; python_version >= "3.8" and python_version < "3.10"
|
|
||||||
|
|
@ -5,7 +5,11 @@ from _pytest.config import UsageError
|
||||||
from redis.asyncio.connection import parse_url as parse_redis_url
|
from redis.asyncio.connection import parse_url as parse_redis_url
|
||||||
|
|
||||||
from aiogram import Bot, Dispatcher
|
from aiogram import Bot, Dispatcher
|
||||||
from aiogram.fsm.storage.memory import DisabledEventIsolation, MemoryStorage, SimpleEventIsolation
|
from aiogram.fsm.storage.memory import (
|
||||||
|
DisabledEventIsolation,
|
||||||
|
MemoryStorage,
|
||||||
|
SimpleEventIsolation,
|
||||||
|
)
|
||||||
from aiogram.fsm.storage.redis import RedisEventIsolation, RedisStorage
|
from aiogram.fsm.storage.redis import RedisEventIsolation, RedisStorage
|
||||||
from tests.mocked_bot import MockedBot
|
from tests.mocked_bot import MockedBot
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,10 @@
|
||||||
from aiogram import Bot
|
from aiogram import Bot
|
||||||
from aiogram.methods import AnswerInlineQuery, Request
|
from aiogram.methods import AnswerInlineQuery, Request
|
||||||
from aiogram.types import InlineQueryResult, InlineQueryResultPhoto, InputTextMessageContent
|
from aiogram.types import (
|
||||||
|
InlineQueryResult,
|
||||||
|
InlineQueryResultPhoto,
|
||||||
|
InputTextMessageContent,
|
||||||
|
)
|
||||||
from tests.mocked_bot import MockedBot
|
from tests.mocked_bot import MockedBot
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,11 @@
|
||||||
from aiogram.methods import AnswerShippingQuery
|
from aiogram.methods import AnswerShippingQuery
|
||||||
from aiogram.types import LabeledPrice, ShippingAddress, ShippingOption, ShippingQuery, User
|
from aiogram.types import (
|
||||||
|
LabeledPrice,
|
||||||
|
ShippingAddress,
|
||||||
|
ShippingOption,
|
||||||
|
ShippingQuery,
|
||||||
|
User,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class TestInlineQuery:
|
class TestInlineQuery:
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,15 @@ from typing import Sequence, Type
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from aiogram.filters import Text
|
from aiogram.filters import Text
|
||||||
from aiogram.types import CallbackQuery, Chat, InlineQuery, Message, Poll, PollOption, User
|
from aiogram.types import (
|
||||||
|
CallbackQuery,
|
||||||
|
Chat,
|
||||||
|
InlineQuery,
|
||||||
|
Message,
|
||||||
|
Poll,
|
||||||
|
PollOption,
|
||||||
|
User,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class TestText:
|
class TestText:
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,11 @@
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from aiogram.fsm.storage.base import DEFAULT_DESTINY, StorageKey
|
from aiogram.fsm.storage.base import DEFAULT_DESTINY, StorageKey
|
||||||
from aiogram.fsm.storage.redis import DefaultKeyBuilder, RedisEventIsolation, RedisStorage
|
from aiogram.fsm.storage.redis import (
|
||||||
|
DefaultKeyBuilder,
|
||||||
|
RedisEventIsolation,
|
||||||
|
RedisStorage,
|
||||||
|
)
|
||||||
|
|
||||||
PREFIX = "test"
|
PREFIX = "test"
|
||||||
BOT_ID = 42
|
BOT_ID = 42
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,12 @@ from aiogram.fsm.context import FSMContext
|
||||||
from aiogram.fsm.storage.base import StorageKey
|
from aiogram.fsm.storage.base import StorageKey
|
||||||
from aiogram.fsm.storage.memory import MemoryStorage
|
from aiogram.fsm.storage.memory import MemoryStorage
|
||||||
from aiogram.types import Update, User
|
from aiogram.types import Update, User
|
||||||
from aiogram.utils.i18n import ConstI18nMiddleware, FSMI18nMiddleware, I18n, SimpleI18nMiddleware
|
from aiogram.utils.i18n import (
|
||||||
|
ConstI18nMiddleware,
|
||||||
|
FSMI18nMiddleware,
|
||||||
|
I18n,
|
||||||
|
SimpleI18nMiddleware,
|
||||||
|
)
|
||||||
from aiogram.utils.i18n.context import get_i18n, gettext, lazy_gettext
|
from aiogram.utils.i18n.context import get_i18n, gettext, lazy_gettext
|
||||||
from tests.conftest import DATA_DIR
|
from tests.conftest import DATA_DIR
|
||||||
from tests.mocked_bot import MockedBot
|
from tests.mocked_bot import MockedBot
|
||||||
|
|
@ -51,24 +56,24 @@ class TestI18nCore:
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"locale,case,result",
|
"locale,case,result",
|
||||||
[
|
[
|
||||||
[None, dict(singular="test"), "test"],
|
[None, {"singular": "test"}, "test"],
|
||||||
[None, dict(singular="test", locale="uk"), "тест"],
|
[None, {"singular": "test", "locale": "uk"}, "тест"],
|
||||||
["en", dict(singular="test", locale="uk"), "тест"],
|
["en", {"singular": "test", "locale": "uk"}, "тест"],
|
||||||
["uk", dict(singular="test", locale="uk"), "тест"],
|
["uk", {"singular": "test", "locale": "uk"}, "тест"],
|
||||||
["uk", dict(singular="test"), "тест"],
|
["uk", {"singular": "test"}, "тест"],
|
||||||
["it", dict(singular="test"), "test"],
|
["it", {"singular": "test"}, "test"],
|
||||||
[None, dict(singular="test", n=2), "test"],
|
[None, {"singular": "test", "n": 2}, "test"],
|
||||||
[None, dict(singular="test", n=2, locale="uk"), "тест"],
|
[None, {"singular": "test", "n": 2, "locale": "uk"}, "тест"],
|
||||||
["en", dict(singular="test", n=2, locale="uk"), "тест"],
|
["en", {"singular": "test", "n": 2, "locale": "uk"}, "тест"],
|
||||||
["uk", dict(singular="test", n=2, locale="uk"), "тест"],
|
["uk", {"singular": "test", "n": 2, "locale": "uk"}, "тест"],
|
||||||
["uk", dict(singular="test", n=2), "тест"],
|
["uk", {"singular": "test", "n": 2}, "тест"],
|
||||||
["it", dict(singular="test", n=2), "test"],
|
["it", {"singular": "test", "n": 2}, "test"],
|
||||||
[None, dict(singular="test", plural="test2", n=2), "test2"],
|
[None, {"singular": "test", "plural": "test2", "n": 2}, "test2"],
|
||||||
[None, dict(singular="test", plural="test2", n=2, locale="uk"), "test2"],
|
[None, {"singular": "test", "plural": "test2", "n": 2, "locale": "uk"}, "test2"],
|
||||||
["en", dict(singular="test", plural="test2", n=2, locale="uk"), "test2"],
|
["en", {"singular": "test", "plural": "test2", "n": 2, "locale": "uk"}, "test2"],
|
||||||
["uk", dict(singular="test", plural="test2", n=2, locale="uk"), "test2"],
|
["uk", {"singular": "test", "plural": "test2", "n": 2, "locale": "uk"}, "test2"],
|
||||||
["uk", dict(singular="test", plural="test2", n=2), "test2"],
|
["uk", {"singular": "test", "plural": "test2", "n": 2}, "test2"],
|
||||||
["it", dict(singular="test", plural="test2", n=2), "test2"],
|
["it", {"singular": "test", "plural": "test2", "n": 2}, "test2"],
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_gettext(self, i18n: I18n, locale: str, case: Dict[str, Any], result: str):
|
def test_gettext(self, i18n: I18n, locale: str, case: Dict[str, Any], result: str):
|
||||||
|
|
@ -110,8 +115,17 @@ class TestSimpleI18nMiddleware:
|
||||||
middleware = SimpleI18nMiddleware(i18n=i18n)
|
middleware = SimpleI18nMiddleware(i18n=i18n)
|
||||||
middleware.setup(router=dp)
|
middleware.setup(router=dp)
|
||||||
|
|
||||||
assert middleware not in dp.update.outer_middleware
|
|
||||||
assert middleware in dp.message.outer_middleware
|
assert middleware in dp.message.outer_middleware
|
||||||
|
assert middleware in dp.callback_query.outer_middleware
|
||||||
|
|
||||||
|
async def test_setup_exclude(self, i18n: I18n):
|
||||||
|
dp = Dispatcher()
|
||||||
|
middleware = SimpleI18nMiddleware(i18n=i18n)
|
||||||
|
middleware.setup(router=dp, exclude={"message"})
|
||||||
|
|
||||||
|
assert middleware not in dp.update.outer_middleware
|
||||||
|
assert middleware not in dp.message.outer_middleware
|
||||||
|
assert middleware in dp.callback_query.outer_middleware
|
||||||
|
|
||||||
async def test_get_unknown_locale(self, i18n: I18n):
|
async def test_get_unknown_locale(self, i18n: I18n):
|
||||||
dp = Dispatcher()
|
dp = Dispatcher()
|
||||||
|
|
@ -131,6 +145,19 @@ class TestSimpleI18nMiddleware:
|
||||||
)
|
)
|
||||||
assert locale == i18n.default_locale
|
assert locale == i18n.default_locale
|
||||||
|
|
||||||
|
async def test_custom_keys(self, i18n: I18n):
|
||||||
|
async def handler(event, data):
|
||||||
|
return data
|
||||||
|
|
||||||
|
middleware = SimpleI18nMiddleware(
|
||||||
|
i18n=i18n, i18n_key="translator", middleware_key="middleware"
|
||||||
|
)
|
||||||
|
context: dict[str, Any] = await middleware(handler, None, {})
|
||||||
|
assert "translator" in context
|
||||||
|
assert context["translator"] == i18n
|
||||||
|
assert "middleware" in context
|
||||||
|
assert context["middleware"] == middleware
|
||||||
|
|
||||||
|
|
||||||
class TestConstI18nMiddleware:
|
class TestConstI18nMiddleware:
|
||||||
async def test_middleware(self, i18n: I18n):
|
async def test_middleware(self, i18n: I18n):
|
||||||
|
|
@ -138,13 +165,17 @@ class TestConstI18nMiddleware:
|
||||||
result = await middleware(
|
result = await middleware(
|
||||||
next_call,
|
next_call,
|
||||||
Update(update_id=42),
|
Update(update_id=42),
|
||||||
{"event_from_user": User(id=42, is_bot=False, language_code="it", first_name="Test")},
|
{
|
||||||
|
"event_from_user": User(
|
||||||
|
id=42, is_bot=False, language_code="it", first_name="Test"
|
||||||
|
),
|
||||||
|
},
|
||||||
)
|
)
|
||||||
assert result == "тест"
|
assert result == "тест"
|
||||||
|
|
||||||
|
|
||||||
class TestFSMI18nMiddleware:
|
class TestFSMI18nMiddleware:
|
||||||
async def test_middleware(self, i18n: I18n, bot: MockedBot):
|
async def test_middleware(self, i18n: I18n, bot: MockedBot, extra):
|
||||||
middleware = FSMI18nMiddleware(i18n=i18n)
|
middleware = FSMI18nMiddleware(i18n=i18n)
|
||||||
storage = MemoryStorage()
|
storage = MemoryStorage()
|
||||||
state = FSMContext(
|
state = FSMContext(
|
||||||
|
|
@ -160,3 +191,16 @@ class TestFSMI18nMiddleware:
|
||||||
assert i18n.current_locale == "uk"
|
assert i18n.current_locale == "uk"
|
||||||
result = await middleware(next_call, Update(update_id=42), data)
|
result = await middleware(next_call, Update(update_id=42), data)
|
||||||
assert result == "тест"
|
assert result == "тест"
|
||||||
|
|
||||||
|
async def test_without_state(self, i18n: I18n, bot: MockedBot, extra):
|
||||||
|
middleware = FSMI18nMiddleware(i18n=i18n)
|
||||||
|
data = {
|
||||||
|
"event_from_user": User(id=42, is_bot=False, language_code="it", first_name="Test"),
|
||||||
|
}
|
||||||
|
result = await middleware(next_call, Update(update_id=42), data)
|
||||||
|
assert i18n.current_locale == "en"
|
||||||
|
assert result == "test"
|
||||||
|
|
||||||
|
assert i18n.current_locale == "en"
|
||||||
|
result = await middleware(next_call, Update(update_id=42), data)
|
||||||
|
assert i18n.current_locale == "en"
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,11 @@ from aiogram.types import (
|
||||||
KeyboardButton,
|
KeyboardButton,
|
||||||
ReplyKeyboardMarkup,
|
ReplyKeyboardMarkup,
|
||||||
)
|
)
|
||||||
from aiogram.utils.keyboard import InlineKeyboardBuilder, KeyboardBuilder, ReplyKeyboardBuilder
|
from aiogram.utils.keyboard import (
|
||||||
|
InlineKeyboardBuilder,
|
||||||
|
KeyboardBuilder,
|
||||||
|
ReplyKeyboardBuilder,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class MyCallback(CallbackData, prefix="test"):
|
class MyCallback(CallbackData, prefix="test"):
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,11 @@ from typing import List, Optional
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from aiogram.types import MessageEntity, User
|
from aiogram.types import MessageEntity, User
|
||||||
from aiogram.utils.text_decorations import TextDecoration, html_decoration, markdown_decoration
|
from aiogram.utils.text_decorations import (
|
||||||
|
TextDecoration,
|
||||||
|
html_decoration,
|
||||||
|
markdown_decoration,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class TestTextDecoration:
|
class TestTextDecoration:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue