diff --git a/.gitignore b/.gitignore index 04ae1b2..fb39360 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ __pycache__/ dist/ build/ *.egg-info/ +.pypirc diff --git a/.pypirc.example b/.pypirc.example new file mode 100644 index 0000000..3ee100c --- /dev/null +++ b/.pypirc.example @@ -0,0 +1,22 @@ +[distutils] +index-servers = + forgejo + +[forgejo] +# Forgejo PyPI Repository Configuration +# Repository: https://your-forgejo-instance.com/api/packages/{username}/pypi +# Documentation: https://docs.forgejo.org/en/latest/usage/packages/pypi/ +# +# To use this file: +# 1. Replace YOUR_FORGEJO_INSTANCE with your Forgejo URL +# 2. Replace YOUR_USERNAME with your Forgejo username +# 3. Replace YOUR_ACCESS_TOKEN with your Forgejo personal access token +# (Generate at: https://your-forgejo-instance.com/user/settings/applications) +# 4. Keep this file secure (credentials inside!) +# +# WARNING: This file contains sensitive credentials. +# Add to .gitignore to prevent accidental commits! + +repository = https://git.him-tools.de/api/packages/HIM-public/pypi/ +username = __token__ +password = YOUR_ACCESS_TOKEN diff --git a/README.md b/README.md index b3c392b..b86097d 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,20 @@ A Python client for the Booklooker REST API designed for middleware and connecto pip install -e .[dev,webhooks] ``` +## Packaging and publishing + +Build a source distribution and wheel: + +```bash +python build_dist.py +``` + +Upload the artifacts to Forgejo PyPI after creating your local `.pypirc` from `.pypirc.example`: + +```powershell +.\upload_dist_to_forgejo_pypi.ps1 -Build +``` + ## Quick start ```python diff --git a/build_dist.py b/build_dist.py new file mode 100644 index 0000000..62ec250 --- /dev/null +++ b/build_dist.py @@ -0,0 +1,60 @@ +"""Build wheel and source distribution for publishing.""" + +from __future__ import annotations + +import shutil +import subprocess +import sys +from pathlib import Path + +PROJECT_ROOT = Path(__file__).resolve().parent +DIST_DIR = PROJECT_ROOT / "dist" +BUILD_DIR = PROJECT_ROOT / "build" + + +def run(cmd: list[str]) -> None: + print(f"[+] Running: {' '.join(cmd)}") + result = subprocess.run(cmd, cwd=PROJECT_ROOT) + if result.returncode != 0: + raise SystemExit(result.returncode) + + +def ensure_package(package: str) -> None: + result = subprocess.run( + [sys.executable, "-m", "pip", "show", package], + cwd=PROJECT_ROOT, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + if result.returncode != 0: + print(f"[+] Installing missing dependency: {package}") + run([sys.executable, "-m", "pip", "install", package]) + + +def main() -> None: + print() + print("=" * 72) + print("Booklooker Client - Build Python Distribution") + print("=" * 72) + print() + + ensure_package("build") + + if DIST_DIR.exists(): + shutil.rmtree(DIST_DIR) + if BUILD_DIR.exists(): + shutil.rmtree(BUILD_DIR) + + run([sys.executable, "-m", "build"]) + + files = sorted(path.name for path in DIST_DIR.glob("*")) + print() + print("Built artifacts:") + for name in files: + print(f" - {name}") + print() + print("Build completed successfully.") + + +if __name__ == "__main__": + main() diff --git a/pyproject.toml b/pyproject.toml index a782471..e0d0789 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,7 +24,9 @@ webhooks = [ ] dev = [ "pytest>=8.3.3", - "pytest-asyncio>=0.24.0" + "pytest-asyncio>=0.24.0", + "build>=1.2.2", + "twine>=5.1.1" ] [tool.setuptools.package-dir] diff --git a/upload_dist_to_forgejo_pypi.ps1 b/upload_dist_to_forgejo_pypi.ps1 new file mode 100644 index 0000000..e752704 --- /dev/null +++ b/upload_dist_to_forgejo_pypi.ps1 @@ -0,0 +1,118 @@ +# Upload Python distribution artifacts to Forgejo PyPI +# +# Prerequisites: +# 1. Create .pypirc in the project root +# 2. Copy from .pypirc.example and add your Forgejo credentials +# +# Usage: +# .\upload_dist_to_forgejo_pypi.ps1 +# .\upload_dist_to_forgejo_pypi.ps1 -Build + +param( + [switch]$Build = $false, + [switch]$Help = $false +) + +if ($Help) { + Write-Host @" +Upload distribution artifacts to Forgejo PyPI for Booklooker Client + +USAGE: + .\upload_dist_to_forgejo_pypi.ps1 [-Build] + +OPTIONS: + -Build Force rebuild the package before uploading + -Help Display this help message + +SETUP: + 1. Create .pypirc in the project root + 2. Copy from .pypirc.example as template + 3. Add your Forgejo repository URL and credentials: + [distutils] + index-servers = forgejo + + [forgejo] + repository = https://your-forgejo-instance.com/api/packages/YOUR_USERNAME/pypi + username = __token__ + password = YOUR_ACCESS_TOKEN + +EXAMPLES: + .\upload_dist_to_forgejo_pypi.ps1 # Upload existing dist artifacts + .\upload_dist_to_forgejo_pypi.ps1 -Build # Rebuild and upload +"@ + exit 0 +} + +$projectRoot = Split-Path -Parent $MyInvocation.MyCommand.Path +$pypiRcSrc = Join-Path $projectRoot ".pypirc" +$pypiRcDest = Join-Path $env:USERPROFILE ".pypirc" +$activateScript = Join-Path $projectRoot ".venv\Scripts\Activate.ps1" + +Write-Host "" +Write-Host "========================================================================" +Write-Host "Booklooker Client - Upload to Forgejo PyPI" +Write-Host "========================================================================" +Write-Host "" + +if (!(Test-Path $pypiRcSrc)) { + Write-Host "[!] .pypirc not found in the project root." -ForegroundColor Red + Write-Host "" + Write-Host "Setup instructions:" + Write-Host " 1. Copy .pypirc.example to .pypirc" + Write-Host " 2. Edit .pypirc with your Forgejo credentials" + Write-Host " 3. Run this script again" + Write-Host "" + exit 1 +} + +if (!(Test-Path $activateScript)) { + Write-Host "[!] Virtual environment activation script not found: $activateScript" -ForegroundColor Red + exit 1 +} + +Write-Host "Configuring credentials..." +Copy-Item -Path $pypiRcSrc -Destination $pypiRcDest -Force + +try { + & $activateScript + + python -m pip show twine *> $null + if ($LASTEXITCODE -ne 0) { + Write-Host "Installing twine..." + python -m pip install twine + if ($LASTEXITCODE -ne 0) { + exit $LASTEXITCODE + } + } + + Push-Location $projectRoot + + $distArtifacts = Get-ChildItem -Path "dist" -File -ErrorAction SilentlyContinue + if ($Build -or !$distArtifacts) { + if ($Build) { + Write-Host "Rebuilding distribution artifacts..." + } else { + Write-Host "No distribution artifacts found. Building package..." + } + + python .\build_dist.py + if ($LASTEXITCODE -ne 0) { + exit $LASTEXITCODE + } + } + + Write-Host "" + Write-Host "========================================================================" + Write-Host "Uploading distribution artifacts to Forgejo PyPI..." + Write-Host "========================================================================" + Write-Host "" + + twine upload -r forgejo dist/* + exit $LASTEXITCODE +} +finally { + Pop-Location -ErrorAction SilentlyContinue + if (Test-Path $pypiRcDest) { + Remove-Item $pypiRcDest -Force -ErrorAction SilentlyContinue + } +}