Ops is the official Python framework for developing Kubernetes and machine charms within the Juju ecosystem. It provides an event-driven abstraction layer for charm developers.
This workspace is a monorepo containing:
ops/- Core framework providing the event system, charm base classes, and model abstractions. Note that the tests are in a top-level foldertesttesting/- Theops-scenariostate transition testing framework (accessed asops.testing). This uses src-layout.tracing/- Theops-tracingobservability integration. The tests are in a subfolder calledtest.docs/- Common documentation, predominantly Markdown.
This is a mature, production framework used by thousands of charms. Changes require careful consideration of backward compatibility and testing.
| File | Purpose |
|---|---|
ops/charm.py |
CharmBase, event types, metadata parsing |
ops/framework.py |
Core event system, Handle, Object, Framework |
ops/model.py |
Juju model abstractions |
ops/pebble.py |
Pebble API for container workload management |
ops/__init__.py |
Public API exports |
CONTRIBUTING.md |
Contribution process and development setup |
- Always preserve backward compatibility in public APIs
- Document all breaking changes in commit messages
- Always preserve existing behavior unless fixing a bug
- Tests for
ops/main.pygo intest/test_main.py - Large test suites may get their own file:
test/test_main_foo.py - Scenario tests have a different structure - follow existing patterns
- Read existing code before proposing changes - use Read tool to understand patterns
- Check tests - examine
test/for similar test patterns - Verify changes - execute
toxafter changes to ensure linting and unit tests pass
- Follow conventions in our Python and docs style guides: https://raw.githubusercontent.com/canonical/charm-tech/refs/heads/main/style/python.md and https://raw.githubusercontent.com/canonical/charm-tech/refs/heads/main/style/docs.md
- Use Ruff for formatting (
tox -e format) - Python 3.10+ with full type hints required (check with
tox -e lint) - Use modern
x: int | Noneannotations, not old-stylex: Optional[int] - Always provide a return type, other than for
__init__and in test code
# DO: Import modules, not objects (except typing)
import ops
import subprocess
from typing import Generator # typing is an exception
class MyCharm(ops.CharmBase):
def handler(self, event: ops.PebbleReadyEvent):
subprocess.run(['echo', 'hello'])
# DON'T: Import objects directly
from ops import CharmBase, PebbleReadyEvent # Avoid thisFollow the Diátaxis framework for documentation structure:
- Tutorials (
docs/tutorial/) - Learning-oriented - How-to guides (
docs/howto/) - Task-oriented - Reference - Generated from docstrings
- Explanation (
docs/explanation/) - Understanding-oriented
Comments are always full sentences that end with punctuation. Avoid using comments to explain what the code is doing, use them (sparingly, as required) to explain why the code is doing what it is doing.
Use Google-style docstrings for all public APIs, with proper formatting. The text is used to generate reference documentation with Sphinx so must be appropriate ReST.
def my_function(param: str, count: int = 1) -> list[str]:
"""Brief one-line summary.
Longer description providing more context. Focus on what the function
does for users, not implementation details.
Args:
param: Description of the parameter.
count: Number of times to repeat. Defaults to 1.
Returns:
A list of processed strings.
Raises:
ValueError: If count is negative.
Example:
>>> my_function("hello", 2)
['hello', 'hello']
"""Documentation writing tips:
- Use active voice: "Create a check" not "A check is created"
- Be objective: Avoid "simply", "easily", "just"
- Be concise: No long introductions, get to the point
- Use short sentences and simple phrasing
- Be consistent with choice of words
- Avoid words or phrases specific to US or UK English where possible, and use British English otherwise
- State conditions positively: What should happen, not what shouldn't
- Spell out abbreviations and avoid Latin: "for example" not "e.g."
- Use sentence case for headings
Only document Juju version dependencies in docstrings:
def new_feature():
"""Do something new.
.. jujuadded:: 3.5
Further functionality was added in Juju 3.6.
"""Don't document Ops version changes in docstrings - that's in the changelog.
See CONTRIBUTING.md for more details.
Follow conventional commit style in PR titles:
feat:- New featurefix:- Bug fixdocs:- Documentation changesrefactor:- Code refactoringtest:- Test additions or updateschore:- Maintenance tasksci:- CI/CD changes
Examples:
feat: add support for Pebble noticesfix: correct type hints for ConfigDatadocs: clarify usage of Container.push
The project does not use conventional commit "scopes".
- Add tests for any new functionality
- Update docstrings for any API changes
- Ensure backward compatibility
- Run
tox -e formatto format code - Run
tox -e lintto check linting and types - Run
tox -e unitto verify unit tests pass - avoid drops in coverage (tox -e coverage) - Run
make htmlin thedocsfolder to ensure that the documentation can be generated - Search the explanation, how-to, and tutorial documentation in the
docsfolder for topics related to the changes, then suggest places that might need expanding/altering - If a virtual environment or sandbox is available, run
tox -e pebbleandtox -e integration