Compare commits
5 Commits
190f448b8e
...
9278bfeee9
Author | SHA1 | Date |
---|---|---|
Ethan Paul | 9278bfeee9 | |
Ethan Paul | 5e00046622 | |
Ethan Paul | b86a8e5083 | |
Ethan Paul | bf78aeac98 | |
Ethan Paul | bbcfc30126 |
|
@ -107,3 +107,8 @@ ENV/
|
|||
# Pycharm
|
||||
.idea/
|
||||
.idea/vcs.xml
|
||||
|
||||
# Sqlite database files
|
||||
**.db
|
||||
**.db-shm
|
||||
**.db-wal
|
||||
|
|
|
@ -201,7 +201,7 @@ clikit = ">=0.6.0,<0.7.0"
|
|||
name = "click"
|
||||
version = "8.0.3"
|
||||
description = "Composable command line interface toolkit"
|
||||
category = "dev"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
|
@ -225,7 +225,7 @@ pylev = ">=1.3,<2.0"
|
|||
name = "colorama"
|
||||
version = "0.4.4"
|
||||
description = "Cross-platform colored terminal text."
|
||||
category = "dev"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
|
||||
|
@ -311,6 +311,36 @@ python-versions = ">=3.7"
|
|||
docs = ["furo (>=2021.8.17b43)", "sphinx (>=4.1)", "sphinx-autodoc-typehints (>=1.12)"]
|
||||
testing = ["covdefaults (>=1.2.0)", "coverage (>=4)", "pytest (>=4)", "pytest-cov", "pytest-timeout (>=1.4.2)"]
|
||||
|
||||
[[package]]
|
||||
name = "flask"
|
||||
version = "2.0.2"
|
||||
description = "A simple framework for building complex web applications."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.dependencies]
|
||||
click = ">=7.1.2"
|
||||
itsdangerous = ">=2.0"
|
||||
Jinja2 = ">=3.0"
|
||||
Werkzeug = ">=2.0"
|
||||
|
||||
[package.extras]
|
||||
async = ["asgiref (>=3.2)"]
|
||||
dotenv = ["python-dotenv"]
|
||||
|
||||
[[package]]
|
||||
name = "flask-assets"
|
||||
version = "2.0"
|
||||
description = "Asset management for Flask, to compress and merge CSS and Javascript files."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[package.dependencies]
|
||||
Flask = ">=0.8"
|
||||
webassets = ">=2.0"
|
||||
|
||||
[[package]]
|
||||
name = "gitdb"
|
||||
version = "4.0.9"
|
||||
|
@ -424,6 +454,14 @@ requirements_deprecated_finder = ["pipreqs", "pip-api"]
|
|||
colors = ["colorama (>=0.4.3,<0.5.0)"]
|
||||
plugins = ["setuptools"]
|
||||
|
||||
[[package]]
|
||||
name = "itsdangerous"
|
||||
version = "2.0.1"
|
||||
description = "Safely pass data to untrusted environments and back."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[[package]]
|
||||
name = "jedi"
|
||||
version = "0.18.1"
|
||||
|
@ -451,6 +489,20 @@ python-versions = ">=3.6"
|
|||
test = ["pytest", "pytest-trio", "pytest-asyncio", "testpath", "trio", "async-timeout"]
|
||||
trio = ["trio", "async-generator"]
|
||||
|
||||
[[package]]
|
||||
name = "jinja2"
|
||||
version = "3.0.3"
|
||||
description = "A very fast and expressive template engine."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.dependencies]
|
||||
MarkupSafe = ">=2.0"
|
||||
|
||||
[package.extras]
|
||||
i18n = ["Babel (>=2.7)"]
|
||||
|
||||
[[package]]
|
||||
name = "keyring"
|
||||
version = "21.8.0"
|
||||
|
@ -503,6 +555,14 @@ linkify = ["linkify-it-py (>=1.0,<2.0)"]
|
|||
rtd = ["myst-nb (>=0.11.1,<0.12.0)", "sphinx-book-theme", "sphinx-panels (>=0.4.0,<0.5.0)", "sphinx-copybutton", "sphinx (>=2,<4)", "pyyaml"]
|
||||
testing = ["coverage", "pytest (>=3.6,<4)", "pytest-cov", "pytest-regressions", "pytest-benchmark (>=3.2,<4.0)", "psutil"]
|
||||
|
||||
[[package]]
|
||||
name = "markupsafe"
|
||||
version = "2.0.1"
|
||||
description = "Safely add untrusted strings to HTML/XML markup."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[[package]]
|
||||
name = "matplotlib-inline"
|
||||
version = "0.1.3"
|
||||
|
@ -1182,6 +1242,14 @@ category = "dev"
|
|||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "webassets"
|
||||
version = "2.0"
|
||||
description = "Media asset management for Python, with glue code for various web frameworks"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "webencodings"
|
||||
version = "0.5.1"
|
||||
|
@ -1190,6 +1258,17 @@ category = "dev"
|
|||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "werkzeug"
|
||||
version = "2.0.3"
|
||||
description = "The comprehensive WSGI web application library."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.extras]
|
||||
watchdog = ["watchdog"]
|
||||
|
||||
[[package]]
|
||||
name = "wrapt"
|
||||
version = "1.13.3"
|
||||
|
@ -1201,7 +1280,7 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
|
|||
[metadata]
|
||||
lock-version = "1.1"
|
||||
python-versions = "^3.10"
|
||||
content-hash = "34a315e8100872c11d5de1aabd6411adef473a95c3cb9d63451482ca6067875a"
|
||||
content-hash = "4f4b096207885bcf88a3a525c18e8304f71769e277314dc8e4cd46b8ec7e8edb"
|
||||
|
||||
[metadata.files]
|
||||
appnope = [
|
||||
|
@ -1438,6 +1517,14 @@ filelock = [
|
|||
{file = "filelock-3.4.2-py3-none-any.whl", hash = "sha256:cf0fc6a2f8d26bd900f19bf33915ca70ba4dd8c56903eeb14e1e7a2fd7590146"},
|
||||
{file = "filelock-3.4.2.tar.gz", hash = "sha256:38b4f4c989f9d06d44524df1b24bd19e167d851f19b50bf3e3559952dddc5b80"},
|
||||
]
|
||||
flask = [
|
||||
{file = "Flask-2.0.2-py3-none-any.whl", hash = "sha256:cb90f62f1d8e4dc4621f52106613488b5ba826b2e1e10a33eac92f723093ab6a"},
|
||||
{file = "Flask-2.0.2.tar.gz", hash = "sha256:7b2fb8e934ddd50731893bdcdb00fc8c0315916f9fcd50d22c7cc1a95ab634e2"},
|
||||
]
|
||||
flask-assets = [
|
||||
{file = "Flask-Assets-2.0.tar.gz", hash = "sha256:1dfdea35e40744d46aada72831f7613d67bf38e8b20ccaaa9e91fdc37aa3b8c2"},
|
||||
{file = "Flask_Assets-2.0-py3-none-any.whl", hash = "sha256:2845bd3b479be9db8556801e7ebc2746ce2d9edb4e7b64a1c786ecbfc1e5867b"},
|
||||
]
|
||||
gitdb = [
|
||||
{file = "gitdb-4.0.9-py3-none-any.whl", hash = "sha256:8033ad4e853066ba6ca92050b9df2f89301b8fc8bf7e9324d412a63f8bf1a8fd"},
|
||||
{file = "gitdb-4.0.9.tar.gz", hash = "sha256:bac2fd45c0a1c9cf619e63a90d62bdc63892ef92387424b855792a6cabe789aa"},
|
||||
|
@ -1470,6 +1557,10 @@ isort = [
|
|||
{file = "isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"},
|
||||
{file = "isort-5.10.1.tar.gz", hash = "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"},
|
||||
]
|
||||
itsdangerous = [
|
||||
{file = "itsdangerous-2.0.1-py3-none-any.whl", hash = "sha256:5174094b9637652bdb841a3029700391451bd092ba3db90600dea710ba28e97c"},
|
||||
{file = "itsdangerous-2.0.1.tar.gz", hash = "sha256:9e724d68fc22902a1435351f84c3fb8623f303fffcc566a4cb952df8c572cff0"},
|
||||
]
|
||||
jedi = [
|
||||
{file = "jedi-0.18.1-py2.py3-none-any.whl", hash = "sha256:637c9635fcf47945ceb91cd7f320234a7be540ded6f3e99a50cb6febdfd1ba8d"},
|
||||
{file = "jedi-0.18.1.tar.gz", hash = "sha256:74137626a64a99c8eb6ae5832d99b3bdd7d29a3850fe2aa80a4126b2a7d949ab"},
|
||||
|
@ -1478,6 +1569,10 @@ jeepney = [
|
|||
{file = "jeepney-0.7.1-py3-none-any.whl", hash = "sha256:1b5a0ea5c0e7b166b2f5895b91a08c14de8915afda4407fb5022a195224958ac"},
|
||||
{file = "jeepney-0.7.1.tar.gz", hash = "sha256:fa9e232dfa0c498bd0b8a3a73b8d8a31978304dcef0515adc859d4e096f96f4f"},
|
||||
]
|
||||
jinja2 = [
|
||||
{file = "Jinja2-3.0.3-py3-none-any.whl", hash = "sha256:077ce6014f7b40d03b47d1f1ca4b0fc8328a692bd284016f806ed0eaca390ad8"},
|
||||
{file = "Jinja2-3.0.3.tar.gz", hash = "sha256:611bb273cd68f3b993fabdc4064fc858c5b47a973cb5aa7999ec1ba405c87cd7"},
|
||||
]
|
||||
keyring = [
|
||||
{file = "keyring-21.8.0-py3-none-any.whl", hash = "sha256:4be9cbaaaf83e61d6399f733d113ede7d1c73bc75cb6aeb64eee0f6ac39b30ea"},
|
||||
{file = "keyring-21.8.0.tar.gz", hash = "sha256:1746d3ac913d449a090caf11e9e4af00e26c3f7f7e81027872192b2398b98675"},
|
||||
|
@ -1529,6 +1624,77 @@ markdown-it-py = [
|
|||
{file = "markdown-it-py-0.6.2.tar.gz", hash = "sha256:c3b9f995be0792cbbc8ab2f53d74072eb7ff8a8b622be8d61d38ab879709eca3"},
|
||||
{file = "markdown_it_py-0.6.2-py3-none-any.whl", hash = "sha256:30b3e9f8198dc82a5df0dcb73fd31d56cd9a43bf8a747feb10b2ba74f962bcb1"},
|
||||
]
|
||||
markupsafe = [
|
||||
{file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53"},
|
||||
{file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38"},
|
||||
{file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad"},
|
||||
{file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:add36cb2dbb8b736611303cd3bfcee00afd96471b09cda130da3581cbdc56a6d"},
|
||||
{file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:168cd0a3642de83558a5153c8bd34f175a9a6e7f6dc6384b9655d2697312a646"},
|
||||
{file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4dc8f9fb58f7364b63fd9f85013b780ef83c11857ae79f2feda41e270468dd9b"},
|
||||
{file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:20dca64a3ef2d6e4d5d615a3fd418ad3bde77a47ec8a23d984a12b5b4c74491a"},
|
||||
{file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cdfba22ea2f0029c9261a4bd07e830a8da012291fbe44dc794e488b6c9bb353a"},
|
||||
{file = "MarkupSafe-2.0.1-cp310-cp310-win32.whl", hash = "sha256:99df47edb6bda1249d3e80fdabb1dab8c08ef3975f69aed437cb69d0a5de1e28"},
|
||||
{file = "MarkupSafe-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:e0f138900af21926a02425cf736db95be9f4af72ba1bb21453432a07f6082134"},
|
||||
{file = "MarkupSafe-2.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51"},
|
||||
{file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff"},
|
||||
{file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b"},
|
||||
{file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94"},
|
||||
{file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872"},
|
||||
{file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f"},
|
||||
{file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf5d821ffabf0ef3533c39c518f3357b171a1651c1ff6827325e4489b0e46c3c"},
|
||||
{file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0d4b31cc67ab36e3392bbf3862cfbadac3db12bdd8b02a2731f509ed5b829724"},
|
||||
{file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:baa1a4e8f868845af802979fcdbf0bb11f94f1cb7ced4c4b8a351bb60d108145"},
|
||||
{file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:deb993cacb280823246a026e3b2d81c493c53de6acfd5e6bfe31ab3402bb37dd"},
|
||||
{file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:63f3268ba69ace99cab4e3e3b5840b03340efed0948ab8f78d2fd87ee5442a4f"},
|
||||
{file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:8d206346619592c6200148b01a2142798c989edcb9c896f9ac9722a99d4e77e6"},
|
||||
{file = "MarkupSafe-2.0.1-cp36-cp36m-win32.whl", hash = "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d"},
|
||||
{file = "MarkupSafe-2.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9"},
|
||||
{file = "MarkupSafe-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567"},
|
||||
{file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18"},
|
||||
{file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f"},
|
||||
{file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f"},
|
||||
{file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2"},
|
||||
{file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d"},
|
||||
{file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9936f0b261d4df76ad22f8fee3ae83b60d7c3e871292cd42f40b81b70afae85"},
|
||||
{file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2a7d351cbd8cfeb19ca00de495e224dea7e7d919659c2841bbb7f420ad03e2d6"},
|
||||
{file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:60bf42e36abfaf9aff1f50f52644b336d4f0a3fd6d8a60ca0d054ac9f713a864"},
|
||||
{file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d6c7ebd4e944c85e2c3421e612a7057a2f48d478d79e61800d81468a8d842207"},
|
||||
{file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f0567c4dc99f264f49fe27da5f735f414c4e7e7dd850cfd8e69f0862d7c74ea9"},
|
||||
{file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:89c687013cb1cd489a0f0ac24febe8c7a666e6e221b783e53ac50ebf68e45d86"},
|
||||
{file = "MarkupSafe-2.0.1-cp37-cp37m-win32.whl", hash = "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415"},
|
||||
{file = "MarkupSafe-2.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914"},
|
||||
{file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5bb28c636d87e840583ee3adeb78172efc47c8b26127267f54a9c0ec251d41a9"},
|
||||
{file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066"},
|
||||
{file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35"},
|
||||
{file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b"},
|
||||
{file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298"},
|
||||
{file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75"},
|
||||
{file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb"},
|
||||
{file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fcf051089389abe060c9cd7caa212c707e58153afa2c649f00346ce6d260f1b"},
|
||||
{file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5855f8438a7d1d458206a2466bf82b0f104a3724bf96a1c781ab731e4201731a"},
|
||||
{file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3dd007d54ee88b46be476e293f48c85048603f5f516008bee124ddd891398ed6"},
|
||||
{file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:aca6377c0cb8a8253e493c6b451565ac77e98c2951c45f913e0b52facdcff83f"},
|
||||
{file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:04635854b943835a6ea959e948d19dcd311762c5c0c6e1f0e16ee57022669194"},
|
||||
{file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6300b8454aa6930a24b9618fbb54b5a68135092bc666f7b06901f897fa5c2fee"},
|
||||
{file = "MarkupSafe-2.0.1-cp38-cp38-win32.whl", hash = "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64"},
|
||||
{file = "MarkupSafe-2.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833"},
|
||||
{file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26"},
|
||||
{file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7"},
|
||||
{file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8"},
|
||||
{file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5"},
|
||||
{file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135"},
|
||||
{file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902"},
|
||||
{file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509"},
|
||||
{file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c47adbc92fc1bb2b3274c4b3a43ae0e4573d9fbff4f54cd484555edbf030baf1"},
|
||||
{file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:37205cac2a79194e3750b0af2a5720d95f786a55ce7df90c3af697bfa100eaac"},
|
||||
{file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1f2ade76b9903f39aa442b4aadd2177decb66525062db244b35d71d0ee8599b6"},
|
||||
{file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4296f2b1ce8c86a6aea78613c34bb1a672ea0e3de9c6ba08a960efe0b0a09047"},
|
||||
{file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f02365d4e99430a12647f09b6cc8bab61a6564363f313126f775eb4f6ef798e"},
|
||||
{file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5b6d930f030f8ed98e3e6c98ffa0652bdb82601e7a016ec2ab5d7ff23baa78d1"},
|
||||
{file = "MarkupSafe-2.0.1-cp39-cp39-win32.whl", hash = "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74"},
|
||||
{file = "MarkupSafe-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8"},
|
||||
{file = "MarkupSafe-2.0.1.tar.gz", hash = "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a"},
|
||||
]
|
||||
matplotlib-inline = [
|
||||
{file = "matplotlib-inline-0.1.3.tar.gz", hash = "sha256:a04bfba22e0d1395479f866853ec1ee28eea1485c1d69a6faf00dc3e24ff34ee"},
|
||||
{file = "matplotlib_inline-0.1.3-py3-none-any.whl", hash = "sha256:aed605ba3b72462d64d475a21a9296f400a19c4f74a31b59103d2a99ffd5aa5c"},
|
||||
|
@ -1872,10 +2038,18 @@ wcwidth = [
|
|||
{file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"},
|
||||
{file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"},
|
||||
]
|
||||
webassets = [
|
||||
{file = "webassets-2.0-py3-none-any.whl", hash = "sha256:a31a55147752ba1b3dc07dee0ad8c8efff274464e08bbdb88c1fd59ffd552724"},
|
||||
{file = "webassets-2.0.tar.gz", hash = "sha256:167132337677c8cedc9705090f6d48da3fb262c8e0b2773b29f3352f050181cd"},
|
||||
]
|
||||
webencodings = [
|
||||
{file = "webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"},
|
||||
{file = "webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"},
|
||||
]
|
||||
werkzeug = [
|
||||
{file = "Werkzeug-2.0.3-py3-none-any.whl", hash = "sha256:1421ebfc7648a39a5c58c601b154165d05cf47a3cd0ccb70857cbdacf6c8f2b8"},
|
||||
{file = "Werkzeug-2.0.3.tar.gz", hash = "sha256:b863f8ff057c522164b6067c9e28b041161b4be5ba4d0daceeaa50a163822d3c"},
|
||||
]
|
||||
wrapt = [
|
||||
{file = "wrapt-1.13.3-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:e05e60ff3b2b0342153be4d1b597bbcfd8330890056b9619f4ad6b8d5c96a81a"},
|
||||
{file = "wrapt-1.13.3-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:85148f4225287b6a0665eef08a178c15097366d46b210574a658c1ff5b377489"},
|
||||
|
|
|
@ -16,6 +16,8 @@ readme = "README.md"
|
|||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.10"
|
||||
Flask = "^2.0.2"
|
||||
Flask-Assets = "^2.0"
|
||||
peewee = "^3.14.8"
|
||||
peewee-plus = "^1.1.0"
|
||||
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
"""Development server stub entrypoint
|
||||
|
||||
Flask comes with a built-in development server. This entrypoint allows ``section7``
|
||||
to be run directly to run the development server and expose some simple config options
|
||||
for ease of access. Run the below command to start the server:
|
||||
|
||||
::
|
||||
|
||||
python -m section7
|
||||
|
||||
.. warning:: As the development server will tell you on startup, do not use this for
|
||||
production deployments.
|
||||
"""
|
||||
import argparse
|
||||
|
||||
from section7.application import APPLICATION
|
||||
|
||||
|
||||
def main():
|
||||
"""Run the werkzeug development server"""
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("-b", "--bind", help="Host to listen on", default="127.0.0.1")
|
||||
parser.add_argument(
|
||||
"-p", "--port", help="Port to listen on", default=5000, type=int
|
||||
)
|
||||
parser.add_argument("-d", "--debug", help="Run in debug mode", action="store_true")
|
||||
args = parser.parse_args()
|
||||
APPLICATION.run(host=args.bind, port=args.port, debug=args.debug, load_dotenv=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -0,0 +1,33 @@
|
|||
import flask
|
||||
import flask_assets
|
||||
|
||||
from section7 import configuration
|
||||
from section7 import database
|
||||
from section7 import filters
|
||||
from section7 import router
|
||||
|
||||
|
||||
class Section7Flask(flask.Flask):
|
||||
"""Stub class to typehint the ``section7`` attribute with the application config"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.section7 = configuration.load()
|
||||
|
||||
|
||||
APPLICATION = Section7Flask(__name__)
|
||||
assets = flask_assets.Environment(APPLICATION)
|
||||
|
||||
# Register the global application handler
|
||||
APPLICATION.register_error_handler(Exception, router.handle_error)
|
||||
|
||||
# Initialize the database connection
|
||||
APPLICATION.before_first_request(database.initialize)
|
||||
|
||||
# Inject the custom jinja2 filters so that they're available in the templates
|
||||
for custom_filter in filters.FILTERS:
|
||||
APPLICATION.jinja_env.filters[custom_filter.__name__] = custom_filter
|
||||
|
||||
# Attach the application route endpoints
|
||||
for route in router.ROUTES:
|
||||
APPLICATION.route(route.route, methods=list(route.methods))(route.entrypoint)
|
|
@ -0,0 +1,111 @@
|
|||
import os
|
||||
from dataclasses import dataclass
|
||||
from dataclasses import field
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
from section7 import constants
|
||||
|
||||
|
||||
@dataclass
|
||||
class DatabaseSqliteConfig:
|
||||
"""SQLite database backend configuration options
|
||||
|
||||
:param path: Path to the SQLite database file
|
||||
"""
|
||||
|
||||
path: Path = Path.cwd() / "section7.db"
|
||||
|
||||
@classmethod
|
||||
def from_env(cls):
|
||||
"""Build dataclass from environment"""
|
||||
return cls(
|
||||
path=Path(os.environ.get("SECTION7_DATABASE_SQLITE_PATH", cls.path))
|
||||
.expanduser()
|
||||
.resolve(),
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class DatabaseMariaConfig:
|
||||
"""MariaDB database backend configuration options
|
||||
|
||||
:param hostname: Hostname or IP address of the host running the database server
|
||||
:param username: Username of the account to use for connecting to the database server
|
||||
:param password: Password for the account to use for connecting to the database server
|
||||
:param port: Port on the host that the database server is listening on
|
||||
:param schema: Database schema that the application should use
|
||||
"""
|
||||
|
||||
hostname: str = "localhost"
|
||||
username: str = "root"
|
||||
password: Optional[str] = None
|
||||
port: int = 3306
|
||||
schema: str = "section7"
|
||||
|
||||
@classmethod
|
||||
def from_env(cls):
|
||||
"""Build dataclass from environment"""
|
||||
|
||||
return cls(
|
||||
hostname=os.getenv("SECTION7_DATABASE_MARIADB_HOSTNAME", cls.hostname),
|
||||
username=os.getenv("SECTION7_DATABASE_MARIADB_USERNAME", cls.username),
|
||||
password=os.environ.get("SECTION7_DATABASE_MARIADB_PASSWORD", cls.password),
|
||||
port=int(os.environ.get("SECTION7_DATABASE_MARIADB_PORT", cls.port)),
|
||||
schema=os.getenv("SECTION7_DATABASE_MARIADB_SCHEMA", cls.schema),
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class DatabaseConfig:
|
||||
"""Database backend configuration
|
||||
|
||||
:param backend: Enum selecting the backend to use for storing data
|
||||
:param sqlite: Container of SQLite settings
|
||||
:param mariadb: Container of MariaDB settings
|
||||
"""
|
||||
|
||||
backend: constants.DatabaseBackend = constants.DatabaseBackend.SQLITE
|
||||
sqlite: DatabaseSqliteConfig = field(default_factory=DatabaseSqliteConfig.from_env)
|
||||
mariadb: DatabaseMariaConfig = field(default_factory=DatabaseMariaConfig.from_env)
|
||||
|
||||
@classmethod
|
||||
def from_env(cls):
|
||||
"""Build dataclass from environment"""
|
||||
|
||||
return cls(
|
||||
backend=constants.DatabaseBackend[
|
||||
os.environ["SECTION7_DATABASE_BACKEND"].upper()
|
||||
]
|
||||
if "SECTION7_DATABASE_BACKEND" in os.environ
|
||||
else cls.backend,
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Section7Config:
|
||||
"""Global application configuration settings
|
||||
|
||||
:param database: Container of database backend settings
|
||||
:param contact_email: Public administrative contact email for the site
|
||||
"""
|
||||
|
||||
database: DatabaseConfig = field(default_factory=DatabaseConfig.from_env)
|
||||
contact_email: str = "admin@enp.one"
|
||||
|
||||
@classmethod
|
||||
def from_env(cls):
|
||||
"""Build dataclass from environment"""
|
||||
return cls(contact_email=os.getenv("SECTION7_CONTACT_EMAIL", cls.contact_email))
|
||||
|
||||
|
||||
def load() -> Section7Config:
|
||||
"""Load the application configuration from environment variables
|
||||
|
||||
:returns: Populated environment configuration
|
||||
"""
|
||||
|
||||
try:
|
||||
return Section7Config.from_env()
|
||||
except (ValueError, TypeError, IndexError, KeyError) as err:
|
||||
raise RuntimeError("Failed to load application configuration") from err
|
|
@ -0,0 +1,282 @@
|
|||
"""Application constants"""
|
||||
import enum
|
||||
|
||||
|
||||
class DatabaseBackend(enum.Enum):
|
||||
"""Enum of supported database backends"""
|
||||
|
||||
MARIADB = enum.auto()
|
||||
SQLITE = enum.auto()
|
||||
|
||||
|
||||
class PayRate(enum.Enum):
|
||||
"""Enum of payment interval options"""
|
||||
|
||||
ANNUALY = enum.auto()
|
||||
QUARTERLY = enum.auto()
|
||||
MONTHLY = enum.auto()
|
||||
WEEKLY = enum.auto()
|
||||
DAILY = enum.auto()
|
||||
HOURLY = enum.auto()
|
||||
OTHER = enum.auto()
|
||||
|
||||
|
||||
class Industry(enum.Enum):
|
||||
"""Enum of industires records can be associated with"""
|
||||
|
||||
|
||||
class Currency(enum.Enum):
|
||||
"""Enum of currency options"""
|
||||
|
||||
|
||||
class Country(enum.Enum):
|
||||
"""Enum of country options"""
|
||||
|
||||
AF = "Afghanistan"
|
||||
AX = "Ã…land Islands"
|
||||
AL = "Albania"
|
||||
DZ = "Algeria"
|
||||
AS = "American Samoa"
|
||||
AD = "Andorra"
|
||||
AO = "Angola"
|
||||
AI = "Anguilla"
|
||||
AQ = "Antarctica"
|
||||
AG = "Antigua and Barbuda"
|
||||
AR = "Argentina"
|
||||
AM = "Armenia"
|
||||
AW = "Aruba"
|
||||
AU = "Australia"
|
||||
AT = "Austria"
|
||||
AZ = "Azerbaijan"
|
||||
BS = "Bahamas"
|
||||
BH = "Bahrain"
|
||||
BD = "Bangladesh"
|
||||
BB = "Barbados"
|
||||
BY = "Belarus"
|
||||
BE = "Belgium"
|
||||
BZ = "Belize"
|
||||
BJ = "Benin"
|
||||
BM = "Bermuda"
|
||||
BT = "Bhutan"
|
||||
BO = "Plurinational State of Bolivia"
|
||||
BQ = "Sint Eustatius and Saba Bonaire"
|
||||
BA = "Bosnia and Herzegovina"
|
||||
BW = "Botswana"
|
||||
BV = "Bouvet Island"
|
||||
BR = "Brazil"
|
||||
IO = "British Indian Ocean Territory"
|
||||
BN = "Brunei Darussalam"
|
||||
BG = "Bulgaria"
|
||||
BF = "Burkina Faso"
|
||||
BI = "Burundi"
|
||||
KH = "Cambodia"
|
||||
CM = "Cameroon"
|
||||
CA = "Canada"
|
||||
CV = "Cape Verde"
|
||||
KY = "Cayman Islands"
|
||||
CF = "Central African Republic"
|
||||
TD = "Chad"
|
||||
CL = "Chile"
|
||||
CN = "China"
|
||||
CX = "Christmas Island"
|
||||
CC = "Cocos (Keeling) Islands"
|
||||
CO = "Colombia"
|
||||
KM = "Comoros"
|
||||
CG = "Congo"
|
||||
CD = "the Democratic Republic of the Congo"
|
||||
CK = "Cook Islands"
|
||||
CR = "Costa Rica"
|
||||
CI = "Côte d'Ivoire"
|
||||
HR = "Croatia"
|
||||
CU = "Cuba"
|
||||
CW = "Curaçao"
|
||||
CY = "Cyprus"
|
||||
CZ = "Czech Republic"
|
||||
DK = "Denmark"
|
||||
DJ = "Djibouti"
|
||||
DM = "Dominica"
|
||||
DO = "Dominican Republic"
|
||||
EC = "Ecuador"
|
||||
EG = "Egypt"
|
||||
SV = "El Salvador"
|
||||
GQ = "Equatorial Guinea"
|
||||
ER = "Eritrea"
|
||||
EE = "Estonia"
|
||||
ET = "Ethiopia"
|
||||
FK = "Falkland Islands (Malvinas)"
|
||||
FO = "Faroe Islands"
|
||||
FJ = "Fiji"
|
||||
FI = "Finland"
|
||||
FR = "France"
|
||||
GF = "French Guiana"
|
||||
PF = "French Polynesia"
|
||||
TF = "French Southern Territories"
|
||||
GA = "Gabon"
|
||||
GM = "Gambia"
|
||||
GE = "Georgia"
|
||||
DE = "Germany"
|
||||
GH = "Ghana"
|
||||
GI = "Gibraltar"
|
||||
GR = "Greece"
|
||||
GL = "Greenland"
|
||||
GD = "Grenada"
|
||||
GP = "Guadeloupe"
|
||||
GU = "Guam"
|
||||
GT = "Guatemala"
|
||||
GG = "Guernsey"
|
||||
GN = "Guinea"
|
||||
GW = "Guinea-Bissau"
|
||||
GY = "Guyana"
|
||||
HT = "Haiti"
|
||||
HM = "Heard Island and McDonald Islands"
|
||||
VA = "Holy See (Vatican City State)"
|
||||
HN = "Honduras"
|
||||
HK = "Hong Kong"
|
||||
HU = "Hungary"
|
||||
IS = "Iceland"
|
||||
IN = "India"
|
||||
ID = "Indonesia"
|
||||
IR = "Islamic Republic of Iran"
|
||||
IQ = "Iraq"
|
||||
IE = "Ireland"
|
||||
IM = "Isle of Man"
|
||||
IL = "Israel"
|
||||
IT = "Italy"
|
||||
JM = "Jamaica"
|
||||
JP = "Japan"
|
||||
JE = "Jersey"
|
||||
JO = "Jordan"
|
||||
KZ = "Kazakhstan"
|
||||
KE = "Kenya"
|
||||
KI = "Kiribati"
|
||||
KP = "Democratic People's Republic of Korea"
|
||||
KR = "Republic of Korea"
|
||||
KW = "Kuwait"
|
||||
KG = "Kyrgyzstan"
|
||||
LA = "Lao People's Democratic Republic"
|
||||
LV = "Latvia"
|
||||
LB = "Lebanon"
|
||||
LS = "Lesotho"
|
||||
LR = "Liberia"
|
||||
LY = "Libya"
|
||||
LI = "Liechtenstein"
|
||||
LT = "Lithuania"
|
||||
LU = "Luxembourg"
|
||||
MO = "Macao"
|
||||
MK = "the Former Yugoslav Republic of Macedonia"
|
||||
MG = "Madagascar"
|
||||
MW = "Malawi"
|
||||
MY = "Malaysia"
|
||||
MV = "Maldives"
|
||||
ML = "Mali"
|
||||
MT = "Malta"
|
||||
MH = "Marshall Islands"
|
||||
MQ = "Martinique"
|
||||
MR = "Mauritania"
|
||||
MU = "Mauritius"
|
||||
YT = "Mayotte"
|
||||
MX = "Mexico"
|
||||
FM = "Federated States of Micronesia"
|
||||
MD = "Republic of Moldova"
|
||||
MC = "Monaco"
|
||||
MN = "Mongolia"
|
||||
ME = "Montenegro"
|
||||
MS = "Montserrat"
|
||||
MA = "Morocco"
|
||||
MZ = "Mozambique"
|
||||
MM = "Myanmar"
|
||||
NA = "Namibia"
|
||||
NR = "Nauru"
|
||||
NP = "Nepal"
|
||||
NL = "Netherlands"
|
||||
NC = "New Caledonia"
|
||||
NZ = "New Zealand"
|
||||
NI = "Nicaragua"
|
||||
NE = "Niger"
|
||||
NG = "Nigeria"
|
||||
NU = "Niue"
|
||||
NF = "Norfolk Island"
|
||||
MP = "Northern Mariana Islands"
|
||||
NO = "Norway"
|
||||
OM = "Oman"
|
||||
PK = "Pakistan"
|
||||
PW = "Palau"
|
||||
PS = "State of Palestine"
|
||||
PA = "Panama"
|
||||
PG = "Papua New Guinea"
|
||||
PY = "Paraguay"
|
||||
PE = "Peru"
|
||||
PH = "Philippines"
|
||||
PN = "Pitcairn"
|
||||
PL = "Poland"
|
||||
PT = "Portugal"
|
||||
PR = "Puerto Rico"
|
||||
QA = "Qatar"
|
||||
RE = "Réunion"
|
||||
RO = "Romania"
|
||||
RU = "Russian Federation"
|
||||
RW = "Rwanda"
|
||||
BL = "Saint Barthélemy"
|
||||
SH = "Ascension and Tristan da Cunha Saint Helena"
|
||||
KN = "Saint Kitts and Nevis"
|
||||
LC = "Saint Lucia"
|
||||
MF = "Saint Martin (French part)"
|
||||
PM = "Saint Pierre and Miquelon"
|
||||
VC = "Saint Vincent and the Grenadines"
|
||||
WS = "Samoa"
|
||||
SM = "San Marino"
|
||||
ST = "Sao Tome and Principe"
|
||||
SA = "Saudi Arabia"
|
||||
SN = "Senegal"
|
||||
RS = "Serbia"
|
||||
SC = "Seychelles"
|
||||
SL = "Sierra Leone"
|
||||
SG = "Singapore"
|
||||
SX = "Sint Maarten (Dutch part)"
|
||||
SK = "Slovakia"
|
||||
SI = "Slovenia"
|
||||
SB = "Solomon Islands"
|
||||
SO = "Somalia"
|
||||
ZA = "South Africa"
|
||||
GS = "South Georgia and the South Sandwich Islands"
|
||||
SS = "South Sudan"
|
||||
ES = "Spain"
|
||||
LK = "Sri Lanka"
|
||||
SD = "Sudan"
|
||||
SR = "Suriname"
|
||||
SJ = "Svalbard and Jan Mayen"
|
||||
SZ = "Swaziland"
|
||||
SE = "Sweden"
|
||||
CH = "Switzerland"
|
||||
SY = "Syrian Arab Republic"
|
||||
TW = "Province of China Taiwan"
|
||||
TJ = "Tajikistan"
|
||||
TZ = "United Republic of Tanzania"
|
||||
TH = "Thailand"
|
||||
TL = "Timor-Leste"
|
||||
TG = "Togo"
|
||||
TK = "Tokelau"
|
||||
TO = "Tonga"
|
||||
TT = "Trinidad and Tobago"
|
||||
TN = "Tunisia"
|
||||
TR = "Turkey"
|
||||
TM = "Turkmenistan"
|
||||
TC = "Turks and Caicos Islands"
|
||||
TV = "Tuvalu"
|
||||
UG = "Uganda"
|
||||
UA = "Ukraine"
|
||||
AE = "United Arab Emirates"
|
||||
GB = "United Kingdom"
|
||||
US = "United States"
|
||||
UM = "United States Minor Outlying Islands"
|
||||
UY = "Uruguay"
|
||||
UZ = "Uzbekistan"
|
||||
VU = "Vanuatu"
|
||||
VE = "Bolivarian Republic of Venezuela"
|
||||
VN = "Viet Nam"
|
||||
VG = "British Virgin Islands"
|
||||
VI = "U.S. Virgin Islands"
|
||||
WF = "Wallis and Futuna"
|
||||
EH = "Western Sahara"
|
||||
YE = "Yemen"
|
||||
ZM = "Zambia"
|
|
@ -0,0 +1,108 @@
|
|||
import datetime
|
||||
import logging
|
||||
import uuid
|
||||
from typing import Optional
|
||||
|
||||
import peewee
|
||||
import peewee_plus
|
||||
|
||||
from section7 import configuration
|
||||
from section7 import constants
|
||||
|
||||
|
||||
INTERFACE = peewee.DatabaseProxy()
|
||||
|
||||
|
||||
def initialize(config: Optional[configuration.Section7Config] = None):
|
||||
"""Initialize the database interface
|
||||
|
||||
Defining the database as an
|
||||
`unconfigured proxy object <http://docs.peewee-orm.com/en/latest/peewee/database.html#setting-the-database-at-run-time>`_
|
||||
allows it to be configured at runtime based on the config values.
|
||||
|
||||
:param config: Populated configuration container object
|
||||
"""
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
config = config or configuration.load()
|
||||
|
||||
if config.database.backend == constants.DatabaseBackend.SQLITE:
|
||||
logger.debug("Using SQLite database backend")
|
||||
logger.debug(f"Applying SQLite pragmas: {peewee_plus.SQLITE_DEFAULT_PRAGMAS}")
|
||||
database = peewee.SqliteDatabase(
|
||||
config.database.sqlite.path, pragmas=peewee_plus.SQLITE_DEFAULT_PRAGMAS
|
||||
)
|
||||
elif config.database.backend == constants.DatabaseBackend.MARIADB:
|
||||
logger.debug("Using MariaDB database backend")
|
||||
logger.debug(
|
||||
"Configuring MariaDB:"
|
||||
f" {config.database.mariadb.username}@{config.database.mariadb.hostname}:{config.database.mariadb.port},"
|
||||
f" with database '{config.database.mariadb.schema}'"
|
||||
)
|
||||
database = peewee.MySQLDatabase(
|
||||
config.database.mariadb.schema,
|
||||
host=config.database.mariadb.hostname,
|
||||
port=config.database.mariadb.port,
|
||||
user=config.database.mariadb.username,
|
||||
password=config.database.mariadb.password,
|
||||
charset="utf8mb4",
|
||||
)
|
||||
else:
|
||||
raise RuntimeError(
|
||||
f"Invalid storage backend in configuration: {config.database.backend.name}"
|
||||
)
|
||||
|
||||
INTERFACE.initialize(database)
|
||||
|
||||
with INTERFACE.atomic():
|
||||
INTERFACE.create_tables([DisclosureRecord])
|
||||
|
||||
|
||||
class Section7Model(peewee.Model):
|
||||
"""Base model for defining common fields and attaching database"""
|
||||
|
||||
class Meta: # pylint: disable=too-few-public-methods,missing-class-docstring
|
||||
database = INTERFACE
|
||||
|
||||
uuid = peewee.UUIDField(null=False, unique=True, default=uuid.uuid4)
|
||||
created = peewee.DateTimeField(null=False, default=datetime.datetime.utcnow)
|
||||
|
||||
|
||||
class DisclosureRecord(Section7Model):
|
||||
"""Record of a user submitted payment disclosure
|
||||
|
||||
:param value: The monetary value of the compensation
|
||||
:param currency: The currency the value is represented in
|
||||
:param rate: The rate that the compensation is assigned
|
||||
:param additionals: Mapping of additional, user defined, compensation details
|
||||
:param recipients: List of email addresses that should receive the compensation disclosure
|
||||
:param publish: Whether the record should be published in the publicly available dataset
|
||||
:param age: Optional age of the user
|
||||
:param gender: Optional gender of the user
|
||||
:param race: Optional race of the user
|
||||
:param country: Optional country where the user's job is located
|
||||
:param city: Optional city where the user's job is located
|
||||
:param industry: Optional industry that the user works in
|
||||
:param title: Optional job title of the user's job
|
||||
:param company: Optional company the user works at
|
||||
:param years_experience: Optional number of years the user has worked in the inudstry
|
||||
:param years_at_company: Optional number of years the user has worked at the company
|
||||
"""
|
||||
|
||||
value = peewee.DecimalField(max_digits=20, decimal_places=3)
|
||||
currency = peewee_plus.EnumField(constants.Currency)
|
||||
rate = peewee_plus.EnumField(constants.PayRate)
|
||||
additionals = peewee_plus.JSONField(default={})
|
||||
recipients = peewee_plus.JSONField(default=[])
|
||||
publish = peewee.BooleanField()
|
||||
|
||||
age = peewee.IntegerField(null=True)
|
||||
gender = peewee.CharField(null=True)
|
||||
race = peewee.CharField(null=True)
|
||||
country = peewee_plus.EnumField(constants.Country, null=True)
|
||||
city = peewee.CharField(null=True)
|
||||
industry = peewee_plus.EnumField(constants.Industry, null=True)
|
||||
title = peewee.CharField(null=True)
|
||||
years_experience = peewee.IntegerField(null=True)
|
||||
years_at_company = peewee.IntegerField(null=True)
|
|
@ -0,0 +1,16 @@
|
|||
from typing import Callable
|
||||
from typing import Optional
|
||||
from typing import Tuple
|
||||
|
||||
import flask
|
||||
|
||||
|
||||
def url_for(value: str, filename: Optional[str] = None) -> str:
|
||||
"""Wrapper around :func:`flask.url_for` to expose the functionality in a template"""
|
||||
|
||||
if filename:
|
||||
return flask.url_for(value, filename=filename)
|
||||
return flask.url_for(value)
|
||||
|
||||
|
||||
FILTERS: Tuple[Callable, ...] = (url_for,)
|
|
@ -0,0 +1,23 @@
|
|||
from typing import Callable
|
||||
from typing import NamedTuple
|
||||
from typing import Sequence
|
||||
from typing import Tuple
|
||||
|
||||
from section7.router.error_handler import handle_error
|
||||
from section7.router.home import display_home
|
||||
|
||||
|
||||
class Routable(NamedTuple):
|
||||
"""Structure for storing details for a given route
|
||||
|
||||
:param route: String indicating the Flask URL rule to apply to the route entry
|
||||
:param entrypoint: The callable that Flask should invoke when the route is accessed
|
||||
:param methods: Sequence of HTTP method verbs that should be accepted by the route
|
||||
"""
|
||||
|
||||
route: str
|
||||
entrypoint: Callable
|
||||
methods: Sequence[str] = ("GET",)
|
||||
|
||||
|
||||
ROUTES: Tuple[Routable, ...] = (Routable(route="/", entrypoint=display_home),)
|
|
@ -0,0 +1,122 @@
|
|||
"""Custom error handler and error page rendering endpoint"""
|
||||
import enum
|
||||
import logging
|
||||
import uuid
|
||||
from dataclasses import dataclass
|
||||
from dataclasses import field
|
||||
from typing import Tuple
|
||||
|
||||
import flask
|
||||
import werkzeug
|
||||
|
||||
|
||||
class ErrorColor(enum.Enum):
|
||||
"""Enum of possible colors for the error page to be rendered with
|
||||
|
||||
Colors can be used to indicate the severity of the problem to a user. Generally, red is reserved
|
||||
for fatal errors (errors that the user cannot fix themselves), orange for user input or
|
||||
temporary errors (errors that the user can potentially fix themselves), and blue for
|
||||
informational errors that don't necessarily indicate a problem, but still needs to block user
|
||||
access.
|
||||
|
||||
Names are the intended usage of the color while values are the CSS classes that the page
|
||||
uses for the corresponding color.
|
||||
"""
|
||||
|
||||
WARNING = "warning"
|
||||
ERROR = "error"
|
||||
INFORMATIONAL = "informational"
|
||||
|
||||
|
||||
class ErrorIcon(enum.Enum):
|
||||
"""Enum of possible icons for the error page
|
||||
|
||||
Names are the intended usage of the icon while values are the Font Awesome CSS class structure
|
||||
for the corresponding icon.
|
||||
"""
|
||||
|
||||
EXCLAMATION = "fas fa-exclamation-triangle"
|
||||
INFORMATION = "fas fa-exclamation-circle"
|
||||
|
||||
|
||||
@dataclass
|
||||
class ErrorDetails:
|
||||
"""Container of details for an error that can be used to render an error page
|
||||
|
||||
:param code: The HTTP response code for the error
|
||||
:param title: Page title/short description of the error
|
||||
:param description: Long description of the error. Ideally should include an action the user
|
||||
can take to debug or fix the problem.
|
||||
:param icon: The icon that should be added to the page
|
||||
:param color: The color of the page highlights
|
||||
"""
|
||||
|
||||
code: int = 500
|
||||
title: str = "Internal Server Error"
|
||||
description: str = "The application encountered an unhandled error while trying to fulfill the request."
|
||||
icon: ErrorIcon = ErrorIcon.EXCLAMATION
|
||||
color: ErrorColor = ErrorColor.ERROR
|
||||
event_id: str = field(default_factory=lambda: uuid.uuid4().hex)
|
||||
|
||||
|
||||
def handle_error(error: Exception) -> Tuple[str, int]:
|
||||
"""Handle an exception raised elsewhere in the application
|
||||
|
||||
:returns: Rendered template of an error page for the raised exception
|
||||
"""
|
||||
logger = logging.getLogger(__name__)
|
||||
# If the application is running in debug mode then we re-raise the exception so that the
|
||||
# developer gets the helpful browser-rendered stack trace werkzeug provides
|
||||
if flask.current_app.config["DEBUG"]:
|
||||
logger.warning(
|
||||
"Application is running in debug mode, stack trace will be returned to client"
|
||||
)
|
||||
raise error
|
||||
|
||||
details = ErrorDetails()
|
||||
|
||||
if isinstance(error, NotImplementedError):
|
||||
details.title = "Not Implemented"
|
||||
details.description = "The functionality for this resource has not yet been implemented. The resource name is reserved for for future use"
|
||||
details.color = ErrorColor.INFORMATIONAL
|
||||
details.icon = ErrorIcon.INFORMATION
|
||||
|
||||
elif isinstance(error, werkzeug.exceptions.HTTPException):
|
||||
details.code = error.code
|
||||
details.title = error.name
|
||||
details.description = error.description
|
||||
if 400 <= error.code < 500:
|
||||
details.color = ErrorColor.WARNING
|
||||
details.icon = ErrorIcon.INFORMATION
|
||||
|
||||
else:
|
||||
logger.exception(
|
||||
f"Event ID {details.event_id}: unhandled application error at '{flask.request.full_path}'"
|
||||
)
|
||||
|
||||
logger.error(
|
||||
f"Event ID {details.event_id}: Error {details.code} {details.description}"
|
||||
)
|
||||
|
||||
def _render_template(
|
||||
title: str,
|
||||
error: ErrorDetails,
|
||||
) -> str:
|
||||
"""Stub function to type, enumerate, and document the template parameters
|
||||
|
||||
:param title: Value of the page title header
|
||||
:param error: Container of error details about the error to render on the page
|
||||
"""
|
||||
return flask.render_template(
|
||||
"error.html.j2",
|
||||
title=title,
|
||||
error=error,
|
||||
)
|
||||
|
||||
return (
|
||||
_render_template(
|
||||
title=f"{details.title} ({details.code})",
|
||||
error=details,
|
||||
),
|
||||
details.code,
|
||||
)
|
|
@ -0,0 +1,24 @@
|
|||
"""Endpoint for displaying the main homepage of the application"""
|
||||
import flask
|
||||
|
||||
|
||||
def display_home() -> str:
|
||||
"""Display the home page
|
||||
|
||||
:returns: A response rendering the home page template
|
||||
"""
|
||||
|
||||
def _render_template() -> str:
|
||||
"""Stub function to type, enumerate, and document the template parameters
|
||||
|
||||
:param version: Semantic version of the currently running application
|
||||
:param common_urls: Container of common application URLs used in mutliple templates
|
||||
:param testers: List of tester names that the logged in user can read to link to for
|
||||
easy access search access
|
||||
:param messages: Optional list of Flask flash messages to render as popup notifications
|
||||
"""
|
||||
return flask.render_template(
|
||||
"home.html.j2",
|
||||
)
|
||||
|
||||
return _render_template()
|
|
@ -0,0 +1,14 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
{% block header %}{% endblock %}
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="preloader"><div class="spinner"><div></div></div></div>
|
||||
|
||||
<div id="main" class="grid-x grid-margin-x">
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,10 @@
|
|||
{% extends "base.html.j2" %}
|
||||
{% from "macros.html.j2" import make_header %}
|
||||
|
||||
{% block header %}
|
||||
{{ make_header(title) }}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{{ error.title }}
|
||||
{% endblock %}
|
|
@ -0,0 +1,10 @@
|
|||
{% extends "base.html.j2" %}
|
||||
{% from "macros.html.j2" import make_header %}
|
||||
|
||||
{% block header %}
|
||||
{{ make_header('Disclose My Pay') }}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
Hello world
|
||||
{% endblock %}
|
|
@ -0,0 +1,56 @@
|
|||
{% macro make_header(title, js_bundle=none, css_bundle=none) %}
|
||||
<!-- OpenGraph integration meta -->
|
||||
<meta property="og:title" content="{{ title }}"/>
|
||||
<meta property="og:url" content="https://enpaul.net/"/>
|
||||
<meta property='og:site_name' content="Disclose My Pay"/>
|
||||
<meta property="og:type" content="website"/>
|
||||
<meta property='og:locale' content="en_US"/>
|
||||
<meta property="og:image" content="{{ 'static' | url_for('assets/section7.png') }}"/>
|
||||
<meta property='og:description' content="Share your compensation information privately with your coworkers"/>
|
||||
|
||||
<meta charset="utf-8"/>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<meta name="author" content="admin@enp.one"/>
|
||||
<meta name="description" content="Share your compensation information privately with your coworkers"/>
|
||||
|
||||
<title>{{ title }}</title>
|
||||
<link rel="shortcut icon" href="{{ 'static' | url_for('assets/section7.ico') }}">
|
||||
<link rel="icon" type="image/ico" href="{{ 'static' | url_for('assets/section7.ico') }}" sizes="32x32">
|
||||
<link rel="icon" type="image/ico" href="{{ 'static' | url_for('assets/section7.ico') }}" sizes="16x16">
|
||||
<link rel="icon" type="image/ico" href="{{ 'static' | url_for('assets/section7.ico') }}" sizes="8x8">
|
||||
|
||||
<!-- third party includes -->
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://use.fontawesome.com/releases/v5.6.3/css/all.css"
|
||||
integrity="sha384-UHRtZLI+pbxtHCWp1t77Bi1L4ZtiqrqD80Kn4Z8NTSRyMA2Fd33n5dQ8lWUE00s/"
|
||||
crossorigin="anonymous"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/npm/foundation-sites@6.6.3/dist/css/foundation.min.css"
|
||||
integrity="sha256-ogmFxjqiTMnZhxCqVmcqTvjfe1Y/ec4WaRj/aQPvn+I="
|
||||
crossorigin="anonymous"
|
||||
/>
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="https://code.jquery.com/jquery-3.6.0.min.js"
|
||||
integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4="
|
||||
crossorigin="anonymous"
|
||||
></script>
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="https://cdn.jsdelivr.net/npm/foundation-sites@6.6.3/dist/js/foundation.min.js"
|
||||
integrity="sha256-pRF3zifJRA9jXGv++b06qwtSqX1byFQOLjqa2PTEb2o="
|
||||
crossorigin="anonymous"
|
||||
></script>
|
||||
|
||||
{% if js_bundle is not none %}{% assets js_bundle %}
|
||||
<script type="text/javascript" src="{{ ASSET_URL }}"></script>
|
||||
{% endassets %}{% endif %}
|
||||
|
||||
{% if css_bundle is not none %}{% assets css_bundle %}
|
||||
<link href="{{ ASSET_URL }}" rel="stylesheet">
|
||||
{% endassets %}{% endif %}
|
||||
{% endmacro %}
|
Reference in New Issue