-
Notifications
You must be signed in to change notification settings - Fork 55
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feature(websocket): Add websocket client #508
Merged
hydrosquall
merged 14 commits into
hydrosquall:master
from
Mohamedemad4:added-websocket-client
Jul 19, 2021
Merged
Changes from all commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
4ce17b8
Added barebone support for websockets API
Mohamedemad4 1910c4e
added docs to README
Mohamedemad4 febcd8a
added a more informative docstring with response examples;more verbos…
Mohamedemad4 111dcb0
ammends to the last commit
Mohamedemad4 a76fa92
minor comments and doc changes
Mohamedemad4 e5b89b3
Added tests to check for valid initialization arguments for TiingoWeb…
Mohamedemad4 fa6b010
Added tests to check for valid initialization arguments for TiingoWeb…
Mohamedemad4 d9cb894
ditched the Globals and used a higher order func instead;also ditched…
Mohamedemad4 716a5b6
removed uneeded import
Mohamedemad4 f157b0c
removed config as a default arg;added comment to explain why on_error…
Mohamedemad4 be25aa2
better error handling for config option
Mohamedemad4 a15986f
test of missing API key in os env and config dict
Mohamedemad4 0be8171
ignore lgtm [py/not-named-self]
Mohamedemad4 b162391
use unittest.mock to remove TIINGO_API_KEY
Mohamedemad4 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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,44 @@ | ||
import os | ||
from unittest import TestCase,mock | ||
from tiingo.wsclient import TiingoWebsocketClient | ||
from tiingo.exceptions import MissingRequiredArgumentError | ||
|
||
class TestRestClientWithSession(TestCase): | ||
def setUp(self): | ||
|
||
def msg_cb(msg): | ||
print(msg) | ||
|
||
self.cb=msg_cb | ||
|
||
self.config = { | ||
'eventName':'subscribe', | ||
'authorization':os.getenv("TIINGO_API_KEY"), | ||
#see https://api.tiingo.com/documentation/websockets/iex > Request for more info | ||
'eventData': { | ||
'thresholdLevel':5 | ||
} | ||
} | ||
|
||
# test for missing or incorrectly supplied endpoints | ||
def test_missing_or_wrong_endpoint(self): | ||
with self.assertRaises(AttributeError) as ex: | ||
TiingoWebsocketClient(config=self.config,on_msg_cb=self.cb) | ||
self.assertTrue(type(ex.exception)==AttributeError) | ||
|
||
with self.assertRaises(AttributeError) as ex: | ||
TiingoWebsocketClient(config=self.config,endpoint='wq',on_msg_cb=self.cb) | ||
self.assertTrue(type(ex.exception)==AttributeError) | ||
|
||
# test for missing callback argument | ||
def test_missing_msg_cb(self): | ||
with self.assertRaises(MissingRequiredArgumentError) as ex: | ||
TiingoWebsocketClient(config=self.config,endpoint='iex') | ||
self.assertTrue(type(ex.exception)==MissingRequiredArgumentError) | ||
|
||
# test for missing API keys in config dict and in os env | ||
def test_missing_api_key(self): | ||
with mock.patch.dict(os.environ, {}, clear=True): #clear env vars including the TIINGO_API_KEY | ||
with self.assertRaises(RuntimeError) as ex: | ||
TiingoWebsocketClient(config={},endpoint='iex',on_msg_cb=self.cb) | ||
self.assertTrue(type(ex.exception)==RuntimeError) |
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,5 +1,6 @@ | ||
# -*- coding: utf-8 -*- | ||
from tiingo.api import TiingoClient | ||
from tiingo.wsclient import TiingoWebsocketClient | ||
|
||
__author__ = """Cameron Yick""" | ||
__email__ = '[email protected]' |
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,96 @@ | ||
import os | ||
import websocket | ||
import json | ||
from tiingo.exceptions import MissingRequiredArgumentError | ||
|
||
class TiingoWebsocketClient: | ||
''' | ||
from tiingo import TiingoWebsocketClient | ||
|
||
def cb_fn(msg): | ||
|
||
# Example response | ||
# msg = { | ||
# "service":"iex" # An identifier telling you this is IEX data. | ||
# The value returned by this will correspond to the endpoint argument. | ||
# | ||
# # Will always return "A" meaning new price quotes. There are also H type Heartbeat msgs used to keep the connection alive | ||
# "messageType":"A" # A value telling you what kind of data packet this is from our IEX feed. | ||
# | ||
# # see https://api.tiingo.com/documentation/websockets/iex > Response for more info | ||
# "data":[] # an array containing trade information and a timestamp | ||
# | ||
# } | ||
|
||
print(msg) | ||
|
||
subscribe = { | ||
'eventName':'subscribe', | ||
'authorization':'API_KEY_GOES_HERE', | ||
#see https://api.tiingo.com/documentation/websockets/iex > Request for more info | ||
'eventData': { | ||
'thresholdLevel':5 | ||
} | ||
} | ||
# notice how the object isn't needed after using it | ||
# any logic should be implemented in the callback function | ||
TiingoWebsocketClient(subscribe,endpoint="iex",on_msg_cb=cb_fn) | ||
while True:pass | ||
''' | ||
|
||
def __init__(self,config=None,endpoint=None,on_msg_cb=None): | ||
|
||
self._base_url = "wss://api.tiingo.com" | ||
self.config = {} if config is None else config | ||
|
||
try: | ||
api_key = self.config['authorization'] | ||
except KeyError: | ||
api_key = os.environ.get('TIINGO_API_KEY') | ||
self.config.update({"authorization":api_key}) | ||
|
||
self._api_key = api_key | ||
if not(api_key): | ||
raise RuntimeError("Tiingo API Key not provided. Please provide" | ||
" via environment variable or config argument." | ||
"Notice that this config dict takes the API Key as authorization ") | ||
|
||
self.endpoint = endpoint | ||
if not (self.endpoint=="iex" or self.endpoint=="fx" or self.endpoint=="crypto"): | ||
raise AttributeError("Endpoint must be defined as either (iex,fx,crypto) ") | ||
|
||
self.on_msg_cb = on_msg_cb | ||
if not self.on_msg_cb: | ||
raise MissingRequiredArgumentError("please define on_msg_cb It's a callback that gets called when new messages arrive " | ||
"Example:" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice idea to put an example function in the error message for how to fix the bug, developers really appreciate that! :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. glad you liked it |
||
"def cb_fn(msg):" | ||
" print(msg)") | ||
|
||
websocket.enableTrace(False) | ||
|
||
ws = websocket.WebSocketApp("{0}/{1}".format(self._base_url,self.endpoint), | ||
on_message = self.get_on_msg_cb(), | ||
on_error = self.on_error, | ||
on_close = self.on_close, | ||
on_open = self.get_on_open(self.config)) | ||
ws.run_forever() | ||
|
||
def get_on_open(self,config): | ||
# the methods passed to websocketClient have to be unbounded if we want WebSocketApp to pass everything correctly | ||
# see websocket-client/#471 | ||
def on_open(ws): | ||
ws.send(json.dumps(config)) | ||
return on_open | ||
|
||
def get_on_msg_cb(self): | ||
def on_msg_cb_local(ws,msg): | ||
self.on_msg_cb(msg) | ||
return | ||
return on_msg_cb_local | ||
|
||
# since methods need to be unbound in order for websocketClient these methods don't have a self as their first parameter | ||
def on_error(ws, error): # lgtm[py/not-named-self] | ||
print(error) | ||
|
||
def on_close(ws): # lgtm[py/not-named-self] | ||
pass |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice work on the documentation for "service" and "messageType"!
Can we provide a sample of what one of these objects would look like? 1 or 2 would be plenty.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think adding examples would work out for stylistic reasons. anyways the link provided gives a lot of in-depth details