From 0e3be8cdc5aeaa2642d1e3e03697441d3f0aa634 Mon Sep 17 00:00:00 2001 From: Edward-K1 Date: Fri, 24 Jul 2026 14:03:46 +0300 Subject: [PATCH 1/4] update rtd docs for 0.7 --- docs/conf.py | 2 +- docs/examples.rst | 48 ++++++++++++++++++++++++++++++- docs/fastmodel.rst | 46 +++++++++++++++++++++++++++++- docs/index.rst | 18 ++++++------ docs/quickstart.rst | 26 +++++++++++++++-- docs/v.rst | 69 +++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 194 insertions(+), 15 deletions(-) create mode 100644 docs/v.rst diff --git a/docs/conf.py b/docs/conf.py index 233db92..d215490 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -6,7 +6,7 @@ project = 'validatedata' copyright = '2026, Edward Kigozi' author = 'Edward Kigozi' -release = '0.6.0' +release = '0.7.0' extensions = [ 'sphinx.ext.autodoc', diff --git a/docs/examples.rst b/docs/examples.rst index 17463b8..faa3959 100644 --- a/docs/examples.rst +++ b/docs/examples.rst @@ -42,6 +42,26 @@ The performance advantage of the *validator* function on invalid data comes from is_str_or_int_list(['a', 1, 'c']) # True +One-off checks with ``V`` +-------------------------- + +When you just need a yes/no answer about a single value — no rule, no +compiled validator — use ``V``. + +.. code-block:: python + + from validatedata import V + + if not V.email(request.form.get('email', '')): + return 'invalid email', 400 + + V.raise_on_fail(True) + V.int(user_input) # raises TypeError if not an int + +See :doc:`v` for the full reference. + +---- + User registration ----------------- @@ -540,4 +560,30 @@ When you have a recurring data shape, define a model once and reuse it. data = product.to_dict() # Reconstruct from dict (fast path) - product2 = Product.from_dict(data, validate="check") \ No newline at end of file + product2 = Product.from_dict(data, validate="check") + +---- + +Bridging an existing Pydantic model +------------------------------------- + +Already have models defined with Pydantic, msgspec, or dataclasses? Bridge +them into ``FastModel`` instead of rewriting them from scratch. + +.. code-block:: python + + from pydantic import BaseModel, Field + from validatedata import FastModel + + class PyProduct(BaseModel): + name: str = Field(min_length=3, max_length=100) + price: float = Field(ge=0) + + FastProduct = FastModel.bridge(PyProduct) + + product = FastProduct(name="Widget", price=19.99) # compiled validation + data = product.to_dict() # same serialization as any FastModel + +See :doc:`fastmodel` for the full reference, including ``extra_rules`` and +``field_overrides`` for constraints (like ``gt``/``lt`` or ``multiple_of``) +that have no direct equivalent in validatedata's engine. \ No newline at end of file diff --git a/docs/fastmodel.rst b/docs/fastmodel.rst index 11b97eb..e6e718c 100644 --- a/docs/fastmodel.rst +++ b/docs/fastmodel.rst @@ -321,4 +321,48 @@ whole model — the same function used internally by `is_valid_data` and the .. code-block:: python validate = User.get_validator() - validate({"username": "alice", "email": "alice@example.com"}) # True / False \ No newline at end of file + validate({"username": "alice", "email": "alice@example.com"}) # True / False + +---- + +Bridging from Pydantic, msgspec, or dataclasses +------------------------------------------------- + +.. versionadded:: 0.7.0 + +Already have models defined with another library? ``FastModel.bridge()`` builds +an equivalent `FastModel` subclass from a Pydantic model, msgspec ``Struct``, or +dataclass — carrying over field constraints (``min_length``/``max_length``, +``ge``/``le``, ``pattern``, ``Literal`` choices, defaults) so you get compiled +validation and serialization without rewriting the model. + +.. code-block:: python + + from pydantic import BaseModel, Field + from validatedata import FastModel + + class PyUser(BaseModel): + username: str = Field(min_length=3, max_length=32, pattern=r'^[a-z0-9_]+$') + age: int = Field(ge=18) + + FastUser = FastModel.bridge(PyUser) + + FastUser(username="alice", age=25) # works + FastUser(username="al", age=25) # raises ValidationError + +Constraints with no equivalent in validatedata's engine — ``gt``/``lt`` (strict +bounds), ``multiple_of``, and msgspec's ``tz`` — raise ``ValueError`` at bridge +time rather than being silently dropped or loosened. Supply your own rule for +that field via ``extra_rules`` to bridge it anyway: + +.. code-block:: python + + FastModel.bridge( + PyUser, + extra_rules={"age": "int|min:18"}, # handle it yourself + field_overrides={"username": Rule(min=5, max=10)}, # or replace a rule entirely + model_check=my_cross_field_check, + ) + + # Bridge an instance directly — returns a populated FastModel instance + bridged_user = FastModel.bridge(PyUser(username="alice", age=25)) \ No newline at end of file diff --git a/docs/index.rst b/docs/index.rst index 121134e..2397aed 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -14,21 +14,18 @@ An easier way to validate data in python. **Seven validation modes – one simple syntax.** 1. **`validator()`** – One word: speed. Ideal for high‑throughput streaming. msgspec, handwritten code, and this function will compete for first place. - 2. **`FastModel`** – declarative, typed models with compiled validation, rich error messages, and serialization. + 2. **`FastModel`** – declarative, typed models with compiled validation, rich error messages, serialization, and one-line bridging from Pydantic, msgspec, or dataclasses. 3. **`V`** – fast validation using simple inline checks, e.g if V.int(5), V.email("not"). Returns bool by default but user can enable exceptions 4. **`validate_data()`** / **`validate_data_fast()`** – general‑purpose validation with detailed errors, nested structures, and optional mutation. - 4. **`@validate`** – decorator for function argument validation. - 5. **`@validate_types`** – decorator that uses Python type annotations. - 6. **`FastModel`** – declarative, typed models with compiled validation, rich error messages, and serialization. + 5. **`@validate`** – decorator for function argument validation. + 6. **`@validate_types`** – decorator that uses Python type annotations. 7. **`autovalidate` / `autovalidate_package`** – automatically apply `@validate_types` to entire modules or packages. -Validatedata gives you expressive rules and fits naturally into any Python workflow. It can be used in lightweight scripts, Web APIs, and high‑volume data processing. +Validatedata gives you expressive rules and fits naturally into any Python workflow. It can be used by everything from lightweight scripts to high‑volume data processing. -**New in v0.6:** -- **`FastModel`** – declarative models with compiled validation, cross‑field checks, and zero‑overhead serialization. -- **`validate_data_fast`** – the speed of `validator()` combined with rich error messages. This is an **experimental** fast path that will eventually replace `validate_data` once the API stabilises. -- **`autovalidate` & `autovalidate_package`** – automatically apply `@validate_types` to whole modules or packages. -- **Custom type registration** – add your own type checkers with `register_type` / `unregister_type`. +**New in v0.7:** +- **`FastModel.bridge()`** – turn an existing Pydantic model, msgspec ``Struct``, or dataclass into a `FastModel` subclass in one line, carrying over field constraints (`min_length`, `ge`/`le`, `pattern`, `Literal` choices, and more). +- **`V`** – single-line type checks (`V.int(x)`, `V.email(x)`) for when a full `Rule` or `FastModel` is more than you need. - **`check_rule`** – validate rule dicts before using them. - **`VALID_RULE_KEYS`** – introspection of all recognised rule keys. @@ -106,6 +103,7 @@ Benchmarks (1 million repetitions) autovalidate fast-validator fastmodel + v examples .. toctree:: diff --git a/docs/quickstart.rst b/docs/quickstart.rst index b372e1c..71c881c 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -48,7 +48,7 @@ import. Seven ways to validate -------------------- -Validatedata offers six entry points, from ultra‑fast boolean checks to automatic +Validatedata offers seven entry points, from ultra‑fast boolean checks to automatic package‑wide validation. 1. **`validator()`** – fastest, boolean only @@ -118,7 +118,29 @@ package‑wide validation. user = User(name="Alice", email="alice@example.com") -7. **Auto‑validation of modules / packages** +Already have a Pydantic model, msgspec ``Struct``, or dataclass? Bridge it +instead of rewriting it — see :doc:`fastmodel` for details. + +.. code-block:: python + + FastUser = FastModel.bridge(ExistingPydanticModel) + +7. **`V`** – single-line type checks +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: python + + from validatedata import V + + if V.int(5): + print('ok') + if not V.email('not-an-email'): + print('invalid') + +See :doc:`v` for the full reference, including ``V.raise_on_fail()`` and +``V.check()``. + +8. **Auto‑validation of modules / packages** ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: python diff --git a/docs/v.rst b/docs/v.rst new file mode 100644 index 0000000..1812378 --- /dev/null +++ b/docs/v.rst @@ -0,0 +1,69 @@ +V – Single-Line Type Checks +============================ + +.. versionadded:: 0.7.0 + +``V`` answers one question about one value, inline — no ``Rule``, no +``FastModel``, no rule dict. + +.. code-block:: python + + from validatedata import V + + if V.int(5): + ... + if not V.email(user_input): + raise ValueError("bad email") + +``V`` covers the same base types as ``validator()`` — ``int``, ``str``, +``float``, ``bool``, ``list``, ``dict``, ``tuple``, ``set`` — plus format +checks like ``email``, ``url``, ``uuid``, ``date``, ``ip``, ``phone``, +``slug``, ``semver``, ``color``, ``even``, ``odd``, ``prime``, ``decimal``, +``path``. Each is a plain function on the class — nothing to instantiate or +compile. + +---- + +Raising instead of returning ``False`` +---------------------------------------- + +By default a failed check returns ``False``. Call ``V.raise_on_fail(True)`` to +switch every check to a raising variant that throws ``TypeError`` naming the +expected and actual types: + +.. code-block:: python + + V.raise_on_fail(True) + V.int("not an int") + # TypeError: expected int, got str + + V.raise_on_fail(False) # back to bool-returning + V.int("not an int") # False + +This is global to the ``V`` class, not per-call. If you need both behaviors +concurrently, use ``V.check(...)`` with your own ``try``/``except`` instead of +toggling this. + +---- + +Types not predeclared on ``V`` +--------------------------------- + +For a type that isn't one of ``V``'s base-type attributes — one you registered +via :func:`register_type`, or a plain Python/stdlib type — use ``V.check()``: + +.. code-block:: python + + V.check("datetime", some_dt) + V.check("MyRegisteredType", obj) + +``V.check()`` always returns a bool and does not honor ``raise_on_fail()``. + +---- + +What ``V`` deliberately doesn't do +-------------------------------------- + +No rules, no pipe strings, no ``Rule`` composition. Constraint logic (``min``, +``max``, ``pattern``, ``nullable``, ...) belongs to ``Rule``/``FastModel`` — +``V`` only ever answers "is this value this type," optionally loudly. From 3d2177bdacf9eeab547b7bb6272cf2a92d0abd3a Mon Sep 17 00:00:00 2001 From: Edward-K1 Date: Fri, 14 Aug 2026 06:29:37 +0300 Subject: [PATCH 2/4] make typing extensions dependency optional --- validatedata/fastmodel.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/validatedata/fastmodel.py b/validatedata/fastmodel.py index 3ca9571..f2bec4b 100644 --- a/validatedata/fastmodel.py +++ b/validatedata/fastmodel.py @@ -35,7 +35,11 @@ if sys.version_info >= (3, 9): from typing import Annotated else: - from typing_extensions import Annotated + try: + from typing_extensions import Annotated + except ImportError: + # Fallback for users on Python < 3.9 who opt out of the extension + Annotated = type("Annotated", (), {}) # --------------------------------------------------------------------------- # Codegen flag From 8e7464ca9bc7890eba70da9fcb146c18cdfb2c87 Mon Sep 17 00:00:00 2001 From: Edward-K1 Date: Fri, 14 Aug 2026 06:33:42 +0300 Subject: [PATCH 3/4] bump version to 0.7.1 --- pyproject.toml | 3 +-- uv.lock | 2 +- validatedata/__init__.py | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 5fa86e6..0903b73 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "validatedata" -version = "0.7.0" +version = "0.7.1" authors = [ { name="Edward Kigozi", email="eddyk1collab@gmail.com" }, ] @@ -15,7 +15,6 @@ license = "MIT" requires-python = ">=3.8" dependencies = [ "python-dateutil", - "typing_extensions>=4.0.0; python_version<'3.9'", ] keywords = ["validate", "data", "validation"] classifiers = [ diff --git a/uv.lock b/uv.lock index 5463e39..f905296 100644 --- a/uv.lock +++ b/uv.lock @@ -38,7 +38,7 @@ wheels = [ [[package]] name = "validatedata" -version = "0.7.0" +version = "0.7.1" source = { editable = "." } dependencies = [ { name = "python-dateutil" }, diff --git a/validatedata/__init__.py b/validatedata/__init__.py index 05f2187..fdd9018 100644 --- a/validatedata/__init__.py +++ b/validatedata/__init__.py @@ -10,7 +10,7 @@ from . import bridge as _bridge # noqa: F401 (attaches FastModel.bridge) from .v import V -__version__ = '0.7.0' +__version__ = '0.7.1' __all__ = [ 'validate', From a508fa5e610221b06e00cd41b6303433ed767760 Mon Sep 17 00:00:00 2001 From: Edward-K1 Date: Fri, 14 Aug 2026 06:40:49 +0300 Subject: [PATCH 4/4] comment out typing extensions in requirements.txt --- requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 4513623..c952a95 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,6 @@ pytest python-dateutil>=2.8.2 -typing_extensions>=4.0.0; python_version<'3.9' +# typing_extensions>=4.0.0; python_version<'3.9' # optional. for users who want to use annotated +# on older python versions zipp>=3.19.1 # not directly required, pinned by Snyk to avoid a vulnerability