Repository: pydantic/pydantic

Issue:
Add a standalone implementation for parse_raw similar to parse_file_as and parse_obj_as
# Feature Request

As described in the [docs](https://pydantic-docs.helpmanual.io/usage/models/#parsing-data-into-a-specified-type):
> Pydantic includes a standalone utility function parse_obj_as that can be used to apply the parsing logic used to populate pydantic models in a more ad-hoc way. This function behaves similarly to BaseModel.parse_obj, but works with arbitrary pydantic-compatible types.

Enabling code such as this:
```py
from typing import List

from pydantic import BaseModel, parse_obj_as

class Item(BaseModel):
    id: int
    name: str

item_data = [{'id': 1, 'name': 'My Item'}]

items = parse_obj_as(List[Item], item_data)
print(items)
#> [Item(id=1, name='My Item')]
```

This works if you have a given object () to start with, but if you have a raw string or bytes you are required to parse them first into an object.

Forcing you to write this:

```py
import json

item_data = '[{"id": 1, "name": "My Item"}]'
item_data_obj = json.loads(item_data)

items = parse_obj_as(List[Item], item_data_obj)
```

Instead, I suggest a standalone parse function, similar to `parse_obj_as` but for raw data, enabling something like this:

```py
import json

item_data = '[{"id": 1, "name": "My Item"}]'

items = parse_raw_as(List[Item], item_data)
```


Discussion:

Resolving patch:
diff --git a/pydantic/__init__.py b/pydantic/__init__.py
--- a/pydantic/__init__.py
+++ b/pydantic/__init__.py
@@ -56,6 +56,7 @@
     # tools
     'parse_file_as',
     'parse_obj_as',
+    'parse_raw_as',
     # types
     'NoneStr',
     'NoneBytes',
diff --git a/pydantic/tools.py b/pydantic/tools.py
--- a/pydantic/tools.py
+++ b/pydantic/tools.py
@@ -3,11 +3,11 @@
 from pathlib import Path
 from typing import Any, Callable, Optional, Type, TypeVar, Union
 
-from pydantic.parse import Protocol, load_file
-
+from .parse import Protocol, load_file, load_str_bytes
+from .types import StrBytes
 from .typing import display_as_type
 
-__all__ = ('parse_file_as', 'parse_obj_as')
+__all__ = ('parse_file_as', 'parse_obj_as', 'parse_raw_as')
 
 NameFactory = Union[str, Callable[[Type[Any]], str]]
 
@@ -55,3 +55,20 @@ def parse_file_as(
         json_loads=json_loads,
     )
     return parse_obj_as(type_, obj, type_name=type_name)
+
+
+def parse_raw_as(
+    type_: Type[T],
+    b: StrBytes,
+    *,
+    content_type: str = None,
+    encoding: str = 'utf8',
+    proto: Protocol = None,
+    allow_pickle: bool = False,
+    json_loads: Callable[[str], Any] = json.loads,
+    type_name: Optional[NameFactory] = None,
+) -> T:
+    obj = load_str_bytes(
+        b, proto=proto, content_type=content_type, encoding=encoding, allow_pickle=allow_pickle, json_loads=json_loads,
+    )
+    return parse_obj_as(type_, obj, type_name=type_name)


Test patch:
diff --git a/tests/test_tools.py b/tests/test_tools.py
--- a/tests/test_tools.py
+++ b/tests/test_tools.py
@@ -5,7 +5,7 @@
 
 from pydantic import BaseModel, ValidationError
 from pydantic.dataclasses import dataclass
-from pydantic.tools import parse_file_as, parse_obj_as
+from pydantic.tools import parse_file_as, parse_obj_as, parse_raw_as
 
 
 @pytest.mark.parametrize('obj,type_,parsed', [('1', int, 1), (['1'], List[int], [1])])
@@ -88,3 +88,13 @@ def custom_json_loads(*args, **kwargs):
     p = tmp_path / 'test_json_loads.json'
     p.write_text('{"1": "2"}')
     assert parse_file_as(Dict[int, int], p, json_loads=custom_json_loads) == {1: 99}
+
+
+def test_raw_as():
+    class Item(BaseModel):
+        id: int
+        name: str
+
+    item_data = '[{"id": 1, "name": "My Item"}]'
+    items = parse_raw_as(List[Item], item_data)
+    assert items == [Item(id=1, name='My Item')]