Repository: pydantic/pydantic

Issue:
Increase tests coverage for new pipeline API
We're introducing an experimental pipeline API in https://github.com/pydantic/pydantic/pull/9459.

The test coverage on this new file is quite low. We figured this could be a great opportunity for some eager new contributors to get their hands dirty with:

* Writing some basic tests
* Learning about this new API
* Navigating increasing test coverage

We're planning on introducing this API in 2.8, so this issue belongs in said milestone!


Discussion:
Hello!

I'm excited about the opportunity to contribute to this project, especially with the upcoming introduction of the new API in version 2.8. I have experience in writing tests and am keen to learn more about this API.

To get started, could you please provide:
•  Any specific guidelines or standards for writing tests in this project?

•  Access to the documentation for the new API?

•  Information on the current test coverage and areas that need the most attention?


I'm looking forward to getting my hands dirty with writing some basic tests and helping to navigate the increase in test coverage. Let's ensure a robust introduction for this API in the milestone!

Thank you!

Hi @mohsen2001,

> I'm excited about the opportunity to contribute to this project, especially with the upcoming introduction of the new API in version 2.8. I have experience in writing tests and am keen to learn more about this API.

Fantastic! We're excited for you to contribute, too! 🚀 

> Any specific guidelines or standards for writing tests in this project?

Regarding writing tests, we don't have any specific guidelines, but you can check out existing tests in the `tests` folder. I've also added a few tests in the PR linked above, so you can use those as a model for tests in this specific context! I've used a few helpful concepts like parametrizing tests and using pydantic's `TypeAdapter` concept for simple validation.

> Access to the documentation for the new API?

Sure thing - @adriangb has written a helpful example in the concepts section of the docs also available in that PR, and I've added some tests already that can help you get started.

> Information on the current test coverage and areas that need the most attention?

I think the best way to do this would be to pull down the code from that PR and run pytest with coverage locally. Then you can see what areas are uncovered by the existing (limited) test suite!

Resolving patch:
diff --git a/pydantic/experimental/pipeline.py b/pydantic/experimental/pipeline.py
--- a/pydantic/experimental/pipeline.py
+++ b/pydantic/experimental/pipeline.py
@@ -112,7 +112,7 @@ def validate_as(self, tp: type[_NewOutT] | Any = Ellipsis, *, strict: bool = Fal
         Types are parsed in Pydantic's `lax` mode by default,
         but you can enable `strict` mode by passing `strict=True`.
 
-        TODO: Eventually overload with type[_NewOutT] as one option and ellipsis or EllipsisType as the other, 
+        TODO: Eventually overload with type[_NewOutT] as one option and ellipsis or EllipsisType as the other,
         but this is blocked by 3.8 and 3.9 support.
         """
         if tp is Ellipsis:


Test patch:
diff --git a/tests/test_pipeline.py b/tests/test_pipeline.py
--- a/tests/test_pipeline.py
+++ b/tests/test_pipeline.py
@@ -1,11 +1,14 @@
 """Tests for the experimental transform module."""
 from __future__ import annotations
 
+import datetime
 import sys
 import warnings
+from decimal import Decimal
 from typing import Any, List, Union
 
 import pytest
+import pytz
 from typing_extensions import Annotated
 
 if sys.version_info >= (3, 9):
@@ -15,7 +18,7 @@
 
 with warnings.catch_warnings():
     warnings.filterwarnings('ignore', category=PydanticExperimentalWarning)
-    from pydantic.experimental.pipeline import transform, validate_as
+    from pydantic.experimental.pipeline import _Pipeline, transform, validate_as
 
 
 @pytest.mark.parametrize('potato_variation', ['potato', ' potato ', ' potato', 'potato ', ' POTATO ', ' PoTatO '])
@@ -31,6 +34,46 @@ def test_parse_str_with_pattern() -> None:
         ta_pattern.validate_python('POTATO')
 
 
+@pytest.mark.parametrize(
+    'type_, pipeline, valid_cases, invalid_cases',
+    [
+        (int, validate_as(...).ge(0), [0, 1, 100], [-1, -100]),
+        (float, validate_as(...).ge(0.0), [1.8, 0.0], [-1.0]),
+        (Decimal, validate_as(...).ge(Decimal(0.0)), [Decimal(1), Decimal(0.0)], [Decimal(-1.0)]),
+        (int, validate_as(...).le(5), [2, 4], [6, 100]),
+        (float, validate_as(...).le(1.0), [0.5, 0.0], [100.0]),
+        (Decimal, validate_as(...).le(Decimal(1.0)), [Decimal(1)], [Decimal(5.0)]),
+    ],
+)
+def test_ge_le(type_: Any, pipeline: _Pipeline, valid_cases: list[Any], invalid_cases: list[Any]) -> None:
+    ta = TypeAdapter(Annotated[type_, pipeline])
+    for x in valid_cases:
+        assert ta.validate_python(x) == x
+    for y in invalid_cases:
+        with pytest.raises(ValueError):
+            ta.validate_python(y)
+
+
+def test_parse_multipleOf() -> None:
+    ta_m = TypeAdapter(Annotated[int, validate_as(int).multiple_of(5)])
+    assert ta_m.validate_python(5) == 5
+    assert ta_m.validate_python(20) == 20
+    with pytest.raises(ValueError):
+        ta_m.validate_python(18)
+
+
+def test_parse_tz() -> None:
+    ta_tz = TypeAdapter(Annotated[datetime.datetime, validate_as(str).datetime_tz_naive()])
+    date = datetime.datetime(2032, 6, 4, 11, 15, 30, 400000)
+    assert ta_tz.validate_python(date) == date
+
+    ta_tza = TypeAdapter(Annotated[datetime.datetime, validate_as(str).datetime_tz_aware()])
+    date_a = datetime.datetime(2032, 6, 4, 11, 15, 30, 400000, pytz.UTC)
+    assert ta_tza.validate_python(date_a) == date_a
+    with pytest.raises(ValueError):
+        ta_tza.validate_python(date)
+
+
 @pytest.mark.parametrize(
     'method, method_arg, input_string, expected_output',
     [