<!-- 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. -->

This fixes an issue where the JSON schema can end up using the drive letter (like `C`, `D` etc) instead of the model name on Windows if the module with the model is imported from an absolute path on the filesystem (for example using importlib).  This was caused by splitting at the first colon `:` character to remove it and the ID following it, thus on an absolute path on Windows of the form `C:\...` ended up becoming just the drive letter. This fix changes it to remove the LAST colon character and anything following it.

Modified description post-further investigation of the issue / source of the problem:

This makes the model name used in the JSON Schema be correct also if the model is loaded from a module that has a colon in the name. This should not really happen in any correct use case (python modules should not typically have colons in them), but it's possible to achieve by accident if you use the absolute file path as the module name on Windows when working with importlib. In that case you would have ended up getting just the drive letter as a model name in the JSON Schema, this fix ensures it will still work correctly in that case.



## Related issue number

<!-- WARNING: please use "fix #123" style references so the issue is closed when this PR is merged. -->

fix #7860

## 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
* [ ] My PR is ready to review, **please add a comment including the phrase "please review" to assign reviewers**

Make the model name generation more robust in JSON schema. <!-- 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. -->

This fixes an issue where the JSON schema can end up using the drive letter (like `C`, `D` etc) instead of the model name on Windows if the module with the model is imported from an absolute path on the filesystem (for example using importlib).  This was caused by splitting at the first colon `:` character to remove it and the ID following it, thus on an absolute path on Windows of the form `C:\...` ended up becoming just the drive letter. This fix changes it to remove the LAST colon character and anything following it.

Modified description post-further investigation of the issue / source of the problem:

This makes the model name used in the JSON Schema be correct also if the model is loaded from a module that has a colon in the name. This should not really happen in any correct use case (python modules should not typically have colons in them), but it's possible to achieve by accident if you use the absolute file path as the module name on Windows when working with importlib. In that case you would have ended up getting just the drive letter as a model name in the JSON Schema, this fix ensures it will still work correctly in that case.



## Related issue number

<!-- WARNING: please use "fix #123" style references so the issue is closed when this PR is merged. -->

fix #7860

## 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
* [ ] My PR is ready to review, **please add a comment including the phrase "please review" to assign reviewers**

import importlib
import inspect
import os
import re
import secrets
import sys
import textwrap
from dataclasses import dataclass
from types import FunctionType
from typing import Any, Optional

import pytest
from _pytest.assertion.rewrite import AssertionRewritingHook


def pytest_addoption(parser: pytest.Parser):
    parser.addoption('--test-mypy', action='store_true', help='run mypy tests')
    parser.addoption('--update-mypy', action='store_true', help='update mypy tests')


def _extract_source_code_from_function(function):
    if function.__code__.co_argcount:
        raise RuntimeError(f'function {function.__qualname__} cannot have any arguments')

    code_lines = ''
    body_started = False
    for line in textwrap.dedent(inspect.getsource(function)).split('\n'):
        if line.startswith('def '):
            body_started = True
            continue
        elif body_started:
            code_lines += f'{line}\n'

    return textwrap.dedent(code_lines)


def _create_module_file(code, tmp_path, name):
    name = f'{name}_{secrets.token_hex(5)}'
    path = tmp_path / f'{name}.py'
    path.write_text(code)
    return name, str(path)


@pytest.fixture(scope='session', autouse=True)
def disable_error_urls():
    # Don't add URLs during docs tests when printing
    # Otherwise we'll get version numbers in the URLs that will update frequently
    os.environ['PYDANTIC_ERRORS_OMIT_URL'] = 'true'


@pytest.fixture
def create_module(tmp_path, request):
    def run(source_code_or_function, rewrite_assertions=True):
        """
        Create module object, execute it and return
        Can be used as a decorator of the function from the source code of which the module will be constructed

        :param source_code_or_function string or function with body as a source code for created module
        :param rewrite_assertions: whether to rewrite assertions in module or not

        """
        if isinstance(source_code_or_function, FunctionType):
            source_code = _extract_source_code_from_function(source_code_or_function)
        else:
            source_code = source_code_or_function

        module_name, filename = _create_module_file(source_code, tmp_path, request.node.name)

        if rewrite_assertions:
            loader = AssertionRewritingHook(config=request.config)
            loader.mark_rewrite(module_name)
        else:
            loader = None

        spec = importlib.util.spec_from_file_location(module_name, filename, loader=loader)
        sys.modules[module_name] = module = importlib.util.module_from_spec(spec)
        spec.loader.exec_module(module)
        return module

    return run


@dataclass
class Err:
    message: str
    errors: Optional[Any] = None

    def __repr__(self):
        if self.errors:
            return f'Err({self.message!r}, errors={self.errors!r})'
        else:
            return f'Err({self.message!r})'

    def message_escaped(self):
        return re.escape(self.message)

import importlib
import inspect
import os
import re
import secrets
import sys
import textwrap
from dataclasses import dataclass
from types import FunctionType
from typing import Any, Optional

import pytest
from _pytest.assertion.rewrite import AssertionRewritingHook


def pytest_addoption(parser: pytest.Parser):
    parser.addoption('--test-mypy', action='store_true', help='run mypy tests')
    parser.addoption('--update-mypy', action='store_true', help='update mypy tests')


def _extract_source_code_from_function(function):
    if function.__code__.co_argcount:
        raise RuntimeError(f'function {function.__qualname__} cannot have any arguments')

    code_lines = ''
    body_started = False
    for line in textwrap.dedent(inspect.getsource(function)).split('\n'):
        if line.startswith('def '):
            body_started = True
            continue
        elif body_started:
            code_lines += f'{line}\n'

    return textwrap.dedent(code_lines)


def _create_module_file(code, tmp_path, name):
    name = f'{name}_{secrets.token_hex(5)}'
    path = tmp_path / f'{name}.py'
    path.write_text(code)
    return name, str(path)


@pytest.fixture(scope='session', autouse=True)
def disable_error_urls():
    # Don't add URLs during docs tests when printing
    # Otherwise we'll get version numbers in the URLs that will update frequently
    os.environ['PYDANTIC_ERRORS_OMIT_URL'] = 'true'


@pytest.fixture
def create_module(tmp_path, request):
    def run(source_code_or_function, rewrite_assertions=True, module_name_prefix=None):
        """
        Create module object, execute it and return
        Can be used as a decorator of the function from the source code of which the module will be constructed

        :param source_code_or_function string or function with body as a source code for created module
        :param rewrite_assertions: whether to rewrite assertions in module or not
        :param module_name_prefix: string prefix to use in the name of the module, does not affect the name of the file.

        """
        if isinstance(source_code_or_function, FunctionType):
            source_code = _extract_source_code_from_function(source_code_or_function)
        else:
            source_code = source_code_or_function

        module_name, filename = _create_module_file(source_code, tmp_path, request.node.name)
        if module_name_prefix:
            module_name = module_name_prefix + module_name

        if rewrite_assertions:
            loader = AssertionRewritingHook(config=request.config)
            loader.mark_rewrite(module_name)
        else:
            loader = None

        spec = importlib.util.spec_from_file_location(module_name, filename, loader=loader)
        sys.modules[module_name] = module = importlib.util.module_from_spec(spec)
        spec.loader.exec_module(module)
        return module

    return run


@dataclass
class Err:
    message: str
    errors: Optional[Any] = None

    def __repr__(self):
        if self.errors:
            return f'Err({self.message!r}, errors={self.errors!r})'
        else:
            return f'Err({self.message!r})'

    def message_escaped(self):
        return re.escape(self.message)
