파이썬에서 최소 플러그인 아키텍처 구축


190

필자는 상당히 기술적 인 청중 (과학자)이 사용하는 Python으로 작성된 응용 프로그램을 가지고 있습니다.

사용자가 응용 프로그램을 확장 가능하게 만드는 좋은 방법, 즉 스크립팅 / 플러그인 아키텍처를 찾고 있습니다.

나는 매우 가벼운 것을 찾고 있습니다. 대부분의 스크립트 또는 플러그인은 타사에서 개발 및 배포하지 않고 몇 분 안에 사용자가 반복 작업을 자동화하고 파일 형식에 대한 지원을 추가하기 위해 채찍질 할 것입니다. 따라서 플러그인에는 절대 최소 상용구 코드가 있어야하며 폴더에 복사하는 것 외에 '설치'가 필요하지 않습니다 (따라서 setuptools 진입 점 또는 Zope 플러그인 아키텍처와 같은 것).

이와 같은 시스템이 이미 있거나 아이디어 / 영감을 살펴 봐야하는 유사한 체계를 구현하는 프로젝트가 있습니까?

답변:


150

Mine은 기본적으로 "plugins"라는 디렉토리로, 메인 앱이 폴링 한 다음 imp.load_module 을 사용 하여 파일을 픽업하고, 모듈 수준 구성 매개 변수를 사용하여 잘 알려진 진입 점을 찾은 다음 거기서 갈 수있는 디렉토리 입니다. 플러그인이 활성화 된 특정 양의 역 동성을 위해 파일 모니터링 기능을 사용하지만 사용하기 편리합니다.

물론, "[복잡한 것 X은 필요하지 않습니다. 단지 가벼운 것을 원합니다."라고 말하는 모든 요구 사항은 한 번에 하나의 발견 된 요구 사항을 다시 구현할 위험이 있습니다. 그러나 그것은 당신이 어쨌든 재미있는 일을 할 수 없다고 말하는 것은 아닙니다 :)


26
고마워요! 귀하의 게시물을 기반으로 작은 자습서를 작성했습니다 : lkubuntu.wordpress.com/2012/10/02/writing-a-python-plugin-api
MiJyn

9
imp모듈은 찬성되지되고 importlib파이썬 3.4에서 시작
b0fh

1
많은 유스 케이스에서 importlib.import_module 을 대신 사용할 수 있습니다 imp.load_module.
Chris Arndt

58

module_example.py:

def plugin_main(*args, **kwargs):
    print args, kwargs

loader.py:

def load_plugin(name):
    mod = __import__("module_%s" % name)
    return mod

def call_plugin(name, *args, **kwargs):
    plugin = load_plugin(name)
    plugin.plugin_main(*args, **kwargs)

call_plugin("example", 1234)

확실히 "최소"이며, 오류 검사가 전혀 없으며, 수많은 보안 문제가있을 수 있으며, 매우 유연하지는 않습니다. 그러나 파이썬의 플러그인 시스템이 얼마나 간단한 지 보여줍니다 ..

당신은 아마 보길 원하는 꼬마 도깨비 당신이 단지와 많이 할 수 있지만, 너무 모듈 __import__, os.listdir일부 문자열 조작을.


4
난 당신이 변경할 수 있습니다 생각 def call_plugin(name, *args)def call_plugin(name, *args, **kwargs), 다음 plugin.plugin_main(*args)plugin.plugin_main(*args, **kwargs)
론 클라인

12
파이썬 3에서, imp찬성되지 않습니다importlib
아담 박스터


25

그 질문은 정말 흥미롭지 만 자세한 내용은 대답하기가 상당히 어렵다고 생각합니다. 어떤 종류의 응용입니까? GUI가 있습니까? 명령 줄 도구입니까? 스크립트 세트? 독특한 진입 점 등을 가진 프로그램 ...

내가 가진 작은 정보를 감안할 때, 나는 매우 일반적인 방식으로 답변 할 것입니다.

플러그인을 추가해야하는 것은 무엇입니까?

  • 로드 할 경로 / 디렉토리를 나열하는 구성 파일을 추가해야합니다.
  • 또 다른 방법은 "그 플러그인 / 디렉토리의 모든 파일이로드됩니다"라고 말하지만 사용자가 파일을 이동해야하는 불편 함이 있습니다.
  • 마지막으로 중간 옵션은 모든 플러그인이 동일한 plugin / 폴더에 있어야하고 구성 파일의 상대 경로를 사용하여 활성화 / 비활성화하는 것입니다.

순수한 코드 / 디자인 실습에서는 사용자가 확장하려는 행동 / 특정 작업을 명확하게 결정해야합니다. 항상 대체 될 공통 진입 점 / 기능 세트를 식별하고 이러한 조치 내의 그룹을 결정하십시오. 이 작업이 완료되면 응용 프로그램을 쉽게 확장 할 수 있어야합니다.

MediaWiki에서 영감을 얻은 hooks 사용 예제 (PHP이지만 언어는 실제로 중요합니까?) :

import hooks

# In your core code, on key points, you allow user to run actions:
def compute(...):
    try:
        hooks.runHook(hooks.registered.beforeCompute)
    except hooks.hookException:
        print('Error while executing plugin')

    # [compute main code] ...

    try:
        hooks.runHook(hooks.registered.afterCompute)
    except hooks.hookException:
        print('Error while executing plugin')

# The idea is to insert possibilities for users to extend the behavior 
# where it matters.
# If you need to, pass context parameters to runHook. Remember that
# runHook can be defined as a runHook(*args, **kwargs) function, not
# requiring you to define a common interface for *all* hooks. Quite flexible :)

# --------------------

# And in the plugin code:
# [...] plugin magic
def doStuff():
    # ....
# and register the functionalities in hooks

# doStuff will be called at the end of each core.compute() call
hooks.registered.afterCompute.append(doStuff)

수은에서 영감을 얻은 또 다른 예. 여기서 확장은 hg 명령 줄 실행 파일 에만 명령을 추가 하여 동작을 확장합니다.

def doStuff(ui, repo, *args, **kwargs):
    # when called, a extension function always receives:
    # * an ui object (user interface, prints, warnings, etc)
    # * a repository object (main object from which most operations are doable)
    # * command-line arguments that were not used by the core program

    doMoreMagicStuff()
    obj = maybeCreateSomeObjects()

# each extension defines a commands dictionary in the main extension file
commands = { 'newcommand': doStuff }

두 가지 방법 모두 확장에 대한 공통 초기화마무리 가 필요할 수 있습니다 . 모든 확장이 구현해야하는 공통 인터페이스를 사용하거나 (두 번째 접근 방식에 더 적합합니다. 머큐리얼은 모든 확장에 대해 호출되는 reposetup (ui, repo)를 사용합니다) 또는 hooks.setup 후크.

그러나 더 유용한 답변을 원한다면 질문 범위를 좁혀 야합니다.)


11

Marty Allchin의 간단한 플러그인 프레임 워크 는 내가 필요로하는 기반입니다. 나는 그것을 보라고 정말로 권합니다. 단순하고 쉽게 해킹 할 수있는 것을 원한다면 정말 좋은 시작이라고 생각합니다. Django Snippets 로도 찾을 수 있습니다 .


나는 pyduck을 기반으로 그런 일을하려고합니다.
edomaur

내가 말할 수있는 것은 장고에 따라 다릅니다.
Zoran Pavlovic

3
@ZoranPavlovic : 표준 파이썬의 일부 라인은 아니므로 장고를 사용할 필요가 없습니다.
edomaur 2018

11

저는 디지털 마이크로 그래프를 다루고 SGi 머신에서 실행하기 위해 이미지 처리 및 분석 패키지 (기술적으로 라이브러리가 아님)를 작성해야하는 은퇴 한 생물 학자입니다. C로 코드를 작성하고 스크립팅 언어로 Tcl을 사용했습니다. GUI는 Tk를 사용하여 수행되었습니다. Tcl에 나타난 명령은 "extensionName commandName arg0 arg1 ... param0 param1 ..."형식, 즉 공백으로 구분 된 간단한 단어와 숫자입니다. Tcl이 "extensionName"하위 문자열을 보았을 때 제어는 C 패키지로 전달되었습니다. 그런 다음 lexer / parser (lex / yacc에서 수행)를 통해 명령을 실행 한 다음 필요에 따라 C 루틴을 호출했습니다.

패키지를 조작하는 명령은 GUI의 창을 통해 하나씩 실행될 수 있지만 배치 작업은 유효한 Tcl 스크립트 인 텍스트 파일을 편집하여 수행되었습니다. 원하는 파일 수준 작업을 수행 한 템플릿을 선택한 다음 실제 디렉토리 및 파일 이름과 패키지 명령을 포함하도록 복사본을 편집합니다. 그것은 매력처럼 작동했습니다. ...까지

1) 세계는 PC로 바뀌었고 2) Tcl의 iffy 조직 구성 기능이 실제로 불편 해지기 시작했을 때 스크립트는 약 500 줄을 넘었습니다. 시간이 지났다 ...

나는 은퇴했고, 파이썬은 발명되었고, Tcl의 완벽한 후계자처럼 보였다. PC에서 C 프로그램을 컴파일하고 C 패키지로 Python을 확장하며 Python / Gt? / Tk? /?에서 GUI를 수행하는 문제에 직면 한 적이 없기 때문에 이제는 포트를 한 적이 없습니다. ?. 그러나 편집 가능한 템플릿 스크립트를 사용한다는 오래된 아이디어는 여전히 실행 가능한 것으로 보입니다. 또한 다음과 같이 네이티브 파이썬 형식으로 패키지 명령을 입력하는 것이 너무 큰 부담이되어서는 안됩니다.

packageName.command (arg0, arg1, ..., param0, param1, ...)

몇 가지 추가 점, 괄호 및 쉼표가 있지만 표시되지 않습니다.

누군가 파이썬에서 lex 및 yacc 버전을 수행 한 것을 본 기억이 있습니다 ( http://www.dabeaz.com/ply/ ). 그래도 필요한 경우 주변에 있습니다.

이 혼란의 요점은 파이썬 자체가 과학자들이 사용할 수있는 원하는 "경량"프론트 엔드 인 것 같습니다. 왜 그렇지 않다고 생각하는지 궁금합니다. 진심입니다.


나중에 추가 : 응용 프로그램 gedit 은 플러그인이 추가 될 것으로 예상하며 해당 사이트에는 몇 분 동안 둘러 본 간단한 플러그인 절차에 대한 가장 명확한 설명이 있습니다. 시험:

https://wiki.gnome.org/Apps/Gedit/PythonPluginHowToOld

여전히 귀하의 질문을 더 잘 이해하고 싶습니다. 1) 과학자가 (Python) 응용 프로그램을 다양한 방식으로 매우 간단하게 사용할 수 있기를 원하는지 또는 2) 과학자가 응용 프로그램에 새로운 기능을 추가 할 수 있는지 여부는 확실하지 않습니다. 선택 # 1은 이미지에 직면 한 상황으로, 현재의 필요에 맞게 수정 된 일반 스크립트를 사용하게되었습니다. 플러그인 아이디어로 연결되는 선택 # 2입니까, 아니면 명령을 실행하는 것이 불가능한 응용 프로그램의 일부입니까?


2
링크 썩음 수리 : Gedit 플러그인 – wiki.gnome.org/Apps/Gedit/PythonPluginHowTo
ohhorob

1
이것은 현대의 생물 학자들이 얼마나 운이 좋은지 명확하고 간결하게 보여주기 때문에 아름다운 글입니다. 그에게 파이썬은 모듈 C 언어를 분석 할 필요가 없도록 모듈 개발자에게 추상화를 제공하는 데 사용되는 모듈 식 스크립팅 언어입니다. 그러나 요즘에는 파이썬으로 모든 것을하는 대신 C를 배우는 생물학자는 거의 없습니다. 모듈을 작성할 때 주요 파이썬 프로그램의 복잡성을 어떻게 제거합니까? 10 년 후, 아마도 프로그램은 Emoji로 작성 될 것이며 모듈은 일련의 그런트를 포함하는 오디오 파일 일 것입니다. 그리고 아마도 괜찮습니다.
JJ

10

Python Decorators를 검색 할 때 간단하지만 유용한 코드 스 니펫을 찾았습니다. 그것은 당신의 필요에 맞지 않지만 매우 고무적 일 수 있습니다.

Scipy 고급 Python # Plugin 등록 시스템

class TextProcessor(object):
    PLUGINS = []

    def process(self, text, plugins=()):
        if plugins is ():
            for plugin in self.PLUGINS:
                text = plugin().process(text)
        else:
            for plugin in plugins:
                text = plugin().process(text)
        return text

    @classmethod
    def plugin(cls, plugin):
        cls.PLUGINS.append(plugin)
        return plugin


@TextProcessor.plugin
class CleanMarkdownBolds(object):
    def process(self, text):
        return text.replace('**', '')

용법:

processor = TextProcessor()
processed = processor.process(text="**foo bar**", plugins=(CleanMarkdownBolds, ))
processed = processor.process(text="**foo bar**")

1
참고 :이 예제에서는 WordProcessor.plugin아무 것도 반환하지 않으므로 ( None) CleanMdashesExtension나중에 클래스를 가져 오면 import가 None됩니다. 플러그인 클래스가 자체적으로 유용한 경우 .plugin클래스 메소드를 작성하십시오 return plugin.
jkmacc

@jkmacc 당신이 맞아요. 귀하의 의견을 듣고 13 일 후에 스 니펫을 수정했습니다. 감사합니다.
guneysus

7

Pycon 2009의 Andre Roberge 박사가 제공 한 다양한 플러그인 아키텍처에 대한 멋진 토론을 즐겼습니다. 그는 플러그인을 구현하는 다양한 방법에 대한 훌륭한 개요를 제공합니다.

A와 사용 가능한 팟 캐스트 일련의 동반 (원숭이 패치에 대한 설명은 다음 두 번째 부분) 여섯 개 블로그 항목 .

결정을 내리기 전에 빨리 들어 보는 것이 좋습니다.


4

나는 최소한의 플러그인 아키텍처를 찾기 위해 여기에 도착했고, 모든 것이 나에게 과장된 것처럼 보였습니다. 그래서 저는 Super Simple Python Plugins를 구현했습니다 . 이를 사용하려면 하나 이상의 디렉토리를 작성 __init__.py하고 각 디렉토리에 특수 파일을 삭제하십시오 . 해당 디렉토리를 가져 오면 다른 모든 Python 파일이 하위 모듈로로드되고 해당 이름이 __all__목록에 배치됩니다 . 그런 다음 해당 모듈의 유효성을 검사 / 초기화 / 등록하는 것은 사용자의 책임입니다. README 파일에 예제가 있습니다.


4

실제로 setuptools 는 프로젝트 문서에서 가져온 다음 예제와 같이 "plugins directory"와 함께 작동합니다. http://peak.telecommunity.com/DevCenter/PkgResources#locating-plugins

사용법 예 :

plugin_dirs = ['foo/plugins'] + sys.path
env = Environment(plugin_dirs)
distributions, errors = working_set.find_plugins(env)
map(working_set.add, distributions)  # add plugins+libs to sys.path
print("Couldn't load plugins due to: %s" % errors)

장기적으로 setuptools 는 충돌이나 요구 사항없이 플러그인을로드 할 수 있기 때문에 훨씬 안전합니다.

또 다른 이점은 원래 응용 프로그램이 신경 쓰지 않아도 동일한 메커니즘을 사용하여 플러그인 자체를 확장 할 수 있다는 것입니다.


3

플러그인 시스템에 대한 또 다른 접근 방식으로 Extend Me project를 확인할 수 있습니다 .

예를 들어 간단한 클래스와 그 확장을 정의 해 봅시다

# Define base class for extensions (mount point)
class MyCoolClass(Extensible):
    my_attr_1 = 25
    def my_method1(self, arg1):
        print('Hello, %s' % arg1)

# Define extension, which implements some aditional logic
# or modifies existing logic of base class (MyCoolClass)
# Also any extension class maby be placed in any module You like,
# It just needs to be imported at start of app
class MyCoolClassExtension1(MyCoolClass):
    def my_method1(self, arg1):
        super(MyCoolClassExtension1, self).my_method1(arg1.upper())

    def my_method2(self, arg1):
        print("Good by, %s" % arg1)

그리고 그것을 사용하십시오 :

>>> my_cool_obj = MyCoolClass()
>>> print(my_cool_obj.my_attr_1)
25
>>> my_cool_obj.my_method1('World')
Hello, WORLD
>>> my_cool_obj.my_method2('World')
Good by, World

그리고 장면 뒤에 숨겨진 것을 보여주십시오.

>>> my_cool_obj.__class__.__bases__
[MyCoolClassExtension1, MyCoolClass]

extend_me 따라서 예에서, 메타 클래스를 통해 라이브러리를 조작 클래스 생성 과정을, 위의 새로운 인스턴스를 생성 할 때 MyCoolClass, 우리 모두의 서브 클래스 새 클래스의 인스턴스를 가지고 MyCoolClassExtensionMyCoolClass파이썬에 모두의 필요 기능 덕분에 다중 상속

클래스 작성을보다 잘 제어하기 위해이 lib에 정의 된 메타 클래스가 거의 없습니다.

  • ExtensibleType -서브 클래 싱으로 간단한 확장 성을 허용

  • ExtensibleByHashType -ExtensibleType과 유사하지만 특수 버전의 클래스를 구축 할 수있어 기본 클래스의 글로벌 확장 및 특수 버전의 클래스 확장 가능

이 lib는 OpenERP Proxy Project 에서 사용되며 충분히 작동하는 것 같습니다!

실제 사용 예를 보려면 OpenERP 프록시 'field_datetime'확장자를 확인하십시오 .

from ..orm.record import Record
import datetime

class RecordDateTime(Record):
    """ Provides auto conversion of datetime fields from
        string got from server to comparable datetime objects
    """

    def _get_field(self, ftype, name):
        res = super(RecordDateTime, self)._get_field(ftype, name)
        if res and ftype == 'date':
            return datetime.datetime.strptime(res, '%Y-%m-%d').date()
        elif res and ftype == 'datetime':
            return datetime.datetime.strptime(res, '%Y-%m-%d %H:%M:%S')
        return res

Record여기에 extesible 객체가 있습니다. RecordDateTime확장입니다.

확장 기능을 사용하려면 확장 클래스가 포함 된 모듈을 가져 오십시오. 위의 경우 Record생성 된 모든 객체는 기본 클래스에 확장 클래스가 있으므로 모든 기능을 갖습니다.

이 라이브러리의 주요 장점은 확장 가능한 개체를 운영하는 코드는 확장에 대해 알 필요가 없으며 확장은 확장 가능한 개체의 모든 것을 변경할 수 있다는 것입니다.


난 당신이 서브 클래스에서 인스턴스화하는 의미 생각, 즉 my_cool_obj = MyCoolClassExtension1()대신my_cool_obj = MyCoolClass()
pylang

아니요, Extensible 클래스는 __new__메서드를 재정의 했으므로 모든 하위 클래스를 자동으로 찾아서 모든 하위 클래스 인 새 클래스를 작성하고이 생성 된 클래스의 새 인스턴스를 반환합니다. 따라서 원래 응용 프로그램은 모든 확장에 대해 알 필요가 없습니다. 이 접근 방식은 최종 사용자가 라이브러리를 쉽게 수정하거나 확장 할 수 있도록 라이브러리를 빌드 할 때 유용합니다. 위의 예에서 MyCoolClass는 라이브러리에 정의되어 사용될 수 있으며 MyCoolClassExtension은 최종 사용자가 정의 할 수 있습니다.
FireMage

답변을위한 또 다른 예가 추가되었습니다
FireMage

3

setuptools에는 EntryPoint가 있습니다 .

진입 점은 다른 배포판에서 사용할 수 있도록 배포에서 Python 객체 (예 : 함수 또는 클래스)를 "광고"하는 간단한 방법입니다. 확장 가능한 응용 프로그램 및 프레임 워크는 특정 배포 또는 sys.path의 모든 활성 배포에서 특정 이름 또는 그룹의 진입 점을 검색 한 다음 원하는대로 광고 된 개체를 검사하거나로드 할 수 있습니다.

AFAIK이 패키지는 pip 또는 virtualenv를 사용하는 경우 항상 사용할 수 있습니다.


2

에서 @ edomaur의 대답에 확장하면 내가 한 번 봐 복용 제안 할 수 simple_plugins 에 의해 영감을 간단한 플러그인 프레임 워크 (뻔뻔한 플러그), 마티 Alchin의 일을 .

프로젝트의 README에 기반한 간단한 사용법 예 :

# All plugin info
>>> BaseHttpResponse.plugins.keys()
['valid_ids', 'instances_sorted_by_id', 'id_to_class', 'instances',
 'classes', 'class_to_id', 'id_to_instance']

# Plugin info can be accessed using either dict...
>>> BaseHttpResponse.plugins['valid_ids']
set([304, 400, 404, 200, 301])

# ... or object notation
>>> BaseHttpResponse.plugins.valid_ids
set([304, 400, 404, 200, 301])

>>> BaseHttpResponse.plugins.classes
set([<class '__main__.NotFound'>, <class '__main__.OK'>,
     <class '__main__.NotModified'>, <class '__main__.BadRequest'>,
     <class '__main__.MovedPermanently'>])

>>> BaseHttpResponse.plugins.id_to_class[200]
<class '__main__.OK'>

>>> BaseHttpResponse.plugins.id_to_instance[200]
<OK: 200>

>>> BaseHttpResponse.plugins.instances_sorted_by_id
[<OK: 200>, <MovedPermanently: 301>, <NotModified: 304>, <BadRequest: 400>, <NotFound: 404>]

# Coerce the passed value into the right instance
>>> BaseHttpResponse.coerce(200)
<OK: 200>

2

파이썬에서 플러그인 프레임 워크를 검색하는 동안이 스레드를 읽는 데 시간을 보냈습니다. 나는 몇 가지를 사용했지만 그들에게 단점 이있었습니다. 다음은 인터페이스가없고 느슨하게 연결된 플러그인 관리 시스템 인 2017 년의 조사를 위해 제시 한 내용입니다 . 나중에로드 . 사용 방법에 대한 자습서 는 다음과 같습니다 .


2

pluginlib 를 사용할 수 있습니다 .

플러그인은 작성하기 쉽고 다른 패키지, 파일 경로 또는 진입 점에서로드 할 수 있습니다.

필요한 메소드를 정의하여 플러그인 상위 클래스를 작성하십시오.

import pluginlib

@pluginlib.Parent('parser')
class Parser(object):

    @pluginlib.abstractmethod
    def parse(self, string):
        pass

상위 클래스를 상속하여 플러그인을 작성하십시오.

import json

class JSON(Parser):
    _alias_ = 'json'

    def parse(self, string):
        return json.loads(string)

플러그인을로드하십시오.

loader = pluginlib.PluginLoader(modules=['sample_plugins'])
plugins = loader.plugins
parser = plugins.parser.json()
print(parser.parse('{"json": "test"}'))

1
예를 주셔서 감사합니다. 나는 하나의 질문으로 어려움을 겪고있다. 다른 패키지에서 플러그인을로드 할 가능성을 언급 했으므로 이미 생각했을 것입니다. 부모 클래스가 어디에 있는지 궁금합니다. 일반적으로 응용 프로그램 패키지 (아마도 별도의 소스 코드 저장소)에 보관하는 것이 좋지만 플러그인의 코드베이스에서 어떻게 상속합니까? 우리는 이것에 대한 전체 응용 프로그램을 가져 옵니까? 아니면 3 번째 패키지 (3 번째 코드 저장소)에 파서 클래스 나 유사한 추상화와 같은 인터페이스 코드가 필요합니까?
JAponte

1
부모 클래스는 응용 프로그램과 동일한 코드베이스에 있지만 아마도 자체 모듈에 있어야합니다. 따라서이라는 패키지의 경우 상위 클래스를 정의하는 위치 foo라는 모듈이있을 수 있습니다 foo.parents. 그런 다음 플러그인을 가져옵니다 foo.parents. 대부분의 사용 사례에서 잘 작동합니다. 'foo'자체도 가져 오기 때문에 순환 가져 오기의 가능성을 피하기 위해 많은 프로젝트가 모듈의 루트를 비워두고 __main__.py파일 또는 진입 점을 사용하여 애플리케이션을 실행합니다.
aviso

1

필자는 필자의 요구에 맞는 작은 플러그인 시스템을 찾으려고 많은 시간을 보냈다. 그러나 나는 자연스럽고 유연한 상속이 이미 있다면 그것을 사용하지 않을 것이라고 생각했습니다.

플러그인에 상속을 사용할 때의 유일한 문제는 가장 구체적인 (상속 트리에서 가장 낮은) 플러그인 클래스가 무엇인지 모른다는 것입니다.

그러나 이것은 기본 클래스의 상속을 추적하는 메타 클래스로 해결할 수 있으며 대부분의 특정 플러그인에서 상속되는 클래스를 빌드 할 수 있습니다 (아래 그림의 'Root extended')

여기에 이미지 설명을 입력하십시오

그래서 나는 그러한 메타 클래스를 코딩하여 해결책을 찾았습니다.

class PluginBaseMeta(type):
    def __new__(mcls, name, bases, namespace):
        cls = super(PluginBaseMeta, mcls).__new__(mcls, name, bases, namespace)
        if not hasattr(cls, '__pluginextensions__'):  # parent class
            cls.__pluginextensions__ = {cls}  # set reflects lowest plugins
            cls.__pluginroot__ = cls
            cls.__pluginiscachevalid__ = False
        else:  # subclass
            assert not set(namespace) & {'__pluginextensions__',
                                         '__pluginroot__'}     # only in parent
            exts = cls.__pluginextensions__
            exts.difference_update(set(bases))  # remove parents
            exts.add(cls)  # and add current
            cls.__pluginroot__.__pluginiscachevalid__ = False
        return cls

    @property
    def PluginExtended(cls):
        # After PluginExtended creation we'll have only 1 item in set
        # so this is used for caching, mainly not to create same PluginExtended
        if cls.__pluginroot__.__pluginiscachevalid__:
            return next(iter(cls.__pluginextensions__))  # only 1 item in set
        else:
            name = cls.__pluginroot__.__name__ + 'PluginExtended'
            extended = type(name, tuple(cls.__pluginextensions__), {})
            cls.__pluginroot__.__pluginiscachevalid__ = True
return extended

따라서 메타베이스로 만든 루트 기반이 있고 그로부터 상속되는 플러그인 트리가 있으면 하위 클래스를 지정하여 가장 구체적인 플러그인에서 상속하는 클래스를 자동으로 가져올 수 있습니다.

class RootExtended(RootBase.PluginExtended):
    ... your code here ...

코드베이스는 매우 작으며 (~ 30 줄의 순수 코드) 상속이 허용하는만큼 유연합니다.

관심이 있으시면 https://github.com/thodnev/pluginlib에 참여 하십시오.


1

기초를 살펴볼 수도 있습니다 .

아이디어는 패턴 및 플러그인이라고하는 재사용 가능한 컴포넌트를 중심으로 애플리케이션을 빌드하는 것입니다. 플러그인은에서 파생 된 클래스입니다 GwBasePattern. 기본 예는 다음과 같습니다.

from groundwork import App
from groundwork.patterns import GwBasePattern

class MyPlugin(GwBasePattern):
    def __init__(self, app, **kwargs):
        self.name = "My Plugin"
        super().__init__(app, **kwargs)

    def activate(self): 
        pass

    def deactivate(self):
        pass

my_app = App(plugins=[MyPlugin])       # register plugin
my_app.plugins.activate(["My Plugin"]) # activate it

명령 줄 인터페이스, 신호 또는 공유 객체와 같은 고급 패턴도 처리 할 수 ​​있습니다.

Groundwork는 위와 같이 또는 프로그래밍 방식으로 플러그인을 앱에 프로그래밍 방식으로 바인딩하여 플러그인을 찾습니다 setuptools. 플러그인을 포함하는 파이썬 패키지는 특별한 진입 점을 사용하여 선언해야합니다 groundwork.plugin.

여기 문서가 있습니다.

면책 조항 : 저는 Groundwork의 저자 중 하나입니다.


0

현재 건강 관리 제품에는 인터페이스 클래스로 구현 된 플러그인 아키텍처가 있습니다. 우리의 기술 스택은 API 용 Python 위에 Django이고 프론트 엔드 용 nodejs 위에 Nuxtjs입니다.

우리는 Django 및 Nuxtjs를 준수하는 기본적으로 pip 및 npm 패키지 인 제품을 위해 작성된 플러그인 관리자 앱을 보유하고 있습니다.

새로운 플러그인 개발 (pip 및 npm)을 위해 플러그인 관리자를 종속성으로 만들었습니다.

Pip 패키지 : setup.py의 도움으로 플러그인의 엔트리 포인트를 추가하여 플러그인 관리자 (레지스트리, 시작 등 ...)로 무언가를 할 수 있습니다 https://setuptools.readthedocs.io/en/latest/setuptools .html # automatic-script-creation

npm 패키지 : pip와 유사하게 설치를 처리하기위한 npm 스크립트에 후크가 있습니다. https://docs.npmjs.com/misc/scripts

사용 사례 :

플러그인 개발팀은 현재 핵심 개발팀과 분리되어 있습니다. 플러그인 개발 범위는 제품 범주 중 하나에 정의 된 타사 앱과 통합하기위한 것입니다. 플러그인 인터페이스는 다음과 같이 분류됩니다 :-팩스, 전화, 이메일 등 플러그인 관리자는 새로운 카테고리로 향상 될 수 있습니다.

귀하의 경우 : 어쩌면 하나의 플러그인을 작성하고 작업을 수행하기 위해 동일한 플러그인을 재사용 할 수 있습니다.

플러그인 개발자가 핵심 객체를 재사용해야하는 경우 플러그인 관리자에서 추상화 레벨을 수행하여 해당 플러그인을 사용할 수 있도록 해당 오브젝트를 사용할 수 있습니다.

제품에 구현 된 방식을 공유하는 것만으로도 약간의 아이디어가 제공되기를 바랍니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.