Gunicorn: Problema v20: no se pudo encontrar el objeto de aplicación 'create_app ()' en 'app'

Creado en 9 nov. 2019  ·  47Comentarios  ·  Fuente: benoitc/gunicorn

Me había olvidado de anclar mi versión de gunicorn, y el comando de ejecución comenzó a fallar esta mañana cuando volví a implementar mi aplicación y se actualizó automáticamente a 20.0.

Bajar mi versión de gunicorn a 19.9 solucionó el problema.

Este es el comando que estoy usando para ejecutar mi aplicación:

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

El error es:

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

Comentario más útil

fijo en master. gracias @davidism por el parche!

todos los casos manejados están en estas pruebas: https://github.com/benoitc/gunicorn/commit/19cb68f4c3b55da22581c008659ee62d8c54ab2b#diff -5832adf374920d75d4bf48e546367f53R67

Todos 47 comentarios

También he experimentado este problema, es decir
Failed to find application object 'create_app()' in 'app'
y anclar a la versión 19.9.0 resuelve el problema.

Inicialmente pensé que la solución era cambiar el comando gunicorn de:
gunicorn --bind 0.0.0.0:$PORT app:create_app()
para:
gunicorn --bind 0.0.0.0:$PORT app:create_app
(observe que los corchetes después de create_app ya no están). Inicialmente, todo parece estar bien:

sitio web_1 | [2019-11-10 19:18:54 +0000] [1] [INFO] Arranque de gunicorn 20.0.0
sitio web_1 | [2019-11-10 19:18:54 +0000] [1] [INFO] Escuchando en: http://0.0.0.0 : 8000 (1)
sitio web_1 | [2019-11-10 19:18:54 +0000] [1] [INFO] Usando trabajador: sincronización
sitio web_1 | [2019-11-10 19:18:54 +0000] [11] [INFO] Trabajador de arranque con pid: 11

Pero, por desgracia, es solo un espejismo porque cuando intente cargar el sitio web / punto final de su matraz, dirá:

[2019-11-10 19:20:28 +0000] [11] [ERROR] Error al manejar la solicitud /
sitio web_1 | Rastreo (llamadas recientes más última):
sitio web_1 | Archivo "/usr/local/lib/python3.7/site-packages/gunicorn/workers/sync.py", línea 134, en el identificador
sitio web_1 | self.handle_request (escucha, req, cliente, addr)
sitio web_1 | Archivo "/usr/local/lib/python3.7/site-packages/gunicorn/workers/sync.py", línea 175, en handle_request
sitio web_1 | respiter = self.wsgi (entorno, resp.start_response)
sitio web_1 | TypeError: create_app () toma 0 argumentos posicionales pero se dieron 2

Esto es claramente un problema con la versión 20.0.0 de gunicorn.

Debe estar relacionado con este cambio: https://github.com/benoitc/gunicorn/commit/3701ad9f26a7a4c0a081dfd0f6e97ecb272de515#diff -0b90f794c3e9742c45bf484505e3db8dR377 vía # 2043.

Una forma de arreglarlo de su lado es haber exportado myapp = create_app() en su módulo principal y diez comenzar con app:myapp . Esto debería funcionar, avíseme si no funciona.

Veré si hay que hacer algo allí. @berkerpeksag ¿ necesitaba la eliminación de eval allí?

Debe estar relacionado con este cambio: 3701ad9 # diff-0b90f794c3e9742c45bf484505e3db8dR377 vía # 2043.

Una forma de arreglarlo de su lado es haber exportado myapp = create_app() en su módulo principal y diez comenzar con app:myapp . Esto debería funcionar, avíseme si no funciona.

Veré si hay que hacer algo allí. @berkerpeksag ¿ necesitaba la eliminación de eval allí?

Hice este cambio en mi aplicación y solucioné el bloqueo, Gunicorn ahora puede ejecutar mi aplicación guardando el resultado de create_app() en una variable y exportándolo para que pueda usarse en mi comando de ejecución de Gunicorn

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

my_app = create_app()

gunicorn "app:my_app" --workers 8

Puedo confirmar que hacer lo que @benoitc y @ jrusso1020 sugirieron anteriormente soluciona el problema. ¡Gracias a todos!

No veo que la solución funcione si se requiere el paso de parámetros en el momento del lanzamiento, como:

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

El paso de parámetros funciona en 19.9.0 pero falla en 20.0.0.

@benoitc en caso de que sea útil saberlo, los documentos del matraz recomiendan el patrón app:create_app() cuando se usa gunicorn. Sospecho que algunos de sus nuevos usuarios primero prueban gunicorn como resultado de la creación de aplicaciones de matraces, e intentarán usar la recomendación ahora rota de esos documentos (esa fue mi experiencia al menos).

Puedo comunicarme con ese equipo para pedirles que actualicen, sin embargo, esperaré a que @berkerpeksag intervenga en la caída de exec en caso de que tenga sentido traerlo de vuelta.

@ tjwaterman99 bueno, no estoy seguro de que me guste pasar argumentos de esta manera a una aplicación. No creo que sea una buena idea. Los argumentos deben pasarse a través de env.

Nuestro propio ejemplo de uso de Flask está haciendo lo que describo. Creo que la forma actual es más sencilla de manejar. ¿Pensamientos?

cc @tilgovi @berkerpeksag ^^

FWIW también nos encontramos con esto.

Espero que haya mucha gente siguiendo el patrón de matraces de "fábrica de aplicaciones".
Hay una solución segura, pero al menos el registro de cambios debería mencionar esto como un cambio importante.

No creo que nunca hayamos apoyado intencionalmente usos como app:callable() y app:callable(some, args) . Yo diría que fue un efecto secundario desafortunado de usar eval() en la implementación anterior.

La implementación actual está bastante cerca de lo que hace import_string() Django:

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

Me complacerá mejorar la documentación, agregar una nota de la versión y generar un mensaje de error más descriptivo.

No creo que nunca hayamos admitido intencionalmente usos como app: callable () y app: callable (some, args). Yo diría que fue un efecto secundario desafortunado de usar eval () en la implementación anterior.

Sí estoy de acuerdo. Nunca apoyamos tal forma de iniciar una aplicación por lo que estoy buscando.

Soy +1 por un error más explícito. ¿Quizás deberíamos generar un error si el nombre de la aplicación no es un nombre simple?

Tenga en cuenta que este fue un comportamiento explícito mencionado en la documentación pública para uno de los principales marcos de trabajo de wsgi (matraz), y anteriormente fue compatible con su proyecto. La eliminación de eval evita el inicio diferido de una aplicación, lo cual es un problema si una aplicación es 1) proporcionada por una biblioteca y 2) incurre en costos de configuración no triviales. Si no hay seguridad u otra razón por la que una evaluación sea inapropiada, ¿no sería más sencillo continuar apoyando su comportamiento actual?

En caso de que alguien se encuentre con un caso similar, la solución alternativa adecuada de Python 3.7 en adelante sería falsificar una variable de nivel de módulo creando un nivel de módulo __getattr__ , según este PEP . Eso permitiría una iniciación perezosa (a la aplicación de fábricas) sin golpear el cambio rotundo en gunicorn 20.0.0.

Bueno, realmente nunca apoyamos tal comportamiento, ninguno de nuestros documentos o ejemplos lo usa. Eso no se ajusta a la línea de comandos.

Pero este es realmente un cambio rompedor e inesperado. Entonces estaría a favor de devolver eval y advertir al usuario sobre un comportamiento obsoleto. Quizás también para reemplazarlo y permitir que la gente use un patrón de diseño de "fábrica", podríamos agregar una configuración --init-args :

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

O algo parecido. ¿Pensamientos?

@benoitc Sería excelente admitir métodos de fábrica con una bandera de línea de comandos explícita 😄 Tal vez algo como:

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

(O tal vez otro nombre base, como --factory )

He tenido problemas con este cambio porque ya no hay una forma de ejecutar pruebas fácilmente. Debido a que (i) mi aplicación depende de variables de entorno, (ii) la colección de prueba carga todos los módulos (para pruebas de documentación) y (iii) ya no puedo aplazar la construcción de la aplicación hasta después de la importación, no puedo probar mi proyecto sin agregar una cadena larga de las variables de entorno antes de cada comando de prueba, y la prueba lleva más tiempo de lo que solía.

Como estoy en Python 3.7, creo que puedo solucionar esto con un nivel de módulo __getattr__ , pero para versiones anteriores a 3.7 no creo que haya ninguna solución para este problema además de la degradación.

Creo que admitir métodos de fábrica con una bandera de línea de comandos resolvería este problema. Sin embargo, si me falta una solución obvia, también agradecería otras sugerencias 🙃

@ tjwaterman99 bueno, no estoy seguro de que me guste pasar argumentos de esta manera a una aplicación. No creo que sea una buena idea. Los argumentos deben pasarse a través de env.

Nuestro propio ejemplo de uso de Flask está haciendo lo que describo. Creo que la forma actual es más sencilla de manejar. ¿Pensamientos?

Estoy de acuerdo, creo que pasar argumentos a través del entorno es más intuitivo y anima a los usuarios a tener su configuración en vivo en un solo lugar. Sin embargo, admitir objetos / fábricas invocables es importante al menos para Flask, y quizás también para otros marcos.

+1 por generar una advertencia y proporcionar instrucciones sobre cómo usar Gunicorn con las fábricas antes de desaprobar la próxima versión de exec .

Es lamentable que esto haya sucedido. Tenemos dos opciones de cómo responder. Podríamos volver a cambiar el comportamiento o ayudar a todos a migrar.

Si tuviéramos que volver a cambiar el comportamiento, podría tener sentido retirar el lanzamiento de PyPI, pero creo que esto es demasiado drástico. Gunicorn nunca documentó ni sugirió este uso.

Por lo tanto, sugiero que ayudemos a todos a adaptarse y disculpemos las molestias.

Deberíamos comunicarnos con Flask con un PR para actualizar su documentación. Estoy feliz de hacer eso. Creo que otros ya están documentando el camino de la migración aquí.

Agregaré a las sugerencias que puede ser útil tener un módulo o script _separado_ que importe la fábrica de aplicaciones, la llame y la exporte. Eso puede servir como el punto de entrada de Gunicorn y se puede omitir de doctest y otras herramientas para que no active importaciones no deseadas cuando se ejecutan estas herramientas en desarrollo. Algo como un script __main__.py o web.py puede funcionar para esto.

En el futuro, deberíamos hacer que las versiones candidatas estén disponibles incluso cuando pensamos que las versiones deben ser seguras. Podríamos haber captado esto con un candidato de lanzamiento y luego tener la oportunidad de documentar el cambio radical en nuestras notas de lanzamiento, o desaprobarlo por un ciclo.

No creo que tenga sentido agregar soporte para argumentos de inicialización en la línea de comando. Es demasiado tarde para este lanzamiento; ya admitimos aplicaciones personalizadas para casos de uso avanzados; y muchos marcos tienen sus propias formas recomendadas de pasar configuraciones a las aplicaciones. Gunicorn no debería necesitar proporcionar el suyo. Intentar agregar argumentos para solucionar este problema expande el área de superficie para este tipo de cambio radical en el futuro. Debemos apuntar a minimizar la superficie CLI de Gunicorn tanto como sea práctico.

Deberíamos comunicarnos con Flask con un PR para actualizar su documentación. Estoy feliz de hacer eso. Creo que otros ya están documentando el camino de la migración aquí.

Veo que @ bilalshaikh42 ya ha hecho esto en https://github.com/pallets/flask/pull/3421

(Uno de los mantenedores de Flask aquí)

Si bien estoy totalmente de acuerdo con deshacerme de eval allí, creo que debería haber soporte explícito para las fábricas de aplicaciones. El objetivo de una fábrica de aplicaciones es evitar tener un objeto app importable (ya que usarlo a menudo resulta en un infierno de dependencia circular).

En el flask run cli (solo para desarrollo) agregamos soporte explícito para fábricas de aplicaciones, porque son muy comunes.

Claro, crear un wsgi.py contenga from myapp. import make_app; app = make_app() es fácil. Pero necesito mantener este archivo por separado (lo cual es un inconveniente porque ahora pip install myapp no instalará todo lo necesario para ejecutarlo), o ponerlo en mi paquete (lo que significa que ahora puede importarlo desde dentro la aplicación en sí, lo que sería incorrecto)

En Flask, buscamos una forma explícita de verificar una fábrica de aplicaciones invocables y llamarla sin recurrir a eval ; ¿tal vez podría considerar algo como esto también? Si desea menos magia, incluso podría usar diferentes argumentos CLI para señalar una aplicación y para señalar una fábrica de aplicaciones.

En el futuro, deberíamos hacer que las versiones candidatas estén disponibles incluso cuando pensamos que las versiones deben ser seguras. Podríamos haber captado esto con un candidato de lanzamiento y luego tener la oportunidad de documentar el cambio radical en nuestras notas de lanzamiento, o desaprobarlo por un ciclo.

No estoy seguro de si los RC realmente ayudan; por lo general, la gente no instala / actualiza con --pre (también debido a lo mal que funciona esto; no solo afecta a los paquetes especificados explícitamente, sino a todas las dependencias anidadas sin importar cuán profundas sean, por lo que es muy fácil que alguna dependencia de una dependencia genere una versión preliminar rota), por lo que cualquiera que simplemente no fijó sus versiones no detectará ninguna rotura hasta su lanzamiento real.

Por lo que vale, zope.hookable proporciona una manera fácil de implementar un enfoque de fábrica perezoso sin esencialmente gastos generales (debido a una extensión C opcional). Sin embargo, no hace nada para pasar argumentos adicionales.

# 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

Claro, crear un wsgi.py contenga from myapp. import make_app; app = make_app() es fácil. Pero necesito mantener este archivo por separado (lo cual es un inconveniente porque ahora pip install myapp no instalará todo lo necesario para ejecutarlo), o ponerlo en mi paquete (lo que significa que ahora puede importarlo desde dentro la aplicación en sí, lo que sería incorrecto)

Otra razón por la que tener un wsgi.py en su proyecto es incorrecto: algunas herramientas importan todos los módulos en un proyecto; p.ej. pytest lo hace cuando busca pruebas de documentación.

Otro mantenedor de Flask aquí. @ThiefMaster ya dijo todo lo que quería decir, así que sobre todo reitero mi apoyo a la función.

Estoy de acuerdo con deshacerme de eval , y lo evité en flask run . Puede agregar una versión más restringida del comportamiento anterior. Si la opción de línea de comando tiene parens, suponga que es una fábrica que devuelve la aplicación real. Use literal_eval para analizar el contenido de los parens, luego llame a la fábrica con los parámetros analizados.

Creo que el patrón de fábrica sin un archivo wsgi.py es bastante valioso. Me gustaría ayudar a encontrar una manera de mantenerlo en Gunicorn.

¿A alguien le gustaría armar un PR por literal_eval de cadenas de aplicaciones similares a las de fábrica? Esto estaría en gunicorn.util.import_app .

Necesito agregar pruebas, pero aquí está el código de Flask portado a Gunicorn: https://github.com/benoitc/gunicorn/compare/master...davidism : import-factory

@davidism Si está interesado, aquí hay una función que podría ser útil para cargar aplicaciones desde fábricas de aplicaciones (con pruebas de documentación 😄). Utiliza el analizador AST incorporado de Python para diferenciar los nombres de atributos y las llamadas a funciones (en lugar de una expresión regular). También admite argumentos de palabras clave en la función de fábrica. Todo todavía se evalúa usando ast.parse y ast.literal_eval , por lo que no hay llamadas 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 se toma la decisión de admitir solo las fábricas de aplicaciones sin argumentos, se puede eliminar todo el código de procesamiento de argumentos. Seguirá funcionando bien para diferenciar de forma segura los nombres y las llamadas de fábrica (y será útil para dar mensajes de error específicos a los usuarios cuando intenten pasar argumentos a la fábrica)

@ThiefMaster Todavía no estoy convencido de que debamos apoyar tal patrón. ¿Qué utilidad tiene? ¿Por qué no usar variables de entorno para pasar argumentos personalizados o una configuración si realmente es necesario?

Claro, creando un wsgi.py que contenga desde myapp. import make_app; app = make_app () es fácil. Pero necesito mantener este archivo por separado (lo cual es un inconveniente porque ahora pip install myapp no ​​instalará todo lo necesario para ejecutarlo), o ponerlo en mi paquete (lo que significa que ahora puede importarlo desde la propia aplicación que estaría mal)

No entiendo eso, ¿por qué ese archivo debe mantenerse por separado?

Si está en el paquete, entonces es importable. Entonces, si tiene un proyecto más grande, alguien eventualmente lo importará en lugar de usar current_app etc., es decir, más trabajo cuando se trata de PR que contienen este tipo de errores.

Si está fuera del paquete, no lo obtendrá al hacer un pip install .


FWIW, realmente no me importa pasar argumentos. Por lo general, esos no son necesarios (env vars son, de hecho, el camino a seguir). ¡Pero al menos poder apuntar a una fábrica de aplicaciones invocables en lugar de un objeto de aplicación es increíblemente útil!

¿Por qué no usar variables de entorno para pasar argumentos personalizados o una configuración si realmente es necesario?

pytest carga todos los módulos del proyecto para buscar pruebas. Si tiene un objeto app=Flask() global que depende de las variables de entorno o de un archivo de configuración, ese objeto se cargará al ejecutar las pruebas. Es útil poder ejecutar pruebas sin establecer variables de entorno o archivos de configuración. El patrón de fábrica de aplicaciones es ideal para esto.

El patrón de fábrica con argumentos es algo común debido a algunos tutoriales populares de Flask, por lo que lo admití en flask run . Estoy de acuerdo en que es preferible usar el entorno para configurar la aplicación, por lo que estaría bien con una versión más reducida que admita llamar a una fábrica sin argumentos.

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

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

@tilgovi estoy de acuerdo. Mi problema principal es que no esperábamos que eso rompiera a nadie, por lo que estaba sugiriendo volver a colocar la evaluación (o algo más seguro) y desaprobarlo. Por otro lado, sí, ese comportamiento nunca fue admitido y fue un efecto desafortunado de usar eval : /

@davidismo interesante. Pero, ¿en qué se diferenciaría del uso de un objeto invocable como aplicación?

No estoy seguro de lo que quiere decir, ¿podría dar un ejemplo más específico? Una fábrica devuelve una aplicación WSGI, no es en sí misma una aplicación WSGI.

@davidism me refiero a algo como esto


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

application = make_app()

entonces alguien corre gunicorn -b :8000 somemodule:application

Eso hace que application siempre se evalúe al importar el código, anulando el propósito de la fábrica.

Un WSGI invocable también puede ser una instancia de clase, por lo que quizás esto sea lo que se pretendía:

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

(El ejemplo de zope.hookable es esencialmente el mismo, solo que menos gastos generales en la condición de estado estable).

Tener una aplicación WSGI que cree la aplicación WSGI real no es ideal. Ahora es una llamada de función adicional en cada solicitud de proxy al punto de entrada real. La configuración debe realizarse antes de la primera solicitud, pero ahora se aplaza hasta entonces, lo que hace que la primera solicitud demore (potencialmente mucho) más tiempo.

La funcionalidad en cuestión aquí es una función de fábrica que crea ese objeto basado en la configuración del entorno / tiempo de ejecución, que es útil para desacoplar las partes de la aplicación, evitar importaciones circulares y un aislamiento de prueba más fácil. Tener en algún lugar del código que llame explícitamente a la fábrica frustra el propósito de desacoplamiento, ya que les garantizo que los usuarios pensarán "oh, debería importar este objeto de aplicación ahora" cuando en su lugar deberían usar las funciones disponibles para ellos en Flask.

En este punto, todo lo que pedimos es "si la cadena de importación termina con parens, llame al nombre importado para obtener la aplicación".

Creo que tenemos muchas formas de evitar esto, pero eso solo significa que no somos el público objetivo. Sé que puedo hacer cosas como enviar un script que está fuera del paquete como un punto de entrada para mi contenedor y configurar pytest para ignorarlo, etc., etc., pero queremos preocuparnos por las personas para quienes esto se rompe que están siguiendo tutoriales y podrían No entiendo el rastreo.

Un patrón muy limitado "si el objeto de la aplicación es invocable con cero argumentos, entonces invocarlo como una fábrica" ​​podría funcionar, pero falla si el invocable es en realidad una aplicación WSGI que está mal decorada y no revela sus argumentos tan fácilmente desde la introspección. . Si queremos ser generosos, debemos apoyar todo lo que teníamos antes y evitar eval , así que creo que ese es el camino que debemos seguir.

Realmente aprecio todas las sugerencias y ayudo a todos a resolver esto.

Me gustan las sugerencias de @davidism y @connorbrinton usando literal_eval .

Eso hace que la aplicación siempre se evalúe al importar el código, frustrando el propósito de la fábrica.

Bueno, iniciaría la aplicación en tiempo de ejecución y devolvería un invocable utilizado por los trabajadores. Eso no es tan diferente.

Mi principal reserva sobre ese patrón es que anima a las personas a ejecutar algún código pre-spawn que puede romper las expectativas en HUP o USR2. También rompe la interfaz de usuario actual. ¿Funcionará con usos futuros de gunicorn?

De todos modos las opciones son las siguientes entonces:

  1. podemos considerar que este comportamiento no fue respaldado, indocumentado (en gunicorn). El cambio que se hizo en base a eso.
  2. algunos usuarios confiaban en él y ahora queremos apoyar ese comportamiento

(1) es difícil, pero también el camino lógico, considerando que nunca lo apoyamos.
(2) algún tipo de estética y rompe la interfaz de usuario de la línea de comandos, necesita algunas pruebas / ejemplos para probar en gunicorn, use algo como literal_evals

Podemos apoyar (2) pero me gustaría hacer algunas pruebas. ¿También deberíamos documentarlo?

@tilgovi @jamadden @berkerpeksag @sirkonst ¿cuál es su preferencia?

Parece que otro caso de uso importante fue el acceso a atributos , que no será resuelto por literal_eval .

Por ejemplo, en Plotly Dash, usa el objeto Dash , que internamente tiene una instancia Flask como el atributo server . Algunas personas estaban usando:

gunicorn "module:app.server"

Sin embargo, no estoy seguro de que esto deba ser compatible. flask run tampoco lo admite. Parece que el objeto Dash debería tener un método __call__ que pasa a Flask.__call__ . Además, los documentos de Dash dicen que debe hacer server = app.server y señalar a Gunicorn en eso, por lo que este parece ser principalmente un caso de información incorrecta.

@davidism He estado enfermo hoy, pero lo @tilgovi es buena y el plan general es tener una evaluación segura que reemplace la evaluación anterior.

A los que creo que deberíamos advertirle al usuario que está usando dicha inicialización. Pensamientos ? cc @tilgovi

Intentaré convertir la rama que vinculé anteriormente en un PR con pruebas el sábado, a menos que quisieras crear una implementación diferente.

@davidism adelante. Volveré el domingo donde lo revisaré si es necesario :) ¡Gracias!

Un poco atrasado, trabajando en esto ahora.

@connorbrinton idea genial de usar ast.parse , lo probaré y te incluiré como coautor en el compromiso si voy con él.

Solo quería comentar que hay una respuesta de Stack Overflow algo popular (y bastante antigua) que dirige a los usuarios hacia el comportamiento v19, que podría necesitar una actualización según la elección que se haga: https://stackoverflow.com/questions/ 8495367 / usando-argumentos-de-línea-de-comandos-adicionales-con-gunicorn

fijo en master. gracias @davidism por el parche!

todos los casos manejados están en estas pruebas: https://github.com/benoitc/gunicorn/commit/19cb68f4c3b55da22581c008659ee62d8c54ab2b#diff -5832adf374920d75d4bf48e546367f53R67

¿Fue útil esta página
0 / 5 - 0 calificaciones