select-ai 1.2.0rc3__py3-none-any.whl

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.
select_ai/db.py ADDED
@@ -0,0 +1,191 @@
1
+ # -----------------------------------------------------------------------------
2
+ # Copyright (c) 2025, Oracle and/or its affiliates.
3
+ #
4
+ # Licensed under the Universal Permissive License v 1.0 as shown at
5
+ # http://oss.oracle.com/licenses/upl.
6
+ # -----------------------------------------------------------------------------
7
+
8
+ import contextlib
9
+ import os
10
+ from threading import get_ident
11
+ from typing import Dict, Hashable
12
+
13
+ import oracledb
14
+
15
+ from select_ai.errors import DatabaseNotConnectedError
16
+
17
+ __conn__: Dict[Hashable, oracledb.Connection] = {}
18
+ __async_conn__: Dict[Hashable, oracledb.AsyncConnection] = {}
19
+
20
+ __all__ = [
21
+ "connect",
22
+ "async_connect",
23
+ "is_connected",
24
+ "async_is_connected",
25
+ "get_connection",
26
+ "async_get_connection",
27
+ "cursor",
28
+ "async_cursor",
29
+ "disconnect",
30
+ "async_disconnect",
31
+ ]
32
+
33
+
34
+ def connect(user: str, password: str, dsn: str, *args, **kwargs):
35
+ """Creates an oracledb.Connection object
36
+ and saves it global dictionary __conn__
37
+ The connection object is thread local meaning
38
+ in a multithreaded application, individual
39
+ threads cannot see each other's connection
40
+ object
41
+ """
42
+ conn = oracledb.connect(
43
+ user=user,
44
+ password=password,
45
+ dsn=dsn,
46
+ connection_id_prefix="python-select-ai",
47
+ *args,
48
+ **kwargs,
49
+ )
50
+ _set_connection(conn=conn)
51
+
52
+
53
+ async def async_connect(user: str, password: str, dsn: str, *args, **kwargs):
54
+ """Creates an oracledb.AsyncConnection object
55
+ and saves it global dictionary __async_conn__
56
+ The connection object is thread local meaning
57
+ in a multithreaded application, individual
58
+ threads cannot see each other's connection
59
+ object
60
+ """
61
+ async_conn = await oracledb.connect_async(
62
+ user=user,
63
+ password=password,
64
+ dsn=dsn,
65
+ connection_id_prefix="async-python-select-ai",
66
+ *args,
67
+ **kwargs,
68
+ )
69
+ _set_connection(async_conn=async_conn)
70
+
71
+
72
+ def is_connected() -> bool:
73
+ """Checks if database connection is open and healthy"""
74
+ global __conn__
75
+ key = (os.getpid(), get_ident())
76
+ conn = __conn__.get(key)
77
+ if conn is None:
78
+ return False
79
+ try:
80
+ return conn.ping() is None
81
+ except (oracledb.DatabaseError, oracledb.InterfaceError):
82
+ return False
83
+
84
+
85
+ async def async_is_connected() -> bool:
86
+ """Asynchronously checks if database connection is open and healthy"""
87
+
88
+ global __async_conn__
89
+ key = (os.getpid(), get_ident())
90
+ conn = __async_conn__.get(key)
91
+ if conn is None:
92
+ return False
93
+ try:
94
+ return await conn.ping() is None
95
+ except (oracledb.DatabaseError, oracledb.InterfaceError):
96
+ return False
97
+
98
+
99
+ def _set_connection(
100
+ conn: oracledb.Connection = None,
101
+ async_conn: oracledb.AsyncConnection = None,
102
+ ):
103
+ """Set existing connection for select_ai Python API to reuse
104
+
105
+ :param conn: python-oracledb Connection object
106
+ :param async_conn: python-oracledb
107
+ :return:
108
+ """
109
+ key = (os.getpid(), get_ident())
110
+ if conn:
111
+ global __conn__
112
+ __conn__[key] = conn
113
+ if async_conn:
114
+ global __async_conn__
115
+ __async_conn__[key] = async_conn
116
+
117
+
118
+ def get_connection() -> oracledb.Connection:
119
+ """Returns the connection object if connection is healthy"""
120
+ if not is_connected():
121
+ raise DatabaseNotConnectedError()
122
+ global __conn__
123
+ key = (os.getpid(), get_ident())
124
+ return __conn__[key]
125
+
126
+
127
+ async def async_get_connection() -> oracledb.AsyncConnection:
128
+ """Returns the AsyncConnection object if connection is healthy"""
129
+ if not await async_is_connected():
130
+ raise DatabaseNotConnectedError()
131
+ global __async_conn__
132
+ key = (os.getpid(), get_ident())
133
+ return __async_conn__[key]
134
+
135
+
136
+ @contextlib.contextmanager
137
+ def cursor():
138
+ """
139
+ Creates a context manager for database cursor
140
+
141
+ Typical usage:
142
+
143
+ with select_ai.cursor() as cr:
144
+ cr.execute(<QUERY>)
145
+
146
+ This ensures that the cursor is closed regardless
147
+ of whether an exception occurred
148
+
149
+ """
150
+ cr = get_connection().cursor()
151
+ try:
152
+ yield cr
153
+ finally:
154
+ cr.close()
155
+
156
+
157
+ @contextlib.asynccontextmanager
158
+ async def async_cursor():
159
+ """
160
+ Creates an async context manager for database cursor
161
+
162
+ Typical usage:
163
+
164
+ async with select_ai.cursor() as cr:
165
+ await cr.execute(<QUERY>)
166
+ :return:
167
+ """
168
+ conn = await async_get_connection()
169
+ cr = conn.cursor()
170
+ try:
171
+ yield cr
172
+ finally:
173
+ cr.close()
174
+
175
+
176
+ def disconnect():
177
+ try:
178
+ conn = get_connection()
179
+ except DatabaseNotConnectedError:
180
+ pass
181
+ else:
182
+ conn.close()
183
+
184
+
185
+ async def async_disconnect():
186
+ try:
187
+ conn = await async_get_connection()
188
+ except DatabaseNotConnectedError:
189
+ pass
190
+ else:
191
+ await conn.close()
select_ai/errors.py ADDED
@@ -0,0 +1,113 @@
1
+ # -----------------------------------------------------------------------------
2
+ # Copyright (c) 2025, Oracle and/or its affiliates.
3
+ #
4
+ # Licensed under the Universal Permissive License v 1.0 as shown at
5
+ # http://oss.oracle.com/licenses/upl.
6
+ # -----------------------------------------------------------------------------
7
+
8
+
9
+ class SelectAIError(Exception):
10
+ """Base class for any SelectAIErrors"""
11
+
12
+ pass
13
+
14
+
15
+ class DatabaseNotConnectedError(SelectAIError):
16
+ """Raised when a database is not connected"""
17
+
18
+ def __str__(self):
19
+ return (
20
+ "Not connected to the Database. "
21
+ "Use select_ai.connect() or select_ai.async_connect() "
22
+ "to establish connection"
23
+ )
24
+
25
+
26
+ class ConversationNotFoundError(SelectAIError):
27
+ """Conversation not found in the database"""
28
+
29
+ def __init__(self, conversation_id: str):
30
+ self.conversation_id = conversation_id
31
+
32
+ def __str__(self):
33
+ return f"Conversation with id {self.conversation_id} not found"
34
+
35
+
36
+ class ProfileNotFoundError(SelectAIError):
37
+ """Profile not found in the database"""
38
+
39
+ def __init__(self, profile_name: str):
40
+ self.profile_name = profile_name
41
+
42
+ def __str__(self):
43
+ return f"Profile {self.profile_name} not found"
44
+
45
+
46
+ class ProfileExistsError(SelectAIError):
47
+ """Profile already exists in the database"""
48
+
49
+ def __init__(self, profile_name: str):
50
+ self.profile_name = profile_name
51
+
52
+ def __str__(self):
53
+ return (
54
+ f"Profile {self.profile_name} already exists. "
55
+ f"Use either replace=True or merge=True"
56
+ )
57
+
58
+
59
+ class VectorIndexNotFoundError(SelectAIError):
60
+ """VectorIndex not found in the database"""
61
+
62
+ def __init__(self, index_name: str, profile_name: str = None):
63
+ self.index_name = index_name
64
+ self.profile_name = profile_name
65
+
66
+ def __str__(self):
67
+ if self.profile_name:
68
+ return (
69
+ f"VectorIndex {self.index_name} "
70
+ f"not found for profile {self.profile_name}"
71
+ )
72
+ else:
73
+ return f"VectorIndex {self.index_name} not found"
74
+
75
+
76
+ class AgentNotFoundError(SelectAIError):
77
+ """Agent not found in the database"""
78
+
79
+ def __init__(self, agent_name: str):
80
+ self.agent_name = agent_name
81
+
82
+ def __str__(self):
83
+ return f"Agent {self.agent_name} not found"
84
+
85
+
86
+ class AgentTaskNotFoundError(SelectAIError):
87
+ """Agent task not found in the database"""
88
+
89
+ def __init__(self, task_name: str):
90
+ self.task_name = task_name
91
+
92
+ def __str__(self):
93
+ return f"Agent Task {self.task_name} not found"
94
+
95
+
96
+ class AgentToolNotFoundError(SelectAIError):
97
+ """Agent tool not found in the database"""
98
+
99
+ def __init__(self, tool_name: str):
100
+ self.tool_name = tool_name
101
+
102
+ def __str__(self):
103
+ return f"Agent Tool {self.tool_name} not found"
104
+
105
+
106
+ class AgentTeamNotFoundError(SelectAIError):
107
+ """Agent team not found in the database"""
108
+
109
+ def __init__(self, team_name: str):
110
+ self.team_name = team_name
111
+
112
+ def __str__(self):
113
+ return f"Agent Team {self.team_name} not found"
select_ai/feedback.py ADDED
@@ -0,0 +1,19 @@
1
+ # -----------------------------------------------------------------------------
2
+ # Copyright (c) 2025, Oracle and/or its affiliates.
3
+ #
4
+ # Licensed under the Universal Permissive License v 1.0 as shown at
5
+ # http://oss.oracle.com/licenses/upl.
6
+ # -----------------------------------------------------------------------------
7
+ from select_ai._enums import StrEnum
8
+
9
+
10
+ class FeedbackType(StrEnum):
11
+
12
+ POSITIVE = "positive"
13
+ NEGATIVE = "negative"
14
+
15
+
16
+ class FeedbackOperation(StrEnum):
17
+
18
+ ADD = "add"
19
+ DELETE = "delete"
select_ai/privilege.py ADDED
@@ -0,0 +1,135 @@
1
+ # -----------------------------------------------------------------------------
2
+ # Copyright (c) 2025, Oracle and/or its affiliates.
3
+ #
4
+ # Licensed under the Universal Permissive License v 1.0 as shown at
5
+ # http://oss.oracle.com/licenses/upl.
6
+ # -----------------------------------------------------------------------------
7
+ from typing import List, Union
8
+
9
+ from .db import async_cursor, cursor
10
+ from .sql import (
11
+ DISABLE_AI_PROFILE_DOMAIN_FOR_USER,
12
+ ENABLE_AI_PROFILE_DOMAIN_FOR_USER,
13
+ GRANT_PRIVILEGES_TO_USER,
14
+ REVOKE_PRIVILEGES_FROM_USER,
15
+ )
16
+
17
+
18
+ async def async_grant_privileges(users: Union[str, List[str]]):
19
+ """
20
+ This method grants execute privilege on the packages DBMS_CLOUD,
21
+ DBMS_CLOUD_AI, DBMS_CLOUD_AI_AGENT and DBMS_CLOUD_PIPELINE.
22
+
23
+ """
24
+ if isinstance(users, str):
25
+ users = [users]
26
+
27
+ async with async_cursor() as cr:
28
+ for user in users:
29
+ await cr.execute(GRANT_PRIVILEGES_TO_USER.format(user.strip()))
30
+
31
+
32
+ async def async_revoke_privileges(users: Union[str, List[str]]):
33
+ """
34
+ This method revokes execute privilege on the packages DBMS_CLOUD,
35
+ DBMS_CLOUD_AI, DBMS_CLOUD_AI_AGENT and DBMS_CLOUD_PIPELINE.
36
+
37
+ """
38
+ if isinstance(users, str):
39
+ users = [users]
40
+
41
+ async with async_cursor() as cr:
42
+ for user in users:
43
+ await cr.execute(REVOKE_PRIVILEGES_FROM_USER.format(user.strip()))
44
+
45
+
46
+ async def async_grant_http_access(
47
+ users: Union[str, List[str]],
48
+ provider_endpoint: str,
49
+ ):
50
+ """
51
+ Async method to add ACL for HTTP access.
52
+ """
53
+ if isinstance(users, str):
54
+ users = [users]
55
+
56
+ async with async_cursor() as cr:
57
+ for user in users:
58
+ await cr.execute(
59
+ ENABLE_AI_PROFILE_DOMAIN_FOR_USER,
60
+ user=user,
61
+ host=provider_endpoint,
62
+ )
63
+
64
+
65
+ async def async_revoke_http_access(
66
+ users: Union[str, List[str]],
67
+ provider_endpoint: str,
68
+ ):
69
+ """
70
+ Async method to remove ACL for HTTP access.
71
+ """
72
+ if isinstance(users, str):
73
+ users = [users]
74
+
75
+ async with async_cursor() as cr:
76
+ for user in users:
77
+ await cr.execute(
78
+ DISABLE_AI_PROFILE_DOMAIN_FOR_USER,
79
+ user=user,
80
+ host=provider_endpoint,
81
+ )
82
+
83
+
84
+ def grant_privileges(users: Union[str, List[str]]):
85
+ """
86
+ This method grants execute privilege on the packages DBMS_CLOUD,
87
+ DBMS_CLOUD_AI, DBMS_CLOUD_AI_AGENT and DBMS_CLOUD_PIPELINE
88
+ """
89
+ if isinstance(users, str):
90
+ users = [users]
91
+ with cursor() as cr:
92
+ for user in users:
93
+ cr.execute(GRANT_PRIVILEGES_TO_USER.format(user.strip()))
94
+
95
+
96
+ def revoke_privileges(users: Union[str, List[str]]):
97
+ """
98
+ This method revokes execute privilege on the packages DBMS_CLOUD,
99
+ DBMS_CLOUD_AI, DBMS_CLOUD_AI_AGENT and DBMS_CLOUD_PIPELINE.
100
+ """
101
+ if isinstance(users, str):
102
+ users = [users]
103
+ with cursor() as cr:
104
+ for user in users:
105
+ cr.execute(REVOKE_PRIVILEGES_FROM_USER.format(user.strip()))
106
+
107
+
108
+ def grant_http_access(users: Union[str, List[str]], provider_endpoint: str):
109
+ """
110
+ Adds ACL entry for HTTP access
111
+ """
112
+ if isinstance(users, str):
113
+ users = [users]
114
+ with cursor() as cr:
115
+ for user in users:
116
+ cr.execute(
117
+ ENABLE_AI_PROFILE_DOMAIN_FOR_USER,
118
+ user=user,
119
+ host=provider_endpoint,
120
+ )
121
+
122
+
123
+ def revoke_http_access(users: Union[str, List[str]], provider_endpoint: str):
124
+ """
125
+ Removes ACL entry for HTTP access
126
+ """
127
+ if isinstance(users, str):
128
+ users = [users]
129
+ with cursor() as cr:
130
+ for user in users:
131
+ cr.execute(
132
+ DISABLE_AI_PROFILE_DOMAIN_FOR_USER,
133
+ user=user,
134
+ host=provider_endpoint,
135
+ )