mirror of
https://github.com/bckelley/tconnectsync.git
synced 2026-08-24 19:54:11 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
25b8dee9d8 | ||
|
|
f9952acaed | ||
|
|
dc32812ede | ||
|
|
73757801f8 | ||
|
|
1d937114c7 | ||
|
|
ae7f07e288 | ||
|
|
87fee28419 | ||
|
|
c03b89d707 | ||
|
|
a78d716990 | ||
|
|
8e46103433 | ||
|
|
bb7fc4b153 | ||
|
|
99c68d86a6 | ||
|
|
a91631f3a0 | ||
|
|
cd092bb2cc | ||
|
|
b4267560b4 | ||
|
|
df9e87c7e3 |
@@ -1,35 +0,0 @@
|
||||
name: Publish to PyPI
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
build-binary:
|
||||
name: Build Binary and Publish to PyPI
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- name: Set up Python 3.9
|
||||
uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: 3.9
|
||||
|
||||
- name: Install pypa/build
|
||||
run: >-
|
||||
python -m
|
||||
pip install
|
||||
build
|
||||
--user
|
||||
- name: Build a binary wheel and a source tarball
|
||||
run: >-
|
||||
python -m
|
||||
build
|
||||
--sdist
|
||||
--wheel
|
||||
--outdir dist/
|
||||
.
|
||||
- name: Publish to PyPI
|
||||
uses: pypa/gh-action-pypi-publish@master
|
||||
with:
|
||||
password: ${{ secrets.PYPI_API_TOKEN }}
|
||||
@@ -1,9 +1,6 @@
|
||||
__pycache__
|
||||
dist
|
||||
build
|
||||
*.pyc
|
||||
*.pyo
|
||||
*.swp
|
||||
*.swa
|
||||
*.egg-info
|
||||
.env
|
||||
|
||||
+2
-8
@@ -15,22 +15,16 @@ FROM base AS python-deps
|
||||
RUN pip install pipenv
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends gcc
|
||||
|
||||
RUN mkdir -p /base
|
||||
WORKDIR /base
|
||||
|
||||
# Install python dependencies in /.venv
|
||||
COPY Pipfile .
|
||||
COPY Pipfile.lock .
|
||||
COPY setup.cfg .
|
||||
COPY setup.py .
|
||||
COPY pyproject.toml .
|
||||
RUN PIPENV_VENV_IN_PROJECT=1 pipenv install --deploy
|
||||
|
||||
FROM base AS runtime
|
||||
|
||||
# Copy virtualenv from python-deps stage
|
||||
COPY --from=python-deps /base/.venv /base/.venv
|
||||
ENV PATH="/base/.venv/bin:$PATH"
|
||||
COPY --from=python-deps /.venv /.venv
|
||||
ENV PATH="/.venv/bin:$PATH"
|
||||
|
||||
# Create and switch to a new user
|
||||
RUN useradd --create-home appuser
|
||||
|
||||
-21
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 James Woglom
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -4,10 +4,8 @@ url = "https://pypi.org/simple"
|
||||
verify_ssl = true
|
||||
|
||||
[dev-packages]
|
||||
ptpython = "*"
|
||||
|
||||
[packages]
|
||||
tconnectsync = {editable = true, path = "."}
|
||||
requests = "*"
|
||||
bs4 = "*"
|
||||
arrow = "*"
|
||||
|
||||
Generated
+40
-111
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "413f8bbd66e702656262deda1b6586d42b5a2e9fa52f6d23416b1ea1efbc2591"
|
||||
"sha256": "979feca3aba1b7f43890a94862347738158af5847959c14dc1d5f23ce5db8cdc"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {},
|
||||
@@ -16,19 +16,19 @@
|
||||
"default": {
|
||||
"arrow": {
|
||||
"hashes": [
|
||||
"sha256:16fc29bbd9e425e3eb0fef3018297910a0f4568f21116fc31771e2760a50e074",
|
||||
"sha256:8fb7d9d3d4bf90e49e734c22fa077bdd0964135c4b8120de2510575a8d1f620c"
|
||||
"sha256:3515630f11a15c61dcb4cdd245883270dd334c83f3e639824e65a4b79cc48543",
|
||||
"sha256:399c9c8ae732270e1aa58ead835a79a40d7be8aa109c579898eb41029b5a231d"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.2.0"
|
||||
"version": "==1.0.3"
|
||||
},
|
||||
"beautifulsoup4": {
|
||||
"hashes": [
|
||||
"sha256:9a315ce70049920ea4572a4055bc4bd700c940521d36fc858205ad4fcde149bf",
|
||||
"sha256:c23ad23c521d818955a4151a67d81580319d4bf548d3d49f4223ae041ff98891"
|
||||
"sha256:4c98143716ef1cb40bf7f39a8e3eec8f8b009509e74904ba3a7b315431577e35",
|
||||
"sha256:84729e322ad1d5b4d25f805bfa05b902dd96450f43842c4e99067d5e1369eb25",
|
||||
"sha256:fff47e031e34ec82bf17e00da8f592fe7de69aeea38be00523c04623c04fb666"
|
||||
],
|
||||
"markers": "python_version >= '3.1'",
|
||||
"version": "==4.10.0"
|
||||
"version": "==4.9.3"
|
||||
},
|
||||
"bs4": {
|
||||
"hashes": [
|
||||
@@ -39,51 +39,44 @@
|
||||
},
|
||||
"certifi": {
|
||||
"hashes": [
|
||||
"sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872",
|
||||
"sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"
|
||||
"sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c",
|
||||
"sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"
|
||||
],
|
||||
"version": "==2021.10.8"
|
||||
"version": "==2020.12.5"
|
||||
},
|
||||
"charset-normalizer": {
|
||||
"chardet": {
|
||||
"hashes": [
|
||||
"sha256:e019de665e2bcf9c2b64e2e5aa025fa991da8720daa3c1138cadd2fd1856aed0",
|
||||
"sha256:f7af805c321bfa1ce6714c51f254e0d5bb5e5834039bc17db7ebe3a4cec9492b"
|
||||
"sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa",
|
||||
"sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"
|
||||
],
|
||||
"markers": "python_version >= '3'",
|
||||
"version": "==2.0.7"
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
|
||||
"version": "==4.0.0"
|
||||
},
|
||||
"idna": {
|
||||
"hashes": [
|
||||
"sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff",
|
||||
"sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"
|
||||
"sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6",
|
||||
"sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"
|
||||
],
|
||||
"markers": "python_version >= '3'",
|
||||
"version": "==3.3"
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==2.10"
|
||||
},
|
||||
"lxml": {
|
||||
"hashes": [
|
||||
"sha256:079f3ae844f38982d156efce585bc540c16a926d4436712cf4baee0cce487a3d",
|
||||
"sha256:0fbcf5565ac01dff87cbfc0ff323515c823081c5777a9fc7703ff58388c258c3",
|
||||
"sha256:122fba10466c7bd4178b07dba427aa516286b846b2cbd6f6169141917283aae2",
|
||||
"sha256:1b38116b6e628118dea5b2186ee6820ab138dbb1e24a13e478490c7db2f326ae",
|
||||
"sha256:1b7584d421d254ab86d4f0b13ec662a9014397678a7c4265a02a6d7c2b18a75f",
|
||||
"sha256:26e761ab5b07adf5f555ee82fb4bfc35bf93750499c6c7614bd64d12aaa67927",
|
||||
"sha256:289e9ca1a9287f08daaf796d96e06cb2bc2958891d7911ac7cae1c5f9e1e0ee3",
|
||||
"sha256:2a9d50e69aac3ebee695424f7dbd7b8c6d6eb7de2a2eb6b0f6c7db6aa41e02b7",
|
||||
"sha256:3082c518be8e97324390614dacd041bb1358c882d77108ca1957ba47738d9d59",
|
||||
"sha256:33bb934a044cf32157c12bfcfbb6649807da20aa92c062ef51903415c704704f",
|
||||
"sha256:3439c71103ef0e904ea0a1901611863e51f50b5cd5e8654a151740fde5e1cade",
|
||||
"sha256:36108c73739985979bf302006527cf8a20515ce444ba916281d1c43938b8bb96",
|
||||
"sha256:39b78571b3b30645ac77b95f7c69d1bffc4cf8c3b157c435a34da72e78c82468",
|
||||
"sha256:4289728b5e2000a4ad4ab8da6e1db2e093c63c08bdc0414799ee776a3f78da4b",
|
||||
"sha256:4bff24dfeea62f2e56f5bab929b4428ae6caba2d1eea0c2d6eb618e30a71e6d4",
|
||||
"sha256:4c61b3a0db43a1607d6264166b230438f85bfed02e8cff20c22e564d0faff354",
|
||||
"sha256:542d454665a3e277f76954418124d67516c5f88e51a900365ed54a9806122b83",
|
||||
"sha256:5a0a14e264069c03e46f926be0d8919f4105c1623d620e7ec0e612a2e9bf1c04",
|
||||
"sha256:5c8c163396cc0df3fd151b927e74f6e4acd67160d6c33304e805b84293351d16",
|
||||
"sha256:64812391546a18896adaa86c77c59a4998f33c24788cadc35789e55b727a37f4",
|
||||
"sha256:66e575c62792c3f9ca47cb8b6fab9e35bab91360c783d1606f758761810c9791",
|
||||
"sha256:6f12e1427285008fd32a6025e38e977d44d6382cf28e7201ed10d6c1698d2a9a",
|
||||
"sha256:74f7d8d439b18fa4c385f3f5dfd11144bb87c1da034a466c5b5577d23a1d9b51",
|
||||
"sha256:7610b8c31688f0b1be0ef882889817939490a36d0ee880ea562a4e1399c447a1",
|
||||
"sha256:76fa7b1362d19f8fbd3e75fe2fb7c79359b0af8747e6f7141c338f0bee2f871a",
|
||||
@@ -96,15 +89,10 @@
|
||||
"sha256:b007cbb845b28db4fb8b6a5cdcbf65bacb16a8bd328b53cbc0698688a68e1caa",
|
||||
"sha256:bc4313cbeb0e7a416a488d72f9680fffffc645f8a838bd2193809881c67dd106",
|
||||
"sha256:bccbfc27563652de7dc9bdc595cb25e90b59c5f8e23e806ed0fd623755b6565d",
|
||||
"sha256:c1a40c06fd5ba37ad39caa0b3144eb3772e813b5fb5b084198a985431c2f1e8d",
|
||||
"sha256:c47ff7e0a36d4efac9fd692cfa33fbd0636674c102e9e8d9b26e1b93a94e7617",
|
||||
"sha256:c4f05c5a7c49d2fb70223d0d5bcfbe474cf928310ac9fa6a7c6dddc831d0b1d4",
|
||||
"sha256:cdaf11d2bd275bf391b5308f86731e5194a21af45fbaaaf1d9e8147b9160ea92",
|
||||
"sha256:ce256aaa50f6cc9a649c51be3cd4ff142d67295bfc4f490c9134d0f9f6d58ef0",
|
||||
"sha256:d2e35d7bf1c1ac8c538f88d26b396e73dd81440d59c1ef8522e1ea77b345ede4",
|
||||
"sha256:d916d31fd85b2f78c76400d625076d9124de3e4bda8b016d25a050cc7d603f24",
|
||||
"sha256:df7c53783a46febb0e70f6b05df2ba104610f2fb0d27023409734a3ecbb78fb2",
|
||||
"sha256:e1cbd3f19a61e27e011e02f9600837b921ac661f0c40560eefb366e4e4fb275e",
|
||||
"sha256:efac139c3f0bf4f0939f9375af4b02c5ad83a622de52d6dfa8e438e8e01d0eb0",
|
||||
"sha256:efd7a09678fd8b53117f6bae4fa3825e0a22b03ef0a932e070c0bdbb3a35e654",
|
||||
"sha256:f2380a6376dfa090227b663f9678150ef27543483055cc327555fb592c5967e2",
|
||||
@@ -116,119 +104,60 @@
|
||||
},
|
||||
"python-dateutil": {
|
||||
"hashes": [
|
||||
"sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86",
|
||||
"sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"
|
||||
"sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c",
|
||||
"sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==2.8.2"
|
||||
"version": "==2.8.1"
|
||||
},
|
||||
"python-dotenv": {
|
||||
"hashes": [
|
||||
"sha256:14f8185cc8d494662683e6914addcb7e95374771e707601dfc70166946b4c4b8",
|
||||
"sha256:bbd3da593fc49c249397cbfbcc449cf36cb02e75afc8157fcc6a81df6fb7750a"
|
||||
"sha256:471b782da0af10da1a80341e8438fca5fadeba2881c54360d5fd8d03d03a4f4a",
|
||||
"sha256:49782a97c9d641e8a09ae1d9af0856cc587c8d2474919342d5104d85be9890b2"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.19.1"
|
||||
"version": "==0.17.0"
|
||||
},
|
||||
"requests": {
|
||||
"hashes": [
|
||||
"sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24",
|
||||
"sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7"
|
||||
"sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804",
|
||||
"sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.26.0"
|
||||
"version": "==2.25.1"
|
||||
},
|
||||
"requests-mock": {
|
||||
"hashes": [
|
||||
"sha256:0a2d38a117c08bb78939ec163522976ad59a6b7fdd82b709e23bb98004a44970",
|
||||
"sha256:8d72abe54546c1fc9696fa1516672f1031d72a55a1d66c85184f972a24ba0eba"
|
||||
"sha256:11215c6f4df72702aa357f205cf1e537cffd7392b3e787b58239bde5fb3db53b",
|
||||
"sha256:e68f46844e4cee9d447150343c9ae875f99fa8037c6dcf5f15bf1fe9ab43d226"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.9.3"
|
||||
"version": "==1.8.0"
|
||||
},
|
||||
"six": {
|
||||
"hashes": [
|
||||
"sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926",
|
||||
"sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"
|
||||
"sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259",
|
||||
"sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==1.16.0"
|
||||
"version": "==1.15.0"
|
||||
},
|
||||
"soupsieve": {
|
||||
"hashes": [
|
||||
"sha256:052774848f448cf19c7e959adf5566904d525f33a3f8b6ba6f6f8f26ec7de0cc",
|
||||
"sha256:c2c1c2d44f158cdbddab7824a9af8c4f83c76b1e23e049479aa432feb6c4c23b"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"markers": "python_version >= '3.0'",
|
||||
"version": "==2.2.1"
|
||||
},
|
||||
"tconnectsync": {
|
||||
"editable": true,
|
||||
"path": "."
|
||||
},
|
||||
"urllib3": {
|
||||
"hashes": [
|
||||
"sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece",
|
||||
"sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844"
|
||||
"sha256:2f4da4594db7e1e110a944bb1b551fdf4e6c136ad42e4234131391e21eb5b0df",
|
||||
"sha256:e7b021f7241115872f92f43c6508082facffbd1c048e3c6e2bb9c2a157e28937"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'",
|
||||
"version": "==1.26.7"
|
||||
"version": "==1.26.4"
|
||||
}
|
||||
},
|
||||
"develop": {
|
||||
"appdirs": {
|
||||
"hashes": [
|
||||
"sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41",
|
||||
"sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"
|
||||
],
|
||||
"version": "==1.4.4"
|
||||
},
|
||||
"jedi": {
|
||||
"hashes": [
|
||||
"sha256:18456d83f65f400ab0c2d3319e48520420ef43b23a086fdc05dff34132f0fb93",
|
||||
"sha256:92550a404bad8afed881a137ec9a461fed49eca661414be45059329614ed0707"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==0.18.0"
|
||||
},
|
||||
"parso": {
|
||||
"hashes": [
|
||||
"sha256:12b83492c6239ce32ff5eed6d3639d6a536170723c6f3f1506869f1ace413398",
|
||||
"sha256:a8c4922db71e4fdb90e0d0bc6e50f9b273d3397925e5e60a717e719201778d22"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==0.8.2"
|
||||
},
|
||||
"prompt-toolkit": {
|
||||
"hashes": [
|
||||
"sha256:6076e46efae19b1e0ca1ec003ed37a933dc94b4d20f486235d436e64771dcd5c",
|
||||
"sha256:eb71d5a6b72ce6db177af4a7d4d7085b99756bf656d98ffcc4fecd36850eea6c"
|
||||
],
|
||||
"markers": "python_full_version >= '3.6.2'",
|
||||
"version": "==3.0.20"
|
||||
},
|
||||
"ptpython": {
|
||||
"hashes": [
|
||||
"sha256:99636899ab0e4d026e2ecc9368269114f387b4bb5411e57f072b0bde724d9f99",
|
||||
"sha256:eafd4ced27ca5dc370881d4358d1ab5041b32d88d31af8e3c24167fe4af64ed6"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==3.0.20"
|
||||
},
|
||||
"pygments": {
|
||||
"hashes": [
|
||||
"sha256:b8e67fe6af78f492b3c4b3e2970c0624cbf08beb1e493b2c99b9fa1b67a20380",
|
||||
"sha256:f398865f7eb6874156579fdf36bc840a03cab64d1cde9e93d68f46a425ec52c6"
|
||||
],
|
||||
"markers": "python_version >= '3.5'",
|
||||
"version": "==2.10.0"
|
||||
},
|
||||
"wcwidth": {
|
||||
"hashes": [
|
||||
"sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784",
|
||||
"sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"
|
||||
],
|
||||
"version": "==0.2.5"
|
||||
}
|
||||
}
|
||||
"develop": {}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,77 @@
|
||||
#!/usr/bin/env python3
|
||||
from tconnectsync import main
|
||||
|
||||
import sys
|
||||
import datetime
|
||||
import arrow
|
||||
import argparse
|
||||
import logging
|
||||
|
||||
from tconnectsync.api import TConnectApi
|
||||
from tconnectsync.process import process_time_range
|
||||
from tconnectsync.autoupdate import process_auto_update
|
||||
from tconnectsync.check import check_login
|
||||
from tconnectsync.nightscout import NightscoutApi
|
||||
|
||||
try:
|
||||
from tconnectsync.secret import (
|
||||
TCONNECT_EMAIL,
|
||||
TCONNECT_PASSWORD,
|
||||
NS_URL,
|
||||
NS_SECRET
|
||||
)
|
||||
except Exception:
|
||||
print('Unable to read secret.py')
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser(description="Syncs bolus, basal, and IOB data from Tandem Diabetes t:connect to Nightscout.")
|
||||
parser.add_argument('--pretend', dest='pretend', action='store_const', const=True, default=False, help='Pretend mode: do not upload any data to Nightscout.')
|
||||
parser.add_argument('-v', '--verbose', dest='verbose', action='store_const', const=True, default=False, help='Verbose mode: show extra logging details')
|
||||
parser.add_argument('--start-date', dest='start_date', type=str, default=None, help='The oldest date to process data from. Must be specified with --end-date.')
|
||||
parser.add_argument('--end-date', dest='end_date', type=str, default=None, help='The newest date to process data until (inclusive). Must be specified with --start-date.')
|
||||
parser.add_argument('--days', dest='days', type=int, default=1, help='The number of days of t:connect data to read in. Cannot be used with --from-date and --until-date.')
|
||||
parser.add_argument('--auto-update', dest='auto_update', action='store_const', const=True, default=False, help='If set, continuously checks for updates from t:connect and syncs with Nightscout.')
|
||||
parser.add_argument('--check-login', dest='check_login', action='store_const', const=True, default=False, help='If set, checks that the provided t:connect credentials can be used to log in.')
|
||||
|
||||
return parser.parse_args()
|
||||
|
||||
def main():
|
||||
args = parse_args()
|
||||
|
||||
if args.verbose:
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
logging.root.debug("Set logging level to DEBUG")
|
||||
else:
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
if args.auto_update and (args.start_date or args.end_date):
|
||||
raise Exception('Auto-update cannot be used with start/end date')
|
||||
|
||||
if args.start_date and args.end_date:
|
||||
time_start = arrow.get(args.start_date)
|
||||
time_end = arrow.get(args.end_date)
|
||||
else:
|
||||
time_end = datetime.datetime.now()
|
||||
time_start = time_end - datetime.timedelta(days=args.days)
|
||||
|
||||
if time_end < time_start:
|
||||
raise Exception('time_start must be before time_end')
|
||||
|
||||
tconnect = TConnectApi(TCONNECT_EMAIL, TCONNECT_PASSWORD)
|
||||
|
||||
nightscout = NightscoutApi(NS_URL, NS_SECRET)
|
||||
|
||||
if args.check_login:
|
||||
return check_login(tconnect, time_start, time_end)
|
||||
|
||||
if args.auto_update:
|
||||
print("Starting auto-update between", time_start, "and", time_end, "(PRETEND)" if args.pretend else "")
|
||||
process_auto_update(tconnect, nightscout, time_start, time_end, args.pretend)
|
||||
else:
|
||||
print("Processing data between", time_start, "and", time_end, "(PRETEND)" if args.pretend else "")
|
||||
added = process_time_range(tconnect, nightscout, time_start, time_end, args.pretend)
|
||||
print("Added", added, "items")
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -1,6 +0,0 @@
|
||||
[build-system]
|
||||
requires = [
|
||||
"setuptools>=42",
|
||||
"wheel"
|
||||
]
|
||||
build-backend = "setuptools.build_meta"
|
||||
@@ -1,37 +0,0 @@
|
||||
[metadata]
|
||||
name = tconnectsync
|
||||
version = 0.4.0
|
||||
author = James Woglom
|
||||
author_email = j@wogloms.net
|
||||
description = Syncs Tandem t:connect pump data to Nightscout for the t:slim X2
|
||||
long_description = file: README.md
|
||||
long_description_content_type = text/markdown
|
||||
url = https://github.com/jwoglom/tconnectsync
|
||||
project_urls =
|
||||
Bug Tracker = https://github.com/jwoglom/tconnectsync/issues
|
||||
classifiers =
|
||||
Programming Language :: Python :: 3
|
||||
License :: OSI Approved :: MIT License
|
||||
Operating System :: OS Independent
|
||||
|
||||
[options]
|
||||
package_dir =
|
||||
= .
|
||||
packages = find:
|
||||
python_requires = >=3.6
|
||||
install_requires =
|
||||
requests
|
||||
bs4
|
||||
arrow
|
||||
lxml
|
||||
python-dotenv
|
||||
|
||||
[options.packages.find]
|
||||
where = .
|
||||
exclude =
|
||||
tests*
|
||||
scripts*
|
||||
|
||||
[options.entry_points]
|
||||
console_scripts =
|
||||
tconnectsync = tconnectsync:main
|
||||
@@ -1,86 +0,0 @@
|
||||
import sys
|
||||
import datetime
|
||||
import arrow
|
||||
import argparse
|
||||
import logging
|
||||
import pkg_resources
|
||||
|
||||
from .api import TConnectApi
|
||||
from .process import process_time_range
|
||||
from .autoupdate import process_auto_update
|
||||
from .check import check_login
|
||||
from .nightscout import NightscoutApi
|
||||
|
||||
try:
|
||||
from .secret import (
|
||||
TCONNECT_EMAIL,
|
||||
TCONNECT_PASSWORD,
|
||||
NS_URL,
|
||||
NS_SECRET
|
||||
)
|
||||
except Exception:
|
||||
print('Unable to read secret.py')
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
try:
|
||||
__version__ = pkg_resources.require("tconnectsync")[0].version
|
||||
except Exception:
|
||||
__version__ = "UNKNOWN"
|
||||
|
||||
def parse_args(*args, **kwargs):
|
||||
parser = argparse.ArgumentParser(description="Syncs bolus, basal, and IOB data from Tandem Diabetes t:connect to Nightscout.")
|
||||
parser.add_argument('--version', action='version', version='tconnectsync %s' % __version__)
|
||||
parser.add_argument('--pretend', dest='pretend', action='store_const', const=True, default=False, help='Pretend mode: do not upload any data to Nightscout.')
|
||||
parser.add_argument('-v', '--verbose', dest='verbose', action='store_const', const=True, default=False, help='Verbose mode: show extra logging details')
|
||||
parser.add_argument('--start-date', dest='start_date', type=str, default=None, help='The oldest date to process data from. Must be specified with --end-date.')
|
||||
parser.add_argument('--end-date', dest='end_date', type=str, default=None, help='The newest date to process data until (inclusive). Must be specified with --start-date.')
|
||||
parser.add_argument('--days', dest='days', type=int, default=1, help='The number of days of t:connect data to read in. Cannot be used with --from-date and --until-date.')
|
||||
parser.add_argument('--auto-update', dest='auto_update', action='store_const', const=True, default=False, help='If set, continuously checks for updates from t:connect and syncs with Nightscout.')
|
||||
parser.add_argument('--check-login', dest='check_login', action='store_const', const=True, default=False, help='If set, checks that the provided t:connect credentials can be used to log in.')
|
||||
|
||||
return parser.parse_args(*args, **kwargs)
|
||||
|
||||
def main(*args, **kwargs):
|
||||
args = parse_args(*args, **kwargs)
|
||||
|
||||
if args.verbose:
|
||||
logging.basicConfig(
|
||||
level=logging.DEBUG,
|
||||
format='%(asctime)s %(levelname)-8s %(message)s',
|
||||
datefmt='%Y-%m-%d %H:%M:%S')
|
||||
logging.root.debug("Set logging level to DEBUG")
|
||||
else:
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s %(levelname)-8s %(message)s',
|
||||
datefmt='%Y-%m-%d %H:%M:%S')
|
||||
|
||||
if args.auto_update and (args.start_date or args.end_date):
|
||||
raise Exception('Auto-update cannot be used with start/end date')
|
||||
|
||||
if args.start_date and args.end_date:
|
||||
time_start = arrow.get(args.start_date)
|
||||
time_end = arrow.get(args.end_date)
|
||||
else:
|
||||
time_end = datetime.datetime.now()
|
||||
time_start = time_end - datetime.timedelta(days=args.days)
|
||||
|
||||
if time_end < time_start:
|
||||
raise Exception('time_start must be before time_end')
|
||||
|
||||
tconnect = TConnectApi(TCONNECT_EMAIL, TCONNECT_PASSWORD)
|
||||
|
||||
nightscout = NightscoutApi(NS_URL, NS_SECRET)
|
||||
|
||||
if args.check_login:
|
||||
return check_login(tconnect, time_start, time_end)
|
||||
|
||||
if args.auto_update:
|
||||
print("Starting auto-update between", time_start, "and", time_end, "(PRETEND)" if args.pretend else "")
|
||||
process_auto_update(tconnect, nightscout, time_start, time_end, args.pretend)
|
||||
else:
|
||||
print("Processing data between", time_start, "and", time_end, "(PRETEND)" if args.pretend else "")
|
||||
added = process_time_range(tconnect, nightscout, time_start, time_end, args.pretend)
|
||||
print("Added", added, "items")
|
||||
|
||||
|
||||
@@ -69,6 +69,7 @@ class AndroidApi:
|
||||
self.refreshToken = j["refreshToken"]
|
||||
self.refreshTokenExpiresAt = j["refreshTokenExpiresAt"]
|
||||
self.userId = j["user"]["id"]
|
||||
self.patientObjectId = j["user"]["patientObjectId"]
|
||||
|
||||
logger.info("Logged in to AndroidApi successfully (expiration: %s, %s)" % (self.accessTokenExpiresAt, timeago(self.accessTokenExpiresAt)))
|
||||
|
||||
|
||||
@@ -126,10 +126,3 @@ class ControlIQApi:
|
||||
"startDate": startDate,
|
||||
"endDate": endDate
|
||||
})
|
||||
|
||||
"""
|
||||
Returns active account features, including the date when ControlIQ was enabled.
|
||||
[{"serialNumber": "11111111", "features": {"controlIQ": {"feature": 1, "dateTimeFirstDetected": "YYYY-MM-DD:THH:MM:SS", "unixTimestamp": 1111111111}}}]
|
||||
"""
|
||||
def pumpfeatures(self):
|
||||
return self.get('pumpfeatures/users/%s' % self.userGuid, {})
|
||||
|
||||
+1
-29
@@ -12,7 +12,6 @@ class WS2Api:
|
||||
BASE_URL = 'https://tconnectws2.tandemdiabetes.com/'
|
||||
|
||||
MAX_RETRIES = 2
|
||||
SLEEP_SECONDS_INCREMENT = 60
|
||||
|
||||
userGuid = None
|
||||
|
||||
@@ -61,15 +60,6 @@ class WS2Api:
|
||||
return data
|
||||
|
||||
|
||||
"""
|
||||
Returns information on therapy, displayed in the therapy timeline on the
|
||||
t:connect website.
|
||||
Contains BG reading (CGM), IOB, basal, and bolus data.
|
||||
|
||||
Basal data does NOT appear for the specified time range if using Control-IQ.
|
||||
The ControlIQ API endpoints must be used for basal data instead.
|
||||
However, all other fields are still accessed via this endpoint.
|
||||
"""
|
||||
def therapy_timeline_csv(self, start=None, end=None, tries=0):
|
||||
startDate = parse_date(start)
|
||||
endDate = parse_date(end)
|
||||
@@ -80,7 +70,7 @@ class WS2Api:
|
||||
# This seems to occur as some kind of soft rate-limit.
|
||||
logger.warning("Received ApiException in therapy_timeline_csv: (retry count %d) %s" % (tries, e))
|
||||
if e.status_code == 500:
|
||||
sleep_seconds = (tries+1) * self.SLEEP_SECONDS_INCREMENT
|
||||
sleep_seconds = (tries+1) * 60
|
||||
logger.error("Retrying in %d seconds after HTTP 500 in therapy_timeline_csv (retry count %d): %s" % (sleep_seconds, tries, e))
|
||||
time.sleep(sleep_seconds)
|
||||
if tries < self.MAX_RETRIES:
|
||||
@@ -113,24 +103,6 @@ class WS2Api:
|
||||
"basalData": self._csv_to_dict(basalData),
|
||||
"bolusData": self._csv_to_dict(bolusData)
|
||||
}
|
||||
|
||||
"""
|
||||
Returns information on basal suspension. The filterbasal option only returns site/cartridge changes.
|
||||
SuspendReason values are:
|
||||
- "site-cart"
|
||||
- "basal-profile"
|
||||
- "manual"
|
||||
- "previous"
|
||||
- "alarm"
|
||||
|
||||
{"BasalSuspension":[{"EventDateTime":"/Date(EPOCH_MS-0000)/", "SuspendReason": "reason"}]}
|
||||
"""
|
||||
def basalsuspension(self, start=None, end=None, filterbasal=False):
|
||||
startDate = parse_date(start)
|
||||
endDate = parse_date(end)
|
||||
arg = "filterbasal/1" if filterbasal else ""
|
||||
|
||||
return self.get_jsonp('basalsuspension/%s/%s/%s/%s' % (self.userGuid, startDate, endDate, arg))
|
||||
|
||||
"""
|
||||
Returns info on BasalIQ in JSONP format.
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import time
|
||||
import logging
|
||||
import sys
|
||||
|
||||
from .process import process_time_range
|
||||
from .secret import (
|
||||
@@ -55,19 +54,18 @@ def process_auto_update(tconnect, nightscout, time_start, time_end, pretend):
|
||||
logger.info('No new reported t:connect data. (last event index: %s)' % last_event['maxPumpEventIndex'])
|
||||
now = time.time()
|
||||
|
||||
if last_event_time and (now - last_event_time) >= 60 * AUTOUPDATE_FAILURE_MINUTES:
|
||||
logger.error(AutoupdateFailureException("No new data event indexes have been detected for over %d minutes. " % AUTOUPDATE_FAILURE_MINUTES +
|
||||
"The t:connect app might no longer be functioning."))
|
||||
if (now - last_event_time) >= 60 * AUTOUPDATE_FAILURE_MINUTES:
|
||||
logger.error("No new data event indexes have been detected for over %d minutes. " % AUTOUPDATE_FAILURE_MINUTES +
|
||||
"The t:connect app might no longer be functioning.")
|
||||
|
||||
if AUTOUPDATE_RESTART_ON_FAILURE:
|
||||
sys.exit(1)
|
||||
|
||||
elif last_process_time_range and (now - last_process_time_range) >= 60 * AUTOUPDATE_FAILURE_MINUTES:
|
||||
logger.error(AutoupdateFailureException("No new data has been found via the API for over %d minutes. " % AUTOUPDATE_FAILURE_MINUTES +
|
||||
"tconnectsync might not be functioning properly."))
|
||||
raise AutoupdateFailureException
|
||||
elif (now - last_process_time_range) >= 60 * AUTOUPDATE_FAILURE_MINUTES:
|
||||
logger.error("No new data has been found via the API for over %d minutes. " % AUTOUPDATE_FAILURE_MINUTES +
|
||||
"tconnectsync might not be functioning properly.")
|
||||
|
||||
if AUTOUPDATE_RESTART_ON_FAILURE:
|
||||
sys.exit(1)
|
||||
raise AutoupdateFailureException
|
||||
|
||||
|
||||
if len(time_diffs) > 2:
|
||||
|
||||
@@ -36,7 +36,7 @@ class NightscoutEntry:
|
||||
def iob(iob, created_at):
|
||||
return {
|
||||
"activityType": IOB_ACTIVITYTYPE,
|
||||
"iob": float(iob),
|
||||
"iob": iob,
|
||||
"created_at": created_at,
|
||||
"enteredBy": ENTERED_BY
|
||||
}
|
||||
@@ -85,20 +85,16 @@ class TConnectEntry:
|
||||
@staticmethod
|
||||
def parse_bolus_entry(data):
|
||||
# All DateTime's are stored in the user's timezone.
|
||||
def is_complete(s):
|
||||
return s and int(s) == 1
|
||||
|
||||
complete = is_complete(data["ExtendedBolusIsComplete"]) or is_complete(data["BolusIsComplete"])
|
||||
complete = (data["ExtendedBolusIsComplete"] or data["BolusIsComplete"])
|
||||
extended_bolus = ("extended" in data["Description"].lower())
|
||||
|
||||
return {
|
||||
"description": data["Description"],
|
||||
"complete": "1" if complete else "",
|
||||
"completion": data["CompletionStatusDesc"] if not extended_bolus else data["BolexCompletionStatusDesc"],
|
||||
"request_time": TConnectEntry._datetime_parse(data["RequestDateTime"]).format() if not extended_bolus else None,
|
||||
"completion_time": TConnectEntry._datetime_parse(data["CompletionDateTime"]).format() if not extended_bolus else None,
|
||||
"request_time": TConnectEntry._datetime_parse(data["RequestDateTime"]).format() if complete and not extended_bolus else None,
|
||||
"completion_time": TConnectEntry._datetime_parse(data["CompletionDateTime"]).format() if complete and not extended_bolus else None,
|
||||
"insulin": data["InsulinDelivered"],
|
||||
"requested_insulin": data["ActualTotalBolusRequested"],
|
||||
"carbs": data["CarbSize"],
|
||||
"user_override": data["UserOverride"],
|
||||
"extended_bolus": "1" if extended_bolus else "",
|
||||
|
||||
@@ -74,5 +74,5 @@ def process_time_range(tconnect, nightscout, time_start, time_end, pretend):
|
||||
iobEvents = process_iob_events(iobData)
|
||||
added += ns_write_iob_events(nightscout, iobEvents, pretend=pretend)
|
||||
|
||||
logger.info("Wrote %d events to Nightscout this process cycle" % added)
|
||||
logger.info("Wrote %d events to Nightscout this process cycle")
|
||||
return added
|
||||
@@ -20,7 +20,7 @@ def process_bolus_events(bolusdata):
|
||||
if parsed["completion"] != "Completed":
|
||||
if parsed["insulin"] and float(parsed["insulin"]) > 0:
|
||||
# Count non-completed bolus if any insulin was delivered (vs. the amount of insulin requested)
|
||||
parsed["description"] += " (%s: requested %s units)" % (parsed["completion"], parsed["requested_insulin"])
|
||||
parsed["description"] += " (%s)" % parsed["completion"]
|
||||
else:
|
||||
logger.warning("Skipping non-completed bolus data (was a bolus in progress?): %s parsed: %s" % (b, parsed))
|
||||
continue
|
||||
@@ -43,8 +43,7 @@ def ns_write_bolus_events(nightscout, bolusEvents, pretend=False):
|
||||
|
||||
add_count = 0
|
||||
for event in bolusEvents:
|
||||
created_at = event["completion_time"] if not event["extended_bolus"] else event["bolex_start_time"]
|
||||
if last_upload_time and arrow.get(created_at) <= last_upload_time:
|
||||
if last_upload_time and arrow.get(event["completion_time"]) <= last_upload_time:
|
||||
if pretend:
|
||||
logger.info("Skipping basal event before last upload time: %s" % event)
|
||||
continue
|
||||
@@ -52,7 +51,7 @@ def ns_write_bolus_events(nightscout, bolusEvents, pretend=False):
|
||||
entry = NightscoutEntry.bolus(
|
||||
bolus=event["insulin"],
|
||||
carbs=event["carbs"],
|
||||
created_at=created_at,
|
||||
created_at=event["completion_time"] if not event["extended_bolus"] else event["bolex_start_time"],
|
||||
notes="{}{}{}".format(event["description"], " (Override)" if event["user_override"] == "1" else "", " (Extended)" if event["extended_bolus"] == "1" else "")
|
||||
)
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@ class ControlIQApi(tconnectsync.api.controliq.ControlIQApi):
|
||||
class WS2Api(tconnectsync.api.ws2.WS2Api):
|
||||
def __init__(self):
|
||||
self.BASE_URL = 'invalid://'
|
||||
self.SLEEP_SECONDS_INCREMENT = 0.01
|
||||
|
||||
def get(self, endpoint, query):
|
||||
raise NotImplementedError
|
||||
|
||||
@@ -8,14 +8,14 @@ class NightscoutApi(tconnectsync.nightscout.NightscoutApi):
|
||||
self.secret = 'invalid'
|
||||
|
||||
self.uploaded_entries = collections.defaultdict(list)
|
||||
self.deleted_entries = []
|
||||
self.deleted_entries = collections.defaultdict(list)
|
||||
self.put_entries = collections.defaultdict(list)
|
||||
|
||||
def upload_entry(self, ns_format, entity='treatments'):
|
||||
self.uploaded_entries[entity].append(ns_format)
|
||||
|
||||
def delete_entry(self, ns_path):
|
||||
self.deleted_entries.append(ns_path)
|
||||
def delete_entry(self, ns_format, entity):
|
||||
self.deleted_entries[entity].append(ns_format)
|
||||
|
||||
def put_entry(self, ns_format, entity):
|
||||
self.put_entries[entity].append(ns_format)
|
||||
|
||||
@@ -33,83 +33,6 @@ class TestTConnectEntryBasal(unittest.TestCase):
|
||||
}
|
||||
)
|
||||
|
||||
class TestTConnectEntrySuspension(unittest.TestCase):
|
||||
def test_parse_suspension_entry(self):
|
||||
self.assertEqual(
|
||||
TConnectEntry.parse_suspension_entry({
|
||||
"suspendReason": "control-iq",
|
||||
"continuation": None,
|
||||
"x": 1615879821
|
||||
}),
|
||||
{
|
||||
"time": "2021-03-16 00:30:21-04:00",
|
||||
"continuation": None,
|
||||
"suspendReason": "control-iq"
|
||||
}
|
||||
)
|
||||
self.assertEqual(
|
||||
TConnectEntry.parse_suspension_entry({
|
||||
"suspendReason": "control-iq",
|
||||
"continuation": "previous",
|
||||
"x": 1634022000
|
||||
}),
|
||||
{
|
||||
"time": "2021-10-12 00:00:00-04:00",
|
||||
"continuation": "previous",
|
||||
"suspendReason": "control-iq"
|
||||
}
|
||||
)
|
||||
|
||||
class TestTConnectEntryCGM(unittest.TestCase):
|
||||
def test_parse_cgm_entry(self):
|
||||
self.assertEqual(
|
||||
TConnectEntry.parse_cgm_entry({
|
||||
"DeviceType": "t:slim X2 Insulin Pump",
|
||||
"SerialNumber": "11111111",
|
||||
"Description": "EGV",
|
||||
"EventDateTime": "2021-10-12T00:01:12",
|
||||
"Readings (CGM / BGM)": "131"
|
||||
}),
|
||||
{
|
||||
"time": "2021-10-12 00:01:12-04:00",
|
||||
"reading": "131",
|
||||
"reading_type": "EGV"
|
||||
}
|
||||
)
|
||||
|
||||
class TestTConnectEntryIOB(unittest.TestCase):
|
||||
entry1 = {
|
||||
"Type": "IOB",
|
||||
"EventID": "81",
|
||||
"EventDateTime": "2021-10-12T00:00:30",
|
||||
"IOB": "6.91"
|
||||
}
|
||||
def test_parse_iob_entry1(self):
|
||||
self.assertEqual(
|
||||
TConnectEntry.parse_iob_entry(self.entry1),
|
||||
{
|
||||
"time": "2021-10-12 00:00:30-04:00",
|
||||
"iob": "6.91",
|
||||
"event_id": "81"
|
||||
}
|
||||
)
|
||||
|
||||
entry2 = {
|
||||
"Type": "IOB",
|
||||
"EventID": "9",
|
||||
"EventDateTime": "2021-10-12T00:10:30",
|
||||
"IOB": "6.80"
|
||||
}
|
||||
def test_parse_iob_entry2(self):
|
||||
self.assertEqual(
|
||||
TConnectEntry.parse_iob_entry(self.entry2),
|
||||
{
|
||||
"time": "2021-10-12 00:10:30-04:00",
|
||||
"iob": "6.80",
|
||||
"event_id": "9"
|
||||
}
|
||||
)
|
||||
|
||||
class TestTConnectEntryBolus(unittest.TestCase):
|
||||
entryStdCorrection = {
|
||||
"Type": "Bolus",
|
||||
@@ -164,7 +87,6 @@ class TestTConnectEntryBolus(unittest.TestCase):
|
||||
"request_time": "2021-04-01 12:53:36-04:00",
|
||||
"completion_time": "2021-04-01 12:58:26-04:00",
|
||||
"insulin": "13.53",
|
||||
"requested_insulin": "13.53",
|
||||
"carbs": "75",
|
||||
"user_override": "0",
|
||||
"extended_bolus": "",
|
||||
@@ -225,7 +147,6 @@ class TestTConnectEntryBolus(unittest.TestCase):
|
||||
"request_time": "2021-04-01 23:21:58-04:00",
|
||||
"completion_time": "2021-04-01 23:23:17-04:00",
|
||||
"insulin": "1.25",
|
||||
"requested_insulin": "1.25",
|
||||
"carbs": "0",
|
||||
"user_override": "1",
|
||||
"extended_bolus": "",
|
||||
@@ -286,135 +207,14 @@ class TestTConnectEntryBolus(unittest.TestCase):
|
||||
"request_time": "2021-04-02 00:59:13-04:00",
|
||||
"completion_time": "2021-04-02 01:00:47-04:00",
|
||||
"insulin": "1.70",
|
||||
"requested_insulin": "1.70",
|
||||
"carbs": "0",
|
||||
"user_override": "0",
|
||||
"extended_bolus": "",
|
||||
"bolex_completion_time": None,
|
||||
"bolex_start_time": None
|
||||
})
|
||||
|
||||
entryStdIncompleteZero = {
|
||||
"Type": "Bolus",
|
||||
"Description": "Standard",
|
||||
"BG": "144",
|
||||
"IOB": "1.20",
|
||||
"BolusRequestID": "9694.000",
|
||||
"BolusCompletionID": "9694.000",
|
||||
"CompletionDateTime": "2021-10-08T15:47:02",
|
||||
"InsulinDelivered": "0.00",
|
||||
"FoodDelivered": "0.00",
|
||||
"CorrectionDelivered": "0.00",
|
||||
"CompletionStatusID": "0",
|
||||
"CompletionStatusDesc": "User Aborted",
|
||||
"BolusIsComplete": "0",
|
||||
"BolexCompletionID": "",
|
||||
"BolexSize": "",
|
||||
"BolexStartDateTime": "",
|
||||
"BolexCompletionDateTime": "",
|
||||
"BolexInsulinDelivered": "",
|
||||
"BolexIOB": "",
|
||||
"BolexCompletionStatusID": "",
|
||||
"BolexCompletionStatusDesc": "",
|
||||
"ExtendedBolusIsComplete": "",
|
||||
"EventDateTime": "2021-10-08T15:46:56",
|
||||
"RequestDateTime": "2021-10-08T15:46:56",
|
||||
"BolusType": "Carb",
|
||||
"BolusRequestOptions": "Standard",
|
||||
"StandardPercent": "100.00",
|
||||
"Duration": "0",
|
||||
"CarbSize": "0",
|
||||
"UserOverride": "1",
|
||||
"TargetBG": "110",
|
||||
"CorrectionFactor": "30.00",
|
||||
"FoodBolusSize": "0.00",
|
||||
"CorrectionBolusSize": "0.00",
|
||||
"ActualTotalBolusRequested": "0.50",
|
||||
"IsQuickBolus": "0",
|
||||
"EventHistoryReportEventDesc": "0",
|
||||
"EventHistoryReportDetails": "Food Bolus",
|
||||
"NoteID": "CF 1:30 - Carb Ratio 1:6 - Target BG 110 | Override: Pump calculated Bolus = 0.0 units",
|
||||
"IndexID": "0",
|
||||
"Note": "1669328"
|
||||
}
|
||||
def test_parse_bolus_entry_std_incomplete_zero(self):
|
||||
self.assertEqual(
|
||||
TConnectEntry.parse_bolus_entry(self.entryStdIncompleteZero),
|
||||
{
|
||||
"description": "Standard",
|
||||
"complete": "",
|
||||
"completion": "User Aborted",
|
||||
"request_time": "2021-10-08 15:46:56-04:00",
|
||||
"completion_time": "2021-10-08 15:47:02-04:00",
|
||||
"insulin": "0.00",
|
||||
"requested_insulin": "0.50",
|
||||
"carbs": "0",
|
||||
"user_override": "1",
|
||||
"extended_bolus": "",
|
||||
"bolex_completion_time": None,
|
||||
"bolex_start_time": None
|
||||
})
|
||||
|
||||
entryStdIncompletePartial = {
|
||||
"Type": "Bolus",
|
||||
"Description": "Standard/Correction",
|
||||
"BG": "189",
|
||||
"IOB": "",
|
||||
"BolusRequestID": "9261.000",
|
||||
"BolusCompletionID": "9261.000",
|
||||
"CompletionDateTime": "2021-09-06T12:24:47",
|
||||
"InsulinDelivered": "1.82",
|
||||
"FoodDelivered": "0.00",
|
||||
"CorrectionDelivered": "1.82",
|
||||
"CompletionStatusID": "1",
|
||||
"CompletionStatusDesc": "Terminated by Alarm",
|
||||
"BolusIsComplete": "0",
|
||||
"BolexCompletionID": "",
|
||||
"BolexSize": "",
|
||||
"BolexStartDateTime": "",
|
||||
"BolexCompletionDateTime": "",
|
||||
"BolexInsulinDelivered": "",
|
||||
"BolexIOB": "",
|
||||
"BolexCompletionStatusID": "",
|
||||
"BolexCompletionStatusDesc": "",
|
||||
"ExtendedBolusIsComplete": "",
|
||||
"EventDateTime": "2021-09-06T12:23:23",
|
||||
"RequestDateTime": "2021-09-06T12:23:23",
|
||||
"BolusType": "Carb",
|
||||
"BolusRequestOptions": "Standard/Correction",
|
||||
"StandardPercent": "100.00",
|
||||
"Duration": "0",
|
||||
"CarbSize": "0",
|
||||
"UserOverride": "0",
|
||||
"TargetBG": "110",
|
||||
"CorrectionFactor": "30.00",
|
||||
"FoodBolusSize": "0.00",
|
||||
"CorrectionBolusSize": "2.63",
|
||||
"ActualTotalBolusRequested": "2.63",
|
||||
"IsQuickBolus": "0",
|
||||
"EventHistoryReportEventDesc": "0",
|
||||
"EventHistoryReportDetails": "Correction & Food Bolus",
|
||||
"NoteID": "CF 1:30 - Carb Ratio 1:6 - Target BG 110",
|
||||
"IndexID": "0",
|
||||
"Note": "1589227"
|
||||
}
|
||||
def test_parse_bolus_entry_std_incomplete_partial(self):
|
||||
self.assertEqual(
|
||||
TConnectEntry.parse_bolus_entry(self.entryStdIncompletePartial),
|
||||
{
|
||||
"description": "Standard/Correction",
|
||||
"complete": "",
|
||||
"completion": "Terminated by Alarm",
|
||||
"request_time": "2021-09-06 12:23:23-04:00",
|
||||
"completion_time": "2021-09-06 12:24:47-04:00",
|
||||
"insulin": "1.82",
|
||||
"requested_insulin": "2.63",
|
||||
"carbs": "0",
|
||||
"user_override": "0",
|
||||
"extended_bolus": "",
|
||||
"bolex_completion_time": None,
|
||||
"bolex_start_time": None
|
||||
})
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -1,7 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import unittest
|
||||
import random
|
||||
|
||||
from tconnectsync.sync.bolus import process_bolus_events
|
||||
from tconnectsync.parser.tconnect import TConnectEntry
|
||||
@@ -13,72 +12,23 @@ class TestBolusSync(unittest.TestCase):
|
||||
@staticmethod
|
||||
def get_example_csv_bolus_events():
|
||||
return [
|
||||
TestTConnectEntryBolus.entryStdCorrection,
|
||||
TestTConnectEntryBolus.entryStd,
|
||||
TestTConnectEntryBolus.entryStdAutomatic,
|
||||
TestTConnectEntryBolus.entryStdIncompletePartial
|
||||
]
|
||||
|
||||
def test_process_bolus_events_standard(self):
|
||||
bolusData = [
|
||||
TestTConnectEntryBolus.entryStdCorrection,
|
||||
TestTConnectEntryBolus.entryStd,
|
||||
TestTConnectEntryBolus.entryStdAutomatic
|
||||
]
|
||||
|
||||
def test_process_bolus_events(self):
|
||||
bolusData = TestBolusSync.get_example_csv_bolus_events()
|
||||
|
||||
bolusEvents = process_bolus_events(bolusData)
|
||||
self.assertEqual(len(bolusEvents), len(bolusData))
|
||||
self.assertEqual(len(bolusEvents), 3)
|
||||
|
||||
self.assertListEqual(bolusEvents, [
|
||||
TConnectEntry.parse_bolus_entry(d) for d in bolusData
|
||||
TConnectEntry.parse_bolus_entry(bolusData[0]),
|
||||
TConnectEntry.parse_bolus_entry(bolusData[1]),
|
||||
TConnectEntry.parse_bolus_entry(bolusData[2])
|
||||
])
|
||||
|
||||
def test_process_bolus_events_update_partial_description(self):
|
||||
stdData = [
|
||||
TestTConnectEntryBolus.entryStdCorrection,
|
||||
TestTConnectEntryBolus.entryStd,
|
||||
TestTConnectEntryBolus.entryStdAutomatic
|
||||
]
|
||||
partialData = [
|
||||
TestTConnectEntryBolus.entryStdIncompletePartial
|
||||
]
|
||||
|
||||
bolusData = stdData + partialData
|
||||
|
||||
bolusEvents = process_bolus_events(bolusData)
|
||||
self.assertEqual(len(bolusEvents), len(bolusData))
|
||||
|
||||
partialEntries = [
|
||||
TConnectEntry.parse_bolus_entry(e) for e in partialData
|
||||
]
|
||||
|
||||
for e in partialEntries:
|
||||
e["description"] += " (%s: requested %s units)" % (e["completion"], e["requested_insulin"])
|
||||
|
||||
self.assertListEqual(bolusEvents, [
|
||||
TConnectEntry.parse_bolus_entry(d) for d in stdData
|
||||
] + partialEntries)
|
||||
|
||||
def test_process_bolus_events_skip_zero(self):
|
||||
stdData = [
|
||||
TestTConnectEntryBolus.entryStdCorrection,
|
||||
TestTConnectEntryBolus.entryStd,
|
||||
TestTConnectEntryBolus.entryStdAutomatic
|
||||
]
|
||||
zeroData = [
|
||||
TestTConnectEntryBolus.entryStdIncompleteZero
|
||||
]
|
||||
bolusData = stdData + zeroData
|
||||
|
||||
bolusEvents = process_bolus_events(bolusData)
|
||||
self.assertEqual(len(bolusEvents), len(stdData))
|
||||
|
||||
self.assertListEqual(bolusEvents, [
|
||||
TConnectEntry.parse_bolus_entry(d) for d in stdData
|
||||
])
|
||||
|
||||
for d in zeroData:
|
||||
self.assertNotIn(TConnectEntry.parse_bolus_entry(d), bolusEvents)
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -1,30 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import unittest
|
||||
|
||||
from tconnectsync.sync.iob import process_iob_events
|
||||
from tconnectsync.parser.tconnect import TConnectEntry
|
||||
|
||||
from ..parser.test_tconnect import TestTConnectEntryIOB
|
||||
|
||||
class TestIOBSync(unittest.TestCase):
|
||||
|
||||
@staticmethod
|
||||
def get_example_csv_iob_events():
|
||||
return [
|
||||
TestTConnectEntryIOB.entry1,
|
||||
TestTConnectEntryIOB.entry2,
|
||||
]
|
||||
|
||||
def test_process_iob_events(self):
|
||||
iobData = TestIOBSync.get_example_csv_iob_events()
|
||||
|
||||
iobEvents = process_iob_events(iobData)
|
||||
self.assertEqual(len(iobEvents), len(iobData))
|
||||
|
||||
self.assertListEqual(iobEvents, [
|
||||
TConnectEntry.parse_iob_entry(d) for d in iobData
|
||||
])
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
+7
-98
@@ -5,13 +5,12 @@ import datetime
|
||||
import pprint
|
||||
|
||||
from tconnectsync.process import process_time_range
|
||||
from tconnectsync.parser.nightscout import IOB_ACTIVITYTYPE, NightscoutEntry
|
||||
from tconnectsync.parser.nightscout import NightscoutEntry
|
||||
|
||||
from .api.fake import TConnectApi
|
||||
from .nightscout_fake import NightscoutApi
|
||||
from .sync.test_basal import TestBasalSync
|
||||
from .sync.test_bolus import TestBolusSync
|
||||
from .sync.test_iob import TestIOBSync
|
||||
|
||||
class TestProcessTimeRange(unittest.TestCase):
|
||||
maxDiff = None
|
||||
@@ -37,7 +36,6 @@ class TestProcessTimeRange(unittest.TestCase):
|
||||
def test_new_ciq_basal_data(self):
|
||||
tconnect = TConnectApi()
|
||||
|
||||
# datetimes are unused by the API fake
|
||||
start = datetime.datetime(2021, 4, 20, 12, 0)
|
||||
end = datetime.datetime(2021, 4, 21, 12, 0)
|
||||
|
||||
@@ -66,14 +64,13 @@ class TestProcessTimeRange(unittest.TestCase):
|
||||
NightscoutEntry.basal(0, 2693/60, "2021-03-16 00:30:21-04:00", reason="algorithmDelivery")
|
||||
]})
|
||||
self.assertDictEqual(nightscout.put_entries, {})
|
||||
self.assertListEqual(nightscout.deleted_entries, [])
|
||||
self.assertDictEqual(nightscout.deleted_entries, {})
|
||||
|
||||
|
||||
"""Two basal entries in Nightscout. Two new basal entries in tconnect."""
|
||||
def test_partial_ciq_basal_data(self):
|
||||
tconnect = TConnectApi()
|
||||
|
||||
# datetimes are unused by the API fake
|
||||
start = datetime.datetime(2021, 4, 20, 12, 0)
|
||||
end = datetime.datetime(2021, 4, 21, 12, 0)
|
||||
|
||||
@@ -107,7 +104,7 @@ class TestProcessTimeRange(unittest.TestCase):
|
||||
NightscoutEntry.basal(0, 2693/60, "2021-03-16 00:30:21-04:00", reason="algorithmDelivery")
|
||||
]})
|
||||
self.assertDictEqual(nightscout.put_entries, {})
|
||||
self.assertListEqual(nightscout.deleted_entries, [])
|
||||
self.assertDictEqual(nightscout.deleted_entries, {})
|
||||
|
||||
|
||||
"""
|
||||
@@ -116,7 +113,6 @@ class TestProcessTimeRange(unittest.TestCase):
|
||||
def test_with_updated_duration_ciq_basal_data(self):
|
||||
tconnect = TConnectApi()
|
||||
|
||||
# datetimes are unused by the API fake
|
||||
start = datetime.datetime(2021, 4, 20, 12, 0)
|
||||
end = datetime.datetime(2021, 4, 21, 12, 0)
|
||||
|
||||
@@ -159,23 +155,21 @@ class TestProcessTimeRange(unittest.TestCase):
|
||||
}
|
||||
]
|
||||
})
|
||||
self.assertListEqual(nightscout.deleted_entries, [])
|
||||
self.assertDictEqual(nightscout.deleted_entries, {})
|
||||
|
||||
"""No data in Nightscout. Uploads all bolus data from tconnect."""
|
||||
def test_new_ciq_bolus_data(self):
|
||||
tconnect = TConnectApi()
|
||||
|
||||
# datetimes are unused by the API fake
|
||||
start = datetime.datetime(2021, 4, 20, 12, 0)
|
||||
end = datetime.datetime(2021, 4, 21, 12, 0)
|
||||
|
||||
tconnect.controliq.therapy_timeline = self.stub_therapy_timeline
|
||||
|
||||
bolusData = TestBolusSync.get_example_csv_bolus_events()
|
||||
def fake_therapy_timeline_csv(time_start, time_end):
|
||||
return {
|
||||
**self.stub_therapy_timeline_csv(time_start, time_end),
|
||||
"bolusData": bolusData,
|
||||
"bolusData": TestBolusSync.get_example_csv_bolus_events(),
|
||||
}
|
||||
|
||||
tconnect.ws2.therapy_timeline_csv = fake_therapy_timeline_csv
|
||||
@@ -188,100 +182,15 @@ class TestProcessTimeRange(unittest.TestCase):
|
||||
process_time_range(tconnect, nightscout, start, end, pretend=False)
|
||||
|
||||
pprint.pprint(nightscout.uploaded_entries)
|
||||
self.assertEqual(len(nightscout.uploaded_entries["treatments"]), len(bolusData))
|
||||
self.assertEqual(len(nightscout.uploaded_entries["treatments"]), 3)
|
||||
self.assertDictEqual(dict(nightscout.uploaded_entries), {
|
||||
"treatments": [
|
||||
NightscoutEntry.bolus(13.53, 75, "2021-04-01 12:58:26-04:00", notes="Standard/Correction"),
|
||||
NightscoutEntry.bolus(1.25, 0, "2021-04-01 23:23:17-04:00", notes="Standard (Override)"),
|
||||
NightscoutEntry.bolus(1.7, 0, "2021-04-02 01:00:47-04:00", notes="Automatic Bolus/Correction"),
|
||||
NightscoutEntry.bolus(1.82, 0, "2021-09-06 12:24:47-04:00", notes="Standard/Correction (Terminated by Alarm: requested 2.63 units)"),
|
||||
]})
|
||||
self.assertDictEqual(nightscout.put_entries, {})
|
||||
self.assertListEqual(nightscout.deleted_entries, [])
|
||||
|
||||
"""No data in Nightscout. Uploads new iob reading from tconnect."""
|
||||
def test_new_ciq_iob_data(self):
|
||||
tconnect = TConnectApi()
|
||||
|
||||
# datetimes are unused by the API fake
|
||||
start = datetime.datetime(2021, 4, 20, 12, 0)
|
||||
end = datetime.datetime(2021, 4, 21, 12, 0)
|
||||
|
||||
tconnect.controliq.therapy_timeline = self.stub_therapy_timeline
|
||||
|
||||
iobData = TestIOBSync.get_example_csv_iob_events()
|
||||
def fake_therapy_timeline_csv(time_start, time_end):
|
||||
return {
|
||||
**self.stub_therapy_timeline_csv(time_start, time_end),
|
||||
"iobData": iobData,
|
||||
}
|
||||
|
||||
tconnect.ws2.therapy_timeline_csv = fake_therapy_timeline_csv
|
||||
|
||||
nightscout = NightscoutApi()
|
||||
|
||||
nightscout.last_uploaded_entry = self.stub_last_uploaded_entry
|
||||
nightscout.last_uploaded_activity = self.stub_last_uploaded_activity
|
||||
|
||||
process_time_range(tconnect, nightscout, start, end, pretend=False)
|
||||
|
||||
pprint.pprint(nightscout.uploaded_entries)
|
||||
self.assertEqual(len(nightscout.uploaded_entries["activity"]), 1)
|
||||
self.assertDictEqual(dict(nightscout.uploaded_entries), {
|
||||
"activity": [
|
||||
# the most recent IOB entry is added
|
||||
NightscoutEntry.iob(6.80, "2021-10-12 00:10:30-04:00")
|
||||
]})
|
||||
self.assertDictEqual(nightscout.put_entries, {})
|
||||
self.assertListEqual(nightscout.deleted_entries, [])
|
||||
|
||||
"""Existing IOB in Nightscout. Uploads new iob reading and deletes old IOB."""
|
||||
def test_updates_ciq_iob_data(self):
|
||||
tconnect = TConnectApi()
|
||||
|
||||
# datetimes are unused by the API fake
|
||||
start = datetime.datetime(2021, 4, 20, 12, 0)
|
||||
end = datetime.datetime(2021, 4, 21, 12, 0)
|
||||
|
||||
tconnect.controliq.therapy_timeline = self.stub_therapy_timeline
|
||||
|
||||
iobData = TestIOBSync.get_example_csv_iob_events()
|
||||
iobData[0]["created_at"] = start
|
||||
iobData[0]["_id"] = "sentinel_existing_iob_id"
|
||||
|
||||
def fake_therapy_timeline_csv(time_start, time_end):
|
||||
return {
|
||||
**self.stub_therapy_timeline_csv(time_start, time_end),
|
||||
"iobData": iobData,
|
||||
}
|
||||
|
||||
tconnect.ws2.therapy_timeline_csv = fake_therapy_timeline_csv
|
||||
|
||||
nightscout = NightscoutApi()
|
||||
|
||||
nightscout.last_uploaded_entry = self.stub_last_uploaded_entry
|
||||
|
||||
def fake_last_uploaded_activity(activityType):
|
||||
if activityType == IOB_ACTIVITYTYPE:
|
||||
return iobData[0]
|
||||
return self.stub_last_uploaded_activity(activityType)
|
||||
|
||||
nightscout.last_uploaded_activity = fake_last_uploaded_activity
|
||||
|
||||
process_time_range(tconnect, nightscout, start, end, pretend=False)
|
||||
|
||||
pprint.pprint(nightscout.uploaded_entries)
|
||||
self.assertEqual(len(nightscout.uploaded_entries["activity"]), 1)
|
||||
self.assertDictEqual(dict(nightscout.uploaded_entries), {
|
||||
"activity": [
|
||||
# the most recent IOB entry is added
|
||||
NightscoutEntry.iob(6.80, "2021-10-12 00:10:30-04:00")
|
||||
]})
|
||||
self.assertDictEqual(nightscout.put_entries, {})
|
||||
self.assertListEqual(nightscout.deleted_entries, [
|
||||
"activity/sentinel_existing_iob_id"
|
||||
])
|
||||
|
||||
self.assertDictEqual(nightscout.deleted_entries, {})
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user