2016-11-09 1 views
14

Ich entwickle einen Slack-Bot mit Plugins mit Einstiegspunkten. Ich möchte während der Laufzeit ein Plugin dynamisch hinzufügen.Wie können Einstiegspunkte dynamisch hinzugefügt und geladen werden?

Ich habe ein Projekt mit dieser Struktur:

+ ~/my_project_dir/ 
    + my_projects_python_code/ 
    + plugins/ 
     - plugin1.py 
     - plugin2.py 
     - ... 
     - pluginN.py 
    - setup.py 
    - venv/ 
    - install.sh 

Meine setup.py Datei sieht wie folgt aus:

from setuptools import setup, find_packages 

setup(
    name="My_Project_plugins", 
    version="1.0", 
    packages=['plugins'], 
    entry_points=""" 
     [my_project.plugins] 
     plugin1 = plugins.plugin1:plugin1_class 
     plugin2 = plugins.plugin2:plugin2_class 
     ... 
     pluginN = plugins.pluginN:pluginN_class 
    """ 
     ) 

sudo install.sh Laufen macht folgendes:

  1. Kopiert die benötigten Dateien zu /usr/share/my_project_dir/

  2. aktivieren virtualenv bei /usr/share/my_project_dir/venv/bin/activate

  3. Run: python setup.py develop

Dies funktioniert wie erwartet und setzt meine Einstiegspunkte richtig so, dass ich sie durch den Bot nutzen können.

Aber ich möchte ein Plugin zu setup.py hinzufügen und in der Lage sein, es zu verwenden, während der Bot ausgeführt wird. Also ich möchte eine Zeile hinzufügen: pluginN+1 = plugins.pluginN+1:pluginN+1_class und haben pluginN + 1 verfügbar zu verwenden.

Was ich versucht habe/gelernt:

  • Nach /usr/share/my_project_dir/venv/bin/activate ich eine interaktive Python-Shell öffnen und durchlaufen pkg_resources.iter_entry_points(), die alles auflistet, die von dem Anfangszustand von setup.py geladen wurde (dh plugin1 durch Pluginn)

  • wenn ich eine Linie zu setup.py und laufen sudo python setup.py develop und iterieren wieder mit der gleichen Python-Shell hinzufügen, ist es nicht das neue Plugin abholen, aber wenn ich die Shell verlassen und wieder öffnen, wird das neue Plugin gepflückt oben.

  • Ich habe bemerkt, dass, wenn ich den Bot installiert haben, einen Teil des Ausgangs sagt:

    • Copying My_Project_plugins-1.0-py2.7.egg to /usr/share/my_project-dir/venv/lib/python2.7/site-packages
  • Als ich cd /usr/share/my_project_dir/, mein virtualenv aktivieren, und führen Sie setup.py von der Shell sagt es :

    • Creating /usr/local/lib/python2.7/dist-packages/My_Project-plugins.egg-link (link to .) My_Project-plugins 1.0 is already the active version in easy-install.pth
+0

Wenn Sie mehrere Plugins für ein gemeinsames Programm haben, macht etwas wie "pip install", "pip uninstall", "pip search" eine unerwünschte Wahl? –

+0

Das Ziel ist, dass der Bot Plugins während der Laufzeit über eine Leermeldung installiert. Also muss es dynamisch aktualisiert werden und jedes dieser Plugins wird proprietär sein. –

+0

Können Sie näher beschreiben, wie es funktioniert? 1. Bot bekommt Befehl, ein Plugin zu installieren? Was ist "installieren"? Sollte der Bot diese Datei herunterladen oder kopieren? Oder muss es nur eine Funktion von bereits vorhandener Datei aufrufen? 2. Bot ruft eine Funktion auf oder muss ein Skript starten? Wie? –

Antwort

1

Es ist mehr als mindestens 5 Jahre, seit der Zeit, als ich mich zuerst fast die gleiche Frage gestellt, und Ihre Frage ist nun ein Impuls, endlich es herausfinden.

Für mich war es auch interessant, wenn man Einstiegspunkte aus demselben Verzeichnis wie das Skript ohne Installation eines Pakets hinzufügen kann. Obwohl ich immer wusste, dass der einzige Inhalt des Pakets ein Meta mit Einstiegspunkten sein könnte, die einige andere Pakete betrachten.

, jedenfalls hier einige Setup meines Verzeichnis:

ep_test newtover$ tree 
. 
├── foo-0.1.0.dist-info 
│   ├── METADATA 
│   └── entry_points.txt 
└── foo.py 

1 directory, 3 files 

Hier ist der Inhalt von foo.py:

ep_test newtover$ cat foo.py 
def foo1(): 
    print 'foo1' 

def foo2(): 
    print 'foo2' 

des ipython Nun lassen Sie öffnen:

In [1]: def write_ep(lines): # a helper to update entry points file 
    ...:  with open('foo-0.1.0.dist-info/entry_points.txt', 'w') as f1: 
    ...:   print >> f1, '\n'.join(lines) 
    ...:   

In [2]: write_ep([ # only one entry point under foo.test 
    ...: "[foo.test]", 
    ...: "foo_1 = foo:foo1", 
    ...: ]) 

In [3]: !cat foo-0.1.0.dist-info/entry_points.txt 
[foo.test] 
foo1 = foo:foo1 

In [4]: import pkg_resources 

In [5]: ws = pkg_resources.WorkingSet() # here is the answer on the question 

In [6]: list(ws.iter_entry_points('foo.test')) 
Out[6]: [EntryPoint.parse('foo_1 = foo:foo1')] 

In [7]: write_ep([ # two entry points 
    ...: "[foo.test]", 
    ...: "foo_1 = foo:foo1", 
    ...: "foo_2 = foo:foo2" 
    ...: ]) 

In [8]: ws = pkg_resources.WorkingSet() # a new instance of WorkingSet 

mit Standardparametern WorkingSet überprüft einfach jeden Eintrag in sys.p ath, aber Sie können die Liste eingrenzen. pkg_resources.iter_entry_points ist an eine globale Instanz von WorkingSet gebunden.

In [9]: list(ws.iter_entry_points('foo.test')) # both are visible 
Out[9]: [EntryPoint.parse('foo_1 = foo:foo1'), EntryPoint.parse('foo_2 = foo:foo2')] 

In [10]: foos = [ep.load() for ep in ws.iter_entry_points('foo.test')] 

In [11]: for func in foos: print 'name is {}'.format(func.__name__); func() 
name is foo1 
foo1 
name is foo2 
foo2 

Und der Inhalt METADATA auch:

ep_test newtover$ cat foo-0.1.0.dist-info/METADATA 
Metadata-Version: 1.2 
Name: foo 
Version: 0.1.0 
Summary: entry point test 

UPD1: ich dachte, es noch einmal über und verstehen jetzt, dass Sie einen zusätzlichen Schritt benötigen, bevor die neuen Plugins: Sie müssen laden Sie die Module neu.

Dies könnte so einfach sein wie:

In [33]: modules_to_reload = {ep1.module_name for ep1 in ws.iter_entry_points('foo.test')} 

In [34]: for module_name in modules_to_reload: 
    ....:  reload(__import__(module_name)) 
    ....: 

Aber wenn eine neue Version des Plugins Paket auf wesentliche Änderungen in anderen verwendeten Modulen basiert, können Sie eine bestimmte Reihenfolge der Umladung und Nachladen von denen geändert benötigen Module. Dies könnte zu einer mühsamen Aufgabe werden, so dass der Neustart des Bot der einzige Weg wäre.

0

Ich musste etwas ähnliches tun, um ein Dummy-Plugin für Testzwecke zu laden. Dies unterscheidet sich etwas von Ihrem Anwendungsfall dahingehend, dass ich speziell versucht habe, die Eintrittspunkte im Paket zu definieren (da es sich nur um Testcode handelt).

Ich fand ich dynamisch Einträge in die pkg_resources Strukturen Daten einfügen könnte wie folgt:

import pkg_resources 
# Create the fake entry point definition 
ep = pkg_resources.EntryPoint.parse('dummy = dummy_module:DummyPlugin') 

# Create a fake distribution to insert into the global working_set 
d = pkg_resources.Distribution() 

# Add the mapping to the fake EntryPoint 
d._ep_map = {'namespace': {'dummy': ep}} 

# Add the fake distribution to the global working_set 
pkg_resources.working_set.add(d, 'dummy') 

Diese, zur Laufzeit hinzugefügt einen Einstiegspunkt ‚Dummy‘ zu ‚Namensraum‘ genannt, die die Klasse wäre 'DummyPlugin' in 'dummy_module.py'.

Dies wurde durch Verwendung der setuptools docs und dir() auf den Objekten ermittelt, um weitere Informationen nach Bedarf zu erhalten.

Docs sind hier: http://setuptools.readthedocs.io/en/latest/pkg_resources.html

Sie besonders bei http://setuptools.readthedocs.io/en/latest/pkg_resources.html#locating-plugins aussehen könnte, wenn alles, was Sie tun müssen, ein Plugin ist zu laden, die Sie auf Ihrem lokalen Dateisystem gerade gespeichert haben.

Verwandte Themen