@an5/adapters 0.2.3 → 0.2.4

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.
Files changed (96) hide show
  1. package/README.md +304 -283
  2. package/dotnet/An5Adapter.cs +275 -275
  3. package/dotnet/Base/Core.cs +82 -82
  4. package/dotnet/Mssql/MssqlEngine.cs +144 -144
  5. package/dotnet/Postgres/PostgresEngine.cs +121 -121
  6. package/package.json +20 -10
  7. package/python/an5_adapter.py +97 -97
  8. package/python/base/__init__.py +3 -3
  9. package/python/base/dialects.py +8 -8
  10. package/python/base/metadata.py +10 -10
  11. package/python/base/sql.py +121 -121
  12. package/python/mssql/__init__.py +1 -1
  13. package/python/mssql/provider.py +62 -62
  14. package/python/postgres/__init__.py +1 -1
  15. package/python/postgres/provider.py +15 -15
  16. package/python/table_client.py +216 -216
  17. package/typescript/src/an5Adapter.ts +505 -505
  18. package/typescript/src/base/index.ts +3 -3
  19. package/typescript/src/base/metadata.ts +28 -28
  20. package/typescript/src/base/sql.ts +98 -98
  21. package/typescript/src/base/types.ts +24 -24
  22. package/typescript/src/browser.ts +6 -6
  23. package/typescript/src/config.ts +95 -95
  24. package/typescript/src/googlesheets/adapter.ts +259 -259
  25. package/typescript/src/googlesheets/an5SheetsAdapter.ts +3 -3
  26. package/typescript/src/googlesheets/config.ts +47 -47
  27. package/typescript/src/googlesheets/helpers.ts +143 -143
  28. package/typescript/src/googlesheets/index.ts +3 -3
  29. package/typescript/src/googlesheets/parseConnectionString.ts +46 -46
  30. package/typescript/src/googlesheets/retry.ts +126 -126
  31. package/typescript/src/googlesheets/sqlExecutor.ts +301 -301
  32. package/typescript/src/googlesheets/tableClient.ts +455 -455
  33. package/typescript/src/googlesheets/types.ts +6 -6
  34. package/typescript/src/index.ts +18 -18
  35. package/typescript/src/mssql/engine.ts +132 -132
  36. package/typescript/src/mssql/index.ts +1 -1
  37. package/typescript/src/mysql/engine.ts +80 -80
  38. package/typescript/src/mysql/index.ts +1 -1
  39. package/typescript/src/postgres/engine.ts +87 -87
  40. package/typescript/src/postgres/index.ts +1 -1
  41. package/typescript/src/sqlite/engine.ts +55 -55
  42. package/typescript/src/sqlite/index.ts +1 -1
  43. /package/{typescript → dist}/an5Adapter.d.ts +0 -0
  44. /package/{typescript → dist}/an5Adapter.js +0 -0
  45. /package/{typescript → dist}/base/index.d.ts +0 -0
  46. /package/{typescript → dist}/base/index.js +0 -0
  47. /package/{typescript → dist}/base/metadata.d.ts +0 -0
  48. /package/{typescript → dist}/base/metadata.js +0 -0
  49. /package/{typescript → dist}/base/sql.d.ts +0 -0
  50. /package/{typescript → dist}/base/sql.js +0 -0
  51. /package/{typescript → dist}/base/types.d.ts +0 -0
  52. /package/{typescript → dist}/base/types.js +0 -0
  53. /package/{typescript → dist}/base/uuid.d.ts +0 -0
  54. /package/{typescript → dist}/base/uuid.js +0 -0
  55. /package/{typescript → dist}/browser.d.ts +0 -0
  56. /package/{typescript → dist}/browser.js +0 -0
  57. /package/{typescript → dist}/config.d.ts +0 -0
  58. /package/{typescript → dist}/config.js +0 -0
  59. /package/{typescript → dist}/googlesheets/adapter.d.ts +0 -0
  60. /package/{typescript → dist}/googlesheets/adapter.js +0 -0
  61. /package/{typescript → dist}/googlesheets/an5SheetsAdapter.d.ts +0 -0
  62. /package/{typescript → dist}/googlesheets/an5SheetsAdapter.js +0 -0
  63. /package/{typescript → dist}/googlesheets/config.d.ts +0 -0
  64. /package/{typescript → dist}/googlesheets/config.js +0 -0
  65. /package/{typescript → dist}/googlesheets/helpers.d.ts +0 -0
  66. /package/{typescript → dist}/googlesheets/helpers.js +0 -0
  67. /package/{typescript → dist}/googlesheets/index.d.ts +0 -0
  68. /package/{typescript → dist}/googlesheets/index.js +0 -0
  69. /package/{typescript → dist}/googlesheets/parseConnectionString.d.ts +0 -0
  70. /package/{typescript → dist}/googlesheets/parseConnectionString.js +0 -0
  71. /package/{typescript → dist}/googlesheets/retry.d.ts +0 -0
  72. /package/{typescript → dist}/googlesheets/retry.js +0 -0
  73. /package/{typescript → dist}/googlesheets/sqlExecutor.d.ts +0 -0
  74. /package/{typescript → dist}/googlesheets/sqlExecutor.js +0 -0
  75. /package/{typescript → dist}/googlesheets/tableClient.d.ts +0 -0
  76. /package/{typescript → dist}/googlesheets/tableClient.js +0 -0
  77. /package/{typescript → dist}/googlesheets/types.d.ts +0 -0
  78. /package/{typescript → dist}/googlesheets/types.js +0 -0
  79. /package/{typescript → dist}/index.d.ts +0 -0
  80. /package/{typescript → dist}/index.js +0 -0
  81. /package/{typescript → dist}/mssql/engine.d.ts +0 -0
  82. /package/{typescript → dist}/mssql/engine.js +0 -0
  83. /package/{typescript → dist}/mssql/index.d.ts +0 -0
  84. /package/{typescript → dist}/mssql/index.js +0 -0
  85. /package/{typescript → dist}/mysql/engine.d.ts +0 -0
  86. /package/{typescript → dist}/mysql/engine.js +0 -0
  87. /package/{typescript → dist}/mysql/index.d.ts +0 -0
  88. /package/{typescript → dist}/mysql/index.js +0 -0
  89. /package/{typescript → dist}/postgres/engine.d.ts +0 -0
  90. /package/{typescript → dist}/postgres/engine.js +0 -0
  91. /package/{typescript → dist}/postgres/index.d.ts +0 -0
  92. /package/{typescript → dist}/postgres/index.js +0 -0
  93. /package/{typescript → dist}/sqlite/engine.d.ts +0 -0
  94. /package/{typescript → dist}/sqlite/engine.js +0 -0
  95. /package/{typescript → dist}/sqlite/index.d.ts +0 -0
  96. /package/{typescript → dist}/sqlite/index.js +0 -0
@@ -1,121 +1,121 @@
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 _quote(name: str, dialect: str) -> str:
8
- if dialect == DIALECT_POSTGRES:
9
- if name.startswith("[") and name.endswith("]"):
10
- return f'"{name[1:-1]}"'
11
- return f'"{name}"'
12
- return name if name.startswith("[") else f"[{name}]"
13
-
14
-
15
- # ─── Connection helpers live in provider modules ────────────────────────────────
16
-
17
- # ─── Where clause builder ──────────────────────────────────────────────────────────
18
-
19
- def _parse_where(model_name: str, where: Optional[Dict], params: Dict, dialect: str, prefix: str = "") -> str:
20
- if not where:
21
- return ""
22
- conditions: List[str] = []
23
-
24
- for key, value in where.items():
25
- if key == "OR" and isinstance(value, list):
26
- sub = [_parse_where(model_name, v, params, dialect, f"{prefix}or{i}_") for i, v in enumerate(value)]
27
- sub = [s for s in sub if s]
28
- if sub:
29
- conditions.append(f"({' OR '.join(sub)})")
30
- elif key == "AND" and isinstance(value, list):
31
- sub = [_parse_where(model_name, v, params, dialect, f"{prefix}and{i}_") for i, v in enumerate(value)]
32
- sub = [s for s in sub if s]
33
- if sub:
34
- conditions.append(f"({' AND '.join(sub)})")
35
- else:
36
- col = _quote(key, dialect)
37
- pname = f"{prefix}{key}"
38
- placeholder = "%s" if dialect == DIALECT_POSTGRES else "?"
39
- if value is None:
40
- conditions.append(f"{col} IS NULL")
41
- elif isinstance(value, dict):
42
- if "not" in value:
43
- if value["not"] is None:
44
- conditions.append(f"{col} IS NOT NULL")
45
- else:
46
- params[f"{pname}_not"] = value["not"]
47
- conditions.append(f"{col} <> {placeholder}")
48
- if "equals" in value:
49
- params[f"{pname}_eq"] = value["equals"]
50
- conditions.append(f"{col} = {placeholder}")
51
- if "contains" in value:
52
- params[f"{pname}_co"] = f"%{value['contains']}%"
53
- conditions.append(f"{col} LIKE {placeholder}")
54
- if "startsWith" in value:
55
- params[f"{pname}_sw"] = f"{value['startsWith']}%"
56
- conditions.append(f"{col} LIKE {placeholder}")
57
- if "endsWith" in value:
58
- params[f"{pname}_ew"] = f"%{value['endsWith']}"
59
- conditions.append(f"{col} LIKE {placeholder}")
60
- if "gte" in value:
61
- params[f"{pname}_gte"] = value["gte"]
62
- conditions.append(f"{col} >= {placeholder}")
63
- if "lte" in value:
64
- params[f"{pname}_lte"] = value["lte"]
65
- conditions.append(f"{col} <= {placeholder}")
66
- if "gt" in value:
67
- params[f"{pname}_gt"] = value["gt"]
68
- conditions.append(f"{col} > {placeholder}")
69
- if "lt" in value:
70
- params[f"{pname}_lt"] = value["lt"]
71
- conditions.append(f"{col} < {placeholder}")
72
- if "in" in value:
73
- vals = value["in"]
74
- if vals:
75
- placeholders = ", ".join([placeholder] * len(vals))
76
- for i, v in enumerate(vals):
77
- params[f"{pname}_in{i}"] = v
78
- conditions.append(f"{col} IN ({placeholders})")
79
- else:
80
- conditions.append("1=0")
81
- else:
82
- params[pname] = value
83
- conditions.append(f"{col} = {placeholder}")
84
-
85
- return " AND ".join(conditions)
86
-
87
-
88
- def _build_order_by(order_by: Any, dialect: str) -> str:
89
- if not order_by:
90
- return ""
91
- entries = order_by if isinstance(order_by, list) else [order_by]
92
- parts: List[str] = []
93
- for entry in entries:
94
- for col, direction in entry.items():
95
- parts.append(f"{_quote(col, dialect)} {str(direction).upper()}")
96
- return f"ORDER BY {', '.join(parts)}" if parts else ""
97
-
98
-
99
- # ─── Table name resolution ─────────────────────────────────────────────────────────
100
-
101
- def _resolve_table(model_name: str) -> str:
102
- if model_name in model_to_table:
103
- return model_to_table[model_name]
104
- camel = model_name[0].lower() + model_name[1:] if model_name else model_name
105
- if camel in model_to_table:
106
- return model_to_table[camel]
107
- lower = model_name.lower()
108
- if lower in model_to_table:
109
- return model_to_table[lower]
110
- return model_name
111
-
112
-
113
- def _quote_table(t: str, dialect: str) -> str:
114
- q = lambda s: f'"{s}"' if dialect == DIALECT_POSTGRES else f"[{s}]"
115
- if t.startswith("[") or t.startswith('"'):
116
- return t
117
- if "." in t:
118
- return ".".join(q(p) for p in t.split("."))
119
- return q(t)
120
-
121
-
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 _quote(name: str, dialect: str) -> str:
8
+ if dialect == DIALECT_POSTGRES:
9
+ if name.startswith("[") and name.endswith("]"):
10
+ return f'"{name[1:-1]}"'
11
+ return f'"{name}"'
12
+ return name if name.startswith("[") else f"[{name}]"
13
+
14
+
15
+ # ─── Connection helpers live in provider modules ────────────────────────────────
16
+
17
+ # ─── Where clause builder ──────────────────────────────────────────────────────────
18
+
19
+ def _parse_where(model_name: str, where: Optional[Dict], params: Dict, dialect: str, prefix: str = "") -> str:
20
+ if not where:
21
+ return ""
22
+ conditions: List[str] = []
23
+
24
+ for key, value in where.items():
25
+ if key == "OR" and isinstance(value, list):
26
+ sub = [_parse_where(model_name, v, params, dialect, f"{prefix}or{i}_") for i, v in enumerate(value)]
27
+ sub = [s for s in sub if s]
28
+ if sub:
29
+ conditions.append(f"({' OR '.join(sub)})")
30
+ elif key == "AND" and isinstance(value, list):
31
+ sub = [_parse_where(model_name, v, params, dialect, f"{prefix}and{i}_") for i, v in enumerate(value)]
32
+ sub = [s for s in sub if s]
33
+ if sub:
34
+ conditions.append(f"({' AND '.join(sub)})")
35
+ else:
36
+ col = _quote(key, dialect)
37
+ pname = f"{prefix}{key}"
38
+ placeholder = "%s" if dialect == DIALECT_POSTGRES else "?"
39
+ if value is None:
40
+ conditions.append(f"{col} IS NULL")
41
+ elif isinstance(value, dict):
42
+ if "not" in value:
43
+ if value["not"] is None:
44
+ conditions.append(f"{col} IS NOT NULL")
45
+ else:
46
+ params[f"{pname}_not"] = value["not"]
47
+ conditions.append(f"{col} <> {placeholder}")
48
+ if "equals" in value:
49
+ params[f"{pname}_eq"] = value["equals"]
50
+ conditions.append(f"{col} = {placeholder}")
51
+ if "contains" in value:
52
+ params[f"{pname}_co"] = f"%{value['contains']}%"
53
+ conditions.append(f"{col} LIKE {placeholder}")
54
+ if "startsWith" in value:
55
+ params[f"{pname}_sw"] = f"{value['startsWith']}%"
56
+ conditions.append(f"{col} LIKE {placeholder}")
57
+ if "endsWith" in value:
58
+ params[f"{pname}_ew"] = f"%{value['endsWith']}"
59
+ conditions.append(f"{col} LIKE {placeholder}")
60
+ if "gte" in value:
61
+ params[f"{pname}_gte"] = value["gte"]
62
+ conditions.append(f"{col} >= {placeholder}")
63
+ if "lte" in value:
64
+ params[f"{pname}_lte"] = value["lte"]
65
+ conditions.append(f"{col} <= {placeholder}")
66
+ if "gt" in value:
67
+ params[f"{pname}_gt"] = value["gt"]
68
+ conditions.append(f"{col} > {placeholder}")
69
+ if "lt" in value:
70
+ params[f"{pname}_lt"] = value["lt"]
71
+ conditions.append(f"{col} < {placeholder}")
72
+ if "in" in value:
73
+ vals = value["in"]
74
+ if vals:
75
+ placeholders = ", ".join([placeholder] * len(vals))
76
+ for i, v in enumerate(vals):
77
+ params[f"{pname}_in{i}"] = v
78
+ conditions.append(f"{col} IN ({placeholders})")
79
+ else:
80
+ conditions.append("1=0")
81
+ else:
82
+ params[pname] = value
83
+ conditions.append(f"{col} = {placeholder}")
84
+
85
+ return " AND ".join(conditions)
86
+
87
+
88
+ def _build_order_by(order_by: Any, dialect: str) -> str:
89
+ if not order_by:
90
+ return ""
91
+ entries = order_by if isinstance(order_by, list) else [order_by]
92
+ parts: List[str] = []
93
+ for entry in entries:
94
+ for col, direction in entry.items():
95
+ parts.append(f"{_quote(col, dialect)} {str(direction).upper()}")
96
+ return f"ORDER BY {', '.join(parts)}" if parts else ""
97
+
98
+
99
+ # ─── Table name resolution ─────────────────────────────────────────────────────────
100
+
101
+ def _resolve_table(model_name: str) -> str:
102
+ if model_name in model_to_table:
103
+ return model_to_table[model_name]
104
+ camel = model_name[0].lower() + model_name[1:] if model_name else model_name
105
+ if camel in model_to_table:
106
+ return model_to_table[camel]
107
+ lower = model_name.lower()
108
+ if lower in model_to_table:
109
+ return model_to_table[lower]
110
+ return model_name
111
+
112
+
113
+ def _quote_table(t: str, dialect: str) -> str:
114
+ q = lambda s: f'"{s}"' if dialect == DIALECT_POSTGRES else f"[{s}]"
115
+ if t.startswith("[") or t.startswith('"'):
116
+ return t
117
+ if "." in t:
118
+ return ".".join(q(p) for p in t.split("."))
119
+ return q(t)
120
+
121
+
@@ -1 +1 @@
1
- from .provider import PYODBC_AVAILABLE, connect, parse_connection_string, placeholder
1
+ from .provider import PYODBC_AVAILABLE, connect, parse_connection_string, placeholder
@@ -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"