Skip to content

Commit 8962b03

Browse files
Merge pull request #38 from pollen-robotics/37-pydoc-example
enhancement #37: generate doc with pdoc
2 parents 18bfaf0 + c21328e commit 8962b03

File tree

12 files changed

+262
-17
lines changed

12 files changed

+262
-17
lines changed

.github/workflows/docs.yml

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
name: Generate Python Documentation
2+
3+
on: [pull_request]
4+
5+
jobs:
6+
check-docstrings:
7+
runs-on: ubuntu-22.04
8+
steps:
9+
- uses: actions/checkout@v3
10+
- name: Set up Python 3.10
11+
uses: actions/setup-python@v4
12+
with:
13+
python-version: "3.10"
14+
cache: 'pip' # caching pip dependencies
15+
- name: Install dependencies
16+
run: |
17+
python -m pip install --upgrade pip
18+
pip install .[dev]
19+
- name : Check docstrings
20+
run : pydocstyle src/ --convention google --count
21+
22+
build-docs:
23+
runs-on: ubuntu-22.04
24+
steps:
25+
- name: Checkout Repository
26+
uses: actions/checkout@v3
27+
28+
- name: Set up Python 3.10
29+
uses: actions/setup-python@v4
30+
with:
31+
python-version: '3.10'
32+
33+
- name: Install Dependencies
34+
run: |
35+
python -m pip install --upgrade pip
36+
pip install .[dev]
37+
38+
- name: Generate Documentation
39+
run: pdoc example --output-dir docs
40+
41+
- name: Archive Documentation
42+
uses: actions/upload-artifact@v4
43+
with:
44+
name: documentation
45+
path: docs/

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,3 +163,8 @@ cython_debug/
163163
# and can be added to the global gitignore or merged into this file. For a more nuclear
164164
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
165165
#.idea/
166+
167+
# generate documentation
168+
docs/*.html
169+
docs/*.js
170+
docs/example/

README.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,4 +143,17 @@ Then, if all tests are sucessful:
143143
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.
144144

145145
Specific choices are detailed in dedicated document:
146-
- for the mathematical notation please refer to [Coding convention for Maths](docs/convention_maths.md)
146+
- for the mathematical notation please refer to [Coding convention for Maths](docs/convention_maths.md)
147+
148+
## Documentation
149+
150+
The documentation is generated with [pdoc](https://pdoc.dev), automatically with the CI. To generate it locally you can run:
151+
152+
```
153+
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
154+
```
155+
156+
The documentation relies on the provided docstings with the google style. [pydocstyle](http://www.pydocstyle.org/en/stable/) is used to enforced this style.
157+
```
158+
pydocstyle src/ --convention google --count
159+
```

scripts/git_hooks/pre-commit

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,6 @@ echo "-------> Flake8 passed!"
2424
mypy .
2525
echo "-------> Mypy passed!"
2626

27+
# Run pydocstring against all code in the `source_code` directory
28+
pydocstyle --convention google src/
29+
echo "-------> pydocstring passed!"

setup.cfg

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ dev = black==24.10.0
3232
coverage==7.3.2
3333
mypy==1.8.0
3434
isort==5.13.2
35+
pdoc==14.7.0
36+
pydocstyle==6.3.0
3537

3638
[options.entry_points]
3739
console_scripts =

src/config_files/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
"""config_files module.
2+
3+
Contains example of config files that are accessible by the modules.
4+
"""

src/example/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
"""Example module.
2+
3+
Illustrates various use cases.
4+
"""

src/example/cam_config.py

Lines changed: 51 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,50 @@
1+
"""Camera Configuration Management.
2+
3+
This module provides classes and functions for managing camera configuration data stored in JSON
4+
files. It includes a class for reading and displaying camera configuration data, as well as
5+
functions for retrieving the names of available configuration files and their paths.
6+
"""
7+
18
import json
29
from importlib.resources import files
310
from typing import Any, List
411

512

613
class CamConfig:
7-
def __init__(
8-
self,
9-
cam_config_json: str,
10-
) -> None:
14+
"""A class to manage camera configuration data from a JSON file.
15+
16+
This class reads a JSON file containing camera configuration data and provides methods to
17+
access and display the information.
18+
19+
Attributes:
20+
cam_config_json (str): The path to the JSON file containing the camera configuration data.
21+
socket_to_name (dict): A dictionary mapping socket IDs to camera names.
22+
inverted (bool): A boolean indicating whether the camera is inverted.
23+
fisheye (bool): A boolean indicating whether the camera is a fisheye camera.
24+
mono (bool): A boolean indicating whether the camera is a monochrome camera.
25+
"""
26+
27+
def __init__(self, cam_config_json: str) -> None:
28+
"""Initialize the camera configuration data from the given JSON file.
29+
30+
Args:
31+
cam_config_json (str): The path to the JSON file containing the camera configuration data.
32+
"""
1133
self.cam_config_json = cam_config_json
1234

13-
config = json.load(open(self.cam_config_json, "rb"))
14-
self.socket_to_name = config["socket_to_name"]
15-
self.inverted = config["inverted"]
16-
self.fisheye = config["fisheye"]
17-
self.mono = config["mono"]
35+
with open(self.cam_config_json, "rb") as f:
36+
config = json.load(f)
37+
self.socket_to_name = config["socket_to_name"]
38+
self.inverted = config["inverted"]
39+
self.fisheye = config["fisheye"]
40+
self.mono = config["mono"]
1841

1942
def to_string(self) -> str:
43+
"""Return a string representation of the camera configuration data.
44+
45+
Returns:
46+
str: A string containing the camera configuration data in a human-readable format.
47+
"""
2048
ret_string = "Camera Config: \n"
2149
ret_string += "Inverted: {}\n".format(self.inverted)
2250
ret_string += "Fisheye: {}\n".format(self.fisheye)
@@ -26,11 +54,25 @@ def to_string(self) -> str:
2654

2755

2856
def get_config_files_names() -> List[str]:
57+
"""Return a list of the names of the JSON configuration files in the config_files package.
58+
59+
Returns:
60+
List[str]: A list of the names of the JSON configuration files in the config_files package.
61+
"""
2962
path = files("config_files")
3063
return [file.stem for file in path.glob("**/*.json")] # type: ignore[attr-defined]
3164

3265

3366
def get_config_file_path(name: str) -> Any:
67+
"""Return the path to the JSON configuration file with the given name in the config_files package.
68+
69+
Args:
70+
name (str): The name of the JSON configuration file.
71+
72+
Returns:
73+
Any: The path to the JSON configuration file with the given name in the config_files package.
74+
If the file is not found, returns None.
75+
"""
3476
path = files("config_files")
3577
for file in path.glob("**/*"): # type: ignore[attr-defined]
3678
if file.stem == name:

src/example/celcius.py

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,81 @@
1+
"""Celsius Temperature Conversion and Management.
2+
3+
This module provides a class for managing Celsius temperatures and converting them to Fahrenheit.
4+
It also includes a main function that demonstrates the usage of the class.
5+
"""
6+
17
import logging
28

39

410
class Celsius:
5-
"""Manage celcius temperature and other format."""
11+
"""A class to manage Celsius temperature and convert it to other formats.
12+
13+
This class provides a way to store and manipulate Celsius temperatures, as well as convert
14+
them to Fahrenheit. It also includes a check to ensure that the temperature is not below
15+
absolute zero (-273.15°C).
16+
17+
Attributes:
18+
_temperature (float): The current temperature in Celsius.
19+
"""
620

721
def __init__(self, temperature: float = 0):
22+
"""Initialize the logger and the temperature attribute.
23+
24+
Args:
25+
temperature (float, optional): The initial temperature in Celsius. Defaults to 0.
26+
"""
827
self._logger = logging.getLogger(__name__)
928
self._temperature = temperature
1029

1130
def to_fahrenheit(self) -> float:
31+
"""Convert the current temperature from Celsius to Fahrenheit.
32+
33+
Returns:
34+
float: The temperature in Fahrenheit.
35+
"""
1236
return (self._temperature * 1.8) + 32
1337

1438
@property
1539
def temperature(self) -> float:
40+
"""A property decorator that allows access to the temperature attribute.
41+
42+
This property decorator provides a way to access the temperature attribute from outside
43+
the class. It also logs a message indicating that the value is being retrieved.
44+
45+
Returns:
46+
float: The current temperature in Celsius.
47+
"""
1648
self._logger.info("Getting value...")
1749
return self._temperature
1850

1951
@temperature.setter
2052
def temperature(self, value: float) -> None:
53+
"""A setter for the temperature property.
54+
55+
This method allows the value of the temperature attribute to be changed from outside the
56+
class. It also logs a message indicating that the value is being set and checks that the
57+
temperature is not below absolute zero.
58+
59+
Args:
60+
value (float): The new temperature in Celsius.
61+
62+
Raises:
63+
ValueError: If the temperature is below -273.15°C.
64+
"""
2165
self._logger.info("Setting value...")
2266
if value < -273.15:
2367
raise ValueError("Temperature below -273 is not possible")
2468
self._temperature = value
2569

2670

2771
def main() -> None:
72+
"""The main function that demonstrates the usage of the Celsius class.
73+
74+
This function creates an instance of the Celsius class, sets its temperature, and prints
75+
the equivalent temperature in Fahrenheit. It also activates logging at the INFO level.
76+
"""
77+
logging.basicConfig(level=logging.INFO)
78+
2879
print("Test entry point")
2980
temp = Celsius(37)
3081
temp.temperature = -30

src/example/foo.py

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,43 @@
55

66

77
class Foo:
8-
"""This is a template class"""
8+
"""This is a template class."""
99

1010
def __init__(self) -> None:
11-
"""Set up empty slots."""
11+
"""Set up empty slots and initialize the logger and private variable.
12+
13+
This method is called when an object of the class is created. It sets up the logger and
14+
initializes the private variable.
15+
"""
1216
self._logger = logging.getLogger(__name__)
1317
self._logger.info("Constructor")
1418
self._private_variable = "private"
1519
self.public_variable = "public"
1620

1721
@property
1822
def private_variable(self) -> str:
23+
"""A property decorator that allows access to the private variable.
24+
25+
This property decorator provides a way to access the private variable from outside
26+
the class. It returns the value of the private variable.
27+
"""
1928
return self._private_variable
2029

2130
@private_variable.setter
2231
def private_variable(self, value: str) -> None:
32+
"""A setter for the private_variable property.
33+
34+
This method allows the value of the private variable to be changed from outside the
35+
class. It sets the value of the private variable to the provided argument.
36+
"""
2337
self._private_variable = value
2438

2539
def __del__(self) -> None:
40+
"""The destructor method called when the object is about to be destroyed.
41+
42+
This method is called when an object of the class is about to be destroyed. It logs a
43+
message indicating that the destructor has been called.
44+
"""
2645
self._logger.info("Destructor")
2746

2847
@overload
@@ -32,6 +51,12 @@ def doingstuffs(self, var: int, var2: float) -> None: ...
3251
def doingstuffs(self, var: int) -> None: ...
3352

3453
def doingstuffs(self, var: Any = None, var2: Any = None) -> None:
54+
"""An overloaded method that takes one or two arguments and logs their values and types.
55+
56+
This method demonstrates the use of overloading in Python. It takes one or two arguments
57+
and logs their values and types using the logger. If no arguments are provided, it does
58+
nothing.
59+
"""
3560
if var is not None:
3661
self._logger.info(f"{var} {type(var)} ")
3762
if var2 is not None:

0 commit comments

Comments
 (0)