OpenOrchestrator 1.0.2__tar.gz → 1.2.0__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.
- OpenOrchestrator-1.2.0/OpenOrchestrator/__init__.py +7 -0
- {OpenOrchestrator-1.0.2 → OpenOrchestrator-1.2.0}/OpenOrchestrator/__main__.py +2 -0
- OpenOrchestrator-1.2.0/OpenOrchestrator/common/connection_frame.py +81 -0
- {OpenOrchestrator-1.0.2 → OpenOrchestrator-1.2.0}/OpenOrchestrator/common/crypto_util.py +4 -4
- OpenOrchestrator-1.2.0/OpenOrchestrator/common/datetime_util.py +20 -0
- {OpenOrchestrator-1.0.2 → OpenOrchestrator-1.2.0}/OpenOrchestrator/database/constants.py +25 -19
- {OpenOrchestrator-1.0.2 → OpenOrchestrator-1.2.0}/OpenOrchestrator/database/db_util.py +77 -30
- {OpenOrchestrator-1.0.2 → OpenOrchestrator-1.2.0}/OpenOrchestrator/database/logs.py +13 -0
- {OpenOrchestrator-1.0.2 → OpenOrchestrator-1.2.0}/OpenOrchestrator/database/queues.py +17 -0
- {OpenOrchestrator-1.0.2 → OpenOrchestrator-1.2.0}/OpenOrchestrator/database/triggers.py +25 -56
- OpenOrchestrator-1.2.0/OpenOrchestrator/orchestrator/application.py +94 -0
- OpenOrchestrator-1.2.0/OpenOrchestrator/orchestrator/datetime_input.py +75 -0
- OpenOrchestrator-1.2.0/OpenOrchestrator/orchestrator/popups/constant_popup.py +95 -0
- OpenOrchestrator-1.2.0/OpenOrchestrator/orchestrator/popups/credential_popup.py +99 -0
- OpenOrchestrator-1.2.0/OpenOrchestrator/orchestrator/popups/generic_popups.py +27 -0
- OpenOrchestrator-1.2.0/OpenOrchestrator/orchestrator/popups/trigger_popup.py +216 -0
- OpenOrchestrator-1.2.0/OpenOrchestrator/orchestrator/tabs/constants_tab.py +52 -0
- OpenOrchestrator-1.2.0/OpenOrchestrator/orchestrator/tabs/logging_tab.py +70 -0
- OpenOrchestrator-1.2.0/OpenOrchestrator/orchestrator/tabs/queue_tab.py +116 -0
- OpenOrchestrator-1.2.0/OpenOrchestrator/orchestrator/tabs/settings_tab.py +22 -0
- OpenOrchestrator-1.2.0/OpenOrchestrator/orchestrator/tabs/trigger_tab.py +87 -0
- {OpenOrchestrator-1.0.2 → OpenOrchestrator-1.2.0}/OpenOrchestrator/scheduler/application.py +3 -3
- {OpenOrchestrator-1.0.2/OpenOrchestrator/common → OpenOrchestrator-1.2.0/OpenOrchestrator/scheduler}/connection_frame.py +1 -0
- OpenOrchestrator-1.2.0/OpenOrchestrator/scheduler/run_tab.py +175 -0
- {OpenOrchestrator-1.0.2 → OpenOrchestrator-1.2.0}/OpenOrchestrator/scheduler/runner.py +33 -25
- {OpenOrchestrator-1.0.2 → OpenOrchestrator-1.2.0}/OpenOrchestrator/scheduler/settings_tab.py +2 -1
- {OpenOrchestrator-1.0.2 → OpenOrchestrator-1.2.0}/OpenOrchestrator.egg-info/PKG-INFO +2 -2
- {OpenOrchestrator-1.0.2 → OpenOrchestrator-1.2.0}/OpenOrchestrator.egg-info/SOURCES.txt +13 -9
- {OpenOrchestrator-1.0.2 → OpenOrchestrator-1.2.0}/OpenOrchestrator.egg-info/requires.txt +1 -1
- {OpenOrchestrator-1.0.2 → OpenOrchestrator-1.2.0}/PKG-INFO +2 -2
- {OpenOrchestrator-1.0.2 → OpenOrchestrator-1.2.0}/pyproject.toml +3 -3
- OpenOrchestrator-1.2.0/tests/test_db_util.py +298 -0
- OpenOrchestrator-1.2.0/tests/test_orchestrator_connection.py +118 -0
- OpenOrchestrator-1.0.2/OpenOrchestrator/orchestrator/application.py +0 -41
- OpenOrchestrator-1.0.2/OpenOrchestrator/orchestrator/constants_tab.py +0 -169
- OpenOrchestrator-1.0.2/OpenOrchestrator/orchestrator/logging_tab.py +0 -221
- OpenOrchestrator-1.0.2/OpenOrchestrator/orchestrator/popups/constant_popup.py +0 -77
- OpenOrchestrator-1.0.2/OpenOrchestrator/orchestrator/popups/credential_popup.py +0 -89
- OpenOrchestrator-1.0.2/OpenOrchestrator/orchestrator/popups/queue_trigger_popup.py +0 -129
- OpenOrchestrator-1.0.2/OpenOrchestrator/orchestrator/popups/scheduled_trigger_popup.py +0 -129
- OpenOrchestrator-1.0.2/OpenOrchestrator/orchestrator/popups/single_trigger_popup.py +0 -134
- OpenOrchestrator-1.0.2/OpenOrchestrator/orchestrator/settings_tab.py +0 -31
- OpenOrchestrator-1.0.2/OpenOrchestrator/orchestrator/table_util.py +0 -76
- OpenOrchestrator-1.0.2/OpenOrchestrator/orchestrator/trigger_tab.py +0 -231
- OpenOrchestrator-1.0.2/OpenOrchestrator/scheduler/__init__.py +0 -0
- OpenOrchestrator-1.0.2/OpenOrchestrator/scheduler/run_tab.py +0 -168
- {OpenOrchestrator-1.0.2 → OpenOrchestrator-1.2.0}/LICENSE +0 -0
- {OpenOrchestrator-1.0.2/OpenOrchestrator → OpenOrchestrator-1.2.0/OpenOrchestrator/common}/__init__.py +0 -0
- {OpenOrchestrator-1.0.2/OpenOrchestrator/common → OpenOrchestrator-1.2.0/OpenOrchestrator/database}/__init__.py +0 -0
- {OpenOrchestrator-1.0.2/OpenOrchestrator/database → OpenOrchestrator-1.2.0/OpenOrchestrator/orchestrator}/__init__.py +0 -0
- {OpenOrchestrator-1.0.2/OpenOrchestrator/orchestrator → OpenOrchestrator-1.2.0/OpenOrchestrator/orchestrator/popups}/__init__.py +0 -0
- {OpenOrchestrator-1.0.2/OpenOrchestrator/orchestrator/popups → OpenOrchestrator-1.2.0/OpenOrchestrator/orchestrator_connection}/__init__.py +0 -0
- {OpenOrchestrator-1.0.2 → OpenOrchestrator-1.2.0}/OpenOrchestrator/orchestrator_connection/connection.py +0 -0
- {OpenOrchestrator-1.0.2/OpenOrchestrator/orchestrator_connection → OpenOrchestrator-1.2.0/OpenOrchestrator/scheduler}/__init__.py +0 -0
- {OpenOrchestrator-1.0.2 → OpenOrchestrator-1.2.0}/OpenOrchestrator.egg-info/dependency_links.txt +0 -0
- {OpenOrchestrator-1.0.2 → OpenOrchestrator-1.2.0}/OpenOrchestrator.egg-info/top_level.txt +0 -0
- {OpenOrchestrator-1.0.2 → OpenOrchestrator-1.2.0}/README.md +0 -0
- {OpenOrchestrator-1.0.2 → OpenOrchestrator-1.2.0}/setup.cfg +0 -0
@@ -5,9 +5,11 @@ import sys
|
|
5
5
|
from OpenOrchestrator.scheduler.application import Application as s_app
|
6
6
|
from OpenOrchestrator.orchestrator.application import Application as o_app
|
7
7
|
|
8
|
+
|
8
9
|
def _print_usage():
|
9
10
|
print("Usage: -o to start Orchestrator. -s to start Scheduler")
|
10
11
|
|
12
|
+
|
11
13
|
if len(sys.argv) != 2:
|
12
14
|
_print_usage()
|
13
15
|
elif sys.argv[1] == '-s':
|
@@ -0,0 +1,81 @@
|
|
1
|
+
"""This module contains a single class: ConnectionFrame."""
|
2
|
+
|
3
|
+
import os
|
4
|
+
|
5
|
+
from nicegui import ui
|
6
|
+
|
7
|
+
from OpenOrchestrator.common import crypto_util
|
8
|
+
from OpenOrchestrator.database import db_util
|
9
|
+
|
10
|
+
|
11
|
+
# pylint: disable-next=too-few-public-methods
|
12
|
+
class ConnectionFrame():
|
13
|
+
"""A ui module containing input and buttons for connecting to the database."""
|
14
|
+
def __init__(self):
|
15
|
+
self.conn_input = ui.input("Connection String").classes("w-4/6")
|
16
|
+
self.key_input = ui.input("Encryption Key").classes("w-4/6")
|
17
|
+
self._define_validation()
|
18
|
+
with ui.row().classes("w-full"):
|
19
|
+
self.conn_button = ui.button("Connect", on_click=self._connect)
|
20
|
+
self.disconn_button = ui.button("Disconnect", on_click=self._disconnect, )
|
21
|
+
self.disconn_button.disable()
|
22
|
+
|
23
|
+
self._initial_connect()
|
24
|
+
|
25
|
+
def _define_validation(self):
|
26
|
+
self.conn_input.validation = {"Please enter a connection string": bool}
|
27
|
+
self.key_input.validation = {"Invalid AES key": crypto_util.validate_key}
|
28
|
+
|
29
|
+
def _connect(self) -> None:
|
30
|
+
"""Validate the connection string and encryption key
|
31
|
+
and connect to the database.
|
32
|
+
"""
|
33
|
+
if not self.conn_input.validate() & self.key_input.validate():
|
34
|
+
ui.notify("Please fill out all fields.", type='warning')
|
35
|
+
return
|
36
|
+
|
37
|
+
conn_string = self.conn_input.value
|
38
|
+
crypto_key = self.key_input.value
|
39
|
+
|
40
|
+
if db_util.connect(conn_string):
|
41
|
+
crypto_util.set_key(crypto_key)
|
42
|
+
self._set_state(True)
|
43
|
+
ui.notify("Connected!", type='positive')
|
44
|
+
|
45
|
+
def _disconnect(self) -> None:
|
46
|
+
db_util.disconnect()
|
47
|
+
crypto_util.set_key(None)
|
48
|
+
self._set_state(False)
|
49
|
+
ui.notify("Disconnected!", type='positive')
|
50
|
+
|
51
|
+
def _set_state(self, connected: bool) -> None:
|
52
|
+
if connected:
|
53
|
+
self.conn_input.disable()
|
54
|
+
self.key_input.disable()
|
55
|
+
self.conn_button.disable()
|
56
|
+
self.disconn_button.enable()
|
57
|
+
else:
|
58
|
+
self.conn_input.enable()
|
59
|
+
self.key_input.enable()
|
60
|
+
self.conn_button.enable()
|
61
|
+
self.disconn_button.disable()
|
62
|
+
|
63
|
+
def _initial_connect(self) -> None:
|
64
|
+
"""Check the environment for a connection string
|
65
|
+
and encryption key and connect to the database if both
|
66
|
+
are found.
|
67
|
+
"""
|
68
|
+
conn_string = os.environ.get('OpenOrchestratorConnString', None)
|
69
|
+
if conn_string:
|
70
|
+
self.conn_input.value = conn_string
|
71
|
+
|
72
|
+
crypto_key = os.environ.get('OpenOrchestratorKey', None)
|
73
|
+
if crypto_key:
|
74
|
+
self.key_input.value = crypto_key
|
75
|
+
|
76
|
+
def new_key(self):
|
77
|
+
"""Creates a new encryption key and inserts it
|
78
|
+
into the key entry.
|
79
|
+
"""
|
80
|
+
key = crypto_util.generate_key()
|
81
|
+
self.key_input.value = key.decode()
|
@@ -3,9 +3,9 @@
|
|
3
3
|
from cryptography.fernet import Fernet
|
4
4
|
from cryptography.exceptions import InvalidSignature
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
6
|
+
|
7
|
+
_encryption_key: str = None
|
8
|
+
|
9
9
|
|
10
10
|
def generate_key() -> bytes:
|
11
11
|
"""Generates a new valid AES crypto key.
|
@@ -20,7 +20,7 @@ def set_key(key: str) -> None:
|
|
20
20
|
"""Set the crypto key for the module.
|
21
21
|
The key will be used in all subsequent calls to this module.
|
22
22
|
"""
|
23
|
-
global _encryption_key
|
23
|
+
global _encryption_key # pylint: disable=global-statement
|
24
24
|
_encryption_key = key
|
25
25
|
|
26
26
|
|
@@ -0,0 +1,20 @@
|
|
1
|
+
"""A module for performing common tasks regarding datetimes."""
|
2
|
+
|
3
|
+
|
4
|
+
from datetime import datetime
|
5
|
+
|
6
|
+
|
7
|
+
def format_datetime(datetime_: datetime, default: str = 'N/A') -> str:
|
8
|
+
"""Format a datetime to a string.
|
9
|
+
|
10
|
+
Args:
|
11
|
+
datetime_: The datetime to format.
|
12
|
+
default: A default string to return if the datetime is None. Defaults to 'N/A'.
|
13
|
+
|
14
|
+
Returns:
|
15
|
+
A datetime string in the format %d-%m-%Y %H:%M:%S.
|
16
|
+
"""
|
17
|
+
if not datetime_:
|
18
|
+
return default
|
19
|
+
|
20
|
+
return datetime_.strftime("%d-%m-%Y %H:%M:%S")
|
@@ -5,9 +5,12 @@ from datetime import datetime
|
|
5
5
|
from sqlalchemy import String, Engine
|
6
6
|
from sqlalchemy.orm import Mapped, DeclarativeBase, mapped_column
|
7
7
|
|
8
|
+
from OpenOrchestrator.common import datetime_util
|
9
|
+
|
8
10
|
# All classes in this module are effectively dataclasses without methods.
|
9
11
|
# pylint: disable=too-few-public-methods
|
10
12
|
|
13
|
+
|
11
14
|
class Base(DeclarativeBase):
|
12
15
|
"""SqlAlchemy base class for all ORM classes in this module."""
|
13
16
|
|
@@ -20,13 +23,13 @@ class Constant(Base):
|
|
20
23
|
value: Mapped[str] = mapped_column(String(1000))
|
21
24
|
changed_at: Mapped[datetime] = mapped_column(onupdate=datetime.now, default=datetime.now)
|
22
25
|
|
23
|
-
def
|
24
|
-
"""Convert
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
26
|
+
def to_row_dict(self) -> dict[str, str]:
|
27
|
+
"""Convert constant to a row dictionary for display in a table."""
|
28
|
+
return {
|
29
|
+
"Constant Name": self.name,
|
30
|
+
"Value": self.value,
|
31
|
+
"Last Changed": datetime_util.format_datetime(self.changed_at)
|
32
|
+
}
|
30
33
|
|
31
34
|
|
32
35
|
class Credential(Base):
|
@@ -38,18 +41,21 @@ class Credential(Base):
|
|
38
41
|
password: Mapped[str] = mapped_column(String(1000))
|
39
42
|
changed_at: Mapped[datetime] = mapped_column(onupdate=datetime.now, default=datetime.now)
|
40
43
|
|
41
|
-
def
|
42
|
-
"""Convert
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
)
|
44
|
+
def to_row_dict(self) -> dict[str, str]:
|
45
|
+
"""Convert credential to a row dictionary for display in a table."""
|
46
|
+
return {
|
47
|
+
"Credential Name": self.name,
|
48
|
+
"Username": self.username,
|
49
|
+
"Password": self.format_password(),
|
50
|
+
"Last Changed": datetime_util.format_datetime(self.changed_at)
|
51
|
+
}
|
52
|
+
|
53
|
+
def format_password(self) -> str:
|
54
|
+
"""Format the password to be shown in a table."""
|
55
|
+
length = len(self.password)
|
56
|
+
lower_length = int(((length-100)/20)*16)
|
57
|
+
upper_length = lower_length + 15
|
58
|
+
return f"{length} encrypted bytes. {lower_length}-{upper_length} decrypted bytes."
|
53
59
|
|
54
60
|
|
55
61
|
def create_tables(engine: Engine):
|
@@ -1,9 +1,9 @@
|
|
1
1
|
"""This module handles the connection to the database in OpenOrchestrator."""
|
2
2
|
|
3
3
|
from datetime import datetime
|
4
|
-
from
|
5
|
-
from typing import Callable
|
4
|
+
from typing import Callable, TypeVar, ParamSpec
|
6
5
|
|
6
|
+
from croniter import croniter
|
7
7
|
from sqlalchemy import Engine, create_engine, select, insert, desc
|
8
8
|
from sqlalchemy import exc as alc_exc
|
9
9
|
from sqlalchemy import func as alc_func
|
@@ -16,7 +16,11 @@ from OpenOrchestrator.database.constants import Constant, Credential
|
|
16
16
|
from OpenOrchestrator.database.triggers import Trigger, SingleTrigger, ScheduledTrigger, QueueTrigger, TriggerStatus
|
17
17
|
from OpenOrchestrator.database.queues import QueueElement, QueueStatus
|
18
18
|
|
19
|
-
|
19
|
+
# Type hint helpers for decorators
|
20
|
+
T = TypeVar("T")
|
21
|
+
P = ParamSpec("P")
|
22
|
+
|
23
|
+
_connection_engine: Engine = None
|
20
24
|
|
21
25
|
|
22
26
|
def connect(conn_string: str) -> bool:
|
@@ -35,28 +39,27 @@ def connect(conn_string: str) -> bool:
|
|
35
39
|
engine.connect()
|
36
40
|
_connection_engine = engine
|
37
41
|
return True
|
38
|
-
except alc_exc.InterfaceError
|
42
|
+
except (alc_exc.InterfaceError, alc_exc.ArgumentError, alc_exc.OperationalError):
|
39
43
|
_connection_engine = None
|
40
|
-
messagebox.showerror("Connection failed", str(exc))
|
41
44
|
|
42
45
|
return False
|
43
46
|
|
44
47
|
|
45
48
|
def disconnect() -> None:
|
46
49
|
"""Disconnect from the database."""
|
47
|
-
global _connection_engine
|
50
|
+
global _connection_engine # pylint: disable=global-statement
|
48
51
|
_connection_engine.dispose()
|
49
52
|
_connection_engine = None
|
50
53
|
|
51
54
|
|
52
|
-
def catch_db_error(func: Callable) -> Callable:
|
55
|
+
def catch_db_error(func: Callable[P, T]) -> Callable[P, T]:
|
53
56
|
"""A decorator that catches errors in SQL calls."""
|
54
|
-
def inner(*args, **kwargs):
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
57
|
+
def inner(*args, **kwargs) -> T:
|
58
|
+
if _connection_engine is None:
|
59
|
+
raise RuntimeError("Not connected to Database")
|
60
|
+
|
61
|
+
return func(*args, **kwargs)
|
62
|
+
|
60
63
|
return inner
|
61
64
|
|
62
65
|
|
@@ -64,12 +67,14 @@ def get_conn_string() -> str:
|
|
64
67
|
"""Get the connection string.
|
65
68
|
|
66
69
|
Returns:
|
67
|
-
str: The connection string.
|
70
|
+
str: The connection string if any.
|
68
71
|
"""
|
69
72
|
try:
|
70
73
|
return str(_connection_engine.url)
|
71
|
-
except AttributeError
|
72
|
-
|
74
|
+
except AttributeError:
|
75
|
+
pass
|
76
|
+
|
77
|
+
return None
|
73
78
|
|
74
79
|
|
75
80
|
@catch_db_error
|
@@ -79,7 +84,6 @@ def initialize_database() -> None:
|
|
79
84
|
triggers.create_tables(_connection_engine)
|
80
85
|
constants.create_tables(_connection_engine)
|
81
86
|
queues.create_tables(_connection_engine)
|
82
|
-
messagebox.showinfo("Database initialized", "Database has been initialized!")
|
83
87
|
|
84
88
|
|
85
89
|
@catch_db_error
|
@@ -101,6 +105,21 @@ def get_trigger(trigger_id: str) -> Trigger:
|
|
101
105
|
return session.scalar(query)
|
102
106
|
|
103
107
|
|
108
|
+
@catch_db_error
|
109
|
+
def get_all_triggers() -> tuple[Trigger]:
|
110
|
+
"""Get all triggers in the database.
|
111
|
+
|
112
|
+
Returns:
|
113
|
+
A tuple of Trigger objects.
|
114
|
+
"""
|
115
|
+
with Session(_connection_engine) as session:
|
116
|
+
query = (
|
117
|
+
select(Trigger)
|
118
|
+
.options(selectin_polymorphic(Trigger, (ScheduledTrigger, QueueTrigger, SingleTrigger)))
|
119
|
+
)
|
120
|
+
return tuple(session.scalars(query))
|
121
|
+
|
122
|
+
|
104
123
|
@catch_db_error
|
105
124
|
def update_trigger(trigger: Trigger):
|
106
125
|
"""Updates an existing trigger in the database.
|
@@ -111,6 +130,7 @@ def update_trigger(trigger: Trigger):
|
|
111
130
|
with Session(_connection_engine) as session:
|
112
131
|
session.add(trigger)
|
113
132
|
session.commit()
|
133
|
+
session.refresh(trigger)
|
114
134
|
|
115
135
|
|
116
136
|
@catch_db_error
|
@@ -167,8 +187,8 @@ def delete_trigger(trigger_id: str) -> None:
|
|
167
187
|
|
168
188
|
@catch_db_error
|
169
189
|
def get_logs(offset: int, limit: int,
|
170
|
-
from_date: datetime
|
171
|
-
process_name: str
|
190
|
+
from_date: datetime = None, to_date: datetime = None,
|
191
|
+
process_name: str = None, log_level: LogLevel = None) -> tuple[Log]:
|
172
192
|
"""Get the logs from the database using filters and pagination.
|
173
193
|
|
174
194
|
Args:
|
@@ -343,7 +363,7 @@ def get_constant(name: str) -> Constant:
|
|
343
363
|
|
344
364
|
Returns:
|
345
365
|
Constant: The constant with the given name.
|
346
|
-
|
366
|
+
|
347
367
|
Raises:
|
348
368
|
ValueError: If no constant with the given name exists.
|
349
369
|
"""
|
@@ -418,7 +438,7 @@ def get_credential(name: str) -> Credential:
|
|
418
438
|
|
419
439
|
Returns:
|
420
440
|
Credential: The credential with the given name.
|
421
|
-
|
441
|
+
|
422
442
|
Raises:
|
423
443
|
ValueError: If no credential with the given name exists.
|
424
444
|
"""
|
@@ -427,6 +447,7 @@ def get_credential(name: str) -> Credential:
|
|
427
447
|
|
428
448
|
if credential is None:
|
429
449
|
raise ValueError(f"No credential with name '{name}' was found.")
|
450
|
+
|
430
451
|
credential.password = crypto_util.decrypt_string(credential.password)
|
431
452
|
return credential
|
432
453
|
|
@@ -501,12 +522,12 @@ def delete_credential(name: str) -> None:
|
|
501
522
|
|
502
523
|
@catch_db_error
|
503
524
|
def begin_single_trigger(trigger_id: str) -> bool:
|
504
|
-
"""Set the status of a single trigger to 'running' and
|
525
|
+
"""Set the status of a single trigger to 'running' and
|
505
526
|
set the last run time to the current time.
|
506
527
|
|
507
528
|
Args:
|
508
529
|
trigger_id: The id of the trigger to begin.
|
509
|
-
|
530
|
+
|
510
531
|
Returns:
|
511
532
|
bool: True if the trigger was 'idle' and now 'running'.
|
512
533
|
"""
|
@@ -560,15 +581,15 @@ def get_next_scheduled_trigger() -> ScheduledTrigger | None:
|
|
560
581
|
|
561
582
|
|
562
583
|
@catch_db_error
|
563
|
-
def begin_scheduled_trigger(trigger_id: str
|
564
|
-
"""Set the status of a scheduled trigger to 'running',
|
584
|
+
def begin_scheduled_trigger(trigger_id: str) -> bool:
|
585
|
+
"""Set the status of a scheduled trigger to 'running',
|
565
586
|
set the last run time to the current time,
|
566
587
|
and set the next run time to the given datetime.
|
567
588
|
|
568
589
|
Args:
|
569
590
|
trigger_id: The id of the trigger to begin.
|
570
591
|
next_run: The next datetime the trigger should run.
|
571
|
-
|
592
|
+
|
572
593
|
Returns:
|
573
594
|
bool: True if the trigger was 'idle' and now 'running'.
|
574
595
|
"""
|
@@ -580,7 +601,7 @@ def begin_scheduled_trigger(trigger_id: str, next_run: datetime) -> bool:
|
|
580
601
|
|
581
602
|
trigger.process_status = TriggerStatus.RUNNING
|
582
603
|
trigger.last_run = datetime.now()
|
583
|
-
trigger.next_run =
|
604
|
+
trigger.next_run = croniter(trigger.cron_expr, datetime.now()).get_next(datetime)
|
584
605
|
|
585
606
|
session.commit()
|
586
607
|
return True
|
@@ -617,12 +638,12 @@ def get_next_queue_trigger() -> QueueTrigger | None:
|
|
617
638
|
|
618
639
|
@catch_db_error
|
619
640
|
def begin_queue_trigger(trigger_id: str) -> None:
|
620
|
-
"""Set the status of a queue trigger to 'running' and
|
641
|
+
"""Set the status of a queue trigger to 'running' and
|
621
642
|
set the last run time to the current time.
|
622
643
|
|
623
644
|
Args:
|
624
645
|
trigger_id: The id of the trigger to begin.
|
625
|
-
|
646
|
+
|
626
647
|
Returns:
|
627
648
|
bool: True if the trigger was 'idle' and now 'running'.
|
628
649
|
"""
|
@@ -773,7 +794,7 @@ def get_queue_elements(queue_name: str, reference: str = None, status: QueueStat
|
|
773
794
|
query = (
|
774
795
|
select(QueueElement)
|
775
796
|
.where(QueueElement.queue_name == queue_name)
|
776
|
-
.order_by(QueueElement.created_date)
|
797
|
+
.order_by(desc(QueueElement.created_date))
|
777
798
|
.offset(offset)
|
778
799
|
.limit(limit)
|
779
800
|
)
|
@@ -787,6 +808,32 @@ def get_queue_elements(queue_name: str, reference: str = None, status: QueueStat
|
|
787
808
|
return tuple(result)
|
788
809
|
|
789
810
|
|
811
|
+
@catch_db_error
|
812
|
+
def get_queue_count() -> dict[str, dict[QueueStatus, int]]:
|
813
|
+
"""Count the number of queue elements of each status for every queue.
|
814
|
+
|
815
|
+
Returns:
|
816
|
+
A dict for each queue with the count for each status. E.g. result[queue_name][status] => count.
|
817
|
+
"""
|
818
|
+
with Session(_connection_engine) as session:
|
819
|
+
query = (
|
820
|
+
select(QueueElement.queue_name, QueueElement.status, alc_func.count()) # pylint: disable=not-callable
|
821
|
+
.group_by(QueueElement.queue_name)
|
822
|
+
.group_by(QueueElement.status)
|
823
|
+
)
|
824
|
+
rows = session.execute(query)
|
825
|
+
rows = tuple(rows)
|
826
|
+
|
827
|
+
# Aggregate result into a dict
|
828
|
+
result = {}
|
829
|
+
for queue_name, status, count in rows:
|
830
|
+
if queue_name not in result:
|
831
|
+
result[queue_name] = {}
|
832
|
+
result[queue_name][status] = count
|
833
|
+
|
834
|
+
return result
|
835
|
+
|
836
|
+
|
790
837
|
@catch_db_error
|
791
838
|
def set_queue_element_status(element_id: str, status: QueueStatus, message: str = None) -> None:
|
792
839
|
"""Set the status of a queue element.
|
@@ -7,9 +7,12 @@ import uuid
|
|
7
7
|
from sqlalchemy import String, Engine
|
8
8
|
from sqlalchemy.orm import Mapped, DeclarativeBase, mapped_column
|
9
9
|
|
10
|
+
from OpenOrchestrator.common import datetime_util
|
11
|
+
|
10
12
|
# All classes in this module are effectively dataclasses without methods.
|
11
13
|
# pylint: disable=too-few-public-methods
|
12
14
|
|
15
|
+
|
13
16
|
class LogLevel(enum.Enum):
|
14
17
|
"""An enum representing the level of logs."""
|
15
18
|
TRACE = "Trace"
|
@@ -31,6 +34,16 @@ class Log(Base):
|
|
31
34
|
process_name: Mapped[str] = mapped_column(String(100))
|
32
35
|
log_message: Mapped[str] = mapped_column(String(8000))
|
33
36
|
|
37
|
+
def to_row_dict(self) -> dict[str, str]:
|
38
|
+
"""Convert log to a row dictionary for display in a table."""
|
39
|
+
return {
|
40
|
+
"Log Time": datetime_util.format_datetime(self.log_time),
|
41
|
+
"Level": self.log_level.value,
|
42
|
+
"Process Name": self.process_name,
|
43
|
+
"Message": self.log_message,
|
44
|
+
"ID": str(self.id)
|
45
|
+
}
|
46
|
+
|
34
47
|
|
35
48
|
def create_tables(engine: Engine):
|
36
49
|
"""Create all SQL tables related to ORM classes in this module.
|
@@ -8,9 +8,12 @@ import uuid
|
|
8
8
|
from sqlalchemy import String, Engine
|
9
9
|
from sqlalchemy.orm import Mapped, DeclarativeBase, mapped_column
|
10
10
|
|
11
|
+
from OpenOrchestrator.common import datetime_util
|
12
|
+
|
11
13
|
# All classes in this module are effectively dataclasses without methods.
|
12
14
|
# pylint: disable=too-few-public-methods
|
13
15
|
|
16
|
+
|
14
17
|
class QueueStatus(enum.Enum):
|
15
18
|
"""An enum representing the status of a queue element."""
|
16
19
|
NEW = 'New'
|
@@ -38,6 +41,20 @@ class QueueElement(Base):
|
|
38
41
|
message: Mapped[Optional[str]] = mapped_column(String(1000))
|
39
42
|
created_by: Mapped[Optional[str]] = mapped_column(String(100))
|
40
43
|
|
44
|
+
def to_row_dict(self) -> dict:
|
45
|
+
"""Convert the object to a dict for display in a table."""
|
46
|
+
return {
|
47
|
+
"ID": self.id,
|
48
|
+
"Reference": self.reference,
|
49
|
+
"Status": self.status,
|
50
|
+
"Data": self.data,
|
51
|
+
"Created Date": datetime_util.format_datetime(self.created_date),
|
52
|
+
"Start Date": datetime_util.format_datetime(self.start_date),
|
53
|
+
"End Date": datetime_util.format_datetime(self.end_date),
|
54
|
+
"Message": self.message,
|
55
|
+
"Created By": self.created_by
|
56
|
+
}
|
57
|
+
|
41
58
|
|
42
59
|
def create_tables(engine: Engine):
|
43
60
|
"""Create all SQL tables related to ORM classes in this module.
|
@@ -8,6 +8,8 @@ import uuid
|
|
8
8
|
from sqlalchemy import String, ForeignKey, Engine
|
9
9
|
from sqlalchemy.orm import Mapped, DeclarativeBase, mapped_column
|
10
10
|
|
11
|
+
from OpenOrchestrator.common import datetime_util
|
12
|
+
|
11
13
|
# All classes in this module are effectively dataclasses without methods.
|
12
14
|
# pylint: disable=too-few-public-methods
|
13
15
|
|
@@ -55,6 +57,17 @@ class Trigger(Base):
|
|
55
57
|
def __repr__(self) -> str:
|
56
58
|
return f"{self.trigger_name}: {self.type.value}"
|
57
59
|
|
60
|
+
def to_row_dict(self) -> dict[str, str]:
|
61
|
+
"""Convert trigger to a row dictionary for display in a table."""
|
62
|
+
return {
|
63
|
+
"Trigger Name": self.trigger_name,
|
64
|
+
"Type": self.type.value,
|
65
|
+
"Status": self.process_status.value,
|
66
|
+
"Process Name": self.process_name,
|
67
|
+
"Last Run": datetime_util.format_datetime(self.last_run, "Never"),
|
68
|
+
"ID": str(self.id)
|
69
|
+
}
|
70
|
+
|
58
71
|
|
59
72
|
class SingleTrigger(Trigger):
|
60
73
|
"""A class representing single trigger objects in the ORM."""
|
@@ -65,24 +78,10 @@ class SingleTrigger(Trigger):
|
|
65
78
|
|
66
79
|
__mapper_args__ = {"polymorphic_identity": TriggerType.SINGLE}
|
67
80
|
|
68
|
-
def
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
tuple: A tuple of all the triggers values.
|
73
|
-
"""
|
74
|
-
return (
|
75
|
-
self.trigger_name,
|
76
|
-
self.process_status.value,
|
77
|
-
self.process_name,
|
78
|
-
self.last_run,
|
79
|
-
self.next_run,
|
80
|
-
self.process_path,
|
81
|
-
self.process_args,
|
82
|
-
self.is_git_repo,
|
83
|
-
self.is_blocking,
|
84
|
-
self.id
|
85
|
-
)
|
81
|
+
def to_row_dict(self) -> dict[str, str]:
|
82
|
+
row_dict = super().to_row_dict()
|
83
|
+
row_dict["Next Run"] = datetime_util.format_datetime(self.next_run)
|
84
|
+
return row_dict
|
86
85
|
|
87
86
|
|
88
87
|
class ScheduledTrigger(Trigger):
|
@@ -95,25 +94,10 @@ class ScheduledTrigger(Trigger):
|
|
95
94
|
|
96
95
|
__mapper_args__ = {"polymorphic_identity": TriggerType.SCHEDULED}
|
97
96
|
|
98
|
-
def
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
tuple: A tuple of all the triggers values.
|
103
|
-
"""
|
104
|
-
return (
|
105
|
-
self.trigger_name,
|
106
|
-
self.process_status.value,
|
107
|
-
self.process_name,
|
108
|
-
self.cron_expr,
|
109
|
-
self.last_run,
|
110
|
-
self.next_run,
|
111
|
-
self.process_path,
|
112
|
-
self.process_args,
|
113
|
-
self.is_git_repo,
|
114
|
-
self.is_blocking,
|
115
|
-
self.id
|
116
|
-
)
|
97
|
+
def to_row_dict(self) -> dict[str, str]:
|
98
|
+
row_dict = super().to_row_dict()
|
99
|
+
row_dict["Next Run"] = datetime_util.format_datetime(self.next_run)
|
100
|
+
return row_dict
|
117
101
|
|
118
102
|
|
119
103
|
class QueueTrigger(Trigger):
|
@@ -126,25 +110,10 @@ class QueueTrigger(Trigger):
|
|
126
110
|
|
127
111
|
__mapper_args__ = {"polymorphic_identity": TriggerType.QUEUE}
|
128
112
|
|
129
|
-
def
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
tuple: A tuple of all the triggers values.
|
134
|
-
"""
|
135
|
-
return (
|
136
|
-
self.trigger_name,
|
137
|
-
self.process_status.value,
|
138
|
-
self.process_name,
|
139
|
-
self.queue_name,
|
140
|
-
self.min_batch_size,
|
141
|
-
self.last_run,
|
142
|
-
self.process_path,
|
143
|
-
self.process_args,
|
144
|
-
self.is_git_repo,
|
145
|
-
self.is_blocking,
|
146
|
-
self.id
|
147
|
-
)
|
113
|
+
def to_row_dict(self) -> dict[str, str]:
|
114
|
+
row_dict = super().to_row_dict()
|
115
|
+
row_dict["Next Run"] = "N/A"
|
116
|
+
return row_dict
|
148
117
|
|
149
118
|
|
150
119
|
def create_tables(engine: Engine):
|