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.

Metadata

Release files for inmemory-datastore-stub 1.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for inmemory-datastore-stub 1.1.0
File Size Uploaded Provenance Actions
inmemory_datastore_stub-1.1.0.tar.gz 69.5 kB With provenance Details for inmemory_datastore_stub-1.1.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for inmemory-datastore-stub 1.1.0
File Interpreter ABI Platform Actions
inmemory_datastore_stub-1.1.0-py3-none-any.whl Python 3 none any Details for inmemory_datastore_stub-1.1.0-py3-none-any.whl

Total release size: 88.8 kB

Release files / inmemory_datastore_stub-1.1.0.tar.gz

Download URL inmemory_datastore_stub-1.1.0.tar.gz
Size 69.5 kB
Tags Source
SHA-256 checksum
How to use checksums
3289c7c914590634745d07a231dcd5ed87b6a9ef8c89f2ca5998760778505bae
BLAKE2b-256 checksum
How to use checksums
79067ee3751935e55a30fb25660ab501f326b6ad5ca17093f70049af5f3bc416
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 19, 2026.

Transparency log

Release files / inmemory_datastore_stub-1.1.0-py3-none-any.whl

Download URL inmemory_datastore_stub-1.1.0-py3-none-any.whl
Size 19.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
806f4ba56efa9db828445795aeda03359e8a94d17658fe4185d3b92ed05bc865
BLAKE2b-256 checksum
How to use checksums
b20b6abc2ce2076f32358c390d7a2e450ccdaf46bafae31b5728bcb6ea92a5d2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 19, 2026.

Transparency log

Security for inmemory-datastore-stub 1.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for inmemory_datastore_stub-1.1.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 19, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

1.1.0 This release

2 release files Full provenance

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