mbu-dev-shared-components 2.4.7__tar.gz → 3.0.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.
- {mbu_dev_shared_components-2.4.7 → mbu_dev_shared_components-3.0.0}/PKG-INFO +1 -1
- mbu_dev_shared_components-3.0.0/mbu_dev_shared_components/database/__init__.py +4 -0
- mbu_dev_shared_components-3.0.0/mbu_dev_shared_components/database/connection.py +49 -0
- mbu_dev_shared_components-3.0.0/mbu_dev_shared_components/database/constants.py +54 -0
- mbu_dev_shared_components-3.0.0/mbu_dev_shared_components/database/logging.py +139 -0
- mbu_dev_shared_components-3.0.0/mbu_dev_shared_components/database/utility.py +109 -0
- {mbu_dev_shared_components-2.4.7 → mbu_dev_shared_components-3.0.0}/mbu_dev_shared_components/solteqtand/application/document.py +0 -10
- {mbu_dev_shared_components-2.4.7 → mbu_dev_shared_components-3.0.0}/mbu_dev_shared_components.egg-info/PKG-INFO +1 -1
- {mbu_dev_shared_components-2.4.7 → mbu_dev_shared_components-3.0.0}/mbu_dev_shared_components.egg-info/SOURCES.txt +3 -1
- {mbu_dev_shared_components-2.4.7 → mbu_dev_shared_components-3.0.0}/pyproject.toml +1 -5
- mbu_dev_shared_components-3.0.0/tests/test_database.py +319 -0
- mbu_dev_shared_components-2.4.7/mbu_dev_shared_components/database/constants.py +0 -131
- mbu_dev_shared_components-2.4.7/mbu_dev_shared_components/database/logging.py +0 -86
- mbu_dev_shared_components-2.4.7/mbu_dev_shared_components/database/utility.py +0 -44
- mbu_dev_shared_components-2.4.7/mbu_dev_shared_components/utils/__init__.py +0 -0
- {mbu_dev_shared_components-2.4.7 → mbu_dev_shared_components-3.0.0}/LICENSE +0 -0
- {mbu_dev_shared_components-2.4.7 → mbu_dev_shared_components-3.0.0}/README.md +0 -0
- {mbu_dev_shared_components-2.4.7/mbu_dev_shared_components/database → mbu_dev_shared_components-3.0.0/mbu_dev_shared_components/getorganized}/__init__.py +0 -0
- {mbu_dev_shared_components-2.4.7 → mbu_dev_shared_components-3.0.0}/mbu_dev_shared_components/getorganized/auth.py +0 -0
- {mbu_dev_shared_components-2.4.7 → mbu_dev_shared_components-3.0.0}/mbu_dev_shared_components/getorganized/cases.py +0 -0
- {mbu_dev_shared_components-2.4.7 → mbu_dev_shared_components-3.0.0}/mbu_dev_shared_components/getorganized/contacts.py +0 -0
- {mbu_dev_shared_components-2.4.7 → mbu_dev_shared_components-3.0.0}/mbu_dev_shared_components/getorganized/documents.py +0 -0
- {mbu_dev_shared_components-2.4.7 → mbu_dev_shared_components-3.0.0}/mbu_dev_shared_components/getorganized/objects.py +0 -0
- {mbu_dev_shared_components-2.4.7/mbu_dev_shared_components/getorganized → mbu_dev_shared_components-3.0.0/mbu_dev_shared_components/google}/__init__.py +0 -0
- {mbu_dev_shared_components-2.4.7/mbu_dev_shared_components/google → mbu_dev_shared_components-3.0.0/mbu_dev_shared_components/google/api}/__init__.py +0 -0
- {mbu_dev_shared_components-2.4.7 → mbu_dev_shared_components-3.0.0}/mbu_dev_shared_components/google/api/auth.py +0 -0
- {mbu_dev_shared_components-2.4.7/mbu_dev_shared_components/google/api → mbu_dev_shared_components-3.0.0/mbu_dev_shared_components/google/workspace}/__init__.py +0 -0
- {mbu_dev_shared_components-2.4.7 → mbu_dev_shared_components-3.0.0}/mbu_dev_shared_components/google/workspace/alerts.py +0 -0
- {mbu_dev_shared_components-2.4.7/mbu_dev_shared_components/google/workspace → mbu_dev_shared_components-3.0.0/mbu_dev_shared_components/msoffice365}/__init__.py +0 -0
- {mbu_dev_shared_components-2.4.7/mbu_dev_shared_components/msoffice365 → mbu_dev_shared_components-3.0.0/mbu_dev_shared_components/msoffice365/excel}/__init__.py +0 -0
- {mbu_dev_shared_components-2.4.7 → mbu_dev_shared_components-3.0.0}/mbu_dev_shared_components/msoffice365/excel/excel_reader.py +0 -0
- {mbu_dev_shared_components-2.4.7/mbu_dev_shared_components/msoffice365/excel → mbu_dev_shared_components-3.0.0/mbu_dev_shared_components/msoffice365/sharepoint_api}/__init__.py +0 -0
- {mbu_dev_shared_components-2.4.7 → mbu_dev_shared_components-3.0.0}/mbu_dev_shared_components/msoffice365/sharepoint_api/files.py +0 -0
- {mbu_dev_shared_components-2.4.7/mbu_dev_shared_components/msoffice365/sharepoint_api → mbu_dev_shared_components-3.0.0/mbu_dev_shared_components/os2forms}/__init__.py +0 -0
- {mbu_dev_shared_components-2.4.7 → mbu_dev_shared_components-3.0.0}/mbu_dev_shared_components/os2forms/documents.py +0 -0
- {mbu_dev_shared_components-2.4.7 → mbu_dev_shared_components-3.0.0}/mbu_dev_shared_components/os2forms/forms.py +0 -0
- {mbu_dev_shared_components-2.4.7/mbu_dev_shared_components/os2forms → mbu_dev_shared_components-3.0.0/mbu_dev_shared_components/romexis}/__init__.py +0 -0
- {mbu_dev_shared_components-2.4.7 → mbu_dev_shared_components-3.0.0}/mbu_dev_shared_components/romexis/db_handler.py +0 -0
- {mbu_dev_shared_components-2.4.7 → mbu_dev_shared_components-3.0.0}/mbu_dev_shared_components/romexis/helper_functions.py +0 -0
- {mbu_dev_shared_components-2.4.7/mbu_dev_shared_components/romexis → mbu_dev_shared_components-3.0.0/mbu_dev_shared_components/sap}/__init__.py +0 -0
- {mbu_dev_shared_components-2.4.7 → mbu_dev_shared_components-3.0.0}/mbu_dev_shared_components/sap/create_invoice.py +0 -0
- {mbu_dev_shared_components-2.4.7 → mbu_dev_shared_components-3.0.0}/mbu_dev_shared_components/solteqtand/__init__.py +0 -0
- {mbu_dev_shared_components-2.4.7 → mbu_dev_shared_components-3.0.0}/mbu_dev_shared_components/solteqtand/application/__init__.py +0 -0
- {mbu_dev_shared_components-2.4.7 → mbu_dev_shared_components-3.0.0}/mbu_dev_shared_components/solteqtand/application/app_handler.py +0 -0
- {mbu_dev_shared_components-2.4.7 → mbu_dev_shared_components-3.0.0}/mbu_dev_shared_components/solteqtand/application/appointment.py +0 -0
- {mbu_dev_shared_components-2.4.7 → mbu_dev_shared_components-3.0.0}/mbu_dev_shared_components/solteqtand/application/base_ui.py +0 -0
- {mbu_dev_shared_components-2.4.7 → mbu_dev_shared_components-3.0.0}/mbu_dev_shared_components/solteqtand/application/clinic.py +0 -0
- {mbu_dev_shared_components-2.4.7 → mbu_dev_shared_components-3.0.0}/mbu_dev_shared_components/solteqtand/application/edi_portal.py +0 -0
- {mbu_dev_shared_components-2.4.7 → mbu_dev_shared_components-3.0.0}/mbu_dev_shared_components/solteqtand/application/event.py +0 -0
- {mbu_dev_shared_components-2.4.7 → mbu_dev_shared_components-3.0.0}/mbu_dev_shared_components/solteqtand/application/exceptions.py +0 -0
- {mbu_dev_shared_components-2.4.7 → mbu_dev_shared_components-3.0.0}/mbu_dev_shared_components/solteqtand/application/handler_base.py +0 -0
- {mbu_dev_shared_components-2.4.7 → mbu_dev_shared_components-3.0.0}/mbu_dev_shared_components/solteqtand/application/journal_note.py +0 -0
- {mbu_dev_shared_components-2.4.7 → mbu_dev_shared_components-3.0.0}/mbu_dev_shared_components/solteqtand/application/patient.py +0 -0
- {mbu_dev_shared_components-2.4.7 → mbu_dev_shared_components-3.0.0}/mbu_dev_shared_components/solteqtand/database/__init__.py +0 -0
- {mbu_dev_shared_components-2.4.7 → mbu_dev_shared_components-3.0.0}/mbu_dev_shared_components/solteqtand/database/db_handler.py +0 -0
- {mbu_dev_shared_components-2.4.7/mbu_dev_shared_components/sap → mbu_dev_shared_components-3.0.0/mbu_dev_shared_components/utils}/__init__.py +0 -0
- {mbu_dev_shared_components-2.4.7 → mbu_dev_shared_components-3.0.0}/mbu_dev_shared_components/utils/db_stored_procedure_executor.py +0 -0
- {mbu_dev_shared_components-2.4.7 → mbu_dev_shared_components-3.0.0}/mbu_dev_shared_components/utils/fernet_encryptor.py +0 -0
- {mbu_dev_shared_components-2.4.7 → mbu_dev_shared_components-3.0.0}/mbu_dev_shared_components/utils/file_handler.py +0 -0
- {mbu_dev_shared_components-2.4.7 → mbu_dev_shared_components-3.0.0}/mbu_dev_shared_components/utils/json_handler.py +0 -0
- {mbu_dev_shared_components-2.4.7 → mbu_dev_shared_components-3.0.0}/mbu_dev_shared_components.egg-info/dependency_links.txt +0 -0
- {mbu_dev_shared_components-2.4.7 → mbu_dev_shared_components-3.0.0}/mbu_dev_shared_components.egg-info/requires.txt +0 -0
- {mbu_dev_shared_components-2.4.7 → mbu_dev_shared_components-3.0.0}/mbu_dev_shared_components.egg-info/top_level.txt +0 -0
- {mbu_dev_shared_components-2.4.7 → mbu_dev_shared_components-3.0.0}/setup.cfg +0 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""Handles the RPA connection"""
|
|
2
|
+
|
|
3
|
+
from .constants import Constants
|
|
4
|
+
from .utility import Utility
|
|
5
|
+
from .logging import Log
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class RPAConnection(
|
|
9
|
+
Constants,
|
|
10
|
+
Utility,
|
|
11
|
+
Log,
|
|
12
|
+
):
|
|
13
|
+
"""Class for running database related """
|
|
14
|
+
def __init__(self, db_env: str = "PROD", commit: bool | str = False):
|
|
15
|
+
Constants.__init__(self)
|
|
16
|
+
Utility.__init__(self)
|
|
17
|
+
Log.__init__(self)
|
|
18
|
+
self.db_env = db_env
|
|
19
|
+
self.commit = commit if isinstance(commit, bool) else commit == "True"
|
|
20
|
+
self.conn = None
|
|
21
|
+
self.cursor = None
|
|
22
|
+
|
|
23
|
+
def __enter__(self):
|
|
24
|
+
self.conn = self.connect_to_db(autocommit=False, db_env=self.db_env)
|
|
25
|
+
self.cursor = self.conn.cursor()
|
|
26
|
+
|
|
27
|
+
return self
|
|
28
|
+
|
|
29
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
30
|
+
if self.commit:
|
|
31
|
+
print("Commiting transaction...")
|
|
32
|
+
self.conn.commit()
|
|
33
|
+
else:
|
|
34
|
+
print("Rolling back transaction....")
|
|
35
|
+
self.conn.rollback()
|
|
36
|
+
print("Closing conection...")
|
|
37
|
+
self.close()
|
|
38
|
+
print("Connection closed.")
|
|
39
|
+
|
|
40
|
+
def rollback(self):
|
|
41
|
+
"""Rollback transaction on connection if autocommit is not enabled"""
|
|
42
|
+
if self.autocommit:
|
|
43
|
+
raise RuntimeError("Cannot rollback: autocommit is enabled.")
|
|
44
|
+
self.conn.rollback()
|
|
45
|
+
|
|
46
|
+
def close(self):
|
|
47
|
+
"""Closes connection"""
|
|
48
|
+
self.cursor.close()
|
|
49
|
+
self.conn.close()
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""This module handles generating and fetching constants and credentials from the database"""
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
|
|
5
|
+
from mbu_dev_shared_components.utils.fernet_encryptor import Encryptor
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Constants:
|
|
9
|
+
"""Base class for adding and collection constants and credentials"""
|
|
10
|
+
|
|
11
|
+
def add_constant(self, constant_name: str, value: str, changed_at: datetime = datetime.now()):
|
|
12
|
+
query = """
|
|
13
|
+
INSERT INTO [RPA].[rpa].[Constants] ([name], [value], [changed_at])
|
|
14
|
+
VALUES (?, ?, ?)
|
|
15
|
+
"""
|
|
16
|
+
self.execute_query(query, [constant_name, value, changed_at])
|
|
17
|
+
|
|
18
|
+
def get_constant(self, constant_name: str) -> dict:
|
|
19
|
+
query = """
|
|
20
|
+
SELECT name, value FROM [RPA].[rpa].[Constants] WHERE name = ?
|
|
21
|
+
"""
|
|
22
|
+
res = self.execute_query(query, [constant_name])
|
|
23
|
+
if res:
|
|
24
|
+
name, value = res[0]
|
|
25
|
+
return {"constant_name": name, "value": value}
|
|
26
|
+
raise ValueError(f"No constant found with name: {constant_name}")
|
|
27
|
+
|
|
28
|
+
def add_credential(self, credential_name: str, username: str, password: str,
|
|
29
|
+
changed_at: datetime = datetime.now()):
|
|
30
|
+
encryptor = Encryptor()
|
|
31
|
+
encrypted_password = encryptor.encrypt(password)
|
|
32
|
+
query = """
|
|
33
|
+
INSERT INTO [RPA].[rpa].[Credentials] ([name], [username], [password], [changed_at])
|
|
34
|
+
VALUES (?, ?, ?, ?)
|
|
35
|
+
"""
|
|
36
|
+
self.execute_query(query, [credential_name, username, encrypted_password, changed_at])
|
|
37
|
+
|
|
38
|
+
def get_credential(self, credential_name: str) -> dict:
|
|
39
|
+
encryptor = Encryptor()
|
|
40
|
+
query = """
|
|
41
|
+
SELECT username, CAST(password AS varbinary(max))
|
|
42
|
+
FROM [RPA].[rpa].[Credentials]
|
|
43
|
+
WHERE name = ?
|
|
44
|
+
"""
|
|
45
|
+
res = self.execute_query(query, [credential_name])
|
|
46
|
+
if res:
|
|
47
|
+
username, encrypted_password = res[0]
|
|
48
|
+
decrypted_password = encryptor.decrypt(encrypted_password)
|
|
49
|
+
return {
|
|
50
|
+
"username": username,
|
|
51
|
+
"decrypted_password": decrypted_password,
|
|
52
|
+
"encrypted_password": encrypted_password
|
|
53
|
+
}
|
|
54
|
+
raise ValueError(f"No credential found with name {credential_name}")
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"""This module handles logging in the RPA database"""
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
import time
|
|
5
|
+
import socket
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Log:
|
|
9
|
+
"""Base class for handling logging"""
|
|
10
|
+
def log_event(
|
|
11
|
+
self,
|
|
12
|
+
log_db: str,
|
|
13
|
+
level: str,
|
|
14
|
+
message: str,
|
|
15
|
+
context: str,
|
|
16
|
+
):
|
|
17
|
+
"""Logs the inputted parameters in """
|
|
18
|
+
created_at = datetime.now()
|
|
19
|
+
query = f"""
|
|
20
|
+
INSERT INTO RPA.{log_db}
|
|
21
|
+
([level]
|
|
22
|
+
,[message]
|
|
23
|
+
,[created_at]
|
|
24
|
+
,[context])
|
|
25
|
+
VALUES
|
|
26
|
+
(?
|
|
27
|
+
,?
|
|
28
|
+
,?
|
|
29
|
+
,?)
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
params = [level, message, created_at, context]
|
|
33
|
+
self.execute_query(query=query, params=params)
|
|
34
|
+
|
|
35
|
+
def _get_log_event(
|
|
36
|
+
self,
|
|
37
|
+
log_db: str,
|
|
38
|
+
level: str,
|
|
39
|
+
message: str,
|
|
40
|
+
context: str,
|
|
41
|
+
):
|
|
42
|
+
query = f"""
|
|
43
|
+
SELECT
|
|
44
|
+
([level]
|
|
45
|
+
,[message]
|
|
46
|
+
,[created_at]
|
|
47
|
+
,[context])
|
|
48
|
+
FROM
|
|
49
|
+
RPA.{log_db}
|
|
50
|
+
WHERE
|
|
51
|
+
level={level},
|
|
52
|
+
message={message},
|
|
53
|
+
context={context}
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
res = self.execute_query(query=query)
|
|
57
|
+
return res
|
|
58
|
+
|
|
59
|
+
def get_latest_log(
|
|
60
|
+
self,
|
|
61
|
+
log_db: str,
|
|
62
|
+
):
|
|
63
|
+
"""Retrieve latest log message from database"""
|
|
64
|
+
query = f"""
|
|
65
|
+
SELECT TOP (1)
|
|
66
|
+
[level]
|
|
67
|
+
,[message]
|
|
68
|
+
,[created_at]
|
|
69
|
+
,[context]
|
|
70
|
+
FROM
|
|
71
|
+
RPA.{log_db}
|
|
72
|
+
ORDER BY
|
|
73
|
+
created_at desc
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
res = self.execute_query(query=query)
|
|
77
|
+
return res
|
|
78
|
+
|
|
79
|
+
def _send_heartbeat(
|
|
80
|
+
self,
|
|
81
|
+
servicename,
|
|
82
|
+
status,
|
|
83
|
+
details
|
|
84
|
+
):
|
|
85
|
+
"""Function to send heartbeat to database"""
|
|
86
|
+
hostname = socket.gethostname()
|
|
87
|
+
params = {
|
|
88
|
+
"ServiceName": (str, servicename),
|
|
89
|
+
"Status": (str, status),
|
|
90
|
+
"HostName": (str, hostname),
|
|
91
|
+
"Details": (str, details)
|
|
92
|
+
}
|
|
93
|
+
result = self.execute_stored_procedure(
|
|
94
|
+
stored_procedure='rpa.sp_UpdateHeartbeat',
|
|
95
|
+
params=params)
|
|
96
|
+
if result["success"] is not True:
|
|
97
|
+
print(result["error_message"])
|
|
98
|
+
|
|
99
|
+
def log_heartbeat(
|
|
100
|
+
self,
|
|
101
|
+
stop: str | bool,
|
|
102
|
+
servicename: str,
|
|
103
|
+
heartbeat_interval: int,
|
|
104
|
+
details: str = "",
|
|
105
|
+
):
|
|
106
|
+
"""Function to log heartbeat"""
|
|
107
|
+
# Update connection such that it autocommits
|
|
108
|
+
self.conn.autocommit = True # Sets class attribute
|
|
109
|
+
if isinstance(stop, str):
|
|
110
|
+
stop = stop == "True"
|
|
111
|
+
if not isinstance(heartbeat_interval, int):
|
|
112
|
+
heartbeat_interval = int(heartbeat_interval)
|
|
113
|
+
while not stop:
|
|
114
|
+
status = "RUNNING"
|
|
115
|
+
self._send_heartbeat(
|
|
116
|
+
servicename,
|
|
117
|
+
status,
|
|
118
|
+
details,
|
|
119
|
+
)
|
|
120
|
+
time.sleep(heartbeat_interval)
|
|
121
|
+
status = "STOPPED"
|
|
122
|
+
self._send_heartbeat(
|
|
123
|
+
servicename,
|
|
124
|
+
status,
|
|
125
|
+
details,
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
def get_heartbeat(self, service_name: str):
|
|
129
|
+
"""Get hearbeats """
|
|
130
|
+
query = f"""
|
|
131
|
+
SELECT
|
|
132
|
+
*
|
|
133
|
+
FROM
|
|
134
|
+
[RPA].[rpa].[ServiceHeartbeat]
|
|
135
|
+
WHERE
|
|
136
|
+
ServiceName = '{service_name}'
|
|
137
|
+
"""
|
|
138
|
+
res = self.execute_query(query)
|
|
139
|
+
return res
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"""This module handles general database connection and calls"""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import json
|
|
5
|
+
from typing import Dict, Union, Tuple, Any
|
|
6
|
+
from dateutil import parser
|
|
7
|
+
import pyodbc
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Utility:
|
|
11
|
+
"""Base class handling general utilities"""
|
|
12
|
+
def connect_to_db(self, autocommit=True, db_env="PROD") -> pyodbc.Connection:
|
|
13
|
+
"""Establish connection to sql database
|
|
14
|
+
|
|
15
|
+
Returns:
|
|
16
|
+
rpa_conn (pyodbc.Connection): The connection object to the SQL database.
|
|
17
|
+
"""
|
|
18
|
+
connection_env = self.fetch_env(db_env)
|
|
19
|
+
rpa_conn_string = os.getenv(connection_env)
|
|
20
|
+
rpa_conn = pyodbc.connect(rpa_conn_string, autocommit=autocommit)
|
|
21
|
+
return rpa_conn
|
|
22
|
+
|
|
23
|
+
def execute_query(self, query: str, params: list = None) -> pyodbc.Cursor:
|
|
24
|
+
"""Execute SQL query with pyodbc"""
|
|
25
|
+
params = [] if not params else params
|
|
26
|
+
is_select = query.strip().upper().startswith('SELECT')
|
|
27
|
+
try:
|
|
28
|
+
res = self.cursor.execute(query, params)
|
|
29
|
+
if is_select:
|
|
30
|
+
res = self.cursor.fetchall()
|
|
31
|
+
if len(res) == 0:
|
|
32
|
+
print("No results from query")
|
|
33
|
+
return None
|
|
34
|
+
return res
|
|
35
|
+
else:
|
|
36
|
+
return None
|
|
37
|
+
except pyodbc.Error as e:
|
|
38
|
+
print(e)
|
|
39
|
+
print(query)
|
|
40
|
+
raise e
|
|
41
|
+
|
|
42
|
+
def fetch_env(self, db_env):
|
|
43
|
+
"""Get env variable based on context, PROD or TEST"""
|
|
44
|
+
if db_env.upper() == "PROD":
|
|
45
|
+
connection_env = "DbConnectionString"
|
|
46
|
+
return connection_env
|
|
47
|
+
if db_env.upper() == "TEST":
|
|
48
|
+
connection_env = "DbConnectionStringTest"
|
|
49
|
+
return connection_env
|
|
50
|
+
|
|
51
|
+
raise ValueError(f"arg db_env is {db_env.upper()} but should be 'PROD' or 'TEST'")
|
|
52
|
+
|
|
53
|
+
def execute_stored_procedure(self, stored_procedure: str, params: Dict[str, Tuple[type, Any]] | None = None) -> Dict[str, Union[bool, str, Any]]:
|
|
54
|
+
"""
|
|
55
|
+
Executes a stored procedure with the given parameters.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
connection_string (str): The connection string to connect to the database.
|
|
59
|
+
stored_procedure (str): The name of the stored procedure to execute.
|
|
60
|
+
params (Dict[str, Tuple[type, Any]], optional): A dictionary of parameters to pass to the stored procedure.
|
|
61
|
+
Each value should be a tuple of (type, actual_value).
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
Dict[str, Union[bool, str, Any]]: A dictionary containing the success status, an error message (if any),
|
|
65
|
+
number of affected rows, and additional data.
|
|
66
|
+
"""
|
|
67
|
+
result = {
|
|
68
|
+
"success": False,
|
|
69
|
+
"error_message": None,
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
type_mapping = {
|
|
73
|
+
"str": str,
|
|
74
|
+
"int": int,
|
|
75
|
+
"float": float,
|
|
76
|
+
"datetime": parser.isoparse,
|
|
77
|
+
"json": lambda x: json.dumps(x, ensure_ascii=False)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
try:
|
|
81
|
+
if params:
|
|
82
|
+
param_placeholders = ', '.join([f"@{key} = ?" for key in params.keys()])
|
|
83
|
+
param_values = []
|
|
84
|
+
|
|
85
|
+
for key, value in params.items():
|
|
86
|
+
if isinstance(value, tuple) and len(value) == 2:
|
|
87
|
+
value_type, actual_value = value
|
|
88
|
+
if value_type in type_mapping:
|
|
89
|
+
param_values.append(type_mapping[value_type](actual_value))
|
|
90
|
+
else:
|
|
91
|
+
param_values.append(actual_value)
|
|
92
|
+
else:
|
|
93
|
+
raise ValueError("Each parameter value must be a tuple of (type, actual_value).")
|
|
94
|
+
|
|
95
|
+
sql = f"EXEC {stored_procedure} {param_placeholders}"
|
|
96
|
+
rows_updated = self.cursor.execute(sql, tuple(param_values))
|
|
97
|
+
else:
|
|
98
|
+
sql = f"EXEC {stored_procedure}"
|
|
99
|
+
rows_updated = self.cursor.execute(sql)
|
|
100
|
+
result["success"] = True
|
|
101
|
+
result["rows_updated"] = rows_updated.rowcount
|
|
102
|
+
except pyodbc.Error as e:
|
|
103
|
+
result["error_message"] = f"Database error: {str(e)}"
|
|
104
|
+
except ValueError as e:
|
|
105
|
+
result["error_message"] = f"Value error: {str(e)}"
|
|
106
|
+
except Exception as e:
|
|
107
|
+
result["error_message"] = f"An unexpected error occurred: {str(e)}"
|
|
108
|
+
|
|
109
|
+
return result
|
|
@@ -8,8 +8,6 @@ import uiautomation as auto
|
|
|
8
8
|
from datetime import datetime
|
|
9
9
|
|
|
10
10
|
from .handler_base import HandlerBase
|
|
11
|
-
from .appointment import AppointmentHandler
|
|
12
|
-
from .app_handler import SolteqTandApp
|
|
13
11
|
|
|
14
12
|
|
|
15
13
|
class DocumentHandler(HandlerBase):
|
|
@@ -128,14 +126,6 @@ class DocumentHandler(HandlerBase):
|
|
|
128
126
|
first_booking = controls[0]
|
|
129
127
|
first_booking.RightClick(simulateMove=False, waitTime=0)
|
|
130
128
|
|
|
131
|
-
# list_bookings_group = self.wait_for_control(
|
|
132
|
-
# auto.GroupControl,
|
|
133
|
-
# {'AutomationId': 'GroupBoxView'},
|
|
134
|
-
# search_depth=13,
|
|
135
|
-
# )
|
|
136
|
-
# group_bookings_list = list_bookings_group.GetChildren()[0].GetChildren()[1]
|
|
137
|
-
# group_bookings_list.RightClick(simulateMove=False, waitTime=0)
|
|
138
|
-
|
|
139
129
|
pop_up_right_click_menu = self.wait_for_control(
|
|
140
130
|
auto.MenuControl,
|
|
141
131
|
{'Name': 'Kontekst'},
|
|
@@ -7,6 +7,7 @@ mbu_dev_shared_components.egg-info/dependency_links.txt
|
|
|
7
7
|
mbu_dev_shared_components.egg-info/requires.txt
|
|
8
8
|
mbu_dev_shared_components.egg-info/top_level.txt
|
|
9
9
|
mbu_dev_shared_components/database/__init__.py
|
|
10
|
+
mbu_dev_shared_components/database/connection.py
|
|
10
11
|
mbu_dev_shared_components/database/constants.py
|
|
11
12
|
mbu_dev_shared_components/database/logging.py
|
|
12
13
|
mbu_dev_shared_components/database/utility.py
|
|
@@ -53,4 +54,5 @@ mbu_dev_shared_components/utils/__init__.py
|
|
|
53
54
|
mbu_dev_shared_components/utils/db_stored_procedure_executor.py
|
|
54
55
|
mbu_dev_shared_components/utils/fernet_encryptor.py
|
|
55
56
|
mbu_dev_shared_components/utils/file_handler.py
|
|
56
|
-
mbu_dev_shared_components/utils/json_handler.py
|
|
57
|
+
mbu_dev_shared_components/utils/json_handler.py
|
|
58
|
+
tests/test_database.py
|
|
@@ -2,13 +2,9 @@
|
|
|
2
2
|
requires = ["setuptools>=65.0"]
|
|
3
3
|
build-backend = "setuptools.build_meta"
|
|
4
4
|
|
|
5
|
-
#[tool.setuptools]
|
|
6
|
-
#package-dir = { "" = "." }
|
|
7
|
-
#packages = ["mbu_dev_shared_components"]
|
|
8
|
-
|
|
9
5
|
[project]
|
|
10
6
|
name = "mbu_dev_shared_components"
|
|
11
|
-
version = "
|
|
7
|
+
version = "3.0.0" # Specify the version manually here
|
|
12
8
|
authors = [
|
|
13
9
|
{ name="MBU", email="rpa@mbu.aarhus.dk" },
|
|
14
10
|
]
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Module to test RPAConnection functionalities in the Solteq Tand system
|
|
3
|
+
|
|
4
|
+
Tested functionalities:
|
|
5
|
+
|
|
6
|
+
- Establish database connection:
|
|
7
|
+
Function:
|
|
8
|
+
RPAConnection.__enter__
|
|
9
|
+
Assertion:
|
|
10
|
+
Connection and cursor are not None
|
|
11
|
+
Dependencies:
|
|
12
|
+
None
|
|
13
|
+
|
|
14
|
+
- Add and retrieve constant:
|
|
15
|
+
Function:
|
|
16
|
+
RPAConnection.add_constant, RPAConnection.get_constant
|
|
17
|
+
Assertion:
|
|
18
|
+
Constant is correctly inserted and retrieved
|
|
19
|
+
Dependencies:
|
|
20
|
+
test_connection
|
|
21
|
+
|
|
22
|
+
- Add and retrieve credential:
|
|
23
|
+
Function:
|
|
24
|
+
RPAConnection.add_credential, RPAConnection.get_credential
|
|
25
|
+
Assertion:
|
|
26
|
+
Credential is correctly inserted and retrieved
|
|
27
|
+
Dependencies:
|
|
28
|
+
test_connection
|
|
29
|
+
|
|
30
|
+
- Rollback functionality:
|
|
31
|
+
Function:
|
|
32
|
+
RPAConnection.__exit__
|
|
33
|
+
Assertion:
|
|
34
|
+
Constant added without commit is not persisted
|
|
35
|
+
Dependencies:
|
|
36
|
+
test_connection, test_add_get_constant
|
|
37
|
+
|
|
38
|
+
- Log event:
|
|
39
|
+
Function:
|
|
40
|
+
RPAConnection.log_event, RPAConnection.get_latest_log
|
|
41
|
+
Assertion:
|
|
42
|
+
Log entry is correctly inserted and retrieved
|
|
43
|
+
Dependencies:
|
|
44
|
+
None
|
|
45
|
+
|
|
46
|
+
- Heartbeat logging (stop as string):
|
|
47
|
+
Function:
|
|
48
|
+
RPAConnection.log_heartbeat, RPAConnection.get_heartbeat
|
|
49
|
+
Assertion:
|
|
50
|
+
Heartbeat status is "STOPPED"
|
|
51
|
+
Dependencies:
|
|
52
|
+
None
|
|
53
|
+
|
|
54
|
+
- Heartbeat logging (stop as boolean):
|
|
55
|
+
Function:
|
|
56
|
+
RPAConnection.log_heartbeat, RPAConnection.get_heartbeat
|
|
57
|
+
Assertion:
|
|
58
|
+
Heartbeat status is "STOPPED"
|
|
59
|
+
Dependencies:
|
|
60
|
+
None
|
|
61
|
+
|
|
62
|
+
- Run heartbeat process:
|
|
63
|
+
Function:
|
|
64
|
+
External subprocess running heartbeat_worker.py
|
|
65
|
+
Assertion:
|
|
66
|
+
Heartbeat status is "RUNNING" and updates over time
|
|
67
|
+
Dependencies:
|
|
68
|
+
None
|
|
69
|
+
|
|
70
|
+
Requirements:
|
|
71
|
+
- Database environment variable `TEST` must be configured
|
|
72
|
+
- pyodbc must be installed and accessible
|
|
73
|
+
- mbu_dev_shared_components must be available in PYTHONPATH
|
|
74
|
+
- `heartbeat_worker.py` must exist in the `tests/` directory and be executable
|
|
75
|
+
- `.venv/Scripts/python` must point to the correct Python interpreter
|
|
76
|
+
Further description:
|
|
77
|
+
Each test uses the RPAConnection context manager to ensure proper handling of database transactions.
|
|
78
|
+
COMMIT is set to False to test rollback behavior and avoid persistent changes to the test database.
|
|
79
|
+
Heartbeat and logging tests validate time-sensitive operations with a threshold of 0.5 seconds.
|
|
80
|
+
The heartbeat process test (`test_run_heartbeat`) runs a parallel subprocess to simulate real-time heartbeat updates.
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
from datetime import datetime, timedelta
|
|
84
|
+
import time
|
|
85
|
+
from uuid import uuid4
|
|
86
|
+
import socket
|
|
87
|
+
import subprocess
|
|
88
|
+
|
|
89
|
+
import pyodbc
|
|
90
|
+
import pytest
|
|
91
|
+
from mbu_dev_shared_components.database.connection import RPAConnection
|
|
92
|
+
|
|
93
|
+
# Global test configuration
|
|
94
|
+
DB_ENV = "TEST"
|
|
95
|
+
COMMIT = False
|
|
96
|
+
THRES_SEC = 0.5
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@pytest.mark.dependency()
|
|
100
|
+
def test_connection():
|
|
101
|
+
"""Test that RPAConnection successfully establishes a database connection
|
|
102
|
+
|
|
103
|
+
Verifies that the `conn` and `cursor` attributes are initialized and not None
|
|
104
|
+
after creating an instance of RPAConnection. Checks that
|
|
105
|
+
"""
|
|
106
|
+
with RPAConnection(db_env=DB_ENV, commit=COMMIT) as rpa_connection:
|
|
107
|
+
# Assert connection is established
|
|
108
|
+
assert rpa_connection.conn is not None
|
|
109
|
+
assert rpa_connection.cursor is not None
|
|
110
|
+
|
|
111
|
+
# Assert connection is closed
|
|
112
|
+
with pytest.raises(pyodbc.ProgrammingError, match="Attempt to use a closed cursor."):
|
|
113
|
+
rpa_connection.get_constant("test_uuid")
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
@pytest.mark.dependency(depends=["test_connection"])
|
|
117
|
+
def test_add_get_constant():
|
|
118
|
+
"""
|
|
119
|
+
Adds a test constant, rolls back the transaction.
|
|
120
|
+
"""
|
|
121
|
+
test_constant_name = f"pytest_constant_{uuid4()}"
|
|
122
|
+
test_value = "temporary_value"
|
|
123
|
+
|
|
124
|
+
with RPAConnection(db_env=DB_ENV, commit=COMMIT) as rpa_connection:
|
|
125
|
+
# Add constant (should be rolled back)
|
|
126
|
+
rpa_connection.add_constant(test_constant_name, test_value, datetime.now())
|
|
127
|
+
|
|
128
|
+
assert rpa_connection.cursor.rowcount == 1
|
|
129
|
+
|
|
130
|
+
# Check that constant is added (will be rolled back after function)
|
|
131
|
+
test_const = rpa_connection.get_constant(test_constant_name)
|
|
132
|
+
|
|
133
|
+
assert test_const
|
|
134
|
+
assert test_const["constant_name"] == test_constant_name
|
|
135
|
+
assert test_const["value"] == test_value
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
@pytest.mark.dependency(depends=["test_connection"])
|
|
139
|
+
def test_add_get_credential():
|
|
140
|
+
"""
|
|
141
|
+
Adds a test credential, rolls back the transaction.
|
|
142
|
+
"""
|
|
143
|
+
test_credential_name = f"pytest_constant_{uuid4()}"
|
|
144
|
+
test_username = "test_user"
|
|
145
|
+
test_password = "test_password"
|
|
146
|
+
|
|
147
|
+
with RPAConnection(db_env=DB_ENV, commit=COMMIT) as rpa_connection:
|
|
148
|
+
# Add constant (should be rolled back)
|
|
149
|
+
rpa_connection.add_credential(test_credential_name, test_username, test_password, datetime.now())
|
|
150
|
+
|
|
151
|
+
# Check that constant is added (will be rolled back after function)
|
|
152
|
+
test_const = rpa_connection.get_credential(test_credential_name)
|
|
153
|
+
|
|
154
|
+
assert test_const
|
|
155
|
+
assert test_const["username"] == test_username
|
|
156
|
+
assert test_const["decrypted_password"] == test_password
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
@pytest.mark.dependency(depends=["test_connection", "test_add_get_constant"])
|
|
160
|
+
def test_rollback():
|
|
161
|
+
"""
|
|
162
|
+
Test that rollback undoes the insertion of a constant through the context manager.
|
|
163
|
+
Asserts that a constant added in a scope without commiting to the db cannot be accessed outside the scope
|
|
164
|
+
"""
|
|
165
|
+
test_constant_name = f"pytest_rollback_same_conn_{uuid4()}"
|
|
166
|
+
test_value = "temporary_value"
|
|
167
|
+
|
|
168
|
+
with RPAConnection(db_env=DB_ENV, commit=COMMIT) as rpa_connection:
|
|
169
|
+
# Add constant
|
|
170
|
+
rpa_connection.add_constant(test_constant_name, test_value, datetime.now())
|
|
171
|
+
|
|
172
|
+
with RPAConnection(db_env=DB_ENV, commit=COMMIT) as rpa_connection:
|
|
173
|
+
# Try to retrieve the constant in new connection
|
|
174
|
+
with pytest.raises(ValueError, match=f"No constant found with name: {test_constant_name}"):
|
|
175
|
+
rpa_connection.get_constant(test_constant_name)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def test_log():
|
|
179
|
+
"""Test log functionality """
|
|
180
|
+
|
|
181
|
+
# Variables for test log row
|
|
182
|
+
log_db = "journalizing.Journalize_log"
|
|
183
|
+
level = "INFO"
|
|
184
|
+
message = "test_log"
|
|
185
|
+
context = "pytest"
|
|
186
|
+
|
|
187
|
+
with RPAConnection(db_env=DB_ENV, commit=COMMIT) as rpa_connection:
|
|
188
|
+
now = datetime.now()
|
|
189
|
+
# Attempt insertion of log_event
|
|
190
|
+
rpa_connection.log_event(
|
|
191
|
+
log_db=log_db,
|
|
192
|
+
level=level,
|
|
193
|
+
message=message,
|
|
194
|
+
context=context
|
|
195
|
+
)
|
|
196
|
+
# Assert that one row is inserted
|
|
197
|
+
assert rpa_connection.cursor.rowcount == 1
|
|
198
|
+
|
|
199
|
+
# Get latest log
|
|
200
|
+
# pylint: disable-next=W0212
|
|
201
|
+
log_row = rpa_connection.get_latest_log(
|
|
202
|
+
log_db=log_db
|
|
203
|
+
)[0]
|
|
204
|
+
|
|
205
|
+
# Assert values of inserted element
|
|
206
|
+
assert log_row[0] == level
|
|
207
|
+
assert log_row[1] == message
|
|
208
|
+
assert abs(log_row[2]-now) < timedelta(seconds=THRES_SEC) # Latest log was within 0.1 second of start of function
|
|
209
|
+
assert log_row[3] == context
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def test_stop_heartbeat_str():
|
|
213
|
+
"""Test stopping heartbeat functionality"""
|
|
214
|
+
|
|
215
|
+
servicename = "pytest"
|
|
216
|
+
heartbeat_interval = 1.0
|
|
217
|
+
details = "pytest testing heartbeat functionality"
|
|
218
|
+
stop = "True"
|
|
219
|
+
with RPAConnection(db_env=DB_ENV, commit=COMMIT) as rpa_connection:
|
|
220
|
+
now = datetime.now()
|
|
221
|
+
rpa_connection.log_heartbeat(
|
|
222
|
+
stop=stop,
|
|
223
|
+
servicename=servicename,
|
|
224
|
+
heartbeat_interval=heartbeat_interval,
|
|
225
|
+
details=details
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
heartbeat = rpa_connection.get_heartbeat(service_name=servicename)[0]
|
|
229
|
+
|
|
230
|
+
assert heartbeat[0] == servicename
|
|
231
|
+
assert abs(heartbeat[1]-now) < timedelta(seconds=THRES_SEC)
|
|
232
|
+
assert heartbeat[2] == "STOPPED"
|
|
233
|
+
assert heartbeat[3] == socket.gethostname()
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def test_stop_heartbeat_bool():
|
|
237
|
+
"""Test stopping heartbeat functionality"""
|
|
238
|
+
|
|
239
|
+
servicename = "pytest"
|
|
240
|
+
heartbeat_interval = 1.0
|
|
241
|
+
details = "pytest testing heartbeat functionality"
|
|
242
|
+
stop = True
|
|
243
|
+
with RPAConnection(db_env=DB_ENV, commit=COMMIT) as rpa_connection:
|
|
244
|
+
now = datetime.now()
|
|
245
|
+
rpa_connection.log_heartbeat(
|
|
246
|
+
stop=stop,
|
|
247
|
+
servicename=servicename,
|
|
248
|
+
heartbeat_interval=heartbeat_interval,
|
|
249
|
+
details=details
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
heartbeat = rpa_connection.get_heartbeat(service_name=servicename)[0]
|
|
253
|
+
|
|
254
|
+
assert heartbeat[0] == servicename
|
|
255
|
+
assert abs(heartbeat[1]-now) < timedelta(seconds=THRES_SEC)
|
|
256
|
+
assert heartbeat[2] == "STOPPED"
|
|
257
|
+
assert heartbeat[3] == socket.gethostname()
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def test_run_heartbeat():
|
|
261
|
+
"""Test running heartbeat functionality
|
|
262
|
+
Uses subprocess to run the heartbeat process in parallel and allows to stop it after some time
|
|
263
|
+
Since we are using a stored procedure, we cannot roll back the transaction, so we have to accept the table being affected by the test
|
|
264
|
+
Effectively we insert one row to the heartbeat table for the 'pytest' service
|
|
265
|
+
"""
|
|
266
|
+
servicename = "pytest"
|
|
267
|
+
heartbeat_interval = 2.0
|
|
268
|
+
details = "pytest testing heartbeat functionality"
|
|
269
|
+
stop = False
|
|
270
|
+
now = datetime.now()
|
|
271
|
+
heartbeat_process = subprocess.Popen(
|
|
272
|
+
[
|
|
273
|
+
".venv/Scripts/python",
|
|
274
|
+
"tests/heartbeat_worker.py",
|
|
275
|
+
DB_ENV,
|
|
276
|
+
str(stop),
|
|
277
|
+
servicename,
|
|
278
|
+
str(heartbeat_interval),
|
|
279
|
+
details
|
|
280
|
+
]
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
time.sleep(heartbeat_interval)
|
|
284
|
+
|
|
285
|
+
with RPAConnection(db_env="TEST", commit=False) as rpa_connection:
|
|
286
|
+
heartbeat = rpa_connection.get_heartbeat(servicename)[0]
|
|
287
|
+
|
|
288
|
+
# Assert heartbeat is running and recent
|
|
289
|
+
assert heartbeat[0] == servicename
|
|
290
|
+
assert abs(heartbeat[1]-now) < timedelta(seconds=THRES_SEC)
|
|
291
|
+
assert heartbeat[2] == "RUNNING"
|
|
292
|
+
assert heartbeat[3] == socket.gethostname()
|
|
293
|
+
|
|
294
|
+
# Test that heartbeat is updated by heartbeat interval time
|
|
295
|
+
for i in range(5):
|
|
296
|
+
print(f"Running assertion loop {i+1}")
|
|
297
|
+
prev_heartbeat_time = heartbeat[1]
|
|
298
|
+
time.sleep(heartbeat_interval)
|
|
299
|
+
# Get new heartbeat and assert that it is newer than previous hearbeat
|
|
300
|
+
heartbeat = rpa_connection.get_heartbeat(servicename)[0]
|
|
301
|
+
assert heartbeat[1] > prev_heartbeat_time, f"Heartbeat not updated on iteration {i+1}"
|
|
302
|
+
|
|
303
|
+
print("Should have finished assertion loop ")
|
|
304
|
+
|
|
305
|
+
heartbeat_process.terminate()
|
|
306
|
+
|
|
307
|
+
print("Should have terminated heartbeat process")
|
|
308
|
+
|
|
309
|
+
with RPAConnection(db_env="TEST", commit=True) as rpa_connection:
|
|
310
|
+
rpa_connection.log_heartbeat(
|
|
311
|
+
stop=True,
|
|
312
|
+
servicename=servicename,
|
|
313
|
+
heartbeat_interval=2,
|
|
314
|
+
details="Stop send from pytest"
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
if __name__ == '__main__':
|
|
319
|
+
pytest.main([__file__])
|
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
"""This module handles generating and fetching constants and credentials from the database"""
|
|
2
|
-
|
|
3
|
-
import os
|
|
4
|
-
import pyodbc
|
|
5
|
-
from datetime import datetime
|
|
6
|
-
|
|
7
|
-
from mbu_dev_shared_components.utils.fernet_encryptor import Encryptor
|
|
8
|
-
from mbu_dev_shared_components.database.utility import connect_to_db, execute_query
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
def add_credential(
|
|
12
|
-
credential_name: str,
|
|
13
|
-
username: str,
|
|
14
|
-
password: str,
|
|
15
|
-
changed_at: datetime = datetime.now(),
|
|
16
|
-
db_env: str = "PROD"
|
|
17
|
-
):
|
|
18
|
-
encryptor = Encryptor()
|
|
19
|
-
encrypted_password = encryptor.encrypt(password)
|
|
20
|
-
|
|
21
|
-
rpa_conn = connect_to_db(db_env=db_env)
|
|
22
|
-
cursor = rpa_conn.cursor()
|
|
23
|
-
|
|
24
|
-
query = """
|
|
25
|
-
INSERT INTO [RPA].[rpa].[Credentials]
|
|
26
|
-
([name]
|
|
27
|
-
,[username]
|
|
28
|
-
,[password]
|
|
29
|
-
,[changed_at])
|
|
30
|
-
VALUES
|
|
31
|
-
(?
|
|
32
|
-
,?
|
|
33
|
-
,?
|
|
34
|
-
,?)
|
|
35
|
-
"""
|
|
36
|
-
params = [credential_name, username, encrypted_password, changed_at]
|
|
37
|
-
execute_query(query=query, cursor=cursor, params=params)
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
def get_credential(
|
|
41
|
-
credential_name: str,
|
|
42
|
-
db_env: str = "PROD"
|
|
43
|
-
) -> dict:
|
|
44
|
-
|
|
45
|
-
rpa_conn = connect_to_db(db_env=db_env)
|
|
46
|
-
cursor = rpa_conn.cursor()
|
|
47
|
-
encryptor = Encryptor()
|
|
48
|
-
|
|
49
|
-
query = """
|
|
50
|
-
SELECT
|
|
51
|
-
Username
|
|
52
|
-
,cast(Password as varbinary(max))
|
|
53
|
-
FROM [RPA].[rpa].[Credentials]
|
|
54
|
-
WHERE
|
|
55
|
-
name = ?
|
|
56
|
-
"""
|
|
57
|
-
|
|
58
|
-
params = [credential_name]
|
|
59
|
-
|
|
60
|
-
res = execute_query(query=query, cursor=cursor, params=params)
|
|
61
|
-
if res is not None:
|
|
62
|
-
res = res[0]
|
|
63
|
-
username = res[0]
|
|
64
|
-
encrypted_password = res[1]
|
|
65
|
-
|
|
66
|
-
decrypted_password = encryptor.decrypt(encrypted_password)
|
|
67
|
-
|
|
68
|
-
return {
|
|
69
|
-
"username": username,
|
|
70
|
-
"decrypted_password": decrypted_password,
|
|
71
|
-
"encrypted_password": encrypted_password
|
|
72
|
-
}
|
|
73
|
-
else:
|
|
74
|
-
print(f"No credential found with name {credential_name}")
|
|
75
|
-
|
|
76
|
-
def get_constant(
|
|
77
|
-
constant_name: str,
|
|
78
|
-
db_env: str = "PROD"
|
|
79
|
-
) -> tuple:
|
|
80
|
-
|
|
81
|
-
rpa_conn = connect_to_db(db_env=db_env)
|
|
82
|
-
cursor = rpa_conn.cursor()
|
|
83
|
-
encryptor = Encryptor()
|
|
84
|
-
|
|
85
|
-
query = """
|
|
86
|
-
SELECT
|
|
87
|
-
name
|
|
88
|
-
,value
|
|
89
|
-
FROM [RPA].[rpa].[Constants]
|
|
90
|
-
WHERE
|
|
91
|
-
name = ?
|
|
92
|
-
"""
|
|
93
|
-
|
|
94
|
-
params = [constant_name]
|
|
95
|
-
|
|
96
|
-
res = execute_query(query=query, cursor=cursor, params=params)
|
|
97
|
-
if res is not None:
|
|
98
|
-
|
|
99
|
-
returned_constant = res[0]
|
|
100
|
-
constant_name = returned_constant[0]
|
|
101
|
-
value = returned_constant[1]
|
|
102
|
-
|
|
103
|
-
return {"constant_name": constant_name, "value": value}
|
|
104
|
-
else:
|
|
105
|
-
print(f"No constant found with name: {constant_name}")
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
def add_constant(
|
|
109
|
-
constant_name: str,
|
|
110
|
-
value: str,
|
|
111
|
-
changed_at: datetime = datetime.now(),
|
|
112
|
-
db_env: str = "PROD"
|
|
113
|
-
):
|
|
114
|
-
query = """
|
|
115
|
-
INSERT INTO [RPA].[rpa].[Constants]
|
|
116
|
-
([name]
|
|
117
|
-
,[value]
|
|
118
|
-
,[changed_at])
|
|
119
|
-
VALUES
|
|
120
|
-
(?
|
|
121
|
-
,?
|
|
122
|
-
,?)
|
|
123
|
-
"""
|
|
124
|
-
|
|
125
|
-
rpa_conn = connect_to_db(db_env=db_env)
|
|
126
|
-
cursor = rpa_conn.cursor()
|
|
127
|
-
|
|
128
|
-
params = [constant_name, value, changed_at]
|
|
129
|
-
execute_query(query=query, cursor=cursor, params=params)
|
|
130
|
-
|
|
131
|
-
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
"""This module handles logging in the RPA database"""
|
|
2
|
-
|
|
3
|
-
from datetime import datetime
|
|
4
|
-
import time
|
|
5
|
-
import os
|
|
6
|
-
import socket
|
|
7
|
-
|
|
8
|
-
from mbu_dev_shared_components.database.utility import execute_query, connect_to_db, fetch_env
|
|
9
|
-
from mbu_dev_shared_components.utils.db_stored_procedure_executor import execute_stored_procedure
|
|
10
|
-
|
|
11
|
-
def log_event(
|
|
12
|
-
log_db: str,
|
|
13
|
-
level: str,
|
|
14
|
-
message: str,
|
|
15
|
-
context: str,
|
|
16
|
-
db_env="TEST"
|
|
17
|
-
):
|
|
18
|
-
created_at = datetime.now()
|
|
19
|
-
"""Logs the inputted parameters in """
|
|
20
|
-
query = f"""
|
|
21
|
-
INSERT INTO RPA.{log_db}
|
|
22
|
-
([level]
|
|
23
|
-
,[message]
|
|
24
|
-
,[created_at]
|
|
25
|
-
,[context])
|
|
26
|
-
VALUES
|
|
27
|
-
(?
|
|
28
|
-
,?
|
|
29
|
-
,?
|
|
30
|
-
,?)
|
|
31
|
-
"""
|
|
32
|
-
rpa_conn = connect_to_db(db_env=db_env)
|
|
33
|
-
cursor = rpa_conn.cursor()
|
|
34
|
-
|
|
35
|
-
params = [level, message, created_at, context]
|
|
36
|
-
execute_query(query=query, cursor=cursor, params=params)
|
|
37
|
-
|
|
38
|
-
def _send_heartbeat(
|
|
39
|
-
servicename,
|
|
40
|
-
status,
|
|
41
|
-
details,
|
|
42
|
-
db_env,
|
|
43
|
-
):
|
|
44
|
-
conn_env = fetch_env(db_env=db_env)
|
|
45
|
-
conn_str = os.getenv(conn_env)
|
|
46
|
-
hostname = socket.gethostname()
|
|
47
|
-
params = {
|
|
48
|
-
"ServiceName": (str, servicename),
|
|
49
|
-
"Status": (str, status),
|
|
50
|
-
"HostName": (str, hostname),
|
|
51
|
-
"Details": (str, details)
|
|
52
|
-
}
|
|
53
|
-
result = execute_stored_procedure(
|
|
54
|
-
connection_string=conn_str,
|
|
55
|
-
stored_procedure='rpa.sp_UpdateHeartbeat',
|
|
56
|
-
params=params)
|
|
57
|
-
if result["success"] is not True:
|
|
58
|
-
print(result["error_message"])
|
|
59
|
-
|
|
60
|
-
def log_heartbeat(
|
|
61
|
-
stop: str|bool,
|
|
62
|
-
servicename: str,
|
|
63
|
-
heartbeat_interval: int,
|
|
64
|
-
details: str = "",
|
|
65
|
-
db_env: str = "PROD"
|
|
66
|
-
):
|
|
67
|
-
if isinstance(stop,str):
|
|
68
|
-
stop = stop == "True"
|
|
69
|
-
if not isinstance(heartbeat_interval, int):
|
|
70
|
-
heartbeat_interval = int(heartbeat_interval)
|
|
71
|
-
while not stop:
|
|
72
|
-
status = "RUNNING"
|
|
73
|
-
_send_heartbeat(
|
|
74
|
-
servicename,
|
|
75
|
-
status,
|
|
76
|
-
details,
|
|
77
|
-
db_env,
|
|
78
|
-
)
|
|
79
|
-
time.sleep(heartbeat_interval)
|
|
80
|
-
status = "STOPPED"
|
|
81
|
-
_send_heartbeat(
|
|
82
|
-
servicename,
|
|
83
|
-
status,
|
|
84
|
-
details,
|
|
85
|
-
db_env,
|
|
86
|
-
)
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
"""This module handles general database connection and calls"""
|
|
2
|
-
|
|
3
|
-
import os
|
|
4
|
-
import pyodbc
|
|
5
|
-
from datetime import datetime
|
|
6
|
-
|
|
7
|
-
def connect_to_db(autocommit=True, db_env="PROD") -> pyodbc.Connection:
|
|
8
|
-
"""Establish connection to sql database
|
|
9
|
-
|
|
10
|
-
Returns:
|
|
11
|
-
rpa_conn (pyodbc.Connection): The connection object to the SQL database.
|
|
12
|
-
"""
|
|
13
|
-
connection_env = fetch_env(db_env)
|
|
14
|
-
rpa_conn_string = os.getenv(connection_env)
|
|
15
|
-
rpa_conn = pyodbc.connect(rpa_conn_string, autocommit=autocommit)
|
|
16
|
-
return rpa_conn
|
|
17
|
-
|
|
18
|
-
def execute_query(query: str, cursor: pyodbc.Cursor, params: list) -> pyodbc.Cursor:
|
|
19
|
-
is_select = query.strip().upper().startswith('SELECT')
|
|
20
|
-
try:
|
|
21
|
-
res = cursor.execute(query, params)
|
|
22
|
-
if is_select:
|
|
23
|
-
res = cursor.fetchall()
|
|
24
|
-
if len(res) == 0:
|
|
25
|
-
print("No results from query")
|
|
26
|
-
return None
|
|
27
|
-
return res
|
|
28
|
-
else:
|
|
29
|
-
return None
|
|
30
|
-
except pyodbc.Error as e:
|
|
31
|
-
print(e)
|
|
32
|
-
finally:
|
|
33
|
-
cursor.close()
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
def fetch_env(db_env):
|
|
37
|
-
if db_env.upper() == "PROD":
|
|
38
|
-
connection_env = "DbConnectionString"
|
|
39
|
-
return connection_env
|
|
40
|
-
if db_env.upper() == "TEST":
|
|
41
|
-
connection_env = "DbConnectionStringTest"
|
|
42
|
-
return connection_env
|
|
43
|
-
|
|
44
|
-
raise ValueError(f"arg db_env is {db_env.upper()} but should be 'PROD' or 'TEST'")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|