-
Notifications
You must be signed in to change notification settings - Fork 9
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
Move from Mongo/caching to datastore. #44
Conversation
Current coverage is 78.69% (diff: 80.36%)@@ master #44 diff @@
==========================================
Files 22 22
Lines 1980 1436 -544
Methods 0 0
Messages 0 0
Branches 0 0
==========================================
- Hits 1681 1130 -551
- Misses 299 306 +7
Partials 0 0
|
This PR is marked as a Work In Progress as the docs haven't been updated and there's no migration path from 1.3.7. This PR is not yet ready to land. |
QAThe first step is to make sure the example app works properly in its default configuration; that is, running against a file-based datastore. Note that this is the manual version of what happens when you run
Assuming you still have your virtualenv active...
Once those steps are finished, run through steps 2-9 above to re-test Switchboard against a Redis backend. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks really nice Kyle, I'll try to QA later.
|
||
# Setup a file-based datastore with pickle serialization. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the reason why these imports cannot be done at the beginning of the file? In the usual 3 import groups?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I originally had these imports up in their usual place, but when I started playing around with connecting to different types of datastores (Mongo, Redis, etc.) I found it handy to have the datastore-specific imports by the code that setup the datastore. Then I could snag the imports when I commented/uncommented a block.
# Setup a file-based datastore with pickle serialization. | ||
import datastore.filesystem | ||
import os | ||
base_path = os.path.dirname(os.path.realpath(__file__)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please leave a couple of new lines between the imports and the code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
flake8 doesn't actually complain about this... I wonder if it's because I'm assigning variables below the imports and not defining a function or class. I know flake8 complains in those cases. Regardless, if possible, I'd like to keep the imports with the code for easy experimentation with different datastores.
@@ -44,12 +47,16 @@ def assert_switch_inactive(browser, url=url): | |||
'Switch is not inactive') | |||
|
|||
|
|||
def drop_datastore(): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was Switch.c.drop() before, but I guess this is correct. Maybe add a docstring? [shrug]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, it was Switch.c.drop()
before because Pymongo's Collection class supports a drop command. Datastore (Switch.ds
) doesn't support a drop, so to make things easier I created a classmethod Model.drop()
. If you look throughout the diff, you'll see all instances of Switch.c.drop()
and Model.c.drop()
changed to Switch.drop()
and Model.drop()
.
@@ -19,7 +19,7 @@ | |||
packages=find_packages(exclude=['ez_setup']), | |||
include_package_data=True, | |||
install_requires=[ | |||
'pymongo >= 2.3, < 3', | |||
'datastore', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd add a >= constraint here, just to document the minimum supported version of datastore.
@@ -182,22 +189,6 @@ def test_add_edit_delete_switch(self): | |||
wait_time=10) | |||
assert_true(is_deleted, 'Switch was not deleted.') | |||
|
|||
def test_switch_history(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So I guess, as part of this simplification, you are removing a switch history feature?
Was that used/working before?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I removed it because it wasn't going to work as architected with Datastore; however, it is used, so I'm planning on re-adding support in a subsequent PR. It will likely be a VersionedDatastore
that can be optionally enabled.
|
||
NoValue = object() | ||
mydict = ModelDict(Model, value='foo') | ||
mydict['test'] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where does "test" come from? It's not clear from your example, as it states that a model that has a attribute named foo
where the value is "bar".
|
||
def __setitem__(self, key, value): | ||
if isinstance(value, self.model): | ||
nested_value = getattr(value, self.value) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can't this raise an AttributeError?
instance, created = self.model.get_or_create(key, defaults=defaults) | ||
|
||
# Ensure we're updating the value in the datastore if it changes. | ||
if getattr(instance, self.value) != nested_value: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again, why we can be sure that a filed named self.value is in instance? Please add comments.
def save(self): | ||
if hasattr(self, '_id'): | ||
previous = self.get(_id=self._id) | ||
if hasattr(self, 'key'): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or alternatively:
try:
key = self.key
except AttributeError:
key = self.key = str(uuid.uuid4())
previous = None
else:
previus = self.get(key)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice.
''' | ||
try: | ||
import datastore.redis | ||
return isinstance(cls.ds, datastore.redis.RedisDatastore) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please move the return at the end, so that only the code that we want to protect for that exception is in the try/except block.
I encountered the following error while running "make install".
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've taken a look through the code and made a few inline comments. Overall I think this is a good set of changes, and the datastore system should have enough flexibility for caching, etc (nice to get rid of all the internal caching logic!) I do have a few requests:
- can we have a ticket for adding versioning (or at least a simple audit log/list of changes)? It would be good to reference back to the commit that removed the versioning since some tests and UI support at least can be restored at that point.
- what about import/export of switches to facilitate migrating between datastores? (Also would be handy to allow copying switches between environments). Export functionality would probably be best in a 1.3.x branch release too so that we can migrate data forward. A separate ticket is fine for that, but will be a blocker for SourceForge to update. Or I guess a datastore implementation that saves mongo data in the same format as before, but offhand I'd guess that's not trivial.
|
||
def __repr__(self): # pragma: nocover | ||
return "<%s>" % (self.__class__.__name__) | ||
|
||
def iteritems(self): # pragma: nocover | ||
self._populate() | ||
return self._cache.iteritems() | ||
return zip(self.iterkeys(), self.itervalues()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This isn't very efficient, since iterkeys()
and itervalues()
will each end up calling self._model.all()
. Pratically though, I'm not sure if this method is really used anywhere that would matter.
return result | ||
|
||
@classmethod | ||
def all(cls): | ||
return [cls(**s) for s in cls.c.find()] | ||
if cls._is_redis_datastore(): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Checking specifically for redis here isn't ideal. It makes me think other datastores could suffer from the same limitations (lack of querying). Can we feature-detect query support rather than redis specifically?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good call. I'll switch to a try/except on NotImplemented or something similar.
QA: Redis: |
re: exclude conditions - in my experience you can't have just an exclude condition. You need to have at least one condition match and none of the exclude conditions match. (We often have a ".*" positive match to go with the NOT "whatever"). So I agree it's pre-existing and a separate issue for making that work more intuitively would be nice. |
@brondsem and @frankban: I've created issues #45, #46, and #47 to cover various problems discussed in this PR. At the very least, I'd see #45 and #46 being addressed quickly, before a 2.0 release. That said, I'd like to do that work separate from this PR, in part because this is a pretty significant hunk of work. |
Things look good here to me |
Fixes switchboardpy#16 The datastore library (https://github.com/datastore/datastore) lets Switchboard be much more flexible about how the backend is arranged. Now Switchboard can support file-based storage for easy local development and much more complex, cached, sharded, multiple-layered datastores for production environments.
Thanks all! Going to ship this and move on to smoothing the rough edges and prepping for a 2.0 release. |
Fixes #16
The datastore library (https://github.com/datastore/datastore) lets
Switchboard be much more flexible about how the backend is arranged. Now
Switchboard can support file-based storage for easy local development
and much more complex, cached, sharded, multiple-layered datastores for
production environments.