Where to mock to test a Django view that calls a service? - python

This is Django 1.6.8, Python 2.7, and the mock library.
I have a view that calls a remote service using suds for sales tax information (this is a simplified version):
def sales_tax(self, bundle_slug):
bundle = get_object_or_404(Bundle, slug=bundle_slug,
active=True)
cloud = TaxCloud(TAXCLOUD_API_ID, TAXCLOUD_API_KEY)
origin = Address('origin address')
destination = Address('customer address')
cart_item = CartItem(bundle.sku, TAXCLOUD_TIC_ONLINE_GAMES,
bundle.price, 1)
try:
rate_info = cloud.get_rate(origin, destination,
[cart_item],
str(customer.id))
sales_tax = Decimal(rate_info['sales_tax'])
response = {'salesTax': locale.currency(sales_tax),
'total': locale.currency(bundle.price + sales_tax)}
except TaxCloudException as tce:
response = {
'error': str(tce)
}
Here's relevant code from the TaxCloud class:
from suds.client import Client
from suds import WebFault
class TaxCloud(object):
def __init__(self, api_login_id, api_key):
self.api_login_id = api_login_id
self.api_key = api_key
self.soap_url = 'https://api.taxcloud.net/1.0/TaxCloud.asmx'
self.wsdl_url = 'https://api.taxcloud.net/1.0/?wsdl'
self.client = Client(url=self.wsdl_url, location=self.soap_url, faults=False)
def get_rate(self, billing_address, origin_address, raw_cart_items, customer_id):
address = self.convert_to_address(billing_address)
origin = self.convert_to_address(origin_address)
cart_items = self.convert_to_cart_list(raw_cart_items)
response = self.client.service.Lookup(self.api_login_id,
self.api_key, customer_id,
self.cart_id(customer_id), cart_items,
address, origin, True, None)
if( response[1].ResponseType == 'Error' ):
raise TaxCloudException(response[1].Messages[0][0].Message)
return {
'sales_tax': str(response[1].CartItemsResponse[0][0].TaxAmount),
'cart_id': response[1].CartID
}
In my test for the view, I don't want to call the remote service. Using this example of a mocked client, I built out a dumb mock class (my ClientMock matches exactly the example in that answer):
class TaxCloudServiceClientMock(ClientMock):
"""
Mock object that implements remote side services.
"""
def Lookup(cls, api_id, api, customer_id, cart_id, cart_items,
address, origin, flag, setting):
"""
Stub for remote service.
"""
return """(200, (LookupRsp){
ResponseType = "OK"
Messages = ""
CartID = "82cabf35faf66d8b197c7040a9f7382b3f61573fc043d73717"
CartItemsResponse =
(ArrayOfCartItemResponse){
CartItemResponse[] =
(CartItemResponse){
CartItemIndex = 1
TaxAmount = 0.10875
},
}
})"""
In my test, I'm trying to #patch the Client used in the TaxCloud class:
#patch('sales.TaxCloud.Client', new=TaxCloudServiceClientMock)
def test_can_retrieve_sales_tax(self):
from django.core.urlresolvers import reverse
tax_url = reverse('sales_tax', kwargs={'bundle_slug': self.bundle.slug})
self.client.login(username=self.user.username, password='testpassword')
response = self.client.get(tax_url, {'address1': '1234 Blah St',
'city': 'Some City',
'state': 'OH',
'zipcode': '12345',
'country': 'US'},
HTTP_X_REQUESTED_WITH='XMLHttpRequest')
self.assertEqual(response.status_code, 200)
The remote call is still being made, however. Based on the "Where to mock" documentation, I'm correctly targeting sales.TaxCloud.Client instead of suds.client.Client.
What could be causing the patch to be ignored/bypassed?

Related

difficulty setting up consumer test pact python

Im trying to set up a consumer test with Pact, but Im struggling. If someone could help me where Im going wrong it would be appreciated.
The file I am trying to test is as follows:
import requests
from orders_service.exceptions import (
APIIntegrationError,
InvalidActionError
)
class OrderItem:
def __init__(self, id, product, quantity, size):
self.id = id
self.product = product
self.quantity = quantity
self.size = size
def dict(self):
return {
'product': self.product,
'size': self.size,
'quantity': self.quantity
}
class Order:
def __init__(self, id, created, items, status, schedule_id=None,
delivery_id=None, order_=None):
self._order = order_
self._id = id
self._created = created
self.items = [OrderItem(**item) for item in items]
self.status = status
self.schedule_id = schedule_id
self.delivery_id = delivery_id
#property
def id(self):
return self._id or self._order.id
#property
def created(self):
return self._created or self._order.created
#property
def status(self):
return self._status or self._order.status
def cancel(self):
if self.status == 'progress':
response = requests.get(
f'http://localhost:3001/kitchen/schedule/{self.schedule_id}/cancel',
data={'order': self.items}
)
if response.status_code == 200:
return
raise APIIntegrationError(
f'Could not cancel order with id {self.id}'
)
if self.status == 'delivery':
raise InvalidActionError(f'Cannot cancel order with id {self.id}')
def pay(self):
response = requests.post(
'http://localhost:3001/payments', data={'order_id': self.id}
)
if response.status_code == 200:
return
raise APIIntegrationError(
f'Could not process payment for order with id {self.id}'
)
def schedule(self):
response = requests.post(
'http://localhost:3000/kitchen/schedule',
data={'order': [item.dict() for item in self.items]}
)
if response.status_code == 201:
return response.json()['id']
raise APIIntegrationError(
f'Could not schedule order with id {self.id}'
)
def dict(self):
return {
'id': self.id,
'order': [item.dict() for item in self.items],
'status': self.status,
'created': self.created,
}
The consumer test I just can't get it to stage where it is publishing the contract. There are 2 areas Im not too familiar with firstly the python fixture. Im really unsure what needs to go here or how to do that and lastly the "consumer.cancel()" at the very bottom of the test.
Some help getting me set up and one the way would be greatly appreciated. Here is what I wrote for the test:
import atexit
from datetime import datetime
import logging
import os
from uuid import UUID
import requests
import pytest
import subprocess
from pact import Consumer, Like, Provider, Term, Format
from orders_service.orders import Order, OrderItem
log = logging.getLogger(__name__)
logging.basicConfig(level=logging.INFO)
# If publishing the Pact(s), they will be submitted to the Pact Broker here.
# For the purposes of this example, the broker is started up as a fixture defined
# in conftest.py. For normal usage this would be self-hosted or using Pactflow.
PACT_BROKER_URL = "https://xxx.pactflow.io/"
PACT_BROKER_USERNAME = xxx
PACT_BROKER_PASSWORD = xxx
# Define where to run the mock server, for the consumer to connect to. These
# are the defaults so may be omitted
PACT_MOCK_HOST = "localhost"
PACT_MOCK_PORT = 1234
# Where to output the JSON Pact files created by any tests
PACT_DIR = os.path.dirname(os.path.realpath(__file__))
#pytest.fixture
def consumer() -> Order.cancel:
# return Order.cancel("http://{host}:{port}".format(host=PACT_MOCK_HOST, "port=PACT_MOCK_PORT))
order = [OrderItem(**{"id":1, "product":"coffee", "size":"big", "quantity":2})]
payload = Order(id=UUID, created=datetime.now, items=order, status="progress")
return Order.cancel(payload)
#pytest.fixture(scope="session")
def pact(request):
"""Setup a Pact Consumer, which provides the Provider mock service. This
will generate and optionally publish Pacts to the Pact Broker"""
# When publishing a Pact to the Pact Broker, a version number of the Consumer
# is required, to be able to construct the compatability matrix between the
# Consumer versions and Provider versions
# version = request.config.getoption("--publish-pact")
# publish = True if version else False
pact = Consumer("UserServiceClient", version=1).has_pact_with(
Provider("UserService"),
host_name=PACT_MOCK_HOST,
port=PACT_MOCK_PORT,
pact_dir=PACT_DIR,
publish_to_broker=True,
broker_base_url=PACT_BROKER_URL,
broker_username=PACT_BROKER_USERNAME,
broker_password=PACT_BROKER_PASSWORD,
)
pact.start_service()
# Make sure the Pact mocked provider is stopped when we finish, otherwise
# port 1234 may become blocked
atexit.register(pact.stop_service)
yield pact
# This will stop the Pact mock server, and if publish is True, submit Pacts
# to the Pact Broker
pact.stop_service()
# Given we have cleanly stopped the service, we do not want to re-submit the
# Pacts to the Pact Broker again atexit, since the Broker may no longer be
# available if it has been started using the --run-broker option, as it will
# have been torn down at that point
pact.publish_to_broker = False
def test_cancel_scheduled_order(pact, consumer):
expected = \
{
"id": "1e54e244-d0ab-46ed-a88a-b9e6037655ef",
"order": [
{
"product": "coffee",
"quantity": 1,
"size": "small"
}
],
"scheduled": "Wed, 22 Jun 2022 09:21:26 GMT",
"status": "cancelled"
}
(pact
.given('A scheduled order exists and it is not cancelled already')
.upon_receiving('a request for cancellation')
.with_request('get', f'http://localhost:3001/kitchen/schedule/{Like(12343)}/cancel')
.will_respond_with(200, body=Like(expected)))
with pact:
payload = Order(UUID, datetime.now, {"product":"coffee", "size":"large", "quantity":1}, "progress")
print(payload)
response = consumer.cancel(payload)
assert response['status'] == "cancelled"
pact.verify()
Also I originally had(adapted from the example in pact):
# return Order.cancel("http://{host}:{port}".format(host=PACT_MOCK_HOST, "port=PACT_MOCK_PORT))
but i'm not sure how that works
Thanks for helping me
There are a couple of issues here:
.with_request('get', f'http://localhost:3001/kitchen/schedule/{Like(12343)}/cancel')
The Like matcher is a function that returns an object. Adding this within a string is likely to cause issues when it is stringified
You don't need to put the protocol and host portion here - just the path e.g.:
.with_request(method='GET', path='/kitchen/schedule/bc72e917-4af1-4e39-b897-1eda6d006b18/cancel', headers={'Content-Type': 'application/json'} ...)
If you want to use a matcher on the path, it needs to be on the string as a whole e.g. Regex('/kitchen/schedule/([0-9]+)/cancel') (this is not a real regex, but hopefully you get the idea).
I can’t see in this code where it calls the actual mock service. I’ve removed the commented items for readability:
(pact
.given('A scheduled order exists and it is not cancelled already')
.upon_receiving('a request for cancellation')
.with_request(method='GET', path='/kitchen/schedule/bc72e917-4af1-4e39-b897-1eda6d006b18/cancel', headers={'Content-Type': 'application/json'},)
.will_respond_with(200, body=Like(expected)))
with pact:
# this needs to be sending a request to
# http://localhost:1234/kitchen/schedule/bc72e917-4af1-4e39-b897-1eda6d006b18/cancel
response = consumer.cancel()
pact.verify()
The definition of the function you are calling doesn't make any HTTP request to the pact mock service, it just returns a canned response.
#pytest.fixture
def consumer() -> Order.cancel:
# return Order.cancel("http://{host}:{port}".format(host=PACT_MOCK_HOST, "port=PACT_MOCK_PORT))
order = [OrderItem(**{"id":1, "product":"coffee", "size":"big", "quantity":2})]
payload = Order(id=UUID, created=datetime.now, items=order, status="progress")
return Order.cancel(payload)
For a Pact test to pass, you need to demonstrate your code actually calls the correct HTTP endpoints with the right data, and that your code can handle it.

Python output RETURN not working in an Api key sms script

I'm trying to make a python script that returns an account information from API Key
The website : pvasms.com
Tutorial in pvasms.com : http://smspva.com/new_theme_api.html
I've found 2 scripts already on github :
https://github.com/ooojustin/smspva/blob/master/smspva/smsrequest.py
https://github.com/alihossein/smspva/blob/master/SmsPva.py
I've tried both of them, i've replaced value with my Api Key, when i run : 0 Error but it didn't return anything
import requests
class SmsPva:
def __init__(self):
self._url = 'http://smspva.com/priemnik.php'
self._api_key = 'myapikey'
self._method_type = 'get'
self._query_string = {}
def get_number(self, id=1, country='ru', service='opt29'):
"""
Request for receiving a phone number for a certain service
:return:
"""
self._query_string = {'metod': 'get_number', 'country': country, 'service': service, 'id': id,
'apikey': self._api_key}
result = self.__make_request()
return result
def get_sms(self, id, country='ru', service='opt29'):
"""
Receiving a SMS for a certain service
:return:
"""
self._query_string = {'metod': 'get_sms', 'country': country, 'service': service, 'id': id,
'apikey': self._api_key}
result = self.__make_request()
return result
def get_balance(self, service='opt29'):
"""
User's balance request
:param service:
:return:
"""
self._query_string = {'metod': 'get_balance', 'service': service, 'apikey': self._api_key}
result = self.__make_request()
return result
def get_userinfo(self, service='opt29'):
"""
User's balance request and karma (Your rating)
:return:
"""
self._query_string = {'metod': 'get_userinfo', 'service': service, 'apikey': self._api_key}
result = self.__make_request()
return result
def get_count_new(self, service='opt29', country='ru'):
"""
Request for the amount of free activations for a certain service
:param country:
:param service:
:return:
"""
self.query_string = {'metod': 'get_count_new', 'service': service, 'country': country, 'apikey': self._api_key}
result = self.__make_request()
return result
def denial(self, id, country='ru', service='opt29'):
"""
Cancel the order to number you got
:return:
"""
self._query_string = {'metod': 'denial', 'country': country, 'service': service, 'id': id,
'apikey': self._api_key}
result = self.__make_request()
return result
def __make_request(self):
"""
make request post or get , ...
:return:
"""
try:
if self._method_type == 'get':
response = requests.get(self._url, self._query_string)
elif self._method_type == 'post':
pass
if response.status_code == 200:
return response.json()
else:
return response
except Exception as e:
return e
You should first change all country and service parts in code to what you want and then look at the methods in the class (like get_number) and the comments below them and see which one do you want to use. when you did these two steps write this underneath the code (without any indentations)
request = SmsPva
request.get_number() # change get_number with any other methods in the class that you want
# you can do the last line over and over with other methods
and then you can get the output by using print function like this:
print(request.get_number())
and finally I recommend learning python in the future ;)

Error: Timestamp for this request is outside of revcWindow

I'm trying to send a request to the binance servers which require an api-key and a signature but the console is saying that the timestamp is outside of the revcWindow
I've looked up the problem and found that I need sync my computer's time to the Binance's. I'm not quite sure how to do that though (pretty newbie in Python)
def test(self):
self.url += self.url_list['test']
params = {'symbol': 'BTCETH', "timestamp": 0, "side": "BUY", "type": "LIMIT", "quantity": 0.0005, "recvWindow": 500 }
data = parammanger.encode_params(params)
data += "&signature=" + self.hash(data)
headers = {'X-MBX-APIKEY': self.a_key}
print(data)
r_body = {'signature': self.hash(data)}
r = requests.post(self.url, data, headers=headers)
print(r.request.headers)
print(r.json())
def hash(self, data):
return hashmanager.create_hash(self.s_key.encode("utf-8"), data.encode("utf-8"))
{'code': -1021, 'msg': 'Timestamp for this request is outside of the recvWindow.'}
The syncing has to do more with you SO that with Python.
Those guys here: have some solutions enter link description here
This one seems to be bulletproof (so far)
import time
from binance.client import Client
class Binance:
def __init__(self, public_key = '', secret_key = '', sync = False):
self.time_offset = 0
self.b = Client(public_key, secret_key)
if sync:
self.time_offset = self._get_time_offset()
def _get_time_offset(self):
res = self.b.get_server_time()
return res['serverTime'] - int(time.time() * 1000)
def synced(self, fn_name, **args):
args['timestamp'] = int(time.time() - self.time_offset)
return getattr(self.b, fn_name)(**args)
#I then instantiate it and call my private account functions with synced
binance = Binance(public_key = 'my_pub_key', secret_key = 'my_secret_key', sync=True)
binance.synced('order_market_buy', symbol='BNBBTC', quantity=10)
Edit:
I found an easier solution here, and the other one does not seems to be so bulletproof: enter link description here
from binance.client import Client
client = Client('api_key', 'api_secret')
import time
import win32api
gt = client.get_server_time()
tt=time.gmtime(int((gt["serverTime"])/1000))
win32api.SetSystemTime(tt[0],tt[1],0,tt[2],tt[3],tt[4],tt[5],0)

Flask socketio multiple connections

I am trying to create a FB-chat like application. I have already started with the bare minimum of flask socketio implementation. So far, I already understand that client and server side communicate through a common handler. My current implementation is a free for all chat. for brevity i just show the handlers:
Client side:
socket.on('message', function(msg) {
$("#messages").append('<li>'+msg+'</li>');
console.log('Received message');
});
Server side:
#socketio.on('message')
def handleMessage(msg):
print('Message: ' + msg)
send(msg, broadcast=True)
Question:
How do I pass in arguments so I could have different chat ids
I guess what you want to do is some chat rooms. Good news! Flask-SocketIO is now natively dealing with rooms.
You can import the modules like this :
from flask_socketio import SocketIO, emit, join_room, leave_room,
close_room, rooms, disconnect
For joining a room, it looks like this :
#socketio.on('join', namespace='/test')
def join(message):
join_room(message['room'])
session['receive_count'] = session.get('receive_count', 0) + 1
emit('my_response',
{'data': 'In rooms: ' + ', '.join(rooms()),
'count': session['receive_count']})
You can find a complete example of code in the Miguel's Github page :)
Hi sorry for previous answer.
You can get cookies from sid. If you save chat id into cookies. you can identify user id easily.
this is my namespace code.
import socketio
import imp
import json
from datetime import datetime, timedelta
gameDBModule = imp.load_compiled("ChatDatabase", "./game_chat/chat_database.pyc")
apiModule = imp.load_compiled("TotoAPI", "./game_api/__init__.pyc")
class GameChat(socketio.Namespace):
def init(self):
self.lastDataIndex = 0
self.api = apiModule.TotoAPI()
def on_join(self, sid, message):
self.db = gameDBModule.ChatDatabase()
sessid = self.getUserId(sid)
try:
info = json.loads(self.api.userinfo(sessid))
userID = str(info[4])
except Exception as e:
userID = None
self.emit('join', {'history': self.db.get_chathistory(), 'channel': message, 'moderator': False, 'user_id': userID}, room=sid)
def on_connect(self, sid, environ):
self.emit('connect', {'data': 'Connected', 'count': 0}, room=sid)
print('GameChat Connected')
def on_say(self, sid, message, environ):
sessid =self.getUserId(sid)
try:
info = json.loads(self.api.userinfo(sessid))
userID = str(info[4])
except Exception as e:
userID = None
if userID !=None:
userID = None
#Save into History
now = self.get_current_time(9)
self.lastDataIndex = self.lastDataIndex + 1
item = {
'dataIndex': self.lastDataIndex,
'bot': False,
'date': now,
'message': message,
'role': 'user',
'type': 'say',
'user_id': userID
}
self.emit('msg', item)
item['date'] = self.get_current_time(0)
self.db.insert_chatting(item)
def on_disconnect(self, sid):
print('GameChat disconnected')
def getUserId(self, sid):
try:
#Get UserID from Cookies
cookies = str(self.server.environ[sid]['HTTP_COOKIE']).split(';')
id = None
for item in (cookies):
name=str(item).split('=')[0].strip()
value=str(item).split('=')[1].strip()
if name.__eq__('ci_session'):
id = value
return id
except Exception as e:
return None
def get_current_time(self, diff):
i = datetime.now() - timedelta(hours = diff)
return i.isoformat()[:-3]+"Z"
this is the code using above name space
async_mode = 'eventlet'
import eventlet
import eventlet.wsgi
from flask import Flask, render_template, Response, request, make_response
import socketio
import imp
gameChatModule = imp.load_compiled("GameChat", "./game_chat/__init__.pyc")
apiModule = imp.load_compiled("TotoAPI", "./game_api/__init__.pyc")
sio = socketio.Server(logger=False, async_mode=async_mode)
app = Flask(__name__)
app.wsgi_app = socketio.Middleware(sio, app.wsgi_app)
app.config['SECRET_KEY'] = 'secret!'
api = apiModule.TotoAPI()
ns = gameChatModule.GameChat("/")
ns.init()
if __name__ == '__main__':
sio.register_namespace(ns)
eventlet.wsgi.server(eventlet.listen(('', 8880)), app)
I have implemented it in python 2.7 You seem use python 3. but you can implement my code.

Flask Social Authentication Class Issue

I am working off of a Miguel Grinberg tutorial on social authentication.
On the homepage template I have this code, and I removed the twitter portion from the tutorial:
<h2>I don't know you!</h2>
<p>Login with Facebook</p>
{% endif %}
So when you click that link, you pass Facebook as the provider through this view function:
#app.route('/authorize/<provider>')
def oauth_authorize(provider):
if not current_user.is_anonymous():
return redirect(url_for('index'))
oauth = OAuthSignIn.get_provider(provider)
return oauth.authorize()
Now, in a different file, oauth.py, I have the following and my issue is this. I keep getting an error when I click the Facebook link UNLESS the TwitterSignIn class is removed. I guess I am curious as to why the TwitterSignIn class needs to be removed for this to work, because no data is being passed to it, right? Even if Facebook wasn't the only option, why would clicking the Facebook sign-in link pass any data to the TwitterSignIn class?
from rauth import OAuth1Service, OAuth2Service
from flask import current_app, url_for, request, redirect, session
class OAuthSignIn(object):
providers = None
def __init__(self, provider_name):
self.provider_name = provider_name
credentials = current_app.config['OAUTH_CREDENTIALS'][provider_name]
self.consumer_id = credentials['id']
self.consumer_secret = credentials['secret']
def authorize(self):
pass
def callback(self):
pass
def get_callback_url(self):
return url_for('oauth_callback', provider=self.provider_name,
_external=True)
#classmethod
def get_provider(self, provider_name):
if self.providers is None:
self.providers = {}
for provider_class in self.__subclasses__():
provider = provider_class()
self.providers[provider.provider_name] = provider
return self.providers[provider_name]
class FacebookSignIn(OAuthSignIn):
def __init__(self):
super(FacebookSignIn, self).__init__('facebook')
self.service = OAuth2Service(
name='facebook',
client_id=self.consumer_id,
client_secret=self.consumer_secret,
authorize_url='https://graph.facebook.com/oauth/authorize',
access_token_url='https://graph.facebook.com/oauth/access_token',
base_url='https://graph.facebook.com/'
)
def authorize(self):
return redirect(self.service.get_authorize_url(
scope='email',
response_type='code',
redirect_uri=self.get_callback_url())
)
def callback(self):
if 'code' not in request.args:
return None, None, None
oauth_session = self.service.get_auth_session(
data={'code': request.args['code'],
'grant_type': 'authorization_code',
'redirect_uri': self.get_callback_url()}
)
me = oauth_session.get('me').json()
return (
'facebook$' + me['id'],
me.get('email').split('#')[0], # Facebook does not provide
# username, so the email's user
# is used instead
me.get('email')
)
class TwitterSignIn(OAuthSignIn):
def __init__(self):
super(TwitterSignIn, self).__init__('twitter')
self.service = OAuth1Service(
name='twitter',
consumer_key=self.consumer_id,
consumer_secret=self.consumer_secret,
request_token_url='https://api.twitter.com/oauth/request_token',
authorize_url='https://api.twitter.com/oauth/authorize',
access_token_url='https://api.twitter.com/oauth/access_token',
base_url='https://api.twitter.com/1.1/'
)
def authorize(self):
request_token = self.service.get_request_token(
params={'oauth_callback': self.get_callback_url()}
)
session['request_token'] = request_token
return redirect(self.service.get_authorize_url(request_token[0]))
def callback(self):
request_token = session.pop('request_token')
if 'oauth_verifier' not in request.args:
return None, None, None
oauth_session = self.service.get_auth_session(
request_token[0],
request_token[1],
data={'oauth_verifier': request.args['oauth_verifier']}
)
me = oauth_session.get('account/verify_credentials.json').json()
social_id = 'twitter$' + str(me.get('id'))
username = me.get('screen_name')
return social_id, username, None # Twitter does not provide email
Some additional information-
The specific error is this:
File "/Users/metersky/code/mylastapt/app/oauth.py", line 29, in get_provider
provider = provider_class()
File "/Users/metersky/code/mylastapt/app/oauth.py", line 73, in __init__
super(TwitterSignIn, self).__init__('twitter')
File "/Users/metersky/code/mylastapt/app/oauth.py", line 10, in __init__
credentials = current_app.config['OAUTH_CREDENTIALS'][provider_name]
KeyError: 'twitter'
And this is where the I think the issue might be happening:
app.config['OAUTH_CREDENTIALS'] = {
'facebook': {
'id': 'XXX',
'secret': 'XXXX'
}
}
The problem is in OAuthSignIn.get_provider.
#classmethod
def get_provider(self, provider_name):
if self.providers is None:
self.providers = {}
for provider_class in self.__subclasses__():
provider = provider_class()
self.providers[provider.provider_name] = provider
return self.providers[provider_name]
The first time you call it from within your view
oauth = OAuthSignIn.get_provider(provider)
the method caches the providers you've defined. It does this by checking for all of OAuthSignIn's subclasses.
for provider_class in self.__subclasses__():
When you include TwitterSignIn, it will be included as a subclass. You'll then instantiate an instance of the class
provider = provider_class()
Inside OAuthSignIn.__init__, you load the provider's settings with current_app.config['OAUTH_CREDENTIALS'][provider_name]. Since Twitter isn't included, you get the KeyError.
If you don't want to support Twitter, just remove the class. If you want to protect your application a little more so that providers can be removed from your settings without updating code, you'll need to check for the exception. You could do the check inside OAuthSignIn.__init__, but there probably isn't much value to including an unsupported provider in OAuthSignIn.providers. You're better off putting the check in OAuthSignIn.get_provider.
#classmethod
def get_provider(cls, provider_name):
if cls.providers is None:
cls.providers = {}
for provider_class in cls.__subclassess__():
try:
provider = provider_class()
except KeyError:
pass # unsupported provider
else:
cls.providers[provider.provider_name] = provider
return cls.providers[provider_name]

Categories

Resources