Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1"""WSGI Flask-app for browsing a database. 

2 

3:: 

4 

5 +---------------------+ 

6 | layout.html | 

7 | +-----------------+ | +--------------+ 

8 | | search.html | | | layout.html | 

9 | | + | | | +---------+ | 

10 | | table.html ----------->| |row.html | | 

11 | | | | | +---------+ | 

12 | +-----------------+ | +--------------+ 

13 +---------------------+ 

14 

15You can launch Flask's local webserver like this:: 

16 

17 $ ase db abc.db -w 

18 

19or this:: 

20 

21 $ python3 -m ase.db.app abc.db 

22 

23""" 

24 

25import io 

26import sys 

27from typing import Dict, Any, Set 

28from pathlib import Path 

29 

30from ase.db import connect 

31from ase.db.core import Database 

32from ase.formula import Formula 

33from ase.db.web import create_key_descriptions, Session 

34from ase.db.row import row2dct, AtomsRow 

35from ase.db.table import all_columns 

36 

37 

38def request2string(args) -> str: 

39 """Converts request args to ase.db query string.""" 

40 return args['query'] 

41 

42 

43def row_to_dict(row: AtomsRow, 

44 project: Dict[str, Any]) -> Dict[str, Any]: 

45 """Convert row to dict for use in html template.""" 

46 dct = row2dct(row, project['key_descriptions']) 

47 dct['formula'] = Formula(Formula(row.formula).format('abc')).format('html') 

48 return dct 

49 

50 

51class DBApp: 

52 root = Path(__file__).parent.parent.parent 

53 

54 def __init__(self): 

55 self.projects = {} 

56 

57 flask = new_app(self.projects) 

58 self.flask = flask 

59 

60 @flask.route('/') 

61 def frontpage(): 

62 projectname = next(iter(self.projects)) 

63 return flask.view_functions['search'](projectname) 

64 

65 def add_project(self, name: str, db: Database) -> None: 

66 all_keys: Set[str] = set() 

67 for row in db.select(columns=['key_value_pairs'], include_data=False): 

68 all_keys.update(row._keys) 

69 

70 key_descriptions = {key: (key, '', '') for key in all_keys} 

71 

72 meta: Dict[str, Any] = db.metadata 

73 

74 if 'key_descriptions' in meta: 

75 key_descriptions.update(meta['key_descriptions']) 

76 

77 default_columns = meta.get('default_columns') 

78 if default_columns is None: 

79 default_columns = all_columns[:] 

80 

81 self.projects[name] = { 

82 'name': name, 

83 'title': meta.get('title', ''), 

84 'uid_key': 'id', 

85 'key_descriptions': create_key_descriptions(key_descriptions), 

86 'database': db, 

87 'row_to_dict_function': row_to_dict, 

88 'handle_query_function': request2string, 

89 'default_columns': default_columns, 

90 'search_template': 'ase/db/templates/search.html', 

91 'row_template': 'ase/db/templates/row.html', 

92 'table_template': 'ase/db/templates/table.html'} 

93 

94 @classmethod 

95 def run_db(cls, db): 

96 app = cls() 

97 app.add_project('default', db) 

98 app.flask.run(host='0.0.0.0', debug=True) 

99 

100 

101def new_app(projects): 

102 from flask import Flask, render_template, request 

103 app = Flask(__name__, template_folder=str(DBApp.root)) 

104 

105 @app.route('/<project_name>') 

106 @app.route('/<project_name>/') 

107 def search(project_name: str): 

108 """Search page. 

109 

110 Contains input form for database query and a table result rows. 

111 """ 

112 if project_name == 'favicon.ico': 

113 return '', 204, [] # 204: "No content" 

114 session = Session(project_name) 

115 project = projects[project_name] 

116 return render_template(project['search_template'], 

117 q=request.args.get('query', ''), 

118 p=project, 

119 session_id=session.id) 

120 

121 @app.route('/update/<int:sid>/<what>/<x>/') 

122 def update(sid: int, what: str, x: str): 

123 """Update table of rows inside search page. 

124 

125 ``what`` must be one of: 

126 

127 * query: execute query in request.args (x not used) 

128 * limit: set number of rows to show to x 

129 * toggle: toggle column x 

130 * sort: sort after column x 

131 * page: show page x 

132 """ 

133 session = Session.get(sid) 

134 project = projects[session.project_name] 

135 session.update(what, x, request.args, project) 

136 table = session.create_table(project['database'], 

137 project['uid_key'], 

138 keys=list(project['key_descriptions'])) 

139 return render_template(project['table_template'], 

140 t=table, 

141 p=project, 

142 s=session) 

143 

144 @app.route('/<project_name>/row/<uid>') 

145 def row(project_name: str, uid: str): 

146 """Show details for one database row.""" 

147 project = projects[project_name] 

148 uid_key = project['uid_key'] 

149 row = project['database'].get('{uid_key}={uid}' 

150 .format(uid_key=uid_key, uid=uid)) 

151 dct = project['row_to_dict_function'](row, project) 

152 return render_template(project['row_template'], 

153 d=dct, row=row, p=project, uid=uid) 

154 

155 @app.route('/atoms/<project_name>/<int:id>/<type>') 

156 def atoms(project_name: str, id: int, type: str): 

157 """Return atomic structure as cif, xyz or json.""" 

158 row = projects[project_name]['database'].get(id=id) 

159 a = row.toatoms() 

160 if type == 'cif': 

161 b = io.BytesIO() 

162 a.pbc = True 

163 a.write(b, 'cif', wrap=False) 

164 return b.getvalue(), 200, [] 

165 

166 fd = io.StringIO() 

167 if type == 'xyz': 

168 a.write(fd, format='extxyz') 

169 elif type == 'json': 

170 con = connect(fd, type='json') 

171 con.write(row, 

172 data=row.get('data', {}), 

173 **row.get('key_value_pairs', {})) 

174 else: 

175 1 / 0 

176 

177 headers = [('Content-Disposition', 

178 'attachment; filename="{project_name}-{id}.{type}"' 

179 .format(project_name=project_name, id=id, type=type))] 

180 txt = fd.getvalue() 

181 return txt, 200, headers 

182 

183 @app.route('/gui/<int:id>') 

184 def gui(id: int): 

185 """Pop ud ase gui window.""" 

186 from ase.visualize import view 

187 # XXX so broken 

188 arbitrary_project = next(iter(projects)) 

189 atoms = projects[arbitrary_project]['database'].get_atoms(id) 

190 view(atoms) 

191 return '', 204, [] 

192 

193 @app.route('/test') 

194 def test(): 

195 return 'hello, world!' 

196 

197 @app.route('/robots.txt') 

198 def robots(): 

199 return ('User-agent: *\n' 

200 'Disallow: /\n' 

201 '\n' 

202 'User-agent: Baiduspider\n' 

203 'Disallow: /\n' 

204 '\n' 

205 'User-agent: SiteCheck-sitecrawl by Siteimprove.com\n' 

206 'Disallow: /\n', 

207 200) 

208 

209 return app 

210 

211 

212handle_query = request2string 

213 

214 

215def main(): 

216 db = connect(sys.argv[1]) 

217 DBApp.run_db(db) 

218 

219 

220if __name__ == '__main__': 

221 main()