Compare commits

..
18 Commits
Author SHA1 Message Date
James Woglom f672a2e199 bump to v0.7.2 2022-07-20 01:36:26 -04:00
James Woglom 52031d1d8a fix GETs with data 2022-07-20 01:32:32 -04:00
James Woglom 7139247d1c ensure consistent user agent across runtime 2022-07-20 01:17:05 -04:00
James Woglom f99c2b6119 HTTP 403 workarounds 2022-07-20 01:08:52 -04:00
James Woglom 0b3808d43d fix session ordering 2022-07-20 00:39:39 -04:00
James Woglom d4918984f8 requests version upgrade: explicitly pass data for get requests 2022-07-20 00:38:48 -04:00
James Woglom 123acaf163 fix requests proxies monkey-patch 2022-07-20 00:36:22 -04:00
James Woglom 898fb63af5 fix session creation ordering 2022-07-20 00:28:07 -04:00
James Woglom 46c4d60525 YAML was a mistake
https://github.com/actions/setup-python/issues/160
2022-07-20 00:07:52 -04:00
James Woglom e0d452bdea drop python 3.6 support in setup.cfg 2022-07-20 00:04:44 -04:00
James Woglom 31d2185154 drop python 3.6 support. no longer supported by requests 2022-07-20 00:04:14 -04:00
James Woglom d7cc6f957f test proxy 2022-07-19 23:57:49 -04:00
James Woglom ae2bf9a3a6 support proxy 2022-07-19 23:23:12 -04:00
James Woglom fe57bd6c14 bump to 0.7.1 for consistency between pip package and github/package metadata 2022-04-06 17:51:32 -04:00
James WoglomandGitHub 0872c756d2 Only run publish-docker on tags 2022-03-17 16:32:45 -04:00
James Woglom 7d8dabb3ef AndroidApi: match HTTP headers with app 2022-03-17 16:18:15 -04:00
James Woglom 798225c606 api: add user_agent to AndroidApi, move improperly scoped endpoint to tconnectapi 2022-03-17 16:07:57 -04:00
James WoglomandGitHub b08b9b8c76 trigger GitHub Actions on tag push 2022-02-27 00:35:53 -05:00
18 changed files with 430 additions and 202 deletions
+2 -3
View File
@@ -1,9 +1,8 @@
name: Publish Docker image
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
tags:
- 'v*'
workflow_dispatch:
jobs:
+1 -1
View File
@@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.6, 3.7, 3.8, 3.9]
python-version: ['3.7', '3.8', '3.9', '3.10']
steps:
- uses: actions/checkout@v2
+1
View File
@@ -14,6 +14,7 @@ arrow = "*"
lxml = "*"
python-dotenv = "*"
requests-mock = "*"
pysocks = "*"
[scripts]
tconnectsync = "python3 main.py"
Generated
+119 -99
View File
@@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
"sha256": "84349eb022c87848b29854316de1740d1223d444f06eb9dcbf7df92aa98475e2"
"sha256": "b5eacb40f06a4e1d7e3951b84c11699899f1f264f028d43281bdf217f6737956"
},
"pipfile-spec": 6,
"requires": {},
@@ -16,19 +16,19 @@
"default": {
"arrow": {
"hashes": [
"sha256:6b2914ef3997d1fd7b37a71ce9dd61a6e329d09e1c7b44f4d3099ca4a5c0933e",
"sha256:c2dde3c382d9f7e6922ce636bf0b318a7a853df40ecb383b29192e6c5cc82840"
"sha256:05caf1fd3d9a11a1135b2b6f09887421153b94558e5ef4d090b567b47173ac2b",
"sha256:d622c46ca681b5b3e3574fcb60a04e5cc81b9625112d5fb2b44220c36c892177"
],
"index": "pypi",
"version": "==1.2.1"
"version": "==1.2.2"
},
"beautifulsoup4": {
"hashes": [
"sha256:9a315ce70049920ea4572a4055bc4bd700c940521d36fc858205ad4fcde149bf",
"sha256:c23ad23c521d818955a4151a67d81580319d4bf548d3d49f4223ae041ff98891"
"sha256:58d5c3d29f5a36ffeb94f02f0d786cd53014cf9b3b3951d42e0080d8a9498d30",
"sha256:ad9aa55b65ef2808eb405f46cf74df7fcb7044d5cbc26487f96eb2ef2e436693"
],
"markers": "python_version >= '3.1'",
"version": "==4.10.0"
"markers": "python_full_version >= '3.6.0'",
"version": "==4.11.1"
},
"bs4": {
"hashes": [
@@ -39,92 +39,112 @@
},
"certifi": {
"hashes": [
"sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872",
"sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"
"sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d",
"sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412"
],
"version": "==2021.10.8"
"markers": "python_full_version >= '3.6.0'",
"version": "==2022.6.15"
},
"charset-normalizer": {
"hashes": [
"sha256:876d180e9d7432c5d1dfd4c5d26b72f099d503e8fcc0feb7532c9289be60fcbd",
"sha256:cb957888737fc0bbcd78e3df769addb41fd1ff8cf950dc9e7ad7793f1bf44455"
"sha256:5189b6f22b01957427f35b6a08d9a0bc45b46d3788ef5a92e978433c7a35f8a5",
"sha256:575e708016ff3a5e3681541cb9d79312c416835686d054a23accb873b254f413"
],
"markers": "python_version >= '3'",
"version": "==2.0.10"
"markers": "python_full_version >= '3.6.0'",
"version": "==2.1.0"
},
"idna": {
"hashes": [
"sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff",
"sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"
],
"markers": "python_version >= '3'",
"markers": "python_version >= '3.5'",
"version": "==3.3"
},
"lxml": {
"hashes": [
"sha256:0607ff0988ad7e173e5ddf7bf55ee65534bd18a5461183c33e8e41a59e89edf4",
"sha256:09b738360af8cb2da275998a8bf79517a71225b0de41ab47339c2beebfff025f",
"sha256:0a5f0e4747f31cff87d1eb32a6000bde1e603107f632ef4666be0dc065889c7a",
"sha256:0b5e96e25e70917b28a5391c2ed3ffc6156513d3db0e1476c5253fcd50f7a944",
"sha256:1104a8d47967a414a436007c52f533e933e5d52574cab407b1e49a4e9b5ddbd1",
"sha256:13dbb5c7e8f3b6a2cf6e10b0948cacb2f4c9eb05029fe31c60592d08ac63180d",
"sha256:2a906c3890da6a63224d551c2967413b8790a6357a80bf6b257c9a7978c2c42d",
"sha256:317bd63870b4d875af3c1be1b19202de34c32623609ec803b81c99193a788c1e",
"sha256:34c22eb8c819d59cec4444d9eebe2e38b95d3dcdafe08965853f8799fd71161d",
"sha256:36b16fecb10246e599f178dd74f313cbdc9f41c56e77d52100d1361eed24f51a",
"sha256:38d9759733aa04fb1697d717bfabbedb21398046bd07734be7cccc3d19ea8675",
"sha256:3e26ad9bc48d610bf6cc76c506b9e5ad9360ed7a945d9be3b5b2c8535a0145e3",
"sha256:41358bfd24425c1673f184d7c26c6ae91943fe51dfecc3603b5e08187b4bcc55",
"sha256:447d5009d6b5447b2f237395d0018901dcc673f7d9f82ba26c1b9f9c3b444b60",
"sha256:44f552e0da3c8ee3c28e2eb82b0b784200631687fc6a71277ea8ab0828780e7d",
"sha256:490712b91c65988012e866c411a40cc65b595929ececf75eeb4c79fcc3bc80a6",
"sha256:4c093c571bc3da9ebcd484e001ba18b8452903cd428c0bc926d9b0141bcb710e",
"sha256:50d3dba341f1e583265c1a808e897b4159208d814ab07530202b6036a4d86da5",
"sha256:534e946bce61fd162af02bad7bfd2daec1521b71d27238869c23a672146c34a5",
"sha256:585ea241ee4961dc18a95e2f5581dbc26285fcf330e007459688096f76be8c42",
"sha256:59e7da839a1238807226f7143c68a479dee09244d1b3cf8c134f2fce777d12d0",
"sha256:5b0f782f0e03555c55e37d93d7a57454efe7495dab33ba0ccd2dbe25fc50f05d",
"sha256:5bee1b0cbfdb87686a7fb0e46f1d8bd34d52d6932c0723a86de1cc532b1aa489",
"sha256:610807cea990fd545b1559466971649e69302c8a9472cefe1d6d48a1dee97440",
"sha256:6308062534323f0d3edb4e702a0e26a76ca9e0e23ff99be5d82750772df32a9e",
"sha256:67fa5f028e8a01e1d7944a9fb616d1d0510d5d38b0c41708310bd1bc45ae89f6",
"sha256:6a2ab9d089324d77bb81745b01f4aeffe4094306d939e92ba5e71e9a6b99b71e",
"sha256:6c198bfc169419c09b85ab10cb0f572744e686f40d1e7f4ed09061284fc1303f",
"sha256:6e56521538f19c4a6690f439fefed551f0b296bd785adc67c1777c348beb943d",
"sha256:6ec829058785d028f467be70cd195cd0aaf1a763e4d09822584ede8c9eaa4b03",
"sha256:718d7208b9c2d86aaf0294d9381a6acb0158b5ff0f3515902751404e318e02c9",
"sha256:735e3b4ce9c0616e85f302f109bdc6e425ba1670a73f962c9f6b98a6d51b77c9",
"sha256:772057fba283c095db8c8ecde4634717a35c47061d24f889468dc67190327bcd",
"sha256:7b5e2acefd33c259c4a2e157119c4373c8773cf6793e225006a1649672ab47a6",
"sha256:82d16a64236970cb93c8d63ad18c5b9f138a704331e4b916b2737ddfad14e0c4",
"sha256:87c1b0496e8c87ec9db5383e30042357b4839b46c2d556abd49ec770ce2ad868",
"sha256:8e54945dd2eeb50925500957c7c579df3cd07c29db7810b83cf30495d79af267",
"sha256:9393a05b126a7e187f3e38758255e0edf948a65b22c377414002d488221fdaa2",
"sha256:9fbc0dee7ff5f15c4428775e6fa3ed20003140560ffa22b88326669d53b3c0f4",
"sha256:a1613838aa6b89af4ba10a0f3a972836128801ed008078f8c1244e65958f1b24",
"sha256:a1bbc4efa99ed1310b5009ce7f3a1784698082ed2c1ef3895332f5df9b3b92c2",
"sha256:a555e06566c6dc167fbcd0ad507ff05fd9328502aefc963cb0a0547cfe7f00db",
"sha256:a58d78653ae422df6837dd4ca0036610b8cb4962b5cfdbd337b7b24de9e5f98a",
"sha256:a5edc58d631170de90e50adc2cc0248083541affef82f8cd93bea458e4d96db8",
"sha256:a5f623aeaa24f71fce3177d7fee875371345eb9102b355b882243e33e04b7175",
"sha256:adaab25be351fff0d8a691c4f09153647804d09a87a4e4ea2c3f9fe9e8651851",
"sha256:ade74f5e3a0fd17df5782896ddca7ddb998845a5f7cd4b0be771e1ffc3b9aa5b",
"sha256:b1d381f58fcc3e63fcc0ea4f0a38335163883267f77e4c6e22d7a30877218a0e",
"sha256:bf6005708fc2e2c89a083f258b97709559a95f9a7a03e59f805dd23c93bc3986",
"sha256:d546431636edb1d6a608b348dd58cc9841b81f4116745857b6cb9f8dadb2725f",
"sha256:d5618d49de6ba63fe4510bdada62d06a8acfca0b4b5c904956c777d28382b419",
"sha256:dfd0d464f3d86a1460683cd742306d1138b4e99b79094f4e07e1ca85ee267fe7",
"sha256:e18281a7d80d76b66a9f9e68a98cf7e1d153182772400d9a9ce855264d7d0ce7",
"sha256:e410cf3a2272d0a85526d700782a2fa92c1e304fdcc519ba74ac80b8297adf36",
"sha256:e662c6266e3a275bdcb6bb049edc7cd77d0b0f7e119a53101d367c841afc66dc",
"sha256:ec9027d0beb785a35aa9951d14e06d48cfbf876d8ff67519403a2522b181943b",
"sha256:eed394099a7792834f0cb4a8f615319152b9d801444c1c9e1b1a2c36d2239f9e",
"sha256:f76dbe44e31abf516114f6347a46fa4e7c2e8bceaa4b6f7ee3a0a03c8eba3c17",
"sha256:fc15874816b9320581133ddc2096b644582ab870cf6a6ed63684433e7af4b0d3",
"sha256:fc9fb11b65e7bc49f7f75aaba1b700f7181d95d4e151cf2f24d51bfd14410b77"
"sha256:04da965dfebb5dac2619cb90fcf93efdb35b3c6994fea58a157a834f2f94b318",
"sha256:0538747a9d7827ce3e16a8fdd201a99e661c7dee3c96c885d8ecba3c35d1032c",
"sha256:0645e934e940107e2fdbe7c5b6fb8ec6232444260752598bc4d09511bd056c0b",
"sha256:079b68f197c796e42aa80b1f739f058dcee796dc725cc9a1be0cdb08fc45b000",
"sha256:0f3f0059891d3254c7b5fb935330d6db38d6519ecd238ca4fce93c234b4a0f73",
"sha256:10d2017f9150248563bb579cd0d07c61c58da85c922b780060dcc9a3aa9f432d",
"sha256:1355755b62c28950f9ce123c7a41460ed9743c699905cbe664a5bcc5c9c7c7fb",
"sha256:13c90064b224e10c14dcdf8086688d3f0e612db53766e7478d7754703295c7c8",
"sha256:1423631e3d51008871299525b541413c9b6c6423593e89f9c4cfbe8460afc0a2",
"sha256:1436cf0063bba7888e43f1ba8d58824f085410ea2025befe81150aceb123e345",
"sha256:1a7c59c6ffd6ef5db362b798f350e24ab2cfa5700d53ac6681918f314a4d3b94",
"sha256:1e1cf47774373777936c5aabad489fef7b1c087dcd1f426b621fda9dcc12994e",
"sha256:206a51077773c6c5d2ce1991327cda719063a47adc02bd703c56a662cdb6c58b",
"sha256:21fb3d24ab430fc538a96e9fbb9b150029914805d551deeac7d7822f64631dfc",
"sha256:27e590352c76156f50f538dbcebd1925317a0f70540f7dc8c97d2931c595783a",
"sha256:287605bede6bd36e930577c5925fcea17cb30453d96a7b4c63c14a257118dbb9",
"sha256:2aaf6a0a6465d39b5ca69688fce82d20088c1838534982996ec46633dc7ad6cc",
"sha256:32a73c53783becdb7eaf75a2a1525ea8e49379fb7248c3eeefb9412123536387",
"sha256:41fb58868b816c202e8881fd0f179a4644ce6e7cbbb248ef0283a34b73ec73bb",
"sha256:4780677767dd52b99f0af1f123bc2c22873d30b474aa0e2fc3fe5e02217687c7",
"sha256:4878e667ebabe9b65e785ac8da4d48886fe81193a84bbe49f12acff8f7a383a4",
"sha256:487c8e61d7acc50b8be82bda8c8d21d20e133c3cbf41bd8ad7eb1aaeb3f07c97",
"sha256:4beea0f31491bc086991b97517b9683e5cfb369205dac0148ef685ac12a20a67",
"sha256:4cfbe42c686f33944e12f45a27d25a492cc0e43e1dc1da5d6a87cbcaf2e95627",
"sha256:4d5bae0a37af799207140652a700f21a85946f107a199bcb06720b13a4f1f0b7",
"sha256:4e285b5f2bf321fc0857b491b5028c5f276ec0c873b985d58d7748ece1d770dd",
"sha256:57e4d637258703d14171b54203fd6822fda218c6c2658a7d30816b10995f29f3",
"sha256:5974895115737a74a00b321e339b9c3f45c20275d226398ae79ac008d908bff7",
"sha256:5ef87fca280fb15342726bd5f980f6faf8b84a5287fcc2d4962ea8af88b35130",
"sha256:603a464c2e67d8a546ddaa206d98e3246e5db05594b97db844c2f0a1af37cf5b",
"sha256:6653071f4f9bac46fbc30f3c7838b0e9063ee335908c5d61fb7a4a86c8fd2036",
"sha256:6ca2264f341dd81e41f3fffecec6e446aa2121e0b8d026fb5130e02de1402785",
"sha256:6d279033bf614953c3fc4a0aa9ac33a21e8044ca72d4fa8b9273fe75359d5cca",
"sha256:6d949f53ad4fc7cf02c44d6678e7ff05ec5f5552b235b9e136bd52e9bf730b91",
"sha256:6daa662aba22ef3258934105be2dd9afa5bb45748f4f702a3b39a5bf53a1f4dc",
"sha256:6eafc048ea3f1b3c136c71a86db393be36b5b3d9c87b1c25204e7d397cee9536",
"sha256:830c88747dce8a3e7525defa68afd742b4580df6aa2fdd6f0855481e3994d391",
"sha256:86e92728ef3fc842c50a5cb1d5ba2bc66db7da08a7af53fb3da79e202d1b2cd3",
"sha256:8caf4d16b31961e964c62194ea3e26a0e9561cdf72eecb1781458b67ec83423d",
"sha256:8d1a92d8e90b286d491e5626af53afef2ba04da33e82e30744795c71880eaa21",
"sha256:8f0a4d179c9a941eb80c3a63cdb495e539e064f8054230844dcf2fcb812b71d3",
"sha256:9232b09f5efee6a495a99ae6824881940d6447debe272ea400c02e3b68aad85d",
"sha256:927a9dd016d6033bc12e0bf5dee1dde140235fc8d0d51099353c76081c03dc29",
"sha256:93e414e3206779ef41e5ff2448067213febf260ba747fc65389a3ddaa3fb8715",
"sha256:98cafc618614d72b02185ac583c6f7796202062c41d2eeecdf07820bad3295ed",
"sha256:9c3a88d20e4fe4a2a4a84bf439a5ac9c9aba400b85244c63a1ab7088f85d9d25",
"sha256:9f36de4cd0c262dd9927886cc2305aa3f2210db437aa4fed3fb4940b8bf4592c",
"sha256:a60f90bba4c37962cbf210f0188ecca87daafdf60271f4c6948606e4dabf8785",
"sha256:a614e4afed58c14254e67862456d212c4dcceebab2eaa44d627c2ca04bf86837",
"sha256:ae06c1e4bc60ee076292e582a7512f304abdf6c70db59b56745cca1684f875a4",
"sha256:b122a188cd292c4d2fcd78d04f863b789ef43aa129b233d7c9004de08693728b",
"sha256:b570da8cd0012f4af9fa76a5635cd31f707473e65a5a335b186069d5c7121ff2",
"sha256:bcaa1c495ce623966d9fc8a187da80082334236a2a1c7e141763ffaf7a405067",
"sha256:bd34f6d1810d9354dc7e35158aa6cc33456be7706df4420819af6ed966e85448",
"sha256:be9eb06489bc975c38706902cbc6888f39e946b81383abc2838d186f0e8b6a9d",
"sha256:c4b2e0559b68455c085fb0f6178e9752c4be3bba104d6e881eb5573b399d1eb2",
"sha256:c62e8dd9754b7debda0c5ba59d34509c4688f853588d75b53c3791983faa96fc",
"sha256:c852b1530083a620cb0de5f3cd6826f19862bafeaf77586f1aef326e49d95f0c",
"sha256:d9fc0bf3ff86c17348dfc5d322f627d78273eba545db865c3cd14b3f19e57fa5",
"sha256:dad7b164905d3e534883281c050180afcf1e230c3d4a54e8038aa5cfcf312b84",
"sha256:e5f66bdf0976ec667fc4594d2812a00b07ed14d1b44259d19a41ae3fff99f2b8",
"sha256:e8f0c9d65da595cfe91713bc1222af9ecabd37971762cb830dea2fc3b3bb2acf",
"sha256:edffbe3c510d8f4bf8640e02ca019e48a9b72357318383ca60e3330c23aaffc7",
"sha256:eea5d6443b093e1545ad0210e6cf27f920482bfcf5c77cdc8596aec73523bb7e",
"sha256:ef72013e20dd5ba86a8ae1aed7f56f31d3374189aa8b433e7b12ad182c0d2dfb",
"sha256:f05251bbc2145349b8d0b77c0d4e5f3b228418807b1ee27cefb11f69ed3d233b",
"sha256:f1be258c4d3dc609e654a1dc59d37b17d7fef05df912c01fc2e15eb43a9735f3",
"sha256:f9ced82717c7ec65a67667bb05865ffe38af0e835cdd78728f1209c8fffe0cad",
"sha256:fe17d10b97fdf58155f858606bddb4e037b805a60ae023c009f760d8361a4eb8",
"sha256:fe749b052bb7233fe5d072fcb549221a8cb1a16725c47c37e42b0b9cb3ff2c3f"
],
"index": "pypi",
"version": "==4.7.1"
"version": "==4.9.1"
},
"pysocks": {
"hashes": [
"sha256:08e69f092cc6dbe92a0fdd16eeb9b9ffbc13cadfe5ca4c7bd92ffb078b293299",
"sha256:2725bd0a9925919b9b51739eea5f9e2bae91e83288108a9ad338b2e3a4435ee5",
"sha256:3f8804571ebe159c380ac6de37643bb4685970655d3bba243530d6558b799aa0"
],
"index": "pypi",
"version": "==1.7.1"
},
"python-dateutil": {
"hashes": [
@@ -136,19 +156,19 @@
},
"python-dotenv": {
"hashes": [
"sha256:32b2bdc1873fd3a3c346da1c6db83d0053c3c62f28f1f38516070c4c8971b1d3",
"sha256:a5de49a31e953b45ff2d2fd434bbc2670e8db5273606c1e737cc6b93eff3655f"
"sha256:b7e3b04a59693c42c36f9ab1cc2acc46fa5df8c78e178fc33a8d4cd05c8d498f",
"sha256:d92a187be61fe482e4fd675b6d52200e7be63a12b724abbf931a40ce4fa92938"
],
"index": "pypi",
"version": "==0.19.2"
"version": "==0.20.0"
},
"requests": {
"hashes": [
"sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61",
"sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"
"sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983",
"sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"
],
"index": "pypi",
"version": "==2.27.1"
"version": "==2.28.1"
},
"requests-mock": {
"hashes": [
@@ -168,23 +188,23 @@
},
"soupsieve": {
"hashes": [
"sha256:1a3cca2617c6b38c0343ed661b1fa5de5637f257d4fe22bd9f1338010a1efefb",
"sha256:b8d49b1cd4f037c7082a9683dfa1801aa2597fb11c3a1155b7a5b94829b4f1f9"
"sha256:3b2503d3c7084a42b1ebd08116e5f81aadfaea95863628c80a3b774a11b7c759",
"sha256:fc53893b3da2c33de295667a0e19f078c14bf86544af307354de5fcf12a3f30d"
],
"markers": "python_version >= '3.6'",
"version": "==2.3.1"
"markers": "python_full_version >= '3.6.0'",
"version": "==2.3.2.post1"
},
"tconnectsync": {
"path": ".",
"version": "==0.6.4"
"version": "==0.7.1"
},
"urllib3": {
"hashes": [
"sha256:000ca7f471a233c2251c6c7023ee85305721bfdf18621ebff4fd17a8653427ed",
"sha256:0e7c33d9a63e7ddfcb86780aac87befc2fbddf46c58dbb487e0855f7ceec283c"
"sha256:8298d6d56d39be0e3bc13c1c97d133f9b45d797169a0e11cdd0e0489d786f7ec",
"sha256:879ba4d1e89654d9769ce13121e0f94310ea32e8d2f8cf587b77c08bbcdb30d6"
],
"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.8"
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5' and python_version < '4'",
"version": "==1.26.10"
}
},
"develop": {
@@ -213,11 +233,11 @@
},
"prompt-toolkit": {
"hashes": [
"sha256:1bb05628c7d87b645974a1bad3f17612be0c29fa39af9f7688030163f680bad6",
"sha256:e56f2ff799bacecd3e88165b1e2f5ebf9bcd59e80e06d395fa0cc4b8bd7bb506"
"sha256:859b283c50bde45f5f97829f77a4674d1c1fcd88539364f1b28a37805cfd89c0",
"sha256:d8916d3f62a7b67ab353a952ce4ced6a1d2587dfe9ef8ebc30dd7c386751f289"
],
"markers": "python_full_version >= '3.6.2'",
"version": "==3.0.24"
"version": "==3.0.30"
},
"ptpython": {
"hashes": [
@@ -229,11 +249,11 @@
},
"pygments": {
"hashes": [
"sha256:44238f1b60a76d78fc8ca0528ee429702aae011c265fe6a8dd8b63049ae41c65",
"sha256:4e426f72023d88d03b2fa258de560726ce890ff3b630f88c21cbb8b2503b8c6a"
"sha256:5eb116118f9612ff1ee89ac96437bb6b49e8f04d8a13b514ba26f620208e26eb",
"sha256:dc9c10fb40944260f6ed4c688ece0cd2048414940f1cea51b8b226318411c519"
],
"markers": "python_version >= '3.5'",
"version": "==2.11.2"
"markers": "python_version >= '3.6'",
"version": "==2.12.0"
},
"wcwidth": {
"hashes": [
+2 -2
View File
@@ -1,6 +1,6 @@
[metadata]
name = tconnectsync
version = 0.6.6
version = 0.7.2
author = James Woglom
author_email = j@wogloms.net
description = Syncs Tandem t:connect pump data to Nightscout for the t:slim X2
@@ -18,7 +18,7 @@ classifiers =
package_dir =
= .
packages = find:
python_requires = >=3.6
python_requires = >=3.7
install_requires =
requests
bs4
+18 -18
View File
@@ -11,7 +11,7 @@ import logging
from bs4 import BeautifulSoup
from ..util import timeago
from .common import ApiException, ApiLoginException, parse_date
from .common import ApiException, ApiLoginException, parse_date, base_session
logger = logging.getLogger(__name__)
@@ -29,6 +29,8 @@ class AndroidApi:
ANDROID_API_USERNAME = base64.b64decode('QzIzMzFDRDYtRDQ1MC00OTVFLTlDMTktNjcyMTUyMzBDODVD').decode()
ANDROID_API_PASSWORD = base64.b64decode('dHo0MzNLVzVRREM5VjdmIXo2QF4ybyZZNlNHR1lo').decode()
ANDROID_USER_AGENT = 'Dalvik/2.1.0 (Linux; U; Android 12; Pixel 4a Build/SP2A.220305.012)'
# These tokens are separate from the "standard" tdcservices API
accessToken = None
accessTokenExpiresAt = None
@@ -38,12 +40,13 @@ class AndroidApi:
patientObjectId = None
def __init__(self, email, password):
self.session = base_session()
self.login(email, password)
self._email = email
self._password = password
def login(self, email, password):
r = requests.post(
r = self.session.post(
self.BASE_URL + self.OAUTH_TOKEN_PATH,
{
'username': email,
@@ -51,7 +54,10 @@ class AndroidApi:
'grant_type': 'password',
'scope': self.OAUTH_SCOPES
},
headers={'Content-Type': 'application/x-www-form-urlencoded'},
headers={
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
'User-Agent': self.ANDROID_USER_AGENT
},
auth=requests.auth.HTTPBasicAuth(self.ANDROID_API_USERNAME, self.ANDROID_API_PASSWORD)
)
@@ -82,7 +88,11 @@ class AndroidApi:
return {'Authorization': 'Bearer %s' % self.accessToken}
def _get(self, endpoint, query={}, **kwargs):
r = requests.get(self.BASE_URL + endpoint, query, headers=self.api_headers(), **kwargs)
r = self.session.get(self.BASE_URL + endpoint, data=query, headers={
'User-Agent': self.ANDROID_USER_AGENT,
'Content-Type': 'application/json',
**self.api_headers()
}, **kwargs)
if r.status_code != 200:
raise ApiException(r.status_code, "Android API HTTP %s response: %s" % (str(r.status_code), r.text))
@@ -109,7 +119,7 @@ class AndroidApi:
def post(self, endpoint, query={}, **kwargs):
r = requests.post(self.BASE_URL + endpoint, query, headers=self.api_headers(), **kwargs)
r = self.session.post(self.BASE_URL + endpoint, query, headers=self.api_headers(), **kwargs)
if r.status_code != 200:
raise ApiException(r.status_code, "Internal API HTTP %s response: %s" % (str(r.status_code), r.text))
return r.json()
@@ -134,6 +144,9 @@ class AndroidApi:
# TODO: these methods are used in the web app, not the Android app,
# but support the same auth tokens and are on this domain. They should
# be moved to a new Api class.
# 3/17/2022: the API appears to be more stringently checking scopes,
# and some of these endpoints no longer work with the API token scoped
# to the Android app.
"""
Returns BG and pump threshold values.
@@ -154,16 +167,3 @@ class AndroidApi:
"""
def user_profile(self):
return self.get('cloud/usersettings/api/UserProfile?userId=%s' % self.userId)
"""
Returns therapy events, used by the webui Therapy Timeline.
{'event': [
{'type': 'Basal', 'basalRate': ...},
{'type': 'Bolus', 'standard': ...},
{'type': 'CGM', 'egv': ...}
]}
"""
def therapy_events(self, start_date=None, end_date=None):
startDate = parse_date(start_date)
endDate = parse_date(end_date)
return self.get('tconnect/therapyevents/api/TherapyEvents/%s/%s/false?userId=%s' % (startDate, endDate, self.userId))
+90 -1
View File
@@ -1,12 +1,101 @@
import datetime
import requests
import random
from tconnectsync import secret
def parse_date(date):
if type(date) == str:
return date
return (date or datetime.datetime.now()).strftime('%m-%d-%Y')
USER_AGENTS = [
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.0.0 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.54 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.64 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.115 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.61 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.1 Safari/605.1.15',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.3 Safari/605.1.15',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.4 Safari/605.1.15',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.5 Safari/605.1.15',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:100.0) Gecko/20100101 Firefox/100.0',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:101.0) Gecko/20100101 Firefox/101.0',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:102.0) Gecko/20100101 Firefox/102.0',
'Mozilla/5.0 (Windows NT 10.0; rv:91.0) Gecko/20100101 Firefox/91.0',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36 OPR/86.0.4363.64',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36 OPR/86.0.4363.70',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.75 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.0.0 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.54 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.64 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.64 Safari/537.36 Edg/101.0.1210.47',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.64 Safari/537.36 Edg/101.0.1210.53',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.67 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.67 Safari/537.36 OPR/87.0.4390.45',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.115 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.124 Safari/537.36 Edg/102.0.1245.41',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.124 Safari/537.36 Edg/102.0.1245.44',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.61 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.62 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.63 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.63 Safari/537.36 Edg/102.0.1245.30',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.63 Safari/537.36 Edg/102.0.1245.33',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.63 Safari/537.36 Edg/102.0.1245.39',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.84 Safari/537.36 OPR/85.0.4341.71',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:100.0) Gecko/20100101 Firefox/100.0',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:101.0) Gecko/20100101 Firefox/101.0',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:102.0) Gecko/20100101 Firefox/102.0',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:91.0) Gecko/20100101 Firefox/91.0',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:99.0) Gecko/20100101 Firefox/99.0',
'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36',
'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36',
'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:100.0) Gecko/20100101 Firefox/100.0',
'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:101.0) Gecko/20100101 Firefox/101.0',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.0.0 Safari/537.36',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.41 Safari/537.36',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.64 Safari/537.36',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.115 Safari/537.36',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.61 Safari/537.36',
'Mozilla/5.0 (X11; Linux x86_64; rv:100.0) Gecko/20100101 Firefox/100.0',
'Mozilla/5.0 (X11; Linux x86_64; rv:101.0) Gecko/20100101 Firefox/101.0',
'Mozilla/5.0 (X11; Linux x86_64; rv:91.0) Gecko/20100101 Firefox/91.0',
'Mozilla/5.0 (X11; Linux x86_64; rv:95.0) Gecko/20100101 Firefox/95.0',
'Mozilla/5.0 (X11; Linux x86_64; rv:99.0) Gecko/20100101 Firefox/99.0',
'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:100.0) Gecko/20100101 Firefox/100.0',
'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:101.0) Gecko/20100101 Firefox/101.0'
]
# Consistent for entire runtime
random_ua = random.choice(USER_AGENTS)
def base_headers():
return {'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.182 Safari/537.36'}
return {'user-agent': random_ua}
def base_session():
s = requests.Session()
if secret.REQUESTS_PROXY:
def wrapped_request(self, *args, **kwargs):
if not kwargs:
kwargs = {}
kwargs['proxies'] = {
'http': secret.REQUESTS_PROXY,
'https': secret.REQUESTS_PROXY
}
return self._original_request(*args, **kwargs)
s._original_request = s.request
s.request = wrapped_request.__get__(s, requests.Session)
return s
class ApiException(Exception):
def __init__(self, status_code, text, *args, **kwargs):
+28 -13
View File
@@ -1,6 +1,4 @@
import requests
import urllib
import datetime
import arrow
import time
import logging
@@ -8,12 +6,12 @@ import logging
from bs4 import BeautifulSoup
from ..util import timeago
from .common import parse_date, base_headers, ApiException, ApiLoginException
from .common import parse_date, base_headers, base_session, ApiException, ApiLoginException
logger = logging.getLogger(__name__)
class ControlIQApi:
BASE_URL = 'https://tdcservices.tandemdiabetes.com/tconnect/controliq/api/'
BASE_URL = 'https://tdcservices.tandemdiabetes.com/'
LOGIN_URL = 'https://tconnect.tandemdiabetes.com/login.aspx?ReturnUrl=%2f'
userGuid = None
@@ -27,7 +25,7 @@ class ControlIQApi:
def login(self, email, password):
logger.info("Logging in to ControlIQApi...")
with requests.Session() as s:
with base_session() as s:
initial = s.get(self.LOGIN_URL, headers=base_headers())
soup = BeautifulSoup(initial.content, features='lxml')
data = self._build_login_data(email, password, soup)
@@ -69,10 +67,15 @@ class ControlIQApi:
def api_headers(self):
if not self.accessToken:
raise Exception('No access token provided')
return {'Authorization': 'Bearer %s' % self.accessToken, **base_headers()}
return {
'Authorization': 'Bearer %s' % self.accessToken,
'Origin': 'https://tconnect.tandemdiabetes.com',
'Referer': 'https://tconnect.tandemdiabetes.com/',
**base_headers()
}
def _get(self, endpoint, query):
r = requests.get(self.BASE_URL + endpoint, query, headers=self.api_headers())
r = base_session().get(self.BASE_URL + endpoint, data=query, headers=self.api_headers())
if r.status_code != 200:
raise ApiException(r.status_code, "ControlIQ API HTTP %s response: %s" % (str(r.status_code), r.text))
@@ -107,10 +110,9 @@ class ControlIQApi:
startDate = parse_date(start)
endDate = parse_date(end)
return self.get('therapytimeline/users/%s' % (self.userGuid), {
"startDate": startDate,
"endDate": endDate
})
# Microsoft-Azure-Application-Gateway/v2 WAF error message appears
# if startDate and endDate are not specified in exactly this order.
return self.get('tconnect/controliq/api/therapytimeline/users/%s?startDate=%s&endDate=%s' % (self.userGuid, startDate, endDate), {})
"""
Returns a summary of pump and cgm activity.
@@ -123,7 +125,7 @@ class ControlIQApi:
startDate = parse_date(start)
endDate = parse_date(end)
return self.get('summary/users/%s' % (self.userGuid), {
return self.get('tconnect/controliq/api/summary/users/%s' % (self.userGuid), {
"startDate": startDate,
"endDate": endDate
})
@@ -133,4 +135,17 @@ class ControlIQApi:
[{"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, {})
return self.get('tconnect/controliq/api/pumpfeatures/users/%s' % self.userGuid, {})
"""
Returns therapy events, used by the webui Therapy Timeline.
{'event': [
{'type': 'Basal', 'basalRate': ...},
{'type': 'Bolus', 'standard': ...},
{'type': 'CGM', 'egv': ...}
]}
"""
def therapy_events(self, start_date=None, end_date=None):
startDate = parse_date(start_date)
endDate = parse_date(end_date)
return self.get('tconnect/therapyevents/api/TherapyEvents/%s/%s/false?userId=%s' % (startDate, endDate, self.userGuid), {})
+6 -5
View File
@@ -5,7 +5,7 @@ import logging
import time
import json
from .common import parse_date, base_headers, ApiException
from .common import base_session, parse_date, base_headers, ApiException
logger = logging.getLogger(__name__)
@@ -19,15 +19,16 @@ class WS2Api:
def __init__(self, userGuid):
self.userGuid = userGuid
self.session = base_session()
def get(self, endpoint, query):
r = requests.get(self.BASE_URL + endpoint, query, headers=base_headers())
def get(self, endpoint):
r = self.session.get(self.BASE_URL + endpoint, headers=base_headers())
if r.status_code != 200:
raise ApiException(r.status_code, "WS2 API HTTP %s response: %s" % (str(r.status_code), r.text))
return r.text
def get_jsonp(self, endpoint):
r = requests.get(self.BASE_URL + endpoint, {'callback': 'cb'}, headers=base_headers())
r = self.session.get(self.BASE_URL + endpoint + '?callback=cb', headers=base_headers())
if r.status_code != 200:
raise ApiException(r.status_code, "WS2 API HTTP %s response: %s" % (str(r.status_code), r.text))
@@ -76,7 +77,7 @@ class WS2Api:
endDate = parse_date(end)
try:
req_text = self.get('therapytimeline2csv/%s/%s/%s?format=csv' % (self.userGuid, startDate, endDate), {})
req_text = self.get('therapytimeline2csv/%s/%s/%s?format=csv' % (self.userGuid, startDate, endDate))
except ApiException as e:
# 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))
+79 -22
View File
@@ -1,10 +1,15 @@
import sys
import time
import arrow
import logging
import pkg_resources
from datetime import datetime
from pprint import pformat
from .nightscout import NightscoutApi
from .parser.nightscout import BASAL_EVENTTYPE, BOLUS_EVENTTYPE
from .parser.tconnect import TConnectEntry
from .sync.basal import process_ciq_basal_events
try:
__version__ = pkg_resources.require("tconnectsync")[0].version
@@ -16,7 +21,7 @@ Attempts to authenticate with each t:connect API,
and returns the output of a sample API call from each.
Also attempts to connect to the Nightscout API.
"""
def check_login(tconnect, time_start, time_end, verbose=False):
def check_login(tconnect, time_start, time_end, verbose=False, sanitize=False):
errors = 0
loglines = []
@@ -70,36 +75,58 @@ def check_login(tconnect, time_start, time_end, verbose=False):
log("Logging in to t:connect ControlIQ API...")
try:
summary = tconnect.controliq.dashboard_summary(time_start, time_end)
debug("ControlIQ dashboard summary: %s" % summary)
debug("ControlIQ dashboard summary: \n%s" % pformat(summary))
except Exception as e:
log("Error occurred querying ControlIQ API:")
log("Error occurred querying ControlIQ API for dashboard_summary:")
log(e)
errors += 1
log("Querying ControlIQ therapy_timeline...")
lastBasalTime = None
lastBasalDuration = None
try:
tt = tconnect.controliq.therapy_timeline(time_start, time_end)
debug("ControlIQ therapy_timeline: %s" % tt)
debug("ControlIQ therapy_timeline: \n%s" % pformat(tt))
if tt:
processed_tt = process_ciq_basal_events(tt)
debug("ControlIQ processed therapy_timeline: \n%s" % pformat(processed_tt))
if processed_tt:
log("Last ControlIQ processed therapy_timeline event: \n%s" % pformat(processed_tt[-1]))
lastBasalTime = processed_tt[-1]['time']
lastBasalDuration = processed_tt[-1]['duration_mins']
except Exception as e:
log("Error occurred querying ControlIQ therapy_timeline:")
log(e)
errors += 1
log("Querying ControlIQ therapy_events...")
try:
androidevents = tconnect.controliq.therapy_events(time_start, time_end)
debug("controliq therapy_events: \n%s" % pformat(androidevents))
except Exception as e:
log("Error occurred querying ControlIQ therapy_events:")
log(e)
errors += 1
log("-----")
log("Logging in to t:connect WS2 API...")
try:
summary = tconnect.ws2.basaliqtech(time_start, time_end)
debug("WS2 basaliq status: %s" % summary)
debug("WS2 basaliq status: \n%s" % pformat(summary))
except Exception as e:
log("Error occurred querying WS2 API:")
log(e)
errors += 1
log("Querying WS2 therapy_timeline_csv...")
lastReadingTime = None
try:
ttcsv = tconnect.ws2.therapy_timeline_csv(time_start, time_end)
debug("therapy_timeline_csv: %s", ttcsv)
debug("therapy_timeline_csv: \n%s" % pformat(ttcsv))
if ttcsv and "readingData" in ttcsv and len(ttcsv["readingData"]) > 0:
log("Last therapy_timeline_csv reading: \n%s" % pformat(ttcsv["readingData"][-1]))
lastReadingTime = TConnectEntry._datetime_parse(ttcsv["readingData"][-1]['EventDateTime'])
except Exception as e:
log("Error occurred querying WS2 therapy_timeline_csv:")
log(e)
@@ -108,38 +135,31 @@ def check_login(tconnect, time_start, time_end, verbose=False):
log("-----")
log("Logging in to t:connect Android API...")
summary = None
try:
summary = tconnect.android.user_profile()
debug("Android user profile: %s" % summary)
debug("Android user profile: \n%s" % pformat(summary))
event = tconnect.android.last_event_uploaded(PUMP_SERIAL_NUMBER)
debug("Android last uploaded event: %s" % event)
debug("Android last uploaded event: \n%s" % pformat(event))
except Exception as e:
log("Error occurred querying Android API:")
log(e)
errors += 1
log("Querying Android therapy_events...")
try:
androidevents = tconnect.android.therapy_events(time_start, time_end)
debug("android therapy_events: %s" % androidevents)
except Exception as e:
log("Error occurred querying Android therapy_events:")
log(e)
errors += 1
log("-----")
log("Logging in to Nightscout...")
try:
nightscout = NightscoutApi(NS_URL, NS_SECRET)
status = nightscout.api_status()
debug("Nightscout status: %s" % status)
debug("Nightscout status: \n%s" % pformat(status))
last_upload_basal = nightscout.last_uploaded_entry(BASAL_EVENTTYPE)
debug("Nightscout last uploaded basal: %s" % last_upload_basal)
debug("Nightscout last uploaded basal: \n%s" % pformat(last_upload_basal))
last_upload_bolus = nightscout.last_uploaded_entry(BOLUS_EVENTTYPE)
debug("Nightscout last uploaded bolus: %s" % last_upload_bolus)
debug("Nightscout last uploaded bolus: \n%s" % pformat(last_upload_bolus))
except Exception as e:
log("Error occurred querying Nightscout API:")
log(e)
@@ -147,6 +167,15 @@ def check_login(tconnect, time_start, time_end, verbose=False):
log("-----")
def time_ago(t):
return '%s ago' % (arrow.now() - arrow.get(t)) if t else 'n/a'
log("Last basal start time: %s (%s)" % (lastBasalTime, time_ago(lastBasalTime)))
log("Last basal duration: %s" % lastBasalDuration)
log("Last reading time: %s (%s)" % (lastReadingTime, time_ago(lastReadingTime)))
log("-----")
if errors == 0:
log("No API errors returned!")
else:
@@ -154,9 +183,37 @@ def check_login(tconnect, time_start, time_end, verbose=False):
with open('tconnectsync-check-output.log', 'w') as f:
if sanitize:
sanitizedData = {
'TCONNECT_EMAIL': TCONNECT_EMAIL,
'TCONNECT_PASSWORD': TCONNECT_PASSWORD,
'PUMP_SERIAL_NUMBER': PUMP_SERIAL_NUMBER,
'NS_URL': NS_URL,
'NS_SECRET': NS_SECRET
}
if summary:
sanitizedData.update({
'ANDROID_PROFILE_USERID': summary.get('userID'),
'ANDROID_PROFILE_PATIENT_FULLNAME': summary.get('patientFullName'),
'ANDROID_PROFILE_CAREGIVER_FULLNAME': summary.get('caregiverFullName')
})
loglines = [run_sanitize(i, sanitizedData) for i in loglines]
f.writelines(loglines)
print("Created file tconnectsync-check-output.log containing additional debugging information.")
print("For support, you can upload this file to https://github.com/jwoglom/tconnectsync/issues/new")
print("Before uploading, look through the file and remove any sensitive data, such as")
print("Nightscout URL and pump serial number.")
if sanitize:
print("The file -- but NOT the output printed above -- has been sanitized to remove sensitive data.")
print("Please verify and remove any sensitive data, such as your Nightscout URL/secret and pump serial number,")
print("as necessary.")
def run_sanitize(s, sanitizedData):
ret = str(s)
for k, v in sanitizedData.items():
if v and len(str(v)) > 0:
ret = ret.replace(str(v), '[%s]' % k)
return ret
+3
View File
@@ -175,6 +175,9 @@ class TConnectEntry:
}
@staticmethod
def parse_basalsuspension_event(data):
if not data or "SuspendReason" not in data:
return None
if data["SuspendReason"] in TConnectEntry.BASALSUSPENSION_SKIPPED_EVENTS:
return None
+1
View File
@@ -59,6 +59,7 @@ AUTOUPDATE_MAX_LOOP_INVOCATIONS = get_number('AUTOUPDATE_MAX_LOOP_INVOCATIONS',
ENABLE_TESTING_MODES = get_bool('ENABLE_TESTING_MODES', 'false')
SKIP_NS_LAST_UPLOADED_CHECK = get_bool('SKIP_NS_LAST_UPLOADED_CHECK', 'false')
REQUESTS_PROXY = get('REQUESTS_PROXY', '')
if __name__ == '__main__':
for k in locals():
+3 -1
View File
@@ -1,9 +1,11 @@
import tconnectsync.api
import requests
class ControlIQApi(tconnectsync.api.controliq.ControlIQApi):
def __init__(self):
self.BASE_URL = 'invalid://'
self.LOGIN_URL = 'invalid://'
self.session = requests.Session() # mocked in tests
def login(self, email, password):
raise NotImplementedError
@@ -19,7 +21,7 @@ class WS2Api(tconnectsync.api.ws2.WS2Api):
self.BASE_URL = 'invalid://'
self.SLEEP_SECONDS_INCREMENT = 0.01
def get(self, endpoint, query):
def get(self, endpoint):
raise NotImplementedError
def get_jsonp(self, endpoint):
+44
View File
@@ -0,0 +1,44 @@
import unittest
from unittest.mock import patch
from tconnectsync.api.common import base_session
class TestRequestsProxy(unittest.TestCase):
def test_proxy_used_in_base_session(self):
with patch("tconnectsync.api.common.secret") as mock_secret, \
patch("requests.Session.request") as mock_request:
s = base_session()
s.request('sentinel')
mock_request.assert_called_once_with('sentinel', proxies={
'http': mock_secret.REQUESTS_PROXY,
'https': mock_secret.REQUESTS_PROXY
})
def test_proxy_used_in_base_session_with_two_args(self):
with patch("tconnectsync.api.common.secret") as mock_secret, \
patch("requests.Session.request") as mock_request:
s = base_session()
s.request('GET', 'sentinel')
mock_request.assert_called_once_with('GET', 'sentinel', proxies={
'http': mock_secret.REQUESTS_PROXY,
'https': mock_secret.REQUESTS_PROXY
})
def test_proxy_used_in_base_session_with_kwargs(self):
with patch("tconnectsync.api.common.secret") as mock_secret, \
patch("requests.Session.request") as mock_request:
s = base_session()
s.request('GET', 'sentinel', foo={'bar': 'baz'})
mock_request.assert_called_once_with('GET', 'sentinel', foo={'bar': 'baz'}, proxies={
'http': mock_secret.REQUESTS_PROXY,
'https': mock_secret.REQUESTS_PROXY
})
+4 -6
View File
@@ -121,7 +121,7 @@ class TestControlIQApi(unittest.TestCase):
tries = 0
def fake_get(endpoint, query):
nonlocal http_code, expected_endpoint, num_times, tries
if endpoint.endswith(expected_endpoint):
if endpoint.split("?")[0].endswith(expected_endpoint):
if tries < num_times:
tries += 1
raise ApiException(http_code, "fake HTTP %d" % http_code)
@@ -202,12 +202,10 @@ class TestControlIQApi(unittest.TestCase):
ciq = ControlIQApi()
ciq.userGuid = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee'
def fake_get(endpoint, query):
def fake_get(raw_endpoint, ignored_query):
endpoint, query = raw_endpoint.split("?")
self.assertTrue(endpoint.endswith("therapytimeline/users/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"))
self.assertEqual(query, {
"startDate": "04-01-2021",
"endDate": "04-02-2021"
})
self.assertEqual(query, "startDate=04-01-2021&endDate=04-02-2021")
return {"faked_json": True}
+3 -3
View File
@@ -10,7 +10,7 @@ from tconnectsync.api.common import ApiException
class TestWS2Api(unittest.TestCase):
def fake_get_with_http_500(self, num_times):
tries = 0
def fake_get(endpoint, query):
def fake_get(endpoint):
nonlocal tries, num_times
if "therapytimeline2csv" in endpoint:
if tries < num_times:
@@ -94,7 +94,7 @@ Report Generated On, 4/24/2021 7:50:04 PM
rawData = self.RAW_DATA_FULL
def fake_get(endpoint, query):
def fake_get(endpoint):
nonlocal rawData
if endpoint == 'therapytimeline2csv/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/2021-04-01/2021-04-02?format=csv':
return rawData
@@ -111,7 +111,7 @@ Report Generated On, 4/24/2021 7:50:04 PM
rawData = ""
def fake_get(endpoint, query):
def fake_get(endpoint):
nonlocal rawData
if endpoint == 'therapytimeline2csv/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/2021-04-01/2021-04-02?format=csv':
return rawData
+15
View File
@@ -0,0 +1,15 @@
import importlib
def build_secrets(**kwargs):
secret = importlib.reload(importlib.import_module("tconnectsync.secret"))
class FakeSecret(object):
pass
fake = FakeSecret()
for k in dir(secret):
setattr(fake, k, getattr(secret, k))
for k, v in kwargs.items():
setattr(fake, k, v)
return fake
+11 -28
View File
@@ -1,20 +1,17 @@
#!/usr/bin/env python3
import os
import logging
import time
import unittest
import datetime
import importlib
import contextlib
from unittest.mock import patch
from tconnectsync.autoupdate import Autoupdate, AutoupdateFailureError, AutoupdateFailureWarning, AutoupdateNoEventIndexesDetectedError, AutoupdateNoIndexChangeWarning
from tconnectsync.secret import AUTOUPDATE_FAILURE_MINUTES, AUTOUPDATE_NO_DATA_FAILURE_MINUTES, AUTOUPDATE_RESTART_ON_FAILURE, TCONNECT_PASSWORD
from .api.fake import TConnectApi
from .nightscout_fake import NightscoutApi
from .secrets import build_secrets
logger = logging.getLogger(__name__)
@@ -69,20 +66,6 @@ class TestAutoupdate(unittest.TestCase):
return fake
def build_secrets(self, **kwargs):
secret = importlib.reload(importlib.import_module("tconnectsync.secret"))
class FakeSecret(object):
pass
fake = FakeSecret()
for k in dir(secret):
setattr(fake, k, getattr(secret, k))
for k, v in kwargs.items():
setattr(fake, k, v)
return fake
"""process_time_range should always be invoked the first time"""
def test_process_time_range_called_on_start(self):
tconnect = TConnectApi()
@@ -90,7 +73,7 @@ class TestAutoupdate(unittest.TestCase):
nightscout = NightscoutApi()
secret = self.build_secrets(
secret = build_secrets(
AUTOUPDATE_MAX_LOOP_INVOCATIONS=1,
AUTOUPDATE_USE_FIXED_SLEEP=True,
AUTOUPDATE_DEFAULT_SLEEP_SECONDS=0
@@ -116,7 +99,7 @@ class TestAutoupdate(unittest.TestCase):
nightscout = NightscoutApi()
secret = self.build_secrets(
secret = build_secrets(
AUTOUPDATE_MAX_LOOP_INVOCATIONS=1,
AUTOUPDATE_USE_FIXED_SLEEP=True,
AUTOUPDATE_DEFAULT_SLEEP_SECONDS=0
@@ -144,7 +127,7 @@ class TestAutoupdate(unittest.TestCase):
nightscout = NightscoutApi()
secret = self.build_secrets(
secret = build_secrets(
AUTOUPDATE_MAX_LOOP_INVOCATIONS=2,
AUTOUPDATE_USE_FIXED_SLEEP=True,
AUTOUPDATE_DEFAULT_SLEEP_SECONDS=0,
@@ -174,7 +157,7 @@ class TestAutoupdate(unittest.TestCase):
nightscout = NightscoutApi()
secret = self.build_secrets(
secret = build_secrets(
AUTOUPDATE_MAX_LOOP_INVOCATIONS=1,
AUTOUPDATE_USE_FIXED_SLEEP=True,
AUTOUPDATE_DEFAULT_SLEEP_SECONDS=0
@@ -204,7 +187,7 @@ class TestAutoupdate(unittest.TestCase):
nightscout = NightscoutApi()
secret = self.build_secrets(
secret = build_secrets(
AUTOUPDATE_MAX_LOOP_INVOCATIONS=2,
AUTOUPDATE_USE_FIXED_SLEEP=True,
AUTOUPDATE_DEFAULT_SLEEP_SECONDS=0,
@@ -236,7 +219,7 @@ class TestAutoupdate(unittest.TestCase):
nightscout = NightscoutApi()
secret = self.build_secrets(
secret = build_secrets(
AUTOUPDATE_MAX_LOOP_INVOCATIONS=3,
AUTOUPDATE_USE_FIXED_SLEEP=True,
AUTOUPDATE_DEFAULT_SLEEP_SECONDS=0,
@@ -265,7 +248,7 @@ class TestAutoupdate(unittest.TestCase):
nightscout = NightscoutApi()
secret = self.build_secrets(
secret = build_secrets(
AUTOUPDATE_MAX_LOOP_INVOCATIONS=1,
AUTOUPDATE_USE_FIXED_SLEEP=True,
AUTOUPDATE_DEFAULT_SLEEP_SECONDS=0
@@ -292,7 +275,7 @@ class TestAutoupdate(unittest.TestCase):
nightscout = NightscoutApi()
sleep_length = 3 # sentinel
secret = self.build_secrets(
secret = build_secrets(
AUTOUPDATE_MAX_LOOP_INVOCATIONS=1,
AUTOUPDATE_USE_FIXED_SLEEP=True,
AUTOUPDATE_DEFAULT_SLEEP_SECONDS=sleep_length
@@ -324,7 +307,7 @@ class TestAutoupdate(unittest.TestCase):
nightscout = NightscoutApi()
secret = self.build_secrets(
secret = build_secrets(
AUTOUPDATE_MAX_LOOP_INVOCATIONS=100,
AUTOUPDATE_USE_FIXED_SLEEP=True,
AUTOUPDATE_DEFAULT_SLEEP_SECONDS=0.1,
@@ -356,7 +339,7 @@ class TestAutoupdate(unittest.TestCase):
nightscout = NightscoutApi()
secret = self.build_secrets(
secret = build_secrets(
AUTOUPDATE_MAX_LOOP_INVOCATIONS=5,
AUTOUPDATE_USE_FIXED_SLEEP=True,
AUTOUPDATE_DEFAULT_SLEEP_SECONDS=0.1,