diff --git a/tm_admin/tmdb.py b/tm_admin/tmdb.py new file mode 100755 index 00000000..c67d76ed --- /dev/null +++ b/tm_admin/tmdb.py @@ -0,0 +1,238 @@ +#!/usr/bin/python3 + +# Copyright (c) 2022, 2023 Humanitarian OpenStreetMap Team +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +# Humanitarian OpenStreetmap Team +# 1100 13th Street NW Suite 800 Washington, D.C. 20005 +# + +import argparse +import logging +import sys +from sys import argv +import os +from datetime import datetime +from osm_rawdata.postgres import uriParser, PostgresClient +from progress.bar import Bar, PixelBar +from tm_admin.types_tm import Userrole, Mappinglevel + +# Instantiate logger +log = logging.getLogger(__name__) + +import tm_admin as tma +rootdir = tma.__path__[0] + + +class TMImport(object): + def __init__(self, + inuri: str, + outuri: str, + ): + """ + This class contains support to accessing a Tasking Manager database, and importing + it in the TM Admin database. + + Args: + inuri (str): The URI for the TM database + outuri (str): The URI for the TM Admin database + + Returns: + (TMImport): An instance of this class + """ + # The Tasking Manager database + self.tmdb = PostgresClient(inuri) + # The TMAdmin database + self.admindb = PostgresClient(outuri) + self.columns = list() + self.data = list() + + def getColumns(self, + table: str, + ): + """ + Create the data structure for the database table with default values. + + Args: + table str(): The table to get the columns for. + + Returns: + (dict): The table definition. + """ + sql = f"SELECT column_name, data_type,column_default FROM information_schema.columns WHERE table_name = '{table}' ORDER BY dtd_identifier;" + results = self.tmdb.queryLocal(sql) + log.info(f"There are {len(results)} columns in the TM '{table}' table") + table = dict() + for column in results: + # print(f"FIXME: {column}") + if column[2] and column[2][:7] == 'nextval': + # log.debug(f"Dropping SEQUENCE variable '{column[2]}'") + continue + if column[1][:9] == 'timestamp': + table[column[0]] = None + elif column[1][:5] == 'ARRAY': + table[column[0]] = None + elif column[1] == 'boolean': + table[column[0]] = False + elif column[1] == 'bigint' or column[1] == 'integer': + table[column[0]] = 0 + else: + # it's character varying + table[column[0]] = '' + + if len(results) > 0: + self.columns = list(table.keys()) + + return table + + def getAllData(self, + table: str, + ): + """ + Read all the data for a table in the Tasking Manager. The TM database is + relatively small, so load the entire table. + + Args: + table str(): The table to get the columns for. + + Returns: + (list): All the data from the table. + """ + columns = self.getColumns(table) + keys = self.columns + + columns = str(keys)[1:-1].replace("'", "") + sql = f"SELECT {columns} FROM {table}" + results = self.tmdb.queryLocal(sql) + log.info(f"There are {len(results)} records in the TM '{table}' table") + data = list() + for record in results: + table = dict(zip(keys, record)) + data.append(table) + return data + + def writeAllData(self, + data: list, + table: str, + ): + """ + Write the data into table in TM Admin. + + Args: + data (list): The table data from TM + table str(): The table to get the columns for. + """ + bar = Bar('Importing into TMAdmin', max=len(data)) + for record in data: + columns = str(list(record.keys()))[1:-1].replace("'", "") + values = "" + bar.next() + for key, val in record.items(): + # In TM, role is an integer, but it's also an enum, so use the + # correct enum instead of the integer. Python Enums start with + # a 1 instead of 0, but in the TM database it starts at 0, so + # we have to adjust it. + if table == 'users': + if key == 'role': + try: + # The role column occasionally has a bad value that + # doesn't map to the enum + if val < 0: + val = 0 + role = Userrole(val + 1) + values += f"'{role.name}', " + continue + except Exception as e: + log.error(f"{val} {e}") + values += f"'USER_READ_ONLY', " + continue + #elif table == 'organizations': + # pass + # Mapping level is another column that's an int in TM, but also is + # an enum in TM, so use the correct enum instead of the integer. + # Unlike role, this starts with 1. + if key == 'mapping_level': + level = Mappinglevel(val) + values += f"'{level.name}', " + continue + # All tables + if type(val) == str: + tmp = val.replace("'", "'") + values += f"'{tmp}', " + elif type(val) == datetime: + values += f"'{val}', " + elif type(val) == bool: + if val: + values += f"'t', " + else: + values += f"'f', " + elif type(val) == list: + tmp = str(val)[1:-1] + values += f"'{{{tmp}}}', " + elif val is None: + if key == 'projects_mapped': + values += f"'{{}}', " + elif key == 'name': + values += f"'{{}}', " + else: + values += f"NULL, " + else: + values += f"'{val}', " + + sql = f"INSERT INTO {table}({columns}) VALUES({values[:-2]})" + # sql = f"INSERT INTO organizations VALUES({values[:-2]})" + # print(sql) + results = self.admindb.queryLocal(sql) + + bar.finish() + +def main(): + """This main function lets this class be run standalone by a bash script.""" + parser = argparse.ArgumentParser( + prog="tmclient", + formatter_class=argparse.RawDescriptionHelpFormatter, + description="organizationimport", + epilog=""" + Program to import from a TM database into the TMAdmin schema.. + """, + ) + parser.add_argument("-v", "--verbose", nargs="?", const="0", help="verbose output") + parser.add_argument("-i", "--inuri", default='localhost/tm4', help="The URI string for the TM database") + parser.add_argument("-o", "--outuri", default='localhost/tm_admin', help="The URI string for the TM Admin database") + args = parser.parse_args() + + # if len(argv) <= 1: + # parser.print_help() + # quit() + + # if verbose, dump to the terminal. + log_level = os.getenv("LOG_LEVEL", default="INFO") + if args.verbose is not None: + log_level = logging.DEBUG + + logging.basicConfig( + level=log_level, + format=("%(asctime)s.%(msecs)03d [%(levelname)s] " "%(name)s | %(funcName)s:%(lineno)d | %(message)s"), + datefmt="%y-%m-%d %H:%M:%S", + stream=sys.stdout, + ) + + doit = TMImport(args.inuri, args.outuri) + data = doit.getAllData('users') + doit.writeAllData(data, 'users') + +if __name__ == "__main__": + """This is just a hook so this file can be run standalone during development.""" + main()