@an5/adapters 0.2.4 → 0.2.6
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.
- package/README.md +189 -304
- package/dist/an5Adapter.d.ts +85 -7
- package/dist/an5Adapter.js +736 -64
- package/dist/base/metadata.d.ts +10 -0
- package/dist/base/metadata.js +107 -0
- package/dist/base/sql.d.ts +19 -1
- package/dist/base/sql.js +152 -24
- package/dist/base/types.d.ts +4 -1
- package/dist/browser.d.ts +5 -2
- package/dist/browser.js +10 -2
- package/dist/googlesheets/adapter.d.ts +1 -0
- package/dist/googlesheets/adapter.js +9 -2
- package/dist/googlesheets/config.d.ts +4 -4
- package/dist/googlesheets/helpers.js +2 -0
- package/dist/googlesheets/parseConnectionString.js +5 -2
- package/dist/googlesheets/sqlExecutor.js +29 -24
- package/dist/googlesheets/tableClient.js +14 -5
- package/dist/index.d.ts +5 -1
- package/dist/index.js +12 -1
- package/dist/mssql/engine.js +8 -4
- package/dist/mysql/engine.js +1 -1
- package/dist/postgres/engine.js +1 -1
- package/dist/sqlite/browserEngine.d.ts +27 -0
- package/dist/sqlite/browserEngine.js +161 -0
- package/dist/sqlite/engine.js +15 -3
- package/dist/sqlite/index.d.ts +1 -0
- package/dist/sqlite/index.js +1 -0
- package/dist/typed.d.ts +5 -0
- package/dist/typed.js +2 -0
- package/dotnet/An5Adapter.cs +1089 -275
- package/dotnet/Base/Core.cs +340 -82
- package/dotnet/Mssql/MssqlEngine.cs +145 -144
- package/dotnet/Postgres/PostgresEngine.cs +121 -121
- package/golang/an5_adapter.go +314 -0
- package/golang/base/base.go +100 -0
- package/golang/base/metadata.go +116 -0
- package/golang/base/where.go +311 -0
- package/golang/go.mod +3 -0
- package/golang/mssql/mssql.go +32 -0
- package/golang/postgres/postgres.go +37 -0
- package/golang/sqlite/sqlite.go +14 -0
- package/golang/sqlite_test.go +42 -0
- package/golang/table_client.go +1329 -0
- package/package.json +18 -8
- package/python/an5_adapter.py +119 -97
- package/python/base/__init__.py +3 -3
- package/python/base/dialects.py +11 -8
- package/python/base/metadata.py +35 -10
- package/python/base/sql.py +172 -121
- package/python/mssql/__init__.py +1 -1
- package/python/mssql/provider.py +62 -62
- package/python/postgres/__init__.py +1 -1
- package/python/postgres/provider.py +15 -15
- package/python/table_client.py +619 -216
- package/typescript/src/an5Adapter.ts +1220 -505
- package/typescript/src/base/index.ts +3 -3
- package/typescript/src/base/metadata.ts +148 -28
- package/typescript/src/base/sql.ts +264 -98
- package/typescript/src/base/types.ts +27 -24
- package/typescript/src/browser.ts +10 -6
- package/typescript/src/config.ts +95 -95
- package/typescript/src/googlesheets/adapter.ts +269 -259
- package/typescript/src/googlesheets/an5SheetsAdapter.ts +3 -3
- package/typescript/src/googlesheets/config.ts +47 -47
- package/typescript/src/googlesheets/helpers.ts +144 -143
- package/typescript/src/googlesheets/index.ts +3 -3
- package/typescript/src/googlesheets/parseConnectionString.ts +48 -46
- package/typescript/src/googlesheets/retry.ts +126 -126
- package/typescript/src/googlesheets/sqlExecutor.ts +304 -301
- package/typescript/src/googlesheets/tableClient.ts +461 -455
- package/typescript/src/googlesheets/types.ts +6 -6
- package/typescript/src/index.ts +32 -19
- package/typescript/src/mssql/engine.ts +134 -132
- package/typescript/src/mssql/index.ts +1 -1
- package/typescript/src/mysql/engine.ts +80 -80
- package/typescript/src/mysql/index.ts +1 -1
- package/typescript/src/postgres/engine.ts +87 -87
- package/typescript/src/postgres/index.ts +1 -1
- package/typescript/src/sqlite/browserEngine.ts +192 -0
- package/typescript/src/sqlite/engine.ts +66 -55
- package/typescript/src/sqlite/index.ts +3 -1
- package/typescript/src/typed.ts +52 -0
package/python/base/sql.py
CHANGED
|
@@ -1,121 +1,172 @@
|
|
|
1
|
-
from typing import Any, Dict, List, Optional
|
|
2
|
-
from .dialects import DIALECT_MSSQL, DIALECT_POSTGRES
|
|
3
|
-
from .metadata import model_to_table, model_fields
|
|
4
|
-
|
|
5
|
-
# ─── Quoting ───────────────────────────────────────────────────────────────────────
|
|
6
|
-
|
|
7
|
-
def
|
|
8
|
-
if
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
1
|
+
from typing import Any, Dict, List, Optional
|
|
2
|
+
from .dialects import DIALECT_MSSQL, DIALECT_POSTGRES, DIALECT_SQLITE
|
|
3
|
+
from .metadata import model_to_table, model_fields
|
|
4
|
+
|
|
5
|
+
# ─── Quoting ───────────────────────────────────────────────────────────────────────
|
|
6
|
+
|
|
7
|
+
def _strip_wrapping(name: str, left: str, right: str) -> str:
|
|
8
|
+
return name[len(left):-len(right)] if name.startswith(left) and name.endswith(right) else name
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _quote(name: str, dialect: str) -> str:
|
|
12
|
+
raw = str(name)
|
|
13
|
+
if dialect in (DIALECT_POSTGRES, DIALECT_SQLITE):
|
|
14
|
+
unwrapped = _strip_wrapping(_strip_wrapping(raw, "[", "]"), '"', '"')
|
|
15
|
+
return f'"{unwrapped.replace(chr(34), chr(34) * 2)}"'
|
|
16
|
+
unwrapped = _strip_wrapping(raw, "[", "]")
|
|
17
|
+
return f"[{unwrapped.replace(']', ']]')}]"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _sanitize_param_name(name: str) -> str:
|
|
21
|
+
cleaned = "".join(ch if ch.isalnum() or ch == "_" else "_" for ch in str(name))
|
|
22
|
+
return cleaned if cleaned[:1].isalpha() else f"p_{cleaned}"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _normalize_sort_direction(direction: Any) -> str:
|
|
26
|
+
return "DESC" if isinstance(direction, str) and direction.upper() == "DESC" else "ASC"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# ─── Connection helpers live in provider modules ────────────────────────────────
|
|
30
|
+
|
|
31
|
+
# ─── Where clause builder ──────────────────────────────────────────────────────────
|
|
32
|
+
|
|
33
|
+
_OPERATOR_KEYS = {"equals", "in", "notIn", "contains", "startsWith", "endsWith", "not", "gte", "lte", "gt", "lt"}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _is_operator_value(value: Any) -> bool:
|
|
37
|
+
return isinstance(value, dict) and any(op in value for op in _OPERATOR_KEYS)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _parse_where(model_name: str, where: Optional[Dict], params: Dict, dialect: str, prefix: str = "") -> str:
|
|
41
|
+
if not where:
|
|
42
|
+
return ""
|
|
43
|
+
conditions: List[str] = []
|
|
44
|
+
|
|
45
|
+
clean_where: Dict[str, Any] = {}
|
|
46
|
+
for key, value in where.items():
|
|
47
|
+
if "_" in key and isinstance(value, dict) and not _is_operator_value(value):
|
|
48
|
+
clean_where.update(value)
|
|
49
|
+
else:
|
|
50
|
+
clean_where[key] = value
|
|
51
|
+
|
|
52
|
+
for key, value in clean_where.items():
|
|
53
|
+
if key == "OR" and isinstance(value, list):
|
|
54
|
+
sub = [_parse_where(model_name, v, params, dialect, f"{prefix}or{i}_") for i, v in enumerate(value)]
|
|
55
|
+
sub = [s for s in sub if s]
|
|
56
|
+
if sub:
|
|
57
|
+
conditions.append(f"({' OR '.join(sub)})")
|
|
58
|
+
elif key == "AND":
|
|
59
|
+
items = value if isinstance(value, list) else [value]
|
|
60
|
+
sub = [_parse_where(model_name, v, params, dialect, f"{prefix}and{i}_") for i, v in enumerate(items)]
|
|
61
|
+
sub = [s for s in sub if s]
|
|
62
|
+
if sub:
|
|
63
|
+
conditions.append(f"({' AND '.join(sub)})")
|
|
64
|
+
elif key == "NOT":
|
|
65
|
+
items = value if isinstance(value, list) else [value]
|
|
66
|
+
sub = [_parse_where(model_name, v, params, dialect, f"{prefix}not{i}_") for i, v in enumerate(items)]
|
|
67
|
+
sub = [s for s in sub if s]
|
|
68
|
+
if sub:
|
|
69
|
+
conditions.append(f"NOT ({' AND '.join(sub)})")
|
|
70
|
+
else:
|
|
71
|
+
col = _quote(key, dialect)
|
|
72
|
+
pname = _sanitize_param_name(f"{prefix}{key}")
|
|
73
|
+
placeholder = "%s" if dialect == DIALECT_POSTGRES else "?"
|
|
74
|
+
if value is None:
|
|
75
|
+
conditions.append(f"{col} IS NULL")
|
|
76
|
+
elif isinstance(value, dict):
|
|
77
|
+
if "not" in value:
|
|
78
|
+
if value["not"] is None:
|
|
79
|
+
conditions.append(f"{col} IS NOT NULL")
|
|
80
|
+
elif isinstance(value["not"], dict):
|
|
81
|
+
nested_params: Dict[str, Any] = {}
|
|
82
|
+
nested_sql = _parse_where(model_name, {key: value["not"]}, nested_params, dialect, f"{prefix}{key}_not_")
|
|
83
|
+
params.update(nested_params)
|
|
84
|
+
if nested_sql:
|
|
85
|
+
conditions.append(f"NOT ({nested_sql})")
|
|
86
|
+
else:
|
|
87
|
+
params[f"{pname}_not"] = value["not"]
|
|
88
|
+
conditions.append(f"{col} <> {placeholder}")
|
|
89
|
+
if "equals" in value:
|
|
90
|
+
if value["equals"] is None:
|
|
91
|
+
conditions.append(f"{col} IS NULL")
|
|
92
|
+
else:
|
|
93
|
+
params[f"{pname}_eq"] = value["equals"]
|
|
94
|
+
conditions.append(f"{col} = {placeholder}")
|
|
95
|
+
if "contains" in value:
|
|
96
|
+
params[f"{pname}_co"] = f"%{value['contains']}%"
|
|
97
|
+
conditions.append(f"{col} LIKE {placeholder}")
|
|
98
|
+
if "startsWith" in value:
|
|
99
|
+
params[f"{pname}_sw"] = f"{value['startsWith']}%"
|
|
100
|
+
conditions.append(f"{col} LIKE {placeholder}")
|
|
101
|
+
if "endsWith" in value:
|
|
102
|
+
params[f"{pname}_ew"] = f"%{value['endsWith']}"
|
|
103
|
+
conditions.append(f"{col} LIKE {placeholder}")
|
|
104
|
+
if "gte" in value:
|
|
105
|
+
params[f"{pname}_gte"] = value["gte"]
|
|
106
|
+
conditions.append(f"{col} >= {placeholder}")
|
|
107
|
+
if "lte" in value:
|
|
108
|
+
params[f"{pname}_lte"] = value["lte"]
|
|
109
|
+
conditions.append(f"{col} <= {placeholder}")
|
|
110
|
+
if "gt" in value:
|
|
111
|
+
params[f"{pname}_gt"] = value["gt"]
|
|
112
|
+
conditions.append(f"{col} > {placeholder}")
|
|
113
|
+
if "lt" in value:
|
|
114
|
+
params[f"{pname}_lt"] = value["lt"]
|
|
115
|
+
conditions.append(f"{col} < {placeholder}")
|
|
116
|
+
if "in" in value:
|
|
117
|
+
vals = value["in"]
|
|
118
|
+
if vals:
|
|
119
|
+
placeholders = ", ".join([placeholder] * len(vals))
|
|
120
|
+
for i, v in enumerate(vals):
|
|
121
|
+
params[f"{pname}_in{i}"] = v
|
|
122
|
+
conditions.append(f"{col} IN ({placeholders})")
|
|
123
|
+
else:
|
|
124
|
+
conditions.append("1=0")
|
|
125
|
+
if "notIn" in value:
|
|
126
|
+
vals = value["notIn"]
|
|
127
|
+
if vals:
|
|
128
|
+
placeholders = ", ".join([placeholder] * len(vals))
|
|
129
|
+
for i, v in enumerate(vals):
|
|
130
|
+
params[f"{pname}_notin{i}"] = v
|
|
131
|
+
conditions.append(f"{col} NOT IN ({placeholders})")
|
|
132
|
+
else:
|
|
133
|
+
conditions.append("1=1")
|
|
134
|
+
else:
|
|
135
|
+
params[pname] = value
|
|
136
|
+
conditions.append(f"{col} = {placeholder}")
|
|
137
|
+
|
|
138
|
+
return " AND ".join(conditions)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _build_order_by(order_by: Any, dialect: str) -> str:
|
|
142
|
+
if not order_by:
|
|
143
|
+
return ""
|
|
144
|
+
entries = order_by if isinstance(order_by, list) else [order_by]
|
|
145
|
+
parts: List[str] = []
|
|
146
|
+
for entry in entries:
|
|
147
|
+
for col, direction in entry.items():
|
|
148
|
+
parts.append(f"{_quote(col, dialect)} {_normalize_sort_direction(direction)}")
|
|
149
|
+
return f"ORDER BY {', '.join(parts)}" if parts else ""
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
# ─── Table name resolution ─────────────────────────────────────────────────────────
|
|
153
|
+
|
|
154
|
+
def _resolve_table(model_name: str) -> str:
|
|
155
|
+
if model_name in model_to_table:
|
|
156
|
+
return model_to_table[model_name]
|
|
157
|
+
camel = model_name[0].lower() + model_name[1:] if model_name else model_name
|
|
158
|
+
if camel in model_to_table:
|
|
159
|
+
return model_to_table[camel]
|
|
160
|
+
lower = model_name.lower()
|
|
161
|
+
if lower in model_to_table:
|
|
162
|
+
return model_to_table[lower]
|
|
163
|
+
return model_name
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _quote_table(t: str, dialect: str) -> str:
|
|
167
|
+
if t.startswith("[") or t.startswith('"'):
|
|
168
|
+
return t
|
|
169
|
+
if "." in t:
|
|
170
|
+
return ".".join(_quote(p, dialect) for p in t.split("."))
|
|
171
|
+
return _quote(t, dialect)
|
|
172
|
+
|
package/python/mssql/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
from .provider import PYODBC_AVAILABLE, connect, parse_connection_string, placeholder
|
|
1
|
+
from .provider import PYODBC_AVAILABLE, connect, parse_connection_string, placeholder
|
package/python/mssql/provider.py
CHANGED
|
@@ -1,62 +1,62 @@
|
|
|
1
|
-
try:
|
|
2
|
-
import pyodbc
|
|
3
|
-
PYODBC_AVAILABLE = True
|
|
4
|
-
except ImportError:
|
|
5
|
-
pyodbc = None
|
|
6
|
-
PYODBC_AVAILABLE = False
|
|
7
|
-
|
|
8
|
-
def parse_connection_string(url: str) -> str:
|
|
9
|
-
"""Convert sqlserver:// URL to pyodbc connection string."""
|
|
10
|
-
url = url.replace("sqlserver://", "")
|
|
11
|
-
parts = url.split(";")
|
|
12
|
-
host_part = parts[0]
|
|
13
|
-
host, _, port = host_part.partition(":")
|
|
14
|
-
port = port or "1433"
|
|
15
|
-
|
|
16
|
-
config: Dict[str, str] = {"server": host, "port": port}
|
|
17
|
-
for part in parts[1:]:
|
|
18
|
-
if "=" not in part:
|
|
19
|
-
continue
|
|
20
|
-
k, _, v = part.partition("=")
|
|
21
|
-
k = k.strip().lower()
|
|
22
|
-
v = v.strip()
|
|
23
|
-
if k in ("database",):
|
|
24
|
-
config["database"] = v
|
|
25
|
-
elif k in ("user", "uid"):
|
|
26
|
-
config["user"] = v
|
|
27
|
-
elif k in ("password", "pwd"):
|
|
28
|
-
config["password"] = v
|
|
29
|
-
elif k == "encrypt":
|
|
30
|
-
config["encrypt"] = "yes" if v.lower() == "true" else "no"
|
|
31
|
-
elif k == "trustservercertificate":
|
|
32
|
-
config["trust"] = "yes" if v.lower() == "true" else "no"
|
|
33
|
-
|
|
34
|
-
encrypt = config.get("encrypt", "yes")
|
|
35
|
-
trust = config.get("trust", "yes")
|
|
36
|
-
if "user" in config:
|
|
37
|
-
return (
|
|
38
|
-
f"DRIVER={{ODBC Driver 17 for SQL Server}};"
|
|
39
|
-
f"SERVER={config['server']},{config['port']};"
|
|
40
|
-
f"DATABASE={config.get('database', '')};"
|
|
41
|
-
f"UID={config['user']};"
|
|
42
|
-
f"PWD={config.get('password', '')};"
|
|
43
|
-
f"Encrypt={encrypt};TrustServerCertificate={trust};"
|
|
44
|
-
)
|
|
45
|
-
# Windows auth
|
|
46
|
-
return (
|
|
47
|
-
f"DRIVER={{ODBC Driver 17 for SQL Server}};"
|
|
48
|
-
f"SERVER={config['server']},{config['port']};"
|
|
49
|
-
f"DATABASE={config.get('database', '')};"
|
|
50
|
-
f"Trusted_Connection=yes;"
|
|
51
|
-
f"Encrypt={encrypt};TrustServerCertificate={trust};"
|
|
52
|
-
)
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
def connect(connection_string: str):
|
|
57
|
-
if not PYODBC_AVAILABLE:
|
|
58
|
-
raise ImportError("pyodbc is required: pip install pyodbc")
|
|
59
|
-
return pyodbc.connect(parse_connection_string(connection_string), autocommit=True)
|
|
60
|
-
|
|
61
|
-
def placeholder() -> str:
|
|
62
|
-
return "?"
|
|
1
|
+
try:
|
|
2
|
+
import pyodbc
|
|
3
|
+
PYODBC_AVAILABLE = True
|
|
4
|
+
except ImportError:
|
|
5
|
+
pyodbc = None
|
|
6
|
+
PYODBC_AVAILABLE = False
|
|
7
|
+
|
|
8
|
+
def parse_connection_string(url: str) -> str:
|
|
9
|
+
"""Convert sqlserver:// URL to pyodbc connection string."""
|
|
10
|
+
url = url.replace("sqlserver://", "")
|
|
11
|
+
parts = url.split(";")
|
|
12
|
+
host_part = parts[0]
|
|
13
|
+
host, _, port = host_part.partition(":")
|
|
14
|
+
port = port or "1433"
|
|
15
|
+
|
|
16
|
+
config: Dict[str, str] = {"server": host, "port": port}
|
|
17
|
+
for part in parts[1:]:
|
|
18
|
+
if "=" not in part:
|
|
19
|
+
continue
|
|
20
|
+
k, _, v = part.partition("=")
|
|
21
|
+
k = k.strip().lower()
|
|
22
|
+
v = v.strip()
|
|
23
|
+
if k in ("database",):
|
|
24
|
+
config["database"] = v
|
|
25
|
+
elif k in ("user", "uid"):
|
|
26
|
+
config["user"] = v
|
|
27
|
+
elif k in ("password", "pwd"):
|
|
28
|
+
config["password"] = v
|
|
29
|
+
elif k == "encrypt":
|
|
30
|
+
config["encrypt"] = "yes" if v.lower() == "true" else "no"
|
|
31
|
+
elif k == "trustservercertificate":
|
|
32
|
+
config["trust"] = "yes" if v.lower() == "true" else "no"
|
|
33
|
+
|
|
34
|
+
encrypt = config.get("encrypt", "yes")
|
|
35
|
+
trust = config.get("trust", "yes")
|
|
36
|
+
if "user" in config:
|
|
37
|
+
return (
|
|
38
|
+
f"DRIVER={{ODBC Driver 17 for SQL Server}};"
|
|
39
|
+
f"SERVER={config['server']},{config['port']};"
|
|
40
|
+
f"DATABASE={config.get('database', '')};"
|
|
41
|
+
f"UID={config['user']};"
|
|
42
|
+
f"PWD={config.get('password', '')};"
|
|
43
|
+
f"Encrypt={encrypt};TrustServerCertificate={trust};"
|
|
44
|
+
)
|
|
45
|
+
# Windows auth
|
|
46
|
+
return (
|
|
47
|
+
f"DRIVER={{ODBC Driver 17 for SQL Server}};"
|
|
48
|
+
f"SERVER={config['server']},{config['port']};"
|
|
49
|
+
f"DATABASE={config.get('database', '')};"
|
|
50
|
+
f"Trusted_Connection=yes;"
|
|
51
|
+
f"Encrypt={encrypt};TrustServerCertificate={trust};"
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def connect(connection_string: str):
|
|
57
|
+
if not PYODBC_AVAILABLE:
|
|
58
|
+
raise ImportError("pyodbc is required: pip install pyodbc")
|
|
59
|
+
return pyodbc.connect(parse_connection_string(connection_string), autocommit=True)
|
|
60
|
+
|
|
61
|
+
def placeholder() -> str:
|
|
62
|
+
return "?"
|
|
@@ -1 +1 @@
|
|
|
1
|
-
from .provider import PSYCOPG2_AVAILABLE, connect, placeholder
|
|
1
|
+
from .provider import PSYCOPG2_AVAILABLE, connect, placeholder
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
try:
|
|
2
|
-
import psycopg2
|
|
3
|
-
import psycopg2.extras
|
|
4
|
-
PSYCOPG2_AVAILABLE = True
|
|
5
|
-
except ImportError:
|
|
6
|
-
psycopg2 = None
|
|
7
|
-
PSYCOPG2_AVAILABLE = False
|
|
8
|
-
|
|
9
|
-
def connect(connection_string: str):
|
|
10
|
-
if not PSYCOPG2_AVAILABLE:
|
|
11
|
-
raise ImportError("psycopg2 is required: pip install psycopg2-binary")
|
|
12
|
-
return psycopg2.connect(connection_string)
|
|
13
|
-
|
|
14
|
-
def placeholder() -> str:
|
|
15
|
-
return "%s"
|
|
1
|
+
try:
|
|
2
|
+
import psycopg2
|
|
3
|
+
import psycopg2.extras
|
|
4
|
+
PSYCOPG2_AVAILABLE = True
|
|
5
|
+
except ImportError:
|
|
6
|
+
psycopg2 = None
|
|
7
|
+
PSYCOPG2_AVAILABLE = False
|
|
8
|
+
|
|
9
|
+
def connect(connection_string: str):
|
|
10
|
+
if not PSYCOPG2_AVAILABLE:
|
|
11
|
+
raise ImportError("psycopg2 is required: pip install psycopg2-binary")
|
|
12
|
+
return psycopg2.connect(connection_string)
|
|
13
|
+
|
|
14
|
+
def placeholder() -> str:
|
|
15
|
+
return "%s"
|