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

added docstring checker test #11

Merged
merged 8 commits into from
Oct 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .github/workflows/call_check_docstrings.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
name: Call Check Docstrings
on:
workflow_dispatch:

jobs:
check-docstrings:
uses: catalystneuro/.github/.github/workflows/check_docstrings.yaml@docstring_tests
with:
python-version: '3.10'
repository: 'catalystneuro/roiextractors'
package-name: 'roiextractors'
53 changes: 53 additions & 0 deletions .github/workflows/check_docstrings.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
name: Check Docstrings
on:
workflow_call:
inputs:
python-version:
description: 'The version of Python to use for the workflow.'
default: '3.10'
required: false
type: string
repository:
description: 'The repository to check the docstrings for.'
required: True
type: string
package-name:
description: 'The name of the package to check the docstrings for.'
required: True
type: string

jobs:
run:
runs-on: ubuntu-latest
steps:
- name: Setup Conda
uses: s-weigand/setup-conda@v1

- name: Setup Python ${{ inputs.python-version }}
uses: actions/setup-python@v2
with:
python-version: ${{ inputs.python-version }}

- name: Global Setup
run: |
pip install -U pip
pip install pytest-xdist
git config --global user.email "CI@example.com"
git config --global user.name "CI Almighty"
pip install wheel==0.41.2 # needed for scanimage

- name: Checkout Repository to be tested
uses: actions/checkout@v2
with:
repository: ${{ inputs.repository }}

- name: Install package
run: pip install .

- name: Checkout Home Repository
uses: actions/checkout@v2
with:
repository: ${{ github.repository }}

- name: Run docstring check
run: pytest tests/test_docstrings.py --package=${{ inputs.package-name }}
Empty file added tests/__init__.py
Empty file.
13 changes: 13 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import pytest
from .traverse import traverse_package
from importlib import import_module

def pytest_addoption(parser):
parser.addoption("--package", action="store")

def pytest_generate_tests(metafunc):
package_name = metafunc.config.getoption("--package")
package = import_module(package_name)
objs = traverse_package(package, package_name)
if "obj" in metafunc.fixturenames:
metafunc.parametrize("obj", objs)
11 changes: 11 additions & 0 deletions tests/test_docstrings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import inspect
import pytest

def test_has_docstring(obj):
"""Check if an object has a docstring."""
doc = inspect.getdoc(obj)
if inspect.ismodule(obj):
msg = f"{obj.__name__} has no docstring."
else:
msg = f"{obj.__module__}.{obj.__qualname__} has no docstring."
assert doc is not None, msg
103 changes: 103 additions & 0 deletions tests/traverse.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
from types import ModuleType, FunctionType
from typing import List

def traverse_class(class_object: type, parent: str) -> List[FunctionType]:
"""Traverse the class dictionary and return the methods overridden by this module.

Parameters
----------
class_object : type
The class to traverse.
parent : str
The parent package name.

Returns
-------
class_methods : List[FunctionType]
A list of all methods defined in the given class.
"""
class_methods = []
for attribute_name, attribute_value in class_object.__dict__.items():
if isinstance(attribute_value, FunctionType) and attribute_value.__module__.startswith(parent):
if attribute_name.startswith("__") and attribute_name.endswith("__"):
continue
class_methods.append(attribute_value)
return class_methods


def traverse_module(module: ModuleType, parent: str) -> List:
"""Traverse the module and return all classes and functions defined along the way.

Parameters
----------
module : ModuleType
The module to traverse.
parent : str
The parent package name.

Returns
-------
local_classes_and_functions : List
A list of all classes and functions defined in the given module.
"""
local_classes_and_functions = []

for name in dir(module):
if name.startswith("__") and name.endswith("__"): # skip all magic methods
continue

object_ = getattr(module, name)

if isinstance(object_, type) and object_.__module__.startswith(parent): # class
class_object = object_
class_functions = traverse_class(class_object=class_object, parent=parent)
local_classes_and_functions.append(class_object)
local_classes_and_functions.extend(class_functions)

elif isinstance(object_, FunctionType) and object_.__module__.startswith(parent):
function = object_
local_classes_and_functions.append(function)

return local_classes_and_functions


def traverse_package(package: ModuleType, parent: str) -> List[ModuleType]:
"""Traverse the package and return all subpackages and modules defined along the way.

Parameters
----------
package : ModuleType
The package, subpackage, or module to traverse.
parent : str
The parent package name.

Returns
-------
local_packages_and_modules : List[ModuleType]
A list of all subpackages and modules defined in the given package.
"""
local_packages_and_modules = []

for name in dir(package):
if name.startswith("__") and name.endswith("__"): # skip all magic methods
continue

object_ = getattr(package, name)

if (
isinstance(object_, ModuleType)
and object_.__file__[-11:] == "__init__.py"
and object_.__package__.startswith(parent)
):
subpackage = object_
subpackage_members = traverse_package(package=subpackage, parent=parent)
local_packages_and_modules.append(subpackage)
local_packages_and_modules.extend(subpackage_members)

elif isinstance(object_, ModuleType) and object_.__package__.startswith(parent):
module = object_
module_members = traverse_module(module=module, parent=parent)
local_packages_and_modules.append(module)
local_packages_and_modules.extend(module_members)

return local_packages_and_modules