Daniel Kinzler has submitted this change and it was merged.

Change subject: Final wikiparser mapper and reducer modules using classes. 
Added more docs. Reduced and polished code.
......................................................................


Final wikiparser mapper and reducer modules using classes. Added more docs. 
Reduced and polished code.

Change-Id: I33d8b0aa1927a07a08b405905f6e65a14632b116
---
D wikiparser/disco/chunk.py
D wikiparser/disco/wikiparser.py
A wikiparser/mapper.py
A wikiparser/reducer.py
A wikiparser/runhadoop.sh
D wikiparser/wikiparser.py
D wikiparser/wikiparser_db.py
D wikiparser/wikiparser_r.py
8 files changed, 422 insertions(+), 347 deletions(-)

Approvals:
  Daniel Kinzler: Verified; Looks good to me, approved



diff --git a/wikiparser/disco/chunk.py b/wikiparser/disco/chunk.py
deleted file mode 100644
index f979766..0000000
--- a/wikiparser/disco/chunk.py
+++ /dev/null
@@ -1,41 +0,0 @@
-import sys, os
-from disco.ddfs import DDFS
-
-
-if(len(sys.argv) < 2):
-    print "USAGE: python", sys.argv[0], "<ddfs tag:name> [<uncompressed 
wikidata dump file path>]"
-    print "You may pipe the dump file contents using lbzcat or bzcat into 
stdin like:"
-    print "lbzcat wikidatawiki-yyyymmdd-pages-meta-current.xml.bz2 | python 
<ddfs tag:name>\n"
-    sys.exit()
-
-tag = (sys.argv[1])
-infile = sys.stdin if len(sys.argv) < 3 else sys.argv[2]
-
-getChunk = lambda x : sys.argv[1] + ".chunk" + str(x)
-fcount = 1
-count = 1
-outfile = open(getChunk(fcount), "w")
-
-for i in infile:
-    outfile.write(i)
-    if '</page>' in i:
-        count += 1
-        printProgress = True;
-    
-    if count % 100000 == 0 and printProgress == True:
-        sys.stdout.write("\r%s" % "Finished " + str(count) + " pages.")
-        sys.stdout.flush()
-        printProgress = True if count % 1000000 == 0 else False
-        
-    if count % 1000000 == 0 and printProgress == True:
-        outfile.close()
-        DDFS(master="disco://localhost").push(tag, 
[os.path.abspath(getChunk(fcount))])
-        sys.stdout.write("\nChunk " + str(fcount) + " pushed to tag %s on 
DDFS.\n" % tag)
-        sys.stdout.flush()
-        os.remove(getChunk(fcount))
-        fcount += 1
-        outfile = open(getChunk(fcount), "w")
-        printProgress = False
-        break
-        
-if len(sys.argv) > 2: infile.close()
diff --git a/wikiparser/disco/wikiparser.py b/wikiparser/disco/wikiparser.py
deleted file mode 100644
index 9938f70..0000000
--- a/wikiparser/disco/wikiparser.py
+++ /dev/null
@@ -1,147 +0,0 @@
-from disco.core import Job, result_iterator
-import sys
-    
-class WikiParser(Job):
-    partitions = 4
-    
-    def map(self, row, params):
-        results = self._parsePage("<page>" + row[0] + "</page>")
-        if results != None:
-            for j in results:
-                if j != None:
-                    yield j[0], j[1]
-
-    @staticmethod
-    def regex_reader(item_re_str, fd, size, fname, output_tail=False, 
read_buffer_size=8192):
-        """
-        Modified version of disco.worker.classic.func.re_reader - does not 
throw DataError
-        A map reader that uses an arbitrary regular expression to parse the 
input
-        stream.
-
-        :param item_re_str: regular expression for matching input items
-
-        The reader works as follows:
-
-        1. X bytes is read from *fd* and appended to an internal buffer *buf*.
-        2. ``m = regexp.match(buf)`` is executed.
-        3. If *buf* produces a match, ``m.groups()`` is yielded, which 
contains an
-        input entry for the map function. Step 2. is executed for the remaining
-        part of *buf*. If no match is made, go to step 1.
-        4. If *fd* is exhausted before *size* bytes have been read,
-        and *size* tests ``True``, instead of raising a 
:class:`disco.error.DataError`,
-        it prints something about the error.
-        5. When *fd* is exhausted but *buf* contains unmatched bytes, two 
modes are
-        available: If ``output_tail=True``, the remaining *buf* is yielded as 
is.
-        Otherwise, a message is sent that warns about trailing bytes.
-        The remaining *buf* is discarded.
-
-        Note that :func:`re_reader` fails if the input streams contains 
unmatched
-        bytes between matched entries.
-        Make sure that your *item_re_str* is constructed so that it covers all
-        bytes in the input stream.
-
-        :func:`re_reader` provides an easy way to construct parsers for textual
-        input streams.
-        For instance, the following reader produces full HTML
-        documents as input entries::
-
-        def html_reader(fd, size, fname):
-        for x in re_reader("<HTML>(.*?)</HTML>", fd, size, fname):
-        yield x[0]
-
-        """
-        item_re = re.compile(item_re_str)
-        buf = b""
-        tot = 0
-        while True:
-            if size:
-                r = fd.read(min(read_buffer_size, size - tot))
-            else:
-                r = fd.read(read_buffer_size)
-            tot += len(r)
-            buf += r
-
-            m = item_re.match(buf)
-            while m:
-                yield m.groups()
-                buf = buf[m.end():]
-                m = item_re.match(buf)
-
-            if not len(r) or (size!=None and tot >= size):
-                if size != None and tot < size:
-                    print("Truncated input: Expected {0} bytes, got 
{1}".format(size, tot), fname)
-                if len(buf):
-                    if output_tail:
-                        yield [buf]
-                    else:
-                        print("Couldn't match the last {0} bytes in {1}. "
-                              "Some bytes may be missing from 
input.".format(len(buf), fname))
-                break
-
-    @staticmethod
-    def map_reader(fd, size, url, params):
-        from wikiparser import WikiParser
-        count = 0
-        line = fd.readline()
-        while "<page>" not in line:
-            line = fd.readline()
-            count += 1
-
-        fd.seek(0);
-
-        while count > 0:
-            fd.readline()
-            count -= 1
-
-        reader = WikiParser.regex_reader("\s\s<page>([\s\S]*?)</page>\\n", fd, 
size, url);
-        for row in reader:
-                yield row
-
-    def reduce2(self, rows_iter):
-        for i in rows_iter:
-            yield i
-
-    def _parsePage(self, page):
-        from lxml import etree
-        from StringIO import StringIO
-        import json
-
-        tree = etree.parse(StringIO(page))
-        page = {child.tag:child.text for child in tree.iter()}
-        title = page['title'][1:]
-        try:
-            if page['ns'] == '0':
-                text = json.loads(page['text'])
-                statement = None
-                if 'claims' in text:
-                    for a in text['claims']:
-                        statement =  {i:j for i, j in self._pairwise(a['m'])}
-                    if statement != None:
-                        try:
-                            toyield1 = (title, str(statement['value']))
-                            value = 
str(statement['wikibase-entityid']['numeric-id']) if 'wikibase-entityid' in 
statement else statement['string']
-                            toyield2 = (title, str(statement['value']) + 
"----" + value)
-                        except KeyError:
-                            toyield1 = toyield2 = None
-                        yield toyield1
-                        yield toyield2
-        except KeyError:
-            pass
-
-    def _pairwise(self, iterable):
-        from itertools import izip
-        a = iter(iterable)
-        return izip(a, a)
-
-if __name__ == '__main__':
-    from wikiparser import WikiParser
-    
-    if(len(sys.argv) < 2):
-        print "USAGE: python", sys.argv[0], "<ddfs tag:name> [<output file 
path>]"
-        print "You may omit the output file; it's stdout by default.\n"
-        sys.exit()
-
-    job = WikiParser().run(input=[sys.argv[1]])
-    outf = sys.stdout if len(sys.argv) < 3 else open(sys.argv[2], "w")
-    for a, b in result_iterator(job.wait(show=True)):
-        outf.write(a.encode('utf-8') + "," + b.encode('utf-8') + "\n")
diff --git a/wikiparser/mapper.py b/wikiparser/mapper.py
new file mode 100644
index 0000000..84f9e50
--- /dev/null
+++ b/wikiparser/mapper.py
@@ -0,0 +1,245 @@
+#!/usr/bin/python
+
+"""This is the mapper module for running the wikiparser Hadoop scripts. See 
main() for usage info.
+The org.wikimedia.wikibase.entitysuggester.wikiparser.WikiPageInputFormat 
class in wikiparser-0.1.jar
+is an InputFormat class that is used by Hadoop to split the input XML data 
dump into chunks of <page>...</page>.
+In simple terms, if there are N mappers and M chunks, Hadoop divides them into 
the N mappers.
+
+This module contains an abstract class with a public run() method. It reads 
the chunks of <page>...</page> from
+stdin and calls _getClaimsAndTitle on a single chunk, which in turn extracts 
the claims from a page (if it's a wikibase Item)
+and calls _generateKeyValues on the list of claims. _generateKeyValues is 
implemented in the different subclasses to yield
+different kinds of CSV rows for the different datasets.
+"""
+
+from lxml import etree
+from itertools import izip
+from StringIO import StringIO
+import json
+import sys
+import traceback
+
+def main():
+    """The following are 6 types of sample commands. Note that the -input and 
-output parameters in the Hadoop command
+    are paths on HDFS and not on the local filesystem. Replace /path/to with 
the actual paths of the files.
+    wikiparser-0.1.jar, mapper.py and reducer.py have to available in a 
location where Hadoop has enough permissions to access.
+
+    1. For generating the item-property pairs using Hadoop:
+    (Dataset used to suggest global claim properties)
+    bin/hadoop jar contrib/streaming/hadoop*streaming*jar -libjars 
/path/to/wikiparser-0.1.jar \
+      -inputformat 
org.wikimedia.wikibase.entitysuggester.wikiparser.WikiPageInputFormat \
+      -input /input/dump.xml -output /output/global-ip-pairs \
+      -file  /path/to/mapper.py -mapper '/path/to/mapper.py global-ip-pairs' \
+      -reducer '/bin/cat'
+
+    2. For generating the claim guid-property pairs for source refs using 
Hadoop:
+    (Dataset used to suggest source ref properties)
+    bin/hadoop jar contrib/streaming/hadoop*streaming*jar -libjars 
/path/to/wikiparser-0.1.jar \
+      -inputformat 
org.wikimedia.wikibase.entitysuggester.wikiparser.WikiPageInputFormat \
+      -input /input/dump.xml -output /output/ref-cp-pairs \
+      -file  /path/to/mapper.py -mapper '/path/to/mapper.py ref-cp-pairs' \
+      -reducer '/bin/cat'
+
+    3. For counting frequencies of properties used in source refs:
+    (Dataset used to suggest source ref properties for empty source refs)
+    bin/hadoop jar contrib/streaming/hadoop*streaming*jar -libjars 
/path/to/wikiparser-0.1.jar \
+      -inputformat 
org.wikimedia.wikibase.entitysuggester.wikiparser.WikiPageInputFormat \
+      -input /input/dump.xml -output /output/ref-props \
+      -file  /path/to/mapper.py -mapper '/path/to/mapper.py ref-props' \
+      -file /path/to/reducer.py -reducer '/path/to/reducer.py ref-props'
+
+    4. For counting global property (used in all claims) frequencies:
+    (Dataset used to suggest global claim properties for empty items)
+    bin/hadoop jar contrib/streaming/hadoop*streaming*jar -libjars 
/path/to/wikiparser-0.1.jar \
+      -inputformat 
org.wikimedia.wikibase.entitysuggester.wikiparser.WikiPageInputFormat \
+      -input /input/dump.xml -output /output/global-props \
+      -file  /path/to/mapper.py -mapper '/path/to/mapper.py global-props' \
+      -file /path/to/reducer.py -reducer '/path/to/reducer.py global-props'
+
+    5. For counting frequency of properties used in qualifiers, to find the 
top N popular qualifier properties for each property:
+    (Dataset used to suggest qualifiers, given a property)
+    bin/hadoop jar contrib/streaming/hadoop*streaming*jar -libjars 
/path/to/wikiparser-0.1.jar \
+      -inputformat 
org.wikimedia.wikibase.entitysuggester.wikiparser.WikiPageInputFormat \
+      -input /input/dump.xml -output /output/qual-props \
+      -file  /path/to/mapper.py -mapper '/path/to/mapper.py qual-props' \
+      -file /path/to/reducer.py -reducer '/path/to/reducer.py qual-props'
+
+    6. For counting frequency of values used with properties, to find the top 
N popular values for each property:
+    (Dataset used to suggest values, given a property)
+    bin/hadoop jar contrib/streaming/hadoop*streaming*jar -libjars 
/path/to/wikiparser-0.1.jar \
+      -inputformat 
org.wikimedia.wikibase.entitysuggester.wikiparser.WikiPageInputFormat \
+      -input /input/dump.xml -output /output/prop-values \
+      -file  /path/to/mapper.py -mapper '/path/to/mapper.py prop-values' \
+      -file /path/to/reducer.py -reducer '/path/to/reducer.py prop-values'
+    """
+
+    mapperClass = {
+        'global-ip-pairs': GlobalIPPairGeneratorMapper,
+        'ref-cp-pairs': RefClaimPropPairGeneratorMapper,
+        'ref-props': CountRefPropsMapper,
+        'global-props': CountGlobalPropsMapper,
+        'qual-props': CountQualiferPropsMapper,
+        'prop-values': CountPropertyValuesMapper
+    }
+
+    try:
+        mapper = mapperClass[sys.argv[1]]()
+    except (KeyError, IndexError):
+        print "Unrecognized/missing mapper arguments."
+        print "Please enter any of global-ip-pairs, ref-cp-pairs, ref-props, 
global-props, qual-props, prop-values."
+        return
+
+    mapper.run()
+
+class AbstractWikiParserMapper:
+    """Abstract class that should be inherited by all mapper classes"""
+
+    def run(self):
+        """Main driver method that iterates through the pages and sends them 
to the generateKeyValues method."""
+        while True:
+            page = ''
+            i = sys.stdin.readline()
+            if not i: break
+            if '<page>' in i:
+                i = sys.stdin.readline()
+                while '</page>' not in i:
+                    page += i
+                    i = sys.stdin.readline()
+                page = '<page>' + page + '</page>'
+                (claims, title) = self._getClaimsAndTitle(page)
+                if claims != None: self._generateKeyValues(claims, title)
+                sys.stdout.flush()
+
+    def _getClaimsAndTitle(self, page):
+        """Parses a page and its JSON text to return the list of claims.       
 """
+        tree = etree.parse(StringIO(page))
+        page = {child.tag:child.text for child in tree.iter()}
+        if page['ns'] == '0':
+            title = page['title'][1:]
+            text = json.loads(page['text'])
+            if 'claims' in text:
+                return text['claims'], title
+            else:
+                return None, None
+
+    def _generateKeyValues(self, claims, title):
+        """Iterates through a list of claims, performs some operations and 
writes (key, value) pairs
+        to stdout to be fed into the reducer script."""
+        raise NotImplementedError("Please implement this method in a 
subclass.")
+
+    def _pairwise(self, iterable):
+        a = iter(iterable)
+        return izip(a, a)
+
+class GlobalIPPairGeneratorMapper(AbstractWikiParserMapper):
+    """The mapper class for generating the item-property pairs and 
item-(property-value) pairs using Hadoop."""
+
+    def _generateKeyValues(self, claims, title):
+        """Iterates through an item's claims, extracts the wikibaseProperties 
and sends the item ID, property ID
+        and a relative score of 1 to stdout, separated by commas."""
+        ipPair = ipPairOld = None
+        for claim in claims:
+            statement = {i:j for i, j in self._pairwise(claim['m'])}
+            if statement == None: continue
+            try:
+                ipPair = "Q%s,P%s,1" % (str(title), str(statement['value'])) # 
,1 is for relative score. It's same for all, ie. 1.
+                # This code may be added in the future to generate CSV rows 
with values. Please make it in a different class:
+                ## if 'wikibase-entityid' not in statement: continue
+                ## value = 
str(statement['wikibase-entityid']['numeric-id']).encode("utf-8", 
'ignore').strip()
+                ## ipvPair = "Q%s,P%s----Q%s,1" % (str(title), 
str(statement['value']), value) # for itemID,propertyID---valueID rows
+                ## pvPair = "P%s,Q%s,1" % (str(statement['value']), value) # 
for propertyID,valueID rows
+                # After that, write ipvPair or pvPair to stdout, whichever is 
needed.
+                
+                if ipPairOld != ipPair: #This check is to prevent printing 
duplicate itemID,propertyID pairs.
+                    ipPairOld = ipPair
+                    sys.stdout.write(ipPair.encode("utf-8", 'ignore').strip() 
+ "\n")
+            except KeyError:
+                pass
+
+class RefClaimPropPairGeneratorMapper(AbstractWikiParserMapper):
+    """The mapper class for generating the claim guid-property pairs for 
source refs using Hadoop."""
+
+    def _generateKeyValues(self, claims, title):
+        """Iterates through an item's claims, extracts the claim GUIDs and 
their source ref wikibaseProperties
+        and sends them to stdout with claim GUID as key and prefixed property 
ID as value."""
+        ipPair = ipPairOld = None
+        for claim in claims:
+            statement = {i:j for i, j in self._pairwise(claim['m'])}
+            if statement == None: continue
+            try:
+                guid = claim['g']
+                for ref in claim['refs']:
+                    reference = {i:j for i, j in self._pairwise(ref[0])}
+                    sys.stdout.write("%s,P%s,1\n" % (guid, 
str(reference['value'])))
+            except KeyError:
+                pass
+
+class CountRefPropsMapper(AbstractWikiParserMapper):
+    """The mapper class to count frequencies of properties used in source refs 
from the wikidata dump using Hadoop."""
+
+    def _generateKeyValues(self, claims, title):
+        """Iterates through an item's claims to extract the properties from 
the source references and sends them
+        to stdout with the property id as the key and "1" as the value. This 
is similar to MapReduce wordcount."""
+        for claim in claims:
+            if 'refs' not in claim: continue
+            for ref in claim['refs']:
+                source = {i:j for i, j in self._pairwise(ref[0])}
+                if source == None: continue
+                try:
+                    prop = str(source['value'])
+                    sys.stdout.write("P%s\t1\n" % prop)
+                except KeyError:
+                    pass
+
+class CountGlobalPropsMapper(AbstractWikiParserMapper):
+    """The mapper class to count global property frequencies from the wikidata 
dump using Hadoop."""
+
+    def _generateKeyValues(self, claims, title):
+        """Iterates through an item's claims to extract the properties from 
the statements and sends them to stdout
+        with the property id as the key and "1" as the value. This is similar 
to MapReduce wordcount."""
+        for claim in claims:
+            statement = {i:j for i, j in self._pairwise(claim['m'])}
+            if statement == None: continue
+            try:
+                prop = str(statement['value'])
+                sys.stdout.write("P%s\t1\n" % prop)
+            except KeyError:
+                pass
+
+class CountQualiferPropsMapper(AbstractWikiParserMapper):
+    """The mapper class to count global qualifier property frequencies from 
the wikidata dump using Hadoop."""
+
+    def _generateKeyValues(self, claims, title):
+        """Iterates through an item's claims to extract the properties from 
the statements and the properties of its
+        qualifiers if any exist, and sends them to stdout with the statement's 
property id as the key and the qualifier
+        property id as the value. This is similar to MapReduce wordcount."""
+        for claim in claims:
+            statement = {i:j for i, j in self._pairwise(claim['m'])}
+            if statement == None: continue
+            try:
+                prop = str(statement['value'])
+                for q in claim['q']:
+                    qualifier = {i:j for i, j in self._pairwise(q)}
+                    sys.stdout.write("P%s\tP%s\n" % (prop, 
str(qualifier['value'])))
+            except KeyError:
+                pass
+
+class CountPropertyValuesMapper(AbstractWikiParserMapper):
+    """The mapper class to count global qualifier property frequencies from 
the wikidata dump using Hadoop."""
+
+    def _generateKeyValues(self, claims, title):
+        """Iterates through an item's claims to extract the wikibaseProperties 
and wikibaseValues from the statements
+        and sends them to stdout with the wikibaseProperty id as the key and 
the wikibaseValue as the value.
+        This is similar to MapReduce wordcount."""
+        for claim in claims:
+            statement = {i:j for i, j in self._pairwise(claim['m'])}
+            if statement == None: continue
+            try:
+                prop = str(statement['value'])
+                if 'wikibase-entityid' not in statement: continue
+                value = 
str(statement['wikibase-entityid']['numeric-id']).encode("utf-8", 
'ignore').strip()
+                sys.stdout.write("P%s\tQ%s\n" % (prop, value))
+            except KeyError:
+                pass
+
+if __name__ == '__main__':
+    main()
diff --git a/wikiparser/reducer.py b/wikiparser/reducer.py
new file mode 100644
index 0000000..4c3d1ab
--- /dev/null
+++ b/wikiparser/reducer.py
@@ -0,0 +1,142 @@
+#!/usr/bin/python
+
+"""This is the reducer module for the wikiparser Hadoop scripts. See main() 
for usage info."""
+
+import sys
+
+def main():
+    """ Here are 6 types of sample commands:
+    1. For generating the item-property pairs using Hadoop:
+    (Dataset used to suggest global claim properties)
+    bin/hadoop jar contrib/streaming/hadoop*streaming*jar -libjars 
/path/to/wikiparser-0.1.jar \
+      -inputformat 
org.wikimedia.wikibase.entitysuggester.wikiparser.WikiPageInputFormat \
+      -input /input/dump.xml -output /output/global-ip-pairs \
+      -file  /path/to/mapper.py -mapper '/path/to/mapper.py global-ip-pairs' \
+      -reducer '/bin/cat'
+
+    2. For generating the claim guid-property pairs for source refs using 
Hadoop:
+    (Dataset used to suggest source ref properties)
+    bin/hadoop jar contrib/streaming/hadoop*streaming*jar -libjars 
/path/to/wikiparser-0.1.jar \
+      -inputformat 
org.wikimedia.wikibase.entitysuggester.wikiparser.WikiPageInputFormat \
+      -input /input/dump.xml -output /output/ref-cp-pairs \
+      -file  /path/to/mapper.py -mapper '/path/to/mapper.py ref-cp-pairs' \
+      -reducer '/bin/cat'
+
+    3. For counting frequencies of properties used in source refs:
+    (Dataset used to suggest source ref properties for empty source refs)
+    bin/hadoop jar contrib/streaming/hadoop*streaming*jar -libjars 
/path/to/wikiparser-0.1.jar \
+      -inputformat 
org.wikimedia.wikibase.entitysuggester.wikiparser.WikiPageInputFormat \
+      -input /input/dump.xml -output /output/ref-props \
+      -file  /path/to/mapper.py -mapper '/path/to/mapper.py ref-props' \
+      -file /path/to/reducer.py -reducer '/path/to/reducer.py ref-props'
+
+    4. For counting global property (used in all claims) frequencies:
+    (Dataset used to suggest global claim properties for empty items)
+    bin/hadoop jar contrib/streaming/hadoop*streaming*jar -libjars 
/path/to/wikiparser-0.1.jar \
+      -inputformat 
org.wikimedia.wikibase.entitysuggester.wikiparser.WikiPageInputFormat \
+      -input /input/dump.xml -output /output/global-props \
+      -file  /path/to/mapper.py -mapper '/path/to/mapper.py global-props' \
+      -file /path/to/reducer.py -reducer '/path/to/reducer.py global-props'
+      
+    5. For counting frequency of properties used in qualifiers, to find the 
top N popular qualifier properties for each property:
+    (Dataset used to suggest qualifiers, given a property)
+    bin/hadoop jar contrib/streaming/hadoop*streaming*jar -libjars 
/path/to/wikiparser-0.1.jar \
+      -inputformat 
org.wikimedia.wikibase.entitysuggester.wikiparser.WikiPageInputFormat \
+      -input /input/dump.xml -output /output/qual-props \
+      -file  /path/to/mapper.py -mapper '/path/to/mapper.py qual-props' \
+      -file /path/to/reducer.py -reducer '/path/to/reducer.py qual-props'
+      
+    6. For counting frequency of values used with properties, to find the top 
N popular values for each property:
+    (Dataset used to suggest values, given a property)
+    bin/hadoop jar contrib/streaming/hadoop*streaming*jar -libjars 
/path/to/wikiparser-0.1.jar \
+      -inputformat 
org.wikimedia.wikibase.entitysuggester.wikiparser.WikiPageInputFormat \
+      -input /input/dump.xml -output /output/prop-values \
+      -file  /path/to/mapper.py -mapper '/path/to/mapper.py prop-values' \
+      -file /path/to/reducer.py -reducer '/path/to/reducer.py prop-values'
+    """
+
+    reducerClass = {
+        'ref-props': CountPropsReducer,
+        'global-props': CountPropsReducer,
+        'qual-props': CountQualiferPropsReducer,
+        'prop-values': CountQualiferPropsReducer
+    }
+
+    try:
+        reducer = reducerClass[sys.argv[1]]()
+    except (KeyError, IndexError):
+        print "Unrecognized/missing reducer arguments."
+        print "Please enter any of ref-props, global-props, qual-props, 
prop-values."
+        return
+
+    reducer.run()
+
+class BaseWikiParserReducer:
+    """Base/abstract class that should be inherited by all reducer classes"""
+
+    def run(self):
+        """Main driver method that iterates through the key-value pairs 
obtained from the mapper and writes out the final results."""
+        raise NotImplementedError("Please implement this method in a 
subclass.")
+
+class CountPropsReducer(BaseWikiParserReducer):
+    """The reducer class to count frequencies of properties used in source 
refs from the wikidata dump, and global property frequencies using Hadoop."""
+
+    def run(self):
+        """Counts the number of occurrences for each property and outputs the 
property id as key
+        and the occurrence frequency as the value to stdout.
+        """
+        oldKey = None
+        currentCount = 0
+        key = None
+
+        for i in sys.stdin:
+            (key, value) = i.split("\t")
+            try:
+                count = int(value.strip())
+            except ValueError:
+                continue
+
+            if oldKey == key:
+                currentCount += count
+
+            else:
+                if oldKey:
+                    sys.stdout.write("%s,%d\n" % (oldKey, currentCount))
+                currentCount = count
+                oldKey = key
+
+        if oldKey == key:
+            sys.stdout.write("%s,%d\n" % (oldKey, currentCount))
+
+class CountQualiferPropsReducer(BaseWikiParserReducer):
+    """The reducer class to count global qualifier property frequencies from 
the wikidata dump using Hadoop.
+    This is also used for counting the occurrences of values for each 
property."""
+
+    def run(self):
+        """Counts the number of occurrences for each property and outputs the 
property id as key
+        and the occurrence frequency as the value to stdout.
+        """
+        oldKey = None
+        key = None
+        qualifierCounts = {}
+
+        for i in sys.stdin:
+            (key, value) = i.split("\t")
+            value = value.strip()
+
+            if oldKey == key:
+                if value in qualifierCounts:
+                    qualifierCounts[value] += 1
+                else:
+                    qualifierCounts[value] = 1
+            else:
+                if oldKey:
+                    sys.stdout.write("####%s\n" % oldKey) # The property ID, 
followed by the list of qualifer prop counts
+                    for q in qualifierCounts:
+                        sys.stdout.write("%s,%d\n" % (q, qualifierCounts[q]))
+                oldKey = key
+                qualifierCounts = {}
+                qualifierCounts[value] = 1
+
+if __name__ == '__main__':
+    main()
diff --git a/wikiparser/runhadoop.sh b/wikiparser/runhadoop.sh
new file mode 100755
index 0000000..ab9d860
--- /dev/null
+++ b/wikiparser/runhadoop.sh
@@ -0,0 +1,35 @@
+#!/bin/bash 
+    d=`dirname "$0"`
+
+    #You may need to change the following two variables to suit your 
installation.
+    hadoop=/usr/share/hadoop
+    hadoop_executable=hadoop
+
+    if [ -z "$3" ]; then
+        echo "$0 <mode> <xmldump> <output> [<local-output>]"
+        echo "<mode> can be any of global-ip-pairs, ref-cp-pairs, ref-props, 
global-props, qual-props, prop-values."
+        echo "<xmldump> is the location of the wikidata data dump file on 
HDFS."
+        echo "<output> is the directory to write output to on HDFS."
+        echo "<local-output> is optional - if it is specified, the output from 
HDFS will be copied to this file on the local file system."
+        exit 1
+    fi
+
+    if [ $1 == "global-ip-pairs" -o $1 == "ref-cp-pairs" ]; then
+        $hadoop_executable jar $hadoop/contrib/streaming/hadoop*streaming*.jar 
\
+            -libjars "$d/target/wikiparser-0.1.jar" \
+            -inputformat 
org.wikimedia.wikibase.entitysuggester.wikiparser.WikiPageInputFormat \
+            -input "$2" -output "$3" \
+            -file "$d/mapper.py" -mapper "$d/mapper.py $1" \
+            -reducer '/bin/cat'
+    else
+        $hadoop_executable jar $hadoop/contrib/streaming/hadoop*streaming*.jar 
\
+            -libjars "$d/target/wikiparser-0.1.jar" \
+            -inputformat 
org.wikimedia.wikibase.entitysuggester.wikiparser.WikiPageInputFormat \
+            -input "$2" -output "$3" \
+            -file "$d/mapper.py" -mapper "$d/mapper.py $1" \
+            -file "$d/reducer.py" -reducer "$d/reducer.py $1"
+    fi
+
+    if [ -n "$4" ]; then
+        $hadoop_executable dfs -cat $3/part-* > $4
+    fi
diff --git a/wikiparser/wikiparser.py b/wikiparser/wikiparser.py
deleted file mode 100644
index c7e3731..0000000
--- a/wikiparser/wikiparser.py
+++ /dev/null
@@ -1,55 +0,0 @@
-#!/usr/bin/python
-
-from lxml import etree
-from StringIO import StringIO
-import json
-import sys
-
-page = ''
-
-def main():
-    while True:
-        page = ''
-        i = sys.stdin.readline()
-        if not i: break
-        if '<page>' in i:
-            i = sys.stdin.readline()
-            while '</page>' not in i:
-                page += i#.strip()
-                i = sys.stdin.readline()
-            page = '<page>' + page + '</page>'
-            parsePage(page)
-
-def pairwise(iterable):
-    from itertools import izip
-    a = iter(iterable)
-    return izip(a, a)
-
-def parsePage(page):
-    tree = etree.parse(StringIO(page))
-    page = {child.tag:child.text for child in tree.iter()}
-    try:
-        if page['ns'] == '0':
-            title = page['title'][1:]
-            text = json.loads(page['text'])
-            statement = None
-            if 'claims' in text:
-                for a in text['claims']:
-                    statement = {i:j for i, j in pairwise(a['m'])}
-                    if statement != None:
-                        try:
-                            toyield1 = str(statement['value'])
-                            value = 
str(statement['wikibase-entityid']['numeric-id']) if 'wikibase-entityid' in 
statement else statement['string']
-                            toyield2 = str(statement['value']) + "----" + value
-                            sys.stdout.write("$$\t" + toyield1.encode("utf-8", 
'ignore').strip() + "\n")
-                            sys.stdout.write("$$\t" + toyield2.encode("utf-8", 
'ignore').strip() + "\n")
-                            sys.stdout.write("@@\t" + str(title) + "," + 
toyield1.encode("utf-8", 'ignore').strip() + "\n")
-                            sys.stdout.write("@@\t" + str(title) + "," + 
toyield2.encode("utf-8", 'ignore').strip() + "\n")
-                        except KeyError:
-                            pass
-    except (KeyError, ValueError, TypeError) as e:
-        sys.stderr.write("Error occurred for page : " + str(title) + ", ns = " 
+ str(page['ns']) + "\n")
-        sys.stderr.write(traceback.format_exc() + "\n")
-
-if __name__ == '__main__':
-    main()
diff --git a/wikiparser/wikiparser_db.py b/wikiparser/wikiparser_db.py
deleted file mode 100644
index 958e9ab..0000000
--- a/wikiparser/wikiparser_db.py
+++ /dev/null
@@ -1,88 +0,0 @@
-#!/usr/bin/python
-
-from lxml import etree
-from StringIO import StringIO
-import json
-import sys
-import MySQLdb as mdb
-import traceback
-
-count = 0
-page = ''
-
-def main():
-    con = None
-    cur = None
-    try:
-        con = mdb.connect('localhost', 'root', 'password', 'wikidatawiki');
-        cur = con.cursor()
-        cur.execute("SET FOREIGN_KEY_CHECKS = 0")
-        cur.execute("SET UNIQUE_CHECKS = 0")
-        cur.execute("SET AUTOCOMMIT = 0")
-        count = 0
-        while True:
-            page = ''
-            i = sys.stdin.readline()
-            if not i: break
-            if '<page>' in i:
-                i = sys.stdin.readline()
-                while '</page>' not in i:
-                    page += i
-                    i = sys.stdin.readline()
-                page = '<page>' + page + '</page>'
-                parsePage(con, cur, page)
-                count += 1
-                if(count % 10000 == 0):
-                    con.commit()
-    finally:
-        if cur:
-            cur.execute("SET UNIQUE_CHECKS = 1")
-            cur.execute("SET FOREIGN_KEY_CHECKS = 1")
-            con.commit()
-        if con:
-            con.close()
-
-def pairwise(iterable):
-    from itertools import izip
-    a = iter(iterable)
-    return izip(a, a)
-
-def parsePage(con, cur, page):
-    tree = etree.parse(StringIO(page))
-    page = {child.tag:child.text for child in tree.iter()}
-    try:
-        if page['ns'] == '0':
-            title = page['title'][1:]
-            text = json.loads(page['text'])
-            if 'en' not in text['label']: return
-            label = text['label']['en'].encode("utf-8", 'ignore')
-            cur.execute("""INSERT INTO label VALUES (%s, %s, %s)""", 
(int(title), 'en', label))
-            statement = None
-            if 'claims' in text:
-                for a in text['claims']:
-                    statement = {i:j for i, j in pairwise(a['m'])}
-                    if statement != None:
-                        try:
-                            toyield1 = str(statement['value'])
-                            value = 
str(statement['wikibase-entityid']['numeric-id']) if 'wikibase-entityid' in 
statement else statement['string']
-                            toyield2 = str(statement['value']) + "----" + value
-                            sys.stdout.write("$$\t" + toyield1.encode("utf-8", 
'ignore').strip() + "\n")
-                            sys.stdout.write("$$\t" + toyield2.encode("utf-8", 
'ignore').strip() + "\n")
-                            sys.stdout.write("@@\t" + str(title) + "," + 
toyield1.encode("utf-8", 'ignore').strip() + "\n")
-                            sys.stdout.write("@@\t" + str(title) + "," + 
toyield2.encode("utf-8", 'ignore').strip() + "\n")
-                        except KeyError:
-                            pass
-        elif page['ns'] == '120':
-            title = page['title'][10:]
-            text = json.loads(page['text'])
-            label = text['label']['en'].encode("utf-8", 'ignore')
-            cur.execute("""INSERT INTO plabel VALUES (%s, %s, %s)""", 
(int(title), 'en', label))
-    except (KeyError, ValueError, TypeError) as e:
-        sys.stderr.write("Error occurred for page : " + str(title) + ", ns = " 
+ str(page['ns']) + "\n")
-        sys.stderr.write(traceback.format_exc() + "\n")
-    except mdb.Error, e:
-        print "Error %d: %s" % (e.args[0],e.args[1])
-        sys.exit(1)
-
-if __name__ == '__main__':
-    main()
diff --git a/wikiparser/wikiparser_r.py b/wikiparser/wikiparser_r.py
deleted file mode 100644
index 0593c0e..0000000
--- a/wikiparser/wikiparser_r.py
+++ /dev/null
@@ -1,16 +0,0 @@
-#!/usr/bin/python
-
-import sys
-
-def main():
-    listout = open(sys.argv[1], "w")
-    for i in sys.stdin:
-        (key, value) = i.split("\t")
-        if key == "@@":
-            sys.stdout.write(value)
-        else:
-            listout.write(value)
-    listout.close()
-        
-if __name__ == '__main__':
-    main()

-- 
To view, visit https://gerrit.wikimedia.org/r/84463
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: I33d8b0aa1927a07a08b405905f6e65a14632b116
Gerrit-PatchSet: 7
Gerrit-Project: mediawiki/extensions/WikidataEntitySuggester
Gerrit-Branch: master
Gerrit-Owner: Nilesh <[email protected]>
Gerrit-Reviewer: Addshore <[email protected]>
Gerrit-Reviewer: Daniel Kinzler <[email protected]>
Gerrit-Reviewer: Nilesh <[email protected]>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to