Skip to content

Commit

Permalink
Make CLI startup faster, support tab completion with argcomplete (#173)
Browse files Browse the repository at this point in the history
* Import ansible only when needed.

* Import rstcheck only when needed.

* Add argcomplete marker so that completion actually works.

* Add changelog fragment.

* Fix tests.

* Improve changelog.

* Improve changelog fragment.

Co-authored-by: Maxwell G <maxwell@gtmx.me>

* Improve code.

Co-authored-by: Maxwell G <maxwell@gtmx.me>

* Remove no longer needed suppression.

* Fix test.

---------

Co-authored-by: Maxwell G <maxwell@gtmx.me>
  • Loading branch information
felixfontein and gotmax23 authored Aug 21, 2024
1 parent 2d34898 commit c15ad61
Show file tree
Hide file tree
Showing 5 changed files with 380 additions and 330 deletions.
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)."
91 changes: 45 additions & 46 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,65 @@
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:
raise ValueError("Cannot import ansible.release")
return ansible_release.__version__, ansible_release.__codename__
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 as exc:
raise ValueError("Cannot import ansible.release") from exc
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]
Loading

0 comments on commit c15ad61

Please sign in to comment.