Skip to content

Commit

Permalink
TST: Fixes to suite tests for Strava Client and added docs.
Browse files Browse the repository at this point in the history
  • Loading branch information
Marcel Pinheiro Caraciolo committed Jul 29, 2022
1 parent b313d25 commit e46e3d0
Show file tree
Hide file tree
Showing 9 changed files with 273 additions and 35 deletions.
58 changes: 45 additions & 13 deletions examples/strava.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,34 @@
"import runpandas\n",
"import os\n",
"import pandas as pd\n",
"pd.set_option('display.max_rows', 500)\n"
"pd.set_option('display.max_rows', 500)"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "360f8407",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from dotenv import load_dotenv\n",
"load_dotenv()"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "346f6024",
"metadata": {},
"outputs": [],
Expand All @@ -35,21 +57,28 @@
"id": "32938533",
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"Unable to set attribute media_type on entity <ActivityPhotoPrimary id=None>\n"
]
},
{
"data": {
"text/plain": [
"Session Running: 25-09-2021 05:29:40\n",
"Total distance (meters) 100940.5\n",
"Total ellapsed time 0 days 17:00:43\n",
"Total moving time 0 days 15:21:50\n",
"Session Running: 18-06-2022 07:08:07\n",
"Total distance (meters) 21389.8\n",
"Total ellapsed time 0 days 02:02:20\n",
"Total moving time 0 days 02:02:19\n",
"Average speed (km/h) NaN\n",
"Average moving speed (km/h) NaN\n",
"Average pace (per 1 km) NaN\n",
"Average pace moving (per 1 km) NaN\n",
"Average cadence 65.456174\n",
"Average moving cadence 69.492244\n",
"Average heart rate 119.068156\n",
"Average moving heart rate 120.551058\n",
"Average cadence 87.7889\n",
"Average moving cadence 87.847\n",
"Average heart rate 155.674\n",
"Average moving heart rate 155.713\n",
"Average temperature NaN\n",
"dtype: object"
]
Expand All @@ -60,16 +89,19 @@
}
],
"source": [
"activity = runpandas.read_strava('6019886157')\n",
"activity = runpandas.read_strava('7329257123')\n",
"activity.summary()"
]
}
],
"metadata": {
"interpreter": {
"hash": "2a188acd0f27a53b17cfad69c436eac3f19ae51e9e26340e7d32ca2c8c278930"
},
"kernelspec": {
"display_name": "runpandas",
"display_name": "Python 3.8.3 ('runpandas_dev')",
"language": "python",
"name": "runpandas"
"name": "python3"
},
"language_info": {
"codemirror_mode": {
Expand All @@ -81,7 +113,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.8.10"
"version": "3.8.3"
}
},
"nbformat": 4,
Expand Down
1 change: 1 addition & 0 deletions examples/token.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"{\"access_token\": \"01dc593b8ab20487df9e6a6fe874ea49aa26f4d5\", \"refresh_token\": \"59c8537628a19d70330e5aca2ec5a558023a2c03\", \"expires_at\": 1658450893}"
2 changes: 1 addition & 1 deletion runpandas/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from runpandas.reader import _read_dir as read_dir # noqa
from runpandas.reader import _read_dir_aggregate as read_dir_aggregate # noqa
from runpandas.io.strava._parser import read_strava # noqa
from runpandas.io.strava._client import StravaClient # noqa
from runpandas.io.strava._client import StravaClient # noqa
from runpandas.io.nikerun._parser import read_nikerun # noqa
from runpandas.io.nikerun._parser import read_dir_nikerun # noqa
from runpandas.datasets.utils import activity_examples # noqa
Expand Down
66 changes: 53 additions & 13 deletions runpandas/io/strava/_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,8 @@
import json
import time
import webbrowser
from json.decoder import JSONDecodeError
from http.server import BaseHTTPRequestHandler, HTTPServer
from urllib.request import urlopen, HTTPError
from webbrowser import open_new
from urllib.parse import urlsplit, parse_qs


Expand All @@ -19,6 +18,7 @@ def coalesce(iterable):
class HTTPResponder(HTTPServer):
allow_reuse_address = True
timeout = 60
access_token = None

def handle_timeout(self):
self.server_close()
Expand All @@ -38,8 +38,7 @@ def do_GET(self):
self.auth_code = parse_qs(urlsplit(self.path).query)["code"]
self.wfile.write(
bytes(
"<html><h1>You may now close this window."
+ "</h1></html>",
"<html><h1>You may now close this window." + "</h1></html>",
"utf-8",
)
)
Expand All @@ -51,6 +50,18 @@ def log_message(self, *args):


class StravaClient(Client):
"""
The StravaClient object is a helper tool for handling
the authentication process (i.e. authorization, token update, ...) against the Strava.
Parameters
----------
token_file: File, The Strava access token path where it will be kept or loaded, optional
refresh_token: str, The Strava refresh token, optional
client_secret: str, The strava client secret used for token refresh, optional
client_id: int, The Strava client id used for token refresh, optional
"""

def __init__(
self,
*args,
Expand All @@ -61,15 +72,11 @@ def __init__(
**kwargs
):
super(self.__class__, self).__init__(*args, **kwargs)
self.token_file = coalesce(
(token_file, os.getenv("STRAVA_TOKEN_FILE", None))
)
self.token_file = coalesce((token_file, os.getenv("STRAVA_TOKEN_FILE", None)))
self.client_secret = coalesce(
(client_secret, os.getenv("STRAVA_CLIENT_SECRET", None))
)
self.client_id = coalesce(
(client_id, os.getenv("STRAVA_CLIENT_ID", None))
)
self.client_id = coalesce((client_id, os.getenv("STRAVA_CLIENT_ID", None)))

if self.token_file is not None:
self.get_token_from_file()
Expand All @@ -78,26 +85,50 @@ def __init__(
self.refresh_token = refresh_token

def set_token_from_dict(self, token):
"""
It extracts the token components from a token dict response.
"""

self.access_token = token["access_token"]
self.refresh_token = token["refresh_token"]
self.token_expires_at = token["expires_at"]

def get_token_from_file(self):
"""
Gets the token from a token_file and returns as dict.
"""
if self.token_file is not None:
with open(self.token_file, "r") as f:
token = json.load(f)
try:
token = json.load(f)
except JSONDecodeError:
print(
"problems on reading %s file. It considers an empty file."
% self.token_file
)
token = None
if token is not None:
if type(token) == str:
token = json.loads(token)
self.set_token_from_dict(token)

def save_token_to_file(self, token):
"""
It saves the token to the file
Parameters
----------
token: File, the file where the token will be stored at.
"""
if self.token_file is None:
return None
with open(self.token_file, "w") as f:
f.write(json.dumps(token))

def refresh(self):
"""
It updates the access token if the token is expired.
"""
if time.time() > self.token_expires_at:
token = self.refresh_access_token(
client_id=self.client_id,
Expand All @@ -109,6 +140,14 @@ def refresh(self):
f.write(json.dumps(token))

def authenticate_web(self):
"""
It handles the authentication web negotiation with Strava.
Returns
-------
token_response: dict, the token response from Strava.
"""
scope = [
"read",
"read_all",
Expand All @@ -135,7 +174,8 @@ def authenticate_web(self):
code=httpServer.access_token,
)
# Save it to file so we can use it until it expires.
access_token_string = json.dumps(token_response)
if self.token_file is not None:
with open(self.token_file, "w+") as f:
json.dump(access_token_string, f)
json.dump(token_response, f)

return token_response
3 changes: 0 additions & 3 deletions runpandas/io/strava/_parser.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
"""
Tools for pulling and parsing stream data from Strava.
"""
import time
from datetime import timedelta
import pandas as pd
from pandas import TimedeltaIndex
from runpandas import _utils as utils
from runpandas.types import Activity
from runpandas.types import columns
from runpandas.io.strava._client import StravaClient
import os


COLUMNS_SCHEMA = {
Expand Down Expand Up @@ -83,7 +81,6 @@ def read_strava(
Return a obj:`runpandas.Activity` if `to_df=True`, otherwise
a :obj:`pandas.DataFrame` will be returned.
"""

if client is None:
client = StravaClient()
client.refresh()
Expand Down
1 change: 1 addition & 0 deletions runpandas/tests/io/data/strava/token.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"access_token": "2334444", "refresh_token": "235555", "expires_at": 1658341120.318284}
27 changes: 26 additions & 1 deletion runpandas/tests/test_moving_acessors.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@

import os
import json
import time
import pytest
from pandas import Timedelta
from stravalib.protocol import ApiV3
from stravalib.client import Client
from stravalib.model import Stream
from runpandas import reader
from runpandas import read_strava
from runpandas import StravaClient

pytestmark = pytest.mark.stable

Expand All @@ -20,6 +22,28 @@ def dirpath(datapath):
return datapath("io", "data")


@pytest.fixture(scope="session")
def valid_token_file(tmpdir_factory):
return tmpdir_factory.getbasetemp().join("token.json")


@pytest.fixture
def strava_client(valid_token_file):
file_handler = open(valid_token_file, "w")
file_handler.write(
'{"access_token": "2334444", "refresh_token": "235555", "expires_at": %d}'
% (time.time() + 3600)
)
file_handler.close()

client = StravaClient(
client_id="STRAVA_ID",
client_secret="STRAVA_CLIENT_SECRET",
token_file=valid_token_file,
)
return client


class MockResponse:
def __init__(self, json_file):
with open(json_file) as json_handler:
Expand Down Expand Up @@ -68,7 +92,7 @@ def test_metrics_validate(dirpath):
frame_without_index.only_moving()


def test_only_moving_acessor(dirpath, mocker):
def test_only_moving_acessor(dirpath, mocker, strava_client):
gpx_file = os.path.join(dirpath, "gpx", "stopped_example.gpx")
frame_gpx = reader._read_file(gpx_file, to_df=False)
frame_gpx["distpos"] = frame_gpx.compute.distance(correct_distance=False)
Expand Down Expand Up @@ -116,6 +140,7 @@ def test_only_moving_acessor(dirpath, mocker):
activity_strava = read_strava(
activity_id=4437021783,
access_token=None,
client=strava_client,
refresh_token=None,
to_df=False,
)
Expand Down
Loading

0 comments on commit e46e3d0

Please sign in to comment.