Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make CLI startup faster, support tab completion with argcomplete #173

Merged
merged 10 commits into from
Aug 21, 2024
5 changes: 5 additions & 0 deletions changelogs/fragments/173-argcomplete.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
minor_changes:
- "If you are using `argcomplete <https://pypi.org/project/argcomplete/>`__ global completion, you can now tab-complete ``antsibull-changelog`` command lines.
See `Activating global completion <https://pypi.org/project/argcomplete/#activating-global-completion>`__ in the argcomplete README for
how to enable tab completion globally. This will also tab-complete Ansible commands such as ``ansible-playbook`` and ``ansible-test``
(https://github.com/ansible-community/antsibull-changelog/pull/173)."
90 changes: 45 additions & 45 deletions src/antsibull_changelog/ansible.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,10 @@

from __future__ import annotations

from typing import Any
from functools import cache

import packaging.version

try:
from ansible import constants as C

HAS_ANSIBLE_CONSTANTS = True
except ImportError:
HAS_ANSIBLE_CONSTANTS = False


ansible_release: Any
try:
from ansible import release as ansible_release

HAS_ANSIBLE_RELEASE = True
except ImportError:
ansible_release = None
HAS_ANSIBLE_RELEASE = False


OBJECT_TYPES = ("role", "playbook")

OTHER_PLUGIN_TYPES = ("module", "test", "filter")
Expand All @@ -41,48 +23,66 @@
PLUGIN_EXCEPTIONS = (("cache", "base.py"), ("module", "async_wrapper.py"))


@cache
def get_documentable_plugins() -> tuple[str, ...]:
"""
Retrieve plugin types that can be documented.
"""
if HAS_ANSIBLE_CONSTANTS:
return C.DOCUMENTABLE_PLUGINS
return (
"become",
"cache",
"callback",
"cliconf",
"connection",
"httpapi",
"inventory",
"lookup",
"netconf",
"shell",
"vars",
"module",
"strategy",
)

try:
# We import from ansible locally since importing it is rather slow
from ansible import constants as C # pylint: disable=import-outside-toplevel

return C.DOCUMENTABLE_PLUGINS
except ImportError:
return (
"become",
"cache",
"callback",
"cliconf",
"connection",
"httpapi",
"inventory",
"lookup",
"netconf",
"shell",
"vars",
"module",
"strategy",
)


@cache
def get_documentable_objects() -> tuple[str, ...]:
"""
Retrieve object types that can be documented.
"""
if not HAS_ANSIBLE_RELEASE:
return ()
if packaging.version.Version(
ansible_release.__version__
) < packaging.version.Version("2.11.0"):
try:
# We import from ansible locally since importing it is rather slow
# pylint: disable-next=import-outside-toplevel
from ansible import release as ansible_release

if packaging.version.Version(
ansible_release.__version__
) < packaging.version.Version("2.11.0"):
return ()
return ("role",)
except ImportError:
return ()
return ("role",)


@cache
def get_ansible_release() -> tuple[str, str]:
"""
Retrieve current version and codename of Ansible.

:return: Tuple with version and codename
"""
if not HAS_ANSIBLE_RELEASE:
try:
# We import from ansible locally since importing it is rather slow
# pylint: disable-next=import-outside-toplevel
from ansible import release as ansible_release

return ansible_release.__version__, ansible_release.__codename__
except ImportError:
# pylint: disable-next=raise-missing-from
raise ValueError("Cannot import ansible.release")
felixfontein marked this conversation as resolved.
Show resolved Hide resolved
return ansible_release.__version__, ansible_release.__codename__
2 changes: 2 additions & 0 deletions src/antsibull_changelog/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
# SPDX-License-Identifier: GPL-3.0-or-later
# SPDX-FileCopyrightText: 2020, Ansible Project

# PYTHON_ARGCOMPLETE_OK

"""
Entrypoint to the antsibull-changelog script.
"""
Expand Down
27 changes: 12 additions & 15 deletions src/antsibull_changelog/rstcheck.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,6 @@
import pathlib
import tempfile

# rstcheck >= 6.0.0 depends on rstcheck-core
try:
import rstcheck_core.checker
import rstcheck_core.config

HAS_RSTCHECK_CORE = True
except ImportError:
HAS_RSTCHECK_CORE = False
import docutils.utils
import rstcheck


def check_rst_content(
content: str, filename: str | None = None
Expand All @@ -35,7 +24,12 @@ def check_rst_content(
The entries in the return list are tuples with line number, column number, and
error/warning message.
"""
if HAS_RSTCHECK_CORE:
# rstcheck >= 6.0.0 depends on rstcheck-core
try:
# We import from rstcheck_core locally since importing it is rather slow
import rstcheck_core.checker # pylint: disable=import-outside-toplevel
import rstcheck_core.config # pylint: disable=import-outside-toplevel

filename = os.path.basename(filename or "file.rst") or "file.rst"
with tempfile.TemporaryDirectory() as tempdir:
rst_path = os.path.join(tempdir, filename)
Expand All @@ -50,11 +44,14 @@ def check_rst_content(
return [
(result["line_number"], 0, result["message"]) for result in core_results
]
else:
results = rstcheck.check( # pylint: disable=no-member,used-before-assignment
except ImportError:
# We import from rstcheck_core locally since importing it is rather slow
import docutils.utils # pylint: disable=import-outside-toplevel
import rstcheck # pylint: disable=import-outside-toplevel

results = rstcheck.check( # pylint: disable=no-member
content,
filename=filename,
# pylint: disable-next=used-before-assignment
report_level=docutils.utils.Reporter.WARNING_LEVEL,
)
return [(result[0], 0, result[1]) for result in results]
17 changes: 7 additions & 10 deletions tests/functional/test_changelog_basic_collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from fixtures import collection_changelog # noqa: F401; pylint: disable=unused-variable
from fixtures import create_plugin

import antsibull_changelog.ansible # noqa: F401; pylint: disable=unused-variable
import antsibull_changelog.plugins # noqa: F401; pylint: disable=unused-variable
from antsibull_changelog import constants as C
from antsibull_changelog.config import TextFormat

Expand Down Expand Up @@ -1951,16 +1951,13 @@ def test_changelog_release_simple_no_galaxy( # pylint: disable=redefined-outer-
}


class FakeAnsibleRelease:
def __init__(self, version: str, codename: str):
self.__version__ = version
self.__codename__ = codename


@mock.patch("antsibull_changelog.ansible.HAS_ANSIBLE_RELEASE", True)
@mock.patch(
"antsibull_changelog.ansible.ansible_release",
FakeAnsibleRelease("2.11.0", "dummy codename"),
"antsibull_changelog.plugins.get_ansible_release",
lambda: ("2.11.0", "dummy codename"),
felixfontein marked this conversation as resolved.
Show resolved Hide resolved
)
@mock.patch(
"antsibull_changelog.plugins.get_documentable_objects",
lambda: ("role",),
felixfontein marked this conversation as resolved.
Show resolved Hide resolved
)
def test_changelog_release_plugin_cache( # pylint: disable=redefined-outer-name
collection_changelog,
Expand Down