-
-
Notifications
You must be signed in to change notification settings - Fork 970
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Allow colons in routes again #1657
Conversation
e863f7b
to
019857b
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
And also thank you @bodograumann for the PR 👍 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup, that's a nice one. Brings the previous behavior by default while keeping the fix on host domains.
Actually... We should note that since So, should we I'm thinking of the latter. In fact, So, maybe the behavior could be this, without needing a new flag? # Leading '/', treat as path, don't strip :rest
compile_path("/files:upload_homework/") # -> /files:upload_homework/
# No leading '/', treat as host, strip :port
compile_path("{subdomain}.example.org:3000") # -> {subdomain}.example.org Essentially, we'd do: is_host = not path.startswith("/") # {subdomain}.example.org:port vs /users:list
tail = path[idx:].split(":")[0] if is_host else path[idx:]
path_regex += re.escape(tail) + "$" Symetrically to |
You bring up some really good points. I also thought that it was weird to use the same method for Host and Route in the first place, but did not want change too much. Should I try to make your suggested changes, do you want to do them yourself, or should we wait for this to be discussed a bit more? |
Curious what @Kludex or @aminalaee think about ^^? |
I like the idea @florimondmanca |
I didn't like that parameter as well, that's when I added the I prefer what you suggested, yep. |
I implemented it according to your suggestion @florimondmanca 🚀 |
@@ -108,34 +108,47 @@ def replace_params( | |||
|
|||
|
|||
def compile_path( | |||
path: str, | |||
pattern: str, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Question, isn't this still considered backwards-incompatible?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ugh. I guess technically you are correct. I didn't think about keyword argument usage.
We are also on a 0.x version. So we could do breaking changes in 0.21 I guess.
How about defining an alias for now. Then we can also get rid of the inprecise compile_path
name internally.
E.g. rename into def compile_pattern(pattern: str)
and def compile_path(path: str): return compile_pattern(path)
for backwards compatibility.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We try to avoid breaking changes as much as possible. I also don't see much benefit on the renaming here. 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed adding a parameter is a breaking change
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Imho the method should not have been used for compiling host patterns in the first place, because a hostname is not a path.
The least we can do now is apply proper naming and documentation.
The renaming would allow keeping backward-compatibility for the old interface, while implementing a better on. Personally I wouldn’t regard this function as part of the public starlette api currently, because it is not documented anywhere and I would keep it that way. So the new function name would be _compile_pattern
I guess.
Having said all that, I don’t feel too strongly about any of this. Just thought it would be an improvement to the code base. If you don’t care about correct naming, I can simply revert that part.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes I think this has come up a few times before, but the priority is to keep backwards-compatibility until there's a major release like from 1.x.x to 2.x.x
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The only case when this would break is if people do compile_path(path=...)
.
It's possible to go for a deprecation, allowing this usage at runtime while keeping the new signature for type hints, with a decorator...
def _allow_deprecated_path_kwarg(func: Callable) -> Callable:
@wraps(func)
def wrapped(*args: Any, **kwargs: Any):
if "path" in kwargs:
... # Raise a deprecation warning
return func(kwargs["path"])
return func(*args, **kwargs)
return wrapped
@_allow_deprecated_path_kwarg
def compile_path(pattern: str):
...
Edit: But not really convinced either this is something we should do, especially in this bugfix PR (see #1657 (comment)).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After reviewing this, I still don't see why we need to change path
by pattern
. 😅
""" | ||
is_host: bool = not pattern.startswith("/") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is_host: bool = not pattern.startswith("/") | |
is_host = not pattern.startswith("/") |
|
||
path_regex += re.escape(path[idx:].split(":")[0]) + "$" | ||
path_format += path[idx:] | ||
tail: str = pattern[idx:].split(":")[0] if is_host else pattern[idx:] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
tail: str = pattern[idx:].split(":")[0] if is_host else pattern[idx:] | |
tail = pattern[idx:].split(":")[0] if is_host else pattern[idx:] |
@Kludex From my POV, it is mainly a naming consistency. Kind of like HTTPX's "URLPattern", which was internal. I'd personally be OK with keeping We should note that actually the name of the function itself is This sounds like relevant discussion to have, but maybe not here, as this PR is about fixing colons in route names. So...? @bodograumann WDYT? |
Closing as we just merged #1675. Kudos to @bodograumann for helping out on this! |
Am I missing something? By my read a colon is not a valid path character. I don't see any change to these in RFC updates to 3986. |
Starlette's path matching happens after the path has been decoded, so all characters are valid, they just have to be encoded when going over the wire, @ahopkins .
From what I have seen, it is common practice to always encode the colon, even though it could theoretically be unambiguously interpreted in the path part of the URI. |
Got it. Yeah, agreed that encoded should be fine. Thanks for the clarification. |
This fixes the issue that was brought up as fastapi/fastapi#4892
Due to starlette stripping any
:.*
ending when compiling path patterns, it became impossible to use colons in them.This issue was introduced in #1322, because now the port suffix was stripped from host names. This is not necessary when compiling Route and Mount patterns however.
My use case, like the one of the original reporter, comes from following the google api design guide.