enumplus 1.0.0__tar.gz → 1.2.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.
Files changed (43) hide show
  1. {enumplus-1.0.0 → enumplus-1.2.0}/.github/ISSUE_TEMPLATE/bug_report.md +1 -1
  2. enumplus-1.2.0/.github/workflows/ci.yml +62 -0
  3. enumplus-1.2.0/.github/workflows/release.yml +176 -0
  4. {enumplus-1.0.0 → enumplus-1.2.0}/.gitignore +3 -0
  5. enumplus-1.2.0/AGENTS.md +73 -0
  6. enumplus-1.2.0/CHANGELOG.md +76 -0
  7. {enumplus-1.0.0 → enumplus-1.2.0}/CONTRIBUTING.md +16 -3
  8. enumplus-1.2.0/PKG-INFO +478 -0
  9. enumplus-1.0.0/PKG-INFO → enumplus-1.2.0/README.md +172 -27
  10. {enumplus-1.0.0 → enumplus-1.2.0}/SECURITY.md +2 -1
  11. enumplus-1.2.0/enumplus/__init__.py +12 -0
  12. enumplus-1.2.0/enumplus/enum.py +507 -0
  13. enumplus-1.2.0/enumplus/pydantic.py +56 -0
  14. enumplus-1.2.0/enumplus/serialize.py +86 -0
  15. enumplus-1.2.0/pyproject.toml +55 -0
  16. enumplus-1.2.0/tests/__init__.py +0 -0
  17. {enumplus-1.0.0 → enumplus-1.2.0}/tests/test_compatibility.py +22 -0
  18. enumplus-1.2.0/tests/test_edge_cases.py +581 -0
  19. {enumplus-1.0.0 → enumplus-1.2.0}/tests/test_enum.py +67 -1
  20. enumplus-1.2.0/tests/test_new_features.py +357 -0
  21. {enumplus-1.0.0 → enumplus-1.2.0}/tests/test_ordered.py +1 -1
  22. {enumplus-1.0.0 → enumplus-1.2.0}/tests/test_public_api.py +1 -1
  23. enumplus-1.2.0/tests/test_pydantic.py +79 -0
  24. {enumplus-1.0.0 → enumplus-1.2.0}/tests/test_serialize.py +16 -0
  25. enumplus-1.0.0/.github/workflows/ci.yml +0 -36
  26. enumplus-1.0.0/.github/workflows/release.yml +0 -124
  27. enumplus-1.0.0/README.md +0 -283
  28. enumplus-1.0.0/enumplus/__init__.py +0 -6
  29. enumplus-1.0.0/enumplus/enum.py +0 -206
  30. enumplus-1.0.0/enumplus/pydantic.py +0 -29
  31. enumplus-1.0.0/enumplus/serialize.py +0 -38
  32. enumplus-1.0.0/pyproject.toml +0 -32
  33. enumplus-1.0.0/tests/test_pydantic.py +0 -43
  34. {enumplus-1.0.0 → enumplus-1.2.0}/.github/FUNDING.yml +0 -0
  35. {enumplus-1.0.0 → enumplus-1.2.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  36. {enumplus-1.0.0 → enumplus-1.2.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  37. {enumplus-1.0.0 → enumplus-1.2.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  38. {enumplus-1.0.0 → enumplus-1.2.0}/CODE_OF_CONDUCT.md +0 -0
  39. {enumplus-1.0.0 → enumplus-1.2.0}/LICENSE +0 -0
  40. {enumplus-1.0.0 → enumplus-1.2.0}/SUPPORT.md +0 -0
  41. /enumplus-1.0.0/tests/__init__.py → /enumplus-1.2.0/enumplus/py.typed +0 -0
  42. {enumplus-1.0.0 → enumplus-1.2.0}/tests/test_metadata_unpacking.py +0 -0
  43. {enumplus-1.0.0 → enumplus-1.2.0}/tests/test_type_hints.py +0 -0
@@ -31,7 +31,7 @@ A clear and concise description of what actually happened.
31
31
  ## Environment
32
32
 
33
33
  - Python version: [e.g. 3.12.1]
34
- - enumplus version: [e.g. 0.1.0]
34
+ - enumplus version: [e.g. 1.1.0]
35
35
  - OS: [e.g. Ubuntu 22.04, Windows 11, macOS 14]
36
36
 
37
37
  ## Additional context
@@ -0,0 +1,62 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: ["main", "master"]
6
+ pull_request:
7
+ branches: ["main", "master"]
8
+
9
+ concurrency:
10
+ group: ci-${{ github.ref }}
11
+ cancel-in-progress: true
12
+
13
+ jobs:
14
+ test:
15
+ runs-on: ubuntu-latest
16
+ strategy:
17
+ fail-fast: false
18
+ matrix:
19
+ python-version: ["3.11", "3.12", "3.13", "3.14"]
20
+
21
+ steps:
22
+ - uses: actions/checkout@v4
23
+
24
+ - name: Set up Python ${{ matrix.python-version }}
25
+ uses: actions/setup-python@v5
26
+ with:
27
+ python-version: ${{ matrix.python-version }}
28
+
29
+ - name: Install dependencies
30
+ run: |
31
+ python -m pip install --upgrade pip
32
+ pip install -e ".[dev]"
33
+
34
+ - name: Lint with ruff
35
+ run: ruff check enumplus/ tests/
36
+
37
+ - name: Type check with mypy
38
+ run: mypy --strict enumplus/ tests/
39
+
40
+ - name: Run tests
41
+ run: pytest --tb=short
42
+
43
+ build:
44
+ needs: test
45
+ runs-on: ubuntu-latest
46
+ steps:
47
+ - uses: actions/checkout@v4
48
+
49
+ - name: Set up Python
50
+ uses: actions/setup-python@v5
51
+ with:
52
+ python-version: "3.12"
53
+
54
+ - name: Build package
55
+ run: |
56
+ python -m pip install --upgrade pip build
57
+ python -m build
58
+
59
+ - name: Verify wheel contents
60
+ run: |
61
+ python -m zipfile -l dist/*.whl
62
+ python -c "import zipfile, sys; z=zipfile.ZipFile([f for f in __import__('glob').glob('dist/*.whl')][0]); assert 'enumplus/py.typed' in z.namelist(), 'py.typed missing from wheel'"
@@ -0,0 +1,176 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+
8
+ permissions:
9
+ contents: write
10
+ id-token: write
11
+
12
+ concurrency:
13
+ group: release-${{ github.ref }}
14
+ cancel-in-progress: false
15
+
16
+ jobs:
17
+ test:
18
+ runs-on: ubuntu-latest
19
+ steps:
20
+ - uses: actions/checkout@v4
21
+
22
+ - name: Set up Python
23
+ uses: actions/setup-python@v5
24
+ with:
25
+ python-version: "3.12"
26
+
27
+ - name: Install dependencies
28
+ run: |
29
+ python -m pip install --upgrade pip
30
+ pip install -e ".[dev]"
31
+
32
+ - name: Lint with ruff
33
+ run: ruff check enumplus/ tests/
34
+
35
+ - name: Type check with mypy
36
+ run: mypy --strict enumplus/ tests/
37
+
38
+ - name: Run tests
39
+ run: pytest --tb=short
40
+
41
+ build:
42
+ needs: test
43
+ runs-on: ubuntu-latest
44
+ steps:
45
+ - uses: actions/checkout@v4
46
+
47
+ - name: Set up Python
48
+ uses: actions/setup-python@v5
49
+ with:
50
+ python-version: "3.12"
51
+
52
+ - name: Install build dependencies
53
+ run: |
54
+ python -m pip install --upgrade pip
55
+ pip install build
56
+
57
+ - name: Build package
58
+ run: python -m build
59
+
60
+ - name: Upload artifacts
61
+ uses: actions/upload-artifact@v4
62
+ with:
63
+ name: dist
64
+ path: dist/
65
+
66
+ github-release:
67
+ needs: build
68
+ runs-on: ubuntu-latest
69
+ permissions:
70
+ contents: write
71
+ steps:
72
+ - uses: actions/checkout@v4
73
+ with:
74
+ fetch-depth: 0
75
+
76
+ - name: Download artifacts
77
+ uses: actions/download-artifact@v4
78
+ with:
79
+ name: dist
80
+ path: dist/
81
+
82
+ - name: Generate changelog
83
+ id: changelog
84
+ run: |
85
+ PREVIOUS_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "")
86
+ if [ -z "$PREVIOUS_TAG" ]; then
87
+ RANGE=""
88
+ else
89
+ RANGE="${PREVIOUS_TAG}..HEAD"
90
+ fi
91
+
92
+ # Group commits by conventional commit type
93
+ FEATURES=$(git log --pretty=format:"- %s" ${RANGE} --grep="^feat" --grep="^add" --grep="^support" -i | grep -v "release:" || true)
94
+ FIXES=$(git log --pretty=format:"- %s" ${RANGE} --grep="^fix" --grep="^patch" -i || true)
95
+ REFACTOR=$(git log --pretty=format:"- %s" ${RANGE} --grep="^refactor" --grep="^clean" -i || true)
96
+ DOCS=$(git log --pretty=format:"- %s" ${RANGE} --grep="^docs" --grep="^readme" -i || true)
97
+ TESTS=$(git log --pretty=format:"- %s" ${RANGE} --grep="^test" -i || true)
98
+ CHORE=$(git log --pretty=format:"- %s" ${RANGE} --grep="^chore" --grep="^ci" --grep="^build" -i || true)
99
+ RELEASE=$(git log --pretty=format:"- %s" ${RANGE} --grep="^release" -i || true)
100
+
101
+ {
102
+ echo "body<<EOF"
103
+ if [ -n "$FEATURES" ]; then
104
+ echo "### Features"
105
+ echo ""
106
+ echo "$FEATURES"
107
+ echo ""
108
+ fi
109
+ if [ -n "$FIXES" ]; then
110
+ echo "### Bug Fixes"
111
+ echo ""
112
+ echo "$FIXES"
113
+ echo ""
114
+ fi
115
+ if [ -n "$REFACTOR" ]; then
116
+ echo "### Refactoring"
117
+ echo ""
118
+ echo "$REFACTOR"
119
+ echo ""
120
+ fi
121
+ if [ -n "$DOCS" ]; then
122
+ echo "### Documentation"
123
+ echo ""
124
+ echo "$DOCS"
125
+ echo ""
126
+ fi
127
+ if [ -n "$TESTS" ]; then
128
+ echo "### Tests"
129
+ echo ""
130
+ echo "$TESTS"
131
+ echo ""
132
+ fi
133
+ if [ -n "$CHORE" ]; then
134
+ echo "### Chores"
135
+ echo ""
136
+ echo "$CHORE"
137
+ echo ""
138
+ fi
139
+ echo "---"
140
+ echo ""
141
+ echo "**PyPI**: https://pypi.org/project/enumplus/"
142
+ if [ -n "$PREVIOUS_TAG" ]; then
143
+ echo "**Full Changelog**: https://github.com/${{ github.repository }}/compare/${PREVIOUS_TAG}...${{ github.ref_name }}"
144
+ else
145
+ echo "**Commits**: https://github.com/${{ github.repository }}/commits/${{ github.ref_name }}"
146
+ fi
147
+ echo "EOF"
148
+ } >> "$GITHUB_OUTPUT"
149
+
150
+ - name: Create GitHub Release
151
+ uses: softprops/action-gh-release@v2
152
+ with:
153
+ tag_name: ${{ github.ref_name }}
154
+ name: ${{ github.ref_name }}
155
+ body: ${{ steps.changelog.outputs.body }}
156
+ files: dist/*
157
+ draft: false
158
+ prerelease: false
159
+
160
+ publish-pypi:
161
+ needs: [build, github-release]
162
+ runs-on: ubuntu-latest
163
+ environment: pypi
164
+ permissions:
165
+ id-token: write
166
+ steps:
167
+ - uses: actions/checkout@v4
168
+
169
+ - name: Download artifacts
170
+ uses: actions/download-artifact@v4
171
+ with:
172
+ name: dist
173
+ path: dist/
174
+
175
+ - name: Publish to PyPI
176
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -37,3 +37,6 @@ htmlcov/
37
37
  # OS
38
38
  .DS_Store
39
39
  Thumbs.db
40
+
41
+ # Temporary
42
+ .git_commit_msg.txt
@@ -0,0 +1,73 @@
1
+ # Agent Notes for enumplus
2
+
3
+ ## Project purpose
4
+
5
+ `enumplus` is a zero-dependency Python library that extends `enum.Enum` with:
6
+
7
+ - Per-member display labels (`label`)
8
+ - Arbitrary per-member metadata, accessible as attributes
9
+ - `(value, metadata)` tuple unpacking at class definition
10
+ - `choices()`, `from_value()`, `from_name()`, `filter()`, `get()`, `map()`
11
+ - JSON serialization (`to_json`, `from_json`, `SerializableEncoder`)
12
+ - Declaration-order comparison (`OrderedEnum`)
13
+ - Optional Pydantic v2 integration (install with `pip install "enumplus[pydantic]"`)
14
+
15
+ Requires Python `>=3.11`.
16
+
17
+ ## Important implementation details
18
+
19
+ - The metaclass in `enumplus/enum.py` uses a custom `_EnumPlusDict` namespace.
20
+ This is required to:
21
+ - Unpack `(value, {metadata})` tuples before stdlib enum machinery sees them.
22
+ - Keep class config keys such as `serialize_by_name` from becoming enum members
23
+ or affecting `auto()` generation.
24
+ - Support `auto()` following a metadata tuple on Python 3.11–3.14.
25
+ - `Enum.__eq__` and `EnumMeta.__contains__` use `_safe_equal`, which coerces
26
+ `a == b` to a `bool` without raising on shape/length mismatches (e.g. NumPy
27
+ arrays, unhashable container values).
28
+ - `Enum.__hash__` uses `hash(self.value)` when possible and falls back to
29
+ `id(self)` for unhashable values, so enum members with list/dict values are
30
+ still usable as dict keys.
31
+ - `serialize.py` `from_json` validates that input is a `str` and that the
32
+ parsed JSON is a `dict` (`TypeError` otherwise). Invalid JSON strings raise
33
+ `ValueError`.
34
+ - `py.typed` is included in the wheel so the package is a PEP 561 typed package.
35
+
36
+ ## Build and verification commands
37
+
38
+ ```bash
39
+ python -m ruff check enumplus/ tests/
40
+ python -m mypy --strict enumplus/ tests/
41
+ python -m pytest --tb=short
42
+ python -m build
43
+ ```
44
+
45
+ After building, verify the wheel contains `py.typed`:
46
+
47
+ ```bash
48
+ python -m zipfile -l dist/*.whl
49
+ ```
50
+
51
+ ## Cross-version testing
52
+
53
+ CI tests Python 3.11, 3.12, 3.13, and 3.14. Locally, the following versions have
54
+ been verified:
55
+
56
+ - Python 3.11.2
57
+ - Python 3.12.4
58
+ - Python 3.14.5
59
+
60
+ Run a different version with the Windows launcher, e.g.:
61
+
62
+ ```bash
63
+ py -3.11 -m pip install -e ".[dev]"
64
+ py -3.11 -m pytest --tb=short
65
+ py -3.11 -m mypy --strict enumplus/ tests/
66
+ ```
67
+
68
+ ## Known constraints
69
+
70
+ - The git remote is `MathiasPaulenko/enumpy`, while the package name and PyPI
71
+ project are `enumplus`. Package URLs in `pyproject.toml` point to the actual
72
+ remote repository; update if the repository is renamed.
73
+ - `ref/` is intentionally ignored (local reference material). Do not commit it.
@@ -0,0 +1,76 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented in this file.
4
+
5
+ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [1.2.0] - 2026-08-23
11
+
12
+ ### Fixed
13
+
14
+ - `serialize_by_name` is now correctly inherited by subclass enums instead of being silently reset to `False`.
15
+
16
+ ### Changed
17
+
18
+ - Improved README with table of contents, requirements section, and dynamic CI badge.
19
+ - Added `pydantic` optional dependency extra for Pydantic v2 integration.
20
+ - Added concurrency groups to CI and release workflows to cancel superseded runs.
21
+ - Updated issue templates with correct version examples.
22
+ - Updated CONTRIBUTING with cross-version testing instructions and repository name clarification.
23
+
24
+ ## [1.1.0] - 2025-01-15
25
+
26
+ ### Added
27
+
28
+ - Case-insensitive lookup via `case_insensitive=True` on `from_value()` and `from_name()`.
29
+ - `serialize_by_name` class config for Pydantic v2 validation and serialization by member name.
30
+ - `to_dict()` method for serializing an enum to a nested dictionary.
31
+ - `get()` method for dict-style lookup with a default.
32
+ - `map()` method for mapping each member to a value.
33
+ - `keys()` method as an alias of `names()`.
34
+ - `get_initial()` and `get_final()` for declaration-order access.
35
+ - Callable labels for i18n / translatable labels.
36
+ - `@dataclass_transform()` on the metaclass for type-checker metadata attribute support.
37
+ - `py.typed` marker for PEP 561 typed-package support.
38
+ - Python 3.14 support and CI coverage.
39
+
40
+ ### Fixed
41
+
42
+ - `auto()` now works correctly after metadata tuples on Python 3.11–3.14.
43
+ - Class config (`serialize_by_name`) no longer registers as an enum member or corrupts `auto()` state.
44
+ - `__eq__`, `__hash__`, `__contains__`, `from_value`, `is_valid`, and `filter` are now safe for unhashable values and non-boolean comparison results.
45
+ - `from_json` validates input type and JSON shape (`TypeError` for wrong type, `ValueError` for invalid JSON).
46
+ - `from_name` raises `TypeError` for non-string input instead of `KeyError`.
47
+ - `to_dict()` and `to_json()` no longer invoke non-label callable metadata values.
48
+ - Removed redundant import in `SerializableEncoder`.
49
+
50
+ ### Changed
51
+
52
+ - Improved package metadata: classifiers, project URLs, and dev dependency lower bounds.
53
+ - Release workflow uses `${{ github.repository }}` for dynamic changelog URLs.
54
+ - CONTRIBUTING and SUPPORT docs updated with correct commands.
55
+
56
+ ## [1.0.0] - 2024-12-01
57
+
58
+ ### Added
59
+
60
+ - `Enum` base class as a drop-in replacement for `enum.Enum`.
61
+ - `OrderedEnum` with declaration-order comparison operators.
62
+ - Per-member labels with automatic `name.title()` fallback.
63
+ - Per-member metadata via `(value, dict)` tuple syntax.
64
+ - Metadata attribute access via `__getattr__`.
65
+ - `choices()`, `from_value()`, `from_name()`, `is_valid()`, `validate()`.
66
+ - `values()`, `names()`, `labels()`.
67
+ - `filter()` by metadata key-value pairs.
68
+ - `to_json()` / `from_json()` serialization.
69
+ - `SerializableEncoder` for `json.dumps`.
70
+ - Pydantic v2 integration via `__get_pydantic_core_schema__`.
71
+ - Full test suite, CI, and release workflows.
72
+
73
+ [Unreleased]: https://github.com/MathiasPaulenko/enumpy/compare/v1.2.0...HEAD
74
+ [1.2.0]: https://github.com/MathiasPaulenko/enumpy/releases/tag/v1.2.0
75
+ [1.1.0]: https://github.com/MathiasPaulenko/enumpy/releases/tag/v1.1.0
76
+ [1.0.0]: https://github.com/MathiasPaulenko/enumpy/releases/tag/v1.0.0
@@ -17,6 +17,8 @@ Thank you for your interest in contributing to enumplus! This document describes
17
17
  pip install -e ".[dev]"
18
18
  ```
19
19
 
20
+ > **Note:** The git repository is named `enumpy`, while the package name is `enumplus`. This is expected — the repository name predates the package rename.
21
+
20
22
  ## Development Workflow
21
23
 
22
24
  1. Create a branch for your feature or bugfix:
@@ -26,9 +28,9 @@ Thank you for your interest in contributing to enumplus! This document describes
26
28
  2. Make your changes, keeping code style consistent with the existing codebase.
27
29
  3. Run the checks before committing:
28
30
  ```bash
29
- ruff check enumplus tests
30
- mypy enumplus
31
- pytest
31
+ ruff check enumplus/ tests/
32
+ mypy --strict enumplus/ tests/
33
+ pytest --tb=short
32
34
  ```
33
35
  4. Commit using [conventional commits](https://www.conventionalcommits.org/):
34
36
  ```bash
@@ -51,12 +53,23 @@ Thank you for your interest in contributing to enumplus! This document describes
51
53
  - Run the full suite with `pytest`.
52
54
  - Do not weaken or delete existing tests without explicit justification.
53
55
 
56
+ ## Cross-Version Testing
57
+
58
+ CI tests Python 3.11, 3.12, 3.13, and 3.14. To test locally against multiple Python versions, use the Python launcher (Windows) or `pyenv` (macOS/Linux):
59
+
60
+ ```bash
61
+ py -3.11 -m pip install -e ".[dev]"
62
+ py -3.11 -m pytest --tb=short
63
+ py -3.11 -m mypy --strict enumplus/ tests/
64
+ ```
65
+
54
66
  ## Pull Request Guidelines
55
67
 
56
68
  - Keep PRs focused and small.
57
69
  - Reference any related issues (e.g., `Closes #123`).
58
70
  - Ensure all CI checks pass before requesting review.
59
71
  - Update documentation (`README.md`) if your change affects the public API.
72
+ - Add a `CHANGELOG.md` entry under the `[Unreleased]` section.
60
73
 
61
74
  ## Reporting Issues
62
75