Merge pull request #3 from javacruft/add-type-hints
[chore] Add docstrings and typing to core module.
This commit is contained in:
@@ -26,6 +26,10 @@ import ops.charm
|
||||
import ops.framework
|
||||
import ops.model
|
||||
|
||||
from collections.abc import Callable
|
||||
from typing import List, Tuple
|
||||
from ops_openstack.adapters import OpenStackOperRelationAdapter
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -35,12 +39,16 @@ ContainerConfigFile = collections.namedtuple(
|
||||
|
||||
|
||||
class PebbleHandler(ops.charm.Object):
|
||||
"""Base handler for Pebble based containers."""
|
||||
|
||||
_state = ops.framework.StoredState()
|
||||
|
||||
def __init__(self, charm, container_name, service_name,
|
||||
container_configs, template_dir, openstack_release,
|
||||
adapters, callback_f):
|
||||
def __init__(self, charm: ops.charm.CharmBase,
|
||||
container_name: str, service_name: str,
|
||||
container_configs: List[ContainerConfigFile],
|
||||
template_dir: str, openstack_release: str,
|
||||
adapters: List[OpenStackOperRelationAdapter],
|
||||
callback_f: Callable):
|
||||
super().__init__(charm, None)
|
||||
self._state.set_default(pebble_ready=False)
|
||||
self._state.set_default(config_pushed=False)
|
||||
@@ -56,7 +64,8 @@ class PebbleHandler(ops.charm.Object):
|
||||
self.callback_f = callback_f
|
||||
self.setup_pebble_handler()
|
||||
|
||||
def setup_pebble_handler(self):
|
||||
def setup_pebble_handler(self) -> None:
|
||||
"""Configure handler for pebble ready event."""
|
||||
prefix = self.container_name.replace('-', '_')
|
||||
pebble_ready_event = getattr(
|
||||
self.charm.on,
|
||||
@@ -66,6 +75,7 @@ class PebbleHandler(ops.charm.Object):
|
||||
|
||||
def _on_service_pebble_ready(self,
|
||||
event: ops.charm.PebbleReadyEvent) -> None:
|
||||
"""Handle pebble ready event."""
|
||||
container = event.workload
|
||||
container.add_layer(
|
||||
self.service_name,
|
||||
@@ -76,7 +86,13 @@ class PebbleHandler(ops.charm.Object):
|
||||
self.charm.configure_charm(event)
|
||||
self._state.pebble_ready = True
|
||||
|
||||
def write_config(self):
|
||||
def write_config(self) -> None:
|
||||
"""Write configuration files into the container.
|
||||
|
||||
On the pre-condition that all relation adapters are ready
|
||||
for use, write all configuration files into the container
|
||||
so that the underlying service may be started.
|
||||
"""
|
||||
for adapter in self.adapters:
|
||||
if not adapter[1].ready:
|
||||
logger.info("Adapter incomplete")
|
||||
@@ -95,40 +111,61 @@ class PebbleHandler(ops.charm.Object):
|
||||
logger.debug(
|
||||
'Container not ready')
|
||||
|
||||
def get_layer(self):
|
||||
def get_layer(self) -> dict:
|
||||
"""Pebble configuration layer for the container"""
|
||||
return {}
|
||||
|
||||
def init_service(self):
|
||||
def init_service(self) -> None:
|
||||
"""Initialise service ready for use.
|
||||
|
||||
Write configuration files to the container and record
|
||||
that service is ready for us.
|
||||
"""
|
||||
self.write_config()
|
||||
self._state.service_ready = True
|
||||
|
||||
def default_container_configs(self):
|
||||
def default_container_configs(self) -> List[ContainerConfigFile]:
|
||||
"""Generate default container configurations.
|
||||
|
||||
These should be used by all inheriting classes and are
|
||||
automatically added to the list or container configurations
|
||||
provided during object instantiation.
|
||||
"""
|
||||
return []
|
||||
|
||||
@property
|
||||
def pebble_ready(self):
|
||||
def pebble_ready(self) -> bool:
|
||||
"""Determine if pebble is running and ready for use."""
|
||||
return self._state.pebble_ready
|
||||
|
||||
@property
|
||||
def config_pushed(self):
|
||||
def config_pushed(self) -> bool:
|
||||
"""Determine if configuration has been pushed to the container."""
|
||||
return self._state.config_pushed
|
||||
|
||||
@property
|
||||
def service_ready(self):
|
||||
def service_ready(self) -> bool:
|
||||
"""Determine whether the service the container provides is running."""
|
||||
return self._state.service_ready
|
||||
|
||||
|
||||
class WSGIPebbleHandler(PebbleHandler):
|
||||
"""WSGI oriented handler for a Pebble managed container."""
|
||||
|
||||
def __init__(self, charm, container_name, service_name, container_configs,
|
||||
template_dir, openstack_release, adapters, callback_f,
|
||||
wsgi_service_name):
|
||||
def __init__(self, charm: ops.charm.CharmBase,
|
||||
container_name: str, service_name: str,
|
||||
container_configs: List[ContainerConfigFile],
|
||||
template_dir: str, openstack_release: str,
|
||||
adapters: List[OpenStackOperRelationAdapter],
|
||||
callback_f: Callable,
|
||||
wsgi_service_name: str):
|
||||
super().__init__(charm, container_name, service_name,
|
||||
container_configs, template_dir, openstack_release,
|
||||
adapters, callback_f)
|
||||
self.wsgi_service_name = wsgi_service_name
|
||||
|
||||
def start_wsgi(self):
|
||||
def start_wsgi(self) -> None:
|
||||
"""Start WSGI service"""
|
||||
container = self.charm.unit.get_container(self.container_name)
|
||||
if not container:
|
||||
logger.debug(f'{self.container_name} container is not ready. '
|
||||
@@ -140,11 +177,10 @@ class WSGIPebbleHandler(PebbleHandler):
|
||||
|
||||
container.start(self.wsgi_service_name)
|
||||
|
||||
def get_layer(self):
|
||||
"""Apache WSGI service
|
||||
def get_layer(self) -> dict:
|
||||
"""Apache WSGI service pebble layer
|
||||
|
||||
:returns: pebble layer configuration for wsgi services
|
||||
:rtype: dict
|
||||
:returns: pebble layer configuration for wsgi service
|
||||
"""
|
||||
return {
|
||||
'summary': f'{self.service_name} layer',
|
||||
@@ -159,7 +195,8 @@ class WSGIPebbleHandler(PebbleHandler):
|
||||
},
|
||||
}
|
||||
|
||||
def init_service(self):
|
||||
def init_service(self) -> None:
|
||||
"""Enable and start WSGI service"""
|
||||
container = self.charm.unit.get_container(self.container_name)
|
||||
self.write_config()
|
||||
try:
|
||||
@@ -175,10 +212,10 @@ class WSGIPebbleHandler(PebbleHandler):
|
||||
self._state.service_ready = True
|
||||
|
||||
@property
|
||||
def wsgi_conf(self):
|
||||
def wsgi_conf(self) -> str:
|
||||
return f'/etc/apache2/sites-available/wsgi-{self.service_name}.conf'
|
||||
|
||||
def default_container_configs(self):
|
||||
def default_container_configs(self) -> List[ContainerConfigFile]:
|
||||
return [
|
||||
ContainerConfigFile(
|
||||
[self.container_name],
|
||||
@@ -188,34 +225,52 @@ class WSGIPebbleHandler(PebbleHandler):
|
||||
|
||||
|
||||
class RelationHandler(ops.charm.Object):
|
||||
"""Base handler class for relations"""
|
||||
|
||||
def __init__(self, charm, relation_name, callback_f):
|
||||
def __init__(self, charm: ops.charm.CharmBase,
|
||||
relation_name: str, callback_f: Callable):
|
||||
super().__init__(charm, None)
|
||||
self.charm = charm
|
||||
self.relation_name = relation_name
|
||||
self.callback_f = callback_f
|
||||
self.interface = self.setup_event_handler()
|
||||
|
||||
def setup_event_handler(self):
|
||||
def setup_event_handler(self) -> ops.charm.Object:
|
||||
"""Configure event handlers for the relation.
|
||||
|
||||
This method must be overridden in concrete class
|
||||
implementations.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def get_interface(self):
|
||||
def get_interface(self) -> Tuple[ops.charm.Object, str]:
|
||||
"""Returns the interface that this handler encapsulates.
|
||||
|
||||
This is a combination of the interface object and the
|
||||
name of the relation its wired into.
|
||||
"""
|
||||
return self.interface, self.relation_name
|
||||
|
||||
@property
|
||||
def ready(self):
|
||||
def ready(self) -> bool:
|
||||
"""Determine with the relation is ready for use."""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class IngressHandler(RelationHandler):
|
||||
"""Handler for Ingress relations"""
|
||||
|
||||
def __init__(self, charm, relation_name, service_name,
|
||||
default_public_ingress_port, callback_f):
|
||||
def __init__(self, charm: ops.charm.CharmBase,
|
||||
relation_name: str,
|
||||
service_name: str,
|
||||
default_public_ingress_port: int,
|
||||
callback_f: Callable):
|
||||
self.default_public_ingress_port = default_public_ingress_port
|
||||
self.service_name = service_name
|
||||
super().__init__(charm, relation_name, callback_f)
|
||||
|
||||
def setup_event_handler(self):
|
||||
def setup_event_handler(self) -> ops.charm.Object:
|
||||
"""Configure event handlers for an Ingress relation."""
|
||||
logger.debug('Setting up ingress event handler')
|
||||
interface = ingress.IngressRequires(
|
||||
self.charm,
|
||||
@@ -223,7 +278,8 @@ class IngressHandler(RelationHandler):
|
||||
return interface
|
||||
|
||||
@property
|
||||
def ingress_config(self):
|
||||
def ingress_config(self) -> dict:
|
||||
"""Ingress controller configuration dictionary."""
|
||||
# Most charms probably won't (or shouldn't) expose service-port
|
||||
# but use it if its there.
|
||||
port = self.model.config.get(
|
||||
@@ -238,14 +294,16 @@ class IngressHandler(RelationHandler):
|
||||
'service-port': port}
|
||||
|
||||
@property
|
||||
def ready(self):
|
||||
def ready(self) -> bool:
|
||||
# Nothing to wait for
|
||||
return True
|
||||
|
||||
|
||||
class DBHandler(RelationHandler):
|
||||
"""Handler for DB relations"""
|
||||
|
||||
def setup_event_handler(self):
|
||||
def setup_event_handler(self) -> ops.charm.Object:
|
||||
"""Configure event handlers for a MySQL relation."""
|
||||
logger.debug('Setting up DB event handler')
|
||||
db = mysql.MySQLConsumer(
|
||||
self.charm,
|
||||
@@ -282,7 +340,8 @@ class DBHandler(RelationHandler):
|
||||
self.callback_f(event)
|
||||
|
||||
@property
|
||||
def ready(self):
|
||||
def ready(self) -> bool:
|
||||
"""Handler ready for use."""
|
||||
try:
|
||||
# Nothing to wait for
|
||||
return bool(self.interface.databases())
|
||||
@@ -291,6 +350,8 @@ class DBHandler(RelationHandler):
|
||||
|
||||
|
||||
class OSBaseOperatorCharm(ops.charm.CharmBase):
|
||||
"""Base charms for OpenStack operators."""
|
||||
|
||||
_state = ops.framework.StoredState()
|
||||
|
||||
def __init__(self, framework, adapters=None):
|
||||
@@ -312,10 +373,12 @@ class OSBaseOperatorCharm(ops.charm.CharmBase):
|
||||
self.framework.observe(self.on.config_changed,
|
||||
self._on_config_changed)
|
||||
|
||||
def get_relation_handlers(self):
|
||||
def get_relation_handlers(self) -> List[RelationHandler]:
|
||||
"""Relation handlers for the operator."""
|
||||
return []
|
||||
|
||||
def get_pebble_handlers(self):
|
||||
def get_pebble_handlers(self) -> List[PebbleHandler]:
|
||||
"""Pebble handlers for the operator."""
|
||||
return [
|
||||
PebbleHandler(
|
||||
self,
|
||||
@@ -327,53 +390,64 @@ class OSBaseOperatorCharm(ops.charm.CharmBase):
|
||||
self.adapters,
|
||||
self.configure_charm)]
|
||||
|
||||
def configure_charm(self, event):
|
||||
def configure_charm(self, event) -> None:
|
||||
"""Configure containers when all dependencies are met.
|
||||
|
||||
Iterates over all Pebble handlers and writes configuration
|
||||
files if the handler is ready for use.
|
||||
"""
|
||||
for h in self.pebble_handlers:
|
||||
if h.ready:
|
||||
h.write_config()
|
||||
|
||||
@property
|
||||
def container_configs(self):
|
||||
def container_configs(self) -> List[ContainerConfigFile]:
|
||||
"""Container configuration files for the operator."""
|
||||
return []
|
||||
|
||||
@property
|
||||
def config_adapters(self):
|
||||
def config_adapters(self) -> List[sunbeam_adapters.CharmConfigAdapter]:
|
||||
"""Configuration adapters for the operator."""
|
||||
return [
|
||||
sunbeam_adapters.CharmConfigAdapter(self, 'options')]
|
||||
|
||||
@property
|
||||
def handler_prefix(self):
|
||||
def handler_prefix(self) -> str:
|
||||
"""Prefix for handlers??"""
|
||||
return self.service_name.replace('-', '_')
|
||||
|
||||
@property
|
||||
def container_names(self):
|
||||
"""Containers that form part of this service."""
|
||||
return [self.service_name]
|
||||
|
||||
@property
|
||||
def template_dir(self):
|
||||
def template_dir(self) -> str:
|
||||
"""Directory containing Jinja2 templates."""
|
||||
return 'src/templates'
|
||||
|
||||
def _on_config_changed(self, event):
|
||||
self.configure_charm(None)
|
||||
|
||||
def containers_ready(self):
|
||||
def containers_ready(self) -> bool:
|
||||
"""Determine whether all containers are ready for configuration."""
|
||||
for ph in self.pebble_handlers:
|
||||
if not ph.service_ready:
|
||||
logger.info("Container incomplete")
|
||||
logger.info(f"Container incomplete: {ph.container_name}")
|
||||
return False
|
||||
return True
|
||||
|
||||
def relation_handlers_ready(self):
|
||||
def relation_handlers_ready(self) -> bool:
|
||||
"""Determine whether all relations are ready for use."""
|
||||
for handler in self.relation_handlers:
|
||||
if not handler.ready:
|
||||
logger.info("Relation {} incomplete".format(
|
||||
handler.relation_name))
|
||||
logger.info(f"Relation {handler.relation_name} incomplete")
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
class OSBaseOperatorAPICharm(OSBaseOperatorCharm):
|
||||
_state = ops.framework.StoredState()
|
||||
"""Base class for OpenStack API operators"""
|
||||
|
||||
def __init__(self, framework, adapters=None):
|
||||
if not adapters:
|
||||
@@ -382,7 +456,8 @@ class OSBaseOperatorAPICharm(OSBaseOperatorCharm):
|
||||
self._state.set_default(db_ready=False)
|
||||
self._state.set_default(bootstrapped=False)
|
||||
|
||||
def get_pebble_handlers(self):
|
||||
def get_pebble_handlers(self) -> List[PebbleHandler]:
|
||||
"""Pebble handlers for the service"""
|
||||
return [
|
||||
WSGIPebbleHandler(
|
||||
self,
|
||||
@@ -395,7 +470,8 @@ class OSBaseOperatorAPICharm(OSBaseOperatorCharm):
|
||||
self.configure_charm,
|
||||
f'wsgi-{self.service_name}')]
|
||||
|
||||
def get_relation_handlers(self):
|
||||
def get_relation_handlers(self) -> List[RelationHandler]:
|
||||
"""Relation handlers for the service."""
|
||||
self.db = DBHandler(
|
||||
self,
|
||||
f'{self.service_name}-db',
|
||||
@@ -409,7 +485,8 @@ class OSBaseOperatorAPICharm(OSBaseOperatorCharm):
|
||||
return [self.db, self.ingress]
|
||||
|
||||
@property
|
||||
def container_configs(self):
|
||||
def container_configs(self) -> List[ContainerConfigFile]:
|
||||
"""Container configuration files for the service."""
|
||||
_cconfigs = super().container_configs
|
||||
_cconfigs.extend([
|
||||
ContainerConfigFile(
|
||||
@@ -420,33 +497,40 @@ class OSBaseOperatorAPICharm(OSBaseOperatorCharm):
|
||||
return _cconfigs
|
||||
|
||||
@property
|
||||
def service_user(self):
|
||||
def service_user(self) -> str:
|
||||
"""Service user file and directory ownership."""
|
||||
return self.service_name
|
||||
|
||||
@property
|
||||
def service_group(self):
|
||||
def service_group(self) -> str:
|
||||
"""Service group file and directory ownership."""
|
||||
return self.service_name
|
||||
|
||||
@property
|
||||
def service_conf(self):
|
||||
def service_conf(self) -> str:
|
||||
"""Service default configuration file."""
|
||||
return f'/etc/{self.service_name}/{self.service_name}.conf'
|
||||
|
||||
@property
|
||||
def config_adapters(self):
|
||||
def config_adapters(self) -> List[sunbeam_adapters.ConfigAdapter]:
|
||||
"""Generate list of configuration adapters for the charm."""
|
||||
_cadapters = super().config_adapters
|
||||
_cadapters.extend([
|
||||
sunbeam_adapters.WSGIWorkerConfigAdapter(self, 'wsgi_config')])
|
||||
return _cadapters
|
||||
|
||||
@property
|
||||
def wsgi_container_name(self):
|
||||
def wsgi_container_name(self) -> str:
|
||||
"""Name of the WSGI application container."""
|
||||
return self.service_name
|
||||
|
||||
@property
|
||||
def default_public_ingress_port(self):
|
||||
def default_public_ingress_port(self) -> int:
|
||||
"""Port to use for ingress access to service."""
|
||||
raise NotImplementedError
|
||||
|
||||
def configure_charm(self, event):
|
||||
def configure_charm(self, event) -> None:
|
||||
"""Catchall handler to cconfigure charm services."""
|
||||
if not self.relation_handlers_ready():
|
||||
logging.debug("Aborting charm relations not ready")
|
||||
return
|
||||
@@ -466,14 +550,14 @@ class OSBaseOperatorAPICharm(OSBaseOperatorCharm):
|
||||
self.unit.status = ops.model.ActiveStatus()
|
||||
self._state.bootstrapped = True
|
||||
|
||||
def _do_bootstrap(self):
|
||||
def _do_bootstrap(self) -> None:
|
||||
"""Bootstrap the service ready for operation.
|
||||
|
||||
This method should be overridden as part of a concrete
|
||||
charm implementation
|
||||
"""
|
||||
pass
|
||||
|
||||
def bootstrapped(self):
|
||||
"""Returns True if the instance is bootstrapped.
|
||||
|
||||
:returns: True if the keystone service has been bootstrapped,
|
||||
False otherwise
|
||||
:rtype: bool
|
||||
"""
|
||||
def bootstrapped(self) -> bool:
|
||||
"""Determine whether the service has been boostrapped."""
|
||||
return self._state.bootstrapped
|
||||
|
||||
Reference in New Issue
Block a user