- Branching Strategy
- Pull Requests
- Changelog Fragments
- Setting Up Your Development Environment
- Coding Standards
- Testing
- Getting Help
All development work should be done on feature branches derived from the main branch.
Branch names should follow the format SEP-XXX, taken from the SEP Jira Board.
Example:
git checkout main
git pull
git checkout -b SEP-123When your feature or fix is ready, open a Pull Request (PR) from your feature branch (SEP-XXX) to the main branch. Ensure the PR description includes a representative summary of your changes.
All PRs must be reviewed and approved by at least one of our CODEOWNERS.
If your PR contains a user-facing change (a new feature, a bug fix, a behaviour change, a security fix, or a configuration change), add a changelog fragment so the entry is picked up in the next release notes. Fragments live under changelog.d/ at the repo root — each PR writes its own file, which means parallel PRs never collide on CHANGELOG.md.
To create a fragment, use the Makefile helper:
make changelog-add TICKET=SEP-XXX SECTION=<section> MSG="Brief description"<section> is one of added, changed, breaking, config, fixed, or security. If your change belongs in multiple sections (e.g. it is both a Change and a Breaking Change), run the command once per section.
Skip this step when either applies:
- Purely internal changes — CI, refactoring, tooling, docs with no user-visible effect.
- Same-release-cycle fix — the PR fixes a regression or behaviour
change introduced by another ticket that shares this PR's Jira
Fix Versionand is itself still unreleased (no[vX.Y.Z]header for that Fix Version inCHANGELOG.md). Usually surfaces as a Jirais caused bylink to a sibling ticket in the same version. The bug never shipped to users, so a fragment would add confusing "regression fixed" noise to release notes describing behaviour users never saw.
See changelog.d/README.md § "TL;DR — adding an entry" for the canonical rule.
See changelog.d/README.md for the full format and examples. A pre-commit hook validates any fragments you touch, and make changelog-check / make changelog-list are available to inspect or preview them locally.
- Clone the repository
git clone https://github.com/percona/SEP.gitor
git clone git@github.com:percona/SEP.git- Install the dependencies
Navigate to the project directory, create a virtual environment and install the required packages:
cd SEP
make venv- Install pre-commit hooks
We use pre-commit hooks to maintain code quality. Install them by running:
pre-commit installThis ensures that your code adheres to our linting and formatting standards before each commit (see Coding Standards).
- Linting and Formatting:
We enforce code style guidelines using Ruff. The rules are defined in the pyproject.toml file.
- Type checking (opt-in):
Because the project is fully type-annotated, you can check the annotations locally with ty (Astral's type checker) by running make typecheck. It is not enforced in CI or pre-commit yet, so it is safe to run on demand and will currently report a backlog of existing diagnostics.
- Docstrings:
Use reStructuredText (rST) docstrings compatible with Sphinx for documentation. All public modules, classes, methods, and functions should include docstrings.
Because the project is fully type-annotated, the function signature (or model field annotation) is the source of truth for types. Use :param: / :return: to describe a parameter's or return value's meaning, and document every parameter with a :param: entry. Omit :type: / :rtype: — they are optional; add one only when the documented type should intentionally differ from the annotation (for example, an Any whose real contract is narrower). When a type directive is needed, :type: is for :param: only and :vartype: for class/instance variables.
Example:
class OAuthToken(BaseModel):
"""Represent an OAuth token.
:param access_token: The token used to access protected resources.
:param id_token: The token that contains identity information about the user.
:param refresh_token: The token used to obtain new access tokens after the
current one expires.
:param token_type: The type of token, typically "bearer".
:param expires_in: The time duration after which the token expires.
:param scope: The scope of the access granted by the token.
"""
access_token: str
id_token: str
refresh_token: str
token_type: str
expires_in: TimedeltaSeconds
scope: str- Type Annotations:
All functions and methods should include type annotations for parameters and return types.
Example:
def deep_dict_update(main_dict: dict[Any, Any], update_dict: dict[Any, Any]) -> None:
"""Merge `update_dict` into `main_dict` recursively.
Update `main_dict` with the contents of `update_dict` recursively. For each
key in `update_dict`, if the key exists in `main_dict` and both values are
dictionaries, merge them recursively. If the key exists in `main_dict` and
both values are lists, prepend the non-empty list from `update_dict` to the
list in `main_dict`, or replace with an empty list when the overlay is empty
so profiles can clear inherited list settings. Otherwise, overwrite the value
in `main_dict` with the value from `update_dict`.
:param main_dict: The dictionary to be updated.
:param update_dict: The dictionary containing updates to apply.
"""
for key, value in update_dict.items():
if (
key in main_dict
and isinstance(main_dict[key], dict)
and isinstance(value, dict)
):
deep_dict_update(main_dict[key], value)
elif (
key in main_dict
and isinstance(main_dict[key], list)
and isinstance(value, list)
):
main_dict[key] = value + main_dict[key] if value else []
else:
main_dict[key] = valueSEP apps (checksums, backups, snippets, and the rest under app/sep/apps/) are
built on the declarative app framework: you describe an app with a single
TaskExecutionApp object and the framework derives its whole HTTP surface. If
you are adding an app, start with the
App Developer Guide. It is the
zero-to-working-app path — mental model, the make startapp quickstart, the
form DSL, the escape hatches, and testing — with the examples sourced from real
apps.
New code usually means new tests. Make sure new features and bug fixes include corresponding tests. SEP v0.1.0-alpha has a test coverage of over 50%, and our goal is to continually increase this percentage.
Tests are located in the tests/ directory and mirror the structure of the app/ directory. For example:
- For app/models.py, the tests are in tests/test_models.py.
- For app/api/routes/oauth.py, the tests are in tests/api/routes/test_oauth.py.
As you can see, test filenames are prefixed with a test_.
Ensure all tests pass before pushing your code:
make testMutation testing (mutmut) is an optional local spike tool, not part of
make test, pre-commit, or CI. Configuration for the pilot scoped to
app/core/db/utils.py lives under [tool.mutmut] in pyproject.toml. Run it
from the repo root; it writes a gitignored mutants/ working copy:
poetry run mutmut run # generate and test mutants
poetry run mutmut results # list surviving mutants
poetry run mutmut show <mutant> # diff for a single mutantOn macOS, export the Homebrew library path first. mutmut invokes pytest
directly, so it bypasses the wrapper that make test uses to make WeasyPrint's
native libraries resolvable:
export DYLD_FALLBACK_LIBRARY_PATH="$(brew --prefix)/lib:${DYLD_FALLBACK_LIBRARY_PATH}"If you have any questions or need assistance, feel free to reach out:
- Open an issue on GitHub or on Jira.
- Contact one of the CODEOWNERS or anyone from the GAS team.