Skip to content

Commit

Permalink
Small improvements to import performance (#7590)
Browse files Browse the repository at this point in the history
  • Loading branch information
samuelcolvin committed Sep 25, 2023
1 parent 0af0cc3 commit cc4b8c2
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 29 deletions.
40 changes: 25 additions & 15 deletions pydantic/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,7 @@
from ._internal._generate_schema import GenerateSchema as GenerateSchema
from ._migration import getattr_migration
from .annotated_handlers import GetCoreSchemaHandler, GetJsonSchemaHandler
from .config import ConfigDict, Extra
from .deprecated.class_validators import root_validator, validator
from .deprecated.config import BaseConfig
from .deprecated.tools import *
from .config import ConfigDict
from .errors import *
from .fields import AliasChoices, AliasPath, Field, PrivateAttr, computed_field
from .functional_serializers import PlainSerializer, SerializeAsAny, WrapSerializer, field_serializer, model_serializer
Expand Down Expand Up @@ -44,21 +41,30 @@
# this encourages pycharm to import `ValidationError` from here, not pydantic_core
ValidationError = pydantic_core.ValidationError

# WARNING __all__ from .errors is not included here, it will be removed as an export here in v2
# please use "from pydantic.errors import ..." instead
if typing.TYPE_CHECKING:
# these are imported via `__getattr__` below, but we need them here for type checking and IDE support
from .deprecated.class_validators import root_validator, validator
from .deprecated.config import BaseConfig, Extra
from .deprecated.tools import *
from .root_model import RootModel

__all__ = [
# dataclasses
'dataclasses',
# functional validators
# pydantic_core.core_schema
'ValidationInfo',
'ValidatorFunctionWrapHandler',
# functional validators
'field_validator',
'model_validator',
'AfterValidator',
'BeforeValidator',
'PlainValidator',
'WrapValidator',
# deprecated V1 functional validators
'SkipValidation',
'InstanceOf',
'WithJsonSchema',
# deprecated V1 functional validators, these are imported via `__getattr__` below
'root_validator',
'validator',
# functional serializers
Expand All @@ -71,8 +77,9 @@
'SerializationInfo',
'SerializerFunctionWrapHandler',
# config
'BaseConfig',
'ConfigDict',
# deprecated V1 config, these are imported via `__getattr__` below
'BaseConfig',
'Extra',
# validate_call
'validate_call',
Expand Down Expand Up @@ -115,7 +122,7 @@
'validate_email',
# root_model
'RootModel',
# tools
# deprecated tools, these are imported via `__getattr__` below
'parse_obj_as',
'schema_of',
'schema_json_of',
Expand Down Expand Up @@ -174,9 +181,6 @@
'Base64Str',
'Base64UrlBytes',
'Base64UrlStr',
'SkipValidation',
'InstanceOf',
'WithJsonSchema',
'GetPydanticSchema',
# type_adapter
'TypeAdapter',
Expand All @@ -194,10 +198,16 @@
# A mapping of {<member name>: (package, <module name>)} defining dynamic imports
_dynamic_imports: 'dict[str, tuple[str, str]]' = {
'RootModel': (__package__, '.root_model'),
'root_validator': (__package__, '.deprecated.class_validators'),
'validator': (__package__, '.deprecated.class_validators'),
'BaseConfig': (__package__, '.deprecated.config'),
'Extra': (__package__, '.deprecated.config'),
'parse_obj_as': (__package__, '.deprecated.tools'),
'schema_of': (__package__, '.deprecated.tools'),
'schema_json_of': (__package__, '.deprecated.tools'),
# FieldValidationInfo is deprecated, and hidden behind module a `__getattr__`
'FieldValidationInfo': ('pydantic_core', '.core_schema'),
}
if typing.TYPE_CHECKING:
from .root_model import RootModel

_getattr_migration = getattr_migration(__name__)

Expand Down
5 changes: 5 additions & 0 deletions pydantic/_migration.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
'pydantic.decorator:validate_arguments': 'pydantic.deprecated.decorator:validate_arguments',
'pydantic.class_validators:validator': 'pydantic.deprecated.class_validators:validator',
'pydantic.class_validators:root_validator': 'pydantic.deprecated.class_validators:root_validator',
'pydantic.config:BaseConfig': 'pydantic.deprecated.config:BaseConfig',
'pydantic.config:Extra': 'pydantic.deprecated.config:Extra',
}

REDIRECT_TO_V1 = {
Expand Down Expand Up @@ -270,6 +272,9 @@ def wrapper(name: str) -> object:
Returns:
The object.
"""
if name == '__path__':
raise AttributeError(f'module {__name__!r} has no attribute {name!r}')

import_path = f'{module}:{name}'
if import_path in MOVED_IN_V2.keys():
new_location = MOVED_IN_V2[import_path]
Expand Down
3 changes: 1 addition & 2 deletions pydantic/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,11 @@
from typing_extensions import Literal, TypeAlias, TypedDict

from ._migration import getattr_migration
from .deprecated.config import BaseConfig, Extra

if TYPE_CHECKING:
from ._internal._generate_schema import GenerateSchema as _GenerateSchema

__all__ = 'BaseConfig', 'ConfigDict', 'Extra'
__all__ = ('ConfigDict',)


JsonEncoder = Callable[[Any], Any]
Expand Down
39 changes: 27 additions & 12 deletions pydantic/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@
from ._migration import getattr_migration
from .annotated_handlers import GetCoreSchemaHandler, GetJsonSchemaHandler
from .config import ConfigDict
from .deprecated import copy_internals as _deprecated_copy_internals
from .deprecated import parse as _deprecated_parse
from .errors import PydanticUndefinedAnnotation, PydanticUserError
from .fields import ComputedFieldInfo, FieldInfo, ModelPrivateAttr
from .json_schema import DEFAULT_REF_TEMPLATE, GenerateJsonSchema, JsonSchemaMode, JsonSchemaValue, model_json_schema
Expand All @@ -41,6 +39,7 @@
from typing_extensions import Literal, Unpack

from ._internal._utils import AbstractSetIntStr, MappingIntStrAny
from .deprecated.parse import Protocol as DeprecatedParseProtocol
from .fields import Field as _Field

AnyClassMethod = classmethod[Any, Any, Any]
Expand Down Expand Up @@ -1022,16 +1021,18 @@ def parse_raw( # noqa: D102
*,
content_type: str | None = None,
encoding: str = 'utf8',
proto: _deprecated_parse.Protocol | None = None,
proto: DeprecatedParseProtocol | None = None,
allow_pickle: bool = False,
) -> Model: # pragma: no cover
warnings.warn(
'The `parse_raw` method is deprecated; if your data is JSON use `model_validate_json`, '
'otherwise load the data then use `model_validate` instead.',
DeprecationWarning,
)
from .deprecated import parse

try:
obj = _deprecated_parse.load_str_bytes(
obj = parse.load_str_bytes(
b,
proto=proto,
content_type=content_type,
Expand Down Expand Up @@ -1073,15 +1074,17 @@ def parse_file( # noqa: D102
*,
content_type: str | None = None,
encoding: str = 'utf8',
proto: _deprecated_parse.Protocol | None = None,
proto: DeprecatedParseProtocol | None = None,
allow_pickle: bool = False,
) -> Model:
warnings.warn(
'The `parse_file` method is deprecated; load the data from file, then if your data is JSON '
'use `model_validate_json` otherwise `model_validate` instead.',
DeprecationWarning,
)
obj = _deprecated_parse.load_file(
from .deprecated import parse

obj = parse.load_file(
path,
proto=proto,
content_type=content_type,
Expand Down Expand Up @@ -1157,9 +1160,10 @@ def copy(
'See the docstring of `BaseModel.copy` for details about how to handle `include` and `exclude`.',
DeprecationWarning,
)
from .deprecated import copy_internals

values = dict(
_deprecated_copy_internals._iter(
copy_internals._iter(
self, to_dict=False, by_alias=False, include=include, exclude=exclude, exclude_unset=False
),
**(update or {}),
Expand Down Expand Up @@ -1190,7 +1194,7 @@ def copy(
if exclude:
fields_set -= set(exclude)

return _deprecated_copy_internals._copy_and_set_values(self, values, fields_set, extra, private, deep=deep)
return copy_internals._copy_and_set_values(self, values, fields_set, extra, private, deep=deep)

@classmethod
@typing_extensions.deprecated(
Expand Down Expand Up @@ -1250,7 +1254,10 @@ def update_forward_refs(cls, **localns: Any) -> None: # noqa: D102
)
def _iter(self, *args: Any, **kwargs: Any) -> Any:
warnings.warn('The private method `_iter` will be removed and should no longer be used.', DeprecationWarning)
return _deprecated_copy_internals._iter(self, *args, **kwargs)

from .deprecated import copy_internals

return copy_internals._iter(self, *args, **kwargs)

@typing_extensions.deprecated(
'The private method `_copy_and_set_values` will be removed and should no longer be used.',
Expand All @@ -1261,7 +1268,9 @@ def _copy_and_set_values(self, *args: Any, **kwargs: Any) -> Any:
'The private method `_copy_and_set_values` will be removed and should no longer be used.',
DeprecationWarning,
)
return _deprecated_copy_internals._copy_and_set_values(self, *args, **kwargs)
from .deprecated import copy_internals

return copy_internals._copy_and_set_values(self, *args, **kwargs)

@classmethod
@typing_extensions.deprecated(
Expand All @@ -1272,7 +1281,10 @@ def _get_value(cls, *args: Any, **kwargs: Any) -> Any:
warnings.warn(
'The private method `_get_value` will be removed and should no longer be used.', DeprecationWarning
)
return _deprecated_copy_internals._get_value(cls, *args, **kwargs)

from .deprecated import copy_internals

return copy_internals._get_value(cls, *args, **kwargs)

@typing_extensions.deprecated(
'The private method `_calculate_keys` will be removed and should no longer be used.',
Expand All @@ -1282,7 +1294,10 @@ def _calculate_keys(self, *args: Any, **kwargs: Any) -> Any:
warnings.warn(
'The private method `_calculate_keys` will be removed and should no longer be used.', DeprecationWarning
)
return _deprecated_copy_internals._calculate_keys(self, *args, **kwargs)

from .deprecated import copy_internals

return copy_internals._calculate_keys(self, *args, **kwargs)


@typing.overload
Expand Down
25 changes: 25 additions & 0 deletions tests/test_exports.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import importlib
import importlib.util
import json
import platform
import subprocess
import sys
from pathlib import Path
from types import ModuleType
Expand Down Expand Up @@ -68,3 +70,26 @@ def test_public_internal():

if public_internal_attributes:
pytest.fail('The following should not be publicly accessible:\n ' + '\n '.join(public_internal_attributes))


# language=Python
IMPORTED_MODULES_CODE = """
import sys
import pydantic
modules = list(sys.modules.keys())
import json
print(json.dumps(modules))
"""


def test_imported_modules(tmp_path: Path):
py_file = tmp_path / 'test.py'
py_file.write_text(IMPORTED_MODULES_CODE)

output = subprocess.check_output([sys.executable, str(py_file)], cwd=tmp_path)
imported_modules = json.loads(output)
# debug(imported_modules)
assert 'pydantic' in imported_modules
assert 'pydantic.deprecated' not in imported_modules

0 comments on commit cc4b8c2

Please sign in to comment.