Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/flask/ctx.py: 70%

151 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2023-12-09 07:17 +0000

1from __future__ import annotations 

2 

3import contextvars 

4import sys 

5import typing as t 

6from functools import update_wrapper 

7from types import TracebackType 

8 

9from werkzeug.exceptions import HTTPException 

10 

11from . import typing as ft 

12from .globals import _cv_app 

13from .globals import _cv_request 

14from .signals import appcontext_popped 

15from .signals import appcontext_pushed 

16 

17if t.TYPE_CHECKING: # pragma: no cover 

18 from .app import Flask 

19 from .sessions import SessionMixin 

20 from .wrappers import Request 

21 

22 

23# a singleton sentinel value for parameter defaults 

24_sentinel = object() 

25 

26 

27class _AppCtxGlobals: 

28 """A plain object. Used as a namespace for storing data during an 

29 application context. 

30 

31 Creating an app context automatically creates this object, which is 

32 made available as the :data:`g` proxy. 

33 

34 .. describe:: 'key' in g 

35 

36 Check whether an attribute is present. 

37 

38 .. versionadded:: 0.10 

39 

40 .. describe:: iter(g) 

41 

42 Return an iterator over the attribute names. 

43 

44 .. versionadded:: 0.10 

45 """ 

46 

47 # Define attr methods to let mypy know this is a namespace object 

48 # that has arbitrary attributes. 

49 

50 def __getattr__(self, name: str) -> t.Any: 

51 try: 

52 return self.__dict__[name] 

53 except KeyError: 

54 raise AttributeError(name) from None 

55 

56 def __setattr__(self, name: str, value: t.Any) -> None: 

57 self.__dict__[name] = value 

58 

59 def __delattr__(self, name: str) -> None: 

60 try: 

61 del self.__dict__[name] 

62 except KeyError: 

63 raise AttributeError(name) from None 

64 

65 def get(self, name: str, default: t.Any | None = None) -> t.Any: 

66 """Get an attribute by name, or a default value. Like 

67 :meth:`dict.get`. 

68 

69 :param name: Name of attribute to get. 

70 :param default: Value to return if the attribute is not present. 

71 

72 .. versionadded:: 0.10 

73 """ 

74 return self.__dict__.get(name, default) 

75 

76 def pop(self, name: str, default: t.Any = _sentinel) -> t.Any: 

77 """Get and remove an attribute by name. Like :meth:`dict.pop`. 

78 

79 :param name: Name of attribute to pop. 

80 :param default: Value to return if the attribute is not present, 

81 instead of raising a ``KeyError``. 

82 

83 .. versionadded:: 0.11 

84 """ 

85 if default is _sentinel: 

86 return self.__dict__.pop(name) 

87 else: 

88 return self.__dict__.pop(name, default) 

89 

90 def setdefault(self, name: str, default: t.Any = None) -> t.Any: 

91 """Get the value of an attribute if it is present, otherwise 

92 set and return a default value. Like :meth:`dict.setdefault`. 

93 

94 :param name: Name of attribute to get. 

95 :param default: Value to set and return if the attribute is not 

96 present. 

97 

98 .. versionadded:: 0.11 

99 """ 

100 return self.__dict__.setdefault(name, default) 

101 

102 def __contains__(self, item: str) -> bool: 

103 return item in self.__dict__ 

104 

105 def __iter__(self) -> t.Iterator[str]: 

106 return iter(self.__dict__) 

107 

108 def __repr__(self) -> str: 

109 ctx = _cv_app.get(None) 

110 if ctx is not None: 

111 return f"<flask.g of '{ctx.app.name}'>" 

112 return object.__repr__(self) 

113 

114 

115def after_this_request(f: ft.AfterRequestCallable) -> ft.AfterRequestCallable: 

116 """Executes a function after this request. This is useful to modify 

117 response objects. The function is passed the response object and has 

118 to return the same or a new one. 

119 

120 Example:: 

121 

122 @app.route('/') 

123 def index(): 

124 @after_this_request 

125 def add_header(response): 

126 response.headers['X-Foo'] = 'Parachute' 

127 return response 

128 return 'Hello World!' 

129 

130 This is more useful if a function other than the view function wants to 

131 modify a response. For instance think of a decorator that wants to add 

132 some headers without converting the return value into a response object. 

133 

134 .. versionadded:: 0.9 

135 """ 

136 ctx = _cv_request.get(None) 

137 

138 if ctx is None: 

139 raise RuntimeError( 

140 "'after_this_request' can only be used when a request" 

141 " context is active, such as in a view function." 

142 ) 

143 

144 ctx._after_request_functions.append(f) 

145 return f 

146 

147 

148def copy_current_request_context(f: t.Callable) -> t.Callable: 

149 """A helper function that decorates a function to retain the current 

150 request context. This is useful when working with greenlets. The moment 

151 the function is decorated a copy of the request context is created and 

152 then pushed when the function is called. The current session is also 

153 included in the copied request context. 

154 

155 Example:: 

156 

157 import gevent 

158 from flask import copy_current_request_context 

159 

160 @app.route('/') 

161 def index(): 

162 @copy_current_request_context 

163 def do_some_work(): 

164 # do some work here, it can access flask.request or 

165 # flask.session like you would otherwise in the view function. 

166 ... 

167 gevent.spawn(do_some_work) 

168 return 'Regular response' 

169 

170 .. versionadded:: 0.10 

171 """ 

172 ctx = _cv_request.get(None) 

173 

174 if ctx is None: 

175 raise RuntimeError( 

176 "'copy_current_request_context' can only be used when a" 

177 " request context is active, such as in a view function." 

178 ) 

179 

180 ctx = ctx.copy() 

181 

182 def wrapper(*args, **kwargs): 

183 with ctx: 

184 return ctx.app.ensure_sync(f)(*args, **kwargs) 

185 

186 return update_wrapper(wrapper, f) 

187 

188 

189def has_request_context() -> bool: 

190 """If you have code that wants to test if a request context is there or 

191 not this function can be used. For instance, you may want to take advantage 

192 of request information if the request object is available, but fail 

193 silently if it is unavailable. 

194 

195 :: 

196 

197 class User(db.Model): 

198 

199 def __init__(self, username, remote_addr=None): 

200 self.username = username 

201 if remote_addr is None and has_request_context(): 

202 remote_addr = request.remote_addr 

203 self.remote_addr = remote_addr 

204 

205 Alternatively you can also just test any of the context bound objects 

206 (such as :class:`request` or :class:`g`) for truthness:: 

207 

208 class User(db.Model): 

209 

210 def __init__(self, username, remote_addr=None): 

211 self.username = username 

212 if remote_addr is None and request: 

213 remote_addr = request.remote_addr 

214 self.remote_addr = remote_addr 

215 

216 .. versionadded:: 0.7 

217 """ 

218 return _cv_request.get(None) is not None 

219 

220 

221def has_app_context() -> bool: 

222 """Works like :func:`has_request_context` but for the application 

223 context. You can also just do a boolean check on the 

224 :data:`current_app` object instead. 

225 

226 .. versionadded:: 0.9 

227 """ 

228 return _cv_app.get(None) is not None 

229 

230 

231class AppContext: 

232 """The app context contains application-specific information. An app 

233 context is created and pushed at the beginning of each request if 

234 one is not already active. An app context is also pushed when 

235 running CLI commands. 

236 """ 

237 

238 def __init__(self, app: Flask) -> None: 

239 self.app = app 

240 self.url_adapter = app.create_url_adapter(None) 

241 self.g: _AppCtxGlobals = app.app_ctx_globals_class() 

242 self._cv_tokens: list[contextvars.Token] = [] 

243 

244 def push(self) -> None: 

245 """Binds the app context to the current context.""" 

246 self._cv_tokens.append(_cv_app.set(self)) 

247 appcontext_pushed.send(self.app, _async_wrapper=self.app.ensure_sync) 

248 

249 def pop(self, exc: BaseException | None = _sentinel) -> None: # type: ignore 

250 """Pops the app context.""" 

251 try: 

252 if len(self._cv_tokens) == 1: 

253 if exc is _sentinel: 

254 exc = sys.exc_info()[1] 

255 self.app.do_teardown_appcontext(exc) 

256 finally: 

257 ctx = _cv_app.get() 

258 _cv_app.reset(self._cv_tokens.pop()) 

259 

260 if ctx is not self: 

261 raise AssertionError( 

262 f"Popped wrong app context. ({ctx!r} instead of {self!r})" 

263 ) 

264 

265 appcontext_popped.send(self.app, _async_wrapper=self.app.ensure_sync) 

266 

267 def __enter__(self) -> AppContext: 

268 self.push() 

269 return self 

270 

271 def __exit__( 

272 self, 

273 exc_type: type | None, 

274 exc_value: BaseException | None, 

275 tb: TracebackType | None, 

276 ) -> None: 

277 self.pop(exc_value) 

278 

279 

280class RequestContext: 

281 """The request context contains per-request information. The Flask 

282 app creates and pushes it at the beginning of the request, then pops 

283 it at the end of the request. It will create the URL adapter and 

284 request object for the WSGI environment provided. 

285 

286 Do not attempt to use this class directly, instead use 

287 :meth:`~flask.Flask.test_request_context` and 

288 :meth:`~flask.Flask.request_context` to create this object. 

289 

290 When the request context is popped, it will evaluate all the 

291 functions registered on the application for teardown execution 

292 (:meth:`~flask.Flask.teardown_request`). 

293 

294 The request context is automatically popped at the end of the 

295 request. When using the interactive debugger, the context will be 

296 restored so ``request`` is still accessible. Similarly, the test 

297 client can preserve the context after the request ends. However, 

298 teardown functions may already have closed some resources such as 

299 database connections. 

300 """ 

301 

302 def __init__( 

303 self, 

304 app: Flask, 

305 environ: dict, 

306 request: Request | None = None, 

307 session: SessionMixin | None = None, 

308 ) -> None: 

309 self.app = app 

310 if request is None: 

311 request = app.request_class(environ) 

312 request.json_module = app.json 

313 self.request: Request = request 

314 self.url_adapter = None 

315 try: 

316 self.url_adapter = app.create_url_adapter(self.request) 

317 except HTTPException as e: 

318 self.request.routing_exception = e 

319 self.flashes: list[tuple[str, str]] | None = None 

320 self.session: SessionMixin | None = session 

321 # Functions that should be executed after the request on the response 

322 # object. These will be called before the regular "after_request" 

323 # functions. 

324 self._after_request_functions: list[ft.AfterRequestCallable] = [] 

325 

326 self._cv_tokens: list[tuple[contextvars.Token, AppContext | None]] = [] 

327 

328 def copy(self) -> RequestContext: 

329 """Creates a copy of this request context with the same request object. 

330 This can be used to move a request context to a different greenlet. 

331 Because the actual request object is the same this cannot be used to 

332 move a request context to a different thread unless access to the 

333 request object is locked. 

334 

335 .. versionadded:: 0.10 

336 

337 .. versionchanged:: 1.1 

338 The current session object is used instead of reloading the original 

339 data. This prevents `flask.session` pointing to an out-of-date object. 

340 """ 

341 return self.__class__( 

342 self.app, 

343 environ=self.request.environ, 

344 request=self.request, 

345 session=self.session, 

346 ) 

347 

348 def match_request(self) -> None: 

349 """Can be overridden by a subclass to hook into the matching 

350 of the request. 

351 """ 

352 try: 

353 result = self.url_adapter.match(return_rule=True) # type: ignore 

354 self.request.url_rule, self.request.view_args = result # type: ignore 

355 except HTTPException as e: 

356 self.request.routing_exception = e 

357 

358 def push(self) -> None: 

359 # Before we push the request context we have to ensure that there 

360 # is an application context. 

361 app_ctx = _cv_app.get(None) 

362 

363 if app_ctx is None or app_ctx.app is not self.app: 

364 app_ctx = self.app.app_context() 

365 app_ctx.push() 

366 else: 

367 app_ctx = None 

368 

369 self._cv_tokens.append((_cv_request.set(self), app_ctx)) 

370 

371 # Open the session at the moment that the request context is available. 

372 # This allows a custom open_session method to use the request context. 

373 # Only open a new session if this is the first time the request was 

374 # pushed, otherwise stream_with_context loses the session. 

375 if self.session is None: 

376 session_interface = self.app.session_interface 

377 self.session = session_interface.open_session(self.app, self.request) 

378 

379 if self.session is None: 

380 self.session = session_interface.make_null_session(self.app) 

381 

382 # Match the request URL after loading the session, so that the 

383 # session is available in custom URL converters. 

384 if self.url_adapter is not None: 

385 self.match_request() 

386 

387 def pop(self, exc: BaseException | None = _sentinel) -> None: # type: ignore 

388 """Pops the request context and unbinds it by doing that. This will 

389 also trigger the execution of functions registered by the 

390 :meth:`~flask.Flask.teardown_request` decorator. 

391 

392 .. versionchanged:: 0.9 

393 Added the `exc` argument. 

394 """ 

395 clear_request = len(self._cv_tokens) == 1 

396 

397 try: 

398 if clear_request: 

399 if exc is _sentinel: 

400 exc = sys.exc_info()[1] 

401 self.app.do_teardown_request(exc) 

402 

403 request_close = getattr(self.request, "close", None) 

404 if request_close is not None: 

405 request_close() 

406 finally: 

407 ctx = _cv_request.get() 

408 token, app_ctx = self._cv_tokens.pop() 

409 _cv_request.reset(token) 

410 

411 # get rid of circular dependencies at the end of the request 

412 # so that we don't require the GC to be active. 

413 if clear_request: 

414 ctx.request.environ["werkzeug.request"] = None 

415 

416 if app_ctx is not None: 

417 app_ctx.pop(exc) 

418 

419 if ctx is not self: 

420 raise AssertionError( 

421 f"Popped wrong request context. ({ctx!r} instead of {self!r})" 

422 ) 

423 

424 def __enter__(self) -> RequestContext: 

425 self.push() 

426 return self 

427 

428 def __exit__( 

429 self, 

430 exc_type: type | None, 

431 exc_value: BaseException | None, 

432 tb: TracebackType | None, 

433 ) -> None: 

434 self.pop(exc_value) 

435 

436 def __repr__(self) -> str: 

437 return ( 

438 f"<{type(self).__name__} {self.request.url!r}" 

439 f" [{self.request.method}] of {self.app.name}>" 

440 )