Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Development

Client Configuration

pip install figio

Developer Configuration

Since figio is a package within the autotwin framework, we suggest cloning the figio repo to the autotwin folder:

cd ~/autotwin
git clone git@github.com:autotwin/figio.git
cd ~/autotwin/figio

From the ~/autotwin/figio folder, create the 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. uv is 10-100x faster than pip.

# pip method 
python -m venv .venv

# uv method
uv venv

# both methods
source .venv/bin/activate       # bash
source .venv/bin/activate.fish  # fish shell

Install the code in editable form,

# pip method
pip install -e .[dev]

# uv method
uv pip install -e .[dev]

Continuous Integration

We use GitHub Actions to ensure code quality. On every push and pull request to any branch, we run a test matrix across all supported Python versions (3.10 through 3.14).

Create .github/workflows/test.yml:

name: Test

on:
  push:
    branches:
      - '**'

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: ['3.10', '3.11', '3.12', '3.13', '3.14']
      fail-fast: false

    steps:
    - uses: actions/checkout@v4
    
    - name: Set up Python ${{ matrix.python-version }}
      uses: actions/setup-python@v5
      with:
        python-version: ${{ matrix.python-version }}
        allow-prereleases: true

    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        pip install .[dev]

    - name: Run tests
      run: |
        pytest -v

Manual Distribution

Even for manual builds, the version is automatically derived from the Git tags.

# Ensure you have a tag locally, otherwise the version will be 0.0.0
python -m build . --sdist  # source distribution
python -m build . --wheel
twine check dist/*

Automatic Distribution

On March 10, 2026, we refactored the pyproject.toml to implement dynamic versioning. We use hatchling (a modern build backend) along with the hatch-vcs plugin. The plugin reads the Git tags automatically.

[build-system]
requires = ["hatchling", "hatch-vcs"]
build-backend = "hatchling.build"

[project]
name = "figio"
dynamic = ["version"]  # Removed static version, now determined by VCS
description = "A declarative method for plotting (x, y) and histogram data"
# ... other metadata ...

[tool.hatch.version]
source = "vcs"  # Version Control System is the authority

[tool.hatch.build.targets.wheel]
packages = ["src/figio"]

Tagging

When you are ready to release, create an annotated tag. This signals to the CI/CD pipeline to build and publish.

# Create the tag
git tag -a v1.0.0 -m "Release version 1.0.0"

# Push the tag to GitHub
git push origin v1.0.0

GitHub Actions Workflow

Instead of a "bump" script (historical approach) that edits files and creates merge/pull requests, use the Release workflow. This workflow is triggered only when you push a tag. It builds the project and publishes it to PyPI.

Create .github/workflows/release.yml:

name: Release

on:
  push:
    tags:
      - 'v*'  # Trigger on version tags like v1.0.0

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0  # Fetch all history for versioning, needed for hatch-vcs to see tags

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.12'

      - name: Build
        run: |
          pip install build
          python -m build .

      - uses: actions/upload-artifact@v4
        with:
          name: dist
          path: dist/

  publish:
    needs: build
    runs-on: ubuntu-latest
    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

Why is this method better?

  • There is now a single source of truth. The version lives in the git tag.
  • You never have to update the pyproject.toml with a manual version again.
  • The git history stays clean since it avoids the "Bumping version to X" commits clutting the log.
  • Accuracy: The .whl file generated by the CI will exactly match the tag name.

Trusted Publishing

In release.yml we have removed the manual -p ${{ secrets.PYPI_FIGIO_TOKEN }}. The industry standard is now Trusted Publishing. You configure this in your PyPI project setting once, and GitHub Actions authenticates securely without you needing to store and rotate secrets.

To configure Trusted Publishing, you tell PyPI, "Trust any code that coes from this specific GitHub repository and workflow." This removes the need to manage long-lived API tokens or passwords in your secrets.

Steps:

  • Log into your PyPI account
  • Go to your project's Manage page (or your account's 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: autotwin
    • Repository name: figio
    • Workflow name: release.yml (This must match your filename in your .github/workflows/ directory)
    • Environment You can leave this blank or name it pypi (if you use it in your YAML).

The environment: pypi with GitHub Repo Settings > Environments > pypi and

Deprecated

The distribution steps will

  • tag the code state as a release version, with a semantic version number,
  • build the code as a wheel file, and
  • publish the wheel file as a release to GitHub.

Configure the pyproject.toml file to make the build system to be dynamic, which tells the packaging tool to look at Git, rather than a static string, for the build number.

Tag

The industry standard best practices approach to version control is to use the Git Tag as the version authority. So, instead of hard-coding a version number, you use a tool that detects the nearest Git tag and dynamically generates the version during the build process.

We will use semantic versioning convention: MAJOR.MINOR.PATCH.

View existing tags, if any:

git tag

Create a tag. Tags can be either lightweight or annotated. Annotated tags are recommended since they store tagger name, email, date, and message information. Create an annotated tag:

# example of an annotated tag
git tag -a v1.0.0 -m "Release version 1.0.0"

Push the tag to the repo

# example continued
git push origin v1.0.0

Verify the tag appears on the repo

Build

Ensure that setuptools and build are installed:

pip install setuptools build

Navigate to the project directory, where the pyproject.toml file is located, and create a wheel distribution.

# generates a .whl file in the dist directory
python -m build --wheel

Semantic Version Bump

Create .github/workflows/bump.yml as follows:

name: Bump
on:
  release:
    types: published
jobs:
  bump:
    runs-on: ubuntu-latest
    steps:
      - name: checkout
        uses: actions/checkout@v4
      - name: bump
        id: bump
        run: |
          # VERSION=$(cargo tree | grep automesh | cut -d " " -f 2 | cut -d "v" -f 2)
          VERSION=$(grep version pyproject.toml | cut -d '"' -f 2)
          MAJOR_MINOR=$(echo $VERSION | rev | cut -d "." -f 2- | rev)
          PATCH=$(echo $VERSION | rev | cut -d "." -f 1)
          BUMP=$(( $PATCH + 1))
          BUMPED_VERSION=$(echo $MAJOR_MINOR"."$BUMP)
          BUMP_BRANCH=$(echo "bump-$VERSION-to-$BUMPED_VERSION")
          echo "bump_branch=$BUMP_BRANCH" >> $GITHUB_OUTPUT
          sed -i "s/version = \"$VERSION\"/version = \"$BUMPED_VERSION\"/" pyproject.toml
          git config --global user.email "bump"
          git config --global user.name "bump"
          git add pyproject.toml
          git commit -m "Bumping version from $VERSION to $BUMPED_VERSION."
          git branch $BUMP_BRANCH
          git checkout $BUMP_BRANCH
          git push --set-upstream origin $BUMP_BRANCH
      - name: pr
        uses: rematocorp/open-pull-request-action@v1
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          from-branch: ${{ steps.bump.outputs.bump_branch }}
          to-branch: ${{ github.event.repository.default_branch }}
          repository-owner: autotwin
          repository: ${{ github.event.repository.name }}