Repository: hydrosquall/tiingo-python

Issue:
Create Scripts to Detect/Remove API Keys from Fixtures
@dcwtx carefully described the process of removing the user's API keys from generated test fixtures in the following comment: https://github.com/hydrosquall/tiingo-python/pull/84#issue-181374833

We should produce 2 scripts, and include them in a `tools/` folder under this repository.  Feel free to tackle just 1 if you'd like to help with this issue.

1. a script (python or shell) that can search for API keys in the generated fixture files, and return false if any of those API keys are not (`30 * 0`)o
2. a script (python or shell) that can replace the users's API key in generated fixtures with `30 * 0`.
3. Some documentation in each script about how to use it

In the absence of other names, something like `api_key_detector` and `api_key_remover` will do.

Lastly, we should mention where to find / how to use these tools in `CONTRIBUTING.md`.

Comments:

Patch:
diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst
index 519b6b0..bff2266 100644
--- a/CONTRIBUTING.rst
+++ b/CONTRIBUTING.rst
@@ -117,3 +117,12 @@ To regenerate fixture remove it from tests/fixtures and run tests again::
 
     $ rm tests/fixtures/NAME.yaml
     $ py.test
+
+In order for py.test to run, you will have had to create an environment variable containing a valid tiingo API key so that the test runner can make a valid api call.  One way to do that is to::
+
+    $ export TIINGO_API_KEY='...insert api key here...'
+
+However, now this api key will become embedded in the test fixture file that is created per the prior procedure.  In order to remove this api key from the new test fixtures, run the following from the top level directory::
+
+    $ ./tools/api_key_tool.py
+
diff --git a/tools/__init__.py b/tools/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tools/api_key_tool.py b/tools/api_key_tool.py
new file mode 100755
index 0000000..3d92246
--- /dev/null
+++ b/tools/api_key_tool.py
@@ -0,0 +1,58 @@
+#!/usr/bin/env python
+
+from __future__ import print_function
+import glob
+import re
+import argparse
+
+fixtures_directory = 'tests/fixtures/'
+zero_api_regex = r'(\[Token )0{40}(\])'
+real_api_regex = r'(\[Token ).{40}(\])'
+zero_token_string = '[Token ' + 40 * '0' + ']'
+
+
+def has_api_key(file):
+    """
+    Detect whether the file contains an api key in the Token object that is not 40*'0'.
+    See issue #86.
+    :param file: path-to-file to check
+    :return: boolean
+    """
+    f = open(file, 'r')
+    text = f.read()
+    if re.search(real_api_regex, text) is not None and  \
+            re.search(zero_api_regex, text) is None:
+        return True
+    return False
+
+
+def remove_api_key(file):
+    """
+    Change the api key in the Token object to 40*'0'.  See issue #86.
+    :param file: path-to-file to change
+    """
+    with open(file, 'r') as fp:
+        text = fp.read()
+    text = re.sub(real_api_regex, zero_token_string, text)
+    with open(file, 'w') as fp:
+        fp.write(text)
+    return
+
+
+def main(path):
+    if path[-1] != '/':
+        raise ValueError('Final character in path must be /.')
+    n_files_changed = 0
+    for filename in glob.glob(path+'*.yaml'):
+        if has_api_key(filename):
+            remove_api_key(filename)
+            n_files_changed += 1
+    print("Changed {} files.".format(n_files_changed))
+
+
+if __name__ == '__main__':
+    parser = argparse.ArgumentParser()
+    parser.add_argument("path", help="path to test fixtures",
+                        nargs='?', default=fixtures_directory)
+    args = parser.parse_args()
+    main(args.path)


Test patch:
diff --git a/tests/test_api_key_tools.py b/tests/test_api_key_tools.py
new file mode 100644
index 0000000..43223f9
--- /dev/null
+++ b/tests/test_api_key_tools.py
@@ -0,0 +1,51 @@
+#
+# Test setup based on https://gist.github.com/odyniec/d4ea0959d4e0ba17a980
+#
+
+import shutil, tempfile
+from os import path
+from unittest import TestCase
+from tools.api_key_tool import remove_api_key, has_api_key
+
+
+class TestAPIKeyTools(TestCase):
+
+    def setUp(self):
+        self.test_dir = tempfile.mkdtemp()
+        f = open(path.join(self.test_dir, 'test.yaml'), 'w')
+        txt = '''interactions:
+                    - request:
+                        body: null
+                        headers:
+                          Accept: ['*/*']
+                          Accept-Encoding: ['gzip, deflate']
+                          Authorization: [Token a00000000000000000000a00000000000000000a]
+                          Connection: [keep-alive]
+                          Content-Type: [application/json]
+                          User-Agent: [tiingo-python-client 0.5.0]
+                        method: GET
+                        uri: https://api.tiingo.com/tiingo/daily/GOOGL/prices?format=json&resampleFreq=daily
+                      response:
+                        body: {string: '[{"adjClose":1037.29,"adjHigh":1044.65,"adjLow":1026.05,"adjOpen":1031.47,"adjVolume":1644794,"close":1037.29,"date":"2018-04-12T00:00:00+00:00","divCash":0.0,"high":1044.65,"low":1026.05,"open":1031.47,"splitFactor":1.0,"volume":1644794}]'}
+                        headers:
+                          Allow: ['GET, HEAD, OPTIONS']
+                          Content-Length: ['239']
+                          Content-Type: [application/json]
+                          Date: ['Fri, 13 Apr 2018 02:42:05 GMT']
+                          Server: [nginx/1.10.1]
+                          Vary: ['Accept, Cookie']
+                          X-Frame-Options: [SAMEORIGIN]
+                        status: {code: 200, message: OK}
+                    version: 1
+                    '''
+        f.write(txt)
+
+    def tearDown(self):
+        shutil.rmtree(self.test_dir)
+
+    def test_key_detector(self):
+        assert has_api_key(path.join(self.test_dir, 'test.yaml')) is True
+
+    def test_key_remover(self):
+        remove_api_key(path.join(self.test_dir, 'test.yaml'))
+        assert has_api_key(path.join(self.test_dir, 'test.yaml')) is False