diff --git a/docs/api.rst b/docs/api.rst index 41a6f355..ec8ba9dc 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -58,12 +58,12 @@ Incoming Request Data the following: ============= ====================================================== - `path` ``u'/π/page.html'`` - `full_path` ``u'/π/page.html?x=y'`` - `script_root` ``u'/myapplication'`` - `base_url` ``u'http://www.example.com/myapplication/π/page.html'`` - `url` ``u'http://www.example.com/myapplication/π/page.html?x=y'`` - `url_root` ``u'http://www.example.com/myapplication/'`` + `path` ``'/π/page.html'`` + `full_path` ``'/π/page.html?x=y'`` + `script_root` ``'/myapplication'`` + `base_url` ``'http://www.example.com/myapplication/π/page.html'`` + `url` ``'http://www.example.com/myapplication/π/page.html?x=y'`` + `url_root` ``'http://www.example.com/myapplication/'`` ============= ====================================================== diff --git a/docs/config.rst b/docs/config.rst index f76f4175..d93fc366 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -596,8 +596,8 @@ your configuration classes:: DB_SERVER = '192.168.1.56' @property - def DATABASE_URI(self): # Note: all caps - return 'mysql://user@{}/foo'.format(self.DB_SERVER) + def DATABASE_URI(self): # Note: all caps + return f"mysql://user@{self.DB_SERVER}/foo" class ProductionConfig(Config): """Uses production database server.""" diff --git a/docs/patterns/flashing.rst b/docs/patterns/flashing.rst index a61c719f..4c8a7870 100644 --- a/docs/patterns/flashing.rst +++ b/docs/patterns/flashing.rst @@ -103,7 +103,7 @@ error messages could be displayed with a red background. To flash a message with a different category, just use the second argument to the :func:`~flask.flash` function:: - flash(u'Invalid password provided', 'error') + flash('Invalid password provided', 'error') Inside the template you then have to tell the :func:`~flask.get_flashed_messages` function to also return the diff --git a/docs/patterns/lazyloading.rst b/docs/patterns/lazyloading.rst index 27182a84..658a1cd4 100644 --- a/docs/patterns/lazyloading.rst +++ b/docs/patterns/lazyloading.rst @@ -93,7 +93,7 @@ write this by having a function that calls into name and a dot, and by wrapping `view_func` in a `LazyView` as needed. :: def url(import_name, url_rules=[], **options): - view = LazyView('yourapplication.' + import_name) + view = LazyView(f"yourapplication.{import_name}") for url_rule in url_rules: app.add_url_rule(url_rule, view_func=view, **options) diff --git a/docs/patterns/requestchecksum.rst b/docs/patterns/requestchecksum.rst index 902be64a..25bc38b2 100644 --- a/docs/patterns/requestchecksum.rst +++ b/docs/patterns/requestchecksum.rst @@ -52,4 +52,4 @@ Example usage:: files = request.files # At this point the hash is fully constructed. checksum = hash.hexdigest() - return 'Hash was: %s' % checksum + return f"Hash was: {checksum}" diff --git a/docs/patterns/sqlalchemy.rst b/docs/patterns/sqlalchemy.rst index 1089b61d..f240e5da 100644 --- a/docs/patterns/sqlalchemy.rst +++ b/docs/patterns/sqlalchemy.rst @@ -104,9 +104,9 @@ You can insert entries into the database like this: Querying is simple as well: >>> User.query.all() -[] +[] >>> User.query.filter(User.name == 'admin').first() - + .. _SQLAlchemy: https://www.sqlalchemy.org/ .. _declarative: @@ -200,19 +200,19 @@ SQLAlchemy will automatically commit for us. To query your database, you use the engine directly or use a connection: >>> users.select(users.c.id == 1).execute().first() -(1, u'admin', u'admin@localhost') +(1, 'admin', 'admin@localhost') These results are also dict-like tuples: >>> r = users.select(users.c.id == 1).execute().first() >>> r['name'] -u'admin' +'admin' You can also pass strings of SQL statements to the :meth:`~sqlalchemy.engine.base.Connection.execute` method: >>> engine.execute('select * from users where id = :1', [1]).first() -(1, u'admin', u'admin@localhost') +(1, 'admin', 'admin@localhost') For more information about SQLAlchemy, head over to the `website `_. diff --git a/docs/patterns/streaming.rst b/docs/patterns/streaming.rst index f5bff3ca..38b493f1 100644 --- a/docs/patterns/streaming.rst +++ b/docs/patterns/streaming.rst @@ -21,7 +21,7 @@ data and to then invoke that function and pass it to a response object:: def generate_large_csv(): def generate(): for row in iter_all_rows(): - yield ','.join(row) + '\n' + yield f"{','.join(row)}\n" return Response(generate(), mimetype='text/csv') Each ``yield`` expression is directly sent to the browser. Note though diff --git a/docs/patterns/viewdecorators.rst b/docs/patterns/viewdecorators.rst index 2fc04bf2..ff88fceb 100644 --- a/docs/patterns/viewdecorators.rst +++ b/docs/patterns/viewdecorators.rst @@ -142,8 +142,7 @@ Here is the code for that decorator:: def decorated_function(*args, **kwargs): template_name = template if template_name is None: - template_name = request.endpoint \ - .replace('.', '/') + '.html' + template_name = f"'{request.endpoint.replace('.', '/')}.html'" ctx = f(*args, **kwargs) if ctx is None: ctx = {} diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 8db25585..eb699886 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -447,11 +447,11 @@ Here is a basic introduction to how the :class:`~markupsafe.Markup` class works: >>> from markupsafe import Markup >>> Markup('Hello %s!') % 'hacker' - Markup(u'Hello <blink>hacker</blink>!') + Markup('Hello <blink>hacker</blink>!') >>> Markup.escape('hacker') - Markup(u'<blink>hacker</blink>') + Markup('<blink>hacker</blink>') >>> Markup('Marked up » HTML').striptags() - u'Marked up \xbb HTML' + 'Marked up \xbb HTML' .. versionchanged:: 0.5 @@ -609,8 +609,8 @@ Werkzeug provides for you:: @app.route('/upload', methods=['GET', 'POST']) def upload_file(): if request.method == 'POST': - f = request.files['the_file'] - f.save('/var/www/uploads/' + secure_filename(f.filename)) + file = request.files['the_file'] + file.save(f"/var/www/uploads/{secure_filename(f.filename)}") ... For some better examples, checkout the :ref:`uploading-files` pattern. diff --git a/docs/templating.rst b/docs/templating.rst index 0d558c8c..5ae4fd72 100644 --- a/docs/templating.rst +++ b/docs/templating.rst @@ -222,8 +222,8 @@ functions):: @app.context_processor def utility_processor(): - def format_price(amount, currency=u'€'): - return u'{0:.2f}{1}'.format(amount, currency) + def format_price(amount, currency="€"): + return f"{amount:.2f}{currency}" return dict(format_price=format_price) The context processor above makes the `format_price` function available to all diff --git a/docs/testing.rst b/docs/testing.rst index 20064e5a..881e73bb 100644 --- a/docs/testing.rst +++ b/docs/testing.rst @@ -165,16 +165,19 @@ invalid credentials. Add this new test function:: def test_login_logout(client): """Make sure login and logout works.""" - rv = login(client, flaskr.app.config['USERNAME'], flaskr.app.config['PASSWORD']) + username = flaskr.app.config["USERNAME"] + password = flaskr.app.config["PASSWORD"] + + rv = login(client, username, password) assert b'You were logged in' in rv.data rv = logout(client) assert b'You were logged out' in rv.data - rv = login(client, flaskr.app.config['USERNAME'] + 'x', flaskr.app.config['PASSWORD']) + rv = login(client, f"{username}x", password) assert b'Invalid username' in rv.data - rv = login(client, flaskr.app.config['USERNAME'], flaskr.app.config['PASSWORD'] + 'x') + rv = login(client, username, f'{password}x') assert b'Invalid password' in rv.data Test Adding Messages diff --git a/docs/tutorial/blog.rst b/docs/tutorial/blog.rst index 82dcc957..b06329ea 100644 --- a/docs/tutorial/blog.rst +++ b/docs/tutorial/blog.rst @@ -207,7 +207,7 @@ it from each view. ).fetchone() if post is None: - abort(404, "Post id {0} doesn't exist.".format(id)) + abort(404, f"Post id {id} doesn't exist.") if check_author and post['author_id'] != g.user['id']: abort(403) diff --git a/docs/tutorial/views.rst b/docs/tutorial/views.rst index 86689111..2d5a01d3 100644 --- a/docs/tutorial/views.rst +++ b/docs/tutorial/views.rst @@ -94,7 +94,7 @@ write templates to generate the HTML form. elif db.execute( 'SELECT id FROM user WHERE username = ?', (username,) ).fetchone() is not None: - error = 'User {} is already registered.'.format(username) + error = f"User {username} is already registered." if error is None: db.execute( diff --git a/src/flask/app.py b/src/flask/app.py index 4ce78055..d4a0ac05 100644 --- a/src/flask/app.py +++ b/src/flask/app.py @@ -585,7 +585,7 @@ class Flask(_PackageBoundObject): bool(static_host) == host_matching ), "Invalid static_host/host_matching combination" self.add_url_rule( - self.static_url_path + "/", + f"{self.static_url_path}/", endpoint="static", host=static_host, view_func=self.send_static_file, @@ -711,7 +711,7 @@ class Flask(_PackageBoundObject): 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", self.name + "-instance") + return os.path.join(prefix, "var", f"{self.name}-instance") def open_instance_resource(self, resource, mode="rb"): """Opens a resource from the application's instance folder @@ -1084,10 +1084,11 @@ class Flask(_PackageBoundObject): 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) + "A name collision occurred between blueprints" + f" {blueprint!r} and {self.blueprints[blueprint.name]!r}." + f" Both share the same name {blueprint.name!r}." + f" Blueprints that are created on the fly need unique" + f" names." ) else: self.blueprints[blueprint.name] = blueprint @@ -1209,8 +1210,8 @@ class Flask(_PackageBoundObject): 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 endpoint function: %s" % endpoint + "View function mapping is overwriting an existing" + f" endpoint function: {endpoint}" ) self.view_functions[endpoint] = view_func @@ -1341,17 +1342,18 @@ class Flask(_PackageBoundObject): """ if isinstance(code_or_exception, HTTPException): # old broken behavior raise ValueError( - "Tried to register a handler for an exception instance {!r}." - " Handlers can only be registered for exception classes or" - " HTTP error codes.".format(code_or_exception) + "Tried to register a handler for an exception instance" + f" {code_or_exception!r}. Handlers can only be" + " registered for exception classes or HTTP error codes." ) try: exc_class, code = self._get_exc_class_and_code(code_or_exception) except KeyError: raise KeyError( - "'{}' is not a recognized HTTP error code. Use a subclass of" - " HTTPException with that code instead.".format(code_or_exception) + f"'{code_or_exception}' is not a recognized HTTP error" + " code. Use a subclass of HTTPException with that code" + " instead." ) handlers = self.error_handler_spec.setdefault(key, {}).setdefault(code, {}) @@ -1730,7 +1732,7 @@ class Flask(_PackageBoundObject): # 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) + e.description = f"KeyError: {e.args[0]!r}" elif not hasattr(BadRequestKeyError, "show_exception"): e.args = () @@ -2006,9 +2008,9 @@ class Flask(_PackageBoundObject): # the body must not be None if rv is None: raise TypeError( - 'The view function for "{}" did not return a valid response. The' - " function either returned None or ended without a return" - " statement.".format(request.endpoint) + 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." ) # make sure the body is an instance of the response class @@ -2028,17 +2030,17 @@ class Flask(_PackageBoundObject): rv = self.response_class.force_type(rv, request.environ) except TypeError as e: raise 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) + 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]) 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 callable, but it was a" - " {rv.__class__.__name__}.".format(rv=rv) + " response. The return type must be a string," + " dict, tuple, Response instance, or WSGI" + f" callable, but it was a {type(rv).__name__}." ) # prefer the status if it was provided @@ -2375,4 +2377,4 @@ class Flask(_PackageBoundObject): return self.wsgi_app(environ, start_response) def __repr__(self): - return f"<{self.__class__.__name__} {self.name!r}>" + return f"<{type(self).__name__} {self.name!r}>" diff --git a/src/flask/blueprints.py b/src/flask/blueprints.py index b43ef31a..2f07deda 100644 --- a/src/flask/blueprints.py +++ b/src/flask/blueprints.py @@ -246,7 +246,7 @@ class Blueprint(_PackageBoundObject): if self.has_static_folder: state.add_url_rule( - self.static_url_path + "/", + f"{self.static_url_path}/", view_func=self.send_static_file, endpoint="static", ) diff --git a/src/flask/cli.py b/src/flask/cli.py index 6ebff1d0..90742456 100644 --- a/src/flask/cli.py +++ b/src/flask/cli.py @@ -62,13 +62,13 @@ def find_best_app(script_info, module): return matches[0] elif len(matches) > 1: raise NoAppException( - 'Detected multiple Flask applications in module "{module}". Use ' - '"FLASK_APP={module}:name" to specify the correct ' - "one.".format(module=module.__name__) + "Detected multiple Flask applications in module" + f" {module.__name__!r}. Use 'FLASK_APP={module.__name__}:name'" + f" to specify the correct one." ) # Search for app factory functions. - for attr_name in ("create_app", "make_app"): + for attr_name in {"create_app", "make_app"}: app_factory = getattr(module, attr_name, None) if inspect.isfunction(app_factory): @@ -81,15 +81,16 @@ def find_best_app(script_info, module): if not _called_with_wrong_args(app_factory): raise raise NoAppException( - '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__) + 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." ) raise NoAppException( - 'Failed to find Flask application or factory in module "{module}". ' - 'Use "FLASK_APP={module}:name to specify one.'.format(module=module.__name__) + "Failed to find Flask application or factory in module" + f" {module.__name__!r}. Use 'FLASK_APP={module.__name__}:name'" + " to specify one." ) @@ -150,8 +151,7 @@ def find_app_by_string(script_info, module, app_name): if not match: raise NoAppException( - '"{name}" is not a valid variable name or function ' - "expression.".format(name=app_name) + f"{app_name!r} is not a valid variable name or function expression." ) name, args = match.groups() @@ -165,11 +165,8 @@ def find_app_by_string(script_info, module, app_name): if args: try: args = ast.literal_eval(f"({args},)") - except (ValueError, SyntaxError) as e: - raise NoAppException( - "Could not parse the arguments in " - '"{app_name}".'.format(e=e, app_name=app_name) - ) + except (ValueError, SyntaxError): + raise NoAppException(f"Could not parse the arguments in {app_name!r}.") else: args = () @@ -180,10 +177,9 @@ def find_app_by_string(script_info, module, app_name): raise raise NoAppException( - '{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__ - ) + f"{e}\nThe factory {app_name!r} in module" + f" {module.__name__!r} could not be called with the" + " specified arguments." ) else: app = attr @@ -192,8 +188,8 @@ def find_app_by_string(script_info, module, app_name): return app raise NoAppException( - "A valid Flask application was not obtained from " - '"{module}:{app_name}".'.format(module=module.__name__, app_name=app_name) + "A valid Flask application was not obtained from" + f" '{module.__name__}:{app_name}'." ) @@ -236,11 +232,11 @@ def locate_app(script_info, module_name, app_name, raise_if_not_found=True): # Determine this by checking whether the trace has a depth > 1. if sys.exc_info()[2].tb_next: raise NoAppException( - 'While importing "{name}", an ImportError was raised:' - "\n\n{tb}".format(name=module_name, tb=traceback.format_exc()) + f"While importing {module_name!r}, an ImportError was" + f" raised:\n\n{traceback.format_exc()}" ) elif raise_if_not_found: - raise NoAppException(f'Could not import "{module_name}".') + raise NoAppException(f"Could not import {module_name!r}.") else: return @@ -259,14 +255,10 @@ def get_version(ctx, param, value): import werkzeug from . import __version__ - message = "Python %(python)s\nFlask %(flask)s\nWerkzeug %(werkzeug)s" click.echo( - message - % { - "python": platform.python_version(), - "flask": __version__, - "werkzeug": werkzeug.__version__, - }, + f"Python {platform.python_version()}\n" + f"Flask {__version__}\n" + f"Werkzeug {werkzeug.__version__}", color=ctx.color, ) ctx.exit() @@ -659,7 +651,7 @@ 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}"' + message = f" * Serving Flask app {app_import_path!r}" if not eager_loading: message += " (lazy loading)" @@ -670,14 +662,14 @@ def show_server_banner(env, debug, app_import_path, eager_loading): 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(" * Debug mode: {}".format("on" if debug else "off")) + click.echo(f" * Debug mode: {'on' if debug else 'off'}") class CertParamType(click.ParamType): @@ -809,7 +801,7 @@ class SeparatedPathType(click.Path): type=SeparatedPathType(), help=( "Extra files that trigger a reload on change. Multiple paths" - " are separated by '{}'.".format(os.path.pathsep) + f" are separated by {os.path.pathsep!r}." ), ) @pass_script_info @@ -863,8 +855,10 @@ def shell_command(): from .globals import _app_ctx_stack app = _app_ctx_stack.top.app - banner = "Python {} on {}\nApp: {} [{}]\nInstance: {}".format( - sys.version, sys.platform, app.import_name, app.env, app.instance_path, + banner = ( + f"Python {sys.version} on {sys.platform}\n" + f"App: {app.import_name} [{app.env}]\n" + f"Instance: {app.instance_path}" ) ctx = {} diff --git a/src/flask/config.py b/src/flask/config.py index 7e18b37f..8515a2d4 100644 --- a/src/flask/config.py +++ b/src/flask/config.py @@ -98,10 +98,10 @@ class Config(dict): if silent: return False raise RuntimeError( - "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 + 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" ) return self.from_pyfile(rv, silent=silent) @@ -128,7 +128,7 @@ class Config(dict): except OSError as e: if silent and e.errno in (errno.ENOENT, errno.EISDIR, errno.ENOTDIR): return False - e.strerror = "Unable to load configuration file (%s)" % e.strerror + e.strerror = f"Unable to load configuration file ({e.strerror})" raise self.from_object(d) return True @@ -200,7 +200,7 @@ class Config(dict): if silent and e.errno in (errno.ENOENT, errno.EISDIR): return False - e.strerror = "Unable to load configuration file (%s)" % e.strerror + e.strerror = f"Unable to load configuration file ({e.strerror})" raise return self.from_mapping(obj) @@ -219,7 +219,7 @@ class Config(dict): mappings.append(mapping[0]) elif len(mapping) > 1: raise TypeError( - "expected at most 1 positional argument, got %d" % len(mapping) + f"expected at most 1 positional argument, got {len(mapping)}" ) mappings.append(kwargs.items()) for mapping in mappings: @@ -270,4 +270,4 @@ class Config(dict): return rv def __repr__(self): - return "<{} {}>".format(self.__class__.__name__, dict.__repr__(self)) + return f"<{type(self).__name__} {dict.__repr__(self)}>" diff --git a/src/flask/ctx.py b/src/flask/ctx.py index 9c077f8c..42ae707d 100644 --- a/src/flask/ctx.py +++ b/src/flask/ctx.py @@ -88,7 +88,7 @@ class _AppCtxGlobals: def __repr__(self): top = _app_ctx_stack.top if top is not None: - return "" % top.app.name + return f"" return object.__repr__(self) @@ -445,9 +445,7 @@ class RequestContext: self.auto_pop(exc_value) def __repr__(self): - return "<{} '{}' [{}] of {}>".format( - self.__class__.__name__, - self.request.url, - self.request.method, - self.app.name, + return ( + f"<{type(self).__name__} {self.request.url!r}" + f" [{self.request.method}] of {self.app.name}>" ) diff --git a/src/flask/debughelpers.py b/src/flask/debughelpers.py index ae7ead1d..54b11957 100644 --- a/src/flask/debughelpers.py +++ b/src/flask/debughelpers.py @@ -29,17 +29,18 @@ class DebugFilesKeyError(KeyError, AssertionError): def __init__(self, request, key): form_matches = request.form.getlist(key) buf = [ - '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) + 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.' ] if form_matches: + names = ", ".join(repr(x) for x in form_matches) buf.append( "\n\nThe browser instead transmitted some file names. " - "This was submitted: %s" % ", ".join('"%s"' % x for x in form_matches) + f"This was submitted: {names}" ) self.msg = "".join(buf) @@ -56,24 +57,24 @@ class FormDataRoutingRedirect(AssertionError): def __init__(self, request): exc = request.routing_exception buf = [ - "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) + 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}." ] # In case just a slash was appended we can be extra helpful - if request.base_url + "/" == exc.new_url.split("?")[0]: + if f"{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 %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 + " 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." ) buf.append("\n\nNote: this exception is only raised in debug mode") AssertionError.__init__(self, "".join(buf).encode("utf-8")) @@ -101,16 +102,16 @@ def attach_enctype_error_multidict(request): def _dump_loader_info(loader): - yield "class: {}.{}".format(type(loader).__module__, type(loader).__name__) + yield f"class: {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): continue - yield "%s:" % key + yield f"{key}:" for item in value: - yield " - %s" % item + yield f" - {item}" continue elif not isinstance(value, (str, int, float, bool)): continue @@ -119,7 +120,7 @@ def _dump_loader_info(loader): def explain_template_loading_attempts(app, template, attempts): """This should help developers understand what failed""" - info = ['Locating template "%s":' % template] + info = [f"Locating template {template!r}:"] total_found = 0 blueprint = None reqctx = _request_ctx_stack.top @@ -128,23 +129,23 @@ def explain_template_loading_attempts(app, template, attempts): for idx, (loader, srcobj, triple) in enumerate(attempts): if isinstance(srcobj, Flask): - src_info = 'application "%s"' % srcobj.import_name + src_info = f"application {srcobj.import_name!r}" elif isinstance(srcobj, Blueprint): - src_info = f'blueprint "{srcobj.name}" ({srcobj.import_name})' + src_info = f"blueprint {srcobj.name!r} ({srcobj.import_name})" else: src_info = repr(srcobj) - info.append("% 5d: trying loader of %s" % (idx + 1, src_info)) + info.append(f"{idx + 1:5}: trying loader of {src_info}") for line in _dump_loader_info(loader): - info.append(" %s" % line) + info.append(f" {line}") if triple is None: detail = "no match" else: - detail = "found (%r)" % (triple[1] or "") + detail = f"found ({triple[1] or ''!r})" total_found += 1 - info.append(" -> %s" % detail) + info.append(f" -> {detail}") seems_fishy = False if total_found == 0: @@ -156,8 +157,8 @@ def explain_template_loading_attempts(app, template, attempts): if blueprint is not None and seems_fishy: info.append( - " The template was looked up from an endpoint that " - 'belongs to the blueprint "%s".' % blueprint + " The template was looked up from an endpoint that belongs" + f" to the blueprint {blueprint!r}." ) info.append(" Maybe you did not place a template in the right folder?") info.append(" See https://flask.palletsprojects.com/blueprints/#templates") @@ -169,11 +170,10 @@ 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/helpers.py b/src/flask/helpers.py index 8cc94e58..87b33b05 100644 --- a/src/flask/helpers.py +++ b/src/flask/helpers.py @@ -311,7 +311,7 @@ def url_for(endpoint, **values): if endpoint[:1] == ".": if blueprint_name is not None: - endpoint = blueprint_name + endpoint + endpoint = f"{blueprint_name}{endpoint}" else: endpoint = endpoint[1:] @@ -364,7 +364,7 @@ def url_for(endpoint, **values): return appctx.app.handle_url_build_error(error, endpoint, values) if anchor is not None: - rv += "#" + url_quote(anchor) + rv += f"#{url_quote(anchor)}" return rv @@ -608,11 +608,12 @@ def send_file( try: attachment_filename = attachment_filename.encode("ascii") except UnicodeEncodeError: + quoted = url_quote(attachment_filename, safe="") filenames = { "filename": unicodedata.normalize("NFKD", attachment_filename).encode( "ascii", "ignore" ), - "filename*": "UTF-8''%s" % url_quote(attachment_filename, safe=""), + "filename*": f"UTF-8''{quoted}", } else: filenames = {"filename": attachment_filename} @@ -661,23 +662,19 @@ def send_file( from warnings import warn try: - rv.set_etag( - "%s-%s-%s" - % ( - os.path.getmtime(filename), - os.path.getsize(filename), - adler32( - filename.encode("utf-8") - if isinstance(filename, str) - else filename - ) - & 0xFFFFFFFF, + check = ( + adler32( + filename.encode("utf-8") if isinstance(filename, str) else filename ) + & 0xFFFFFFFF + ) + rv.set_etag( + f"{os.path.getmtime(filename)}-{os.path.getsize(filename)}-{check}" ) except OSError: warn( - "Access %s failed, maybe it does not exist, so ignore etags in " - "headers" % filename, + f"Access {filename} failed, maybe it does not exist, so" + " ignore etags in headers", stacklevel=2, ) @@ -806,13 +803,12 @@ def get_root_path(import_name): # first module that is contained in our package. if filepath is None: raise RuntimeError( - "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 + "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." ) # filepath is import_name.py for a module, or __init__.py for a package. @@ -823,6 +819,7 @@ 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. """ + cls = type(loader) # If the loader can tell us if something is a package, we can # directly ask the loader. if hasattr(loader, "is_package"): @@ -830,20 +827,13 @@ def _matching_loader_thinks_module_is_package(loader, 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" - ): + elif cls.__module__ == "_frozen_importlib" and cls.__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__ + f"{cls.__name__}.is_package() method is missing but is required" + " for PEP 302 import hooks." ) @@ -1014,7 +1004,7 @@ class _PackageBoundObject: if self.static_folder is not None: basename = os.path.basename(self.static_folder) - return ("/" + basename).rstrip("/") + return f"/{basename}".rstrip("/") @static_url_path.setter def static_url_path(self, value): diff --git a/src/flask/json/__init__.py b/src/flask/json/__init__.py index 19e92403..5ebf49cf 100644 --- a/src/flask/json/__init__.py +++ b/src/flask/json/__init__.py @@ -364,7 +364,7 @@ def jsonify(*args, **kwargs): data = args or kwargs return current_app.response_class( - dumps(data, indent=indent, separators=separators) + "\n", + f"{dumps(data, indent=indent, separators=separators)}\n", mimetype=current_app.config["JSONIFY_MIMETYPE"], ) diff --git a/src/flask/json/tag.py b/src/flask/json/tag.py index 54638551..46bd74d6 100644 --- a/src/flask/json/tag.py +++ b/src/flask/json/tag.py @@ -105,7 +105,7 @@ class TagDict(JSONTag): def to_json(self, value): key = next(iter(value)) - return {key + "__": self.serializer.tag(value[key])} + return {f"{key}__": self.serializer.tag(value[key])} def to_python(self, value): key = next(iter(value)) diff --git a/src/flask/sessions.py b/src/flask/sessions.py index 9644b5f9..300777a5 100644 --- a/src/flask/sessions.py +++ b/src/flask/sessions.py @@ -208,13 +208,13 @@ class SessionInterface: rv = rv.rsplit(":", 1)[0].lstrip(".") if "." not in rv: - # Chrome doesn't allow names without a '.' - # this should only come up with localhost - # hack around this by not setting the name, and show a warning + # Chrome doesn't allow names without a '.'. This should only + # come up with localhost. Hack around this by not setting + # the name, and show a warning. warnings.warn( - '"{rv}" is not a valid cookie domain, it must contain a ".".' - " Add an entry to your hosts file, for example" - ' "{rv}.localdomain", and use that instead.'.format(rv=rv) + f"{rv!r} is not a valid cookie domain, it must contain" + " a '.'. Add an entry to your hosts file, for example" + f" '{rv}.localdomain', and use that instead." ) app.config["SESSION_COOKIE_DOMAIN"] = False return None @@ -232,7 +232,7 @@ class SessionInterface: # if this is not an ip and app is mounted at the root, allow subdomain # matching by adding a '.' prefix if self.get_cookie_path(app) == "/" and not ip: - rv = "." + rv + rv = f".{rv}" app.config["SESSION_COOKIE_DOMAIN"] = rv return rv diff --git a/src/flask/testing.py b/src/flask/testing.py index b534b7a2..ca75b9e7 100644 --- a/src/flask/testing.py +++ b/src/flask/testing.py @@ -69,10 +69,9 @@ class EnvironBuilder(werkzeug.test.EnvironBuilder): url_scheme = app.config["PREFERRED_URL_SCHEME"] url = url_parse(path) - base_url = "{scheme}://{netloc}/{path}".format( - scheme=url.scheme or url_scheme, - netloc=url.netloc or http_host, - path=app_root.lstrip("/"), + base_url = ( + f"{url.scheme or url_scheme}://{url.netloc or http_host}" + f"/{app_root.lstrip('/')}" ) path = url.path @@ -114,7 +113,7 @@ class FlaskClient(Client): super().__init__(*args, **kwargs) self.environ_base = { "REMOTE_ADDR": "127.0.0.1", - "HTTP_USER_AGENT": "werkzeug/" + werkzeug.__version__, + "HTTP_USER_AGENT": f"werkzeug/{werkzeug.__version__}", } @contextmanager diff --git a/src/flask/views.py b/src/flask/views.py index 993480bb..8034e9b5 100644 --- a/src/flask/views.py +++ b/src/flask/views.py @@ -26,7 +26,7 @@ class View: methods = ['GET'] def dispatch_request(self, name): - return 'Hello %s!' % name + return f"Hello {name}!" app.add_url_rule('/hello/', view_func=MyView.as_view('myview')) @@ -157,5 +157,5 @@ class MethodView(View, metaclass=MethodViewType): if meth is None and request.method == "HEAD": meth = getattr(self, "get", None) - assert meth is not None, "Unimplemented method %r" % request.method + assert meth is not None, f"Unimplemented method {request.method!r}" return meth(*args, **kwargs) diff --git a/tests/conftest.py b/tests/conftest.py index a817650f..ed9c26a1 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -116,9 +116,8 @@ def limit_loader(request, monkeypatch): self.loader = loader def __getattr__(self, name): - if name in ("archive", "get_filename"): - msg = "Mocking a loader which does not have `%s.`" % name - raise AttributeError(msg) + if name in {"archive", "get_filename"}: + raise AttributeError(f"Mocking a loader which does not have {name!r}.") return getattr(self.loader, name) old_get_loader = pkgutil.get_loader @@ -148,7 +147,7 @@ def site_packages(modules_tmpdir, monkeypatch): """Create a fake site-packages.""" rv = ( modules_tmpdir.mkdir("lib") - .mkdir("python{x[0]}.{x[1]}".format(x=sys.version_info)) + .mkdir(f"python{sys.version_info.major}.{sys.version_info.minor}") .mkdir("site-packages") ) monkeypatch.syspath_prepend(str(rv)) @@ -161,23 +160,21 @@ def install_egg(modules_tmpdir, monkeypatch): sys.path.""" def inner(name, base=modules_tmpdir): - if not isinstance(name, str): - raise ValueError(name) base.join(name).ensure_dir() base.join(name).join("__init__.py").ensure() egg_setup = base.join("setup.py") egg_setup.write( textwrap.dedent( - """ - from setuptools import setup - setup(name='{}', - version='1.0', - packages=['site_egg'], - zip_safe=True) - """.format( - name + f""" + from setuptools import setup + setup( + name="{name}", + version="1.0", + packages=["site_egg"], + zip_safe=True, ) + """ ) ) diff --git a/tests/test_basic.py b/tests/test_basic.py index 41f8af82..dc7211a9 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -361,7 +361,7 @@ def test_session_localhost_warning(recwarn, app, client): rv = client.get("/", "http://localhost:5000/") assert "domain" not in rv.headers["set-cookie"].lower() w = recwarn.pop(UserWarning) - assert '"localhost" is not a valid cookie domain' in str(w.message) + assert "'localhost' is not a valid cookie domain" in str(w.message) def test_session_ip_warning(recwarn, app, client): @@ -1095,7 +1095,7 @@ def test_enctype_debug_helper(app, client): with pytest.raises(DebugFilesKeyError) as e: client.post("/fail", data={"foo": "index.txt"}) assert "no file contents were transmitted" in str(e.value) - assert 'This was submitted: "index.txt"' in str(e.value) + assert "This was submitted: 'index.txt'" in str(e.value) def test_response_types(app, client): @@ -1821,7 +1821,7 @@ def test_subdomain_matching(): @app.route("/", subdomain="") def index(user): - return "index for %s" % user + return f"index for {user}" rv = client.get("/", "http://mitsuhiko.localhost.localdomain/") assert rv.data == b"index for mitsuhiko" @@ -1834,7 +1834,7 @@ def test_subdomain_matching_with_ports(): @app.route("/", subdomain="") def index(user): - return "index for %s" % user + return f"index for {user}" rv = client.get("/", "http://mitsuhiko.localhost.localdomain:3000/") assert rv.data == b"index for mitsuhiko" diff --git a/tests/test_blueprints.py b/tests/test_blueprints.py index f935286d..33bf8f15 100644 --- a/tests/test_blueprints.py +++ b/tests/test_blueprints.py @@ -144,7 +144,7 @@ def test_blueprint_url_defaults(app, client): @bp.route("/foo", defaults={"baz": 42}) def foo(bar, baz): - return "%s/%d" % (bar, baz) + return f"{bar}/{baz:d}" @bp.route("/bar") def bar(bar): diff --git a/tests/test_cli.py b/tests/test_cli.py index 34f40362..40a21f4f 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -268,9 +268,9 @@ def test_get_version(test_apps, capsys): ctx = MockCtx() get_version(ctx, None, "test") out, err = capsys.readouterr() - assert "Python " + python_version() in out - assert "Flask " + flask_version in out - assert "Werkzeug " + werkzeug_version in out + assert f"Python {python_version()}" in out + assert f"Flask {flask_version}" in out + assert f"Werkzeug {werkzeug_version}" in out def test_scriptinfo(test_apps, monkeypatch): @@ -288,7 +288,7 @@ def test_scriptinfo(test_apps, monkeypatch): app = obj.load_app() assert app.name == "testapp" assert obj.load_app() is app - obj = ScriptInfo(app_import_path=cli_app_path + ":testapp") + obj = ScriptInfo(app_import_path=f"{cli_app_path}:testapp") app = obj.load_app() assert app.name == "testapp" assert obj.load_app() is app @@ -406,7 +406,7 @@ def test_flaskgroup_debug(runner, set_debug_flag): result = runner.invoke(cli, ["test"]) assert result.exit_code == 0 - assert result.output == "%s\n" % str(not set_debug_flag) + assert result.output == f"{not set_debug_flag}\n" def test_print_exceptions(runner): @@ -656,4 +656,4 @@ def test_cli_empty(app): app.register_blueprint(bp) result = app.test_cli_runner().invoke(args=["blue", "--help"]) - assert result.exit_code == 2, "Unexpected success:\n\n" + result.output + assert result.exit_code == 2, f"Unexpected success:\n\n{result.output}" diff --git a/tests/test_config.py b/tests/test_config.py index ac0eef64..00d287fa 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -28,7 +28,7 @@ def common_object_test(app): def test_config_from_pyfile(): app = flask.Flask(__name__) - app.config.from_pyfile(__file__.rsplit(".", 1)[0] + ".py") + app.config.from_pyfile(f"{__file__.rsplit('.', 1)[0]}.py") common_object_test(app) @@ -84,7 +84,7 @@ def test_config_from_envvar(monkeypatch): assert not app.config.from_envvar("FOO_SETTINGS", silent=True) monkeypatch.setattr( - "os.environ", {"FOO_SETTINGS": __file__.rsplit(".", 1)[0] + ".py"} + "os.environ", {"FOO_SETTINGS": f"{__file__.rsplit('.', 1)[0]}.py"} ) assert app.config.from_envvar("FOO_SETTINGS") common_object_test(app) @@ -185,12 +185,10 @@ def test_from_pyfile_weird_encoding(tmpdir, encoding): f = tmpdir.join("my_config.py") f.write_binary( textwrap.dedent( + f""" + # -*- coding: {encoding} -*- + TEST_VALUE = "föö" """ - # -*- coding: {} -*- - TEST_VALUE = "föö" - """.format( - encoding - ) ).encode(encoding) ) app = flask.Flask(__name__) diff --git a/tests/test_helpers.py b/tests/test_helpers.py index ec9981cc..274ed0bc 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -286,7 +286,7 @@ class TestJSON: class MyEncoder(flask.json.JSONEncoder): def default(self, o): if isinstance(o, X): - return "<%d>" % o.val + return f"<{o.val}>" return flask.json.JSONEncoder.default(self, o) class MyDecoder(flask.json.JSONDecoder): @@ -314,14 +314,16 @@ class TestJSON: assert rv.data == b'"<42>"' def test_blueprint_json_customization(self, app, client): - class X: # noqa: B903, for Python2 compatibility + class X: + __slots__ = ("val",) + def __init__(self, val): self.val = val class MyEncoder(flask.json.JSONEncoder): def default(self, o): if isinstance(o, X): - return "<%d>" % o.val + return f"<{o.val}>" return flask.json.JSONEncoder.default(self, o) @@ -687,9 +689,9 @@ class TestSendfile: ) rv.close() content_disposition = rv.headers["Content-Disposition"] - assert "filename=%s" % ascii in content_disposition + assert f"filename={ascii}" in content_disposition if utf8: - assert "filename*=UTF-8''" + utf8 in content_disposition + assert f"filename*=UTF-8''{utf8}" in content_disposition else: assert "filename*=UTF-8''" not in content_disposition @@ -818,7 +820,7 @@ class TestUrlFor: def get(self, id=None): if id is None: return "List" - return "Get %d" % id + return f"Get {id:d}" def post(self): return "Create" diff --git a/tests/test_reqctx.py b/tests/test_reqctx.py index 145f4038..36deca56 100644 --- a/tests/test_reqctx.py +++ b/tests/test_reqctx.py @@ -110,7 +110,7 @@ def test_proper_test_request_context(app): def test_context_binding(app): @app.route("/") def index(): - return "Hello %s!" % flask.request.args["name"] + return f"Hello {flask.request.args['name']}!" @app.route("/meh") def meh(): @@ -138,7 +138,7 @@ def test_context_test(app): def test_manual_context_binding(app): @app.route("/") def index(): - return "Hello %s!" % flask.request.args["name"] + return f"Hello {flask.request.args['name']}!" ctx = app.test_request_context("/?name=World") ctx.push() diff --git a/tests/test_templating.py b/tests/test_templating.py index ab5c745f..e6e91b01 100644 --- a/tests/test_templating.py +++ b/tests/test_templating.py @@ -414,16 +414,16 @@ def test_template_loader_debugging(test_apps, monkeypatch): def handle(self, record): called.append(True) text = str(record.msg) - assert '1: trying loader of application "blueprintapp"' in text + assert "1: trying loader of application 'blueprintapp'" in text assert ( - '2: trying loader of blueprint "admin" (blueprintapp.apps.admin)' + "2: trying loader of blueprint 'admin' (blueprintapp.apps.admin)" ) in text assert ( - 'trying loader of blueprint "frontend" (blueprintapp.apps.frontend)' + "trying loader of blueprint 'frontend' (blueprintapp.apps.frontend)" ) in text assert "Error: the template could not be found" in text assert ( - 'looked up from an endpoint that belongs to the blueprint "frontend"' + "looked up from an endpoint that belongs to the blueprint 'frontend'" ) in text assert "See https://flask.palletsprojects.com/blueprints/#templates" in text diff --git a/tests/test_testing.py b/tests/test_testing.py index fd7f828c..1fbba6e3 100644 --- a/tests/test_testing.py +++ b/tests/test_testing.py @@ -59,7 +59,7 @@ def test_environ_base_default(app, client, app_ctx): rv = client.get("/") assert rv.data == b"127.0.0.1" - assert flask.g.user_agent == "werkzeug/" + werkzeug.__version__ + assert flask.g.user_agent == f"werkzeug/{werkzeug.__version__}" def test_environ_base_modified(app, client, app_ctx): @@ -321,7 +321,7 @@ def test_json_request_and_response(app, client): def test_client_json_no_app_context(app, client): @app.route("/hello", methods=["POST"]) def hello(): - return "Hello, {}!".format(flask.request.json["name"]) + return f"Hello, {flask.request.json['name']}!" class Namespace: count = 0 diff --git a/tests/test_user_error_handler.py b/tests/test_user_error_handler.py index b7e21a26..f7d3db45 100644 --- a/tests/test_user_error_handler.py +++ b/tests/test_user_error_handler.py @@ -29,7 +29,7 @@ def test_error_handler_no_match(app, client): original = getattr(e, "original_exception", None) if original is not None: - return "wrapped " + type(original).__name__ + return f"wrapped {type(original).__name__}" return "direct" @@ -238,9 +238,9 @@ class TestGenericHandlers: original = getattr(e, "original_exception", None) if original is not None: - return "wrapped " + type(original).__name__ + return f"wrapped {type(original).__name__}" - return "direct " + type(e).__name__ + return f"direct {type(e).__name__}" @pytest.mark.parametrize("to_handle", (InternalServerError, 500)) def test_handle_class_or_code(self, app, client, to_handle):