## Change Summary

This means `pydantic/__init__.py` is now lazy about all imports using `__getattr__`.

Import performance changes:
### Before

```
code/pydantic 0 ➤ pdm run python -m timeit -n 1 -r 1 "import pydantic"
1 loop, best of 1: 29.7 msec per loop
```

```
code/pydantic 0 ➤ pdm run python -m timeit -n 1 -r 1 "from pydantic import BaseModel"
1 loop, best of 1: 30.4 msec per loop
```

### After

```
code/pydantic 0 ➤ pdm run python -m timeit -n 1 -r 1 "import pydantic"
1 loop, best of 1: 4.8 msec per loop
```

```
(env3112) code/pydantic 0 ➤ pdm run python -m timeit -n 1 -r 1 "from pydantic import BaseModel"           
1 loop, best of 1: 18.7 msec per loop
```

Tuna output:

<img width="1421" alt="image" src="https://github.com/pydantic/pydantic/assets/4039449/0a57434e-fdda-4709-8793-d96c0a0ee762">

## Related issue number

As promised in https://github.com/pydantic/pydantic/pull/7423#issuecomment-1733361629.

## Checklist

* [x] The pull request title is a good summary of the changes - it will be used in the changelog
* [ ] Unit tests for the changes exist
* [x] Tests pass on CI
* [ ] Documentation reflects the changes where applicable
* [x] My PR is ready to review, **please add a comment including the phrase "please review" to assign reviewers**


Selected Reviewer: @sydney-runkle
Use `__getattr__` for all package imports, improve import time. ## Change Summary

This means `pydantic/__init__.py` is now lazy about all imports using `__getattr__`.

Import performance changes:
### Before

```
code/pydantic 0 ➤ pdm run python -m timeit -n 1 -r 1 "import pydantic"
1 loop, best of 1: 29.7 msec per loop
```

```
code/pydantic 0 ➤ pdm run python -m timeit -n 1 -r 1 "from pydantic import BaseModel"
1 loop, best of 1: 30.4 msec per loop
```

### After

```
code/pydantic 0 ➤ pdm run python -m timeit -n 1 -r 1 "import pydantic"
1 loop, best of 1: 4.8 msec per loop
```

```
(env3112) code/pydantic 0 ➤ pdm run python -m timeit -n 1 -r 1 "from pydantic import BaseModel"           
1 loop, best of 1: 18.7 msec per loop
```

Tuna output:

<img width="1421" alt="image" src="https://github.com/pydantic/pydantic/assets/4039449/0a57434e-fdda-4709-8793-d96c0a0ee762">

## Related issue number

As promised in https://github.com/pydantic/pydantic/pull/7423#issuecomment-1733361629.

## Checklist

* [x] The pull request title is a good summary of the changes - it will be used in the changelog
* [ ] Unit tests for the changes exist
* [x] Tests pass on CI
* [ ] Documentation reflects the changes where applicable
* [x] My PR is ready to review, **please add a comment including the phrase "please review" to assign reviewers**


Selected Reviewer: @sydney-runkle
"""
PYTEST_DONT_REWRITE
"""
import difflib
import pprint

import pytest
from dirty_equals import HasRepr

from pydantic import BaseModel, ValidationError, field_validator


def _pformat_lines(obj):
    return pprint.pformat(obj).splitlines(keepends=True)


def _assert_eq(left, right):
    if left != right:
        pytest.fail('\n' + '\n'.join(difflib.ndiff(_pformat_lines(left), _pformat_lines(right))))


def test_assert_raises_validation_error():
    class Model(BaseModel):
        a: str

        @field_validator('a')
        @classmethod
        def check_a(cls, v):
            assert v == 'a', 'invalid a'
            return v

    assert Model(a='a').a == 'a'

    with pytest.raises(ValidationError) as exc_info:
        Model(a='snap')

    _assert_eq(
        [
            {
                'ctx': {'error': HasRepr(repr(AssertionError('invalid a')))},
                'input': 'snap',
                'loc': ('a',),
                'msg': 'Assertion failed, invalid a',
                'type': 'assertion_error',
            }
        ],
        exc_info.value.errors(include_url=False),
    )

"""
PYTEST_DONT_REWRITE
"""
import difflib
import pprint

import pytest
from dirty_equals import HasRepr

from pydantic import BaseModel, ValidationError, field_validator


def _pformat_lines(obj):
    return pprint.pformat(obj).splitlines(keepends=True)


def _assert_eq(left, right):
    if left != right:
        pytest.fail('\n' + '\n'.join(difflib.ndiff(_pformat_lines(left), _pformat_lines(right))))


def test_assert_raises_validation_error():
    class Model(BaseModel):
        a: str

        @field_validator('a')
        @classmethod
        def check_a(cls, v):
            assert v == 'a', 'invalid a'
            return v

    assert Model(a='a').a == 'a'

    with pytest.raises(ValidationError) as exc_info:
        Model(a='snap')

    _assert_eq(
        [
            {
                'ctx': {'error': HasRepr(repr(AssertionError('invalid a')))},
                'input': 'snap',
                'loc': ('a',),
                'msg': 'Assertion failed, invalid a',
                'type': 'assertion_error',
            }
        ],
        exc_info.value.errors(include_url=False),
    )
