<!-- Thank you for your contribution! -->
<!-- Unless your change is trivial, please create an issue to discuss the change before creating a PR -->

## Change Summary

<!-- Please give a short summary of the changes. -->

Emit a warning when writing:

```python
class Model(Generic[T], BaseModel):
```

instead of:

```python
class Model(BaseModel, Generic[T]):
```

More generally, `BaseModel` must appear in the `__mro__` before `Generic`, so indirect subclasses are also checked. This means that `__class_getitem__` will use `BaseModel` instead of `Generic` and `Model[T]` will return a model class rather than a `_GenericAlias`.

## Related issue number

Closes https://github.com/pydantic/pydantic/issues/7845

Waiting for a reply to https://github.com/pydantic/pydantic/issues/6994#issuecomment-1773295810 before continuing here, but thought I should push what I have so far. The failing tests might be of interest.

## Checklist

* [x] The pull request title is a good summary of the changes - it will be used in the changelog
* [x] Unit tests for the changes exist
* [x] Tests pass on CI
* [x] 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: @dmontagu
Warn if a class inherits from Generic before BaseModel. <!-- Thank you for your contribution! -->
<!-- Unless your change is trivial, please create an issue to discuss the change before creating a PR -->

## Change Summary

<!-- Please give a short summary of the changes. -->

Emit a warning when writing:

```python
class Model(Generic[T], BaseModel):
```

instead of:

```python
class Model(BaseModel, Generic[T]):
```

More generally, `BaseModel` must appear in the `__mro__` before `Generic`, so indirect subclasses are also checked. This means that `__class_getitem__` will use `BaseModel` instead of `Generic` and `Model[T]` will return a model class rather than a `_GenericAlias`.

## Related issue number

Closes https://github.com/pydantic/pydantic/issues/7845

Waiting for a reply to https://github.com/pydantic/pydantic/issues/6994#issuecomment-1773295810 before continuing here, but thought I should push what I have so far. The failing tests might be of interest.

## Checklist

* [x] The pull request title is a good summary of the changes - it will be used in the changelog
* [x] Unit tests for the changes exist
* [x] Tests pass on CI
* [x] 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: @dmontagu
"""Pydantic-specific warnings."""
from __future__ import annotations as _annotations

from .version import version_short

__all__ = 'PydanticDeprecatedSince20', 'PydanticDeprecationWarning'


class PydanticDeprecationWarning(DeprecationWarning):
    """A Pydantic specific deprecation warning.

    This warning is raised when using deprecated functionality in Pydantic. It provides information on when the
    deprecation was introduced and the expected version in which the corresponding functionality will be removed.

    Attributes:
        message: Description of the warning.
        since: Pydantic version in what the deprecation was introduced.
        expected_removal: Pydantic version in what the corresponding functionality expected to be removed.
    """

    message: str
    since: tuple[int, int]
    expected_removal: tuple[int, int]

    def __init__(
        self, message: str, *args: object, since: tuple[int, int], expected_removal: tuple[int, int] | None = None
    ) -> None:
        super().__init__(message, *args)
        self.message = message.rstrip('.')
        self.since = since
        self.expected_removal = expected_removal if expected_removal is not None else (since[0] + 1, 0)

    def __str__(self) -> str:
        message = (
            f'{self.message}. Deprecated in Pydantic V{self.since[0]}.{self.since[1]}'
            f' to be removed in V{self.expected_removal[0]}.{self.expected_removal[1]}.'
        )
        if self.since == (2, 0):
            message += f' See Pydantic V2 Migration Guide at https://errors.pydantic.dev/{version_short()}/migration/'
        return message


class PydanticDeprecatedSince20(PydanticDeprecationWarning):
    """A specific `PydanticDeprecationWarning` subclass defining functionality deprecated since Pydantic 2.0."""

    def __init__(self, message: str, *args: object) -> None:
        super().__init__(message, *args, since=(2, 0), expected_removal=(3, 0))

"""Pydantic-specific warnings."""
from __future__ import annotations as _annotations

from .version import version_short

__all__ = 'PydanticDeprecatedSince20', 'PydanticDeprecationWarning'


class PydanticDeprecationWarning(DeprecationWarning):
    """A Pydantic specific deprecation warning.

    This warning is raised when using deprecated functionality in Pydantic. It provides information on when the
    deprecation was introduced and the expected version in which the corresponding functionality will be removed.

    Attributes:
        message: Description of the warning.
        since: Pydantic version in what the deprecation was introduced.
        expected_removal: Pydantic version in what the corresponding functionality expected to be removed.
    """

    message: str
    since: tuple[int, int]
    expected_removal: tuple[int, int]

    def __init__(
        self, message: str, *args: object, since: tuple[int, int], expected_removal: tuple[int, int] | None = None
    ) -> None:
        super().__init__(message, *args)
        self.message = message.rstrip('.')
        self.since = since
        self.expected_removal = expected_removal if expected_removal is not None else (since[0] + 1, 0)

    def __str__(self) -> str:
        message = (
            f'{self.message}. Deprecated in Pydantic V{self.since[0]}.{self.since[1]}'
            f' to be removed in V{self.expected_removal[0]}.{self.expected_removal[1]}.'
        )
        if self.since == (2, 0):
            message += f' See Pydantic V2 Migration Guide at https://errors.pydantic.dev/{version_short()}/migration/'
        return message


class PydanticDeprecatedSince20(PydanticDeprecationWarning):
    """A specific `PydanticDeprecationWarning` subclass defining functionality deprecated since Pydantic 2.0."""

    def __init__(self, message: str, *args: object) -> None:
        super().__init__(message, *args, since=(2, 0), expected_removal=(3, 0))


class GenericBeforeBaseModelWarning(Warning):
    pass
