from typing import Optional,Any,Generator
from typing_extensions import Self
from dataclasses import dataclass,fields,field
from datetime import datetime
import MySQLdb, MySQLdb.cursors
from Utils import toInt as i
from pprint import pprint
from json import dumps
import Utils
import sys
from Utils import Debug,toInt,toList,toStr

_DEBUG = True


class DB_Configs():
	data = {
			"pp": {
				"host":"localhost",
				"user":"build",
				"passwd":"buildweb31337",
				"db":"passport"
			}
	}
	@classmethod
	def get(cls,key:str) -> dict[str,str] | None:
		return cls.data.get(key)

	@classmethod
	def default(cls) -> dict[str,str] | None:
		return cls.data.get("pp")


class DB():
	_debug:bool = False
	error:str = ""
	rows_affected:int = 0
	last_id:Any

	def __init__(self,config:Optional[dict]=None):
		config = config or DB_Configs.default() or {}
		self.db = MySQLdb.connect(host=config["host"], user=config["user"], passwd=config["passwd"], db=config["db"], cursorclass=MySQLdb.cursors.DictCursor,use_unicode=True)
		self.db.set_character_set('utf8')
		self.dbName = config["db"]
		self.cur = None
		self._debug = False

	def debug(self, sql):
		if self._debug:
			Utils.Debug(title="SQL",data=sql)

	@classmethod
	def fuzzyr(cls, val):
		return f"{val}%"

	@classmethod
	def fuzzyl(cls, val):
		return f"%{val}"

	@classmethod
	def fuzzy(cls, val):
		return f"%{val}%"

	def escape(self,val):
		return self.db.escape(val)

	def close(self):
		if self.db and self.db.open:
			self.db.close()

	def commit(self):
		self.db.commit()

	def get_keys(self,table:str):
		keys = []
		fields = []
		for row in self.query(sql=f"describe {table}"):
			fields.append(row.get("Field"))
			if row.get("Key") == "PRI":
				keys.append(row.get("Field"))
		return (keys,fields)

	def query(self, sql:str, values:Optional[list]=None,commit:bool=False, debug:bool=False):
		cur = self.db.cursor(MySQLdb.cursors.DictCursor)
		self.Error = None
		self.rows_affected = 0
		values = toList(values)
		try:
			if values:
				cur.execute(sql, values)
			else:
				cur.execute(sql)
		except Exception as e:
			self.error = str(e)
			Utils.Debug(title="Query Debug",data={"Error":self.Error,"sql":sql,"parms":values,"records":cur.rowcount},debug=debug)
			return []
		if commit:
			self.db.commit()
		self.rows_affected = cur.rowcount
		self.last_id = cur.lastrowid
		Utils.Debug(title="Query Debug",data={"sql":sql,"parms":values,"commit":commit,"records":cur.rowcount},debug=debug)

		for row in cur.fetchall():
			yield row

	def cursor(self,cursor_type:Any = MySQLdb.cursors.DictCursor):
		return self.db.cursor(cursor_type)

class data_row(dict):	
	def __str__(self):
		return self.__repr__()
	
	def __repr__(self):
		return Utils.toJSON(self)

@dataclass(slots=True)
class user_row():
	id: int
	user: str
	pw: str

	def __getitem__(self,key):
		return getattr(self,key)

class row(dict):
	pass

class row_ext():
	_db: DB
	_table:str
	_data:data_row

	def __init__(self,table:str,db:DB,data:Any,row_cls:str = "data_row"):
		self._table = table
		self._db = db

		if row_cls:			
			self._data = getattr(sys.modules[__name__], row_cls)(**data)
		else:
			self._data = data


	def __getitem__(self,key):
		return getattr(self._data,key)


	def __getattribute__(self,key:str):		
		try:
			return object.__getattribute__(self,key)
		except:
			try:
				return getattr(self._data,key)
			except:
				return ""

	def get(self,key:str,default:Any = "") -> Any:
		try:
			return self._data[key]
		except:
			return default
	
	def items(self):
		return self._data.items()
	
	def __str__(self) -> str:
		return Utils.toJSON(self._data)
	
	def __repr__(self) -> str:	
		return Utils.toJSON(self)

class Row(dict):
	_table:str = ""
	_db:DB
	_fields:Optional[list] = None
	_keys:Optional[list] = None
	
	def __init__(self,table:str,data:dict,db:DB):
		self.table = table
		self._db = db
		super().__init__(data)

	@property
	def table(self):
		return self._table

	@table.setter
	def table(self,table:str) -> str:
		self.table = table
		return self._table 

	@property
	def fields(self) -> list:
		if not self._fields:
			self._keys,self._fields = self._db.get_keys(self.table)
		return self._fields or []


class Query_Builder():
	_query:str = ""
	_parms:list
	_join:str = ""
	_limit:str = ""
	_order:str = ""
	_group:str = ""
	_having:str = ""
	_suffix:str = ""
	_keys:Optional[list]
	_q_cache:dict[str,str]
	_field_names:Optional[list] = None
	_values:dict[str,Any] = {}
	_commit:bool = False
	debug:bool = False
	def __init__(self,action:str="select",filter:str="",parms:Optional[list]=None,limit:str="",order:str="",fields:str="*",join:str="",table:str="",group:str="",having:str="",suffix:str="",q_cache:Optional[dict]=None,vals:Optional[dict]=None,parent:str="",keys:Optional[list] = None,field_names:Optional[list]=None):
		self._query = filter or ""
		self._parms = parms or []
		self._action = str(action).lower()
		self._table = table
		self._fields = fields
		self._suffix = suffix
		self._q_cache = q_cache or {}
		self._parent = parent
		self._values = vals or {}
		self._keys = keys or []
		self._field_names = field_names
		self.action(action)
		self.order(order)
		self.limit(limit)
		self.group(group)
		self.join(join)
		self.having(having)
	
	@property
	def commit(self) -> bool:
		return self._commit

	def action(self,action:str) -> Self:
		self._action = str(action).lower()
		return self

	# def select(self,**args):
	# 	return self._parent.select(**args)


	def having(self,having:str) -> Self:
		if self._having:
			self._having +=f" and {having}"
		else:
			self._having = having
		return self

	def group(self,group:str) -> Self:
		if str(group).find("group by")>=0:
			group = group.replace("group by","")
		self._group = group
		return self

	def values(self,data:dict) -> Self:
		Utils.Debug(title="before insert val/parm",data=[self._values,self._parms])
		
		if isinstance(data,dict):
			self._values.update(data)
		return self

	def join(self,join:str) -> Self:		
		if self._q_cache and join in self._q_cache:
			join = self._q_cache.get(join) or ""

		if isinstance(join,str) and join and join.find("join")<0:
			join = "join {join}"

		if self._join:
			self._join +=f" {join}"
		else:
			self._join = join
		return self

	def limit(self,limit:str) -> Self:
		self._limit = limit
		return self

	def fields(self,fields:str) -> Self:
		self._fields = fields
		return self

	def order(self,field:str,direction:str="") -> Self:
		if str(field).find("order by")>=0:
			field = field.replace("order by","")
		self._order = field
		if direction:
			self._order+=f" {direction}"
		return self

	def filter_item(self,field:str,val:Optional[str]=None,op:str='=') -> Self:
		q = f"{field} {op} %s"
		self.filter(query=q,parms=[val])
		return self

	def filter_data(self,data:dict,op:str = '=',condition:str="and",filter_join="and") -> Self:
		try:
			assert isinstance(data,dict), "Data must be a dictionary"
			filterStr = ""
			key_list = []
			parms = []
			for k,v in data.items():
				key_list.append(f"`{k}`{op}%s")
				parms.append(v)
			filterStr = f" {filter_join} ".join(key_list)
			self.filter(query=f"({filterStr})",parms=parms,op=condition)

		except Exception as e:
			Debug(title="trouble with sQuery.filter_data",data=e)
		return self

	def filter_list(self,field:str,items:list,op:str='=',condition:str="and",filter_join:str="and") -> Self:
		try:
			assert items, "No items"
			assert field, "No field"
			filterStr = ""
			parms = []
			q = []
			for item in items:
				q.append(f"{field} {op} %s")
				# filterStr+=" {} {} {} %s".format(condition,field,op)
				parms.append(item)

			filterStr = " {} ".format(condition).join(q)
			# filterStr = "({})".format(filterStr[len(condition)+2:])
			self.filter(query=f"({filterStr})",parms=parms,op=filter_join)
			return self
		except Exception as e:
			Debug(title="Trouble",data=e)
			return self

	def filter(self,query:str,parms:Optional[list]=None,op:str="and") -> Self:
		parms = toList(parms)
		if self._query:
			self._query+=f" {op} {query}"
		else:
			self._query = query

		self._parms += parms
		return self

	def lookup_keys(self) -> list:
		return self._keys
	
	def insert(self,data:dict) -> Self:
		self.action("insert")
		self.values(data)
		Utils.Debug(title="after insert val/parm",data=[self._values,self._parms])
		return self
	
	def update(self,data:dict,filter:Optional[str]=None,parms:Optional[list]=None) -> Self:
		self.action("update")
		self.values(data)
		if filter:
			self.filter(query=filter,parms=parms)
		if self._keys:
			key_parms = {key:data.get(key) for key in self._keys if key in self._values}
			self.filter_data(data=key_parms)
		return self

	def delete(self,data:Optional[dict[str,Any]],filter:Optional[str]=None,parms:Optional[list]=None) -> Self:
		self.action("delete")
		if filter:
			self.filter(query=filter,parms=parms)
		elif data:
			try:
				filter_d = {key:data[key] for key in self._keys}
				self.filter_data(data=filter_d)
			except Exception as e:
				Utils.Debug(title="Missing key for delete",data=e)
		
		
		return self

	def __str__(self) -> str:
		self.build()
		s = f"Query: {self._query}\n"
		s += f"sql: {self.build()}\n"
		s += f"Parms: {self._parms}\n"
		s += f"Values: {self._values}\n"
		s += f"Commit: {self.commit}\n"
		return s

	def build(self) -> tuple:
		field_names = self._field_names or (list(self._values.keys()) if self.values else [])
		sql = ""
		if self._action.lower() == 'update':
			if self._values:
				sql = f"update {self._table}"
				sql+=" set "
				val_set = self._values or {}
				sql_set = []
				for k,v in val_set.copy().items():
					if v is None:
						val_set[k] = ""
					##### Remove field not in table #####
					if k not in field_names or k in self._keys:
						del val_set[k]
					
					elif v !="null":
						sql_set.append(f"`{k}`=%s")
					else:
						sql_set.append(f"`{k}`=null")
						del val_set[k]
				sql+=",".join(sql_set)
				self._parms = list(val_set.values()) + list(self._parms)

		elif self._action.lower() == 'insert':
			if self._values:
				sql = f"insert into {self._table}"
				val_set = self._values or {}
				for k,v in val_set.copy().items():
					######## Remove fields not in table###########
					if k not in field_names:
						del val_set[k]
					elif v is None:
						val_set[k] = ""

				######## Build the field list ############
				names = [str(name) for name in val_set if name in field_names]
				######## Build the value list ############
				vals = ["%s" for name in val_set if name in field_names]

				sql+="({}) values({})".format(",".join(names),",".join(vals))
				self._parms = list(list(val_set.values()) + list(self._parms))
				
				Utils.Debug(title="After parms",data=self._parms)

		elif self._action.lower() == 'delete':
			sql = f"delete from {self._table}"
		else:
			sql = f"select {self._fields} from {self._table}"

		query = ""
		if self._query:
			query = self._query
		if self._suffix:
			if query:
				query+=" and "
			query+=self._suffix

		
		if self._join:
			sql += f" {self._join}"
		if self._query or query:
			sql += f" where {query}"
		if self._having:
			sql += f" having {self._having}"
		if self._group:
			sql += f" group by {self._group}"
		if self._order:
			sql += f" order by {self._order}"
		if self._limit:
			sql += f" limit {self._limit}"

		self._commit = False if self._action.lower() == "select" else True
		return (sql,self._parms)

class Model:
	table:str = ""
	qb:Optional[Query_Builder]
	db:DB
	row_cls = "data_row"
	_key_names:Optional[list] = None
	_field_names:Optional[list] = None
	_error:str = ""
	def __init__(self):
		self.db = DB()
		self.qb = self.qb_init()

	@property
	def key_names(self):
		if not self._key_names:
			self.table_struct()
		return self._key_names

	def log_error(self,error) -> Self:
		if isinstance(error,Exception):
			self._error = Utils.exception_info(error)
		else:
			self._error = str(error)
		return self

	@property
	def error(self) -> str:
		return str(self._error)

	def table_struct(self):
		self._key_names,self._field_names = self.db.get_keys(table=self.table)

	def mk_row(self,data:dict) -> row:
		return row(data)
		# return row(table=self.table,data=data,db=self.db,row_cls=self.row_cls)

	def qb_init(self,*argc,**argv) -> Query_Builder:
		argv["table"] = self.table
		argv["keys"] = self._key_names
		argv["field_names"] = self._field_names
		argv["keys"] = self._key_names
		self.qb = Query_Builder(*argc,**argv)
		return self.qb

	def select_one(self) -> row:
		try:
			return list(self.select())[0]
		except:
			return self.mk_row({})

	def select(self) -> Generator[row,list,None]:
		try:
			assert self.qb is not None
			sql,vals = self.qb.build()
			
			if self.qb.debug:
				Utils.Debug(title="SQL Query",data=[sql,vals])
			self.qb = None
			
			for row in self.db.query(sql=sql,values=vals,debug=True):
				yield self.mk_row(data=row)
		except Exception as e:
			Utils.Debug(title="select exception",data=e)

	def execute(self) -> int:
		try:
			assert self.qb is not None
			sql,vals = self.qb.build()
			if self.qb.debug:
				Utils.Debug(title="Sql Exec Debug",data=[sql,vals])
			resp = list(self.db.query(sql=sql,values=vals,commit=True,debug=self.qb.debug))
			self.qb = None
			Utils.Debug(title="Response",data=resp)
			return self.db.rows_affected
		except:
			return 0

	@property
	def last_row_id(self):
		return self.db.db.insert_id()

	def add(self,data:dict) -> int:
		self.table_struct()
		self.qb = self.qb_init()
		self.qb.insert(data=data)
		self.qb.debug = True
		if self.execute():
			last_id = self.db.last_id
			print(f"Last ID {last_id}")
			return last_id
		else:
			return 0
	
	def update(self,data:dict) -> int:		
		self.table_struct()
		self.qb = self.qb_init()
		self.qb.update(data=data)
		self.qb.debug = True
		return self.execute()
	
	def delete(self,data:Optional[dict]=None,query:Optional[str]=None,parms:Optional[list]=None) -> int:
		self.table_struct()
		self.qb = self.qb_init()
		self.qb.debug = True
		if data or query:
			self.qb.delete(data=data,filter=query,parms=parms)
		Utils.Debug(title="Delete",data=str(self.qb))
		return self.execute()
	
	def get(self,filter:Optional[str],parms:Optional[list]):
		self.qb = self.qb_init(limit="1")
		assert self.qb is not None
		
		if filter:
			self.qb.filter(query=filter,parms=parms)

		return Utils.first(list(self.select()),{})

	def all(self,filter:Optional[str]=None,parms:Optional[list]=None) -> list:
		try:
			self.qb_init()
			assert self.qb is not None
			if filter:
				self.qb.filter(query=filter,parms=parms)
			return list(self.select())
		except Exception as e:
			Utils.Debug(title="DB trouble",data=str(e))
			return []

def empty_gen():
	yield

class Users(Model):
	table = "Users"

	def get_name(self,user:str):
		self.qb_init().filter(query="User=%s",parms=[user])
		return Utils.first(self.select(),{})

class Events(Model):
	table = "Events"


	def detail(self,loc_id) -> dict:
		self.qb = self.qb_init()
		self.qb.fields(fields="Events.Map as event_map, Events.Style as Event_Style,IF(Events.Checkin_Frequency<0,1,0) as anon,Theme_Primary,Theme_Secondary,Theme_Alt,Events.Home_URL,Events.Name as Event_Name,Events.Logo as Event_Logo,Events.Style,Locations.Name as Loc_Name, Locations.image as Loc_Image,Locations.Description as Loc_Description,Locations.Message as Loc_Message,Events.Message as Event_Message,Locations.Next_Location,Locations.Offer,Locations.link,Locations.Event_ID,Locations.Address as Address,Locations.Media as Media,Locations.Offer_Destination as Offer_Destination")
		self.qb.join(join="join Locations on Events.ID=Locations.Event_ID")
		self.qb.filter(query="Locations.ID=%s",parms=[loc_id])

		data = self.select_one()
		locations = Locations()
		next_id = data.get("Next_Location")

		if next_id:
			next_loc = locations.get(location_id=next_id)
			Debug(title="Next Loc",data=next_loc)
			data["Next_Link"] = next_loc.get("test_link")
			data["Next_Address"] = next_loc.get("Address")
			data["Next_Title"] = next_loc.get("Name")
			data["Next_Image"] = next_loc.get("image")
		else:
			data["Next_Link"] = ""
			data["Next_Address"] = "No Next ID"
		return data

	def summary_count(self) -> dict:
		self.qb = self.qb_init()
		self.qb.fields(fields="Events.ID,count(distinct(Locations.ID)) as Locations, count(distinct(Explorers.ID)) as Explorers")
		self.qb.join(join="left join Locations on Locations.Event_ID = Events.ID left join Explorers on Explorers.Location = Locations.ID")
		self.qb.group(group="Events.ID")
		# data = self.db.query(sql="select Events.ID,count(distinct(Locations.ID)) as Locations, count(distinct(Explorers.ID)) as Explorers from Events join Locations on Locations.Event_ID = Events.ID join Explorers on Explorers.Location = Locations.ID group by Events.ID")
		return Utils.group_by(field="ID",data=self.select())
	
	def names(self) -> list:
		self.qb = self.qb_init()
		self.qb.fields("Name")
		
		names = []
		for row in self.select():
			names.append(row.get("Name"))
		return names

	def overview(self,id:int) -> dict:
		self.qb = self.qb_init()
		self.qb.filter(query="ID=%s",parms=[id])
		self.qb.debug = True
		data = {
			"Event":self.select_one(),
			"Locations":list(Locations().get_event(event_id=id)),
			"Explorers":list(Explorers().get_event(event_id=id))
			}
		return data

	def get(self,id:int) -> dict:
		self.qb = self.qb_init()
		self.qb.filter(query="ID=%s",parms=[id])
		return self.select_one()

	def all(self) -> list:
		data = super().all()
		summary = self.summary_count()
		
		for i in range(len(data)):
			id = data[i].get("ID")
			data[i]["Locations"] = summary.get(id,{}).get("Locations") or 0
			data[i]["Explorers"] = summary.get(id,{}).get("Explorers") or 0
		return data

	def delete(self,id:int) -> bool:
		return bool(super().delete({"ID":id}))

	def clone(self,id:int) -> bool:
		try:
			oevent = self.get(id=id)
			assert oevent is not None,"No event found"
			if oevent:
				del oevent["ID"]
				del oevent["Timestamp"]
				new_event_id = self.add(oevent)
				assert new_event_id, "Trouble Cloning"
				Utils.Debug(title="New Event ID",data=new_event_id)

				Locations().clone_event(old_event_id=id,event_id=new_event_id)

			return 1
		except Exception as e:
			Utils.Debug(title="Trouble cloning",data=e)
			self._error = str(e)
			return 0

class Locations(Model):
	table = "Locations"
	def get(self,location_id:int) -> dict:
		self.qb = self.qb_init()
		self.qb.filter(query="ID=%s",parms=[location_id])		
		loc = dict(self.select_one())
		loc["test_link"] = self.get_url(id=loc.get("ID")) if loc.get("ID") else ""
		return loc

	def url_list(self,event_id) -> list:
		self.qb = self.qb_init()
		self.qb.fields("Name,ID")
		self.qb.filter(query="Event_ID=%s",parms=[event_id])
		self.qb.order(field="Next_Location")
		data = []
		for row in self.select():
			row["url"] = self.get_url(row.get("ID"))
			data.append(row)
		return data

	def clone_event(self,old_event_id:int,event_id:int) -> bool:
		for loc in self.get_event(event_id=old_event_id):
			del loc["ID"]
			loc["Event_ID"] = event_id
			self.add(loc)
		return True

	def delete(self,id:int) -> bool:
		return bool(super().delete({"ID":id}))

	def get_event(self,event_id:int) -> Generator:
		self.qb = self.qb_init()
		self.qb.filter(query="Event_ID=%s",parms=[event_id])
		data = []
		for row in self.select():
			Debug(title="Row",data=row)
			row["link"] = self.get_url(row.get("ID"))
			data.append(row)
			Debug(title=f"get Event {event_id}",data=row)
		
		return data

	def get_url(self,id:int) -> str:
		url = ""
		try:
			assert id>0, f"Invalid Location ID {id}"
			inc_code = Utils.encr(data={"loc_id":id})
			Debug(title="inc_code",data={"loc_id":id})
			url = f"https://passport.experiencechehalis.com/?d_id={Utils.url_encode(inc_code)}"		
		
		
		except Exception as e:
			Debug(title="Trouble getting url",data=e)
		return url

	def qr_code(self,location_id:int):
		import qrcode
		import qrcode.image.svg
		inc_code = Utils.encr(data={"loc_id":location_id})
		# inc_code=location_id
		url = f"https://passport.experiencechehalis.com/?d_id={Utils.url_encode(inc_code)}"		
		
		Utils.Debug(title="Location",data=url)
		factory = qrcode.image.svg.SvgPathImage
		img = qrcode.make(url, image_factory=factory)
		return img.to_string(encoding='unicode')

@dataclass(slots=True,init=True)
class Explorer_Data():
	ID:int
	Email:str
	Location:int
	Timestamp:Utils.IODate
	Geo:str
	Event_ID:int

class Explorer(data_row):
	@property
	def Timestamp(self):
		return Utils.IODate(self.get("Timestamp") or "0000-00-00")

	@Timestamp.setter
	def Timestamp(self,tstamp):
		self["Timestamp"] = Utils.IODate(tstamp)
		return self

class Explorers(Model):
	table = "Explorers"
	row_cls = "Explorer"

	def log_book(self,email:str,event_id:int) -> list:
		self.qb = self.qb_init()
		self.qb.fields(fields="Explorers.Timestamp,Locations.*")
		self.qb.filter(query="Email=%s and Locations.Event_ID=%s",parms=[email,event_id])
		self.qb.join(join="join Locations on Locations.ID = Explorers.Location")
		return list(self.select())

	def checkin(self,email:str,location_id:int,geo:str="") -> int:
		loc = Locations().get(location_id=location_id)
		event_id = i(loc.get("Event_ID"))
		event = Events().get(id=event_id)
		freq_limit = event.get("Checkin_Frequency") or 1

		if self.check_dupe(email=email,location_id=location_id,freq_limit = freq_limit):
			return -1
		else:
			data = {"Email":email,"Location":location_id,"Timestamp":Utils.IODate().utc().full(),"Event_ID":event_id,"Geo":geo}
			Utils.Debug(title="Adding data",data=data)
			self.add(data=data)
		return True
	
	def check_dupe(self,email:str,location_id:int,freq_limit:int) -> bool:
		freq_date = Utils.IODate().delta(days=-freq_limit)
		self.qb = self.qb_init()
		self.qb.filter(query="Email=%s and Location =%s and Timestamp>=%s",parms=[email,location_id,freq_date.full()])
		dupes = list(self.select())
		if len(dupes)<=0:
			return False
		else:
			return True

	def get_event(self,event_id:int) -> Generator:
		self.qb = self.qb_init()
		self.qb.fields(fields="Explorers.ID,Explorers.Event_ID,Explorers.Location as Location_ID,Email,Locations.Name as Location_Name,Events.Name as Event_Name,Geo,Explorers.Timestamp")
		self.qb.join(join="join Locations on Explorers.Location = Locations.ID join Events on Events.ID = Explorers.Event_ID")
		self.qb.filter(query="Explorers.Event_ID=%s",parms=[event_id])
		self.qb.order(field="Explorers.Email,Explorers.Timestamp desc")
		return self.select()

def auth_failed(func):
	def message(*argc,**argv):
		user = argv.get("User")
		level = argv.get("level")
		response = {"status":0,"message":f"User {user} is not {level}"}
		Utils.Debug(title="Response",data=response)
		return response

	return message

def auth(level:str):
	Utils.Debug(title="Auth args",data={"Level":level})
	user = "Brenta"

	def grab_func(func):
		def wrapper(*argc,**argv):
			Utils.Debug(title="inside",data={"argc":argc,"argv":argv})
			func(*argc,**argv)
		return wrapper
	if user == "Brent":
		return grab_func
	else:
		return auth_failed

# print(Utils.decr(code))
# print(list(exp.select()))
# locs = Locations()
# locs.qb = locs.qb_init()
# locs.qb.fields(fields="Explorers.ID as ex_id,Locations.Event_ID")
# locs.qb.join(join="join Explorers on Locations.ID = Explorers.Location")
# r = Explorers().get(filter="ID=%s",parms=[1666])
# Utils.Debug(title="Stamp",data=r)
# for item in locs.select():
# 	event_id = item.get("Event_ID")
# 	explorer_id = item.get("ex_id")
# 	explorer.update({"ID":explorer_id,"Event_ID":event_id})
# 	break
# import time
# startTime = time.time()

# for i in range(1_000_000):
# 	# b = row(table="test",db=None,data={"user":"Brent","id":1,"pw":"Words"})
# 	# c = b.get("id")
# 	u = user_row(id=1,user="Brent",pw="kasjdkfjas")
# 	c = u["id"]

# field = "id"
# print(getattr(u,field))
# 	c = b.get("user")
#b = row(table="test",db=None,data={"user":"Brent","id":1,"pw":"Words"},row_cls="user_row")

# print(u)
# print(b["id"])
# executionTime = (time.time() - startTime)
# print('Execution time in seconds: ' + str(executionTime))
# import sys
# d = {"id":9,"user":"brent.dickinson@gmail.com","pw":"test"}
# clsName = "default_row"
# clsObj = getattr(sys.modules[__name__], clsName)
# parms = {}
# for field in fields(clsObj):
# 	parms[field.name] = d.get(field.name)
# 	print(f"Field: {field.name}")
# r = row(table="Users",db=DB(),data=d,row_cls=clsName)
# r = getattr(sys.modules[__name__], clsName)(**parms)
# for k,v in r.items():
# 	print(f"{k} = {v}")
# print(dir(Users()))
# for user in Users().all():
# 	print(user)
# print(fields(clsObj))
# u = user_row()
# print(u)

# users = Users()
# users.add({"User":"Test@gmail.com","PW":"PW0rd"})
# d = users.delete(filter="User=%s",parms=['test@gmail.com'])
# Utils.Debug(title="Delete d",data=d)
# if d:
# 	print ("Yep")
# else:
# 	print ("Nope")

# print(list(users.all()))

# test = users.get(filter="User=%s",parms=["test@gmail.com"])
# Utils.Debug(title="Test obj",data=test.__dict__)
# Utils.Debug(title="Test obj",data=dir(test))
# Utils.Debug(title="All",data=list(users.all()))
# test["PW"] = "Woot"
# print(users.update(data=test))
# # d = users.get(user="Brent@vulcan-creative.com")
# Utils.Debug(title="User get",data=users.all())

# @auth(level="Admin")
# def words(title:str):
# 	print(f"Words:{title}")

# words(title="Blah")
