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.tomlwith a manual version again. - The
githistory stays clean since it avoids the "Bumping version to X" commits clutting the log. - Accuracy: The
.whlfile 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).
- Owner:
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 }}