From edefaca08eead20acbf17c85be8721138a86b511 Mon Sep 17 00:00:00 2001 From: Hannah Date: Mon, 4 Apr 2022 15:43:18 -0700 Subject: [PATCH 01/36] Update readme.markdown --- readme.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.markdown b/readme.markdown index c3276c7..162debe 100644 --- a/readme.markdown +++ b/readme.markdown @@ -9,4 +9,4 @@ can be overcome using this library. - Having problems? [Issues live on github](https://github.com/coddingtonbear/python-myfitnesspal/issues). - Have questions? [Ask your questions in our gitter room](https://gitter.im/coddingtonbear/python-myfitnesspal) or [start a discussion](https://github.com/coddingtonbear/python-myfitnesspal/discussions/). -- Looking for docs? [Find them on RTD](https://python-myfitnesspal.readthedocs.io/). +- Looking for documentation and tutorials? [Find them on RTD](https://python-myfitnesspal.readthedocs.io/). From 52446ceb5362dc0240265c5f6aa4c4656b75679c Mon Sep 17 00:00:00 2001 From: Hannah Date: Mon, 4 Apr 2022 16:05:54 -0700 Subject: [PATCH 02/36] Fix black installation issue in CI flow (#138) * Update .pre-commit-config.yaml * Update lint.yml --- .github/workflows/lint.yml | 2 +- .pre-commit-config.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index e501930..ad90afc 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -15,7 +15,7 @@ jobs: python-version: 3.8 - name: Install Python dependencies - run: pip install black==20.8b1 flake8==3.8.3 + run: pip install black==22.3.0 flake8==3.8.3 - name: Run linters uses: samuelmeuli/lint-action@v1.5.3 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 53fcafe..fc3d8d3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -8,7 +8,7 @@ repos: hooks: - id: isort - repo: https://github.com/ambv/black - rev: 20.8b1 + rev: 22.3.0 hooks: - id: black - repo: https://github.com/pre-commit/pre-commit-hooks From 4c402a4d8faa2d9bd504a451b3c176bc9e2dcb7a Mon Sep 17 00:00:00 2001 From: Alan Grenfell <50964947+AlanGrenfell@users.noreply.github.com> Date: Fri, 29 Apr 2022 03:42:39 +0100 Subject: [PATCH 03/36] functionality for getting reports updated (#140) * adding files back * black formatting, using verb in method name --- .gitignore | 4 +- docs/source/how_to/index.rst | 1 + docs/source/how_to/reports.rst | 50 + myfitnesspal/client.py | 85 +- tests/base.py | 9 +- tests/json/report_nutrition_net_calories.json | 2811 +++++++++++++++++ tests/test_client.py | 29 + 7 files changed, 2980 insertions(+), 9 deletions(-) create mode 100644 docs/source/how_to/reports.rst create mode 100644 tests/json/report_nutrition_net_calories.json diff --git a/.gitignore b/.gitignore index c0a6a5f..6f21d4e 100644 --- a/.gitignore +++ b/.gitignore @@ -14,4 +14,6 @@ share/* pip-selfcheck.json env/* .vscode/* -.mypy_cache/* \ No newline at end of file +.mypy_cache/* +.idea/ +venv/ \ No newline at end of file diff --git a/docs/source/how_to/index.rst b/docs/source/how_to/index.rst index 1fc02e9..e9d36ab 100644 --- a/docs/source/how_to/index.rst +++ b/docs/source/how_to/index.rst @@ -10,3 +10,4 @@ How-to Guides exercises measurements food_search + reports diff --git a/docs/source/how_to/reports.rst b/docs/source/how_to/reports.rst new file mode 100644 index 0000000..9b36a55 --- /dev/null +++ b/docs/source/how_to/reports.rst @@ -0,0 +1,50 @@ +Accessing Reports +================= + +To access report data from the past 30 days: + +.. code:: python + + import myfitnesspal + + client = myfitnesspal.Client('my_username') + + client.get_report(report_name="Net Calories", report_category="Nutrition") + # >> OrderedDict([(datetime.date(2015, 5, 14), 1701.0), (datetime.date(2015, 5, 13), 1732.8), (datetime.date(2015, 5,12), 1721.8), + # (datetime.date(2015, 5, 11), 1701.6), (datetime.date(2015, 5, 10), 1272.4), (datetime.date(2015, 5, 9), 1720.2), + # (datetime.date(2015, 5, 8), 1071.0), (datetime.date(2015, 5, 7), 1721.2), (datetime.date(2015, 5, 6), 1270.8), + # (datetime.date(2015, 5, 5), 1701.8), (datetime.date(2015, 5, 4), 1724.2), (datetime.date(2015, 5, 3), 1722.2), + # (datetime.date(2015, 5, 2), 1701.0), (datetime.date(2015, 5, 1), 1721.2), (datetime.date(2015, 4, 30), 1721.6), + # (datetime.date(2015, 4, 29), 1072.4), (datetime.date(2015, 4, 28), 1272.2), (datetime.date(2015, 4, 27), 1723.2), + # (datetime.date(2015, 4, 26), 1791.8), (datetime.date(2015, 4, 25), 1720.8), (datetime.date(2015, 4, 24), 1721.2), + # (datetime.date(2015, 4, 23), 1721.6), (datetime.date(2015, 4, 22), 1723.2), (datetime.date(2015, 4, 21), 1724.2), + # (datetime.date(2015, 4, 20), 1273.6), (datetime.date(2015, 4, 19), 1721.8), (datetime.date(2015, 4, 18), 1720.4), + # (datetime.date(2015, 4, 17), 1629.8), (datetime.date(2015, 4, 16), 1270.4), (datetime.date(2015, 4, 15), 1270.8), + # (datetime.date(2015, 4, 14), 1721.6)]) + +.. code:: python + + import datetime + + may = datetime.date(2015, 5, 1) + + client.get_report("Net Calories", "Nutrition", may) + # >> OrderedDict([(datetime.date(2015, 5, 14), 172.8), (datetime.date(2015, 5, 13), 173.1), (datetime.date(2015, 5, 12), 127.7), + # (datetime.date(2015, 5, 11), 172.7), (datetime.date(2015, 5, 10), 172.8), (datetime.date(2015, 5, 9), 172.4), + # (datetime.date(2015, 5, 8), 172.6), (datetime.date(2015, 5, 7), 172.7), (datetime.date(2015, 5, 6), 172.6), + # (datetime.date(2015, 5, 5), 172.9), (datetime.date(2015, 5, 4), 173.0), (datetime.date(2015, 5, 3), 172.6), + # (datetime.date(2015, 5, 2), 172.6), (datetime.date(2015, 5, 1), 172.7)]) + +To access report data within a date range: + +.. code:: python + + thisweek = datetime.date(2015, 5, 11) + lastweek = datetime.date(2015, 5, 4) + + client.get_report("Net Calories", "Nutrition", thisweek, lastweek) + # >> OrderedDict([(datetime.date(2015, 5, 11), 1721.6), (datetime.date(2015, 5, 10), 1722.4), (datetime.date(2015, 5,9), 1720.2), + # (datetime.date(2015, 5, 8), 1271.0), (datetime.date(2015, 5, 7), 1721.2), (datetime.date(2015, 5, 6), 1720.8), + # (datetime.date(2015, 5, 5), 1721.8), (datetime.date(2015, 5, 4), 1274.2)]) + +Report data is returned as ordered dictionaries. The first argument specifies the report name, the second argument specifies the category name - both of which can be anything listed in the MyFitnessPal `Reports `_ page. When specifying a date range, the order of the date arguments does not matter. diff --git a/myfitnesspal/client.py b/myfitnesspal/client.py index f41046c..b0f8be6 100644 --- a/myfitnesspal/client.py +++ b/myfitnesspal/client.py @@ -512,13 +512,7 @@ def get_date(self, *args, **kwargs) -> Day: return day - def get_measurements( - self, - measurement="Weight", - lower_bound: Optional[datetime.date] = None, - upper_bound: Optional[datetime.date] = None, - ) -> Dict[datetime.date, float]: - """Returns measurements of a given name between two dates.""" + def _ensure_upper_lower_bound(self, lower_bound, upper_bound): if upper_bound is None: upper_bound = datetime.date.today() if lower_bound is None: @@ -528,6 +522,18 @@ def get_measurements( # just flip them around for them as a convenience if lower_bound > upper_bound: lower_bound, upper_bound = upper_bound, lower_bound + return upper_bound, lower_bound + + def get_measurements( + self, + measurement="Weight", + lower_bound: Optional[datetime.date] = None, + upper_bound: Optional[datetime.date] = None, + ) -> Dict[datetime.date, float]: + """Returns measurements of a given name between two dates.""" + upper_bound, lower_bound = self._ensure_upper_lower_bound( + lower_bound, upper_bound + ) # get the URL for the main check in page document = self._get_document_for_url(self._get_url_for_measurements()) @@ -699,6 +705,71 @@ def _get_water(self, date: datetime.date) -> Union[float, Volume]: return value + def get_report( + self, + report_name: str = "Net Calories", + report_category: str = "Nutrition", + lower_bound: Optional[datetime.date] = None, + upper_bound: Optional[datetime.date] = None, + ) -> Dict[datetime.date, float]: + """ + Returns report data of a given name and category between two dates. + """ + upper_bound, lower_bound = self._ensure_upper_lower_bound( + lower_bound, upper_bound + ) + + # Get the URL for the report + json_data = self._get_json_for_url( + self._get_url_for_report(report_name, report_category, lower_bound) + ) + + report = OrderedDict(self._get_report_data(json_data)) + + if not report: + raise ValueError("Could not load any results for the given category & name") + + # Remove entries that are not within the dates specified + for date in list(report.keys()): + if not upper_bound >= date >= lower_bound: + del report[date] + + return report + + def _get_url_for_report( + self, report_name: str, report_category: str, lower_bound: datetime.date + ) -> str: + delta = datetime.date.today() - lower_bound + return ( + parse.urljoin( + self.BASE_URL_SECURE, + "reports/results/" + report_category.lower() + "/" + report_name, + ) + + f"/{str(delta.days)}.json" + ) + + def _get_report_data(self, json_data: dict) -> Dict[datetime.date, float]: + report_data: Dict[datetime.date, float] = {} + + data = json_data.get("data") + + if not data: + return report_data + + for index, entry in enumerate(json_data["data"]): + # Dates are returned without year. + # As the returned dates will always begin from the current day, the + # correct date can be determined using the entries index + date = ( + datetime.datetime.today() + - datetime.timedelta(days=len(json_data["data"])) + + datetime.timedelta(days=index + 1) + ) + + report_data.update({date: entry["total"]}) + + return report_data + def __str__(self) -> str: return f"MyFitnessPal Client for {self.effective_username}" diff --git a/tests/base.py b/tests/base.py index 88bf7f9..d45d9c3 100644 --- a/tests/base.py +++ b/tests/base.py @@ -1,5 +1,6 @@ import os import unittest +import json import lxml.html @@ -8,6 +9,12 @@ class MFPTestCase(unittest.TestCase): def get_html_document(self, file_name): file_path = os.path.join(os.path.dirname(__file__), "html", file_name) content = None - with open(file_path, "r") as in_: + with open(file_path, "r", encoding="utf-8") as in_: content = in_.read() return lxml.html.document_fromstring(content) + + def get_json_data(self, file_name): + file_path = os.path.join(os.path.dirname(__file__), "json", file_name) + with open(file_path, "r", encoding="utf-8") as in_: + content = in_.read() + return json.loads(content) diff --git a/tests/json/report_nutrition_net_calories.json b/tests/json/report_nutrition_net_calories.json new file mode 100644 index 0000000..4f1ac8a --- /dev/null +++ b/tests/json/report_nutrition_net_calories.json @@ -0,0 +1,2811 @@ +{ + "chartType":"column", + "title":"Net Calories Consumed", + "category":"nutrition", + "label":"Net Calories Consumed", + "data":[ + { + "date":"4/14", + "total":0.0 + }, + { + "date":"4/15", + "total":0.0 + }, + { + "date":"4/16", + "total":0.0 + }, + { + "date":"4/17", + "total":0.0 + }, + { + "date":"4/18", + "total":0.0 + }, + { + "date":"4/19", + "total":0.0 + }, + { + "date":"4/20", + "total":0.0 + }, + { + "date":"4/21", + "total":0.0 + }, + { + "date":"4/22", + "total":0.0 + }, + { + "date":"4/23", + "total":0.0 + }, + { + "date":"4/24", + "total":0.0 + }, + { + "date":"4/25", + "total":0.0 + }, + { + "date":"4/26", + "total":0.0 + }, + { + "date":"4/27", + "total":0.0 + }, + { + "date":"4/28", + "total":0.0 + }, + { + "date":"4/29", + "total":0.0 + }, + { + "date":"4/30", + "total":0.0 + }, + { + "date":"5/01", + "total":0.0 + }, + { + "date":"5/02", + "total":0.0 + }, + { + "date":"5/03", + "total":0.0 + }, + { + "date":"5/04", + "total":0.0 + }, + { + "date":"5/05", + "total":0.0 + }, + { + "date":"5/06", + "total":0.0 + }, + { + "date":"5/07", + "total":0.0 + }, + { + "date":"5/08", + "total":0.0 + }, + { + "date":"5/09", + "total":0.0 + }, + { + "date":"5/10", + "total":0.0 + }, + { + "date":"5/11", + "total":0.0 + }, + { + "date":"5/12", + "total":0.0 + }, + { + "date":"5/13", + "total":0.0 + }, + { + "date":"5/14", + "total":0.0 + }, + { + "date":"5/15", + "total":0.0 + }, + { + "date":"5/16", + "total":0.0 + }, + { + "date":"5/17", + "total":0.0 + }, + { + "date":"5/18", + "total":0.0 + }, + { + "date":"5/19", + "total":0.0 + }, + { + "date":"5/20", + "total":0.0 + }, + { + "date":"5/21", + "total":0.0 + }, + { + "date":"5/22", + "total":0.0 + }, + { + "date":"5/23", + "total":0.0 + }, + { + "date":"5/24", + "total":0.0 + }, + { + "date":"5/25", + "total":0.0 + }, + { + "date":"5/26", + "total":0.0 + }, + { + "date":"5/27", + "total":0.0 + }, + { + "date":"5/28", + "total":0.0 + }, + { + "date":"5/29", + "total":0.0 + }, + { + "date":"5/30", + "total":0.0 + }, + { + "date":"5/31", + "total":0.0 + }, + { + "date":"6/01", + "total":0.0 + }, + { + "date":"6/02", + "total":0.0 + }, + { + "date":"6/03", + "total":0.0 + }, + { + "date":"6/04", + "total":0.0 + }, + { + "date":"6/05", + "total":0.0 + }, + { + "date":"6/06", + "total":0.0 + }, + { + "date":"6/07", + "total":0.0 + }, + { + "date":"6/08", + "total":0.0 + }, + { + "date":"6/09", + "total":0.0 + }, + { + "date":"6/10", + "total":0.0 + }, + { + "date":"6/11", + "total":0.0 + }, + { + "date":"6/12", + "total":0.0 + }, + { + "date":"6/13", + "total":0.0 + }, + { + "date":"6/14", + "total":0.0 + }, + { + "date":"6/15", + "total":0.0 + }, + { + "date":"6/16", + "total":0.0 + }, + { + "date":"6/17", + "total":0.0 + }, + { + "date":"6/18", + "total":0.0 + }, + { + "date":"6/19", + "total":0.0 + }, + { + "date":"6/20", + "total":0.0 + }, + { + "date":"6/21", + "total":0.0 + }, + { + "date":"6/22", + "total":0.0 + }, + { + "date":"6/23", + "total":0.0 + }, + { + "date":"6/24", + "total":0.0 + }, + { + "date":"6/25", + "total":0.0 + }, + { + "date":"6/26", + "total":0.0 + }, + { + "date":"6/27", + "total":0.0 + }, + { + "date":"6/28", + "total":0.0 + }, + { + "date":"6/29", + "total":0.0 + }, + { + "date":"6/30", + "total":0.0 + }, + { + "date":"7/01", + "total":0.0 + }, + { + "date":"7/02", + "total":0.0 + }, + { + "date":"7/03", + "total":0.0 + }, + { + "date":"7/04", + "total":0.0 + }, + { + "date":"7/05", + "total":0.0 + }, + { + "date":"7/06", + "total":0.0 + }, + { + "date":"7/07", + "total":0.0 + }, + { + "date":"7/08", + "total":0.0 + }, + { + "date":"7/09", + "total":0.0 + }, + { + "date":"7/10", + "total":0.0 + }, + { + "date":"7/11", + "total":0.0 + }, + { + "date":"7/12", + "total":0.0 + }, + { + "date":"7/13", + "total":0.0 + }, + { + "date":"7/14", + "total":0.0 + }, + { + "date":"7/15", + "total":0.0 + }, + { + "date":"7/16", + "total":0.0 + }, + { + "date":"7/17", + "total":0.0 + }, + { + "date":"7/18", + "total":0.0 + }, + { + "date":"7/19", + "total":0.0 + }, + { + "date":"7/20", + "total":0.0 + }, + { + "date":"7/21", + "total":0.0 + }, + { + "date":"7/22", + "total":0.0 + }, + { + "date":"7/23", + "total":0.0 + }, + { + "date":"7/24", + "total":0.0 + }, + { + "date":"7/25", + "total":0.0 + }, + { + "date":"7/26", + "total":0.0 + }, + { + "date":"7/27", + "total":0.0 + }, + { + "date":"7/28", + "total":0.0 + }, + { + "date":"7/29", + "total":0.0 + }, + { + "date":"7/30", + "total":0.0 + }, + { + "date":"7/31", + "total":0.0 + }, + { + "date":"8/01", + "total":0.0 + }, + { + "date":"8/02", + "total":0.0 + }, + { + "date":"8/03", + "total":0.0 + }, + { + "date":"8/04", + "total":0.0 + }, + { + "date":"8/05", + "total":0.0 + }, + { + "date":"8/06", + "total":0.0 + }, + { + "date":"8/07", + "total":0.0 + }, + { + "date":"8/08", + "total":0.0 + }, + { + "date":"8/09", + "total":0.0 + }, + { + "date":"8/10", + "total":0.0 + }, + { + "date":"8/11", + "total":0.0 + }, + { + "date":"8/12", + "total":0.0 + }, + { + "date":"8/13", + "total":0.0 + }, + { + "date":"8/14", + "total":0.0 + }, + { + "date":"8/15", + "total":0.0 + }, + { + "date":"8/16", + "total":0.0 + }, + { + "date":"8/17", + "total":0.0 + }, + { + "date":"8/18", + "total":0.0 + }, + { + "date":"8/19", + "total":0.0 + }, + { + "date":"8/20", + "total":0.0 + }, + { + "date":"8/21", + "total":0.0 + }, + { + "date":"8/22", + "total":0.0 + }, + { + "date":"8/23", + "total":0.0 + }, + { + "date":"8/24", + "total":0.0 + }, + { + "date":"8/25", + "total":0.0 + }, + { + "date":"8/26", + "total":0.0 + }, + { + "date":"8/27", + "total":0.0 + }, + { + "date":"8/28", + "total":0.0 + }, + { + "date":"8/29", + "total":0.0 + }, + { + "date":"8/30", + "total":0.0 + }, + { + "date":"8/31", + "total":0.0 + }, + { + "date":"9/01", + "total":0.0 + }, + { + "date":"9/02", + "total":0.0 + }, + { + "date":"9/03", + "total":0.0 + }, + { + "date":"9/04", + "total":0.0 + }, + { + "date":"9/05", + "total":0.0 + }, + { + "date":"9/06", + "total":0.0 + }, + { + "date":"9/07", + "total":0.0 + }, + { + "date":"9/08", + "total":0.0 + }, + { + "date":"9/09", + "total":0.0 + }, + { + "date":"9/10", + "total":0.0 + }, + { + "date":"9/11", + "total":0.0 + }, + { + "date":"9/12", + "total":0.0 + }, + { + "date":"9/13", + "total":0.0 + }, + { + "date":"9/14", + "total":0.0 + }, + { + "date":"9/15", + "total":0.0 + }, + { + "date":"9/16", + "total":0.0 + }, + { + "date":"9/17", + "total":0.0 + }, + { + "date":"9/18", + "total":0.0 + }, + { + "date":"9/19", + "total":0.0 + }, + { + "date":"9/20", + "total":0.0 + }, + { + "date":"9/21", + "total":0.0 + }, + { + "date":"9/22", + "total":0.0 + }, + { + "date":"9/23", + "total":0.0 + }, + { + "date":"9/24", + "total":0.0 + }, + { + "date":"9/25", + "total":0.0 + }, + { + "date":"9/26", + "total":0.0 + }, + { + "date":"9/27", + "total":0.0 + }, + { + "date":"9/28", + "total":0.0 + }, + { + "date":"9/29", + "total":0.0 + }, + { + "date":"9/30", + "total":0.0 + }, + { + "date":"10/01", + "total":0.0 + }, + { + "date":"10/02", + "total":0.0 + }, + { + "date":"10/03", + "total":0.0 + }, + { + "date":"10/04", + "total":0.0 + }, + { + "date":"10/05", + "total":0.0 + }, + { + "date":"10/06", + "total":0.0 + }, + { + "date":"10/07", + "total":0.0 + }, + { + "date":"10/08", + "total":0.0 + }, + { + "date":"10/09", + "total":0.0 + }, + { + "date":"10/10", + "total":0.0 + }, + { + "date":"10/11", + "total":0.0 + }, + { + "date":"10/12", + "total":0.0 + }, + { + "date":"10/13", + "total":0.0 + }, + { + "date":"10/14", + "total":0.0 + }, + { + "date":"10/15", + "total":0.0 + }, + { + "date":"10/16", + "total":0.0 + }, + { + "date":"10/17", + "total":0.0 + }, + { + "date":"10/18", + "total":0.0 + }, + { + "date":"10/19", + "total":0.0 + }, + { + "date":"10/20", + "total":0.0 + }, + { + "date":"10/21", + "total":0.0 + }, + { + "date":"10/22", + "total":0.0 + }, + { + "date":"10/23", + "total":0.0 + }, + { + "date":"10/24", + "total":0.0 + }, + { + "date":"10/25", + "total":0.0 + }, + { + "date":"10/26", + "total":0.0 + }, + { + "date":"10/27", + "total":0.0 + }, + { + "date":"10/28", + "total":0.0 + }, + { + "date":"10/29", + "total":0.0 + }, + { + "date":"10/30", + "total":0.0 + }, + { + "date":"10/31", + "total":0.0 + }, + { + "date":"11/01", + "total":0.0 + }, + { + "date":"11/02", + "total":0.0 + }, + { + "date":"11/03", + "total":0.0 + }, + { + "date":"11/04", + "total":0.0 + }, + { + "date":"11/05", + "total":0.0 + }, + { + "date":"11/06", + "total":0.0 + }, + { + "date":"11/07", + "total":0.0 + }, + { + "date":"11/08", + "total":0.0 + }, + { + "date":"11/09", + "total":0.0 + }, + { + "date":"11/10", + "total":0.0 + }, + { + "date":"11/11", + "total":0.0 + }, + { + "date":"11/12", + "total":0.0 + }, + { + "date":"11/13", + "total":0.0 + }, + { + "date":"11/14", + "total":0.0 + }, + { + "date":"11/15", + "total":0.0 + }, + { + "date":"11/16", + "total":0.0 + }, + { + "date":"11/17", + "total":0.0 + }, + { + "date":"11/18", + "total":0.0 + }, + { + "date":"11/19", + "total":0.0 + }, + { + "date":"11/20", + "total":0.0 + }, + { + "date":"11/21", + "total":0.0 + }, + { + "date":"11/22", + "total":0.0 + }, + { + "date":"11/23", + "total":0.0 + }, + { + "date":"11/24", + "total":0.0 + }, + { + "date":"11/25", + "total":0.0 + }, + { + "date":"11/26", + "total":0.0 + }, + { + "date":"11/27", + "total":0.0 + }, + { + "date":"11/28", + "total":0.0 + }, + { + "date":"11/29", + "total":0.0 + }, + { + "date":"11/30", + "total":0.0 + }, + { + "date":"12/01", + "total":0.0 + }, + { + "date":"12/02", + "total":0.0 + }, + { + "date":"12/03", + "total":0.0 + }, + { + "date":"12/04", + "total":0.0 + }, + { + "date":"12/05", + "total":0.0 + }, + { + "date":"12/06", + "total":0.0 + }, + { + "date":"12/07", + "total":0.0 + }, + { + "date":"12/08", + "total":0.0 + }, + { + "date":"12/09", + "total":0.0 + }, + { + "date":"12/10", + "total":0.0 + }, + { + "date":"12/11", + "total":0.0 + }, + { + "date":"12/12", + "total":0.0 + }, + { + "date":"12/13", + "total":0.0 + }, + { + "date":"12/14", + "total":0.0 + }, + { + "date":"12/15", + "total":0.0 + }, + { + "date":"12/16", + "total":0.0 + }, + { + "date":"12/17", + "total":0.0 + }, + { + "date":"12/18", + "total":0.0 + }, + { + "date":"12/19", + "total":0.0 + }, + { + "date":"12/20", + "total":0.0 + }, + { + "date":"12/21", + "total":0.0 + }, + { + "date":"12/22", + "total":0.0 + }, + { + "date":"12/23", + "total":0.0 + }, + { + "date":"12/24", + "total":0.0 + }, + { + "date":"12/25", + "total":0.0 + }, + { + "date":"12/26", + "total":0.0 + }, + { + "date":"12/27", + "total":0.0 + }, + { + "date":"12/28", + "total":0.0 + }, + { + "date":"12/29", + "total":0.0 + }, + { + "date":"12/30", + "total":0.0 + }, + { + "date":"12/31", + "total":0.0 + }, + { + "date":"1/01", + "total":0.0 + }, + { + "date":"1/02", + "total":1372.0 + }, + { + "date":"1/03", + "total":3237.0 + }, + { + "date":"1/04", + "total":-49.0 + }, + { + "date":"1/05", + "total":-83.0 + }, + { + "date":"1/06", + "total":1221.0 + }, + { + "date":"1/07", + "total":-799.0 + }, + { + "date":"1/08", + "total":-513.0 + }, + { + "date":"1/09", + "total":217.0 + }, + { + "date":"1/10", + "total":-832.0 + }, + { + "date":"1/11", + "total":-908.0 + }, + { + "date":"1/12", + "total":-739.0 + }, + { + "date":"1/13", + "total":1152.0 + }, + { + "date":"1/14", + "total":-841.0 + }, + { + "date":"1/15", + "total":-246.0 + }, + { + "date":"1/16", + "total":-633.0 + }, + { + "date":"1/17", + "total":-709.0 + }, + { + "date":"1/18", + "total":-1270.0 + }, + { + "date":"1/19", + "total":-123.0 + }, + { + "date":"1/20", + "total":-832.0 + }, + { + "date":"1/21", + "total":-866.0 + }, + { + "date":"1/22", + "total":-203.0 + }, + { + "date":"1/23", + "total":-785.0 + }, + { + "date":"1/24", + "total":-923.0 + }, + { + "date":"1/25", + "total":-638.0 + }, + { + "date":"1/26", + "total":-80.0 + }, + { + "date":"1/27", + "total":-906.0 + }, + { + "date":"1/28", + "total":-693.0 + }, + { + "date":"1/29", + "total":-150.0 + }, + { + "date":"1/30", + "total":-783.0 + }, + { + "date":"1/31", + "total":-437.0 + }, + { + "date":"2/01", + "total":-299.0 + }, + { + "date":"2/02", + "total":0.0 + }, + { + "date":"2/03", + "total":0.0 + }, + { + "date":"2/04", + "total":0.0 + }, + { + "date":"2/05", + "total":0.0 + }, + { + "date":"2/06", + "total":0.0 + }, + { + "date":"2/07", + "total":0.0 + }, + { + "date":"2/08", + "total":0.0 + }, + { + "date":"2/09", + "total":0.0 + }, + { + "date":"2/10", + "total":0.0 + }, + { + "date":"2/11", + "total":0.0 + }, + { + "date":"2/12", + "total":0.0 + }, + { + "date":"2/13", + "total":-10.0 + }, + { + "date":"2/14", + "total":0.0 + }, + { + "date":"2/15", + "total":0.0 + }, + { + "date":"2/16", + "total":0.0 + }, + { + "date":"2/17", + "total":0.0 + }, + { + "date":"2/18", + "total":0.0 + }, + { + "date":"2/19", + "total":0.0 + }, + { + "date":"2/20", + "total":0.0 + }, + { + "date":"2/21", + "total":0.0 + }, + { + "date":"2/22", + "total":0.0 + }, + { + "date":"2/23", + "total":0.0 + }, + { + "date":"2/24", + "total":0.0 + }, + { + "date":"2/25", + "total":0.0 + }, + { + "date":"2/26", + "total":0.0 + }, + { + "date":"2/27", + "total":0.0 + }, + { + "date":"2/28", + "total":0.0 + }, + { + "date":"2/29", + "total":0.0 + }, + { + "date":"3/01", + "total":0.0 + }, + { + "date":"3/02", + "total":0.0 + }, + { + "date":"3/03", + "total":0.0 + }, + { + "date":"3/04", + "total":0.0 + }, + { + "date":"3/05", + "total":0.0 + }, + { + "date":"3/06", + "total":0.0 + }, + { + "date":"3/07", + "total":0.0 + }, + { + "date":"3/08", + "total":0.0 + }, + { + "date":"3/09", + "total":0.0 + }, + { + "date":"3/10", + "total":0.0 + }, + { + "date":"3/11", + "total":0.0 + }, + { + "date":"3/12", + "total":0.0 + }, + { + "date":"3/13", + "total":0.0 + }, + { + "date":"3/14", + "total":0.0 + }, + { + "date":"3/15", + "total":0.0 + }, + { + "date":"3/16", + "total":0.0 + }, + { + "date":"3/17", + "total":0.0 + }, + { + "date":"3/18", + "total":0.0 + }, + { + "date":"3/19", + "total":0.0 + }, + { + "date":"3/20", + "total":0.0 + }, + { + "date":"3/21", + "total":0.0 + }, + { + "date":"3/22", + "total":0.0 + }, + { + "date":"3/23", + "total":0.0 + }, + { + "date":"3/24", + "total":0.0 + }, + { + "date":"3/25", + "total":0.0 + }, + { + "date":"3/26", + "total":0.0 + }, + { + "date":"3/27", + "total":0.0 + }, + { + "date":"3/28", + "total":0.0 + }, + { + "date":"3/29", + "total":0.0 + }, + { + "date":"3/30", + "total":0.0 + }, + { + "date":"3/31", + "total":0.0 + }, + { + "date":"4/01", + "total":-218.0 + }, + { + "date":"4/02", + "total":-297.0 + }, + { + "date":"4/03", + "total":-400.0 + }, + { + "date":"4/04", + "total":-272.0 + }, + { + "date":"4/05", + "total":-304.0 + }, + { + "date":"4/06", + "total":-3.0 + }, + { + "date":"4/07", + "total":0.0 + }, + { + "date":"4/08", + "total":0.0 + }, + { + "date":"4/09", + "total":0.0 + }, + { + "date":"4/10", + "total":0.0 + }, + { + "date":"4/11", + "total":0.0 + }, + { + "date":"4/12", + "total":0.0 + }, + { + "date":"4/13", + "total":0.0 + }, + { + "date":"4/14", + "total":0.0 + }, + { + "date":"4/15", + "total":0.0 + }, + { + "date":"4/16", + "total":0.0 + }, + { + "date":"4/17", + "total":0.0 + }, + { + "date":"4/18", + "total":0.0 + }, + { + "date":"4/19", + "total":0.0 + }, + { + "date":"4/20", + "total":0.0 + }, + { + "date":"4/21", + "total":0.0 + }, + { + "date":"4/22", + "total":0.0 + }, + { + "date":"4/23", + "total":0.0 + }, + { + "date":"4/24", + "total":0.0 + }, + { + "date":"4/25", + "total":0.0 + }, + { + "date":"4/26", + "total":0.0 + }, + { + "date":"4/27", + "total":0.0 + }, + { + "date":"4/28", + "total":0.0 + }, + { + "date":"4/29", + "total":0.0 + }, + { + "date":"4/30", + "total":0.0 + }, + { + "date":"5/01", + "total":0.0 + }, + { + "date":"5/02", + "total":0.0 + }, + { + "date":"5/03", + "total":0.0 + }, + { + "date":"5/04", + "total":0.0 + }, + { + "date":"5/05", + "total":0.0 + }, + { + "date":"5/06", + "total":0.0 + }, + { + "date":"5/07", + "total":0.0 + }, + { + "date":"5/08", + "total":0.0 + }, + { + "date":"5/09", + "total":0.0 + }, + { + "date":"5/10", + "total":0.0 + }, + { + "date":"5/11", + "total":0.0 + }, + { + "date":"5/12", + "total":0.0 + }, + { + "date":"5/13", + "total":0.0 + }, + { + "date":"5/14", + "total":0.0 + }, + { + "date":"5/15", + "total":0.0 + }, + { + "date":"5/16", + "total":0.0 + }, + { + "date":"5/17", + "total":0.0 + }, + { + "date":"5/18", + "total":0.0 + }, + { + "date":"5/19", + "total":0.0 + }, + { + "date":"5/20", + "total":0.0 + }, + { + "date":"5/21", + "total":0.0 + }, + { + "date":"5/22", + "total":0.0 + }, + { + "date":"5/23", + "total":0.0 + }, + { + "date":"5/24", + "total":0.0 + }, + { + "date":"5/25", + "total":0.0 + }, + { + "date":"5/26", + "total":0.0 + }, + { + "date":"5/27", + "total":0.0 + }, + { + "date":"5/28", + "total":0.0 + }, + { + "date":"5/29", + "total":0.0 + }, + { + "date":"5/30", + "total":0.0 + }, + { + "date":"5/31", + "total":0.0 + }, + { + "date":"6/01", + "total":0.0 + }, + { + "date":"6/02", + "total":0.0 + }, + { + "date":"6/03", + "total":0.0 + }, + { + "date":"6/04", + "total":0.0 + }, + { + "date":"6/05", + "total":0.0 + }, + { + "date":"6/06", + "total":0.0 + }, + { + "date":"6/07", + "total":0.0 + }, + { + "date":"6/08", + "total":0.0 + }, + { + "date":"6/09", + "total":0.0 + }, + { + "date":"6/10", + "total":0.0 + }, + { + "date":"6/11", + "total":0.0 + }, + { + "date":"6/12", + "total":0.0 + }, + { + "date":"6/13", + "total":0.0 + }, + { + "date":"6/14", + "total":0.0 + }, + { + "date":"6/15", + "total":0.0 + }, + { + "date":"6/16", + "total":0.0 + }, + { + "date":"6/17", + "total":0.0 + }, + { + "date":"6/18", + "total":0.0 + }, + { + "date":"6/19", + "total":0.0 + }, + { + "date":"6/20", + "total":0.0 + }, + { + "date":"6/21", + "total":0.0 + }, + { + "date":"6/22", + "total":0.0 + }, + { + "date":"6/23", + "total":0.0 + }, + { + "date":"6/24", + "total":0.0 + }, + { + "date":"6/25", + "total":0.0 + }, + { + "date":"6/26", + "total":0.0 + }, + { + "date":"6/27", + "total":0.0 + }, + { + "date":"6/28", + "total":0.0 + }, + { + "date":"6/29", + "total":0.0 + }, + { + "date":"6/30", + "total":0.0 + }, + { + "date":"7/01", + "total":0.0 + }, + { + "date":"7/02", + "total":0.0 + }, + { + "date":"7/03", + "total":0.0 + }, + { + "date":"7/04", + "total":0.0 + }, + { + "date":"7/05", + "total":0.0 + }, + { + "date":"7/06", + "total":0.0 + }, + { + "date":"7/07", + "total":0.0 + }, + { + "date":"7/08", + "total":0.0 + }, + { + "date":"7/09", + "total":0.0 + }, + { + "date":"7/10", + "total":0.0 + }, + { + "date":"7/11", + "total":0.0 + }, + { + "date":"7/12", + "total":0.0 + }, + { + "date":"7/13", + "total":0.0 + }, + { + "date":"7/14", + "total":0.0 + }, + { + "date":"7/15", + "total":0.0 + }, + { + "date":"7/16", + "total":0.0 + }, + { + "date":"7/17", + "total":0.0 + }, + { + "date":"7/18", + "total":0.0 + }, + { + "date":"7/19", + "total":0.0 + }, + { + "date":"7/20", + "total":0.0 + }, + { + "date":"7/21", + "total":0.0 + }, + { + "date":"7/22", + "total":0.0 + }, + { + "date":"7/23", + "total":0.0 + }, + { + "date":"7/24", + "total":0.0 + }, + { + "date":"7/25", + "total":0.0 + }, + { + "date":"7/26", + "total":0.0 + }, + { + "date":"7/27", + "total":0.0 + }, + { + "date":"7/28", + "total":0.0 + }, + { + "date":"7/29", + "total":0.0 + }, + { + "date":"7/30", + "total":0.0 + }, + { + "date":"7/31", + "total":0.0 + }, + { + "date":"8/01", + "total":0.0 + }, + { + "date":"8/02", + "total":0.0 + }, + { + "date":"8/03", + "total":0.0 + }, + { + "date":"8/04", + "total":0.0 + }, + { + "date":"8/05", + "total":0.0 + }, + { + "date":"8/06", + "total":0.0 + }, + { + "date":"8/07", + "total":0.0 + }, + { + "date":"8/08", + "total":0.0 + }, + { + "date":"8/09", + "total":0.0 + }, + { + "date":"8/10", + "total":0.0 + }, + { + "date":"8/11", + "total":0.0 + }, + { + "date":"8/12", + "total":0.0 + }, + { + "date":"8/13", + "total":0.0 + }, + { + "date":"8/14", + "total":0.0 + }, + { + "date":"8/15", + "total":0.0 + }, + { + "date":"8/16", + "total":0.0 + }, + { + "date":"8/17", + "total":0.0 + }, + { + "date":"8/18", + "total":0.0 + }, + { + "date":"8/19", + "total":0.0 + }, + { + "date":"8/20", + "total":0.0 + }, + { + "date":"8/21", + "total":0.0 + }, + { + "date":"8/22", + "total":0.0 + }, + { + "date":"8/23", + "total":0.0 + }, + { + "date":"8/24", + "total":0.0 + }, + { + "date":"8/25", + "total":0.0 + }, + { + "date":"8/26", + "total":0.0 + }, + { + "date":"8/27", + "total":0.0 + }, + { + "date":"8/28", + "total":0.0 + }, + { + "date":"8/29", + "total":0.0 + }, + { + "date":"8/30", + "total":0.0 + }, + { + "date":"8/31", + "total":0.0 + }, + { + "date":"9/01", + "total":0.0 + }, + { + "date":"9/02", + "total":0.0 + }, + { + "date":"9/03", + "total":0.0 + }, + { + "date":"9/04", + "total":0.0 + }, + { + "date":"9/05", + "total":0.0 + }, + { + "date":"9/06", + "total":0.0 + }, + { + "date":"9/07", + "total":0.0 + }, + { + "date":"9/08", + "total":0.0 + }, + { + "date":"9/09", + "total":0.0 + }, + { + "date":"9/10", + "total":0.0 + }, + { + "date":"9/11", + "total":0.0 + }, + { + "date":"9/12", + "total":0.0 + }, + { + "date":"9/13", + "total":0.0 + }, + { + "date":"9/14", + "total":0.0 + }, + { + "date":"9/15", + "total":0.0 + }, + { + "date":"9/16", + "total":0.0 + }, + { + "date":"9/17", + "total":0.0 + }, + { + "date":"9/18", + "total":0.0 + }, + { + "date":"9/19", + "total":0.0 + }, + { + "date":"9/20", + "total":0.0 + }, + { + "date":"9/21", + "total":0.0 + }, + { + "date":"9/22", + "total":0.0 + }, + { + "date":"9/23", + "total":0.0 + }, + { + "date":"9/24", + "total":0.0 + }, + { + "date":"9/25", + "total":0.0 + }, + { + "date":"9/26", + "total":0.0 + }, + { + "date":"9/27", + "total":0.0 + }, + { + "date":"9/28", + "total":0.0 + }, + { + "date":"9/29", + "total":0.0 + }, + { + "date":"9/30", + "total":0.0 + }, + { + "date":"10/01", + "total":0.0 + }, + { + "date":"10/02", + "total":0.0 + }, + { + "date":"10/03", + "total":0.0 + }, + { + "date":"10/04", + "total":0.0 + }, + { + "date":"10/05", + "total":0.0 + }, + { + "date":"10/06", + "total":0.0 + }, + { + "date":"10/07", + "total":0.0 + }, + { + "date":"10/08", + "total":0.0 + }, + { + "date":"10/09", + "total":0.0 + }, + { + "date":"10/10", + "total":0.0 + }, + { + "date":"10/11", + "total":-287.0 + }, + { + "date":"10/12", + "total":-30.0 + }, + { + "date":"10/13", + "total":-81.0 + }, + { + "date":"10/14", + "total":-107.0 + }, + { + "date":"10/15", + "total":-95.0 + }, + { + "date":"10/16", + "total":-156.0 + }, + { + "date":"10/17", + "total":-81.0 + }, + { + "date":"10/18", + "total":239.0 + }, + { + "date":"10/19", + "total":-14.0 + }, + { + "date":"10/20", + "total":-35.0 + }, + { + "date":"10/21", + "total":-283.0 + }, + { + "date":"10/22", + "total":-56.0 + }, + { + "date":"10/23", + "total":-91.0 + }, + { + "date":"10/24", + "total":-217.0 + }, + { + "date":"10/25", + "total":-311.0 + }, + { + "date":"10/26", + "total":-168.0 + }, + { + "date":"10/27", + "total":-70.0 + }, + { + "date":"10/28", + "total":-41.0 + }, + { + "date":"10/29", + "total":-45.0 + }, + { + "date":"10/30", + "total":-36.0 + }, + { + "date":"10/31", + "total":0.0 + }, + { + "date":"11/01", + "total":0.0 + }, + { + "date":"11/02", + "total":0.0 + }, + { + "date":"11/03", + "total":0.0 + }, + { + "date":"11/04", + "total":0.0 + }, + { + "date":"11/05", + "total":0.0 + }, + { + "date":"11/06", + "total":0.0 + }, + { + "date":"11/07", + "total":0.0 + }, + { + "date":"11/08", + "total":0.0 + }, + { + "date":"11/09", + "total":0.0 + }, + { + "date":"11/10", + "total":-425.0 + }, + { + "date":"11/11", + "total":0.0 + }, + { + "date":"11/12", + "total":0.0 + }, + { + "date":"11/13", + "total":0.0 + }, + { + "date":"11/14", + "total":0.0 + }, + { + "date":"11/15", + "total":0.0 + }, + { + "date":"11/16", + "total":0.0 + }, + { + "date":"11/17", + "total":0.0 + }, + { + "date":"11/18", + "total":0.0 + }, + { + "date":"11/19", + "total":0.0 + }, + { + "date":"11/20", + "total":0.0 + }, + { + "date":"11/21", + "total":0.0 + }, + { + "date":"11/22", + "total":0.0 + }, + { + "date":"11/23", + "total":0.0 + }, + { + "date":"11/24", + "total":0.0 + }, + { + "date":"11/25", + "total":0.0 + }, + { + "date":"11/26", + "total":0.0 + }, + { + "date":"11/27", + "total":0.0 + }, + { + "date":"11/28", + "total":0.0 + }, + { + "date":"11/29", + "total":0.0 + }, + { + "date":"11/30", + "total":0.0 + }, + { + "date":"12/01", + "total":0.0 + }, + { + "date":"12/02", + "total":0.0 + }, + { + "date":"12/03", + "total":0.0 + }, + { + "date":"12/04", + "total":0.0 + }, + { + "date":"12/05", + "total":0.0 + }, + { + "date":"12/06", + "total":0.0 + }, + { + "date":"12/07", + "total":0.0 + }, + { + "date":"12/08", + "total":0.0 + }, + { + "date":"12/09", + "total":0.0 + }, + { + "date":"12/10", + "total":0.0 + }, + { + "date":"12/11", + "total":0.0 + }, + { + "date":"12/12", + "total":0.0 + }, + { + "date":"12/13", + "total":0.0 + }, + { + "date":"12/14", + "total":0.0 + }, + { + "date":"12/15", + "total":0.0 + }, + { + "date":"12/16", + "total":0.0 + }, + { + "date":"12/17", + "total":0.0 + }, + { + "date":"12/18", + "total":0.0 + }, + { + "date":"12/19", + "total":0.0 + }, + { + "date":"12/20", + "total":0.0 + }, + { + "date":"12/21", + "total":0.0 + }, + { + "date":"12/22", + "total":0.0 + }, + { + "date":"12/23", + "total":0.0 + }, + { + "date":"12/24", + "total":0.0 + }, + { + "date":"12/25", + "total":0.0 + }, + { + "date":"12/26", + "total":0.0 + }, + { + "date":"12/27", + "total":0.0 + }, + { + "date":"12/28", + "total":0.0 + }, + { + "date":"12/29", + "total":0.0 + }, + { + "date":"12/30", + "total":0.0 + }, + { + "date":"12/31", + "total":0.0 + }, + { + "date":"1/01", + "total":0.0 + }, + { + "date":"1/02", + "total":0.0 + }, + { + "date":"1/03", + "total":0.0 + }, + { + "date":"1/04", + "total":0.0 + }, + { + "date":"1/05", + "total":0.0 + }, + { + "date":"1/06", + "total":0.0 + }, + { + "date":"1/07", + "total":0.0 + }, + { + "date":"1/08", + "total":0.0 + }, + { + "date":"1/09", + "total":0.0 + }, + { + "date":"1/10", + "total":0.0 + }, + { + "date":"1/11", + "total":0.0 + }, + { + "date":"1/12", + "total":0.0 + }, + { + "date":"1/13", + "total":0.0 + }, + { + "date":"1/14", + "total":0.0 + }, + { + "date":"1/15", + "total":0.0 + }, + { + "date":"1/16", + "total":0.0 + }, + { + "date":"1/17", + "total":0.0 + }, + { + "date":"1/18", + "total":0.0 + }, + { + "date":"1/19", + "total":0.0 + }, + { + "date":"1/20", + "total":0.0 + }, + { + "date":"1/21", + "total":0.0 + }, + { + "date":"1/22", + "total":0.0 + }, + { + "date":"1/23", + "total":0.0 + }, + { + "date":"1/24", + "total":0.0 + }, + { + "date":"1/25", + "total":0.0 + }, + { + "date":"1/26", + "total":0.0 + }, + { + "date":"1/27", + "total":0.0 + }, + { + "date":"1/28", + "total":0.0 + }, + { + "date":"1/29", + "total":0.0 + }, + { + "date":"1/30", + "total":0.0 + }, + { + "date":"1/31", + "total":0.0 + }, + { + "date":"2/01", + "total":0.0 + }, + { + "date":"2/02", + "total":0.0 + }, + { + "date":"2/03", + "total":0.0 + }, + { + "date":"2/04", + "total":0.0 + }, + { + "date":"2/05", + "total":0.0 + }, + { + "date":"2/06", + "total":0.0 + }, + { + "date":"2/07", + "total":0.0 + }, + { + "date":"2/08", + "total":0.0 + }, + { + "date":"2/09", + "total":0.0 + }, + { + "date":"2/10", + "total":0.0 + }, + { + "date":"2/11", + "total":0.0 + }, + { + "date":"2/12", + "total":0.0 + }, + { + "date":"2/13", + "total":0.0 + }, + { + "date":"2/14", + "total":0.0 + }, + { + "date":"2/15", + "total":0.0 + }, + { + "date":"2/16", + "total":-21.0 + }, + { + "date":"2/17", + "total":-22.0 + }, + { + "date":"2/18", + "total":0.0 + }, + { + "date":"2/19", + "total":-26.0 + }, + { + "date":"2/20", + "total":-405.0 + }, + { + "date":"2/21", + "total":-176.0 + }, + { + "date":"2/22", + "total":-29.0 + }, + { + "date":"2/23", + "total":1419.0 + }, + { + "date":"2/24", + "total":1442.0 + }, + { + "date":"2/25", + "total":1312.0 + }, + { + "date":"2/26", + "total":1424.0 + }, + { + "date":"2/27", + "total":1575.0 + }, + { + "date":"2/28", + "total":985.0 + }, + { + "date":"3/01", + "total":1510.0 + }, + { + "date":"3/02", + "total":1272.0 + }, + { + "date":"3/03", + "total":1409.0 + }, + { + "date":"3/04", + "total":1168.0 + }, + { + "date":"3/05", + "total":1581.0 + }, + { + "date":"3/06", + "total":1416.0 + }, + { + "date":"3/07", + "total":1312.0 + }, + { + "date":"3/08", + "total":1387.0 + }, + { + "date":"3/09", + "total":1390.0 + }, + { + "date":"3/10", + "total":1489.0 + }, + { + "date":"3/11", + "total":1451.0 + }, + { + "date":"3/12", + "total":1454.0 + }, + { + "date":"3/13", + "total":425.0 + } + ], + "ordinate_axis_min":"-1800.0", + "ordinate_axis_max":"3700.0", + "goal":"1500" +} \ No newline at end of file diff --git a/tests/test_client.py b/tests/test_client.py index 2b7a693..c687d79 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -431,3 +431,32 @@ def test_get_completed_day(self): day.complete, True, ) + + def test_get_report(self): + with patch.object(self.client, "_get_json_for_url") as get_doc: + get_doc.return_value = self.get_json_data( + "report_nutrition_net_calories.json" + ) + actual_measurements = self.client.get_report( + report_name="Net Calories", + report_category="Nutrition", + lower_bound=datetime.date.today() - datetime.timedelta(days=4), + ) + + # Dates are determined based on the assumption that the results will + # always start from the current day. Dates held in sample data file are + # therefore irrelevant for this test. + + expected_measurements = OrderedDict( + sorted( + [ + (datetime.date.today(), 425.0), + (datetime.date.today() - datetime.timedelta(days=1), 1454.0), + (datetime.date.today() - datetime.timedelta(days=2), 1451.0), + (datetime.date.today() - datetime.timedelta(days=3), 1489.0), + (datetime.date.today() - datetime.timedelta(days=4), 1390.0), + ] + ) + ) + + self.assertEqual(expected_measurements, actual_measurements) From 5c6bc75e84a86c0b7126492d539b6a6b36788bbf Mon Sep 17 00:00:00 2001 From: judgewooden Date: Wed, 27 Jul 2022 21:20:43 +0200 Subject: [PATCH 04/36] Negative values export as positive (#143) Adding negative symbol (-) to _get_numeric() --- myfitnesspal/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/myfitnesspal/client.py b/myfitnesspal/client.py index b0f8be6..0986aa1 100644 --- a/myfitnesspal/client.py +++ b/myfitnesspal/client.py @@ -252,7 +252,7 @@ def _get_numeric(self, string: str) -> float: ) else: try: - str_value = re.sub(r"[^\d.]+", "", string) + str_value = re.sub(r"[^-\d.]+", "", string) return float(str_value) except ValueError: return 0 From af516ff709825ede65b3ea71b8155cd1f15a5140 Mon Sep 17 00:00:00 2001 From: Adam Coddington Date: Fri, 26 Aug 2022 19:54:04 -0700 Subject: [PATCH 05/36] Updates python-myfitnesspal to use browser_cookie3 for gathering login credentials directly from local browsers instead of directly logging in to MyfitnessPal. See #144 for more information. --- .github/workflows/test.yml | 3 -- .pre-commit-config.yaml | 13 ++---- changelog.markdown | 4 ++ docs/source/cmdline.rst | 16 +------ docs/source/getting_started.rst | 33 +++++++------ docs/source/how_to/diary.rst | 4 +- docs/source/how_to/exercises.rst | 4 +- docs/source/how_to/food_search.rst | 4 +- docs/source/how_to/friends_diary.rst | 4 ++ docs/source/how_to/measurements.rst | 2 +- myfitnesspal/client.py | 70 ++++++++++++---------------- myfitnesspal/commands.py | 44 +---------------- myfitnesspal/keyring_utils.py | 39 ---------------- requirements.txt | 2 +- 14 files changed, 73 insertions(+), 169 deletions(-) delete mode 100644 myfitnesspal/keyring_utils.py diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7e39b3a..dfdff2f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -24,6 +24,3 @@ jobs: - name: Test with pytest run: | pytest - env: - MFP_INTEGRATION_TESTING_USERNAME: ${{ secrets.MFP_INTEGRATION_TESTING_USERNAME }} - MFP_INTEGRATION_TESTING_PASSWORD: ${{ secrets.MFP_INTEGRATION_TESTING_PASSWORD }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index fc3d8d3..0487f39 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -8,20 +8,13 @@ repos: hooks: - id: isort - repo: https://github.com/ambv/black - rev: 22.3.0 + rev: 22.6.0 hooks: - id: black - - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v2.4.0 + - repo: https://github.com/pycqa/flake8 + rev: 5.0.4 hooks: - - id: end-of-file-fixer - files: '.*\.py$' - id: flake8 - additional_dependencies: - - flake8-bugbear==19.3.0 - - flake8-printf-formatting==1.1.0 - - flake8-pytest==1.3 - - flake8-pytest-style==0.1.3 - repo: https://github.com/asottile/pyupgrade rev: v1.25.1 hooks: diff --git a/changelog.markdown b/changelog.markdown index e51aba6..033488b 100644 --- a/changelog.markdown +++ b/changelog.markdown @@ -1,3 +1,7 @@ +# 2.0.0 (2022-08-26) + +* Now uses the [`browser_cookie3`](https://github.com/borisbabic/browser_cookie3) library for gathering log in credentials instead of logging in to MyFitnessPal directly. This became necessary due to the recent addition of a hidden captcha in the log-in flow for MyFitnessPal; see [Issue #144](https://github.com/coddingtonbear/python-myfitnesspal/issues/144) for details. + # 1.13 (2018-11-20) * Adds support for searching for food items and accessing their nutritional values. Thanks @pydolan! diff --git a/docs/source/cmdline.rst b/docs/source/cmdline.rst index 909b6ed..cf41cf6 100644 --- a/docs/source/cmdline.rst +++ b/docs/source/cmdline.rst @@ -3,20 +3,8 @@ Using the Command-Line API Although most people will probably be using Python-MyFitnessPal as a way of integrating their MyFitnessPal data into another application, -Python-MyFitnessPal does provide a command-line API with a handful of -commands described below. - -``store-password $USERNAME`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Store a password for a given MyFitnessPal account in your system’s -keyring. - -``delete-password $USERNAME`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Delete a password for a given MyFitnessPal account from your system -keyring. +Python-MyFitnessPal does provide a command-line API with a single +command described below. ``day $USERNAME [$DATE]`` ~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/source/getting_started.rst b/docs/source/getting_started.rst index aa38093..0863adf 100644 --- a/docs/source/getting_started.rst +++ b/docs/source/getting_started.rst @@ -18,16 +18,23 @@ You can either install from pip:: Authentication -------------- -It is a good security practice to not type out the passwords for any of your services (including MyFitnessPal) in either a source file or in the console in such a way that somebody else might be able to read them. Toward that end, python-myfitnesspal allows you to use your system keyring. - -To store your MyFitnessPal password in the system keyring, run:: - - myfitnesspal store-password my_username - -You will immediately be asked for your password, and that password will be stored in your system keyring for later interactions with MyFitnessPal. - -Please note that all examples below *assume* you've stored your password in your system keyring like above, but you can also provide your password by providing your password as a keyword argument to the `myfitnesspal.Client` instance: - -.. code:: python - - client = myfitnesspal.Client('my_username', password='my_password') +This library uses your local browser's MyFitnessPal cookies +for interacting with MyFitnessPal via the `browser_cookie3 `_ library. +To control which user account this library uses for interacting with MyFitnessPal, +just log in to the appropriate account in your browser +and, +with a bit of luck, +python-myfitnesspal should be able to find the authentication credentials needed. + +By default, this library will look for cookies set for the ``www.myfitnesspal.com`` and ``myfitnesspal.com`` domains in all browsers supported by ``browser_cookie3``. You can control which cookiejar is used by passing a ``http.cookiejar.CookieJar`` object via the constructor's `cookiejar` keyword parameter. See `browser_cookie3's readme `_ for details around how you might select a cookiejar from a particular browser. + +.. note:: + + Starting on August 25th, 2022, MyFitnessPal added + a hidden captcha to their login flow. + That change unfortunately prevents this library from logging-in directly, + and as of version 2.0 of python-myfitnesspal, + this library now relies on reading browser cookies directly + for gathering login credentials. + + See `Issue #144 `_ for details and context. diff --git a/docs/source/how_to/diary.rst b/docs/source/how_to/diary.rst index 44cf32e..e8a1d5c 100644 --- a/docs/source/how_to/diary.rst +++ b/docs/source/how_to/diary.rst @@ -7,7 +7,7 @@ To access a single day’s information: import myfitnesspal - client = myfitnesspal.Client('my_username') + client = myfitnesspal.Client() day = client.get_date(2013, 3, 2) day @@ -133,7 +133,7 @@ argument. .. code:: python - client = myfitnesspal.Client('my_username', unit_aware=True) + client = myfitnesspal.Client(unit_aware=True) day = client.get_date(2013, 3, 2) lunch = day['lunch'] print lunch diff --git a/docs/source/how_to/exercises.rst b/docs/source/how_to/exercises.rst index 0ea40ae..e1fa89e 100644 --- a/docs/source/how_to/exercises.rst +++ b/docs/source/how_to/exercises.rst @@ -11,7 +11,7 @@ To get a list of cardiovascular exercises import myfitnesspal - client = myfitnesspal.Client('my_username') + client = myfitnesspal.Client() day = client.get_date(2019, 3, 12) @@ -38,7 +38,7 @@ To get a list of strength exercises import myfitnesspal - client = myfitnesspal.Client('my_username') + client = myfitnesspal.Client() day = client.get_date(2019, 3, 12) diff --git a/docs/source/how_to/food_search.rst b/docs/source/how_to/food_search.rst index 5f463ee..ca09def 100644 --- a/docs/source/how_to/food_search.rst +++ b/docs/source/how_to/food_search.rst @@ -7,7 +7,7 @@ To search for items: import myfitnesspal - client = myfitnesspal.Client('my_username') + client = myfitnesspal.Client() food_items = client.get_food_search_results("bacon cheeseburger") food_items @@ -34,7 +34,7 @@ To get details for a particular food: import myfitnesspal - client = myfitnesspal.Client('my_username') + client = myfitnesspal.Client() item = client.get_food_item_details("89755756637885") item.servings diff --git a/docs/source/how_to/friends_diary.rst b/docs/source/how_to/friends_diary.rst index f7629da..00d8fa0 100644 --- a/docs/source/how_to/friends_diary.rst +++ b/docs/source/how_to/friends_diary.rst @@ -6,6 +6,10 @@ diary entries: .. code:: python + import myfitnesspal + + client = myfitnesspal.Client() + friend_day = client.get_date(2020, 8, 23, username="username_of_my_friend") >>> friend_day <08/23/20 {'calories': 891.0, 'carbohydrates': 105.0, 'fat': 38.0, 'protein': 29.0, 'sodium': 0.0, 'sugar': 2.0}> diff --git a/docs/source/how_to/measurements.rst b/docs/source/how_to/measurements.rst index 587619b..407e29a 100644 --- a/docs/source/how_to/measurements.rst +++ b/docs/source/how_to/measurements.rst @@ -7,7 +7,7 @@ To access measurements from the past 30 days: import myfitnesspal - client = myfitnesspal.Client('my_username') + client = myfitnesspal.Client() weight = client.get_measurements('Weight') weight diff --git a/myfitnesspal/client.py b/myfitnesspal/client.py index 0986aa1..b46c72b 100644 --- a/myfitnesspal/client.py +++ b/myfitnesspal/client.py @@ -5,8 +5,10 @@ import logging import re from collections import OrderedDict +from http.cookiejar import CookieJar from typing import Any, Dict, List, Optional, Union, cast, overload +import browser_cookie3 import lxml.html import requests from measurement.base import MeasureBase @@ -20,7 +22,6 @@ from .exceptions import MyfitnesspalLoginError, MyfitnesspalRequestFailed from .exercise import Exercise from .fooditem import FoodItem -from .keyring_utils import get_password_from_keyring from .meal import Meal from .note import Note @@ -32,6 +33,10 @@ class Client(MFPBase): """Provides access to MyFitnessPal APIs""" + COOKIE_DOMAINS = [ + "myfitnesspal.com", + "www.myfitnesspal.com", + ] BASE_URL = "http://www.myfitnesspal.com/" BASE_URL_SECURE = "https://www.myfitnesspal.com/" BASE_API_URL = "https://api.myfitnesspal.com/" @@ -56,28 +61,27 @@ class Client(MFPBase): def __init__( self, - username: str, - password: Optional[str] = None, - login: bool = True, + cookiejar: CookieJar = None, unit_aware: bool = False, ): - self.provided_username = username - if password is None: - password = get_password_from_keyring(username) - self.__password = password self.unit_aware = unit_aware - self._user_metadata: Optional[types.UserMetadata] = None - self._auth_data: Optional[types.AuthData] = None - self.session = requests.Session() self.session.headers.update( { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.104 Safari/537.36" } ) - if login: - self._login() + if cookiejar is not None: + self.session.cookies.update(cookiejar) + else: + for domain_name in self.COOKIE_DOMAINS: + self.session.cookies.update( + browser_cookie3.load(domain_name=domain_name) + ) + + self._auth_data = self._get_auth_data() + self._user_metadata = self._get_user_metadata() @property def user_id(self) -> Optional[types.MyfitnesspalUserId]: @@ -88,7 +92,7 @@ def user_id(self) -> Optional[types.MyfitnesspalUserId]: return self._auth_data["user_id"] @property - def user_metadata(self) -> Optional[types.UserMetadata]: + def user_metadata(self) -> types.UserMetadata: """Metadata about of the logged-in account.""" return self._user_metadata @@ -108,31 +112,7 @@ def effective_username(self) -> str: will fall back to the one provided if it is not. """ - if self.user_metadata: - return self.user_metadata["username"] - return self.provided_username - - def _login(self): - csrf_url = parse.urljoin(self.BASE_URL_SECURE, self.CSRF_PATH) - csrf_token = self._get_json_for_url(csrf_url)["csrfToken"] - - login_json_url = parse.urljoin(self.BASE_URL_SECURE, self.LOGIN_JSON_PATH) - - result = self.session.post( - login_json_url, - data={ - "csrfToken": csrf_token, - "username": self.effective_username, - "password": self.__password, - "redirect": False, - "json": True, - }, - ) - if "error=CredentialsSignin" in result.url: - raise MyfitnesspalLoginError() - - self._auth_data = self._get_auth_data() - self._user_metadata = self._get_user_metadata() + return self.user_metadata["username"] def _get_auth_data(self) -> types.AuthData: result = self._get_request_for_url( @@ -144,6 +124,15 @@ def _get_auth_data(self) -> types.AuthData: "status code: {status}".format(status=result.status_code) ) + if not result.headers["Content-Type"].startswith("application/json"): + # That we didn't receive a JSON document for this request + # is the only obvious clear signal that we aren't logged-in. + raise MyfitnesspalLoginError( + "Could not access MyFitnessPal using the cookies provided " + "by your browser. Are you sure you have logged in to " + "MyFitnessPal using a browser on this computer?" + ) + return result.json() def _get_user_metadata(self) -> types.UserMetadata: @@ -719,6 +708,9 @@ def get_report( lower_bound, upper_bound ) + assert upper_bound + assert lower_bound + # Get the URL for the report json_data = self._get_json_for_url( self._get_url_for_report(report_name, report_category, lower_bound) diff --git a/myfitnesspal/commands.py b/myfitnesspal/commands.py index e60aed1..158300c 100644 --- a/myfitnesspal/commands.py +++ b/myfitnesspal/commands.py @@ -1,18 +1,12 @@ import argparse import logging from datetime import datetime -from getpass import getpass from typing import Dict from dateutil.parser import parse as dateparse from rich import print from . import Client -from .keyring_utils import ( - delete_password_in_keyring, - get_password_from_keyring_or_interactive, - store_password_in_keyring, -) from .types import CommandDefinition COMMANDS: Dict[str, CommandDefinition] = {} @@ -56,46 +50,11 @@ def decorator(fn): return decorator -@command( - "Store a MyFitnessPal password in your system keychain.", - aliases=["store-password"], -) -def store_password(args, *extra, **kwargs): - parser = argparse.ArgumentParser() - parser.add_argument( - "username", help="The MyFitnessPal username for which to store this password." - ) - args = parser.parse_args(extra) - - password = getpass(f"MyFitnessPal Password for {args.username}: ") - - store_password_in_keyring(args.username, password) - - -@command( - "Delete a MyFitnessPal password from your system keychain.", - aliases=["delete-password"], -) -def delete_password(args, *extra, **kwargs): - parser = argparse.ArgumentParser() - parser.add_argument( - "username", - help="The MyFitnessPal username for which to delete a stored password.", - ) - args = parser.parse_args(extra) - - delete_password_in_keyring(args.username) - - @command( "Display MyFitnessPal data for a given date.", ) def day(args, *extra, **kwargs): parser = argparse.ArgumentParser() - parser.add_argument( - "username", - help="The MyFitnessPal username for which to delete a stored password.", - ) parser.add_argument( "date", nargs="?", @@ -105,8 +64,7 @@ def day(args, *extra, **kwargs): ) args = parser.parse_args(extra) - password = get_password_from_keyring_or_interactive(args.username) - client = Client(args.username, password) + client = Client() day = client.get_date(args.date) date_str = args.date.strftime("%Y-%m-%d") diff --git a/myfitnesspal/keyring_utils.py b/myfitnesspal/keyring_utils.py deleted file mode 100644 index db81536..0000000 --- a/myfitnesspal/keyring_utils.py +++ /dev/null @@ -1,39 +0,0 @@ -import getpass - -import keyring - -KEYRING_SYSTEM = "python-myfitnesspal://myfitnesspal-password" - - -class NoStoredPasswordAvailable(Exception): - pass - - -def get_password_from_keyring(username: str) -> str: - result = keyring.get_password(KEYRING_SYSTEM, username) - if result is None: - raise NoStoredPasswordAvailable( - "No MyFitnessPal password for {username} could be found " - "in the system keychain. Use the `store-password` " - "command-line command for storing a password for this " - "username.".format( - username=username, - ) - ) - - return result - - -def store_password_in_keyring(username: str, password: str) -> None: - return keyring.set_password(KEYRING_SYSTEM, username, password) - - -def delete_password_in_keyring(username: str) -> None: - return keyring.delete_password(KEYRING_SYSTEM, username) - - -def get_password_from_keyring_or_interactive(username: str) -> str: - try: - return get_password_from_keyring(username) - except NoStoredPasswordAvailable: - return getpass.getpass(f"MyFitnessPal password for {username}: ") diff --git a/requirements.txt b/requirements.txt index d92370c..dfc588f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ blessed>=1.8.5,<2.0 -keyring>=8.0,<22.0 lxml>=4.2.5,<5 measurement>=3.2.0,<4.0 python-dateutil>=2.4,<3 requests>=2.17.0,<3 rich>=9.10.0,<10 +browser_cookie3>=0.16.1,<1 From 69d5edce5c4a5072357c043bd2dea6ae63b9537a Mon Sep 17 00:00:00 2001 From: Adam Coddington Date: Fri, 26 Aug 2022 19:54:49 -0700 Subject: [PATCH 06/36] Updates python-myfitnesspal to use browser_cookie3 for gathering login credentials directly from local browsers instead of directly logging in to MyfitnessPal. See #144 for more information. --- .github/workflows/lint.yml | 2 +- tests/test_client.py | 16 +++++++++++----- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index ad90afc..c675514 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -15,7 +15,7 @@ jobs: python-version: 3.8 - name: Install Python dependencies - run: pip install black==22.3.0 flake8==3.8.3 + run: pip install black==22.6.0 flake8==5.0.4 - name: Run linters uses: samuelmeuli/lint-action@v1.5.3 diff --git a/tests/test_client.py b/tests/test_client.py index c687d79..83630d7 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -1,11 +1,12 @@ import copy import datetime from collections import OrderedDict -from unittest.mock import patch +from http.cookiejar import CookieJar +from unittest.mock import DEFAULT, patch from measurement.measures import Energy, Weight -from myfitnesspal import Client +import myfitnesspal from .base import MFPTestCase @@ -16,9 +17,14 @@ def setUp(self): self.arbitrary_password = "beta" self.arbitrary_date1 = datetime.date(2015, 4, 20) self.arbitrary_date2 = datetime.date(2015, 4, 28) - self.client = Client( - self.arbitrary_username, self.arbitrary_password, login=False - ) + + with patch.multiple( + "myfitnesspal.Client", _get_auth_data=DEFAULT, _get_user_metadata=DEFAULT + ) as patches: + patches["_get_user_metadata"].return_value = {"username": ""} + + self.client = myfitnesspal.Client(cookiejar=CookieJar()) + super().setUp() def test_get_measurement_ids(self): From bf0360bab0372994883a99763ddbc95ada209788 Mon Sep 17 00:00:00 2001 From: Adam Coddington Date: Fri, 26 Aug 2022 19:55:09 -0700 Subject: [PATCH 07/36] =?UTF-8?q?Bump=20version:=201.17.0=20=E2=86=92=202.?= =?UTF-8?q?0.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .bumpversion.cfg | 2 +- myfitnesspal/__init__.py | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 6e5e523..71f7da5 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 1.17.0 +current_version = 2.0.0 commit = True tag = True tag_name = {new_version} diff --git a/myfitnesspal/__init__.py b/myfitnesspal/__init__.py index 8a88388..0b90608 100644 --- a/myfitnesspal/__init__.py +++ b/myfitnesspal/__init__.py @@ -1,5 +1,5 @@ from myfitnesspal.client import Client # noqa -__version__ = "1.17.0" +__version__ = "2.0.0" VERSION = tuple(int(v) for v in __version__.split(".")) diff --git a/setup.py b/setup.py index 3ffd2a6..b42acfa 100644 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ setup( name="myfitnesspal", - version="1.17.0", + version="2.0.0", url="http://github.com/coddingtonbear/python-myfitnesspal/", description="Access health and fitness data stored in Myfitnesspal", author="Adam Coddington", From 892dd4c27b61fbfa7558540137f66798a2de0655 Mon Sep 17 00:00:00 2001 From: Adam Coddington Date: Fri, 26 Aug 2022 19:56:51 -0700 Subject: [PATCH 08/36] Updating 'rich' requirement version. --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index dfc588f..80ce0b4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,5 +3,5 @@ lxml>=4.2.5,<5 measurement>=3.2.0,<4.0 python-dateutil>=2.4,<3 requests>=2.17.0,<3 -rich>=9.10.0,<10 +rich>=12,<13 browser_cookie3>=0.16.1,<1 From ce21cc53777ab2034b39ea3f35874f515c588c89 Mon Sep 17 00:00:00 2001 From: Adam Coddington Date: Fri, 26 Aug 2022 20:16:13 -0700 Subject: [PATCH 09/36] Updating docs. --- docs/source/cmdline.rst | 4 ++-- docs/source/how_to/reports.rst | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/source/cmdline.rst b/docs/source/cmdline.rst index cf41cf6..8311433 100644 --- a/docs/source/cmdline.rst +++ b/docs/source/cmdline.rst @@ -6,8 +6,8 @@ of integrating their MyFitnessPal data into another application, Python-MyFitnessPal does provide a command-line API with a single command described below. -``day $USERNAME [$DATE]`` -~~~~~~~~~~~~~~~~~~~~~~~~~ +``day [$DATE]`` +~~~~~~~~~~~~~~~ Display meals and totals for a given date. If no date is specified, totals will be printed for today. diff --git a/docs/source/how_to/reports.rst b/docs/source/how_to/reports.rst index 9b36a55..0ce7ed0 100644 --- a/docs/source/how_to/reports.rst +++ b/docs/source/how_to/reports.rst @@ -7,7 +7,7 @@ To access report data from the past 30 days: import myfitnesspal - client = myfitnesspal.Client('my_username') + client = myfitnesspal.Client() client.get_report(report_name="Net Calories", report_category="Nutrition") # >> OrderedDict([(datetime.date(2015, 5, 14), 1701.0), (datetime.date(2015, 5, 13), 1732.8), (datetime.date(2015, 5,12), 1721.8), From d6f16b3dcea3d6dd85de2c08472b0176f0b0ad26 Mon Sep 17 00:00:00 2001 From: Adam Coddington Date: Sat, 27 Aug 2022 10:27:19 -0700 Subject: [PATCH 10/36] Adding 'Upgrading to 2.x' doc. --- docs/source/index.rst | 1 + docs/source/upgrading.rst | 50 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 docs/source/upgrading.rst diff --git a/docs/source/index.rst b/docs/source/index.rst index 554dc70..1911809 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -14,6 +14,7 @@ Python Myfitnesspal how_to/index contributing api/index + upgrading Do you track your eating habits on `MyFitnessPal `_? Have you ever wanted to analyze the information you're entering into MyFitnessPal programatically? diff --git a/docs/source/upgrading.rst b/docs/source/upgrading.rst new file mode 100644 index 0000000..5224354 --- /dev/null +++ b/docs/source/upgrading.rst @@ -0,0 +1,50 @@ +Upgrading from 1.x to 2.x +========================= + +Between the 1.x and 2.x versions of this library, +the mechanism used for authentication changed, +and this change has an impact +on how you instantiate the ``myfitnesspal.Client`` object. + +For more information about why this change was necessary, +see `Issue #144 `_. + +Version 1.x **(Obsolete)** +-------------------------- + +Before getting started, +you would store a password in your system keyring +for the user account you would like to use +(in this example: 'myusername'). + +In your code, you would then instantiate your +``myfitnespal.Client`` like this: + +.. code:: python + + import myfitnesspal + + client = myfitnesspal.Client('myusername') + +Version 2.x **(Current)** +------------------------- + +Before getting started, +now you should open a web browser on the same computer +you will be using this library from, +go to `https://myfitnesspal.com/ `_, +and log in to MyFitnessPal using +the user account you would like to use. + +In your code, you can then instantiate your +``myfitnespal.Client`` like this: + +.. code:: python + + import myfitnesspal + + client = myfitnesspal.Client() + +Note that the instantiation no longer accepts a username, +and instead reads the log in information directly +from your browser. From ee1b811c468a4a754896fa2212916ca1b6f5290a Mon Sep 17 00:00:00 2001 From: Adam Coddington Date: Sat, 27 Aug 2022 10:46:23 -0700 Subject: [PATCH 11/36] Doc formatting changes. --- docs/source/upgrading.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/source/upgrading.rst b/docs/source/upgrading.rst index 5224354..e530a40 100644 --- a/docs/source/upgrading.rst +++ b/docs/source/upgrading.rst @@ -9,8 +9,8 @@ on how you instantiate the ``myfitnesspal.Client`` object. For more information about why this change was necessary, see `Issue #144 `_. -Version 1.x **(Obsolete)** --------------------------- +Version 1.x (Obsolete) +---------------------- Before getting started, you would store a password in your system keyring @@ -26,8 +26,8 @@ In your code, you would then instantiate your client = myfitnesspal.Client('myusername') -Version 2.x **(Current)** -------------------------- +Version 2.x (Current) +--------------------- Before getting started, now you should open a web browser on the same computer From 989137db47a42186c70209a22df6c4db489c1bc9 Mon Sep 17 00:00:00 2001 From: Adam Coddington Date: Sun, 11 Sep 2022 14:23:54 -0700 Subject: [PATCH 12/36] Adding instuctions for helping people log into myfitnesspal when running this via WSL. --- docs/source/how_to/index.rst | 1 + docs/source/how_to/use_with_wsl.rst | 11 +++++++++++ 2 files changed, 12 insertions(+) create mode 100644 docs/source/how_to/use_with_wsl.rst diff --git a/docs/source/how_to/index.rst b/docs/source/how_to/index.rst index e9d36ab..829ed16 100644 --- a/docs/source/how_to/index.rst +++ b/docs/source/how_to/index.rst @@ -11,3 +11,4 @@ How-to Guides measurements food_search reports + use_with_wsl diff --git a/docs/source/how_to/use_with_wsl.rst b/docs/source/how_to/use_with_wsl.rst new file mode 100644 index 0000000..d0d2406 --- /dev/null +++ b/docs/source/how_to/use_with_wsl.rst @@ -0,0 +1,11 @@ +Use on Windows Subsystem for Linux +================================== + +When using python-myfitnesspal on Windows via Windows Subsystem for Linux +you will encounter one extra complication when logging in +due to the mechanism python-myfitnesspal uses +for gathering authentication credentials. + +You must log in to MyFitnessPal +via a browser installed on your guest (Linux) installation +instead of via one installed the conventional way. From 934960ea9ace2b6c59fef472ad0d490c337dd5a0 Mon Sep 17 00:00:00 2001 From: Hannah Date: Wed, 26 Oct 2022 08:51:28 -0700 Subject: [PATCH 13/36] 146 fix get report datetime issue (#147) * return dict with keys of type date instead of datetime (as per type hint); fix indentation of test * MFP get_report API (URL) seems to return incorrect numbers or zeroes for dates over 80 days ago. Log a warning in this case. --- myfitnesspal/client.py | 11 +++++---- tests/test_client.py | 52 +++++++++++++++++++++--------------------- 2 files changed, 33 insertions(+), 30 deletions(-) diff --git a/myfitnesspal/client.py b/myfitnesspal/client.py index b46c72b..dd93ca7 100644 --- a/myfitnesspal/client.py +++ b/myfitnesspal/client.py @@ -704,6 +704,9 @@ def get_report( """ Returns report data of a given name and category between two dates. """ + if (datetime.date.today()-lower_bound).days > 80: + logger.warning(f"Report API may not be able to look back this far. Some results may be incorrect.") + upper_bound, lower_bound = self._ensure_upper_lower_bound( lower_bound, upper_bound ) @@ -748,13 +751,13 @@ def _get_report_data(self, json_data: dict) -> Dict[datetime.date, float]: if not data: return report_data - for index, entry in enumerate(json_data["data"]): + for index, entry in enumerate(data): # Dates are returned without year. # As the returned dates will always begin from the current day, the - # correct date can be determined using the entries index + # correct date can be determined using the entry's index date = ( - datetime.datetime.today() - - datetime.timedelta(days=len(json_data["data"])) + datetime.date.today() + - datetime.timedelta(days=len(data)) + datetime.timedelta(days=index + 1) ) diff --git a/tests/test_client.py b/tests/test_client.py index 83630d7..50c28f1 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -438,31 +438,31 @@ def test_get_completed_day(self): True, ) - def test_get_report(self): - with patch.object(self.client, "_get_json_for_url") as get_doc: - get_doc.return_value = self.get_json_data( - "report_nutrition_net_calories.json" - ) - actual_measurements = self.client.get_report( - report_name="Net Calories", - report_category="Nutrition", - lower_bound=datetime.date.today() - datetime.timedelta(days=4), - ) - - # Dates are determined based on the assumption that the results will - # always start from the current day. Dates held in sample data file are - # therefore irrelevant for this test. - - expected_measurements = OrderedDict( - sorted( - [ - (datetime.date.today(), 425.0), - (datetime.date.today() - datetime.timedelta(days=1), 1454.0), - (datetime.date.today() - datetime.timedelta(days=2), 1451.0), - (datetime.date.today() - datetime.timedelta(days=3), 1489.0), - (datetime.date.today() - datetime.timedelta(days=4), 1390.0), - ] - ) + def test_get_report(self): + with patch.object(self.client, "_get_json_for_url") as get_doc: + get_doc.return_value = self.get_json_data( + "report_nutrition_net_calories.json" ) + actual_measurements = self.client.get_report( + report_name="Net Calories", + report_category="Nutrition", + lower_bound=datetime.date.today() - datetime.timedelta(days=4), + ) + + # Dates are determined based on the assumption that the results will + # always start from the current day. Dates held in sample data file are + # therefore irrelevant for this test. + + expected_measurements = OrderedDict( + sorted( + [ + (datetime.date.today(), 425.0), + (datetime.date.today() - datetime.timedelta(days=1), 1454.0), + (datetime.date.today() - datetime.timedelta(days=2), 1451.0), + (datetime.date.today() - datetime.timedelta(days=3), 1489.0), + (datetime.date.today() - datetime.timedelta(days=4), 1390.0), + ] + ) + ) - self.assertEqual(expected_measurements, actual_measurements) + self.assertEqual(expected_measurements, actual_measurements) From 223995b5f67933a5c5165adddd7617e6fd41cee5 Mon Sep 17 00:00:00 2001 From: Adam Coddington Date: Mon, 16 Jan 2023 12:46:58 -0800 Subject: [PATCH 14/36] [#147] Minor style fixes. --- myfitnesspal/client.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/myfitnesspal/client.py b/myfitnesspal/client.py index dd93ca7..8e426a0 100644 --- a/myfitnesspal/client.py +++ b/myfitnesspal/client.py @@ -704,8 +704,10 @@ def get_report( """ Returns report data of a given name and category between two dates. """ - if (datetime.date.today()-lower_bound).days > 80: - logger.warning(f"Report API may not be able to look back this far. Some results may be incorrect.") + if lower_bound and ((datetime.date.today() - lower_bound).days > 80): + logger.warning( + "Report API may not be able to look back this far. Some results may be incorrect." + ) upper_bound, lower_bound = self._ensure_upper_lower_bound( lower_bound, upper_bound From d0668121c01a3567653e2c40ba93af69429b0c9e Mon Sep 17 00:00:00 2001 From: Adam Coddington Date: Mon, 16 Jan 2023 12:50:05 -0800 Subject: [PATCH 15/36] =?UTF-8?q?Bump=20version:=202.0.0=20=E2=86=92=202.0?= =?UTF-8?q?.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .bumpversion.cfg | 2 +- myfitnesspal/__init__.py | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 71f7da5..eb40c0d 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 2.0.0 +current_version = 2.0.1 commit = True tag = True tag_name = {new_version} diff --git a/myfitnesspal/__init__.py b/myfitnesspal/__init__.py index 0b90608..8888637 100644 --- a/myfitnesspal/__init__.py +++ b/myfitnesspal/__init__.py @@ -1,5 +1,5 @@ from myfitnesspal.client import Client # noqa -__version__ = "2.0.0" +__version__ = "2.0.1" VERSION = tuple(int(v) for v in __version__.split(".")) diff --git a/setup.py b/setup.py index b42acfa..d2fdad1 100644 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ setup( name="myfitnesspal", - version="2.0.0", + version="2.0.1", url="http://github.com/coddingtonbear/python-myfitnesspal/", description="Access health and fitness data stored in Myfitnesspal", author="Adam Coddington", From 09c5fee3667782026ad386eff34d1a1aac64c492 Mon Sep 17 00:00:00 2001 From: Sean Freeman <1815807+sean-freeman@users.noreply.github.com> Date: Wed, 29 Mar 2023 00:30:46 +0100 Subject: [PATCH 16/36] Isolate execution flow for friend shared diary (#153) * Friends private shared diary separate exec * Update friends private shared diary week export * Add exception for friends private shared diary locked with key * Append updated fields * Accept PR code review 1 Co-authored-by: Hannah * Accept PR code review 2 Co-authored-by: Hannah * Accept PR code review 3 Co-authored-by: Hannah * Accept PR code review 4 Co-authored-by: Hannah * Accept PR code review 5 Co-authored-by: Hannah * Accept PR code review 6 Co-authored-by: Hannah --------- Co-authored-by: Hannah --- ...nds_diary.rst => friends_diary_public.rst} | 2 +- docs/source/how_to/friends_diary_shared.rst | 54 ++++++++++++++++ myfitnesspal/client.py | 64 +++++++++++++------ 3 files changed, 98 insertions(+), 22 deletions(-) rename docs/source/how_to/{friends_diary.rst => friends_diary_public.rst} (82%) create mode 100644 docs/source/how_to/friends_diary_shared.rst diff --git a/docs/source/how_to/friends_diary.rst b/docs/source/how_to/friends_diary_public.rst similarity index 82% rename from docs/source/how_to/friends_diary.rst rename to docs/source/how_to/friends_diary_public.rst index 00d8fa0..699ea46 100644 --- a/docs/source/how_to/friends_diary.rst +++ b/docs/source/how_to/friends_diary_public.rst @@ -1,7 +1,7 @@ Accessing a Friend’s Diary ========================== -If a friend has their diary visibility set to public, you can grab their +If a friend has their diary visibility set to "Public", you can grab their diary entries: .. code:: python diff --git a/docs/source/how_to/friends_diary_shared.rst b/docs/source/how_to/friends_diary_shared.rst new file mode 100644 index 0000000..15d5abe --- /dev/null +++ b/docs/source/how_to/friends_diary_shared.rst @@ -0,0 +1,54 @@ +Accessing a Friend’s Diary (Shared) +=================================== + +If a friend has their diary visibility set to "Friends Only", you can grab their +diary entries. + +To access a single day’s information: + +.. code:: python + + import myfitnesspal + + client = myfitnesspal.Client() + + friend_day = client.get_date(2020, 8, 23, friend_username="username_of_my_friend") + + friend_day + # >> <03/02/13 {'sodium': 3326, 'carbohydrates': 369, 'calories': 2001, 'fat': 22, 'sugar': 103, 'protein': 110}> + + friend_day.totals + # >> {'calories': 2001, + # 'carbohydrates': 369, + # 'fat': 22, + # 'protein': 110, + # 'sodium': 3326, + # 'sugar': 103} + + friend_day.meals + # >> [, + # , + # , + # ] + + +To access a week’s information with a loop on a date range: + +.. code:: python + + import datetime + + start_date = datetime.date(2023, 2, 18) + end_date = datetime.date(2023, 2, 25) + + friend_shared_diary_data = [] + + for day in range(int((end_date - start_date).days)): + loop_year=((start_date + datetime.timedelta(day)).strftime("%Y")) + loop_month=((start_date + datetime.timedelta(day)).strftime("%m")) + loop_day=((start_date + datetime.timedelta(day)).strftime("%d")) + loop_pretty_date=str(start_date + datetime.timedelta(day)) + diary_day_data = client.get_date(loop_year, loop_month, loop_day, friend_username="username_of_my_friend") + friend_shared_diary_data.append({loop_pretty_date: diary_day_data.totals}) + + print(friend_shared_diary_data) diff --git a/myfitnesspal/client.py b/myfitnesspal/client.py index 8e426a0..8ffd136 100644 --- a/myfitnesspal/client.py +++ b/myfitnesspal/client.py @@ -147,6 +147,10 @@ def _get_user_metadata(self) -> types.UserMetadata: "system_data", "profiles", "step_sources", + "privacy_preferences", + "social_preferences", + "app_preferences", + "partner_only_fields", ] query_string = parse.urlencode( [ @@ -179,12 +183,16 @@ def _get_full_name(self, raw_name: str) -> str: return name return self.ABBREVIATIONS[name] - def _get_url_for_date(self, date: datetime.date, username: str) -> str: + def _get_url_for_date(self, date: datetime.date, username: str, friend_username=None) -> str: + if friend_username is not None: + name = friend_username + else: + name = username date_str = date.strftime("%Y-%m-%d") return ( - parse.urljoin(self.BASE_URL_SECURE, "food/diary/" + username) - + f"?date={date_str}" - ) + parse.urljoin(self.BASE_URL_SECURE, "food/diary/" + name) + + f"?date={date_str}" + ) def _get_url_for_measurements(self, page: int = 1, measurement_id: int = 1) -> str: return ( @@ -428,15 +436,17 @@ def _get_exercise(self, document): return exercises - def _get_exercises(self, date: datetime.date): + def _get_exercises(self, date: datetime.date, friend_username=None): + if friend_username is not None: + name = friend_username + else: + name = self.effective_username # get the exercise URL document = self._get_document_for_url( - self._get_url_for_exercise(date, self.effective_username) + self._get_url_for_exercise(date, name) ) - # gather the exercise goals exercise = self._get_exercise(document) - return exercise def _extract_value(self, element): @@ -475,9 +485,13 @@ def get_date(self, *args, **kwargs) -> Day: ) document = self._get_document_for_url( self._get_url_for_date( - date, kwargs.get("username", self.effective_username) + date, kwargs.get("username", self.effective_username), kwargs.get("friend_username") ) ) + if "diary is locked with a key" in document.text_content(): + raise Exception("Error: diary is locked with a key") + if kwargs.get("friend_username") is not None and "user maintains a private diary" in document.text_content(): + raise Exception(f"Error: Friend {kwargs.get('friend_username')}'s diary is private.") meals = self._get_meals(document) goals = self._get_goals(document) @@ -487,18 +501,26 @@ def get_date(self, *args, **kwargs) -> Day: # allow the day object to run the request if necessary. notes = lambda: self._get_notes(date) # noqa: E731 water = lambda: self._get_water(date) # noqa: E731 - exercises = lambda: self._get_exercises(date) # noqa: E731 - - day = Day( - date=date, - meals=meals, - goals=goals, - notes=notes, - water=water, - exercises=exercises, - complete=complete, - ) - + exercises = lambda: self._get_exercises(date, kwargs.get("friend_username")) # noqa: E731 + + if 'friend_username' not in kwargs: + day = Day( + date=date, + meals=meals, + goals=goals, + notes=notes, + water=water, + exercises=exercises, + complete=complete, + ) + else: + day = Day( + date=date, + meals=meals, + goals=goals, + exercises=exercises, + complete=complete, + ) return day def _ensure_upper_lower_bound(self, lower_bound, upper_bound): From 3378a7896f170b9211265c934708b33cb21403f1 Mon Sep 17 00:00:00 2001 From: Hannah Date: Sat, 27 May 2023 16:44:46 -0700 Subject: [PATCH 17/36] - measurements/edits now expects the (escaped) measurement name, not ID. update measurement loading accordingly - measurement data now arrives in a script tag with json data in it (`__NEXT_DATA__`). parse data from here instead of from table elements --- myfitnesspal/client.py | 58 ++++++++++++++++++++---------------------- 1 file changed, 27 insertions(+), 31 deletions(-) diff --git a/myfitnesspal/client.py b/myfitnesspal/client.py index b46c72b..2d2b387 100644 --- a/myfitnesspal/client.py +++ b/myfitnesspal/client.py @@ -186,10 +186,11 @@ def _get_url_for_date(self, date: datetime.date, username: str) -> str: + f"?date={date_str}" ) - def _get_url_for_measurements(self, page: int = 1, measurement_id: int = 1) -> str: + def _get_url_for_measurements(self, page: int = 1, measurement_name: str = "") -> str: return ( parse.urljoin(self.BASE_URL_SECURE, "measurements/edit") - + f"?page={page}&type={measurement_id}" + + "?" + + parse.urlencode({'page': page, "type": measurement_name}) ) def _get_request_for_url( @@ -543,7 +544,7 @@ def get_measurements( while True: # retrieve the HTML from MyFitnessPal document = self._get_document_for_url( - self._get_url_for_measurements(page, measurement_id) + self._get_url_for_measurements(page, measurement) ) # parse the HTML for measurement entries and add to dictionary @@ -631,42 +632,37 @@ def set_measurements( ) def _get_measurements(self, document): - # find the tr element for each measurement entry on the page - trs = document.xpath("//table[contains(@class,'check-in')]/tbody/tr") - - measurements = OrderedDict() + measurements = [] - # create a dictionary out of the date and value of each entry - for entry in trs: + for next_data in document.xpath("//script[@id='__NEXT_DATA__']"): + for q in json.loads(next_data.text)['props']['pageProps']['dehydratedState']['queries']: + if 'measurements' in q['queryKey']: + if 'items' in q['state']['data']: + measurements += q['state']['data']['items'] - # ensure there are measurement entries on the page - if len(entry) == 1: - return measurements - else: - measurements[entry[1].text] = entry[2].text - - temp_measurements = OrderedDict() + measurements_dict = OrderedDict() # converts the date to a datetime object and the value to a float - for date in measurements: - temp_measurements[ - datetime.datetime.strptime(date, "%m/%d/%Y").date() - ] = self._get_numeric(measurements[date]) - - measurements = temp_measurements + for entry in measurements: + date = datetime.datetime.strptime(entry['date'], "%Y-%m-%d").date() + if 'unit' in entry: + value = f"{entry['value']} {entry['unit']}" + else: + value = f"{entry['value']}" + measurements_dict[date] = self._get_numeric(value) - return measurements + return measurements_dict def _get_measurement_ids(self, document) -> Dict[str, int]: - - # find the option element for all of the measurement choices - options = document.xpath("//select[@id='type']/option") - ids = {} - - # create a dictionary out of the text and value of each choice - for option in options: - ids[option.text] = int(option.attrib.get("value")) + for next_data in document.xpath("//script[@id='__NEXT_DATA__']"): + for q in json.loads(next_data.text)['props']['pageProps']['dehydratedState']['queries']: + if 'measurementTypes' in q['queryKey']: + for m in q['state']['data']: + ids[m['description']] = m['id'] + if 'measurements' in q['queryKey']: + if q['queryKey'][1] not in ids: + ids[q['queryKey'][1]] = '' return ids From 452bfbd77cb4d63cfd7bd0968794c4fb4b173613 Mon Sep 17 00:00:00 2001 From: Hannah Date: Sat, 27 May 2023 17:28:30 -0700 Subject: [PATCH 18/36] unit test and formatting fixes --- myfitnesspal/client.py | 34 +- tests/html/measurements.html | 5097 ++++++++++++++++++++++++++++------ tests/test_client.py | 34 +- 3 files changed, 4208 insertions(+), 957 deletions(-) diff --git a/myfitnesspal/client.py b/myfitnesspal/client.py index 2d2b387..7983b73 100644 --- a/myfitnesspal/client.py +++ b/myfitnesspal/client.py @@ -186,11 +186,13 @@ def _get_url_for_date(self, date: datetime.date, username: str) -> str: + f"?date={date_str}" ) - def _get_url_for_measurements(self, page: int = 1, measurement_name: str = "") -> str: + def _get_url_for_measurements( + self, page: int = 1, measurement_name: str = "" + ) -> str: return ( parse.urljoin(self.BASE_URL_SECURE, "measurements/edit") + "?" - + parse.urlencode({'page': page, "type": measurement_name}) + + parse.urlencode({"page": page, "type": measurement_name}) ) def _get_request_for_url( @@ -635,17 +637,18 @@ def _get_measurements(self, document): measurements = [] for next_data in document.xpath("//script[@id='__NEXT_DATA__']"): - for q in json.loads(next_data.text)['props']['pageProps']['dehydratedState']['queries']: - if 'measurements' in q['queryKey']: - if 'items' in q['state']['data']: - measurements += q['state']['data']['items'] + next_data_json = json.loads(next_data.text) + for q in next_data_json["props"]["pageProps"]["dehydratedState"]["queries"]: + if "measurements" in q["queryKey"]: + if "items" in q["state"]["data"]: + measurements += q["state"]["data"]["items"] measurements_dict = OrderedDict() # converts the date to a datetime object and the value to a float for entry in measurements: - date = datetime.datetime.strptime(entry['date'], "%Y-%m-%d").date() - if 'unit' in entry: + date = datetime.datetime.strptime(entry["date"], "%Y-%m-%d").date() + if "unit" in entry: value = f"{entry['value']} {entry['unit']}" else: value = f"{entry['value']}" @@ -656,13 +659,14 @@ def _get_measurements(self, document): def _get_measurement_ids(self, document) -> Dict[str, int]: ids = {} for next_data in document.xpath("//script[@id='__NEXT_DATA__']"): - for q in json.loads(next_data.text)['props']['pageProps']['dehydratedState']['queries']: - if 'measurementTypes' in q['queryKey']: - for m in q['state']['data']: - ids[m['description']] = m['id'] - if 'measurements' in q['queryKey']: - if q['queryKey'][1] not in ids: - ids[q['queryKey'][1]] = '' + next_data_json = json.loads(next_data.text) + for q in next_data_json["props"]["pageProps"]["dehydratedState"]["queries"]: + if "measurementTypes" in q["queryKey"]: + for m in q["state"]["data"]: + ids[m["description"]] = m["id"] + if "measurements" in q["queryKey"]: + if q["queryKey"][1] not in ids: + ids[q["queryKey"][1]] = "" return ids diff --git a/tests/html/measurements.html b/tests/html/measurements.html index e9588e1..7e13458 100644 --- a/tests/html/measurements.html +++ b/tests/html/measurements.html @@ -1,979 +1,4236 @@ - - + + - - - - - - - Free Calorie Counter, Diet & Exercise Journal | MyFitnessPal.com - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ +
+
+ +
+
+
+
+
- - - - -

Copyright 2005-2015 MyFitnessPal, Inc.

- - SheKnows Health & Beauty - -
-
-
-
- - - - - - - - - - - - - - - - - - +
+ - + \ No newline at end of file diff --git a/tests/test_client.py b/tests/test_client.py index 83630d7..4de1836 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -15,8 +15,8 @@ class TestClient(MFPTestCase): def setUp(self): self.arbitrary_username = "alpha" self.arbitrary_password = "beta" - self.arbitrary_date1 = datetime.date(2015, 4, 20) - self.arbitrary_date2 = datetime.date(2015, 4, 28) + self.arbitrary_date1 = datetime.date(2022, 1, 10) + self.arbitrary_date2 = datetime.date(2022, 1, 9) with patch.multiple( "myfitnesspal.Client", _get_auth_data=DEFAULT, _get_user_metadata=DEFAULT @@ -32,13 +32,10 @@ def test_get_measurement_ids(self): actual_ids = self.client._get_measurement_ids(document) expected_ids = { - "Weight": 1, - "Body Fat": 91955886, - "Butt": 92738807, - "Bicep": 92738811, - "Quad": 92738815, - "Mid Section": 92738819, - "Shoulders": 92738861, + 'Hips': '278869596622717', + 'Neck': '278869604978685', + 'Waist': '278319840808829', + 'Weight': '' } self.assertEqual( @@ -58,24 +55,17 @@ def test_get_meals(self): def test_get_measurements(self): with patch.object(self.client, "_get_document_for_url") as get_doc: get_doc.return_value = self.get_html_document("measurements.html") + get_doc.called actual_measurements = self.client.get_measurements( - "Body Fat", + "Weight", self.arbitrary_date1, self.arbitrary_date2, ) - expected_measurements = OrderedDict( - [ - (datetime.date(2015, 4, 28), 19.2), - (datetime.date(2015, 4, 27), 19.2), - (datetime.date(2015, 4, 26), 19.0), - (datetime.date(2015, 4, 25), 18.7), - (datetime.date(2015, 4, 23), 18.7), - (datetime.date(2015, 4, 22), 18.4), - (datetime.date(2015, 4, 21), 18.9), - (datetime.date(2015, 4, 20), 19.1), - ] - ) + expected_measurements = OrderedDict([ + (datetime.date(2022, 1, 10), 155.0), + (datetime.date(2022, 1, 9), 156.0) + ]) self.assertEqual( expected_measurements, From a66cbe525f4bd78f85e962027f96a12bd819684d Mon Sep 17 00:00:00 2001 From: Hannah Date: Sat, 27 May 2023 17:31:47 -0700 Subject: [PATCH 19/36] more black and flake8 lint fixes --- myfitnesspal/client.py | 5 +---- tests/test_client.py | 15 +++++++-------- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/myfitnesspal/client.py b/myfitnesspal/client.py index 7983b73..e8ae5f1 100644 --- a/myfitnesspal/client.py +++ b/myfitnesspal/client.py @@ -533,10 +533,7 @@ def get_measurements( # gather the IDs for all measurement types measurement_ids = self._get_measurement_ids(document) - # select the measurement ID based on the input - if measurement in measurement_ids.keys(): - measurement_id = measurement_ids[measurement] - else: + if measurement not in measurement_ids.keys(): raise ValueError(f"Measurement '{measurement}' does not exist.") page = 1 diff --git a/tests/test_client.py b/tests/test_client.py index 4de1836..d83ae7c 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -32,10 +32,10 @@ def test_get_measurement_ids(self): actual_ids = self.client._get_measurement_ids(document) expected_ids = { - 'Hips': '278869596622717', - 'Neck': '278869604978685', - 'Waist': '278319840808829', - 'Weight': '' + "Hips": "278869596622717", + "Neck": "278869604978685", + "Waist": "278319840808829", + "Weight": "", } self.assertEqual( @@ -62,10 +62,9 @@ def test_get_measurements(self): self.arbitrary_date2, ) - expected_measurements = OrderedDict([ - (datetime.date(2022, 1, 10), 155.0), - (datetime.date(2022, 1, 9), 156.0) - ]) + expected_measurements = OrderedDict( + [(datetime.date(2022, 1, 10), 155.0), (datetime.date(2022, 1, 9), 156.0)] + ) self.assertEqual( expected_measurements, From db822aa2743836ef65f4a100e9b6f1763f786886 Mon Sep 17 00:00:00 2001 From: Hannah Date: Sat, 3 Jun 2023 10:23:09 -0700 Subject: [PATCH 20/36] Remove `utf8` field from the search request as it seems to no longer be required or expected (#159) --- myfitnesspal/client.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/myfitnesspal/client.py b/myfitnesspal/client.py index 8ffd136..7534de1 100644 --- a/myfitnesspal/client.py +++ b/myfitnesspal/client.py @@ -799,12 +799,10 @@ def get_food_search_results(self, query: str) -> List[FoodItem]: authenticity_token = document.xpath( "(//input[@name='authenticity_token']/@value)[1]" )[0] - utf8_field = document.xpath("(//input[@name='utf8']/@value)[1]")[0] result = self.session.post( search_url, data={ - "utf8": utf8_field, "authenticity_token": authenticity_token, "search": query, "date": datetime.datetime.today().strftime("%Y-%m-%d"), From 9baf832da955b3994c7cd2c1e5c1165711370fe4 Mon Sep 17 00:00:00 2001 From: Hannah Date: Sat, 3 Jun 2023 10:23:31 -0700 Subject: [PATCH 21/36] Handle non-existed nutritional info in search results (#160) * if there's no `search-nutritional-info` element, don't try to parse calories and brand information * flake8 linting fix * black linting fixes * lint fix * lint fix --- myfitnesspal/client.py | 47 ++++++++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/myfitnesspal/client.py b/myfitnesspal/client.py index 7534de1..5942874 100644 --- a/myfitnesspal/client.py +++ b/myfitnesspal/client.py @@ -183,16 +183,18 @@ def _get_full_name(self, raw_name: str) -> str: return name return self.ABBREVIATIONS[name] - def _get_url_for_date(self, date: datetime.date, username: str, friend_username=None) -> str: + def _get_url_for_date( + self, date: datetime.date, username: str, friend_username=None + ) -> str: if friend_username is not None: name = friend_username else: name = username date_str = date.strftime("%Y-%m-%d") return ( - parse.urljoin(self.BASE_URL_SECURE, "food/diary/" + name) - + f"?date={date_str}" - ) + parse.urljoin(self.BASE_URL_SECURE, "food/diary/" + name) + + f"?date={date_str}" + ) def _get_url_for_measurements(self, page: int = 1, measurement_id: int = 1) -> str: return ( @@ -442,9 +444,7 @@ def _get_exercises(self, date: datetime.date, friend_username=None): else: name = self.effective_username # get the exercise URL - document = self._get_document_for_url( - self._get_url_for_exercise(date, name) - ) + document = self._get_document_for_url(self._get_url_for_exercise(date, name)) # gather the exercise goals exercise = self._get_exercise(document) return exercise @@ -483,15 +483,23 @@ def get_date(self, *args, **kwargs) -> Day: "or three integers representing year, month, and day " "respectively." ) + friend_username = kwargs.get("friend_username") document = self._get_document_for_url( self._get_url_for_date( - date, kwargs.get("username", self.effective_username), kwargs.get("friend_username") + date, + kwargs.get("username", self.effective_username), + friend_username, ) ) if "diary is locked with a key" in document.text_content(): raise Exception("Error: diary is locked with a key") - if kwargs.get("friend_username") is not None and "user maintains a private diary" in document.text_content(): - raise Exception(f"Error: Friend {kwargs.get('friend_username')}'s diary is private.") + if ( + friend_username is not None + and "user maintains a private diary" in document.text_content() + ): + raise Exception( + f"Error: Friend {kwargs.get('friend_username')}'s diary is private." + ) meals = self._get_meals(document) goals = self._get_goals(document) @@ -501,9 +509,9 @@ def get_date(self, *args, **kwargs) -> Day: # allow the day object to run the request if necessary. notes = lambda: self._get_notes(date) # noqa: E731 water = lambda: self._get_water(date) # noqa: E731 - exercises = lambda: self._get_exercises(date, kwargs.get("friend_username")) # noqa: E731 + exercises = lambda: self._get_exercises(date, friend_username) # noqa: E731 - if 'friend_username' not in kwargs: + if "friend_username" not in kwargs: day = Day( date=date, meals=meals, @@ -833,15 +841,14 @@ def _get_food_search_results(self, document) -> List[FoodItem]: if item_div.xpath(".//div[@class='verified verified-list-icon']") else False ) - nutr_info = ( - item_div.xpath(".//p[@class='search-nutritional-info']")[0] - .text.strip() - .split(",") - ) + calories = None brand = "" - if len(nutr_info) >= 3: - brand = " ".join(nutr_info[0:-2]).strip() - calories = float(nutr_info[-1].replace("calories", "").strip()) + nutr_info_xpath = item_div.xpath(".//p[@class='search-nutritional-info']") + if nutr_info_xpath: + nutr_info = nutr_info_xpath[0].text.strip().split(",") + if len(nutr_info) >= 3: + brand = " ".join(nutr_info[0:-2]).strip() + calories = float(nutr_info[-1].replace("calories", "").strip()) items.append( FoodItem(mfp_id, mfp_name, brand, verif, calories, client=self) ) From 50b79644aeca46052ef1db9da47f1733eb9fb834 Mon Sep 17 00:00:00 2001 From: Adam Coddington Date: Sun, 3 Dec 2023 15:52:36 -0800 Subject: [PATCH 22/36] [168] Adding some changes that make it possible to get more insight into running requests. --- .pre-commit-config.yaml | 4 ++- myfitnesspal/client.py | 53 ++++++++++++++++++++++++++++++++++++++-- myfitnesspal/cmdline.py | 2 ++ myfitnesspal/commands.py | 4 +-- 4 files changed, 58 insertions(+), 5 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0487f39..300ceb2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -21,7 +21,7 @@ repos: - id: pyupgrade args: ["--py37-plus"] - repo: https://github.com/pre-commit/mirrors-mypy - rev: v0.782 + rev: v1.7.1 hooks: - id: mypy args: @@ -29,3 +29,5 @@ repos: - --show-error-codes - --show-error-context - --ignore-missing-imports + additional_dependencies: + - types-python-dateutil diff --git a/myfitnesspal/client.py b/myfitnesspal/client.py index 117ef1d..103a9ab 100644 --- a/myfitnesspal/client.py +++ b/myfitnesspal/client.py @@ -4,8 +4,10 @@ import json import logging import re +import uuid from collections import OrderedDict from http.cookiejar import CookieJar +from pathlib import Path from typing import Any, Dict, List, Optional, Union, cast, overload import browser_cookie3 @@ -63,7 +65,16 @@ def __init__( self, cookiejar: CookieJar = None, unit_aware: bool = False, + log_requests_to: Path | None = None, ): + self._client_instance_id = uuid.uuid4() + self._request_counter = 0 + if log_requests_to: + self._log_requests_to = log_requests_to / Path( + str(self._client_instance_id) + ) + self._log_requests_to.mkdir(parents=True, exist_ok=True) + self.unit_aware = unit_aware self.session = requests.Session() @@ -212,6 +223,14 @@ def _get_request_for_url( headers: Optional[Dict[str, str]] = None, **kwargs, ) -> requests.Response: + request_id = uuid.uuid4() + self._request_counter += 1 + logger.debug( + "Sending request %s (#%s for client) to url %s", + self._request_counter, + request_id, + url, + ) if headers is None: headers = {} @@ -225,7 +244,38 @@ def _get_request_for_url( if self.user_id: headers["mfp-user-id"] = self.user_id - return self.session.get(url, headers=headers, **kwargs) + result = self.session.get(url, headers=headers, **kwargs) + if self._log_requests_to: + with open( + self._log_requests_to + / Path( + str(self._request_counter).zfill(3) + "__" + str(request_id) + ).with_suffix(".json"), + "w", + encoding="utf-8", + ) as outf: + outf.write( + json.dumps( + { + "request": { + "url": url, + "send_token": send_token, + "user_id": self.user_id if send_token else None, + "headers": headers, + "kwargs": kwargs, + }, + "response": { + "headers": dict(result.headers), + "status_code": result.status_code, + "content": result.content.decode("utf-8"), + }, + }, + indent=4, + sort_keys=True, + ) + ) + + return result def _get_content_for_url(self, *args, **kwargs) -> str: return self._get_request_for_url(*args, **kwargs).content.decode("utf8") @@ -402,7 +452,6 @@ def _get_exercise(self, document): # If name is empty string: if columns[0].find("a") is None or not name: - # check for `td > div > a` if columns[0].find("div").find("a") is None: # then check for just `td > div` diff --git a/myfitnesspal/cmdline.py b/myfitnesspal/cmdline.py index 4998745..95440ea 100644 --- a/myfitnesspal/cmdline.py +++ b/myfitnesspal/cmdline.py @@ -1,6 +1,7 @@ import argparse import logging import sys +from pathlib import Path from rich.console import Console from rich.logging import RichHandler @@ -20,6 +21,7 @@ def main(args=None): parser.add_argument("command", type=str, nargs=1, choices=COMMANDS.keys()) parser.add_argument("--loglevel", type=str, default="INFO") parser.add_argument("--traceback-locals", action="store_true") + parser.add_argument("--log-requests-to", type=Path, default=None) parser.add_argument("--debugger", action="store_true") args, extra = parser.parse_known_args() diff --git a/myfitnesspal/commands.py b/myfitnesspal/commands.py index 158300c..58bd616 100644 --- a/myfitnesspal/commands.py +++ b/myfitnesspal/commands.py @@ -53,7 +53,7 @@ def decorator(fn): @command( "Display MyFitnessPal data for a given date.", ) -def day(args, *extra, **kwargs): +def day(super_args, *extra, **kwargs): parser = argparse.ArgumentParser() parser.add_argument( "date", @@ -64,7 +64,7 @@ def day(args, *extra, **kwargs): ) args = parser.parse_args(extra) - client = Client() + client = Client(log_requests_to=super_args.log_requests_to) day = client.get_date(args.date) date_str = args.date.strftime("%Y-%m-%d") From cd136748037f3ed423f976e8b0f40901f1cf8704 Mon Sep 17 00:00:00 2001 From: Adam Coddington Date: Wed, 3 Jan 2024 18:56:27 -0800 Subject: [PATCH 23/36] Fixing a bug in which not logging requests would cause an error due to an oversight. --- myfitnesspal/client.py | 1 + 1 file changed, 1 insertion(+) diff --git a/myfitnesspal/client.py b/myfitnesspal/client.py index 103a9ab..c616859 100644 --- a/myfitnesspal/client.py +++ b/myfitnesspal/client.py @@ -69,6 +69,7 @@ def __init__( ): self._client_instance_id = uuid.uuid4() self._request_counter = 0 + self._log_requests_to: Path | None = None if log_requests_to: self._log_requests_to = log_requests_to / Path( str(self._client_instance_id) From 1bbda814b88a0f6d99b66706e9ddb4074f6d8ea3 Mon Sep 17 00:00:00 2001 From: Adam Coddington Date: Wed, 3 Jan 2024 18:57:47 -0800 Subject: [PATCH 24/36] Use cloudscraper for performing requests; thanks @qmirioni! --- myfitnesspal/client.py | 3 ++- requirements.txt | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/myfitnesspal/client.py b/myfitnesspal/client.py index c616859..54bae8f 100644 --- a/myfitnesspal/client.py +++ b/myfitnesspal/client.py @@ -11,6 +11,7 @@ from typing import Any, Dict, List, Optional, Union, cast, overload import browser_cookie3 +import cloudscraper import lxml.html import requests from measurement.base import MeasureBase @@ -78,7 +79,7 @@ def __init__( self.unit_aware = unit_aware - self.session = requests.Session() + self.session = cloudscraper.create_scraper(sess=requests.Session()) self.session.headers.update( { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.104 Safari/537.36" diff --git a/requirements.txt b/requirements.txt index 80ce0b4..da27f4e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,3 +5,4 @@ python-dateutil>=2.4,<3 requests>=2.17.0,<3 rich>=12,<13 browser_cookie3>=0.16.1,<1 +cloudscraper>=1.2.71,<2 From cecd11a8df8150182c34b13b18084203004f80c1 Mon Sep 17 00:00:00 2001 From: Adam Coddington Date: Wed, 3 Jan 2024 19:07:59 -0800 Subject: [PATCH 25/36] Mypy corrections. --- .pre-commit-config.yaml | 1 + myfitnesspal/client.py | 6 +++--- myfitnesspal/day.py | 8 ++++---- myfitnesspal/note.py | 4 ++-- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 300ceb2..e0203ce 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -30,4 +30,5 @@ repos: - --show-error-context - --ignore-missing-imports additional_dependencies: + - types-requests - types-python-dateutil diff --git a/myfitnesspal/client.py b/myfitnesspal/client.py index 54bae8f..8b8b348 100644 --- a/myfitnesspal/client.py +++ b/myfitnesspal/client.py @@ -9,6 +9,7 @@ from http.cookiejar import CookieJar from pathlib import Path from typing import Any, Dict, List, Optional, Union, cast, overload +from urllib import parse import browser_cookie3 import cloudscraper @@ -16,7 +17,6 @@ import requests from measurement.base import MeasureBase from measurement.measures import Energy, Mass, Volume -from six.moves.urllib import parse from . import types from .base import MFPBase @@ -64,7 +64,7 @@ class Client(MFPBase): def __init__( self, - cookiejar: CookieJar = None, + cookiejar: Optional[CookieJar] = None, unit_aware: bool = False, log_requests_to: Path | None = None, ): @@ -654,7 +654,7 @@ def get_measurements( def set_measurements( self, measurement="Weight", - value: float = None, + value: Optional[float] = None, date: Optional[datetime.date] = None, ) -> None: """Sets measurement for today's date.""" diff --git a/myfitnesspal/day.py b/myfitnesspal/day.py index 7e18f39..681603b 100644 --- a/myfitnesspal/day.py +++ b/myfitnesspal/day.py @@ -16,10 +16,10 @@ def __init__( self, date: datetime.date, meals: Optional[List[Meal]] = None, - goals: Dict[str, float] = None, - notes: Callable[[], str] = None, - water: Callable[[], float] = None, - exercises: Callable[[], List[Exercise]] = None, + goals: Optional[Dict[str, float]] = None, + notes: Optional[Callable[[], str]] = None, + water: Optional[Callable[[], float]] = None, + exercises: Optional[Callable[[], List[Exercise]]] = None, complete: bool = False, ): self._date = date diff --git a/myfitnesspal/note.py b/myfitnesspal/note.py index 493fa8e..5ccdd41 100644 --- a/myfitnesspal/note.py +++ b/myfitnesspal/note.py @@ -2,7 +2,7 @@ import datetime from html import unescape -from typing import Dict, Optional +from typing import Optional from .types import NoteDataDict @@ -10,7 +10,7 @@ class Note(str): """Stores information about a note""" - _note_data: Dict[str, str] + _note_data: NoteDataDict def __new__(cls, note_data: NoteDataDict) -> Note: # I'm not sure I understand why this is double-encoded, but it is? From 3cf3be1f5f932e69cb0d5854f52c4a50500c0039 Mon Sep 17 00:00:00 2001 From: Adam Coddington Date: Thu, 4 Jan 2024 06:56:11 -0800 Subject: [PATCH 26/36] Mypy corrections. --- myfitnesspal/fooditem.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/myfitnesspal/fooditem.py b/myfitnesspal/fooditem.py index 3de175b..6a65730 100644 --- a/myfitnesspal/fooditem.py +++ b/myfitnesspal/fooditem.py @@ -21,7 +21,7 @@ def __init__( name: str, brand: Optional[str], verified: bool, - calories: float, + calories: Optional[float], details: Optional[FoodItemNutritionDict] = None, confirmations: Optional[int] = None, serving_sizes: Optional[List[types.ServingSizeDict]] = None, @@ -92,7 +92,7 @@ def serving(self) -> Optional[str]: return None @property - def calories(self) -> float: + def calories(self) -> Optional[float]: """Calories""" return self._calories From 66185f8edad69878eb78d92e6fa4f9150c8950c8 Mon Sep 17 00:00:00 2001 From: Adam Coddington Date: Thu, 4 Jan 2024 06:56:28 -0800 Subject: [PATCH 27/36] =?UTF-8?q?Bump=20version:=202.0.1=20=E2=86=92=202.1?= =?UTF-8?q?.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .bumpversion.cfg | 2 +- myfitnesspal/__init__.py | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index eb40c0d..0473717 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 2.0.1 +current_version = 2.1.0 commit = True tag = True tag_name = {new_version} diff --git a/myfitnesspal/__init__.py b/myfitnesspal/__init__.py index 8888637..59ff7f7 100644 --- a/myfitnesspal/__init__.py +++ b/myfitnesspal/__init__.py @@ -1,5 +1,5 @@ from myfitnesspal.client import Client # noqa -__version__ = "2.0.1" +__version__ = "2.1.0" VERSION = tuple(int(v) for v in __version__.split(".")) diff --git a/setup.py b/setup.py index d2fdad1..fd288d2 100644 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ setup( name="myfitnesspal", - version="2.0.1", + version="2.1.0", url="http://github.com/coddingtonbear/python-myfitnesspal/", description="Access health and fitness data stored in Myfitnesspal", author="Adam Coddington", From 58042948a8a612147b1d2cd460b939c7d2a8ff5a Mon Sep 17 00:00:00 2001 From: Hannah Date: Tue, 20 Aug 2024 22:32:35 -0400 Subject: [PATCH 28/36] update URL and path to result data --- myfitnesspal/client.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/myfitnesspal/client.py b/myfitnesspal/client.py index 8b8b348..a3c517a 100644 --- a/myfitnesspal/client.py +++ b/myfitnesspal/client.py @@ -818,7 +818,7 @@ def _get_url_for_report( return ( parse.urljoin( self.BASE_URL_SECURE, - "reports/results/" + report_category.lower() + "/" + report_name, + "api/services/reports/results/" + report_category.lower() + "/" + report_name, ) + f"/{str(delta.days)}.json" ) @@ -826,7 +826,7 @@ def _get_url_for_report( def _get_report_data(self, json_data: dict) -> Dict[datetime.date, float]: report_data: Dict[datetime.date, float] = {} - data = json_data.get("data") + data = json_data.get("outcome").get("results") if not data: return report_data From 2ab167af56db67f87c7159b3dd45f981eff76e04 Mon Sep 17 00:00:00 2001 From: Adam Coddington Date: Sun, 9 Mar 2025 12:32:20 -0500 Subject: [PATCH 29/36] Updating python spec versions to be a little morre modern. --- .github/workflows/lint.yml | 2 +- .github/workflows/test.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index c675514..ee1a3d4 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -12,7 +12,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v1 with: - python-version: 3.8 + python-version: 3.13 - name: Install Python dependencies run: pip install black==22.6.0 flake8==5.0.4 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index dfdff2f..4e62200 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -8,7 +8,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.7, 3.8] + python-version: [3.9, 3.10, 3.11, 3.12, 3.13] steps: - uses: actions/checkout@v2 From c6c0003a38b817987c44e3a8fc71d278530e617e Mon Sep 17 00:00:00 2001 From: Adam Coddington Date: Sun, 9 Mar 2025 12:55:35 -0500 Subject: [PATCH 30/36] Modernization. --- .github/workflows/lint.yml | 2 +- .pre-commit-config.yaml | 12 ++--- myfitnesspal/client.py | 103 +++++++++++++++++++------------------ setup.cfg | 17 ++++-- 4 files changed, 71 insertions(+), 63 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index ee1a3d4..b78de3d 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -18,7 +18,7 @@ jobs: run: pip install black==22.6.0 flake8==5.0.4 - name: Run linters - uses: samuelmeuli/lint-action@v1.5.3 + uses: wearerequired/lint-action with: github_token: ${{ secrets.github_token }} # Enable linters diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e0203ce..0c7a3a5 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,24 +4,24 @@ repos: # Sort imports prior to black reformatting, to # ensure black always takes prescedence - repo: https://github.com/timothycrosley/isort - rev: 4.3.21 + rev: 6.0.1 hooks: - id: isort - repo: https://github.com/ambv/black - rev: 22.6.0 + rev: 25.1.0 hooks: - id: black - repo: https://github.com/pycqa/flake8 - rev: 5.0.4 + rev: 7.1.2 hooks: - id: flake8 - repo: https://github.com/asottile/pyupgrade - rev: v1.25.1 + rev: v3.19.1 hooks: - id: pyupgrade - args: ["--py37-plus"] + args: ["--py39-plus"] - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.7.1 + rev: v1.15.0 hooks: - id: mypy args: diff --git a/myfitnesspal/client.py b/myfitnesspal/client.py index a3c517a..235910c 100644 --- a/myfitnesspal/client.py +++ b/myfitnesspal/client.py @@ -8,7 +8,7 @@ from collections import OrderedDict from http.cookiejar import CookieJar from pathlib import Path -from typing import Any, Dict, List, Optional, Union, cast, overload +from typing import Any, cast, overload from urllib import parse import browser_cookie3 @@ -64,7 +64,7 @@ class Client(MFPBase): def __init__( self, - cookiejar: Optional[CookieJar] = None, + cookiejar: CookieJar | None = None, unit_aware: bool = False, log_requests_to: Path | None = None, ): @@ -97,7 +97,7 @@ def __init__( self._user_metadata = self._get_user_metadata() @property - def user_id(self) -> Optional[types.MyfitnesspalUserId]: + def user_id(self) -> types.MyfitnesspalUserId | None: """The user_id of the logged-in account.""" if self._auth_data is None: return None @@ -110,7 +110,7 @@ def user_metadata(self) -> types.UserMetadata: return self._user_metadata @property - def access_token(self) -> Optional[str]: + def access_token(self) -> str | None: """The access token for the logged-in account.""" if self._auth_data is None: return None @@ -222,7 +222,7 @@ def _get_request_for_url( self, url: str, send_token: bool = False, - headers: Optional[Dict[str, str]] = None, + headers: dict[str, str] | None = None, **kwargs, ) -> requests.Response: request_id = uuid.uuid4() @@ -292,7 +292,7 @@ def _get_json_for_url(self, url): return json.loads(content) - def _get_measurement(self, name: str, value: Optional[float]) -> MeasureBase: + def _get_measurement(self, name: str, value: float | None) -> MeasureBase: if not self.unit_aware: return value measure, kwarg = self.DEFAULT_MEASURE_AND_UNIT[name] @@ -357,7 +357,7 @@ def _get_completion(self, document) -> bool: return False # Who knows, probably not my diary. - def _get_meals(self, document) -> List[Meal]: + def _get_meals(self, document) -> list[Meal]: meals = [] fields = None meal_headers = document.xpath("//tr[@class='meal_header']") @@ -514,12 +514,10 @@ def _extract_value(self, element): return value @overload - def get_date(self, year: int, month: int, day: int) -> Day: - ... + def get_date(self, year: int, month: int, day: int) -> Day: ... @overload - def get_date(self, date: datetime.date) -> Day: - ... + def get_date(self, date: datetime.date) -> Day: ... def get_date(self, *args, **kwargs) -> Day: """Returns your meal diary for a particular date""" @@ -600,9 +598,9 @@ def _ensure_upper_lower_bound(self, lower_bound, upper_bound): def get_measurements( self, measurement="Weight", - lower_bound: Optional[datetime.date] = None, - upper_bound: Optional[datetime.date] = None, - ) -> Dict[datetime.date, float]: + lower_bound: datetime.date | None = None, + upper_bound: datetime.date | None = None, + ) -> dict[datetime.date, float]: """Returns measurements of a given name between two dates.""" upper_bound, lower_bound = self._ensure_upper_lower_bound( lower_bound, upper_bound @@ -654,8 +652,8 @@ def get_measurements( def set_measurements( self, measurement="Weight", - value: Optional[float] = None, - date: Optional[datetime.date] = None, + value: float | None = None, + date: datetime.date | None = None, ) -> None: """Sets measurement for today's date.""" if value is None: @@ -734,7 +732,7 @@ def _get_measurements(self, document): return measurements_dict - def _get_measurement_ids(self, document) -> Dict[str, int]: + def _get_measurement_ids(self, document) -> dict[str, int]: ids = {} for next_data in document.xpath("//script[@id='__NEXT_DATA__']"): next_data_json = json.loads(next_data.text) @@ -758,7 +756,7 @@ def _get_notes(self, date: datetime.date) -> Note: ) return Note(result.json()["item"]) - def _get_water(self, date: datetime.date) -> Union[float, Volume]: + def _get_water(self, date: datetime.date) -> float | Volume: result = self._get_request_for_url( parse.urljoin( self.BASE_URL_SECURE, @@ -776,9 +774,9 @@ def get_report( self, report_name: str = "Net Calories", report_category: str = "Nutrition", - lower_bound: Optional[datetime.date] = None, - upper_bound: Optional[datetime.date] = None, - ) -> Dict[datetime.date, float]: + lower_bound: datetime.date | None = None, + upper_bound: datetime.date | None = None, + ) -> dict[datetime.date, float]: """ Returns report data of a given name and category between two dates. """ @@ -818,15 +816,18 @@ def _get_url_for_report( return ( parse.urljoin( self.BASE_URL_SECURE, - "api/services/reports/results/" + report_category.lower() + "/" + report_name, + "api/services/reports/results/" + + report_category.lower() + + "/" + + report_name, ) + f"/{str(delta.days)}.json" ) - def _get_report_data(self, json_data: dict) -> Dict[datetime.date, float]: - report_data: Dict[datetime.date, float] = {} + def _get_report_data(self, json_data: dict) -> dict[datetime.date, float]: + report_data: dict[datetime.date, float] = {} - data = json_data.get("outcome").get("results") + data = json_data.get("outcome", {}).get("results") if not data: return report_data @@ -848,7 +849,7 @@ def _get_report_data(self, json_data: dict) -> Dict[datetime.date, float]: def __str__(self) -> str: return f"MyFitnessPal Client for {self.effective_username}" - def get_food_search_results(self, query: str) -> List[FoodItem]: + def get_food_search_results(self, query: str) -> list[FoodItem]: """Search for foods matching a specified query.""" search_url = parse.urljoin(self.BASE_URL_SECURE, self.SEARCH_PATH) document = self._get_document_for_url(search_url) @@ -875,7 +876,7 @@ def get_food_search_results(self, query: str) -> List[FoodItem]: return self._get_food_search_results(document) - def _get_food_search_results(self, document) -> List[FoodItem]: + def _get_food_search_results(self, document) -> list[FoodItem]: item_divs = document.xpath("//li[@class='matched-food']") items = [] @@ -970,19 +971,19 @@ def set_new_food( fat: float, carbs: float, protein: float, - sodium: Optional[float] = None, - potassium: Optional[float] = None, - saturated_fat: Optional[float] = None, - polyunsaturated_fat: Optional[float] = None, - fiber: Optional[float] = None, - monounsaturated_fat: Optional[float] = None, - sugar: Optional[float] = None, - trans_fat: Optional[float] = None, - cholesterol: Optional[float] = None, - vitamin_a: Optional[float] = None, - calcium: Optional[float] = None, - vitamin_c: Optional[float] = None, - iron: Optional[float] = None, + sodium: float | None = None, + potassium: float | None = None, + saturated_fat: float | None = None, + polyunsaturated_fat: float | None = None, + fiber: float | None = None, + monounsaturated_fat: float | None = None, + sugar: float | None = None, + trans_fat: float | None = None, + cholesterol: float | None = None, + vitamin_a: float | None = None, + calcium: float | None = None, + vitamin_c: float | None = None, + iron: float | None = None, serving_size: str = "1 Serving", servingspercontainer: float = 1.0, sharepublic: bool = False, @@ -1104,12 +1105,12 @@ def set_new_goal( self, energy: float, energy_unit: str = "calories", - carbohydrates: Optional[float] = None, - protein: Optional[float] = None, - fat: Optional[float] = None, - percent_carbohydrates: Optional[float] = None, - percent_protein: Optional[float] = None, - percent_fat: Optional[float] = None, + carbohydrates: float | None = None, + protein: float | None = None, + fat: float | None = None, + percent_carbohydrates: float | None = None, + percent_protein: float | None = None, + percent_fat: float | None = None, ) -> None: """Updates your nutrition goals. @@ -1259,7 +1260,7 @@ def set_new_goal( "status code: {status}".format(status=result.status_code) ) - def get_recipes(self) -> Dict[int, str]: + def get_recipes(self) -> dict[int, str]: """Returns a dictionary with all saved recipes. Recipe ID will be used as dictionary key, recipe title as dictionary value. @@ -1313,7 +1314,7 @@ def get_recipe(self, recipeid: int) -> types.Recipe: recipe_url = parse.urljoin(self.BASE_URL_SECURE, recipe_path) document = self._get_document_for_url(recipe_url) - recipe_dict: Dict[str, Any] = { + recipe_dict: dict[str, Any] = { "@context": "https://schema.org", "@type": "Recipe", "author": self.effective_username, @@ -1372,7 +1373,7 @@ def get_recipe(self, recipeid: int) -> types.Recipe: recipe_dict["tags"] = ["MyFitnessPal"] return cast(types.Recipe, recipe_dict) - def get_meals(self) -> Dict[int, str]: + def get_meals(self) -> dict[int, str]: """Returns a dictionary with all saved meals. Key: Meal ID @@ -1386,7 +1387,7 @@ def get_meals(self) -> Dict[int, str]: meals = document.xpath( "//*[@id='matching']/li" ) # get all items in the recipe list - _idx: Optional[int] = None + _idx: int | None = None try: for _idx, meal in enumerate(meals): meal_path = meal.xpath("./a")[0].attrib["href"] @@ -1409,7 +1410,7 @@ def get_meal(self, meal_id: int, meal_title: str) -> types.Recipe: meal_url = parse.urljoin(self.BASE_URL_SECURE, meal_path) document = self._get_document_for_url(meal_url) - recipe_dict: Dict[str, Any] = { + recipe_dict: dict[str, Any] = { "@context": "https://schema.org", "@type": "Recipe", "author": self.effective_username, diff --git a/setup.cfg b/setup.cfg index 7cffc38..9212aa1 100644 --- a/setup.cfg +++ b/setup.cfg @@ -2,11 +2,18 @@ # https://github.com/ambv/black#line-length max-line-length = 88 ignore = - E203, # E203: whitespace before ':' (defer to black) - E231, # E231: missing whitespace after ',' (defer to black) - E501, # E501: line length (defer to black) - W503, # W503: break before binary operators (defer to black) - A003, # A003: [builtins] allow class attributes to be named after builtins (e.g., `id`) + # E203: whitespace before ':' (defer to black) + E203, + # E231: missing whitespace after ',' (defer to black) + E231, + # E501: line length (defer to black) + E501, + # W503: break before binary operators (defer to black) + W503, + # A003: [builtins] allow class attributes to be named after builtins (e.g., `id`) + A003, + # E704: Allow multiple statements on one line + E704, [pep8] max-line-length = 88 From 14ab98abf3b006ded8a8dd8ab7025961208cf872 Mon Sep 17 00:00:00 2001 From: Adam Coddington Date: Sun, 9 Mar 2025 12:56:44 -0500 Subject: [PATCH 31/36] Specify python versions as strings. --- .github/workflows/lint.yml | 2 +- .github/workflows/test.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index b78de3d..43fffb3 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -12,7 +12,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v1 with: - python-version: 3.13 + python-version: "3.13" - name: Install Python dependencies run: pip install black==22.6.0 flake8==5.0.4 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4e62200..6d4121f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -8,7 +8,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.9, 3.10, 3.11, 3.12, 3.13] + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] steps: - uses: actions/checkout@v2 From 75ba9aecea6a5972ab46ae4a02a146aac655f01b Mon Sep 17 00:00:00 2001 From: Adam Coddington Date: Sun, 9 Mar 2025 13:12:11 -0500 Subject: [PATCH 32/36] Fixing a test, updating lxml to require 5.x version. --- requirements.txt | 3 +- tests/json/report_nutrition_net_calories.json | 5622 +++++++++-------- 2 files changed, 2814 insertions(+), 2811 deletions(-) diff --git a/requirements.txt b/requirements.txt index da27f4e..158950c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,8 +1,9 @@ blessed>=1.8.5,<2.0 -lxml>=4.2.5,<5 +lxml>=5.0.2,<6 measurement>=3.2.0,<4.0 python-dateutil>=2.4,<3 requests>=2.17.0,<3 rich>=12,<13 browser_cookie3>=0.16.1,<1 cloudscraper>=1.2.71,<2 +typing-extensions==4.12.2 diff --git a/tests/json/report_nutrition_net_calories.json b/tests/json/report_nutrition_net_calories.json index 4f1ac8a..d940d02 100644 --- a/tests/json/report_nutrition_net_calories.json +++ b/tests/json/report_nutrition_net_calories.json @@ -1,2811 +1,2813 @@ { - "chartType":"column", - "title":"Net Calories Consumed", - "category":"nutrition", - "label":"Net Calories Consumed", - "data":[ - { - "date":"4/14", - "total":0.0 - }, - { - "date":"4/15", - "total":0.0 - }, - { - "date":"4/16", - "total":0.0 - }, - { - "date":"4/17", - "total":0.0 - }, - { - "date":"4/18", - "total":0.0 - }, - { - "date":"4/19", - "total":0.0 - }, - { - "date":"4/20", - "total":0.0 - }, - { - "date":"4/21", - "total":0.0 - }, - { - "date":"4/22", - "total":0.0 - }, - { - "date":"4/23", - "total":0.0 - }, - { - "date":"4/24", - "total":0.0 - }, - { - "date":"4/25", - "total":0.0 - }, - { - "date":"4/26", - "total":0.0 - }, - { - "date":"4/27", - "total":0.0 - }, - { - "date":"4/28", - "total":0.0 - }, - { - "date":"4/29", - "total":0.0 - }, - { - "date":"4/30", - "total":0.0 - }, - { - "date":"5/01", - "total":0.0 - }, - { - "date":"5/02", - "total":0.0 - }, - { - "date":"5/03", - "total":0.0 - }, - { - "date":"5/04", - "total":0.0 - }, - { - "date":"5/05", - "total":0.0 - }, - { - "date":"5/06", - "total":0.0 - }, - { - "date":"5/07", - "total":0.0 - }, - { - "date":"5/08", - "total":0.0 - }, - { - "date":"5/09", - "total":0.0 - }, - { - "date":"5/10", - "total":0.0 - }, - { - "date":"5/11", - "total":0.0 - }, - { - "date":"5/12", - "total":0.0 - }, - { - "date":"5/13", - "total":0.0 - }, - { - "date":"5/14", - "total":0.0 - }, - { - "date":"5/15", - "total":0.0 - }, - { - "date":"5/16", - "total":0.0 - }, - { - "date":"5/17", - "total":0.0 - }, - { - "date":"5/18", - "total":0.0 - }, - { - "date":"5/19", - "total":0.0 - }, - { - "date":"5/20", - "total":0.0 - }, - { - "date":"5/21", - "total":0.0 - }, - { - "date":"5/22", - "total":0.0 - }, - { - "date":"5/23", - "total":0.0 - }, - { - "date":"5/24", - "total":0.0 - }, - { - "date":"5/25", - "total":0.0 - }, - { - "date":"5/26", - "total":0.0 - }, - { - "date":"5/27", - "total":0.0 - }, - { - "date":"5/28", - "total":0.0 - }, - { - "date":"5/29", - "total":0.0 - }, - { - "date":"5/30", - "total":0.0 - }, - { - "date":"5/31", - "total":0.0 - }, - { - "date":"6/01", - "total":0.0 - }, - { - "date":"6/02", - "total":0.0 - }, - { - "date":"6/03", - "total":0.0 - }, - { - "date":"6/04", - "total":0.0 - }, - { - "date":"6/05", - "total":0.0 - }, - { - "date":"6/06", - "total":0.0 - }, - { - "date":"6/07", - "total":0.0 - }, - { - "date":"6/08", - "total":0.0 - }, - { - "date":"6/09", - "total":0.0 - }, - { - "date":"6/10", - "total":0.0 - }, - { - "date":"6/11", - "total":0.0 - }, - { - "date":"6/12", - "total":0.0 - }, - { - "date":"6/13", - "total":0.0 - }, - { - "date":"6/14", - "total":0.0 - }, - { - "date":"6/15", - "total":0.0 - }, - { - "date":"6/16", - "total":0.0 - }, - { - "date":"6/17", - "total":0.0 - }, - { - "date":"6/18", - "total":0.0 - }, - { - "date":"6/19", - "total":0.0 - }, - { - "date":"6/20", - "total":0.0 - }, - { - "date":"6/21", - "total":0.0 - }, - { - "date":"6/22", - "total":0.0 - }, - { - "date":"6/23", - "total":0.0 - }, - { - "date":"6/24", - "total":0.0 - }, - { - "date":"6/25", - "total":0.0 - }, - { - "date":"6/26", - "total":0.0 - }, - { - "date":"6/27", - "total":0.0 - }, - { - "date":"6/28", - "total":0.0 - }, - { - "date":"6/29", - "total":0.0 - }, - { - "date":"6/30", - "total":0.0 - }, - { - "date":"7/01", - "total":0.0 - }, - { - "date":"7/02", - "total":0.0 - }, - { - "date":"7/03", - "total":0.0 - }, - { - "date":"7/04", - "total":0.0 - }, - { - "date":"7/05", - "total":0.0 - }, - { - "date":"7/06", - "total":0.0 - }, - { - "date":"7/07", - "total":0.0 - }, - { - "date":"7/08", - "total":0.0 - }, - { - "date":"7/09", - "total":0.0 - }, - { - "date":"7/10", - "total":0.0 - }, - { - "date":"7/11", - "total":0.0 - }, - { - "date":"7/12", - "total":0.0 - }, - { - "date":"7/13", - "total":0.0 - }, - { - "date":"7/14", - "total":0.0 - }, - { - "date":"7/15", - "total":0.0 - }, - { - "date":"7/16", - "total":0.0 - }, - { - "date":"7/17", - "total":0.0 - }, - { - "date":"7/18", - "total":0.0 - }, - { - "date":"7/19", - "total":0.0 - }, - { - "date":"7/20", - "total":0.0 - }, - { - "date":"7/21", - "total":0.0 - }, - { - "date":"7/22", - "total":0.0 - }, - { - "date":"7/23", - "total":0.0 - }, - { - "date":"7/24", - "total":0.0 - }, - { - "date":"7/25", - "total":0.0 - }, - { - "date":"7/26", - "total":0.0 - }, - { - "date":"7/27", - "total":0.0 - }, - { - "date":"7/28", - "total":0.0 - }, - { - "date":"7/29", - "total":0.0 - }, - { - "date":"7/30", - "total":0.0 - }, - { - "date":"7/31", - "total":0.0 - }, - { - "date":"8/01", - "total":0.0 - }, - { - "date":"8/02", - "total":0.0 - }, - { - "date":"8/03", - "total":0.0 - }, - { - "date":"8/04", - "total":0.0 - }, - { - "date":"8/05", - "total":0.0 - }, - { - "date":"8/06", - "total":0.0 - }, - { - "date":"8/07", - "total":0.0 - }, - { - "date":"8/08", - "total":0.0 - }, - { - "date":"8/09", - "total":0.0 - }, - { - "date":"8/10", - "total":0.0 - }, - { - "date":"8/11", - "total":0.0 - }, - { - "date":"8/12", - "total":0.0 - }, - { - "date":"8/13", - "total":0.0 - }, - { - "date":"8/14", - "total":0.0 - }, - { - "date":"8/15", - "total":0.0 - }, - { - "date":"8/16", - "total":0.0 - }, - { - "date":"8/17", - "total":0.0 - }, - { - "date":"8/18", - "total":0.0 - }, - { - "date":"8/19", - "total":0.0 - }, - { - "date":"8/20", - "total":0.0 - }, - { - "date":"8/21", - "total":0.0 - }, - { - "date":"8/22", - "total":0.0 - }, - { - "date":"8/23", - "total":0.0 - }, - { - "date":"8/24", - "total":0.0 - }, - { - "date":"8/25", - "total":0.0 - }, - { - "date":"8/26", - "total":0.0 - }, - { - "date":"8/27", - "total":0.0 - }, - { - "date":"8/28", - "total":0.0 - }, - { - "date":"8/29", - "total":0.0 - }, - { - "date":"8/30", - "total":0.0 - }, - { - "date":"8/31", - "total":0.0 - }, - { - "date":"9/01", - "total":0.0 - }, - { - "date":"9/02", - "total":0.0 - }, - { - "date":"9/03", - "total":0.0 - }, - { - "date":"9/04", - "total":0.0 - }, - { - "date":"9/05", - "total":0.0 - }, - { - "date":"9/06", - "total":0.0 - }, - { - "date":"9/07", - "total":0.0 - }, - { - "date":"9/08", - "total":0.0 - }, - { - "date":"9/09", - "total":0.0 - }, - { - "date":"9/10", - "total":0.0 - }, - { - "date":"9/11", - "total":0.0 - }, - { - "date":"9/12", - "total":0.0 - }, - { - "date":"9/13", - "total":0.0 - }, - { - "date":"9/14", - "total":0.0 - }, - { - "date":"9/15", - "total":0.0 - }, - { - "date":"9/16", - "total":0.0 - }, - { - "date":"9/17", - "total":0.0 - }, - { - "date":"9/18", - "total":0.0 - }, - { - "date":"9/19", - "total":0.0 - }, - { - "date":"9/20", - "total":0.0 - }, - { - "date":"9/21", - "total":0.0 - }, - { - "date":"9/22", - "total":0.0 - }, - { - "date":"9/23", - "total":0.0 - }, - { - "date":"9/24", - "total":0.0 - }, - { - "date":"9/25", - "total":0.0 - }, - { - "date":"9/26", - "total":0.0 - }, - { - "date":"9/27", - "total":0.0 - }, - { - "date":"9/28", - "total":0.0 - }, - { - "date":"9/29", - "total":0.0 - }, - { - "date":"9/30", - "total":0.0 - }, - { - "date":"10/01", - "total":0.0 - }, - { - "date":"10/02", - "total":0.0 - }, - { - "date":"10/03", - "total":0.0 - }, - { - "date":"10/04", - "total":0.0 - }, - { - "date":"10/05", - "total":0.0 - }, - { - "date":"10/06", - "total":0.0 - }, - { - "date":"10/07", - "total":0.0 - }, - { - "date":"10/08", - "total":0.0 - }, - { - "date":"10/09", - "total":0.0 - }, - { - "date":"10/10", - "total":0.0 - }, - { - "date":"10/11", - "total":0.0 - }, - { - "date":"10/12", - "total":0.0 - }, - { - "date":"10/13", - "total":0.0 - }, - { - "date":"10/14", - "total":0.0 - }, - { - "date":"10/15", - "total":0.0 - }, - { - "date":"10/16", - "total":0.0 - }, - { - "date":"10/17", - "total":0.0 - }, - { - "date":"10/18", - "total":0.0 - }, - { - "date":"10/19", - "total":0.0 - }, - { - "date":"10/20", - "total":0.0 - }, - { - "date":"10/21", - "total":0.0 - }, - { - "date":"10/22", - "total":0.0 - }, - { - "date":"10/23", - "total":0.0 - }, - { - "date":"10/24", - "total":0.0 - }, - { - "date":"10/25", - "total":0.0 - }, - { - "date":"10/26", - "total":0.0 - }, - { - "date":"10/27", - "total":0.0 - }, - { - "date":"10/28", - "total":0.0 - }, - { - "date":"10/29", - "total":0.0 - }, - { - "date":"10/30", - "total":0.0 - }, - { - "date":"10/31", - "total":0.0 - }, - { - "date":"11/01", - "total":0.0 - }, - { - "date":"11/02", - "total":0.0 - }, - { - "date":"11/03", - "total":0.0 - }, - { - "date":"11/04", - "total":0.0 - }, - { - "date":"11/05", - "total":0.0 - }, - { - "date":"11/06", - "total":0.0 - }, - { - "date":"11/07", - "total":0.0 - }, - { - "date":"11/08", - "total":0.0 - }, - { - "date":"11/09", - "total":0.0 - }, - { - "date":"11/10", - "total":0.0 - }, - { - "date":"11/11", - "total":0.0 - }, - { - "date":"11/12", - "total":0.0 - }, - { - "date":"11/13", - "total":0.0 - }, - { - "date":"11/14", - "total":0.0 - }, - { - "date":"11/15", - "total":0.0 - }, - { - "date":"11/16", - "total":0.0 - }, - { - "date":"11/17", - "total":0.0 - }, - { - "date":"11/18", - "total":0.0 - }, - { - "date":"11/19", - "total":0.0 - }, - { - "date":"11/20", - "total":0.0 - }, - { - "date":"11/21", - "total":0.0 - }, - { - "date":"11/22", - "total":0.0 - }, - { - "date":"11/23", - "total":0.0 - }, - { - "date":"11/24", - "total":0.0 - }, - { - "date":"11/25", - "total":0.0 - }, - { - "date":"11/26", - "total":0.0 - }, - { - "date":"11/27", - "total":0.0 - }, - { - "date":"11/28", - "total":0.0 - }, - { - "date":"11/29", - "total":0.0 - }, - { - "date":"11/30", - "total":0.0 - }, - { - "date":"12/01", - "total":0.0 - }, - { - "date":"12/02", - "total":0.0 - }, - { - "date":"12/03", - "total":0.0 - }, - { - "date":"12/04", - "total":0.0 - }, - { - "date":"12/05", - "total":0.0 - }, - { - "date":"12/06", - "total":0.0 - }, - { - "date":"12/07", - "total":0.0 - }, - { - "date":"12/08", - "total":0.0 - }, - { - "date":"12/09", - "total":0.0 - }, - { - "date":"12/10", - "total":0.0 - }, - { - "date":"12/11", - "total":0.0 - }, - { - "date":"12/12", - "total":0.0 - }, - { - "date":"12/13", - "total":0.0 - }, - { - "date":"12/14", - "total":0.0 - }, - { - "date":"12/15", - "total":0.0 - }, - { - "date":"12/16", - "total":0.0 - }, - { - "date":"12/17", - "total":0.0 - }, - { - "date":"12/18", - "total":0.0 - }, - { - "date":"12/19", - "total":0.0 - }, - { - "date":"12/20", - "total":0.0 - }, - { - "date":"12/21", - "total":0.0 - }, - { - "date":"12/22", - "total":0.0 - }, - { - "date":"12/23", - "total":0.0 - }, - { - "date":"12/24", - "total":0.0 - }, - { - "date":"12/25", - "total":0.0 - }, - { - "date":"12/26", - "total":0.0 - }, - { - "date":"12/27", - "total":0.0 - }, - { - "date":"12/28", - "total":0.0 - }, - { - "date":"12/29", - "total":0.0 - }, - { - "date":"12/30", - "total":0.0 - }, - { - "date":"12/31", - "total":0.0 - }, - { - "date":"1/01", - "total":0.0 - }, - { - "date":"1/02", - "total":1372.0 - }, - { - "date":"1/03", - "total":3237.0 - }, - { - "date":"1/04", - "total":-49.0 - }, - { - "date":"1/05", - "total":-83.0 - }, - { - "date":"1/06", - "total":1221.0 - }, - { - "date":"1/07", - "total":-799.0 - }, - { - "date":"1/08", - "total":-513.0 - }, - { - "date":"1/09", - "total":217.0 - }, - { - "date":"1/10", - "total":-832.0 - }, - { - "date":"1/11", - "total":-908.0 - }, - { - "date":"1/12", - "total":-739.0 - }, - { - "date":"1/13", - "total":1152.0 - }, - { - "date":"1/14", - "total":-841.0 - }, - { - "date":"1/15", - "total":-246.0 - }, - { - "date":"1/16", - "total":-633.0 - }, - { - "date":"1/17", - "total":-709.0 - }, - { - "date":"1/18", - "total":-1270.0 - }, - { - "date":"1/19", - "total":-123.0 - }, - { - "date":"1/20", - "total":-832.0 - }, - { - "date":"1/21", - "total":-866.0 - }, - { - "date":"1/22", - "total":-203.0 - }, - { - "date":"1/23", - "total":-785.0 - }, - { - "date":"1/24", - "total":-923.0 - }, - { - "date":"1/25", - "total":-638.0 - }, - { - "date":"1/26", - "total":-80.0 - }, - { - "date":"1/27", - "total":-906.0 - }, - { - "date":"1/28", - "total":-693.0 - }, - { - "date":"1/29", - "total":-150.0 - }, - { - "date":"1/30", - "total":-783.0 - }, - { - "date":"1/31", - "total":-437.0 - }, - { - "date":"2/01", - "total":-299.0 - }, - { - "date":"2/02", - "total":0.0 - }, - { - "date":"2/03", - "total":0.0 - }, - { - "date":"2/04", - "total":0.0 - }, - { - "date":"2/05", - "total":0.0 - }, - { - "date":"2/06", - "total":0.0 - }, - { - "date":"2/07", - "total":0.0 - }, - { - "date":"2/08", - "total":0.0 - }, - { - "date":"2/09", - "total":0.0 - }, - { - "date":"2/10", - "total":0.0 - }, - { - "date":"2/11", - "total":0.0 - }, - { - "date":"2/12", - "total":0.0 - }, - { - "date":"2/13", - "total":-10.0 - }, - { - "date":"2/14", - "total":0.0 - }, - { - "date":"2/15", - "total":0.0 - }, - { - "date":"2/16", - "total":0.0 - }, - { - "date":"2/17", - "total":0.0 - }, - { - "date":"2/18", - "total":0.0 - }, - { - "date":"2/19", - "total":0.0 - }, - { - "date":"2/20", - "total":0.0 - }, - { - "date":"2/21", - "total":0.0 - }, - { - "date":"2/22", - "total":0.0 - }, - { - "date":"2/23", - "total":0.0 - }, - { - "date":"2/24", - "total":0.0 - }, - { - "date":"2/25", - "total":0.0 - }, - { - "date":"2/26", - "total":0.0 - }, - { - "date":"2/27", - "total":0.0 - }, - { - "date":"2/28", - "total":0.0 - }, - { - "date":"2/29", - "total":0.0 - }, - { - "date":"3/01", - "total":0.0 - }, - { - "date":"3/02", - "total":0.0 - }, - { - "date":"3/03", - "total":0.0 - }, - { - "date":"3/04", - "total":0.0 - }, - { - "date":"3/05", - "total":0.0 - }, - { - "date":"3/06", - "total":0.0 - }, - { - "date":"3/07", - "total":0.0 - }, - { - "date":"3/08", - "total":0.0 - }, - { - "date":"3/09", - "total":0.0 - }, - { - "date":"3/10", - "total":0.0 - }, - { - "date":"3/11", - "total":0.0 - }, - { - "date":"3/12", - "total":0.0 - }, - { - "date":"3/13", - "total":0.0 - }, - { - "date":"3/14", - "total":0.0 - }, - { - "date":"3/15", - "total":0.0 - }, - { - "date":"3/16", - "total":0.0 - }, - { - "date":"3/17", - "total":0.0 - }, - { - "date":"3/18", - "total":0.0 - }, - { - "date":"3/19", - "total":0.0 - }, - { - "date":"3/20", - "total":0.0 - }, - { - "date":"3/21", - "total":0.0 - }, - { - "date":"3/22", - "total":0.0 - }, - { - "date":"3/23", - "total":0.0 - }, - { - "date":"3/24", - "total":0.0 - }, - { - "date":"3/25", - "total":0.0 - }, - { - "date":"3/26", - "total":0.0 - }, - { - "date":"3/27", - "total":0.0 - }, - { - "date":"3/28", - "total":0.0 - }, - { - "date":"3/29", - "total":0.0 - }, - { - "date":"3/30", - "total":0.0 - }, - { - "date":"3/31", - "total":0.0 - }, - { - "date":"4/01", - "total":-218.0 - }, - { - "date":"4/02", - "total":-297.0 - }, - { - "date":"4/03", - "total":-400.0 - }, - { - "date":"4/04", - "total":-272.0 - }, - { - "date":"4/05", - "total":-304.0 - }, - { - "date":"4/06", - "total":-3.0 - }, - { - "date":"4/07", - "total":0.0 - }, - { - "date":"4/08", - "total":0.0 - }, - { - "date":"4/09", - "total":0.0 - }, - { - "date":"4/10", - "total":0.0 - }, - { - "date":"4/11", - "total":0.0 - }, - { - "date":"4/12", - "total":0.0 - }, - { - "date":"4/13", - "total":0.0 - }, - { - "date":"4/14", - "total":0.0 - }, - { - "date":"4/15", - "total":0.0 - }, - { - "date":"4/16", - "total":0.0 - }, - { - "date":"4/17", - "total":0.0 - }, - { - "date":"4/18", - "total":0.0 - }, - { - "date":"4/19", - "total":0.0 - }, - { - "date":"4/20", - "total":0.0 - }, - { - "date":"4/21", - "total":0.0 - }, - { - "date":"4/22", - "total":0.0 - }, - { - "date":"4/23", - "total":0.0 - }, - { - "date":"4/24", - "total":0.0 - }, - { - "date":"4/25", - "total":0.0 - }, - { - "date":"4/26", - "total":0.0 - }, - { - "date":"4/27", - "total":0.0 - }, - { - "date":"4/28", - "total":0.0 - }, - { - "date":"4/29", - "total":0.0 - }, - { - "date":"4/30", - "total":0.0 - }, - { - "date":"5/01", - "total":0.0 - }, - { - "date":"5/02", - "total":0.0 - }, - { - "date":"5/03", - "total":0.0 - }, - { - "date":"5/04", - "total":0.0 - }, - { - "date":"5/05", - "total":0.0 - }, - { - "date":"5/06", - "total":0.0 - }, - { - "date":"5/07", - "total":0.0 - }, - { - "date":"5/08", - "total":0.0 - }, - { - "date":"5/09", - "total":0.0 - }, - { - "date":"5/10", - "total":0.0 - }, - { - "date":"5/11", - "total":0.0 - }, - { - "date":"5/12", - "total":0.0 - }, - { - "date":"5/13", - "total":0.0 - }, - { - "date":"5/14", - "total":0.0 - }, - { - "date":"5/15", - "total":0.0 - }, - { - "date":"5/16", - "total":0.0 - }, - { - "date":"5/17", - "total":0.0 - }, - { - "date":"5/18", - "total":0.0 - }, - { - "date":"5/19", - "total":0.0 - }, - { - "date":"5/20", - "total":0.0 - }, - { - "date":"5/21", - "total":0.0 - }, - { - "date":"5/22", - "total":0.0 - }, - { - "date":"5/23", - "total":0.0 - }, - { - "date":"5/24", - "total":0.0 - }, - { - "date":"5/25", - "total":0.0 - }, - { - "date":"5/26", - "total":0.0 - }, - { - "date":"5/27", - "total":0.0 - }, - { - "date":"5/28", - "total":0.0 - }, - { - "date":"5/29", - "total":0.0 - }, - { - "date":"5/30", - "total":0.0 - }, - { - "date":"5/31", - "total":0.0 - }, - { - "date":"6/01", - "total":0.0 - }, - { - "date":"6/02", - "total":0.0 - }, - { - "date":"6/03", - "total":0.0 - }, - { - "date":"6/04", - "total":0.0 - }, - { - "date":"6/05", - "total":0.0 - }, - { - "date":"6/06", - "total":0.0 - }, - { - "date":"6/07", - "total":0.0 - }, - { - "date":"6/08", - "total":0.0 - }, - { - "date":"6/09", - "total":0.0 - }, - { - "date":"6/10", - "total":0.0 - }, - { - "date":"6/11", - "total":0.0 - }, - { - "date":"6/12", - "total":0.0 - }, - { - "date":"6/13", - "total":0.0 - }, - { - "date":"6/14", - "total":0.0 - }, - { - "date":"6/15", - "total":0.0 - }, - { - "date":"6/16", - "total":0.0 - }, - { - "date":"6/17", - "total":0.0 - }, - { - "date":"6/18", - "total":0.0 - }, - { - "date":"6/19", - "total":0.0 - }, - { - "date":"6/20", - "total":0.0 - }, - { - "date":"6/21", - "total":0.0 - }, - { - "date":"6/22", - "total":0.0 - }, - { - "date":"6/23", - "total":0.0 - }, - { - "date":"6/24", - "total":0.0 - }, - { - "date":"6/25", - "total":0.0 - }, - { - "date":"6/26", - "total":0.0 - }, - { - "date":"6/27", - "total":0.0 - }, - { - "date":"6/28", - "total":0.0 - }, - { - "date":"6/29", - "total":0.0 - }, - { - "date":"6/30", - "total":0.0 - }, - { - "date":"7/01", - "total":0.0 - }, - { - "date":"7/02", - "total":0.0 - }, - { - "date":"7/03", - "total":0.0 - }, - { - "date":"7/04", - "total":0.0 - }, - { - "date":"7/05", - "total":0.0 - }, - { - "date":"7/06", - "total":0.0 - }, - { - "date":"7/07", - "total":0.0 - }, - { - "date":"7/08", - "total":0.0 - }, - { - "date":"7/09", - "total":0.0 - }, - { - "date":"7/10", - "total":0.0 - }, - { - "date":"7/11", - "total":0.0 - }, - { - "date":"7/12", - "total":0.0 - }, - { - "date":"7/13", - "total":0.0 - }, - { - "date":"7/14", - "total":0.0 - }, - { - "date":"7/15", - "total":0.0 - }, - { - "date":"7/16", - "total":0.0 - }, - { - "date":"7/17", - "total":0.0 - }, - { - "date":"7/18", - "total":0.0 - }, - { - "date":"7/19", - "total":0.0 - }, - { - "date":"7/20", - "total":0.0 - }, - { - "date":"7/21", - "total":0.0 - }, - { - "date":"7/22", - "total":0.0 - }, - { - "date":"7/23", - "total":0.0 - }, - { - "date":"7/24", - "total":0.0 - }, - { - "date":"7/25", - "total":0.0 - }, - { - "date":"7/26", - "total":0.0 - }, - { - "date":"7/27", - "total":0.0 - }, - { - "date":"7/28", - "total":0.0 - }, - { - "date":"7/29", - "total":0.0 - }, - { - "date":"7/30", - "total":0.0 - }, - { - "date":"7/31", - "total":0.0 - }, - { - "date":"8/01", - "total":0.0 - }, - { - "date":"8/02", - "total":0.0 - }, - { - "date":"8/03", - "total":0.0 - }, - { - "date":"8/04", - "total":0.0 - }, - { - "date":"8/05", - "total":0.0 - }, - { - "date":"8/06", - "total":0.0 - }, - { - "date":"8/07", - "total":0.0 - }, - { - "date":"8/08", - "total":0.0 - }, - { - "date":"8/09", - "total":0.0 - }, - { - "date":"8/10", - "total":0.0 - }, - { - "date":"8/11", - "total":0.0 - }, - { - "date":"8/12", - "total":0.0 - }, - { - "date":"8/13", - "total":0.0 - }, - { - "date":"8/14", - "total":0.0 - }, - { - "date":"8/15", - "total":0.0 - }, - { - "date":"8/16", - "total":0.0 - }, - { - "date":"8/17", - "total":0.0 - }, - { - "date":"8/18", - "total":0.0 - }, - { - "date":"8/19", - "total":0.0 - }, - { - "date":"8/20", - "total":0.0 - }, - { - "date":"8/21", - "total":0.0 - }, - { - "date":"8/22", - "total":0.0 - }, - { - "date":"8/23", - "total":0.0 - }, - { - "date":"8/24", - "total":0.0 - }, - { - "date":"8/25", - "total":0.0 - }, - { - "date":"8/26", - "total":0.0 - }, - { - "date":"8/27", - "total":0.0 - }, - { - "date":"8/28", - "total":0.0 - }, - { - "date":"8/29", - "total":0.0 - }, - { - "date":"8/30", - "total":0.0 - }, - { - "date":"8/31", - "total":0.0 - }, - { - "date":"9/01", - "total":0.0 - }, - { - "date":"9/02", - "total":0.0 - }, - { - "date":"9/03", - "total":0.0 - }, - { - "date":"9/04", - "total":0.0 - }, - { - "date":"9/05", - "total":0.0 - }, - { - "date":"9/06", - "total":0.0 - }, - { - "date":"9/07", - "total":0.0 - }, - { - "date":"9/08", - "total":0.0 - }, - { - "date":"9/09", - "total":0.0 - }, - { - "date":"9/10", - "total":0.0 - }, - { - "date":"9/11", - "total":0.0 - }, - { - "date":"9/12", - "total":0.0 - }, - { - "date":"9/13", - "total":0.0 - }, - { - "date":"9/14", - "total":0.0 - }, - { - "date":"9/15", - "total":0.0 - }, - { - "date":"9/16", - "total":0.0 - }, - { - "date":"9/17", - "total":0.0 - }, - { - "date":"9/18", - "total":0.0 - }, - { - "date":"9/19", - "total":0.0 - }, - { - "date":"9/20", - "total":0.0 - }, - { - "date":"9/21", - "total":0.0 - }, - { - "date":"9/22", - "total":0.0 - }, - { - "date":"9/23", - "total":0.0 - }, - { - "date":"9/24", - "total":0.0 - }, - { - "date":"9/25", - "total":0.0 - }, - { - "date":"9/26", - "total":0.0 - }, - { - "date":"9/27", - "total":0.0 - }, - { - "date":"9/28", - "total":0.0 - }, - { - "date":"9/29", - "total":0.0 - }, - { - "date":"9/30", - "total":0.0 - }, - { - "date":"10/01", - "total":0.0 - }, - { - "date":"10/02", - "total":0.0 - }, - { - "date":"10/03", - "total":0.0 - }, - { - "date":"10/04", - "total":0.0 - }, - { - "date":"10/05", - "total":0.0 - }, - { - "date":"10/06", - "total":0.0 - }, - { - "date":"10/07", - "total":0.0 - }, - { - "date":"10/08", - "total":0.0 - }, - { - "date":"10/09", - "total":0.0 - }, - { - "date":"10/10", - "total":0.0 - }, - { - "date":"10/11", - "total":-287.0 - }, - { - "date":"10/12", - "total":-30.0 - }, - { - "date":"10/13", - "total":-81.0 - }, - { - "date":"10/14", - "total":-107.0 - }, - { - "date":"10/15", - "total":-95.0 - }, - { - "date":"10/16", - "total":-156.0 - }, - { - "date":"10/17", - "total":-81.0 - }, - { - "date":"10/18", - "total":239.0 - }, - { - "date":"10/19", - "total":-14.0 - }, - { - "date":"10/20", - "total":-35.0 - }, - { - "date":"10/21", - "total":-283.0 - }, - { - "date":"10/22", - "total":-56.0 - }, - { - "date":"10/23", - "total":-91.0 - }, - { - "date":"10/24", - "total":-217.0 - }, - { - "date":"10/25", - "total":-311.0 - }, - { - "date":"10/26", - "total":-168.0 - }, - { - "date":"10/27", - "total":-70.0 - }, - { - "date":"10/28", - "total":-41.0 - }, - { - "date":"10/29", - "total":-45.0 - }, - { - "date":"10/30", - "total":-36.0 - }, - { - "date":"10/31", - "total":0.0 - }, - { - "date":"11/01", - "total":0.0 - }, - { - "date":"11/02", - "total":0.0 - }, - { - "date":"11/03", - "total":0.0 - }, - { - "date":"11/04", - "total":0.0 - }, - { - "date":"11/05", - "total":0.0 - }, - { - "date":"11/06", - "total":0.0 - }, - { - "date":"11/07", - "total":0.0 - }, - { - "date":"11/08", - "total":0.0 - }, - { - "date":"11/09", - "total":0.0 - }, - { - "date":"11/10", - "total":-425.0 - }, - { - "date":"11/11", - "total":0.0 - }, - { - "date":"11/12", - "total":0.0 - }, - { - "date":"11/13", - "total":0.0 - }, - { - "date":"11/14", - "total":0.0 - }, - { - "date":"11/15", - "total":0.0 - }, - { - "date":"11/16", - "total":0.0 - }, - { - "date":"11/17", - "total":0.0 - }, - { - "date":"11/18", - "total":0.0 - }, - { - "date":"11/19", - "total":0.0 - }, - { - "date":"11/20", - "total":0.0 - }, - { - "date":"11/21", - "total":0.0 - }, - { - "date":"11/22", - "total":0.0 - }, - { - "date":"11/23", - "total":0.0 - }, - { - "date":"11/24", - "total":0.0 - }, - { - "date":"11/25", - "total":0.0 - }, - { - "date":"11/26", - "total":0.0 - }, - { - "date":"11/27", - "total":0.0 - }, - { - "date":"11/28", - "total":0.0 - }, - { - "date":"11/29", - "total":0.0 - }, - { - "date":"11/30", - "total":0.0 - }, - { - "date":"12/01", - "total":0.0 - }, - { - "date":"12/02", - "total":0.0 - }, - { - "date":"12/03", - "total":0.0 - }, - { - "date":"12/04", - "total":0.0 - }, - { - "date":"12/05", - "total":0.0 - }, - { - "date":"12/06", - "total":0.0 - }, - { - "date":"12/07", - "total":0.0 - }, - { - "date":"12/08", - "total":0.0 - }, - { - "date":"12/09", - "total":0.0 - }, - { - "date":"12/10", - "total":0.0 - }, - { - "date":"12/11", - "total":0.0 - }, - { - "date":"12/12", - "total":0.0 - }, - { - "date":"12/13", - "total":0.0 - }, - { - "date":"12/14", - "total":0.0 - }, - { - "date":"12/15", - "total":0.0 - }, - { - "date":"12/16", - "total":0.0 - }, - { - "date":"12/17", - "total":0.0 - }, - { - "date":"12/18", - "total":0.0 - }, - { - "date":"12/19", - "total":0.0 - }, - { - "date":"12/20", - "total":0.0 - }, - { - "date":"12/21", - "total":0.0 - }, - { - "date":"12/22", - "total":0.0 - }, - { - "date":"12/23", - "total":0.0 - }, - { - "date":"12/24", - "total":0.0 - }, - { - "date":"12/25", - "total":0.0 - }, - { - "date":"12/26", - "total":0.0 - }, - { - "date":"12/27", - "total":0.0 - }, - { - "date":"12/28", - "total":0.0 - }, - { - "date":"12/29", - "total":0.0 - }, - { - "date":"12/30", - "total":0.0 - }, - { - "date":"12/31", - "total":0.0 - }, - { - "date":"1/01", - "total":0.0 - }, - { - "date":"1/02", - "total":0.0 - }, - { - "date":"1/03", - "total":0.0 - }, - { - "date":"1/04", - "total":0.0 - }, - { - "date":"1/05", - "total":0.0 - }, - { - "date":"1/06", - "total":0.0 - }, - { - "date":"1/07", - "total":0.0 - }, - { - "date":"1/08", - "total":0.0 - }, - { - "date":"1/09", - "total":0.0 - }, - { - "date":"1/10", - "total":0.0 - }, - { - "date":"1/11", - "total":0.0 - }, - { - "date":"1/12", - "total":0.0 - }, - { - "date":"1/13", - "total":0.0 - }, - { - "date":"1/14", - "total":0.0 - }, - { - "date":"1/15", - "total":0.0 - }, - { - "date":"1/16", - "total":0.0 - }, - { - "date":"1/17", - "total":0.0 - }, - { - "date":"1/18", - "total":0.0 - }, - { - "date":"1/19", - "total":0.0 - }, - { - "date":"1/20", - "total":0.0 - }, - { - "date":"1/21", - "total":0.0 - }, - { - "date":"1/22", - "total":0.0 - }, - { - "date":"1/23", - "total":0.0 - }, - { - "date":"1/24", - "total":0.0 - }, - { - "date":"1/25", - "total":0.0 - }, - { - "date":"1/26", - "total":0.0 - }, - { - "date":"1/27", - "total":0.0 - }, - { - "date":"1/28", - "total":0.0 - }, - { - "date":"1/29", - "total":0.0 - }, - { - "date":"1/30", - "total":0.0 - }, - { - "date":"1/31", - "total":0.0 - }, - { - "date":"2/01", - "total":0.0 - }, - { - "date":"2/02", - "total":0.0 - }, - { - "date":"2/03", - "total":0.0 - }, - { - "date":"2/04", - "total":0.0 - }, - { - "date":"2/05", - "total":0.0 - }, - { - "date":"2/06", - "total":0.0 - }, - { - "date":"2/07", - "total":0.0 - }, - { - "date":"2/08", - "total":0.0 - }, - { - "date":"2/09", - "total":0.0 - }, - { - "date":"2/10", - "total":0.0 - }, - { - "date":"2/11", - "total":0.0 - }, - { - "date":"2/12", - "total":0.0 - }, - { - "date":"2/13", - "total":0.0 - }, - { - "date":"2/14", - "total":0.0 - }, - { - "date":"2/15", - "total":0.0 - }, - { - "date":"2/16", - "total":-21.0 - }, - { - "date":"2/17", - "total":-22.0 - }, - { - "date":"2/18", - "total":0.0 - }, - { - "date":"2/19", - "total":-26.0 - }, - { - "date":"2/20", - "total":-405.0 - }, - { - "date":"2/21", - "total":-176.0 - }, - { - "date":"2/22", - "total":-29.0 - }, - { - "date":"2/23", - "total":1419.0 - }, - { - "date":"2/24", - "total":1442.0 - }, - { - "date":"2/25", - "total":1312.0 - }, - { - "date":"2/26", - "total":1424.0 - }, - { - "date":"2/27", - "total":1575.0 - }, - { - "date":"2/28", - "total":985.0 - }, - { - "date":"3/01", - "total":1510.0 - }, - { - "date":"3/02", - "total":1272.0 - }, - { - "date":"3/03", - "total":1409.0 - }, - { - "date":"3/04", - "total":1168.0 - }, - { - "date":"3/05", - "total":1581.0 - }, - { - "date":"3/06", - "total":1416.0 - }, - { - "date":"3/07", - "total":1312.0 - }, - { - "date":"3/08", - "total":1387.0 - }, - { - "date":"3/09", - "total":1390.0 - }, - { - "date":"3/10", - "total":1489.0 - }, - { - "date":"3/11", - "total":1451.0 - }, - { - "date":"3/12", - "total":1454.0 - }, - { - "date":"3/13", - "total":425.0 - } - ], - "ordinate_axis_min":"-1800.0", - "ordinate_axis_max":"3700.0", - "goal":"1500" -} \ No newline at end of file + "chartType": "column", + "title": "Net Calories Consumed", + "category": "nutrition", + "label": "Net Calories Consumed", + "outcome": { + "results": [ + { + "date": "4/14", + "total": 0.0 + }, + { + "date": "4/15", + "total": 0.0 + }, + { + "date": "4/16", + "total": 0.0 + }, + { + "date": "4/17", + "total": 0.0 + }, + { + "date": "4/18", + "total": 0.0 + }, + { + "date": "4/19", + "total": 0.0 + }, + { + "date": "4/20", + "total": 0.0 + }, + { + "date": "4/21", + "total": 0.0 + }, + { + "date": "4/22", + "total": 0.0 + }, + { + "date": "4/23", + "total": 0.0 + }, + { + "date": "4/24", + "total": 0.0 + }, + { + "date": "4/25", + "total": 0.0 + }, + { + "date": "4/26", + "total": 0.0 + }, + { + "date": "4/27", + "total": 0.0 + }, + { + "date": "4/28", + "total": 0.0 + }, + { + "date": "4/29", + "total": 0.0 + }, + { + "date": "4/30", + "total": 0.0 + }, + { + "date": "5/01", + "total": 0.0 + }, + { + "date": "5/02", + "total": 0.0 + }, + { + "date": "5/03", + "total": 0.0 + }, + { + "date": "5/04", + "total": 0.0 + }, + { + "date": "5/05", + "total": 0.0 + }, + { + "date": "5/06", + "total": 0.0 + }, + { + "date": "5/07", + "total": 0.0 + }, + { + "date": "5/08", + "total": 0.0 + }, + { + "date": "5/09", + "total": 0.0 + }, + { + "date": "5/10", + "total": 0.0 + }, + { + "date": "5/11", + "total": 0.0 + }, + { + "date": "5/12", + "total": 0.0 + }, + { + "date": "5/13", + "total": 0.0 + }, + { + "date": "5/14", + "total": 0.0 + }, + { + "date": "5/15", + "total": 0.0 + }, + { + "date": "5/16", + "total": 0.0 + }, + { + "date": "5/17", + "total": 0.0 + }, + { + "date": "5/18", + "total": 0.0 + }, + { + "date": "5/19", + "total": 0.0 + }, + { + "date": "5/20", + "total": 0.0 + }, + { + "date": "5/21", + "total": 0.0 + }, + { + "date": "5/22", + "total": 0.0 + }, + { + "date": "5/23", + "total": 0.0 + }, + { + "date": "5/24", + "total": 0.0 + }, + { + "date": "5/25", + "total": 0.0 + }, + { + "date": "5/26", + "total": 0.0 + }, + { + "date": "5/27", + "total": 0.0 + }, + { + "date": "5/28", + "total": 0.0 + }, + { + "date": "5/29", + "total": 0.0 + }, + { + "date": "5/30", + "total": 0.0 + }, + { + "date": "5/31", + "total": 0.0 + }, + { + "date": "6/01", + "total": 0.0 + }, + { + "date": "6/02", + "total": 0.0 + }, + { + "date": "6/03", + "total": 0.0 + }, + { + "date": "6/04", + "total": 0.0 + }, + { + "date": "6/05", + "total": 0.0 + }, + { + "date": "6/06", + "total": 0.0 + }, + { + "date": "6/07", + "total": 0.0 + }, + { + "date": "6/08", + "total": 0.0 + }, + { + "date": "6/09", + "total": 0.0 + }, + { + "date": "6/10", + "total": 0.0 + }, + { + "date": "6/11", + "total": 0.0 + }, + { + "date": "6/12", + "total": 0.0 + }, + { + "date": "6/13", + "total": 0.0 + }, + { + "date": "6/14", + "total": 0.0 + }, + { + "date": "6/15", + "total": 0.0 + }, + { + "date": "6/16", + "total": 0.0 + }, + { + "date": "6/17", + "total": 0.0 + }, + { + "date": "6/18", + "total": 0.0 + }, + { + "date": "6/19", + "total": 0.0 + }, + { + "date": "6/20", + "total": 0.0 + }, + { + "date": "6/21", + "total": 0.0 + }, + { + "date": "6/22", + "total": 0.0 + }, + { + "date": "6/23", + "total": 0.0 + }, + { + "date": "6/24", + "total": 0.0 + }, + { + "date": "6/25", + "total": 0.0 + }, + { + "date": "6/26", + "total": 0.0 + }, + { + "date": "6/27", + "total": 0.0 + }, + { + "date": "6/28", + "total": 0.0 + }, + { + "date": "6/29", + "total": 0.0 + }, + { + "date": "6/30", + "total": 0.0 + }, + { + "date": "7/01", + "total": 0.0 + }, + { + "date": "7/02", + "total": 0.0 + }, + { + "date": "7/03", + "total": 0.0 + }, + { + "date": "7/04", + "total": 0.0 + }, + { + "date": "7/05", + "total": 0.0 + }, + { + "date": "7/06", + "total": 0.0 + }, + { + "date": "7/07", + "total": 0.0 + }, + { + "date": "7/08", + "total": 0.0 + }, + { + "date": "7/09", + "total": 0.0 + }, + { + "date": "7/10", + "total": 0.0 + }, + { + "date": "7/11", + "total": 0.0 + }, + { + "date": "7/12", + "total": 0.0 + }, + { + "date": "7/13", + "total": 0.0 + }, + { + "date": "7/14", + "total": 0.0 + }, + { + "date": "7/15", + "total": 0.0 + }, + { + "date": "7/16", + "total": 0.0 + }, + { + "date": "7/17", + "total": 0.0 + }, + { + "date": "7/18", + "total": 0.0 + }, + { + "date": "7/19", + "total": 0.0 + }, + { + "date": "7/20", + "total": 0.0 + }, + { + "date": "7/21", + "total": 0.0 + }, + { + "date": "7/22", + "total": 0.0 + }, + { + "date": "7/23", + "total": 0.0 + }, + { + "date": "7/24", + "total": 0.0 + }, + { + "date": "7/25", + "total": 0.0 + }, + { + "date": "7/26", + "total": 0.0 + }, + { + "date": "7/27", + "total": 0.0 + }, + { + "date": "7/28", + "total": 0.0 + }, + { + "date": "7/29", + "total": 0.0 + }, + { + "date": "7/30", + "total": 0.0 + }, + { + "date": "7/31", + "total": 0.0 + }, + { + "date": "8/01", + "total": 0.0 + }, + { + "date": "8/02", + "total": 0.0 + }, + { + "date": "8/03", + "total": 0.0 + }, + { + "date": "8/04", + "total": 0.0 + }, + { + "date": "8/05", + "total": 0.0 + }, + { + "date": "8/06", + "total": 0.0 + }, + { + "date": "8/07", + "total": 0.0 + }, + { + "date": "8/08", + "total": 0.0 + }, + { + "date": "8/09", + "total": 0.0 + }, + { + "date": "8/10", + "total": 0.0 + }, + { + "date": "8/11", + "total": 0.0 + }, + { + "date": "8/12", + "total": 0.0 + }, + { + "date": "8/13", + "total": 0.0 + }, + { + "date": "8/14", + "total": 0.0 + }, + { + "date": "8/15", + "total": 0.0 + }, + { + "date": "8/16", + "total": 0.0 + }, + { + "date": "8/17", + "total": 0.0 + }, + { + "date": "8/18", + "total": 0.0 + }, + { + "date": "8/19", + "total": 0.0 + }, + { + "date": "8/20", + "total": 0.0 + }, + { + "date": "8/21", + "total": 0.0 + }, + { + "date": "8/22", + "total": 0.0 + }, + { + "date": "8/23", + "total": 0.0 + }, + { + "date": "8/24", + "total": 0.0 + }, + { + "date": "8/25", + "total": 0.0 + }, + { + "date": "8/26", + "total": 0.0 + }, + { + "date": "8/27", + "total": 0.0 + }, + { + "date": "8/28", + "total": 0.0 + }, + { + "date": "8/29", + "total": 0.0 + }, + { + "date": "8/30", + "total": 0.0 + }, + { + "date": "8/31", + "total": 0.0 + }, + { + "date": "9/01", + "total": 0.0 + }, + { + "date": "9/02", + "total": 0.0 + }, + { + "date": "9/03", + "total": 0.0 + }, + { + "date": "9/04", + "total": 0.0 + }, + { + "date": "9/05", + "total": 0.0 + }, + { + "date": "9/06", + "total": 0.0 + }, + { + "date": "9/07", + "total": 0.0 + }, + { + "date": "9/08", + "total": 0.0 + }, + { + "date": "9/09", + "total": 0.0 + }, + { + "date": "9/10", + "total": 0.0 + }, + { + "date": "9/11", + "total": 0.0 + }, + { + "date": "9/12", + "total": 0.0 + }, + { + "date": "9/13", + "total": 0.0 + }, + { + "date": "9/14", + "total": 0.0 + }, + { + "date": "9/15", + "total": 0.0 + }, + { + "date": "9/16", + "total": 0.0 + }, + { + "date": "9/17", + "total": 0.0 + }, + { + "date": "9/18", + "total": 0.0 + }, + { + "date": "9/19", + "total": 0.0 + }, + { + "date": "9/20", + "total": 0.0 + }, + { + "date": "9/21", + "total": 0.0 + }, + { + "date": "9/22", + "total": 0.0 + }, + { + "date": "9/23", + "total": 0.0 + }, + { + "date": "9/24", + "total": 0.0 + }, + { + "date": "9/25", + "total": 0.0 + }, + { + "date": "9/26", + "total": 0.0 + }, + { + "date": "9/27", + "total": 0.0 + }, + { + "date": "9/28", + "total": 0.0 + }, + { + "date": "9/29", + "total": 0.0 + }, + { + "date": "9/30", + "total": 0.0 + }, + { + "date": "10/01", + "total": 0.0 + }, + { + "date": "10/02", + "total": 0.0 + }, + { + "date": "10/03", + "total": 0.0 + }, + { + "date": "10/04", + "total": 0.0 + }, + { + "date": "10/05", + "total": 0.0 + }, + { + "date": "10/06", + "total": 0.0 + }, + { + "date": "10/07", + "total": 0.0 + }, + { + "date": "10/08", + "total": 0.0 + }, + { + "date": "10/09", + "total": 0.0 + }, + { + "date": "10/10", + "total": 0.0 + }, + { + "date": "10/11", + "total": 0.0 + }, + { + "date": "10/12", + "total": 0.0 + }, + { + "date": "10/13", + "total": 0.0 + }, + { + "date": "10/14", + "total": 0.0 + }, + { + "date": "10/15", + "total": 0.0 + }, + { + "date": "10/16", + "total": 0.0 + }, + { + "date": "10/17", + "total": 0.0 + }, + { + "date": "10/18", + "total": 0.0 + }, + { + "date": "10/19", + "total": 0.0 + }, + { + "date": "10/20", + "total": 0.0 + }, + { + "date": "10/21", + "total": 0.0 + }, + { + "date": "10/22", + "total": 0.0 + }, + { + "date": "10/23", + "total": 0.0 + }, + { + "date": "10/24", + "total": 0.0 + }, + { + "date": "10/25", + "total": 0.0 + }, + { + "date": "10/26", + "total": 0.0 + }, + { + "date": "10/27", + "total": 0.0 + }, + { + "date": "10/28", + "total": 0.0 + }, + { + "date": "10/29", + "total": 0.0 + }, + { + "date": "10/30", + "total": 0.0 + }, + { + "date": "10/31", + "total": 0.0 + }, + { + "date": "11/01", + "total": 0.0 + }, + { + "date": "11/02", + "total": 0.0 + }, + { + "date": "11/03", + "total": 0.0 + }, + { + "date": "11/04", + "total": 0.0 + }, + { + "date": "11/05", + "total": 0.0 + }, + { + "date": "11/06", + "total": 0.0 + }, + { + "date": "11/07", + "total": 0.0 + }, + { + "date": "11/08", + "total": 0.0 + }, + { + "date": "11/09", + "total": 0.0 + }, + { + "date": "11/10", + "total": 0.0 + }, + { + "date": "11/11", + "total": 0.0 + }, + { + "date": "11/12", + "total": 0.0 + }, + { + "date": "11/13", + "total": 0.0 + }, + { + "date": "11/14", + "total": 0.0 + }, + { + "date": "11/15", + "total": 0.0 + }, + { + "date": "11/16", + "total": 0.0 + }, + { + "date": "11/17", + "total": 0.0 + }, + { + "date": "11/18", + "total": 0.0 + }, + { + "date": "11/19", + "total": 0.0 + }, + { + "date": "11/20", + "total": 0.0 + }, + { + "date": "11/21", + "total": 0.0 + }, + { + "date": "11/22", + "total": 0.0 + }, + { + "date": "11/23", + "total": 0.0 + }, + { + "date": "11/24", + "total": 0.0 + }, + { + "date": "11/25", + "total": 0.0 + }, + { + "date": "11/26", + "total": 0.0 + }, + { + "date": "11/27", + "total": 0.0 + }, + { + "date": "11/28", + "total": 0.0 + }, + { + "date": "11/29", + "total": 0.0 + }, + { + "date": "11/30", + "total": 0.0 + }, + { + "date": "12/01", + "total": 0.0 + }, + { + "date": "12/02", + "total": 0.0 + }, + { + "date": "12/03", + "total": 0.0 + }, + { + "date": "12/04", + "total": 0.0 + }, + { + "date": "12/05", + "total": 0.0 + }, + { + "date": "12/06", + "total": 0.0 + }, + { + "date": "12/07", + "total": 0.0 + }, + { + "date": "12/08", + "total": 0.0 + }, + { + "date": "12/09", + "total": 0.0 + }, + { + "date": "12/10", + "total": 0.0 + }, + { + "date": "12/11", + "total": 0.0 + }, + { + "date": "12/12", + "total": 0.0 + }, + { + "date": "12/13", + "total": 0.0 + }, + { + "date": "12/14", + "total": 0.0 + }, + { + "date": "12/15", + "total": 0.0 + }, + { + "date": "12/16", + "total": 0.0 + }, + { + "date": "12/17", + "total": 0.0 + }, + { + "date": "12/18", + "total": 0.0 + }, + { + "date": "12/19", + "total": 0.0 + }, + { + "date": "12/20", + "total": 0.0 + }, + { + "date": "12/21", + "total": 0.0 + }, + { + "date": "12/22", + "total": 0.0 + }, + { + "date": "12/23", + "total": 0.0 + }, + { + "date": "12/24", + "total": 0.0 + }, + { + "date": "12/25", + "total": 0.0 + }, + { + "date": "12/26", + "total": 0.0 + }, + { + "date": "12/27", + "total": 0.0 + }, + { + "date": "12/28", + "total": 0.0 + }, + { + "date": "12/29", + "total": 0.0 + }, + { + "date": "12/30", + "total": 0.0 + }, + { + "date": "12/31", + "total": 0.0 + }, + { + "date": "1/01", + "total": 0.0 + }, + { + "date": "1/02", + "total": 1372.0 + }, + { + "date": "1/03", + "total": 3237.0 + }, + { + "date": "1/04", + "total": -49.0 + }, + { + "date": "1/05", + "total": -83.0 + }, + { + "date": "1/06", + "total": 1221.0 + }, + { + "date": "1/07", + "total": -799.0 + }, + { + "date": "1/08", + "total": -513.0 + }, + { + "date": "1/09", + "total": 217.0 + }, + { + "date": "1/10", + "total": -832.0 + }, + { + "date": "1/11", + "total": -908.0 + }, + { + "date": "1/12", + "total": -739.0 + }, + { + "date": "1/13", + "total": 1152.0 + }, + { + "date": "1/14", + "total": -841.0 + }, + { + "date": "1/15", + "total": -246.0 + }, + { + "date": "1/16", + "total": -633.0 + }, + { + "date": "1/17", + "total": -709.0 + }, + { + "date": "1/18", + "total": -1270.0 + }, + { + "date": "1/19", + "total": -123.0 + }, + { + "date": "1/20", + "total": -832.0 + }, + { + "date": "1/21", + "total": -866.0 + }, + { + "date": "1/22", + "total": -203.0 + }, + { + "date": "1/23", + "total": -785.0 + }, + { + "date": "1/24", + "total": -923.0 + }, + { + "date": "1/25", + "total": -638.0 + }, + { + "date": "1/26", + "total": -80.0 + }, + { + "date": "1/27", + "total": -906.0 + }, + { + "date": "1/28", + "total": -693.0 + }, + { + "date": "1/29", + "total": -150.0 + }, + { + "date": "1/30", + "total": -783.0 + }, + { + "date": "1/31", + "total": -437.0 + }, + { + "date": "2/01", + "total": -299.0 + }, + { + "date": "2/02", + "total": 0.0 + }, + { + "date": "2/03", + "total": 0.0 + }, + { + "date": "2/04", + "total": 0.0 + }, + { + "date": "2/05", + "total": 0.0 + }, + { + "date": "2/06", + "total": 0.0 + }, + { + "date": "2/07", + "total": 0.0 + }, + { + "date": "2/08", + "total": 0.0 + }, + { + "date": "2/09", + "total": 0.0 + }, + { + "date": "2/10", + "total": 0.0 + }, + { + "date": "2/11", + "total": 0.0 + }, + { + "date": "2/12", + "total": 0.0 + }, + { + "date": "2/13", + "total": -10.0 + }, + { + "date": "2/14", + "total": 0.0 + }, + { + "date": "2/15", + "total": 0.0 + }, + { + "date": "2/16", + "total": 0.0 + }, + { + "date": "2/17", + "total": 0.0 + }, + { + "date": "2/18", + "total": 0.0 + }, + { + "date": "2/19", + "total": 0.0 + }, + { + "date": "2/20", + "total": 0.0 + }, + { + "date": "2/21", + "total": 0.0 + }, + { + "date": "2/22", + "total": 0.0 + }, + { + "date": "2/23", + "total": 0.0 + }, + { + "date": "2/24", + "total": 0.0 + }, + { + "date": "2/25", + "total": 0.0 + }, + { + "date": "2/26", + "total": 0.0 + }, + { + "date": "2/27", + "total": 0.0 + }, + { + "date": "2/28", + "total": 0.0 + }, + { + "date": "2/29", + "total": 0.0 + }, + { + "date": "3/01", + "total": 0.0 + }, + { + "date": "3/02", + "total": 0.0 + }, + { + "date": "3/03", + "total": 0.0 + }, + { + "date": "3/04", + "total": 0.0 + }, + { + "date": "3/05", + "total": 0.0 + }, + { + "date": "3/06", + "total": 0.0 + }, + { + "date": "3/07", + "total": 0.0 + }, + { + "date": "3/08", + "total": 0.0 + }, + { + "date": "3/09", + "total": 0.0 + }, + { + "date": "3/10", + "total": 0.0 + }, + { + "date": "3/11", + "total": 0.0 + }, + { + "date": "3/12", + "total": 0.0 + }, + { + "date": "3/13", + "total": 0.0 + }, + { + "date": "3/14", + "total": 0.0 + }, + { + "date": "3/15", + "total": 0.0 + }, + { + "date": "3/16", + "total": 0.0 + }, + { + "date": "3/17", + "total": 0.0 + }, + { + "date": "3/18", + "total": 0.0 + }, + { + "date": "3/19", + "total": 0.0 + }, + { + "date": "3/20", + "total": 0.0 + }, + { + "date": "3/21", + "total": 0.0 + }, + { + "date": "3/22", + "total": 0.0 + }, + { + "date": "3/23", + "total": 0.0 + }, + { + "date": "3/24", + "total": 0.0 + }, + { + "date": "3/25", + "total": 0.0 + }, + { + "date": "3/26", + "total": 0.0 + }, + { + "date": "3/27", + "total": 0.0 + }, + { + "date": "3/28", + "total": 0.0 + }, + { + "date": "3/29", + "total": 0.0 + }, + { + "date": "3/30", + "total": 0.0 + }, + { + "date": "3/31", + "total": 0.0 + }, + { + "date": "4/01", + "total": -218.0 + }, + { + "date": "4/02", + "total": -297.0 + }, + { + "date": "4/03", + "total": -400.0 + }, + { + "date": "4/04", + "total": -272.0 + }, + { + "date": "4/05", + "total": -304.0 + }, + { + "date": "4/06", + "total": -3.0 + }, + { + "date": "4/07", + "total": 0.0 + }, + { + "date": "4/08", + "total": 0.0 + }, + { + "date": "4/09", + "total": 0.0 + }, + { + "date": "4/10", + "total": 0.0 + }, + { + "date": "4/11", + "total": 0.0 + }, + { + "date": "4/12", + "total": 0.0 + }, + { + "date": "4/13", + "total": 0.0 + }, + { + "date": "4/14", + "total": 0.0 + }, + { + "date": "4/15", + "total": 0.0 + }, + { + "date": "4/16", + "total": 0.0 + }, + { + "date": "4/17", + "total": 0.0 + }, + { + "date": "4/18", + "total": 0.0 + }, + { + "date": "4/19", + "total": 0.0 + }, + { + "date": "4/20", + "total": 0.0 + }, + { + "date": "4/21", + "total": 0.0 + }, + { + "date": "4/22", + "total": 0.0 + }, + { + "date": "4/23", + "total": 0.0 + }, + { + "date": "4/24", + "total": 0.0 + }, + { + "date": "4/25", + "total": 0.0 + }, + { + "date": "4/26", + "total": 0.0 + }, + { + "date": "4/27", + "total": 0.0 + }, + { + "date": "4/28", + "total": 0.0 + }, + { + "date": "4/29", + "total": 0.0 + }, + { + "date": "4/30", + "total": 0.0 + }, + { + "date": "5/01", + "total": 0.0 + }, + { + "date": "5/02", + "total": 0.0 + }, + { + "date": "5/03", + "total": 0.0 + }, + { + "date": "5/04", + "total": 0.0 + }, + { + "date": "5/05", + "total": 0.0 + }, + { + "date": "5/06", + "total": 0.0 + }, + { + "date": "5/07", + "total": 0.0 + }, + { + "date": "5/08", + "total": 0.0 + }, + { + "date": "5/09", + "total": 0.0 + }, + { + "date": "5/10", + "total": 0.0 + }, + { + "date": "5/11", + "total": 0.0 + }, + { + "date": "5/12", + "total": 0.0 + }, + { + "date": "5/13", + "total": 0.0 + }, + { + "date": "5/14", + "total": 0.0 + }, + { + "date": "5/15", + "total": 0.0 + }, + { + "date": "5/16", + "total": 0.0 + }, + { + "date": "5/17", + "total": 0.0 + }, + { + "date": "5/18", + "total": 0.0 + }, + { + "date": "5/19", + "total": 0.0 + }, + { + "date": "5/20", + "total": 0.0 + }, + { + "date": "5/21", + "total": 0.0 + }, + { + "date": "5/22", + "total": 0.0 + }, + { + "date": "5/23", + "total": 0.0 + }, + { + "date": "5/24", + "total": 0.0 + }, + { + "date": "5/25", + "total": 0.0 + }, + { + "date": "5/26", + "total": 0.0 + }, + { + "date": "5/27", + "total": 0.0 + }, + { + "date": "5/28", + "total": 0.0 + }, + { + "date": "5/29", + "total": 0.0 + }, + { + "date": "5/30", + "total": 0.0 + }, + { + "date": "5/31", + "total": 0.0 + }, + { + "date": "6/01", + "total": 0.0 + }, + { + "date": "6/02", + "total": 0.0 + }, + { + "date": "6/03", + "total": 0.0 + }, + { + "date": "6/04", + "total": 0.0 + }, + { + "date": "6/05", + "total": 0.0 + }, + { + "date": "6/06", + "total": 0.0 + }, + { + "date": "6/07", + "total": 0.0 + }, + { + "date": "6/08", + "total": 0.0 + }, + { + "date": "6/09", + "total": 0.0 + }, + { + "date": "6/10", + "total": 0.0 + }, + { + "date": "6/11", + "total": 0.0 + }, + { + "date": "6/12", + "total": 0.0 + }, + { + "date": "6/13", + "total": 0.0 + }, + { + "date": "6/14", + "total": 0.0 + }, + { + "date": "6/15", + "total": 0.0 + }, + { + "date": "6/16", + "total": 0.0 + }, + { + "date": "6/17", + "total": 0.0 + }, + { + "date": "6/18", + "total": 0.0 + }, + { + "date": "6/19", + "total": 0.0 + }, + { + "date": "6/20", + "total": 0.0 + }, + { + "date": "6/21", + "total": 0.0 + }, + { + "date": "6/22", + "total": 0.0 + }, + { + "date": "6/23", + "total": 0.0 + }, + { + "date": "6/24", + "total": 0.0 + }, + { + "date": "6/25", + "total": 0.0 + }, + { + "date": "6/26", + "total": 0.0 + }, + { + "date": "6/27", + "total": 0.0 + }, + { + "date": "6/28", + "total": 0.0 + }, + { + "date": "6/29", + "total": 0.0 + }, + { + "date": "6/30", + "total": 0.0 + }, + { + "date": "7/01", + "total": 0.0 + }, + { + "date": "7/02", + "total": 0.0 + }, + { + "date": "7/03", + "total": 0.0 + }, + { + "date": "7/04", + "total": 0.0 + }, + { + "date": "7/05", + "total": 0.0 + }, + { + "date": "7/06", + "total": 0.0 + }, + { + "date": "7/07", + "total": 0.0 + }, + { + "date": "7/08", + "total": 0.0 + }, + { + "date": "7/09", + "total": 0.0 + }, + { + "date": "7/10", + "total": 0.0 + }, + { + "date": "7/11", + "total": 0.0 + }, + { + "date": "7/12", + "total": 0.0 + }, + { + "date": "7/13", + "total": 0.0 + }, + { + "date": "7/14", + "total": 0.0 + }, + { + "date": "7/15", + "total": 0.0 + }, + { + "date": "7/16", + "total": 0.0 + }, + { + "date": "7/17", + "total": 0.0 + }, + { + "date": "7/18", + "total": 0.0 + }, + { + "date": "7/19", + "total": 0.0 + }, + { + "date": "7/20", + "total": 0.0 + }, + { + "date": "7/21", + "total": 0.0 + }, + { + "date": "7/22", + "total": 0.0 + }, + { + "date": "7/23", + "total": 0.0 + }, + { + "date": "7/24", + "total": 0.0 + }, + { + "date": "7/25", + "total": 0.0 + }, + { + "date": "7/26", + "total": 0.0 + }, + { + "date": "7/27", + "total": 0.0 + }, + { + "date": "7/28", + "total": 0.0 + }, + { + "date": "7/29", + "total": 0.0 + }, + { + "date": "7/30", + "total": 0.0 + }, + { + "date": "7/31", + "total": 0.0 + }, + { + "date": "8/01", + "total": 0.0 + }, + { + "date": "8/02", + "total": 0.0 + }, + { + "date": "8/03", + "total": 0.0 + }, + { + "date": "8/04", + "total": 0.0 + }, + { + "date": "8/05", + "total": 0.0 + }, + { + "date": "8/06", + "total": 0.0 + }, + { + "date": "8/07", + "total": 0.0 + }, + { + "date": "8/08", + "total": 0.0 + }, + { + "date": "8/09", + "total": 0.0 + }, + { + "date": "8/10", + "total": 0.0 + }, + { + "date": "8/11", + "total": 0.0 + }, + { + "date": "8/12", + "total": 0.0 + }, + { + "date": "8/13", + "total": 0.0 + }, + { + "date": "8/14", + "total": 0.0 + }, + { + "date": "8/15", + "total": 0.0 + }, + { + "date": "8/16", + "total": 0.0 + }, + { + "date": "8/17", + "total": 0.0 + }, + { + "date": "8/18", + "total": 0.0 + }, + { + "date": "8/19", + "total": 0.0 + }, + { + "date": "8/20", + "total": 0.0 + }, + { + "date": "8/21", + "total": 0.0 + }, + { + "date": "8/22", + "total": 0.0 + }, + { + "date": "8/23", + "total": 0.0 + }, + { + "date": "8/24", + "total": 0.0 + }, + { + "date": "8/25", + "total": 0.0 + }, + { + "date": "8/26", + "total": 0.0 + }, + { + "date": "8/27", + "total": 0.0 + }, + { + "date": "8/28", + "total": 0.0 + }, + { + "date": "8/29", + "total": 0.0 + }, + { + "date": "8/30", + "total": 0.0 + }, + { + "date": "8/31", + "total": 0.0 + }, + { + "date": "9/01", + "total": 0.0 + }, + { + "date": "9/02", + "total": 0.0 + }, + { + "date": "9/03", + "total": 0.0 + }, + { + "date": "9/04", + "total": 0.0 + }, + { + "date": "9/05", + "total": 0.0 + }, + { + "date": "9/06", + "total": 0.0 + }, + { + "date": "9/07", + "total": 0.0 + }, + { + "date": "9/08", + "total": 0.0 + }, + { + "date": "9/09", + "total": 0.0 + }, + { + "date": "9/10", + "total": 0.0 + }, + { + "date": "9/11", + "total": 0.0 + }, + { + "date": "9/12", + "total": 0.0 + }, + { + "date": "9/13", + "total": 0.0 + }, + { + "date": "9/14", + "total": 0.0 + }, + { + "date": "9/15", + "total": 0.0 + }, + { + "date": "9/16", + "total": 0.0 + }, + { + "date": "9/17", + "total": 0.0 + }, + { + "date": "9/18", + "total": 0.0 + }, + { + "date": "9/19", + "total": 0.0 + }, + { + "date": "9/20", + "total": 0.0 + }, + { + "date": "9/21", + "total": 0.0 + }, + { + "date": "9/22", + "total": 0.0 + }, + { + "date": "9/23", + "total": 0.0 + }, + { + "date": "9/24", + "total": 0.0 + }, + { + "date": "9/25", + "total": 0.0 + }, + { + "date": "9/26", + "total": 0.0 + }, + { + "date": "9/27", + "total": 0.0 + }, + { + "date": "9/28", + "total": 0.0 + }, + { + "date": "9/29", + "total": 0.0 + }, + { + "date": "9/30", + "total": 0.0 + }, + { + "date": "10/01", + "total": 0.0 + }, + { + "date": "10/02", + "total": 0.0 + }, + { + "date": "10/03", + "total": 0.0 + }, + { + "date": "10/04", + "total": 0.0 + }, + { + "date": "10/05", + "total": 0.0 + }, + { + "date": "10/06", + "total": 0.0 + }, + { + "date": "10/07", + "total": 0.0 + }, + { + "date": "10/08", + "total": 0.0 + }, + { + "date": "10/09", + "total": 0.0 + }, + { + "date": "10/10", + "total": 0.0 + }, + { + "date": "10/11", + "total": -287.0 + }, + { + "date": "10/12", + "total": -30.0 + }, + { + "date": "10/13", + "total": -81.0 + }, + { + "date": "10/14", + "total": -107.0 + }, + { + "date": "10/15", + "total": -95.0 + }, + { + "date": "10/16", + "total": -156.0 + }, + { + "date": "10/17", + "total": -81.0 + }, + { + "date": "10/18", + "total": 239.0 + }, + { + "date": "10/19", + "total": -14.0 + }, + { + "date": "10/20", + "total": -35.0 + }, + { + "date": "10/21", + "total": -283.0 + }, + { + "date": "10/22", + "total": -56.0 + }, + { + "date": "10/23", + "total": -91.0 + }, + { + "date": "10/24", + "total": -217.0 + }, + { + "date": "10/25", + "total": -311.0 + }, + { + "date": "10/26", + "total": -168.0 + }, + { + "date": "10/27", + "total": -70.0 + }, + { + "date": "10/28", + "total": -41.0 + }, + { + "date": "10/29", + "total": -45.0 + }, + { + "date": "10/30", + "total": -36.0 + }, + { + "date": "10/31", + "total": 0.0 + }, + { + "date": "11/01", + "total": 0.0 + }, + { + "date": "11/02", + "total": 0.0 + }, + { + "date": "11/03", + "total": 0.0 + }, + { + "date": "11/04", + "total": 0.0 + }, + { + "date": "11/05", + "total": 0.0 + }, + { + "date": "11/06", + "total": 0.0 + }, + { + "date": "11/07", + "total": 0.0 + }, + { + "date": "11/08", + "total": 0.0 + }, + { + "date": "11/09", + "total": 0.0 + }, + { + "date": "11/10", + "total": -425.0 + }, + { + "date": "11/11", + "total": 0.0 + }, + { + "date": "11/12", + "total": 0.0 + }, + { + "date": "11/13", + "total": 0.0 + }, + { + "date": "11/14", + "total": 0.0 + }, + { + "date": "11/15", + "total": 0.0 + }, + { + "date": "11/16", + "total": 0.0 + }, + { + "date": "11/17", + "total": 0.0 + }, + { + "date": "11/18", + "total": 0.0 + }, + { + "date": "11/19", + "total": 0.0 + }, + { + "date": "11/20", + "total": 0.0 + }, + { + "date": "11/21", + "total": 0.0 + }, + { + "date": "11/22", + "total": 0.0 + }, + { + "date": "11/23", + "total": 0.0 + }, + { + "date": "11/24", + "total": 0.0 + }, + { + "date": "11/25", + "total": 0.0 + }, + { + "date": "11/26", + "total": 0.0 + }, + { + "date": "11/27", + "total": 0.0 + }, + { + "date": "11/28", + "total": 0.0 + }, + { + "date": "11/29", + "total": 0.0 + }, + { + "date": "11/30", + "total": 0.0 + }, + { + "date": "12/01", + "total": 0.0 + }, + { + "date": "12/02", + "total": 0.0 + }, + { + "date": "12/03", + "total": 0.0 + }, + { + "date": "12/04", + "total": 0.0 + }, + { + "date": "12/05", + "total": 0.0 + }, + { + "date": "12/06", + "total": 0.0 + }, + { + "date": "12/07", + "total": 0.0 + }, + { + "date": "12/08", + "total": 0.0 + }, + { + "date": "12/09", + "total": 0.0 + }, + { + "date": "12/10", + "total": 0.0 + }, + { + "date": "12/11", + "total": 0.0 + }, + { + "date": "12/12", + "total": 0.0 + }, + { + "date": "12/13", + "total": 0.0 + }, + { + "date": "12/14", + "total": 0.0 + }, + { + "date": "12/15", + "total": 0.0 + }, + { + "date": "12/16", + "total": 0.0 + }, + { + "date": "12/17", + "total": 0.0 + }, + { + "date": "12/18", + "total": 0.0 + }, + { + "date": "12/19", + "total": 0.0 + }, + { + "date": "12/20", + "total": 0.0 + }, + { + "date": "12/21", + "total": 0.0 + }, + { + "date": "12/22", + "total": 0.0 + }, + { + "date": "12/23", + "total": 0.0 + }, + { + "date": "12/24", + "total": 0.0 + }, + { + "date": "12/25", + "total": 0.0 + }, + { + "date": "12/26", + "total": 0.0 + }, + { + "date": "12/27", + "total": 0.0 + }, + { + "date": "12/28", + "total": 0.0 + }, + { + "date": "12/29", + "total": 0.0 + }, + { + "date": "12/30", + "total": 0.0 + }, + { + "date": "12/31", + "total": 0.0 + }, + { + "date": "1/01", + "total": 0.0 + }, + { + "date": "1/02", + "total": 0.0 + }, + { + "date": "1/03", + "total": 0.0 + }, + { + "date": "1/04", + "total": 0.0 + }, + { + "date": "1/05", + "total": 0.0 + }, + { + "date": "1/06", + "total": 0.0 + }, + { + "date": "1/07", + "total": 0.0 + }, + { + "date": "1/08", + "total": 0.0 + }, + { + "date": "1/09", + "total": 0.0 + }, + { + "date": "1/10", + "total": 0.0 + }, + { + "date": "1/11", + "total": 0.0 + }, + { + "date": "1/12", + "total": 0.0 + }, + { + "date": "1/13", + "total": 0.0 + }, + { + "date": "1/14", + "total": 0.0 + }, + { + "date": "1/15", + "total": 0.0 + }, + { + "date": "1/16", + "total": 0.0 + }, + { + "date": "1/17", + "total": 0.0 + }, + { + "date": "1/18", + "total": 0.0 + }, + { + "date": "1/19", + "total": 0.0 + }, + { + "date": "1/20", + "total": 0.0 + }, + { + "date": "1/21", + "total": 0.0 + }, + { + "date": "1/22", + "total": 0.0 + }, + { + "date": "1/23", + "total": 0.0 + }, + { + "date": "1/24", + "total": 0.0 + }, + { + "date": "1/25", + "total": 0.0 + }, + { + "date": "1/26", + "total": 0.0 + }, + { + "date": "1/27", + "total": 0.0 + }, + { + "date": "1/28", + "total": 0.0 + }, + { + "date": "1/29", + "total": 0.0 + }, + { + "date": "1/30", + "total": 0.0 + }, + { + "date": "1/31", + "total": 0.0 + }, + { + "date": "2/01", + "total": 0.0 + }, + { + "date": "2/02", + "total": 0.0 + }, + { + "date": "2/03", + "total": 0.0 + }, + { + "date": "2/04", + "total": 0.0 + }, + { + "date": "2/05", + "total": 0.0 + }, + { + "date": "2/06", + "total": 0.0 + }, + { + "date": "2/07", + "total": 0.0 + }, + { + "date": "2/08", + "total": 0.0 + }, + { + "date": "2/09", + "total": 0.0 + }, + { + "date": "2/10", + "total": 0.0 + }, + { + "date": "2/11", + "total": 0.0 + }, + { + "date": "2/12", + "total": 0.0 + }, + { + "date": "2/13", + "total": 0.0 + }, + { + "date": "2/14", + "total": 0.0 + }, + { + "date": "2/15", + "total": 0.0 + }, + { + "date": "2/16", + "total": -21.0 + }, + { + "date": "2/17", + "total": -22.0 + }, + { + "date": "2/18", + "total": 0.0 + }, + { + "date": "2/19", + "total": -26.0 + }, + { + "date": "2/20", + "total": -405.0 + }, + { + "date": "2/21", + "total": -176.0 + }, + { + "date": "2/22", + "total": -29.0 + }, + { + "date": "2/23", + "total": 1419.0 + }, + { + "date": "2/24", + "total": 1442.0 + }, + { + "date": "2/25", + "total": 1312.0 + }, + { + "date": "2/26", + "total": 1424.0 + }, + { + "date": "2/27", + "total": 1575.0 + }, + { + "date": "2/28", + "total": 985.0 + }, + { + "date": "3/01", + "total": 1510.0 + }, + { + "date": "3/02", + "total": 1272.0 + }, + { + "date": "3/03", + "total": 1409.0 + }, + { + "date": "3/04", + "total": 1168.0 + }, + { + "date": "3/05", + "total": 1581.0 + }, + { + "date": "3/06", + "total": 1416.0 + }, + { + "date": "3/07", + "total": 1312.0 + }, + { + "date": "3/08", + "total": 1387.0 + }, + { + "date": "3/09", + "total": 1390.0 + }, + { + "date": "3/10", + "total": 1489.0 + }, + { + "date": "3/11", + "total": 1451.0 + }, + { + "date": "3/12", + "total": 1454.0 + }, + { + "date": "3/13", + "total": 425.0 + } + ] + }, + "ordinate_axis_min": "-1800.0", + "ordinate_axis_max": "3700.0", + "goal": "1500" +} From 70e8fa0dca61dfa31f42551955eb9b6151eca6fc Mon Sep 17 00:00:00 2001 From: Adam Coddington Date: Sun, 9 Mar 2025 13:16:04 -0500 Subject: [PATCH 33/36] Release 2.1.1; adds fixes for report gathering. --- .bumpversion.cfg | 2 +- myfitnesspal/__init__.py | 2 +- setup.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 0473717..a8744b6 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 2.1.0 +current_version = 2.1.1 commit = True tag = True tag_name = {new_version} diff --git a/myfitnesspal/__init__.py b/myfitnesspal/__init__.py index 59ff7f7..62fd643 100644 --- a/myfitnesspal/__init__.py +++ b/myfitnesspal/__init__.py @@ -1,5 +1,5 @@ from myfitnesspal.client import Client # noqa -__version__ = "2.1.0" +__version__ = "2.1.1" VERSION = tuple(int(v) for v in __version__.split(".")) diff --git a/setup.py b/setup.py index fd288d2..6db36e9 100644 --- a/setup.py +++ b/setup.py @@ -3,12 +3,12 @@ from setuptools import find_packages, setup requirements = [] -with open("requirements.txt", "r") as in_: +with open("requirements.txt") as in_: requirements = in_.readlines() setup( name="myfitnesspal", - version="2.1.0", + version="2.1.1", url="http://github.com/coddingtonbear/python-myfitnesspal/", description="Access health and fitness data stored in Myfitnesspal", author="Adam Coddington", From cffb0fa7c54590502814d87baa3f5f8599de0409 Mon Sep 17 00:00:00 2001 From: Adam Coddington Date: Sun, 9 Mar 2025 13:20:56 -0500 Subject: [PATCH 34/36] Explicitly declare which directory is our package. --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 6db36e9..6299b0d 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,6 @@ import multiprocessing # noqa -from setuptools import find_packages, setup +from setuptools import setup requirements = [] with open("requirements.txt") as in_: @@ -20,7 +20,7 @@ "Programming Language :: Python", "Topic :: Utilities", ], - packages=find_packages(), + packages=["myfitnesspal"], install_requires=requirements, test_suite="nose.collector", tests_require=[ From 539fa51fc39ac9494211a8a19c2b5273473b5b3b Mon Sep 17 00:00:00 2001 From: Adam Coddington Date: Sun, 9 Mar 2025 13:21:07 -0500 Subject: [PATCH 35/36] =?UTF-8?q?Bump=20version:=202.1.1=20=E2=86=92=202.1?= =?UTF-8?q?.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .bumpversion.cfg | 2 +- myfitnesspal/__init__.py | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index a8744b6..862f5ac 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 2.1.1 +current_version = 2.1.2 commit = True tag = True tag_name = {new_version} diff --git a/myfitnesspal/__init__.py b/myfitnesspal/__init__.py index 62fd643..4f53009 100644 --- a/myfitnesspal/__init__.py +++ b/myfitnesspal/__init__.py @@ -1,5 +1,5 @@ from myfitnesspal.client import Client # noqa -__version__ = "2.1.1" +__version__ = "2.1.2" VERSION = tuple(int(v) for v in __version__.split(".")) diff --git a/setup.py b/setup.py index 6299b0d..6062da5 100644 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ setup( name="myfitnesspal", - version="2.1.1", + version="2.1.2", url="http://github.com/coddingtonbear/python-myfitnesspal/", description="Access health and fitness data stored in Myfitnesspal", author="Adam Coddington", From 661ad6e57170a5cf06190384f007681dd8b58353 Mon Sep 17 00:00:00 2001 From: ThisALV Date: Sun, 13 Jul 2025 06:31:43 +0200 Subject: [PATCH 36/36] fix: removes cloudscraper causing 403 --- myfitnesspal/client.py | 3 +-- requirements.txt | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/myfitnesspal/client.py b/myfitnesspal/client.py index 235910c..ec3b3e4 100644 --- a/myfitnesspal/client.py +++ b/myfitnesspal/client.py @@ -12,7 +12,6 @@ from urllib import parse import browser_cookie3 -import cloudscraper import lxml.html import requests from measurement.base import MeasureBase @@ -79,7 +78,7 @@ def __init__( self.unit_aware = unit_aware - self.session = cloudscraper.create_scraper(sess=requests.Session()) + self.session = requests.Session() self.session.headers.update( { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.104 Safari/537.36" diff --git a/requirements.txt b/requirements.txt index 158950c..607ef64 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,5 +5,4 @@ python-dateutil>=2.4,<3 requests>=2.17.0,<3 rich>=12,<13 browser_cookie3>=0.16.1,<1 -cloudscraper>=1.2.71,<2 typing-extensions==4.12.2