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

add KVStoreClient, examples #28

Merged
merged 15 commits into from
Nov 13, 2019
Merged

add KVStoreClient, examples #28

merged 15 commits into from
Nov 13, 2019

Conversation

mmou
Copy link
Contributor

@mmou mmou commented Oct 27, 2019

can't release until keybase/client is released with kv store feature

@AMarcedone take a look at 4_secret_storage.py; i implemented the solution we discussed

@@ -27,6 +27,6 @@ repos:
name: mypy
stages: [commit]
language: system
entry: poetry run mypy **/*.py
entry: poetry run mypy pykeybasebot/
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was getting "No directory found" for **/*.py. Also I think this should match with what is in the Makefile for make test

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we could do poetry run mypy pykeybasebot examples tests (though i think i was getting errors with the tests dir)

examples/6_totp.py Outdated Show resolved Hide resolved
@mmou mmou requested a review from xgess October 27, 2019 21:26
examples/6_totp.py Outdated Show resolved Hide resolved
examples/6_totp.py Outdated Show resolved Hide resolved
examples/6_totp.py Outdated Show resolved Hide resolved
examples/6_totp.py Outdated Show resolved Hide resolved
examples/6_totp.py Outdated Show resolved Hide resolved
examples/6_totp.py Outdated Show resolved Hide resolved
examples/6_totp.py Outdated Show resolved Hide resolved
examples/6_totp.py Outdated Show resolved Hide resolved

logging.basicConfig(level=logging.DEBUG)

if "win32" in sys.platform:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ugh. we need to figure out how to pull this down into the framework. not for this PR. just saying it so it's not only in my head. these examples should have as little boilerplate as possible.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

made this #29

examples/6_totp.py Outdated Show resolved Hide resolved
@mmou mmou changed the title add KVStoreClient, 6_totp example. add KVStoreClient, examples Oct 28, 2019
examples/4_totp_storage.py Outdated Show resolved Hide resolved
examples/4_totp_storage.py Outdated Show resolved Hide resolved
examples/4_totp_storage.py Outdated Show resolved Hide resolved
examples/4_totp_storage.py Outdated Show resolved Hide resolved
examples/4_totp_storage.py Outdated Show resolved Hide resolved
examples/4_totp_storage.py Outdated Show resolved Hide resolved
@mmou mmou requested a review from AMarcedone November 4, 2019 22:28
examples/5_secret_storage.py Outdated Show resolved Hide resolved
examples/5_secret_storage.py Outdated Show resolved Hide resolved
examples/5_secret_storage.py Outdated Show resolved Hide resolved
examples/5_secret_storage.py Outdated Show resolved Hide resolved
cached = self.cache[entry_key].copy() if entry_key in self.cache else None
if cached is not None:
cached["info"] = (
cached["info"].copy() if cached["info"] is not None else None
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we do a deepcopy of the self.cache[entry_key] instead?

#!/usr/bin/env python3

###################################
# WHAT IS IN THIS EXAMPLE?
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i really like the way this example came out :)

"""

def __init__(self, *args, **kwargs):
self.secret_kvstore_client = SecretKeyKVStoreClient(self)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is never referenced from the CachedBot (which is good, because it would be an implicit violation of the law of demeter). i'd suggest make this constraint explicit...

def __init__(self, *args, **kwargs):
    secret_client = SecretKeyKVStoreClient(self)
    self._cached_secret_kvstore = CachedKVStoreClient(self, secret_client)
    super().__init__(*args, **kwargs)

@property
def cached_secret_kvstore(self):
    return self._cached_secret_kvstore

if you're feeling bold, you could also make it like this...

@property
def kvstore(self):
    return self._cached_secret_kvstore

which might simplify some of the other objects higher up in the stack: i.e. your RentalClient just needs the bot and it can interact with it like it would a normal bot, this one has just been juiced up.

given the way this bot object has developed, i'd also consider renaming it from CachedBot to something a little more telling. like maybe CustomBot or JuicedUpBot or PrivateConcurrentBot? dunno. CachedBot is a bit misleading.

Copy link
Collaborator

@xgess xgess Nov 12, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

after later comments, adding another suggestion...

class WhateverYouChooseBot(Bot):
    @property
    def kvstore(self):
        if not self._cached_secret_kvstore
            basic_client = KVStoreClient(self)
            secret_client = SecretKeyKVStoreClient(basic_client)
            self._cached_secret_kvstore = CachedKVStoreClient(secret_client)
        return self._cached_secret_kvstore

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok i renamed the bot class to CustomKVStoreBot, and this bot has a kvstore property that returns a CachedKVStoreClient.
then i renamed RentalClient to RentalBotClient. and i made RentalBotClient take in a Bot.

examples/5_secret_storage.py Outdated Show resolved Hide resolved
examples/5_secret_storage.py Outdated Show resolved Hide resolved
except RevisionError:
# refresh cached value
curr_info = await self.get(team, namespace, entry_key)
return curr_info # failed put. return KVGetResult.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure i love this pattern. a PUT could return the PUT result OR a GET result?
not a blocker. i just think this is a little bit of a weird API.

examples/5_secret_storage.py Outdated Show resolved Hide resolved
self.secrets[team][namespace] = secret
return self.secrets[team][namespace]

async def hmac_key(self, team, namespace, entry_key) -> str:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NAB: it's probably a better practice to call this method something like hidden_key. it's only incidental that it happens to do an hmac to achieve those ends.

return b64decode(x.encode("utf-8"))


class SecretKeyKVStoreClient:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i really like the encapsulation of responsibility that this object now has. 👍


NAMESPACE = "rental"

def __init__(self, kvstore: CachedKVStoreClient):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i don't know much about duck typing with python's type system, but this would be a case for it. it looks to me like this could take a CachedKVStoreClient or a SecretClient or a regular ol' client.

oh actually, what i suggested before was to pass in a Bot to this thing. yeah. in which case the ducktyping would be any object that has a kvstore property.

this isn't important.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ahh. i see this is calling additional methods on the kvstore that don't exist on the basic one. ok. nevermind.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok - i think i'd argue there's not really a compelling reason to expose get_cache to the outside world. the only reason something outside of the CachedKVStoreClient would want to call it is for performance. and that's not really the point of this thing, it's for concurrency. plus it adds branching logic in this client that's just not really necessary. so yeah. i think i'd argue:

  1. the cached kvstore client shouldn't expose get_cached to the outside world
  2. the rental client should always just call get (and now it never needs to worry about getting outdated data)
  3. the rental client can be instantiated with anything kvstore-like. even if that's not a duck-typing exercise worth doing

expected_revision = 1
cached = self.kvstore.get_cached(tool)
if cached is not None:
if cached["info"]:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah - it's sub-optimal that the RentalClient knows about how the CachedClient arranges its internal data structures

Copy link
Collaborator

@xgess xgess left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looking great. just a couple small change requests. mostly renames and typos.



class CachedKVStoreClient:
class TryingKVStoreClient:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oooo i like this name

res = await self.lookup(team, tool)
info = (
json.loads(res.entry_value) if res.entry_value != "" else {}
) # if tool already exists, propagate existing info
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

interesting choice. no opinions. :)

res = await self.kvstore.put(
team, self.NAMESPACE, tool, info, expected_revision
)
return res
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NAB: maybe here we should return nil because (1) you're good to go (and it's generally good practice to separate command from query methods), and (2) to distinguish it from an unsuccessful write

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or True/False for whether or not the reservation was accepted? (i.e. false if it's not accepted for an expected reason (i.e. someone else has that date))

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

decided to change return type to Tuple[bool, Union[keybase1.KVGetResult, None]]

if (day not in info) or (day in info and info[day] != user):
# failed to put because currently not reserved, or current reserver is not user
return res
expected_revision = res.revision + 1
res = await self.kvstore.put(
team, self.NAMESPACE, tool, info, expected_revision
)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

github won't let me comment on the line below, but same thing

Copy link
Collaborator

@xgess xgess left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

great work! i think this is going to be really useful to people <3

@mmou mmou merged commit 8a6884c into master Nov 13, 2019
@mmou mmou deleted the mmou-kvstore branch November 13, 2019 02:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants