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..1d062a5 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -1,4 +1,4 @@ -# This file is autogenerated by maturin v1.8.3 +# This file is autogenerated by maturin v1.10.2 # To update, run # # maturin generate-ci --pytest github @@ -231,7 +231,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 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 77c0638..b8a2a95 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,24 +4,18 @@ version = 4 [[package]] name = "aho-corasick" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] [[package]] name = "autocfg" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" - -[[package]] -name = "cfg-if" -version = "1.0.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "displaydoc" @@ -36,9 +30,9 @@ dependencies = [ [[package]] name = "form_urlencoded" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" dependencies = [ "percent-encoding", ] @@ -51,21 +45,22 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "icu_collections" -version = "1.5.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" dependencies = [ "displaydoc", + "potential_utf", "yoke", "zerofrom", "zerovec", ] [[package]] -name = "icu_locid" -version = "1.5.0" +name = "icu_locale_core" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" dependencies = [ "displaydoc", "litemap", @@ -74,104 +69,66 @@ dependencies = [ "zerovec", ] -[[package]] -name = "icu_locid_transform" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_locid_transform_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_locid_transform_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" - [[package]] name = "icu_normalizer" -version = "1.5.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" dependencies = [ - "displaydoc", "icu_collections", "icu_normalizer_data", "icu_properties", "icu_provider", "smallvec", - "utf16_iter", - "utf8_iter", - "write16", "zerovec", ] [[package]] name = "icu_normalizer_data" -version = "1.5.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" [[package]] name = "icu_properties" -version = "1.5.1" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +checksum = "e93fcd3157766c0c8da2f8cff6ce651a31f0810eaa1c51ec363ef790bbb5fb99" dependencies = [ - "displaydoc", "icu_collections", - "icu_locid_transform", + "icu_locale_core", "icu_properties_data", "icu_provider", - "tinystr", + "zerotrie", "zerovec", ] [[package]] name = "icu_properties_data" -version = "1.5.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" +checksum = "02845b3647bb045f1100ecd6480ff52f34c35f82d9880e029d329c21d1054899" [[package]] name = "icu_provider" -version = "1.5.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" dependencies = [ "displaydoc", - "icu_locid", - "icu_provider_macros", - "stable_deref_trait", - "tinystr", + "icu_locale_core", "writeable", "yoke", "zerofrom", + "zerotrie", "zerovec", ] -[[package]] -name = "icu_provider_macros" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "idna" -version = "1.0.3" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" dependencies = [ "idna_adapter", "smallvec", @@ -180,9 +137,9 @@ dependencies = [ [[package]] name = "idna_adapter" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" dependencies = [ "icu_normalizer", "icu_properties", @@ -190,27 +147,30 @@ dependencies = [ [[package]] name = "indoc" -version = "2.0.6" +version = "2.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd" +checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" +dependencies = [ + "rustversion", +] [[package]] name = "libc" -version = "0.2.171" +version = "0.2.178" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" +checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" [[package]] name = "litemap" -version = "0.7.5" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" [[package]] name = "memchr" -version = "2.7.4" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "memoffset" @@ -223,38 +183,46 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.21.1" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d75b0bedcc4fe52caa0e03d9f1151a323e4aa5e2d78ba3580400cd3c9e2bc4bc" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "percent-encoding" -version = "2.3.1" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "portable-atomic" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" + +[[package]] +name = "potential_utf" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +dependencies = [ + "zerovec", +] [[package]] name = "proc-macro2" -version = "1.0.94" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" dependencies = [ "unicode-ident", ] [[package]] name = "pyo3" -version = "0.24.1" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17da310086b068fbdcefbba30aeb3721d5bb9af8db4987d6735b2183ca567229" +checksum = "ab53c047fcd1a1d2a8820fe84f05d6be69e9526be40cb03b73f86b6b03e6d87d" dependencies = [ - "cfg-if", "indoc", "libc", "memoffset", @@ -268,19 +236,18 @@ dependencies = [ [[package]] name = "pyo3-build-config" -version = "0.24.1" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e27165889bd793000a098bb966adc4300c312497ea25cf7a690a9f0ac5aa5fc1" +checksum = "b455933107de8642b4487ed26d912c2d899dec6114884214a0b3bb3be9261ea6" dependencies = [ - "once_cell", "target-lexicon", ] [[package]] name = "pyo3-ffi" -version = "0.24.1" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05280526e1dbf6b420062f3ef228b78c0c54ba94e157f5cb724a609d0f2faabc" +checksum = "1c85c9cbfaddf651b1221594209aed57e9e5cff63c4d11d1feead529b872a089" dependencies = [ "libc", "pyo3-build-config", @@ -288,9 +255,9 @@ dependencies = [ [[package]] name = "pyo3-macros" -version = "0.24.1" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c3ce5686aa4d3f63359a5100c62a127c9f15e8398e5fdeb5deef1fed5cd5f44" +checksum = "0a5b10c9bf9888125d917fb4d2ca2d25c8df94c7ab5a52e13313a07e050a3b02" dependencies = [ "proc-macro2", "pyo3-macros-backend", @@ -300,9 +267,9 @@ dependencies = [ [[package]] name = "pyo3-macros-backend" -version = "0.24.1" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4cf6faa0cbfb0ed08e89beb8103ae9724eb4750e3a78084ba4017cbe94f3855" +checksum = "03b51720d314836e53327f5871d4c0cfb4fb37cc2c4a11cc71907a86342c40f9" dependencies = [ "heck", "proc-macro2", @@ -313,28 +280,26 @@ dependencies = [ [[package]] name = "python-urlpattern" -version = "0.1.3" +version = "0.1.5" dependencies = [ "pyo3", - "regex", - "url", "urlpattern", ] [[package]] name = "quote" -version = "1.0.40" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" dependencies = [ "proc-macro2", ] [[package]] name = "regex" -version = "1.11.1" +version = "1.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" dependencies = [ "aho-corasick", "memchr", @@ -344,9 +309,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.9" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" dependencies = [ "aho-corasick", "memchr", @@ -355,24 +320,40 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.5" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "serde" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -381,21 +362,21 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.14.0" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "stable_deref_trait" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] name = "syn" -version = "2.0.100" +version = "2.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" +checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" dependencies = [ "proc-macro2", "quote", @@ -404,9 +385,9 @@ dependencies = [ [[package]] name = "synstructure" -version = "0.13.1" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", @@ -415,66 +396,25 @@ dependencies = [ [[package]] name = "target-lexicon" -version = "0.13.2" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e502f78cdbb8ba4718f566c418c52bc729126ffd16baee5baa718cf25dd5a69a" +checksum = "df7f62577c25e07834649fc3b39fafdc597c0a3527dc1c60129201ccfcbaa50c" [[package]] name = "tinystr" -version = "0.7.6" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" dependencies = [ "displaydoc", "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.18" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "unindent" @@ -484,58 +424,46 @@ checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3" [[package]] name = "url" -version = "2.5.4" +version = "2.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" dependencies = [ "form_urlencoded", "idna", "percent-encoding", + "serde", ] [[package]] name = "urlpattern" -version = "0.3.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70acd30e3aa1450bc2eece896ce2ad0d178e9c079493819301573dae3c37ba6d" +checksum = "957a88ad1abd5d13336275adb17d4f9b6a2404f3baed2e075e0b026dc0b2b58d" dependencies = [ + "icu_properties", "regex", "serde", - "unic-ucd-ident", "url", ] -[[package]] -name = "utf16_iter" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" - [[package]] name = "utf8_iter" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" -[[package]] -name = "write16" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" - [[package]] name = "writeable" -version = "0.5.5" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" [[package]] name = "yoke" -version = "0.7.5" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" dependencies = [ - "serde", "stable_deref_trait", "yoke-derive", "zerofrom", @@ -543,9 +471,9 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.7.5" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", @@ -574,11 +502,22 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zerotrie" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + [[package]] name = "zerovec" -version = "0.10.4" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" dependencies = [ "yoke", "zerofrom", @@ -587,9 +526,9 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.10.3" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index a525e72..d2fd6a4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,9 +1,9 @@ [package] name = "python-urlpattern" -version = "0.1.4" +version = "0.1.5" 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.24.1" -regex = "1.11.0" -url = "2.5.0" -urlpattern = "0.3.0" +pyo3 = "0.27.2" +deno_urlpattern = { package = "urlpattern", version = "0.4.1" } diff --git a/README.md b/README.md index 277afd6..46f32f9 100644 --- a/README.md +++ b/README.md @@ -5,16 +5,38 @@ [![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. 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). +## Installation + +On Linux/UNIX or macOS: + +```sh +pip install urlpattern +``` + +On Windows: + +```sh +py -m pip install urlpattern +``` + +## Usage + +Check [urlpattern.pyi](urlpattern.pyi). + ## Examples +For various usage examples, refer to [Chrome for Developers](https://developer.chrome.com/docs/web-platform/urlpattern) or [MDN](https://developer.mozilla.org/en-US/docs/Web/API/URLPattern) (you may need to convert JavaScript into Python). + +### `test` + ```py from urlpattern import URLPattern @@ -23,6 +45,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 +55,45 @@ result = pattern.exec({"pathname": "/users/4163/"}) print(result["pathname"]["groups"]["id"]) # output: 4163 ``` -## Installation +### `ignoreCase` -On Linux/UNIX or macOS: +```py +from urlpattern import URLPattern -```sh -pip install 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 ``` -On Windows: +### Simple WSGI app -```sh -py -m pip install urlpattern +```py +from urlpattern import URLPattern +from wsgiref.simple_server import make_server + + +user_id_pattern = URLPattern({"pathname": "/users/:id"}) + + +def app(environ, start_response): + path = environ["PATH_INFO"] + + if result := user_id_pattern.exec({"pathname": path}): + user_id = result["pathname"]["groups"]["id"] + start_response("200 OK", [("Content-Type", "text/plain; charset=utf-8")]) + return [f"{user_id=}".encode()] + + +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 the `pytest.skip` in [`tests/test_lib.py`](tests/test_lib.py). diff --git a/pyproject.toml b/pyproject.toml index 031401a..0639a66 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,10 +1,10 @@ [build-system] -requires = ["maturin>=1.7,<2.0"] +requires = ["maturin>=1.10,<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" }] @@ -21,10 +21,11 @@ classifiers = [ "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Rust", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", + "Programming Language :: Rust", "Topic :: Internet :: WWW/HTTP", "Typing :: Typed", ] diff --git a/src/lib.rs b/src/lib.rs index 171807b..d50f5bd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,32 +3,63 @@ 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 +79,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 +98,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 +117,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 +196,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 { 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(pattern) + } + UrlPatternInput::Init(init) => deno_urlpattern::quirks::StringOrInit::Init( + deno_urlpattern::quirks::UrlPatternInit { protocol: init .get_item("protocol")? .map(|v| v.extract::()) @@ -214,25 +247,25 @@ 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, 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 +277,10 @@ impl<'py> IntoPyObject<'py> for URLPatternResult { let list = PyList::empty(py); match string_or_init { - urlpattern::quirks::StringOrInit::String(string) => { + deno_urlpattern::quirks::StringOrInit::String(string) => { list.append(string).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 +333,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 +346,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..6f8b2c0 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,30 @@ 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"]) + assert result 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 + 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"] == {} \ No newline at end of file diff --git a/tests/urlpatterntestdata.json b/tests/urlpatterntestdata.json index aeb0cd6..6b3fe0d 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,122 @@ "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" } } + } } ] \ No newline at end of file diff --git a/urlpattern.pyi b/urlpattern.pyi index 7d85965..1e2e266 100644 --- a/urlpattern.pyi +++ b/urlpattern.pyi @@ -2,11 +2,21 @@ from typing_extensions import TypeAlias, TypedDict, overload URLPatternInput: TypeAlias = str | URLPatternInit +class URLPatternOptions(TypedDict, total=False): + ignoreCase: bool + class URLPattern: @overload - def __init__(self, input: URLPatternInput, baseURL: str): ... + def __init__( + self, + input: URLPatternInput, + baseURL: str, + options: URLPatternOptions | None = None, + ): ... @overload - def __init__(self, input: URLPatternInput = {}): ... + def __init__( + self, input: URLPatternInput, options: URLPatternOptions | None = None + ): ... def test(self, input: URLPatternInput = {}, baseURL: str | None = None) -> bool: ... def exec( self, input: URLPatternInput = {}, baseURL: str | None = None