'''
ORM 对象关系映射 --->映射到数据表的一条条记录
类名 ---> 表名
对象 ---> 一条记录
对象.属性 ---> 字段
'''
from day38.mysql_control import MySQL
# 1.创建字段的类型 对应数据表中一个个字段的创建规范
class Field: #父类
def __init__(self,name,column_type,primary_key,default):
self.name = name
self.column_type = column_type
self.primary_key = primary_key
self.default = default
class StringField(Field): #varchar
def __init__(self,name,column_type='varchar(64)',primary_key=False,default=None):
super().__init__(name,column_type,primary_key,default) #继承父类
class IntegerField(Field): #int
def __init__(self,name,column_type='int',primary_key=False,default=0):
super().__init__(name,column_type,primary_key,default)
# 自定义元类:
# 解决三件事情
# 1、保证一张表必须要有表名 2、保证一张表中只能有一个主键
# 3、将所有 “字段名” 与 “字段对象” 添加到一个独立的字典中(mappings),
# 以key(字段名):value(字段对象) , 添加到类的名称空间中,方便后期使用
# 控制表类的创建 ---> 控制表创建的过程
class OrmMetaClass(type):
# __call__ ---> __new__ ----> type.__new__() ---> obj ---> Models
#只要定义类就会触发__new__, 因为类也是对象,OrmMetaClass() ---> Models类对象 、User类对象
def __new__(cls,class_name,class_bases,class_attr):
# print(f'类名: {class_name}')
# print(f'基类: {class_bases}')
# print(f'类的名称空间: {class_attr}')
#1、过滤Models类,因为只需要控制user类,不需要控制Models类
if class_name =='Models':
# 将models类的类名、基类、名称空间原路返回
return type.__new__(cls,class_name,class_bases,class_attr)
# 2、获取 table 表名,若自定义则获取,没有则默认使用类名
# dict.get(key) ---> key若有则返回对应的值,若没有则返回默认值 class_name就是默认值
# 将类名当做表名
table_name = class_attr.get('table_name',class_name)
# print(table_name)
# 主键值: 主键名为 字段名, 比如 主键是 id字段 ---》 id就是主键的名字
primary_key = None
mappings = {} # 存放字段名与字段对象的字典
# 3、保证一张表只能有一个唯一的主键
for k,v in class_attr.items(): # 循环遍历类的名称空间
# print(k,v)
#将字段以外的属性过滤调掉
if isinstance(v,Field): #判断v是否是Field的一个实例
# print(k,v)
# print(v.__dict__)
# 4、将所有 “字段名” 与 “字段对象” 添加到一个独立的字典中(mappings)
mappings[k] = v
if v.primary_key: # 判断字段对象如果有 主键primary_key, 则为primary_key 变量赋值
if primary_key:
raise TypeError('一张表只能有一个主键')
primary_key = v.name
# print(mappings)
# 5、过滤掉类名称空间中重复的字段属性
for key in mappings.keys():
class_attr.pop(key)
if not primary_key:
raise TypeError('必须要有一个主键!!!')
# 6、给类的名称空间,添加table_name, primary_key,mappings属性;
class_attr['table_name'] = table_name
class_attr['primary_key'] = primary_key
class_attr['mappings'] = mappings
# print(class_attr)
return type.__new__(cls,class_name,class_bases,class_attr)
# 创建数据表类
#让models类继承字典类,所有子类继承Models类就等于继承了dict类,调用User类例触发dict类的__init__
class Models(dict,metaclass=OrmMetaClass): #创建一个父类,用继承减少写__init__的次数
#在通过 “对象.属性” 获取属性,若 “属性没有” 时触发。
def __getattr__(self, item):
# print(item,'调用对象没有的属性时触发') #item --->对象.属性
# 将字典中key对应的值返回给User的对象
return self.get(item) #dict_obj.get(key) = 属性值
# 在对象.属性=值时,触发
def __setattr__(self, key, value):#self--->user对象,即字典对象 key---> 对象.属性名 value ---》 属性值
self[key]=value # 给字典添加键值对的方式
# 查询数据
@classmethod
def select_data(cls,**kwargs): # name=tank ---> {'name': "tank"}
mysql_obj = MySQL()
if not kwargs: #若kwargs为False代表没有查询条件
#1.查所有
#sql = 'select * from 表名'
sql = f'select * from {cls.table_name}' #cls-->User
res = mysql_obj.select(sql)
# print(res)
return [cls(**r) for r in res]
else:
field_name = list(kwargs.keys())[0] #获取字段名 #keys返回的是对象
field_value = kwargs.get(field_name) #获取字段值
#2.根据条件查询
#select * from 表名 where 字段=字段值
sql = 'select * from %s where %s=?' %(cls.table_name,field_name)
sql = sql.replace('?','%s')
res = mysql_obj.select(sql,field_value)
# print(res) #[{},{}]
# ** ---> [{key: values}, {}] ---> [User(key=value)]
return [cls(**r) for r in res] #---》[obj,obj....]
#插入数据
def insert_data(self): #self--->user_obj
mysql_obj = MySQL()
# sql: insert into 表名(字段名1, 字段名2) values(字段值1, 字段值2);
# 1.表名 ---》 self.table_name
# 2.字段名与字段值---》 mappings
field_names = [] # 存放字段名的列表
field_values = [] # 存放字段值的列表
replace_list = [] # 设置一个替换值的列表 [?, ?]
for k,v in self.mappings.items():
field_names.append(v.name)
field_values.append(getattr(self,v.name,v.default)) #self-->user_obj
replace_list.append('?')
sql = 'insert into %s(%s) values(%s)' %(
self.table_name,",".join(field_names),",".join(replace_list))
sql = sql.replace('?','%s')
mysql_obj.execute(sql,field_values)
#更新数据
def update_data(self):
mysql_obj = MySQL()
#sql : update 表名 set 字段名=字段值 where pk=主键值;
#主键值
pk = None
filed_names = [] # [字段名, 字段名, 字段名, ]
filed_values = [] # [字段值, 字段值,字段值,]
for k, v in self.mappings.items():
if v.primary_key: # 判断mappings中哪一个字段是主键
#若是主键,则获取主键值
pk = getattr(self,v.name)
else:
filed_names.append(v.name+'=?') # 添加字段名
filed_values.append(getattr(self,v.name)) # 添加字段值
# sql: update User set 字段名=字段值, 字段名=字段值
# sql: update User set username=?, password=? where pk=1;
sql = 'update %s set %s where %s=%s' %(
self.table_name,
','.join(filed_names),
self.primary_key,
pk
)
sql = sql.replace('?','%s')
mysql_obj.execute(sql,filed_values)
class User(Models): #用户表类
# table_name = 'user_info'
# IntegerField字段类中的name属性 必须与User表中类属性同名
id = IntegerField(name='id',primary_key=True)
username = StringField(name='username')
password = StringField(name='password')
# class Movie(Models):
# pass
# user = User(name='tank') #user本质上就是一个字典对象
# print(user.sex)
if __name__ == '__main__':
#查询数据
# user_obj = User.select_data()[0]
user_obj = User.select_data(username='tank')[0] #select_data()括号中不传参指的是查询所有数据
print(user_obj)
# # print(user_obj.name)
# #插入数据
# user_obj = User(username='tank_dsb',password=123)
# user_obj.insert_data()
#更新数据
# 获取jason对象
# user_obj = User.select_data(username='tank_dsb')[0]
# print(user_obj)
# # 修改对象中属性的值
# user_obj.username = '大弟弟_DDSB'
# # 然后再调用update_data方法更新到数据库中
# user_obj.update_data()
'''
问题1:解决代码冗余问题,比如有100张表,需要写100次__init__
解决1:定义一个父类,继承父类
问题2:无法通过预测每一张表中的字段是什么,无法通过父类的__init__解决问题
解决2:通过继承字典,内部的__init__,可以接受“任意个数”的关键字参数。
问题3:继承字典的类实例化的对象,无法通过对象.属性的方式存取值
解决3:通过__setattr__,__getattr__实现,让字典对象与普通对象一样,并具备字典原有的特性
'''