From: Shane Jaroch Date: Thu, 29 Sep 2022 14:32:27 +0000 (-0400) Subject: cleanup; testable colors.py (#16) X-Git-Tag: v0.2.7~3 X-Git-Url: https://git.nutra.tk/v1?a=commitdiff_plain;h=a3d3cdd672b5f225ceedd431032322dacaecbcbd;p=nutratech%2Fcli.git cleanup; testable colors.py (#16) * tidy calculate service & makefile * split eqs onto multiple lines * more testable / idiomatic colors.py (and lintable) * remove yamllint --- diff --git a/.yamllint.yml b/.yamllint.yml deleted file mode 100644 index 757ef82..0000000 --- a/.yamllint.yml +++ /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: {} diff --git a/Makefile b/Makefile index fb6fa9c..0e30fdc 100644 --- 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 # --------------------------------------- diff --git a/ntclient/__init__.py b/ntclient/__init__.py index 204a9e7..df18834 100644 --- a/ntclient/__init__.py +++ b/ntclient/__init__.py @@ -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""" diff --git a/ntclient/services/calculate.py b/ntclient/services/calculate.py index 15a636b..5ff1cae 100644 --- a/ntclient/services/calculate.py +++ b/ntclient/services/calculate.py @@ -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} # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/ntclient/utils/colors.py b/ntclient/utils/colors.py index ce9a81f..d319e5e 100644 --- a/ntclient/utils/colors.py +++ b/ntclient/utils/colors.py @@ -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 diff --git a/requirements-lint.txt b/requirements-lint.txt index e4826db..4120b78 100644 --- a/requirements-lint.txt +++ b/requirements-lint.txt @@ -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 diff --git a/tests/services/test_recipe.py b/tests/services/test_recipe.py index b277857..f7985f5 100644 --- a/tests/services/test_recipe.py +++ b/tests/services/test_recipe.py @@ -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") ) diff --git a/tests/test_cli.py b/tests/test_cli.py index 325b2e8..c46ee84 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -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"""