pychestrate-core 0.0.1__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.
File without changes
@@ -0,0 +1,52 @@
1
+
2
+ __author__ = "Myron Walker"
3
+ __copyright__ = "Copyright 2025, Automation Mojo LLC"
4
+ __credits__ = []
5
+ __version__ = "1.0.0"
6
+
7
+ from ccai.runtime.activation import activate_runtime, ActivationProfile
8
+
9
+ from ccai.configuration.configurationmaps import resolve_configuration_maps
10
+ from ccai.configuration.configurationsettings import establish_configuration_settings
11
+ from ccai.configuration.configurationvariables import resolve_configuration_variables
12
+
13
+
14
+ from ccai.credentials.wellknown import CredentialManagerSingleton
15
+
16
+ from ccai.dataprofiles.wellknown import DataProfileManagerSingleton
17
+
18
+
19
+ from ccai.pychestrate.core.storage.dbconnection import create_database_connection_factory
20
+ from ccai.pychestrate.core.storage.dbio import database_exists, database_create
21
+ from ccai.pychestrate.core.storage.orm import PychestrateModelBase
22
+
23
+
24
+ def create_database_main():
25
+
26
+ establish_configuration_settings(name="pychestrate", presence_dir="~/pychestrate")
27
+
28
+ resolve_configuration_variables()
29
+
30
+ activate_runtime(profile=ActivationProfile.Command)
31
+
32
+ resolve_configuration_maps(use_credentials=True, use_landscape=True)
33
+
34
+ profile_manager = DataProfileManagerSingleton()
35
+ dbprofile = profile_manager.lookup_profile("software-pipeline-admin")
36
+
37
+ cred_manager = CredentialManagerSingleton()
38
+
39
+ dbfactory = create_database_connection_factory(dbprofile=dbprofile, cred_manager=cred_manager)
40
+
41
+ dbname = "pipeline"
42
+
43
+ if not database_exists(dbfactory, dbname):
44
+ database_create(dbfactory, dbname=dbname, dbmetadata=PychestrateModelBase.metadata)
45
+
46
+ return
47
+
48
+
49
+ if __name__ == "__main__":
50
+
51
+ create_database_main()
52
+
File without changes
File without changes
@@ -0,0 +1,269 @@
1
+ """
2
+ .. module:: dbconnection
3
+ :platform: Darwin, Linux, Unix, Windows
4
+ :synopsis: Contains helper functions for working with specific databases
5
+
6
+ .. moduleauthor:: Myron Walker <myron.walker@gmail.com>
7
+ """
8
+
9
+ __author__ = "Myron Walker"
10
+ __copyright__ = "Copyright 2025, Automation Mojo LLC"
11
+ __credits__ = []
12
+ __version__ = "1.0.0"
13
+
14
+ from typing import Optional
15
+
16
+ import logging
17
+ import os
18
+
19
+ from sqlalchemy import create_engine
20
+ from sqlalchemy.engine.base import Engine as SqlEngine
21
+
22
+ from ccai.errors.exceptions import ConfigurationError
23
+
24
+ from ccai.credentials.basecredential import BaseCredential
25
+ from ccai.credentials.basiccredential import BasicCredential
26
+ from ccai.credentials.credentialmanager import CredentialManager
27
+
28
+ from ccai.dataprofiles.databasebasicsocketprofile import DatabaseBasicSocketProfile
29
+ from ccai.dataprofiles.databasebasictcpprofile import DatabaseBasicTcpProfile
30
+
31
+
32
+ logger = logging.getLogger()
33
+
34
+
35
+ class DatabaseConnectionFactory:
36
+
37
+ def __init__(self, profile_name: str, *, dbprofile: DatabaseBasicSocketProfile, credential: BaseCredential):
38
+ self._profile_name = profile_name
39
+ self._dbprofile = dbprofile
40
+ self._credential = credential
41
+ return
42
+
43
+ @property
44
+ def credential(self) -> BaseCredential:
45
+ return self._credential
46
+
47
+ @property
48
+ def dbprofile(self) -> DatabaseBasicSocketProfile:
49
+ return self._dbprofile
50
+
51
+ @property
52
+ def profile_name(self) -> str:
53
+ return self._profile_name
54
+
55
+ def create_engine(self, *, dbname=None, echo=True) -> SqlEngine:
56
+ errmsg = "The 'create_engine' method must be overloaded by 'DatabaseConnectionFactory' derived types."
57
+ raise NotImplementedError(errmsg)
58
+
59
+ def format_connection_string() -> str:
60
+ errmsg = "The 'format_connection_string' method must be overloaded by 'DatabaseConnectionFactory' derived types."
61
+ raise NotImplementedError(errmsg)
62
+
63
+
64
+ class BasicSocketDatabaseConnectionFactory(DatabaseConnectionFactory):
65
+
66
+ def __init__(self, profile_name: str, *, dbprofile: DatabaseBasicSocketProfile, credential: BaseCredential):
67
+ super().__init__(profile_name, dbprofile=dbprofile, credential=credential)
68
+ return
69
+
70
+ def create_engine(self, *, dbname=None, echo=True) -> SqlEngine:
71
+ """Create a SQLAlchemy engine using pipe based connectivity."""
72
+
73
+ socket_path: str = self.dbprofile.socket
74
+
75
+ if dbname is None and self.dbprofile.dbname is not None:
76
+ dbname = self.dbprofile.dbname
77
+
78
+ if dbname is None:
79
+ warnmsg = "BasicSocketDatabaseConnectionFactory.create_engine: dbname was None."
80
+ logger.warn(warnmsg)
81
+
82
+ if isinstance(self.credential, BasicCredential):
83
+ connstr = self.format_connection_string(dbname=dbname)
84
+ dbengine = create_engine(connstr, echo=echo)
85
+ else:
86
+ errmsg = (
87
+ "BasicSocketDatabaseConnectionFactory requires a BasicCredential to "
88
+ "create an engine."
89
+ )
90
+ raise ConfigurationError(errmsg)
91
+
92
+ return dbengine
93
+
94
+ def get_connection_scheme(self):
95
+ """Return the SQLAlchemy connection scheme for the profile."""
96
+
97
+ conn_scheme = None
98
+
99
+ if self.dbprofile.dbtype == "postgresql":
100
+ try:
101
+ import psycopg2 # noqa: F401
102
+ except ImportError:
103
+ errmsg_lines = [
104
+ "In order to create a postgresql database engine, you must install the following dependencies.",
105
+ "DEPENDENCIES",
106
+ " psycopg2",
107
+ ]
108
+ errmsg = os.linesep.join(errmsg_lines)
109
+ raise ConfigurationError(errmsg)
110
+
111
+ conn_scheme = "postgresql+psycopg2"
112
+
113
+ return conn_scheme
114
+
115
+ def format_connection_string(self, dbname: Optional[str] = None) -> str:
116
+ """Build a connection string for pipe based access."""
117
+
118
+ conn_scheme: str = self.get_connection_scheme()
119
+
120
+ socket_path: str = self.dbprofile.socket
121
+
122
+ if not isinstance(self.credential, BasicCredential):
123
+ errmsg = (
124
+ "BasicSocketDatabaseConnectionFactory requires a BasicCredential to "
125
+ "format the connection string."
126
+ )
127
+ raise ConfigurationError(errmsg)
128
+
129
+ credobj: BasicCredential = self.credential
130
+
131
+ username = credobj.username
132
+ password = credobj.password
133
+
134
+ if dbname is None:
135
+ dbname = self.dbprofile.dbname
136
+
137
+ conn_str = f"{conn_scheme}://{username}:{password}@/{dbname}?host={socket_path}"
138
+
139
+ return conn_str
140
+
141
+
142
+ class BasicTcpDatabaseConnectionFactory(DatabaseConnectionFactory):
143
+
144
+ def __init__(self, profile_name: str, *, dbprofile: DatabaseBasicTcpProfile, credential: BaseCredential):
145
+ super().__init__(profile_name, dbprofile=dbprofile, credential=credential)
146
+ return
147
+
148
+ @property
149
+ def dbprofile(self) -> DatabaseBasicTcpProfile:
150
+ return self._dbprofile
151
+
152
+ def create_engine(self, *, dbname=None, echo=True) -> SqlEngine:
153
+
154
+ conn_scheme = None
155
+
156
+ # If the database type we are creating an engine for is postgresql, make sure
157
+ # we can import all of the postgresql dependencies.
158
+ if self.dbprofile.dbtype == 'postgresql':
159
+ try:
160
+ import psycopg2
161
+ except ImportError:
162
+ errmsg_lines = [
163
+ "In order to create a postgresql database engine, you must install the following dependencies.",
164
+ "DEPENDENCIES",
165
+ " psycopg2"
166
+ ]
167
+ errmsg = os.linesep.join(errmsg_lines)
168
+ raise ConfigurationError(errmsg)
169
+
170
+ conn_scheme = "postgresql+psycopg2"
171
+
172
+ if dbname is None and self.dbprofile.dbname is not None:
173
+ dbname = self.dbprofile.dbname
174
+
175
+ if dbname is None:
176
+ warnmsg = "BasicTcpDatabaseConnectionFactory.create_engine: dbname was None."
177
+ logger.warn(warnmsg)
178
+
179
+ if isinstance(self.credential, BasicCredential):
180
+ credobj: BasicCredential = self.credential
181
+
182
+ passwd = credobj.password
183
+
184
+ connstr = self.format_connection_string(dbname=dbname)
185
+
186
+ dbengine = create_engine(connstr, echo=echo)
187
+
188
+ return dbengine
189
+
190
+ def get_connection_scheme(self):
191
+
192
+ conn_scheme = None
193
+
194
+ # If the database type we are creating an engine for is postgresql, make sure
195
+ # we can import all of the postgresql dependencies.
196
+ if self.dbprofile.dbtype == 'postgresql':
197
+ try:
198
+ import psycopg2
199
+ except ImportError:
200
+ errmsg_lines = [
201
+ "In order to create a postgresql database engine, you must install the following dependencies.",
202
+ "DEPENDENCIES",
203
+ " psycopg2"
204
+ ]
205
+ errmsg = os.linesep.join(errmsg_lines)
206
+ raise ConfigurationError(errmsg)
207
+
208
+ conn_scheme = "postgresql+psycopg2"
209
+
210
+ return conn_scheme
211
+
212
+ def format_connection_string(self, dbname: Optional[str]=None) -> str:
213
+
214
+ conn_str = None
215
+
216
+ conn_scheme: str = self.get_connection_scheme()
217
+ host: str = self.dbprofile.host
218
+ port: str = self.dbprofile.port
219
+
220
+ if isinstance(self.credential, BasicCredential):
221
+ credobj: BasicCredential = self.credential
222
+
223
+ username = credobj.username
224
+ password = credobj.password
225
+
226
+ conn_str = f"{conn_scheme}://{username}:{password}@{host}:{port}"
227
+
228
+ if dbname is not None:
229
+ conn_str += f"/{dbname}"
230
+ else:
231
+ conn_str += f"/{self.dbprofile.dbname}"
232
+
233
+ return conn_str
234
+
235
+
236
+
237
+ DATABASE_CONNECTION_FACTORIES = {}
238
+
239
+
240
+ def create_database_connection_factory(dbprofile: DatabaseBasicSocketProfile, cred_manager: CredentialManager) -> DatabaseConnectionFactory:
241
+
242
+ global DATABASE_CONNECTION_FACTORIES
243
+
244
+ conn_factory = None
245
+
246
+ profile_name = dbprofile.identifier
247
+
248
+ if profile_name in DATABASE_CONNECTION_FACTORIES:
249
+ conn_factory = DATABASE_CONNECTION_FACTORIES[profile_name]
250
+
251
+ else:
252
+
253
+ credential_name = dbprofile.credential
254
+
255
+ credential: BaseCredential = cred_manager.credentials[credential_name]
256
+
257
+ category = dbprofile.category
258
+ if category == "basic-socket":
259
+ conn_factory = BasicSocketDatabaseConnectionFactory(profile_name, dbprofile=dbprofile, credential=credential)
260
+ elif category == "basic-tcp":
261
+ conn_factory = BasicTcpDatabaseConnectionFactory(profile_name, dbprofile=dbprofile, credential=credential)
262
+ else:
263
+ errmsg = "Unknown database connection type. profile_name={} category={}".format(
264
+ profile_name, category)
265
+ raise ConfigurationError(errmsg)
266
+
267
+ DATABASE_CONNECTION_FACTORIES[profile_name] = conn_factory
268
+
269
+ return conn_factory
@@ -0,0 +1,158 @@
1
+ """
2
+ .. module:: dbio
3
+ :platform: Darwin, Linux, Unix, Windows
4
+ :synopsis: Contains helper functions for working with specific databases
5
+
6
+ .. moduleauthor:: Myron Walker <myron.walker@gmail.com>
7
+ """
8
+
9
+ __author__ = "Myron Walker"
10
+ __copyright__ = "Copyright 2025, Automation Mojo LLC"
11
+ __credits__ = []
12
+ __version__ = "1.0.0"
13
+
14
+
15
+ from typing import Optional
16
+
17
+
18
+ import contextlib
19
+ import logging
20
+
21
+ from sqlalchemy.engine.base import Engine as SqlEngine
22
+ from sqlalchemy.sql.schema import MetaData
23
+
24
+ from sqlalchemy_utils import database_exists as sqlutils_database_exists
25
+ from sqlalchemy_utils import create_database as sqlutils_create_database
26
+ from sqlalchemy_utils import drop_database as sqlutils_drop_database
27
+
28
+ from ccai.errors.exceptions import ConfigurationError
29
+
30
+ from ccai.credentials.credentialmanager import CredentialManager
31
+
32
+ from ccai.dataprofiles.databasebasicsocketprofile import DatabaseBasicSocketProfile
33
+
34
+ from ccai.pychestrate.core.storage.orm import PychestrateModelBase
35
+
36
+ from ccai.pychestrate.core.storage.dbconnection import DatabaseConnectionFactory, create_database_connection_factory
37
+
38
+
39
+ logger = logging.getLogger()
40
+
41
+
42
+ def database_exists(conn_factory: DatabaseConnectionFactory, dbname: Optional[str]=None) -> bool:
43
+ """
44
+ Checks to see if the specified database exists.
45
+ """
46
+ conn_str = conn_factory.format_connection_string(dbname=dbname)
47
+
48
+ exists = sqlutils_database_exists(conn_str)
49
+
50
+ return exists
51
+
52
+
53
+ def database_create(conn_factory: DatabaseConnectionFactory, dbname: Optional[str]=None, dbmetadata: Optional[MetaData]=None):
54
+ """
55
+ Creates the specified database.
56
+ """
57
+
58
+ conn_str = conn_factory.format_connection_string(dbname=dbname)
59
+
60
+ sqlutils_create_database(conn_str)
61
+
62
+ if dbmetadata is not None:
63
+ engine = conn_factory.create_engine(dbname=dbname)
64
+ dbmetadata.create_all(engine)
65
+
66
+ return
67
+
68
+
69
+ def create_network_attached_database(db_profile: DatabaseBasicSocketProfile, cred_manager: CredentialManager, orm_metadata: MetaData, *, reset: bool = False):
70
+ """
71
+ Creates the 'pychestrate' database.
72
+
73
+ :param db_profile: The database profile configuration.
74
+ :param cred_manager: Credential manager instance for obtaining credentials.
75
+ :param orm_metadata: The Object Relational Model to use when creating the database.
76
+ :param reset: When ``True`` delete the existing database before creation.
77
+ """
78
+ db_name = db_profile.dbname
79
+
80
+ conn_factory = create_database_connection_factory(db_profile, cred_manager)
81
+ if conn_factory is None:
82
+ db_profile_name = db_profile["name"]
83
+ errmsg = "'create_network_attached_database' could not get a connection factory for profile={}".format(
84
+ db_profile_name
85
+ )
86
+ raise ConfigurationError(errmsg)
87
+
88
+ if reset and database_exists(conn_factory, db_name):
89
+ conn_str = conn_factory.format_connection_string(dbname=db_name)
90
+ sqlutils_drop_database(conn_str)
91
+
92
+ if not database_exists(conn_factory, db_name):
93
+ database_create(conn_factory, db_name)
94
+
95
+ engine = conn_factory.create_engine(dbname=db_name, echo=True)
96
+
97
+ orm_metadata.create_all(engine, checkfirst=True)
98
+
99
+ return
100
+
101
+
102
+ def open_network_attached_database(db_profile: DatabaseBasicSocketProfile, cred_manager: CredentialManager) -> SqlEngine:
103
+ """
104
+ Opens the postgresql database.
105
+
106
+ :param db_profile: The database profile configuration.
107
+ :param cred_manager: Credential manager instance for obtaining credentials.
108
+ :param orm_metadata: The Object Relational Model to use when creating the database.
109
+ :param reset: When ``True`` delete the existing database before creation.
110
+
111
+ :returns: A :class:`SqlEngine` object that can be used to interoperate with the specified database.
112
+ """
113
+ dbname = db_profile.dbname
114
+
115
+ conn_factory = create_database_connection_factory(db_profile, cred_manager)
116
+ if conn_factory is None:
117
+ db_profile_name = db_profile["name"]
118
+ errmsg = "'open_pychestrate_database' could not get a connection factory for profile={}".format(
119
+ db_profile_name
120
+ )
121
+ raise ConfigurationError(errmsg)
122
+
123
+ engine = conn_factory.create_engine(dbname=dbname, echo=True)
124
+
125
+ return engine
126
+
127
+
128
+ def reset_network_attached_database(db_profile: DatabaseBasicSocketProfile, cred_manager: CredentialManager, orm_metadata: MetaData):
129
+ """
130
+ Reset a network attached database database.
131
+
132
+ :param db_profile: The database profile configuration.
133
+ :param cred_manager: Credential manager instance for obtaining credentials.
134
+ :param orm_metadata: The Object Relational Model to use when creating the database.
135
+ :param reset: When ``True`` delete the existing database before creation.
136
+ """
137
+ dbname = db_profile.dbname
138
+
139
+ conn_factory = create_database_connection_factory(db_profile, cred_manager)
140
+ if conn_factory is None:
141
+ db_profile_name = db_profile["name"]
142
+ errmsg = "'reset_pychestrate_database' could not get a connection factory for profile={}".format(
143
+ db_profile_name
144
+ )
145
+ raise ConfigurationError(errmsg)
146
+
147
+ if database_exists(conn_factory, dbname):
148
+
149
+ engine = conn_factory.create_engine(dbname=dbname, echo=True)
150
+
151
+ with contextlib.closing(engine.connect()) as con:
152
+ trans = con.begin()
153
+ con.execute('TRUNCATE {} RESTART IDENTITY;'.format(
154
+ ','.join(table.name
155
+ for table in reversed(orm_metadata.sorted_tables))))
156
+ trans.commit()
157
+
158
+ return
@@ -0,0 +1,29 @@
1
+ __author__ = "Myron Walker"
2
+ __copyright__ = "Copyright 2025, Automation Mojo LLC"
3
+ __credits__ = []
4
+
5
+ from uuid import uuid4
6
+
7
+ from datetime import datetime, timezone, timedelta
8
+
9
+ def new_uuid() -> str:
10
+ rtnval = str(uuid4())
11
+ return rtnval
12
+
13
+ def utc_now() -> datetime:
14
+ """
15
+ Provides the current UTC time. This is used as a default value for datetime fields in models.
16
+
17
+ :return: datetime
18
+ """
19
+ now = datetime.now(timezone.utc)
20
+ return now
21
+
22
+ def reset_token_expiration() -> datetime:
23
+ """
24
+ Provides the expiration time for reset tokens. This is set to 24 hours from now.
25
+
26
+ :return: datetime
27
+ """
28
+ expiration = utc_now() + timedelta(days=1)
29
+ return expiration