Skip to content

Fix #788: make MemoryStore equality reflect data isolation - #1123

Open
rkingsbury wants to merge 6 commits into
materialsproject:mainfrom
rkingsbury:fix-788-memorystore-eq
Open

Fix #788: make MemoryStore equality reflect data isolation#1123
rkingsbury wants to merge 6 commits into
materialsproject:mainfrom
rkingsbury:fix-788-memorystore-eq

Conversation

@rkingsbury

Copy link
Copy Markdown
Collaborator

Summary

Fixes #788.

Two MemoryStores instantiated with the same collection_name are isolated — they do not share data — yet MemoryStore.__eq__ compared only collection_name and returned True. That inconsistency is what #788 reports.

This makes equality reflect the actual data source, consistent with MongoStore (which is equal only when it points at the same physical database/collection). Each MemoryStore is backed by its own unique ephemeral database (_database), so:

  • two separately-created MemoryStores — even with the same collection_name — are not equal (and hash differently);
  • a store still equals itself.
from maggma.stores import MemoryStore

MemoryStore() == MemoryStore()          # now False (was True); they are isolated
s = MemoryStore(); s == s               # True

Details

  • MemoryStore.__eq__ / __hash__ now include the unique per-instance _database. A class-level _database = None default keeps subclasses that don't call MemoryStore.__init__ (i.e. MontyStore) working.
  • FileStore is backed by an on-disk directory, not isolated in-memory data, so it previously inherited MemoryStore's equality incorrectly (all FileStores shared collection_name="file_store", so any two compared equal while hashing differently — a broken __eq__/__hash__ invariant). It now compares by its tracked path, so two FileStores for the same directory are equal and two for different directories are not.
  • JSONStore already compared by paths and is unchanged.

Tests

  • test_memory_store_eq_isolated — two same-collection_name MemoryStores are isolated (count() 2 vs 0) and therefore unequal with differing hashes; a store equals itself.
  • test_file_store_eqFileStores for the same directory are equal (and hash-equal); different directories are not.

Verified on both backends: tests/stores/test_mongolike.py + tests/stores/test_file_store.py + tests/builders pass (53 with mongod; new tests also pass on the mongomock fallback), and tests/stores/test_aws.py is unaffected.

Note

This branches off #1121 and will show that PR's commits until it merges; review/merge #1121 first. It intentionally addresses #788 separately from #1121, per discussion there.

🤖 Generated with Claude Code

rkingsbury and others added 5 commits July 8, 2026 11:10
…gomock-ng

MemoryStore now probes host:port (default localhost:27017) on connect and, if
a MongoDB server answers, stores data in a real, ephemeral MongoDB database for
full MongoDB compatibility and performance. Each instance uses a unique
maggma_memory_<uuid> database that is dropped automatically (via weakref
finalizer) when the store is garbage collected or the interpreter exits, so the
user never has to set up or clean up a database. If no server is reachable the
store transparently falls back to an in-process mongomock-ng database.

- close() is now a no-op that leaves the store usable, preserving the historical
  mongomock behavior relied upon by the builders (pymongo raises on
  use-after-close). Use connect(force_reset=True) to discard contents.
- JSONStore/FileStore inherit the behavior via a shared _connect_collection;
  MontyStore gets its own close() so its disk-backed client still closes.
- Adds backward-compatible host/port/mongoclient_kwargs/server_selection_timeout_ms
  kwargs; server_selection_timeout_ms=0 forces the mongomock backend.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Addresses GitHub AI security warnings about except clauses that only pass
without an explanatory comment (the two best-effort ephemeral-database drops).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Previously MemoryStore.close() was a no-op so that callers could keep querying
a "closed" store. This makes close() actually disconnect: querying a closed
store now raises StoreError, and the store must be reconnected first. Data is
preserved across a close()/connect() cycle -- the real-MongoDB backend keeps it
in the ephemeral server-side database (dropped by the finalizer on GC/exit), and
the mongomock backend keeps its in-process client alive -- so reconnect restores
the contents while connect(force_reset=True) starts fresh.

The Builder framework already only queries stores before closing them
(run/serial/multi all do connect -> get_items -> update_targets -> finalize, and
MapBuilder.finalize queries before super().finalize()). The only query-after-close
was in the builder test suite, which relied on the old no-op close(); those tests
now reconnect the target before asserting, matching the existing pattern in
test_copy_builder.test_run.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
test_close expected an AttributeError from querying a closed S3Store (because
S3Store.close() sets s3_bucket=None). With MemoryStore.close() now being a
genuine close, the S3 index is genuinely closed and raises StoreError when
queried, before the s3_bucket access is reached. Expect StoreError, which is
the more principled signal that the store was closed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ation

Two MemoryStores instantiated with the same collection_name are isolated (they
do not share data), yet MemoryStore.__eq__ compared only collection_name and
returned True. Equality now reflects the actual data source: each MemoryStore
has a unique ephemeral database (_database), and __eq__/__hash__ include it, so
distinct instances are unequal while a store still equals itself, matching the
"same data source" semantics of MongoStore.

FileStore is backed by an on-disk directory and previously inherited
MemoryStore's equality; it now compares by its tracked path so two FileStores
for the same directory remain equal. A class-level _database=None default keeps
subclasses that skip MemoryStore.__init__ (MontyStore) working.

Adds regression tests for the isolated-and-unequal MemoryStore case and for
FileStore path-based equality.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Comment thread tests/stores/test_mongolike.py Dismissed
The materialsproject#788 fix made MemoryStore.__eq__ depend on the unique per-instance
_database, but that id was regenerated on every from_dict, so a store no longer
equaled its own serialized round-trip copy. MultiStore.add_store caches a
round-tripped copy and later matches stores by equality
(get_store_index), so the lookup returned None and self._stores[None] raised
"TypeError: list indices must be integers or slices, not NoneType" in the
multistore query/count tests.

_database is now an (internal) init parameter, so it is carried through
as_dict/from_dict: a round-tripped store keeps the same identity and compares
equal to the original, while a freshly constructed store still gets its own new
identity (preserving the materialsproject#788 fix). Adds a regression test for the round-trip
equality.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@codecov

codecov Bot commented Jul 8, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 92.00000% with 6 lines in your changes missing coverage. Please review.
✅ Project coverage is 69.93%. Comparing base (ca564e4) to head (1fd7531).
⚠️ Report is 4 commits behind head on main.

Files with missing lines Patch % Lines
src/maggma/stores/mongolike.py 92.64% 5 Missing ⚠️
src/maggma/stores/file_store.py 85.71% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1123      +/-   ##
==========================================
+ Coverage   69.53%   69.93%   +0.40%     
==========================================
  Files          47       47              
  Lines        4103     4171      +68     
==========================================
+ Hits         2853     2917      +64     
- Misses       1250     1254       +4     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@rkingsbury rkingsbury added the fix label Jul 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

MemoryStore __eq__ does not behave as expected

2 participants