{"id":15728570,"url":"https://github.com/aaron-schroeder/distilling-flask","last_synced_at":"2025-05-01T00:52:39.583Z","repository":{"id":65006300,"uuid":"337189769","full_name":"aaron-schroeder/distilling-flask","owner":"aaron-schroeder","description":"Strava app for display and analysis of personal running data, powered by Flask, Dash, and Pandas.","archived":false,"fork":false,"pushed_at":"2023-04-08T01:57:15.000Z","size":4319,"stargazers_count":4,"open_issues_count":39,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-05-01T00:52:34.090Z","etag":null,"topics":["flask","pandas","plotly-dash","python","strava","strava-api"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/aaron-schroeder.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-02-08T19:42:22.000Z","updated_at":"2025-04-20T06:25:16.000Z","dependencies_parsed_at":"2024-10-24T21:22:12.359Z","dependency_job_id":null,"html_url":"https://github.com/aaron-schroeder/distilling-flask","commit_stats":{"total_commits":233,"total_committers":2,"mean_commits":116.5,"dds":"0.025751072961373356","last_synced_commit":"ddb8048e93cfd3b6ba4ef9543ee940c116eb0e08"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aaron-schroeder%2Fdistilling-flask","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aaron-schroeder%2Fdistilling-flask/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aaron-schroeder%2Fdistilling-flask/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aaron-schroeder%2Fdistilling-flask/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/aaron-schroeder","download_url":"https://codeload.github.com/aaron-schroeder/distilling-flask/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251806208,"owners_count":21646843,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["flask","pandas","plotly-dash","python","strava","strava-api"],"created_at":"2024-10-03T23:03:47.436Z","updated_at":"2025-05-01T00:52:39.557Z","avatar_url":"https://github.com/aaron-schroeder.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# distilling-flask\n\n\u003ePersonal running data display and analysis app, powered by Flask/Dash/Pandas.\n\n[![License](http://img.shields.io/:license-mit-blue.svg)](http://badges.mit-license.org)\n\n---\n\n## Table of Contents                                                                    \n- [Introduction](#introduction)\n- [Dependencies and Installation](#dependencies-and-installation)\n- [Running the App](#running-the-app)\u003c!-- - [Project Status](#project-status) --\u003e\n- [Testing](#testing)\n- [Contact](#contact)\n- [License](#license)\n\n---\n\n## Introduction\n\nGiven a valid client id and client secret for your Strava API app,\nthis app talks to Strava's API and displays running activities in a\nvariety of dashboards using plotly Dash.\n\nI built this app so I could pick apart my raw Strava data, which includes\ndata streams for elevation, grade, and moving/stopped periods. I think Strava\npresents data in an unrealistically favorable way in its app ecosystem,\nand I wanted to work with the raw feeds. There is power in looking at the\nreality of things.\n\nIf you have a [ersponal Strava API application, you can view any of your Strava\nruns in a dashboard powered by Plotly Dash. From there, you can save each\nrun to a database, and view the long-term effects of training in a\ntraining log dashboard.\n\nA [demo app](https://fit.trailzealot.com) is available to view on my website.\n\nSee the [Running the App](#running-the-app) below to see how everything works.\n\n---\n\n## Dependencies and Installation\n\nCheck out [the requirements file](requirements.txt) to see all dependencies.\n\n### Python IDE\n\nClone the repo:\n```\ngit clone https://github.com/aaron-schroeder/distilling-flask.git\n```\n\nChange into the new directory and start a virtual environment. Then, install\nthe requirements:\n```\npip install -r requirements.txt\n```\n\nYou should be able to run the app now. See [Examples](#examples) below for more info.\n\n### Docker container\n\nCreate an image by running the following command in the same dir as `Dockerfile`: \n```sh\ndocker build -t distillingflask:latest .\n```\n\nCreate and start a container from the image with\n```sh\ndocker run --name distillingflask  \\\n    -e MODULE_NAME=application.app  \\\n    -e VARIABLE_NAME=server  \\\n    -e STRAVA_CLIENT_ID=\u003cclient id\u003e  \\\n    -e STRAVA_CLIENT_SECRET=\u003cclient secret\u003e  \\\n    -e PASSWORD=\u003cpassword\u003e  \\\n    -d  \\\n    -p 5000:80  \\\n    --rm  \\\n    distillingflask:latest\n```\n\n## Running the App\n\n### Locally\n\n#### Strava-connected, with your Strava app client id and client secret\n\nThis option pretty much gets you the full-blown app running on your local machine.\nYou can now authorize the app to use the data from your Strava account.\n\nTo do this, you must:\n- Create your own API application [on Strava's website](https://www.strava.com/settings/api)\n- Within the [\"My API application\"](https://www.strava.com/settings/api)\n  section of your Strava settings:\n  - Set the authorization callback domain for your app to `localhost`\n  - Copy your app's \"client ID\" and \"client secret\" somewhere secure\n\nTo run the app using Flask's CLI:\n```\nSTRAVA_CLIENT_ID=${STRAVA_CLIENT_ID}  \\\nSTRAVA_CLIENT_SECRET=${STRAVA_CLIENT_SECRET}  \\\nPASSWORD=super_secret_password  \\\nflask --app application\n```\n\n![List of activities](https://github.com/aaron-schroeder/distilling-flask/blob/master/images/activity_list_screenshot.jpg?raw=true)\n\n![Saved activities in training log dashboard](https://github.com/aaron-schroeder/distilling-flask/blob/master/images/training_log_screenshot.jpg?raw=true)\n\n#### Strava-disconnected, allowing a subset of features.\n\nYou don't need to set your app up with Strava to access some of\nits features like the file upload analysis dashboard.\nThe command to run this configuration of the app is simpler.\n\nFlask CLI:\n```sh\nPASSWORD=super_secret_password  \\\nflask --app 'application:create_app(\"dummy\")'\n```\n\ndistilling-flask CLI:\nTo use `df` (the distilling-flask CLI), first install the package locally with\n`pip install -e .`\n```\nPASSWORD=super_secret_password  \\\ndf rundummy\n```\n\nIn a python script:\n```python\nimport os\n\nimport application\n\n\n# Choose the password for this app. Ideally don't use your Strava password.\nos.environ['PASSWORD'] = 'super_secret_password'\napp = application.create_app()\napp.run()\n```\n\nThere are a number of optional settings that control the behavior of the\nsimulated Strava client. They can all be set with environment variables,\na `.env` file, or (in the case of the distilling-flask CLI) arguments to\nthe command.\n- `MOCK_STRAVALIB_ACTIVITY_COUNT`\n- `MOCK_STRAVALIB_SHORT_LIMIT`\n- `MOCK_STRAVALIB_LONG_LIMIT`\n- `MOCK_STRAVALIB_SHORT_USAGE`\n- `MOCK_STRAVALIB_LONG_USAGE`\n\nFiletypes accepted by the upload-to-analyze dashboard:\n  - `fit` file (requires [`fitparse`](https://github.com/dtcooper/python-fitparse) and [`dateutil`](https://dateutil.readthedocs.io/en/stable/))\n  - `tcx` file (requires [`activereader`](https://github.com/aaron-schroeder/activereader))\n  - `gpx` file (requires [`activereader`](https://github.com/aaron-schroeder/activereader))\n  - `csv` file (requires that headers adhere to [the naming convention defined\n    by the application](application/plotlydash/figure_layout.py#L4-L11))\n  \u003c!--\n  - `csv` file from Wahoo Fitness (WIP) \n  --\u003e\n\n![The dashboard in action](https://github.com/aaron-schroeder/distilling-flask/blob/master/images/db_screenshot.jpg?raw=true)\n\n### Production\n\nTo create an instance of the app with production-oriented settings, call\n`create_app(config_name='prod')`.\n\nLike the development configuration, the production configuration of \ndefaults to using an on-disk SQLite database, or any database \nspecified by the `DATABASE_URL` environment variable\n(including in-memory SQLite with the url `sqlite://`)\n\nThe production configuration also allows for the use of a PostgreSQL database\nif the right environment variables (starting with `POSTGRES_`) are set.\n\n```\nSECRET_KEY=random_secret_key  \\\nSTRAVA_CLIENT_ID=00000  \\\nSTRAVA_CLIENT_SECRET=gobbledygoop  \\\nPOSTGRES_DB=db_name  \\\nPOSTGRES_PORT=5432  \\\nPOSTGRES_USER=user  \\\nPOSTGRES_PW=password  \\\nPOSTGRES_URL=dburl.example.com  \\\nflask --app \"application:create_app('prod')\"\n```\n\n### `StreamLabel` and custom accessors for `pandas` objects\n\nCreate a `DataFrame` where each row represents a record, and each column \nrepresents a data stream with a unique (field, source) id.\n```python\nimport pandas as pd\nfrom application.labels import StreamLabel\n\ndf = pd.DataFrame.from_dict({\n    StreamLabel('time', 'strava'): [0, 1, 2, 3, 4, 5],\n    StreamLabel('speed', 'strava'): [3.0, 3.2, 3.4, 3.6, 3.8, 3.6],\n    StreamLabel('speed', 'garmin'): [2.9, 3.1, 3.3, 3.5, 3.7, 3.8],\n})\n```\n\nUse the custom accessor to work with this specifically-formatted DataFrame.\n```\n\u003e\u003e\u003e df.sl.has_source('strava')\nTrue\n\n\u003e\u003e\u003e df.sl.has_source('device')\nFalse\n\n\u003e\u003e\u003e df.sl.source('strava')\n\n   time (strava)  speed (strava)\n0              0             3.0\n1              1             3.2\n2              2             3.4\n3              3             3.6\n4              4             3.8\n5              5             3.6\n\n\u003e\u003e\u003e df.sl.has_fields('speed', 'time')\nTrue\n\n\u003e\u003e\u003e df.sl.field('speed')\n\n   speed (strava)  speed (garmin)\n0             3.0             2.9\n1             3.2             3.1\n2             3.4             3.3\n3             3.6             3.5\n4             3.8             3.7\n5             3.6             3.8\n\n\u003e\u003e\u003e StreamLabel.from_str('speed~new_src')\nspeed (new_src)\n```\n\n---\n\n## Testing\n\n### Functional testing\n\nThis requires user-supplied files in the following locations:\n  - `client_secrets.json`\n  - `tests/functional_tests/strava_credentials.json`\n\n```sh\npip install -r requirements_dev.txt\npython -m unittest discover -p test_*.py tests.functional_tests\n```\n\n### Unit testing\n```sh\npython -m unittest discover -p test_*.py tests.unit_tests\n```\n\n## Project Status\n\n### Current Activities\n\nThe Flask app is becoming a full-fledged training log. Strava activities\ncan be viewed in a dashboard and saved to a database, and soon uploaded\nfiles will be saveable too. The long-term effects of training can be \nvisualized in a training log dashboard, which is still evolving.\n\n### Future Work\n\nComing up, I'd like to set up pipelines that take running activity data from\na variety of sources and filetypes, and displays the time series in a common\ninterface. To that end, I've created a class to be used as column labels in\n`pandas.DataFrame`. `StreamLabel` keeps track of both the field name and the\nsource of data streams. This facilitates a common, recognizable labeling\nsystem for data streams stored in `DataFrame` columns. I've created custom \naccessors for `pandas.DataFrame` and `pandas.Index` to work with `StreamLabel`.\n\n---\n\n## Contact\n\nYou can get in touch with me at one of the following places:\n\n[//]: # (- Website: \u003ca href=\"https://trailzealot.com\" target=\"_blank\"\u003etrailzealot.com\u003c/a\u003e)\n- GitHub: \u003ca href=\"https://github.com/aaron-schroeder\" target=\"_blank\"\u003egithub.com/aaron-schroeder\u003c/a\u003e\n- LinkedIn: \u003ca href=\"https://www.linkedin.com/in/aarondschroeder/\" target=\"_blank\"\u003elinkedin.com/in/aarondschroeder\u003c/a\u003e\n- Twitter: \u003ca href=\"https://twitter.com/trailzealot\" target=\"_blank\"\u003e@trailzealot\u003c/a\u003e\n- Instagram: \u003ca href=\"https://instagram.com/trailzealot\" target=\"_blank\"\u003e@trailzealot\u003c/a\u003e\n\n---\n\n## License\n\n[![License](http://img.shields.io/:license-mit-blue.svg)](http://badges.mit-license.org)\n\nThis project is licensed under the MIT License. See\n[LICENSE](https://github.com/aaron-schroeder/distilling-flask/blob/master/LICENSE)\nfile for details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faaron-schroeder%2Fdistilling-flask","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Faaron-schroeder%2Fdistilling-flask","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faaron-schroeder%2Fdistilling-flask/lists"}