Add build scripts and configuration for Elytra PIM Client

This commit is contained in:
claudi 2026-02-20 09:22:11 +01:00
parent 459838b2e6
commit b8a7fb903e
7 changed files with 940 additions and 2 deletions

377
BUILD.md Normal file
View file

@ -0,0 +1,377 @@
# Building and Distributing Elytra PIM Client
This guide explains how to build wheel distributions and upload them to PyPI or other package repositories.
## Project Version
Current version: **0.1.0** (Development Release)
The version is defined in `pyproject.toml` under `[project]` section.
## Build Tools
### Requirements
- Python 3.9 or higher
- pip with setuptools and wheel
### Build Dependencies
Install build requirements:
```bash
pip install -r build_requirements.txt
```
Or manually:
```bash
pip install "setuptools>=65.0" "wheel>=0.38.0" "build>=0.10.0"
```
## Building Wheels
### Option 1: Using Python Script (Recommended for All Platforms)
```bash
# Activate virtual environment first
.venv\Scripts\activate # Windows
source .venv/bin/activate # macOS/Linux
# Run the build script
python build_wheel.py
```
This script will:
- Clean previous build artifacts
- Build wheel (`.whl`) and source (`.tar.gz`) distributions
- Display the build results with file sizes
- Show installation instructions
### Option 2: Using PowerShell Script (Windows)
```powershell
# Activate virtual environment
.\.venv\Scripts\Activate.ps1
# Run the build script
.\build_wheel.ps1
# Clean and rebuild
.\build_wheel.ps1 -Clean
# Build and upload to PyPI
.\build_wheel.ps1 -Upload
```
### Option 3: Using Shell Script (macOS/Linux)
```bash
# Activate virtual environment
source .venv/bin/activate
# Run the build script
chmod +x build_wheel.sh # Make executable (first time only)
./build_wheel.sh
# Clean and rebuild
./build_wheel.sh --clean
# Build and upload to PyPI
./build_wheel.sh --upload
```
### Option 4: Using Modern Build Tool Directly
```bash
# Install build tool
pip install build
# Build wheel and sdist in one command
python -m build
# Build only wheel
python -m build --wheel
# Build only source distribution
python -m build --sdist
```
### Option 5: Using setuptools Directly
```bash
# Create wheel only
python setup.py bdist_wheel
# Create both wheel and source distribution
python setup.py sdist bdist_wheel
```
## Build Output
Distributions are created in the `dist/` directory:
```
dist/
├── elytra_pim_client-0.1.0-py3-none-any.whl # Wheel distribution
└── elytra_pim_client-0.1.0.tar.gz # Source distribution
```
### Understanding the Wheel Filename
`elytra_pim_client-0.1.0-py3-none-any.whl`
- `elytra_pim_client` - Package name
- `0.1.0` - Version
- `py3` - Python version (3.x only)
- `none` - No C extensions (pure Python)
- `any` - Platform independent
## Installation from Built Wheel
### From Local File
After building, install the wheel locally:
```bash
pip install dist/elytra_pim_client-0.1.0-py3-none-any.whl
```
### Editable Install During Development
During development, use editable install so changes are reflected immediately:
```bash
pip install -e .
```
Or with dev dependencies:
```bash
pip install -e ".[dev]"
```
## Uploading to PyPI
### Prerequisites
1. Create PyPI account at https://pypi.org/
2. Create PyPI token: https://pypi.org/manage/account/tokens/
3. Configure credentials
### Using twine
```bash
# Install twine
pip install twine
# Upload to PyPI (after building)
twine upload dist/*
# Or to TestPyPI for testing first
twine upload -r testpypi dist/*
```
### Authentication
**Option 1: Using .pypirc file**
Create `~/.pypirc`:
```ini
[distutils]
index-servers =
pypi
testpypi
[pypi]
repository = https://upload.pypi.org/legacy/
username = __token__
password = pypi_your_token_here
[testpypi]
repository = https://test.pypi.org/legacy/
username = __token__
password = pypi_your_test_token_here
```
**Option 2: Interactive prompt**
twine will prompt for username and password when uploading.
### Upload to Test PyPI First
Before uploading to production, test with TestPyPI:
```bash
twine upload -r testpypi dist/*
```
Then test installation:
```bash
pip install -i https://test.pypi.org/simple/ elytra-pim-client==0.1.0
```
## Versioning
### Version Format
Follow PEP 440 versioning:
```
Major.Minor.Patch (e.g., 0.1.0)
Major.Minor.Patch.preN (e.g., 0.1.0.pre1)
Major.Minor.Patch.postN (e.g., 0.1.0.post1)
Major.Minor.Patch.devN (e.g., 0.1.0.dev1)
```
### Updating Version
Edit `pyproject.toml`:
```toml
[project]
name = "elytra-pim-client"
version = "0.2.0" # Update version here
```
## Automatic Build Validation
Before building, scripts automatically:
1. Remove old build artifacts
2. Clean `dist/`, `build/`, and `.egg-info` directories
3. Verify build dependencies
4. Validate Python version compatibility
5. Check that distributions were created
## Continuous Integration
### GitHub Actions Example
```yaml
name: Build Distribution
on:
release:
types: [created]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: '3.11'
- run: pip install -r build_requirements.txt
- run: python build_wheel.py
- uses: actions/upload-artifact@v3
with:
name: dist
path: dist/
upload:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v3
with:
name: dist
path: dist/
- uses: pypa/gh-action-pypi-publish@release/v1
```
## Troubleshooting
### Build Fails with "No module named 'build'"
```bash
pip install build
python build_wheel.py
```
### "setuptools not found"
```bash
pip install setuptools>=65.0
```
### Permission Denied (Unix/Linux)
Make build script executable:
```bash
chmod +x build_wheel.sh
```
### "twine: command not found"
```bash
pip install twine
python -m twine upload dist/*
```
### Wheel not in dist/
Check that `pyproject.toml` exists and is valid:
```bash
python -m build --verbose
```
## File Structure
```
elytra_client/
├── build_wheel.py # Python build script (all platforms)
├── build_wheel.ps1 # PowerShell build script (Windows)
├── build_wheel.sh # Shell build script (Unix/Linux)
├── build_requirements.txt # Build dependencies
├── setup.py # Setup configuration (legacy compatibility)
├── pyproject.toml # Modern build configuration (PEP 517/518)
├── MANIFEST.in # (Optional) File inclusion rules
└── dist/ # Output directory for distributions
├── elytra_pim_client-0.1.0-py3-none-any.whl
└── elytra_pim_client-0.1.0.tar.gz
```
## Best Practices
1. **Always test before releasing**
```bash
twine upload -r testpypi dist/*
pip install -i https://test.pypi.org/simple/ elytra-pim-client==0.1.0
```
2. **Increment version for each release**
- Patch: Bug fixes (0.1.1)
- Minor: New features (0.2.0)
- Major: Breaking changes (1.0.0)
3. **Clean before rebuilding**
```bash
python build_wheel.py # Automatically cleans
# Or manually
rm -rf dist/ build/ *.egg-info/
```
4. **Keep dependencies minimal**
- Only required packages in `dependencies`
- Development tools in `[project.optional-dependencies]`
5. **Document changes**
- Update CHANGELOG.md (if present)
- Update version in pyproject.toml
- Create git tag for release
## Resources
- [Python Packaging Guide](https://packaging.python.org/)
- [PEP 517 - Build System Interface](https://www.python.org/dev/peps/pep-0517/)
- [PEP 518 - pyproject.toml](https://www.python.org/dev/peps/pep-0518/)
- [PEP 440 - Version Identification](https://www.python.org/dev/peps/pep-0440/)
- [setuptools Documentation](https://setuptools.pypa.io/)
- [twine Documentation](https://twine.readthedocs.io/)
- [PyPI Help](https://pypi.org/help/)

15
build_requirements.txt Normal file
View file

@ -0,0 +1,15 @@
# Build Requirements for Elytra PIM Client
# Core build tools
setuptools>=65.0
wheel>=0.38.0
# Modern build frontend (PEP 517)
build>=0.10.0
# Distribution upload
twine>=4.0.0
# Optional: for building documentation
# sphinx>=5.0.0
# sphinx-rtd-theme>=1.0.0

188
build_wheel.ps1 Normal file
View file

@ -0,0 +1,188 @@
# build_wheel.ps1
# Build wheel distribution for Elytra PIM Client (PowerShell)
#
# Usage:
# .\build_wheel.ps1
#
# Requires:
# - Python 3.9+
# - setuptools>=65.0
# - wheel
# - build (recommended)
param(
[switch]$Clean = $false,
[switch]$Upload = $false
)
# Define symbols as variables to avoid encoding issues
$symSuccess = "[OK]"
$symError = "[!]"
$symBullet = "[*]"
# Color functions for output
function Write-Success {
Write-Host "$symSuccess $args" -ForegroundColor Green
}
function Write-Error-Custom {
Write-Host "$symError $args" -ForegroundColor Red
}
function Write-Header {
Write-Host ("=" * 70)
Write-Host $args
Write-Host ("=" * 70)
}
function Write-SubHeader {
Write-Host ("-" * 70)
Write-Host $args
Write-Host ("-" * 70)
}
# Main script
$projectRoot = Split-Path -Parent $MyInvocation.MyCommand.Path
$distDir = Join-Path $projectRoot "dist"
$buildDir = Join-Path $projectRoot "build"
$eggInfoDir = Join-Path $projectRoot "elytra_pim_client.egg-info"
Write-Header "Elytra PIM Client - Wheel Builder (PowerShell)"
# Clean if requested
if ($Clean) {
Write-Host ""
Write-Host "Cleaning previous builds..."
@($distDir, $buildDir, $eggInfoDir) | ForEach-Object {
if (Test-Path $_) {
Remove-Item $_ -Recurse -Force -ErrorAction SilentlyContinue
Write-Success "Removed $_"
}
}
}
# Check if build directory exists from previous build
if (Test-Path $distDir) {
Write-Host ""
Write-Host "Cleaning dist directory..."
Remove-Item $distDir -Recurse -Force -ErrorAction SilentlyContinue
Write-Success "Removed old distributions"
}
# Install build requirements if needed
Write-Host ""
Write-Host "Checking build dependencies..."
$buildCheck = python -m pip list 2>$null | Select-String "build"
if (-not $buildCheck) {
Write-Host "Installing build tools..."
python -m pip install -q build setuptools wheel
if ($LASTEXITCODE -eq 0) {
Write-Success "Build tools installed"
} else {
Write-Error-Custom "Failed to install build tools"
exit 1
}
}
# Build
Write-SubHeader "Building distributions..."
Write-Host ""
Push-Location $projectRoot
try {
python -m build
if ($LASTEXITCODE -ne 0) {
Write-Error-Custom "Build failed"
exit 1
}
} finally {
Pop-Location
}
# Verify output
Write-SubHeader "Build Results"
Write-Host ""
if (!(Test-Path $distDir)) {
Write-Error-Custom "dist directory not created"
exit 1
}
$wheels = @(Get-ChildItem -Path $distDir -Filter "*.whl" -ErrorAction SilentlyContinue)
$sdists = @(Get-ChildItem -Path $distDir -Filter "*.tar.gz" -ErrorAction SilentlyContinue)
if ($wheels.Count -eq 0 -and $sdists.Count -eq 0) {
Write-Error-Custom "No distributions created"
exit 1
}
Write-Success "Build successful!"
Write-Host ""
if ($wheels.Count -gt 0) {
Write-Host "Wheels:"
$wheels | ForEach-Object {
$sizeMB = [Math]::Round($_.Length / 1MB, 2)
Write-Host " $symBullet $($_.Name) ($sizeMB MB)"
}
}
if ($sdists.Count -gt 0) {
Write-Host ""
Write-Host "Source Distributions:"
$sdists | ForEach-Object {
$sizeMB = [Math]::Round($_.Length / 1MB, 2)
Write-Host " $symBullet $($_.Name) ($sizeMB MB)"
}
}
Write-Host ""
Write-Success "Distributions saved to: $distDir"
# Installation instructions
Write-SubHeader "Installation Instructions"
Write-Host ""
if ($wheels.Count -gt 0) {
$wheel = $wheels[0]
Write-Host "Install the wheel locally:"
Write-Host " pip install `"$($wheel.FullName)`""
Write-Host ""
Write-Host "Or upload to PyPI:"
Write-Host " pip install twine"
Write-Host " twine upload dist/*"
Write-Host ""
}
# Optional upload
if ($Upload) {
Write-SubHeader "Uploading to PyPI..."
Write-Host ""
$twineCheck = python -m pip list 2>$null | Select-String "twine"
if (-not $twineCheck) {
Write-Host "Installing twine..."
python -m pip install -q twine
}
Write-Host "Running twine upload..."
Push-Location $projectRoot
try {
python -m twine upload dist/*
if ($LASTEXITCODE -eq 0) {
Write-Success "Upload complete!"
} else {
Write-Error-Custom "Upload failed"
exit 1
}
} finally {
Pop-Location
}
}
Write-Host ""
Write-Host "Done!"
Write-Host ""
exit 0

152
build_wheel.py Normal file
View file

@ -0,0 +1,152 @@
#!/usr/bin/env python
"""Build wheel distribution for Elytra PIM Client.
This script builds wheel and source distributions for the Elytra PIM Client package.
It requires setuptools and wheel to be installed.
Usage:
python build_wheel.py
Output:
- Wheels saved to dist/ directory
- Source distribution (sdist) also created for reference
"""
import shutil
import subprocess
import sys
from pathlib import Path
def main() -> int:
"""Build wheel distribution.
Returns:
Exit code (0 for success, 1 for failure)
"""
project_root = Path(__file__).parent
dist_dir = project_root / "dist"
print("=" * 70)
print("Elytra PIM Client - Wheel Builder")
print("=" * 70)
# Clean previous builds
print("\nCleaning previous builds...")
if dist_dir.exists():
try:
shutil.rmtree(dist_dir)
print(f"✓ Removed {dist_dir}")
except OSError as e:
print(f"✗ Failed to remove {dist_dir}: {e}")
return 1
build_dir = project_root / "build"
if build_dir.exists():
try:
shutil.rmtree(build_dir)
print(f"✓ Removed {build_dir}")
except OSError as e:
print(f"✗ Failed to remove {build_dir}: {e}")
return 1
egg_info_dir = project_root / "elytra_pim_client.egg-info"
if egg_info_dir.exists():
try:
shutil.rmtree(egg_info_dir)
print(f"✓ Removed {egg_info_dir}")
except OSError as e:
print(f"✗ Failed to remove {egg_info_dir}: {e}")
return 1
# Build wheel and sdist
print("\n" + "-" * 70)
print("Building distributions...")
print("-" * 70 + "\n")
try:
# Use python -m build for modern approach
result = subprocess.run(
[sys.executable, "-m", "build"],
cwd=project_root,
check=False
)
if result.returncode != 0:
print("\n✗ Build failed")
print("\nTrying alternative build method...")
# Fallback to direct setuptools invocation
result = subprocess.run(
[sys.executable, "setup.py", "sdist", "bdist_wheel"],
cwd=project_root,
check=False
)
if result.returncode != 0:
print("\n✗ Build failed with fallback method")
return 1
except FileNotFoundError:
# If 'build' module not available, use setup.py
print("Using setuptools directly...\n")
result = subprocess.run(
[sys.executable, "setup.py", "sdist", "bdist_wheel"],
cwd=project_root,
check=False
)
if result.returncode != 0:
print("\n✗ Build failed")
return 1
# Verify build output
print("\n" + "-" * 70)
print("Build Results")
print("-" * 70 + "\n")
if not dist_dir.exists():
print("✗ dist directory not created")
return 1
wheels = list(dist_dir.glob("*.whl"))
sdists = list(dist_dir.glob("*.tar.gz"))
if not wheels and not sdists:
print("✗ No distributions created")
return 1
print("✓ Build successful!\n")
if wheels:
print("Wheels:")
for wheel in wheels:
size_mb = wheel.stat().st_size / (1024 * 1024)
print(f"{wheel.name} ({size_mb:.2f} MB)")
if sdists:
print("\nSource Distributions:")
for sdist in sdists:
size_mb = sdist.stat().st_size / (1024 * 1024)
print(f"{sdist.name} ({size_mb:.2f} MB)")
print(f"\n✓ Distributions saved to: {dist_dir}")
# Installation instructions
print("\n" + "-" * 70)
print("Installation Instructions")
print("-" * 70 + "\n")
if wheels:
wheel = wheels[0]
print(f"Install the wheel locally:\n")
print(f" pip install {dist_dir / wheel.name}\n")
print(f"Or upload to PyPI:\n")
print(f" pip install twine")
print(f" twine upload dist/*\n")
return 0
if __name__ == "__main__":
sys.exit(main())

197
build_wheel.sh Normal file
View file

@ -0,0 +1,197 @@
#!/bin/bash
# build_wheel.sh - Build wheel distribution for Elytra PIM Client
#
# Usage:
# ./build_wheel.sh [--clean] [--upload]
#
# Options:
# --clean Clean previous builds before building
# --upload Upload to PyPI after building
#
# Requires:
# - Python 3.9+
# - setuptools>=65.0
# - wheel
# - build (recommended)
set -e
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Functions
print_success() {
echo -e "${GREEN}$1${NC}"
}
print_error() {
echo -e "${RED}$1${NC}"
}
print_header() {
echo "========================================================================"
echo "$1"
echo "========================================================================"
}
print_subheader() {
echo "------------------------------------------------------------------------"
echo "$1"
echo "------------------------------------------------------------------------"
}
# Parse arguments
CLEAN=false
UPLOAD=false
while [[ $# -gt 0 ]]; do
case $1 in
--clean)
CLEAN=true
shift
;;
--upload)
UPLOAD=true
shift
;;
*)
echo "Unknown option: $1"
echo "Usage: $0 [--clean] [--upload]"
exit 1
;;
esac
done
# Get project root
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
DIST_DIR="$PROJECT_ROOT/dist"
BUILD_DIR="$PROJECT_ROOT/build"
EGG_INFO_DIR="$PROJECT_ROOT/elytra_pim_client.egg-info"
print_header "Elytra PIM Client - Wheel Builder"
# Clean if requested
if [ "$CLEAN" = true ]; then
echo ""
echo "Cleaning previous builds..."
for dir in "$DIST_DIR" "$BUILD_DIR" "$EGG_INFO_DIR"; do
if [ -d "$dir" ]; then
rm -rf "$dir"
print_success "Removed $dir"
fi
done
fi
# Clean dist directory for fresh build
if [ -d "$DIST_DIR" ]; then
rm -rf "$DIST_DIR"
print_success "Removed old distributions"
fi
# Check build dependencies
echo ""
echo "Checking build dependencies..."
if ! python3 -m pip list 2>/dev/null | grep -q "^build "; then
echo "Installing build tools..."
python3 -m pip install -q build setuptools wheel
if [ $? -eq 0 ]; then
print_success "Build tools installed"
else
print_error "Failed to install build tools"
exit 1
fi
fi
# Build
print_subheader "Building distributions..."
echo ""
cd "$PROJECT_ROOT"
if ! python3 -m build; then
print_error "Build failed"
exit 1
fi
# Verify output
print_subheader "Build Results"
echo ""
if [ ! -d "$DIST_DIR" ]; then
print_error "dist directory not created"
exit 1
fi
# Count wheels and sdists
WHEEL_COUNT=$(find "$DIST_DIR" -maxdepth 1 -name "*.whl" 2>/dev/null | wc -l)
SDIST_COUNT=$(find "$DIST_DIR" -maxdepth 1 -name "*.tar.gz" 2>/dev/null | wc -l)
if [ $WHEEL_COUNT -eq 0 ] && [ $SDIST_COUNT -eq 0 ]; then
print_error "No distributions created"
exit 1
fi
print_success "Build successful!"
echo ""
if [ $WHEEL_COUNT -gt 0 ]; then
echo "Wheels:"
for wheel in "$DIST_DIR"/*.whl; do
SIZE=$(du -h "$wheel" | cut -f1)
echo "$(basename "$wheel") ($SIZE)"
done
fi
if [ $SDIST_COUNT -gt 0 ]; then
echo ""
echo "Source Distributions:"
for sdist in "$DIST_DIR"/*.tar.gz; do
SIZE=$(du -h "$sdist" | cut -f1)
echo "$(basename "$sdist") ($SIZE)"
done
fi
echo ""
print_success "Distributions saved to: $DIST_DIR"
# Installation instructions
print_subheader "Installation Instructions"
echo ""
if [ $WHEEL_COUNT -gt 0 ]; then
WHEEL=$(ls "$DIST_DIR"/*.whl | head -1)
echo "Install the wheel locally:"
echo " pip install \"$WHEEL\""
echo ""
echo "Or upload to PyPI:"
echo " pip install twine"
echo " twine upload dist/*"
echo ""
fi
# Optional upload
if [ "$UPLOAD" = true ]; then
print_subheader "Uploading to PyPI..."
echo ""
if ! python3 -m pip list 2>/dev/null | grep -q "^twine "; then
echo "Installing twine..."
python3 -m pip install -q twine
fi
echo "Running twine upload..."
cd "$PROJECT_ROOT"
if python3 -m twine upload dist/*; then
print_success "Upload complete!"
else
print_error "Upload failed"
exit 1
fi
fi
echo ""
echo "Done!"
echo ""
exit 0

View file

@ -8,7 +8,7 @@ version = "0.1.0"
description = "A Pythonic client for the Elytra PIM API"
readme = "README.md"
requires-python = ">=3.9"
license = {text = "MIT"}
license = "MIT"
authors = [
{name = "Your Name", email = "your.email@example.com"}
]
@ -16,7 +16,6 @@ keywords = ["elytra", "pim", "api", "client"]
classifiers = [
"Development Status :: 3 - Alpha",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",

10
setup.py Normal file
View file

@ -0,0 +1,10 @@
"""Setup configuration for Elytra PIM Client.
This setup.py file provides compatibility with legacy build tools.
The main configuration is in pyproject.toml (PEP 517/518).
"""
from setuptools import setup
if __name__ == "__main__":
setup()