Skip to main content

A flexible, modular version monitoring tool that tracks changes across multiple sources including PyPI, Conda channels (conda-forge, bioconda, custom), and arbitrary URLs.

PyPI - Version PyPI - Python Version write-the - docs write-the - test codecov


Table of Contents

Features

  • 🔍 Multi-source monitoring - Track versions from PyPI, Conda, Bioconda, and arbitrary URLs
  • 📏 Version constraints - Monitor only versions matching specific criteria (e.g., ">=1.21,<2.0")
  • 🎯 Flexible parsing - Built-in parsers for common formats (ETag, regex, JSON)
  • 🔧 Custom parsers - Write your own parsers for specialized sources
  • 💾 Persistent storage - JSON-based database to track version changes
  • 📢 Change notifications - Execute custom callbacks when versions change
  • 🌐 HTTP resilience - Built-in retries and error handling
  • 🏗️ Modular architecture - Clean separation of concerns for easy extension

Installation

pip install glasscandle

Quick Start

from glasscandle import Watcher

# Create a watcher instance
watch = Watcher("versions.json")

# Optional: Add change notification callback
def on_version_change(key: str, old: str, new: str):
    print(f"📦 {key} updated: {old} → {new}")

# Monitor PyPI packages
watch.pypi("requests", on_change=on_version_change)
watch.pypi("numpy")

# Monitor Conda packages from any channel
watch.conda("pytorch", channels=["pytorch", "conda-forge"])
watch.conda("tensorflow")  # Uses default channels

# Monitor Bioconda packages
watch.bioconda("samtools", on_change=on_version_change)
watch.bioconda("bwa")

# Monitor with version constraints
watch.pypi("django", version=">=4.0,<5.0", on_change=on_version_change)
watch.conda("numpy", version=">=1.21", channels=["conda-forge"])
watch.bioconda("blast", version="~=2.12.0")

# Monitor a URL with ETag parsing (default)
watch.url("https://example.com/releases/latest")

# Monitor a URL with regex parsing
watch.url_regex(
    "https://example.com/version", 
    r"version:\s*(\d+\.\d+\.\d+)",
    on_change=on_version_change
)

# Run checks once
watch.run()

# Or run continuously (every 60 seconds)
watch.start(interval=60)

Supported Providers

PyPI

Monitor Python packages from the Python Package Index:

watch.pypi("package-name")

# With version constraints
watch.pypi("django", version=">=4.0,<5.0")
watch.pypi("requests", version="~=2.28.0")  # Compatible release

Conda

Monitor packages from Conda channels with full flexibility:

# Monitor from default channels (conda-forge, defaults)
watch.conda("numpy")

# Monitor from specific channels
watch.conda("pytorch", channels=["pytorch", "conda-forge"])
watch.conda("tensorflow-gpu", channels=["conda-forge"])

# With version constraints
watch.conda("numpy", version=">=1.21,<2.0", channels=["conda-forge"])
watch.conda("scipy", version="~=1.9.0")  # Compatible release

Bioconda

Monitor bioinformatics packages from Bioconda:

watch.bioconda("package-name")

# With version constraints
watch.bioconda("samtools", version=">=1.15")
watch.bioconda("blast", version="~=2.12.0")

Conda-forge

Monitor packages specifically from the conda-forge channel:

# Monitor any version from conda-forge
watch.condaforge("numpy")

# With version constraints
watch.condaforge("scipy", version=">=1.7,<2.0")
watch.condaforge("matplotlib", version="~=3.5.0")

Version Constraints

All package providers support version constraints using standard Python packaging syntax:

# Greater than or equal to
watch.pypi("django", version=">=4.0")

# Range constraints
watch.conda("numpy", version=">=1.21,<2.0")

# Compatible release (equivalent to >=1.21.0,<1.22.0)
watch.bioconda("blast", version="~=1.21.0")

# Exact version
watch.pypi("requests", version="==2.28.1")

# Not equal to
watch.conda("scipy", version=">=1.9,!=1.9.2")

# Complex constraints
watch.pypi("flask", version=">2.0,!=2.1.0,<3.0")

JSON APIs

Monitor JSON endpoints using JSONPath expressions:

# GitHub releases
watch.json("https://api.github.com/repos/user/repo/releases/latest", "$.tag_name")

# npm packages
watch.json("https://registry.npmjs.org/package/latest", "$.version")

# Complex nested paths
watch.json("https://api.example.com/data", "$.results[0].version")

URL with Built-in Parsers

Monitor arbitrary URLs using built-in parsers:

# ETag parser (default)
watch.url("https://example.com/file")

# Last-Modified parser
from glasscandle import last_modified
watch.url("https://example.com/file", parser=last_modified)

# SHA256 hash parser
from glasscandle import sha256_of_body
watch.url("https://example.com/file", parser=sha256_of_body)

# Regex parser
watch.url_regex("https://example.com/version", r"v(\d+\.\d+\.\d+)")

# JSON parser with JSONPath
watch.json("https://api.github.com/repos/user/repo/releases/latest", "$.tag_name")

Custom URL Parsers

Write custom parsers for specialized sources:

from glasscandle import Response

@watch.response("https://api.github.com/repos/user/repo/releases/latest")
def github_latest_release(res: Response):
    """Extract the latest release tag from GitHub API."""
    data = res.json()
    return data["tag_name"]

Change Notifications

Execute custom functions when versions change:

def notify_change(key: str, old_version: str, new_version: str):
    print(f"📦 {key} updated: {old_version} → {new_version}")
    # Send email, webhook, Slack notification, etc.

# Add callbacks to any provider
watch.pypi("requests", on_change=notify_change)
watch.conda("numpy", version=">=1.21", on_change=notify_change)
watch.bioconda("samtools", on_change=notify_change)
watch.json("https://api.github.com/repos/user/repo/releases/latest", "$.tag_name", on_change=notify_change)
watch.url("https://example.com/version", on_change=notify_change)

# Custom URLs with callbacks
@watch.response("https://api.github.com/repos/user/repo/releases", 
                on_change=notify_change)
def custom_parser(res: Response):
    return res.json()[0]["tag_name"]

Built-in Notification Helpers

Use pre-built notification functions for common services:

from glasscandle.notifications import slack_notifier, multi_notifier

# Slack notifications (uses SLACK_WEBHOOK_URL env var)
slack_notify = slack_notifier()
watch.pypi("django", version=">=4.0", on_change=slack_notify)

# Email notifications (uses EMAIL_* env vars)  
email_notify = email_notifier()
watch.conda("numpy", version=">=1.21", on_change=email_notify)

# Multiple notification methods
multi_notify = multi_notifier(slack_notify, email_notify)
watch.json("https://api.github.com/repos/user/repo/releases/latest", 
           "$.tag_name", on_change=multi_notify)

# Direct external function calls
from glasscandle.external.slack import send_slack_msg
import os

def custom_notifier(key: str, old: str, new: str):
    webhook_url = os.getenv("SLACK_WEBHOOK_URL")
    send_slack_msg(f"Version Update: {key}", f"Updated from {old} → {new}", webhook_url=webhook_url)

watch.pypi("requests", on_change=custom_notifier)

GitHub Actions Integration

Watcher is designed to work seamlessly with GitHub Actions for automated monitoring:

1. Environment Variables

Set these as GitHub repository secrets:

  • SLACK_WEBHOOK_URL - Required for Slack notifications
  • EMAIL_TO - Optional email recipient
  • EMAIL_SMTP_SERVER - Optional SMTP server
  • EMAIL_USERNAME - Optional SMTP username
  • EMAIL_PASSWORD - Optional SMTP password

2. GitHub Actions Workflow

Create .github/workflows/version-watcher.yml:

name: Version Watcher
on:
  schedule:
    - cron: '0 */6 * * *'  # Every 6 hours
  workflow_dispatch:

jobs:
  watch-versions:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    - uses: actions/setup-python@v4
      with:
        python-version: '3.11'
    - run: pip install watcher
    - run: python watch_script.py
      env:
        SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
    - run: |
        git config --local user.email "action@github.com"
        git config --local user.name "GitHub Action"
        git add .
        git diff --staged --quiet || git commit -m "Update versions [skip ci]"
        git push

3. Watch Script

Create a watch_script.py that uses environment variables:

import os
from glasscandle import Watcher
from glasscandle.notifications import slack_notifier

watch = Watcher("versions.json")
notify = slack_notifier()  # Uses SLACK_WEBHOOK_URL env var

watch.pypi("requests", on_change=notify)
watch.json("https://api.github.com/repos/user/repo/releases/latest", 
           "$.tag_name", on_change=notify)

watch.run()

Usage Examples

Basic Version Monitoring

from glasscandle import Watcher

watch = Watcher("my-versions.json")

# Add packages to monitor
watch.pypi("django")
watch.pypi("flask", version=">=2.0")
watch.conda("numpy", channels=["conda-forge"])
watch.bioconda("blast", version="~=2.12.0")

# Check once
watch.run()

Version Constraint Examples

from glasscandle import Watcher

watch = Watcher("versions.json")

# Monitor only major version 4.x of Django
watch.pypi("django", version=">=4.0,<5.0")

# Monitor compatible releases (patch-level updates)
watch.conda("numpy", version="~=1.21.0")

# Complex constraints with exclusions
watch.pypi("requests", version=">=2.28,!=2.28.2,<3.0")

# Monitor latest of any version (default behavior)
watch.bioconda("samtools")

watch.run()

Advanced URL Monitoring

from glasscandle import Watcher, regex, etag

watch = Watcher("versions.json")

# Monitor with different parsers
watch.url("https://releases.example.com/latest", parser=etag)
watch.url_regex("https://version.example.com", r"Version (\d+\.\d+)")
watch.json("https://api.example.com/version", "$.latest")

# Custom domain restrictions
watch = Watcher(
    "versions.json", 
    allowed_custom_domains=("trusted.com", "example.org")
)

Continuous Monitoring

from glasscandle import Watcher

watch = Watcher("versions.json")

# Add your packages with constraints
watch.pypi("requests", version=">=2.28,<3.0")
watch.conda("pytorch", channels=["pytorch"], version=">=1.12")
watch.bioconda("samtools", version=">=1.15")
watch.json("https://api.github.com/repos/user/repo/releases/latest", "$.tag_name")

# Start monitoring (checks every 5 minutes)
watch.start(interval=300)

License

watcher is distributed under the terms of the MIT license.

Metadata

Release files for glasscandle 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for glasscandle 0.1.1
File Size Uploaded Provenance
glasscandle-0.1.1.tar.gz 104.0 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for glasscandle 0.1.1
File Interpreter ABI Platform
glasscandle-0.1.1-py3-none-any.whl Python 3 none any Details

Total release size: 134.0 kB

Release files / glasscandle-0.1.1.tar.gz

Download URL glasscandle-0.1.1.tar.gz
Size 104.0 kB
Tags Source
SHA-256 checksum
How to use checksums
7d0e9a1866fab68745e8687ee1e0c7fdbae786dc10f342ef4145490c3b074c03
BLAKE2b-256 checksum
How to use checksums
446a0f0ab2fd0bfa19e9378320f1595d2a19a01799b4ad2c05ab7d51c33dbbcc
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via python-httpx/0.28.1

Release files / glasscandle-0.1.1-py3-none-any.whl

Download URL glasscandle-0.1.1-py3-none-any.whl
Size 30.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
fad62032e34ca52501826f0f3368a5f8684c82619a05c2d179382e6ce07eb6eb
BLAKE2b-256 checksum
How to use checksums
f3e59d2d1be931025b81f2bd6bc1464edb1d12e2d87afe6af3d007f4e6fb0d24
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via python-httpx/0.28.1

Security for glasscandle 0.1.1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page