Project Enforce Rules Project Enforce Rules (or PER for short) expands the Python type system to allow more constraints.
Version #
MAJOR: 1 MINOR: 0 PATCH: 0
If you need to catch up, you can see the full version history in the CHANGELOG.
It exists to let you use features Python doesn't already provide in the typing system — things you probably want, like min, max, length, all_same, and many more.
PER now supports runtime validation anywhere using:
validate(value: object, rules: Dict[str, typing.Any])
This enforces rule dictionaries and returns the original value if valid. If invalid, it raises a descriptive error.
Why I Made It I wanted type‑hint features Python didn’t give me. I thought dictionaries would work until I learned... well... Python didn’t enforce them.
So with the help of Microsoft Copilot and my dad, we designed a module that enforces this type of stuff. Then I realized everyone in the Python community could use this, so it became a project. Hence, creating PER.
PER uses rule dictionaries and validate() to enforce constraints at runtime.
Install it with pip:
pip install enforce-rules
Then use it like:
from enforce_rules import validate
Features
- Runtime enforcement of rule dictionaries
- Dictionary‑based rule definitions
- No extra objects required
- Works anywhere in your code
- Extensible via must_be_true
How It Works PER validates values using:
validate(value, rules)
If the value violates a rule, PER raises an error. If the value passes, PER returns the original value unchanged.
This means validated values behave exactly like normal Python values.
Keywords and Usage Below are all supported keywords.
length The length of the object must be exactly this.
lst = validate([1, 2, 3, 4, 5], {"length": 5})
min_length Minimum length (inclusive).
lst = validate(['a', 'b', 'c', 'd', 'e'], {"min_length": 3})
max_length Maximum length (inclusive).
lst = validate([1, 2, 3, 4, 5, 6], {"max_length": 7})
min Minimum numeric value (inclusive).
number = validate(10, {"min": 0})
max Maximum numeric value (inclusive).
number = validate(10, {"max": 20})
allowed_values Similar to Literal; value must be one of the allowed values.
val = validate("a", {"allowed_values": ("a", "b", "c", "d")})
invariant Value must be truthy.
val = validate((0 == 0), {"invariant": True})
all_same All values in the collection must be the same.
numbers = validate([1, 1, 1], {"all_same": True})
all_unique All values in the collection must be unique.
numbers = validate([1, 2, 3], {"all_unique": True})
non_empty Collection must not be empty.
my_strings = validate(['a', 'b', 'c'], {"non_empty": True})
no_nulls Collection must not contain None.
my_things = validate([1, 2, 3, "a", "b", "c"], {"no_nulls": True})
sorted List must be sorted (increasing or decreasing).
numbers = validate([1, 5, 9], {"sorted": True})
increasing List must be strictly increasing.
numbers = validate([1, 5, 9], {"increasing": True})
decreasing List must be strictly decreasing.
numbers = validate([9, 5, 1], {"decreasing": True})
sum_min Minimum sum of the collection (inclusive).
numbers = validate([10, 20, 30], {"sum_min": 50})
sum_max Maximum sum of the collection (inclusive).
numbers = validate([10, 20, 30], {"sum_max": 70})
element_min Minimum value for any element (inclusive).
numbers = validate([10, 20, 30], {"element_min": 5})
element_max Maximum value for any element (inclusive).
numbers = validate([10, 20, 30], {"element_max": 40})
regex String must match the regex.
cat_or_dog = validate("cat", {"regex": "cat|dog"})
must_be_true Custom rule: a function that returns True for allowed values.
def is_even(x: int) -> bool:
return x % 2 == 0
even_number = validate(8, {"must_be_true": is_even})
This calls:
is_even(8)
If enough people use a must_be_true lambda, it may become an added keyword in a later version
Contributing Contributions are welcome. Please open an issue or pull request. When contributing, ensure backwards compatibility (you cannot remove keywords and/or features).
Please note, when using my module, that you will manually have to validate each time should you choose to mutate a variable.
Versioning Policy
This project guarantees full backwards compatibility. Existing rule files, keyword meanings, validator behaviors, and metadata formats will continue to work exactly as before. No update will ever break existing configurations.
Version Numbering
This project uses a non-breaking semantic versioning model:
MAJOR.MINOR.PATCH
MAJOR = large new feature families MINOR = small additive keywords or enhancements PATCH = bug fixes or internal improvements
Major bumps do not imply breaking changes. They only indicate that a significant new capability has been added.
Major bumps always reset MINOR and PATCH to 0. For example: 1.7.3 -> 2.0.0 1.12.0 -> 2.0.0 1.0.0 -> 2.0.0
Minor bumps always reset PATCH to 0. For example: 1.7.3 -> 1.8.0 1.12.9 -> 1.13.0 1.0.4 -> 1.1.0
Minor Version Bumps
Minor bumps occur when adding small keywords. Examples include:
min_inclusive max_inclusive trim_whitespace pattern_flags
These additions do not change the meaning of existing keywords, do not require users to modify rule files, and do not alter validator behavior. They are classified as minor updates.
Minor bumps always reset PATCH to 0
Major Version Bumps
Major bumps occur only when adding large new keyword families or entire new capability domains. Examples include:
a full date-validation system a full numeric-range system a full conditional-rules system a full schema-level rule system
These are major features, even though they remain fully backwards-compatible. Major bumps communicate that the release adds a significant new capability.
Major bumps always reset MINOR and PATCH to 0.
Patch Version Bumps
Patch bumps occur when fixing bugs, improving internal logic, optimizing performance, correcting documentation, or adjusting error messages without changing meaning. Patch updates never add new keywords.
Summary
MAJOR: large new feature families, backwards-compatible, resets MINOR and PATCH to 0 MINOR: small additive keywords, backwards-compatible PATCH: fixes only, backwards-compatible
Credits
Microsoft Copilot — for helping me code it. It did a TON of the coding, and to be honest, I couldn't have made this project without it. It did some a lot of readme, in addition to it writing most of CONTRIBUTING.md
Dad — for helping me along the journey.
License Type: BSD 3-clause
Release files for enforce-rules 1.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| enforce_rules-1.0.0.tar.gz | 8.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| enforce_rules-1.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 16.0 kB
Release files / enforce_rules-1.0.0.tar.gz
| Download URL | enforce_rules-1.0.0.tar.gz |
|---|---|
| Size | 8.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
bec55c43962ace1b853b8bdcd13dbf59682d52bf5fc4a1a174a32308f95bdf2a
|
|
BLAKE2b-256 checksum How to use checksums |
bf169631b7a2714231d91ceaf18d9f504ad34c5b50af9ccb3a4cc02a542b2cf7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.7
|
Release files / enforce_rules-1.0.0-py3-none-any.whl
| Download URL | enforce_rules-1.0.0-py3-none-any.whl |
|---|---|
| Size | 7.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
8fde46b5e23a402ec1702ceff9f33a13628637bbf6df71c3959dab9327c8da2c
|
|
BLAKE2b-256 checksum How to use checksums |
103bcdffcb734e9e4b8111ba68215b2fc8d167cd5ec679a48c676795723fa0d7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.7
|