Skip to content

Commit

Permalink
functionality for getting reports updated (coddingtonbear#140)
Browse files Browse the repository at this point in the history
* adding files back

* black formatting, using verb in method name
  • Loading branch information
AlanGrenfell committed Apr 29, 2022
1 parent 52446ce commit 4c402a4
Show file tree
Hide file tree
Showing 7 changed files with 2,980 additions and 9 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,6 @@ share/*
pip-selfcheck.json
env/*
.vscode/*
.mypy_cache/*
.mypy_cache/*
.idea/
venv/
1 change: 1 addition & 0 deletions docs/source/how_to/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ How-to Guides
exercises
measurements
food_search
reports
50 changes: 50 additions & 0 deletions docs/source/how_to/reports.rst
Original file line number Diff line number Diff line change
@@ -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 <https://www.myfitnesspal.com/reports>`_ page. When specifying a date range, the order of the date arguments does not matter.
85 changes: 78 additions & 7 deletions myfitnesspal/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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())
Expand Down Expand Up @@ -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}"

Expand Down
9 changes: 8 additions & 1 deletion tests/base.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import os
import unittest
import json

import lxml.html

Expand All @@ -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)
Loading

0 comments on commit 4c402a4

Please sign in to comment.