Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-uritools for openSUSE:Factory 
checked in at 2026-05-20 15:24:24
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-uritools (Old)
 and      /work/SRC/openSUSE:Factory/.python-uritools.new.1966 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-uritools"

Wed May 20 15:24:24 2026 rev:8 rq:1354095 version:6.1.1

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-uritools/python-uritools.changes  
2026-04-26 21:14:12.656524718 +0200
+++ 
/work/SRC/openSUSE:Factory/.python-uritools.new.1966/python-uritools.changes    
    2026-05-20 15:25:16.163989018 +0200
@@ -1,0 +2,9 @@
+Tue May 19 20:38:51 UTC 2026 - Dirk Müller <[email protected]>
+
+- update to 6.1.1:
+  * Minor documentation improvements.
+  * Update build environment.
+  * Add type stubs.
+  * Improve unit tests.
+
+-------------------------------------------------------------------

Old:
----
  uritools-6.0.2.tar.gz

New:
----
  uritools-6.1.1.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-uritools.spec ++++++
--- /var/tmp/diff_new_pack.VDC6UL/_old  2026-05-20 15:25:17.188031214 +0200
+++ /var/tmp/diff_new_pack.VDC6UL/_new  2026-05-20 15:25:17.188031214 +0200
@@ -18,7 +18,7 @@
 
 %{?sle15_python_module_pythons}
 Name:           python-uritools
-Version:        6.0.2
+Version:        6.1.1
 Release:        0
 Summary:        URI parsing, classification and composition
 License:        MIT
@@ -26,7 +26,8 @@
 URL:            https://github.com/tkem/uritools/
 Source:         
https://files.pythonhosted.org/packages/source/u/uritools/uritools-%{version}.tar.gz
 BuildRequires:  %{python_module pip}
-BuildRequires:  %{python_module setuptools}
+BuildRequires:  %{python_module setuptools >= 78}
+BuildRequires:  %{python_module setuptools-scm >= 8.2}
 BuildRequires:  %{python_module wheel}
 BuildRequires:  fdupes
 BuildRequires:  python-rpm-macros

++++++ uritools-6.0.2.tar.gz -> uritools-6.1.1.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/uritools-6.0.2/.github/FUNDING.yml 
new/uritools-6.1.1/.github/FUNDING.yml
--- old/uritools-6.0.2/.github/FUNDING.yml      1970-01-01 01:00:00.000000000 
+0100
+++ new/uritools-6.1.1/.github/FUNDING.yml      2026-05-09 21:51:08.000000000 
+0200
@@ -0,0 +1,2 @@
+github: [tkem]
+custom: ["https://www.paypal.me/tkem";]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/uritools-6.0.2/.github/ISSUE_TEMPLATE/bug_report.md 
new/uritools-6.1.1/.github/ISSUE_TEMPLATE/bug_report.md
--- old/uritools-6.0.2/.github/ISSUE_TEMPLATE/bug_report.md     1970-01-01 
01:00:00.000000000 +0100
+++ new/uritools-6.1.1/.github/ISSUE_TEMPLATE/bug_report.md     2026-05-09 
21:51:08.000000000 +0200
@@ -0,0 +1,29 @@
+---
+name: Bug report
+about: Create a report to help us improve
+title: ''
+labels: bug
+assignees: tkem
+
+---
+
+Before reporting a bug, please make sure you have the latest `uritools` 
version installed:
+```
+pip install --upgrade uritools
+```
+
+**Describe the bug**
+A clear and concise description of what the bug is.
+
+**Expected result**
+A clear and concise description of what you expected to happen.
+
+**Actual result**
+A clear and concise description of what happened instead.
+
+**Reproduction steps**
+
+```python
+import uritools
+
+```
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/uritools-6.0.2/.github/ISSUE_TEMPLATE/config.yml 
new/uritools-6.1.1/.github/ISSUE_TEMPLATE/config.yml
--- old/uritools-6.0.2/.github/ISSUE_TEMPLATE/config.yml        1970-01-01 
01:00:00.000000000 +0100
+++ new/uritools-6.1.1/.github/ISSUE_TEMPLATE/config.yml        2026-05-09 
21:51:08.000000000 +0200
@@ -0,0 +1 @@
+blank_issues_enabled: false
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/uritools-6.0.2/.github/ISSUE_TEMPLATE/feature_request.md 
new/uritools-6.1.1/.github/ISSUE_TEMPLATE/feature_request.md
--- old/uritools-6.0.2/.github/ISSUE_TEMPLATE/feature_request.md        
1970-01-01 01:00:00.000000000 +0100
+++ new/uritools-6.1.1/.github/ISSUE_TEMPLATE/feature_request.md        
2026-05-09 21:51:08.000000000 +0200
@@ -0,0 +1,10 @@
+---
+name: Feature request
+about: Suggest an idea for this project
+title: ''
+labels: enhancement
+assignees: tkem
+
+---
+
+Sorry, but `uritools` is not accepting feature requests at this time.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/uritools-6.0.2/.github/SECURITY.md 
new/uritools-6.1.1/.github/SECURITY.md
--- old/uritools-6.0.2/.github/SECURITY.md      1970-01-01 01:00:00.000000000 
+0100
+++ new/uritools-6.1.1/.github/SECURITY.md      2026-05-09 21:51:08.000000000 
+0200
@@ -0,0 +1,13 @@
+# Security Policy
+
+## Supported Versions
+
+Security updates are applied only to the latest release.
+
+## Reporting a Vulnerability
+
+If you have discovered a security vulnerability in this project, please report 
it privately. **Do not disclose it as a public issue.** This gives us time to 
work with you to fix the issue before public exposure, reducing the chance that 
the exploit will be used before a patch is released.
+
+Please disclose it at [security 
advisory](https://github.com/tkem/uritools/security/advisories/new).
+
+This project is maintained by a single person on a best effort basis. As such, 
vulnerability reports will be investigated and fixed or disclosed as soon as 
possible, but there may be delays in response time due to the maintainer's 
other commitments.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/uritools-6.0.2/.github/copilot-instructions.md 
new/uritools-6.1.1/.github/copilot-instructions.md
--- old/uritools-6.0.2/.github/copilot-instructions.md  1970-01-01 
01:00:00.000000000 +0100
+++ new/uritools-6.1.1/.github/copilot-instructions.md  2026-05-18 
20:08:33.000000000 +0200
@@ -0,0 +1,76 @@
+# uritools Project Guidelines
+
+## Overview
+
+RFC 3986 compliant URI parsing library replacing Python's `urllib.parse`. 
Requires Python >= 3.10, zero runtime dependencies.
+
+- **Single-file design**: Everything in `src/uritools/__init__.py` — do not 
create submodules
+- **Dual type support**: All public functions handle both `str` and `bytes` 
via `SplitResultString` / `SplitResultBytes`
+- **Public API**: Defined in `__all__` — changes are breaking
+- **Type stubs**: `src/uritools/__init__.pyi` — keep in sync with runtime API
+
+## Architecture
+
+Functions return `namedtuple` subclasses (`SplitResult`, `DefragResult`) with 
both tuple access and getter methods (`.gethost()`, `.getport()`, 
`.getquerydict()`). Preserve both interfaces when modifying.
+
+Encoding uses cached lookup tables (`_encoded` dict) with uppercase hex digits 
per RFC 3986. Safe characters vary by component (`_SAFE_PATH`, `_SAFE_QUERY`, 
etc.).
+
+Type dispatch pattern used throughout:
+```python
+if isinstance(uristring, bytes):
+    result = SplitResultBytes
+else:
+    result = SplitResultString
+```
+
+### Composition rules (`uricompose()`)
+- Scheme: `[A-Za-z][A-Za-z0-9+.-]*`, lowercased
+- Path with authority must start with `/` or be empty
+- Path without authority cannot start with `//`
+- Relative paths with `:` in first segment get `./` prefix
+- Query accepts `str`, `bytes`, `dict`, or list of tuples
+- Invalid host types must raise `TypeError`, not leak `AttributeError`
+
+### IP address handling
+- Use `ipaddress` module; reject IPvFuture (addresses starting with `v`)
+- IPv6 bracketed in output: `[2001:db8::1]`, always compressed form
+
+### Design decisions (intentional — do not "fix")
+- `getscheme()` always returns `str` (decodes bytes schemes)
+- `getfragment()` and other `get*()` methods decode to `str` by default, even 
for bytes input
+- `gethost()` returns `""` for empty host when `default=None`, but returns 
`default` when it's not None
+- `SplitResultBytes` / `SplitResultString` are semi-private (not in `__all__`)
+
+## Development
+
+### Running checks
+```bash
+tox              # all environments (recommended)
+tox -e py        # tests with coverage
+tox -e ruff-format  # formatting (ruff format)
+tox -e ruff      # linting (ruff check)
+tox -e pyright   # type checking
+tox -e docs      # Sphinx HTML build
+tox -e doctest   # Sphinx doctest
+```
+
+Use `tox` or `PYTHONPATH=src python3 -m pytest tests/` to test against the 
workspace source (not the system-installed package).
+
+### Testing conventions
+- `unittest.TestCase` with `check()` helpers — not pytest-style
+- Test both `str` and `bytes` variants of every case
+- Test RFC 3986 examples explicitly in `test_rfc3986()` methods
+- Edge cases in separate `test_abnormal()` methods
+- Maintain 100% line coverage
+- Avoid shadowing built-ins (`dict`, `input`) in loop variables
+
+### Documentation
+- Sphinx docs in `docs/`, built with `tox -e docs`
+- `README.rst` examples must work as doctests
+- Docstrings describe types in prose (no type annotations in signatures)
+- Directive body content uses 3-space indentation in `docs/index.rst`
+
+### Changelog
+- `CHANGELOG.rst` uses `vX.Y.Z (YYYY-MM-DD)` headers with `===` underlines
+- Unreleased changes go under `vX.Y.Z (UNRELEASED)`
+- Each entry is a `- ` bullet (imperative mood, no trailing period)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/uritools-6.0.2/.github/copilot-review.md 
new/uritools-6.1.1/.github/copilot-review.md
--- old/uritools-6.0.2/.github/copilot-review.md        1970-01-01 
01:00:00.000000000 +0100
+++ new/uritools-6.1.1/.github/copilot-review.md        2026-05-18 
20:08:33.000000000 +0200
@@ -0,0 +1,158 @@
+# Code, Tests & Docs Review
+
+**Date:** 2026-05-18
+**Version:** 6.1.1
+**Python:** 3.13.7
+**Scope:** `src/uritools/__init__.py`, `src/uritools/__init__.pyi`, `tests/`, 
`docs/`, `README.rst`
+
+## Checks Summary
+
+| Check      | Result |
+|------------|--------|
+| Tests      | 45 passed |
+| Coverage   | 100% (394 statements) |
+| Lint       | Clean (ruff check) |
+| Format     | Clean (ruff format) |
+| Type check | 0 errors (pyright) |
+| Docs build | OK (sphinx html) |
+| Doctests   | 10 passed |
+
+---
+
+## Code Findings
+
+### C1. Unbounded cache growth in `uriencode()` (low risk)
+
+`_encoded` dict grows without limit when callers pass arbitrary `safe` values. 
The existing FIXME (line 79) acknowledges this. Consider `@functools.lru_cache` 
or capping the dict size. In practice, the library only uses a handful of fixed 
`safe` values internally, so risk is low unless users call `uriencode()` 
directly with dynamic `safe` arguments.
+
+### C2. `DefragResult.getfragment()` always returns `str` (design note)
+
+The FIXME at line 129 notes that `getfragment()` decodes to `str` by default 
even when `geturi()` returns `bytes`. This is consistent with 
`SplitResult.getfragment()` and all other `get*()` methods, so changing it 
would be a breaking API change. The FIXME can be considered resolved or 
intentional.
+
+### C3. `getscheme()` always returns `str` (design note)
+
+Similar FIXME at line 203: `getscheme()` decodes bytes schemes to `str`. The 
stub file already types this as `-> str | None`, confirming this is intentional 
behavior.
+
+### C4. `SplitResultBytes` / `SplitResultString` visibility (minor)
+
+Both classes have a `# TODO: make private?` comment. They are not in `__all__` 
so they are already semi-private. Making them fully private (prefixing with 
`_`) would be a minor cleanup but could break users who import them directly.
+
+### C5. `gethost()` returns empty string for empty host
+
+`gethost(default=X)` returns `default` when host is empty *and* default is not 
None, but returns `""` when default is None. This edge case (`"file:///"` -> 
`gethost()` returns `""`) is tested and documented, but the asymmetry between 
`default=None` and `default="x"` may be unexpected.
+
+### C6. `_AUTHORITY_RE_*` port group matches empty string
+
+The regex `(?::([0-9]*))?$` captures an empty port string for URIs like 
`http://host:`. This is handled correctly downstream (`_port()` returns `b""` 
for empty port, `getport()` returns default), but the empty-string-vs-None 
distinction for port is a subtle edge.
+
+### C8. `getauthority()` TODO about efficiency (trivial)
+
+Line 224: `# TODO: this could be much more efficient by using a dedicated 
regex` — `getauthority()` calls three separate getters. Could be 
micro-optimized with a single regex, but unlikely to matter in practice.
+
+---
+
+## Test Findings
+
+### Good practices observed
+- All public functions tested with both `str` and `bytes` input
+- RFC 3986 examples tested exhaustively (Sections 3, 5.4.1, 5.4.2)
+- Edge cases and abnormal inputs covered thoroughly
+- `check()` helpers enforce round-trip consistency (parse → recompose)
+- IPv4, IPv6, IPv4-mapped IPv6, and IPvFuture rejection all tested
+- Error cases test `TypeError` vs `ValueError` distinction
+- Cross-type `urijoin` (bytes base + str ref, etc.) tested
+- 100% line coverage maintained
+- Python 3.13 IPv4-mapped address representation change handled in 
`test_ipv4_mapped_literal` with dual expected values
+
+### T1. `uricompose` with `fragment` parameter
+
+Only tested implicitly via the RFC 3986 example in `test_rfc3986`. No 
dedicated test for fragment encoding (e.g., `fragment="foo bar"` producing 
`#foo%20bar`).
+
+### T2. `uricompose` with non-default `encoding`
+
+No test passes a non-default `encoding` to `uricompose()`. For example, 
composing with `encoding="latin-1"` and non-ASCII path characters.
+
+### T3. `uricompose` authority override with bytes
+
+`test_authority_override` only uses `str` values. No test overrides authority 
subcomponents with `bytes` kwargs.
+
+### T4. `uriunsplit` with mixed types
+
+The type dispatch in `uriunsplit()` uses `path` type to select the result 
class. Mixed types (e.g., str scheme + bytes path) could produce unexpected 
results but are not tested. Consider documenting this as unsupported or adding 
a guard.
+
+### T5. `DefragResult.getfragment()` default parameter
+
+No test passes a non-None `default` to `DefragResult.getfragment()` (e.g., 
`uridefrag("").getfragment(default="fallback")`).
+
+### T6. `getauthority` with bytes default tuple
+
+`test_encoding_none` tests str and bytes URI input, but `getauthority()` with 
a bytes default tuple is not tested.
+
+---
+
+## Documentation Findings
+
+### D3. `docs/index.rst`: no documentation for `uriencode`/`uridecode` 
parameters
+
+The `uriencode` and `uridecode` docs describe input/output types but do not 
document the `encoding`, `errors`, or `safe` parameters individually. Users 
must read the function signature and docstring. Consider adding parameter 
descriptions.
+
+### D4. `docs/index.rst`: `SplitResult` attribute table missing `Index` for 
properties
+
+The `urisplit` attribute table has empty `Index` cells for `userinfo`, `host`, 
and `port`. This is intentional (they are properties, not tuple fields), but it 
could be clearer with a note like "n/a" or "(property)".
+
+### D6. `docs/conf.py`: `master_doc` deprecated
+
+`master_doc` is deprecated in Sphinx 4.0+ in favor of `root_doc`. Works fine 
currently but will eventually produce a warning.
+
+### D7. `docs/index.rst`: `DefragResult` and `SplitResult` documented twice
+
+These classes are first described in the "URI Decomposition" section (inline 
attribute tables) and then again in "Structured Parse Results" via `.. 
autoclass::` with `:members:`. This provides the best of both worlds (table 
overview + detailed API), but the duplication could confuse readers scanning 
for the complete API of these classes.
+
+### D8. Changelog entry format guidance
+
+The `CHANGELOG.rst` format is documented in `copilot-instructions.md` 
(imperative mood bullets under `vX.Y.Z (YYYY-MM-DD)` headers with `===` 
underlines). No separate `CONTRIBUTING.md` exists, but the conventions are 
clear from the instructions file.
+
+---
+
+## Type Stub Findings
+
+### S1. `DefragResult` and `SplitResult` stub inheritance
+
+The stubs declare `DefragResult(NamedTuple, Generic[AnyStr])` and 
`SplitResult(NamedTuple, Generic[AnyStr])`, but the runtime classes inherit 
from `collections.namedtuple(...)` (not `typing.NamedTuple`). This is 
acceptable for type checking purposes since pyright accepts it (0 errors), but 
it's a divergence from the runtime class hierarchy.
+
+### S2. `gethost()` default parameter type
+
+The stub types `default` as `str | ipaddress.IPv4Address | 
ipaddress.IPv6Address | None`, but at runtime any value could be passed as 
default (it's just returned as-is when host is absent). This is appropriate — 
the stub reflects intended usage, not arbitrary usage.
+
+### S3. `uriencode` overload for str input missing `encoding=None`
+
+The str overload of `uriencode` types `encoding` as `str`, not `str | None`. 
The bytes overload allows `str | None`. At runtime, passing `encoding=None` to 
`uriencode` with a str input would fail (can't call `str.encode(None)`), so the 
stub is correct — but this asymmetry is not obvious.
+
+---
+
+## Security
+
+No security issues found. The library:
+- Does not perform network I/O
+- Does not execute or evaluate URI content
+- Properly validates IP literals and rejects IPvFuture
+- Uses percent-encoding with uppercase hex digits per RFC 3986
+- Handles path traversal (`../`) correctly per RFC 3986 Section 5.2.4
+- `test_path_traversal_limits` confirms `../` * 100 cannot escape root
+
+---
+
+## Summary
+
+The codebase is clean, well-tested (100% coverage), and fully passing all 
checks. Key findings by priority:
+
+**Consider addressing:**
+1. **C1**: Consider capping `_encoded` cache or switching to `lru_cache`
+
+**Nice to have:**
+2. **T1-T3**: Add test cases for `uricompose` fragment, encoding, and bytes 
authority override
+3. **C2-C3**: Resolve or remove FIXME comments about bytes return types 
(behavior is intentional)
+4. **D6**: Rename `master_doc` to `root_doc` in `docs/conf.py`
+
+**No action needed:**
+- C4, C5, C6, C8, D3, D4, D7, D8, S1-S3, T4-T6 — documented here for awareness
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/uritools-6.0.2/.github/dependabot.yml 
new/uritools-6.1.1/.github/dependabot.yml
--- old/uritools-6.0.2/.github/dependabot.yml   1970-01-01 01:00:00.000000000 
+0100
+++ new/uritools-6.1.1/.github/dependabot.yml   2026-05-09 21:51:08.000000000 
+0200
@@ -0,0 +1,6 @@
+version: 2
+updates:
+  - package-ecosystem: "github-actions"
+    directory: "/"
+    schedule:
+      interval: "monthly"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/uritools-6.0.2/.github/workflows/ci.yml 
new/uritools-6.1.1/.github/workflows/ci.yml
--- old/uritools-6.0.2/.github/workflows/ci.yml 1970-01-01 01:00:00.000000000 
+0100
+++ new/uritools-6.1.1/.github/workflows/ci.yml 2026-05-09 21:51:08.000000000 
+0200
@@ -0,0 +1,27 @@
+name: CI
+
+on: [push, pull_request, workflow_dispatch]
+
+permissions:
+  contents: read
+
+jobs:
+  main:
+    name: Python ${{ matrix.python }}
+    runs-on: ubuntu-latest
+    strategy:
+      fail-fast: false
+      matrix:
+        python: ["3.10", "3.11", "3.12", "3.13", "3.13t", "3.14", "3.14t", 
"pypy3.10", "pypy3.11"]
+    steps:
+      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # 
v6.0.2
+      - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # 
v6.2.0
+        with:
+          python-version: ${{ matrix.python }}
+          allow-prereleases: true
+      - run: pip install coverage tox
+      - run: tox
+      - uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 
# v6.0.0
+        with:
+          name: ${{ matrix.python }}
+          token: ${{ secrets.CODECOV_TOKEN }}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/uritools-6.0.2/.gitignore 
new/uritools-6.1.1/.gitignore
--- old/uritools-6.0.2/.gitignore       1970-01-01 01:00:00.000000000 +0100
+++ new/uritools-6.1.1/.gitignore       2026-05-09 21:51:08.000000000 +0200
@@ -0,0 +1,11 @@
+*.egg-info
+*.pyc
+*.swp
+.cache/
+.coverage
+.pytest_cache/
+.tox/
+MANIFEST
+build/
+dist/
+docs/_build/
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/uritools-6.0.2/.readthedocs.yaml 
new/uritools-6.1.1/.readthedocs.yaml
--- old/uritools-6.0.2/.readthedocs.yaml        1970-01-01 01:00:00.000000000 
+0100
+++ new/uritools-6.1.1/.readthedocs.yaml        2026-05-09 21:51:08.000000000 
+0200
@@ -0,0 +1,11 @@
+# Configure ReadTheDocs.
+
+version: 2
+
+build:
+  os: "ubuntu-22.04"
+  tools:
+    python: "3.11"
+
+sphinx:
+  configuration: "docs/conf.py"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/uritools-6.0.2/CHANGELOG.rst 
new/uritools-6.1.1/CHANGELOG.rst
--- old/uritools-6.0.2/CHANGELOG.rst    2026-04-22 22:09:19.000000000 +0200
+++ new/uritools-6.1.1/CHANGELOG.rst    2026-05-18 20:08:33.000000000 +0200
@@ -1,3 +1,19 @@
+v6.1.1 (2026-05-18)
+===================
+
+- Minor documentation improvements.
+
+- Update build environment.
+
+
+v6.1.0 (2026-04-30)
+===================
+
+- Add type stubs.
+
+- Improve unit tests.
+
+
 v6.0.2 (2026-04-22)
 ===================
 
@@ -7,6 +23,7 @@
 
 - Update CI environment.
 
+
 v6.0.1 (2025-12-21)
 ===================
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/uritools-6.0.2/LICENSE new/uritools-6.1.1/LICENSE
--- old/uritools-6.0.2/LICENSE  2025-05-02 15:05:47.000000000 +0200
+++ new/uritools-6.1.1/LICENSE  2026-05-18 20:08:33.000000000 +0200
@@ -1,6 +1,6 @@
 The MIT License (MIT)
 
-Copyright (c) 2014-2025 Thomas Kemmer
+Copyright (c) 2014-2026 Thomas Kemmer
 
 Permission is hereby granted, free of charge, to any person obtaining a copy of
 this software and associated documentation files (the "Software"), to deal in
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/uritools-6.0.2/MANIFEST.in 
new/uritools-6.1.1/MANIFEST.in
--- old/uritools-6.0.2/MANIFEST.in      2024-05-01 20:41:31.000000000 +0200
+++ new/uritools-6.1.1/MANIFEST.in      1970-01-01 01:00:00.000000000 +0100
@@ -1,11 +0,0 @@
-include CHANGELOG.rst
-include LICENSE
-include MANIFEST.in
-include README.rst
-include tox.ini
-exclude .readthedocs.yaml
-
-recursive-include docs *
-prune docs/_build
-
-recursive-include tests *.py
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/uritools-6.0.2/PKG-INFO new/uritools-6.1.1/PKG-INFO
--- old/uritools-6.0.2/PKG-INFO 2026-04-22 22:16:59.532577000 +0200
+++ new/uritools-6.1.1/PKG-INFO 2026-05-18 20:22:32.359385700 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 2.4
 Name: uritools
-Version: 6.0.2
+Version: 6.1.1
 Summary: URI parsing, classification and composition
 Author-email: Thomas Kemmer <[email protected]>
 Maintainer-email: Thomas Kemmer <[email protected]>
@@ -46,19 +46,10 @@
    :target: https://codecov.io/gh/tkem/uritools
    :alt: Test coverage
 
-.. image:: https://img.shields.io/librariesio/sourcerank/pypi/uritools
-   :target: https://libraries.io/pypi/uritools
-   :alt: Libraries.io SourceRank
-
 .. image:: https://img.shields.io/github/license/tkem/uritools
    :target: https://raw.github.com/tkem/uritools/master/LICENSE
    :alt: License
 
-.. image:: https://img.shields.io/badge/code%20style-black-000000.svg
-   :target: https://github.com/psf/black
-   :alt: Code style: black
-
-
 This module provides RFC 3986 compliant functions for parsing,
 classifying and composing URIs and URI references, largely replacing
 the Python Standard Library's ``urllib.parse`` module.
@@ -86,10 +77,9 @@
     >>> urijoin(uriunsplit(parts), '/right/here?name=swallow#beak')
     'foo://example.com:8042/right/here?name=swallow#beak'
 
-For various reasons, ``urllib.parse`` and its Python 2 predecessor
-``urlparse`` are not compliant with current Internet standards.  As
-stated in `Lib/urllib/parse.py
-<https://github.com/python/cpython/blob/main/Lib/urllib/parse.py#L22>`_:
+For various reasons, ``urllib.parse`` is not compliant with current
+Internet standards.  As stated in `Lib/urllib/parse.py
+<https://github.com/python/cpython/blob/3.14/Lib/urllib/parse.py#L22>`_:
 
     RFC 3986 is considered the current standard and any future changes
     to urlparse module should conform with it.  The urlparse module is
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/uritools-6.0.2/README.rst 
new/uritools-6.1.1/README.rst
--- old/uritools-6.0.2/README.rst       2026-04-22 22:09:19.000000000 +0200
+++ new/uritools-6.1.1/README.rst       2026-05-18 20:08:33.000000000 +0200
@@ -17,19 +17,10 @@
    :target: https://codecov.io/gh/tkem/uritools
    :alt: Test coverage
 
-.. image:: https://img.shields.io/librariesio/sourcerank/pypi/uritools
-   :target: https://libraries.io/pypi/uritools
-   :alt: Libraries.io SourceRank
-
 .. image:: https://img.shields.io/github/license/tkem/uritools
    :target: https://raw.github.com/tkem/uritools/master/LICENSE
    :alt: License
 
-.. image:: https://img.shields.io/badge/code%20style-black-000000.svg
-   :target: https://github.com/psf/black
-   :alt: Code style: black
-
-
 This module provides RFC 3986 compliant functions for parsing,
 classifying and composing URIs and URI references, largely replacing
 the Python Standard Library's ``urllib.parse`` module.
@@ -57,10 +48,9 @@
     >>> urijoin(uriunsplit(parts), '/right/here?name=swallow#beak')
     'foo://example.com:8042/right/here?name=swallow#beak'
 
-For various reasons, ``urllib.parse`` and its Python 2 predecessor
-``urlparse`` are not compliant with current Internet standards.  As
-stated in `Lib/urllib/parse.py
-<https://github.com/python/cpython/blob/main/Lib/urllib/parse.py#L22>`_:
+For various reasons, ``urllib.parse`` is not compliant with current
+Internet standards.  As stated in `Lib/urllib/parse.py
+<https://github.com/python/cpython/blob/3.14/Lib/urllib/parse.py#L22>`_:
 
     RFC 3986 is considered the current standard and any future changes
     to urlparse module should conform with it.  The urlparse module is
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/uritools-6.0.2/docs/.gitignore 
new/uritools-6.1.1/docs/.gitignore
--- old/uritools-6.0.2/docs/.gitignore  2020-08-10 19:35:07.000000000 +0200
+++ new/uritools-6.1.1/docs/.gitignore  1970-01-01 01:00:00.000000000 +0100
@@ -1 +0,0 @@
-_build
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/uritools-6.0.2/docs/conf.py 
new/uritools-6.1.1/docs/conf.py
--- old/uritools-6.0.2/docs/conf.py     2026-04-22 22:09:19.000000000 +0200
+++ new/uritools-6.1.1/docs/conf.py     2026-05-09 21:51:08.000000000 +0200
@@ -16,6 +16,7 @@
         full_version = line.partition("=")[2].strip().strip("\"'")
         partial_version = ".".join(full_version.split(".")[:2])
         return full_version, partial_version
+    return (None, None)
 
 
 project = "uritools"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/uritools-6.0.2/docs/index.rst 
new/uritools-6.1.1/docs/index.rst
--- old/uritools-6.0.2/docs/index.rst   2025-05-02 15:51:45.000000000 +0200
+++ new/uritools-6.1.1/docs/index.rst   2026-05-18 20:08:33.000000000 +0200
@@ -31,10 +31,9 @@
     >>> urijoin(uriunsplit(parts), '/right/here?name=swallow#beak')
     'foo://example.com:8042/right/here?name=swallow#beak'
 
-For various reasons, :mod:`urllib.parse` and its Python 2 predecessor
-:mod:`urlparse` are not compliant with current Internet standards.  As
-stated in `Lib/urllib/parse.py
-<https://github.com/python/cpython/blob/main/Lib/urllib/parse.py#L22>`_:
+For various reasons, :mod:`urllib.parse` is not compliant with current
+Internet standards.  As stated in `Lib/urllib/parse.py
+<https://github.com/python/cpython/blob/3.14/Lib/urllib/parse.py#L22>`_:
 
     RFC 3986 is considered the current standard and any future changes
     to urlparse module should conform with it.  The urlparse module is
@@ -103,7 +102,7 @@
    All components may be specified as either Unicode strings, which
    will be encoded according to `encoding`, or :class:`bytes` objects.
 
-   `authority` may also be passed a three-item iterable specifying
+   `authority` may also be passed a three-item sequence specifying
    userinfo, host and port subcomponents.  If both `authority` and any
    of the `userinfo`, `host` or `port` keyword arguments are given,
    the keyword argument will override the corresponding `authority`
@@ -117,8 +116,8 @@
 
 .. autofunction:: urijoin
 
-    If `strict` is :const:`False`, a scheme in the reference is
-    ignored if it is identical to the base URI's scheme.
+   If `strict` is :const:`False`, a scheme in the reference is
+   ignored if it is identical to the base URI's scheme.
 
 .. autofunction:: uriunsplit
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/uritools-6.0.2/pyproject.toml 
new/uritools-6.1.1/pyproject.toml
--- old/uritools-6.0.2/pyproject.toml   2026-04-22 22:09:19.000000000 +0200
+++ new/uritools-6.1.1/pyproject.toml   2026-05-18 20:08:33.000000000 +0200
@@ -1,5 +1,5 @@
 [build-system]
-requires = ["setuptools >= 61.0.0", "wheel"]
+requires = ["setuptools >= 78", "setuptools-scm >= 8.2"]
 build-backend = "setuptools.build_meta"
 
 [project]
@@ -47,10 +47,7 @@
 [tool.setuptools.dynamic]
 version = {attr = "uritools.__version__"}
 
-[tool.flake8]
-max-line-length = 80
-exclude = [".git", ".tox", "build"]
-select = ["C", "E", "F", "W", "B", "B950", "I", "N"]
-# E501: line too long (black)
-# W503 line break before binary operator (black)
-ignore = ["E501", "W503"]
+[tool.pyright]
+typeCheckingMode = "standard"
+reportOptionalMemberAccess = false
+reportAttributeAccessIssue = false
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/uritools-6.0.2/src/uritools/__init__.py 
new/uritools-6.1.1/src/uritools/__init__.py
--- old/uritools-6.0.2/src/uritools/__init__.py 2026-04-22 22:09:19.000000000 
+0200
+++ new/uritools-6.1.1/src/uritools/__init__.py 2026-05-18 20:08:33.000000000 
+0200
@@ -33,7 +33,7 @@
     "uriunsplit",
 )
 
-__version__ = "6.0.2"
+__version__ = "6.1.1"
 
 
 # RFC 3986 2.2.  Reserved Characters
@@ -84,7 +84,7 @@
     except KeyError:
         encoded = _encoded[b""][:]
         for i in safe:
-            encoded[i] = bytes([i])
+            encoded[i] = bytes([i])  # type: ignore
         _encoded[safe] = encoded
     return b"".join(map(encoded.__getitem__, uristring))
 
@@ -126,6 +126,7 @@
         original URI did not contain a fragment component.
 
         """
+        # FIXME: by default, getfragment() should return bytes if geturi() 
returns bytes
         fragment = self.fragment
         if fragment is not None:
             return uridecode(fragment, encoding, errors)
@@ -145,7 +146,7 @@
         authority = self.authority
         if authority is None:
             return None
-        userinfo, present, _ = authority.rpartition(self.AT)
+        userinfo, present, _ = authority.rpartition(self._AT)
         if present:
             return userinfo
         else:
@@ -156,9 +157,9 @@
         authority = self.authority
         if authority is None:
             return None
-        _, _, hostinfo = authority.rpartition(self.AT)
-        host, _, port = hostinfo.rpartition(self.COLON)
-        if port.lstrip(self.DIGITS):
+        _, _, hostinfo = authority.rpartition(self._AT)
+        host, _, port = hostinfo.rpartition(self._COLON)
+        if port.lstrip(self._DIGITS):
             return hostinfo
         else:
             return host
@@ -168,8 +169,8 @@
         authority = self.authority
         if authority is None:
             return None
-        _, present, port = authority.rpartition(self.COLON)
-        if present and not port.lstrip(self.DIGITS):
+        _, present, port = authority.rpartition(self._COLON)
+        if present and not port.lstrip(self._DIGITS):
             return port
         else:
             return None
@@ -184,21 +185,22 @@
         # RFC 3986 5.3. Component Recomposition
         result = []
         if scheme is not None:
-            result.extend([scheme, self.COLON])
+            result.extend([scheme, self._COLON])
         if authority is not None:
-            result.extend([self.SLASH, self.SLASH, authority])
+            result.extend([self._SLASH, self._SLASH, authority])
         result.append(path)
         if query is not None:
-            result.extend([self.QUEST, query])
+            result.extend([self._QUEST, query])
         if fragment is not None:
-            result.extend([self.HASH, fragment])
-        return self.EMPTY.join(result)
+            result.extend([self._HASH, fragment])
+        return self._EMPTY.join(result)
 
     def getscheme(self, default=None):
         """Return the URI scheme in canonical (lowercase) form, or `default`
         if the original URI reference did not contain a scheme component.
 
         """
+        # FIXME: should getscheme() return bytes if geturi() returns bytes?
         scheme = self.scheme
         if scheme is None:
             return default
@@ -215,7 +217,7 @@
         # TBD: (userinfo, host, port) kwargs, default string?
         if default is None:
             default = (None, None, None)
-        elif not isinstance(default, collections.abc.Iterable):
+        elif not isinstance(default, collections.abc.Sequence):
             raise TypeError("Invalid default type")
         elif len(default) != 3:
             raise ValueError("Invalid default length")
@@ -247,9 +249,9 @@
         host = self.host
         if host is None or (not host and default is not None):
             return default
-        elif host.startswith(self.LBRACKET) and host.endswith(self.RBRACKET):
+        elif host.startswith(self._LBRACKET) and host.endswith(self._RBRACKET):
             return self.__parse_ip_literal(host[1:-1])
-        elif host.startswith(self.LBRACKET) or host.endswith(self.RBRACKET):
+        elif host.startswith(self._LBRACKET) or host.endswith(self._RBRACKET):
             raise ValueError("Invalid host %r: mismatched brackets" % host)
         # TODO: faster check for IPv4 address?
         try:
@@ -315,7 +317,7 @@
         else:
             qsl = self.query.split(sep.encode("ascii"))
         result = []
-        for parts in [qs.partition(self.EQ) for qs in qsl if qs]:
+        for parts in [qs.partition(self._EQ) for qs in qsl if qs]:
             name = uridecode(parts[0], encoding, errors)
             if parts[1]:
                 value = uridecode(parts[2], encoding, errors)
@@ -352,7 +354,7 @@
         return (
             self.scheme is None
             and self.authority is None
-            and self.path.startswith(self.SLASH)
+            and self.path.startswith(self._SLASH)
         )
 
     def isrelpath(self):
@@ -360,7 +362,7 @@
         return (
             self.scheme is None
             and self.authority is None
-            and not self.path.startswith(self.SLASH)
+            and not self.path.startswith(self._SLASH)
         )
 
     def issamedoc(self):
@@ -377,7 +379,7 @@
         :class:`SplitResult` representing its target URI.
 
         """
-        scheme, authority, path, query, fragment = self.RE.match(ref).groups()
+        scheme, authority, path, query, fragment = self._match(ref).groups()
 
         # RFC 3986 5.2.2. Transform References
         if scheme is not None and (strict or scheme != self.scheme):
@@ -390,7 +392,7 @@
             authority = self.authority
             path = self.path
             query = self.query if query is None else query
-        elif path.startswith(self.SLASH):
+        elif path.startswith(self._SLASH):
             scheme = self.scheme
             authority = self.authority
             path = self.__remove_dot_segments(path)
@@ -403,32 +405,32 @@
     def __merge(self, path):
         # RFC 3986 5.2.3. Merge Paths
         if self.authority is not None and not self.path:
-            return self.SLASH + path
+            return self._SLASH + path
         else:
-            parts = self.path.rpartition(self.SLASH)
+            parts = self.path.rpartition(self._SLASH)
             return parts[1].join((parts[0], path))
 
     @classmethod
     def __remove_dot_segments(cls, path):
         # RFC 3986 5.2.4. Remove Dot Segments
         pseg = []
-        for s in path.split(cls.SLASH):
-            if s == cls.DOT:
+        for s in path.split(cls._SLASH):
+            if s == cls._DOT:
                 continue
-            elif s != cls.DOTDOT:
+            elif s != cls._DOTDOT:
                 pseg.append(s)
             elif len(pseg) == 1 and not pseg[0]:
                 continue
-            elif pseg and pseg[-1] != cls.DOTDOT:
+            elif pseg and pseg[-1] != cls._DOTDOT:
                 pseg.pop()
             else:
                 pseg.append(s)
         # adjust for trailing '/.' or '/..'
-        if path.rpartition(cls.SLASH)[2] in (cls.DOT, cls.DOTDOT):
-            pseg.append(cls.EMPTY)
-        if path and len(pseg) == 1 and pseg[0] == cls.EMPTY:
-            pseg.insert(0, cls.DOT)
-        return cls.SLASH.join(pseg)
+        if path.rpartition(cls._SLASH)[2] in (cls._DOT, cls._DOTDOT):
+            pseg.append(cls._EMPTY)
+        if path and len(pseg) == 1 and pseg[0] == cls._EMPTY:
+            pseg.insert(0, cls._DOT)
+        return cls._SLASH.join(pseg)
 
     @classmethod
     def __parse_ip_literal(cls, address):
@@ -454,11 +456,12 @@
         return ipaddress.IPv6Address(address)
 
 
+# TODO: make private?
 class SplitResultBytes(SplitResult):
     __slots__ = ()  # prevent creation of instance dictionary
 
     # RFC 3986 Appendix B
-    RE = re.compile(
+    _RE = re.compile(
         rb"""
     (?:([A-Za-z][A-Za-z0-9+.-]*):)?  # scheme (RFC 3986 3.1)
     (?://([^/?#]*))?                 # authority
@@ -469,8 +472,12 @@
         flags=re.VERBOSE,
     )
 
+    @classmethod
+    def _match(cls, ref):
+        return cls._RE.match(ref)
+
     # RFC 3986 2.2 gen-delims
-    COLON, SLASH, QUEST, HASH, LBRACKET, RBRACKET, AT = (
+    _COLON, _SLASH, _QUEST, _HASH, _LBRACKET, _RBRACKET, _AT = (
         b":",
         b"/",
         b"?",
@@ -481,18 +488,19 @@
     )
 
     # RFC 3986 3.3 dot-segments
-    DOT, DOTDOT = b".", b".."
+    _DOT, _DOTDOT = b".", b".."
 
-    EMPTY, EQ = b"", b"="
+    _EMPTY, _EQ = b"", b"="
 
-    DIGITS = b"0123456789"
+    _DIGITS = b"0123456789"
 
 
+# TODO: make private?
 class SplitResultString(SplitResult):
     __slots__ = ()  # prevent creation of instance dictionary
 
     # RFC 3986 Appendix B
-    RE = re.compile(
+    _RE = re.compile(
         r"""
     (?:([A-Za-z][A-Za-z0-9+.-]*):)?  # scheme (RFC 3986 3.1)
     (?://([^/?#]*))?                 # authority
@@ -503,8 +511,12 @@
         flags=re.VERBOSE,
     )
 
+    @classmethod
+    def _match(cls, ref):
+        return cls._RE.match(ref)
+
     # RFC 3986 2.2 gen-delims
-    COLON, SLASH, QUEST, HASH, LBRACKET, RBRACKET, AT = (
+    _COLON, _SLASH, _QUEST, _HASH, _LBRACKET, _RBRACKET, _AT = (
         ":",
         "/",
         "?",
@@ -515,11 +527,11 @@
     )
 
     # RFC 3986 3.3 dot-segments
-    DOT, DOTDOT = ".", ".."
+    _DOT, _DOTDOT = ".", ".."
 
-    EMPTY, EQ = "", "="
+    _EMPTY, _EQ = "", "="
 
-    DIGITS = "0123456789"
+    _DIGITS = "0123456789"
 
 
 def uridefrag(uristring):
@@ -542,7 +554,7 @@
         result = SplitResultBytes
     else:
         result = SplitResultString
-    return result(*result.RE.match(uristring).groups())
+    return result(*result._match(uristring).groups())
 
 
 def uriunsplit(parts):
@@ -601,8 +613,6 @@
     return urisplit(uristring).issamedoc()
 
 
-# TBD: move compose to its own submodule?
-
 # RFC 3986 3.1: scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
 _SCHEME_RE = re.compile(b"^[A-Za-z][A-Za-z0-9+.-]*$")
 
@@ -741,14 +751,14 @@
     elif scheme is not None:
         scheme = _scheme(scheme.encode())
 
-    # authority must be string type or three-item iterable
+    # authority must be string type or three-item sequence
     if authority is None:
         authority = (None, None, None)
     elif isinstance(authority, bytes):
         authority = _AUTHORITY_RE_BYTES.match(authority).groups()
     elif isinstance(authority, str):
         authority = _AUTHORITY_RE_STR.match(authority).groups()
-    elif not isinstance(authority, collections.abc.Iterable):
+    elif not isinstance(authority, collections.abc.Sequence):
         raise TypeError("Invalid authority type")
     elif len(authority) != 3:
         raise ValueError("Invalid authority length")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/uritools-6.0.2/src/uritools/__init__.pyi 
new/uritools-6.1.1/src/uritools/__init__.pyi
--- old/uritools-6.0.2/src/uritools/__init__.pyi        1970-01-01 
01:00:00.000000000 +0100
+++ new/uritools-6.1.1/src/uritools/__init__.pyi        2026-05-09 
21:51:08.000000000 +0200
@@ -0,0 +1,243 @@
+import ipaddress
+from collections.abc import Iterable, Mapping, Sequence
+from typing import Any, AnyStr, Generic, NamedTuple, overload
+from typing_extensions import TypeAlias
+
+__all__ = [
+    "GEN_DELIMS",
+    "RESERVED",
+    "SUB_DELIMS",
+    "UNRESERVED",
+    "isabspath",
+    "isabsuri",
+    "isnetpath",
+    "isrelpath",
+    "issamedoc",
+    "isuri",
+    "uricompose",
+    "uridecode",
+    "uridefrag",
+    "uriencode",
+    "urijoin",
+    "urisplit",
+    "uriunsplit",
+]
+__version__: str
+
+GEN_DELIMS: str
+SUB_DELIMS: str
+RESERVED: str
+UNRESERVED: str
+
+@overload
+def uriencode(
+    uristring: str,
+    safe: str | bytes = ...,
+    encoding: str = ...,
+    errors: str = ...,
+) -> bytes: ...
+@overload
+def uriencode(
+    uristring: bytes,
+    safe: str | bytes = ...,
+    encoding: str | None = ...,
+    errors: str = ...,
+) -> bytes: ...
+@overload
+def uridecode(
+    uristring: str | bytes,
+    encoding: str = ...,
+    errors: str = ...,
+) -> str: ...
+@overload
+def uridecode(
+    uristring: str | bytes,
+    encoding: None,
+    errors: str = ...,
+) -> bytes: ...
+
+class DefragResult(NamedTuple, Generic[AnyStr]):
+    uri: AnyStr
+    fragment: AnyStr | None
+    def geturi(self) -> AnyStr: ...
+    @overload
+    def getfragment(
+        self,
+        default: str | None = ...,
+        encoding: str = ...,
+        errors: str = ...,
+    ) -> str | None: ...
+    @overload
+    def getfragment(
+        self,
+        default: bytes | None = ...,
+        *,
+        encoding: None,
+        errors: str = ...,
+    ) -> bytes | None: ...
+
+class SplitResult(NamedTuple, Generic[AnyStr]):
+    scheme: AnyStr | None
+    authority: AnyStr | None
+    path: AnyStr
+    query: AnyStr | None
+    fragment: AnyStr | None
+    @property
+    def userinfo(self) -> AnyStr | None: ...
+    @property
+    def host(self) -> AnyStr | None: ...
+    @property
+    def port(self) -> AnyStr | None: ...
+    def geturi(self) -> AnyStr: ...
+    def getscheme(self, default: str | None = ...) -> str | None: ...
+    @overload
+    def getauthority(
+        self,
+        default: tuple[Any, Any, Any] | None = ...,
+        encoding: str = ...,
+        errors: str = ...,
+    ) -> tuple[
+        str | None,
+        str | ipaddress.IPv4Address | ipaddress.IPv6Address | None,
+        int | None,
+    ]: ...
+    @overload
+    def getauthority(
+        self,
+        default: tuple[Any, Any, Any] | None = ...,
+        *,
+        encoding: None,
+        errors: str = ...,
+    ) -> tuple[
+        bytes | None,
+        str | ipaddress.IPv4Address | ipaddress.IPv6Address | None,
+        int | None,
+    ]: ...
+    @overload
+    def getuserinfo(
+        self,
+        default: str | None = ...,
+        encoding: str = ...,
+        errors: str = ...,
+    ) -> str | None: ...
+    @overload
+    def getuserinfo(
+        self,
+        default: bytes | None = ...,
+        *,
+        encoding: None,
+        errors: str = ...,
+    ) -> bytes | None: ...
+    def gethost(
+        self,
+        default: str | ipaddress.IPv4Address | ipaddress.IPv6Address | None = 
...,
+        errors: str = ...,
+    ) -> str | ipaddress.IPv4Address | ipaddress.IPv6Address | None: ...
+    def getport(self, default: int | None = ...) -> int | None: ...
+    @overload
+    def getpath(self, encoding: str = ..., errors: str = ...) -> str: ...
+    @overload
+    def getpath(self, encoding: None, errors: str = ...) -> bytes: ...
+    @overload
+    def getquery(
+        self,
+        default: str | None = ...,
+        encoding: str = ...,
+        errors: str = ...,
+    ) -> str | None: ...
+    @overload
+    def getquery(
+        self,
+        default: bytes | None = ...,
+        *,
+        encoding: None,
+        errors: str = ...,
+    ) -> bytes | None: ...
+    @overload
+    def getquerydict(
+        self,
+        sep: str | bytes = ...,
+        encoding: str = ...,
+        errors: str = ...,
+    ) -> dict[str, list[str | None]]: ...
+    @overload
+    def getquerydict(
+        self,
+        sep: str | bytes = ...,
+        *,
+        encoding: None,
+        errors: str = ...,
+    ) -> dict[bytes, list[bytes | None]]: ...
+    @overload
+    def getquerylist(
+        self,
+        sep: str | bytes = ...,
+        encoding: str = ...,
+        errors: str = ...,
+    ) -> list[tuple[str, str | None]]: ...
+    @overload
+    def getquerylist(
+        self,
+        sep: str | bytes = ...,
+        *,
+        encoding: None,
+        errors: str = ...,
+    ) -> list[tuple[bytes, bytes | None]]: ...
+    @overload
+    def getfragment(
+        self,
+        default: str | None = ...,
+        encoding: str = ...,
+        errors: str = ...,
+    ) -> str | None: ...
+    @overload
+    def getfragment(
+        self,
+        default: bytes | None = ...,
+        *,
+        encoding: None,
+        errors: str = ...,
+    ) -> bytes | None: ...
+    def isuri(self) -> bool: ...
+    def isabsuri(self) -> bool: ...
+    def isnetpath(self) -> bool: ...
+    def isabspath(self) -> bool: ...
+    def isrelpath(self) -> bool: ...
+    def issamedoc(self) -> bool: ...
+    def transform(self, ref: AnyStr, strict: bool = ...) -> 
SplitResult[AnyStr]: ...
+
+def uridefrag(uristring: AnyStr) -> DefragResult[AnyStr]: ...
+def urisplit(uristring: AnyStr) -> SplitResult[AnyStr]: ...
+def uriunsplit(parts: Iterable[AnyStr | None]) -> AnyStr: ...
+@overload
+def urijoin(base: str, ref: str | bytes, strict: bool = ...) -> str: ...
+@overload
+def urijoin(base: bytes, ref: str, strict: bool = ...) -> str: ...
+@overload
+def urijoin(base: bytes, ref: bytes, strict: bool = ...) -> bytes: ...
+def isuri(uristring: str | bytes) -> bool: ...
+def isabsuri(uristring: str | bytes) -> bool: ...
+def isnetpath(uristring: str | bytes) -> bool: ...
+def isabspath(uristring: str | bytes) -> bool: ...
+def isrelpath(uristring: str | bytes) -> bool: ...
+def issamedoc(uristring: str | bytes) -> bool: ...
+
+_QueryType: TypeAlias = (
+    str | bytes | Mapping[str | bytes, object] | Iterable[tuple[str | bytes, 
object]]
+)
+
+def uricompose(
+    scheme: str | bytes | None = ...,
+    authority: str
+    | bytes
+    | Sequence[str | bytes | ipaddress.IPv4Address | ipaddress.IPv6Address | 
int | None]
+    | None = ...,
+    path: str | bytes = ...,
+    query: _QueryType | None = ...,
+    fragment: str | bytes | None = ...,
+    userinfo: str | bytes | None = ...,
+    host: str | bytes | ipaddress.IPv4Address | ipaddress.IPv6Address | None = 
...,
+    port: int | str | bytes | None = ...,
+    querysep: str = ...,
+    encoding: str = ...,
+) -> str: ...
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/uritools-6.0.2/src/uritools.egg-info/PKG-INFO 
new/uritools-6.1.1/src/uritools.egg-info/PKG-INFO
--- old/uritools-6.0.2/src/uritools.egg-info/PKG-INFO   2026-04-22 
22:16:59.000000000 +0200
+++ new/uritools-6.1.1/src/uritools.egg-info/PKG-INFO   2026-05-18 
20:22:32.000000000 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 2.4
 Name: uritools
-Version: 6.0.2
+Version: 6.1.1
 Summary: URI parsing, classification and composition
 Author-email: Thomas Kemmer <[email protected]>
 Maintainer-email: Thomas Kemmer <[email protected]>
@@ -46,19 +46,10 @@
    :target: https://codecov.io/gh/tkem/uritools
    :alt: Test coverage
 
-.. image:: https://img.shields.io/librariesio/sourcerank/pypi/uritools
-   :target: https://libraries.io/pypi/uritools
-   :alt: Libraries.io SourceRank
-
 .. image:: https://img.shields.io/github/license/tkem/uritools
    :target: https://raw.github.com/tkem/uritools/master/LICENSE
    :alt: License
 
-.. image:: https://img.shields.io/badge/code%20style-black-000000.svg
-   :target: https://github.com/psf/black
-   :alt: Code style: black
-
-
 This module provides RFC 3986 compliant functions for parsing,
 classifying and composing URIs and URI references, largely replacing
 the Python Standard Library's ``urllib.parse`` module.
@@ -86,10 +77,9 @@
     >>> urijoin(uriunsplit(parts), '/right/here?name=swallow#beak')
     'foo://example.com:8042/right/here?name=swallow#beak'
 
-For various reasons, ``urllib.parse`` and its Python 2 predecessor
-``urlparse`` are not compliant with current Internet standards.  As
-stated in `Lib/urllib/parse.py
-<https://github.com/python/cpython/blob/main/Lib/urllib/parse.py#L22>`_:
+For various reasons, ``urllib.parse`` is not compliant with current
+Internet standards.  As stated in `Lib/urllib/parse.py
+<https://github.com/python/cpython/blob/3.14/Lib/urllib/parse.py#L22>`_:
 
     RFC 3986 is considered the current standard and any future changes
     to urlparse module should conform with it.  The urlparse module is
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/uritools-6.0.2/src/uritools.egg-info/SOURCES.txt 
new/uritools-6.1.1/src/uritools.egg-info/SOURCES.txt
--- old/uritools-6.0.2/src/uritools.egg-info/SOURCES.txt        2026-04-22 
22:16:59.000000000 +0200
+++ new/uritools-6.1.1/src/uritools.egg-info/SOURCES.txt        2026-05-18 
20:22:32.000000000 +0200
@@ -1,13 +1,24 @@
+.gitignore
+.readthedocs.yaml
 CHANGELOG.rst
 LICENSE
-MANIFEST.in
 README.rst
 pyproject.toml
 tox.ini
-docs/.gitignore
+.github/FUNDING.yml
+.github/SECURITY.md
+.github/copilot-instructions.md
+.github/copilot-review.md
+.github/dependabot.yml
+.github/ISSUE_TEMPLATE/bug_report.md
+.github/ISSUE_TEMPLATE/config.yml
+.github/ISSUE_TEMPLATE/feature_request.md
+.github/workflows/ci.yml
 docs/conf.py
 docs/index.rst
 src/uritools/__init__.py
+src/uritools/__init__.pyi
+src/uritools/py.typed
 src/uritools.egg-info/PKG-INFO
 src/uritools.egg-info/SOURCES.txt
 src/uritools.egg-info/dependency_links.txt
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/uritools-6.0.2/tests/test_compose.py 
new/uritools-6.1.1/tests/test_compose.py
--- old/uritools-6.0.2/tests/test_compose.py    2026-04-22 22:09:19.000000000 
+0200
+++ new/uritools-6.1.1/tests/test_compose.py    2026-05-09 21:51:08.000000000 
+0200
@@ -68,7 +68,7 @@
         # invalid authority type
         for authority in (True, 42, 3.14, ipaddress.IPv6Address("::1")):
             with self.assertRaises(TypeError, msg="authority=%r" % authority):
-                uricompose(authority=authority)
+                uricompose(authority=authority)  # type: ignore
 
     def test_authority_kwargs(self):
         from ipaddress import IPv4Address, IPv6Address
@@ -116,9 +116,9 @@
         # invalid host type
         for host in (True, 42, 3.14, ipaddress.IPv6Network("2001:db00::0/24")):
             with self.assertRaises(TypeError, msg="host=%r" % host):
-                uricompose(authority=[None, host, None])
+                uricompose(authority=[None, host, None])  # type: ignore
             with self.assertRaises(TypeError, msg="host=%r" % host):
-                uricompose(host=host)
+                uricompose(host=host)  # type: ignore
         # invalid host ip-literal
         for host in ("[foo]", "[v1.x]"):
             with self.assertRaises(ValueError, msg="host=%r" % host):
@@ -128,9 +128,9 @@
         # invalid port value
         for port in (-1, "foo", 3.14):
             with self.assertRaises(ValueError, msg="port=%r" % port):
-                uricompose(authority=[None, "", port])
+                uricompose(authority=[None, "", port])  # type: ignore
             with self.assertRaises(ValueError, msg="port=%r" % port):
-                uricompose(port=port)
+                uricompose(port=port)  # type: ignore
 
     def test_authority_override(self):
         cases = [
@@ -225,7 +225,7 @@
         # invalid query type
         for query in (0, [1]):
             with self.assertRaises(TypeError, msg="query=%r" % query):
-                uricompose(query=query)
+                uricompose(query=query)  # type: ignore
 
     def test_query_sep(self):
         cases = [
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/uritools-6.0.2/tests/test_defrag.py 
new/uritools-6.1.1/tests/test_defrag.py
--- old/uritools-6.0.2/tests/test_defrag.py     2021-10-11 20:18:34.000000000 
+0200
+++ new/uritools-6.1.1/tests/test_defrag.py     2026-05-09 21:51:08.000000000 
+0200
@@ -41,3 +41,15 @@
         self.assertEqual(uridefrag(b"#foo").getfragment(), "foo")
         self.assertEqual(uridefrag("#foo%20bar").getfragment(), "foo bar")
         self.assertEqual(uridefrag(b"#foo%20bar").getfragment(), "foo bar")
+
+    def test_getfragment_encoding_none(self):
+        self.assertEqual(uridefrag("").getfragment(encoding=None), None)
+        self.assertEqual(uridefrag(b"").getfragment(encoding=None), None)
+        self.assertEqual(uridefrag("#").getfragment(encoding=None), b"")
+        self.assertEqual(uridefrag(b"#").getfragment(encoding=None), b"")
+        self.assertEqual(uridefrag("#foo").getfragment(encoding=None), b"foo")
+        self.assertEqual(uridefrag(b"#foo").getfragment(encoding=None), b"foo")
+        self.assertEqual(uridefrag("#foo%20bar").getfragment(encoding=None), 
b"foo bar")
+        self.assertEqual(
+            uridefrag(b"#foo%20bar").getfragment(encoding=None), b"foo bar"
+        )
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/uritools-6.0.2/tests/test_encoding.py 
new/uritools-6.1.1/tests/test_encoding.py
--- old/uritools-6.0.2/tests/test_encoding.py   2026-04-22 22:09:19.000000000 
+0200
+++ new/uritools-6.1.1/tests/test_encoding.py   2026-05-18 20:08:33.000000000 
+0200
@@ -8,9 +8,7 @@
         self.assertEqual(uriencode(decoded, safe, encoding), encoded)
         self.assertEqual(uridecode(encoded, encoding), decoded)
         # swap bytes/string types
-        self.assertEqual(
-            uriencode(decoded.encode(encoding), safe, encoding), encoded
-        )  # noqa
+        self.assertEqual(uriencode(decoded.encode(encoding), safe, encoding), 
encoded)  # noqa
         self.assertEqual(uridecode(encoded.decode("ascii"), encoding), decoded)
 
     def test_encoding(self):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/uritools-6.0.2/tests/test_split.py 
new/uritools-6.1.1/tests/test_split.py
--- old/uritools-6.0.2/tests/test_split.py      2026-04-22 22:09:19.000000000 
+0200
+++ new/uritools-6.1.1/tests/test_split.py      2026-05-09 21:51:08.000000000 
+0200
@@ -1,3 +1,4 @@
+import ipaddress
 import unittest
 
 from uritools import urisplit
@@ -288,10 +289,10 @@
                 urisplit(uri).getauthority()
             with self.assertRaises(ValueError, msg="%r" % uri):
                 urisplit(uri.encode()).getauthority()
-        with self.assertRaises(TypeError, msg="%r" % uri):
-            urisplit("").getauthority(42)
-        with self.assertRaises(ValueError, msg="%r" % uri):
-            urisplit("").getauthority(("userinfo", "test.python.org"))
+        with self.assertRaises(TypeError):
+            urisplit("").getauthority(42)  # type: ignore
+        with self.assertRaises(ValueError):
+            urisplit("").getauthority(("userinfo", "test.python.org"))  # 
type: ignore
 
     def test_gethost(self):
         from ipaddress import IPv4Address, IPv6Address
@@ -480,7 +481,9 @@
         ]
         for uri, host, port in cases:
             for parts in (urisplit(uri), urisplit(uri.encode("ascii"))):
-                self.assertEqual(host, parts.gethost().exploded)
+                result = parts.gethost()
+                assert isinstance(result, ipaddress.IPv6Address)
+                self.assertEqual(host, result.exploded)
                 self.assertEqual(port, parts.getport())
 
     def test_ipv4_mapped_literal(self):
@@ -538,10 +541,14 @@
         ]
         for uri, hosts, port in cases:
             parts = urisplit(uri)
-            self.assertIn(parts.gethost().exploded, hosts)
+            result = parts.gethost()
+            assert isinstance(result, ipaddress.IPv6Address)
+            self.assertIn(result.exploded, hosts)
             self.assertEqual(parts.getport(), port)
             parts = urisplit(uri.encode("ascii"))
-            self.assertIn(parts.gethost().exploded, hosts)
+            result = parts.gethost()
+            assert isinstance(result, ipaddress.IPv6Address)
+            self.assertIn(result.exploded, hosts)
             self.assertEqual(parts.getport(), port)
 
     def test_invalid_ip_literal(self):
@@ -558,3 +565,38 @@
                 urisplit(uri).gethost()
             with self.assertRaises(ValueError, msg="%r" % uri.encode("ascii")):
                 urisplit(uri.encode("ascii")).gethost()
+
+    def test_encoding_none(self):
+        uri = "foo://[email protected]:8042/over/there?name=ferret#nose"
+        for ref in [uri, uri.encode("ascii")]:
+            parts = urisplit(ref)
+            self.assertEqual(
+                parts.getauthority(encoding=None),
+                (b"user", "example.com", 8042),
+            )
+            self.assertEqual(parts.getuserinfo(encoding=None), b"user")
+            self.assertEqual(parts.getpath(encoding=None), b"/over/there")
+            self.assertEqual(parts.getquery(encoding=None), b"name=ferret")
+            self.assertEqual(parts.getfragment(encoding=None), b"nose")
+            self.assertEqual(parts.getquerylist(encoding=None), [(b"name", 
b"ferret")])
+            self.assertEqual(
+                dict(parts.getquerydict(encoding=None)), {b"name": [b"ferret"]}
+            )
+
+    def test_encoding_none_percent(self):
+        uri = "foo:///foo%20bar?name=foo%20bar#foo%20bar"
+        for ref in [uri, uri.encode("ascii")]:
+            parts = urisplit(ref)
+            self.assertEqual(parts.getpath(encoding=None), b"/foo bar")
+            self.assertEqual(parts.getquery(encoding=None), b"name=foo bar")
+            self.assertEqual(parts.getfragment(encoding=None), b"foo bar")
+            self.assertEqual(parts.getquerylist(encoding=None), [(b"name", 
b"foo bar")])
+
+    def test_encoding_none_defaults(self):
+        parts = urisplit("")
+        self.assertEqual(parts.getuserinfo(encoding=None), None)
+        self.assertEqual(parts.getquery(encoding=None), None)
+        self.assertEqual(parts.getfragment(encoding=None), None)
+        self.assertEqual(parts.getpath(encoding=None), b"")
+        self.assertEqual(parts.getquerylist(encoding=None), [])
+        self.assertEqual(dict(parts.getquerydict(encoding=None)), {})
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/uritools-6.0.2/tox.ini new/uritools-6.1.1/tox.ini
--- old/uritools-6.0.2/tox.ini  2025-12-21 19:41:35.000000000 +0100
+++ new/uritools-6.1.1/tox.ini  2026-05-18 20:08:33.000000000 +0200
@@ -1,5 +1,5 @@
 [tox]
-envlist = check-manifest,docs,doctest,flake8,py
+envlist = py,docs,doctest,ruff,ruff-format,pyright
 
 [testenv]
 deps =
@@ -8,13 +8,6 @@
 commands =
     py.test --basetemp={envtmpdir} --cov=uritools --cov-report term-missing 
{posargs}
 
-[testenv:check-manifest]
-deps =
-    check-manifest
-commands =
-    check-manifest
-skip_install = true
-
 [testenv:docs]
 deps =
      sphinx
@@ -27,13 +20,23 @@
 commands =
      sphinx-build -W -b doctest -d {envtmpdir}/doctrees docs 
{envtmpdir}/doctest
 
-[testenv:flake8]
+[testenv:ruff]
+deps =
+    ruff
+commands =
+    ruff check {posargs}
+skip_install = true
+
+[testenv:ruff-format]
+deps =
+    ruff
+commands =
+    ruff format --diff -q {posargs}
+skip_install = true
+
+[testenv:pyright]
 deps =
-    flake8
-    flake8-black
-    flake8-bugbear
-    flake8-import-order
-    flake8-pyproject
+    pyright
 commands =
-    flake8
+    pyright {posargs}
 skip_install = true

Reply via email to