Skip to content

Commit c8f36d9

Browse files
ngjunsiangclaude
andauthored
fix: add end_slash parameter to ResourceCollection.make_path() to resolve issue #31
Fixes #31 - ResourceCollection.make_path() incorrectly adds trailing slash to terminal endpoints Changes: - Add end_slash parameter to ResourceCollection.make_path() (defaults to True for backward compatibility) - Update action endpoints to use end_slash=False to prevent trailing slashes on terminal endpoints - Bump version to 0.1.61 Action endpoints updated: - sessions.py: authorization_code, sweep - clients.py: revoke, grant (in ClientAccess) - users.py: activate - submissions.py: submit - timetable.py: current, next This resolves 405 Method Not Allowed errors when servers use strict_slashes=True. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent ad6b8c1 commit c8f36d9

7 files changed

Lines changed: 21 additions & 18 deletions

File tree

campus_python/api/v1/submissions.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ def update(
173173

174174
def submit(self) -> None:
175175
"""Finalize/submit this submission (marks submitted_at timestamp)."""
176-
path = f"{self.make_path()}/submit"
176+
path = self.make_path("submit", end_slash=False)
177177
resp = self.client.post(path)
178178
resp.raise_for_status()
179179
return None

campus_python/api/v1/timetable.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,13 +62,13 @@ def __getitem__(self, timetable_id: str) -> "Timetables.Timetable":
6262

6363
def get_current(self) -> str:
6464
"""Get the timetable ID of current timetable."""
65-
resp = self.client.get(self.make_path("current"))
65+
resp = self.client.get(self.make_path("current", end_slash=False))
6666
resp.raise_for_status()
6767
return resp.json()["value"]
6868

6969
def get_next(self) -> str:
7070
"""Get the timetable ID of next timetable."""
71-
resp = self.client.get(self.make_path("next"))
71+
resp = self.client.get(self.make_path("next", end_slash=False))
7272
resp.raise_for_status()
7373
return resp.json()["value"]
7474

@@ -79,7 +79,7 @@ def set_current(self, timetable_id: str) -> None:
7979
timetable_id: ID of the timetable to set as current
8080
"""
8181
resp = self.client.put(
82-
self.make_path("current"),
82+
self.make_path("current", end_slash=False),
8383
json={"value": timetable_id}
8484
)
8585
resp.raise_for_status()
@@ -91,7 +91,7 @@ def set_next(self, timetable_id: str) -> None:
9191
timetable_id: ID of the timetable to set as next
9292
"""
9393
resp = self.client.put(
94-
self.make_path("next"),
94+
self.make_path("next", end_slash=False),
9595
json={"value": timetable_id}
9696
)
9797
resp.raise_for_status()

campus_python/auth/v1/clients.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ def revoke(self) -> str:
6666
Returns:
6767
The newly generated client secret.
6868
"""
69-
resp = self.client.post(self.make_path("revoke"))
69+
resp = self.client.post(self.make_path("revoke", end_slash=False))
7070
# Raise error if status code is not 2XX or 3XX
7171
resp.raise_for_status()
7272
return resp.json()["secret"]
@@ -105,7 +105,7 @@ def grant(
105105
vault: str,
106106
permission: int,
107107
) -> JsonDict:
108-
resp = self.client.post(self.make_path("grant"), json={
108+
resp = self.client.post(self.make_path("grant", end_slash=False), json={
109109
"vault": vault,
110110
"permission": permission,
111111
})
@@ -118,7 +118,7 @@ def revoke(
118118
vault: str,
119119
permission: int,
120120
) -> JsonDict:
121-
resp = self.client.post(self.make_path("revoke"), json={
121+
resp = self.client.post(self.make_path("revoke", end_slash=False), json={
122122
"vault": vault,
123123
"permission": permission,
124124
})

campus_python/auth/v1/sessions.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ def __getitem__(self, session_id: str) -> "CampusSessions.Session":
3232
def from_code(self, code: str) -> campus.model.AuthSession:
3333
"""Get a session using authorization code."""
3434
resp = self.client.post(
35-
self.make_path("authorization_code"),
35+
self.make_path("authorization_code", end_slash=False),
3636
json={"code": code}
3737
)
3838
resp.raise_for_status()
@@ -85,7 +85,7 @@ def sweep(self, at_time: datetime | str | None = None) -> int:
8585
json_data["at_time"] = schema.DateTime(at_time)
8686
case None:
8787
json_data["at_time"] = schema.DateTime.utcnow()
88-
resp = self.client.post(self.make_path("sweep"), json=json_data)
88+
resp = self.client.post(self.make_path("sweep", end_slash=False), json=json_data)
8989
resp.raise_for_status()
9090
return int(resp.json()["swept_count"])
9191

campus_python/auth/v1/users.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ class User(Resource):
4141
"""Single vault user resource."""
4242

4343
def activate(self) -> campus.model.User:
44-
resp = self.client.post(self.make_path("activate"))
44+
resp = self.client.post(self.make_path("activate", end_slash=False))
4545
resp.raise_for_status()
4646
return campus.model.User.from_resource(resp.json())
4747

campus_python/interface.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -90,16 +90,19 @@ def client(self) -> JsonClient:
9090
return self.root.client
9191
raise AttributeError(f"No client defined for {self}")
9292

93-
def make_path(self, part: str | None = None) -> str:
93+
def make_path(self, part: str | None = None, end_slash: bool = True) -> str:
9494
"""Create a full path for a resource collection.
9595
96-
Resource collection paths always end in a /.
96+
Args:
97+
part: Optional sub-resource or action path.
98+
end_slash: Whether to add a trailing slash (default: True for collections).
99+
100+
Returns:
101+
Full path for the resource collection or sub-resource.
97102
"""
98103
if part:
99-
return (
100-
f"/{self.root.make_path(self.path).strip(SLASH)}"
101-
f"/{part.strip(SLASH)}/"
102-
)
104+
base = f"/{self.root.make_path(self.path).strip(SLASH)}/{part.strip(SLASH)}"
105+
return f"{base}/" if end_slash else base
103106
else:
104107
return f"/{self.root.make_path(self.path).strip(SLASH)}/"
105108

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "campus-api-python"
3-
version = "0.1.60"
3+
version = "0.1.61"
44
description = "Campus API for Python projects"
55
authors = ["NYJC Computing <nyjc.computing@nyjc.edu.sg>"]
66

0 commit comments

Comments
 (0)