Coverage for /builds/ase/ase/ase/db/web.py : 76.19%

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"""Helper functions for Flask WSGI-app."""
2import re
3from typing import Any, Dict, List, Optional, Tuple
5from ase.db.core import Database, default_key_descriptions
6from ase.db.table import Table, all_columns
9class Session:
10 """Seesion object.
12 Stores stuff that the jinja2 templetes (like templates/table.html)
13 need to show. Example from table.html::
15 Displaying rows {{ s.row1 }}-{{ s.row2 }} out of {{ s.nrows }}
17 where *s* is the session object.
18 """
19 next_id = 1
20 sessions: Dict[int, 'Session'] = {}
22 def __init__(self, project_name: str):
23 self.id = Session.next_id
24 Session.next_id += 1
26 Session.sessions[self.id] = self
27 if len(Session.sessions) > 2000:
28 # Forget old sessions:
29 for id in sorted(Session.sessions)[:400]:
30 del Session.sessions[id]
32 self.columns: Optional[List[str]] = None
33 self.nrows: Optional[int] = None
34 self.nrows_total: Optional[int] = None
35 self.page = 0
36 self.limit = 25
37 self.sort = ''
38 self.query = ''
39 self.project_name = project_name
41 def __str__(self) -> str:
42 return str(self.__dict__)
44 @staticmethod
45 def get(id: int) -> 'Session':
46 return Session.sessions[id]
48 def update(self,
49 what: str,
50 x: str,
51 args: Dict[str, str],
52 project: Dict[str, Any]) -> None:
54 if self.columns is None:
55 self.columns = project['default_columns'][:]
57 if what == 'query':
58 self.query = project['handle_query_function'](args)
59 self.nrows = None
60 self.page = 0
62 elif what == 'sort':
63 if x == self.sort:
64 self.sort = '-' + x
65 elif '-' + x == self.sort:
66 self.sort = 'id'
67 else:
68 self.sort = x
69 self.page = 0
71 elif what == 'limit':
72 self.limit = int(x)
73 self.page = 0
75 elif what == 'page':
76 self.page = int(x)
78 elif what == 'toggle':
79 column = x
80 if column == 'reset':
81 self.columns = project['default_columns'][:]
82 else:
83 if column in self.columns:
84 self.columns.remove(column)
85 if column == self.sort.lstrip('-'):
86 self.sort = 'id'
87 self.page = 0
88 else:
89 self.columns.append(column)
91 @property
92 def row1(self) -> int:
93 return self.page * self.limit + 1
95 @property
96 def row2(self) -> int:
97 assert self.nrows is not None
98 return min((self.page + 1) * self.limit, self.nrows)
100 def paginate(self) -> List[Tuple[int, str]]:
101 """Helper function for pagination stuff."""
102 assert self.nrows is not None
103 npages = (self.nrows + self.limit - 1) // self.limit
104 p1 = min(5, npages)
105 p2 = max(self.page - 4, p1)
106 p3 = min(self.page + 5, npages)
107 p4 = max(npages - 4, p3)
108 pgs = list(range(p1))
109 if p1 < p2:
110 pgs.append(-1)
111 pgs += list(range(p2, p3))
112 if p3 < p4:
113 pgs.append(-1)
114 pgs += list(range(p4, npages))
115 pages = [(self.page - 1, 'previous')]
116 for p in pgs:
117 if p == -1:
118 pages.append((-1, '...'))
119 elif p == self.page:
120 pages.append((-1, str(p + 1)))
121 else:
122 pages.append((p, str(p + 1)))
123 nxt = min(self.page + 1, npages - 1)
124 if nxt == self.page:
125 nxt = -1
126 pages.append((nxt, 'next'))
127 return pages
129 def create_table(self,
130 db: Database,
131 uid_key: str,
132 keys: List[str]) -> Table:
133 query = self.query
135 if self.nrows_total is None:
136 self.nrows_total = db.count()
138 if self.nrows is None:
139 try:
140 self.nrows = db.count(query)
141 except (ValueError, KeyError) as e:
142 error = ', '.join(['Bad query'] + list(e.args))
143 from flask import flash
144 flash(error)
145 query = 'id=0' # this will return no rows
146 self.nrows = 0
148 table = Table(db, uid_key)
149 table.select(query, self.columns, self.sort,
150 self.limit, offset=self.page * self.limit,
151 show_empty_columns=True)
152 table.format()
153 assert self.columns is not None
154 table.addcolumns = sorted(column for column in
155 all_columns + keys
156 if column not in self.columns)
157 return table
160KeyDescriptions = Dict[str, Tuple[str, str, str]] # type-hint shortcut
163def create_key_descriptions(kd: KeyDescriptions) -> KeyDescriptions:
164 kd = kd.copy()
165 kd.update(default_key_descriptions)
167 # Fill in missing descriptions:
168 for key, (short, long, unit) in kd.items():
169 if not short:
170 kd[key] = (key, key, unit)
171 elif not long:
172 kd[key] = (short, short, unit)
174 sub = re.compile(r'`(.)_(.)`')
175 sup = re.compile(r'`(.*)\^\{?(.*?)\}?`')
177 # Convert LaTeX to HTML:
178 for key, value in kd.items():
179 short, long, unit = value
180 unit = sub.sub(r'\1<sub>\2</sub>', unit)
181 unit = sup.sub(r'\1<sup>\2</sup>', unit)
182 unit = unit.replace(r'\text{', '').replace('}', '')
183 kd[key] = (short, long, unit)
185 return kd