Gunicorn: Problema de v20: falha ao encontrar o objeto de aplicativo 'create_app ()' em 'app'

Criado em 9 nov. 2019  ·  47Comentários  ·  Fonte: benoitc/gunicorn

Eu tinha esquecido de fixar minha versão do gunicorn, e o comando run começou a falhar esta manhã quando reimplantei meu aplicativo e ele foi atualizado automaticamente para 20.0.

O downgrade da minha versão do gunicorn para 19.9 corrigiu o problema.

Este é o comando que estou usando para executar meu aplicativo:

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

O erro é:

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

Comentários muito úteis

fixado no mestre. obrigado @davidism pelo patch!

todos os casos tratados estão nestes testes: https://github.com/benoitc/gunicorn/commit/19cb68f4c3b55da22581c008659ee62d8c54ab2b#diff -5832adf374920d75d4bf48e546367f53R67

Todos 47 comentários

Eu também tive esse problema, ou seja,
Failed to find application object 'create_app()' in 'app'
e fixar a versão 19.9.0 resolve o problema.

Eu inicialmente pensei que a correção era mudar o 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 os colchetes após create_app sumiram). Inicialmente, tudo parece bem:

website_1 | [2019-11-10 19:18:54 +0000] [1] [INFO] Iniciando gunicorn 20.0.0
website_1 | [2019-11-10 19:18:54 +0000] [1] [INFO] Ouvindo em: http://0.0.0.0 : 8000 (1)
website_1 | [2019-11-10 19:18:54 +0000] [1] [INFO] Usando trabalhador: sincronizar
website_1 | [2019-11-10 19:18:54 +0000] [11] [INFO] Inicializando trabalhador com pid: 11

Mas, infelizmente, é apenas uma miragem porque quando você tenta carregar seu site / endpoint do frasco, ele dirá:

[2019-11-10 19:20:28 +0000] [11] [ERROR] Erro ao manipular solicitação /
website_1 | Traceback (última chamada mais recente):
website_1 | Arquivo "/usr/local/lib/python3.7/site-packages/gunicorn/workers/sync.py", linha 134, no identificador
website_1 | self.handle_request (ouvinte, req, cliente, addr)
website_1 | Arquivo "/usr/local/lib/python3.7/site-packages/gunicorn/workers/sync.py", linha 175, em handle_request
website_1 | respiter = self.wsgi (amb, resp.start_response)
website_1 | TypeError: create_app () leva 0 argumentos posicionais, mas 2 foram dados

Este é claramente um problema com o gunicorn versão 20.0.0.

Deve estar relacionado a esta mudança: https://github.com/benoitc/gunicorn/commit/3701ad9f26a7a4c0a081dfd0f6e97ecb272de515#diff -0b90f794c3e9742c45bf484505e3db8dR377 via # 2043.

Uma maneira de corrigir isso do seu lado é exportar myapp = create_app() em seu módulo principal e dez começar com app:myapp . Isso deve funcionar, deixe-me saber se não funcionar.

Vou ver se algo precisa ser feito lá. @berkerpeksag por que a remoção de eval foi necessária lá?

Deve estar relacionado a esta mudança: 3701ad9 # diff-0b90f794c3e9742c45bf484505e3db8dR377 via # 2043.

Uma maneira de corrigir isso do seu lado é exportar myapp = create_app() em seu módulo principal e dez começar com app:myapp . Isso deve funcionar, deixe-me saber se não funcionar.

Vou ver se algo precisa ser feito lá. @berkerpeksag por que a remoção de eval foi necessária lá?

Fiz essa alteração em meu aplicativo e consertei o travamento, o Gunicorn agora é capaz de executar meu aplicativo salvando o resultado de create_app() em uma variável e exportando-o para que possa ser usado em meu comando de execução do Gunicorn

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

my_app = create_app()

gunicorn "app:my_app" --workers 8

Posso confirmar que fazer o que @benoitc e @ jrusso1020 sugeriram acima corrige o problema. Obrigado a todos!

Não vejo a correção funcionando se a passagem de parâmetro for necessária no momento da inicialização, como:

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

A passagem de parâmetros funciona em 19.9.0, mas falha em 20.0.0.

@benoitc caso seja útil saber, os documentos do frasco recomendam o padrão app:create_app() ao usar gunicorn. Suspeito que alguns de seus novos usuários experimentam o gunicorn pela primeira vez como resultado da construção de aplicativos flask, e eles tentarão usar a recomendação agora quebrada desses documentos (essa foi, pelo menos, minha experiência).

Posso entrar em contato com essa equipe para pedir uma atualização, no entanto, vou esperar que redução de exec , caso faça sentido trazê-lo de volta.

@ tjwaterman99 bem, não tenho certeza se gosto de passar argumentos dessa forma para um aplicativo. Não acho que seja uma boa ideia. Os argumentos devem ser transmitidos por meio do env.

Nosso próprio exemplo de uso do Flask é fazer o que eu descrevo. Estou pensando que a forma atual é mais simples de manusear. Pensamentos?

cc @tilgovi @berkerpeksag ^^

FWIW, estamos nos deparando com isso também.

Espero que haja muitas pessoas seguindo o padrão Flask de "fábrica de aplicativos".
Existe uma solução alternativa com certeza, mas pelo menos o changelog deve mencionar isso como uma alteração importante.

Acho que nunca apoiamos intencionalmente usos como app:callable() e app:callable(some, args) . Eu diria que foi um efeito colateral infeliz de usar eval() na implementação anterior.

A implementação atual é muito próxima do que import_string() do Django faz:

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

Eu ficaria feliz em melhorar a documentação, adicionar uma nota de versão e levantar uma mensagem de erro mais descritiva.

Acho que nunca oferecemos suporte intencionalmente a usos como app: callable () e app: callable (some, args). Eu diria que foi um efeito colateral infeliz de usar eval () na implementação anterior.

Sim eu concordo. Nunca apoiamos tal forma de iniciar um aplicativo até onde estou olhando.

Sou +1 para um erro mais explícito. Talvez devêssemos gerar um erro se o nome do aplicativo não for um nome simples?

Lembre-se de que esse foi um comportamento explícito mencionado na documentação pública de um dos principais frameworks wsgi (flask), e foi anteriormente suportado por seu projeto. A remoção do eval evita o início lento de um aplicativo, o que é um problema se um aplicativo for 1) fornecido por uma biblioteca e 2) incorrer em custos de configuração não triviais. Se não houver segurança ou outro motivo pelo qual uma avaliação seja inadequada, não seria mais simples continuar a apoiar seu comportamento existente?

No caso de alguém encontrar um caso semelhante, a solução alternativa apropriada do Python 3.7 em diante seria falsificar uma variável de nível de módulo criando um nível de módulo __getattr__ , de acordo com este PEP . Isso permitiria uma iniciação preguiçosa (como as fábricas de aplicativos) sem atingir a alteração significativa no gunicorn 20.0.0.

Bem, nós nunca apoiamos tal comportamento realmente, nenhum de nossos documentos ou exemplos o usa. Isso não se encaixa na linha de comando.

Mas certo, esta é realmente uma mudança significativa e inesperada. Eu seria a favor de colocar de volta eval e alertar o usuário sobre um comportamento obsoleto. Talvez também para substituí-lo e permitir que as pessoas usem um padrão de design de "fábrica", poderíamos adicionar uma configuração --init-args :

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

Ou algo parecido. Pensamentos?

@benoitc Suportar métodos de fábrica com uma sinalização de linha de comando explícita seria excelente 😄 Talvez algo como:

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

(Ou talvez outro nome de base, como --factory )

Tenho enfrentado problemas com essa mudança porque não há mais uma maneira de executar testes facilmente. Como (i) meu aplicativo depende de variáveis ​​de ambiente, (ii) a coleção de teste carrega todos os módulos (para doctests) e (iii) não posso mais adiar a construção do aplicativo até depois da importação, não posso testar meu projeto sem adicionar uma longa string de variáveis ​​de ambiente antes de cada comando de teste, e o teste leva mais tempo do que antes.

Como estou no Python 3.7, acho que posso contornar isso com um nível de módulo __getattr__ , mas para o pré-3.7 não acho que haja outra solução para esse problema além do downgrade.

Acho que o suporte a métodos de fábrica com um sinalizador de linha de comando resolveria esse problema. Se estou perdendo uma solução óbvia, porém, outras sugestões também seriam apreciadas 🙃

@ tjwaterman99 bem, não tenho certeza se gosto de passar argumentos dessa forma para um aplicativo. Não acho que seja uma boa ideia. Os argumentos devem ser transmitidos por meio do env.

Nosso próprio exemplo de uso do Flask é fazer o que eu descrevo. Estou pensando que a forma atual é mais simples de manusear. Pensamentos?

Eu concordo, acho que passar argumentos pelo ambiente é mais intuitivo e incentiva os usuários a ter suas configurações ao vivo em um só lugar. No entanto, o suporte a objetos / fábricas que podem ser chamados é importante pelo menos para o Flask, e talvez para outros frameworks também.

+1 para gerar um aviso e fornecer instruções sobre como usar o Gunicorn com fábricas antes de descontinuar exec próximo lançamento.

É uma pena que isso tenha acontecido. Temos duas opções de como responder. Podemos mudar o comportamento de volta ou podemos ajudar todos na migração.

Se tivéssemos de mudar o comportamento de volta, faria sentido retirar o PyPI, mas acho que isso é muito drástico. Gunicorn nunca documentou ou sugeriu esse uso.

Portanto, sugiro que ajudemos a todos na adaptação e pedimos desculpas pelo transtorno.

Devemos entrar em contato com o Flask com um PR para atualizar sua documentação. Estou feliz em fazer isso. Acho que outros já estão documentando o caminho de migração aqui.

Acrescentarei às sugestões que pode ser útil ter um módulo ou script _separate_ que importa a fábrica de aplicativos, a chama e a exporta. Isso pode servir como ponto de entrada do Gunicorn e pode ser omitido do doctest e outras ferramentas para que não acione importações indesejadas ao executar essas ferramentas no desenvolvimento. Algo como __main__.py ou web.py script pode funcionar para isso.

No futuro, devemos disponibilizar candidatos a lançamento, mesmo quando acharmos que os lançamentos deveriam ser seguros. Poderíamos ter detectado isso com um candidato a lançamento e, em seguida, ter a oportunidade de documentar a alteração significativa em nossas notas de lançamento ou descontinuá-lo por um ciclo.

Não acho que faça sentido adicionar suporte para argumentos de inicialização na linha de comando. É tarde demais para este lançamento; já oferecemos suporte a aplicativos personalizados para casos de uso avançados; e muitos frameworks têm suas próprias maneiras recomendadas de passar configurações para aplicativos. O Gunicorn não precisa fornecer seus próprios. Tentar adicionar argumentos para corrigir esse problema expande a área de superfície para esse tipo de alteração significativa no futuro. Devemos ter como objetivo minimizar a superfície CLI do Gunicorn tanto quanto possível.

Devemos entrar em contato com o Flask com um PR para atualizar sua documentação. Estou feliz em fazer isso. Acho que outros já estão documentando o caminho de migração aqui.

Vejo que @ bilalshaikh42 já fez isso em https://github.com/pallets/flask/pull/3421

(Um dos mantenedores do Flask aqui)

Embora eu concorde totalmente em me livrar de eval lá, acho que deveria haver suporte explícito para fábricas de aplicativos! O objetivo de uma fábrica de aplicativos é evitar ter um objeto app importável (já que usar isso geralmente resulta em um inferno de dependência circular).

No flask run cli (apenas para desenvolvimento), adicionamos suporte explícito para fábricas de aplicativos, porque são muito comuns.

Claro, criar um wsgi.py contendo from myapp. import make_app; app = make_app() é fácil. Mas eu preciso manter este arquivo separadamente (o que é inconveniente porque agora pip install myapp não instalará tudo o que é necessário apenas para executá-lo) ou colocá-lo no meu pacote (o que significa que agora você pode importá-lo de dentro o próprio aplicativo que estaria errado)

No Flask, buscamos uma maneira explícita de verificar se há uma fábrica de aplicativos que pode ser chamada e chamá-la sem recorrer a eval - talvez você possa considerar algo assim também? Se você quiser menos mágica, pode até usar diferentes argumentos CLI para apontar para um aplicativo e para apontar para uma fábrica de aplicativos.

No futuro, devemos disponibilizar candidatos a lançamento, mesmo quando acharmos que os lançamentos deveriam ser seguros. Poderíamos ter detectado isso com um candidato a lançamento e, em seguida, ter a oportunidade de documentar a alteração significativa em nossas notas de lançamento ou descontinuá-lo por um ciclo.

Não tenho certeza se os RCs realmente ajudam - geralmente as pessoas não instalam / atualizam com --pre (também por causa de como isso funciona mal - não afeta apenas os pacotes explicitamente especificados, mas todas as dependências aninhadas, não importa o quão profundo, então é muito fácil que alguma dependência de uma dependência puxa um pré-lançamento quebrado), então qualquer pessoa que simplesmente não fixou suas versões não notará nenhuma quebra até que seja liberada.

Pelo que vale a pena, zope.hookable fornece uma maneira fácil de implementar uma abordagem do tipo fábrica preguiçosa, essencialmente sem sobrecarga (devido a uma extensão C opcional). Ele não faz nada sobre passar argumentos extras, no entanto.

# 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, criar um wsgi.py contendo from myapp. import make_app; app = make_app() é fácil. Mas eu preciso manter este arquivo separadamente (o que é inconveniente porque agora pip install myapp não instalará tudo o que é necessário apenas para executá-lo) ou colocá-lo no meu pacote (o que significa que agora você pode importá-lo de dentro o próprio aplicativo que estaria errado)

Outro motivo para ter wsgi.py em seu projeto é errado: algumas ferramentas importam todos os módulos em um projeto; por exemplo. pytest faz ao procurar doctests.

Outro mantenedor do Flask aqui. @ThiefMaster já disse tudo o que eu queria dizer, então estou reiterando meu apoio ao recurso.

Eu concordo em me livrar de eval e evitei isso em flask run . Você pode adicionar uma versão mais restrita do comportamento anterior. Se a opção de linha de comando tiver parênteses, suponha que seja uma fábrica que retorna o aplicativo real. Use literal_eval para analisar o conteúdo dos parênteses e, a seguir, chame a fábrica com os parâmetros analisados.

Acho que o padrão de fábrica sem um arquivo wsgi.py é muito valioso. Eu gostaria de ajudar a encontrar uma maneira de mantê-lo em Gunicorn.

Alguém gostaria de montar um PR para literal_eval de strings de aplicativo do tipo fábrica? Isso seria em gunicorn.util.import_app .

É preciso adicionar testes, mas aqui está o código do Flask portado para o Gunicorn: https://github.com/benoitc/gunicorn/compare/master...davidism : import-factory

@davidism Se você estiver interessado, aqui está uma função que pode ser útil para carregar aplicativos de fábricas de aplicativos (com doctests 😄). Ele usa o analisador AST integrado do Python para diferenciar nomes de atributos e chamadas de funções (em vez de um regex). Ele também oferece suporte a argumentos de palavra-chave na função de fábrica. Tudo ainda é avaliado usando ast.parse e ast.literal_eval , então não há eval chamadas:

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

Se for tomada a decisão de oferecer suporte apenas a fábricas de aplicativos sem argumento, todo o código de processamento de argumento pode ser removido. Ainda funcionará bem para diferenciar nomes e chamadas de fábrica com segurança (e será útil para fornecer mensagens de erro específicas aos usuários quando eles tentam passar argumentos para a fábrica)

@ThiefMaster Ainda não estou convencido de que devemos apoiar esse padrão. Como isso é útil? Por que não usar variáveis ​​de ambiente para passar argumentos personalizados ou uma configuração se for realmente necessário?

Claro, criando um wsgi.py contendo de myapp. import make_app; app = make_app () é fácil. Mas eu preciso manter este arquivo separadamente (o que é inconveniente porque agora o pip install myapp não instalará tudo o que é necessário apenas para executá-lo) ou colocá-lo no meu pacote (o que significa que agora você pode importá-lo de dentro do próprio aplicativo o que seria errado)

Eu não entendo isso, por que esse arquivo tem que ser mantido separadamente?

Se estiver no pacote, é importável. Portanto, se você tiver um projeto maior, alguém acabará por importá-lo em vez de usar current_app etc., ou seja, mais trabalho ao lidar com PRs contendo esse tipo de erro.

Se estiver fora do pacote, você não conseguirá ao fazer pip install .


FWIW, eu realmente não me importo em passar argumentos. Normalmente, esses não são necessários (env vars são de fato o caminho a percorrer). Mas, pelo menos, ser capaz de apontar para uma fábrica de aplicativos que pode ser chamada em vez de um objeto de aplicativo é incrivelmente útil!

Por que não usar variáveis ​​de ambiente para passar argumentos personalizados ou uma configuração se for realmente necessário?

pytest carrega todos os módulos do projeto para encontrar testes. Se você tiver um objeto app=Flask() global que dependa de variáveis ​​de ambiente ou um arquivo de configuração, esse objeto será carregado durante a execução de testes. É útil poder executar testes sem definir variáveis ​​de ambiente ou arquivos de configuração. O padrão de fábrica do aplicativo é ótimo para isso.

O padrão de fábrica com argumentos é um tanto comum devido a alguns tutoriais populares do Flask, e é por isso que o apoiei em flask run . Eu concordo que é preferível usar o ambiente para configurar o aplicativo, então ficaria bem com uma versão mais reduzida que suporta chamar uma fábrica sem argumentos.

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

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

@tilgovi eu concordo. Meu principal problema é que não esperávamos que isso quebrasse ninguém, por isso eu estava sugerindo colocar de volta o eval (ou algo mais seguro) e descontinuá-lo. Por outro lado, sim, esse comportamento nunca foi suportado e foi um efeito infeliz de usar eval : /

@davidism interessante. Mas como seria diferente de usar um objeto que pode ser chamado como um aplicativo?

Não tenho certeza do que você quer dizer, você poderia dar um exemplo mais específico? Uma fábrica retorna um aplicativo WSGI, não é em si um aplicativo WSGI.

@davidism quero dizer algo como isso


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

application = make_app()

então alguém corre gunicorn -b :8000 somemodule:application

Isso faz com que application seja sempre avaliado ao importar o código, contrariando o propósito da fábrica.

Um WSGI que pode ser chamado também pode ser uma instância de classe, então talvez seja isso o que se pretendia:

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

(O exemplo zope.hookable é essencialmente o mesmo, apenas menos sobrecarga na condição de estado estacionário.)

Ter um aplicativo WSGI que cria o aplicativo WSGI real não é o ideal. Agora é uma chamada de função extra em cada solicitação de proxy para o ponto de entrada real. A configuração deve ser feita antes da primeira solicitação, mas agora é adiada até então, fazendo a primeira solicitação demorar (potencialmente muito) mais tempo.

A funcionalidade em questão aqui é uma função de fábrica que cria esse objeto com base na configuração do tempo de execução / ambiente, o que é útil para desacoplar as partes do aplicativo, evitando importações circulares e facilitando o isolamento do teste. Ter algum lugar no código que chama explicitamente a fábrica acaba com o propósito de desacoplamento, pois garanto que os usuários pensarão "ah, devo importar este objeto de aplicativo agora" quando, em vez disso, deveriam usar os recursos disponíveis para eles no Flask.

Neste ponto, tudo o que estamos pedindo é "se a string de importação terminar com parênteses, chame o nome importado para obter o aplicativo".

Acho que temos muitas maneiras de contornar isso, mas isso significa apenas que não somos o público-alvo. Eu sei que posso fazer coisas como enviar um script que está fora do pacote como um ponto de entrada para meu contêiner e configurar o pytest para ignorá-lo, etc, etc, mas queremos nos preocupar com as pessoas que estão seguindo os tutoriais e podem não entendo o traceback.

Um padrão muito limitado "se o objeto do aplicativo pode ser chamado com zero argumentos, invoque-o como uma fábrica" ​​pode funcionar, mas falha se o chamável for na verdade um aplicativo WSGI que está mal decorado e não revela seus argumentos tão facilmente pela introspecção . Se quisermos ser generosos, devemos apoiar tudo o que tínhamos antes, evitando eval , então acho que esse é o caminho que devemos seguir.

Agradeço muito todas as sugestões e ajuda a resolver isso a todos.

Eu gosto das sugestões de @davidism e @connorbrinton usando literal_eval .

Isso faz com que a aplicação seja sempre avaliada na importação do código, contrariando o propósito da fábrica.

Bem, isso inicializaria o aplicativo em tempo de execução e retornaria um chamável usado pelos trabalhadores. Isso não é tão diferente.

Minha principal reserva sobre esse padrão é encorajar as pessoas a rodar algum código pré-spawn que pode quebrar as expectativas no HUP ou USR2. Também quebra a interface do usuário atual. Funcionará com usos futuros de gunicorn?

De qualquer forma, as opções são as seguintes:

  1. podemos considerar que este comportamento não era suportado, não documentado (no gunicorn). A mudança que foi feita com base nele.
  2. alguns usuários confiavam nele e agora queremos apoiar esse comportamento

(1) é difícil, mas também o caminho lógico, considerando que nunca o apoiamos.
(2) algum tipo de estética e quebra a IU da linha de comando, ele precisa de alguns testes / exemplos para testar no gunicorn, use algo como literal_evals

Podemos apoiar (2), mas gostaria de fazer alguns testes. Também devemos documentar isso?

@tilgovi @jamadden @berkerpeksag @sirkonst qual é a sua preferência?

Parece que outro caso de uso de quebra foi o acesso a atributos , que não será resolvido por literal_eval .

Por exemplo, no Plotly Dash você usa o objeto Dash , que internamente tem uma instância Flask como o atributo server . Algumas pessoas estavam usando:

gunicorn "module:app.server"

Não tenho certeza se isso deve ser suportado. flask run também não o suporta. Parece que o objeto Dash deve ter um método __call__ que passa para Flask.__call__ . Além disso, os documentos de Dash dizem para fazer server = app.server e apontar Gunicorn para isso, então este parece ser principalmente um caso de algumas informações incorretas sendo repassadas.

@davidism estive doente hoje, mas vou dar uma olhada neste domingo para um lançamento na segunda. A sugestão do @tilgovi é boa e o plano geral é ter uma avaliação segura substituindo a avaliação antiga.

Acho que devemos alertar o usuário que ele está usando essa inicialização. Pensamentos ? cc @tilgovi

Vou tentar transformar o branch que vinculei acima em um PR com testes no sábado, a menos que você queira criar uma implementação diferente.

@davidism vá em frente. Eu estarei de volta no domingo, onde irei revisá-lo se necessário :) Obrigado!

Um pouco atrás, trabalhando nisso agora.

@connorbrinton ideia legal de usar ast.parse , vou experimentar e incluí-lo como coautor no commit se eu continuar.

Só queria alertar que há uma resposta Stack Overflow um tanto popular (e bastante antiga) que está direcionando os usuários para o comportamento da v19, que pode precisar de uma atualização dependendo da escolha feita: https://stackoverflow.com/questions/ 8495367 / using-additional-command-line-arguments-with-gunicorn

fixado no mestre. obrigado @davidism pelo patch!

todos os casos tratados estão nestes testes: https://github.com/benoitc/gunicorn/commit/19cb68f4c3b55da22581c008659ee62d8c54ab2b#diff -5832adf374920d75d4bf48e546367f53R67

Esta página foi útil?
0 / 5 - 0 avaliações