# Manual release practice

These steps are instructions for William Friend. Repository intentionally contains no publishing automation or credentials. Run remote commands yourself only after reviewing every diff and verifying distribution name `abuseipdb-python-sdk` is still available on each index.

## A. Local verification

```bash
git status --short
python -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install -e ".[dev]"
ruff check .
ruff format --check .
pytest --cov
mypy src
rm -rf dist build
python -m build
twine check dist/*
```

Inspect names and contents:

```bash
ls -lh dist/
python -m zipfile -l dist/abuseipdb_python_sdk-0.1.0-py3-none-any.whl
tar -tzf dist/abuseipdb_python_sdk-0.1.0.tar.gz
```

Wheel is ready-to-install built distribution. Source distribution (`.tar.gz`) contains source and build metadata so an installer can build when no suitable wheel exists.

## B. Clean wheel installation

```bash
python -m venv /tmp/abuseipdb-sdk-smoke
/tmp/abuseipdb-sdk-smoke/bin/python -m pip install --upgrade pip
/tmp/abuseipdb-sdk-smoke/bin/python -m pip install dist/abuseipdb_python_sdk-0.1.0-py3-none-any.whl
cd /tmp
/tmp/abuseipdb-sdk-smoke/bin/python -c "from abuseipdb import AbuseIPDB, AbuseCategory; c = AbuseIPDB(api_key='smoke'); print(AbuseIPDB, AbuseCategory.SSH, c); c.close()"
```

## C. Git release

Current checkout is already a Git repository. Create or rename personal GitHub repository to
`William0Friend/abuseipdb-python`, review local changes, then run:

```bash
git add .
git status
git commit -m "feat: release unofficial AbuseIPDB Python SDK 0.1.0"
git branch -M main
git remote set-url origin git@github.com:William0Friend/abuseipdb-python.git
git push -u origin main
git tag -a v0.1.0 -m "abuseipdb-python-sdk 0.1.0"
git push origin v0.1.0
```

Create personal GitHub repository and GitHub release manually. Select tag `v0.1.0`, title it `abuseipdb-python-sdk 0.1.0`, paste changelog notes, and optionally attach wheel/sdist. Do not call project official.

## D. TestPyPI

TestPyPI and PyPI accounts are separate. Register and verify email at `https://test.pypi.org/account/register/`. Enable 2FA, create account-scoped API token for first project upload, copy it once into password manager, and never commit it.

```bash
python -m pip install --upgrade twine
TWINE_USERNAME=__token__ TWINE_PASSWORD='paste-TestPyPI-token-here' \
  python -m twine upload --repository testpypi dist/*
```

Prefer entering token interactively to avoid shell history:

```bash
python -m twine upload --repository testpypi dist/*
```

Use username `__token__`; paste full `pypi-...` token as password. Test install with PyPI fallback because TestPyPI may not contain `httpx` dependencies:

```bash
python -m venv /tmp/abuseipdb-testpypi
/tmp/abuseipdb-testpypi/bin/python -m pip install \
  --index-url https://test.pypi.org/simple/ \
  --extra-index-url https://pypi.org/simple/ \
  abuseipdb-python-sdk==0.1.0
cd /tmp
/tmp/abuseipdb-testpypi/bin/python -c "from abuseipdb import AbuseIPDB; print(AbuseIPDB)"
```

## E. Real PyPI

Register/verify at `https://pypi.org/account/register/`, enable 2FA, and create API token. First upload may need account-scoped token; after project exists, replace it with project-scoped token.

```bash
python -m twine upload dist/*
```

Use username `__token__` and full token as password. PyPI files and versions are immutable: `0.1.0` cannot be overwritten. For bad release, fix code, set version and changelog to `0.1.1`, rebuild from clean `dist/`, verify again, commit/tag `v0.1.1`, and upload new artifacts.

## F. Public verification

```bash
python -m venv /tmp/abuseipdb-pypi
/tmp/abuseipdb-pypi/bin/python -m pip install abuseipdb-python-sdk==0.1.0
cd /tmp
/tmp/abuseipdb-pypi/bin/python -c "import abuseipdb; print(abuseipdb.__version__)"
```

## G. Trusted Publishing later

PyPI recommends Trusted Publishing for supported CI. It exchanges GitHub Actions OIDC identity for short-lived credentials, avoiding stored long-lived tokens. Later create a dedicated manually triggered/tag-gated `release.yml`, protect a `pypi` GitHub environment, grant `id-token: write` only to publish job, register exact repository/workflow/environment on PyPI, and use `pypa/gh-action-pypi-publish@release/v1`. Keep first release manual for this exercise.

Authoritative guidance: [PyPA TestPyPI guide](https://packaging.python.org/en/latest/guides/using-testpypi/) and [PyPI Trusted Publishers](https://docs.pypi.org/trusted-publishers/).
