cleanup; testable colors.py (#16)
authorShane Jaroch <chown_tee@proton.me>
Thu, 29 Sep 2022 14:32:27 +0000 (10:32 -0400)
committerGitHub <noreply@github.com>
Thu, 29 Sep 2022 14:32:27 +0000 (10:32 -0400)
* tidy calculate service & makefile

* split eqs onto multiple lines

* more testable / idiomatic colors.py (and lintable)

* remove yamllint

.yamllint.yml [deleted file]
Makefile
ntclient/__init__.py
ntclient/services/calculate.py
ntclient/utils/colors.py
requirements-lint.txt
tests/services/test_recipe.py
tests/test_cli.py

diff --git a/.yamllint.yml b/.yamllint.yml
deleted file mode 100644 (file)
index 757ef82..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
----
-rules:
-  braces:
-    min-spaces-inside: 0
-    max-spaces-inside: 1
-  brackets:
-    min-spaces-inside: 0
-    max-spaces-inside: 0
-  colons:
-    max-spaces-before: 0
-    max-spaces-after: 1
-  commas:
-    max-spaces-before: 0
-    min-spaces-after: 1
-    max-spaces-after: 1
-  comments:
-    level: warning
-    require-starting-space: yes
-    min-spaces-from-content: 1
-  comments-indentation:
-    level: warning
-  document-end: disable
-  document-start:
-    level: warning
-    present: yes
-  empty-lines:
-    max: 2
-    max-start: 0
-    max-end: 0
-  hyphens:
-    max-spaces-after: 1
-  indentation:
-    spaces: 2
-    indent-sequences: yes
-    check-multi-line-strings: no
-  key-duplicates: {}
-  line-length:
-    level: warning
-    max: 80
-    allow-non-breakable-words: yes
-  new-line-at-end-of-file: { level: error }
-  new-lines:
-    type: unix
-  trailing-spaces: {}
index fb6fa9ccbe4bb0883a6a40202dc346b4293c1cb2..0e30fdcc75709488e62b09598574b2316be47a73 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -31,6 +31,7 @@ _venv:
        [ "$(PYTHON)" = "$(PWD)/.venv/bin/python" ] || [ "$(PYTHON)" = "$(PWD)/.venv/Scripts/python" ]
 
 
+
 # ---------------------------------------
 # Install requirements
 # ---------------------------------------
@@ -53,6 +54,7 @@ REQ_LINT := requirements-lint.txt
 REQ_TEST := requirements-test.txt
 REQ_TEST_OLD := requirements-test-old.txt
 
+
 PIP_OPT_ARGS ?=
 
 .PHONY: _deps
@@ -80,8 +82,6 @@ format:
 
 
 LINT_LOCS := ntclient/ tests/ setup.py
-YAML_LOCS := ntclient/ntsqlite/.*.yml .github/workflows/ .*.yml
-# NOTE: yamllint       ntclient/ntsqlite/.travis.yml ? (submodule)
 # NOTE: doc8           ntclient/ntsqlite/README.rst  ? (submodule)
 .PHONY: _lint
 _lint:
@@ -92,8 +92,6 @@ _lint:
        black --check $(LINT_LOCS)
        # lint RST (last param is search term, NOT ignore)
        doc8 --quiet *.rst ntclient/ntsqlite/*.rst
-       # lint YAML
-       yamllint $(YAML_LOCS)
        # lint Python
        bandit -q -c .banditrc -r $(LINT_LOCS)
        mypy $(LINT_LOCS)
@@ -115,6 +113,7 @@ _test:
 test: _venv _test      ## Run CLI unittests
 
 
+
 # ---------------------------------------
 # SQLite submodule: nt-sqlite
 # ---------------------------------------
@@ -128,6 +127,7 @@ ntsqlite/build:
 # TODO: nt-sqlite/test
 
 
+
 # ---------------------------------------
 # Python build & install
 # ---------------------------------------
@@ -150,6 +150,7 @@ install:    ## pip install nutra
        nutra -v
 
 
+
 # ---------------------------------------
 # Clean
 # ---------------------------------------
@@ -160,6 +161,7 @@ clean:      ## Clean up __pycache__ and leftover bits
        rm -rf build/
        rm -rf nutra.egg-info/
        rm -rf .pytest_cache/ .mypy_cache/
+       # Recursively find & remove
        find ntclient/ tests/ \
        -name \
        __pycache__ \
@@ -170,6 +172,7 @@ clean:      ## Clean up __pycache__ and leftover bits
        | xargs rm -rf
 
 
+
 # ---------------------------------------
 # Extras
 # ---------------------------------------
index 204a9e767ea9f32e6bd4342670020c0b986a5c84..df188349f1805f9641a777ebea7721f652fc4341 100644 (file)
@@ -19,7 +19,7 @@ from ntclient.utils import colors
 
 # Package info
 __title__ = "nutra"
-__version__ = "0.2.6"
+__version__ = "0.2.7.dev0"
 __author__ = "Shane Jaroch"
 __email__ = "chown_tee@proton.me"
 __license__ = "GPL v3"
@@ -102,6 +102,7 @@ class RdaColors(Enum):
     COLOR_RED = colors.COLOR_RED
 
 
+# pylint: disable=too-few-public-methods,too-many-instance-attributes
 class _CliConfig:
     """Mutable global store for configuration values"""
 
index 15a636b1624d376c1e52dad8a65f492d8900eef3..5ff1cae3335d2a0009517e2233d768ea6a1575b8 100644 (file)
@@ -15,6 +15,7 @@ from ntclient import Gender
 # 1 rep max
 # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
+# The only ones displayed in the result table
 common_n_reps = (1, 2, 3, 5, 6, 8, 10, 12, 15, 20)
 
 
@@ -28,16 +29,20 @@ def orm_epley(weight: float, reps: float) -> dict:
     Source: https://workoutable.com/one-rep-max-calculator/
     """
 
-    def one_rm() -> float:
-        _un_rounded_result = weight * (1 + (reps - 1) / 30)
-        return round(_un_rounded_result, 1)
+    # Compute the 1-rep max
+    one_rm = round(
+        weight * (1 + (reps - 1) / 30),
+        1,
+    )
 
-    def weight_max_reps(target_reps: float) -> float:
-        _un_rounded_result = one_rm() * 30 / (29 + target_reps)
-        return round(_un_rounded_result, 1)
+    def max_weight(target_reps: float) -> float:
+        """Used to calculate max weight for a given rep count, e.g. 205x3 or 135x15"""
+        return round(
+            one_rm * 30 / (29 + target_reps),
+            1,
+        )
 
-    maxes = {n_reps: weight_max_reps(n_reps) for n_reps in common_n_reps}
-    return maxes
+    return {n_reps: max_weight(n_reps) for n_reps in common_n_reps}
 
 
 def orm_brzycki(weight: float, reps: float) -> dict:
@@ -48,24 +53,27 @@ def orm_brzycki(weight: float, reps: float) -> dict:
     1 RM = weight * 36 / (37 - reps)
 
     NOTE: Adjusted formula is below, with quadratic term.
+      This makes it more accurate in the 12-20 rep range.
 
-    1 RM = weight * 36 / (37 - reps + 0.005 * reps^2)
+    1 RM = weight * 36 / (36.995 - reps + 0.005 * reps^2)
 
     Source: https://workoutable.com/one-rep-max-calculator/
     """
 
-    def _one_rm() -> float:
-        _un_rounded_result = weight * 36 / (37 - reps + 0.005 * reps**2)
-        return round(_un_rounded_result, 1)
-
-    one_rm = _one_rm()
+    # Compute the 1-rep max
+    one_rm = round(
+        weight * 36 / (36.995 - reps + 0.005 * reps**2),
+        1,
+    )
 
-    def weight_max_reps(target_reps: float) -> float:
-        _un_rounded_result = one_rm * (37 - target_reps + 0.005 * target_reps**2) / 36
-        return round(_un_rounded_result, 1)
+    def max_weight(target_reps: float) -> float:
+        """Used to calculate max weight for a given rep count, e.g. 205x3 or 135x15"""
+        return round(
+            one_rm * (36.995 - target_reps + 0.005 * target_reps**2) / 36,
+            1,
+        )
 
-    maxes = {n_reps: weight_max_reps(n_reps) for n_reps in common_n_reps}
-    return maxes
+    return {n_reps: max_weight(n_reps) for n_reps in common_n_reps}
 
 
 def orm_dos_remedios(weight: float, reps: int) -> dict:
@@ -73,13 +81,14 @@ def orm_dos_remedios(weight: float, reps: int) -> dict:
     Returns dict {n_reps: max_weight, ...}
         for n_reps: (1, 2, 3, 5, 6, 8, 10, 12, 15, 20)
 
-    Or an {"errMsg": "INVALID_RANGE", ...}
+    This is a manual data set, curated by dos Remedios;
+    the added values are provided by Mathematica's spline interpolation.
 
     Source:
         https://www.peterrobertscoaching.com/blog/the-best-way-to-calculate-1-rep-max
     """
 
-    _common_n_reps = {
+    _max_rep_ratios = {
         1: 1,
         2: 0.92,
         3: 0.9,
@@ -102,21 +111,21 @@ def orm_dos_remedios(weight: float, reps: int) -> dict:
         20: 0.55,  # NOTE: I added this, 20 reps is NOT in the original equation.
     }
 
-    def _one_rm() -> float:
-        _multiplier = _common_n_reps[reps]
-        _un_rounded_result = weight / _multiplier
-        return round(_un_rounded_result, 1)
-
     # Compute the 1-rep max
-    one_rm = _one_rm()
+    # NOTE: this should be guaranteed by arg-parse to be an integer, and 0 < n <= 20
+    one_rm = round(
+        weight / _max_rep_ratios[reps],
+        1,
+    )
 
     def max_weight(target_reps: int) -> float:
-        """Used to calculate max weight based on actual reps, e.g. 5 or 12"""
-        _multiplier = _common_n_reps[target_reps]
-        _un_rounded_result = one_rm * _multiplier
-        return round(_un_rounded_result, 1)
+        """Used to calculate max weight for a given rep count, e.g. 205x3 or 135x15"""
+        return round(
+            one_rm * _max_rep_ratios[target_reps],
+            1,
+        )
 
-    return {n_reps: max_weight(n_reps) for n_reps in _common_n_reps}
+    return {n_reps: max_weight(n_reps) for n_reps in common_n_reps}
 
 
 # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
index ce9a81fa69e35a02b8d39b0c26be110974af5a9b..d319e5e8addf7bce5ba27a7658ca57efbd094887 100644 (file)
@@ -7,52 +7,65 @@ Created on Mon Aug  8 14:35:43 2022
 Allows the safe avoidance of ImportError on non-colorama capable systems.
 """
 
-try:
-    from colorama import Fore, Style
-    from colorama import init as colorama_init
+# pylint: disable=invalid-name
+
+
+# pylint: disable=too-few-public-methods
+class _STYLE:
+    def __init__(self) -> None:
+        self.BRIGHT = str()
+        self.DIM = str()
+        self.RESET_ALL = str()
 
-    # Made it this far, so run the init function (which is needed on Windows)
-    colorama_init()
 
-    # Styles
-    STYLE_BRIGHT = Style.BRIGHT
-    STYLE_DIM = Style.DIM
-    STYLE_RESET_ALL = Style.RESET_ALL
+# pylint: disable=too-few-public-methods,too-many-instance-attributes
+class _FORE:
+    def __init__(self) -> None:
+        self.WARN = str()
+        self.CRIT = str()
+        self.OVER = str()
+        self.DEFAULT = str()
 
-    # Colors
-    COLOR_WARN = Fore.YELLOW
-    COLOR_CRIT = Style.DIM + Fore.RED
-    COLOR_OVER = Style.DIM + Fore.MAGENTA
+        self.YELLOW = str()
+        self.BLUE = str()
+        self.RED = str()
+        self.MAGENTA = str()
 
-    COLOR_DEFAULT = Fore.CYAN
+        self.GREEN = str()
+        self.CYAN = str()
 
-    # Used in macro bars
-    COLOR_YELLOW = Fore.YELLOW
-    COLOR_BLUE = Fore.BLUE
-    COLOR_RED = Fore.RED
 
-    # Used by `tree.py` utility
-    COLOR_GREEN = Fore.GREEN
-    COLOR_CYAN = Fore.CYAN
+_Style = _STYLE()
+_Fore = _FORE()
+
+try:
+    from colorama import Fore, Style
+    from colorama import init as colorama_init
+
+    # Made it this far, so run the init function (which is needed on Windows)
+    colorama_init()
 
 except ImportError:
-    # These will all just be empty strings if colorama isn't installed
+    Fore, Style = _Fore, _Style  # type: ignore
 
-    # Styles
-    STYLE_BRIGHT = str()
-    STYLE_DIM = str()
-    STYLE_RESET_ALL = str()
 
-    # Colors
-    COLOR_WARN = str()
-    COLOR_CRIT = str()
-    COLOR_OVER = str()
+# NOTE: These will all just be empty strings if colorama isn't installed
+# Styles
+STYLE_BRIGHT = Style.BRIGHT
+STYLE_DIM = Style.DIM
+STYLE_RESET_ALL = Style.RESET_ALL
 
-    COLOR_DEFAULT = str()
+# Colors for Progress / RDA bar
+COLOR_WARN = Fore.YELLOW
+COLOR_CRIT = Style.DIM + Fore.RED
+COLOR_OVER = Style.DIM + Fore.MAGENTA
+COLOR_DEFAULT = Fore.CYAN
 
-    COLOR_YELLOW = str()
-    COLOR_BLUE = str()
-    COLOR_RED = str()
+# Used in macro bars
+COLOR_YELLOW = Fore.YELLOW
+COLOR_BLUE = Fore.BLUE
+COLOR_RED = Fore.RED
 
-    COLOR_GREEN = str()
-    COLOR_CYAN = str()
+# Used by `tree.py` utility
+COLOR_GREEN = Fore.GREEN
+COLOR_CYAN = Fore.CYAN
index e4826db6e12f0b0a9a131243eebe47f02cea0d30..4120b78e5d0b533f1b359f8d66546ad6b41354ae 100644 (file)
@@ -9,4 +9,3 @@ types-colorama>=0.4.15
 types-psycopg2>=2.9.18
 types-setuptools>=57.4.0
 types-tabulate>=0.8.11
-yamllint>=1.27
index b277857b8e3ac9f084a5306deb78d475bba615f4..f7985f5098124698bd5547a9297ff814f66c6944 100644 (file)
@@ -41,7 +41,7 @@ class TestRecipe(unittest.TestCase):
             r.recipe_overview("-12345-FAKE-PATH-")
 
     def test_recipe_overview_might_succeed_for_maybe_existing_id(self):
-        """Tries check for existing ID, but only can if the user initialized"""
+        """Tries 'check for existing ID', but only can if the user initialized"""
         exit_code, _ = r.recipe_overview(
             os.path.join(RECIPE_STOCK, "dinner", "burrito-bowl.csv")
         )
index 325b2e8bccd7e6984cfa821c23f06ffbf4f000fb..c46ee849cbc9712dbb2beafb7797543354adbe34 100644 (file)
@@ -162,7 +162,11 @@ class TestCli(unittest.TestCase):
         assert len(servings_rows[0]) == 1
 
     def test_410_nt_argparser_funcs(self):
-        """Tests nt functions in argparser.funcs (to varying degrees each)"""
+        """
+        Tests nt functions in argparser.funcs (to varying degrees each)
+
+        TODO: split this up... separate argparser tests; then test missing service lines
+        """
         # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
         # Day
         # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -258,7 +262,6 @@ class TestCli(unittest.TestCase):
         assert result["sevenSite"] == 9.93
 
         # Female test
-        # TODO: better values, and don't require hip above (it's 0)
         args = arg_parser.parse_args(
             args="calc bf -F -a 29 -ht 178 -w 70 -hip 100 -n 35 "
             "15 23 19 14 11 10 9".split()
@@ -272,8 +275,25 @@ class TestCli(unittest.TestCase):
         args = arg_parser.parse_args(args="calc lbl 179 0.1 17.2 21.5".split())
         code, result = args.func(args)
         assert code == 0
-        # NOTE: wip
-        print(result)
+        assert result["berkhan"] == {
+            "condition": "Contest shape (5-6%)",
+            "weight": "169.8 ~ 178.6 lbs",
+        }
+        assert result["helms"] == {
+            "condition": "10.0% body fat",
+            "weight": "172.7 ~ 192.3 lbs",
+        }
+        assert result["casey"] == {
+            "condition": "10.0% body fat",
+            "weight": "196.3 lbs",
+            "lbm": "176.7 lbs",
+            "chest": 46.39,
+            "arm": 16.86,
+            "forearm": 13.49,
+            "neck": 16.45,
+            "thigh": 24.46,
+            "calf": 16.4,
+        }
 
     def test_415_invalid_path_day_throws_error(self):
         """Ensures invalid path throws exception in `day` subcommand"""