diff --git a/.github/workflows/publish.yml b/.github/workflows/_dep_publish.txt similarity index 64% rename from .github/workflows/publish.yml rename to .github/workflows/_dep_publish.txt index 401d35d..ec22599 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/_dep_publish.txt @@ -1,3 +1,4 @@ +<<<<<<< HEAD name: Package on: pull_request: @@ -62,3 +63,41 @@ jobs: - name: Upload if: github.event_name == 'release' run: twine upload -u __token__ -p ${{ secrets.PYPI_TOKEN }} dist/* +======= +name: Publish to PyPI + +on: + push: + branches: + - main # Triggers when push/merge to the 'main' branch + tags: + - 'v*' # Triggers whenever you push a tag starting with 'v' (e.g., v0.0.2) + +jobs: + build-and-publish: + name: Build and publish Python distribution + runs-on: ubuntu-latest + permissions: + # This permission is REQUIRED for Trusted Publishing + id-token: write + contents: read + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install build + + - name: Build binary wheel and source tarball + run: python -m build + + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 +>>>>>>> 8e30c8a94f451d004fdfc990f5ddf8019266eb2f diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..3cc3361 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,76 @@ +name: Release + +on: + push: + tags: + - 'v*' # Trigger only on version tags (e.g., v1.0.0) + workflow_dispatch: # allows manual triggering of the workflow from the GitHub Actions tab + +jobs: + # 1. TEST JOB: Runs Pytest + test: + uses: ./.github/workflows/test.yml # Reuse the test workflow defined in test.yml + + # 2. BUILD: Only runs if 'test' passes + build: + needs: test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # Required for versioning tools to see tags + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.8.12' # to match pyproject.toml exactly + + - name: Build + run: | + pip install build + python -m build . + + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: dist + path: dist/ + + # 3. GITHUB RELEASE: Creates the release on GitHub and attaches the build files + github-release: + needs: build + runs-on: ubuntu-latest + permissions: + contents: write # Needed to create releases and upload assets + steps: + - name: Download build artifacts + uses: actions/download-artifact@v4 + with: + name: dist + path: dist/ + + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + with: + files: dist/* + generate_release_notes: true # Automatically generate release notes based on commits since the last release + draft: false # Set to true if you want to create a draft release instead of publishing immediately + prerelease: false # Set to true if this is a pre-release (e.g., alpha, beta) + + # 4. PUBLISH: Only runs if 'build' passes + publish: + needs: [build, github-release] # Ensure both build and GitHub release steps succeed before publishing + runs-on: ubuntu-latest + # Optional: Use environment protection rule for manual approval before publishing + environment: pypi + permissions: + id-token: write # Needed for authentication with PyPI using GitHub Actions + steps: + - name: Download build artifacts + uses: actions/download-artifact@v4 + with: + name: dist + path: dist/ + + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..77dee0f --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,32 @@ +# This workflow will not run without Autoscript. +name: Test + +on: + push: + branches: + - '**' # Trigger on any branch push + pull_request: + branches: + - '**' # Trigger on any pull request + workflow_call: # CRITICAL: Allows this workflow to be called by others + +jobs: + test: + name: Run Subset of Tests + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.8.12' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -e ".[dev]" + + - name: Run Pytest + run: pytest tests/test_example.py diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..a70815c --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,108 @@ +# Contributing + +## Repository Download + +```sh +git clone git@github.com:sandialabs/pytribeam.git +``` + +## Virtual Environment + +From within the `pytribeam` directory, create a virtual environment. A virtual environment is a self-contained directory that contains a specific Python installation, along with additional packages. It allows users to create isolated environments for different projects. This ensures that dependencies and libraries do not interfere with each other. + +Create a virtual environment with either `pip` or `uv`. `pip` is already included with Python. `uv` must be [installed](https://docs.astral.sh/uv/getting-started/installation/). `uv` is 10 to 100 times faster than `pip`. + +```sh +cd pytribeam + +# (a) pip method, or +python -m venv .venv + +# (b) uv method +uv venv + +# for both methods (a) and (b) +source .venv/bin/activate # bash +source .venv/bin/activate.fish # fish shell +``` + +Install the code in editable form, + +```sh +# (a) pip method, or +pip install -e .[dev] + +# (b) uv method +uv pip install -e .[dev] +``` + +## CI/CD + +We separate the concerns of test, build, release, and publish throughout the `.github/workflows/` files: + +* [`test.yml`](/.github/workflows/test.yml) +* [`release.yml`](/.github/workflows/release.yml) + +These YAML files cover: + +* **Test (Verification)** + * **Purpose:** To ensure that the code is functional and hasn't introduced regressions (broken existing features). + * **What happens:** Automated tools like `pytest` run your unit and integration tests. It often includes "linting" (checking code style) and type-checking. + * **Key Outcome:** Confidence. If this stage fails, the process stops immediately, preventing broken code from ever reaching a user. +* **Build (Packaging)** + * **Purpose:** To transform your "human-readable" source code into "machine-installable" artifacts. + * **What happens:** Tools (like `python -m build`) bundle your code into standard formats, such as a Wheel (`.whl`) or a Source Distribution (`.tar.gz`). + * **Key Outcome:** Portability. You now have a single file (an "artifact") that contains everything needed to install your library on any compatible system. +* **Release (Documentation & Tagging)** + * **Purpose:** To create an official "point-in-time" snapshot of the project for project management and users. + * **What happens:** A permanent Git tag (like v1.0.0) is assigned to a specific commit. A GitHub Release page is generated with a Changelog (i.e., What's New?) and the build artifacts are attached to it as "Release Assets." + * **Key Outcome:** Traceability. It provides a clear history of the project's evolution and a stable place for users to download specific versions. +* **Publish (Distribution)** + * **Purpose:** To make the software easily available to the global ecosystem. + * **What happens:** The built artifacts are uploaded to a package registry, such as PyPI (the Python Package Index). + * **Key Outcome:** Accessibility. Once published, anyone in the world can install your software using a simple command like `pip install pytribeam`. + +Implementation details: + +* The reuse of `test.yml` via a `workflow_call` ensures that test logic is not duplicated. +* **Dependency Chain:** `build` waits for `test`, and publish waits for both `build` and `github-release`. +* **Artifact Integrity:** By building once and downing the artifacts in subsequent jobs, we ensure the exact same files go to GitHub and PyPI. +* **Security:** We use `id-token: write` for PyPI's Trusted Publishing, which is a modern and secure way to handle authentication. + +In `release.yml` we have removed the manual `-p ${{ secrets.PYPI_TOKEN }}`. The industry standard is now [**Trusted Publishing**](https://docs.pypi.org/trusted-publishers/). You configure this in your PyPI project settings once, and GitHub Actions authenticates securely without you needing to store and rotate secrets. + +To configure Trusted Publishing, you tell PyPI, "Trust any code from this specific GitHub repository and workflow." This removes the need to mange long-lived API tokens or passwords in your secrets. + +Step: + +* Log into your [PyPI](https://pypi.org) account +* Go your project's **Manage** page (or your accounts **Publishing** settings if you are setting it up for the first time.) +* Look for the **Publishing**tab +* Click **Add new publisher** +* Select **GitHub** as the source +* Enter the following details: + * Owner: sandialabs + * Repository name: pytribeam + * Workflow name: `release.yml` (This must match your filename in your `.github/workflows/` directory)) + * Environment name: You can leave this blank or name it `pypi` (if you use it in your YAML). We used `pypi`. + * Click the **Add** button + +To create a release: + +* Merge the `dev` branch into the `main` branch. +* On the `main` branch, `git tag` and push to `main`, e.g., + +```sh +# Ensure you are on the main branch +git checkout main +git pull + +# View existing tags, if any +git tag + +# Create the new tag, e.g., +git tag -a v1.0.0 -m "Release version 1.0.0" + +# On the main branch, push the tag to GitHub +git push origin v1.0.0 +``` diff --git a/README.md b/README.md index 3f776d8..b572e00 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,14 @@ [lint_badge]: https://sandialabs.github.io/pytribeam/badges/lint.svg [version_badge]: https://sandialabs.github.io/pytribeam/badges/version.svg +## Development work + +See release target [here](https://github.com/sandialabs/pytribeam/blob/dev/docs/developers/release_plan.md). + +## Development work + +See release target [here](https://github.com/sandialabs/pytribeam/blob/dev/docs/developers/release_plan.md). + ## Getting Started Installation instructions and more can be found in the [User Guide](https://sandialabs.github.io/pytribeam/docs/userguide/book/index.html). diff --git a/docs/developers/release_plan.md b/docs/developers/release_plan.md new file mode 100644 index 0000000..ed79c71 --- /dev/null +++ b/docs/developers/release_plan.md @@ -0,0 +1,80 @@ +# Release targets for `v1.0.0` + +**~~Strikethrough~~ compeleted tasks, please** + +## User support +**Andrew** +- adjust install/pyproject.toml to support Autoscript 4.10 python version 3.?? + +## infrastructure +**Andrew** +- setup virtual machines on workstation + - one machine for each autoscript version + +## deployment +**Chad** +- create ci/cd workflows to automate everything possible, including: + - userguide (mdbook) + - api docs (pdoc) + - release, versioning, publish to pypi, update gh-pages branch + +**James** +- figure out if we can install pytribeam in .venv +- create python-->bash script to automate things locally including: + - testing + - auto add location of root directory to the .coveragerc? + - linting + +## test improvements + +**Andrew** +- Stop test suite on hard ware if test fails + - insert/rectract EBSD is not self contained if it fails +- check for test independence +- order tests in increasing complexity +- some tests won't work on some systems (CBS stage restrictions) +- need decorators for: + - offline machines + - machines with lasers + - machines with CBS stage restriction locks (only the windows 7 Helios) + - machine that doesn't fit into above (a fib with newer XtUI software) +- Some kind of cleanup/setup procedure before/after hardware tests: + - lock/unlock laser objective + - move laser objective to safe position + - stage start pos/end pos + - beam voltages on/off + - all detectors retracted +- Track all machine types in constants +- setup environment for different AS version +- multi-AS version support: can we require 100% code coverage on specific functions? + - can require code coverage amounts on files + +**James** +- GUI tests + +## State Recorder +**James** +- Adapter functions for pytribeam? + +## Version support up to 4.11 +**Andrew** +- deal with breaking change on detector insertable issue + - propagate enum to other functions that call this + + + +## Features (near- and long-term) +- multi-quad images + - insert everything before ACB +- custom autofocus/autostig +- record beam shifts +- state recorder update +- EDAX API testing +- Bruker API support +- Oxford API support +- FIB serial sectioning + +## List of API requests + +- Multi quad imaging at custom resolution +- FIB shutter support diff --git a/pyproject.toml b/pyproject.toml index 4b559b7..85e141f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,49 +1,49 @@ [build-system] -requires = ["setuptools>=61.0", "wheel"] -build-backend = "setuptools.build_meta" +requires = ["hatchling", "hatch-vcs"] +build-backend = "hatchling.build" [project] name = "pytribeam" +# version = "0.0.2" +dynamic = ["version"] # Removed static version, now determined by VCS +description = "automated data collection on TriBeam tools" +requires-python = "==3.8.12" +readme = "README.md" authors = [ { name="Andrew Polonsky", email="apolon@sandia.gov" }, { name="Chad Hovey", email="chovey@sandia.gov" }, { name="James Lamb", email="jdlamb@sandia.gov" }, + { name="James Lamb", email="jdlamb@sandia.gov" }, ] -description = "automated data collection on TriBeam tools" -readme = "README.md" -requires-python = "==3.8.12" + dependencies = [ - "black", - "pytest", - "pytest-cov", "schema", ] -version = '0.0.2' [project.optional-dependencies] dev = [ - "black", + # "black", # now handled by ruff #"docstr-coverage", #"docutils<0.18,>=0.14", - "flake8", + # "flake8", # now handled by ruff #"jinja2", #"nbsphinx", #"pdbp", - 'pycodestyle', + # "pycodestyle", # now handled by ruff + # "h5py", + # "numpy", + "pdoc", "pytest==8.3.3", "pytest-cov==5.0.0", - "pdoc", ] -[docstr-coverage] -ignore = [ - "tests/", - "src/pytribeam/GUI/", -] - -[project.urls] -documentation = "https://sandialabs.github.io/pytribeam/docs/userguide/book/index.html" -repository = "https://github.com/sandialabs/pytribeam" +# pdoc configuration +# we only build pdoc on the source code, so we can ignore the tests and GUI for doc coverage +# [docstr-coverage] +# ignore = [ +# "tests/", +# "src/pytribeam/GUI/", +# ] # CLI Entry Points # https://setuptools.pypa.io/en/latest/userguide/entry_point.html @@ -53,3 +53,25 @@ pytribeam_info ="pytribeam.command_line:module_info" pytribeam_gui="pytribeam.command_line:launch_gui" pytribeam_exp="pytribeam.command_line:run_experiment" +[project.urls] +documentation = "https://sandialabs.github.io/pytribeam/docs/userguide/book/index.html" +repository = "https://github.com/sandialabs/pytribeam" + +[tool.hatch.version] +source = "vcs" + +[tool.hatch.version.raw-options] +local_scheme = "no-local-version" # Ensures clean versions like 0.0.9 instead of 0.0.9+g1234567 + +[tool.hatch.build.targets.wheel] +packages = ["src/pytribeam"] + +# [tool.ruff] +# line-length = 88 +# target-version = ["py38"] + +[tool.pytest.ini_options] +python_files = ["test_*.py", "*_test.py"] +testpaths = ["tests"] +pythonpath = ["src"] +addopts = "--cov=src/pytribeam --cov-report=xml --cov-report=html" diff --git a/tests/test_example.py b/tests/test_example.py new file mode 100644 index 0000000..3bb91e1 --- /dev/null +++ b/tests/test_example.py @@ -0,0 +1,7 @@ +"""This module is a simply placeholder to demonstrate that test can be +triggered from CI/CD workflows. It does not contain any actual tests.""" + + +def test_placeholder(): + """This is a placeholder test that always passes.""" + assert True