obstore

repository·main·Indexed 20 days ago

https://github.com/developmentseed/obstore

A high-performance Python library powered by Rust providing a unified, high-throughput interface to Amazon S3, Google Cloud Storage, Azure Blob Storage, and S3-compliant APIs like Cloudflare R2. It supports sync and async APIs, streaming operations, and zero-copy data exchange via the buffer protocol. Version 0.11.0.

Tokens
40.7K
Snippets
126
Records
210
Agent score
68%

What's inside obstore

  1. Overview of pyo3-bytes

    main
    pyo3-bytes provides an integration between the Rust bytes crate and pyo3. It introduces the PyBytes type, which is a wrapper around bytes::Bytes that supports the Python buffer protocol. This allows for efficient data transfer between Rust and Python by avoiding unnecessary copies.
  2. Overview of obstore features

    main

    obstore is a high-throughput Python interface to S3-compliant APIs (Amazon S3, Google Cloud Storage, Azure Storage, etc.) powered by Rust. Key features include:

    • Unified Interface: One interface for multiple backends with no required Python dependencies.
    • API Styles: Supports both sync and async APIs with full type hinting.
    • Streaming Operations:
      • Streaming downloads with configurable chunking.
      • Streaming uploads from files or async/sync iterators.
      • Streaming list operations (no manual pagination required).
    • Automated Management: Automatic multipart uploads for large objects and automatic credential refresh before expiration.
    • Integrations: File-like object API and fsspec integration.
    • Performance:
      • Optional Apache Arrow format for list results (faster and more memory-efficient than Python dicts).
      • Zero-copy data exchange between Rust and Python via the buffer protocol.
      • High throughput for concurrent, small, async GET requests.
  3. Leverage Obstore developer experience features

    main

    Beyond performance, Obstore provides several features designed to simplify object storage workflows:

    • Zero Python dependencies: Simple to install without a complex dependency tree.
    • Cloud Agnostic: The same interface works across AWS S3, Google Cloud Storage, and Azure Storage.
    • Type Safety: Full type hinting for all store configurations and operations.
    • Stream-friendly APIs: Downloads automatically act as iterators, and uploads automatically accept iterators.
    • Automatic Pagination: list calls handle pagination automatically behind the scenes.
  4. Choose between Method API and Functional API for Get operations

    main

    Obstore provides two distinct API designs for performing get operations: a Method API and a Functional API.

    • Method API: These are methods called directly on an instance of an object store (e.g., store.get(...)). Use this when you have an existing ObjectStore instance and want to use its encapsulated state.
    • Functional API: These are standalone functions (e.g., obstore.get(store, ...)). Use these for a more functional programming style or when you prefer passing the store as an explicit argument.
  5. Understand Obstore performance characteristics

    main

    Obstore's performance impact depends on the type of operation being performed. Use the following mental model to choose your implementation strategy:

    Improved Performance

    • Many-request throughput (Asynchronous API): This is the primary strength of Obstore. It excels when making many concurrent requests, particularly for small files, by reducing Python overhead.

    Possibly Improved Performance

    • Synchronous API in Thread Pools: While not explicitly benchmarked, Obstore releases the Python Global Interpreter Lock (GIL) during all synchronous operations. This means synchronous Obstore calls may perform better than other Python request libraries when executed within a thread pool.

    Equal Performance

    • Single-request throughput: For a single request, performance is typically limited by network conditions rather than Python overhead. However, note that obstore.put uses multipart uploads by default, which may provide efficiency gains by uploading various parts of a file concurrently.
    • Latency: Download latency (time until the first byte is received) is primarily driven by hardware and network conditions; Obstore's latency is expected to be similar to other standard Python request libraries.
  6. List objects as Arrow for high performance

    main

    To avoid the overhead of creating many small Python dictionaries when listing large buckets, use return_arrow=True in the list method. This returns each chunk of results as an Arrow RecordBatch.

    This approach allows for zero-copy conversion to other Arrow-backed libraries. This requires the arro3-core dependency.

    Supported zero-copy conversions:

    • pyarrow: pyarrow.record_batch(record_batch)
    • polars: polars.DataFrame(record_batch)
    • pandas: pyarrow.record_batch(record_batch).to_pandas(types_mapper=pd.ArrowDtype)
    • arro3: arro3.core.RecordBatch(record_batch)
    import pandas as pd
    import pyarrow as pa
    from obstore.store import S3Store
    
    store = S3Store("sentinel-cogs", region="us-west-2", skip_signature=True)
    stream = store.list(chunk_size=20, return_arrow=True)
    
    for record_batch in stream:
        # Convert to pyarrow (zero-copy), then to pandas
        df = pa.record_batch(record_batch).to_pandas()
        print(df.iloc[:5].to_markdown(index=False))
        break
  7. Safety considerations with the Python buffer protocol

    main

    The pyo3-bytes interface is not 100% safe because the Python buffer protocol does not enforce buffer immutability.

    Warning: Python users must be instructed not to mutate buffers that have been passed to Rust to avoid undefined behavior.

  8. Understand Native Authentication in obstore

    main

    Native authentication refers to authentication methods supported directly by the underlying Rust object_store library. Using native authentication is the most efficient method because obstore does not need to call into Python to update or manage credentials.

    Order of Application

    Native authentication credentials are applied in the following priority order:

    1. Keyword/Config parameters: Any parameters passed directly via the config parameter or as keyword arguments during store construction.
    2. Environment variables: If no explicit parameters are provided, obstore looks for corresponding environment variables.

    Explicitly passed parameters will always override values found in environment variables.

  9. Use the functional API for object store operations

    main

    Obstore uses a functional API design. Instead of calling methods directly on a store instance (e.g., store.put(...)), you must pass the store instance as the first argument to top-level functions provided by the obstore module (e.g., obstore.put(store, ...)).

    This design ensures that generic operations work across all types of stores and middlewares (like prefixes) without needing to re-implement or re-expose specific methods on every wrapper.

    import obstore as obs
    from obstore.store import AzureStore
    
    store = AzureStore()
    # Correct: Use the top-level function
    obs.put(store, ...)
    
    # Incorrect: Do not attempt to call methods on the store instance
    # store.put(...) 
  10. Use the Functional API for object storage operations

    main

    The functional API provides top-level functions in the obstore module to perform operations. Unlike the method-based API, you must explicitly pass the store instance as the first argument to every function call. This API is required for certain features that are not universally supported by all object storage backends.

    import obstore as obs
    from obstore.store import S3Store
    
    store = S3Store("bucket", ...)
    buffer = obs.get_range(store, "path", start=0, end=16384)
  11. Accept external ObjectStores using AnyObjectStore

    main

    If you want your library to accept ObjectStore instances created by other Python libraries (such as obstore), use AnyObjectStore as your function parameter. AnyObjectStore acts as a wrapper that can handle both PyObjectStore (from your own library) and PyExternalObjectStore (from external libraries).

    Warning on ABI Stability: Because object_store does not have a stable ABI, using AnyObjectStore to accept external stores results in the store being recreated. This means connection pooling cannot be shared between your library and the external library.

  12. Understand Pickle support in Obstore

    main

    Obstore supports Python's pickle protocol, which is useful for distributed execution frameworks like Dask that need to manage and share store state across different workers.

    Important Limitations:

    • Not for Persistence: Do not use pickle for long-term data storage. The pickle format used by stores may change between versions. It is intended only for sharing state between workers running the same environment (same Python and obstore versions).
    • MemoryStore Incompatibility: Pickling is not supported for MemoryStore because the raw state of the store cannot be accessed for serialization.
    • Middleware Compatibility: Future middlewares (e.g., for request metrics) are unlikely to support pickle.