Skip to main content

soigia-init

An experimental pip package — built to learn the publish workflow end-to-end (scaffold → test → build → check → upload → install). Zero dependencies, published to PyPI for real as a proof of workflow.

Install

pip install soigia-init

Usage

soigia-init --name SoiGia
# Hello, SoiGia! 👋 from soigia-init

Or from Python:

import soigia_init
from soigia_init.cli import hello

print(soigia_init.__version__)   # prints the current version (synced with pyproject.toml)
print(hello("Soi Gia"))

📖 How to Build a Pip Package From Zero — Step by Step

Everything below is the exact workflow this package went through. Follow the steps with any new package and it will end up on PyPI, published with one command and no manual token typing.

📁 Layout of a package (this repo is the template)

pip-soigia-init/                  # folder of the package
├── pyproject.toml                # package metadata + build config
├── Makefile                      # one-command workflow: test/build/publish
├── README.md                     # this guide (rendered on PyPI page too)
├── LICENSE                       # BSD-3-Clause
├── .gitignore                    # keep build artifacts & secrets out of git
├── soigia_init/                  # source code — importable package
│   ├── __init__.py               # __version__ lives here
│   └── cli.py                    # CLI entry point (optional)
├── scripts/
│   ├── release.py                # bump → build → check → upload (auto)
│   └── bump_version.py           # version bumping helper
└── tests/
    └── test_smoke.py             # pytest smoke tests

Step 1 — Create the folder, learn the name rules

mkdir pip-soigia-mcp && cd pip-soigia-mcp
mkdir soigia_mcp tests scripts

Three different names, easy to mix up:

What Example Rule
Folder pip-soigia-mcp dash - — one folder per package
Import name soigia_mcp underscore _ — what Python imports
PyPI name soigia-mcp what pip install uses — must be unique on PyPI

Step 2 — pyproject.toml (the heart of the package)

[build-system]
requires = ["setuptools>=61", "wheel"]
build-backend = "setuptools.build_meta"

[project]
name = "soigia-mcp"                 # PyPI name — check it's free first!
version = "0.1.0"                   # starts at 0.1.0
description = "One-line description of the package"
readme = { file = "README.md", content-type = "text/markdown" }
license = "BSD-3-Clause"
license-files = ["LICENSE"]
authors = [{ name = "Soi Gia", email = "sojgja@gmail.com" }]
keywords = ["soigia", "mcp"]
requires-python = ">=3.8"
dependencies = []                   # e.g. ["requests>=2.31", "typer>=0.9"]

[project.scripts]
soigia-mcp = "soigia_mcp.cli:main"  # (optional) installs a CLI command

[tool.setuptools.packages.find]
include = ["soigia_mcp", "soigia_mcp.*"]

[tool.pytest.ini_options]
testpaths = ["tests"]

Key points:

  • version is static here — keep it in sync with __init__.py (the release script does this automatically — Step 6).
  • [project.scripts] turns your Python function into a shell command.
  • Check the PyPI name is free first: curl https://pypi.org/pypi/<name>/json404 means the name is available.

Step 3 — Source code

# soigia_mcp/__init__.py
__version__ = "0.1.0"
# soigia_mcp/cli.py
import argparse


def hello(name: str = "world") -> str:
    return f"Hello, {name}! 👋 from soigia-mcp"


def main() -> None:
    parser = argparse.ArgumentParser(prog="soigia-mcp")
    parser.add_argument("--name", default="world")
    args = parser.parse_args()
    print(hello(args.name))

Step 4 — Tests (run before every publish)

# tests/test_smoke.py
import soigia_mcp


def test_version():
    assert soigia_mcp.__version__ == "0.1.0"


def test_hello():
    assert "soigia-mcp" in soigia_mcp.cli.hello("Soi Gia")

Install dev tools once, then run:

python -m pip install -e . pytest build twine
make test

Step 5 — Makefile (one command per job)

Copy the Makefile from this package — the targets are generic:

Target What it does
make test run pytest
make build build dist/*.whl + dist/*.tar.gz
make check twine check (metadata validation)
make bump-patch/minor/major bump version in pyproject.toml + __init__.py
make publish clean → test → bump patch → build → check → upload
make publish-minor/major same, different version bump
make publish-test upload to TestPyPI instead of PyPI
make clean remove build artifacts

Step 6 — Release scripts (the automation core)

Copy from this package, then change one line (the package path):

cp ../pip-soigia-init/Makefile .
mkdir scripts
cp ../pip-soigia-init/scripts/release.py scripts/
cp ../pip-soigia-init/scripts/bump_version.py scripts/
# edit scripts/release.py: init_path = root / "soigia_mcp" / "__init__.py"
# edit scripts/bump_version.py: same package name fix

What release.py does automatically:

  1. Reads the current version from pyproject.toml
  2. Bumps it (patch by default) in both pyproject.toml and __init__.py
  3. Builds + validates with twine
  4. Uploads with credentials from .secret/pypi.yaml (Step 7)
  5. On any failure: rolls the version back — a broken publish never leaves your files half-bumped, and make publish can be re-run safely.

Step 7 — 🔐 Credentials: .secret/pypi.yaml (READ THIS)

Credentials are shared by every package in the repo — they live once at the repo root, not inside each package:

soigia-sdk/
├── .secret/
│   └── pypi.yaml          ← token pypi.org (real) + token testpypi (empty)
├── pip-soigia/
├── pip-soigia-init/       ← release.py finds .secret at repo root (..)
└── ...

release.py finds it automatically: the script is at <package>/scripts/, so it looks at <package>/../.secret/pypi.yaml. Do not copy .secret/ into your package folder.

pypi:                        # used when repository = soigia / pypi (default)
  repository: https://upload.pypi.org/legacy/
  username: __token__        # fixed — do not change
  password: pypi-...         # token from https://pypi.org/manage/account/token/

testpypi:                    # used when repository = testpypi
  repository: https://test.pypi.org/legacy/
  username: __token__
  password: ""               # token from https://test.pypi.org/manage/account/token/

⚠️ Security rules — non-negotiable

Rule Why
.secret/ is in .gitignore — verify with git check-ignore .secret/pypi.yaml the token must never reach GitHub
Never print a token into a chat, log, or screenshot it stays in the conversation history forever
One token per account: pypi.org ≠ test.pypi.org a 403 means you used the wrong kind
Token leaked? Revoke it at the token page, generate a new one, paste it back revoking kills the leaked one instantly
git log --all -S pypi-... shows nothing confirms no token ever entered history

Step 8 — Publish (one command, no typing)

make publish          # 0.1.0 → 0.1.1 on PyPI (real)
make publish-test     # uploads to TestPyPI instead (needs testpypi token)
make publish-minor    # 0.1.0 → 0.2.0
make publish-major    # 0.1.0 → 1.0.0

Which credentials are used:

--repository (set by Makefile) Section read Uploads to
soigia (default) / pypi pypi: https://pypi.orgreal
testpypi testpypi: https://test.pypi.org — trial

Success looks like:

Uploading soigia_mcp-0.1.1-py3-none-any.whl ...
View at: https://pypi.org/project/soigia-mcp/0.1.1/

Step 9 — Verify the published package

pip install soigia-mcp       # install from PyPI (not from source!)
soigia-mcp --help            # CLI works
python -c "import soigia_mcp; print(soigia_mcp.__version__)"

Step 10 — Next releases

Always run make publish — never bump versions by hand. Version bumps are recorded in pyproject.toml and __init__.py by the script, so the installed __version__ always matches the release.


🛠️ Everyday Command Summary

make test          # run tests
make build         # build artifacts
make check         # validate artifacts
make publish       # release patch on PyPI (recommended everyday flow)
make publish-minor # release minor
make publish-major # release major
make clean         # remove build/

🔒 Security Checklist (quick)

  • git check-ignore .secret/pypi.yaml → prints the file (ignored)
  • git status --short → no .secret/ listed
  • git log --all -S pypi- → empty output
  • Token never typed into chat/logs/screenshots
  • Leaked token → revoked + replaced in .secret/pypi.yaml

📄 License

BSD-3-Clause

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

soigia_init-0.1.2.tar.gz (8.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

soigia_init-0.1.2-py3-none-any.whl (7.1 kB view details)

Uploaded Python 3

File details

Details for the file soigia_init-0.1.2.tar.gz.

File metadata

  • Download URL: soigia_init-0.1.2.tar.gz
  • Upload date:
  • Size: 8.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.3

File hashes

Hashes for soigia_init-0.1.2.tar.gz
Algorithm Hash digest
SHA256 8fa972f1b5dc150c615435375acacbcd936518637f7c2771829a5380ada47854
MD5 4dae5167ca12ce65b94743daa68aad7e
BLAKE2b-256 96a38fede2955cc1fe0850b4788ce81d8c6227e70ff75c2351302b931117ceb1

See more details on using hashes here.

File details

Details for the file soigia_init-0.1.2-py3-none-any.whl.

File metadata

  • Download URL: soigia_init-0.1.2-py3-none-any.whl
  • Upload date:
  • Size: 7.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.3

File hashes

Hashes for soigia_init-0.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 48a958d3af30e325df9f48b818d70d28258dc09da9db5b4565d58bc35d741677
MD5 340ade058919e9de28da7899e4a60c24
BLAKE2b-256 d38f474b59d32e72b6d1615772a9b7ed5f4c8f7d0df4de8b21cdf8711a55a8f5

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.1.2 This release

2 files

0.1.1

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page