Add SQLAlchemy storage
This commit is contained in:
@@ -22,6 +22,7 @@ Submodules
|
||||
oauth2client.contrib.keyring_storage
|
||||
oauth2client.contrib.locked_file
|
||||
oauth2client.contrib.multistore_file
|
||||
oauth2client.contrib.sqlalchemy
|
||||
oauth2client.contrib.xsrfutil
|
||||
|
||||
Module contents
|
||||
|
||||
7
docs/source/oauth2client.contrib.sqlalchemy.rst
Normal file
7
docs/source/oauth2client.contrib.sqlalchemy.rst
Normal file
@@ -0,0 +1,7 @@
|
||||
oauth2client.contrib.sqlalchemy module
|
||||
======================================
|
||||
|
||||
.. automodule:: oauth2client.contrib.sqlalchemy
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
170
oauth2client/contrib/sqlalchemy.py
Normal file
170
oauth2client/contrib/sqlalchemy.py
Normal file
@@ -0,0 +1,170 @@
|
||||
# Copyright 2016 Google Inc. All rights reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""OAuth 2.0 utilities for SQLAlchemy.
|
||||
|
||||
Utilities for using OAuth 2.0 in conjunction with a SQLAlchemy.
|
||||
|
||||
Configuration
|
||||
=============
|
||||
|
||||
In order to use this storage, you'll need to create table
|
||||
with :class:`oauth2client.contrib.sql_alchemy.CredentialsType` column.
|
||||
It's recommended to either put this column on some sort of user info
|
||||
table or put the column in a table with a belongs-to relationship to
|
||||
a user info table.
|
||||
|
||||
Here's an example of a simple table with a :class:`CredentialsType`
|
||||
column that's related to a user table by the `user_id` key.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from oauth2client.contrib.sql_alchemy import CredentialsType
|
||||
from sqlalchemy import Column, ForeignKey, Integer
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
|
||||
class Credentials(Base):
|
||||
__tablename__ = 'credentials'
|
||||
|
||||
user_id = Column(Integer, ForeignKey('user.id'))
|
||||
credentials = Column(CredentialsType)
|
||||
|
||||
|
||||
class User(Base):
|
||||
id = Column(Integer, primary_key=True)
|
||||
# bunch of other columns
|
||||
credentials = relationship('Credentials')
|
||||
|
||||
|
||||
Usage
|
||||
=====
|
||||
|
||||
With tables ready, you are now able to store credentials in database.
|
||||
We will reuse tables defined above.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from oauth2client.client import OAuth2Credentials
|
||||
from oauth2client.contrib.sql_alchemy import Storage
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
session = Session()
|
||||
user = session.query(User).first()
|
||||
storage = Storage(
|
||||
session=session,
|
||||
model_class=Credentials,
|
||||
# This is the key column used to identify
|
||||
# the row that stores the credentials.
|
||||
key_name='user_id',
|
||||
key_value=user.id,
|
||||
property_name='credentials',
|
||||
)
|
||||
|
||||
# Store
|
||||
credentials = OAuth2Credentials(...)
|
||||
storage.put(credentials)
|
||||
|
||||
# Retrieve
|
||||
credentials = storage.get()
|
||||
|
||||
# Delete
|
||||
storage.delete()
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import oauth2client.client
|
||||
import sqlalchemy.types
|
||||
|
||||
|
||||
class CredentialsType(sqlalchemy.types.PickleType):
|
||||
"""Type representing credentials.
|
||||
|
||||
Alias for :class:`sqlalchemy.types.PickleType`.
|
||||
"""
|
||||
|
||||
|
||||
class Storage(oauth2client.client.Storage):
|
||||
"""Store and retrieve a single credential to and from SQLAlchemy.
|
||||
This helper presumes the Credentials
|
||||
have been stored as a Credentials column
|
||||
on a db model class.
|
||||
"""
|
||||
|
||||
def __init__(self, session, model_class, key_name,
|
||||
key_value, property_name):
|
||||
"""Constructor for Storage.
|
||||
|
||||
Args:
|
||||
session: An instance of :class:`sqlalchemy.orm.Session`.
|
||||
model_class: SQLAlchemy declarative mapping.
|
||||
key_name: string, key name for the entity that has the credentials
|
||||
key_value: key value for the entity that has the credentials
|
||||
property_name: A string indicating which property on the
|
||||
``model_class`` to store the credentials.
|
||||
This property must be a
|
||||
:class:`CredentialsType` column.
|
||||
"""
|
||||
super(Storage, self).__init__()
|
||||
|
||||
self.session = session
|
||||
self.model_class = model_class
|
||||
self.key_name = key_name
|
||||
self.key_value = key_value
|
||||
self.property_name = property_name
|
||||
|
||||
def locked_get(self):
|
||||
"""Retrieve stored credential.
|
||||
|
||||
Returns:
|
||||
A :class:`oauth2client.Credentials` instance or `None`.
|
||||
"""
|
||||
filters = {self.key_name: self.key_value}
|
||||
query = self.session.query(self.model_class).filter_by(**filters)
|
||||
entity = query.first()
|
||||
|
||||
if entity:
|
||||
credential = getattr(entity, self.property_name)
|
||||
if credential and hasattr(credential, 'set_store'):
|
||||
credential.set_store(self)
|
||||
return credential
|
||||
else:
|
||||
return None
|
||||
|
||||
def locked_put(self, credentials):
|
||||
"""Write a credentials to the SQLAlchemy datastore.
|
||||
|
||||
Args:
|
||||
credentials: :class:`oauth2client.Credentials`
|
||||
"""
|
||||
filters = {self.key_name: self.key_value}
|
||||
query = self.session.query(self.model_class).filter_by(**filters)
|
||||
entity = query.first()
|
||||
|
||||
if not entity:
|
||||
entity = self.model_class(**filters)
|
||||
|
||||
setattr(entity, self.property_name, credentials)
|
||||
self.session.add(entity)
|
||||
|
||||
def locked_delete(self):
|
||||
"""Delete credentials from the SQLAlchemy datastore."""
|
||||
filters = {self.key_name: self.key_value}
|
||||
self.session.query(self.model_class).filter_by(**filters).delete()
|
||||
118
tests/contrib/test_sqlalchemy.py
Normal file
118
tests/contrib/test_sqlalchemy.py
Normal file
@@ -0,0 +1,118 @@
|
||||
# Copyright 2016 Google Inc. All rights reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import datetime
|
||||
|
||||
import oauth2client
|
||||
import oauth2client.client
|
||||
import oauth2client.contrib.sqlalchemy
|
||||
import sqlalchemy
|
||||
import sqlalchemy.ext.declarative
|
||||
import sqlalchemy.orm
|
||||
import unittest2
|
||||
|
||||
Base = sqlalchemy.ext.declarative.declarative_base()
|
||||
|
||||
|
||||
class DummyModel(Base):
|
||||
__tablename__ = 'dummy'
|
||||
|
||||
id = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True)
|
||||
# we will query against this, because of ROWID
|
||||
key = sqlalchemy.Column(sqlalchemy.Integer)
|
||||
credentials = sqlalchemy.Column(
|
||||
oauth2client.contrib.sqlalchemy.CredentialsType)
|
||||
|
||||
|
||||
class TestSQLAlchemyStorage(unittest2.TestCase):
|
||||
def setUp(self):
|
||||
engine = sqlalchemy.create_engine('sqlite://')
|
||||
Base.metadata.create_all(engine)
|
||||
|
||||
self.session = sqlalchemy.orm.sessionmaker(bind=engine)
|
||||
self.credentials = oauth2client.client.OAuth2Credentials(
|
||||
access_token='token',
|
||||
client_id='client_id',
|
||||
client_secret='client_secret',
|
||||
refresh_token='refresh_token',
|
||||
token_expiry=datetime.datetime.utcnow(),
|
||||
token_uri=oauth2client.GOOGLE_TOKEN_URI,
|
||||
user_agent='DummyAgent',
|
||||
)
|
||||
|
||||
def tearDown(self):
|
||||
session = self.session()
|
||||
session.query(DummyModel).filter_by(key=1).delete()
|
||||
session.commit()
|
||||
|
||||
def compare_credentials(self, result):
|
||||
self.assertEqual(result.access_token, self.credentials.access_token)
|
||||
self.assertEqual(result.client_id, self.credentials.client_id)
|
||||
self.assertEqual(result.client_secret, self.credentials.client_secret)
|
||||
self.assertEqual(result.refresh_token, self.credentials.refresh_token)
|
||||
self.assertEqual(result.token_expiry, self.credentials.token_expiry)
|
||||
self.assertEqual(result.token_uri, self.credentials.token_uri)
|
||||
self.assertEqual(result.user_agent, self.credentials.user_agent)
|
||||
|
||||
def test_get(self):
|
||||
session = self.session()
|
||||
session.add(DummyModel(
|
||||
key=1,
|
||||
credentials=self.credentials,
|
||||
))
|
||||
session.commit()
|
||||
|
||||
credentials = oauth2client.contrib.sqlalchemy.Storage(
|
||||
session=session,
|
||||
model_class=DummyModel,
|
||||
key_name='key',
|
||||
key_value=1,
|
||||
property_name='credentials',
|
||||
).get()
|
||||
|
||||
self.compare_credentials(credentials)
|
||||
|
||||
def test_put(self):
|
||||
session = self.session()
|
||||
oauth2client.contrib.sqlalchemy.Storage(
|
||||
session=session,
|
||||
model_class=DummyModel,
|
||||
key_name='key',
|
||||
key_value=1,
|
||||
property_name='credentials',
|
||||
).put(self.credentials)
|
||||
session.commit()
|
||||
|
||||
entity = session.query(DummyModel).filter_by(key=1).first()
|
||||
self.compare_credentials(entity.credentials)
|
||||
|
||||
def test_delete(self):
|
||||
session = self.session()
|
||||
session.add(DummyModel(
|
||||
key=1,
|
||||
credentials=self.credentials,
|
||||
))
|
||||
session.commit()
|
||||
|
||||
query = session.query(DummyModel).filter_by(key=1)
|
||||
self.assertIsNotNone(query.first())
|
||||
oauth2client.contrib.sqlalchemy.Storage(
|
||||
session=session,
|
||||
model_class=DummyModel,
|
||||
key_name='key',
|
||||
key_value=1,
|
||||
property_name='credentials',
|
||||
).delete()
|
||||
session.commit()
|
||||
self.assertIsNone(query.first())
|
||||
Reference in New Issue
Block a user