enum.verify is a standard-library feature that validates enumeration rules when an enum class is created. It catches duplicate values, unexpected gaps in numeric sequences, and invalid flag combinations before those defects reach production. This is especially useful when enums represent stable database codes, API states, permissions, workflow stages, or protocol values.
The feature uses the verify() decorator together with built-in checks such as UNIQUE, CONTINUOUS, and NAMED_FLAGS. Instead of relying only on separate tests, you place the rule beside the enum definition. If the rule is broken, Python raises an exception while building the class.
Why enum validation matters
Enums often look simple, but they can become public contracts. A duplicated value may create an unintended alias. A missing number may break an external system that expects a complete sequence. A composite flag may contain a bit that has no corresponding name. These problems can remain hidden because the enum still imports and many ordinary operations still work.
Validation moves the failure to application startup or test time, where the cause is easier to understand. It also communicates intent to future maintainers: values are not merely arranged this way by accident; they must obey a declared rule.
Unique values with UNIQUE
from enum import Enum, verify, UNIQUE
@verify(UNIQUE)
class Status(Enum):
PENDING = 1
PROCESSING = 2
COMPLETED = 3
Every member in this enum has a different value. If two names use the same value, class creation fails. Without the check, the second name would normally become an alias of the first.
@verify(UNIQUE)
class Status(Enum):
PENDING = 1
PROCESSING = 2
FINISHED = 2
Aliases are not always wrong. They can support a migration from an old name to a new one. However, aliases should be intentional, documented, and tested. For persisted values and external APIs, accidental aliases introduce ambiguity in serialization, logs, and comparisons.
When aliases are appropriate
A project may keep an old symbolic name for backward compatibility. In that case, do not apply UNIQUE, or isolate compatibility in a conversion layer. Record why the alias exists and when it can be removed. Also test whether clients receive the canonical name or the legacy name during serialization.
Keeping compatibility outside the enum is often cleaner because the core model remains strict. A parser can accept legacy text and convert it to the new member without exposing two names throughout the codebase.
Continuous sequences with CONTINUOUS
from enum import IntEnum, verify, CONTINUOUS
@verify(CONTINUOUS)
class Priority(IntEnum):
LOW = 1
MEDIUM = 2
HIGH = 3
CONTINUOUS checks that every integer between the minimum and maximum value is present. A sequence containing 1, 2, and 4 fails because 3 is missing. This is valuable for ranked levels, contiguous workflow stages, indexes, or compact protocol ranges.
Do not use it when gaps are meaningful. HTTP status codes are not continuous, and legacy protocols may reserve ranges. Validation should reflect domain semantics rather than a preference for tidy numbering.
Detecting a missing value
@verify(CONTINUOUS)
class Stage(IntEnum):
START = 1
VALIDATION = 2
PUBLICATION = 4
If the missing value is accidental, the class fails immediately. If the gap is reserved by design, remove the check and document the reserved range. This distinction prevents developers from “fixing” an intentional protocol decision later.
Named flag combinations with NAMED_FLAGS
from enum import Flag, verify, NAMED_FLAGS
@verify(NAMED_FLAGS)
class Permission(Flag):
READ = 1
WRITE = 2
DELETE = 4
ADMIN = READ | WRITE | DELETE
NAMED_FLAGS verifies that aliases and composite masks use bits represented by named members. A composite value containing an unknown bit is difficult to inspect and audit. It may also grant a capability that the application cannot describe correctly.
For basic flag members, use powers of two. Build named combinations with the bitwise OR operator. This produces readable logs and predictable permission checks.
Combining checks
from enum import IntEnum, verify, UNIQUE, CONTINUOUS
@verify(UNIQUE, CONTINUOUS)
class Level(IntEnum):
BEGINNER = 1
INTERMEDIATE = 2
ADVANCED = 3
The decorator accepts multiple checks. Here, values must be both unique and continuous. Combine rules only when each one describes a real requirement. Excessively strict validation can make legitimate future extensions harder.
Import-time failures
Enum validation runs when Python creates the class, usually while importing its module. An invalid enum can therefore prevent the application from starting. For contract violations, this fail-fast behavior is useful. In plugin systems or dynamic module loaders, catch and report import failures with enough context to identify the faulty component.
Add import tests to continuous integration. They make enum errors visible before deployment and protect generated or frequently edited enum modules.
Choosing Enum, IntEnum, and Flag
Enum provides symbolic members without implicit integer behavior. IntEnum interoperates with integer-based systems. Flag represents bitwise combinations. Choose the narrowest type that satisfies the integration requirement.
If a value does not need to compare as an integer, prefer Enum. If legacy code or a protocol requires numeric compatibility, use IntEnum carefully. For combined permissions or feature masks, use Flag or IntFlag. The verify decorator strengthens these design choices rather than replacing them.
APIs and database storage
When persisting enums, decide whether to store member names or values. Names are readable but renaming them requires a migration. Numeric values are compact but must stay stable forever once published. UNIQUE helps prevent collisions when numeric codes become part of stored data.
For APIs, serialize consistently. Do not return a name in one endpoint and a number in another without a clear contract. Validate unknown input and map external values explicitly. A valid enum definition does not make untrusted input valid.
Testing verified enums
def test_status_contract():
assert Status.PENDING.value == 1
assert Status.COMPLETED.name == "COMPLETED"
def test_priority_sequence():
assert [item.value for item in Priority] == [1, 2, 3]
The decorator validates structure, while tests protect public values, serialization, parsing, and backward compatibility. Treat a value change as an API change when other systems store or exchange it.
Migration strategy
When introducing validation into an existing project, audit aliases and gaps before adding decorators. Some unusual values may already be part of a public contract. Start by documenting current behavior, add tests, and then decide which irregularities are defects and which must remain supported.
For an intentional rename, keep input compatibility in a parser, migrate stored data, and expose only the canonical member in new output. This limits the lifetime of aliases.
Practical best practices
Use descriptive member names, never recycle published numeric values, document intentional gaps, and place enum definitions in modules with clear ownership. Keep basic flags simple and create named composites for common permission sets. Avoid dynamically changing enum-like contracts from untrusted configuration.
For large catalogs maintained by an external authority, generate the enum from the official source and validate the generated result in CI. Store the source version so changes can be reviewed.
Related resources
Continue with Academify articles about Python dictionaries, Python sets, Python classes, and Python dataclasses. For authoritative references, read the official enum documentation and PEP 435.
Conclusion
enum.verify turns implicit enum assumptions into executable checks. UNIQUE prevents accidental aliases, CONTINUOUS detects unwanted numeric gaps, and NAMED_FLAGS protects bit masks from unnamed values. Applied according to domain requirements, these checks create safer contracts for APIs, databases, permissions, workflows, and protocols.







