{"action":"created","issue":{"url":"https://api.github.com/repos/google/oauth2client/issues/319","labels_url":"https://api.github.com/repos/google/oauth2client/issues/319/labels{/name}","comments_url":"https://api.github.com/repos/google/oauth2client/issues/319/comments","events_url":"https://api.github.com/repos/google/oauth2client/issues/319/events","html_url":"https://github.com/google/oauth2client/issues/319","id":110707938,"number":319,"title":"[FR] Useful storage for web applications.","user":{"login":"jonparrott","id":250995,"avatar_url":"https://avatars.githubusercontent.com/u/250995?v=3","gravatar_id":"","url":"https://api.github.com/users/jonparrott","html_url":"https://github.com/jonparrott","followers_url":"https://api.github.com/users/jonparrott/followers","following_url":"https://api.github.com/users/jonparrott/following{/other_user}","gists_url":"https://api.github.com/users/jonparrott/gists{/gist_id}","starred_url":"https://api.github.com/users/jonparrott/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jonparrott/subscriptions","organizations_url":"https://api.github.com/users/jonparrott/orgs","repos_url":"https://api.github.com/users/jonparrott/repos","events_url":"https://api.github.com/users/jonparrott/events{/privacy}","received_events_url":"https://api.github.com/users/jonparrott/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":4,"created_at":"2015-10-09T17:55:38Z","updated_at":"2015-10-13T17:43:12Z","closed_at":null,"body":"Currently, we have the following storage classes:\r\n\r\n* ``appengine.StorageByKeyName``, which is of course particular to GAE and Datastore.\r\n* ``django_orm.Storage``, which appears to be a port of ``StorageByKeyName`` to Django's ORM.\r\n* ``flask_util.FlaskSessionStorage``.\r\n* ``file.Storage``, ``keyring_storage.Storage``, and ``multistore_file.Storage`` which are not useful in a web application context.\r\n\r\nWe really don't have a good set of general-purpose storage classes for web applications. I propose a set of new storage classes to make usage of the ``flask_util`` and incoming``django_util`` and ``bottle_util`` easier. Myself and @waprin are willing to implement these, we just want to make sure we're on the right track and that these will be welcome additions.\r\n\r\n/cc @dhermes @anthmgoogle\r\n\r\n### `storage.DictionaryStorage`\r\n\r\nSimilar to `StorageByKeyName`, but will write to any object with a dictionary-like interface. This will be used to implement storage for Flask, Django, and other web frameworks' ``session`` object easily.\r\n\r\n```python\r\nstorage = DictionaryStorage(flask.session, key='google_oauth2_credentials') \r\n```\r\n\r\n### `storage.KeyValueStorage`\r\n\r\nAgain, similar to `StorageByKeyName`, but will write to any object with ``get(key)``, ``set(key, value)`` and, ``delete(key)`` methods. This will be used to provide storage for redis, memcache, and other kv-stores.\r\n\r\n```python\r\nr = redis.StrictRedis()\r\nkey = \"google_oauth2_credentials_{}\".format(flask.session['user_id'])\r\nstorage = KeyValueStorage(r, key=key)\r\n```\r\n\r\n### App Engine changes\r\n\r\nWe'll create two new storage classes ``NdbStorage`` and ``DbStorage`` that will default the model to the existing ``CredentialsNDBModel`` and ``CredentialsModel``\r\n\r\nFinally, `OAuth2Decorator` will be modified to accept a `storage` argument and only use the existing `_storage_class`, `_credentials_class `, and `_credentials_property_name` for backwards-compatibility.\r\n\r\n### `storage.CloudDatastoreStorage`\r\n\r\nUses ``gcloud-python`` to store the credentials, similar usage to ``KeyValueStorage``.\r\n\r\n```python\r\nds = gcloud.datastore.Client()\r\nkey = flask.session['user_id']\r\nstorage = CloudDatastoreStorage(ds, key=key)\r\n```\r\n\r\n### `storage.SqlAlchemyStorage`\r\n\r\nUses sqlalchemy to store the credentials. I haven't given this much thought, but it'll likely be similar to the Django ORM storage.\r\n\r\n## Implementation / usage suggestions\r\n\r\nFor all classes that accept ``key`` argument, it should be possible to specify a string or zero-argument callable. This will allow user-defined functions that can ascertain the key:\r\n\r\n```python\r\ndef get_key_from_user():\r\n    return users.get_current_user().user_id\r\n\r\nstorage = KeyValueStorage(google.appengine.api.memcache, key=key)\r\n```\r\n\r\nThe `*_util` helper classes should accept a ``Storage`` instance or a callable that returns a storage class.  The helper class will pass in the relevant context for the given framework (such as the request). This provides even greater control over the storage:\r\n\r\n```python\r\ndef get_storage_for_gitkit_user(request):\r\n   gitkit_user = gitkit_instance.VerifyGitkitToken(request.cookies['gtoken'])\r\n   key = 'google-oauth2-credentials-{}'.format(gitkit_user.email)\r\n   return KeyValueStorage(redis_instance, key=key)\r\n\r\n\r\noauth2 = flask_util.UserOAuth2(app, storage=get_storage_for_gitkit_user)\r\n```"},"comment":{"url":"https://api.github.com/repos/google/oauth2client/issues/comments/147789735","html_url":"https://github.com/google/oauth2client/issues/319#issuecomment-147789735","issue_url":"https://api.github.com/repos/google/oauth2client/issues/319","id":147789735,"user":{"login":"jonparrott","id":250995,"avatar_url":"https://avatars.githubusercontent.com/u/250995?v=3","gravatar_id":"","url":"https://api.github.com/users/jonparrott","html_url":"https://github.com/jonparrott","followers_url":"https://api.github.com/users/jonparrott/followers","following_url":"https://api.github.com/users/jonparrott/following{/other_user}","gists_url":"https://api.github.com/users/jonparrott/gists{/gist_id}","starred_url":"https://api.github.com/users/jonparrott/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jonparrott/subscriptions","organizations_url":"https://api.github.com/users/jonparrott/orgs","repos_url":"https://api.github.com/users/jonparrott/repos","events_url":"https://api.github.com/users/jonparrott/events{/privacy}","received_events_url":"https://api.github.com/users/jonparrott/received_events","type":"User","site_admin":false},"created_at":"2015-10-13T17:43:12Z","updated_at":"2015-10-13T17:43:12Z","body":"Alrighty, so what sort of plan would be acceptable to you at this point? We can take it in small, incremental pieces, starting with:\r\n\r\n*  A PR for `DictionaryStorage`, with optional locking, and updating `flask_util` to use that instead of its own storage.\r\n* A PR for `KeyValueStorage`.\r\n* A PR to perform the noted changes to `appengine`.\r\n\r\nWe can hold off on the other stuff (`CloudDatastoreStorage`, `SqlAlchemyStorage`) until we figure out how/if to handle plugins. If we decide to handle plugins, we can move `appengine`, `flask_util`, `django_orm`, `django_util`, and `bottle_util` into plugins.\r\n\r\n(My recommendation for 'plugins' is to maybe have a `contrib` module like `urllib3`)"}}