Coverage for /builds/ase/ase/ase/db/app.py: 83.72%
86 statements
« prev ^ index » next coverage.py v7.5.3, created at 2025-08-02 00:12 +0000
« prev ^ index » next coverage.py v7.5.3, created at 2025-08-02 00:12 +0000
1# fmt: off
3"""WSGI Flask-app for browsing a database.
5::
7 +---------------------+
8 | layout.html |
9 | +-----------------+ | +--------------+
10 | | search.html | | | layout.html |
11 | | + | | | +---------+ |
12 | | table.html ----------->| |row.html | |
13 | | | | | +---------+ |
14 | +-----------------+ | +--------------+
15 +---------------------+
17You can launch Flask's local webserver like this::
19 $ ase db abc.db -w
21or this::
23 $ python3 -m ase.db.app abc.db
25"""
27import io
28import sys
29from pathlib import Path
31from ase.db import connect
32from ase.db.core import Database
33from ase.db.project import DatabaseProject
34from ase.db.web import Session
37class DBApp:
38 root = Path(__file__).parent.parent.parent
40 def __init__(self):
41 self.projects = {}
43 flask = new_app(self.projects)
44 self.flask = flask
46 # Other projects will want to control the routing of the front
47 # page, for which reasons we route it here in DBApp instead of
48 # already in new_app().
49 @flask.route('/')
50 def frontpage():
51 projectname = next(iter(self.projects))
52 return flask.view_functions['search'](projectname)
54 def add_project(self, name: str, db: Database) -> None:
55 self.projects[name] = DatabaseProject.load_db_as_ase_project(
56 name=name, database=db)
58 @classmethod
59 def run_db(cls, db):
60 app = cls()
61 app.add_project('default', db)
62 app.flask.run(host='0.0.0.0', debug=True)
65def new_app(projects):
66 from flask import Flask, render_template, request
67 app = Flask(__name__, template_folder=str(DBApp.root))
69 @app.route('/<project_name>')
70 @app.route('/<project_name>/')
71 def search(project_name: str):
72 """Search page.
74 Contains input form for database query and a table result rows.
75 """
76 if project_name == 'favicon.ico':
77 return '', 204, [] # 204: "No content"
78 session = Session(project_name)
79 project = projects[project_name]
80 return render_template(str(project.get_search_template()),
81 q=request.args.get('query', ''),
82 project=project,
83 session_id=session.id)
85 @app.route('/update/<int:sid>/<what>/<x>/')
86 def update(sid: int, what: str, x: str):
87 """Update table of rows inside search page.
89 ``what`` must be one of:
91 * query: execute query in request.args (x not used)
92 * limit: set number of rows to show to x
93 * toggle: toggle column x
94 * sort: sort after column x
95 * page: show page x
96 """
97 session = Session.get(sid)
98 project = projects[session.project_name]
99 session.update(what, x, request.args, project)
100 table = session.create_table(project.database,
101 project.uid_key,
102 keys=list(project.key_descriptions))
103 return render_template(str(project.get_table_template()),
104 table=table,
105 project=project,
106 session=session)
108 @app.route('/<project_name>/row/<uid>')
109 def row(project_name: str, uid: str):
110 """Show details for one database row."""
111 project = projects[project_name]
112 row = project.uid_to_row(uid)
113 dct = project.row_to_dict(row)
114 return render_template(str(project.get_row_template()),
115 dct=dct, row=row, project=project, uid=uid)
117 @app.route('/atoms/<project_name>/<int:id>/<type>')
118 def atoms(project_name: str, id: int, type: str):
119 """Return atomic structure as cif, xyz or json."""
120 row = projects[project_name].database.get(id=id)
121 a = row.toatoms()
122 if type == 'cif':
123 b = io.BytesIO()
124 a.pbc = True
125 a.write(b, 'cif', wrap=False)
126 return b.getvalue(), 200, []
128 fd = io.StringIO()
129 if type == 'xyz':
130 a.write(fd, format='extxyz')
131 elif type == 'json':
132 con = connect(fd, type='json')
133 con.write(row,
134 data=row.get('data', {}),
135 **row.get('key_value_pairs', {}))
136 else:
137 1 / 0
139 headers = [('Content-Disposition',
140 'attachment; filename="{project_name}-{id}.{type}"'
141 .format(project_name=project_name, id=id, type=type))]
142 txt = fd.getvalue()
143 return txt, 200, headers
145 @app.route('/gui/<int:id>')
146 def gui(id: int):
147 """Pop ud ase gui window."""
148 from ase.visualize import view
150 # XXX so broken
151 arbitrary_project = next(iter(projects))
152 atoms = projects[arbitrary_project].database.get_atoms(id)
153 view(atoms)
154 return '', 204, []
156 @app.route('/test')
157 def test():
158 return 'hello, world!'
160 @app.route('/robots.txt')
161 def robots():
162 return ('User-agent: *\n'
163 'Disallow: /\n'
164 '\n'
165 'User-agent: Baiduspider\n'
166 'Disallow: /\n'
167 '\n'
168 'User-agent: SiteCheck-sitecrawl by Siteimprove.com\n'
169 'Disallow: /\n',
170 200)
172 return app
175def main():
176 db = connect(sys.argv[1])
177 DBApp.run_db(db)
180if __name__ == '__main__':
181 main()