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:
versionis 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>/json→404means 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:
- Reads the current version from
pyproject.toml - Bumps it (
patchby default) in bothpyproject.tomland__init__.py - Builds + validates with twine
- Uploads with credentials from
.secret/pypi.yaml(Step 7) - On any failure: rolls the version back — a broken publish never leaves
your files half-bumped, and
make publishcan 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.org — real |
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
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8fa972f1b5dc150c615435375acacbcd936518637f7c2771829a5380ada47854
|
|
| MD5 |
4dae5167ca12ce65b94743daa68aad7e
|
|
| BLAKE2b-256 |
96a38fede2955cc1fe0850b4788ce81d8c6227e70ff75c2351302b931117ceb1
|
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
48a958d3af30e325df9f48b818d70d28258dc09da9db5b4565d58bc35d741677
|
|
| MD5 |
340ade058919e9de28da7899e4a60c24
|
|
| BLAKE2b-256 |
d38f474b59d32e72b6d1615772a9b7ed5f4c8f7d0df4de8b21cdf8711a55a8f5
|