flask-sqlalchemy的類似django-orm的管理器objects擴展

說明

note:

最近項目上使用了falsk框架,發現 sqlalchemy 中的 Model中並沒有像 django-orm中Model的管理器 objects, 但是我的Model中需要很多使用到當前Model的類方法, 比如基本的create 方法, 還有很多針對業務方面的方法,如果都放在 Model中顯得模型類很亂,因此就想著能不能構造出一種django中的objects管理器,將這些方法放在管理器中。 但是新的問題出現了,就是管理器中需要用到當前的 Model, 如何將當前的Model傳入管理器, 我們不可能針對每一個模型中都重新定義objects, 將當前Model傳進去,這顯然不是很優雅的解決方式,也不適合繼承的方式。因此在查看了 django-orm的源碼,發現了原來 django-orm採用的是元類編程,定義了一個 metaclass,動態的創建 Model,創建的過程中調用管理器中的方法,將當前 Model傳進去了,因此objects中會有一個 model的屬性。

元類編程簡介(以下代碼都是建立在python3中)

概念

  • Python中所有的東西都是對象(如str, int, dict, list, function, object, class)
  • Class()創建對象 instance, 那麼Class類既然是對象,誰創建了類呢, 那就是type(元類)來創建類

class A(object):
pass
a = A()

print(type(zc), type([1,2]), type(a), type(A))
print(-*50)
print(type(str), type(list), type(object), type(type))

輸出:

<class str> <class list> <class __main__.A> <class type>
--------------------------------------------------
<class type> <class type> <class type> <class type>

  • type有兩個作用

    • type(zc)當傳入一個參數的時候,是查看創建了參數對象的對象
    • type(new_class_name, base_classes, new_class_attrs)傳入三個參數的時候,是動態的創建類

  • 當在文件中通過 class 關鍵字的時候,默認是通過 type元類創建類
  • 在 python3 中如果想通過元類type的子類, 包含type操作的函數作為元類來動態的創建類,可以如下:

def upper_attr(future_class_name, future_class_parents, future_class_attr):
"""返回一個類對象,將屬性都轉為大寫形式"""
# 選擇所有不以__開頭的屬性 列表(元祖)生成式
attrs = ((name, value) for name, value in future_class_attr.items() if not name.startswith(__))
uppercase_attr = dict((name.upper(), value) for name, value in attrs)
# 通過type來做類對象的創建
return type(future_class_name, future_class_parents, uppercase_attr)

class UpperMeta(type):
def __new__(cls, future_class_name, future_class_parents, future_class_attr):
attrs = ((name, value) for name, value in future_class_attr.items() if not name.startswith(__))
uppercase_attr = dict((name.upper(), value) for name, value in attrs)
# 通過type來做類對象的創建
return type(future_class_name, future_class_parents, uppercase_attr)
# __metaclass__ = UpperMeta # python2 中這會作用到這個模塊中所有的類
class Foo(metaclass=upper_attr):
# 可以在這裡定義__metaclass__,這樣只會作用這個類中
# __metaclass__ = UpperMeta # python2
bar = bip

print(hasattr(Foo, bar))
print(hasattr(Foo, BAR))
f = Foo()
print(f.BAR)
False
True
bip

django-orm中的object實現

  • 從源碼中可以發現 Model 的元類指定了 metaclass=ModelBase

class Model(metaclass=ModelBase):

def __init__(self, *args, **kwargs):
# Alias some things as locals to avoid repeat global lookups
cls = self.__class__
opts = self._meta
_setattr = setattr
_DEFERRED = DEFERRED

pre_init.send(sender=cls, args=args, kwargs=kwargs)

  • 從ModelBase源碼中 __new__(cls, *args, **kwargs)中可以發現

def __new__(cls, name, bases, attrs):
super_new = super().__new__

# Also ensure initialization is only performed for subclasses of Model
# (excluding Model class itself).
parents = [b for b in bases if isinstance(b, ModelBase)]
# 如果沒有父類是Model的子類, 那麼就普通的創建
if not parents:
return super_new(cls, name, bases, attrs)

# Create the class.
module = attrs.pop(__module__)
# 構建新的類屬性 new_attrs
new_attrs = {__module__: module}
classcell = attrs.pop(__classcell__, None)
if classcell is not None:
new_attrs[__classcell__] = classcell
new_class = super_new(cls, name, bases, new_attrs)

# ....
new_class._prepare() # 調用了從 ModelBase 中繼承來的_prepare()方法
new_class._meta.apps.register_model(new_class._meta.app_label,new_class)
return new_class

  • 從上述代碼可知__new__調用了_prepare方法

def _prepare(cls):
"""Create some methods once self._meta has been populated."""
opts = cls._meta
# 設置模型的主鍵
opts._prepare(cls)
# ....

if not opts.managers:
if any(f.name == objects for f in opts.fields):
raise ValueError(
"Model %s must specify a custom Manager, because it has a "
"field named objects." % cls.__name__
)
manager = Manager()
manager.auto_created = True
cls.add_to_class(objects, manager)
# ...

class_prepared.send(sender=cls)

  • opts.managers, 將父類管理器都存放到一個列表,如果父類有, 那麼將當前Model賦值給管理器,顯然如果父類沒有管理器,那麼,將返回上一步驟,創建manager = Manager(), 並通過 cls.add_to_class()方法

@cached_property
def managers(self):
managers = []
seen_managers = set()
bases = (b for b in self.model.mro() if hasattr(b, _meta))
for depth, base in enumerate(bases):
for manager in base._meta.local_managers:
if manager.name in seen_managers:
continue

manager = copy.copy(manager)
manager.model = self.model ## 這一步驟很重要
seen_managers.add(manager.name)
managers.append((depth, manager.creation_counter, manager))

return make_immutable_fields_list(
"managers",
(m[2] for m in sorted(managers)),
)

  • 因為管理器 Manager 有 contribute_to_class方法,所以調用了此方法,第一步驟將 Model傳給 Manager; 第二步給 Model的object屬性賦值到 ManagerDescriptor(self), self代表剛創建的 Manager()對象。而描述符又增加了訪問管理器的規則。

# Model
def add_to_class(cls, name, value):
# We should call the contribute_to_class method only if its bound
if not inspect.isclass(value) and hasattr(value, contribute_to_class):
value.contribute_to_class(cls, name)
else:
setattr(cls, name, value)

# Manager
def contribute_to_class(self, model, name):
if not self.name:
self.name = name
self.model = model

setattr(model, name, ManagerDescriptor(self))

model._meta.add_manager(self)

  • ManagerDescriptor, 讓模型實例無法訪問管理器,抽象Model也訪問不到, 通過manager_map 映射的名字訪問到管理器

class ManagerDescriptor:

def __init__(self, manager):
self.manager = manager

def __get__(self, instance, cls=None):
if instance is not None:
raise AttributeError("Manager isnt accessible via %s instances" % cls.__name__)

if cls._meta.abstract:
raise AttributeError("Manager isnt available; %s is abstract" % (
cls._meta.object_name,
))

if cls._meta.swapped:
raise AttributeError(
"Manager isnt available; %s.%s has been swapped for %s" % (
cls._meta.app_label,
cls._meta.object_name,
cls._meta.swapped,
)
)

return cls._meta.managers_map[self.manager.name]

依照django-orm 的管理器,去實現sqlalchemy的管理器

note:

實現的困境主要有兩個,一個是講Model傳入到Manager中, 第二個是將db.session資料庫會話也傳進去,稍後將實現的文檔,補充上去

  • 實現代碼模塊 ext_sa_manager.py

# ext_sa_manager.py

from flask_sqlalchemy import Model
from sqlalchemy import orm
from sqlalchemy.orm.exc import UnmappedClassError
from flask_sqlalchemy import SQLAlchemy

class DefaultManager(object):
"""sqlalchemy 自定義管理器"""
def __init__(self, model, session, sa):
self.model = model
self.session = session
self.sa = sa
self.query = self.model.query

def create(self, **kwargs):
"""創建對象"""
session = self.session
if id in kwargs:
obj = session.query(self.model).get(kwargs[id])
if obj:
return obj
obj = self.model(**kwargs)
session.add(obj)
session.commit()
return obj

class _ManagerProperty(object):
"""管理器的 描述符"""
def __init__(self, sa):
self.sa = sa

def __get__(self, obj, type):
try:
mapper = orm.class_mapper(type)
if mapper:
assert issubclass(type.Manager, self.sa.Manager),
{}不滿足自定義管理器必須是{}的子類.format(type.Manager, self.sa.Manager)
return type.Manager(type, session=self.sa.session, sa=self.sa)
except UnmappedClassError:
return None

class BaseModel(Model):
"""orm模型基類"""
# 管理器的默認類
Manager = None

objects = None

def to_dict(self):
columns = self.__table__.columns.keys()
return {key: getattr(self, key) for key in columns}

class DefineSQLAlchemy(SQLAlchemy):
"""自定義sqlalchemy"""
def __init__(self, *args, **kwargs):
self.Manager = DefaultManager
if manager in kwargs:
self.Manager = kwargs.pop(manager)
if model_class in kwargs:
assert issubclass(kwargs[model_class], BaseModel), 如果你使用了ext_manager, 那麼你自定義的model_class
必須是{}的實例.format(BaseModel)
else:
kwargs[model_class] = BaseModel
super().__init__(*args, **kwargs)

def make_declarative_base(self, model, metadata=None):
"""重寫此方法構造管理器objects對象"""
rv = super().make_declarative_base(model, metadata)
rv.Manager = self.Manager
rv.objects = _ManagerProperty(self)
return rv

def ext_manager(sa=SQLAlchemy):
"""
擴展 sa, 增加 manager
return SQLAlchemy
"""
def __init__(self, *args, **kwargs):
self.Manager = DefaultManager
if manager in kwargs:
self.Manager = kwargs.pop(manager)
if model_class in kwargs:
assert issubclass(kwargs[model_class], BaseModel), 如果你使用了ext_manager, 那麼你自定義的model_class
必須是{}的實例.format(BaseModel)
else:
kwargs[model_class] = BaseModel
super(self.__class__, self).__init__(*args, **kwargs)

def make_declarative_base(self, model, metadata=None):
"""重寫此方法構造管理器objects對象"""
# rv = super().make_declarative_base(model, metadata)
rv = super(self.__class__, self).make_declarative_base(model, metadata)
rv.Manager = self.Manager
rv.objects = _ManagerProperty(self)
return rv

name = SQLAlchemy
bases = (sa,)
class_dict = {
__init__: __init__,
make_declarative_base: make_declarative_base
}
rv = type(name, bases, class_dict)
# SQLAlchemy.__init__ = __init__
# SQLAlchemy.make_declarative_base = make_declarative_base
# return DefineSQLAlchemy
return rv

  • 在flask中使用擴展的 sqlalchemy

from ext_sa_manager import ext_manager
from flask_sqlalchemy import SQLAlchemy
from flask import Flask

app = Flask(__file__)
db = ext_manager(SQLAlchemy)()
db.init_app(app)

推薦閱讀:

Django基礎(9): 表單Forms的高級使用技巧
【記錄 】Django學習1
【記錄】Django學習42
【記錄】Django學習14
如何用Django創建聯動選擇框

TAG:Python | Django(框架) | Flask |