Skip to content

helpers

check_required_config_parameters(new_model)

Verifies if ormar.Model has database and metadata set.

Recreates Connection pool for sqlite3

:param new_model: newly declared ormar Model :type new_model: Model class

Source code in ormar/models/helpers/models.py
Python
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
def check_required_config_parameters(new_model: type["Model"]) -> None:
    """
    Verifies if ormar.Model has database and metadata set.

    Recreates Connection pool for sqlite3

    :param new_model: newly declared ormar Model
    :type new_model: Model class
    """
    if new_model.ormar_config.proxy and new_model.ormar_config.abstract:
        raise ormar.ModelDefinitionError(
            f"{new_model.__name__} cannot be both proxy and abstract."
        )

    skip_db_metadata_checks = (
        new_model.ormar_config.abstract or new_model.ormar_config.proxy
    )
    if new_model.ormar_config.database is None and not skip_db_metadata_checks:
        raise ormar.ModelDefinitionError(
            f"{new_model.__name__} does not have database defined."
        )

    if new_model.ormar_config.metadata is None and not skip_db_metadata_checks:
        raise ormar.ModelDefinitionError(
            f"{new_model.__name__} does not have metadata defined."
        )

config_field_not_set(model, field_name)

Checks if field with given name is already present in model.OrmarConfig. Then check if it's set to something truthful (in practice meaning not None, as it's non or ormar Field only).

:param model: newly constructed model :type model: Model class :param field_name: name of the ormar field :type field_name: str :return: result of the check :rtype: bool

Source code in ormar/models/helpers/models.py
Python
129
130
131
132
133
134
135
136
137
138
139
140
141
142
def config_field_not_set(model: type["Model"], field_name: str) -> bool:
    """
    Checks if field with given name is already present in model.OrmarConfig.
    Then check if it's set to something truthful
    (in practice meaning not None, as it's non or ormar Field only).

    :param model: newly constructed model
    :type model: Model class
    :param field_name: name of the ormar field
    :type field_name: str
    :return: result of the check
    :rtype: bool
    """
    return not getattr(model.ormar_config, field_name)

expand_reverse_relationships(model)

Iterates through model_fields of given model and verifies if all reverse relation have been populated on related models.

If the reverse relation has not been set before it's set here.

:param model: model on which relation should be checked and registered :type model: Model class

Source code in ormar/models/helpers/relations.py
Python
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
def expand_reverse_relationships(model: type["Model"]) -> None:
    """
    Iterates through model_fields of given model and verifies if all reverse
    relation have been populated on related models.

    If the reverse relation has not been set before it's set here.

    :param model: model on which relation should be checked and registered
    :type model: Model class
    """
    model_fields = list(model.ormar_config.model_fields.values())
    for model_field in model_fields:
        if (
            model_field.is_relation
            and not model_field.has_unresolved_forward_refs()
            and not model_field.is_through
        ):
            model_field = cast("ForeignKeyField", model_field)
            expand_reverse_relationship(model_field=model_field)

extract_annotations_and_default_vals(attrs)

Extracts annotations from class namespace dict and triggers extraction of ormar model_fields.

:param attrs: namespace of the class created :type attrs: dict :return: namespace of the class updated, dict of extracted model_fields :rtype: tuple[dict, dict]

Source code in ormar/models/helpers/models.py
Python
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
def extract_annotations_and_default_vals(attrs: dict) -> tuple[dict, dict]:
    """
    Extracts annotations from class namespace dict and triggers
    extraction of ormar model_fields.

    :param attrs: namespace of the class created
    :type attrs: dict
    :return: namespace of the class updated, dict of extracted model_fields
    :rtype: tuple[dict, dict]
    """
    key = "__annotations__"
    attrs[key] = attrs.get(key, {})
    attrs, model_fields = populate_pydantic_default_values(attrs)
    return attrs, model_fields

get_potential_fields(attrs)

Gets all the fields in current class namespace that are Fields.

:param attrs: current class namespace :type attrs: dict :return: extracted fields that are ormar Fields :rtype: dict

Source code in ormar/models/helpers/pydantic.py
Python
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
def get_potential_fields(attrs: Union[dict, MappingProxyType]) -> dict:
    """
    Gets all the fields in current class namespace that are Fields.

    :param attrs: current class namespace
    :type attrs: dict
    :return: extracted fields that are ormar Fields
    :rtype: dict
    """
    return {
        k: v
        for k, v in attrs.items()
        if (
            (isinstance(v, type) and issubclass(v, BaseField))
            or isinstance(v, BaseField)
        )
    }

get_pydantic_base_orm_config()

Returns empty pydantic Config with orm_mode set to True.

:return: empty default config with orm_mode set. :rtype: pydantic Config

Source code in ormar/models/helpers/pydantic.py
Python
101
102
103
104
105
106
107
108
109
def get_pydantic_base_orm_config() -> pydantic.ConfigDict:
    """
    Returns empty pydantic Config with orm_mode set to True.

    :return: empty default config with orm_mode set.
    :rtype: pydantic Config
    """

    return ConfigDict(validate_assignment=True, ser_json_bytes="base64")

merge_or_generate_pydantic_config(attrs, name)

Checks if the user provided pydantic Config, and if he did merges it with the default one.

Updates the attrs in place with a new config.

:rtype: None

Source code in ormar/models/helpers/pydantic.py
Python
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
def merge_or_generate_pydantic_config(attrs: dict, name: str) -> None:
    """
    Checks if the user provided pydantic Config,
    and if he did merges it with the default one.

    Updates the attrs in place with a new config.

    :rtype: None
    """
    default_config = get_pydantic_base_orm_config()
    if "model_config" in attrs:
        provided_config = attrs["model_config"]
        if not isinstance(provided_config, dict):
            raise ModelDefinitionError(
                f"Config provided for class {name} has to be a dictionary."
            )

        config = {**default_config, **provided_config}
        attrs["model_config"] = config
    else:
        attrs["model_config"] = default_config

modify_schema_example(model)

Modifies the schema example in openapi schema.

:param model: newly constructed Model :type model: Model class

Source code in ormar/models/helpers/validation.py
Python
212
213
214
215
216
217
218
219
220
def modify_schema_example(model: type["Model"]) -> None:  # noqa CCR001
    """
    Modifies the schema example in openapi schema.

    :param model: newly constructed Model
    :type model: Model class
    """
    if not config_field_not_set(model=model, field_name="model_fields"):
        model.model_config["json_schema_extra"] = construct_schema_function()

populate_config_sqlalchemy_table_if_required(config)

Constructs sqlalchemy table out of columns and parameters set on OrmarConfig. It populates name, metadata, columns and constraints.

:param config: OrmarConfig of the Model without sqlalchemy table constructed :type config: Model class OrmarConfig

Source code in ormar/models/helpers/sqlalchemy.py
Python
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
def populate_config_sqlalchemy_table_if_required(config: "OrmarConfig") -> None:
    """
    Constructs sqlalchemy table out of columns and parameters set on OrmarConfig.
    It populates name, metadata, columns and constraints.

    :param config: OrmarConfig of the Model without sqlalchemy table constructed
    :type config: Model class OrmarConfig
    """
    if config.table is None and check_for_null_type_columns_from_forward_refs(
        config=config
    ):
        set_constraint_names(config=config)
        table = sqlalchemy.Table(
            config.tablename,
            config.metadata,
            *config.columns,
            *config.constraints,
            schema=config.schema,
        )
        config.table = table

populate_config_tablename_columns_and_pk(name, new_model)

Sets Model tablename if it's not already set in OrmarConfig. Default tablename if not present is class name lower + s (i.e. Bed becomes -> beds)

Checks if Model's OrmarConfig have pkname and columns set. If not calls the sqlalchemy_columns_from_model_fields to populate columns from ormar.fields definitions.

:raises ModelDefinitionError: if pkname is not present raises ModelDefinitionError. Each model has to have pk.

:param name: name of the current Model :type name: str :param new_model: currently constructed Model :type new_model: ormar.models.metaclass.ModelMetaclass :return: Model with populated pkname and columns in OrmarConfig :rtype: ormar.models.metaclass.ModelMetaclass

Source code in ormar/models/helpers/sqlalchemy.py
Python
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
def populate_config_tablename_columns_and_pk(
    name: str, new_model: type["Model"]
) -> type["Model"]:
    """
    Sets Model tablename if it's not already set in OrmarConfig.
    Default tablename if not present is class name lower + s (i.e. Bed becomes -> beds)

    Checks if Model's OrmarConfig have pkname and columns set.
    If not calls the sqlalchemy_columns_from_model_fields to populate
    columns from ormar.fields definitions.

    :raises ModelDefinitionError: if pkname is not present raises ModelDefinitionError.
    Each model has to have pk.

    :param name: name of the current Model
    :type name: str
    :param new_model: currently constructed Model
    :type new_model: ormar.models.metaclass.ModelMetaclass
    :return: Model with populated pkname and columns in OrmarConfig
    :rtype: ormar.models.metaclass.ModelMetaclass
    """
    tablename = name.lower() + "s"
    new_model.ormar_config.tablename = (
        new_model.ormar_config.tablename
        if new_model.ormar_config.tablename
        else tablename
    )
    pkname: Optional[str]

    if new_model.ormar_config.columns:
        columns = new_model.ormar_config.columns
        pkname = new_model.ormar_config.pkname
    else:
        pkname, columns = sqlalchemy_columns_from_model_fields(
            new_model.ormar_config.model_fields, new_model
        )

    if pkname is None:
        raise ormar.ModelDefinitionError("Table has to have a primary key.")

    new_model.ormar_config.columns = columns
    new_model.ormar_config.pkname = pkname
    if not new_model.ormar_config.orders_by:
        # by default, we sort by pk name if other option not provided
        new_model.ormar_config.orders_by.append(pkname)
    return new_model

populate_default_options_values(new_model, model_fields)

Sets all optional OrmarConfig values to its defaults and set model_fields that were already previously extracted.

Here should live all options that are not overwritten/set for all models.

Current options are: * constraints = [] * abstract = False

:param new_model: newly constructed Model :type new_model: Model class :param model_fields: dict of model fields :type model_fields: Union[dict[str, type], dict]

Source code in ormar/models/helpers/models.py
Python
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
def populate_default_options_values(  # noqa: CCR001
    new_model: type["Model"], model_fields: dict
) -> None:
    """
    Sets all optional OrmarConfig values to its defaults
    and set model_fields that were already previously extracted.

    Here should live all options that are not overwritten/set for all models.

    Current options are:
    * constraints = []
    * abstract = False

    :param new_model: newly constructed Model
    :type new_model: Model class
    :param model_fields: dict of model fields
    :type model_fields: Union[dict[str, type], dict]
    """
    new_model.ormar_config.model_fields.update(model_fields)
    if any(is_field_an_forward_ref(field) for field in model_fields.values()):
        new_model.ormar_config.requires_ref_update = True

    new_model._json_fields = {
        name for name, field in model_fields.items() if field.__type__ == pydantic.Json
    }
    new_model._bytes_fields = {
        name for name, field in model_fields.items() if field.__type__ is bytes
    }
    new_model._onupdate_fields = {
        name for name, field in model_fields.items() if field.has_on_update()
    }

    new_model.__relation_map__ = None
    new_model.__ormar_fields_validators__ = None

register_relation_in_alias_manager(field)

Registers the relation (and reverse relation) in alias manager. The m2m relations require registration of through model between actual end models of the relation.

Delegates the actual registration to: m2m - register_many_to_many_relation_on_build fk - register_relation_on_build

:param field: relation field :type field: ForeignKey or ManyToManyField class

Source code in ormar/models/helpers/relations.py
Python
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
def register_relation_in_alias_manager(field: "BaseField") -> None:
    """
    Registers the relation (and reverse relation) in alias manager.
    The m2m relations require registration of through model between
    actual end models of the relation.

    Delegates the actual registration to:
    m2m - register_many_to_many_relation_on_build
    fk - register_relation_on_build

    :param field: relation field
    :type field: ForeignKey or ManyToManyField class
    """
    if field.is_multi:
        if field.has_unresolved_forward_refs():
            return
        field = cast("ManyToManyField", field)
        register_many_to_many_relation_on_build(field=field)
    elif field.is_relation and not field.is_through:
        if field.has_unresolved_forward_refs():
            return
        register_relation_on_build(field=cast("ForeignKeyField", field))

sqlalchemy_columns_from_model_fields(model_fields, new_model)

Iterates over declared on Model model fields and extracts fields that should be treated as database fields.

If the model is empty it sets mandatory id field as primary key (used in through models in m2m relations).

Triggers a validation of relation_names in relation fields. If multiple fields are leading to the same related model only one can have empty related_name param. Also related_names have to be unique.

Trigger validation of primary_key - only one and required pk can be set

Sets owner on each model_field as reference to newly created Model.

:raises ModelDefinitionError: if validation of related_names fail, or pkname validation fails. :param model_fields: dictionary of declared ormar model fields :type model_fields: dict[str, ormar.Field] :param new_model: :type new_model: Model class :return: pkname, list of sqlalchemy columns :rtype: tuple[Optional[str], list[sqlalchemy.Column]]

Source code in ormar/models/helpers/sqlalchemy.py
Python
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
def sqlalchemy_columns_from_model_fields(
    model_fields: dict, new_model: type["Model"]
) -> tuple[Optional[str], list[sqlalchemy.Column]]:
    """
    Iterates over declared on Model model fields and extracts fields that
    should be treated as database fields.

    If the model is empty it sets mandatory id field as primary key
    (used in through models in m2m relations).

    Triggers a validation of relation_names in relation fields. If multiple fields
    are leading to the same related model only one can have empty related_name param.
    Also related_names have to be unique.

    Trigger validation of primary_key - only one and required pk can be set

    Sets `owner` on each model_field as reference to newly created Model.

    :raises ModelDefinitionError: if validation of related_names fail,
    or pkname validation fails.
    :param model_fields: dictionary of declared ormar model fields
    :type model_fields: dict[str, ormar.Field]
    :param new_model:
    :type new_model: Model class
    :return: pkname, list of sqlalchemy columns
    :rtype: tuple[Optional[str], list[sqlalchemy.Column]]
    """
    if len(model_fields.keys()) == 0:
        model_fields["id"] = ormar.Integer(name="id", primary_key=True)
        logging.warning(
            f"Table {new_model.ormar_config.tablename} had no fields so auto "
            "Integer primary key named `id` created."
        )
    validate_related_names_in_relations(model_fields, new_model)
    return _process_fields(model_fields=model_fields, new_model=new_model)