From 4df50059ea6f028c621a2a2c06e8a6f91d82fcd8 Mon Sep 17 00:00:00 2001 From: Fabien Danieau Date: Thu, 10 Oct 2024 11:04:22 +0200 Subject: [PATCH 1/6] enhancement #37: generate doc with pdoc --- .github/workflows/docs.yml | 29 +++++++++++++++++++++++++++++ .gitignore | 5 +++++ README.md | 10 +++++++++- setup.cfg | 1 + 4 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/docs.yml diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..7e251cf --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,29 @@ +name: Generate Python Documentation + +on: [pull_request] + +jobs: + build-docs: + runs-on: ubuntu-22.04 + steps: + - name: Checkout Repository + uses: actions/checkout@v3 + + - name: Set up Python 3.10 + uses: actions/setup-python@v4 + with: + python-version: '3.10' + + - name: Install Dependencies + run: | + python -m pip install --upgrade pip + pip install .[dev] + + - name: Generate Documentation + run: pdoc example --output-dir docs + + - name: Archive Documentation + uses: actions/upload-artifact@v4 + with: + name: documentation + path: docs/ diff --git a/.gitignore b/.gitignore index 8f8c06a..66a6599 100644 --- a/.gitignore +++ b/.gitignore @@ -163,3 +163,8 @@ cython_debug/ # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ + +# generate documentation +docs/*.html +docs/*.js +docs/example/ \ No newline at end of file diff --git a/README.md b/README.md index 7c59e63..9df62b9 100644 --- a/README.md +++ b/README.md @@ -143,4 +143,12 @@ Then, if all tests are sucessful: The main guidelines for the coding style is defined by [PEP8](https://peps.python.org/pep-0008/). You can directly refer to the examples in the code. Specific choices are detailed in dedicated document: - - for the mathematical notation please refer to [Coding convention for Maths](docs/convention_maths.md) \ No newline at end of file + - for the mathematical notation please refer to [Coding convention for Maths](docs/convention_maths.md) + + ## Documentation + +The documentation is generated with [pdoc](https://pdoc.dev), automatically with the CI. To generate it locally you can run: + +``` + pdoc example --output-dir docs + ``` \ No newline at end of file diff --git a/setup.cfg b/setup.cfg index 83ef400..ed87620 100644 --- a/setup.cfg +++ b/setup.cfg @@ -32,6 +32,7 @@ dev = black==24.10.0 coverage==7.3.2 mypy==1.8.0 isort==5.13.2 + pdoc==14.7.0 [options.entry_points] console_scripts = From 143959ef4592a47477f996450ed070f454120e57 Mon Sep 17 00:00:00 2001 From: Fabien Danieau Date: Thu, 10 Oct 2024 11:13:37 +0200 Subject: [PATCH 2/6] enhancement #37: adding temp pollen logo --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9df62b9..6bf3529 100644 --- a/README.md +++ b/README.md @@ -150,5 +150,6 @@ Then, if all tests are sucessful: The documentation is generated with [pdoc](https://pdoc.dev), automatically with the CI. To generate it locally you can run: ``` - pdoc example --output-dir docs + pdoc example --output-dir docs --logo https://www.pollen-robotics.com/wp-content/themes/bambi-theme-main/assets/images/pollen_robotics_logo.webp --logo-link https://www.pollen-robotics.com + ``` \ No newline at end of file From 2bca5e227d11d3661eabd96126d6e66da4886a1d Mon Sep 17 00:00:00 2001 From: Fabien Danieau Date: Thu, 10 Oct 2024 11:44:42 +0200 Subject: [PATCH 3/6] documentation #37: add all documentation --- README.md | 6 +++- setup.cfg | 1 + src/config_files/__init__.py | 4 +++ src/example/__init__.py | 4 +++ src/example/cam_config.py | 60 ++++++++++++++++++++++++++++++------ src/example/celcius.py | 53 ++++++++++++++++++++++++++++++- src/example/foo.py | 29 +++++++++++++++-- src/example/xterrabot.py | 35 +++++++++++++++++++-- src/main.py | 24 ++++++++++++++- 9 files changed, 199 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 6bf3529..a808718 100644 --- a/README.md +++ b/README.md @@ -150,6 +150,10 @@ Then, if all tests are sucessful: The documentation is generated with [pdoc](https://pdoc.dev), automatically with the CI. To generate it locally you can run: ``` - pdoc example --output-dir docs --logo https://www.pollen-robotics.com/wp-content/themes/bambi-theme-main/assets/images/pollen_robotics_logo.webp --logo-link https://www.pollen-robotics.com + pdoc example --output-dir docs --logo https://www.pollen-robotics.com/wp-content/themes/bambi-theme-main/assets/images/pollen_robotics_logo.webp --logo-link https://www.pollen-robotics.com --docformat google + ``` + The documentation relies on the provided docstings with the google style. [pydocstyle](http://www.pydocstyle.org/en/stable/) is used to enforced this style. + ``` + pydocstyle src/ --convention google --count ``` \ No newline at end of file diff --git a/setup.cfg b/setup.cfg index ed87620..ad79521 100644 --- a/setup.cfg +++ b/setup.cfg @@ -33,6 +33,7 @@ dev = black==24.10.0 mypy==1.8.0 isort==5.13.2 pdoc==14.7.0 + pydocstyle==6.3.0 [options.entry_points] console_scripts = diff --git a/src/config_files/__init__.py b/src/config_files/__init__.py index e69de29..f286f3a 100644 --- a/src/config_files/__init__.py +++ b/src/config_files/__init__.py @@ -0,0 +1,4 @@ +"""config_files module. + +Contains example of config files that are accessible by the modules. +""" diff --git a/src/example/__init__.py b/src/example/__init__.py index e69de29..fce27db 100644 --- a/src/example/__init__.py +++ b/src/example/__init__.py @@ -0,0 +1,4 @@ +"""Example module. + +Illustrates various use cases. +""" diff --git a/src/example/cam_config.py b/src/example/cam_config.py index 1a6c849..d3caf8c 100644 --- a/src/example/cam_config.py +++ b/src/example/cam_config.py @@ -1,22 +1,50 @@ +"""Camera Configuration Management. + +This module provides classes and functions for managing camera configuration data stored in JSON +files. It includes a class for reading and displaying camera configuration data, as well as +functions for retrieving the names of available configuration files and their paths. +""" + import json from importlib.resources import files from typing import Any, List class CamConfig: - def __init__( - self, - cam_config_json: str, - ) -> None: + """A class to manage camera configuration data from a JSON file. + + This class reads a JSON file containing camera configuration data and provides methods to + access and display the information. + + Attributes: + cam_config_json (str): The path to the JSON file containing the camera configuration data. + socket_to_name (dict): A dictionary mapping socket IDs to camera names. + inverted (bool): A boolean indicating whether the camera is inverted. + fisheye (bool): A boolean indicating whether the camera is a fisheye camera. + mono (bool): A boolean indicating whether the camera is a monochrome camera. + """ + + def __init__(self, cam_config_json: str) -> None: + """Initialize the camera configuration data from the given JSON file. + + Args: + cam_config_json (str): The path to the JSON file containing the camera configuration data. + """ self.cam_config_json = cam_config_json - config = json.load(open(self.cam_config_json, "rb")) - self.socket_to_name = config["socket_to_name"] - self.inverted = config["inverted"] - self.fisheye = config["fisheye"] - self.mono = config["mono"] + with open(self.cam_config_json, "rb") as f: + config = json.load(f) + self.socket_to_name = config["socket_to_name"] + self.inverted = config["inverted"] + self.fisheye = config["fisheye"] + self.mono = config["mono"] def to_string(self) -> str: + """Return a string representation of the camera configuration data. + + Returns: + str: A string containing the camera configuration data in a human-readable format. + """ ret_string = "Camera Config: \n" ret_string += "Inverted: {}\n".format(self.inverted) ret_string += "Fisheye: {}\n".format(self.fisheye) @@ -26,11 +54,25 @@ def to_string(self) -> str: def get_config_files_names() -> List[str]: + """Return a list of the names of the JSON configuration files in the config_files package. + + Returns: + List[str]: A list of the names of the JSON configuration files in the config_files package. + """ path = files("config_files") return [file.stem for file in path.glob("**/*.json")] # type: ignore[attr-defined] def get_config_file_path(name: str) -> Any: + """Return the path to the JSON configuration file with the given name in the config_files package. + + Args: + name (str): The name of the JSON configuration file. + + Returns: + Any: The path to the JSON configuration file with the given name in the config_files package. + If the file is not found, returns None. + """ path = files("config_files") for file in path.glob("**/*"): # type: ignore[attr-defined] if file.stem == name: diff --git a/src/example/celcius.py b/src/example/celcius.py index 6d7862d..b7833cf 100644 --- a/src/example/celcius.py +++ b/src/example/celcius.py @@ -1,23 +1,67 @@ +"""Celsius Temperature Conversion and Management. + +This module provides a class for managing Celsius temperatures and converting them to Fahrenheit. +It also includes a main function that demonstrates the usage of the class. +""" + import logging class Celsius: - """Manage celcius temperature and other format.""" + """A class to manage Celsius temperature and convert it to other formats. + + This class provides a way to store and manipulate Celsius temperatures, as well as convert + them to Fahrenheit. It also includes a check to ensure that the temperature is not below + absolute zero (-273.15°C). + + Attributes: + _temperature (float): The current temperature in Celsius. + """ def __init__(self, temperature: float = 0): + """Initialize the logger and the temperature attribute. + + Args: + temperature (float, optional): The initial temperature in Celsius. Defaults to 0. + """ self._logger = logging.getLogger(__name__) self._temperature = temperature def to_fahrenheit(self) -> float: + """Convert the current temperature from Celsius to Fahrenheit. + + Returns: + float: The temperature in Fahrenheit. + """ return (self._temperature * 1.8) + 32 @property def temperature(self) -> float: + """A property decorator that allows access to the temperature attribute. + + This property decorator provides a way to access the temperature attribute from outside + the class. It also logs a message indicating that the value is being retrieved. + + Returns: + float: The current temperature in Celsius. + """ self._logger.info("Getting value...") return self._temperature @temperature.setter def temperature(self, value: float) -> None: + """A setter for the temperature property. + + This method allows the value of the temperature attribute to be changed from outside the + class. It also logs a message indicating that the value is being set and checks that the + temperature is not below absolute zero. + + Args: + value (float): The new temperature in Celsius. + + Raises: + ValueError: If the temperature is below -273.15°C. + """ self._logger.info("Setting value...") if value < -273.15: raise ValueError("Temperature below -273 is not possible") @@ -25,6 +69,13 @@ def temperature(self, value: float) -> None: def main() -> None: + """The main function that demonstrates the usage of the Celsius class. + + This function creates an instance of the Celsius class, sets its temperature, and prints + the equivalent temperature in Fahrenheit. It also activates logging at the INFO level. + """ + logging.basicConfig(level=logging.INFO) + print("Test entry point") temp = Celsius(37) temp.temperature = -30 diff --git a/src/example/foo.py b/src/example/foo.py index f28f859..3f11f1b 100644 --- a/src/example/foo.py +++ b/src/example/foo.py @@ -5,10 +5,14 @@ class Foo: - """This is a template class""" + """This is a template class.""" def __init__(self) -> None: - """Set up empty slots.""" + """Set up empty slots and initialize the logger and private variable. + + This method is called when an object of the class is created. It sets up the logger and + initializes the private variable. + """ self._logger = logging.getLogger(__name__) self._logger.info("Constructor") self._private_variable = "private" @@ -16,13 +20,28 @@ def __init__(self) -> None: @property def private_variable(self) -> str: + """A property decorator that allows access to the private variable. + + This property decorator provides a way to access the private variable from outside + the class. It returns the value of the private variable. + """ return self._private_variable @private_variable.setter def private_variable(self, value: str) -> None: + """A setter for the private_variable property. + + This method allows the value of the private variable to be changed from outside the + class. It sets the value of the private variable to the provided argument. + """ self._private_variable = value def __del__(self) -> None: + """The destructor method called when the object is about to be destroyed. + + This method is called when an object of the class is about to be destroyed. It logs a + message indicating that the destructor has been called. + """ self._logger.info("Destructor") @overload @@ -32,6 +51,12 @@ def doingstuffs(self, var: int, var2: float) -> None: ... def doingstuffs(self, var: int) -> None: ... def doingstuffs(self, var: Any = None, var2: Any = None) -> None: + """An overloaded method that takes one or two arguments and logs their values and types. + + This method demonstrates the use of overloading in Python. It takes one or two arguments + and logs their values and types using the logger. If no arguments are provided, it does + nothing. + """ if var is not None: self._logger.info(f"{var} {type(var)} ") if var2 is not None: diff --git a/src/example/xterrabot.py b/src/example/xterrabot.py index 681c33a..5ea31a9 100644 --- a/src/example/xterrabot.py +++ b/src/example/xterrabot.py @@ -1,3 +1,10 @@ +"""XTerraBot. + +Illustrate Maths notation based on + https://www.mecharithm.com/ + homogenous-transformation-matrices-configurations-in-robotics/. +""" + import logging import numpy as np @@ -5,11 +12,20 @@ class XTerraBot: - """Illustrate Maths notation based on - https://www.mecharithm.com/ - homogenous-transformation-matrices-configurations-in-robotics/.""" + """XTerraBot class. + + This class illustrates the use of homogeneous transformation matrices to represent the + configuration of a robot in the context of robotics. It demonstrates the calculation of + the transformation matrix of an object with respect to the gripper frame using matrix + multiplication and inversion. + """ def __init__(self) -> None: + """Constructor. + + Initialize the logger and the homogeneous transformation matrices representing the + configuration of the robot. + """ self._logger = logging.getLogger(__name__) # b is the mobile base # d is the camera @@ -27,6 +43,19 @@ def __init__(self) -> None: self._T_a_d = np.array([[0, 0, -1, 400], [0, -1, 0, 50], [-1, 0, 0, 300], [0, 0, 0, 1]]) # a is the root def get_object_in_gripper_frame(self) -> npt.NDArray[np.float64]: + """Get the object in the gripper frame. + + Calculate and return the homogeneous transformation matrix representing the + configuration of the object with respect to the gripper frame. + + This method calculates the transformation matrix (T_c_e) by performing matrix + multiplication and inversion on the given transformation matrices. It returns the + resulting matrix as a NumPy array. + + Returns: + np.ndarray: The homogeneous transformation matrix representing the configuration + of the object with respect to the gripper frame. + """ T_c_e = ( np.linalg.inv(self._T_b_c) @ np.linalg.inv(self._T_d_b) diff --git a/src/main.py b/src/main.py index 79042be..236f7b7 100644 --- a/src/main.py +++ b/src/main.py @@ -1,3 +1,12 @@ +"""Main script for the application. + +This script processes command-line arguments, performs various operations using classes and +methods from the `example` module, and uses data stored in configuration files. It logs +information about the provided arguments and demonstrates the usage of the application's +features. + +""" + import argparse import logging import sys @@ -8,8 +17,21 @@ from example.xterrabot import XTerraBot -# the main function could be called from somewhere else def main(args: argparse.Namespace) -> int: + """The main function that processes command-line arguments and performs various operations. + + This function logs information about the provided arguments, demonstrates the usage of + classes and methods from the example module, and uses data stored in configuration + files. It returns 0 to indicate successful execution. + + Args: + args (argparse.Namespace): The namespace containing the command-line arguments. + + Returns: + int: Exit code indicating the success (0) of the application. + + + """ logging.info("str param: {}".format(args.str_param)) logging.info("bool param: {}".format(args.bool_param)) logging.info("int param: {}".format(args.int_param)) From 3ba178766b44c44754154e34bd2b274f7bd75591 Mon Sep 17 00:00:00 2001 From: Fabien Danieau Date: Thu, 10 Oct 2024 11:50:13 +0200 Subject: [PATCH 4/6] enhancement #37: check docstring in CI --- .github/workflows/docs.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 7e251cf..457d7a6 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -3,6 +3,22 @@ name: Generate Python Documentation on: [pull_request] jobs: + check-docstrings: + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v3 + - name: Set up Python 3.10 + uses: actions/setup-python@v4 + with: + python-version: "3.10" + cache: 'pip' # caching pip dependencies + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install .[dev] + - name : Check import order + run : pydocstyle src/ --convention google --count + build-docs: runs-on: ubuntu-22.04 steps: From 420b074a1b820b0b99ee405a40acb3df358e8e61 Mon Sep 17 00:00:00 2001 From: Fabien Danieau Date: Thu, 10 Oct 2024 11:54:50 +0200 Subject: [PATCH 5/6] enhancement #37: update pre-commit. add mistake to check CI --- .github/workflows/docs.yml | 2 +- scripts/git_hooks/pre-commit | 3 +++ src/main.py | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 457d7a6..2e82b03 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -16,7 +16,7 @@ jobs: run: | python -m pip install --upgrade pip pip install .[dev] - - name : Check import order + - name : Check docstrings run : pydocstyle src/ --convention google --count build-docs: diff --git a/scripts/git_hooks/pre-commit b/scripts/git_hooks/pre-commit index cf115a9..c7ac1d5 100755 --- a/scripts/git_hooks/pre-commit +++ b/scripts/git_hooks/pre-commit @@ -24,3 +24,6 @@ echo "-------> Flake8 passed!" mypy . echo "-------> Mypy passed!" +# Run pydocstring against all code in the `source_code` directory +pydocstyle --convention google src/ +echo "-------> pydocstring passed!" \ No newline at end of file diff --git a/src/main.py b/src/main.py index 236f7b7..075f4b8 100644 --- a/src/main.py +++ b/src/main.py @@ -1,4 +1,4 @@ -"""Main script for the application. +"""Main script for the application This script processes command-line arguments, performs various operations using classes and methods from the `example` module, and uses data stored in configuration files. It logs From c21328e3e8a701d97d480427d13c8de0518d465e Mon Sep 17 00:00:00 2001 From: Fabien Danieau Date: Thu, 10 Oct 2024 11:57:19 +0200 Subject: [PATCH 6/6] enhancement #37: fix docstrings mistake --- src/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.py b/src/main.py index 075f4b8..236f7b7 100644 --- a/src/main.py +++ b/src/main.py @@ -1,4 +1,4 @@ -"""Main script for the application +"""Main script for the application. This script processes command-line arguments, performs various operations using classes and methods from the `example` module, and uses data stored in configuration files. It logs