From eb807bfa233609d67615edcbbd71bcaea963e9cd Mon Sep 17 00:00:00 2001 From: Nelson Stoik Date: Mon, 8 Jan 2024 21:55:18 -0700 Subject: [PATCH] Update to sqlalchemy 2.0 (#304) * update sqlalchemy to 2.0 * suppress pylint errror until upstream fix * change to DeclarativeBase * Changes for sqlalchemy 2.0 * add a database migration * remove unused ignores * updates tests * update vscode settings for dev container * update from query to execute api --- device/.devcontainer/devcontainer.json | 16 +- device/Pipfile | 4 +- device/Pipfile.lock | 380 ++++-------------- device/README.md | 11 +- device/fd_device/cli/db/commands.py | 7 +- device/fd_device/cli/manage/setup_commands.py | 11 +- device/fd_device/database/base.py | 41 -- device/fd_device/database/database.py | 95 +++-- device/fd_device/database/device.py | 65 +-- device/fd_device/database/system.py | 60 +-- device/fd_device/device/service.py | 2 +- device/fd_device/device/update.py | 10 +- device/fd_device/grainbin/update.py | 8 +- device/fd_device/main.py | 2 +- device/fd_device/network/ethernet.py | 11 +- device/fd_device/network/wifi.py | 69 +++- device/fd_device/startup.py | 28 +- device/fd_device/system/control.py | 9 +- device/migrations/env.py | 3 +- .../4988ca3aa994_update_to_sqlalchemy_2_0.py | 146 +++++++ device/setup.cfg | 12 - device/tests/conftest.py | 2 +- device/tests/database/test_device.py | 13 +- device/tests/database/test_system.py | 60 ++- device/tests/device/test_update.py | 4 +- device/tests/factories.py | 5 +- device/tests/network/conftest.py | 9 +- device/tests/network/test_wifi.py | 16 +- device/tests/system/test_control.py | 13 +- 29 files changed, 536 insertions(+), 576 deletions(-) delete mode 100755 device/fd_device/database/base.py create mode 100644 device/migrations/versions/4988ca3aa994_update_to_sqlalchemy_2_0.py diff --git a/device/.devcontainer/devcontainer.json b/device/.devcontainer/devcontainer.json index 45535cc..429b9bc 100644 --- a/device/.devcontainer/devcontainer.json +++ b/device/.devcontainer/devcontainer.json @@ -23,20 +23,20 @@ "vscode": { "extensions": [ "ms-python.python", + "ms-python.pylint", + "ms-python.flake8", + "matangover.mypy", + "ms-python.black-formatter", "eamodio.gitlens", "ms-python.vscode-pylance", "github.copilot" ], "postCreateCommand": "git config --global core.autocrlf true && git config --global user.email 'nelsonstoik@gmail.com' && git config --global user.name 'Nelson'", "settings": { - "python.linting.enabled": true, - "python.linting.pylintEnabled": true, - "python.linting.pylintArgs": ["--load-plugins"], - "python.linting.flake8Enabled": true, - "python.linting.flake8Args": ["--config=/workspaces/device/setup.cfg"], - "python.linting.mypyEnabled": true, - "python.linting.mypyArgs": ["--config=/workspaces/device/setup.cfg"], - "python.formatting.provider": "black" + "pylint.args": ["--load-plugins"], + "flake8.args": ["--config=/workspaces/device/setup.cfg"], + "mypy.configFile": "/workspaces/device/setup.cfg", + "mypy.runUsingActiveInterpreter": true } } } diff --git a/device/Pipfile b/device/Pipfile index 1eed964..1a686e0 100644 --- a/device/Pipfile +++ b/device/Pipfile @@ -11,7 +11,7 @@ pytest-cov = "~=4.1" pytest-mock = "~=3.12" # Lint and code style black = "~=23.12" -flake8 = "~=6.1.0" +flake8 = "~=7.0" flake8-blind-except = "~=0.2.0" flake8-debugger = "~=4.1.2" flake8-docstrings = "~=1.7.0" @@ -27,7 +27,7 @@ types-psutil = "~=5.9.5" types-requests = "~=2.31" [packages] -SQLAlchemy = "~=1.4.42" +SQLAlchemy = "~=2.0" # for Postgresql psycopg2 = "~=2.9.4" alembic = "~=1.13.1" diff --git a/device/Pipfile.lock b/device/Pipfile.lock index ef371f0..406e427 100644 --- a/device/Pipfile.lock +++ b/device/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "bd77a43a5770cd0db8f1a8bfce415099d5ad114e3bb50f5e0acf04da2b202a71" + "sha256": "7f07e5dd90f7a3a763d44dd71e8b5b28973418b42c6cb22553aea1f8089d0bec" }, "pipfile-spec": 6, "requires": { @@ -284,7 +284,7 @@ "sha256:fd096eb7ffef17c456cfa587523c5f92321ae02427ff955bebe9e3c63bc9f0da", "sha256:fe754d231288e1e64323cfad462fcee8f0288654c10bdf4f603a39ed923bef33" ], - "markers": "python_version >= '3' and platform_machine == 'aarch64' or (platform_machine == 'ppc64le' or (platform_machine == 'x86_64' or (platform_machine == 'amd64' or (platform_machine == 'AMD64' or (platform_machine == 'win32' or platform_machine == 'WIN32')))))", + "markers": "platform_machine == 'aarch64' or (platform_machine == 'ppc64le' or (platform_machine == 'x86_64' or (platform_machine == 'amd64' or (platform_machine == 'AMD64' or (platform_machine == 'win32' or platform_machine == 'WIN32')))))", "version": "==3.0.3" }, "idna": { @@ -556,34 +556,58 @@ }, "sqlalchemy": { "hashes": [ - "sha256:0535d5b57d014d06ceeaeffd816bb3a6e2dddeb670222570b8c4953e2d2ea678", - "sha256:0892e7ac8bc76da499ad3ee8de8da4d7905a3110b952e2a35a940dab1ffa550e", - "sha256:0d661cff58c91726c601cc0ee626bf167b20cc4d7941c93c5f3ac28dc34ddbea", - "sha256:245c67c88e63f1523e9216cad6ba3107dea2d3ee19adc359597a628afcabfbcb", - "sha256:2ad16880ccd971ac8e570550fbdef1385e094b022d6fc85ef3ce7df400dddad3", - "sha256:2be4e6294c53f2ec8ea36486b56390e3bcaa052bf3a9a47005687ccf376745d1", - "sha256:2c55040d8ea65414de7c47f1a23823cd9f3fad0dc93e6b6b728fee81230f817b", - "sha256:352df882088a55293f621328ec33b6ffca936ad7f23013b22520542e1ab6ad1b", - "sha256:3823dda635988e6744d4417e13f2e2b5fe76c4bf29dd67e95f98717e1b094cad", - "sha256:38ef80328e3fee2be0a1abe3fe9445d3a2e52a1282ba342d0dab6edf1fef4707", - "sha256:3ec7a0ed9b32afdf337172678a4a0e6419775ba4e649b66f49415615fa47efbd", - "sha256:55e699466106d09f028ab78d3c2e1f621b5ef2c8694598242259e4515715da7c", - "sha256:6cacc0b2dd7d22a918a9642fc89840a5d3cee18a0e1fe41080b1141b23b10916", - "sha256:7deeae5071930abb3669b5185abb6c33ddfd2398f87660fafdb9e6a5fb0f3f2f", - "sha256:86a22143a4001f53bf58027b044da1fb10d67b62a785fc1390b5c7f089d9838c", - "sha256:8ca484ca11c65e05639ffe80f20d45e6be81fbec7683d6c9a15cd421e6e8b340", - "sha256:af55cc207865d641a57f7044e98b08b09220da3d1b13a46f26487cc2f898a072", - "sha256:b97fd5bb6b7c1a64b7ac0632f7ce389b8ab362e7bd5f60654c2a418496be5d7f", - "sha256:c37bc677690fd33932182b85d37433845de612962ed080c3e4d92f758d1bd894", - "sha256:d3cf56cc36d42908495760b223ca9c2c0f9f0002b4eddc994b24db5fcb86a9e4", - "sha256:e646b19f47d655261b22df9976e572f588185279970efba3d45c377127d35349", - "sha256:e7908c2025eb18394e32d65dd02d2e37e17d733cdbe7d78231c2b6d7eb20cdb9", - "sha256:e8f2df79a46e130235bc5e1bbef4de0583fb19d481eaa0bffa76e8347ea45ec6", - "sha256:eb18549b770351b54e1ab5da37d22bc530b8bfe2ee31e22b9ebe650640d2ef12", - "sha256:f8cafa6f885a0ff5e39efa9325195217bb47d5929ab0051636610d24aef45ade" + "sha256:0d3cab3076af2e4aa5693f89622bef7fa770c6fec967143e4da7508b3dceb9b9", + "sha256:0dacf67aee53b16f365c589ce72e766efaabd2b145f9de7c917777b575e3659d", + "sha256:10331f129982a19df4284ceac6fe87353ca3ca6b4ca77ff7d697209ae0a5915e", + "sha256:14a6f68e8fc96e5e8f5647ef6cda6250c780612a573d99e4d881581432ef1669", + "sha256:1b1180cda6df7af84fe72e4530f192231b1f29a7496951db4ff38dac1687202d", + "sha256:29049e2c299b5ace92cbed0c1610a7a236f3baf4c6b66eb9547c01179f638ec5", + "sha256:342d365988ba88ada8af320d43df4e0b13a694dbd75951f537b2d5e4cb5cd002", + "sha256:420362338681eec03f53467804541a854617faed7272fe71a1bfdb07336a381e", + "sha256:4344d059265cc8b1b1be351bfb88749294b87a8b2bbe21dfbe066c4199541ebd", + "sha256:4f7a7d7fcc675d3d85fbf3b3828ecd5990b8d61bd6de3f1b260080b3beccf215", + "sha256:555651adbb503ac7f4cb35834c5e4ae0819aab2cd24857a123370764dc7d7e24", + "sha256:59a21853f5daeb50412d459cfb13cb82c089ad4c04ec208cd14dddd99fc23b39", + "sha256:5fdd402169aa00df3142149940b3bf9ce7dde075928c1886d9a1df63d4b8de62", + "sha256:605b6b059f4b57b277f75ace81cc5bc6335efcbcc4ccb9066695e515dbdb3900", + "sha256:665f0a3954635b5b777a55111ababf44b4fc12b1f3ba0a435b602b6387ffd7cf", + "sha256:6f9e2e59cbcc6ba1488404aad43de005d05ca56e069477b33ff74e91b6319735", + "sha256:736ea78cd06de6c21ecba7416499e7236a22374561493b456a1f7ffbe3f6cdb4", + "sha256:74b080c897563f81062b74e44f5a72fa44c2b373741a9ade701d5f789a10ba23", + "sha256:75432b5b14dc2fff43c50435e248b45c7cdadef73388e5610852b95280ffd0e9", + "sha256:75f99202324383d613ddd1f7455ac908dca9c2dd729ec8584c9541dd41822a2c", + "sha256:790f533fa5c8901a62b6fef5811d48980adeb2f51f1290ade8b5e7ba990ba3de", + "sha256:798f717ae7c806d67145f6ae94dc7c342d3222d3b9a311a784f371a4333212c7", + "sha256:7c88f0c7dcc5f99bdb34b4fd9b69b93c89f893f454f40219fe923a3a2fd11625", + "sha256:7d505815ac340568fd03f719446a589162d55c52f08abd77ba8964fbb7eb5b5f", + "sha256:84daa0a2055df9ca0f148a64fdde12ac635e30edbca80e87df9b3aaf419e144a", + "sha256:87d91043ea0dc65ee583026cb18e1b458d8ec5fc0a93637126b5fc0bc3ea68c4", + "sha256:87f6e732bccd7dcf1741c00f1ecf33797383128bd1c90144ac8adc02cbb98643", + "sha256:884272dcd3ad97f47702965a0e902b540541890f468d24bd1d98bcfe41c3f018", + "sha256:8b8cb63d3ea63b29074dcd29da4dc6a97ad1349151f2d2949495418fd6e48db9", + "sha256:91f7d9d1c4dd1f4f6e092874c128c11165eafcf7c963128f79e28f8445de82d5", + "sha256:a2c69a7664fb2d54b8682dd774c3b54f67f84fa123cf84dda2a5f40dcaa04e08", + "sha256:a3be4987e3ee9d9a380b66393b77a4cd6d742480c951a1c56a23c335caca4ce3", + "sha256:a86b4240e67d4753dc3092d9511886795b3c2852abe599cffe108952f7af7ac3", + "sha256:aa9373708763ef46782d10e950b49d0235bfe58facebd76917d3f5cbf5971aed", + "sha256:b64b183d610b424a160b0d4d880995e935208fc043d0302dd29fee32d1ee3f95", + "sha256:b801154027107461ee992ff4b5c09aa7cc6ec91ddfe50d02bca344918c3265c6", + "sha256:bb209a73b8307f8fe4fe46f6ad5979649be01607f11af1eb94aa9e8a3aaf77f0", + "sha256:bc8b7dabe8e67c4832891a5d322cec6d44ef02f432b4588390017f5cec186a84", + "sha256:c51db269513917394faec5e5c00d6f83829742ba62e2ac4fa5c98d58be91662f", + "sha256:c55731c116806836a5d678a70c84cb13f2cedba920212ba7dcad53260997666d", + "sha256:cf18ff7fc9941b8fc23437cc3e68ed4ebeff3599eec6ef5eebf305f3d2e9a7c2", + "sha256:d24f571990c05f6b36a396218f251f3e0dda916e0c687ef6fdca5072743208f5", + "sha256:db854730a25db7c956423bb9fb4bdd1216c839a689bf9cc15fada0a7fb2f4570", + "sha256:dc55990143cbd853a5d038c05e79284baedf3e299661389654551bd02a6a68d7", + "sha256:e607cdd99cbf9bb80391f54446b86e16eea6ad309361942bf88318bcd452363c", + "sha256:ecf6d4cda1f9f6cb0b45803a01ea7f034e2f1aed9475e883410812d9f9e3cfcf", + "sha256:f2a159111a0f58fb034c93eeba211b4141137ec4b0a6e75789ab7a3ef3c7e7e3", + "sha256:f37c0caf14b9e9b9e8f6dbc81bc56db06acb4363eba5a633167781a48ef036ed", + "sha256:f5693145220517b5f42393e07a6898acdfe820e136c98663b971906120549da5" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", - "version": "==1.4.51" + "markers": "python_version >= '3.7'", + "version": "==2.0.25" }, "typing-extensions": { "hashes": [ @@ -619,22 +643,13 @@ }, "wcwidth": { "hashes": [ - "sha256:f01c104efdf57971bcb756f054dd58ddec5204dd15fa31d6503ea57947d97c02", - "sha256:f26ec43d96c8cbfed76a5075dac87680124fa84e0855195a6184da9c187f133c" + "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", + "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5" ], - "version": "==0.2.12" + "version": "==0.2.13" } }, "develop": { - "alembic": { - "hashes": [ - "sha256:2edcc97bed0bd3272611ce3a98d98279e9c209e7186e43e75bbb1b2bdfdbcc43", - "sha256:4932c8558bf68f2ee92b9bbcb8218671c627064d5b08939437af6d77dc05e595" - ], - "index": "pypi", - "markers": "python_version >= '3.8'", - "version": "==1.13.1" - }, "astroid": { "hashes": [ "sha256:4a61cf0a59097c7bb52689b0fd63717cd2a8a14dc9f1eee97b82d814881c8c91", @@ -681,13 +696,6 @@ "markers": "python_version >= '3.7'", "version": "==8.1.7" }, - "colorzero": { - "hashes": [ - "sha256:0e60d743a6b8071498a56465f7719c96a5e92928f858bab1be2a0d606c9aa0f8", - "sha256:e7d5a5c26cd0dc37b164ebefc609f388de24f8593b659191e12d85f8f9d5eb58" - ], - "version": "==2.0" - }, "coverage": { "extras": [ "toml" @@ -774,18 +782,14 @@ "markers": "python_version >= '3.8'", "version": "==22.0.0" }, - "fd-device": { - "editable": true, - "file": "." - }, "flake8": { "hashes": [ - "sha256:d5b3857f07c030bdb5bf41c7f53799571d75c4491748a3adcd47de929e34cd23", - "sha256:ffdfce58ea94c6580c77888a86506937f9a1a227dfcd15f245d694ae20a6b6e5" + "sha256:33f96621059e65eec474169085dc92bf26e7b2d47366b70be2f67ab80dc25132", + "sha256:a6dfbb75e03252917f2473ea9653f7cd799c3064e54d4c8140044c5c065f53c3" ], "index": "pypi", "markers": "python_full_version >= '3.8.1'", - "version": "==6.1.0" + "version": "==7.0.0" }, "flake8-blind-except": { "hashes": [ @@ -821,78 +825,6 @@ "markers": "python_version >= '3.8'", "version": "==6.1.1" }, - "gpiozero": { - "hashes": [ - "sha256:403bcc9e7f24f0877653e7fced91ba51508d5197c9d1f80e383fe6693b9c3c27", - "sha256:a103ff70c96f333841480fe1b1dbb8bc869438805ed291bf0b8bf7c95d3b915d" - ], - "index": "pypi", - "version": "==2.0" - }, - "greenlet": { - "hashes": [ - "sha256:01bc7ea167cf943b4c802068e178bbf70ae2e8c080467070d01bfa02f337ee67", - "sha256:0448abc479fab28b00cb472d278828b3ccca164531daab4e970a0458786055d6", - "sha256:086152f8fbc5955df88382e8a75984e2bb1c892ad2e3c80a2508954e52295257", - "sha256:098d86f528c855ead3479afe84b49242e174ed262456c342d70fc7f972bc13c4", - "sha256:149e94a2dd82d19838fe4b2259f1b6b9957d5ba1b25640d2380bea9c5df37676", - "sha256:1551a8195c0d4a68fac7a4325efac0d541b48def35feb49d803674ac32582f61", - "sha256:15d79dd26056573940fcb8c7413d84118086f2ec1a8acdfa854631084393efcc", - "sha256:1996cb9306c8595335bb157d133daf5cf9f693ef413e7673cb07e3e5871379ca", - "sha256:1a7191e42732df52cb5f39d3527217e7ab73cae2cb3694d241e18f53d84ea9a7", - "sha256:1ea188d4f49089fc6fb283845ab18a2518d279c7cd9da1065d7a84e991748728", - "sha256:1f672519db1796ca0d8753f9e78ec02355e862d0998193038c7073045899f305", - "sha256:2516a9957eed41dd8f1ec0c604f1cdc86758b587d964668b5b196a9db5bfcde6", - "sha256:2797aa5aedac23af156bbb5a6aa2cd3427ada2972c828244eb7d1b9255846379", - "sha256:2dd6e660effd852586b6a8478a1d244b8dc90ab5b1321751d2ea15deb49ed414", - "sha256:3ddc0f794e6ad661e321caa8d2f0a55ce01213c74722587256fb6566049a8b04", - "sha256:3ed7fb269f15dc662787f4119ec300ad0702fa1b19d2135a37c2c4de6fadfd4a", - "sha256:419b386f84949bf0e7c73e6032e3457b82a787c1ab4a0e43732898a761cc9dbf", - "sha256:43374442353259554ce33599da8b692d5aa96f8976d567d4badf263371fbe491", - "sha256:52f59dd9c96ad2fc0d5724107444f76eb20aaccb675bf825df6435acb7703559", - "sha256:57e8974f23e47dac22b83436bdcf23080ade568ce77df33159e019d161ce1d1e", - "sha256:5b51e85cb5ceda94e79d019ed36b35386e8c37d22f07d6a751cb659b180d5274", - "sha256:649dde7de1a5eceb258f9cb00bdf50e978c9db1b996964cd80703614c86495eb", - "sha256:64d7675ad83578e3fc149b617a444fab8efdafc9385471f868eb5ff83e446b8b", - "sha256:68834da854554926fbedd38c76e60c4a2e3198c6fbed520b106a8986445caaf9", - "sha256:6b66c9c1e7ccabad3a7d037b2bcb740122a7b17a53734b7d72a344ce39882a1b", - "sha256:70fb482fdf2c707765ab5f0b6655e9cfcf3780d8d87355a063547b41177599be", - "sha256:7170375bcc99f1a2fbd9c306f5be8764eaf3ac6b5cb968862cad4c7057756506", - "sha256:73a411ef564e0e097dbe7e866bb2dda0f027e072b04da387282b02c308807405", - "sha256:77457465d89b8263bca14759d7c1684df840b6811b2499838cc5b040a8b5b113", - "sha256:7f362975f2d179f9e26928c5b517524e89dd48530a0202570d55ad6ca5d8a56f", - "sha256:81bb9c6d52e8321f09c3d165b2a78c680506d9af285bfccbad9fb7ad5a5da3e5", - "sha256:881b7db1ebff4ba09aaaeae6aa491daeb226c8150fc20e836ad00041bcb11230", - "sha256:894393ce10ceac937e56ec00bb71c4c2f8209ad516e96033e4b3b1de270e200d", - "sha256:99bf650dc5d69546e076f413a87481ee1d2d09aaaaaca058c9251b6d8c14783f", - "sha256:9da2bd29ed9e4f15955dd1595ad7bc9320308a3b766ef7f837e23ad4b4aac31a", - "sha256:afaff6cf5200befd5cec055b07d1c0a5a06c040fe5ad148abcd11ba6ab9b114e", - "sha256:b1b5667cced97081bf57b8fa1d6bfca67814b0afd38208d52538316e9422fc61", - "sha256:b37eef18ea55f2ffd8f00ff8fe7c8d3818abd3e25fb73fae2ca3b672e333a7a6", - "sha256:b542be2440edc2d48547b5923c408cbe0fc94afb9f18741faa6ae970dbcb9b6d", - "sha256:b7dcbe92cc99f08c8dd11f930de4d99ef756c3591a5377d1d9cd7dd5e896da71", - "sha256:b7f009caad047246ed379e1c4dbcb8b020f0a390667ea74d2387be2998f58a22", - "sha256:bba5387a6975598857d86de9eac14210a49d554a77eb8261cc68b7d082f78ce2", - "sha256:c5e1536de2aad7bf62e27baf79225d0d64360d4168cf2e6becb91baf1ed074f3", - "sha256:c5ee858cfe08f34712f548c3c363e807e7186f03ad7a5039ebadb29e8c6be067", - "sha256:c9db1c18f0eaad2f804728c67d6c610778456e3e1cc4ab4bbd5eeb8e6053c6fc", - "sha256:d353cadd6083fdb056bb46ed07e4340b0869c305c8ca54ef9da3421acbdf6881", - "sha256:d46677c85c5ba00a9cb6f7a00b2bfa6f812192d2c9f7d9c4f6a55b60216712f3", - "sha256:d4d1ac74f5c0c0524e4a24335350edad7e5f03b9532da7ea4d3c54d527784f2e", - "sha256:d73a9fe764d77f87f8ec26a0c85144d6a951a6c438dfe50487df5595c6373eac", - "sha256:da70d4d51c8b306bb7a031d5cff6cc25ad253affe89b70352af5f1cb68e74b53", - "sha256:daf3cb43b7cf2ba96d614252ce1684c1bccee6b2183a01328c98d36fcd7d5cb0", - "sha256:dca1e2f3ca00b84a396bc1bce13dd21f680f035314d2379c4160c98153b2059b", - "sha256:dd4f49ae60e10adbc94b45c0b5e6a179acc1736cf7a90160b404076ee283cf83", - "sha256:e1f145462f1fa6e4a4ae3c0f782e580ce44d57c8f2c7aae1b6fa88c0b2efdb41", - "sha256:e3391d1e16e2a5a1507d83e4a8b100f4ee626e8eca43cf2cadb543de69827c4c", - "sha256:fcd2469d6a2cf298f198f0487e0a5b1a47a42ca0fa4dfd1b6862c999f018ebbf", - "sha256:fd096eb7ffef17c456cfa587523c5f92321ae02427ff955bebe9e3c63bc9f0da", - "sha256:fe754d231288e1e64323cfad462fcee8f0288654c10bdf4f603a39ed923bef33" - ], - "markers": "python_version >= '3' and platform_machine == 'aarch64' or (platform_machine == 'ppc64le' or (platform_machine == 'x86_64' or (platform_machine == 'amd64' or (platform_machine == 'AMD64' or (platform_machine == 'win32' or platform_machine == 'WIN32')))))", - "version": "==3.0.3" - }, "iniconfig": { "hashes": [ "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", @@ -910,80 +842,6 @@ "markers": "python_full_version >= '3.8.0'", "version": "==5.13.2" }, - "mako": { - "hashes": [ - "sha256:57d4e997349f1a92035aa25c17ace371a4213f2ca42f99bee9a602500cfd54d9", - "sha256:e3a9d388fd00e87043edbe8792f45880ac0114e9c4adc69f6e9bfb2c55e3b11b" - ], - "markers": "python_version >= '3.8'", - "version": "==1.3.0" - }, - "markupsafe": { - "hashes": [ - "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e", - "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e", - "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431", - "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686", - "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c", - "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559", - "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc", - "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb", - "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939", - "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c", - "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0", - "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4", - "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9", - "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575", - "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba", - "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d", - "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd", - "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3", - "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00", - "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155", - "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac", - "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52", - "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f", - "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8", - "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b", - "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007", - "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24", - "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea", - "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198", - "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0", - "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee", - "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be", - "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2", - "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1", - "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707", - "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6", - "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c", - "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58", - "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823", - "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779", - "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636", - "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c", - "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad", - "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee", - "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc", - "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2", - "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48", - "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7", - "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e", - "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b", - "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa", - "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5", - "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e", - "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb", - "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9", - "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57", - "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc", - "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc", - "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2", - "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11" - ], - "markers": "python_version >= '3.7'", - "version": "==2.1.3" - }, "mccabe": { "hashes": [ "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325", @@ -1034,42 +892,6 @@ "markers": "python_version >= '3.5'", "version": "==1.0.0" }, - "netifaces": { - "hashes": [ - "sha256:043a79146eb2907edf439899f262b3dfe41717d34124298ed281139a8b93ca32", - "sha256:08e3f102a59f9eaef70948340aeb6c89bd09734e0dca0f3b82720305729f63ea", - "sha256:0f6133ac02521270d9f7c490f0c8c60638ff4aec8338efeff10a1b51506abe85", - "sha256:18917fbbdcb2d4f897153c5ddbb56b31fa6dd7c3fa9608b7e3c3a663df8206b5", - "sha256:2479bb4bb50968089a7c045f24d120f37026d7e802ec134c4490eae994c729b5", - "sha256:2650beee182fed66617e18474b943e72e52f10a24dc8cac1db36c41ee9c041b7", - "sha256:28f4bf3a1361ab3ed93c5ef360c8b7d4a4ae060176a3529e72e5e4ffc4afd8b0", - "sha256:3ecb3f37c31d5d51d2a4d935cfa81c9bc956687c6f5237021b36d6fdc2815b2c", - "sha256:469fc61034f3daf095e02f9f1bbac07927b826c76b745207287bc594884cfd05", - "sha256:48324183af7f1bc44f5f197f3dad54a809ad1ef0c78baee2c88f16a5de02c4c9", - "sha256:50721858c935a76b83dd0dd1ab472cad0a3ef540a1408057624604002fcfb45b", - "sha256:54ff6624eb95b8a07e79aa8817288659af174e954cca24cdb0daeeddfc03c4ff", - "sha256:5be83986100ed1fdfa78f11ccff9e4757297735ac17391b95e17e74335c2047d", - "sha256:5f9ca13babe4d845e400921973f6165a4c2f9f3379c7abfc7478160e25d196a4", - "sha256:73ff21559675150d31deea8f1f8d7e9a9a7e4688732a94d71327082f517fc6b4", - "sha256:7dbb71ea26d304e78ccccf6faccef71bb27ea35e259fb883cfd7fd7b4f17ecb1", - "sha256:815eafdf8b8f2e61370afc6add6194bd5a7252ae44c667e96c4c1ecf418811e4", - "sha256:841aa21110a20dc1621e3dd9f922c64ca64dd1eb213c47267a2c324d823f6c8f", - "sha256:84e4d2e6973eccc52778735befc01638498781ce0e39aa2044ccfd2385c03246", - "sha256:8f7da24eab0d4184715d96208b38d373fd15c37b0dafb74756c638bd619ba150", - "sha256:96c0fe9696398253f93482c84814f0e7290eee0bfec11563bd07d80d701280c3", - "sha256:aab1dbfdc55086c789f0eb37affccf47b895b98d490738b81f3b2360100426be", - "sha256:c03fb2d4ef4e393f2e6ffc6376410a22a3544f164b336b3a355226653e5efd89", - "sha256:c37a1ca83825bc6f54dddf5277e9c65dec2f1b4d0ba44b8fd42bc30c91aa6ea1", - "sha256:c92ff9ac7c2282009fe0dcb67ee3cd17978cffbe0c8f4b471c00fe4325c9b4d4", - "sha256:c9a3a47cd3aaeb71e93e681d9816c56406ed755b9442e981b07e3618fb71d2ac", - "sha256:cb925e1ca024d6f9b4f9b01d83215fd00fe69d095d0255ff3f64bffda74025c8", - "sha256:d07b01c51b0b6ceb0f09fc48ec58debd99d2c8430b09e56651addeaf5de48048", - "sha256:e76c7f351e0444721e85f975ae92718e21c1f361bda946d60a214061de1f00a1", - "sha256:eb4813b77d5df99903af4757ce980a98c4d702bbcb81f32a0b305a1537bdf0b1" - ], - "index": "pypi", - "version": "==0.11.0" - }, "packaging": { "hashes": [ "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5", @@ -1129,11 +951,11 @@ }, "pyflakes": { "hashes": [ - "sha256:4132f6d49cb4dae6819e5379898f2b8cce3c5f23994194c24b77d5da2e36f774", - "sha256:a0aae034c444db0071aa077972ba4768d40c830d9539fd45bf4cd3f8f6992efc" + "sha256:1c61603ff154621fb2a9172037d84dca3500def8c8b630657d1701f026f8af3f", + "sha256:84b5be138a2dfbb40689ca07e2152deb896a65c3a3e24c251c5c62489568074a" ], "markers": "python_version >= '3.8'", - "version": "==3.1.0" + "version": "==3.2.0" }, "pylint": { "hashes": [ @@ -1179,14 +1001,6 @@ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==2.8.2" }, - "setuptools": { - "hashes": [ - "sha256:385eb4edd9c9d5c17540511303e39a147ce2fc04bc55289c322b9e5904fe2c05", - "sha256:be1af57fc409f93647f2e8e4573a142ed38724b8cdd389706a867bb4efcf1e78" - ], - "markers": "python_version >= '3.8'", - "version": "==69.0.3" - }, "six": { "hashes": [ "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", @@ -1202,37 +1016,6 @@ ], "version": "==2.2.0" }, - "sqlalchemy": { - "hashes": [ - "sha256:0535d5b57d014d06ceeaeffd816bb3a6e2dddeb670222570b8c4953e2d2ea678", - "sha256:0892e7ac8bc76da499ad3ee8de8da4d7905a3110b952e2a35a940dab1ffa550e", - "sha256:0d661cff58c91726c601cc0ee626bf167b20cc4d7941c93c5f3ac28dc34ddbea", - "sha256:245c67c88e63f1523e9216cad6ba3107dea2d3ee19adc359597a628afcabfbcb", - "sha256:2ad16880ccd971ac8e570550fbdef1385e094b022d6fc85ef3ce7df400dddad3", - "sha256:2be4e6294c53f2ec8ea36486b56390e3bcaa052bf3a9a47005687ccf376745d1", - "sha256:2c55040d8ea65414de7c47f1a23823cd9f3fad0dc93e6b6b728fee81230f817b", - "sha256:352df882088a55293f621328ec33b6ffca936ad7f23013b22520542e1ab6ad1b", - "sha256:3823dda635988e6744d4417e13f2e2b5fe76c4bf29dd67e95f98717e1b094cad", - "sha256:38ef80328e3fee2be0a1abe3fe9445d3a2e52a1282ba342d0dab6edf1fef4707", - "sha256:3ec7a0ed9b32afdf337172678a4a0e6419775ba4e649b66f49415615fa47efbd", - "sha256:55e699466106d09f028ab78d3c2e1f621b5ef2c8694598242259e4515715da7c", - "sha256:6cacc0b2dd7d22a918a9642fc89840a5d3cee18a0e1fe41080b1141b23b10916", - "sha256:7deeae5071930abb3669b5185abb6c33ddfd2398f87660fafdb9e6a5fb0f3f2f", - "sha256:86a22143a4001f53bf58027b044da1fb10d67b62a785fc1390b5c7f089d9838c", - "sha256:8ca484ca11c65e05639ffe80f20d45e6be81fbec7683d6c9a15cd421e6e8b340", - "sha256:af55cc207865d641a57f7044e98b08b09220da3d1b13a46f26487cc2f898a072", - "sha256:b97fd5bb6b7c1a64b7ac0632f7ce389b8ab362e7bd5f60654c2a418496be5d7f", - "sha256:c37bc677690fd33932182b85d37433845de612962ed080c3e4d92f758d1bd894", - "sha256:d3cf56cc36d42908495760b223ca9c2c0f9f0002b4eddc994b24db5fcb86a9e4", - "sha256:e646b19f47d655261b22df9976e572f588185279970efba3d45c377127d35349", - "sha256:e7908c2025eb18394e32d65dd02d2e37e17d733cdbe7d78231c2b6d7eb20cdb9", - "sha256:e8f2df79a46e130235bc5e1bbef4de0583fb19d481eaa0bffa76e8347ea45ec6", - "sha256:eb18549b770351b54e1ab5da37d22bc530b8bfe2ee31e22b9ebe650640d2ef12", - "sha256:f8cafa6f885a0ff5e39efa9325195217bb47d5929ab0051636610d24aef45ade" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", - "version": "==1.4.51" - }, "tomlkit": { "hashes": [ "sha256:75baf5012d06501f07bee5bf8e801b9f343e7aac5a92581f20f80ce632e6b5a4", @@ -1243,46 +1026,47 @@ }, "types-beautifulsoup4": { "hashes": [ - "sha256:59980028d29bf55d0db359efa305b75bacf0cb92e3f3f6b3fd408f2531df274c", - "sha256:8b03b054cb2e62abf82bbbeda57a07257026f4ed9010ef17d8f8eff43bb1f9b7" + "sha256:98d628985b71b140bd3bc22a8cb0ab603c2f2d08f20d37925965eb4a21739be8", + "sha256:cbdd60ab8aeac737ac014431b6e921b43e84279c0405fdd25a6900bb0e71da5b" ], "index": "pypi", - "markers": "python_version >= '3.7'", - "version": "==4.12.0.7" + "markers": "python_version >= '3.8'", + "version": "==4.12.0.20240106" }, "types-html5lib": { "hashes": [ - "sha256:16fe936d99b9f7fc210e2e21a2aed1b6bbbc554ad8242a6ef75f6f2bddb27e58", - "sha256:80e1a2062d22a3affe5c28d97da30bffbf3a076d393c80fc6f1671216c1bd492" + "sha256:61993cb89220107481e0f1da65c388ff8cf3d8c5f6e8483c97559639a596b697", + "sha256:fc3a1b18eb601b3eeaf92c900bd67675c0a4fa1dd1d2a2893ebdb46923547ee9" ], - "version": "==1.1.11.15" + "markers": "python_version >= '3.8'", + "version": "==1.1.11.20240106" }, "types-psutil": { "hashes": [ - "sha256:2161d166256084acf629d30aaf6bda8bee726ae1fea530559650281056b491fc", - "sha256:f7d8769812d72a4b513d7ec9eb5580fe2f6013fc270394a603cb6534811f3e4d" + "sha256:60b233fb613b41fe859526103dbda0b4812d7a16c5f791119ec7016fbe1c8128", + "sha256:fea169a85b1bb9d9edd0b063a93ad950e37d574290b1bf11ef5e46c9c5d82326" ], "index": "pypi", - "markers": "python_version >= '3.7'", - "version": "==5.9.5.17" + "markers": "python_version >= '3.8'", + "version": "==5.9.5.20240106" }, "types-requests": { "hashes": [ - "sha256:0f8c0c9764773384122813548d9eea92a5c4e1f33ed54556b508968ec5065cee", - "sha256:2e2230c7bc8dd63fa3153c1c0ae335f8a368447f0582fc332f17d54f88e69027" + "sha256:0e1c731c17f33618ec58e022b614a1a2ecc25f7dc86800b36ef341380402c612", + "sha256:da997b3b6a72cc08d09f4dba9802fdbabc89104b35fe24ee588e674037689354" ], "index": "pypi", - "markers": "python_version >= '3.7'", - "version": "==2.31.0.20231231" + "markers": "python_version >= '3.8'", + "version": "==2.31.0.20240106" }, "types-setuptools": { "hashes": [ - "sha256:8c86195bae2ad81e6dea900a570fe9d64a59dbce2b11cc63c046b03246ea77bf", - "sha256:b0a06219f628c6527b2f8ce770a4f47550e00d3e8c3ad83e2dc31bc6e6eda95d" + "sha256:b1da8981425723a674fd459c43dfa4402abeaee3f9cf682723ee9cf226125cc3", + "sha256:e077f9089578df3c9938f6e4aa1633f182ba6740a6fdb1333f162bae5dfcbadc" ], "index": "pypi", - "markers": "python_version >= '3.7'", - "version": "==69.0.0.0" + "markers": "python_version >= '3.8'", + "version": "==69.0.0.20240106" }, "typing-extensions": { "hashes": [ diff --git a/device/README.md b/device/README.md index 0d7f805..745d06e 100644 --- a/device/README.md +++ b/device/README.md @@ -1,15 +1,22 @@ -# FD Device Devive repo +# FD Device Device repo ## Vscode Dev Container -Run ```pipenv install --dev``` inside the container after setting it up +When the dev container is first built, you will need to configure the virtual environment inside the container after setting it up ```bash cd device pipenv install --dev ``` + +Then you may be prompted to select a python interpreter and/or reload the window for some of the python extensions to work properly. This only needs to be done when the dev container is rebuilt. + +The installation of the dependencies and the setup of the virtual environment could be done automatically using a dockerfile step, but it is commented out to help speed up the dev container build time. + + ## Commands +The available app commands are: - ```pipenv shell``` - start the virtual environment - ```pipenv install``` - install dependencies - ```pipenv install --dev``` - install dev dependencies diff --git a/device/fd_device/cli/db/commands.py b/device/fd_device/cli/db/commands.py index e22f0f0..77a16f1 100644 --- a/device/fd_device/cli/db/commands.py +++ b/device/fd_device/cli/db/commands.py @@ -3,10 +3,10 @@ from alembic import command as al_command from alembic.config import Config as AlConfig -from fd_device.database.base import ( +from fd_device.database.database import ( + Base, create_all_tables, drop_all_tables, - get_base, get_session, ) @@ -39,9 +39,8 @@ def delete_all_data(confirm): else: click.echo("deleting all data from the database.") - base = get_base() session = get_session() - for table in reversed(base.metadata.sorted_tables): + for table in reversed(Base.metadata.sorted_tables): session.execute(table.delete()) session.commit() diff --git a/device/fd_device/cli/manage/setup_commands.py b/device/fd_device/cli/manage/setup_commands.py index 7e3bf73..9082dce 100644 --- a/device/fd_device/cli/manage/setup_commands.py +++ b/device/fd_device/cli/manage/setup_commands.py @@ -2,9 +2,10 @@ from datetime import datetime import click +from sqlalchemy import select from sqlalchemy.orm.exc import NoResultFound -from fd_device.database.base import get_session +from fd_device.database.database import get_session from fd_device.database.device import Device, Grainbin from fd_device.database.system import Hardware, Software, SystemSetup from fd_device.device.temperature import get_connected_sensors @@ -34,7 +35,7 @@ def first_setup(standalone): # noqa: C901 session = get_session() try: - system = session.query(SystemSetup).one() + system = session.scalars(select(SystemSetup)).one() except NoResultFound: system = SystemSetup() session.add(system) @@ -147,14 +148,14 @@ def initialize_device(): session = get_session() try: - hd = session.query(Hardware).one() - sd = session.query(Software).one() + hd = session.scalars(select(Hardware)).one() + sd = session.scalars(select(Software)).one() except NoResultFound: session.close() return try: - device = session.query(Device).one() + device = session.scalars(select(Device)).one() except NoResultFound: device = Device( device_id=hd.serial_number, diff --git a/device/fd_device/database/base.py b/device/fd_device/database/base.py deleted file mode 100755 index de50af5..0000000 --- a/device/fd_device/database/base.py +++ /dev/null @@ -1,41 +0,0 @@ -"""Database base configurations.""" -from sqlalchemy import create_engine -from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy.orm import scoped_session, sessionmaker - -from ..settings import get_config - -Base = declarative_base() - -config = get_config() # pylint: disable=invalid-name -engine = create_engine(config.SQLALCHEMY_DATABASE_URI) -db_session = scoped_session(sessionmaker(bind=engine)) - - -def get_session(): - """Return the sqlalchemy db_session.""" - - return db_session - - -def get_base(with_query=False): - """Return the sqlalchemy base. - - :param with_query=False. If True is passed, it adds the query property to models. - """ - if with_query: - # Adds Query Property to Models - enables `User.query.query_method()` - Base.query = db_session.query_property() - return Base - - -def create_all_tables(): - """Create all tables.""" - base = get_base(with_query=True) - base.metadata.create_all(bind=engine) - - -def drop_all_tables(): - """Drop all tables.""" - base = get_base() - base.metadata.drop_all(bind=engine) diff --git a/device/fd_device/database/database.py b/device/fd_device/database/database.py index 43821a9..3d60558 100755 --- a/device/fd_device/database/database.py +++ b/device/fd_device/database/database.py @@ -1,49 +1,81 @@ # -*- coding: utf-8 -*- """Database module, including the SQLAlchemy database object and DB-related utilities.""" -from sqlalchemy import Column, ForeignKey, Integer +from sqlalchemy import ForeignKey, String, create_engine +from sqlalchemy.orm import ( + DeclarativeBase, + Mapped, + mapped_column, + scoped_session, + sessionmaker, +) +from sqlalchemy.orm.session import Session +from typing_extensions import Annotated -from fd_device.database.base import get_base, get_session +from ..settings import get_config -Base = get_base(with_query=True) +config = get_config() # pylint: disable=invalid-name +engine = create_engine(config.SQLALCHEMY_DATABASE_URI) +db_session = scoped_session(sessionmaker(bind=engine)) + +# special types for SQLAlchemy Base class below +str20 = Annotated[str, 20] # pylint: disable=invalid-name +str7 = Annotated[str, 7] # pylint: disable=invalid-name + + +def get_session() -> Session: + """Return the sqlalchemy db_session.""" + + return db_session() + + +def create_all_tables(): + """Create all tables.""" + Base.metadata.create_all(bind=engine) + + +def drop_all_tables(): + """Drop all tables.""" + Base.metadata.drop_all(bind=engine) + + +class Base(DeclarativeBase): # pylint: disable=too-few-public-methods + """Base class for SQLAlchemy model definitions.""" + + type_annontation_map = { + str20: String(20), + str7: String(7), + } class CRUDMixin: """Mixin that adds convenience methods for CRUD (create, read, update, delete) operations.""" @classmethod - def create(cls, session=None, **kwargs): + def create(cls, **kwargs): """Create a new record and save it the database.""" - if session is None: - session = get_session() instance = cls(**kwargs) - return instance.save(session) + return instance.save() - def update(self, session=None, commit=True, **kwargs): + def update(self, commit=True, **kwargs): """Update specific fields of a record.""" - if session is None: - session = get_session() for attr, value in kwargs.items(): setattr(self, attr, value) - return self.save(session) if commit else self + return self.save() if commit else self - def save(self, session=None, commit=True): + def save(self, commit=True): """Save the record.""" - if session is None: - session = get_session() - session.add(self) + db_session.add(self) if commit: - session.commit() + db_session.commit() return self - def delete(self, session=None, commit=True): + def delete(self, commit=True): """Remove the record from the database.""" - if session is None: - session = get_session() - session.delete(self) - return commit and session.commit() + db_session.delete(self) + return commit and db_session.commit() -class Model(CRUDMixin, Base): # type: ignore[valid-type, misc] +class Model(CRUDMixin, Base): """Base model class that includes CRUD convenience methods.""" __abstract__ = True @@ -57,10 +89,10 @@ class SurrogatePK(Model): # pylint: disable=too-few-public-methods __table_args__ = {"extend_existing": True} __abstract__ = True - id = Column(Integer, primary_key=True) + id: Mapped[int] = mapped_column(primary_key=True) @classmethod - def get_by_id(cls, record_id, session=None): + def get_by_id(cls, record_id): """Get record by ID.""" if any( ( @@ -68,19 +100,18 @@ def get_by_id(cls, record_id, session=None): isinstance(record_id, (int, float)), ), ): - if session: - return session.query(cls).get(int(record_id)) - - return cls.query.get(int(record_id)) + return db_session.get(cls, int(record_id)) return None -def reference_col(tablename, nullable=False, pk_name="id", **kwargs): +def reference_col(tablename, nullable=False, pk_name="id", **kwargs) -> Mapped[int]: """Column that adds primary key foreign key reference. Usage: :: - category_id = reference_col('category') - category = relationship('Category', backref='categories') + category_id: Mapped[ForeignKey] = reference_col('category') + categorys: Mapped[List["Category"]] = relationship(backref='categories') """ - return Column(ForeignKey(f"{tablename}.{pk_name}"), nullable=nullable, **kwargs) + return mapped_column( + ForeignKey(f"{tablename}.{pk_name}"), nullable=nullable, **kwargs + ) diff --git a/device/fd_device/database/device.py b/device/fd_device/database/device.py index 662e3cf..227c081 100755 --- a/device/fd_device/database/device.py +++ b/device/fd_device/database/device.py @@ -1,33 +1,42 @@ """The device models for the database.""" -from sqlalchemy import Boolean, Column, DateTime, Integer, String -from sqlalchemy.orm import relationship +from datetime import datetime +from typing import List, Optional + +from sqlalchemy import String +from sqlalchemy.orm import Mapped, mapped_column, relationship from sqlalchemy.sql import func -from .database import SurrogatePK, reference_col +from .database import SurrogatePK, reference_col, str7, str20 + +# https://github.com/pylint-dev/pylint/issues/8138 +# can be removed once upstream issue in pylint is fixed +# pylint: disable=not-callable class Connection(SurrogatePK): """Represent the device's connecticon to the server.""" __tablename__ = "connection" - address = Column(String(20)) - last_updated = Column(DateTime, onupdate=func.now()) - first_connected = Column(DateTime, default=func.now()) - is_connected = Column(Boolean, default=False) + address: Mapped[Optional[str20]] + last_updated: Mapped[Optional[datetime]] = mapped_column(onupdate=func.now()) + first_connected: Mapped[datetime] = mapped_column(default=func.now()) + is_connected: Mapped[bool] = mapped_column(default=False) class Grainbin(SurrogatePK): """Represent a Grainbin that is connected to the device.""" __tablename__ = "grainbin" - name = Column(String(20), unique=True) - bus_number = Column(Integer, nullable=False) - bus_number_string = Column(String(10), nullable=True) - creation_time = Column(DateTime, default=func.now()) - last_updated = Column(DateTime, default=func.now(), onupdate=func.now()) - average_temp = Column(String(7)) + name: Mapped[str20] = mapped_column(unique=True) + bus_number: Mapped[int] + bus_number_string: Mapped[str] = mapped_column(String(10), nullable=True) + creation_time: Mapped[datetime] = mapped_column(default=func.now()) + last_updated: Mapped[datetime] = mapped_column( + default=func.now(), onupdate=func.now() + ) + average_temp: Mapped[str7] - device_id = reference_col("device") + device_id: Mapped[int] = reference_col("device") def __init__(self, name: str, bus_number: int, device_id: int): """Create the Grainbin object.""" @@ -46,22 +55,24 @@ class Device(SurrogatePK): """Represent the Device.""" __tablename__ = "device" - device_id = Column(String(20), unique=True) - hardware_version = Column(String(20)) - software_version = Column(String(20)) - creation_time = Column(DateTime, default=func.now()) - last_updated = Column(DateTime, default=func.now(), onupdate=func.now()) - - interior_sensor = Column(String(20), nullable=True, default=None) - exterior_sensor = Column(String(20), nullable=True, default=None) - interior_temp = Column(String(7), nullable=True, default=None) - exterior_temp = Column(String(7), nullable=True, default=None) + device_id: Mapped[str20] = mapped_column(unique=True) + hardware_version: Mapped[Optional[str20]] + software_version: Mapped[Optional[str20]] + creation_time: Mapped[datetime] = mapped_column(default=func.now()) + last_updated: Mapped[datetime] = mapped_column( + default=func.now(), onupdate=func.now() + ) + + interior_sensor: Mapped[Optional[str20]] + exterior_sensor: Mapped[Optional[str20]] + interior_temp: Mapped[Optional[str7]] + exterior_temp: Mapped[Optional[str7]] # grainbin related data - grainbin_count = Column(Integer, default=0) - grainbins = relationship("Grainbin", backref="device") + grainbin_count: Mapped[int] = mapped_column(default=0) + grainbins: Mapped[List["Grainbin"]] = relationship(backref="device") - def __init__(self, device_id, interior_sensor="null", exterior_sensor="null"): + def __init__(self, device_id: str, interior_sensor="null", exterior_sensor="null"): """Create the Device object.""" self.device_id = device_id self.interior_sensor = interior_sensor diff --git a/device/fd_device/database/system.py b/device/fd_device/database/system.py index cab5d86..e928660 100755 --- a/device/fd_device/database/system.py +++ b/device/fd_device/database/system.py @@ -1,9 +1,19 @@ """The system models for the database.""" -from sqlalchemy import Boolean, Column, DateTime, Integer, String -from sqlalchemy.orm import relationship +# pylint: disable=duplicate-code +from datetime import datetime +from typing import List, Optional + +from sqlalchemy import String +from sqlalchemy.orm import Mapped, mapped_column, relationship from sqlalchemy.sql import func -from .database import SurrogatePK, reference_col +from .database import SurrogatePK, reference_col, str20 + +# pylint: enable=duplicate-code + +# https://github.com/pylint-dev/pylint/issues/8138 +# can be removed once upstream issue in pylint is fixed +# pylint: disable=not-callable class SystemSetup(SurrogatePK): @@ -11,9 +21,9 @@ class SystemSetup(SurrogatePK): __tablename__ = "system_setup" - first_setup = Column(Boolean, default=False) - first_setup_time = Column(DateTime, default=func.now()) - standalone_configuration = Column(Boolean, default=True) + first_setup: Mapped[bool] = mapped_column(default=False) + first_setup_time: Mapped[datetime] = mapped_column(default=func.now()) + standalone_configuration: Mapped[bool] = mapped_column(default=True) def __init__(self): """Create the SystemSetup object.""" @@ -25,12 +35,12 @@ class Wifi(SurrogatePK): __tablename__ = "system_wifi" - name = Column(String(20), default="FarmMonitor") - password = Column(String(20), default="raspberry") - mode = Column(String(20), default="wpa") + name: Mapped[str20] = mapped_column(default="FarmMonitor") + password: Mapped[str20] = mapped_column(default="raspberry") + mode: Mapped[str20] = mapped_column(default="wpa") - interface_id = reference_col("system_interface", nullable=True) - interface = relationship("Interface", backref="credentials") + interface_id: Mapped[int] = reference_col("system_interface", nullable=True) + interface: Mapped["Interface"] = relationship(back_populates="credentials") def __init__(self): """Create the Wifi object.""" @@ -42,11 +52,13 @@ class Interface(SurrogatePK): __tablename__ = "system_interface" - interface = Column(String(5), nullable=True) - is_active = Column(Boolean, default=True) - is_for_fm = Column(Boolean, default=False) - is_external = Column(Boolean, default=False) - state = Column(String(20)) + interface: Mapped[str] = mapped_column(String(5), nullable=True) + is_active: Mapped[bool] = mapped_column(default=True) + is_for_fm: Mapped[bool] = mapped_column(default=False) + is_external: Mapped[bool] = mapped_column(default=False) + state: Mapped[Optional[str20]] + + credentials: Mapped[List["Wifi"]] = relationship(back_populates="interface") def __init__(self, interface): """Create the interface object.""" @@ -59,14 +71,14 @@ class Hardware(SurrogatePK): __tablename__ = "system_hardware" - device_name = Column(String(20)) - hardware_version = Column(String(20)) + device_name: Mapped[Optional[str20]] + hardware_version: Mapped[Optional[str20]] - interior_sensor = Column(String(20), nullable=True, default=None) - exterior_sensor = Column(String(20), nullable=True, default=None) + interior_sensor: Mapped[str20] = mapped_column(nullable=True, default=None) + exterior_sensor: Mapped[str20] = mapped_column(nullable=True, default=None) - serial_number = Column(String(20)) - grainbin_reader_count = Column(Integer, default=0) + serial_number: Mapped[Optional[str20]] + grainbin_reader_count: Mapped[int] = mapped_column(default=0) def __init__(self): """Create the Hardware object.""" @@ -78,8 +90,8 @@ class Software(SurrogatePK): __tablename__ = "system_software" - software_version = Column(String(20)) - software_version_last = Column(String(20)) + software_version: Mapped[Optional[str20]] + software_version_last: Mapped[Optional[str20]] def __init__(self): """Create the Software object.""" diff --git a/device/fd_device/device/service.py b/device/fd_device/device/service.py index 0399ffd..9f3c38b 100755 --- a/device/fd_device/device/service.py +++ b/device/fd_device/device/service.py @@ -7,7 +7,7 @@ from fd_device.celery_runner import app from fd_device.controller.connection import Connection, Message -from fd_device.database.base import get_session +from fd_device.database.database import get_session from fd_device.database.device import Connection as db_Connection from fd_device.database.device import Device from fd_device.device.update import get_device_info diff --git a/device/fd_device/device/update.py b/device/fd_device/device/update.py index 5b108e0..b60d85c 100755 --- a/device/fd_device/device/update.py +++ b/device/fd_device/device/update.py @@ -1,7 +1,9 @@ """Create a device update object.""" import datetime -from fd_device.database.base import get_session +from sqlalchemy import select + +from fd_device.database.database import get_session from fd_device.database.device import Device from fd_device.device.temperature import temperature @@ -14,7 +16,11 @@ def get_device_info(session=None) -> dict: close_session = True session = get_session() - device: Device = session.query(Device).first() + device = session.scalars(select(Device).limit(1)).first() + if device is None: + # this is an error, there should always be a device + return {} + device.interior_temp = temperature(device.interior_sensor) device.exterior_temp = temperature(device.exterior_sensor) session.commit() diff --git a/device/fd_device/grainbin/update.py b/device/fd_device/grainbin/update.py index 7e92e11..8d09dcf 100755 --- a/device/fd_device/grainbin/update.py +++ b/device/fd_device/grainbin/update.py @@ -2,10 +2,12 @@ import datetime import logging import statistics +from typing import Optional +from sqlalchemy import select from sqlalchemy.orm.session import Session -from fd_device.database.base import get_session +from fd_device.database.database import get_session from fd_device.database.device import Grainbin from fd_device.grainbin.owfs_interface import ( get_all_busses, @@ -16,7 +18,7 @@ LOGGER = logging.getLogger("fd.grainbin.update") -def get_grainbin_updates(session: Session = None) -> list: +def get_grainbin_updates(session: Optional[Session] = None) -> list: """Get all grainbin updates as a list for each grainbin.""" close_session = False @@ -26,7 +28,7 @@ def get_grainbin_updates(session: Session = None) -> list: all_updates: list = [] - grainbins: list[Grainbin] = session.query(Grainbin).all() + grainbins: list[Grainbin] = list(session.scalars(select(Grainbin)).all()) all_busses = get_all_busses() diff --git a/device/fd_device/main.py b/device/fd_device/main.py index 8c6d182..676a018 100755 --- a/device/fd_device/main.py +++ b/device/fd_device/main.py @@ -7,7 +7,7 @@ from multiprocessing_logging import install_mp_handler from fd_device.celery_runner import run_scheduled_tasks -from fd_device.database.base import get_session +from fd_device.database.database import get_session from .settings import get_config from .startup import check_if_setup, get_rabbitmq_address diff --git a/device/fd_device/network/ethernet.py b/device/fd_device/network/ethernet.py index 26b4e55..b3a72d9 100755 --- a/device/fd_device/network/ethernet.py +++ b/device/fd_device/network/ethernet.py @@ -5,7 +5,7 @@ import netifaces from netifaces import AF_INET -from fd_device.database.base import get_session +from fd_device.database.database import get_session from fd_device.database.system import Interface logger = logging.getLogger("fm.network.ethernet") @@ -72,10 +72,11 @@ def get_external_interface() -> str: if ethernet_connected(): ethernet = session.query(Interface).filter_by(interface="eth0").first() - ethernet.is_external = True - session.commit() - session.close() - return "eth0" + if ethernet is not None: + ethernet.is_external = True + session.commit() + session.close() + return "eth0" # now check if it is either wlan0 or wlan1 interfaces = session.query(Interface).filter_by(state="dhcp").all() diff --git a/device/fd_device/network/wifi.py b/device/fd_device/network/wifi.py index e3ec687..3c92273 100755 --- a/device/fd_device/network/wifi.py +++ b/device/fd_device/network/wifi.py @@ -1,13 +1,13 @@ """Control the wifi connections of the device.""" import logging import subprocess -from typing import List, Optional +from typing import List, Optional, TypedDict import netifaces from sqlalchemy.orm.exc import NoResultFound from sqlalchemy.orm.session import object_session -from fd_device.database.base import get_session +from fd_device.database.database import get_session from fd_device.database.system import Interface, Wifi from fd_device.network.ethernet import get_external_interface, get_interfaces from fd_device.network.network_files import ( @@ -132,7 +132,12 @@ def add_wifi_network( break # if an interface is passed in, get the session from the interface. else: - session = object_session(interface) + retrieved_session = object_session(interface) + if retrieved_session is None: + logger.error("No session available from the supplied interface") + session = get_session() + else: + session = retrieved_session if interface is None: logger.error("No interface available to add new wifi network") @@ -169,7 +174,19 @@ def delete_wifi_network(wifi_id: str) -> bool: return bool(deleted_count > 0) -def wifi_info() -> List: +class WifiInfo(TypedDict): + """The information that is returned from the wifi_info function.""" + + interface: Interface + clients: int + ssid: str + password: str + state: str + state_boolean: bool + address: str + + +def wifi_info() -> List[WifiInfo]: """Get a list of WiFi details for all wlan interfaces. Returns: @@ -192,34 +209,48 @@ def wifi_info() -> List: wlan_interfaces = get_interfaces(keep_eth=False) - wifi = [] + wifi: List[WifiInfo] = [] session = get_session() for w_interface in wlan_interfaces: try: - info = {} interface = session.query(Interface).filter_by(interface=w_interface).one() - info["interface"] = interface if interface.state == "ap": - info["clients"] = wifi_ap_clients(interface.interface) - info["ssid"] = interface.credentials[0].name - info["password"] = interface.credentials[0].password + wifi_clients = wifi_ap_clients(interface.interface) + wifi_ssid = interface.credentials[0].name + wifi_password = interface.credentials[0].password + wifi_state = "ap" + wifi_state_boolean = True + wifi_address = "" else: - info["state"] = wifi_dhcp_info(interface.interface) - if info["state"] is False: - info["state_boolean"] = False + wifi_clients = 0 + wifi_ssid = "" + wifi_password = "" + wifi_state = wifi_dhcp_info(interface.interface) + wifi_address = "" + if wifi_state == "Not connected": + wifi_state_boolean = False else: - info["state_boolean"] = True + wifi_state_boolean = True if w_interface in netifaces.interfaces(): address = netifaces.ifaddresses(w_interface) - info["address"] = address[netifaces.AF_INET][0]["addr"] + wifi_address = address[netifaces.AF_INET][0]["addr"] if interface.credentials: - info["ssid"] = interface.credentials[0].name - info["password"] = interface.credentials[0].password - - wifi.append(info) + wifi_ssid = interface.credentials[0].name + wifi_password = interface.credentials[0].password + wifi.append( + WifiInfo( + interface=interface, + clients=wifi_clients, + ssid=wifi_ssid, + password=wifi_password, + state=wifi_state, + state_boolean=wifi_state_boolean, + address=wifi_address, + ) + ) except NoResultFound: pass diff --git a/device/fd_device/startup.py b/device/fd_device/startup.py index 9665f5e..24816ed 100644 --- a/device/fd_device/startup.py +++ b/device/fd_device/startup.py @@ -4,7 +4,8 @@ import pika from pika.exceptions import AMQPConnectionError -from sqlalchemy.orm.exc import NoResultFound +from sqlalchemy import select as sql_select +from sqlalchemy.orm import Session from fd_device.database.device import Connection from fd_device.database.system import Interface, SystemSetup @@ -12,7 +13,7 @@ from fd_device.system.info import get_ip_of_interface -def check_if_setup(logger, session): +def check_if_setup(logger, session: Session) -> bool: """Check if the system has been setup. @return True if the system has been setup, False otherwise. @@ -20,26 +21,25 @@ def check_if_setup(logger, session): logger.debug("Checking if the system has been setup.") - try: - system_setup = session.query(SystemSetup).one() - if system_setup.first_setup: - logger.debug(f"System has been setup on {system_setup.first_setup_time}") - return True + system_setup = session.execute(sql_select(SystemSetup)).scalar_one_or_none() - except NoResultFound: + if system_setup is None: logger.warn("System has not been setup") return False + if system_setup.first_setup: + logger.debug(f"System has been setup on {system_setup.first_setup_time}") + return True + logger.warn("System has not been setup") return False -def get_rabbitmq_address(logger, session): # noqa: C901 +def get_rabbitmq_address(logger, session: Session): # noqa: C901 """Find and return the address of the RabbitMQ server to connect to.""" - try: - connection = session.query(Connection).one() - except NoResultFound: + connection = session.execute(sql_select(Connection)).scalar_one_or_none() + if connection is None: connection = Connection() session.add(connection) @@ -90,7 +90,9 @@ def search_on_socket(logger, session, connection): presence_port = config.PRESENCE_PORT # get the first interface that is for farm monitor - interface = session.query(Interface.interface).filter_by(is_for_fm=True).scalar() + interface = session.execute( + sql_select(Interface.interface).filter_by(is_for_fm=True) + ).scalar_one() if interface is None: logger.warning( diff --git a/device/fd_device/system/control.py b/device/fd_device/system/control.py index abcfa03..79a38be 100755 --- a/device/fd_device/system/control.py +++ b/device/fd_device/system/control.py @@ -2,9 +2,10 @@ import logging import subprocess +from sqlalchemy import select from sqlalchemy.orm.exc import NoResultFound -from fd_device.database.base import get_session +from fd_device.database.database import get_session from fd_device.database.system import Hardware, Software from fd_device.device.temperature import get_connected_sensors @@ -65,7 +66,7 @@ def set_sensor_info(interior: int, exterior: int): # now set the sensor info into the tables session = get_session() try: - hd = session.query(Hardware).one() + hd = session.execute(select(Hardware)).scalar_one() except NoResultFound: hd = Hardware() @@ -96,7 +97,7 @@ def set_hardware_info(hardware_version: str, gb_reader_count: str): serial_number = get_serial() try: - hd = session.query(Hardware).one() + hd = session.execute(select(Hardware)).scalar_one() except NoResultFound: hd = Hardware() @@ -122,7 +123,7 @@ def set_software_info(software_version: str): session = get_session() try: - sd = session.query(Software).one() + sd = session.execute(select(Software)).scalar_one() except NoResultFound: sd = Software() diff --git a/device/migrations/env.py b/device/migrations/env.py index 30720f6..5c126c7 100644 --- a/device/migrations/env.py +++ b/device/migrations/env.py @@ -20,10 +20,9 @@ # add your model's MetaData object here # for 'autogenerate' support -from fd_device.database.base import get_base +from fd_device.database.database import Base from fd_device.database.device import Device, Grainbin from fd_device.database.system import Hardware -Base = get_base() target_metadata = Base.metadata # other values from the config, defined by the needs of env.py, diff --git a/device/migrations/versions/4988ca3aa994_update_to_sqlalchemy_2_0.py b/device/migrations/versions/4988ca3aa994_update_to_sqlalchemy_2_0.py new file mode 100644 index 0000000..a9f0ac4 --- /dev/null +++ b/device/migrations/versions/4988ca3aa994_update_to_sqlalchemy_2_0.py @@ -0,0 +1,146 @@ +"""Update to sqlalchemy 2.0 + +Revision ID: 4988ca3aa994 +Revises: 1637cff09270 +Create Date: 2024-01-06 06:31:47.498478 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = '4988ca3aa994' +down_revision = '1637cff09270' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.alter_column('connection', 'first_connected', + existing_type=postgresql.TIMESTAMP(), + nullable=False) + op.alter_column('connection', 'is_connected', + existing_type=sa.BOOLEAN(), + nullable=False) + op.alter_column('device', 'device_id', + existing_type=sa.VARCHAR(length=20), + nullable=False) + op.alter_column('device', 'creation_time', + existing_type=postgresql.TIMESTAMP(), + nullable=False) + op.alter_column('device', 'last_updated', + existing_type=postgresql.TIMESTAMP(), + nullable=False) + op.alter_column('device', 'grainbin_count', + existing_type=sa.INTEGER(), + nullable=False) + op.alter_column('grainbin', 'name', + existing_type=sa.VARCHAR(length=20), + nullable=False) + op.alter_column('grainbin', 'creation_time', + existing_type=postgresql.TIMESTAMP(), + nullable=False) + op.alter_column('grainbin', 'last_updated', + existing_type=postgresql.TIMESTAMP(), + nullable=False) + op.alter_column('grainbin', 'average_temp', + existing_type=sa.VARCHAR(length=7), + nullable=False) + op.alter_column('system_hardware', 'grainbin_reader_count', + existing_type=sa.INTEGER(), + nullable=False) + op.alter_column('system_interface', 'is_active', + existing_type=sa.BOOLEAN(), + nullable=False) + op.alter_column('system_interface', 'is_for_fm', + existing_type=sa.BOOLEAN(), + nullable=False) + op.alter_column('system_interface', 'is_external', + existing_type=sa.BOOLEAN(), + nullable=False) + op.alter_column('system_setup', 'first_setup', + existing_type=sa.BOOLEAN(), + nullable=False) + op.alter_column('system_setup', 'first_setup_time', + existing_type=postgresql.TIMESTAMP(), + nullable=False) + op.alter_column('system_setup', 'standalone_configuration', + existing_type=sa.BOOLEAN(), + nullable=False) + op.alter_column('system_wifi', 'name', + existing_type=sa.VARCHAR(length=20), + nullable=False) + op.alter_column('system_wifi', 'password', + existing_type=sa.VARCHAR(length=20), + nullable=False) + op.alter_column('system_wifi', 'mode', + existing_type=sa.VARCHAR(length=20), + nullable=False) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.alter_column('system_wifi', 'mode', + existing_type=sa.VARCHAR(length=20), + nullable=True) + op.alter_column('system_wifi', 'password', + existing_type=sa.VARCHAR(length=20), + nullable=True) + op.alter_column('system_wifi', 'name', + existing_type=sa.VARCHAR(length=20), + nullable=True) + op.alter_column('system_setup', 'standalone_configuration', + existing_type=sa.BOOLEAN(), + nullable=True) + op.alter_column('system_setup', 'first_setup_time', + existing_type=postgresql.TIMESTAMP(), + nullable=True) + op.alter_column('system_setup', 'first_setup', + existing_type=sa.BOOLEAN(), + nullable=True) + op.alter_column('system_interface', 'is_external', + existing_type=sa.BOOLEAN(), + nullable=True) + op.alter_column('system_interface', 'is_for_fm', + existing_type=sa.BOOLEAN(), + nullable=True) + op.alter_column('system_interface', 'is_active', + existing_type=sa.BOOLEAN(), + nullable=True) + op.alter_column('system_hardware', 'grainbin_reader_count', + existing_type=sa.INTEGER(), + nullable=True) + op.alter_column('grainbin', 'average_temp', + existing_type=sa.VARCHAR(length=7), + nullable=True) + op.alter_column('grainbin', 'last_updated', + existing_type=postgresql.TIMESTAMP(), + nullable=True) + op.alter_column('grainbin', 'creation_time', + existing_type=postgresql.TIMESTAMP(), + nullable=True) + op.alter_column('grainbin', 'name', + existing_type=sa.VARCHAR(length=20), + nullable=True) + op.alter_column('device', 'grainbin_count', + existing_type=sa.INTEGER(), + nullable=True) + op.alter_column('device', 'last_updated', + existing_type=postgresql.TIMESTAMP(), + nullable=True) + op.alter_column('device', 'creation_time', + existing_type=postgresql.TIMESTAMP(), + nullable=True) + op.alter_column('device', 'device_id', + existing_type=sa.VARCHAR(length=20), + nullable=True) + op.alter_column('connection', 'is_connected', + existing_type=sa.BOOLEAN(), + nullable=True) + op.alter_column('connection', 'first_connected', + existing_type=postgresql.TIMESTAMP(), + nullable=True) + # ### end Alembic commands ### diff --git a/device/setup.cfg b/device/setup.cfg index 4198372..924cc9f 100644 --- a/device/setup.cfg +++ b/device/setup.cfg @@ -9,12 +9,6 @@ python_version = 3.12 warn_return_any = True warn_unused_configs = True -[mypy-alembic.*] -ignore_missing_imports = True - -[mypy-sqlalchemy.*] -ignore_missing_imports = True - [mypy-celery.*] ignore_missing_imports = True @@ -29,9 +23,3 @@ ignore_missing_imports = True [mypy-factory.*] ignore_missing_imports = True - -[mypy-pyment.*] -ignore_missing_imports = True - -[mypy-schedule.*] -ignore_missing_imports = True \ No newline at end of file diff --git a/device/tests/conftest.py b/device/tests/conftest.py index 5f3d559..bcc9276 100644 --- a/device/tests/conftest.py +++ b/device/tests/conftest.py @@ -3,7 +3,7 @@ # pylint: disable=redefined-outer-name import pytest -from fd_device.database.base import create_all_tables, drop_all_tables, get_session +from fd_device.database.database import create_all_tables, drop_all_tables, get_session @pytest.fixture(scope="session") diff --git a/device/tests/database/test_device.py b/device/tests/database/test_device.py index 0070b16..3375966 100644 --- a/device/tests/database/test_device.py +++ b/device/tests/database/test_device.py @@ -30,8 +30,7 @@ def test_update_connection_object(): connection = Connection() connection.save() - connection.is_connected = True - connection.save() + connection.update(is_connected=True) assert isinstance(connection.last_updated, dt.datetime) assert bool(connection.is_connected) @@ -104,8 +103,9 @@ def test_create_grainbin(): device = DeviceFactory() device.save() - grainbin = Grainbin(name="TestGrainbin", bus_number=1, device_id=device.id) - grainbin.save() + grainbin = Grainbin.create( + name="TestGrainbin", bus_number=1, device_id=device.id + ) assert grainbin.device_id == device.id assert grainbin.bus_number == 1 @@ -117,8 +117,9 @@ def test_get_grainbin_by_id(): device = DeviceFactory() device.save() - grainbin = Grainbin(name="TestGrainbin", bus_number=1, device_id=device.id) - grainbin.save() + grainbin = Grainbin.create( + name="TestGrainbin", bus_number=1, device_id=device.id + ) retrieved = Grainbin.get_by_id(grainbin.id) diff --git a/device/tests/database/test_system.py b/device/tests/database/test_system.py index f3dc536..60da331 100644 --- a/device/tests/database/test_system.py +++ b/device/tests/database/test_system.py @@ -11,22 +11,20 @@ class TestSystemSetup: """SystemSetup model tests.""" @staticmethod - def test_create_systemsetup(dbsession): + def test_create_systemsetup(): """Create a SystemSetup instance.""" - system_setup = SystemSetup() - system_setup.save(dbsession) + system_setup = SystemSetup.create() assert not bool(system_setup.first_setup) assert isinstance(system_setup.first_setup_time, dt.datetime) assert bool(system_setup.standalone_configuration) @staticmethod - def test_get_system_setup_by_id(dbsession): + def test_get_system_setup_by_id(): """Retrieve a SystemSetup instance by id.""" - system_setup = SystemSetup() - system_setup.save(dbsession) + system_setup = SystemSetup.create() retrieved = SystemSetup.get_by_id(system_setup.id) assert retrieved.id == system_setup.id @@ -37,10 +35,9 @@ class TestInterface: """Interface model tests.""" @staticmethod - def test_create_interface(dbsession): + def test_create_interface(): """Create a Interface instance.""" - interface = Interface("eth0") - interface.save(dbsession) + interface = Interface.create(interface="eth0") assert interface.interface == "eth0" assert bool(interface.is_active) @@ -50,10 +47,9 @@ def test_create_interface(dbsession): assert interface.credentials == [] @staticmethod - def test_interface_get_by_id(dbsession): + def test_interface_get_by_id(): """Retrieve an interface by the id.""" - interface = Interface("eth0") - interface.save(dbsession) + interface = Interface.create(interface="eth0") retrieved = Interface.get_by_id(interface.id) assert retrieved.id == interface.id @@ -64,10 +60,9 @@ class TestWifi: """WiFi model tests.""" @staticmethod - def test_create_wifi(dbsession): + def test_create_wifi(): """Create a WiFi instance.""" - wifi = Wifi() - wifi.save(dbsession) + wifi = Wifi.create() assert wifi.name == "FarmMonitor" assert wifi.password == "raspberry" @@ -76,23 +71,20 @@ def test_create_wifi(dbsession): assert wifi.interface is None @staticmethod - def test_create_wifi_with_interface(dbsession): + def test_create_wifi_with_interface(): """Create a WiFi instance with an interface.""" - interface = Interface("eth0") - interface.save(dbsession) + interface = Interface.create(interface="eth0") - wifi = Wifi() - wifi.interface = interface - wifi.save(dbsession) + wifi = Wifi.create() + wifi.update(interface=interface) assert wifi.interface == interface assert wifi.interface_id == interface.id @staticmethod - def test_wifi_get_by_id(dbsession): + def test_wifi_get_by_id(): """Test retrieving a WiFi instance by id.""" - wifi = Wifi() - wifi.save(dbsession) + wifi = Wifi.create() retrieved = Wifi.get_by_id(wifi.id) @@ -104,10 +96,9 @@ class TestHardware: """Hardware model tests.""" @staticmethod - def test_create_hardware(dbsession): + def test_create_hardware(): """Create a Hardware instance.""" - hardware = Hardware() - hardware.save(dbsession) + hardware = Hardware.create() assert hardware.device_name is None assert hardware.hardware_version is None @@ -117,10 +108,9 @@ def test_create_hardware(dbsession): assert hardware.grainbin_reader_count == 0 @staticmethod - def test_hardware_get_by_id(dbsession): + def test_hardware_get_by_id(): """Retrieve a Hardware instance by the id.""" - hardware = Hardware() - hardware.save(dbsession) + hardware = Hardware.create() retrieved = Hardware.get_by_id(hardware.id) assert retrieved.id == hardware.id @@ -131,19 +121,17 @@ class TestSoftware: """Software model tests.""" @staticmethod - def test_create_software(dbsession): + def test_create_software(): """Create a Software instance.""" - software = Software() - software.save(dbsession) + software = Software.create() assert software.software_version is None assert software.software_version_last is None @staticmethod - def test_software_get_by_id(dbsession): + def test_software_get_by_id(): """Retrieve a Software instance by the id.""" - software = Software() - software.save(dbsession) + software = Software.create() retrieved = Software.get_by_id(software.id) assert retrieved.id == software.id diff --git a/device/tests/device/test_update.py b/device/tests/device/test_update.py index 8a2e50d..9020fb9 100644 --- a/device/tests/device/test_update.py +++ b/device/tests/device/test_update.py @@ -13,9 +13,7 @@ def test_get_device_info(mocker): """Test the get_device_info function.""" device = DeviceFactory() - device.interior_sensor = "sensor_1" - device.exterior_sensor = "sensor_2" - device.save() + device.update(interior_sensor="sensor_1", exterior_sensor="sensor_2") mocker.patch( "fd_device.device.update.temperature", diff --git a/device/tests/factories.py b/device/tests/factories.py index 225c53b..0279a4f 100644 --- a/device/tests/factories.py +++ b/device/tests/factories.py @@ -4,7 +4,7 @@ from factory.alchemy import SQLAlchemyModelFactory from factory.declarations import SelfAttribute, SubFactory -from fd_device.database.base import get_session +from fd_device.database.database import get_session from fd_device.database.device import Device, Grainbin @@ -21,8 +21,7 @@ def _create(cls, model_class, *args, **kwargs): correctly. """ obj = model_class(*args, **kwargs) - obj.save() - return obj + return obj.save() class Meta: """Factory configuration.""" diff --git a/device/tests/network/conftest.py b/device/tests/network/conftest.py index 599a19e..bc0bd54 100644 --- a/device/tests/network/conftest.py +++ b/device/tests/network/conftest.py @@ -6,13 +6,8 @@ @pytest.fixture() -def populate_interfaces(tables, dbsession): +def populate_interfaces(tables): """Populate interfaces into the database.""" interface = Interface("wlan0") - interface.is_active = True - interface.is_for_fm = True - interface.is_external = True - interface.state = "dhcp" - - interface.save(dbsession) + interface.update(is_active=True, is_for_fm=True, is_external=True, state="dhcp") diff --git a/device/tests/network/test_wifi.py b/device/tests/network/test_wifi.py index 7f078bd..b1af8e5 100644 --- a/device/tests/network/test_wifi.py +++ b/device/tests/network/test_wifi.py @@ -10,16 +10,15 @@ @pytest.mark.usefixtures("tables") -def test_add_wifi_network(dbsession): +def test_add_wifi_network(): """Test the add_wifi_network function.""" interface = Interface("wlan0") - interface.state = "dhcp" - interface.save(dbsession) + interface.update(state="dhcp") wifi = add_wifi_network(wifi_name="TestWiFiName", wifi_password="password") - dbsession.add(wifi) + wifi.save() assert wifi.name == "TestWiFiName" assert wifi.password == "password" @@ -29,12 +28,11 @@ def test_add_wifi_network(dbsession): @pytest.mark.usefixtures("tables") -def test_add_wifi_network_with_interface(dbsession): +def test_add_wifi_network_with_interface(): """Test the add_wifi_netwrok function passing in an Interface.""" interface = Interface("wlan0") - interface.state = "dhcp" - interface.save(dbsession) + interface.update(state="dhcp") wifi = add_wifi_network( wifi_name="TestWiFiName", wifi_password="password", interface=interface @@ -57,11 +55,11 @@ def test_add_wifi_network_no_interface(): @pytest.mark.usefixtures("populate_interfaces") -def test_delete_wifi_network(dbsession): +def test_delete_wifi_network(): """Test the delete_wifi_network function.""" wifi = add_wifi_network(wifi_name="TestWiFiName", wifi_password="password") - dbsession.add(wifi) + wifi.save() confirmed_deleted = delete_wifi_network(wifi.id) retrieved = Wifi.get_by_id(wifi.id) diff --git a/device/tests/system/test_control.py b/device/tests/system/test_control.py index eaa6a23..0e5fdca 100644 --- a/device/tests/system/test_control.py +++ b/device/tests/system/test_control.py @@ -3,6 +3,7 @@ TODO: add tests for set_device_name, set_service_state, set_sensor_info """ import pytest +from sqlalchemy import select from sqlalchemy.orm.exc import NoResultFound from fd_device.database.system import Hardware, Software @@ -18,7 +19,7 @@ def test_set_sensor_info(dbsession, mocker): """Test the set_sensor_info function works.""" with pytest.raises(NoResultFound): - dbsession.query(Hardware).one() + dbsession.scalars(select(Hardware)).one() mocked_sensors = [{"name": "sensor_1_name"}, {"name": "sensor_2_name"}] @@ -30,7 +31,7 @@ def test_set_sensor_info(dbsession, mocker): set_sensor_info(interior=1, exterior=2) - hd = dbsession.query(Hardware).one() + hd = dbsession.scalars(select(Hardware)).one() assert hd.interior_sensor == mocked_sensors[0]["name"] assert hd.exterior_sensor == mocked_sensors[1]["name"] @@ -41,7 +42,7 @@ def test_set_sensor_info_wrong_choices(dbsession, mocker): """Test the set_sensor_info function handles incorrect choices.""" with pytest.raises(NoResultFound): - dbsession.query(Hardware).one() + dbsession.scalars(select(Hardware)).one() mocked_sensors = [{"name": "sensor_1_name"}, {"name": "sensor_2_name"}] @@ -53,7 +54,7 @@ def test_set_sensor_info_wrong_choices(dbsession, mocker): set_sensor_info(interior=3, exterior=4) - hd = dbsession.query(Hardware).one() + hd = dbsession.scalars(select(Hardware)).one() assert hd.interior_sensor == "no_sensor_selected" assert hd.exterior_sensor == "no_sensor_selected" @@ -65,7 +66,7 @@ def test_set_hardware_info(dbsession): set_hardware_info("test_hardware_version", 0) - hd = dbsession.query(Hardware).one() + hd = dbsession.scalars(select(Hardware)).one() assert hd.hardware_version == "test_hardware_version" assert isinstance(hd.device_name, str) @@ -82,6 +83,6 @@ def test_set_software_info(dbsession): set_software_info("test_software_version") - sd = dbsession.query(Software).one() + sd = dbsession.scalars(select(Software)).one() assert sd.software_version == "test_software_version"