# Python : Pallets : Flask \[ [pypi](https://pypi.org/project/Flask/) | [src](https://github.com/pallets/flask/) | [docs](https://flask.palletsprojects.com/en/3.0.x/) | [api](https://flask.palletsprojects.com/en/3.0.x/api/) ] The core is a thin wrapper around [[Werkzeug]] – for example, Flask’s `Request` and `Response` classes subclass from Werkzeug while adding/changing very little. It also makes use of [[Click]], [[Jinja]], and [[MarkupSafe]]. ###### Hello World ```python app = Flask( __name__ ) @app.route( "/<name>" ) def hello( name ): return f"Hello { markupsafe.escape( name ) }!"" ``` ```bash flask [--app <app>] run [--debug] # can omit --app if file is "app.py" or "wsgi.py" * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) ``` ###### Imports ```python from flask import... Flask, # app current_app, g, request, session, # context globals abort, has_app_context, has_request_context, jsonify, # utilities make_response, redirect, render_template, send_file, url_for from flask.views import... View, MethodView # view classes ``` ###### Globals - look like globals, but are unique to your context (based on Werkzeug’s [Context Locals](https://werkzeug.palletsprojects.com/en/latest/local/) which are based on Python’s [contextvars](https://docs.python.org/3/library/contextvars.html)) - require app and/or request contexts to have been “pushed” ```python current_app # currently active app request # currently active request g # namespace object for storing data for the life of the app context # (good place to stash a User object or DB session) session # currently active session # utilities abort( <code>, description="..." ) # raise HTTPException for given status code has_app_context() # bool (whether an app context is currently "pushed") has_request_context() # bool (whether a request context is currently "pushed") redirect( location, code=<code> ) # create redirect response object render_template() send_file() ``` #### App API ```python app.config # subclass of dict app.url_for( <name>, **values ) app.url_map ``` ###### Modes Two ways to set, both referencing a shared value. ```python app.config[ "DEBUG" ] -or- app.debug # reloader, interactive debugger app.config[ "TESTING" ] -or- app.testing # exceptions propogated ``` #### Routes ```python "/" "/<name>" # variable (passed to view function as args) "/<int:id>" # variable with converter; converters: string (default) accepts any text without a slash int accepts positive integers float accepts positive floating point values path like string but also accepts slashes uuid accepts UUID strings # re: Trailing Slashes "/foo/" # "/foo" will redirect to "/foo/" "/foo" # "/foo/" will produce a 404 # By default, a route only answers to GET requests. # GET gives you HEAD and OPTIONS for free. @app.route( "/login", methods=[ "GET", "POST" ] ) @app.get( "/login" ) @app.post( "/login" ) # alternative to decorators app.add_url_rule( "/", view_func=home ) app.add_url_rule( "/other", view_func=other ) ``` ###### Reversing ```python url_for( "<func_name>", var1=, ..., qparam1=, ... ) url_for( ".<func_name>" ) # reference view in same Blueprint url_for( "<blueprint>.<func_name>" ) # reference view in different Blueprint Examples: url_for( "index" ) -> "/" url_for( "login", next="/" ) -> "/login?next=/" ``` #### Request & Response See [[Werkzeug]] for Request and Response APIs. ###### Return Values Normally you return data and let Flask turn it into a Response object. If you want to do that yourself: ```python response = make_response( content, code ) # takes same kinds of args you can return from a View ``` Otherwise: - string -> treated as response body with 200 code and “text/html” mimetype - dict/list -> `jsonify()` called to produce a response - iterator/generator returning strings or bytes -> treated as a streaming response - tuples -> ```python ( response, status ) ( response, headers ) ( response, status, headers ) ``` - otherwise, assumes return value is a valid WSGI app #### View Classes ###### View ```python class MyView( View ): init_every_request = False # default is True, which creates new instance on each request decorators = [ ... ] # equiv of dec2( dec1( view ) ) ) methods = [ "GET", ... ] def __init__( self, ...iargs... ): ... def dispatch_request( self, ...pargs... ): ... app.add_url_rule( "/foo/", view_func=MyView.as_view( "foo", ...iargs... ) ) ``` - iargs -> instantiation args - pargs -> path args ###### MethodView ```python MethodView < View class MyView( MethodView ): def __init__( self, ...iargs... ): ... def <method>( self, ... ): ... ``` #### Blueprints Flask and Blueprint both inherit from Scaffold, which contains the route methods. ```python foo = Blueprint( "foo", __name__, ...options... ) @foo.route( ... ) def whatever( ... ): ... app.register_blueprint( foo, ...options... ) subdomain = "..." url_defaults = { ... } url_prefix = "..." ``` #### Misc ###### Event Handlers ```python @app.before_request # for setup such as establishing a DB conn @app.after_request # for modifying responses @app.teardown_request # for cleanup and error-handling ``` ###### Logging ```python app.logger.debug( "A value for debugging" ) app.logger.warning( "A warning occurred (%d apples)", 42 ) app.logger.error( "An error occurred' ) ``` ###### Sessions ```python app.secret_key = b"_5#y2L"F4Q8z\n\xec]/" # python -c 'import secrets; print(secrets.token_hex())' if "username" in session: return f"Logged in as { session[ "username" ] }" session[ "username" ] = request.form[ "username" ] session.pop( "username", None ) ``` ###### Static Files For support during development: - create a folder called `static` in your package; it will be available at `/static` - to generate URLs, use the special `static` name: `url_for( "static", filename="style.css" )` ###### WSGI Middleware ```python app.wsgi_app = MyWSGIMiddleware( app.wsgi_app ) ``` ## Testing #### Classes ```python flask.testing.EnvironBuilder < werkzeug.test.EnvironBuilder flask.testing.FlaskClient < werkzeug.test.Client werkzeug.test.TestResponse ``` #### Test Client ```python client = app.test_client() response = client.<method>( query_string={ "key": "value", ... } headers={} data= # body for POST, PUT json={} # Content-Type header will be set to application/json automatically follow_redirects=True ) response.data # bytes response.text # string response.json response.history # if following redirects ``` ###### Body Input Types - bytes - dict -> `Content-Type` will be `multipart/form-data` or `application/x-www-form-urlencoded` - open file #### Fixtures Assuming [[Tech/Python/Testing/pytest/Overview]]. ###### Simplest Version ```python @pytest.fixture( name="app", scope="session" ) def fixture__app(): return create_app() # your factory function @pytest.fixture( name="client" ) def fixture__client( app ): return app.test_client() ``` ###### With App Setup and Teardown ```python @pytest.fixture( name="app", scope="session" ) def fixture__app(): app = create_app() # your factory function ... # setup yield app ... # teardown ```