Skip to main content

storix

Storix

Storage for Unix lovers.

One unix-flavored filesystem API over any storage — local disk, in-memory,
Azure Data Lake — sync & async, sandboxable, fully typed.

PyPI version GitHub stars License

0.2.0 is a ground-up rework (hexagonal core, generated sync flavor, layers, capabilities). Migrating from 0.1.x? See the table in release-notes.md. Full documentation site: coming soon.


Install

uv add storix            # local filesystem + in-memory
uv add "storix[azure]"   # + Azure Data Lake Gen2 (HNS accounts)

Five minutes of storix

from storix import Storix
from storix.backends import LocalBackend

fs = Storix(LocalBackend('~/data'))     # '/' is anchored at ~/data

fs.mkdir('/docs')
fs.echo(b'hello, storix!', '/docs/readme.txt')
print(fs.cat('/docs/readme.txt'))       # b'hello, storix!'

fs.cd('/docs')                          # sessions have a cwd, like a shell
fs.touch('a.txt', 'b.txt')              # variadic, like real touch
print(fs.ls())                          # dotfiles hidden, like real ls
fs.mkdir('/archive')
fs.mv('a.txt', 'b.txt', '/archive')     # last argument is the destination
fs.rm('/archive', recursive=True)       # rm -r; rmdir is strictly empty dirs

Async is the same API under storix.aio - every operation awaitable:

from storix.aio import Storix
from storix.aio.backends import AzureBackend

async with Storix(AzureBackend('raw', account_name=..., credential=...)) as fs:
    await fs.echo(b'...', '/report.csv')
    print(await fs.url('/report.csv', expires_in=600))   # SAS link

(The sync flavor is generated from the async source - identical semantics, verified by one conformance suite running against every backend in both flavors.)

Configuration

get_storage() builds a session from the environment (see env.example):

STORIX_PROVIDER=azure
STORIX_AZURE_CONTAINER=raw
STORIX_AZURE_ACCOUNT_NAME=myaccount
STORIX_AZURE_CREDENTIAL=...
from storix import get_storage

fs = get_storage()                        # env-driven; defaults to ~/.storix
fs = get_storage('local', base='~/x')     # typed per-provider overrides

Sandboxing & scratch spaces

Layers are backends that wrap backends. SandboxLayer is chroot as middleware - escape-proof, with errors re-scoped so the real prefix never leaks to the sandboxed caller:

from storix import SandboxLayer, Storix, temporary
from storix.backends import LocalBackend

backend = LocalBackend('/srv/data')
fs = Storix(SandboxLayer(backend, root='/tenant-42'))   # escape-proof jail

with fs.scratch() as tmp:               # ephemeral workspace on fs's OWN
    tmp.echo(b'work', '/notes.txt')     # backend (any backend) - unique
                                        # subtree, deleted on exit

with fs.scratch(root='/agent-7') as tmp:  # pinned: created if missing,
    ...                                   # reused, PERSISTS on exit

with temporary() as fs:                 # local-only convenience: zero-config
    fs.echo(b'scratch', '/tmp.txt')     # mkdtemp on real disk, self-destructs

Write your own layers by subclassing LayerBase (override what you change, upgrade the capabilities you add) — see samples/layers/.

Capabilities

Backends advertise optional features; storix fails loudly instead of silently dropping arguments:

fs.echo(b'x', '/f.png', content_type='image/png')       # azure: stored
fs.set_metadata('/f.png', {'owner': 'me'}, merge=True)  # azure/memory
fs.url('/f.png', expires_in=600)                        # presigned (SAS)
fs.data_url('/f.png')                                   # any backend
# unsupported -> UnsupportedOperationError naming the missing capability

Portable capabilities via layers. Bundled layers backfill missing capabilities so one construction path works across providers. with_layer_missing infers the capability from the layer and skips it where the backend is already native:

from storix import DataUrlLayer, MetadataLayer

# url() everywhere: native SAS on azure, data: URLs on local
fs = get_storage('local').with_layer_missing(DataUrlLayer)
# custom metadata everywhere: native on azure, JSON sidecar on local
fs = fs.with_layer_missing(MetadataLayer)
# with_layer forwards typed kwargs to the layer, Starlette-style
# (serialize/deserialize are object<->bytes; orjson works, json.dumps
# does not - it returns str):
fs = fs.with_layer(MetadataLayer, serialize=orjson.dumps, deserialize=orjson.loads)

Switching 'local' to 'azure' needs no code change — the native capability wins and the layer becomes a no-op.

Caching

CacheLayer is a read-through cache — another layer. Cache the ops that repeat: metadata (stat/ls/exists, on by default), the expensive du walk, file reads, and presigned urls. Each op is True or a cache(...) spec; every write through the layer evicts what it touches.

from storix import CacheLayer, cache, get_storage

fs = get_storage('azure').with_layer(
    CacheLayer,
    du=cache(ttl=60),                 # opt in; du (the tree walk) is the big win
    read=cache(max_bytes=8 << 20),    # content, capped per file
)
fs.du('/big-tree')                    # first call: a full walk
fs.du('/big-tree')                    # then: instant, until you write

The store is pluggable — a cashews-shaped get/set/delete/delete_match protocol — so the in-memory default swaps for a cashews.Cache (Redis, disk) with no adapter. Keys are namespaced and keyed on the physical location, so sessions sharing one store never collide. Correctness assumes you are the only writer; pass ttl to bound staleness.

In the sx shell, one flag turns it on:

sx -p azure --cache                        # cache on: du/ls/stat/cat
sx -p azure --cache --sandbox /tenant-42   # compose with a jail
# in the REPL: `refresh` clears it; `provider` shows the stack + store

Backends

Backend Import Notes
Local disk storix.backends.LocalBackend anchored at a base directory
In-memory storix.backends.MemoryBackend reference backend; great for tests
Azure ADLS Gen2 storix.backends.AzureBackend requires hierarchical namespaces

Third-party backends implement the ~13-method StorageBackend port (or subclass BackendBase for generic fallbacks) and hook in via register_backend().

License

Storix is licensed under the Apache 2.0 License

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

storix-0.2.1.tar.gz (78.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

storix-0.2.1-py3-none-any.whl (110.4 kB view details)

Uploaded Python 3

File details

Details for the file storix-0.2.1.tar.gz.

File metadata

  • Download URL: storix-0.2.1.tar.gz
  • Upload date:
  • Size: 78.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.28 {"installer":{"name":"uv","version":"0.11.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for storix-0.2.1.tar.gz
Algorithm Hash digest
SHA256 bef1322bd011fd8475982fcde82317a102fc6348baf9aba7f62ef5f615cffb6c
MD5 110653b78505a5c2eaf3cb3878a959a2
BLAKE2b-256 37f1da5180694a5fa4d93475501369b843ec7ef5c6ef392e0c3e1d68e4404641

See more details on using hashes here.

File details

Details for the file storix-0.2.1-py3-none-any.whl.

File metadata

  • Download URL: storix-0.2.1-py3-none-any.whl
  • Upload date:
  • Size: 110.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.28 {"installer":{"name":"uv","version":"0.11.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for storix-0.2.1-py3-none-any.whl
Algorithm Hash digest
SHA256 b06624bf5a28f6a250fe7eaba9ec2822080ea3924140ac98a232d6549b1d6140
MD5 073f8089069411f44f34398721b0ab2a
BLAKE2b-256 d6ba68d1a07ff4bb9c4367bd6170cf5d84d1f96445dd4551fa302cc22b891ab7

See more details on using hashes here.

Release history Release notifications | RSS feed

0.5.5

2 files

0.5.4

2 files

0.5.3

2 files

0.5.2

2 files

0.5.1

2 files

0.5.0

2 files

0.4.9

2 files

0.4.8

2 files

0.4.7

2 files

0.4.6

2 files

0.4.5

2 files

0.4.4

2 files

0.4.3

2 files

0.4.2

2 files

0.4.1

2 files

0.4.0

2 files

0.3.0

2 files

0.2.2

2 files

This release

0.2.1 This release

2 files

0.2.0

2 files

0.1.3

2 files

0.1.2

2 files

0.1.1

2 files

0.1.0

2 files

0.0.3

2 files

0.0.2

2 files

0.0.1

2 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page