forked from orbit-oss/flask
show subdomain or host in routes output
This commit is contained in:
parent
182ce3dd15
commit
84c007d34f
3 changed files with 69 additions and 49 deletions
|
|
@ -44,6 +44,8 @@ Unreleased
|
||||||
to set the domain, which modern browsers interpret as an exact match rather than
|
to set the domain, which modern browsers interpret as an exact match rather than
|
||||||
a subdomain match. Warnings about ``localhost`` and IP addresses are also removed.
|
a subdomain match. Warnings about ``localhost`` and IP addresses are also removed.
|
||||||
:issue:`5051`
|
:issue:`5051`
|
||||||
|
- The ``routes`` command shows each rule's ``subdomain`` or ``host`` when domain
|
||||||
|
matching is in use. :issue:`5004`
|
||||||
|
|
||||||
|
|
||||||
Version 2.2.4
|
Version 2.2.4
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import sys
|
||||||
import traceback
|
import traceback
|
||||||
import typing as t
|
import typing as t
|
||||||
from functools import update_wrapper
|
from functools import update_wrapper
|
||||||
from operator import attrgetter
|
from operator import itemgetter
|
||||||
|
|
||||||
import click
|
import click
|
||||||
from click.core import ParameterSource
|
from click.core import ParameterSource
|
||||||
|
|
@ -989,49 +989,62 @@ def shell_command() -> None:
|
||||||
@click.option(
|
@click.option(
|
||||||
"--sort",
|
"--sort",
|
||||||
"-s",
|
"-s",
|
||||||
type=click.Choice(("endpoint", "methods", "rule", "match")),
|
type=click.Choice(("endpoint", "methods", "domain", "rule", "match")),
|
||||||
default="endpoint",
|
default="endpoint",
|
||||||
help=(
|
help=(
|
||||||
'Method to sort routes by. "match" is the order that Flask will match '
|
"Method to sort routes by. 'match' is the order that Flask will match routes"
|
||||||
"routes when dispatching a request."
|
" when dispatching a request."
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@click.option("--all-methods", is_flag=True, help="Show HEAD and OPTIONS methods.")
|
@click.option("--all-methods", is_flag=True, help="Show HEAD and OPTIONS methods.")
|
||||||
@with_appcontext
|
@with_appcontext
|
||||||
def routes_command(sort: str, all_methods: bool) -> None:
|
def routes_command(sort: str, all_methods: bool) -> None:
|
||||||
"""Show all registered routes with endpoints and methods."""
|
"""Show all registered routes with endpoints and methods."""
|
||||||
|
|
||||||
rules = list(current_app.url_map.iter_rules())
|
rules = list(current_app.url_map.iter_rules())
|
||||||
|
|
||||||
if not rules:
|
if not rules:
|
||||||
click.echo("No routes were registered.")
|
click.echo("No routes were registered.")
|
||||||
return
|
return
|
||||||
|
|
||||||
ignored_methods = set(() if all_methods else ("HEAD", "OPTIONS"))
|
ignored_methods = set() if all_methods else {"HEAD", "OPTIONS"}
|
||||||
|
host_matching = current_app.url_map.host_matching
|
||||||
|
has_domain = any(rule.host if host_matching else rule.subdomain for rule in rules)
|
||||||
|
rows = []
|
||||||
|
|
||||||
if sort in ("endpoint", "rule"):
|
for rule in rules:
|
||||||
rules = sorted(rules, key=attrgetter(sort))
|
row = [
|
||||||
elif sort == "methods":
|
rule.endpoint,
|
||||||
rules = sorted(rules, key=lambda rule: sorted(rule.methods)) # type: ignore
|
", ".join(sorted((rule.methods or set()) - ignored_methods)),
|
||||||
|
]
|
||||||
|
|
||||||
rule_methods = [
|
if has_domain:
|
||||||
", ".join(sorted(rule.methods - ignored_methods)) # type: ignore
|
row.append((rule.host if host_matching else rule.subdomain) or "")
|
||||||
for rule in rules
|
|
||||||
]
|
|
||||||
|
|
||||||
headers = ("Endpoint", "Methods", "Rule")
|
row.append(rule.rule)
|
||||||
widths = (
|
rows.append(row)
|
||||||
max(len(rule.endpoint) for rule in rules),
|
|
||||||
max(len(methods) for methods in rule_methods),
|
|
||||||
max(len(rule.rule) for rule in rules),
|
|
||||||
)
|
|
||||||
widths = [max(len(h), w) for h, w in zip(headers, widths)]
|
|
||||||
row = "{{0:<{0}}} {{1:<{1}}} {{2:<{2}}}".format(*widths)
|
|
||||||
|
|
||||||
click.echo(row.format(*headers).strip())
|
headers = ["Endpoint", "Methods"]
|
||||||
click.echo(row.format(*("-" * width for width in widths)))
|
sorts = ["endpoint", "methods"]
|
||||||
|
|
||||||
for rule, methods in zip(rules, rule_methods):
|
if has_domain:
|
||||||
click.echo(row.format(rule.endpoint, methods, rule.rule).rstrip())
|
headers.append("Host" if host_matching else "Subdomain")
|
||||||
|
sorts.append("domain")
|
||||||
|
|
||||||
|
headers.append("Rule")
|
||||||
|
sorts.append("rule")
|
||||||
|
|
||||||
|
try:
|
||||||
|
rows.sort(key=itemgetter(sorts.index(sort)))
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
rows.insert(0, headers)
|
||||||
|
widths = [max(len(row[i]) for row in rows) for i in range(len(headers))]
|
||||||
|
rows.insert(1, ["-" * w for w in widths])
|
||||||
|
template = " ".join(f"{{{i}:<{w}}}" for i, w in enumerate(widths))
|
||||||
|
|
||||||
|
for row in rows:
|
||||||
|
click.echo(template.format(*row))
|
||||||
|
|
||||||
|
|
||||||
cli = FlaskGroup(
|
cli = FlaskGroup(
|
||||||
|
|
|
||||||
|
|
@ -433,16 +433,12 @@ class TestRoutes:
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def app(self):
|
def app(self):
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
app.testing = True
|
app.add_url_rule(
|
||||||
|
"/get_post/<int:x>/<int:y>",
|
||||||
@app.route("/get_post/<int:x>/<int:y>", methods=["GET", "POST"])
|
methods=["GET", "POST"],
|
||||||
def yyy_get_post(x, y):
|
endpoint="yyy_get_post",
|
||||||
pass
|
)
|
||||||
|
app.add_url_rule("/zzz_post", methods=["POST"], endpoint="aaa_post")
|
||||||
@app.route("/zzz_post", methods=["POST"])
|
|
||||||
def aaa_post():
|
|
||||||
pass
|
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
|
|
@ -450,17 +446,6 @@ class TestRoutes:
|
||||||
cli = FlaskGroup(create_app=lambda: app)
|
cli = FlaskGroup(create_app=lambda: app)
|
||||||
return partial(runner.invoke, cli)
|
return partial(runner.invoke, cli)
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def invoke_no_routes(self, runner):
|
|
||||||
def create_app():
|
|
||||||
app = Flask(__name__, static_folder=None)
|
|
||||||
app.testing = True
|
|
||||||
|
|
||||||
return app
|
|
||||||
|
|
||||||
cli = FlaskGroup(create_app=create_app)
|
|
||||||
return partial(runner.invoke, cli)
|
|
||||||
|
|
||||||
def expect_order(self, order, output):
|
def expect_order(self, order, output):
|
||||||
# skip the header and match the start of each row
|
# skip the header and match the start of each row
|
||||||
for expect, line in zip(order, output.splitlines()[2:]):
|
for expect, line in zip(order, output.splitlines()[2:]):
|
||||||
|
|
@ -493,11 +478,31 @@ class TestRoutes:
|
||||||
output = invoke(["routes", "--all-methods"]).output
|
output = invoke(["routes", "--all-methods"]).output
|
||||||
assert "GET, HEAD, OPTIONS, POST" in output
|
assert "GET, HEAD, OPTIONS, POST" in output
|
||||||
|
|
||||||
def test_no_routes(self, invoke_no_routes):
|
def test_no_routes(self, runner):
|
||||||
result = invoke_no_routes(["routes"])
|
app = Flask(__name__, static_folder=None)
|
||||||
|
cli = FlaskGroup(create_app=lambda: app)
|
||||||
|
result = runner.invoke(cli, ["routes"])
|
||||||
assert result.exit_code == 0
|
assert result.exit_code == 0
|
||||||
assert "No routes were registered." in result.output
|
assert "No routes were registered." in result.output
|
||||||
|
|
||||||
|
def test_subdomain(self, runner):
|
||||||
|
app = Flask(__name__, static_folder=None)
|
||||||
|
app.add_url_rule("/a", subdomain="a", endpoint="a")
|
||||||
|
app.add_url_rule("/b", subdomain="b", endpoint="b")
|
||||||
|
cli = FlaskGroup(create_app=lambda: app)
|
||||||
|
result = runner.invoke(cli, ["routes"])
|
||||||
|
assert result.exit_code == 0
|
||||||
|
assert "Subdomain" in result.output
|
||||||
|
|
||||||
|
def test_host(self, runner):
|
||||||
|
app = Flask(__name__, static_folder=None, host_matching=True)
|
||||||
|
app.add_url_rule("/a", host="a", endpoint="a")
|
||||||
|
app.add_url_rule("/b", host="b", endpoint="b")
|
||||||
|
cli = FlaskGroup(create_app=lambda: app)
|
||||||
|
result = runner.invoke(cli, ["routes"])
|
||||||
|
assert result.exit_code == 0
|
||||||
|
assert "Host" in result.output
|
||||||
|
|
||||||
|
|
||||||
def dotenv_not_available():
|
def dotenv_not_available():
|
||||||
try:
|
try:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue