pymnemon 0.1.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.
@@ -0,0 +1,180 @@
1
+ """
2
+ Connection error normalization for user-friendly feedback.
3
+ Maps database-specific errors to actionable messages.
4
+ """
5
+
6
+ from enum import Enum
7
+ from typing import Optional
8
+ from dataclasses import dataclass
9
+ import re
10
+
11
+
12
+ class ErrorCategory(Enum):
13
+ AUTH_FAILED = "auth_failed"
14
+ NETWORK_UNREACHABLE = "network_unreachable"
15
+ DATABASE_NOT_FOUND = "database_not_found"
16
+ SCHEMA_NOT_FOUND = "schema_not_found"
17
+ TABLE_NOT_FOUND = "table_not_found"
18
+ PERMISSION_DENIED = "permission_denied"
19
+ SSL_ERROR = "ssl_error"
20
+ TIMEOUT = "timeout"
21
+ INVALID_CONFIG = "invalid_config"
22
+ UNKNOWN = "unknown"
23
+
24
+
25
+ @dataclass
26
+ class NormalizedError:
27
+ category: ErrorCategory
28
+ message: str
29
+ next_steps: list[str]
30
+ original_error: str
31
+ field_hint: Optional[str] = None # Which form field is likely wrong
32
+
33
+
34
+ # Error patterns per database
35
+ ERROR_PATTERNS = {
36
+ "postgresql": [
37
+ (r"password authentication failed", ErrorCategory.AUTH_FAILED, ["Verify username and password", "Ensure user exists in PostgreSQL"]),
38
+ (r"could not connect to server.*Connection refused", ErrorCategory.NETWORK_UNREACHABLE, ["Verify host and port", "Check if PostgreSQL is running", "Check firewall rules"]),
39
+ (r"database .* does not exist", ErrorCategory.DATABASE_NOT_FOUND, ["Verify database name", "Check spelling"]),
40
+ (r"SSL.*certificate", ErrorCategory.SSL_ERROR, ["Verify SSL certificate paths", "Check certificate validity"]),
41
+ (r"permission denied", ErrorCategory.PERMISSION_DENIED, ["Check user privileges", "Contact database administrator"]),
42
+ (r"timeout", ErrorCategory.TIMEOUT, ["Check network connectivity", "Increase timeout settings"]),
43
+ ],
44
+
45
+ "snowflake": [
46
+ (r"Incorrect username or password", ErrorCategory.AUTH_FAILED, ["Verify username and password", "Check account identifier format"]),
47
+ (r"JWT token is invalid", ErrorCategory.AUTH_FAILED, ["Verify private key matches public key in Snowflake", "Check key pair configuration"]),
48
+ (r"account .* does not exist", ErrorCategory.INVALID_CONFIG, ["Verify account identifier", "Include region if required (e.g., xy12345.us-east-1)"]),
49
+ (r"Warehouse .* does not exist", ErrorCategory.INVALID_CONFIG, ["Verify warehouse name", "Check warehouse is created and accessible"]),
50
+ (r"Database .* does not exist", ErrorCategory.DATABASE_NOT_FOUND, ["Verify database name", "Check user has access to database"]),
51
+ (r"IP .* is not allowed", ErrorCategory.NETWORK_UNREACHABLE, ["Add your IP to Snowflake network policy", "Contact Snowflake administrator"]),
52
+ ],
53
+
54
+ "bigquery": [
55
+ (r"Could not deserialize key data", ErrorCategory.AUTH_FAILED, ["Verify service account JSON is valid", "Re-download credentials from GCP Console"]),
56
+ (r"403.*Access Denied", ErrorCategory.PERMISSION_DENIED, ["Verify service account has BigQuery permissions", "Check IAM roles in GCP Console"]),
57
+ (r"404.*Not found: Project", ErrorCategory.INVALID_CONFIG, ["Verify project ID", "Check project exists and is accessible"]),
58
+ (r"404.*Not found: Dataset", ErrorCategory.SCHEMA_NOT_FOUND, ["Verify dataset name", "Check dataset exists in project"]),
59
+ ],
60
+
61
+ "redshift": [
62
+ (r"password authentication failed", ErrorCategory.AUTH_FAILED, ["Verify username and password", "Ensure user exists in Redshift"]),
63
+ (r"could not connect to server", ErrorCategory.NETWORK_UNREACHABLE, ["Verify cluster endpoint", "Check VPC security groups", "Ensure cluster is publicly accessible or VPN is connected"]),
64
+ (r"database .* does not exist", ErrorCategory.DATABASE_NOT_FOUND, ["Verify database name"]),
65
+ ],
66
+
67
+ "mysql": [
68
+ (r"Access denied for user", ErrorCategory.AUTH_FAILED, ["Verify username and password", "Check user host permissions"]),
69
+ (r"Can't connect to MySQL server", ErrorCategory.NETWORK_UNREACHABLE, ["Verify host and port", "Check if MySQL is running"]),
70
+ (r"Unknown database", ErrorCategory.DATABASE_NOT_FOUND, ["Verify database name"]),
71
+ (r"SSL connection error", ErrorCategory.SSL_ERROR, ["Verify SSL certificate paths", "Check certificate validity"]),
72
+ ],
73
+
74
+ "clickhouse": [
75
+ (r"Authentication failed", ErrorCategory.AUTH_FAILED, ["Verify username and password"]),
76
+ (r"Connection refused", ErrorCategory.NETWORK_UNREACHABLE, ["Verify host and port", "Check if ClickHouse is running"]),
77
+ (r"Database .* doesn't exist", ErrorCategory.DATABASE_NOT_FOUND, ["Verify database name"]),
78
+ ],
79
+
80
+ "databricks": [
81
+ (r"Invalid access token", ErrorCategory.AUTH_FAILED, ["Generate new Personal Access Token in Databricks", "Verify token has not expired"]),
82
+ (r"Could not resolve host", ErrorCategory.NETWORK_UNREACHABLE, ["Verify workspace URL", "Check network connectivity"]),
83
+ (r"HTTP 404", ErrorCategory.INVALID_CONFIG, ["Verify HTTP path for SQL warehouse", "Check warehouse exists"]),
84
+ ],
85
+
86
+ "athena": [
87
+ (r"InvalidAccessKeyId", ErrorCategory.AUTH_FAILED, ["Verify AWS access key ID"]),
88
+ (r"SignatureDoesNotMatch", ErrorCategory.AUTH_FAILED, ["Verify AWS secret access key"]),
89
+ (r"Access Denied", ErrorCategory.PERMISSION_DENIED, ["Check IAM permissions for Athena and S3", "Verify S3 staging directory access"]),
90
+ (r"Database .* not found", ErrorCategory.DATABASE_NOT_FOUND, ["Verify schema/database name in Glue catalog"]),
91
+ ],
92
+
93
+ # Default patterns for databases without specific patterns
94
+ "_default": [
95
+ (r"authentication|password|credential|login failed", ErrorCategory.AUTH_FAILED, ["Verify username and password"]),
96
+ (r"connection refused|could not connect|unreachable|host", ErrorCategory.NETWORK_UNREACHABLE, ["Verify host and port", "Check network connectivity"]),
97
+ (r"database.*not exist|unknown database", ErrorCategory.DATABASE_NOT_FOUND, ["Verify database name"]),
98
+ (r"permission denied|access denied|not authorized", ErrorCategory.PERMISSION_DENIED, ["Check user permissions"]),
99
+ (r"ssl|certificate|tls", ErrorCategory.SSL_ERROR, ["Check SSL configuration"]),
100
+ (r"timeout|timed out", ErrorCategory.TIMEOUT, ["Check network connectivity", "Increase timeout"]),
101
+ ],
102
+ }
103
+
104
+ # Field hints for common error categories
105
+ FIELD_HINTS = {
106
+ ErrorCategory.AUTH_FAILED: "user or password",
107
+ ErrorCategory.NETWORK_UNREACHABLE: "host or port",
108
+ ErrorCategory.DATABASE_NOT_FOUND: "database",
109
+ ErrorCategory.SCHEMA_NOT_FOUND: "schema",
110
+ ErrorCategory.SSL_ERROR: "SSL certificate fields",
111
+ ErrorCategory.INVALID_CONFIG: "account or connection details",
112
+ }
113
+
114
+
115
+ def normalize_error(database_type: str, error: Exception) -> NormalizedError:
116
+ """
117
+ Convert database-specific error to normalized, user-friendly format.
118
+
119
+ Args:
120
+ database_type: The database type (e.g., 'postgresql', 'snowflake')
121
+ error: The original exception
122
+
123
+ Returns:
124
+ NormalizedError with category, message, and actionable next steps
125
+ """
126
+ error_str = str(error).lower()
127
+ original_error = str(error)
128
+
129
+ # Get patterns for this database type, fallback to default
130
+ patterns = ERROR_PATTERNS.get(database_type, []) + ERROR_PATTERNS["_default"]
131
+
132
+ for pattern, category, next_steps in patterns:
133
+ if re.search(pattern, error_str, re.IGNORECASE):
134
+ return NormalizedError(
135
+ category=category,
136
+ message=_get_user_message(category),
137
+ next_steps=next_steps,
138
+ original_error=original_error,
139
+ field_hint=FIELD_HINTS.get(category),
140
+ )
141
+
142
+ # Unknown error
143
+ return NormalizedError(
144
+ category=ErrorCategory.UNKNOWN,
145
+ message="An unexpected error occurred while connecting to the database.",
146
+ next_steps=["Check connection details", "Review error message below", "Contact support if issue persists"],
147
+ original_error=original_error,
148
+ field_hint=None,
149
+ )
150
+
151
+
152
+ def _get_user_message(category: ErrorCategory) -> str:
153
+ """Get user-friendly message for error category."""
154
+ messages = {
155
+ ErrorCategory.AUTH_FAILED: "Authentication failed. Please check your credentials.",
156
+ ErrorCategory.NETWORK_UNREACHABLE: "Unable to reach the database server.",
157
+ ErrorCategory.DATABASE_NOT_FOUND: "The specified database was not found.",
158
+ ErrorCategory.SCHEMA_NOT_FOUND: "The specified schema was not found.",
159
+ ErrorCategory.TABLE_NOT_FOUND: "The specified table was not found.",
160
+ ErrorCategory.PERMISSION_DENIED: "You don't have permission to perform this action.",
161
+ ErrorCategory.SSL_ERROR: "SSL/TLS connection error. Check certificate configuration.",
162
+ ErrorCategory.TIMEOUT: "Connection timed out.",
163
+ ErrorCategory.INVALID_CONFIG: "Invalid connection configuration.",
164
+ ErrorCategory.UNKNOWN: "An unexpected error occurred.",
165
+ }
166
+ return messages.get(category, messages[ErrorCategory.UNKNOWN])
167
+
168
+
169
+ def to_api_response(normalized: NormalizedError) -> dict:
170
+ """Convert NormalizedError to API response format."""
171
+ return {
172
+ "success": False,
173
+ "error": {
174
+ "category": normalized.category.value,
175
+ "message": normalized.message,
176
+ "next_steps": normalized.next_steps,
177
+ "field_hint": normalized.field_hint,
178
+ "details": normalized.original_error,
179
+ }
180
+ }
@@ -0,0 +1,220 @@
1
+ Metadata-Version: 2.4
2
+ Name: pymnemon
3
+ Version: 0.1.1
4
+ Summary: Unified db connector system with normalized error handling across multiple databases.
5
+ Author-email: Causum <support@causum.com>
6
+ Maintainer-email: Causum <support@causum.com>
7
+ License: MIT License
8
+
9
+ Copyright (c) 2026 The pymnemon contributors
10
+
11
+ Permission is hereby granted, free of charge, to any person obtaining a copy
12
+ of this software and associated documentation files (the "Software"), to deal
13
+ in the Software without restriction, including without limitation the rights
14
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15
+ copies of the Software, and to permit persons to whom the Software is
16
+ furnished to do so, subject to the following conditions:
17
+
18
+ The above copyright notice and this permission notice shall be included in all
19
+ copies or substantial portions of the Software.
20
+
21
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27
+ SOFTWARE.
28
+
29
+ Project-URL: Repository, https://gitlab.com/causum/mnemon
30
+ Requires-Python: >=3.9
31
+ Description-Content-Type: text/markdown
32
+ License-File: LICENSE
33
+ Requires-Dist: SQLAlchemy==2.0.45
34
+ Requires-Dist: psycopg2-binary
35
+ Requires-Dist: sqlalchemy-bigquery
36
+ Requires-Dist: google-cloud-bigquery
37
+ Requires-Dist: redshift-connector
38
+ Requires-Dist: clickhouse-driver
39
+ Requires-Dist: clickhouse-sqlalchemy>=0.3
40
+ Requires-Dist: duckdb
41
+ Requires-Dist: PyMySQL
42
+ Requires-Dist: pyodbc
43
+ Requires-Dist: databricks-sql-connector>=3.0
44
+ Requires-Dist: trino
45
+ Requires-Dist: PyHive
46
+ Requires-Dist: vertica-python
47
+ Requires-Dist: oracledb
48
+ Requires-Dist: teradatasql
49
+ Requires-Dist: ibm-db
50
+ Requires-Dist: ibm-db-sa
51
+ Requires-Dist: snowflake-sqlalchemy
52
+ Requires-Dist: google-cloud-bigquery-storage
53
+ Requires-Dist: PyAthena[SQLAlchemy]
54
+ Dynamic: license-file
55
+
56
+ # pymnemon
57
+
58
+ Unified SQLAlchemy connector with normalized error handling across multiple databases.
59
+
60
+ Note: The PyPI package name is `pymnemon`, but the import name is `mnemon`.
61
+
62
+ ## Install
63
+
64
+ ```bash
65
+ pip install pymnemon
66
+ ```
67
+
68
+ ## Quickstart
69
+
70
+ ```python
71
+ from mnemon import SchemaDBConnection
72
+
73
+ config = {
74
+ "database_type": "postgresql",
75
+ "auth_method": "password",
76
+ "connection": {
77
+ "user": "dbuser",
78
+ "password": "dbpass",
79
+ "host": "localhost",
80
+ "port": 5432,
81
+ "database": "analytics",
82
+ # optional
83
+ # "sslmode": "require",
84
+ },
85
+ }
86
+
87
+ with SchemaDBConnection(config) as db:
88
+ ok, engine, error = db.safe_connect()
89
+ if not ok:
90
+ print(error)
91
+ else:
92
+ rows = db.execute_query("SELECT 1 AS ok")
93
+ print(rows)
94
+ ```
95
+
96
+ ## Configuration
97
+
98
+ `SchemaDBConnection` expects a config dict with this shape:
99
+
100
+ ```python
101
+ {
102
+ "database_type": "<supported database>",
103
+ "auth_method": "<supported auth method>",
104
+ "connection": { ... database-specific fields ... }
105
+ }
106
+ ```
107
+
108
+ Supported databases and auth methods:
109
+
110
+ - postgresql: password, scram, ssl_verify, ssl_cert
111
+ - mysql: password, ssl_verify, ssl_cert
112
+ - mariadb: password, ssl_verify, ssl_cert
113
+ - clickhouse: password, ssl_verify, ssl_cert
114
+ - sqlserver: password
115
+ - trino: none, password, jwt, certificate
116
+ - sparksql: none, password, ldap
117
+ - vertica: password, ldap, ssl_verify
118
+ - oracle: password, wallet
119
+ - teradata: password, ldap
120
+ - db2: password, ldap
121
+ - snowflake: password, key_pair
122
+ - bigquery: service_account
123
+ - redshift: password, iam_role
124
+ - duckdb: local_file, motherduck
125
+ - databricks: token, oauth_m2m
126
+ - athena: iam_credentials
127
+
128
+ ### Example configs
129
+
130
+ PostgreSQL (password):
131
+
132
+ ```python
133
+ {
134
+ "database_type": "postgresql",
135
+ "auth_method": "password",
136
+ "connection": {
137
+ "user": "dbuser",
138
+ "password": "dbpass",
139
+ "host": "localhost",
140
+ "port": 5432,
141
+ "database": "analytics"
142
+ }
143
+ }
144
+ ```
145
+
146
+ Snowflake (key pair):
147
+
148
+ ```python
149
+ {
150
+ "database_type": "snowflake",
151
+ "auth_method": "key_pair",
152
+ "connection": {
153
+ "account": "xy12345.us-east-1",
154
+ "user": "DBUSER",
155
+ "warehouse": "COMPUTE_WH",
156
+ "database": "ANALYTICS",
157
+ "schema": "PUBLIC",
158
+ "private_key": {"content": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----"},
159
+ "private_key_passphrase": "optional"
160
+ }
161
+ }
162
+ ```
163
+
164
+ BigQuery (service account):
165
+
166
+ ```python
167
+ {
168
+ "database_type": "bigquery",
169
+ "auth_method": "service_account",
170
+ "connection": {
171
+ "project": "my-gcp-project",
172
+ "dataset": "analytics",
173
+ "location": "US",
174
+ "credentials_json": {"content": "{... service account json ...}"}
175
+ }
176
+ }
177
+ ```
178
+
179
+ Databricks (PAT):
180
+
181
+ ```python
182
+ {
183
+ "database_type": "databricks",
184
+ "auth_method": "token",
185
+ "connection": {
186
+ "host": "adb-1234567890.12.azuredatabricks.net",
187
+ "http_path": "/sql/1.0/warehouses/abcd1234",
188
+ "access_token": "dapi..."
189
+ }
190
+ }
191
+ ```
192
+
193
+ ## Error handling
194
+
195
+ `safe_connect()` returns `(success, engine, error)` where `error` is a normalized JSON payload with:
196
+
197
+ ```json
198
+ {
199
+ "success": false,
200
+ "error": {
201
+ "category": "auth_failed",
202
+ "message": "Authentication failed. Please check your credentials.",
203
+ "next_steps": ["Verify username and password"],
204
+ "field_hint": "user or password",
205
+ "details": "... original error ..."
206
+ }
207
+ }
208
+ ```
209
+
210
+ ## Notes on dependencies
211
+
212
+ Some drivers require system dependencies or extra setup:
213
+
214
+ - `pyodbc` for SQL Server requires an ODBC driver (e.g., ODBC Driver 18).
215
+ - `oracledb` may require Oracle client configuration depending on mode.
216
+ - `PyAthena[SQLAlchemy]` is used for Athena SQLAlchemy dialect support.
217
+
218
+ ## License
219
+
220
+ MIT
@@ -0,0 +1,11 @@
1
+ mnemon/__init__.py,sha256=cqCaWE6LJSQwJ_UFGe_eDMNxv5SKB8wfwBvKeFcjb_k,154
2
+ mnemon/db_connect.py,sha256=9AwOXhtURcl6aSyInIFQu7D9GlE1ks7mlhOUpqMjMXA,26264
3
+ mnemon/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ mnemon/utils/databricks_engine.py,sha256=tAmZxSkihn_YY3itAAlvqfJD7QMctbYfufYpE-xjSYw,5332
5
+ mnemon/utils/dialect_queries.py,sha256=BCAC83UX7bnnUNc-NimiiLCbx98c4Wbakh39p1FKJxo,13740
6
+ mnemon/utils/error_handling.py,sha256=QbsZ3S7pnjF5vMwmz2TAg3Zlrg4HwKT9HzwMDUa4Z_0,9375
7
+ pymnemon-0.1.1.dist-info/licenses/LICENSE,sha256=15QLuwn7sTjGUIFYvN4UOhGRgd-goVM6L5BV3a-dioI,1082
8
+ pymnemon-0.1.1.dist-info/METADATA,sha256=8JQK0WehCgBXihJq4R1hzXlJGLzsJ-UM2kP2AZR3dHo,5881
9
+ pymnemon-0.1.1.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
10
+ pymnemon-0.1.1.dist-info/top_level.txt,sha256=gdVWuvUbAYZHDK1-Uav9qGngx7utfne-k5ixROarUHQ,7
11
+ pymnemon-0.1.1.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.10.2)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 The pymnemon contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ mnemon