Spaces:
Running on Zero
Running on Zero
Download Agent/db_uri.py from Aniruddha7/QueryLensV2b: direct link, hf CLI and curl.
- Browser
- Download file 7.02 kB
-
https://huggingface.co/spaces/Aniruddha7/QueryLensV2b/resolve/main/Agent/db_uri.py
- Command line
-
hf download hf://spaces/Aniruddha7/QueryLensV2b/Agent/db_uri.py
-
curl -L -o db_uri.py https://huggingface.co/spaces/Aniruddha7/QueryLensV2b/resolve/main/Agent/db_uri.py
7.02 kB
| """ | |
| Database-agnostic URI normalization for SQLAlchemy. | |
| QueryLens is architected to interface with PostgreSQL, MySQL, SQLite, | |
| MS SQL Server and Oracle. Users habitually type bare scheme URLs such as | |
| `mysql://user:pass@host/db`, but SQLAlchemy resolves bare schemes to | |
| dialects whose drivers may not be installed (`mysqldb`, `pyodbc`, | |
| `cx_Oracle`). This module maps every commonly typed scheme onto the | |
| drivers the project actually ships: | |
| postgres:// / postgresql:// .... postgresql+psycopg2 (sync) | postgresql+asyncpg (async) | |
| mysql:// / mariadb:// .......... mysql+pymysql (sync) | mysql+aiomysql (async) | |
| mssql:// / sqlserver:// ........ mssql+pymssql (sync) | no async driver exists | |
| oracle:// ...................... oracle+oracledb thin (sync) | no stable async dialect | |
| sqlite:///path ................. sqlite (sync) | sqlite+aiosqlite (async) | |
| URLs that already pin a dialect driver (`scheme+driver://`) are respected: | |
| the pinned driver is kept for the layer it belongs to, and the other layer | |
| gets its family's default driver. Fully unknown schemes are passed through | |
| end to end so users can bring any SQLAlchemy-compatible database. | |
| No heavy imports here: drivers are lazy-loaded by SQLAlchemy itself, so a | |
| machine without (say) pymssql still boots and only fails when someone | |
| actually connects to an MSSQL URL. | |
| """ | |
| import re | |
| from typing import Dict, Optional, Tuple | |
| _SCHEME_RE = re.compile(r"^([a-zA-Z][a-zA-Z0-9+._-]*)://") | |
| _FAMILY_BY_SCHEME = { | |
| "postgres": "postgresql", | |
| "postgresql": "postgresql", | |
| "pg": "postgresql", | |
| "mysql": "mysql", | |
| "mariadb": "mysql", | |
| "mssql": "mssql", | |
| "sqlserver": "mssql", | |
| "oracle": "oracle", | |
| "sqlite": "sqlite", | |
| } | |
| _SYNC_DRIVERS = { | |
| "postgresql": "psycopg2", | |
| "mysql": "pymysql", | |
| "mssql": "pymssql", | |
| "oracle": "oracledb", | |
| } | |
| _ASYNC_DRIVERS = { | |
| "postgresql": "asyncpg", | |
| "mysql": "aiomysql", | |
| "sqlite": "aiosqlite", | |
| } | |
| _PREFIXES_OF_ASYNC_DRIVERS = {"asyncpg", "aiomysql", "aiosqlite", "asyncpg+gssapi"} | |
| def _parse(url: str) -> Tuple[Optional[str], Optional[str], str]: | |
| """Split a URL into (sqlalchemy_family, pinned_driver|None, '://rest').""" | |
| m = _SCHEME_RE.match(url or "") | |
| if not m: | |
| return None, None, url or "" | |
| scheme = m.group(1).lower() | |
| head, _, driver = scheme.partition("+") | |
| family = _FAMILY_BY_SCHEME.get(head, head) | |
| # '://' bytes remain part of the tail so callers can rebuild the URL | |
| return family, (driver or None) or None, url[m.end(1):] | |
| # libpq connection parameters that the shipped drivers cannot accept. | |
| # psycopg2.connect() validates keyword arguments against its own whitelist and | |
| # raises TypeError "connect() got an unexpected keyword argument '<name>'" for | |
| # anything else; asyncpg behaves the same. Neon's current console template | |
| # appends `channel_binding=require` to freshly copied connection strings, | |
| # which trips both drivers even though the URL is otherwise valid - so strip | |
| # the offending params here instead of asking users to hand-edit pasted | |
| # credentials. | |
| _DRIVER_UNSUPPORTED_QUERY_PARAMS = ("channel_binding",) | |
| def sanitize_driver_query_params(raw_uri: str) -> str: | |
| """Drop query parameters the shipped drivers reject (e.g. Neon's | |
| `channel_binding=require` from freshly copied connection strings). | |
| Scoped to the PostgreSQL family (the only dialect those libpq params | |
| apply to); TLS via `sslmode` and the password are preserved verbatim. | |
| Returns the URL unchanged when nothing needs stripping. | |
| """ | |
| url = (raw_uri or "").strip() | |
| if not url or "://" not in url: | |
| return raw_uri or url | |
| family, _, _ = _parse(url) | |
| if family != "postgresql": | |
| return url | |
| try: | |
| from sqlalchemy.engine import make_url # lazy: keep db_uri import-light | |
| _u = make_url(url) | |
| except Exception: | |
| # Regex fallback for URLs make_url cannot parse. | |
| out = url | |
| for _name in _DRIVER_UNSUPPORTED_QUERY_PARAMS: | |
| out = re.sub(r"[?&]" + re.escape(_name) + r"=[^&]*", "", out, flags=re.IGNORECASE) | |
| if "?" not in out and "&" in out.split("//", 1)[-1]: | |
| out = out.replace("&", "?", 1) | |
| return out | |
| query = dict(_u.query) | |
| dropped = [k for k in _DRIVER_UNSUPPORTED_QUERY_PARAMS if k in query] | |
| if not dropped: | |
| return url | |
| for k in dropped: | |
| query.pop(k, None) | |
| try: | |
| return _u.set(query=query).render_as_string(hide_password=False) | |
| except Exception: | |
| # Older SQLAlchemy without render_as_string: render manually. | |
| _pw = _u.password | |
| _auth = (_u.username + (f":{_pw}" if _pw else "") + "@") if _u.username else "" | |
| _hostport = (_u.host or "") + (f":{_u.port}" if _u.port else "") | |
| _db = ("/" + _u.database) if _u.database else "" | |
| _qs = "&".join(f"{k}={v}" for k, v in query.items()) if query else "" | |
| return f"{_u.drivername}://{_auth}{_hostport}{_db}" + (f"?{_qs}" if _qs else "") | |
| def normalize_db_uris(raw_uri: str) -> Dict[str, Optional[str]]: | |
| """Normalize a user-supplied connection URL for dual-layer engine use. | |
| Returns: | |
| { | |
| "family": "postgresql" | "mysql" | "mssql" | "oracle" | "sqlite" | None, | |
| "sync_uri": URL for SQLAlchemy.create_engine (always set), | |
| "async_uri": URL for create_async_engine, or None when the family | |
| has no usable async driver (mssql / oracle / exotic), | |
| } | |
| """ | |
| url = sanitize_driver_query_params(str(raw_uri or "").strip()) | |
| if not url: | |
| return {"family": None, "sync_uri": "", "async_uri": None} | |
| family, pinned, tail = _parse(url) | |
| if family is None: | |
| # Not a scheme URL at all (plain path, DSN name, ...) — trust the | |
| # caller verbatim but keep the async layer off (no async dialect). | |
| return {"family": None, "sync_uri": url, "async_uri": None} | |
| if pinned: | |
| if pinned in _PREFIXES_OF_ASYNC_DRIVERS: | |
| # User handed us an ASYNC URL; pin the sync layer to the family's | |
| # sync driver and keep their async URL verbatim. | |
| sync_uri = f"{family}+{_SYNC_DRIVERS[family]}{tail}" if family in _SYNC_DRIVERS else f"{family}{tail}" | |
| return {"family": family, "sync_uri": sync_uri, "async_uri": url} | |
| # Sync (or exotic) driver pinned: keep it verbatim so the user stays in | |
| # full control; async layer uses the family default when one exists. | |
| sync_uri = url | |
| async_uri = f"{family}+{_ASYNC_DRIVERS[family]}{tail}" if family in _ASYNC_DRIVERS else None | |
| return {"family": family, "sync_uri": sync_uri, "async_uri": async_uri} | |
| # Bare scheme: map both layers onto the drivers this project ships. | |
| sync_uri = f"{family}+{_SYNC_DRIVERS[family]}{tail}" if family in _SYNC_DRIVERS else url | |
| async_uri = f"{family}+{_ASYNC_DRIVERS[family]}{tail}" if family in _ASYNC_DRIVERS else None | |
| return {"family": family, "sync_uri": sync_uri, "async_uri": async_uri} | |