anyuri
Polymorphic URI types for Python. AnyUri auto-dispatches to the right subclass based on the input string, works as a plain str, and integrates with Pydantic v1/v2.
The Problem
Python data pipelines routinely handle assets from multiple backends — a thumbnail might live at gs://bucket/img.jpg, a report at s3://bucket/report.pdf, and a local fixture at /tmp/test.png. Without a shared abstraction, code degrades into scattered if uri.startswith("gs://") checks, duplicated normalization logic, and type annotations that lie (str accepts anything).
Cloud storage also has a dual-representation problem: the same GCS object is gs://bucket/key in SDK calls but https://storage.googleapis.com/bucket/key in HTTP clients and CDNs. Converting between them by hand is error-prone and leaks storage-specific logic everywhere.
anyuri solves both:
- Polymorphic dispatch —
AnyUri(value)returns the right subtype for the scheme; your code branches on type, not string prefixes. - Dual representation —
as_uri()gives the canonical SDK form;str(uri)gives the HTTPS form accepted by any HTTP client. Conversion is automatic. - Zero friction — every
AnyUrisubclass is astr, so it passes to any existing API without casting or adapters.
from anyuri import AnyUri
from anyuri.providers import GSUri, S3Uri # registers cloud providers
AnyUri("https://example.com/1.jpg") # → HttpUri
AnyUri("gs://bucket/key.jpg") # → GSUri
AnyUri("s3://bucket/key.jpg") # → S3Uri
AnyUri("/local/path.jpg") # → FileUri
uri = AnyUri("gs://bucket/key.jpg")
uri.as_uri() # "gs://bucket/key.jpg"
uri.as_source() # "https://storage.googleapis.com/bucket/key.jpg"
str(uri) # "https://storage.googleapis.com/bucket/key.jpg"
uri == "https://storage.googleapis.com/bucket/key.jpg" # True
Installation
pip install anyuri # core only (HttpUri, FileUri)
pip install anyuri[gcs] # + GSUri
pip install anyuri[s3] # + S3Uri
pip install anyuri[azure] # + AzureUri
pip install anyuri[all] # all cloud providers
pip install anyuri[all,pydantic] # + Pydantic integration
Supported URI Types
| Class | Schemes accepted | as_uri() | as_source() |
|---|---|---|---|
HttpUri |
http://, https:// |
original URL | original URL |
FileUri |
/path, file:///path |
file://localhost/path |
/path |
GSUri |
gs://, https://storage.googleapis.com/ |
gs://bucket/key |
https://storage.googleapis.com/bucket/key |
S3Uri |
s3://, virtual-hosted/path-style HTTPS |
s3://bucket/key |
https://bucket.s3.amazonaws.com/key |
AzureUri |
abfs://, abfss://, https://*.blob.core.windows.net/ |
abfs://container@account.dfs.core.windows.net/path |
https://account.blob.core.windows.net/container/path |
R2Uri |
r2://, https://*.r2.cloudflarestorage.com/ |
r2://account/bucket/key |
https://account.r2.cloudflarestorage.com/bucket/key |
B2Uri |
b2://, https://f*.backblazeb2.com/file/ |
b2://bucket/key |
b2://bucket/key |
Custom URI Types
from anyuri import AnyUri
from anyuri._exceptions import UriSchemaError
@AnyUri.register
class SFTPUri(AnyUri):
def __new__(cls, value):
return str.__new__(cls, cls._validate(value))
@classmethod
def _validate(cls, value):
v = str(value)
if not v.startswith("sftp://"):
raise UriSchemaError(f"Not an SFTP URI: {v!r}")
return v
@classmethod
def validate(cls, value):
return cls(cls._validate(value))
AnyUri("sftp://host/path") # → SFTPUri
Acknowledgements
This project would not exist without the support of my wife, Agnes, whose patience and encouragement make everything possible.
Pydantic Integration
from pydantic import BaseModel
from anyuri import AnyUri
from anyuri.providers import GSUri
class Asset(BaseModel):
uri: AnyUri
Asset(uri="gs://bucket/key.jpg").uri # GSUri instance
Asset(uri="https://example.com").uri # HttpUri instance
Metadata
Release files for anyuri 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| anyuri-0.1.0.tar.gz | 32.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| anyuri-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 44.7 kB
Release files / anyuri-0.1.0.tar.gz
| Download URL | anyuri-0.1.0.tar.gz |
|---|---|
| Size | 32.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
fae46bfeccc76ca112c1155a4058eb07ac42b6d405e9e8e4fd9eca79350e0496
|
|
BLAKE2b-256 checksum How to use checksums |
63d609d9e42e03e749576905a72c24927e6947e5d6d49585ae9f5ddc338fe6b4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Apr 18, 2026.
Transparency logRelease files / anyuri-0.1.0-py3-none-any.whl
| Download URL | anyuri-0.1.0-py3-none-any.whl |
|---|---|
| Size | 12.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
96f1abbb0ff647022c354174a77a5f54b17982d6c829bd7fbd4326980783228e
|
|
BLAKE2b-256 checksum How to use checksums |
03741b3c3d43d9319ab6f841849062ba6ba2576704643bf7fe25461a29037d8e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Apr 18, 2026.
Transparency log