diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index be7279c..cc69d17 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -7,7 +7,7 @@ on: - main # Trigger when pushing to main jobs: - ci: + update-branches: env: UV_SYSTEM_PYTHON: true runs-on: ubuntu-latest @@ -20,17 +20,35 @@ jobs: uses: astral-sh/setup-uv@v3 with: enable-cache: true + + - name: Install yq + run: sudo apt-get update && sudo apt-get install -y yq + + - name: Read deployment mode from YAML file + run: | + export MODE=$(cat deploy_mode.yaml | yq '.mode') + echo "mode=$MODE" >> $GITHUB_ENV + + - name: Print the deployment mode + run: echo "The deployment mode is ${{ env.mode }}" - name: Generate requirements.txt with `uv` run: uv export --no-hashes -o requirements.txt - name: Generate rsconnect-python manifest.json run: uvx --from rsconnect-python rsconnect write-manifest shiny . --entrypoint shinylims.app:app - - - name: Commit changes and push to deploy branch + + - name: Commit and push changes based on mode run: | git config --global user.name "github-actions[bot]" git config --global user.email "github-actions[bot]@users.noreply.github.com" git add requirements.txt manifest.json - git commit -m "Update requirements on new commit from main" || echo "No changes to commit" - git push origin --force HEAD:deploy # Push to deploy branch, using force because of diverging branches \ No newline at end of file + git commit -m "Update requirements and manifest" || echo "No changes to commit" + + if [ "${{ env.mode }}" == "test" ]; then + git push origin --force HEAD:test_deploy # Push to test branch + elif [ "${{ env.mode }}" == "prod" ]; then + git push origin --force HEAD:deploy # Push to deploy branch + elif [ "${{ env.mode }}" == "both" ]; then + git push origin --force HEAD:test_deploy # Push to test branch + git push origin --force HEAD:deploy # Push to deploy branch \ No newline at end of file diff --git a/.gitignore b/.gitignore index 8e5ff24..e7401cb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ +# Ignore cache __pycache__/ /src/shinylims/__pycache__ -*.pyc \ No newline at end of file +*.pyc + +# Other +.env \ No newline at end of file diff --git a/README.md b/README.md index 34b142e..2201e0c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ ### Shinylims -A Python Shiny app for LIMS reporting. +A Python Shiny app for LIMS reporting hosted on Posit Connect. This python package is managed with uv (https://docs.astral.sh/uv). To run it, clone the repository and install the package with: ``` @@ -13,9 +13,8 @@ Then, run the shiny app with: uv run uvicorn shinylims.app:app ``` -Remember to update the manifest before making commits - -``` -uvx --from rsconnect-python --python .venv/Scripts/python.exe rsconnect write-manifest shiny . --overwrite --entrypoint shinylims.app:app -``` +For development: +* Set the deploy mode in deploy_mode.yaml. If set to 'test', any pushes to main will be deployed to the test-deploy shiny app instance on posit connect. Else set the mode to 'prod' to deploy on the production instance or 'both' to update both instances. The idea here is to first implement changes on an instance running on ypu local computer. When confirmed running as expected locally, first push to the test posit instance to confirm that everything also functions on posit. Then change to 'prod' and push again to implement changes to the production instance of the app. +* For local development, the apikey and posit connect url must be provided in a .env file. +* When pushing to main a Github actions pipeline will ensure that manifest and requirement files are created for posit deployment. These files along with any updates to the code are forced pushed to either the production deploy branch or the test_deploy branch depending on the mode set (see above) \ No newline at end of file diff --git a/deploy_mode.yaml b/deploy_mode.yaml new file mode 100644 index 0000000..c8dafdc --- /dev/null +++ b/deploy_mode.yaml @@ -0,0 +1,6 @@ +# Mode options used by the deployment script: +# - prod: for production updates +# - test: for testing updates +# - both: for updating both test and production + +mode: test \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 8334240..3498985 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,11 +13,15 @@ dependencies = [ "itables==2.0.1", "pandas==2.2.2", "pins==0.8.5", + "python-dotenv>=1.0.1", "seaborn==0.13.2", "shiny==0.9.0", "shinyswatch==0.6.1", ] +[tool.hatch.build.targets.wheel] +packages = ["src/shinylims"] + [build-system] requires = ["hatchling"] build-backend = "hatchling.build" diff --git a/src/shinylims/data_utils.py b/src/shinylims/data_utils.py index ab75287..334a64d 100644 --- a/src/shinylims/data_utils.py +++ b/src/shinylims/data_utils.py @@ -2,7 +2,8 @@ from pins import board_connect from shiny import ui import numpy as np - +from dotenv import load_dotenv +import os def transform_to_html(limsid): if pd.isna(limsid) or limsid == '': @@ -36,7 +37,11 @@ def custom_to_datetime(date_series): def fetch_pinned_data(pin_name): - board = board_connect() # ADD server_url and api_key here for local deployment! + # Load environment variables from .env file + load_dotenv() + + board = board_connect(api_key=os.getenv('POSIT_API_KEY'), server_url=os.getenv('POSIT_SERVER_URL')) + df = board.pin_read(pin_name) if 'Open Date' in df.columns: df['Open Date'] = pd.to_datetime(df['Open Date']) diff --git a/uv.lock b/uv.lock index 961d137..0047645 100644 --- a/uv.lock +++ b/uv.lock @@ -828,6 +828,15 @@ 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 }, ] +[[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 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/3e/b68c118422ec867fa7ab88444e1274aa40681c606d59ac27de5a5588f082/python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a", size = 19863 }, +] + [[package]] name = "python-multipart" version = "0.0.12" @@ -951,12 +960,13 @@ wheels = [ [[package]] name = "shinylims-uv" version = "0.1.0" -source = { virtual = "." } +source = { editable = "." } dependencies = [ { name = "faicons" }, { name = "itables" }, { name = "pandas" }, { name = "pins" }, + { name = "python-dotenv" }, { name = "seaborn" }, { name = "shiny" }, { name = "shinyswatch" }, @@ -968,6 +978,7 @@ requires-dist = [ { name = "itables", specifier = "==2.0.1" }, { name = "pandas", specifier = "==2.2.2" }, { name = "pins", specifier = "==0.8.5" }, + { name = "python-dotenv", specifier = ">=1.0.1" }, { name = "seaborn", specifier = "==0.13.2" }, { name = "shiny", specifier = "==0.9.0" }, { name = "shinyswatch", specifier = "==0.6.1" },