From 529962d6b540c804769ae653e79fd9ea49dbef03 Mon Sep 17 00:00:00 2001 From: Fernando Ochoa O Date: Thu, 24 Jun 2021 14:43:08 -0500 Subject: [PATCH] Flask Update. Flask Update, works for CPython 3.10.0 Beta 3. --- .gitignore | 26 - src/flask/__init__.py | 102 +- src/flask/__main__.py | 16 +- .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 2095 bytes .../__pycache__/__main__.cpython-310.pyc | Bin 0 -> 393 bytes src/flask/__pycache__/_compat.cpython-310.pyc | Bin 0 -> 4620 bytes src/flask/__pycache__/app.cpython-310.pyc | Bin 0 -> 74615 bytes .../__pycache__/blueprints.cpython-310.pyc | Bin 0 -> 21961 bytes src/flask/__pycache__/cli.cpython-310.pyc | Bin 0 -> 26570 bytes src/flask/__pycache__/config.cpython-310.pyc | Bin 0 -> 10210 bytes src/flask/__pycache__/ctx.cpython-310.pyc | Bin 0 -> 14433 bytes .../__pycache__/debughelpers.cpython-310.pyc | Bin 0 -> 6614 bytes src/flask/__pycache__/globals.cpython-310.pyc | Bin 0 -> 1668 bytes src/flask/__pycache__/helpers.cpython-310.pyc | Bin 0 -> 34868 bytes src/flask/__pycache__/logging.cpython-310.pyc | Bin 0 -> 3138 bytes .../__pycache__/sessions.cpython-310.pyc | Bin 0 -> 12200 bytes src/flask/__pycache__/signals.cpython-310.pyc | Bin 0 -> 2380 bytes .../__pycache__/templating.cpython-310.pyc | Bin 0 -> 4964 bytes src/flask/__pycache__/testing.cpython-310.pyc | Bin 0 -> 8820 bytes src/flask/__pycache__/views.cpython-310.pyc | Bin 0 -> 4788 bytes .../__pycache__/wrappers.cpython-310.pyc | Bin 0 -> 4338 bytes src/flask/_compat.py | 145 ++ src/flask/app.py | 1202 +++++++++++------ src/flask/blueprints.py | 527 ++++---- src/flask/cli.py | 365 +++-- src/flask/config.py | 148 +- src/flask/ctx.py | 161 ++- src/flask/debughelpers.py | 98 +- src/flask/globals.py | 27 +- src/flask/helpers.py | 995 +++++++++----- src/flask/json/__init__.py | 464 ++++--- .../json/__pycache__/__init__.cpython-310.pyc | Bin 0 -> 11635 bytes .../json/__pycache__/tag.cpython-310.pyc | Bin 0 -> 10462 bytes src/flask/json/tag.py | 127 +- src/flask/logging.py | 55 +- src/flask/sessions.py | 128 +- src/flask/signals.py | 23 +- src/flask/templating.py | 52 +- src/flask/testing.py | 155 +-- src/flask/views.py | 46 +- src/flask/wrappers.py | 112 +- 41 files changed, 2882 insertions(+), 2092 deletions(-) delete mode 100644 .gitignore create mode 100644 src/flask/__pycache__/__init__.cpython-310.pyc create mode 100644 src/flask/__pycache__/__main__.cpython-310.pyc create mode 100644 src/flask/__pycache__/_compat.cpython-310.pyc create mode 100644 src/flask/__pycache__/app.cpython-310.pyc create mode 100644 src/flask/__pycache__/blueprints.cpython-310.pyc create mode 100644 src/flask/__pycache__/cli.cpython-310.pyc create mode 100644 src/flask/__pycache__/config.cpython-310.pyc create mode 100644 src/flask/__pycache__/ctx.cpython-310.pyc create mode 100644 src/flask/__pycache__/debughelpers.cpython-310.pyc create mode 100644 src/flask/__pycache__/globals.cpython-310.pyc create mode 100644 src/flask/__pycache__/helpers.cpython-310.pyc create mode 100644 src/flask/__pycache__/logging.cpython-310.pyc create mode 100644 src/flask/__pycache__/sessions.cpython-310.pyc create mode 100644 src/flask/__pycache__/signals.cpython-310.pyc create mode 100644 src/flask/__pycache__/templating.cpython-310.pyc create mode 100644 src/flask/__pycache__/testing.cpython-310.pyc create mode 100644 src/flask/__pycache__/views.cpython-310.pyc create mode 100644 src/flask/__pycache__/wrappers.cpython-310.pyc create mode 100644 src/flask/_compat.py create mode 100644 src/flask/json/__pycache__/__init__.cpython-310.pyc create mode 100644 src/flask/json/__pycache__/tag.cpython-310.pyc diff --git a/.gitignore b/.gitignore deleted file mode 100644 index e50a290e..00000000 --- a/.gitignore +++ /dev/null @@ -1,26 +0,0 @@ -.DS_Store -.env -.flaskenv -*.pyc -*.pyo -env/ -venv/ -.venv/ -env* -dist/ -build/ -*.egg -*.egg-info/ -_mailinglist -.tox/ -.cache/ -.pytest_cache/ -.idea/ -docs/_build/ -.vscode - -# Coverage reports -htmlcov/ -.coverage -.coverage.* -*,cover diff --git a/src/flask/__init__.py b/src/flask/__init__.py index 3e7198a6..f1f6466b 100644 --- a/src/flask/__init__.py +++ b/src/flask/__init__.py @@ -1,46 +1,60 @@ -from markupsafe import escape -from markupsafe import Markup -from werkzeug.exceptions import abort as abort -from werkzeug.utils import redirect as redirect +# -*- coding: utf-8 -*- +""" + flask + ~~~~~ -from . import json as json -from .app import Flask as Flask -from .app import Request as Request -from .app import Response as Response -from .blueprints import Blueprint as Blueprint -from .config import Config as Config -from .ctx import after_this_request as after_this_request -from .ctx import copy_current_request_context as copy_current_request_context -from .ctx import has_app_context as has_app_context -from .ctx import has_request_context as has_request_context -from .globals import _app_ctx_stack as _app_ctx_stack -from .globals import _request_ctx_stack as _request_ctx_stack -from .globals import current_app as current_app -from .globals import g as g -from .globals import request as request -from .globals import session as session -from .helpers import flash as flash -from .helpers import get_flashed_messages as get_flashed_messages -from .helpers import get_template_attribute as get_template_attribute -from .helpers import make_response as make_response -from .helpers import safe_join as safe_join -from .helpers import send_file as send_file -from .helpers import send_from_directory as send_from_directory -from .helpers import stream_with_context as stream_with_context -from .helpers import url_for as url_for -from .json import jsonify as jsonify -from .signals import appcontext_popped as appcontext_popped -from .signals import appcontext_pushed as appcontext_pushed -from .signals import appcontext_tearing_down as appcontext_tearing_down -from .signals import before_render_template as before_render_template -from .signals import got_request_exception as got_request_exception -from .signals import message_flashed as message_flashed -from .signals import request_finished as request_finished -from .signals import request_started as request_started -from .signals import request_tearing_down as request_tearing_down -from .signals import signals_available as signals_available -from .signals import template_rendered as template_rendered -from .templating import render_template as render_template -from .templating import render_template_string as render_template_string + A microframework based on Werkzeug. It's extensively documented + and follows best practice patterns. -__version__ = "2.1.0.dev0" + :copyright: 2010 Pallets + :license: BSD-3-Clause +""" +# utilities we import from Werkzeug and Jinja2 that are unused +# in the module but are exported as public interface. +from jinja2 import escape +from jinja2 import Markup +from werkzeug.exceptions import abort +from werkzeug.utils import redirect + +from . import json +from ._compat import json_available +from .app import Flask +from .app import Request +from .app import Response +from .blueprints import Blueprint +from .config import Config +from .ctx import after_this_request +from .ctx import copy_current_request_context +from .ctx import has_app_context +from .ctx import has_request_context +from .globals import _app_ctx_stack +from .globals import _request_ctx_stack +from .globals import current_app +from .globals import g +from .globals import request +from .globals import session +from .helpers import flash +from .helpers import get_flashed_messages +from .helpers import get_template_attribute +from .helpers import make_response +from .helpers import safe_join +from .helpers import send_file +from .helpers import send_from_directory +from .helpers import stream_with_context +from .helpers import url_for +from .json import jsonify +from .signals import appcontext_popped +from .signals import appcontext_pushed +from .signals import appcontext_tearing_down +from .signals import before_render_template +from .signals import got_request_exception +from .signals import message_flashed +from .signals import request_finished +from .signals import request_started +from .signals import request_tearing_down +from .signals import signals_available +from .signals import template_rendered +from .templating import render_template +from .templating import render_template_string + +__version__ = "1.1.4" diff --git a/src/flask/__main__.py b/src/flask/__main__.py index 4e28416e..f61dbc0b 100644 --- a/src/flask/__main__.py +++ b/src/flask/__main__.py @@ -1,3 +1,15 @@ -from .cli import main +# -*- coding: utf-8 -*- +""" + flask.__main__ + ~~~~~~~~~~~~~~ -main() + Alias for flask.run for the command line. + + :copyright: 2010 Pallets + :license: BSD-3-Clause +""" + +if __name__ == "__main__": + from .cli import main + + main(as_module=True) diff --git a/src/flask/__pycache__/__init__.cpython-310.pyc b/src/flask/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2a2deff5b137593c350763e87c6783f08844ebaa GIT binary patch literal 2095 zcmbuATW?}F5XT`2Ie`QSAtCqVn)joA*fCS~W2l40U@z@^E*qk_@&!yn`_sieEHJw!IA8ZUiBhdI3 zzGel;PqCDr_D57Y9AP7Ac*(7mR zO@(=uC(Z#ESb=yFxX6mcQ))Uau@Z4!&4guECN8MiaE{Fp7lA9RLOcyT&*q6szzb}F zct$OTOKgd_47|*iiD!XV*b4C+@G4s+t^lvGHR5^Tb+%5tpxkhSZ4fU4Z?a9|CEzW# zMZ65W&9;eGfOpsq@hb2x+a+ED-eY^j>%jYLpV$SivMTY0%7zE*fOu2A2oKpI@fL86 z)rhx&>#R<^1AN4ehLN0DR8QiC+M}WG{&ifnTv# z#5MIge8b)l*MZ-%x4=j2!ax3$s-8Ujm2=@~Dq*gP{=g?k_|6RjPsdFyLU|kOYquec z^xZgef0g?BL3Ubo*Zsln8CTv}8JXZls(aUuy-p}2D}9 zJn1IFTB##b@2z&>#mT)6T5Wsbo*thZyT1sfq%}0lg@O^0kr(d!Pv6zfY9Ey77@7Nw zr@or*j!WZ-L?Znp^mQiz%7{j+Ex?49exRjiKc{=%V^=1Qpre4lT-=C2iH5?_neQS!@{27UivxXNt`6o ze{SrUHW}xKJgpQ^r55+&Ta2pGkT5Yu9r;+O{=T4Rt`$GkM&5ZcvAqlx`^gw22Yjao zm1zc1&|kDTut8-soOy7Y9hja4Q8Ga*5(;WQbmB^bSo*Qi3^3{QI4jRga1wBX51Ett zN&Woi>Q`Nd!)`s!+4xluU5QhTZvj0D!pgUU`}(*;qsOqP@YvI_figM+#%@Z(OC<#p zdT|JsR8OGiC}dY>fi%8*^d#DHjlp#=dI}8Pai|nr37yBrsAunV0iBB|(<8x{lmqjm)zTS z99{kxG%ihGWi5f5ikIn<{#%bYheKmIuP682iLb-h?2BmLiA J>woLS$bWgQMwtKr literal 0 HcmV?d00001 diff --git a/src/flask/__pycache__/__main__.cpython-310.pyc b/src/flask/__pycache__/__main__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e129613d1d9986894224022f31c39a94fc7c873f GIT binary patch literal 393 zcmY*V!Ait15KY=@YjM5!1G!dcUG-K()b%JLtfx|lG0E; z|3DTL)2s{nU}o}$nY@|NgFy@|K38w|-!{JYli2ihYZfWW}kV?mtN%; zJ_Oun0o!_p5U}mBvm|_ciIK%b5;M;*u82?=yVTP^#4dLs1u;rreZn^TUm1Rxi8gbS-k$>zn@W0gU2ZxaS35KiykxasU7T literal 0 HcmV?d00001 diff --git a/src/flask/__pycache__/_compat.cpython-310.pyc b/src/flask/__pycache__/_compat.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4ea75d0af8683b4e1acffbc0a2fdebbe56a2f5e9 GIT binary patch literal 4620 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 PQ%$afE$DOM(w=_-G*L%W literal 0 HcmV?d00001 diff --git a/src/flask/__pycache__/blueprints.cpython-310.pyc b/src/flask/__pycache__/blueprints.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..10e62d7866a722cd4d68d645b20040262686c456 GIT binary patch literal 21961 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 Mv{lH>=8io3zsJyS+5i9m literal 0 HcmV?d00001 diff --git a/src/flask/__pycache__/cli.cpython-310.pyc b/src/flask/__pycache__/cli.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6de97e850ff0ad3b67c72af10c5faac54bce7c51 GIT binary patch literal 26570 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 AhyVZp literal 0 HcmV?d00001 diff --git a/src/flask/__pycache__/config.cpython-310.pyc b/src/flask/__pycache__/config.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7ba988760e2ad353be415ddd7c78dd053802de47 GIT binary patch literal 10210 zcmeHN-E-Skb_YQ4)08ZKM2VAZ7u&><*|n*7x1HT`yqhSpoK@>cszr}$t}`S^fD$AS zpf5nl(o&hsDt+8Zr%!#9ANRTcL!bK8x8il5_CM&P(@lTpTmS@Qd6Jj*snig{*FE># z^Z7gH(x02FDfs;7H~-$dzoaOCpo{EB$Hm9^24fs7rK4D?rMYTH<^NhozZ{5*O%-$ zT+dtDw$i8{e_b>18M`D ztU|^CllU*YI7%H(0=7s!o-zGSzSdKikUh)s2Q0 zRk~fz9NFD&G`C}iUC8*r7FcMb5b1&K4kMi&3$m!+D`F1+zPY{HdNvpzg}c7@?4i?t z7C#^zw(+_8gatk-3!tLFHKCU86_>~?PuBV>CewWc1wb#9r>Fje=Isj{cuP)-W?ui=R*7N^RYHZ8nVaj&qghzbT_EhWj2sN#|r)6m5w zoJR%NPo$F?BTXFe0_9$r1?UFWT+B=uqbM@dnh_+mdW>=))BbdLt}xNn?1ZEj*?; z_Qw;7P~@N-eWV4(aca%MZDAbw6Jr3+=ILiKPO@Rf5s&$4wPb*!&}ojxao}tczuoZ> zaeQkD)I>bXV7Rl?kRvvQVY_r>!$GWb z1K$|?LE!XVFo+;$OZCKL5Vbk(1qe+EMbop4Lr7Kfn6NREX_7bCpQLv8CIQUBMlg+~ zfQ4n{e1Gke$DbO%Fxp}wRY=0$UR!;%*6#l8+EYAPvTTUdA5)St_U)r3UgbqPk|Mg5 zMB7+5>z>i~!SlcjV2rm&49GmO`n@A#i=!+X00}quTuL%%+~zrb51O5f$F?9^#0$ho z7HUDT`8>ha3n`B{ufXWDlBR@6{JZ{POiBtYHioQ8QWk<{C}*1D$cW!#6QP+sKa{gG zy>_ZZy_mL0vIvz~l z4nh(uoSmj6DdsI6rXD0hXX>_h;0WJiwI0CbLDPnqK#b^shWVXN2@)82Yw79c<3}r7 zTMyS)SK8~F8{O@-c6)u})9u7PVFKYUrf0Rq#t2;E4R(2|{4&{hcg+KK3Pg7v`g)FI zp|cRl>kmzsDQ-f^FoI3bc<6YO7w}cf*xWY$o(UzsWxpUSWCS5%7|9{)81b{$E$tUe z%jERSp&#RbW#V8d4o4o`)U*7m4SVa$73PRho5)d~wg*h^W?}uF?)JkHAak%RY zc3Je&x&;PFB#6+)8pOJu?Ao)yMYf#($OswZMpFC$R0v7-rP*Yx>b6vkBf~|W3|*0x zWnN!GR3wbn>U|;6?E}WR={kB%mT*fh57uu^+%RZStdt=)n`Vv-e@Gre@5H{vqhCdN z1d>)M!M=?{sGI@kO|@~!QC@1QM16op3C;klqb$f-KB}gyC79z~kccV?Yv=PmL4(IM z6HU25FfK-`DSCU5!#?y{%H2CUIUL=x~^>}!I)x)t34kW(O>rtZX!5fDhXE<8OcZ{LwxDy0*3i%@k ziP@7y8!>b4L%xo3EV7Up@VzkAfgIJXkdvN?u z?bj^MxGBh)DeKUBD5jZ#5lj#-q{k-!V8Eb*-pc}y?Kqgi)#7kZ6evX!EacEH<9?nP zEf(cqNs4KsQH(S{h|00_qXO!IsQzezyfL!Z$Y7#N8ORnFA)UEoQdXq+GkA))Mu!>r z=5j=i1#!Ip6wd>K>GisfVnkOp`YUMvt$#aTI=;HfRiwm+azM^Afshpf?;JP&6(_N0 zDtiTZr*@)-`dQJEeyUCj-O};HPxP=7R;T(|?Io3D`YDR7y-TNvH%}C+ zsKe<`3##H(?kYj^P*oNBdy4N9#rr^4!unBPRZh@VIDQ8aU~5|3n+xam=JCCZ@4~Qf zpopy}7-1DpieI2k-d3zK-nz1=OpDzszm4x7B37JTrD6|#h|0Eds5GkP8;BN<#}*>G z>>6AQUkK)#S$LI1fgv(4q2JWx`ZncT#2$bI=~|pl59ylQ1;`vYk8y2`Kxc6ABAA@a z5vM!yl-MJQGpAo(|MyN4ovO9&9}L=7Y!H1)(U37p(4EH-kpIaZUUXt`g}4O3)Vq9u%A~ zi`)i^YM*gw-KJ&&+8#6zWw!2*ZBL*Phjs$$#4+whIsq4YhjKCADX(wJ=ukvf@%*T? zwz0Xk(Z>Jv?fdJGqB5@AbW%azi4#v8M`T&iD9L6ehU{N z!b7N@YN8qV4(&xf5-wo#`(X@pf25b4oi!Yig?9Q=ZQeBxq>nKNc@^ zWv`sT&J-~!XKuArJsiAQ<2+?Rs1`B(LlEfRZm!qDJX<`}xVhfE1 zfrh4gG^_VFWs8&Rxk&&@04TBN$3-9&R0DubRFTb#D)W>fs(k`TB`%R>1!LM<#8VmO zBy}mu5ZYxWXt5Zy4rnC~`yhg4ZlU`@a zPx^}FXeOSu7bew)#JA-64saX6Z2o^8WWkP!Ma2cjws6{I7pCD}LW+r-(JOfPOTU`A>U z`AIcL(t3*GGw$|~a-|*M#UvBbL|m4WkHy}SQy+EhQW;_gfSPCd7m}KV)8jcJ$=oU6 zOG$CQ&YG(XY7hs|A3B8WYpiAy49Z@EG93MdP+zf)k`2r35z`rKskBDwzVxAp(+uQb1Hd;EKvlz%eB%1No9kC50t7vkP$i zbC_2A9AkfnZ$JpQW>k=HDo8upm-?Ub=|(d0dK7zx@#j(O@ee|s(#_YGPYPl2tTZj0 zm0v2TK6T(!HHTAwcn+t&uPb5YXfwvCH-J+=0s^gu)x8?NmxdZJ>PL)GRmP~ORjmTv zs53^b1EcbN#;9{s{UuTw@TqQU5Z3p_r+>4(xxp~4X|$=dOxz{a#;f3zhs3^57*#gC zxp95OMxAxlnQLcVV1)PttAWAASol?2NpQx+I5oqze}JP@~FuZ?*| zl%j!i@fUO;JSm!VARHAHIbjRh`V!PY5kIBFU((^P=GumSCsCLan4 zz;4PWzQI4^u!Ai+f`X^mW8T-qk3yAd$s6z(K*JmGN`OPUJ}Z!SqHIR}4tJ?u3E5=a<)VTmV+DfnvLMMtMJ9XP zL~9l359tBGkQ>-{sXgydNeTpMik%5HV{aQfNTWBcHihE?YR0IHj85@3D#3eg&k}1y zwMw-JR~F(UTqkuxIkuIkw2y7%ouE^RZvad6F>Vih) z0g8if=L<4&R}i_iKk0?zYgr44$+rh&)X9goX47J!jSELXUjbXXTps)gnk$}^rbYBK z(T4~Yo+EmTccw*INLcDWz+O(uo;EF>lo6fZo0iD}a9*0~(=v37|6E2mnB);Rq0WUv zXgf5qISr+n=oIy-Zv+r2jUXxi;?1yB!@R#SGDGy02(0k-;Lw4>C}9T3_xAD_`P+HX zG!kGsp$_5e0qC!n)<0YMbgjGH-h8yyZ9m;wTl$>#mWIJ>JBjec3^wLRY^}6^B}Xiy zFSLx;mYosxB){MAPgb_qx{n_{lq7b;aNJr3ft}`|-KVX6K-E#65>v;oB(QH5<6^Ko z5FfT?(BCEvP;n8bsotv-^iW;UUZLyz`|O_Q#rd05K}4E9 zOH|q|wOEdFk4)Z|$0e%a>FIfyUGOTE6Wxh$>Gkg;pOYD!O9$}2>LH~{eGY?7ZrJ}&==Dr(os0J_P>p%~(&N&(P2$3Xfh zoiZMiZUoX8Y-=C@de9Yi-6$5yS|h3F$RSdVri8L$m**K>XZL{P9mjNF%mHfg(afIy zSJ|NA#PJ61#FB;TOe!f;-x1O`(9@;V7u5ieMHo&HisRuGJ=VP+`$xce>511vOpJnLp(%y`Qmd#aF*L2`%{<+HaRhhGM?wQNm zl!=J>)r68oyD`|a36Wi=$jn1+5j#l@ZR5Cn?`B}!41U?rqiVOyg$RB-!auKh2cxOC zCG4^2b{nevHA|j#v5lANn2&$GvY6dSlEdV$R@4U#t=(?aCE+(QM!ZD_`Ynrea`ht53oxPNRB=*l%b9ce-Ol&>&T+V~P|D!<(~tkLv34Vq`8VE-|8lswjwAXJPJX7A@h#tO zTQv)Rvu(SRtz|7f%e8Zzd@YabykBS+I>lO1u8X*KYK~kx?YT~=R+8&ET+i3$<+{`^ zcNS_3ay^gh#oD5k364F@)RyjM{Ib9BLB?OeeYSSoU-XZCkg1*Um;B@Sd(uDQpTyr& z{we=7{+>qpGyYkWKP%L6-o9#WZpAC^ zm#$pB;@vCqoDL-NH?=uv4)HNp6|s$eNTD4 zUZC(~;nlUZd$%7qgI?SUyFa#25KqtEy#MWQ-CnKVd;8wo^;>Iiy>a(`{q|e0t>INs z1nY>*qeOYPlf4VbLaVUBJ_#@8GKF&-o?j%kk&^GXBo_3;rVh zmP9{G==Y6Y?^gWyZaZA}+L8BH81)Ue*Y;Z7L=#o_%?MiXBG+@fUMGlpUNdkvLghwr zs9N1k7h?2Wzc1HbSA>sILi`@(=uYbFmJ09~UwK^@rOaE`>&IaSZ<)2h+goqrN*m7#`?Av5Y`8rYKHg94q*`_TAZn`Cda$zM zF7E{U%Pz*ZsjY|xdTT3a?x2AH7O13aag5^qI6!--D~Mp9No_2T8g~N~VR)YJ2R>@O zQoVXb*EiW(D-P7f&B~~$`$61S7<<>{D-SYsL)0ba?WL`Xq)BL+ijgu%FQ-?ZJQC4dTfmuJ4OxFS+OoG#PAo{dVl`dhLEtbzg7o1fo;6 zwbFrvRvLb*8CN%hxG~nlm7b@(j#RYbuEJ204LR%2q4c;ar{m*y;vtwE!s&v*@=v zhAYvaKAqz28JvCxM^wPcnJZh1_7NrF+vh4tmGNE0_P&;=fD@GXg%~%yICWUqYbzcL z7H}rUl08fhY1gAvi8=(#aJgz>2BcdHRnVeNRS)MN~I%OwY&MLm0 zkSaBrO7#S8DAjHIFiZJa&@N@@{WXMt`*F|&OdvYi=y#ifc)Sg0Qe&3{16EykjZg;v z0fcV!+nnhz)YitnxEBCMF9cXM=vT@EWtkcYLcEALj1g$6G-dAORP3Bcq7meOoe(Um`FkF7ueaT(E2UyYELbsMDYS-UM z95tU$yiZ`n@~c6+9l8%g)%HKTtVe4!(Fs*x)PdMHra%eZ5H&!WMEn+nm+E5AlT;;? z=Cls+t?2|bcLy+v)1B7^P=+8#A+0DB5Uk3iIKN;|8SoT7t@3lGoLwH{5jV#XN_3$Tcw%LK*R z7;`H6)9}Pm<*(y;MCiGAI%lab;9|n1vUp&Q=xex11C<2?G#}f({nYv+gsa2sv&Iz?LC|HAnYd^NN=s^E?M8a^bki9mPiUdHPb<(ml)TWi z%Az{oLBXyz1r+PLJxK+bmFS+2*UdC3@RL3yLvo`aVoJgak;`cVZFQUNK2j7WJTm;S z*TUWO$hFPP2n(%FFH|wG`>q)#J#RyW9i7#?NdXS9o^-^71DRKmuKzkg<`>q>u9zRg zjUU$OM0ILxI$W{b=TO2GH)9EykP0plwl$fIHk%M%LRA>?gi7qhw4v+J^QIAY8~KsD zANFA;{WkC|ge<7f2z+-uc#Occsj^y~e5M6mMV7tSy*NIeQAFmaCcNDbHv4T)rQs(D zaWgUW747|l0||hmXvEQnU1Z(s5)NU7vD*$|VQvKJ!@Er z1cNd;69LB{hv8H>L)SEH02J*JOvRyI_=|xkw4%g-rYzx-wAu0YWy&M+i9Ps?=kH=B zMN%zh$-FSJuT~fA61gCTvdNIC!Kef{L;YNGLDRS!AcCZxsMSWGf-&PuosjjrDNU51 zpK;jJ1T`6v;!+p=_39gtNgD`d!s{7XfX=%u5C|7Z99$x5P(-Ge(CJPH77=r}dTA8^ zYX-dGCsp$WAdR`4XoOCUE{|2mQL;t@@1~>btN_Pa#;bVA2(>!)^hmqX!Lb79GZ5K+}4Io^`I|-VYX58&QtVT@7>!y=j!uq-EVISx;A$6tYi%}mm9tgV?k^M>| z(R+jRlX*;%`jAB5nz-zqXcU#r>~~)Io!dLe&_W zOH-ubNu&_dHue_?A`xR)>AdbWg}ym^ z0A?DpSh{KmW7mEAh?!yj_Fz+CJ3zQ;3Tvz9Ao(((e+>*l#V{&aPY^(kL?np-v*cvJ zu`PMMOHkl*PmAEOZU03GN>F7F>MJ&-r=$%?AZA_yxZx zyuIVk;hE!?{CWJH^UJ`#3&YY4(Dix|#CsQ6>e{6IQcSsylnp3QICdl4R1`@4TFn-M zqzH}i7_}G>8E=7mhsUZE>RxiARtG#gv=zEr;U0@?aVHLZUkC1_Ur??zJ|mnbc=b67 zH@KCMGU;FnjGs3tCBRv_!+BuJo>(QPD%wccGXNajYwfmtunW(%J*n^r(L_z&q9%U} zClXC{oDV^@JmWR^Tjnmf%7J#t)bqq9pjs5!1kfuk{tQp(9@qP-jY-4nA*v4N$8Kt; z#k8=Hw5gY5>LTQ!wK*)pJ2#r$*f21xZ_JYgN{~j9iRz#6GNEBe!jy7@lOxlqno<)P z+63vbg6|K2y0)?A_;sL)dM36cM+21>g9v#B>}3cgTon$BpxqpjBYqa6&J8Z#6e~y+ zk{afewHaSmOdN-E!Q&>z8+dB9GN-=DM@4uWS=M1T+K&{uTuoEum>nsC9FqB6U!<$f z^S!ug>aXf$Jo{H1!VUsBftD&+OV;4nm^@h7gtn<^B2&@!V_XbgG}8EnNP{G!Z|_*@ z{DBoSgFUOI;#)6dK)+?SNw$H{w=xe!I?pms>~5ZN;?*9Q{{t}p(N}&C^5XS|eI~>F zH+Bx^__atf$9#8i`d--6%UduFBgm>qOiXKV83nMC=Hh_r)@7n^kfCgmi_)I`yAD}(h7G0trW~- zGUO%%t5!}nAuJ@D5Ehe72*>mmgxZoUX1_38oScZfpFs9&nA*%tl-RMj5b1ufB<=L2 z3fch^5+H>%{lI+n8W(l+Y#?jWk{vUstp}T}ZkN2!6sMFBWPtd2{Z)R z6A13Yd69IpT7)uI*ot-Ado6TPPZ{a7`{w=Ekvz%Nk<_5`@&xH{X`VGbd~o+QRJYqg zR3JyiC2`DpV2w1QeJRf$Y7FX;P*5W zaz}P|Faa2iQ9~3N;d8%8)M9W{gI!36ieBjPB)xrTSg>hjq99iA5dMV85qUw6?LPJo zh<+go>48a9xwB-mg~dqjElKzb#JChBiTIM>=LW*=BN3G zyILYF6p+;g3x>~ObKsD%3jymEU<9PHb$qaJKRTIN;@bm<{k z(JR%jB$h+S$)pfe03_Ljj49A$G|3VL+x1(%m>&~&v0{+j5v-HO-)OvY`{tW>8x0vI z+PJw7ZZjqcE~9BQmbK;8WjRvq2F8Y#@eQn~(O6Uc0N06rkGO&{3KJ2;Y-D=@P+}`) zLi~K4phLo-LDX;v-(62|G_*0>K)45}-GU1?L>Pfyd|^k4bY<@oB`RYB*?@&Y)JhoC z?e|iG#cQ`>v!R7~QHT4U+zo&%EqU_cTv(#G`+$J-N1^UO+Av-32B_4uG8W@BmrGMJ z!qD7bVnD3ULp~SB*k#iOz6cb?{{{#g>2B0V^%w|Z3)pxt14aUSf!Y}a{mts@WD*~6 zQ&bhk4)vfV;S1R-C6aDw_Y!-=ZVR|BJZ@|OT}qA*ia`O7vo(fVyLXCYeoNCRF zGcM8LEgaFG;FQ4iEY?Lcs!Z6<3H?d-0Bd@6+s71N&kpABJO}uNH@ttC>7LGI;{5(? zEAs?f?gpg;te|e&^}<7tk3OEgm+$8E-#w%O_@;P(@9WMcujeEUD9Q7Ac~T}s*ke*~ zr?Q}2bgQBv8PO;x%xU@Dz1|#POrn`T$+Oe023sxTr1KEwt{F_Ot|Rb*}6%c zCby#v=ZvHH*zuKKvo5ua+L4?P#Yu;T#bnM8L%5s!=6Bl{STQh0W zBlwd@HO9@-S@jm8^b_Ovc!S+vtqlYq!UeO^HCmsz88o?vQd}5Q2J`31W;y$$J<>?R z>oTljHjq#m>zRZd`_(#;un~PMxq+rl)@aaCv?5N=nvV7X5o{;LIV>`Z?=k!JCf{?) z+ms!BSF594EGVGt&e+5lVtSTJpc*XF^yACn#$|kIHVTY%gG6i%<~4HK)k>ZjkU%-?n{d;Y zQjBgr(|@ku1)^O>ydk%cgB$1Yukf?M$@`NoOyiueb;hYQLO*+be&L8e_kKgne)wT-;{6zlb|gvFzh9b6spO2+ZW z0vNr`cxm#ux+=6$<@6Z;eVbqC&1Ag*cx3c0||ApK~dJ?wZ$Co058?M{dPNjF4H^rN?|j`66@p|=&&fwW1;d9 zADv83GUt-$pxPt$rchc0i&Ds)5=I5rcFD#pY?lfS=HL>U7Q2kK!Xs{bgzB0=N|3co zQHK_3s=-@65fYp@S`jRRO&U-f%qY%5wV#gjhlOWw&V=$RLiyS-=Hk4cgJFCDB}!r# z^Qff^4#0kzeUdxK`8L%1jf31%D!1?iNGo2DHuDErbr-GVx63e&;x*7NQuZFq5t~Cu z!4QMNY#)I-b9im2GqM0oTVe!tYW*bSr4)e`+7zJyCr4-^P z0vcR_$VNQml-Q~>sL0AOCGI7jC_X9fC`6Sw+I2Lj9rNTkCz({OJyJSV#8zw4#CrZC zc9iMR0NWdU@GefZ1)-Ns=p9Gj7?irna#L={&i#;X_X;jbj%{;JTzD_c&MR2>)V%RhJiYw}6y=YtdXCudURMMqF6C(~OvWYLHo$W7r4yV0?Pemsk( z+3lRd|7ruD1lf#pfw&bxv#33Vca`kw;H7f}y_sGy#xhUJk1p_VBjdPMAY%?XzZx=Rl+{VkzyQi*Qh}`81(X#u_h3H*Ikqgnw zz$$Oyd(8qfc^hVdRE7oqKO14PKysV~X5~#U;>AC41k8eLv2U^RpB+)qXe03IYUTKI zIp?9HIe~azF+my@$lw655nw7#eAA?KgU_fq%}ldHV`3J8%QW}~UVMTh;*MY0%6r~+ zOI8l%&NMVG$K8sN~m s26hkd@@1$a6Oqmb1&{xCp={%yES~UO$Sq}^JI)KvMaNw#o%+&$1M|)bX8-^I literal 0 HcmV?d00001 diff --git a/src/flask/__pycache__/debughelpers.cpython-310.pyc b/src/flask/__pycache__/debughelpers.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..64b5bab4bb2a843a2079e352228a2c57dce92564 GIT binary patch literal 6614 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 literal 0 HcmV?d00001 diff --git a/src/flask/__pycache__/helpers.cpython-310.pyc b/src/flask/__pycache__/helpers.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b5c20a7841d3abc16a449c770005551a69c2e26a GIT binary patch literal 34868 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�IO77zaC8ZpLV2J1fV?cHY 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 C_KOSv literal 0 HcmV?d00001 diff --git a/src/flask/__pycache__/sessions.cpython-310.pyc b/src/flask/__pycache__/sessions.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a899b68532d4dbbb796f7be8134cc1fbb7fbc5f7 GIT binary patch literal 12200 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 literal 0 HcmV?d00001 diff --git a/src/flask/__pycache__/testing.cpython-310.pyc b/src/flask/__pycache__/testing.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..25dcd9fb16a838f8cf6b5ea05ee0bb61991271ef GIT binary patch literal 8820 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 MwQ^qii?z!C0$O{BfdBvi literal 0 HcmV?d00001 diff --git a/src/flask/__pycache__/views.cpython-310.pyc b/src/flask/__pycache__/views.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..007821c4645a907ba9efa26237fefcad85ed4a29 GIT binary patch literal 4788 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 literal 0 HcmV?d00001 diff --git a/src/flask/__pycache__/wrappers.cpython-310.pyc b/src/flask/__pycache__/wrappers.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..102f221cb39ac48dba116a452969ef964979449c GIT binary patch literal 4338 zcmai1TaVku73NJ6rPW@%PBv-M8si|SD`187^{gYmfKQ+AEKucbr#!og1M(H5@~yu^F0E%c-fcg>l_!sBtZ{r%k7Y zah*59rD@w~tFav}Pgk53UAwFCCU3pacuTYnO{a=yda)JwKiu$Y4B4d+e9jKfTWW=!Kz3^P_rU;0rA7v0K}}yZWoE zw?l89h}L(olGf3))-8{3<+AbCI2zhp(cuB9_Kn^#`isk-c|j;ROJl}`A9KM-XBH24g%7iQb^U7`WJ6R| z<6<};OFAQ~k77CX(vFc?i3mrTE?#SL8Nd9{z1@54&koW_96i1t3?C;!Dz45vf6p6> ziqoujqASp|nz#?4G}&->GM|>qg#G zxNg>R-D%9{A&uLv`{mpV^Iv2GGMy!LV!lGIVn*qHtXrWuGVeQRN)KDJ^tO?k9CIfg z6K^-rl6&Y%e{tP0xdERU@R#A#MT48%dZ8Waj*W4R*VVX*af91x+{zQiS>gu#yCg}j z_jg3j4&u5Dfvfxp){BVG;vmYi z;YlF&nJ33{l{-aBY(i4P_9tFSixW1VF)v{~AEJ}q;CY^sg(`zCW1Ay(5YO4Z2cZZd zDQ2PP=Z8`Q2!i5 zk?f-j>bE8VR;i3gP^QbnmTlkt;)3+xX=7LOFVkY>Sfo?`YiYx+~f3I%iV>n-8& zt8o+_Fy*&Gox$M2%|E%fANF_d^mp9*clvjC?hOXakE2mAW_#kG>#Uz$(eD^}O4sv+ zuXVYEgHOeOwm$Wqx>%7CPK6>Gr<0qcB&DTgwsh;w;)B9^-Lp&Ie#s5&f2Qq%T;{rL z(r8!5MbSFO;$P&#Do#@h#4>u11r#E0hoS~i@5j?&5P4}3M{GYxC(6V`A4L?Axm<*K z0<$3Mh9XZL6|A^ilFGS{*u|*{u!F^td6ftt1bm0Y`C{vS9_)}Hp88^@j++oOMfiDt zBBESPAlN5)K<@`(SP8fg7(3ZmM{*;0m-(ZE=}gL9kf{f5BO7 z7|z8^0!K2vD=8Wz1#!P)N^-5zHLJ^H76$2gm&uxyfX}gBU1{BXN4MWBE|gcQR-CvK z~((4W@!*WVI2%w+x51w_Wk0$(v+w4=(Ob;3B5oa;Z4?XT^{?ctGwqltAueb z$atO-Cs!e#*#Jc-nI6b@X+MRf{D3-A>X@Qb3rNMHF6Yq%tnXr315AL(l<=|{3 zsNij&%I9;GNJufjtm0B0M_1B(Glck3c=%ayWI_Ezd{lJ-%Y0Rn0!vsA5j8o*nNx31 zxTVGepd)xJ#T_>+@ie_Q zNvGkN+P|u#VmbI@!=w;Y@)XSsax2|W8y00k<$Zt=1$-Tzso}0%rp!5j;evTc_(ekpX0kkt0@4rpePV*EvONOn^T7edB zi6Zi2Ov}sEk^F4AIN29rSe~v_xI#-54wL3%w4{wr;qV39Fw6~I-KpPM&5ycR*|q}x zM1Au)7ph;%?>B|o@~3nJNq5@iwa6RJ#+mh9`s`AlP)^IOK8-WfyV#vI#$i14!X!7k k(*CGZ!E65}|780uMPEW?628frZC(8tZGFvLZ*6S;7cr}lJOBUy literal 0 HcmV?d00001 diff --git a/src/flask/_compat.py b/src/flask/_compat.py new file mode 100644 index 00000000..76c442ca --- /dev/null +++ b/src/flask/_compat.py @@ -0,0 +1,145 @@ +# -*- coding: utf-8 -*- +""" + flask._compat + ~~~~~~~~~~~~~ + + Some py2/py3 compatibility support based on a stripped down + version of six so we don't have to depend on a specific version + of it. + + :copyright: 2010 Pallets + :license: BSD-3-Clause +""" +import sys + +PY2 = sys.version_info[0] == 2 +_identity = lambda x: x + +try: # Python 2 + text_type = unicode + string_types = (str, unicode) + integer_types = (int, long) +except NameError: # Python 3 + text_type = str + string_types = (str,) + integer_types = (int,) + +if not PY2: + iterkeys = lambda d: iter(d.keys()) + itervalues = lambda d: iter(d.values()) + iteritems = lambda d: iter(d.items()) + + from inspect import getfullargspec as getargspec + from io import StringIO + import collections.abc as collections_abc + + def reraise(tp, value, tb=None): + if value.__traceback__ is not tb: + raise value.with_traceback(tb) + raise value + + implements_to_string = _identity + +else: + iterkeys = lambda d: d.iterkeys() + itervalues = lambda d: d.itervalues() + iteritems = lambda d: d.iteritems() + + from inspect import getargspec + from cStringIO import StringIO + import collections as collections_abc + + exec("def reraise(tp, value, tb=None):\n raise tp, value, tb") + + def implements_to_string(cls): + cls.__unicode__ = cls.__str__ + cls.__str__ = lambda x: x.__unicode__().encode("utf-8") + return cls + + +def with_metaclass(meta, *bases): + """Create a base class with a metaclass.""" + # This requires a bit of explanation: the basic idea is to make a + # dummy metaclass for one level of class instantiation that replaces + # itself with the actual metaclass. + class metaclass(type): + def __new__(metacls, name, this_bases, d): + return meta(name, bases, d) + + return type.__new__(metaclass, "temporary_class", (), {}) + + +# Certain versions of pypy have a bug where clearing the exception stack +# breaks the __exit__ function in a very peculiar way. The second level of +# exception blocks is necessary because pypy seems to forget to check if an +# exception happened until the next bytecode instruction? +# +# Relevant PyPy bugfix commit: +# https://bitbucket.org/pypy/pypy/commits/77ecf91c635a287e88e60d8ddb0f4e9df4003301 +# According to ronan on #pypy IRC, it is released in PyPy2 2.3 and later +# versions. +# +# Ubuntu 14.04 has PyPy 2.2.1, which does exhibit this bug. +BROKEN_PYPY_CTXMGR_EXIT = False +if hasattr(sys, "pypy_version_info"): + + class _Mgr(object): + def __enter__(self): + return self + + def __exit__(self, *args): + if hasattr(sys, "exc_clear"): + # Python 3 (PyPy3) doesn't have exc_clear + sys.exc_clear() + + try: + try: + with _Mgr(): + raise AssertionError() + except: # noqa: B001 + # We intentionally use a bare except here. See the comment above + # regarding a pypy bug as to why. + raise + except TypeError: + BROKEN_PYPY_CTXMGR_EXIT = True + except AssertionError: + pass + + +try: + from os import fspath +except ImportError: + # Backwards compatibility as proposed in PEP 0519: + # https://www.python.org/dev/peps/pep-0519/#backwards-compatibility + def fspath(path): + return path.__fspath__() if hasattr(path, "__fspath__") else path + + +class _DeprecatedBool(object): + def __init__(self, name, version, value): + self.message = "'{}' is deprecated and will be removed in version {}.".format( + name, version + ) + self.value = value + + def _warn(self): + import warnings + + warnings.warn(self.message, DeprecationWarning, stacklevel=2) + + def __eq__(self, other): + self._warn() + return other == self.value + + def __ne__(self, other): + self._warn() + return other != self.value + + def __bool__(self): + self._warn() + return self.value + + __nonzero__ = __bool__ + + +json_available = _DeprecatedBool("flask.json_available", "2.0.0", True) diff --git a/src/flask/app.py b/src/flask/app.py index cacb40a5..e385899e 100644 --- a/src/flask/app.py +++ b/src/flask/app.py @@ -1,32 +1,42 @@ -import functools -import inspect -import logging +# -*- coding: utf-8 -*- +""" + flask.app + ~~~~~~~~~ + + This module implements the central WSGI application object. + + :copyright: 2010 Pallets + :license: BSD-3-Clause +""" import os import sys -import typing as t -import weakref +import warnings from datetime import timedelta +from functools import update_wrapper from itertools import chain from threading import Lock -from types import TracebackType from werkzeug.datastructures import Headers from werkzeug.datastructures import ImmutableDict from werkzeug.exceptions import BadRequest from werkzeug.exceptions import BadRequestKeyError +from werkzeug.exceptions import default_exceptions from werkzeug.exceptions import HTTPException from werkzeug.exceptions import InternalServerError -from werkzeug.local import ContextVar +from werkzeug.exceptions import MethodNotAllowed from werkzeug.routing import BuildError from werkzeug.routing import Map -from werkzeug.routing import MapAdapter from werkzeug.routing import RequestRedirect from werkzeug.routing import RoutingException from werkzeug.routing import Rule -from werkzeug.wrappers import Response as BaseResponse +from werkzeug.wrappers import BaseResponse from . import cli from . import json +from ._compat import integer_types +from ._compat import reraise +from ._compat import string_types +from ._compat import text_type from .config import Config from .config import ConfigAttribute from .ctx import _AppCtxGlobals @@ -36,7 +46,9 @@ from .globals import _request_ctx_stack from .globals import g from .globals import request from .globals import session -from .helpers import _split_blueprint_path +from .helpers import _endpoint_from_view_func +from .helpers import _PackageBoundObject +from .helpers import find_package from .helpers import get_debug_flag from .helpers import get_env from .helpers import get_flashed_messages @@ -45,62 +57,50 @@ from .helpers import locked_cached_property from .helpers import url_for from .json import jsonify from .logging import create_logger -from .scaffold import _endpoint_from_view_func -from .scaffold import _sentinel -from .scaffold import find_package -from .scaffold import Scaffold -from .scaffold import setupmethod from .sessions import SecureCookieSessionInterface from .signals import appcontext_tearing_down from .signals import got_request_exception from .signals import request_finished from .signals import request_started from .signals import request_tearing_down +from .templating import _default_template_ctx_processor from .templating import DispatchingJinjaLoader from .templating import Environment -from .typing import AfterRequestCallable -from .typing import BeforeFirstRequestCallable -from .typing import BeforeRequestCallable -from .typing import ErrorHandlerCallable -from .typing import ResponseReturnValue -from .typing import TeardownCallable -from .typing import TemplateContextProcessorCallable -from .typing import TemplateFilterCallable -from .typing import TemplateGlobalCallable -from .typing import TemplateTestCallable -from .typing import URLDefaultCallable -from .typing import URLValuePreprocessorCallable from .wrappers import Request from .wrappers import Response -if t.TYPE_CHECKING: - import typing_extensions as te - from .blueprints import Blueprint - from .testing import FlaskClient - from .testing import FlaskCliRunner - -if sys.version_info >= (3, 8): - iscoroutinefunction = inspect.iscoroutinefunction -else: - - def iscoroutinefunction(func: t.Any) -> bool: - while inspect.ismethod(func): - func = func.__func__ - - while isinstance(func, functools.partial): - func = func.func - - return inspect.iscoroutinefunction(func) +# a singleton sentinel value for parameter defaults +_sentinel = object() -def _make_timedelta(value: t.Optional[timedelta]) -> t.Optional[timedelta]: - if value is None or isinstance(value, timedelta): - return value - - return timedelta(seconds=value) +def _make_timedelta(value): + if not isinstance(value, timedelta): + return timedelta(seconds=value) + return value -class Flask(Scaffold): +def setupmethod(f): + """Wraps a method so that it performs a check in debug mode if the + first request was already handled. + """ + + def wrapper_func(self, *args, **kwargs): + if self.debug and self._got_first_request: + raise AssertionError( + "A setup function was called after the " + "first request was handled. This usually indicates a bug " + "in the application where a module was not imported " + "and decorators or other functionality was called too late.\n" + "To fix this make sure to import all your view modules, " + "database models and everything related at a central place " + "before the application starts serving requests." + ) + return f(self, *args, **kwargs) + + return update_wrapper(wrapper_func, f) + + +class Flask(_PackageBoundObject): """The flask object implements a WSGI application and acts as the central object. It is passed the name of the module or package of the application. Once it is created it will act as a central registry for @@ -192,9 +192,11 @@ class Flask(Scaffold): for loading the config are assumed to be relative to the instance path instead of the application root. - :param root_path: The path to the root of the application files. - This should only be set manually when it can't be detected - automatically, such as for namespace packages. + :param root_path: Flask by default will automatically calculate the path + to the root of the application. In certain situations + this cannot be achieved (for instance if the package + is a Python 3 namespace package) and needs to be + manually defined. """ #: The class that is used for request objects. See :class:`~flask.Request` @@ -274,16 +276,13 @@ class Flask(Scaffold): "PERMANENT_SESSION_LIFETIME", get_converter=_make_timedelta ) - #: A :class:`~datetime.timedelta` or number of seconds which is used - #: as the default ``max_age`` for :func:`send_file`. The default is - #: ``None``, which tells the browser to use conditional requests - #: instead of a timed cache. + #: A :class:`~datetime.timedelta` which is used as default cache_timeout + #: for the :func:`send_file` functions. The default is 12 hours. #: - #: Configured with the :data:`SEND_FILE_MAX_AGE_DEFAULT` - #: configuration key. - #: - #: .. versionchanged:: 2.0 - #: Defaults to ``None`` instead of 12 hours. + #: This attribute can also be configured from the config with the + #: ``SEND_FILE_MAX_AGE_DEFAULT`` configuration key. This configuration + #: variable can also be set with an integer value used as seconds. + #: Defaults to ``timedelta(hours=12)`` send_file_max_age_default = ConfigAttribute( "SEND_FILE_MAX_AGE_DEFAULT", get_converter=_make_timedelta ) @@ -317,7 +316,7 @@ class Flask(Scaffold): #: This is a ``dict`` instead of an ``ImmutableDict`` to allow #: easier configuration. #: - jinja_options: dict = {} + jinja_options = {"extensions": ["jinja2.ext.autoescape", "jinja2.ext.with_"]} #: Default configuration parameters. default_config = ImmutableDict( @@ -340,7 +339,7 @@ class Flask(Scaffold): "SESSION_COOKIE_SAMESITE": None, "SESSION_REFRESH_EACH_REQUEST": True, "MAX_CONTENT_LENGTH": None, - "SEND_FILE_MAX_AGE_DEFAULT": None, + "SEND_FILE_MAX_AGE_DEFAULT": timedelta(hours=12), "TRAP_BAD_REQUEST_ERRORS": None, "TRAP_HTTP_EXCEPTIONS": False, "EXPLAIN_TEMPLATE_LOADING": False, @@ -369,7 +368,7 @@ class Flask(Scaffold): #: the test client that is used with when `test_client` is used. #: #: .. versionadded:: 0.7 - test_client_class: t.Optional[t.Type["FlaskClient"]] = None + test_client_class = None #: The :class:`~click.testing.CliRunner` subclass, by default #: :class:`~flask.testing.FlaskCliRunner` that is used by @@ -377,7 +376,7 @@ class Flask(Scaffold): #: Flask app object as the first argument. #: #: .. versionadded:: 1.0 - test_cli_runner_class: t.Optional[t.Type["FlaskCliRunner"]] = None + test_cli_runner_class = None #: the session interface to use. By default an instance of #: :class:`~flask.sessions.SecureCookieSessionInterface` is used here. @@ -385,27 +384,41 @@ class Flask(Scaffold): #: .. versionadded:: 0.8 session_interface = SecureCookieSessionInterface() + # TODO remove the next three attrs when Sphinx :inherited-members: works + # https://github.com/sphinx-doc/sphinx/issues/741 + + #: The name of the package or module that this app belongs to. Do not + #: change this once it is set by the constructor. + import_name = None + + #: Location of the template files to be added to the template lookup. + #: ``None`` if templates should not be added. + template_folder = None + + #: Absolute path to the package on the filesystem. Used to look up + #: resources contained in the package. + root_path = None + def __init__( self, - import_name: str, - static_url_path: t.Optional[str] = None, - static_folder: t.Optional[str] = "static", - static_host: t.Optional[str] = None, - host_matching: bool = False, - subdomain_matching: bool = False, - template_folder: t.Optional[str] = "templates", - instance_path: t.Optional[str] = None, - instance_relative_config: bool = False, - root_path: t.Optional[str] = None, + import_name, + static_url_path=None, + static_folder="static", + static_host=None, + host_matching=False, + subdomain_matching=False, + template_folder="templates", + instance_path=None, + instance_relative_config=False, + root_path=None, ): - super().__init__( - import_name=import_name, - static_folder=static_folder, - static_url_path=static_url_path, - template_folder=template_folder, - root_path=root_path, + _PackageBoundObject.__init__( + self, import_name, template_folder=template_folder, root_path=root_path ) + self.static_url_path = static_url_path + self.static_folder = static_folder + if instance_path is None: instance_path = self.auto_find_instance_path() elif not os.path.isabs(instance_path): @@ -424,6 +437,24 @@ class Flask(Scaffold): #: to load a config from files. self.config = self.make_config(instance_relative_config) + #: A dictionary of all view functions registered. The keys will + #: be function names which are also used to generate URLs and + #: the values are the function objects themselves. + #: To register a view function, use the :meth:`route` decorator. + self.view_functions = {} + + #: A dictionary of all registered error handlers. The key is ``None`` + #: for error handlers active on the application, otherwise the key is + #: the name of the blueprint. Each key points to another dictionary + #: where the key is the status code of the http exception. The + #: special key ``None`` points to a list of tuples where the first item + #: is the class for the instance check and the second the error handler + #: function. + #: + #: To register an error handler, use the :meth:`errorhandler` + #: decorator. + self.error_handler_spec = {} + #: A list of functions that are called when :meth:`url_for` raises a #: :exc:`~werkzeug.routing.BuildError`. Each function registered here #: is called with `error`, `endpoint` and `values`. If a function @@ -431,16 +462,40 @@ class Flask(Scaffold): #: tried. #: #: .. versionadded:: 0.9 - self.url_build_error_handlers: t.List[ - t.Callable[[Exception, str, dict], str] - ] = [] + self.url_build_error_handlers = [] + + #: A dictionary with lists of functions that will be called at the + #: beginning of each request. The key of the dictionary is the name of + #: the blueprint this function is active for, or ``None`` for all + #: requests. To register a function, use the :meth:`before_request` + #: decorator. + self.before_request_funcs = {} #: A list of functions that will be called at the beginning of the #: first request to this instance. To register a function, use the #: :meth:`before_first_request` decorator. #: #: .. versionadded:: 0.8 - self.before_first_request_funcs: t.List[BeforeFirstRequestCallable] = [] + self.before_first_request_funcs = [] + + #: A dictionary with lists of functions that should be called after + #: each request. The key of the dictionary is the name of the blueprint + #: this function is active for, ``None`` for all requests. This can for + #: example be used to close database connections. To register a function + #: here, use the :meth:`after_request` decorator. + self.after_request_funcs = {} + + #: A dictionary with lists of functions that are called after + #: each request, even if an exception has occurred. The key of the + #: dictionary is the name of the blueprint this function is active for, + #: ``None`` for all requests. These functions are not allowed to modify + #: the request, and their return values are ignored. If an exception + #: occurred while processing the request, it gets passed to each + #: teardown_request function. To register a function here, use the + #: :meth:`teardown_request` decorator. + #: + #: .. versionadded:: 0.7 + self.teardown_request_funcs = {} #: A list of functions that are called when the application context #: is destroyed. Since the application context is also torn down @@ -448,32 +503,66 @@ class Flask(Scaffold): #: from databases. #: #: .. versionadded:: 0.9 - self.teardown_appcontext_funcs: t.List[TeardownCallable] = [] + self.teardown_appcontext_funcs = [] + + #: A dictionary with lists of functions that are called before the + #: :attr:`before_request_funcs` functions. The key of the dictionary is + #: the name of the blueprint this function is active for, or ``None`` + #: for all requests. To register a function, use + #: :meth:`url_value_preprocessor`. + #: + #: .. versionadded:: 0.7 + self.url_value_preprocessors = {} + + #: A dictionary with lists of functions that can be used as URL value + #: preprocessors. The key ``None`` here is used for application wide + #: callbacks, otherwise the key is the name of the blueprint. + #: Each of these functions has the chance to modify the dictionary + #: of URL values before they are used as the keyword arguments of the + #: view function. For each function registered this one should also + #: provide a :meth:`url_defaults` function that adds the parameters + #: automatically again that were removed that way. + #: + #: .. versionadded:: 0.7 + self.url_default_functions = {} + + #: A dictionary with list of functions that are called without argument + #: to populate the template context. The key of the dictionary is the + #: name of the blueprint this function is active for, ``None`` for all + #: requests. Each returns a dictionary that the template context is + #: updated with. To register a function here, use the + #: :meth:`context_processor` decorator. + self.template_context_processors = {None: [_default_template_ctx_processor]} #: A list of shell context processor functions that should be run #: when a shell context is created. #: #: .. versionadded:: 0.11 - self.shell_context_processors: t.List[t.Callable[[], t.Dict[str, t.Any]]] = [] + self.shell_context_processors = [] - #: Maps registered blueprint names to blueprint objects. The - #: dict retains the order the blueprints were registered in. - #: Blueprints can be registered multiple times, this dict does - #: not track how often they were attached. + #: all the attached blueprints in a dictionary by name. Blueprints + #: can be attached multiple times so this dictionary does not tell + #: you how often they got attached. #: #: .. versionadded:: 0.7 - self.blueprints: t.Dict[str, "Blueprint"] = {} + self.blueprints = {} + self._blueprint_order = [] #: a place where extensions can store application specific state. For #: example this is where an extension could store database engines and - #: similar things. + #: similar things. For backwards compatibility extensions should register + #: themselves like this:: + #: + #: if not hasattr(app, 'extensions'): + #: app.extensions = {} + #: app.extensions['extensionname'] = SomeObject() #: #: The key must match the name of the extension module. For example in #: case of a "Flask-Foo" extension in `flask_foo`, the key would be #: ``'foo'``. #: #: .. versionadded:: 0.7 - self.extensions: dict = {} + self.extensions = {} #: The :class:`~werkzeug.routing.Map` for this instance. You can use #: this to change the routing converters after the class was created @@ -509,25 +598,19 @@ class Flask(Scaffold): assert ( bool(static_host) == host_matching ), "Invalid static_host/host_matching combination" - # Use a weakref to avoid creating a reference cycle between the app - # and the view function (see #3761). - self_ref = weakref.ref(self) self.add_url_rule( - f"{self.static_url_path}/", + self.static_url_path + "/", endpoint="static", host=static_host, - view_func=lambda **kw: self_ref().send_static_file(**kw), # type: ignore # noqa: B950 + view_func=self.send_static_file, ) # Set the name of the Click group in case someone wants to add # the app's commands to another CLI tool. self.cli.name = self.name - def _is_setup_finished(self) -> bool: - return self.debug and self._got_first_request - @locked_cached_property - def name(self) -> str: # type: ignore + def name(self): """The name of the application. This is usually the import name with the difference that it's guessed from the run file if the import name is main. This name is used as a display name when @@ -544,7 +627,7 @@ class Flask(Scaffold): return self.import_name @property - def propagate_exceptions(self) -> bool: + def propagate_exceptions(self): """Returns the value of the ``PROPAGATE_EXCEPTIONS`` configuration value in case it's set, otherwise a sensible default is returned. @@ -556,7 +639,7 @@ class Flask(Scaffold): return self.testing or self.debug @property - def preserve_context_on_exception(self) -> bool: + def preserve_context_on_exception(self): """Returns the value of the ``PRESERVE_CONTEXT_ON_EXCEPTION`` configuration value in case it's set, otherwise a sensible default is returned. @@ -569,7 +652,7 @@ class Flask(Scaffold): return self.debug @locked_cached_property - def logger(self) -> logging.Logger: + def logger(self): """A standard Python :class:`~logging.Logger` for the app, with the same name as :attr:`name`. @@ -596,7 +679,7 @@ class Flask(Scaffold): return create_logger(self) @locked_cached_property - def jinja_env(self) -> Environment: + def jinja_env(self): """The Jinja environment used to load templates. The environment is created the first time this property is @@ -606,7 +689,7 @@ class Flask(Scaffold): return self.create_jinja_environment() @property - def got_first_request(self) -> bool: + def got_first_request(self): """This attribute is set to ``True`` if the application started handling the first request. @@ -614,7 +697,7 @@ class Flask(Scaffold): """ return self._got_first_request - def make_config(self, instance_relative: bool = False) -> Config: + def make_config(self, instance_relative=False): """Used to create the config attribute by the Flask constructor. The `instance_relative` parameter is passed in from the constructor of Flask (there named `instance_relative_config`) and indicates if @@ -631,7 +714,7 @@ class Flask(Scaffold): defaults["DEBUG"] = get_debug_flag() return self.config_class(root_path, defaults) - def auto_find_instance_path(self) -> str: + def auto_find_instance_path(self): """Tries to locate the instance path if it was not provided to the constructor of the application class. It will basically calculate the path to a folder named ``instance`` next to your main file or @@ -642,9 +725,9 @@ class Flask(Scaffold): prefix, package_path = find_package(self.import_name) if prefix is None: return os.path.join(package_path, "instance") - return os.path.join(prefix, "var", f"{self.name}-instance") + return os.path.join(prefix, "var", self.name + "-instance") - def open_instance_resource(self, resource: str, mode: str = "rb") -> t.IO[t.AnyStr]: + def open_instance_resource(self, resource, mode="rb"): """Opens a resource from the application's instance folder (:attr:`instance_path`). Otherwise works like :meth:`open_resource`. Instance resources can also be opened for @@ -657,7 +740,7 @@ class Flask(Scaffold): return open(os.path.join(self.instance_path, resource), mode) @property - def templates_auto_reload(self) -> bool: + def templates_auto_reload(self): """Reload templates when they are changed. Used by :meth:`create_jinja_environment`. @@ -672,10 +755,10 @@ class Flask(Scaffold): return rv if rv is not None else self.debug @templates_auto_reload.setter - def templates_auto_reload(self, value: bool) -> None: + def templates_auto_reload(self, value): self.config["TEMPLATES_AUTO_RELOAD"] = value - def create_jinja_environment(self) -> Environment: + def create_jinja_environment(self): """Create the Jinja environment based on :attr:`jinja_options` and the various Jinja-related methods of the app. Changing :attr:`jinja_options` after this will have no effect. Also adds @@ -707,10 +790,10 @@ class Flask(Scaffold): session=session, g=g, ) - rv.policies["json.dumps_function"] = json.dumps + rv.filters["tojson"] = json.tojson_filter return rv - def create_global_jinja_loader(self) -> DispatchingJinjaLoader: + def create_global_jinja_loader(self): """Creates the loader for the Jinja2 environment. Can be used to override just the loader and keeping the rest unchanged. It's discouraged to override this function. Instead one should override @@ -723,7 +806,7 @@ class Flask(Scaffold): """ return DispatchingJinjaLoader(self) - def select_jinja_autoescape(self, filename: str) -> bool: + def select_jinja_autoescape(self, filename): """Returns ``True`` if autoescaping should be active for the given template name. If no template name is given, returns `True`. @@ -733,7 +816,7 @@ class Flask(Scaffold): return True return filename.endswith((".html", ".htm", ".xml", ".xhtml")) - def update_template_context(self, context: dict) -> None: + def update_template_context(self, context): """Update the template context with some commonly used variables. This injects request, session, config and g into the template context as well as everything template context processors want @@ -744,14 +827,12 @@ class Flask(Scaffold): :param context: the context as a dictionary that is updated in place to add extra variables. """ - funcs: t.Iterable[ - TemplateContextProcessorCallable - ] = self.template_context_processors[None] + funcs = self.template_context_processors[None] reqctx = _request_ctx_stack.top if reqctx is not None: - for bp in request.blueprints: - if bp in self.template_context_processors: - funcs = chain(funcs, self.template_context_processors[bp]) + bp = reqctx.request.blueprint + if bp is not None and bp in self.template_context_processors: + funcs = chain(funcs, self.template_context_processors[bp]) orig_ctx = context.copy() for func in funcs: context.update(func()) @@ -760,7 +841,7 @@ class Flask(Scaffold): # existing views. context.update(orig_ctx) - def make_shell_context(self) -> dict: + def make_shell_context(self): """Returns the shell context for an interactive shell for this application. This runs all the registered shell context processors. @@ -784,7 +865,7 @@ class Flask(Scaffold): env = ConfigAttribute("ENV") @property - def debug(self) -> bool: + def debug(self): """Whether debug mode is enabled. When using ``flask run`` to start the development server, an interactive debugger will be shown for unhandled exceptions, and the server will be reloaded when code @@ -801,23 +882,16 @@ class Flask(Scaffold): return self.config["DEBUG"] @debug.setter - def debug(self, value: bool) -> None: + def debug(self, value): self.config["DEBUG"] = value self.jinja_env.auto_reload = self.templates_auto_reload - def run( - self, - host: t.Optional[str] = None, - port: t.Optional[int] = None, - debug: t.Optional[bool] = None, - load_dotenv: bool = True, - **options: t.Any, - ) -> None: + def run(self, host=None, port=None, debug=None, load_dotenv=True, **options): """Runs the application on a local development server. Do not use ``run()`` in a production setting. It is not intended to meet security and performance requirements for a production server. - Instead, see :doc:`/deploying/index` for WSGI server recommendations. + Instead, see :ref:`deployment` for WSGI server recommendations. If the :attr:`debug` flag is set the server will automatically reload for code changes and show a debugger in case an exception happened. @@ -892,24 +966,17 @@ class Flask(Scaffold): if debug is not None: self.debug = bool(debug) + _host = "127.0.0.1" + _port = 5000 server_name = self.config.get("SERVER_NAME") - sn_host = sn_port = None + sn_host, sn_port = None, None if server_name: sn_host, _, sn_port = server_name.partition(":") - if not host: - if sn_host: - host = sn_host - else: - host = "127.0.0.1" - - if port or port == 0: - port = int(port) - elif sn_port: - port = int(sn_port) - else: - port = 5000 + host = host or sn_host or _host + # pick the first value that's not None (0 is allowed) + port = int(next((p for p in (port, sn_port) if p is not None), _port)) options.setdefault("use_reloader", self.debug) options.setdefault("use_debugger", self.debug) @@ -920,16 +987,16 @@ class Flask(Scaffold): from werkzeug.serving import run_simple try: - run_simple(t.cast(str, host), port, self, **options) + run_simple(host, port, self, **options) finally: # reset the first request information if the development server # reset normally. This makes it possible to restart the server # without reloader and that stuff from an interactive shell. self._got_first_request = False - def test_client(self, use_cookies: bool = True, **kwargs: t.Any) -> "FlaskClient": + def test_client(self, use_cookies=True, **kwargs): """Creates a test client for this application. For information - about unit testing head over to :doc:`/testing`. + about unit testing head over to :ref:`testing`. Note that if you are testing for assertions or exceptions in your application code, you must set ``app.testing = True`` in order for the @@ -980,12 +1047,10 @@ class Flask(Scaffold): """ cls = self.test_client_class if cls is None: - from .testing import FlaskClient as cls # type: ignore - return cls( # type: ignore - self, self.response_class, use_cookies=use_cookies, **kwargs - ) + from .testing import FlaskClient as cls + return cls(self, self.response_class, use_cookies=use_cookies, **kwargs) - def test_cli_runner(self, **kwargs: t.Any) -> "FlaskCliRunner": + def test_cli_runner(self, **kwargs): """Create a CLI runner for testing CLI commands. See :ref:`testing-cli`. @@ -998,12 +1063,76 @@ class Flask(Scaffold): cls = self.test_cli_runner_class if cls is None: - from .testing import FlaskCliRunner as cls # type: ignore + from .testing import FlaskCliRunner as cls - return cls(self, **kwargs) # type: ignore + return cls(self, **kwargs) + + def open_session(self, request): + """Creates or opens a new session. Default implementation stores all + session data in a signed cookie. This requires that the + :attr:`secret_key` is set. Instead of overriding this method + we recommend replacing the :class:`session_interface`. + + .. deprecated: 1.0 + Will be removed in 2.0. Use + ``session_interface.open_session`` instead. + + :param request: an instance of :attr:`request_class`. + """ + + warnings.warn( + DeprecationWarning( + '"open_session" is deprecated and will be removed in' + ' 2.0. Use "session_interface.open_session" instead.' + ) + ) + return self.session_interface.open_session(self, request) + + def save_session(self, session, response): + """Saves the session if it needs updates. For the default + implementation, check :meth:`open_session`. Instead of overriding this + method we recommend replacing the :class:`session_interface`. + + .. deprecated: 1.0 + Will be removed in 2.0. Use + ``session_interface.save_session`` instead. + + :param session: the session to be saved (a + :class:`~werkzeug.contrib.securecookie.SecureCookie` + object) + :param response: an instance of :attr:`response_class` + """ + + warnings.warn( + DeprecationWarning( + '"save_session" is deprecated and will be removed in' + ' 2.0. Use "session_interface.save_session" instead.' + ) + ) + return self.session_interface.save_session(self, session, response) + + def make_null_session(self): + """Creates a new instance of a missing session. Instead of overriding + this method we recommend replacing the :class:`session_interface`. + + .. deprecated: 1.0 + Will be removed in 2.0. Use + ``session_interface.make_null_session`` instead. + + .. versionadded:: 0.7 + """ + + warnings.warn( + DeprecationWarning( + '"make_null_session" is deprecated and will be removed' + ' in 2.0. Use "session_interface.make_null_session"' + " instead." + ) + ) + return self.session_interface.make_null_session(self) @setupmethod - def register_blueprint(self, blueprint: "Blueprint", **options: t.Any) -> None: + def register_blueprint(self, blueprint, **options): """Register a :class:`~flask.Blueprint` on the application. Keyword arguments passed to this method will override the defaults set on the blueprint. @@ -1020,34 +1149,94 @@ class Flask(Scaffold): :class:`~flask.blueprints.BlueprintSetupState`. They can be accessed in :meth:`~flask.Blueprint.record` callbacks. - .. versionchanged:: 2.0.1 - The ``name`` option can be used to change the (pre-dotted) - name the blueprint is registered with. This allows the same - blueprint to be registered multiple times with unique names - for ``url_for``. - .. versionadded:: 0.7 """ - blueprint.register(self, options) + first_registration = False - def iter_blueprints(self) -> t.ValuesView["Blueprint"]: + if blueprint.name in self.blueprints: + assert self.blueprints[blueprint.name] is blueprint, ( + "A name collision occurred between blueprints %r and %r. Both" + ' share the same name "%s". Blueprints that are created on the' + " fly need unique names." + % (blueprint, self.blueprints[blueprint.name], blueprint.name) + ) + else: + self.blueprints[blueprint.name] = blueprint + self._blueprint_order.append(blueprint) + first_registration = True + + blueprint.register(self, options, first_registration) + + def iter_blueprints(self): """Iterates over all blueprints by the order they were registered. .. versionadded:: 0.11 """ - return self.blueprints.values() + return iter(self._blueprint_order) @setupmethod def add_url_rule( self, - rule: str, - endpoint: t.Optional[str] = None, - view_func: t.Optional[t.Callable] = None, - provide_automatic_options: t.Optional[bool] = None, - **options: t.Any, - ) -> None: + rule, + endpoint=None, + view_func=None, + provide_automatic_options=None, + **options + ): + """Connects a URL rule. Works exactly like the :meth:`route` + decorator. If a view_func is provided it will be registered with the + endpoint. + + Basically this example:: + + @app.route('/') + def index(): + pass + + Is equivalent to the following:: + + def index(): + pass + app.add_url_rule('/', 'index', index) + + If the view_func is not provided you will need to connect the endpoint + to a view function like so:: + + app.view_functions['index'] = index + + Internally :meth:`route` invokes :meth:`add_url_rule` so if you want + to customize the behavior via subclassing you only need to change + this method. + + For more information refer to :ref:`url-route-registrations`. + + .. versionchanged:: 0.2 + `view_func` parameter added. + + .. versionchanged:: 0.6 + ``OPTIONS`` is added automatically as method. + + :param rule: the URL rule as string + :param endpoint: the endpoint for the registered URL rule. Flask + itself assumes the name of the view function as + endpoint + :param view_func: the function to call when serving a request to the + provided endpoint + :param provide_automatic_options: controls whether the ``OPTIONS`` + method should be added automatically. This can also be controlled + by setting the ``view_func.provide_automatic_options = False`` + before adding the rule. + :param options: the options to be forwarded to the underlying + :class:`~werkzeug.routing.Rule` object. A change + to Werkzeug is handling of method options. methods + is a list of methods this rule should be limited + to (``GET``, ``POST`` etc.). By default a rule + just listens for ``GET`` (and implicitly ``HEAD``). + Starting with Flask 0.6, ``OPTIONS`` is implicitly + added and handled by the standard request handling. + """ if endpoint is None: - endpoint = _endpoint_from_view_func(view_func) # type: ignore + endpoint = _endpoint_from_view_func(view_func) options["endpoint"] = endpoint methods = options.pop("methods", None) @@ -1056,12 +1245,12 @@ class Flask(Scaffold): # a tuple of only ``GET`` as default. if methods is None: methods = getattr(view_func, "methods", None) or ("GET",) - if isinstance(methods, str): + if isinstance(methods, string_types): raise TypeError( - "Allowed methods must be a list of strings, for" - ' example: @app.route(..., methods=["POST"])' + "Allowed methods have to be iterables of strings, " + 'for example: @app.route(..., methods=["POST"])' ) - methods = {item.upper() for item in methods} + methods = set(item.upper() for item in methods) # Methods that should always be added required_methods = set(getattr(view_func, "required_methods", ())) @@ -1084,22 +1273,163 @@ class Flask(Scaffold): methods |= required_methods rule = self.url_rule_class(rule, methods=methods, **options) - rule.provide_automatic_options = provide_automatic_options # type: ignore + rule.provide_automatic_options = provide_automatic_options self.url_map.add(rule) if view_func is not None: old_func = self.view_functions.get(endpoint) if old_func is not None and old_func != view_func: raise AssertionError( - "View function mapping is overwriting an existing" - f" endpoint function: {endpoint}" + "View function mapping is overwriting an " + "existing endpoint function: %s" % endpoint ) self.view_functions[endpoint] = view_func + def route(self, rule, **options): + """A decorator that is used to register a view function for a + given URL rule. This does the same thing as :meth:`add_url_rule` + but is intended for decorator usage:: + + @app.route('/') + def index(): + return 'Hello World' + + For more information refer to :ref:`url-route-registrations`. + + :param rule: the URL rule as string + :param endpoint: the endpoint for the registered URL rule. Flask + itself assumes the name of the view function as + endpoint + :param options: the options to be forwarded to the underlying + :class:`~werkzeug.routing.Rule` object. A change + to Werkzeug is handling of method options. methods + is a list of methods this rule should be limited + to (``GET``, ``POST`` etc.). By default a rule + just listens for ``GET`` (and implicitly ``HEAD``). + Starting with Flask 0.6, ``OPTIONS`` is implicitly + added and handled by the standard request handling. + """ + + def decorator(f): + endpoint = options.pop("endpoint", None) + self.add_url_rule(rule, endpoint, f, **options) + return f + + return decorator + @setupmethod - def template_filter( - self, name: t.Optional[str] = None - ) -> t.Callable[[TemplateFilterCallable], TemplateFilterCallable]: + def endpoint(self, endpoint): + """A decorator to register a function as an endpoint. + Example:: + + @app.endpoint('example.endpoint') + def example(): + return "example" + + :param endpoint: the name of the endpoint + """ + + def decorator(f): + self.view_functions[endpoint] = f + return f + + return decorator + + @staticmethod + def _get_exc_class_and_code(exc_class_or_code): + """Get the exception class being handled. For HTTP status codes + or ``HTTPException`` subclasses, return both the exception and + status code. + + :param exc_class_or_code: Any exception class, or an HTTP status + code as an integer. + """ + if isinstance(exc_class_or_code, integer_types): + exc_class = default_exceptions[exc_class_or_code] + else: + exc_class = exc_class_or_code + + assert issubclass(exc_class, Exception) + + if issubclass(exc_class, HTTPException): + return exc_class, exc_class.code + else: + return exc_class, None + + @setupmethod + def errorhandler(self, code_or_exception): + """Register a function to handle errors by code or exception class. + + A decorator that is used to register a function given an + error code. Example:: + + @app.errorhandler(404) + def page_not_found(error): + return 'This page does not exist', 404 + + You can also register handlers for arbitrary exceptions:: + + @app.errorhandler(DatabaseError) + def special_exception_handler(error): + return 'Database connection failed', 500 + + .. versionadded:: 0.7 + Use :meth:`register_error_handler` instead of modifying + :attr:`error_handler_spec` directly, for application wide error + handlers. + + .. versionadded:: 0.7 + One can now additionally also register custom exception types + that do not necessarily have to be a subclass of the + :class:`~werkzeug.exceptions.HTTPException` class. + + :param code_or_exception: the code as integer for the handler, or + an arbitrary exception + """ + + def decorator(f): + self._register_error_handler(None, code_or_exception, f) + return f + + return decorator + + @setupmethod + def register_error_handler(self, code_or_exception, f): + """Alternative error attach function to the :meth:`errorhandler` + decorator that is more straightforward to use for non decorator + usage. + + .. versionadded:: 0.7 + """ + self._register_error_handler(None, code_or_exception, f) + + @setupmethod + def _register_error_handler(self, key, code_or_exception, f): + """ + :type key: None|str + :type code_or_exception: int|T<=Exception + :type f: callable + """ + if isinstance(code_or_exception, HTTPException): # old broken behavior + raise ValueError( + "Tried to register a handler for an exception instance {0!r}." + " Handlers can only be registered for exception classes or" + " HTTP error codes.".format(code_or_exception) + ) + + try: + exc_class, code = self._get_exc_class_and_code(code_or_exception) + except KeyError: + raise KeyError( + "'{0}' is not a recognized HTTP error code. Use a subclass of" + " HTTPException with that code instead.".format(code_or_exception) + ) + + handlers = self.error_handler_spec.setdefault(key, {}).setdefault(code, {}) + handlers[exc_class] = f + + @setupmethod + def template_filter(self, name=None): """A decorator that is used to register custom template filter. You can specify a name for the filter, otherwise the function name will be used. Example:: @@ -1112,16 +1442,14 @@ class Flask(Scaffold): function name will be used. """ - def decorator(f: TemplateFilterCallable) -> TemplateFilterCallable: + def decorator(f): self.add_template_filter(f, name=name) return f return decorator @setupmethod - def add_template_filter( - self, f: TemplateFilterCallable, name: t.Optional[str] = None - ) -> None: + def add_template_filter(self, f, name=None): """Register a custom template filter. Works exactly like the :meth:`template_filter` decorator. @@ -1131,9 +1459,7 @@ class Flask(Scaffold): self.jinja_env.filters[name or f.__name__] = f @setupmethod - def template_test( - self, name: t.Optional[str] = None - ) -> t.Callable[[TemplateTestCallable], TemplateTestCallable]: + def template_test(self, name=None): """A decorator that is used to register custom template test. You can specify a name for the test, otherwise the function name will be used. Example:: @@ -1153,16 +1479,14 @@ class Flask(Scaffold): function name will be used. """ - def decorator(f: TemplateTestCallable) -> TemplateTestCallable: + def decorator(f): self.add_template_test(f, name=name) return f return decorator @setupmethod - def add_template_test( - self, f: TemplateTestCallable, name: t.Optional[str] = None - ) -> None: + def add_template_test(self, f, name=None): """Register a custom template test. Works exactly like the :meth:`template_test` decorator. @@ -1174,9 +1498,7 @@ class Flask(Scaffold): self.jinja_env.tests[name or f.__name__] = f @setupmethod - def template_global( - self, name: t.Optional[str] = None - ) -> t.Callable[[TemplateGlobalCallable], TemplateGlobalCallable]: + def template_global(self, name=None): """A decorator that is used to register a custom template global function. You can specify a name for the global function, otherwise the function name will be used. Example:: @@ -1191,16 +1513,14 @@ class Flask(Scaffold): function name will be used. """ - def decorator(f: TemplateGlobalCallable) -> TemplateGlobalCallable: + def decorator(f): self.add_template_global(f, name=name) return f return decorator @setupmethod - def add_template_global( - self, f: TemplateGlobalCallable, name: t.Optional[str] = None - ) -> None: + def add_template_global(self, f, name=None): """Register a custom template global function. Works exactly like the :meth:`template_global` decorator. @@ -1212,9 +1532,21 @@ class Flask(Scaffold): self.jinja_env.globals[name or f.__name__] = f @setupmethod - def before_first_request( - self, f: BeforeFirstRequestCallable - ) -> BeforeFirstRequestCallable: + def before_request(self, f): + """Registers a function to run before each request. + + For example, this can be used to open a database connection, or to load + the logged in user from the session. + + The function will be called without any arguments. If it returns a + non-None value, the value is handled as if it was the return value from + the view, and further request handling is stopped. + """ + self.before_request_funcs.setdefault(None, []).append(f) + return f + + @setupmethod + def before_first_request(self, f): """Registers a function to be run before the first request to this instance of the application. @@ -1227,7 +1559,60 @@ class Flask(Scaffold): return f @setupmethod - def teardown_appcontext(self, f: TeardownCallable) -> TeardownCallable: + def after_request(self, f): + """Register a function to be run after each request. + + Your function must take one parameter, an instance of + :attr:`response_class` and return a new response object or the + same (see :meth:`process_response`). + + As of Flask 0.7 this function might not be executed at the end of the + request in case an unhandled exception occurred. + """ + self.after_request_funcs.setdefault(None, []).append(f) + return f + + @setupmethod + def teardown_request(self, f): + """Register a function to be run at the end of each request, + regardless of whether there was an exception or not. These functions + are executed when the request context is popped, even if not an + actual request was performed. + + Example:: + + ctx = app.test_request_context() + ctx.push() + ... + ctx.pop() + + When ``ctx.pop()`` is executed in the above example, the teardown + functions are called just before the request context moves from the + stack of active contexts. This becomes relevant if you are using + such constructs in tests. + + Generally teardown functions must take every necessary step to avoid + that they will fail. If they do execute code that might fail they + will have to surround the execution of these code by try/except + statements and log occurring errors. + + When a teardown function was called because of an exception it will + be passed an error object. + + The return values of teardown functions are ignored. + + .. admonition:: Debug Note + + In debug mode Flask will not tear down a request on an exception + immediately. Instead it will keep it alive so that the interactive + debugger can still access it. This behavior can be controlled + by the ``PRESERVE_CONTEXT_ON_EXCEPTION`` configuration variable. + """ + self.teardown_request_funcs.setdefault(None, []).append(f) + return f + + @setupmethod + def teardown_appcontext(self, f): """Registers a function to be called when the application context ends. These functions are typically also called when the request context is popped. @@ -1260,7 +1645,13 @@ class Flask(Scaffold): return f @setupmethod - def shell_context_processor(self, f: t.Callable) -> t.Callable: + def context_processor(self, f): + """Registers a template context processor function.""" + self.template_context_processors[None].append(f) + return f + + @setupmethod + def shell_context_processor(self, f): """Registers a shell context processor function. .. versionadded:: 0.11 @@ -1268,7 +1659,33 @@ class Flask(Scaffold): self.shell_context_processors.append(f) return f - def _find_error_handler(self, e: Exception) -> t.Optional[ErrorHandlerCallable]: + @setupmethod + def url_value_preprocessor(self, f): + """Register a URL value preprocessor function for all view + functions in the application. These functions will be called before the + :meth:`before_request` functions. + + The function can modify the values captured from the matched url before + they are passed to the view. For example, this can be used to pop a + common language code value and place it in ``g`` rather than pass it to + every view. + + The function is passed the endpoint name and values dict. The return + value is ignored. + """ + self.url_value_preprocessors.setdefault(None, []).append(f) + return f + + @setupmethod + def url_defaults(self, f): + """Callback function for URL defaults for all view functions of the + application. It's called with the endpoint and values and should + update the values passed in place. + """ + self.url_default_functions.setdefault(None, []).append(f) + return f + + def _find_error_handler(self, e): """Return a registered error handler for an exception in this order: blueprint handler for a specific code, app handler for a specific code, blueprint handler for an exception class, app handler for an exception @@ -1276,23 +1693,24 @@ class Flask(Scaffold): """ exc_class, code = self._get_exc_class_and_code(type(e)) - for c in [code, None]: - for name in chain(request.blueprints, [None]): - handler_map = self.error_handler_spec[name][c] + for name, c in ( + (request.blueprint, code), + (None, code), + (request.blueprint, None), + (None, None), + ): + handler_map = self.error_handler_spec.setdefault(name, {}).get(c) - if not handler_map: - continue + if not handler_map: + continue - for cls in exc_class.__mro__: - handler = handler_map.get(cls) + for cls in exc_class.__mro__: + handler = handler_map.get(cls) - if handler is not None: - return handler - return None + if handler is not None: + return handler - def handle_http_exception( - self, e: HTTPException - ) -> t.Union[HTTPException, ResponseReturnValue]: + def handle_http_exception(self, e): """Handles an HTTP exception. By default this will invoke the registered error handlers and fall back to returning the exception as response. @@ -1323,9 +1741,9 @@ class Flask(Scaffold): handler = self._find_error_handler(e) if handler is None: return e - return self.ensure_sync(handler)(e) + return handler(e) - def trap_http_exception(self, e: Exception) -> bool: + def trap_http_exception(self, e): """Checks if an HTTP exception should be trapped or not. By default this will return ``False`` for all exceptions except for a bad request key error if ``TRAP_BAD_REQUEST_ERRORS`` is set to ``True``. It @@ -1360,9 +1778,7 @@ class Flask(Scaffold): return False - def handle_user_exception( - self, e: Exception - ) -> t.Union[HTTPException, ResponseReturnValue]: + def handle_user_exception(self, e): """This method is called whenever an exception occurs that should be handled. A special case is :class:`~werkzeug .exceptions.HTTPException` which is forwarded to the @@ -1377,10 +1793,24 @@ class Flask(Scaffold): .. versionadded:: 0.7 """ - if isinstance(e, BadRequestKeyError) and ( - self.debug or self.config["TRAP_BAD_REQUEST_ERRORS"] - ): - e.show_exception = True + exc_type, exc_value, tb = sys.exc_info() + assert exc_value is e + # ensure not to trash sys.exc_info() at that point in case someone + # wants the traceback preserved in handle_http_exception. Of course + # we cannot prevent users from trashing it themselves in a custom + # trap_http_exception method so that's their fault then. + + if isinstance(e, BadRequestKeyError): + if self.debug or self.config["TRAP_BAD_REQUEST_ERRORS"]: + e.show_exception = True + + # Werkzeug < 0.15 doesn't add the KeyError to the 400 + # message, add it in manually. + # TODO: clean up once Werkzeug >= 0.15.5 is required + if e.args[0] not in e.get_description(): + e.description = "KeyError: '{}'".format(*e.args) + elif not hasattr(BadRequestKeyError, "show_exception"): + e.args = () if isinstance(e, HTTPException) and not self.trap_http_exception(e): return self.handle_http_exception(e) @@ -1388,11 +1818,10 @@ class Flask(Scaffold): handler = self._find_error_handler(e) if handler is None: - raise + reraise(exc_type, exc_value, tb) + return handler(e) - return self.ensure_sync(handler)(e) - - def handle_exception(self, e: Exception) -> Response: + def handle_exception(self, e): """Handle an exception that did not have an error handler associated with it, or that was raised from an error handler. This always causes a 500 ``InternalServerError``. @@ -1409,6 +1838,12 @@ class Flask(Scaffold): always receive the ``InternalServerError``. The original unhandled exception is available as ``e.original_exception``. + .. note:: + Prior to Werkzeug 1.0.0, ``InternalServerError`` will not + always have an ``original_exception`` attribute. Use + ``getattr(e, "original_exception", None)`` to simulate the + behavior for compatibility. + .. versionchanged:: 1.1.0 Always passes the ``InternalServerError`` instance to the handler, setting ``original_exception`` to the unhandled @@ -1420,33 +1855,32 @@ class Flask(Scaffold): .. versionadded:: 0.3 """ - exc_info = sys.exc_info() + exc_type, exc_value, tb = sys.exc_info() got_request_exception.send(self, exception=e) if self.propagate_exceptions: - # Re-raise if called with an active exception, otherwise - # raise the passed in exception. - if exc_info[1] is e: - raise + # if we want to repropagate the exception, we can attempt to + # raise it with the whole traceback in case we can do that + # (the function was actually called from the except part) + # otherwise, we just raise the error again + if exc_value is e: + reraise(exc_type, exc_value, tb) + else: + raise e - raise e - - self.log_exception(exc_info) - server_error: t.Union[InternalServerError, ResponseReturnValue] - server_error = InternalServerError(original_exception=e) + self.log_exception((exc_type, exc_value, tb)) + server_error = InternalServerError() + # TODO: pass as param when Werkzeug>=1.0.0 is required + # TODO: also remove note about this from docstring and docs + server_error.original_exception = e handler = self._find_error_handler(server_error) if handler is not None: - server_error = self.ensure_sync(handler)(server_error) + server_error = handler(server_error) return self.finalize_request(server_error, from_error_handler=True) - def log_exception( - self, - exc_info: t.Union[ - t.Tuple[type, BaseException, TracebackType], t.Tuple[None, None, None] - ], - ) -> None: + def log_exception(self, exc_info): """Logs an exception. This is called by :meth:`handle_exception` if debugging is disabled and right before the handler is called. The default implementation logs the exception as error on the @@ -1455,10 +1889,10 @@ class Flask(Scaffold): .. versionadded:: 0.8 """ self.logger.error( - f"Exception on {request.path} [{request.method}]", exc_info=exc_info + "Exception on %s [%s]" % (request.path, request.method), exc_info=exc_info ) - def raise_routing_exception(self, request: Request) -> "te.NoReturn": + def raise_routing_exception(self, request): """Exceptions that are recording during routing are reraised with this method. During debug we are not reraising redirect requests for non ``GET``, ``HEAD``, or ``OPTIONS`` requests and we're raising @@ -1471,13 +1905,13 @@ class Flask(Scaffold): or not isinstance(request.routing_exception, RequestRedirect) or request.method in ("GET", "HEAD", "OPTIONS") ): - raise request.routing_exception # type: ignore + raise request.routing_exception from .debughelpers import FormDataRoutingRedirect raise FormDataRoutingRedirect(request) - def dispatch_request(self) -> ResponseReturnValue: + def dispatch_request(self): """Does the request dispatching. Matches the URL and returns the return value of the view or error handler. This does not have to be a response object. In order to convert the return value to a @@ -1499,9 +1933,9 @@ class Flask(Scaffold): ): return self.make_default_options_response() # otherwise dispatch to the handler for that endpoint - return self.ensure_sync(self.view_functions[rule.endpoint])(**req.view_args) + return self.view_functions[rule.endpoint](**req.view_args) - def full_dispatch_request(self) -> Response: + def full_dispatch_request(self): """Dispatches the request and on top of that performs request pre and postprocessing as well as HTTP exception catching and error handling. @@ -1518,11 +1952,7 @@ class Flask(Scaffold): rv = self.handle_user_exception(e) return self.finalize_request(rv) - def finalize_request( - self, - rv: t.Union[ResponseReturnValue, HTTPException], - from_error_handler: bool = False, - ) -> Response: + def finalize_request(self, rv, from_error_handler=False): """Given the return value from a view function this finalizes the request by converting it into a response and invoking the postprocessing functions. This is invoked for both normal @@ -1547,7 +1977,7 @@ class Flask(Scaffold): ) return response - def try_trigger_before_first_request_functions(self) -> None: + def try_trigger_before_first_request_functions(self): """Called before each request and will ensure that it triggers the :attr:`before_first_request_funcs` and only exactly once per application instance (which means process usually). @@ -1560,10 +1990,10 @@ class Flask(Scaffold): if self._got_first_request: return for func in self.before_first_request_funcs: - self.ensure_sync(func)() + func() self._got_first_request = True - def make_default_options_response(self) -> Response: + def make_default_options_response(self): """This method is called to create the default ``OPTIONS`` response. This can be changed through subclassing to change the default behavior of ``OPTIONS`` responses. @@ -1571,12 +2001,22 @@ class Flask(Scaffold): .. versionadded:: 0.7 """ adapter = _request_ctx_stack.top.url_adapter - methods = adapter.allowed_methods() + if hasattr(adapter, "allowed_methods"): + methods = adapter.allowed_methods() + else: + # fallback for Werkzeug < 0.7 + methods = [] + try: + adapter.match(method="--") + except MethodNotAllowed as e: + methods = e.valid_methods + except HTTPException: + pass rv = self.response_class() rv.allow.update(methods) return rv - def should_ignore_error(self, error: t.Optional[BaseException]) -> bool: + def should_ignore_error(self, error): """This is called to figure out if an error should be ignored or not as far as the teardown system is concerned. If this function returns ``True`` then the teardown handlers will not be @@ -1586,51 +2026,7 @@ class Flask(Scaffold): """ return False - def ensure_sync(self, func: t.Callable) -> t.Callable: - """Ensure that the function is synchronous for WSGI workers. - Plain ``def`` functions are returned as-is. ``async def`` - functions are wrapped to run and wait for the response. - - Override this method to change how the app runs async views. - - .. versionadded:: 2.0 - """ - if iscoroutinefunction(func): - return self.async_to_sync(func) - - return func - - def async_to_sync( - self, func: t.Callable[..., t.Coroutine] - ) -> t.Callable[..., t.Any]: - """Return a sync function that will run the coroutine function. - - .. code-block:: python - - result = app.async_to_sync(func)(*args, **kwargs) - - Override this method to change how the app converts async code - to be synchronously callable. - - .. versionadded:: 2.0 - """ - try: - from asgiref.sync import async_to_sync as asgiref_async_to_sync - except ImportError: - raise RuntimeError( - "Install Flask with the 'async' extra in order to use async views." - ) - - # Check that Werkzeug isn't using its fallback ContextVar class. - if ContextVar.__module__ == "werkzeug.local": - raise RuntimeError( - "Async cannot be used with this combination of Python " - "and Greenlet versions." - ) - - return asgiref_async_to_sync(func) - - def make_response(self, rv: ResponseReturnValue) -> Response: + def make_response(self, rv): """Convert the return value from a view function to an instance of :attr:`response_class`. @@ -1639,11 +2035,11 @@ class Flask(Scaffold): without returning, is not allowed. The following types are allowed for ``view_rv``: - ``str`` + ``str`` (``unicode`` in Python 2) A response object is created with the string encoded to UTF-8 as the body. - ``bytes`` + ``bytes`` (``str`` in Python 2) A response object is created with the bytes as the body. ``dict`` @@ -1699,14 +2095,14 @@ class Flask(Scaffold): # the body must not be None if rv is None: raise TypeError( - f"The view function for {request.endpoint!r} did not" - " return a valid response. The function either returned" - " None or ended without a return statement." + "The view function did not return a valid response. The" + " function either returned None or ended without a return" + " statement." ) # make sure the body is an instance of the response class if not isinstance(rv, self.response_class): - if isinstance(rv, (str, bytes, bytearray)): + if isinstance(rv, (text_type, bytes, bytearray)): # let the response class set the status and headers instead of # waiting to do it manually, so that the class can handle any # special logic @@ -1718,39 +2114,37 @@ class Flask(Scaffold): # evaluate a WSGI callable, or coerce a different response # class to the correct type try: - rv = self.response_class.force_type(rv, request.environ) # type: ignore # noqa: B950 + rv = self.response_class.force_type(rv, request.environ) except TypeError as e: - raise TypeError( - f"{e}\nThe view function did not return a valid" - " response. The return type must be a string," - " dict, tuple, Response instance, or WSGI" - f" callable, but it was a {type(rv).__name__}." - ).with_traceback(sys.exc_info()[2]) + new_error = TypeError( + "{e}\nThe view function did not return a valid" + " response. The return type must be a string, dict, tuple," + " Response instance, or WSGI callable, but it was a" + " {rv.__class__.__name__}.".format(e=e, rv=rv) + ) + reraise(TypeError, new_error, sys.exc_info()[2]) else: raise TypeError( "The view function did not return a valid" - " response. The return type must be a string," - " dict, tuple, Response instance, or WSGI" - f" callable, but it was a {type(rv).__name__}." + " response. The return type must be a string, dict, tuple," + " Response instance, or WSGI callable, but it was a" + " {rv.__class__.__name__}.".format(rv=rv) ) - rv = t.cast(Response, rv) # prefer the status if it was provided if status is not None: - if isinstance(status, (str, bytes, bytearray)): - rv.status = status # type: ignore + if isinstance(status, (text_type, bytes, bytearray)): + rv.status = status else: rv.status_code = status # extend existing headers with provided headers if headers: - rv.headers.update(headers) + rv.headers.extend(headers) return rv - def create_url_adapter( - self, request: t.Optional[Request] - ) -> t.Optional[MapAdapter]: + def create_url_adapter(self, request): """Creates a URL adapter for the given request. The URL adapter is created at a point where the request context is not yet set up so the request is passed explicitly. @@ -1769,11 +2163,11 @@ class Flask(Scaffold): # If subdomain matching is disabled (the default), use the # default subdomain in all cases. This should be the default # in Werkzeug but it currently does not have that feature. - if not self.subdomain_matching: - subdomain = self.url_map.default_subdomain or None - else: - subdomain = None - + subdomain = ( + (self.url_map.default_subdomain or None) + if not self.subdomain_matching + else None + ) return self.url_map.bind_to_environ( request.environ, server_name=self.config["SERVER_NAME"], @@ -1788,51 +2182,41 @@ class Flask(Scaffold): url_scheme=self.config["PREFERRED_URL_SCHEME"], ) - return None - - def inject_url_defaults(self, endpoint: str, values: dict) -> None: + def inject_url_defaults(self, endpoint, values): """Injects the URL defaults for the given endpoint directly into the values dictionary passed. This is used internally and automatically called on URL building. .. versionadded:: 0.7 """ - funcs: t.Iterable[URLDefaultCallable] = self.url_default_functions[None] - + funcs = self.url_default_functions.get(None, ()) if "." in endpoint: - # This is called by url_for, which can be called outside a - # request, can't use request.blueprints. - bps = _split_blueprint_path(endpoint.rpartition(".")[0]) - bp_funcs = chain.from_iterable(self.url_default_functions[bp] for bp in bps) - funcs = chain(funcs, bp_funcs) - + bp = endpoint.rsplit(".", 1)[0] + funcs = chain(funcs, self.url_default_functions.get(bp, ())) for func in funcs: func(endpoint, values) - def handle_url_build_error( - self, error: Exception, endpoint: str, values: dict - ) -> str: - """Handle :class:`~werkzeug.routing.BuildError` on - :meth:`url_for`. + def handle_url_build_error(self, error, endpoint, values): + """Handle :class:`~werkzeug.routing.BuildError` on :meth:`url_for`. """ + exc_type, exc_value, tb = sys.exc_info() for handler in self.url_build_error_handlers: try: rv = handler(error, endpoint, values) - except BuildError as e: - # make error available outside except block - error = e - else: if rv is not None: return rv + except BuildError as e: + # make error available outside except block (py3) + error = e - # Re-raise if called with an active exception, otherwise raise - # the passed in exception. - if error is sys.exc_info()[1]: - raise - + # At this point we want to reraise the exception. If the error is + # still the same one we can reraise it with the original traceback, + # otherwise we raise it from here. + if error is exc_value: + reraise(exc_type, exc_value, tb) raise error - def preprocess_request(self) -> t.Optional[ResponseReturnValue]: + def preprocess_request(self): """Called before the request is dispatched. Calls :attr:`url_value_preprocessors` registered with the app and the current blueprint (if any). Then calls :attr:`before_request_funcs` @@ -1843,27 +2227,23 @@ class Flask(Scaffold): further request handling is stopped. """ - funcs: t.Iterable[URLValuePreprocessorCallable] = self.url_value_preprocessors[ - None - ] - for bp in request.blueprints: - if bp in self.url_value_preprocessors: - funcs = chain(funcs, self.url_value_preprocessors[bp]) + bp = _request_ctx_stack.top.request.blueprint + + funcs = self.url_value_preprocessors.get(None, ()) + if bp is not None and bp in self.url_value_preprocessors: + funcs = chain(funcs, self.url_value_preprocessors[bp]) for func in funcs: func(request.endpoint, request.view_args) - funcs: t.Iterable[BeforeRequestCallable] = self.before_request_funcs[None] - for bp in request.blueprints: - if bp in self.before_request_funcs: - funcs = chain(funcs, self.before_request_funcs[bp]) + funcs = self.before_request_funcs.get(None, ()) + if bp is not None and bp in self.before_request_funcs: + funcs = chain(funcs, self.before_request_funcs[bp]) for func in funcs: - rv = self.ensure_sync(func)() + rv = func() if rv is not None: return rv - return None - - def process_response(self, response: Response) -> Response: + def process_response(self, response): """Can be overridden in order to modify the response object before it's sent to the WSGI server. By default this will call all the :meth:`after_request` decorated functions. @@ -1877,21 +2257,19 @@ class Flask(Scaffold): instance of :attr:`response_class`. """ ctx = _request_ctx_stack.top - funcs: t.Iterable[AfterRequestCallable] = ctx._after_request_functions - for bp in request.blueprints: - if bp in self.after_request_funcs: - funcs = chain(funcs, reversed(self.after_request_funcs[bp])) + bp = ctx.request.blueprint + funcs = ctx._after_request_functions + if bp is not None and bp in self.after_request_funcs: + funcs = chain(funcs, reversed(self.after_request_funcs[bp])) if None in self.after_request_funcs: funcs = chain(funcs, reversed(self.after_request_funcs[None])) for handler in funcs: - response = self.ensure_sync(handler)(response) + response = handler(response) if not self.session_interface.is_null_session(ctx.session): self.session_interface.save_session(self, ctx.session, response) return response - def do_teardown_request( - self, exc: t.Optional[BaseException] = _sentinel # type: ignore - ) -> None: + def do_teardown_request(self, exc=_sentinel): """Called after the request is dispatched and the response is returned, right before the request context is popped. @@ -1914,19 +2292,15 @@ class Flask(Scaffold): """ if exc is _sentinel: exc = sys.exc_info()[1] - funcs: t.Iterable[TeardownCallable] = reversed( - self.teardown_request_funcs[None] - ) - for bp in request.blueprints: - if bp in self.teardown_request_funcs: - funcs = chain(funcs, reversed(self.teardown_request_funcs[bp])) + funcs = reversed(self.teardown_request_funcs.get(None, ())) + bp = _request_ctx_stack.top.request.blueprint + if bp is not None and bp in self.teardown_request_funcs: + funcs = chain(funcs, reversed(self.teardown_request_funcs[bp])) for func in funcs: - self.ensure_sync(func)(exc) + func(exc) request_tearing_down.send(self, exc=exc) - def do_teardown_appcontext( - self, exc: t.Optional[BaseException] = _sentinel # type: ignore - ) -> None: + def do_teardown_appcontext(self, exc=_sentinel): """Called right before the application context is popped. When handling a request, the application context is popped @@ -1944,10 +2318,10 @@ class Flask(Scaffold): if exc is _sentinel: exc = sys.exc_info()[1] for func in reversed(self.teardown_appcontext_funcs): - self.ensure_sync(func)(exc) + func(exc) appcontext_tearing_down.send(self, exc=exc) - def app_context(self) -> AppContext: + def app_context(self): """Create an :class:`~flask.ctx.AppContext`. Use as a ``with`` block to push the context, which will make :data:`current_app` point at this application. @@ -1968,7 +2342,7 @@ class Flask(Scaffold): """ return AppContext(self) - def request_context(self, environ: dict) -> RequestContext: + def request_context(self, environ): """Create a :class:`~flask.ctx.RequestContext` representing a WSGI environment. Use a ``with`` block to push the context, which will make :data:`request` point at this request. @@ -1984,7 +2358,7 @@ class Flask(Scaffold): """ return RequestContext(self, environ) - def test_request_context(self, *args: t.Any, **kwargs: t.Any) -> RequestContext: + def test_request_context(self, *args, **kwargs): """Create a :class:`~flask.ctx.RequestContext` for a WSGI environment created from the given values. This is mostly useful during testing, where you may want to run a function that uses @@ -2040,7 +2414,7 @@ class Flask(Scaffold): finally: builder.close() - def wsgi_app(self, environ: dict, start_response: t.Callable) -> t.Any: + def wsgi_app(self, environ, start_response): """The actual WSGI application. This is not implemented in :meth:`__call__` so that middlewares can be applied without losing a reference to the app object. Instead of doing this:: @@ -2066,7 +2440,7 @@ class Flask(Scaffold): start the response. """ ctx = self.request_context(environ) - error: t.Optional[BaseException] = None + error = None try: try: ctx.push() @@ -2083,9 +2457,11 @@ class Flask(Scaffold): error = None ctx.auto_pop(error) - def __call__(self, environ: dict, start_response: t.Callable) -> t.Any: + def __call__(self, environ, start_response): """The WSGI server calls the Flask application object as the - WSGI application. This calls :meth:`wsgi_app`, which can be - wrapped to apply middleware. - """ + WSGI application. This calls :meth:`wsgi_app` which can be + wrapped to applying middleware.""" return self.wsgi_app(environ, start_response) + + def __repr__(self): + return "<%s %r>" % (self.__class__.__name__, self.name) diff --git a/src/flask/blueprints.py b/src/flask/blueprints.py index 883fc2ff..8978104d 100644 --- a/src/flask/blueprints.py +++ b/src/flask/blueprints.py @@ -1,42 +1,31 @@ -import typing as t -from collections import defaultdict +# -*- coding: utf-8 -*- +""" + flask.blueprints + ~~~~~~~~~~~~~~~~ + + Blueprints are the recommended way to implement larger or more + pluggable applications in Flask 0.7 and later. + + :copyright: 2010 Pallets + :license: BSD-3-Clause +""" from functools import update_wrapper -from .scaffold import _endpoint_from_view_func -from .scaffold import _sentinel -from .scaffold import Scaffold -from .typing import AfterRequestCallable -from .typing import BeforeFirstRequestCallable -from .typing import BeforeRequestCallable -from .typing import ErrorHandlerCallable -from .typing import TeardownCallable -from .typing import TemplateContextProcessorCallable -from .typing import TemplateFilterCallable -from .typing import TemplateGlobalCallable -from .typing import TemplateTestCallable -from .typing import URLDefaultCallable -from .typing import URLValuePreprocessorCallable +from .helpers import _endpoint_from_view_func +from .helpers import _PackageBoundObject -if t.TYPE_CHECKING: - from .app import Flask - -DeferredSetupFunction = t.Callable[["BlueprintSetupState"], t.Callable] +# a singleton sentinel value for parameter defaults +_sentinel = object() -class BlueprintSetupState: +class BlueprintSetupState(object): """Temporary holder object for registering a blueprint with the application. An instance of this class is created by the :meth:`~flask.Blueprint.make_setup_state` method and later passed to all register callback functions. """ - def __init__( - self, - blueprint: "Blueprint", - app: "Flask", - options: t.Any, - first_registration: bool, - ) -> None: + def __init__(self, blueprint, app, options, first_registration): #: a reference to the current application self.app = app @@ -68,21 +57,12 @@ class BlueprintSetupState: #: blueprint. self.url_prefix = url_prefix - self.name = self.options.get("name", blueprint.name) - self.name_prefix = self.options.get("name_prefix", "") - #: A dictionary with URL defaults that is added to each and every #: URL that was defined with the blueprint. self.url_defaults = dict(self.blueprint.url_values_defaults) self.url_defaults.update(self.options.get("url_defaults", ())) - def add_url_rule( - self, - rule: str, - endpoint: t.Optional[str] = None, - view_func: t.Optional[t.Callable] = None, - **options: t.Any, - ) -> None: + def add_url_rule(self, rule, endpoint=None, view_func=None, **options): """A helper method to register a rule (and optionally a view function) to the application. The endpoint is automatically prefixed with the blueprint's name. @@ -94,21 +74,20 @@ class BlueprintSetupState: rule = self.url_prefix options.setdefault("subdomain", self.subdomain) if endpoint is None: - endpoint = _endpoint_from_view_func(view_func) # type: ignore + endpoint = _endpoint_from_view_func(view_func) defaults = self.url_defaults if "defaults" in options: defaults = dict(defaults, **options.pop("defaults")) - self.app.add_url_rule( rule, - f"{self.name_prefix}.{self.name}.{endpoint}".lstrip("."), + "%s.%s" % (self.blueprint.name, endpoint), view_func, defaults=defaults, - **options, + **options ) -class Blueprint(Scaffold): +class Blueprint(_PackageBoundObject): """Represents a blueprint, a collection of routes and other app-related functions that can be registered on a real application later. @@ -122,7 +101,14 @@ class Blueprint(Scaffold): that is called with :class:`~flask.blueprints.BlueprintSetupState` when the blueprint is registered on an application. - See :doc:`/blueprints` for more information. + See :ref:`blueprints` for more information. + + .. versionchanged:: 1.1.0 + Blueprints have a ``cli`` group to register nested CLI commands. + The ``cli_group`` parameter controls the name of the group under + the ``flask`` command. + + .. versionadded:: 0.7 :param name: The name of the blueprint. Will be prepended to each endpoint name. @@ -148,69 +134,65 @@ class Blueprint(Scaffold): default. :param url_defaults: A dict of default values that blueprint routes will receive by default. - :param root_path: By default, the blueprint will automatically set - this based on ``import_name``. In certain situations this - automatic detection can fail, so the path can be specified - manually instead. - - .. versionchanged:: 1.1.0 - Blueprints have a ``cli`` group to register nested CLI commands. - The ``cli_group`` parameter controls the name of the group under - the ``flask`` command. - - .. versionadded:: 0.7 + :param root_path: By default, the blueprint will automatically this + based on ``import_name``. In certain situations this automatic + detection can fail, so the path can be specified manually + instead. """ warn_on_modifications = False _got_registered_once = False - #: Blueprint local JSON encoder class to use. Set to ``None`` to use - #: the app's :class:`~flask.Flask.json_encoder`. + #: Blueprint local JSON decoder class to use. + #: Set to ``None`` to use the app's :class:`~flask.app.Flask.json_encoder`. json_encoder = None - #: Blueprint local JSON decoder class to use. Set to ``None`` to use - #: the app's :class:`~flask.Flask.json_decoder`. + #: Blueprint local JSON decoder class to use. + #: Set to ``None`` to use the app's :class:`~flask.app.Flask.json_decoder`. json_decoder = None + # TODO remove the next three attrs when Sphinx :inherited-members: works + # https://github.com/sphinx-doc/sphinx/issues/741 + + #: The name of the package or module that this app belongs to. Do not + #: change this once it is set by the constructor. + import_name = None + + #: Location of the template files to be added to the template lookup. + #: ``None`` if templates should not be added. + template_folder = None + + #: Absolute path to the package on the filesystem. Used to look up + #: resources contained in the package. + root_path = None + def __init__( self, - name: str, - import_name: str, - static_folder: t.Optional[str] = None, - static_url_path: t.Optional[str] = None, - template_folder: t.Optional[str] = None, - url_prefix: t.Optional[str] = None, - subdomain: t.Optional[str] = None, - url_defaults: t.Optional[dict] = None, - root_path: t.Optional[str] = None, - cli_group: t.Optional[str] = _sentinel, # type: ignore + name, + import_name, + static_folder=None, + static_url_path=None, + template_folder=None, + url_prefix=None, + subdomain=None, + url_defaults=None, + root_path=None, + cli_group=_sentinel, ): - super().__init__( - import_name=import_name, - static_folder=static_folder, - static_url_path=static_url_path, - template_folder=template_folder, - root_path=root_path, + _PackageBoundObject.__init__( + self, import_name, template_folder, root_path=root_path ) - - if "." in name: - raise ValueError("'name' may not contain a dot '.' character.") - self.name = name self.url_prefix = url_prefix self.subdomain = subdomain - self.deferred_functions: t.List[DeferredSetupFunction] = [] - + self.static_folder = static_folder + self.static_url_path = static_url_path + self.deferred_functions = [] if url_defaults is None: url_defaults = {} - self.url_values_defaults = url_defaults self.cli_group = cli_group - self._blueprints: t.List[t.Tuple["Blueprint", dict]] = [] - def _is_setup_finished(self) -> bool: - return self.warn_on_modifications and self._got_registered_once - - def record(self, func: t.Callable) -> None: + def record(self, func): """Registers a function that is called when the blueprint is registered on the application. This function is called with the state as argument as returned by the :meth:`make_setup_state` @@ -221,212 +203,114 @@ class Blueprint(Scaffold): warn( Warning( - "The blueprint was already registered once but is" - " getting modified now. These changes will not show" - " up." + "The blueprint was already registered once " + "but is getting modified now. These changes " + "will not show up." ) ) self.deferred_functions.append(func) - def record_once(self, func: t.Callable) -> None: + def record_once(self, func): """Works like :meth:`record` but wraps the function in another function that will ensure the function is only called once. If the blueprint is registered a second time on the application, the function passed is not called. """ - def wrapper(state: BlueprintSetupState) -> None: + def wrapper(state): if state.first_registration: func(state) return self.record(update_wrapper(wrapper, func)) - def make_setup_state( - self, app: "Flask", options: dict, first_registration: bool = False - ) -> BlueprintSetupState: + def make_setup_state(self, app, options, first_registration=False): """Creates an instance of :meth:`~flask.blueprints.BlueprintSetupState` object that is later passed to the register callback functions. Subclasses can override this to return a subclass of the setup state. """ return BlueprintSetupState(self, app, options, first_registration) - def register_blueprint(self, blueprint: "Blueprint", **options: t.Any) -> None: - """Register a :class:`~flask.Blueprint` on this blueprint. Keyword - arguments passed to this method will override the defaults set - on the blueprint. + def register(self, app, options, first_registration=False): + """Called by :meth:`Flask.register_blueprint` to register all views + and callbacks registered on the blueprint with the application. Creates + a :class:`.BlueprintSetupState` and calls each :meth:`record` callback + with it. - .. versionchanged:: 2.0.1 - The ``name`` option can be used to change the (pre-dotted) - name the blueprint is registered with. This allows the same - blueprint to be registered multiple times with unique names - for ``url_for``. - - .. versionadded:: 2.0 - """ - if blueprint is self: - raise ValueError("Cannot register a blueprint on itself") - self._blueprints.append((blueprint, options)) - - def register(self, app: "Flask", options: dict) -> None: - """Called by :meth:`Flask.register_blueprint` to register all - views and callbacks registered on the blueprint with the - application. Creates a :class:`.BlueprintSetupState` and calls - each :meth:`record` callback with it. - - :param app: The application this blueprint is being registered - with. + :param app: The application this blueprint is being registered with. :param options: Keyword arguments forwarded from :meth:`~Flask.register_blueprint`. - - .. versionchanged:: 2.0.1 - Nested blueprints are registered with their dotted name. - This allows different blueprints with the same name to be - nested at different locations. - - .. versionchanged:: 2.0.1 - The ``name`` option can be used to change the (pre-dotted) - name the blueprint is registered with. This allows the same - blueprint to be registered multiple times with unique names - for ``url_for``. - - .. versionchanged:: 2.0.1 - Registering the same blueprint with the same name multiple - times is deprecated and will become an error in Flask 2.1. + :param first_registration: Whether this is the first time this + blueprint has been registered on the application. """ - first_registration = not any(bp is self for bp in app.blueprints.values()) - name_prefix = options.get("name_prefix", "") - self_name = options.get("name", self.name) - name = f"{name_prefix}.{self_name}".lstrip(".") - - if name in app.blueprints: - existing_at = f" '{name}'" if self_name != name else "" - - if app.blueprints[name] is not self: - raise ValueError( - f"The name '{self_name}' is already registered for" - f" a different blueprint{existing_at}. Use 'name='" - " to provide a unique name." - ) - else: - import warnings - - warnings.warn( - f"The name '{self_name}' is already registered for" - f" this blueprint{existing_at}. Use 'name=' to" - " provide a unique name. This will become an error" - " in Flask 2.1.", - stacklevel=4, - ) - - app.blueprints[name] = self self._got_registered_once = True state = self.make_setup_state(app, options, first_registration) if self.has_static_folder: state.add_url_rule( - f"{self.static_url_path}/", + self.static_url_path + "/", view_func=self.send_static_file, endpoint="static", ) - # Merge blueprint data into parent. - if first_registration: - - def extend(bp_dict, parent_dict): - for key, values in bp_dict.items(): - key = name if key is None else f"{name}.{key}" - parent_dict[key].extend(values) - - for key, value in self.error_handler_spec.items(): - key = name if key is None else f"{name}.{key}" - value = defaultdict( - dict, - { - code: { - exc_class: func for exc_class, func in code_values.items() - } - for code, code_values in value.items() - }, - ) - app.error_handler_spec[key] = value - - for endpoint, func in self.view_functions.items(): - app.view_functions[endpoint] = func - - extend(self.before_request_funcs, app.before_request_funcs) - extend(self.after_request_funcs, app.after_request_funcs) - extend( - self.teardown_request_funcs, - app.teardown_request_funcs, - ) - extend(self.url_default_functions, app.url_default_functions) - extend(self.url_value_preprocessors, app.url_value_preprocessors) - extend(self.template_context_processors, app.template_context_processors) - for deferred in self.deferred_functions: deferred(state) cli_resolved_group = options.get("cli_group", self.cli_group) - if self.cli.commands: - if cli_resolved_group is None: - app.cli.commands.update(self.cli.commands) - elif cli_resolved_group is _sentinel: - self.cli.name = name - app.cli.add_command(self.cli) - else: - self.cli.name = cli_resolved_group - app.cli.add_command(self.cli) + if not self.cli.commands: + return - for blueprint, bp_options in self._blueprints: - bp_options = bp_options.copy() - bp_url_prefix = bp_options.get("url_prefix") + if cli_resolved_group is None: + app.cli.commands.update(self.cli.commands) + elif cli_resolved_group is _sentinel: + self.cli.name = self.name + app.cli.add_command(self.cli) + else: + self.cli.name = cli_resolved_group + app.cli.add_command(self.cli) - if bp_url_prefix is None: - bp_url_prefix = blueprint.url_prefix + def route(self, rule, **options): + """Like :meth:`Flask.route` but for a blueprint. The endpoint for the + :func:`url_for` function is prefixed with the name of the blueprint. + """ - if state.url_prefix is not None and bp_url_prefix is not None: - bp_options["url_prefix"] = ( - state.url_prefix.rstrip("/") + "/" + bp_url_prefix.lstrip("/") - ) - elif bp_url_prefix is not None: - bp_options["url_prefix"] = bp_url_prefix - elif state.url_prefix is not None: - bp_options["url_prefix"] = state.url_prefix + def decorator(f): + endpoint = options.pop("endpoint", f.__name__) + self.add_url_rule(rule, endpoint, f, **options) + return f - bp_options["name_prefix"] = name - blueprint.register(app, bp_options) + return decorator - def add_url_rule( - self, - rule: str, - endpoint: t.Optional[str] = None, - view_func: t.Optional[t.Callable] = None, - provide_automatic_options: t.Optional[bool] = None, - **options: t.Any, - ) -> None: + def add_url_rule(self, rule, endpoint=None, view_func=None, **options): """Like :meth:`Flask.add_url_rule` but for a blueprint. The endpoint for the :func:`url_for` function is prefixed with the name of the blueprint. """ - if endpoint and "." in endpoint: - raise ValueError("'endpoint' may not contain a dot '.' character.") + if endpoint: + assert "." not in endpoint, "Blueprint endpoints should not contain dots" + if view_func and hasattr(view_func, "__name__"): + assert ( + "." not in view_func.__name__ + ), "Blueprint view function name should not contain dots" + self.record(lambda s: s.add_url_rule(rule, endpoint, view_func, **options)) - if view_func and hasattr(view_func, "__name__") and "." in view_func.__name__: - raise ValueError("'view_func' name may not contain a dot '.' character.") + def endpoint(self, endpoint): + """Like :meth:`Flask.endpoint` but for a blueprint. This does not + prefix the endpoint with the blueprint name, this has to be done + explicitly by the user of this method. If the endpoint is prefixed + with a `.` it will be registered to the current blueprint, otherwise + it's an application independent endpoint. + """ - self.record( - lambda s: s.add_url_rule( - rule, - endpoint, - view_func, - provide_automatic_options=provide_automatic_options, - **options, - ) - ) + def decorator(f): + def register_endpoint(state): + state.app.view_functions[endpoint] = f - def app_template_filter( - self, name: t.Optional[str] = None - ) -> t.Callable[[TemplateFilterCallable], TemplateFilterCallable]: + self.record_once(register_endpoint) + return f + + return decorator + + def app_template_filter(self, name=None): """Register a custom template filter, available application wide. Like :meth:`Flask.template_filter` but for a blueprint. @@ -434,15 +318,13 @@ class Blueprint(Scaffold): function name will be used. """ - def decorator(f: TemplateFilterCallable) -> TemplateFilterCallable: + def decorator(f): self.add_app_template_filter(f, name=name) return f return decorator - def add_app_template_filter( - self, f: TemplateFilterCallable, name: t.Optional[str] = None - ) -> None: + def add_app_template_filter(self, f, name=None): """Register a custom template filter, available application wide. Like :meth:`Flask.add_template_filter` but for a blueprint. Works exactly like the :meth:`app_template_filter` decorator. @@ -451,14 +333,12 @@ class Blueprint(Scaffold): function name will be used. """ - def register_template(state: BlueprintSetupState) -> None: + def register_template(state): state.app.jinja_env.filters[name or f.__name__] = f self.record_once(register_template) - def app_template_test( - self, name: t.Optional[str] = None - ) -> t.Callable[[TemplateTestCallable], TemplateTestCallable]: + def app_template_test(self, name=None): """Register a custom template test, available application wide. Like :meth:`Flask.template_test` but for a blueprint. @@ -468,15 +348,13 @@ class Blueprint(Scaffold): function name will be used. """ - def decorator(f: TemplateTestCallable) -> TemplateTestCallable: + def decorator(f): self.add_app_template_test(f, name=name) return f return decorator - def add_app_template_test( - self, f: TemplateTestCallable, name: t.Optional[str] = None - ) -> None: + def add_app_template_test(self, f, name=None): """Register a custom template test, available application wide. Like :meth:`Flask.add_template_test` but for a blueprint. Works exactly like the :meth:`app_template_test` decorator. @@ -487,14 +365,12 @@ class Blueprint(Scaffold): function name will be used. """ - def register_template(state: BlueprintSetupState) -> None: + def register_template(state): state.app.jinja_env.tests[name or f.__name__] = f self.record_once(register_template) - def app_template_global( - self, name: t.Optional[str] = None - ) -> t.Callable[[TemplateGlobalCallable], TemplateGlobalCallable]: + def app_template_global(self, name=None): """Register a custom template global, available application wide. Like :meth:`Flask.template_global` but for a blueprint. @@ -504,15 +380,13 @@ class Blueprint(Scaffold): function name will be used. """ - def decorator(f: TemplateGlobalCallable) -> TemplateGlobalCallable: + def decorator(f): self.add_app_template_global(f, name=name) return f return decorator - def add_app_template_global( - self, f: TemplateGlobalCallable, name: t.Optional[str] = None - ) -> None: + def add_app_template_global(self, f, name=None): """Register a custom template global, available application wide. Like :meth:`Flask.add_template_global` but for a blueprint. Works exactly like the :meth:`app_template_global` decorator. @@ -523,12 +397,22 @@ class Blueprint(Scaffold): function name will be used. """ - def register_template(state: BlueprintSetupState) -> None: + def register_template(state): state.app.jinja_env.globals[name or f.__name__] = f self.record_once(register_template) - def before_app_request(self, f: BeforeRequestCallable) -> BeforeRequestCallable: + def before_request(self, f): + """Like :meth:`Flask.before_request` but for a blueprint. This function + is only executed before each request that is handled by a function of + that blueprint. + """ + self.record_once( + lambda s: s.app.before_request_funcs.setdefault(self.name, []).append(f) + ) + return f + + def before_app_request(self, f): """Like :meth:`Flask.before_request`. Such a function is executed before each request, even if outside of a blueprint. """ @@ -537,16 +421,24 @@ class Blueprint(Scaffold): ) return f - def before_app_first_request( - self, f: BeforeFirstRequestCallable - ) -> BeforeFirstRequestCallable: + def before_app_first_request(self, f): """Like :meth:`Flask.before_first_request`. Such a function is executed before the first request to the application. """ self.record_once(lambda s: s.app.before_first_request_funcs.append(f)) return f - def after_app_request(self, f: AfterRequestCallable) -> AfterRequestCallable: + def after_request(self, f): + """Like :meth:`Flask.after_request` but for a blueprint. This function + is only executed after each request that is handled by a function of + that blueprint. + """ + self.record_once( + lambda s: s.app.after_request_funcs.setdefault(self.name, []).append(f) + ) + return f + + def after_app_request(self, f): """Like :meth:`Flask.after_request` but for a blueprint. Such a function is executed after each request, even if outside of the blueprint. """ @@ -555,7 +447,19 @@ class Blueprint(Scaffold): ) return f - def teardown_app_request(self, f: TeardownCallable) -> TeardownCallable: + def teardown_request(self, f): + """Like :meth:`Flask.teardown_request` but for a blueprint. This + function is only executed when tearing down requests handled by a + function of that blueprint. Teardown request functions are executed + when the request context is popped, even when no actual request was + performed. + """ + self.record_once( + lambda s: s.app.teardown_request_funcs.setdefault(self.name, []).append(f) + ) + return f + + def teardown_app_request(self, f): """Like :meth:`Flask.teardown_request` but for a blueprint. Such a function is executed when tearing down each request, even if outside of the blueprint. @@ -565,9 +469,18 @@ class Blueprint(Scaffold): ) return f - def app_context_processor( - self, f: TemplateContextProcessorCallable - ) -> TemplateContextProcessorCallable: + def context_processor(self, f): + """Like :meth:`Flask.context_processor` but for a blueprint. This + function is only executed for requests handled by a blueprint. + """ + self.record_once( + lambda s: s.app.template_context_processors.setdefault( + self.name, [] + ).append(f) + ) + return f + + def app_context_processor(self, f): """Like :meth:`Flask.context_processor` but for a blueprint. Such a function is executed each request, even if outside of the blueprint. """ @@ -576,29 +489,81 @@ class Blueprint(Scaffold): ) return f - def app_errorhandler(self, code: t.Union[t.Type[Exception], int]) -> t.Callable: + def app_errorhandler(self, code): """Like :meth:`Flask.errorhandler` but for a blueprint. This handler is used for all requests, even if outside of the blueprint. """ - def decorator(f: ErrorHandlerCallable) -> ErrorHandlerCallable: + def decorator(f): self.record_once(lambda s: s.app.errorhandler(code)(f)) return f return decorator - def app_url_value_preprocessor( - self, f: URLValuePreprocessorCallable - ) -> URLValuePreprocessorCallable: - """Same as :meth:`url_value_preprocessor` but application wide.""" + def url_value_preprocessor(self, f): + """Registers a function as URL value preprocessor for this + blueprint. It's called before the view functions are called and + can modify the url values provided. + """ + self.record_once( + lambda s: s.app.url_value_preprocessors.setdefault(self.name, []).append(f) + ) + return f + + def url_defaults(self, f): + """Callback function for URL defaults for this blueprint. It's called + with the endpoint and values and should update the values passed + in place. + """ + self.record_once( + lambda s: s.app.url_default_functions.setdefault(self.name, []).append(f) + ) + return f + + def app_url_value_preprocessor(self, f): + """Same as :meth:`url_value_preprocessor` but application wide. + """ self.record_once( lambda s: s.app.url_value_preprocessors.setdefault(None, []).append(f) ) return f - def app_url_defaults(self, f: URLDefaultCallable) -> URLDefaultCallable: - """Same as :meth:`url_defaults` but application wide.""" + def app_url_defaults(self, f): + """Same as :meth:`url_defaults` but application wide. + """ self.record_once( lambda s: s.app.url_default_functions.setdefault(None, []).append(f) ) return f + + def errorhandler(self, code_or_exception): + """Registers an error handler that becomes active for this blueprint + only. Please be aware that routing does not happen local to a + blueprint so an error handler for 404 usually is not handled by + a blueprint unless it is caused inside a view function. Another + special case is the 500 internal server error which is always looked + up from the application. + + Otherwise works as the :meth:`~flask.Flask.errorhandler` decorator + of the :class:`~flask.Flask` object. + """ + + def decorator(f): + self.record_once( + lambda s: s.app._register_error_handler(self.name, code_or_exception, f) + ) + return f + + return decorator + + def register_error_handler(self, code_or_exception, f): + """Non-decorator version of the :meth:`errorhandler` error attach + function, akin to the :meth:`~flask.Flask.register_error_handler` + application-wide function of the :class:`~flask.Flask` object but + for error handlers limited to this blueprint. + + .. versionadded:: 0.11 + """ + self.record_once( + lambda s: s.app._register_error_handler(self.name, code_or_exception, f) + ) diff --git a/src/flask/cli.py b/src/flask/cli.py index d9e810da..c09b2cd0 100644 --- a/src/flask/cli.py +++ b/src/flask/cli.py @@ -1,3 +1,15 @@ +# -*- coding: utf-8 -*- +""" + flask.cli + ~~~~~~~~~ + + A simple command line application to run flask apps. + + :copyright: 2010 Pallets + :license: BSD-3-Clause +""" +from __future__ import print_function + import ast import inspect import os @@ -5,7 +17,6 @@ import platform import re import sys import traceback -import warnings from functools import update_wrapper from operator import attrgetter from threading import Lock @@ -14,6 +25,10 @@ from threading import Thread import click from werkzeug.utils import import_string +from ._compat import getargspec +from ._compat import itervalues +from ._compat import reraise +from ._compat import text_type from .globals import current_app from .helpers import get_debug_flag from .helpers import get_env @@ -27,7 +42,7 @@ except ImportError: try: import ssl except ImportError: - ssl = None # type: ignore + ssl = None class NoAppException(click.UsageError): @@ -48,15 +63,15 @@ def find_best_app(script_info, module): return app # Otherwise find the only object that is a Flask instance. - matches = [v for v in module.__dict__.values() if isinstance(v, Flask)] + matches = [v for v in itervalues(module.__dict__) if isinstance(v, Flask)] if len(matches) == 1: return matches[0] elif len(matches) > 1: raise NoAppException( - "Detected multiple Flask applications in module" - f" {module.__name__!r}. Use 'FLASK_APP={module.__name__}:name'" - f" to specify the correct one." + 'Detected multiple Flask applications in module "{module}". Use ' + '"FLASK_APP={module}:name" to specify the correct ' + "one.".format(module=module.__name__) ) # Search for app factory functions. @@ -73,140 +88,109 @@ def find_best_app(script_info, module): if not _called_with_wrong_args(app_factory): raise raise NoAppException( - f"Detected factory {attr_name!r} in module {module.__name__!r}," - " but could not call it without arguments. Use" - f" \"FLASK_APP='{module.__name__}:{attr_name}(args)'\"" - " to specify arguments." + 'Detected factory "{factory}" in module "{module}", but ' + "could not call it without arguments. Use " + "\"FLASK_APP='{module}:{factory}(args)'\" to specify " + "arguments.".format(factory=attr_name, module=module.__name__) ) raise NoAppException( - "Failed to find Flask application or factory in module" - f" {module.__name__!r}. Use 'FLASK_APP={module.__name__}:name'" - " to specify one." + 'Failed to find Flask application or factory in module "{module}". ' + 'Use "FLASK_APP={module}:name to specify one.'.format(module=module.__name__) ) -def call_factory(script_info, app_factory, args=None, kwargs=None): +def call_factory(script_info, app_factory, arguments=()): """Takes an app factory, a ``script_info` object and optionally a tuple of arguments. Checks for the existence of a script_info argument and calls the app_factory depending on that and the arguments provided. """ - sig = inspect.signature(app_factory) - args = [] if args is None else args - kwargs = {} if kwargs is None else kwargs + args_spec = getargspec(app_factory) + arg_names = args_spec.args + arg_defaults = args_spec.defaults - if "script_info" in sig.parameters: - warnings.warn( - "The 'script_info' argument is deprecated and will not be" - " passed to the app factory function in Flask 2.1.", - DeprecationWarning, - ) - kwargs["script_info"] = script_info + if "script_info" in arg_names: + return app_factory(*arguments, script_info=script_info) + elif arguments: + return app_factory(*arguments) + elif not arguments and len(arg_names) == 1 and arg_defaults is None: + return app_factory(script_info) - if ( - not args - and len(sig.parameters) == 1 - and next(iter(sig.parameters.values())).default is inspect.Parameter.empty - ): - warnings.warn( - "Script info is deprecated and will not be passed as the" - " single argument to the app factory function in Flask" - " 2.1.", - DeprecationWarning, - ) - args.append(script_info) - - return app_factory(*args, **kwargs) + return app_factory() -def _called_with_wrong_args(f): +def _called_with_wrong_args(factory): """Check whether calling a function raised a ``TypeError`` because the call failed or because something in the factory raised the error. - :param f: The function that was called. - :return: ``True`` if the call failed. + :param factory: the factory function that was called + :return: true if the call failed """ tb = sys.exc_info()[2] try: while tb is not None: - if tb.tb_frame.f_code is f.__code__: - # In the function, it was called successfully. + if tb.tb_frame.f_code is factory.__code__: + # in the factory, it was called successfully return False tb = tb.tb_next - # Didn't reach the function. + # didn't reach the factory return True finally: - # Delete tb to break a circular reference. + # explicitly delete tb as it is circular referenced # https://docs.python.org/2/library/sys.html#sys.exc_info del tb def find_app_by_string(script_info, module, app_name): - """Check if the given string is a variable name or a function. Call - a function to get the app instance, or return the variable directly. + """Checks if the given string is a variable name or a function. If it is a + function, it checks for specified arguments and whether it takes a + ``script_info`` argument and calls the function with the appropriate + arguments. """ from . import Flask - # Parse app_name as a single expression to determine if it's a valid - # attribute name or function call. - try: - expr = ast.parse(app_name.strip(), mode="eval").body - except SyntaxError: + match = re.match(r"^ *([^ ()]+) *(?:\((.*?) *,? *\))? *$", app_name) + + if not match: raise NoAppException( - f"Failed to parse {app_name!r} as an attribute name or function call." + '"{name}" is not a valid variable name or function ' + "expression.".format(name=app_name) ) - if isinstance(expr, ast.Name): - name = expr.id - args = kwargs = None - elif isinstance(expr, ast.Call): - # Ensure the function name is an attribute name only. - if not isinstance(expr.func, ast.Name): - raise NoAppException( - f"Function reference must be a simple name: {app_name!r}." - ) - - name = expr.func.id - - # Parse the positional and keyword arguments as literals. - try: - args = [ast.literal_eval(arg) for arg in expr.args] - kwargs = {kw.arg: ast.literal_eval(kw.value) for kw in expr.keywords} - except ValueError: - # literal_eval gives cryptic error messages, show a generic - # message with the full expression instead. - raise NoAppException( - f"Failed to parse arguments as literal values: {app_name!r}." - ) - else: - raise NoAppException( - f"Failed to parse {app_name!r} as an attribute name or function call." - ) + name, args = match.groups() try: attr = getattr(module, name) - except AttributeError: - raise NoAppException( - f"Failed to find attribute {name!r} in {module.__name__!r}." - ) + except AttributeError as e: + raise NoAppException(e.args[0]) - # If the attribute is a function, call it with any args and kwargs - # to get the real application. if inspect.isfunction(attr): + if args: + try: + args = ast.literal_eval("({args},)".format(args=args)) + except (ValueError, SyntaxError) as e: + raise NoAppException( + "Could not parse the arguments in " + '"{app_name}".'.format(e=e, app_name=app_name) + ) + else: + args = () + try: - app = call_factory(script_info, attr, args, kwargs) - except TypeError: + app = call_factory(script_info, attr, args) + except TypeError as e: if not _called_with_wrong_args(attr): raise raise NoAppException( - f"The factory {app_name!r} in module" - f" {module.__name__!r} could not be called with the" - " specified arguments." + '{e}\nThe factory "{app_name}" in module "{module}" could not ' + "be called with the specified arguments.".format( + e=e, app_name=app_name, module=module.__name__ + ) ) else: app = attr @@ -215,8 +199,8 @@ def find_app_by_string(script_info, module, app_name): return app raise NoAppException( - "A valid Flask application was not obtained from" - f" '{module.__name__}:{app_name}'." + "A valid Flask application was not obtained from " + '"{module}:{app_name}".'.format(module=module.__name__, app_name=app_name) ) @@ -257,13 +241,13 @@ def locate_app(script_info, module_name, app_name, raise_if_not_found=True): except ImportError: # Reraise the ImportError if it occurred within the imported module. # Determine this by checking whether the trace has a depth > 1. - if sys.exc_info()[2].tb_next: + if sys.exc_info()[-1].tb_next: raise NoAppException( - f"While importing {module_name!r}, an ImportError was" - f" raised:\n\n{traceback.format_exc()}" + 'While importing "{name}", an ImportError was raised:' + "\n\n{tb}".format(name=module_name, tb=traceback.format_exc()) ) elif raise_if_not_found: - raise NoAppException(f"Could not import {module_name!r}.") + raise NoAppException('Could not import "{name}".'.format(name=module_name)) else: return @@ -282,10 +266,14 @@ def get_version(ctx, param, value): import werkzeug from . import __version__ + message = "Python %(python)s\nFlask %(flask)s\nWerkzeug %(werkzeug)s" click.echo( - f"Python {platform.python_version()}\n" - f"Flask {__version__}\n" - f"Werkzeug {werkzeug.__version__}", + message + % { + "python": platform.python_version(), + "flask": __version__, + "werkzeug": werkzeug.__version__, + }, color=ctx.color, ) ctx.exit() @@ -301,22 +289,18 @@ version_option = click.Option( ) -class DispatchingApp: +class DispatchingApp(object): """Special application that dispatches to a Flask application which is imported by name in a background thread. If an error happens it is recorded and shown as part of the WSGI handling which in case of the Werkzeug debugger means that it shows up in the browser. """ - def __init__(self, loader, use_eager_loading=None): + def __init__(self, loader, use_eager_loading=False): self.loader = loader self._app = None self._lock = Lock() self._bg_loading_exc_info = None - - if use_eager_loading is None: - use_eager_loading = os.environ.get("WERKZEUG_RUN_MAIN") != "true" - if use_eager_loading: self._load_unlocked() else: @@ -339,7 +323,7 @@ class DispatchingApp: exc_info = self._bg_loading_exc_info if exc_info is not None: self._bg_loading_exc_info = None - raise exc_info + reraise(*exc_info) def _load_unlocked(self): __traceback_hide__ = True # noqa: F841 @@ -360,7 +344,7 @@ class DispatchingApp: return rv(environ, start_response) -class ScriptInfo: +class ScriptInfo(object): """Helper object to deal with Flask applications. This is usually not necessary to interface with as it's used internally in the dispatching to click. In future versions of Flask this object will most likely play @@ -391,6 +375,8 @@ class ScriptInfo: if self._loaded_app is not None: return self._loaded_app + app = None + if self.create_app is not None: app = call_factory(self, self.create_app) else: @@ -478,7 +464,9 @@ class FlaskGroup(AppGroup): loading more commands from the configured Flask app. Normally a developer does not have to interface with this class but there are some very advanced use cases for which it makes sense to create an - instance of this. see :ref:`custom-scripts`. + instance of this. + + For information as of why this is useful see :ref:`custom-scripts`. :param add_default_commands: if this is True then the default run and shell commands will be added. @@ -503,7 +491,7 @@ class FlaskGroup(AppGroup): add_version_option=True, load_dotenv=True, set_debug_flag=True, - **extra, + **extra ): params = list(extra.pop("params", None) or ()) @@ -537,41 +525,43 @@ class FlaskGroup(AppGroup): def get_command(self, ctx, name): self._load_plugin_commands() - # Look up built-in and plugin commands, which should be - # available even if the app fails to load. - rv = super().get_command(ctx, name) + # We load built-in commands first as these should always be the + # same no matter what the app does. If the app does want to + # override this it needs to make a custom instance of this group + # and not attach the default commands. + # + # This also means that the script stays functional in case the + # application completely fails. + rv = AppGroup.get_command(self, ctx, name) if rv is not None: return rv info = ctx.ensure_object(ScriptInfo) - - # Look up commands provided by the app, showing an error and - # continuing if the app couldn't be loaded. try: - return info.load_app().cli.get_command(ctx, name) - except NoAppException as e: - click.secho(f"Error: {e.format_message()}\n", err=True, fg="red") + rv = info.load_app().cli.get_command(ctx, name) + if rv is not None: + return rv + except NoAppException: + pass def list_commands(self, ctx): self._load_plugin_commands() - # Start with the built-in and plugin commands. - rv = set(super().list_commands(ctx)) - info = ctx.ensure_object(ScriptInfo) - # Add commands provided by the app, showing an error and - # continuing if the app couldn't be loaded. + # The commands available is the list of both the application (if + # available) plus the builtin commands. + rv = set(click.Group.list_commands(self, ctx)) + info = ctx.ensure_object(ScriptInfo) try: rv.update(info.load_app().cli.list_commands(ctx)) - except NoAppException as e: - # When an app couldn't be loaded, show the error message - # without the traceback. - click.secho(f"Error: {e.format_message()}\n", err=True, fg="red") except Exception: - # When any other errors occurred during loading, show the - # full traceback. - click.secho(f"{traceback.format_exc()}\n", err=True, fg="red") - + # Here we intentionally swallow all exceptions as we don't + # want the help page to break if the app does not exist. + # If someone attempts to use the command we try to create + # the app again and this will give us the error. + # However, we will not do so silently because that would confuse + # users. + traceback.print_exc() return sorted(rv) def main(self, *args, **kwargs): @@ -593,7 +583,7 @@ class FlaskGroup(AppGroup): kwargs["obj"] = obj kwargs.setdefault("auto_envvar_prefix", "FLASK") - return super().main(*args, **kwargs) + return super(FlaskGroup, self).main(*args, **kwargs) def _path_is_ancestor(path, other): @@ -609,6 +599,10 @@ def load_dotenv(path=None): If an env var is already set it is not overwritten, so earlier files in the list are preferred over later files. + Changes the current working directory to the location of the first file + found, with the assumption that it is in the top level project directory + and will be where the Python path should import local packages from. + This is a no-op if `python-dotenv`_ is not installed. .. _python-dotenv: https://github.com/theskumar/python-dotenv#readme @@ -620,9 +614,6 @@ def load_dotenv(path=None): Returns ``False`` when python-dotenv is not installed, or when the given path isn't a file. - .. versionchanged:: 2.0 - When loading the env files, set the default encoding to UTF-8. - .. versionadded:: 1.0 """ if dotenv is None: @@ -640,7 +631,7 @@ def load_dotenv(path=None): # else False if path is not None: if os.path.isfile(path): - return dotenv.load_dotenv(path, encoding="utf-8") + return dotenv.load_dotenv(path) return False @@ -655,7 +646,10 @@ def load_dotenv(path=None): if new_dir is None: new_dir = os.path.dirname(path) - dotenv.load_dotenv(path, encoding="utf-8") + dotenv.load_dotenv(path) + + if new_dir and os.getcwd() != new_dir: + os.chdir(new_dir) return new_dir is not None # at least one file was located and loaded @@ -668,25 +662,25 @@ def show_server_banner(env, debug, app_import_path, eager_loading): return if app_import_path is not None: - message = f" * Serving Flask app {app_import_path!r}" + message = ' * Serving Flask app "{0}"'.format(app_import_path) if not eager_loading: message += " (lazy loading)" click.echo(message) - click.echo(f" * Environment: {env}") + click.echo(" * Environment: {0}".format(env)) if env == "production": click.secho( - " WARNING: This is a development server. Do not use it in" - " a production deployment.", + " WARNING: This is a development server. " + "Do not use it in a production deployment.", fg="red", ) click.secho(" Use a production WSGI server instead.", dim=True) if debug is not None: - click.echo(f" * Debug mode: {'on' if debug else 'off'}") + click.echo(" * Debug mode: {0}".format("on" if debug else "off")) class CertParamType(click.ParamType): @@ -715,20 +709,22 @@ class CertParamType(click.ParamType): if value == "adhoc": try: - import cryptography # noqa: F401 + import OpenSSL # noqa: F401 except ImportError: raise click.BadParameter( - "Using ad-hoc certificates requires the cryptography library.", - ctx, - param, + "Using ad-hoc certificates requires pyOpenSSL.", ctx, param ) return value obj = import_string(value, silent=True) - if isinstance(obj, ssl.SSLContext): - return obj + if sys.version_info < (2, 7, 9): + if obj: + return obj + else: + if isinstance(obj, ssl.SSLContext): + return obj raise @@ -739,7 +735,11 @@ def _validate_key(ctx, param, value): """ cert = ctx.params.get("cert") is_adhoc = cert == "adhoc" - is_context = ssl and isinstance(cert, ssl.SSLContext) + + if sys.version_info < (2, 7, 9): + is_context = cert and not isinstance(cert, (text_type, bytes)) + else: + is_context = isinstance(cert, ssl.SSLContext) if value is not None: if is_adhoc: @@ -772,7 +772,7 @@ class SeparatedPathType(click.Path): def convert(self, value, param, ctx): items = self.split_envvar_value(value) - super_convert = super().convert + super_convert = super(SeparatedPathType, self).convert return [super_convert(item, param, ctx) for item in items] @@ -802,7 +802,7 @@ class SeparatedPathType(click.Path): "is active if debug is enabled.", ) @click.option( - "--eager-loading/--lazy-loading", + "--eager-loading/--lazy-loader", default=None, help="Enable or disable eager loading. By default eager " "loading is enabled if the reloader is disabled.", @@ -818,7 +818,7 @@ class SeparatedPathType(click.Path): type=SeparatedPathType(), help=( "Extra files that trigger a reload on change. Multiple paths" - f" are separated by {os.path.pathsep!r}." + " are separated by '{}'.".format(os.path.pathsep) ), ) @pass_script_info @@ -841,6 +841,9 @@ def run_command( if debugger is None: debugger = debug + if eager_loading is None: + eager_loading = not reload + show_server_banner(get_env(), debug, info.app_import_path, eager_loading) app = DispatchingApp(info.load_app, use_eager_loading=eager_loading) @@ -860,10 +863,10 @@ def run_command( @click.command("shell", short_help="Run a shell in the app context.") @with_appcontext -def shell_command() -> None: +def shell_command(): """Run an interactive Python shell in the context of a given Flask application. The application will populate the default - namespace of this shell according to its configuration. + namespace of this shell according to it's configuration. This is useful for executing small snippets of management code without having to manually configure the application. @@ -872,40 +875,24 @@ def shell_command() -> None: from .globals import _app_ctx_stack app = _app_ctx_stack.top.app - banner = ( - f"Python {sys.version} on {sys.platform}\n" - f"App: {app.import_name} [{app.env}]\n" - f"Instance: {app.instance_path}" + banner = "Python %s on %s\nApp: %s [%s]\nInstance: %s" % ( + sys.version, + sys.platform, + app.import_name, + app.env, + app.instance_path, ) - ctx: dict = {} + ctx = {} # Support the regular Python interpreter startup script if someone # is using it. startup = os.environ.get("PYTHONSTARTUP") if startup and os.path.isfile(startup): - with open(startup) as f: + with open(startup, "r") as f: eval(compile(f.read(), startup, "exec"), ctx) ctx.update(app.make_shell_context()) - # Site, customize, or startup script can set a hook to call when - # entering interactive mode. The default one sets up readline with - # tab and history completion. - interactive_hook = getattr(sys, "__interactivehook__", None) - - if interactive_hook is not None: - try: - import readline - from rlcompleter import Completer - except ImportError: - pass - else: - # rlcompleter uses __main__.__dict__ by default, which is - # flask.__main__. Use the shell context instead. - readline.set_completer(Completer(ctx).complete) - - interactive_hook() - code.interact(banner=banner, local=ctx) @@ -922,7 +909,7 @@ def shell_command() -> None: ) @click.option("--all-methods", is_flag=True, help="Show HEAD and OPTIONS methods.") @with_appcontext -def routes_command(sort: str, all_methods: bool) -> None: +def routes_command(sort, all_methods): """Show all registered routes with endpoints and methods.""" rules = list(current_app.url_map.iter_rules()) @@ -935,12 +922,9 @@ def routes_command(sort: str, all_methods: bool) -> None: if sort in ("endpoint", "rule"): rules = sorted(rules, key=attrgetter(sort)) elif sort == "methods": - rules = sorted(rules, key=lambda rule: sorted(rule.methods)) # type: ignore + rules = sorted(rules, key=lambda rule: sorted(rule.methods)) - rule_methods = [ - ", ".join(sorted(rule.methods - ignored_methods)) # type: ignore - for rule in rules - ] + rule_methods = [", ".join(sorted(rule.methods - ignored_methods)) for rule in rules] headers = ("Endpoint", "Methods", "Rule") widths = ( @@ -978,17 +962,10 @@ debug mode. ) -def main() -> None: - if int(click.__version__[0]) < 8: - warnings.warn( - "Using the `flask` cli with Click 7 is deprecated and" - " will not be supported starting with Flask 2.1." - " Please upgrade to Click 8 as soon as possible.", - DeprecationWarning, - ) +def main(as_module=False): # TODO omit sys.argv once https://github.com/pallets/click/issues/536 is fixed - cli.main(args=sys.argv[1:]) + cli.main(args=sys.argv[1:], prog_name="python -m flask" if as_module else None) if __name__ == "__main__": - main() + main(as_module=True) diff --git a/src/flask/config.py b/src/flask/config.py index c79a558e..809de336 100644 --- a/src/flask/config.py +++ b/src/flask/config.py @@ -1,19 +1,32 @@ +# -*- coding: utf-8 -*- +""" + flask.config + ~~~~~~~~~~~~ + + Implements the configuration related objects. + + :copyright: 2010 Pallets + :license: BSD-3-Clause +""" import errno import os import types -import typing as t from werkzeug.utils import import_string +from . import json +from ._compat import iteritems +from ._compat import string_types -class ConfigAttribute: + +class ConfigAttribute(object): """Makes an attribute forward to the config""" - def __init__(self, name: str, get_converter: t.Optional[t.Callable] = None) -> None: + def __init__(self, name, get_converter=None): self.__name__ = name self.get_converter = get_converter - def __get__(self, obj: t.Any, owner: t.Any = None) -> t.Any: + def __get__(self, obj, type=None): if obj is None: return self rv = obj.config[self.__name__] @@ -21,7 +34,7 @@ class ConfigAttribute: rv = self.get_converter(rv) return rv - def __set__(self, obj: t.Any, value: t.Any) -> None: + def __set__(self, obj, value): obj.config[self.__name__] = value @@ -69,11 +82,11 @@ class Config(dict): :param defaults: an optional dictionary of default values """ - def __init__(self, root_path: str, defaults: t.Optional[dict] = None) -> None: + def __init__(self, root_path, defaults=None): dict.__init__(self, defaults or {}) self.root_path = root_path - def from_envvar(self, variable_name: str, silent: bool = False) -> bool: + def from_envvar(self, variable_name, silent=False): """Loads a configuration from an environment variable pointing to a configuration file. This is basically just a shortcut with nicer error messages for this line of code:: @@ -90,14 +103,14 @@ class Config(dict): if silent: return False raise RuntimeError( - f"The environment variable {variable_name!r} is not set" - " and as such configuration could not be loaded. Set" - " this variable and make it point to a configuration" - " file" + "The environment variable %r is not set " + "and as such configuration could not be " + "loaded. Set this variable and make it " + "point to a configuration file" % variable_name ) return self.from_pyfile(rv, silent=silent) - def from_pyfile(self, filename: str, silent: bool = False) -> bool: + def from_pyfile(self, filename, silent=False): """Updates the values in the config from a Python file. This function behaves as if the file was imported as module with the :meth:`from_object` function. @@ -117,15 +130,15 @@ class Config(dict): try: with open(filename, mode="rb") as config_file: exec(compile(config_file.read(), filename, "exec"), d.__dict__) - except OSError as e: + except IOError as e: if silent and e.errno in (errno.ENOENT, errno.EISDIR, errno.ENOTDIR): return False - e.strerror = f"Unable to load configuration file ({e.strerror})" + e.strerror = "Unable to load configuration file (%s)" % e.strerror raise self.from_object(d) return True - def from_object(self, obj: t.Union[object, str]) -> None: + def from_object(self, obj): """Updates the values from the given object. An object can be of one of the following two types: @@ -157,96 +170,61 @@ class Config(dict): :param obj: an import name or object """ - if isinstance(obj, str): + if isinstance(obj, string_types): obj = import_string(obj) for key in dir(obj): if key.isupper(): self[key] = getattr(obj, key) - def from_file( - self, - filename: str, - load: t.Callable[[t.IO[t.Any]], t.Mapping], - silent: bool = False, - ) -> bool: - """Update the values in the config from a file that is loaded - using the ``load`` parameter. The loaded data is passed to the - :meth:`from_mapping` method. + def from_json(self, filename, silent=False): + """Updates the values in the config from a JSON file. This function + behaves as if the JSON object was a dictionary and passed to the + :meth:`from_mapping` function. - .. code-block:: python + :param filename: the filename of the JSON file. This can either be an + absolute filename or a filename relative to the + root path. + :param silent: set to ``True`` if you want silent failure for missing + files. - import toml - app.config.from_file("config.toml", load=toml.load) - - :param filename: The path to the data file. This can be an - absolute path or relative to the config root path. - :param load: A callable that takes a file handle and returns a - mapping of loaded data from the file. - :type load: ``Callable[[Reader], Mapping]`` where ``Reader`` - implements a ``read`` method. - :param silent: Ignore the file if it doesn't exist. - - .. versionadded:: 2.0 + .. versionadded:: 0.11 """ filename = os.path.join(self.root_path, filename) try: - with open(filename) as f: - obj = load(f) - except OSError as e: + with open(filename) as json_file: + obj = json.loads(json_file.read()) + except IOError as e: if silent and e.errno in (errno.ENOENT, errno.EISDIR): return False - - e.strerror = f"Unable to load configuration file ({e.strerror})" + e.strerror = "Unable to load configuration file (%s)" % e.strerror raise - return self.from_mapping(obj) - def from_json(self, filename: str, silent: bool = False) -> bool: - """Update the values in the config from a JSON file. The loaded - data is passed to the :meth:`from_mapping` method. - - :param filename: The path to the JSON file. This can be an - absolute path or relative to the config root path. - :param silent: Ignore the file if it doesn't exist. - - .. deprecated:: 2.0.0 - Will be removed in Flask 2.1. Use :meth:`from_file` instead. - This was removed early in 2.0.0, was added back in 2.0.1. - - .. versionadded:: 0.11 - """ - import warnings - from . import json - - warnings.warn( - "'from_json' is deprecated and will be removed in Flask" - " 2.1. Use 'from_file(path, json.load)' instead.", - DeprecationWarning, - stacklevel=2, - ) - return self.from_file(filename, json.load, silent=silent) - - def from_mapping( - self, mapping: t.Optional[t.Mapping[str, t.Any]] = None, **kwargs: t.Any - ) -> bool: + def from_mapping(self, *mapping, **kwargs): """Updates the config like :meth:`update` ignoring items with non-upper keys. .. versionadded:: 0.11 """ - mappings: t.Dict[str, t.Any] = {} - if mapping is not None: - mappings.update(mapping) - mappings.update(kwargs) - for key, value in mappings.items(): - if key.isupper(): - self[key] = value + mappings = [] + if len(mapping) == 1: + if hasattr(mapping[0], "items"): + mappings.append(mapping[0].items()) + else: + mappings.append(mapping[0]) + elif len(mapping) > 1: + raise TypeError( + "expected at most 1 positional argument, got %d" % len(mapping) + ) + mappings.append(kwargs.items()) + for mapping in mappings: + for (key, value) in mapping: + if key.isupper(): + self[key] = value return True - def get_namespace( - self, namespace: str, lowercase: bool = True, trim_namespace: bool = True - ) -> t.Dict[str, t.Any]: + def get_namespace(self, namespace, lowercase=True, trim_namespace=True): """Returns a dictionary containing a subset of configuration options that match the specified namespace/prefix. Example usage:: @@ -275,7 +253,7 @@ class Config(dict): .. versionadded:: 0.11 """ rv = {} - for k, v in self.items(): + for k, v in iteritems(self): if not k.startswith(namespace): continue if trim_namespace: @@ -287,5 +265,5 @@ class Config(dict): rv[key] = v return rv - def __repr__(self) -> str: - return f"<{type(self).__name__} {dict.__repr__(self)}>" + def __repr__(self): + return "<%s %s>" % (self.__class__.__name__, dict.__repr__(self)) diff --git a/src/flask/ctx.py b/src/flask/ctx.py index 5c064635..172f6a01 100644 --- a/src/flask/ctx.py +++ b/src/flask/ctx.py @@ -1,27 +1,31 @@ +# -*- coding: utf-8 -*- +""" + flask.ctx + ~~~~~~~~~ + + Implements the objects required to keep the context. + + :copyright: 2010 Pallets + :license: BSD-3-Clause +""" import sys -import typing as t from functools import update_wrapper -from types import TracebackType from werkzeug.exceptions import HTTPException +from ._compat import BROKEN_PYPY_CTXMGR_EXIT +from ._compat import reraise from .globals import _app_ctx_stack from .globals import _request_ctx_stack from .signals import appcontext_popped from .signals import appcontext_pushed -from .typing import AfterRequestCallable - -if t.TYPE_CHECKING: - from .app import Flask - from .sessions import SessionMixin - from .wrappers import Request # a singleton sentinel value for parameter defaults _sentinel = object() -class _AppCtxGlobals: +class _AppCtxGlobals(object): """A plain object. Used as a namespace for storing data during an application context. @@ -41,25 +45,7 @@ class _AppCtxGlobals: .. versionadded:: 0.10 """ - # Define attr methods to let mypy know this is a namespace object - # that has arbitrary attributes. - - def __getattr__(self, name: str) -> t.Any: - try: - return self.__dict__[name] - except KeyError: - raise AttributeError(name) from None - - def __setattr__(self, name: str, value: t.Any) -> None: - self.__dict__[name] = value - - def __delattr__(self, name: str) -> None: - try: - del self.__dict__[name] - except KeyError: - raise AttributeError(name) from None - - def get(self, name: str, default: t.Optional[t.Any] = None) -> t.Any: + def get(self, name, default=None): """Get an attribute by name, or a default value. Like :meth:`dict.get`. @@ -70,12 +56,12 @@ class _AppCtxGlobals: """ return self.__dict__.get(name, default) - def pop(self, name: str, default: t.Any = _sentinel) -> t.Any: + def pop(self, name, default=_sentinel): """Get and remove an attribute by name. Like :meth:`dict.pop`. :param name: Name of attribute to pop. :param default: Value to return if the attribute is not present, - instead of raising a ``KeyError``. + instead of raise a ``KeyError``. .. versionadded:: 0.11 """ @@ -84,32 +70,32 @@ class _AppCtxGlobals: else: return self.__dict__.pop(name, default) - def setdefault(self, name: str, default: t.Any = None) -> t.Any: + def setdefault(self, name, default=None): """Get the value of an attribute if it is present, otherwise set and return a default value. Like :meth:`dict.setdefault`. :param name: Name of attribute to get. - :param default: Value to set and return if the attribute is not + :param: default: Value to set and return if the attribute is not present. .. versionadded:: 0.11 """ return self.__dict__.setdefault(name, default) - def __contains__(self, item: str) -> bool: + def __contains__(self, item): return item in self.__dict__ - def __iter__(self) -> t.Iterator[str]: + def __iter__(self): return iter(self.__dict__) - def __repr__(self) -> str: + def __repr__(self): top = _app_ctx_stack.top if top is not None: - return f"" + return "" % top.app.name return object.__repr__(self) -def after_this_request(f: AfterRequestCallable) -> AfterRequestCallable: +def after_this_request(f): """Executes a function after this request. This is useful to modify response objects. The function is passed the response object and has to return the same or a new one. @@ -134,7 +120,7 @@ def after_this_request(f: AfterRequestCallable) -> AfterRequestCallable: return f -def copy_current_request_context(f: t.Callable) -> t.Callable: +def copy_current_request_context(f): """A helper function that decorates a function to retain the current request context. This is useful when working with greenlets. The moment the function is decorated a copy of the request context is created and @@ -174,7 +160,7 @@ def copy_current_request_context(f: t.Callable) -> t.Callable: return update_wrapper(wrapper, f) -def has_request_context() -> bool: +def has_request_context(): """If you have code that wants to test if a request context is there or not this function can be used. For instance, you may want to take advantage of request information if the request object is available, but fail @@ -206,7 +192,7 @@ def has_request_context() -> bool: return _request_ctx_stack.top is not None -def has_app_context() -> bool: +def has_app_context(): """Works like :func:`has_request_context` but for the application context. You can also just do a boolean check on the :data:`current_app` object instead. @@ -216,7 +202,7 @@ def has_app_context() -> bool: return _app_ctx_stack.top is not None -class AppContext: +class AppContext(object): """The application context binds an application object implicitly to the current thread or greenlet, similar to how the :class:`RequestContext` binds request information. The application @@ -225,7 +211,7 @@ class AppContext: context. """ - def __init__(self, app: "Flask") -> None: + def __init__(self, app): self.app = app self.url_adapter = app.create_url_adapter(None) self.g = app.app_ctx_globals_class() @@ -234,13 +220,15 @@ class AppContext: # but there a basic "refcount" is enough to track them. self._refcnt = 0 - def push(self) -> None: + def push(self): """Binds the app context to the current context.""" self._refcnt += 1 + if hasattr(sys, "exc_clear"): + sys.exc_clear() _app_ctx_stack.push(self) appcontext_pushed.send(self.app) - def pop(self, exc: t.Optional[BaseException] = _sentinel) -> None: # type: ignore + def pop(self, exc=_sentinel): """Pops the app context.""" try: self._refcnt -= 1 @@ -250,20 +238,21 @@ class AppContext: self.app.do_teardown_appcontext(exc) finally: rv = _app_ctx_stack.pop() - assert rv is self, f"Popped wrong app context. ({rv!r} instead of {self!r})" + assert rv is self, "Popped wrong app context. (%r instead of %r)" % (rv, self) appcontext_popped.send(self.app) - def __enter__(self) -> "AppContext": + def __enter__(self): self.push() return self - def __exit__( - self, exc_type: type, exc_value: BaseException, tb: TracebackType - ) -> None: + def __exit__(self, exc_type, exc_value, tb): self.pop(exc_value) + if BROKEN_PYPY_CTXMGR_EXIT and exc_type is not None: + reraise(exc_type, exc_value, tb) -class RequestContext: + +class RequestContext(object): """The request context contains all request relevant information. It is created at the beginning of the request and pushed to the `_request_ctx_stack` and removed at the end of it. It will create the @@ -293,13 +282,7 @@ class RequestContext: that situation, otherwise your unittests will leak memory. """ - def __init__( - self, - app: "Flask", - environ: dict, - request: t.Optional["Request"] = None, - session: t.Optional["SessionMixin"] = None, - ) -> None: + def __init__(self, app, environ, request=None, session=None): self.app = app if request is None: request = app.request_class(environ) @@ -316,7 +299,7 @@ class RequestContext: # other request contexts. Now only if the last level is popped we # get rid of them. Additionally if an application context is missing # one is created implicitly so for each level we add this information - self._implicit_app_ctx_stack: t.List[t.Optional["AppContext"]] = [] + self._implicit_app_ctx_stack = [] # indicator if the context was preserved. Next time another context # is pushed the preserved context is popped. @@ -329,17 +312,17 @@ class RequestContext: # Functions that should be executed after the request on the response # object. These will be called before the regular "after_request" # functions. - self._after_request_functions: t.List[AfterRequestCallable] = [] + self._after_request_functions = [] @property - def g(self) -> AppContext: + def g(self): return _app_ctx_stack.top.g @g.setter - def g(self, value: AppContext) -> None: + def g(self, value): _app_ctx_stack.top.g = value - def copy(self) -> "RequestContext": + def copy(self): """Creates a copy of this request context with the same request object. This can be used to move a request context to a different greenlet. Because the actual request object is the same this cannot be used to @@ -359,17 +342,17 @@ class RequestContext: session=self.session, ) - def match_request(self) -> None: + def match_request(self): """Can be overridden by a subclass to hook into the matching of the request. """ try: - result = self.url_adapter.match(return_rule=True) # type: ignore - self.request.url_rule, self.request.view_args = result # type: ignore + result = self.url_adapter.match(return_rule=True) + self.request.url_rule, self.request.view_args = result except HTTPException as e: self.request.routing_exception = e - def push(self) -> None: + def push(self): """Binds the request context to the current context.""" # If an exception occurs in debug mode or if context preservation is # activated under exception situations exactly one context stays @@ -393,6 +376,9 @@ class RequestContext: else: self._implicit_app_ctx_stack.append(None) + if hasattr(sys, "exc_clear"): + sys.exc_clear() + _request_ctx_stack.push(self) # Open the session at the moment that the request context is available. @@ -406,12 +392,10 @@ class RequestContext: if self.session is None: self.session = session_interface.make_null_session(self.app) - # Match the request URL after loading the session, so that the - # session is available in custom URL converters. if self.url_adapter is not None: self.match_request() - def pop(self, exc: t.Optional[BaseException] = _sentinel) -> None: # type: ignore + def pop(self, exc=_sentinel): """Pops the request context and unbinds it by doing that. This will also trigger the execution of functions registered by the :meth:`~flask.Flask.teardown_request` decorator. @@ -420,9 +404,9 @@ class RequestContext: Added the `exc` argument. """ app_ctx = self._implicit_app_ctx_stack.pop() - clear_request = False try: + clear_request = False if not self._implicit_app_ctx_stack: self.preserved = False self._preserved_exc = None @@ -430,6 +414,13 @@ class RequestContext: exc = sys.exc_info()[1] self.app.do_teardown_request(exc) + # If this interpreter supports clearing the exception information + # we do that now. This will only go into effect on Python 2.x, + # on 3.x it disappears automatically at the end of the exception + # stack. + if hasattr(sys, "exc_clear"): + sys.exc_clear() + request_close = getattr(self.request, "close", None) if request_close is not None: request_close() @@ -446,26 +437,25 @@ class RequestContext: if app_ctx is not None: app_ctx.pop(exc) - assert ( - rv is self - ), f"Popped wrong request context. ({rv!r} instead of {self!r})" + assert rv is self, "Popped wrong request context. (%r instead of %r)" % ( + rv, + self, + ) - def auto_pop(self, exc: t.Optional[BaseException]) -> None: + def auto_pop(self, exc): if self.request.environ.get("flask._preserve_context") or ( exc is not None and self.app.preserve_context_on_exception ): self.preserved = True - self._preserved_exc = exc # type: ignore + self._preserved_exc = exc else: self.pop(exc) - def __enter__(self) -> "RequestContext": + def __enter__(self): self.push() return self - def __exit__( - self, exc_type: type, exc_value: BaseException, tb: TracebackType - ) -> None: + def __exit__(self, exc_type, exc_value, tb): # do not pop the request stack if we are in debug mode and an # exception happened. This will allow the debugger to still # access the request object in the interactive shell. Furthermore @@ -473,8 +463,13 @@ class RequestContext: # See flask.testing for how this works. self.auto_pop(exc_value) - def __repr__(self) -> str: - return ( - f"<{type(self).__name__} {self.request.url!r}" - f" [{self.request.method}] of {self.app.name}>" + if BROKEN_PYPY_CTXMGR_EXIT and exc_type is not None: + reraise(exc_type, exc_value, tb) + + def __repr__(self): + return "<%s '%s' [%s] of %s>" % ( + self.__class__.__name__, + self.request.url, + self.request.method, + self.app.name, ) diff --git a/src/flask/debughelpers.py b/src/flask/debughelpers.py index ce65c487..e475bd1a 100644 --- a/src/flask/debughelpers.py +++ b/src/flask/debughelpers.py @@ -1,7 +1,18 @@ +# -*- coding: utf-8 -*- +""" + flask.debughelpers + ~~~~~~~~~~~~~~~~~~ + + Various helpers to make the development experience better. + + :copyright: 2010 Pallets + :license: BSD-3-Clause +""" import os -import typing as t from warnings import warn +from ._compat import implements_to_string +from ._compat import text_type from .app import Flask from .blueprints import Blueprint from .globals import _request_ctx_stack @@ -13,6 +24,7 @@ class UnexpectedUnicodeError(AssertionError, UnicodeError): """ +@implements_to_string class DebugFilesKeyError(KeyError, AssertionError): """Raised from request.files during debugging. The idea is that it can provide a better error message than just a generic KeyError/BadRequest. @@ -21,18 +33,17 @@ class DebugFilesKeyError(KeyError, AssertionError): def __init__(self, request, key): form_matches = request.form.getlist(key) buf = [ - f"You tried to access the file {key!r} in the request.files" - " dictionary but it does not exist. The mimetype for the" - f" request is {request.mimetype!r} instead of" - " 'multipart/form-data' which means that no file contents" - " were transmitted. To fix this error you should provide" - ' enctype="multipart/form-data" in your form.' + 'You tried to access the file "%s" in the request.files ' + "dictionary but it does not exist. The mimetype for the request " + 'is "%s" instead of "multipart/form-data" which means that no ' + "file contents were transmitted. To fix this error you should " + 'provide enctype="multipart/form-data" in your form.' + % (key, request.mimetype) ] if form_matches: - names = ", ".join(repr(x) for x in form_matches) buf.append( "\n\nThe browser instead transmitted some file names. " - f"This was submitted: {names}" + "This was submitted: %s" % ", ".join('"%s"' % x for x in form_matches) ) self.msg = "".join(buf) @@ -49,24 +60,24 @@ class FormDataRoutingRedirect(AssertionError): def __init__(self, request): exc = request.routing_exception buf = [ - f"A request was sent to this URL ({request.url}) but a" - " redirect was issued automatically by the routing system" - f" to {exc.new_url!r}." + "A request was sent to this URL (%s) but a redirect was " + 'issued automatically by the routing system to "%s".' + % (request.url, exc.new_url) ] # In case just a slash was appended we can be extra helpful - if f"{request.base_url}/" == exc.new_url.split("?")[0]: + if request.base_url + "/" == exc.new_url.split("?")[0]: buf.append( - " The URL was defined with a trailing slash so Flask" - " will automatically redirect to the URL with the" - " trailing slash if it was accessed without one." + " The URL was defined with a trailing slash so " + "Flask will automatically redirect to the URL " + "with the trailing slash if it was accessed " + "without one." ) buf.append( - " Make sure to directly send your" - f" {request.method}-request to this URL since we can't make" - " browsers or HTTP clients redirect with form data reliably" - " or without user interaction." + " Make sure to directly send your %s-request to this URL " + "since we can't make browsers or HTTP clients redirect " + "with form data reliably or without user interaction." % request.method ) buf.append("\n\nNote: this exception is only raised in debug mode") AssertionError.__init__(self, "".join(buf).encode("utf-8")) @@ -93,26 +104,26 @@ def attach_enctype_error_multidict(request): request.files.__class__ = newcls -def _dump_loader_info(loader) -> t.Generator: - yield f"class: {type(loader).__module__}.{type(loader).__name__}" +def _dump_loader_info(loader): + yield "class: %s.%s" % (type(loader).__module__, type(loader).__name__) for key, value in sorted(loader.__dict__.items()): if key.startswith("_"): continue if isinstance(value, (tuple, list)): - if not all(isinstance(x, str) for x in value): + if not all(isinstance(x, (str, text_type)) for x in value): continue - yield f"{key}:" + yield "%s:" % key for item in value: - yield f" - {item}" + yield " - %s" % item continue - elif not isinstance(value, (str, int, float, bool)): + elif not isinstance(value, (str, text_type, int, float, bool)): continue - yield f"{key}: {value!r}" + yield "%s: %r" % (key, value) -def explain_template_loading_attempts(app: Flask, template, attempts) -> None: +def explain_template_loading_attempts(app, template, attempts): """This should help developers understand what failed""" - info = [f"Locating template {template!r}:"] + info = ['Locating template "%s":' % template] total_found = 0 blueprint = None reqctx = _request_ctx_stack.top @@ -121,23 +132,23 @@ def explain_template_loading_attempts(app: Flask, template, attempts) -> None: for idx, (loader, srcobj, triple) in enumerate(attempts): if isinstance(srcobj, Flask): - src_info = f"application {srcobj.import_name!r}" + src_info = 'application "%s"' % srcobj.import_name elif isinstance(srcobj, Blueprint): - src_info = f"blueprint {srcobj.name!r} ({srcobj.import_name})" + src_info = 'blueprint "%s" (%s)' % (srcobj.name, srcobj.import_name) else: src_info = repr(srcobj) - info.append(f"{idx + 1:5}: trying loader of {src_info}") + info.append("% 5d: trying loader of %s" % (idx + 1, src_info)) for line in _dump_loader_info(loader): - info.append(f" {line}") + info.append(" %s" % line) if triple is None: detail = "no match" else: - detail = f"found ({triple[1] or ''!r})" + detail = "found (%r)" % (triple[1] or "") total_found += 1 - info.append(f" -> {detail}") + info.append(" -> %s" % detail) seems_fishy = False if total_found == 0: @@ -149,23 +160,24 @@ def explain_template_loading_attempts(app: Flask, template, attempts) -> None: if blueprint is not None and seems_fishy: info.append( - " The template was looked up from an endpoint that belongs" - f" to the blueprint {blueprint!r}." + " The template was looked up from an endpoint that " + 'belongs to the blueprint "%s".' % blueprint ) info.append(" Maybe you did not place a template in the right folder?") - info.append(" See https://flask.palletsprojects.com/blueprints/#templates") + info.append(" See http://flask.pocoo.org/docs/blueprints/#templates") app.logger.info("\n".join(info)) -def explain_ignored_app_run() -> None: +def explain_ignored_app_run(): if os.environ.get("WERKZEUG_RUN_MAIN") != "true": warn( Warning( - "Silently ignoring app.run() because the application is" - " run from the flask command line executable. Consider" - ' putting app.run() behind an if __name__ == "__main__"' - " guard to silence this warning." + "Silently ignoring app.run() because the " + "application is run from the flask command line " + "executable. Consider putting app.run() behind an " + 'if __name__ == "__main__" guard to silence this ' + "warning." ), stacklevel=3, ) diff --git a/src/flask/globals.py b/src/flask/globals.py index 6d91c75e..6d32dcfd 100644 --- a/src/flask/globals.py +++ b/src/flask/globals.py @@ -1,14 +1,19 @@ -import typing as t +# -*- coding: utf-8 -*- +""" + flask.globals + ~~~~~~~~~~~~~ + + Defines all the global objects that are proxies to the current + active context. + + :copyright: 2010 Pallets + :license: BSD-3-Clause +""" from functools import partial from werkzeug.local import LocalProxy from werkzeug.local import LocalStack -if t.TYPE_CHECKING: - from .app import Flask - from .ctx import _AppCtxGlobals - from .sessions import SessionMixin - from .wrappers import Request _request_ctx_err_msg = """\ Working outside of request context. @@ -51,9 +56,7 @@ def _find_app(): # context locals _request_ctx_stack = LocalStack() _app_ctx_stack = LocalStack() -current_app: "Flask" = LocalProxy(_find_app) # type: ignore -request: "Request" = LocalProxy(partial(_lookup_req_object, "request")) # type: ignore -session: "SessionMixin" = LocalProxy( # type: ignore - partial(_lookup_req_object, "session") -) -g: "_AppCtxGlobals" = LocalProxy(partial(_lookup_app_object, "g")) # type: ignore +current_app = LocalProxy(_find_app) +request = LocalProxy(partial(_lookup_req_object, "request")) +session = LocalProxy(partial(_lookup_req_object, "session")) +g = LocalProxy(partial(_lookup_app_object, "g")) diff --git a/src/flask/helpers.py b/src/flask/helpers.py index 7b8b0870..c396b8b1 100644 --- a/src/flask/helpers.py +++ b/src/flask/helpers.py @@ -1,20 +1,39 @@ +# -*- coding: utf-8 -*- +""" + flask.helpers + ~~~~~~~~~~~~~ + + Implements various helpers. + + :copyright: 2010 Pallets + :license: BSD-3-Clause +""" +import io +import mimetypes import os import pkgutil +import posixpath import socket import sys -import typing as t -import warnings -from datetime import datetime -from datetime import timedelta -from functools import lru_cache +import unicodedata from functools import update_wrapper from threading import RLock +from time import time +from zlib import adler32 -import werkzeug.utils +from jinja2 import FileSystemLoader +from werkzeug.datastructures import Headers +from werkzeug.exceptions import BadRequest from werkzeug.exceptions import NotFound +from werkzeug.exceptions import RequestedRangeNotSatisfiable from werkzeug.routing import BuildError from werkzeug.urls import url_quote +from werkzeug.wsgi import wrap_file +from ._compat import fspath +from ._compat import PY2 +from ._compat import string_types +from ._compat import text_type from .globals import _app_ctx_stack from .globals import _request_ctx_stack from .globals import current_app @@ -22,11 +41,19 @@ from .globals import request from .globals import session from .signals import message_flashed -if t.TYPE_CHECKING: - from .wrappers import Response +# sentinel +_missing = object() -def get_env() -> str: +# what separators does this operating system provide that are not a slash? +# this is used by the send_from_directory function to ensure that nobody is +# able to access files from outside the filesystem. +_os_alt_seps = list( + sep for sep in [os.path.sep, os.path.altsep] if sep not in (None, "/") +) + + +def get_env(): """Get the environment the app is running in, indicated by the :envvar:`FLASK_ENV` environment variable. The default is ``'production'``. @@ -34,7 +61,7 @@ def get_env() -> str: return os.environ.get("FLASK_ENV") or "production" -def get_debug_flag() -> bool: +def get_debug_flag(): """Get whether debug mode should be enabled for the app, indicated by the :envvar:`FLASK_DEBUG` environment variable. The default is ``True`` if :func:`.get_env` returns ``'development'``, or ``False`` @@ -48,7 +75,7 @@ def get_debug_flag() -> bool: return val.lower() not in ("0", "false", "no") -def get_load_dotenv(default: bool = True) -> bool: +def get_load_dotenv(default=True): """Get whether the user has disabled loading dotenv files by setting :envvar:`FLASK_SKIP_DOTENV`. The default is ``True``, load the files. @@ -63,11 +90,15 @@ def get_load_dotenv(default: bool = True) -> bool: return val.lower() in ("0", "false", "no") -def stream_with_context( - generator_or_function: t.Union[ - t.Iterator[t.AnyStr], t.Callable[..., t.Iterator[t.AnyStr]] - ] -) -> t.Iterator[t.AnyStr]: +def _endpoint_from_view_func(view_func): + """Internal helper that returns the default endpoint for a given + function. This always is the function name. + """ + assert view_func is not None, "expected view func if endpoint is not provided." + return view_func.__name__ + + +def stream_with_context(generator_or_function): """Request contexts disappear when the response is started on the server. This is done for efficiency reasons and to make it less likely to encounter memory leaks with badly written WSGI middlewares. The downside is that if @@ -102,16 +133,16 @@ def stream_with_context( .. versionadded:: 0.9 """ try: - gen = iter(generator_or_function) # type: ignore + gen = iter(generator_or_function) except TypeError: - def decorator(*args: t.Any, **kwargs: t.Any) -> t.Any: - gen = generator_or_function(*args, **kwargs) # type: ignore + def decorator(*args, **kwargs): + gen = generator_or_function(*args, **kwargs) return stream_with_context(gen) - return update_wrapper(decorator, generator_or_function) # type: ignore + return update_wrapper(decorator, generator_or_function) - def generator() -> t.Generator: + def generator(): ctx = _request_ctx_stack.top if ctx is None: raise RuntimeError( @@ -128,10 +159,11 @@ def stream_with_context( # don't need that because they are closed on their destruction # automatically. try: - yield from gen + for item in gen: + yield item finally: if hasattr(gen, "close"): - gen.close() # type: ignore + gen.close() # The trick is to start the generator. Then the code execution runs until # the first dummy None is yielded at which point the context was already @@ -142,7 +174,7 @@ def stream_with_context( return wrapped_g -def make_response(*args: t.Any) -> "Response": +def make_response(*args): """Sometimes it is necessary to set additional headers in a view. Because views do not have to return response objects but can return a value that is converted into a response object by Flask itself, it becomes tricky to @@ -191,7 +223,7 @@ def make_response(*args: t.Any) -> "Response": return current_app.make_response(args) -def url_for(endpoint: str, **values: t.Any) -> str: +def url_for(endpoint, **values): """Generates a URL to the given endpoint with the method provided. Variable arguments that are unknown to the target endpoint are appended @@ -204,7 +236,7 @@ def url_for(endpoint: str, **values: t.Any) -> str: url_for('.index') - See :ref:`url-building`. + For more information, head over to the :ref:`Quickstart `. Configuration values ``APPLICATION_ROOT`` and ``SERVER_NAME`` are only used when generating URLs outside of a request context. @@ -230,7 +262,7 @@ def url_for(endpoint: str, **values: t.Any) -> str: # Re-raise the BuildError, in context of original traceback. exc_type, exc_value, tb = sys.exc_info() if exc_value is error: - raise exc_type(exc_value).with_traceback(tb) + raise exc_type, exc_value, tb else: raise error # url_for will use this result, instead of raising BuildError. @@ -261,9 +293,9 @@ def url_for(endpoint: str, **values: t.Any) -> str: :param _scheme: a string specifying the desired URL scheme. The `_external` parameter must be set to ``True`` or a :exc:`ValueError` is raised. The default behavior uses the same scheme as the current request, or - :data:`PREFERRED_URL_SCHEME` if no request context is available. - This also can be set to an empty string to build protocol-relative - URLs. + ``PREFERRED_URL_SCHEME`` from the :ref:`app configuration ` if no + request context is available. As of Werkzeug 0.10, this also can be set + to an empty string to build protocol-relative URLs. :param _anchor: if provided this is added as anchor to the URL. :param _method: if provided this explicitly specifies an HTTP method. """ @@ -285,7 +317,7 @@ def url_for(endpoint: str, **values: t.Any) -> str: if endpoint[:1] == ".": if blueprint_name is not None: - endpoint = f"{blueprint_name}{endpoint}" + endpoint = blueprint_name + endpoint else: endpoint = endpoint[1:] @@ -338,11 +370,11 @@ def url_for(endpoint: str, **values: t.Any) -> str: return appctx.app.handle_url_build_error(error, endpoint, values) if anchor is not None: - rv += f"#{url_quote(anchor)}" + rv += "#" + url_quote(anchor) return rv -def get_template_attribute(template_name: str, attribute: str) -> t.Any: +def get_template_attribute(template_name, attribute): """Loads a macro (or variable) a template exports. This can be used to invoke a macro from within Python code. If you for example have a template named :file:`_cider.html` with the following contents: @@ -364,7 +396,7 @@ def get_template_attribute(template_name: str, attribute: str) -> t.Any: return getattr(current_app.jinja_env.get_template(template_name).module, attribute) -def flash(message: str, category: str = "message") -> None: +def flash(message, category="message"): """Flashes a message to the next request. In order to remove the flashed message from the session and to display it to the user, the template has to call :func:`get_flashed_messages`. @@ -390,15 +422,11 @@ def flash(message: str, category: str = "message") -> None: flashes.append((category, message)) session["_flashes"] = flashes message_flashed.send( - current_app._get_current_object(), # type: ignore - message=message, - category=category, + current_app._get_current_object(), message=message, category=category ) -def get_flashed_messages( - with_categories: bool = False, category_filter: t.Iterable[str] = () -) -> t.Union[t.List[str], t.List[t.Tuple[str, str]]]: +def get_flashed_messages(with_categories=False, category_filter=()): """Pulls all flashed messages from the session and returns them. Further calls in the same request to the function will return the same messages. By default just the messages are returned, @@ -415,7 +443,7 @@ def get_flashed_messages( * `category_filter` filters the messages down to only those matching the provided categories. - See :doc:`/patterns/flashing` for examples. + See :ref:`message-flashing-pattern` for examples. .. versionchanged:: 0.3 `with_categories` parameter added. @@ -424,8 +452,7 @@ def get_flashed_messages( `category_filter` parameter added. :param with_categories: set to ``True`` to also receive categories. - :param category_filter: filter of categories to limit return values. Only - categories in the list will be returned. + :param category_filter: whitelist of categories to limit return values """ flashes = _request_ctx_stack.top.flashes if flashes is None: @@ -439,398 +466,690 @@ def get_flashed_messages( return flashes -def _prepare_send_file_kwargs( - download_name: t.Optional[str] = None, - attachment_filename: t.Optional[str] = None, - etag: t.Optional[t.Union[bool, str]] = None, - add_etags: t.Optional[t.Union[bool]] = None, - max_age: t.Optional[ - t.Union[int, t.Callable[[t.Optional[str]], t.Optional[int]]] - ] = None, - cache_timeout: t.Optional[int] = None, - **kwargs: t.Any, -) -> t.Dict[str, t.Any]: - if attachment_filename is not None: - warnings.warn( - "The 'attachment_filename' parameter has been renamed to" - " 'download_name'. The old name will be removed in Flask" - " 2.1.", - DeprecationWarning, - stacklevel=3, - ) - download_name = attachment_filename - - if cache_timeout is not None: - warnings.warn( - "The 'cache_timeout' parameter has been renamed to" - " 'max_age'. The old name will be removed in Flask 2.1.", - DeprecationWarning, - stacklevel=3, - ) - max_age = cache_timeout - - if add_etags is not None: - warnings.warn( - "The 'add_etags' parameter has been renamed to 'etag'. The" - " old name will be removed in Flask 2.1.", - DeprecationWarning, - stacklevel=3, - ) - etag = add_etags - - if max_age is None: - max_age = current_app.get_send_file_max_age - - kwargs.update( - environ=request.environ, - download_name=download_name, - etag=etag, - max_age=max_age, - use_x_sendfile=current_app.use_x_sendfile, - response_class=current_app.response_class, - _root_path=current_app.root_path, # type: ignore - ) - return kwargs - - def send_file( - path_or_file: t.Union[os.PathLike, str, t.BinaryIO], - mimetype: t.Optional[str] = None, - as_attachment: bool = False, - download_name: t.Optional[str] = None, - attachment_filename: t.Optional[str] = None, - conditional: bool = True, - etag: t.Union[bool, str] = True, - add_etags: t.Optional[bool] = None, - last_modified: t.Optional[t.Union[datetime, int, float]] = None, - max_age: t.Optional[ - t.Union[int, t.Callable[[t.Optional[str]], t.Optional[int]]] - ] = None, - cache_timeout: t.Optional[int] = None, + filename_or_fp, + mimetype=None, + as_attachment=False, + attachment_filename=None, + add_etags=True, + cache_timeout=None, + conditional=False, + last_modified=None, ): - """Send the contents of a file to the client. + """Sends the contents of a file to the client. This will use the + most efficient method available and configured. By default it will + try to use the WSGI server's file_wrapper support. Alternatively + you can set the application's :attr:`~Flask.use_x_sendfile` attribute + to ``True`` to directly emit an ``X-Sendfile`` header. This however + requires support of the underlying webserver for ``X-Sendfile``. - The first argument can be a file path or a file-like object. Paths - are preferred in most cases because Werkzeug can manage the file and - get extra information from the path. Passing a file-like object - requires that the file is opened in binary mode, and is mostly - useful when building a file in memory with :class:`io.BytesIO`. + By default it will try to guess the mimetype for you, but you can + also explicitly provide one. For extra security you probably want + to send certain files as attachment (HTML for instance). The mimetype + guessing requires a `filename` or an `attachment_filename` to be + provided. - Never pass file paths provided by a user. The path is assumed to be - trusted, so a user could craft a path to access a file you didn't - intend. Use :func:`send_from_directory` to safely serve - user-requested paths from within a directory. + ETags will also be attached automatically if a `filename` is provided. You + can turn this off by setting `add_etags=False`. - If the WSGI server sets a ``file_wrapper`` in ``environ``, it is - used, otherwise Werkzeug's built-in wrapper is used. Alternatively, - if the HTTP server supports ``X-Sendfile``, configuring Flask with - ``USE_X_SENDFILE = True`` will tell the server to send the given - path, which is much more efficient than reading it in Python. + If `conditional=True` and `filename` is provided, this method will try to + upgrade the response stream to support range requests. This will allow + the request to be answered with partial content response. - :param path_or_file: The path to the file to send, relative to the - current working directory if a relative path is given. - Alternatively, a file-like object opened in binary mode. Make - sure the file pointer is seeked to the start of the data. - :param mimetype: The MIME type to send for the file. If not - provided, it will try to detect it from the file name. - :param as_attachment: Indicate to a browser that it should offer to - save the file instead of displaying it. - :param download_name: The default name browsers will use when saving - the file. Defaults to the passed file name. - :param conditional: Enable conditional and range responses based on - request headers. Requires passing a file path and ``environ``. - :param etag: Calculate an ETag for the file, which requires passing - a file path. Can also be a string to use instead. - :param last_modified: The last modified time to send for the file, - in seconds. If not provided, it will try to detect it from the - file path. - :param max_age: How long the client should cache the file, in - seconds. If set, ``Cache-Control`` will be ``public``, otherwise - it will be ``no-cache`` to prefer conditional caching. + Please never pass filenames to this function from user sources; + you should use :func:`send_from_directory` instead. - .. versionchanged:: 2.0 - ``download_name`` replaces the ``attachment_filename`` - parameter. If ``as_attachment=False``, it is passed with - ``Content-Disposition: inline`` instead. + .. versionadded:: 0.2 - .. versionchanged:: 2.0 - ``max_age`` replaces the ``cache_timeout`` parameter. - ``conditional`` is enabled and ``max_age`` is not set by - default. + .. versionadded:: 0.5 + The `add_etags`, `cache_timeout` and `conditional` parameters were + added. The default behavior is now to attach etags. - .. versionchanged:: 2.0 - ``etag`` replaces the ``add_etags`` parameter. It can be a - string to use instead of generating one. + .. versionchanged:: 0.7 + mimetype guessing and etag support for file objects was + deprecated because it was unreliable. Pass a filename if you are + able to, otherwise attach an etag yourself. This functionality + will be removed in Flask 1.0 - .. versionchanged:: 2.0 - Passing a file-like object that inherits from - :class:`~io.TextIOBase` will raise a :exc:`ValueError` rather - than sending an empty file. + .. versionchanged:: 0.9 + cache_timeout pulls its default from application config, when None. - .. versionadded:: 2.0 - Moved the implementation to Werkzeug. This is now a wrapper to - pass some Flask-specific arguments. + .. versionchanged:: 0.12 + The filename is no longer automatically inferred from file objects. If + you want to use automatic mimetype and etag support, pass a filepath via + `filename_or_fp` or `attachment_filename`. - .. versionchanged:: 1.1 - ``filename`` may be a :class:`~os.PathLike` object. + .. versionchanged:: 0.12 + The `attachment_filename` is preferred over `filename` for MIME-type + detection. - .. versionchanged:: 1.1 - Passing a :class:`~io.BytesIO` object supports range requests. + .. versionchanged:: 1.0 + UTF-8 filenames, as specified in `RFC 2231`_, are supported. + + .. _RFC 2231: https://tools.ietf.org/html/rfc2231#section-4 .. versionchanged:: 1.0.3 Filenames are encoded with ASCII instead of Latin-1 for broader compatibility with WSGI servers. - .. versionchanged:: 1.0 - UTF-8 filenames as specified in :rfc:`2231` are supported. + .. versionchanged:: 1.1 + Filename may be a :class:`~os.PathLike` object. - .. versionchanged:: 0.12 - The filename is no longer automatically inferred from file - objects. If you want to use automatic MIME and etag support, - pass a filename via ``filename_or_fp`` or - ``attachment_filename``. + .. versionadded:: 1.1 + Partial content supports :class:`~io.BytesIO`. - .. versionchanged:: 0.12 - ``attachment_filename`` is preferred over ``filename`` for MIME - detection. + :param filename_or_fp: the filename of the file to send. + This is relative to the :attr:`~Flask.root_path` + if a relative path is specified. + Alternatively a file object might be provided in + which case ``X-Sendfile`` might not work and fall + back to the traditional method. Make sure that the + file pointer is positioned at the start of data to + send before calling :func:`send_file`. + :param mimetype: the mimetype of the file if provided. If a file path is + given, auto detection happens as fallback, otherwise an + error will be raised. + :param as_attachment: set to ``True`` if you want to send this file with + a ``Content-Disposition: attachment`` header. + :param attachment_filename: the filename for the attachment if it + differs from the file's filename. + :param add_etags: set to ``False`` to disable attaching of etags. + :param conditional: set to ``True`` to enable conditional responses. - .. versionchanged:: 0.9 - ``cache_timeout`` defaults to - :meth:`Flask.get_send_file_max_age`. - - .. versionchanged:: 0.7 - MIME guessing and etag support for file-like objects was - deprecated because it was unreliable. Pass a filename if you are - able to, otherwise attach an etag yourself. - - .. versionchanged:: 0.5 - The ``add_etags``, ``cache_timeout`` and ``conditional`` - parameters were added. The default behavior is to add etags. - - .. versionadded:: 0.2 + :param cache_timeout: the timeout in seconds for the headers. When ``None`` + (default), this value is set by + :meth:`~Flask.get_send_file_max_age` of + :data:`~flask.current_app`. + :param last_modified: set the ``Last-Modified`` header to this value, + a :class:`~datetime.datetime` or timestamp. + If a file was passed, this overrides its mtime. """ - return werkzeug.utils.send_file( - **_prepare_send_file_kwargs( - path_or_file=path_or_file, - environ=request.environ, - mimetype=mimetype, - as_attachment=as_attachment, - download_name=download_name, - attachment_filename=attachment_filename, - conditional=conditional, - etag=etag, - add_etags=add_etags, - last_modified=last_modified, - max_age=max_age, - cache_timeout=cache_timeout, - ) - ) + mtime = None + fsize = None + if hasattr(filename_or_fp, "__fspath__"): + filename_or_fp = fspath(filename_or_fp) -def safe_join(directory: str, *pathnames: str) -> str: - """Safely join zero or more untrusted path components to a base - directory to avoid escaping the base directory. + if isinstance(filename_or_fp, string_types): + filename = filename_or_fp + if not os.path.isabs(filename): + filename = os.path.join(current_app.root_path, filename) + file = None + if attachment_filename is None: + attachment_filename = os.path.basename(filename) + else: + file = filename_or_fp + filename = None - :param directory: The trusted base directory. - :param pathnames: The untrusted path components relative to the - base directory. - :return: A safe path, otherwise ``None``. - """ - warnings.warn( - "'flask.helpers.safe_join' is deprecated and will be removed in" - " Flask 2.1. Use 'werkzeug.utils.safe_join' instead.", - DeprecationWarning, - stacklevel=2, - ) - path = werkzeug.utils.safe_join(directory, *pathnames) - - if path is None: - raise NotFound() - - return path - - -def send_from_directory( - directory: t.Union[os.PathLike, str], - path: t.Union[os.PathLike, str], - filename: t.Optional[str] = None, - **kwargs: t.Any, -) -> "Response": - """Send a file from within a directory using :func:`send_file`. - - .. code-block:: python - - @app.route("/uploads/") - def download_file(name): - return send_from_directory( - app.config['UPLOAD_FOLDER'], name, as_attachment=True + if mimetype is None: + if attachment_filename is not None: + mimetype = ( + mimetypes.guess_type(attachment_filename)[0] + or "application/octet-stream" ) - This is a secure way to serve files from a folder, such as static - files or uploads. Uses :func:`~werkzeug.security.safe_join` to - ensure the path coming from the client is not maliciously crafted to - point outside the specified directory. + if mimetype is None: + raise ValueError( + "Unable to infer MIME-type because no filename is available. " + "Please set either `attachment_filename`, pass a filepath to " + "`filename_or_fp` or set your own MIME-type via `mimetype`." + ) - If the final path does not point to an existing regular file, - raises a 404 :exc:`~werkzeug.exceptions.NotFound` error. + headers = Headers() + if as_attachment: + if attachment_filename is None: + raise TypeError("filename unavailable, required for sending as attachment") - :param directory: The directory that ``path`` must be located under. - :param path: The path to the file to send, relative to - ``directory``. - :param kwargs: Arguments to pass to :func:`send_file`. + if not isinstance(attachment_filename, text_type): + attachment_filename = attachment_filename.decode("utf-8") - .. versionchanged:: 2.0 - ``path`` replaces the ``filename`` parameter. + try: + attachment_filename = attachment_filename.encode("ascii") + except UnicodeEncodeError: + filenames = { + "filename": unicodedata.normalize("NFKD", attachment_filename).encode( + "ascii", "ignore" + ), + "filename*": "UTF-8''%s" % url_quote(attachment_filename, safe=b""), + } + else: + filenames = {"filename": attachment_filename} - .. versionadded:: 2.0 - Moved the implementation to Werkzeug. This is now a wrapper to - pass some Flask-specific arguments. + headers.add("Content-Disposition", "attachment", **filenames) - .. versionadded:: 0.5 - """ - if filename is not None: - warnings.warn( - "The 'filename' parameter has been renamed to 'path'. The" - " old name will be removed in Flask 2.1.", - DeprecationWarning, - stacklevel=2, - ) - path = filename + if current_app.use_x_sendfile and filename: + if file is not None: + file.close() + headers["X-Sendfile"] = filename + fsize = os.path.getsize(filename) + headers["Content-Length"] = fsize + data = None + else: + if file is None: + file = open(filename, "rb") + mtime = os.path.getmtime(filename) + fsize = os.path.getsize(filename) + headers["Content-Length"] = fsize + elif isinstance(file, io.BytesIO): + try: + fsize = file.getbuffer().nbytes + except AttributeError: + # Python 2 doesn't have getbuffer + fsize = len(file.getvalue()) + headers["Content-Length"] = fsize + data = wrap_file(request.environ, file) - return werkzeug.utils.send_from_directory( # type: ignore - directory, path, **_prepare_send_file_kwargs(**kwargs) + rv = current_app.response_class( + data, mimetype=mimetype, headers=headers, direct_passthrough=True ) + if last_modified is not None: + rv.last_modified = last_modified + elif mtime is not None: + rv.last_modified = mtime -def get_root_path(import_name: str) -> str: - """Find the root path of a package, or the path that contains a - module. If it cannot be found, returns the current working - directory. + rv.cache_control.public = True + if cache_timeout is None: + cache_timeout = current_app.get_send_file_max_age(filename) + if cache_timeout is not None: + rv.cache_control.max_age = cache_timeout + rv.expires = int(time() + cache_timeout) - Not to be confused with the value returned by :func:`find_package`. + if add_etags and filename is not None: + from warnings import warn - :meta private: + try: + rv.set_etag( + "%s-%s-%s" + % ( + os.path.getmtime(filename), + os.path.getsize(filename), + adler32( + filename.encode("utf-8") + if isinstance(filename, text_type) + else filename + ) + & 0xFFFFFFFF, + ) + ) + except OSError: + warn( + "Access %s failed, maybe it does not exist, so ignore etags in " + "headers" % filename, + stacklevel=2, + ) + + if conditional: + try: + rv = rv.make_conditional(request, accept_ranges=True, complete_length=fsize) + except RequestedRangeNotSatisfiable: + if file is not None: + file.close() + raise + # make sure we don't send x-sendfile for servers that + # ignore the 304 status code for x-sendfile. + if rv.status_code == 304: + rv.headers.pop("x-sendfile", None) + return rv + + +def safe_join(directory, *pathnames): + """Safely join `directory` and zero or more untrusted `pathnames` + components. + + Example usage:: + + @app.route('/wiki/') + def wiki_page(filename): + filename = safe_join(app.config['WIKI_FOLDER'], filename) + with open(filename, 'rb') as fd: + content = fd.read() # Read and process the file content... + + :param directory: the trusted base directory. + :param pathnames: the untrusted pathnames relative to that directory. + :raises: :class:`~werkzeug.exceptions.NotFound` if one or more passed + paths fall out of its boundaries. """ - # Module already imported and has a file attribute. Use that first. - mod = sys.modules.get(import_name) + parts = [directory] + + for filename in pathnames: + if filename != "": + filename = posixpath.normpath(filename) + + if ( + any(sep in filename for sep in _os_alt_seps) + or os.path.isabs(filename) + or filename == ".." + or filename.startswith("../") + ): + raise NotFound() + + parts.append(filename) + + return posixpath.join(*parts) + + +def send_from_directory(directory, filename, **options): + """Send a file from a given directory with :func:`send_file`. This + is a secure way to quickly expose static files from an upload folder + or something similar. + + Example usage:: + + @app.route('/uploads/') + def download_file(filename): + return send_from_directory(app.config['UPLOAD_FOLDER'], + filename, as_attachment=True) + + .. admonition:: Sending files and Performance + + It is strongly recommended to activate either ``X-Sendfile`` support in + your webserver or (if no authentication happens) to tell the webserver + to serve files for the given path on its own without calling into the + web application for improved performance. + + .. versionadded:: 0.5 + + :param directory: the directory where all the files are stored. + :param filename: the filename relative to that directory to + download. + :param options: optional keyword arguments that are directly + forwarded to :func:`send_file`. + """ + filename = fspath(filename) + directory = fspath(directory) + filename = safe_join(directory, filename) + if not os.path.isabs(filename): + filename = os.path.join(current_app.root_path, filename) + try: + if not os.path.isfile(filename): + raise NotFound() + except (TypeError, ValueError): + raise BadRequest() + options.setdefault("conditional", True) + return send_file(filename, **options) + + +def get_root_path(import_name): + """Returns the path to a package or cwd if that cannot be found. This + returns the path of a package or the folder that contains a module. + + Not to be confused with the package path returned by :func:`find_package`. + """ + # Module already imported and has a file attribute. Use that first. + mod = sys.modules.get(import_name) if mod is not None and hasattr(mod, "__file__"): return os.path.dirname(os.path.abspath(mod.__file__)) # Next attempt: check the loader. loader = pkgutil.get_loader(import_name) - # Loader does not exist or we're referring to an unloaded main - # module or a main module without path (interactive sessions), go - # with the current working directory. + # Loader does not exist or we're referring to an unloaded main module + # or a main module without path (interactive sessions), go with the + # current working directory. if loader is None or import_name == "__main__": return os.getcwd() + # For .egg, zipimporter does not have get_filename until Python 2.7. + # Some other loaders might exhibit the same behavior. if hasattr(loader, "get_filename"): - filepath = loader.get_filename(import_name) # type: ignore + filepath = loader.get_filename(import_name) else: # Fall back to imports. __import__(import_name) mod = sys.modules[import_name] filepath = getattr(mod, "__file__", None) - # If we don't have a file path it might be because it is a - # namespace package. In this case pick the root path from the - # first module that is contained in the package. + # If we don't have a filepath it might be because we are a + # namespace package. In this case we pick the root path from the + # first module that is contained in our package. if filepath is None: raise RuntimeError( - "No root path can be found for the provided module" - f" {import_name!r}. This can happen because the module" - " came from an import hook that does not provide file" - " name information or because it's a namespace package." - " In this case the root path needs to be explicitly" - " provided." + "No root path can be found for the provided " + 'module "%s". This can happen because the ' + "module came from an import hook that does " + "not provide file name information or because " + "it's a namespace package. In this case " + "the root path needs to be explicitly " + "provided." % import_name ) # filepath is import_name.py for a module, or __init__.py for a package. return os.path.dirname(os.path.abspath(filepath)) -class locked_cached_property(werkzeug.utils.cached_property): - """A :func:`property` that is only evaluated once. Like - :class:`werkzeug.utils.cached_property` except access uses a lock - for thread safety. +def _matching_loader_thinks_module_is_package(loader, mod_name): + """Given the loader that loaded a module and the module this function + attempts to figure out if the given module is actually a package. + """ + # If the loader can tell us if something is a package, we can + # directly ask the loader. + if hasattr(loader, "is_package"): + return loader.is_package(mod_name) + # importlib's namespace loaders do not have this functionality but + # all the modules it loads are packages, so we can take advantage of + # this information. + elif ( + loader.__class__.__module__ == "_frozen_importlib" + and loader.__class__.__name__ == "NamespaceLoader" + ): + return True + # Otherwise we need to fail with an error that explains what went + # wrong. + raise AttributeError( + ( + "%s.is_package() method is missing but is required by Flask of " + "PEP 302 import hooks. If you do not use import hooks and " + "you encounter this error please file a bug against Flask." + ) + % loader.__class__.__name__ + ) - .. versionchanged:: 2.0 - Inherits from Werkzeug's ``cached_property`` (and ``property``). + +def _find_package_path(root_mod_name): + """Find the path where the module's root exists in""" + if sys.version_info >= (3, 4): + import importlib.util + + try: + spec = importlib.util.find_spec(root_mod_name) + if spec is None: + raise ValueError("not found") + # ImportError: the machinery told us it does not exist + # ValueError: + # - the module name was invalid + # - the module name is __main__ + # - *we* raised `ValueError` due to `spec` being `None` + except (ImportError, ValueError): + pass # handled below + else: + # namespace package + if spec.origin in {"namespace", None}: + return os.path.dirname(next(iter(spec.submodule_search_locations))) + # a package (with __init__.py) + elif spec.submodule_search_locations: + return os.path.dirname(os.path.dirname(spec.origin)) + # just a normal module + else: + return os.path.dirname(spec.origin) + + # we were unable to find the `package_path` using PEP 451 loaders + loader = pkgutil.get_loader(root_mod_name) + if loader is None or root_mod_name == "__main__": + # import name is not found, or interactive/main module + return os.getcwd() + else: + # For .egg, zipimporter does not have get_filename until Python 2.7. + if hasattr(loader, "get_filename"): + filename = loader.get_filename(root_mod_name) + elif hasattr(loader, "archive"): + # zipimporter's loader.archive points to the .egg or .zip + # archive filename is dropped in call to dirname below. + filename = loader.archive + else: + # At least one loader is missing both get_filename and archive: + # Google App Engine's HardenedModulesHook + # + # Fall back to imports. + __import__(root_mod_name) + filename = sys.modules[root_mod_name].__file__ + package_path = os.path.abspath(os.path.dirname(filename)) + + # In case the root module is a package we need to chop of the + # rightmost part. This needs to go through a helper function + # because of python 3.3 namespace packages. + if _matching_loader_thinks_module_is_package(loader, root_mod_name): + package_path = os.path.dirname(package_path) + + return package_path + + +def find_package(import_name): + """Finds a package and returns the prefix (or None if the package is + not installed) as well as the folder that contains the package or + module as a tuple. The package path returned is the module that would + have to be added to the pythonpath in order to make it possible to + import the module. The prefix is the path below which a UNIX like + folder structure exists (lib, share etc.). + """ + root_mod_name, _, _ = import_name.partition(".") + package_path = _find_package_path(root_mod_name) + site_parent, site_folder = os.path.split(package_path) + py_prefix = os.path.abspath(sys.prefix) + if package_path.startswith(py_prefix): + return py_prefix, package_path + elif site_folder.lower() == "site-packages": + parent, folder = os.path.split(site_parent) + # Windows like installations + if folder.lower() == "lib": + base_dir = parent + # UNIX like installations + elif os.path.basename(parent).lower() == "lib": + base_dir = os.path.dirname(parent) + else: + base_dir = site_parent + return base_dir, package_path + return None, package_path + + +class locked_cached_property(object): + """A decorator that converts a function into a lazy property. The + function wrapped is called the first time to retrieve the result + and then that calculated result is used the next time you access + the value. Works like the one in Werkzeug but has a lock for + thread safety. """ - def __init__( - self, - fget: t.Callable[[t.Any], t.Any], - name: t.Optional[str] = None, - doc: t.Optional[str] = None, - ) -> None: - super().__init__(fget, name=name, doc=doc) + def __init__(self, func, name=None, doc=None): + self.__name__ = name or func.__name__ + self.__module__ = func.__module__ + self.__doc__ = doc or func.__doc__ + self.func = func self.lock = RLock() - def __get__(self, obj: object, type: type = None) -> t.Any: # type: ignore + def __get__(self, obj, type=None): if obj is None: return self - with self.lock: - return super().__get__(obj, type=type) - - def __set__(self, obj: object, value: t.Any) -> None: - with self.lock: - super().__set__(obj, value) - - def __delete__(self, obj: object) -> None: - with self.lock: - super().__delete__(obj) + value = obj.__dict__.get(self.__name__, _missing) + if value is _missing: + value = self.func(obj) + obj.__dict__[self.__name__] = value + return value -def total_seconds(td: timedelta) -> int: +class _PackageBoundObject(object): + #: The name of the package or module that this app belongs to. Do not + #: change this once it is set by the constructor. + import_name = None + + #: Location of the template files to be added to the template lookup. + #: ``None`` if templates should not be added. + template_folder = None + + #: Absolute path to the package on the filesystem. Used to look up + #: resources contained in the package. + root_path = None + + def __init__(self, import_name, template_folder=None, root_path=None): + self.import_name = import_name + self.template_folder = template_folder + + if root_path is None: + root_path = get_root_path(self.import_name) + + self.root_path = root_path + self._static_folder = None + self._static_url_path = None + + # circular import + from .cli import AppGroup + + #: The Click command group for registration of CLI commands + #: on the application and associated blueprints. These commands + #: are accessible via the :command:`flask` command once the + #: application has been discovered and blueprints registered. + self.cli = AppGroup() + + @property + def static_folder(self): + """The absolute path to the configured static folder.""" + if self._static_folder is not None: + return os.path.join(self.root_path, self._static_folder) + + @static_folder.setter + def static_folder(self, value): + if value is not None: + value = fspath(value).rstrip(r"\/") + self._static_folder = value + + @property + def static_url_path(self): + """The URL prefix that the static route will be accessible from. + + If it was not configured during init, it is derived from + :attr:`static_folder`. + """ + if self._static_url_path is not None: + return self._static_url_path + + if self.static_folder is not None: + basename = os.path.basename(self.static_folder) + return ("/" + basename).rstrip("/") + + @static_url_path.setter + def static_url_path(self, value): + if value is not None: + value = value.rstrip("/") + + self._static_url_path = value + + @property + def has_static_folder(self): + """This is ``True`` if the package bound object's container has a + folder for static files. + + .. versionadded:: 0.5 + """ + return self.static_folder is not None + + @locked_cached_property + def jinja_loader(self): + """The Jinja loader for this package bound object. + + .. versionadded:: 0.5 + """ + if self.template_folder is not None: + return FileSystemLoader(os.path.join(self.root_path, self.template_folder)) + + def get_send_file_max_age(self, filename): + """Provides default cache_timeout for the :func:`send_file` functions. + + By default, this function returns ``SEND_FILE_MAX_AGE_DEFAULT`` from + the configuration of :data:`~flask.current_app`. + + Static file functions such as :func:`send_from_directory` use this + function, and :func:`send_file` calls this function on + :data:`~flask.current_app` when the given cache_timeout is ``None``. If a + cache_timeout is given in :func:`send_file`, that timeout is used; + otherwise, this method is called. + + This allows subclasses to change the behavior when sending files based + on the filename. For example, to set the cache timeout for .js files + to 60 seconds:: + + class MyFlask(flask.Flask): + def get_send_file_max_age(self, name): + if name.lower().endswith('.js'): + return 60 + return flask.Flask.get_send_file_max_age(self, name) + + .. versionadded:: 0.9 + """ + return total_seconds(current_app.send_file_max_age_default) + + def send_static_file(self, filename): + """Function used internally to send static files from the static + folder to the browser. + + .. versionadded:: 0.5 + """ + if not self.has_static_folder: + raise RuntimeError("No static folder for this object") + # Ensure get_send_file_max_age is called in all cases. + # Here, we ensure get_send_file_max_age is called for Blueprints. + cache_timeout = self.get_send_file_max_age(filename) + return send_from_directory( + self.static_folder, filename, cache_timeout=cache_timeout + ) + + def open_resource(self, resource, mode="rb"): + """Opens a resource from the application's resource folder. To see + how this works, consider the following folder structure:: + + /myapplication.py + /schema.sql + /static + /style.css + /templates + /layout.html + /index.html + + If you want to open the :file:`schema.sql` file you would do the + following:: + + with app.open_resource('schema.sql') as f: + contents = f.read() + do_something_with(contents) + + :param resource: the name of the resource. To access resources within + subfolders use forward slashes as separator. + :param mode: Open file in this mode. Only reading is supported, + valid values are "r" (or "rt") and "rb". + """ + if mode not in {"r", "rt", "rb"}: + raise ValueError("Resources can only be opened for reading") + + return open(os.path.join(self.root_path, resource), mode) + + +def total_seconds(td): """Returns the total seconds from a timedelta object. :param timedelta td: the timedelta to be converted in seconds :returns: number of seconds :rtype: int - - .. deprecated:: 2.0 - Will be removed in Flask 2.1. Use - :meth:`timedelta.total_seconds` instead. """ - warnings.warn( - "'total_seconds' is deprecated and will be removed in Flask" - " 2.1. Use 'timedelta.total_seconds' instead.", - DeprecationWarning, - stacklevel=2, - ) return td.days * 60 * 60 * 24 + td.seconds -def is_ip(value: str) -> bool: +def is_ip(value): """Determine if the given string is an IP address. + Python 2 on Windows doesn't provide ``inet_pton``, so this only + checks IPv4 addresses in that environment. + :param value: value to check :type value: str :return: True if string is an IP address :rtype: bool """ + if PY2 and os.name == "nt": + try: + socket.inet_aton(value) + return True + except socket.error: + return False + for family in (socket.AF_INET, socket.AF_INET6): try: socket.inet_pton(family, value) - except OSError: + except socket.error: pass else: return True return False - - -@lru_cache(maxsize=None) -def _split_blueprint_path(name: str) -> t.List[str]: - out: t.List[str] = [name] - - if "." in name: - out.extend(_split_blueprint_path(name.rpartition(".")[0])) - - return out diff --git a/src/flask/json/__init__.py b/src/flask/json/__init__.py index 5780e204..88f2747d 100644 --- a/src/flask/json/__init__.py +++ b/src/flask/json/__init__.py @@ -1,335 +1,357 @@ -import io -import json as _json -import typing as t -import uuid -import warnings -from datetime import date +# -*- coding: utf-8 -*- +""" +flask.json +~~~~~~~~~~ -from jinja2.utils import htmlsafe_json_dumps as _jinja_htmlsafe_dumps +:copyright: 2010 Pallets +:license: BSD-3-Clause +""" +import codecs +import io +import uuid +from datetime import date +from datetime import datetime + +from itsdangerous import json as _json +from jinja2 import Markup from werkzeug.http import http_date +from .._compat import PY2 +from .._compat import text_type from ..globals import current_app from ..globals import request -if t.TYPE_CHECKING: - from ..app import Flask - from ..wrappers import Response - try: import dataclasses except ImportError: - # Python < 3.7 - dataclasses = None # type: ignore + dataclasses = None + +# Figure out if simplejson escapes slashes. This behavior was changed +# from one version to another without reason. +_slash_escape = "\\/" not in _json.dumps("/") + + +__all__ = [ + "dump", + "dumps", + "load", + "loads", + "htmlsafe_dump", + "htmlsafe_dumps", + "JSONDecoder", + "JSONEncoder", + "jsonify", +] + + +def _wrap_reader_for_text(fp, encoding): + if isinstance(fp.read(0), bytes): + fp = io.TextIOWrapper(io.BufferedReader(fp), encoding) + return fp + + +def _wrap_writer_for_text(fp, encoding): + try: + fp.write("") + except TypeError: + fp = io.TextIOWrapper(fp, encoding) + return fp class JSONEncoder(_json.JSONEncoder): - """The default JSON encoder. Handles extra types compared to the - built-in :class:`json.JSONEncoder`. + """The default Flask JSON encoder. This one extends the default + encoder by also supporting ``datetime``, ``UUID``, ``dataclasses``, + and ``Markup`` objects. - - :class:`datetime.datetime` and :class:`datetime.date` are - serialized to :rfc:`822` strings. This is the same as the HTTP - date format. - - :class:`uuid.UUID` is serialized to a string. - - :class:`dataclasses.dataclass` is passed to - :func:`dataclasses.asdict`. - - :class:`~markupsafe.Markup` (or any object with a ``__html__`` - method) will call the ``__html__`` method to get a string. + ``datetime`` objects are serialized as RFC 822 datetime strings. + This is the same as the HTTP date format. - Assign a subclass of this to :attr:`flask.Flask.json_encoder` or - :attr:`flask.Blueprint.json_encoder` to override the default. + In order to support more data types, override the :meth:`default` + method. """ - def default(self, o: t.Any) -> t.Any: - """Convert ``o`` to a JSON serializable type. See - :meth:`json.JSONEncoder.default`. Python does not support - overriding how basic types like ``str`` or ``list`` are - serialized, they are handled before this method. + def default(self, o): + """Implement this method in a subclass such that it returns a + serializable object for ``o``, or calls the base implementation (to + raise a :exc:`TypeError`). + + For example, to support arbitrary iterators, you could implement + default like this:: + + def default(self, o): + try: + iterable = iter(o) + except TypeError: + pass + else: + return list(iterable) + return JSONEncoder.default(self, o) """ + if isinstance(o, datetime): + return http_date(o.utctimetuple()) if isinstance(o, date): - return http_date(o) + return http_date(o.timetuple()) if isinstance(o, uuid.UUID): return str(o) if dataclasses and dataclasses.is_dataclass(o): return dataclasses.asdict(o) if hasattr(o, "__html__"): - return str(o.__html__()) - return super().default(o) + return text_type(o.__html__()) + return _json.JSONEncoder.default(self, o) class JSONDecoder(_json.JSONDecoder): - """The default JSON decoder. - - This does not change any behavior from the built-in - :class:`json.JSONDecoder`. - - Assign a subclass of this to :attr:`flask.Flask.json_decoder` or - :attr:`flask.Blueprint.json_decoder` to override the default. + """The default JSON decoder. This one does not change the behavior from + the default simplejson decoder. Consult the :mod:`json` documentation + for more information. This decoder is not only used for the load + functions of this module but also :attr:`~flask.Request`. """ -def _dump_arg_defaults( - kwargs: t.Dict[str, t.Any], app: t.Optional["Flask"] = None -) -> None: +def _dump_arg_defaults(kwargs, app=None): """Inject default arguments for dump functions.""" if app is None: app = current_app if app: - cls = app.json_encoder - bp = app.blueprints.get(request.blueprint) if request else None # type: ignore - if bp is not None and bp.json_encoder is not None: - cls = bp.json_encoder + bp = app.blueprints.get(request.blueprint) if request else None + kwargs.setdefault( + "cls", bp.json_encoder if bp and bp.json_encoder else app.json_encoder + ) + + if not app.config["JSON_AS_ASCII"]: + kwargs.setdefault("ensure_ascii", False) - kwargs.setdefault("cls", cls) - kwargs.setdefault("ensure_ascii", app.config["JSON_AS_ASCII"]) kwargs.setdefault("sort_keys", app.config["JSON_SORT_KEYS"]) else: kwargs.setdefault("sort_keys", True) kwargs.setdefault("cls", JSONEncoder) -def _load_arg_defaults( - kwargs: t.Dict[str, t.Any], app: t.Optional["Flask"] = None -) -> None: +def _load_arg_defaults(kwargs, app=None): """Inject default arguments for load functions.""" if app is None: app = current_app if app: - cls = app.json_decoder - bp = app.blueprints.get(request.blueprint) if request else None # type: ignore - if bp is not None and bp.json_decoder is not None: - cls = bp.json_decoder - - kwargs.setdefault("cls", cls) + bp = app.blueprints.get(request.blueprint) if request else None + kwargs.setdefault( + "cls", bp.json_decoder if bp and bp.json_decoder else app.json_decoder + ) else: kwargs.setdefault("cls", JSONDecoder) -def dumps(obj: t.Any, app: t.Optional["Flask"] = None, **kwargs: t.Any) -> str: - """Serialize an object to a string of JSON. +def detect_encoding(data): + """Detect which UTF codec was used to encode the given bytes. - Takes the same arguments as the built-in :func:`json.dumps`, with - some defaults from application configuration. + The latest JSON standard (:rfc:`8259`) suggests that only UTF-8 is + accepted. Older documents allowed 8, 16, or 32. 16 and 32 can be big + or little endian. Some editors or libraries may prepend a BOM. + + :param data: Bytes in unknown UTF encoding. + :return: UTF encoding name + """ + head = data[:4] + + if head[:3] == codecs.BOM_UTF8: + return "utf-8-sig" + + if b"\x00" not in head: + return "utf-8" + + if head in (codecs.BOM_UTF32_BE, codecs.BOM_UTF32_LE): + return "utf-32" + + if head[:2] in (codecs.BOM_UTF16_BE, codecs.BOM_UTF16_LE): + return "utf-16" + + if len(head) == 4: + if head[:3] == b"\x00\x00\x00": + return "utf-32-be" + + if head[::2] == b"\x00\x00": + return "utf-16-be" + + if head[1:] == b"\x00\x00\x00": + return "utf-32-le" + + if head[1::2] == b"\x00\x00": + return "utf-16-le" + + if len(head) == 2: + return "utf-16-be" if head.startswith(b"\x00") else "utf-16-le" + + return "utf-8" + + +def dumps(obj, app=None, **kwargs): + """Serialize ``obj`` to a JSON-formatted string. If there is an + app context pushed, use the current app's configured encoder + (:attr:`~flask.Flask.json_encoder`), or fall back to the default + :class:`JSONEncoder`. + + Takes the same arguments as the built-in :func:`json.dumps`, and + does some extra configuration based on the application. If the + simplejson package is installed, it is preferred. :param obj: Object to serialize to JSON. - :param app: Use this app's config instead of the active app context - or defaults. + :param app: App instance to use to configure the JSON encoder. + Uses ``current_app`` if not given, and falls back to the default + encoder when not in an app context. :param kwargs: Extra arguments passed to :func:`json.dumps`. - .. versionchanged:: 2.0 - ``encoding`` is deprecated and will be removed in Flask 2.1. - .. versionchanged:: 1.0.3 + ``app`` can be passed directly, rather than requiring an app context for configuration. """ _dump_arg_defaults(kwargs, app=app) encoding = kwargs.pop("encoding", None) rv = _json.dumps(obj, **kwargs) - - if encoding is not None: - warnings.warn( - "'encoding' is deprecated and will be removed in Flask 2.1.", - DeprecationWarning, - stacklevel=2, - ) - - if isinstance(rv, str): - return rv.encode(encoding) # type: ignore - + if encoding is not None and isinstance(rv, text_type): + rv = rv.encode(encoding) return rv -def dump( - obj: t.Any, fp: t.IO[str], app: t.Optional["Flask"] = None, **kwargs: t.Any -) -> None: - """Serialize an object to JSON written to a file object. - - Takes the same arguments as the built-in :func:`json.dump`, with - some defaults from application configuration. - - :param obj: Object to serialize to JSON. - :param fp: File object to write JSON to. - :param app: Use this app's config instead of the active app context - or defaults. - :param kwargs: Extra arguments passed to :func:`json.dump`. - - .. versionchanged:: 2.0 - Writing to a binary file, and the ``encoding`` argument, is - deprecated and will be removed in Flask 2.1. - """ +def dump(obj, fp, app=None, **kwargs): + """Like :func:`dumps` but writes into a file object.""" _dump_arg_defaults(kwargs, app=app) encoding = kwargs.pop("encoding", None) - show_warning = encoding is not None - - try: - fp.write("") - except TypeError: - show_warning = True - fp = io.TextIOWrapper(fp, encoding or "utf-8") # type: ignore - - if show_warning: - warnings.warn( - "Writing to a binary file, and the 'encoding' argument, is" - " deprecated and will be removed in Flask 2.1.", - DeprecationWarning, - stacklevel=2, - ) - + if encoding is not None: + fp = _wrap_writer_for_text(fp, encoding) _json.dump(obj, fp, **kwargs) -def loads(s: str, app: t.Optional["Flask"] = None, **kwargs: t.Any) -> t.Any: - """Deserialize an object from a string of JSON. +def loads(s, app=None, **kwargs): + """Deserialize an object from a JSON-formatted string ``s``. If + there is an app context pushed, use the current app's configured + decoder (:attr:`~flask.Flask.json_decoder`), or fall back to the + default :class:`JSONDecoder`. - Takes the same arguments as the built-in :func:`json.loads`, with - some defaults from application configuration. + Takes the same arguments as the built-in :func:`json.loads`, and + does some extra configuration based on the application. If the + simplejson package is installed, it is preferred. :param s: JSON string to deserialize. - :param app: Use this app's config instead of the active app context - or defaults. + :param app: App instance to use to configure the JSON decoder. + Uses ``current_app`` if not given, and falls back to the default + encoder when not in an app context. :param kwargs: Extra arguments passed to :func:`json.loads`. - .. versionchanged:: 2.0 - ``encoding`` is deprecated and will be removed in Flask 2.1. The - data must be a string or UTF-8 bytes. - .. versionchanged:: 1.0.3 + ``app`` can be passed directly, rather than requiring an app context for configuration. """ _load_arg_defaults(kwargs, app=app) - encoding = kwargs.pop("encoding", None) - - if encoding is not None: - warnings.warn( - "'encoding' is deprecated and will be removed in Flask 2.1." - " The data must be a string or UTF-8 bytes.", - DeprecationWarning, - stacklevel=2, - ) - - if isinstance(s, bytes): - s = s.decode(encoding) - + if isinstance(s, bytes): + encoding = kwargs.pop("encoding", None) + if encoding is None: + encoding = detect_encoding(s) + s = s.decode(encoding) return _json.loads(s, **kwargs) -def load(fp: t.IO[str], app: t.Optional["Flask"] = None, **kwargs: t.Any) -> t.Any: - """Deserialize an object from JSON read from a file object. - - Takes the same arguments as the built-in :func:`json.load`, with - some defaults from application configuration. - - :param fp: File object to read JSON from. - :param app: Use this app's config instead of the active app context - or defaults. - :param kwargs: Extra arguments passed to :func:`json.load`. - - .. versionchanged:: 2.0 - ``encoding`` is deprecated and will be removed in Flask 2.1. The - file must be text mode, or binary mode with UTF-8 bytes. - """ +def load(fp, app=None, **kwargs): + """Like :func:`loads` but reads from a file object.""" _load_arg_defaults(kwargs, app=app) - encoding = kwargs.pop("encoding", None) - - if encoding is not None: - warnings.warn( - "'encoding' is deprecated and will be removed in Flask 2.1." - " The file must be text mode, or binary mode with UTF-8" - " bytes.", - DeprecationWarning, - stacklevel=2, - ) - - if isinstance(fp.read(0), bytes): - fp = io.TextIOWrapper(fp, encoding) # type: ignore - + if not PY2: + fp = _wrap_reader_for_text(fp, kwargs.pop("encoding", None) or "utf-8") return _json.load(fp, **kwargs) -def htmlsafe_dumps(obj: t.Any, **kwargs: t.Any) -> str: - """Serialize an object to a string of JSON with :func:`dumps`, then - replace HTML-unsafe characters with Unicode escapes and mark the - result safe with :class:`~markupsafe.Markup`. +def htmlsafe_dumps(obj, **kwargs): + """Works exactly like :func:`dumps` but is safe for use in ``