Gunicorn: Masalah v20: Gagal menemukan objek aplikasi 'create_app()' di 'aplikasi'

Dibuat pada 9 Nov 2019  ·  47Komentar  ·  Sumber: benoitc/gunicorn

Saya telah lalai menyematkan versi gunicorn saya, dan perintah run mulai rusak pagi ini ketika saya menerapkan kembali aplikasi saya dan secara otomatis ditingkatkan ke 20.0.

Menurunkan versi gunicorn saya kembali ke 19.9 memperbaiki masalah.

Ini adalah perintah yang saya gunakan untuk menjalankan aplikasi saya:

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

Kesalahannya adalah:

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

Komentar yang paling membantu

tetap di master. terima kasih @davidism untuk tambalannya!

semua kasus yang ditangani ada dalam tes ini: https://github.com/benoitc/gunicorn/commit/19cb68f4c3b55da22581c008659ee62d8c54ab2b#diff -5832adf374920d75d4bf48e546367f53R67

Semua 47 komentar

Saya juga mengalami masalah ini, yaitu
Failed to find application object 'create_app()' in 'app'
dan menyematkan ke versi 19.9.0 memecahkan masalah.

Saya awalnya berpikir perbaikannya adalah mengubah perintah gunicorn dari:
gunicorn --bind 0.0.0.0:$PORT app:create_app()
ke:
gunicorn --bind 0.0.0.0:$PORT app:create_app
(perhatikan tanda kurung setelah create_app sekarang hilang). Awalnya, semuanya tampak baik-baik saja:

situs web_1 | [2019-11-10 19:18:54 +0000] [1] [INFO] Memulai gunicorn 20.0.0
situs web_1 | [2019-11-10 19:18:54 +0000] [1] [INFO] Mendengarkan di: http://0.0.0.0 :8000 (1)
situs web_1 | [2019-11-10 19:18:54 +0000] [1] [INFO] Menggunakan pekerja: sinkronisasi
situs web_1 | [2019-11-10 19:18:54 +0000] [11] [INFO] Boot pekerja dengan pid: 11

Tapi sayangnya itu hanya fatamorgana karena ketika Anda mencoba memuat situs web/titik akhir labu Anda, ia akan mengatakan:

[2019-11-10 19:20:28 +0000] [11] [ERROR] Permintaan penanganan kesalahan /
situs web_1 | Traceback (panggilan terakhir terakhir):
situs web_1 | File "/usr/local/lib/python3.7/site-packages/gunicorn/workers/sync.py", baris 134, di handle
situs web_1 | self.handle_request(pendengar, permintaan, klien, addr)
situs web_1 | File "/usr/local/lib/python3.7/site-packages/gunicorn/workers/sync.py", baris 175, di handle_request
situs web_1 | respiter = self.wsgi(environ, resp.start_response)
situs web_1 | TypeError: create_app() mengambil 0 argumen posisi tetapi 2 diberikan

Ini jelas merupakan masalah dengan gunicorn versi 20.0.0.

Itu harus terkait dengan perubahan ini: https://github.com/benoitc/gunicorn/commit/3701ad9f26a7a4c0a081dfd0f6e97ecb272de515#diff -0b90f794c3e9742c45bf484505e3db8dR377 melalui #2043 .

Salah satu cara untuk memperbaikinya di pihak Anda adalah dengan mengekspor myapp = create_app() di modul utama Anda dan sepuluh mulai dengan app:myapp . Ini seharusnya berhasil, beri tahu saya jika tidak.

Saya akan melihat apakah ada sesuatu yang perlu dilakukan di sana. @berkerpeksag mengapa penghapusan eval diperlukan di sana?

Itu harus terkait dengan perubahan ini: 3701ad9#diff-0b90f794c3e9742c45bf484505e3db8dR377 melalui #2043 .

Salah satu cara untuk memperbaikinya di pihak Anda adalah dengan mengekspor myapp = create_app() di modul utama Anda dan sepuluh mulai dengan app:myapp . Ini seharusnya berhasil, beri tahu saya jika tidak.

Saya akan melihat apakah ada sesuatu yang perlu dilakukan di sana. @berkerpeksag mengapa penghapusan eval diperlukan di sana?

Saya telah membuat perubahan ini di aplikasi saya dan memperbaiki crash, Gunicorn sekarang dapat menjalankan aplikasi saya dengan menyimpan hasil create_app() dalam variabel dan mengekspornya sehingga dapat digunakan dalam perintah run Gunicorn saya

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

my_app = create_app()

gunicorn "app:my_app" --workers 8

Saya dapat mengonfirmasi bahwa melakukan apa yang disarankan @benoitc dan @jrusso1020 di atas memperbaiki masalah. Terima kasih semuanya!

Saya tidak melihat perbaikan berfungsi jika penerusan parameter diperlukan pada waktu peluncuran seperti:

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

Lewat parameter bekerja di 19.9.0 tetapi gagal di 20.0.0.

@benoitc jika perlu diketahui, labu docs merekomendasikan pola app:create_app() saat menggunakan gunicorn. Saya menduga beberapa pengguna baru Anda pertama kali mencoba gunicorn sebagai hasil dari pembuatan aplikasi flask, dan mereka akan mencoba menggunakan rekomendasi yang sekarang rusak dari dokumen tersebut (setidaknya itulah pengalaman saya).

Saya dapat menghubungi tim tersebut untuk meminta mereka memperbarui, namun saya akan menunggu @berkerpeksag untuk mempertimbangkan penurunan exec jika masuk akal untuk mengembalikannya.

@tjwaterman99 yah saya tidak yakin saya suka meneruskan argumen dengan cara ini ke aplikasi. Saya tidak berpikir itu ide yang baik. Argumen harus diteruskan melalui env.

Contoh penggunaan Flask kami adalah melakukan apa yang saya jelaskan. Saya pikir cara saat ini lebih sederhana untuk ditangani. Pikiran?

cc @tilgovi @berkerpeksag ^^

FWIW kami juga mengalami ini.

Saya berharap di luar sana cukup banyak orang yang mengikuti pola Flask "pabrik aplikasi".
Pasti ada solusi, tetapi setidaknya changelog harus menyebutkan ini sebagai perubahan yang melanggar.

Saya rasa kami tidak pernah dengan sengaja mendukung penggunaan seperti app:callable() dan app:callable(some, args) . Saya akan mengatakan itu adalah efek samping yang tidak menguntungkan dari penggunaan eval() dalam implementasi sebelumnya.

Implementasi saat ini cukup dekat dengan apa yang dilakukan import_string() Django:

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

Saya akan dengan senang hati meningkatkan dokumentasi, menambahkan catatan rilis, dan memunculkan pesan kesalahan yang lebih deskriptif.

Saya rasa kami tidak pernah dengan sengaja mendukung penggunaan seperti app:callable () dan app:callable (some, args). Saya akan mengatakan itu adalah efek samping yang tidak menguntungkan dari penggunaan eval() dalam implementasi sebelumnya.

Ya saya setuju. Kami tidak pernah mendukung cara seperti itu untuk memulai aplikasi sejauh yang saya cari.

Saya memberi +1 untuk kesalahan yang lebih eksplisit. Mungkin kita harus memunculkan kesalahan jika nama aplikasi bukan nama yang sederhana?

Harap diingat bahwa ini adalah perilaku eksplisit yang disebutkan dalam dokumentasi publik untuk salah satu kerangka kerja wsgi utama (flask), dan sebelumnya telah didukung oleh proyek Anda. Menghapus eval mencegah inisiasi aplikasi yang lambat, yang merupakan masalah jika aplikasi 1) disediakan oleh perpustakaan, dan 2) menimbulkan biaya pengaturan yang tidak sepele. Jika tidak ada keamanan atau alasan lain mengapa evaluasi tidak sesuai, bukankah lebih mudah untuk terus mendukung perilaku Anda yang ada?

Jika ada orang yang menemukan kasus serupa, solusi yang sesuai dari Python 3.7 dan seterusnya adalah memalsukan variabel level modul dengan membuat level modul __getattr__ , sesuai PEP ini . Itu akan memungkinkan inisiasi malas (pabrik aplikasi a la) tanpa mencapai perubahan besar di gunicorn 20.0.0.

Yah, kami tidak pernah benar-benar mendukung perilaku seperti itu, tidak ada dokumen atau contoh kami yang menggunakannya. Itu tidak sesuai dengan baris perintah.

Tapi kan ini benar-benar perubahan yang luar biasa dan tidak terduga. Saya kemudian akan mendukung untuk mengembalikan eval dan memperingatkan pengguna tentang perilaku yang tidak digunakan lagi. Mungkin juga untuk menggantinya dan membiarkan orang menggunakan pola desain "pabrik" kita bisa menambahkan pengaturan --init-args :

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

Atau sesuatu seperti itu. Pikiran?

@benoitc Mendukung metode pabrik dengan flag baris perintah eksplisit akan sangat baik Mungkin sesuatu seperti:

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

(Atau mungkin nama dasar lain, seperti --factory )

Saya mengalami masalah dengan perubahan ini karena tidak ada lagi cara bagi saya untuk menjalankan tes dengan mudah. Karena (i) aplikasi saya bergantung pada variabel lingkungan, (ii) kumpulan pengujian memuat semua modul (untuk doctests) dan (iii) saya tidak dapat lagi menunda konstruksi aplikasi hingga setelah impor, saya tidak dapat menguji proyek saya tanpa menambahkan string panjang variabel lingkungan sebelum setiap perintah pengujian, dan pengujian membutuhkan waktu lebih lama dari biasanya.

Karena saya menggunakan Python 3.7, saya pikir saya dapat meretas ini dengan level modul __getattr__ , tetapi untuk pra-3.7 saya tidak berpikir ada solusi untuk masalah ini selain menurunkan versi.

Saya pikir mendukung metode pabrik dengan flag baris perintah akan menyelesaikan masalah ini. Jika saya melewatkan solusi yang jelas, saran lain juga akan dihargai

@tjwaterman99 yah saya tidak yakin saya suka meneruskan argumen dengan cara ini ke aplikasi. Saya tidak berpikir itu ide yang baik. Argumen harus diteruskan melalui env.

Contoh penggunaan Flask kami adalah melakukan apa yang saya jelaskan. Saya pikir cara saat ini lebih sederhana untuk ditangani. Pikiran?

Saya setuju, saya pikir menyampaikan argumen melalui lingkungan lebih intuitif dan mendorong pengguna untuk memiliki konfigurasi mereka tinggal di satu tempat. Namun mendukung objek/pabrik yang dapat dipanggil penting untuk setidaknya Flask, dan mungkin juga kerangka kerja lain.

+1 untuk memberikan peringatan, dan memberikan instruksi tentang cara menggunakan Gunicorn dengan pabrik sebelum menghentikan exec rilis berikutnya.

Sangat disayangkan bahwa ini terjadi. Kami memiliki dua pilihan bagaimana merespons. Kami dapat mengubah perilaku tersebut kembali atau kami dapat membantu semua orang bermigrasi.

Jika kita mengubah perilaku kembali, mungkin masuk akal untuk menarik rilis dari PyPI, tapi saya pikir ini terlalu drastis. Gunicorn tidak pernah mendokumentasikan atau menyarankan penggunaan ini.

Oleh karena itu, saya sarankan kami hanya membantu semua orang beradaptasi dan meminta maaf atas ketidaknyamanan ini.

Kami harus menghubungi Flask dengan PR untuk memperbarui dokumentasi mereka. Saya senang melakukannya. Saya pikir orang lain sudah mendokumentasikan jalur migrasi di sini.

Saya akan menambahkan saran yang berguna untuk memiliki modul atau skrip _separate_ yang mengimpor pabrik aplikasi, memanggilnya, dan mengekspornya. Itu dapat berfungsi sebagai titik masuk Gunicorn dan dapat dihilangkan dari doctest dan alat lainnya sehingga tidak memicu impor yang tidak diinginkan saat menjalankan alat ini dalam pengembangan. Sesuatu seperti skrip __main__.py atau web.py dapat bekerja untuk ini.

Di masa mendatang, kita harus menyediakan kandidat rilis meskipun menurut kita rilis seharusnya aman. Kami dapat menangkap ini dengan kandidat rilis dan kemudian memiliki kesempatan untuk mendokumentasikan perubahan yang melanggar dalam catatan rilis kami, atau menghentikannya untuk siklus.

Saya tidak berpikir masuk akal untuk menambahkan dukungan untuk argumen inisialisasi pada baris perintah. Sudah terlambat untuk rilis ini; kami telah mendukung aplikasi khusus untuk kasus penggunaan tingkat lanjut; dan banyak kerangka kerja memiliki cara yang direkomendasikan sendiri untuk meneruskan pengaturan ke aplikasi. Gunicorn tidak perlu menyediakan sendiri. Mencoba menambahkan argumen untuk memperbaiki masalah ini memperluas area permukaan untuk jenis perubahan yang melanggar ini di masa mendatang. Kita harus meminimalkan permukaan CLI Gunicorn sebanyak mungkin.

Kami harus menghubungi Flask dengan PR untuk memperbarui dokumentasi mereka. Saya senang melakukannya. Saya pikir orang lain sudah mendokumentasikan jalur migrasi di sini.

Saya melihat bahwa @bilalshaikh42 sudah melakukan ini di https://github.com/pallets/flask/pull/3421

(Salah satu pengelola Flask di sini)

Sementara saya sepenuhnya setuju dengan menyingkirkan eval sana, saya pikir harus ada dukungan eksplisit untuk pabrik aplikasi! Inti dari pabrik aplikasi adalah untuk menghindari objek app dapat diimpor (karena menggunakan itu sering menghasilkan neraka ketergantungan melingkar).

Di flask run cli (hanya untuk pengembangan) kami sebenarnya menambahkan dukungan eksplisit untuk pabrik aplikasi, karena sangat umum.

Tentu, membuat wsgi.py berisi from myapp. import make_app; app = make_app() itu mudah. Tetapi saya juga perlu menyimpan file ini secara terpisah (yang tidak nyaman karena sekarang pip install myapp tidak akan menginstal semua yang diperlukan untuk menjalankannya), atau memasukkannya ke dalam paket saya (yang berarti sekarang Anda dapat mengimpornya dari dalam aplikasi itu sendiri yang akan salah)

Di Flask kami mencari cara eksplisit untuk memeriksa pabrik aplikasi yang dapat dipanggil dan memanggilnya tanpa menggunakan eval - mungkin Anda dapat mempertimbangkan sesuatu seperti ini juga? Jika Anda menginginkan lebih sedikit keajaiban, Anda bahkan dapat menggunakan argumen CLI yang berbeda untuk menunjuk ke aplikasi dan untuk menunjuk ke pabrik aplikasi.

Di masa mendatang, kita harus menyediakan kandidat rilis meskipun menurut kita rilis seharusnya aman. Kami dapat menangkap ini dengan kandidat rilis dan kemudian memiliki kesempatan untuk mendokumentasikan perubahan yang melanggar dalam catatan rilis kami, atau menghentikannya untuk siklus.

Tidak yakin apakah RC benar-benar membantu - umumnya orang tidak menginstal/memutakhirkan dengan --pre (juga karena seberapa buruk kerjanya - ini tidak hanya memengaruhi paket yang ditentukan secara eksplisit, tetapi semua dependensi bersarang tidak peduli seberapa dalam, jadi sangat mudah bahwa beberapa ketergantungan ketergantungan menarik prarilis yang rusak), jadi siapa pun yang tidak menyematkan versi mereka tidak akan melihat kerusakan apa pun sampai mereka benar-benar dirilis.

Untuk apa nilainya, zope.hookable menyediakan cara mudah untuk menerapkan pendekatan seperti pabrik yang malas pada dasarnya tanpa overhead (karena ekstensi C opsional). Itu tidak melakukan apa-apa tentang memberikan argumen tambahan.

# 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

Tentu, membuat wsgi.py berisi from myapp. import make_app; app = make_app() itu mudah. Tetapi saya juga perlu menyimpan file ini secara terpisah (yang tidak nyaman karena sekarang pip install myapp tidak akan menginstal semua yang diperlukan untuk menjalankannya), atau memasukkannya ke dalam paket saya (yang berarti sekarang Anda dapat mengimpornya dari dalam aplikasi itu sendiri yang akan salah)

Alasan lain memiliki wsgi.py dalam proyek Anda salah: beberapa alat mengimpor semua modul dalam proyek; misalnya. pytest lakukan ketika mencari doctests.

Pengelola Flask lain di sini. @ThiefMaster sudah mengatakan semua yang ingin saya katakan, jadi saya kebanyakan mengulangi dukungan saya untuk fitur tersebut.

Saya setuju dengan menyingkirkan eval , dan saya menghindarinya di flask run . Anda dapat menambahkan versi perilaku sebelumnya yang lebih dibatasi. Jika opsi baris perintah memiliki parens, anggap itu adalah pabrik yang mengembalikan aplikasi asli. Gunakan literal_eval untuk mengurai isi parens, lalu panggil pabrik dengan params yang diurai.

Saya pikir pola pabrik tanpa file wsgi.py cukup berharga. Saya ingin membantu menemukan cara untuk menyimpannya di Gunicorn.

Apakah seseorang ingin membuat PR bersama untuk literal_eval dari string aplikasi seperti pabrik? Ini akan berada di gunicorn.util.import_app .

Perlu menambahkan tes, tetapi inilah kode Flask yang di-porting ke Gunicorn: https://github.com/benoitc/gunicorn/compare/master...davidism :import-factory

@davidism Jika Anda tertarik, inilah fungsi yang mungkin berguna untuk memuat aplikasi dari pabrik aplikasi (dengan doctests ). Ini menggunakan parser AST bawaan Python untuk membedakan nama atribut dan panggilan fungsi (bukan regex). Ini juga mendukung argumen kata kunci dalam fungsi pabrik. Semuanya masih dievaluasi menggunakan ast.parse dan ast.literal_eval , jadi tidak ada panggilan 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))

Jika keputusan dibuat untuk hanya mendukung pabrik aplikasi tanpa argumen, semua kode pemrosesan argumen dapat dihapus. Ini masih akan berfungsi dengan baik untuk membedakan nama dan panggilan pabrik dengan aman (dan berguna untuk memberikan pesan kesalahan khusus kepada pengguna ketika mereka mencoba meneruskan argumen ke pabrik)

@ThiefMaster Saya masih tidak yakin kita harus mendukung pola seperti itu. Bagaimana itu berguna? Mengapa tidak menggunakan variabel lingkungan untuk meneruskan argumen khusus atau konfigurasi jika itu benar-benar diperlukan?

Tentu, membuat wsgi.py yang berisi dari myapp. impor make_app; app = make_app() itu mudah. Tetapi saya juga perlu memelihara file ini secara terpisah (yang tidak nyaman karena sekarang pip install myapp tidak akan menginstal semua yang diperlukan untuk menjalankannya), atau memasukkannya ke dalam paket saya (yang berarti sekarang Anda dapat mengimpornya dari dalam aplikasi itu sendiri yang akan salah)

Saya tidak mengerti, mengapa file seperti itu harus disimpan secara terpisah?

Jika ada dalam paket, maka itu dapat diimpor. Jadi jika Anda memiliki proyek yang lebih besar, seseorang pada akhirnya akan mengimpornya daripada menggunakan current_app dll. yaitu lebih banyak pekerjaan ketika berurusan dengan PR yang mengandung kesalahan semacam ini.

Jika di luar paket, Anda tidak akan mendapatkannya saat melakukan pip install .


FWIW, saya tidak terlalu peduli dengan argumen yang lewat. Biasanya itu tidak diperlukan (env vars memang cara yang harus dilakukan). Tapi setidaknya bisa menunjuk ke pabrik aplikasi yang dapat dipanggil alih-alih objek aplikasi sangat berguna!

Mengapa tidak menggunakan variabel lingkungan untuk meneruskan argumen khusus atau konfigurasi jika itu benar-benar diperlukan?

pytest memuat setiap modul dalam proyek untuk menemukan tes. Jika Anda memiliki objek global app=Flask() yang bergantung pada variabel lingkungan atau file konfigurasi, objek tersebut akan dimuat saat menjalankan pengujian. Ini berguna untuk dapat menjalankan tes tanpa mengatur variabel lingkungan atau file konfigurasi. Pola pabrik aplikasi sangat bagus untuk ini.

Pabrik dengan pola argumen agak umum karena beberapa tutorial Flask yang populer, itulah sebabnya saya mendukungnya di flask run . Saya setuju lebih baik menggunakan lingkungan untuk mengonfigurasi aplikasi, jadi saya akan baik-baik saja dengan versi yang lebih diperkecil yang mendukung panggilan pabrik tanpa argumen.

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

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

@tilgovi saya setuju. Masalah utama saya adalah bahwa kami tidak berharap itu akan menghancurkan siapa pun, oleh karena itu mengapa saya menyarankan untuk mengembalikan eval (atau sesuatu yang lebih aman) dan mencelanya. Di sisi lain, ya perilaku itu tidak pernah didukung dan merupakan efek yang tidak menguntungkan dari penggunaan eval :/

@davidisme menarik. Tetapi apa bedanya dengan menggunakan objek yang dapat dipanggil sebagai aplikasi?

Saya tidak yakin apa yang Anda maksud, dapatkah Anda memberikan contoh yang lebih spesifik? Pabrik mengembalikan aplikasi WSGI, itu sendiri bukan aplikasi WSGI.

@davidism maksud saya seperti ini


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

application = make_app()

kemudian seseorang menjalankan gunicorn -b :8000 somemodule:application

Itu menyebabkan application selalu dievaluasi saat mengimpor kode, mengalahkan tujuan pabrik.

Callable WSGI juga bisa menjadi instance kelas, jadi mungkin inilah yang dimaksudkan:

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

(Contoh zope.hookable pada dasarnya sama, hanya lebih sedikit overhead dalam kondisi mapan.)

Memiliki aplikasi WSGI yang membuat aplikasi WSGI sebenarnya tidak ideal. Sekarang panggilan fungsi tambahan pada setiap permintaan untuk proxy ke titik masuk yang sebenarnya. Penyiapan harus dilakukan sebelum permintaan pertama, tetapi sekarang ditunda hingga saat itu, membuat permintaan pertama memakan waktu (berpotensi jauh lebih lama).

Fungsionalitas yang dimaksud di sini adalah fungsi pabrik yang membuat objek itu berdasarkan konfigurasi runtime/lingkungan, yang berguna untuk memisahkan bagian-bagian aplikasi, menghindari impor melingkar, dan isolasi pengujian yang lebih mudah. Memiliki suatu tempat dalam kode yang secara eksplisit menyebut jenis pabrik mengalahkan tujuan decoupling, karena saya jamin Anda pengguna akan berpikir "oh, saya harus mengimpor objek aplikasi ini sekarang" ketika mereka seharusnya menggunakan fitur yang tersedia untuk mereka di Flask.

Pada titik ini, yang kami minta hanyalah "jika string impor diakhiri dengan parens, panggil nama yang diimpor untuk mendapatkan aplikasi".

Saya pikir kami memiliki banyak cara untuk mengatasi ini, tetapi itu hanya berarti kami bukan target audiens. Saya tahu bahwa saya dapat melakukan hal-hal seperti mengirimkan skrip yang berada di luar paket sebagai titik masuk untuk wadah saya dan mengonfigurasi pytest untuk mengabaikannya, dll, dll, tetapi kami ingin peduli dengan orang-orang yang rusak ini yang mengikuti tutorial dan mungkin tidak mengerti traceback.

Pola "jika objek aplikasi dapat dipanggil dengan nol argumen, maka panggil itu sebagai pabrik" yang sangat terbatas mungkin berfungsi, tetapi gagal jika yang dapat dipanggil sebenarnya adalah aplikasi WSGI yang didekorasi dengan buruk dan tidak mengungkapkan argumennya dengan mudah dari introspeksi . Jika kita ingin bermurah hati, kita harus mendukung semua yang kita miliki sebelumnya sambil menghindari eval , jadi saya pikir itulah jalan yang harus kita lakukan.

Saya sangat menghargai semua saran dan bantuan untuk menyelesaikan ini, semuanya.

Saya suka saran dari @davidism dan @connorbrinton menggunakan literal_eval .

Itu menyebabkan aplikasi selalu dievaluasi saat mengimpor kode, mengalahkan tujuan pabrik.

Yah itu akan memulai aplikasi saat runtime dan mengembalikan callable yang digunakan oleh para pekerja. Itu tidak jauh berbeda.

Cadangan utama saya tentang pola itu adalah mendorong orang untuk menjalankan beberapa kode pra-spawn yang dapat mematahkan harapan pada HUP atau USR2. Juga merusak UI saat ini. Apakah ini akan berfungsi dengan penggunaan gunicorn di masa mendatang?

Pokoknya pilihannya adalah sebagai berikut:

  1. kita dapat menganggap perilaku ini tidak didukung, tidak didokumentasikan (di gunicorn). Perubahan yang dilakukan berdasarkan itu.
  2. beberapa pengguna mengandalkannya dan sekarang kami ingin mendukung perilaku itu

(1) adalah pengambilan yang sulit, tetapi juga jalur logis, mengingat kami tidak pernah mendukungnya.
(2) semacam estetika dan merusak UI baris perintah, perlu beberapa tes/contoh untuk menguji di gunicorn, gunakan sesuatu seperti literal_evals

Kami dapat mendukung (2) tetapi saya ingin melakukan beberapa pengujian. Juga haruskah kita mendokumentasikannya?

@tilgovi @jamadden @berkerpeksag @sirkonst apa pilihan Anda?

Sepertinya kasus penggunaan lain yang melanggar adalah akses atribut , yang tidak akan diselesaikan dengan literal_eval .

Misalnya, dalam Plotly Dash Anda menggunakan Dash objek, yang secara internal memiliki Flask misalnya sebagai server atribut. Beberapa orang menggunakan:

gunicorn "module:app.server"

Saya tidak yakin ini harus didukung. flask run juga tidak mendukungnya. Sepertinya objek Dash harus memiliki metode __call__ yang diteruskan ke Flask.__call__ . Selain itu, dokumen Dash mengatakan untuk melakukan server = app.server dan mengarahkan Gunicorn ke sana, jadi ini tampaknya sebagian besar merupakan kasus beberapa informasi yang salah disebarkan.

@davidism saya sakit hari ini tetapi saya akan memeriksanya hari Minggu ini untuk rilis pada hari Senin. Saran dari @tilgovi bagus dan rencana umumnya adalah memiliki eval yang aman menggantikan eval yang lama.

Yang saya pikir kita harus memperingatkan pengguna dia menggunakan inisialisasi tersebut. Pikiran ? cc @tilgovi

Saya akan mencoba mengubah cabang yang saya tautkan di atas menjadi PR dengan tes pada hari Sabtu, kecuali jika Anda ingin membuat implementasi yang berbeda.

@davidisme lanjutkan. Saya akan kembali pada hari Minggu di mana saya akan meninjaunya jika perlu :) Terima kasih!

Sedikit di belakang, kerjakan ini sekarang.

@connorbrinton ide keren untuk menggunakan ast.parse , saya akan mencobanya dan menyertakan Anda sebagai rekan penulis dalam komit jika saya melakukannya.

Hanya ingin berpadu bahwa ada jawaban Stack Overflow yang agak populer (dan cukup lama) yang mengarahkan pengguna ke perilaku v19, yang mungkin memerlukan pembaruan tergantung pada pilihan apa yang dibuat: https://stackoverflow.com/questions/ 8495367/menggunakan-baris-perintah-tambahan-argumen-dengan-gunicorn

tetap di master. terima kasih @davidism untuk tambalannya!

semua kasus yang ditangani ada dalam tes ini: https://github.com/benoitc/gunicorn/commit/19cb68f4c3b55da22581c008659ee62d8c54ab2b#diff -5832adf374920d75d4bf48e546367f53R67

Apakah halaman ini membantu?
0 / 5 - 0 peringkat