kelvin-python-sdk 0.2.0__tar.gz → 0.2.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {kelvin_python_sdk-0.2.0/kelvin_python_sdk.egg-info → kelvin_python_sdk-0.2.3}/PKG-INFO +3 -2
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/examples/csv_publisher/csv_publisher.py +27 -24
- kelvin_python_sdk-0.2.3/examples/custom_action/action_generator.py +29 -0
- kelvin_python_sdk-0.2.3/examples/custom_action/app.yaml +16 -0
- kelvin_python_sdk-0.2.3/examples/custom_action/main.py +80 -0
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/kelvin/application/client.py +22 -11
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/kelvin/application/filters.py +5 -0
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/kelvin/application/window.py +16 -7
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/kelvin/config/common.py +14 -3
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/kelvin/config/exporter.py +11 -1
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/kelvin/config/importer.py +11 -1
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/kelvin/config/manifest.py +13 -1
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/kelvin/config/smart_app.py +17 -1
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/kelvin/message/__init__.py +10 -2
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/kelvin/message/base_messages.py +100 -41
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/kelvin/message/evidences.py +1 -1
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/kelvin/message/message.py +9 -3
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/kelvin/message/msg_builders.py +245 -34
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/kelvin/message/msg_type.py +28 -0
- kelvin_python_sdk-0.2.3/kelvin/publisher/__init__.py +3 -0
- kelvin_python_sdk-0.2.3/kelvin/publisher/csv_publisher.py +116 -0
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/kelvin/publisher/main.py +13 -15
- kelvin_python_sdk-0.2.0/kelvin/publisher/publisher.py → kelvin_python_sdk-0.2.3/kelvin/publisher/server.py +85 -315
- kelvin_python_sdk-0.2.3/kelvin/publisher/simulator.py +119 -0
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3/kelvin_python_sdk.egg-info}/PKG-INFO +3 -2
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/kelvin_python_sdk.egg-info/SOURCES.txt +8 -1
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/kelvin_python_sdk.egg-info/requires.txt +1 -0
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/pyproject.toml +1 -0
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/tests/test_convert_exporter.py +13 -1
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/tests/test_convert_importer.py +14 -2
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/tests/test_convert_smart_app.py +13 -1
- kelvin_python_sdk-0.2.3/tests/test_evidences.py +87 -0
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/tests/test_message.py +4 -6
- kelvin_python_sdk-0.2.3/tests/test_message_builders.py +154 -0
- kelvin_python_sdk-0.2.3/tests/test_msg_type.py +50 -0
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/tests/test_window.py +12 -10
- kelvin_python_sdk-0.2.0/kelvin/publisher/__init__.py +0 -3
- kelvin_python_sdk-0.2.0/tests/test_message_builders.py +0 -76
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/.gitignore +0 -0
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/Jenkinsfile +0 -0
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/README.md +0 -0
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/constraints.txt +0 -0
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/examples/csv_publisher/app.yaml +0 -0
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/examples/csv_publisher/data.csv +0 -0
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/examples/csv_test/assets.csv +0 -0
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/examples/csv_test/data2.csv +0 -0
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/examples/csv_test/smart_app.yaml +0 -0
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/examples/data_tags/app.yaml +0 -0
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/examples/data_tags/main.py +0 -0
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/examples/evidence/app.yaml +0 -0
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/examples/evidence/main.py +0 -0
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/examples/example/app.yaml +0 -0
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/examples/example/example.py +0 -0
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/examples/mygenerator.py +0 -0
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/examples/random_publisher/app.yaml +0 -0
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/examples/random_publisher/random_publisher.py +0 -0
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/examples/rec_app.py +0 -0
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/examples/rec_app.yaml +0 -0
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/examples/windows/hopping-window/.dockerignore +0 -0
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/examples/windows/hopping-window/Dockerfile +0 -0
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/examples/windows/hopping-window/app.yaml +0 -0
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/examples/windows/hopping-window/data.csv +0 -0
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/examples/windows/hopping-window/main.py +0 -0
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/examples/windows/hopping-window/requirements.txt +0 -0
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/examples/windows/rolling-window/.dockerignore +0 -0
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/examples/windows/rolling-window/Dockerfile +0 -0
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/examples/windows/rolling-window/app.yaml +0 -0
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/examples/windows/rolling-window/data.csv +0 -0
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/examples/windows/rolling-window/main.py +0 -0
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/examples/windows/rolling-window/requirements.txt +0 -0
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/examples/windows/tumbling-window/.dockerignore +0 -0
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/examples/windows/tumbling-window/Dockerfile +0 -0
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/examples/windows/tumbling-window/app.yaml +0 -0
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/examples/windows/tumbling-window/data.csv +0 -0
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/examples/windows/tumbling-window/main.py +0 -0
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/examples/windows/tumbling-window/requirements.txt +0 -0
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/kelvin/application/__init__.py +0 -0
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/kelvin/application/config.py +0 -0
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/kelvin/application/stream.py +0 -0
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/kelvin/config/__init__.py +0 -0
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/kelvin/config/appyaml.py +0 -0
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/kelvin/config/external.py +0 -0
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/kelvin/config/parser.py +0 -0
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/kelvin/krn/__init__.py +0 -0
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/kelvin/krn/krn.py +0 -0
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/kelvin/logs.py +0 -0
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/kelvin/message/krn.py +0 -0
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/kelvin/message/primitives.py +0 -0
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/kelvin/message/utils.py +0 -0
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/kelvin_python_sdk.egg-info/dependency_links.txt +0 -0
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/kelvin_python_sdk.egg-info/entry_points.txt +0 -0
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/kelvin_python_sdk.egg-info/top_level.txt +0 -0
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/setup.cfg +0 -0
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/tests/configs/docker.yaml +0 -0
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/tests/configs/exporter.json +0 -0
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/tests/configs/exporter.yaml +0 -0
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/tests/configs/importer.yaml +0 -0
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/tests/configs/invalid.yaml +0 -0
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/tests/configs/legacy_docker.yaml +0 -0
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/tests/configs/smartapp.yaml +0 -0
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/tests/schemas/configuration.json +0 -0
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/tests/schemas/parameters.json +0 -0
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/tests/schemas/test_io_cc_schema.json +0 -0
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/tests/schemas/test_io_schema.json +0 -0
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/tests/test_app_base_config.py +0 -0
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/tests/test_client.py +0 -0
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/tests/test_client_calbacks.py +0 -0
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/tests/test_convert_external.py +0 -0
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/tests/test_filters.py +0 -0
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/tests/test_krn.py +0 -0
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/tests/test_parse_config.py +0 -0
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/tests/test_process_runtime_manifest.py +0 -0
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/tests/test_utils.py +0 -0
- {kelvin_python_sdk-0.2.0 → kelvin_python_sdk-0.2.3}/tox.ini +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: kelvin-python-sdk
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.3
|
|
4
4
|
Summary: Kelvin Python SDK
|
|
5
5
|
Author-email: Kelvin Inc <engineering@kelvininc.com>
|
|
6
6
|
Classifier: Operating System :: OS Independent
|
|
@@ -22,6 +22,7 @@ Requires-Dist: pytest-asyncio; extra == "tests"
|
|
|
22
22
|
Requires-Dist: pytest-cov; extra == "tests"
|
|
23
23
|
Requires-Dist: time-machine; extra == "tests"
|
|
24
24
|
Requires-Dist: asyncmock; python_version < "3.8" and extra == "tests"
|
|
25
|
+
Requires-Dist: pandas; extra == "tests"
|
|
25
26
|
Provides-Extra: format
|
|
26
27
|
Requires-Dist: isort; extra == "format"
|
|
27
28
|
Requires-Dist: black; extra == "format"
|
|
@@ -3,36 +3,39 @@ from __future__ import annotations
|
|
|
3
3
|
import asyncio
|
|
4
4
|
from typing import List, Optional
|
|
5
5
|
|
|
6
|
-
import yaml
|
|
7
6
|
from pydantic import BaseModel
|
|
8
7
|
|
|
9
|
-
from kelvin.application import KelvinApp
|
|
8
|
+
from kelvin.application import KelvinApp, Datastream
|
|
10
9
|
from kelvin.krn import KRNAssetDataStream
|
|
11
10
|
from kelvin.logs import logger
|
|
12
|
-
from kelvin.message import
|
|
13
|
-
from kelvin.publisher.
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
11
|
+
from kelvin.message import Message
|
|
12
|
+
from kelvin.publisher.server import MessageData
|
|
13
|
+
from kelvin.publisher.csv_publisher import CSVPublisher
|
|
14
|
+
from datetime import datetime
|
|
15
|
+
from typing import Any, Type, Union
|
|
16
|
+
|
|
17
|
+
def string_to_strict_type(value: Any, data_type: Type) -> Union[bool, float, str, dict]:
|
|
18
|
+
if isinstance(value, data_type):
|
|
19
|
+
return value
|
|
20
|
+
if data_type is bool:
|
|
21
|
+
return str(value).lower() in ["true", "1"]
|
|
22
|
+
if data_type is float:
|
|
23
|
+
return float(value)
|
|
24
|
+
return value
|
|
25
|
+
|
|
26
|
+
def message_from_message_data(data: MessageData, outputs: List[Datastream]) -> Optional[Message]:
|
|
27
|
+
output = next((output for output in outputs if output.name == data.resource.data_stream), None)
|
|
18
28
|
if output is None:
|
|
19
29
|
logger.error("csv metric not found in outputs", metric=data.resource)
|
|
20
30
|
return None
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
elif data_type == "string":
|
|
30
|
-
msg_type = String
|
|
31
|
-
value = str(data.value)
|
|
32
|
-
else:
|
|
33
|
-
return None
|
|
34
|
-
|
|
35
|
-
return msg_type(resource=KRNAssetDataStream(data.asset, data.resource), payload=value)
|
|
31
|
+
|
|
32
|
+
msg = Message(
|
|
33
|
+
type=output.type,
|
|
34
|
+
timestamp=data.timestamp or datetime.now().astimezone(),
|
|
35
|
+
resource=data.resource
|
|
36
|
+
)
|
|
37
|
+
msg.payload = string_to_strict_type(data.value, type(msg.payload))
|
|
38
|
+
return msg
|
|
36
39
|
|
|
37
40
|
|
|
38
41
|
class AppConfiguration(BaseModel):
|
|
@@ -55,7 +58,7 @@ async def main() -> None:
|
|
|
55
58
|
first_run = False
|
|
56
59
|
async for data in publisher.run():
|
|
57
60
|
for asset in assets:
|
|
58
|
-
data.
|
|
61
|
+
data.resource = KRNAssetDataStream(asset, data.resource.data_stream)
|
|
59
62
|
msg = message_from_message_data(data, app.outputs)
|
|
60
63
|
if msg is not None:
|
|
61
64
|
await app.publish(msg)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from datetime import timedelta
|
|
3
|
+
from typing import AsyncGenerator
|
|
4
|
+
|
|
5
|
+
from kelvin.message import CustomAction, CustomActionMsg
|
|
6
|
+
from kelvin.publisher import DataGenerator
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class CustomActionGenerator(DataGenerator):
|
|
10
|
+
def __init__(self) -> None:
|
|
11
|
+
print("Hello from MyGenerator")
|
|
12
|
+
|
|
13
|
+
async def run(self) -> AsyncGenerator[CustomActionMsg, None]:
|
|
14
|
+
print("Running MyGenerator")
|
|
15
|
+
|
|
16
|
+
for i in range(10):
|
|
17
|
+
msg = CustomAction(
|
|
18
|
+
resource="krn:asset:test-asset-1",
|
|
19
|
+
expiration_date=timedelta(seconds=30),
|
|
20
|
+
type="example-in",
|
|
21
|
+
title="hello generator",
|
|
22
|
+
description="big description",
|
|
23
|
+
payload={"big": "payload"},
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
print("msg", msg)
|
|
27
|
+
|
|
28
|
+
yield msg
|
|
29
|
+
await asyncio.sleep(1)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# yaml-language-server: $schema=https://apps.kelvininc.com/schemas/kelvin/5.0.0/importer.json
|
|
2
|
+
|
|
3
|
+
spec_version: 5.0.0
|
|
4
|
+
type: importer
|
|
5
|
+
|
|
6
|
+
name: custom-action-example
|
|
7
|
+
title: Custom Action Example
|
|
8
|
+
description: Receive and send a custom action
|
|
9
|
+
version: 1.0.0
|
|
10
|
+
|
|
11
|
+
custom_actions:
|
|
12
|
+
inputs:
|
|
13
|
+
- type: example-in
|
|
14
|
+
outputs:
|
|
15
|
+
- type: example-out
|
|
16
|
+
- type: example-out-2
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from datetime import timedelta
|
|
3
|
+
|
|
4
|
+
from kelvin.application import KelvinApp
|
|
5
|
+
from kelvin.krn import KRNAsset
|
|
6
|
+
from kelvin.message import CustomAction, Recommendation
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class CustomActionApp:
|
|
10
|
+
def __init__(self, app: KelvinApp = KelvinApp()) -> None:
|
|
11
|
+
self.app = app
|
|
12
|
+
self.app.on_custom_action = self.on_custom_action
|
|
13
|
+
|
|
14
|
+
async def on_custom_action(self, action: CustomAction) -> None:
|
|
15
|
+
"""Callback when a Custom Action is received."""
|
|
16
|
+
print("Received Custom Action: ", action)
|
|
17
|
+
|
|
18
|
+
await self.app.publish(action.result(success=True, message="Custom Action applied"))
|
|
19
|
+
# OR
|
|
20
|
+
# await self.app.publish(
|
|
21
|
+
# CustomActionAckMsg(
|
|
22
|
+
# resource=action.resource,
|
|
23
|
+
# payload={
|
|
24
|
+
# "id": action._msg.id,
|
|
25
|
+
# "success": True,
|
|
26
|
+
# "message": "Custom Action applied",
|
|
27
|
+
# },
|
|
28
|
+
# )
|
|
29
|
+
# )
|
|
30
|
+
|
|
31
|
+
async def publisher_task(self) -> None:
|
|
32
|
+
i = 0
|
|
33
|
+
while True:
|
|
34
|
+
await self.app.publish(
|
|
35
|
+
CustomAction(
|
|
36
|
+
resource=KRNAsset("test-asset-1"),
|
|
37
|
+
trace_id=f"trace-{i}",
|
|
38
|
+
type="example-out",
|
|
39
|
+
title=f"Test Action {i}",
|
|
40
|
+
description=f"This is the test action number {i}",
|
|
41
|
+
expiration_date=timedelta(seconds=30),
|
|
42
|
+
payload={"big": "payload", "action number": i},
|
|
43
|
+
)
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
await asyncio.sleep(1)
|
|
47
|
+
|
|
48
|
+
await self.app.publish(
|
|
49
|
+
Recommendation(
|
|
50
|
+
resource=KRNAsset("test-asset-1"),
|
|
51
|
+
type="test-recommendation",
|
|
52
|
+
description=f"This is the recommendation number {i}",
|
|
53
|
+
expiration_date=timedelta(minutes=5),
|
|
54
|
+
trace_id=f"rec-trace-{i}",
|
|
55
|
+
actions=[
|
|
56
|
+
CustomAction(
|
|
57
|
+
resource=KRNAsset("test-asset-1"),
|
|
58
|
+
type="example-out-2",
|
|
59
|
+
title=f"Test Action {i}",
|
|
60
|
+
description=f"This is the test action number {i}",
|
|
61
|
+
expiration_date=timedelta(minutes=5),
|
|
62
|
+
payload={"big": "payload", "action number": i},
|
|
63
|
+
)
|
|
64
|
+
],
|
|
65
|
+
auto_accepted=True,
|
|
66
|
+
)
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
i += 1
|
|
70
|
+
await asyncio.sleep(5)
|
|
71
|
+
|
|
72
|
+
async def run(self) -> None:
|
|
73
|
+
"""Run the app."""
|
|
74
|
+
await self.app.connect()
|
|
75
|
+
await self.publisher_task()
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
if __name__ == "__main__":
|
|
79
|
+
app = CustomActionApp()
|
|
80
|
+
asyncio.run(app.run())
|
|
@@ -16,7 +16,7 @@ from kelvin.krn import KRNAsset, KRNAssetDataStream
|
|
|
16
16
|
from kelvin.logs import configure_logger, logger
|
|
17
17
|
from kelvin.message import AssetDataMessage, ControlChangeStatus, KMessageType, KMessageTypeData, Message
|
|
18
18
|
from kelvin.message.base_messages import Resource, RuntimeManifest
|
|
19
|
-
from kelvin.message.msg_builders import MessageBuilder
|
|
19
|
+
from kelvin.message.msg_builders import CustomAction, MessageBuilder, convert_message
|
|
20
20
|
|
|
21
21
|
if TYPE_CHECKING:
|
|
22
22
|
from kelvin.application.window import HoppingWindow, RollingWindow, TumblingWindow
|
|
@@ -92,6 +92,8 @@ class KelvinApp:
|
|
|
92
92
|
""" Callback when a control change is received. """
|
|
93
93
|
self.on_control_status: Optional[Callable[[ControlChangeStatus], Awaitable[None]]] = None
|
|
94
94
|
""" Callback when a control status is received. """
|
|
95
|
+
self.on_custom_action: Optional[Callable[[CustomAction], Awaitable[None]]] = None
|
|
96
|
+
""" Callback when a custom action is received. """
|
|
95
97
|
|
|
96
98
|
self.on_asset_change: Optional[Callable[[Optional[AssetInfo], Optional[AssetInfo]], Awaitable[None]]] = None
|
|
97
99
|
""" Callback when an asset is added, removed or changed.
|
|
@@ -294,18 +296,21 @@ class KelvinApp:
|
|
|
294
296
|
if self.on_app_configuration is not None and self._config_received.is_set():
|
|
295
297
|
await self.on_app_configuration(self.app_configuration)
|
|
296
298
|
|
|
297
|
-
inputs =
|
|
298
|
-
outputs =
|
|
299
|
+
inputs = {}
|
|
300
|
+
outputs = {}
|
|
299
301
|
assets_in_manifest = set()
|
|
300
302
|
for resource in msg.payload.resources:
|
|
301
|
-
|
|
303
|
+
# check resource is asset
|
|
304
|
+
if not isinstance(resource.resource, KRNAsset):
|
|
302
305
|
continue
|
|
303
306
|
|
|
304
|
-
|
|
307
|
+
asset_name = resource.resource.asset
|
|
305
308
|
|
|
306
|
-
|
|
309
|
+
assets_in_manifest.add(asset_name)
|
|
310
|
+
|
|
311
|
+
self._last_asset_resources[asset_name] = resource
|
|
307
312
|
asset_info = AssetInfo(
|
|
308
|
-
name=
|
|
313
|
+
name=asset_name, properties=resource.properties, parameters=resource.parameters, datastreams={}
|
|
309
314
|
)
|
|
310
315
|
|
|
311
316
|
for ds_name, datastream in resource.datastreams.items():
|
|
@@ -315,7 +320,7 @@ class KelvinApp:
|
|
|
315
320
|
|
|
316
321
|
name = datastream.map_to if datastream.map_to else ds_name
|
|
317
322
|
asset_info.datastreams[name] = ResourceDatastream(
|
|
318
|
-
asset=
|
|
323
|
+
asset=resource.resource,
|
|
319
324
|
io_name=name,
|
|
320
325
|
access=datastream.access,
|
|
321
326
|
owned=datastream.owned or False,
|
|
@@ -334,8 +339,8 @@ class KelvinApp:
|
|
|
334
339
|
name=name, type=KMessageTypeData(manif_ds.primitive_type_name) # type: ignore
|
|
335
340
|
)
|
|
336
341
|
|
|
337
|
-
old_asset_info = self._assets.get(
|
|
338
|
-
self._assets[
|
|
342
|
+
old_asset_info = self._assets.get(asset_name, None)
|
|
343
|
+
self._assets[asset_name] = asset_info
|
|
339
344
|
|
|
340
345
|
if self.on_asset_change is not None and self._config_received.is_set():
|
|
341
346
|
await self.on_asset_change(asset_info, old_asset_info)
|
|
@@ -370,11 +375,17 @@ class KelvinApp:
|
|
|
370
375
|
await self.on_control_status(msg) # type: ignore
|
|
371
376
|
return
|
|
372
377
|
|
|
378
|
+
if self.on_custom_action is not None and filters.is_custom_action(msg):
|
|
379
|
+
converted = convert_message(msg)
|
|
380
|
+
await self.on_custom_action(converted) # type: ignore
|
|
381
|
+
return
|
|
382
|
+
|
|
373
383
|
def _route_to_filters(self, msg: Message) -> None:
|
|
374
384
|
for queue, func in self._filters:
|
|
375
385
|
if func(msg) is True:
|
|
386
|
+
converted = convert_message(msg) or msg # convert to message builder
|
|
376
387
|
# todo: check if the message is reference
|
|
377
|
-
queue.put_nowait(
|
|
388
|
+
queue.put_nowait(converted)
|
|
378
389
|
|
|
379
390
|
def filter(self, func: filters.KelvinFilterType) -> Queue[Message]:
|
|
380
391
|
"""Creates a filter for the received Kelvin Messages based on a filter function.
|
|
@@ -6,6 +6,7 @@ from typing_extensions import TypeAlias
|
|
|
6
6
|
|
|
7
7
|
from kelvin.krn import KRN, KRNAssetDataStream
|
|
8
8
|
from kelvin.message import ControlChangeStatus, KMessageTypeData, Message
|
|
9
|
+
from kelvin.message.base_messages import CustomActionMsg
|
|
9
10
|
|
|
10
11
|
KelvinFilterType: TypeAlias = Callable[[Message], bool]
|
|
11
12
|
|
|
@@ -62,3 +63,7 @@ def asset_equals(asset: Union[str, List[str]]) -> KelvinFilterType:
|
|
|
62
63
|
return msg.resource.asset == asset
|
|
63
64
|
|
|
64
65
|
return _check
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def is_custom_action(msg: Message) -> bool:
|
|
69
|
+
return isinstance(msg, CustomActionMsg)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
-
from datetime import datetime, timedelta
|
|
2
|
+
from datetime import datetime, timedelta, timezone
|
|
3
3
|
from typing import AsyncGenerator, Dict, List, Optional, Tuple
|
|
4
4
|
|
|
5
5
|
from kelvin.message.primitives import AssetDataMessage
|
|
@@ -12,6 +12,9 @@ except ImportError as e:
|
|
|
12
12
|
) from e
|
|
13
13
|
|
|
14
14
|
|
|
15
|
+
UTC = timezone.utc
|
|
16
|
+
|
|
17
|
+
|
|
15
18
|
def round_nearest_time(dt: datetime, round_to: Optional[timedelta] = None) -> datetime:
|
|
16
19
|
"""
|
|
17
20
|
Rounds the given datetime to the nearest time interval as specified by self.round_to.
|
|
@@ -86,7 +89,7 @@ class BaseWindow:
|
|
|
86
89
|
Initializes the dataframes for the specified assets and datastreams.
|
|
87
90
|
"""
|
|
88
91
|
return {
|
|
89
|
-
asset: pd.DataFrame(columns=self.datastreams, index=pd.DatetimeIndex([], name="timestamp"))
|
|
92
|
+
asset: pd.DataFrame(columns=self.datastreams, index=pd.DatetimeIndex([], name="timestamp", tz=UTC))
|
|
90
93
|
for asset in self.assets
|
|
91
94
|
}
|
|
92
95
|
|
|
@@ -97,12 +100,16 @@ class BaseWindow:
|
|
|
97
100
|
Args:
|
|
98
101
|
message (AssetDataMessage): The message to append.
|
|
99
102
|
window_start (Optional[datetime]): The start time of the window to check against.
|
|
103
|
+
If supplied, it must be timezone-aware.
|
|
100
104
|
"""
|
|
101
105
|
asset = message.resource.asset
|
|
102
106
|
datastream = message.resource.data_stream
|
|
103
107
|
value = message.payload
|
|
104
108
|
timestamp = round_nearest_time(message.timestamp, self.round_to)
|
|
105
|
-
timestamp = timestamp.
|
|
109
|
+
timestamp = timestamp.astimezone(UTC) # Ensure timestamp is in UTC
|
|
110
|
+
|
|
111
|
+
if window_start is not None:
|
|
112
|
+
window_start = window_start.astimezone(UTC)
|
|
106
113
|
|
|
107
114
|
if asset not in self.assets:
|
|
108
115
|
# Ignore messages for assets not in the list
|
|
@@ -192,16 +199,18 @@ class BaseTimeWindow(BaseWindow):
|
|
|
192
199
|
"""
|
|
193
200
|
Streams data windows continuously by consuming messages and yielding appropriate windows.
|
|
194
201
|
|
|
202
|
+
Args:
|
|
203
|
+
window_start (Optional[datetime]): The start time of the window. Defaults to the current time.
|
|
204
|
+
If provided, it must be timezone-aware.
|
|
205
|
+
|
|
195
206
|
Yields:
|
|
196
207
|
AsyncGenerator[Tuple[str, pd.DataFrame], None]: Generator yielding asset and its respective DataFrame.
|
|
197
208
|
"""
|
|
198
|
-
if window_start is None
|
|
199
|
-
window_start = datetime.now()
|
|
200
|
-
|
|
209
|
+
window_start = datetime.now(UTC) if window_start is None else window_start.astimezone(UTC)
|
|
201
210
|
window_end = window_start + self.hop_size
|
|
202
211
|
|
|
203
212
|
while True:
|
|
204
|
-
remaining_time = (window_end - datetime.now()).total_seconds()
|
|
213
|
+
remaining_time = (window_end - datetime.now(UTC)).total_seconds()
|
|
205
214
|
if remaining_time <= 0:
|
|
206
215
|
for asset, df in self.dataframes.items():
|
|
207
216
|
df.sort_index(inplace=True) # Sort to ensure chronological order
|
|
@@ -4,10 +4,10 @@ import json
|
|
|
4
4
|
import os
|
|
5
5
|
from enum import Enum
|
|
6
6
|
from pathlib import Path
|
|
7
|
-
from typing import Any, Literal, Optional
|
|
7
|
+
from typing import Any, List, Literal, Optional
|
|
8
8
|
|
|
9
9
|
from pydantic import BaseModel, StringConstraints
|
|
10
|
-
from typing_extensions import Annotated
|
|
10
|
+
from typing_extensions import Annotated, override
|
|
11
11
|
|
|
12
12
|
NameDNS = Annotated[str, StringConstraints(pattern="^[a-z0-9]([-a-z0-9]*[a-z0-9])?$")]
|
|
13
13
|
VersionStr = Annotated[
|
|
@@ -16,6 +16,7 @@ VersionStr = Annotated[
|
|
|
16
16
|
pattern="^([0-9]+)\\.([0-9]+)\\.([0-9]+)(?:-([0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*))?(?:\\+[0-9A-Za-z-]+)?$"
|
|
17
17
|
),
|
|
18
18
|
]
|
|
19
|
+
CustomActionTypeStr = Annotated[str, StringConstraints(pattern="^[a-zA-Z0-9]([-_ .a-zA-Z0-9]*[a-zA-Z0-9])?$")]
|
|
19
20
|
|
|
20
21
|
|
|
21
22
|
class ConfigError(Exception):
|
|
@@ -25,7 +26,8 @@ class ConfigError(Exception):
|
|
|
25
26
|
class ConfigBaseModel(BaseModel):
|
|
26
27
|
model_config = {"populate_by_name": True}
|
|
27
28
|
|
|
28
|
-
|
|
29
|
+
@override
|
|
30
|
+
def model_dump( # type: ignore[override]
|
|
29
31
|
self,
|
|
30
32
|
mode: Literal["json", "python"] | str = "json",
|
|
31
33
|
by_alias: bool = True,
|
|
@@ -65,6 +67,15 @@ class PrimitiveTypes(str, Enum):
|
|
|
65
67
|
object = "object"
|
|
66
68
|
|
|
67
69
|
|
|
70
|
+
class CustomActionDef(ConfigBaseModel):
|
|
71
|
+
type: CustomActionTypeStr
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class CustomActionsIO(ConfigBaseModel):
|
|
75
|
+
inputs: List[CustomActionDef] = []
|
|
76
|
+
outputs: List[CustomActionDef] = []
|
|
77
|
+
|
|
78
|
+
|
|
68
79
|
class AppBaseConfig(ConfigBaseModel):
|
|
69
80
|
name: NameDNS
|
|
70
81
|
title: str
|
|
@@ -5,17 +5,19 @@ from typing import Dict, List, Literal, Optional
|
|
|
5
5
|
|
|
6
6
|
from pydantic import Field
|
|
7
7
|
|
|
8
|
-
from kelvin.config.common import AppBaseConfig, AppTypes, ConfigBaseModel, read_schema_file
|
|
8
|
+
from kelvin.config.common import AppBaseConfig, AppTypes, ConfigBaseModel, CustomActionsIO, read_schema_file
|
|
9
9
|
|
|
10
10
|
from .manifest import (
|
|
11
11
|
AppDefaults,
|
|
12
12
|
AppManifest,
|
|
13
|
+
CustomActionWay,
|
|
13
14
|
DefaultsDefinition,
|
|
14
15
|
DynamicIODefinition,
|
|
15
16
|
DynamicIoOwnership,
|
|
16
17
|
DynamicIoType,
|
|
17
18
|
Flags,
|
|
18
19
|
IOSchema,
|
|
20
|
+
ManifCustomAction,
|
|
19
21
|
RuntimeUpdateFlags,
|
|
20
22
|
SchemasDefinition,
|
|
21
23
|
)
|
|
@@ -27,6 +29,7 @@ class RuntimeUpdateConfig(ConfigBaseModel):
|
|
|
27
29
|
|
|
28
30
|
class ExporterFlags(ConfigBaseModel):
|
|
29
31
|
enable_runtime_update: RuntimeUpdateConfig = RuntimeUpdateConfig()
|
|
32
|
+
resources_required: Optional[bool] = None
|
|
30
33
|
|
|
31
34
|
|
|
32
35
|
class SchemasConfig(ConfigBaseModel):
|
|
@@ -52,6 +55,7 @@ class ExporterConfig(AppBaseConfig):
|
|
|
52
55
|
exporter_io: List[ExporterIO] = []
|
|
53
56
|
ui_schemas: SchemasConfig = SchemasConfig()
|
|
54
57
|
defaults: DeploymentDefaults = DeploymentDefaults()
|
|
58
|
+
custom_actions: CustomActionsIO = CustomActionsIO()
|
|
55
59
|
|
|
56
60
|
def to_manifest(self, read_schemas: bool = True, workdir: Path = Path(".")) -> AppManifest:
|
|
57
61
|
return convert_exporter_to_manifest(self, read_schemas=read_schemas, workdir=workdir)
|
|
@@ -70,6 +74,10 @@ def convert_exporter_to_manifest(
|
|
|
70
74
|
for io, schema_path in config.ui_schemas.io_configuration.items()
|
|
71
75
|
]
|
|
72
76
|
|
|
77
|
+
manif_custom_actions = [
|
|
78
|
+
ManifCustomAction(type=cai.type, way=CustomActionWay.input_ca) for cai in config.custom_actions.inputs
|
|
79
|
+
] + [ManifCustomAction(type=cai.type, way=CustomActionWay.output_ca) for cai in config.custom_actions.outputs]
|
|
80
|
+
|
|
73
81
|
return AppManifest(
|
|
74
82
|
name=config.name,
|
|
75
83
|
title=config.title,
|
|
@@ -80,6 +88,7 @@ def convert_exporter_to_manifest(
|
|
|
80
88
|
flags=Flags(
|
|
81
89
|
spec_version=config.spec_version,
|
|
82
90
|
enable_runtime_update=RuntimeUpdateFlags(configuration=config.flags.enable_runtime_update.configuration),
|
|
91
|
+
resources_required=config.flags.resources_required,
|
|
83
92
|
),
|
|
84
93
|
dynamic_io=[
|
|
85
94
|
DynamicIODefinition(
|
|
@@ -94,4 +103,5 @@ def convert_exporter_to_manifest(
|
|
|
94
103
|
defaults=DefaultsDefinition(
|
|
95
104
|
app=AppDefaults(configuration=config.defaults.configuration), system=config.defaults.system
|
|
96
105
|
),
|
|
106
|
+
custom_actions=manif_custom_actions,
|
|
97
107
|
)
|
|
@@ -5,17 +5,19 @@ from typing import Dict, List, Literal, Optional
|
|
|
5
5
|
|
|
6
6
|
from pydantic import Field
|
|
7
7
|
|
|
8
|
-
from kelvin.config.common import AppBaseConfig, AppTypes, ConfigBaseModel, read_schema_file
|
|
8
|
+
from kelvin.config.common import AppBaseConfig, AppTypes, ConfigBaseModel, CustomActionsIO, read_schema_file
|
|
9
9
|
|
|
10
10
|
from .manifest import (
|
|
11
11
|
AppDefaults,
|
|
12
12
|
AppManifest,
|
|
13
|
+
CustomActionWay,
|
|
13
14
|
DefaultsDefinition,
|
|
14
15
|
DynamicIODefinition,
|
|
15
16
|
DynamicIoOwnership,
|
|
16
17
|
DynamicIoType,
|
|
17
18
|
Flags,
|
|
18
19
|
IOSchema,
|
|
20
|
+
ManifCustomAction,
|
|
19
21
|
RuntimeUpdateFlags,
|
|
20
22
|
SchemasDefinition,
|
|
21
23
|
)
|
|
@@ -27,6 +29,7 @@ class RuntimeUpdateConfig(ConfigBaseModel):
|
|
|
27
29
|
|
|
28
30
|
class ImporterFlags(ConfigBaseModel):
|
|
29
31
|
enable_runtime_update: RuntimeUpdateConfig = RuntimeUpdateConfig()
|
|
32
|
+
resources_required: Optional[bool] = None
|
|
30
33
|
|
|
31
34
|
|
|
32
35
|
class SchemasConfig(ConfigBaseModel):
|
|
@@ -53,6 +56,7 @@ class ImporterConfig(AppBaseConfig):
|
|
|
53
56
|
importer_io: List[ImporterIO] = []
|
|
54
57
|
ui_schemas: SchemasConfig = SchemasConfig()
|
|
55
58
|
defaults: DeploymentDefaults = DeploymentDefaults()
|
|
59
|
+
custom_actions: CustomActionsIO = CustomActionsIO()
|
|
56
60
|
|
|
57
61
|
def to_manifest(self, read_schemas: bool = True, workdir: Path = Path(".")) -> AppManifest:
|
|
58
62
|
return convert_importer_to_manifest(self, read_schemas=read_schemas, workdir=workdir)
|
|
@@ -71,6 +75,10 @@ def convert_importer_to_manifest(
|
|
|
71
75
|
for io, schema_path in config.ui_schemas.io_configuration.items()
|
|
72
76
|
]
|
|
73
77
|
|
|
78
|
+
manif_custom_actions = [
|
|
79
|
+
ManifCustomAction(type=cai.type, way=CustomActionWay.input_ca) for cai in config.custom_actions.inputs
|
|
80
|
+
] + [ManifCustomAction(type=cai.type, way=CustomActionWay.output_ca) for cai in config.custom_actions.outputs]
|
|
81
|
+
|
|
74
82
|
return AppManifest(
|
|
75
83
|
name=config.name,
|
|
76
84
|
title=config.title,
|
|
@@ -81,6 +89,7 @@ def convert_importer_to_manifest(
|
|
|
81
89
|
flags=Flags(
|
|
82
90
|
spec_version=config.spec_version,
|
|
83
91
|
enable_runtime_update=RuntimeUpdateFlags(configuration=config.flags.enable_runtime_update.configuration),
|
|
92
|
+
resources_required=config.flags.resources_required,
|
|
84
93
|
),
|
|
85
94
|
dynamic_io=[
|
|
86
95
|
DynamicIODefinition(
|
|
@@ -95,4 +104,5 @@ def convert_importer_to_manifest(
|
|
|
95
104
|
defaults=DefaultsDefinition(
|
|
96
105
|
app=AppDefaults(configuration=config.defaults.configuration), system=config.defaults.system
|
|
97
106
|
),
|
|
107
|
+
custom_actions=manif_custom_actions,
|
|
98
108
|
)
|
|
@@ -5,7 +5,7 @@ from typing import Any, Dict, List, Literal, Optional
|
|
|
5
5
|
|
|
6
6
|
from pydantic import Field
|
|
7
7
|
|
|
8
|
-
from kelvin.config.common import AppBaseConfig, ConfigBaseModel, VersionStr
|
|
8
|
+
from kelvin.config.common import AppBaseConfig, ConfigBaseModel, CustomActionTypeStr, VersionStr
|
|
9
9
|
from kelvin.krn import KRN
|
|
10
10
|
from kelvin.message import ParameterType
|
|
11
11
|
from kelvin.message.msg_type import PrimitiveTypes
|
|
@@ -26,6 +26,7 @@ class Flags(ConfigBaseModel):
|
|
|
26
26
|
spec_version: VersionStr
|
|
27
27
|
enable_runtime_update: RuntimeUpdateFlags = RuntimeUpdateFlags()
|
|
28
28
|
deployment: DeploymentFlags = DeploymentFlags()
|
|
29
|
+
resources_required: Optional[bool] = None
|
|
29
30
|
|
|
30
31
|
|
|
31
32
|
class IOWay(str, Enum):
|
|
@@ -74,6 +75,16 @@ class DynamicIODefinition(ConfigBaseModel):
|
|
|
74
75
|
data_types: List[str] = []
|
|
75
76
|
|
|
76
77
|
|
|
78
|
+
class CustomActionWay(str, Enum):
|
|
79
|
+
input_ca = "input-ca"
|
|
80
|
+
output_ca = "output-ca"
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class ManifCustomAction(ConfigBaseModel):
|
|
84
|
+
type: CustomActionTypeStr
|
|
85
|
+
way: CustomActionWay
|
|
86
|
+
|
|
87
|
+
|
|
77
88
|
class ParamDefinition(ConfigBaseModel):
|
|
78
89
|
name: str
|
|
79
90
|
title: Optional[str] = None
|
|
@@ -136,3 +147,4 @@ class AppManifest(AppBaseConfig):
|
|
|
136
147
|
parameters: List[ParamDefinition] = []
|
|
137
148
|
schemas: SchemasDefinition = SchemasDefinition()
|
|
138
149
|
defaults: DefaultsDefinition = DefaultsDefinition()
|
|
150
|
+
custom_actions: List[ManifCustomAction] = []
|
|
@@ -3,18 +3,27 @@ from __future__ import annotations
|
|
|
3
3
|
from pathlib import Path
|
|
4
4
|
from typing import Dict, List, Literal, Optional
|
|
5
5
|
|
|
6
|
-
from kelvin.config.common import
|
|
6
|
+
from kelvin.config.common import (
|
|
7
|
+
AppBaseConfig,
|
|
8
|
+
AppTypes,
|
|
9
|
+
ConfigBaseModel,
|
|
10
|
+
ConfigError,
|
|
11
|
+
CustomActionsIO,
|
|
12
|
+
read_schema_file,
|
|
13
|
+
)
|
|
7
14
|
from kelvin.message import ParameterType
|
|
8
15
|
from kelvin.message.msg_type import PrimitiveTypes
|
|
9
16
|
|
|
10
17
|
from .manifest import (
|
|
11
18
|
AppDefaults,
|
|
12
19
|
AppManifest,
|
|
20
|
+
CustomActionWay,
|
|
13
21
|
DefaultsDefinition,
|
|
14
22
|
Flags,
|
|
15
23
|
IODatastreamMapping,
|
|
16
24
|
IODefinition,
|
|
17
25
|
IOWay,
|
|
26
|
+
ManifCustomAction,
|
|
18
27
|
ParamDefinition,
|
|
19
28
|
RuntimeUpdateFlags,
|
|
20
29
|
SchemasDefinition,
|
|
@@ -75,6 +84,7 @@ class SmartAppConfig(AppBaseConfig):
|
|
|
75
84
|
parameters: List[SmartAppParams] = []
|
|
76
85
|
ui_schemas: SchemasConfig = SchemasConfig()
|
|
77
86
|
defaults: DeploymentDefaults = DeploymentDefaults()
|
|
87
|
+
custom_actions: CustomActionsIO = CustomActionsIO()
|
|
78
88
|
|
|
79
89
|
def to_manifest(self, read_schemas: bool = True, workdir: Path = Path(".")) -> AppManifest:
|
|
80
90
|
return convert_smart_app_to_manifest(self, read_schemas=read_schemas, workdir=workdir)
|
|
@@ -147,6 +157,10 @@ def convert_smart_app_to_manifest(
|
|
|
147
157
|
read_schema_file(workdir / config.ui_schemas.parameters) if config.ui_schemas.parameters else {}
|
|
148
158
|
)
|
|
149
159
|
|
|
160
|
+
manif_custom_actions = [
|
|
161
|
+
ManifCustomAction(type=cai.type, way=CustomActionWay.input_ca) for cai in config.custom_actions.inputs
|
|
162
|
+
] + [ManifCustomAction(type=cai.type, way=CustomActionWay.output_ca) for cai in config.custom_actions.outputs]
|
|
163
|
+
|
|
150
164
|
return AppManifest(
|
|
151
165
|
name=config.name,
|
|
152
166
|
title=config.title,
|
|
@@ -162,6 +176,7 @@ def convert_smart_app_to_manifest(
|
|
|
162
176
|
resource_properties=config.flags.enable_runtime_update.resource_properties,
|
|
163
177
|
configuration=config.flags.enable_runtime_update.configuration,
|
|
164
178
|
),
|
|
179
|
+
resources_required=True,
|
|
165
180
|
),
|
|
166
181
|
parameters=[
|
|
167
182
|
ParamDefinition(name=p.name, data_type=p.data_type, default=config.defaults.parameters.get(p.name))
|
|
@@ -173,4 +188,5 @@ def convert_smart_app_to_manifest(
|
|
|
173
188
|
app=AppDefaults(configuration=config.defaults.configuration, io_datastream_mapping=io_map),
|
|
174
189
|
system=config.defaults.system,
|
|
175
190
|
),
|
|
191
|
+
custom_actions=manif_custom_actions,
|
|
176
192
|
)
|