Better enums including flags with subsets in which there can be only one... set.
Project description
🗡️ Highlander Enum
"There can be only one!" - A Python library for mutually exclusive flag enums with smart conflict resolution.
Highlander Enum provides ExFlag and OptionsFlag - specialized variations of Python's IntFlag enum designed for scenarios where certain flags must be mutually exclusive. Think command-line options where --verbose and --quiet can't both be active, or configuration settings where only one mode can be selected at a time.
✨ Key Features
- 🚫 Mutually Exclusive Flags: Define groups where only one flag can be active
- 🔀 Smart Conflict Resolution: Choose how conflicts are handled (RHS wins, LHS wins, or strict mode)
- 🏃 Performance Optimized: Uses bitmasks for fast conflict detection during bitwise operations
- 📋 Command-Line Ready:
OptionsFlagincludes aliases and help text for CLI tools - 🎯 Type Safe: Full type hints and comprehensive test coverage (100%)
- 🐍 Modern Python: Supports Python 3.11, 3.12, and 3.13
🚀 Quick Start
Installation
pip install highlander-enum
Basic Usage
from highlander import ExFlag
class NetworkMode(ExFlag):
# Basic flags that can be combined
IPV4 = 1
IPV6 = 2
ENCRYPTION = 4
COMPRESSION = 8
# Mutually exclusive verbosity levels
QUIET = 16
VERBOSE = 32
DEBUG = 64, (QUIET, VERBOSE)
# RHS (right-hand side) wins by default
mode = NetworkMode.QUIET | NetworkMode.VERBOSE
print(mode) # NetworkMode.VERBOSE (VERBOSE wins)
# Non-conflicting flags combine normally
mode = NetworkMode.IPV4 | NetworkMode.ENCRYPTION | NetworkMode.VERBOSE
print(mode) # NetworkMode.ENCRYPTION|IPV4|VERBOSE
🎛️ Conflict Resolution Strategies
Choose how conflicts should be resolved when mutually exclusive flags are combined:
RHS (Right-Hand Side) - Default
class LogLevel(ExFlag): # Default: conflict="rhs"
ERROR = 1
WARN = 2
INFO = 4, (ERROR, WARN)
result = LogLevel.ERROR | LogLevel.INFO
print(result) # LogLevel.INFO (newer value wins)
LHS (Left-Hand Side)
class LogLevel(ExFlag, conflict="lhs"):
ERROR = 1
WARN = 2
INFO = 4, (ERROR, WARN)
result = LogLevel.ERROR | LogLevel.INFO
print(result) # LogLevel.ERROR (existing value wins)
Strict Mode
class LogLevel(ExFlag, conflict="strict"):
ERROR = 1
WARN = 2
INFO = 4, (ERROR, WARN)
try:
result = LogLevel.ERROR | LogLevel.INFO
except ValueError as e:
print(e) # LogLevel.INFO conflicts with LogLevel.ERROR
🖥️ Command-Line Options with OptionsFlag
Perfect for building CLI tools with rich help text and aliases:
from highlander import OptionsFlag
class ServerOptions(OptionsFlag):
# Format: VALUE, [aliases], "help text", [exclusions]
VERBOSE = 1, ["v", "verbose"], "Enable verbose logging"
QUIET = 2, ["q", "quiet"], "Suppress all output"
DEBUG = 4, ["d", "debug"], "Enable debug mode", (VERBOSE, QUIET)
# Different tuple formats supported
DAEMON = 8, "Run as daemon" # Just help text
CONFIG = 16, ["c", "config"], "Specify config file" # No exclusions
FORCE = 32, "Force operation", (DAEMON,) # Help + exclusions
# Access help text and aliases
opt = ServerOptions.VERBOSE
print(f"Help: {opt.help}") # Help: Enable verbose logging
print(f"Aliases: {opt.aliases}") # Aliases: ['v', 'verbose']
# Smart conflict resolution
flags = ServerOptions.QUIET | ServerOptions.DEBUG
print(flags) # ServerOptions.DEBUG (DEBUG wins over QUIET)
🔧 Advanced Usage
Dynamic Exclusions
Add exclusions at runtime:
class DynamicFlag(ExFlag):
A = 1
B = 2
C = 4
flag_a = DynamicFlag.A
flag_a.add_exclusions(DynamicFlag.B, DynamicFlag.C)
result = DynamicFlag.A | DynamicFlag.B
print(result) # DynamicFlag.A (conflicts resolved)
Multiple Exclusion Groups
Create complex relationships between different groups of flags:
class UITheme(ExFlag):
# Color schemes (mutually exclusive)
DARK = 1
LIGHT = 2
HIGH_CONTRAST = 4, (DARK, LIGHT)
# Size options (separate exclusion group)
SMALL = 8
MEDIUM = 16
LARGE = 32, (SMALL, MEDIUM)
# Independent options (no conflicts)
ANIMATIONS = 64
SOUND_EFFECTS = 128
# Mix and match from different groups
theme = UITheme.DARK | UITheme.LARGE | UITheme.ANIMATIONS
print(theme) # UITheme.ANIMATIONS|DARK|LARGE
# Conflicts within groups are resolved
theme = UITheme.DARK | UITheme.LIGHT | UITheme.SMALL
print(theme) # UITheme.LIGHT|SMALL (LIGHT wins over DARK)
Working with Integer Values
# Create flags from integer values with automatic conflict resolution
mixed_flags = NetworkMode(1 | 16 | 32) # IPV4 + QUIET + VERBOSE
print(mixed_flags) # NetworkMode.IPV4|QUIET (conflicts resolved)
# Check if flags are set
if NetworkMode.IPV4 in mixed_flags:
print("IPv4 is enabled")
🛡️ Type Safety & IDE Support
Highlander Enum provides full type hints for excellent IDE support:
from highlander import ExFlag
class StatusFlag(ExFlag):
IDLE = 1
BUSY = 2, (IDLE,)
ERROR = 4
def process_status(status: StatusFlag) -> str:
if status & StatusFlag.ERROR:
return "Error occurred"
elif status & StatusFlag.BUSY:
return "Currently busy"
else:
return "Ready"
# IDE will provide autocompletion and type checking
result = process_status(StatusFlag.BUSY | StatusFlag.ERROR)
print(result) # "Error occurred"
📊 Performance
Highlander Enum is designed for performance with bitwise operations:
import timeit
from highlander import ExFlag
class PerfTest(ExFlag):
A = 1
B = 2, (A,)
C = 4
D = 8
# Fast bitwise operations with conflict resolution
def test_operations():
return PerfTest.A | PerfTest.B | PerfTest.C
# Benchmark shows minimal overhead compared to standard IntFlag
print(f"Time per operation: {timeit.timeit(test_operations, number=100000):.6f}s")
🧪 Real-World Examples
File Processing Tool
from highlander import OptionsFlag
class FileProcessor(OptionsFlag):
# Output verbosity (mutually exclusive)
SILENT = 1, ["s", "silent"], "No output"
NORMAL = 2, ["n", "normal"], "Normal output"
VERBOSE = 4, ["v", "verbose"], "Verbose output", (SILENT, NORMAL)
# Processing modes (mutually exclusive)
FAST = 8, ["f", "fast"], "Fast processing"
ACCURATE = 16, ["a", "accurate"], "Accurate processing", (FAST,)
# Independent options
BACKUP = 32, ["b", "backup"], "Create backups"
COMPRESS = 64, ["c", "compress"], "Compress output"
def process_files(options: FileProcessor):
if options & FileProcessor.VERBOSE:
print("Verbose mode enabled")
if options & FileProcessor.BACKUP:
print("Creating backups")
# Usage
opts = FileProcessor.VERBOSE | FileProcessor.ACCURATE | FileProcessor.BACKUP
process_files(opts)
Game Settings
from highlander import ExFlag
class GraphicsSettings(ExFlag):
# Quality levels (mutually exclusive)
LOW = 1
MEDIUM = 2
HIGH = 4
ULTRA = 8, (LOW, MEDIUM, HIGH)
# Independent features
VSYNC = 16
HDR = 32
ANTIALIASING = 64
class GameConfig:
def __init__(self):
self.graphics = GraphicsSettings.MEDIUM | GraphicsSettings.VSYNC
def upgrade_quality(self):
# Automatically resolves conflicts
self.graphics |= GraphicsSettings.HIGH
def toggle_hdr(self):
self.graphics ^= GraphicsSettings.HDR
config = GameConfig()
print(config.graphics) # GraphicsSettings.VSYNC|MEDIUM
config.upgrade_quality()
print(config.graphics) # GraphicsSettings.VSYNC|HIGH (conflict resolved)
🏗️ Development
Requirements
- Python 3.11+
- uv (recommended) or pip
Setup
git clone https://github.com/florean/highlander-enum.git
cd highlander-enum
make install # Sets up virtual environment and pre-commit hooks
Testing
make test # Run pytest with coverage
make check # Run all quality checks (linting, type checking, etc.)
tox # Test across multiple Python versions
Building Documentation
make docs # Serve documentation locally
make docs-test # Test documentation build
📈 Project Roadmap
For 1.0
- New conflict resolutions: smallest wins and largest wins
- More robust constraint specification at member definition
- Better CLI integration for OptionsFlag - more helper methods or parser-specific subclasses
- Solidify internal API and naming
- More real-world usage
Future Enhancements
- Allow inheriting from and extending existing enums
🤝 Contributing
Contributions are welcome! This project maintains 100% test coverage because reliability is paramount. Please:
- Fork the repository
- Create a feature branch
- Add comprehensive tests for new functionality (we can help!)
- Ensure all tests pass and coverage remains 100%
- Submit a pull request
For bug reports, please open an issue. For feature requests or discussions about potential enhancements, start a discussion.
📄 License
This project is licensed under the Apache 2.0 License - see the LICENSE file for details.
🔗 Links
- Documentation: https://florean.github.io/highlander-enum/
- Repository: https://github.com/florean/highlander-enum
- PyPI: https://pypi.org/project/highlander-enum/
"In the end, there can be only one... flag active in each subset of flags, unless you use add_exclusions and only apply it to one side, in which case yo—🗡️"
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file highlander_enum-0.9.1.tar.gz.
File metadata
- Download URL: highlander_enum-0.9.1.tar.gz
- Upload date:
- Size: 99.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.6.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
33d672ce0ccb231114cb67cb170b66bbc4542823a33f9a525ae62abf58b08e8e
|
|
| MD5 |
d692be2b7c872bfa2b11f03cffcf19be
|
|
| BLAKE2b-256 |
3e9f55e006ce29c589f10905ca3bfe7c8d7f51d2fc9f9763d1afdea35ea9282d
|
File details
Details for the file highlander_enum-0.9.1-py3-none-any.whl.
File metadata
- Download URL: highlander_enum-0.9.1-py3-none-any.whl
- Upload date:
- Size: 19.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.6.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
53b355efa0f9884757ae1f6a90a3889873656d8e7c2e76abb8b942b97db276a9
|
|
| MD5 |
50f97e2dc3153dbb584772453a67922c
|
|
| BLAKE2b-256 |
332c47adcb06723a75f952326c86e4f4229a627f95d6f23624816848f5dfc9ca
|