Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added backends to synced_collection #364

Merged
merged 40 commits into from
Aug 20, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
5531735
Added redis, zarr, mongodb backeneds
vishav1771 Jul 31, 2020
22c4235
Added redis, zarr, mongodb backeneds
vishav1771 Jul 31, 2020
80ef22d
Removed extra import
vishav1771 Jul 31, 2020
0203f28
Added zarr, redis to requirement-dev.txt
vishav1771 Jul 31, 2020
113b274
Correction requirement-dev.txt
vishav1771 Jul 31, 2020
b1795f6
moved numcodecs import in tests
vishav1771 Jul 31, 2020
84ec0b5
Linting error
vishav1771 Jul 31, 2020
8c0d31b
Renamed zarr temp dir
vishav1771 Aug 2, 2020
83492a4
Added test for redis
vishav1771 Aug 4, 2020
456878f
Added test for redis
vishav1771 Aug 4, 2020
5275f3a
Added test for redis-backend
vishav1771 Aug 4, 2020
0c8cc25
Added test for redis backend and changed Zarr to ZARR
vishav1771 Aug 4, 2020
dc7f8ae
Linting changes
vishav1771 Aug 4, 2020
dd2e4fa
Linting changes
vishav1771 Aug 4, 2020
5d765b4
Added test for mongocollection
vishav1771 Aug 6, 2020
5dd9e15
Linting Changes
vishav1771 Aug 6, 2020
c1ded73
Added comments
vishav1771 Aug 6, 2020
1a1b075
Added test for mongodb server
vishav1771 Aug 6, 2020
c17abb8
Moved import to class __init__
vishav1771 Aug 6, 2020
8632cdb
removed numcodec import error
vishav1771 Aug 6, 2020
6cdb6f4
Updated documentation
vishav1771 Aug 6, 2020
f113452
Changed API as suggested
vishav1771 Aug 11, 2020
3dc7413
Documentation changes
vishav1771 Aug 11, 2020
6ff7ecb
Change name MongoCollection to MongoDBCollection
vishav1771 Aug 11, 2020
db8a96e
Moved error for name and parent to synced_collection
vishav1771 Aug 12, 2020
064eafc
Removed pass
vishav1771 Aug 12, 2020
a61a5eb
Implemented deepcopy for MongodbCollection and RedisCollection
vishav1771 Aug 12, 2020
25b1139
Moved testdata to confest.py
vishav1771 Aug 12, 2020
3bea260
Moved backend test to specific file
vishav1771 Aug 12, 2020
4789b37
Linting errors
vishav1771 Aug 12, 2020
813fe90
Error correction
vishav1771 Aug 12, 2020
c9b659a
Added __deepcopy__ for zarr
vishav1771 Aug 12, 2020
a0f28aa
Changed name of MongoDict and MongoList to MongoDBDict and MongoDBList
vishav1771 Aug 13, 2020
c2e402e
Changed documentation and __deepcopy__ in zarr collection
vishav1771 Aug 13, 2020
d9b3304
Added _psuedo_deepcopy for redis and mongodb
vishav1771 Aug 14, 2020
c8b82cf
Changed name MongoX to MongoDBX in test_mongodb_collection
vishav1771 Aug 14, 2020
62c346a
Added docstring for _pseudo_deepcopy
vishav1771 Aug 16, 2020
49f2dff
Removed trailing space
vishav1771 Aug 16, 2020
663781c
Changes the docstring of _pseudo_deepcopy
vishav1771 Aug 17, 2020
36787ed
Changes the order of Parameters in documentation and minor corrections
vishav1771 Aug 18, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,5 @@ pytest-subtests==0.3.1
pydocstyle==5.0.2
pytest-cov==2.10.0
pymongo==3.10.1
zarr==2.4.0
redis==3.5.3
31 changes: 12 additions & 19 deletions signac/core/jsoncollection.py → signac/core/collection_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,10 @@ class JSONCollection(SyncedCollection):
backend = __name__ # type: ignore

def __init__(self, filename=None, write_concern=False, **kwargs):
self._filename = os.path.realpath(filename) if filename is not None else None
self._filename = None if filename is None else os.path.realpath(filename)
self._write_concern = write_concern
kwargs['name'] = filename
super().__init__(**kwargs)
if (filename is None) == (self._parent is None):
raise ValueError(
"Illegal argument combination, one of the two arguments, "
"parent or filename must be None, but not both.")

def _load(self):
"""Load the data from a JSON-file."""
Expand Down Expand Up @@ -96,22 +93,20 @@ class JSONDict(JSONCollection, SyncedAttrDict):
----------
filename: str, optional
The filename of the associated JSON file on disk (Default value = None).
write_concern: bool, optional
Ensure file consistency by writing changes back to a temporary file
first, before replacing the original file (Default value = None).
data: mapping, optional
The intial data pass to JSONDict. Defaults to `list()`
parent: object, optional
A parent instance of JSONDict or None (Default value = None).
write_concern: bool, optional
Ensure file consistency by writing changes back to a temporary file
first, before replacing the original file (Default value = None).
"""

pass


class JSONList(JSONCollection, SyncedList):
"""A non-string sequence interface to a persistent JSON file.

The JSONDict inherits from :class:`~core.collection_api.SyncedCollection`
The JSONList inherits from :class:`~core.collection_api.SyncedCollection`
and :class:`~core.syncedlist.SyncedList`.

.. code-block:: python
Expand All @@ -134,18 +129,16 @@ class JSONList(JSONCollection, SyncedList):

Parameters
----------
filename: str
filename: str, optional
The filename of the associated JSON file on disk (Default value = None).
data: non-str Sequence
The intial data pass to JSONDict
parent: object
A parent instance of JSONDict or None (Default value = None).
write_concern: bool
write_concern: bool, optional
Ensure file consistency by writing changes back to a temporary file
first, before replacing the original file (Default value = None).
data: non-str Sequence, optional
The intial data pass to JSONList
parent: object, optional
A parent instance of JSONList or None (Default value = None).
"""

pass


SyncedCollection.register(JSONDict, JSONList)
135 changes: 135 additions & 0 deletions signac/core/collection_mongodb.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
# Copyright (c) 2020 The Regents of the University of Michigan
# All rights reserved.
# This software is licensed under the BSD 3-Clause License.
"""Implements MongoDB-backend.

This implements the MongoDB-backend for SyncedCollection API by
implementing sync and load methods.
"""
from copy import deepcopy

from .synced_collection import SyncedCollection
from .syncedattrdict import SyncedAttrDict
from .synced_list import SyncedList


class MongoDBCollection(SyncedCollection):
"""Implement sync and load using a MongoDB backend."""

backend = __name__ # type: ignore

def __init__(self, collection=None, **kwargs):
import bson # for InvalidDocument

self._collection = collection
self._errors = bson.errors
self._key = type(self).__name__ + '::name'
super().__init__(**kwargs)

def _load(self):
"""Load the data from a MongoDB."""
blob = self._collection.find_one({self._key: self._name})
return blob['data'] if blob is not None else None

def _sync(self):
"""Write the data from MongoDB."""
data = self.to_base()
data_to_insert = {self._key: self._name, 'data': data}
try:
self._collection.replace_one({self._key: self._name}, data_to_insert, True)
except self._errors.InvalidDocument as err:
raise TypeError(str(err))

def _pseudo_deepcopy(self):
"""Return a copy of instance.

It is a psuedo implementation for `deepcopy` because
`pymongo.Collection` does not support `deepcopy` method.
"""
return type(self)(collection=self._collection, name=self._name, data=self.to_base(),
csadorf marked this conversation as resolved.
Show resolved Hide resolved
parent=deepcopy(self._parent))


class MongoDBDict(MongoDBCollection, SyncedAttrDict):
"""A dict-like mapping interface to a persistent Mongo-database.

The MongoDBDict inherits from :class:`~core.collection_api.MongoCollection`
and :class:`~core.syncedattrdict.SyncedAttrDict`.

.. code-block:: python

doc = MongoDBDict('data')
doc['foo'] = "bar"
assert doc.foo == doc['foo'] == "bar"
assert 'foo' in doc
del doc['foo']

.. code-block:: python

>>> doc['foo'] = dict(bar=True)
>>> doc
{'foo': {'bar': True}}
>>> doc.foo.bar = False
{'foo': {'bar': False}}

.. warning::

While the MongoDBDict object behaves like a dictionary, there are
important distinctions to remember. In particular, because operations
are reflected as changes to an underlying database, copying (even deep
copying) a MongoDBDict instance may exhibit unexpected behavior. If a
true copy is required, you should use the `to_base()` method to get a
dictionary representation, and if necessary construct a new MongoDBDict
instance: `new_dict = MongoDBDict(old_dict.to_base())`.

Parameters
----------
collection : object, optional
A pymongo.Collection instance.
data: mapping, optional
The intial data pass to MongoDBDict. Defaults to `dict()`.
name: str, optional
The name of the collection (Default value = None).
parent: object, optional
A parent instance of MongoDBDict (Default value = None).
"""


class MongoDBList(MongoDBCollection, SyncedList):
"""A non-string sequence interface to a persistent Mongo file.

The MongoDBList inherits from :class:`~core.synced_collection.SyncedCollection`
and :class:`~core.syncedlist.SyncedList`.

.. code-block:: python

synced_list = MongoDBList('data')
synced_list.append("bar")
assert synced_list[0] == "bar"
assert len(synced_list) == 1
del synced_list[0]

.. warning::

While the MongoDBList object behaves like a list, there are
important distinctions to remember. In particular, because operations
are reflected as changes to an underlying database, copying (even deep
copying) a MongoDBList instance may exhibit unexpected behavior. If a
true copy is required, you should use the `to_base()` method to get a
dictionary representation, and if necessary construct a new MongoDBList
instance: `new_list = MongoDBList(old_list.to_base())`.

Parameters
----------
collection : object, optional
A pymongo.Collection instance (Default value = None).
data: non-str Sequence, optional
The intial data pass to MongoDBList. Defaults to `list()`.
name: str, optional
The name of the collection (Default value = None).
parent: object, optional
A parent instance of MongoDBList (Default value = None).
"""


SyncedCollection.register(MongoDBDict, MongoDBList)
127 changes: 127 additions & 0 deletions signac/core/collection_redis.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
# Copyright (c) 2020 The Regents of the University of Michigan
# All rights reserved.
# This software is licensed under the BSD 3-Clause License.
"""Implements Redis-backend.

This implements the Redis-backend for SyncedCollection API by
implementing sync and load methods.
"""
import json
from copy import deepcopy

from .synced_collection import SyncedCollection
from .syncedattrdict import SyncedAttrDict
from .synced_list import SyncedList


class RedisCollection(SyncedCollection):
"""Implement sync and load using a Redis backend."""

backend = __name__ # type: ignore

def __init__(self, client=None, **kwargs):
self._client = client
super().__init__(**kwargs)

def _load(self):
"""Load the data from a Redis-database."""
blob = self._client.get(self._name)
return None if blob is None else json.loads(blob)

def _sync(self):
"""Write the data from Redis-database."""
self._client.set(self._name, json.dumps(self.to_base()).encode())

def _pseudo_deepcopy(self):
"""Return a copy of instance.

It is a psuedo implementation for `deepcopy` because
`redis.Redis` does not support `deepcopy` method.
"""
return type(self)(client=self._client, name=self._name, data=self.to_base(),
csadorf marked this conversation as resolved.
Show resolved Hide resolved
parent=deepcopy(self._parent))


class RedisDict(RedisCollection, SyncedAttrDict):
"""A dict-like mapping interface to a persistent Redis-database.

The RedisDict inherits from :class:`~core.rediscollection.RedisCollection`
and :class:`~core.syncedattrdict.SyncedAttrDict`.

.. code-block:: python

doc = RedisDict('data')
doc['foo'] = "bar"
assert doc.foo == doc['foo'] == "bar"
assert 'foo' in doc
del doc['foo']

.. code-block:: python

>>> doc['foo'] = dict(bar=True)
>>> doc
{'foo': {'bar': True}}
>>> doc.foo.bar = False
{'foo': {'bar': False}}

.. warning::

While the RedisDict object behaves like a dictionary, there are
important distinctions to remember. In particular, because operations
are reflected as changes to an underlying database, copying (even deep
copying) a RedisDict instance may exhibit unexpected behavior. If a
true copy is required, you should use the `to_base()` method to get a
dictionary representation, and if necessary construct a new RedisDict
instance: `new_dict = RedisDict(old_dict.to_base())`.

Parameters
----------
client: object, optional
A redis client (Default value = None).
data: mapping, optional
The intial data pass to RedisDict. Defaults to `dict()`
name: str, optional
The name of the collection (Default value = None).
parent: object, optional
A parent instance of RedisDict (Default value = None).
"""


class RedisList(RedisCollection, SyncedList):
"""A non-string sequence interface to a persistent Redis file.

The RedisList inherits from :class:`~core.collection_api.SyncedCollection`
and :class:`~core.syncedlist.SyncedList`.

.. code-block:: python

synced_list = RedisList('data')
synced_list.append("bar")
assert synced_list[0] == "bar"
assert len(synced_list) == 1
del synced_list[0]

.. warning::

While the RedisList object behaves like a list, there are
important distinctions to remember. In particular, because operations
are reflected as changes to an underlying database, copying (even deep
copying) a RedisList instance may exhibit unexpected behavior. If a
true copy is required, you should use the `to_base()` method to get a
dictionary representation, and if necessary construct a new RedisList
instance: `new_list = RedisList(old_list.to_base())`.

Parameters
----------
client: object, optional
A redis client (Default value = None).
data: non-str Sequence, optional
The intial data pass to RedisList. Defaults to `list()`
name: str, optional
The name of the collection (Default value = None).
parent: object, optional
A parent instance of RedisList (Default value = None).
"""


SyncedCollection.register(RedisDict, RedisList)
Loading