Gunicorn: v20-Problem: Anwendungsobjekt 'create_app()' in 'app' konnte nicht gefunden werden

Erstellt am 9. Nov. 2019  ·  47Kommentare  ·  Quelle: benoitc/gunicorn

Ich hatte es versäumt, meine Version von gunicorn anzuheften, und der Ausführungsbefehl begann heute Morgen zu brechen, als ich meine App erneut bereitstellte und sie automatisch auf 20.0 aktualisiert wurde.

Ein Downgrade meiner Version von gunicorn auf 19.9 hat das Problem behoben.

Dies ist der Befehl, den ich verwende, um meine App auszuführen:

gunicorn 'app:create_app()' --workers 4 --threads 4 --bind 0.0.0.0:$PORT

Der Fehler ist:

Failed to find application object 'create_app()' in 'app'
( Feedback Requested FeaturApp Investigation

Hilfreichster Kommentar

im Meister fixiert. danke @davidism für den Patch!

alle behandelten Fälle sind in diesen Tests enthalten: https://github.com/benoitc/gunicorn/commit/19cb68f4c3b55da22581c008659ee62d8c54ab2b#diff -5832adf374920d75d4bf48e546367f53R67

Alle 47 Kommentare

Ich habe dieses Problem auch erlebt, dh
Failed to find application object 'create_app()' in 'app'
und das Anheften an Version 19.9.0 behebt das Problem.

Ich dachte zunächst, die Lösung bestand darin, den Gunicorn-Befehl zu ändern von:
gunicorn --bind 0.0.0.0:$PORT app:create_app()
zu:
gunicorn --bind 0.0.0.0:$PORT app:create_app
(Beachten Sie, dass die Klammern nach create_app jetzt weg sind). Zunächst scheint alles gut zu sein:

webseite_1 | [2019-11-10 19:18:54 +0000] [1] [INFO] Gunicorn starten 20.0.0
webseite_1 | [2019-11-10 19:18:54 +0000] [1] [INFO] Hören unter: http://0.0.0.0 :8000 (1)
webseite_1 | [2019-11-10 19:18:54 +0000] [1] [INFO] Worker verwenden: sync
webseite_1 | [2019-11-10 19:18:54 +0000] [11] [INFO] Booten des Workers mit pid: 11

Aber leider ist es nur eine Fata Morgana, denn wenn Sie versuchen, Ihre Flaschen-Website / Ihren Endpunkt zu laden, wird es sagen:

[2019-11-10 19:20:28 +0000] [11] [ERROR] Fehler bei der Bearbeitung der Anfrage /
webseite_1 | Traceback (letzter Anruf zuletzt):
webseite_1 | Datei "/usr/local/lib/python3.7/site-packages/gunicorn/workers/sync.py", Zeile 134, im Handle
webseite_1 | self.handle_request(listener, req, client, addr)
webseite_1 | Datei "/usr/local/lib/python3.7/site-packages/gunicorn/workers/sync.py", Zeile 175, in handle_request
webseite_1 | respiter = self.wsgi(environ, resp.start_response)
webseite_1 | TypeError: create_app() nimmt 0 Positionsargumente an, aber 2 wurden angegeben

Dies ist eindeutig ein Problem mit der Gunicorn-Version 20.0.0.

Es muss mit dieser Änderung zusammenhängen: https://github.com/benoitc/gunicorn/commit/3701ad9f26a7a4c0a081dfd0f6e97ecb272de515#diff -0b90f794c3e9742c45bf484505e3db8dR377 via #2043.

Eine Möglichkeit, dies auf Ihrer Seite zu beheben, besteht darin, myapp = create_app() in Ihr Hauptmodul exportiert zu haben und zehn mit app:myapp . Das sollte funktionieren, lass es mich wissen, wenn es nicht funktioniert.

Ich schau mal, ob da was gemacht werden muss. @berkerpeksag warum war dort die Entfernung der eval nötig?

Es muss mit dieser Änderung zusammenhängen: 3701ad9#diff-0b90f794c3e9742c45bf484505e3db8dR377 über #2043 .

Eine Möglichkeit, dies auf Ihrer Seite zu beheben, besteht darin, myapp = create_app() in Ihr Hauptmodul exportiert zu haben und zehn mit app:myapp . Das sollte funktionieren, lass es mich wissen, wenn es nicht funktioniert.

Ich schau mal, ob da was gemacht werden muss. @berkerpeksag warum war dort die Entfernung der eval nötig?

Ich habe diese Änderung in meiner Anwendung vorgenommen und den Absturz behoben. Gunicorn kann meine Anwendung jetzt ausführen, indem das Ergebnis von create_app() in einer Variablen gespeichert und exportiert wird, damit es in meinem Gunicorn-Ausführungsbefehl verwendet werden kann

# app.py
def create_app():
    ...

my_app = create_app()

gunicorn "app:my_app" --workers 8

Ich kann bestätigen, dass das Problem behoben wird , indem Sie das tun, was @jrusso1020 oben vorgeschlagen haben. Danke an alle!

Ich sehe die Fehlerbehebung nicht, wenn die Parameterübergabe zur Startzeit erforderlich ist, wie zum Beispiel:

gunicorn --chdir hcli_core path "hcli_core :HCLI (" hcli_core sample hfm ").connector".

Die Parameterübergabe funktioniert in 19.9.0, schlägt jedoch in 20.0.0 fehl.

@benoitc Falls es hilfreich ist zu wissen, empfehlen die Flaschendokumente das app:create_app() Muster bei der Verwendung von Gunicorn. Ich vermute, dass einige Ihrer neuen Benutzer zuerst Gunicorn ausprobieren, weil sie Flask-Apps erstellt haben, und sie werden versuchen, die jetzt gebrochene Empfehlung aus diesen Dokumenten zu verwenden (das war zumindest meine Erfahrung).

Ich kann mich an dieses Team wenden , um es um eine Aktualisierung zu bitten, aber ich warte, bis Abnahme von exec berücksichtigt , falls es sinnvoll ist, es wieder

@tjwaterman99 Nun, ich bin mir nicht sicher, ob ich Argumente auf diese Weise an eine App weitergeben möchte. Ich glaube nicht, dass es eine gute Idee ist. Argumente sollten über die env übergeben werden.

Unser eigenes Beispiel für die Verwendung von Flasks tut das, was ich beschreibe. Ich denke, der aktuelle Weg ist einfacher zu handhaben. Die Gedanken?

cc @tilgovi @berkerpeksag ^^

FWIW haben wir auch damit zu tun.

Ich gehe davon aus, dass es ziemlich viele Leute gibt, die dem Flask-Muster der "Anwendungsfabrik" folgen.
Es gibt sicher einen Workaround, aber zumindest sollte dies im Changelog als Breaking Change erwähnt werden.

Ich glaube nicht, dass wir jemals absichtlich Verwendungen wie app:callable() und app:callable(some, args) . Ich würde sagen, es war ein unglücklicher Nebeneffekt der Verwendung von eval() in der vorherigen Implementierung.

Die aktuelle Implementierung kommt dem von Djangos import_string() ziemlich nahe:

https://github.com/django/django/blob/master/django/utils/module_loading.py#L7 -L24

Gerne verbessere ich die Dokumentation, füge einen Versionshinweis hinzu und gebe eine aussagekräftigere Fehlermeldung aus.

Ich glaube nicht, dass wir jemals absichtlich Verwendungen wie app:callable () und app:callable (some, args) unterstützt haben. Ich würde sagen, es war ein unglücklicher Nebeneffekt der Verwendung von eval() in der vorherigen Implementierung.

Ja, ich stimme zu. Wir haben nie einen solchen Weg unterstützt, eine Anwendung zu starten, soweit ich suche.

Ich gebe +1 für einen expliziteren Fehler. Vielleicht sollten wir einen Fehler melden, wenn der Anwendungsname kein einfacher Name ist?

Bitte beachten Sie, dass dies explizites Verhalten war, das in der öffentlichen Dokumentation für eines der wichtigsten wsgi-Frameworks (flask) erwähnt wurde und zuvor von Ihrem Projekt unterstützt wurde. Das Entfernen der Auswertung verhindert eine verzögerte Initiierung einer Anwendung, was ein Problem darstellt, wenn eine Anwendung 1) von einer Bibliothek bereitgestellt wird und 2) nicht triviale Einrichtungskosten verursacht. Wäre es nicht einfacher, Ihr bestehendes Verhalten weiter zu unterstützen, wenn es keine Sicherheit oder einen anderen Grund gibt, warum eine Bewertung unangemessen ist?

Falls jemand auf einen ähnlichen Fall stößt, besteht die geeignete Problemumgehung ab Python 3.7 darin, eine Variable auf Modulebene zu fälschen, indem gemäß diesem PEP ein __getattr__ Modulebene erstellt wird. Das würde eine träge Initiierung (a la Anwendungsfabriken) ermöglichen, ohne den Breaking Change in gunicorn 20.0.0 zu treffen.

Nun, wir haben ein solches Verhalten nie wirklich unterstützt, keines unserer Dokumente oder Beispiele verwendet es. Das passt nicht in die Kommandozeile.

Aber richtig, das ist wirklich eine bahnbrechende Veränderung und unerwartet. Ich wäre dann dafür, die eval und den Benutzer vor einem veralteten Verhalten zu warnen. Vielleicht auch, um es zu ersetzen und die Leute ein "Fabrik"-Designmuster verwenden zu lassen, könnten wir eine Einstellung --init-args hinzufügen:

gunicorn -b :8000 --init-args="arg1,arg2"  app:factory_method

Oder so ähnlich. Die Gedanken?

@benoitc Die Unterstützung von Factory-Methoden mit einem expliziten Befehlszeilen-Flag wäre ausgezeichnet 😄 Vielleicht so etwas wie:

$ gunicorn -b :8000 \
  --callable \
  --callable-arg "abc" \
  --callable-arg "xyz" \
  --callable-kwarg "key" "value" \
  app:factory_method

(Oder vielleicht ein anderer Basisname, wie --factory )

Bei dieser Änderung sind Probleme aufgetreten, da es für mich keine einfache Möglichkeit mehr gibt, Tests durchzuführen. Da (i) meine App von Umgebungsvariablen abhängt, (ii) die Testsammlung alle Module lädt (für Doctests) und (iii) ich die App-Erstellung nicht mehr bis nach dem Import verschieben kann, kann ich mein Projekt nicht testen, ohne einen langen String hinzuzufügen von Umgebungsvariablen vor jedem Testbefehl, und das Testen dauert länger als früher.

Da ich Python 3.7 verwende, denke ich, dass ich dies mit einem __getattr__ Modulebene

Ich denke, die Unterstützung von Factory-Methoden mit einem Befehlszeilen-Flag würde dieses Problem lösen. Wenn mir jedoch eine offensichtliche Lösung fehlt, wären auch andere Vorschläge dankbar 🙃

@tjwaterman99 Nun, ich bin mir nicht sicher, ob ich Argumente auf diese Weise an eine App weitergeben möchte. Ich glaube nicht, dass es eine gute Idee ist. Argumente sollten über die env übergeben werden.

Unser eigenes Beispiel für die Verwendung von Flasks tut das, was ich beschreibe. Ich denke, der aktuelle Weg ist einfacher zu handhaben. Die Gedanken?

Ich stimme zu, ich denke, die Übergabe von Argumenten über die Umgebung ist intuitiver und ermutigt die Benutzer, ihre Konfiguration an einem Ort live zu haben. Die Unterstützung von aufrufbaren Objekten / Fabriken ist jedoch zumindest für Flask und möglicherweise auch für andere Frameworks wichtig.

+1 für das Auslösen einer Warnung und die Bereitstellung von Anweisungen zur Verwendung von Gunicorn mit Fabriken, bevor die nächste Version von exec .

Es ist bedauerlich, dass dies passiert ist. Wir haben zwei Möglichkeiten zu reagieren. Wir könnten das Verhalten wieder ändern oder allen bei der Migration helfen.

Wenn wir das Verhalten wieder ändern würden, wäre es vielleicht sinnvoll, die Veröffentlichung von PyPI zu entfernen, aber ich denke, das ist zu drastisch. Gunicorn hat diese Verwendung nie dokumentiert oder vorgeschlagen.

Daher schlage ich vor, dass wir einfach jedem helfen, sich anzupassen und sich für die Unannehmlichkeiten zu entschuldigen.

Wir sollten Flask mit einer PR kontaktieren, um die Dokumentation zu aktualisieren. Das mache ich gerne. Ich denke, andere dokumentieren hier bereits den Migrationsweg.

Ich werde die Vorschläge ergänzen, dass es nützlich sein kann, ein _separates_ Modul oder Skript zu haben, das die Anwendungsfactory importiert, aufruft und exportiert. Dies kann als Einstiegspunkt für Gunicorn dienen und kann von doctest und anderen Tools weggelassen werden, damit es beim Ausführen dieser Tools in der Entwicklung keine unerwünschten Importe auslöst. Etwas wie ein __main__.py oder web.py Skript kann dafür funktionieren.

In Zukunft sollten wir Release-Kandidaten auch dann zur Verfügung stellen, wenn wir der Meinung sind, dass Releases sicher sein sollten. Wir hätten dies mit einem Release Candidate auffangen können und hatten dann die Gelegenheit, den Breaking Change in unseren Release Notes zu dokumentieren oder für einen Zyklus zu verwerfen.

Ich denke nicht, dass es sinnvoll ist, die Unterstützung für Initialisierungsargumente auf der Befehlszeile hinzuzufügen. Für diese Veröffentlichung ist es zu spät; Wir unterstützen bereits benutzerdefinierte Anwendungen für fortgeschrittene Anwendungsfälle; und viele Frameworks haben ihre eigenen empfohlenen Methoden, um Einstellungen an Anwendungen zu übergeben. Gunicorn sollte keine eigene bereitstellen müssen. Der Versuch, Argumente hinzuzufügen, um dieses Problem zu beheben, erweitert die Oberfläche für diese Art von Breaking Change in der Zukunft. Wir sollten versuchen, die CLI-Oberfläche von Gunicorn so weit wie möglich zu minimieren.

Wir sollten Flask mit einer PR kontaktieren, um die Dokumentation zu aktualisieren. Das mache ich gerne. Ich denke, andere dokumentieren hier bereits den Migrationsweg.

Ich sehe, dass @bilalshaikh42 dies bereits unter https://github.com/pallets/flask/pull/3421 getan hat

(Einer der Flask-Betreuer hier)

Obwohl ich voll und ganz damit einverstanden bin, eval dort loszuwerden, denke ich, dass es explizite Unterstützung für App-Fabriken geben sollte! Der Sinn einer App-Factory besteht darin, ein importierbares app Objekt zu vermeiden (da dies oft zu einer zirkulären Abhängigkeitshölle führt).

In der flask run Cli (nur für die Entwicklung) haben wir tatsächlich explizite Unterstützung für App-Factorys hinzugefügt, weil sie so häufig sind.

Sicher, ein wsgi.py mit from myapp. import make_app; app = make_app() erstellen ist einfach. Aber ich muss diese Datei entweder separat verwalten (was unpraktisch ist, da pip install myapp jetzt nicht alles installiert, was zum Ausführen benötigt wird), oder sie in mein Paket einfügen (was bedeutet, dass Sie sie jetzt von innen importieren können die App selbst, was falsch wäre)

In Flask haben wir uns für eine explizite Methode entschieden , nach einer aufrufbaren App-Factory zu suchen und sie aufzurufen, ohne auf eval - vielleicht könnten Sie so etwas auch in Betracht ziehen? Wenn Sie weniger Magie wünschen, können Sie sogar verschiedene CLI-Argumente verwenden, um auf eine Anwendung und auf eine App-Factory zu verweisen.

In Zukunft sollten wir Release-Kandidaten auch dann zur Verfügung stellen, wenn wir der Meinung sind, dass Releases sicher sein sollten. Wir hätten dies mit einem Release Candidate auffangen können und hatten dann die Gelegenheit, den Breaking Change in unseren Release Notes zu dokumentieren oder für einen Zyklus zu verwerfen.

Ich bin mir nicht sicher, ob RCs wirklich helfen - im Allgemeinen installieren / aktualisieren Leute nicht mit --pre (auch weil dies so schlecht funktioniert - es betrifft nicht nur explizit angegebene Pakete, sondern alle verschachtelten Abhängigkeiten, egal wie tief, Es ist also sehr einfach, dass eine Abhängigkeit einer Abhängigkeit ein defektes Prerelease mit sich bringt).

Für das, was es wert ist, bietet zope.hookable eine einfache Möglichkeit, einen faulen, fabrikähnlichen Ansatz ohne praktisch keinen Overhead (aufgrund einer optionalen C-Erweiterung) zu implementieren. Es hat jedoch nichts mit der Übergabe zusätzlicher Argumente zu tun.

# app.py
from zope.hookable import hookable

def make_app():
    def _(environ, start_response, value=b'Hello'):
        start_response('200 OK',
                       [('Content-Type', 'text/plain')])
        return [value]
    return _

<strong i="8">@hookable</strong>
def app(environ, start_response):
    real_app = make_app()
    app.sethook(real_app)
    return real_app(environ, start_response, b"First time")
$ gunicorn app:app
[2019-11-12 05:53:47 -0600] [12457] [INFO] Starting gunicorn 20.0.0
[2019-11-12 05:53:47 -0600] [12457] [INFO] Listening at: http://127.0.0.1:8000 (12457)
[2019-11-12 05:53:47 -0600] [12457] [INFO] Using worker: sync
[2019-11-12 05:53:47 -0600] [12460] [INFO] Booting worker with pid: 12460
...
% http localhost:8000
HTTP/1.1 200 OK
Connection: close
Content-Type: text/plain
Date: Tue, 12 Nov 2019 11:53:49 GMT
Server: gunicorn/20.0.0
Transfer-Encoding: chunked

First time

% http localhost:8000
HTTP/1.1 200 OK
Connection: close
Content-Type: text/plain
Date: Tue, 12 Nov 2019 11:53:51 GMT
Server: gunicorn/20.0.0
Transfer-Encoding: chunked

Hello

Sicher, ein wsgi.py mit from myapp. import make_app; app = make_app() erstellen ist einfach. Aber ich muss diese Datei entweder separat verwalten (was unpraktisch ist, da pip install myapp jetzt nicht alles installiert, was zum Ausführen benötigt wird), oder sie in mein Paket einfügen (was bedeutet, dass Sie sie jetzt von innen importieren können die App selbst, was falsch wäre)

Ein anderer Grund, ein wsgi.py in Ihrem Projekt zu haben, ist falsch: Einige Tools importieren alle Module in einem Projekt; z.B. pytest tut es bei der Suche nach doctests.

Ein weiterer Flask-Maintainer hier. @ThiefMaster hat bereits alles gesagt, was ich sagen wollte, daher wiederhole ich hauptsächlich meine Unterstützung für die Funktion.

Ich bin damit einverstanden, eval loszuwerden, und ich habe es in flask run vermieden. Sie können eine eingeschränktere Version des vorherigen Verhaltens hinzufügen. Wenn die Befehlszeilenoption parens enthält, nehmen Sie an, dass es sich um eine Factory handelt, die die echte App zurückgibt. Verwenden Sie literal_eval , um den Inhalt der Parens zu analysieren, und rufen Sie dann die Factory mit den geparsten Parametern auf.

Ich denke, das Fabrikmuster ohne eine wsgi.py Datei ist ziemlich wertvoll. Ich würde gerne helfen, einen Weg zu finden, es in Gunicorn zu behalten.

Möchte jemand einen PR für literal_eval fabrikähnlicher Anwendungsstrings zusammenstellen? Dies wäre in gunicorn.util.import_app .

Es müssen Tests hinzugefügt werden, aber hier ist der Code von Flask, der auf Gunicorn portiert wurde: https://github.com/benoitc/gunicorn/compare/master...davidism :import-factory

@davidism Bei Interesse hier eine Funktion, die zum Laden von Apps aus App-Factorys (mit doctests 😄) nützlich sein könnte. Es verwendet Pythons integrierten AST-Parser, um Attributnamen und Funktionsaufrufe zu unterscheiden (anstelle einer Regex). Es unterstützt auch Schlüsselwortargumente in der Factory-Funktion. Alles wird immer noch mit ast.parse und ast.literal_eval ausgewertet, es gibt also keine eval Aufrufe:

import ast
from types import ModuleType
from typing import Any


def get_app(module: ModuleType, obj: str) -> Any:
    """
    Get the app referenced by ``obj`` from the given ``module``.

    Supports either direct named references or app factories, using `ast.literal_eval` for safety.

    Example usage::

        >>> import collections
        >>> get_app(collections, 'Counter')
        <class 'collections.Counter'>
        >>> get_app(collections, 'Counter()')
        Counter()
        >>> get_app(collections, 'import evil_module')  # doctest: +ELLIPSIS
        Traceback (most recent call last):
          ...
        ValueError: Could not parse 'import evil_module' as a reference to a module attribute or app factory.
        >>> get_app(collections, '(lambda: sys.do_evil)()')
        Traceback (most recent call last):
            ...
        ValueError: App factories must be referenced by a simple function name
        >>> get_app(collections, '(1, 2, 3)')
        Traceback (most recent call last):
            ...
        ValueError: Could not parse '(1, 2, 3)' as a reference to a module attribute or app factory.
    """
    # Parse `obj` to an AST expression, handling syntax errors with an informative error
    try:
        # Note that mode='eval' only means that a single expression should be parsed
        # It does not mean that `ast.parse` actually evaluates `obj`
        expression = ast.parse(obj, mode='eval').body
    except SyntaxError as syntax_error:
        raise ValueError("Could not parse '{}' as a reference to a module attribute or app factory.".format(obj)) from syntax_error

    # Handle expressions that just reference a module attribute by name
    if isinstance(expression, ast.Name):
        # Expression is just a name, attempt to get the attribute from the module
        return getattr(module, expression.id)

    # Handle expressions that make a function call (factory)
    if isinstance(expression, ast.Call):
        # Make sure the function name is just a name reference
        if not isinstance(expression.func, ast.Name):
            raise ValueError("App factories must be referenced by a simple function name")

        # Extract the function name, args and kwargs from the call
        try:
            name = expression.func.id
            args = [ast.literal_eval(arg) for arg in expression.args]
            kwargs = {keyword.arg: ast.literal_eval(keyword.value) for keyword in expression.keywords}
        except ValueError as value_error:
            raise ValueError("Could not evaluate factory arguments, please ensure that arguments include only literals.") from value_error

        # Get and call the function, passing in the given arguments:
        return getattr(module, name)(*args, **kwargs)

    # Raise an error, we only support named references and factory methods
    raise ValueError("Could not parse '{}' as a reference to a module attribute or app factory.".format(obj))

Wenn die Entscheidung getroffen wird, nur App-Factorys ohne Argumente zu unterstützen, kann der gesamte Argumentverarbeitungscode entfernt werden. Es funktioniert immer noch gut, Namen und Factory-Aufrufe sicher zu unterscheiden (und ist nützlich, um Benutzern bestimmte Fehlermeldungen zu geben, wenn sie versuchen, Argumente an die Factory zu übergeben).

@ThiefMaster Ich bin immer noch nicht überzeugt, dass wir ein solches Muster unterstützen sollten. Wie ist es nützlich? Warum nicht Umgebungsvariablen verwenden, um benutzerdefinierte Argumente oder eine Konfiguration zu übergeben, wenn dies wirklich erforderlich ist?

Sicher, Erstellen einer wsgi.py-Datei, die aus myapp enthält. make_app importieren; app = make_app() ist einfach. Aber ich muss diese Datei entweder separat verwalten (was unpraktisch ist, da pip install myapp jetzt nicht alles installiert, was zum Ausführen benötigt wird), oder sie in mein Paket einfügen (was bedeutet, dass Sie sie jetzt aus der App selbst importieren können was falsch wäre)

Ich verstehe das nicht, warum muss eine solche Datei separat gepflegt werden?

Wenn es im Paket enthalten ist, kann es importiert werden. Wenn Sie also ein größeres Projekt haben, wird es irgendwann einfach jemand importieren, anstatt current_app usw. zu verwenden. dh mehr Arbeit beim Umgang mit PRs, die solche Fehler enthalten.

Wenn es sich außerhalb des Pakets befindet, erhalten Sie es nicht, wenn Sie pip install .


FWIW, ich interessiere mich nicht wirklich dafür, Argumente zu übergeben. Normalerweise werden diese nicht benötigt (env vars sind in der Tat der richtige Weg). Aber zumindest auf eine aufrufbare App-Factory statt auf ein App-Objekt verweisen zu können, ist unglaublich nützlich!

Warum nicht Umgebungsvariablen verwenden, um benutzerdefinierte Argumente oder eine Konfiguration zu übergeben, wenn dies wirklich erforderlich ist?

pytest lädt jedes Modul im Projekt, um Tests zu finden. Wenn Sie ein globales app=Flask() Objekt haben, das von Umgebungsvariablen oder einer Konfigurationsdatei abhängt, wird dieses Objekt beim Ausführen von Tests geladen. Es ist nützlich, Tests ausführen zu können, ohne Umgebungsvariablen oder Konfigurationsdateien festzulegen. Das App-Factory-Muster ist dafür ideal.

Das Fabrik mit Argumenten-Muster ist aufgrund einiger beliebter Flask-Tutorials etwas üblich, weshalb ich es in flask run . Ich stimme zu, dass es vorzuziehen ist, die Umgebung zum Konfigurieren der App zu verwenden, daher wäre ich mit einer reduzierteren Version einverstanden, die das Aufrufen einer Factory ohne Argumente unterstützt.

# an app is a name only
$ gunicorn module:app

# a factory has (), but no args allowed
$ gunicorn module:factory()

@tilgovi Ich stimme zu. Mein Hauptproblem ist, dass wir nicht erwartet haben, dass dies jemanden kaputt machen würde, daher schlug ich vor, das Eval (oder etwas Sichereres) zurückzusetzen und es abzulehnen. Auf der anderen Seite, ja, dieses Verhalten wurde nie unterstützt und war ein unglücklicher Effekt der Verwendung von eval :/

@davidismus interessant. Aber wie wäre es anders als ein Callable Object als Anwendung zu verwenden?

Ich bin mir nicht sicher was du meinst, kannst du ein konkreteres Beispiel geben? Eine Factory gibt eine WSGI-Anwendung zurück, sie ist selbst keine WSGI-Anwendung.

@davidism ich meine sowas


def make_app():
  from mymodule.application import MainApp
  return MainApp()

application = make_app()

dann läuft jemand gunicorn -b :8000 somemodule:application

Das führt dazu, dass application beim Importieren des Codes immer ausgewertet wird, was den Zweck der Factory zunichte macht.

Ein WSGI-Callable kann auch eine Klasseninstanz sein, also war vielleicht dies beabsichtigt:

class Application:
    _app = None
    def __call__(self, environ, start_response):
        if self._app is None:
            from wherever import make_app
            self._app = make_app()
        return self._app(environ, start_response)

application = Application()

(Das Beispiel zope.hookable ist im Wesentlichen das gleiche, nur mit weniger Overhead im stationären Zustand.)

Eine WSGI-App zu haben, die die echte WSGI-App erstellt, ist nicht ideal. Es ist jetzt ein zusätzlicher Funktionsaufruf bei jeder Anforderung, um an den echten Einstiegspunkt weiterzuleiten. Die Einrichtung sollte vor der ersten Anfrage erfolgen, aber jetzt wird sie bis dahin verschoben, wodurch die erste Anfrage (möglicherweise viel) länger dauert.

Die fragliche Funktionalität ist hier eine Factory-Funktion, die dieses Objekt basierend auf der Laufzeit- / Umgebungskonfiguration erstellt, was nützlich ist, um die Anwendungsteile zu entkoppeln, zirkuläre Importe zu vermeiden und die Testisolierung zu erleichtern. Irgendwo im Code zu haben, der explizit die Fabrik aufruft, vereitelt den Entkopplungszweck, da ich Ihnen garantiere, dass Benutzer dann denken werden "Oh, ich sollte dieses App-Objekt jetzt importieren", wenn sie stattdessen die ihnen in Flask zur Verfügung stehenden Funktionen verwenden sollten.

An dieser Stelle fragen wir nur: "Wenn die Importzeichenfolge mit Klammern endet, rufen Sie den importierten Namen auf, um die App zu erhalten".

Ich denke, wir haben viele Möglichkeiten, dies zu umgehen, aber das bedeutet nur, dass wir nicht die Zielgruppe sind. Ich weiß, dass ich Dinge tun kann, wie zum Beispiel ein Skript, das sich außerhalb des Pakets als Einstiegspunkt für meinen Container befindet, und pytest so konfigurieren kann, dass es ignoriert usw. usw Verstehe die Rückverfolgung nicht.

Ein sehr eingeschränktes Muster "wenn das App-Objekt ohne Argumente aufrufbar ist, dann rufe es als Fabrik auf" könnte funktionieren, aber es schlägt fehl, wenn es sich bei dem aufrufbaren Objekt tatsächlich um eine WSGI-Anwendung handelt, die schlecht dekoriert ist und ihre Argumente aus der Introspektion nicht so leicht preisgibt . Wenn wir großzügig sein wollen, sollten wir alles, was wir bisher hatten, unterstützen und dabei eval vermeiden. Ich denke, das ist der Weg, den wir gehen sollten.

Ich freue mich sehr über alle Vorschläge und helfe bei der Lösung dieses Problems.

Ich mag beide Vorschläge von @davidism und @connorbrinton mit literal_eval .

Dies führt dazu, dass die Anwendung beim Importieren des Codes immer ausgewertet wird, was den Zweck der Factory zunichte macht.

Nun, es würde die App zur Laufzeit initiieren und ein Callable zurückgeben, das von den Arbeitern verwendet wird. Das ist nicht anders.

Mein größter Vorbehalt zu diesem Muster ist, dass es die Leute dazu ermutigt, Code vor dem Spawn auszuführen, der die Erwartungen an HUP oder USR2 brechen kann. Außerdem unterbricht es die aktuelle Benutzeroberfläche. Wird es mit zukünftigen Verwendungen von Gunicorn funktionieren?

Wie auch immer, die Auswahl ist dann die folgende:

  1. wir können davon ausgehen, dass dieses Verhalten nicht unterstützt und nicht dokumentiert war (in Gunicorn). Die darauf basierende Änderung.
  2. einige Benutzer haben sich darauf verlassen und jetzt wollen wir dieses Verhalten unterstützen

(1) ist ein harter Weg, aber auch der logische Weg, wenn man bedenkt, dass wir ihn nie unterstützt haben.
(2) eine Art Ästhetik und unterbricht die Befehlszeilen-Benutzeroberfläche, es sind einige Tests / Beispiele erforderlich, um in gunicorn zu testen, verwenden Sie so etwas wie literal_evals

Wir können (2) unterstützen, aber ich würde gerne einige Tests durchführen. Sollen wir das auch dokumentieren?

@tilgovi @jamadden @berkerpeksag @sirkonst was ist Ihre Präferenz?

Es sieht so aus, als ob ein weiterer bahnbrechender Anwendungsfall der Attributzugriff war , der nicht von literal_eval gelöst wird.

In Plotly Dash verwenden Sie beispielsweise das Dash Objekt, das intern eine Flask Instanz als server Attribut hat. Einige Leute benutzten:

gunicorn "module:app.server"

Ich bin mir nicht sicher, ob dies unterstützt werden sollte. flask run unterstützt es auch nicht. Scheint, als ob das Dash -Objekt eine __call__ -Methode haben sollte, die an Flask.__call__ übergeht. Darüber hinaus sagen die Dokumente von Dash , server = app.server zu tun und Gunicorn darauf hinzuweisen, also scheint dies hauptsächlich ein Fall von falschen Informationen zu sein, die herumgereicht werden.

@davidism ich war heute krank, aber ich werde heute Sonntag nachsehen, um es am Montag zu veröffentlichen. Der Vorschlag von @tilgovi ist gut und der allgemeine Plan besteht darin, ein sicheres Eval zu haben, das das alte Eval ersetzt.

Ich denke, wir sollten den Benutzer warnen, dass er eine solche Initialisierung verwendet. Die Gedanken ? cc @tilgovi

Ich werde versuchen, den oben verlinkten Branch mit Tests am Samstag in eine PR zu verwandeln, es sei denn, Sie wollten eine andere Implementierung erstellen.

@davidismus mach weiter. Ich werde am Sonntag zurück sein, wo ich es bei Bedarf überprüfen werde :) Danke!

Etwas im Rückstand, arbeite jetzt daran.

@connorbrinton coole Idee, ast.parse , ich werde das ausprobieren und dich als Co-Autor in den Commit einbeziehen , wenn ich mitmache.

Wollte nur einstimmen, dass es eine etwas beliebte (und ziemlich alte) Stack Overflow-Antwort gibt, die Benutzer auf das Verhalten von v19 verweist, das je nach getroffener Wahl möglicherweise ein Update benötigt: https://stackoverflow.com/questions/ 8495367/using-additional-commandline-arguments-with-gunicorn

im Meister fixiert. danke @davidism für den Patch!

alle behandelten Fälle sind in diesen Tests enthalten: https://github.com/benoitc/gunicorn/commit/19cb68f4c3b55da22581c008659ee62d8c54ab2b#diff -5832adf374920d75d4bf48e546367f53R67

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen