PageRenderTime 990ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/_unsorted/experimental/orm.py

https://bitbucket.org/ericsnowcurrently/commonlib
Python | 336 lines | 320 code | 1 blank | 15 comment | 2 complexity | 6602744726047c512f33b65c9201386f MD5 | raw file
Possible License(s): BSD-3-Clause
  1. """
  2. We'll start out with a very specific approach and generalize from there.
  3. """
  4. from collections.abc import MutableMapping, MutableSequence
  5. import sqlite3
  6. BASE_DB = sqlite3.Connection
  7. class RowView(MutableMapping):
  8. def __init__(self, db, table, id, key="id"):
  9. self._db = db
  10. query = "select id from ? where ?=?"
  11. row = db.execute(query, table, id).fetchone()
  12. self.table = table
  13. self.id = row[0]
  14. self.COLUMNS = tuple(row.keys())
  15. def _raw(self):
  16. query = "select * from ? where id=?"
  17. return self._db.execute(query, self.table, self.id).fetchone()
  18. def _check(self, key):
  19. if key not in self.COLUMNS:
  20. raise KeyError(key)
  21. if key == "id":
  22. raise TypeError("'id' is read-only")
  23. def __iter__(self):
  24. return iter(self._raw())
  25. def __container__(self, obj):
  26. return self.COLUMNS
  27. def __len__(self):
  28. return len(self.COLUMNS)
  29. def __getitem__(self, key):
  30. if key == "id":
  31. return self.id
  32. self.check(key)
  33. return self._raw()[key]
  34. def __setitem__(self, key, value):
  35. self._check(key)
  36. query = "update ? set ?=? where id=?"
  37. self._db.execute(query, self.table, key, value, self.id)
  38. def __delitem__(self, key):
  39. self._check(key)
  40. raise TypeError("Currently unsupported")
  41. class CSVItem(MutableSequence):
  42. """A csv-backed pseudo-minirow.
  43. The first value in the CSV sequence is considered the primary one.
  44. """
  45. def __init__(self, row, column):
  46. self._row = row
  47. self.id = row["id"]
  48. self.column = column
  49. def _raw(self):
  50. self._row[self.column].split(",")
  51. class PrimaryType(object):
  52. def __get__(self, obj, cls):
  53. if obj is None: return self
  54. try: return obj[0]
  55. except IndexError: return cls.DEFAULT
  56. def __set__(self, obj, value):
  57. obj[0] = value # XXX insert at 0 instead?
  58. primary = PrimaryType()
  59. del PrimaryType
  60. def __iter__(self):
  61. return iter(self.raw())
  62. def __contains__(self, value):
  63. return value in self.raw()
  64. def __len__(self):
  65. return len(self.raw())
  66. def __getitem__(self, index):
  67. return self.raw()[index]
  68. def __setitem__(self, index, value):
  69. types = self._raw()
  70. types[index] = value
  71. self._row[self.column] = ",".join(types)
  72. def __delitem__(self, index):
  73. types = self._raw()
  74. del types[index]
  75. self._row[self.column] = ",".join(types)
  76. class Row(sqlite3.Row):
  77. def __new__(cls, cursor, row):
  78. ...
  79. def __setitem__(self, key, value):
  80. UPDATE_SINGLE_VALUE = (
  81. "UPDATE :table"
  82. " SET :key = :value"
  83. " WHERE :idkey = :idvalue")
  84. table = self.TABLE
  85. idkey = table.primary
  86. idvalue = self[idkey]
  87. self.conn.execute(UPDATE_SINGLE_VALUE, locals())
  88. def __getattr__(self, name):
  89. return self[name]
  90. def __setattr__(self, name, value):
  91. self[name] = value
  92. class ColumnDefinition(namedtuple("BaseColumn", "name type constraints")):
  93. def __conform__(self, protocol):
  94. if protocol is sqlite3.PrepareProtocol:
  95. return self.name
  96. class TableDefinition(namedtuple("BaseTable", "name primary columns")):
  97. def __new__(cls, name, columns, primary="id"):
  98. if primary not in columns:
  99. raise TypeError("%s not in %s" % (primary, columns))
  100. return super(cls, cls).__new__(cls, name, primary, columns)
  101. def __conform__(self, protocol):
  102. if protocol is sqlite3.PrepareProtocol:
  103. return self.name
  104. class TableMeta(type):
  105. def from_sql(cls, conn, sql):
  106. ...
  107. class Table(Mapping):
  108. """
  109. """
  110. __metaclass__ = TableMeta
  111. ROW_CLASS = Row
  112. def __init__(self, conn, definition):
  113. self.conn = conn
  114. self.definition = definition
  115. @property
  116. def name(self):
  117. self.definition.name
  118. @property
  119. def primary(self):
  120. self.definition.primary
  121. @property
  122. def columns(self):
  123. self.definition.columns
  124. def _select(self, query, values):
  125. with self.conn.using_rowclass(self.ROW_CLASS):
  126. return self.conn.execute(query, values).fetchall()
  127. def keys(self):
  128. #return (r[0] for r in self._select(GET_KEYS, (self.primary,)))
  129. return (r[self.primary] for r in self.all())
  130. def one(self, *primary, **pair):
  131. total = len(primary) + len(pair)
  132. if total == 0:
  133. return None
  134. if total > 1:
  135. msg = "only one column may be queried, received %s"
  136. raise TypeError(msg % total)
  137. if pair:
  138. key, value = pair.items()[0]
  139. else:
  140. key, value = self.primary, primary
  141. GET_ONE = "SELECT * FROM ? WHERE ? = ?"
  142. values = (self.name, key, value)
  143. with self.conn.using_rowclass(self.ROW_CLASS):
  144. return self.conn.execute(GET_ONE, values).fetchall()
  145. def all(self):
  146. GET_ALL = "SELECT * FROM ?"
  147. with self.conn.using_rowclass(self.ROW_CLASS):
  148. return self.conn.execute(GET_ONE, values).fetchall()
  149. def __iter__(self):
  150. return iter(self.all())
  151. def __len__(self):
  152. rows = self.conn.execute("SELECT count(*) FROM ?", (self.name,))
  153. return rows.fetchone()[0]
  154. def __contains__(self, name):
  155. return name in self.keys()
  156. def __getitem__(self, key):
  157. row = self.one(key)
  158. if not row:
  159. return KeyError(key)
  160. return row
  161. def add(self, row):
  162. ...
  163. class RowClassContext(object):
  164. def __init__(self, db, rowclass):
  165. self.db = db
  166. self.cls = rowclass
  167. def __enter__(self):
  168. self.oldclass = self.db.row_factory
  169. self.db.row_factory = self.cls
  170. def __exit__(self, *args, **kwargs):
  171. self.db.row_factory = self.oldclass
  172. class TableClassContext(object):
  173. def __init__(self, db, tableclass):
  174. self.db = db
  175. self.cls = tableclass
  176. def __enter__(self):
  177. self.oldclass = self.db.table_factory
  178. self.db.table_factory = self.cls
  179. def __exit__(self, *args, **kwargs):
  180. self.db.table_factory = self.oldclass
  181. class DBMeta(type(BASE_DB)):
  182. def __call__(cls, location=None):
  183. if location is None:
  184. location = cls.LOCATION
  185. obj = super(cls, cls).__call__(location, isolation_level=None)
  186. return obj
  187. def from_class(cls, definition):
  188. """Class decorator to turn a bare class into a DB subclass.
  189. Ignores bases and all "private" attributes (except for __name__,
  190. __module__, and __doc__).
  191. LOCATION and ROW_CLASS are passed through. All other attributes
  192. of the class are treated as TableDefinition objects.
  193. """
  194. namespace = dict(definition.__dict__)
  195. name = namespace.pop("__name__")
  196. module = namespace.pop("__module__")
  197. doc = namespace.pop("__doc__")
  198. tables = {}
  199. for name in namespace:
  200. if name.startswith("_"):
  201. del namespace[name]
  202. elif name not in ("LOCATION", "ROW_CLASS"):
  203. tables[name] = namespace.pop(name)
  204. namespace["TABLES"] = tables
  205. obj = type(cls)(name, (cls,), namespace)
  206. obj.__doc__ = doc
  207. obj.__module__ = module
  208. return obj
  209. class DB(sqlite3.Connection, Mapping):
  210. """A wrapper providing defaults to sqlite3.Connection."""
  211. __metaclass__ = DBMeta
  212. LOCATION = ":memory:"
  213. ROW_CLASS = Row
  214. TABLE_CLASS = Table
  215. TABLES = None
  216. def __init__(self, database, *args, **kwargs)
  217. super(type(self), self).__init__(database, *args, **kwargs)
  218. self.row_factory = self.ROW_CLASS
  219. self.table_factory = self.TABLE_CLASS
  220. self.location = database
  221. self.sync()
  222. def __repr__(self):
  223. return "%s(%s)" % (type(self), self.LOCATION)
  224. def __iter__(self):
  225. return iter(tables)
  226. def __len__(self):
  227. return len(tables)
  228. def __contains__(self, key):
  229. return key in self.keys()
  230. def __getitem__(self, key):
  231. return tables[key]
  232. def keys(self):
  233. return tuple(t.name for t in tables)
  234. def using_tableclass(self, cls):
  235. return TableClassContext(self, cls)
  236. def using_rowclass(self, cls):
  237. return RowClassContext(self, cls)
  238. def _fresh_tables(self, tableclass=None):
  239. if tableclass is None:
  240. tableclass = self.table_factory
  241. with self.using_rowclass(sqlite3.Row):
  242. rows = self.execute("SELECT * FROM sqlite_master").fetchall()
  243. for row in rows:
  244. if row["name"].startswith("sqlite"):
  245. continue
  246. yield tableclass.from_sql(self, row["sql"])
  247. def sync(self):
  248. cls = self.table_factory
  249. tables = self._fresh_tables(cls)
  250. if self.TABLES is None:
  251. self._tables = dict((t.name, t) for t in tables)
  252. return
  253. self._tables = {}
  254. names = list(self.TABLES)
  255. for table in tables:
  256. if table.name not in names:
  257. msg = "Table %s exists, but is not defined."
  258. raise TypeError(msg % table.name)
  259. if table.definition != self.TABLES[table.name]:
  260. msg = "Table %s out of sync"
  261. raise TypeError(msg % table.name)
  262. self._tables[names.pop(table.name)] = table
  263. for name in names:
  264. #print("adding table %s" % name)
  265. table = cls.from_definition(self, self.TABLES[name])
  266. self._tables[name] = table