2016-06-03 4 views
1

모델이 상속하는 mixin 클래스를 만드는 대신 다른 방법으로 클래스를 구성해야하는 유스 케이스가 있습니다. 일반적으로 믹스 인 클래스가 될 클래스는 모델 객체를 생성하는 클래스뿐만 아니라 모델을 상속하는 클래스 여야합니다. 이는 모델 및 매퍼 구성이 기본 저장소의 외부 라이브러리에 있기 때문입니다. 모델을로드하기 전에 기본 저장소에서 모델 라이브러리로 엔진의 호스트를 전달하여 이미 구성된 선언적 기본을로드 할 수 있어야합니다. 엔진 정보가 전달 된 후에는 세션, 기본 클래스 및 모든 것이 모델이 상속하는 일종의 기본 클래스 내에서 만들어집니다. 다음은 간단한 예입니다 :외부 라이브러리에서 sqlalchemy 모델을 상속하는 사용자 정의 클래스를 만드는 방법

class SQLAlchemyBase(object): 

    metadata = None 
    Session = None 
    Base = object 
    sessionfactory = sessionmaker() 

    def initialize(self, host): 
     engine = create_engine(host) 
     self.metadata = MetaData(bind=engine) 
     self.Session = scoped_session(self.sessionfactory) 
     self.Base = declarative_base(metadata=self.metadata) 

models = SQLAlchemyBase() 

(모델은 models.Base에서 상속) 그래서 SQLAlchemyBase 메인 저장소로 가져옵니다

, 초기화 방법은의 호스트에 전달 호출됩니다 엔진으로 변환 한 다음 모델을 가져올 수 있습니다. 주 저장소는 모델과 동일한 이름을 가진 자체 클래스를 가지고 있으며 일반적인 mixin 클래스가 기능을 확장해야하는 추가 메소드를 가지고 있습니다. 그러나 외부 저장소 라이브러리에서 확장 된이 비정상적인 상속을 사용하여 매퍼를 훌륭하게 재생할 수 없으므로 기본 저장소의 클래스를 사용하여 모델 개체를 만들 수 없습니다. 또한 모델 라이브러리에는 여러 수준의 상속 된 다형성 관계가있는 모델이 있습니다. 내가 점점 된 원래 오류는 것이었다

class Foo(models.Base): 

    __tablename__ = "foo" 
    id = Column(Integer, primary_key=True) 
    type = Column(String) 
    foo_bar_id = Column(Integer, ForeignKey("foo_bar.id")) 
    foo_bar = relationship(Foo, backref=backref("foos")) 

    __mapper_args__ = {"polymorphic_on": type} 


class Bar(Foo): 

    __mapper_args__ = {"polymorphic_identity": "bar"} 


class FooBar(models.Base): 

    __tablename__ = "foo_bar" 
    id = Column(Integer, primary_key=True) 

홈페이지 저장소

from separate_library.models import models, Foo as BaseFoo, Bar as BaseBar, FooBar as BaseFooBar 


class Foo(BaseFoo): 

    @classmethod 
    def custom_create_method(cls, **kw): 
     foo_obj = cls(**kw) 
     models.session.add(foo_obj) 
     models.session.flush() 


class Bar(BaseBar): 
    pass 


class FooBar(BaseFooBar): 
    pass 

모델 라이브러리 : 여기에 기본적인 상속 다형성 관계 중 하나와 유사한 예입니다 이렇게 :

InvalidRequestError : 하나 이상의 맵퍼가 초기화하지 못했습니다. 다른 맵퍼 초기화를 진행할 수 없습니다.
원본 예외 :이 선언적베이스의 레지스트리에 경로 Foo에 대해 여러 클래스가 있습니다. 완전한 모듈 공인 경로를 사용하십시오.

그래서 관계의 전체 경로를 시도했습니다.

FlushError : 수집 FooBar.foos의 구성원으로 유형의 항목을 플러시하려고 그런 다음 다음과 같이 나에게 오류를 제공하기 시작했다. 이 객체는, 형태의 객체 또는이 형태의 다형성 서브 클래스가 필요합니다. if의 하위 클래스 인 경우 매퍼 Mapper|Foo|foo을 구성하여이 하위 유형을 다형 적으로로드하거나 enable_typechecks=False을 설정하여 모든 하위 유형을 플러시 할 수 있도록 허용합니다.

기본적으로 주 모듈의 클래스가 모델 클래스를 가리키고 동작하도록하는 것이 주된 문제입니다. 예를 들어 관계를 만들려고 할 때 main_module.models.Foo 대신 separate_library.models.Foo 유형의 개체가 필요하다고합니다. 또한 다형성 관계에서 polymorphic_onity 열에 대한 polymorphic_identity를 채울 수 없습니다. 예를 들어, 객체가 처음 생성 될 때 주 저장소의 Bar는 type 열을 비 웁니다.

내가 시도한 한 가지 아이디어는 모델 라이브러리의 선언적 기본에 메타 클래스를 추가하고 초기화 중에 __init__ 메서드에서 매퍼를 수정하는 것이 었습니다. 나는 이런 식으로 진전을 보였지만 완전히 작동하지는 못했다.

복잡한 설명을 드려 죄송합니다. 그러나 이것은 복잡한 문제입니다. 불행히도 모델이나 유스 케이스에 대해서는 아무 것도 변경할 수 없다. 이러한 제약 조건 안에서 작업해야합니다. 누구나 모델 저장소의 모델처럼 작동하도록 기본 저장소의 클래스에 대한 매퍼를 구성하는 방법에 대한 아이디어를 제공 할 수 있다면 매우 감사하게 생각합니다.

답변

3

여기에 세 가지 문제가 있습니다 : 당신은 서브 클래스 FooBar 아닌 BaseFooBar을 참조하기 foo_bar = relationship(FooBar, backref=backref("foos"))FooBar 요구 쓰기

  1. .
  2. Bar은 상속 메커니즘이 작동하려면 Foo에서 상속해야합니다. BaseFoo에서 상속받을 수 없습니다.
  3. 기본 클래스에 매퍼가 연결되어 있으면 안됩니다. 그렇지 않으면 상속 메커니즘이 깨어납니다. 순서대로 문제

용액 :

  1. 클래스 이름을 참조하는 문자열을 사용합니다. 이것은 소비자들로 하여금 그들의 수업을 특정한 방식으로 명명하도록 제한한다. 지금이 제한을 받아 들여야합니다.
  2. 메타 클래스를 사용하여 기본 클래스를 동적으로 변경할 수 있습니다. SQLAlchemy의 선언 확장이 메타 클래스를 자유롭게 사용하기 때문에 메타 클래스는 Base의 메타 클래스에서 파생되어야합니다. 메타 클래스 접근법은 문제 1을 유연한 방법으로 해결할 수 있음을 알 수 있습니다.
  3. __abstract__ = True을 사용하십시오.

가장 간단한 예 :

from sqlalchemy import * 
from sqlalchemy.ext.declarative import declarative_base, declared_attr, DeclarativeMeta 

class BaseMeta(DeclarativeMeta): 
    def __new__(cls, name, bases, attrs): 
     if not attrs.get("__abstract__"): 
      if len(bases) != 1: 
       # you'll need to have multiple inheritance if you have that 
       # as well 
       raise NotImplementedError() 
      base, = bases 
      extra_bases = tuple(b._impl for b in base.__bases__ 
           if hasattr(b, "_impl")) 
      bases += extra_bases 
      self = super(BaseMeta, cls).__new__(cls, name, bases, attrs) 
      if getattr(base, "__abstract__", False): 
       base._impl = self 
      return self 
     else: 
      return super(BaseMeta, cls).__new__(cls, name, bases, attrs) 

Base = declarative_base(metaclass=BaseMeta) 

class BaseFoo(Base): 
    __abstract__ = True 

    __tablename__ = "foo" 
    id = Column(Integer, primary_key=True) 
    type = Column(String) 

    @declared_attr 
    def foo_bar_id(cls): 
     return Column(Integer, ForeignKey("foo_bar.id")) 

    @declared_attr 
    def foo_bar(cls): 
     return relationship(lambda: BaseFooBar._impl, backref=backref("foos")) 

    __mapper_args__ = {"polymorphic_on": type} 

class BaseBar(BaseFoo): 
    __abstract__ = True 

    __mapper_args__ = {"polymorphic_identity": "bar"} 

class BaseFooBar(Base): 
    __abstract__ = True 

    __tablename__ = "foo_bar" 
    id = Column(Integer, primary_key=True) 

class Foo(BaseFoo): 
    @classmethod 
    def custom_create_method(cls, **kw): 
     foo_obj = cls(**kw) 
     models.session.add(foo_obj) 
     models.session.flush() 

class Bar(BaseBar): 
    pass 

class FooBar(BaseFooBar): 
    pass 

print(Bar.__bases__) # (<class '__main__.BaseBar'>, <class '__main__.Foo'>) 

메타 클래스의 기본적인 아이디어는 사실에 기초하여, Bar의 염기로 클래스 Foo를 주입하는 것을 BaseBarBaseFoo로부터 상속 및 사실 Foo 그런 BaseFoo (상속)을 구현합니다.

다중 상속 지원 또는 우아한 오류 처리와 같이 맨 위에 더 복잡한 항목을 추가 할 수 있습니다 (예 : 보유하고있는 각 기본 클래스에 대한 하위 클래스가 누락되었다는 경고 또는 동일한 기본 클래스에 대한 여러 하위 클래스 제공) .

관련 문제