Metadata-Version: 2.5
Name: inmemory-datastore-stub
Version: 1.0.0
Summary: An in-memory Google Cloud Datastore stub for unit-testing python-ndb code
Project-URL: Homepage, https://github.com/skippdot/inmemory-datastore-stub
Project-URL: Issues, https://github.com/skippdot/inmemory-datastore-stub/issues
Project-URL: Changelog, https://github.com/skippdot/inmemory-datastore-stub/blob/main/CHANGELOG.md
Author: Stepan Shamaiev
License-Expression: MIT
License-File: LICENSE
Keywords: datastore,emulator,google-cloud,ndb,stub,testing
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
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: Topic :: Software Development :: Testing
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: google-cloud-datastore>=2.0.0
Requires-Dist: google-cloud-ndb>=2.0.0
Requires-Dist: grpcio>=1.40.0
Provides-Extra: dev
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: pyyaml>=6.0; extra == 'dev'
Requires-Dist: ruff<0.17,>=0.16; extra == 'dev'
Provides-Extra: indexes
Requires-Dist: pyyaml>=6.0; extra == 'indexes'
Description-Content-Type: text/markdown

# inmemory-datastore-stub

[![PyPI](https://img.shields.io/pypi/v/inmemory-datastore-stub?label=pypi%20package&color=3fb950)](https://pypi.org/project/inmemory-datastore-stub/)
[![Python](https://img.shields.io/pypi/pyversions/inmemory-datastore-stub)](https://pypi.org/project/inmemory-datastore-stub/)
[![License](https://img.shields.io/badge/License-MIT-e3b341)](LICENSE)
[![CI](https://img.shields.io/github/actions/workflow/status/skippdot/inmemory-datastore-stub/test.yml?branch=main&label=CI&logo=github)](https://github.com/skippdot/inmemory-datastore-stub/actions/workflows/test.yml)
[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)

An in-memory Google Cloud Datastore stub for unit-testing [python-ndb](https://github.com/googleapis/python-ndb) code — no emulator, no Java, no network, no `gcloud` component to install.

It implements the [Datastore RPC surface](https://cloud.google.com/datastore/docs/reference/data/rpc) in pure Python, so your tests run at in-process speed.

## Why this package exists

This is a maintained fork of [InMemoryCloudDatastoreStub](https://github.com/phil-lopreiato/google-cloud-datastore-stub) by Phil Lopreiato, which has been unmaintained since January 2021 and only works with `google-cloud-datastore` 1.x. On the 2.x libraries it fails outright:

- `google.cloud.datastore_v1.proto` was removed in datastore 2.x, so importing the original raises `ModuleNotFoundError`
- datastore 2.x wraps every message in [proto-plus](https://proto-plus-python.readthedocs.io/), which does not expose the protobuf-only API the stub relies on (`WhichOneof`, `HasField`, `CopyFrom`, `SerializeToString`)
- ndb 2.x calls the RPC methods by snake_case name (`lookup`, `commit`, `run_query`, …) while the original only exposes the 1.x CamelCase names
- ndb 2.x expects a `database` attribute on the client
- `allocate_ids` was never implemented

This fork fixes all of the above and is tested on Python 3.10–3.14.

## Install

```bash
pip install inmemory-datastore-stub
```

## Usage

The simplest form — use the stub-backed client directly:

```python
from google.cloud import ndb
from inmemory_datastore_stub import Client


class Movie(ndb.Model):
    title = ndb.StringProperty()


def test_movies():
    with Client().context():
        Movie(title="Roman Holiday").put()
        assert Movie.query().count() == 1
```

As a pytest fixture:

```python
import pytest
from inmemory_datastore_stub import Client


@pytest.fixture
def ndb_context():
    with Client().context():
        yield
```

If the code under test constructs its own `ndb.Client()`, patch it globally:

```python
from inmemory_datastore_stub import patch_ndb

with patch_ndb():
    import myapp  # myapp calls ndb.Client() at import time

    myapp.run()
```

`patch_ndb()` restores the original `ndb.Client` on exit.

## Catching bugs the stub would otherwise hide

Two behaviours of real Datastore make code fail in production that passes against a naive in-memory stub. Both are reproduced here, and both are opt-in so they never break an existing suite.

### Eventual consistency

Non-ancestor queries in Datastore do not see writes immediately. Turn that on and the stub holds fresh writes back until you call `catch_up()`:

```python
client = Client(eventual_consistency=True)

with client.context():
    Movie(title="fresh").put()

    assert Movie.query().fetch() == []  # non-ancestor query lags, as in production
    assert movie.key.get().title == "fresh"  # key lookups are strongly consistent

    client.catch_up()
    assert len(Movie.query().fetch()) == 1
```

Ancestor queries and key lookups stay strongly consistent, matching Datastore.

### Composite index validation

Datastore refuses any query whose composite index is not declared in `index.yaml`. Point the stub at yours and it refuses them too — with the same index suggestion Datastore gives you, ready to paste:

```python
client = Client(index_yaml="index.yaml")
```

```
NoMatchingIndexError: no matching index found. recommended index is:
indexes:
- kind: Movie
  properties:
  - name: genre
  - name: title
```

Needs PyYAML: `pip install 'inmemory-datastore-stub[indexes]'`.

## What is supported

`put`, `get`, `delete`, `put_multi`/`get_multi`/`delete_multi`, `get_or_insert`, `allocate_ids`, transactions, and ancestor queries.

Queries: equality, inequality, `!=`, `IN`, `NOT_IN`, `OR`, repeated properties, `StructuredProperty` and `ComputedProperty`, ordering by several properties in either direction, `offset`/`limit`, `count`, cursors and `fetch_page`, projection and keys-only queries, and GQL.

Not reproduced: index build state, query planning details, and Datastore's own size and rate limits. For those, use the [official Datastore emulator](https://cloud.google.com/datastore/docs/tools/datastore-emulator).

## License

MIT — see [LICENSE](LICENSE). Original work © 2020 Phil Lopreiato; modifications © 2026 Stepan Shamaiev.
