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 flask import Flask, render_template, request 

31 

32from ase.db import connect 

33from ase.db.core import Database 

34from ase.formula import Formula 

35from ase.db.web import create_key_descriptions, Session 

36from ase.db.row import row2dct, AtomsRow 

37from ase.db.table import all_columns 

38 

39 

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

41app = Flask(__name__, template_folder=str(root)) 

42 

43projects: Dict[str, Dict[str, Any]] = {} 

44 

45 

46@app.route('/', defaults={'project_name': 'default'}) 

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

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

49def search(project_name: str): 

50 """Search page. 

51 

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

53 """ 

54 if project_name == 'favicon.ico': 

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

56 session = Session(project_name) 

57 project = projects[project_name] 

58 return render_template(project['search_template'], 

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

60 p=project, 

61 session_id=session.id) 

62 

63 

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

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

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

67 

68 ``what`` must be one of: 

69 

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

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

72 * toggle: toggle column x 

73 * sort: sort after column x 

74 * page: show page x 

75 """ 

76 session = Session.get(sid) 

77 project = projects[session.project_name] 

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

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

80 project['uid_key'], 

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

82 return render_template(project['table_template'], 

83 t=table, 

84 p=project, 

85 s=session) 

86 

87 

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

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

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

91 project = projects[project_name] 

92 uid_key = project['uid_key'] 

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

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

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

96 return render_template(project['row_template'], 

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

98 

99 

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

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

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

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

104 a = row.toatoms() 

105 if type == 'cif': 

106 b = io.BytesIO() 

107 a.pbc = True 

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

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

110 

111 fd = io.StringIO() 

112 if type == 'xyz': 

113 a.write(fd, 'xyz') 

114 elif type == 'json': 

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

116 con.write(row, 

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

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

119 else: 

120 1 / 0 

121 

122 headers = [('Content-Disposition', 

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

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

125 txt = fd.getvalue() 

126 return txt, 200, headers 

127 

128 

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

130def gui(id: int): 

131 """Pop ud ase gui window.""" 

132 from ase.visualize import view 

133 atoms = projects['default']['database'].get_atoms(id) 

134 view(atoms) 

135 return '', 204, [] 

136 

137 

138@app.route('/test') 

139def test(): 

140 from pyjokes import get_joke as j 

141 return j() 

142 

143 

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

145def robots(): 

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

147 'Disallow: /\n' 

148 '\n' 

149 'User-agent: Baiduspider\n' 

150 'Disallow: /\n' 

151 '\n' 

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

153 'Disallow: /\n', 

154 200) 

155 

156 

157def handle_query(args) -> str: 

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

159 return args['query'] 

160 

161 

162def row_to_dict(row: AtomsRow, project: Dict[str, Any]) -> Dict[str, Any]: 

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

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

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

166 return dct 

167 

168 

169def add_project(db: Database) -> None: 

170 """Add database to projects with name 'default'.""" 

171 all_keys: Set[str] = set() 

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

173 all_keys.update(row._keys) 

174 

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

176 

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

178 

179 if 'key_descriptions' in meta: 

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

181 

182 default_columns = meta.get('default_columns') 

183 if default_columns is None: 

184 default_columns = all_columns[:] 

185 

186 projects['default'] = { 

187 'name': 'default', 

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

189 'uid_key': 'id', 

190 'key_descriptions': create_key_descriptions(key_descriptions), 

191 'database': db, 

192 'row_to_dict_function': row_to_dict, 

193 'handle_query_function': handle_query, 

194 'default_columns': default_columns, 

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

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

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

198 

199 

200if __name__ == '__main__': 

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

202 add_project(db) 

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