gaard-connectors 0.1.0__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
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -0,0 +1,51 @@
1
+ import re
2
+ from collections.abc import Mapping
3
+ from typing import Any, cast
4
+
5
+ from sqlalchemy import create_engine, text
6
+ from sqlalchemy.engine import Engine, RowMapping
7
+ from sqlalchemy.exc import SQLAlchemyError
8
+
9
+ from gaard_core.errors import QueryExecutionError
10
+ from gaard_core.json_utils import to_jsonable
11
+ from gaard_core.query_pipeline.models import QueryResult
12
+
13
+
14
+ class SQLAlchemyQueryExecutor:
15
+ def __init__(self, database_url: str, max_rows: int = 100) -> None:
16
+ self.database_url = database_url
17
+ self.max_rows = max_rows
18
+ self.engine: Engine = create_engine(database_url)
19
+
20
+ def execute(self, sql: str) -> QueryResult:
21
+ limited_sql = self._apply_limit(sql)
22
+
23
+ try:
24
+ with self.engine.connect() as connection:
25
+ result = connection.execute(text(limited_sql))
26
+ rows = result.mappings().fetchall()
27
+ except SQLAlchemyError as exc:
28
+ raise QueryExecutionError(
29
+ f"Query execution failed. SQL: {limited_sql}. Error: {exc}",
30
+ sql=limited_sql,
31
+ error_detail=str(exc),
32
+ ) from exc
33
+
34
+ normalized_rows = [self._normalize_row(row) for row in rows]
35
+ columns = list(normalized_rows[0].keys()) if normalized_rows else []
36
+
37
+ return QueryResult(
38
+ columns=columns,
39
+ rows=normalized_rows,
40
+ )
41
+
42
+ def _apply_limit(self, sql: str) -> str:
43
+ normalized = sql.strip().rstrip(";")
44
+
45
+ if re.search(r"\blimit\s+\d+\b", normalized, flags=re.IGNORECASE):
46
+ return normalized
47
+
48
+ return f"{normalized} LIMIT {self.max_rows}"
49
+
50
+ def _normalize_row(self, row: Mapping[str, Any] | RowMapping) -> dict[str, Any]:
51
+ return cast(dict[str, Any], to_jsonable(dict(row)))
@@ -0,0 +1,62 @@
1
+ from typing import Literal
2
+
3
+ from sqlalchemy import create_engine, inspect
4
+ from sqlalchemy.engine import Engine
5
+ from sqlalchemy.engine.reflection import Inspector
6
+
7
+ from gaard_core.schema.models import ColumnInfo, DatabaseSchema, ForeignKeyInfo, TableInfo
8
+
9
+
10
+ class SQLAlchemySchemaIntrospector:
11
+ def __init__(self, database_url: str) -> None:
12
+ self.database_url = database_url
13
+ self.engine: Engine = create_engine(database_url)
14
+
15
+ def introspect(self) -> DatabaseSchema:
16
+ inspector = inspect(self.engine)
17
+
18
+ tables: list[TableInfo] = []
19
+
20
+ for table_name in inspector.get_table_names():
21
+ tables.append(self._introspect_table(inspector, table_name, "table"))
22
+
23
+ for view_name in inspector.get_view_names():
24
+ tables.append(self._introspect_table(inspector, view_name, "view"))
25
+
26
+ return DatabaseSchema(tables=tables)
27
+
28
+ def _introspect_table(
29
+ self,
30
+ inspector: Inspector,
31
+ name: str,
32
+ object_type: Literal["table", "view"],
33
+ ) -> TableInfo:
34
+ columns = [
35
+ ColumnInfo(
36
+ name=column["name"],
37
+ type=str(column["type"]),
38
+ nullable=bool(column.get("nullable", True)),
39
+ primary_key=bool(column.get("primary_key", False)),
40
+ )
41
+ for column in inspector.get_columns(name)
42
+ ]
43
+
44
+ foreign_keys = []
45
+
46
+ if object_type == "table":
47
+ foreign_keys = [
48
+ ForeignKeyInfo(
49
+ constrained_columns=list(foreign_key.get("constrained_columns") or []),
50
+ referred_table=str(foreign_key.get("referred_table")),
51
+ referred_columns=list(foreign_key.get("referred_columns") or []),
52
+ )
53
+ for foreign_key in inspector.get_foreign_keys(name)
54
+ if foreign_key.get("referred_table")
55
+ ]
56
+
57
+ return TableInfo(
58
+ name=name,
59
+ object_type=object_type,
60
+ columns=columns,
61
+ foreign_keys=foreign_keys,
62
+ )
File without changes
@@ -0,0 +1,31 @@
1
+ Metadata-Version: 2.4
2
+ Name: gaard-connectors
3
+ Version: 0.1.0
4
+ Summary: Database connectors for GAARD based on SQLAlchemy
5
+ Requires-Python: >=3.11
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: gaard-core==0.1.0
8
+ Requires-Dist: sqlalchemy>=2.0.0
9
+ Provides-Extra: postgres
10
+ Requires-Dist: psycopg[binary]>=3.2.0; extra == "postgres"
11
+ Provides-Extra: mysql
12
+ Requires-Dist: pymysql>=1.1.0; extra == "mysql"
13
+ Provides-Extra: mssql
14
+ Requires-Dist: pyodbc>=5.1.0; extra == "mssql"
15
+ Provides-Extra: oracle
16
+ Requires-Dist: oracledb>=2.0.0; extra == "oracle"
17
+ Provides-Extra: dev
18
+ Requires-Dist: pytest>=8.0.0; extra == "dev"
19
+ Requires-Dist: ruff>=0.5.0; extra == "dev"
20
+ Requires-Dist: mypy>=1.10.0; extra == "dev"
21
+
22
+ # GAARD - Governed AI Access to Relational Data
23
+
24
+ GAARD is a self-hosted AI SQL Gateway for governed natural-language access to relational data.
25
+
26
+ GAARD allows applications and users to ask questions about relational databases using natural language while keeping SQL generation, validation, execution, prompts, connectors, and auditability under control.
27
+
28
+ For more informacion see https://github.com/pkroliszewski/gaard
29
+
30
+ # This package
31
+ Package gaard-connectors extends gaard functionality by adding support for various database connection schemes
@@ -0,0 +1,13 @@
1
+ gaard_connectors/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ gaard_connectors/mssql/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ gaard_connectors/mysql/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ gaard_connectors/oracle/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ gaard_connectors/postgres/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ gaard_connectors/sqlalchemy/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
+ gaard_connectors/sqlalchemy/executor.py,sha256=CwAYDCk5nFk9CjcRZEGlpCyVidXqOH6BErawYQMPNow,1775
8
+ gaard_connectors/sqlalchemy/introspector.py,sha256=RDmxqKdCxXqs3gyQBNRaLJ6uqTk-BSx0hFSZnm2wKGw,2067
9
+ gaard_connectors/sqlite/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
+ gaard_connectors-0.1.0.dist-info/METADATA,sha256=A0uMaGaf__qt5tJCUrCAsHvzlP5nWLC6rC6npHdq8Vg,1244
11
+ gaard_connectors-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
12
+ gaard_connectors-0.1.0.dist-info/top_level.txt,sha256=kih-wrFnx5FwDucrAcPgxDJe5N4lZwYDoxnR9PhBA64,17
13
+ gaard_connectors-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ gaard_connectors