1 """Contains functions that maintain/update the cache tables."""
2 import mwparserfromhell
3 from sqlalchemy import schema, Table
4 from sqlalchemy.engine import Connection
5 from sqlalchemy.sql import select
6 from sqlalchemy.sql.expression import func as sqlfunc
8 from wrpylib import mwdb, wrmwdb, wrmwmarkup, wrvalidators
11 class UpdateCacheError(RuntimeError):
15 def _get_mw_text(connection: Connection, text: Table, content_address: str) -> str:
16 parts = content_address.split(':') # e.g. 'tt:15664'
17 if len(parts) != 2 or parts[0] != 'tt':
18 raise ValueError('Content has unexpected format')
19 old_id = int(parts[1])
20 query = select([text], text.c.old_id == old_id)
21 text_row = connection.execute(query).fetchone()
22 return text_row.old_text
25 def update_wrsledruncache(connection):
26 """Updates the wrsledruncache table from the wiki. If convert errors occur, an UpdateCacheError exception
27 is raised. No other exception type should be raised under normal circumstances.
29 >>> from sqlalchemy.engine import create_engine
30 >>> engine = create_engine('mysql://philipp@localhost:3306/philipp_winterrodeln_wiki?charset=utf8mb4')
31 >>> update_wrsledruncache(engine.connect())
33 metadata = schema.MetaData()
34 wrsledruncache = wrmwdb.wrsledruncache_table(metadata)
35 page = mwdb.page_table(metadata)
36 categorylinks = mwdb.categorylinks_table(metadata)
37 slots = mwdb.slots_table(metadata)
38 content = mwdb.content_table(metadata)
39 text = mwdb.text_table(metadata)
45 with connection.begin():
49 [page, categorylinks, slots, content],
50 (page.c.page_latest == slots.c.slot_revision_id) & (slots.c.slot_content_id == content.c.content_id) &
51 (categorylinks.c.cl_from == page.c.page_id) & (categorylinks.c.cl_to == 'Rodelbahn'))
52 sledrun_pages = connection.execute(q)
54 # Delete all existing entries in wrsledruncache
55 # We rely on transactions MySQL InnoDB
56 connection.execute(wrsledruncache.delete())
58 # Refill wrsledruncache table
59 for sledrun_page in sledrun_pages:
60 old_text = _get_mw_text(connection, text, sledrun_page.content_address)
61 rodelbahnbox = wrvalidators.rodelbahnbox_from_str(old_text)
62 sledrun = wrmwmarkup.sledrun_from_rodelbahnbox(rodelbahnbox, Sledrun())
63 sledrun.page_id = sledrun_page.page_id
64 sledrun.page_title = sledrun_page.page_title
65 sledrun.name_url = wrvalidators.sledrun_page_title_to_pretty_url(sledrun_page.page_title)
66 sledrun.under_construction = connection.execute(select(
68 (categorylinks.c.cl_from == sledrun_page.page_id) &
69 (categorylinks.c.cl_to == 'In_Arbeit')).alias('x')).fetchone()[0] > 0
70 connection.execute(wrsledruncache.insert(sledrun.__dict__))
72 except ValueError as e:
73 error_msg = f"Error at sled run '{sledrun_page.page_title}': {e}"
74 raise UpdateCacheError(error_msg, sledrun_page.page_title, e)
77 def update_wrinncache(connection):
78 """Updates the wrinncache table from the wiki. If convert errors occur, an UpdateCacheError exception
79 is raised. No other exception type should be raised under normal circumstances.
81 >>> from sqlalchemy.engine import create_engine
82 >>> engine = create_engine('mysql://philipp@localhost:3306/philipp_winterrodeln_wiki?charset=utf8mb4')
83 >>> update_wrinncache(engine.connect())
85 metadata = schema.MetaData()
86 wrinncache = wrmwdb.wrinncache_table(metadata)
87 page = mwdb.page_table(metadata)
88 categorylinks = mwdb.categorylinks_table(metadata)
89 slots = mwdb.slots_table(metadata)
90 content = mwdb.content_table(metadata)
91 text = mwdb.text_table(metadata)
97 with connection.begin():
101 [page, categorylinks, slots, content],
102 (page.c.page_latest == slots.c.slot_revision_id) & (slots.c.slot_content_id == content.c.content_id) &
103 (categorylinks.c.cl_from == page.c.page_id) & (categorylinks.c.cl_to == 'Gasthaus'))
104 inn_pages = connection.execute(q)
106 # Delete all existing entries in wrinncache
107 # We rely on transactions MySQL InnoDB
108 connection.execute(wrinncache.delete())
110 # Refill wrinncache table
111 for inn_page in inn_pages:
112 old_text = _get_mw_text(connection, text, inn_page.content_address)
113 gasthausbox = wrvalidators.gasthausbox_from_str(old_text)
114 inn = wrmwmarkup.inn_from_gasthausbox(gasthausbox, Inn())
115 inn.page_id = inn_page.page_id
116 inn.page_title = inn_page.page_title
117 inn.under_construction = connection.execute(select(
119 (categorylinks.c.cl_from == inn_page.page_id) &
120 (categorylinks.c.cl_to == 'In_Arbeit')).alias('x')) \
121 .fetchone()[0] > 0 # it would be better to do this in the query above
122 connection.execute(wrinncache.insert(inn.__dict__))
124 except ValueError as e:
125 error_msg = f"Error as inn '{inn_page.page_title}': {e}"
126 raise UpdateCacheError(error_msg, inn_page.page_title, e)
129 def update_wrreportcache(connection, page_id=None):
130 """Updates the wrreportcache table.
131 :param connection: sqlalchemy connection
132 :param page_id: Updates only the reportcache table for the sledrun described on the Winterrodeln wiki page
133 with the specified page_id. Use None for this parameter to update the whole table.
135 >>> from sqlalchemy.engine import create_engine
136 >>> engine = create_engine('mysql://philipp@localhost:3306/philipp_winterrodeln_wiki?charset=utf8mb4')
137 >>> update_wrreportcache(engine.connect())
139 metadata = schema.MetaData()
140 wrreportcache = wrmwdb.wrreportcache_table(metadata)
141 with connection.begin():
142 # Delete the datasets we are going to update
143 sql_del = wrreportcache.delete()
144 if page_id is not None:
145 sql_del = sql_del.where(wrreportcache.c.page_id == page_id)
146 connection.execute(sql_del)
148 def insert_row(connection_, row_list_):
149 if len(row_list_) == 0:
152 row_ = dict(row_list_[0])
153 connection_.execute(wrreportcache.insert(values=row_))
155 # Select the rows to update
156 sql = 'select page_id, page_title, wrreport.id as report_id, date_report, `condition`, description, ' \
158 'if(author_userid is null, null, author_username) as author_username from wrreport ' \
159 'where {0}`condition` is not null and date_invalid > now() and delete_date is null ' \
160 'order by page_id, date_report desc, date_entry desc' \
161 .format('' if page_id is None else f'page_id={page_id} and ')
162 cursor = connection.execute(sql)
166 if row.page_id != page_id:
167 insert_row(connection, row_list)
168 page_id = row.page_id
171 insert_row(connection, row_list)
174 def update_wrmapcache(connection):
175 """Updates the wrmappointcache and wrmappathcache tables from the wiki. If convert errors occur,
176 an UpdateCacheError exception is raised. No other exception type should be raised under normal circumstances.
178 >>> from sqlalchemy.engine import create_engine
179 >>> engine = create_engine('mysql://philipp@localhost:3306/philipp_winterrodeln_wiki?charset=utf8mb4')
181 >>> # engine = create_engine('mysql://philipp@localhost:3306/philipp_winterrodeln_wiki?charset=utf8mb4&passwd=XXX')
182 >>> update_wrmapcache(engine.connect())
184 metadata = schema.MetaData()
185 page = mwdb.page_table(metadata)
186 categorylinks = mwdb.categorylinks_table(metadata)
187 slots = mwdb.slots_table(metadata)
188 content = mwdb.content_table(metadata)
189 text = mwdb.text_table(metadata)
192 with connection.begin():
196 [page, categorylinks, slots, content],
197 (page.c.page_latest == slots.c.slot_revision_id) & (slots.c.slot_content_id == content.c.content_id) &
198 (categorylinks.c.cl_from == page.c.page_id) & (categorylinks.c.cl_to == 'Rodelbahn'))
199 sledrun_pages = connection.execute(q)
201 # Delete all existing entries in wrmappointcache
202 # We rely on transactions MySQL InnoDB
203 connection.execute('delete from wrmappointcache')
204 connection.execute('delete from wrmappathcache')
206 # Refill wrmappointcache and wrmappathcache tables
207 for sledrun_page in sledrun_pages:
208 old_text = _get_mw_text(connection, text, sledrun_page.content_address)
209 wikicode = mwparserfromhell.parse(old_text)
210 wrmap_list = wikicode.filter_tags(recursive=False, matches=lambda tag: tag.tag == 'wrmap')
211 if len(wrmap_list) == 0:
212 continue # not wrmap in page
213 if len(wrmap_list) > 1:
214 raise UpdateCacheError(
215 f'{len(wrmap_list)} <wrmap ...> entries found in article "{sledrun_page.page_title}"')
216 wrmap = wrmap_list[0]
217 geojson = wrmwmarkup.parse_wrmap(str(wrmap))
219 for feature in geojson['features']:
220 properties = feature['properties']
221 coordinates = feature['geometry']['coordinates']
224 if properties['type'] in wrmwmarkup.WRMAP_POINT_TYPES:
225 lon, lat = coordinates
226 label = properties.get('name')
229 'haltestelle': 'busstop',
230 'parkplatz': 'carpark',
231 'achtung': 'warning',
236 point_type = point_types[properties['type']]
237 sql = 'insert into wrmappointcache (page_id, type, point, label) values (%s, %s, POINT(%s, %s), %s)'
238 connection.execute(sql, (sledrun_page.page_id, point_type, lon, lat, label))
241 elif properties['type'] in wrmwmarkup.WRMAP_LINE_TYPES:
243 'rodelbahn': 'sledrun',
245 'alternative': 'alternative',
247 'anfahrt': 'recommendedcarroute',
249 path_type = path_types[properties['type']]
250 path = ", ".join([f"{lon} {lat}" for lon, lat in coordinates])
251 path = f'LineString({path})'
252 if path_type == 'recommendedcarroute':
254 sql = 'insert into wrmappathcache (path, page_id, type) values (GeomFromText(%s), %s, %s)'
255 connection.execute(sql, (path, sledrun_page.page_id, path_type))
258 raise RuntimeError(f'Unknown feature type {properties["type"]}')
259 except RuntimeError as e:
260 error_msg = f"Error at sledrun '{sledrun_page.page_title}': {e}"
261 raise UpdateCacheError(error_msg, sledrun_page.page_title, e)
264 def update_wrregioncache(connection):
265 """Updates the wrregioncache table from the wiki.
266 It relays on the table wrsledruncache to be up-to-date.
267 No exceptions should be raised under normal circumstances.
269 >>> from sqlalchemy.engine import create_engine
270 >>> engine = create_engine('mysql://philipp@localhost:3306/philipp_winterrodeln_wiki?charset=utf8mb4')
272 >>> # engine = create_engine('mysql://philipp@localhost:3306/philipp_winterrodeln_wiki?charset=utf8mb4&passwd=XXX')
273 >>> update_wrregioncache(engine.connect())
275 metadata = schema.MetaData()
276 wrregion = wrmwdb.wrregion_table(metadata)
277 wrsledruncache = wrmwdb.wrsledruncache_table(metadata)
278 wrregioncache = wrmwdb.wrregioncache_table(metadata)
280 with connection.begin():
282 # Delete all existing entries in wrregioncache
283 # We rely on transactions MySQL InnoDB
284 connection.execute(wrregioncache.delete())
286 # Query all combinations of sledruns and regions
289 wrregion.c.id.label('region_id'),
290 sqlfunc.AsWKB(wrregion.c.border).label('border'),
291 wrsledruncache.c.page_id,
292 wrsledruncache.c.position_longitude,
293 wrsledruncache.c.position_latitude
297 sqlfunc.point(wrsledruncache.c.position_longitude, wrsledruncache.c.position_latitude)
300 ins = wrregioncache.insert()
302 # Refill wrregioncache
303 point = ogr.Geometry(ogr.wkbPoint)
304 result = connection.execute(sel)
306 point.SetPoint(0, row.position_longitude, row.position_latitude)
307 if point.Within(ogr.CreateGeometryFromWkb(row.border)):
308 connection.execute(ins.values(region_id=row.region_id, page_id=row.page_id))