table.py 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874
  1. # -*- coding: utf-8 -*-
  2. """
  3. Created on Wed Aug 8 15:29:21 2012
  4. @author: pietro
  5. """
  6. import ctypes
  7. try:
  8. from collections import OrderedDict
  9. except:
  10. from pygrass.orderdict import OrderedDict
  11. import grass.lib.vector as libvect
  12. from pygrass.gis import Mapset
  13. import sql
  14. DRIVERS = ('sqlite', 'pg')
  15. class DBError(Exception):
  16. def __init__(self, value):
  17. self.value = value
  18. def __str__(self):
  19. return repr(self.value)
  20. def get_path(path):
  21. if "$" not in path:
  22. return path
  23. else:
  24. mapset = Mapset()
  25. path = path.replace('$GISDBASE', mapset.gisdbase)
  26. path = path.replace('$LOCATION_NAME', mapset.location)
  27. path = path.replace('$MAPSET', mapset.name)
  28. return path
  29. class Filters(object):
  30. """Help user to build a simple sql query. ::
  31. >>> filter = Filters('table')
  32. >>> filter.get_sql()
  33. 'SELECT * FROM table;'
  34. >>> filter.where("area<10000").get_sql()
  35. 'SELECT * FROM table WHERE area<10000;'
  36. >>> filter.select("cat", "area").get_sql()
  37. 'SELECT cat, area FROM table WHERE area<10000;'
  38. >>> filter.order_by("area").limit(10).get_sql()
  39. 'SELECT cat, area FROM table WHERE area<10000 ORDER BY area LIMIT 10;'
  40. ..
  41. """
  42. def __init__(self, tname):
  43. self.tname = tname
  44. self._select = None
  45. self._where = None
  46. self._orderby = None
  47. self._limit = None
  48. def __repr__(self):
  49. return "Filters(%r)" % self.get_sql()
  50. def select(self, *args):
  51. cols = ', '.join(args) if args else '*'
  52. select = sql.SELECT[:-1]
  53. self._select = select.format(cols=cols, tname=self.tname)
  54. return self
  55. def where(self, condition):
  56. self._where = 'WHERE {condition}'.format(condition=condition)
  57. return self
  58. def order_by(self, orderby):
  59. if not isinstance(orderby, str):
  60. orderby = ', '.join(orderby)
  61. self._orderby = 'ORDER BY {orderby}'.format(orderby=orderby)
  62. return self
  63. def limit(self, number):
  64. if not isinstance(number, int):
  65. raise ValueError("Must be an integer.")
  66. else:
  67. self._limit = 'LIMIT {number}'.format(number=number)
  68. return self
  69. def get_sql(self):
  70. sql_list = list()
  71. if self._select is None:
  72. self.select()
  73. sql_list.append(self._select)
  74. if self._where is not None:
  75. sql_list.append(self._where)
  76. if self._orderby is not None:
  77. sql_list.append(self._orderby)
  78. if self._limit is not None:
  79. sql_list.append(self._limit)
  80. return "%s;" % ' '.join(sql_list)
  81. def reset(self):
  82. self._select = None
  83. self._where = None
  84. self._orderby = None
  85. self._limit = None
  86. class Columns(object):
  87. """Object to work with columns table.
  88. It is possible to instantiate a Columns object given the table name and
  89. the database connection.
  90. For a sqlite table: ::
  91. >>> import sqlite3
  92. >>> path = '$GISDBASE/$LOCATION_NAME/$MAPSET/sqlite.db'
  93. >>> cols_sqlite = Columns('boundary_municp_sqlite',
  94. ... sqlite3.connect(get_path(path)))
  95. >>> cols_sqlite.tname
  96. 'boundary_municp_sqlite'
  97. For a postgreSQL table: ::
  98. >>> import psycopg2 as pg
  99. >>> cols_pg = Columns('boundary_municp_pg',
  100. ... pg.connect('host=localhost dbname=grassdb'))
  101. >>> cols_pg.tname
  102. 'boundary_municp_pg'
  103. ..
  104. """
  105. def __init__(self, tname, connection, key='cat'):
  106. self.tname = tname
  107. self.conn = connection
  108. self.key = key
  109. self.odict = None
  110. self.update_odict()
  111. def __contains__(self, item):
  112. return item in self.names()
  113. def __repr__(self):
  114. return "Columns(%r)" % self.items()
  115. def __getitem__(self, key):
  116. return self.odict[key]
  117. def __setitem__(self, name, new_type):
  118. self.cast(name, new_type)
  119. self.update_odict(self)
  120. def __iter__(self):
  121. return self.odict.__iter__()
  122. def __len__(self):
  123. return self.odict.__len__()
  124. def __eq__(self, obj):
  125. return obj.tname == self.tname and obj.odict == self.odict
  126. def is_pg(self):
  127. """Return True if is a psycopg connection. ::
  128. >>> import sqlite3
  129. >>> path = '$GISDBASE/$LOCATION_NAME/$MAPSET/sqlite.db'
  130. >>> cols_sqlite = Columns('boundary_municp_sqlite',
  131. ... sqlite3.connect(get_path(path)))
  132. >>> cols_sqlite.is_pg()
  133. False
  134. >>> import psycopg2 as pg
  135. >>> cols_pg = Columns('boundary_municp_pg',
  136. ... pg.connect('host=localhost dbname=grassdb'))
  137. >>> cols_pg.is_pg()
  138. True
  139. ..
  140. """
  141. return hasattr(self.conn, 'xid')
  142. def update_odict(self):
  143. """Read columns name and types from table and update the odict
  144. attribute.
  145. """
  146. if self.is_pg():
  147. # is a postgres connection
  148. cur = self.conn.cursor()
  149. cur.execute("SELECT oid,typname FROM pg_type")
  150. diz = dict(cur.fetchall())
  151. odict = OrderedDict()
  152. import psycopg2 as pg
  153. try:
  154. cur.execute(sql.SELECT.format(cols='*', tname=self.tname))
  155. descr = cur.description
  156. for column in descr:
  157. name, ctype = column[:2]
  158. odict[name] = diz[ctype]
  159. except pg.ProgrammingError:
  160. pass
  161. self.odict = odict
  162. else:
  163. # is a sqlite connection
  164. cur = self.conn.cursor()
  165. cur.execute(sql.PRAGMA.format(tname=self.tname))
  166. descr = cur.fetchall()
  167. odict = OrderedDict()
  168. for column in descr:
  169. name, ctype = column[1:3]
  170. odict[name] = ctype
  171. self.odict = odict
  172. values = ','.join(['?', ] * self.__len__())
  173. kv = ','.join(['%s=?' % k for k in self.odict.keys()])
  174. where = "%s=?" % self.key
  175. self.insert_str = sql.INSERT.format(tname=self.tname, values=values)
  176. self.update_str = sql.UPDATE_WHERE.format(tname=self.tname,
  177. values=kv, condition=where)
  178. def sql_descr(self, remove=None):
  179. """Return a string with description of columns.
  180. Remove it is used to remove a columns.::
  181. >>> import sqlite3
  182. >>> path = '$GISDBASE/$LOCATION_NAME/$MAPSET/sqlite.db'
  183. >>> cols_sqlite = Columns('boundary_municp_sqlite',
  184. ... sqlite3.connect(get_path(path)))
  185. >>> cols_sqlite.sql_descr() # doctest: +ELLIPSIS
  186. u'cat integer, OBJECTID integer, AREA double precision, ...'
  187. >>> import psycopg2 as pg
  188. >>> cols_pg = Columns('boundary_municp_pg',
  189. ... pg.connect('host=localhost dbname=grassdb'))
  190. >>> cols_pg.sql_descr() # doctest: +ELLIPSIS
  191. 'cat int4, objectid int4, area float8, perimeter float8, ...'
  192. """
  193. if remove:
  194. return ', '.join(['%s %s' % (key, val) for key, val in self.items()
  195. if key != remove])
  196. else:
  197. return ', '.join(['%s %s' % (key, val)
  198. for key, val in self.items()])
  199. def types(self):
  200. """Return a list with the column types. ::
  201. >>> import sqlite3
  202. >>> path = '$GISDBASE/$LOCATION_NAME/$MAPSET/sqlite.db'
  203. >>> cols_sqlite = Columns('boundary_municp_sqlite',
  204. ... sqlite3.connect(get_path(path)))
  205. >>> cols_sqlite.types() # doctest: +ELLIPSIS
  206. [u'integer', u'integer', ...]
  207. >>> import psycopg2 as pg
  208. >>> cols_pg = Columns('boundary_municp_pg',
  209. ... pg.connect('host=localhost dbname=grassdb'))
  210. >>> cols_pg.types() # doctest: +ELLIPSIS
  211. ['int4', 'int4', 'float8', 'float8', 'float8', ...]
  212. ..
  213. """
  214. return self.odict.values()
  215. def names(self, remove=None, unicod=True):
  216. """Return a list with the column names.
  217. Remove it is used to remove a columns.::
  218. >>> import sqlite3
  219. >>> path = '$GISDBASE/$LOCATION_NAME/$MAPSET/sqlite.db'
  220. >>> cols_sqlite = Columns('boundary_municp_sqlite',
  221. ... sqlite3.connect(get_path(path)))
  222. >>> cols_sqlite.names() # doctest: +ELLIPSIS
  223. [u'cat', u'OBJECTID', u'AREA', u'PERIMETER', ...]
  224. >>> import psycopg2 as pg
  225. >>> cols_pg = Columns('boundary_municp_pg',
  226. ... pg.connect('host=localhost dbname=grassdb'))
  227. >>> cols_pg.names() # doctest: +ELLIPSIS
  228. ['cat', 'objectid', 'area', 'perimeter', ...]
  229. ..
  230. """
  231. if remove:
  232. nams = self.odict.keys()
  233. nams.remove(remove)
  234. else:
  235. nams = self.odict.keys()
  236. if unicod:
  237. return nams
  238. else:
  239. return [str(name) for name in nams]
  240. def items(self):
  241. """Return a list of tuple with column name and column type. ::
  242. >>> import sqlite3
  243. >>> path = '$GISDBASE/$LOCATION_NAME/$MAPSET/sqlite.db'
  244. >>> cols_sqlite = Columns('boundary_municp_sqlite',
  245. ... sqlite3.connect(get_path(path)))
  246. >>> cols_sqlite.items() # doctest: +ELLIPSIS
  247. [(u'cat', u'integer'), ...]
  248. >>> import psycopg2 as pg
  249. >>> cols_pg = Columns('boundary_municp_pg',
  250. ... pg.connect('host=localhost dbname=grassdb'))
  251. >>> cols_pg.items() # doctest: +ELLIPSIS
  252. [('cat', 'int4'), ('objectid', 'int4'), ('area', 'float8'), ...]
  253. ..
  254. """
  255. return self.odict.items()
  256. def create(self, cols):
  257. """"""
  258. cur = self.conn.cursor()
  259. coldef = ',\n'.join(['%s %s' % col for col in cols])
  260. cur.execute(sql.CREATE_TAB.format(tname=self.tname, coldef=coldef))
  261. self.conn.commit()
  262. cur.close()
  263. self.update_odict()
  264. def add(self, col_name, col_type):
  265. """Add a new column to the table. ::
  266. >>> import sqlite3
  267. >>> path = '$GISDBASE/$LOCATION_NAME/$MAPSET/sqlite.db'
  268. >>> cols_sqlite = Columns('boundary_municp_sqlite',
  269. ... sqlite3.connect(get_path(path)))
  270. >>> cols_sqlite.add('n_pizza', 'int4')
  271. >>> 'n_pizza' in cols_sqlite
  272. True
  273. >>> import psycopg2 as pg
  274. >>> cols_pg = Columns('boundary_municp_pg',
  275. ... pg.connect('host=localhost dbname=grassdb'))
  276. >>> cols_pg.add('n_pizza', 'int4')
  277. >>> 'n_pizza' in cols_pg
  278. True
  279. ..
  280. """
  281. valid_type = ('DOUBLE PRECISION', 'INT', 'DATE')
  282. if 'VARCHAR' in col_type or col_type.upper() not in valid_type:
  283. str_err = "Type is not supported, supported types are: %s"
  284. raise TypeError(str_err % ", ".join(valid_type))
  285. cur = self.conn.cursor()
  286. cur.execute(sql.ADD_COL.format(tname=self.tname,
  287. cname=col_name,
  288. ctype=col_type))
  289. self.conn.commit()
  290. cur.close()
  291. self.update_odict()
  292. def rename(self, old_name, new_name):
  293. """Rename a column of the table. ::
  294. >>> import sqlite3
  295. >>> path = '$GISDBASE/$LOCATION_NAME/$MAPSET/sqlite.db'
  296. >>> cols_sqlite = Columns('boundary_municp_sqlite',
  297. ... sqlite3.connect(get_path(path)))
  298. >>> cols_sqlite.rename('n_pizza', 'n_pizzas') # doctest: +ELLIPSIS
  299. >>> 'n_pizza' in cols_sqlite
  300. False
  301. >>> 'n_pizzas' in cols_sqlite
  302. True
  303. >>> import psycopg2 as pg
  304. >>> cols_pg = Columns('boundary_municp_pg',
  305. ... pg.connect('host=localhost dbname=grassdb'))
  306. >>> cols_pg.rename('n_pizza', 'n_pizzas')
  307. >>> 'n_pizza' in cols_pg
  308. False
  309. >>> 'n_pizzas' in cols_pg
  310. True
  311. ..
  312. """
  313. cur = self.conn.cursor()
  314. if self.is_pg():
  315. cur.execute(sql.RENAME_COL.format(tname=self.tname,
  316. old_name=old_name,
  317. new_name=new_name))
  318. self.conn.commit()
  319. cur.close()
  320. self.update_odict()
  321. else:
  322. cur.execute(sql.ADD_COL.format(tname=self.tname,
  323. cname=new_name,
  324. ctype=str(self.odict[old_name])))
  325. cur.execute(sql.UPDATE.format(tname=self.tname,
  326. new_col=new_name,
  327. old_col=old_name))
  328. self.conn.commit()
  329. cur.close()
  330. self.update_odict()
  331. self.drop(old_name)
  332. def cast(self, col_name, new_type):
  333. """Change the column type. ::
  334. >>> import sqlite3
  335. >>> path = '$GISDBASE/$LOCATION_NAME/$MAPSET/sqlite.db'
  336. >>> cols_sqlite = Columns('boundary_municp_sqlite',
  337. ... sqlite3.connect(get_path(path)))
  338. >>> cols_sqlite.cast('n_pizzas', 'float8') # doctest: +ELLIPSIS
  339. Traceback (most recent call last):
  340. ...
  341. DBError: 'SQLite does not support to cast columns.'
  342. >>> import psycopg2 as pg
  343. >>> cols_pg = Columns('boundary_municp_pg',
  344. ... pg.connect('host=localhost dbname=grassdb'))
  345. >>> cols_pg.cast('n_pizzas', 'float8')
  346. >>> cols_pg['n_pizzas']
  347. 'float8'
  348. .. warning ::
  349. It is not possible to cast a column with sqlite
  350. ..
  351. """
  352. if self.is_pg():
  353. cur = self.conn.cursor()
  354. cur.execute(sql.CAST_COL.format(tname=self.tname, col=col_name,
  355. ctype=new_type))
  356. self.conn.commit()
  357. cur.close()
  358. self.update_odict()
  359. else:
  360. # sqlite does not support rename columns:
  361. raise DBError('SQLite does not support to cast columns.')
  362. def drop(self, col_name):
  363. """Drop a column from the table. ::
  364. >>> import sqlite3
  365. >>> path = '$GISDBASE/$LOCATION_NAME/$MAPSET/sqlite.db'
  366. >>> cols_sqlite = Columns('boundary_municp_sqlite',
  367. ... sqlite3.connect(get_path(path)))
  368. >>> cols_sqlite.drop('n_pizzas') # doctest: +ELLIPSIS
  369. >>> 'n_pizzas' in cols_sqlite
  370. False
  371. >>> import psycopg2 as pg
  372. >>> cols_pg = Columns('boundary_municp_pg',
  373. ... pg.connect('host=localhost dbname=grassdb'))
  374. >>> cols_pg.drop('n_pizzas')
  375. >>> 'n_pizzas' in cols_pg
  376. False
  377. ..
  378. """
  379. cur = self.conn.cursor()
  380. if self.is_pg():
  381. cur.execute(sql.DROP_COL.format(tname=self.tname,
  382. cname=col_name))
  383. else:
  384. desc = str(self.sql_descr(remove=col_name))
  385. names = ', '.join(self.names(remove=col_name, unicod=False))
  386. queries = sql.DROP_COL_SQLITE.format(tname=self.tname,
  387. keycol=self.key,
  388. coldef=desc,
  389. colnames=names).split('\n')
  390. for query in queries:
  391. cur.execute(query)
  392. self.conn.commit()
  393. cur.close()
  394. self.update_odict()
  395. class Link(object):
  396. """Define a Link between vector map and the attributes table.
  397. It is possible to define a Link object or given all the information
  398. (layer, name, table name, key, database, driver): ::
  399. >>> link = Link(1, 'link0', 'boundary_municp_sqlite', 'cat',
  400. ... '$GISDBASE/$LOCATION_NAME/$MAPSET/sqlite.db', 'sqlite')
  401. >>> link.number
  402. 1
  403. >>> link.name
  404. 'link0'
  405. >>> link.table_name
  406. 'boundary_municp_sqlite'
  407. >>> link.key
  408. 'cat'
  409. >>> link.database
  410. '$GISDBASE/$LOCATION_NAME/$MAPSET/sqlite.db'
  411. >>> link.driver
  412. 'sqlite'
  413. >>> link
  414. Link(1, link0, sqlite)
  415. It is possible to change parameters with: ::
  416. >>> link.driver = 'pg'
  417. >>> link.driver
  418. 'pg'
  419. >>> link.driver = 'postgres' # doctest: +ELLIPSIS
  420. Traceback (most recent call last):
  421. ...
  422. TypeError: Driver not supported, use: sqlite, pg.
  423. >>> link.driver
  424. 'pg'
  425. >>> link.number = 0 # doctest: +ELLIPSIS
  426. Traceback (most recent call last):
  427. ...
  428. TypeError: Number must be positive and greater than 0.
  429. Or given a c_fieldinfo object that is a ctypes pointer to the field_info C
  430. struct. ::
  431. >>> link = Link(c_fieldinfo = ctypes.pointer(libvect.field_info()))
  432. ..
  433. """
  434. def _get_layer(self):
  435. return self.c_fieldinfo.contents.number
  436. def _set_layer(self, number):
  437. if number <= 0:
  438. raise TypeError("Number must be positive and greater than 0.")
  439. self.c_fieldinfo.contents.number = number
  440. layer = property(fget=_get_layer, fset=_set_layer)
  441. def _get_name(self):
  442. return self.c_fieldinfo.contents.name
  443. def _set_name(self, name):
  444. self.c_fieldinfo.contents.name = name
  445. name = property(fget=_get_name, fset=_set_name)
  446. def _get_table(self):
  447. return self.c_fieldinfo.contents.table
  448. def _set_table(self, new_name):
  449. self.c_fieldinfo.contents.table = new_name
  450. table_name = property(fget=_get_table, fset=_set_table)
  451. def _get_key(self):
  452. return self.c_fieldinfo.contents.key
  453. def _set_key(self, key):
  454. self.c_fieldinfo.contents.key = key
  455. key = property(fget=_get_key, fset=_set_key)
  456. def _get_database(self):
  457. return self.c_fieldinfo.contents.database
  458. def _set_database(self, database):
  459. self.c_fieldinfo.contents.database = database
  460. database = property(fget=_get_database, fset=_set_database)
  461. def _get_driver(self):
  462. return self.c_fieldinfo.contents.driver
  463. def _set_driver(self, driver):
  464. if driver not in ('sqlite', 'pg'):
  465. str_err = "Driver not supported, use: %s." % ", ".join(DRIVERS)
  466. raise TypeError(str_err)
  467. self.c_fieldinfo.contents.driver = driver
  468. driver = property(fget=_get_driver, fset=_set_driver)
  469. def __init__(self, layer=1, name=None, table=None, key=None,
  470. database=None, driver=None, c_fieldinfo=None):
  471. if c_fieldinfo is not None:
  472. self.c_fieldinfo = c_fieldinfo
  473. else:
  474. self.c_fieldinfo = ctypes.pointer(libvect.field_info())
  475. self.layer = layer
  476. self.name = name
  477. self.table_name = table
  478. self.key = key
  479. self.database = database
  480. self.driver = driver
  481. def __repr__(self):
  482. return "Link(%d, %s, %s)" % (self.layer, self.name, self.driver)
  483. def connection(self):
  484. """Return a connection object. ::
  485. >>> link = Link(1, 'link0', 'boundary_municp_sqlite', 'cat',
  486. ... '$GISDBASE/$LOCATION_NAME/$MAPSET/sqlite.db',
  487. ... 'sqlite')
  488. >>> conn = link.connection()
  489. >>> cur = conn.cursor()
  490. >>> cur.execute("SELECT cat,COUNTY,PERIMETER FROM %s" %
  491. ... link.table_name) # doctest: +ELLIPSIS
  492. <sqlite3.Cursor object at ...>
  493. >>> cur.fetchone()
  494. (1, u'SURRY', 1415.331)
  495. >>> cur.close()
  496. >>> conn.close()
  497. ...
  498. """
  499. if self.driver == 'sqlite':
  500. import sqlite3
  501. return sqlite3.connect(get_path(self.database))
  502. elif self.driver == 'pg':
  503. try:
  504. import psycopg2
  505. db = ' '.join(self.database.split(','))
  506. return psycopg2.connect(db)
  507. except ImportError:
  508. er = "You need to install psycopg2 to connect with this table."
  509. raise ImportError(er)
  510. else:
  511. str_err = "Driver is not supported yet, pleas use: sqlite or pg"
  512. raise TypeError(str_err)
  513. def table(self):
  514. """Return a Table object. ::
  515. >>> link = Link(1, 'link0', 'boundary_municp_sqlite', 'cat',
  516. ... '$GISDBASE/$LOCATION_NAME/$MAPSET/sqlite.db',
  517. ... 'sqlite')
  518. >>> table = link.table()
  519. >>> table.filters.select('cat', 'COUNTY', 'PERIMETER')
  520. Filters('SELECT cat, COUNTY, PERIMETER FROM boundary_municp_sqlite;')
  521. >>> cur = table.execute()
  522. >>> cur.fetchone()
  523. (1, u'SURRY', 1415.331)
  524. >>> cur.close()
  525. ..
  526. """
  527. return Table(self.table_name, self.connection(), self.key)
  528. def info(self):
  529. """Print information of the link. ::
  530. >>> link = Link(1, 'link0', 'boundary_municp_sqlite', 'cat',
  531. ... '$GISDBASE/$LOCATION_NAME/$MAPSET/sqlite.db',
  532. ... 'sqlite')
  533. >>> link.info()
  534. layer: 1
  535. name: link0
  536. table: boundary_municp_sqlite
  537. key: cat
  538. database: $GISDBASE/$LOCATION_NAME/$MAPSET/sqlite.db
  539. driver: sqlite
  540. ..
  541. """
  542. print "layer: ", self.layer
  543. print "name: ", self.name
  544. print "table: ", self.table_name
  545. print "key: ", self.key
  546. print "database: ", self.database
  547. print "driver: ", self.driver
  548. class DBlinks(object):
  549. """Interface containing link to the table DB. ::
  550. >>> from pygrass.vector import VectorTopo
  551. >>> municip = VectorTopo('boundary_municp_sqlite')
  552. >>> municip.open()
  553. >>> dblinks = DBlinks(municip.c_mapinfo)
  554. >>> dblinks
  555. DBlinks([Link(1, boundary_municp, sqlite)])
  556. >>> dblinks[1]
  557. Link(1, boundary_municp, sqlite)
  558. >>> dblinks[0] # doctest: +ELLIPSIS
  559. Traceback (most recent call last):
  560. ...
  561. TypeError: The index must be != 0.
  562. >>> dblinks['boundary_municp']
  563. Link(1, boundary_municp, sqlite)
  564. ..
  565. """
  566. def __init__(self, c_mapinfo):
  567. self.c_mapinfo = c_mapinfo
  568. def __len__(self):
  569. return self.num_dblinks()
  570. def __iter__(self):
  571. return (self.by_index(i)
  572. for i in xrange(self.num_dblinks()))
  573. def __getitem__(self, item):
  574. """
  575. """
  576. if isinstance(item, int):
  577. return self.by_index(item)
  578. else:
  579. return self.by_name(item)
  580. def __repr__(self):
  581. return "DBlinks(%r)" % [link for link in self.__iter__()]
  582. def by_index(self, indx):
  583. c_fieldinfo = libvect.Vect_get_dblink(self.c_mapinfo, indx)
  584. return Link(c_fieldinfo=c_fieldinfo)
  585. def by_layer(self, layer):
  586. c_fieldinfo = libvect.Vect_get_field(self.c_mapinfo, layer)
  587. return Link(c_fieldinfo=c_fieldinfo)
  588. def by_name(self, name):
  589. c_fieldinfo = libvect.Vect_get_field_by_name(self.c_mapinfo, name)
  590. return Link(c_fieldinfo=c_fieldinfo)
  591. def num_dblinks(self):
  592. return libvect.Vect_get_num_dblinks(self.c_mapinfo)
  593. def add(self, link):
  594. """Add a new link. ::
  595. >>> from pygrass.vector import VectorTopo
  596. >>> municip = VectorTopo('boundary_municp_sqlite')
  597. >>> municip.open()
  598. >>> dblinks = DBlinks(municip.c_mapinfo)
  599. >>> dblinks
  600. DBlinks([Link(1, boundary_municp, sqlite)])
  601. >>> link = Link(2, 'pg_link', 'boundary_municp_pg', 'cat',
  602. ... 'host=localhost dbname=grassdb', 'pg')
  603. >>> dblinks.add(link)
  604. >>> dblinks # need to open vector map in write mode
  605. DBlinks([Link(1, boundary_municp, sqlite)])
  606. ..
  607. """
  608. #TODO: check if open in write mode or not.
  609. libvect.Vect_map_add_dblink(self.c_mapinfo,
  610. link.layer, link.name, link.table_name,
  611. link.key, link.database, link.driver)
  612. def remove(self, key):
  613. """Remove a link. ::
  614. >>> from pygrass.vector import VectorTopo
  615. >>> municip = VectorTopo('boundary_municp_sqlite')
  616. >>> municip.open()
  617. >>> dblinks = DBlinks(municip.c_mapinfo)
  618. >>> dblinks
  619. DBlinks([Link(1, boundary_municp, sqlite)])
  620. >>> dblinks.remove('pg_link')
  621. >>> dblinks # need to open vector map in write mode
  622. DBlinks([Link(1, boundary_municp, sqlite)])
  623. ..
  624. """
  625. if isinstance(key, str):
  626. key = self.from_name_to_num(key)
  627. libvect.Vect_map_del_dblink(self.c_mapinfo, key)
  628. def from_name_to_num(self, name):
  629. """
  630. Vect_get_field_number
  631. """
  632. return libvect.Vect_get_field_number(self.c_mapinfo, name)
  633. class Table(object):
  634. """::
  635. >>> import sqlite3
  636. >>> path = '$GISDBASE/$LOCATION_NAME/$MAPSET/sqlite.db'
  637. >>> tab_sqlite = Table(name='boundary_municp_sqlite',
  638. ... connection=sqlite3.connect(get_path(path)))
  639. >>> tab_sqlite.name
  640. 'boundary_municp_sqlite'
  641. >>> import psycopg2
  642. >>> tab_pg = Table('boundary_municp_pg',
  643. ... psycopg2.connect('host=localhost dbname=grassdb',
  644. ... 'pg'))
  645. >>> tab_pg.columns # doctest: +ELLIPSIS
  646. Columns([('cat', 'int4'), ...])
  647. ..
  648. """
  649. def _get_name(self):
  650. return self._name
  651. def _set_name(self, new_name):
  652. old_name = self._name
  653. cur = self.conn.cursor()
  654. cur.execute(sql.RENAME_TAB.format(old_name=old_name,
  655. new_name=new_name))
  656. cur.commit()
  657. cur.close()
  658. name = property(fget=_get_name, fset=_set_name)
  659. def __init__(self, name, connection, key='cat'):
  660. self._name = name
  661. self.conn = connection
  662. self.key = key
  663. self.columns = Columns(self.name,
  664. self.conn,
  665. self.key)
  666. self.filters = Filters(self.name)
  667. def __repr__(self):
  668. """::
  669. >>> import sqlite3
  670. >>> path = '$GISDBASE/$LOCATION_NAME/$MAPSET/sqlite.db'
  671. >>> tab_sqlite = Table(name='boundary_municp_sqlite',
  672. ... connection=sqlite3.connect(get_path(path)))
  673. >>> tab_sqlite
  674. Table('boundary_municp_sqlite')
  675. ..
  676. """
  677. return "Table(%r)" % (self.name)
  678. def __iter__(self):
  679. cur = self.execute()
  680. return (cur.fetchone() for _ in xrange(self.__len__()))
  681. def __len__(self):
  682. """Return the nuber of rows"""
  683. return self.n_rows()
  684. def n_rows(self):
  685. cur = self.conn.cursor()
  686. cur.execute(sql.SELECT.format(cols='Count(*)', tname=self.name))
  687. number = cur.fetchone()[0]
  688. cur.close()
  689. return number
  690. def execute(self, sql_code=None):
  691. """Execute SQL code from a given string or build with filters and
  692. return a cursor object. ::
  693. >>> import sqlite3
  694. >>> path = '$GISDBASE/$LOCATION_NAME/$MAPSET/sqlite.db'
  695. >>> tab_sqlite = Table(name='boundary_municp_sqlite',
  696. ... connection=sqlite3.connect(get_path(path)))
  697. >>> tab_sqlite.filters.select('cat', 'COUNTY').order_by('AREA')
  698. Filters('SELECT cat, COUNTY FROM boundary_municp_sqlite ORDER BY AREA;')
  699. >>> cur = tab_sqlite.execute()
  700. >>> cur.fetchone()
  701. (1, u'SURRY')
  702. ..
  703. """
  704. try:
  705. sqlc = sql_code if sql_code else self.filters.get_sql()
  706. cur = self.conn.cursor()
  707. #if hasattr(self.cur, 'executescript'):
  708. # return cur.executescript(sqlc)
  709. return cur.execute(sqlc)
  710. except:
  711. raise ValueError("The SQL is not correct:\n%r" % sqlc)
  712. def insert(self, values, many=False):
  713. cur = self.conn.cursor()
  714. if many:
  715. return cur.executemany(self.columns.insert_str, values)
  716. return cur.execute(self.columns.insert_str, values)
  717. def update(self, key, values):
  718. cur = self.conn.cursor()
  719. vals = list(values)
  720. vals.append(key)
  721. return cur.execute(self.columns.update_str, vals)