{"id":37073064,"url":"https://github.com/miute/urlstd","last_synced_at":"2026-01-14T08:34:36.664Z","repository":{"id":37940042,"uuid":"445694114","full_name":"miute/urlstd","owner":"miute","description":"An implementation of the WHATWG URL Standard in Python","archived":false,"fork":false,"pushed_at":"2025-12-15T17:47:08.000Z","size":916,"stargazers_count":3,"open_issues_count":6,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-12-19T00:37:09.987Z","etag":null,"topics":["python","url-parser","url-parsing","whatwg-url"],"latest_commit_sha":null,"homepage":"https://miute.github.io/urlstd/","language":"HTML","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/miute.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2022-01-08T01:14:23.000Z","updated_at":"2024-01-23T17:26:04.000Z","dependencies_parsed_at":"2023-10-17T04:38:06.717Z","dependency_job_id":"4bb38fcd-7957-4bc2-8335-dc150d08b789","html_url":"https://github.com/miute/urlstd","commit_stats":{"total_commits":64,"total_committers":3,"mean_commits":"21.333333333333332","dds":0.4375,"last_synced_commit":"264d1ab186ebdebe78badf2b089bfb3e4e592ec6"},"previous_names":["miute/url-standard"],"tags_count":6,"template":false,"template_full_name":null,"purl":"pkg:github/miute/urlstd","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/miute%2Furlstd","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/miute%2Furlstd/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/miute%2Furlstd/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/miute%2Furlstd/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/miute","download_url":"https://codeload.github.com/miute/urlstd/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/miute%2Furlstd/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28414418,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-14T08:31:27.429Z","status":"ssl_error","status_checked_at":"2026-01-14T08:31:19.098Z","response_time":107,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["python","url-parser","url-parsing","whatwg-url"],"created_at":"2026-01-14T08:34:35.977Z","updated_at":"2026-01-14T08:34:36.653Z","avatar_url":"https://github.com/miute.png","language":"HTML","funding_links":[],"categories":[],"sub_categories":[],"readme":"# urlstd\n\n[![PyPI](https://img.shields.io/pypi/v/urlstd)](https://pypi.org/project/urlstd/)\n[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/urlstd)](https://pypi.org/project/urlstd/)\n[![PyPI - License](https://img.shields.io/pypi/l/urlstd)](https://pypi.org/project/urlstd/)\n[![CI](https://github.com/miute/urlstd/actions/workflows/main.yml/badge.svg)](https://github.com/miute/urlstd/actions/workflows/main.yml)\n[![codecov](https://codecov.io/gh/miute/urlstd/branch/main/graph/badge.svg?token=XJGM09H5TS)](https://codecov.io/gh/miute/urlstd)\n\n`urlstd` is a Python implementation of the WHATWG [URL Living Standard](https://url.spec.whatwg.org/).\n\nThis library provides `URL` class, `URLSearchParams` class, and low-level APIs that comply with the URL specification.\n\n## Supported APIs\n\n- [URL class](https://url.spec.whatwg.org/#url-class)\n  - class urlstd.parse.`URL(url: str, base: Optional[str | URL] = None)`\n    - [canParse](https://url.spec.whatwg.org/#dom-url-canparse): classmethod `can_parse(url: str, base: Optional[str | URL] = None) -\u003e bool`\n    - stringifier: `__str__() -\u003e str`\n    - [href](https://url.spec.whatwg.org/#dom-url-href): `readonly property href: str`\n    - [origin](https://url.spec.whatwg.org/#dom-url-origin): `readonly property origin: str`\n    - [protocol](https://url.spec.whatwg.org/#dom-url-protocol): `property protocol: str`\n    - [username](https://url.spec.whatwg.org/#dom-url-username): `property username: str`\n    - [password](https://url.spec.whatwg.org/#dom-url-password): `property password: str`\n    - [host](https://url.spec.whatwg.org/#dom-url-host): `property host: str`\n    - [hostname](https://url.spec.whatwg.org/#dom-url-hostname): `property hostname: str`\n    - [port](https://url.spec.whatwg.org/#dom-url-port): `property port: str`\n    - [pathname](https://url.spec.whatwg.org/#dom-url-pathname): `property pathname: str`\n    - [search](https://url.spec.whatwg.org/#dom-url-search): `property search: str`\n    - [searchParams](https://url.spec.whatwg.org/#dom-url-searchparams): `readonly property search_params: URLSearchParams`\n    - [hash](https://url.spec.whatwg.org/#dom-url-hash): `property hash: str`\n    - [URL equivalence](https://url.spec.whatwg.org/#url-equivalence): `__eq__(other: Any) -\u003e bool` and `equals(other: URL, exclude_fragments: bool = False) → bool`\n\n- [URLSearchParams class](https://url.spec.whatwg.org/#interface-urlsearchparams)\n  - class urlstd.parse.`URLSearchParams(init: Optional[str | Sequence[Sequence[str | int | float]] | dict[str, str | int | float] | URLRecord | URLSearchParams] = None)`\n    - [size](https://url.spec.whatwg.org/#dom-urlsearchparams-size): `__len__() -\u003e int`\n    - [append](https://url.spec.whatwg.org/#dom-urlsearchparams-append): `append(name: str, value: str | int | float) -\u003e None`\n    - [delete](https://url.spec.whatwg.org/#dom-urlsearchparams-delete): `delete(name: str, value: Optional[str | int | float] = None) -\u003e None`\n    - [get](https://url.spec.whatwg.org/#dom-urlsearchparams-get): `get(name: str) -\u003e str | None`\n    - [getAll](https://url.spec.whatwg.org/#dom-urlsearchparams-getall): `get_all(name: str) -\u003e tuple[str, ...]`\n    - [has](https://url.spec.whatwg.org/#dom-urlsearchparams-has): `has(name: str, value: Optional[str | int | float] = None) -\u003e bool`\n    - [set](https://url.spec.whatwg.org/#dom-urlsearchparams-set): `set(name: str, value: str | int | float) -\u003e None`\n    - [sort](https://url.spec.whatwg.org/#dom-urlsearchparams-sort): `sort() -\u003e None`\n    - iterable\u003cUSVString, USVString\u003e: `__iter__() -\u003e Iterator[tuple[str, str]]`\n    - [stringifier](https://url.spec.whatwg.org/#urlsearchparams-stringification-behavior): `__str__() -\u003e str`\n\n- Low-level APIs\n\n  - [URL parser](https://url.spec.whatwg.org/#concept-url-parser)\n    - urlstd.parse.`parse_url(urlstring: str, base: Optional[str | URLRecord] = None, encoding: str = \"utf-8\") -\u003e URLRecord`\n\n  - [basic URL parser](https://url.spec.whatwg.org/#concept-basic-url-parser)\n    - class urlstd.parse.`BasicURLParser`\n      - classmethod `parse(urlstring: str, base: Optional[URLRecord] = None, encoding: str = \"utf-8\", url: Optional[URLRecord] = None, state_override: Optional[URLParserState] = None) -\u003e URLRecord`\n\n  - [URL record](https://url.spec.whatwg.org/#concept-url)\n    - class urlstd.parse.`URLRecord`\n      - [scheme](https://url.spec.whatwg.org/#concept-url-scheme): `property scheme: str = \"\"`\n      - [username](https://url.spec.whatwg.org/#concept-url-username): `property username: str = \"\"`\n      - [password](https://url.spec.whatwg.org/#concept-url-password): `property password: str = \"\"`\n      - [host](https://url.spec.whatwg.org/#concept-url-host): `property host: Optional[str | int | tuple[int, ...]] = None`\n      - [port](https://url.spec.whatwg.org/#concept-url-port): `property port: Optional[int] = None`\n      - [path](https://url.spec.whatwg.org/#concept-url-path): `property path: list[str] | str = []`\n      - [query](https://url.spec.whatwg.org/#concept-url-query): `property query: Optional[str] = None`\n      - [fragment](https://url.spec.whatwg.org/#concept-url-fragment): `property fragment: Optional[str] = None`\n      - [origin](https://url.spec.whatwg.org/#concept-url-origin): `readonly property origin: Origin | None`\n      - [is special](https://url.spec.whatwg.org/#is-special): `is_special() -\u003e bool`\n      - [is not special](https://url.spec.whatwg.org/#is-not-special): `is_not_special() -\u003e bool`\n      - [includes credentials](https://url.spec.whatwg.org/#include-credentials): `includes_credentials() -\u003e bool`\n      - [has an opaque path](https://url.spec.whatwg.org/#url-opaque-path): `has_opaque_path() -\u003e bool`\n      - [cannot have a username/password/port](https://url.spec.whatwg.org/#cannot-have-a-username-password-port): `cannot_have_username_password_port() -\u003e bool`\n      - [URL serializer](https://url.spec.whatwg.org/#concept-url-serializer): `serialize_url(exclude_fragment: bool = False) -\u003e str`\n      - [host serializer](https://url.spec.whatwg.org/#concept-host-serializer): `serialize_host() -\u003e str`\n      - [URL path serializer](https://url.spec.whatwg.org/#url-path-serializer): `serialize_path() -\u003e str`\n      - [URL equivalence](https://url.spec.whatwg.org/#url-equivalence): `__eq__(other: Any) -\u003e bool` and `equals(other: URLRecord, exclude_fragments: bool = False) → bool`\n\n  - [Hosts (domains and IP addresses)](https://url.spec.whatwg.org/#hosts-(domains-and-ip-addresses))\n    - class urlstd.parse.`IDNA`\n      - [domain to ASCII](https://url.spec.whatwg.org/#concept-domain-to-ascii): classmethod `domain_to_ascii(domain: str, be_strict: bool = False) -\u003e str`\n      - [domain to Unicode](https://url.spec.whatwg.org/#concept-domain-to-unicode): classmethod `domain_to_unicode(domain: str, be_strict: bool = False) -\u003e str`\n    - class urlstd.parse.`Host`\n      - [host parser](https://url.spec.whatwg.org/#concept-host-parser): classmethod `parse(host: str, is_not_special: bool = False) -\u003e str | int | tuple[int, ...]`\n      - [host serializer](https://url.spec.whatwg.org/#concept-host-serializer): classmethod `serialize(host: str | int | Sequence[int]) -\u003e str`\n\n  - [percent-decode a string](https://url.spec.whatwg.org/#string-percent-decode)\n    - urlstd.parse.`string_percent_decode(s: str) -\u003e bytes`\n\n  - [percent-encode after encoding](https://url.spec.whatwg.org/#string-percent-encode-after-encoding)\n    - urlstd.parse.`string_percent_encode(s: str, safe: str, encoding: str = \"utf-8\", space_as_plus: bool = False) -\u003e str`\n\n  - [application/x-www-form-urlencoded parser](https://url.spec.whatwg.org/#concept-urlencoded-parser)\n    - urlstd.parse.`parse_qsl(query: bytes) -\u003e list[tuple[str, str]]`\n\n  - [application/x-www-form-urlencoded serializer](https://url.spec.whatwg.org/#concept-urlencoded-serializer)\n    - urlstd.parse.`urlencode(query: Sequence[tuple[str, str]], encoding: str = \"utf-8\") -\u003e str`\n\n  - Validation\n    - class urlstd.parse.`HostValidator`\n      - [valid host string](https://url.spec.whatwg.org/#valid-host-string): classmethod `is_valid(host: str) -\u003e bool`\n      - [valid domain string](https://url.spec.whatwg.org/#valid-domain-string): classmethod `is_valid_domain(domain: str) -\u003e bool`\n      - [valid IPv4-address string](https://url.spec.whatwg.org/#valid-ipv4-address-string): classmethod `is_valid_ipv4_address(address: str) -\u003e bool`\n      - [valid IPv6-address string](https://url.spec.whatwg.org/#valid-ipv6-address-string): classmethod `is_valid_ipv6_address(address: str) -\u003e bool`\n    - class urlstd.parse.`URLValidator`\n      - [valid URL string](https://url.spec.whatwg.org/#valid-url-string): classmethod `is_valid(urlstring: str, base: Optional[str | URLRecord] = None, encoding: str = \"utf-8\") -\u003e bool`\n      - valid [URL-scheme string](https://url.spec.whatwg.org/#url-scheme-string): classmethod `is_valid_url_scheme(value: str) -\u003e bool`\n\n- Compatibility with standard library `urllib`\n  - urlstd.parse.`urlparse(urlstring: str, base: str = None, encoding: str = \"utf-8\", allow_fragments: bool = True) -\u003e urllib.parse.ParseResult`\n\n    `urlstd.parse.urlparse()` ia an alternative to `urllib.parse.urlparse()`.\n    Parses a string representation of a URL using the basic URL parser, and returns `urllib.parse.ParseResult`.\n\n## Basic Usage\n\nTo parse a string into a `URL`:\n\n```python\nfrom urlstd.parse import URL\nURL('http://user:pass@foo:21/bar;par?b#c')\n# → \u003cURL(href='http://user:pass@foo:21/bar;par?b#c', origin='http://foo:21', protocol='http:', username='user', password='pass', host='foo:21', hostname='foo', port='21', pathname='/bar;par', search='?b', hash='#c')\u003e\n```\n\nTo parse a string into a `URL` with using a base URL:\n\n```python\nurl = URL('?ﬃ\u0026🌈', base='http://example.org')\nurl  # → \u003cURL(href='http://example.org/?%EF%AC%83\u0026%F0%9F%8C%88', origin='http://example.org', protocol='http:', username='', password='', host='example.org', hostname='example.org', port='', pathname='/', search='?%EF%AC%83\u0026%F0%9F%8C%88', hash='')\u003e\nurl.search  # → '?%EF%AC%83\u0026%F0%9F%8C%88'\nparams = url.search_params\nparams  # → URLSearchParams([('ﬃ', ''), ('🌈', '')])\nparams.sort()\nparams  # → URLSearchParams([('🌈', ''), ('ﬃ', '')])\nurl.search  # → '?%F0%9F%8C%88=\u0026%EF%AC%83='\nstr(url)  # → 'http://example.org/?%F0%9F%8C%88=\u0026%EF%AC%83='\n```\n\nTo validate a URL string:\n\n```python\nfrom urlstd.parse import URL, URLValidator, ValidityState\nURL.can_parse('https://user:password@example.org/')  # → True\nURLValidator.is_valid('https://user:password@example.org/')  # → False\nvalidity = ValidityState()\nURLValidator.is_valid('https://user:password@example.org/', validity=validity)\nvalidity.valid  # → False\nvalidity.validation_errors  # → 1\nvalidity.descriptions[0]  # → \"invalid-credentials: input includes credentials: 'https://user:password@example.org/' at position 21\"\n```\n\n```python\nURL.can_parse('file:///C|/demo')  # → True\nURLValidator.is_valid('file:///C|/demo')  # → False\nvalidity = ValidityState()\nURLValidator.is_valid('file:///C|/demo', validity=validity)  # → False\nvalidity.valid  # → False\nvalidity.validation_errors  # → 1\nvalidity.descriptions[0]  # → \"invalid-URL-unit: code point is found that is not a URL unit: U+007C (|) in 'file:///C|/demo' at position 9\"\n```\n\nTo parse a string into a `urllib.parse.ParseResult` with using a base URL:\n\n```python\nimport html\nfrom urllib.parse import unquote\nfrom urlstd.parse import urlparse\npr = urlparse('?aÿb', base='http://example.org/foo/', encoding='utf-8')\npr  # → ParseResult(scheme='http', netloc='example.org', path='/foo/', params='', query='a%C3%BFb', fragment='')\nunquote(pr.query)  # → 'aÿb'\npr = urlparse('?aÿb', base='http://example.org/foo/', encoding='windows-1251')\npr  # → ParseResult(scheme='http', netloc='example.org', path='/foo/', params='', query='a%26%23255%3Bb', fragment='')\nunquote(pr.query, encoding='windows-1251')  # → 'a\u0026#255;b'\nhtml.unescape('a\u0026#255;b')  # → 'aÿb'\npr = urlparse('?aÿb', base='http://example.org/foo/', encoding='windows-1252')\npr  # → ParseResult(scheme='http', netloc='example.org', path='/foo/', params='', query='a%FFb', fragment='')\nunquote(pr.query, encoding='windows-1252')  # → 'aÿb'\n```\n\n## Logging\n\n`urlstd` uses standard library [logging](https://docs.python.org/3/library/logging.html) for [validation error](https://url.spec.whatwg.org/#validation-error).\nChange the logger log level of `urlstd` if needed:\n\n```python\nlogging.getLogger('urlstd').setLevel(logging.ERROR)\n```\n\n## Dependencies\n\n- [icupy](https://pypi.org/project/icupy/) \u003e= 0.11.0 ([pre-built packages](https://github.com/miute/icupy/releases) are available)\n  - `icupy` requirements:\n    - [ICU4C](https://github.com/unicode-org/icu/releases) ([ICU - International Components for Unicode](https://icu.unicode.org/)) - latest version recommended\n    - C++17 compatible compiler (see [supported compilers](https://github.com/pybind/pybind11#supported-compilers))\n    - [CMake](https://cmake.org/) \u003e= 3.7\n\n## Installation\n\n1. Configuring environment variables for icupy (ICU):\n    - Windows:\n      - Set the `ICU_ROOT` environment variable to the root of the ICU installation (default is `C:\\icu`).\n        For example, if the ICU is located in `C:\\icu4c`:\n\n        ```bat\n        set ICU_ROOT=C:\\icu4c\n        ```\n\n        or in PowerShell:\n\n        ```bat\n        $env:ICU_ROOT = \"C:\\icu4c\"\n        ```\n\n      - To verify settings using *icuinfo (64 bit)*:\n\n        ```bat\n        %ICU_ROOT%\\bin64\\icuinfo\n        ```\n\n        or in PowerShell:\n\n        ```bat\n        \u0026 $env:ICU_ROOT\\bin64\\icuinfo\n        ```\n\n    - Linux/POSIX:\n      - If the ICU is located in a non-regular place, set the `PKG_CONFIG_PATH` and `LD_LIBRARY_PATH` environment variables.\n        For example, if the ICU is located in `/usr/local`:\n\n        ```bash\n        export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig:$PKG_CONFIG_PATH\n        export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH\n        ```\n\n      - To verify settings using *pkg-config*:\n\n        ```bash\n        $ pkg-config --cflags --libs icu-uc\n        -I/usr/local/include -L/usr/local/lib -licuuc -licudata\n        ```\n\n2. Installing from PyPI:\n\n    ```bash\n    pip install urlstd\n    ```\n\n## Running Tests\n\nInstall dependencies:\n\n```bash\npipx install tox\n# or\npip install --user tox\n```\n\nTo run tests and generate a report:\n\n```bash\ngit clone https://github.com/miute/urlstd.git\ncd urlstd\ntox -e wpt\n```\n\nSee result: [tests/wpt/report.html](https://htmlpreview.github.io/?https://github.com/miute/urlstd/blob/main/tests/wpt/report.html)\n\n## License\n\n[MIT License](https://github.com/miute/urlstd/blob/main/LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmiute%2Furlstd","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmiute%2Furlstd","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmiute%2Furlstd/lists"}