This repository has been archived by the owner on Feb 8, 2018. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 308
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Factor out an AccountElsewhere class; #406
- Loading branch information
1 parent
a6f58c6
commit db5bb15
Showing
10 changed files
with
206 additions
and
190 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,97 +1,145 @@ | ||
from aspen import log | ||
import gittip | ||
from aspen import Response | ||
from aspen.utils import typecheck | ||
from gittip import db | ||
from gittip.authentication import User | ||
from gittip.participant import reserve_a_random_participant_id | ||
from psycopg2 import IntegrityError | ||
|
||
|
||
def _resolve(platform, username_key, username): | ||
"""Given three unicodes, return a participant_id. | ||
""" | ||
typecheck(platform, unicode, username_key, unicode, username, unicode) | ||
rec = gittip.db.fetchone(""" | ||
SELECT participant_id | ||
FROM elsewhere | ||
WHERE platform=%s | ||
AND user_info->%s = %s | ||
""", (platform, username_key, username,)) | ||
# XXX Do we want a uniqueness constraint on $username_key? Can we do that? | ||
|
||
if rec is None: | ||
raise Exception( "User %s on %s isn't known to us." | ||
% (username, platform) | ||
) | ||
return rec['participant_id'] | ||
|
||
|
||
class AccountElsewhere(object): | ||
pass | ||
|
||
platform = None # set in subclass | ||
|
||
def upsert(platform, user_id, username, user_info): | ||
"""Given str, unicode, unicode, and dict, return unicode and boolean. | ||
def __init__(self, user_id): | ||
typecheck(user_id, (int, unicode)) | ||
self.user_id = unicode(user_id) | ||
|
||
Platform is the name of a platform that we support (ASCII blah). User_id is | ||
an immutable unique identifier for the given user on the given platform. | ||
Username is the user's login/username on the given platform. It is only | ||
used here for logging. Specifically, we don't reserve their username for | ||
them on Gittip if they're new here. We give them a random participant_id | ||
here, and they'll have a chance to change it if/when they opt in. User_id | ||
and username may or may not be the same. User_info is a dictionary of | ||
profile info per the named platform. All platform dicts must have an id key | ||
that corresponds to the primary key in the underlying table in our own db. | ||
|
||
The return value is a tuple: (participant_id [unicode], is_claimed | ||
[boolean], is_locked [boolean], balance [Decimal]). | ||
def set_is_locked(self, is_locked): | ||
gittip.db.execute(""" | ||
""" | ||
typecheck( platform, str | ||
, user_id, (int, unicode) | ||
, username, unicode | ||
, user_info, dict | ||
) | ||
user_id = unicode(user_id) | ||
UPDATE elsewhere | ||
SET is_locked=%s | ||
WHERE platform=%s AND user_id=%s | ||
""", (is_locked, self.platform, self.user_id)) | ||
|
||
# Insert the account if needed. | ||
# ============================= | ||
# Do this with a transaction so that if the insert fails, the | ||
# participant we reserved for them is rolled back as well. | ||
|
||
try: | ||
with db.get_transaction() as txn: | ||
participant_id = reserve_a_random_participant_id(txn) | ||
INSERT = """\ | ||
def opt_in(self, participant_id, user_info): | ||
"""Given a desired participant_id, return a User object. | ||
""" | ||
self.set_is_locked(False) | ||
participant_id, is_claimed, is_locked, balance = self.upsert(user_info) | ||
user = User.from_id(participant_id) # give them a session | ||
if not is_claimed: | ||
user.set_as_claimed() | ||
try: | ||
user.change_id(participant_id) | ||
except (Response, IntegrityError): | ||
pass | ||
return user | ||
|
||
INSERT INTO elsewhere | ||
(platform, user_id, participant_id) | ||
VALUES (%s, %s, %s) | ||
|
||
""" | ||
txn.execute(INSERT, (platform, user_id, participant_id)) | ||
except IntegrityError: | ||
pass | ||
def upsert(self, user_info): | ||
"""Given a dict, return a tuple. | ||
Platform is the name of a platform that we support (ASCII blah). | ||
User_id is an immutable unique identifier for the given user on the | ||
given platform. Username is the user's login/username on the given | ||
platform. It is only used here for logging. Specifically, we don't | ||
reserve their username for them on Gittip if they're new here. We give | ||
them a random participant_id here, and they'll have a chance to change | ||
it if/when they opt in. User_id and username may or may not be the | ||
same. User_info is a dictionary of profile info per the named platform. | ||
All platform dicts must have an id key that corresponds to the primary | ||
key in the underlying table in our own db. | ||
# Update their user_info. | ||
# ======================= | ||
# Cast everything to unicode, because (I believe) hstore can take any type | ||
# of value, but psycopg2 can't. | ||
# | ||
# https://postgres.heroku.com/blog/past/2012/3/14/introducing_keyvalue_data_storage_in_heroku_postgres/ | ||
# http://initd.org/psycopg/docs/extras.html#hstore-data-type | ||
The return value is a tuple: (participant_id [unicode], is_claimed | ||
[boolean], is_locked [boolean], balance [Decimal]). | ||
for k, v in user_info.items(): | ||
user_info[k] = unicode(v) | ||
""" | ||
typecheck(user_info, dict) | ||
|
||
UPDATE = """\ | ||
|
||
UPDATE elsewhere | ||
SET user_info=%s | ||
WHERE platform=%s AND user_id=%s | ||
RETURNING participant_id | ||
# Insert the account if needed. | ||
# ============================= | ||
# Do this with a transaction so that if the insert fails, the | ||
# participant we reserved for them is rolled back as well. | ||
|
||
""" | ||
try: | ||
with gittip.db.get_transaction() as txn: | ||
_participant_id = reserve_a_random_participant_id(txn) | ||
txn.execute( "INSERT INTO elsewhere " | ||
"(platform, user_id, participant_id) " | ||
"VALUES (%s, %s, %s)" | ||
, (self.platform, self.user_id, _participant_id) | ||
) | ||
except IntegrityError: | ||
pass | ||
|
||
|
||
# Update their user_info. | ||
# ======================= | ||
# Cast everything to unicode, because (I believe) hstore can take any | ||
# type of value, but psycopg2 can't. | ||
# | ||
# https://postgres.heroku.com/blog/past/2012/3/14/introducing_keyvalue_data_storage_in_heroku_postgres/ | ||
# http://initd.org/psycopg/docs/extras.html#hstore-data-type | ||
|
||
for k, v in user_info.items(): | ||
user_info[k] = unicode(v) | ||
|
||
|
||
participant_id = gittip.db.fetchone(""" | ||
UPDATE elsewhere | ||
SET user_info=%s | ||
WHERE platform=%s AND user_id=%s | ||
RETURNING participant_id | ||
""", (user_info, self.platform, self.user_id))['participant_id'] | ||
|
||
|
||
# Get a little more info to return. | ||
# ================================= | ||
|
||
rec = db.fetchone(UPDATE, (user_info, platform, user_id)) | ||
participant_id = rec['participant_id'] | ||
rec = gittip.db.fetchone(""" | ||
SELECT claimed_time, balance, is_locked | ||
FROM participants | ||
JOIN elsewhere | ||
ON participants.id=participant_id | ||
WHERE platform=%s | ||
AND participants.id=%s | ||
# Get a little more info to return. | ||
# ================================= | ||
""", (self.platform, participant_id)) | ||
|
||
rec = db.fetchone( "SELECT claimed_time, balance, is_locked " | ||
"FROM participants " | ||
"JOIN elsewhere ON participants.id = participant_id " | ||
"WHERE participants.id=%s" | ||
, (participant_id,) | ||
) | ||
assert rec is not None # sanity check | ||
assert rec is not None # sanity check | ||
|
||
|
||
return ( participant_id | ||
, rec['claimed_time'] is not None | ||
, rec['is_locked'] | ||
, rec['balance'] | ||
) | ||
return ( participant_id | ||
, rec['claimed_time'] is not None | ||
, rec['is_locked'] | ||
, rec['balance'] | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
from gittip.testing import tip_graph | ||
from gittip.elsewhere import github, twitter | ||
|
||
|
||
def test_github_resolve_resolves(): | ||
with tip_graph(('alice', 'bob', 1)): | ||
expected = 'alice' | ||
actual = github.resolve(u'alice') | ||
assert actual == expected, actual | ||
|
||
|
||
def test_twitter_resolve_resolves(): | ||
with tip_graph(('alice', 'bob', 1, True, False, False, "twitter", "2345")): | ||
expected = 'alice' | ||
actual = twitter.resolve(u'alice') | ||
assert actual == expected, actual |
Oops, something went wrong.