Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
decl_base.py2153 linesDownload Raw Back to orm
1# orm/decl_base.py
2# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
3# <see AUTHORS file>
4#
5# This module is part of SQLAlchemy and is released under
6# the MIT License: https://www.opensource.org/licenses/mit-license.php
7
8"""Internal implementation for declarative."""
9
10from __future__ import annotations
11
12import collections
13import dataclasses
14import re
15from typing import Any
16from typing import Callable
17from typing import cast
18from typing import Dict
19from typing import Iterable
20from typing import List
21from typing import Mapping
22from typing import NamedTuple
23from typing import NoReturn
24from typing import Optional
25from typing import Sequence
26from typing import Tuple
27from typing import Type
28from typing import TYPE_CHECKING
29from typing import TypeVar
30from typing import Union
31import weakref
32
33from . import attributes
34from . import clsregistry
35from . import exc as orm_exc
36from . import instrumentation
37from . import mapperlib
38from ._typing import _O
39from ._typing import attr_is_internal_proxy
40from .attributes import InstrumentedAttribute
41from .attributes import QueryableAttribute
42from .base import _is_mapped_class
43from .base import InspectionAttr
44from .descriptor_props import CompositeProperty
45from .descriptor_props import SynonymProperty
46from .interfaces import _AttributeOptions
47from .interfaces import _DCAttributeOptions
48from .interfaces import _IntrospectsAnnotations
49from .interfaces import _MappedAttribute
50from .interfaces import _MapsColumns
51from .interfaces import MapperProperty
52from .mapper import Mapper
53from .properties import ColumnProperty
54from .properties import MappedColumn
55from .util import _extract_mapped_subtype
56from .util import _is_mapped_annotation
57from .util import class_mapper
58from .util import de_stringify_annotation
59from .. import event
60from .. import exc
61from .. import util
62from ..sql import expression
63from ..sql.base import _NoArg
64from ..sql.schema import Column
65from ..sql.schema import Table
66from ..util import topological
67from ..util.typing import _AnnotationScanType
68from ..util.typing import is_fwd_ref
69from ..util.typing import is_literal
70from ..util.typing import Protocol
71from ..util.typing import TypedDict
72from ..util.typing import typing_get_args
73
74if TYPE_CHECKING:
75    from ._typing import _ClassDict
76    from ._typing import _RegistryType
77    from .base import Mapped
78    from .decl_api import declared_attr
79    from .instrumentation import ClassManager
80    from ..sql.elements import NamedColumn
81    from ..sql.schema import MetaData
82    from ..sql.selectable import FromClause
83
84_T = TypeVar("_T", bound=Any)
85
86_MapperKwArgs = Mapping[str, Any]
87_TableArgsType = Union[Tuple[Any, ...], Dict[str, Any]]
88
89
90class MappedClassProtocol(Protocol[_O]):
91    """A protocol representing a SQLAlchemy mapped class.
92
93    The protocol is generic on the type of class, use
94    ``MappedClassProtocol[Any]`` to allow any mapped class.
95    """
96
97    __name__: str
98    __mapper__: Mapper[_O]
99    __table__: FromClause
100
101    def __call__(self, **kw: Any) -> _O: ...
102
103
104class _DeclMappedClassProtocol(MappedClassProtocol[_O], Protocol):
105    "Internal more detailed version of ``MappedClassProtocol``."
106    metadata: MetaData
107    __tablename__: str
108    __mapper_args__: _MapperKwArgs
109    __table_args__: Optional[_TableArgsType]
110
111    _sa_apply_dc_transforms: Optional[_DataclassArguments]
112
113    def __declare_first__(self) -> None: ...
114
115    def __declare_last__(self) -> None: ...
116
117
118class _DataclassArguments(TypedDict):
119    init: Union[_NoArg, bool]
120    repr: Union[_NoArg, bool]
121    eq: Union[_NoArg, bool]
122    order: Union[_NoArg, bool]
123    unsafe_hash: Union[_NoArg, bool]
124    match_args: Union[_NoArg, bool]
125    kw_only: Union[_NoArg, bool]
126    dataclass_callable: Union[_NoArg, Callable[..., Type[Any]]]
127
128
129def _declared_mapping_info(
130    cls: Type[Any],
131) -> Optional[Union[_DeferredMapperConfig, Mapper[Any]]]:
132    # deferred mapping
133    if _DeferredMapperConfig.has_cls(cls):
134        return _DeferredMapperConfig.config_for_cls(cls)
135    # regular mapping
136    elif _is_mapped_class(cls):
137        return class_mapper(cls, configure=False)
138    else:
139        return None
140
141
142def _is_supercls_for_inherits(cls: Type[Any]) -> bool:
143    """return True if this class will be used as a superclass to set in
144    'inherits'.
145
146    This includes deferred mapper configs that aren't mapped yet, however does
147    not include classes with _sa_decl_prepare_nocascade (e.g.
148    ``AbstractConcreteBase``); these concrete-only classes are not set up as
149    "inherits" until after mappers are configured using
150    mapper._set_concrete_base()
151
152    """
153    if _DeferredMapperConfig.has_cls(cls):
154        return not _get_immediate_cls_attr(
155            cls, "_sa_decl_prepare_nocascade", strict=True
156        )
157    # regular mapping
158    elif _is_mapped_class(cls):
159        return True
160    else:
161        return False
162
163
164def _resolve_for_abstract_or_classical(cls: Type[Any]) -> Optional[Type[Any]]:
165    if cls is object:
166        return None
167
168    sup: Optional[Type[Any]]
169
170    if cls.__dict__.get("__abstract__", False):
171        for base_ in cls.__bases__:
172            sup = _resolve_for_abstract_or_classical(base_)
173            if sup is not None:
174                return sup
175        else:
176            return None
177    else:
178        clsmanager = _dive_for_cls_manager(cls)
179
180        if clsmanager:
181            return clsmanager.class_
182        else:
183            return cls
184
185
186def _get_immediate_cls_attr(
187    cls: Type[Any], attrname: str, strict: bool = False
188) -> Optional[Any]:
189    """return an attribute of the class that is either present directly
190    on the class, e.g. not on a superclass, or is from a superclass but
191    this superclass is a non-mapped mixin, that is, not a descendant of
192    the declarative base and is also not classically mapped.
193
194    This is used to detect attributes that indicate something about
195    a mapped class independently from any mapped classes that it may
196    inherit from.
197
198    """
199
200    # the rules are different for this name than others,
201    # make sure we've moved it out.  transitional
202    assert attrname != "__abstract__"
203
204    if not issubclass(cls, object):
205        return None
206
207    if attrname in cls.__dict__:
208        return getattr(cls, attrname)
209
210    for base in cls.__mro__[1:]:
211        _is_classical_inherits = _dive_for_cls_manager(base) is not None
212
213        if attrname in base.__dict__ and (
214            base is cls
215            or (
216                (base in cls.__bases__ if strict else True)
217                and not _is_classical_inherits
218            )
219        ):
220            return getattr(base, attrname)
221    else:
222        return None
223
224
225def _dive_for_cls_manager(cls: Type[_O]) -> Optional[ClassManager[_O]]:
226    # because the class manager registration is pluggable,
227    # we need to do the search for every class in the hierarchy,
228    # rather than just a simple "cls._sa_class_manager"
229
230    for base in cls.__mro__:
231        manager: Optional[ClassManager[_O]] = attributes.opt_manager_of_class(
232            base
233        )
234        if manager:
235            return manager
236    return None
237
238
239def _as_declarative(
240    registry: _RegistryType, cls: Type[Any], dict_: _ClassDict
241) -> Optional[_MapperConfig]:
242    # declarative scans the class for attributes.  no table or mapper
243    # args passed separately.
244    return _MapperConfig.setup_mapping(registry, cls, dict_, None, {})
245
246
247def _mapper(
248    registry: _RegistryType,
249    cls: Type[_O],
250    table: Optional[FromClause],
251    mapper_kw: _MapperKwArgs,
252) -> Mapper[_O]:
253    _ImperativeMapperConfig(registry, cls, table, mapper_kw)
254    return cast("MappedClassProtocol[_O]", cls).__mapper__
255
256
257@util.preload_module("sqlalchemy.orm.decl_api")
258def _is_declarative_props(obj: Any) -> bool:
259    _declared_attr_common = util.preloaded.orm_decl_api._declared_attr_common
260
261    return isinstance(obj, (_declared_attr_common, util.classproperty))
262
263
264def _check_declared_props_nocascade(
265    obj: Any, name: str, cls: Type[_O]
266) -> bool:
267    if _is_declarative_props(obj):
268        if getattr(obj, "_cascading", False):
269            util.warn(
270                "@declared_attr.cascading is not supported on the %s "
271                "attribute on class %s.  This attribute invokes for "
272                "subclasses in any case." % (name, cls)
273            )
274        return True
275    else:
276        return False
277
278
279class _MapperConfig:
280    __slots__ = (
281        "cls",
282        "classname",
283        "properties",
284        "declared_attr_reg",
285        "__weakref__",
286    )
287
288    cls: Type[Any]
289    classname: str
290    properties: util.OrderedDict[
291        str,
292        Union[
293            Sequence[NamedColumn[Any]], NamedColumn[Any], MapperProperty[Any]
294        ],
295    ]
296    declared_attr_reg: Dict[declared_attr[Any], Any]
297
298    @classmethod
299    def setup_mapping(
300        cls,
301        registry: _RegistryType,
302        cls_: Type[_O],
303        dict_: _ClassDict,
304        table: Optional[FromClause],
305        mapper_kw: _MapperKwArgs,
306    ) -> Optional[_MapperConfig]:
307        manager = attributes.opt_manager_of_class(cls)
308        if manager and manager.class_ is cls_:
309            raise exc.InvalidRequestError(
310                f"Class {cls!r} already has been instrumented declaratively"
311            )
312
313        if cls_.__dict__.get("__abstract__", False):
314            return None
315
316        defer_map = _get_immediate_cls_attr(
317            cls_, "_sa_decl_prepare_nocascade", strict=True
318        ) or hasattr(cls_, "_sa_decl_prepare")
319
320        if defer_map:
321            return _DeferredMapperConfig(
322                registry, cls_, dict_, table, mapper_kw
323            )
324        else:
325            return _ClassScanMapperConfig(
326                registry, cls_, dict_, table, mapper_kw
327            )
328
329    def __init__(
330        self,
331        registry: _RegistryType,
332        cls_: Type[Any],
333        mapper_kw: _MapperKwArgs,
334    ):
335        self.cls = util.assert_arg_type(cls_, type, "cls_")
336        self.classname = cls_.__name__
337        self.properties = util.OrderedDict()
338        self.declared_attr_reg = {}
339
340        if not mapper_kw.get("non_primary", False):
341            instrumentation.register_class(
342                self.cls,
343                finalize=False,
344                registry=registry,
345                declarative_scan=self,
346                init_method=registry.constructor,
347            )
348        else:
349            manager = attributes.opt_manager_of_class(self.cls)
350            if not manager or not manager.is_mapped:
351                raise exc.InvalidRequestError(
352                    "Class %s has no primary mapper configured.  Configure "
353                    "a primary mapper first before setting up a non primary "
354                    "Mapper." % self.cls
355                )
356
357    def set_cls_attribute(self, attrname: str, value: _T) -> _T:
358        manager = instrumentation.manager_of_class(self.cls)
359        manager.install_member(attrname, value)
360        return value
361
362    def map(self, mapper_kw: _MapperKwArgs = ...) -> Mapper[Any]:
363        raise NotImplementedError()
364
365    def _early_mapping(self, mapper_kw: _MapperKwArgs) -> None:
366        self.map(mapper_kw)
367
368
369class _ImperativeMapperConfig(_MapperConfig):
370    __slots__ = ("local_table", "inherits")
371
372    def __init__(
373        self,
374        registry: _RegistryType,
375        cls_: Type[_O],
376        table: Optional[FromClause],
377        mapper_kw: _MapperKwArgs,
378    ):
379        super().__init__(registry, cls_, mapper_kw)
380
381        self.local_table = self.set_cls_attribute("__table__", table)
382
383        with mapperlib._CONFIGURE_MUTEX:
384            if not mapper_kw.get("non_primary", False):
385                clsregistry.add_class(
386                    self.classname, self.cls, registry._class_registry
387                )
388
389            self._setup_inheritance(mapper_kw)
390
391            self._early_mapping(mapper_kw)
392
393    def map(self, mapper_kw: _MapperKwArgs = util.EMPTY_DICT) -> Mapper[Any]:
394        mapper_cls = Mapper
395
396        return self.set_cls_attribute(
397            "__mapper__",
398            mapper_cls(self.cls, self.local_table, **mapper_kw),
399        )
400
401    def _setup_inheritance(self, mapper_kw: _MapperKwArgs) -> None:
402        cls = self.cls
403
404        inherits = mapper_kw.get("inherits", None)
405
406        if inherits is None:
407            # since we search for classical mappings now, search for
408            # multiple mapped bases as well and raise an error.
409            inherits_search = []
410            for base_ in cls.__bases__:
411                c = _resolve_for_abstract_or_classical(base_)
412                if c is None:
413                    continue
414
415                if _is_supercls_for_inherits(c) and c not in inherits_search:
416                    inherits_search.append(c)
417
418            if inherits_search:
419                if len(inherits_search) > 1:
420                    raise exc.InvalidRequestError(
421                        "Class %s has multiple mapped bases: %r"
422                        % (cls, inherits_search)
423                    )
424                inherits = inherits_search[0]
425        elif isinstance(inherits, Mapper):
426            inherits = inherits.class_
427
428        self.inherits = inherits
429
430
431class _CollectedAnnotation(NamedTuple):
432    raw_annotation: _AnnotationScanType
433    mapped_container: Optional[Type[Mapped[Any]]]
434    extracted_mapped_annotation: Union[Type[Any], str]
435    is_dataclass: bool
436    attr_value: Any
437    originating_module: str
438    originating_class: Type[Any]
439
440
441class _ClassScanMapperConfig(_MapperConfig):
442    __slots__ = (
443        "registry",
444        "clsdict_view",
445        "collected_attributes",
446        "collected_annotations",
447        "local_table",
448        "persist_selectable",
449        "declared_columns",
450        "column_ordering",
451        "column_copies",
452        "table_args",
453        "tablename",
454        "mapper_args",
455        "mapper_args_fn",
456        "inherits",
457        "single",
458        "allow_dataclass_fields",
459        "dataclass_setup_arguments",
460        "is_dataclass_prior_to_mapping",
461        "allow_unmapped_annotations",
462    )
463
464    is_deferred = False
465    registry: _RegistryType
466    clsdict_view: _ClassDict
467    collected_annotations: Dict[str, _CollectedAnnotation]
468    collected_attributes: Dict[str, Any]
469    local_table: Optional[FromClause]
470    persist_selectable: Optional[FromClause]
471    declared_columns: util.OrderedSet[Column[Any]]
472    column_ordering: Dict[Column[Any], int]
473    column_copies: Dict[
474        Union[MappedColumn[Any], Column[Any]],
475        Union[MappedColumn[Any], Column[Any]],
476    ]
477    tablename: Optional[str]
478    mapper_args: Mapping[str, Any]
479    table_args: Optional[_TableArgsType]
480    mapper_args_fn: Optional[Callable[[], Dict[str, Any]]]
481    inherits: Optional[Type[Any]]
482    single: bool
483
484    is_dataclass_prior_to_mapping: bool
485    allow_unmapped_annotations: bool
486
487    dataclass_setup_arguments: Optional[_DataclassArguments]
488    """if the class has SQLAlchemy native dataclass parameters, where
489    we will turn the class into a dataclass within the declarative mapping
490    process.
491
492    """
493
494    allow_dataclass_fields: bool
495    """if true, look for dataclass-processed Field objects on the target
496    class as well as superclasses and extract ORM mapping directives from
497    the "metadata" attribute of each Field.
498
499    if False, dataclass fields can still be used, however they won't be
500    mapped.
501
502    """
503
504    def __init__(
505        self,
506        registry: _RegistryType,
507        cls_: Type[_O],
508        dict_: _ClassDict,
509        table: Optional[FromClause],
510        mapper_kw: _MapperKwArgs,
511    ):
512        # grab class dict before the instrumentation manager has been added.
513        # reduces cycles
514        self.clsdict_view = (
515            util.immutabledict(dict_) if dict_ else util.EMPTY_DICT
516        )
517        super().__init__(registry, cls_, mapper_kw)
518        self.registry = registry
519        self.persist_selectable = None
520
521        self.collected_attributes = {}
522        self.collected_annotations = {}
523        self.declared_columns = util.OrderedSet()
524        self.column_ordering = {}
525        self.column_copies = {}
526        self.single = False
527        self.dataclass_setup_arguments = dca = getattr(
528            self.cls, "_sa_apply_dc_transforms", None
529        )
530
531        self.allow_unmapped_annotations = getattr(
532            self.cls, "__allow_unmapped__", False
533        ) or bool(self.dataclass_setup_arguments)
534
535        self.is_dataclass_prior_to_mapping = cld = dataclasses.is_dataclass(
536            cls_
537        )
538
539        sdk = _get_immediate_cls_attr(cls_, "__sa_dataclass_metadata_key__")
540
541        # we don't want to consume Field objects from a not-already-dataclass.
542        # the Field objects won't have their "name" or "type" populated,
543        # and while it seems like we could just set these on Field as we
544        # read them, Field is documented as "user read only" and we need to
545        # stay far away from any off-label use of dataclasses APIs.
546        if (not cld or dca) and sdk:
547            raise exc.InvalidRequestError(
548                "SQLAlchemy mapped dataclasses can't consume mapping "
549                "information from dataclass.Field() objects if the immediate "
550                "class is not already a dataclass."
551            )
552
553        # if already a dataclass, and __sa_dataclass_metadata_key__ present,
554        # then also look inside of dataclass.Field() objects yielded by
555        # dataclasses.get_fields(cls) when scanning for attributes
556        self.allow_dataclass_fields = bool(sdk and cld)
557
558        self._setup_declared_events()
559
560        self._scan_attributes()
561
562        self._setup_dataclasses_transforms()
563
564        with mapperlib._CONFIGURE_MUTEX:
565            clsregistry.add_class(
566                self.classname, self.cls, registry._class_registry
567            )
568
569            self._setup_inheriting_mapper(mapper_kw)
570
571            self._extract_mappable_attributes()
572
573            self._extract_declared_columns()
574
575            self._setup_table(table)
576
577            self._setup_inheriting_columns(mapper_kw)
578
579            self._early_mapping(mapper_kw)
580
581    def _setup_declared_events(self) -> None:
582        if _get_immediate_cls_attr(self.cls, "__declare_last__"):
583
584            @event.listens_for(Mapper, "after_configured")
585            def after_configured() -> None:
586                cast(
587                    "_DeclMappedClassProtocol[Any]", self.cls
588                ).__declare_last__()
589
590        if _get_immediate_cls_attr(self.cls, "__declare_first__"):
591
592            @event.listens_for(Mapper, "before_configured")
593            def before_configured() -> None:
594                cast(
595                    "_DeclMappedClassProtocol[Any]", self.cls
596                ).__declare_first__()
597
598    def _cls_attr_override_checker(
599        self, cls: Type[_O]
600    ) -> Callable[[str, Any], bool]:
601        """Produce a function that checks if a class has overridden an
602        attribute, taking SQLAlchemy-enabled dataclass fields into account.
603
604        """
605
606        if self.allow_dataclass_fields:
607            sa_dataclass_metadata_key = _get_immediate_cls_attr(
608                cls, "__sa_dataclass_metadata_key__"
609            )
610        else:
611            sa_dataclass_metadata_key = None
612
613        if not sa_dataclass_metadata_key:
614
615            def attribute_is_overridden(key: str, obj: Any) -> bool:
616                return getattr(cls, key, obj) is not obj
617
618        else:
619            all_datacls_fields = {
620                f.name: f.metadata[sa_dataclass_metadata_key]
621                for f in util.dataclass_fields(cls)
622                if sa_dataclass_metadata_key in f.metadata
623            }
624            local_datacls_fields = {
625                f.name: f.metadata[sa_dataclass_metadata_key]
626                for f in util.local_dataclass_fields(cls)
627                if sa_dataclass_metadata_key in f.metadata
628            }
629
630            absent = object()
631
632            def attribute_is_overridden(key: str, obj: Any) -> bool:
633                if _is_declarative_props(obj):
634                    obj = obj.fget
635
636                # this function likely has some failure modes still if
637                # someone is doing a deep mixing of the same attribute
638                # name as plain Python attribute vs. dataclass field.
639
640                ret = local_datacls_fields.get(key, absent)
641                if _is_declarative_props(ret):
642                    ret = ret.fget
643
644                if ret is obj:
645                    return False
646                elif ret is not absent:
647                    return True
648
649                all_field = all_datacls_fields.get(key, absent)
650
651                ret = getattr(cls, key, obj)
652
653                if ret is obj:
654                    return False
655
656                # for dataclasses, this could be the
657                # 'default' of the field.  so filter more specifically
658                # for an already-mapped InstrumentedAttribute
659                if ret is not absent and isinstance(
660                    ret, InstrumentedAttribute
661                ):
662                    return True
663
664                if all_field is obj:
665                    return False
666                elif all_field is not absent:
667                    return True
668
669                # can't find another attribute
670                return False
671
672        return attribute_is_overridden
673
674    _include_dunders = {
675        "__table__",
676        "__mapper_args__",
677        "__tablename__",
678        "__table_args__",
679    }
680
681    _match_exclude_dunders = re.compile(r"^(?:_sa_|__)")
682
683    def _cls_attr_resolver(
684        self, cls: Type[Any]
685    ) -> Callable[[], Iterable[Tuple[str, Any, Any, bool]]]:
686        """produce a function to iterate the "attributes" of a class
687        which we want to consider for mapping, adjusting for SQLAlchemy fields
688        embedded in dataclass fields.
689
690        """
691        cls_annotations = util.get_annotations(cls)
692
693        cls_vars = vars(cls)
694
695        _include_dunders = self._include_dunders
696        _match_exclude_dunders = self._match_exclude_dunders
697
698        names = [
699            n
700            for n in util.merge_lists_w_ordering(
701                list(cls_vars), list(cls_annotations)
702            )
703            if not _match_exclude_dunders.match(n) or n in _include_dunders
704        ]
705
706        if self.allow_dataclass_fields:
707            sa_dataclass_metadata_key: Optional[str] = _get_immediate_cls_attr(
708                cls, "__sa_dataclass_metadata_key__"
709            )
710        else:
711            sa_dataclass_metadata_key = None
712
713        if not sa_dataclass_metadata_key:
714
715            def local_attributes_for_class() -> (
716                Iterable[Tuple[str, Any, Any, bool]]
717            ):
718                return (
719                    (
720                        name,
721                        cls_vars.get(name),
722                        cls_annotations.get(name),
723                        False,
724                    )
725                    for name in names
726                )
727
728        else:
729            dataclass_fields = {
730                field.name: field for field in util.local_dataclass_fields(cls)
731            }
732
733            fixed_sa_dataclass_metadata_key = sa_dataclass_metadata_key
734
735            def local_attributes_for_class() -> (
736                Iterable[Tuple[str, Any, Any, bool]]
737            ):
738                for name in names:
739                    field = dataclass_fields.get(name, None)
740                    if field and sa_dataclass_metadata_key in field.metadata:
741                        yield field.name, _as_dc_declaredattr(
742                            field.metadata, fixed_sa_dataclass_metadata_key
743                        ), cls_annotations.get(field.name), True
744                    else:
745                        yield name, cls_vars.get(name), cls_annotations.get(
746                            name
747                        ), False
748
749        return local_attributes_for_class
750
751    def _scan_attributes(self) -> None:
752        cls = self.cls
753
754        cls_as_Decl = cast("_DeclMappedClassProtocol[Any]", cls)
755
756        clsdict_view = self.clsdict_view
757        collected_attributes = self.collected_attributes
758        column_copies = self.column_copies
759        _include_dunders = self._include_dunders
760        mapper_args_fn = None
761        table_args = inherited_table_args = None
762
763        tablename = None
764        fixed_table = "__table__" in clsdict_view
765
766        attribute_is_overridden = self._cls_attr_override_checker(self.cls)
767
768        bases = []
769
770        for base in cls.__mro__:
771            # collect bases and make sure standalone columns are copied
772            # to be the column they will ultimately be on the class,
773            # so that declared_attr functions use the right columns.
774            # need to do this all the way up the hierarchy first
775            # (see #8190)
776
777            class_mapped = base is not cls and _is_supercls_for_inherits(base)
778
779            local_attributes_for_class = self._cls_attr_resolver(base)
780
781            if not class_mapped and base is not cls:
782                locally_collected_columns = self._produce_column_copies(
783                    local_attributes_for_class,
784                    attribute_is_overridden,
785                    fixed_table,
786                    base,
787                )
788            else:
789                locally_collected_columns = {}
790
791            bases.append(
792                (
793                    base,
794                    class_mapped,
795                    local_attributes_for_class,
796                    locally_collected_columns,
797                )
798            )
799
800        for (
801            base,
802            class_mapped,
803            local_attributes_for_class,
804            locally_collected_columns,
805        ) in bases:
806            # this transfer can also take place as we scan each name
807            # for finer-grained control of how collected_attributes is
808            # populated, as this is what impacts column ordering.
809            # however it's simpler to get it out of the way here.
810            collected_attributes.update(locally_collected_columns)
811
812            for (
813                name,
814                obj,
815                annotation,
816                is_dataclass_field,
817            ) in local_attributes_for_class():
818                if name in _include_dunders:
819                    if name == "__mapper_args__":
820                        check_decl = _check_declared_props_nocascade(
821                            obj, name, cls
822                        )
823                        if not mapper_args_fn and (
824                            not class_mapped or check_decl
825                        ):
826                            # don't even invoke __mapper_args__ until
827                            # after we've determined everything about the
828                            # mapped table.
829                            # make a copy of it so a class-level dictionary
830                            # is not overwritten when we update column-based
831                            # arguments.
832                            def _mapper_args_fn() -> Dict[str, Any]:
833                                return dict(cls_as_Decl.__mapper_args__)
834
835                            mapper_args_fn = _mapper_args_fn
836
837                    elif name == "__tablename__":
838                        check_decl = _check_declared_props_nocascade(
839                            obj, name, cls
840                        )
841                        if not tablename and (not class_mapped or check_decl):
842                            tablename = cls_as_Decl.__tablename__
843                    elif name == "__table_args__":
844                        check_decl = _check_declared_props_nocascade(
845                            obj, name, cls
846                        )
847                        if not table_args and (not class_mapped or check_decl):
848                            table_args = cls_as_Decl.__table_args__
849                            if not isinstance(
850                                table_args, (tuple, dict, type(None))
851                            ):
852                                raise exc.ArgumentError(
853                                    "__table_args__ value must be a tuple, "
854                                    "dict, or None"
855                                )
856                            if base is not cls:
857                                inherited_table_args = True
858                    else:
859                        # skip all other dunder names, which at the moment
860                        # should only be __table__
861                        continue
862                elif class_mapped:
863                    if _is_declarative_props(obj) and not obj._quiet:
864                        util.warn(
865                            "Regular (i.e. not __special__) "
866                            "attribute '%s.%s' uses @declared_attr, "
867                            "but owning class %s is mapped - "
868                            "not applying to subclass %s."
869                            % (base.__name__, name, base, cls)
870                        )
871
872                    continue
873                elif base is not cls:
874                    # we're a mixin, abstract base, or something that is
875                    # acting like that for now.
876
877                    if isinstance(obj, (Column, MappedColumn)):
878                        # already copied columns to the mapped class.
879                        continue
880                    elif isinstance(obj, MapperProperty):
881                        raise exc.InvalidRequestError(
882                            "Mapper properties (i.e. deferred,"
883                            "column_property(), relationship(), etc.) must "
884                            "be declared as @declared_attr callables "
885                            "on declarative mixin classes.  For dataclass "
886                            "field() objects, use a lambda:"
887                        )
888                    elif _is_declarative_props(obj):
889                        # tried to get overloads to tell this to
890                        # pylance, no luck
891                        assert obj is not None
892
893                        if obj._cascading:
894                            if name in clsdict_view:
895                                # unfortunately, while we can use the user-
896                                # defined attribute here to allow a clean
897                                # override, if there's another
898                                # subclass below then it still tries to use
899                                # this.  not sure if there is enough
900                                # information here to add this as a feature
901                                # later on.
902                                util.warn(
903                                    "Attribute '%s' on class %s cannot be "
904                                    "processed due to "
905                                    "@declared_attr.cascading; "
906                                    "skipping" % (name, cls)
907                                )
908                            collected_attributes[name] = column_copies[obj] = (
909                                ret
910                            ) = obj.__get__(obj, cls)
911                            setattr(cls, name, ret)
912                        else:
913                            if is_dataclass_field:
914                                # access attribute using normal class access
915                                # first, to see if it's been mapped on a
916                                # superclass.   note if the dataclasses.field()
917                                # has "default", this value can be anything.
918                                ret = getattr(cls, name, None)
919
920                                # so, if it's anything that's not ORM
921                                # mapped, assume we should invoke the
922                                # declared_attr
923                                if not isinstance(ret, InspectionAttr):
924                                    ret = obj.fget()
925                            else:
926                                # access attribute using normal class access.
927                                # if the declared attr already took place
928                                # on a superclass that is mapped, then
929                                # this is no longer a declared_attr, it will
930                                # be the InstrumentedAttribute
931                                ret = getattr(cls, name)
932
933                            # correct for proxies created from hybrid_property
934                            # or similar.  note there is no known case that
935                            # produces nested proxies, so we are only
936                            # looking one level deep right now.
937
938                            if (
939                                isinstance(ret, InspectionAttr)
940                                and attr_is_internal_proxy(ret)
941                                and not isinstance(
942                                    ret.original_property, MapperProperty
943                                )
944                            ):
945                                ret = ret.descriptor
946
947                            collected_attributes[name] = column_copies[obj] = (
948                                ret
949                            )
950
951                        if (
952                            isinstance(ret, (Column, MapperProperty))
953                            and ret.doc is None
954                        ):
955                            ret.doc = obj.__doc__
956
957                        self._collect_annotation(
958                            name,
959                            obj._collect_return_annotation(),
960                            base,
961                            True,
962                            obj,
963                        )
964                    elif _is_mapped_annotation(annotation, cls, base):
965                        # Mapped annotation without any object.
966                        # product_column_copies should have handled this.
967                        # if future support for other MapperProperty,
968                        # then test if this name is already handled and
969                        # otherwise proceed to generate.
970                        if not fixed_table:
971                            assert (
972                                name in collected_attributes
973                                or attribute_is_overridden(name, None)
974                            )
975                        continue
976                    else:
977                        # here, the attribute is some other kind of
978                        # property that we assume is not part of the
979                        # declarative mapping.  however, check for some
980                        # more common mistakes
981                        self._warn_for_decl_attributes(base, name, obj)
982                elif is_dataclass_field and (
983                    name not in clsdict_view or clsdict_view[name] is not obj
984                ):
985                    # here, we are definitely looking at the target class
986                    # and not a superclass.   this is currently a
987                    # dataclass-only path.  if the name is only
988                    # a dataclass field and isn't in local cls.__dict__,
989                    # put the object there.
990                    # assert that the dataclass-enabled resolver agrees
991                    # with what we are seeing
992
993                    assert not attribute_is_overridden(name, obj)
994
995                    if _is_declarative_props(obj):
996                        obj = obj.fget()
997
998                    collected_attributes[name] = obj
999                    self._collect_annotation(
1000                        name, annotation, base, False, obj
1001                    )
1002                else:
1003                    collected_annotation = self._collect_annotation(
1004                        name, annotation, base, None, obj
1005                    )
1006                    is_mapped = (
1007                        collected_annotation is not None
1008                        and collected_annotation.mapped_container is not None
1009                    )
1010                    generated_obj = (
1011                        collected_annotation.attr_value
1012                        if collected_annotation is not None
1013                        else obj
1014                    )
1015                    if obj is None and not fixed_table and is_mapped:
1016                        collected_attributes[name] = (
1017                            generated_obj
1018                            if generated_obj is not None
1019                            else MappedColumn()
1020                        )
1021                    elif name in clsdict_view:
1022                        collected_attributes[name] = obj
1023                    # else if the name is not in the cls.__dict__,
1024                    # don't collect it as an attribute.
1025                    # we will see the annotation only, which is meaningful
1026                    # both for mapping and dataclasses setup
1027
1028        if inherited_table_args and not tablename:
1029            table_args = None
1030
1031        self.table_args = table_args
1032        self.tablename = tablename
1033        self.mapper_args_fn = mapper_args_fn
1034
1035    def _setup_dataclasses_transforms(self) -> None:
1036        dataclass_setup_arguments = self.dataclass_setup_arguments
1037        if not dataclass_setup_arguments:
1038            return
1039
1040        # can't use is_dataclass since it uses hasattr
1041        if "__dataclass_fields__" in self.cls.__dict__:
1042            raise exc.InvalidRequestError(
1043                f"Class {self.cls} is already a dataclass; ensure that "
1044                "base classes / decorator styles of establishing dataclasses "
1045                "are not being mixed. "
1046                "This can happen if a class that inherits from "
1047                "'MappedAsDataclass', even indirectly, is been mapped with "
1048                "'@registry.mapped_as_dataclass'"
1049            )
1050
1051        warn_for_non_dc_attrs = collections.defaultdict(list)
1052
1053        def _allow_dataclass_field(
1054            key: str, originating_class: Type[Any]
1055        ) -> bool:
1056            if (
1057                originating_class is not self.cls
1058                and "__dataclass_fields__" not in originating_class.__dict__
1059            ):
1060                warn_for_non_dc_attrs[originating_class].append(key)
1061
1062            return True
1063
1064        manager = instrumentation.manager_of_class(self.cls)
1065        assert manager is not None
1066
1067        field_list = [
1068            _AttributeOptions._get_arguments_for_make_dataclass(
1069                key,
1070                anno,
1071                mapped_container,
1072                self.collected_attributes.get(key, _NoArg.NO_ARG),
1073            )
1074            for key, anno, mapped_container in (
1075                (
1076                    key,
1077                    mapped_anno if mapped_anno else raw_anno,
1078                    mapped_container,
1079                )
1080                for key, (
1081                    raw_anno,
1082                    mapped_container,
1083                    mapped_anno,
1084                    is_dc,
1085                    attr_value,
1086                    originating_module,
1087                    originating_class,
1088                ) in self.collected_annotations.items()
1089                if _allow_dataclass_field(key, originating_class)
1090                and (
1091                    key not in self.collected_attributes
1092                    # issue #9226; check for attributes that we've collected
1093                    # which are already instrumented, which we would assume
1094                    # mean we are in an ORM inheritance mapping and this
1095                    # attribute is already mapped on the superclass.   Under
1096                    # no circumstance should any QueryableAttribute be sent to
1097                    # the dataclass() function; anything that's mapped should
1098                    # be Field and that's it
1099                    or not isinstance(
1100                        self.collected_attributes[key], QueryableAttribute
1101                    )
1102                )
1103            )
1104        ]
1105
1106        if warn_for_non_dc_attrs:
1107            for (
1108                originating_class,
1109                non_dc_attrs,
1110            ) in warn_for_non_dc_attrs.items():
1111                util.warn_deprecated(
1112                    f"When transforming {self.cls} to a dataclass, "
1113                    f"attribute(s) "
1114                    f"{', '.join(repr(key) for key in non_dc_attrs)} "
1115                    f"originates from superclass "
1116                    f"{originating_class}, which is not a dataclass.  This "
1117                    f"usage is deprecated and will raise an error in "
1118                    f"SQLAlchemy 2.1.  When declaring SQLAlchemy Declarative "
1119                    f"Dataclasses, ensure that all mixin classes and other "
1120                    f"superclasses which include attributes are also a "
1121                    f"subclass of MappedAsDataclass.",
1122                    "2.0",
1123                    code="dcmx",
1124                )
1125
1126        annotations = {}
1127        defaults = {}
1128        for item in field_list:
1129            if len(item) == 2:
1130                name, tp = item
1131            elif len(item) == 3:
1132                name, tp, spec = item
1133                defaults[name] = spec
1134            else:
1135                assert False
1136            annotations[name] = tp
1137
1138        for k, v in defaults.items():
1139            setattr(self.cls, k, v)
1140
1141        self._apply_dataclasses_to_any_class(
1142            dataclass_setup_arguments, self.cls, annotations
1143        )
1144
1145    @classmethod
1146    def _update_annotations_for_non_mapped_class(
1147        cls, klass: Type[_O]
1148    ) -> Mapping[str, _AnnotationScanType]:
1149        cls_annotations = util.get_annotations(klass)
1150
1151        new_anno = {}
1152        for name, annotation in cls_annotations.items():
1153            if _is_mapped_annotation(annotation, klass, klass):
1154                extracted = _extract_mapped_subtype(
1155                    annotation,
1156                    klass,
1157                    klass.__module__,
1158                    name,
1159                    type(None),
1160                    required=False,
1161                    is_dataclass_field=False,
1162                    expect_mapped=False,
1163                )
1164                if extracted:
1165                    inner, _ = extracted
1166                    new_anno[name] = inner
1167            else:
1168                new_anno[name] = annotation
1169        return new_anno
1170
1171    @classmethod
1172    def _apply_dataclasses_to_any_class(
1173        cls,
1174        dataclass_setup_arguments: _DataclassArguments,
1175        klass: Type[_O],
1176        use_annotations: Mapping[str, _AnnotationScanType],
1177    ) -> None:
1178        cls._assert_dc_arguments(dataclass_setup_arguments)
1179
1180        dataclass_callable = dataclass_setup_arguments["dataclass_callable"]
1181        if dataclass_callable is _NoArg.NO_ARG:
1182            dataclass_callable = dataclasses.dataclass
1183
1184        restored: Optional[Any]
1185
1186        if use_annotations:
1187            # apply constructed annotations that should look "normal" to a
1188            # dataclasses callable, based on the fields present.  This
1189            # means remove the Mapped[] container and ensure all Field
1190            # entries have an annotation
1191            restored = getattr(klass, "__annotations__", None)
1192            klass.__annotations__ = cast("Dict[str, Any]", use_annotations)
1193        else:
1194            restored = None
1195
1196        try:
1197            dataclass_callable(
1198                klass,
1199                **{
1200                    k: v

Showing the first 1,200 of 2153 lines. Download the file for the rest.

codekingpro/portable-devtools · Team Ai