Skip to content

Commit

Permalink
docs: add project guidelines (#7)
Browse files Browse the repository at this point in the history
### Summary of Changes

Add guidelines for documentation, tests, and code style.
  • Loading branch information
lars-reimann authored Nov 29, 2023
1 parent 6f7c5dd commit 7941db9
Show file tree
Hide file tree
Showing 3 changed files with 248 additions and 2 deletions.
2 changes: 1 addition & 1 deletion docs/development/environment.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ This document describes how to configure and use your development environment.

You must complete these steps once before you can start setting up the project itself:

1. Install [Python 3.10](https://www.python.org/downloads/).
1. Install [Python 3.11](https://www.python.org/downloads/).
2. Verify that `python` can be launched by running this command in a **new** terminal:
```shell
python --version
Expand Down
245 changes: 245 additions & 0 deletions docs/development/project_guidelines.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@
# Project Guidelines

This document describes general guidelines for the Safe-DS Runner.

## Docstrings

The docstrings **should** use the [numpydoc](https://numpydoc.readthedocs.io/en/latest/format.html) format. The
descriptions **should not** start with "this" and **should** use imperative mood. Refer to the subsections below for
more details on how to document specific API elements.

!!! success "**DO**:"

```py
def add_ints(a: int, b: int) -> int:
"""Add two integers."""
return a + b
```

!!! failure "**DON'T**:"

```py
def add_ints(a: int, b: int) -> int:
"""This function adds two integers."""
return a + b
```

!!! failure "**DON'T**:"

```py
def add_ints(a: int, b: int) -> int:
"""Adds two integers."""
return a + b
```

!!! success "**DO**:"

```py
def __init__(self, data: Mapping[str, Any] | None = None) -> None:
"""
data : Mapping[str, Any] | None
"""
```

!!! failure "**DON'T**:"

```py
def __init__(self, data: Optional[Mapping[str, Any]] = None) -> None:
"""
data : Optional[Mapping[str, Any]]
"""
```

!!! failure "**DON'T**:"

```py
def __init__(self, data: Mapping[str, Any] | None = None) -> None:
"""
data : Optional[Mapping[str, Any]]
"""
```

### Modules

All modules should have

* a one-line description ([short summary][short-summary-section]),
* a longer description if needed ([extended summary][extended-summary-section]).

Example:

```py
"""Containers for tabular data."""
```

### Classes

All classes should have

* a one-line description ([short summary][short-summary-section]),
* a longer description if needed ([extended summary][extended-summary-section])
* a description of the parameters of their `__init__` method ([`Parameters` section][parameters-section]),
* examples that show how to use them correctly ([`Examples` section][examples-section]).

Example:

```py
"""
A row is a collection of named values.
Parameters
----------
data : Mapping[str, Any] | None
The data. If None, an empty row is created.
Examples
--------
>>> from safeds.data.tabular.containers import Row
>>> row = Row({"a": 1, "b": 2})
"""
```

### Functions

All functions should have

* a one-line description ([short summary][short-summary-section]),
* a longer description if needed ([extended summary][extended-summary-section])
* a description of their parameters ([`Parameters` section][parameters-section]),
* a description of their results ([`Returns` section][returns-section]),
* a description of any exceptions that may be raised and under which conditions that may
happen ([`Raises` section][raises-section]),
* a description of any warnings that may be issued and under which conditions that may
happen ([`Warns` section][warns-section]),
* examples that show how to use them correctly ([`Examples` section][examples-section]).

Example:

```py
"""
Return the value of a specified column.
Parameters
----------
column_name : str
The column name.
Returns
-------
value : Any
The column value.
Raises
------
UnknownColumnNameError
If the row does not contain the specified column.
Examples
--------
>>> from safeds.data.tabular.containers import Row
>>> row = Row({"a": 1, "b": 2})
>>> row.get_value("a")
1
"""
```

## Tests

We aim for 100% line coverage, so automated tests should be added for any new function.

### File structure

Tests belong in the [`tests`][tests-folder] folder. The file structure in the tests folder should mirror the file
structure of the [`src`][src-folder] folder.

### Naming

Names of test functions shall start with `test_should_` followed by a description of the expected behaviour,
e.g. `test_should_add_column`.

!!! success "**DO**:"

```py
def test_should_raise_if_less_than_or_equal_to_0(self, number_of_trees) -> None:
with pytest.raises(ValueError, match="The parameter 'number_of_trees' has to be greater than 0."):
...
```

!!! failure "**DON'T**:"

```py
def test_value_error(self, number_of_trees) -> None:
with pytest.raises(ValueError, match="The parameter 'number_of_trees' has to be greater than 0."):
...
```

### Parametrization

Tests should be parametrized using `@pytest.mark.parametrize`, even if there is only a single test case. This makes it
easier to add new test cases in the future. Test cases should be given descriptive IDs.

!!! success "**DO**:"

```py
@pytest.mark.parametrize("number_of_trees", [0, -1], ids=["zero", "negative"])
def test_should_raise_if_less_than_or_equal_to_0(self, number_of_trees) -> None:
with pytest.raises(ValueError, match="The parameter 'number_of_trees' has to be greater than 0."):
RandomForest(number_of_trees=number_of_trees)
```

!!! failure "**DON'T**:"

```py
def test_should_raise_if_less_than_0(self, number_of_trees) -> None:
with pytest.raises(ValueError, match="The parameter 'number_of_trees' has to be greater than 0."):
RandomForest(number_of_trees=-1)

def test_should_raise_if_equal_to_0(self, number_of_trees) -> None:
with pytest.raises(ValueError, match="The parameter 'number_of_trees' has to be greater than 0."):
RandomForest(number_of_trees=0)
```

## Code style

### Consistency

If there is more than one way to solve a particular task, check how it has been solved at other places in the codebase
and stick to that solution.

### Sort exported classes in `__init__.py`

Classes defined in a module that other classes shall be able to import must be defined in a list named `__all__` in the
module's `__init__.py` file. This list should be sorted alphabetically, to reduce the likelihood of merge conflicts when
adding new classes to it.

!!! success "**DO**:"

```py
__all__ = [
"Column",
"Row",
"Table",
"TaggedTable",
]
```

!!! failure "**DON'T**:"

```py
__all__ = [
"Table",
"TaggedTable",
"Column",
"Row",
]
```

[src-folder]: https://github.com/Safe-DS/Library/tree/main/src
[tests-folder]: https://github.com/Safe-DS/Library/tree/main/tests
[short-summary-section]: https://numpydoc.readthedocs.io/en/latest/format.html#short-summary
[extended-summary-section]: https://numpydoc.readthedocs.io/en/latest/format.html#extended-summary
[parameters-section]: https://numpydoc.readthedocs.io/en/latest/format.html#parameters
[returns-section]: https://numpydoc.readthedocs.io/en/latest/format.html#returns
[raises-section]: https://numpydoc.readthedocs.io/en/latest/format.html#raises
[warns-section]: https://numpydoc.readthedocs.io/en/latest/format.html#warns
[examples-section]: https://numpydoc.readthedocs.io/en/latest/format.html#examples
3 changes: 2 additions & 1 deletion mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ nav:
- Changelog: CHANGELOG.md
- Development:
- Environment: development/environment.md
- Contributing: https://github.com/Safe-DS/Runner/contribute
- Project Guidelines: development/project_guidelines.md
- Contributing 🌐: https://github.com/Safe-DS/Runner/contribute

# Configuration of MkDocs & Material for MkDocs --------------------------------

Expand Down

0 comments on commit 7941db9

Please sign in to comment.