Skip to main content

Object storage interface definitions for Python.

Project description

obspec

Object storage protocol definitions for Python.

Background

Python defines two types of subtyping: nominal and structural subtyping. In essence, nominal subtyping is subclassing. Class A is a nominal subtype of class B if A subclasses from B. Structural subtyping is duck typing. Class A is a structural subtype of class B if A "looks like" B, that is, it conforms to the same shape as B.

Using structural subtyping means that an ecosystem of libraries don't need to have any knowledge or dependency on each other, as long as they strictly and accurately implement the same duck-typed interface.

For example, an Iterable is a protocol. You don't need to subclass from a base Iterable class in order to make your type iterable. Instead, if you define an __iter__ dunder method on your class, it automatically becomes iterable because Python has a convention that if you see an __iter__ method, you can call it to iterate over a sequence.

As another example, the Buffer Protocol is a protocol to enable zero-copy exchange of binary data between Python libraries. Unlike Iterable, this is a protocol that is inaccessible in user Python code and only accessible at the C level, but it's still a protocol. Numpy can create arrays that view a buffer via the buffer protocol, even when Numpy has no prior knowledge of the library that produces the buffer.

Obspec defines core protocols to interface with data stored on file systems, remote object stores, etc.

Usage

You should use the minimal methods required for your use case, creating your own protocol with just what you need.

In particular, Python allows you to intersect protocols:

from typing import Protocol

from obspec import Delete, Get, List, Put


class MyCustomObspecProtocol(Delete, Get, List, Put, Protocol):
    """My custom protocol."""

Then use that protocol generically:

def do_something(backend: MyCustomObspecProtocol):
    backend.put("path.txt", b"hello world!")

    files = backend.list().collect()
    assert any(file["path"] == "path.txt" for file in files)

    assert backend.get("path.txt").bytes() == b"hello world!"

    backend.delete("path.txt")

    files = backend.list().collect()
    assert not any(file["path"] == "path.txt" for file in files)

In particular, by defining the most minimal interface you require, it widens the set of possible backends that can implement your interface. For example, making a range request is possible by any HTTP client, but a list call may have semantics not defined in the HTTP specification. So by only requiring, say, Get and GetRange you allow more implementations to be used with your program.

Example: Cloud-Optimized GeoTIFF reader

A Cloud-Optimized GeoTIFF (COG) reader might only require range requests

from typing import Protocol

from obspec import GetRange, GetRanges


class CloudOptimizedGeoTiffReader(GetRange, GetRanges, Protocol):
    """Protocol with necessary methods to read a Cloud-Optimized GeoTIFF file."""


def read_cog_header(backend: CloudOptimizedGeoTiffReader, path: str):
    # Make request for first 32KB of file
    header_bytes = backend.get_range(path, start=0, end=32 * 1024)

    # TODO: parse information from header
    raise NotImplementedError


def read_cog_image(backend: CloudOptimizedGeoTiffReader, path: str):
    header = read_cog_header(backend, path)

    # TODO: read image data from file.

An async Cloud-Optimized GeoTIFF reader might instead subclass from obspec's async methods:

from typing import Protocol

from obspec import GetRangeAsync, GetRangesAsync


class AsyncCloudOptimizedGeoTiffReader(GetRangeAsync, GetRangesAsync, Protocol):
    """Necessary methods to asynchronously read a Cloud-Optimized GeoTIFF file."""


async def read_cog_header(backend: AsyncCloudOptimizedGeoTiffReader, path: str):
    # Make request for first 32KB of file
    header_bytes = await backend.get_range_async(path, start=0, end=32 * 1024)

    # TODO: parse information from header

    raise NotImplementedError


async def read_cog_image(backend: AsyncCloudOptimizedGeoTiffReader, path: str):
    header = await read_cog_header(backend, path)

    # TODO: read image data from file.

Implementations

The primary implementation that implements obspec is obstore, and the obspec protocol was designed around the obstore API.

Utilities

There are planned to be utilities that build on top of obspec. Potentially:

  • globbing: an implementation of glob() similar to fsspec.glob that uses obspec primitives.
  • Caching: wrappers around Get/GetRange/GetRanges that store a cache of bytes.

By having these utilities operate on generic obspec protocols, it means that they can instantly be used with any future obspec backend.

Project details


Download files

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

Source Distribution

obspec-0.1.0b5.tar.gz (114.2 kB view details)

Uploaded Source

Built Distribution

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

obspec-0.1.0b5-py3-none-any.whl (19.0 kB view details)

Uploaded Python 3

File details

Details for the file obspec-0.1.0b5.tar.gz.

File metadata

  • Download URL: obspec-0.1.0b5.tar.gz
  • Upload date:
  • Size: 114.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.7.5

File hashes

Hashes for obspec-0.1.0b5.tar.gz
Algorithm Hash digest
SHA256 55abb6f718e279b3c15dca4b82d763ec6931ffffab6d428967be71b8ade2a02e
MD5 674313aa56b0e567ac1ee956911448a4
BLAKE2b-256 f24e1611f2e3e0bf5d42c17e6a709cc8f57f48562c56542d637b7b7e8f8b014a

See more details on using hashes here.

File details

Details for the file obspec-0.1.0b5-py3-none-any.whl.

File metadata

  • Download URL: obspec-0.1.0b5-py3-none-any.whl
  • Upload date:
  • Size: 19.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.7.5

File hashes

Hashes for obspec-0.1.0b5-py3-none-any.whl
Algorithm Hash digest
SHA256 5e07ec126806e7e9bc57d5f7b9f52926227f57a4cbc9da37640d367efa14d539
MD5 d8eec6d57c79e4724ba25b01e6eb7216
BLAKE2b-256 8a9017f2a80dbeb80e41f3f6251d8badc6e42ee9b8ebc36aed0814682de8b94c

See more details on using hashes here.

Supported by

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