do $$
import re, subprocess, tempfile

# pattern to extract just the function header from pg_get_functiondef result
aspat = re.compile("^(.*?\nAS )", re.DOTALL)
# pattern for replacing LANGUAGE portion
langpat = re.compile("\n LANGUAGE plpython2?u\n")

# collect info about functions to update
rv = plpy.execute("""
select pg_catalog.pg_get_functiondef(p.oid) as fd, prosrc as body
from pg_catalog.pg_proc p join pg_catalog.pg_language l on p.prolang = l.oid
where lanname in ('plpythonu', 'plpython2u')
""")

# For some benighted reason, lib2to3 has exactly no documented API,
# so we must use the command-line API "2to3" instead.  Adjust path
# and optional arguments for it here.
cmd2to3 = "2to3 --no-diffs"

# Make a temp directory to hold the file for it to work on.
with tempfile.TemporaryDirectory() as tmpdirname:

    # process each function
    for r in rv:
        # extract everything but the body from pg_get_functiondef result
        m = aspat.match(r["fd"])
        if not m:
            raise ValueError('unexpected match failure')
        fheader = m.group(1)

        # replace the language clause
        fheader = langpat.sub("\n LANGUAGE plpython3u\n", fheader, 1)

        # put body in a temp file so we can apply 2to3
        f = open(tmpdirname + "/temp.py", mode = 'w')
        f.write(r["body"])
        f.close()

        # apply 2to3 to body
        subprocess.check_call(cmd2to3 + " -w " + tmpdirname + "/temp.py", shell=True)
        f = open(tmpdirname + "/temp.py", mode = 'r')
        fbody = f.read()
        f.close()

        # construct and execute SQL command to replace the function
        newstmt = fheader + plpy.quote_literal(fbody)
        # uncomment this for debugging purposes:
        # plpy.info(newstmt)
        plpy.execute(newstmt)

        # commit after each successful replacement, in case a later one fails
        plpy.commit()
$$
language plpython3u;
