vcti-enum 1.0.3__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.
- vcti_enum-1.0.3/LICENSE +8 -0
- vcti_enum-1.0.3/PKG-INFO +145 -0
- vcti_enum-1.0.3/README.md +129 -0
- vcti_enum-1.0.3/pyproject.toml +35 -0
- vcti_enum-1.0.3/setup.cfg +4 -0
- vcti_enum-1.0.3/src/vcti/enum/__init__.py +40 -0
- vcti_enum-1.0.3/src/vcti/enum/enum_coder.py +157 -0
- vcti_enum-1.0.3/src/vcti/enum/enum_pydantic_helpers.py +180 -0
- vcti_enum-1.0.3/src/vcti/enum/py.typed +0 -0
- vcti_enum-1.0.3/src/vcti/enum/value_generated_enums.py +149 -0
- vcti_enum-1.0.3/src/vcti/enum/value_generated_enums.pyi +53 -0
- vcti_enum-1.0.3/src/vcti_enum.egg-info/PKG-INFO +145 -0
- vcti_enum-1.0.3/src/vcti_enum.egg-info/SOURCES.txt +18 -0
- vcti_enum-1.0.3/src/vcti_enum.egg-info/dependency_links.txt +1 -0
- vcti_enum-1.0.3/src/vcti_enum.egg-info/requires.txt +8 -0
- vcti_enum-1.0.3/src/vcti_enum.egg-info/top_level.txt +1 -0
- vcti_enum-1.0.3/src/vcti_enum.egg-info/zip-safe +1 -0
- vcti_enum-1.0.3/tests/test_enum_coder.py +105 -0
- vcti_enum-1.0.3/tests/test_enum_pydantic_helpers.py +208 -0
- vcti_enum-1.0.3/tests/test_value_generated_enums.py +217 -0
vcti_enum-1.0.3/LICENSE
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
Copyright (c) 2018-2026 Visual Collaboration Technologies Inc.
|
|
2
|
+
All Rights Reserved.
|
|
3
|
+
|
|
4
|
+
This software is proprietary and confidential. Unauthorized copying,
|
|
5
|
+
distribution, or use of this software, via any medium, is strictly
|
|
6
|
+
prohibited. Access is granted only to authorized VCollab developers
|
|
7
|
+
and individuals explicitly authorized by Visual Collaboration
|
|
8
|
+
Technologies Inc.
|
vcti_enum-1.0.3/PKG-INFO
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: vcti-enum
|
|
3
|
+
Version: 1.0.3
|
|
4
|
+
Summary: Value-generated string enums with automatic naming conventions for Python
|
|
5
|
+
Author: Visual Collaboration Technologies Inc.
|
|
6
|
+
Requires-Python: <3.15,>=3.12
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Provides-Extra: test
|
|
10
|
+
Requires-Dist: pytest; extra == "test"
|
|
11
|
+
Requires-Dist: pytest-cov; extra == "test"
|
|
12
|
+
Requires-Dist: pydantic; extra == "test"
|
|
13
|
+
Provides-Extra: lint
|
|
14
|
+
Requires-Dist: ruff; extra == "lint"
|
|
15
|
+
Dynamic: license-file
|
|
16
|
+
|
|
17
|
+
# Enum Framework
|
|
18
|
+
|
|
19
|
+
## Purpose
|
|
20
|
+
|
|
21
|
+
Python's `StrEnum` requires manually writing string values for each member.
|
|
22
|
+
When enums follow a naming convention (lowercase, camelCase, PascalCase, etc.),
|
|
23
|
+
the values are predictable from the member names. The `vcti-enum` package
|
|
24
|
+
provides base classes that auto-generate member values from naming conventions,
|
|
25
|
+
plus an `EnumCoder` for flexible serialization and Pydantic integration helpers.
|
|
26
|
+
|
|
27
|
+
This package has **zero external dependencies**.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Installation
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pip install vcti-enum
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### In `requirements.txt`
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
vcti-enum>=1.0.3
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### In `pyproject.toml` dependencies
|
|
44
|
+
|
|
45
|
+
```toml
|
|
46
|
+
dependencies = [
|
|
47
|
+
"vcti-enum>=1.0.3",
|
|
48
|
+
]
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Quick Start
|
|
54
|
+
|
|
55
|
+
### Value-generated enums
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
from vcti.enum import EnumValueLowerCase, auto_enum_value
|
|
59
|
+
|
|
60
|
+
class FileFormat(EnumValueLowerCase):
|
|
61
|
+
JSON = auto_enum_value() # value: "json"
|
|
62
|
+
CSV = auto_enum_value() # value: "csv"
|
|
63
|
+
PARQUET = auto_enum_value() # value: "parquet"
|
|
64
|
+
|
|
65
|
+
FileFormat.JSON.value # "json"
|
|
66
|
+
FileFormat.JSON == "json" # True (StrEnum)
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### EnumCoder for serialization
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
from enum import Enum
|
|
73
|
+
from vcti.enum import EnumCoder
|
|
74
|
+
|
|
75
|
+
class Color(Enum):
|
|
76
|
+
RED = 1
|
|
77
|
+
GREEN = 2
|
|
78
|
+
|
|
79
|
+
coder = EnumCoder(Color, value_generator=lambda e: e.name.lower(), default=Color.RED)
|
|
80
|
+
|
|
81
|
+
coder.encode(Color.RED) # "red"
|
|
82
|
+
coder.decode("green") # Color.GREEN
|
|
83
|
+
coder.default # "red"
|
|
84
|
+
coder.list # ["red", "green"]
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Pydantic integration
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
from pydantic import BaseModel, Field
|
|
91
|
+
from vcti.enum import EnumValueLowerCase, auto_enum_value, enum_for_pydantic
|
|
92
|
+
|
|
93
|
+
@enum_for_pydantic(default="JSON")
|
|
94
|
+
class FileFormat(EnumValueLowerCase):
|
|
95
|
+
JSON = auto_enum_value()
|
|
96
|
+
CSV = auto_enum_value()
|
|
97
|
+
|
|
98
|
+
class Config(BaseModel):
|
|
99
|
+
format: FileFormat = Field(default=FileFormat.DEFAULT_VALUE)
|
|
100
|
+
|
|
101
|
+
Config().format # "json"
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Enum Base Classes
|
|
107
|
+
|
|
108
|
+
| Class | Convention | `FIRST_VALUE` becomes |
|
|
109
|
+
|-------|-----------|----------------------|
|
|
110
|
+
| `EnumValueSameAsName` | Unchanged | `FIRST_VALUE` |
|
|
111
|
+
| `EnumValueLowerCase` | lower_case | `first_value` |
|
|
112
|
+
| `EnumValueCamelCase` | camelCase | `firstValue` |
|
|
113
|
+
| `EnumValuePascalCase` | PascalCase | `FirstValue` |
|
|
114
|
+
| `EnumValueCapitalizedPhrase` | Title Case | `First Value` |
|
|
115
|
+
| `EnumValueSpaceSeparatedLower` | space lower | `first value` |
|
|
116
|
+
|
|
117
|
+
All classes extend `StrEnum` -- members are string instances.
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## Public API
|
|
122
|
+
|
|
123
|
+
| Symbol | Import | Purpose |
|
|
124
|
+
|--------|--------|---------|
|
|
125
|
+
| `auto_enum_value()` | `vcti.enum` | Triggers automatic value generation |
|
|
126
|
+
| `create_enum_class()` | `vcti.enum` | Factory to create custom enum base classes |
|
|
127
|
+
| `EnumValueSameAsName` | `vcti.enum` | Base class: value = member name |
|
|
128
|
+
| `EnumValueLowerCase` | `vcti.enum` | Base class: value = lowercase |
|
|
129
|
+
| `EnumValueCamelCase` | `vcti.enum` | Base class: value = camelCase |
|
|
130
|
+
| `EnumValuePascalCase` | `vcti.enum` | Base class: value = PascalCase |
|
|
131
|
+
| `EnumValueCapitalizedPhrase` | `vcti.enum` | Base class: value = Title Case |
|
|
132
|
+
| `EnumValueSpaceSeparatedLower` | `vcti.enum` | Base class: value = space separated |
|
|
133
|
+
| `EnumCoder` | `vcti.enum` | Flexible enum serialization/deserialization |
|
|
134
|
+
| `setup_enum_for_pydantic()` | `vcti.enum` | Attach coder attributes to enum class |
|
|
135
|
+
| `enum_for_pydantic()` | `vcti.enum` | Decorator version of setup |
|
|
136
|
+
| `get_enum_field_description()` | `vcti.enum` | Generate Pydantic Field description |
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## Documentation
|
|
141
|
+
|
|
142
|
+
- [Design](docs/design.md) -- Concepts, factory pattern, and architecture decisions
|
|
143
|
+
- [Source Guide](docs/source-guide.md) -- File descriptions and execution flow traces
|
|
144
|
+
- [Extension Guide](docs/extending.md) -- How to add new enum base classes
|
|
145
|
+
- [API Reference](docs/api.md) -- Autodoc for all modules
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# Enum Framework
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
Python's `StrEnum` requires manually writing string values for each member.
|
|
6
|
+
When enums follow a naming convention (lowercase, camelCase, PascalCase, etc.),
|
|
7
|
+
the values are predictable from the member names. The `vcti-enum` package
|
|
8
|
+
provides base classes that auto-generate member values from naming conventions,
|
|
9
|
+
plus an `EnumCoder` for flexible serialization and Pydantic integration helpers.
|
|
10
|
+
|
|
11
|
+
This package has **zero external dependencies**.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pip install vcti-enum
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### In `requirements.txt`
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
vcti-enum>=1.0.3
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### In `pyproject.toml` dependencies
|
|
28
|
+
|
|
29
|
+
```toml
|
|
30
|
+
dependencies = [
|
|
31
|
+
"vcti-enum>=1.0.3",
|
|
32
|
+
]
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Quick Start
|
|
38
|
+
|
|
39
|
+
### Value-generated enums
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
from vcti.enum import EnumValueLowerCase, auto_enum_value
|
|
43
|
+
|
|
44
|
+
class FileFormat(EnumValueLowerCase):
|
|
45
|
+
JSON = auto_enum_value() # value: "json"
|
|
46
|
+
CSV = auto_enum_value() # value: "csv"
|
|
47
|
+
PARQUET = auto_enum_value() # value: "parquet"
|
|
48
|
+
|
|
49
|
+
FileFormat.JSON.value # "json"
|
|
50
|
+
FileFormat.JSON == "json" # True (StrEnum)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### EnumCoder for serialization
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
from enum import Enum
|
|
57
|
+
from vcti.enum import EnumCoder
|
|
58
|
+
|
|
59
|
+
class Color(Enum):
|
|
60
|
+
RED = 1
|
|
61
|
+
GREEN = 2
|
|
62
|
+
|
|
63
|
+
coder = EnumCoder(Color, value_generator=lambda e: e.name.lower(), default=Color.RED)
|
|
64
|
+
|
|
65
|
+
coder.encode(Color.RED) # "red"
|
|
66
|
+
coder.decode("green") # Color.GREEN
|
|
67
|
+
coder.default # "red"
|
|
68
|
+
coder.list # ["red", "green"]
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Pydantic integration
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
from pydantic import BaseModel, Field
|
|
75
|
+
from vcti.enum import EnumValueLowerCase, auto_enum_value, enum_for_pydantic
|
|
76
|
+
|
|
77
|
+
@enum_for_pydantic(default="JSON")
|
|
78
|
+
class FileFormat(EnumValueLowerCase):
|
|
79
|
+
JSON = auto_enum_value()
|
|
80
|
+
CSV = auto_enum_value()
|
|
81
|
+
|
|
82
|
+
class Config(BaseModel):
|
|
83
|
+
format: FileFormat = Field(default=FileFormat.DEFAULT_VALUE)
|
|
84
|
+
|
|
85
|
+
Config().format # "json"
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## Enum Base Classes
|
|
91
|
+
|
|
92
|
+
| Class | Convention | `FIRST_VALUE` becomes |
|
|
93
|
+
|-------|-----------|----------------------|
|
|
94
|
+
| `EnumValueSameAsName` | Unchanged | `FIRST_VALUE` |
|
|
95
|
+
| `EnumValueLowerCase` | lower_case | `first_value` |
|
|
96
|
+
| `EnumValueCamelCase` | camelCase | `firstValue` |
|
|
97
|
+
| `EnumValuePascalCase` | PascalCase | `FirstValue` |
|
|
98
|
+
| `EnumValueCapitalizedPhrase` | Title Case | `First Value` |
|
|
99
|
+
| `EnumValueSpaceSeparatedLower` | space lower | `first value` |
|
|
100
|
+
|
|
101
|
+
All classes extend `StrEnum` -- members are string instances.
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## Public API
|
|
106
|
+
|
|
107
|
+
| Symbol | Import | Purpose |
|
|
108
|
+
|--------|--------|---------|
|
|
109
|
+
| `auto_enum_value()` | `vcti.enum` | Triggers automatic value generation |
|
|
110
|
+
| `create_enum_class()` | `vcti.enum` | Factory to create custom enum base classes |
|
|
111
|
+
| `EnumValueSameAsName` | `vcti.enum` | Base class: value = member name |
|
|
112
|
+
| `EnumValueLowerCase` | `vcti.enum` | Base class: value = lowercase |
|
|
113
|
+
| `EnumValueCamelCase` | `vcti.enum` | Base class: value = camelCase |
|
|
114
|
+
| `EnumValuePascalCase` | `vcti.enum` | Base class: value = PascalCase |
|
|
115
|
+
| `EnumValueCapitalizedPhrase` | `vcti.enum` | Base class: value = Title Case |
|
|
116
|
+
| `EnumValueSpaceSeparatedLower` | `vcti.enum` | Base class: value = space separated |
|
|
117
|
+
| `EnumCoder` | `vcti.enum` | Flexible enum serialization/deserialization |
|
|
118
|
+
| `setup_enum_for_pydantic()` | `vcti.enum` | Attach coder attributes to enum class |
|
|
119
|
+
| `enum_for_pydantic()` | `vcti.enum` | Decorator version of setup |
|
|
120
|
+
| `get_enum_field_description()` | `vcti.enum` | Generate Pydantic Field description |
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## Documentation
|
|
125
|
+
|
|
126
|
+
- [Design](docs/design.md) -- Concepts, factory pattern, and architecture decisions
|
|
127
|
+
- [Source Guide](docs/source-guide.md) -- File descriptions and execution flow traces
|
|
128
|
+
- [Extension Guide](docs/extending.md) -- How to add new enum base classes
|
|
129
|
+
- [API Reference](docs/api.md) -- Autodoc for all modules
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "vcti-enum"
|
|
7
|
+
version = "1.0.3"
|
|
8
|
+
description = "Value-generated string enums with automatic naming conventions for Python"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
authors = [
|
|
11
|
+
{name = "Visual Collaboration Technologies Inc."}
|
|
12
|
+
]
|
|
13
|
+
requires-python = ">=3.12,<3.15"
|
|
14
|
+
dependencies = []
|
|
15
|
+
|
|
16
|
+
[project.optional-dependencies]
|
|
17
|
+
test = ["pytest", "pytest-cov", "pydantic"]
|
|
18
|
+
lint = ["ruff"]
|
|
19
|
+
|
|
20
|
+
[tool.setuptools.packages.find]
|
|
21
|
+
where = ["src"]
|
|
22
|
+
include = ["vcti.enum", "vcti.enum.*"]
|
|
23
|
+
|
|
24
|
+
[tool.pytest.ini_options]
|
|
25
|
+
addopts = "--cov=vcti.enum --cov-report=term-missing --cov-fail-under=95 --strict-markers"
|
|
26
|
+
|
|
27
|
+
[tool.setuptools]
|
|
28
|
+
zip-safe = true
|
|
29
|
+
|
|
30
|
+
[tool.ruff]
|
|
31
|
+
target-version = "py312"
|
|
32
|
+
line-length = 99
|
|
33
|
+
|
|
34
|
+
[tool.ruff.lint]
|
|
35
|
+
select = ["E", "F", "W", "I", "UP"]
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Copyright Visual Collaboration Technologies Inc. All Rights Reserved.
|
|
2
|
+
# See LICENSE for details.
|
|
3
|
+
"""vcti.enum package."""
|
|
4
|
+
|
|
5
|
+
from importlib.metadata import version
|
|
6
|
+
|
|
7
|
+
from .enum_coder import EnumCoder
|
|
8
|
+
from .enum_pydantic_helpers import (
|
|
9
|
+
enum_for_pydantic,
|
|
10
|
+
get_enum_field_description,
|
|
11
|
+
setup_enum_for_pydantic,
|
|
12
|
+
)
|
|
13
|
+
from .value_generated_enums import (
|
|
14
|
+
EnumValueCamelCase,
|
|
15
|
+
EnumValueCapitalizedPhrase,
|
|
16
|
+
EnumValueLowerCase,
|
|
17
|
+
EnumValuePascalCase,
|
|
18
|
+
EnumValueSameAsName,
|
|
19
|
+
EnumValueSpaceSeparatedLower,
|
|
20
|
+
auto_enum_value,
|
|
21
|
+
create_enum_class,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
__version__ = version("vcti-enum")
|
|
25
|
+
|
|
26
|
+
__all__ = [
|
|
27
|
+
"__version__",
|
|
28
|
+
"auto_enum_value",
|
|
29
|
+
"create_enum_class",
|
|
30
|
+
"EnumValueSameAsName",
|
|
31
|
+
"EnumValueLowerCase",
|
|
32
|
+
"EnumValueCamelCase",
|
|
33
|
+
"EnumValuePascalCase",
|
|
34
|
+
"EnumValueCapitalizedPhrase",
|
|
35
|
+
"EnumValueSpaceSeparatedLower",
|
|
36
|
+
"EnumCoder",
|
|
37
|
+
"setup_enum_for_pydantic",
|
|
38
|
+
"enum_for_pydantic",
|
|
39
|
+
"get_enum_field_description",
|
|
40
|
+
]
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# Copyright Visual Collaboration Technologies Inc. All Rights Reserved.
|
|
2
|
+
# See LICENSE for details.
|
|
3
|
+
"""Enhanced Enum utilities for flexible serialization with configurable string mappings."""
|
|
4
|
+
|
|
5
|
+
from collections.abc import Callable
|
|
6
|
+
from enum import Enum
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class EnumCoder[E: Enum]:
|
|
11
|
+
"""
|
|
12
|
+
Provides flexible serialization/deserialization for Enums with configurable string mappings.
|
|
13
|
+
|
|
14
|
+
Example:
|
|
15
|
+
class Color(Enum):
|
|
16
|
+
RED = 1
|
|
17
|
+
GREEN = 2
|
|
18
|
+
|
|
19
|
+
# Create serializer with lowercase mapping
|
|
20
|
+
color_coder = EnumCoder(
|
|
21
|
+
Color,
|
|
22
|
+
value_generator=lambda enum_var: enum_var.name.lower(),
|
|
23
|
+
default=Color.RED
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
# Serialize/Encode
|
|
27
|
+
color_coder.encode(Color.RED) # "red"
|
|
28
|
+
|
|
29
|
+
# Deserialize/Decode
|
|
30
|
+
color_coder.decode("green") # Color.GREEN
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
def __init__(
|
|
34
|
+
self,
|
|
35
|
+
enum_class: type[E],
|
|
36
|
+
value_generator: Callable[[E], str] = lambda enum_var: enum_var.name,
|
|
37
|
+
default: E | None = None,
|
|
38
|
+
):
|
|
39
|
+
"""
|
|
40
|
+
Initialize the serializer.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
enum_class: The Enum class to serialize
|
|
44
|
+
value_generator: Function that converts enums to string values
|
|
45
|
+
default: Default enum value for this serializer
|
|
46
|
+
"""
|
|
47
|
+
self.enum_class: type[E] = enum_class
|
|
48
|
+
self.value_generator: Callable[[E], str] = value_generator
|
|
49
|
+
|
|
50
|
+
# Generate mappings
|
|
51
|
+
self._enum_to_string: dict[E, str] = {
|
|
52
|
+
member: value_generator(member) for member in enum_class
|
|
53
|
+
}
|
|
54
|
+
self._string_to_enum: dict[str, E] = {v: k for k, v in self._enum_to_string.items()}
|
|
55
|
+
|
|
56
|
+
# Detect collisions in the reverse mapping
|
|
57
|
+
if len(self._string_to_enum) != len(self._enum_to_string):
|
|
58
|
+
from collections import Counter
|
|
59
|
+
|
|
60
|
+
counts = Counter(self._enum_to_string.values())
|
|
61
|
+
duplicates = {v for v, c in counts.items() if c > 1}
|
|
62
|
+
raise ValueError(f"value_generator produced duplicate string values: {duplicates}")
|
|
63
|
+
|
|
64
|
+
# Pre-compute stringified default
|
|
65
|
+
self._default = self._enum_to_string.get(default) if default is not None else None
|
|
66
|
+
|
|
67
|
+
def __repr__(self) -> str:
|
|
68
|
+
default_part = f", default={self._default!r}" if self._default is not None else ""
|
|
69
|
+
return f"EnumCoder({self.enum_class.__name__}{default_part})"
|
|
70
|
+
|
|
71
|
+
@property
|
|
72
|
+
def default(self) -> str | None:
|
|
73
|
+
"""The string representation of the default enum value (None if no default set)."""
|
|
74
|
+
return self._default
|
|
75
|
+
|
|
76
|
+
@property
|
|
77
|
+
def tuple(self) -> tuple[str, ...]:
|
|
78
|
+
"""All possible string values for this enum."""
|
|
79
|
+
return tuple(self._enum_to_string.values())
|
|
80
|
+
|
|
81
|
+
@property
|
|
82
|
+
def list(self) -> list[str]:
|
|
83
|
+
"""List of all possible string values."""
|
|
84
|
+
return list(self.tuple)
|
|
85
|
+
|
|
86
|
+
@property
|
|
87
|
+
def Literal(self) -> Any:
|
|
88
|
+
"""
|
|
89
|
+
Literal type containing all possible string values.
|
|
90
|
+
|
|
91
|
+
Note: This creates a Literal type dynamically at runtime for use in type annotations.
|
|
92
|
+
Due to Python's type system limitations, the return type is Any.
|
|
93
|
+
|
|
94
|
+
Example:
|
|
95
|
+
LogLevelStr = LogLevelCoder.Literal # Get the Literal type
|
|
96
|
+
my_level: LogLevelStr = "info" # Type-checked value
|
|
97
|
+
"""
|
|
98
|
+
from typing import Literal
|
|
99
|
+
|
|
100
|
+
return Literal[*self.tuple] # type: ignore
|
|
101
|
+
|
|
102
|
+
def encode(self, enum_value: E) -> str:
|
|
103
|
+
"""Convert enum to its string representation.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
enum_value: The enum member to encode.
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
String representation of the enum value.
|
|
110
|
+
|
|
111
|
+
Raises:
|
|
112
|
+
ValueError: If enum_value is not a member of the configured enum class.
|
|
113
|
+
"""
|
|
114
|
+
try:
|
|
115
|
+
return self._enum_to_string[enum_value]
|
|
116
|
+
except KeyError:
|
|
117
|
+
raise ValueError(
|
|
118
|
+
f"'{enum_value}' is not a member of {self.enum_class.__name__}. "
|
|
119
|
+
f"Valid members: {tuple(m.name for m in self.enum_class)}"
|
|
120
|
+
) from None
|
|
121
|
+
|
|
122
|
+
def decode(self, string_value: str) -> E:
|
|
123
|
+
"""Convert string back to enum value.
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
string_value: The string to decode into an enum member.
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
The enum member corresponding to string_value.
|
|
130
|
+
|
|
131
|
+
Raises:
|
|
132
|
+
ValueError: If string_value doesn't match any enum value.
|
|
133
|
+
"""
|
|
134
|
+
try:
|
|
135
|
+
return self._string_to_enum[string_value]
|
|
136
|
+
except KeyError:
|
|
137
|
+
raise ValueError(
|
|
138
|
+
f"'{string_value}' is not a valid representation. Valid values: {self.tuple}"
|
|
139
|
+
) from None
|
|
140
|
+
|
|
141
|
+
def get_mapping_info(self) -> dict[str, Any]:
|
|
142
|
+
"""Get complete mapping information as a dictionary.
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
Dictionary with keys: enum_class, values, default, mapping.
|
|
146
|
+
"""
|
|
147
|
+
return {
|
|
148
|
+
"enum_class": self.enum_class.__name__,
|
|
149
|
+
"values": self.tuple,
|
|
150
|
+
"default": self.default,
|
|
151
|
+
"mapping": {e.name: self.encode(e) for e in self.enum_class},
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
__all__ = [
|
|
156
|
+
"EnumCoder",
|
|
157
|
+
]
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# Copyright Visual Collaboration Technologies Inc. All Rights Reserved.
|
|
2
|
+
# See LICENSE for details.
|
|
3
|
+
"""Pydantic integration helpers for EnumCoder.
|
|
4
|
+
|
|
5
|
+
This module provides utilities to seamlessly integrate EnumCoder with Pydantic models,
|
|
6
|
+
making it easy to use enums with custom string representations.
|
|
7
|
+
|
|
8
|
+
Example:
|
|
9
|
+
>>> from vcti.enum import EnumValueLowerCase, auto_enum_value, setup_enum_for_pydantic
|
|
10
|
+
>>>
|
|
11
|
+
>>> class FileFormat(EnumValueLowerCase):
|
|
12
|
+
... JSON = auto_enum_value()
|
|
13
|
+
... CSV = auto_enum_value()
|
|
14
|
+
>>>
|
|
15
|
+
>>> # Setup for Pydantic usage
|
|
16
|
+
>>> setup_enum_for_pydantic(
|
|
17
|
+
... FileFormat,
|
|
18
|
+
... value_generator=lambda e: e.value,
|
|
19
|
+
... default_member='JSON'
|
|
20
|
+
... )
|
|
21
|
+
>>>
|
|
22
|
+
>>> # Now you can use in Pydantic:
|
|
23
|
+
>>> class MyModel(BaseModel):
|
|
24
|
+
... format: FileFormat.LITERAL = Field(default=FileFormat.DEFAULT_VALUE)
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from collections.abc import Callable
|
|
28
|
+
from enum import Enum
|
|
29
|
+
from typing import Any
|
|
30
|
+
|
|
31
|
+
from .enum_coder import EnumCoder
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def setup_enum_for_pydantic[E: Enum](
|
|
35
|
+
enum_class: type[E],
|
|
36
|
+
value_generator: Callable[[E], str] | None = None,
|
|
37
|
+
default_member: str | None = None,
|
|
38
|
+
) -> EnumCoder[E]:
|
|
39
|
+
"""Setup an enum class for use in Pydantic models.
|
|
40
|
+
|
|
41
|
+
This function creates an EnumCoder and attaches it and its properties
|
|
42
|
+
to the enum class, making it easy to use in Pydantic Field definitions.
|
|
43
|
+
|
|
44
|
+
Adds these attributes to the enum class:
|
|
45
|
+
- CODER: The EnumCoder instance
|
|
46
|
+
- DEFAULT_VALUE: Default string value
|
|
47
|
+
- LIST: List of all string values
|
|
48
|
+
- TUPLE: Tuple of all string values
|
|
49
|
+
- LITERAL: Literal type of all values
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
enum_class: The enum class to setup
|
|
53
|
+
value_generator: Function to convert enum to string (default: uses enum.value)
|
|
54
|
+
default_member: Name of enum member to use as default
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
The EnumCoder instance (also attached to enum_class.CODER)
|
|
58
|
+
|
|
59
|
+
Example:
|
|
60
|
+
>>> class Status(EnumValueLowerCase):
|
|
61
|
+
... PENDING = auto_enum_value()
|
|
62
|
+
... ACTIVE = auto_enum_value()
|
|
63
|
+
>>>
|
|
64
|
+
>>> setup_enum_for_pydantic(Status, default_member='PENDING')
|
|
65
|
+
>>>
|
|
66
|
+
>>> # Now available:
|
|
67
|
+
>>> Status.DEFAULT_VALUE # 'pending'
|
|
68
|
+
>>> Status.LIST # ['pending', 'active']
|
|
69
|
+
>>> Status.CODER.encode(Status.ACTIVE) # 'active'
|
|
70
|
+
"""
|
|
71
|
+
# Default value generator: use the enum's value
|
|
72
|
+
if value_generator is None:
|
|
73
|
+
|
|
74
|
+
def _default_value_generator(e):
|
|
75
|
+
return e.value
|
|
76
|
+
|
|
77
|
+
value_generator = _default_value_generator
|
|
78
|
+
|
|
79
|
+
# Determine default enum member
|
|
80
|
+
default_enum = None
|
|
81
|
+
if default_member:
|
|
82
|
+
default_enum = getattr(enum_class, default_member)
|
|
83
|
+
else:
|
|
84
|
+
# Use first member as default
|
|
85
|
+
default_enum = next(iter(enum_class))
|
|
86
|
+
|
|
87
|
+
# Create the coder
|
|
88
|
+
coder = EnumCoder(enum_class, value_generator=value_generator, default=default_enum)
|
|
89
|
+
|
|
90
|
+
# Attach coder and properties to enum class
|
|
91
|
+
setattr(enum_class, "CODER", coder)
|
|
92
|
+
setattr(enum_class, "DEFAULT_VALUE", coder.default)
|
|
93
|
+
setattr(enum_class, "LIST", coder.list)
|
|
94
|
+
setattr(enum_class, "TUPLE", coder.tuple)
|
|
95
|
+
setattr(enum_class, "LITERAL", coder.Literal)
|
|
96
|
+
|
|
97
|
+
return coder
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def enum_for_pydantic[E: Enum](
|
|
101
|
+
default: str | None = None, value_generator: Callable[[Any], str] | None = None
|
|
102
|
+
) -> Callable[[type[E]], type[E]]:
|
|
103
|
+
"""Decorator version of setup_enum_for_pydantic.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
default: Name of default enum member
|
|
107
|
+
value_generator: Function to convert enum to string
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
Decorator function
|
|
111
|
+
|
|
112
|
+
Example:
|
|
113
|
+
>>> @enum_for_pydantic(default='JSON')
|
|
114
|
+
... class FileFormat(EnumValueLowerCase):
|
|
115
|
+
... JSON = auto_enum_value()
|
|
116
|
+
... CSV = auto_enum_value()
|
|
117
|
+
>>>
|
|
118
|
+
>>> FileFormat.DEFAULT_VALUE # 'json'
|
|
119
|
+
"""
|
|
120
|
+
|
|
121
|
+
def decorator(enum_class: type[E]) -> type[E]:
|
|
122
|
+
setup_enum_for_pydantic(
|
|
123
|
+
enum_class, value_generator=value_generator, default_member=default
|
|
124
|
+
)
|
|
125
|
+
return enum_class
|
|
126
|
+
|
|
127
|
+
return decorator
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def get_enum_field_description(enum_class: type[Enum], include_default: bool = True) -> str:
|
|
131
|
+
"""Generate a description string for a Pydantic Field using this enum.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
enum_class: The enum class (must have LIST and DEFAULT_VALUE attributes)
|
|
135
|
+
include_default: Whether to include default value in description
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
Description string suitable for Pydantic Field
|
|
139
|
+
|
|
140
|
+
Example::
|
|
141
|
+
|
|
142
|
+
from pydantic import BaseModel, Field
|
|
143
|
+
from vcti.enum import (
|
|
144
|
+
EnumValueLowerCase, auto_enum_value,
|
|
145
|
+
enum_for_pydantic, get_enum_field_description,
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
@enum_for_pydantic(default='JSON')
|
|
149
|
+
class FileFormat(EnumValueLowerCase):
|
|
150
|
+
JSON = auto_enum_value()
|
|
151
|
+
CSV = auto_enum_value()
|
|
152
|
+
|
|
153
|
+
get_enum_field_description(FileFormat)
|
|
154
|
+
# 'Supported values: (json/csv). Default: json'
|
|
155
|
+
|
|
156
|
+
get_enum_field_description(FileFormat, include_default=False)
|
|
157
|
+
# 'Supported values: (json/csv)'
|
|
158
|
+
|
|
159
|
+
# Use in a Pydantic model Field:
|
|
160
|
+
class ExportConfig(BaseModel):
|
|
161
|
+
format: FileFormat = Field(
|
|
162
|
+
default=FileFormat.DEFAULT_VALUE,
|
|
163
|
+
description=get_enum_field_description(FileFormat),
|
|
164
|
+
)
|
|
165
|
+
"""
|
|
166
|
+
enum_list = getattr(enum_class, "LIST", [member.value for member in enum_class])
|
|
167
|
+
values_str = "/".join(str(v) for v in enum_list)
|
|
168
|
+
|
|
169
|
+
if include_default and hasattr(enum_class, "DEFAULT_VALUE"):
|
|
170
|
+
default_value = getattr(enum_class, "DEFAULT_VALUE")
|
|
171
|
+
return f"Supported values: ({values_str}). Default: {default_value}"
|
|
172
|
+
else:
|
|
173
|
+
return f"Supported values: ({values_str})"
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
__all__ = [
|
|
177
|
+
"setup_enum_for_pydantic",
|
|
178
|
+
"enum_for_pydantic",
|
|
179
|
+
"get_enum_field_description",
|
|
180
|
+
]
|
|
File without changes
|