anysite-cli 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.

Potentially problematic release.


This version of anysite-cli might be problematic. Click here for more details.

Files changed (64) hide show
  1. anysite/__init__.py +4 -0
  2. anysite/__main__.py +6 -0
  3. anysite/api/__init__.py +21 -0
  4. anysite/api/client.py +271 -0
  5. anysite/api/errors.py +137 -0
  6. anysite/api/schemas.py +333 -0
  7. anysite/batch/__init__.py +1 -0
  8. anysite/batch/executor.py +176 -0
  9. anysite/batch/input.py +160 -0
  10. anysite/batch/rate_limiter.py +98 -0
  11. anysite/cli/__init__.py +1 -0
  12. anysite/cli/config.py +176 -0
  13. anysite/cli/executor.py +388 -0
  14. anysite/cli/options.py +249 -0
  15. anysite/config/__init__.py +11 -0
  16. anysite/config/paths.py +46 -0
  17. anysite/config/settings.py +187 -0
  18. anysite/dataset/__init__.py +37 -0
  19. anysite/dataset/analyzer.py +268 -0
  20. anysite/dataset/cli.py +644 -0
  21. anysite/dataset/collector.py +686 -0
  22. anysite/dataset/db_loader.py +248 -0
  23. anysite/dataset/errors.py +30 -0
  24. anysite/dataset/exporters.py +121 -0
  25. anysite/dataset/history.py +153 -0
  26. anysite/dataset/models.py +245 -0
  27. anysite/dataset/notifications.py +87 -0
  28. anysite/dataset/scheduler.py +107 -0
  29. anysite/dataset/storage.py +171 -0
  30. anysite/dataset/transformer.py +213 -0
  31. anysite/db/__init__.py +38 -0
  32. anysite/db/adapters/__init__.py +1 -0
  33. anysite/db/adapters/base.py +158 -0
  34. anysite/db/adapters/postgres.py +201 -0
  35. anysite/db/adapters/sqlite.py +183 -0
  36. anysite/db/cli.py +687 -0
  37. anysite/db/config.py +92 -0
  38. anysite/db/manager.py +166 -0
  39. anysite/db/operations/__init__.py +1 -0
  40. anysite/db/operations/insert.py +199 -0
  41. anysite/db/operations/query.py +43 -0
  42. anysite/db/schema/__init__.py +1 -0
  43. anysite/db/schema/inference.py +213 -0
  44. anysite/db/schema/types.py +71 -0
  45. anysite/db/utils/__init__.py +1 -0
  46. anysite/db/utils/sanitize.py +99 -0
  47. anysite/main.py +498 -0
  48. anysite/models/__init__.py +1 -0
  49. anysite/output/__init__.py +11 -0
  50. anysite/output/console.py +45 -0
  51. anysite/output/formatters.py +301 -0
  52. anysite/output/templates.py +76 -0
  53. anysite/py.typed +0 -0
  54. anysite/streaming/__init__.py +1 -0
  55. anysite/streaming/progress.py +121 -0
  56. anysite/streaming/writer.py +130 -0
  57. anysite/utils/__init__.py +1 -0
  58. anysite/utils/fields.py +242 -0
  59. anysite/utils/retry.py +109 -0
  60. anysite_cli-0.1.0.dist-info/METADATA +437 -0
  61. anysite_cli-0.1.0.dist-info/RECORD +64 -0
  62. anysite_cli-0.1.0.dist-info/WHEEL +4 -0
  63. anysite_cli-0.1.0.dist-info/entry_points.txt +2 -0
  64. anysite_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,183 @@
1
+ """SQLite database adapter using stdlib sqlite3."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import sqlite3
7
+ from pathlib import Path
8
+ from typing import Any
9
+
10
+ from anysite.db.adapters.base import DatabaseAdapter
11
+ from anysite.db.config import ConnectionConfig, OnConflict
12
+ from anysite.db.utils.sanitize import sanitize_identifier, sanitize_table_name
13
+
14
+
15
+ class SQLiteAdapter(DatabaseAdapter):
16
+ """SQLite adapter using the stdlib sqlite3 module."""
17
+
18
+ def __init__(self, config: ConnectionConfig) -> None:
19
+ self.config = config
20
+ self.db_path = config.path or ":memory:"
21
+ self._conn: sqlite3.Connection | None = None
22
+
23
+ def connect(self) -> None:
24
+ if self._conn is not None:
25
+ return
26
+
27
+ # Ensure parent directory exists for file-based databases
28
+ if self.db_path != ":memory:":
29
+ Path(self.db_path).parent.mkdir(parents=True, exist_ok=True)
30
+
31
+ self._conn = sqlite3.connect(self.db_path)
32
+ self._conn.row_factory = sqlite3.Row
33
+
34
+ # Enable WAL mode for better concurrent access
35
+ if self.db_path != ":memory:":
36
+ self._conn.execute("PRAGMA journal_mode=WAL")
37
+
38
+ # Enable foreign keys
39
+ self._conn.execute("PRAGMA foreign_keys=ON")
40
+
41
+ def disconnect(self) -> None:
42
+ if self._conn is not None:
43
+ self._conn.close()
44
+ self._conn = None
45
+
46
+ @property
47
+ def conn(self) -> sqlite3.Connection:
48
+ if self._conn is None:
49
+ raise RuntimeError("Not connected. Call connect() first or use as context manager.")
50
+ return self._conn
51
+
52
+ def execute(self, sql: str, params: tuple[Any, ...] | None = None) -> None:
53
+ self.conn.execute(sql, params or ())
54
+ self.conn.commit()
55
+
56
+ def fetch_one(self, sql: str, params: tuple[Any, ...] | None = None) -> dict[str, Any] | None:
57
+ cursor = self.conn.execute(sql, params or ())
58
+ row = cursor.fetchone()
59
+ if row is None:
60
+ return None
61
+ return dict(row)
62
+
63
+ def fetch_all(self, sql: str, params: tuple[Any, ...] | None = None) -> list[dict[str, Any]]:
64
+ cursor = self.conn.execute(sql, params or ())
65
+ return [dict(row) for row in cursor.fetchall()]
66
+
67
+ def insert_batch(
68
+ self,
69
+ table: str,
70
+ rows: list[dict[str, Any]],
71
+ on_conflict: OnConflict = OnConflict.ERROR,
72
+ conflict_columns: list[str] | None = None,
73
+ ) -> int:
74
+ if not rows:
75
+ return 0
76
+
77
+ safe_table = sanitize_table_name(table)
78
+
79
+ # Collect all column names from all rows
80
+ all_columns: list[str] = []
81
+ seen: set[str] = set()
82
+ for row in rows:
83
+ for col in row:
84
+ if col not in seen:
85
+ seen.add(col)
86
+ all_columns.append(col)
87
+
88
+ safe_columns = [sanitize_identifier(col) for col in all_columns]
89
+ placeholders = ", ".join("?" for _ in all_columns)
90
+ col_list = ", ".join(safe_columns)
91
+
92
+ # Build the INSERT statement based on conflict strategy
93
+ if on_conflict == OnConflict.IGNORE:
94
+ sql = f"INSERT OR IGNORE INTO {safe_table} ({col_list}) VALUES ({placeholders})"
95
+ elif on_conflict == OnConflict.REPLACE:
96
+ sql = f"INSERT OR REPLACE INTO {safe_table} ({col_list}) VALUES ({placeholders})"
97
+ elif on_conflict == OnConflict.UPDATE and conflict_columns:
98
+ safe_conflict = [sanitize_identifier(c) for c in conflict_columns]
99
+ conflict_list = ", ".join(safe_conflict)
100
+ update_cols = [c for c in safe_columns if c not in safe_conflict]
101
+ update_clause = ", ".join(f"{c} = excluded.{c}" for c in update_cols)
102
+ sql = (
103
+ f"INSERT INTO {safe_table} ({col_list}) VALUES ({placeholders}) "
104
+ f"ON CONFLICT ({conflict_list}) DO UPDATE SET {update_clause}"
105
+ )
106
+ else:
107
+ sql = f"INSERT INTO {safe_table} ({col_list}) VALUES ({placeholders})"
108
+
109
+ # Prepare parameter rows, serializing complex types
110
+ param_rows: list[tuple[Any, ...]] = []
111
+ for row in rows:
112
+ values: list[Any] = []
113
+ for col in all_columns:
114
+ val = row.get(col)
115
+ if isinstance(val, (dict, list)):
116
+ val = json.dumps(val)
117
+ values.append(val)
118
+ param_rows.append(tuple(values))
119
+
120
+ cursor = self.conn.cursor()
121
+ cursor.executemany(sql, param_rows)
122
+ self.conn.commit()
123
+ return cursor.rowcount
124
+
125
+ def table_exists(self, table: str) -> bool:
126
+ row = self.fetch_one(
127
+ "SELECT name FROM sqlite_master WHERE type='table' AND name=?",
128
+ (table,),
129
+ )
130
+ return row is not None
131
+
132
+ def get_table_schema(self, table: str) -> list[dict[str, str]]:
133
+ safe_table = sanitize_table_name(table)
134
+ rows = self.fetch_all(f"PRAGMA table_info({safe_table})")
135
+ result: list[dict[str, str]] = []
136
+ for row in rows:
137
+ result.append({
138
+ "name": row["name"],
139
+ "type": row["type"],
140
+ "nullable": "NO" if row["notnull"] else "YES",
141
+ "primary_key": "YES" if row["pk"] else "NO",
142
+ })
143
+ return result
144
+
145
+ def create_table(
146
+ self,
147
+ table: str,
148
+ columns: dict[str, str],
149
+ primary_key: str | None = None,
150
+ ) -> None:
151
+ safe_table = sanitize_table_name(table)
152
+ col_defs: list[str] = []
153
+ for col_name, col_type in columns.items():
154
+ safe_col = sanitize_identifier(col_name)
155
+ pk_suffix = " PRIMARY KEY" if col_name == primary_key else ""
156
+ col_defs.append(f"{safe_col} {col_type}{pk_suffix}")
157
+
158
+ cols_sql = ", ".join(col_defs)
159
+ sql = f"CREATE TABLE IF NOT EXISTS {safe_table} ({cols_sql})"
160
+ self.execute(sql)
161
+
162
+ def get_server_info(self) -> dict[str, str]:
163
+ return {
164
+ "type": "sqlite",
165
+ "version": sqlite3.sqlite_version,
166
+ "path": self.db_path,
167
+ "journal_mode": self._get_pragma("journal_mode"),
168
+ }
169
+
170
+ def _get_pragma(self, name: str) -> str:
171
+ row = self.fetch_one(f"PRAGMA {name}")
172
+ if row:
173
+ return str(list(row.values())[0])
174
+ return "unknown"
175
+
176
+ def _begin_transaction(self) -> None:
177
+ self.conn.execute("BEGIN")
178
+
179
+ def _commit_transaction(self) -> None:
180
+ self.conn.commit()
181
+
182
+ def _rollback_transaction(self) -> None:
183
+ self.conn.rollback()