Repository: pydantic/pydantic

Issue:
What are we doing with `tools` in V2?
I suggest we move it to deprecated, and rely on `AnalysedType` for similar functionality.

Maybe we add some convenience functions to replace `parse_obj_as` etc.?


Discussion:
I think that `AnalyzedType` is basically a direct (though non-caching) replacement for the functions in `tools`. I am okay deprecating the `tools` functions and telling people to move toward using `AnalyzedType` (and take responsibility for caching the analyzer if you want), or just not deprecating them.

I am also okay moving them to another module (without deprecation), but I think if we do that I'd be inclined to just re-export them in tools with a deprecation warning, but leave them otherwise unmodified.

The main downside I see to dropping them now is that `parse_obj_as` handles the caching for you, which I could see being a bit of a nuisance in some circumstances. (E.g., if I wrote a function that takes two items, and tries to validate the second as the type of the first. The `parse_obj_as` would handle the slightly-annoying-to-implement caching automatically; using AnalyzedType for this would be slow without implementing your own lru_cache. Not that this is a pattern I can imagine recommending though.)
Do we still plan on having simple if less performant API's like `def validate_python(type_, value)`? If so we should think about how that relates to the `tools` stuff as well.
> Do we still plan on having simple if less performant API's like `def validate_python(type_, value)`

If the answer is yes, and I'm not opposed to it being yes, I would say this is tantamount to the "moving them to another module (without deprecation)" case I mentioned in my message above. (I.e., my suggestion in this case would be that we re-export from `.tools` with a `DeprecationWarning` purely for the import path.)

Same points about caching apply.
move to `pydantic.deprecated`.

Resolving patch:
diff --git a/pydantic/__init__.py b/pydantic/__init__.py
--- a/pydantic/__init__.py
+++ b/pydantic/__init__.py
@@ -13,11 +13,11 @@
 from .config import ConfigDict, Extra
 from .decorators import field_serializer, field_validator, model_serializer, root_validator, validator
 from .deprecated.config import BaseConfig
+from .deprecated.tools import *
 from .errors import *
 from .fields import Field, PrivateAttr
 from .main import *
 from .networks import *
-from .tools import *
 from .types import *
 from .validate_call import validate_call
 from .version import VERSION
diff --git a/pydantic/deprecated/__init__.py b/pydantic/deprecated/__init__.py
new file mode 100644
diff --git a/pydantic/tools.py b/pydantic/deprecated/tools.py
similarity index 95%
rename from pydantic/tools.py
rename to pydantic/deprecated/tools.py
--- a/pydantic/tools.py
+++ b/pydantic/deprecated/tools.py
@@ -4,11 +4,11 @@
 import warnings
 from typing import Any, Callable, Type, TypeVar, Union
 
-from . import AnalyzedType
+from .. import AnalyzedType
 
 __all__ = 'parse_obj_as', 'schema_of', 'schema_json_of'
 
-from .json_schema import DEFAULT_REF_TEMPLATE, GenerateJsonSchema
+from ..json_schema import DEFAULT_REF_TEMPLATE, GenerateJsonSchema
 
 NameFactory = Union[str, Callable[[Type[Any]], str]]
 


Test patch:
diff --git a/tests/mypy/test_mypy.py b/tests/mypy/test_mypy.py
--- a/tests/mypy/test_mypy.py
+++ b/tests/mypy/test_mypy.py
@@ -210,9 +210,9 @@ def test_success_cases_run(module: str) -> None:
 
 def test_explicit_reexports():
     from pydantic import __all__ as root_all
+    from pydantic.deprecated.tools import __all__ as tools
     from pydantic.main import __all__ as main
     from pydantic.networks import __all__ as networks
-    from pydantic.tools import __all__ as tools
     from pydantic.types import __all__ as types
 
     for name, export_all in [('main', main), ('network', networks), ('tools', tools), ('types', types)]:
diff --git a/tests/test_tools.py b/tests/test_tools.py
--- a/tests/test_tools.py
+++ b/tests/test_tools.py
@@ -4,7 +4,7 @@
 
 from pydantic import BaseModel, ValidationError
 from pydantic.dataclasses import dataclass
-from pydantic.tools import parse_obj_as, schema_json_of, schema_of
+from pydantic.deprecated.tools import parse_obj_as, schema_json_of, schema_of
 
 
 @pytest.mark.parametrize('obj,type_,parsed', [('1', int, 1), (['1'], List[int], [1])])