├── aiosignal ├── py.typed └── __init__.py ├── tests ├── conftest.py └── test_signals.py ├── CHANGES ├── .gitignore └── .TEMPLATE.rst ├── requirements ├── towncrier.txt ├── ci-bot.txt ├── dev.txt ├── doc.txt ├── ci-wheel.txt ├── wheel.txt ├── doc-spelling.txt └── ci.txt ├── .github ├── codeql.yml ├── config.yml ├── dependabot.yml ├── ISSUE_TEMPLATE.md ├── workflows │ ├── dependabot-auto-merge.yml │ ├── codeql.yml │ └── ci-cd.yml ├── reusables │ └── tox-dev │ │ └── workflow │ │ └── reusable-tox │ │ └── hooks │ │ ├── post-src-checkout │ │ └── action.yml │ │ └── post-tox-run │ │ └── action.yml ├── actions │ └── cache-keys │ │ └── action.yml └── PULL_REQUEST_TEMPLATE.md ├── tools ├── drop_merged_branches.sh ├── run_docker.sh ├── check_changes.py └── build-wheels.sh ├── setup.py ├── CONTRIBUTORS.txt ├── .cherry_picker.toml ├── MANIFEST.in ├── .coveragerc ├── pyproject.toml ├── .editorconfig ├── .codecov.yml ├── .gitignore ├── .readthedocs.yaml ├── .mypy.ini ├── Makefile ├── setup.cfg ├── pytest.ini ├── README.rst ├── CODE_OF_CONDUCT.md ├── docs ├── index.rst ├── spelling_wordlist.txt ├── Makefile ├── make.bat └── conf.py ├── CHANGES.rst ├── .pre-commit-config.yaml ├── LICENSE └── tox.ini /aiosignal/py.typed: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /CHANGES/.gitignore: -------------------------------------------------------------------------------- 1 | !.gitignore 2 | -------------------------------------------------------------------------------- /requirements/towncrier.txt: -------------------------------------------------------------------------------- 1 | towncrier==25.8.0 2 | -------------------------------------------------------------------------------- /requirements/ci-bot.txt: -------------------------------------------------------------------------------- 1 | -r ci-wheel.txt 2 | -e . 3 | -------------------------------------------------------------------------------- /requirements/dev.txt: -------------------------------------------------------------------------------- 1 | -r ci.txt 2 | -r towncrier.txt 3 | cherry_picker==2.6.0 4 | -------------------------------------------------------------------------------- /requirements/doc.txt: -------------------------------------------------------------------------------- 1 | sphinx==8.2.3 2 | pygments>=2.1 3 | aiohttp-theme==0.1.7 4 | -------------------------------------------------------------------------------- /.github/codeql.yml: -------------------------------------------------------------------------------- 1 | query-filters: 2 | - exclude: 3 | id: py/unsafe-cyclic-import 4 | -------------------------------------------------------------------------------- /tools/drop_merged_branches.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | git remote prune origin 4 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | if __name__ == "__main__": 4 | setup() 5 | -------------------------------------------------------------------------------- /requirements/ci-wheel.txt: -------------------------------------------------------------------------------- 1 | -r wheel.txt 2 | 3 | coverage==7.10.7 4 | pytest-cov==7.0.0 5 | tox==4.30.3 6 | -------------------------------------------------------------------------------- /requirements/wheel.txt: -------------------------------------------------------------------------------- 1 | pytest==8.4.2 2 | pytest-asyncio==1.2.0 3 | pre-commit==4.3.0 4 | twine==6.2.0 5 | -------------------------------------------------------------------------------- /CONTRIBUTORS.txt: -------------------------------------------------------------------------------- 1 | - Contributors - 2 | ---------------- 3 | Andrew Svetlov 4 | Martijn Pieters 5 | Nikolay Kim 6 | Vizonex 7 | -------------------------------------------------------------------------------- /.github/config.yml: -------------------------------------------------------------------------------- 1 | chronographer: 2 | exclude: 3 | bots: 4 | - dependabot-preview 5 | humans: 6 | - pyup-bot 7 | -------------------------------------------------------------------------------- /requirements/doc-spelling.txt: -------------------------------------------------------------------------------- 1 | -r doc.txt 2 | sphinxcontrib-spelling==8.0.1; platform_system!="Windows" # We only use it in Travis CI 3 | -------------------------------------------------------------------------------- /.cherry_picker.toml: -------------------------------------------------------------------------------- 1 | team = "aio-libs" 2 | repo = "aiosignal" 3 | check_sha = "f382b5ffc445e45a110734f5396728da7914aeb6" 4 | fix_commit_msg = false 5 | -------------------------------------------------------------------------------- /requirements/ci.txt: -------------------------------------------------------------------------------- 1 | setuptools-git==1.2 2 | mypy==1.18.2; implementation_name=="cpython" 3 | mypy-extensions==1.1.0; implementation_name=="cpython" 4 | 5 | -r ci-wheel.txt 6 | -r doc.txt 7 | -e . 8 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: pip 4 | directory: / 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | target-branch: master 9 | - package-ecosystem: github-actions 10 | directory: / 11 | schedule: 12 | interval: daily 13 | open-pull-requests-limit: 10 14 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include .codecov.yml 2 | include .coveragerc 3 | include LICENSE 4 | include CHANGES.rst 5 | include README.rst 6 | include CONTRIBUTORS.txt 7 | include Makefile 8 | include pytest.ini 9 | include tox.ini 10 | graft aiosignal 11 | graft docs 12 | graft requirements 13 | graft tests 14 | global-include *.pyi 15 | global-exclude *.pyc 16 | prune docs/_build 17 | -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | [html] 2 | show_contexts = true 3 | skip_covered = false 4 | 5 | [paths] 6 | _site-packages-to-src-mapping = 7 | . 8 | */lib/pypy*/site-packages 9 | */lib/python*/site-packages 10 | *\Lib\site-packages 11 | 12 | [run] 13 | branch = true 14 | cover_pylib = false 15 | omit = 16 | setup.py 17 | parallel = true 18 | relative_files = true 19 | source = 20 | . 21 | source_pkgs = 22 | aiosignal 23 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "setuptools>=51.0", 4 | ] 5 | build-backend = "setuptools.build_meta" 6 | 7 | 8 | [tool.towncrier] 9 | package = "aiosignal" 10 | filename = "CHANGES.rst" 11 | directory = "CHANGES/" 12 | title_format = "{version} ({project_date})" 13 | template = "CHANGES/.TEMPLATE.rst" 14 | issue_format = "`#{issue} `_" 15 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Unix-style newlines with a newline ending every file 7 | [*] 8 | end_of_line = lf 9 | insert_final_newline = true 10 | indent_style = space 11 | indent_size = 4 12 | trim_trailing_whitespace = true 13 | charset = utf-8 14 | 15 | [Makefile] 16 | indent_style = tab 17 | 18 | [*.{yml,yaml}] 19 | indent_size = 2 20 | 21 | [*.rst] 22 | max_line_length = 80 23 | -------------------------------------------------------------------------------- /.codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | range: 95..100 3 | 4 | status: 5 | project: off 6 | 7 | flags: 8 | library: 9 | paths: 10 | - aiosignal/ 11 | configs: 12 | paths: 13 | - requirements/ 14 | - .git* 15 | - '*.toml' 16 | - '*.yml' 17 | changelog: 18 | paths: 19 | - CHANGES/ 20 | - CHANGES.rst 21 | docs: 22 | paths: 23 | - docs/ 24 | - '*.md' 25 | - '*.rst' 26 | - '*.txt' 27 | tests: 28 | paths: 29 | - tests/ 30 | tools: 31 | paths: 32 | - tools/ 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.bak 3 | *.egg 4 | *.egg-info 5 | *.eggs 6 | *.pyc 7 | *.pyd 8 | *.pyo 9 | *.so 10 | *.tar.gz 11 | *~ 12 | .DS_Store 13 | .Python 14 | .cache 15 | .coverage 16 | .coverage.* 17 | .direnv 18 | .envrc 19 | .idea 20 | .installed.cfg 21 | .noseids 22 | .tox 23 | .vimrc 24 | bin 25 | build 26 | htmlcov 27 | develop-eggs 28 | dist 29 | docs/_build/ 30 | eggs 31 | include/ 32 | lib/ 33 | man/ 34 | nosetests.xml 35 | parts 36 | pyvenv 37 | sources 38 | var/* 39 | venv 40 | virtualenv.py 41 | .install-deps 42 | .develop 43 | .gitconfig 44 | .flake 45 | .python-version 46 | .pytest_cache 47 | .vscode 48 | .mypy_cache 49 | pip-wheel-metadata 50 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Long story short 2 | 3 | 4 | 5 | ## Expected behaviour 6 | 7 | 8 | 9 | ## Actual behaviour 10 | 11 | 12 | 13 | ## Steps to reproduce 14 | 15 | 18 | 19 | ## Your environment 20 | 21 | 25 | -------------------------------------------------------------------------------- /.github/workflows/dependabot-auto-merge.yml: -------------------------------------------------------------------------------- 1 | name: Dependabot auto-merge 2 | on: pull_request_target 3 | 4 | permissions: 5 | pull-requests: write 6 | contents: write 7 | 8 | jobs: 9 | dependabot: 10 | runs-on: ubuntu-latest 11 | if: ${{ github.actor == 'dependabot[bot]' }} 12 | steps: 13 | - name: Dependabot metadata 14 | id: metadata 15 | uses: dependabot/fetch-metadata@v2.4.0 16 | with: 17 | github-token: ${{ secrets.GITHUB_TOKEN }} 18 | - name: Enable auto-merge for Dependabot PRs 19 | run: gh pr merge --auto --squash "$PR_URL" 20 | env: 21 | PR_URL: ${{github.event.pull_request.html_url}} 22 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 23 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | build: 4 | os: ubuntu-24.04 5 | tools: 6 | python: >- # has to be parsed as a YAML string 7 | 3.13 8 | commands: 9 | - >- 10 | PYTHONWARNINGS=error 11 | python3 -Im venv "${READTHEDOCS_VIRTUALENV_PATH}" 12 | - >- 13 | PYTHONWARNINGS=error 14 | "${READTHEDOCS_VIRTUALENV_PATH}"/bin/python -Im 15 | pip install tox 16 | - >- 17 | PYTHONWARNINGS=error 18 | "${READTHEDOCS_VIRTUALENV_PATH}"/bin/python -Im 19 | tox -e build-docs --notest -vvvvv 20 | - >- 21 | PYTHONWARNINGS=error 22 | "${READTHEDOCS_VIRTUALENV_PATH}"/bin/python -Im 23 | tox -e build-docs --skip-pkg-install -q 24 | -- 25 | "${READTHEDOCS_OUTPUT}"/html -b dirhtml 26 | -------------------------------------------------------------------------------- /CHANGES/.TEMPLATE.rst: -------------------------------------------------------------------------------- 1 | {# TOWNCRIER TEMPLATE #} 2 | {% for section, _ in sections.items() %} 3 | {% set underline = underlines[0] %}{% if section %}{{section}} 4 | {{ underline * section|length }}{% set underline = underlines[1] %} 5 | 6 | {% endif %} 7 | 8 | {% if sections[section] %} 9 | {% for category, val in definitions.items() if category in sections[section]%} 10 | {{ definitions[category]['name'] }} 11 | {{ underline * definitions[category]['name']|length }} 12 | 13 | {% if definitions[category]['showcontent'] %} 14 | {% for text, values in sections[section][category].items() %} 15 | - {{ text }} 16 | {{ values|join(',\n ') }} 17 | {% endfor %} 18 | 19 | {% else %} 20 | - {{ sections[section][category]['']|join(', ') }} 21 | 22 | {% endif %} 23 | {% if sections[section][category]|length == 0 %} 24 | No significant changes. 25 | 26 | {% else %} 27 | {% endif %} 28 | 29 | {% endfor %} 30 | {% else %} 31 | No significant changes. 32 | 33 | 34 | {% endif %} 35 | {% endfor %} 36 | ---- 37 | -------------------------------------------------------------------------------- /.mypy.ini: -------------------------------------------------------------------------------- 1 | [mypy] 2 | files = aiosignal, tests 3 | check_untyped_defs = True 4 | follow_imports_for_stubs = True 5 | disallow_any_decorated = True 6 | disallow_any_generics = True 7 | disallow_any_unimported = True 8 | disallow_incomplete_defs = True 9 | disallow_subclassing_any = True 10 | disallow_untyped_calls = True 11 | disallow_untyped_decorators = True 12 | disallow_untyped_defs = True 13 | # TODO(PY312): explicit-override 14 | enable_error_code = deprecated, ignore-without-code, possibly-undefined, redundant-expr, redundant-self, truthy-bool, truthy-iterable, unused-awaitable 15 | extra_checks = True 16 | follow_untyped_imports = True 17 | implicit_reexport = False 18 | no_implicit_optional = True 19 | pretty = True 20 | show_column_numbers = True 21 | show_error_codes = True 22 | show_error_code_links = True 23 | strict_bytes = True 24 | strict_equality = True 25 | warn_incomplete_stub = True 26 | warn_redundant_casts = True 27 | warn_return_any = True 28 | warn_unreachable = True 29 | warn_unused_ignores = True 30 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | name: CodeQL 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | branches: [master] 8 | schedule: 9 | - cron: 22 22 * * 5 10 | 11 | jobs: 12 | analyze: 13 | name: Analyze 14 | runs-on: ubuntu-latest 15 | permissions: 16 | actions: read 17 | contents: read 18 | security-events: write 19 | 20 | strategy: 21 | fail-fast: false 22 | matrix: 23 | language: [python] 24 | 25 | steps: 26 | - name: Checkout 27 | uses: actions/checkout@v5 28 | 29 | - name: Initialize CodeQL 30 | uses: github/codeql-action/init@v4 31 | with: 32 | languages: ${{ matrix.language }} 33 | config-file: ./.github/codeql.yml 34 | queries: +security-and-quality 35 | 36 | - name: Autobuild 37 | uses: github/codeql-action/autobuild@v4 38 | 39 | - name: Perform CodeQL Analysis 40 | uses: github/codeql-action/analyze@v4 41 | with: 42 | category: /language:${{ matrix.language }} 43 | -------------------------------------------------------------------------------- /.github/reusables/tox-dev/workflow/reusable-tox/hooks/post-src-checkout/action.yml: -------------------------------------------------------------------------------- 1 | inputs: 2 | calling-job-context: 3 | description: A JSON with the calling job inputs 4 | type: string 5 | job-dependencies-context: 6 | default: >- 7 | {} 8 | description: >- 9 | The `$ {{ needs }}` context passed from the calling workflow 10 | encoded as a JSON string. The caller is expected to form this 11 | input as follows: 12 | `job-dependencies-context: $ {{ toJSON(needs) }}`. 13 | required: false 14 | type: string 15 | 16 | runs: 17 | using: composite 18 | steps: 19 | - name: Log setting up pre-commit cache 20 | if: fromJSON(inputs.calling-job-context).toxenv == 'pre-commit' 21 | run: >- 22 | >&2 echo Caching ~/.cache/pre-commit based on 23 | the contents of '.pre-commit-config.yaml'... 24 | shell: bash 25 | - name: Cache pre-commit.com virtualenvs 26 | if: fromJSON(inputs.calling-job-context).toxenv == 'pre-commit' 27 | uses: actions/cache@v4 28 | with: 29 | path: ~/.cache/pre-commit 30 | key: >- 31 | ${{ 32 | runner.os 33 | }}-pre-commit-${{ 34 | hashFiles('.pre-commit-config.yaml') 35 | }} 36 | -------------------------------------------------------------------------------- /.github/actions/cache-keys/action.yml: -------------------------------------------------------------------------------- 1 | name: sentinel 2 | description: sentinel 3 | 4 | outputs: 5 | cache-key-for-dep-files: 6 | description: >- 7 | A cache key string derived from the dependency declaration files. 8 | value: ${{ steps.calc-cache-key-files.outputs.files-hash-key }} 9 | 10 | runs: 11 | using: composite 12 | steps: 13 | - name: >- 14 | Calculate dependency files' combined hash value 15 | for use in the cache key 16 | id: calc-cache-key-files 17 | run: | 18 | from os import environ 19 | from pathlib import Path 20 | 21 | FILE_APPEND_MODE = 'a' 22 | 23 | files_derived_hash = '${{ 24 | hashFiles( 25 | 'tox.ini', 26 | 'pyproject.toml', 27 | '.pre-commit-config.yaml', 28 | 'pytest.ini', 29 | 'requirements/**', 30 | 'setup.cfg', 31 | 'setup.py' 32 | ) 33 | }}' 34 | 35 | print(f'Computed file-derived hash is {files_derived_hash}.') 36 | 37 | with Path(environ['GITHUB_OUTPUT']).open( 38 | mode=FILE_APPEND_MODE, 39 | ) as outputs_file: 40 | print( 41 | f'files-hash-key={files_derived_hash}', 42 | file=outputs_file, 43 | ) 44 | shell: python 45 | -------------------------------------------------------------------------------- /tools/run_docker.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | package_name="$1" 6 | if [ -z "$package_name" ] 7 | then 8 | >&2 echo "Please pass package name as a first argument of this script ($0)" 9 | exit 1 10 | fi 11 | 12 | manylinux1_image_prefix="quay.io/pypa/manylinux1_" 13 | dock_ext_args="" 14 | declare -A docker_pull_pids=() # This syntax requires at least bash v4 15 | 16 | for arch in x86_64 i686 17 | do 18 | docker pull "${manylinux1_image_prefix}${arch}" & 19 | docker_pull_pids[$arch]=$! 20 | done 21 | 22 | echo Creating dist folder with privileges of host-machine user 23 | mkdir -p dist # This is required to be created with host-machine user privileges 24 | 25 | for arch in x86_64 i686 26 | do 27 | echo 28 | echo 29 | arch_pull_pid=${docker_pull_pids[$arch]} 30 | echo Waiting for docker pull PID $arch_pull_pid to complete downloading container for $arch arch... 31 | wait $arch_pull_pid # await for docker image for current arch to be pulled from hub 32 | [ $arch == "i686" ] && dock_ext_args="linux32" 33 | 34 | echo Building wheel for $arch arch 35 | docker run --rm -v `pwd`:/io "${manylinux1_image_prefix}${arch}" $dock_ext_args /io/tools/build-wheels.sh "$package_name" 36 | 37 | dock_ext_args="" # Reset docker args, just in case 38 | done 39 | 40 | set +u 41 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## What do these changes do? 4 | 5 | 6 | 7 | ## Are there changes in behavior for the user? 8 | 9 | 10 | 11 | ## Related issue number 12 | 13 | 14 | 15 | ## Checklist 16 | 17 | - [ ] I think the code is well written 18 | - [ ] Unit tests for the changes exist 19 | - [ ] Documentation reflects the changes 20 | - [ ] If you provide code modification, please add yourself to `CONTRIBUTORS.txt` 21 | * The format is <Name> <Surname>. 22 | * Please keep alphabetical order, the file is sorted by names. 23 | - [ ] Add a new news fragment into the `CHANGES` folder 24 | * name it `.` for example (588.bugfix) 25 | * if you don't have an `issue_id` change it to the pr id after creating the pr 26 | * ensure type is one of the following: 27 | * `.feature`: Signifying a new feature. 28 | * `.bugfix`: Signifying a bug fix. 29 | * `.doc`: Signifying a documentation improvement. 30 | * `.removal`: Signifying a deprecation or removal of public API. 31 | * `.misc`: A ticket has been closed, but it is not of interest to users. 32 | * Make sure to use full sentences with correct case and punctuation, for example: "Fix issue with non-ascii contents in doctest text files." 33 | -------------------------------------------------------------------------------- /tools/check_changes.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | from pathlib import Path 5 | 6 | ALLOWED_SUFFIXES = [".feature", ".bugfix", ".doc", ".removal", ".misc"] 7 | 8 | 9 | def get_root(script_path): 10 | folder = script_path.resolve().parent 11 | while not (folder / ".git").exists(): 12 | folder = folder.parent 13 | if folder == folder.anchor: 14 | raise RuntimeError("git repo not found") 15 | return folder 16 | 17 | 18 | def main(argv): 19 | print('Check "CHANGES" folder... ', end="", flush=True) 20 | here = Path(argv[0]) 21 | root = get_root(here) 22 | changes = root / "CHANGES" 23 | failed = False 24 | for fname in changes.iterdir(): 25 | if fname.name in (".gitignore", ".TEMPLATE.rst", "README.rst"): 26 | continue 27 | if fname.suffix == ".rst": 28 | test_name = Path(fname.stem) 29 | else: 30 | test_name = fname 31 | if test_name.suffix not in ALLOWED_SUFFIXES: 32 | if not failed: 33 | print("") 34 | print(fname, "has illegal suffix", file=sys.stderr) 35 | failed = True 36 | 37 | if failed: 38 | print("", file=sys.stderr) 39 | print( 40 | "Allowed suffixes are:", 41 | ALLOWED_SUFFIXES + [suff + ".rst" for suff in ALLOWED_SUFFIXES], 42 | file=sys.stderr, 43 | ) 44 | print("", file=sys.stderr) 45 | else: 46 | print("OK") 47 | 48 | return int(failed) 49 | 50 | 51 | if __name__ == "__main__": 52 | sys.exit(main(sys.argv)) 53 | -------------------------------------------------------------------------------- /aiosignal/__init__.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from typing import Any, Awaitable, Callable, TypeVar 3 | 4 | from frozenlist import FrozenList 5 | 6 | if sys.version_info >= (3, 11): 7 | from typing import Unpack 8 | else: 9 | from typing_extensions import Unpack 10 | 11 | if sys.version_info >= (3, 13): 12 | from typing import TypeVarTuple 13 | else: 14 | from typing_extensions import TypeVarTuple 15 | 16 | _T = TypeVar("_T") 17 | _Ts = TypeVarTuple("_Ts", default=Unpack[tuple[()]]) 18 | 19 | __version__ = "1.4.0" 20 | 21 | __all__ = ("Signal",) 22 | 23 | 24 | class Signal(FrozenList[Callable[[Unpack[_Ts]], Awaitable[object]]]): 25 | """Coroutine-based signal implementation. 26 | 27 | To connect a callback to a signal, use any list method. 28 | 29 | Signals are fired using the send() coroutine, which takes named 30 | arguments. 31 | """ 32 | 33 | __slots__ = ("_owner",) 34 | 35 | def __init__(self, owner: object): 36 | super().__init__() 37 | self._owner = owner 38 | 39 | def __repr__(self) -> str: 40 | return "".format( 41 | self._owner, self.frozen, list(self) 42 | ) 43 | 44 | async def send(self, *args: Unpack[_Ts], **kwargs: Any) -> None: 45 | """ 46 | Sends data to all registered receivers. 47 | """ 48 | if not self.frozen: 49 | raise RuntimeError("Cannot send non-frozen signal.") 50 | 51 | for receiver in self: 52 | await receiver(*args, **kwargs) 53 | 54 | def __call__( 55 | self, func: Callable[[Unpack[_Ts]], Awaitable[_T]] 56 | ) -> Callable[[Unpack[_Ts]], Awaitable[_T]]: 57 | """Decorator to add a function to this Signal.""" 58 | self.append(func) 59 | return func 60 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Some simple testing tasks (sorry, UNIX only). 2 | 3 | SRC = aiosignal tests setup.py 4 | 5 | all: test 6 | 7 | .install-deps: 8 | pip install -r requirements/dev.txt 9 | @touch .install-deps 10 | 11 | lint: 12 | # CI env-var is set by GitHub actions 13 | ifndef CI 14 | pre-commit run --all-files 15 | endif 16 | mypy aiosignal 17 | 18 | .develop: .install-deps $(shell find aiosignal -type f) 19 | # pip install -e . 20 | @touch .develop 21 | 22 | test: .develop 23 | @pytest -q 24 | 25 | vtest: .develop 26 | @pytest -s -v 27 | 28 | cov cover coverage: 29 | tox 30 | 31 | cov-dev: .develop 32 | @pytest -c pytest.ci.ini --cov-report=html 33 | @echo "open file://`pwd`/htmlcov/index.html" 34 | 35 | cov-ci-run: .develop 36 | @echo "Regular run" 37 | @pytest -c pytest.ci.ini --cov-report=html 38 | 39 | cov-dev-full: cov-ci-run 40 | @echo "open file://`pwd`/htmlcov/index.html" 41 | 42 | clean: 43 | @rm -rf `find . -name __pycache__` 44 | @rm -f `find . -type f -name '*.py[co]' ` 45 | @rm -f `find . -type f -name '*~' ` 46 | @rm -f `find . -type f -name '.*~' ` 47 | @rm -f `find . -type f -name '@*' ` 48 | @rm -f `find . -type f -name '#*#' ` 49 | @rm -f `find . -type f -name '*.orig' ` 50 | @rm -f `find . -type f -name '*.rej' ` 51 | @rm -f .coverage 52 | @rm -rf htmlcov 53 | @rm -rf build 54 | @rm -rf cover 55 | @make -C docs clean 56 | @python setup.py clean 57 | @rm -rf .tox 58 | @rm -f .develop 59 | @rm -f .flake 60 | @rm -f .install-deps 61 | @rm -rf aiosignal.egg-info 62 | 63 | doc: 64 | @make -C docs html SPHINXOPTS="-W -E" 65 | @echo "open file://`pwd`/docs/_build/html/index.html" 66 | 67 | doc-spelling: 68 | @make -C docs spelling SPHINXOPTS="-W -E" 69 | 70 | install: 71 | @pip install -U 'pip' 72 | @pip install -Ur requirements/dev.txt 73 | @pre-commit install 74 | 75 | install-dev: .develop 76 | 77 | .PHONY: all build flake test vtest cov clean doc mypy 78 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | name = aiosignal 3 | version = attr: aiosignal.__version__ 4 | url = https://github.com/aio-libs/aiosignal 5 | project_urls = 6 | Chat: Gitter = https://gitter.im/aio-libs/Lobby 7 | CI: GitHub Actions = https://github.com/aio-libs/aiosignal/actions 8 | Coverage: codecov = https://codecov.io/github/aio-libs/aiosignal 9 | Docs: RTD = https://docs.aiosignal.org 10 | GitHub: issues = https://github.com/aio-libs/aiosignal/issues 11 | GitHub: repo = https://github.com/aio-libs/aiosignal 12 | description = aiosignal: a list of registered asynchronous callbacks 13 | long_description = file: README.rst 14 | long_description_content_type = text/x-rst 15 | maintainer = aiohttp team 16 | maintainer_email = team@aiohttp.org 17 | license = Apache 2.0 18 | license_file = LICENSE 19 | classifiers = 20 | License :: OSI Approved :: Apache Software License 21 | 22 | Intended Audience :: Developers 23 | 24 | Programming Language :: Python 25 | Programming Language :: Python :: 3 26 | Programming Language :: Python :: 3 :: Only 27 | 28 | Development Status :: 5 - Production/Stable 29 | Operating System :: POSIX 30 | Operating System :: MacOS :: MacOS X 31 | Operating System :: Microsoft :: Windows 32 | Framework :: AsyncIO 33 | 34 | [options] 35 | python_requires = >=3.9 36 | packages = find: 37 | include_package_data = True 38 | 39 | install_requires = 40 | frozenlist >= 1.1.0 41 | typing-extensions >= 4.4; python_version < '3.13' 42 | 43 | 44 | [pep8] 45 | max-line-length=88 46 | 47 | [easy_install] 48 | zip_ok = false 49 | 50 | [flake8] 51 | ignore = N801,N802,N803,E203,E226,E305,W504,E252,E301,E302,E704,W503,W504,F811 52 | max-line-length = 88 53 | 54 | [isort] 55 | line_length=88 56 | include_trailing_comma=True 57 | multi_line_output=3 58 | force_grid_wrap=0 59 | combine_as_imports=True 60 | 61 | known_third_party=pytest 62 | known_first_party=aiosignal 63 | 64 | [report] 65 | exclude_lines = 66 | @abc.abstractmethod 67 | @abstractmethod 68 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | addopts = 3 | # `pytest-xdist`: 4 | # --numprocesses=auto 5 | # NOTE: the plugin disabled because it's slower with so few tests 6 | # --numprocesses=0 7 | 8 | # Show 10 slowest invocations: 9 | --durations=10 10 | 11 | # Report all the things == -rxXs: 12 | -ra 13 | 14 | # Show values of the local vars in errors/tracebacks: 15 | --showlocals 16 | 17 | # Autocollect and invoke the doctests from all modules: 18 | # https://docs.pytest.org/en/stable/doctest.html 19 | --doctest-modules 20 | 21 | # Pre-load the `pytest-cov` plugin early: 22 | -p pytest_cov 23 | 24 | # `pytest-cov`: 25 | --cov 26 | --cov-config=.coveragerc 27 | --cov-context=test 28 | --no-cov-on-fail 29 | 30 | # Fail on config parsing warnings: 31 | # --strict-config 32 | 33 | # Fail on non-existing markers: 34 | # * Deprecated since v6.2.0 but may be reintroduced later covering a 35 | # broader scope: 36 | # --strict 37 | # * Exists since v4.5.0 (advised to be used instead of `--strict`): 38 | --strict-markers 39 | 40 | asyncio_mode = auto 41 | asyncio_default_fixture_loop_scope = function 42 | 43 | doctest_optionflags = ALLOW_UNICODE ELLIPSIS 44 | 45 | # Marks tests with an empty parameterset as xfail(run=False) 46 | empty_parameter_set_mark = xfail 47 | 48 | faulthandler_timeout = 30 49 | 50 | filterwarnings = 51 | error 52 | 53 | # https://docs.pytest.org/en/stable/usage.html#creating-junitxml-format-files 54 | junit_duration_report = call 55 | # xunit1 contains more metadata than xunit2 so it's better for CI UIs: 56 | junit_family = xunit1 57 | junit_logging = all 58 | junit_log_passing_tests = true 59 | junit_suite_name = aiosignal_test_suite 60 | 61 | # A mapping of markers to their descriptions allowed in strict mode: 62 | markers = 63 | 64 | minversion = 6.1.0 65 | 66 | # Optimize pytest's lookup by restricting potentially deep dir tree scan: 67 | norecursedirs = 68 | build 69 | dependencies 70 | dist 71 | docs 72 | .* 73 | *.egg 74 | *.egg-info 75 | */*.egg-info 76 | */**/*.egg-info 77 | *.dist-info 78 | */*.dist-info 79 | */**/*.dist-info 80 | 81 | testpaths = tests/ 82 | 83 | xfail_strict = true 84 | -------------------------------------------------------------------------------- /.github/reusables/tox-dev/workflow/reusable-tox/hooks/post-tox-run/action.yml: -------------------------------------------------------------------------------- 1 | inputs: 2 | calling-job-context: 3 | description: A JSON with the calling job inputs 4 | type: string 5 | job-dependencies-context: 6 | default: >- 7 | {} 8 | description: >- 9 | The `$ {{ needs }}` context passed from the calling workflow 10 | encoded as a JSON string. The caller is expected to form this 11 | input as follows: 12 | `job-dependencies-context: $ {{ toJSON(needs) }}`. 13 | required: false 14 | type: string 15 | 16 | runs: 17 | using: composite 18 | steps: 19 | - name: Verify that the artifacts with expected names got created 20 | if: fromJSON(inputs.calling-job-context).toxenv == 'build-dists' 21 | run: > 22 | # Verify that the artifacts with expected names got created 23 | 24 | 25 | ls -1 26 | dist/${{ 27 | fromJSON( 28 | inputs.job-dependencies-context 29 | ).pre-setup.outputs.sdist-artifact-name 30 | }} 31 | dist/${{ 32 | fromJSON( 33 | inputs.job-dependencies-context 34 | ).pre-setup.outputs.wheel-artifact-name 35 | }} 36 | shell: bash 37 | - name: Store the distribution packages 38 | if: fromJSON(inputs.calling-job-context).toxenv == 'build-dists' 39 | uses: actions/upload-artifact@v4 40 | with: 41 | name: >- 42 | ${{ 43 | fromJSON( 44 | inputs.job-dependencies-context 45 | ).pre-setup.outputs.dists-artifact-name 46 | }} 47 | # NOTE: Exact expected file names are specified here 48 | # NOTE: as a safety measure — if anything weird ends 49 | # NOTE: up being in this dir or not all dists will be 50 | # NOTE: produced, this will fail the workflow. 51 | path: | 52 | dist/${{ 53 | fromJSON( 54 | inputs.job-dependencies-context 55 | ).pre-setup.outputs.sdist-artifact-name 56 | }} 57 | dist/${{ 58 | fromJSON( 59 | inputs.job-dependencies-context 60 | ).pre-setup.outputs.wheel-artifact-name 61 | }} 62 | retention-days: >- 63 | ${{ 64 | fromJSON( 65 | fromJSON( 66 | inputs.job-dependencies-context 67 | ).pre-setup.outputs.release-requested 68 | ) 69 | && 90 70 | || 30 71 | }} 72 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ========= 2 | aiosignal 3 | ========= 4 | 5 | .. image:: https://github.com/aio-libs/aiosignal/workflows/CI/badge.svg 6 | :target: https://github.com/aio-libs/aiosignal/actions?query=workflow%3ACI 7 | :alt: GitHub status for master branch 8 | 9 | .. image:: https://codecov.io/gh/aio-libs/aiosignal/branch/master/graph/badge.svg?flag=pytest 10 | :target: https://codecov.io/gh/aio-libs/aiosignal?flags[0]=pytest 11 | :alt: codecov.io status for master branch 12 | 13 | .. image:: https://badge.fury.io/py/aiosignal.svg 14 | :target: https://pypi.org/project/aiosignal 15 | :alt: Latest PyPI package version 16 | 17 | .. image:: https://readthedocs.org/projects/aiosignal/badge/?version=latest 18 | :target: https://aiosignal.readthedocs.io/ 19 | :alt: Latest Read The Docs 20 | 21 | .. image:: https://img.shields.io/discourse/topics?server=https%3A%2F%2Faio-libs.discourse.group%2F 22 | :target: https://aio-libs.discourse.group/ 23 | :alt: Discourse group for io-libs 24 | 25 | .. image:: https://badges.gitter.im/Join%20Chat.svg 26 | :target: https://gitter.im/aio-libs/Lobby 27 | :alt: Chat on Gitter 28 | 29 | Introduction 30 | ============ 31 | 32 | A project to manage callbacks in `asyncio` projects. 33 | 34 | ``Signal`` is a list of registered asynchronous callbacks. 35 | 36 | The signal's life-cycle has two stages: after creation its content 37 | could be filled by using standard list operations: ``sig.append()`` 38 | etc. 39 | 40 | After you call ``sig.freeze()`` the signal is *frozen*: adding, removing 41 | and dropping callbacks is forbidden. 42 | 43 | The only available operation is calling the previously registered 44 | callbacks by using ``await sig.send(data)``. 45 | 46 | For concrete usage examples see the `Signals 47 | 48 | section of the `Web Server Advanced 49 | ` chapter of the `aiohttp 50 | documentation`_. 51 | 52 | 53 | Installation 54 | ------------ 55 | 56 | :: 57 | 58 | $ pip install aiosignal 59 | 60 | 61 | Documentation 62 | ============= 63 | 64 | https://aiosignal.readthedocs.io/ 65 | 66 | License 67 | ======= 68 | 69 | ``aiosignal`` is offered under the Apache 2 license. 70 | 71 | Source code 72 | =========== 73 | 74 | The project is hosted on GitHub_ 75 | 76 | Please file an issue in the `bug tracker 77 | `_ if you have found a bug 78 | or have some suggestions to improve the library. 79 | 80 | .. _GitHub: https://github.com/aio-libs/aiosignal 81 | .. _aiohttp documentation: https://docs.aiohttp.org/ 82 | -------------------------------------------------------------------------------- /tools/build-wheels.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | if [ -n "$DEBUG" ] 3 | then 4 | set -x 5 | fi 6 | 7 | package_name="$1" 8 | if [ -z "$package_name" ] 9 | then 10 | >&2 echo "Please pass package name as a first argument of this script ($0)" 11 | exit 1 12 | fi 13 | 14 | export WORKDIR_PATH="${GITHUB_WORKSPACE:-/io}" 15 | 16 | BUILD_DIR=`mktemp -d "/tmp/${package_name}-manylinux1-build.XXXXXXXXXX"` 17 | ORIG_WHEEL_DIR="${BUILD_DIR}/original-wheelhouse" 18 | SRC_DIR="${BUILD_DIR}/src" 19 | WHEELHOUSE_DIR="${WORKDIR_PATH}/dist" 20 | 21 | set -euo pipefail 22 | # ref: https://coderwall.com/p/fkfaqq/safer-bash-scripts-with-set-euxo-pipefail 23 | 24 | PYTHON_VERSIONS="cp35-cp35m cp36-cp36m cp37-cp37m" 25 | 26 | # Avoid creation of __pycache__/*.py[c|o] 27 | export PYTHONDONTWRITEBYTECODE=1 28 | 29 | arch=`uname -m` 30 | 31 | echo 32 | echo 33 | echo "Copying source to ${SRC_DIR}..." 34 | cp -a "${WORKDIR_PATH}" "${SRC_DIR}" 35 | 36 | echo 37 | echo 38 | echo "Removing pre-existing ${SRC_DIR}/dist..." 39 | rm -rfv "${SRC_DIR}/dist" 40 | 41 | echo 42 | echo 43 | echo "Building ${package_name} dist has been requested" 44 | 45 | echo 46 | echo 47 | echo "Compile wheels" 48 | for PYTHON in ${PYTHON_VERSIONS}; do 49 | /opt/python/${PYTHON}/bin/python -m pip install -U pip 50 | /opt/python/${PYTHON}/bin/python -m pip install -r "${WORKDIR_PATH}/requirements/wheel.txt" 51 | /opt/python/${PYTHON}/bin/python -m pip wheel "${SRC_DIR}/" --no-deps -w "${ORIG_WHEEL_DIR}/${PYTHON}" -v 52 | done 53 | 54 | echo 55 | echo 56 | echo "Bundle external shared libraries into the wheels" 57 | for whl in ${ORIG_WHEEL_DIR}/*/${package_name}-*-linux_${arch}.whl; do 58 | echo "Repairing $whl..." 59 | auditwheel repair "$whl" -w "${WHEELHOUSE_DIR}" 60 | done 61 | 62 | echo 63 | echo 64 | echo "Cleanup OS specific wheels" 65 | rm -fv ${WHEELHOUSE_DIR}/*-linux_*.whl 66 | 67 | echo 68 | echo 69 | echo "Cleanup non-$package_name wheels" 70 | find "${WHEELHOUSE_DIR}" -maxdepth 1 -type f ! -name "$package_name"'-*-manylinux1_*.whl' -print0 | xargs -0 rm -rf 71 | 72 | echo 73 | echo 74 | echo "Install packages and test" 75 | echo "dist directory:" 76 | ls ${WHEELHOUSE_DIR} 77 | 78 | for PYTHON in ${PYTHON_VERSIONS}; do 79 | # clear python cache 80 | find "${WORKDIR_PATH}" -type d -name __pycache__ -print0 | xargs -0 rm -rf 81 | 82 | echo 83 | echo -n "Test $PYTHON: " 84 | /opt/python/${PYTHON}/bin/python -c "import platform; print('Building wheel for {platform} platform.'.format(platform=platform.platform()))" 85 | /opt/python/${PYTHON}/bin/pip install -r ${WORKDIR_PATH}/requirements/ci-wheel.txt 86 | /opt/python/${PYTHON}/bin/pip install "$package_name" --no-index -f "file://${WHEELHOUSE_DIR}" 87 | /opt/python/${PYTHON}/bin/py.test ${WORKDIR_PATH}/tests 88 | done 89 | 90 | chown -R --reference="${WORKDIR_PATH}/.travis.yml" "${WORKDIR_PATH}" 91 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at andrew.svetlov@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | aiosignal 2 | ========= 3 | 4 | A project to manage callbacks in `asyncio` projects. 5 | 6 | ``Signal`` is a list of registered asynchronous callbacks. 7 | 8 | The signal's life-cycle has two stages: after creation its content 9 | could be filled by using standard list operations: ``sig.append()`` 10 | etc. 11 | 12 | After you call ``sig.freeze()`` the signal is *frozen*: adding, removing 13 | and dropping callbacks is forbidden. 14 | 15 | The only available operation is calling the previously registered 16 | callbacks by using ``await sig.send(data)``. 17 | 18 | The callback parameters, which should be passed in the ``.send()`` call, can be 19 | specified for a type checker:: 20 | 21 | signal = Signal[int, str](owner) 22 | signal.send(42, "foo") 23 | 24 | 25 | For concrete usage examples see the :ref:`aiohttp:aiohttp-web-signals` section of the :doc:`aiohttp:web_advanced` chapter of the :doc:`aiohttp documentation `. 26 | 27 | API 28 | --- 29 | 30 | .. class:: aiosignal.Signal(owner) 31 | 32 | The signal, implements the :class:`collections.abc.MutableSequence` 33 | interface. The *owner* object is shown in the signal representation, 34 | and is there to make debugging easier. 35 | 36 | .. method:: send(*args, **kwargs) 37 | :async: 38 | 39 | Call all registered callbacks one by one starting from the beginning 40 | of the list. 41 | 42 | .. attribute:: frozen 43 | 44 | ``True`` if :meth:`freeze` was called, read-only property. 45 | 46 | .. method:: freeze() 47 | 48 | Freeze the list. After calling, any content modification is forbidden. 49 | 50 | Installation 51 | ------------ 52 | 53 | .. code-block:: bash 54 | 55 | $ pip install aiosignal 56 | 57 | The library requires Python 3.8 or newer. 58 | 59 | Dependencies 60 | ------------ 61 | 62 | aiosignal depends on the frozenlist_ library. 63 | 64 | Documentation 65 | ============= 66 | 67 | https://aiosignal.readthedocs.io/ 68 | 69 | Communication channels 70 | ====================== 71 | 72 | *aio-libs discourse group*: https://aio-libs.discourse.group 73 | 74 | Feel free to post your questions and ideas here. 75 | 76 | *gitter chat* https://gitter.im/aio-libs/Lobby 77 | 78 | Requirements 79 | ============ 80 | 81 | - Python >= 3.8 82 | - frozenlist >= 1.0.0 83 | 84 | License 85 | ======= 86 | 87 | ``aiosignal`` is offered under the Apache 2 license. 88 | 89 | Source code 90 | =========== 91 | 92 | The project is hosted on GitHub_ 93 | 94 | Please file an issue in the `bug tracker 95 | `_ if you have found a bug 96 | or have some suggestions to improve the library. 97 | 98 | Authors and License 99 | =================== 100 | 101 | The ``aiosignal`` package was originally part of the 102 | :doc:`aiohttp project `, written by Nikolay Kim and Andrew Svetlov. 103 | It is now being maintained by Martijn Pieters. 104 | 105 | It's *Apache 2* licensed and freely available. 106 | 107 | Feel free to improve this package and send a pull request to GitHub_. 108 | 109 | 110 | .. toctree:: 111 | :maxdepth: 2 112 | 113 | Indices and tables 114 | ================== 115 | 116 | * :ref:`genindex` 117 | * :ref:`search` 118 | 119 | 120 | .. _GitHub: https://github.com/aio-libs/aiosignal 121 | .. _frozenlist: https://github.com/aio-libs/frozenlist 122 | -------------------------------------------------------------------------------- /CHANGES.rst: -------------------------------------------------------------------------------- 1 | ========= 2 | Changelog 3 | ========= 4 | 5 | .. 6 | You should *NOT* be adding new change log entries to this file, this 7 | file is managed by towncrier. You *may* edit previous change logs to 8 | fix problems like typo corrections or such. 9 | To add a new change log entry, please see 10 | https://pip.pypa.io/en/latest/development/contributing/#news-entries 11 | we named the news folder "changes". 12 | 13 | WARNING: Don't drop the next directive! 14 | 15 | .. towncrier release notes start 16 | 17 | 1.4.0 (2025-07-03) 18 | ================== 19 | 20 | Features 21 | -------- 22 | 23 | - Added decorator functionality to ``Signal`` as a convenient way to add a callback -- by ``@Vizonex``. 24 | `#699 `_ 25 | 26 | - Improved type safety by allowing callback parameters to be type checked (typing-extensions is now required for Python <3.13). 27 | Parameters for a ``Signal`` callback should now be defined like ``Signal[int, str]`` -- by @Vizonex and @Dreamsorcerer. 28 | `#699 `_, `#710 `_ 29 | 30 | 31 | Misc 32 | ---- 33 | 34 | - Removed the sphinxcontrib-asyncio documentation dependency. 35 | `#528 `_ 36 | 37 | 38 | ---- 39 | 40 | 1.3.2 (2024-12-13) 41 | ================== 42 | 43 | Deprecations and Removals 44 | ------------------------- 45 | 46 | - Dropped Python 3.7 support. 47 | `#413 `_ 48 | 49 | - Dropped Python 3.8 support. 50 | `#645 `_ 51 | 52 | 53 | Misc 54 | ---- 55 | 56 | - `#362 `_ 57 | 58 | 59 | ---- 60 | 61 | 1.3.1 (2022-11-08) 62 | ================== 63 | 64 | Bugfixes 65 | -------- 66 | 67 | - Removed stray quote and comma from setup.cfg preventing PyPI from accepting a 68 | release. 69 | `#361 `_ 70 | 71 | 72 | ---- 73 | 74 | 75 | 1.3.0 (2022-11-08) 76 | ================== 77 | 78 | Features 79 | -------- 80 | 81 | - Switched to declarative package setup. 82 | `#267 `_ 83 | - Added support for Python 3.11. 84 | `#360 `_ 85 | 86 | 87 | Deprecations and Removals 88 | ------------------------- 89 | 90 | - Dropped Python 3.6 support. 91 | `#267 `_ 92 | 93 | 94 | ---- 95 | 96 | 97 | 1.2.0 (2021-10-16) 98 | ================== 99 | 100 | Features 101 | -------- 102 | 103 | - Added support for Python 3.10. 104 | `#328 `_ 105 | 106 | 107 | Bugfixes 108 | -------- 109 | 110 | - Mark aiosignal as Python3-only package 111 | `#165 `_ 112 | 113 | 114 | ---- 115 | 116 | 117 | 1.1.2 (2020-11-27) 118 | ================== 119 | 120 | Features 121 | -------- 122 | 123 | - Fix MANIFEST.in to include ``aiosignal/py.typed`` marker 124 | 125 | 126 | 1.1.1 (2020-11-27) 127 | ================== 128 | 129 | Features 130 | -------- 131 | 132 | - Support type hints 133 | 134 | 1.1.0 (2020-10-13) 135 | ================== 136 | 137 | Features 138 | -------- 139 | 140 | - Added support of Python 3.8 and 3.9 141 | 142 | 143 | 1.0.0 (2019-11-11) 144 | ================== 145 | 146 | Deprecations and Removals 147 | ------------------------- 148 | 149 | - Dropped support for Python 3.5; only 3.6, 3.7 and 3.8 are supported going forward. 150 | `#23 `_ 151 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: local 3 | hooks: 4 | - id: check-changes 5 | name: Check CHANGES 6 | language: system 7 | entry: ./tools/check_changes.py 8 | pass_filenames: false 9 | - repo: https://github.com/pre-commit/pre-commit-hooks 10 | rev: v5.0.0 11 | hooks: 12 | - id: check-merge-conflict 13 | exclude: rst$ 14 | - repo: https://github.com/asottile/yesqa 15 | rev: v1.5.0 16 | hooks: 17 | - id: yesqa 18 | - repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks 19 | rev: v2.15.0 20 | hooks: 21 | - id: pretty-format-yaml 22 | alias: yaml 23 | args: [--autofix, --indent, '2'] 24 | - repo: https://github.com/pre-commit/pre-commit-hooks 25 | rev: v5.0.0 26 | hooks: 27 | - id: trailing-whitespace 28 | - id: end-of-file-fixer 29 | - id: fix-encoding-pragma 30 | args: [--remove] 31 | - id: file-contents-sorter 32 | files: CONTRIBUTORS.txt 33 | - id: check-case-conflict 34 | - id: check-json 35 | - id: check-xml 36 | - id: check-yaml 37 | - id: debug-statements 38 | - repo: https://github.com/PyCQA/flake8 39 | rev: 7.3.0 40 | hooks: 41 | - id: flake8 42 | language_version: python3 43 | - repo: https://github.com/pre-commit/pygrep-hooks 44 | rev: v1.10.0 45 | hooks: 46 | - id: python-use-type-annotations 47 | - repo: https://github.com/rhysd/actionlint 48 | rev: v1.7.7 49 | hooks: 50 | - id: actionlint-docker 51 | args: 52 | - -ignore 53 | - 'SC2155:' 54 | - -ignore 55 | - 'SC2086:' 56 | - -ignore 57 | - 'SC1004:' 58 | - repo: https://github.com/python-jsonschema/check-jsonschema 59 | rev: 0.33.1 60 | hooks: 61 | - id: check-github-actions 62 | 63 | - repo: https://github.com/pre-commit/mirrors-mypy.git 64 | rev: v1.16.1 65 | hooks: 66 | - id: mypy 67 | alias: mypy-py313 68 | name: MyPy, for Python 3.13 69 | additional_dependencies: 70 | - frozenlist # runtime dependency 71 | - lxml # dep of `--txt-report`, `--cobertura-xml-report` & `--html-report` 72 | - pytest 73 | args: 74 | - --python-version=3.13 75 | - --any-exprs-report=.tox/.tmp/.test-results/mypy--py-3.13 76 | - --cobertura-xml-report=.tox/.tmp/.test-results/mypy--py-3.13 77 | - --html-report=.tox/.tmp/.test-results/mypy--py-3.13 78 | - --linecount-report=.tox/.tmp/.test-results/mypy--py-3.13 79 | - --linecoverage-report=.tox/.tmp/.test-results/mypy--py-3.13 80 | - --lineprecision-report=.tox/.tmp/.test-results/mypy--py-3.13 81 | - --txt-report=.tox/.tmp/.test-results/mypy--py-3.13 82 | pass_filenames: false 83 | - id: mypy 84 | alias: mypy-py311 85 | name: MyPy, for Python 3.11 86 | additional_dependencies: 87 | - frozenlist # runtime dependency 88 | - lxml # dep of `--txt-report`, `--cobertura-xml-report` & `--html-report` 89 | - pytest 90 | args: 91 | - --python-version=3.11 92 | - --any-exprs-report=.tox/.tmp/.test-results/mypy--py-3.11 93 | - --cobertura-xml-report=.tox/.tmp/.test-results/mypy--py-3.11 94 | - --html-report=.tox/.tmp/.test-results/mypy--py-3.11 95 | - --linecount-report=.tox/.tmp/.test-results/mypy--py-3.11 96 | - --linecoverage-report=.tox/.tmp/.test-results/mypy--py-3.11 97 | - --lineprecision-report=.tox/.tmp/.test-results/mypy--py-3.11 98 | - --txt-report=.tox/.tmp/.test-results/mypy--py-3.11 99 | pass_filenames: false 100 | - id: mypy 101 | alias: mypy-py39 102 | name: MyPy, for Python 3.9 103 | additional_dependencies: 104 | - frozenlist # runtime dependency 105 | - lxml # dep of `--txt-report`, `--cobertura-xml-report` & `--html-report` 106 | - pytest 107 | args: 108 | - --python-version=3.9 109 | - --any-exprs-report=.tox/.tmp/.test-results/mypy--py-3.9 110 | - --cobertura-xml-report=.tox/.tmp/.test-results/mypy--py-3.9 111 | - --html-report=.tox/.tmp/.test-results/mypy--py-3.9 112 | - --linecount-report=.tox/.tmp/.test-results/mypy--py-3.9 113 | - --linecoverage-report=.tox/.tmp/.test-results/mypy--py-3.9 114 | - --lineprecision-report=.tox/.tmp/.test-results/mypy--py-3.9 115 | - --txt-report=.tox/.tmp/.mypy/python-3.9 116 | pass_filenames: false 117 | ci: 118 | skip: 119 | - actionlint-docker 120 | - check-github-actions 121 | -------------------------------------------------------------------------------- /docs/spelling_wordlist.txt: -------------------------------------------------------------------------------- 1 | abc 2 | aiodns 3 | aioes 4 | aiohttp 5 | aiohttpdemo 6 | aiohttp’s 7 | aiopg 8 | alives 9 | api 10 | api’s 11 | app 12 | apps 13 | app’s 14 | arg 15 | Arsenic 16 | async 17 | asyncio 18 | auth 19 | autocalculated 20 | autodetection 21 | autogenerates 22 | autogeneration 23 | awaitable 24 | backend 25 | backends 26 | Backport 27 | Backporting 28 | backport 29 | backports 30 | BaseEventLoop 31 | basename 32 | BasicAuth 33 | BodyPartReader 34 | boolean 35 | botocore 36 | Bugfixes 37 | builtin 38 | bugfix 39 | BytesIO 40 | cchardet 41 | cChardet 42 | Changelog 43 | charset 44 | charsetdetect 45 | chunked 46 | chunking 47 | CIMultiDict 48 | ClientSession 49 | cls 50 | cmd 51 | codec 52 | Codings 53 | committer 54 | committers 55 | config 56 | Config 57 | configs 58 | conjunction 59 | contextmanager 60 | CookieJar 61 | coroutine 62 | Coroutine 63 | coroutines 64 | cpu 65 | CPython 66 | css 67 | ctor 68 | Ctrl 69 | de 70 | deduplicate 71 | # de-facto: 72 | deprecations 73 | DER 74 | Dev 75 | dict 76 | Dict 77 | Discord 78 | django 79 | Django 80 | dns 81 | DNSResolver 82 | docstring 83 | Dup 84 | elasticsearch 85 | encodings 86 | env 87 | environ 88 | eof 89 | epoll 90 | Facebook 91 | facto 92 | fallback 93 | fallbacks 94 | filename 95 | finalizers 96 | frontend 97 | getall 98 | gethostbyname 99 | github 100 | google 101 | gunicorn 102 | gunicorn’s 103 | gzipped 104 | hackish 105 | highlevel 106 | hostnames 107 | HTTPException 108 | HttpProcessingError 109 | httpretty 110 | https 111 | impl 112 | incapsulates 113 | Indices 114 | infos 115 | initializer 116 | inline 117 | intaking 118 | io 119 | ip 120 | IP 121 | ipdb 122 | IPv 123 | ish 124 | iterable 125 | iterables 126 | javascript 127 | Jinja 128 | json 129 | keepalive 130 | keepalived 131 | keepalives 132 | keepaliving 133 | kwarg 134 | latin 135 | linux 136 | localhost 137 | Locator 138 | login 139 | lookup 140 | lookups 141 | lossless 142 | Mako 143 | Martijn 144 | manylinux 145 | metadata 146 | microservice 147 | middleware 148 | middlewares 149 | miltidict 150 | misbehaviors 151 | misformed 152 | Mixcloud 153 | Mongo 154 | msg 155 | MsgType 156 | multi 157 | multidict 158 | multidicts 159 | multidict’s 160 | Multidicts 161 | multipart 162 | Multipart 163 | Nagle 164 | Nagle’s 165 | namedtuple 166 | nameservers 167 | namespace 168 | nginx 169 | Nginx 170 | Nikolay 171 | noop 172 | nowait 173 | OAuth 174 | Online 175 | optimizations 176 | os 177 | outcoming 178 | Overridable 179 | Paolini 180 | param 181 | params 182 | pathlib 183 | peername 184 | Pieters 185 | ping 186 | pipelining 187 | pluggable 188 | plugin 189 | poller 190 | pong 191 | Postgres 192 | pre 193 | programmatically 194 | proxied 195 | PRs 196 | pubsub 197 | Punycode 198 | py 199 | pyenv 200 | pyflakes 201 | pytest 202 | Pytest 203 | Quickstart 204 | quote’s 205 | readonly 206 | readpayload 207 | rebase 208 | redirections 209 | Redis 210 | refactor 211 | Refactor 212 | refactored 213 | refactoring 214 | regex 215 | regexps 216 | regexs 217 | reloader 218 | renderer 219 | renderers 220 | repo 221 | repr 222 | repr’s 223 | RequestContextManager 224 | request’s 225 | Request’s 226 | requote 227 | requoting 228 | resolvehost 229 | resolvers 230 | reusage 231 | reuseconn 232 | Runit 233 | sa 234 | Satisfiable 235 | schemas 236 | sendfile 237 | serializable 238 | shourtcuts 239 | skipuntil 240 | Skyscanner 241 | SocketSocketTransport 242 | ssl 243 | SSLContext 244 | startup 245 | subapplication 246 | subclasses 247 | submodules 248 | subpackage 249 | subprotocol 250 | subprotocols 251 | subtype 252 | supervisord 253 | Supervisord 254 | Svetlov 255 | symlink 256 | symlinks 257 | syscall 258 | syscalls 259 | Systemd 260 | tarball 261 | TCP 262 | TLS 263 | teardown 264 | Teardown 265 | TestClient 266 | Testsuite 267 | Tf 268 | timestamps 269 | toolbar 270 | toplevel 271 | towncrier 272 | tp 273 | tuples 274 | UI 275 | un 276 | unawaited 277 | unclosed 278 | unicode 279 | unittest 280 | Unittest 281 | unix 282 | unsets 283 | unstripped 284 | upstr 285 | url 286 | urldispatcher 287 | urlencoded 288 | urls 289 | url’s 290 | utf 291 | utils 292 | uvloop 293 | vcvarsall 294 | waituntil 295 | webapp 296 | websocket 297 | websockets 298 | websocket’s 299 | Websockets 300 | wildcard 301 | Workflow 302 | ws 303 | wsgi 304 | WSMessage 305 | WSMsgType 306 | wss 307 | www 308 | xxx 309 | yarl 310 | -------------------------------------------------------------------------------- /tests/test_signals.py: -------------------------------------------------------------------------------- 1 | import re 2 | import sys 3 | from unittest import mock 4 | 5 | import pytest 6 | 7 | from aiosignal import Signal 8 | 9 | if sys.version_info >= (3, 11): 10 | from typing import Unpack 11 | else: 12 | from typing_extensions import Unpack 13 | 14 | 15 | class Owner: 16 | def __repr__(self) -> str: 17 | return "" 18 | 19 | 20 | @pytest.fixture 21 | def owner() -> Owner: 22 | return Owner() 23 | 24 | 25 | async def test_signal_positional_args(owner: Owner) -> None: 26 | async def callback(a: int, b: str) -> None: 27 | return 28 | 29 | signal = Signal[int, str](owner) 30 | signal.append(callback) 31 | signal.freeze() 32 | await signal.send(42, "foo") 33 | 34 | 35 | async def test_add_signal_handler_not_a_callable(owner: Owner) -> None: 36 | callback = True 37 | signal = Signal(owner) 38 | signal.append(callback) # type: ignore[arg-type] 39 | signal.freeze() 40 | with pytest.raises(TypeError): 41 | await signal.send() 42 | 43 | 44 | async def test_function_signal_dispatch_kwargs(owner: Owner) -> None: 45 | signal = Signal(owner) 46 | kwargs = {"foo": 1, "bar": 2} 47 | 48 | callback_mock = mock.Mock() 49 | 50 | async def callback(**kwargs: object) -> None: 51 | callback_mock(**kwargs) 52 | 53 | signal.append(callback) 54 | signal.freeze() 55 | 56 | await signal.send(**kwargs) 57 | callback_mock.assert_called_once_with(**kwargs) 58 | 59 | 60 | async def test_function_signal_dispatch_args_kwargs(owner: Owner) -> None: 61 | signal = Signal[Unpack[tuple[str, ...]]](owner) 62 | kwargs = {"foo": 1, "bar": 2} 63 | 64 | callback_mock = mock.Mock() 65 | 66 | async def callback(*args: str, **kwargs: object) -> None: 67 | callback_mock(*args, **kwargs) 68 | 69 | signal.append(callback) 70 | signal.freeze() 71 | 72 | await signal.send("a", "b", **kwargs) 73 | callback_mock.assert_called_once_with("a", "b", **kwargs) 74 | 75 | 76 | async def test_non_coroutine(owner: Owner) -> None: 77 | signal = Signal(owner) 78 | kwargs = {"foo": 1, "bar": 2} 79 | 80 | callback = mock.Mock() 81 | 82 | signal.append(callback) 83 | signal.freeze() 84 | 85 | with pytest.raises(TypeError): 86 | await signal.send(**kwargs) 87 | 88 | 89 | def test_setitem(owner: Owner) -> None: 90 | signal = Signal(owner) 91 | m1 = mock.Mock() 92 | signal.append(m1) 93 | assert signal[0] is m1 94 | m2 = mock.Mock() 95 | signal[0] = m2 96 | assert signal[0] is m2 97 | 98 | 99 | def test_delitem(owner: Owner) -> None: 100 | signal = Signal(owner) 101 | m1 = mock.Mock() 102 | signal.append(m1) 103 | assert len(signal) == 1 104 | del signal[0] 105 | assert len(signal) == 0 106 | 107 | 108 | def test_cannot_append_to_frozen_signal(owner: Owner) -> None: 109 | signal = Signal(owner) 110 | m1 = mock.Mock() 111 | m2 = mock.Mock() 112 | signal.append(m1) 113 | signal.freeze() 114 | with pytest.raises(RuntimeError): 115 | signal.append(m2) 116 | 117 | assert list(signal) == [m1] 118 | 119 | 120 | def test_cannot_setitem_in_frozen_signal(owner: Owner) -> None: 121 | signal = Signal(owner) 122 | m1 = mock.Mock() 123 | m2 = mock.Mock() 124 | signal.append(m1) 125 | signal.freeze() 126 | with pytest.raises(RuntimeError): 127 | signal[0] = m2 128 | 129 | assert list(signal) == [m1] 130 | 131 | 132 | def test_cannot_delitem_in_frozen_signal(owner: Owner) -> None: 133 | signal = Signal(owner) 134 | m1 = mock.Mock() 135 | signal.append(m1) 136 | signal.freeze() 137 | with pytest.raises(RuntimeError): 138 | del signal[0] 139 | 140 | assert list(signal) == [m1] 141 | 142 | 143 | async def test_cannot_send_non_frozen_signal(owner: Owner) -> None: 144 | signal = Signal(owner) 145 | 146 | callback_mock = mock.Mock() 147 | 148 | async def callback(**kwargs: object) -> None: 149 | callback_mock(**kwargs) # pragma: no cover # mustn't be called 150 | 151 | signal.append(callback) 152 | 153 | with pytest.raises(RuntimeError): 154 | await signal.send() 155 | 156 | assert not callback_mock.called 157 | 158 | 159 | def test_repr(owner: Owner) -> None: 160 | signal = Signal(owner) 161 | 162 | signal.append(mock.Mock(__repr__=lambda *a: "")) 163 | 164 | assert ( 165 | re.match( 166 | r", frozen=False, " r"\[\]>", 167 | repr(signal), 168 | ) 169 | is not None 170 | ) 171 | 172 | async def test_decorator_callback_dispatch_args_kwargs(owner: Owner) -> None: 173 | signal = Signal(owner) 174 | args = {"a", "b"} 175 | kwargs = {"foo": 1, "bar": 2} 176 | 177 | callback_mock = mock.Mock() 178 | 179 | @signal 180 | async def callback(*args: object, **kwargs: object) -> None: 181 | callback_mock(*args, **kwargs) 182 | 183 | signal.freeze() 184 | await signal.send(*args, **kwargs) 185 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " devhelp to make HTML files and a Devhelp project" 34 | @echo " epub to make an epub" 35 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 36 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 37 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 38 | @echo " text to make text files" 39 | @echo " man to make manual pages" 40 | @echo " texinfo to make Texinfo files" 41 | @echo " info to make Texinfo files and run them through makeinfo" 42 | @echo " gettext to make PO message catalogs" 43 | @echo " changes to make an overview of all changed/added/deprecated items" 44 | @echo " xml to make Docutils-native XML files" 45 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 46 | @echo " linkcheck to check all external links for integrity" 47 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 48 | 49 | clean: 50 | rm -rf $(BUILDDIR)/* 51 | 52 | html: 53 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 54 | @echo 55 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 56 | 57 | dirhtml: 58 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 59 | @echo 60 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 61 | 62 | singlehtml: 63 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 64 | @echo 65 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 66 | 67 | pickle: 68 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 69 | @echo 70 | @echo "Build finished; now you can process the pickle files." 71 | 72 | json: 73 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 74 | @echo 75 | @echo "Build finished; now you can process the JSON files." 76 | 77 | htmlhelp: 78 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 79 | @echo 80 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 81 | ".hhp project file in $(BUILDDIR)/htmlhelp." 82 | 83 | qthelp: 84 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 85 | @echo 86 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 87 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 88 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/aiosignal.qhcp" 89 | @echo "To view the help file:" 90 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/aiosignal.qhc" 91 | 92 | devhelp: 93 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 94 | @echo 95 | @echo "Build finished." 96 | @echo "To view the help file:" 97 | @echo "# mkdir -p $$HOME/.local/share/devhelp/aiosignal" 98 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/aiosignal" 99 | @echo "# devhelp" 100 | 101 | epub: 102 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 103 | @echo 104 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 105 | 106 | latex: 107 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 108 | @echo 109 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 110 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 111 | "(use \`make latexpdf' here to do that automatically)." 112 | 113 | latexpdf: 114 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 115 | @echo "Running LaTeX files through pdflatex..." 116 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 117 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 118 | 119 | latexpdfja: 120 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 121 | @echo "Running LaTeX files through platex and dvipdfmx..." 122 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 123 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 124 | 125 | text: 126 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 127 | @echo 128 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 129 | 130 | man: 131 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 132 | @echo 133 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 134 | 135 | texinfo: 136 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 137 | @echo 138 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 139 | @echo "Run \`make' in that directory to run these through makeinfo" \ 140 | "(use \`make info' here to do that automatically)." 141 | 142 | info: 143 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 144 | @echo "Running Texinfo files through makeinfo..." 145 | make -C $(BUILDDIR)/texinfo info 146 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 147 | 148 | gettext: 149 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 150 | @echo 151 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 152 | 153 | changes: 154 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 155 | @echo 156 | @echo "The overview file is in $(BUILDDIR)/changes." 157 | 158 | linkcheck: 159 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 160 | @echo 161 | @echo "Link check complete; look for any errors in the above output " \ 162 | "or in $(BUILDDIR)/linkcheck/output.txt." 163 | 164 | doctest: 165 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 166 | @echo "Testing of doctests in the sources finished, look at the " \ 167 | "results in $(BUILDDIR)/doctest/output.txt." 168 | 169 | xml: 170 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 171 | @echo 172 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 173 | 174 | pseudoxml: 175 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 176 | @echo 177 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 178 | 179 | spelling: 180 | $(SPHINXBUILD) -b spelling $(ALLSPHINXOPTS) $(BUILDDIR)/spelling 181 | @echo 182 | @echo "Build finished." 183 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | set I18NSPHINXOPTS=%SPHINXOPTS% . 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. xml to make Docutils-native XML files 37 | echo. pseudoxml to make pseudoxml-XML files for display purposes 38 | echo. linkcheck to check all external links for integrity 39 | echo. doctest to run all doctests embedded in the documentation if enabled 40 | goto end 41 | ) 42 | 43 | if "%1" == "clean" ( 44 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 45 | del /q /s %BUILDDIR%\* 46 | goto end 47 | ) 48 | 49 | 50 | %SPHINXBUILD% 2> nul 51 | if errorlevel 9009 ( 52 | echo. 53 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 54 | echo.installed, then set the SPHINXBUILD environment variable to point 55 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 56 | echo.may add the Sphinx directory to PATH. 57 | echo. 58 | echo.If you don't have Sphinx installed, grab it from 59 | echo.http://sphinx-doc.org/ 60 | exit /b 1 61 | ) 62 | 63 | if "%1" == "html" ( 64 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 65 | if errorlevel 1 exit /b 1 66 | echo. 67 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 68 | goto end 69 | ) 70 | 71 | if "%1" == "dirhtml" ( 72 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 73 | if errorlevel 1 exit /b 1 74 | echo. 75 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 76 | goto end 77 | ) 78 | 79 | if "%1" == "singlehtml" ( 80 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 81 | if errorlevel 1 exit /b 1 82 | echo. 83 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 84 | goto end 85 | ) 86 | 87 | if "%1" == "pickle" ( 88 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 89 | if errorlevel 1 exit /b 1 90 | echo. 91 | echo.Build finished; now you can process the pickle files. 92 | goto end 93 | ) 94 | 95 | if "%1" == "json" ( 96 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 97 | if errorlevel 1 exit /b 1 98 | echo. 99 | echo.Build finished; now you can process the JSON files. 100 | goto end 101 | ) 102 | 103 | if "%1" == "htmlhelp" ( 104 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 105 | if errorlevel 1 exit /b 1 106 | echo. 107 | echo.Build finished; now you can run HTML Help Workshop with the ^ 108 | .hhp project file in %BUILDDIR%/htmlhelp. 109 | goto end 110 | ) 111 | 112 | if "%1" == "qthelp" ( 113 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 114 | if errorlevel 1 exit /b 1 115 | echo. 116 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 117 | .qhcp project file in %BUILDDIR%/qthelp, like this: 118 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\aiosignal.qhcp 119 | echo.To view the help file: 120 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\aiosignal.ghc 121 | goto end 122 | ) 123 | 124 | if "%1" == "devhelp" ( 125 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished. 129 | goto end 130 | ) 131 | 132 | if "%1" == "epub" ( 133 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 134 | if errorlevel 1 exit /b 1 135 | echo. 136 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 137 | goto end 138 | ) 139 | 140 | if "%1" == "latex" ( 141 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 142 | if errorlevel 1 exit /b 1 143 | echo. 144 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 145 | goto end 146 | ) 147 | 148 | if "%1" == "latexpdf" ( 149 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 150 | cd %BUILDDIR%/latex 151 | make all-pdf 152 | cd %BUILDDIR%/.. 153 | echo. 154 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 155 | goto end 156 | ) 157 | 158 | if "%1" == "latexpdfja" ( 159 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 160 | cd %BUILDDIR%/latex 161 | make all-pdf-ja 162 | cd %BUILDDIR%/.. 163 | echo. 164 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 165 | goto end 166 | ) 167 | 168 | if "%1" == "text" ( 169 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 170 | if errorlevel 1 exit /b 1 171 | echo. 172 | echo.Build finished. The text files are in %BUILDDIR%/text. 173 | goto end 174 | ) 175 | 176 | if "%1" == "man" ( 177 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 178 | if errorlevel 1 exit /b 1 179 | echo. 180 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 181 | goto end 182 | ) 183 | 184 | if "%1" == "texinfo" ( 185 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 186 | if errorlevel 1 exit /b 1 187 | echo. 188 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 189 | goto end 190 | ) 191 | 192 | if "%1" == "gettext" ( 193 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 194 | if errorlevel 1 exit /b 1 195 | echo. 196 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 197 | goto end 198 | ) 199 | 200 | if "%1" == "changes" ( 201 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 202 | if errorlevel 1 exit /b 1 203 | echo. 204 | echo.The overview file is in %BUILDDIR%/changes. 205 | goto end 206 | ) 207 | 208 | if "%1" == "linkcheck" ( 209 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 210 | if errorlevel 1 exit /b 1 211 | echo. 212 | echo.Link check complete; look for any errors in the above output ^ 213 | or in %BUILDDIR%/linkcheck/output.txt. 214 | goto end 215 | ) 216 | 217 | if "%1" == "doctest" ( 218 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 219 | if errorlevel 1 exit /b 1 220 | echo. 221 | echo.Testing of doctests in the sources finished, look at the ^ 222 | results in %BUILDDIR%/doctest/output.txt. 223 | goto end 224 | ) 225 | 226 | if "%1" == "xml" ( 227 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml 228 | if errorlevel 1 exit /b 1 229 | echo. 230 | echo.Build finished. The XML files are in %BUILDDIR%/xml. 231 | goto end 232 | ) 233 | 234 | if "%1" == "pseudoxml" ( 235 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml 236 | if errorlevel 1 exit /b 1 237 | echo. 238 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. 239 | goto end 240 | ) 241 | 242 | :end 243 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2013-2019 Nikolay Kim and Andrew Svetlov 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # aiosignal documentation build configuration file, created by 4 | # sphinx-quickstart on Wed Mar 5 12:35:35 2014. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | import os 16 | import re 17 | 18 | _docs_path = os.path.dirname(__file__) 19 | _version_path = os.path.abspath( 20 | os.path.join(_docs_path, "..", "aiosignal", "__init__.py") 21 | ) 22 | with open(_version_path, encoding="latin1") as fp: 23 | try: 24 | _version_info = re.search( 25 | r'^__version__ = "' 26 | r"(?P\d+)" 27 | r"\.(?P\d+)" 28 | r"\.(?P\d+)" 29 | r'(?P.*)?"$', 30 | fp.read(), 31 | re.M, 32 | ).groupdict() 33 | except IndexError: 34 | raise RuntimeError("Unable to determine version.") 35 | 36 | 37 | # -- General configuration ------------------------------------------------ 38 | 39 | # If your documentation needs a minimal Sphinx version, state it here. 40 | # needs_sphinx = '1.0' 41 | 42 | # Add any Sphinx extension module names here, as strings. They can be 43 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 44 | # ones. 45 | extensions = [ 46 | "sphinx.ext.viewcode", 47 | "sphinx.ext.intersphinx", 48 | ] 49 | 50 | 51 | try: 52 | import sphinxcontrib.spelling # noqa 53 | 54 | extensions.append("sphinxcontrib.spelling") 55 | except ImportError: 56 | pass 57 | 58 | 59 | intersphinx_mapping = { 60 | "python": ("http://docs.python.org/3", None), 61 | "aiohttp": ("https://docs.aiohttp.org/en/stable/", None), 62 | "frozenlist": ("https://frozenlist.readthedocs.io/en/latest/", None), 63 | } 64 | 65 | # Add any paths that contain templates here, relative to this directory. 66 | templates_path = ["_templates"] 67 | 68 | # The suffix of source filenames. 69 | source_suffix = ".rst" 70 | 71 | # The encoding of source files. 72 | # source_encoding = 'utf-8-sig' 73 | 74 | # The master toctree document. 75 | master_doc = "index" 76 | 77 | # General information about the project. 78 | project = "aiosignal" 79 | copyright = "2013-2019, aiosignal contributors" 80 | 81 | # The version info for the project you're documenting, acts as replacement for 82 | # |version| and |release|, also used in various other places throughout the 83 | # built documents. 84 | # 85 | # The short X.Y version. 86 | version = "{major}.{minor}".format(**_version_info) 87 | # The full version, including alpha/beta/rc tags. 88 | release = "{major}.{minor}.{patch}{tag}".format(**_version_info) 89 | 90 | # The language for content autogenerated by Sphinx. Refer to documentation 91 | # for a list of supported languages. 92 | # language = None 93 | 94 | # There are two options for replacing |today|: either, you set today to some 95 | # non-false value, then it is used: 96 | # today = '' 97 | # Else, today_fmt is used as the format for a strftime call. 98 | # today_fmt = '%B %d, %Y' 99 | 100 | # List of patterns, relative to source directory, that match files and 101 | # directories to ignore when looking for source files. 102 | exclude_patterns = ["_build"] 103 | 104 | # The reST default role (used for this markup: `text`) to use for all 105 | # documents. 106 | # default_role = None 107 | 108 | # If true, '()' will be appended to :func: etc. cross-reference text. 109 | # add_function_parentheses = True 110 | 111 | # If true, the current module name will be prepended to all description 112 | # unit titles (such as .. function::). 113 | # add_module_names = True 114 | 115 | # If true, sectionauthor and moduleauthor directives will be shown in the 116 | # output. They are ignored by default. 117 | # show_authors = False 118 | 119 | # The name of the Pygments (syntax highlighting) style to use. 120 | # pygments_style = 'sphinx' 121 | 122 | # The default language to highlight source code in. 123 | highlight_language = "python3" 124 | 125 | # A list of ignored prefixes for module index sorting. 126 | # modindex_common_prefix = [] 127 | 128 | # If true, keep warnings as "system message" paragraphs in the built documents. 129 | # keep_warnings = False 130 | 131 | 132 | # -- Options for HTML output ---------------------------------------------- 133 | 134 | # The theme to use for HTML and HTML Help pages. See the documentation for 135 | # a list of builtin themes. 136 | html_theme = "aiohttp_theme" 137 | 138 | # Theme options are theme-specific and customize the look and feel of a theme 139 | # further. For a list of options available for each theme, see the 140 | # documentation. 141 | html_theme_options = { 142 | "logo": None, 143 | "description": "aiosignal: a list of registered asynchronous callbacks.", 144 | "canonical_url": "http://aiosignal.readthedocs.io/en/stable/", 145 | "github_user": "aio-libs", 146 | "github_repo": "aiosignal", 147 | "github_button": True, 148 | "github_type": "star", 149 | "github_banner": True, 150 | "badges": [ 151 | { 152 | "image": "https://github.com/aio-libs/aiosignal/workflows/CI/badge.svg", 153 | "target": ( 154 | "https://github.com/aio-libs/aiosignal/" "actions?query=workflow%3ACI" 155 | ), 156 | "height": "20", 157 | "alt": "GitHub CI status for master branch", 158 | }, 159 | { 160 | "image": ( 161 | "https://codecov.io/github/aio-libs/aiosignal/" 162 | "coverage.svg?branch=master" 163 | ), 164 | "target": "https://codecov.io/github/aio-libs/aiosignal", 165 | "height": "20", 166 | "alt": "Code coverage status", 167 | }, 168 | { 169 | "image": "https://badge.fury.io/py/aiosignal.svg", 170 | "target": "https://badge.fury.io/py/aiosignal", 171 | "height": "20", 172 | "alt": "Latest PyPI package version", 173 | }, 174 | { 175 | "image": ( 176 | "https://img.shields.io/discourse/" 177 | "topics?server=https%3A%2F%2Faio-libs.discourse.group%2F" 178 | ), 179 | "target": "https://aio-libs.discourse.group/", 180 | "height": "20", 181 | "alt": "Discourse group for io-libs", 182 | }, 183 | { 184 | "image": "https://badges.gitter.im/Join%20Chat.svg", 185 | "target": "https://gitter.im/aio-libs/Lobby", 186 | "height": "20", 187 | "alt": "Chat on Gitter", 188 | }, 189 | ], 190 | } 191 | 192 | # Add any paths that contain custom themes here, relative to this directory. 193 | # html_theme_path = [alabaster.get_path()] 194 | 195 | # The name for this set of Sphinx documents. If None, it defaults to 196 | # " v documentation". 197 | # html_title = None 198 | 199 | # A shorter title for the navigation bar. Default is the same as html_title. 200 | # html_short_title = None 201 | 202 | # The name of an image file (relative to this directory) to place at the top 203 | # of the sidebar. 204 | # html_logo = 'aiosignal-icon.svg' 205 | 206 | # The name of an image file (within the static path) to use as favicon of the 207 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 208 | # pixels large. 209 | # html_favicon = 'favicon.ico' 210 | 211 | # Add any paths that contain custom static files (such as style sheets) here, 212 | # relative to this directory. They are copied after the builtin static files, 213 | # so a file named "default.css" will overwrite the builtin "default.css". 214 | # html_static_path = [] 215 | 216 | # Add any extra paths that contain custom files (such as robots.txt or 217 | # .htaccess) here, relative to this directory. These files are copied 218 | # directly to the root of the documentation. 219 | # html_extra_path = [] 220 | 221 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 222 | # using the given strftime format. 223 | # html_last_updated_fmt = '%b %d, %Y' 224 | 225 | # If true, SmartyPants will be used to convert quotes and dashes to 226 | # typographically correct entities. 227 | # html_use_smartypants = True 228 | 229 | # Custom sidebar templates, maps document names to template names. 230 | html_sidebars = { 231 | "**": [ 232 | "about.html", 233 | "navigation.html", 234 | "searchbox.html", 235 | ] 236 | } 237 | 238 | # Additional templates that should be rendered to pages, maps page names to 239 | # template names. 240 | # html_additional_pages = {} 241 | 242 | # If false, no module index is generated. 243 | # html_domain_indices = True 244 | 245 | # If false, no index is generated. 246 | # html_use_index = True 247 | 248 | # If true, the index is split into individual pages for each letter. 249 | # html_split_index = False 250 | 251 | # If true, links to the reST sources are added to the pages. 252 | # html_show_sourcelink = True 253 | 254 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 255 | # html_show_sphinx = True 256 | 257 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 258 | # html_show_copyright = True 259 | 260 | # If true, an OpenSearch description file will be output, and all pages will 261 | # contain a tag referring to it. The value of this option must be the 262 | # base URL from which the finished HTML is served. 263 | # html_use_opensearch = '' 264 | 265 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 266 | # html_file_suffix = None 267 | 268 | # Output file base name for HTML help builder. 269 | htmlhelp_basename = "aiosignaldoc" 270 | 271 | 272 | # -- Options for LaTeX output --------------------------------------------- 273 | 274 | latex_elements = { 275 | # The paper size ('letterpaper' or 'a4paper'). 276 | # 'papersize': 'letterpaper', 277 | # The font size ('10pt', '11pt' or '12pt'). 278 | # 'pointsize': '10pt', 279 | # Additional stuff for the LaTeX preamble. 280 | # 'preamble': '', 281 | } 282 | 283 | # Grouping the document tree into LaTeX files. List of tuples 284 | # (source start file, target name, title, 285 | # author, documentclass [howto, manual, or own class]). 286 | latex_documents = [ 287 | ( 288 | "index", 289 | "aiosignal.tex", 290 | "aiosignal Documentation", 291 | "aiosignal contributors", 292 | "manual", 293 | ), 294 | ] 295 | 296 | # The name of an image file (relative to this directory) to place at the top of 297 | # the title page. 298 | # latex_logo = None 299 | 300 | # For "manual" documents, if this is true, then toplevel headings are parts, 301 | # not chapters. 302 | # latex_use_parts = False 303 | 304 | # If true, show page references after internal links. 305 | # latex_show_pagerefs = False 306 | 307 | # If true, show URL addresses after external links. 308 | # latex_show_urls = False 309 | 310 | # Documents to append as an appendix to all manuals. 311 | # latex_appendices = [] 312 | 313 | # If false, no module index is generated. 314 | # latex_domain_indices = True 315 | 316 | 317 | # -- Options for manual page output --------------------------------------- 318 | 319 | # One entry per manual page. List of tuples 320 | # (source start file, name, description, authors, manual section). 321 | man_pages = [("index", "aiosignal", "aiosignal Documentation", ["aiosignal"], 1)] 322 | 323 | # If true, show URL addresses after external links. 324 | # man_show_urls = False 325 | 326 | 327 | # -- Options for Texinfo output ------------------------------------------- 328 | 329 | # Grouping the document tree into Texinfo files. List of tuples 330 | # (source start file, target name, title, author, 331 | # dir menu entry, description, category) 332 | texinfo_documents = [ 333 | ( 334 | "index", 335 | "aiosignal", 336 | "aiosignal Documentation", 337 | "aiosignal contributors", 338 | "aiosignal", 339 | "One line description of project.", 340 | "Miscellaneous", 341 | ), 342 | ] 343 | 344 | # Documents to append as an appendix to all manuals. 345 | # texinfo_appendices = [] 346 | 347 | # If false, no module index is generated. 348 | # texinfo_domain_indices = True 349 | 350 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 351 | # texinfo_show_urls = 'footnote' 352 | 353 | # If true, do not generate a @detailmenu in the "Top" node's menu. 354 | # texinfo_no_detailmenu = False 355 | -------------------------------------------------------------------------------- /.github/workflows/ci-cd.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - '[0-9].[0-9]+' # matches to backport branches, e.g. 3.6 8 | tags: [v*] 9 | pull_request: 10 | 11 | concurrency: 12 | group: >- 13 | ${{ 14 | github.workflow 15 | }}-${{ 16 | github.ref_type 17 | }}-${{ 18 | github.event.pull_request.number || github.sha 19 | }} 20 | cancel-in-progress: true 21 | 22 | env: 23 | COLOR: >- # Supposedly, pytest or coveragepy use this 24 | yes 25 | FORCE_COLOR: 1 # Request colored output from CLI tools supporting it 26 | MYPY_FORCE_COLOR: 1 # MyPy's color enforcement 27 | PIP_DISABLE_PIP_VERSION_CHECK: 1 # Hide "there's a newer pip" message 28 | PIP_NO_PYTHON_VERSION_WARNING: 1 # Hide "this Python is deprecated" message 29 | PIP_NO_WARN_SCRIPT_LOCATION: 1 # Hide "script dir is not in $PATH" message 30 | PRE_COMMIT_COLOR: always 31 | PROJECT_NAME: aiosignal 32 | PY_COLORS: 1 # Recognized by the `py` package, dependency of `pytest` 33 | PYTHONIOENCODING: utf-8 34 | PYTHONUTF8: 1 35 | TOX_PARALLEL_NO_SPINNER: 1 # Disable tox's parallel run spinner animation 36 | TOX_TESTENV_PASSENV: >- # Make tox-wrapped tools see color requests 37 | FORCE_COLOR 38 | MYPY_FORCE_COLOR 39 | NO_COLOR 40 | PIP_DISABLE_PIP_VERSION_CHECK 41 | PIP_NO_PYTHON_VERSION_WARNING 42 | PIP_NO_WARN_SCRIPT_LOCATION 43 | PRE_COMMIT_COLOR 44 | PY_COLORS 45 | PYTEST_THEME 46 | PYTEST_THEME_MODE 47 | PYTHONIOENCODING 48 | PYTHONLEGACYWINDOWSSTDIO 49 | PYTHONUTF8 50 | UPSTREAM_REPOSITORY_ID: >- 51 | 205007356 52 | 53 | 54 | jobs: 55 | pre-setup: 56 | name: ⚙️ Pre-set global build settings 57 | 58 | runs-on: ubuntu-latest 59 | 60 | timeout-minutes: 2 # network is slow sometimes when fetching from Git 61 | 62 | defaults: 63 | run: 64 | shell: python 65 | 66 | outputs: 67 | # NOTE: These aren't env vars because the `${{ env }}` context is 68 | # NOTE: inaccessible when passing inputs to reusable workflows. 69 | dists-artifact-name: python-package-distributions 70 | release-requested: >- 71 | ${{ 72 | ( 73 | github.event_name == 'push' 74 | && github.ref_type == 'tag' 75 | ) 76 | && true 77 | || false 78 | }} 79 | cache-key-for-dep-files: >- 80 | ${{ steps.calc-cache-key-files.outputs.cache-key-for-dep-files }} 81 | sdist-artifact-name: ${{ steps.artifact-name.outputs.sdist }} 82 | wheel-artifact-name: ${{ steps.artifact-name.outputs.wheel }} 83 | upstream-repository-id: ${{ env.UPSTREAM_REPOSITORY_ID }} 84 | is-debug-mode: ${{ toJSON(runner.debug == '1') }} 85 | 86 | steps: 87 | - name: Switch to using Python 3.11 by default 88 | uses: actions/setup-python@v6 89 | with: 90 | python-version: 3.11 91 | - name: Check out src from Git 92 | uses: actions/checkout@v5 93 | - name: >- 94 | Calculate dependency files' combined hash value 95 | for use in the cache key 96 | id: calc-cache-key-files 97 | uses: ./.github/actions/cache-keys 98 | - name: Set the expected dist artifact names 99 | id: artifact-name 100 | run: | 101 | from os import environ 102 | from pathlib import Path 103 | 104 | FILE_APPEND_MODE = 'a' 105 | 106 | whl_file_prj_base_name = '${{ env.PROJECT_NAME }}'.replace('-', '_') 107 | sdist_file_prj_base_name = whl_file_prj_base_name.replace('.', '_') 108 | 109 | with Path(environ['GITHUB_OUTPUT']).open( 110 | mode=FILE_APPEND_MODE, 111 | ) as outputs_file: 112 | print( 113 | f"sdist={sdist_file_prj_base_name !s}-*.tar.gz", 114 | file=outputs_file, 115 | ) 116 | print( 117 | f"wheel={whl_file_prj_base_name !s}-*-py3-none-any.whl", 118 | file=outputs_file, 119 | ) 120 | 121 | build: 122 | name: >- 123 | 📦 ${{ github.ref_name }} 124 | needs: 125 | - pre-setup 126 | 127 | 128 | uses: tox-dev/workflow/.github/workflows/reusable-tox.yml@208490c75f7f6b81e2698cc959f24d264c462d57 # yamllint disable-line rule:line-length 129 | with: 130 | cache-key-for-dependency-files: >- 131 | ${{ needs.pre-setup.outputs.cache-key-for-dep-files }} 132 | check-name: Build dists under 🐍3.13 133 | job-dependencies-context: >- # context for hooks 134 | ${{ toJSON(needs) }} 135 | python-version: 3.13 136 | runner-vm-os: ubuntu-latest 137 | timeout-minutes: 2 138 | toxenv: build-dists 139 | xfail: false 140 | 141 | lint: 142 | name: 🧹 Linters${{ '' }} # nest jobs under the same sidebar category 143 | needs: 144 | - build 145 | - pre-setup # transitive, for accessing settings 146 | 147 | strategy: 148 | matrix: 149 | runner-vm-os: 150 | - ubuntu-latest 151 | python-version: 152 | - 3.13 153 | toxenv: 154 | - pre-commit 155 | - metadata-validation 156 | - build-docs 157 | # - codelinter-docs 158 | # - coverage-docs 159 | # - doctest-docs 160 | # - linkcheck-docs 161 | - spellcheck-docs 162 | xfail: 163 | - false 164 | fail-fast: false 165 | 166 | uses: tox-dev/workflow/.github/workflows/reusable-tox.yml@208490c75f7f6b81e2698cc959f24d264c462d57 # yamllint disable-line rule:line-length 167 | with: 168 | built-wheel-names: >- 169 | ${{ 170 | matrix.toxenv == 'metadata-validation' 171 | && needs.pre-setup.outputs.wheel-artifact-name 172 | || '' 173 | }} 174 | cache-key-for-dependency-files: >- 175 | ${{ needs.pre-setup.outputs.cache-key-for-dep-files }} 176 | checkout-src-git-fetch-depth: >- 177 | ${{ 178 | fromJSON(needs.pre-setup.outputs.release-requested) 179 | && 1 180 | || 0 181 | }} 182 | dists-artifact-name: >- 183 | ${{ needs.pre-setup.outputs.dists-artifact-name }} 184 | post-toxenv-preparation-command: >- 185 | ${{ 186 | matrix.toxenv == 'pre-commit' 187 | && 'python -Im pre_commit install-hooks' 188 | || '' 189 | }} 190 | python-version: >- 191 | ${{ matrix.python-version }} 192 | require-successful-codecov-uploads: >- 193 | ${{ 194 | toJSON( 195 | needs.pre-setup.outputs.upstream-repository-id 196 | == github.repository_id 197 | ) 198 | }} 199 | runner-vm-os: >- 200 | ${{ matrix.runner-vm-os }} 201 | # NOTE: `pre-commit --show-diff-on-failure` and 202 | # NOTE: `sphinxcontrib-spellcheck` with Git authors allowlist enabled 203 | # NOTE: both depend on the presence of a Git repository. 204 | source-tarball-name: >- 205 | ${{ 206 | !contains( 207 | fromJSON('["pre-commit", "spellcheck-docs"]'), 208 | matrix.toxenv 209 | ) 210 | && needs.pre-setup.outputs.sdist-artifact-name 211 | || '' 212 | }} 213 | # NOTE: `pre-commit` and `sphinxcontrib-spellcheck` both depend on Git. 214 | # NOTE: They may get slower due to network I/O, hence bigger timeout. 215 | timeout-minutes: >- 216 | ${{ 217 | !contains( 218 | fromJSON('["pre-commit", "spellcheck-docs"]'), 219 | matrix.toxenv 220 | ) 221 | && 4 222 | || 2 223 | }} 224 | toxenv: >- 225 | ${{ matrix.toxenv }} 226 | xfail: >- 227 | ${{ matrix.xfail }} 228 | secrets: 229 | codecov-token: ${{ secrets.CODECOV_TOKEN }} 230 | 231 | tests: 232 | name: 🧪 Tests${{ '' }} # nest jobs under the same sidebar category 233 | 234 | needs: 235 | - build 236 | - pre-setup # transitive, for accessing settings 237 | 238 | strategy: 239 | fail-fast: >- # ${{ runner.debug }} is unavailable in this context 240 | ${{ 241 | fromJSON(needs.pre-setup.outputs.is-debug-mode) 242 | && false 243 | || true 244 | }} 245 | matrix: 246 | python-version: 247 | # NOTE: The latest and the lowest supported Pythons are prioritized 248 | # NOTE: to improve the responsiveness. It's nice to see the most 249 | # NOTE: important results first. 250 | - 3.13 251 | - 3.9 252 | - 3.12 253 | - 3.11 254 | - >- 255 | 3.10 256 | runner-vm-os: 257 | - ubuntu-24.04 258 | toxenv: 259 | - py 260 | xfail: 261 | - false 262 | 263 | uses: tox-dev/workflow/.github/workflows/reusable-tox.yml@208490c75f7f6b81e2698cc959f24d264c462d57 # yamllint disable-line rule:line-length 264 | with: 265 | built-wheel-names: >- 266 | ${{ needs.pre-setup.outputs.wheel-artifact-name }} 267 | cache-key-for-dependency-files: >- 268 | ${{ needs.pre-setup.outputs.cache-key-for-dep-files }} 269 | check-name: >- 270 | 🧪 🐍${{ 271 | matrix.python-version 272 | }} @ ${{ 273 | matrix.runner-vm-os 274 | }} 275 | dists-artifact-name: >- 276 | ${{ needs.pre-setup.outputs.dists-artifact-name }} 277 | job-dependencies-context: >- # context for hooks 278 | ${{ toJSON(needs) }} 279 | python-version: >- 280 | ${{ matrix.python-version }} 281 | require-successful-codecov-uploads: >- 282 | ${{ 283 | toJSON( 284 | needs.pre-setup.outputs.upstream-repository-id 285 | == github.repository_id 286 | ) 287 | }} 288 | runner-vm-os: >- 289 | ${{ matrix.runner-vm-os }} 290 | source-tarball-name: >- 291 | ${{ needs.pre-setup.outputs.sdist-artifact-name }} 292 | timeout-minutes: 2 293 | toxenv: >- 294 | ${{ matrix.toxenv }} 295 | # tox-provision-args: >- 296 | # --force-dep '...' 297 | tox-run-posargs: >- 298 | --cov-report=xml:.tox/.tmp/.test-results/pytest-${{ 299 | matrix.python-version 300 | }}/cobertura.xml 301 | --junitxml=.tox/.tmp/.test-results/pytest-${{ 302 | matrix.python-version 303 | }}/test.xml 304 | tox-rerun-posargs: >- 305 | --no-cov 306 | -vvvvv 307 | --lf 308 | xfail: >- 309 | ${{ matrix.xfail }} 310 | secrets: 311 | codecov-token: ${{ secrets.CODECOV_TOKEN }} 312 | 313 | test-summary: 314 | name: Test matrix status 315 | runs-on: ubuntu-latest 316 | needs: 317 | - lint 318 | - tests 319 | if: always() 320 | steps: 321 | - name: Decide whether the needed jobs succeeded or failed 322 | uses: re-actors/alls-green@release/v1 323 | with: 324 | jobs: ${{ toJSON(needs) }} 325 | 326 | deploy: 327 | name: Deploy 328 | runs-on: ubuntu-latest 329 | needs: 330 | - pre-setup # transitive, for accessing settings 331 | - test-summary 332 | # Run only on pushing a tag 333 | if: >- 334 | always() 335 | && needs.test-summary.result == 'success' 336 | && fromJSON(needs.pre-setup.outputs.release-requested) 337 | && needs.pre-setup.outputs.upstream-repository-id == github.repository_id 338 | permissions: 339 | contents: write # IMPORTANT: mandatory for making GitHub Releases 340 | id-token: write # IMPORTANT: mandatory for trusted publishing & sigstore 341 | environment: 342 | name: pypi 343 | url: https://pypi.org/p/${{ env.PROJECT_NAME }} 344 | steps: 345 | - name: Download all the dists 346 | uses: actions/download-artifact@v6 347 | with: 348 | name: >- 349 | ${{ needs.pre-setup.outputs.dists-artifact-name }} 350 | path: dist/ 351 | 352 | - name: Login 353 | run: | 354 | echo "${{ secrets.GITHUB_TOKEN }}" | gh auth login --with-token 355 | - name: Release 356 | uses: aio-libs/create-release@v1.6.6 357 | with: 358 | changes_file: CHANGES.rst 359 | name: ${{ env.PROJECT_NAME }} 360 | github_token: ${{ secrets.GITHUB_TOKEN }} 361 | pypi_token: ${{ secrets.PYPI_TOKEN }} 362 | artifact: '' 363 | version_file: ${{ env.PROJECT_NAME }}/__init__.py 364 | fix_issue_regex: "\n?\\s*`#(\\d+) `_" 365 | fix_issue_repl: ' (#\1)' 366 | 367 | - name: >- 368 | Publish 🐍📦 to PyPI 369 | uses: pypa/gh-action-pypi-publish@release/v1 370 | 371 | - name: Sign the dists with Sigstore 372 | uses: sigstore/gh-action-sigstore-python@v3.1.0 373 | with: 374 | inputs: >- 375 | dist/${{ needs.pre-setup.outputs.sdist-artifact-name }} 376 | dist/${{ needs.pre-setup.outputs.wheel-artifact-name }} 377 | 378 | - name: Upload artifact signatures to GitHub Release 379 | # Confusingly, this action also supports updating releases, not 380 | # just creating them. This is what we want here, since we've manually 381 | # created the release above. 382 | uses: softprops/action-gh-release@v2 383 | with: 384 | # dist/ contains the built packages, which smoketest-artifacts/ 385 | # contains the signatures and certificates. 386 | files: dist/** 387 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = check, clean, {py39,py310,py311,py312,py313}, report 3 | minversion = 4 4 | 5 | 6 | [python-cli-options] 7 | byte-warnings = -b 8 | byte-errors = -bb 9 | max-isolation = -E -s -I 10 | # some-isolation = -I 11 | # FIXME: Python 2 shim. Is this equivalent to the above? 12 | some-isolation = -E -s 13 | warnings-to-errors = -Werror 14 | 15 | 16 | [testenv] 17 | description = Run pytest under {envpython} 18 | # deps = 19 | # pytest 20 | # pytest-asyncio 21 | # pytest-xdist 22 | # pytest-cov 23 | deps = -rrequirements{/}ci-wheel.txt 24 | 25 | commands = 26 | {envpython} \ 27 | {[python-cli-options]byte-errors} \ 28 | {[python-cli-options]max-isolation} \ 29 | {[python-cli-options]warnings-to-errors} \ 30 | -W 'ignore:Coverage failure::pytest_cov.plugin' \ 31 | -m pytest \ 32 | {tty:--color=yes} \ 33 | {posargs:--cov-report=html:{envtmpdir}{/}htmlcov{/}} 34 | commands_post = 35 | -{envpython} \ 36 | {[python-cli-options]byte-errors} \ 37 | {[python-cli-options]max-isolation} \ 38 | {[python-cli-options]warnings-to-errors} \ 39 | -c \ 40 | 'import atexit, os, sys; \ 41 | os.getenv("GITHUB_ACTIONS") == "true" or sys.exit(); \ 42 | import coverage; \ 43 | gh_summary_fd = open(\ 44 | os.environ["GITHUB_STEP_SUMMARY"], encoding="utf-8", mode="a",\ 45 | ); \ 46 | atexit.register(gh_summary_fd.close); \ 47 | cov = coverage.Coverage(); \ 48 | cov.load(); \ 49 | cov.report(file=gh_summary_fd, output_format="markdown")' 50 | {envpython} \ 51 | {[python-cli-options]byte-errors} \ 52 | {[python-cli-options]max-isolation} \ 53 | {[python-cli-options]warnings-to-errors} \ 54 | -c \ 55 | 'import os, pathlib, sys; \ 56 | os.getenv("GITHUB_ACTIONS") == "true" or sys.exit(); \ 57 | cov_report_arg_prefix = "--cov-report=xml:"; \ 58 | test_report_arg_prefix = "--junitxml="; \ 59 | cov_reports = [\ 60 | arg[len(cov_report_arg_prefix):] for arg in sys.argv \ 61 | if arg.startswith(cov_report_arg_prefix)\ 62 | ]; \ 63 | test_reports = [\ 64 | arg[len(test_report_arg_prefix):] for arg in sys.argv \ 65 | if arg.startswith(test_report_arg_prefix)\ 66 | ]; \ 67 | cov_report_file = cov_reports[-1] if cov_reports else None; \ 68 | test_report_file = test_reports[-1] if test_reports else None; \ 69 | gh_output_fd = open(\ 70 | os.environ["GITHUB_OUTPUT"], encoding="utf-8", mode="a",\ 71 | ); \ 72 | cov_report_file and \ 73 | print(f"cov-report-files={cov_report_file !s}", file=gh_output_fd); \ 74 | test_report_file and \ 75 | print(f"test-result-files={test_report_file !s}", file=gh_output_fd); \ 76 | print("codecov-flags=pytest", file=gh_output_fd); \ 77 | gh_output_fd.close()' \ 78 | {posargs} 79 | # Print out the output coverage dir and a way to serve html: 80 | {envpython} \ 81 | {[python-cli-options]byte-errors} \ 82 | {[python-cli-options]max-isolation} \ 83 | {[python-cli-options]warnings-to-errors} \ 84 | -c\ 85 | 'import pathlib, shlex, sys; \ 86 | cov_html_report_arg_prefix = "--cov-report=html:"; \ 87 | cov_html_reports = [\ 88 | arg[len(cov_html_report_arg_prefix):] for arg in sys.argv \ 89 | if arg.startswith(cov_html_report_arg_prefix)\ 90 | ]; \ 91 | cov_html_reports or sys.exit(); \ 92 | cov_html_report_dir = pathlib.Path(cov_html_reports[-1]); \ 93 | index_file = cov_html_report_dir / "index.html";\ 94 | index_file.exists() or sys.exit(); \ 95 | html_url = f"file://\{index_file\}";\ 96 | browse_cmd = shlex.join(("python3", "-Im", "webbrowser", html_url)); \ 97 | serve_cmd = shlex.join((\ 98 | "python3", "-Im", "http.server", \ 99 | "--directory", str(cov_html_report_dir), "0", \ 100 | )); \ 101 | print(f"\nTo open the HTML coverage report, run\n\n\ 102 | \t\{browse_cmd !s\}\n");\ 103 | print(f"To serve \ 104 | the HTML coverage report with a local web server, use\n\n\ 105 | \t\{serve_cmd !s\}\n")' \ 106 | {posargs:--cov-report=html:{envtmpdir}{/}htmlcov{/}} 107 | package = editable 108 | pass_env = 109 | CI 110 | GITHUB_* 111 | SSH_AUTH_SOCK 112 | TERM 113 | set_env = 114 | COVERAGE_PROCESS_START = {toxinidir}{/}.coveragerc 115 | wheel_build_env = .pkg 116 | 117 | 118 | [testenv:cleanup-dists] 119 | description = 120 | Wipe the the dist{/} folder 121 | deps = 122 | commands_pre = 123 | commands = 124 | {envpython} \ 125 | {[python-cli-options]byte-errors} \ 126 | {[python-cli-options]max-isolation} \ 127 | {[python-cli-options]warnings-to-errors} \ 128 | -c \ 129 | 'import os, shutil, sys; \ 130 | dists_dir = "{toxinidir}{/}dist{/}"; \ 131 | shutil.rmtree(dists_dir, ignore_errors=True); \ 132 | sys.exit(os.path.exists(dists_dir))' 133 | commands_post = 134 | package = skip 135 | 136 | 137 | [testenv:build-dists] 138 | description = 139 | Build dists with {basepython} and put them into the dist{/} folder 140 | depends = 141 | cleanup-dists 142 | deps = 143 | build 144 | commands = 145 | {envpython} \ 146 | {[python-cli-options]byte-errors} \ 147 | {[python-cli-options]max-isolation} \ 148 | {[python-cli-options]warnings-to-errors} \ 149 | -m build \ 150 | {posargs:} 151 | commands_post = 152 | package = skip 153 | 154 | 155 | [testenv:metadata-validation] 156 | description = 157 | Verify that dists under the `dist{/}` dir 158 | have valid metadata 159 | depends = 160 | build-dists 161 | deps = -rrequirements{/}wheel.txt 162 | commands = 163 | {envpython} \ 164 | {[python-cli-options]byte-errors} \ 165 | {[python-cli-options]max-isolation} \ 166 | {[python-cli-options]warnings-to-errors} \ 167 | -m twine \ 168 | check \ 169 | --strict \ 170 | dist{/}* 171 | commands_post = 172 | package = skip 173 | 174 | 175 | [testenv:pre-commit] 176 | description = 177 | Run the quality checks under {basepython}; run as 178 | `SKIP=check-id1,check-id2 tox r -e pre-commit` to instruct the underlying 179 | `pre-commit` invocation avoid running said checks; Use 180 | `tox r -e pre-commit -- check-id1 --all-files` to select checks matching IDs 181 | aliases{:} `tox r -e pre-commit -- mypy --all-files` will run 3 MyPy 182 | invocations, but `tox r -e pre-commit -- mypy-py313 --all-files` runs one. 183 | commands = 184 | {envpython} \ 185 | {[python-cli-options]byte-errors} \ 186 | {[python-cli-options]max-isolation} \ 187 | {[python-cli-options]warnings-to-errors} \ 188 | -m pre_commit \ 189 | run \ 190 | --color=always \ 191 | --show-diff-on-failure \ 192 | {posargs:--all-files} 193 | 194 | # Print out the advice on how to install pre-commit from this env into Git: 195 | -{envpython} \ 196 | {[python-cli-options]byte-errors} \ 197 | {[python-cli-options]max-isolation} \ 198 | {[python-cli-options]warnings-to-errors} \ 199 | -c \ 200 | 'cmd = "{envpython} -m pre_commit install"; \ 201 | scr_width = len(cmd) + 10; \ 202 | sep = "=" * scr_width; \ 203 | cmd_str = " $ \{cmd\}";' \ 204 | 'print(f"\n\{sep\}\nTo install pre-commit hooks into the Git repo, run:\ 205 | \n\n\{cmd_str\}\n\n\{sep\}\n")' 206 | commands_post = 207 | {envpython} \ 208 | {[python-cli-options]byte-errors} \ 209 | {[python-cli-options]max-isolation} \ 210 | {[python-cli-options]warnings-to-errors} \ 211 | -c \ 212 | 'import os, pathlib, sys; \ 213 | os.getenv("GITHUB_ACTIONS") == "true" or sys.exit(); \ 214 | project_root_path = pathlib.Path(r"{toxinidir}"); \ 215 | test_results_dir = pathlib.Path(r"{temp_dir}") / ".test-results"; \ 216 | coverage_result_files = ",".join(\ 217 | str(xml_path.relative_to(project_root_path)) \ 218 | for xml_path in test_results_dir.glob("mypy--py-*{/}cobertura.xml")\ 219 | ); \ 220 | gh_output_fd = open(\ 221 | os.environ["GITHUB_OUTPUT"], encoding="utf-8", mode="a",\ 222 | ); \ 223 | print(\ 224 | f"cov-report-files={coverage_result_files !s}", file=gh_output_fd\ 225 | ); \ 226 | print("codecov-flags=MyPy", file=gh_output_fd); \ 227 | gh_output_fd.close()' 228 | {envpython} \ 229 | {[python-cli-options]byte-errors} \ 230 | {[python-cli-options]max-isolation} \ 231 | {[python-cli-options]warnings-to-errors} \ 232 | -c \ 233 | 'import itertools, os, pathlib, shlex, sys; \ 234 | os.getenv("GITHUB_ACTIONS") == "true" or sys.exit(); \ 235 | test_results_dir = pathlib.Path(r"{temp_dir}") / ".test-results"; \ 236 | text_and_json_reports = itertools.chain( \ 237 | test_results_dir.glob("mypy--py-*{/}*.json"), \ 238 | test_results_dir.glob("mypy--py-*{/}*.txt"), \ 239 | ); \ 240 | report_contents = { \ 241 | report{:} report.read_text() \ 242 | for report in text_and_json_reports \ 243 | }; \ 244 | reports_summary_text_blob = "\n\n".join( \ 245 | f"\N\{NUMBER SIGN\}\N\{NUMBER SIGN\} {report_path.parent.name}{:} " \ 246 | f"`{report_path.name}`\n\n" \ 247 | f"```{report_path.suffix[1:]}\n{report_text}\n```\n" \ 248 | for report_path, report_text in report_contents.items() \ 249 | ); \ 250 | gh_summary_fd = open( \ 251 | os.environ["GITHUB_STEP_SUMMARY"], encoding="utf-8", mode="a", \ 252 | ); \ 253 | print(reports_summary_text_blob, file=gh_summary_fd); \ 254 | gh_summary_fd.close()' 255 | # Print out the output coverage dir and a way to serve html: 256 | {envpython} \ 257 | {[python-cli-options]byte-errors} \ 258 | {[python-cli-options]max-isolation} \ 259 | {[python-cli-options]warnings-to-errors} \ 260 | -c\ 261 | 'import os, pathlib, sys; \ 262 | os.getenv("GITHUB_ACTIONS") == "true" and sys.exit(); \ 263 | len(sys.argv) >= 3 and all(\ 264 | arg != "mypy" and not arg.startswith("mypy-py3") \ 265 | for arg in sys.argv \ 266 | ) and sys.exit(); \ 267 | project_root_path = pathlib.Path(r"{toxinidir}"); \ 268 | test_results_dir = pathlib.Path(r"{temp_dir}") / ".test-results"; \ 269 | coverage_html_report_urls = [\ 270 | f"file://\{xml_path !s\}" \ 271 | for xml_path in test_results_dir.glob("mypy--py-*{/}index.html")\ 272 | ]; \ 273 | coverage_html_report_open_cmds = [\ 274 | f"python3 -Im webbrowser \N\{QUOTATION MARK\}\{html_url !s\}\N\{QUOTATION MARK\}" \ 275 | for html_url in coverage_html_report_urls\ 276 | ]; \ 277 | coverage_html_report_open_cmds_blob = "\n\n\t".join(\ 278 | coverage_html_report_open_cmds,\ 279 | ); \ 280 | print(\ 281 | f"\nTo open the HTML coverage reports, run\n\n\ 282 | \t\{coverage_html_report_open_cmds_blob !s\}\n"\ 283 | ); \ 284 | print(\ 285 | f"[*] Find rest of JSON and text reports, are in the same directories."\ 286 | )\ 287 | ' \ 288 | {posargs:--all-files} 289 | deps = -rrequirements{/}wheel.txt 290 | isolated_build = true 291 | package = skip 292 | pass_env = 293 | {[testenv]pass_env} 294 | SKIP # set this variable 295 | 296 | 297 | [testenv:build-docs] 298 | # NOTE: Passing the `is_unversioned` tag speeds up rebuilds in dev env 299 | allowlist_externals = 300 | git 301 | description = Build The Docs 302 | changedir = docs{/} 303 | commands_pre = 304 | # Retrieve possibly missing commits: 305 | -git fetch --unshallow 306 | -git fetch --tags 307 | 308 | # Clean up sphinxcontrib-apidoc generated RST files: 309 | -git clean -x -f -- 'pkg{/}*.rst' 310 | commands = 311 | {envpython} \ 312 | {[python-cli-options]byte-errors} \ 313 | {[python-cli-options]max-isolation} \ 314 | {[python-cli-options]warnings-to-errors} \ 315 | -m sphinx \ 316 | -j auto \ 317 | -b html \ 318 | {tty:--color} \ 319 | -a \ 320 | -n \ 321 | -W --keep-going \ 322 | -d '{temp_dir}{/}.doctrees' \ 323 | . \ 324 | {posargs:{envtmpdir}{/}html -t is_unversioned} 325 | commands_post = 326 | # Print out the output docs dir and a way to serve html: 327 | {envpython} \ 328 | {[python-cli-options]byte-errors} \ 329 | {[python-cli-options]max-isolation} \ 330 | {[python-cli-options]warnings-to-errors} \ 331 | -c\ 332 | 'import os, pathlib;\ 333 | IS_RTD_ENV = os.getenv("READTHEDOCS", "False") == "True";\ 334 | docs_dir = pathlib.Path(r"{envdir}") / r"{envtmpdir}" / "html";\ 335 | index_file = docs_dir / "index.html";\ 336 | docs_url = os.environ["READTHEDOCS_CANONICAL_URL"] if IS_RTD_ENV \ 337 | else f"file://\{index_file\}";\ 338 | print(f"\nTo open the documentation, run\n\n\ 339 | \tpython3 -Im webbrowser \ 340 | \N\{QUOTATION MARK\}\{docs_url !s\}\N\{QUOTATION MARK\}\n");\ 341 | not IS_RTD_ENV and \ 342 | print(f"To serve \ 343 | the docs with a local web server, use\n\n\ 344 | \tpython3 -Im http.server --directory \ 345 | \N\{QUOTATION MARK\}\{docs_dir\}\N\{QUOTATION MARK\} 0\n")' 346 | deps = 347 | -r{toxinidir}{/}requirements{/}doc.txt 348 | pass_env = 349 | {[testenv]pass_env} 350 | READTHEDOCS* # Present @ RTD 351 | 352 | 353 | [testenv:spellcheck-docs] 354 | allowlist_externals = 355 | {[testenv:build-docs]allowlist_externals} 356 | description = Spellcheck The Docs 357 | changedir = {[testenv:build-docs]changedir} 358 | commands_pre = 359 | # Retrieve possibly missing commits: 360 | -git fetch --unshallow 361 | -git fetch --tags 362 | 363 | # Clean up sphinxcontrib-apidoc generated RST files: 364 | -git clean -x -f -- 'pkg{/}*.rst' 365 | commands = 366 | {envpython} \ 367 | {[python-cli-options]byte-errors} \ 368 | {[python-cli-options]max-isolation} \ 369 | {[python-cli-options]warnings-to-errors} \ 370 | -m sphinx \ 371 | -j auto \ 372 | {tty:--color} \ 373 | -a \ 374 | -n \ 375 | -W --keep-going \ 376 | -b spelling --color \ 377 | -d "{temp_dir}{/}.doctrees" \ 378 | . "{toxworkdir}{/}spelling" 379 | commands_post = 380 | deps = 381 | -r{toxinidir}{/}requirements{/}doc-spelling.txt 382 | pass_env = 383 | {[testenv:build-docs]pass_env} 384 | 385 | 386 | [testenv:check] 387 | basepython = python3.13 388 | 389 | deps = 390 | wheel 391 | flake8 392 | docutils 393 | pygments 394 | twine 395 | build 396 | 397 | commands = 398 | flake8 aiosignal tests 399 | python -m build 400 | python -m twine check --strict dist/* 401 | commands_post = 402 | 403 | [testenv:clean] 404 | basepython = python3.13 405 | 406 | deps = coverage 407 | skip_install = true 408 | 409 | commands = 410 | coverage erase 411 | commands_post = 412 | 413 | [testenv:report] 414 | basepython = python3.13 415 | 416 | deps = coverage 417 | skip_install = true 418 | 419 | commands = 420 | coverage report 421 | coverage html 422 | {envpython} -c '\ 423 | print("python -m webbrowser \ 424 | \N{Apostrophe}file://{toxinidir}/htmlcov/index.html\N{Apostrophe}")\ 425 | ' 426 | commands_post = 427 | --------------------------------------------------------------------------------