Skip to content

Commit 33cf63d

Browse files
authored
Merge pull request #35 from volfpeter/feat/use-hyphens-in-urls
Replace underscores with hyphens in routes by default, except in path parameters
2 parents a470e2b + 12e4ae5 commit 33cf63d

File tree

21 files changed

+51
-43
lines changed

21 files changed

+51
-43
lines changed

docs/application-components.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ Rules for *actions*:
5757

5858
- An action must be a FastAPI dependency, just like a page.
5959
- Actions can be registered with any HTTP method (`GET`, `POST`, `PUT`, `PATCH`, `DELETE`), using the appropriate `@action` decorator.
60-
- You can specify a custom URL path for an action (e.g., `@action.post("/do-something")`), but it is optional. If no path is provided, the function's name is used as the path. In both cases, the path is relative to the URL corresponding to the package that contains the action.
60+
- You can specify a custom URL path for an action (e.g., `@action.post("/do-something")`), but it is optional. If no path is provided, the function's name is used as the path, with underscores (`_`) being replaced by hyphens (`-`). In both cases, the path is relative to the URL corresponding to the package that contains the action.
6161
- By default, components returned from actions are not wrapped in layouts. This is ideal for returning HTML fragments for client frameworks like HTMX.
6262
- To render an action's return value in its owner layouts (like it is done by default for pages), you can set `use_layout=True` in the decorator: `@action.get(use_layout=True)`. This behavior can of course be combined with the [`without_layout` utility](utilities.md#without_layout).
6363
- Actions can also have `metadata`: `@action.get(metadata={"title": "Hello"})`. `metadata` works identically to page metadata and is particularly useful when combined with `use_layout`.

docs/file-system-based-routing.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Going through the [Application components](application-components.md) documentat
99
## Core concepts
1010

1111
- Packages define URL segments: Each Python package within your application's root maps to a URL segment. Nesting directories creates nested routes. For example, the `my_app/users/` package will create routes under the `/users` URL path.
12+
- Underscores (`_`) are replaced with hyphens (`-`) in paths by default, unless a path segment corresponds to a path parameter, in which case the underscore is preserved.
1213
- Special files mark application components: `holm` looks for specific filenames within your application directory to discover application components and compose your application.
1314
- `page.py`: Creates a publicly accessible URL for a route segment.
1415
- `layout.py`: Defines a shared UI that wraps a route segment and its children.
@@ -63,7 +64,7 @@ An `actions.py` file offers a dedicated place to define actions, which are flexi
6364
Actions are declared using the `@action` decorators (for example `@action.post()`). Their paths are always prefixed with the package's URL path.
6465

6566
- An action in `my_app/users/actions.py` (or `my_app/users/page.py`) decorated with `@action.post("/enable")` creates a route that handles `POST /users/enable` requests.
66-
- If no path is specified in the decorator, the action function's name is used. Decorating a `def disable(): ...` function without setting a path would create a route at `/users/disable`.
67+
- If no path is specified in the decorator, the action function's name is used with underscores (`_`) being replaced by hyphens (`-`). Decorating a `def deactivate_user(): ...` function without setting a path would create a route at `/users/deactivate-user`.
6768

6869
### APIs
6970

docs/guides/actions-with-htmx.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ Note: `holm` applies file-system based routing to both pages and actions! If bot
7474

7575
The action we create will be very simple, just a `welcome_message() -> str` function that returns a welcome message in a randomly chosen language. The returned message is just a string (which happens to be a `htmy` `Component`), which `holm` automatically renders into an HTML response.
7676

77-
The `welcome_message()` function is registered as an action using the `@action.get()` decorator. Since we don't pass a path to it, it will be registered under `/welcome_message`.
77+
The `welcome_message()` function is registered as an action using the `@action.get()` decorator. Since we don't pass a path to it and underscores (`_`) are replaced with hyphens (`-`) by default, it will be registered under `/welcome-message`.
7878

7979
```python hl_lines="15-16"
8080
import random
@@ -136,7 +136,7 @@ def page() -> Component:
136136
return html.div(
137137
html.h1(
138138
"Welcome to My App",
139-
hx_get="/welcome_message",
139+
hx_get="/welcome-message",
140140
hx_trigger="every 2s",
141141
),
142142
html.p("This is a minimal holm application demonstrating:"),

examples/actions-with-htmx/page.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ def page() -> Component:
99
return html.div(
1010
html.h1(
1111
"Welcome to My App",
12-
hx_get="/welcome_message",
12+
hx_get="/welcome-message",
1313
hx_trigger="every 2s",
1414
),
1515
html.p("This is a minimal holm application demonstrating:"),

holm/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
__version__ = "0.6.0"
1+
__version__ = "0.7.0"
22

33
from .app import App as App
44
from .fastapi import FastAPIDependency as FastAPIDependency

holm/_model.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,14 @@ def from_marker_file(cls, file_path: Path, *, config: AppConfig) -> PackageInfo:
172172
# That option is to use the `_name_` pattern instead and convert such package/directory
173173
# names to the `{name}` format here and use that as the URL segment.
174174
url = "/".join(
175-
(f"{{{p[1:-1]}}}" if len(p) > 2 and p[0] == p[-1] == "_" else p for p in url.split("/"))
175+
(
176+
f"{{{p[1:-1]}}}"
177+
if len(p) > 2 and p[0] == p[-1] == "_"
178+
else p.replace(
179+
"_", "-"
180+
) # Replace underscores with hyphens if the given part is not a path parameter.
181+
for p in url.split("/")
182+
)
176183
)
177184
return cls(package_dir=package_dir, package_name=package_name, url=url)
178185

holm/module_options/_actions.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -354,7 +354,7 @@ def _register_action(
354354
setattr(module, _actions_variable, module_actions)
355355

356356
if path is None:
357-
path = f"/{action_name}"
357+
path = f"/{action_name.replace('_', '-')}"
358358

359359
route_args["name"] = f"{action_module}.{action_name}"
360360
# Use the Action tag if no tags were set by the user.

test_app/api.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,5 @@ def list_urls(request: Request) -> list[str]:
1010
str(request.url_for("test_app.page")),
1111
str(request.url_for("test_app.calculator.page")),
1212
str(request.url_for("test_app.user.page")),
13-
str(request.url_for("test_app.user._id_.page", id="1")),
13+
str(request.url_for("test_app.user._user_id_.page", user_id="1")),
1414
]

test_app/user/_id_/layout.py

Lines changed: 0 additions & 5 deletions
This file was deleted.

0 commit comments

Comments
 (0)