ops_tools

Tools to extend Ops charms.

Includes tools to:
  • Generate charmcraft.yaml from Python config and action classes.

class ops_tools.ActionDict[source]

Bases: TypedDict

additionalProperties: bool

Whether additional properties are allowed in the action parameters.

description: str
params: dict[str, Any]

A dictionary of parameters for the action.

required: list[str]

A list of required parameters for the action.

class ops_tools.OptionDict[source]

Bases: TypedDict

default: NotRequired[bool | int | float | str]
description: NotRequired[str]
type: str

The Juju option type.

ops_tools.action_to_juju_schema(cls: type[object]) dict[str, Any][source]

Translate the class to a dictionary suitable for charmcraft.yaml.

For example:

>>> import enum
>>> import pydantic
>>> import yaml
>>> class RunBackup(pydantic.BaseModel):
...     '''Backup the database.'''
...     class Compression(enum.Enum):
...         GZ = 'gzip'
...         BZ = 'bzip2'
...
...     filename: str = pydantic.Field(description='The name of the backup file.')
...     compression: Compression = pydantic.Field(
...         Compression.GZ,
...         description='The type of compression to use.',
...     )
>>> print(yaml.safe_dump(action_to_juju_schema(RunBackup)))
run-backup:
  additionalProperties: false
  description: Backup the database.
  params:
    compression:
      type: string
      default: gzip
      description: The type of compression to use.
      enum: [gzip, bzip2]
    filename:
      description: The name of the backup file.
      title: Filename
      type: string
  required:
  - filename

>>>

To adjust the YAML, provide a to_juju_schema method in the class. For example, to allow additional properties:

def to_juju_schema(cls, schema: dict[str, ActionDict]) -> dict[str, ActionDict]:
    schema['run-backup']['additionalProperties'] = True
    return schema
ops_tools.config_to_juju_schema(
cls: type[object],
) dict[str, dict[str, OptionDict]][source]

Translate the class to YAML suitable for charmcraft.yaml.

For example:

>>> import pydantic
>>> import yaml
>>> class MyConfig(pydantic.BaseModel):
...     my_bool: bool = pydantic.Field(default=False, description='A boolean value.')
...     my_float: float = pydantic.Field(
...         default=3.14, description='A floating point value.'
...     )
...     my_int: int = pydantic.Field(default=42, description='An integer value.')
...     my_str: str = pydantic.Field(default="foo", description='A string value.')
...     my_secret: ops.Secret | None = pydantic.Field(
...         default=None, description='A user secret.'
...     )
...     class Config:
...         arbitrary_types_allowed = True
>>> print(yaml.safe_dump(config_to_juju_schema(MyConfig)))
options:
  my-bool:
    default: false
    description: A boolean value.
    type: boolean
  my-float:
    default: 3.14
    description: A floating point value.
    type: float
  my-int:
    default: 42
    description: An integer value.
    type: int
  my-secret:
    description: A user secret.
    type: secret
  my-str:
    default: foo
    description: A string value.
    type: string

Options with a default value of None will not have a default key in the output. If the type of the option cannot be determined, it will be set to string. If there is a default value, but it is not one of the Juju option types, the str() representation of the value will be used.

To customise, define a to_juju_schema method in your class. For example:

@classmethod
def to_juju_schema(cls, schema: dict[str, OptionDict]) -> dict[str, OptionDict]:
    # Change the key names to upper-case.
    schema = {key.upper(): value for key, value in schema.items()}
    return schema