Code Style
General
We base our code style on a modified version of the Google style guide for Python code. The key differences are:
-
Docstrings: The Numpy style guide applies here.
When writing docstrings for functions, use the imperative style, as per PEP-257. For example, write "Do X and Y" instead of "Does X and Y".
-
Overridden methods: If the documentation did not change from the base class (i.e. the base class' method's docstring still applies without modification), do not add a short docstring รก la "See base class". This lets automated tools pick up the full base class docstring instead, and is therefore more useful in IDEs etc.
-
Linting: Use pylint for static code analysis, and mypy for static type checking.
-
Formatting: Use ruff as code auto-formatter. The maximum line length is 79, as per PEP-8. This setting should be automatically picked up from the
pyproject.toml
file. The reason for the shorter line length is that it avoids wrapping and overflows in side-by-side split views (e.g. diffs) if there's also information displayed to the side of it (e.g. a tree view of the modified files).Be aware of the different line length of 72 for docstrings. We currently do not have a satisfactory solution to automatically apply or enforce this.
Note that, while you're encouraged to do so in general, it is not a hard requirement to break up long strings into smaller parts. Additionally, never break up strings that are presented to the user in e.g. log messages, as that makes it significantly harder to grep for them.
-
Typing: We do not make an exception for
typing
imports. Instead of writingfrom typing import SomeName
, useimport typing as t
and access typing related classes liket.TypedDict
.Use the new syntax and classes for typing introduced with Python 3.10.
- Instead of
t.Tuple
,t.List
etc. use the builtin classestuple
,list
etc. - For classes that are not builtin (e.g.
Iterable
),import collections.abc as cabc
and then use them likecabc.Iterable
. - Use PEP-604-style unions, e.g.
int | float
instead oft.Union[int, float]
. - Use
... | None
(withNone
always as the last union member) instead oft.Optional[...]
and always explicitly annotate whereNone
is possible.
- Instead of
-
Python style rules: For conflicting parts, the ruff code style wins. If you have set up
ruff
correctly, you don't need to worry about this though :) - When working with
dict
s, consider usingt.TypedDict
instead of a more genericdict[str, float|int|str]
-like annotation where possible, as the latter is much less precise (often requiring additionalassert
s orisinstance
checks to pass) and can grow unwieldy very quickly. - Prefer
t.NamedTuple
overcollections.namedtuple
, because the former uses a more convenientclass ...:
syntax and also supports type annotations.
Conventions
Imports
-
Always use
from x import y
orfrom x import y as z
when importing modules. The only exception is when you are importing a high-level package or module, such asimport fastapi
-
Given that we often have identical file names across our modules and submodules, adhering to the Google style guide can lead to naming conflicts during imports. To address this, we distinguish between the following two cases:
-
Importing a module from the current directory: In this case, we do not need to rename the module and can use it as is. For instance, if we are in
capellacollab.projects.toolsmodels
, we can simply import thecrud
andmodel
modules like this:from . import crud, model
. -
Importing a module from a different directory: In this scenario, we must add an
as xy
suffix to avoid naming conflicts with the first case. We follow this pattern:from capellacollab.extensions.<extension> import submodule as <extension>_<submodule>
For example, if we are in
capellacollab.sessions
and want to importcrud
fromcapellacollab.projects.toolsmodels
, we would do it like this:from capellacollab.projects.toolmodels import crud as toolmodels_crud
-
-
Only use relative imports up to one level above the current one. This means you should use
from . import y
for the current module andfrom .. import y as z
for one level above. For all other imports beyond this level, use the full path as described in 3.
Naming Conventions
- All SQLAlchemy models should have
Database
as a prefix, e.g.,DatabaseProject
orDatabaseUser
.