Skip to main content

inmemory-datastore-stub

PyPI Python License CI Ruff

An in-memory Google Cloud Datastore stub for unit-testing python-ndb code — no emulator, no Java, no network, no gcloud component to install.

It implements the Datastore RPC surface in pure Python, so your tests run at in-process speed.

Why this package exists

This is a maintained fork of InMemoryCloudDatastoreStub 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, 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

pip install inmemory-datastore-stub

Usage

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

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:

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:

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():

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:

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]'.

Size limits

Datastore rejects entities that are too large, and the failure only shows up once real data arrives. These limits are enforced on write, so the test fails where the bug is:

Doc(blob="x" * 1_500_000).put()
# LimitExceededError: entity is 1500051 bytes, over the 1048572 byte limit

Doc(tags=["t"] * 21_000).put()
# LimitExceededError: entity has 21000 index entries, over the 20000 limit

Enforced: entity size (1 MiB - 4 bytes), key size (6 KiB), indexed string values (1500 bytes), nested value depth (20), index entries per entity (20,000), and keys per lookup (1000).

LimitExceededError subclasses google.api_core.exceptions.InvalidArgument, which is what Datastore raises, so an except InvalidArgument in production code catches it here too. This is the one behaviour that is on by default — pass enforce_limits=False to store what the real service would refuse.

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, by design: index build state lives in the Admin API rather than index.yaml; query planning has no meaning without a real planner, and inventing ExplainMetrics numbers would be worse than omitting them; write rate limits need load and wall-clock time, which unit tests do not have. For those, use the official Datastore emulator.

License

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

Download files

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

Source Distribution

inmemory_datastore_stub-1.1.0.tar.gz (69.5 kB view details)

Uploaded Source

Built Distribution

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

inmemory_datastore_stub-1.1.0-py3-none-any.whl (19.3 kB view details)

Uploaded Python 3

File details

Details for the file inmemory_datastore_stub-1.1.0.tar.gz.

File metadata

  • Download URL: inmemory_datastore_stub-1.1.0.tar.gz
  • Upload date:
  • Size: 69.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for inmemory_datastore_stub-1.1.0.tar.gz
Algorithm Hash digest
SHA256 3289c7c914590634745d07a231dcd5ed87b6a9ef8c89f2ca5998760778505bae
MD5 24ce480e1d7805c97f6007a51fd30aa5
BLAKE2b-256 79067ee3751935e55a30fb25660ab501f326b6ad5ca17093f70049af5f3bc416

See more details on using hashes here.

Provenance

The following attestation bundles were made for inmemory_datastore_stub-1.1.0.tar.gz:

Publisher: publish.yml on skippdot/inmemory-datastore-stub

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file inmemory_datastore_stub-1.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for inmemory_datastore_stub-1.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 806f4ba56efa9db828445795aeda03359e8a94d17658fe4185d3b92ed05bc865
MD5 1315b50ec1fa0b51d165647821559bfc
BLAKE2b-256 b20b6abc2ce2076f32358c390d7a2e450ccdaf46bafae31b5728bcb6ea92a5d2

See more details on using hashes here.

Provenance

The following attestation bundles were made for inmemory_datastore_stub-1.1.0-py3-none-any.whl:

Publisher: publish.yml on skippdot/inmemory-datastore-stub

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

1.1.0 This release

2 files

1.0.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page