What Flask is
TipFlask has no database layer, forms or user system. You choose and add extensions yourself.
Asked in interviews
AskedWhat is Flask, and why is it called a microframework?request, session, current_app and g look global, but they are context locals. Each worker sees only its own request.create_app() factory, put features in blueprints and bind extensions with init_app().TipFlask has no database layer, forms or user system. You choose and add extensions yourself.
Asked in interviews
AskedWhat is Flask, and why is it called a microframework?flask --app hello run --debug__name__The import name. Flask uses it to find templates and static.Tip__name__ is '__main__' when you run the file directly, and the module name when it is imported.
A request pushes the app context, then the request context, and pops them in reverse. A script pushes the app context alone.
with app.app_context(): request looks globalIt's a proxy built on contextvars.TrapWith no active context, these names raise RuntimeError. A thread you start can't read the request thread's request.
Asked in interviews
AskedExplain the Flask application context and request context.app(environ, start_response)The Python standard for how a web server calls an app.@app.before_request Return a value to skip the view.@app.after_request It must return the response.@app.teardown_request @app.teardown_appcontext app.wsgi_app = Middleware(app.wsgi_app) TrapIf an after_request function raises, Flask skips the remaining ones. Cleanup belongs in teardown.
methods=['GET', 'POST']request.method == 'POST'@app.post('/users') class Users(MethodView): app.add_url_rule('/x', 'x', view)Do it during setup. In a loop, write the view as lambda e=e:.TrapA route with no methods answers only GET, plus HEAD and OPTIONS. Other methods get 405.
/post/<int:post_id> Others are string, float, uuid, and path for slashes.url_for('user', name='j')Extra arguments become query parameters.Trap/post/abc doesn't fit <int:post_id>, so it returns 404 and never reaches the view.
request.args.get('page', 1, type=int)request.args.getlist('tag') request.form.get('email', '')request.get_json() A non-JSON request gets 415, or None with silent=True.request.headers.get('X-Id') request.cookies.get('sid') Traprequest. raises KeyError for a missing key, and Flask turns that into 400. Prefer get().
return {'ok': True}, 201jsonify(data)redirect(url_for('index')) abort(404)send_file(path, as_attachment=True) resp = make_response(body) resp.set_cookie('k', 'v')TrapFlask raises a TypeError if a view returns None or ends without a return.
f = request.files['file'] secure_filename(f.filename) The client controls the name.MAX_CONTENT_LENGTH Larger uploads get 413.TrapThe form must use enctype=, or the browser sends no file at all.
Asked in interviews
AskedHow do you handle file uploads in Flask?render_template('u.html', user=u)@app.context_processor Flask already adds request, session, g and config.TrapNever build a render_ template from user input. That is server-side template injection.
{% if user %}{% macro field(name) %}{{ name|upper }}@app.template_filter('money') {% block content %}{% extends "base.html" %}{{ super() }}TrapAutoescaping prevents most cross-site scripting (XSS), not all. |safe and unquoted attributes can still let a script through.
GET renders the form, and POST reads request.form.redirect(url_for('thanks')) This is Post/Redirect/Get.flash('Saved', 'success')get_flashed_messages() Trapflash() uses the session, so it needs a SECRET_KEY. A message too large for the cookie fails silently.
class Login(FlaskForm): form.validate_on_submit() def validate_email(self, f):raise ValidationError('Taken') CSRFProtect(app)A missing or wrong token gets 400.{{ form.hidden_tag() }}JavaScript sends the X-CSRFToken header.TrapA uniqueness check still needs a unique constraint. Two requests can both pass before either saves.
session['user'] = namesession.get('user') session.pop('user', None)SESSION_TYPE = 'redis'The cookie holds only an ID, and all servers share the store.TrapThe cookie is signed with secret_key, not encrypted. Users can read it but can't change it, so never put secrets in it.
@login_manager.user_loader login_user(user) logout_user()@login_requiredgenerate_password_hash(pw) check_password_hash(hash, pw)TipFlask-Login doesn't register users or store passwords. Your login view checks the hash.
TrapNever use a fast hash such as SHA-256. The default is salted scrypt, and String(128) is too short for its result.
jwt.decode(t, key, algorithms=['HS256']) A bad token gets 401.@require_role('admin') Your decorator returns 403.TrapPass a fixed algorithms list to jwt.decode(), never one read from the token.
Asked in interviews
AskedHow do you handle API authentication and authorization?db, then map a tableSQLALCHEMY_DATABASE_URI db.init_app(app) class User(db.Model):db.create_all()It needs an app context.Trapdb.create_all() never alters existing tables. Schema changes go through migrations.
Asked in interviews
AskedHow do you integrate databases with Flask?db.session.get(User, 1)It replaced Query.get().User.query.filter_by(name='j').first() q = db.select(User).filter_by(name='j') db.session.execute(q).scalar_one() User.query.paginate(page=1, per_page=20)Tipfilter_by() takes keyword equality checks, and filter() takes full expressions.
Asked in interviews
AskedHow do you perform database queries in Flask-SQLAlchemy?db.ForeignKey('user.id') db.relationship('Post') backref='author' back_populates='author' secondary=tagsuselist=FalseTrapRelationships load lazily, so a loop over posts runs a query for each one. This is the N+1 query problem, and selectinload() avoids it.
Asked in interviews
AskedHow do you define relationships between models?flask db initflask db migrate -m "msg"flask db upgradeflask db downgradeTrapAlembic, which Flask-Migrate runs, can't detect renames. A rename shows up as a dropped column and a new one, so review each script.
Asked in interviews
AskedWhat are database migrations and how do you handle them?db.session.commit() db.session.rollback() with_for_update=True Lock rows in a fixed order to stop deadlocks.'pool_size': 10'max_overflow': 20Set these in SQLALCHEMY_ENGINE_OPTIONS .'pool_recycle': 3600'pool_pre_ping': TrueTrapThe pool is per process. Four Gunicorn workers at 10 plus 20 can open 120 connections, so stay under max_connections.
def create_app(cfg): db.init_app(app) app.register_blueprint(bp) db in its own module. Reach the app through current_app.app/ package with a folder per blueprint.Blueprint('auth', __name__, url_prefix='/auth') url_for('auth.login') TipA blueprint isn't an app. It records operations and replays them at registration.
FLASK_ variablesapp.config.from_object(cfg) app.config.from_prefixed_env() flask command readsFLASK_APP=helloFLASK_DEBUG=1.env under another serverload_dotenv()FLASK_ENV=development TrapA 'dev-secret-key' fallback in the base class also reaches production. Check for SECRET_KEY at startup.
return jsonify(obj), 201return '', 204400 with the field errors.url_prefix='/api/v1' Accept: application/vnd.app.v2+json user.to_dict()UserSchema().dump(user) DefaultJSONProvider and assign it to app.json.Trapjsonify() on a model object raises TypeError. The default provider also writes dates in HTTP format.
@app.errorhandler(404) return page, 404raise APIError('No user', 404)@app.errorhandler(APIError) RotatingFileHandler('logs/app.log') Set maxBytes and backupCount.logging.config.dictConfig(cfg) TrapA handler for base Exception also catches errors such as 405, so a client mistake becomes a 500 page.
app = create_app('testing') app.test_client() client.post('/api', json=data)response.get_json() requestwith app.test_request_context(): 400, 401, 403, 404 and the shape of the JSON.TipTESTING = True makes exceptions reach the test instead of becoming 500 pages.
@patch('app.routes.send_email') mock.assert_called_once() mock.return_value = Truemock.side_effect = TimeoutErrorTrapPatch where the name is looked up, not where it is defined.
Asked in interviews
AskedHow do you mock dependencies in Flask tests?|safe or Markup() on user data.Talisman(app, force_https=True) SESSION_COOKIE_SECURE=True SESSION_COOKIE_HTTPONLY=True SESSION_COOKIE_SAMESITE='Lax' TrapDebug mode in production lets the Werkzeug debugger run Python from the browser. Only a PIN guards it.
Limiter(get_remote_address, app=app)@limiter.limit("5 per minute")Over the limit, a client gets 429.TrapThe default in-memory storage is only for development, because each worker keeps its own counts. Set storage_uri to Redis.
Asked in interviews
AskedHow do you implement rate limiting in Flask applications?gunicorn -w 4 app:appStart at about two workers per CPU core. Use Waitress on Windows.TrapBehind a proxy, wrap the app in ProxyFix, or Flask sees the proxy's address and scheme, not the client's.
Asked in interviews
AskedHow do you deploy Flask applications?@cache.cached(timeout=300) @cache.memoize(timeout=600) cache.delete_memoized(fn, id)cache.clear()TrapNever cache a per-user view under a shared key, or one user may see another user's page.
Asked in interviews
AskedHow do you implement caching in Flask?send_email_task.delay(recipient, subject, body)A broker carries the message to a worker.app.app_context() .async def dashboard():The request still holds one worker. A mostly async app fits Quart better.TrapA thread started in a view lives only in process memory, and Flask cancels asyncio. tasks when the view returns.
@socketio.on('join') emit('m', d, room=room)emit('m', d, broadcast=True)SocketIO(app, message_queue=url) TrapGunicorn can run only one worker process for Socket.IO. Scale with several processes and sticky sessions.
Asked in interviews
AskedHow do you implement WebSocket support in Flask?Nothing matches. Try a shorter word, or clear the search.
With Pro, you can:
Already have Pro? Sign in