From e163fa41f7d11c8f7845de3862c443ceb3c779fc Mon Sep 17 00:00:00 2001 From: Leandro Lucarella Date: Thu, 17 Aug 2023 13:15:31 +0200 Subject: [PATCH 01/12] Remove extensions and _cookiecutter from replay file We always want to use the template provided extensions, keeping our own list only makes it more likely to have errors when upgrading. Also `_cookiecutter` is just noise, not very useful to keep. Signed-off-by: Leandro Lucarella --- .cookiecutter-replay.json | 39 +-------------------------------------- 1 file changed, 1 insertion(+), 38 deletions(-) diff --git a/.cookiecutter-replay.json b/.cookiecutter-replay.json index f7085ec67..e90db3936 100644 --- a/.cookiecutter-replay.json +++ b/.cookiecutter-replay.json @@ -14,43 +14,6 @@ "pypi_package_name": "frequenz-sdk", "github_repo_name": "frequenz-sdk-python", "default_codeowners": "@frequenz-floss/python-sdk-team", - "_extensions": [ - "jinja2_time.TimeExtension", - "local_extensions.as_identifier", - "local_extensions.default_codeowners", - "local_extensions.github_repo_name", - "local_extensions.introduction", - "local_extensions.keywords", - "local_extensions.pypi_package_name", - "local_extensions.python_package", - "local_extensions.src_path", - "local_extensions.title" - ], - "_template": "gh:frequenz-floss/frequenz-repo-config-python", - }, - "_cookiecutter": { - "Introduction": "{{cookiecutter | introduction}}", - "type": [ - "actor", - "api", - "app", - "lib", - "model" - ], - "name": null, - "description": null, - "title": "{{cookiecutter | title}}", - "keywords": "(comma separated: 'frequenz', and are included automatically)", - "github_org": "frequenz-floss", - "license": [ - "MIT", - "Proprietary" - ], - "author_name": "Frequenz Energy-as-a-Service GmbH", - "author_email": "floss@frequenz.com", - "python_package": "{{cookiecutter | python_package}}", - "pypi_package_name": "{{cookiecutter | pypi_package_name}}", - "github_repo_name": "{{cookiecutter | github_repo_name}}", - "default_codeowners": "(like @some-org/some-team; defaults to a team based on the repo type)" + "_template": "gh:frequenz-floss/frequenz-repo-config-python" } } \ No newline at end of file From d83896f3f2c171f6608345ee6369f95f6c543a6d Mon Sep 17 00:00:00 2001 From: Leandro Lucarella Date: Thu, 17 Aug 2023 13:16:09 +0200 Subject: [PATCH 02/12] Remove unused `Introduction` from the replay file The key needs to be there, but the contents is only to show on the first generation, so we don't need to save it in the replay file. Signed-off-by: Leandro Lucarella --- .cookiecutter-replay.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.cookiecutter-replay.json b/.cookiecutter-replay.json index e90db3936..83f09b4f3 100644 --- a/.cookiecutter-replay.json +++ b/.cookiecutter-replay.json @@ -1,6 +1,6 @@ { "cookiecutter": { - "Introduction": "]\n\nWelcome to repo-config Cookiecutter template!\n\nThis template will help you to create a new repository for your project. You will be asked to provide some information about your project.\n\nHere is an explanation of what each variable is for and will be used for:\n\n* `type`: The type of repository. It must be chosen from the list.\n\n* `name`: The name of the project. This will be used to build defaults for\n other inputs, such as `title`, `python_package`, etc. It should be one word,\n using only alphanumeric characters (and starting with a letter). It can\n include also `_` and `-` which will be handled differently when building\n other variables from it (replaced by spaces in titles for example).\n\n* `description`: A short description of the project. It will be used as the\n description in the `README.md`, `pyproject.toml`, `mkdocs.yml`, etc.\n\n* `title`: A human-readable name or title for the project. It will be used in\n the `README.md`, `CONTRIBUTING.md`, and other files to refer to the project,\n as well as the site title in `mkdocs.yml`.\n\n* `keywords`: A comma-separated list of keywords that will be used in the\n `pyproject.toml` file. If left untouched, it will use only some predefined\n keywords. If anything else is entered, it will be **added** to the default\n keywords.\n\n* `github_org`: The GitHub handle of the organization where the project will\n reside. This will be used to generate links to the project on GitHub.\n\n* `license`: Currently, only two options are provided: `MIT`, which should be\n used for open-source projects, and `Proprietary`, which should be used for\n closed-source projects. This will be added to file headers and used as the\n license in `pyproject.toml`.\n\n* `author_name`, `author_email`: The name and email address of the author of\n the project. They will be used in the copyright notice in file headers and\n as the author in `pyproject.toml`.\n\n* `python_package`: The Python package in which this project will reside. All\n files provided by this project should be located in this package. This needs\n to be a list of valid Python identifiers separated by dots. The source file\n structure will be derived from this. For example, `frequenz.actor.example`\n will generate files in `src/frequenz/actor/example`.\n\n* `pypi_package_name`: The name of the PyPI/wheel/distribution package. This\n should be consistent with the `python_package`, usually replacing `.` with\n `-`. For example, `frequenz-actor-example`.\n\n* `github_repo_name`: The handle of the GitHub repository where the project\n will reside. This will be used to generate links to the project on GitHub and\n as the top-level directory name.\n\n* `default_codeowners`: A space-separated list of GitHub teams (`@org/team`) or\n users (`@user`) that will be the default code owners for this project. This\n will be used to build the `CODEOWNERS` file. Please refer to the [code owners\n documentation] for more details on the valid syntax.\n\n[code owners documentation]: https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners\n\n\n[Please press any key to continue", + "Introduction": "", "type": "lib", "name": "sdk", "description": "A development kit to interact with the Frequenz development platform", From 703ae28fbda1a6a5c87cc1d81e1f705868e1e4de Mon Sep 17 00:00:00 2001 From: Leandro Lucarella Date: Thu, 17 Aug 2023 13:16:27 +0200 Subject: [PATCH 03/12] Add `\n` to the end of the replay file This is just to be nice to editors, that usually like having a `\n` at the end of text files. Signed-off-by: Leandro Lucarella --- .cookiecutter-replay.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.cookiecutter-replay.json b/.cookiecutter-replay.json index 83f09b4f3..0d2fbe5a9 100644 --- a/.cookiecutter-replay.json +++ b/.cookiecutter-replay.json @@ -16,4 +16,4 @@ "default_codeowners": "@frequenz-floss/python-sdk-team", "_template": "gh:frequenz-floss/frequenz-repo-config-python" } -} \ No newline at end of file +} From e7a3c3a1d5855f3de5a4072a338d236ff6a3cba8 Mon Sep 17 00:00:00 2001 From: Leandro Lucarella Date: Thu, 17 Aug 2023 13:22:03 +0200 Subject: [PATCH 04/12] Bump repo-config version to v0.5.2 Signed-off-by: Leandro Lucarella --- pyproject.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index a5c8e87e3..11da7241d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ requires = [ "setuptools == 67.7.2", "setuptools_scm[toml] == 7.1.0", - "frequenz-repo-config[lib] == 0.4.0", + "frequenz-repo-config[lib] == 0.5.2", ] build-backend = "setuptools.build_meta" @@ -63,7 +63,7 @@ dev-mkdocs = [ "mkdocs-material == 9.2.5", "mkdocs-section-index == 0.3.5", "mkdocstrings[python] == 0.22.0", - "frequenz-repo-config[lib] == 0.4.0", + "frequenz-repo-config[lib] == 0.5.2", ] dev-mypy = [ "mypy == 1.5.1", @@ -72,7 +72,7 @@ dev-mypy = [ # For checking the noxfile, docs/ script, and tests "frequenz-sdk[dev-mkdocs,dev-noxfile,dev-pytest]", ] -dev-noxfile = ["nox == 2023.4.22", "frequenz-repo-config[lib] == 0.4.0"] +dev-noxfile = ["nox == 2023.4.22", "frequenz-repo-config[lib] == 0.5.2"] dev-pylint = [ "pylint == 2.17.5", # For checking the noxfile, docs/ script, and tests From 5420f59cbe38f189b30f517adaaf86d6fdfabd33 Mon Sep 17 00:00:00 2001 From: Leandro Lucarella Date: Thu, 17 Aug 2023 13:22:59 +0200 Subject: [PATCH 05/12] Add editorconfig file This is to have a common basic configuration that should work with most editors/IDEs. Signed-off-by: Leandro Lucarella --- .editorconfig | 26 ++++++++++++++++++++++++++ .github/labeler.yml | 1 + MANIFEST.in | 1 + 3 files changed, 28 insertions(+) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..c6c7f9e56 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,26 @@ +# EditorConfig is awesome: https://EditorConfig.org + +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +end_of_line = lf +insert_final_newline = true + +# Set default charset, indent style and trimming of whitespace +[{.editorconfig,CODEOWNERS,LICENSE,*.{in,json,md,proto,py,pyi,toml,yaml,yml}}] +charset = utf-8 +indent_style = space +trim_trailing_whitespace = true + +# 4 space indentation +[*.{py,pyi}] +indent_size = 4 + +# 2 space indentation +[{.editorconfig,CODEOWNERS,LICENSE,*.{in,json,proto,toml,yaml,yml}}] +indent_size = 2 + +# No indentation size specified for *.md because different blocks have +# different indentation rules diff --git a/.github/labeler.yml b/.github/labeler.yml index 8fb98645c..7d6121477 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -40,6 +40,7 @@ - "**/*.toml" - "**/*.yaml" - "**/*.yml" + - ".editorconfig" - ".git*" - ".git*/**" - "docs/*.py" diff --git a/MANIFEST.in b/MANIFEST.in index a69e51f11..ee5dd7a0d 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,5 @@ exclude .darglint +exclude .editorconfig exclude .gitignore exclude CODEOWNERS exclude minimum-requirements-ci.txt From 9395717d5bd46680f22b7553a77d0bbee309352b Mon Sep 17 00:00:00 2001 From: Leandro Lucarella Date: Thu, 17 Aug 2023 13:26:51 +0200 Subject: [PATCH 06/12] Use repo-config for linting examples in docstrings Now repo-config ships support to easily enable the collection and linting of code examples in docstrings, so we use that instead. Also make sure that we label the `conftest.py` file properly and we exclude it from the source distribution. Signed-off-by: Leandro Lucarella --- .github/labeler.yml | 2 + MANIFEST.in | 1 + pyproject.toml | 1 + src/conftest.py | 211 ++------------------------------------------ 4 files changed, 9 insertions(+), 206 deletions(-) diff --git a/.github/labeler.yml b/.github/labeler.yml index 7d6121477..7a6dbbdc6 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -33,6 +33,7 @@ - "src/frequenz/sdk/power/**" "part:tests": + - "**/conftest.py" - "tests/**" "part:tooling": @@ -40,6 +41,7 @@ - "**/*.toml" - "**/*.yaml" - "**/*.yml" + - "**/conftest.py" - ".editorconfig" - ".git*" - ".git*/**" diff --git a/MANIFEST.in b/MANIFEST.in index ee5dd7a0d..9c1a4beaa 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -4,6 +4,7 @@ exclude .gitignore exclude CODEOWNERS exclude minimum-requirements-ci.txt exclude noxfile.py +exclude src/conftest.py recursive-exclude .github * recursive-exclude benchmarks * recursive-exclude examples * diff --git a/pyproject.toml b/pyproject.toml index 11da7241d..2b9a0d551 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -80,6 +80,7 @@ dev-pylint = [ ] dev-pytest = [ "pytest == 7.4.0", + "frequenz-repo-config[extra-lint-examples] == 0.5.2", "pytest-mock == 3.11.1", "pytest-asyncio == 0.21.1", "time-machine == 2.12.0", diff --git a/src/conftest.py b/src/conftest.py index b53dc6ebe..2c6e0fcff 100644 --- a/src/conftest.py +++ b/src/conftest.py @@ -1,214 +1,13 @@ # License: MIT # Copyright © 2023 Frequenz Energy-as-a-Service GmbH -"""Pytest plugin to validate docstring code examples. +"""Validate docstring code examples. -Code examples are often wrapped in triple backticks (```) within our docstrings. +Code examples are often wrapped in triple backticks (```) within docstrings. This plugin extracts these code examples and validates them using pylint. """ -from __future__ import annotations +from frequenz.repo.config.pytest import examples +from sybil import Sybil -import ast -import os -import subprocess -from pathlib import Path - -from sybil import Example, Sybil -from sybil.evaluators.python import pad -from sybil.parsers.abstract.lexers import textwrap -from sybil.parsers.myst import CodeBlockParser - -PYLINT_DISABLE_COMMENT = ( - "# pylint: {}=unused-import,wildcard-import,unused-wildcard-import" -) - -FORMAT_STRING = """ -# Generated auto-imports for code example -{disable_pylint} -{imports} -{enable_pylint} - -{code}""" - - -def get_import_statements(code: str) -> list[str]: - """Get all import statements from a given code string. - - Args: - code: The code to extract import statements from. - - Returns: - A list of import statements. - """ - tree = ast.parse(code) - import_statements: list[str] = [] - - for node in ast.walk(tree): - if isinstance(node, (ast.Import, ast.ImportFrom)): - import_statement = ast.get_source_segment(code, node) - assert import_statement is not None - import_statements.append(import_statement) - - return import_statements - - -def path_to_import_statement(path: Path) -> str: - """Convert a path to a Python file to an import statement. - - Args: - path: The path to convert. - - Returns: - The import statement. - - Raises: - ValueError: If the path does not point to a Python file. - """ - # Make the path relative to the present working directory - if path.is_absolute(): - path = path.relative_to(Path.cwd()) - - # Check if the path is a Python file - if path.suffix != ".py": - raise ValueError("Path must point to a Python file (.py)") - - # Remove 'src' prefix if present - parts = path.parts - if parts[0] == "src": - parts = parts[1:] - - # Remove the '.py' extension and join parts with '.' - module_path = ".".join(parts)[:-3] - - # Create the import statement - import_statement = f"from {module_path} import *" - return import_statement - - -# We need to add the type ignore comment here because the Sybil library does not -# have type annotations. -class CustomPythonCodeBlockParser(CodeBlockParser): # type: ignore[misc] - """Code block parser that validates extracted code examples using pylint. - - This parser is a modified version of the default Python code block parser - from the Sybil library. - It uses pylint to validate the extracted code examples. - - All code examples are preceded by the original file's import statements as - well as an wildcard import of the file itself. - This allows us to use the code examples as if they were part of the original - file. - - Additionally, the code example is padded with empty lines to make sure the - line numbers are correct. - - Pylint warnings which are unimportant for code examples are disabled. - """ - - def __init__(self) -> None: - """Initialize the parser.""" - super().__init__("python") - - def evaluate(self, example: Example) -> None | str: - """Validate the extracted code example using pylint. - - Args: - example: The extracted code example. - - Returns: - None if the code example is valid, otherwise the pylint output. - """ - # Get the import statements for the original file - import_header = get_import_statements(example.document.text) - # Add a wildcard import of the original file - import_header.append( - path_to_import_statement(Path(os.path.relpath(example.path))) - ) - imports_code = "\n".join(import_header) - - # Dedent the code example - # There is also example.parsed that is already prepared, but it has - # empty lines stripped and thus fucks up the line numbers. - example_code = textwrap.dedent( - example.document.text[example.start : example.end] - ) - # Remove first line (the line with the triple backticks) - example_code = example_code[example_code.find("\n") + 1 :] - - example_with_imports = FORMAT_STRING.format( - disable_pylint=PYLINT_DISABLE_COMMENT.format("disable"), - imports=imports_code, - enable_pylint=PYLINT_DISABLE_COMMENT.format("enable"), - code=example_code, - ) - - # Make sure the line numbers are correct - source = pad( - example_with_imports, - example.line - imports_code.count("\n") - FORMAT_STRING.count("\n"), - ) - - # pylint disable parameters - pylint_disable_params = [ - "missing-module-docstring", - "missing-class-docstring", - "missing-function-docstring", - "reimported", - "unused-variable", - "no-name-in-module", - "await-outside-async", - ] - - response = validate_with_pylint(source, example.path, pylint_disable_params) - - if len(response) > 0: - return ( - f"Pylint validation failed for code example:\n" - f"{example_with_imports}\nOutput: " + "\n".join(response) - ) - - return None - - -def validate_with_pylint( - code_example: str, path: str, disable_params: list[str] -) -> list[str]: - """Validate a code example using pylint. - - Args: - code_example: The code example to validate. - path: The path to the original file. - disable_params: The pylint disable parameters. - - Returns: - A list of pylint messages. - """ - try: - pylint_command = [ - "pylint", - "--disable", - ",".join(disable_params), - "--from-stdin", - path, - ] - - subprocess.run( - pylint_command, - input=code_example, - text=True, - capture_output=True, - check=True, - ) - except subprocess.CalledProcessError as exception: - output = exception.output - assert isinstance(output, str) - return output.splitlines() - - return [] - - -pytest_collect_file = Sybil( - parsers=[CustomPythonCodeBlockParser()], - patterns=["*.py"], -).pytest() +pytest_collect_file = Sybil(**examples.get_sybil_arguments()).pytest() From 9a84c47058887068b721f312c0e1097393596bf5 Mon Sep 17 00:00:00 2001 From: Leandro Lucarella Date: Thu, 17 Aug 2023 13:27:36 +0200 Subject: [PATCH 07/12] ci: Checkout submodules This is not really used in this project, but it is included as part of the repo-config ci template, as it doesn't hurt either. Signed-off-by: Leandro Lucarella --- .github/workflows/ci.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 5d15160e3..1cb82cd57 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -37,6 +37,8 @@ jobs: steps: - name: Fetch sources uses: actions/checkout@v3 + with: + submodules: true - name: Set up Python uses: actions/setup-python@v4 @@ -61,6 +63,8 @@ jobs: steps: - name: Fetch sources uses: actions/checkout@v3 + with: + submodules: true - name: Set up Python uses: actions/setup-python@v4 @@ -115,6 +119,8 @@ jobs: steps: - name: Fetch sources uses: actions/checkout@v3 + with: + submodules: true - name: Setup Git user and e-mail uses: frequenz-floss/setup-git-user@v2 @@ -192,6 +198,8 @@ jobs: - name: Fetch sources if: steps.mike-metadata.outputs.version uses: actions/checkout@v3 + with: + submodules: true - name: Setup Git user and e-mail if: steps.mike-metadata.outputs.version From 3715fb43aa57b842acd4ff7e782e81cf3bceb447 Mon Sep 17 00:00:00 2001 From: Leandro Lucarella Date: Thu, 17 Aug 2023 13:27:56 +0200 Subject: [PATCH 08/12] Allow matching dotfiles in the labeler workflow patterns Signed-off-by: Leandro Lucarella --- .github/workflows/labeler.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index 10c7adb6c..783997ba7 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -21,3 +21,4 @@ jobs: uses: actions/labeler@ac9175f8a1f3625fd0d4fb234536d26811351594 # 4.3.0 with: repo-token: "${{ secrets.GITHUB_TOKEN }}" + dot: true From dd82f2fdf1ffe7edf9525fe41fe01b1631c7ebbb Mon Sep 17 00:00:00 2001 From: Leandro Lucarella Date: Thu, 17 Aug 2023 13:29:10 +0200 Subject: [PATCH 09/12] Rename the workflow to check for release notes This is to match the name used by the repo-config template. Signed-off-by: Leandro Lucarella --- .../{release_notes_check.yml => release-notes-check.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/workflows/{release_notes_check.yml => release-notes-check.yml} (100%) diff --git a/.github/workflows/release_notes_check.yml b/.github/workflows/release-notes-check.yml similarity index 100% rename from .github/workflows/release_notes_check.yml rename to .github/workflows/release-notes-check.yml From 7fad853362cdb6c65e7add22e91c0e5b8545c496 Mon Sep 17 00:00:00 2001 From: Leandro Lucarella Date: Thu, 17 Aug 2023 14:13:48 +0200 Subject: [PATCH 10/12] Don't ship development files in the source distribution Signed-off-by: Leandro Lucarella --- MANIFEST.in | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/MANIFEST.in b/MANIFEST.in index 9c1a4beaa..1dc8c9d55 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,11 +1,13 @@ +exclude .cookiecutter-replay.json exclude .darglint exclude .editorconfig exclude .gitignore exclude CODEOWNERS -exclude minimum-requirements-ci.txt +exclude CONTRIBUTING.md +exclude mkdocs.yml exclude noxfile.py exclude src/conftest.py recursive-exclude .github * -recursive-exclude benchmarks * -recursive-exclude examples * +recursive-exclude docs * recursive-exclude tests * +recursive-include py *.pyi From b31a382cebaff9bdc938816ea6109fbccd093d26 Mon Sep 17 00:00:00 2001 From: Leandro Lucarella Date: Fri, 18 Aug 2023 09:37:04 +0200 Subject: [PATCH 11/12] Bump setuptools dependency Signed-off-by: Leandro Lucarella --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 2b9a0d551..da039339d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,7 @@ [build-system] requires = [ - "setuptools == 67.7.2", + "setuptools == 68.1.0", "setuptools_scm[toml] == 7.1.0", "frequenz-repo-config[lib] == 0.5.2", ] From 90226298a8f3cb60c25f9971d5fdf179e7fc8317 Mon Sep 17 00:00:00 2001 From: Leandro Lucarella Date: Tue, 29 Aug 2023 15:59:31 +0200 Subject: [PATCH 12/12] mypy: Add `no_incremental` option This option avoids using a cache. It is slower but prevents some issues with `mypy` giving different results on different runs (typically complaining about some `type: ignore[issue]` not being used but when removing it complaining about an `issue` again. Signed-off-by: Leandro Lucarella --- noxfile.py | 8 ++++++-- pyproject.toml | 7 +++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/noxfile.py b/noxfile.py index 025ceb99f..9ad0966bf 100644 --- a/noxfile.py +++ b/noxfile.py @@ -3,6 +3,10 @@ """Configuration file for nox.""" -from frequenz.repo.config import RepositoryType, nox +from frequenz.repo.config import nox +from frequenz.repo.config.nox import default -nox.configure(RepositoryType.LIB) +config = default.lib_config.copy() +config.opts.mypy = [] # Set in pyproject.toml + +nox.configure(config) diff --git a/pyproject.toml b/pyproject.toml index da039339d..e2972f903 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -135,6 +135,13 @@ testpaths = ["tests", "src"] asyncio_mode = "auto" required_plugins = ["pytest-asyncio", "pytest-mock"] +[tool.mypy] +explicit_package_bases = true +namespace_packages = true +no_incremental = true +packages = ["frequenz.sdk"] +strict = true + [[tool.mypy.overrides]] module = [ "async_solipsism",