This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
QGIS Hub Plugin is a QGIS plugin that enables users to discover, browse, and integrate resources from the QGIS Hub service directly into their QGIS projects. Resources include processing models, styles, scripts, geopackages, layer definitions, 3D models, and more.
This plugin requires QGIS Python bindings (PyQGIS) which must be installed system-wide. The virtual environment must link to these system packages:
# Create virtual environment linking to system packages (for pyqgis)
python3 -m venv .venv --system-site-packages
source .venv/bin/activate
# Upgrade pip and install dependencies
python -m pip install -U pip setuptools wheel
python -m pip install -U -r requirements/development.txt
# Install pre-commit hooks
pre-commit install# Run all pre-commit hooks on all files
pre-commit run --all-files
# Format code with black
black qgis_hub_plugin/
# Sort imports with isort
isort qgis_hub_plugin/
# Lint with flake8
flake8 qgis_hub_plugin/ --count --statistics# Run unit tests only (fast, no QGIS dependencies, < 1 second)
pytest tests/unit/ -v
# Run unit tests with coverage report
pytest tests/unit/ --cov=qgis_hub_plugin --cov-report=html --cov-report=term
# Run specific test file
pytest tests/qgis/test_api_client_mocked.py -v
# Run specific test
pytest tests/qgis/test_api_client_mocked.py::TestApiClientMocked::test_get_all_resources_with_cache -v
# Run QGIS integration tests (requires QGIS, ~8-10 seconds)
pytest tests/qgis/ -v
# Run all tests
pytest tests/ -v
# Skip QGIS tests (unit tests only)
pytest tests/ -v -m "not qgis"Note: Integration tests require QGIS and take ~8-10 seconds due to QGIS initialization. This is normal! See TESTING_GUIDE.md for troubleshooting.
Test Structure:
tests/unit/- Lightweight tests without QGIS (2 tests)test_plg_metadata.py- Metadata parsing
tests/qgis/- All QGIS-dependent tests (71 tests)test_api_client_mocked.py- API client with mocked network callstest_resource_item.py- ResourceItem creation and data rolestest_utilities.py- Download functions and utilitiestest_filter_proxy.py- MultiRoleFilterProxyModel filtering/sortingtest_integration.py- End-to-end workflowstest_api_client.py- API client (original, makes real calls)test_plg_preferences.py- Settings structure
tests/fixtures/- Mock data for testingapi_responses/- Mock API response JSON files
tests/conftest.py- Shared pytest fixtures
When making changes:
- Make your code changes
- Run
pre-commit run --all-filesto check code style - Run
pytest tests/unit/to verify tests pass - Commit changes (pre-commit hooks will run automatically)
- Entry:
qgis_hub_plugin/__init__.pydefinesclassFactory(iface)which QGIS calls to instantiate the plugin - Main Class:
qgis_hub_plugin/plugin_main.pycontainsQgisHubPluginPluginclass__init__(iface): Initialize plugin with QGIS interfaceinitGui(): Register UI elements (toolbar, menu items, settings widget)unload(): Clean up on plugin disable
- Function:
get_all_resources(force_update=False) - Fetches resources from
https://hub.qgis.org/api/v1/resources/ - Implements caching in
~/.qgis2/qgis_hub/response.json - Uses QGIS network utilities for HTTP requests
Resource Browser (resource_browser.py):
- Main dialog for browsing and managing resources (1,129 lines)
- Dual-view interface: Icon view and List view
- Tree-based category filtering with subtypes
- Full-text search across resource metadata
- Type-specific operations for downloading/adding resources to QGIS
Resource Item (resource_item.py):
ResourceItem(QStandardItem): Represents a single resource with metadata- Custom Qt data roles for filtering (ResourceTypeRole, NameRole, CreatorRole, etc.)
Settings Dialog (dlg_settings.py):
ConfigOptionsPage: Settings form integrated into QGIS Options menuPlgOptionsFactory: Factory for creating settings widgets
Constants (constants.py):
- Resource type definitions and categories
- Dynamic type registration for unknown resource types
MultiRoleFilterProxyModel(QSortFilterProxyModel): Advanced filtering- Multi-role filtering (type, subtype, text search)
- Checkbox state management
- Custom sorting by integer values (download counts, dates)
Toolbelt (toolbelt/):
log_handler.py:PlgLogger- Logging with QGIS message bar integrationpreferences.py:PlgOptionsManager- Settings management with dataclass structure
Utilities (utilities/):
common.py: File download, icon loading, thumbnail cachingexception.py: Custom exceptions (DownloadError)qgis_util.py:@show_busy_cursordecorator for UI responsivenessi18n.py: Internationalization helpers
- Menu Integration: Adds "QGIS Hub Plugin" submenu to Plugins menu
- Toolbar: Creates custom toolbar with Resource Browser action
- Settings: Integrates into QGIS → Preferences → Options via
PlgOptionsFactory - Core APIs Used:
QgsApplication: Network access, settings directory, processing registryQgsProject: Layer operationsQgsStyle: Style database importQgsLayerDefinition: Loading .qlr filesQgsNetworkAccessManager: HTTP requests
-
Model-View-Proxy Pattern:
- Model:
QStandardItemModelholds resource data - View:
ListView(icon view) andTreeView(list view) - Proxy:
MultiRoleFilterProxyModelfor filtering without modifying source
- Model:
-
Separation of Concerns:
- API layer: Isolated in
api_client.py - GUI layer: All UI components in
gui/package - Core logic: Filtering in
core/package - Utilities: Common operations in
utilities/andtoolbelt/
- API layer: Isolated in
-
Settings Management:
- Type-safe with dataclass (
PlgSettingsStructure) - Centralized access via
PlgOptionsManager - Stored in QgsSettings (QGIS configuration)
- Type-safe with dataclass (
-
Caching Strategy:
- API response cached to disk
- Thumbnail caching in
~/.qgis2/qgis_hub/thumbnails/ - Smart refresh with
force_updateflag
-
Dynamic Type Registration:
- Automatically handles new resource types from API
- No code changes needed when QGIS Hub adds new types
-
Error Handling:
- Custom exception hierarchy
- User feedback via QGIS message bar
- Logging at multiple levels
User Opens Resource Browser
↓
get_all_resources() fetches from API (with caching)
↓
ResourceItem objects created for each resource
↓
QStandardItemModel populated
↓
MultiRoleFilterProxyModel applies filtering
↓
Display in ListView/TreeView
↓
User selects and downloads/adds resource
↓
Type-specific handler processes resource
↓
Resource integrated into QGIS
The project uses pre-commit hooks (.pre-commit-config.yaml) to enforce:
- Large file detection (max 500KB)
- XML/YAML validation
- Private key detection
- End-of-file fixer
- Trailing whitespace removal
- Python code upgrade to 3.9+ syntax
- Formatting: Black (no configuration needed)
- Import Sorting: isort with Black profile
- Linting: Flake8 with QGIS-specific rules
- Docstrings: Sphinx-style for technical documentation
- Max Line Length: 100 characters (flake8), 88 (black)
- Max Complexity: 15 (flake8)
See setup.cfg for detailed flake8 rules. Key settings:
- Excludes:
.git,__pycache__,docs/conf.py,build,dist,.venv*,tests - Ignored rules: E121, E123, E126, E203, E226, E24, E704, QGS105, W503, W504
- QGIS-specific checks via
flake8-qgisplugin
- Name: QGIS Hub Plugin
- Category: Web
- Min QGIS Version: 3.28
- Authors: Ismail Sunni, Ronit Jadhav
- Homepage: https://qgis.github.io/QGIS-Hub-Plugin/
- Repository: https://github.com/qgis/QGIS-Hub-Plugin
- Tracker: https://github.com/qgis/QGIS-Hub-Plugin/issues
qgis_hub_plugin/
├── __init__.py # QGIS plugin entry point
├── plugin_main.py # Main plugin class
├── __about__.py # Metadata and version
├── metadata.txt # QGIS plugin metadata
├── core/
│ ├── api_client.py # API communication & caching
│ └── custom_filter_proxy.py # Advanced filtering logic
├── gui/
│ ├── resource_browser.py # Main dialog (1,129 lines)
│ ├── resource_item.py # Item models
│ ├── dlg_settings.py # Settings page
│ └── constants.py # Resource types & categories
├── toolbelt/
│ ├── log_handler.py # Logging system
│ └── preferences.py # Settings management
└── utilities/
├── common.py # File operations, thumbnails
├── exception.py # Custom exceptions
├── qgis_util.py # QGIS utilities
└── i18n.py # Internationalization
The project uses GitHub Actions for automated testing and deployment:
Tester (.github/workflows/tester.yml):
- Unit Tests: Fast tests without QGIS (~30 seconds)
- Runs on: Ubuntu Latest + Python 3.9
- Tests:
tests/unit/(28+ tests)
- Integration Tests: Tests with QGIS (~1-2 minutes)
- Runs on: QGIS Docker container (
qgis/qgis:release-3_34) - Tests:
tests/qgis/(24+ tests) - Environment:
QT_QPA_PLATFORM=offscreen(no display)
- Runs on: QGIS Docker container (
Linter (.github/workflows/linter.yml):
- Runs flake8 on Python code
Documentation (.github/workflows/documentation.yml):
- Builds and publishes Sphinx docs to GitHub Pages
Releaser (.github/workflows/releaser.yml):
- Packages and releases plugin
Tests run on push/PR to main when **.py files are modified.
Integration tests use the official QGIS Docker image for consistent environment:
- Image:
qgis/qgis:release-3_34(QGIS LTS) - No manual QGIS installation needed
- Full PyQGIS and Qt libraries included
- See
.github/workflows/README.mdfor details