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.
Files changed (130) hide show
  1. {nwp500_python-4.7 → nwp500_python-4.7.1}/.github/copilot-instructions.md +27 -0
  2. {nwp500_python-4.7 → nwp500_python-4.7.1}/CHANGELOG.rst +8 -0
  3. {nwp500_python-4.7 → nwp500_python-4.7.1}/Makefile +18 -2
  4. {nwp500_python-4.7/src/nwp500_python.egg-info → nwp500_python-4.7.1}/PKG-INFO +1 -1
  5. {nwp500_python-4.7 → nwp500_python-4.7.1}/RELEASE.md +69 -6
  6. nwp500_python-4.7.1/scripts/README.md +206 -0
  7. nwp500_python-4.7.1/scripts/bump_version.py +228 -0
  8. nwp500_python-4.7.1/scripts/validate_version.py +152 -0
  9. {nwp500_python-4.7 → nwp500_python-4.7.1}/setup.cfg +1 -1
  10. {nwp500_python-4.7 → nwp500_python-4.7.1/src/nwp500_python.egg-info}/PKG-INFO +1 -1
  11. {nwp500_python-4.7 → nwp500_python-4.7.1}/src/nwp500_python.egg-info/SOURCES.txt +3 -0
  12. {nwp500_python-4.7 → nwp500_python-4.7.1}/.coveragerc +0 -0
  13. {nwp500_python-4.7 → nwp500_python-4.7.1}/.github/workflows/ci.yml +0 -0
  14. {nwp500_python-4.7 → nwp500_python-4.7.1}/.github/workflows/release.yml +0 -0
  15. {nwp500_python-4.7 → nwp500_python-4.7.1}/.gitignore +0 -0
  16. {nwp500_python-4.7 → nwp500_python-4.7.1}/.pre-commit-config.yaml +0 -0
  17. {nwp500_python-4.7 → nwp500_python-4.7.1}/.readthedocs.yml +0 -0
  18. {nwp500_python-4.7 → nwp500_python-4.7.1}/AUTHORS.rst +0 -0
  19. {nwp500_python-4.7 → nwp500_python-4.7.1}/CONTRIBUTING.rst +0 -0
  20. {nwp500_python-4.7 → nwp500_python-4.7.1}/LICENSE.txt +0 -0
  21. {nwp500_python-4.7 → nwp500_python-4.7.1}/README.rst +0 -0
  22. {nwp500_python-4.7 → nwp500_python-4.7.1}/docs/Makefile +0 -0
  23. {nwp500_python-4.7 → nwp500_python-4.7.1}/docs/_static/.gitignore +0 -0
  24. {nwp500_python-4.7 → nwp500_python-4.7.1}/docs/authors.rst +0 -0
  25. {nwp500_python-4.7 → nwp500_python-4.7.1}/docs/changelog.rst +0 -0
  26. {nwp500_python-4.7 → nwp500_python-4.7.1}/docs/conf.py +0 -0
  27. {nwp500_python-4.7 → nwp500_python-4.7.1}/docs/configuration.rst +0 -0
  28. {nwp500_python-4.7 → nwp500_python-4.7.1}/docs/development/contributing.rst +0 -0
  29. {nwp500_python-4.7 → nwp500_python-4.7.1}/docs/development/history.rst +0 -0
  30. {nwp500_python-4.7 → nwp500_python-4.7.1}/docs/guides/auto_recovery.rst +0 -0
  31. {nwp500_python-4.7 → nwp500_python-4.7.1}/docs/guides/command_queue.rst +0 -0
  32. {nwp500_python-4.7 → nwp500_python-4.7.1}/docs/guides/energy_monitoring.rst +0 -0
  33. {nwp500_python-4.7 → nwp500_python-4.7.1}/docs/guides/event_system.rst +0 -0
  34. {nwp500_python-4.7 → nwp500_python-4.7.1}/docs/guides/reservations.rst +0 -0
  35. {nwp500_python-4.7 → nwp500_python-4.7.1}/docs/guides/time_of_use.rst +0 -0
  36. {nwp500_python-4.7 → nwp500_python-4.7.1}/docs/index.rst +0 -0
  37. {nwp500_python-4.7 → nwp500_python-4.7.1}/docs/installation.rst +0 -0
  38. {nwp500_python-4.7 → nwp500_python-4.7.1}/docs/license.rst +0 -0
  39. {nwp500_python-4.7 → nwp500_python-4.7.1}/docs/openapi.yaml +0 -0
  40. {nwp500_python-4.7 → nwp500_python-4.7.1}/docs/protocol/device_features.rst +0 -0
  41. {nwp500_python-4.7 → nwp500_python-4.7.1}/docs/protocol/device_status.rst +0 -0
  42. {nwp500_python-4.7 → nwp500_python-4.7.1}/docs/protocol/error_codes.rst +0 -0
  43. {nwp500_python-4.7 → nwp500_python-4.7.1}/docs/protocol/firmware_tracking.rst +0 -0
  44. {nwp500_python-4.7 → nwp500_python-4.7.1}/docs/protocol/mqtt_protocol.rst +0 -0
  45. {nwp500_python-4.7 → nwp500_python-4.7.1}/docs/protocol/rest_api.rst +0 -0
  46. {nwp500_python-4.7 → nwp500_python-4.7.1}/docs/python_api/api_client.rst +0 -0
  47. {nwp500_python-4.7 → nwp500_python-4.7.1}/docs/python_api/auth_client.rst +0 -0
  48. {nwp500_python-4.7 → nwp500_python-4.7.1}/docs/python_api/cli.rst +0 -0
  49. {nwp500_python-4.7 → nwp500_python-4.7.1}/docs/python_api/constants.rst +0 -0
  50. {nwp500_python-4.7 → nwp500_python-4.7.1}/docs/python_api/events.rst +0 -0
  51. {nwp500_python-4.7 → nwp500_python-4.7.1}/docs/python_api/exceptions.rst +0 -0
  52. {nwp500_python-4.7 → nwp500_python-4.7.1}/docs/python_api/models.rst +0 -0
  53. {nwp500_python-4.7 → nwp500_python-4.7.1}/docs/python_api/mqtt_client.rst +0 -0
  54. {nwp500_python-4.7 → nwp500_python-4.7.1}/docs/quickstart.rst +0 -0
  55. {nwp500_python-4.7 → nwp500_python-4.7.1}/docs/requirements.txt +0 -0
  56. {nwp500_python-4.7 → nwp500_python-4.7.1}/examples/.ruff.toml +0 -0
  57. {nwp500_python-4.7 → nwp500_python-4.7.1}/examples/README.md +0 -0
  58. {nwp500_python-4.7 → nwp500_python-4.7.1}/examples/anti_legionella_example.py +0 -0
  59. {nwp500_python-4.7 → nwp500_python-4.7.1}/examples/api_client_example.py +0 -0
  60. {nwp500_python-4.7 → nwp500_python-4.7.1}/examples/auth_constructor_example.py +0 -0
  61. {nwp500_python-4.7 → nwp500_python-4.7.1}/examples/authenticate.py +0 -0
  62. {nwp500_python-4.7 → nwp500_python-4.7.1}/examples/auto_recovery_example.py +0 -0
  63. {nwp500_python-4.7 → nwp500_python-4.7.1}/examples/combined_callbacks.py +0 -0
  64. {nwp500_python-4.7 → nwp500_python-4.7.1}/examples/command_queue_demo.py +0 -0
  65. {nwp500_python-4.7 → nwp500_python-4.7.1}/examples/device_feature_callback.py +0 -0
  66. {nwp500_python-4.7 → nwp500_python-4.7.1}/examples/device_status_callback.py +0 -0
  67. {nwp500_python-4.7 → nwp500_python-4.7.1}/examples/device_status_callback_debug.py +0 -0
  68. {nwp500_python-4.7 → nwp500_python-4.7.1}/examples/energy_usage_example.py +0 -0
  69. {nwp500_python-4.7 → nwp500_python-4.7.1}/examples/event_emitter_demo.py +0 -0
  70. {nwp500_python-4.7 → nwp500_python-4.7.1}/examples/improved_auth_pattern.py +0 -0
  71. {nwp500_python-4.7 → nwp500_python-4.7.1}/examples/mask.py +0 -0
  72. {nwp500_python-4.7 → nwp500_python-4.7.1}/examples/mqtt_client_example.py +0 -0
  73. {nwp500_python-4.7 → nwp500_python-4.7.1}/examples/periodic_device_info.py +0 -0
  74. {nwp500_python-4.7 → nwp500_python-4.7.1}/examples/periodic_requests.py +0 -0
  75. {nwp500_python-4.7 → nwp500_python-4.7.1}/examples/power_control_example.py +0 -0
  76. {nwp500_python-4.7 → nwp500_python-4.7.1}/examples/reconnection_demo.py +0 -0
  77. {nwp500_python-4.7 → nwp500_python-4.7.1}/examples/reservation_schedule_example.py +0 -0
  78. {nwp500_python-4.7 → nwp500_python-4.7.1}/examples/set_dhw_temperature_example.py +0 -0
  79. {nwp500_python-4.7 → nwp500_python-4.7.1}/examples/set_mode_example.py +0 -0
  80. {nwp500_python-4.7 → nwp500_python-4.7.1}/examples/simple_auto_recovery.py +0 -0
  81. {nwp500_python-4.7 → nwp500_python-4.7.1}/examples/simple_periodic_info.py +0 -0
  82. {nwp500_python-4.7 → nwp500_python-4.7.1}/examples/simple_periodic_status.py +0 -0
  83. {nwp500_python-4.7 → nwp500_python-4.7.1}/examples/test_api_client.py +0 -0
  84. {nwp500_python-4.7 → nwp500_python-4.7.1}/examples/test_mqtt_connection.py +0 -0
  85. {nwp500_python-4.7 → nwp500_python-4.7.1}/examples/test_mqtt_messaging.py +0 -0
  86. {nwp500_python-4.7 → nwp500_python-4.7.1}/examples/test_periodic_minimal.py +0 -0
  87. {nwp500_python-4.7 → nwp500_python-4.7.1}/examples/tou_openei_example.py +0 -0
  88. {nwp500_python-4.7 → nwp500_python-4.7.1}/examples/tou_schedule_example.py +0 -0
  89. {nwp500_python-4.7 → nwp500_python-4.7.1}/pyproject.toml +0 -0
  90. {nwp500_python-4.7 → nwp500_python-4.7.1}/scripts/format.py +0 -0
  91. {nwp500_python-4.7 → nwp500_python-4.7.1}/scripts/lint.py +0 -0
  92. {nwp500_python-4.7 → nwp500_python-4.7.1}/scripts/setup-dev.py +0 -0
  93. {nwp500_python-4.7 → nwp500_python-4.7.1}/setup.py +0 -0
  94. {nwp500_python-4.7 → nwp500_python-4.7.1}/src/nwp500/__init__.py +0 -0
  95. {nwp500_python-4.7 → nwp500_python-4.7.1}/src/nwp500/api_client.py +0 -0
  96. {nwp500_python-4.7 → nwp500_python-4.7.1}/src/nwp500/auth.py +0 -0
  97. {nwp500_python-4.7 → nwp500_python-4.7.1}/src/nwp500/cli/__init__.py +0 -0
  98. {nwp500_python-4.7 → nwp500_python-4.7.1}/src/nwp500/cli/__main__.py +0 -0
  99. {nwp500_python-4.7 → nwp500_python-4.7.1}/src/nwp500/cli/commands.py +0 -0
  100. {nwp500_python-4.7 → nwp500_python-4.7.1}/src/nwp500/cli/monitoring.py +0 -0
  101. {nwp500_python-4.7 → nwp500_python-4.7.1}/src/nwp500/cli/output_formatters.py +0 -0
  102. {nwp500_python-4.7 → nwp500_python-4.7.1}/src/nwp500/cli/token_storage.py +0 -0
  103. {nwp500_python-4.7 → nwp500_python-4.7.1}/src/nwp500/cli.py +0 -0
  104. {nwp500_python-4.7 → nwp500_python-4.7.1}/src/nwp500/config.py +0 -0
  105. {nwp500_python-4.7 → nwp500_python-4.7.1}/src/nwp500/constants.py +0 -0
  106. {nwp500_python-4.7 → nwp500_python-4.7.1}/src/nwp500/encoding.py +0 -0
  107. {nwp500_python-4.7 → nwp500_python-4.7.1}/src/nwp500/events.py +0 -0
  108. {nwp500_python-4.7 → nwp500_python-4.7.1}/src/nwp500/models.py +0 -0
  109. {nwp500_python-4.7 → nwp500_python-4.7.1}/src/nwp500/mqtt_client.py +0 -0
  110. {nwp500_python-4.7 → nwp500_python-4.7.1}/src/nwp500/mqtt_command_queue.py +0 -0
  111. {nwp500_python-4.7 → nwp500_python-4.7.1}/src/nwp500/mqtt_connection.py +0 -0
  112. {nwp500_python-4.7 → nwp500_python-4.7.1}/src/nwp500/mqtt_device_control.py +0 -0
  113. {nwp500_python-4.7 → nwp500_python-4.7.1}/src/nwp500/mqtt_periodic.py +0 -0
  114. {nwp500_python-4.7 → nwp500_python-4.7.1}/src/nwp500/mqtt_reconnection.py +0 -0
  115. {nwp500_python-4.7 → nwp500_python-4.7.1}/src/nwp500/mqtt_subscriptions.py +0 -0
  116. {nwp500_python-4.7 → nwp500_python-4.7.1}/src/nwp500/mqtt_utils.py +0 -0
  117. {nwp500_python-4.7 → nwp500_python-4.7.1}/src/nwp500/py.typed +0 -0
  118. {nwp500_python-4.7 → nwp500_python-4.7.1}/src/nwp500/utils.py +0 -0
  119. {nwp500_python-4.7 → nwp500_python-4.7.1}/src/nwp500_python.egg-info/dependency_links.txt +0 -0
  120. {nwp500_python-4.7 → nwp500_python-4.7.1}/src/nwp500_python.egg-info/entry_points.txt +0 -0
  121. {nwp500_python-4.7 → nwp500_python-4.7.1}/src/nwp500_python.egg-info/not-zip-safe +0 -0
  122. {nwp500_python-4.7 → nwp500_python-4.7.1}/src/nwp500_python.egg-info/requires.txt +0 -0
  123. {nwp500_python-4.7 → nwp500_python-4.7.1}/src/nwp500_python.egg-info/top_level.txt +0 -0
  124. {nwp500_python-4.7 → nwp500_python-4.7.1}/tests/conftest.py +0 -0
  125. {nwp500_python-4.7 → nwp500_python-4.7.1}/tests/test_api_helpers.py +0 -0
  126. {nwp500_python-4.7 → nwp500_python-4.7.1}/tests/test_auth.py +0 -0
  127. {nwp500_python-4.7 → nwp500_python-4.7.1}/tests/test_command_queue.py +0 -0
  128. {nwp500_python-4.7 → nwp500_python-4.7.1}/tests/test_events.py +0 -0
  129. {nwp500_python-4.7 → nwp500_python-4.7.1}/tests/test_utils.py +0 -0
  130. {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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nwp500-python
3
- Version: 4.7
3
+ Version: 4.7.1
4
4
  Summary: A library for controlling Navien NWP500 Water Heaters via NaviLink
5
5
  Home-page: https://github.com/eman/nwp500-python
6
6
  Author: Emmanuel Levijarvi
@@ -95,15 +95,74 @@ This runs:
95
95
 
96
96
  ### 4. Update Version and Changelog
97
97
 
98
- 1. Update version in relevant files (handled by setuptools_scm)
99
- 2. Update `CHANGELOG.rst` with changes for this release
100
- 3. Commit changes:
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
- Create and push a git tag:
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()