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
« prev ^ index » next coverage.py v7.2.7, created at 2023-12-10 11:04 +0000
1"""WSGI Flask-app for browsing a database.
3::
5 +---------------------+
6 | layout.html |
7 | +-----------------+ | +--------------+
8 | | search.html | | | layout.html |
9 | | + | | | +---------+ |
10 | | table.html ----------->| |row.html | |
11 | | | | | +---------+ |
12 | +-----------------+ | +--------------+
13 +---------------------+
15You can launch Flask's local webserver like this::
17 $ ase db abc.db -w
19or this::
21 $ python3 -m ase.db.app abc.db
23"""
25import io
26import sys
27from pathlib import Path
29from ase.db import connect
30from ase.db.core import Database
31from ase.db.project import DatabaseProject
32from ase.db.web import Session
35class DBApp:
36 root = Path(__file__).parent.parent.parent
38 def __init__(self):
39 self.projects = {}
41 flask = new_app(self.projects)
42 self.flask = flask
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)
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)
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)
63def new_app(projects):
64 from flask import Flask, render_template, request
65 app = Flask(__name__, template_folder=str(DBApp.root))
67 @app.route('/<project_name>')
68 @app.route('/<project_name>/')
69 def search(project_name: str):
70 """Search page.
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)
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.
87 ``what`` must be one of:
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)
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)
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, []
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
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
143 @app.route('/gui/<int:id>')
144 def gui(id: int):
145 """Pop ud ase gui window."""
146 from ase.visualize import view
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, []
154 @app.route('/test')
155 def test():
156 return 'hello, world!'
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)
170 return app
173def main():
174 db = connect(sys.argv[1])
175 DBApp.run_db(db)
178if __name__ == '__main__':
179 main()