563 lines
20 KiB
Python
563 lines
20 KiB
Python
__author__ = "Wendell Jones"
|
|
|
|
import os
|
|
import sqlalchemy
|
|
from sqlalchemy import text
|
|
from sqlalchemy import MetaData
|
|
from sqlalchemy.ext.automap import automap_base
|
|
from sqlalchemy.orm import Session
|
|
from db.Tables import Createtables, CreateTempTables, Tables as dbTables
|
|
from db.mssql.Merger import Charactermerge as MSCharactermerge
|
|
from db.mssql.Merger import Jsonmerge as MSJsonmerge
|
|
from db.mssql.Merger import SQLGenerate as MSSQLGenerate
|
|
from db.mssql.Merger import Episodemerge as MSEpisodemerge
|
|
from db.pgsql.Merger import Charactermerge as PGCharactermerge
|
|
from db.pgsql.Merger import Updatemerge as PGUpdatemerge
|
|
from db.mssql.Merger import Jsonmerge as PGJsonmerge
|
|
from db.pgsql.Merger import SQLGenerate as PGSQLGenerate
|
|
from db.pgsql.Merger import Episodemerge as PGEpisodemerge
|
|
import api.tvmaze.episodes
|
|
import api.tvmaze.cast
|
|
import api.tvmaze.series
|
|
from pymongo import MongoClient, ReplaceOne, InsertOne
|
|
from tqdm import tqdm
|
|
from datetime import datetime
|
|
|
|
Createtables = Createtables
|
|
CreateTempTables = CreateTempTables
|
|
dbTables = dbTables
|
|
|
|
|
|
class settype(object):
|
|
|
|
def __init__(self, dbtype, apitype, options):
|
|
self.dbclass = {}
|
|
self.dbengine = {}
|
|
self.apiengine = {}
|
|
self.load(dbtype)
|
|
self.dbcreate(dbtype, options)
|
|
self.setapi(apitype)
|
|
self.get_data()
|
|
|
|
def load(self, dbtype):
|
|
dbtype = dbtype.lower()
|
|
if dbtype in ["pgsql", "mysql"]:
|
|
Updatemerge = PGUpdatemerge
|
|
Jsonmerge = PGJsonmerge
|
|
Episodemerge = PGEpisodemerge
|
|
Charactermerge = PGCharactermerge
|
|
merger = PGSQLGenerate
|
|
elif dbtype == "mssql":
|
|
Jsonmerge = MSJsonmerge
|
|
Charactermerge = MSCharactermerge
|
|
merger = MSSQLGenerate
|
|
Episodemerge = MSEpisodemerge
|
|
|
|
self.dbclass["jmerge"] = Jsonmerge()
|
|
self.dbclass["cmerge"] = Charactermerge()
|
|
self.dbclass["Createtables"] = Createtables
|
|
self.dbclass["CreateTemptables"] = CreateTempTables
|
|
self.dbclass["Tables"] = Createtables
|
|
self.dbclass["merger"] = merger()
|
|
|
|
if dbtype == "pgsql":
|
|
self.dbclass["Updatemerge"] = Updatemerge
|
|
self.dbclass["Episodemerge"] = Episodemerge
|
|
elif dbtype == "mssql":
|
|
self.dbclass["Charactermerge"] = Charactermerge
|
|
|
|
def dbcreate(self, dbtype, options):
|
|
updatesBase = automap_base()
|
|
dboBase = automap_base()
|
|
connection_string = None
|
|
if dbtype.lower() == "mssql":
|
|
connection_string = (
|
|
"mssql+pymssql://"
|
|
+ options["mssqlusername"]
|
|
+ ":"
|
|
+ options["mssqlpassword"]
|
|
+ "@"
|
|
+ options["hostname"]
|
|
+ ":"
|
|
+ options["mssqlport"]
|
|
+ "/"
|
|
+ options["dbname"]
|
|
)
|
|
elif dbtype.lower() == "pgsql":
|
|
connection_string = (
|
|
"postgresql+psycopg2://"
|
|
+ options["pgsqlusername"]
|
|
+ ":"
|
|
+ options["pgsqlpassword"]
|
|
+ "@"
|
|
+ options["hostname"]
|
|
+ ":"
|
|
+ options["pgsqlport"]
|
|
+ "/"
|
|
+ options["dbname"]
|
|
)
|
|
elif dbtype.lower() == "mysql":
|
|
connection_string = (
|
|
"mysql+pymysql://"
|
|
+ options["mysqlusername"]
|
|
+ ":"
|
|
+ options["mysqlpassword"]
|
|
+ "@"
|
|
+ options["hostname"]
|
|
+ ":"
|
|
+ options["mysqlport"]
|
|
+ "/"
|
|
+ options["dbname"]
|
|
)
|
|
engine = sqlalchemy.create_engine(
|
|
connection_string, pool_size=20, max_overflow=20, pool_recycle=15
|
|
)
|
|
currentDir = os.path.dirname(os.path.abspath(__file__))
|
|
currentDir = os.path.dirname(currentDir)
|
|
ddl_file_path = f"{currentDir}/db/ddl/recreate_updates_schema.sql"
|
|
with open(ddl_file_path, "r") as file:
|
|
sql = file.read()
|
|
file
|
|
with engine.connect() as conn:
|
|
with conn as cursor:
|
|
cursor.execute(text(sql))
|
|
updatesBase.prepare(engine, reflect=True, schema="updates")
|
|
dboBase.prepare(engine, reflect=True, schema="dbo")
|
|
metadata = sqlalchemy.MetaData("updates")
|
|
metadata.reflect(bind=engine)
|
|
dboMetadata = sqlalchemy.MetaData("dbo")
|
|
dboMetadata.reflect(bind=engine)
|
|
session = Session(engine)
|
|
self.dbengine["engine"] = engine
|
|
self.dbengine["metadata"] = metadata
|
|
self.dbengine["dbometadata"] = dboMetadata
|
|
self.dbengine["updatesBase"] = updatesBase
|
|
self.dbengine["dboBase"] = dboBase
|
|
self.dbengine["session"] = session
|
|
return self.dbengine
|
|
|
|
def setapi(self, apitype):
|
|
if apitype.lower() == "tvmaze":
|
|
self.apiengine["sprocess"] = api.tvmaze.series
|
|
self.apiengine["cprocess"] = api.tvmaze.cast
|
|
self.apiengine["eprocess"] = api.tvmaze.episodes
|
|
return self.apiengine
|
|
elif apitype.lower() == "thetvdb":
|
|
pass
|
|
|
|
def reflect_tables(self, engine, Metadata):
|
|
meta = MetaData()
|
|
meta.reflect(bind=engine)
|
|
return meta.tables
|
|
|
|
def get_data(self):
|
|
return self.dbclass, self.dbengine, self.apiengine
|
|
|
|
def __del__(self):
|
|
pass
|
|
|
|
|
|
class dbmongo(object):
|
|
|
|
global mgclient
|
|
global mgseries
|
|
global mgepisodes
|
|
global mgactors
|
|
global mgcharacters
|
|
global mgcrew
|
|
|
|
def bulk_upsert_by_id(self, collection, docs, batch_size=1000):
|
|
ops = []
|
|
for doc in docs:
|
|
if "id" not in doc:
|
|
# skip documents without an `id` field
|
|
continue
|
|
# replace existing document with same `id`, or insert if it does not exist
|
|
ops.append(ReplaceOne({"id": doc["id"]}, doc, upsert=True))
|
|
if len(ops) >= batch_size:
|
|
collection.bulk_write(ops, ordered=False)
|
|
ops = []
|
|
if ops:
|
|
collection.bulk_write(ops, ordered=False)
|
|
|
|
def convert_updated_time(self, timestamp):
|
|
new_timestamp = datetime.fromtimestamp(timestamp)
|
|
update_time = str(new_timestamp).split(".")[0]
|
|
return update_time
|
|
|
|
def __init__(self, options):
|
|
host = options["mghostname"]
|
|
port = options["mgport"]
|
|
print(f"Connecting to mongodb at {host}:{port}")
|
|
try:
|
|
self.connection_string = (
|
|
f"mongodb://{options['mghostname']}:{options['mgport']}"
|
|
)
|
|
self.mgclient = MongoClient(self.connection_string)
|
|
self.mgdb = self.mgclient[options["mgdbname"]]
|
|
except BaseException as e:
|
|
print(f"Unable to connect to mongodb.{e}\nExitting!")
|
|
exit()
|
|
else:
|
|
self.mgseries = self.mgdb["series"]
|
|
self.mgseries.create_index("id", unique=True)
|
|
self.mgepisodes = self.mgdb["episodes"]
|
|
self.mgepisodes.create_index("id", unique=True)
|
|
self.mgactors = self.mgdb["actors"]
|
|
self.mgactors.create_index("id", unique=True)
|
|
self.mgcharacters = self.mgdb["characters"]
|
|
self.mgcharacters.create_index("id", unique=True)
|
|
self.mgcrew = self.mgdb["crew"]
|
|
self.mgcrew.create_index("id", unique=True)
|
|
print(
|
|
f"Connection to mongodb at {options['mghostname']}:"
|
|
f"{options['mgport']} successful."
|
|
)
|
|
|
|
def load_json_file(self, cachefile, jget, lprint, seriesid, datatype):
|
|
try:
|
|
with open(cachefile, "r") as file:
|
|
jsondata = file.read()
|
|
datastring = jget.load(jsondata)
|
|
data = jget.jconvert(datastring)
|
|
return data
|
|
except Exception:
|
|
lprint.logprint(
|
|
"info", f"Unable to open {datatype} file for {seriesid}"
|
|
)
|
|
|
|
def process_series_data(
|
|
self,
|
|
seriesdata,
|
|
jget,
|
|
dbExec,
|
|
dbengine,
|
|
dboBase,
|
|
lprint,
|
|
countrydictionary,
|
|
seriesid,
|
|
):
|
|
try:
|
|
seriesresults, networkchannel, country, webchannel = (
|
|
jget.processSeriesJson(seriesdata)
|
|
)
|
|
except Exception:
|
|
lprint.logprint(
|
|
"info", f"Unable to process series file for {seriesid}"
|
|
)
|
|
else:
|
|
update_time = self.convert_updated_time(seriesdata["updated"])
|
|
seriesdata["updated"] = update_time
|
|
self.update_country_data(
|
|
country,
|
|
countrydictionary,
|
|
dbExec,
|
|
dbengine,
|
|
dboBase,
|
|
dbengine["dbometadata"],
|
|
lprint,
|
|
)
|
|
self.update_network_data(
|
|
networkchannel,
|
|
countrydictionary,
|
|
seriesresults,
|
|
dbExec,
|
|
dbengine,
|
|
dboBase,
|
|
lprint,
|
|
)
|
|
self.update_webchannel_data(
|
|
webchannel,
|
|
countrydictionary,
|
|
seriesresults,
|
|
dbExec,
|
|
dbengine,
|
|
dboBase,
|
|
lprint,
|
|
)
|
|
#self.bulk_upsert_by_id(self.mgseries, seriesdata)
|
|
self.insert_or_update_mongo(
|
|
self.mgseries, seriesdata, "id"
|
|
)
|
|
|
|
def process_cast_data(self, persondata, characterdata, seriesid, lprint):
|
|
self.bulk_upsert_by_id(self.mgactors, persondata)
|
|
self.bulk_upsert_by_id(self.mgcharacters, characterdata)
|
|
|
|
def process_crew_data(self, crewdata, seriesid, jget, lprint):
|
|
self.bulk_upsert_by_id(self.mgcrew, crewdata)
|
|
|
|
def update_country_data(
|
|
self, country, countrydictionary, dbExec, dbengine, dbBase,
|
|
metadata, lprint
|
|
):
|
|
if len(country) > 0:
|
|
try:
|
|
selectquery = (
|
|
f"select row_id, country_name from dbo.countrydata where "
|
|
f"country_name = '{country['name'].replace("'", "''")}'"
|
|
)
|
|
countrylisting = dbExec.rawsql_select(
|
|
dbengine["engine"], selectquery, lprint
|
|
)
|
|
for countryname in countrylisting:
|
|
countrydictionary[countryname[1]] = countryname[0]
|
|
except Exception as e:
|
|
lprint.logprint(
|
|
"info", (
|
|
(
|
|
f'Unable to process country data for '
|
|
f'{country["name"]}. {e}'
|
|
)
|
|
)
|
|
)
|
|
else:
|
|
if len(countrylisting) > 0:
|
|
pass
|
|
else:
|
|
insert_sql = (
|
|
(
|
|
f"insert into dbo.countrydata "
|
|
"(country_name, country_code, country_timezone) "
|
|
f"values ('{country['name'].replace("'", "''")}', "
|
|
f"'{country['code'].replace("'", "''")}', "
|
|
f"'{country['timezone'].replace("'", "''")}')"
|
|
)
|
|
)
|
|
dbExec.rawsql_insert(
|
|
dbengine["engine"], insert_sql, lprint
|
|
)
|
|
|
|
def update_network_data(
|
|
self,
|
|
networkchannel,
|
|
countrydictionary,
|
|
seriesresults,
|
|
dbExec,
|
|
dbengine,
|
|
dboBase,
|
|
lprint,
|
|
):
|
|
if len(networkchannel) > 0:
|
|
try:
|
|
networkchannel["countryid"] = countrydictionary[
|
|
networkchannel["country"]["name"]
|
|
]
|
|
seriesresults["networkid"] = networkchannel["id"]
|
|
except Exception:
|
|
pass
|
|
|
|
def update_webchannel_data(
|
|
self,
|
|
webchannel,
|
|
countrydictionary,
|
|
seriesresults,
|
|
dbExec,
|
|
dbengine,
|
|
dboBase,
|
|
lprint,
|
|
):
|
|
if len(webchannel) > 0:
|
|
try:
|
|
webchannel["countryid"] = countrydictionary[
|
|
webchannel["country"]["name"]
|
|
]
|
|
seriesresults["webchannelid"] = webchannel["id"]
|
|
except Exception:
|
|
pass
|
|
seriesresults["seriesid"] = seriesresults["id"]
|
|
del seriesresults["webchannel"]
|
|
|
|
def insert_or_update_mongo(self, collection, data, unique_key):
|
|
try:
|
|
collection.insert_one(data)
|
|
except Exception:
|
|
filter_query = {unique_key: data[unique_key]}
|
|
new_values = {"$set": data}
|
|
del new_values["$set"]["_id"]
|
|
collection.update_one(filter_query, new_values)
|
|
|
|
def update_mongo(
|
|
self,
|
|
updateList,
|
|
directories,
|
|
jget,
|
|
countrydictionary,
|
|
dbExec,
|
|
dbengine,
|
|
dboBase,
|
|
lprint,
|
|
):
|
|
print("Now inserting data into tables.")
|
|
series_failed_count = 0
|
|
episode_failed_count = 0
|
|
cast_failed_count = 0
|
|
crew_failed_count = 0
|
|
processed_count = 0
|
|
failed_file_count = 0
|
|
|
|
def process_data(
|
|
file_path, data_type, failed_file_count, lprint
|
|
):
|
|
"""Helper function to load and process data."""
|
|
data = self.load_json_file(
|
|
file_path, jget, lprint, seriesid, data_type
|
|
)
|
|
if data:
|
|
if "episode" in file_path:
|
|
try:
|
|
for item in data:
|
|
item["seriesid"] = seriesid
|
|
except Exception as e:
|
|
lprint.logprint(
|
|
"info", f"Unable to add {data_type} to data. {e}"
|
|
)
|
|
if "cast" in file_path:
|
|
persondata = []
|
|
characterdata = []
|
|
for item in data:
|
|
update_time = self.convert_updated_time(item["person"]["updated"])
|
|
item["person"]["updated"] = update_time
|
|
if "person" in item:
|
|
item["person"]["seriesid"] = seriesid
|
|
persondata.append(item["person"])
|
|
if "character" in item:
|
|
item["character"]["seriesid"] = seriesid
|
|
characterdata.append(item["character"])
|
|
|
|
try:
|
|
self.bulk_upsert_by_id(self.mgactors, persondata)
|
|
self.bulk_upsert_by_id(self.mgcharacters, characterdata)
|
|
failed_file_count = "N/A"
|
|
except Exception as e:
|
|
failed_file_count = 1
|
|
lprint.logprint(
|
|
"info",
|
|
f"Unable to process {data_type} data. {e}"
|
|
)
|
|
if "crew" in file_path:
|
|
try:
|
|
crewdata = []
|
|
except Exception as e:
|
|
lprint.logprint(
|
|
"info",
|
|
f"Unable to add {data_type} data. {e}"
|
|
)
|
|
else:
|
|
for item in data:
|
|
item["person"]["seriesid"] = seriesid
|
|
item["person"]["type"] = item["type"]
|
|
update_time =self.convert_updated_time(item["person"]["updated"])
|
|
if update_time:
|
|
item["person"]["updated"] = str(update_time).split(".")[0]
|
|
if "person" in item:
|
|
crewdata.append(item["person"])
|
|
try:
|
|
self.bulk_upsert_by_id(self.mgcrew, crewdata)
|
|
failed_file_count = "N/A"
|
|
return failed_file_count
|
|
except Exception as e:
|
|
failed_file_count = 1
|
|
lprint.logprint(
|
|
"info",
|
|
f"Unable to process {data_type} data.{e}"
|
|
)
|
|
return failed_file_count
|
|
return data
|
|
for series in tqdm(
|
|
range(len(updateList)), desc="Update MongoDB", unit="series"
|
|
):
|
|
seriesid = updateList[series]
|
|
seriescachefile = f'{directories["SERIESDIR"]}{seriesid}.json'
|
|
episodecachefile = f'{directories["EPISODEDIR"]}{seriesid}.json'
|
|
castcachefile = f'{directories["CASTDIR"]}{seriesid}.json'
|
|
crewcachefile = f'{directories["CREWDIR"]}{seriesid}.json'
|
|
|
|
# Process series data
|
|
seriesdata = self.load_json_file(
|
|
seriescachefile, jget, lprint, seriesid, "series"
|
|
)
|
|
if seriesdata:
|
|
try:
|
|
self.process_series_data(
|
|
seriesdata,
|
|
jget,
|
|
dbExec,
|
|
dbengine,
|
|
dboBase,
|
|
lprint,
|
|
countrydictionary,
|
|
seriesid,
|
|
)
|
|
self.bulk_upsert_by_id(self.mgseries, seriesdata)
|
|
except Exception as e:
|
|
series_failed_count += 1
|
|
lprint.logprint(
|
|
"info",
|
|
(
|
|
f"Unable to process series data for seriesid "
|
|
f"{seriesid}. {e}"
|
|
),
|
|
)
|
|
else:
|
|
processed_count += 1
|
|
|
|
fcount = process_data(
|
|
episodecachefile,
|
|
"episode",
|
|
failed_file_count,
|
|
lprint,
|
|
)
|
|
if isinstance(fcount, int):
|
|
episode_failed_count = episode_failed_count + fcount
|
|
else:
|
|
try:
|
|
self.bulk_upsert_by_id(self.mgepisodes, fcount)
|
|
# for episode in fcount:
|
|
# self.insert_or_update_mongo(
|
|
# self.mgepisodes, episode, "id"
|
|
# )
|
|
except Exception as e:
|
|
lprint.logprint(
|
|
"info",
|
|
(
|
|
f"Unable to insert episode data for seriesid "
|
|
f"{seriesid}. {e}"
|
|
),
|
|
)
|
|
fcount = process_data(
|
|
castcachefile, "cast",failed_file_count, lprint
|
|
)
|
|
if isinstance(fcount, int):
|
|
cast_failed_count = cast_failed_count + fcount
|
|
if os.path.isfile(crewcachefile):
|
|
fcount = process_data(
|
|
crewcachefile, "crew",
|
|
failed_file_count, lprint
|
|
)
|
|
if isinstance(fcount, int):
|
|
crew_failed_count = crew_failed_count + fcount
|
|
else:
|
|
try:
|
|
for crew in fcount:
|
|
self.insert_or_update_mongo(
|
|
self.mgcrew, crew, "id"
|
|
)
|
|
except Exception as e:
|
|
lprint.logprint(
|
|
"info",
|
|
(
|
|
f"Unable to insert crew data for seriesid "
|
|
f"{seriesid}. {e}"
|
|
),
|
|
)
|
|
|
|
print(f"Processing complete. {processed_count} series processed.")
|
|
print(f"Failed to load {series_failed_count} series files.")
|
|
print(f"Failed to load {episode_failed_count} episode files.")
|
|
print(f"Failed to load {crew_failed_count} crew files.")
|
|
print(f"Failed to load {cast_failed_count} cast files.")
|
|
|
|
def __del__(self):
|
|
pass
|