Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update charm libraries #211

Merged
merged 2 commits into from
Apr 3, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 125 additions & 6 deletions lib/charms/data_platform_libs/v0/data_interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -295,10 +295,21 @@ def _on_topic_requested(self, event: TopicRequestedEvent):
import json
import logging
from abc import ABC, abstractmethod
from collections import namedtuple
from collections import UserDict, namedtuple
from datetime import datetime
from enum import Enum
from typing import Callable, Dict, List, Optional, Set, Tuple, Union
from typing import (
Callable,
Dict,
ItemsView,
KeysView,
List,
Optional,
Set,
Tuple,
Union,
ValuesView,
)

from ops import JujuVersion, Model, Secret, SecretInfo, SecretNotFoundError
from ops.charm import (
Expand All @@ -320,7 +331,7 @@ def _on_topic_requested(self, event: TopicRequestedEvent):

# Increment this PATCH version before using `charmcraft publish-lib` or reset
# to 0 if you are raising the major API version
LIBPATCH = 29
LIBPATCH = 31

PYDEPS = ["ops>=2.0.0"]

Expand Down Expand Up @@ -612,6 +623,102 @@ def remove(self, label: str) -> None:
# Base Data


class DataDict(UserDict[str, str]):
"""Python Standard Library 'dict' - like representation of Relation Data."""

def __init__(self, relation_data: "Data", relation_id: int):
self.relation_data = relation_data
self.relation_id = relation_id

@property
def data(self) -> Dict[str, str]:
"""Return the full content of the Abstract Relation Data dictionary."""
result = self.relation_data.fetch_my_relation_data([self.relation_id])
try:
result_remote = self.relation_data.fetch_relation_data([self.relation_id])
except NotImplementedError:
result_remote = {self.relation_id: {}}
if result:
result_remote[self.relation_id].update(result[self.relation_id])
return result_remote.get(self.relation_id, {})

def __setitem__(self, key: str, item: str) -> None:
"""Set an item of the Abstract Relation Data dictionary."""
self.relation_data.update_relation_data(self.relation_id, {key: item})

def __getitem__(self, key: str) -> str:
"""Get an item of the Abstract Relation Data dictionary."""
result = None
if not (result := self.relation_data.fetch_my_relation_field(self.relation_id, key)):
try:
result = self.relation_data.fetch_relation_field(self.relation_id, key)
except NotImplementedError:
pass
if not result:
raise KeyError
return result

def __eq__(self, d: dict) -> bool:
"""Equality."""
return self.data == d

def __repr__(self) -> str:
"""String representation Abstract Relation Data dictionary."""
return repr(self.data)

def __len__(self) -> int:
"""Length of the Abstract Relation Data dictionary."""
return len(self.data)

def __delitem__(self, key: str) -> None:
"""Delete an item of the Abstract Relation Data dictionary."""
self.relation_data.delete_relation_data(self.relation_id, [key])

def has_key(self, key: str) -> bool:
"""Does the key exist in the Abstract Relation Data dictionary?"""
return key in self.data

def update(self, items: Dict[str, str]):
"""Update the Abstract Relation Data dictionary."""
self.relation_data.update_relation_data(self.relation_id, items)

def keys(self) -> KeysView[str]:
"""Keys of the Abstract Relation Data dictionary."""
return self.data.keys()

def values(self) -> ValuesView[str]:
"""Values of the Abstract Relation Data dictionary."""
return self.data.values()

def items(self) -> ItemsView[str, str]:
"""Items of the Abstract Relation Data dictionary."""
return self.data.items()

def pop(self, item: str) -> str:
"""Pop an item of the Abstract Relation Data dictionary."""
result = self.relation_data.fetch_my_relation_field(self.relation_id, item)
if not result:
raise KeyError(f"Item {item} doesn't exist.")
self.relation_data.delete_relation_data(self.relation_id, [item])
return result

def __contains__(self, item: str) -> bool:
"""Does the Abstract Relation Data dictionary contain item?"""
return item in self.data.values()

def __iter__(self):
"""Iterate through the Abstract Relation Data dictionary."""
return iter(self.data)

def get(self, key: str, default: Optional[str] = None) -> Optional[str]:
"""Safely get an item of the Abstract Relation Data dictionary."""
try:
if result := self[key]:
return result
except KeyError:
return default


class Data(ABC):
"""Base relation data mainpulation (abstract) class."""

Expand Down Expand Up @@ -929,6 +1036,10 @@ def _delete_relation_data_without_secrets(
# Public interface methods
# Handling Relation Fields seamlessly, regardless if in databag or a Juju Secret

def as_dict(self, relation_id: int) -> UserDict[str, str]:
"""Dict behavior representation of the Abstract Data."""
return DataDict(self, relation_id)

def get_relation(self, relation_name, relation_id) -> Relation:
"""Safe way of retrieving a relation."""
relation = self._model.get_relation(relation_name, relation_id)
Expand Down Expand Up @@ -1787,6 +1898,14 @@ def __init__(self, unit: Unit, *args, **kwargs):
self.local_unit = unit
self.component = unit

def update_relation_data(self, relation_id: int, data: dict) -> None:
"""This method makes no sense for a Other Peer Relation."""
raise NotImplementedError("It's not possible to update data of another unit.")

def delete_relation_data(self, relation_id: int, fields: List[str]) -> None:
"""This method makes no sense for a Other Peer Relation."""
raise NotImplementedError("It's not possible to delete data of another unit.")


class DataPeerOtherUnitEventHandlers(DataPeerEventHandlers):
"""Requires-side of the relation."""
Expand All @@ -1809,18 +1928,18 @@ def __init__(
additional_secret_fields: Optional[List[str]] = [],
secret_field_name: Optional[str] = None,
deleted_label: Optional[str] = None,
unique_key: str = "",
):
DataPeerData.__init__(
DataPeerOtherUnitData.__init__(
self,
unit,
charm.model,
relation_name,
extra_user_roles,
additional_secret_fields,
secret_field_name,
deleted_label,
)
DataPeerEventHandlers.__init__(self, charm, self, unique_key)
DataPeerOtherUnitEventHandlers.__init__(self, charm, self)


# General events
Expand Down
7 changes: 6 additions & 1 deletion lib/charms/nginx_ingress_integrator/v0/nginx_route.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@

# Increment this PATCH version before using `charmcraft publish-lib` or reset
# to 0 if you are raising the major API version
LIBPATCH = 5
LIBPATCH = 6

__all__ = ["require_nginx_route", "provide_nginx_route"]

Expand Down Expand Up @@ -181,6 +181,7 @@ def require_nginx_route( # pylint: disable=too-many-locals,too-many-branches,to
service_port: int,
additional_hostnames: typing.Optional[str] = None,
backend_protocol: typing.Optional[str] = None,
enable_access_log: typing.Optional[bool] = None,
limit_rps: typing.Optional[int] = None,
limit_whitelist: typing.Optional[str] = None,
max_body_size: typing.Optional[int] = None,
Expand Down Expand Up @@ -211,6 +212,8 @@ def require_nginx_route( # pylint: disable=too-many-locals,too-many-branches,to
additional-hostnames option via relation, optional.
backend_protocol: configure Nginx ingress integrator
backend-protocol option via relation, optional.
enable_access_log: configure Nginx ingress
nginx.ingress.kubernetes.io/enable-access-log option.
limit_rps: configure Nginx ingress integrator limit-rps
option via relation, optional.
limit_whitelist: configure Nginx ingress integrator
Expand Down Expand Up @@ -251,6 +254,8 @@ def require_nginx_route( # pylint: disable=too-many-locals,too-many-branches,to
config["additional-hostnames"] = additional_hostnames
if backend_protocol is not None:
config["backend-protocol"] = backend_protocol
if enable_access_log is not None:
config["enable-access-log"] = "true" if enable_access_log else "false"
if limit_rps is not None:
config["limit-rps"] = limit_rps
if limit_whitelist is not None:
Expand Down
Loading