pkg-ext 0.1.0__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.
- pkg_ext-0.1.0/.gitignore +44 -0
- pkg_ext-0.1.0/LICENSE +21 -0
- pkg_ext-0.1.0/PKG-INFO +439 -0
- pkg_ext-0.1.0/README.md +421 -0
- pkg_ext-0.1.0/pkg_ext/__init__.py +20 -0
- pkg_ext-0.1.0/pkg_ext/_internal/__init__.py +1 -0
- pkg_ext-0.1.0/pkg_ext/_internal/__main__.py +4 -0
- pkg_ext-0.1.0/pkg_ext/_internal/api_diff.py +449 -0
- pkg_ext-0.1.0/pkg_ext/_internal/api_dumper.py +218 -0
- pkg_ext-0.1.0/pkg_ext/_internal/changelog/__init__.py +59 -0
- pkg_ext-0.1.0/pkg_ext/_internal/changelog/actions.py +432 -0
- pkg_ext-0.1.0/pkg_ext/_internal/changelog/committer.py +147 -0
- pkg_ext-0.1.0/pkg_ext/_internal/changelog/parser.py +27 -0
- pkg_ext-0.1.0/pkg_ext/_internal/changelog/write_changelog_md.py +195 -0
- pkg_ext-0.1.0/pkg_ext/_internal/cli/__init__.py +13 -0
- pkg_ext-0.1.0/pkg_ext/_internal/cli/api_cmds.py +65 -0
- pkg_ext-0.1.0/pkg_ext/_internal/cli/base_commands.py +123 -0
- pkg_ext-0.1.0/pkg_ext/_internal/cli/changelog_cmds.py +134 -0
- pkg_ext-0.1.0/pkg_ext/_internal/cli/gen_cmds.py +51 -0
- pkg_ext-0.1.0/pkg_ext/_internal/cli/options.py +52 -0
- pkg_ext-0.1.0/pkg_ext/_internal/cli/stability.py +127 -0
- pkg_ext-0.1.0/pkg_ext/_internal/cli/stability_cmds.py +120 -0
- pkg_ext-0.1.0/pkg_ext/_internal/cli/workflow_cmds.py +340 -0
- pkg_ext-0.1.0/pkg_ext/_internal/cli/workflows.py +288 -0
- pkg_ext-0.1.0/pkg_ext/_internal/cli.py +14 -0
- pkg_ext-0.1.0/pkg_ext/_internal/config.py +240 -0
- pkg_ext-0.1.0/pkg_ext/_internal/context.py +118 -0
- pkg_ext-0.1.0/pkg_ext/_internal/errors.py +49 -0
- pkg_ext-0.1.0/pkg_ext/_internal/file_parser.py +199 -0
- pkg_ext-0.1.0/pkg_ext/_internal/generation/__init__.py +16 -0
- pkg_ext-0.1.0/pkg_ext/_internal/generation/docs.py +226 -0
- pkg_ext-0.1.0/pkg_ext/_internal/generation/docs_constants.py +7 -0
- pkg_ext-0.1.0/pkg_ext/_internal/generation/docs_mkdocs.py +174 -0
- pkg_ext-0.1.0/pkg_ext/_internal/generation/docs_render.py +473 -0
- pkg_ext-0.1.0/pkg_ext/_internal/generation/docs_version.py +132 -0
- pkg_ext-0.1.0/pkg_ext/_internal/generation/example_gen.py +183 -0
- pkg_ext-0.1.0/pkg_ext/_internal/generation/groups.py +69 -0
- pkg_ext-0.1.0/pkg_ext/_internal/generation/init_file.py +55 -0
- pkg_ext-0.1.0/pkg_ext/_internal/generation/pyproject.py +15 -0
- pkg_ext-0.1.0/pkg_ext/_internal/generation/test_gen.py +103 -0
- pkg_ext-0.1.0/pkg_ext/_internal/git_usage/__init__.py +28 -0
- pkg_ext-0.1.0/pkg_ext/_internal/git_usage/actions.py +36 -0
- pkg_ext-0.1.0/pkg_ext/_internal/git_usage/state.py +280 -0
- pkg_ext-0.1.0/pkg_ext/_internal/git_usage/url.py +44 -0
- pkg_ext-0.1.0/pkg_ext/_internal/interactive.py +185 -0
- pkg_ext-0.1.0/pkg_ext/_internal/models/__init__.py +78 -0
- pkg_ext-0.1.0/pkg_ext/_internal/models/api_dump.py +138 -0
- pkg_ext-0.1.0/pkg_ext/_internal/models/code_state.py +96 -0
- pkg_ext-0.1.0/pkg_ext/_internal/models/groups.py +176 -0
- pkg_ext-0.1.0/pkg_ext/_internal/models/py_files.py +108 -0
- pkg_ext-0.1.0/pkg_ext/_internal/models/py_symbols.py +90 -0
- pkg_ext-0.1.0/pkg_ext/_internal/models/ref_state.py +47 -0
- pkg_ext-0.1.0/pkg_ext/_internal/models/types.py +57 -0
- pkg_ext-0.1.0/pkg_ext/_internal/pkg_state.py +229 -0
- pkg_ext-0.1.0/pkg_ext/_internal/py_format.py +66 -0
- pkg_ext-0.1.0/pkg_ext/_internal/reference_handling/__init__.py +9 -0
- pkg_ext-0.1.0/pkg_ext/_internal/reference_handling/added.py +219 -0
- pkg_ext-0.1.0/pkg_ext/_internal/reference_handling/promote.py +150 -0
- pkg_ext-0.1.0/pkg_ext/_internal/reference_handling/removed.py +64 -0
- pkg_ext-0.1.0/pkg_ext/_internal/settings.py +190 -0
- pkg_ext-0.1.0/pkg_ext/_internal/signature_parser.py +402 -0
- pkg_ext-0.1.0/pkg_ext/_internal/testdata/e2e/01_initial/my_pkg/__init__.py +10 -0
- pkg_ext-0.1.0/pkg_ext/_internal/testdata/e2e/01_initial/my_pkg/_internal.py +13 -0
- pkg_ext-0.1.0/pkg_ext/_internal/testdata/e2e/01_initial/my_pkg/_internal2.py +6 -0
- pkg_ext-0.1.0/pkg_ext/_internal/testdata/e2e/01_initial/my_pkg/my_dep.py +4 -0
- pkg_ext-0.1.0/pkg_ext/_internal/testdata/e2e/01_initial/my_pkg/my_group.py +6 -0
- pkg_ext-0.1.0/pkg_ext/_internal/testdata/e2e/02_dep_order/my_pkg/__init__.py +8 -0
- pkg_ext-0.1.0/pkg_ext/_internal/testdata/e2e/02_dep_order/my_pkg/a.py +5 -0
- pkg_ext-0.1.0/pkg_ext/_internal/testdata/e2e/02_dep_order/my_pkg/b.py +5 -0
- pkg_ext-0.1.0/pkg_ext/_internal/testdata/e2e/02_dep_order/my_pkg/c.py +2 -0
- pkg_ext-0.1.0/pkg_ext/_internal/testdata/e2e/02_dep_order/my_pkg/g1.py +8 -0
- pkg_ext-0.1.0/pkg_ext/_internal/testdata/e2e/03_nested/my_pkg/__init__.py +8 -0
- pkg_ext-0.1.0/pkg_ext/_internal/testdata/e2e/03_nested/my_pkg/_internal/__init__.py +0 -0
- pkg_ext-0.1.0/pkg_ext/_internal/testdata/e2e/03_nested/my_pkg/_internal/a.py +6 -0
- pkg_ext-0.1.0/pkg_ext/_internal/testdata/e2e/03_nested/my_pkg/n1.py +6 -0
- pkg_ext-0.1.0/pkg_ext/_internal/testdata/e2e/04_git_fix/my_pkg/__init__.py +8 -0
- pkg_ext-0.1.0/pkg_ext/_internal/testdata/e2e/04_git_fix/my_pkg/chosen.py +2 -0
- pkg_ext-0.1.0/pkg_ext/_internal/testdata/e2e/04_git_fix/my_pkg/git_inferred.py +4 -0
- pkg_ext-0.1.0/pkg_ext/_internal/testdata/e2e/04_git_fix/my_pkg/inferred.py +2 -0
- pkg_ext-0.1.0/pkg_ext/_internal/version_bump.py +173 -0
- pkg_ext-0.1.0/pkg_ext/_internal/warnings.py +153 -0
- pkg_ext-0.1.0/pkg_ext/_internal/warnings_gen.py +199 -0
- pkg_ext-0.1.0/pkg_ext/api_commands.py +6 -0
- pkg_ext-0.1.0/pkg_ext/api_dump.py +30 -0
- pkg_ext-0.1.0/pkg_ext/changelog.py +8 -0
- pkg_ext-0.1.0/pkg_ext/cli.py +5 -0
- pkg_ext-0.1.0/pkg_ext/generate.py +8 -0
- pkg_ext-0.1.0/pkg_ext/py.typed +0 -0
- pkg_ext-0.1.0/pkg_ext/stability.py +8 -0
- pkg_ext-0.1.0/pkg_ext/workflows.py +8 -0
- pkg_ext-0.1.0/pyproject.toml +96 -0
pkg_ext-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# path-sync copy -n python-template
|
|
2
|
+
|
|
3
|
+
# === DO_NOT_EDIT: path-sync gitignore ===
|
|
4
|
+
# Python bytecode
|
|
5
|
+
__pycache__/
|
|
6
|
+
*.pyc
|
|
7
|
+
*.pyo
|
|
8
|
+
|
|
9
|
+
# Virtual environments
|
|
10
|
+
.venv/
|
|
11
|
+
venv/
|
|
12
|
+
|
|
13
|
+
# Build artifacts
|
|
14
|
+
dist/
|
|
15
|
+
build/
|
|
16
|
+
*.egg-info/
|
|
17
|
+
|
|
18
|
+
# Coverage
|
|
19
|
+
.coverage
|
|
20
|
+
htmlcov/
|
|
21
|
+
coverage.xml
|
|
22
|
+
|
|
23
|
+
# Cache directories
|
|
24
|
+
.ruff_cache/
|
|
25
|
+
.pytest_cache/
|
|
26
|
+
.mypy_cache/
|
|
27
|
+
|
|
28
|
+
# IDE
|
|
29
|
+
.idea/
|
|
30
|
+
*.iml
|
|
31
|
+
.vscode/
|
|
32
|
+
|
|
33
|
+
# pkg-ext dev mode files
|
|
34
|
+
.groups-dev.yaml
|
|
35
|
+
CHANGELOG-dev.md
|
|
36
|
+
*.api-dev.yaml
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# MkDocs build output
|
|
40
|
+
site/
|
|
41
|
+
# === OK_EDIT: path-sync gitignore ===
|
|
42
|
+
# Include dev mode files in testdata for e2e regression tests
|
|
43
|
+
!pkg_ext/_internal/testdata/e2e/**/.groups-dev.yaml
|
|
44
|
+
!pkg_ext/_internal/testdata/e2e/**/CHANGELOG-dev.md
|
pkg_ext-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Espen Albert
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
pkg_ext-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pkg-ext
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python package public API management, versioning, and changelog generation
|
|
5
|
+
Author-email: EspenAlbert <espen.albert1@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Keywords: api,changelog,cli,package-management,versioning
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
11
|
+
Requires-Python: >=3.13
|
|
12
|
+
Requires-Dist: ask-shell
|
|
13
|
+
Requires-Dist: gitpython>=3.1.0
|
|
14
|
+
Requires-Dist: model-lib[toml]
|
|
15
|
+
Requires-Dist: tomlkit>=0.13
|
|
16
|
+
Requires-Dist: zero-3rdparty
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
|
|
19
|
+
# Pkg Ext
|
|
20
|
+
|
|
21
|
+
A CLI tool for managing Python package public API, versioning, and changelog generation.
|
|
22
|
+
|
|
23
|
+
## Overview
|
|
24
|
+
|
|
25
|
+
`pkg-ext` tracks which symbols (functions, classes, exceptions) in your package are "exposed" (public) vs "hidden" (internal). It:
|
|
26
|
+
- Generates `__init__.py` with imports and `__all__` based on decisions stored in changelog entries
|
|
27
|
+
- Creates group modules (e.g., `my_group.py`) that re-export related symbols
|
|
28
|
+
- Maintains a structured changelog directory (`.changelog/`) per PR
|
|
29
|
+
- Bumps version based on changelog action types (make_public=minor, fix=patch, delete/rename=major)
|
|
30
|
+
- Writes a human-readable `CHANGELOG.md`
|
|
31
|
+
- Provides [stability decorators](docs/stability.md) (`@experimental`, `@deprecated`) with suppressible warnings
|
|
32
|
+
- Generates `_warnings.py` in target packages to avoid runtime pkg-ext dependency
|
|
33
|
+
|
|
34
|
+
## Installation
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
uv pip install pkg-ext
|
|
38
|
+
# or
|
|
39
|
+
pip install pkg-ext
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Core Concepts
|
|
43
|
+
|
|
44
|
+
### Symbol Reference IDs
|
|
45
|
+
|
|
46
|
+
Symbols are identified by `{module_path}.{symbol_name}`, e.g., `my_pkg.utils.parse_config`.
|
|
47
|
+
|
|
48
|
+
### Changelog Actions
|
|
49
|
+
|
|
50
|
+
Stored in `.changelog/{pr_number}.yaml` files using Pydantic discriminated unions:
|
|
51
|
+
|
|
52
|
+
| Action Type | Description | Version Bump | Key Fields |
|
|
53
|
+
|-------------|-------------|--------------|------------|
|
|
54
|
+
| `make_public` | Make symbol public | Minor | `group`, `details` |
|
|
55
|
+
| `keep_private` | Keep symbol internal | None | `full_path` |
|
|
56
|
+
| `fix` | Bug fix from git commit | Patch | `short_sha`, `message`, `changelog_message`, `ignored` |
|
|
57
|
+
| `delete` | Remove from public API | Major | `group` |
|
|
58
|
+
| `rename` | Rename with old alias | Major | `group`, `old_name` |
|
|
59
|
+
| `breaking_change` | Breaking API change | Major | `group`, `details` |
|
|
60
|
+
| `additional_change` | Non-breaking change | Patch | `group`, `details` |
|
|
61
|
+
| `group_module` | Assign module to a group | None | `module_path` |
|
|
62
|
+
| `release` | Version release marker | None | `old_version` |
|
|
63
|
+
| `experimental` | Mark as experimental | Patch | `target`, `group`/`parent` |
|
|
64
|
+
| `ga` | Graduate to GA | Patch | `target`, `group`/`parent` |
|
|
65
|
+
| `deprecated` | Mark as deprecated | Patch | `target`, `group`/`parent`, `replacement` |
|
|
66
|
+
| `max_bump_type` | Cap version bump | None | `max_bump`, `reason` |
|
|
67
|
+
| `chore` | Internal changes | Patch | `description` |
|
|
68
|
+
|
|
69
|
+
All actions inherit common fields: `name`, `ts`, `author`, `pr`.
|
|
70
|
+
|
|
71
|
+
The `breaking_change` and `additional_change` actions support optional fields for API diff:
|
|
72
|
+
- `change_kind: str | None` - machine-readable change type (e.g., `param_removed`, `default_changed`)
|
|
73
|
+
- `auto_generated: bool` - `true` when created by API diff, `false` for interactive actions
|
|
74
|
+
- `field_name: str | None` - field name for field-level changes
|
|
75
|
+
|
|
76
|
+
### Stability Targets
|
|
77
|
+
|
|
78
|
+
Stability actions (`experimental`, `ga`, `deprecated`) support three target levels:
|
|
79
|
+
|
|
80
|
+
| Target | Description | Required Field |
|
|
81
|
+
|--------|-------------|----------------|
|
|
82
|
+
| `group` | Entire group | `name` = group name |
|
|
83
|
+
| `symbol` | Single symbol | `group` + `name` = symbol name |
|
|
84
|
+
| `arg` | Function argument | `parent` = `{group}.{symbol}`, `name` = arg name |
|
|
85
|
+
|
|
86
|
+
### Public Groups
|
|
87
|
+
|
|
88
|
+
Groups organize related symbols. Configured in `.groups.yaml`:
|
|
89
|
+
|
|
90
|
+
```yaml
|
|
91
|
+
groups:
|
|
92
|
+
- name: __ROOT__ # Top-level exports in __init__.py
|
|
93
|
+
owned_refs: []
|
|
94
|
+
owned_modules: []
|
|
95
|
+
- name: my_group
|
|
96
|
+
owned_refs:
|
|
97
|
+
- my_pkg.utils.parse_config
|
|
98
|
+
owned_modules:
|
|
99
|
+
- my_pkg.utils
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
When a new symbol is exposed, the tool prompts you to select which group it belongs to. All symbols from the same module go to the same group.
|
|
103
|
+
|
|
104
|
+
## CLI Commands
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
pkg-ext [OPTIONS] COMMAND
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Global Options
|
|
111
|
+
|
|
112
|
+
| Option | Description |
|
|
113
|
+
|--------|-------------|
|
|
114
|
+
| `-p, --path, --pkg-path` | Package directory path (auto-detected if not provided) |
|
|
115
|
+
| `--repo-root` | Repository root (auto-detected from `.git`) |
|
|
116
|
+
| `--is-bot` | CI mode: no prompts, fail on missing decisions |
|
|
117
|
+
| `--skip-open` | Skip opening files in editor |
|
|
118
|
+
| `--tag-prefix` | Git tag prefix (e.g., `v` for `v1.0.0`) |
|
|
119
|
+
|
|
120
|
+
### Command Reference
|
|
121
|
+
|
|
122
|
+
| Category | Commands | Description | Docs |
|
|
123
|
+
|----------|----------|-------------|------|
|
|
124
|
+
| Workflow | `pre-change`, `pre-commit`, `post-merge` | Development lifecycle commands | [docs/workflows](docs/workflows/index.md) |
|
|
125
|
+
| Changelog | `chore`, `promote`, `release-notes` | Changelog management | [docs/changelog](docs/changelog/index.md) |
|
|
126
|
+
| Stability | `exp`, `ga`, `dep` | Stability level management | [docs/stability](docs/stability/index.md) |
|
|
127
|
+
| API | `diff-api`, `dump-api` | API comparison and export | [docs/api_commands](docs/api_commands/index.md) |
|
|
128
|
+
| Generation | `gen-docs`, `gen-examples`, `gen-tests` | Generate documentation and scaffolds | [docs/generate](docs/generate/index.md) |
|
|
129
|
+
|
|
130
|
+
### When to Use Workflow Commands
|
|
131
|
+
|
|
132
|
+
| Scenario | Command |
|
|
133
|
+
|----------|---------|
|
|
134
|
+
| Added or removed symbols | `pre-change` |
|
|
135
|
+
| Final validation before commit | `pre-commit` |
|
|
136
|
+
| Single command for everything | `pre-change --full` |
|
|
137
|
+
| CI/CD pipeline | `pre-commit` (bot mode) |
|
|
138
|
+
|
|
139
|
+
- **`pre-change`** handles interactive decisions (expose/hide symbols, delete/rename). Fast because it only generates example and test scaffolds.
|
|
140
|
+
- **`pre-commit`** validates all decisions are made (fails in bot mode if prompts needed), syncs generated files, regenerates docs, and runs the dirty check.
|
|
141
|
+
- **`pre-change --full`** combines both: runs interactive prompts, generates examples/tests, then syncs files and regenerates docs. The dirty check is skipped since you're still developing.
|
|
142
|
+
|
|
143
|
+
## Configuration
|
|
144
|
+
|
|
145
|
+
### User Config (`~/.config/pkg-ext/config.toml`)
|
|
146
|
+
|
|
147
|
+
```toml
|
|
148
|
+
[user]
|
|
149
|
+
editor = "cursor" # or "code", "vim", etc.
|
|
150
|
+
skip_open_in_editor = false
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Project Config (`pyproject.toml`)
|
|
154
|
+
|
|
155
|
+
```toml
|
|
156
|
+
[tool.pkg-ext]
|
|
157
|
+
tag_prefix = "v"
|
|
158
|
+
file_header = "# Generated by pkg-ext"
|
|
159
|
+
commit_fix_prefixes = ["fix:", "bugfix:", "hotfix:"]
|
|
160
|
+
commit_diff_suffixes = [".py", ".pyi"]
|
|
161
|
+
changelog_cleanup_count = 30 # Archive when count exceeds this
|
|
162
|
+
changelog_keep_count = 10 # Keep this many after cleanup
|
|
163
|
+
format_command = ["ruff", "format"] # ruff check --fix always runs first
|
|
164
|
+
max_bump_type = "minor" # Cap version bumps (patch, minor, major)
|
|
165
|
+
# after_file_write_hooks = ["extra-cmd {pkg_path}"] # Custom post-write hooks
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Group Configuration
|
|
169
|
+
|
|
170
|
+
Define groups with explicit settings in `pyproject.toml`:
|
|
171
|
+
|
|
172
|
+
```toml
|
|
173
|
+
[tool.pkg-ext.groups.my_group]
|
|
174
|
+
dependencies = ["__ROOT__"] # Groups this depends on
|
|
175
|
+
docs_exclude = ["internal_helper"]
|
|
176
|
+
docstring = "Utilities for common operations"
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
**Note:** Stability is not configured here. Use `pkg-ext exp/ga/dep` CLI commands to manage stability via changelog actions.
|
|
180
|
+
|
|
181
|
+
### Version Bump Limits
|
|
182
|
+
|
|
183
|
+
For pre-1.0.0 packages where breaking changes are expected, cap the version bump:
|
|
184
|
+
|
|
185
|
+
**Project-level** (applies to all PRs):
|
|
186
|
+
|
|
187
|
+
```toml
|
|
188
|
+
# pyproject.toml
|
|
189
|
+
[tool.pkg-ext]
|
|
190
|
+
max_bump_type = "minor" # All PRs capped to minor
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
**Per-PR override** (`MaxBumpTypeAction` in changelog overrides config):
|
|
194
|
+
|
|
195
|
+
```yaml
|
|
196
|
+
# .changelog/{pr}.yaml
|
|
197
|
+
name: version_cap
|
|
198
|
+
type: max_bump_type
|
|
199
|
+
max_bump: patch
|
|
200
|
+
reason: Documentation-only release
|
|
201
|
+
ts: '2026-01-17T14:35:00+00:00'
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### Dev Mode
|
|
205
|
+
|
|
206
|
+
The `pre-commit` command enables dev mode, which writes to `-dev` suffixed files:
|
|
207
|
+
- `.groups-dev.yaml` instead of `.groups.yaml`
|
|
208
|
+
- `CHANGELOG-dev.md` instead of `CHANGELOG.md`
|
|
209
|
+
|
|
210
|
+
This allows iterating on changelog entries during development without modifying the production files. The real files are only updated by `post-merge` after PR is merged.
|
|
211
|
+
|
|
212
|
+
## Generated Files
|
|
213
|
+
|
|
214
|
+
### Files Updated During PR
|
|
215
|
+
|
|
216
|
+
| File | Purpose | Editable |
|
|
217
|
+
|------|---------|----------|
|
|
218
|
+
| `.changelog/{pr}.yaml` | Changelog actions for this PR | Yes |
|
|
219
|
+
| `.groups-dev.yaml` | Group assignments (dev copy) | No |
|
|
220
|
+
| `CHANGELOG-dev.md` | Human-readable changelog (dev copy) | No |
|
|
221
|
+
| `{pkg}.api-dev.yaml` | API dump for dev comparison (gitignored) | No |
|
|
222
|
+
| `{pkg}/__init__.py` | Package exports (VERSION unchanged) | No |
|
|
223
|
+
| `{pkg}/{group}.py` | Group re-export modules | No |
|
|
224
|
+
| `{pkg}/_warnings.py` | Stability warning decorators | No |
|
|
225
|
+
| `docs/**/*.md` | API documentation | Yes (outside markers) |
|
|
226
|
+
| `{group}_examples.py` | Example scaffolds | Yes (outside markers) |
|
|
227
|
+
| `{group}_test.py` | Test scaffolds | Yes (outside markers) |
|
|
228
|
+
|
|
229
|
+
- `__init__.py` exports are updated but VERSION remains unchanged until release
|
|
230
|
+
- Symbol doc pages include a "Changes" table showing unreleased modifications
|
|
231
|
+
- Content outside `=== OK_EDIT: pkg-ext ... ===` markers can be customized and is preserved
|
|
232
|
+
|
|
233
|
+
### Files Updated During Release
|
|
234
|
+
|
|
235
|
+
These files are updated by `post-merge` after PR is merged (main branch only):
|
|
236
|
+
|
|
237
|
+
| File | What Changes |
|
|
238
|
+
|------|--------------|
|
|
239
|
+
| `.groups.yaml` | Copied from `.groups-dev.yaml` |
|
|
240
|
+
| `CHANGELOG.md` | Copied from `CHANGELOG-dev.md` |
|
|
241
|
+
| `{pkg}/__init__.py` | VERSION updated to new version |
|
|
242
|
+
| `pyproject.toml` | Version field updated (if used) |
|
|
243
|
+
| `{pkg}.api.yaml` | Regenerated with new version |
|
|
244
|
+
| `docs/**/*.md` | Unreleased changes become versioned |
|
|
245
|
+
|
|
246
|
+
### File Contents Examples
|
|
247
|
+
|
|
248
|
+
**`__init__.py`:**
|
|
249
|
+
|
|
250
|
+
```python
|
|
251
|
+
# Generated by pkg-ext
|
|
252
|
+
# flake8: noqa
|
|
253
|
+
from my_pkg import my_group
|
|
254
|
+
from my_pkg.utils import parse_config
|
|
255
|
+
|
|
256
|
+
VERSION = "0.1.0"
|
|
257
|
+
__all__ = [
|
|
258
|
+
"my_group",
|
|
259
|
+
"parse_config",
|
|
260
|
+
]
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
**Group module (`my_group.py`):**
|
|
264
|
+
|
|
265
|
+
```python
|
|
266
|
+
# Generated by pkg-ext
|
|
267
|
+
from my_pkg.helpers import helper_func as _helper_func
|
|
268
|
+
from my_pkg._warnings import _experimental
|
|
269
|
+
|
|
270
|
+
helper_func = _experimental(_helper_func) # With experimental stability
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
The underscore alias pattern prevents re-export issues with `__all__`.
|
|
274
|
+
|
|
275
|
+
**`_warnings.py`:**
|
|
276
|
+
|
|
277
|
+
When any group has non-GA stability, pkg-ext generates a `_warnings.py` module in the target package (removes runtime dependency on pkg-ext):
|
|
278
|
+
|
|
279
|
+
```python
|
|
280
|
+
class MyPkgWarning(UserWarning): ...
|
|
281
|
+
class MyPkgExperimentalWarning(MyPkgWarning): ...
|
|
282
|
+
class MyPkgDeprecationWarning(MyPkgWarning, DeprecationWarning): ...
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
## Symbol Detection
|
|
286
|
+
|
|
287
|
+
The tool parses Python files using AST to find:
|
|
288
|
+
- **Functions** - Public functions (not starting with `_`)
|
|
289
|
+
- **Classes** - Public classes
|
|
290
|
+
- **Exceptions** - Classes inheriting from `Exception` or `BaseException`
|
|
291
|
+
- **Type Aliases** - Names ending with `T`
|
|
292
|
+
- **Global Variables** - UPPERCASE names with 2+ characters
|
|
293
|
+
|
|
294
|
+
Files skipped:
|
|
295
|
+
- `__init__.py`, `__main__.py` (dunder files)
|
|
296
|
+
- `*_test.py`, `test_*.py`, `conftest.py` (test files)
|
|
297
|
+
- Files starting with the configured `file_header` (already generated)
|
|
298
|
+
|
|
299
|
+
## Automatic Behaviors
|
|
300
|
+
|
|
301
|
+
### Function Argument Exposure
|
|
302
|
+
|
|
303
|
+
When exposing a function, its type hint arguments are auto-exposed if they reference local package types.
|
|
304
|
+
|
|
305
|
+
### Git Integration
|
|
306
|
+
|
|
307
|
+
- Uses [GitPython](https://gitpython.readthedocs.io/) for commit analysis
|
|
308
|
+
- Uses [gh CLI](https://cli.github.com/) to detect PR info
|
|
309
|
+
- Extracts PR number from merge commit message (`Merge pull request #123`)
|
|
310
|
+
|
|
311
|
+
## API Diff and Breaking Change Detection
|
|
312
|
+
|
|
313
|
+
During `pre-commit`, pkg-ext compares `{pkg}.api.yaml` (baseline from last release) against `{pkg}.api-dev.yaml` (current code) to detect API changes.
|
|
314
|
+
|
|
315
|
+
### Detected Change Types
|
|
316
|
+
|
|
317
|
+
| Change | Breaking? | `change_kind` |
|
|
318
|
+
|--------|-----------|---------------|
|
|
319
|
+
| Parameter removed | Yes | `param_removed` |
|
|
320
|
+
| Required parameter added | Yes | `required_param_added` |
|
|
321
|
+
| Parameter type changed | Yes | `param_type_changed` |
|
|
322
|
+
| Return type changed | Yes | `return_type_changed` |
|
|
323
|
+
| Default removed | Yes | `default_removed` |
|
|
324
|
+
| Required field added | Yes | `required_field_added` |
|
|
325
|
+
| Field removed | Yes | `field_removed` |
|
|
326
|
+
| Base class removed | Yes | `base_class_removed` |
|
|
327
|
+
| Optional parameter added | No | `optional_param_added` |
|
|
328
|
+
| Default added | No | `default_added` |
|
|
329
|
+
| Default changed | No | `default_changed` |
|
|
330
|
+
| Optional field added | No | `optional_field_added` |
|
|
331
|
+
|
|
332
|
+
### Auto-Generated Actions
|
|
333
|
+
|
|
334
|
+
API diff creates `BreakingChangeAction` or `AdditionalChangeAction` entries with `auto_generated: true`. These are:
|
|
335
|
+
- Replaced on each `pre-commit` run
|
|
336
|
+
- Keyed by `(name, group, type, change_kind)` for deduplication
|
|
337
|
+
- Timestamps preserved for unchanged changes
|
|
338
|
+
|
|
339
|
+
Interactive actions (from `pre-change`) are never replaced.
|
|
340
|
+
|
|
341
|
+
### First Release
|
|
342
|
+
|
|
343
|
+
When no baseline `{pkg}.api.yaml` exists, diff is skipped (nothing to compare against).
|
|
344
|
+
|
|
345
|
+
## Limitations
|
|
346
|
+
|
|
347
|
+
### Symbol Detection
|
|
348
|
+
- **Type aliases require `T` suffix** - e.g., `ConfigT` not `Config`
|
|
349
|
+
- **Global vars require UPPERCASE** - e.g., `DEFAULT_TIMEOUT` not `default_timeout`
|
|
350
|
+
- **Exceptions require `Error` suffix** - e.g., `ParseError` not `ParseException`
|
|
351
|
+
- **No relative import support** - Only `from pkg.module import ...` is tracked
|
|
352
|
+
|
|
353
|
+
### Group Handling
|
|
354
|
+
- **One group per module** - All symbols from a module belong to the same group
|
|
355
|
+
- **Cannot move symbols between groups** - Once assigned, module-to-group mapping is fixed
|
|
356
|
+
- **Root group always exists** - Cannot be removed, used for top-level exports
|
|
357
|
+
|
|
358
|
+
### Git Requirements
|
|
359
|
+
- **Requires `gh` CLI** for PR info detection
|
|
360
|
+
- **Merge commit format expected** - `Merge pull request #123 from ...`
|
|
361
|
+
- **Single remote assumed** - Uses first remote for URL
|
|
362
|
+
|
|
363
|
+
### Changelog
|
|
364
|
+
- **PR-based storage** - Each PR gets one `.yaml` file
|
|
365
|
+
- **No conflict resolution** - Manual merge of `.changelog/` files needed
|
|
366
|
+
- **Archiving by PR number** - Old entries archived to `.changelog/000/*.yaml`
|
|
367
|
+
|
|
368
|
+
### Version Bumping
|
|
369
|
+
- **SemVer only** - No calendar versioning support
|
|
370
|
+
- **`pyproject.toml` or `__init__.py`** - Version must exist in one of these
|
|
371
|
+
- **Pre-release suffixes** - Supports `rc`, `a` (alpha), `b` (beta)
|
|
372
|
+
|
|
373
|
+
### Interactive Mode
|
|
374
|
+
- **Removed reference handling incomplete** - `select_ref` and `select_multiple_ref_state` raise `NotImplementedError`. This breaks rename workflows when symbols are removed.
|
|
375
|
+
- **Alias creation not implemented** - `confirm_create_alias` always returns `False`
|
|
376
|
+
|
|
377
|
+
### Stability
|
|
378
|
+
- **Non-callable symbols** - Constants and type aliases in experimental/deprecated groups don't emit warnings. `@experimental` and `@deprecated` only work on functions and classes.
|
|
379
|
+
- **Arg-level only for GA groups** - Cannot track arg-level stability changes until group is GA.
|
|
380
|
+
|
|
381
|
+
### API Diff
|
|
382
|
+
- **No rename detection** - Renames are treated as remove + add (two separate actions)
|
|
383
|
+
- **Return types always breaking** - No semantic analysis (e.g., returning subclass is flagged as breaking)
|
|
384
|
+
- **Factory defaults** - Defaults using `"..."` (factory pattern) may cause false positives
|
|
385
|
+
|
|
386
|
+
## Dependencies
|
|
387
|
+
|
|
388
|
+
- **[ask-shell](https://github.com/EspenAlbert/ask-shell)** - Interactive prompts and shell execution
|
|
389
|
+
- **[model-lib](https://github.com/EspenAlbert/model-lib)** - YAML/TOML parsing and Pydantic models
|
|
390
|
+
- **[GitPython](https://gitpython.readthedocs.io/)** - Git repository access
|
|
391
|
+
|
|
392
|
+
## Appendix
|
|
393
|
+
|
|
394
|
+
### File Structure
|
|
395
|
+
|
|
396
|
+
```
|
|
397
|
+
my-repo/
|
|
398
|
+
CHANGELOG.md # Human-readable changelog
|
|
399
|
+
.groups.yaml # Group definitions
|
|
400
|
+
.changelog/ # Per-PR changelog actions
|
|
401
|
+
123.yaml # Actions from PR #123
|
|
402
|
+
000/ # Archived old entries
|
|
403
|
+
001.yaml
|
|
404
|
+
my_pkg/
|
|
405
|
+
__init__.py # Generated exports
|
|
406
|
+
my_group.py # Generated group module
|
|
407
|
+
_warnings.py # Generated stability module (if needed)
|
|
408
|
+
utils.py # Source file
|
|
409
|
+
_internal.py # Private module (ignored)
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
### CI Configuration
|
|
413
|
+
|
|
414
|
+
**GitHub Actions:**
|
|
415
|
+
|
|
416
|
+
```yaml
|
|
417
|
+
jobs:
|
|
418
|
+
validate:
|
|
419
|
+
runs-on: ubuntu-latest
|
|
420
|
+
steps:
|
|
421
|
+
- uses: actions/checkout@v4
|
|
422
|
+
- run: pip install pkg-ext
|
|
423
|
+
- run: pkg-ext pre-commit
|
|
424
|
+
|
|
425
|
+
release:
|
|
426
|
+
if: github.ref == 'refs/heads/main'
|
|
427
|
+
needs: validate
|
|
428
|
+
runs-on: ubuntu-latest
|
|
429
|
+
steps:
|
|
430
|
+
- uses: actions/checkout@v4
|
|
431
|
+
with:
|
|
432
|
+
fetch-depth: 0
|
|
433
|
+
- run: pip install pkg-ext
|
|
434
|
+
- run: pkg-ext post-merge --push
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
## Contributing
|
|
438
|
+
|
|
439
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup, workflow, and git hooks.
|