Error with asynchronous request in DRF - python

I need to fulfill a request to two services.
The code looks like this:
async def post1(data):
response = await aiohttp.request('post', 'http://', json=data)
json_response = await response.json()
response.close()
return json_response
async def get2():
response = await aiohttp.request('get', 'http://')
json_response = await response.json()
response.close()
return json_response
async def asynchronous(parameters):
task1 = post1(parameters['data'])
task2 = get2()
result_list = []
for body in await asyncio.gather(task1, task2):
result_list.append(body)
return result_list
If I run the code locally, it's OK. The code looks like this:
if __name__ == "__main__":
ioloop = asyncio.get_event_loop()
parameters = {'data': data}
result = ioloop.run_until_complete(asynchronous(parameters))
ioloop.close()
print(result)
I get the right result. But if I try to execute code from the DRF method, an error occurs:
TypeError: object _SessionRequestContextManager can't be used in
'await' expression
example code that I run:
.....
class MyViewSet(GenericAPIView):
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
......
ioloop = asyncio.get_event_loop()
result = ioloop.run_until_complete(asynchronous(serializer.data)) # <<<<< error here
ioloop.close()
......
return Response(serializer.data, status=status.HTTP_201_CREATED)
Please, tell me what the problem may be?

The object returned by aiohttp.request cannot be awaited, it must be used as an async context manager. This code:
response = await aiohttp.request('post', 'http://', json=data)
json_response = await response.json()
response.close()
needs to changed to something like:
async with aiohttp.request('post', 'http://', json=data) as response:
json_response = await response.json()
See the documentation for more usage examples.
Perhaps you have a different aiohttp version on the server where you run DRF, which is why it works locally and fails under DRF.

Try it https://github.com/Skorpyon/aiorestframework
Create some middleware for authentication:
import re
from xml.etree import ElementTree as etree
from json.decoder import JSONDecodeError
from multidict import MultiDict, MultiDictProxy
from aiohttp import web
from aiohttp.hdrs import (
METH_POST, METH_PUT, METH_PATCH, METH_DELETE
)
from aiorestframework import exceptions
from aiorestframework omport serializers
from aiorestframework import Response
from aiorestframework.views import BaseViewSet
from aiorestframework.permissions import set_permissions, AllowAny
from aiorestframework.app import APIApplication
from my_project.settings import Settings # Generated by aiohttp-devtools
TOKEN_RE = re.compile(r'^\s*BEARER\s{,3}(\S{64})\s*$')
async def token_authentication(app, handler):
"""
Authorization middleware
Catching Authorization: BEARER <token> from request headers
Found user in Tarantool by token and bind User or AnonymousUser to request
"""
async def middleware_handler(request):
# Check that `Authorization` header exists
if 'authorization' in request.headers:
authorization = request.headers['authorization']
# Check matches in header value
match = TOKEN_RE.match(authorization)
if not match:
setattr(request, 'user', AnonymousUser())
return await handler(request)
else:
token = match[1]
elif 'authorization_token' in request.query:
token = request.query['authorization_token']
else:
setattr(request, 'user', AnonymousUser())
return await handler(request)
# Try select user auth record from Tarantool by token index
res = await app['tnt'].select('auth', [token, ])
cached = res.body
if not cached:
raise exceptions.AuthenticationFailed()
# Build basic user data and bind it to User instance
record = cached[0]
user = User()
user.bind_cached_tarantool(record)
# Add User to request
setattr(request, 'user', user)
return await handler(request)
return middleware_handler
And for data extraction from request:
DATA_METHODS = [METH_POST, METH_PUT, METH_PATCH, METH_DELETE]
JSON_CONTENT = ['application/json', ]
XML_CONTENT = ['application/xml', ]
FORM_CONTENT = ['application/x-www-form-urlencoded', 'multipart/form-data']
async def request_data_handler(app, handler):
"""
Request .data middleware
Try extract POST data or application/json from request body
"""
async def middleware_handler(request):
data = None
if request.method in DATA_METHODS:
if request.has_body:
if request.content_type in JSON_CONTENT:
# If request has body - try to decode it to JSON
try:
data = await request.json()
except JSONDecodeError:
raise exceptions.ParseError()
elif request.content_type in XML_CONTENT:
if request.charset is not None:
encoding = request.charset
else:
encoding = api_settings.DEFAULT_CHARSET
parser = etree.XMLParser(encoding=encoding)
try:
text = await request.text()
tree = etree.XML(text, parser=parser)
except (etree.ParseError, ValueError) as exc:
raise exceptions.ParseError(
detail='XML parse error - %s' % str(exc))
data = tree
elif request.content_type in FORM_CONTENT:
data = await request.post()
if data is None:
# If not catch any data create empty MultiDictProxy
data = MultiDictProxy(MultiDict())
# Attach extracted data to request
setattr(request, 'data', data)
return await handler(request)
return middleware_handler
Create few serializers:
class UserRegisterSerializer(s.Serializer):
"""Register new user"""
email = s.EmailField(max_length=256)
password = s.CharField(min_length=8, max_length=64)
first_name = s.CharField(min_length=2, max_length=64)
middle_name = s.CharField(default='', min_length=2, max_length=64,
required=False, allow_blank=True)
last_name = s.CharField(min_length=2, max_length=64)
phone = s.CharField(max_length=32, required=False,
allow_blank=True, default='')
async def register_user(self, app):
user = User()
data = self.validated_data
try:
await user.register_user(data, app)
except Error as e:
resolve_db_exception(e, self)
return user
And few ViewSets. It may be nested in bindings['custom']
class UserViewSet(BaseViewSet):
name = 'user'
lookup_url_kwarg = '{user_id:[0-9a-f]{32}}'
permission_classes = [AllowAny, ]
bindings = {
'list': {
'retrieve': 'get',
'update': 'put'
},
'custom': {
'list': {
'set_status': 'post',
'create_new_sip_password': 'post',
'get_registration_domain': 'get',
'report': UserReportViewSet
}
}
}
#staticmethod
async def resolve_sip_host(data, user, app):
sip_host = await resolve_registration_switch(user, app)
data.update({'sip_host': sip_host})
async def retrieve(self, request):
user = User()
await user.load_from_db(request.match_info['user_id'], request.app)
serializer = user_ser.UserProfileSerializer(instance=user)
data = serializer.data
await self.resolve_sip_host(data, user, request.app)
return Response(data=data)
#atomic
#set_permissions([AuthenticatedOnly, IsCompanyMember, CompanyIsEnabled])
async def update(self, request):
serializer = user_ser.UserProfileSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = await serializer.update_user(user_id=request.user.id,
app=request.app)
serializer = user_ser.UserProfileSerializer(instance=user)
data = serializer.data
await self.resolve_sip_host(data, user, request.app)
return Response(data=data)
Register Viewsets and run Application:
def setup_routes(app: APIApplication):
"""Add app routes here"""
# Auth API
app.router.register_viewset('/auth', auth_vws.AuthViewSet())
# User API
app.router.register_viewset('/user', user_vws.UserViewSet())
# Root redirection to Swagger
redirect = app.router.add_resource('/', name='home_redirect')
redirect.add_route('*', swagger_redirect)
def create_api_app():
sentry = get_sentry_middleware(settings.SENTRY_CONNECT_STRING, settings.SENTRY_ENVIRONMENT)
middlewares = [sentry, token_authentication, request_data_handler]
api_app = APIApplication(name='api', middlewares=middlewares,
client_max_size=10*(1024**2))
api_app.on_startup.append(startup.startup_api)
api_app.on_shutdown.append(startup.shutdown_api)
api_app.on_cleanup.append(startup.cleanup_api)
setup_routes(api_app)
if __name__ == '__main__':
app = create_api_app()
web.run_app(app)

Related

422 Unprocessable Entity

Here is my code:
from fastapi import (FastAPI, BackgroundTasks, UploadFile,
File, Form, Depends, HTTPException, status, Request)
from tortoise.contrib.fastapi import register_tortoise
from models import (User, Business, Product, user_pydantic, user_pydanticIn,
product_pydantic,product_pydanticIn, business_pydantic,
business_pydanticIn, user_pydanticOut)
# signals
from tortoise.signals import post_save
from typing import List, Optional, Type
from tortoise import BaseDBAsyncClient
from starlette.responses import JSONResponse
from starlette.requests import Request
#authentication and authorization
import jwt
from dotenv import dotenv_values
from fastapi.security import (
OAuth2PasswordBearer,
OAuth2PasswordRequestForm
)
# self packages
from emails import *
from authentication import *
from dotenv import dotenv_values
import math
# user image uploads
# pip install python-multipart
from fastapi import File, UploadFile
import secrets
# static files
from fastapi.staticfiles import StaticFiles
# pillow
from PIL import Image
# templates
from fastapi.templating import Jinja2Templates
# HTMLResponse
from fastapi.responses import HTMLResponse
config_credentials = dict(dotenv_values(".env"))
app = FastAPI()
# static files
# pip install aiofiles
app.mount("/static", StaticFiles(directory="static"), name="static")
# authorization configs
oath2_scheme = OAuth2PasswordBearer(tokenUrl = 'token')
# password helper functions
#app.post('/token')
async def generate_token(request_form: OAuth2PasswordRequestForm = Depends()):
token = await token_generator(request_form.username, request_form.password)
return {'access_token' : token, 'token_type' : 'bearer'}
# process signals here
#post_save(User)
async def create_business(
sender: "Type[User]",
instance: User,
created: bool,
using_db: "Optional[BaseDBAsyncClient]",
update_fields: List[str]) -> None:
if created:
business_obj = await Business.create(
business_name = instance.username, owner = instance)
await business_pydantic.from_tortoise_orm(business_obj)
# send email functionality
await send_email([instance.email], instance)
#app.post('/registration')
async def user_registration(user: user_pydanticIn):
user_info = user.dict(exclude_unset = True)
user_info['password'] = get_password_hash(user_info['password'])
user_obj = await User.create(**user_info)
new_user = await user_pydantic.from_tortoise_orm(user_obj)
return {"status" : "ok",
"data" :
f"Hello {new_user.username} thanks for choosing our services. Please check your email inbox and click on the link to confirm your registration."}
# template for email verification
templates = Jinja2Templates(directory="templates")
#app.get('/verification', response_class=HTMLResponse)
# make sure to import request from fastapi and HTMLResponse
async def email_verification(request: Request, token: str):
user = await verify_token(token)
if user and not user.is_verified:
user.is_verified = True
await user.save()
return templates.TemplateResponse("verification.html",
{"request": request, "username": user.username}
)
raise HTTPException(
status_code = status.HTTP_401_UNAUTHORIZED,
detail = "Invalid or expired token",
headers={"WWW-Authenticate": "Bearer"},
)
async def get_current_user(token: str = Depends(oath2_scheme)):
try:
payload = jwt.decode(token, config_credentials['SECRET'], algorithms = ['HS256'])
user = await User.get(id = payload.get("id"))
except:
raise HTTPException(
status_code = status.HTTP_401_UNAUTHORIZED,
detail = "Invalid username or password",
headers={"WWW-Authenticate": "Bearer"},
)
return await user
#app.post('/user/me')
async def user_login(user: user_pydantic = Depends(get_current_user)):
business = await Business.get(owner = user)
logo = business.logo
logo = "localhost:8000/static/images/"+logo
return {"status" : "ok",
"data" :
{
"username" : user.username,
"email" : user.email,
"verified" : user.is_verified,
"join_date" : user.join_date.strftime("%b %d %Y"),
"logo" : logo
}
}
#app.post("/products")
async def add_new_product(product: product_pydanticIn,
user: user_pydantic = Depends(get_current_user)):
product = product.dict(exclude_unset = True)
# to avoid division by zero error
if product['original_price'] > 0:
product["percentage_discount"] = ((product["original_price"] - product['new_price'] )/ product['original_price']) * 100
product_obj = await Product.create(**product, business = user)
product_obj = await product_pydantic.from_tortoise_orm(product_obj)
return {"status" : "ok", "data" : product_obj}
#app.get("/products")
async def get_products():
response = await product_pydantic.from_tortoise_orm(Product.all())
return {"status" : "ok", "data" : response}
#app.get("/products/{id}")
async def specific_product(id: int):
product = await Product.get(id = id)
business = await product.business
owner = await business.owner
response = await product_pydantic.from_queryset_single(Product.get(id = id))
print(type(response))
return {"status" : "ok",
"data" :
{
"product_details" : response,
"business_details" : {
"name" : business.business_name,
"city" : business.city,
"region" : business.region,
"description" : business.business_description,
"logo" : business.logo,
"owner_id" : owner.id,
"email" : owner.email,
"join_date" : owner.join_date.strftime("%b %d %Y")
}
}
}
#app.delete("/products/{id}")
async def delete_product(id: int, user: user_pydantic = Depends(get_current_user)):
product = await Product.get(id = id)
business = await product.business
owner = await business.owner
if user == owner:
product.delete()
else:
raise HTTPException(
status_code = status.HTTP_401_UNAUTHORIZED,
detail = "Not authenticated to perform this action",
headers={"WWW-Authenticate": "Bearer"},
)
return {"status" : "ok"}
# image upload
#app.post("/uploadfile/profile")
async def create_upload_file(file: UploadFile = File(...),
user: user_pydantic = Depends(get_current_user)):
FILEPATH = "./static/images/"
filename = file.filename
extension = filename.split(".")[1]
if extension not in ["jpg", "png"]:
return {"status" : "error", "detail" : "file extension not allowed"}
token_name = secrets.token_hex(10)+"."+extension
generated_name = FILEPATH + token_name
file_content = await file.read()
with open(generated_name, "wb") as file:
file.write(file_content)
# pillow
img = Image.open(generated_name)
img = img.resize(size = (200,200))
img.save(generated_name)
file.close()
business = await Business.get(owner = user)
owner = await business.owner
# check if the user making the request is authenticated
print(user.id)
print(owner.id)
if owner == user:
business.logo = token_name
await business.save()
else:
raise HTTPException(
status_code = status.HTTP_401_UNAUTHORIZED,
detail = "Not authenticated to perform this action",
headers={"WWW-Authenticate": "Bearer"},
)
file_url = "localhost:8000" + generated_name[1:]
return {"status": "ok", "filename": file_url}
#app.post("/uploadfile/product/{id}")
# check for product owner before making the changes.
async def create_upload_file(id: int, file: UploadFile = File(...),
user: user_pydantic = Depends(get_current_user)):
FILEPATH = "./static/images/"
filename = file.filename
extension = filename.split(".")[1]
if extension not in ["jpg", "png"]:
return {"status" : "error", "detail" : "file extension not allowed"}
token_name = secrets.token_hex(10)+"."+extension
generated_name = FILEPATH + token_name
file_content = await file.read()
with open(generated_name, "wb") as file:
file.write(file_content)
# pillow
img = Image.open(generated_name)
img = img.resize(size = (200,200))
img.save(generated_name)
file.close()
#get product details
product = await Product.get(id = id)
business = await product.business
owner = await business.owner
# check if the user making the request is authenticated
if owner == user:
product.product_image = token_name
await product.save()
else:
raise HTTPException(
status_code = status.HTTP_401_UNAUTHORIZED,
detail = "Not authenticated to perform this action",
headers={"WWW-Authenticate": "Bearer"},
)
file_url = "localhost:8000" + generated_name[1:]
return {"status": "ok", "filename": file_url}
register_tortoise(
app,
db_url='sqlite://database.sqlite3',
modules={'models': ['models']},
generate_schemas = True,
add_exception_handlers = True
)
When im opening up http://127.0.0.1:8000/docs and try the "/registration" request I get the error: Code: 422 Details: Error: Unprocessable Entity`
Response body:
{
"detail": [
{
"loc": [],
"msg": "UNIQUE constraint failed: user.username",
"type": "IntegrityError"
}
]
}
Response headers:
content-length: 95
content-type: application/json
date: Sun,04 Dec 2022 23:22:02 GMT
server: uvicorn
And in the console I get:
127.0.0.1:54566 - "POST /registration HTTP/1.1" 422 Unprocessable Entity
After some debugging I think that the line 86
user_obj = await User.create(**user_info)
Is causing problems, but I can't seem to be able to fix the issue. Feel free to ask for any additional information
UPD: I was followng Princekrampah's guide on youtube, the git link to the repo is: https://github.com/Princekrampah/learningFastAPI/tree/master/shoppingAPI

self.scope['user'] always returns AnonymousUser in websocket

I have researched similar questions but can't find an answer that works for me.
I would like to get the username or user_id from a session when a user connects to a websocket.
This is what I have in consumers.py:
class PracticeConsumer(AsyncConsumer):
async def websocket_connect(self, event):
print('session data', self.scope['user'])
await self.send({"type": "websocket.accept", })
...
#database_sync_to_async
def get_user(self, user_id):
try:
return User.objects.get(username=user_id).pk
except User.DoesNotExist:
return AnonymousUser()
This is my asgi.py:
"""
ASGI config for restapi project.
It exposes the ASGI callable as a module-level variable named application.
For more information on this file, see
https://docs.djangoproject.com/en/4.0/howto/deployment/asgi/
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'signup.settings')
application = ProtocolTypeRouter({
"http": get_asgi_application(),
"websocket": AllowedHostsOriginValidator(
AuthMiddlewareStack(
URLRouter(websocket_urlpatterns)
)
)
})
and the user login function + token sent when user logs in:
class CustomTokenObtainPairSerializer(TokenObtainPairSerializer):
def validate(self, attrs):
authenticate_kwargs = {
self.username_field: attrs[self.username_field],
"password": attrs["password"],
}
try:
authenticate_kwargs["request"] = self.context["request"]
except KeyError:
pass
user = authenticate(**authenticate_kwargs)
if not user:
return {
'user': 'Username or password is incorrect',
}
token = RefreshToken.for_user(user)
# customizing token payload
token['username'] = user.username
token['first_name'] = user.first_name
token['last_name'] = user.last_name
token['country'] = user.profile.country
token['city'] = user.profile.city
token['bio'] = user.profile.bio
token['photo'] = json.dumps(str(user.profile.profile_pic))
user_logged_in.send(sender=user.__class__, request=self.context['request'], user=user)
if not api_settings.USER_AUTHENTICATION_RULE(user):
raise exceptions.AuthenticationFailed(
self.error_messages["no_active_account"],
"no_active_account",
)
return {
'refresh': str(token),
'access': str(token.access_token),
}
Whenever I print out self.scope['user'] upon connecting, I get AnonymousUser
UPDATE
I tried writing some custom middleware to handle simple JWT authentication:
#database_sync_to_async
def get_user(validated_token):
try:
user = get_user_model().objects.get(id=validated_token["user_id"])
print(f"{user}")
return user
except User.DoesNotExist:
return AnonymousUser()
class JwtAuthMiddleware(BaseMiddleware):
def __init__(self, inner):
self.inner = inner
async def __call__(self, scope, receive, send):
close_old_connections()
token = parse_qs(scope["query_string"].decode("utf8"))["token"][0]
try:
UntypedToken(token)
except (InvalidToken, TokenError) as e:
print(e)
return None
else:
decoded_data = jwt_decode(token, settings.SECRET_KEY, algorithms=["HS256"])
print(decoded_data)
scope["user"] = await get_user(validated_token=decoded_data)
return await super().__call__(scope, receive, send)
def JwtAuthMiddlewareStack(inner):
return JwtAuthMiddleware(AuthMiddlewareStack(inner))
However, this gives me this error:
File "C:\Users\15512\Desktop\django-project\peerplatform\signup\middleware.py", line 37, in __call__
token = parse_qs(scope["query_string"].decode("utf8"))["token"][0]
KeyError: 'token'

convert synchronous websocket callbacks to async in python using asyncio

need you assistance to convert asynchronous websocket callbacks to async using asyncio in python 3.6.
Some brokers in India provide a websocket for tick data to their users and users can further use this tick data, one such brokers sample code(from their git repo) is pasted here
`from smartapi import SmartWebSocket
# feed_token=092017047
FEED_TOKEN="YOUR_FEED_TOKEN"
CLIENT_CODE="YOUR_CLIENT_CODE"
# token="mcx_fo|224395"
token="EXCHANGE|TOKEN_SYMBOL" #SAMPLE: nse_cm|2885&nse_cm|1594&nse_cm|11536&nse_cm|3045
# token="mcx_fo|226745&mcx_fo|220822&mcx_fo|227182&mcx_fo|221599"
task="mw" # mw|sfi|dp
ss = SmartWebSocket(FEED_TOKEN, CLIENT_CODE)
def on_message(ws, message):
print("Ticks: {}".format(message))
def on_open(ws):
print("on open")
ss.subscribe(task,token)
def on_error(ws, error):
print(error)
def on_close(ws):
print("Close")
# Assign the callbacks.
ss._on_open = on_open
ss._on_message = on_message
ss._on_error = on_error
ss._on_close = on_close
ss.connect()
so what I think the broker is doing is that it provides messages/ticks aka events that probably trigger a callback function on every received messages/tick in a synchronous way.
I however want to alter the above code so that it can work in an async manner, I have tried to write in a manner how binance provides such as async with websocketName('currencyPair')as ts: but this doesn't seem to work with my broker.
Appreciate if you can share some ideas/code/insights around this.
Thank you for your time.
Edit 1:
Thank you for your response Dirn,
Yes, I've tried this
from smartapi import SmartConnect
from smartapi import SmartWebSocket
import logging
import asyncio
CLIENT_CODE = Code_provided_by_broker
PASSWORD = My_Password
obj=SmartConnect(api_key=My_api_key)
data = obj.generateSession(CLIENT_CODE, PASSWORD)
refreshToken = data['data']['refreshToken']
print("refreshToken", refreshToken)
feedToken=obj.getfeedToken()
print("feedToken", feedToken)
FEED_TOKEN= feedToken
token="nse_cm|3499"
# token="nse_fo|53595"
task = 'mw'
async def on_message(ws, message):
print("Ticks: {}".format(message))
async def chk_msg():
# ss = SmartWebSocket(FEED_TOKEN, CLIENT_CODE)
# ss.subscribe(task,token)
# ss._on_message = on_message
# ss.connect()
# print("Ticks: {}".format(message))
async with SmartWebSocket(FEED_TOKEN, CLIENT_CODE) as ss: #throws an error: AttributeError: __aexit__
ss.subscribe(task,token)
ss.connect()
while True:
ss._on_message = on_message # not sure how to await a non-async method _on_message, also there is no use of async module in the modules(imported at start of the script) SmartWebSocket and SmartConnect
if __name__ == "__main__":
loop = asyncio.get_event_loop()
loop.run_until_complete(chk_msg())
here is the smartConnect.py
from six.moves.urllib.parse import urljoin
import sys
import csv
import json
import dateutil.parser
import hashlib
import logging
import datetime
import smartapi.smartExceptions as ex
import requests
from requests import get
import re, uuid
import socket
import platform
from smartapi.version import __version__, __title__
log = logging.getLogger(__name__)
#user_sys=platform.system()
#print("the system",user_sys)
class SmartConnect(object):
#_rootUrl = "https://openapisuat.angelbroking.com"
_rootUrl="https://apiconnect.angelbroking.com" #prod endpoint
#_login_url ="https://smartapi.angelbroking.com/login"
_login_url="https://smartapi.angelbroking.com/publisher-login" #prod endpoint
_default_timeout = 7 # In seconds
_routes = {
"api.login":"/rest/auth/angelbroking/user/v1/loginByPassword",
"api.logout":"/rest/secure/angelbroking/user/v1/logout",
"api.token": "/rest/auth/angelbroking/jwt/v1/generateTokens",
"api.refresh": "/rest/auth/angelbroking/jwt/v1/generateTokens",
"api.user.profile": "/rest/secure/angelbroking/user/v1/getProfile",
"api.order.place": "/rest/secure/angelbroking/order/v1/placeOrder",
"api.order.modify": "/rest/secure/angelbroking/order/v1/modifyOrder",
"api.order.cancel": "/rest/secure/angelbroking/order/v1/cancelOrder",
"api.order.book":"/rest/secure/angelbroking/order/v1/getOrderBook",
"api.ltp.data": "/rest/secure/angelbroking/order/v1/getLtpData",
"api.trade.book": "/rest/secure/angelbroking/order/v1/getTradeBook",
"api.rms.limit": "/rest/secure/angelbroking/user/v1/getRMS",
"api.holding": "/rest/secure/angelbroking/portfolio/v1/getHolding",
"api.position": "/rest/secure/angelbroking/order/v1/getPosition",
"api.convert.position": "/rest/secure/angelbroking/order/v1/convertPosition",
"api.gtt.create":"/gtt-service/rest/secure/angelbroking/gtt/v1/createRule",
"api.gtt.modify":"/gtt-service/rest/secure/angelbroking/gtt/v1/modifyRule",
"api.gtt.cancel":"/gtt-service/rest/secure/angelbroking/gtt/v1/cancelRule",
"api.gtt.details":"/rest/secure/angelbroking/gtt/v1/ruleDetails",
"api.gtt.list":"/rest/secure/angelbroking/gtt/v1/ruleList",
"api.candle.data":"/rest/secure/angelbroking/historical/v1/getCandleData"
}
try:
clientPublicIp= " " + get('https://api.ipify.org').text
if " " in clientPublicIp:
clientPublicIp=clientPublicIp.replace(" ","")
hostname = socket.gethostname()
clientLocalIp=socket.gethostbyname(hostname)
except Exception as e:
print("Exception while retriving IP Address,using local host IP address",e)
finally:
clientPublicIp="106.193.147.98"
clientLocalIp="127.0.0.1"
clientMacAddress=':'.join(re.findall('..', '%012x' % uuid.getnode()))
accept = "application/json"
userType = "USER"
sourceID = "WEB"
def __init__(self, api_key=None, access_token=None, refresh_token=None,feed_token=None, userId=None, root=None, debug=False, timeout=None, proxies=None, pool=None, disable_ssl=False,accept=None,userType=None,sourceID=None,Authorization=None,clientPublicIP=None,clientMacAddress=None,clientLocalIP=None,privateKey=None):
self.debug = debug
self.api_key = api_key
self.session_expiry_hook = None
self.disable_ssl = disable_ssl
self.access_token = access_token
self.refresh_token = refresh_token
self.feed_token = feed_token
self.userId = userId
self.proxies = proxies if proxies else {}
self.root = root or self._rootUrl
self.timeout = timeout or self._default_timeout
self.Authorization= None
self.clientLocalIP=self.clientLocalIp
self.clientPublicIP=self.clientPublicIp
self.clientMacAddress=self.clientMacAddress
self.privateKey=api_key
self.accept=self.accept
self.userType=self.userType
self.sourceID=self.sourceID
if pool:
self.reqsession = requests.Session()
reqadapter = requests.adapters.HTTPAdapter(**pool)
self.reqsession.mount("https://", reqadapter)
print("in pool")
else:
self.reqsession = requests
# disable requests SSL warning
requests.packages.urllib3.disable_warnings()
def requestHeaders(self):
return{
"Content-type":self.accept,
"X-ClientLocalIP": self.clientLocalIp,
"X-ClientPublicIP": self.clientPublicIp,
"X-MACAddress": self.clientMacAddress,
"Accept": self.accept,
"X-PrivateKey": self.privateKey,
"X-UserType": self.userType,
"X-SourceID": self.sourceID
}
def setSessionExpiryHook(self, method):
if not callable(method):
raise TypeError("Invalid input type. Only functions are accepted.")
self.session_expiry_hook = method
def getUserId():
return userId
def setUserId(self,id):
self.userId=id
def setAccessToken(self, access_token):
self.access_token = access_token
def setRefreshToken(self, refresh_token):
self.refresh_token = refresh_token
def setFeedToken(self,feedToken):
self.feed_token=feedToken
def getfeedToken(self):
return self.feed_token
def login_url(self):
"""Get the remote login url to which a user should be redirected to initiate the login flow."""
return "%s?api_key=%s" % (self._login_url, self.api_key)
def _request(self, route, method, parameters=None):
"""Make an HTTP request."""
params = parameters.copy() if parameters else {}
uri =self._routes[route].format(**params)
url = urljoin(self.root, uri)
# Custom headers
headers = self.requestHeaders()
if self.access_token:
# set authorization header
auth_header = self.access_token
headers["Authorization"] = "Bearer {}".format(auth_header)
if self.debug:
log.debug("Request: {method} {url} {params} {headers}".format(method=method, url=url, params=params, headers=headers))
try:
r = requests.request(method,
url,
data=json.dumps(params) if method in ["POST", "PUT"] else None,
params=json.dumps(params) if method in ["GET", "DELETE"] else None,
headers=headers,
verify=not self.disable_ssl,
allow_redirects=True,
timeout=self.timeout,
proxies=self.proxies)
except Exception as e:
raise e
if self.debug:
log.debug("Response: {code} {content}".format(code=r.status_code, content=r.content))
# Validate the content type.
if "json" in headers["Content-type"]:
try:
data = json.loads(r.content.decode("utf8"))
except ValueError:
raise ex.DataException("Couldn't parse the JSON response received from the server: {content}".format(
content=r.content))
# api error
if data.get("error_type"):
# Call session hook if its registered and TokenException is raised
if self.session_expiry_hook and r.status_code == 403 and data["error_type"] == "TokenException":
self.session_expiry_hook()
# native errors
exp = getattr(ex, data["error_type"], ex.GeneralException)
raise exp(data["message"], code=r.status_code)
return data
elif "csv" in headers["Content-type"]:
return r.content
else:
raise ex.DataException("Unknown Content-type ({content_type}) with response: ({content})".format(
content_type=headers["Content-type"],
content=r.content))
def _deleteRequest(self, route, params=None):
"""Alias for sending a DELETE request."""
return self._request(route, "DELETE", params)
def _putRequest(self, route, params=None):
"""Alias for sending a PUT request."""
return self._request(route, "PUT", params)
def _postRequest(self, route, params=None):
"""Alias for sending a POST request."""
return self._request(route, "POST", params)
def _getRequest(self, route, params=None):
"""Alias for sending a GET request."""
return self._request(route, "GET", params)
def generateSession(self,clientCode,password):
params={"clientcode":clientCode,"password":password}
loginResultObject=self._postRequest("api.login",params)
if loginResultObject['status']==True:
jwtToken=loginResultObject['data']['jwtToken']
self.setAccessToken(jwtToken)
refreshToken=loginResultObject['data']['refreshToken']
feedToken=loginResultObject['data']['feedToken']
self.setRefreshToken(refreshToken)
self.setFeedToken(feedToken)
user=self.getProfile(refreshToken)
id=user['data']['clientcode']
#id='D88311'
self.setUserId(id)
user['data']['jwtToken']="Bearer "+jwtToken
user['data']['refreshToken']=refreshToken
return user
else:
return loginResultObject
def terminateSession(self,clientCode):
logoutResponseObject=self._postRequest("api.logout",{"clientcode":clientCode})
return logoutResponseObject
def generateToken(self,refresh_token):
response=self._postRequest('api.token',{"refreshToken":refresh_token})
jwtToken=response['data']['jwtToken']
feedToken=response['data']['feedToken']
self.setFeedToken(feedToken)
self.setAccessToken(jwtToken)
return response
def renewAccessToken(self):
response =self._postRequest('api.refresh', {
"jwtToken": self.access_token,
"refreshToken": self.refresh_token,
})
tokenSet={}
if "jwtToken" in response:
tokenSet['jwtToken']=response['data']['jwtToken']
tokenSet['clientcode']=self. userId
tokenSet['refreshToken']=response['data']["refreshToken"]
return tokenSet
def getProfile(self,refreshToken):
user=self._getRequest("api.user.profile",{"refreshToken":refreshToken})
return user
def placeOrder(self,orderparams):
params=orderparams
for k in list(params.keys()):
if params[k] is None :
del(params[k])
orderResponse= self._postRequest("api.order.place", params)['data']['orderid']
return orderResponse
def modifyOrder(self,orderparams):
params = orderparams
for k in list(params.keys()):
if params[k] is None:
del(params[k])
orderResponse= self._postRequest("api.order.modify", params)
return orderResponse
def cancelOrder(self, order_id,variety):
orderResponse= self._postRequest("api.order.cancel", {"variety": variety,"orderid": order_id})
return orderResponse
def ltpData(self,exchange,tradingsymbol,symboltoken):
params={
"exchange":exchange,
"tradingsymbol":tradingsymbol,
"symboltoken":symboltoken
}
ltpDataResponse= self._postRequest("api.ltp.data",params)
return ltpDataResponse
def orderBook(self):
orderBookResponse=self._getRequest("api.order.book")
return orderBookResponse
def tradeBook(self):
tradeBookResponse=self._getRequest("api.trade.book")
return tradeBookResponse
def rmsLimit(self):
rmsLimitResponse= self._getRequest("api.rms.limit")
return rmsLimitResponse
def position(self):
positionResponse= self._getRequest("api.position")
return positionResponse
def holding(self):
holdingResponse= self._getRequest("api.holding")
return holdingResponse
def convertPosition(self,positionParams):
params=positionParams
for k in list(params.keys()):
if params[k] is None:
del(params[k])
convertPositionResponse= self._postRequest("api.convert.position",params)
return convertPositionResponse
def gttCreateRule(self,createRuleParams):
params=createRuleParams
for k in list(params.keys()):
if params[k] is None:
del(params[k])
createGttRuleResponse=self._postRequest("api.gtt.create",params)
#print(createGttRuleResponse)
return createGttRuleResponse['data']['id']
def gttModifyRule(self,modifyRuleParams):
params=modifyRuleParams
for k in list(params.keys()):
if params[k] is None:
del(params[k])
modifyGttRuleResponse=self._postRequest("api.gtt.modify",params)
#print(modifyGttRuleResponse)
return modifyGttRuleResponse['data']['id']
def gttCancelRule(self,gttCancelParams):
params=gttCancelParams
for k in list(params.keys()):
if params[k] is None:
del(params[k])
#print(params)
cancelGttRuleResponse=self._postRequest("api.gtt.cancel",params)
#print(cancelGttRuleResponse)
return cancelGttRuleResponse
def gttDetails(self,id):
params={
"id":id
}
gttDetailsResponse=self._postRequest("api.gtt.details",params)
return gttDetailsResponse
def gttLists(self,status,page,count):
if type(status)== list:
params={
"status":status,
"page":page,
"count":count
}
gttListResponse=self._postRequest("api.gtt.list",params)
#print(gttListResponse)
return gttListResponse
else:
message="The status param is entered as" +str(type(status))+". Please enter status param as a list i.e., status=['CANCELLED']"
return message
def getCandleData(self,historicDataParams):
params=historicDataParams
for k in list(params.keys()):
if params[k] is None:
del(params[k])
getCandleDataResponse=self._postRequest("api.candle.data",historicDataParams)
return getCandleDataResponse
def _user_agent(self):
return (__title__ + "-python/").capitalize() + __version__
the smartApiWebsocket.py
import websocket
import six
import base64
import zlib
import datetime
import time
import json
import threading
import ssl
class SmartWebSocket(object):
ROOT_URI='wss://wsfeeds.angelbroking.com/NestHtml5Mobile/socket/stream'
HB_INTERVAL=30
HB_THREAD_FLAG=False
WS_RECONNECT_FLAG=False
feed_token=None
client_code=None
ws=None
task_dict = {}
def __init__(self, FEED_TOKEN, CLIENT_CODE):
self.root = self.ROOT_URI
self.feed_token = FEED_TOKEN
self.client_code = CLIENT_CODE
if self.client_code == None or self.feed_token == None:
return "client_code or feed_token or task is missing"
def _subscribe_on_open(self):
request = {"task": "cn", "channel": "NONLM", "token": self.feed_token, "user": self.client_code,
"acctid": self.client_code}
print(request)
self.ws.send(
six.b(json.dumps(request))
)
thread = threading.Thread(target=self.run, args=())
thread.daemon = True
thread.start()
def run(self):
while True:
# More statements comes here
if self.HB_THREAD_FLAG:
break
print(datetime.datetime.now().__str__() + ' : Start task in the background')
self.heartBeat()
time.sleep(self.HB_INTERVAL)
def subscribe(self, task, token):
# print(self.task_dict)
self.task_dict.update([(task,token),])
# print(self.task_dict)
if task in ("mw", "sfi", "dp"):
strwatchlistscrips = token # dynamic call
try:
request = {"task": task, "channel": strwatchlistscrips, "token": self.feed_token,
"user": self.client_code, "acctid": self.client_code}
self.ws.send(
six.b(json.dumps(request))
)
return True
except Exception as e:
self._close(reason="Error while request sending: {}".format(str(e)))
raise
else:
print("The task entered is invalid, Please enter correct task(mw,sfi,dp) ")
def resubscribe(self):
for task, marketwatch in self.task_dict.items():
print(task, '->', marketwatch)
try:
request = {"task": task, "channel": marketwatch, "token": self.feed_token,
"user": self.client_code, "acctid": self.client_code}
self.ws.send(
six.b(json.dumps(request))
)
return True
except Exception as e:
self._close(reason="Error while request sending: {}".format(str(e)))
raise
def heartBeat(self):
try:
request = {"task": "hb", "channel": "", "token": self.feed_token, "user": self.client_code,
"acctid": self.client_code}
print(request)
self.ws.send(
six.b(json.dumps(request))
)
except:
print("HeartBeat Sending Failed")
# time.sleep(60)
def _parse_text_message(self, message):
"""Parse text message."""
data = base64.b64decode(message)
try:
data = bytes((zlib.decompress(data)).decode("utf-8"), 'utf-8')
data = json.loads(data.decode('utf8').replace("'", '"'))
data = json.loads(json.dumps(data, indent=4, sort_keys=True))
except ValueError:
return
# return data
if data:
self._on_message(self.ws,data)
def connect(self):
# websocket.enableTrace(True)
self.ws = websocket.WebSocketApp(self.ROOT_URI,
on_message=self.__on_message,
on_close=self.__on_close,
on_open=self.__on_open,
on_error=self.__on_error)
self.ws.run_forever(sslopt={"cert_reqs": ssl.CERT_NONE})
def __on_message(self, ws, message):
self._parse_text_message(message)
# print(msg)
def __on_open(self, ws):
print("__on_open################")
self.HB_THREAD_FLAG = False
self._subscribe_on_open()
if self.WS_RECONNECT_FLAG:
self.WS_RECONNECT_FLAG = False
self.resubscribe()
else:
self._on_open(ws)
def __on_close(self, ws):
self.HB_THREAD_FLAG = True
print("__on_close################")
self._on_close(ws)
def __on_error(self, ws, error):
if ( "timed" in str(error) ) or ( "Connection is already closed" in str(error) ) or ( "Connection to remote host was lost" in str(error) ):
self.WS_RECONNECT_FLAG = True
self.HB_THREAD_FLAG = True
if (ws is not None):
ws.close()
ws.on_message = None
ws.on_open = None
ws.close = None
# print (' deleting ws')
del ws
self.connect()
else:
print ('Error info: %s' %(error))
self._on_error(ws, error)
def _on_message(self, ws, message):
pass
def _on_open(self, ws):
pass
def _on_close(self, ws):
pass
def _on_error(self, ws, error):
pass
the provider has also imported the websocket module in the above smartApiWebsocket file and has also provided a custom webSocket.py
here is the link webSocket.py
enter link description here

Token based authorization results in an Unauthorized 401

Package versioning
Flask 1.0.2
Flask-HTTPAuth 3.2.4
Flask-RESTful 0.3.8
itsdangerous 0.24
I'm working on a API project where a POST request to a Todo resource requires an user to have a token. Upon trying to test for this scenario, I'm getting the following assertion error: AssertionError: 401 != 201. Both BasicHTTPAuth and TokenHTTPAutth from flask-HTTPAuth are handling Authorization credentials.
Based on a User having a token to access this resource, I'm not clear on why I'm getting an Unauthorized error.
tests.py
class TestAuthenicatedUserPostTodo(ApiTestCase):
'''Verify that an API user successfully adds a Todo'''
def setUp(self):
super().setUp()
previous_todo_count = Todo.select().count()
user = User.get(User.id == 1)
token_serializer = Serializer(SECRET_KEY)
self.token = token_serializer.dumps({'id': user.id})
def test_todo_collection_post_todo_success(self):
with app.test_client() as client:
http_response = client.post(
"/api/v1/todos/",
headers={
'Authorization': f"Bearer {self.token}"
},
content_type="application/json",
data={
"name": "Must do a todo",
"user": 1
}
)
current_todo_count = Todo.select().count()
self.assertEqual(http_response.status_code, 201)
self.assertGreater(current_todo_count, previous_todo_count)
auth.py
basic_auth = HTTPBasicAuth()
token_auth = HTTPTokenAuth(scheme="Bearer")
auth = MultiAuth(token_auth, basic_auth)
#basic_auth.verify_password
def verify_password(username, password):
try:
api_user = User.get(User.username == username)
except User.DoesNotExist:
return False
user_verified = api_user.check_password(password)
if user_verified:
g.user = api_user
return True
return False
#token_auth.verify_token
def verify_token(token):
timed_serializer = Serializer(SECRET_KEY)
try:
user = timed_serializer.loads(token)
api_user = User.get_by_id(user['id'])
except (SignatureExpired, BadSignature) as e:
abort(400, description=str(e))
return True
todo.py
#auth.error_handler
def errorhandler():
return jsonify(unauthorized="Cannot add Todo. Login required."), 401
class TodoCollection(Resource):
#auth.login_required
def post(self):
import pdb; pdb.set_trace()
args = self.request_parser.parse_args()
if not args['name']:
return make_response(
{'invalid_request': "Invalid todo provided"}, 400
)
new_todo = Todo.create(**args)
return (
marshal(set_todo_creator(new_todo), todo_fields, 'new_todo'),
201, {'Location': f'{new_todo.location}'}
)

flask http-auth and unittesting

Hi!
I have a route that I have protected using HTTP Basic authentication, which is implemented by Flask-HTTPAuth. Everything works fine (i can access the route) if i use curl, but when unit testing, the route can't be accessed, even though i provide it with the right username and password.
Here are the relevant code snippets in my testing module:
class TestClient(object):
def __init__(self, app):
self.client = app.test_client()
def send(self, url, method, data=None, headers={}):
if data:
data = json.dumps(data)
rv = method(url, data=data, headers=headers)
return rv, json.loads(rv.data.decode('utf-8'))
def delete(self, url, headers={}):
return self.send(url, self.client.delete, headers)
class TestCase(unittest.TestCase):
def setUp(self):
app.config.from_object('test_config')
self.app = app
self.app_context = self.app.app_context()
self.app_context.push()
db.create_all()
self.client = TestClient(self.app)
def test_delete_user(self):
# create new user
data = {'username': 'john', 'password': 'doe'}
self.client.post('/users', data=data)
# delete previously created user
headers = {}
headers['Authorization'] = 'Basic ' + b64encode((data['username'] + ':' + data['password'])
.encode('utf-8')).decode('utf-8')
headers['Content-Type'] = 'application/json'
headers['Accept'] = 'application/json'
rv, json = self.client.delete('/users', headers=headers)
self.assertTrue(rv.status_code == 200) # Returns 401 instead
Here are the callback methods required by Flask-HTTPAuth:
auth = HTTPBasicAuth()
#auth.verify_password
def verify_password(username, password):
# THIS METHOD NEVER GETS CALLED
user = User.query.filter_by(username=username).first()
if not user or not user.verify_password(password):
return False
g.user = user
return True
#auth.error_handler
def unauthorized():
response = jsonify({'status': 401, 'error': 'unauthorized', 'message': 'Please authenticate to access this API.'})
response.status_code = 401
return response
Any my route:
#app.route('/users', methods=['DELETE'])
#auth.login_required
def delete_user():
db.session.delete(g.user)
db.session.commit()
return jsonify({})
The unit test throws the following exception:
Traceback (most recent call last):
File "test_api.py", line 89, in test_delete_user
self.assertTrue(rv.status_code == 200) # Returns 401 instead
AssertionError: False is not true
I want to emphazise once more that everything works fine when i run curl with exactly the same arguments i provide for my test client, but when i run the test, verify_password method doesn't even get called.
Thank you very much for your help!
Here is an example how this could be done with pytest and the inbuilt monkeypatch fixture.
If I have this API function in some_flask_app:
from flask_httpauth import HTTPBasicAuth
app = Flask(__name__)
auth = HTTPBasicAuth()
#app.route('/api/v1/version')
#auth.login_required
def api_get_version():
return jsonify({'version': get_version()})
I can create a fixture that returns a flask test client and patches the authenticate function in HTTPBasicAuth to always return True:
import pytest
from some_flask_app import app, auth
#pytest.fixture(name='client')
def initialize_authorized_test_client(monkeypatch):
app.testing = True
client = app.test_client()
monkeypatch.setattr(auth, 'authenticate', lambda x, y: True)
yield client
app.testing = False
def test_settings_tracking(client):
r = client.get("/api/v1/version")
assert r.status_code == 200
You are going to love this.
Your send method:
def send(self, url, method, data=None, headers={}):
pass
Your delete method:
def delete(self, url, headers={}):
return self.send(url, self.client.delete, headers)
Note you are passing headers as third positional argument, so it's going as data into send().

Categories