blob: 5e049447ab913dab26453f5a2083dcdcc5020c67 [file] [log] [blame] [view] [edit]
# Style
Google Python style, 80 column limit
Commit messages should have the module name or recipe prefix instead of
something like `fix:` or `[fix]`
- Yes: `[futures] Fix spelling error`
- No: `fix: Fix spelling error`
Recipe testing often requires `test_data` or `step_test_data` arguments in what
otherwise looks like production codeleave these arguments alone.
Setting properties on a `StepPresentation` modifies global data, so don't remove
apparent no-ops that just modify a `properties` member of an object.
Don't include comments on import lines.
The `RunSteps()` function in a recipe gets passed an `api` object of type
`recipe_api.RecipeScriptApi`. Any additional arguments relate to the
`PROPERTIES` global variable. It returns `RawResult | None`.
The `GenTests()` function in a recipe gets passed an `api` object of type
`recipe_test_api.RecipeTestApi` and it returns
`Iterator[recipe_test_api.TestData]`.
All recipes must define both `RunSteps()` and `GenTests()`.
Indentation in docstrings should be independent of the length of the variable
name.
- Yes:
Args:
very_long_variable_name: Description takes
multiple lines.
- No:
Args:
very_long_variable_name: Description takes
multiple lines.
Prefer the `dataclasses` module over the `attrs` module. Exceptions ok for when
using attrs features not in dataclasses.
Don't import individual classes or variables—always import modules, and never
use `from <module> import *`. Exceptions:
- from collections.abc import <type>
- from typing import <type>
- from PB.<module> import foo as foo_pb
All imports should be at top-level, even if they`re only used in GenTests()
When importing a module under `PB`, import `as <module>_pb`.
- Yes: from PB.recipe_engine import result as result_pb
- No: from PB.recipe_engine import result
# Module Dependencies
Aside from the Python standard library and third party packages, recipes don't
share code via imports. Instead, shared recipe code is defined in a recipe
module under the recipe_modules directory. A recipe can import a recipe module
by listing the module in its DEPS list. Then the module will be accessible via
the `api` object that's passed to RunSteps.
For example, a call to a `api.foo.bar()` function in a recipe's `RunSteps`
function calls the `bar` method of an object of the `recipe_api.RecipeApi`
subclass defined in `recipe_modules/foo/api.py`. Any recipe module that a
recipe calls must also be listed in the recipe's `DEPS` variable, e.g.
`DEPS = ["recipe_engine/foo"]`.
In a recipe module, dependencies are injected via the `self.m` object instead of
`api`, but it works the same way - `self.m.foo.bar()` calls the `foo` recipe
module's `bar` method. The dependencies of recipe module `foo` are listed in the
`DEPS` variable in `recipe_modules/foo/__init__.py`.
On the other hand, a call to `api.foo.bar()` in a `GenTests` function refers to
the `bar` method in `recipe_modules/foo/test_api.py`. `test_api.py` files
provide helper functions for mocking results of various recipe steps.
If necessary, modules themselves can be imported with lines like this:
`from RECIPE_MODULES.<repo-name>.<mod-name> import api as <mod-name>_api`.
Prefer this structure over other ways to import modules directly.
Types defined in a recipe module should be included in the
`recipe_api.RecipeApi` subclass. This makes it easier to reference these types
outside of the module for annotations. Do not do this for types not part of the
module's API. Example:
```
class FooData:
pass
class FooApi(recipe_api.RecipeApi):
FooData = FooData
def __init__(self, *args, **kwargs):
...
```
# Python Type Annotations
Use `from __future__ import annotations` in all files.
Use `list` and `dict` instead of `typing.List` and `typing.Dict`.
Function and method arguments should use generic types if possible, like
`Sequence` or `Mapping`.
Return values should use specific types, like `list` or `dict`.
When there are suitable types in both `typing` and `collections.abc`, use the
type from `collections.abc`.
Don't use `import typing` and then using `typing.TypeName` everywhere. Instead,
use `from typing import TypeName`. The same rule applies to `collections.abc`
imports.
Don't hide type annotation imports under `if typing.TYPE_CHECKING:`. Exception
for imports that begin with `RECIPE_MODULES` and the corresponding module is not
included in DEPS.
Make sure any `if TYPE_CHECKING:` lines that do exist have a
`# pragma: no cover` comment.
# Testing
After making a change, run `./recipes.py test train --stop` to check that
everything still works. If making many changes, wait to run this until the end.
Also run `shac fmt` to adjust any formatting, and run `shac check` to check for
lint issues.