2021-07-08 22:31:29 +00:00
#!/usr/bin/env python
__doc__ = """
(c) 2018 - 2021 data-transport
steve@the-phi.com, The Phi Technology LLC
https://dev.the-phi.com/git/steve/data-transport.git
This program performs ETL between 9 supported data sources : Couchdb, Mongodb, Mysql, Mariadb, PostgreSQL, Netezza,Redshift, Sqlite, File
2021-11-18 21:21:26 +00:00
LICENSE (MIT)
Copyright 2016-2020, The Phi Technology LLC
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
2021-07-08 22:31:29 +00:00
"""
import pandas as pd
import numpy as np
import json
import sys
import transport
import time
from multiprocessing import Process
2024-06-10 05:42:42 +00:00
2023-09-30 01:27:53 +00:00
import os
2023-09-30 05:18:37 +00:00
import transport
2024-12-31 18:20:22 +00:00
# from transport import etl
from transport.iowrapper import IETL
2024-03-28 20:34:39 +00:00
# from transport import providers
2024-06-10 05:42:42 +00:00
import typer
from typing_extensions import Annotated
from typing import Optional
import time
2024-06-14 19:14:12 +00:00
from termcolor import colored
2024-12-31 18:20:22 +00:00
from enum import Enum
from rich import print
2023-09-30 01:27:53 +00:00
app = typer.Typer()
2024-12-31 18:32:14 +00:00
app_e = typer.Typer() #-- handles etl (run, generate)
app_x = typer.Typer() #-- handles plugins (list,add, test)
app_i = typer.Typer() #-- handles information (version, license)
app_r = typer.Typer() #-- handles registry
2024-06-14 19:14:12 +00:00
REGISTRY_PATH=os.sep.join([os.environ['HOME'],'.data-transport'])
REGISTRY_FILE= 'transport-registry.json'
2024-12-31 18:20:22 +00:00
CHECK_MARK = '[ [green]\u2713[/green] ]' #' '.join(['[',colored(u'\u2713', 'green'),']'])
TIMES_MARK= '[ [red]\u2717[/red] ]' #' '.join(['[',colored(u'\u2717','red'),']'])
2023-09-30 01:27:53 +00:00
# @app.command()
def help() :
print (__doc__)
def wait(jobs):
while jobs :
jobs = [thread for thread in jobs if thread.is_alive()]
time.sleep(1)
2024-12-31 18:20:22 +00:00
def wait (jobs):
while jobs :
jobs = [pthread for pthread in jobs if pthread.is_alive()]
2023-09-30 01:27:53 +00:00
2024-12-31 18:32:14 +00:00
@app_e.command(name="run")
2024-06-10 05:42:42 +00:00
def apply (path:Annotated[str,typer.Argument(help="path of the configuration file")],
2024-12-31 18:20:22 +00:00
index:int = typer.Option(default= None, help="index of the item of interest, otherwise everything in the file will be processed"),
batch:int = typer.Option(default=5, help="The number of parallel processes to run at once")
):
2024-04-03 01:59:01 +00:00
"""
2024-06-14 19:14:12 +00:00
This function applies data transport ETL feature to read data from one source to write it one or several others
2024-04-03 01:59:01 +00:00
"""
2024-06-10 05:42:42 +00:00
# _proxy = lambda _object: _object.write(_object.read())
2023-09-30 01:27:53 +00:00
if os.path.exists(path):
file = open(path)
_config = json.loads (file.read() )
file.close()
2024-12-31 18:20:22 +00:00
if index is not None:
2024-06-10 05:42:42 +00:00
_config = [_config[ int(index)]]
2024-12-31 18:20:22 +00:00
jobs = []
2024-06-10 05:42:42 +00:00
for _args in _config :
2024-12-31 18:20:22 +00:00
# pthread = etl.instance(**_args) #-- automatically starts the process
def bootup ():
_worker = IETL(**_args)
_worker.run()
pthread = Process(target=bootup)
pthread.start()
2024-06-10 05:42:42 +00:00
jobs.append(pthread)
2024-12-31 18:20:22 +00:00
if len(jobs) == batch :
wait(jobs)
jobs = []
if jobs :
wait (jobs)
2024-06-10 05:42:42 +00:00
#
2024-12-31 18:20:22 +00:00
# @TODO: Log the number of processes started and estfrom transport impfrom transport impimated time
# while jobs :
# jobs = [pthread for pthread in jobs if pthread.is_alive()]
# time.sleep(1)
2024-06-10 05:42:42 +00:00
#
# @TODO: Log the job termination here ...
2024-12-31 18:20:22 +00:00
@app_i.command(name="supported")
2024-06-10 05:42:42 +00:00
def supported (format:Annotated[str,typer.Argument(help="format of the output, supported formats are (list,table,json)")]="table") :
2024-03-28 20:34:39 +00:00
"""
2024-12-31 18:20:22 +00:00
This function will print supported database technologies
2024-03-28 20:34:39 +00:00
"""
_df = (transport.supported())
2024-04-01 23:37:47 +00:00
if format in ['list','json'] :
print (json.dumps(_df.to_dict(orient="list")))
else:
print (_df)
print ()
2023-09-30 01:27:53 +00:00
2024-12-31 18:20:22 +00:00
@app_i.command(name="license")
def info():
2024-06-10 05:42:42 +00:00
"""
This function will display version and license information
"""
2024-12-31 18:20:22 +00:00
print (f'[bold] {transport.__app_name__} ,version {transport.__version__}[/bold]')
print ()
2024-06-10 05:42:42 +00:00
print (transport.__license__)
2024-12-31 18:32:14 +00:00
@app_e.command()
2024-06-10 05:42:42 +00:00
def generate (path:Annotated[str,typer.Argument(help="path of the ETL configuration file template (name included)")]):
2024-06-14 20:30:09 +00:00
"""
This function will generate a configuration template to give a sense of how to create one
"""
_config = [
{
"source":{"provider":"http","url":"https://raw.githubusercontent.com/codeforamerica/ohana-api/master/data/sample-csv/addresses.csv"},
"target":
2024-12-31 18:20:22 +00:00
[{"provider":"files","path":"addresses.csv","delimiter":","},{"provider":"sqlite3","database":"sample.db3","table":"addresses"}]
2023-09-30 05:18:37 +00:00
}
]
2024-06-14 20:30:09 +00:00
file = open(path,'w')
file.write(json.dumps(_config))
file.close()
2024-12-31 18:20:22 +00:00
print (f"""{CHECK_MARK} Successfully generated a template ETL file at [bold]{path}[/bold]""" )
2024-06-14 20:30:09 +00:00
print ("""NOTE: Each line (source or target) is the content of an auth-file""")
2024-06-14 19:14:12 +00:00
2024-12-31 18:20:22 +00:00
@app_r.command(name="reset")
2024-06-14 19:14:12 +00:00
def initregistry (email:Annotated[str,typer.Argument(help="email")],
path:str=typer.Option(default=REGISTRY_PATH,help="path or location of the configuration file"),
override:bool=typer.Option(default=False,help="override existing configuration or not")):
"""
This functiion will initialize the registry and have both application and calling code loading the database parameters by a label
"""
try:
transport.registry.init(email=email, path=path, override=override)
2024-12-31 18:20:22 +00:00
_msg = f"""{CHECK_MARK} Successfully wrote configuration to [bold]{path}[/bold] from [bold]{email}[/bold]"""
2024-06-14 19:14:12 +00:00
except Exception as e:
_msg = f"{TIMES_MARK} {e}"
print (_msg)
print ()
2024-12-31 18:20:22 +00:00
@app_r.command(name="add")
2024-06-14 19:14:12 +00:00
def register (label:Annotated[str,typer.Argument(help="unique label that will be used to load the parameters of the database")],
auth_file:Annotated[str,typer.Argument(help="path of the auth_file")],
default:bool=typer.Option(default=False,help="set the auth_file as default"),
path:str=typer.Option(default=REGISTRY_PATH,help="path of the data-transport registry file")):
"""
2024-12-31 18:20:22 +00:00
This function add a database label for a given auth-file. which allows access to the database using a label of your choice.
2024-06-14 19:14:12 +00:00
"""
try:
2024-06-14 20:30:09 +00:00
if transport.registry.exists(path) :
transport.registry.set(label=label,auth_file=auth_file, default=default, path=path)
2024-12-31 18:20:22 +00:00
_msg = f"""{CHECK_MARK} Successfully added label [bold]"{label}"[/bold] to data-transport registry"""
2024-06-14 20:30:09 +00:00
else:
_msg = f"""{TIMES_MARK} Registry is not initialized, please initialize the registry (check help)"""
2024-06-14 19:14:12 +00:00
except Exception as e:
_msg = f"""{TIMES_MARK} {e}"""
print (_msg)
pass
2024-12-31 18:20:22 +00:00
@app_x.command(name='add')
def register_plugs (
alias:Annotated[str,typer.Argument(help="unique alias fo the file being registered")],
path:Annotated[str,typer.Argument(help="path of the python file, that contains functions")]
):
"""
This function will register a file and the functions within will be refrences <alias>.<function> in a configuration file
"""
transport.registry.plugins.init()
_log = transport.registry.plugins.add(alias,path)
_mark = TIMES_MARK if not _log else CHECK_MARK
_msg = f"""Could NOT add the [bold]{alias}[/bold]to the registry""" if not _log else f""" successfully added {alias}, {len(_log)} functions added"""
print (f"""{_mark} {_msg}""")
@app_x.command(name="list")
def registry_list ():
transport.registry.plugins.init()
_d = []
for _alias in transport.registry.plugins._data :
_data = transport.registry.plugins._data[_alias]
_d += [{'alias':_alias,"plugin-count":len(_data['content']),'e.g':'@'.join([_alias,_data['content'][0]]),'plugins':json.dumps(_data['content'])}]
if _d:
print (pd.DataFrame(_d))
else:
print (f"""{TIMES_MARK}, Plugin registry is not available or needs initialization""")
@app_x.command(name="test")
def registry_test (key):
"""
This function allows to test syntax for a plugin i.e in terms of alias@function
"""
_item = transport.registry.plugins.has(key=key)
if _item :
del _item['pointer']
print (f"""{CHECK_MARK} successfully loaded \033[1m{key}\033[0m found, version {_item['version']}""")
print (pd.DataFrame([_item]))
else:
print (f"{TIMES_MARK} unable to load \033[1m{key}\033[0m. Make sure it is registered")
2024-12-31 18:32:14 +00:00
app.add_typer(app_e,name='etl',help="This function will run etl or generate a template etl configuration file")
2024-12-31 18:20:22 +00:00
app.add_typer(app_r,name='registry',help='This function allows labeling database access information')
app.add_typer(app_i,name="info",help="This function will print either license or supported database technologies")
app.add_typer(app_x, name="plugins",help="This function enables add/list/test of plugins in the registry")
2023-09-30 05:18:37 +00:00
if __name__ == '__main__' :
app()
2024-06-14 19:14:12 +00:00
2023-09-30 01:27:53 +00:00