import pymysql
###-------------------属性描述器-------------------###
class Field: #描述器(类型判断) int varchar date enum float
def __init__(self,name,fieldname=None,pk=False,unique=False,default=None,nullable=True,index=False):
self.name = name
if fieldname is None: #字段名为空则将传入name当做字段名
self.fieldname = name
else:
self.fieldname =fieldname
self.pk = pk
self.unique = unique
self.default = default
self.nullable = nullable
self.index = index
def validate(self,value): #数据校验 分不同类型进行
raise NotImplementedError
def __get__(self, instance, owner): #instance是创建字段student的实例
if instance is None: #描述器的get返回的就是字段名--值的字典
return self
return instance.__dict__[self.name]
def __set__(self, instance, value): #设置字段的值 字段名--值
self.validate(value) #set之前先校验数据
instance.__dict__[self.name] = value #给对应属性名设置值
def __str__(self): #字段实例的本身是哪个 做区分
return "< {}类型的{}字段 >".format(self.__class__.__name__ , self.name)
__repr__ = __str__
class IntField(Field):
def __init__(self,name,fieldname=None,pk=False,unique=False,default=None,nullable=True,auto_increment=False,index=False):
self.auto_increment = auto_increment #int仅有
super().__init__(name,fieldname,pk,unique,default,nullable,index)
def validate(self,value): #auto_increment
if value is None:
if self.pk: #主键不可为None
raise TypeError(u"作为主键的%s 的属性%s 的值%s 异常" % (self.pk,self.name,value))
if not self.nullable: #如果当前为空 但数据库不允许为空时
raise TypeError(u"%s required" % self.name)
else:
if not isinstance(value,int): #判断是否为int型数据
raise TypeError(u"%s should be int" % self.name)
class StringField(Field): #字符串需要一个长度属性
def __init__(self,name,length=32,fieldname=None,pk=False,unique=False,default=None,nullable=True,index=False):
self.length = length
super().__init__(name,fieldname,pk,unique,default,nullable,index)
def validate(self,value):
if value is None:
if self.pk: #主键不可为None
raise (u"作为主键的%s 的属性%s 的值%s 异常" % (self.pk,self.name,value))
if not self.nullable: #如果当前为空 但数据库不允许为空时
raise TypeError(u"%s required"% self.name)
else:
if not isinstance(value,str): #判断是否为int型数据
raise TypeError(u"%s should be str"% self.name)
if len(value) > self.length:
raise ValueError(u"字段%s 长度超出范围,value=%s"% (self.name,value) )
###-------------------数据库操作会话 cursor操作的类-------------------###
class Session(): #这种模式会造成线程不安全 有一定风险 但Session应该不跨线程 全局使用
def __init__(self, conn):#:pymysql.connections.Connection):
self.conn = conn
self.cursor = None
print("初始化完成")
def execute(self, query,*args): #SQL执行通用方法 query参数接收SQL语句
#连接数据库 执行SQL语句 数据库的连接由外部传入,应是全局变量
if self.cursor is None: #判断保护 如果没有游标则新建一个
self.cursor = self.conn.cursor() #指令参数化 传入的是元组
print("已建立游标")
print("已存在游标")
self.cursor.execute(query,args)
def __enter__(self): #with Session实例操作时,单独获取一个游标
self.cursor = self.conn.cursor()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type: #有异常 回滚
self.conn.rollback()
else: #无异常 执行
self.conn.commit()
self.cursor.close()
###-------------------元类式反射 定义实体类的模板-------------------###
class ModelMeta(type): #元类只适合放共性相同的属性和方法
def __new__(cls, name:str, bases,attrs:dict): #attrs是 属性-值 的字典
#解决表名的问题 给了直接用 没给 就用name首字母大写作为表名
if attrs.get('__tablename__',None)is None: #获取表名 __tablename__
attrs['__tablename__'] = name.lower()
mapping = {} #映射字典
primarykey = [] #列表保存主键 主键不止一个!
for k,v in attrs.items(): #k属性名 v属性所对应的字段类型 保存在mapping字典中
if isinstance(v,Field):
mapping[k] = v #注意:这里在类构建时已经执行
if v.name is None:
v.name = k
if v.fieldname is None:
v.fieldname = v.name
if v.pk:
primarykey.append(v)
attrs['__mapping__'] = mapping #未定义时已经把字段属性放到映射字典中保存 作为__mapping__属性
attrs['primarykey'] = primarykey
return super().__new__(cls, name, bases,attrs)
"""
session.execute(sql , values)
将sql中的属性名(表名,字段名...)分离出来
values保存对应要添加的值
names保存属性名
"""
class Model(metaclass=ModelMeta): pass #基类来统一数据库操作
#实体类
class Engine:
def __init__(self,*args , **kwargs):
self.conn = pymysql.connect(*args , **kwargs)
def save(self,instance):
names = []
values = []
for k,v in instance.__mapping__.items(): #注意:当Model子类的实例调用方法时才调用
if isinstance(v,Field): #过滤出Field实例化的v
names.append(k) #k是属性名 访问v方式:
values.append(instance.__dict__[k]) #v是属性名对应的Field 访问v方式:__dict__[k]
#sql = "insert into student(id,name,age)values(%s,%s,%s)"
sql = "insert into {}({}) values ({})".format(instance.__tablename__ ,",".join(names), ",".join(['%s']*len(names))) #把values后面的参数拼成列表的形式[%s,%s,%s]
print(sql)
print(values)
session = Session(self.conn)
with session:
session.execute(sql, values)
###-------------------数据库连接和操作 实体类-------------------###
class Student(Model): #操作表student的记录
__tablename__ = "student"
id = IntField('id', 'id',True,nullable=False,auto_increment=True)#用描述器来指定属性名称(字段名) 按照int字段来创建 name fieldname pk null ...
name = StringField('name',length=64,nullable=False)
age = IntField('age') #默认age不做pk 默认就为False
def __init__(self,id,name,age):
self.tablename = ""
self.id = id #调用__set__方法 instance即为当前实例 字典的key为属性名称
self.name = name
self.age = age
# def __str__(self):
# return "Student(%s,%s,%s)" % (self.id,self.name,self.age)
if __name__ == '__main__':
s = Student(int(1),'tom',int(20))
engine = Engine(host = "10.0.0.6",port = 3306,user = "root",password='123',db = 'db1',charset='utf8')
engine.save(s)