# valguard
_Constraint-aware value types for semantic validation in Python pipelines_
**valguard** is a lightweight framework for defining and validating values in data pipelines. It separates values from constraints. This allows a source to publish the `constraint` against which it will validate each `value`. An `implies` function can determine whether values satisfying an upstream constraint are guaranteed to satisfy a downstream constraint.
[](https://www.apache.org/licenses/LICENSE-2.0)
[](https://pypi.org/project/valguard/)
[](https://pypi.org/project/valguard/)
[](https://valguard.readthedocs.io/en/latest/)
---
## Key Features
- Declarative value types
- Validators for clean pipeline integration
- Type-safe abstractions compatible with Python 3.12+
## Usage
### Values
A `Value` stores a particular kind of value. It is immutable. Different methods are provided for accessing the value depending on its type: this provides a concise way to validate type and access the value simultaneously. If `x` is not an integer then `x.as_int` will raise an exception.
To avoid raising an exception, use `isinstance()` to test the type beforehand.
A subclass of `NumericValue` will have the method `.to_float`.
```python
>>> from valguard import FloatValue, IntValue, NumericValue, BoolValue
>>> fv = FloatValue(1.0) # Must use 1.0 and not 1
>>> iv = IntValue(1)
>>> bv = BoolValue(True)
>>> isinstance(bv,NumericValue)
False
>>> isinstance(iv,NumericValue)
True
>>> bv.as_float
Traceback (most recent call last):
...
valguard.exceptions.TypeMismatchError: Incompatible accessor
>>> iv.as_float
Traceback (most recent call last):
...
valguard.exceptions.TypeMismatchError: Incompatible accessor
>>> iv.to_float
1.0
>>> iv.as_int
1
>>> fv.as_float
1.0
>>> fv.to_float
1.0
```
### Constraints
Constraints can be placed on both type and value.
```python
>>> from valguard import IntValue, BoolValue, IntervalConstraint, NumericConstraint, IntConstraint
>>> iv = IntValue(23)
>>> bv = BoolValue(False)
>>> interval_a = IntervalConstraint(0,100)
>>> interval_b = IntervalConstraint(10,20)
>>> interval_a.validate(iv)
IntValue(23)
>>> interval_b.validate(iv)
Traceback (most recent call last):
...
valguard.exceptions.ValidationError: Invalid value: 23.0 lies outside [10.0, 20.0]
>>> interval_a.validate(bv)
Traceback (most recent call last):
...
valguard.exceptions.ValidationError: Invalid value: expected a numeric, got BoolValue(False)
>>> IntConstraint().validate(iv)
IntValue(23)
>>> IntConstraint().validate(iv).as_int
23
>>> NumericConstraint().validate(iv).to_float
23.0
>>> NumericConstraint().validate(bv).to_float
Traceback (most recent call last):
...
valguard.exceptions.ValidationError: Invalid value: expected a numeric, got BoolValue(False)
```
#### The `implies` function
The `implies` function determines whether one constraint implies another constraint. Constraint A implies constraint B if every value that satisfies A will also satisfy B.
```python
>>> from valguard import BoundedIntConstraint, NumericConstraint, FloatConstraint, implies
>>> interval_A = BoundedIntConstraint(0,100)
>>> interval_B = BoundedIntConstraint(20,80)
>>> implies(interval_A, interval_B)
False
>>> implies(interval_B, interval_A)
True
>>> implies(interval_A, FloatConstraint)
False
>>> implies(interval_A, NumericConstraint)
True
>>> implies(NumericConstraint, interval_A)
False
```
Constraints that are parametrised (such as `IntervalConstraint` and `LiteralStrConstraint`) can be used in two ways: as an instance with specific parameters, or as a class. When used as a class, `implies` behaves differently depending on whether the class appears as the first or second argument to `implies`. As the first argument, `implies(class, B)` is True if **all** instances of `class` would imply `B`. As the second argument, `implies(A, class)` is True if **at least one** instance of `class` would be implied by `A`. For non-parametrised classes, there is no difference whether a class or an instance is used.
### Constrained Value Dictionary
A `ConstrainedValueDict` behaves like a dictionary but its values are validated against a constraint at the time of insertion.
It is intentional that no type casting is performed, not even from `int` to `IntValue`.
```python
>>> from valguard import IntValue, IntConstraint, ConstrainedValueDict
>>> d = ConstrainedValueDict(IntConstraint())
>>> d["one"] = IntValue(1)
>>> d["one"]
IntValue(1)
>>> d["two"] = 2
Traceback (most recent call last):
...
valguard.exceptions.ValidationError: Invalid value: expected an integer, got 2
```
## Installation
```bash
pip install valguard
```
Raw data
{
"_id": null,
"home_page": "https://github.com/jhmanton/valguard",
"name": "valguard",
"maintainer": null,
"docs_url": null,
"requires_python": "<4.0,>=3.12",
"maintainer_email": null,
"keywords": "data, validation, values, constraints, pipelines",
"author": "Jonathan Manton",
"author_email": "j.manton@ieee.org",
"download_url": "https://files.pythonhosted.org/packages/dc/91/8158c69c592003446a729f6f8b7df5941e882b587c8418f62a371d183659/valguard-1.0.1.tar.gz",
"platform": null,
"description": "# valguard \n_Constraint-aware value types for semantic validation in Python pipelines_\n\n**valguard** is a lightweight framework for defining and validating values in data pipelines. It separates values from constraints. This allows a source to publish the `constraint` against which it will validate each `value`. An `implies` function can determine whether values satisfying an upstream constraint are guaranteed to satisfy a downstream constraint.\n\n[](https://www.apache.org/licenses/LICENSE-2.0)\n[](https://pypi.org/project/valguard/)\n[](https://pypi.org/project/valguard/)\n[](https://valguard.readthedocs.io/en/latest/)\n\n---\n\n## Key Features\n\n- Declarative value types\n- Validators for clean pipeline integration\n- Type-safe abstractions compatible with Python 3.12+\n\n## Usage\n\n### Values\n\nA `Value` stores a particular kind of value. It is immutable. Different methods are provided for accessing the value depending on its type: this provides a concise way to validate type and access the value simultaneously. If `x` is not an integer then `x.as_int` will raise an exception.\n\nTo avoid raising an exception, use `isinstance()` to test the type beforehand.\n\nA subclass of `NumericValue` will have the method `.to_float`.\n\n```python\n>>> from valguard import FloatValue, IntValue, NumericValue, BoolValue\n>>> fv = FloatValue(1.0) # Must use 1.0 and not 1\n>>> iv = IntValue(1)\n>>> bv = BoolValue(True)\n>>> isinstance(bv,NumericValue)\nFalse\n>>> isinstance(iv,NumericValue)\nTrue\n>>> bv.as_float\nTraceback (most recent call last):\n ...\nvalguard.exceptions.TypeMismatchError: Incompatible accessor\n>>> iv.as_float\nTraceback (most recent call last):\n ...\nvalguard.exceptions.TypeMismatchError: Incompatible accessor\n>>> iv.to_float\n1.0\n>>> iv.as_int\n1\n>>> fv.as_float\n1.0\n>>> fv.to_float\n1.0\n```\n\n### Constraints\n\nConstraints can be placed on both type and value.\n\n```python\n>>> from valguard import IntValue, BoolValue, IntervalConstraint, NumericConstraint, IntConstraint\n>>> iv = IntValue(23)\n>>> bv = BoolValue(False)\n>>> interval_a = IntervalConstraint(0,100)\n>>> interval_b = IntervalConstraint(10,20)\n>>> interval_a.validate(iv)\nIntValue(23)\n>>> interval_b.validate(iv)\nTraceback (most recent call last):\n ...\nvalguard.exceptions.ValidationError: Invalid value: 23.0 lies outside [10.0, 20.0]\n>>> interval_a.validate(bv)\nTraceback (most recent call last):\n ...\nvalguard.exceptions.ValidationError: Invalid value: expected a numeric, got BoolValue(False)\n>>> IntConstraint().validate(iv)\nIntValue(23)\n>>> IntConstraint().validate(iv).as_int\n23\n>>> NumericConstraint().validate(iv).to_float\n23.0\n>>> NumericConstraint().validate(bv).to_float\nTraceback (most recent call last):\n ...\nvalguard.exceptions.ValidationError: Invalid value: expected a numeric, got BoolValue(False)\n```\n\n#### The `implies` function\n\nThe `implies` function determines whether one constraint implies another constraint. Constraint A implies constraint B if every value that satisfies A will also satisfy B.\n\n```python\n>>> from valguard import BoundedIntConstraint, NumericConstraint, FloatConstraint, implies\n>>> interval_A = BoundedIntConstraint(0,100)\n>>> interval_B = BoundedIntConstraint(20,80)\n>>> implies(interval_A, interval_B)\nFalse\n>>> implies(interval_B, interval_A)\nTrue\n>>> implies(interval_A, FloatConstraint)\nFalse\n>>> implies(interval_A, NumericConstraint)\nTrue\n>>> implies(NumericConstraint, interval_A)\nFalse\n```\n\nConstraints that are parametrised (such as `IntervalConstraint` and `LiteralStrConstraint`) can be used in two ways: as an instance with specific parameters, or as a class. When used as a class, `implies` behaves differently depending on whether the class appears as the first or second argument to `implies`. As the first argument, `implies(class, B)` is True if **all** instances of `class` would imply `B`. As the second argument, `implies(A, class)` is True if **at least one** instance of `class` would be implied by `A`. For non-parametrised classes, there is no difference whether a class or an instance is used.\n\n### Constrained Value Dictionary\n\nA `ConstrainedValueDict` behaves like a dictionary but its values are validated against a constraint at the time of insertion.\n\nIt is intentional that no type casting is performed, not even from `int` to `IntValue`.\n\n```python\n>>> from valguard import IntValue, IntConstraint, ConstrainedValueDict\n>>> d = ConstrainedValueDict(IntConstraint()) \n>>> d[\"one\"] = IntValue(1)\n>>> d[\"one\"]\nIntValue(1)\n>>> d[\"two\"] = 2\nTraceback (most recent call last):\n ...\nvalguard.exceptions.ValidationError: Invalid value: expected an integer, got 2\n```\n\n## Installation\n\n```bash\npip install valguard\n```",
"bugtrack_url": null,
"license": "Apache-2.0",
"summary": "valguard: A lightweight framework for defining and validating values in data pipelines.",
"version": "1.0.1",
"project_urls": {
"Homepage": "https://github.com/jhmanton/valguard",
"Repository": "https://github.com/jhmanton/valguard"
},
"split_keywords": [
"data",
" validation",
" values",
" constraints",
" pipelines"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "d38e39ab08e72d05d3abafcba87541491001b706c66d9a01ed727ca12270a7e4",
"md5": "9c12a39c4130cc8b702f31bd2edf8c7a",
"sha256": "1406a2017ebc9b8984b56629e6ae278936c58a4eeeaeb640eca88561c07e0fe2"
},
"downloads": -1,
"filename": "valguard-1.0.1-py3-none-any.whl",
"has_sig": false,
"md5_digest": "9c12a39c4130cc8b702f31bd2edf8c7a",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": "<4.0,>=3.12",
"size": 14392,
"upload_time": "2025-07-27T16:02:41",
"upload_time_iso_8601": "2025-07-27T16:02:41.670287Z",
"url": "https://files.pythonhosted.org/packages/d3/8e/39ab08e72d05d3abafcba87541491001b706c66d9a01ed727ca12270a7e4/valguard-1.0.1-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "dc918158c69c592003446a729f6f8b7df5941e882b587c8418f62a371d183659",
"md5": "f23206d3068323875bfcaf09c4b83dd0",
"sha256": "8a35797b99ccbbc28266d9fcd1868e31edc535a18b9f20ca767029f4d76bf975"
},
"downloads": -1,
"filename": "valguard-1.0.1.tar.gz",
"has_sig": false,
"md5_digest": "f23206d3068323875bfcaf09c4b83dd0",
"packagetype": "sdist",
"python_version": "source",
"requires_python": "<4.0,>=3.12",
"size": 13401,
"upload_time": "2025-07-27T16:02:42",
"upload_time_iso_8601": "2025-07-27T16:02:42.610287Z",
"url": "https://files.pythonhosted.org/packages/dc/91/8158c69c592003446a729f6f8b7df5941e882b587c8418f62a371d183659/valguard-1.0.1.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-07-27 16:02:42",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "jhmanton",
"github_project": "valguard",
"travis_ci": false,
"coveralls": false,
"github_actions": false,
"lcname": "valguard"
}