Skip to content

Improve docstring test legibility for junior devs #1750

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

Merged
100 changes: 72 additions & 28 deletions tests/unit/test_example_docstrings.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,46 +4,90 @@
import pkgutil
import arcade.examples


EXAMPLE_ROOT = "arcade.examples"


def test_docstrings():
def check_single_example_docstring(path: Path, name: str) -> None:
"""
Check each example for docstring.
Check each example for valid run instructions.
Read & check a single file for an appropriate docstring

A docstring should consist of the following:

1. A summary line explaining what it demonstrates per PEP-0257
(https://peps.python.org/pep-0257/#multi-line-docstrings)
2. If necessary, a further minimal explanation of how it will do so
3. A line specifying how this example can be as a module run, usually at
the end

Example::

\"\"\"
Show a timer on screen

If Python and Arcade are installed, this example can be run from the command line with:
python -m arcade.examples.sprite_rooms
\"\"\"

:param path: Path to the file
:param name: Name of module
"""
ignore_patterns = ["__", "perf_test", "text_loc"]

# Get all the modules in the examples directory
check_submodules(EXAMPLE_ROOT)
# Read the file & extract the docstring
code = ast.parse(path.read_text())
docstring = ast.get_docstring(code)

# Check subdirectories
for path in Path(arcade.examples.__path__[0]).iterdir():
if not path.is_dir():
continue
# print(f"Checking if example {name} has a run instruction..")
run_line = f"python -m {name}"
assert docstring is not None, f"{run_line} not in {name} docstring."
assert run_line in docstring, f"{run_line} not in {name} docstring."

if any(pattern in path.name for pattern in ignore_patterns):
continue

check_submodules(f"{EXAMPLE_ROOT}.{path.name}")
def check_submodules(parent_module_absolute_name: str) -> None:
"""
Check docstrings for all immediate child modules of the passed absolute name

It is important to understand that module names and file paths are different things:

* A module name is what Python sees the module's name as (``"arcade.color"``)
* A file path is the location on disk (``C:|Users\Reader\python_project\game.py``)

def check_submodules(module_path: str):
module = importlib.import_module(module_path)
for finder, name, is_pkg in pkgutil.iter_modules(module.__path__):
path = Path(finder.path) / f"{name}.py"
check_code_docstring(path, f"{module_path}.{name}")
:param parent_module_absolute_name: The absolute import name of the module to check.
"""
# Get the file system location of the named parent module
parent_module_info = importlib.import_module(parent_module_absolute_name)
parent_module_file_path = parent_module_info.__path__

# Check all modules nested immediately inside it on the file system
for finder, child_module_name, is_pkg in pkgutil.iter_modules(parent_module_file_path):

def check_code_docstring(path: Path, name: str):
child_module_file_path = Path(finder.path) / f"{child_module_name}.py"
child_module_absolute_name = f"{parent_module_absolute_name}.{child_module_name}"

check_single_example_docstring(child_module_file_path, child_module_absolute_name)


def test_docstrings():
"""
path: Path to the file
name: Name of module
Ensure each user-facing example has a docstring with correct run instructions.
"""
with open(path) as f:
code = ast.parse(f.read())
docstring = ast.get_docstring(code)
run_line = f"python -m {name}"
# print(f"Checking if example {name} has a run instruction..")
assert docstring is not None, f"{run_line} not in {name} docstring."
assert run_line in docstring, f"{run_line} not in {name} docstring."

# Check all immediate child python files in arcade.examples
check_submodules(EXAMPLE_ROOT)

# For each immediate child folder module in arcade.examples,
# check the immediate child python files for correct docstrings.
for folder_submodule_path in Path(arcade.examples.__path__[0]).iterdir():

# Skip file modules we already covered above outside the loop
if not folder_submodule_path.is_dir():
continue

folder_submodule_name = folder_submodule_path.name

# Skip anything which isn't a user-facing example
if any(pattern in folder_submodule_name for pattern in ("__", "perf_test", "text_loc")):
continue

# Check the grandchildren inside the child folder module
check_submodules(f"{EXAMPLE_ROOT}.{folder_submodule_name}")