Deploying DVP CMS Plugins

Intermediate 30 minutes

Guide to packaging, publishing, and deploying DVP CMS plugins.

Development Workflow

Local Development Setup

# 1. Create plugin directory
mkdir my-plugin
cd my-plugin

# 2. Create virtual environment
python -m venv venv
source venv/bin/activate  # On Windows: venv\Scripts\activate

# 3. Install DVP CMS (development mode)
pip install -e /path/to/dvp-cms

# 4. Install development tools
pip install pytest pytest-cov black mypy

Development Cycle

# 1. Write code
vim plugin.py

# 2. Write tests
vim tests/test_plugin.py

# 3. Run tests
pytest tests/ -v

# 4. Check coverage
pytest --cov=my_plugin tests/

# 5. Format code
black plugin.py

# 6. Type check
mypy plugin.py

# 7. Repeat

Plugin Packaging

Required Files

my-plugin/
├── pyproject.toml      # Package configuration (required)
├── README.md           # Documentation (required)
├── LICENSE             # License file (required)
├── CHANGELOG.md        # Version history (recommended)
├── my_plugin/          # Source directory
│   ├── __init__.py
│   ├── plugin.py
│   └── plugin.json
└── tests/              # Test directory
    ├── __init__.py
    └── test_plugin.py

pyproject.toml

Modern Python packaging uses pyproject.toml:

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

[project]
name = "dvp-my-plugin"
version = "1.0.0"
description = "My DVP CMS plugin"
readme = "README.md"
requires-python = ">=3.9"
license = {text = "AGPL-3.0"}
authors = [
    {name = "Your Name", email = "you@example.com"}
]
keywords = ["dvp-cms", "plugin", "content"]
classifiers = [
    "Development Status :: 4 - Beta",
    "Intended Audience :: Developers",
    "License :: OSI Approved :: GNU Affero General Public License v3",
    "Programming Language :: Python :: 3",
    "Programming Language :: Python :: 3.9",
    "Programming Language :: Python :: 3.10",
    "Programming Language :: Python :: 3.11",
]

dependencies = [
    "dvp-cms>=0.1.0",
]

[project.optional-dependencies]
dev = [
    "pytest>=7.0",
    "pytest-cov>=4.0",
    "pytest-asyncio>=0.21",
    "black>=23.0",
    "mypy>=1.0",
]

[project.urls]
Homepage = "https://yourplugin.example.com"
Documentation = "https://dvp-my-plugin.readthedocs.io"

[project.entry-points."dvp_cms.plugins"]
my-plugin = "my_plugin:MyPlugin"

[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = ["test_*.py"]
addopts = "--cov=my_plugin --cov-report=term-missing"

[tool.black]
line-length = 100
target-version = ['py39']

[tool.mypy]
python_version = "3.9"
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = true

Entry Points

What are Entry Points?

Entry points allow automatic plugin discovery:

# In pyproject.toml
[project.entry-points."dvp_cms.plugins"]
my-plugin = "my_plugin:MyPlugin"

This tells Python:

How DVP CMS Uses Entry Points

from dvp_cms.plugins import PluginDiscovery

# Auto-discover all installed plugins
discovery = PluginDiscovery()
plugins = discovery.discover_all()

# Each discovered plugin has:
# - name: "my-plugin"
# - plugin_class: MyPlugin
# - metadata: {...}

Local Installation

Development Installation

# Install in editable mode (changes reflect immediately)
cd my-plugin
pip install -e .

# With development dependencies
pip install -e ".[dev]"

Testing Local Installation

# test_installation.py
from my_plugin import MyPlugin

plugin = MyPlugin()
assert plugin.name == "my-plugin"
print("✓ Plugin installed successfully")
python test_installation.py

Uninstalling

pip uninstall dvp-my-plugin

Publishing to PyPI

Prerequisites

  1. Create PyPI account: https://pypi.org/account/register/
  2. Install build tools:
pip install build twine

Build Distribution

# Clean previous builds
rm -rf dist/ build/ *.egg-info

# Build source distribution and wheel
python -m build

# Result:
# dist/
# ├── dvp_my_plugin-1.0.0-py3-none-any.whl
# └── dvp-my-plugin-1.0.0.tar.gz

Test on TestPyPI

# Upload to TestPyPI first
twine upload --repository testpypi dist/*

# Test installation from TestPyPI
pip install --index-url https://test.pypi.org/simple/ dvp-my-plugin

Publish to PyPI

# Upload to real PyPI
twine upload dist/*

# Enter credentials when prompted
# Or use token authentication (recommended)

API Token Authentication

# Create token at https://pypi.org/manage/account/token/

# Create ~/.pypirc
cat > ~/.pypirc <<EOF
[pypi]
username = __token__
password = pypi-YOUR_TOKEN_HERE
EOF

chmod 600 ~/.pypirc

Private Repositories

Git Repository Installation

# Install directly from Git (if using version control)
pip install git+https://your-git-server.com/yourusername/dvp-my-plugin.git

# Specific branch
pip install git+https://your-git-server.com/yourusername/dvp-my-plugin.git@develop

# Specific tag
pip install git+https://your-git-server.com/yourusername/dvp-my-plugin.git@v1.0.0

Release Distribution

  1. Create a release with a version tag
  2. Build and distribute your package files
  3. Users can install from the distribution URL

Installation Methods

Method Command Pros Cons
pip (Recommended) pip install dvp-my-plugin Simple, standard, versioned Requires package published
Git URL pip install git+https://your-repo... No publishing needed No version pinning
Local Path pip install /path/to/plugin Immediate, no network Not portable
requirements.txt pip install -r requirements.txt Batch install Extra file needed

Version Management

Semantic Versioning

Follow semver:

MAJOR.MINOR.PATCH

1.0.0 → 1.0.1  (patch: bug fix)
1.0.1 → 1.1.0  (minor: new feature, backward compatible)
1.1.0 → 2.0.0  (major: breaking change)

Version Constraints

In pyproject.toml:

dependencies = [
    "dvp-cms>=0.1.0",        # Minimum version
    "requests>=2.28,<3.0",   # Range
    "markdown~=3.4",         # Compatible release (>= 3.4, < 4.0)
]

Version Bumping

# Manual: Edit pyproject.toml and plugin.py

# Or use bump2version:
pip install bump2version

# Create .bumpversion.cfg
cat > .bumpversion.cfg <<EOF
[bumpversion]
current_version = 1.0.0
commit = True
tag = True

[bumpversion:file:pyproject.toml]
[bumpversion:file:my_plugin/plugin.py]
EOF

# Bump patch version
bump2version patch  # 1.0.0 → 1.0.1

# Bump minor version
bump2version minor  # 1.0.1 → 1.1.0

# Bump major version
bump2version major  # 1.1.0 → 2.0.0

Distribution Best Practices

Pre-Release Checklist

Before publishing:

  • All tests passing
  • Coverage ≥80%
  • README.md complete
  • CHANGELOG.md updated
  • Version bumped
  • License file included
  • No hardcoded secrets
  • Dependencies pinned
  • Entry point configured
  • Tested on fresh install

CI/CD Pipeline

Example automation for publishing to PyPI on release:

# Example CI/CD configuration (adapt to your CI system)
name: Publish to PyPI

on:
  release:
    types: [created]

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
    - name: Checkout code
      run: git checkout

    - name: Set up Python
      run: |
        python --version

    - name: Install dependencies
      run: |
        pip install build twine

    - name: Build package
      run: python -m build

    - name: Publish to PyPI
      env:
        TWINE_USERNAME: __token__
        TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
      run: twine upload dist/*

Version Tagging

# Create annotated tag
git tag -a v1.0.0 -m "Release version 1.0.0"

# Push tag to remote
git push origin v1.0.0

# Or push all tags
git push --tags

Quick Reference

Build and Publish

# 1. Update version
vim pyproject.toml  # Update version
vim my_plugin/plugin.py  # Update version

# 2. Update changelog
vim CHANGELOG.md

# 3. Commit changes
git add .
git commit -m "Release v1.0.0"

# 4. Create tag
git tag -a v1.0.0 -m "Release v1.0.0"

# 5. Build
python -m build

# 6. Test on TestPyPI
twine upload --repository testpypi dist/*

# 7. Test installation
pip install --index-url https://test.pypi.org/simple/ dvp-my-plugin

# 8. Publish to PyPI
twine upload dist/*

# 9. Push to Git
git push origin main --tags

Common Issues

Issue: Entry Point Not Found

Problem: Plugin not discovered after installation

Solution:

  1. Check pyproject.toml has entry point
  2. Reinstall package: pip uninstall dvp-my-plugin && pip install dvp-my-plugin

Issue: Import Error

Problem: ModuleNotFoundError: No module named 'my_plugin'

Solution:

  1. Check package structure has __init__.py
  2. Verify package name in pyproject.toml
  3. Ensure installed: pip list | grep dvp-my-plugin

Issue: Version Conflict

Problem: Dependency conflict during installation

Solution: Relax version constraints in pyproject.toml

dependencies = [
    "dvp-cms>=0.1.0",  # Instead of ==0.1.0
]

Resources