def update_forward_refs(cls, **localns: Any) -> None: """ Processes fields that are ForwardRef and need to be evaluated into actual models. Expands relationships, register relation in alias manager and substitutes sqlalchemy columns with new ones with proper column type (null before). Populates Meta table of the Model which is left empty before. Sets self_reference flag on models that links to themselves. Calls the pydantic method to evaluate pydantic fields. :param localns: local namespace :type localns: Any :return: None :rtype: None """ globalns = sys.modules[cls.__module__].__dict__.copy() globalns.setdefault(cls.__name__, cls) fields_to_check = cls.Meta.model_fields.copy() for field in fields_to_check.values(): if field.has_unresolved_forward_refs(): field = cast(Type[ForeignKeyField], field) field.evaluate_forward_ref(globalns=globalns, localns=localns) field.set_self_reference_flag() expand_reverse_relationship(model_field=field) register_relation_in_alias_manager(field=field) update_column_definition(model=cls, field=field) populate_meta_sqlalchemy_table_if_required(meta=cls.Meta) super().update_forward_refs(**localns) cls.Meta.requires_ref_update = False
def __new__( # type: ignore # noqa: CCR001 mcs: "ModelMetaclass", name: str, bases: Any, attrs: dict ) -> "ModelMetaclass": """ Metaclass used by ormar Models that performs configuration and build of ormar Models. Sets pydantic configuration. Extract model_fields and convert them to pydantic FieldInfo, updates class namespace. Extracts settings and fields from parent classes. Fetches methods decorated with @property_field decorator to expose them later in dict(). Construct parent pydantic Metaclass/ Model. If class has Meta class declared (so actual ormar Models) it also: * populate sqlalchemy columns, pkname and tables from model_fields * register reverse relationships on related models * registers all relations in alias manager that populates table_prefixes * exposes alias manager on each Model * creates QuerySet for each model and exposes it on a class :param name: name of current class :type name: str :param bases: base classes :type bases: Tuple :param attrs: class namespace :type attrs: Dict """ attrs["Config"] = get_pydantic_base_orm_config() attrs["__name__"] = name attrs, model_fields = extract_annotations_and_default_vals(attrs) for base in reversed(bases): mod = base.__module__ if mod.startswith("ormar.models.") or mod.startswith("pydantic."): continue attrs, model_fields = extract_from_parents_definition( base_class=base, curr_class=mcs, attrs=attrs, model_fields=model_fields ) new_model = super().__new__( # type: ignore mcs, name, bases, attrs ) add_cached_properties(new_model) if hasattr(new_model, "Meta"): populate_default_options_values(new_model, model_fields) check_required_meta_parameters(new_model) add_property_fields(new_model, attrs) register_signals(new_model=new_model) populate_choices_validators(new_model) if not new_model.Meta.abstract: new_model = populate_meta_tablename_columns_and_pk(name, new_model) populate_meta_sqlalchemy_table_if_required(new_model.Meta) expand_reverse_relationships(new_model) # TODO: iterate only related fields for field in new_model.Meta.model_fields.values(): register_relation_in_alias_manager(field=field) if new_model.Meta.pkname not in attrs["__annotations__"]: field_name = new_model.Meta.pkname attrs["__annotations__"][field_name] = Optional[int] # type: ignore attrs[field_name] = None new_model.__fields__[field_name] = get_pydantic_field( field_name=field_name, model=new_model ) new_model.Meta.alias_manager = alias_manager return new_model