diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 1dedac9..ab092dc 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -3,7 +3,7 @@ { "name": "Python 3", // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile - "image": "mcr.microsoft.com/devcontainers/python:1-3.12-bullseye", + "image": "mcr.microsoft.com/devcontainers/python:3-3.14-trixie", // Features to add to the dev container. More info: https://containers.dev/features. "features": { @@ -14,7 +14,7 @@ // "forwardPorts": [], // Use 'postCreateCommand' to run commands after the container is created. - "postCreateCommand": "pip3 install --user maturin pytest && python3 -m venv .venv" + "postCreateCommand": "python -m venv .venv && .venv/bin/pip install pytest && pipx install maturin && maturin develop" // Configure tool-specific properties. // "customizations": {}, diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 75afc32..4b12b15 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -1,7 +1,7 @@ -# This file is autogenerated by maturin v1.8.3 +# This file is autogenerated by maturin v1.12.3 # To update, run # -# maturin generate-ci --pytest github +# maturin generate-ci github # name: CI @@ -37,8 +37,8 @@ jobs: - runner: ubuntu-22.04 target: ppc64le steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 + - uses: actions/checkout@v6 + - uses: actions/setup-python@v6 with: python-version: 3.x - name: Build wheels @@ -49,35 +49,10 @@ jobs: sccache: ${{ !startsWith(github.ref, 'refs/tags/') }} manylinux: auto - name: Upload wheels - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: wheels-linux-${{ matrix.platform.target }} path: dist - - name: pytest - if: ${{ startsWith(matrix.platform.target, 'x86_64') }} - shell: bash - run: | - set -e - python3 -m venv .venv - source .venv/bin/activate - pip install urlpattern --find-links dist --force-reinstall - pip install pytest - pytest - - name: pytest - if: ${{ !startsWith(matrix.platform.target, 'x86') && matrix.platform.target != 'ppc64' }} - uses: uraimo/run-on-arch-action@v2 - with: - arch: ${{ matrix.platform.target }} - distro: ubuntu22.04 - githubToken: ${{ github.token }} - install: | - apt-get update - apt-get install -y --no-install-recommends python3 python3-pip - pip3 install -U pip pytest - run: | - set -e - pip3 install urlpattern --find-links dist --force-reinstall - pytest musllinux: runs-on: ${{ matrix.platform.runner }} @@ -93,8 +68,8 @@ jobs: - runner: ubuntu-22.04 target: armv7 steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 + - uses: actions/checkout@v6 + - uses: actions/setup-python@v6 with: python-version: 3.x - name: Build wheels @@ -105,40 +80,10 @@ jobs: sccache: ${{ !startsWith(github.ref, 'refs/tags/') }} manylinux: musllinux_1_2 - name: Upload wheels - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: wheels-musllinux-${{ matrix.platform.target }} path: dist - - name: pytest - if: ${{ startsWith(matrix.platform.target, 'x86_64') }} - uses: addnab/docker-run-action@v3 - with: - image: alpine:latest - options: -v ${{ github.workspace }}:/io -w /io - run: | - set -e - apk add py3-pip py3-virtualenv - python3 -m virtualenv .venv - source .venv/bin/activate - pip install urlpattern --no-index --find-links dist --force-reinstall - pip install pytest - pytest - - name: pytest - if: ${{ !startsWith(matrix.platform.target, 'x86') }} - uses: uraimo/run-on-arch-action@v2 - with: - arch: ${{ matrix.platform.target }} - distro: alpine_latest - githubToken: ${{ github.token }} - install: | - apk add py3-virtualenv - run: | - set -e - python3 -m virtualenv .venv - source .venv/bin/activate - pip install pytest - pip install urlpattern --find-links dist --force-reinstall - pytest windows: runs-on: ${{ matrix.platform.runner }} @@ -147,14 +92,19 @@ jobs: platform: - runner: windows-latest target: x64 + python_arch: x64 - runner: windows-latest target: x86 + python_arch: x86 + - runner: windows-11-arm + target: aarch64 + python_arch: arm64 steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 + - uses: actions/checkout@v6 + - uses: actions/setup-python@v6 with: - python-version: 3.x - architecture: ${{ matrix.platform.target }} + python-version: 3.13 + architecture: ${{ matrix.platform.python_arch }} - name: Build wheels uses: PyO3/maturin-action@v1 with: @@ -162,33 +112,23 @@ jobs: args: --release --out dist --find-interpreter sccache: ${{ !startsWith(github.ref, 'refs/tags/') }} - name: Upload wheels - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: wheels-windows-${{ matrix.platform.target }} path: dist - - name: pytest - if: ${{ !startsWith(matrix.platform.target, 'aarch64') }} - shell: bash - run: | - set -e - python3 -m venv .venv - source .venv/Scripts/activate - pip install urlpattern --find-links dist --force-reinstall - pip install pytest - pytest macos: runs-on: ${{ matrix.platform.runner }} strategy: matrix: platform: - - runner: macos-13 + - runner: macos-15-intel target: x86_64 - - runner: macos-14 + - runner: macos-latest target: aarch64 steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 + - uses: actions/checkout@v6 + - uses: actions/setup-python@v6 with: python-version: 3.x - name: Build wheels @@ -198,30 +138,22 @@ jobs: args: --release --out dist --find-interpreter sccache: ${{ !startsWith(github.ref, 'refs/tags/') }} - name: Upload wheels - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: wheels-macos-${{ matrix.platform.target }} path: dist - - name: pytest - run: | - set -e - python3 -m venv .venv - source .venv/bin/activate - pip install urlpattern --find-links dist --force-reinstall - pip install pytest - pytest sdist: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Build sdist uses: PyO3/maturin-action@v1 with: command: sdist args: --out dist - name: Upload sdist - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: wheels-sdist path: dist @@ -231,7 +163,7 @@ jobs: runs-on: ubuntu-latest if: ${{ startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch' }} needs: [linux, musllinux, windows, macos, sdist] - environment: release + environment: pypi permissions: # Use to sign the release artifacts id-token: write @@ -240,14 +172,14 @@ jobs: # Used to generate artifact attestation attestations: write steps: - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v6 - name: Generate artifact attestation - uses: actions/attest-build-provenance@v2 + uses: actions/attest-build-provenance@v3 with: subject-path: 'wheels-*/*' + - name: Install uv + if: ${{ startsWith(github.ref, 'refs/tags/') }} + uses: astral-sh/setup-uv@v7 - name: Publish to PyPI if: ${{ startsWith(github.ref, 'refs/tags/') }} - uses: PyO3/maturin-action@v1 - with: - command: upload - args: --non-interactive --skip-existing wheels-*/* + run: uv publish 'wheels-*/*' diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 06ece4f..fbed416 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -3,6 +3,7 @@ "charliermarsh.ruff", "github.vscode-github-actions", "ms-python.python", + "redhat.vscode-yaml", "rust-lang.rust-analyzer", "tamasfe.even-better-toml" ] diff --git a/.vscode/settings.json b/.vscode/settings.json index 8b30857..201a6d5 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,13 +1,8 @@ { + "python.defaultInterpreterPath": "${workspaceFolder}/.venv", "python.testing.pytestArgs": [ "tests" ], "python.testing.unittestEnabled": false, - "python.testing.pytestEnabled": true, - "python.analysis.typeCheckingMode": "basic", - "python.analysis.autoImportCompletions": true, - "python.analysis.inlayHints.callArgumentNames": "partial", - "python.analysis.inlayHints.functionReturnTypes": true, - "python.analysis.inlayHints.pytestParameters": true, - "python.analysis.inlayHints.variableTypes": true + "python.testing.pytestEnabled": true } \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 0d90500..e96328a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,12 +11,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "autocfg" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" - [[package]] name = "displaydoc" version = "0.2.5" @@ -91,9 +85,9 @@ checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" [[package]] name = "icu_properties" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e93fcd3157766c0c8da2f8cff6ce651a31f0810eaa1c51ec363ef790bbb5fb99" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" dependencies = [ "icu_collections", "icu_locale_core", @@ -105,9 +99,9 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02845b3647bb045f1100ecd6480ff52f34c35f82d9880e029d329c21d1054899" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" [[package]] name = "icu_provider" @@ -145,20 +139,11 @@ dependencies = [ "icu_properties", ] -[[package]] -name = "indoc" -version = "2.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" -dependencies = [ - "rustversion", -] - [[package]] name = "libc" -version = "0.2.177" +version = "0.2.182" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" +checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" [[package]] name = "litemap" @@ -168,18 +153,9 @@ checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" [[package]] name = "memchr" -version = "2.7.6" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" - -[[package]] -name = "memoffset" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" -dependencies = [ - "autocfg", -] +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "once_cell" @@ -195,9 +171,9 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "portable-atomic" -version = "1.11.1" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" [[package]] name = "potential_utf" @@ -210,44 +186,41 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.103" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] [[package]] name = "pyo3" -version = "0.27.1" +version = "0.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37a6df7eab65fc7bee654a421404947e10a0f7085b6951bf2ea395f4659fb0cf" +checksum = "cf85e27e86080aafd5a22eae58a162e133a589551542b3e5cee4beb27e54f8e1" dependencies = [ - "indoc", "libc", - "memoffset", "once_cell", "portable-atomic", "pyo3-build-config", "pyo3-ffi", "pyo3-macros", - "unindent", ] [[package]] name = "pyo3-build-config" -version = "0.27.1" +version = "0.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f77d387774f6f6eec64a004eac0ed525aab7fa1966d94b42f743797b3e395afb" +checksum = "8bf94ee265674bf76c09fa430b0e99c26e319c945d96ca0d5a8215f31bf81cf7" dependencies = [ "target-lexicon", ] [[package]] name = "pyo3-ffi" -version = "0.27.1" +version = "0.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dd13844a4242793e02df3e2ec093f540d948299a6a77ea9ce7afd8623f542be" +checksum = "491aa5fc66d8059dd44a75f4580a2962c1862a1c2945359db36f6c2818b748dc" dependencies = [ "libc", "pyo3-build-config", @@ -255,9 +228,9 @@ dependencies = [ [[package]] name = "pyo3-macros" -version = "0.27.1" +version = "0.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaf8f9f1108270b90d3676b8679586385430e5c0bb78bb5f043f95499c821a71" +checksum = "f5d671734e9d7a43449f8480f8b38115df67bef8d21f76837fa75ee7aaa5e52e" dependencies = [ "proc-macro2", "pyo3-macros-backend", @@ -267,9 +240,9 @@ dependencies = [ [[package]] name = "pyo3-macros-backend" -version = "0.27.1" +version = "0.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70a3b2274450ba5288bc9b8c1b69ff569d1d61189d4bff38f8d22e03d17f932b" +checksum = "22faaa1ce6c430a1f71658760497291065e6450d7b5dc2bcf254d49f66ee700a" dependencies = [ "heck", "proc-macro2", @@ -280,28 +253,26 @@ dependencies = [ [[package]] name = "python-urlpattern" -version = "0.1.4" +version = "0.1.10" dependencies = [ "pyo3", - "regex", - "url", "urlpattern", ] [[package]] name = "quote" -version = "1.0.42" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" dependencies = [ "proc-macro2", ] [[package]] name = "regex" -version = "1.12.2" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" dependencies = [ "aho-corasick", "memchr", @@ -311,9 +282,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", @@ -322,15 +293,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" - -[[package]] -name = "rustversion" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" [[package]] name = "serde" @@ -376,9 +341,9 @@ checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] name = "syn" -version = "2.0.110" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", @@ -398,9 +363,9 @@ dependencies = [ [[package]] name = "target-lexicon" -version = "0.13.3" +version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df7f62577c25e07834649fc3b39fafdc597c0a3527dc1c60129201ccfcbaa50c" +checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca" [[package]] name = "tinystr" @@ -412,64 +377,17 @@ dependencies = [ "zerovec", ] -[[package]] -name = "unic-char-property" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" -dependencies = [ - "unic-char-range", -] - -[[package]] -name = "unic-char-range" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" - -[[package]] -name = "unic-common" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" - -[[package]] -name = "unic-ucd-ident" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e230a37c0381caa9219d67cf063aa3a375ffed5bf541a452db16e744bdab6987" -dependencies = [ - "unic-char-property", - "unic-char-range", - "unic-ucd-version", -] - -[[package]] -name = "unic-ucd-version" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" -dependencies = [ - "unic-common", -] - [[package]] name = "unicode-ident" -version = "1.0.22" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" - -[[package]] -name = "unindent" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "url" -version = "2.5.7" +version = "2.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" dependencies = [ "form_urlencoded", "idna", @@ -479,13 +397,13 @@ dependencies = [ [[package]] name = "urlpattern" -version = "0.3.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70acd30e3aa1450bc2eece896ce2ad0d178e9c079493819301573dae3c37ba6d" +checksum = "df16f50ef4cc145211879a3867ba757076b25dfee812040dcb0658bd9ae7904b" dependencies = [ + "icu_properties", "regex", "serde", - "unic-ucd-ident", "url", ] diff --git a/Cargo.toml b/Cargo.toml index b0083be..4d13888 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,9 +1,9 @@ [package] name = "python-urlpattern" -version = "0.1.4" +version = "0.1.10" authors = ["방성범 (Bang Seongbeom) "] -edition = "2021" -description = "An implementation of the URL Pattern Standard for Python written in Rust" +edition = "2024" +description = "An implementation of the URL Pattern Standard for Python written in Rust." repository = "https://github.com/urlpattern/python-urlpattern" license = "MIT" keywords = ["urlpattern"] @@ -14,7 +14,5 @@ categories = ["web-programming"] crate-type = ["cdylib"] [dependencies] -pyo3 = "0.27.1" -regex = "1.11.0" -url = "2.5.0" -urlpattern = "0.3.0" +pyo3 = "0.28.2" +deno_urlpattern = { package = "urlpattern", version = "0.6.0" } diff --git a/README.md b/README.md index 277afd6..d7580b2 100644 --- a/README.md +++ b/README.md @@ -5,16 +5,40 @@ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff) [![CI](https://github.com/urlpattern/python-urlpattern/actions/workflows/CI.yml/badge.svg)](https://github.com/urlpattern/python-urlpattern/actions) -An implementation of [the URL Pattern Standard](https://urlpattern.spec.whatwg.org/) for Python written in Rust +An implementation of [the URL Pattern Standard](https://urlpattern.spec.whatwg.org/) for Python written in Rust. ## Introduction -It provides a pattern matching syntax like `/users/:id/`, similar to [Express](https://expressjs.com/) or [Path-to-RegExp](https://github.com/pillarjs/path-to-regexp) in Node.js. You can use it as a foundation to build your own web server or framework. +The URL Pattern Standard is a web standard for URL pattern matching. It is useful on the server side when serving different pages based on the URL (a.k.a. routing). It provides pattern matching syntax like `/users/:id`, similar to [route parameters in Express](https://expressjs.com/en/guide/routing.html#route-parameters) or [Path-to-RegExp](https://github.com/pillarjs/path-to-regexp). You can use it as a foundation to build your own web server or framework. It's a thin wrapper of [denoland/rust-urlpattern](https://github.com/denoland/rust-urlpattern) with [PyO3](https://github.com/PyO3/pyo3) + [Maturin](https://github.com/PyO3/maturin). +The naming conventions follow [the standard](https://urlpattern.spec.whatwg.org/) as closely as possible, similar to [xml.dom](https://docs.python.org/3/library/xml.dom.html). + +## Installation + +On Linux/UNIX or macOS: + +```sh +pip install urlpattern +``` + +On Windows: + +```sh +py -m pip install urlpattern +``` + +## Usage + +Check [urlpattern.pyi](https://github.com/urlpattern/python-urlpattern/blob/main/urlpattern.pyi) or the examples below. + +For various usage examples, you can also check [Chrome for Developers](https://developer.chrome.com/docs/web-platform/urlpattern) or [MDN](https://developer.mozilla.org/en-US/docs/Web/API/URL_Pattern_API) (you need to convert JavaScript into Python). + ## Examples +### `test` + ```py from urlpattern import URLPattern @@ -23,6 +47,8 @@ print(pattern.test("https://example.com/admin/main/")) # output: True print(pattern.test("https://example.com/main/")) # output: False ``` +### `exec` + ```py from urlpattern import URLPattern @@ -31,22 +57,71 @@ result = pattern.exec({"pathname": "/users/4163/"}) print(result["pathname"]["groups"]["id"]) # output: 4163 ``` -## Installation +### `baseURL` -On Linux/UNIX or macOS: +```py +from urlpattern import URLPattern -```sh -pip install urlpattern +pattern = URLPattern("b", "https://example.com/a/") +print(pattern.test("a/b", "https://example.com/")) # output: True +print(pattern.test("b", "https://example.com/a/")) # output: True +print( + pattern.test({"pathname": "b", "baseURL": "https://example.com/a/"}) +) # output: True ``` -On Windows: +### `ignoreCase` -```sh -py -m pip install urlpattern +```py +from urlpattern import URLPattern + +pattern = URLPattern("https://example.com/test") +print(pattern.test("https://example.com/test")) # output: True +print(pattern.test("https://example.com/TeST")) # output: False + +pattern = URLPattern("https://example.com/test", {"ignoreCase": True}) +print(pattern.test("https://example.com/test")) # output: True +print(pattern.test("https://example.com/TeST")) # output: True +``` + +### A simple WSGI app + +```py +from wsgiref.simple_server import make_server + +from urlpattern import URLPattern + +user_id_pattern = URLPattern({"pathname": "/users/:id"}) + + +def get_user_id(environ, start_response): + user_id = environ["result"]["pathname"]["groups"]["id"] + status = "200 OK" + response_headers = [("Content-type", "text/plain; charset=utf-8")] + start_response(status, response_headers) + return [f"{user_id=}".encode()] + + +def app(environ, start_response): + path = environ["PATH_INFO"] + method = environ["REQUEST_METHOD"] + + if result := user_id_pattern.exec({"pathname": path}): + if method == "GET": + return get_user_id(environ | {"result": result}, start_response) + + status = "404 Not Found" + response_headers = [("Content-type", "text/plain; charset=utf-8")] + start_response(status, response_headers) + return [b"Not Found"] + + +with make_server("", 8000, app) as httpd: + httpd.serve_forever() ``` ## Limitations Due to limitations in the dependency [denoland/rust-urlpattern](https://github.com/denoland/rust-urlpattern), it may not support all features specified in [the standard](https://urlpattern.spec.whatwg.org/). -Check the limitations in [`tests/test_lib.py`](tests/test_lib.py). +Check `pytest.skip` in [`tests/test_lib.py`](https://github.com/urlpattern/python-urlpattern/blob/main/tests/test_lib.py). diff --git a/pyproject.toml b/pyproject.toml index 031401a..ce28e98 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,10 +1,10 @@ [build-system] -requires = ["maturin>=1.7,<2.0"] +requires = ["maturin>=1.11,<2.0"] build-backend = "maturin" [project] name = "urlpattern" -description = "An implementation of the URL Pattern Standard for Python written in Rust" +description = "An implementation of the URL Pattern Standard for Python written in Rust." readme = "README.md" requires-python = ">=3.8" authors = [{ name = "방성범 (Bang Seongbeom)", email = "bangseongbeom@gmail.com" }] @@ -15,16 +15,18 @@ classifiers = [ "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", - "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Rust", + "Programming Language :: Python :: 3.14", + "Programming Language :: Python :: Free Threading", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", + "Programming Language :: Rust", "Topic :: Internet :: WWW/HTTP", "Typing :: Typed", ] @@ -34,6 +36,3 @@ dynamic = ["version"] Homepage = "https://github.com/urlpattern/python-urlpattern" Repository = "https://github.com/urlpattern/python-urlpattern.git" Issues = "https://github.com/urlpattern/python-urlpattern/issues" - -[tool.maturin] -features = ["pyo3/extension-module"] diff --git a/src/lib.rs b/src/lib.rs index 171807b..1dc8f6a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,34 +1,66 @@ #![allow(non_snake_case)] +use std::borrow::Cow; use std::collections::HashMap; use pyo3::{ + BoundObject, exceptions::PyValueError, prelude::*, types::{PyDict, PyList}, - BoundObject, }; -use urlpattern::UrlPatternOptions; -#[pyclass] -pub struct URLPattern(pub urlpattern::UrlPattern); +#[pyclass(name = "URLPattern")] +struct UrlPattern(deno_urlpattern::UrlPattern); #[pymethods] -impl URLPattern { +impl UrlPattern { #[new] - #[pyo3(signature = (input=None, baseURL=None))] - pub fn new(input: Option, baseURL: Option<&str>) -> PyResult { + #[pyo3(signature = (input=None, baseURL=None, options=None))] + pub fn new( + input: Option, + baseURL: Option<&Bound<'_, PyAny>>, + options: Option<&Bound<'_, PyDict>>, + ) -> PyResult { + let (base_url, options) = match baseURL { + Some(value) => { + if let Ok(options_dict) = value.cast::() { + (None, Some(options_dict)) + } else if value.is_none() { + (None, options) + } else { + (Some(value.extract::()?), options) + } + } + None => (None, options), + }; + let string_or_init_input = match input { - Some(input) => urlpattern::quirks::StringOrInit::try_from(input)?, - None => { - urlpattern::quirks::StringOrInit::Init(urlpattern::quirks::UrlPatternInit::default()) + Some(input) => deno_urlpattern::quirks::StringOrInit::try_from(input)?, + None => deno_urlpattern::quirks::StringOrInit::Init( + deno_urlpattern::quirks::UrlPatternInit::default(), + ), + }; + let options = if let Some(options) = options { + deno_urlpattern::UrlPatternOptions { + ignore_case: options + .get_item("ignoreCase")? + .map(|v| v.extract::()) + .transpose()? + .unwrap_or(false), + ..deno_urlpattern::UrlPatternOptions::default() } + } else { + deno_urlpattern::UrlPatternOptions::default() }; - Ok(URLPattern( - ::parse( - urlpattern::quirks::process_construct_pattern_input(string_or_init_input, baseURL) - .map_err(Error)?, - UrlPatternOptions::default(), + Ok(UrlPattern( + ::parse( + deno_urlpattern::quirks::process_construct_pattern_input( + string_or_init_input, + base_url.as_deref(), + ) + .map_err(Error)?, + options, ) .map_err(Error)?, )) @@ -48,15 +80,15 @@ impl URLPattern { } #[pyo3(signature = (input=None, baseURL=None))] - pub fn test(&self, input: Option, baseURL: Option<&str>) -> PyResult { + pub fn test(&self, input: Option, baseURL: Option<&str>) -> PyResult { let string_or_init_input = match input { - Some(input) => urlpattern::quirks::StringOrInit::try_from(input)?, - None => { - urlpattern::quirks::StringOrInit::Init(urlpattern::quirks::UrlPatternInit::default()) - } + Some(input) => deno_urlpattern::quirks::StringOrInit::try_from(input)?, + None => deno_urlpattern::quirks::StringOrInit::Init( + deno_urlpattern::quirks::UrlPatternInit::default(), + ), }; let Some((match_input, _)) = - urlpattern::quirks::process_match_input(string_or_init_input, baseURL) + deno_urlpattern::quirks::process_match_input(string_or_init_input, baseURL) .map_err(Error)? else { return Ok(false); @@ -67,17 +99,17 @@ impl URLPattern { #[pyo3(signature = (input=None, baseURL=None))] pub fn exec( &self, - input: Option, + input: Option, baseURL: Option<&str>, - ) -> PyResult> { + ) -> PyResult> { let string_or_init_input = match input { - Some(input) => urlpattern::quirks::StringOrInit::try_from(input)?, - None => { - urlpattern::quirks::StringOrInit::Init(urlpattern::quirks::UrlPatternInit::default()) - } + Some(input) => deno_urlpattern::quirks::StringOrInit::try_from(input)?, + None => deno_urlpattern::quirks::StringOrInit::Init( + deno_urlpattern::quirks::UrlPatternInit::default(), + ), }; let Some((match_input, inputs)) = - urlpattern::quirks::process_match_input(string_or_init_input, baseURL) + deno_urlpattern::quirks::process_match_input(string_or_init_input, baseURL) .map_err(Error)? else { return Ok(None); @@ -86,37 +118,37 @@ impl URLPattern { return Ok(None); }; - Ok(Some(URLPatternResult { + Ok(Some(UrlPatternResult { inputs, - protocol: URLPatternComponentResult { + protocol: UrlPatternComponentResult { input: result.protocol.input, groups: result.protocol.groups, }, - username: URLPatternComponentResult { + username: UrlPatternComponentResult { input: result.username.input, groups: result.username.groups, }, - password: URLPatternComponentResult { + password: UrlPatternComponentResult { input: result.password.input, groups: result.password.groups, }, - hostname: URLPatternComponentResult { + hostname: UrlPatternComponentResult { input: result.hostname.input, groups: result.hostname.groups, }, - port: URLPatternComponentResult { + port: UrlPatternComponentResult { input: result.port.input, groups: result.port.groups, }, - pathname: URLPatternComponentResult { + pathname: UrlPatternComponentResult { input: result.pathname.input, groups: result.pathname.groups, }, - search: URLPatternComponentResult { + search: UrlPatternComponentResult { input: result.search.input, groups: result.search.groups, }, - hash: URLPatternComponentResult { + hash: UrlPatternComponentResult { input: result.hash.input, groups: result.hash.groups, }, @@ -165,19 +197,21 @@ impl URLPattern { } #[derive(FromPyObject)] -pub enum URLPatternInput<'py> { +pub enum UrlPatternInput<'py> { String(String), Init(Bound<'py, PyDict>), } -impl<'py> TryFrom> for urlpattern::quirks::StringOrInit { +impl<'py> TryFrom> for deno_urlpattern::quirks::StringOrInit<'static> { type Error = pyo3::PyErr; - fn try_from(input: URLPatternInput<'py>) -> Result { + fn try_from(input: UrlPatternInput<'py>) -> Result { Ok(match input { - URLPatternInput::String(pattern) => urlpattern::quirks::StringOrInit::String(pattern), - URLPatternInput::Init(init) => { - urlpattern::quirks::StringOrInit::Init(urlpattern::quirks::UrlPatternInit { + UrlPatternInput::String(pattern) => { + deno_urlpattern::quirks::StringOrInit::String(Cow::Owned(pattern)) + } + UrlPatternInput::Init(init) => deno_urlpattern::quirks::StringOrInit::Init( + deno_urlpattern::quirks::UrlPatternInit { protocol: init .get_item("protocol")? .map(|v| v.extract::()) @@ -214,25 +248,28 @@ impl<'py> TryFrom> for urlpattern::quirks::StringOrInit { .get_item("baseURL")? .map(|v| v.extract::()) .transpose()?, - }) - } + }, + ), }) } } -pub struct URLPatternResult { - pub inputs: (urlpattern::quirks::StringOrInit, Option), - pub protocol: URLPatternComponentResult, - pub username: URLPatternComponentResult, - pub password: URLPatternComponentResult, - pub hostname: URLPatternComponentResult, - pub port: URLPatternComponentResult, - pub pathname: URLPatternComponentResult, - pub search: URLPatternComponentResult, - pub hash: URLPatternComponentResult, +pub struct UrlPatternResult { + pub inputs: ( + deno_urlpattern::quirks::StringOrInit<'static>, + Option, + ), + pub protocol: UrlPatternComponentResult, + pub username: UrlPatternComponentResult, + pub password: UrlPatternComponentResult, + pub hostname: UrlPatternComponentResult, + pub port: UrlPatternComponentResult, + pub pathname: UrlPatternComponentResult, + pub search: UrlPatternComponentResult, + pub hash: UrlPatternComponentResult, } -impl<'py> IntoPyObject<'py> for URLPatternResult { +impl<'py> IntoPyObject<'py> for UrlPatternResult { type Target = PyDict; type Output = Bound<'py, Self::Target>; type Error = std::convert::Infallible; @@ -244,10 +281,10 @@ impl<'py> IntoPyObject<'py> for URLPatternResult { let list = PyList::empty(py); match string_or_init { - urlpattern::quirks::StringOrInit::String(string) => { - list.append(string).unwrap(); + deno_urlpattern::quirks::StringOrInit::String(string) => { + list.append(string.into_owned()).unwrap(); } - urlpattern::quirks::StringOrInit::Init(init) => { + deno_urlpattern::quirks::StringOrInit::Init(init) => { let init_dict = PyDict::new(py); if let Some(protocol) = init.protocol { init_dict.set_item("protocol", protocol).unwrap(); @@ -300,12 +337,12 @@ impl<'py> IntoPyObject<'py> for URLPatternResult { } #[derive(IntoPyObject, IntoPyObjectRef)] -pub struct URLPatternComponentResult { +pub struct UrlPatternComponentResult { input: String, groups: HashMap>, } -pub struct Error(urlpattern::Error); +pub struct Error(deno_urlpattern::Error); impl From for PyErr { fn from(error: Error) -> Self { @@ -313,16 +350,15 @@ impl From for PyErr { } } -impl From for Error { - fn from(other: urlpattern::Error) -> Self { +impl From for Error { + fn from(other: deno_urlpattern::Error) -> Self { Self(other) } } /// A Python module implemented in Rust. #[pymodule] -#[pyo3(name = "urlpattern")] -pub fn python_urlpattern(m: &Bound<'_, PyModule>) -> PyResult<()> { - m.add_class::()?; - Ok(()) +mod urlpattern { + #[pymodule_export] + use super::UrlPattern; } diff --git a/tests/test_lib.py b/tests/test_lib.py index 65848cc..b44c67d 100644 --- a/tests/test_lib.py +++ b/tests/test_lib.py @@ -2,6 +2,7 @@ import pathlib import pytest + from urlpattern import URLPattern # This test is based on the web-platform-tests Project. @@ -11,25 +12,17 @@ # 1. Go to https://github.com/web-platform-tests/wpt/blob/master/urlpattern/resources/urlpatterntestdata.json. # 2. Copy the content. # 3. Paste into `tests/urlpatterntestdata.json`. -urlpatterntestdata_path = pathlib.Path("tests/urlpatterntestdata.json") -urlpatterntestdata = json.loads(urlpatterntestdata_path.read_text("utf-8")) +urlpatterntestdata = json.loads( + pathlib.Path("tests/urlpatterntestdata.json").read_text("utf-8") +) @pytest.mark.parametrize("entry", urlpatterntestdata) def test(entry): - if len(entry["pattern"]) == 2 and isinstance(entry["pattern"][1], dict): - pytest.xfail("unsupported parameter") - - if len(entry["pattern"]) == 3 and isinstance(entry["pattern"][2], dict): - pytest.xfail("unsupported parameter") + if entry["pattern"] == [{"pathname": "*{}**?"}]: + pytest.skip("unsupported in the implementation") if entry.get("expected_obj") == "error": - if ( - isinstance(entry["pattern"][0], dict) - and entry["pattern"][0].get("hostname") == "bad\\:hostname" - ): - pytest.xfail("unknown") - with pytest.raises(Exception): URLPattern(*entry["pattern"]) return @@ -37,35 +30,33 @@ def test(entry): try: pattern = URLPattern(*entry["pattern"]) - except ValueError: - pytest.xfail("unsupported regular expression") + except UnicodeEncodeError as e: + if e.reason == "surrogates not allowed": + pytest.skip("unsupported in the implementation") + raise if "expected_obj" in entry: for key in entry["expected_obj"]: - if getattr(pattern, key) in ("", "/") and entry["expected_obj"][key] == "*": - assert True - - else: - assert getattr(pattern, key) == entry["expected_obj"][key] + assert getattr(pattern, key) == entry["expected_obj"][key] if entry.get("expected_match") == "error": with pytest.raises(Exception): pattern.exec(*entry["inputs"]) return - elif isinstance(entry.get("expected_match"), dict): + if isinstance(entry.get("expected_match"), dict): result = pattern.exec(*entry["inputs"]) for key in entry["expected_match"]: - if key != "inputs": - for group in entry["expected_match"][key]["groups"]: - if entry["expected_match"][key]["groups"][group] is None: - pytest.xfail("unsupported undefined group") + assert result[key] == entry["expected_match"][key] - if "exactly_empty_components" in entry: - if key not in entry["exactly_empty_components"]: - continue + else: + result = pattern.exec(*entry["inputs"]) + assert result is None + + if "exactly_empty_components" in entry: + result = pattern.exec(*entry["inputs"]) - else: - assert result - assert result[key] == entry["expected_match"][key] + for component in entry["exactly_empty_components"]: + if result: + assert result[component]["groups"] == {} diff --git a/tests/urlpatterntestdata.json b/tests/urlpatterntestdata.json index aeb0cd6..ebc171c 100644 --- a/tests/urlpatterntestdata.json +++ b/tests/urlpatterntestdata.json @@ -1202,10 +1202,11 @@ { "pattern": [{ "protocol": "http", "port": "80 " }], "inputs": [{ "protocol": "http", "port": "80" }], - "exactly_empty_components": ["port"], - "expected_match": { - "protocol": { "input": "http", "groups": {} } - } + "expected_obj": { + "protocol": "http", + "port": "80" + }, + "expected_match": null }, { "pattern": [{ "protocol": "http", "port": "100000" }], @@ -1229,6 +1230,34 @@ "port": { "input": "80", "groups": {}} } }, + { + "pattern": [{ "port": "80" }], + "inputs": [{ "port": "8\t0" }], + "expected_match": { + "port": { "input": "80", "groups": {}} + } + }, + { + "pattern": [{ "port": "80" }], + "inputs": [{ "port": "80x" }], + "expected_match": { + "port": { "input": "80", "groups": {}} + } + }, + { + "pattern": [{ "port": "80" }], + "inputs": [{ "port": "80?x" }], + "expected_match": { + "port": { "input": "80", "groups": {}} + } + }, + { + "pattern": [{ "port": "80" }], + "inputs": [{ "port": "80\\x" }], + "expected_match": { + "port": { "input": "80", "groups": {}} + } + }, { "pattern": [{ "port": "(.*)" }], "inputs": [{ "port": "invalid80" }], @@ -1874,7 +1903,17 @@ { "pattern": [ "https://{sub.}?example{.com/}foo" ], "inputs": [ "https://example.com/foo" ], - "expected_obj": "error" + "exactly_empty_components": [ "port" ], + "expected_obj": { + "protocol": "https", + "hostname": "{sub.}?example.com", + "pathname": "*" + }, + "expected_match": { + "protocol": { "input": "https", "groups": {} }, + "hostname": { "input": "example.com", "groups": {} }, + "pathname": { "input": "/foo", "groups": { "0": "/foo" } } + } }, { "pattern": [ "{https://}example.com/foo" ], @@ -2952,5 +2991,132 @@ "pattern": [{ "pathname": "/([\\d&&[0-1]])" }], "inputs": [{ "pathname": "/3" }], "expected_match": null + }, + { + "pattern": [{ "protocol": "http", "hostname": "example.com/ignoredpath" }], + "inputs": ["http://example.com/"], + "expected_obj": { + "protocol": "http", + "hostname": "example.com", + "pathname": "*" + }, + "expected_match": { + "protocol": { "input": "http", "groups": {} }, + "hostname": { "input": "example.com", "groups": {} }, + "pathname": { "input": "/", "groups": { "0": "/" } } + } + }, + { + "pattern": [{ "protocol": "http", "hostname": "example.com\\?ignoredsearch" }], + "inputs": ["http://example.com/"], + "expected_obj": { + "protocol": "http", + "hostname": "example.com", + "search": "*" + }, + "expected_match": { + "protocol": { "input": "http", "groups": {} }, + "hostname": { "input": "example.com", "groups": {} }, + "pathname": { "input": "/", "groups": { "0": "/" } } + } + }, + { + "pattern": [{ "protocol": "http", "hostname": "example.com#ignoredhash" }], + "inputs": ["http://example.com/"], + "expected_obj": { + "protocol": "http", + "hostname": "example.com", + "hash": "*" + }, + "expected_match": { + "protocol": { "input": "http", "groups": {} }, + "hostname": { "input": "example.com", "groups": {} }, + "pathname": { "input": "/", "groups": { "0": "/" } } + } + }, + { + "pattern": ["https://www.example.com/*"], + "inputs": ["https://www.example.com/x"], + "exactly_empty_components": ["port"], + "expected_obj": { + "protocol": "https", + "hostname": "www.example.com", + "pathname": "/*" + }, + "expected_match": { + "protocol": { "input": "https", "groups": {} }, + "hostname": { "input": "www.example.com", "groups": {} }, + "pathname": { "input": "/x", "groups": { "0": "x" } } + } + }, + { + "pattern": ["https://www.example.com/*"], + "inputs": ["https://www.example.com/xyz"], + "exactly_empty_components": ["port"], + "expected_obj": { + "protocol": "https", + "hostname": "www.example.com", + "pathname": "/*" + }, + "expected_match": { + "protocol": { "input": "https", "groups": {} }, + "hostname": { "input": "www.example.com", "groups": {} }, + "pathname": { "input": "/xyz", "groups": { "0": "xyz" } } + } + }, + { + "pattern": ["https://www.example.com/*"], + "inputs": ["https://www.example.com/example"], + "exactly_empty_components": ["port"], + "expected_obj": { + "protocol": "https", + "hostname": "www.example.com", + "pathname": "/*" + }, + "expected_match": { + "protocol": { "input": "https", "groups": {} }, + "hostname": { "input": "www.example.com", "groups": {} }, + "pathname": { "input": "/example", "groups": { "0": "example" } } + } + }, + { + "pattern": ["https://www.example.com/*"], + "inputs": ["https://www.example.com/text"], + "exactly_empty_components": ["port"], + "expected_obj": { + "protocol": "https", + "hostname": "www.example.com", + "pathname": "/*" + }, + "expected_match": { + "protocol": { "input": "https", "groups": {} }, + "hostname": { "input": "www.example.com", "groups": {} }, + "pathname": { "input": "/text", "groups": { "0": "text" } } + } + }, + { + "pattern": ["https://www.example.com/*"], + "inputs": ["https://www.example.com/path/with/x"], + "exactly_empty_components": ["port"], + "expected_obj": { + "protocol": "https", + "hostname": "www.example.com", + "pathname": "/*" + }, + "expected_match": { + "protocol": { "input": "https", "groups": {} }, + "hostname": { "input": "www.example.com", "groups": {} }, + "pathname": { "input": "/path/with/x", "groups": { "0": "path/with/x" } } + } + }, + { + "pattern": [{ "hostname": ":domain(.*)" }], + "inputs": [{ "hostname": "localhost" }], + "expected_obj": { + "hostname": ":domain(.*)" + }, + "expected_match": { + "hostname": { "input": "localhost", "groups": { "domain" : "localhost"} } + } } ] \ No newline at end of file diff --git a/urlpattern.pyi b/urlpattern.pyi index 7d85965..73fb90e 100644 --- a/urlpattern.pyi +++ b/urlpattern.pyi @@ -1,16 +1,65 @@ -from typing_extensions import TypeAlias, TypedDict, overload +from typing import Optional, TypedDict, Union, overload -URLPatternInput: TypeAlias = str | URLPatternInit +from typing_extensions import TypeAlias + +URLPatternInput: TypeAlias = Union[str, URLPatternInit] + +class URLPatternOptions(TypedDict, total=False): + ignoreCase: bool class URLPattern: @overload - def __init__(self, input: URLPatternInput, baseURL: str): ... + def __init__( + self, + input: str, + baseURL: str, + options: URLPatternOptions = {}, + ) -> None: ... + @overload + def __init__( + self, + input: str, + baseURL: str, + options: None, + ) -> None: ... + @overload + def __init__( + self, + input: URLPatternInit, + baseURL: str, + options: URLPatternOptions = {}, + ) -> None: ... + @overload + def __init__( + self, + input: URLPatternInit, + baseURL: str, + options: None, + ) -> None: ... + @overload + def __init__(self, input: str, options: URLPatternOptions = {}) -> None: ... + @overload + def __init__(self, input: str, options: None) -> None: ... + @overload + def __init__( + self, input: URLPatternInit = {}, options: URLPatternOptions = {} + ) -> None: ... + @overload + def __init__(self, input: URLPatternInit, options: None) -> None: ... + @overload + def test(self, input: str, baseURL: Optional[str] = None) -> bool: ... + @overload + def test( + self, input: URLPatternInit = {}, baseURL: Optional[str] = None + ) -> bool: ... + @overload + def exec( + self, input: str, baseURL: Optional[str] = None + ) -> Optional[URLPatternResult]: ... @overload - def __init__(self, input: URLPatternInput = {}): ... - def test(self, input: URLPatternInput = {}, baseURL: str | None = None) -> bool: ... def exec( - self, input: URLPatternInput = {}, baseURL: str | None = None - ) -> URLPatternResult | None: ... + self, input: URLPatternInit = {}, baseURL: Optional[str] = None + ) -> Optional[URLPatternResult]: ... @property def protocol(self) -> str: ... @property @@ -55,4 +104,4 @@ class URLPatternComponentResult(TypedDict): input: str groups: dict[str, str] -URLPatternCompatible: TypeAlias = str | URLPatternInit | URLPattern +URLPatternCompatible: TypeAlias = Union[str, URLPatternInit, URLPattern]