Metadata-Version: 2.5
Name: pathapi-uri
Version: 0.1.0
Summary: pathlib-style paths for URIs: file:, data:, ftp:, zip:, tar:, http:, dav:
Project-URL: Homepage, https://github.com/jose-pr/uripath/
Project-URL: Documentation, https://jose-pr.github.io/uripath/
Project-URL: Issues, https://github.com/jose-pr/uripath/issues
Author: Jose A
License-Expression: MIT
License-File: LICENSE
Classifier: Development Status :: 4 - Beta
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Typing :: Typed
Requires-Python: >=3.9
Requires-Dist: netimps<0.5,>=0.4.0
Requires-Dist: pathapi<0.2,>=0.1.0
Provides-Extra: cli
Requires-Dist: duho<0.8,>=0.7.3; extra == 'cli'
Provides-Extra: dev
Requires-Dist: black; extra == 'dev'
Requires-Dist: build; extra == 'dev'
Requires-Dist: cheroot; extra == 'dev'
Requires-Dist: cryptography; extra == 'dev'
Requires-Dist: duho<0.8,>=0.7.3; extra == 'dev'
Requires-Dist: hatchling; extra == 'dev'
Requires-Dist: hypothesis; extra == 'dev'
Requires-Dist: pyftpdlib; extra == 'dev'
Requires-Dist: pytest; extra == 'dev'
Requires-Dist: pytest-cov; extra == 'dev'
Requires-Dist: requests; extra == 'dev'
Requires-Dist: twine; extra == 'dev'
Requires-Dist: uritools<7,>=5.0; extra == 'dev'
Requires-Dist: wsgidav; extra == 'dev'
Provides-Extra: docs
Requires-Dist: mkdocs; extra == 'docs'
Requires-Dist: mkdocs-material; extra == 'docs'
Requires-Dist: mkdocstrings[python]; extra == 'docs'
Description-Content-Type: text/markdown

# uripath

[![Version](https://img.shields.io/pypi/v/pathapi-uri.svg)](https://pypi.org/project/pathapi-uri/)
[![Python versions](https://img.shields.io/pypi/pyversions/pathapi-uri.svg)](https://pypi.org/project/pathapi-uri/)
[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/jose-pr/uripath/blob/main/LICENSE)
[![Docs](https://img.shields.io/badge/docs-latest-blue.svg)](https://jose-pr.github.io/uripath/)
[![CI](https://img.shields.io/github/actions/workflow/status/jose-pr/uripath/test.yml)](https://github.com/jose-pr/uripath/actions/workflows/test.yml)

Installed as `pathapi-uri` from PyPI; imported, and run as a command, as `uripath`.

**One `pathlib`-style API for URIs.** `URIPath("https://...")`, `URIPath("zip:...")`
and `URIPath("ftp://...")` return the class registered for the scheme, and each
has the method names, signatures and exception types of `pathlib.Path`: write
code once and run it against a local file, an archive member, an FTP or WebDAV
server, a web directory or, through the `repopath` distribution, a GitHub or GitLab
repository. It builds on
[`pathapi`](https://github.com/jose-pr/pathapi) and is documented at
<https://jose-pr.github.io/uripath/>.

## Features

| Scheme (class) | Needs | Read | Write | List | Stat | mkdir | Delete | rename |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
| `file:` (`FileURIPath`) | nothing | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
| `data:` (`DataURIPath`, RFC 2397) | nothing | Yes | No | No | Yes | No | No | No |
| `zip:` (`ZipURIPath`) | nothing | Yes | Local `file:` archive only | Yes | Yes | Local archive only | Local archive only | Local archive only |
| `tar:` (`TarURIPath`, incl. gz/bz2/xz) | nothing | Yes | No | Yes | Yes | No | No | No |
| `archive:`, `archive+zip:`, `archive+tar:` | nothing | As `zip:`/`tar:` for the detected or pinned format | | | | | | |
| `ftp:` / `ftps:` (`FTPURIPath`) | nothing | Yes | Yes | Yes (MLSD, NLST fallback) | Yes | Yes | Yes | Yes |
| `http:` / `https:` (`HTTPURIPath`) | nothing | Yes | Yes (PUT, configurable) | Yes (HTML index) | Yes | No | Yes (DELETE) | No |
| `dav:` / `davs:` (`DAVURIPath`, WebDAV) | nothing | Yes | Yes (no append) | Yes (PROPFIND) | Yes | Yes | Yes | Yes |
| `sftp:` | `sftppath[paramiko]` or `[asyncssh]` | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
| `s3:` | `objpath[s3]` | Yes | Yes (no append) | Yes (prefixes) | Yes | Yes (marker object) | Yes | Yes (same bucket) |
| `gs:` | `objpath[gs]` | Yes | Yes (no append) | Yes (prefixes) | Yes | Yes (marker object) | Yes | Yes (same bucket) |
| `az:` | `objpath[az]` | Yes | Yes (no append) | Yes (prefixes) | Yes | Yes (marker blob) | Yes | Yes (same container) |
| `repo:` | `repopath` | Yes | No | Yes | Yes | No | No | No |
| `github:` | `repopath` | Yes | No | Yes | Yes | No | No | No |
| `gitlab:` | `repopath` | Yes | No | Yes | Yes | No | No | No |

The last seven rows are other distributions: a path of one of those schemes
builds, joins and prints without it, and its first use of the server raises an
`ImportError` that names what to install. The `requests` HTTP transport
(`with_session()`, `~/.netrc`, SOCKS proxies) is the distribution
`pathapi-requests`: once it is installed, `with_session()` and
`PATHAPI_HTTP_TRANSPORT=requests` use it, with nothing to configure.

Every scheme gets `glob()`, `walk()`, `copy()`/`move()`, `rm()` and `PathSyncer`
from the `pathapi` base classes. The per-scheme notes (timeouts,
certificate verification, authentication, limits) are in
[Schemes](https://jose-pr.github.io/uripath/guides/schemes/).

- **Pure URI algebra**: `URI` parses, joins and compares URIs with no I/O and
  no scheme class needed; `Source` and `Query` are its authority and query.
- **HTTP on the standard library**: `http:` and `dav:` send through a
  replaceable transport built on `http.client`.
- **Extensible**: subclass `URIPath` and declare `__SCHEMES` for a new scheme,
  or register it through a `uripath.schemes` entry point.
- **`uripath` command**: `read`, `write`, `cp`, `rm` and `sync` across local
  paths and URIs, with `-` for stdin/stdout (`pip install "pathapi-uri[cli]"`).

## Installation

```bash
pip install pathapi-uri
```

Requires `pathapi` and `netimps`, which pip installs with it.

| Install | Adds |
| --- | --- |
| `pip install pathapi-uri` | every scheme whose Needs is nothing in the table above |
| `pip install "pathapi-uri[cli]"` | the `uripath` command (its only extra) |
| `pip install "sftppath[paramiko]"` or `[asyncssh]` | `sftp:` |
| `pip install "objpath[s3]"`, `[gs]` or `[az]` | `s3:`, `gs:`, `az:` |
| `pip install pathapi-requests` | the `requests` HTTP transport |

## Quick start

**Any URI**: `URIPath` returns the class registered for the scheme:

```python
from uripath import URIPath

report = URIPath("file:data/report.txt")                    # FileURIPath
report.parent.mkdir(parents=True, exist_ok=True)
report.write_text("hello")
print(report.read_text())
print(URIPath("data:text/plain;base64,aGVsbG8=").read_text())  # DataURIPath
```

**Archive members**: `<scheme>:<archive-uri>!/<member>`, where the archive
itself is any URI (`file:`, `http:`, `ftp:`, ...):

```python
import zipfile
from uripath import URIPath

with zipfile.ZipFile("backup.zip", "w") as archive:
    archive.writestr("etc/config.ini", "[main]\n")

member = URIPath("zip:file:backup.zip!/etc/config.ini")
print(member.read_text())
print([child.name for child in member.parent.iterdir()])
```

**Remote paths** use the same API:

<!-- example: skip: reads from remote hosts -->
```python
from uripath import URIPath

for child in URIPath("https://example.com/data/").iterdir():
    print(child.name, child.stat().st_size)

print(URIPath("ftp://user@host/pub/README").read_text())
```

**Copy and sync across schemes**, with the `pathapi` syncer:

```python
from pathapi.sync import PathSyncer
from uripath import URIPath

source = URIPath("file:site")
(source / "css").mkdir(parents=True)
(source / "index.html").write_text("<h1>hi</h1>")

PathSyncer(remove_missing=True).sync(source, URIPath("file:site-copy"))
print(sorted(p.name for p in URIPath("file:site-copy").iterdir()))
```

## Command line

`uripath` reads, writes, copies, removes and syncs local paths and URIs, with
`-` for stdin or stdout. It is built on `duho` and needs the `cli` extra,
`pip install "pathapi-uri[cli]"`; without it the command says so and exits with
status 1. These commands run as written in an empty directory:

```bash
mkdir site
uripath write site/index.html "<h1>hi</h1>"
uripath read site/index.html
uripath cp site/index.html site/copy.html
uripath sync --dry-run --remove-missing site site-copy
uripath sync site site-copy
uripath rm --recursive site-copy
```

Either side can be a URI instead:

<!-- example: skip: needs an FTP host and a web server -->
```bash
uripath cp report.txt ftp://host/tmp/report.txt
uripath sync --dry-run --remove-missing site/ ftp://host/site/
```

The exit status is 0 on success, 1 when the operation failed, 2 for a wrong
invocation, 130 on Ctrl-C and 141 when the reader of stdout went away. Every
flag is described in the
[CLI guide](https://jose-pr.github.io/uripath/guides/cli/), and `uripath` can
be served to an agent as a set of tools.

## API overview

| Module | Purpose |
| --- | --- |
| `uripath` | `URI`, `URIPath`, `URILike`, `Source`, `Query`, `QueryLike`, the functions `as_path` and `copy_stream`, the errors `URIPathError`, `URISyntaxError` and `UnsupportedSchemeError`, and `__version__` |
| `uripath.source` | `Source` (scheme, userinfo, host, port), `same_authority` and the helpers that read and compose a URI's text |
| `uripath.query` | `Query`, an immutable query value, and `QueryLike` |
| `uripath.exceptions` | The errors above |
| `uripath.schemes` | The contract and registry of schemes, `DerivedBackend`, `DelegatingURIPath`, and every built-in scheme's classes: `FileURIPath`, `DataURIPath`, `ZipURIPath`, `TarURIPath`, `ArchiveURIPath`, `FTPURIPath`, `HTTPURIPath`, `DAVURIPath` and their backends |

The full reference is on the [documentation site](https://jose-pr.github.io/uripath/);
an agent-oriented API summary ships inside the package as `uripath/AGENTS.md`.

## Extending

Subclass `URIPath` and declare `__SCHEMES` for a new URI scheme; it is
dispatched as soon as its module is imported, or through a `uripath.schemes`
entry point. A scheme name belongs to one class: a second claim raises unless
the class statement says `replace=True`, and `URIPath.schemes()` /
`URIPath.scheme_class(name)` read the registry. Worked examples:
[Extending](https://jose-pr.github.io/uripath/guides/extending/). The pytest
contracts that verify a scheme are in `pathapi.testing`.

## Development

```bash
pip install -e ".[dev]"
python -m pytest -q -rs
```

Tests for a server that is not installed are skipped, so check the skip list
(`-rs`). Contributor notes (virtual environments, formatting, CI, releases) are
in the repository's
[`AGENTS.md`](https://github.com/jose-pr/uripath/blob/main/AGENTS.md). To
preview the documentation: `pip install -e ".[docs]"` then `mkdocs serve`.

Changes are recorded in the
[changelog](https://github.com/jose-pr/uripath/blob/main/CHANGELOG.md).

### Releasing

This project follows [Semantic Versioning](https://semver.org/) and keeps a
[`CHANGELOG.md`](https://github.com/jose-pr/uripath/blob/main/CHANGELOG.md). Pushing a tag matching `v*` triggers the release
workflow: test gate → build (checking the tag names the version built) → a strict
docs build as a gate → GitHub release → publish. The release workflow never deploys
the docs site itself: for a final release its last job dispatches the docs workflow
at the tag, which owns every Pages deploy.

## License

MIT — see [LICENSE](https://github.com/jose-pr/uripath/blob/main/LICENSE).
