halfway to databased
This commit is contained in:
@@ -31,7 +31,8 @@ Okay, next fill-up set to <date X days from now> (configured in `sup.toml`::FILL
|
|||||||
|
|
||||||
TODO: talk about product_aliases
|
TODO: talk about product_aliases
|
||||||
|
|
||||||
To configure what you take, how much, and when, add entries to `supps.toml`. The fields are configured as such in the `Supp` class:
|
To configure what you take, how much, and when, add entries to `supps.toml`.
|
||||||
|
The fields are configured as such in the `Supp` class:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
name: str
|
name: str
|
||||||
|
|||||||
@@ -83,7 +83,7 @@
|
|||||||
{
|
{
|
||||||
"orderDate": "2024-01-23T23:00:00.000Z",
|
"orderDate": "2024-01-23T23:00:00.000Z",
|
||||||
"name": "Liquid D-3 & MK-7",
|
"name": "Liquid D-3 & MK-7",
|
||||||
"quantity": 81.5,
|
"quantity": 82,
|
||||||
"quantityUnits": "iu",
|
"quantityUnits": "iu",
|
||||||
"servingUnit": "iu",
|
"servingUnit": "iu",
|
||||||
"numUnitsInServing": 5000,
|
"numUnitsInServing": 5000,
|
||||||
@@ -410,7 +410,7 @@
|
|||||||
"quantity": 59,
|
"quantity": 59,
|
||||||
"quantityUnits": "ml",
|
"quantityUnits": "ml",
|
||||||
"servingUnit": "ml",
|
"servingUnit": "ml",
|
||||||
"numUnitsInServing": 0.126,
|
"numUnitsInServing": 126,
|
||||||
"numBottles": 1
|
"numBottles": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -445,7 +445,7 @@
|
|||||||
"quantity": 90,
|
"quantity": 90,
|
||||||
"numUnitsInServing": 25,
|
"numUnitsInServing": 25,
|
||||||
"servingUnit": "mg",
|
"servingUnit": "mg",
|
||||||
"quantityUnit": "caps",
|
"quantityUnits": "caps",
|
||||||
"orderDate": "2024-02-03",
|
"orderDate": "2024-02-03",
|
||||||
"numBottles": 1
|
"numBottles": 1
|
||||||
},
|
},
|
||||||
@@ -480,7 +480,7 @@
|
|||||||
"orderDate": "2024-02-04T23:00:00.000Z",
|
"orderDate": "2024-02-04T23:00:00.000Z",
|
||||||
"name": "Ultimate Omega",
|
"name": "Ultimate Omega",
|
||||||
"quantity": 640,
|
"quantity": 640,
|
||||||
"quantityUnits": "mg per Soft Gel",
|
"quantityUnits": "mg",
|
||||||
"servingUnit": "mg",
|
"servingUnit": "mg",
|
||||||
"numUnitsInServing": 1280,
|
"numUnitsInServing": 1280,
|
||||||
"numBottles": 1
|
"numBottles": 1
|
||||||
@@ -525,9 +525,9 @@
|
|||||||
"orderDate": "2024-02-04T23:00:00.000Z",
|
"orderDate": "2024-02-04T23:00:00.000Z",
|
||||||
"name": "Liquid Iodine Plus",
|
"name": "Liquid Iodine Plus",
|
||||||
"quantity": 59,
|
"quantity": 59,
|
||||||
"quantityUnits": "ml",
|
"quantityUnits": "mcl",
|
||||||
"servingUnit": "ml",
|
"servingUnit": "mcl",
|
||||||
"numUnitsInServing": 0.126,
|
"numUnitsInServing": 126,
|
||||||
"numBottles": 1
|
"numBottles": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
237
poetry.lock
generated
237
poetry.lock
generated
@@ -22,6 +22,24 @@ files = [
|
|||||||
{file = "astroid-3.0.2.tar.gz", hash = "sha256:4a61cf0a59097c7bb52689b0fd63717cd2a8a14dc9f1eee97b82d814881c8c91"},
|
{file = "astroid-3.0.2.tar.gz", hash = "sha256:4a61cf0a59097c7bb52689b0fd63717cd2a8a14dc9f1eee97b82d814881c8c91"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "asttokens"
|
||||||
|
version = "2.4.1"
|
||||||
|
description = "Annotate AST trees with source code positions"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
files = [
|
||||||
|
{file = "asttokens-2.4.1-py2.py3-none-any.whl", hash = "sha256:051ed49c3dcae8913ea7cd08e46a606dba30b79993209636c4875bc1d637bc24"},
|
||||||
|
{file = "asttokens-2.4.1.tar.gz", hash = "sha256:b03869718ba9a6eb027e134bfdf69f38a236d681c83c160d510768af11254ba0"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
six = ">=1.12.0"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
astroid = ["astroid (>=1,<2)", "astroid (>=2,<4)"]
|
||||||
|
test = ["astroid (>=1,<2)", "astroid (>=2,<4)", "pytest"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "black"
|
name = "black"
|
||||||
version = "24.1.1"
|
version = "24.1.1"
|
||||||
@@ -91,6 +109,17 @@ files = [
|
|||||||
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
|
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "decorator"
|
||||||
|
version = "5.1.1"
|
||||||
|
description = "Decorators for Humans"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.5"
|
||||||
|
files = [
|
||||||
|
{file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"},
|
||||||
|
{file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"},
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dill"
|
name = "dill"
|
||||||
version = "0.3.8"
|
version = "0.3.8"
|
||||||
@@ -106,6 +135,20 @@ files = [
|
|||||||
graph = ["objgraph (>=1.7.2)"]
|
graph = ["objgraph (>=1.7.2)"]
|
||||||
profile = ["gprof2dot (>=2022.7.29)"]
|
profile = ["gprof2dot (>=2022.7.29)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "executing"
|
||||||
|
version = "2.0.1"
|
||||||
|
description = "Get the currently executing AST node of a frame, and other information"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.5"
|
||||||
|
files = [
|
||||||
|
{file = "executing-2.0.1-py2.py3-none-any.whl", hash = "sha256:eac49ca94516ccc753f9fb5ce82603156e590b27525a8bc32cce8ae302eb61bc"},
|
||||||
|
{file = "executing-2.0.1.tar.gz", hash = "sha256:35afe2ce3affba8ee97f2d69927fa823b08b472b7b994e36a52a964b93d16147"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipython", "littleutils", "pytest", "rich"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "greenlet"
|
name = "greenlet"
|
||||||
version = "3.0.3"
|
version = "3.0.3"
|
||||||
@@ -177,6 +220,41 @@ files = [
|
|||||||
docs = ["Sphinx", "furo"]
|
docs = ["Sphinx", "furo"]
|
||||||
test = ["objgraph", "psutil"]
|
test = ["objgraph", "psutil"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ipython"
|
||||||
|
version = "8.21.0"
|
||||||
|
description = "IPython: Productive Interactive Computing"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.10"
|
||||||
|
files = [
|
||||||
|
{file = "ipython-8.21.0-py3-none-any.whl", hash = "sha256:1050a3ab8473488d7eee163796b02e511d0735cf43a04ba2a8348bd0f2eaf8a5"},
|
||||||
|
{file = "ipython-8.21.0.tar.gz", hash = "sha256:48fbc236fbe0e138b88773fa0437751f14c3645fb483f1d4c5dee58b37e5ce73"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
colorama = {version = "*", markers = "sys_platform == \"win32\""}
|
||||||
|
decorator = "*"
|
||||||
|
jedi = ">=0.16"
|
||||||
|
matplotlib-inline = "*"
|
||||||
|
pexpect = {version = ">4.3", markers = "sys_platform != \"win32\""}
|
||||||
|
prompt-toolkit = ">=3.0.41,<3.1.0"
|
||||||
|
pygments = ">=2.4.0"
|
||||||
|
stack-data = "*"
|
||||||
|
traitlets = ">=5"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
all = ["black", "curio", "docrepr", "exceptiongroup", "ipykernel", "ipyparallel", "ipywidgets", "matplotlib", "matplotlib (!=3.2.0)", "nbconvert", "nbformat", "notebook", "numpy (>=1.23)", "pandas", "pickleshare", "pytest (<8)", "pytest-asyncio (<0.22)", "qtconsole", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "trio", "typing-extensions"]
|
||||||
|
black = ["black"]
|
||||||
|
doc = ["docrepr", "exceptiongroup", "ipykernel", "matplotlib", "pickleshare", "pytest (<8)", "pytest-asyncio (<0.22)", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "typing-extensions"]
|
||||||
|
kernel = ["ipykernel"]
|
||||||
|
nbconvert = ["nbconvert"]
|
||||||
|
nbformat = ["nbformat"]
|
||||||
|
notebook = ["ipywidgets", "notebook"]
|
||||||
|
parallel = ["ipyparallel"]
|
||||||
|
qtconsole = ["qtconsole"]
|
||||||
|
test = ["pickleshare", "pytest (<8)", "pytest-asyncio (<0.22)", "testpath"]
|
||||||
|
test-extra = ["curio", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.23)", "pandas", "pickleshare", "pytest (<8)", "pytest-asyncio (<0.22)", "testpath", "trio"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "isort"
|
name = "isort"
|
||||||
version = "5.13.2"
|
version = "5.13.2"
|
||||||
@@ -191,6 +269,25 @@ files = [
|
|||||||
[package.extras]
|
[package.extras]
|
||||||
colors = ["colorama (>=0.4.6)"]
|
colors = ["colorama (>=0.4.6)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jedi"
|
||||||
|
version = "0.19.1"
|
||||||
|
description = "An autocompletion tool for Python that can be used for text editors."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.6"
|
||||||
|
files = [
|
||||||
|
{file = "jedi-0.19.1-py2.py3-none-any.whl", hash = "sha256:e983c654fe5c02867aef4cdfce5a2fbb4a50adc0af145f70504238f18ef5e7e0"},
|
||||||
|
{file = "jedi-0.19.1.tar.gz", hash = "sha256:cf0496f3651bc65d7174ac1b7d043eff454892c708a87d1b683e57b569927ffd"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
parso = ">=0.8.3,<0.9.0"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
docs = ["Jinja2 (==2.11.3)", "MarkupSafe (==1.1.1)", "Pygments (==2.8.1)", "alabaster (==0.7.12)", "babel (==2.9.1)", "chardet (==4.0.0)", "commonmark (==0.8.1)", "docutils (==0.17.1)", "future (==0.18.2)", "idna (==2.10)", "imagesize (==1.2.0)", "mock (==1.0.1)", "packaging (==20.9)", "pyparsing (==2.4.7)", "pytz (==2021.1)", "readthedocs-sphinx-ext (==2.1.4)", "recommonmark (==0.5.0)", "requests (==2.25.1)", "six (==1.15.0)", "snowballstemmer (==2.1.0)", "sphinx (==1.8.5)", "sphinx-rtd-theme (==0.4.3)", "sphinxcontrib-serializinghtml (==1.1.4)", "sphinxcontrib-websupport (==1.2.4)", "urllib3 (==1.26.4)"]
|
||||||
|
qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"]
|
||||||
|
testing = ["Django", "attrs", "colorama", "docopt", "pytest (<7.0.0)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "markdown-it-py"
|
name = "markdown-it-py"
|
||||||
version = "3.0.0"
|
version = "3.0.0"
|
||||||
@@ -215,6 +312,20 @@ profiling = ["gprof2dot"]
|
|||||||
rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"]
|
rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"]
|
||||||
testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"]
|
testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "matplotlib-inline"
|
||||||
|
version = "0.1.6"
|
||||||
|
description = "Inline Matplotlib backend for Jupyter"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.5"
|
||||||
|
files = [
|
||||||
|
{file = "matplotlib-inline-0.1.6.tar.gz", hash = "sha256:f887e5f10ba98e8d2b150ddcf4702c1e5f8b3a20005eb0f74bfdbd360ee6f304"},
|
||||||
|
{file = "matplotlib_inline-0.1.6-py3-none-any.whl", hash = "sha256:f1f41aab5328aa5aaea9b16d083b128102f8712542f819fe7e6a420ff581b311"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
traitlets = "*"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mccabe"
|
name = "mccabe"
|
||||||
version = "0.7.0"
|
version = "0.7.0"
|
||||||
@@ -300,6 +411,21 @@ files = [
|
|||||||
{file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"},
|
{file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "parso"
|
||||||
|
version = "0.8.3"
|
||||||
|
description = "A Python Parser"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.6"
|
||||||
|
files = [
|
||||||
|
{file = "parso-0.8.3-py2.py3-none-any.whl", hash = "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75"},
|
||||||
|
{file = "parso-0.8.3.tar.gz", hash = "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
qa = ["flake8 (==3.8.3)", "mypy (==0.782)"]
|
||||||
|
testing = ["docopt", "pytest (<6.0.0)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pathspec"
|
name = "pathspec"
|
||||||
version = "0.12.1"
|
version = "0.12.1"
|
||||||
@@ -311,6 +437,20 @@ files = [
|
|||||||
{file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"},
|
{file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pexpect"
|
||||||
|
version = "4.9.0"
|
||||||
|
description = "Pexpect allows easy control of interactive console applications."
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
files = [
|
||||||
|
{file = "pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523"},
|
||||||
|
{file = "pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
ptyprocess = ">=0.5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "platformdirs"
|
name = "platformdirs"
|
||||||
version = "4.2.0"
|
version = "4.2.0"
|
||||||
@@ -326,6 +466,45 @@ files = [
|
|||||||
docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"]
|
docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"]
|
||||||
test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"]
|
test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "prompt-toolkit"
|
||||||
|
version = "3.0.43"
|
||||||
|
description = "Library for building powerful interactive command lines in Python"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7.0"
|
||||||
|
files = [
|
||||||
|
{file = "prompt_toolkit-3.0.43-py3-none-any.whl", hash = "sha256:a11a29cb3bf0a28a387fe5122cdb649816a957cd9261dcedf8c9f1fef33eacf6"},
|
||||||
|
{file = "prompt_toolkit-3.0.43.tar.gz", hash = "sha256:3527b7af26106cbc65a040bcc84839a3566ec1b051bb0bfe953631e704b0ff7d"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
wcwidth = "*"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ptyprocess"
|
||||||
|
version = "0.7.0"
|
||||||
|
description = "Run a subprocess in a pseudo terminal"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
files = [
|
||||||
|
{file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"},
|
||||||
|
{file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pure-eval"
|
||||||
|
version = "0.2.2"
|
||||||
|
description = "Safely evaluate AST nodes without side effects"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
files = [
|
||||||
|
{file = "pure_eval-0.2.2-py3-none-any.whl", hash = "sha256:01eaab343580944bc56080ebe0a674b39ec44a945e6d09ba7db3cb8cec289350"},
|
||||||
|
{file = "pure_eval-0.2.2.tar.gz", hash = "sha256:2b45320af6dfaa1750f543d714b6d1c520a1688dec6fd24d339063ce0aaa9ac3"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
tests = ["pytest"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pydantic"
|
name = "pydantic"
|
||||||
version = "2.6.1"
|
version = "2.6.1"
|
||||||
@@ -533,6 +712,17 @@ files = [
|
|||||||
{file = "ruff-0.2.0.tar.gz", hash = "sha256:63856b91837606c673537d2889989733d7dffde553828d3b0f0bacfa6def54be"},
|
{file = "ruff-0.2.0.tar.gz", hash = "sha256:63856b91837606c673537d2889989733d7dffde553828d3b0f0bacfa6def54be"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "six"
|
||||||
|
version = "1.16.0"
|
||||||
|
description = "Python 2 and 3 compatibility utilities"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
|
||||||
|
files = [
|
||||||
|
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
|
||||||
|
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sqlalchemy"
|
name = "sqlalchemy"
|
||||||
version = "2.0.25"
|
version = "2.0.25"
|
||||||
@@ -635,6 +825,25 @@ files = [
|
|||||||
pydantic = ">=1.10.13,<3.0.0"
|
pydantic = ">=1.10.13,<3.0.0"
|
||||||
SQLAlchemy = ">=2.0.0,<2.1.0"
|
SQLAlchemy = ">=2.0.0,<2.1.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "stack-data"
|
||||||
|
version = "0.6.3"
|
||||||
|
description = "Extract data from python stack frames and tracebacks for informative displays"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
files = [
|
||||||
|
{file = "stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695"},
|
||||||
|
{file = "stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
asttokens = ">=2.1.0"
|
||||||
|
executing = ">=1.2.0"
|
||||||
|
pure-eval = "*"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml"
|
name = "toml"
|
||||||
version = "0.10.2"
|
version = "0.10.2"
|
||||||
@@ -657,6 +866,21 @@ files = [
|
|||||||
{file = "tomlkit-0.12.3.tar.gz", hash = "sha256:75baf5012d06501f07bee5bf8e801b9f343e7aac5a92581f20f80ce632e6b5a4"},
|
{file = "tomlkit-0.12.3.tar.gz", hash = "sha256:75baf5012d06501f07bee5bf8e801b9f343e7aac5a92581f20f80ce632e6b5a4"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "traitlets"
|
||||||
|
version = "5.14.1"
|
||||||
|
description = "Traitlets Python configuration system"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "traitlets-5.14.1-py3-none-any.whl", hash = "sha256:2e5a030e6eff91737c643231bfcf04a65b0132078dad75e4936700b213652e74"},
|
||||||
|
{file = "traitlets-5.14.1.tar.gz", hash = "sha256:8585105b371a04b8316a43d5ce29c098575c2e477850b62b848b964f1444527e"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"]
|
||||||
|
test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "pre-commit", "pytest (>=7.0,<7.5)", "pytest-mock", "pytest-mypy-testing"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typer"
|
name = "typer"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
@@ -689,7 +913,18 @@ files = [
|
|||||||
{file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"},
|
{file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wcwidth"
|
||||||
|
version = "0.2.13"
|
||||||
|
description = "Measures the displayed width of unicode strings in a terminal"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
files = [
|
||||||
|
{file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"},
|
||||||
|
{file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"},
|
||||||
|
]
|
||||||
|
|
||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.0"
|
lock-version = "2.0"
|
||||||
python-versions = "^3.12"
|
python-versions = "^3.12"
|
||||||
content-hash = "79f33e8ba6a5c94a5af2c291bedb84d6131bd7bde31396e2f4348c1fdacfa3b2"
|
content-hash = "f1694f707d895f0211021d90acef4c7449085c1c2cac19bbb35f14a7820dcaa6"
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ python-dotenv = "^1.0.1"
|
|||||||
pylint = "^3.0.3"
|
pylint = "^3.0.3"
|
||||||
ruff = "^0.2.0"
|
ruff = "^0.2.0"
|
||||||
black = "^24.1.1"
|
black = "^24.1.1"
|
||||||
|
ipython = "^8.21.0"
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["poetry-core"]
|
requires = ["poetry-core"]
|
||||||
|
|||||||
@@ -1,38 +1,180 @@
|
|||||||
|
import collections
|
||||||
from dataclasses import asdict
|
from dataclasses import asdict
|
||||||
|
import datetime as dt
|
||||||
|
import hashlib
|
||||||
import json
|
import json
|
||||||
import pathlib as pl
|
import pathlib as pl
|
||||||
import tomllib
|
import tomllib
|
||||||
|
|
||||||
from sup.commands import add_user_supp_consumption
|
from mysql.connector.errors import IntegrityError
|
||||||
|
|
||||||
|
from sup.commands import (
|
||||||
|
add_user_supp_consumption,
|
||||||
|
add_inventory,
|
||||||
|
add_product,
|
||||||
|
add_alias,
|
||||||
|
set_fill_every_x_days,
|
||||||
|
set_last_fill_date,
|
||||||
|
create_user,
|
||||||
|
)
|
||||||
from sup.models import Supp
|
from sup.models import Supp
|
||||||
from sup.queries import get_product_id_from_alias
|
from sup.queries import product_is_in_products_table, alias_exists, inventory_exists, consumption_exists
|
||||||
|
from sup.sql_funcs import commit
|
||||||
|
|
||||||
|
def load_config():
|
||||||
|
return tomllib.loads(pl.Path('supps.toml').read_text())
|
||||||
|
|
||||||
|
CONFIG = load_config()
|
||||||
|
|
||||||
|
|
||||||
def transfer_inventory_to_sql():
|
def transfer_inventory_to_sql():
|
||||||
...
|
inventory = json.loads(pl.Path('inventory.json').read_text())
|
||||||
|
for i in inventory:
|
||||||
|
add_inventory(
|
||||||
|
user_id='zev@averba.ch',
|
||||||
|
product_name=i['name'],
|
||||||
|
order_date=i['orderDate'][:10],
|
||||||
|
num_bottles=i['numBottles']
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def transfer_supps_consumption_toml_to_sql():
|
def populate_products():
|
||||||
toml = tomllib.loads(pl.Path('supps.toml').read_text())
|
inventory = json.loads(pl.Path('inventory.json').read_text())
|
||||||
for sup in toml["supps"]:
|
for i in inventory:
|
||||||
|
try:
|
||||||
|
add_product(
|
||||||
|
name=i['name'],
|
||||||
|
quantity=i['quantity'],
|
||||||
|
num_units_in_serving=i['numUnitsInServing'],
|
||||||
|
quantity_units=i['quantityUnits'],
|
||||||
|
serving_units=i['servingUnit'],
|
||||||
|
)
|
||||||
|
except KeyError:
|
||||||
|
print(i)
|
||||||
|
raise
|
||||||
|
except IntegrityError:
|
||||||
|
print('already exists')
|
||||||
|
continue
|
||||||
|
commit()
|
||||||
|
|
||||||
|
|
||||||
|
def transfer_supps_consumption_toml_to_sql() -> None:
|
||||||
|
aliases = {v: k for k, v in CONFIG['product_aliases'].items()}
|
||||||
|
product_names = [i['name'] for i in json.loads(pl.Path('inventory.json').read_text())]
|
||||||
|
for sup in CONFIG["supps"]:
|
||||||
si = Supp(**sup)
|
si = Supp(**sup)
|
||||||
si_dict = asdict(si)
|
si_dict = asdict(si)
|
||||||
name = si_dict['name']
|
_name = si_dict['name']
|
||||||
del si_dict['name']
|
del si_dict['name']
|
||||||
product_id = get_product_id_from_alias(name)
|
name = None
|
||||||
|
if _name.lower() in aliases:
|
||||||
add_user_supp_consumption(**si_dict)
|
name = aliases[_name.lower()]
|
||||||
|
else:
|
||||||
|
for pn in product_names:
|
||||||
|
if _name.lower() in pn.lower():
|
||||||
|
name = pn
|
||||||
|
break
|
||||||
|
name = name or _name
|
||||||
|
print(name)
|
||||||
|
add_user_supp_consumption(name=name, user_id="zev@averba.ch", **si_dict)
|
||||||
|
commit()
|
||||||
|
|
||||||
|
|
||||||
def transfer_config_to_sql():
|
def transfer_config_to_sql():
|
||||||
"""fill every x days and last fill"""
|
"""fill every x days and last fill"""
|
||||||
...
|
fill_every_x_days = CONFIG['FILL_EVERY_X_DAYS']
|
||||||
|
last_fill_date = CONFIG['LAST_FILL_DATE']
|
||||||
|
set_fill_every_x_days(value=fill_every_x_days, user_id="zev@averba.ch")
|
||||||
|
set_last_fill_date(value=last_fill_date, user_id="zev@averba.ch")
|
||||||
|
commit()
|
||||||
|
|
||||||
|
|
||||||
def transfer_aliases_to_sql():
|
def transfer_aliases_to_sql():
|
||||||
...
|
aliases = CONFIG['product_aliases']
|
||||||
|
for name, alias in aliases.items():
|
||||||
|
add_alias("zev@averba.ch", name, alias)
|
||||||
|
commit()
|
||||||
|
|
||||||
|
|
||||||
transfer_config_to_sql()
|
def validate_products_against_toml():
|
||||||
transfer_aliases_to_sql()
|
inventory = json.loads(pl.Path('inventory.json').read_text())
|
||||||
transfer_supps_consumption_toml_to_sql()
|
for i in inventory:
|
||||||
transfer_inventory_to_sql()
|
if not product_is_in_products_table(name=i['name'], q=round(i['quantity']), num_units=i['numUnitsInServing']):
|
||||||
|
raise Exception
|
||||||
|
|
||||||
|
|
||||||
|
def do_create_user():
|
||||||
|
email = "zev@averba.ch"
|
||||||
|
pw = "@m_6Lwx.CjqvfwG@hmT"
|
||||||
|
first_name = "Zev"
|
||||||
|
last_name = "Averbach"
|
||||||
|
pw_hash = hashlib.new("SHA256")
|
||||||
|
pw_hash.update(pw.encode())
|
||||||
|
create_user(email, pw_hash.hexdigest(), first_name, last_name, dt.date.today(), 30)
|
||||||
|
commit()
|
||||||
|
print("okay, created user")
|
||||||
|
|
||||||
|
|
||||||
|
def validate_product_aliases():
|
||||||
|
aliases = CONFIG['product_aliases']
|
||||||
|
for name, alias in aliases.items():
|
||||||
|
if not alias_exists(name=name, alias=alias, user_id="zev@averba.ch"):
|
||||||
|
raise Exception
|
||||||
|
|
||||||
|
|
||||||
|
def validate_inventory():
|
||||||
|
inventory = json.loads(pl.Path('inventory.json').read_text())
|
||||||
|
for i in inventory:
|
||||||
|
if not inventory_exists(
|
||||||
|
user_id="zev@averba.ch",
|
||||||
|
product_name=i['name'],
|
||||||
|
order_date=i['orderDate'][:10],
|
||||||
|
quantity=i['quantity'],
|
||||||
|
serving_q=i["numUnitsInServing"],
|
||||||
|
):
|
||||||
|
raise Exception
|
||||||
|
# PRIMARY KEY (user_id, product_id, order_date)
|
||||||
|
|
||||||
|
def validate_consumption():
|
||||||
|
"""
|
||||||
|
Something is wrong here; several of the items in supps.toml are missing from
|
||||||
|
the consumption table.
|
||||||
|
"""
|
||||||
|
aliases = collections.defaultdict(list)
|
||||||
|
for k, v in CONFIG['product_aliases'].items():
|
||||||
|
aliases[v].append(k)
|
||||||
|
product_names = [i['name'] for i in json.loads(pl.Path('inventory.json').read_text())]
|
||||||
|
for sup in CONFIG["supps"]:
|
||||||
|
si = Supp(**sup)
|
||||||
|
si_dict = asdict(si)
|
||||||
|
_name = si_dict['name']
|
||||||
|
del si_dict['name']
|
||||||
|
name = None
|
||||||
|
names = None
|
||||||
|
if _name in aliases:
|
||||||
|
names = aliases[_name]
|
||||||
|
print(f"{names=}")
|
||||||
|
else:
|
||||||
|
for pn in product_names:
|
||||||
|
if _name.lower() in pn.lower():
|
||||||
|
name = pn
|
||||||
|
break
|
||||||
|
if names:
|
||||||
|
if not any(consumption_exists(name=n, user_id="zev@averba.ch", **si_dict) for n in names):
|
||||||
|
raise Exception
|
||||||
|
else:
|
||||||
|
name = name or _name
|
||||||
|
if not consumption_exists(name=name, user_id="zev@averba.ch", **si_dict):
|
||||||
|
raise Exception
|
||||||
|
|
||||||
|
|
||||||
|
# do_create_user()
|
||||||
|
# populate_products()
|
||||||
|
# transfer_inventory_to_sql()
|
||||||
|
# transfer_aliases_to_sql()
|
||||||
|
# transfer_supps_consumption_toml_to_sql()
|
||||||
|
# transfer_config_to_sql()
|
||||||
|
# validate_products_against_toml()
|
||||||
|
# validate_product_aliases()
|
||||||
|
# validate_inventory()
|
||||||
|
# validate_consumption()
|
||||||
|
|||||||
@@ -26,10 +26,14 @@ atexit.register(close_everything)
|
|||||||
|
|
||||||
|
|
||||||
def do_query(create_statement: str) -> None:
|
def do_query(create_statement: str) -> None:
|
||||||
|
table_name = None
|
||||||
try:
|
try:
|
||||||
table_name = create_statement.lower().split("create table ")[1].split(" ")[0]
|
table_name = create_statement.lower().split("create table ")[1].split(" ")[0]
|
||||||
except IndexError:
|
except IndexError:
|
||||||
|
try:
|
||||||
table_name = create_statement.lower().split("drop table ")[1].split(" ")[0]
|
table_name = create_statement.lower().split("drop table ")[1].split(" ")[0]
|
||||||
|
except IndexError:
|
||||||
|
pass
|
||||||
print(f"{table_name=}")
|
print(f"{table_name=}")
|
||||||
print(create_statement)
|
print(create_statement)
|
||||||
try:
|
try:
|
||||||
@@ -63,17 +67,18 @@ def create_table_products():
|
|||||||
id INT NOT NULL AUTO_INCREMENT,
|
id INT NOT NULL AUTO_INCREMENT,
|
||||||
name VARCHAR(150) NOT NULL,
|
name VARCHAR(150) NOT NULL,
|
||||||
quantity INT NOT NULL,
|
quantity INT NOT NULL,
|
||||||
quantity_units ENUM('caps', 'mg', 'g', 'ml', 'mcg', 'iu'),
|
quantity_units ENUM('caps', 'mg', 'g', 'ml', 'mcg', 'mcl', 'iu'),
|
||||||
serving_units ENUM('caps', 'mg', 'g', 'ml', 'mcg', 'iu'),
|
serving_units ENUM('caps', 'mg', 'g', 'ml', 'mcg', 'mcl', 'iu'),
|
||||||
num_units_in_serving INT NOT NULL,
|
num_units_in_serving INT NOT NULL,
|
||||||
CONSTRAINT pk_product PRIMARY KEY (id)
|
CONSTRAINT pk_product_name PRIMARY KEY (id, name),
|
||||||
|
CONSTRAINT uq_name_quantity_numunitsinserving UNIQUE KEY (name, quantity, num_units_in_serving)
|
||||||
)""").strip()
|
)""").strip()
|
||||||
do_query(query)
|
do_query(query)
|
||||||
|
|
||||||
|
|
||||||
def create_table_product_aliases():
|
def create_table_product_aliases():
|
||||||
query = ("""
|
query = ("""
|
||||||
create table user_product_aliases (
|
CREATE TABLE user_product_aliases (
|
||||||
user_id VARCHAR(80) NOT NULL,
|
user_id VARCHAR(80) NOT NULL,
|
||||||
product_id INT NOT NULL,
|
product_id INT NOT NULL,
|
||||||
alias VARCHAR(30) NOT NULL,
|
alias VARCHAR(30) NOT NULL,
|
||||||
@@ -90,11 +95,11 @@ def create_table_user_supplements_consumption():
|
|||||||
user_id VARCHAR(80) NOT NULL,
|
user_id VARCHAR(80) NOT NULL,
|
||||||
product_id INT NOT NULL,
|
product_id INT NOT NULL,
|
||||||
morning INT DEFAULT 0,
|
morning INT DEFAULT 0,
|
||||||
lunch INT NULL,
|
lunch INT DEFAULT 0,
|
||||||
dinner INT NULL,
|
dinner INT DEFAULT 0,
|
||||||
bedtime INT NULL,
|
bedtime INT DEFAULT 0,
|
||||||
days_per_week INT DEFAULT 7,
|
days_per_week INT DEFAULT 7,
|
||||||
units ENUM('caps', 'mg', 'g', 'ml', 'mcg', 'iu'),
|
units ENUM('caps', 'mg', 'g', 'ml', 'mcg', 'mcl', 'iu'),
|
||||||
winter_only BOOL DEFAULT false,
|
winter_only BOOL DEFAULT false,
|
||||||
CONSTRAINT fk_user_id FOREIGN KEY (user_id) REFERENCES users(id),
|
CONSTRAINT fk_user_id FOREIGN KEY (user_id) REFERENCES users(id),
|
||||||
CONSTRAINT fk_product_id FOREIGN KEY (product_id) REFERENCES products(id),
|
CONSTRAINT fk_product_id FOREIGN KEY (product_id) REFERENCES products(id),
|
||||||
@@ -103,7 +108,7 @@ def create_table_user_supplements_consumption():
|
|||||||
do_query(query)
|
do_query(query)
|
||||||
|
|
||||||
|
|
||||||
def create_table_user_supplements_inventory():
|
def create_table_user_supplements_orders():
|
||||||
query = ("""
|
query = ("""
|
||||||
create table user_supplements_orders (
|
create table user_supplements_orders (
|
||||||
user_id VARCHAR(80) NOT NULL,
|
user_id VARCHAR(80) NOT NULL,
|
||||||
@@ -130,19 +135,30 @@ def create_tables1():
|
|||||||
return
|
return
|
||||||
|
|
||||||
def create_tables2():
|
def create_tables2():
|
||||||
# try:
|
try:
|
||||||
# create_table_user_supplements_consumption()
|
create_table_user_supplements_consumption()
|
||||||
# except Exception:
|
except Exception:
|
||||||
# return
|
return
|
||||||
# create_table_user_supplements_inventory()
|
try:
|
||||||
|
create_table_user_supplements_orders()
|
||||||
|
except Exception:
|
||||||
|
do_query("drop table user_supplements_consumption")
|
||||||
|
try:
|
||||||
create_table_product_aliases()
|
create_table_product_aliases()
|
||||||
|
except Exception:
|
||||||
def drop_tables():
|
|
||||||
do_query("drop table users")
|
|
||||||
do_query("drop table products")
|
|
||||||
do_query("drop table user_supplements_consumption")
|
do_query("drop table user_supplements_consumption")
|
||||||
do_query("drop table user_supplements_orders")
|
do_query("drop table user_supplements_orders")
|
||||||
|
|
||||||
|
|
||||||
# drop_tables()
|
def drop_tables():
|
||||||
create_tables2()
|
do_query("drop table user_supplements_consumption")
|
||||||
|
do_query("drop table user_supplements_orders")
|
||||||
|
do_query("drop table user_product_aliases")
|
||||||
|
do_query("drop table users")
|
||||||
|
do_query("drop table products")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
drop_tables()
|
||||||
|
create_tables1()
|
||||||
|
create_tables2()
|
||||||
|
|||||||
@@ -12,10 +12,9 @@ from sup.main import (
|
|||||||
load_config,
|
load_config,
|
||||||
SUPP_CONSUMPTION_FP,
|
SUPP_CONSUMPTION_FP,
|
||||||
load_inventory,
|
load_inventory,
|
||||||
CONFIG,
|
|
||||||
ALIASES_REV,
|
|
||||||
)
|
)
|
||||||
from sup.models import Supp
|
from sup.models import Supp
|
||||||
|
from sup.queries import get_user
|
||||||
|
|
||||||
|
|
||||||
app = typer.Typer()
|
app = typer.Typer()
|
||||||
@@ -97,20 +96,20 @@ def status():
|
|||||||
TODO: this doesn't seem to sense pending orders which have been added to inventory.json
|
TODO: this doesn't seem to sense pending orders which have been added to inventory.json
|
||||||
maybe because the delivery date is in the present/future?
|
maybe because the delivery date is in the present/future?
|
||||||
"""
|
"""
|
||||||
validate_matches()
|
# TODO: make this an arg
|
||||||
|
user_id = "zev@averba.ch"
|
||||||
config = load_config()
|
|
||||||
|
|
||||||
num_days_of_inventory_needed = config["FILL_EVERY_X_DAYS"]
|
|
||||||
|
|
||||||
|
user = get_user(user_id)
|
||||||
|
num_days_of_inventory_needed = user["fill_every_x_days"]
|
||||||
last_fill_date = dt.datetime.strptime(
|
last_fill_date = dt.datetime.strptime(
|
||||||
config["LAST_FILL_DATE"], "%Y-%m-%d"
|
user["last_fill_date"], "%Y-%m-%d"
|
||||||
).date()
|
).date()
|
||||||
next_fill_date = last_fill_date + dt.timedelta(num_days_of_inventory_needed)
|
next_fill_date = last_fill_date + dt.timedelta(num_days_of_inventory_needed)
|
||||||
print()
|
print()
|
||||||
print(f"{next_fill_date=}")
|
print(f"{next_fill_date=}")
|
||||||
print()
|
print()
|
||||||
inventory = load_inventory()
|
# TODO: this is where you left off
|
||||||
|
inventory = load_inventory(user_id)
|
||||||
|
|
||||||
needs = []
|
needs = []
|
||||||
|
|
||||||
@@ -185,7 +184,7 @@ def add(
|
|||||||
date = dt.datetime.now().date() # type: ignore
|
date = dt.datetime.now().date() # type: ignore
|
||||||
else:
|
else:
|
||||||
date = date.date() # type: ignore
|
date = date.date() # type: ignore
|
||||||
ordered_supps = load_ordered_supps()
|
ordered_supps = load_ordered_supps(user_id)
|
||||||
order_dict = dict(
|
order_dict = dict(
|
||||||
name=name,
|
name=name,
|
||||||
quantity=quantity,
|
quantity=quantity,
|
||||||
@@ -204,27 +203,7 @@ def save_ordered_supps(ordered_supps: list[dict]) -> None:
|
|||||||
ORDERED_SUPPS_FP.write_text(json.dumps(ordered_supps, indent=2))
|
ORDERED_SUPPS_FP.write_text(json.dumps(ordered_supps, indent=2))
|
||||||
|
|
||||||
|
|
||||||
def _prettify_json():
|
|
||||||
ORDERED_SUPPS_FP.write_text(json.dumps(json.loads(ORDERED_SUPPS_FP.read_text()), indent=2))
|
|
||||||
|
|
||||||
|
|
||||||
def save_config(config: dict) -> None:
|
def save_config(config: dict) -> None:
|
||||||
SUPP_CONSUMPTION_FP.write_text(toml.dumps(config))
|
SUPP_CONSUMPTION_FP.write_text(toml.dumps(config))
|
||||||
|
|
||||||
|
|
||||||
def validate_matches() -> None:
|
|
||||||
missing = []
|
|
||||||
|
|
||||||
ordered_supp_names_lower = [i["name"].lower() for i in load_ordered_supps()]
|
|
||||||
for i in CONFIG["supps"]:
|
|
||||||
if (
|
|
||||||
not any(
|
|
||||||
i["name"].lower() in ordered_supp_name
|
|
||||||
for ordered_supp_name in ordered_supp_names_lower
|
|
||||||
)
|
|
||||||
and i["name"].lower() not in ALIASES_REV
|
|
||||||
):
|
|
||||||
missing.append(i["name"])
|
|
||||||
|
|
||||||
if missing:
|
|
||||||
raise Missing(", ".join(missing))
|
|
||||||
|
|||||||
@@ -1,19 +1,60 @@
|
|||||||
import datetime as dt
|
import datetime as dt
|
||||||
|
import typing as t
|
||||||
|
|
||||||
from sup.sql_funcs import do_query
|
from sup.sql_funcs import do_query
|
||||||
|
|
||||||
|
|
||||||
|
def set_fill_every_x_days(value: int, user_id: str) -> None:
|
||||||
|
do_query(query=f"update users set fill_every_x_days = {value} where id = '{user_id}'")
|
||||||
|
|
||||||
|
|
||||||
|
def set_last_fill_date(value: int, user_id: str) -> None:
|
||||||
|
do_query(query=f"update users set last_fill_date = '{value}' where id = '{user_id}'")
|
||||||
|
|
||||||
|
|
||||||
def create_user(email: str, pw_hash: str, first_name: str, last_name: str, last_fill_date: dt.date, fill_every_x_days=30):
|
def create_user(email: str, pw_hash: str, first_name: str, last_name: str, last_fill_date: dt.date, fill_every_x_days=30):
|
||||||
do_query(
|
do_query(commit=True,
|
||||||
"insert into users (id, pw_hash, first_name, last_name, last_fill_date, fill_every_x_days) values "
|
query="insert into users (id, pw_hash, first_name, last_name, last_fill_date, fill_every_x_days) values "
|
||||||
f"('{email}', '{pw_hash}', '{first_name}', '{last_name}', '{last_fill_date}', '{fill_every_x_days}')"
|
f"('{email}', '{pw_hash}', '{first_name}', '{last_name}', '{last_fill_date}', '{fill_every_x_days}')"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def add_user_supp_consumption(user_id: str, product_id: int, morning: int, lunch: int, dinner: int, bedtime: int, days_per_week: int, units: str, winter_only: bool):
|
def add_user_supp_consumption(user_id: str, name: str, morning: int, lunch: int, dinner: int, bedtime: int, days_per_week: int, units: str, winter_only: bool):
|
||||||
do_query(
|
do_query(
|
||||||
"insert into user_supplements_consumption "
|
"insert into user_supplements_consumption "
|
||||||
"(user_id, product_id, morning, lunch, dinner, bedtime, days_per_week, units, winter_only) values ("
|
"(user_id, product_id, morning, lunch, dinner, bedtime, days_per_week, units, winter_only) "
|
||||||
f"('{user_id}', {product_id}, {morning}, {lunch}, {dinner}, {bedtime}, {days_per_week}, '{units}', {winter_only}"
|
f"select '{user_id}', id, {morning}, {lunch}, {dinner}, {bedtime}, {days_per_week}, '{units}', {winter_only} "
|
||||||
|
f"from products where name = '{name}'"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def add_inventory(user_id: str, product_name: str, order_date: str, num_bottles: int) -> None:
|
||||||
|
do_query(
|
||||||
|
"insert into user_supplements_orders (user_id, product_id, order_date, num_bottles) "
|
||||||
|
f"select '{user_id}', id, '{order_date}', {num_bottles} "
|
||||||
|
f"from products where name = '{product_name}'"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def add_product(
|
||||||
|
name: str,
|
||||||
|
quantity: int,
|
||||||
|
quantity_units: t.Literal['caps', 'mg', 'g', 'ml', 'mcg', 'iu'],
|
||||||
|
serving_units: t.Literal['caps', 'mg', 'g', 'ml', 'mcg', 'iu'],
|
||||||
|
num_units_in_serving: int,
|
||||||
|
):
|
||||||
|
query = (
|
||||||
|
"insert into products (name, quantity, quantity_units, serving_units, num_units_in_serving) values ("
|
||||||
|
f"'{name}', {quantity}, '{quantity_units}', '{serving_units}', {num_units_in_serving}"
|
||||||
")"
|
")"
|
||||||
)
|
)
|
||||||
|
do_query(query)
|
||||||
|
|
||||||
|
|
||||||
|
def add_alias(user_id: str, name: str, alias: str) -> None:
|
||||||
|
query = (
|
||||||
|
"insert into user_product_aliases (user_id, product_id, alias) "
|
||||||
|
f"select '{user_id}', id, '{alias}' "
|
||||||
|
f"from products where name = '{name}'"
|
||||||
|
)
|
||||||
|
do_query(query)
|
||||||
|
|||||||
@@ -18,14 +18,14 @@ def load_config():
|
|||||||
return tomllib.loads(SUPP_CONSUMPTION_FP.read_text())
|
return tomllib.loads(SUPP_CONSUMPTION_FP.read_text())
|
||||||
|
|
||||||
|
|
||||||
def load_ordered_supps() -> list[dict]:
|
def load_ordered_supps(user_id: str) -> list[dict]:
|
||||||
return json.loads(ORDERED_SUPPS_FP.read_text())
|
return json.loads(ORDERED_SUPPS_FP.read_text())
|
||||||
|
|
||||||
|
|
||||||
CONFIG = load_config()
|
CONFIG = load_config()
|
||||||
ALIASES = CONFIG["product_aliases"]
|
ALIASES = CONFIG["product_aliases"]
|
||||||
|
|
||||||
ordered_supps = load_ordered_supps()
|
ordered_supps = load_ordered_supps(user_id)
|
||||||
|
|
||||||
for i in CONFIG["supps"]:
|
for i in CONFIG["supps"]:
|
||||||
for ordered_supp in ordered_supps:
|
for ordered_supp in ordered_supps:
|
||||||
@@ -35,9 +35,9 @@ for i in CONFIG["supps"]:
|
|||||||
ALIASES_REV = {v: k for k, v in ALIASES.items()}
|
ALIASES_REV = {v: k for k, v in ALIASES.items()}
|
||||||
|
|
||||||
|
|
||||||
def load_inventory():
|
def load_inventory(user_id: str):
|
||||||
inventory = collections.defaultdict(list)
|
inventory = collections.defaultdict(list)
|
||||||
for s in load_ordered_supps():
|
for s in load_ordered_supps(user_id):
|
||||||
if s["name"] not in CONFIG["discontinued"]:
|
if s["name"] not in CONFIG["discontinued"]:
|
||||||
inventory[ALIASES[s["name"]]].append(s)
|
inventory[ALIASES[s["name"]]].append(s)
|
||||||
return inventory
|
return inventory
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ class Supp:
|
|||||||
dinner: int | float = 0
|
dinner: int | float = 0
|
||||||
bedtime: int | float = 0
|
bedtime: int | float = 0
|
||||||
days_per_week: int = 7
|
days_per_week: int = 7
|
||||||
units: t.Literal["caps", "mg", "g", "ml", "mcg", "iu"] = "mg"
|
units: t.Literal["caps", "mg", "g", "ml", "mcg", "mcl", "iu"] = "mg"
|
||||||
winter_only: bool = False
|
winter_only: bool = False
|
||||||
|
|
||||||
def __mul__(self, other: int) -> float:
|
def __mul__(self, other: int) -> float:
|
||||||
|
|||||||
@@ -1,10 +1,59 @@
|
|||||||
from sup.sql_funcs import do_query
|
from sup.sql_funcs import do_query
|
||||||
|
|
||||||
|
|
||||||
class NoResult(Exception):
|
def get_user(user_id: str):
|
||||||
pass
|
return do_query(f"select * from users where id = '{user_id}'", get_result=True, return_dict=True)[0] # type: ignore
|
||||||
|
|
||||||
|
|
||||||
def get_product_id_from_alias(alias: str):
|
def product_is_in_products_table(name: str, q: int, num_units: int) -> bool:
|
||||||
res = do_query(f"select
|
query = f"select count(*) from products where name = '{name}' and quantity = {q} and num_units_in_serving = {num_units}"
|
||||||
raise NoResult(f"no product_id for alias '{alias}'")
|
res = do_query(get_result=True, query=query)
|
||||||
|
if res[0][0] != 1: # type: ignore
|
||||||
|
print(res[0][0]) # type: ignore
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def alias_exists(name: str, alias: str, user_id: str) -> bool:
|
||||||
|
query = (
|
||||||
|
f"select count(*) from user_product_aliases where user_id = '{user_id}' "
|
||||||
|
f"and alias = '{alias}' and product_id = (select id from products where name = '{name}')"
|
||||||
|
)
|
||||||
|
res = do_query(get_result=True, query=query)
|
||||||
|
if res[0][0] != 1: # type: ignore
|
||||||
|
print(res[0][0]) # type: ignore
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def inventory_exists(user_id: str, product_name: str, order_date: str, quantity: int, serving_q: int):
|
||||||
|
query = (
|
||||||
|
f"select count(*) from user_supplements_orders where user_id = '{user_id}' and order_date = '{order_date}' "
|
||||||
|
"and product_id = ("
|
||||||
|
f"select distinct id from products where name = '{product_name}' and quantity = {quantity} "
|
||||||
|
f"and num_units_in_serving = {serving_q}"
|
||||||
|
")"
|
||||||
|
)
|
||||||
|
res = do_query(get_result=True, query=query)
|
||||||
|
if res[0][0] != 1: # type: ignore
|
||||||
|
print(res[0][0]) # type: ignore
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def consumption_exists(name: str, user_id: str, morning: int, lunch: int, dinner: int, bedtime: int, days_per_week: int, units: str, winter_only: bool):
|
||||||
|
name = name.lower()
|
||||||
|
query = (
|
||||||
|
f"select count(*) from user_supplements_consumption where user_id = '{user_id}' and morning = {morning} "
|
||||||
|
f"and lunch = {lunch} and dinner = {dinner} and bedtime = {bedtime} and days_per_week = {days_per_week} "
|
||||||
|
f"and units = '{units}' and winter_only = {winter_only} "
|
||||||
|
f"and product_id = coalesce("
|
||||||
|
f"(select id from products where name = '{name}' limit 1), "
|
||||||
|
f"(select product_id from user_product_aliases where alias = '{name}' and user_id = '{user_id}' limit 1)"
|
||||||
|
")"
|
||||||
|
)
|
||||||
|
res = do_query(get_result=True, query=query)
|
||||||
|
if res[0][0] != 1: # type: ignore
|
||||||
|
print(res[0][0]) # type: ignore
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
import datetime as dt
|
|
||||||
import hashlib
|
|
||||||
|
|
||||||
from sup.commands import create_user
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
email = input('email? ')
|
|
||||||
pw = input('pw? ')
|
|
||||||
first_name = input('first name? ')
|
|
||||||
last_name = input('last name? ')
|
|
||||||
pw_hash = hashlib.new("SHA256")
|
|
||||||
pw_hash.update(pw.encode())
|
|
||||||
create_user(email, pw_hash.hexdigest(), first_name, last_name, dt.date.today(), 30)
|
|
||||||
print("okay, created user")
|
|
||||||
@@ -15,7 +15,8 @@ cnx = mysql.connector.connect(
|
|||||||
host=os.getenv("PLANETSCALE_HOST"),
|
host=os.getenv("PLANETSCALE_HOST"),
|
||||||
database=DB_NAME,
|
database=DB_NAME,
|
||||||
)
|
)
|
||||||
cursor = cnx.cursor()
|
cursor = cnx.cursor(buffered=True)
|
||||||
|
dict_cursor = cnx.cursor(dictionary=True)
|
||||||
|
|
||||||
def close_everything():
|
def close_everything():
|
||||||
cnx.close()
|
cnx.close()
|
||||||
@@ -24,11 +25,18 @@ def close_everything():
|
|||||||
atexit.register(close_everything)
|
atexit.register(close_everything)
|
||||||
|
|
||||||
|
|
||||||
def do_query(query: str) -> None:
|
def do_query(query: str, get_result: bool = False, commit: bool = False, return_dict: bool = False) -> list | None:
|
||||||
|
print(query)
|
||||||
|
c = cursor if not return_dict else dict_cursor
|
||||||
try:
|
try:
|
||||||
cursor.execute(query)
|
c.execute(query)
|
||||||
except mysql.connector.Error as err:
|
except mysql.connector.Error as err:
|
||||||
print(err.msg)
|
print(err.msg)
|
||||||
raise
|
raise
|
||||||
# TODO: return something if appropriate
|
if commit:
|
||||||
|
cnx.commit()
|
||||||
|
if get_result:
|
||||||
|
return cursor.fetchall()
|
||||||
|
|
||||||
|
def commit():
|
||||||
|
cnx.commit()
|
||||||
|
|||||||
11
supps.toml
11
supps.toml
@@ -16,7 +16,7 @@ morning = 12
|
|||||||
|
|
||||||
[[supps]]
|
[[supps]]
|
||||||
name = "b complex"
|
name = "b complex"
|
||||||
morning = 0.5
|
morning = 1
|
||||||
days_per_week = 2
|
days_per_week = 2
|
||||||
units = "caps"
|
units = "caps"
|
||||||
|
|
||||||
@@ -42,9 +42,8 @@ morning = 500
|
|||||||
|
|
||||||
[[supps]]
|
[[supps]]
|
||||||
name = "creatine"
|
name = "creatine"
|
||||||
lunch = 2.5
|
lunch = 2500
|
||||||
days_per_week = 4
|
days_per_week = 4
|
||||||
units = "g"
|
|
||||||
|
|
||||||
[[supps]]
|
[[supps]]
|
||||||
name = "Ca-AKG"
|
name = "Ca-AKG"
|
||||||
@@ -73,7 +72,7 @@ morning = 67
|
|||||||
days_per_week = 3
|
days_per_week = 3
|
||||||
|
|
||||||
[[supps]]
|
[[supps]]
|
||||||
name = "EPA/DHA"
|
name = "epa/dha"
|
||||||
morning = 1280
|
morning = 1280
|
||||||
dinner = 640
|
dinner = 640
|
||||||
|
|
||||||
@@ -106,8 +105,8 @@ morning = 300
|
|||||||
|
|
||||||
[[supps]]
|
[[supps]]
|
||||||
name = "iodine"
|
name = "iodine"
|
||||||
morning = 0.126
|
morning = 126
|
||||||
units = "ml"
|
units = "mcl"
|
||||||
|
|
||||||
[[supps]]
|
[[supps]]
|
||||||
name = "iron"
|
name = "iron"
|
||||||
|
|||||||
Reference in New Issue
Block a user