From 27bf063ab1a43e197151c44d68fccee2ab0dd3a4 Mon Sep 17 00:00:00 2001 From: Utshant Gurung Date: Fri, 12 Jun 2026 14:36:38 -0400 Subject: [PATCH 01/13] feat: migrate MUIOGO to uv (pyproject.toml + uv.lock) --- .gitignore | 2 + pyproject.toml | 48 ++++++- uv.lock | 380 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 429 insertions(+), 1 deletion(-) create mode 100644 uv.lock diff --git a/.gitignore b/.gitignore index 588e1c262..82e1a3267 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,8 @@ build/ venv/ venv .venv/ +*.egg-info/ + # Generic Python caches/artifacts __pycache__/ *.py[cod] diff --git a/pyproject.toml b/pyproject.toml index 3848308ae..b6f32fa8f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,51 @@ +[build-system] +requires = ["setuptools>=64", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "muiogo" +version = "0.1.0" +description = "MUIOGO, integration project to bring the purely Python-based OG-Core model into MUIO, the GUI for OSeMOSYS (CLEWS)" +readme = "README.md" +requires-python = ">=3.11, <3.13" +dependencies = [ + "blinker==1.8.2", + "boto3==1.34.122", + "botocore==1.34.122", + "click==8.1.7", + "colorama==0.4.6", + "et-xmlfile==1.1.0", + "Flask==3.0.3", + "Flask-Cors==4.0.1", + "itsdangerous==2.2.0", + "Jinja2==3.1.4", + "jmespath==1.0.1", + "MarkupSafe==2.1.5", + "numpy==1.26.4", + "openpyxl==3.1.4", + "packaging==24.0", + "pandas==2.2.2", + "python-dateutil==2.9.0.post0", + "python-dotenv==1.0.1", + "pytz==2024.1", + "s3transfer==0.10.1", + "tzdata==2024.1", + "waitress==3.0.0", + "Werkzeug==3.0.3", +] + +[project.urls] +Homepage = "https://github.com/EAPD-DRB/MUIOGO" +"Issue Tracker" = "https://github.com/EAPD-DRB/MUIOGO/issues" + +[tool.setuptools.packages.find] +include = ["API*"] + +#---------------------------------------------------------------------------- +# ruff +# --------------------------------------------------------------------------- [tool.ruff] -target-version = "py310" +target-version = "py311" line-length = 120 include = ["API/**/*.py", "tests/**/*.py"] diff --git a/uv.lock b/uv.lock new file mode 100644 index 000000000..151af8490 --- /dev/null +++ b/uv.lock @@ -0,0 +1,380 @@ +version = 1 +revision = 3 +requires-python = ">=3.11, <3.13" +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version < '3.12'", +] + +[[package]] +name = "blinker" +version = "1.8.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1e/57/a6a1721eff09598fb01f3c7cda070c1b6a0f12d63c83236edf79a440abcc/blinker-1.8.2.tar.gz", hash = "sha256:8f77b09d3bf7c795e969e9486f39c2c5e9c39d4ee07424be2bc594ece9642d83", size = 23161, upload-time = "2024-05-06T17:04:10.101Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bb/2a/10164ed1f31196a2f7f3799368a821765c62851ead0e630ab52b8e14b4d0/blinker-1.8.2-py3-none-any.whl", hash = "sha256:1779309f71bf239144b9399d06ae925637cf6634cf6bd131104184531bf67c01", size = 9456, upload-time = "2024-05-06T17:04:08.444Z" }, +] + +[[package]] +name = "boto3" +version = "1.34.122" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "botocore" }, + { name = "jmespath" }, + { name = "s3transfer" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bb/96/1c2dd3f8d256cd83713515ba48a5081bd24523ccb13893025b392c8f79b2/boto3-1.34.122.tar.gz", hash = "sha256:56840d8ce91654d182f1c113f0791fa2113c3aa43230c50b4481f235348a6037", size = 108306, upload-time = "2024-06-07T19:22:35.576Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8f/7b/fcee692d939f8b4dc5406d4daf15c4d98b8cfa6908a77f224092d445928f/boto3-1.34.122-py3-none-any.whl", hash = "sha256:b2d7400ff84fa547e53b3d9acfa3c95d65d45b5886ba1ede1f7df4768d1cc0b1", size = 139334, upload-time = "2024-06-07T19:21:57.691Z" }, +] + +[[package]] +name = "botocore" +version = "1.34.122" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jmespath" }, + { name = "python-dateutil" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/68/12/0d229d21c69e8276fc3365923a85a700dfb5b005fe5b7416b22a06bd1250/botocore-1.34.122.tar.gz", hash = "sha256:9374e16a36f1062c3e27816e8599b53eba99315dfac71cc84fc3aee3f5d3cbe3", size = 12486967, upload-time = "2024-06-07T19:22:43.623Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b5/60/0d5d689b295ba08b72db0643987fad14a1e29a341c508f2edd4ff29856b9/botocore-1.34.122-py3-none-any.whl", hash = "sha256:6d75df3af831b62f0c7baa109728d987e0a8d34bfadf0476eb32e2f29a079a36", size = 12272292, upload-time = "2024-06-07T19:22:10.444Z" }, +] + +[[package]] +name = "click" +version = "8.1.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/96/d3/f04c7bfcf5c1862a2a5b845c6b2b360488cf47af55dfa79c98f6a6bf98b5/click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de", size = 336121, upload-time = "2023-08-17T17:29:11.868Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", size = 97941, upload-time = "2023-08-17T17:29:10.08Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "et-xmlfile" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3d/5d/0413a31d184a20c763ad741cc7852a659bf15094c24840c5bdd1754765cd/et_xmlfile-1.1.0.tar.gz", hash = "sha256:8eb9e2bc2f8c97e37a2dc85a09ecdcdec9d8a396530a6d5a33b30b9a92da0c5c", size = 3218, upload-time = "2021-04-26T13:26:05.068Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/96/c2/3dd434b0108730014f1b96fd286040dc3bcb70066346f7e01ec2ac95865f/et_xmlfile-1.1.0-py3-none-any.whl", hash = "sha256:a2ba85d1d6a74ef63837eed693bcb89c3f752169b0e3e7ae5b16ca5e1b3deada", size = 4688, upload-time = "2021-04-26T13:26:03.429Z" }, +] + +[[package]] +name = "flask" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "blinker" }, + { name = "click" }, + { name = "itsdangerous" }, + { name = "jinja2" }, + { name = "werkzeug" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/41/e1/d104c83026f8d35dfd2c261df7d64738341067526406b40190bc063e829a/flask-3.0.3.tar.gz", hash = "sha256:ceb27b0af3823ea2737928a4d99d125a06175b8512c445cbd9a9ce200ef76842", size = 676315, upload-time = "2024-04-07T19:26:11.035Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/61/80/ffe1da13ad9300f87c93af113edd0638c75138c42a0994becfacac078c06/flask-3.0.3-py3-none-any.whl", hash = "sha256:34e815dfaa43340d1d15a5c3a02b8476004037eb4840b34910c6e21679d288f3", size = 101735, upload-time = "2024-04-07T19:26:08.569Z" }, +] + +[[package]] +name = "flask-cors" +version = "4.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "flask" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/40/6a/a8d56d60bcfa1ec3e4fdad81b45aafd508c3bd5c244a16526fa29139d7d4/flask_cors-4.0.1.tar.gz", hash = "sha256:eeb69b342142fdbf4766ad99357a7f3876a2ceb77689dc10ff912aac06c389e4", size = 30306, upload-time = "2024-05-04T19:49:43.538Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/52/2aa6285f104616f73ee1ad7905a16b2b35af0143034ad0cf7b64bcba715c/Flask_Cors-4.0.1-py2.py3-none-any.whl", hash = "sha256:f2a704e4458665580c074b714c4627dd5a306b333deb9074d0b1794dfa2fb677", size = 14290, upload-time = "2024-05-04T19:49:41.721Z" }, +] + +[[package]] +name = "itsdangerous" +version = "2.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9c/cb/8ac0172223afbccb63986cc25049b154ecfb5e85932587206f42317be31d/itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173", size = 54410, upload-time = "2024-04-16T21:28:15.614Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", size = 16234, upload-time = "2024-04-16T21:28:14.499Z" }, +] + +[[package]] +name = "jinja2" +version = "3.1.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ed/55/39036716d19cab0747a5020fc7e907f362fbf48c984b14e62127f7e68e5d/jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369", size = 240245, upload-time = "2024-05-05T23:42:02.455Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/31/80/3a54838c3fb461f6fec263ebf3a3a41771bd05190238de3486aae8540c36/jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d", size = 133271, upload-time = "2024-05-05T23:41:59.928Z" }, +] + +[[package]] +name = "jmespath" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/00/2a/e867e8531cf3e36b41201936b7fa7ba7b5702dbef42922193f05c8976cd6/jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe", size = 25843, upload-time = "2022-06-17T18:00:12.224Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/31/b4/b9b800c45527aadd64d5b442f9b932b00648617eb5d63d2c7a6587b7cafc/jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980", size = 20256, upload-time = "2022-06-17T18:00:10.251Z" }, +] + +[[package]] +name = "markupsafe" +version = "2.1.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/87/5b/aae44c6655f3801e81aa3eef09dbbf012431987ba564d7231722f68df02d/MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b", size = 19384, upload-time = "2024-02-02T16:31:22.863Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/e7/291e55127bb2ae67c64d66cef01432b5933859dfb7d6949daa721b89d0b3/MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f", size = 18219, upload-time = "2024-02-02T16:30:19.988Z" }, + { url = "https://files.pythonhosted.org/packages/6b/cb/aed7a284c00dfa7c0682d14df85ad4955a350a21d2e3b06d8240497359bf/MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2", size = 14098, upload-time = "2024-02-02T16:30:21.063Z" }, + { url = "https://files.pythonhosted.org/packages/1c/cf/35fe557e53709e93feb65575c93927942087e9b97213eabc3fe9d5b25a55/MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced", size = 29014, upload-time = "2024-02-02T16:30:22.926Z" }, + { url = "https://files.pythonhosted.org/packages/97/18/c30da5e7a0e7f4603abfc6780574131221d9148f323752c2755d48abad30/MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5", size = 28220, upload-time = "2024-02-02T16:30:24.76Z" }, + { url = "https://files.pythonhosted.org/packages/0c/40/2e73e7d532d030b1e41180807a80d564eda53babaf04d65e15c1cf897e40/MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c", size = 27756, upload-time = "2024-02-02T16:30:25.877Z" }, + { url = "https://files.pythonhosted.org/packages/18/46/5dca760547e8c59c5311b332f70605d24c99d1303dd9a6e1fc3ed0d73561/MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f", size = 33988, upload-time = "2024-02-02T16:30:26.935Z" }, + { url = "https://files.pythonhosted.org/packages/6d/c5/27febe918ac36397919cd4a67d5579cbbfa8da027fa1238af6285bb368ea/MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a", size = 32718, upload-time = "2024-02-02T16:30:28.111Z" }, + { url = "https://files.pythonhosted.org/packages/f8/81/56e567126a2c2bc2684d6391332e357589a96a76cb9f8e5052d85cb0ead8/MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f", size = 33317, upload-time = "2024-02-02T16:30:29.214Z" }, + { url = "https://files.pythonhosted.org/packages/00/0b/23f4b2470accb53285c613a3ab9ec19dc944eaf53592cb6d9e2af8aa24cc/MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906", size = 16670, upload-time = "2024-02-02T16:30:30.915Z" }, + { url = "https://files.pythonhosted.org/packages/b7/a2/c78a06a9ec6d04b3445a949615c4c7ed86a0b2eb68e44e7541b9d57067cc/MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617", size = 17224, upload-time = "2024-02-02T16:30:32.09Z" }, + { url = "https://files.pythonhosted.org/packages/53/bd/583bf3e4c8d6a321938c13f49d44024dbe5ed63e0a7ba127e454a66da974/MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1", size = 18215, upload-time = "2024-02-02T16:30:33.081Z" }, + { url = "https://files.pythonhosted.org/packages/48/d6/e7cd795fc710292c3af3a06d80868ce4b02bfbbf370b7cee11d282815a2a/MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4", size = 14069, upload-time = "2024-02-02T16:30:34.148Z" }, + { url = "https://files.pythonhosted.org/packages/51/b5/5d8ec796e2a08fc814a2c7d2584b55f889a55cf17dd1a90f2beb70744e5c/MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee", size = 29452, upload-time = "2024-02-02T16:30:35.149Z" }, + { url = "https://files.pythonhosted.org/packages/0a/0d/2454f072fae3b5a137c119abf15465d1771319dfe9e4acbb31722a0fff91/MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5", size = 28462, upload-time = "2024-02-02T16:30:36.166Z" }, + { url = "https://files.pythonhosted.org/packages/2d/75/fd6cb2e68780f72d47e6671840ca517bda5ef663d30ada7616b0462ad1e3/MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b", size = 27869, upload-time = "2024-02-02T16:30:37.834Z" }, + { url = "https://files.pythonhosted.org/packages/b0/81/147c477391c2750e8fc7705829f7351cf1cd3be64406edcf900dc633feb2/MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a", size = 33906, upload-time = "2024-02-02T16:30:39.366Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ff/9a52b71839d7a256b563e85d11050e307121000dcebc97df120176b3ad93/MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f", size = 32296, upload-time = "2024-02-02T16:30:40.413Z" }, + { url = "https://files.pythonhosted.org/packages/88/07/2dc76aa51b481eb96a4c3198894f38b480490e834479611a4053fbf08623/MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169", size = 33038, upload-time = "2024-02-02T16:30:42.243Z" }, + { url = "https://files.pythonhosted.org/packages/96/0c/620c1fb3661858c0e37eb3cbffd8c6f732a67cd97296f725789679801b31/MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad", size = 16572, upload-time = "2024-02-02T16:30:43.326Z" }, + { url = "https://files.pythonhosted.org/packages/3f/14/c3554d512d5f9100a95e737502f4a2323a1959f6d0d01e0d0997b35f7b10/MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb", size = 17127, upload-time = "2024-02-02T16:30:44.418Z" }, +] + +[[package]] +name = "muiogo" +version = "0.1.0" +source = { editable = "." } +dependencies = [ + { name = "blinker" }, + { name = "boto3" }, + { name = "botocore" }, + { name = "click" }, + { name = "colorama" }, + { name = "et-xmlfile" }, + { name = "flask" }, + { name = "flask-cors" }, + { name = "itsdangerous" }, + { name = "jinja2" }, + { name = "jmespath" }, + { name = "markupsafe" }, + { name = "numpy" }, + { name = "openpyxl" }, + { name = "packaging" }, + { name = "pandas" }, + { name = "python-dateutil" }, + { name = "python-dotenv" }, + { name = "pytz" }, + { name = "s3transfer" }, + { name = "tzdata" }, + { name = "waitress" }, + { name = "werkzeug" }, +] + +[package.metadata] +requires-dist = [ + { name = "blinker", specifier = "==1.8.2" }, + { name = "boto3", specifier = "==1.34.122" }, + { name = "botocore", specifier = "==1.34.122" }, + { name = "click", specifier = "==8.1.7" }, + { name = "colorama", specifier = "==0.4.6" }, + { name = "et-xmlfile", specifier = "==1.1.0" }, + { name = "flask", specifier = "==3.0.3" }, + { name = "flask-cors", specifier = "==4.0.1" }, + { name = "itsdangerous", specifier = "==2.2.0" }, + { name = "jinja2", specifier = "==3.1.4" }, + { name = "jmespath", specifier = "==1.0.1" }, + { name = "markupsafe", specifier = "==2.1.5" }, + { name = "numpy", specifier = "==1.26.4" }, + { name = "openpyxl", specifier = "==3.1.4" }, + { name = "packaging", specifier = "==24.0" }, + { name = "pandas", specifier = "==2.2.2" }, + { name = "python-dateutil", specifier = "==2.9.0.post0" }, + { name = "python-dotenv", specifier = "==1.0.1" }, + { name = "pytz", specifier = "==2024.1" }, + { name = "s3transfer", specifier = "==0.10.1" }, + { name = "tzdata", specifier = "==2024.1" }, + { name = "waitress", specifier = "==3.0.0" }, + { name = "werkzeug", specifier = "==3.0.3" }, +] + +[[package]] +name = "numpy" +version = "1.26.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/65/6e/09db70a523a96d25e115e71cc56a6f9031e7b8cd166c1ac8438307c14058/numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010", size = 15786129, upload-time = "2024-02-06T00:26:44.495Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/57/baae43d14fe163fa0e4c47f307b6b2511ab8d7d30177c491960504252053/numpy-1.26.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71", size = 20630554, upload-time = "2024-02-05T23:51:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/1a/2e/151484f49fd03944c4a3ad9c418ed193cfd02724e138ac8a9505d056c582/numpy-1.26.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef", size = 13997127, upload-time = "2024-02-05T23:52:15.314Z" }, + { url = "https://files.pythonhosted.org/packages/79/ae/7e5b85136806f9dadf4878bf73cf223fe5c2636818ba3ab1c585d0403164/numpy-1.26.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e", size = 14222994, upload-time = "2024-02-05T23:52:47.569Z" }, + { url = "https://files.pythonhosted.org/packages/3a/d0/edc009c27b406c4f9cbc79274d6e46d634d139075492ad055e3d68445925/numpy-1.26.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5", size = 18252005, upload-time = "2024-02-05T23:53:15.637Z" }, + { url = "https://files.pythonhosted.org/packages/09/bf/2b1aaf8f525f2923ff6cfcf134ae5e750e279ac65ebf386c75a0cf6da06a/numpy-1.26.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a", size = 13885297, upload-time = "2024-02-05T23:53:42.16Z" }, + { url = "https://files.pythonhosted.org/packages/df/a0/4e0f14d847cfc2a633a1c8621d00724f3206cfeddeb66d35698c4e2cf3d2/numpy-1.26.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a", size = 18093567, upload-time = "2024-02-05T23:54:11.696Z" }, + { url = "https://files.pythonhosted.org/packages/d2/b7/a734c733286e10a7f1a8ad1ae8c90f2d33bf604a96548e0a4a3a6739b468/numpy-1.26.4-cp311-cp311-win32.whl", hash = "sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20", size = 5968812, upload-time = "2024-02-05T23:54:26.453Z" }, + { url = "https://files.pythonhosted.org/packages/3f/6b/5610004206cf7f8e7ad91c5a85a8c71b2f2f8051a0c0c4d5916b76d6cbb2/numpy-1.26.4-cp311-cp311-win_amd64.whl", hash = "sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2", size = 15811913, upload-time = "2024-02-05T23:54:53.933Z" }, + { url = "https://files.pythonhosted.org/packages/95/12/8f2020a8e8b8383ac0177dc9570aad031a3beb12e38847f7129bacd96228/numpy-1.26.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218", size = 20335901, upload-time = "2024-02-05T23:55:32.801Z" }, + { url = "https://files.pythonhosted.org/packages/75/5b/ca6c8bd14007e5ca171c7c03102d17b4f4e0ceb53957e8c44343a9546dcc/numpy-1.26.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b", size = 13685868, upload-time = "2024-02-05T23:55:56.28Z" }, + { url = "https://files.pythonhosted.org/packages/79/f8/97f10e6755e2a7d027ca783f63044d5b1bc1ae7acb12afe6a9b4286eac17/numpy-1.26.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b", size = 13925109, upload-time = "2024-02-05T23:56:20.368Z" }, + { url = "https://files.pythonhosted.org/packages/0f/50/de23fde84e45f5c4fda2488c759b69990fd4512387a8632860f3ac9cd225/numpy-1.26.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed", size = 17950613, upload-time = "2024-02-05T23:56:56.054Z" }, + { url = "https://files.pythonhosted.org/packages/4c/0c/9c603826b6465e82591e05ca230dfc13376da512b25ccd0894709b054ed0/numpy-1.26.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a", size = 13572172, upload-time = "2024-02-05T23:57:21.56Z" }, + { url = "https://files.pythonhosted.org/packages/76/8c/2ba3902e1a0fc1c74962ea9bb33a534bb05984ad7ff9515bf8d07527cadd/numpy-1.26.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0", size = 17786643, upload-time = "2024-02-05T23:57:56.585Z" }, + { url = "https://files.pythonhosted.org/packages/28/4a/46d9e65106879492374999e76eb85f87b15328e06bd1550668f79f7b18c6/numpy-1.26.4-cp312-cp312-win32.whl", hash = "sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110", size = 5677803, upload-time = "2024-02-05T23:58:08.963Z" }, + { url = "https://files.pythonhosted.org/packages/16/2e/86f24451c2d530c88daf997cb8d6ac622c1d40d19f5a031ed68a4b73a374/numpy-1.26.4-cp312-cp312-win_amd64.whl", hash = "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818", size = 15517754, upload-time = "2024-02-05T23:58:36.364Z" }, +] + +[[package]] +name = "openpyxl" +version = "3.1.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "et-xmlfile" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/14/b8/acf74229f1b2d9e961f155e0be04e4629a1b53b3eeb4f2ac35c10032cf1b/openpyxl-3.1.4.tar.gz", hash = "sha256:8d2c8adf5d20d6ce8f9bca381df86b534835e974ed0156dacefa76f68c1d69fb", size = 186697, upload-time = "2024-06-12T17:38:15.793Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/30/d0/abcdb0669931be3a98881e6d7851605981693e93a7924061c67d0cd9f292/openpyxl-3.1.4-py2.py3-none-any.whl", hash = "sha256:ec17f6483f2b8f7c88c57e5e5d3b0de0e3fb9ac70edc084d28e864f5b33bbefd", size = 251355, upload-time = "2024-06-12T17:38:13.584Z" }, +] + +[[package]] +name = "packaging" +version = "24.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/b5/b43a27ac7472e1818c4bafd44430e69605baefe1f34440593e0332ec8b4d/packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9", size = 147882, upload-time = "2024-03-10T09:39:28.33Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/df/1fceb2f8900f8639e278b056416d49134fb8d84c5942ffaa01ad34782422/packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5", size = 53488, upload-time = "2024-03-10T09:39:25.947Z" }, +] + +[[package]] +name = "pandas" +version = "2.2.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "python-dateutil" }, + { name = "pytz" }, + { name = "tzdata" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/88/d9/ecf715f34c73ccb1d8ceb82fc01cd1028a65a5f6dbc57bfa6ea155119058/pandas-2.2.2.tar.gz", hash = "sha256:9e79019aba43cb4fda9e4d983f8e88ca0373adbb697ae9c6c43093218de28b54", size = 4398391, upload-time = "2024-04-10T19:45:48.342Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1b/70/61704497903d43043e288017cb2b82155c0d41e15f5c17807920877b45c2/pandas-2.2.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:696039430f7a562b74fa45f540aca068ea85fa34c244d0deee539cb6d70aa288", size = 12574808, upload-time = "2024-04-10T19:44:35.516Z" }, + { url = "https://files.pythonhosted.org/packages/16/c6/75231fd47afd6b3f89011e7077f1a3958441264aca7ae9ff596e3276a5d0/pandas-2.2.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8e90497254aacacbc4ea6ae5e7a8cd75629d6ad2b30025a4a8b09aa4faf55151", size = 11304876, upload-time = "2024-04-10T19:44:39.37Z" }, + { url = "https://files.pythonhosted.org/packages/97/2d/7b54f80b93379ff94afb3bd9b0cd1d17b48183a0d6f98045bc01ce1e06a7/pandas-2.2.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58b84b91b0b9f4bafac2a0ac55002280c094dfc6402402332c0913a59654ab2b", size = 15602548, upload-time = "2024-04-10T19:44:42.902Z" }, + { url = "https://files.pythonhosted.org/packages/fc/a5/4d82be566f069d7a9a702dcdf6f9106df0e0b042e738043c0cc7ddd7e3f6/pandas-2.2.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d2123dc9ad6a814bcdea0f099885276b31b24f7edf40f6cdbc0912672e22eee", size = 13031332, upload-time = "2024-04-10T19:44:46.98Z" }, + { url = "https://files.pythonhosted.org/packages/92/a2/b79c48f530673567805e607712b29814b47dcaf0d167e87145eb4b0118c6/pandas-2.2.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:2925720037f06e89af896c70bca73459d7e6a4be96f9de79e2d440bd499fe0db", size = 16286054, upload-time = "2024-04-10T19:44:50.51Z" }, + { url = "https://files.pythonhosted.org/packages/40/c7/47e94907f1d8fdb4868d61bd6c93d57b3784a964d52691b77ebfdb062842/pandas-2.2.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0cace394b6ea70c01ca1595f839cf193df35d1575986e484ad35c4aeae7266c1", size = 13879507, upload-time = "2024-04-10T19:44:54.412Z" }, + { url = "https://files.pythonhosted.org/packages/ab/63/966db1321a0ad55df1d1fe51505d2cdae191b84c907974873817b0a6e849/pandas-2.2.2-cp311-cp311-win_amd64.whl", hash = "sha256:873d13d177501a28b2756375d59816c365e42ed8417b41665f346289adc68d24", size = 11634249, upload-time = "2024-04-10T19:44:58.183Z" }, + { url = "https://files.pythonhosted.org/packages/dd/49/de869130028fb8d90e25da3b7d8fb13e40f5afa4c4af1781583eb1ff3839/pandas-2.2.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:9dfde2a0ddef507a631dc9dc4af6a9489d5e2e740e226ad426a05cabfbd7c8ef", size = 12500886, upload-time = "2024-04-10T19:45:01.808Z" }, + { url = "https://files.pythonhosted.org/packages/db/7c/9a60add21b96140e22465d9adf09832feade45235cd22f4cb1668a25e443/pandas-2.2.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e9b79011ff7a0f4b1d6da6a61aa1aa604fb312d6647de5bad20013682d1429ce", size = 11340320, upload-time = "2024-04-11T18:36:14.398Z" }, + { url = "https://files.pythonhosted.org/packages/b0/85/f95b5f322e1ae13b7ed7e97bd999160fa003424711ab4dc8344b8772c270/pandas-2.2.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1cb51fe389360f3b5a4d57dbd2848a5f033350336ca3b340d1c53a1fad33bcad", size = 15204346, upload-time = "2024-04-10T19:45:05.903Z" }, + { url = "https://files.pythonhosted.org/packages/40/10/79e52ef01dfeb1c1ca47a109a01a248754ebe990e159a844ece12914de83/pandas-2.2.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eee3a87076c0756de40b05c5e9a6069c035ba43e8dd71c379e68cab2c20f16ad", size = 12733396, upload-time = "2024-04-10T19:45:09.282Z" }, + { url = "https://files.pythonhosted.org/packages/35/9d/208febf8c4eb5c1d9ea3314d52d8bd415fd0ef0dd66bb24cc5bdbc8fa71a/pandas-2.2.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3e374f59e440d4ab45ca2fffde54b81ac3834cf5ae2cdfa69c90bc03bde04d76", size = 15858913, upload-time = "2024-04-10T19:45:12.514Z" }, + { url = "https://files.pythonhosted.org/packages/99/d1/2d9bd05def7a9e08a92ec929b5a4c8d5556ec76fae22b0fa486cbf33ea63/pandas-2.2.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:43498c0bdb43d55cb162cdc8c06fac328ccb5d2eabe3cadeb3529ae6f0517c32", size = 13417786, upload-time = "2024-04-10T19:45:16.275Z" }, + { url = "https://files.pythonhosted.org/packages/22/a5/a0b255295406ed54269814bc93723cfd1a0da63fb9aaf99e1364f07923e5/pandas-2.2.2-cp312-cp312-win_amd64.whl", hash = "sha256:d187d355ecec3629624fccb01d104da7d7f391db0311145817525281e2804d23", size = 11498828, upload-time = "2024-04-10T19:45:19.85Z" }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "python-dotenv" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bc/57/e84d88dfe0aec03b7a2d4327012c1627ab5f03652216c63d49846d7a6c58/python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca", size = 39115, upload-time = "2024-01-23T06:33:00.505Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/3e/b68c118422ec867fa7ab88444e1274aa40681c606d59ac27de5a5588f082/python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a", size = 19863, upload-time = "2024-01-23T06:32:58.246Z" }, +] + +[[package]] +name = "pytz" +version = "2024.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/90/26/9f1f00a5d021fff16dee3de13d43e5e978f3d58928e129c3a62cf7eb9738/pytz-2024.1.tar.gz", hash = "sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812", size = 316214, upload-time = "2024-02-02T01:18:41.693Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9c/3d/a121f284241f08268b21359bd425f7d4825cffc5ac5cd0e1b3d82ffd2b10/pytz-2024.1-py2.py3-none-any.whl", hash = "sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319", size = 505474, upload-time = "2024-02-02T01:18:37.283Z" }, +] + +[[package]] +name = "s3transfer" +version = "0.10.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "botocore" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/83/bc/fb0c1f76517e3380eb142af8a9d6b969c150cfca1324cea7d965d8c66571/s3transfer-0.10.1.tar.gz", hash = "sha256:5683916b4c724f799e600f41dd9e10a9ff19871bf87623cc8f491cb4f5fa0a19", size = 143308, upload-time = "2024-03-14T19:11:37.071Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/37/395cdb6ee92925fa211e55d8f07b9f93cf93f60d7d4ce5e66fd73f1ea986/s3transfer-0.10.1-py3-none-any.whl", hash = "sha256:ceb252b11bcf87080fb7850a224fb6e05c8a776bab8f2b64b7f25b969464839d", size = 82168, upload-time = "2024-03-14T19:11:04.538Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "tzdata" +version = "2024.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/74/5b/e025d02cb3b66b7b76093404392d4b44343c69101cc85f4d180dd5784717/tzdata-2024.1.tar.gz", hash = "sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd", size = 190559, upload-time = "2024-02-11T23:22:40.2Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/65/58/f9c9e6be752e9fcb8b6a0ee9fb87e6e7a1f6bcab2cdc73f02bb7ba91ada0/tzdata-2024.1-py2.py3-none-any.whl", hash = "sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252", size = 345370, upload-time = "2024-02-11T23:22:38.223Z" }, +] + +[[package]] +name = "urllib3" +version = "2.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" }, +] + +[[package]] +name = "waitress" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/70/34/cb77e5249c433eb177a11ab7425056b32d3b57855377fa1e38b397412859/waitress-3.0.0.tar.gz", hash = "sha256:005da479b04134cdd9dd602d1ee7c49d79de0537610d653674cc6cbde222b8a1", size = 179393, upload-time = "2024-02-04T23:33:55.126Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5b/a9/485c953a1ac4cb98c28e41fd2c7184072df36bbf99734a51d44d04176878/waitress-3.0.0-py3-none-any.whl", hash = "sha256:2a06f242f4ba0cc563444ca3d1998959447477363a2d7e9b8b4d75d35cfd1669", size = 56698, upload-time = "2024-02-04T23:33:53.516Z" }, +] + +[[package]] +name = "werkzeug" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/02/51/2e0fc149e7a810d300422ab543f87f2bcf64d985eb6f1228c4efd6e4f8d4/werkzeug-3.0.3.tar.gz", hash = "sha256:097e5bfda9f0aba8da6b8545146def481d06aa7d3266e7448e2cccf67dd8bd18", size = 803342, upload-time = "2024-05-05T23:10:31.999Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9d/6e/e792999e816d19d7fcbfa94c730936750036d65656a76a5a688b57a656c4/werkzeug-3.0.3-py3-none-any.whl", hash = "sha256:fc9645dc43e03e4d630d23143a04a7f947a9a3b5727cd535fdfe155a17cc48c8", size = 227274, upload-time = "2024-05-05T23:10:29.567Z" }, +] From dca533b130c671f299c6a8273e797e1aa16c7229 Mon Sep 17 00:00:00 2001 From: Utshant Gurung Date: Fri, 12 Jun 2026 16:03:24 -0400 Subject: [PATCH 02/13] feat: add --platform-only flag to setup_dev.py for uv bootstrap --- scripts/setup_dev.py | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/scripts/setup_dev.py b/scripts/setup_dev.py index 6c1559664..bd6c0a10c 100644 --- a/scripts/setup_dev.py +++ b/scripts/setup_dev.py @@ -18,7 +18,7 @@ Supports: macOS, Linux (apt/dnf/pacman), Windows -Python support: >=3.10 and <3.13 (recommended: 3.11) +Python support: >=3.11 and <3.13 (recommended: 3.11) Default venv location: ~/.venvs/muiogo (outside repo) """ @@ -48,7 +48,7 @@ REQUIREMENTS = PROJECT_ROOT / "requirements.txt" ENV_FILE = PROJECT_ROOT / ".env" SYSTEM = platform.system() # 'Darwin', 'Linux', 'Windows' -MIN_PYTHON = (3, 10) +MIN_PYTHON = (3, 11) MAX_PYTHON = (3, 13) # exclusive DATA_STORAGE_DIR = PROJECT_ROOT / "WebAPP" / "DataStorage" DEMO_DATA_ARCHIVE = PROJECT_ROOT / "assets" / "demo-data" / "CLEWs.Demo.zip" @@ -1264,10 +1264,15 @@ def main() -> int: action="store_true", help="Skip interactive confirmation prompts (required for non-interactive force reinstall).", ) + parser.add_argument( + "--platform-only", + action="store_true", + help="Skip Python venv/dependency setup and run only platform setup steps after uv sync.", + ) parser.set_defaults(with_demo_data=True) args = parser.parse_args() - conda_env = os.environ.get("CONDA_DEFAULT_ENV", "").strip() + conda_env = os.environ.get("CONDA_DEFAULT_ENV", "").strip() if conda_env: print( f"{RED}{BOLD}Conda environment is active: {conda_env}{RESET}\n" @@ -1315,7 +1320,7 @@ def main() -> int: print(f" Project : {PROJECT_ROOT}") print(f" Venv dir : {VENV_DIR}") - if PROJECT_ROOT.resolve() in VENV_DIR.resolve().parents: + if not args.platform_only and PROJECT_ROOT.resolve() in VENV_DIR.resolve().parents: _print_warn( "Using in-repo virtual environment", "This can cause high CPU in Codex Desktop. External venv is recommended.", @@ -1336,14 +1341,22 @@ def main() -> int: results: dict[str, tuple[bool, str]] = {} - step1_ok = setup_venv() - results["Python virtual environment"] = (step1_ok, str(VENV_DIR)) + if not args.platform_only: + step1_ok = setup_venv() + results["Python virtual environment"] = (step1_ok, str(VENV_DIR)) - if step1_ok: - results["Python dependencies"] = (install_python_deps(), "") + if step1_ok: + results["Python dependencies"] = (install_python_deps(), "") + else: + results["Python dependencies"] = (False, "skipped because venv setup failed") + _print_fail("Skipping Python deps (venv setup failed)") else: - results["Python dependencies"] = (False, "skipped because venv setup failed") - _print_fail("Skipping Python deps (venv setup failed)") + _print_header("Step 1 & 2: Skipped (uv-managed environment)") + _print_pass( + "Python environment managed by uv", + "skipping venv creation and pip install", + ) + results["Python environment"] = (True, "managed by uv sync") results["App secret key"] = (_ensure_secret_key_in_env(), str(ENV_FILE)) From b42311f6f18cbc575dfad0ab7e9e62e05295c821 Mon Sep 17 00:00:00 2001 From: Utshant Gurung Date: Fri, 12 Jun 2026 19:15:35 -0400 Subject: [PATCH 03/13] feat: add Windows bootstrap installer (scripts/install.ps1) --- .gitignore | 4 + scripts/install.ps1 | 476 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 480 insertions(+) create mode 100644 scripts/install.ps1 diff --git a/.gitignore b/.gitignore index 82e1a3267..abbb32e0b 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,10 @@ __pycache__/ .coverage.* htmlcov/ +# Installer log files +.install-*.log +scripts/.install-*.log + # OS artifacts .DS_Store Thumbs.db diff --git a/scripts/install.ps1 b/scripts/install.ps1 new file mode 100644 index 000000000..23fae938c --- /dev/null +++ b/scripts/install.ps1 @@ -0,0 +1,476 @@ +<# +.SYNOPSIS + MUIOGO installer for Windows (uv-based). + +.DESCRIPTION + Takes a user from a clean machine to a running MUIOGO environment: + 1. Check git is installed + 2. Install uv (if not present) + 3. Clone MUIOGO + 4. uv sync (installs Python + all dependencies) + 5. Platform setup: solvers, demo data, secret key, verification + 6. Start the app (optional, user prompted) + +.PARAMETER Dest + Parent directory where the repo is cloned. Default: current directory. + The clone always lands in \MUIOGO. + +.PARAMETER Branch + Clone a non-default branch. Useful for testing forks/PRs. + +.PARAMETER Yes + Auto-confirm every prompt (non-interactive). + +.PARAMETER NoDemoData + Skip demo data installation. + +.PARAMETER SkipUvInstall + Skip uv installation; assume uv is already on PATH. + +.PARAMETER NoLog + Don't write a log file. + +.PARAMETER RepoUrl + Override the repository URL to clone from. Default: https://github.com/EAPD-DRB/MUIOGO.git + Useful for testing a fork before the PR is merged. + +.EXAMPLE + irm https://raw.githubusercontent.com/EAPD-DRB/MUIOGO/main/scripts/install.ps1 | iex + One-liner install with all defaults. + +.EXAMPLE + .\scripts\install.ps1 -Dest C:\work -Yes + Unattended install into C:\work\MUIOGO. + +.EXAMPLE + .\scripts\install.ps1 -RepoUrl https://github.com/YOUR_FORK/MUIOGO.git -Branch feature/472-uv-installer + Test a fork branch before the PR is merged. +#> + +[CmdletBinding()] +param( + [string]$Dest = "", + [string]$Branch = "", + [string]$RepoUrl = "", + [switch]$Yes, + [switch]$NoDemoData, + [switch]$SkipUvInstall, + [switch]$NoLog +) + +$ErrorActionPreference = 'Stop' +$ScriptDir = (Get-Location).Path + +if ($RepoUrl -eq "") { $RepoUrl = "https://github.com/EAPD-DRB/MUIOGO.git" } +$RepoName = "MUIOGO" + +# -- Colors -------------------------------------------------------------------- +$UseAnsi = $Host.UI.SupportsVirtualTerminal -or ($env:WT_SESSION -ne $null) +if ($UseAnsi) { + $E = [char]27 + $BOLD = "$E[1m"; $DIM = "$E[2m" + $RED = "$E[91m"; $GREEN = "$E[92m"; $YELLOW = "$E[93m"; $RESET = "$E[0m" +} else { + $BOLD = ""; $DIM = ""; $RED = ""; $GREEN = ""; $YELLOW = ""; $RESET = "" +} + +# -- Logging ------------------------------------------------------------------- +$Ts = Get-Date -Format "yyyyMMdd-HHmmss" +$LogFile = Join-Path $ScriptDir ".install-$Ts.log" +$WriteLog = -not $NoLog +if ($WriteLog) { + try { Start-Transcript -Path $LogFile -Append | Out-Null } + catch { + Write-Host "WARN: could not start transcript at $LogFile : $($_.Exception.Message)" + $WriteLog = $false + } +} +function Stop-TranscriptIfActive { + if ($script:WriteLog) { try { Stop-Transcript | Out-Null } catch {} } +} + +# -- Helpers ------------------------------------------------------------------- +function Write-Hr { Write-Host "--------------------------------------------------------------" } +function Write-HrThick { Write-Host "==============================================================" } + +function Write-Pass($label, $detail = "") { + $suffix = if ($detail) { " $DIM($detail)$RESET" } else { "" } + Write-Host " $GREEN[PASS]$RESET $label$suffix" +} +function Write-Fail($label, $detail = "") { + $suffix = if ($detail) { " $DIM($detail)$RESET" } else { "" } + Write-Host " $RED[FAIL]$RESET $label$suffix" +} +function Write-Warn2($label, $detail = "") { + $suffix = if ($detail) { " $DIM($detail)$RESET" } else { "" } + Write-Host " $YELLOW[WARN]$RESET $label$suffix" +} +function Write-Skip($label, $detail = "") { + $suffix = if ($detail) { " $DIM($detail)$RESET" } else { "" } + Write-Host " $YELLOW[SKIP]$RESET $label$suffix" +} +function Write-Cmd($cmd) { Write-Host " $DIM`$ $cmd$RESET" } + +$TotalSteps = 5 +function Step-Banner($n, $title) { + Write-Host "" + Write-Hr + Write-Host " ${BOLD}Step $n of ${TotalSteps}: $title${RESET}" + Write-Hr +} + +function Test-Interactive { + try { return -not [Console]::IsInputRedirected } + catch { return [Environment]::UserInteractive } +} + +function Prompt-YN($prompt, $default = 'y') { + $opts = if ($default -eq 'y') { '[Y/n/q]' } else { '[y/N/q]' } + if ($Yes) { + Write-Host "$prompt $opts ${DIM}(auto: yes)${RESET}" + return $true + } + if (-not (Test-Interactive)) { + Write-Host "${RED}ERROR:${RESET} non-interactive session and -Yes not given." + return $false + } + while ($true) { + $ans = Read-Host "$prompt $opts" + if ([string]::IsNullOrWhiteSpace($ans)) { $ans = $default } + switch -Regex ($ans) { + '^(y|yes)$' { return $true } + '^(n|no)$' { return $false } + '^(q|quit)$' { + Write-Host "${YELLOW}Aborted by user.${RESET}" + Stop-TranscriptIfActive + exit 130 + } + default { Write-Host " Please answer y, n, or q." } + } + } +} + +# -- Result tracking ----------------------------------------------------------- +$StepResults = New-Object System.Collections.Generic.List[object] +function Record-Step($name, $state, $detail = "") { + $script:StepResults.Add([pscustomobject]@{ Name=$name; State=$state; Detail=$detail }) +} + +# -- Pre-flight ---------------------------------------------------------------- +if ($env:CONDA_DEFAULT_ENV) { + Write-Host "${RED}ERROR:${RESET} conda env '$($env:CONDA_DEFAULT_ENV)' is active. Run 'conda deactivate' first." + Stop-TranscriptIfActive; exit 1 +} +if ($env:VIRTUAL_ENV) { + Write-Host "${RED}ERROR:${RESET} virtualenv '$($env:VIRTUAL_ENV)' is active. Run 'deactivate' first." + Stop-TranscriptIfActive; exit 1 +} + +# -- Detect existing uv -------------------------------------------------------- +$UvBin = $null +function Detect-Uv { + $cmd = Get-Command uv -ErrorAction SilentlyContinue + if ($cmd) { $script:UvBin = $cmd.Source; return $true } + $candidates = @( + "$env:USERPROFILE\.local\bin\uv.exe", + "$env:USERPROFILE\.cargo\bin\uv.exe", + "$env:LOCALAPPDATA\Programs\uv\uv.exe" + ) + foreach ($c in $candidates) { + if (Test-Path $c) { $script:UvBin = $c; return $true } + } + return $false +} +[void](Detect-Uv) +$UvPresent = [bool]$UvBin + +# -- Pick destination ---------------------------------------------------------- +if (-not $Dest) { + if (-not (Test-Interactive)) { + $Dest = "." + } else { + Write-Host "" + Write-Host " Where would you like to install MUIOGO?" + Write-Host " Enter the PARENT directory; MUIOGO will be cloned as a subfolder inside." + Write-Host " Default: current directory ($(Get-Location))" + $entered = Read-Host " Parent directory [.]" + if ([string]::IsNullOrWhiteSpace($entered)) { $Dest = "." } else { $Dest = $entered } + } +} + +$Dest = [Environment]::ExpandEnvironmentVariables($Dest) +if ($Dest.StartsWith("~")) { + $Dest = Join-Path $env:USERPROFILE $Dest.Substring(1).TrimStart('\','/') +} + +if (-not (Test-Path $Dest)) { + Write-Host "${RED}ERROR:${RESET} parent directory does not exist: $Dest" + Write-Host "Create it first or pick a different -Dest." + Stop-TranscriptIfActive; exit 1 +} +$ParentAbs = (Resolve-Path $Dest).Path +$DestAbs = Join-Path $ParentAbs $RepoName + +# -- Banner -------------------------------------------------------------------- +Write-Host "" +Write-HrThick +Write-Host " ${BOLD}MUIOGO Installer (uv-based)${RESET}" +Write-HrThick +Write-Host (" Platform : Windows {0}" -f $env:PROCESSOR_ARCHITECTURE) +Write-Host (" Destination : {0}" -f $DestAbs) +if ($Branch) { Write-Host (" Branch : {0}" -f $Branch) } +if ($UvPresent) { + Write-Host (" uv : {0} {1}detected{2}" -f $UvBin, $GREEN, $RESET) +} else { + Write-Host (" uv : {0}will install{1} (~5MB, official installer)" -f $YELLOW, $RESET) +} +if ($WriteLog) { Write-Host (" Log file : {0}" -f $LogFile) } +Write-Host "" +Write-Host " ${BOLD}Plan ($TotalSteps steps):${RESET}" +Write-Host " 1. Check git" +if ($UvPresent -or $SkipUvInstall) { + Write-Host " 2. Install uv ${DIM}skipped (already present)${RESET}" +} else { + Write-Host " 2. Install uv ${DIM}~5MB, seconds${RESET}" +} +Write-Host (" 3. Clone MUIOGO ${DIM}depends on network${RESET}") +Write-Host " 4. uv sync (Python + deps) ${DIM}~30s${RESET}" +Write-Host " 5. Platform setup (solvers, demo data, secret key, verification)" +Write-Host "" + +if (-not (Prompt-YN "Proceed with installation?" 'y')) { + Write-Host "${YELLOW}Aborted by user.${RESET}" + Stop-TranscriptIfActive; exit 0 +} + +$StartTime = Get-Date + +# -- Step 1: Check git --------------------------------------------------------- +Step-Banner 1 "Check git" +$GitCmd = Get-Command git -ErrorAction SilentlyContinue +if (-not $GitCmd) { + Write-Fail "git is not installed or not on PATH" + Write-Host "" + Write-Host " Install git for Windows, then re-run this installer:" + Write-Host " winget install -e --id Git.Git" + Write-Host " https://git-scm.com/download/win" + Record-Step "git" "FAIL" "not installed" + Stop-TranscriptIfActive; exit 1 +} +$GitBin = $GitCmd.Source +$gitVer = "" +try { $gitVer = (& $GitBin --version 2>$null).Trim() } catch {} +Write-Pass "git" $gitVer +Record-Step "git" "PASS" $gitVer + +# -- Step 2: Install uv -------------------------------------------------------- +Step-Banner 2 "Install uv" +if ($UvPresent) { + $uvVer = "" + try { $uvVer = ((& $UvBin --version 2>$null) | Select-Object -First 1).Trim() } catch {} + Write-Pass "uv already present" "$UvBin ($uvVer)" + Record-Step "uv" "SKIP" "already present" +} elseif ($SkipUvInstall) { + Write-Fail "-SkipUvInstall given but uv not found" + Record-Step "uv" "FAIL" "no uv and -SkipUvInstall" + Stop-TranscriptIfActive; exit 1 +} else { + Write-Host " Installing uv via the official installer (no admin required)..." + Write-Cmd "irm https://astral.sh/uv/install.ps1 | iex" + $ProgressPreference = 'SilentlyContinue' + $uvInstallCmd = "[Net.ServicePointManager]::SecurityProtocol = 'Tls12'; iwr https://astral.sh/uv/install.ps1 -UseBasicParsing | iex" + & powershell.exe -NoProfile -ExecutionPolicy Bypass -Command $uvInstallCmd + if ($LASTEXITCODE -ne 0) { + Write-Fail "uv install failed (exit $LASTEXITCODE)" + Record-Step "uv" "FAIL" "install failed" + Stop-TranscriptIfActive; exit 1 + } + [void](Detect-Uv) + if (-not $UvBin) { + $candidate = "$env:USERPROFILE\.local\bin\uv.exe" + if (Test-Path $candidate) { $UvBin = $candidate } + } + if (-not $UvBin -or -not (Test-Path $UvBin)) { + Write-Fail "uv installed but binary not found; open a new terminal and re-run" + Record-Step "uv" "FAIL" "binary not found post-install" + Stop-TranscriptIfActive; exit 1 + } + $uvVer = "" + try { $uvVer = ((& $UvBin --version 2>$null) | Select-Object -First 1).Trim() } catch {} + Write-Pass "uv installed" "$UvBin ($uvVer)" + Record-Step "uv" "PASS" $UvBin +} + +# -- Step 3: Clone MUIOGO ------------------------------------------------------ +Step-Banner 3 "Clone MUIOGO" + +function Normalize-RemoteUrl($u) { return ($u -replace '\.git/?$', '').ToLower() } + +$DestHasRepo = $false +if (Test-Path $DestAbs) { + if (Test-Path (Join-Path $DestAbs ".git")) { + $existingUrl = "" + try { $existingUrl = (& $GitBin -C $DestAbs config --get remote.origin.url 2>$null).Trim() } catch {} + if ((Normalize-RemoteUrl $existingUrl) -eq (Normalize-RemoteUrl $RepoUrl)) { + $DestHasRepo = $true + } else { + Write-Fail "Destination exists but points to a different remote" $existingUrl + Write-Host " Remove $DestAbs or pick a different -Dest." + Record-Step "Clone" "FAIL" "wrong remote" + Stop-TranscriptIfActive; exit 1 + } + } else { + $isEmpty = (Get-ChildItem -Force $DestAbs -ErrorAction SilentlyContinue | Measure-Object).Count -eq 0 + if (-not $isEmpty) { + Write-Fail "Destination exists and is not empty" $DestAbs + Write-Host " Remove $DestAbs or pick a different -Dest." + Record-Step "Clone" "FAIL" "destination not empty" + Stop-TranscriptIfActive; exit 1 + } + } +} + +if ($DestHasRepo) { + $branch = "?" + try { $branch = (& $GitBin -C $DestAbs rev-parse --abbrev-ref HEAD 2>$null).Trim() } catch {} + Write-Host (" Existing MUIOGO clone found at {0} (branch: {1})." -f $DestAbs, $branch) + if (Prompt-YN "Update with git pull --ff-only?" 'y') { + Write-Cmd "git -C $DestAbs pull --ff-only" + & $GitBin -C $DestAbs pull --ff-only + if ($LASTEXITCODE -eq 0) { + Write-Pass "Repo updated" $DestAbs + Record-Step "Clone" "PASS" "updated ($branch)" + } else { + Write-Warn2 "git pull failed; continuing with existing state" + Record-Step "Clone" "WARN" "pull failed; existing state used" + } + } else { + Write-Skip "Update" "using existing clone as-is" + Record-Step "Clone" "SKIP" "existing clone used as-is" + } +} else { + $cloneArgs = @("clone") + if ($Branch) { $cloneArgs += @("--branch", $Branch) } + $cloneArgs += @($RepoUrl, $DestAbs) + + Write-Host (" Cloning MUIOGO into {0}..." -f $DestAbs) + Write-Cmd ("git {0}" -f ($cloneArgs -join ' ')) + & $GitBin @cloneArgs + + if ($LASTEXITCODE -ne 0) { + Write-Fail "git clone failed" + Write-Host " Re-run the installer to try again." + Record-Step "Clone" "FAIL" "git clone failed" + Stop-TranscriptIfActive; exit 1 + } + + $branch = "?" + try { $branch = (& $GitBin -C $DestAbs rev-parse --abbrev-ref HEAD 2>$null).Trim() } catch {} + Write-Pass "Cloned MUIOGO" "$DestAbs (branch: $branch)" + Record-Step "Clone" "PASS" $branch +} + +# -- Step 4: uv sync ----------------------------------------------------------- +Step-Banner 4 "Install dependencies (uv sync)" +Write-Host (" Installing Python + all dependencies into {0}\.venv" -f $DestAbs) +Write-Cmd "uv sync" +Push-Location $DestAbs +try { + & $UvBin sync + if ($LASTEXITCODE -ne 0) { + Write-Fail "uv sync failed" + Record-Step "uv sync" "FAIL" "exit $LASTEXITCODE" + Stop-TranscriptIfActive; exit 1 + } + Write-Pass "Dependencies installed" ".venv" + Record-Step "uv sync" "PASS" ".venv" +} finally { + Pop-Location +} + +# -- Step 5: Platform setup ---------------------------------------------------- +Step-Banner 5 "Platform setup (solvers, demo data, secret key, verification)" +$VenvPython = Join-Path $DestAbs ".venv\Scripts\python.exe" +if (-not (Test-Path $VenvPython)) { + Write-Fail "venv Python not found" $VenvPython + Record-Step "Platform setup" "FAIL" "no venv python" + Stop-TranscriptIfActive; exit 1 +} + +$SetupArgs = @("scripts\setup_dev.py", "--platform-only", "--venv-dir", ".venv") +if ($NoDemoData) { $SetupArgs += "--no-demo-data" } + +Write-Cmd ("python {0}" -f ($SetupArgs -join ' ')) +Push-Location $DestAbs +try { + & $VenvPython @SetupArgs + if ($LASTEXITCODE -ne 0) { + Write-Fail "Platform setup reported failures -- review output above" + Record-Step "Platform setup" "FAIL" "setup_dev.py exited $LASTEXITCODE" + Stop-TranscriptIfActive; exit 1 + } + Record-Step "Platform setup" "PASS" "solvers, demo data, secret key, verification" +} finally { + Pop-Location +} + +# -- Summary ------------------------------------------------------------------- +$Elapsed = (Get-Date) - $StartTime +$ElapsedMin = [int]$Elapsed.TotalMinutes +$ElapsedSec = [int]($Elapsed.TotalSeconds - ($ElapsedMin * 60)) + +Write-Host "" +Write-HrThick +Write-Host " ${BOLD}Installation Summary${RESET}" +Write-HrThick + +$AllOk = $true +foreach ($r in $StepResults) { + switch ($r.State) { + "PASS" { Write-Pass $r.Name $r.Detail } + "SKIP" { Write-Skip $r.Name $r.Detail } + "WARN" { Write-Warn2 $r.Name $r.Detail } + "FAIL" { Write-Fail $r.Name $r.Detail; $AllOk = $false } + default { Write-Warn2 $r.Name "unknown state" } + } +} +Write-Host "" +Write-Host (" Elapsed : {0}m {1}s" -f $ElapsedMin, $ElapsedSec) +Write-Host (" Location : {0}" -f $DestAbs) +if ($WriteLog) { Write-Host (" Log : {0}" -f $LogFile) } +Write-Host "" + +if ($AllOk) { + Write-Host " ${GREEN}${BOLD}MUIOGO is installed and ready.${RESET}" + Write-Host "" + Write-Host " ${BOLD}To start MUIOGO:${RESET}" + Write-Host (" cd {0}" -f $DestAbs) + Write-Host (" uv run python API\app.py") + Write-Host "" + Write-Host " Or activate the venv first:" + Write-Host " .\.venv\Scripts\Activate.ps1" + Write-Host " python API\app.py" + Write-Host "" + + if (Prompt-YN "Start MUIOGO now?" 'y') { + $Port = if ($env:PORT) { $env:PORT } else { "5002" } + $Url = "http://127.0.0.1:${Port}/" + Write-Host (" Opening browser at {0}" -f $Url) + Write-Host " Press Ctrl+C to stop the app." + Start-Process $Url + Push-Location $DestAbs + try { + & $VenvPython "API\app.py" + } finally { + Pop-Location + } + } + + Stop-TranscriptIfActive + exit 0 +} else { + Write-Host " ${RED}${BOLD}One or more steps failed -- review the output above.${RESET}" + if ($WriteLog) { Write-Host " Full log: $LogFile" } + Stop-TranscriptIfActive + exit 1 +} From 71ef8c6cc04189e8062d7fcb2d81e997d841178d Mon Sep 17 00:00:00 2001 From: Utshant Gurung Date: Fri, 12 Jun 2026 19:40:58 -0400 Subject: [PATCH 04/13] feat: add macOS/Linux bootstrap installer (scripts/install.sh) --- scripts/install.sh | 441 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 441 insertions(+) create mode 100644 scripts/install.sh diff --git a/scripts/install.sh b/scripts/install.sh new file mode 100644 index 000000000..fc4b93cc4 --- /dev/null +++ b/scripts/install.sh @@ -0,0 +1,441 @@ +#!/usr/bin/env bash +# MUIOGO installer for macOS / Linux (uv-based). +# +# Takes a user from a clean machine to a running MUIOGO environment: +# 1. Check git is installed +# 2. Install uv (if not present) +# 3. Clone MUIOGO +# 4. uv sync (installs Python + all dependencies) +# 5. Platform setup: solvers, demo data, secret key, verification +# 6. Start the app (optional, user prompted) +# +# Usage: +# bash install.sh [options] +# +# Options: +# --dest DIR Parent directory for the clone (default: current dir) +# --branch BRANCH Clone a non-default branch (useful for testing forks/PRs) +# --repo-url URL Override the repository URL (default: EAPD-DRB/MUIOGO) +# --yes Auto-confirm every prompt (non-interactive) +# --no-demo-data Skip demo data installation +# --skip-uv-install Skip uv installation; assume uv is already on PATH +# --no-log Don't write a log file +# +# One-liner: +# curl -fsSL https://raw.githubusercontent.com/EAPD-DRB/MUIOGO/main/scripts/install.sh | bash +# +# Test a fork: +# bash install.sh --repo-url https://github.com/YOUR_FORK/MUIOGO.git --branch feature/472-uv-installer + +set -euo pipefail + +# -- Defaults ------------------------------------------------------------------ +REPO_URL="https://github.com/EAPD-DRB/MUIOGO.git" +REPO_NAME="MUIOGO" +DEST="" +BRANCH="" +YES=0 +NO_DEMO_DATA=0 +SKIP_UV_INSTALL=0 +NO_LOG=0 + +SCRIPT_DIR="$(pwd)" +TOTAL_STEPS=5 + +# -- Argument parsing ---------------------------------------------------------- +while [[ $# -gt 0 ]]; do + case "$1" in + --dest) DEST="$2"; shift 2 ;; + --branch) BRANCH="$2"; shift 2 ;; + --repo-url) REPO_URL="$2"; shift 2 ;; + --yes|-y) YES=1; shift ;; + --no-demo-data) NO_DEMO_DATA=1; shift ;; + --skip-uv-install) SKIP_UV_INSTALL=1; shift ;; + --no-log) NO_LOG=1; shift ;; + -h|--help) + sed -n '/^# Usage/,/^[^#]/p' "$0" | sed 's/^# \?//' + exit 0 + ;; + *) + echo "Unknown option: $1" >&2 + exit 1 + ;; + esac +done + +# -- Colors (detect before any stdout redirect) -------------------------------- +if [[ -t 1 ]]; then + BOLD="\033[1m"; DIM="\033[2m" + RED="\033[91m"; GREEN="\033[92m"; YELLOW="\033[93m"; RESET="\033[0m" +else + BOLD=""; DIM=""; RED=""; GREEN=""; YELLOW=""; RESET="" +fi + +# -- Interactivity (detect before any stdout redirect) ------------------------- +STDIN_IS_TTY=0 +[[ -t 0 ]] && STDIN_IS_TTY=1 + +# -- Logging ------------------------------------------------------------------- +TS="$(date +%Y%m%d-%H%M%S)" +LOG_FILE="${SCRIPT_DIR}/.install-${TS}.log" +if [[ $NO_LOG -eq 0 ]]; then + exec > >(tee -a "$LOG_FILE") 2>&1 +fi + +# -- Helpers ------------------------------------------------------------------- +hr() { echo "--------------------------------------------------------------"; } +hr_thick(){ echo "=============================================================="; } + +pass() { local d="${2:-}"; [[ -n "$d" ]] && echo -e " ${GREEN}[PASS]${RESET} $1 ${DIM}($d)${RESET}" || echo -e " ${GREEN}[PASS]${RESET} $1"; } +fail() { local d="${2:-}"; [[ -n "$d" ]] && echo -e " ${RED}[FAIL]${RESET} $1 ${DIM}($d)${RESET}" || echo -e " ${RED}[FAIL]${RESET} $1"; } +warn() { local d="${2:-}"; [[ -n "$d" ]] && echo -e " ${YELLOW}[WARN]${RESET} $1 ${DIM}($d)${RESET}" || echo -e " ${YELLOW}[WARN]${RESET} $1"; } +skip() { local d="${2:-}"; [[ -n "$d" ]] && echo -e " ${YELLOW}[SKIP]${RESET} $1 ${DIM}($d)${RESET}" || echo -e " ${YELLOW}[SKIP]${RESET} $1"; } +cmd() { echo -e " ${DIM}\$ $1${RESET}"; } + +step_banner() { + echo "" + hr + echo -e " ${BOLD}Step $1 of ${TOTAL_STEPS}: $2${RESET}" + hr +} + +# -- Result tracking ----------------------------------------------------------- +STEP_NAMES=() +STEP_STATES=() +STEP_DETAILS=() + +record_step() { + STEP_NAMES+=("$1") + STEP_STATES+=("$2") + STEP_DETAILS+=("${3:-}") +} + +# -- Interactive helpers ------------------------------------------------------- +is_interactive() { [[ $STDIN_IS_TTY -eq 1 ]]; } + +prompt_yn() { + local prompt="$1" default="${2:-y}" + local opts="[Y/n/q]" + [[ "$default" != "y" ]] && opts="[y/N/q]" + + if [[ $YES -eq 1 ]]; then + echo -e "$prompt $opts ${DIM}(auto: yes)${RESET}" + return 0 + fi + if ! is_interactive; then + echo -e "${RED}ERROR:${RESET} non-interactive session and --yes not given." >&2 + return 1 + fi + while true; do + read -rp "$prompt $opts " ans + [[ -z "$ans" ]] && ans="$default" + case "${ans,,}" in + y|yes) return 0 ;; + n|no) return 1 ;; + q|quit) echo -e "${YELLOW}Aborted by user.${RESET}"; exit 130 ;; + *) echo " Please answer y, n, or q." ;; + esac + done +} + +# -- Pre-flight ---------------------------------------------------------------- +if [[ -n "${CONDA_DEFAULT_ENV:-}" ]]; then + echo -e "${RED}ERROR:${RESET} conda env '${CONDA_DEFAULT_ENV}' is active. Run 'conda deactivate' first." + exit 1 +fi +if [[ -n "${VIRTUAL_ENV:-}" ]]; then + echo -e "${RED}ERROR:${RESET} virtualenv '${VIRTUAL_ENV}' is active. Run 'deactivate' first." + exit 1 +fi + +# -- Detect existing uv -------------------------------------------------------- +UV_BIN="" +detect_uv() { + if command -v uv &>/dev/null; then + UV_BIN="$(command -v uv)" + return 0 + fi + local candidates=( + "$HOME/.local/bin/uv" + "$HOME/.cargo/bin/uv" + ) + for c in "${candidates[@]}"; do + if [[ -x "$c" ]]; then + UV_BIN="$c" + return 0 + fi + done + return 1 +} +detect_uv || true +UV_PRESENT=$( [[ -n "$UV_BIN" ]] && echo 1 || echo 0 ) + +# -- Pick destination ---------------------------------------------------------- +if [[ -z "$DEST" ]]; then + if ! is_interactive; then + DEST="." + else + echo "" + echo " Where would you like to install MUIOGO?" + echo " Enter the PARENT directory; MUIOGO will be cloned as a subfolder inside." + echo " Default: current directory ($(pwd))" + read -rp " Parent directory [.]: " entered + DEST="${entered:-.}" + fi +fi + +# Expand ~ manually (works in bash without eval) +if [[ "$DEST" == "~"* ]]; then + DEST="${HOME}${DEST:1}" +fi + +if [[ ! -d "$DEST" ]]; then + echo -e "${RED}ERROR:${RESET} parent directory does not exist: $DEST" + echo "Create it first or pick a different --dest." + exit 1 +fi + +PARENT_ABS="$(cd "$DEST" && pwd)" +DEST_ABS="${PARENT_ABS}/${REPO_NAME}" + +# -- Detect OS ----------------------------------------------------------------- +OS_LABEL="Linux" +if [[ "$(uname)" == "Darwin" ]]; then + OS_LABEL="macOS $(sw_vers -productVersion 2>/dev/null || true)" +fi + +# -- Banner -------------------------------------------------------------------- +echo "" +hr_thick +echo -e " ${BOLD}MUIOGO Installer (uv-based)${RESET}" +hr_thick +echo " Platform : ${OS_LABEL}" +echo " Destination : ${DEST_ABS}" +[[ -n "$BRANCH" ]] && echo " Branch : ${BRANCH}" +if [[ $UV_PRESENT -eq 1 ]]; then + echo -e " uv : ${UV_BIN} ${GREEN}detected${RESET}" +else + echo -e " uv : ${YELLOW}will install${RESET} (~5MB, official installer)" +fi +[[ $NO_LOG -eq 0 ]] && echo " Log file : ${LOG_FILE}" +echo "" +echo -e " ${BOLD}Plan (${TOTAL_STEPS} steps):${RESET}" +echo " 1. Check git" +if [[ $UV_PRESENT -eq 1 || $SKIP_UV_INSTALL -eq 1 ]]; then + echo -e " 2. Install uv ${DIM}skipped (already present)${RESET}" +else + echo -e " 2. Install uv ${DIM}~5MB, seconds${RESET}" +fi +echo -e " 3. Clone MUIOGO ${DIM}depends on network${RESET}" +echo -e " 4. uv sync (Python + deps) ${DIM}~30s${RESET}" +echo " 5. Platform setup (solvers, demo data, secret key, verification)" +echo "" + +if ! prompt_yn "Proceed with installation?" y; then + echo -e "${YELLOW}Aborted by user.${RESET}" + exit 0 +fi + +START_TIME="$(date +%s)" + +# -- Step 1: Check git --------------------------------------------------------- +step_banner 1 "Check git" +if ! command -v git &>/dev/null; then + fail "git is not installed or not on PATH" + echo "" + echo " Install git, then re-run this installer:" + if [[ "$(uname)" == "Darwin" ]]; then + echo " xcode-select --install" + echo " # or: brew install git" + else + echo " sudo apt install git # Debian/Ubuntu" + echo " sudo dnf install git # Fedora/RHEL" + fi + record_step "git" "FAIL" "not installed" + exit 1 +fi +GIT_BIN="$(command -v git)" +GIT_VER="$(git --version 2>/dev/null || true)" +pass "git" "$GIT_VER" +record_step "git" "PASS" "$GIT_VER" + +# -- Step 2: Install uv -------------------------------------------------------- +step_banner 2 "Install uv" +if [[ $UV_PRESENT -eq 1 ]]; then + UV_VER="$("$UV_BIN" --version 2>/dev/null | head -1 || true)" + pass "uv already present" "${UV_BIN} (${UV_VER})" + record_step "uv" "SKIP" "already present" +elif [[ $SKIP_UV_INSTALL -eq 1 ]]; then + fail "--skip-uv-install given but uv not found" + record_step "uv" "FAIL" "no uv and --skip-uv-install" + exit 1 +else + echo " Installing uv via the official installer (no sudo required)..." + cmd "curl -LsSf https://astral.sh/uv/install.sh | sh" + curl -LsSf https://astral.sh/uv/install.sh | sh + # Reload PATH so uv is findable without a new shell + export PATH="${HOME}/.local/bin:${HOME}/.cargo/bin:${PATH}" + if ! detect_uv; then + fail "uv installed but binary not found; open a new terminal and re-run" + record_step "uv" "FAIL" "binary not found post-install" + exit 1 + fi + UV_VER="$("$UV_BIN" --version 2>/dev/null | head -1 || true)" + pass "uv installed" "${UV_BIN} (${UV_VER})" + record_step "uv" "PASS" "$UV_BIN" +fi + +# -- Step 3: Clone MUIOGO ------------------------------------------------------ +step_banner 3 "Clone MUIOGO" + +normalize_url() { echo "$1" | sed 's/\.git\/*$//' | tr '[:upper:]' '[:lower:]'; } + +DEST_HAS_REPO=0 +if [[ -d "$DEST_ABS" ]]; then + if [[ -d "${DEST_ABS}/.git" ]]; then + EXISTING_URL="$("$GIT_BIN" -C "$DEST_ABS" config --get remote.origin.url 2>/dev/null || true)" + if [[ "$(normalize_url "$EXISTING_URL")" == "$(normalize_url "$REPO_URL")" ]]; then + DEST_HAS_REPO=1 + else + fail "Destination exists but points to a different remote" "$EXISTING_URL" + echo " Remove ${DEST_ABS} or pick a different --dest." + record_step "Clone" "FAIL" "wrong remote" + exit 1 + fi + else + if [[ -n "$(ls -A "$DEST_ABS" 2>/dev/null)" ]]; then + fail "Destination exists and is not empty" "$DEST_ABS" + echo " Remove ${DEST_ABS} or pick a different --dest." + record_step "Clone" "FAIL" "destination not empty" + exit 1 + fi + fi +fi + +if [[ $DEST_HAS_REPO -eq 1 ]]; then + CURRENT_BRANCH="$("$GIT_BIN" -C "$DEST_ABS" rev-parse --abbrev-ref HEAD 2>/dev/null || echo '?')" + echo " Existing MUIOGO clone found at ${DEST_ABS} (branch: ${CURRENT_BRANCH})." + if prompt_yn "Update with git pull --ff-only?" y; then + cmd "git -C ${DEST_ABS} pull --ff-only" + if "$GIT_BIN" -C "$DEST_ABS" pull --ff-only; then + pass "Repo updated" "$DEST_ABS" + record_step "Clone" "PASS" "updated (${CURRENT_BRANCH})" + else + warn "git pull failed; continuing with existing state" + record_step "Clone" "WARN" "pull failed; existing state used" + fi + else + skip "Update" "using existing clone as-is" + record_step "Clone" "SKIP" "existing clone used as-is" + fi +else + CLONE_ARGS=("clone") + [[ -n "$BRANCH" ]] && CLONE_ARGS+=("--branch" "$BRANCH") + CLONE_ARGS+=("$REPO_URL" "$DEST_ABS") + + echo " Cloning MUIOGO into ${DEST_ABS}..." + cmd "git ${CLONE_ARGS[*]}" + "$GIT_BIN" "${CLONE_ARGS[@]}" + + CURRENT_BRANCH="$("$GIT_BIN" -C "$DEST_ABS" rev-parse --abbrev-ref HEAD 2>/dev/null || echo '?')" + pass "Cloned MUIOGO" "${DEST_ABS} (branch: ${CURRENT_BRANCH})" + record_step "Clone" "PASS" "$CURRENT_BRANCH" +fi + +# -- Step 4: uv sync ----------------------------------------------------------- +step_banner 4 "Install dependencies (uv sync)" +echo " Installing Python + all dependencies into ${DEST_ABS}/.venv" +cmd "uv sync" +pushd "$DEST_ABS" >/dev/null +"$UV_BIN" sync +popd >/dev/null +pass "Dependencies installed" ".venv" +record_step "uv sync" "PASS" ".venv" + +# -- Step 5: Platform setup ---------------------------------------------------- +step_banner 5 "Platform setup (solvers, demo data, secret key, verification)" +if [[ -x "${DEST_ABS}/.venv/bin/python" ]]; then + VENV_PYTHON="${DEST_ABS}/.venv/bin/python" +elif [[ -x "${DEST_ABS}/.venv/Scripts/python.exe" ]]; then + VENV_PYTHON="${DEST_ABS}/.venv/Scripts/python.exe" +else + fail "venv Python not found" "${DEST_ABS}/.venv" + record_step "Platform setup" "FAIL" "no venv python" + exit 1 +fi + +SETUP_ARGS=("scripts/setup_dev.py" "--platform-only" "--venv-dir" ".venv") +[[ $NO_DEMO_DATA -eq 1 ]] && SETUP_ARGS+=("--no-demo-data") + +cmd "python ${SETUP_ARGS[*]}" +pushd "$DEST_ABS" >/dev/null +"$VENV_PYTHON" "${SETUP_ARGS[@]}" +SETUP_EXIT=$? +popd >/dev/null + +if [[ $SETUP_EXIT -ne 0 ]]; then + fail "Platform setup reported failures -- review output above" + record_step "Platform setup" "FAIL" "setup_dev.py exited ${SETUP_EXIT}" + exit 1 +fi +record_step "Platform setup" "PASS" "solvers, demo data, secret key, verification" + +# -- Summary ------------------------------------------------------------------- +END_TIME="$(date +%s)" +ELAPSED=$(( END_TIME - START_TIME )) +ELAPSED_MIN=$(( ELAPSED / 60 )) +ELAPSED_SEC=$(( ELAPSED % 60 )) + +echo "" +hr_thick +echo -e " ${BOLD}Installation Summary${RESET}" +hr_thick + +ALL_OK=1 +for i in "${!STEP_NAMES[@]}"; do + case "${STEP_STATES[$i]}" in + PASS) pass "${STEP_NAMES[$i]}" "${STEP_DETAILS[$i]}" ;; + SKIP) skip "${STEP_NAMES[$i]}" "${STEP_DETAILS[$i]}" ;; + WARN) warn "${STEP_NAMES[$i]}" "${STEP_DETAILS[$i]}" ;; + FAIL) fail "${STEP_NAMES[$i]}" "${STEP_DETAILS[$i]}"; ALL_OK=0 ;; + *) warn "${STEP_NAMES[$i]}" "unknown state" ;; + esac +done + +echo "" +echo " Elapsed : ${ELAPSED_MIN}m ${ELAPSED_SEC}s" +echo " Location : ${DEST_ABS}" +[[ $NO_LOG -eq 0 ]] && echo " Log : ${LOG_FILE}" +echo "" + +if [[ $ALL_OK -eq 1 ]]; then + echo -e " ${GREEN}${BOLD}MUIOGO is installed and ready.${RESET}" + echo "" + echo -e " ${BOLD}To start MUIOGO:${RESET}" + echo " cd ${DEST_ABS}" + echo " uv run python API/app.py" + echo "" + + if prompt_yn "Start MUIOGO now?" y; then + PORT="${PORT:-5002}" + URL="http://127.0.0.1:${PORT}/" + echo " Opening browser at ${URL}" + echo " Press Ctrl+C to stop the app." + if command -v cmd.exe &>/dev/null; then + cmd.exe /c start "" "$URL" + elif command -v open &>/dev/null; then + open "$URL" + elif command -v xdg-open &>/dev/null; then + xdg-open "$URL" &>/dev/null & + fi + pushd "$DEST_ABS" >/dev/null + "$VENV_PYTHON" API/app.py + popd >/dev/null + fi + + exit 0 +else + echo -e " ${RED}${BOLD}One or more steps failed -- review the output above.${RESET}" + [[ $NO_LOG -eq 0 ]] && echo " Full log: ${LOG_FILE}" + exit 1 +fi From f01c204d71a3255f13899fa52f56b68185c5f77b Mon Sep 17 00:00:00 2001 From: Utshant Gurung Date: Fri, 12 Jun 2026 20:32:28 -0400 Subject: [PATCH 05/13] point default repo URL to fork for PR testing (to revert before merge) --- scripts/install.ps1 | 2 +- scripts/install.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/install.ps1 b/scripts/install.ps1 index 23fae938c..6791783ac 100644 --- a/scripts/install.ps1 +++ b/scripts/install.ps1 @@ -61,7 +61,7 @@ param( $ErrorActionPreference = 'Stop' $ScriptDir = (Get-Location).Path -if ($RepoUrl -eq "") { $RepoUrl = "https://github.com/EAPD-DRB/MUIOGO.git" } +if ($RepoUrl -eq "") { $RepoUrl = "https://github.com/utsinboots/MUIOGO.git" } $RepoName = "MUIOGO" # -- Colors -------------------------------------------------------------------- diff --git a/scripts/install.sh b/scripts/install.sh index fc4b93cc4..995836347 100644 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -30,7 +30,7 @@ set -euo pipefail # -- Defaults ------------------------------------------------------------------ -REPO_URL="https://github.com/EAPD-DRB/MUIOGO.git" +REPO_URL="https://github.com/utsinboots/MUIOGO.git" REPO_NAME="MUIOGO" DEST="" BRANCH="" From c142e2eca8406e9b0321a2d096d403495f2a8e08 Mon Sep 17 00:00:00 2001 From: Utshant Gurung Date: Mon, 15 Jun 2026 12:43:11 -0400 Subject: [PATCH 06/13] chore: set default branch to feature/472-uv-installer for PR testing --- scripts/install.ps1 | 1 + scripts/install.sh | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/install.ps1 b/scripts/install.ps1 index 6791783ac..cdf73a81f 100644 --- a/scripts/install.ps1 +++ b/scripts/install.ps1 @@ -62,6 +62,7 @@ $ErrorActionPreference = 'Stop' $ScriptDir = (Get-Location).Path if ($RepoUrl -eq "") { $RepoUrl = "https://github.com/utsinboots/MUIOGO.git" } +if ($Branch -eq "") { $Branch = "feature/472-uv-installer" } $RepoName = "MUIOGO" # -- Colors -------------------------------------------------------------------- diff --git a/scripts/install.sh b/scripts/install.sh index 995836347..d51404cf0 100644 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -33,7 +33,7 @@ set -euo pipefail REPO_URL="https://github.com/utsinboots/MUIOGO.git" REPO_NAME="MUIOGO" DEST="" -BRANCH="" +BRANCH="feature/472-uv-installer" YES=0 NO_DEMO_DATA=0 SKIP_UV_INSTALL=0 From 5b47d10a489d08edd6b15762a7e47b2afef3d1fa Mon Sep 17 00:00:00 2001 From: Utshant Gurung Date: Mon, 15 Jun 2026 12:53:07 -0400 Subject: [PATCH 07/13] style: fix indentation of --platform-only in setup_dev.py --- scripts/setup_dev.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/setup_dev.py b/scripts/setup_dev.py index 76494917e..8b5b399fc 100644 --- a/scripts/setup_dev.py +++ b/scripts/setup_dev.py @@ -1275,9 +1275,9 @@ def main() -> int: help="Skip interactive confirmation prompts (required for non-interactive force reinstall).", ) parser.add_argument( - "--platform-only", - action="store_true", - help="Skip Python venv/dependency setup and run only platform setup steps after uv sync.", + "--platform-only", + action="store_true", + help="Skip Python venv/dependency setup and run only platform setup steps after uv sync.", ) parser.set_defaults(with_demo_data=True) args = parser.parse_args() From b210ea66eefaf24487ba6937862d731ec93c4921 Mon Sep 17 00:00:00 2001 From: Utshant Gurung Date: Mon, 15 Jun 2026 17:21:34 -0400 Subject: [PATCH 08/13] Fix Bash 3.2 compatibility in install.sh --- scripts/install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/install.sh b/scripts/install.sh index d51404cf0..213d0e867 100644 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -129,7 +129,7 @@ prompt_yn() { while true; do read -rp "$prompt $opts " ans [[ -z "$ans" ]] && ans="$default" - case "${ans,,}" in + case "$(echo "$ans" | tr '[:upper:]' '[:lower:]')" in y|yes) return 0 ;; n|no) return 1 ;; q|quit) echo -e "${YELLOW}Aborted by user.${RESET}"; exit 130 ;; From c34b06d0fe08da4b36d78683c51bbada254dad8c Mon Sep 17 00:00:00 2001 From: SeaCelo Date: Tue, 16 Jun 2026 16:03:22 -0400 Subject: [PATCH 09/13] Installer review fixes for #482: keep 3.10 floor, uv package=false, fix macOS git detection + graceful platform-setup failure, add QUICK_INSTALL.md --- pyproject.toml | 15 ++++--- requirements.txt | 77 ++++++++++++++++++++++++++++++++---- scripts/QUICK_INSTALL.md | 85 ++++++++++++++++++++++++++++++++++++++++ scripts/install.ps1 | 6 ++- scripts/install.sh | 32 ++++++++++++--- scripts/setup_dev.py | 8 ++-- uv.lock | 32 +++++++++++++-- 7 files changed, 225 insertions(+), 30 deletions(-) create mode 100644 scripts/QUICK_INSTALL.md diff --git a/pyproject.toml b/pyproject.toml index b6f32fa8f..440f77e65 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,13 +1,9 @@ -[build-system] -requires = ["setuptools>=64", "wheel"] -build-backend = "setuptools.build_meta" - [project] name = "muiogo" version = "0.1.0" description = "MUIOGO, integration project to bring the purely Python-based OG-Core model into MUIO, the GUI for OSeMOSYS (CLEWS)" readme = "README.md" -requires-python = ">=3.11, <3.13" +requires-python = ">=3.10, <3.13" dependencies = [ "blinker==1.8.2", "boto3==1.34.122", @@ -38,14 +34,17 @@ dependencies = [ Homepage = "https://github.com/EAPD-DRB/MUIOGO" "Issue Tracker" = "https://github.com/EAPD-DRB/MUIOGO/issues" -[tool.setuptools.packages.find] -include = ["API*"] +[tool.uv] +# MUIOGO is an application we run (python API/app.py), not a library that other +# code imports — so uv installs only the dependencies; it does not build or +# install MUIOGO itself as a package. +package = false #---------------------------------------------------------------------------- # ruff # --------------------------------------------------------------------------- [tool.ruff] -target-version = "py311" +target-version = "py310" line-length = 120 include = ["API/**/*.py", "tests/**/*.py"] diff --git a/requirements.txt b/requirements.txt index cb2b862cd..72a207481 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,25 +1,88 @@ -# Runtime dependencies - cross-platform (Windows / macOS / Linux) +# This file was autogenerated by uv via the following command: +# uv export --no-hashes --no-emit-project -o requirements.txt blinker==1.8.2 + # via + # flask + # muiogo boto3==1.34.122 + # via muiogo botocore==1.34.122 + # via + # boto3 + # muiogo + # s3transfer click==8.1.7 + # via + # flask + # muiogo colorama==0.4.6 + # via + # click + # muiogo et-xmlfile==1.1.0 -Flask==3.0.3 -Flask-Cors==4.0.1 + # via + # muiogo + # openpyxl +flask==3.0.3 + # via + # flask-cors + # muiogo +flask-cors==4.0.1 + # via muiogo itsdangerous==2.2.0 -Jinja2==3.1.4 + # via + # flask + # muiogo +jinja2==3.1.4 + # via + # flask + # muiogo jmespath==1.0.1 -MarkupSafe==2.1.5 + # via + # boto3 + # botocore + # muiogo +markupsafe==2.1.5 + # via + # jinja2 + # muiogo + # werkzeug numpy==1.26.4 + # via + # muiogo + # pandas openpyxl==3.1.4 + # via muiogo packaging==24.0 + # via muiogo pandas==2.2.2 + # via muiogo python-dateutil==2.9.0.post0 + # via + # botocore + # muiogo + # pandas python-dotenv==1.0.1 + # via muiogo pytz==2024.1 + # via + # muiogo + # pandas s3transfer==0.10.1 + # via + # boto3 + # muiogo +six==1.17.0 + # via python-dateutil tzdata==2024.1 + # via + # muiogo + # pandas +urllib3==2.7.0 + # via botocore waitress==3.0.0 -Werkzeug==3.0.3 - + # via muiogo +werkzeug==3.0.3 + # via + # flask + # muiogo diff --git a/scripts/QUICK_INSTALL.md b/scripts/QUICK_INSTALL.md new file mode 100644 index 000000000..4fc466f0d --- /dev/null +++ b/scripts/QUICK_INSTALL.md @@ -0,0 +1,85 @@ +# Quick install + +One command takes you from a clean machine to a running MUIOGO. The only pre-requisite is **git**; the installer adds `uv`, which brings the right Python and every dependency with it. + +You can run it two ways — paste a one-line command, or download the script and run it. Both do the same thing. + +## Option 1 — One line + +### macOS / Linux + +```bash +/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/EAPD-DRB/MUIOGO/main/scripts/install.sh)" +``` + +### Windows (PowerShell) + +```powershell +$f = "$env:TEMP\muiogo-install.ps1"; irm https://raw.githubusercontent.com/EAPD-DRB/MUIOGO/main/scripts/install.ps1 -OutFile $f; powershell -ExecutionPolicy Bypass -File $f +``` + +(On Windows the installer is saved to a temp file and run from there, so it executes as a normal script.) + +## Option 2 — Download, then run + +Handy if you'd rather read the script first, or keep it to re-run later. + +### macOS / Linux + +```bash +curl -fsSL https://raw.githubusercontent.com/EAPD-DRB/MUIOGO/main/scripts/install.sh -o install.sh +bash install.sh +``` + +### Windows (PowerShell) + +```powershell +Invoke-WebRequest -UseBasicParsing -Uri https://raw.githubusercontent.com/EAPD-DRB/MUIOGO/main/scripts/install.ps1 -OutFile install.ps1 +powershell -ExecutionPolicy Bypass -File .\install.ps1 +``` + +## What it does + +1. Checks that git is installed (and tells you how to install it if it isn't). +2. Installs `uv` if it isn't already present (~5 MB, no admin needed). +3. Clones MUIOGO. +4. Runs `uv sync` — installs the right Python and all dependencies into a local `.venv`. +5. Platform setup — installs the GLPK and CBC solvers, downloads the demo data, creates the app secret key, and verifies everything. + +It's safe to re-run: if MUIOGO is already there, it offers to update with `git pull` instead of cloning again. + +## Options / skipping prompts + +These work with either method above: + +- `--dest DIR` / `-Dest` — parent folder to install into (MUIOGO is cloned as a subfolder). Default: current directory. +- `--branch BRANCH` / `-Branch` — install a non-default branch (for testing a fork or PR). +- `--repo-url URL` / `-RepoUrl` — clone from a different repository URL. +- `--yes` / `-Yes` — accept all prompts (non-interactive). +- `--no-demo-data` / `-NoDemoData` — skip the demo-data download. +- `--skip-uv-install` / `-SkipUvInstall` — assume `uv` is already installed. +- `--no-log` / `-NoLog` — don't write an install log file. + +```bash +# macOS / Linux — install into ~/Projects/MUIOGO, no prompts +bash install.sh --dest ~/Projects --yes +``` +```powershell +# Windows — install into C:\Users\\Projects\MUIOGO, no prompts +powershell -ExecutionPolicy Bypass -File .\install.ps1 -Dest C:\Users\$env:USERNAME\Projects -Yes +``` + +## After install + +```bash +# macOS / Linux +cd /MUIOGO +uv run python API/app.py +``` +```powershell +# Windows +cd \MUIOGO +uv run python API\app.py +``` + +Then open in your browser. (The installer also offers to start it for you at the end.) diff --git a/scripts/install.ps1 b/scripts/install.ps1 index cdf73a81f..83227a1ec 100644 --- a/scripts/install.ps1 +++ b/scripts/install.ps1 @@ -406,11 +406,13 @@ Push-Location $DestAbs try { & $VenvPython @SetupArgs if ($LASTEXITCODE -ne 0) { + # Don't exit here: record the failure and fall through to the summary + # (matches install.sh) so the full result table prints before exiting. Write-Fail "Platform setup reported failures -- review output above" Record-Step "Platform setup" "FAIL" "setup_dev.py exited $LASTEXITCODE" - Stop-TranscriptIfActive; exit 1 + } else { + Record-Step "Platform setup" "PASS" "solvers, demo data, secret key, verification" } - Record-Step "Platform setup" "PASS" "solvers, demo data, secret key, verification" } finally { Pop-Location } diff --git a/scripts/install.sh b/scripts/install.sh index 213d0e867..82bee2df8 100644 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -22,7 +22,7 @@ # --no-log Don't write a log file # # One-liner: -# curl -fsSL https://raw.githubusercontent.com/EAPD-DRB/MUIOGO/main/scripts/install.sh | bash +# /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/EAPD-DRB/MUIOGO/main/scripts/install.sh)" # # Test a fork: # bash install.sh --repo-url https://github.com/YOUR_FORK/MUIOGO.git --branch feature/472-uv-installer @@ -148,6 +148,11 @@ if [[ -n "${VIRTUAL_ENV:-}" ]]; then exit 1 fi +if ! command -v curl &>/dev/null; then + echo -e "${RED}ERROR:${RESET} 'curl' is required to install uv. Please install curl and re-run." + exit 1 +fi + # -- Detect existing uv -------------------------------------------------------- UV_BIN="" detect_uv() { @@ -240,7 +245,19 @@ START_TIME="$(date +%s)" # -- Step 1: Check git --------------------------------------------------------- step_banner 1 "Check git" -if ! command -v git &>/dev/null; then +# macOS ships a /usr/bin/git stub that exists even when the Command Line Tools +# (which provide the real git) are not installed. `command -v git` would then +# report git as present, and the clone would later trigger a GUI prompt or fail. +# On macOS we confirm with `xcode-select -p`, which is the reliable check. +git_ok=0 +if [[ "$(uname)" == "Darwin" ]]; then + if xcode-select -p &>/dev/null && command -v git &>/dev/null; then + git_ok=1 + fi +elif command -v git &>/dev/null; then + git_ok=1 +fi +if [[ $git_ok -eq 0 ]]; then fail "git is not installed or not on PATH" echo "" echo " Install git, then re-run this installer:" @@ -369,16 +386,19 @@ SETUP_ARGS=("scripts/setup_dev.py" "--platform-only" "--venv-dir" ".venv") cmd "python ${SETUP_ARGS[*]}" pushd "$DEST_ABS" >/dev/null -"$VENV_PYTHON" "${SETUP_ARGS[@]}" -SETUP_EXIT=$? +# Capture the exit code instead of letting `set -e` abort here, so a failure is +# reported in the summary below (matching install.ps1) rather than aborting +# before the summary prints. +SETUP_EXIT=0 +"$VENV_PYTHON" "${SETUP_ARGS[@]}" || SETUP_EXIT=$? popd >/dev/null if [[ $SETUP_EXIT -ne 0 ]]; then fail "Platform setup reported failures -- review output above" record_step "Platform setup" "FAIL" "setup_dev.py exited ${SETUP_EXIT}" - exit 1 +else + record_step "Platform setup" "PASS" "solvers, demo data, secret key, verification" fi -record_step "Platform setup" "PASS" "solvers, demo data, secret key, verification" # -- Summary ------------------------------------------------------------------- END_TIME="$(date +%s)" diff --git a/scripts/setup_dev.py b/scripts/setup_dev.py index 8b5b399fc..5ecd53f5c 100644 --- a/scripts/setup_dev.py +++ b/scripts/setup_dev.py @@ -18,7 +18,7 @@ Supports: macOS, Linux (apt/dnf/pacman), Windows -Python support: >=3.11 and <3.13 (recommended: 3.11) +Python support: >=3.10 and <3.13 (recommended: 3.11) Default venv location: ~/.venvs/muiogo (outside repo) """ @@ -48,7 +48,7 @@ REQUIREMENTS = PROJECT_ROOT / "requirements.txt" ENV_FILE = PROJECT_ROOT / ".env" SYSTEM = platform.system() # 'Darwin', 'Linux', 'Windows' -MIN_PYTHON = (3, 11) +MIN_PYTHON = (3, 10) MAX_PYTHON = (3, 13) # exclusive DATA_STORAGE_DIR = PROJECT_ROOT / "WebAPP" / "DataStorage" DEMO_DATA_ARCHIVE = PROJECT_ROOT / "assets" / "demo-data" / "CLEWs.Demo.zip" @@ -1282,7 +1282,7 @@ def main() -> int: parser.set_defaults(with_demo_data=True) args = parser.parse_args() - conda_env = os.environ.get("CONDA_DEFAULT_ENV", "").strip() + conda_env = os.environ.get("CONDA_DEFAULT_ENV", "").strip() if conda_env: print( f"{RED}{BOLD}Conda environment is active: {conda_env}{RESET}\n" @@ -1366,7 +1366,7 @@ def main() -> int: "Python environment managed by uv", "skipping venv creation and pip install", ) - results["Python environment"] = (True, "managed by uv sync") + results["Python environment"] = (True, "managed by uv sync") results["App secret key"] = (_ensure_secret_key_in_env(), str(ENV_FILE)) diff --git a/uv.lock b/uv.lock index 151af8490..996df0190 100644 --- a/uv.lock +++ b/uv.lock @@ -1,9 +1,10 @@ version = 1 revision = 3 -requires-python = ">=3.11, <3.13" +requires-python = ">=3.10, <3.13" resolution-markers = [ "python_full_version >= '3.12'", - "python_full_version < '3.12'", + "python_full_version == '3.11.*'", + "python_full_version < '3.11'", ] [[package]] @@ -137,6 +138,16 @@ version = "2.1.5" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/87/5b/aae44c6655f3801e81aa3eef09dbbf012431987ba564d7231722f68df02d/MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b", size = 19384, upload-time = "2024-02-02T16:31:22.863Z" } wheels = [ + { url = "https://files.pythonhosted.org/packages/e4/54/ad5eb37bf9d51800010a74e4665425831a9db4e7c4e0fde4352e391e808e/MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc", size = 18206, upload-time = "2024-02-02T16:30:04.105Z" }, + { url = "https://files.pythonhosted.org/packages/6a/4a/a4d49415e600bacae038c67f9fecc1d5433b9d3c71a4de6f33537b89654c/MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5", size = 14079, upload-time = "2024-02-02T16:30:06.5Z" }, + { url = "https://files.pythonhosted.org/packages/0a/7b/85681ae3c33c385b10ac0f8dd025c30af83c78cec1c37a6aa3b55e67f5ec/MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46", size = 26620, upload-time = "2024-02-02T16:30:08.31Z" }, + { url = "https://files.pythonhosted.org/packages/7c/52/2b1b570f6b8b803cef5ac28fdf78c0da318916c7d2fe9402a84d591b394c/MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f", size = 25818, upload-time = "2024-02-02T16:30:09.577Z" }, + { url = "https://files.pythonhosted.org/packages/29/fe/a36ba8c7ca55621620b2d7c585313efd10729e63ef81e4e61f52330da781/MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900", size = 25493, upload-time = "2024-02-02T16:30:11.488Z" }, + { url = "https://files.pythonhosted.org/packages/60/ae/9c60231cdfda003434e8bd27282b1f4e197ad5a710c14bee8bea8a9ca4f0/MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff", size = 30630, upload-time = "2024-02-02T16:30:13.144Z" }, + { url = "https://files.pythonhosted.org/packages/65/dc/1510be4d179869f5dafe071aecb3f1f41b45d37c02329dfba01ff59e5ac5/MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad", size = 29745, upload-time = "2024-02-02T16:30:14.222Z" }, + { url = "https://files.pythonhosted.org/packages/30/39/8d845dd7d0b0613d86e0ef89549bfb5f61ed781f59af45fc96496e897f3a/MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd", size = 30021, upload-time = "2024-02-02T16:30:16.032Z" }, + { url = "https://files.pythonhosted.org/packages/c7/5c/356a6f62e4f3c5fbf2602b4771376af22a3b16efa74eb8716fb4e328e01e/MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4", size = 16659, upload-time = "2024-02-02T16:30:17.079Z" }, + { url = "https://files.pythonhosted.org/packages/69/48/acbf292615c65f0604a0c6fc402ce6d8c991276e16c80c46a8f758fbd30c/MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5", size = 17213, upload-time = "2024-02-02T16:30:18.251Z" }, { url = "https://files.pythonhosted.org/packages/11/e7/291e55127bb2ae67c64d66cef01432b5933859dfb7d6949daa721b89d0b3/MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f", size = 18219, upload-time = "2024-02-02T16:30:19.988Z" }, { url = "https://files.pythonhosted.org/packages/6b/cb/aed7a284c00dfa7c0682d14df85ad4955a350a21d2e3b06d8240497359bf/MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2", size = 14098, upload-time = "2024-02-02T16:30:21.063Z" }, { url = "https://files.pythonhosted.org/packages/1c/cf/35fe557e53709e93feb65575c93927942087e9b97213eabc3fe9d5b25a55/MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced", size = 29014, upload-time = "2024-02-02T16:30:22.926Z" }, @@ -162,7 +173,7 @@ wheels = [ [[package]] name = "muiogo" version = "0.1.0" -source = { editable = "." } +source = { virtual = "." } dependencies = [ { name = "blinker" }, { name = "boto3" }, @@ -222,6 +233,14 @@ version = "1.26.4" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/65/6e/09db70a523a96d25e115e71cc56a6f9031e7b8cd166c1ac8438307c14058/numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010", size = 15786129, upload-time = "2024-02-06T00:26:44.495Z" } wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/94/ace0fdea5241a27d13543ee117cbc65868e82213fb31a8eb7fe9ff23f313/numpy-1.26.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0", size = 20631468, upload-time = "2024-02-05T23:48:01.194Z" }, + { url = "https://files.pythonhosted.org/packages/20/f7/b24208eba89f9d1b58c1668bc6c8c4fd472b20c45573cb767f59d49fb0f6/numpy-1.26.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a", size = 13966411, upload-time = "2024-02-05T23:48:29.038Z" }, + { url = "https://files.pythonhosted.org/packages/fc/a5/4beee6488160798683eed5bdb7eead455892c3b4e1f78d79d8d3f3b084ac/numpy-1.26.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4", size = 14219016, upload-time = "2024-02-05T23:48:54.098Z" }, + { url = "https://files.pythonhosted.org/packages/4b/d7/ecf66c1cd12dc28b4040b15ab4d17b773b87fa9d29ca16125de01adb36cd/numpy-1.26.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f", size = 18240889, upload-time = "2024-02-05T23:49:25.361Z" }, + { url = "https://files.pythonhosted.org/packages/24/03/6f229fe3187546435c4f6f89f6d26c129d4f5bed40552899fcf1f0bf9e50/numpy-1.26.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a", size = 13876746, upload-time = "2024-02-05T23:49:51.983Z" }, + { url = "https://files.pythonhosted.org/packages/39/fe/39ada9b094f01f5a35486577c848fe274e374bbf8d8f472e1423a0bbd26d/numpy-1.26.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2", size = 18078620, upload-time = "2024-02-05T23:50:22.515Z" }, + { url = "https://files.pythonhosted.org/packages/d5/ef/6ad11d51197aad206a9ad2286dc1aac6a378059e06e8cf22cd08ed4f20dc/numpy-1.26.4-cp310-cp310-win32.whl", hash = "sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07", size = 5972659, upload-time = "2024-02-05T23:50:35.834Z" }, + { url = "https://files.pythonhosted.org/packages/19/77/538f202862b9183f54108557bfda67e17603fc560c384559e769321c9d92/numpy-1.26.4-cp310-cp310-win_amd64.whl", hash = "sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5", size = 15808905, upload-time = "2024-02-05T23:51:03.701Z" }, { url = "https://files.pythonhosted.org/packages/11/57/baae43d14fe163fa0e4c47f307b6b2511ab8d7d30177c491960504252053/numpy-1.26.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71", size = 20630554, upload-time = "2024-02-05T23:51:50.149Z" }, { url = "https://files.pythonhosted.org/packages/1a/2e/151484f49fd03944c4a3ad9c418ed193cfd02724e138ac8a9505d056c582/numpy-1.26.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef", size = 13997127, upload-time = "2024-02-05T23:52:15.314Z" }, { url = "https://files.pythonhosted.org/packages/79/ae/7e5b85136806f9dadf4878bf73cf223fe5c2636818ba3ab1c585d0403164/numpy-1.26.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e", size = 14222994, upload-time = "2024-02-05T23:52:47.569Z" }, @@ -273,6 +292,13 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/88/d9/ecf715f34c73ccb1d8ceb82fc01cd1028a65a5f6dbc57bfa6ea155119058/pandas-2.2.2.tar.gz", hash = "sha256:9e79019aba43cb4fda9e4d983f8e88ca0373adbb697ae9c6c43093218de28b54", size = 4398391, upload-time = "2024-04-10T19:45:48.342Z" } wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/2d/39600d073ea70b9cafdc51fab91d69c72b49dd92810f24cb5ac6631f387f/pandas-2.2.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:90c6fca2acf139569e74e8781709dccb6fe25940488755716d1d354d6bc58bce", size = 12551798, upload-time = "2024-04-10T19:44:10.36Z" }, + { url = "https://files.pythonhosted.org/packages/fd/4b/0cd38e68ab690b9df8ef90cba625bf3f93b82d1c719703b8e1b333b2c72d/pandas-2.2.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c7adfc142dac335d8c1e0dcbd37eb8617eac386596eb9e1a1b77791cf2498238", size = 11287392, upload-time = "2024-04-15T13:26:36.237Z" }, + { url = "https://files.pythonhosted.org/packages/01/c6/d3d2612aea9b9f28e79a30b864835dad8f542dcf474eee09afeee5d15d75/pandas-2.2.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4abfe0be0d7221be4f12552995e58723c7422c80a659da13ca382697de830c08", size = 15634823, upload-time = "2024-04-10T19:44:14.933Z" }, + { url = "https://files.pythonhosted.org/packages/89/1b/12521efcbc6058e2673583bb096c2b5046a9df39bd73eca392c1efed24e5/pandas-2.2.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8635c16bf3d99040fdf3ca3db669a7250ddf49c55dc4aa8fe0ae0fa8d6dcc1f0", size = 13032214, upload-time = "2024-04-10T19:44:19.013Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/303dba73f1c3a9ef067d23e5afbb6175aa25e8121be79be354dcc740921a/pandas-2.2.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:40ae1dffb3967a52203105a077415a86044a2bea011b5f321c6aa64b379a3f51", size = 16278302, upload-time = "2024-04-10T19:44:23.198Z" }, + { url = "https://files.pythonhosted.org/packages/ba/df/8ff7c5ed1cc4da8c6ab674dc8e4860a4310c3880df1283e01bac27a4333d/pandas-2.2.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8e5a0b00e1e56a842f922e7fae8ae4077aee4af0acb5ae3622bd4b4c30aedf99", size = 13892866, upload-time = "2024-04-10T19:44:27.777Z" }, + { url = "https://files.pythonhosted.org/packages/69/a6/81d5dc9a612cf0c1810c2ebc4f2afddb900382276522b18d128213faeae3/pandas-2.2.2-cp310-cp310-win_amd64.whl", hash = "sha256:ddf818e4e6c7c6f4f7c8a12709696d193976b591cc7dc50588d3d1a6b5dc8772", size = 11621592, upload-time = "2024-04-10T19:44:31.481Z" }, { url = "https://files.pythonhosted.org/packages/1b/70/61704497903d43043e288017cb2b82155c0d41e15f5c17807920877b45c2/pandas-2.2.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:696039430f7a562b74fa45f540aca068ea85fa34c244d0deee539cb6d70aa288", size = 12574808, upload-time = "2024-04-10T19:44:35.516Z" }, { url = "https://files.pythonhosted.org/packages/16/c6/75231fd47afd6b3f89011e7077f1a3958441264aca7ae9ff596e3276a5d0/pandas-2.2.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8e90497254aacacbc4ea6ae5e7a8cd75629d6ad2b30025a4a8b09aa4faf55151", size = 11304876, upload-time = "2024-04-10T19:44:39.37Z" }, { url = "https://files.pythonhosted.org/packages/97/2d/7b54f80b93379ff94afb3bd9b0cd1d17b48183a0d6f98045bc01ce1e06a7/pandas-2.2.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58b84b91b0b9f4bafac2a0ac55002280c094dfc6402402332c0913a59654ab2b", size = 15602548, upload-time = "2024-04-10T19:44:42.902Z" }, From 0986a6ec0af5540778de0c79945024cd65a94e6d Mon Sep 17 00:00:00 2001 From: SeaCelo Date: Wed, 17 Jun 2026 14:42:25 -0400 Subject: [PATCH 10/13] Revert fork URL/branch test defaults to EAPD-DRB/main for merge --- scripts/install.ps1 | 5 ++--- scripts/install.sh | 6 +++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/scripts/install.ps1 b/scripts/install.ps1 index 83227a1ec..5d2fdc44a 100644 --- a/scripts/install.ps1 +++ b/scripts/install.ps1 @@ -43,7 +43,7 @@ Unattended install into C:\work\MUIOGO. .EXAMPLE - .\scripts\install.ps1 -RepoUrl https://github.com/YOUR_FORK/MUIOGO.git -Branch feature/472-uv-installer + .\scripts\install.ps1 -RepoUrl https://github.com/YOUR_FORK/MUIOGO.git -Branch YOUR_BRANCH Test a fork branch before the PR is merged. #> @@ -61,8 +61,7 @@ param( $ErrorActionPreference = 'Stop' $ScriptDir = (Get-Location).Path -if ($RepoUrl -eq "") { $RepoUrl = "https://github.com/utsinboots/MUIOGO.git" } -if ($Branch -eq "") { $Branch = "feature/472-uv-installer" } +if ($RepoUrl -eq "") { $RepoUrl = "https://github.com/EAPD-DRB/MUIOGO.git" } $RepoName = "MUIOGO" # -- Colors -------------------------------------------------------------------- diff --git a/scripts/install.sh b/scripts/install.sh index 82bee2df8..56438e2b5 100644 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -25,15 +25,15 @@ # /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/EAPD-DRB/MUIOGO/main/scripts/install.sh)" # # Test a fork: -# bash install.sh --repo-url https://github.com/YOUR_FORK/MUIOGO.git --branch feature/472-uv-installer +# bash install.sh --repo-url https://github.com/YOUR_FORK/MUIOGO.git --branch YOUR_BRANCH set -euo pipefail # -- Defaults ------------------------------------------------------------------ -REPO_URL="https://github.com/utsinboots/MUIOGO.git" +REPO_URL="https://github.com/EAPD-DRB/MUIOGO.git" REPO_NAME="MUIOGO" DEST="" -BRANCH="feature/472-uv-installer" +BRANCH="" YES=0 NO_DEMO_DATA=0 SKIP_UV_INSTALL=0 From 4850e57d75deb16a4786fb9bd6580dafcdfd8e9c Mon Sep 17 00:00:00 2001 From: Utshant Gurung Date: Wed, 17 Jun 2026 16:05:15 -0400 Subject: [PATCH 11/13] feat: add a repo version step and make log off by default in installers --- scripts/QUICK_INSTALL.md | 2 +- scripts/install.ps1 | 57 ++++++++++++++++++++++++++++++++-------- scripts/install.sh | 50 ++++++++++++++++++++++++++++------- 3 files changed, 87 insertions(+), 22 deletions(-) diff --git a/scripts/QUICK_INSTALL.md b/scripts/QUICK_INSTALL.md index 4fc466f0d..704eca7c9 100644 --- a/scripts/QUICK_INSTALL.md +++ b/scripts/QUICK_INSTALL.md @@ -58,7 +58,7 @@ These work with either method above: - `--yes` / `-Yes` — accept all prompts (non-interactive). - `--no-demo-data` / `-NoDemoData` — skip the demo-data download. - `--skip-uv-install` / `-SkipUvInstall` — assume `uv` is already installed. -- `--no-log` / `-NoLog` — don't write an install log file. +- `--log` / `-Log` — write an install log file. ```bash # macOS / Linux — install into ~/Projects/MUIOGO, no prompts diff --git a/scripts/install.ps1 b/scripts/install.ps1 index 5d2fdc44a..70300c4d9 100644 --- a/scripts/install.ps1 +++ b/scripts/install.ps1 @@ -27,8 +27,8 @@ .PARAMETER SkipUvInstall Skip uv installation; assume uv is already on PATH. -.PARAMETER NoLog - Don't write a log file. +.PARAMETER Log + Write an install log file. .PARAMETER RepoUrl Override the repository URL to clone from. Default: https://github.com/EAPD-DRB/MUIOGO.git @@ -55,7 +55,7 @@ param( [switch]$Yes, [switch]$NoDemoData, [switch]$SkipUvInstall, - [switch]$NoLog + [switch]$Log ) $ErrorActionPreference = 'Stop' @@ -77,7 +77,7 @@ if ($UseAnsi) { # -- Logging ------------------------------------------------------------------- $Ts = Get-Date -Format "yyyyMMdd-HHmmss" $LogFile = Join-Path $ScriptDir ".install-$Ts.log" -$WriteLog = -not $NoLog +$WriteLog = [bool]$Log if ($WriteLog) { try { Start-Transcript -Path $LogFile -Append | Out-Null } catch { @@ -111,7 +111,7 @@ function Write-Skip($label, $detail = "") { } function Write-Cmd($cmd) { Write-Host " $DIM`$ $cmd$RESET" } -$TotalSteps = 5 +$TotalSteps = 6 function Step-Banner($n, $title) { Write-Host "" Write-Hr @@ -234,8 +234,9 @@ if ($UvPresent -or $SkipUvInstall) { Write-Host " 2. Install uv ${DIM}~5MB, seconds${RESET}" } Write-Host (" 3. Clone MUIOGO ${DIM}depends on network${RESET}") -Write-Host " 4. uv sync (Python + deps) ${DIM}~30s${RESET}" -Write-Host " 5. Platform setup (solvers, demo data, secret key, verification)" +Write-Host " 4. Check for updates (MUIOGO)" +Write-Host " 5. uv sync (Python + deps) ${DIM}~30s${RESET}" +Write-Host " 6. Platform setup (solvers, demo data, secret key, verification)" Write-Host "" if (-not (Prompt-YN "Proceed with installation?" 'y')) { @@ -370,8 +371,42 @@ if ($DestHasRepo) { Record-Step "Clone" "PASS" $branch } -# -- Step 4: uv sync ----------------------------------------------------------- -Step-Banner 4 "Install dependencies (uv sync)" +# -- Step 4: Check for updates ------------------------------------------------- +Step-Banner 4 "Check for updates (MUIOGO)" +$localSha = "" +$remoteSha = "" +$behind = "?" +try { $localSha = (& $GitBin -C $DestAbs rev-parse --short HEAD 2>$null).Trim() } catch {} +try { & $GitBin -C $DestAbs fetch origin --quiet 2>$null } catch {} +try { $remoteSha = (& $GitBin -C $DestAbs rev-parse --short origin/main 2>$null).Trim() } catch {} +try { $behind = (& $GitBin -C $DestAbs rev-list --count "HEAD..origin/main" 2>$null).Trim() } catch {} + +Write-Host (" Local: {0}" -f $localSha) +Write-Host (" Latest: {0} (EAPD-DRB/MUIOGO)" -f $remoteSha) +Write-Host "" + +if ($behind -eq "0") { + Write-Pass "Already up to date" + Record-Step "Check for updates" "PASS" "up to date" +} elseif ($behind -eq "?") { + Write-Warn2 "Could not check for updates — continuing with current version" + Record-Step "Check for updates" "WARN" "could not reach remote" +} else { + Write-Warn2 "This branch is $behind commit(s) behind EAPD-DRB/MUIOGO:main" + if (Prompt-YN "Update now?" 'y') { + & $GitBin -C $DestAbs pull --ff-only + $newSha = "" + try { $newSha = (& $GitBin -C $DestAbs rev-parse --short HEAD 2>$null).Trim() } catch {} + Write-Pass "Updated to $newSha" + Record-Step "Check for updates" "PASS" "updated (was $behind behind)" + } else { + Write-Skip "Update skipped" "continuing with $localSha" + Record-Step "Check for updates" "SKIP" "user declined update" + } +} + +# -- Step 5: uv sync ----------------------------------------------------------- +Step-Banner 5 "Install dependencies (uv sync)" Write-Host (" Installing Python + all dependencies into {0}\.venv" -f $DestAbs) Write-Cmd "uv sync" Push-Location $DestAbs @@ -388,8 +423,8 @@ try { Pop-Location } -# -- Step 5: Platform setup ---------------------------------------------------- -Step-Banner 5 "Platform setup (solvers, demo data, secret key, verification)" +# -- Step 6: Platform setup ---------------------------------------------------- +Step-Banner 6 "Platform setup (solvers, demo data, secret key, verification)" $VenvPython = Join-Path $DestAbs ".venv\Scripts\python.exe" if (-not (Test-Path $VenvPython)) { Write-Fail "venv Python not found" $VenvPython diff --git a/scripts/install.sh b/scripts/install.sh index 56438e2b5..50f67014c 100644 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -19,7 +19,7 @@ # --yes Auto-confirm every prompt (non-interactive) # --no-demo-data Skip demo data installation # --skip-uv-install Skip uv installation; assume uv is already on PATH -# --no-log Don't write a log file +# --log Write an install log file # # One-liner: # /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/EAPD-DRB/MUIOGO/main/scripts/install.sh)" @@ -37,10 +37,10 @@ BRANCH="" YES=0 NO_DEMO_DATA=0 SKIP_UV_INSTALL=0 -NO_LOG=0 +NO_LOG=1 SCRIPT_DIR="$(pwd)" -TOTAL_STEPS=5 +TOTAL_STEPS=6 # -- Argument parsing ---------------------------------------------------------- while [[ $# -gt 0 ]]; do @@ -51,7 +51,7 @@ while [[ $# -gt 0 ]]; do --yes|-y) YES=1; shift ;; --no-demo-data) NO_DEMO_DATA=1; shift ;; --skip-uv-install) SKIP_UV_INSTALL=1; shift ;; - --no-log) NO_LOG=1; shift ;; + --log) NO_LOG=0; shift ;; -h|--help) sed -n '/^# Usage/,/^[^#]/p' "$0" | sed 's/^# \?//' exit 0 @@ -232,8 +232,9 @@ else echo -e " 2. Install uv ${DIM}~5MB, seconds${RESET}" fi echo -e " 3. Clone MUIOGO ${DIM}depends on network${RESET}" -echo -e " 4. uv sync (Python + deps) ${DIM}~30s${RESET}" -echo " 5. Platform setup (solvers, demo data, secret key, verification)" +echo -e " 4. Check for updates (MUIOGO)" +echo -e " 5. uv sync (Python + deps) ${DIM}~30s${RESET}" +echo " 6. Platform setup (solvers, demo data, secret key, verification)" echo "" if ! prompt_yn "Proceed with installation?" y; then @@ -359,8 +360,37 @@ else record_step "Clone" "PASS" "$CURRENT_BRANCH" fi -# -- Step 4: uv sync ----------------------------------------------------------- -step_banner 4 "Install dependencies (uv sync)" +# -- Step 4: Check for updates ------------------------------------------------- +step_banner 4 "Check for updates (MUIOGO)" +LOCAL_SHA="$("$GIT_BIN" -C "$DEST_ABS" rev-parse --short HEAD 2>/dev/null || echo '?')" +"$GIT_BIN" -C "$DEST_ABS" fetch origin --quiet 2>/dev/null || true +REMOTE_SHA="$("$GIT_BIN" -C "$DEST_ABS" rev-parse --short origin/main 2>/dev/null || echo '?')" +BEHIND="$("$GIT_BIN" -C "$DEST_ABS" rev-list --count HEAD..origin/main 2>/dev/null || echo '?')" + +echo " Local: ${LOCAL_SHA}" +echo " Latest: ${REMOTE_SHA} (EAPD-DRB/MUIOGO)" +echo "" + +if [[ "$BEHIND" == "0" ]]; then + pass "Already up to date" + record_step "Check for updates" "PASS" "up to date" +elif [[ "$BEHIND" == "?" ]]; then + warn "Could not check for updates — continuing with current version" + record_step "Check for updates" "WARN" "could not reach remote" +else + warn "This branch is ${BEHIND} commit(s) behind EAPD-DRB/MUIOGO:main" + if prompt_yn "Update now?" y; then + "$GIT_BIN" -C "$DEST_ABS" pull --ff-only + pass "Updated to $("$GIT_BIN" -C "$DEST_ABS" rev-parse --short HEAD)" + record_step "Check for updates" "PASS" "updated (was ${BEHIND} behind)" + else + skip "Update skipped" "continuing with ${LOCAL_SHA}" + record_step "Check for updates" "SKIP" "user declined update" + fi +fi + +# -- Step 5: uv sync ----------------------------------------------------------- +step_banner 5 "Install dependencies (uv sync)" echo " Installing Python + all dependencies into ${DEST_ABS}/.venv" cmd "uv sync" pushd "$DEST_ABS" >/dev/null @@ -369,8 +399,8 @@ popd >/dev/null pass "Dependencies installed" ".venv" record_step "uv sync" "PASS" ".venv" -# -- Step 5: Platform setup ---------------------------------------------------- -step_banner 5 "Platform setup (solvers, demo data, secret key, verification)" +# -- Step 6: Platform setup ---------------------------------------------------- +step_banner 6 "Platform setup (solvers, demo data, secret key, verification)" if [[ -x "${DEST_ABS}/.venv/bin/python" ]]; then VENV_PYTHON="${DEST_ABS}/.venv/bin/python" elif [[ -x "${DEST_ABS}/.venv/Scripts/python.exe" ]]; then From 190587471952997132ba70cc3a5bd54183c47fe0 Mon Sep 17 00:00:00 2001 From: Utshant Gurung Date: Wed, 17 Jun 2026 16:30:36 -0400 Subject: [PATCH 12/13] fix: move version check into clone step, show ahead/behind counts, fix upstream comparison --- scripts/install.ps1 | 97 ++++++++++++++++++++------------------------- scripts/install.sh | 85 ++++++++++++++++++--------------------- 2 files changed, 82 insertions(+), 100 deletions(-) diff --git a/scripts/install.ps1 b/scripts/install.ps1 index 70300c4d9..98ec7ae41 100644 --- a/scripts/install.ps1 +++ b/scripts/install.ps1 @@ -111,7 +111,7 @@ function Write-Skip($label, $detail = "") { } function Write-Cmd($cmd) { Write-Host " $DIM`$ $cmd$RESET" } -$TotalSteps = 6 +$TotalSteps = 5 function Step-Banner($n, $title) { Write-Host "" Write-Hr @@ -234,9 +234,8 @@ if ($UvPresent -or $SkipUvInstall) { Write-Host " 2. Install uv ${DIM}~5MB, seconds${RESET}" } Write-Host (" 3. Clone MUIOGO ${DIM}depends on network${RESET}") -Write-Host " 4. Check for updates (MUIOGO)" -Write-Host " 5. uv sync (Python + deps) ${DIM}~30s${RESET}" -Write-Host " 6. Platform setup (solvers, demo data, secret key, verification)" +Write-Host " 4. uv sync (Python + deps) ${DIM}~30s${RESET}" +Write-Host " 5. Platform setup (solvers, demo data, secret key, verification)" Write-Host "" if (-not (Prompt-YN "Proceed with installation?" 'y')) { @@ -335,19 +334,45 @@ if ($DestHasRepo) { $branch = "?" try { $branch = (& $GitBin -C $DestAbs rev-parse --abbrev-ref HEAD 2>$null).Trim() } catch {} Write-Host (" Existing MUIOGO clone found at {0} (branch: {1})." -f $DestAbs, $branch) - if (Prompt-YN "Update with git pull --ff-only?" 'y') { - Write-Cmd "git -C $DestAbs pull --ff-only" - & $GitBin -C $DestAbs pull --ff-only - if ($LASTEXITCODE -eq 0) { - Write-Pass "Repo updated" $DestAbs - Record-Step "Clone" "PASS" "updated ($branch)" + + # -- Version check against EAPD-DRB/MUIOGO:main --------------------------- + $localSha = "" + $remoteSha = "" + $behind = "?" + $ahead = "?" + try { $localSha = (& $GitBin -C $DestAbs rev-parse --short HEAD 2>$null).Trim() } catch {} + try { & $GitBin -C $DestAbs fetch https://github.com/EAPD-DRB/MUIOGO.git main --quiet 2>$null } catch {} + try { $remoteSha = (& $GitBin -C $DestAbs rev-parse --short FETCH_HEAD 2>$null).Trim() } catch {} + try { $behind = (& $GitBin -C $DestAbs rev-list --count "HEAD..FETCH_HEAD" 2>$null).Trim() } catch {} + try { $ahead = (& $GitBin -C $DestAbs rev-list --count "FETCH_HEAD..HEAD" 2>$null).Trim() } catch {} + + Write-Host (" Local: {0} ({1})" -f $localSha, $branch) + Write-Host (" Latest: {0} (EAPD-DRB/MUIOGO:main)" -f $remoteSha) + Write-Host "" + + if ($behind -eq "?") { + Write-Warn2 "Could not check for updates - continuing with current version" + Record-Step "Clone" "WARN" "could not reach remote" + } elseif ($behind -eq "0") { + Write-Pass "Up to date -- 0 behind, $ahead ahead" + Record-Step "Clone" "PASS" "up to date ($branch)" + } else { + Write-Warn2 "$behind behind, $ahead ahead -- update now?" + if (Prompt-YN "Update now?" 'y') { + & $GitBin -C $DestAbs pull --ff-only + if ($LASTEXITCODE -eq 0) { + $newSha = "" + try { $newSha = (& $GitBin -C $DestAbs rev-parse --short HEAD 2>$null).Trim() } catch {} + Write-Pass "Updated to $newSha" + Record-Step "Clone" "PASS" "updated (was $behind behind)" + } else { + Write-Warn2 "git pull failed; continuing with existing state" + Record-Step "Clone" "WARN" "pull failed; existing state used" + } } else { - Write-Warn2 "git pull failed; continuing with existing state" - Record-Step "Clone" "WARN" "pull failed; existing state used" + Write-Skip "Update skipped" "continuing with $localSha" + Record-Step "Clone" "SKIP" "user declined update" } - } else { - Write-Skip "Update" "using existing clone as-is" - Record-Step "Clone" "SKIP" "existing clone used as-is" } } else { $cloneArgs = @("clone") @@ -371,42 +396,8 @@ if ($DestHasRepo) { Record-Step "Clone" "PASS" $branch } -# -- Step 4: Check for updates ------------------------------------------------- -Step-Banner 4 "Check for updates (MUIOGO)" -$localSha = "" -$remoteSha = "" -$behind = "?" -try { $localSha = (& $GitBin -C $DestAbs rev-parse --short HEAD 2>$null).Trim() } catch {} -try { & $GitBin -C $DestAbs fetch origin --quiet 2>$null } catch {} -try { $remoteSha = (& $GitBin -C $DestAbs rev-parse --short origin/main 2>$null).Trim() } catch {} -try { $behind = (& $GitBin -C $DestAbs rev-list --count "HEAD..origin/main" 2>$null).Trim() } catch {} - -Write-Host (" Local: {0}" -f $localSha) -Write-Host (" Latest: {0} (EAPD-DRB/MUIOGO)" -f $remoteSha) -Write-Host "" - -if ($behind -eq "0") { - Write-Pass "Already up to date" - Record-Step "Check for updates" "PASS" "up to date" -} elseif ($behind -eq "?") { - Write-Warn2 "Could not check for updates — continuing with current version" - Record-Step "Check for updates" "WARN" "could not reach remote" -} else { - Write-Warn2 "This branch is $behind commit(s) behind EAPD-DRB/MUIOGO:main" - if (Prompt-YN "Update now?" 'y') { - & $GitBin -C $DestAbs pull --ff-only - $newSha = "" - try { $newSha = (& $GitBin -C $DestAbs rev-parse --short HEAD 2>$null).Trim() } catch {} - Write-Pass "Updated to $newSha" - Record-Step "Check for updates" "PASS" "updated (was $behind behind)" - } else { - Write-Skip "Update skipped" "continuing with $localSha" - Record-Step "Check for updates" "SKIP" "user declined update" - } -} - -# -- Step 5: uv sync ----------------------------------------------------------- -Step-Banner 5 "Install dependencies (uv sync)" +# -- Step 4: uv sync ----------------------------------------------------------- +Step-Banner 4 "Install dependencies (uv sync)" Write-Host (" Installing Python + all dependencies into {0}\.venv" -f $DestAbs) Write-Cmd "uv sync" Push-Location $DestAbs @@ -423,8 +414,8 @@ try { Pop-Location } -# -- Step 6: Platform setup ---------------------------------------------------- -Step-Banner 6 "Platform setup (solvers, demo data, secret key, verification)" +# -- Step 5: Platform setup ---------------------------------------------------- +Step-Banner 5 "Platform setup (solvers, demo data, secret key, verification)" $VenvPython = Join-Path $DestAbs ".venv\Scripts\python.exe" if (-not (Test-Path $VenvPython)) { Write-Fail "venv Python not found" $VenvPython diff --git a/scripts/install.sh b/scripts/install.sh index 50f67014c..3521f9036 100644 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -40,7 +40,7 @@ SKIP_UV_INSTALL=0 NO_LOG=1 SCRIPT_DIR="$(pwd)" -TOTAL_STEPS=6 +TOTAL_STEPS=5 # -- Argument parsing ---------------------------------------------------------- while [[ $# -gt 0 ]]; do @@ -232,9 +232,8 @@ else echo -e " 2. Install uv ${DIM}~5MB, seconds${RESET}" fi echo -e " 3. Clone MUIOGO ${DIM}depends on network${RESET}" -echo -e " 4. Check for updates (MUIOGO)" -echo -e " 5. uv sync (Python + deps) ${DIM}~30s${RESET}" -echo " 6. Platform setup (solvers, demo data, secret key, verification)" +echo -e " 4. uv sync (Python + deps) ${DIM}~30s${RESET}" +echo " 5. Platform setup (solvers, demo data, secret key, verification)" echo "" if ! prompt_yn "Proceed with installation?" y; then @@ -333,18 +332,39 @@ fi if [[ $DEST_HAS_REPO -eq 1 ]]; then CURRENT_BRANCH="$("$GIT_BIN" -C "$DEST_ABS" rev-parse --abbrev-ref HEAD 2>/dev/null || echo '?')" echo " Existing MUIOGO clone found at ${DEST_ABS} (branch: ${CURRENT_BRANCH})." - if prompt_yn "Update with git pull --ff-only?" y; then - cmd "git -C ${DEST_ABS} pull --ff-only" - if "$GIT_BIN" -C "$DEST_ABS" pull --ff-only; then - pass "Repo updated" "$DEST_ABS" - record_step "Clone" "PASS" "updated (${CURRENT_BRANCH})" + + # -- Version check against EAPD-DRB/MUIOGO:main --------------------------- + LOCAL_SHA="$("$GIT_BIN" -C "$DEST_ABS" rev-parse --short HEAD 2>/dev/null || echo '?')" + "$GIT_BIN" -C "$DEST_ABS" fetch https://github.com/EAPD-DRB/MUIOGO.git main --quiet 2>/dev/null || true + REMOTE_SHA="$("$GIT_BIN" -C "$DEST_ABS" rev-parse --short FETCH_HEAD 2>/dev/null || echo '?')" + BEHIND="$("$GIT_BIN" -C "$DEST_ABS" rev-list --count HEAD..FETCH_HEAD 2>/dev/null || echo '?')" + AHEAD="$("$GIT_BIN" -C "$DEST_ABS" rev-list --count FETCH_HEAD..HEAD 2>/dev/null || echo '?')" + + echo " Local: ${LOCAL_SHA} (${CURRENT_BRANCH})" + echo " Latest: ${REMOTE_SHA} (EAPD-DRB/MUIOGO:main)" + echo "" + + if [[ "$BEHIND" == "?" ]]; then + warn "Could not check for updates — continuing with current version" + record_step "Clone" "WARN" "could not reach remote" + elif [[ "$BEHIND" == "0" ]]; then + pass "Up to date -- 0 behind, ${AHEAD} ahead" + record_step "Clone" "PASS" "up to date (${CURRENT_BRANCH})" + else + warn "${BEHIND} behind, ${AHEAD} ahead -- update now?" + if prompt_yn "Update now?" y; then + cmd "git -C ${DEST_ABS} pull --ff-only" + if "$GIT_BIN" -C "$DEST_ABS" pull --ff-only; then + pass "Updated to $("$GIT_BIN" -C "$DEST_ABS" rev-parse --short HEAD)" + record_step "Clone" "PASS" "updated (was ${BEHIND} behind)" + else + warn "git pull failed; continuing with existing state" + record_step "Clone" "WARN" "pull failed; existing state used" + fi else - warn "git pull failed; continuing with existing state" - record_step "Clone" "WARN" "pull failed; existing state used" + skip "Update skipped" "continuing with ${LOCAL_SHA}" + record_step "Clone" "SKIP" "user declined update" fi - else - skip "Update" "using existing clone as-is" - record_step "Clone" "SKIP" "existing clone used as-is" fi else CLONE_ARGS=("clone") @@ -360,37 +380,8 @@ else record_step "Clone" "PASS" "$CURRENT_BRANCH" fi -# -- Step 4: Check for updates ------------------------------------------------- -step_banner 4 "Check for updates (MUIOGO)" -LOCAL_SHA="$("$GIT_BIN" -C "$DEST_ABS" rev-parse --short HEAD 2>/dev/null || echo '?')" -"$GIT_BIN" -C "$DEST_ABS" fetch origin --quiet 2>/dev/null || true -REMOTE_SHA="$("$GIT_BIN" -C "$DEST_ABS" rev-parse --short origin/main 2>/dev/null || echo '?')" -BEHIND="$("$GIT_BIN" -C "$DEST_ABS" rev-list --count HEAD..origin/main 2>/dev/null || echo '?')" - -echo " Local: ${LOCAL_SHA}" -echo " Latest: ${REMOTE_SHA} (EAPD-DRB/MUIOGO)" -echo "" - -if [[ "$BEHIND" == "0" ]]; then - pass "Already up to date" - record_step "Check for updates" "PASS" "up to date" -elif [[ "$BEHIND" == "?" ]]; then - warn "Could not check for updates — continuing with current version" - record_step "Check for updates" "WARN" "could not reach remote" -else - warn "This branch is ${BEHIND} commit(s) behind EAPD-DRB/MUIOGO:main" - if prompt_yn "Update now?" y; then - "$GIT_BIN" -C "$DEST_ABS" pull --ff-only - pass "Updated to $("$GIT_BIN" -C "$DEST_ABS" rev-parse --short HEAD)" - record_step "Check for updates" "PASS" "updated (was ${BEHIND} behind)" - else - skip "Update skipped" "continuing with ${LOCAL_SHA}" - record_step "Check for updates" "SKIP" "user declined update" - fi -fi - -# -- Step 5: uv sync ----------------------------------------------------------- -step_banner 5 "Install dependencies (uv sync)" +# -- Step 4: uv sync ----------------------------------------------------------- +step_banner 4 "Install dependencies (uv sync)" echo " Installing Python + all dependencies into ${DEST_ABS}/.venv" cmd "uv sync" pushd "$DEST_ABS" >/dev/null @@ -399,8 +390,8 @@ popd >/dev/null pass "Dependencies installed" ".venv" record_step "uv sync" "PASS" ".venv" -# -- Step 6: Platform setup ---------------------------------------------------- -step_banner 6 "Platform setup (solvers, demo data, secret key, verification)" +# -- Step 5: Platform setup ---------------------------------------------------- +step_banner 5 "Platform setup (solvers, demo data, secret key, verification)" if [[ -x "${DEST_ABS}/.venv/bin/python" ]]; then VENV_PYTHON="${DEST_ABS}/.venv/bin/python" elif [[ -x "${DEST_ABS}/.venv/Scripts/python.exe" ]]; then From ed5e829d13cfd030bd4aa2b7fa312ac0307017d4 Mon Sep 17 00:00:00 2001 From: SeaCelo Date: Thu, 18 Jun 2026 10:54:51 -0400 Subject: [PATCH 13/13] Offer to install Homebrew on macOS; treat missing solvers as non-fatal --- scripts/install.ps1 | 1 + scripts/install.sh | 1 + scripts/setup_dev.py | 138 +++++++++++++++++++++++++++++++++++-------- 3 files changed, 116 insertions(+), 24 deletions(-) diff --git a/scripts/install.ps1 b/scripts/install.ps1 index 98ec7ae41..702cb3462 100644 --- a/scripts/install.ps1 +++ b/scripts/install.ps1 @@ -425,6 +425,7 @@ if (-not (Test-Path $VenvPython)) { $SetupArgs = @("scripts\setup_dev.py", "--platform-only", "--venv-dir", ".venv") if ($NoDemoData) { $SetupArgs += "--no-demo-data" } +if ($Yes) { $SetupArgs += "--yes" } Write-Cmd ("python {0}" -f ($SetupArgs -join ' ')) Push-Location $DestAbs diff --git a/scripts/install.sh b/scripts/install.sh index 3521f9036..8e22538ed 100644 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -404,6 +404,7 @@ fi SETUP_ARGS=("scripts/setup_dev.py" "--platform-only" "--venv-dir" ".venv") [[ $NO_DEMO_DATA -eq 1 ]] && SETUP_ARGS+=("--no-demo-data") +[[ $YES -eq 1 ]] && SETUP_ARGS+=("--yes") cmd "python ${SETUP_ARGS[*]}" pushd "$DEST_ABS" >/dev/null diff --git a/scripts/setup_dev.py b/scripts/setup_dev.py index 5ecd53f5c..7423fbf75 100644 --- a/scripts/setup_dev.py +++ b/scripts/setup_dev.py @@ -870,8 +870,79 @@ def _detect_linux_pkg_manager() -> tuple[str, list[str], list[str]] | None: return None -def install_solvers() -> bool: - """Install GLPK and CBC solver binaries using OS package managers.""" +def _install_homebrew(yes: bool = False) -> tuple[str | None, bool]: + """Offer to install Homebrew on macOS. + + Returns ``(brew_path, attempted_and_failed)``: + * ``(path, False)`` — Homebrew is installed and available; + * ``(None, False)`` — install was declined or skipped (non-fatal); + * ``(None, True)`` — install was attempted and failed (fatal). + + Homebrew's official installer is interactive — it asks for confirmation and a + sudo password and may trigger an Xcode Command Line Tools install — so we never + run it unattended. Under ``--yes`` or a non-interactive shell we point the user + at https://brew.sh and skip it (a non-fatal decline). + """ + print( + "\n Homebrew is needed to install the GLPK/CBC solvers and is not present." + "\n (Homebrew is the standard macOS package manager: https://brew.sh)" + ) + + if yes or not sys.stdin.isatty(): + reason = "--yes given" if yes else "non-interactive shell" + _print_warn( + f"Skipping Homebrew install ({reason})", + "Install it from https://brew.sh, then re-run -- or: brew install glpk cbc", + ) + return None, False + + try: + answer = input(" Install Homebrew now? [y/N]: ").strip().lower() + except (EOFError, KeyboardInterrupt): + answer = "n" # treat Ctrl+D / Ctrl+C as "decline" + if answer not in ("y", "yes"): + _print_warn( + "Skipping Homebrew install", + "Install it from https://brew.sh, then re-run -- or: brew install glpk cbc", + ) + return None, False + + print( + " Installing Homebrew via the official installer " + "(you may be prompted for your password)..." + ) + installer = ( + '/bin/bash -c "$(curl -fsSL ' + 'https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"' + ) + if subprocess.run(installer, shell=True).returncode != 0: + _print_fail("Homebrew installation failed", "Install it manually from https://brew.sh") + return None, True + + # brew is not on this process's PATH yet; add its standard location so the + # solver install below can find it in the same run. + for candidate in ("/opt/homebrew/bin/brew", "/usr/local/bin/brew"): + if Path(candidate).exists(): + os.environ["PATH"] = ( + str(Path(candidate).parent) + os.pathsep + os.environ.get("PATH", "") + ) + _print_pass("Homebrew installed", candidate) + return candidate, False + + found = _which("brew") + if found: + _print_pass("Homebrew installed", found) + return found, False + _print_fail("Homebrew installed but 'brew' not on PATH", "Open a new terminal and re-run.") + return None, True + + +def install_solvers(yes: bool = False) -> bool: + """Install GLPK and CBC solver binaries using OS package managers. + + On macOS, a missing Homebrew (or a declined install) is a non-fatal warning: + MUIOGO still installs and runs -- the solvers are only needed to solve a model. + """ _print_header("Step 3: Solver dependencies (GLPK & CBC)") glpk_ok = _resolve_solver_binary("glpsol", "SOLVER_GLPK_PATH") is not None @@ -885,21 +956,20 @@ def install_solvers() -> bool: # ── macOS (Homebrew) ────────────────────────────────────────────────── if SYSTEM == "Darwin": - if not _which("brew"): - _print_fail( - "Homebrew not found", - "Install from https://brew.sh then re-run this script.", - ) - success = False - else: + brew = _which("brew") + if brew is None: + brew, brew_install_failed = _install_homebrew(yes=yes) + if brew_install_failed: + success = False + if brew is not None: if not glpk_ok: - r = _run(["brew", "install", "glpk"], capture_output=True, text=True) + r = _run([brew, "install", "glpk"], capture_output=True, text=True) if r.returncode != 0: _print_fail("brew install glpk", r.stderr.strip()) success = False if not cbc_ok: - r = _run(["brew", "install", "cbc"], capture_output=True, text=True) + r = _run([brew, "install", "cbc"], capture_output=True, text=True) if r.returncode != 0: _print_fail("brew install cbc", r.stderr.strip()) success = False @@ -983,16 +1053,14 @@ def install_solvers() -> bool: if glpk_exec is not None: _print_pass("GLPK (glpsol) available", str(glpk_exec.parent)) else: - _print_fail("GLPK (glpsol) not available") - success = False + _print_warn("GLPK (glpsol) not installed", "needed only to solve models") if cbc_exec is not None: _print_pass("CBC available", str(cbc_exec.parent)) else: - _print_fail("CBC not available") - success = False + _print_warn("CBC not installed", "needed only to solve models") - if success: + if glpk_exec is not None and cbc_exec is not None: print(f"\n {GREEN}Solver dependencies installed.{RESET}") else: _print_solver_manual_instructions() @@ -1097,8 +1165,7 @@ def run_checks() -> bool: glpsol_exec = _find_solver_binary_on_path("glpsol") if glpsol_exec is None: - _print_fail("GLPK (glpsol)", "not found via SOLVER_GLPK_PATH or PATH") - all_ok = False + _print_warn("GLPK (glpsol) not installed", "needed only to solve models") else: try: r = subprocess.run( @@ -1128,8 +1195,7 @@ def run_checks() -> bool: cbc_exec = _find_solver_binary_on_path("cbc") if cbc_exec is None: - _print_fail("CBC", "not found via SOLVER_CBC_PATH or PATH") - all_ok = False + _print_warn("CBC not installed", "needed only to solve models") else: try: r = subprocess.run( @@ -1190,19 +1256,23 @@ def _print_summary(results: dict[str, tuple[bool, str]]) -> None: _print_header("Setup Summary") all_ok = all(passed for passed, _ in results.values()) + has_warning = any( + detail.lower().startswith("warning") for _, detail in results.values() + ) for step, (passed, detail) in results.items(): if detail.lower().startswith("skipped"): _print_skipped(step, detail) - continue - if passed: + elif detail.lower().startswith("warning"): + _print_warn(step, detail.split(":", 1)[1].strip() if ":" in detail else detail) + elif passed: _print_pass(step, detail) else: _print_fail(step, detail) print() - if all_ok: + if all_ok and not has_warning: start_cmd = r'scripts\start.bat' if SYSTEM == "Windows" else "./scripts/start.sh" run_cmd = f'"{_venv_python()}" "{PROJECT_ROOT / "API" / "app.py"}"' print(textwrap.dedent(f"""\ @@ -1215,6 +1285,16 @@ def _print_summary(results: dict[str, tuple[bool, str]]) -> None: 3. Advanced/manual start (without launcher): {run_cmd} """)) + elif all_ok: + start_cmd = r'scripts\start.bat' if SYSTEM == "Windows" else "./scripts/start.sh" + print(textwrap.dedent(f"""\ + {GREEN}{BOLD}MUIOGO is set up.{RESET} {YELLOW}Some optional components are missing (see warnings above).{RESET} + + You can start the app now: + {start_cmd} + The GLPK/CBC solvers are only needed to run a model — install them (see the + notes above), then re-run with --check when you're ready. + """)) else: check_cmd = r'scripts\setup.bat --check' if SYSTEM == "Windows" else "./scripts/setup.sh --check" setup_cmd = r'scripts\setup.bat' if SYSTEM == "Windows" else "./scripts/setup.sh" @@ -1370,7 +1450,17 @@ def main() -> int: results["App secret key"] = (_ensure_secret_key_in_env(), str(ENV_FILE)) - results["Solver dependencies (GLPK & CBC)"] = (install_solvers(), "") + solver_ok = install_solvers(yes=args.yes) + if not solver_ok: + solver_detail = "see errors above" + elif ( + _resolve_solver_binary("glpsol", "SOLVER_GLPK_PATH") is not None + and _resolve_solver_binary("cbc", "SOLVER_CBC_PATH") is not None + ): + solver_detail = "GLPK & CBC installed" + else: + solver_detail = "warning: GLPK/CBC not installed (optional — see solver notes above)" + results["Solver dependencies (GLPK & CBC)"] = (solver_ok, solver_detail) demo_detail = "" if args.with_demo_data: