Repository: pydantic/pydantic

Issue:
`StrictBytes` does not raise `ValidationError` when `max_length` is present in `Field`
### Initial Checks

- [X] I have searched GitHub for a duplicate issue and I'm sure this is something new
- [X] I have searched Google & StackOverflow for a solution and couldn't find anything
- [X] I have read and followed [the docs](https://pydantic-docs.helpmanual.io) and still think this is a bug
- [X] I am confident that the issue is with pydantic  (not my code, or another library in the ecosystem like [FastAPI](https://fastapi.tiangolo.com) or [mypy](https://mypy.readthedocs.io/en/stable))


### Description

I'm using pydantic version 1.9.2.

Mind this model:

```python
# my_model/__init__.py
from pydantic import BaseModel, StrictBytes, Field


class Model(BaseModel):
    a: StrictBytes = Field(...)
    b: StrictBytes = Field(..., max_length=5)
```

Even though `b` is `StrictBytes`, I can create an object with the following line:

`m = Model(a=b'arthur', b=123)` which I assume is a bug.

I cannot create an object the other way around:
`m = Model(a=123, b=b'art')`, which I assume is the intended behaviour.

---

Here follows a pytest example:
```python
# tests/test_my_model.py

from my_model import Model
import pytest
from pydantic import ValidationError


def test_normal():
    m = Model(a=b'arthur', b=b'lucas')
    assert m.a == b'arthur'
    assert m.b == b'lucas'

def test_large():
    with pytest.raises(ValidationError) as error:
        m = Model(a=b'arthur', b=b'arthur')
    assert 'b\n' in str(error.value)
    assert 'value_error.any_str.max_length' in str(error.value)

def test_a_str():
    with pytest.raises(ValidationError) as error:
        m = Model(a=123123, b=b'lucas')
    assert 'a\n' in str(error.value)
    assert 'type_error.bytes' in str(error.value)

def test_b_str():
    with pytest.raises(ValidationError) as error:
        m = Model(a=b'arthur', b=123)  # does not raise
    assert 'b\n' in str(error.value)
    assert 'type_error.bytes' in str(error.value)
```

And the output from pytest:
```bash
platform linux -- Python 3.10.6, pytest-6.2.5, py-1.11.0, pluggy-1.0.0
rootdir: /home/arthurazs/Desktop/my_model
collected 4 items                                                                     

tests/test_my_model.py ...F                                                      [100%]

====================================== FAILURES =======================================
_____________________________________ test_b_str ______________________________________

    def test_b_str():
>       with pytest.raises(ValidationError) as error:
E       Failed: DID NOT RAISE <class 'pydantic.error_wrappers.ValidationError'>

tests/test_my_model.py:24: Failed
=============================== short test summary info ===============================
FAILED tests/test_my_model.py::test_b_str - Failed: DID NOT RAISE <class 'pydantic.e...
============================= 1 failed, 3 passed in 0.04s =============================
```

### Example Code

```Python
from pydantic import BaseModel, StrictBytes, Field


class Model(BaseModel):
    a: StrictBytes = Field(...)
    b: StrictBytes = Field(..., max_length=5)

m = Model(a=b'arthur', b=123)  # does not raise
```


### Python, Pydantic & OS Version

```Text
pydantic version: 1.9.2
            pydantic compiled: True
                 install path: /home/arthurazs/.cache/pypoetry/virtualenvs/asd-8JLwExQw-py3.10/lib/python3.10/site-packages/pydantic
               python version: 3.10.6 (main, Aug  2 2022, 15:11:28) [GCC 9.4.0]
                     platform: Linux-5.15.0-46-generic-x86_64-with-glibc2.31
     optional deps. installed: ['typing-extensions']
```


### Affected Components

- [ ] [Compatibility between releases](https://pydantic-docs.helpmanual.io/changelog/)
- [X] [Data validation/parsing](https://pydantic-docs.helpmanual.io/usage/models/#basic-model-usage)
- [ ] [Data serialization](https://pydantic-docs.helpmanual.io/usage/exporting_models/) - `.dict()` and `.json()`
- [ ] [JSON Schema](https://pydantic-docs.helpmanual.io/usage/schema/)
- [ ] [Dataclasses](https://pydantic-docs.helpmanual.io/usage/dataclasses/)
- [ ] [Model Config](https://pydantic-docs.helpmanual.io/usage/model_config/)
- [X] [Field Types](https://pydantic-docs.helpmanual.io/usage/types/) - adding or changing a particular data type
- [ ] [Function validation decorator](https://pydantic-docs.helpmanual.io/usage/validation_decorator/)
- [ ] [Generic Models](https://pydantic-docs.helpmanual.io/usage/models/#generic-models)
- [ ] [Other Model behaviour](https://pydantic-docs.helpmanual.io/usage/models/) - `construct()`, pickling, private attributes, ORM mode
- [ ] [Settings Management](https://pydantic-docs.helpmanual.io/usage/settings/)
- [ ] [Plugins](https://pydantic-docs.helpmanual.io/) and integration with other tools - mypy, FastAPI, python-devtools, Hypothesis, VS Code, PyCharm, etc.


Discussion:
The reason is, by setting the `max_length` in `b` (`b: StrictBytes = Field(..., max_length=5)`), it won't be considered as `StrictBytes` anymore. then the provided value `123` will be converted to `bytes` during validation

```python
from pydantic import BaseModel, StrictBytes, Field


class Model(BaseModel):
    a: StrictBytes = Field(...)
    b: StrictBytes = Field(..., max_length=5)

m = Model(a=b'arthur', b=123)  # does not raise
print(m)  # a=b'arthur' b=b'123'
```

I am not sure if is it an intended behavior or not. @samuelcolvin what is your opinion here?
It's an error, happy to review a PR if it's simple and submitted asap. Otherwise this is/will be fixed in V2.

Resolving patch:
diff --git a/pydantic/schema.py b/pydantic/schema.py
--- a/pydantic/schema.py
+++ b/pydantic/schema.py
@@ -57,6 +57,7 @@
     ConstrainedStr,
     SecretBytes,
     SecretStr,
+    StrictBytes,
     conbytes,
     condecimal,
     confloat,
@@ -1087,7 +1088,13 @@ def constraint_func(**kw: Any) -> Type[Any]:
                 constraint_func = constr
             elif issubclass(type_, bytes):
                 attrs = ('max_length', 'min_length', 'regex')
-                constraint_func = conbytes
+                if issubclass(type_, StrictBytes):
+
+                    def constraint_func(**kw: Any) -> Type[Any]:
+                        return type(type_.__name__, (type_,), kw)
+
+                else:
+                    constraint_func = conbytes
             elif issubclass(type_, numeric_types) and not issubclass(
                 type_,
                 (


Test patch:
diff --git a/tests/test_types.py b/tests/test_types.py
--- a/tests/test_types.py
+++ b/tests/test_types.py
@@ -1634,6 +1634,18 @@ class Model(BaseModel):
         Model(v=0.42)
 
 
+def test_strict_bytes_max_length():
+    class Model(BaseModel):
+        u: StrictBytes = Field(..., max_length=5)
+
+    assert Model(u=b'foo').u == b'foo'
+
+    with pytest.raises(ValidationError, match='byte type expected'):
+        Model(u=123)
+    with pytest.raises(ValidationError, match='ensure this value has at most 5 characters'):
+        Model(u=b'1234567')
+
+
 def test_strict_bytes_subclass():
     class MyStrictBytes(StrictBytes):
         pass