Gunicorn: مشكلة الإصدار 20: فشل في العثور على كائن التطبيق "create_app ()" في "التطبيق"

تم إنشاؤها على ٩ نوفمبر ٢٠١٩  ·  47تعليقات  ·  مصدر: benoitc/gunicorn

لقد أهملت تثبيت إصدار برنامج gunicorn الخاص بي ، وبدأ أمر التشغيل في التعطل هذا الصباح عندما أعدت نشر تطبيقي وتمت ترقيته تلقائيًا إلى 20.0.

أدى الرجوع إلى إصدار أقدم من برنامج gunicorn إلى 19.9 إلى إصلاح المشكلة.

هذا هو الأمر الذي أستخدمه لتشغيل تطبيقي:

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

الخطأ هو:

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

التعليق الأكثر فائدة

ثابت في الماجستير. شكرا @ davidism على التصحيح!

جميع الحالات التي تم التعامل معها في هذه الاختبارات: https://github.com/benoitc/gunicorn/commit/19cb68f4c3b55da22581c008659ee62d8c54ab2b#diff -5832adf374920d75d4bf48e546367f53R67

ال 47 كومينتر

لقد واجهت هذه المشكلة أيضًا ، أي
Failed to find application object 'create_app()' in 'app'
والتثبيت في الإصدار 19.9.0 يحل المشكلة.

كنت في البداية على الرغم من أن الإصلاح كان لتغيير أمر gunicorn من:
gunicorn --bind 0.0.0.0:$PORT app:create_app()
إلى:
gunicorn --bind 0.0.0.0:$PORT app:create_app
(لاحظ الآن اختفاء الأقواس بعد create_app). في البداية ، كل شيء يبدو جيدًا:

website_1 | [2019-11-10 19:18:54 +0000] [1] [INFO] بدء تشغيل Gunicorn 20.0.0
website_1 | [2019-11-10 19:18:54 +0000] [1] [INFO] الاستماع على: http://0.0.0.0 : 8000 (1)
website_1 | [2019-11-10 19:18:54 +0000] [1] [INFO] استخدام العامل: المزامنة
website_1 | [2019-11-10 19:18:54 +0000] [11] [INFO] تمهيد العامل مع رقم التعريف الشخصي: 11

لكن للأسف ، هذا مجرد سراب لأنه عندما تحاول تحميل موقع الويب / نقطة النهاية الخاصة بك ، ستقول:

[2019-11-10 19:20:28 +0000] [11] [خطأ] خطأ في معالجة الطلب /
website_1 | Traceback (آخر مكالمة أخيرة):
website_1 | ملف "/usr/local/lib/python3.7/site-packages/gunicorn/workers/sync.py" ، السطر 134 ، في المقبض
website_1 | self.handle_request (مستمع ، مطلوب ، عميل ، العنوان)
website_1 | ملف "/usr/local/lib/python3.7/site-packages/gunicorn/workers/sync.py" ، السطر 175 ، في handle_request
website_1 | respiter = self.wsgi (بيئة ، resp.start_response)
website_1 | TypeError: تأخذ create_app () 0 وسيطة موضعية ولكن تم إعطاء 2

من الواضح أن هذه مشكلة في الإصدار 20.0.0 من برنامج gunicorn.

يجب أن يكون مرتبطًا بهذا التغيير: https://github.com/benoitc/gunicorn/commit/3701ad9f26a7a4c0a081dfd0f6e97ecb272de515#diff -0b90f794c3e9742c45bf484505e3db8dR377 عبر # 2043.

إحدى الطرق لإصلاحها من جانبك هي أن تقوم بتصدير myapp = create_app() في الوحدة النمطية الخاصة بك والعشرة تبدأ بـ app:myapp . يجب أن يعمل هذا ، أخبرني إذا لم يكن كذلك.

سأبحث عما إذا كان هناك شيء يجب القيام به هناك. berkerpeksag لماذا كانت هناك حاجة لإزالة eval هناك؟

يجب أن يكون مرتبطًا بهذا التغيير: 3701ad9 # diff-0b90f794c3e9742c45bf484505e3db8dR377 عبر # 2043.

إحدى الطرق لإصلاحها من جانبك هي أن تقوم بتصدير myapp = create_app() في الوحدة النمطية الخاصة بك والعشرة تبدأ بـ app:myapp . يجب أن يعمل هذا ، أخبرني إذا لم يكن كذلك.

سأبحث عما إذا كان هناك شيء يجب القيام به هناك. berkerpeksag لماذا كانت هناك حاجة لإزالة eval هناك؟

لقد أجريت هذا التغيير في تطبيقي وقمت بإصلاح العطل ، أصبح Gunicorn الآن قادرًا على تشغيل تطبيقي عن طريق حفظ نتيجة create_app() في متغير وتصديرها بحيث يمكن استخدامها في أمر تشغيل Gunicorn الخاص بي

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

my_app = create_app()

gunicorn "app:my_app" --workers 8

أستطيع أن أؤكد أن القيام بما اقترحهbenoitc و @ jrusso1020 أعلاه يعمل على حل المشكلة. شكرا لكم جميعا!

لا أرى الإصلاح يعمل إذا كان تمرير المعلمة مطلوبًا في وقت التشغيل مثل:

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

يعمل تمرير المعلمة في 19.9.0 لكنه فشل في 20.0.0.

benoitc في حال كان من المفيد معرفة ذلك ، توصي مستندات القارورة بنمط app:create_app() عند استخدام gunicorn. أظن أن بعض المستخدمين الجدد يحاولون أولاً استخدام gunicorn كنتيجة لإنشاء تطبيقات flask ، وسيحاولون استخدام التوصية التي تم تعطيلها الآن من تلك المستندات (كانت تلك تجربتي على الأقل).

يمكنني التواصل مع هذا الفريق لأطلب منهم التحديث ، ومع ذلك سأنتظر berkerpeksag للتأمل في إسقاط exec في حال كان من المنطقي إعادته مرة أخرى.

@ tjwaterman99 حسنًا ، لست متأكدًا من أنني أحب تمرير الحجج بهذه الطريقة إلى أحد التطبيقات. لا أعتقد أنها فكرة جيدة. يجب تمرير الحجج عن طريق البيئة المحيطة.

مثالنا على استخدام Flask هو فعل ما أصفه. أعتقد أن الطريقة الحالية أسهل في التعامل معها. أفكار؟

تضمين التغريدة

FWIW نحن نواجه هذا أيضًا.

أتوقع أن يكون هناك الكثير من الأشخاص الذين يتبعون نمط قارورة "مصنع التطبيقات".
هناك عمل حول هذا بالتأكيد ، ولكن على الأقل يجب أن يذكر سجل التغيير هذا على أنه كسر التغيير.

لا أعتقد أننا أبدًا دعمنا استخدامات مثل app:callable() و app:callable(some, args) . أود أن أقول أنه كان أحد الآثار الجانبية المؤسفة لاستخدام eval() في التطبيق السابق.

التنفيذ الحالي قريب جدًا مما يفعله Django import_string() :

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

يسعدني تحسين الوثائق وإضافة ملاحظة إصدار ورفع رسالة خطأ وصفية أكثر.

لا أعتقد أننا دعمنا مطلقًا استخدامات مثل app: callable () و app: callable (بعض ، args). أود أن أقول إنه كان أحد الآثار الجانبية المؤسفة لاستخدام Eval () في التطبيق السابق.

نعم اوافق. لم ندعم أبدًا مثل هذه الطريقة لبدء تطبيق بقدر ما أبحث عنه.

أنا +1 لخطأ أكثر وضوحًا. ربما يجب أن نرفع خطأ إذا كان اسم التطبيق ليس اسمًا بسيطًا؟

يرجى أن تضع في اعتبارك أن هذا كان سلوكًا صريحًا مذكورًا في الوثائق العامة لأحد أطر عمل Wsgi الرئيسية (القارورة) ، وقد تم دعمه مسبقًا بواسطة مشروعك. تؤدي إزالة التقييم إلى منع البدء البطيء للتطبيق ، وهو ما يمثل مشكلة إذا كان التطبيق 1) مقدمًا من مكتبة ، و 2) يؤدي إلى تكاليف إعداد غير بسيطة. إذا لم يكن هناك أمان أو سبب آخر يجعل التقييم غير مناسب ، ألن يكون من الأسهل الاستمرار في دعم سلوكك الحالي؟

في حالة مواجهة أي شخص لحالة مماثلة ، فإن الحل المناسب من Python 3.7 وما بعده سيكون تزييف متغير على مستوى الوحدة النمطية عن طريق إنشاء __getattr__ على مستوى الوحدة ، وفقًا لهذا PEP . من شأن ذلك أن يسمح بالبدء البطيء (على غرار مصانع التطبيقات) دون إحداث تغيير مفاجئ في Gunicorn 20.0.0.

حسنًا ، لم ندعم أبدًا مثل هذا السلوك حقًا ، ولا يستخدمه أي من مستنداتنا أو الأمثلة. هذا لا يتناسب مع سطر الأوامر.

لكن هذا حقًا تغيير مفاجئ وغير متوقع. سأكون بعد ذلك مؤيدًا لإعادة eval وتحذير المستخدم من سلوك مهمل. ربما أيضًا لاستبداله والسماح للأشخاص باستخدام نمط تصميم "مصنع" يمكننا إضافة إعداد --init-args :

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

أو شيء من هذا القبيل. أفكار؟

benoitc سيكون دعم أساليب المصنع بعلامة سطر أوامر واضحة ممتازًا 😄 ربما شيء مثل:

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

(أو ربما اسم أساسي آخر ، مثل --factory )

لقد واجهت مشكلات مع هذا التغيير لأنه لم يعد هناك طريقة تتيح لي إجراء الاختبارات بسهولة. نظرًا لأن (1) تطبيقي يعتمد على متغيرات البيئة ، (2) تقوم مجموعة الاختبار بتحميل جميع الوحدات (للقواعد) و (3) لم يعد بإمكاني تأجيل إنشاء التطبيق إلا بعد الاستيراد ، لا يمكنني اختبار مشروعي دون إضافة سلسلة طويلة متغيرات البيئة قبل كل أمر اختبار ، ويستغرق الاختبار وقتًا أطول مما كان عليه في السابق.

نظرًا لأنني في Python 3.7 ، أعتقد أنه يمكنني اختراق هذا باستخدام مستوى الوحدة __getattr__ ، لكن بالنسبة لما قبل 3.7 ، لا أعتقد أن هناك أي حل لهذه المشكلة إلى جانب الرجوع إلى إصدار سابق.

أعتقد أن دعم أساليب المصنع بعلم سطر الأوامر سيحل هذه المشكلة. إذا فاتني حل واضح ، فسيتم أيضًا تقدير الاقتراحات الأخرى 🙃

@ tjwaterman99 حسنًا ، لست متأكدًا من أنني أحب تمرير الحجج بهذه الطريقة إلى أحد التطبيقات. لا أعتقد أنها فكرة جيدة. يجب تمرير الحجج عن طريق البيئة المحيطة.

مثالنا على استخدام Flask هو فعل ما أصفه. أعتقد أن الطريقة الحالية أسهل في التعامل معها. أفكار؟

أوافق ، أعتقد أن تمرير الحجج عبر البيئة أكثر سهولة ويشجع المستخدمين على أن يكون تكوينهم يعيش في مكان واحد. ومع ذلك ، فإن دعم الكائنات / المصانع القابلة للاستدعاء مهم على الأقل لـ Flask ، وربما لأطر أخرى أيضًا.

+1 لرفع تحذير وتقديم إرشادات حول كيفية استخدام Gunicorn مع المصانع قبل إيقاف الإصدار التالي exec .

من المؤسف أن هذا حدث. لدينا خياران لكيفية الرد. يمكننا تغيير السلوك مرة أخرى أو يمكننا مساعدة الجميع على الهجرة.

إذا أردنا تغيير السلوك مرة أخرى ، فقد يكون من المنطقي سحب الإصدار من PyPI ، لكنني أعتقد أن هذا جذري للغاية. لم يوثق Gunicorn أو يقترح هذا الاستخدام.

لذلك ، أقترح أن نساعد الجميع على التكيف ونعتذر عن الإزعاج.

يجب أن نتواصل مع Flask من خلال العلاقات العامة لتحديث وثائقهم. أنا سعيد لفعل ذلك. أعتقد أن آخرين قاموا بالفعل بتوثيق مسار الهجرة هنا.

سأضيف إلى الاقتراحات أنه قد يكون من المفيد أن يكون لديك وحدة أو برنامج نصي _ منفصل_ يقوم باستيراد مصنع التطبيق واستدعائه وتصديره. يمكن أن يكون هذا بمثابة نقطة دخول Gunicorn ويمكن حذفه من العقيدة والأدوات الأخرى بحيث لا يؤدي إلى عمليات استيراد غير مرغوب فيها عند تشغيل هذه الأدوات في التطوير. يمكن أن يعمل شيء مثل البرنامج النصي __main__.py أو web.py لهذا الغرض.

في المستقبل ، يجب أن نجعل مرشحي الإصدار متاحين حتى عندما نعتقد أن الإصدارات يجب أن تكون آمنة. كان من الممكن أن نكتشف هذا مع مرشح الإصدار ثم أتيحت لنا الفرصة لتوثيق التغيير الفاصل في ملاحظات الإصدار الخاصة بنا ، أو إهماله لدورة ما.

لا أعتقد أنه من المنطقي إضافة دعم لحجج التهيئة في سطر الأوامر. لقد فات الأوان لهذا الإصدار ؛ نحن ندعم بالفعل التطبيقات المخصصة لحالات الاستخدام المتقدمة ؛ والعديد من الأطر لها طرقها الخاصة الموصى بها لتمرير الإعدادات إلى التطبيقات. لا ينبغي أن تحتاج Gunicorn إلى تقديم خدماتها الخاصة. محاولة إضافة الحجج لإصلاح هذه المشكلة يوسع مساحة السطح لهذا النوع من التغيير المفاجئ في المستقبل. يجب أن نهدف إلى تقليل سطح CLI في Gunicorn بقدر ما هو عملي.

يجب أن نتواصل مع Flask من خلال العلاقات العامة لتحديث وثائقهم. أنا سعيد لفعل ذلك. أعتقد أن آخرين قاموا بالفعل بتوثيق مسار الهجرة هنا.

أرى أن @ bilalshaikh42 قد فعل ذلك بالفعل على https://github.com/pallets/flask/pull/3421

(أحد معالجي Flask هنا)

بينما أتفق تمامًا مع التخلص من eval هناك ، أعتقد أنه يجب أن يكون هناك دعم واضح لمصانع التطبيقات! بيت القصيد من مصنع التطبيقات هو تجنب وجود كائن app قابل للاستيراد (نظرًا لأن استخدام ذلك غالبًا ما يؤدي إلى تبعية دائرية).

في flask run cli (للتطوير فقط) أضفنا بالفعل دعمًا واضحًا لمصانع التطبيقات ، لأنها شائعة جدًا.

بالتأكيد ، من السهل إنشاء wsgi.py يحتوي على from myapp. import make_app; app = make_app() . لكنني إما أحتاج إلى الاحتفاظ بهذا الملف بشكل منفصل (وهو أمر غير مريح لأن pip install myapp لن يقوم بتثبيت كل ما هو مطلوب لتشغيله فقط) ، أو وضعه في الحزمة الخاصة بي (مما يعني أنه يمكنك الآن استيراده من الداخل التطبيق نفسه والذي سيكون خطأ)

في Flask ، بحثنا عن طريقة صريحة للتحقق من وجود مصنع تطبيقات قابل للاستدعاء واستدعائه دون اللجوء إلى eval - ربما يمكنك التفكير في شيء مثل هذا أيضًا؟ إذا كنت تريد قدرًا أقل من السحر ، يمكنك حتى استخدام قوائم CLI مختلفة للإشارة إلى أحد التطبيقات وللإشارة إلى مصنع تطبيق.

في المستقبل ، يجب أن نجعل مرشحي الإصدار متاحين حتى عندما نعتقد أن الإصدارات يجب أن تكون آمنة. كان من الممكن أن نكتشف هذا مع مرشح الإصدار ثم أتيحت لنا الفرصة لتوثيق التغيير الفاصل في ملاحظات الإصدار الخاصة بنا ، أو إهماله لدورة ما.

لست متأكدًا مما إذا كانت RCs تساعد حقًا - بشكل عام لا يقوم الأشخاص بالتثبيت / الترقية بـ --pre (أيضًا بسبب سوء عمل ذلك - لا يؤثر ذلك فقط على الحزم المحددة صراحة ، ولكن جميع التبعيات المتداخلة بغض النظر عن مدى عمقها ، لذلك من السهل جدًا أن يتم سحب بعض التبعية في نسخة تجريبية معطلة) ، لذلك لن يكتشف أي شخص لم يعلق على إصداراته أي كسر حتى يتم تحريره فعليًا.

مقابل ما يستحق ، يوفر zope.hookable طريقة سهلة لتنفيذ نهج كسول يشبه المصنع بدون أي تكاليف إضافية (بسبب امتداد C اختياري). ومع ذلك ، فإنه لا يفعل أي شيء حيال تمرير المزيد من الحجج.

# 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

بالتأكيد ، من السهل إنشاء wsgi.py يحتوي على from myapp. import make_app; app = make_app() . لكنني إما أحتاج إلى الاحتفاظ بهذا الملف بشكل منفصل (وهو أمر غير مريح لأن pip install myapp لن يقوم بتثبيت كل ما هو مطلوب لتشغيله فقط) ، أو وضعه في الحزمة الخاصة بي (مما يعني أنه يمكنك الآن استيراده من الداخل التطبيق نفسه والذي سيكون خطأ)

سبب آخر لوجود wsgi.py في مشروعك هو خطأ: بعض الأدوات تستورد جميع الوحدات في المشروع ؛ على سبيل المثال يفعل pytest عند البحث عن الأطباء.

هنا عامل صيانة قارورة آخر. لقد قال

أوافق على التخلص من eval ، وتجنبته في flask run . يمكنك إضافة نسخة أكثر تقييدًا من السلوك السابق. إذا كان خيار سطر الأوامر يحتوي على أقواس ، فافترض أنه مصنع يقوم بإرجاع التطبيق الحقيقي. استخدم literal_eval لتحليل محتويات الأقواس ، ثم اتصل بالمصنع باستخدام المعلمات المحللة.

أعتقد أن نمط المصنع بدون ملف wsgi.py يعد ذا قيمة كبيرة. أود المساعدة في إيجاد طريقة للاحتفاظ بها في Gunicorn.

هل يرغب شخص ما في وضع علاقات عامة معًا مقابل literal_eval من سلاسل التطبيق الشبيهة بالمصنع؟ سيكون هذا في gunicorn.util.import_app .

تحتاج إلى إضافة اختبارات ، ولكن إليك رمز Flask المنقول إلى Gunicorn: https://github.com/benoitc/gunicorn/compare/master...davidism : import-factory

davidism إذا كنت مهتمًا ، فإليك وظيفة قد تكون مفيدة لتحميل التطبيقات من مصانع التطبيقات (مع المبادئ 😄). يستخدم محلل AST المدمج في Python للتمييز بين أسماء السمات واستدعاءات الوظائف (بدلاً من regex). كما أنه يدعم وسيطات الكلمات الرئيسية في وظيفة المصنع. لا يزال يتم تقييم كل شيء باستخدام ast.parse و ast.literal_eval ، لذلك لا توجد مكالمات 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))

إذا تم اتخاذ القرار بدعم مصانع التطبيقات بدون وسيطة فقط ، فيمكن إزالة جميع كود معالجة الوسيطة. ستظل تعمل بشكل جيد للتمييز بين الأسماء ومكالمات المصنع بأمان (وستكون مفيدة لإعطاء رسائل خطأ محددة للمستخدمين عندما يحاولون تمرير الوسيطات إلى المصنع)

ThiefMaster ما زلت غير مقتنع بضرورة دعم مثل هذا النمط. كيف هو مفيد؟ لماذا لا تستخدم متغيرات البيئة لتمرير أرغس مخصصة أو تهيئة إذا كانت هناك حاجة إليها بالفعل؟

بالتأكيد ، إنشاء ملف wsgi.py يحتوي على من myapp. استيراد make_app ؛ app = make_app () سهل. لكنني إما أحتاج إلى الاحتفاظ بهذا الملف بشكل منفصل (وهو أمر غير مريح لأن تثبيت Pip myapp الآن لن يقوم بتثبيت كل ما هو مطلوب لتشغيله فقط) ، أو وضعه في الحزمة الخاصة بي (مما يعني أنه يمكنك الآن استيراده من داخل التطبيق نفسه الذي سيكون خطأ)

لا أفهم ذلك ، لماذا يجب حفظ هذا الملف بشكل منفصل؟

إذا كان في الحزمة ، فهو قابل للاستيراد. لذلك إذا كان لديك مشروع أكبر ، فسيقوم شخص ما في النهاية باستيراده بدلاً من استخدام current_app إلخ .. أي المزيد من العمل عند التعامل مع العلاقات العامة التي تحتوي على هذا النوع من الأخطاء.

إذا كانت خارج الحزمة ، فلن تحصل عليها عند القيام بـ pip install .


FWIW ، أنا لا أهتم حقًا بتجاوز الحجج. عادة لا تكون هناك حاجة إلى هؤلاء (إن env vars هي بالفعل الطريق للذهاب). ولكن على الأقل ، فإن القدرة على الإشارة إلى مصنع تطبيقات قابل للاستدعاء بدلاً من كائن التطبيق أمر مفيد للغاية!

لماذا لا تستخدم متغيرات البيئة لتمرير أرغس مخصصة أو تهيئة إذا كانت هناك حاجة إليها بالفعل؟

pytest بتحميل كل وحدة في المشروع للعثور على الاختبارات. إذا كان لديك كائن app=Flask() عالمي يعتمد على متغيرات البيئة أو ملف تكوين ، فسيتم تحميل هذا الكائن عند تشغيل الاختبارات. من المفيد أن تكون قادرًا على إجراء الاختبارات بدون تعيين متغيرات البيئة أو ملفات التكوين. يعد نمط مصنع التطبيق رائعًا لهذا الغرض.

يعد نمط المصنع الذي يحتوي على الوسائط أمرًا شائعًا إلى حد ما بسبب بعض البرامج التعليمية الشائعة لـ Flask ، ولهذا السبب قمت بدعمه بـ flask run . أوافق على أنه من الأفضل استخدام البيئة لتهيئة التطبيق ، لذلك سأكون بخير مع إصدار أقل يدعم الاتصال بمصنع بدون حجج.

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

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

tilgovi أوافق. مشكلتي الرئيسية هي أننا لم نتوقع أن يؤدي ذلك إلى كسر أي شخص ، ولهذا السبب كنت أقترح إعادة التقييم (أو شيء أكثر أمانًا) وإيقافه. من ناحية أخرى ، نعم ، لم يتم دعم هذا السلوك مطلقًا وكان تأثيرًا مؤسفًا لاستخدام eval : /

davidism مثيرة للاهتمام. ولكن كيف سيكون مختلفًا عن استخدام كائن قابل للاستدعاء كتطبيق؟

لست متأكدًا مما تقصده ، هل يمكنك إعطاء مثال أكثر تحديدًا؟ يقوم المصنع بإرجاع تطبيق WSGI ، فهو ليس تطبيق WSGI في حد ذاته.

davidism أعني شيء مثل هذا


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

application = make_app()

ثم قام شخص ما بتشغيل gunicorn -b :8000 somemodule:application

يؤدي ذلك إلى تقييم application دائمًا عند استيراد الكود ، مما يؤدي إلى تعطيل الغرض من المصنع.

يمكن أيضًا أن يكون استدعاء WSGI عبارة عن مثيل لفئة ، لذلك ربما يكون هذا هو المقصود:

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

(المثال zope.hookable هو نفسه بشكل أساسي ، فقط أقل من النفقات العامة في حالة الاستقرار).

إن امتلاك تطبيق WSGI الذي ينشئ تطبيق WSGI الحقيقي ليس بالأمر المثالي. إنها الآن استدعاء وظيفة إضافية عند كل طلب للتوكيل إلى نقطة الدخول الحقيقية. يجب أن يتم الإعداد قبل الطلب الأول ، لكنه الآن مؤجل حتى ذلك الحين ، مما يجعل الطلب الأول يستغرق وقتًا أطول (من المحتمل أن يكون كثيرًا).

الوظيفة المعنية هنا هي وظيفة المصنع التي تنشئ هذا الكائن بناءً على وقت التشغيل / تكوين البيئة ، وهو أمر مفيد لفصل أجزاء التطبيق ، وتجنب عمليات الاستيراد الدائرية ، وعزل الاختبار الأسهل. إن وجود مكان ما في الكود الذي يستدعي صراحة نوع المصنع من شأنه أن يهزم الغرض من الفصل ، حيث أضمن لك أن المستخدمين سوف يفكرون بعد ذلك "أوه ، يجب أن أقوم باستيراد كائن التطبيق هذا الآن" عندما يتعين عليهم بدلاً من ذلك استخدام الميزات المتاحة لهم في Flask.

في هذه المرحلة ، كل ما نطلبه هو "إذا انتهت سلسلة الاستيراد بأقواس ، فاتصل بالاسم المستورد للحصول على التطبيق".

أعتقد أن لدينا الكثير من الطرق للتغلب على هذا ، لكن هذا يعني فقط أننا لسنا الجمهور المستهدف. أعلم أنه يمكنني القيام بأشياء مثل شحن برنامج نصي خارج الحزمة كنقطة دخول للحاوية الخاصة بي وتكوين pytest لتجاهله ، وما إلى ذلك ، وما إلى ذلك ، لكننا نريد أن نهتم بالأشخاص الذين ينقطعون عنهم ويتابعون البرامج التعليمية وربما لا يفهم traceback.

قد ينجح نموذج محدود للغاية "إذا كان كائن التطبيق قابل للاستدعاء بدون وسيطات ، ثم استدعائه كنمط مصنع" ، لكنه يفشل إذا كان القابل للاستدعاء تطبيق WSGI مزين بشكل سيئ ولا يكشف عن حججه بسهولة من الاستبطان . إذا أردنا أن نكون كرماء ، يجب أن ندعم كل ما كان لدينا من قبل مع تجنب eval ، لذلك أعتقد أن هذا هو المسار الذي يجب أن نفعله.

إنني أقدر حقًا جميع الاقتراحات وأساعد في حل هذا ، الجميع.

أحب الاقتراحات من davidism و connorbrinton باستخدام literal_eval .

يؤدي ذلك إلى تقييم التطبيق دائمًا عند استيراد الكود ، مما يؤدي إلى تعطيل الغرض من المصنع.

حسنًا ، سيبدأ التطبيق في وقت التشغيل ويعيد استدعاء يستخدمه العمال. هذا ليس مختلفًا.

احتياطي الرئيسي حول هذا النمط هو أنه يشجع الناس على تشغيل بعض التعليمات البرمجية مسبقًا والتي يمكن أن تكسر التوقعات على HUP أو USR2. كما أنه يكسر واجهة المستخدم الحالية. هل ستعمل مع الاستخدامات المستقبلية لجونيكورن؟

على أي حال ، فإن الخيارات هي التالية إذن:

  1. يمكننا اعتبار هذا السلوك غير مدعوم وغير موثق (في gunicorn). التغيير الذي تم بناءً عليه.
  2. كان بعض المستخدمين يعتمدون عليه ونريد الآن

(1) هو مسار صعب ، ولكنه أيضًا المسار المنطقي ، مع الأخذ في الاعتبار أننا لم ندعمه أبدًا.
(2) نوع من الجمالية يكسر واجهة مستخدم سطر الأوامر ، يحتاج إلى بعض الاختبارات / الأمثلة للاختبار في gunicorn ، استخدم شيئًا مثل literal_evals

يمكننا دعم (2) ولكن أود إجراء بعض الاختبارات. كما يجب علينا توثيقها؟

tilgovijamaddenberkerpeksagsirkonst ما هو المفضل لديك؟

يبدو أن حالة استخدام معطلة أخرى كانت الوصول إلى السمة ، والتي لن يتم حلها بواسطة literal_eval .

على سبيل المثال ، في Plotly Dash ، تستخدم الكائن Dash ، والذي يحتوي داخليًا على مثيل Flask كسمة server . كان بعض الناس يستخدمون:

gunicorn "module:app.server"

لست متأكدًا من أنه يجب دعم ذلك. flask run لا يدعمه أيضًا. يبدو أن الكائن Dash يجب أن يحتوي على طريقة __call__ تنتقل إلى Flask.__call__ . بالإضافة إلى ذلك ، يقول مستندات Dash إنه يقوم بعمل server = app.server ويشير إلى Gunicorn في ذلك ، لذلك يبدو أن هذا هو في الغالب حالة من بعض المعلومات غير الصحيحة التي يتم تمريرها.

davidism لقد كنت مريضًا اليوم ولكن لإصداره يوم الاثنين. اقتراح tilgovi جيد والخطة العامة هي الحصول على تقييم آمن يحل محل التقييم القديم.

أعتقد أننا يجب أن نحذر المستخدم من أنه يستخدم مثل هذه التهيئة. أفكار ؟ سي سي تيلجوفي

سأحاول تحويل الفرع الذي ربطته أعلاه إلى علاقات عامة مع الاختبارات يوم السبت ، إلا إذا كنت ترغب في إنشاء تطبيق مختلف.

davidism المضي قدما. سأعود يوم الأحد حيث سأراجعها إذا احتجت :) شكرا!

متأخر قليلا ، والعمل على هذا الآن.

connorbrinton فكرة رائعة لاستخدام ast.parse ، سأحاول ذلك وأشركك كمؤلف مشارك في الالتزام إذا ذهبت معها.

أردت فقط أن تتناغم مع وجود إجابة Stack Overflow شائعة إلى حد ما (وقديمة جدًا) والتي توجه المستخدمين نحو سلوك v19 ، والذي قد يحتاج إلى تحديث اعتمادًا على الاختيار الذي يتم اتخاذه: https://stackoverflow.com/questions/ 8495367 / using-extra-command-line-arguments-with-gunicorn

ثابت في الماجستير. شكرا @ davidism على التصحيح!

جميع الحالات التي تم التعامل معها في هذه الاختبارات: https://github.com/benoitc/gunicorn/commit/19cb68f4c3b55da22581c008659ee62d8c54ab2b#diff -5832adf374920d75d4bf48e546367f53R67

هل كانت هذه الصفحة مفيدة؟
0 / 5 - 0 التقييمات