Skip to content

Commit

Permalink
[resotocore][feat] Add typed ConfigId (#886)
Browse files Browse the repository at this point in the history
  • Loading branch information
meln1k committed May 25, 2022
1 parent 6650606 commit 503b736
Show file tree
Hide file tree
Showing 29 changed files with 165 additions and 131 deletions.
14 changes: 7 additions & 7 deletions resotocore/resotocore/cli/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
args_parts_unquoted_parser,
args_parts_parser,
)
from resotocore.ids import TaskId
from resotocore.ids import ConfigId, TaskId
from resotocore.cli.model import (
CLICommand,
CLIContext,
Expand Down Expand Up @@ -115,7 +115,7 @@
respond_cytoscape,
)
from resotocore.worker_task_queue import WorkerTask, WorkerTaskName
from resotocore.task.task_description import TaskDescriptorId
from resotocore.ids import TaskDescriptorId

log = logging.getLogger(__name__)

Expand Down Expand Up @@ -3772,11 +3772,11 @@ def args_info(self) -> ArgsInfo:
}

def parse(self, arg: Optional[str] = None, ctx: CLIContext = EmptyContext, **kwargs: Any) -> CLIAction:
async def show_config(cfg_id: str) -> AsyncIterator[JsonElement]:
async def show_config(cfg_id: ConfigId) -> AsyncIterator[JsonElement]:
cfg = await self.dependencies.config_handler.config_yaml(cfg_id)
yield cfg if cfg else f"No config with this id: {cfg_id}"

async def delete_config(cfg_id: str) -> AsyncIterator[str]:
async def delete_config(cfg_id: ConfigId) -> AsyncIterator[str]:
await self.dependencies.config_handler.delete_config(cfg_id)
yield f"Config {cfg_id} has been deleted."

Expand All @@ -3790,15 +3790,15 @@ async def send_file(content: str) -> AsyncIterator[str]:
finally:
shutil.rmtree(temp_dir)

async def set_config(cfg_id: str, updates: List[Tuple[str, JsonElement]]) -> AsyncIterator[JsonElement]:
async def set_config(cfg_id: ConfigId, updates: List[Tuple[str, JsonElement]]) -> AsyncIterator[JsonElement]:
cfg = await self.dependencies.config_handler.get_config(cfg_id)
updated = cfg.config if cfg else {}
for prop, js in updates:
updated = set_value_in_path(js, prop, updated)
await self.dependencies.config_handler.put_config(ConfigEntity(cfg_id, updated))
yield await self.dependencies.config_handler.config_yaml(cfg_id)

async def edit_config(cfg_id: str) -> AsyncIterator[str]:
async def edit_config(cfg_id: ConfigId) -> AsyncIterator[str]:
# Editing a config is a two-step process:
# 1) download the config and make it available to edit
# 2) upload the config file and update the config from content --> update_config
Expand All @@ -3807,7 +3807,7 @@ async def edit_config(cfg_id: str) -> AsyncIterator[str]:
raise AttributeError(f"No config with this id: {cfg_id}")
return send_file(yml)

async def update_config(cfg_id: str) -> AsyncIterator[str]:
async def update_config(cfg_id: ConfigId) -> AsyncIterator[str]:
# Usually invoked by resh automatically via edit_config, but can also be triggered manually.
# A config with given id is changed by the content of uploaded file "config"
try:
Expand Down
11 changes: 6 additions & 5 deletions resotocore/resotocore/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@

from resotocore.model.model import Model, Kind
from resotocore.types import Json
from resotocore.ids import ConfigId


@dataclass(order=True, unsafe_hash=True, frozen=True)
class ConfigEntity:
id: str
id: ConfigId
config: Json
revision: Optional[str] = None

Expand All @@ -38,11 +39,11 @@ class ConfigValidation:

class ConfigHandler(ABC):
@abstractmethod
def list_config_ids(self) -> AsyncIterator[str]:
def list_config_ids(self) -> AsyncIterator[ConfigId]:
pass

@abstractmethod
async def get_config(self, cfg_id: str) -> Optional[ConfigEntity]:
async def get_config(self, cfg_id: ConfigId) -> Optional[ConfigEntity]:
pass

@abstractmethod
Expand All @@ -54,7 +55,7 @@ async def patch_config(self, cfg: ConfigEntity) -> ConfigEntity:
pass

@abstractmethod
async def delete_config(self, cfg_id: str) -> None:
async def delete_config(self, cfg_id: ConfigId) -> None:
pass

@abstractmethod
Expand All @@ -78,7 +79,7 @@ async def put_config_validation(self, validation: ConfigValidation) -> ConfigVal
pass

@abstractmethod
async def config_yaml(self, cfg_id: str, revision: bool = False) -> Optional[str]:
async def config_yaml(self, cfg_id: ConfigId, revision: bool = False) -> Optional[str]:
pass


Expand Down
12 changes: 6 additions & 6 deletions resotocore/resotocore/config/config_handler_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from resotocore.types import Json
from resotocore.util import uuid_str
from resotocore.worker_task_queue import WorkerTaskQueue, WorkerTask, WorkerTaskName
from resotocore.ids import TaskId
from resotocore.ids import TaskId, ConfigId


class ConfigHandlerService(ConfigHandler):
Expand All @@ -30,7 +30,7 @@ def __init__(
self.task_queue = task_queue
self.message_bus = message_bus

async def coerce_and_check_model(self, cfg_id: str, config: Json, validate: bool = True) -> Json:
async def coerce_and_check_model(self, cfg_id: ConfigId, config: Json, validate: bool = True) -> Json:
model = await self.get_configs_model()

final_config = {}
Expand All @@ -57,10 +57,10 @@ async def coerce_and_check_model(self, cfg_id: str, config: Json, validate: bool
# If we come here, everything is fine
return final_config

def list_config_ids(self) -> AsyncIterator[str]:
def list_config_ids(self) -> AsyncIterator[ConfigId]:
return self.cfg_db.keys()

async def get_config(self, cfg_id: str) -> Optional[ConfigEntity]:
async def get_config(self, cfg_id: ConfigId) -> Optional[ConfigEntity]:
return await self.cfg_db.get(cfg_id)

async def put_config(self, cfg: ConfigEntity, validate: bool = True) -> ConfigEntity:
Expand All @@ -81,7 +81,7 @@ async def patch_config(self, cfg: ConfigEntity) -> ConfigEntity:
await self.message_bus.emit_event(CoreMessage.ConfigUpdated, dict(id=result.id, revision=result.revision))
return result

async def delete_config(self, cfg_id: str) -> None:
async def delete_config(self, cfg_id: ConfigId) -> None:
await self.cfg_db.delete(cfg_id)
await self.validation_db.delete(cfg_id)
await self.message_bus.emit_event(CoreMessage.ConfigDeleted, dict(id=cfg_id))
Expand All @@ -108,7 +108,7 @@ async def update_configs_model(self, kinds: List[Kind]) -> Model:
await self.model_db.update_many(kinds)
return updated

async def config_yaml(self, cfg_id: str, revision: bool = False) -> Optional[str]:
async def config_yaml(self, cfg_id: ConfigId, revision: bool = False) -> Optional[str]:
config = await self.get_config(cfg_id)
if config:
model = await self.get_configs_model()
Expand Down
6 changes: 4 additions & 2 deletions resotocore/resotocore/core_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,14 @@
from resotocore.types import Json, JsonElement
from resotocore.util import set_value_in_path, value_in_path, del_value_in_path
from resotocore.validator import Validator, schema_name
from resotocore.ids import ConfigId


log = logging.getLogger(__name__)

# ids used in the config store
ResotoCoreConfigId = "resoto.core"
ResotoCoreCommandsConfigId = "resoto.core.commands"
ResotoCoreConfigId = ConfigId("resoto.core")
ResotoCoreCommandsConfigId = ConfigId("resoto.core.commands")

# root note of the configuration value
ResotoCoreRoot = "resotocore"
Expand Down
13 changes: 7 additions & 6 deletions resotocore/resotocore/db/configdb.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
from resotocore.config import ConfigEntity, ConfigValidation
from resotocore.db.async_arangodb import AsyncArangoDB
from resotocore.db.entitydb import EntityDb, EventEntityDb, ArangoEntityDb
from resotocore.ids import ConfigId


# Database to store config entities
ConfigEntityDb = EntityDb[ConfigEntity]
EventConfigEntityDb = EventEntityDb[ConfigEntity]
ConfigEntityDb = EntityDb[ConfigId, ConfigEntity]
EventConfigEntityDb = EventEntityDb[ConfigId, ConfigEntity]


def config_entity_db(db: AsyncArangoDB, collection: str) -> ArangoEntityDb[ConfigEntity]:
def config_entity_db(db: AsyncArangoDB, collection: str) -> ArangoEntityDb[ConfigId, ConfigEntity]:
return ArangoEntityDb(db, collection, ConfigEntity, lambda k: k.id)


# Database to store config entity models
ConfigValidationEntityDb = EntityDb[ConfigValidation]
EventConfigValidationEntityDb = EventEntityDb[ConfigValidation]
ConfigValidationEntityDb = EntityDb[str, ConfigValidation]
EventConfigValidationEntityDb = EventEntityDb[str, ConfigValidation]


def config_validation_entity_db(db: AsyncArangoDB, collection: str) -> ArangoEntityDb[ConfigValidation]:
def config_validation_entity_db(db: AsyncArangoDB, collection: str) -> ArangoEntityDb[str, ConfigValidation]:
return ArangoEntityDb(db, collection, ConfigValidation, lambda k: k.id)
46 changes: 29 additions & 17 deletions resotocore/resotocore/db/entitydb.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import logging
from abc import ABC, abstractmethod
from dataclasses import is_dataclass, replace
from typing import AsyncGenerator, Generic, TypeVar, Optional, Type, Union, Callable, List
from typing import AsyncGenerator, Generic, TypeVar, Optional, Type, Callable, List

from arango import DocumentUpdateError, DocumentRevisionError
from jsons import JsonsError
Expand All @@ -15,11 +15,12 @@
log = logging.getLogger(__name__)

T = TypeVar("T")
K = TypeVar("K", bound=str)


class EntityDb(ABC, Generic[T]):
class EntityDb(ABC, Generic[K, T]):
@abstractmethod
def keys(self) -> AsyncGenerator[str, None]:
def keys(self) -> AsyncGenerator[K, None]:
pass

@abstractmethod
Expand All @@ -31,15 +32,19 @@ async def update_many(self, elements: List[T]) -> None:
pass

@abstractmethod
async def get(self, key: str) -> Optional[T]:
async def get(self, key: K) -> Optional[T]:
pass

@abstractmethod
async def update(self, t: T) -> T:
pass

@abstractmethod
async def delete(self, key_or_object: Union[str, T]) -> None:
async def delete(self, key: K) -> None:
pass

@abstractmethod
async def delete_value(self, value: T) -> None:
pass

@abstractmethod
Expand All @@ -51,14 +56,14 @@ async def wipe(self) -> bool:
pass


class ArangoEntityDb(EntityDb[T], ABC):
def __init__(self, db: AsyncArangoDB, collection_name: str, t_type: Type[T], key_fn: Callable[[T], str]):
class ArangoEntityDb(EntityDb[K, T], ABC):
def __init__(self, db: AsyncArangoDB, collection_name: str, t_type: Type[T], key_fn: Callable[[T], K]):
self.db = db
self.collection_name = collection_name
self.t_type = t_type
self.key_of = key_fn

async def keys(self) -> AsyncGenerator[str, None]:
async def keys(self) -> AsyncGenerator[K, None]:
with await self.db.keys(self.collection_name) as cursor:
for element in cursor:
yield element
Expand All @@ -74,7 +79,7 @@ async def all(self) -> AsyncGenerator[T, None]:
async def update_many(self, elements: List[T]) -> None:
await self.db.insert_many(self.collection_name, [self.to_doc(e) for e in elements], overwrite=True)

async def get(self, key: str) -> Optional[T]:
async def get(self, key: K) -> Optional[T]:
result = await self.db.get(self.collection_name, key)
return from_js(result, self.t_type) if result else None

Expand All @@ -96,8 +101,11 @@ async def update(self, t: T) -> T:
setattr(t, "revision", result["_rev"])
return t

async def delete(self, key_or_object: Union[str, T]) -> None:
key = key_or_object if isinstance(key_or_object, str) else self.key_of(key_or_object)
async def delete(self, key: K) -> None:
await self.db.delete(self.collection_name, key, ignore_missing=True)

async def delete_value(self, value: T) -> None:
key = self.key_of(value)
await self.db.delete(self.collection_name, key, ignore_missing=True)

async def create_update_schema(self) -> None:
Expand All @@ -115,13 +123,13 @@ def to_doc(self, elem: T) -> Json:
return js


class EventEntityDb(EntityDb[T]):
def __init__(self, db: EntityDb[T], event_sender: AnalyticsEventSender, entity_name: str):
class EventEntityDb(EntityDb[K, T]):
def __init__(self, db: EntityDb[K, T], event_sender: AnalyticsEventSender, entity_name: str):
self.db = db
self.event_sender = event_sender
self.entity_name = entity_name

def keys(self) -> AsyncGenerator[str, None]:
def keys(self) -> AsyncGenerator[K, None]:
return self.db.keys()

def all(self) -> AsyncGenerator[T, None]:
Expand All @@ -132,16 +140,20 @@ async def update_many(self, elements: List[T]) -> None:
await self.event_sender.core_event(f"{self.entity_name}-updated-many", count=len(elements))
return result

async def get(self, key: str) -> Optional[T]:
async def get(self, key: K) -> Optional[T]:
return await self.db.get(key)

async def update(self, t: T) -> T:
result = await self.db.update(t)
await self.event_sender.core_event(f"{self.entity_name}-updated")
return result

async def delete(self, key_or_object: Union[str, T]) -> None:
await self.db.delete(key_or_object)
async def delete(self, key: K) -> None:
await self.db.delete(key)
await self.event_sender.core_event(f"{self.entity_name}-deleted")

async def delete_value(self, value: T) -> None:
await self.db.delete_value(value)
await self.event_sender.core_event(f"{self.entity_name}-deleted")

async def create_update_schema(self) -> None:
Expand Down
6 changes: 3 additions & 3 deletions resotocore/resotocore/db/jobdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
from resotocore.db.entitydb import EntityDb, EventEntityDb, ArangoEntityDb
from resotocore.task.task_description import Job

JobDb = EntityDb[Job]
EventJobDb = EventEntityDb[Job]
JobDb = EntityDb[str, Job]
EventJobDb = EventEntityDb[str, Job]


def job_db(db: AsyncArangoDB, collection: str) -> ArangoEntityDb[Job]:
def job_db(db: AsyncArangoDB, collection: str) -> ArangoEntityDb[str, Job]:
return ArangoEntityDb(db, collection, Job, lambda k: k.id)
6 changes: 3 additions & 3 deletions resotocore/resotocore/db/modeldb.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
from resotocore.db.entitydb import EntityDb, EventEntityDb, ArangoEntityDb
from resotocore.model.model import Kind

ModelDb = EntityDb[Kind]
EventModelDb = EventEntityDb[Kind]
ModelDb = EntityDb[str, Kind]
EventModelDb = EventEntityDb[str, Kind]


def model_db(db: AsyncArangoDB, collection: str) -> ArangoEntityDb[Kind]:
def model_db(db: AsyncArangoDB, collection: str) -> ArangoEntityDb[str, Kind]:
return ArangoEntityDb(db, collection, Kind, lambda k: k.fqn) # type: ignore
8 changes: 4 additions & 4 deletions resotocore/resotocore/db/runningtaskdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@

from resotocore.db.async_arangodb import AsyncArangoDB
from resotocore.db.entitydb import EntityDb, ArangoEntityDb
from resotocore.ids import TaskId
from resotocore.ids import TaskId, TaskDescriptorId
from resotocore.message_bus import Message
from resotocore.model.typed_model import to_js
from resotocore.task.task_description import RunningTask, TaskDescriptorId
from resotocore.task.task_description import RunningTask
from resotocore.types import Json
from resotocore.util import utc

Expand Down Expand Up @@ -51,7 +51,7 @@ def data(wi: RunningTask) -> RunningTaskData:
)


class RunningTaskDb(EntityDb[RunningTaskData]):
class RunningTaskDb(EntityDb[str, RunningTaskData]):
@abstractmethod
async def update_state(self, wi: RunningTask, message: Optional[Message]) -> None:
pass
Expand All @@ -61,7 +61,7 @@ async def insert(self, task: RunningTask) -> RunningTaskData:
pass


class ArangoRunningTaskDb(ArangoEntityDb[RunningTaskData], RunningTaskDb):
class ArangoRunningTaskDb(ArangoEntityDb[str, RunningTaskData], RunningTaskDb):
def __init__(self, db: AsyncArangoDB, collection: str):
super().__init__(db, collection, RunningTaskData, lambda k: k.id)

Expand Down
7 changes: 4 additions & 3 deletions resotocore/resotocore/db/subscriberdb.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
from resotocore.db.async_arangodb import AsyncArangoDB
from resotocore.db.entitydb import EntityDb, EventEntityDb, ArangoEntityDb
from resotocore.ids import SubscriberId
from resotocore.task.model import Subscriber

SubscriberDb = EntityDb[Subscriber]
EventSubscriberDb = EventEntityDb[Subscriber]
SubscriberDb = EntityDb[SubscriberId, Subscriber]
EventSubscriberDb = EventEntityDb[SubscriberId, Subscriber]


def subscriber_db(db: AsyncArangoDB, collection: str) -> ArangoEntityDb[Subscriber]:
def subscriber_db(db: AsyncArangoDB, collection: str) -> ArangoEntityDb[SubscriberId, Subscriber]:
return ArangoEntityDb(db, collection, Subscriber, lambda k: k.id)
Loading

0 comments on commit 503b736

Please sign in to comment.