From e566177e4e3acbc6bf2d292aefd5ffcc71719ce7 Mon Sep 17 00:00:00 2001 From: Bharat Kalluri Date: Sun, 8 Oct 2017 23:23:24 +0530 Subject: [PATCH 01/12] Added fmt options for all methods --- tiingo/api.py | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/tiingo/api.py b/tiingo/api.py index d404bd5..9a11086 100644 --- a/tiingo/api.py +++ b/tiingo/api.py @@ -4,6 +4,8 @@ import os import sys import pkg_resources import csv +import json +from collections import namedtuple from zipfile import ZipFile @@ -79,7 +81,7 @@ class TiingoClient(RestClient): return [row for row in reader if row.get('assetType') == 'Stock'] - def get_ticker_metadata(self, ticker): + def get_ticker_metadata(self, ticker,fmt='json'): """Return metadata for 1 ticker Use TiingoClient.list_tickers() to get available options @@ -88,7 +90,11 @@ class TiingoClient(RestClient): """ url = "tiingo/daily/{}".format(ticker) response = self._request('GET', url) - return response.json() + if fmt=='json': + return response.json() + elif fmt=='object': + # inspired by https://stackoverflow.com/a/15882054 + return json.loads(json.dumps(response.json()), object_hook=lambda d: namedtuple('X', d.keys())(*d.values())) def get_ticker_price(self, ticker, startDate=None, endDate=None, @@ -128,7 +134,7 @@ class TiingoClient(RestClient): # NEWS FEEDS # tiingo/news def get_news(self, tickers=[], tags=[], sources=[], startDate=None, - endDate=None, limit=100, offset=0, sortBy="publishedDate"): + endDate=None, limit=100, offset=0, sortBy="publishedDate",fmt='json'): """Return list of news articles matching given search terms https://api.tiingo.com/docs/tiingo/news @@ -155,9 +161,12 @@ class TiingoClient(RestClient): 'endDate': endDate } response = self._request('GET', url, params=params) - return response.json() + if fmt=='json': + return response.json() + elif fmt=='object': + return json.loads(json.dumps(response.json()), object_hook=lambda d: namedtuple('X', d.keys())(*d.values())) - def get_bulk_news(self, file_id=None): + def get_bulk_news(self, file_id=None,fmt='json'): """Only available to institutional clients. If ID is NOT provided, return array of available file_ids. If ID is provided, provides URL which you can use to download your @@ -169,4 +178,7 @@ class TiingoClient(RestClient): url = "tiingo/news/bulk_download" response = self._request('GET', url) - return response.json() + if fmt=='json': + return response.json() + elif fmt=='object': + return json.loads(json.dumps(response.json()), object_hook=lambda d: namedtuple('X', d.keys())(*d.values())) From 25a05d4aa42d0e4bb3efb7429ab32bbeea139095 Mon Sep 17 00:00:00 2001 From: Bharat Kalluri Date: Mon, 9 Oct 2017 09:00:37 +0530 Subject: [PATCH 02/12] pep8 corrections and changed all object names --- tests/test_tiingo.py | 4 ++++ tiingo/api.py | 37 ++++++++++++++++++++++--------------- 2 files changed, 26 insertions(+), 15 deletions(-) diff --git a/tests/test_tiingo.py b/tests/test_tiingo.py index 4a35af2..45aa439 100644 --- a/tests/test_tiingo.py +++ b/tests/test_tiingo.py @@ -79,6 +79,10 @@ class TestTickerPrices(TestCase): assert len(tickers) > 1 assert all(ticker['assetType'] == 'Stock' for ticker in tickers) + def test_ticker_metadata_for_object(self): + data = self._client.get_ticker_metadata("GOOGL", fmt='object') + assert len(data.name) > 1 + # tiingo/news class TestNews(TestCase): diff --git a/tiingo/api.py b/tiingo/api.py index 9a11086..329f36f 100644 --- a/tiingo/api.py +++ b/tiingo/api.py @@ -81,7 +81,7 @@ class TiingoClient(RestClient): return [row for row in reader if row.get('assetType') == 'Stock'] - def get_ticker_metadata(self, ticker,fmt='json'): + def get_ticker_metadata(self, ticker, fmt='json'): """Return metadata for 1 ticker Use TiingoClient.list_tickers() to get available options @@ -90,11 +90,13 @@ class TiingoClient(RestClient): """ url = "tiingo/daily/{}".format(ticker) response = self._request('GET', url) - if fmt=='json': - return response.json() - elif fmt=='object': + data = response.json() + if fmt == 'json': + return data + elif fmt == 'object': # inspired by https://stackoverflow.com/a/15882054 - return json.loads(json.dumps(response.json()), object_hook=lambda d: namedtuple('X', d.keys())(*d.values())) + return json.loads(json.dumps(data), + object_hook=lambda d: namedtuple('Ticker', d.keys())(*d.values())) def get_ticker_price(self, ticker, startDate=None, endDate=None, @@ -134,7 +136,8 @@ class TiingoClient(RestClient): # NEWS FEEDS # tiingo/news def get_news(self, tickers=[], tags=[], sources=[], startDate=None, - endDate=None, limit=100, offset=0, sortBy="publishedDate",fmt='json'): + endDate=None, limit=100, offset=0, sortBy="publishedDate", + fmt='json'): """Return list of news articles matching given search terms https://api.tiingo.com/docs/tiingo/news @@ -161,12 +164,14 @@ class TiingoClient(RestClient): 'endDate': endDate } response = self._request('GET', url, params=params) - if fmt=='json': - return response.json() - elif fmt=='object': - return json.loads(json.dumps(response.json()), object_hook=lambda d: namedtuple('X', d.keys())(*d.values())) + data = response.json() + if fmt == 'json': + return data + elif fmt == 'object': + return json.loads(json.dumps(data), + object_hook=lambda d: namedtuple('NewsArticle', d.keys())(*d.values())) - def get_bulk_news(self, file_id=None,fmt='json'): + def get_bulk_news(self, file_id=None, fmt='json'): """Only available to institutional clients. If ID is NOT provided, return array of available file_ids. If ID is provided, provides URL which you can use to download your @@ -178,7 +183,9 @@ class TiingoClient(RestClient): url = "tiingo/news/bulk_download" response = self._request('GET', url) - if fmt=='json': - return response.json() - elif fmt=='object': - return json.loads(json.dumps(response.json()), object_hook=lambda d: namedtuple('X', d.keys())(*d.values())) + data = response.json() + if fmt == 'json': + return data + elif fmt == 'object': + return json.loads(json.dumps(data), + object_hook=lambda d: namedtuple('BulkDownload', d.keys())(*d.values())) From 2c7f76b683c16c4cacd62e5a02b1ea41065fb59e Mon Sep 17 00:00:00 2001 From: Bharat Kalluri Date: Tue, 10 Oct 2017 11:27:40 +0530 Subject: [PATCH 03/12] Fixed code to handle lists --- tests/test_tiingo.py | 2 +- tiingo/api.py | 19 ++++++++++++++----- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/tests/test_tiingo.py b/tests/test_tiingo.py index 45aa439..c683fac 100644 --- a/tests/test_tiingo.py +++ b/tests/test_tiingo.py @@ -81,7 +81,7 @@ class TestTickerPrices(TestCase): def test_ticker_metadata_for_object(self): data = self._client.get_ticker_metadata("GOOGL", fmt='object') - assert len(data.name) > 1 + assert len(data[0].name) > 1 # tiingo/news diff --git a/tiingo/api.py b/tiingo/api.py index 329f36f..fc7d2fc 100644 --- a/tiingo/api.py +++ b/tiingo/api.py @@ -94,9 +94,13 @@ class TiingoClient(RestClient): if fmt == 'json': return data elif fmt == 'object': - # inspired by https://stackoverflow.com/a/15882054 - return json.loads(json.dumps(data), - object_hook=lambda d: namedtuple('Ticker', d.keys())(*d.values())) + obj_arr = [] + for el in data: + # inspired by https://stackoverflow.com/a/15882054 + arr_el = json.loads(json.dumps(data), + object_hook=lambda d: namedtuple('Ticker', d.keys())(*d.values())) + obj_arr.append(arr_el) + return obj_arr def get_ticker_price(self, ticker, startDate=None, endDate=None, @@ -168,8 +172,13 @@ class TiingoClient(RestClient): if fmt == 'json': return data elif fmt == 'object': - return json.loads(json.dumps(data), - object_hook=lambda d: namedtuple('NewsArticle', d.keys())(*d.values())) + obj_arr = [] + for el in data: + # inspired by https://stackoverflow.com/a/15882054 + arr_el = json.loads(json.dumps(data), + object_hook=lambda d: namedtuple('NewsArticle', d.keys())(*d.values())) + obj_arr.append(arr_el) + return obj_arr def get_bulk_news(self, file_id=None, fmt='json'): """Only available to institutional clients. From 76071d78b10cb6f7b1001c9f62236f5494548d97 Mon Sep 17 00:00:00 2001 From: Bharat Kalluri Date: Wed, 11 Oct 2017 21:23:13 +0530 Subject: [PATCH 04/12] Added more tests for news API endpoints --- tests/test_tiingo.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/test_tiingo.py b/tests/test_tiingo.py index c683fac..6b6e5e0 100644 --- a/tests/test_tiingo.py +++ b/tests/test_tiingo.py @@ -135,3 +135,14 @@ class TestNews(TestCase): vcr.use_cassette('tests/fixtures/news_bulk_file_ids.yaml'): value = self._client.get_bulk_news() assert value + + def test_get_news_as_objects(self): + """Fails because this API key lacks institutional license""" + with self.assertRaises(RestClientError): + value = self._client.get_news(fmt="object") + assert value + + def test_news_bulk_as_objects(self): + """Fails because this API key lacks institutional license""" + with self.assertRaises(RestClientError): + assert self._client.get_bulk_news(fmt="object") From 7d2866d9967c276cf94460474dd21b03d58d91e0 Mon Sep 17 00:00:00 2001 From: Bharat Kalluri Date: Wed, 18 Oct 2017 22:45:44 +0530 Subject: [PATCH 05/12] Used cassettes for obj tests --- tests/test_tiingo.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/test_tiingo.py b/tests/test_tiingo.py index 6b6e5e0..dbd7f4a 100644 --- a/tests/test_tiingo.py +++ b/tests/test_tiingo.py @@ -138,11 +138,13 @@ class TestNews(TestCase): def test_get_news_as_objects(self): """Fails because this API key lacks institutional license""" - with self.assertRaises(RestClientError): + with self.assertRaises(RestClientError),\ + vcr.use_cassette('tests/fixtures/news.yaml'): value = self._client.get_news(fmt="object") assert value def test_news_bulk_as_objects(self): """Fails because this API key lacks institutional license""" - with self.assertRaises(RestClientError): + with self.assertRaises(RestClientError),\ + vcr.use_cassette('tests/fixtures/news_bulk.yaml'): assert self._client.get_bulk_news(fmt="object") From af1628630319d408915b81cb2fe7e1cf64e929a4 Mon Sep 17 00:00:00 2001 From: Cameron Yick Date: Sun, 22 Oct 2017 18:38:33 -0400 Subject: [PATCH 06/12] Fix bugs in how JSON response was being interpreted A singleton was converted to an array accidentally in the previous object implementation. --- tiingo/api.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/tiingo/api.py b/tiingo/api.py index fc7d2fc..422b845 100644 --- a/tiingo/api.py +++ b/tiingo/api.py @@ -94,13 +94,10 @@ class TiingoClient(RestClient): if fmt == 'json': return data elif fmt == 'object': - obj_arr = [] - for el in data: - # inspired by https://stackoverflow.com/a/15882054 - arr_el = json.loads(json.dumps(data), - object_hook=lambda d: namedtuple('Ticker', d.keys())(*d.values())) - obj_arr.append(arr_el) - return obj_arr + return json.loads(json.dumps(data), + object_hook=lambda d: + namedtuple('Ticker', d.keys())(*d.values())) + def get_ticker_price(self, ticker, startDate=None, endDate=None, @@ -175,8 +172,9 @@ class TiingoClient(RestClient): obj_arr = [] for el in data: # inspired by https://stackoverflow.com/a/15882054 - arr_el = json.loads(json.dumps(data), - object_hook=lambda d: namedtuple('NewsArticle', d.keys())(*d.values())) + arr_el = json.loads(json.dumps(el), + object_hook=lambda d: + namedtuple('NewsArticle', d.keys())(*d.values())) obj_arr.append(arr_el) return obj_arr From 0106890a965b2b12d9dd34e362a383f41b6dbc7d Mon Sep 17 00:00:00 2001 From: Cameron Yick Date: Sun, 22 Oct 2017 18:44:39 -0400 Subject: [PATCH 07/12] Refactor with a common method for dict to object converstion --- tiingo/api.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/tiingo/api.py b/tiingo/api.py index 422b845..8285f98 100644 --- a/tiingo/api.py +++ b/tiingo/api.py @@ -36,6 +36,15 @@ def get_buffer_from_zipfile(zipfile, filename): return TextIOWrapper(BytesIO(zipfile.read(filename))) +def dict_to_object(item, object_name): + """Converts a python dict to a namedtuple, saving memory.""" + fields = item.keys() + values = item.values() + return json.loads(json.dumps(item), + object_hook=lambda d: + namedtuple(object_name, fields)(*values)) + + class TiingoClient(RestClient): """Class for managing interactions with the Tiingo REST API @@ -94,10 +103,7 @@ class TiingoClient(RestClient): if fmt == 'json': return data elif fmt == 'object': - return json.loads(json.dumps(data), - object_hook=lambda d: - namedtuple('Ticker', d.keys())(*d.values())) - + return dict_to_object(data, "Ticker") def get_ticker_price(self, ticker, startDate=None, endDate=None, @@ -172,9 +178,7 @@ class TiingoClient(RestClient): obj_arr = [] for el in data: # inspired by https://stackoverflow.com/a/15882054 - arr_el = json.loads(json.dumps(el), - object_hook=lambda d: - namedtuple('NewsArticle', d.keys())(*d.values())) + arr_el = dict_to_object(el, "NewsArticle") obj_arr.append(arr_el) return obj_arr @@ -194,5 +198,4 @@ class TiingoClient(RestClient): if fmt == 'json': return data elif fmt == 'object': - return json.loads(json.dumps(data), - object_hook=lambda d: namedtuple('BulkDownload', d.keys())(*d.values())) + return dict_to_object(data, "BulkNews") From 45e12215678001da731f002c4004cd803ff2b7d7 Mon Sep 17 00:00:00 2001 From: Cameron Yick Date: Sun, 22 Oct 2017 18:47:17 -0400 Subject: [PATCH 08/12] Add Objects to Prices endpoint, Refactor with List Comprehension --- tiingo/api.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/tiingo/api.py b/tiingo/api.py index 8285f98..2a63e0c 100644 --- a/tiingo/api.py +++ b/tiingo/api.py @@ -123,7 +123,7 @@ class TiingoClient(RestClient): """ url = "tiingo/daily/{}/prices".format(ticker) params = { - 'format': fmt, + 'format': fmt if fmt != "object" else 'json', # conversion local 'frequency': frequency } @@ -137,6 +137,9 @@ class TiingoClient(RestClient): response = self._request('GET', url, params=params) if fmt == "json": return response.json() + elif fmt == "object": + data = response.json() + return [dict_to_object(item, "TickerPrice") for item in data] else: return response.content.decode("utf-8") @@ -175,12 +178,7 @@ class TiingoClient(RestClient): if fmt == 'json': return data elif fmt == 'object': - obj_arr = [] - for el in data: - # inspired by https://stackoverflow.com/a/15882054 - arr_el = dict_to_object(el, "NewsArticle") - obj_arr.append(arr_el) - return obj_arr + return [dict_to_object(item, "NewsArticle") for item in data] def get_bulk_news(self, file_id=None, fmt='json'): """Only available to institutional clients. From b6ad06aebc3ebf235168e1125399ee245cb86940 Mon Sep 17 00:00:00 2001 From: Cameron Yick Date: Sun, 22 Oct 2017 18:54:28 -0400 Subject: [PATCH 09/12] Use Decorator Syntax for Tests, Update Broken Cassettes The previous issue was that the cassettes weren't called with the same params as the non-object requests. --- tests/test_tiingo.py | 114 +++++++++++++++++++++---------------------- 1 file changed, 57 insertions(+), 57 deletions(-) diff --git a/tests/test_tiingo.py b/tests/test_tiingo.py index dbd7f4a..18cfe4a 100644 --- a/tests/test_tiingo.py +++ b/tests/test_tiingo.py @@ -3,9 +3,7 @@ """Tests for `tiingo` package.""" import csv - from unittest import TestCase - import vcr from tiingo import TiingoClient @@ -35,54 +33,60 @@ class TestTickerPrices(TestCase): def setUp(self): self._client = TiingoClient() - # Stub all endpoints that get reused - with vcr.use_cassette('tests/fixtures/ticker_price.yaml'): - self._ticker_price_response = \ - self._client.get_ticker_price("GOOGL") - with vcr.use_cassette('tests/fixtures/ticker_metadata.yaml'): - self._ticker_metadata_response = \ - self._client.get_ticker_metadata("GOOGL") + @vcr.use_cassette('tests/fixtures/ticker_metadata.yaml') + def test_ticker_metadata(self): + """Refactor this with python data schemavalidation""" + metadata = self._client.get_ticker_metadata("GOOGL") + + assert metadata.get('ticker') == "GOOGL" + assert metadata.get("name") + + @vcr.use_cassette('tests/fixtures/ticker_metadata.yaml') + def test_ticker_metadata_as_object(self): + metadata = self._client.get_ticker_metadata("GOOGL", fmt="object") + assert metadata.ticker == "GOOGL" # Access property via ATTRIBUTE + assert metadata.name # (contrast with key access above + + @vcr.use_cassette('tests/fixtures/ticker_price.yaml') def test_ticker_price(self): - """Test the EOD Prices Endpoint""" - assert len(self._ticker_price_response) == 1 - assert self._ticker_price_response[0].get('adjClose') + """Test that EOD Prices Endpoint works""" + prices = self._client.get_ticker_price("GOOGL") + assert len(prices) == 1 + assert prices[0].get('adjClose') + @vcr.use_cassette('tests/fixtures/ticker_price.yaml') + def test_ticker_price_as_object(self): + """Test that EOD Prices Endpoint works""" + prices = self._client.get_ticker_price("GOOGL", fmt="object") + assert len(prices) == 1 + assert hasattr(prices[0], 'adjClose') + + @vcr.use_cassette('tests/fixtures/ticker_price_with_date.yaml') def test_ticker_price_with_date(self): """Test the EOD Prices Endpoint with data param""" - with vcr.use_cassette('tests/fixtures/ticker_price_with_date.yaml'): - prices = self._client.get_ticker_price("GOOGL", - startDate="2015-01-01", - endDate="2015-01-05") + prices = self._client.get_ticker_price("GOOGL", + startDate="2015-01-01", + endDate="2015-01-05") self.assertGreater(len(prices), 1) + @vcr.use_cassette('tests/fixtures/ticker_price_with_date_csv.yaml') def test_ticker_price_with_csv(self): """Confirm that CSV endpoint works""" - with vcr.use_cassette('tests/fixtures/ticker_price_with_date_csv.yaml'): - prices_csv = self._client.get_ticker_price("GOOGL", - startDate="2015-01-01", - endDate="2015-01-05", - fmt='csv') + prices_csv = self._client.get_ticker_price("GOOGL", + startDate="2015-01-01", + endDate="2015-01-05", + fmt='csv') reader = csv.reader(prices_csv.splitlines(), delimiter=",") rows = list(reader) assert len(rows) > 2 # more than 1 day of data - def test_ticker_metadata(self): - """Refactor this with python data schemavalidation""" - assert self._ticker_metadata_response.get('ticker') == "GOOGL" - assert self._ticker_metadata_response.get("name") - + @vcr.use_cassette('tests/fixtures/list_stock_tickers.yaml') def test_list_stock_tickers(self): - """Update this test when the method is added.""" - with vcr.use_cassette('tests/fixtures/list_stock_tickers.yaml'): - tickers = self._client.list_stock_tickers() + tickers = self._client.list_stock_tickers() assert len(tickers) > 1 assert all(ticker['assetType'] == 'Stock' for ticker in tickers) - def test_ticker_metadata_for_object(self): - data = self._client.get_ticker_metadata("GOOGL", fmt='object') - assert len(data[0].name) > 1 - # tiingo/news class TestNews(TestCase): @@ -100,51 +104,47 @@ class TestNews(TestCase): 'crawlDate', 'id' ] - - def test_get_news_articles(self): - """Confirm that news article work""" - NUM_ARTICLES = 1 - - search_params = { + # Search for articles about a topic + self.num_articles = 1 + self.search_params = { "tickers": ["aapl", "googl"], "tags": ["Technology", "Bitcoin"], "startDate": "2016-01-01", "endDate": "2017-08-31", "sources": ['washingtonpost.com', 'altcointoday.com'], - "limit": NUM_ARTICLES + "limit": self.num_articles } - with vcr.use_cassette('tests/fixtures/news.yaml'): - articles = self._client.get_news(**search_params) - assert len(articles) == NUM_ARTICLES + @vcr.use_cassette('tests/fixtures/news.yaml') + def test_get_news_articles(self): + articles = self._client.get_news(**self.search_params) + assert len(articles) == self.num_articles for article in articles: assert all(key in article for key in self.article_keys) + @vcr.use_cassette('tests/fixtures/news_bulk.yaml') def test_get_news_bulk(self): """Fails because this API key lacks institutional license""" - - with self.assertRaises(RestClientError),\ - vcr.use_cassette('tests/fixtures/news_bulk.yaml'): + with self.assertRaises(RestClientError): value = self._client.get_bulk_news(file_id="1") assert value + @vcr.use_cassette('tests/fixtures/news_bulk_file_ids.yaml') def test_get_news_bulk_ids(self): """Fails because this API key lacks institutional license""" - - with self.assertRaises(RestClientError),\ - vcr.use_cassette('tests/fixtures/news_bulk_file_ids.yaml'): + with self.assertRaises(RestClientError): value = self._client.get_bulk_news() assert value + @vcr.use_cassette('tests/fixtures/news.yaml') def test_get_news_as_objects(self): - """Fails because this API key lacks institutional license""" - with self.assertRaises(RestClientError),\ - vcr.use_cassette('tests/fixtures/news.yaml'): - value = self._client.get_news(fmt="object") - assert value + articles = self._client.get_news(fmt="object", **self.search_params) + assert len(articles) == self.num_articles + for article in articles: # check if attribute access works + assert all(hasattr(article, key) for key in self.article_keys) + @vcr.use_cassette('tests/fixtures/news_bulk.yaml') def test_news_bulk_as_objects(self): """Fails because this API key lacks institutional license""" - with self.assertRaises(RestClientError),\ - vcr.use_cassette('tests/fixtures/news_bulk.yaml'): - assert self._client.get_bulk_news(fmt="object") + with self.assertRaises(RestClientError): + assert self._client.get_bulk_news(file_id="1", fmt="object") From 79ac76806a433037fb8f97ea3c64049815aea8e5 Mon Sep 17 00:00:00 2001 From: Cameron Yick Date: Sun, 22 Oct 2017 19:40:06 -0400 Subject: [PATCH 10/12] Add coverage for bulk_id object exporting --- tests/test_tiingo.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/tests/test_tiingo.py b/tests/test_tiingo.py index 18cfe4a..26977e5 100644 --- a/tests/test_tiingo.py +++ b/tests/test_tiingo.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- """Tests for `tiingo` package.""" import csv @@ -12,13 +11,11 @@ from tiingo.restclient import RestClientError # TODO # Add tests for -# Invalid API key -# Invalid ticker -# Use unittest asserts rather than regular asserts +# - Invalid API key +# - Invalid ticker +# Use unittest asserts rather than regular asserts if applicable # Wrap server errors with client side descriptive errors # Coerce startDate/endDate to string if they are passed in as datetime -# Use VCR.py to enable offline testing -# Expand test coverage def test_client_repr(): @@ -136,6 +133,7 @@ class TestNews(TestCase): value = self._client.get_bulk_news() assert value + # Tests "object" formatting option @vcr.use_cassette('tests/fixtures/news.yaml') def test_get_news_as_objects(self): articles = self._client.get_news(fmt="object", **self.search_params) @@ -143,6 +141,13 @@ class TestNews(TestCase): for article in articles: # check if attribute access works assert all(hasattr(article, key) for key in self.article_keys) + @vcr.use_cassette('tests/fixtures/news_bulk_file_ids.yaml') + def test_get_news_bulk_ids_as_objects(self): + """Fails because this API key lacks institutional license""" + with self.assertRaises(RestClientError): + value = self._client.get_bulk_news(fmt="object") + assert value + @vcr.use_cassette('tests/fixtures/news_bulk.yaml') def test_news_bulk_as_objects(self): """Fails because this API key lacks institutional license""" From 6deb27f88f83cbcbfc81964396f784146fed057e Mon Sep 17 00:00:00 2001 From: Cameron Yick Date: Sun, 22 Oct 2017 19:46:05 -0400 Subject: [PATCH 11/12] Bump Cryptography Version --- requirements_dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_dev.txt b/requirements_dev.txt index 002d144..80e7a1e 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -6,7 +6,7 @@ flake8==3.4.1 tox==2.9.1 coverage==4.4.1 Sphinx==1.6.4 -cryptography==2.0.3 +cryptography==2.1.1 PyYAML==3.12 pytest==3.2.3 pytest-runner==2.12.1 From 6829962ad61f68e38b0b42d797c0b506f16ba053 Mon Sep 17 00:00:00 2001 From: Cameron Yick Date: Sun, 22 Oct 2017 19:54:14 -0400 Subject: [PATCH 12/12] Version Bump and Module Documentation --- AUTHORS.rst | 1 + HISTORY.rst | 6 ++++++ README.rst | 14 ++++++++++---- tiingo/__version__.py | 2 +- 4 files changed, 18 insertions(+), 5 deletions(-) diff --git a/AUTHORS.rst b/AUTHORS.rst index 59662ec..1a481a0 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -11,3 +11,4 @@ Contributors ------------ * Dmitry Budaev +* Bharat Kalluri diff --git a/HISTORY.rst b/HISTORY.rst index 2b617f5..47eb4de 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -2,6 +2,12 @@ History ======= +0.4.0 (2017-10-22) +------------------ + +* Make tests run in 1/10th the time with ``vcr.py`` (@condemil #32) +* Add support for returning python objects instead of dictionaries (@BharatKalluri #33) + 0.3.0 (2017-09-17) ------------------ diff --git a/README.rst b/README.rst index 3d84db2..6fb886e 100644 --- a/README.rst +++ b/README.rst @@ -97,11 +97,17 @@ Features * Easy programmatic access to Tiingo API * Reuse requests session across API calls for better performance -* Coming soon: - * Client-side validation of tickers - * Data validation of returned responses - * Case insensitivity for ticker names +* On most methods, pass in `fmt="object"` as a keyword to have your responses come back as `NamedTuples`, which should have a lower memory impact than regular Python dictionaries. +Roadmap: +-------- + +* Client-side validation of tickers +* Data validation of returned responses +* Case insensitivity for ticker names +* More documentation / code examples + +Feel free to file a PR that implements any of the above items. Credits --------- diff --git a/tiingo/__version__.py b/tiingo/__version__.py index 32aa532..0301595 100644 --- a/tiingo/__version__.py +++ b/tiingo/__version__.py @@ -1,2 +1,2 @@ # -*- coding: utf-8 -*- -__version__ = '0.3.2' +__version__ = '0.4.0'