Skip to content

Commit

Permalink
Check the entire code base for compliance with DOC String standard (#226
Browse files Browse the repository at this point in the history
)

* Update DOC String Complaince

* Fix coderabbit suggestions

* Update needs in compliance job

* Testing without compliance job

* Update some issues

* Fix some test issues

* Fix some test issues

* Fix test issues

* Fix package issue

* Intentional removing doc string

* Update check_docstrings.py

* Fix excluded dir

* Remove raises

* Fix black format

* Fix some issues

* Excluded tests

* Fix None parsing

* format black

* Fix some issues

* Add Args and returns for some files

* Add Args and returns to some files

* Fix linting issue

* Fix multiline DOC issue

* Fix linting issue

* Fix some files

* Fix linting

* Fix another file

* Fix linting

* Fix lint issue

* Fix interfaces DOC issue

* Fix missing descriptions issue

* Fix linting issue

* Fix some files

* Fix linting

* Fix some issues

* Fix linting issue

* Fix some issues

* Fix linting issue

* Fix device file

* Fix linting issue

* Reverted back device file

* Fix linting issue

* Add exlcude for device and snmp_manager file

* Fix some issues
  • Loading branch information
mandeepnh5 authored Jan 7, 2025
1 parent 05c4b46 commit e4b8cba
Show file tree
Hide file tree
Showing 32 changed files with 925 additions and 176 deletions.
35 changes: 35 additions & 0 deletions .github/workflows/pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -247,3 +247,38 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ github.event.pull_request.number }}
GITHUB_REPOSITORY: ${{ github.repository }}

Docstring-Compliance:
name: Check Docstring Compliance
runs-on: ubuntu-latest
needs: [Code-Quality-Checks]
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Set up Python 3.11
uses: actions/setup-python@v4
with:
python-version: 3.11

- name: Cache pip packages
uses: actions/cache@v4
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
restore-keys: |
${{ runner.os }}-pip-
- name: Install dependencies
run: |
python3 -m venv venv
source venv/bin/activate
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Run docstring compliance check
run: |
source venv/bin/activate
python .github/workflows/scripts/check_docstrings.py
184 changes: 184 additions & 0 deletions .github/workflows/scripts/check_docstrings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
import os
import re
import sys
from docstring_parser import parse


def validate_docstring(file_path):
"""
Validate docstrings in a file for compliance with the Google style guide.
Args:
file_path (str): Path to the Python file to validate.
Returns:
list: List of violations found in the file, with details about the issue and corrective action.
"""
print(f"Validating file: {file_path}")
violations = []

try:
with open(file_path, "r") as f:
lines = f.readlines()
except Exception as e:
print(f"Failed to read file {file_path}: {e}")
return violations

for i, line in enumerate(lines):
if re.match(r"^\s*def ", line):
func_name = line.strip()
print(f"Found function definition at line {i + 1}: {func_name}")

docstring_start = i + 1
while (
docstring_start < len(lines)
and lines[docstring_start].strip() == ""
):
docstring_start += 1

if docstring_start < len(lines) and lines[
docstring_start
].strip().startswith('"""'):
print(f"Docstring starts at line {docstring_start + 1}")
docstring_end = docstring_start + 1
while docstring_end < len(lines) and not lines[
docstring_end
].strip().endswith('"""'):
docstring_end += 1

if docstring_end < len(lines):
docstring = "\n".join(
lines[docstring_start : docstring_end + 1]
)
print(
f"Extracted docstring from lines {docstring_start + 1} to {docstring_end + 1}"
)

try:
parsed = parse(docstring)
print("Parsed docstring successfully")

# Check for Args section
if "Args:" not in docstring:
print("Missing 'Args' section")
violations.append(
{
"line": i + 1,
"function": func_name,
"issue": "Missing 'Args' section",
"action": "Add an 'Args' section listing the arguments this function accepts.",
}
)

# Check for Returns section
if "Returns:" not in docstring:
print("Missing 'Returns' section")
violations.append(
{
"line": i + 1,
"function": func_name,
"issue": "Missing 'Returns' section",
"action": "Add a 'Returns' section describing the return value.",
}
)

except Exception as e:
print(f"Error parsing docstring: {e}")
violations.append(
{
"line": i + 1,
"function": func_name,
"issue": "Docstring parsing error",
"action": f"Ensure the docstring is properly formatted: {e}",
}
)
else:
print("Docstring does not close properly")
violations.append(
{
"line": i + 1,
"function": func_name,
"issue": "Unclosed docstring",
"action": "Ensure the docstring is properly closed with triple quotes.",
}
)
else:
print("Missing docstring for function")
violations.append(
{
"line": i + 1,
"function": func_name,
"issue": "Missing docstring",
"action": "Add a Google-style docstring to describe this function.",
}
)

print(f"Found {len(violations)} violations in file: {file_path}")
return violations


def check_directory(directory, exclude_dirs=None):
"""
Check all Python files in a directory for docstring compliance, excluding specified directories.
Args:
directory (str): Directory to scan.
exclude_dirs (list): List of directories to exclude.
Returns:
dict: Dictionary of file violations.
"""
if exclude_dirs is None:
exclude_dirs = []

all_violations = {}
for root, dirs, files in os.walk(directory):
# Skip excluded directories
dirs[:] = [
d for d in dirs if os.path.join(root, d) not in exclude_dirs
]

for file in files:
if file.endswith(".py"):
file_path = os.path.join(root, file)
print(f"Processing file: {file_path}")
violations = validate_docstring(file_path)
if violations:
all_violations[file_path] = violations

print(f"Completed scanning directory: {directory}")
return all_violations


def main():
"""
Args:
None
Returns:
None
"""
directory = sys.argv[1] if len(sys.argv) > 1 else "."
# Define excluded directories (e.g., virtual environment or library folders)
exclude_dirs = [
os.path.join(directory, "venv"),
os.path.join(directory, "lib"),
os.path.join(directory, "venv/lib/python3.11/site-packages"),
os.path.join(directory, "tests"),
os.path.join(directory, "switchmap/poller/snmp"),
os.path.join(directory, "switchmap/server/db/ingest"),
os.path.join(directory, "switchmap/server/db/ingest/update"),
]
violations = check_directory(directory, exclude_dirs=exclude_dirs)
if violations:
print("Docstring violations found:")
for file, issues in violations.items():
for issue in issues:
print(
f"{file}:{issue['line']}: {issue['function']}: {issue['issue']}"
)
print(f" Corrective Action: {issue['action']}")
sys.exit(1)
else:
print("All docstrings are compliant.")


if __name__ == "__main__":
main()
3 changes: 3 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ graphql_server==3.0.0b5
mock
pytest

# Docstring Parser
docstring_parser

# Other
more-itertools

Expand Down
30 changes: 26 additions & 4 deletions switchmap/core/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,13 @@ def name(self):
return value

def query(self):
"""Create placeholder method. Do not delete."""
"""Create placeholder method. Do not delete.
Args:
None
Returns:
None
"""
# Do nothing
pass

Expand All @@ -102,7 +108,7 @@ def run(self):
Args:
None
Return:
Returns:
None
"""
Expand Down Expand Up @@ -386,6 +392,8 @@ def __init__(self, app, parent, options=None):
app: Flask application object of type Flask(__name__)
parent: Name of parent process that is invoking the API
options: Gunicorn CLI options
Returns:
None
"""
# Initialize key variables
Expand All @@ -395,7 +403,13 @@ def __init__(self, app, parent, options=None):
super(_StandaloneApplication, self).__init__()

def load_config(self):
"""Load the configuration."""
"""Load the configuration.
Args:
None
Returns:
None
"""
# Initialize key variables
now = datetime.now()
config = dict(
Expand All @@ -421,7 +435,15 @@ def load_config(self):
print(" {} = {}".format(name, value.get()))

def load(self):
"""Run the Flask application throught the Gunicorn WSGI."""
"""Run the Flask application throught the Gunicorn WSGI.
Args:
None
Returns:
self.application: Flask application object
"""
return self.application


Expand Down
18 changes: 12 additions & 6 deletions switchmap/core/daemon.py
Original file line number Diff line number Diff line change
Expand Up @@ -292,12 +292,18 @@ def status(self):
return bool(pid)

def run(self):
"""You should override this method when you subclass Daemon.
"""Override this method when you subclass Daemon.
It will be called after the process has been daemonized by
start() or restart().
This method will be called after the process has been daemonized by
start() or restart(). The base implementation does nothing and should
be overridden in derived classes to add actual daemon functionality.
Args:
None
Returns:
None
"""
# Simple comment to pass linter
pass


Expand Down Expand Up @@ -334,7 +340,7 @@ def __daemon_running(self):
Args:
None
Return:
Returns:
running: True if daemon is currently running or conducing a process
"""
Expand All @@ -351,7 +357,7 @@ def graceful_shutdown(self, callback):
Args:
callback: callback method
Return:
Returns:
wrapper
"""
Expand Down
22 changes: 16 additions & 6 deletions switchmap/dashboard/table/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,20 @@


class _RawCol(Col):
"""Class outputs whatever it is given and will not escape it."""
"""Class outputs whatever it is given and will not escape it.
Extends the Col class to provide raw HTML output without escaping.
"""

def td_format(self, content):
"""Fix the column formatting."""
"""Format the column content without escaping.
Args:
content: The content to be displayed in the column
Returns:
content: The unmodified content
"""
return content


Expand All @@ -41,11 +51,10 @@ def __init__(self, row_data):
"""Initialize the class.
Args:
row_data: Row data
row_data: List containing the data for each column in the row
Returns:
None
"""
# Initialize key variables
self.col0 = row_data[0]
Expand All @@ -59,12 +68,13 @@ def __init__(self, row_data):
def table(events):
"""Return data for the event's information.
Creates a formatted table object from a list of events.
Args:
events: List of EventMeta objects
Returns:
result: EventTable object
result: EventTable object or False if no valid rows
"""
# Initialize key variables
rows = []
Expand Down
Loading

0 comments on commit e4b8cba

Please sign in to comment.