Skip to main content

Add an is_url_safe() function

ID
3bc2ade
date
2025-12-05 13:17:49+00:00
author
Alex Chan <alex@alexwlchan.net>
parent
a9bcefa
message
Add an `is_url_safe()` function

Closes #3
changed files
4 files, 35 additions, 1 deletion

Changed files

CHANGELOG.md (1112) → CHANGELOG.md (1221)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 40e8dbe..c4f077d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,9 @@
 # CHANGELOG
 
+## v10 - 2025-12-05
+
+Add a new `is_url_safe()` function for checking if a path can be safely used in a URL.
+
 ## v9 - 2025-12-05
 
 This adds three models to `chives.media`: `ImageEntity`, `VideoEntity`, and `ImageEntity`.

src/chives/__init__.py (390) → src/chives/__init__.py (391)

diff --git a/src/chives/__init__.py b/src/chives/__init__.py
index a6f3dde..4a7fae3 100644
--- a/src/chives/__init__.py
+++ b/src/chives/__init__.py
@@ -11,4 +11,4 @@ I share across multiple sites.
 
 """
 
-__version__ = "9"
+__version__ = "10"

src/chives/urls.py (3204) → src/chives/urls.py (3470)

diff --git a/src/chives/urls.py b/src/chives/urls.py
index 7f161b0..93589d1 100644
--- a/src/chives/urls.py
+++ b/src/chives/urls.py
@@ -1,5 +1,6 @@
 """Code for manipulating and tidying URLs."""
 
+from pathlib import Path
 import re
 from typing import TypedDict
 
@@ -9,6 +10,8 @@ import hyperlink
 
 __all__ = [
     "clean_youtube_url",
+    "is_mastodon_host",
+    "is_url_safe",
     "parse_mastodon_post_url",
     "parse_tumblr_post_url",
 ]
@@ -126,3 +129,11 @@ def parse_tumblr_post_url(url: str) -> tuple[str, str]:
         return u.host.replace(".tumblr.com", ""), u.path[1]
 
     raise ValueError("Cannot parse Tumblr URL!")  # pragma: no cover
+
+
+def is_url_safe(path: str | Path) -> bool:
+    """
+    Returns True if a path is safe to use in a URL, False otherwise.
+    """
+    p = str(path)
+    return not ("?" in p or "#" in p or "%" in p)

tests/test_urls.py (3685) → tests/test_urls.py (4206)

diff --git a/tests/test_urls.py b/tests/test_urls.py
index 5666796..593903c 100644
--- a/tests/test_urls.py
+++ b/tests/test_urls.py
@@ -1,11 +1,14 @@
 """Tests for `chives.urls`."""
 
+from pathlib import Path
+
 import pytest
 from vcr.cassette import Cassette
 
 from chives.urls import (
     clean_youtube_url,
     is_mastodon_host,
+    is_url_safe,
     parse_mastodon_post_url,
     parse_tumblr_post_url,
 )
@@ -128,3 +131,19 @@ class TestIsMastodonHost:
         Other websites are not Mastodon servers.
         """
         assert not is_mastodon_host(host)
+
+
+class TestIsUrlSafe:
+    """
+    Tests for `is_url_safe`.
+    """
+
+    @pytest.mark.parametrize("path", ["example.txt", Path("a/b/cat.jpg")])
+    def test_safe(self, path: str | Path) -> None:
+        """Paths which are URL safe."""
+        assert is_url_safe(path)
+
+    @pytest.mark.parametrize("path", ["is it?", Path("cat%c.jpg"), "a#b"])
+    def test_unsafe(self, path: str | Path) -> None:
+        """Paths which are not URL safe."""
+        assert not is_url_safe(path)