From 27ba5d9a64049f2c9dcf84fa1ff2b91fce6b131d Mon Sep 17 00:00:00 2001 From: James Woglom Date: Thu, 23 Dec 2021 19:39:33 -0500 Subject: [PATCH] Update dotenv secret parsing logic, and read from .config/tconnectsync/.env path --- README.md | 43 +++++++++++++++++++++----- tconnectsync/secret.py | 18 ++++++++--- tests/test_secret.py | 70 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 120 insertions(+), 11 deletions(-) create mode 100644 tests/test_secret.py diff --git a/README.md b/README.md index 3c3b08c..264c9f2 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,9 @@ If pushed to GitHub, this will make your tconnect and Nightscout passwords publi ## Installation -First, create a file named `.env` containing configuration values. +First, you need to create a file containing configuration values. +The name of this file will be `.env`, and its location will be dependent on which +method of installation you choose. You should specify the following parameters: ```bash @@ -70,21 +72,31 @@ NS_SECRET='apisecret' TIMEZONE_NAME='America/New_York' ``` -These values can alternatively be specified via environment variables. +This file contains your t:connect username and password, Tandem pump serial number (which is utilized in API calls to t:connect), your Nightscout URL and secret token (for uploading data to Nightscout), and local timezone (the timezone used in t:connect). -The .env file contains your t:connect username and password, Tandem pump serial number (which is utilized in API calls to t:connect), your Nightscout URL and secret token (for uploading data to Nightscout), and local timezone (the timezone used in t:connect). +(Alternatively, these values can be specified via environment variables.) ### Installation via Pip This is the easiest method to install. -First, ensure that you have **Python 3** with **Pip** installed on your -Linux machine. Then, install tconnectsync from pip: +First, ensure that you have **Python 3** with **Pip** installed: + +* **On MacOS:** Open Terminal. Install [Homebrew](https://brew.sh/), and then run `brew install python3` +* **On Linux:** Follow your distribution's specific instructions. + For Debian/Ubuntu based distros, `sudo apt install python3 python3-pip` +* **On Windows:** Install Ubuntu under the [Windows Subsystem for Linux](https://ubuntu.com/wsl). + Open the Ubuntu Terminal, then run `sudo apt install python3 python3-pip`. + Perform the remainder of the steps under the Ubuntu environment. + +Now install the `tconnectsync` package with pip: ``` $ pip3 install tconnectsync ``` +If the pip3 command is not found, run `python3 -m pip install tconnectsync` instead. + After this, you should be able to view tconnectsync's help with: ``` $ tconnectsync --help @@ -108,7 +120,12 @@ optional arguments: Specifies what data should be synchronized between tconnect and Nightscout. ``` -Go to the folder where you created the `.env` file, and run: +Move the `.env` file you created to the following folder: + +* **MacOS:** `/Users//.config/tconnectsync/.env` +* **Linux:** `$HOME/.config/tconnectsync/.env` +* **Windows:** `$HOME/.config/tconnectsync/.env` (inside WSL) + ``` $ tconnectsync --check-login ``` @@ -157,7 +174,8 @@ optional arguments: ``` -Move the `.env` file you created earlier into this folder, and run: +Move the `.env` file you created earlier into the `tconnectsync` folder, and run: + ``` $ pipenv run tconnectsync --check-login ``` @@ -175,6 +193,17 @@ $ docker pull ghcr.io/jwoglom/tconnectsync/tconnectsync:latest $ docker run ghcr.io/jwoglom/tconnectsync/tconnectsync --help ``` +Move the `.env` file you created earlier into the current folder, and run: + +``` +$ docker run tconnectsync --check-login +``` + +If you receive no errors, then you can move on to the **Running Tconnectsync Continuously** section. + + +#### Building Locally + To instead build the image locally and launch the project: ```bash diff --git a/tconnectsync/secret.py b/tconnectsync/secret.py index 771f238..b1efee9 100644 --- a/tconnectsync/secret.py +++ b/tconnectsync/secret.py @@ -1,10 +1,20 @@ -import os, sys -from dotenv import load_dotenv +import os, sys, pathlib +from dotenv import dotenv_values -load_dotenv() +cwd_path = os.path.join(os.getcwd(), '.env') +global_path = os.path.join(pathlib.Path.home(), '.config/tconnectsync/.env') + +values = {} + +if os.path.exists(cwd_path): + values = dotenv_values(cwd_path) +elif os.path.exists(global_path): + values = dotenv_values(global_path) +else: + values = dotenv_values() def get(*args): - return os.environ.get(*args) + return values.get(args[0], os.environ.get(*args)) def get_number(name, default): val = get(name, default) diff --git a/tests/test_secret.py b/tests/test_secret.py new file mode 100644 index 0000000..d5ba7fc --- /dev/null +++ b/tests/test_secret.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 + +import unittest +import unittest.mock +import tempfile +import importlib +import contextlib +import pathlib +import os + +@contextlib.contextmanager +def chdir(dir): + orig_cwd = os.getcwd() + os.chdir(dir) + + try: + yield + finally: + os.chdir(orig_cwd) + +class TestSecretDotEnv(unittest.TestCase): + maxDiff = None + + def write_test_dotenv_file(self, path, type): + with open(os.path.join(path, ".env"), "w") as f: + f.write(""" +TCONNECT_EMAIL=test_%s_email@email.com +NS_URL=http://test_%s_url + """ % (type, type)) + f.close() + + def import_secret(self): + return importlib.reload(importlib.import_module("tconnectsync.secret")) + + def test_dotenv_in_current_working_directory(self): + with tempfile.TemporaryDirectory(prefix='dotenv_cwd') as dir, chdir(dir): + self.write_test_dotenv_file(dir, "dotenv_cwd") + + secret = self.import_secret() + self.assertEqual(secret.TCONNECT_EMAIL, "test_dotenv_cwd_email@email.com") + self.assertEqual(secret.NS_URL, "http://test_dotenv_cwd_url") + + def test_dotenv_in_homedir_config_folder(self): + with tempfile.TemporaryDirectory(prefix='dotenv_homedir_config') as dir, chdir(dir): + config_dir = os.path.join(dir, '.config/tconnectsync') + os.makedirs(config_dir) + + self.write_test_dotenv_file(config_dir, "dotenv_homedir_config") + + with unittest.mock.patch.object(pathlib.Path, "home") as mock_home: + mock_home.return_value = dir + + secret = self.import_secret() + self.assertEqual(secret.TCONNECT_EMAIL, "test_dotenv_homedir_config_email@email.com") + self.assertEqual(secret.NS_URL, "http://test_dotenv_homedir_config_url") + + def test_no_dotenv_file_reads_from_environment(self): + with tempfile.TemporaryDirectory(prefix='dotenv_environ') as dir, chdir(dir): + environ = { + "TCONNECT_EMAIL": "test_environ_email@email.com", + "NS_URL": "http://test_environ_url" + } + + with unittest.mock.patch.dict(os.environ, environ): + secret = self.import_secret() + self.assertEqual(secret.TCONNECT_EMAIL, environ["TCONNECT_EMAIL"]) + self.assertEqual(secret.NS_URL, environ["NS_URL"]) + +if __name__ == '__main__': + unittest.main() \ No newline at end of file