The order of XML attributes is now retained. master
authorPhilipp Spitzer <philipp@spitzer.priv.at>
Wed, 29 Sep 2021 21:15:39 +0000 (23:15 +0200)
committerPhilipp Spitzer <philipp@spitzer.priv.at>
Wed, 29 Sep 2021 21:15:39 +0000 (23:15 +0200)
14 files changed:
tests/test_mwdb.py
tests/test_mwmarkup.py
tests/test_wrdem.py
tests/test_wrmwcache.py
tests/test_wrmwdb.py
tests/test_wrmwmarkup.py
tests/test_wrvalidators.py
wrpylib/mwdb.py
wrpylib/mwmarkup.py
wrpylib/wrdem.py
wrpylib/wrintermaps.py
wrpylib/wrmwcache.py
wrpylib/wrmwdb.py
wrpylib/wrmwmarkup.py

index 92a2b3e53133af2c83f9c3d52a8560c5b9384ded..640566b3c503f596d3ba46a0a0edd11153231380 100644 (file)
@@ -1,4 +1,3 @@
-#!/usr/bin/python3.4
 import unittest
 import MySQLdb
 import sqlalchemy
@@ -22,7 +21,7 @@ class TestMwDb(unittest.TestCase):
 
     def test_page_table(self):
         Page = wrpylib.mwdb.page_table(self.metadata)
-        page = self.session.query(Page).filter(Page.c.page_id==1321).first()
+        page = self.session.query(Page).filter(Page.c.page_id == 1321).first()
         self.assertEqual(page.page_id, 1321)
         self.assertEqual(type(page.page_title), str)
         self.assertEqual(type(page.page_restrictions), bytes)
@@ -32,8 +31,8 @@ class TestMwDb(unittest.TestCase):
         Revision = wrpylib.mwdb.revision_table(self.metadata)
         revision = self.session.query(Revision).filter(Revision.c.rev_id == 666).first()
         self.assertEqual(revision.rev_id, 666)
-        self.assertEqual(type(revision.rev_comment), str)
-        self.assertEqual(type(revision.rev_user_text), str)
+        self.assertEqual(type(revision.rev_comment_id), int)
+        self.assertEqual(type(revision.rev_actor), int)
         self.assertEqual(type(revision.rev_timestamp), str)
 
     def test_text_table(self):
@@ -44,7 +43,6 @@ class TestMwDb(unittest.TestCase):
         self.assertEqual(type(text.old_flags), str)
         self.assertEqual(text.old_flags, 'utf-8')
 
-
     def test_user_table(self):
         User = wrpylib.mwdb.user_table(self.metadata)
         user = self.session.query(User).filter(User.c.user_id == 1).first()
@@ -54,7 +52,6 @@ class TestMwDb(unittest.TestCase):
         self.assertEqual(type(user.user_email), str)
         self.assertEqual(user.user_name, 'Philipp')
 
-
     def test_categorylinks_table(self):
         Categorylinks = wrpylib.mwdb.categorylinks_table(self.metadata)
         categorylinks = self.session.query(Categorylinks).filter(Categorylinks.c.cl_from == 609).first()
@@ -69,6 +66,7 @@ class TestMySqlPython(unittest.TestCase):
     because byte strings are returned instead of unicode for columns having
     a _bin collation, see https://sourceforge.net/p/mysql-python/bugs/289/
     This has been fixed in MySQL_python version 1.2.4."""
+
     @classmethod
     def setUpClass(cls):
         cls.db = MySQLdb.connect(db='philipp_winterrodeln_wiki', charset='utf8mb4')
@@ -88,9 +86,9 @@ class TestMySqlPython(unittest.TestCase):
         self.assertEqual(type(result[2]), bytes)  # binary(14) NOT NULL
 
     def test_datatype_revision(self):
-        result = self.exec_sql('select rev_comment, rev_user_text, rev_timestamp from revision where rev_id = 7586')
-        self.assertEqual(type(result[0]), bytes)  # tinyblob NOT NULL
-        self.assertEqual(type(result[1]), bytes)  # varbinary(255) NOT NULL DEFAULT ''
+        result = self.exec_sql('select rev_comment_id, rev_actor, rev_timestamp from revision where rev_id = 7586')
+        self.assertEqual(type(result[0]), int)  # tinyblob NOT NULL
+        self.assertEqual(type(result[1]), int)  # varbinary(255) NOT NULL DEFAULT ''
         self.assertEqual(type(result[2]), bytes)  # binary(14) NOT NULL
 
     def test_datatypes_text(self):
index 41225f13a1d2bc733712d7e402f20e13e90ea2f1..14e0e17a65d8db76fa25d032fa1e4eabecd7cde2 100644 (file)
@@ -1,4 +1,3 @@
-#!/usr/bin/python3.4
 import unittest
 import mwparserfromhell
 import wrpylib.mwmarkup
@@ -39,8 +38,8 @@ class TestMwParserFromHell(unittest.TestCase):
         rb = list(wikicode.filter_templates())[0]
         self.assertEqual(rb.name.strip(), 'Rodelbahnbox')
         self.assertEqual(rb.get('Aufstiegshilfe').value.strip(), 'Nein')
-        self.assertEqual(rb[:2], '{{')
-        self.assertEqual(rb[-2:], '}}')
+        self.assertEqual(str(rb)[:2], '{{')
+        self.assertEqual(str(rb)[-2:], '}}')
 
     def test_template_to_table(self):
         wikitext = '{{Rodelbahnbox | Unbenannt | Position = 47.309820 N 9.986508 E | Aufstieg möglich = Ja }}'
index 8304ea235953976f064b8edcdd0e75bf38e40c82..fb6f0f69fa3dc70c2279e1feb6e562d07ec02388 100644 (file)
@@ -1,7 +1,7 @@
 import unittest
 
 import owslib.wms
-import rasterio
+import rasterio  # pip install rasterio
 
 from wrpylib.wrdem import get_ele_from_raster, get_ele_from_wms, MultiDem, transform_lon_lat, DemSwitzerland, \
     DemBasemap, DemBavaria
index e4ea4b3e0c716fc3a8931ce76f44eb65cc789ef0..99bc00ab05135f0b7f48f3bce871a6e9c2668aa8 100644 (file)
@@ -1,4 +1,3 @@
-#!/usr/bin/python3.4
 from sqlalchemy.engine import create_engine
 from wrpylib import wrmwcache
 import unittest
index 242db2b77a4b0725b96ee946279be7e26902860e..c57ef89c6715ce9ddedab5ca5f0d3160bb80a345 100644 (file)
@@ -1,4 +1,3 @@
-#!/usr/bin/python3.4
 import os
 import unittest
 import sqlalchemy
index 2be3f6e796db5aa6c18dd3b2fa4fe1086d9cdc9c..b9ef0fbad0982c3ee44fc88dbab6454abc4c54d0 100644 (file)
@@ -1,4 +1,3 @@
-#!/usr/bin/python3.4
 import collections
 import textwrap
 import unittest
@@ -16,7 +15,7 @@ class TestSledrun(unittest.TestCase):
             pass
         rodelbahnbox = collections.OrderedDict([
             ('Position', LonLat(9.986508, 47.30982)),
-            ('Position oben', LonLat(None, None)),
+            ('Position oben', None),
             ('Höhe oben', 1244),
             ('Position unten', LonLat(8.506047, 46.20210)),
             ('Höhe unten', None),
@@ -210,7 +209,7 @@ class TestInn(unittest.TestCase):
         class Inn:
             pass
         gasthausbox = collections.OrderedDict()
-        gasthausbox['Position'] = LonLat(None, None)
+        gasthausbox['Position'] = None
         gasthausbox['Höhe'] = None
         gasthausbox['Betreiber'] = None
         gasthausbox['Sitzplätze'] = None
@@ -265,7 +264,7 @@ class TestInn(unittest.TestCase):
         inn.image = None
         inn.sledding_list = 'Nein'
         gasthausbox = inn_to_gasthausbox(inn)
-        self.assertEqual(gasthausbox['Position'], LonLat(None, None))
+        self.assertEqual(gasthausbox['Position'], None)
         self.assertEqual(gasthausbox['Höhe'], None)
         self.assertEqual(gasthausbox['Betreiber'], None)
         self.assertEqual(gasthausbox['Sitzplätze'], None)
@@ -283,7 +282,7 @@ class TestInn(unittest.TestCase):
         class Inn:
             pass
         gasthausbox = collections.OrderedDict()
-        gasthausbox['Position'] = LonLat(None, None)
+        gasthausbox['Position'] = None
         gasthausbox['Höhe'] = None
         gasthausbox['Betreiber'] = None
         gasthausbox['Sitzplätze'] = None
@@ -338,7 +337,7 @@ class TestInn(unittest.TestCase):
         inn.image = None
         inn.sledding_list = None
         gasthausbox = inn_to_gasthausbox(inn)
-        self.assertEqual(gasthausbox['Position'], LonLat(None, None))
+        self.assertEqual(gasthausbox['Position'], None)
         self.assertEqual(gasthausbox['Höhe'], None)
         self.assertEqual(gasthausbox['Betreiber'], None)
         self.assertEqual(gasthausbox['Sitzplätze'], None)
@@ -442,7 +441,7 @@ class TestWrMap(unittest.TestCase):
 
         wikitext = wrpylib.wrmwmarkup.create_wrmap(geojson)
         self.assertEqual(wikitext, textwrap.dedent('''\
-        <wrmap height="400" lat="47.241713" lon="11.214089" width="700" zoom="14">
+        <wrmap lon="11.214089" lat="47.241713" zoom="14" width="700" height="400">
 
         <gasthaus name="Rosskogelhütte" wiki="Rosskogelhütte">47.240689 N 11.190454 E</gasthaus>
         <parkplatz>47.245789 N 11.238971 E</parkplatz>
index afe28dd7ab476507bdcd11b09909ba35396cc5c0..5b62a633077c0b3d0c4466b5a95e05db29e82d09 100644 (file)
@@ -1,7 +1,7 @@
-#!/usr/bin/python3.4
 import unittest
 from wrpylib.wrvalidators import *
 
+
 # optional converter
 # ------------------
 
@@ -814,7 +814,7 @@ class TestRodelbahnbox(unittest.TestCase):
         self.assertEqual(LonLat(12.806522, 46.807218), value['Position'])
         self.assertEqual(LonLat(12.818658, 46.799014), value['Position oben'])
         self.assertEqual(1046, value['Höhe oben'])
-        self.assertEqual(LonLat(None, None), value['Position unten'])
+        self.assertEqual(None, value['Position unten'])
         self.assertEqual(None, value['Höhe unten'])
         self.assertEqual(3500, value['Länge'])
         self.assertEqual(2, value['Schwierigkeit'])
@@ -840,7 +840,7 @@ class TestRodelbahnbox(unittest.TestCase):
             ('Position', LonLat(12.806522, 46.807218)),
             ('Position oben', LonLat(12.818658, 46.799014)),
             ('Höhe oben', 1046),
-            ('Position unten', LonLat(None, None)),
+            ('Position unten', None),
             ('Höhe unten', None),
             ('Länge', 3500),
             ('Schwierigkeit', 2),
index d522d8685246f9f8c3d1b8dd004155d6d375214e..5d560beba3d68ead296392abf4df533de9004957 100644 (file)
@@ -1,10 +1,10 @@
 """This module contains code to make the access of MediaWiki tables
 easy. The module uses sqlalchemy to access the database.
 """
-from sqlalchemy import Table, Column, types
+from sqlalchemy import Table, Column, types, MetaData
 
 
-def page_table(metadata):
+def page_table(metadata: MetaData) -> Table:
     """Returns the sqlalchemy Table representing the "page" table in MediaWiki.
     :param metadata: metadata = sqlalchemy.MetaData()
     """
@@ -18,15 +18,15 @@ def page_table(metadata):
         Column("page_is_new", types.Integer, nullable=False),
         Column("page_random", types.Float, nullable=False),
         Column("page_touched", types.String(14, convert_unicode='force'), nullable=False),
+        Column("page_links_updated", types.String(14, convert_unicode='force')),
         Column("page_latest", types.Integer, nullable=False),
         Column("page_len", types.Integer, nullable=False),
         Column("page_content_model", types.String(32, convert_unicode='force')),
-        Column("page_links_updated", types.String(14, convert_unicode='force')),
         Column("page_lang", types.String(35, convert_unicode='force')),
     )
 
 
-def revision_table(metadata):
+def revision_table(metadata: MetaData) -> Table:
     """Returns the sqlalchemy Table representing the "revision" table in MediaWiki.
     :param metadata: metadata = sqlalchemy.MetaData()
     """
@@ -34,19 +34,45 @@ def revision_table(metadata):
         "revision", metadata,
         Column("rev_id", types.Integer, nullable=False, primary_key=True),
         Column("rev_page", types.Integer, nullable=False, primary_key=True),
-        Column("rev_text_id", types.Integer, nullable=False),
-        Column("rev_comment", types.String(convert_unicode='force'), nullable=False),  # tinyblob NOT NULL
-        Column("rev_user", types.Integer, nullable=False),
-        Column("rev_user_text", types.String(255, convert_unicode='force'), nullable=False),
+        Column("rev_comment_id", types.Integer, nullable=False),
+        Column("rev_actor", types.Integer, nullable=False),
         Column("rev_timestamp", types.String(14, convert_unicode='force'), nullable=False),
         Column("rev_minor_edit", types.Integer, nullable=False),
         Column("rev_deleted", types.Integer, nullable=False),
-        Column("rev_len", types.Integer, nullable=False),
-        Column("rev_parent_id", types.Integer, nullable=False),
+        Column("rev_len", types.Integer, nullable=True),
+        Column("rev_parent_id", types.Integer, nullable=True),
+        Column("rev_sha1", types.String(32), nullable=False),
+    )
+
+
+def slots_table(metadata: MetaData) -> Table:
+    """Returns the sqlalchemy Table representing the "slots" table in MediaWiki.
+    :param metadata: metadata = sqlalchemy.MetaData()
+    """
+    return Table(
+        "slots", metadata,
+        Column("slot_revision_id", types.Integer, nullable=False, primary_key=True),
+        Column("slot_role_id", types.Integer, nullable=False, primary_key=True),
+        Column("slot_content_id", types.Integer, nullable=False),
+        Column("slot_origin", types.Integer, nullable=False),
+    )
+
+
+def content_table(metadata: MetaData) -> Table:
+    """Returns the sqlalchemy Table representing the "content" table in MediaWiki.
+    :param metadata: metadata = sqlalchemy.MetaData()
+    """
+    return Table(
+        "content", metadata,
+        Column("content_id", types.Integer, nullable=False, primary_key=True),
+        Column("content_size", types.Integer, nullable=False),
+        Column("content_sha1", types.String(32, convert_unicode='force'), nullable=False),
+        Column("content_model", types.Integer, nullable=False),
+        Column("content_address", types.String(255, convert_unicode='force'), nullable=False),
     )
 
 
-def text_table(metadata):
+def text_table(metadata: MetaData) -> Table:
     """Returns the sqlalchemy Table representing the "text" table in MediaWiki.
     :param metadata: metadata = sqlalchemy.MetaData()
     """
@@ -58,7 +84,7 @@ def text_table(metadata):
     )
 
 
-def user_table(metadata):
+def user_table(metadata: MetaData) -> Table:
     """Returns the sqlalchemy Table representing the "user" table in MediaWiki.
     :param metadata: metadata = sqlalchemy.MetaData()
     """
@@ -81,7 +107,7 @@ def user_table(metadata):
     )
 
 
-def user_groups_table(metadata):
+def user_groups_table(metadata: MetaData) -> Table:
     """
     Returns the sqlalchemy Table representing the "user_groups" table in MediaWiki.
 
@@ -95,7 +121,7 @@ def user_groups_table(metadata):
     )
 
 
-def categorylinks_table(metadata):
+def categorylinks_table(metadata: MetaData) -> Table:
     """Returns the sqlalchemy Table representing the "categorylinks" table in MediaWiki.
     :param metadata: metadata = sqlalchemy.MetaData()
     """
index a5d83ba5714c36f90f961dfb3198e4a76eec86e5..c14e96256e924ed5855a3a38a47ce40241207e92 100644 (file)
@@ -6,11 +6,27 @@ Other Python MediaWiki parsers:
 * mwlib http://code.pediapress.com/wiki/wiki
 * https://www.mediawiki.org/wiki/Alternative_parsers
 """
-from typing import Optional
+from typing import Optional, Dict, List
 
 from mwparserfromhell.nodes import Template
 
 
+def create_template(name: str, args: List[str], kwargs: Optional[Dict[str, str]] = None) -> Template:
+    """Creates a mwparserfromhell template with from a dictionary (string: string)
+
+    :param name: Name of the template
+    :param args: list of unnamed parameters
+    :param kwargs: named parameters
+    """
+    template = Template(name)
+    for i, value in enumerate(args, 1):
+        template.add(str(i), value, False)
+    if kwargs is not None:
+        for key, value in kwargs.items():
+            template.add(key, value, True)
+    return template
+
+
 def format_template_table(template: Template, keylen: Optional[int] = None):
     """Reformat the given template to be tabular. The template is modified in-place
 
index f340d7a194ed4743185c452d7bf94925f2ad3318..ce0163fbf4a9420d6382110775f62b247ce24cb2 100644 (file)
@@ -1,10 +1,9 @@
 import json
 import math
 from abc import abstractmethod
-from collections import namedtuple
 from typing import Optional, Tuple
 
-import fiona.crs
+import fiona.crs  # pip install Fiona
 import fiona.transform
 import owslib.crs
 import owslib.wms
index f8d9291a2e4e28078488307880e8a5ee43052eb7..538be78b9a704856fe4b9a388d209adb6cd0ff98 100644 (file)
@@ -1,5 +1,4 @@
 import datetime
-import json
 import re
 from sqlalchemy import orm
 from sqlalchemy.orm.session import Session
index deceff8922b2cce00300479c605eca2702b0e19b..0b26415224aaa2cb52c8a84c0a5ed9f7b092ec3e 100644 (file)
@@ -1,5 +1,7 @@
 """Contains functions that maintain/update the cache tables."""
-from sqlalchemy import schema
+import mwparserfromhell
+from sqlalchemy import schema, Table
+from sqlalchemy.engine import Connection
 from sqlalchemy.sql import select
 from sqlalchemy.sql.expression import func as sqlfunc
 from osgeo import ogr
@@ -10,6 +12,16 @@ class UpdateCacheError(RuntimeError):
     pass
 
 
+def _get_mw_text(connection: Connection, text: Table, content_address: str) -> str:
+    parts = content_address.split(':')  # e.g. 'tt:15664'
+    if len(parts) != 2 or parts[0] != 'tt':
+        raise ValueError('Content has unexpected format')
+    old_id = int(parts[1])
+    query = select([text], text.c.old_id == old_id)
+    text_row = connection.execute(query).fetchone()
+    return text_row.old_text
+
+
 def update_wrsledruncache(connection):
     """Updates the wrsledruncache table from the wiki. If convert errors occur, an UpdateCacheError exception
     is raised. No other exception type should be raised under normal circumstances.
@@ -22,48 +34,44 @@ def update_wrsledruncache(connection):
     wrsledruncache = wrmwdb.wrsledruncache_table(metadata)
     page = mwdb.page_table(metadata)
     categorylinks = mwdb.categorylinks_table(metadata)
-    revision = mwdb.revision_table(metadata)
+    slots = mwdb.slots_table(metadata)
+    content = mwdb.content_table(metadata)
     text = mwdb.text_table(metadata)
 
     class Sledrun:
         pass
 
-    transaction = connection.begin()
-
-    # Query all sled runs
-    q = select(
-        [page, categorylinks, revision, text],
-        (page.c.page_latest == revision.c.rev_id) & (text.c.old_id == revision.c.rev_text_id) &
-        (categorylinks.c.cl_from == page.c.page_id) & (categorylinks.c.cl_to == 'Rodelbahn'))
-    sledrun_pages = connection.execute(q)
-    # Original SQL:
-    # sql = u"select page_id, rev_id, old_id, page_title, old_text, 'In_Arbeit' in
-    # (select cl_to from categorylinks where cl_from=page_id) as under_construction
-    # from page, revision, text, categorylinks where page_latest=rev_id and old_id=rev_text_id and
-    # cl_from=page_id and cl_to='Rodelbahn' order by page_title"
-    
-    # Delete all existing entries in wrsledruncache
-    # We rely on transactions MySQL InnoDB
-    connection.execute(wrsledruncache.delete())
-    
-    # Refill wrsledruncache table
-    for sledrun_page in sledrun_pages:
-        try:
-            rodelbahnbox = wrvalidators.rodelbahnbox_from_str(sledrun_page.old_text)
-            sledrun = wrmwmarkup.sledrun_from_rodelbahnbox(rodelbahnbox, Sledrun())
-            sledrun.page_id = sledrun_page.page_id
-            sledrun.page_title = sledrun_page.page_title
-            sledrun.name_url = wrvalidators.sledrun_page_title_to_pretty_url(sledrun_page.page_title)
-            sledrun.under_construction = connection.execute(select(
-                [sqlfunc.count()],
-                (categorylinks.c.cl_from == sledrun_page.page_id) &
-                (categorylinks.c.cl_to == 'In_Arbeit')).alias('x')).fetchone()[0] > 0
-            connection.execute(wrsledruncache.insert(sledrun.__dict__))
-        except ValueError as e:
-            transaction.rollback()
-            error_msg = f"Error at sled run '{sledrun_page.page_title}': {e}"
-            raise UpdateCacheError(error_msg, sledrun_page.page_title, e)
-    transaction.commit()
+    try:
+        with connection.begin():
+
+            # Query all sled runs
+            q = select(
+                [page, categorylinks, slots, content],
+                (page.c.page_latest == slots.c.slot_revision_id) & (slots.c.slot_content_id == content.c.content_id) &
+                (categorylinks.c.cl_from == page.c.page_id) & (categorylinks.c.cl_to == 'Rodelbahn'))
+            sledrun_pages = connection.execute(q)
+
+            # Delete all existing entries in wrsledruncache
+            # We rely on transactions MySQL InnoDB
+            connection.execute(wrsledruncache.delete())
+
+            # Refill wrsledruncache table
+            for sledrun_page in sledrun_pages:
+                old_text = _get_mw_text(connection, text, sledrun_page.content_address)
+                rodelbahnbox = wrvalidators.rodelbahnbox_from_str(old_text)
+                sledrun = wrmwmarkup.sledrun_from_rodelbahnbox(rodelbahnbox, Sledrun())
+                sledrun.page_id = sledrun_page.page_id
+                sledrun.page_title = sledrun_page.page_title
+                sledrun.name_url = wrvalidators.sledrun_page_title_to_pretty_url(sledrun_page.page_title)
+                sledrun.under_construction = connection.execute(select(
+                    [sqlfunc.count()],
+                    (categorylinks.c.cl_from == sledrun_page.page_id) &
+                    (categorylinks.c.cl_to == 'In_Arbeit')).alias('x')).fetchone()[0] > 0
+                connection.execute(wrsledruncache.insert(sledrun.__dict__))
+
+    except ValueError as e:
+        error_msg = f"Error at sled run '{sledrun_page.page_title}': {e}"
+        raise UpdateCacheError(error_msg, sledrun_page.page_title, e)
 
 
 def update_wrinncache(connection):
@@ -78,43 +86,44 @@ def update_wrinncache(connection):
     wrinncache = wrmwdb.wrinncache_table(metadata)
     page = mwdb.page_table(metadata)
     categorylinks = mwdb.categorylinks_table(metadata)
-    revision = mwdb.revision_table(metadata)
+    slots = mwdb.slots_table(metadata)
+    content = mwdb.content_table(metadata)
     text = mwdb.text_table(metadata)
 
     class Inn:
         pass
 
-    transaction = connection.begin()
-
-    # Query all inns
-    q = select(
-        [page, categorylinks, revision, text],
-        (page.c.page_latest == revision.c.rev_id) & (text.c.old_id == revision.c.rev_text_id) &
-        (categorylinks.c.cl_from == page.c.page_id) & (categorylinks.c.cl_to == 'Gasthaus'))
-    inn_pages = connection.execute(q)
-        
-    # Delete all existing entries in wrinncache
-    # We rely on transactions MySQL InnoDB
-    connection.execute(wrinncache.delete())
-        
-    # Refill wrinncache table
-    for inn_page in inn_pages:
-        try:
-            gasthausbox = wrvalidators.gasthausbox_from_str(inn_page.old_text)
-            inn = wrmwmarkup.inn_from_gasthausbox(gasthausbox, Inn())
-            inn.page_id = inn_page.page_id
-            inn.page_title = inn_page.page_title
-            inn.under_construction = connection.execute(select(
-                [sqlfunc.count()],
-                (categorylinks.c.cl_from == inn_page.page_id) &
-                (categorylinks.c.cl_to == 'In_Arbeit')).alias('x')) \
-                .fetchone()[0] > 0  # it would be better to do this in the query above
-            connection.execute(wrinncache.insert(inn.__dict__))
-        except ValueError as e:
-            transaction.rollback()
-            error_msg = f"Error as inn '{inn_page.page_title}': {e}"
-            raise UpdateCacheError(error_msg, inn_page.page_title, e)
-    transaction.commit()
+    try:
+        with connection.begin():
+
+            # Query all inns
+            q = select(
+                [page, categorylinks, slots, content],
+                (page.c.page_latest == slots.c.slot_revision_id) & (slots.c.slot_content_id == content.c.content_id) &
+                (categorylinks.c.cl_from == page.c.page_id) & (categorylinks.c.cl_to == 'Gasthaus'))
+            inn_pages = connection.execute(q)
+
+            # Delete all existing entries in wrinncache
+            # We rely on transactions MySQL InnoDB
+            connection.execute(wrinncache.delete())
+
+            # Refill wrinncache table
+            for inn_page in inn_pages:
+                old_text = _get_mw_text(connection, text, inn_page.content_address)
+                gasthausbox = wrvalidators.gasthausbox_from_str(old_text)
+                inn = wrmwmarkup.inn_from_gasthausbox(gasthausbox, Inn())
+                inn.page_id = inn_page.page_id
+                inn.page_title = inn_page.page_title
+                inn.under_construction = connection.execute(select(
+                    [sqlfunc.count()],
+                    (categorylinks.c.cl_from == inn_page.page_id) &
+                    (categorylinks.c.cl_to == 'In_Arbeit')).alias('x')) \
+                    .fetchone()[0] > 0  # it would be better to do this in the query above
+                connection.execute(wrinncache.insert(inn.__dict__))
+
+    except ValueError as e:
+        error_msg = f"Error as inn '{inn_page.page_title}': {e}"
+        raise UpdateCacheError(error_msg, inn_page.page_title, e)
 
 
 def update_wrreportcache(connection, page_id=None):
@@ -129,38 +138,37 @@ def update_wrreportcache(connection, page_id=None):
     """
     metadata = schema.MetaData()
     wrreportcache = wrmwdb.wrreportcache_table(metadata)
-    transaction = connection.begin()
-
-    # Delete the datasets we are going to update
-    sql_del = wrreportcache.delete()
-    if page_id is not None:
-        sql_del = sql_del.where(wrreportcache.c.page_id == page_id)
-    connection.execute(sql_del)
-
-    def insert_row(connection_, row_list_):
-        if len(row_list_) == 0:
-            return
-        # Insert the report
-        row_ = dict(row_list_[0])
-        connection_.execute(wrreportcache.insert(values=row_))
-
-    # Select the rows to update
-    sql = 'select page_id, page_title, wrreport.id as report_id, date_report, `condition`, description, author_name, ' \
-          'if(author_userid is null, null, author_username) as author_username from wrreport ' \
-          'where {0}`condition` is not null and date_invalid > now() and delete_date is null ' \
-          'order by page_id, date_report desc, date_entry desc' \
-          .format('' if page_id is None else f'page_id={page_id} and ')
-    cursor = connection.execute(sql)
-    page_id = None
-    row_list = []
-    for row in cursor:
-        if row.page_id != page_id:
-            insert_row(connection, row_list)
-            page_id = row.page_id
-            row_list = []
-        row_list.append(row)
-    insert_row(connection, row_list)
-    transaction.commit()
+    with connection.begin():
+        # Delete the datasets we are going to update
+        sql_del = wrreportcache.delete()
+        if page_id is not None:
+            sql_del = sql_del.where(wrreportcache.c.page_id == page_id)
+        connection.execute(sql_del)
+
+        def insert_row(connection_, row_list_):
+            if len(row_list_) == 0:
+                return
+            # Insert the report
+            row_ = dict(row_list_[0])
+            connection_.execute(wrreportcache.insert(values=row_))
+
+        # Select the rows to update
+        sql = 'select page_id, page_title, wrreport.id as report_id, date_report, `condition`, description, ' \
+              'author_name, ' \
+              'if(author_userid is null, null, author_username) as author_username from wrreport ' \
+              'where {0}`condition` is not null and date_invalid > now() and delete_date is null ' \
+              'order by page_id, date_report desc, date_entry desc' \
+              .format('' if page_id is None else f'page_id={page_id} and ')
+        cursor = connection.execute(sql)
+        page_id = None
+        row_list = []
+        for row in cursor:
+            if row.page_id != page_id:
+                insert_row(connection, row_list)
+                page_id = row.page_id
+                row_list = []
+            row_list.append(row)
+        insert_row(connection, row_list)
 
 
 def update_wrmapcache(connection):
@@ -176,87 +184,81 @@ def update_wrmapcache(connection):
     metadata = schema.MetaData()
     page = mwdb.page_table(metadata)
     categorylinks = mwdb.categorylinks_table(metadata)
-    revision = mwdb.revision_table(metadata)
+    slots = mwdb.slots_table(metadata)
+    content = mwdb.content_table(metadata)
     text = mwdb.text_table(metadata)
 
-    transaction = connection.begin()
-
-    # Query all sledruns
-    q = select(
-        [page, categorylinks, revision, text],
-        (page.c.page_latest == revision.c.rev_id) & (text.c.old_id == revision.c.rev_text_id) &
-        (categorylinks.c.cl_from == page.c.page_id) & (categorylinks.c.cl_to == 'Rodelbahn'))
-    sledrun_pages = connection.execute(q)
-    # Original SQL:
-    # sql = u"select page_id, rev_id, old_id, page_title, old_text, 'In_Arbeit' in
-    # (select cl_to from categorylinks where cl_from=page_id) as under_construction
-    # from page, revision, text, categorylinks where page_latest=rev_id and old_id=rev_text_id and cl_from=page_id
-    # and cl_to='Rodelbahn' order by page_title"
-    
-    # Delete all existing entries in wrmappointcache
-    # We rely on transactions MySQL InnoDB
-    connection.execute('delete from wrmappointcache')
-    connection.execute('delete from wrmappathcache')
-    
-    # Refill wrmappointcache and wrmappathcache tables
-    for sledrun_page in sledrun_pages:
-        try:
-            import mwparserfromhell
-            wikicode = mwparserfromhell.parse(sledrun_page.old_text)
-            wrmap_list = wikicode.filter_tags(recursive=False, matches=lambda tag: tag.tag == 'wrmap')
-            if len(wrmap_list) == 0:
-                continue  # not wrmap in page
-            if len(wrmap_list) > 1:
-                raise UpdateCacheError(
-                    f'{len(wrmap_list)} <wrmap ...> entries found in article "{sledrun_page.page_title}"')
-            wrmap = wrmap_list[0]
-            geojson = wrmwmarkup.parse_wrmap(str(wrmap))
-
-            for feature in geojson['features']:
-                properties = feature['properties']
-                coordinates = feature['geometry']['coordinates']
-
-                # Points
-                if properties['type'] in wrmwmarkup.WRMAP_POINT_TYPES:
-                    lon, lat = coordinates
-                    label = properties.get('name')
-                    point_types = {
-                        'gasthaus': 'hut',
-                        'haltestelle': 'busstop',
-                        'parkplatz': 'carpark',
-                        'achtung': 'warning',
-                        'foto': 'photo',
-                        'verleih': 'rental',
-                        'punkt': 'point'
-                    }
-                    point_type = point_types[properties['type']]
-                    sql = 'insert into wrmappointcache (page_id, type, point, label) values (%s, %s, POINT(%s, %s), %s)'
-                    connection.execute(sql, (sledrun_page.page_id, point_type, lon, lat, label))
-
-                # Paths
-                elif properties['type'] in wrmwmarkup.WRMAP_LINE_TYPES:
-                    path_types = {
-                        'rodelbahn': 'sledrun',
-                        'gehweg': 'walkup',
-                        'alternative': 'alternative',
-                        'lift': 'lift',
-                        'anfahrt': 'recommendedcarroute',
-                        'linie': 'line'}
-                    path_type = path_types[properties['type']]
-                    path = ", ".join([f"{lon} {lat}" for lon, lat in coordinates])
-                    path = f'LineString({path})'
-                    if path_type == 'recommendedcarroute':
-                        continue
-                    sql = 'insert into wrmappathcache (path, page_id, type) values (GeomFromText(%s), %s, %s)'
-                    connection.execute(sql, (path, sledrun_page.page_id, path_type))
-
-                else:
-                    raise RuntimeError(f'Unknown feature type {properties["type"]}')
-        except RuntimeError as e:
-            error_msg = f"Error at sledrun '{sledrun_page.page_title}': {e}"
-            transaction.rollback()
-            raise UpdateCacheError(error_msg, sledrun_page.page_title, e)
-    transaction.commit()
+    try:
+        with connection.begin():
+
+            # Query all sledruns
+            q = select(
+                [page, categorylinks, slots, content],
+                (page.c.page_latest == slots.c.slot_revision_id) & (slots.c.slot_content_id == content.c.content_id) &
+                (categorylinks.c.cl_from == page.c.page_id) & (categorylinks.c.cl_to == 'Rodelbahn'))
+            sledrun_pages = connection.execute(q)
+
+            # Delete all existing entries in wrmappointcache
+            # We rely on transactions MySQL InnoDB
+            connection.execute('delete from wrmappointcache')
+            connection.execute('delete from wrmappathcache')
+
+            # Refill wrmappointcache and wrmappathcache tables
+            for sledrun_page in sledrun_pages:
+                old_text = _get_mw_text(connection, text, sledrun_page.content_address)
+                wikicode = mwparserfromhell.parse(old_text)
+                wrmap_list = wikicode.filter_tags(recursive=False, matches=lambda tag: tag.tag == 'wrmap')
+                if len(wrmap_list) == 0:
+                    continue  # not wrmap in page
+                if len(wrmap_list) > 1:
+                    raise UpdateCacheError(
+                        f'{len(wrmap_list)} <wrmap ...> entries found in article "{sledrun_page.page_title}"')
+                wrmap = wrmap_list[0]
+                geojson = wrmwmarkup.parse_wrmap(str(wrmap))
+
+                for feature in geojson['features']:
+                    properties = feature['properties']
+                    coordinates = feature['geometry']['coordinates']
+
+                    # Points
+                    if properties['type'] in wrmwmarkup.WRMAP_POINT_TYPES:
+                        lon, lat = coordinates
+                        label = properties.get('name')
+                        point_types = {
+                            'gasthaus': 'hut',
+                            'haltestelle': 'busstop',
+                            'parkplatz': 'carpark',
+                            'achtung': 'warning',
+                            'foto': 'photo',
+                            'verleih': 'rental',
+                            'punkt': 'point'
+                        }
+                        point_type = point_types[properties['type']]
+                        sql = 'insert into wrmappointcache (page_id, type, point, label) values (%s, %s, POINT(%s, %s), %s)'
+                        connection.execute(sql, (sledrun_page.page_id, point_type, lon, lat, label))
+
+                    # Paths
+                    elif properties['type'] in wrmwmarkup.WRMAP_LINE_TYPES:
+                        path_types = {
+                            'rodelbahn': 'sledrun',
+                            'gehweg': 'walkup',
+                            'alternative': 'alternative',
+                            'lift': 'lift',
+                            'anfahrt': 'recommendedcarroute',
+                            'linie': 'line'}
+                        path_type = path_types[properties['type']]
+                        path = ", ".join([f"{lon} {lat}" for lon, lat in coordinates])
+                        path = f'LineString({path})'
+                        if path_type == 'recommendedcarroute':
+                            continue
+                        sql = 'insert into wrmappathcache (path, page_id, type) values (GeomFromText(%s), %s, %s)'
+                        connection.execute(sql, (path, sledrun_page.page_id, path_type))
+
+                    else:
+                        raise RuntimeError(f'Unknown feature type {properties["type"]}')
+    except RuntimeError as e:
+        error_msg = f"Error at sledrun '{sledrun_page.page_title}': {e}"
+        raise UpdateCacheError(error_msg, sledrun_page.page_title, e)
 
 
 def update_wrregioncache(connection):
@@ -275,35 +277,32 @@ def update_wrregioncache(connection):
     wrsledruncache = wrmwdb.wrsledruncache_table(metadata)
     wrregioncache = wrmwdb.wrregioncache_table(metadata)
 
-    transaction = connection.begin()
-
-    # Delete all existing entries in wrregioncache
-    # We rely on transactions MySQL InnoDB
-    connection.execute(wrregioncache.delete())
-    
-    # Query all combinations of sledruns and regions
-    sel = select(
-        [
-            wrregion.c.id.label('region_id'),
-            sqlfunc.AsWKB(wrregion.c.border).label('border'),
-            wrsledruncache.c.page_id,
-            wrsledruncache.c.position_longitude,
-            wrsledruncache.c.position_latitude
-        ],
-        sqlfunc.contains(
-            wrregion.c.border,
-            sqlfunc.point(wrsledruncache.c.position_longitude, wrsledruncache.c.position_latitude)
+    with connection.begin():
+
+        # Delete all existing entries in wrregioncache
+        # We rely on transactions MySQL InnoDB
+        connection.execute(wrregioncache.delete())
+
+        # Query all combinations of sledruns and regions
+        sel = select(
+            [
+                wrregion.c.id.label('region_id'),
+                sqlfunc.AsWKB(wrregion.c.border).label('border'),
+                wrsledruncache.c.page_id,
+                wrsledruncache.c.position_longitude,
+                wrsledruncache.c.position_latitude
+            ],
+            sqlfunc.contains(
+                wrregion.c.border,
+                sqlfunc.point(wrsledruncache.c.position_longitude, wrsledruncache.c.position_latitude)
+            )
         )
-    )
-    ins = wrregioncache.insert()
-
-    # Refill wrregioncache
-    point = ogr.Geometry(ogr.wkbPoint)
-    result = connection.execute(sel)
-    for row in result:
-        point.SetPoint(0, row.position_longitude, row.position_latitude)
-        if point.Within(ogr.CreateGeometryFromWkb(row.border)):
-            connection.execute(ins.values(region_id=row.region_id, page_id=row.page_id))
-
-    # commit
-    transaction.commit()
+        ins = wrregioncache.insert()
+
+        # Refill wrregioncache
+        point = ogr.Geometry(ogr.wkbPoint)
+        result = connection.execute(sel)
+        for row in result:
+            point.SetPoint(0, row.position_longitude, row.position_latitude)
+            if point.Within(ogr.CreateGeometryFromWkb(row.border)):
+                connection.execute(ins.values(region_id=row.region_id, page_id=row.page_id))
index cd44b719dd87e31ae04bb1c4ff707ae3aa90df42..e0d8dd185e7ad658a1e77dd4b06f804ac49eea1b 100644 (file)
@@ -43,7 +43,7 @@ def wrreport_table(metadata):
     )
 
 
-def wrsledruncache_table(metadata):
+def wrsledruncache_table(metadata) -> Table:
     """Returns the sqlalchemy Table representing the "wrsledruncache" Winterrodeln table in MediaWiki.
     Current table definition
     * version 1.4 (renamed table and added column walkup_possible)
index f2ea5c871ec7cee1f21a05fd42d98ba59c68004d..2ab28d227efa0ef3ba64ad6141b54a153c48e706 100644 (file)
@@ -3,7 +3,7 @@
 import re
 import xml.etree.ElementTree
 import collections
-from typing import Tuple, Optional, List
+from typing import Tuple, Optional, List, OrderedDict, Union
 
 from mwparserfromhell.nodes import Template
 
@@ -15,17 +15,29 @@ from wrpylib.wrvalidators import LonLat, opt_lonlat_from_str, opt_lonlat_to_str,
     opt_phone_comment_enum_to_str, lift_german_from_str, GASTHAUSBOX_DICT
 
 
-def sledrun_from_rodelbahnbox(value, sledrun):
+def split_lon_lat(value: Optional[LonLat]) -> Union[LonLat, Tuple[None, None]]:
+    if value is None:
+        return None, None
+    return value
+
+
+def join_lon_lat(lon: Optional[float], lat: Optional[float]) -> Optional[LonLat]:
+    if lon is None or lat is None:
+        return None
+    return LonLat(lon, lat)
+
+
+def sledrun_from_rodelbahnbox(value: OrderedDict, sledrun: object):
     """Takes a Rodelbahnbox as returned by rodelbahnbox_from_str (that is, an OrderedDict) and
     updates the sledrun instance with all values present in the Rodelbahnbox. Other values are not
     updated. Does not validate the arguments."""
     # sledrun.page_id = None # this field is not updated because it is not present in the RodelbahnBox
     # sledrun.page_title = None # this field is not updated because it is not present in the RodelbahnBox
     # sledrun.name_url = None # this field is not updated because it is not present in the RodelbahnBox
-    sledrun.position_longitude, sledrun.position_latitude = value['Position']
-    sledrun.top_longitude, sledrun.top_latitude = value['Position oben']
+    sledrun.position_longitude, sledrun.position_latitude = split_lon_lat(value['Position'])
+    sledrun.top_longitude, sledrun.top_latitude = split_lon_lat(value['Position oben'])
     sledrun.top_elevation = value['Höhe oben']
-    sledrun.bottom_longitude, sledrun.bottom_latitude = value['Position unten']
+    sledrun.bottom_longitude, sledrun.bottom_latitude = split_lon_lat(value['Position unten'])
     sledrun.bottom_elevation = value['Höhe unten']
     sledrun.length = value['Länge']
     sledrun.difficulty = value['Schwierigkeit']
@@ -55,10 +67,10 @@ def sledrun_to_rodelbahnbox(sledrun) -> collections.OrderedDict:
     """Takes a sledrun instance that might come from the database and converts it to a OrderedDict ready
     to be formatted as RodelbahnBox."""
     value = collections.OrderedDict()
-    value['Position'] = LonLat(sledrun.position_longitude, sledrun.position_latitude)
-    value['Position oben'] = LonLat(sledrun.top_longitude, sledrun.top_latitude)
+    value['Position'] = join_lon_lat(sledrun.position_longitude, sledrun.position_latitude)
+    value['Position oben'] = join_lon_lat(sledrun.top_longitude, sledrun.top_latitude)
     value['Höhe oben'] = sledrun.top_elevation
-    value['Position unten'] = LonLat(sledrun.bottom_longitude, sledrun.bottom_latitude)
+    value['Position unten'] = join_lon_lat(sledrun.bottom_longitude, sledrun.bottom_latitude)
     value['Höhe unten'] = sledrun.bottom_elevation
     value['Länge'] = sledrun.length
     value['Schwierigkeit'] = sledrun.difficulty
@@ -91,7 +103,7 @@ def inn_from_gasthausbox(value, inn):
         if v == '':
             return None
         return v
-    inn.position_longitude, inn.position_latitude = value['Position']
+    inn.position_longitude, inn.position_latitude = split_lon_lat(value['Position'])
     inn.position_elevation = value['Höhe']
     inn.operator = value['Betreiber']
     inn.seats = value['Sitzplätze']
@@ -112,10 +124,10 @@ def inn_from_gasthausbox(value, inn):
 def inn_to_gasthausbox(inn) -> collections.OrderedDict:
     """Converts an inn class to a dict of Gasthausbox properties. inn is an Inn instance."""
     def convfromdb(val, key):
-        v = '' if value is None else val
+        v = '' if val is None else val
         return GASTHAUSBOX_DICT[key].from_str(v)
     value = collections.OrderedDict()
-    value['Position'] = LonLat(inn.position_longitude, inn.position_latitude)
+    value['Position'] = join_lon_lat(inn.position_longitude, inn.position_latitude)
     value['Höhe'] = inn.position_elevation
     value['Betreiber'] = inn.operator
     value['Sitzplätze'] = inn.seats