This document contains detailed guidelines for contributing to the DevOps Demo project. It covers the development process, code standards, testing, code review process, and best practices for maintaining high code quality.
- Getting Started
- Linting and Testing
- Backend Linting
- Frontend Linting
- Infrastructure Linting
- Git Hooks Setup
- Code Style
- Testing
- CI/CD
- Reference
Install all necessary tools and set up the development environment. Detailed instructions are available in Local setup.
Toolchain versions are pinned in .mise.toml (the single source of truth);
verify your environment with:
make doctorAfter setting up the environment, you can start making changes to the code. Make sure you understand the project structure and follow established code standards.
Main directories:
services/backend/app/- Backend code (FastAPI)services/frontend/src/- Frontend code (React)services/backend/tests/- Backend testsservices/frontend/src/*.test.jsx- Frontend testsobservability/- Observability stack configuration
Commit messages follow Conventional Commits:
<type>(<scope>): <subject>, e.g. feat(backend): add item tags.
Common scopes here: backend, frontend, api, db, infra.
The PR title matters most: this is a squash-merge repository, so the PR
title becomes the commit message on main -- it is linted by CI and drives
automated releases (see CI/CD architecture). Branch commits are
work-in-progress and disappear at merge.
Why this workflow: engineering principles Section 4.
Create a Pull Request and fill in the template: why (with a link to the RFC/ADR/issue it serves) and, for non-trivial changes, the Change model section -- affected components, touched Hard rules, assumptions, plan. Reviewers read the model before the diff.
Review culture and the escalation path (write a proposal, don't argue) are described in engineering principles Section 4. Practical note: add new commits instead of amending and force-pushing -- it keeps review threads readable; everything is squashed at merge anyway.
All code quality checks run automatically via git hooks (prek) and can be run manually via Make commands.
# Run all linting checks
make lint
# Run all tests
make test
# Format code
make format
# Run pre-commit hooks manually
make pre-commit-runIt's recommended to run these commands before each commit to avoid failed CI/CD checks.
Python code is checked via [ruff] for linting and [mypy] for type checking.
Linting check:
# Check code for errors
cd services/backend
.venv/bin/ruff check .
# Automatically fix possible errors
.venv/bin/ruff check . --fix
# Check only specific file
.venv/bin/ruff check app/main.pyCode formatting:
# Format code
cd services/backend
.venv/bin/ruff format .
# Check formatting without changes
.venv/bin/ruff format --check .Via Make:
make lint-backend # Linting check
make format-backend # Code formattingRuff configuration:
- Configuration is located in
services/backend/.ruff.toml - Ruff automatically ignores
.venv/,__pycache__/, andalembic/versions/directories - Maximum line length: 160 characters
- Use double quotes for strings
Type checking:
cd services/backend
.venv/bin/mypy appVia Make:
make type-checkMypy configuration:
- Configuration is located in
services/backend/pyproject.tomlin[tool.mypy]section - Mypy checks only code in
app/directory - Strict mode is used for better type checking
Common errors and solutions:
Missing type annotation- Add type hints to functions and variablesIncompatible types- Check argument and return value typesUnused "type: ignore" comment- Remove unnecessary ignore comments
Frontend code is checked via [ESLint] for linting and [Prettier] for formatting.
Linting check:
cd services/frontend
npm run lint
# Automatically fix possible errors
npm run lint:fixVia Make:
make lint-frontendESLint configuration:
- Configuration is located in
services/frontend/eslint.config.js - Recommended rules for React are used
- Additional rules for better code quality
Code formatting:
cd services/frontend
npm run format
# Check formatting without changes
npm run format:checkVia Make:
make format-frontendPrettier configuration:
- Configuration is located in
services/frontend/.prettierrc.json - Prettier is integrated with ESLint via
eslint-config-prettier
Infrastructure files (YAML, Dockerfiles, GitHub Actions workflows) are checked via [yamllint] and [hadolint].
# Check all infrastructure files
make lint-infraThis command checks:
- YAML files (docker-compose, observability configs)
- GitHub Actions workflows (
.github/workflows/*.yml) - Renovate configuration (
.github/renovate.json5) - Docker Compose validation
- Dockerfiles via hadolint
Individual checks:
YAML linting:
# Install yamllint if not already installed
pip install yamllint
# Check specific file
yamllint -c .yamllint.yml deploy/compose/docker-compose.ymlDocker Compose validation:
# Check deploy/compose/docker-compose.yml syntax
docker compose -f deploy/compose/docker-compose.yml --project-directory . config --quietDockerfile linting:
# Install hadolint if not already installed
# macOS: brew install hadolint
# Linux: https://github.com/hadolint/hadolint#install
# Check Dockerfile
hadolint --config .hadolint.yaml services/backend/DockerfileGit hooks run linting and formatting checks before each commit. The runner is prek (single binary, pre-commit compatible); classic pre-commit works as a fallback with the same config. CI runs the identical tool and config -- hooks cannot drift between local and CI (see CI/CD architecture).
# Install git hooks (uses prek when available)
make pre-commit-install# Run on all files
make pre-commit-runThe authoritative list is .pre-commit-config.yaml -- a list copied here would only drift. Highlights: whitespace/format fixers, YAML/JSON/TOML checks, compose validation, hadolint, markdownlint, ASCII-only docs linter, shellcheck/shfmt, gitleaks secret scanning, and the toolchain drift gate.
Hook versions are dependencies: Renovate updates them automatically
(grouped as git-hooks). Manual bump if needed:
make pre-commit-updateMain principles:
- Follow PEP 8 style guide
- Use type hints where possible
- Maximum line length: 160 characters (configured in ruff)
- Use double quotes for strings
- Follow naming conventions (snake_case for functions and variables, PascalCase for classes)
Examples:
# Good
def get_user_by_id(user_id: int) -> User | None:
"""Get user by ID."""
return db.query(User).filter(User.id == user_id).first()
# Bad
def getUser(id):
return db.query(User).filter(User.id==id).first()Type hints:
# Required type hints for functions
async def create_item(item_data: ItemCreate, db: AsyncSession) -> Item:
"""Create new item."""
db_item = Item(**item_data.model_dump())
db.add(db_item)
await db.commit()
await db.refresh(db_item)
return db_itemDocstrings:
def complex_function(param1: str, param2: int) -> bool:
"""
Short function description.
Detailed description of what the function does and what parameters it takes.
Args:
param1: Parameter 1 description
param2: Parameter 2 description
Returns:
Return value description
Raises:
ValueError: When parameters are invalid
"""
passMain principles:
- Follow ESLint recommended rules
- Use functional components with hooks
- Prefer named exports
- Use Prettier for formatting
- Follow naming conventions (camelCase for functions and variables, PascalCase for components)
Examples:
// Good
import { useState, useEffect } from 'react';
export function ItemList() {
const [items, setItems] = useState([]);
useEffect(() => {
fetchItems().then(setItems);
}, []);
return <div>{/* ... */}</div>;
}
// Bad
import React from 'react';
export default class ItemList extends React.Component {
// ...
}Hooks:
// Use hooks instead of class components
function MyComponent() {
const [state, setState] = useState(initialValue);
useEffect(() => {
// Side effects
return () => {
// Cleanup
};
}, [dependencies]);
const handleClick = useCallback(() => {
// Event handler
}, [dependencies]);
return <div onClick={handleClick}>Content</div>;
}Main principles:
- Use 2 spaces for indentation
- Follow yamllint configuration
- Use quotes for values containing special characters
- Add comments to explain complex configurations
Examples:
# Good
services:
api:
image: python:3.12
environment:
- DATABASE_URL=postgresql://localhost/db
ports:
- "8000:8000"
# Bad
services:
api:
image:python:3.12
environment:-DATABASE_URL=postgresql://localhost/dbBackend tests:
- Place tests in
services/backend/tests/directory - Use pytest fixtures for test environment setup
- Tests should be independent and not depend on execution order
- Use descriptive test names
- Tests should clean up after themselves
Frontend tests:
- Place tests next to components (
.test.jsxfiles) - Use React Testing Library for component testing
- Test behavior, not implementation
- Use user-event for simulating user interaction
Examples:
Backend:
async def test_create_item(db: AsyncSession):
"""Test creating new item."""
item_data = ItemCreate(name="Test Item")
item = await create_item(item_data, db)
assert item.id is not None
assert item.name == "Test Item"Frontend:
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { App } from './App';
test('creates new item', async () => {
render(<App />);
const input = screen.getByLabelText(/item name/i);
await userEvent.type(input, 'New Item');
const button = screen.getByRole('button', { name: /create/i });
await userEvent.click(button);
expect(screen.getByText('New Item')).toBeInTheDocument();
});- All new features should include tests
- Tests should be independent and not depend on execution order
- Use descriptive test names
- Tests should clean up after themselves (don't leave "garbage" in database)
- Code coverage should be sufficient for critical functionality
Detailed information about running tests is available in Running tests.
Pipeline topology, gates, releases, and branch protection are documented in
CI/CD architecture. The contract that matters while contributing:
make ci runs locally exactly what the pipeline runs -- green locally
means green in CI.
- pre-commit - Pre-commit hooks configuration
- ruff - Ruff configuration
- mypy - Mypy configuration
- ESLint - ESLint configuration
- Prettier - Prettier configuration
- yamllint - yamllint configuration
- hadolint - hadolint configuration
- Conventional Commits - Conventional Commits specification
- pre-commit - pre-commit documentation
- Ruff - Ruff documentation
- Mypy - Mypy documentation
- ESLint - ESLint documentation
- Prettier - Prettier documentation
- yamllint - yamllint documentation
- hadolint - hadolint documentation
- Local setup - Local environment setup
- Running tests - Running tests
- Prerequisites - Required knowledge and skills