เมื่อใดควรใช้ Flask.g


174

ผมเห็นว่าgจะย้ายจากบริบทการร้องขอให้เข้ากับบริบท app ในขวด 0.10 gซึ่งทำให้ผมสับสนเกี่ยวกับวัตถุประสงค์การใช้งานของ

ความเข้าใจของฉัน (สำหรับ Flask 0.9) คือ:

  • g อาศัยอยู่ในบริบทคำขอคือสร้างใหม่อีกครั้งเมื่อคำขอเริ่มต้นและพร้อมให้บริการจนกว่าจะสิ้นสุด
  • gมีวัตถุประสงค์เพื่อใช้เป็น "กระดานดำคำขอ" ซึ่งฉันสามารถใส่สิ่งต่าง ๆ ที่เกี่ยวข้องในช่วงเวลาของการร้องขอ (กล่าวคือตั้งค่าสถานะที่จุดเริ่มต้นของคำขอและจัดการกับมันในตอนท้ายอาจมาจากbefore_request/ after_requestคู่)
  • นอกเหนือจากการร้องขอระดับสถานะgสามารถและควรใช้สำหรับการจัดการทรัพยากรเช่นการเชื่อมต่อฐานข้อมูล ฯลฯ

ประโยคใดบ้างที่ไม่เป็นจริงใน Flask 0.10 อีกต่อไป ใครบางคนสามารถชี้แนะฉันไปที่ทรัพยากรเพื่อพูดคุยถึงเหตุผลของการเปลี่ยนแปลง ฉันควรใช้อะไรเป็น "กระดานดำคำขอ" ใน Flask 0.10 - ฉันควรสร้างแอพ / ส่วนขยายพร็อกซีโลคอลของเธรดโลคัลเฉพาะแอพของตัวเองและผลักมันไปที่สแต็กบริบทbefore_requestหรือไม่ จุดประสงค์ของการจัดการทรัพยากรในบริบทของแอปพลิเคชันคืออะไรหากแอปพลิเคชันของฉันใช้งานได้นาน (ไม่ชอบคำขอ) และทำให้ทรัพยากรไม่เคยมีอิสระ


ฉันเห็นด้วยนั่นคือการเปลี่ยนแปลงที่ค่อนข้างแปลก หวังว่า mitsuhiko จะใช้วัตถุบริบทคำขอบางประเภทเพื่อแทนที่gใน 0.10 มิฉะนั้นดูเหมือนว่ารหัสจำนวนมากอาจเริ่มพัฒนาข้อบกพร่องที่คดเคี้ยว
Anorov

11
FWIW, Armin Ronacher (ผู้เขียนขวด) ได้เปิดตัวผลสืบเนื่องของ "รูปแบบขั้นสูงกระติกน้ำ" flask.gซึ่งแสดงให้เห็นตัวอย่างรหัสบางอย่างเกี่ยวกับวิธีการใช้ใหม่ speakerdeck.com/mitsuhiko/advanced-flask-patterns-1
Markus Unterwaditzer

1
บริบทคำขอใหม่หมายถึงบริบทแอปใหม่ดังนั้นจึงควรใช้งานได้ปกติเมื่อใช้งานปกติ
Ronny

คำตอบ:


120

รูปแบบกระติกน้ำขั้นสูงซึ่งเชื่อมโยงโดย Markusอธิบายการเปลี่ยนแปลงบางอย่างgใน 0.10:

  • g ตอนนี้อยู่ในบริบทของแอปพลิเคชัน
  • ทุกคำขอผลักบริบทแอปพลิเคชันใหม่เช็ดเก่าดังนั้นจึงgยังสามารถใช้ในการตั้งค่าสถานะต่อคำขอโดยไม่ต้องเปลี่ยนรหัส
  • บริบทของแอปพลิเคชันถูกเปิดขึ้นหลังจากที่ teardown_requestถูกเรียก (งานนำเสนอของ Armin อธิบายว่าเป็นเพราะสิ่งต่าง ๆ เช่นการสร้างการเชื่อมต่อฐานข้อมูลเป็นงานที่ตั้งค่าสภาพแวดล้อมสำหรับการร้องขอและไม่ควรจัดการภายในbefore_requestและafter_request)

ในซอร์สโค้ดที่คุณเชื่อมโยงเมื่อapp_ctx is None or app_ctx.app != self.appเป็นเท็จบริบทแอปพลิเคชันเก่าจะถูกนำมาใช้ซ้ำหรือไม่ ดูเหมือนจะไม่ถูกต้องเนื่องจากบริบทแอปพลิเคชัน "จะไม่ถูกแชร์ระหว่างคำขอ" ...
nalzok

2
คุณหมายถึงการผลักดันของapp.app_context() ? ถ้าเป็นเช่นนั้นควรมีการบันทึกapp_context()บริบทแอปพลิเคชั่นใหม่ทุกครั้งที่โทร - มันจะไม่นำบริบทมาใช้ซ้ำ
theY4Kman

1
ใช่ว่าเป็นเรื่องจริง แต่เมื่อใดapp_ctx is not None and app_ctx.app == self.appที่app_ctx = self.app.app_context()สายไม่ถูกดำเนินการ เพียง แต่self._implicit_app_ctx_stack.append(None)จะถูกดำเนินการในกรณีนี้
nalzok

1
โอ้ขอโทษฉันอ่านผิด! ในการตั้งค่าการผลิตจะมีการร้องขอเพียงหนึ่งครั้งต่อเธรด (หรือกรีนเล็ต) เพียงคนเดียวที่RequestContextจะถูกผลักดังนั้นเพียงหนึ่งAppContextจะถูกผลัก แต่ถ้าโหมดดีบักเปิดอยู่และคำขอล้มเหลว Flask จะบันทึกบริบทดังนั้นจึงสามารถใช้กับดีบักเกอร์ได้ Noneจะถูกผนวกเข้ากับ_app_ctx_stackดังนั้นเมื่อคำขอถูกดึงลงมันก็ไม่รู้ว่าจะเปิดAppContextเพียงแค่ สิ่งเดียวกันนี้เกิดขึ้นกับไคลเอนต์ทดสอบซึ่งยังคงบริบทดังนั้นจึงสามารถตรวจสอบได้
theY4Kman

ดังนั้นขอบเขตของ g คือตามคำขอ (เธรด) และจะไม่เก็บค่าไว้ในคำขอถัดไป
ตัวแปร

83

ในฐานะที่เป็นภาคผนวกของข้อมูลในหัวข้อนี้: ฉันรู้สึกสับสนเล็กน้อยจากพฤติกรรมของflask.gเช่นกัน แต่การทดสอบอย่างรวดเร็วบางอย่างได้ช่วยให้ฉันชี้แจง นี่คือสิ่งที่ฉันได้ลอง:

from flask import Flask, g
app = Flask(__name__)

with app.app_context():
    print('in app context, before first request context')
    print('setting g.foo to abc')
    g.foo = 'abc'
    print('g.foo should be abc, is: {0}'.format(g.foo))

    with app.test_request_context():
        print('in first request context')
        print('g.foo should be abc, is: {0}'.format(g.foo))
        print('setting g.foo to xyz')
        g.foo = 'xyz'
        print('g.foo should be xyz, is: {0}'.format(g.foo))

    print('in app context, after first request context')
    print('g.foo should be abc, is: {0}'.format(g.foo))

    with app.test_request_context():
        print('in second request context')
        print('g.foo should be abc, is: {0}'.format(g.foo))
        print('setting g.foo to pqr')
        g.foo = 'pqr'
        print('g.foo should be pqr, is: {0}'.format(g.foo))

    print('in app context, after second request context')
    print('g.foo should be abc, is: {0}'.format(g.foo))

และนี่คือผลลัพธ์ที่ได้จาก:

in app context, before first request context
setting g.foo to abc
g.foo should be abc, is: abc  

in first request context
g.foo should be abc, is: abc
setting g.foo to xyz
g.foo should be xyz, is: xyz  

in app context, after first request context
g.foo should be abc, is: xyz  

in second request context
g.foo should be abc, is: xyz
setting g.foo to pqr
g.foo should be pqr, is: pqr  

in app context, after second request context
g.foo should be abc, is: pqr

ดังที่ YKKman กล่าวไว้ข้างต้นว่า "ทุกคำขอจะมีบริบทของแอปพลิเคชันใหม่" และอย่างที่ Flask docs กล่าวว่าบริบทของแอปพลิเคชัน "จะไม่ถูกแชร์ระหว่างคำขอ" ตอนนี้สิ่งที่ยังไม่ได้รับการระบุไว้อย่างชัดเจน (แม้ว่าฉันคิดว่ามันเป็นนัยจากงบเหล่านี้) และสิ่งที่แสดงให้เห็นว่าการทดสอบอย่างชัดเจนของฉันคือคุณควรไม่ชัดเจนสร้างบริบทการร้องขอหลายซ้อนอยู่ภายในบริบทของโปรแกรมประยุกต์หนึ่งเพราะflask.g(และร่วม) doesn' ไม่มีเวทย์มนตร์ใด ๆ ที่มันทำหน้าที่ใน "ระดับ" สองบริบทที่แตกต่างกันโดยมีสถานะแตกต่างกันอย่างอิสระที่ระดับแอปพลิเคชันและคำขอ

ความจริงก็คือ "บริบทการประยุกต์ใช้" อาจค่อนข้างชื่อที่ทำให้เข้าใจผิดเพราะapp.app_context() เป็นบริบทต่อคำขอตรงเช่นเดียวกับ"บริบทคำขอ" คิดว่ามันเป็น "request context lite" จำเป็นเฉพาะในกรณีที่คุณต้องการตัวแปรบางตัวที่โดยปกติต้องใช้บริบทการร้องขอ แต่คุณไม่จำเป็นต้องเข้าถึงวัตถุคำขอใด ๆ (เช่นเมื่อเรียกใช้การดำเนินการ DB แบตช์ใน เชลล์สคริปต์) หากคุณลองและขยายบริบทแอปพลิเคชันเพื่อรวมบริบทคำขอมากกว่าหนึ่งรายการคุณกำลังถามถึงปัญหา ดังนั้นแทนที่จะทดสอบของฉันข้างต้นคุณควรเขียนโค้ดเช่นนี้ด้วยบริบทของ Flask:

from flask import Flask, g
app = Flask(__name__)

with app.app_context():
    print('in app context, before first request context')
    print('setting g.foo to abc')
    g.foo = 'abc'
    print('g.foo should be abc, is: {0}'.format(g.foo))

with app.test_request_context():
    print('in first request context')
    print('g.foo should be None, is: {0}'.format(g.get('foo')))
    print('setting g.foo to xyz')
    g.foo = 'xyz'
    print('g.foo should be xyz, is: {0}'.format(g.foo))

with app.test_request_context():
    print('in second request context')
    print('g.foo should be None, is: {0}'.format(g.get('foo')))
    print('setting g.foo to pqr')
    g.foo = 'pqr'
    print('g.foo should be pqr, is: {0}'.format(g.foo))

ซึ่งจะให้ผลลัพธ์ที่คาดหวัง:

in app context, before first request context
setting g.foo to abc
g.foo should be abc, is: abc  

in first request context
g.foo should be None, is: None
setting g.foo to xyz
g.foo should be xyz, is: xyz  

in second request context
g.foo should be None, is: None
setting g.foo to pqr
g.foo should be pqr, is: pqr

7
การเพิ่มขึ้นเนื่องจากย่อหน้าสุดท้ายบริบทของขวดนั้นค่อนข้างสับสนที่จะเข้าใจในตอนแรก จากชื่อคุณจะได้รับความรู้สึกว่าบริบทการร้องขอนั้นเป็นไปตามคำขอและบริบทของแอพมีอยู่แม้ว่าจะมีการร้องขอหรือไม่ได้รับผลกระทบจากอายุการใช้งาน
simanacci
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.