nwp500-python 4.7__tar.gz → 4.7.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {nwp500_python-4.7 → nwp500_python-4.7.1}/.github/copilot-instructions.md +27 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/CHANGELOG.rst +8 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/Makefile +18 -2
- {nwp500_python-4.7/src/nwp500_python.egg-info → nwp500_python-4.7.1}/PKG-INFO +1 -1
- {nwp500_python-4.7 → nwp500_python-4.7.1}/RELEASE.md +69 -6
- nwp500_python-4.7.1/scripts/README.md +206 -0
- nwp500_python-4.7.1/scripts/bump_version.py +228 -0
- nwp500_python-4.7.1/scripts/validate_version.py +152 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/setup.cfg +1 -1
- {nwp500_python-4.7 → nwp500_python-4.7.1/src/nwp500_python.egg-info}/PKG-INFO +1 -1
- {nwp500_python-4.7 → nwp500_python-4.7.1}/src/nwp500_python.egg-info/SOURCES.txt +3 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/.coveragerc +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/.github/workflows/ci.yml +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/.github/workflows/release.yml +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/.gitignore +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/.pre-commit-config.yaml +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/.readthedocs.yml +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/AUTHORS.rst +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/CONTRIBUTING.rst +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/LICENSE.txt +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/README.rst +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/docs/Makefile +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/docs/_static/.gitignore +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/docs/authors.rst +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/docs/changelog.rst +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/docs/conf.py +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/docs/configuration.rst +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/docs/development/contributing.rst +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/docs/development/history.rst +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/docs/guides/auto_recovery.rst +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/docs/guides/command_queue.rst +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/docs/guides/energy_monitoring.rst +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/docs/guides/event_system.rst +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/docs/guides/reservations.rst +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/docs/guides/time_of_use.rst +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/docs/index.rst +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/docs/installation.rst +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/docs/license.rst +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/docs/openapi.yaml +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/docs/protocol/device_features.rst +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/docs/protocol/device_status.rst +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/docs/protocol/error_codes.rst +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/docs/protocol/firmware_tracking.rst +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/docs/protocol/mqtt_protocol.rst +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/docs/protocol/rest_api.rst +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/docs/python_api/api_client.rst +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/docs/python_api/auth_client.rst +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/docs/python_api/cli.rst +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/docs/python_api/constants.rst +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/docs/python_api/events.rst +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/docs/python_api/exceptions.rst +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/docs/python_api/models.rst +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/docs/python_api/mqtt_client.rst +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/docs/quickstart.rst +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/docs/requirements.txt +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/examples/.ruff.toml +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/examples/README.md +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/examples/anti_legionella_example.py +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/examples/api_client_example.py +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/examples/auth_constructor_example.py +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/examples/authenticate.py +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/examples/auto_recovery_example.py +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/examples/combined_callbacks.py +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/examples/command_queue_demo.py +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/examples/device_feature_callback.py +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/examples/device_status_callback.py +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/examples/device_status_callback_debug.py +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/examples/energy_usage_example.py +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/examples/event_emitter_demo.py +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/examples/improved_auth_pattern.py +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/examples/mask.py +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/examples/mqtt_client_example.py +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/examples/periodic_device_info.py +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/examples/periodic_requests.py +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/examples/power_control_example.py +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/examples/reconnection_demo.py +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/examples/reservation_schedule_example.py +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/examples/set_dhw_temperature_example.py +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/examples/set_mode_example.py +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/examples/simple_auto_recovery.py +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/examples/simple_periodic_info.py +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/examples/simple_periodic_status.py +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/examples/test_api_client.py +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/examples/test_mqtt_connection.py +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/examples/test_mqtt_messaging.py +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/examples/test_periodic_minimal.py +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/examples/tou_openei_example.py +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/examples/tou_schedule_example.py +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/pyproject.toml +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/scripts/format.py +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/scripts/lint.py +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/scripts/setup-dev.py +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/setup.py +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/src/nwp500/__init__.py +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/src/nwp500/api_client.py +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/src/nwp500/auth.py +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/src/nwp500/cli/__init__.py +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/src/nwp500/cli/__main__.py +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/src/nwp500/cli/commands.py +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/src/nwp500/cli/monitoring.py +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/src/nwp500/cli/output_formatters.py +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/src/nwp500/cli/token_storage.py +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/src/nwp500/cli.py +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/src/nwp500/config.py +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/src/nwp500/constants.py +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/src/nwp500/encoding.py +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/src/nwp500/events.py +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/src/nwp500/models.py +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/src/nwp500/mqtt_client.py +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/src/nwp500/mqtt_command_queue.py +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/src/nwp500/mqtt_connection.py +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/src/nwp500/mqtt_device_control.py +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/src/nwp500/mqtt_periodic.py +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/src/nwp500/mqtt_reconnection.py +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/src/nwp500/mqtt_subscriptions.py +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/src/nwp500/mqtt_utils.py +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/src/nwp500/py.typed +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/src/nwp500/utils.py +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/src/nwp500_python.egg-info/dependency_links.txt +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/src/nwp500_python.egg-info/entry_points.txt +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/src/nwp500_python.egg-info/not-zip-safe +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/src/nwp500_python.egg-info/requires.txt +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/src/nwp500_python.egg-info/top_level.txt +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/tests/conftest.py +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/tests/test_api_helpers.py +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/tests/test_auth.py +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/tests/test_command_queue.py +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/tests/test_events.py +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/tests/test_utils.py +0 -0
- {nwp500_python-4.7 → nwp500_python-4.7.1}/tox.ini +0 -0
|
@@ -17,6 +17,33 @@
|
|
|
17
17
|
- **Type checking**: `python3 -m mypy src/nwp500 --config-file pyproject.toml` (static type analysis)
|
|
18
18
|
- **Build docs**: `tox -e docs` (Sphinx docs in `docs/`)
|
|
19
19
|
- **Preview docs**: `python3 -m http.server --directory docs/_build/html`
|
|
20
|
+
- **Version management**: `make version-bump BUMP=patch|minor|major` (creates git tags, see Version Management section)
|
|
21
|
+
|
|
22
|
+
### Version Management
|
|
23
|
+
|
|
24
|
+
**CRITICAL**: This project uses `setuptools_scm` to derive versions from git tags.
|
|
25
|
+
|
|
26
|
+
**Never manually edit version numbers!** The `version` field in `setup.cfg`'s `[pyscaffold]` section is the PyScaffold TOOL version (4.6), NOT the package version. Changing it will cause incorrect releases.
|
|
27
|
+
|
|
28
|
+
#### Creating a New Release
|
|
29
|
+
|
|
30
|
+
1. **Update CHANGELOG.rst** with the new version and changes
|
|
31
|
+
2. **Commit the changelog**: `git add CHANGELOG.rst && git commit -m "Update changelog for vX.Y.Z"`
|
|
32
|
+
3. **Bump version**: Use the version bump script:
|
|
33
|
+
```bash
|
|
34
|
+
make version-bump BUMP=patch # For bug fixes (3.1.4 -> 3.1.5)
|
|
35
|
+
make version-bump BUMP=minor # For new features (3.1.4 -> 3.2.0)
|
|
36
|
+
make version-bump BUMP=major # For breaking changes (3.1.4 -> 4.0.0)
|
|
37
|
+
```
|
|
38
|
+
4. **Push the tag**: `git push origin vX.Y.Z`
|
|
39
|
+
|
|
40
|
+
The version bump script:
|
|
41
|
+
- Gets the current version from git tags
|
|
42
|
+
- Validates the new version progression (prevents large jumps)
|
|
43
|
+
- Prompts for confirmation with checklist
|
|
44
|
+
- Creates a git tag (e.g., `v3.1.5`)
|
|
45
|
+
|
|
46
|
+
**Validation**: Run `make validate-version` to check for version-related mistakes before committing.
|
|
20
47
|
|
|
21
48
|
### Before Committing Changes
|
|
22
49
|
Always run these checks before finalizing changes to ensure your code will pass CI:
|
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
Changelog
|
|
3
3
|
=========
|
|
4
4
|
|
|
5
|
+
Version 4.7.1 (2025-10-27)
|
|
6
|
+
==========================
|
|
7
|
+
|
|
8
|
+
Changed
|
|
9
|
+
-------
|
|
10
|
+
|
|
11
|
+
- **Patch Release**: No code changes, updated version format to full semantic versioning
|
|
12
|
+
|
|
5
13
|
Version 4.7 (2025-10-27)
|
|
6
14
|
========================
|
|
7
15
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
.PHONY: help install install-dev lint format test clean build release check-release ci-lint ci-format ci-check
|
|
1
|
+
.PHONY: help install install-dev lint format test clean build release check-release ci-lint ci-format ci-check version-bump validate-version
|
|
2
2
|
|
|
3
3
|
help: ## Show this help message
|
|
4
4
|
@echo 'Usage: make [target]'
|
|
@@ -50,7 +50,7 @@ clean: ## Remove build artifacts and cache files
|
|
|
50
50
|
build: clean ## Build distribution packages
|
|
51
51
|
python -m build
|
|
52
52
|
|
|
53
|
-
check-release: lint format-check test ## Run all checks before release (lint, format check, tests)
|
|
53
|
+
check-release: lint format-check test validate-version ## Run all checks before release (lint, format check, tests, version validation)
|
|
54
54
|
@echo "✓ All checks passed! Ready for release."
|
|
55
55
|
|
|
56
56
|
release: check-release build ## Prepare and build a release (run checks, then build)
|
|
@@ -80,3 +80,19 @@ docs: ## Build documentation
|
|
|
80
80
|
|
|
81
81
|
docs-clean: ## Clean documentation build
|
|
82
82
|
rm -rf docs/_build
|
|
83
|
+
|
|
84
|
+
version-bump: ## Bump version (usage: make version-bump BUMP=patch|minor|major|X.Y.Z)
|
|
85
|
+
@if [ -z "$(BUMP)" ]; then \
|
|
86
|
+
echo "Error: BUMP parameter required"; \
|
|
87
|
+
echo "Usage: make version-bump BUMP=patch|minor|major|X.Y.Z"; \
|
|
88
|
+
echo "Examples:"; \
|
|
89
|
+
echo " make version-bump BUMP=patch # Bump patch version"; \
|
|
90
|
+
echo " make version-bump BUMP=minor # Bump minor version"; \
|
|
91
|
+
echo " make version-bump BUMP=major # Bump major version"; \
|
|
92
|
+
echo " make version-bump BUMP=3.1.5 # Set explicit version"; \
|
|
93
|
+
exit 1; \
|
|
94
|
+
fi
|
|
95
|
+
python3 scripts/bump_version.py $(BUMP)
|
|
96
|
+
|
|
97
|
+
validate-version: ## Validate version configuration (checks for common mistakes)
|
|
98
|
+
python3 scripts/validate_version.py
|
|
@@ -95,15 +95,74 @@ This runs:
|
|
|
95
95
|
|
|
96
96
|
### 4. Update Version and Changelog
|
|
97
97
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
98
|
+
#### Understanding Version Management
|
|
99
|
+
|
|
100
|
+
**IMPORTANT**: This project uses `setuptools_scm` to manage versions from git tags.
|
|
101
|
+
The version is **NOT** stored in any Python files or config files.
|
|
102
|
+
|
|
103
|
+
**DO NOT** edit the `version` field in `setup.cfg`'s `[pyscaffold]` section!
|
|
104
|
+
That field stores the PyScaffold tool version (4.6), not the package version.
|
|
105
|
+
|
|
106
|
+
#### Version Bump Process
|
|
107
|
+
|
|
108
|
+
1. Update `CHANGELOG.rst` with changes for this release:
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
# Get current date
|
|
112
|
+
date +"%Y-%m-%d"
|
|
113
|
+
|
|
114
|
+
# Edit CHANGELOG.rst and add a new section:
|
|
115
|
+
# Version X.Y.Z (YYYY-MM-DD)
|
|
116
|
+
# ==========================
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
2. Commit the changelog:
|
|
101
120
|
|
|
102
121
|
```bash
|
|
103
122
|
git add CHANGELOG.rst
|
|
104
123
|
git commit -m "Update changelog for vX.Y.Z"
|
|
105
124
|
```
|
|
106
125
|
|
|
126
|
+
3. Use the version bump script to create a git tag:
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
# For a patch release (X.Y.Z -> X.Y.Z+1)
|
|
130
|
+
make version-bump BUMP=patch
|
|
131
|
+
|
|
132
|
+
# For a minor release (X.Y.Z -> X.Y+1.0)
|
|
133
|
+
make version-bump BUMP=minor
|
|
134
|
+
|
|
135
|
+
# For a major release (X.Y.Z -> X+1.0.0)
|
|
136
|
+
make version-bump BUMP=major
|
|
137
|
+
|
|
138
|
+
# Or specify an explicit version
|
|
139
|
+
make version-bump BUMP=3.1.5
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
The script will:
|
|
143
|
+
- Get the current version from git tags
|
|
144
|
+
- Calculate the new version
|
|
145
|
+
- Validate the version progression (prevents large jumps)
|
|
146
|
+
- Create a git tag (e.g., `v3.1.5`)
|
|
147
|
+
- Display next steps
|
|
148
|
+
|
|
149
|
+
4. Push the tag to trigger the release:
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
git push origin vX.Y.Z
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
#### Manual Version Tagging (Not Recommended)
|
|
156
|
+
|
|
157
|
+
If you need to create a tag manually:
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
git tag -a vX.Y.Z -m "Release version X.Y.Z"
|
|
161
|
+
git push origin vX.Y.Z
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
**Warning**: Manual tagging bypasses validation checks. Use the version bump script instead.
|
|
165
|
+
|
|
107
166
|
### 5. Build Distribution
|
|
108
167
|
|
|
109
168
|
Clean and build distribution packages:
|
|
@@ -152,13 +211,15 @@ python -m twine upload dist/*
|
|
|
152
211
|
|
|
153
212
|
### 8. Tag the Release
|
|
154
213
|
|
|
155
|
-
|
|
214
|
+
**Note**: If you used the version bump script, the tag is already created.
|
|
215
|
+
Just push it:
|
|
156
216
|
|
|
157
217
|
```bash
|
|
158
|
-
git tag -a vX.Y.Z -m "Release version X.Y.Z"
|
|
159
218
|
git push origin vX.Y.Z
|
|
160
219
|
```
|
|
161
220
|
|
|
221
|
+
If you created a tag manually, push it now.
|
|
222
|
+
|
|
162
223
|
## Using Tox
|
|
163
224
|
|
|
164
225
|
You can also use tox directly for all steps:
|
|
@@ -243,8 +304,9 @@ Before releasing, ensure:
|
|
|
243
304
|
- [ ] All code is formatted: `make format`
|
|
244
305
|
- [ ] Linting passes: `make lint`
|
|
245
306
|
- [ ] All tests pass: `make test`
|
|
307
|
+
- [ ] Version configuration is valid: `make validate-version`
|
|
246
308
|
- [ ] Changelog is updated
|
|
247
|
-
- [ ] Version is bumped appropriately
|
|
309
|
+
- [ ] Version is bumped appropriately using `make version-bump`
|
|
248
310
|
- [ ] Documentation is up to date
|
|
249
311
|
- [ ] Examples work correctly
|
|
250
312
|
- [ ] Build succeeds: `make build`
|
|
@@ -310,6 +372,7 @@ Example GitHub Actions workflow could run:
|
|
|
310
372
|
|
|
311
373
|
| Command | Description |
|
|
312
374
|
|---------|-------------|
|
|
375
|
+
| `make version-bump` | Bump version (requires BUMP=patch/minor/major/X.Y.Z) |
|
|
313
376
|
| `make help` | Show all available commands |
|
|
314
377
|
| `make install-dev` | Install with dev dependencies |
|
|
315
378
|
| `make format` | Format code with ruff |
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
# Development Scripts
|
|
2
|
+
|
|
3
|
+
This directory contains helper scripts for development and release management.
|
|
4
|
+
|
|
5
|
+
## Version Management Scripts
|
|
6
|
+
|
|
7
|
+
### bump_version.py
|
|
8
|
+
|
|
9
|
+
Creates new releases by bumping version numbers and creating git tags.
|
|
10
|
+
|
|
11
|
+
**Usage:**
|
|
12
|
+
```bash
|
|
13
|
+
# Via Makefile (recommended)
|
|
14
|
+
make version-bump BUMP=patch # 3.1.4 -> 3.1.5
|
|
15
|
+
make version-bump BUMP=minor # 3.1.4 -> 3.2.0
|
|
16
|
+
make version-bump BUMP=major # 3.1.4 -> 4.0.0
|
|
17
|
+
make version-bump BUMP=3.1.5 # Explicit version
|
|
18
|
+
|
|
19
|
+
# Direct invocation
|
|
20
|
+
python3 scripts/bump_version.py patch
|
|
21
|
+
python3 scripts/bump_version.py minor
|
|
22
|
+
python3 scripts/bump_version.py major
|
|
23
|
+
python3 scripts/bump_version.py 3.1.5
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
**Features:**
|
|
27
|
+
- Reads current version from git tags
|
|
28
|
+
- Calculates next version based on bump type
|
|
29
|
+
- Validates version progression (warns on large jumps)
|
|
30
|
+
- Prompts for confirmation with pre-release checklist
|
|
31
|
+
- Creates annotated git tag
|
|
32
|
+
- Shows next steps for publishing
|
|
33
|
+
|
|
34
|
+
**Important:** This project uses `setuptools_scm` to derive versions from git tags. Never manually edit version numbers in config files!
|
|
35
|
+
|
|
36
|
+
### validate_version.py
|
|
37
|
+
|
|
38
|
+
Validates version-related configuration to prevent common mistakes.
|
|
39
|
+
|
|
40
|
+
**Usage:**
|
|
41
|
+
```bash
|
|
42
|
+
# Via Makefile (recommended)
|
|
43
|
+
make validate-version
|
|
44
|
+
|
|
45
|
+
# Direct invocation
|
|
46
|
+
python3 scripts/validate_version.py
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
**Checks:**
|
|
50
|
+
- Verifies `setup.cfg` `[pyscaffold]` version hasn't been modified (should be 4.6)
|
|
51
|
+
- Ensures no hardcoded `__version__` strings in source code
|
|
52
|
+
- Confirms `setup.py` uses `setuptools_scm`
|
|
53
|
+
|
|
54
|
+
**When to run:**
|
|
55
|
+
- Before creating a release
|
|
56
|
+
- As part of `make check-release`
|
|
57
|
+
- In CI/CD pipelines (recommended)
|
|
58
|
+
|
|
59
|
+
## Code Quality Scripts
|
|
60
|
+
|
|
61
|
+
### lint.py
|
|
62
|
+
|
|
63
|
+
Runs ruff linting via tox, mirroring the CI environment exactly.
|
|
64
|
+
|
|
65
|
+
**Usage:**
|
|
66
|
+
```bash
|
|
67
|
+
# Via Makefile (recommended)
|
|
68
|
+
make ci-lint
|
|
69
|
+
|
|
70
|
+
# Direct invocation
|
|
71
|
+
python3 scripts/lint.py
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
This ensures local linting results match CI results, preventing "passes locally but fails in CI" issues.
|
|
75
|
+
|
|
76
|
+
### format.py
|
|
77
|
+
|
|
78
|
+
Formats code with ruff via tox, mirroring the CI environment exactly.
|
|
79
|
+
|
|
80
|
+
**Usage:**
|
|
81
|
+
```bash
|
|
82
|
+
# Via Makefile (recommended)
|
|
83
|
+
make ci-format
|
|
84
|
+
|
|
85
|
+
# Direct invocation
|
|
86
|
+
python3 scripts/format.py
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
This automatically fixes code formatting issues using the same configuration as CI.
|
|
90
|
+
|
|
91
|
+
### setup-dev.py
|
|
92
|
+
|
|
93
|
+
Sets up a minimal development environment with essential tools.
|
|
94
|
+
|
|
95
|
+
**Usage:**
|
|
96
|
+
```bash
|
|
97
|
+
# Via Makefile (recommended)
|
|
98
|
+
make setup-dev
|
|
99
|
+
|
|
100
|
+
# Direct invocation
|
|
101
|
+
python3 scripts/setup-dev.py
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Installs minimal dependencies needed for development (ruff for linting/formatting).
|
|
105
|
+
|
|
106
|
+
## Common Workflows
|
|
107
|
+
|
|
108
|
+
### Creating a New Release
|
|
109
|
+
|
|
110
|
+
1. **Update changelog:**
|
|
111
|
+
```bash
|
|
112
|
+
# Edit CHANGELOG.rst with new version and changes
|
|
113
|
+
git add CHANGELOG.rst
|
|
114
|
+
git commit -m "Update changelog for vX.Y.Z"
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
2. **Validate everything:**
|
|
118
|
+
```bash
|
|
119
|
+
make check-release # Runs lint, format-check, tests, and validate-version
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
3. **Bump version:**
|
|
123
|
+
```bash
|
|
124
|
+
make version-bump BUMP=patch # or minor/major
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
4. **Push tag:**
|
|
128
|
+
```bash
|
|
129
|
+
git push origin vX.Y.Z
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
5. **Build and publish:**
|
|
133
|
+
```bash
|
|
134
|
+
make build
|
|
135
|
+
make publish-test # Test on TestPyPI first
|
|
136
|
+
make publish # Publish to PyPI
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Before Committing
|
|
140
|
+
|
|
141
|
+
Always run these checks:
|
|
142
|
+
```bash
|
|
143
|
+
make ci-lint # Check code style
|
|
144
|
+
make validate-version # Check version config
|
|
145
|
+
python3 -m mypy src/nwp500 --config-file pyproject.toml # Type checking
|
|
146
|
+
pytest # Run tests
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Or run all checks at once:
|
|
150
|
+
```bash
|
|
151
|
+
make check-release
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Version Management Details
|
|
155
|
+
|
|
156
|
+
### How Versions Work
|
|
157
|
+
|
|
158
|
+
This project uses `setuptools_scm` which:
|
|
159
|
+
- Derives the package version from git tags
|
|
160
|
+
- Automatically handles development versions (e.g., `3.1.5.dev1+g1234567`)
|
|
161
|
+
- Requires no manual version editing in source files
|
|
162
|
+
|
|
163
|
+
### What NOT to Do
|
|
164
|
+
|
|
165
|
+
❌ **Never edit the version in `setup.cfg`'s `[pyscaffold]` section!**
|
|
166
|
+
- That field is the PyScaffold tool version (4.6), not the package version
|
|
167
|
+
- Changing it to 4.7 was the bug that caused the version jump from 3.1.4 to 4.7
|
|
168
|
+
|
|
169
|
+
❌ **Never add `__version__` to source code**
|
|
170
|
+
- Version is derived from git tags, not hardcoded
|
|
171
|
+
|
|
172
|
+
❌ **Never create tags manually without validation**
|
|
173
|
+
- Use `make version-bump` which validates version progression
|
|
174
|
+
|
|
175
|
+
### What TO Do
|
|
176
|
+
|
|
177
|
+
✅ Use `make version-bump BUMP=<type>` to create new versions
|
|
178
|
+
|
|
179
|
+
✅ Run `make validate-version` before releases
|
|
180
|
+
|
|
181
|
+
✅ Let `setuptools_scm` derive versions from git tags
|
|
182
|
+
|
|
183
|
+
✅ Follow semantic versioning:
|
|
184
|
+
- **Patch** (X.Y.Z+1): Bug fixes, no API changes
|
|
185
|
+
- **Minor** (X.Y+1.0): New features, backward compatible
|
|
186
|
+
- **Major** (X+1.0.0): Breaking changes
|
|
187
|
+
|
|
188
|
+
## Script Maintenance
|
|
189
|
+
|
|
190
|
+
### Adding New Scripts
|
|
191
|
+
|
|
192
|
+
1. Create script in `scripts/` directory
|
|
193
|
+
2. Make it executable: `chmod +x scripts/yourscript.py`
|
|
194
|
+
3. Add usage to this README
|
|
195
|
+
4. Add Makefile target if appropriate
|
|
196
|
+
5. Consider adding to `make check-release` if it's a validation script
|
|
197
|
+
|
|
198
|
+
### Testing Scripts
|
|
199
|
+
|
|
200
|
+
Test scripts manually before committing:
|
|
201
|
+
```bash
|
|
202
|
+
python3 scripts/validate_version.py # Should pass
|
|
203
|
+
python3 scripts/bump_version.py # Should show usage
|
|
204
|
+
python3 scripts/lint.py # Should run linting
|
|
205
|
+
python3 scripts/format.py # Should format code
|
|
206
|
+
```
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Version bump script for nwp500-python.
|
|
4
|
+
|
|
5
|
+
This script helps create new releases by:
|
|
6
|
+
1. Getting the current version from git tags
|
|
7
|
+
2. Computing the next version based on bump type (major, minor, patch)
|
|
8
|
+
3. Validating the new version
|
|
9
|
+
4. Creating a new git tag
|
|
10
|
+
|
|
11
|
+
Usage:
|
|
12
|
+
python scripts/bump_version.py patch # 3.1.4 -> 3.1.5
|
|
13
|
+
python scripts/bump_version.py minor # 3.1.4 -> 3.2.0
|
|
14
|
+
python scripts/bump_version.py major # 3.1.4 -> 4.0.0
|
|
15
|
+
python scripts/bump_version.py 3.1.5 # Explicit version
|
|
16
|
+
|
|
17
|
+
The script uses setuptools_scm to derive versions from git tags.
|
|
18
|
+
DO NOT manually edit version numbers in setup.cfg - the [pyscaffold] version
|
|
19
|
+
field is for the PyScaffold tool version, not the package version!
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
import re
|
|
23
|
+
import subprocess
|
|
24
|
+
import sys
|
|
25
|
+
from typing import Tuple
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def run_git_command(args: list) -> str:
|
|
29
|
+
"""Run a git command and return the output."""
|
|
30
|
+
try:
|
|
31
|
+
result = subprocess.run(
|
|
32
|
+
["git"] + args,
|
|
33
|
+
capture_output=True,
|
|
34
|
+
text=True,
|
|
35
|
+
check=True,
|
|
36
|
+
)
|
|
37
|
+
return result.stdout.strip()
|
|
38
|
+
except subprocess.CalledProcessError as e:
|
|
39
|
+
print(f"Error running git command: {e}", file=sys.stderr)
|
|
40
|
+
print(f"stderr: {e.stderr}", file=sys.stderr)
|
|
41
|
+
sys.exit(1)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def get_current_version() -> str:
|
|
45
|
+
"""Get the current version from git tags."""
|
|
46
|
+
# Get all tags sorted by version
|
|
47
|
+
tags_output = run_git_command(["tag", "-l", "v*", "--sort=-version:refname"])
|
|
48
|
+
|
|
49
|
+
if not tags_output:
|
|
50
|
+
print("No version tags found. Starting from v0.0.0")
|
|
51
|
+
return "0.0.0"
|
|
52
|
+
|
|
53
|
+
# Get the most recent tag
|
|
54
|
+
latest_tag = tags_output.split("\n")[0]
|
|
55
|
+
|
|
56
|
+
# Remove the 'v' prefix
|
|
57
|
+
version = latest_tag[1:] if latest_tag.startswith("v") else latest_tag
|
|
58
|
+
|
|
59
|
+
return version
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def parse_version(version_str: str) -> Tuple[int, int, int]:
|
|
63
|
+
"""Parse a version string into (major, minor, patch) tuple."""
|
|
64
|
+
match = re.match(r"^(\d+)\.(\d+)\.(\d+)$", version_str)
|
|
65
|
+
if not match:
|
|
66
|
+
print(f"Error: Invalid version format: {version_str}", file=sys.stderr)
|
|
67
|
+
print("Version must be in format: X.Y.Z", file=sys.stderr)
|
|
68
|
+
sys.exit(1)
|
|
69
|
+
|
|
70
|
+
return int(match.group(1)), int(match.group(2)), int(match.group(3))
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def bump_version(version_str: str, bump_type: str) -> str:
|
|
74
|
+
"""Bump a version string according to the bump type."""
|
|
75
|
+
major, minor, patch = parse_version(version_str)
|
|
76
|
+
|
|
77
|
+
if bump_type == "major":
|
|
78
|
+
return f"{major + 1}.0.0"
|
|
79
|
+
elif bump_type == "minor":
|
|
80
|
+
return f"{major}.{minor + 1}.0"
|
|
81
|
+
elif bump_type == "patch":
|
|
82
|
+
return f"{major}.{minor}.{patch + 1}"
|
|
83
|
+
else:
|
|
84
|
+
# Assume it's an explicit version number
|
|
85
|
+
parse_version(bump_type) # Validate format
|
|
86
|
+
return bump_type
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def validate_version_progression(current: str, new: str) -> None:
|
|
90
|
+
"""Validate that the new version is a proper progression from current."""
|
|
91
|
+
curr_major, curr_minor, curr_patch = parse_version(current)
|
|
92
|
+
new_major, new_minor, new_patch = parse_version(new)
|
|
93
|
+
|
|
94
|
+
# Check if new version is greater than current
|
|
95
|
+
curr_tuple = (curr_major, curr_minor, curr_patch)
|
|
96
|
+
new_tuple = (new_major, new_minor, new_patch)
|
|
97
|
+
|
|
98
|
+
if new_tuple <= curr_tuple:
|
|
99
|
+
print(f"Error: New version {new} is not greater than current version {current}", file=sys.stderr)
|
|
100
|
+
sys.exit(1)
|
|
101
|
+
|
|
102
|
+
# Check for unreasonable jumps (more than 1 major version or unusual patterns)
|
|
103
|
+
major_jump = new_major - curr_major
|
|
104
|
+
minor_jump = new_minor - curr_minor
|
|
105
|
+
patch_jump = new_patch - curr_patch
|
|
106
|
+
|
|
107
|
+
if major_jump > 1:
|
|
108
|
+
print(f"Warning: Large major version jump detected ({current} -> {new})")
|
|
109
|
+
print(f"This will jump from {curr_major}.x.x to {new_major}.x.x")
|
|
110
|
+
response = input("Are you sure? (yes/no): ")
|
|
111
|
+
if response.lower() != "yes":
|
|
112
|
+
print("Version bump cancelled.")
|
|
113
|
+
sys.exit(1)
|
|
114
|
+
|
|
115
|
+
if major_jump == 0 and minor_jump > 5:
|
|
116
|
+
print(f"Warning: Large minor version jump detected ({current} -> {new})")
|
|
117
|
+
print(f"This will jump from x.{curr_minor}.x to x.{new_minor}.x")
|
|
118
|
+
response = input("Are you sure? (yes/no): ")
|
|
119
|
+
if response.lower() != "yes":
|
|
120
|
+
print("Version bump cancelled.")
|
|
121
|
+
sys.exit(1)
|
|
122
|
+
|
|
123
|
+
if major_jump == 0 and minor_jump == 0 and patch_jump > 10:
|
|
124
|
+
print(f"Warning: Large patch version jump detected ({current} -> {new})")
|
|
125
|
+
print(f"This will jump from x.x.{curr_patch} to x.x.{new_patch}")
|
|
126
|
+
response = input("Are you sure? (yes/no): ")
|
|
127
|
+
if response.lower() != "yes":
|
|
128
|
+
print("Version bump cancelled.")
|
|
129
|
+
sys.exit(1)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def check_working_directory_clean() -> None:
|
|
133
|
+
"""Check if the git working directory is clean."""
|
|
134
|
+
status = run_git_command(["status", "--porcelain"])
|
|
135
|
+
if status:
|
|
136
|
+
print("Error: Working directory is not clean.", file=sys.stderr)
|
|
137
|
+
print("Please commit or stash your changes before bumping version.", file=sys.stderr)
|
|
138
|
+
sys.exit(1)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def create_tag(version: str, message: str = None) -> None:
|
|
142
|
+
"""Create a git tag for the version."""
|
|
143
|
+
tag_name = f"v{version}"
|
|
144
|
+
|
|
145
|
+
# Check if tag already exists
|
|
146
|
+
try:
|
|
147
|
+
subprocess.run(
|
|
148
|
+
["git", "rev-parse", tag_name],
|
|
149
|
+
capture_output=True,
|
|
150
|
+
check=True,
|
|
151
|
+
)
|
|
152
|
+
print(f"Error: Tag {tag_name} already exists.", file=sys.stderr)
|
|
153
|
+
sys.exit(1)
|
|
154
|
+
except subprocess.CalledProcessError:
|
|
155
|
+
# Tag doesn't exist, which is what we want
|
|
156
|
+
pass
|
|
157
|
+
|
|
158
|
+
# Create the tag
|
|
159
|
+
if message:
|
|
160
|
+
run_git_command(["tag", "-a", tag_name, "-m", message])
|
|
161
|
+
else:
|
|
162
|
+
run_git_command(["tag", "-a", tag_name, "-m", f"Release version {version}"])
|
|
163
|
+
|
|
164
|
+
print(f"✓ Created tag: {tag_name}")
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def main() -> None:
|
|
168
|
+
"""Main entry point."""
|
|
169
|
+
if len(sys.argv) != 2:
|
|
170
|
+
print("Usage: python scripts/bump_version.py [major|minor|patch|X.Y.Z]", file=sys.stderr)
|
|
171
|
+
print("\nExamples:", file=sys.stderr)
|
|
172
|
+
print(" python scripts/bump_version.py patch # Bump patch version", file=sys.stderr)
|
|
173
|
+
print(" python scripts/bump_version.py minor # Bump minor version", file=sys.stderr)
|
|
174
|
+
print(" python scripts/bump_version.py major # Bump major version", file=sys.stderr)
|
|
175
|
+
print(" python scripts/bump_version.py 3.1.5 # Set explicit version", file=sys.stderr)
|
|
176
|
+
sys.exit(1)
|
|
177
|
+
|
|
178
|
+
bump_type = sys.argv[1]
|
|
179
|
+
|
|
180
|
+
# Validate bump type
|
|
181
|
+
if bump_type not in ["major", "minor", "patch"]:
|
|
182
|
+
# Check if it's a valid version number
|
|
183
|
+
try:
|
|
184
|
+
parse_version(bump_type)
|
|
185
|
+
except SystemExit:
|
|
186
|
+
print(f"Error: Invalid bump type: {bump_type}", file=sys.stderr)
|
|
187
|
+
print("Must be one of: major, minor, patch, or X.Y.Z", file=sys.stderr)
|
|
188
|
+
sys.exit(1)
|
|
189
|
+
|
|
190
|
+
# Check working directory is clean
|
|
191
|
+
check_working_directory_clean()
|
|
192
|
+
|
|
193
|
+
# Get current version
|
|
194
|
+
current_version = get_current_version()
|
|
195
|
+
print(f"Current version: {current_version}")
|
|
196
|
+
|
|
197
|
+
# Calculate new version
|
|
198
|
+
new_version = bump_version(current_version, bump_type)
|
|
199
|
+
print(f"New version: {new_version}")
|
|
200
|
+
|
|
201
|
+
# Validate version progression
|
|
202
|
+
validate_version_progression(current_version, new_version)
|
|
203
|
+
|
|
204
|
+
# Confirm with user
|
|
205
|
+
print(f"\nThis will create tag v{new_version}")
|
|
206
|
+
print("Make sure you have:")
|
|
207
|
+
print(" 1. Updated CHANGELOG.rst")
|
|
208
|
+
print(" 2. Committed all changes")
|
|
209
|
+
print(" 3. Run tests and linting")
|
|
210
|
+
response = input("\nProceed with version bump? (yes/no): ")
|
|
211
|
+
|
|
212
|
+
if response.lower() != "yes":
|
|
213
|
+
print("Version bump cancelled.")
|
|
214
|
+
sys.exit(0)
|
|
215
|
+
|
|
216
|
+
# Create the tag
|
|
217
|
+
create_tag(new_version)
|
|
218
|
+
|
|
219
|
+
print("\n✓ Version bump complete!")
|
|
220
|
+
print("\nNext steps:")
|
|
221
|
+
print(f" 1. Push the tag: git push origin v{new_version}")
|
|
222
|
+
print(" 2. Build release: make build")
|
|
223
|
+
print(" 3. Test on TestPyPI: make publish-test")
|
|
224
|
+
print(" 4. Publish to PyPI: make publish")
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
if __name__ == "__main__":
|
|
228
|
+
main()
|