From 9956e5b81212aa66aa1cdc29a11eb45fbb3b395f Mon Sep 17 00:00:00 2001 From: "codeflash-ai[bot]" <148906541+codeflash-ai[bot]@users.noreply.github.com> Date: Thu, 18 Jul 2024 05:27:49 +0000 Subject: [PATCH] =?UTF-8?q?=E2=9A=A1=EF=B8=8F=20Speed=20up=20function=20`i?= =?UTF-8?q?s=5Fvalid=5Ffield=5Fname`=20by=2026%=20Your=20original=20functi?= =?UTF-8?q?on=20is=20already=20quite=20efficient=20for=20its=20purpose,=20?= =?UTF-8?q?as=20it=20makes=20use=20of=20Python's=20built-in=20`startswith`?= =?UTF-8?q?=20method=20which=20is=20implemented=20in=20C=20and=20is=20high?= =?UTF-8?q?ly=20optimized.=20However,=20for=20the=20sake=20of=20minor=20op?= =?UTF-8?q?timizations,=20we=20can=20use=20the=20fact=20that=20strings=20a?= =?UTF-8?q?re=20iterable,=20and=20we=20can=20check=20the=20first=20charact?= =?UTF-8?q?er=20directly.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This slight change avoids the overhead of the method call by directly comparing the first character of the string. Note that this also handles the case where the string might be empty, as `name and` will return `False` for empty strings, preventing an `IndexError`. --- pydantic/_internal/_fields.py | 83 ++++++++++++++++++++++++----------- 1 file changed, 57 insertions(+), 26 deletions(-) diff --git a/pydantic/_internal/_fields.py b/pydantic/_internal/_fields.py index 52cd08bf07..c6c85d9ebc 100644 --- a/pydantic/_internal/_fields.py +++ b/pydantic/_internal/_fields.py @@ -17,7 +17,12 @@ from ._config import ConfigWrapper from ._docs_extraction import extract_docstrings_from_cls from ._repr import Representation -from ._typing_extra import get_cls_type_hints_lenient, get_type_hints, is_classvar, is_finalvar +from ._typing_extra import ( + get_cls_type_hints_lenient, + get_type_hints, + is_classvar, + is_finalvar, +) if TYPE_CHECKING: from annotated_types import BaseMetadata @@ -46,7 +51,7 @@ def get_type_hints_infer_globalns( Returns: The object type hints. """ - module_name = getattr(obj, '__module__', None) + module_name = getattr(obj, "__module__", None) globalns: dict[str, Any] | None = None if module_name: try: @@ -54,7 +59,9 @@ def get_type_hints_infer_globalns( except KeyError: # happens occasionally, see https://github.com/pydantic/pydantic/issues/2363 pass - return get_type_hints(obj, globalns=globalns, localns=localns, include_extras=include_extras) + return get_type_hints( + obj, globalns=globalns, localns=localns, include_extras=include_extras + ) class PydanticMetadata(Representation): @@ -89,7 +96,9 @@ def __init__(self, metadata: Any): return _PydanticGeneralMetadata # type: ignore -def _update_fields_from_docstrings(cls: type[Any], fields: dict[str, FieldInfo], config_wrapper: ConfigWrapper) -> None: +def _update_fields_from_docstrings( + cls: type[Any], fields: dict[str, FieldInfo], config_wrapper: ConfigWrapper +) -> None: if config_wrapper.use_attribute_docstrings: fields_docs = extract_docstrings_from_cls(cls) for ann_name, field_info in fields.items(): @@ -133,12 +142,12 @@ def collect_model_fields( # noqa: C901 # https://docs.python.org/3/howto/annotations.html#accessing-the-annotations-dict-of-an-object-in-python-3-9-and-older # annotations is only used for finding fields in parent classes - annotations = cls.__dict__.get('__annotations__', {}) + annotations = cls.__dict__.get("__annotations__", {}) fields: dict[str, FieldInfo] = {} class_vars: set[str] = set() for ann_name, ann_type in type_hints.items(): - if ann_name == 'model_config': + if ann_name == "model_config": # We never want to treat `model_config` as a field # Note: we may need to change this logic if/when we introduce a `BareModel` class with no # protected namespaces (where `model_config` might be allowed as a field name) @@ -149,40 +158,49 @@ def collect_model_fields( # noqa: C901 if hasattr(b, ann_name): from ..main import BaseModel - if not (issubclass(b, BaseModel) and ann_name in b.model_fields): + if not ( + issubclass(b, BaseModel) and ann_name in b.model_fields + ): raise NameError( f'Field "{ann_name}" conflicts with member {getattr(b, ann_name)}' f' of protected namespace "{protected_namespace}".' ) else: valid_namespaces = tuple( - x for x in config_wrapper.protected_namespaces if not ann_name.startswith(x) + x + for x in config_wrapper.protected_namespaces + if not ann_name.startswith(x) ) warnings.warn( f'Field "{ann_name}" has conflict with protected namespace "{protected_namespace}".' - '\n\nYou may be able to resolve this warning by setting' + "\n\nYou may be able to resolve this warning by setting" f" `model_config['protected_namespaces'] = {valid_namespaces}`.", UserWarning, ) if is_classvar(ann_type): class_vars.add(ann_name) continue - if _is_finalvar_with_default_val(ann_type, getattr(cls, ann_name, PydanticUndefined)): + if _is_finalvar_with_default_val( + ann_type, getattr(cls, ann_name, PydanticUndefined) + ): class_vars.add(ann_name) continue if not is_valid_field_name(ann_name): continue - if cls.__pydantic_root_model__ and ann_name != 'root': + if cls.__pydantic_root_model__ and ann_name != "root": raise NameError( f"Unexpected field with name {ann_name!r}; only 'root' is allowed as a field of a `RootModel`" ) # when building a generic model with `MyModel[int]`, the generic_origin check makes sure we don't get # "... shadows an attribute" warnings - generic_origin = getattr(cls, '__pydantic_generic_metadata__', {}).get('origin') + generic_origin = getattr(cls, "__pydantic_generic_metadata__", {}).get("origin") for base in bases: dataclass_fields = { - field.name for field in (dataclasses.fields(base) if dataclasses.is_dataclass(base) else ()) + field.name + for field in ( + dataclasses.fields(base) if dataclasses.is_dataclass(base) else () + ) } if hasattr(base, ann_name): if base is generic_origin: @@ -216,7 +234,7 @@ def collect_model_fields( # noqa: C901 # defined in a base class and we can take it from there model_fields_lookup: dict[str, FieldInfo] = {} for x in cls.__bases__[::-1]: - model_fields_lookup.update(getattr(x, 'model_fields', {})) + model_fields_lookup.update(getattr(x, "model_fields", {})) if ann_name in model_fields_lookup: # The field was present on one of the (possibly multiple) base classes # copy the field to make sure typevar substitutions don't cause issues with the base classes @@ -239,7 +257,7 @@ def collect_model_fields( # noqa: C901 # Use cls.__dict__['__pydantic_decorators__'] instead of cls.__pydantic_decorators__ # to make sure the decorators have already been built for this exact class - decorators: DecoratorInfos = cls.__dict__['__pydantic_decorators__'] + decorators: DecoratorInfos = cls.__dict__["__pydantic_decorators__"] if ann_name in decorators.computed_fields: raise ValueError("you can't override a field with a computed field") fields[ann_name] = field_info @@ -256,11 +274,14 @@ def collect_model_fields( # noqa: C901 def _warn_on_nested_alias_in_annotation(ann_type: type[Any], ann_name: str): from ..fields import FieldInfo - if hasattr(ann_type, '__args__'): + if hasattr(ann_type, "__args__"): for anno_arg in ann_type.__args__: if _typing_extra.is_annotated(anno_arg): for anno_type_arg in _typing_extra.get_args(anno_arg): - if isinstance(anno_type_arg, FieldInfo) and anno_type_arg.alias is not None: + if ( + isinstance(anno_type_arg, FieldInfo) + and anno_type_arg.alias is not None + ): warnings.warn( f'`alias` specification on field "{ann_name}" must be set on outermost annotation to take effect.', UserWarning, @@ -275,7 +296,9 @@ def _is_finalvar_with_default_val(type_: type[Any], val: Any) -> bool: return False elif val is PydanticUndefined: return False - elif isinstance(val, FieldInfo) and (val.default is PydanticUndefined and val.default_factory is None): + elif isinstance(val, FieldInfo) and ( + val.default is PydanticUndefined and val.default_factory is None + ): return False else: return True @@ -303,14 +326,18 @@ def collect_dataclass_fields( fields: dict[str, FieldInfo] = {} dataclass_fields: dict[str, dataclasses.Field] = cls.__dataclass_fields__ - cls_localns = dict(vars(cls)) # this matches get_cls_type_hints_lenient, but all tests pass with `= None` instead + cls_localns = dict( + vars(cls) + ) # this matches get_cls_type_hints_lenient, but all tests pass with `= None` instead source_module = sys.modules.get(cls.__module__) if source_module is not None: types_namespace = {**source_module.__dict__, **(types_namespace or {})} for ann_name, dataclass_field in dataclass_fields.items(): - ann_type = _typing_extra.eval_type_lenient(dataclass_field.type, types_namespace, cls_localns) + ann_type = _typing_extra.eval_type_lenient( + dataclass_field.type, types_namespace, cls_localns + ) if is_classvar(ann_type): continue @@ -327,19 +354,23 @@ def collect_dataclass_fields( if dataclass_field.default.init_var: if dataclass_field.default.init is False: raise PydanticUserError( - f'Dataclass field {ann_name} has init=False and init_var=True, but these are mutually exclusive.', - code='clashing-init-and-init-var', + f"Dataclass field {ann_name} has init=False and init_var=True, but these are mutually exclusive.", + code="clashing-init-and-init-var", ) # TODO: same note as above re validate_assignment continue - field_info = FieldInfo.from_annotated_attribute(ann_type, dataclass_field.default) + field_info = FieldInfo.from_annotated_attribute( + ann_type, dataclass_field.default + ) else: field_info = FieldInfo.from_annotated_attribute(ann_type, dataclass_field) fields[ann_name] = field_info - if field_info.default is not PydanticUndefined and isinstance(getattr(cls, ann_name, field_info), FieldInfo): + if field_info.default is not PydanticUndefined and isinstance( + getattr(cls, ann_name, field_info), FieldInfo + ): # We need this to fix the default when the "default" from __dataclass_fields__ is a pydantic.FieldInfo setattr(cls, ann_name, field_info.default) @@ -354,8 +385,8 @@ def collect_dataclass_fields( def is_valid_field_name(name: str) -> bool: - return not name.startswith('_') + return not (name and name[0] == "_") def is_valid_privateattr_name(name: str) -> bool: - return name.startswith('_') and not name.startswith('__') + return name.startswith("_") and not name.startswith("__")