Gunicorn: Problème v20 : Impossible de trouver l'objet d'application 'create_app()' dans 'app'

Créé le 9 nov. 2019  ·  47Commentaires  ·  Source: benoitc/gunicorn

J'avais négligé d'épingler ma version de gunicorn, et la commande d'exécution a commencé à s'interrompre ce matin lorsque j'ai redéployé mon application et qu'elle a été automatiquement mise à niveau vers 20.0.

La rétrogradation de ma version de gunicorn à 19,9 a résolu le problème.

Voici la commande que j'utilise pour exécuter mon application :

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

L'erreur est :

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

Commentaire le plus utile

fixé dans le maître. merci @davidism pour le patch !

tous les cas traités sont dans ces tests : https://github.com/benoitc/gunicorn/commit/19cb68f4c3b55da22581c008659ee62d8c54ab2b#diff -5832adf374920d75d4bf48e546367f53R67

Tous les 47 commentaires

J'ai également rencontré ce problème, c'est-à-dire
Failed to find application object 'create_app()' in 'app'
et l'épinglage à la version 19.9.0 résout le problème.

Au départ, je pensais que le correctif consistait à modifier la commande gunicorn de :
gunicorn --bind 0.0.0.0:$PORT app:create_app()
à:
gunicorn --bind 0.0.0.0:$PORT app:create_app
(notez que les crochets après create_app ont maintenant disparu). A priori, tout semble bien :

site Web_1 | [2019-11-10 19:18:54 +0000] [1] [INFO] Démarrage de gunicorn 20.0.0
site Web_1 | [2019-11-10 19:18:54 +0000] [1] [INFO] Écoute sur : http://0.0.0.0 :8000 (1)
site Web_1 | [2019-11-10 19:18:54 +0000] [1] [INFO] Utilisation du travailleur : synchroniser
site Web_1 | [2019-11-10 19:18:54 +0000] [11] [INFO] Amorçage du travailleur avec pid : 11

Mais hélas, ce n'est qu'un mirage car lorsque vous essayez de charger votre site Web/point de terminaison de flacon, il dira :

[2019-11-10 19:20:28 +0000] [11] [ERREUR] Demande de traitement d'erreur /
site Web_1 | Traceback (appel le plus récent en dernier) :
site Web_1 | Fichier "/usr/local/lib/python3.7/site-packages/gunicorn/workers/sync.py", ligne 134, dans le handle
site Web_1 | self.handle_request(auditeur, req, client, addr)
site Web_1 | Fichier "/usr/local/lib/python3.7/site-packages/gunicorn/workers/sync.py", ligne 175, dans handle_request
site Web_1 | respiter = self.wsgi(environ, resp.start_response)
site Web_1 | TypeError : create_app() prend 0 arguments positionnels mais 2 ont été donnés

C'est clairement un problème avec la version 20.0.0 de gunicorn.

Il doit être lié à ce changement : https://github.com/benoitc/gunicorn/commit/3701ad9f26a7a4c0a081dfd0f6e97ecb272de515#diff -0b90f794c3e9742c45bf484505e3db8dR377 via #2043 .

Une façon de résoudre le problème de votre côté est d'avoir exporté myapp = create_app() dans votre module principal et de commencer par app:myapp . Cela devrait fonctionner, faites le moi savoir si ce n'est pas le cas.

Je regarderai si quelque chose doit être fait là-bas. @berkerpeksag pourquoi la suppression du eval était-elle nécessaire là-bas ?

Il doit être lié à ce changement : 3701ad9#diff-0b90f794c3e9742c45bf484505e3db8dR377 via #2043 .

Une façon de résoudre le problème de votre côté est d'avoir exporté myapp = create_app() dans votre module principal et de commencer par app:myapp . Cela devrait fonctionner, faites le moi savoir si ce n'est pas le cas.

Je regarderai si quelque chose doit être fait là-bas. @berkerpeksag pourquoi la suppression du eval était-elle nécessaire là-bas ?

J'ai apporté ce changement dans mon application et corrigé le plantage, Gunicorn est maintenant capable d'exécuter mon application en enregistrant le résultat de create_app() dans une variable et en l'exportant afin qu'il puisse être utilisé dans ma commande d'exécution Gunicorn

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

my_app = create_app()

gunicorn "app:my_app" --workers 8

Je peux confirmer que faire ce que @benoitc et @jrusso1020 ont suggéré ci-dessus résout le problème. Merci a tous!

Je ne vois pas le correctif fonctionner si le passage de paramètres est requis au lancement comme :

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

Le passage de paramètres fonctionne dans 19.9.0 mais échoue dans 20.0.0.

@benoitc au cas où il la documentation sur le flacon recommande le modèle app:create_app() lors de l'utilisation de gunicorn. Je soupçonne que certains de vos nouveaux utilisateurs essaient d'abord gunicorn à la suite de la création d'applications de flacons, et ils essaieront d'utiliser la recommandation maintenant rompue de ces documents (c'était du moins mon expérience).

Je peux contacter cette équipe pour leur demander de mettre à jour, mais j'attendrai que @berkerpeksag se exec au cas où cela aurait du sens de le ramener.

@ tjwaterman99 eh bien, je ne suis pas sûr d'aimer transmettre des arguments de cette façon à une application. Je ne pense pas que ce soit une bonne idée. Les arguments doivent être transmis via env.

Notre propre exemple d'utilisation de Flask fait ce que je décris. Je pense que la méthode actuelle est plus simple à gérer. Les pensées?

cc @tilgovi @berkerpeksag ^^

FWIW, nous rencontrons également cela.

Je suppose qu'il y a beaucoup de gens qui suivent le modèle Flask « usine d'applications ».
Il existe certainement une solution de contournement, mais au moins le journal des modifications devrait le mentionner comme un changement de rupture.

Je ne pense pas que nous ayons jamais intentionnellement soutenu des usages tels que app:callable() et app:callable(some, args) . Je dirais que c'était un effet secondaire malheureux de l'utilisation de eval() dans l'implémentation précédente.

L'implémentation actuelle est assez proche de ce que fait le import_string() Django :

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

Je serais heureux d'améliorer la documentation, d'ajouter une note de version et de générer un message d'erreur plus descriptif.

Je ne pense pas que nous ayons jamais intentionnellement pris en charge des utilisations telles que app:callable () et app:callable (certains, args). Je dirais que c'était un effet secondaire malheureux de l'utilisation de eval() dans l'implémentation précédente.

Oui je suis d'accord. Nous n'avons jamais pris en charge une telle façon de démarrer une application pour autant que je cherche.

Je suis +1 pour une erreur plus explicite. Peut-être devrions-nous lever une erreur si le nom de l'application n'est pas un nom simple ?

Veuillez garder à l'esprit qu'il s'agissait d'un comportement explicite mentionné dans la documentation publique de l'un des principaux frameworks wsgi (flask), et qu'il a déjà été pris en charge par votre projet. La suppression de l'évaluation empêche le démarrage paresseux d'une application, ce qui est un problème si une application est 1) fournie par une bibliothèque et 2) entraîne des coûts d'installation non négligeables. S'il n'y a aucune sécurité ou autre raison pour laquelle une évaluation est inappropriée, ne serait-il pas plus simple de simplement continuer à soutenir votre comportement actuel ?

Au cas où quelqu'un rencontrerait un cas similaire, la solution de contournement appropriée à partir de Python 3.7 consisterait à simuler une variable au niveau du module en créant un __getattr__ niveau du module, conformément à ce PEP . Cela permettrait une initiation paresseuse (à la manière des usines d'applications) sans toucher au changement radical de gunicorn 20.0.0.

Eh bien, nous n'avons jamais vraiment supporté un tel comportement, aucun de nos documents ou exemples ne l'utilise. Cela ne correspond pas à la ligne de commande.

Mais bon, c'est vraiment un changement radical et inattendu. Je serais alors en faveur de remettre le eval et d'avertir l'utilisateur d'un comportement déconseillé. Peut-être aussi pour le remplacer et permettre aux gens d'utiliser un modèle de conception "usine", nous pourrions ajouter un paramètre --init-args :

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

Ou quelque chose comme ça. Les pensées?

@benoitc La prise en charge des méthodes d'usine avec un indicateur de ligne de commande explicite serait excellente 😄 Peut-être quelque chose comme :

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

(Ou peut-être un autre nom de base, comme --factory )

J'ai rencontré des problèmes avec ce changement car il n'y a plus moyen pour moi d'exécuter facilement des tests. Étant donné que (i) mon application dépend des variables d'environnement, (ii) la collection de tests charge tous les modules (pour les doctests) et (iii) je ne peux plus différer la construction de l'application après l'importation, je ne peux pas tester mon projet sans ajouter une longue chaîne des variables d'environnement avant chaque commande de test, et les tests prennent plus de temps qu'auparavant.

Étant donné que je suis sur Python 3.7, je pense que je peux contourner ce problème avec un module __getattr__ , mais pour les versions antérieures à la version 3.7, je ne pense pas qu'il existe de solution à ce problème à part la rétrogradation.

Je pense que la prise en charge des méthodes d'usine avec un indicateur de ligne de commande résoudrait ce problème. S'il me manque une solution évidente, d'autres suggestions seraient également appréciées

@ tjwaterman99 eh bien, je ne suis pas sûr d'aimer transmettre des arguments de cette façon à une application. Je ne pense pas que ce soit une bonne idée. Les arguments doivent être transmis via env.

Notre propre exemple d'utilisation de Flask fait ce que je décris. Je pense que la méthode actuelle est plus simple à gérer. Les pensées?

Je suis d'accord, je pense que passer des arguments via l'environnement est plus intuitif et encourage les utilisateurs à avoir leur configuration en direct au même endroit. Cependant, la prise en charge des objets/usines appelables est importante pour au moins Flask, et peut-être aussi pour d'autres frameworks.

+1 pour avoir émis un avertissement et fourni des instructions sur l'utilisation de Gunicorn avec les usines avant de déprécier la prochaine version de exec .

C'est malheureux que cela se soit produit. Nous avons deux choix de réponse. Nous pourrions changer le comportement ou aider tout le monde à migrer.

Si nous devions modifier le comportement en arrière, il serait peut-être logique de retirer la version de PyPI, mais je pense que c'est trop drastique. Gunicorn n'a jamais documenté ou suggéré cette utilisation.

Par conséquent, je suggère que nous aidions tout le monde à s'adapter et à nous excuser pour la gêne occasionnée.

Nous devrions contacter Flask avec un PR pour mettre à jour leur documentation. Je suis heureux de le faire. Je pense que d'autres documentent déjà le chemin de migration ici.

J'ajouterai aux suggestions qu'il peut être utile d'avoir un module ou un script _séparé_ qui importe la fabrique d'applications, l'appelle et l'exporte. Cela peut servir de point d'entrée Gunicorn et il peut être omis de doctest et d'autres outils afin qu'il ne déclenche pas d'importations indésirables lors de l'exécution de ces outils en développement. Quelque chose comme un script __main__.py ou web.py peut fonctionner pour cela.

À l'avenir, nous devrions rendre les versions candidates disponibles même lorsque nous pensons que les versions devraient être sûres. Nous aurions pu détecter cela avec une version candidate, puis avoir la possibilité de documenter le changement majeur dans nos notes de version, ou de le déprécier pendant un cycle.

Je ne pense pas qu'il soit logique d'ajouter la prise en charge des arguments d'initialisation sur la ligne de commande. Il est trop tard pour cette version ; nous prenons déjà en charge les applications personnalisées pour les cas d'utilisation avancés ; et de nombreux frameworks ont leurs propres méthodes recommandées pour transmettre des paramètres aux applications. Gunicorn ne devrait pas avoir besoin de fournir le sien. Essayer d'ajouter des arguments pour résoudre ce problème étend la surface pour ce type de changement de rupture à l'avenir. Nous devrions viser à minimiser la surface CLI de Gunicorn autant que possible.

Nous devrions contacter Flask avec un PR pour mettre à jour leur documentation. Je suis heureux de le faire. Je pense que d'autres documentent déjà le chemin de migration ici.

Je vois que @bilalshaikh42 l'a déjà fait sur https://github.com/pallets/flask/pull/3421

(L'un des mainteneurs de Flask ici)

Bien que je sois entièrement d'accord pour supprimer eval , je pense qu'il devrait y avoir un support explicite pour les usines d'applications ! L'intérêt d'une fabrique d'applications est d'éviter d'avoir un objet app importable (puisque son utilisation entraîne souvent un enfer de dépendance circulaire).

Dans le flask run cli (uniquement pour le développement), nous avons en fait ajouté une prise en charge explicite des fabriques d'applications, car elles sont si courantes.

Bien sûr, créer un wsgi.py contenant from myapp. import make_app; app = make_app() est facile. Mais je dois soit conserver ce fichier séparément (ce qui est gênant car maintenant pip install myapp n'installera pas tout le nécessaire pour l'exécuter), soit le mettre dans mon package (ce qui signifie que maintenant vous pouvez l'importer de l'intérieur l'application elle-même qui serait fausse)

Dans Flask, nous avons opté pour un moyen explicite de rechercher une fabrique d'applications appelable et de l'appeler sans recourir à eval - peut-être pourriez-vous également envisager quelque chose comme ça ? Si vous voulez moins de magie, vous pouvez même utiliser différents arguments CLI pour pointer vers une application et pour pointer vers une fabrique d'applications.

À l'avenir, nous devrions rendre les versions candidates disponibles même lorsque nous pensons que les versions devraient être sûres. Nous aurions pu détecter cela avec une version candidate, puis avoir la possibilité de documenter le changement majeur dans nos notes de version, ou de le déprécier pendant un cycle.

Je ne sais pas si les RC aident vraiment - en général, les gens n'installent/ne mettent pas à niveau avec --pre (également à cause de la façon dont cela fonctionne - cela n'affecte pas seulement les packages explicitement spécifiés, mais toutes les dépendances imbriquées, quelle que soit leur profondeur, il est donc très facile qu'une dépendance d'une dépendance entraîne une préversion cassée), de sorte que quiconque n'a tout simplement pas épinglé sa version ne remarquera aucune casse jusqu'à sa sortie réelle.

Pour ce que ça vaut, zope.hookable fournit un moyen facile d'implémenter une approche de type usine paresseuse avec essentiellement aucune surcharge (en raison d'une extension C optionnelle). Cependant, cela ne fait rien pour passer des arguments supplémentaires.

# 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

Bien sûr, créer un wsgi.py contenant from myapp. import make_app; app = make_app() est facile. Mais je dois soit conserver ce fichier séparément (ce qui est gênant car maintenant pip install myapp n'installera pas tout le nécessaire pour l'exécuter), soit le mettre dans mon package (ce qui signifie que maintenant vous pouvez l'importer de l'intérieur l'application elle-même qui serait fausse)

Une autre raison pour laquelle un wsgi.py dans votre projet est faux : certains outils importent tous les modules d'un projet ; par exemple. pytest le fait lors de la recherche de doctests.

Un autre mainteneur de Flask ici. @ThiefMaster a déjà dit tout ce que je voulais dire, donc je réitère principalement mon soutien à la fonctionnalité.

Je suis d'accord pour me débarrasser de eval , et je l'ai évité dans flask run . Vous pouvez ajouter une version plus contrainte du comportement précédent. Si l'option de ligne de commande a des parenthèses, supposez qu'il s'agit d'une usine qui renvoie la vraie application. Utilisez literal_eval pour analyser le contenu des parenthèses, puis appelez l'usine avec les paramètres analysés.

Je pense que le modèle d'usine sans fichier wsgi.py est assez précieux. J'aimerais aider à trouver un moyen de le garder dans Gunicorn.

Quelqu'un aimerait-il créer un PR pour literal_eval de chaînes d'application de type usine ? Ce serait dans gunicorn.util.import_app .

Besoin d'ajouter des tests, mais voici le code de Flask porté sur Gunicorn : https://github.com/benoitc/gunicorn/compare/master...davidism :import-factory

@davidism Si cela vous intéresse, voici une fonction qui pourrait être utile pour charger des applications depuis des usines d'applications (avec doctests 😄). Il utilise l'analyseur AST intégré de Python pour différencier les noms d'attributs et les appels de fonction (plutôt qu'une expression régulière). Il prend également en charge les arguments de mot-clé dans la fonction d'usine. Tout est toujours évalué en utilisant ast.parse et ast.literal_eval , donc il n'y a pas d'appels eval :

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))

Si la décision est prise de ne prendre en charge que les fabriques d'applications sans argument, tout le code de traitement des arguments peut être supprimé. Cela fonctionnera toujours bien pour différencier en toute sécurité les noms et les appels d'usine (et sera utile pour donner des messages d'erreur spécifiques aux utilisateurs lorsqu'ils essaient de passer des arguments à l'usine)

@ThiefMaster Je ne suis toujours pas convaincu que nous devrions soutenir un tel modèle. En quoi est-ce utile ? Pourquoi ne pas utiliser des variables d'environnement pour transmettre des arguments personnalisés ou une configuration si cela est vraiment nécessaire ?

Bien sûr, créer un wsgi.py contenant de myapp. importer make_app; app = make_app() est simple. Mais je dois soit conserver ce fichier séparément (ce qui est gênant car maintenant pip install myapp n'installera pas tout le nécessaire pour l'exécuter), soit le mettre dans mon package (ce qui signifie que maintenant vous pouvez l'importer depuis l'application elle-même ce qui serait faux)

Je ne comprends pas cela, pourquoi un tel fichier doit-il être maintenu séparément ?

Si c'est dans le package, alors c'est importable. Donc, si vous avez un projet plus important, quelqu'un finira par l'importer au lieu d'utiliser current_app etc.

Si c'est en dehors du package, vous ne l'obtiendrez pas en faisant un pip install .


FWIW, je ne me soucie pas vraiment de passer des arguments. Habituellement, ceux-ci ne sont pas nécessaires (les vars env sont en effet la voie à suivre). Mais au moins, pouvoir pointer vers une fabrique d'applications appelable au lieu d'un objet d'application est incroyablement utile !

Pourquoi ne pas utiliser des variables d'environnement pour transmettre des arguments personnalisés ou une configuration si cela est vraiment nécessaire ?

pytest charge chaque module du projet pour trouver des tests. Si vous avez un objet global app=Flask() qui dépend de variables d'environnement ou d'un fichier de configuration, cet objet sera chargé lors de l'exécution des tests. Il est utile de pouvoir exécuter des tests sans définir de variables d'environnement ou de fichiers de configuration. Le modèle d'usine d'applications est idéal pour cela.

Le modèle d'usine avec arguments est assez courant en raison de certains tutoriels Flask populaires, c'est pourquoi je l'ai pris en charge dans flask run . Je suis d'accord qu'il est préférable d'utiliser l'environnement pour configurer l'application, donc je serais bien avec une version plus réduite qui prend en charge l'appel d'une usine sans arguments.

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

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

@tilgovi je suis d'accord. Mon principal problème est que nous ne nous attendions pas à ce que cela casse quelqu'un, c'est pourquoi je suggérais de remettre l'eval (ou quelque chose de plus sûr) et de le déprécier. D'un autre côté, oui, ce comportement n'a jamais été pris en charge et était un effet malheureux de l'utilisation de eval :/

@davidisme intéressant. Mais en quoi serait-ce différent de l'utilisation d'un objet appelable en tant qu'application ?

Je ne sais pas ce que vous voulez dire, pourriez-vous donner un exemple plus précis? Une fabrique renvoie une application WSGI, ce n'est pas elle-même une application WSGI.

@davidism je veux dire quelque chose comme ça


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

application = make_app()

alors quelqu'un court gunicorn -b :8000 somemodule:application

Cela fait que application toujours évalué lors de l'importation du code, ce qui va à l'encontre de l'objectif de l'usine.

Un appelable WSGI peut également être une instance de classe, alors c'est peut-être ce qui était prévu :

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()

(L'exemple zope.hookable est essentiellement le même, juste moins de frais généraux dans l'état d'équilibre.)

Avoir une application WSGI qui crée la véritable application WSGI n'est pas idéal. Il s'agit désormais d'un appel de fonction supplémentaire à chaque demande de proxy vers le point d'entrée réel. La configuration doit être effectuée avant la première demande, mais elle est maintenant différée jusque-là, ce qui rend la première demande (potentiellement beaucoup) plus longue.

La fonctionnalité en question ici est une fonction d'usine qui crée cet objet en fonction de la configuration d'exécution / d'environnement, ce qui est utile pour découpler les parties de l'application, éviter les importations circulaires et faciliter l'isolation des tests. Avoir quelque part dans le code qui appelle explicitement l'usine va à l'encontre de l'objectif de découplage, car je vous garantis que les utilisateurs penseront alors "oh, je devrais importer cet objet d'application maintenant" alors qu'ils devraient plutôt utiliser les fonctionnalités disponibles dans Flask.

À ce stade, tout ce que nous demandons est "si la chaîne d'importation se termine par des parenthèses, appelez le nom importé pour obtenir l'application".

Je pense que nous avons une multitude de moyens de contourner cela, mais cela signifie simplement que nous ne sommes pas le public cible. Je sais que je peux faire des choses comme envoyer un script qui est en dehors du paquet comme point d'entrée pour mon conteneur et configurer pytest pour l'ignorer, etc, etc, mais nous voulons nous soucier des personnes pour qui cela casse qui suivent des tutoriels et pourraient pas comprendre le retraçage.

Un modèle très limité "si l'objet d'application est appelable avec zéro argument, alors invoquez-le en tant qu'usine" peut fonctionner, mais il échoue si l'appelable est en fait une application WSGI mal décorée et ne révèle pas ses arguments si facilement par introspection . Si nous voulons être généreux, nous devons soutenir tout ce que nous avions avant tout en évitant eval , donc je pense que c'est la voie que nous devrions faire.

J'apprécie vraiment toutes les suggestions et aide à résoudre ce problème, tout le monde.

J'aime les suggestions de @davidism et de @connorbrinton en utilisant literal_eval .

Cela fait que l'application est toujours évaluée lors de l'importation du code, ce qui va à l'encontre de l'objectif de l'usine.

Eh bien, cela initierait l'application au moment de l'exécution et renverrait un appelable utilisé par les travailleurs. Ce n'est pas si différent.

Ma principale réserve à propos de ce modèle est qu'il encourage les gens à exécuter du code pré-spawn qui peut briser les attentes sur HUP ou USR2. De plus, cela casse l'interface utilisateur actuelle. Cela fonctionnera-t-il avec les futurs usages du gunicorn ?

Quoi qu'il en soit les choix sont les suivants alors :

  1. nous pouvons considérer que ce comportement n'était pas pris en charge, non documenté (dans gunicorn). Le changement qui a été fait sur cette base.
  2. certains utilisateurs comptaient dessus et nous voulons maintenant

(1) est une prise difficile, mais aussi le chemin logique, étant donné que nous ne l'avons jamais soutenu.
(2) une sorte d'esthétique et casse l'interface utilisateur de la ligne de commande, il faut des tests/exemples pour tester dans gunicorn, utilisez quelque chose comme literal_evals

Nous pouvons soutenir (2) mais j'aimerais faire des tests. Doit-on aussi le documenter ?

@tilgovi @jamadden @berkerpeksag @sirkonst quelle est votre préférence ?

On dirait qu'un autre cas d'utilisation de rupture était l'accès aux attributs , qui ne sera pas résolu par literal_eval .

Par exemple, dans Plotly Dash vous utilisez le Dash objet, qui a l' intérieur d' un Flask par exemple comme server attribut. Certaines personnes utilisaient :

gunicorn "module:app.server"

Je ne suis pas sûr que cela devrait être pris en charge cependant. flask run ne le supporte pas non plus. On dirait que l'objet Dash devrait avoir une méthode __call__ qui passe à Flask.__call__ . De plus, les documents de Dash disent de faire server = app.server et pointent Gunicorn là-dessus, donc cela semble être principalement un cas de transmission d'informations incorrectes.

@davidism j'ai été malade aujourd'hui mais j'y @tilgovi est bonne et le plan général est d'avoir un eval sûr remplaçant l'ancien eval.

Ceux que je pense que nous devrions avertir l'utilisateur qu'il utilise une telle initialisation. Les pensées ? cc @tilgovi

J'essaierai de transformer la branche que j'ai liée ci-dessus en un PR avec des tests samedi, à moins que vous ne vouliez créer une implémentation différente.

@davidisme va de l'avant. Je serai de retour dimanche où je le réviserai si besoin :) Merci !

Un peu en retard, je travaille là-dessus maintenant.

@connorbrinton bonne idée d'utiliser ast.parse , je vais l'essayer et vous inclure en tant que co-auteur sur le commit si je le fais.

Je voulais juste souligner qu'il existe une réponse Stack Overflow assez populaire (et assez ancienne) qui oriente les utilisateurs vers le comportement v19, qui pourrait nécessiter une mise à jour en fonction du choix effectué : https://stackoverflow.com/questions/ 8495367/using-additional-command-line-arguments-with-gunicorn

fixé dans le maître. merci @davidism pour le patch !

tous les cas traités sont dans ces tests : https://github.com/benoitc/gunicorn/commit/19cb68f4c3b55da22581c008659ee62d8c54ab2b#diff -5832adf374920d75d4bf48e546367f53R67

Cette page vous a été utile?
0 / 5 - 0 notes