Dev (#19)
authorShane Jaroch <chown_tee@proton.me>
Sat, 8 Apr 2023 14:24:42 +0000 (10:24 -0400)
committerGitHub <noreply@github.com>
Sat, 8 Apr 2023 14:24:42 +0000 (10:24 -0400)
40 files changed:
.banditrc
.envrc
.gitattributes
.github/workflows/install-linux.yml
.github/workflows/install-win32.yml [new file with mode: 0644]
.github/workflows/lint.yml
.github/workflows/test.yml
.gitignore
.mailmap
.pylintrc
CHANGELOG.rst
MANIFEST.in
Makefile
README.rst
ntclient/__init__.py
ntclient/__main__.py
ntclient/argparser/__init__.py
ntclient/argparser/funcs.py
ntclient/models/__init__.py
ntclient/ntsqlite
ntclient/persistence/sql/__init__.py
ntclient/persistence/sql/usda/__init__.py
ntclient/persistence/sql/usda/funcs.py
ntclient/services/analyze.py
ntclient/services/calculate.py
ntclient/services/recipe/csv_utils.py
ntclient/services/usda.py
ntclient/utils/__init__.py
ntclient/utils/colors.py
ntclient/utils/tree.py
requirements-lint.txt
requirements-old.txt
requirements-optional.txt
requirements-test.txt
requirements.txt
scripts/n [new file with mode: 0755]
setup.cfg
setup.py
tests/resources/day/dog-simple.csv
tests/test_cli.py

index f5fba94f3093a1aeed25e63a88c12d83de826436..179faa8fd9b6331081a23e57d3e433ede5fd262a 100644 (file)
--- a/.banditrc
+++ b/.banditrc
@@ -1,3 +1,2 @@
 assert_used:  # B101
     skips: ["*/test_*.py"]
-
diff --git a/.envrc b/.envrc
index 1c21d048064326a73b79d9781533985679ea14ef..b694c1941653dd38b2dfaabf465f81a1efb3da24 100644 (file)
--- a/.envrc
+++ b/.envrc
@@ -1,2 +1,2 @@
-source .venv/bin/activate
+source .venv/bin/activate || source .venv/Scripts/activate
 unset PS1
index 28924fe2c26bb5b8cf94e7850fdca15d0ceef75b..6e5b04ba9f6f31e0954aeaeefb1fc1aba0c191ee 100644 (file)
@@ -1 +1,5 @@
+# Line ending configuration
+* text eol=lf
+
+# GitHub Linguist (language detection overrides)
 *.wls linguist-language=Mathematica
index 6d283a69bc60d7fabcce6527d09f1016cb7be896..ca22d1cf6d08508f79e88db7c8f2962df20bbf69 100644 (file)
@@ -2,18 +2,20 @@
 name: install-linux
 
 "on":
-  push: {}
+  push:
+    branches:
+      - "**"
 
 permissions:
   contents: read
 
 jobs:
   python:
-    runs-on: ubuntu-18.04
+    runs-on: [ubuntu-20.04]
 
     strategy:
       matrix:
-        python-version: ["3.4", "3.6", "3.8", "3.10"]
+        python-version: ["3.5", "3.8", "3.10"]
 
     steps:
       - name: Checkout
@@ -21,16 +23,25 @@ jobs:
         with:
           submodules: recursive
 
-      - name: Set up Python ${{ matrix.python-version }}
+      - name: Set up Python ${{ matrix.python-version }} & Restore Cache
         uses: actions/setup-python@v4
         with:
           python-version: ${{ matrix.python-version }}
+          # update-environment: false
+          # NOTE: fails on Python 3.6 and 3.4 with the pip error,
+          #   ERROR: unknown command "cache" - maybe you meant "check"
+          # cache: "pip" # caching pip dependencies
+          # cache-dependency-path: "**/requirements*.txt"
 
+      # NOTE: see above NOTE, we are still using deprecated cache restore
       - name: Reload Cache / pip
         uses: actions/cache@v3
         with:
           path: ~/.cache/pip
-          key: ${{ runner.os }}-${{ matrix.python-version }}-pip-${{ hashFiles('**/requirements*.txt') }}
+          # NOTE: only cares about base requirements.txt
+          # yamllint disable rule:line-length
+          key: ${{ runner.os }}-${{ matrix.python-version }}-pip-${{ hashFiles('**/requirements.txt') }}
+          # yamllint enable rule:line-length
           restore-keys: |
             ${{ runner.os }}-${{ matrix.python-version }}-pip-
 
@@ -42,5 +53,5 @@ jobs:
       - name: Basic Tests / CLI / Integration
         run: |
           n -v
-          nutra -d recipe init
+          nutra -d recipe init -f
           nutra --no-pager recipe
diff --git a/.github/workflows/install-win32.yml b/.github/workflows/install-win32.yml
new file mode 100644 (file)
index 0000000..9323928
--- /dev/null
@@ -0,0 +1,41 @@
+---
+name: install-win32
+
+"on":
+  push:
+    branches:
+      # TODO: only run on master, it's slow (or just make unrequired check?)
+      - "**"
+
+jobs:
+  windows-latest:
+    runs-on: [windows-latest]
+
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v3
+        with:
+          submodules: recursive
+
+      - name: Reload Cache / pip
+        uses: actions/cache@v3
+        with:
+          path: ~\AppData\Local\pip\Cache
+          # NOTE: only cares about base requirements.txt
+          key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
+          restore-keys: |
+            ${{ runner.os }}-pip-
+
+      - name: Install
+        run: make install
+
+      - name: Basic Tests / CLI / Integration
+        run: |
+          n -v
+          nutra -d init -y
+          nutra --no-pager nt
+          nutra --no-pager sort -c 789
+          nutra --no-pager search ultraviolet mushrooms
+          nutra --no-pager anl 9050
+          nutra --no-pager recipe
+          nutra day tests/resources/day/human-test.csv
index 0776699d4076904138ed6bdae9e3fd2025dd3c98..3cc282317a0d336605a48a523f8d2b3965e6973b 100644 (file)
@@ -9,7 +9,10 @@ permissions:
 
 jobs:
   lint:
-    runs-on: ubuntu-latest
+    runs-on: [ubuntu-latest]
+
+    env:
+      SKIP_VENV: 1
 
     steps:
       - name: Checkout
@@ -17,13 +20,16 @@ jobs:
         with:
           submodules: recursive
 
+      - name: Fetch master (for incremental diff, lint filter mask)
+        run: git fetch origin master
+
       - name: Reload Cache / pip
-        uses: actions/cache@v3
+        uses: actions/setup-python@v4
         with:
-          path: ~/.cache/pip
-          key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements*.txt') }}
-          restore-keys: |
-            ${{ runner.os }}-pip-
+          python-version: 3
+          cache: "pip" # caching pip dependencies
+          cache-dependency-path: "**/requirements*.txt"
+          update-environment: false
 
       - name: Install requirements
         run: |
@@ -34,4 +40,4 @@ jobs:
           pip install -r requirements-test.txt
 
       - name: Lint
-        run: make _lint
+        run: make lint
index 1ce797d209c9b89f59c7da0a780ee93e09d4e276..9372dd2324c07d03bed4c3ff7fbc64636265d67e 100644 (file)
@@ -1,15 +1,14 @@
 ---
 name: test
 
-"on":
-  push: {}
+"on": push
 
 permissions:
   contents: read
 
 jobs:
   test:
-    runs-on: [self-hosted, dev-east]
+    runs-on: [self-hosted, dev]
 
     steps:
       - name: Checkout
@@ -17,18 +16,30 @@ jobs:
         with:
           submodules: recursive
 
-      - name: Cloc
+      - name: Count lines of code
         run: make extras/cloc
 
-      - name: Install requirements
-        run: |
-          /usr/bin/python3 -m pip install -r requirements.txt
-          /usr/bin/python3 -m pip install -r requirements-test.txt
+      # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+      # Install requirements
+      # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+      - name: Initialize (venv)
+        run: make init
 
+      - name: Install requirements
+        run: >
+          source .venv/bin/activate &&
+          python -m pip install
+          coveralls
+          -r requirements.txt
+          -r requirements-test.txt
+
+      # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+      # Test
+      # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
       - name: Test
-        run: PATH=~/.local/bin:$PATH make _test
+        run: source .venv/bin/activate && make test
 
       - name: Submit coverage report / coveralls
         env:
           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-        run: PATH=~/.local/bin:$PATH coveralls --service=github
+        run: /usr/bin/python3 -m coveralls --service=github
index 7a30ff7ba958cfc10e788df58133b9fde5102f4a..c362bbe538c2e8d5324d9dfd019503441beaa100 100644 (file)
@@ -11,7 +11,7 @@
 __sha__.py
 *.sqlite
 .nutra*
-
+.env*
 
 # Temporary
 docs*
index a8d540eda32fd826888e754577379d6a596ff59c..5880a27b590dfaaac399077ca3aa757e275079f1 100644 (file)
--- a/.mailmap
+++ b/.mailmap
@@ -8,4 +8,3 @@ Shane <chown_tee@proton.me> <mathmuncher11@gmail.com>
 Shane <chown_tee@proton.me> <nutratracker@protonmail.com>
 Shane <chown_tee@proton.me> <shane.jaroch@centurylink.com>
 Shane <chown_tee@proton.me> <sjj.lumen@gmail.com>
-
index bb0f16ac24c26378fd353363f879fc13b4e3dfdb..a92217f093711f6a0604475b576a6ae38496191b 100644 (file)
--- a/.pylintrc
+++ b/.pylintrc
@@ -1,6 +1,6 @@
 [MASTER]
 
-fail-under=9.75
+fail-under=9.93
 
 
 [MESSAGES CONTROL]
index 41bc5c17259835e739ca95802675fc673ec5ca70..23c6b48395606a962fa44987cec398c237533daf 100644 (file)
@@ -9,6 +9,35 @@ and this project adheres to `Semantic Versioning <https://semver.org/spec/v2.0.0
 
 
 
+[0.2.8] - Unreleased
+########################################################################
+
+
+
+[0.2.7] - 2023-03-27
+########################################################################
+
+Changed
+~~~~~~~
+
+- Tweaked dependency versions to work with most older stuff (wip still testing)
+- ``n`` vs. ``nutra`` different strategies to work on both Windows/Unix
+- Install Levenshtein speedup with ``pip install --pre nutra[extras]``
+
+Fixed
+~~~~~
+
+- Added backs scripts to fix ``argcomplete`` in different edge cases
+- A couple spots where unsorted dictionaries were causing bugs on older python
+- Enhanced some developer experience things (Makefile, GitHub CI/workflows)
+
+Removed
+~~~~~~~
+
+- PyPI builds exclude ``tests/`` and unneeded ``requirements-*.txt files``
+
+
+
 [0.2.6] - 2022-08-08
 ########################################################################
 
index b9fc9b49177610983390861ee1c4bab0b4a9aa0b..af1949adade25f80e8bc8e190dca776ccfdea852 100644 (file)
@@ -1,5 +1,6 @@
 include requirements.txt
-include requirements-*.txt
+include requirements-old.txt
+include requirements-optional.txt
 
 include ntclient/ntsqlite/sql/*.sql
 include ntclient/ntsqlite/sql/data/*.csv
index 0e30fdcc75709488e62b09598574b2316be47a73..8d94520c23a5f8f5debaae68741fdb162f268590 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -5,30 +5,31 @@ SHELL=/bin/bash
 # NOTE: must put a <TAB> character and two pound "\t##" to show up in this list.  Keep it brief! IGNORE_ME
 .PHONY: _help
 _help:
-       @grep -h "##" $(MAKEFILE_LIST) | grep -v IGNORE_ME | sed -e 's/##//' | column -t -s $$'\t'
+       @printf "\nUsage: make <command>, valid commands:\n\n"
+       @grep "##" $(MAKEFILE_LIST) | grep -v IGNORE_ME | sed -e 's/##//' | column -t -s $$'\t'
 
 
 
 # ---------------------------------------
 # init & venv
 # ---------------------------------------
+
 .PHONY: init
 init:  ## Set up a Python virtual environment
        git submodule update --init
-       if [ ! -d .venv ]; then \
-               $(PY_SYS_INTERPRETER) -m venv .venv; \
-       fi
+       rm -rf .venv
+       ${PY_SYS_INTERPRETER} -m venv .venv
+       - if [ -z "${CI}" ]; then ${PY_SYS_INTERPRETER} -m venv --upgrade-deps .venv; fi
        - direnv allow
-       @echo -e "\r\nNOTE: activate venv, and run 'make deps'\r\n"
-       @echo -e "HINT: run 'source .venv/bin/activate'"
-
 
+# include .env
+SKIP_VENV ?=
 PYTHON ?= $(shell which python)
 PWD ?= $(shell pwd)
 .PHONY: _venv
 _venv:
        # Test to enforce venv usage across important make targets
-       [ "$(PYTHON)" = "$(PWD)/.venv/bin/python" ] || [ "$(PYTHON)" = "$(PWD)/.venv/Scripts/python" ]
+       [ "${SKIP_VENV}" ] || [ "${PYTHON}" = "${PWD}/.venv/bin/python" ]
 
 
 
@@ -46,7 +47,6 @@ ifeq ($(PY_SYS_INTERPRETER),)
 endif
 
 PY_VIRTUAL_INTERPRETER ?= python
-
 PIP ?= $(PY_VIRTUAL_INTERPRETER) -m pip
 
 REQ_OPT := requirements-optional.txt
@@ -54,20 +54,15 @@ REQ_LINT := requirements-lint.txt
 REQ_TEST := requirements-test.txt
 REQ_TEST_OLD := requirements-test-old.txt
 
-
-PIP_OPT_ARGS ?=
-
-.PHONY: _deps
-_deps:
-       $(PIP) install wheel
-       $(PIP) install $(PIP_OPT_ARGS) -r requirements.txt
-       - $(PIP) install $(PIP_OPT_ARGS) -r $(REQ_OPT)
-       - $(PIP) install $(PIP_OPT_ARGS) -r $(REQ_LINT)
-       - $(PIP) install $(PIP_OPT_ARGS) -r $(REQ_TEST) || \
-       echo "TEST REQs failed. Try with '--user' flag, or old version: $(PIP) install -r $(REQ_TEST_OLD)"
+PIP_OPT_ARGS ?= $(shell if [ "$(SKIP_VENV)" ]; then echo "--user"; fi)
 
 .PHONY: deps
-deps: _venv _deps      ## Install requirements
+deps: _venv    ## Install requirements
+       ${PIP} install wheel
+       ${PIP} install ${PIP_OPT_ARGS} -r requirements.txt
+       - ${PIP} install ${PIP_OPT_ARGS} -r ${REQ_OPT}
+       - ${PIP} install ${PIP_OPT_ARGS} -r ${REQ_LINT}
+       ${PIP} install ${PIP_OPT_ARGS} -r ${REQ_TEST} || ${PIP} install ${PIP_OPT_ARGS} -r ${REQ_TEST_OLD}
 
 
 # ---------------------------------------
@@ -75,42 +70,44 @@ deps: _venv _deps   ## Install requirements
 # ---------------------------------------
 
 .PHONY: format
-format:
-       isort $(LINT_LOCS)
-       autopep8 --recursive --in-place --max-line-length 88 $(LINT_LOCS)
-       black $(LINT_LOCS)
+format: _venv  ## Format with isort & black
+       if [ "${CHANGED_FILES_PY_FLAG}" ]; then isort ${CHANGED_FILES_PY} ; fi
+       if [ "${CHANGED_FILES_PY_FLAG}" ]; then black ${CHANGED_FILES_PY} ; fi
 
 
 LINT_LOCS := ntclient/ tests/ setup.py
-# NOTE: doc8           ntclient/ntsqlite/README.rst  ? (submodule)
-.PHONY: _lint
-_lint:
+CHANGED_FILES_RST ?= $(shell git diff origin/master --name-only --diff-filter=MACRU \*.rst)
+CHANGED_FILES_PY ?= $(shell git diff origin/master --name-only --diff-filter=MACRU \*.py)
+CHANGED_FILES_PY_FLAG ?= $(shell if [ "$(CHANGED_FILES_PY)" ]; then echo 1; fi)
+
+.PHONY: lint
+lint: _venv    ## Lint code and documentation
+       # lint RST
+       if [ "${CHANGED_FILES_RST}" ]; then doc8 --quiet ${CHANGED_FILES_RST}; fi
        # check formatting: Python
-       pycodestyle --statistics $(LINT_LOCS)
-       autopep8 --recursive --diff --max-line-length 88 --exit-code $(LINT_LOCS)
-       isort --diff --check $(LINT_LOCS)
-       black --check $(LINT_LOCS)
-       # lint RST (last param is search term, NOT ignore)
-       doc8 --quiet *.rst ntclient/ntsqlite/*.rst
+       if [ "${CHANGED_FILES_PY_FLAG}" ]; then isort --diff --check ${CHANGED_FILES_PY} ; fi
+       if [ "${CHANGED_FILES_PY_FLAG}" ]; then black --check ${CHANGED_FILES_PY} ; fi
        # lint Python
-       bandit -q -c .banditrc -r $(LINT_LOCS)
-       mypy $(LINT_LOCS)
-       flake8 $(LINT_LOCS)
-       pylint $(LINT_LOCS)
+       if [ "${CHANGED_FILES_PY_FLAG}" ]; then pycodestyle --statistics ${CHANGED_FILES_PY}; fi
+       if [ "${CHANGED_FILES_PY_FLAG}" ]; then bandit -q -c .banditrc -r ${CHANGED_FILES_PY}; fi
+       if [ "${CHANGED_FILES_PY_FLAG}" ]; then flake8 ${CHANGED_FILES_PY}; fi
+       if [ "${CHANGED_FILES_PY_FLAG}" ]; then mypy ${CHANGED_FILES_PY}; fi
+       if [ "${CHANGED_FILES_PY_FLAG}" ]; then pylint ${CHANGED_FILES_PY}; fi
 
-.PHONY: lint
-lint: _venv _lint      ## Lint code and documentation
+.PHONY: pylint
+pylint:
+       if [ "${CHANGED_FILES_PY_FLAG}" ]; then pylint ${CHANGED_FILES_PY}; fi
 
+.PHONY: mypy
+mypy:
+       if [ "${CHANGED_FILES_PY_FLAG}" ]; then mypy ${CHANGED_FILES_PY}; fi
 
-TEST_HOME := tests/
-MIN_COV := 80
-.PHONY: _test
-_test:
-       coverage run -m pytest $(TEST_HOME)
-       coverage report
 
 .PHONY: test
-test: _venv _test      ## Run CLI unittests
+test: _venv    ## Run CLI unittests
+       coverage run
+       coverage report
+       - grep fail_under setup.cfg
 
 
 
@@ -122,7 +119,7 @@ test: _venv _test   ## Run CLI unittests
 
 .PHONY: ntsqlite/build
 ntsqlite/build:
-       $(PY_SYS_INTERPRETER) ntclient/ntsqlite/sql/__init__.py
+       ${PY_SYS_INTERPRETER} ntclient/ntsqlite/sql/__init__.py
 
 # TODO: nt-sqlite/test
 
@@ -134,7 +131,7 @@ ntsqlite/build:
 
 .PHONY: _build
 _build:
-       $(PY_SYS_INTERPRETER) setup.py --quiet sdist
+       ${PY_SYS_INTERPRETER} setup.py --quiet sdist
 
 .PHONY: build
 build: ## Create sdist binary *.tar.gz
@@ -142,11 +139,11 @@ build: _build clean
 
 
 .PHONY: install
-install:       ## pip install nutra
-       $(PY_SYS_INTERPRETER) -m pip install wheel
-       $(PY_SYS_INTERPRETER) -m pip install . || $(PY_SYS_INTERPRETER) -m pip install --user .
-       $(PY_SYS_INTERPRETER) -m pip show nutra
-       - $(PY_SYS_INTERPRETER) -c 'import shutil; print(shutil.which("nutra"));'
+install:       ## pip install .
+       ${PY_SYS_INTERPRETER} -m pip install wheel
+       ${PY_SYS_INTERPRETER} -m pip install . || ${PY_SYS_INTERPRETER} -m pip install --user .
+       ${PY_SYS_INTERPRETER} -m pip show nutra
+       - ${PY_SYS_INTERPRETER} -c 'import shutil; print(shutil.which("nutra"));'
        nutra -v
 
 
@@ -155,6 +152,12 @@ install:   ## pip install nutra
 # Clean
 # ---------------------------------------
 
+RECURSIVE_CLEAN_LOCS ?= $(shell find ntclient/ tests/ \
+-name __pycache__ \
+-o -name .coverage \
+-o -name .mypy_cache \
+-o -name .pytest_cache)
+
 .PHONY: clean
 clean: ## Clean up __pycache__ and leftover bits
        rm -f .coverage ntclient/ntsqlite/sql/nt.sqlite3
@@ -162,14 +165,7 @@ clean:     ## Clean up __pycache__ and leftover bits
        rm -rf nutra.egg-info/
        rm -rf .pytest_cache/ .mypy_cache/
        # Recursively find & remove
-       find ntclient/ tests/ \
-       -name \
-       __pycache__ \
-       -o -name \
-       .coverage \
-       -o -name .mypy_cache \
-       -o -name .pytest_cache \
-       | xargs rm -rf
+       if [ "${RECURSIVE_CLEAN_LOCS}" ]; then rm -rf ${RECURSIVE_CLEAN_LOCS}; fi
 
 
 
@@ -177,15 +173,6 @@ clean:     ## Clean up __pycache__ and leftover bits
 # Extras
 # ---------------------------------------
 
-CLOC_ARGS ?=
 .PHONY: extras/cloc
 extras/cloc:   ## Count lines of source code
-       - cloc \
-       --exclude-dir=\
-       .venv,venv,\
-       .mypy_cache,.pytest_cache,\
-       .idea,\
-       build,dist \
-       --exclude-ext=svg \
-       $(CLOC_ARGS) \
-       .
+       - cloc HEAD
index e49ab118a8e997693199278983fc97a2c86a23c5..50699c04aba6c4afc96cba1c627769e690373d74 100644 (file)
@@ -1,13 +1,13 @@
-**************
- nutratracker
-**************
+********************
+ nutratracker (cli)
+********************
 
 Command line tools for interacting with government food database,
 and analyzing your health trends. The ``SR28`` database includes data
 for ~8500 foods and ~180 nutrients. Customizable with extensions
 and mapping rules built on top.
 
-**Requires**
+**Requires:**
 
 - Python 3.4.0 or later (``lzma``, ``ssl`` & ``sqlite3`` modules)
   [Win XP / Ubuntu 14.04].
@@ -83,7 +83,7 @@ extension.
     direnv
 
 
-You can add the direnv hook, ``direnv hook bash >>~.bashrc``.
+You can add the ``direnv`` hook, ``direnv hook bash >>~/.bashrc``.
 Only run this once.
 
 
@@ -93,6 +93,8 @@ Plugin Development
 You can develop plugins (or data modifications sets) that
 are imported and built on the base (or core) installation.
 
+These currently can take the form of custom recipes, foods, and RDA injection.
+
 
 Supporting Old Versions of Python
 #######################################################
@@ -107,18 +109,19 @@ Simply install them with this (inside your ``venv`` environment).
 This won't guarantee compatibility for every version, but it will help.
 We provide a wide range. The oldest version of ``tabulate`` is from 2013.
 
-To use an old interpreter (Python 3.4 does not have the ``typing`` module!
-Only ``collections.abc``.) you may need to use
-a virtual machine or install old SSL libraries or enter a similar messy state.
-My preference is for VirtualBox images, where
-I manually test Windows XP & Ubuntu 14.04.
+There is automated testing on GitHub, but to use an old interpreter
+(Python 3.4 does not have the ``typing`` module! Only ``collections.abc``),
+you may need to use a virtual machine or install old SSL libraries or enter a
+similar messy state.
+My preference is for VirtualBox images, where I manually test *Windows XP*
+& *Ubuntu 14.04*.
 
 
 Notes
 #######################################################
 
 On Windows you should check the box during the Python installer
-to include ``Scripts`` directory in your ``$PATH``.  This can be done
+to include ``Scripts`` directory in your ``%PATH%``.  This can be done
 manually after installation too.
 
 Windows users may also have differing results if they install for all users
@@ -126,19 +129,19 @@ Windows users may also have differing results if they install for all users
 location of installed scripts, and affect the ``$PATH`` variable being
 correctly populated for prior installs.
 
+Install the Levenshtein speedup with this. If it fails remove the ``[extras]``.
+
+.. code-block:: bash
+
+  pip install nutra[extras]
+
 Linux may need to install ``python-dev`` package to build
 ``python-Levenshtein``.
-I am currently debating making this an optional dependency to avoid
-confusing install failures for people without ``gcc`` or ``python3-dev``.
-
-I'm also currently working on doing phased installs of dependencies based on
-the host Python version, since some of the old versions of pip have trouble
-finding something that works, and again, spit out confusing errors.
 
 Windows users may not be able to install ``python-Levenshtein``.
 
 Main program works 100%, but ``test`` and ``lint`` may break on older operating
-systems (Ubuntu 14.04, Windows XP).
+systems (*Ubuntu 14.04*, *Windows XP*).
 
 
 Install PyPi release (from pip)
@@ -153,6 +156,7 @@ Install PyPi release (from pip)
 
 Using the source code directly
 #######################################################
+
 Clone down, initialize ``nt-sqlite`` submodule, and install requirements:
 
 .. code-block:: bash
@@ -165,6 +169,7 @@ Clone down, initialize ``nt-sqlite`` submodule, and install requirements:
 
   ./nutra -h
 
+
 Initialize the DBs (``nt`` and ``usda``).
 
 .. code-block:: bash
@@ -176,6 +181,7 @@ Initialize the DBs (``nt`` and ``usda``).
   make install
   n init
 
+
 If installed (or inside ``cli``) folder, the program can also run
 with ``python -m ntclient``.
 
@@ -235,6 +241,7 @@ I've run the command to seed the autocomplete script.
   mkdir -p $HOME/.bash_completion.d
   activate-global-python-argcomplete --user
 
+
 And my ``~/.bashrc`` file looks like this.
 
 .. code-block:: bash
@@ -246,8 +253,9 @@ And my ``~/.bashrc`` file looks like this.
       source ~/.bash_completion.d/python-argcomplete
   fi
 
-**NOTE:** This is a work in progress, we are adding more autocomplete
-functions.
+
+**NOTE:** Standard autocomplete is fully functional, we are adding customized
+completions
 
 
 Currently Supported Data
@@ -255,11 +263,11 @@ Currently Supported Data
 
 **USDA Stock database**
 
-- Standard reference database (SR28)  `[7794 foods]`
+- Standard reference database (SR28)  **[7794 foods]**
 
-**Relative USDA Extensions**
+**USDA Extensions (Relational)**
 
-- Flavonoid, Isoflavonoids, and Proanthocyanidins  `[1352 foods]`
+- Flavonoid, Isoflavonoids, and Proanthocyanidins  **[1352 foods]**
 
 
 Usage
@@ -268,4 +276,4 @@ Usage
 Requires internet connection to download initial datasets.
 Run ``nutra init`` for this step.
 
-Run the ``n`` script to output usage.
+Run ``n`` or ``nutra`` to output usage (``-h`` flag is optional and defaulted).
index df188349f1805f9641a777ebea7721f652fc4341..eb03714db4c0af045c3ffa4c70dbcab3bd8b582b 100644 (file)
@@ -7,19 +7,16 @@ Created on Fri Jan 31 16:01:31 2020
 
 @author: shane
 """
-import argparse
 import os
 import platform
 import shutil
 import sys
-from enum import Enum
 
 from ntclient.ntsqlite.sql import NT_DB_NAME
-from ntclient.utils import colors
 
 # Package info
 __title__ = "nutra"
-__version__ = "0.2.7.dev0"
+__version__ = "0.2.7.dev3"
 __author__ = "Shane Jaroch"
 __email__ = "chown_tee@proton.me"
 __license__ = "GPL v3"
@@ -47,7 +44,7 @@ PY_MIN_STR = ".".join(str(x) for x in PY_MIN_VER)
 PY_SYS_STR = ".".join(str(x) for x in PY_SYS_VER)
 if PY_SYS_VER < PY_MIN_VER:
     # TODO: make this testable with: `class CliConfig`?
-    raise RuntimeError(
+    raise RuntimeError(  # pragma: no cover
         "ERROR: %s requires Python %s or later to run" % (__title__, PY_MIN_STR),
         "HINT:  You're running Python %s" % PY_SYS_STR,
     )
@@ -70,144 +67,6 @@ DEFAULT_SEARCH_H_BUFFER = (
 )
 
 
-################################################################################
-# CLI config class (settings & preferences / defaults)
-################################################################################
-class RdaColors(Enum):
-    """
-    Stores values for report colors.
-    Default values:
-        Acceptable     =Cyan
-        Overage        =Magenta (Dim)
-        Low            =Yellow
-        Critically Low =Red (Dim)
-    TODO: make configurable in SQLite or prefs.json
-    """
-
-    THRESH_WARN = 0.7
-    THRESH_CRIT = 0.4
-    THRESH_OVER = 1.9
-
-    COLOR_WARN = colors.COLOR_WARN
-    COLOR_CRIT = colors.COLOR_CRIT
-    COLOR_OVER = colors.COLOR_OVER
-
-    COLOR_DEFAULT = colors.COLOR_DEFAULT
-
-    STYLE_RESET_ALL = colors.STYLE_RESET_ALL
-
-    # Used in macro bars
-    COLOR_YELLOW = colors.COLOR_YELLOW
-    COLOR_BLUE = colors.COLOR_BLUE
-    COLOR_RED = colors.COLOR_RED
-
-
-# pylint: disable=too-few-public-methods,too-many-instance-attributes
-class _CliConfig:
-    """Mutable global store for configuration values"""
-
-    def __init__(self, debug: bool = False, paging: bool = True) -> None:
-        self.debug = debug
-        self.paging = paging
-
-        # TODO: respect a prefs.json, or similar config file.
-        self.thresh_warn = RdaColors.THRESH_WARN.value
-        self.thresh_crit = RdaColors.THRESH_CRIT.value
-        self.thresh_over = RdaColors.THRESH_OVER.value
-
-        self.color_warn = RdaColors.COLOR_WARN.value
-        self.color_crit = RdaColors.COLOR_CRIT.value
-        self.color_over = RdaColors.COLOR_OVER.value
-        self.color_default = RdaColors.COLOR_DEFAULT.value
-
-        self.style_reset_all = RdaColors.STYLE_RESET_ALL.value
-
-        self.color_yellow = RdaColors.COLOR_YELLOW.value
-        self.color_red = RdaColors.COLOR_RED.value
-        self.color_blue = RdaColors.COLOR_BLUE.value
-
-    def set_flags(self, args: argparse.Namespace) -> None:
-        """
-        Sets flags:
-          {DEBUG, PAGING}
-            from main (after arg parse). Accessible throughout package.
-            Must be re-imported globally.
-        """
-
-        self.debug = args.debug
-        self.paging = not args.no_pager
-
-        if self.debug:
-            print("Console size: %sh x %sw" % (BUFFER_HT, BUFFER_WD))
-
-
-# Create the shared instance object
-CLI_CONFIG = _CliConfig()
-
-
-# TODO:
-#  Nested nutrient tree, like:
-#       http://www.whfoods.com/genpage.php?tname=nutrientprofile&dbid=132
-#  Attempt to record errors in failed try/catch block (bottom of __main__.py)
-#  Make use of argcomplete.warn(msg) ?
-
-
-################################################################################
-# Validation Enums
-################################################################################
-class Gender(Enum):
-    """
-    A validator and Enum class for gender inputs; used in several calculations.
-    @note: floating point -1 to 1, or 0 to 1... for non-binary?
-    """
-
-    MALE = "m"
-    FEMALE = "f"
-
-
-class ActivityFactor(Enum):
-    """
-    Used in BMR calculations.
-    Different activity levels: {0.200, 0.375, 0.550, 0.725, 0.900}
-
-    Activity Factor\n
-    ------------------------\n
-    0.200 = sedentary (little or no exercise)
-
-    0.375 = lightly active
-        (light exercise/sports 1-3 days/week, approx. 590 Cal/day)
-
-    0.550 = moderately active
-        (moderate exercise/sports 3-5 days/week, approx. 870 Cal/day)
-
-    0.725 = very active
-        (hard exercise/sports 6-7 days a week, approx. 1150 Cal/day)
-
-    0.900 = extremely active
-        (very hard exercise/sports and physical job, approx. 1580 Cal/day)
-
-    @todo: Verify the accuracy of these "names". Access by index?
-    """
-
-    SEDENTARY = {1: 0.2}
-    MILDLY_ACTIVE = {2: 0.375}
-    ACTIVE = {3: 0.55}
-    HIGHLY_ACTIVE = {4: 0.725}
-    INTENSELY_ACTIVE = {5: 0.9}
-
-
-def activity_factor_from_index(activity_factor: int) -> float:
-    """
-    Gets ActivityFactor Enum by float value if it exists, else raise ValueError.
-    Basically just verifies the float is among the allowed values, and re-returns it.
-    """
-    for enum_entry in ActivityFactor:
-        if activity_factor in enum_entry.value:
-            return float(enum_entry.value[activity_factor])
-    # TODO: custom exception. And handle in main file?
-    raise ValueError("No such ActivityFactor for value: %s" % activity_factor)
-
-
 ################################################################################
 # Nutrient IDs
 ################################################################################
index 73e11166f2dd848ea87dfe4321b463e33ad75a64..ccde9616820f2e146fc3d6a8501591018f4744ef 100644 (file)
@@ -15,7 +15,6 @@ from urllib.error import HTTPError, URLError
 import argcomplete
 
 from ntclient import (
-    CLI_CONFIG,
     __db_target_nt__,
     __db_target_usda__,
     __email__,
@@ -24,6 +23,7 @@ from ntclient import (
     __version__,
 )
 from ntclient.argparser import build_subcommands
+from ntclient.utils import CLI_CONFIG
 from ntclient.utils.exceptions import SqlException
 
 
@@ -53,7 +53,7 @@ def build_arg_parser() -> argparse.ArgumentParser:
     return arg_parser
 
 
-def main(args: list = None) -> int:
+def main(args: list = None) -> int:  # type: ignore
     """
     Main method for CLI
 
@@ -67,7 +67,7 @@ def main(args: list = None) -> int:
     def parse_args() -> argparse.Namespace:
         """Returns parsed args"""
         if args is None:
-            return arg_parser.parse_args()
+            return arg_parser.parse_args()  # type: ignore
         return arg_parser.parse_args(args=args)
 
     def func(parser: argparse.Namespace) -> tuple:
@@ -100,20 +100,20 @@ def main(args: list = None) -> int:
     exit_code = 1
     try:
         exit_code, *_results = func(_parser)
-    except SqlException as sql_exception:
+    except SqlException as sql_exception:  # pragma: no cover
         print("Issue with an sqlite database: " + repr(sql_exception))
         if CLI_CONFIG.debug:
             raise
-    except HTTPError as http_error:
+    except HTTPError as http_error:  # pragma: no cover
         err_msg = "{0}: {1}".format(http_error.code, repr(http_error))
         print("Server response error, try again: " + err_msg)
         if CLI_CONFIG.debug:
             raise
-    except URLError as url_error:
+    except URLError as url_error:  # pragma: no cover
         print("Connection error, check your internet: " + repr(url_error.reason))
         if CLI_CONFIG.debug:
             raise
-    except Exception as exception:  # pylint: disable=broad-except
+    except Exception as exception:  # pylint: disable=broad-except  # pragma: no cover
         print("Unforeseen error, run with -d for more info: " + repr(exception))
         print("You can open an issue here: %s" % __url__)
         print("Or send me an email with the debug output: %s" % __email__)
index 698c33902cb9ccf86433f491b1a0382e95ce7503..86a5f67b56e963ab66984a14110e42a79bff0f61 100644 (file)
@@ -190,7 +190,7 @@ def build_calc_subcommand(subparsers: argparse._SubParsersAction) -> None:
         "calc", help="calculate 1-rep max, body fat, BMR, etc."
     )
 
-    calc_subparsers = calc_parser.add_subparsers(title="recipe subcommands")
+    calc_subparsers = calc_parser.add_subparsers(title="calc subcommands")
     calc_parser.set_defaults(func=calc_parser.print_help)
 
     # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -257,43 +257,43 @@ def build_calc_subcommand(subparsers: argparse._SubParsersAction) -> None:
         "chest",
         type=int,
         nargs="?",
-        help="pectoral (mm) -[3-Site skin caliper measurement]",
+        help="pectoral (mm) -[3-Site skin caliper measurement]",
     )
     calc_bf_parser.add_argument(
         "abd",
         type=int,
         nargs="?",
-        help="abdominal (mm) [3-Site skin caliper measurement]",
+        help="abdominal (mm) [3-Site skin caliper measurement]",
     )
     calc_bf_parser.add_argument(
         "thigh",
         type=int,
         nargs="?",
-        help="thigh (mm) --- [3-Site skin caliper measurement]",
+        help="thigh (mm) ----- [3-Site skin caliper measurement]",
     )
     calc_bf_parser.add_argument(
         "tricep",
         type=int,
         nargs="?",
-        help="triceps (mm) - [7-Site skin caliper measurement]",
+        help="triceps (mm) ---- [7-Site skin caliper measurement]",
     )
     calc_bf_parser.add_argument(
         "sub",
         type=int,
         nargs="?",
-        help="sub (mm) ----- [7-Site skin caliper measurement]",
+        help="sub (mm) -------- [7-Site skin caliper measurement]",
     )
     calc_bf_parser.add_argument(
         "sup",
         type=int,
         nargs="?",
-        help="sup (mm) ----- [7-Site skin caliper measurement]",
+        help="sup (mm) -------- [7-Site skin caliper measurement]",
     )
     calc_bf_parser.add_argument(
         "mid",
         type=int,
         nargs="?",
-        help="mid (mm) ----- [7-Site skin caliper measurement]",
+        help="mid (mm) -------- [7-Site skin caliper measurement]",
     )
     calc_bf_parser.set_defaults(func=parser_funcs.calc_body_fat)
 
index a9ac713ebd7854ec170bbb97d0e935a75f20afb5..f29573017610d9495f5dba4b7238676a969bc0c0 100644 (file)
@@ -16,8 +16,8 @@ from tabulate import tabulate
 import ntclient.services.analyze
 import ntclient.services.recipe.utils
 import ntclient.services.usda
-from ntclient import CLI_CONFIG, Gender, activity_factor_from_index
 from ntclient.services import calculate as calc
+from ntclient.utils import CLI_CONFIG, Gender, activity_factor_from_index
 
 
 def init(args: argparse.Namespace) -> tuple:
@@ -126,7 +126,7 @@ def calc_1rm(args: argparse.Namespace) -> tuple:
     # TODO: fourth column: average or `avg` column too.
     # Prepare table rows, to display all 3 results in one table
     _all = []
-    for _rep in _epley.keys():
+    for _rep in sorted(_epley.keys()):  # NOTE: dicts not sorted prior to 3.7
         row = [_rep]
         for _calc, _values in result.items():
             # Round down for now
@@ -235,6 +235,8 @@ def calc_body_fat(args: argparse.Namespace) -> tuple:
     }
     """
 
+    print("HINT: re-run with '-h' to show usage.")
+    print(os.linesep + "INPUTS" + os.linesep + "------")
     gender = Gender.FEMALE if args.female_gender else Gender.MALE
     print("Gender: %s" % gender)
     try:
index c4ed89ec4517ed780d65234bd2e57a7020e48153..7a8b378e5cd3506b958ce30378e2dd66a683e0a2 100644 (file)
@@ -7,7 +7,7 @@ Classes, structures for storing, displaying, and editing data.
 """
 import csv
 
-from ntclient import CLI_CONFIG
+from ntclient.utils import CLI_CONFIG
 
 
 class Recipe:
index 0eb89de7db73aa68d71da3b4113296ba595500bd..e69368ff9a64db7134a212686c08922c6537bcee 160000 (submodule)
@@ -1 +1 @@
-Subproject commit 0eb89de7db73aa68d71da3b4113296ba595500bd
+Subproject commit e69368ff9a64db7134a212686c08922c6537bcee
index 9d603b9bbe69e391eda7b9ae28e8ddafb907d6af..944b0f8708f82832bfc9acf2006742341768f981 100644 (file)
@@ -2,10 +2,11 @@
 import sqlite3
 from collections.abc import Sequence
 
+from ntclient.utils import CLI_CONFIG
+
 # ------------------------------------------------
 # Entry fetching methods
 # ------------------------------------------------
-from ntclient import CLI_CONFIG
 
 
 def sql_entries(sql_result: sqlite3.Cursor) -> list:
index b210326c413ff226cd2dbf24c9f2d0678e4c5fb9..f63fd2be7d4d9a917dc53fbf290c1a1cc2eaa768 100644 (file)
@@ -32,7 +32,7 @@ def usda_init(yes: bool = False) -> None:
             # Extract the archive
             with tarfile.open(save_path, mode="r:xz") as usda_sqlite_file:
                 print("\n" + "tar xvf %s.tar.xz" % USDA_DB_NAME)
-                usda_sqlite_file.extractall(NUTRA_HOME)
+                usda_sqlite_file.extractall(NUTRA_HOME)  # nosec: B202
 
             print("==> done downloading %s" % USDA_DB_NAME)
 
index cc7c5c89342b835fc94d16a6179b2e89a6aab685..34422325cac7fe4181203377adc7362351666625 100644 (file)
@@ -1,4 +1,5 @@
 """usda.sqlite functions module"""
+
 from ntclient import NUTR_ID_KCAL
 from ntclient.persistence.sql.usda import sql, sql_headers
 
@@ -14,7 +15,7 @@ def sql_fdgrp() -> dict:
     return {x[0]: x for x in result}
 
 
-def sql_food_details(_food_ids: set = None) -> list:
+def sql_food_details(_food_ids: set = None) -> list:  # type: ignore
     """Readable human details for foods"""
 
     if not _food_ids:
index 4b533dbb60a40fb1ff5684d7bc475d88476e1d54..6824f3e1ed14b931c58119ea3f1010a197c53500 100644 (file)
@@ -12,7 +12,6 @@ from tabulate import tabulate
 
 from ntclient import (
     BUFFER_WD,
-    CLI_CONFIG,
     NUTR_ID_CARBS,
     NUTR_ID_FAT_TOT,
     NUTR_ID_FIBER,
@@ -25,6 +24,7 @@ from ntclient.persistence.sql.usda.funcs import (
     sql_nutrients_overview,
     sql_servings,
 )
+from ntclient.utils import CLI_CONFIG
 
 
 ################################################################################
index 5ff1cae3335d2a0009517e2233d768ea6a1575b8..ff4e428c2e1e3ad8fb6846f87ae3708d3c1f1089 100644 (file)
@@ -9,7 +9,7 @@ Created on Tue Aug 11 20:53:14 2020
 import argparse
 import math
 
-from ntclient import Gender
+from ntclient.utils import Gender
 
 # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 # 1 rep max
@@ -431,7 +431,7 @@ def lbl_eric_helms(height: float, args: argparse.Namespace) -> dict:
 
     try:
         desired_bf = float(args.desired_bf) * 100
-    except (KeyError, TypeError):
+    except (KeyError, TypeError):  # pragma: no cover
         return {"errMsg": "Eric Helms failed, requires: height, desired_bf."}
 
     _min = round(4851.00 * height * 0.01 * height * 0.01 / (100.0 - desired_bf), 1)
@@ -460,7 +460,7 @@ def lbl_casey_butt(height: float, args: argparse.Namespace) -> dict:
 
         wrist = float(args.wrist) / 2.54  # convert cm --> inches
         ankle = float(args.ankle) / 2.54  # convert cm --> inches
-    except (KeyError, TypeError):
+    except (KeyError, TypeError):  # pragma: no cover
         return {
             "errMsg": "Casey Butt failed, requires: height, desired_bf, wrist, & ankle."
         }
index 39d48b0aae6c33b63447dd7955dc0c2efab24414..c4faa1cdded889e5342ca19d1cdba9c830ee536f 100644 (file)
@@ -15,7 +15,7 @@ from ntclient.utils import tree
 
 
 def csv_files() -> list:
-    """Returns full filenames for everything under RECIPE_HOME'"""
+    """Returns full filenames for everything under RECIPE_HOME"""
     return glob.glob(RECIPE_HOME + "/**/*.csv", recursive=True)
 
 
index cefeb9b129de8da0bc3d8dcb10a8126e9c2d6cce..b6face29ff726c5de7db9625900cf23d4d20e58f 100644 (file)
@@ -10,7 +10,6 @@ import pydoc
 from tabulate import tabulate
 
 from ntclient import (
-    CLI_CONFIG,
     DEFAULT_RESULT_LIMIT,
     DEFAULT_SEARCH_H_BUFFER,
     DEFAULT_SORT_H_BUFFER,
@@ -25,6 +24,7 @@ from ntclient.persistence.sql.usda.funcs import (
     sql_nutrients_overview,
     sql_sort_helper1,
 )
+from ntclient.utils import CLI_CONFIG
 
 
 def list_nutrients() -> tuple:
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..042f66f2ebe8a772d00c34b14f36fc957f8c460b 100644 (file)
@@ -0,0 +1,152 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""
+Created on Sun Mar 26 23:07:30 2023
+
+@author: shane
+"""
+import argparse
+from enum import Enum
+
+from ntclient import BUFFER_HT, BUFFER_WD
+from ntclient.utils import colors
+
+
+################################################################################
+# CLI config class (settings & preferences / defaults)
+################################################################################
+class RdaColors(Enum):
+    """
+    Stores values for report colors.
+    Default values:
+        Acceptable     =Cyan
+        Overage        =Magenta (Dim)
+        Low            =Yellow
+        Critically Low =Red (Dim)
+    TODO: make configurable in SQLite or prefs.json
+    """
+
+    THRESH_WARN = 0.7
+    THRESH_CRIT = 0.4
+    THRESH_OVER = 1.9
+
+    COLOR_WARN = colors.COLOR_WARN
+    COLOR_CRIT = colors.COLOR_CRIT
+    COLOR_OVER = colors.COLOR_OVER
+
+    COLOR_DEFAULT = colors.COLOR_DEFAULT
+
+    STYLE_RESET_ALL = colors.STYLE_RESET_ALL
+
+    # Used in macro bars
+    COLOR_YELLOW = colors.COLOR_YELLOW
+    COLOR_BLUE = colors.COLOR_BLUE
+    COLOR_RED = colors.COLOR_RED
+
+
+# pylint: disable=too-few-public-methods,too-many-instance-attributes
+class _CliConfig:
+    """Mutable global store for configuration values"""
+
+    def __init__(self, debug: bool = False, paging: bool = True) -> None:
+        self.debug = debug
+        self.paging = paging
+
+        # TODO: respect a prefs.json, or similar config file.
+        self.thresh_warn = RdaColors.THRESH_WARN.value
+        self.thresh_crit = RdaColors.THRESH_CRIT.value
+        self.thresh_over = RdaColors.THRESH_OVER.value
+
+        self.color_warn = RdaColors.COLOR_WARN.value
+        self.color_crit = RdaColors.COLOR_CRIT.value
+        self.color_over = RdaColors.COLOR_OVER.value
+        self.color_default = RdaColors.COLOR_DEFAULT.value
+
+        self.style_reset_all = RdaColors.STYLE_RESET_ALL.value
+
+        self.color_yellow = RdaColors.COLOR_YELLOW.value
+        self.color_red = RdaColors.COLOR_RED.value
+        self.color_blue = RdaColors.COLOR_BLUE.value
+
+    def set_flags(self, args: argparse.Namespace) -> None:
+        """
+        Sets flags:
+          {DEBUG, PAGING}
+            from main (after arg parse). Accessible throughout package.
+            Must be re-imported globally.
+        """
+
+        self.debug = args.debug
+        self.paging = not args.no_pager
+
+        if self.debug:
+            print("Console size: %sh x %sw" % (BUFFER_HT, BUFFER_WD))
+
+
+# Create the shared instance object
+CLI_CONFIG = _CliConfig()
+
+
+# TODO:
+#  Nested nutrient tree, like:
+#       http://www.whfoods.com/genpage.php?tname=nutrientprofile&dbid=132
+#  Attempt to record errors in failed try/catch block (bottom of __main__.py)
+#  Make use of argcomplete.warn(msg) ?
+
+
+################################################################################
+# Validation Enums
+################################################################################
+class Gender(Enum):
+    """
+    A validator and Enum class for gender inputs; used in several calculations.
+    @note: floating point -1 to 1, or 0 to 1... for non-binary?
+    """
+
+    MALE = "m"
+    FEMALE = "f"
+
+
+class ActivityFactor(Enum):
+    """
+    Used in BMR calculations.
+    Different activity levels: {0.200, 0.375, 0.550, 0.725, 0.900}
+
+    Activity Factor\n
+    ------------------------\n
+    0.200 = sedentary (little or no exercise)
+
+    0.375 = lightly active
+        (light exercise/sports 1-3 days/week, approx. 590 Cal/day)
+
+    0.550 = moderately active
+        (moderate exercise/sports 3-5 days/week, approx. 870 Cal/day)
+
+    0.725 = very active
+        (hard exercise/sports 6-7 days a week, approx. 1150 Cal/day)
+
+    0.900 = extremely active
+        (very hard exercise/sports and physical job, approx. 1580 Cal/day)
+
+    @todo: Verify the accuracy of these "names". Access by index?
+    """
+
+    SEDENTARY = {1: 0.2}
+    MILDLY_ACTIVE = {2: 0.375}
+    ACTIVE = {3: 0.55}
+    HIGHLY_ACTIVE = {4: 0.725}
+    INTENSELY_ACTIVE = {5: 0.9}
+
+
+def activity_factor_from_index(activity_factor: int) -> float:
+    """
+    Gets ActivityFactor Enum by float value if it exists, else raise ValueError.
+    Basically just verifies the float is among the allowed values, and re-returns it.
+    """
+    for enum_entry in ActivityFactor:
+        if activity_factor in enum_entry.value:
+            return float(enum_entry.value[activity_factor])
+    # TODO: custom exception. And handle in main file?
+    raise ValueError(  # pragma: no cover
+        "No such ActivityFactor for value: %s" % activity_factor
+    )
index d319e5e8addf7bce5ba27a7658ca57efbd094887..13b84bbde11ffe1a6aa132c8f216d504ffc1d725 100644 (file)
@@ -4,52 +4,15 @@ Created on Mon Aug  8 14:35:43 2022
 
 @author: shane
 
-Allows the safe avoidance of ImportError on non-colorama capable systems.
+Wrapper for colorama. Used to default to white text if it wasn't installed,
+but it's available on virtually all systems now so why not.
 """
+from colorama import Fore, Style
+from colorama import init as colorama_init
 
-# pylint: disable=invalid-name
+# Made it this far, so run the init function (which is needed on Windows)
+colorama_init()
 
-
-# pylint: disable=too-few-public-methods
-class _STYLE:
-    def __init__(self) -> None:
-        self.BRIGHT = str()
-        self.DIM = str()
-        self.RESET_ALL = str()
-
-
-# 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()
-
-        self.YELLOW = str()
-        self.BLUE = str()
-        self.RED = str()
-        self.MAGENTA = str()
-
-        self.GREEN = str()
-        self.CYAN = str()
-
-
-_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:
-    Fore, Style = _Fore, _Style  # type: ignore
-
-
-# NOTE: These will all just be empty strings if colorama isn't installed
 # Styles
 STYLE_BRIGHT = Style.BRIGHT
 STYLE_DIM = Style.DIM
index b994090e8d3f0aa86fa6b2c86e6de229ff1dc8e9..9a50a7e2a561752954d5787b3481ab1fcf7fd36f 100644 (file)
@@ -93,13 +93,13 @@ def print_dir(_dir: str, pre: str = str()) -> tuple:
     return (n_dirs, n_files, n_size)
 
 
-def main_tree(_args: list = None) -> int:
+def main_tree(_args: list = None) -> int:  # type: ignore
     """Handle input arguments, print off tree"""
     n_dirs = 0
     n_files = 0
 
-    if not _args:
-        _args = sys.argv
+    if _args is None:
+        _args = sys.argv  # type: ignore
 
     if len(_args) == 1:
         # Used for development
index 4120b78e5d0b533f1b359f8d66546ad6b41354ae..2cab0667397f6965126ab971886f052f94677ac7 100644 (file)
@@ -1,11 +1,10 @@
-autopep8>=1.6
-bandit>=1.7
-black>=22.3
-doc8>=0.11
-flake8>=4.0
-mypy>=0.960
-pylint>=2.13
-types-colorama>=0.4.15
-types-psycopg2>=2.9.18
-types-setuptools>=57.4.0
-types-tabulate>=0.8.11
+bandit==1.7.5
+black==23.3.0
+doc8==1.1.1
+flake8==6.0.0
+mypy==1.1.1
+pylint==2.17.1
+types-colorama==0.4.15.11
+types-psycopg2==2.9.21.9
+types-setuptools==67.6.0.6
+types-tabulate==0.9.0.2
index 73afc0911c7082890e8f2c550f3ca6d1775766b9..88850019ea6de296e1736b17a768e483a78f6264 100644 (file)
@@ -1,4 +1,4 @@
-argcomplete==1.8.0
-colorama==0.3.6
+argcomplete<=1.8.2
+colorama<=0.3.6
 fuzzywuzzy==0.3.0
-tabulate==0.4.3
+tabulate<=0.4.3
index 3e3e163a1d2f95ed6e6427dbe500647adf56d8b2..453c47bd50a4ca160aad92247f20c937a4f2988f 100644 (file)
@@ -1 +1 @@
-python-Levenshtein==0.12.2
+python-Levenshtein<=0.12.2
index c4985c932665c8a126c1d097ab61bb6a47346a6f..86c8192b7062251dd427653007b0a30ef01b2f7f 100644 (file)
@@ -1,2 +1,2 @@
-coverage>=6.0
-pytest>=7.0
+coverage>=6.2
+pytest>=7.0.1
index 1548cc4f476def909ddf4a3b15649c31771bcc7b..6a6c215df12038634d09608d9f7619a1ec0c8a4f 100644 (file)
@@ -1,4 +1,4 @@
-argcomplete>=1.8.0
-colorama>=0.3.6,<=0.4.1
+argcomplete>=1.8.2,<=1.12.3
+colorama>=0.1.17,<=0.4.1
 fuzzywuzzy>=0.3.0
-tabulate>=0.4.3
+tabulate>=0.4.3,<=0.8.9
diff --git a/scripts/n b/scripts/n
new file mode 100755 (executable)
index 0000000..a1b3619
--- /dev/null
+++ b/scripts/n
@@ -0,0 +1,12 @@
+#!/usr/bin/python3
+# -*- coding: utf-8 -*-
+# PYTHON_ARGCOMPLETE_OK
+"""Executable script, copied over by pip"""
+import re
+import sys
+
+from ntclient.__main__ import main
+
+if __name__ == "__main__":
+    sys.argv[0] = re.sub(r"(-script\.pyw|\.exe)?$", "", sys.argv[0])
+    sys.exit(main())
index 33f211e4ddddcf255d15ddb1b213150974dd9e78..377db12d01df7bdd410e8cb07c589e9b9147d4e3 100644 (file)
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,9 +1,16 @@
+[tool:pytest]
+# See: https://docs.pytest.org/en/7.1.x/reference/customize.html
+testpaths =
+    tests
+
 [coverage:run]
+# See: https://coverage.readthedocs.io/en/7.2.2/config.html#run
+command_line = -m pytest
 source = ntclient
 
 [coverage:report]
-fail_under = 80
-precision = 2
+fail_under = 90.00
+precision = 2
 
 show_missing = True
 skip_empty = True
@@ -14,6 +21,9 @@ omit =
     # It directly imports the `build_ntsqlite()` function.
     ntclient/ntsqlite/sql/__main__.py,
 
+exclude_lines =
+    pragma: no cover
+
 
 
 [pycodestyle]
@@ -29,7 +39,8 @@ per-file-ignores =
 max-line-length = 88
 
 ignore =
-    W503,  # line break before binary operator
+    # line break before binary operator
+    W503,
 
 
 
@@ -53,6 +64,9 @@ disallow_untyped_defs = True
 disallow_untyped_calls = True
 disallow_untyped_decorators = True
 
+;strict_optional = True
+no_implicit_optional = True
+
 warn_return_any = True
 warn_redundant_casts = True
 warn_unreachable = True
@@ -61,7 +75,7 @@ warn_unused_ignores = True
 warn_unused_configs = True
 warn_incomplete_stub = True
 
-# Our test, they don't return a value typically
+# Our tests, they don't return a value typically
 [mypy-tests.*]
 disallow_untyped_defs = False
 
@@ -70,5 +84,5 @@ disallow_untyped_defs = False
 ignore_missing_imports = True
 
 # 3rd party packages missing types
-[mypy-argcomplete,coverage,fuzzywuzzy]
+[mypy-argcomplete,fuzzywuzzy]
 ignore_missing_imports = True
index 4a9f1ec227d3e4361f362949d42348c552d987e1..02b230d22e77f109b6986c06b4fe231d98cc81e6 100644 (file)
--- a/setup.py
+++ b/setup.py
@@ -4,6 +4,7 @@ Created on Sat Oct 13 16:30:30 2018
 
 @author: shane
 """
+import glob
 import os
 import platform
 
@@ -49,11 +50,9 @@ with open("README.rst", encoding="utf-8") as file:
 with open("requirements.txt", encoding="utf-8") as file:
     REQUIREMENTS = file.read().split()
 
-if PLATFORM_SYSTEM != "Windows" or int(os.getenv("NUTRA_OS_FORCE_OPT_REQS", str(0))):
-    # python-Levenshtein builds natively on Unix; Windows needs vcvarsall.bat or vc++10
-    with open("requirements-optional.txt", encoding="utf-8") as file:
-        optional_reqs = file.read().split()
-    REQUIREMENTS.extend(optional_reqs)
+
+with open("requirements-optional.txt", encoding="utf-8") as file:
+    REQUIREMENTS_EXTRA = file.read().split()
 
 # Setup method
 setup(
@@ -62,13 +61,15 @@ setup(
     author_email=__email__,
     classifiers=CLASSIFIERS,
     install_requires=REQUIREMENTS,
+    extras_require={"extras": REQUIREMENTS_EXTRA},
     python_requires=">=%s" % PY_MIN_STR,
     zip_safe=False,
-    packages=find_packages(exclude=["tests", "ntclient.docs"]),
+    packages=find_packages(exclude=["tests*"]),
     include_package_data=True,
-    entry_points={
-        "console_scripts": ["nutra=ntclient.__main__:main", "n=ntclient.__main__:main"]
-    },
+    # Linux / macOS argcomplete compatible script "n"
+    scripts=glob.glob("scripts/*"),
+    # Windows compatible nutra.exe
+    entry_points={"console_scripts": ["nutra=ntclient.__main__:main"]},
     platforms=["linux", "darwin", "win32"],
     description="Home and office nutrient tracking software",
     long_description=README,
index e91283b5d49610c50bc239495b58f3ac89bb6f5c..b099adafc6aeaf311a37c0eab6ef0aa83129b061 100644 (file)
@@ -1,14 +1,14 @@
-meal,subcat,id,grams,notes,desired_kcal\r
-breakfast,base,20044,90,"rice, uncooked [55g cooked]",120\r
-breakfast,base,16042,50,"pinto beans, raw [cooked: ¼ cup or 56g]",60\r
-breakfast,base,5062,50,chicken breast,50\r
-breakfast,cooked (veg),11507,25,"sweet potato, raw",20\r
-breakfast,cooked (veg),11233,10,"kale, raw OR broccoli, boiled, w/o salt",5\r
-breakfast,soaked,12220,10,flax [½ tbsp],30\r
-breakfast,chopped,11297,3,"parsley, fresh",1\r
-breakfast,supplements,,0.075,milk thistle extract,\r
-breakfast,supplements,,0.15,"calcium citrate, [1/8 tsp]",\r
-,,,,,\r
-,,,,,\r
-,,,,,\r
+meal,subcat,id,grams,notes,desired_kcal
+breakfast,base,20044,90,"rice, uncooked [55g cooked]",120
+breakfast,base,16042,50,"pinto beans, raw [cooked: ¼ cup or 56g]",60
+breakfast,base,5062,50,chicken breast,50
+breakfast,cooked (veg),11507,25,"sweet potato, raw",20
+breakfast,cooked (veg),11233,10,"kale, raw OR broccoli, boiled, w/o salt",5
+breakfast,soaked,12220,10,flax [½ tbsp],30
+breakfast,chopped,11297,3,"parsley, fresh",1
+breakfast,supplements,,0.075,milk thistle extract,
+breakfast,supplements,,0.15,"calcium citrate, [1/8 tsp]",
+,,,,,
+,,,,,
+,,,,,
 TOTAL,,,,,286
\ No newline at end of file
index c46ee849cbc9712dbb2beafb7797543354adbe34..6623e0e6644c044abe4caf4da6ec0fb0523f770f 100644 (file)
@@ -14,7 +14,6 @@ import unittest
 import pytest
 
 from ntclient import (
-    CLI_CONFIG,
     NTSQLITE_BUILDPATH,
     NUTRA_HOME,
     USDA_DB_NAME,
@@ -32,6 +31,7 @@ from ntclient.persistence.sql.usda import sql as _usda_sql
 from ntclient.persistence.sql.usda import usda_ver
 from ntclient.services import init, usda
 from ntclient.services.recipe import RECIPE_HOME
+from ntclient.utils import CLI_CONFIG
 from ntclient.utils.exceptions import SqlInvalidVersionError
 
 TEST_HOME = os.path.dirname(os.path.abspath(__file__))
@@ -161,6 +161,7 @@ class TestCli(unittest.TestCase):
         assert len(nutrients_rows[0]) == 30
         assert len(servings_rows[0]) == 1
 
+    # pylint: disable=too-many-statements
     def test_410_nt_argparser_funcs(self):
         """
         Tests nt functions in argparser.funcs (to varying degrees each)
@@ -426,7 +427,7 @@ class TestCli(unittest.TestCase):
             pytest.xfail("PermissionError, are you using Microsoft Windows?")
 
         # mocks input, could also pass `-y` flag or set yes=True
-        usda.input = lambda x: "y"
+        usda.input = lambda x: "y"  # pylint: disable=redefined-builtin
 
         code, successful = init()
         assert code == 0