Coverage for /builds/kinetik161/ase/ase/db/app.py: 83.72%

86 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-12-10 11:04 +0000

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 pathlib import Path 

28 

29from ase.db import connect 

30from ase.db.core import Database 

31from ase.db.project import DatabaseProject 

32from ase.db.web import Session 

33 

34 

35class DBApp: 

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

37 

38 def __init__(self): 

39 self.projects = {} 

40 

41 flask = new_app(self.projects) 

42 self.flask = flask 

43 

44 # Other projects will want to control the routing of the front 

45 # page, for which reasons we route it here in DBApp instead of 

46 # already in new_app(). 

47 @flask.route('/') 

48 def frontpage(): 

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

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

51 

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

53 self.projects[name] = DatabaseProject.load_db_as_ase_project( 

54 name=name, database=db) 

55 

56 @classmethod 

57 def run_db(cls, db): 

58 app = cls() 

59 app.add_project('default', db) 

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

61 

62 

63def new_app(projects): 

64 from flask import Flask, render_template, request 

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

66 

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

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

69 def search(project_name: str): 

70 """Search page. 

71 

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

73 """ 

74 if project_name == 'favicon.ico': 

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

76 session = Session(project_name) 

77 project = projects[project_name] 

78 return render_template(str(project.get_search_template()), 

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

80 project=project, 

81 session_id=session.id) 

82 

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

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

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

86 

87 ``what`` must be one of: 

88 

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

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

91 * toggle: toggle column x 

92 * sort: sort after column x 

93 * page: show page x 

94 """ 

95 session = Session.get(sid) 

96 project = projects[session.project_name] 

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

98 table = session.create_table(project.database, 

99 project.uid_key, 

100 keys=list(project.key_descriptions)) 

101 return render_template(str(project.get_table_template()), 

102 table=table, 

103 project=project, 

104 session=session) 

105 

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

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

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

109 project = projects[project_name] 

110 row = project.uid_to_row(uid) 

111 dct = project.row_to_dict(row) 

112 return render_template(str(project.get_row_template()), 

113 dct=dct, row=row, project=project, uid=uid) 

114 

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

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

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

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

119 a = row.toatoms() 

120 if type == 'cif': 

121 b = io.BytesIO() 

122 a.pbc = True 

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

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

125 

126 fd = io.StringIO() 

127 if type == 'xyz': 

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

129 elif type == 'json': 

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

131 con.write(row, 

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

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

134 else: 

135 1 / 0 

136 

137 headers = [('Content-Disposition', 

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

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

140 txt = fd.getvalue() 

141 return txt, 200, headers 

142 

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

144 def gui(id: int): 

145 """Pop ud ase gui window.""" 

146 from ase.visualize import view 

147 

148 # XXX so broken 

149 arbitrary_project = next(iter(projects)) 

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

151 view(atoms) 

152 return '', 204, [] 

153 

154 @app.route('/test') 

155 def test(): 

156 return 'hello, world!' 

157 

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

159 def robots(): 

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

161 'Disallow: /\n' 

162 '\n' 

163 'User-agent: Baiduspider\n' 

164 'Disallow: /\n' 

165 '\n' 

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

167 'Disallow: /\n', 

168 200) 

169 

170 return app 

171 

172 

173def main(): 

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

175 DBApp.run_db(db) 

176 

177 

178if __name__ == '__main__': 

179 main()