ResourceCollection.make_path(part) always adds a trailing slash, even for terminal endpoints that shouldn't have one. This causes 405 Method Not Allowed errors when the client makes requests to servers with strict_slashes=True.
Problem
In interface.py, the ResourceCollection.make_path() method:
def make_path(self, part: str | None = None) -> str:
"""Create a full path for a resource collection.
Resource collection paths always end in a /.
"""
if part:
return (
f"/{self.root.make_path(self.path).strip(SLASH)}"
f"/{part.strip(SLASH)}/"
)
else:
return f"/{self.root.make_path(self.path).strip(SLASH)}/"
This unconditionally adds a trailing slash when part is provided. However, this is incorrect for terminal endpoints (like /authorization_code) that are actions, not resource collections.
Impact
When CampusSessions.from_code() calls:
resp = self.client.post(
self.make_path("authorization_code"),
json={"code": code}
)
It generates /sessions/campus/authorization_code/ (with trailing slash), but the Flask route is defined as POST /sessions/<provider>/authorization_code (without trailing slash).
With strict_slashes=True on the server (which is needed to avoid 308 redirects that strip headers), this results in:
405 Method Not Allowed: The method is not allowed for the requested URL.
Suggested Fix
Add an end_slash parameter to ResourceCollection.make_path(), similar to Resource.make_path():
def make_path(self, part: str | None = None, end_slash: bool = True) -> str:
"""Create a full path for a resource collection.
Args:
part: Optional sub-resource or action path.
end_slash: Whether to add a trailing slash (default: True for collections).
Returns:
Full path for the resource collection or sub-resource.
"""
if part:
base = f"/{self.root.make_path(self.path).strip(SLASH)}/{part.strip(SLASH)}"
return f"{base}/" if end_slash else base
else:
return f"/{self.root.make_path(self.path).strip(SLASH)}/"
Then update from_code() to use end_slash=False:
def from_code(self, code: str) -> campus.model.AuthSession:
"""Get a session using authorization code."""
resp = self.client.post(
self.make_path("authorization_code", end_slash=False),
json={"code": code}
)
# ...
Alternative
If changing the API is too disruptive, consider adding a separate method like make_action_path() for terminal endpoints that don't need trailing slashes.
ResourceCollection.make_path(part)always adds a trailing slash, even for terminal endpoints that shouldn't have one. This causes 405 Method Not Allowed errors when the client makes requests to servers withstrict_slashes=True.Problem
In
interface.py, theResourceCollection.make_path()method:This unconditionally adds a trailing slash when
partis provided. However, this is incorrect for terminal endpoints (like/authorization_code) that are actions, not resource collections.Impact
When
CampusSessions.from_code()calls:It generates
/sessions/campus/authorization_code/(with trailing slash), but the Flask route is defined asPOST /sessions/<provider>/authorization_code(without trailing slash).With
strict_slashes=Trueon the server (which is needed to avoid 308 redirects that strip headers), this results in:Suggested Fix
Add an
end_slashparameter toResourceCollection.make_path(), similar toResource.make_path():Then update
from_code()to useend_slash=False:Alternative
If changing the API is too disruptive, consider adding a separate method like
make_action_path()for terminal endpoints that don't need trailing slashes.