diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..0b389d8 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,13 @@ +name: Lint + +on: push + +jobs: + lint: + if: "!contains(github.event.head_commit.message, 'skip_ci')" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v2 + - run: python -m pip install --upgrade pip nox + - run: nox -s lint diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..4bd88cf --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,21 @@ +name: Publish Python šŸ package šŸ“¦ to PyPI +on: + push: + tags: + - v*.*.* + +jobs: + release: + name: Publish python package + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v2 + - run: python -m pip install --upgrade build + - run: python -m build + + - name: Publish to PyPI + if: startsWith(github.ref, 'refs/tags') + uses: pypa/gh-action-pypi-publish@release/v1 + with: + password: ${{ secrets.PYPI_API_TOKEN }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..bcf0d59 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,24 @@ +name: Pytest + +on: push + +jobs: + test: + if: "!contains(github.event.head_commit.message, 'skip_ci')" + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: [3.8, 3.9] + + name: Python ${{ matrix.python-version }} + steps: + - uses: actions/checkout@v2 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + + - run: python -m pip install --upgrade pip nox + - run: nox -s tests diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 7f79984..0000000 --- a/.travis.yml +++ /dev/null @@ -1,16 +0,0 @@ -language: python -python: - - 3.6 - - 3.7 - - 3.8 -install: - - pip install nox -env: - - SESSION=tests -jobs: - include: - - python: 3.8 - env: SESSION=lint - - python: 3.8 - env: SESSION=docs -script: nox -s $SESSION diff --git a/Pipfile b/Pipfile index bef28be..270a2f1 100644 --- a/Pipfile +++ b/Pipfile @@ -4,11 +4,13 @@ url = "https://pypi.org/simple" verify_ssl = true [dev-packages] +tabulate = "*" +pytest = "*" +twine = "*" +requests-mock = "*" +xlsxwriter = "*" [packages] requests-oauthlib = "*" -flask = "*" -twine = "*" -requests-mock = "*" -pytest = "*" pandas = "*" +Flask = "*" diff --git a/Pipfile.lock b/Pipfile.lock index 967b2b2..dc76ba5 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "dc5fb6bd54030c827b464a9dfac7b7783226b74bd88deda65f67db89ad226c3c" + "sha256": "8c7dc197321c79d9432c793614c7ca8ac8cbd74060b01400195d59b368c687e7" }, "pipfile-spec": 6, "requires": {}, @@ -14,388 +14,482 @@ ] }, "default": { - "attrs": { + "certifi": { "hashes": [ - "sha256:26b54ddbbb9ee1d34d5d3668dd37d6cf74990ab23c828c2888dccdceee395594", - "sha256:fce7fc47dfc976152e82d53ff92fa0407700c21acd20886a13777a0d20e655dc" + "sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d", + "sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412" ], - "version": "==20.2.0" + "version": "==2022.6.15" }, - "bleach": { + "charset-normalizer": { "hashes": [ - "sha256:52b5919b81842b1854196eaae5ca29679a2f2e378905c346d3ca8227c2c66080", - "sha256:9f8ccbeb6183c6e6cddea37592dfb0167485c1e3b13b3363bc325aa8bda3adbd" + "sha256:5189b6f22b01957427f35b6a08d9a0bc45b46d3788ef5a92e978433c7a35f8a5", + "sha256:575e708016ff3a5e3681541cb9d79312c416835686d054a23accb873b254f413" ], - "version": "==3.2.1" + "version": "==2.1.0" }, - "certifi": { + "click": { "hashes": [ - "sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3", - "sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41" + "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e", + "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48" ], - "version": "==2020.6.20" + "version": "==8.1.3" }, - "cffi": { + "flask": { "hashes": [ - "sha256:005f2bfe11b6745d726dbb07ace4d53f057de66e336ff92d61b8c7e9c8f4777d", - "sha256:09e96138280241bd355cd585148dec04dbbedb4f46128f340d696eaafc82dd7b", - "sha256:0b1ad452cc824665ddc682400b62c9e4f5b64736a2ba99110712fdee5f2505c4", - "sha256:0ef488305fdce2580c8b2708f22d7785ae222d9825d3094ab073e22e93dfe51f", - "sha256:15f351bed09897fbda218e4db5a3d5c06328862f6198d4fb385f3e14e19decb3", - "sha256:22399ff4870fb4c7ef19fff6eeb20a8bbf15571913c181c78cb361024d574579", - "sha256:23e5d2040367322824605bc29ae8ee9175200b92cb5483ac7d466927a9b3d537", - "sha256:2791f68edc5749024b4722500e86303a10d342527e1e3bcac47f35fbd25b764e", - "sha256:2f9674623ca39c9ebe38afa3da402e9326c245f0f5ceff0623dccdac15023e05", - "sha256:3363e77a6176afb8823b6e06db78c46dbc4c7813b00a41300a4873b6ba63b171", - "sha256:33c6cdc071ba5cd6d96769c8969a0531be2d08c2628a0143a10a7dcffa9719ca", - "sha256:3b8eaf915ddc0709779889c472e553f0d3e8b7bdf62dab764c8921b09bf94522", - "sha256:3cb3e1b9ec43256c4e0f8d2837267a70b0e1ca8c4f456685508ae6106b1f504c", - "sha256:3eeeb0405fd145e714f7633a5173318bd88d8bbfc3dd0a5751f8c4f70ae629bc", - "sha256:44f60519595eaca110f248e5017363d751b12782a6f2bd6a7041cba275215f5d", - "sha256:4d7c26bfc1ea9f92084a1d75e11999e97b62d63128bcc90c3624d07813c52808", - "sha256:529c4ed2e10437c205f38f3691a68be66c39197d01062618c55f74294a4a4828", - "sha256:6642f15ad963b5092d65aed022d033c77763515fdc07095208f15d3563003869", - "sha256:85ba797e1de5b48aa5a8427b6ba62cf69607c18c5d4eb747604b7302f1ec382d", - "sha256:8f0f1e499e4000c4c347a124fa6a27d37608ced4fe9f7d45070563b7c4c370c9", - "sha256:a624fae282e81ad2e4871bdb767e2c914d0539708c0f078b5b355258293c98b0", - "sha256:b0358e6fefc74a16f745afa366acc89f979040e0cbc4eec55ab26ad1f6a9bfbc", - "sha256:bbd2f4dfee1079f76943767fce837ade3087b578aeb9f69aec7857d5bf25db15", - "sha256:bf39a9e19ce7298f1bd6a9758fa99707e9e5b1ebe5e90f2c3913a47bc548747c", - "sha256:c11579638288e53fc94ad60022ff1b67865363e730ee41ad5e6f0a17188b327a", - "sha256:c150eaa3dadbb2b5339675b88d4573c1be3cb6f2c33a6c83387e10cc0bf05bd3", - "sha256:c53af463f4a40de78c58b8b2710ade243c81cbca641e34debf3396a9640d6ec1", - "sha256:cb763ceceae04803adcc4e2d80d611ef201c73da32d8f2722e9d0ab0c7f10768", - "sha256:cc75f58cdaf043fe6a7a6c04b3b5a0e694c6a9e24050967747251fb80d7bce0d", - "sha256:d80998ed59176e8cba74028762fbd9b9153b9afc71ea118e63bbf5d4d0f9552b", - "sha256:de31b5164d44ef4943db155b3e8e17929707cac1e5bd2f363e67a56e3af4af6e", - "sha256:e66399cf0fc07de4dce4f588fc25bfe84a6d1285cc544e67987d22663393926d", - "sha256:f0620511387790860b249b9241c2f13c3a80e21a73e0b861a2df24e9d6f56730", - "sha256:f4eae045e6ab2bb54ca279733fe4eb85f1effda392666308250714e01907f394", - "sha256:f92cdecb618e5fa4658aeb97d5eb3d2f47aa94ac6477c6daf0f306c5a3b9e6b1", - "sha256:f92f789e4f9241cd262ad7a555ca2c648a98178a953af117ef7fad46aa1d5591" - ], - "version": "==1.14.3" - }, - "chardet": { - "hashes": [ - "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", - "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" - ], - "version": "==3.0.4" + "sha256:15972e5017df0575c3d6c090ba168b6db90259e620ac8d7ea813a396bad5b6cb", + "sha256:9013281a7402ad527f8fd56375164f3aa021ecfaff89bfe3825346c24f87e04c" + ], + "index": "pypi", + "version": "==2.1.3" }, - "click": { + "idna": { "hashes": [ - "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a", - "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc" + "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff", + "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d" ], - "version": "==7.1.2" + "version": "==3.3" }, - "colorama": { + "importlib-metadata": { "hashes": [ - "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b", - "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2" + "sha256:637245b8bab2b6502fcbc752cc4b7a6f6243bb02b31c5c26156ad103d3d45670", + "sha256:7401a975809ea1fdc658c3aa4f78cc2195a0e019c5cbc4c06122884e9ae80c23" ], - "version": "==0.4.4" + "markers": "python_version < '3.10'", + "version": "==4.12.0" }, - "cryptography": { + "itsdangerous": { "hashes": [ - "sha256:07ca431b788249af92764e3be9a488aa1d39a0bc3be313d826bbec690417e538", - "sha256:13b88a0bd044b4eae1ef40e265d006e34dbcde0c2f1e15eb9896501b2d8f6c6f", - "sha256:32434673d8505b42c0de4de86da8c1620651abd24afe91ae0335597683ed1b77", - "sha256:3cd75a683b15576cfc822c7c5742b3276e50b21a06672dc3a800a2d5da4ecd1b", - "sha256:4e7268a0ca14536fecfdf2b00297d4e407da904718658c1ff1961c713f90fd33", - "sha256:545a8550782dda68f8cdc75a6e3bf252017aa8f75f19f5a9ca940772fc0cb56e", - "sha256:55d0b896631412b6f0c7de56e12eb3e261ac347fbaa5d5e705291a9016e5f8cb", - "sha256:5849d59358547bf789ee7e0d7a9036b2d29e9a4ddf1ce5e06bb45634f995c53e", - "sha256:6dc59630ecce8c1f558277ceb212c751d6730bd12c80ea96b4ac65637c4f55e7", - "sha256:7117319b44ed1842c617d0a452383a5a052ec6aa726dfbaffa8b94c910444297", - "sha256:75e8e6684cf0034f6bf2a97095cb95f81537b12b36a8fedf06e73050bb171c2d", - "sha256:7b8d9d8d3a9bd240f453342981f765346c87ade811519f98664519696f8e6ab7", - "sha256:a035a10686532b0587d58a606004aa20ad895c60c4d029afa245802347fab57b", - "sha256:a4e27ed0b2504195f855b52052eadcc9795c59909c9d84314c5408687f933fc7", - "sha256:a733671100cd26d816eed39507e585c156e4498293a907029969234e5e634bc4", - "sha256:a75f306a16d9f9afebfbedc41c8c2351d8e61e818ba6b4c40815e2b5740bb6b8", - "sha256:bd717aa029217b8ef94a7d21632a3bb5a4e7218a4513d2521c2a2fd63011e98b", - "sha256:d25cecbac20713a7c3bc544372d42d8eafa89799f492a43b79e1dfd650484851", - "sha256:d26a2557d8f9122f9bf445fc7034242f4375bd4e95ecda007667540270965b13", - "sha256:d3545829ab42a66b84a9aaabf216a4dce7f16dbc76eb69be5c302ed6b8f4a29b", - "sha256:d3d5e10be0cf2a12214ddee45c6bd203dab435e3d83b4560c03066eda600bfe3", - "sha256:efe15aca4f64f3a7ea0c09c87826490e50ed166ce67368a68f315ea0807a20df" - ], - "version": "==3.2.1" + "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44", + "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a" + ], + "version": "==2.1.2" }, - "docutils": { + "jinja2": { "hashes": [ - "sha256:0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af", - "sha256:c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc" + "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852", + "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61" ], - "version": "==0.16" + "version": "==3.1.2" }, - "flask": { + "markupsafe": { + "hashes": [ + "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003", + "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88", + "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5", + "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7", + "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a", + "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603", + "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1", + "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135", + "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247", + "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6", + "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601", + "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77", + "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02", + "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e", + "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63", + "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f", + "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980", + "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b", + "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812", + "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff", + "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96", + "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1", + "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925", + "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a", + "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6", + "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e", + "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f", + "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4", + "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f", + "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3", + "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c", + "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a", + "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417", + "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a", + "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a", + "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37", + "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452", + "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933", + "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a", + "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7" + ], + "version": "==2.1.1" + }, + "numpy": { + "hashes": [ + "sha256:1408c3527a74a0209c781ac82bde2182b0f0bf54dea6e6a363fe0cc4488a7ce7", + "sha256:173f28921b15d341afadf6c3898a34f20a0569e4ad5435297ba262ee8941e77b", + "sha256:1865fdf51446839ca3fffaab172461f2b781163f6f395f1aed256b1ddc253622", + "sha256:3119daed207e9410eaf57dcf9591fdc68045f60483d94956bee0bfdcba790953", + "sha256:35590b9c33c0f1c9732b3231bb6a72d1e4f77872390c47d50a615686ae7ed3fd", + "sha256:37e5ebebb0eb54c5b4a9b04e6f3018e16b8ef257d26c8945925ba8105008e645", + "sha256:37ece2bd095e9781a7156852e43d18044fd0d742934833335599c583618181b9", + "sha256:3ab67966c8d45d55a2bdf40701536af6443763907086c0a6d1232688e27e5447", + "sha256:47f10ab202fe4d8495ff484b5561c65dd59177949ca07975663f4494f7269e3e", + "sha256:55df0f7483b822855af67e38fb3a526e787adf189383b4934305565d71c4b148", + "sha256:5d732d17b8a9061540a10fda5bfeabca5785700ab5469a5e9b93aca5e2d3a5fb", + "sha256:68b69f52e6545af010b76516f5daaef6173e73353e3295c5cb9f96c35d755641", + "sha256:7e8229f3687cdadba2c4faef39204feb51ef7c1a9b669247d49a24f3e2e1617c", + "sha256:8002574a6b46ac3b5739a003b5233376aeac5163e5dcd43dd7ad062f3e186129", + "sha256:876f60de09734fbcb4e27a97c9a286b51284df1326b1ac5f1bf0ad3678236b22", + "sha256:9ce242162015b7e88092dccd0e854548c0926b75c7924a3495e02c6067aba1f5", + "sha256:a35c4e64dfca659fe4d0f1421fc0f05b8ed1ca8c46fb73d9e5a7f175f85696bb", + "sha256:aeba539285dcf0a1ba755945865ec61240ede5432df41d6e29fab305f4384db2", + "sha256:b15c3f1ed08df4980e02cc79ee058b788a3d0bef2fb3c9ca90bb8cbd5b8a3a04", + "sha256:c2f91f88230042a130ceb1b496932aa717dcbd665350beb821534c5c7e15881c", + "sha256:d748ef349bfef2e1194b59da37ed5a29c19ea8d7e6342019921ba2ba4fd8b624", + "sha256:e0d7447679ae9a7124385ccf0ea990bb85bb869cef217e2ea6c844b6a6855073" + ], + "markers": "platform_machine != 'aarch64' and platform_machine != 'arm64' and python_version < '3.10'", + "version": "==1.23.1" + }, + "oauthlib": { + "hashes": [ + "sha256:23a8208d75b902797ea29fd31fa80a15ed9dc2c6c16fe73f5d346f83f6fa27a2", + "sha256:6db33440354787f9b7f3a6dbd4febf5d0f93758354060e802f6c06cb493022fe" + ], + "version": "==3.2.0" + }, + "pandas": { "hashes": [ - "sha256:4efa1ae2d7c9865af48986de8aeb8504bf32c7f3d6fdc9353d34b21f4b127060", - "sha256:8a4fdd8936eba2512e9c85df320a37e694c93945b33ef33c89946a340a238557" + "sha256:07238a58d7cbc8a004855ade7b75bbd22c0db4b0ffccc721556bab8a095515f6", + "sha256:0daf876dba6c622154b2e6741f29e87161f844e64f84801554f879d27ba63c0d", + "sha256:16ad23db55efcc93fa878f7837267973b61ea85d244fc5ff0ccbcfa5638706c5", + "sha256:1d9382f72a4f0e93909feece6fef5500e838ce1c355a581b3d8f259839f2ea76", + "sha256:24ea75f47bbd5574675dae21d51779a4948715416413b30614c1e8b480909f81", + "sha256:2893e923472a5e090c2d5e8db83e8f907364ec048572084c7d10ef93546be6d1", + "sha256:2ff7788468e75917574f080cd4681b27e1a7bf36461fe968b49a87b5a54d007c", + "sha256:41fc406e374590a3d492325b889a2686b31e7a7780bec83db2512988550dadbf", + "sha256:48350592665ea3cbcd07efc8c12ff12d89be09cd47231c7925e3b8afada9d50d", + "sha256:605d572126eb4ab2eadf5c59d5d69f0608df2bf7bcad5c5880a47a20a0699e3e", + "sha256:6dfbf16b1ea4f4d0ee11084d9c026340514d1d30270eaa82a9f1297b6c8ecbf0", + "sha256:6f803320c9da732cc79210d7e8cc5c8019aad512589c910c66529eb1b1818230", + "sha256:721a3dd2f06ef942f83a819c0f3f6a648b2830b191a72bbe9451bcd49c3bd42e", + "sha256:755679c49460bd0d2f837ab99f0a26948e68fa0718b7e42afbabd074d945bf84", + "sha256:78b00429161ccb0da252229bcda8010b445c4bf924e721265bec5a6e96a92e92", + "sha256:958a0588149190c22cdebbc0797e01972950c927a11a900fe6c2296f207b1d6f", + "sha256:a3924692160e3d847e18702bb048dc38e0e13411d2b503fecb1adf0fcf950ba4", + "sha256:d51674ed8e2551ef7773820ef5dab9322be0828629f2cbf8d1fc31a0c4fed640", + "sha256:d5ebc990bd34f4ac3c73a2724c2dcc9ee7bf1ce6cf08e87bb25c6ad33507e318", + "sha256:d6c0106415ff1a10c326c49bc5dd9ea8b9897a6ca0c8688eb9c30ddec49535ef", + "sha256:e48fbb64165cda451c06a0f9e4c7a16b534fcabd32546d531b3c240ce2844112" ], "index": "pypi", - "version": "==1.1.2" + "version": "==1.4.3" }, - "idna": { + "python-dateutil": { "hashes": [ - "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6", - "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0" + "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86", + "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9" ], - "version": "==2.10" + "version": "==2.8.2" }, - "iniconfig": { + "pytz": { "hashes": [ - "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3", - "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32" + "sha256:1e760e2fe6a8163bc0b3d9a19c4f84342afa0a2affebfaa84b01b978a02ecaa7", + "sha256:e68985985296d9a66a881eb3193b0906246245294a881e7c8afe623866ac6a5c" ], - "version": "==1.1.1" + "version": "==2022.1" }, - "itsdangerous": { + "requests": { "hashes": [ - "sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19", - "sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749" + "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983", + "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349" ], - "version": "==1.1.0" + "version": "==2.28.1" }, - "jeepney": { + "requests-oauthlib": { "hashes": [ - "sha256:3479b861cc2b6407de5188695fa1a8d57e5072d7059322469b62628869b8e36e", - "sha256:d6c6b49683446d2407d2fe3acb7a368a77ff063f9182fe427da15d622adc24cf" + "sha256:2577c501a2fb8d05a304c09d090d6e47c306fef15809d102b327cf8364bddab5", + "sha256:75beac4a47881eeb94d5ea5d6ad31ef88856affe2332b9aafb52c6452ccf0d7a" ], - "version": "==0.4.3" + "index": "pypi", + "version": "==1.3.1" }, - "jinja2": { + "six": { "hashes": [ - "sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0", - "sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035" + "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", + "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" ], - "version": "==2.11.2" + "version": "==1.16.0" }, - "keyring": { + "urllib3": { + "hashes": [ + "sha256:8298d6d56d39be0e3bc13c1c97d133f9b45d797169a0e11cdd0e0489d786f7ec", + "sha256:879ba4d1e89654d9769ce13121e0f94310ea32e8d2f8cf587b77c08bbcdb30d6" + ], + "version": "==1.26.10" + }, + "werkzeug": { + "hashes": [ + "sha256:60ab4823078f08fdb36b5b83b35f1c422eab8c92929eba5487e1bd52d2316fd4", + "sha256:6a3fe061435495aed49c1ea54dbdf1529b6333bb7ddbe20089e4360250b040ec" + ], + "version": "==2.2.0a1" + }, + "zipp": { + "hashes": [ + "sha256:05b45f1ee8f807d0cc928485ca40a07cb491cf092ff587c0df9cb1fd154848d2", + "sha256:47c40d7fe183a6f21403a199b3e4192cca5774656965b0a4988ad2f8feb5f009" + ], + "version": "==3.8.1" + } + }, + "develop": { + "attrs": { "hashes": [ - "sha256:4e34ea2fdec90c1c43d6610b5a5fafa1b9097db1802948e90caf5763974b8f8d", - "sha256:9aeadd006a852b78f4b4ef7c7556c2774d2432bbef8ee538a3e9089ac8b11466" + "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4", + "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd" ], "version": "==21.4.0" }, - "markupsafe": { + "bleach": { "hashes": [ - "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", - "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", - "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", - "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", - "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42", - "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", - "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", - "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", - "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", - "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", - "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", - "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b", - "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", - "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15", - "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", - "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", - "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", - "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", - "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", - "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", - "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", - "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", - "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", - "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", - "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", - "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", - "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", - "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", - "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", - "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", - "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2", - "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7", - "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be" + "sha256:085f7f33c15bd408dd9b17a4ad77c577db66d76203e5984b1bd59baeee948b2a", + "sha256:0d03255c47eb9bd2f26aa9bb7f2107732e7e8fe195ca2f64709fcf3b0a4a085c" ], - "version": "==1.1.1" + "version": "==5.0.1" }, - "numpy": { + "certifi": { "hashes": [ - "sha256:0ee77786eebbfa37f2141fd106b549d37c89207a0d01d8852fde1c82e9bfc0e7", - "sha256:199bebc296bd8a5fc31c16f256ac873dd4d5b4928dfd50e6c4995570fc71a8f3", - "sha256:1a307bdd3dd444b1d0daa356b5f4c7de2e24d63bdc33ea13ff718b8ec4c6a268", - "sha256:1ea7e859f16e72ab81ef20aae69216cfea870676347510da9244805ff9670170", - "sha256:271139653e8b7a046d11a78c0d33bafbddd5c443a5b9119618d0652a4eb3a09f", - "sha256:35bf5316af8dc7c7db1ad45bec603e5fb28671beb98ebd1d65e8059efcfd3b72", - "sha256:463792a249a81b9eb2b63676347f996d3f0082c2666fd0604f4180d2e5445996", - "sha256:50d3513469acf5b2c0406e822d3f314d7ac5788c2b438c24e5dd54d5a81ef522", - "sha256:50f68ebc439821b826823a8da6caa79cd080dee2a6d5ab9f1163465a060495ed", - "sha256:51e8d2ae7c7e985c7bebf218e56f72fa93c900ad0c8a7d9fbbbf362f45710f69", - "sha256:522053b731e11329dd52d258ddf7de5288cae7418b55e4b7d32f0b7e31787e9d", - "sha256:5ea4401ada0d3988c263df85feb33818dc995abc85b8125f6ccb762009e7bc68", - "sha256:604d2e5a31482a3ad2c88206efd43d6fcf666ada1f3188fd779b4917e49b7a98", - "sha256:6ff88bcf1872b79002569c63fe26cd2cda614e573c553c4d5b814fb5eb3d2822", - "sha256:7197ee0a25629ed782c7bd01871ee40702ffeef35bc48004bc2fdcc71e29ba9d", - "sha256:741d95eb2b505bb7a99fbf4be05fa69f466e240c2b4f2d3ddead4f1b5f82a5a5", - "sha256:83af653bb92d1e248ccf5fdb05ccc934c14b936bcfe9b917dc180d3f00250ac6", - "sha256:8802d23e4895e0c65e418abe67cdf518aa5cbb976d97f42fd591f921d6dffad0", - "sha256:8edc4d687a74d0a5f8b9b26532e860f4f85f56c400b3a98899fc44acb5e27add", - "sha256:942d2cdcb362739908c26ce8dd88db6e139d3fa829dd7452dd9ff02cba6b58b2", - "sha256:9a0669787ba8c9d3bb5de5d9429208882fb47764aa79123af25c5edc4f5966b9", - "sha256:9d08d84bb4128abb9fbd9f073e5c69f70e5dab991a9c42e5b4081ea5b01b5db0", - "sha256:9f7f56b5e85b08774939622b7d45a5d00ff511466522c44fc0756ac7692c00f2", - "sha256:a2daea1cba83210c620e359de2861316f49cc7aea8e9a6979d6cb2ddab6dda8c", - "sha256:b9074d062d30c2779d8af587924f178a539edde5285d961d2dfbecbac9c4c931", - "sha256:c4aa79993f5d856765819a3651117520e41ac3f89c3fc1cb6dee11aa562df6da", - "sha256:d78294f1c20f366cde8a75167f822538a7252b6e8b9d6dbfb3bdab34e7c1929e", - "sha256:dfdc8b53aa9838b9d44ed785431ca47aa3efaa51d0d5dd9c412ab5247151a7c4", - "sha256:dffed17848e8b968d8d3692604e61881aa6ef1f8074c99e81647ac84f6038535", - "sha256:e080087148fd70469aade2abfeadee194357defd759f9b59b349c6192aba994c", - "sha256:e983cbabe10a8989333684c98fdc5dd2f28b236216981e0c26ed359aaa676772", - "sha256:ea6171d2d8d648dee717457d0f75db49ad8c2f13100680e284d7becf3dc311a6", - "sha256:eefc13863bf01583a85e8c1121a901cc7cb8f059b960c4eba30901e2e6aba95f", - "sha256:efd656893171bbf1331beca4ec9f2e74358fc732a2084f664fd149cc4b3441d2" - ], - "version": "==1.19.3" + "sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d", + "sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412" + ], + "version": "==2022.6.15" }, - "oauthlib": { + "cffi": { "hashes": [ - "sha256:bee41cc35fcca6e988463cacc3bcb8a96224f470ca547e697b604cc697b2f889", - "sha256:df884cd6cbe20e32633f1db1072e9356f53638e4361bef4e8b03c9127c9328ea" + "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5", + "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef", + "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104", + "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426", + "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405", + "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375", + "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a", + "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e", + "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc", + "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf", + "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185", + "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497", + "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3", + "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35", + "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c", + "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83", + "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21", + "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca", + "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984", + "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac", + "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd", + "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee", + "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a", + "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2", + "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192", + "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7", + "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585", + "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f", + "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e", + "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27", + "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b", + "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e", + "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e", + "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d", + "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c", + "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415", + "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82", + "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02", + "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314", + "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325", + "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c", + "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3", + "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914", + "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045", + "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d", + "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9", + "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5", + "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2", + "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c", + "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3", + "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2", + "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8", + "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d", + "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d", + "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9", + "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162", + "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76", + "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4", + "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e", + "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9", + "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6", + "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b", + "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01", + "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0" + ], + "version": "==1.15.1" + }, + "charset-normalizer": { + "hashes": [ + "sha256:5189b6f22b01957427f35b6a08d9a0bc45b46d3788ef5a92e978433c7a35f8a5", + "sha256:575e708016ff3a5e3681541cb9d79312c416835686d054a23accb873b254f413" + ], + "version": "==2.1.0" + }, + "commonmark": { + "hashes": [ + "sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60", + "sha256:da2f38c92590f83de410ba1a3cbceafbc74fee9def35f9251ba9a971d6d66fd9" ], - "version": "==3.1.0" + "version": "==0.9.1" }, - "packaging": { + "cryptography": { + "hashes": [ + "sha256:190f82f3e87033821828f60787cfa42bff98404483577b591429ed99bed39d59", + "sha256:2be53f9f5505673eeda5f2736bea736c40f051a739bfae2f92d18aed1eb54596", + "sha256:30788e070800fec9bbcf9faa71ea6d8068f5136f60029759fd8c3efec3c9dcb3", + "sha256:3d41b965b3380f10e4611dbae366f6dc3cefc7c9ac4e8842a806b9672ae9add5", + "sha256:4c590ec31550a724ef893c50f9a97a0c14e9c851c85621c5650d699a7b88f7ab", + "sha256:549153378611c0cca1042f20fd9c5030d37a72f634c9326e225c9f666d472884", + "sha256:63f9c17c0e2474ccbebc9302ce2f07b55b3b3fcb211ded18a42d5764f5c10a82", + "sha256:6bc95ed67b6741b2607298f9ea4932ff157e570ef456ef7ff0ef4884a134cc4b", + "sha256:7099a8d55cd49b737ffc99c17de504f2257e3787e02abe6d1a6d136574873441", + "sha256:75976c217f10d48a8b5a8de3d70c454c249e4b91851f6838a4e48b8f41eb71aa", + "sha256:7bc997818309f56c0038a33b8da5c0bfbb3f1f067f315f9abd6fc07ad359398d", + "sha256:80f49023dd13ba35f7c34072fa17f604d2f19bf0989f292cedf7ab5770b87a0b", + "sha256:91ce48d35f4e3d3f1d83e29ef4a9267246e6a3be51864a5b7d2247d5086fa99a", + "sha256:a958c52505c8adf0d3822703078580d2c0456dd1d27fabfb6f76fe63d2971cd6", + "sha256:b62439d7cd1222f3da897e9a9fe53bbf5c104fff4d60893ad1355d4c14a24157", + "sha256:b7f8dd0d4c1f21759695c05a5ec8536c12f31611541f8904083f3dc582604280", + "sha256:d204833f3c8a33bbe11eda63a54b1aad7aa7456ed769a982f21ec599ba5fa282", + "sha256:e007f052ed10cc316df59bc90fbb7ff7950d7e2919c9757fd42a2b8ecf8a5f67", + "sha256:f2dcb0b3b63afb6df7fd94ec6fbddac81b5492513f7b0436210d390c14d46ee8", + "sha256:f721d1885ecae9078c3f6bbe8a88bc0786b6e749bf32ccec1ef2b18929a05046", + "sha256:f7a6de3e98771e183645181b3627e2563dcde3ce94a9e42a3f427d2255190327", + "sha256:f8c0a6e9e1dd3eb0414ba320f85da6b0dcbd543126e30fcc546e7372a7fbf3b9" + ], + "version": "==37.0.4" + }, + "docutils": { "hashes": [ - "sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8", - "sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181" + "sha256:33995a6753c30b7f577febfc2c50411fec6aac7f7ffeb7c4cfe5991072dcf9e6", + "sha256:5e1de4d849fee02c63b040a4a3fd567f4ab104defd8a5511fbbc24a8a017efbc" ], - "version": "==20.4" + "version": "==0.19" }, - "pandas": { + "idna": { "hashes": [ - "sha256:09e0503758ad61afe81c9069505f8cb8c1e36ea8cc1e6826a95823ef5b327daf", - "sha256:0a11a6290ef3667575cbd4785a1b62d658c25a2fd70a5adedba32e156a8f1773", - "sha256:0d9a38a59242a2f6298fff45d09768b78b6eb0c52af5919ea9e45965d7ba56d9", - "sha256:112c5ba0f9ea0f60b2cc38c25f87ca1d5ca10f71efbee8e0f1bee9cf584ed5d5", - "sha256:185cf8c8f38b169dbf7001e1a88c511f653fbb9dfa3e048f5e19c38049e991dc", - "sha256:3aa8e10768c730cc1b610aca688f588831fa70b65a26cb549fbb9f35049a05e0", - "sha256:41746d520f2b50409dffdba29a15c42caa7babae15616bcf80800d8cfcae3d3e", - "sha256:43cea38cbcadb900829858884f49745eb1f42f92609d368cabcc674b03e90efc", - "sha256:5378f58172bd63d8c16dd5d008d7dcdd55bf803fcdbe7da2dcb65dbbf322f05b", - "sha256:54404abb1cd3f89d01f1fb5350607815326790efb4789be60508f458cdd5ccbf", - "sha256:5dac3aeaac5feb1016e94bde851eb2012d1733a222b8afa788202b836c97dad5", - "sha256:5fdb2a61e477ce58d3f1fdf2470ee142d9f0dde4969032edaf0b8f1a9dafeaa2", - "sha256:6613c7815ee0b20222178ad32ec144061cb07e6a746970c9160af1ebe3ad43b4", - "sha256:6d2b5b58e7df46b2c010ec78d7fb9ab20abf1d306d0614d3432e7478993fbdb0", - "sha256:8a5d7e57b9df2c0a9a202840b2881bb1f7a648eba12dd2d919ac07a33a36a97f", - "sha256:8b4c2055ebd6e497e5ecc06efa5b8aa76f59d15233356eb10dad22a03b757805", - "sha256:a15653480e5b92ee376f8458197a58cca89a6e95d12cccb4c2d933df5cecc63f", - "sha256:a7d2547b601ecc9a53fd41561de49a43d2231728ad65c7713d6b616cd02ddbed", - "sha256:a979d0404b135c63954dea79e6246c45dd45371a88631cdbb4877d844e6de3b6", - "sha256:b1f8111635700de7ac350b639e7e452b06fc541a328cf6193cf8fc638804bab8", - "sha256:c5a3597880a7a29a31ebd39b73b2c824316ae63a05c3c8a5ce2aea3fc68afe35", - "sha256:c681e8fcc47a767bf868341d8f0d76923733cbdcabd6ec3a3560695c69f14a1e", - "sha256:cf135a08f306ebbcfea6da8bf775217613917be23e5074c69215b91e180caab4", - "sha256:e2b8557fe6d0a18db4d61c028c6af61bfed44ef90e419ed6fadbdc079eba141e" + "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff", + "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d" ], - "index": "pypi", - "version": "==1.1.4" + "version": "==3.3" + }, + "importlib-metadata": { + "hashes": [ + "sha256:637245b8bab2b6502fcbc752cc4b7a6f6243bb02b31c5c26156ad103d3d45670", + "sha256:7401a975809ea1fdc658c3aa4f78cc2195a0e019c5cbc4c06122884e9ae80c23" + ], + "markers": "python_version < '3.10'", + "version": "==4.12.0" + }, + "iniconfig": { + "hashes": [ + "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3", + "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32" + ], + "version": "==1.1.1" + }, + "jeepney": { + "hashes": [ + "sha256:5efe48d255973902f6badc3ce55e2aa6c5c3b3bc642059ef3a91247bcfcc5806", + "sha256:c0a454ad016ca575060802ee4d590dd912e35c122fa04e70306de3d076cce755" + ], + "version": "==0.8.0" + }, + "keyring": { + "hashes": [ + "sha256:782e1cd1132e91bf459fcd243bcf25b326015c1ac0b198e4408f91fa6791062b", + "sha256:e67fc91a7955785fd2efcbccdd72d7dacf136dbc381d27de305b2b660b3de886" + ], + "version": "==23.7.0" + }, + "packaging": { + "hashes": [ + "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb", + "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522" + ], + "version": "==21.3" }, "pkginfo": { "hashes": [ - "sha256:a6a4ac943b496745cec21f14f021bbd869d5e9b4f6ec06918cffea5a2f4b9193", - "sha256:ce14d7296c673dc4c61c759a0b6c14bae34e34eb819c0017bb6ca5b7292c56e9" + "sha256:848865108ec99d4901b2f7e84058b6e7660aae8ae10164e015a6dcf5b242a594", + "sha256:a84da4318dd86f870a9447a8c98340aa06216bfc6f2b7bdc4b8766984ae1867c" ], - "version": "==1.6.1" + "version": "==1.8.3" }, "pluggy": { "hashes": [ - "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0", - "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d" + "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159", + "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3" ], - "version": "==0.13.1" + "version": "==1.0.0" }, "py": { "hashes": [ - "sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2", - "sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342" + "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719", + "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378" ], - "version": "==1.9.0" + "version": "==1.11.0" }, "pycparser": { "hashes": [ - "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0", - "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705" + "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9", + "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206" ], - "version": "==2.20" + "version": "==2.21" }, "pygments": { "hashes": [ - "sha256:381985fcc551eb9d37c52088a32914e00517e57f4a21609f48141ba08e193fa0", - "sha256:88a0bbcd659fcb9573703957c6b9cff9fab7295e6e76db54c9d00ae42df32773" + "sha256:5eb116118f9612ff1ee89ac96437bb6b49e8f04d8a13b514ba26f620208e26eb", + "sha256:dc9c10fb40944260f6ed4c688ece0cd2048414940f1cea51b8b226318411c519" ], - "version": "==2.7.2" + "version": "==2.12.0" }, "pyparsing": { "hashes": [ - "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1", - "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b" + "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb", + "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc" ], - "version": "==2.4.7" + "version": "==3.0.9" }, "pytest": { "hashes": [ - "sha256:4288fed0d9153d9646bfcdf0c0428197dba1ecb27a33bb6e031d002fa88653fe", - "sha256:c0a7e94a8cdbc5422a51ccdad8e6f1024795939cc89159a0ae7f0b316ad3823e" + "sha256:13d0e3ccfc2b6e26be000cb6568c832ba67ba32e719443bfe725814d3c42433c", + "sha256:a06a0425453864a270bc45e71f783330a7428defb4230fb5e6a731fde06ecd45" ], "index": "pypi", - "version": "==6.1.2" - }, - "python-dateutil": { - "hashes": [ - "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c", - "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a" - ], - "version": "==2.8.1" - }, - "pytz": { - "hashes": [ - "sha256:a494d53b6d39c3c6e44c3bec237336e14305e4f29bbf800b599253057fbb79ed", - "sha256:c35965d010ce31b23eeb663ed3cc8c906275d6be1a34393a1d73a41febf4a048" - ], - "version": "==2020.1" + "version": "==7.1.2" }, "readme-renderer": { "hashes": [ - "sha256:267854ac3b1530633c2394ead828afcd060fc273217c42ac36b6be9c42cd9a9d", - "sha256:6b7e5aa59210a40de72eb79931491eaf46fefca2952b9181268bd7c7c65c260a" + "sha256:73b84905d091c31f36e50b4ae05ae2acead661f6a09a9abb4df7d2ddcdb6a698", + "sha256:a727999acfc222fc21d82a12ed48c957c4989785e5865807c65a487d21677497" ], - "version": "==28.0" + "version": "==35.0" }, "requests": { "hashes": [ - "sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b", - "sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898" + "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983", + "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349" ], - "version": "==2.24.0" + "version": "==2.28.1" }, "requests-mock": { "hashes": [ - "sha256:11215c6f4df72702aa357f205cf1e537cffd7392b3e787b58239bde5fb3db53b", - "sha256:e68f46844e4cee9d447150343c9ae875f99fa8037c6dcf5f15bf1fe9ab43d226" - ], - "index": "pypi", - "version": "==1.8.0" - }, - "requests-oauthlib": { - "hashes": [ - "sha256:7f71572defaecd16372f9006f33c2ec8c077c3cfa6f5911a9a90202beb513f3d", - "sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a", - "sha256:fa6c47b933f01060936d87ae9327fead68768b69c6c9ea2109c48be30f2d4dbc" + "sha256:0a2d38a117c08bb78939ec163522976ad59a6b7fdd82b709e23bb98004a44970", + "sha256:8d72abe54546c1fc9696fa1516672f1031d72a55a1d66c85184f972a24ba0eba" ], "index": "pypi", - "version": "==1.3.0" + "version": "==1.9.3" }, "requests-toolbelt": { "hashes": [ @@ -406,54 +500,71 @@ }, "rfc3986": { "hashes": [ - "sha256:112398da31a3344dc25dbf477d8df6cb34f9278a94fee2625d89e4514be8bb9d", - "sha256:af9147e9aceda37c91a05f4deb128d4b4b49d6b199775fd2d2927768abdc8f50" + "sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd", + "sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c" ], - "version": "==1.4.0" + "version": "==2.0.0" + }, + "rich": { + "hashes": [ + "sha256:2eb4e6894cde1e017976d2975ac210ef515d7548bc595ba20e195fb9628acdeb", + "sha256:63a5c5ce3673d3d5fbbf23cd87e11ab84b6b451436f1b7f19ec54b6bc36ed7ca" + ], + "version": "==12.5.1" }, "secretstorage": { "hashes": [ - "sha256:15da8a989b65498e29be338b3b279965f1b8f09b9668bd8010da183024c8bff6", - "sha256:b5ec909dde94d4ae2fa26af7c089036997030f0cf0a5cb372b4cccabd81c143b" + "sha256:0a8eb9645b320881c222e827c26f4cfcf55363e8b374a021981ef886657a912f", + "sha256:755dc845b6ad76dcbcbc07ea3da75ae54bb1ea529eb72d15f83d26499a5df319" ], "markers": "sys_platform == 'linux'", - "version": "==3.1.2" + "version": "==3.3.2" }, "six": { "hashes": [ - "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", - "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" + "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", + "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" ], - "version": "==1.15.0" + "version": "==1.16.0" }, - "toml": { + "tabulate": { "hashes": [ - "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", - "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" + "sha256:0ba055423dbaa164b9e456abe7920c5e8ed33fcc16f6d1b2f2d152c8e1e8b4fc", + "sha256:436f1c768b424654fce8597290d2764def1eea6a77cfa5c33be00b1bc0f4f63d", + "sha256:6c57f3f3dd7ac2782770155f3adb2db0b1a269637e42f27599925e64b114f519" ], - "version": "==0.10.2" + "index": "pypi", + "version": "==0.8.10" }, - "tqdm": { + "tomli": { "hashes": [ - "sha256:9ad44aaf0fc3697c06f6e05c7cf025dd66bc7bcb7613c66d85f4464c47ac8fad", - "sha256:ef54779f1c09f346b2b5a8e5c61f96fbcb639929e640e59f8cf810794f406432" + "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc", + "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f" ], - "version": "==4.51.0" + "version": "==2.0.1" }, "twine": { "hashes": [ - "sha256:34352fd52ec3b9d29837e6072d5a2a7c6fe4290e97bba46bb8d478b5c598f7ab", - "sha256:ba9ff477b8d6de0c89dd450e70b2185da190514e91c42cc62f96850025c10472" + "sha256:42026c18e394eac3e06693ee52010baa5313e4811d5a11050e7d48436cf41b9e", + "sha256:96b1cf12f7ae611a4a40b6ae8e9570215daff0611828f5fe1f37a16255ab24a0" ], "index": "pypi", - "version": "==3.2.0" + "version": "==4.0.1" + }, + "typing-extensions": { + "hashes": [ + "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02", + "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6" + ], + "markers": "python_version < '3.9'", + "version": "==4.3.0" }, "urllib3": { "hashes": [ - "sha256:8d7eaa5a82a1cac232164990f04874c594c9453ec55eef02eab885aa02fc17a2", - "sha256:f5321fbe4bf3fefa0efd0bfe7fb14e90909eb62a48ccda331726b4319897dd5e" + "sha256:8298d6d56d39be0e3bc13c1c97d133f9b45d797169a0e11cdd0e0489d786f7ec", + "sha256:879ba4d1e89654d9769ce13121e0f94310ea32e8d2f8cf587b77c08bbcdb30d6" ], - "version": "==1.25.11" + "version": "==1.26.10" }, "webencodings": { "hashes": [ @@ -462,13 +573,20 @@ ], "version": "==0.5.1" }, - "werkzeug": { + "xlsxwriter": { "hashes": [ - "sha256:2de2a5db0baeae7b2d2664949077c2ac63fbd16d98da0ff71837f7d1dea3fd43", - "sha256:6c80b1e5ad3665290ea39320b91e1be1e0d5f60652b964a3070216de83d2e47c" + "sha256:df0aefe5137478d206847eccf9f114715e42aaea077e6a48d0e8a2152e983010", + "sha256:e89f4a1d2fa2c9ea15cde77de95cd3fd8b0345d0efb3964623f395c8c4988b7f" ], - "version": "==1.0.1" + "index": "pypi", + "version": "==3.0.3" + }, + "zipp": { + "hashes": [ + "sha256:05b45f1ee8f807d0cc928485ca40a07cb491cf092ff587c0df9cb1fd154848d2", + "sha256:47c40d7fe183a6f21403a199b3e4192cca5774656965b0a4988ad2f8feb5f009" + ], + "version": "==3.8.1" } - }, - "develop": {} + } } diff --git a/README.md b/README.md index 720ed9a..408eb56 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,39 @@ ## Installation -Easiest way is to get it from PyPI: +Easiest way is to get it from [PyPI](https://pypi.org/project/oura/): `pip install oura` +## Project maintenance +If anyone is interested in taking over maintenance of this project, please contact me at +turingcomplet@proton.me or submit an issue. Alternatively, feel free to simply fork this +repo or create a new one and publish the package under a new name. In that case, I will +add a link here. I still plan on doing what I can to keep this up to date, but don't +feel I can commit to maintaining a level of quality and responsiveness that you all +deserve. + +## Note about the v2 API +All sections in the readme should apply to the v2 API that is being rolled out, except that the +methods will differ, pandas support is less sophisticated, and I haven't tested the v2 +clients with the OAuth2 flow. The auth has been updated to pass tokens in the http +header, but the usage of the underlying `requests-oauthlib` library has not been +changed. + +Enjoy the latest clients as follows (and see +[docs](https://cloud.ouraring.com/v2/docs)). All methods except `personal_info` take a +`start_date`, `end_date`, and `next_token`. +``` +from oura.v2 import OuraClientV2, OuraClientDataFrameV2 +v2 = OuraClientDataFrameV2(personal_access_token="MY_PAT") + +# methods will be named after the url path (see docs linked above) +v2.heartrate() + +# pandas methods end with _df +v2.tags_df() +``` + ## Getting started Both personal access tokens and oauth flows are supported by the API (and by diff --git a/noxfile.py b/noxfile.py index 18a5368..b1aa47f 100644 --- a/noxfile.py +++ b/noxfile.py @@ -1,14 +1,14 @@ import nox nox.options.sessions = "lint", "tests" -locations = ["oura", "tests", "samples"] +locations = ["oura", "tests", "samples", "noxfile.py"] @nox.session def tests(session): args = session.posargs session.install("pipenv") - session.run("pipenv", "sync") + session.run("pipenv", "sync", "--dev") session.run("pipenv", "run", "pytest", *args) @@ -18,7 +18,7 @@ def lint(session): session.install("flake8", "black", "isort") session.run("flake8", *args) session.run("black", "--check", "--diff", *args) - session.run("isort", "-m", "3", "--tc", "--check", "--diff", *args) + session.run("isort", "--profile", "black", "--check", "--diff", *args) @nox.session @@ -36,7 +36,7 @@ def black(session): def isort(session): args = session.posargs or locations session.install("isort") - session.run("isort", "-m", "3", "--tc", *args) + session.run("isort", "--profile", "black", *args) @nox.session diff --git a/oura/__init__.py b/oura/__init__.py index fb90c58..2db85e7 100644 --- a/oura/__init__.py +++ b/oura/__init__.py @@ -16,3 +16,4 @@ from .auth import OAuthRequestHandler, OuraOAuth2Client, PersonalRequestHandler from .client import OuraClient from .client_pandas import OuraClientDataFrame +from .export import writers diff --git a/oura/auth.py b/oura/auth.py index eaf9bd5..03a118f 100644 --- a/oura/auth.py +++ b/oura/auth.py @@ -10,7 +10,7 @@ class OuraOAuth2Client: AUTHORIZE_BASE_URL = "https://cloud.ouraring.com/oauth/authorize" TOKEN_BASE_URL = "https://api.ouraring.com/oauth/token" - SCOPE = ["email", "personal", "daily"] + SCOPE = ["email", "personal", "daily", "heartrate", "workout", "tag", "session"] def __init__(self, client_id, client_secret): @@ -92,6 +92,9 @@ def make_request(self, url): response = self._session.request(method, url) return response + def make_request_v2(self, url): + return self.make_request(url) + def _refresh_token(self): token = self._session.refresh_token( self.TOKEN_BASE_URL, @@ -110,3 +113,7 @@ def __init__(self, personal_access_token): def make_request(self, url): return requests.get(url, params={"access_token": self.personal_access_token}) + + def make_request_v2(self, url): + headers = {"Authorization": f"Bearer {self.personal_access_token}"} + return requests.get(url, headers=headers) diff --git a/oura/client_pandas.py b/oura/client_pandas.py index cfae0ee..e0512ff 100644 --- a/oura/client_pandas.py +++ b/oura/client_pandas.py @@ -9,12 +9,12 @@ def to_pandas(summary, metrics=None, date_key="summary_date"): Creates a dataframe from a summary object :param summary: A summary object returned from API - :type summary: dictionary of dictionaries. See https://cloud.ouraring.com/docs/readiness for an example + :type summary: list of dictionaries. See https://cloud.ouraring.com/docs/readiness for an example :param metrics: The metrics to include in the DF. None includes all metrics :type metrics: A list of metric names, or alternatively a string for one metric name - :param date_key: Column to set as the index, mainly for internal use + :param date_key: Column to set as the index :type date_key: str """ @@ -71,6 +71,10 @@ def __init__( personal_access_token, ) + def user_info_df(self): + user_info = super().user_info() + return pd.DataFrame([user_info]) + def sleep_df( self, start=None, end=None, metrics=None, convert=True, convert_cols=None ): diff --git a/oura/converters.py b/oura/converters.py index f25f3d6..176d28d 100644 --- a/oura/converters.py +++ b/oura/converters.py @@ -53,7 +53,7 @@ def _convert_to_dt(self, df, dt_metrics): :param dt_metrics: List of metrics to be converted to datetime :type dt_metrics: List """ - for i, dt_metric in enumerate(dt_metrics): + for _, dt_metric in enumerate(dt_metrics): df[dt_metric] = pd.to_datetime(df[dt_metric], format="%Y-%m-%d %H:%M:%S") df = self._rename_converted_cols(df, dt_metrics, "_dt_adjusted") return df diff --git a/oura/exceptions.py b/oura/exceptions.py index 9cfef33..ca52e48 100644 --- a/oura/exceptions.py +++ b/oura/exceptions.py @@ -52,6 +52,12 @@ class HTTPConflict(HTTPException): pass +class HTTPUpgradeRequired(HTTPException): + """426 - returned when the user does not have an updated version of the app""" + + pass + + class HTTPTooManyRequests(HTTPException): """429 - returned when exceeding rate limits""" @@ -73,6 +79,8 @@ def detect_and_raise_error(response): raise HTTPNotFound(response) elif response.status_code == 409: raise HTTPConflict(response) + elif response.status_code == 426: + raise HTTPUpgradeRequired(response) elif response.status_code == 429: exc = HTTPTooManyRequests(response) exc.retry_after_secs = int(response.headers["Retry-After"]) diff --git a/oura/export/__init__.py b/oura/export/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/oura/writers.py b/oura/export/writers.py similarity index 80% rename from oura/writers.py rename to oura/export/writers.py index b194651..a427d6e 100644 --- a/oura/writers.py +++ b/oura/export/writers.py @@ -28,17 +28,16 @@ def localize(df): df = df.copy() df = localize(df) - writer = pd.ExcelWriter( + with pd.ExcelWriter( file, engine="xlsxwriter", date_format="m/d/yyy", datetime_format="m/d/yyy h:mmAM/PM", - ) - df.to_excel(writer, index=index, **to_excel_kwargs) - writer.save() + ) as writer: + df.to_excel(writer, index=index, **to_excel_kwargs) -def tableize(df, tablefmt="pretty", is_print=True, filename=None): +def tableize(df, tablefmt="pretty", is_print=True): """ Converts dataframe to a formatted table For more details, see https://pypi.org/project/tabulate/ @@ -51,9 +50,6 @@ def tableize(df, tablefmt="pretty", is_print=True, filename=None): :param is_print: print to standard output? :type is_print: boolean - - :param filename: optionally, filename to print to - :type filename: string """ from tabulate import tabulate @@ -67,7 +63,4 @@ def tableize(df, tablefmt="pretty", is_print=True, filename=None): ) if is_print: print(table) - if filename: - with open(filename, "w") as f: - print(table, file=f) return table diff --git a/oura/v2/__init__.py b/oura/v2/__init__.py new file mode 100644 index 0000000..f5abcff --- /dev/null +++ b/oura/v2/__init__.py @@ -0,0 +1,2 @@ +from .client import OuraClientV2 +from .client_pandas import OuraClientDataFrameV2 diff --git a/oura/v2/client.py b/oura/v2/client.py new file mode 100644 index 0000000..9e95dd4 --- /dev/null +++ b/oura/v2/client.py @@ -0,0 +1,97 @@ +import json + +from .. import exceptions +from ..auth import OAuthRequestHandler, PersonalRequestHandler + + +class OuraClientV2: + + API_ENDPOINT = "https://api.ouraring.com/v2/usercollection" + + def __init__( + self, + client_id=None, + client_secret=None, + access_token=None, + refresh_token=None, + refresh_callback=None, + personal_access_token=None, + ): + """ + :param client_id: The client id - identifies your application. + :type client_id: str + + :param client_secret: The client secret. Required for auto refresh. + :type client_secret: str + + :param access_token: Access token. + :type access_token: str + + :param refresh_token: Use this to renew tokens when they expire + :type refresh_token: str + + :param refresh_callback: Callback to handle token response + :type refresh_callback: callable + + :param personal_access_token: Token used for accessing personal data + :type personal_access_token: str + """ + + if client_id is not None: + self._auth_handler = OAuthRequestHandler( + client_id, client_secret, access_token, refresh_token, refresh_callback + ) + + if personal_access_token is not None: + self._auth_handler = PersonalRequestHandler(personal_access_token) + + def daily_activity(self, start_date=None, end_date=None, next_token=None): + # end_date default to current UTC date + # start_date default to end_date - 1 day + return self._get_summary(start_date, end_date, next_token, "daily_activity") + + def heartrate(self, start_date=None, end_date=None, next_token=None): + return self._get_summary(start_date, end_date, next_token, "heartrate") + + def personal_info(self): + url = f"{self.API_ENDPOINT}/personal_info" + return self._make_request(url) + + def session(self, start_date=None, end_date=None, next_token=None): + return self._get_summary(start_date, end_date, next_token, "session") + + def tags(self, start_date=None, end_date=None, next_token=None): + return self._get_summary(start_date, end_date, next_token, "tag") + + def workouts(self, start_date=None, end_date=None, next_token=None): + return self._get_summary(start_date, end_date, next_token, "workout") + + def _get_summary(self, start_date, end_date, next_token, summary_type): + url = self._build_summary_url(start_date, end_date, next_token, summary_type) + return self._make_request(url) + + def _make_request(self, url): + response = self._auth_handler.make_request_v2(url) + exceptions.detect_and_raise_error(response) + payload = json.loads(response.content.decode("utf8")) + return payload + + def _build_summary_url(self, start_date, end_date, next_token, summary_type): + url = f"{self.API_ENDPOINT}/{summary_type}" + params = {} + if start_date is not None: + if not isinstance(start_date, str): + raise TypeError("start date must be of type str") + params["start_date"] = start_date + + if end_date is not None: + if not isinstance(end_date, str): + raise TypeError("end date must be of type str") + params["end_date"] = end_date + + if next_token is not None: + params["next_token"] = next_token + + qs = "&".join([f"{k}={v}" for k, v in params.items()]) + url = f"{url}?{qs}" if qs != "" else url + return url diff --git a/oura/v2/client_pandas.py b/oura/v2/client_pandas.py new file mode 100644 index 0000000..92eb795 --- /dev/null +++ b/oura/v2/client_pandas.py @@ -0,0 +1,97 @@ +import pandas as pd + +from .client import OuraClientV2 + + +def to_pandas(summary, metrics=None, date_key="timestamp"): + """ + Creates a dataframe from a summary object + + :param summary: A summary object returned from API + :type summary: list of dictionaries. See https://cloud.ouraring.com/v2/docs#tag/Daily-Activity for example + + :param metrics: The metrics to include in the DF. None includes all metrics + :type metrics: A list of metric names, or alternatively a string for one metric name + + :param date_key: Column to set as the index + :type date_key: str + """ + + if isinstance(summary, dict): + summary = [summary] + df = pd.DataFrame(summary) + if df.size == 0: + return df + if metrics is not None: + if type(metrics) == str: + metrics = [metrics] + else: + metrics = metrics.copy() + # drop any invalid cols the user may have entered + metrics = [m for m in metrics if m in df.columns] + + # always include summary_date (or date_key, as for bedtime) + if date_key not in metrics: + metrics.insert(0, date_key) + + df = df[metrics] + df[date_key] = pd.to_datetime(df[date_key]).dt.date + df = df.set_index(date_key) + return df + + +class OuraClientDataFrameV2(OuraClientV2): + """ + Similiar to OuraClientV2, but data is returned instead + as a pandas.DataFrame object. Each row will correspond to a single day + of data, indexed by the date. + """ + + def __init__( + self, + client_id=None, + client_secret=None, + access_token=None, + refresh_token=None, + refresh_callback=None, + personal_access_token=None, + ): + super().__init__( + client_id, + client_secret, + access_token, + refresh_token, + refresh_callback, + personal_access_token, + ) + + def activity_df(self, start=None, end=None, metrics=None): + activity_summary = super().daily_activity(start, end)["data"] + df = to_pandas(activity_summary, metrics) + return df + + def heart_rate_df(self, start=None, end=None, metrics=None): + readiness_summary = super().heartrate(start, end)["data"] + return to_pandas(readiness_summary, metrics) + + def personal_info_df(self): + info = super().personal_info() + return pd.DataFrame([info]) + + def sessions_df(self, start=None, end=None, metrics=None): + sessions = super().session(start, end)["data"] + return to_pandas(sessions, metrics, date_key="day") + + def tags_df(self, start=None, end=None, metrics=None): + tags = super().tags(start, end)["data"] + return to_pandas(tags, metrics) + + def workouts_df(self, start=None, end=None, metrics=None): + workouts = super().workouts(start, end)["data"] + return to_pandas(workouts, metrics, date_key="day") + + def sleep_df(self, start=None, end=None, metrics=None): + raise NotImplementedError + + def readiness_df(self, start=None, end=None, metrics=None): + raise NotImplementedError diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..2944221 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,27 @@ +[metadata] +name = oura +version = 1.2.4 +author = turing-complet +author_email = turingcomplet@proton.me +description = Oura API client +long_description = file: README.md +long_description_content_type = text/markdown +url = https://github.com/turing-complet/python-ouraring +project_urls = + Bug Tracker = https://github.com/turing-complet/python-ouraring/issues +classifiers = + License :: OSI Approved :: MIT License + Operating System :: OS Independent + Programming Language :: Python :: 3 + Programming Language :: Python :: 3.8 + Programming Language :: Python :: 3.9 + +[options] +packages = find: +python_requires = >=3.8 +install_requires = + requests-oauthlib + pandas + +[options.packages.find] +exclude = tests diff --git a/setup.py b/setup.py index 30a3a1b..6068493 100644 --- a/setup.py +++ b/setup.py @@ -1,119 +1,3 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- +from setuptools import setup -# Note: To use the 'upload' functionality of this file, you must: -# $ pip install twine - -import io -import os -import sys -from shutil import rmtree - -from setuptools import find_packages, setup, Command - -# Package meta-data. -NAME = "oura" -DESCRIPTION = "Oura api client." -URL = "https://github.com/turing-complet/python-ouraring" -EMAIL = "jhagg314@gmail.com" -AUTHOR = "Jon Hagg" -REQUIRES_PYTHON = ">=3.6" -VERSION = "1.1.4" - -REQUIRED = ["requests-oauthlib", "pandas"] - -EXTRAS = { - # 'fancy feature': ['django'], -} - -here = os.path.abspath(os.path.dirname(__file__)) - -try: - with io.open(os.path.join(here, "README.md"), encoding="utf-8") as f: - long_description = "\n" + f.read() -except FileNotFoundError: - long_description = DESCRIPTION - -about = {} -about["__version__"] = VERSION - - -class UploadCommand(Command): - """Support setup.py upload.""" - - description = "Build and publish the package." - user_options = [("test", None, "Upload to test server")] - - @staticmethod - def status(s): - """Prints things in bold.""" - print("\033[1m{0}\033[0m".format(s)) - - def initialize_options(self): - self.test = False - self.test_server = "https://test.pypi.org/legacy/" - - def finalize_options(self): - pass - - def run(self): - try: - self.status("Removing previous builds…") - rmtree(os.path.join(here, "dist")) - except OSError: - pass - - self.status("Building Source and Wheel (universal) distribution…") - os.system("{0} setup.py sdist bdist_wheel --universal".format(sys.executable)) - - if self.test: - self.status("Uploading the package to test server via Twine…") - os.system( - "twine upload --repository-url {} dist/*".format(self.test_server) - ) - else: - self.status("Uploading the package to PyPI via Twine…") - os.system("twine upload dist/*") - - # self.status('Pushing git tags…') - # os.system('git tag v{0}'.format(about['__version__'])) - # os.system('git push --tags') - sys.exit() - - -# Where the magic happens: -setup( - name=NAME, - version=about["__version__"], - description=DESCRIPTION, - long_description=long_description, - long_description_content_type="text/markdown", - author=AUTHOR, - author_email=EMAIL, - python_requires=REQUIRES_PYTHON, - url=URL, - packages=find_packages(exclude=("tests",)), - # If your package is a single module, use this instead of 'packages': - # py_modules=['oura.client'], - # entry_points={ - # 'console_scripts': ['mycli=mymodule:cli'], - # }, - install_requires=REQUIRED, - extras_require=EXTRAS, - include_package_data=True, - license="MIT", - # $ setup.py publish support. - classifiers=[ - "Intended Audience :: Developers", - "Natural Language :: English", - "License :: OSI Approved :: MIT License", - "Programming Language :: Python", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - ], - cmdclass={ - "upload": UploadCommand, - }, -) +setup() diff --git a/tests/mock_client_v2.py b/tests/mock_client_v2.py new file mode 100644 index 0000000..174532f --- /dev/null +++ b/tests/mock_client_v2.py @@ -0,0 +1,141 @@ +from oura.v2 import OuraClientDataFrameV2, OuraClientV2 + + +class MockClientV2(OuraClientV2): + def personal_info(self): + return { + "age": 31, + "weight": 74.8, + "height": 1.8, + "biological_sex": "male", + "email": "example@example.com", + } + + def daily_activity(self, start_date=None, end_date=None, next_token=None): + return { + "data": [ + { + "class_5_min": "000000000000000000000000000000000000000000000000000000000000000000000000003444544444445545455443454554454443334333322330000000000232232222222232222222322223222000000022332233422333222232233333222222222222222332223212233222122221111111111111121111111111111111111111111111111111111111111111", + "score": 82, + "active_calories": 1222, + "average_met_minutes": 1.90625, + "contributors": { + "meet_daily_targets": 43, + "move_every_hour": 100, + "recovery_time": 100, + "stay_active": 98, + "training_frequency": 71, + "training_volume": 98, + }, + "equivalent_walking_distance": 20122, + "high_activity_met_minutes": 444, + "high_activity_time": 3000, + "inactivity_alerts": 0, + "low_activity_met_minutes": 117, + "low_activity_time": 10020, + "medium_activity_met_minutes": 391, + "medium_activity_time": 6060, + "met": { + "interval": 60, + "items": [ # truncated for readability + 0.1, + 0.1, + 0.1, + 0.1, + 0.9, + 0.9, + 0.9, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.9, + 0.9, + 0.9, + 0.9, + 0.9, + 0.9, + 0.9, + ], + "timestamp": "2021-11-26T04:00:00.000-08:00", + }, + "meters_to_target": -16200, + "non_wear_time": 27480, + "resting_time": 18840, + "sedentary_met_minutes": 10, + "sedentary_time": 21000, + "steps": 18430, + "target_calories": 350, + "target_meters": 7000, + "total_calories": 3446, + "day": "2021-11-26", + "timestamp": "2021-11-26T04:00:00-08:00", + } + ], + "next_token": None, + } + + def heartrate(self, start_date=None, end_date=None, next_token=None): + return { + "data": [ + {"bpm": 60, "source": "sleep", "timestamp": "2021-01-01T01:02:03+00:00"} + ], + "next_token": None, + } + + def session(self, start_date=None, end_date=None, next_token=None): + return { + "data": [ + { + "day": "2021-11-12", + "start_datetime": "2021-11-12T12:32:09-08:00", + "end_datetime": "2021-11-12T12:40:49-08:00", + "type": "rest", + "heart_rate": None, + "heart_rate_variability": None, + "mood": None, + "motion_count": { + "interval": 5, + "items": [0], + "timestamp": "2021-11-12T12:32:09.000-08:00", + }, + } + ], + "next_token": None, + } + + def tags(self, start_date=None, end_date=None, next_token=None): + return { + "data": [ + { + "day": "2021-01-01", + "text": "Need coffee", + "timestamp": "2021-01-01T01:02:03-08:00", + "tags": ["tag_generic_nocaffeine"], + } + ], + "next_token": None, + } + + def workouts(self, start_date=None, end_date=None, next_token=None): + return { + "data": [ + { + "activity": "cycling", + "calories": 300, + "day": "2021-01-01", + "distance": 13500.5, + "end_datetime": "2021-01-01T01:00:00.000000+00:00", + "intensity": "moderate", + "label": None, + "source": "manual", + "start_datetime": "2021-01-01T01:30:00.000000+00:00", + } + ], + "next_token": None, + } + + +class MockDataFrameClientV2(OuraClientDataFrameV2, MockClientV2): + pass diff --git a/tests/test_mocks.py b/tests/test_mocks.py new file mode 100644 index 0000000..0ef8bac --- /dev/null +++ b/tests/test_mocks.py @@ -0,0 +1,20 @@ +from .mock_client import MockDataFrameClient +from .mock_client_v2 import MockDataFrameClientV2 + + +def test_v1(): + v1 = MockDataFrameClient() + v1.activity_df() + v1.bedtime_df() + v1.readiness_df() + v1.user_info_df() + + +def test_v2(): + v2 = MockDataFrameClientV2() + v2.activity_df() + v2.heart_rate_df() + v2.personal_info_df() + v2.sessions_df() + v2.tags_df() + v2.workouts_df() diff --git a/tests/test_writers.py b/tests/test_writers.py index 26d80cb..5a1bfb6 100644 --- a/tests/test_writers.py +++ b/tests/test_writers.py @@ -1,37 +1,41 @@ import os -import pytest +from oura.export.writers import save_as_xlsx, tableize from .mock_client import MockDataFrameClient client = MockDataFrameClient() -@pytest.mark.skip def test_save_xlsx(): """ Check that both raw and edited df's save without issue """ - df_raw = client.sleep_df_raw(start="2020-09-30") - df_edited = client.sleep_df_edited( + df_raw = client.sleep_df(start="2020-09-30", convert=False) + df_edited = client.sleep_df( start="2020-09-30", end="2020-10-01", metrics=["bedtime_start", "bedtime_end", "score"], ) raw_file = "df_raw.xlsx" edited_file = "df_edited.xlsx" - client.save_as_xlsx(df_raw, raw_file, sheet_name="hello world") - client.save_as_xlsx(df_edited, "df_edited.xlsx") + save_as_xlsx(df_raw, raw_file, sheet_name="hello world") + save_as_xlsx(df_edited, edited_file) assert os.path.exists(raw_file) assert os.path.exists(edited_file) -@pytest.mark.skip def test_tableize(): """ - Check that df was printed to file + Check df table is correct """ - f = "df_tableized.txt" - df_raw = client.sleep_df_raw(start="2020-09-30", metrics="score") - client.tableize(df_raw, filename=f) - assert os.path.exists(f) + expected = """ ++--------------+-------+ +| summary_date | score | ++--------------+-------+ +| 2017-11-05 | 70 | ++--------------+-------+ + """.strip() + df = client.sleep_df(start="2020-09-30", metrics="score", convert=False) + table = tableize(df, is_print=False) + assert expected == table