Skip to content

OneToOneField

OneToOneField is the unique variant of ForeignKey.

It behaves like a foreign key at declaration and query time, but the generated relation columns are unique and the reverse accessor is singular by default.

Practical example

class Profile(saffier.Model):
    id = saffier.IntegerField(primary_key=True, autoincrement=True)
    user = saffier.OneToOneField("User", on_delete=saffier.CASCADE)

    class Meta:
        registry = models

Reverse behavior

When related_name is omitted, Saffier generates a singular reverse accessor from the declaring model name. That means a Profile.user relation typically becomes user.profile, not user.profiles_set.

For reverse one-to-one relations, remove() can omit the child object because at most one related row can exist.

saffier.OneToOneField

OneToOneField(to, **kwargs)

Bases: ForeignKey

Foreign-key field that enforces a unique reverse relationship.

The field is implemented as a standard foreign key with unique=True, which gives it one-to-one semantics while reusing the full foreign-key machinery.

Source code in saffier/core/db/fields/base.py
1590
1591
1592
def __init__(self, to: type["Model"] | str, **kwargs: typing.Any):
    kwargs["unique"] = True
    super().__init__(to, **kwargs)

is_virtual class-attribute instance-attribute

is_virtual = False

server_default instance-attribute

server_default = pop('server_default', None)

null instance-attribute

null = get('null', False)

default_value instance-attribute

default_value = get('default')

primary_key instance-attribute

primary_key = primary_key

index instance-attribute

index = index

unique instance-attribute

unique = unique

validator instance-attribute

validator = get_validator(**kwargs)

comment instance-attribute

comment = get('comment')

column_name instance-attribute

column_name = pop('column_name', None)

owner instance-attribute

owner = pop('owner', None)

registry instance-attribute

registry = pop('registry', None)

name instance-attribute

name = pop('name', '')

inherit instance-attribute

inherit = pop('inherit', True)

no_copy instance-attribute

no_copy = pop('no_copy', False)

exclude instance-attribute

exclude = pop('exclude', False)

inject_default_on_partial_update instance-attribute

inject_default_on_partial_update = get(
    "inject_default_on_partial_update", False
)

server_onupdate instance-attribute

server_onupdate = pop('server_onupdate', None)

autoincrement instance-attribute

autoincrement = pop('autoincrement', False)

secret instance-attribute

secret = pop('secret', False)

related_fields instance-attribute

related_fields = tuple(pop('related_fields', ()))

_target_registry instance-attribute

_target_registry = pop('target_registry', None)

to instance-attribute

to = to

on_delete instance-attribute

on_delete = on_delete

on_update instance-attribute

on_update = on_update or CASCADE

related_name instance-attribute

related_name = related_name

embed_parent instance-attribute

embed_parent = embed_parent

no_constraint instance-attribute

no_constraint = no_constraint

remove_referenced instance-attribute

remove_referenced = remove_referenced

use_model_based_deletion instance-attribute

use_model_based_deletion = use_model_based_deletion

force_cascade_deletion_relation instance-attribute

force_cascade_deletion_relation = (
    force_cascade_deletion_relation
)

target_registry property writable

target_registry

Return the registry used to resolve string relation targets.

Explicit target_registry overrides take precedence, followed by the owning model registry and finally the field-level registry fallback.

target property

target

Resolve and cache the target model class for this relation.

String targets are looked up lazily so models can reference classes that are declared later in the import graph.

RETURNS DESCRIPTION
Any

Resolved target model class.

TYPE: Any

related_columns property

related_columns

Return target columns used by the relation mapping.

Composite foreign keys can point to multiple target columns. When explicit related_fields are not provided, the target model primary-key columns are used.

RETURNS DESCRIPTION
dict[str, Column | None]

dict[str, sqlalchemy.Column | None]: Mapping of target column names to

dict[str, Column | None]

SQLAlchemy columns where available.

ForeignKeyValidator

ForeignKeyValidator(
    *,
    title="",
    description="",
    help_text="",
    default=NO_DEFAULT,
    null=False,
    read_only=False,
    **kwargs,
)

Bases: SaffierField

Source code in saffier/core/db/fields/_internal.py
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
def __init__(
    self,
    *,
    title: str = "",
    description: str = "",
    help_text: str = "",
    default: typing.Any = NO_DEFAULT,
    null: bool = False,
    read_only: bool = False,
    **kwargs: typing.Any,
) -> None:
    super().__init__(**kwargs)
    self.explicit_none = default is None
    if null and default is NO_DEFAULT:
        default = None

    if default is not NO_DEFAULT:
        self.default = default

    self.null = null
    self.read_only = read_only
    self.title = title
    self.description = description
    self.help_text = help_text

error_messages class-attribute instance-attribute

error_messages = {}

explicit_none instance-attribute

explicit_none = default is None

default instance-attribute

default = default

null instance-attribute

null = null

read_only instance-attribute

read_only = read_only

title instance-attribute

title = title

description instance-attribute

description = description

help_text instance-attribute

help_text = help_text

__hash__

__hash__()
Source code in saffier/core/datastructures.py
19
20
21
22
23
24
25
26
def __hash__(self) -> int:
    values: dict[str, Any] = {}
    for key, value in self.__dict__.items():
        if isinstance(value, (list, set)):
            values[key] = tuple(value)
        else:
            values[key] = value
    return hash((type(self),) + tuple(values))

validate_or_error

validate_or_error(value)
Source code in saffier/core/db/fields/_internal.py
71
72
73
74
75
76
def validate_or_error(self, value: typing.Any) -> ValidationResult:
    try:
        value = self.check(value)
    except ValidationError as error:
        return ValidationResult(value=None, error=error)
    return ValidationResult(value=value, error=None)

has_default

has_default()
Source code in saffier/core/db/fields/_internal.py
78
79
80
81
82
def has_default(self) -> bool:
    if not hasattr(self, "default"):
        return False
    default = self.default
    return default is not None or self.explicit_none

validation_error

validation_error(code, value=None)
Source code in saffier/core/db/fields/_internal.py
84
85
86
def validation_error(self, code: str, value: typing.Any | None = None) -> ValidationError:
    text = self.get_error_message(code)
    return ValidationError(text=text, code=code)

get_error_message

get_error_message(code)
Source code in saffier/core/db/fields/_internal.py
88
89
def get_error_message(self, code: str) -> str:
    return self.error_messages[code].format(**self.__dict__)

get_default_value

get_default_value()
Source code in saffier/core/db/fields/_internal.py
91
92
93
94
95
def get_default_value(self) -> typing.Any:
    default = getattr(self, "default", None)
    if callable(default):
        return default()
    return default

check

check(value)
Source code in saffier/core/db/fields/base.py
711
712
713
714
715
716
717
718
def check(self, value: typing.Any) -> typing.Any:
    if value is None and self.null:
        return None
    if value is None:
        raise self.validation_error("null")
    if hasattr(value, "pk"):
        return value.pk
    return value

get_validator

get_validator(**kwargs)
Source code in saffier/core/db/fields/base.py
811
812
def get_validator(self, **kwargs: typing.Any) -> SaffierField:
    return self.ForeignKeyValidator(**kwargs)

get_column_type

get_column_type()
Source code in saffier/core/db/fields/base.py
139
140
def get_column_type(self) -> sqlalchemy.types.TypeEngine:
    raise NotImplementedError()  # pragma: no cover

get_constraints

get_constraints()
Source code in saffier/core/db/fields/base.py
142
143
def get_constraints(self) -> typing.Any:
    return []

get_columns_nullable

get_columns_nullable()

Resolve SQLAlchemy nullability for this field during metadata build.

The model declaration remains the canonical field definition, but migration generation can temporarily ask Saffier to render selected columns as nullable. That temporary override makes it possible to add a required field to a table that already has rows, then backfill defaults in the generated online migration before the final model constraint is enforced by later migrations.

RETURNS DESCRIPTION
bool

True when the field declaration or active migration

TYPE: bool

bool

override should make generated columns nullable.

Source code in saffier/core/db/fields/base.py
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
def get_columns_nullable(self) -> bool:
    """Resolve SQLAlchemy nullability for this field during metadata build.

    The model declaration remains the canonical field definition, but
    migration generation can temporarily ask Saffier to render selected
    columns as nullable. That temporary override makes it possible to add a
    required field to a table that already has rows, then backfill defaults
    in the generated online migration before the final model constraint is
    enforced by later migrations.

    Returns:
        bool: ``True`` when the field declaration or active migration
        override should make generated columns nullable.
    """
    if self.null:
        return True

    force_fields = FORCE_FIELDS_NULLABLE.get()
    owner_name = self.owner.__name__ if self.owner is not None else ""
    return (owner_name, self.name) in force_fields or ("", self.name) in force_fields

get_columns

get_columns(name)
Source code in saffier/core/db/fields/base.py
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
def get_columns(self, name: str) -> typing.Sequence[sqlalchemy.Column]:
    target = self.target
    columns: list[sqlalchemy.Column] = []
    for related_key, related_column in self.related_columns.items():
        related_field = target.fields[related_key]
        related_type = (
            related_column.type
            if related_column is not None
            else related_field.get_column_type()
        )
        related_name = (
            getattr(related_column, "name", None) or related_field.column_name or related_key
        )
        related_nullable = (
            related_column.nullable if related_column is not None else related_field.null
        )
        columns.append(
            sqlalchemy.Column(
                key=self.get_fk_field_name(name, related_key),
                name=self.get_fk_column_name(name, related_name),
                type_=related_type,
                nullable=(self.get_columns_nullable() or related_nullable)
                and not self.primary_key,
                primary_key=self.primary_key,
                autoincrement=False,
            )
        )
    return columns

get_global_constraints

get_global_constraints(name, columns, *, schema=None)

Build cross-table constraints and indexes for the foreign key columns.

PARAMETER DESCRIPTION
name

Logical field name on the owning model.

TYPE: str

columns

Physical columns generated for the relation.

TYPE: Sequence[Column]

schema

Optional schema override for the target table reference.

TYPE: str | None DEFAULT: None

RETURNS DESCRIPTION
Sequence[Constraint | Index]

typing.Sequence[sqlalchemy.Constraint | sqlalchemy.Index]: SQLAlchemy constraints and indexes that should be attached to the owning table.

Source code in saffier/core/db/fields/base.py
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
def get_global_constraints(
    self,
    name: str,
    columns: typing.Sequence[sqlalchemy.Column],
    *,
    schema: str | None = None,
) -> typing.Sequence[sqlalchemy.Constraint | sqlalchemy.Index]:
    """Build cross-table constraints and indexes for the foreign key columns.

    Args:
        name: Logical field name on the owning model.
        columns: Physical columns generated for the relation.
        schema: Optional schema override for the target table reference.

    Returns:
        typing.Sequence[sqlalchemy.Constraint | sqlalchemy.Index]:
            SQLAlchemy constraints and indexes that should be attached to
            the owning table.
    """
    owner_registry = getattr(self.owner.meta, "registry", None)
    target = self.target
    target_registry = getattr(target.meta, "registry", None)
    owner_database = getattr(self.owner, "database", None)
    target_database = getattr(target, "database", None)
    use_constraint = not (
        self.no_constraint
        or (
            owner_registry not in (None, False)
            and target_registry not in (None, False)
            and owner_registry is not target_registry
        )
        or (
            owner_database is not None
            and target_database is not None
            and owner_database is not target_database
        )
    )
    constraints: list[sqlalchemy.Constraint | sqlalchemy.Index] = []
    if use_constraint:
        table_name = target.meta.tablename
        if schema is not None:
            table_name = f"{schema}.{table_name}"
        constraints.append(
            sqlalchemy.schema.ForeignKeyConstraint(
                columns,
                [
                    f"{table_name}.{self.from_fk_field_name(name, column.key)}"
                    for column in columns
                ],
                ondelete=self.on_delete,
                onupdate=self.on_update,
                name=self.get_fk_name(name),
            )
        )
    if self.unique or self.index:
        constraints.append(
            sqlalchemy.Index(
                self.get_fkindex_name(name),
                *columns,
                unique=self.unique,
            )
        )
    return constraints

has_column

has_column()
Source code in saffier/core/db/fields/base.py
179
180
def has_column(self) -> bool:
    return not self.is_virtual

get_embedded_fields

get_embedded_fields(field_name, existing_fields)
Source code in saffier/core/db/fields/base.py
182
183
184
185
186
187
188
def get_embedded_fields(
    self,
    field_name: str,
    existing_fields: dict[str, "Field"],
) -> dict[str, "Field"]:
    del field_name, existing_fields
    return {}

expand_relationship

expand_relationship(value)

Normalize relation values into lightweight target model instances.

Raw primary-key values, dictionaries, proxy models, and already-loaded target instances are all accepted so callers can assign foreign keys in a natural way while the ORM preserves a consistent in-memory shape.

PARAMETER DESCRIPTION
value

Incoming relation value.

TYPE: Any

RETURNS DESCRIPTION
Any

typing.Any: Target model instance or None.

Source code in saffier/core/db/fields/base.py
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
def expand_relationship(self, value: typing.Any) -> typing.Any:
    """Normalize relation values into lightweight target model instances.

    Raw primary-key values, dictionaries, proxy models, and already-loaded
    target instances are all accepted so callers can assign foreign keys in a
    natural way while the ORM preserves a consistent in-memory shape.

    Args:
        value: Incoming relation value.

    Returns:
        typing.Any: Target model instance or `None`.
    """
    if value is None:
        return None
    target = self.target
    related_columns = tuple(self.related_columns.keys())
    if isinstance(value, target):
        if (
            self.null
            and related_columns
            and all(
                key in value.__dict__ and getattr(value, key) is None
                for key in related_columns
            )
        ):
            return None
        return value
    if hasattr(value, "__db_model__"):
        value_cls = value.__class__
        if (
            getattr(value_cls, "is_proxy_model", False)
            and getattr(value_cls, "parent", None) is target
        ):
            if (
                self.null
                and related_columns
                and all(
                    key in value.__dict__ and getattr(value, key) is None
                    for key in related_columns
                )
            ):
                return None
            return value
    if isinstance(value, dict):
        return target(**value)
    if hasattr(value, "pk"):
        pk_value = value.pk
        if isinstance(pk_value, dict):
            return target(pk=pk_value)
        return target(pk=pk_value)
    return target(pk=value)

clean

clean(name, value, *, for_query=False)
Source code in saffier/core/db/fields/base.py
 977
 978
 979
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
def clean(
    self, name: str, value: typing.Any, *, for_query: bool = False
) -> dict[str, typing.Any]:
    del for_query
    cleaned: dict[str, typing.Any] = {}
    related_keys = tuple(self.related_columns.keys())
    column_names = self.get_column_names(name)

    if value is None:
        for column_name in column_names:
            cleaned[column_name] = None
        return cleaned

    target = self.target
    if isinstance(value, dict):
        for related_key, column_name in zip(related_keys, column_names, strict=False):
            if related_key in value:
                cleaned[column_name] = value[related_key]
            elif column_name in value:
                cleaned[column_name] = value[column_name]
        return cleaned

    if isinstance(value, target):
        for related_key, column_name in zip(related_keys, column_names, strict=False):
            cleaned[column_name] = getattr(value, related_key, None)
        return cleaned

    if hasattr(value, "__db_model__"):
        value_cls = value.__class__
        if (
            getattr(value_cls, "is_proxy_model", False)
            and getattr(value_cls, "parent", None) is target
        ):
            for related_key, column_name in zip(related_keys, column_names, strict=False):
                cleaned[column_name] = getattr(value, related_key, None)
            return cleaned

    pk_value = getattr(value, "pk", None) if hasattr(value, "pk") else None
    if isinstance(pk_value, dict):
        return self.clean(name, pk_value)
    if pk_value is not None and len(column_names) == 1:
        cleaned[column_names[0]] = pk_value
        return cleaned

    if len(column_names) == 1:
        cleaned[column_names[0]] = value
        return cleaned

    raise ValueError(f"Cannot handle composite foreign key value {value!r}.")

modify_input

modify_input(name, kwargs)
Source code in saffier/core/db/fields/base.py
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
def modify_input(self, name: str, kwargs: dict[str, typing.Any]) -> None:
    if name in kwargs:
        return

    column_names = self.get_column_names(name)
    if len(column_names) <= 1:
        return

    payload: dict[str, typing.Any] = {}
    for column_name in column_names:
        if column_name in kwargs:
            payload[self.from_fk_field_name(name, column_name)] = kwargs.pop(column_name)

    if not payload:
        return
    if len(payload) != len(column_names):
        raise ValueError("Cannot update the foreign key partially")
    kwargs[name] = payload

raise_for_non_default

raise_for_non_default(default, server_default)
Source code in saffier/core/db/fields/base.py
215
216
def raise_for_non_default(self, default: typing.Any, server_default: typing.Any) -> typing.Any:
    del default, server_default

get_default_value

get_default_value()
Source code in saffier/core/db/fields/base.py
218
219
def get_default_value(self) -> typing.Any:
    return self.validator.get_default_value()

has_default

has_default()
Source code in saffier/core/db/fields/base.py
221
222
def has_default(self) -> bool:
    return self.validator.has_default()

get_default_values

get_default_values(field_name, cleaned_data)
Source code in saffier/core/db/fields/base.py
224
225
226
227
228
229
230
231
def get_default_values(
    self,
    field_name: str,
    cleaned_data: dict[str, typing.Any],
) -> dict[str, typing.Any]:
    if field_name in cleaned_data or not self.has_column():
        return {}
    return {field_name: self.get_default_value()}

is_required

is_required()
Source code in saffier/core/db/fields/base.py
233
234
235
236
def is_required(self) -> bool:
    if self.primary_key and self.autoincrement:
        return False
    return not (self.null or self.server_default is not None or self.has_default())

get_is_null_clause

get_is_null_clause(column)
Source code in saffier/core/db/fields/base.py
238
239
def get_is_null_clause(self, column: typing.Any) -> typing.Any:
    return column == None  # noqa: E711

get_is_empty_clause

get_is_empty_clause(column)
Source code in saffier/core/db/fields/base.py
241
242
def get_is_empty_clause(self, column: typing.Any) -> typing.Any:
    return self.get_is_null_clause(column)

operator_to_clause

operator_to_clause(field_name, operator, table, value)

Translate one lookup operator into a SQLAlchemy clause.

PARAMETER DESCRIPTION
field_name

Logical field name being filtered.

TYPE: str

operator

Saffier lookup suffix such as exact or isnull.

TYPE: str

table

SQLAlchemy table containing the target column.

TYPE: Table

value

Lookup value supplied by the caller.

TYPE: Any

RETURNS DESCRIPTION
Any

SQLAlchemy boolean clause implementing the lookup.

TYPE: Any

Source code in saffier/core/db/fields/base.py
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
def operator_to_clause(
    self,
    field_name: str,
    operator: str,
    table: sqlalchemy.Table,
    value: typing.Any,
) -> typing.Any:
    """Translate one lookup operator into a SQLAlchemy clause.

    Args:
        field_name: Logical field name being filtered.
        operator: Saffier lookup suffix such as `exact` or `isnull`.
        table: SQLAlchemy table containing the target column.
        value: Lookup value supplied by the caller.

    Returns:
        Any: SQLAlchemy boolean clause implementing the lookup.
    """
    column = table.columns[field_name]
    mapped_operator = settings.filter_operators.get(operator, operator)

    if mapped_operator == "isnull":
        is_null = self.get_is_null_clause(column)
        return is_null if value else sqlalchemy.not_(is_null)

    if mapped_operator == "isempty":
        is_empty = self.get_is_empty_clause(column)
        return is_empty if value else sqlalchemy.not_(is_empty)

    return getattr(column, mapped_operator)(value)

get_fk_name

get_fk_name(name)
Source code in saffier/core/db/fields/base.py
847
848
849
def get_fk_name(self, name: str) -> str:
    fk_name = f"fk_{self.owner.meta.tablename}_{self.target.meta.tablename}_{name}"
    return fk_name[:CHAR_LIMIT]

get_fkindex_name

get_fkindex_name(name)
Source code in saffier/core/db/fields/base.py
851
852
853
def get_fkindex_name(self, name: str) -> str:
    fk_name = f"fkindex_{self.owner.meta.tablename}_{self.target.meta.tablename}_{name}"
    return fk_name[:CHAR_LIMIT]

get_fk_field_name

get_fk_field_name(name, fieldname)
Source code in saffier/core/db/fields/base.py
855
856
857
858
def get_fk_field_name(self, name: str, fieldname: str) -> str:
    if len(self.related_columns) == 1:
        return name
    return f"{name}_{fieldname}"

get_fk_column_name

get_fk_column_name(name, fieldname)
Source code in saffier/core/db/fields/base.py
860
861
862
863
864
def get_fk_column_name(self, name: str, fieldname: str) -> str:
    name = self.column_name or name
    if len(self.related_columns) == 1:
        return name
    return f"{name}_{fieldname}"

get_column_names

get_column_names(name)
Source code in saffier/core/db/fields/base.py
866
867
868
869
def get_column_names(self, name: str) -> tuple[str, ...]:
    return tuple(
        self.get_fk_field_name(name, field_name) for field_name in self.related_columns
    )

from_fk_field_name

from_fk_field_name(name, fieldname)
Source code in saffier/core/db/fields/base.py
871
872
873
874
def from_fk_field_name(self, name: str, fieldname: str) -> str:
    if len(self.related_columns) == 1:
        return next(iter(self.related_columns.keys()))
    return fieldname.removeprefix(f"{name}_")

pre_save_callback async

pre_save_callback(value, original_value, is_update)

Save nested related objects before persisting the owning row.

The callback allows callers to pass an unsaved related model instance or a dictionary payload. When necessary, the related object is saved first and the method returns the concrete foreign-key column values to persist on the owning model.

PARAMETER DESCRIPTION
value

Current relation value being persisted.

TYPE: Any

original_value

Previous relation value used during updates.

TYPE: Any

is_update

Whether the owning model is performing an update.

TYPE: bool

RETURNS DESCRIPTION
dict[str, Any]

dict[str, typing.Any]: Database-ready foreign-key column payload.

Source code in saffier/core/db/fields/base.py
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
async def pre_save_callback(
    self,
    value: typing.Any,
    original_value: typing.Any,
    is_update: bool,
) -> dict[str, typing.Any]:
    """Save nested related objects before persisting the owning row.

    The callback allows callers to pass an unsaved related model instance or
    a dictionary payload. When necessary, the related object is saved first
    and the method returns the concrete foreign-key column values to persist
    on the owning model.

    Args:
        value: Current relation value being persisted.
        original_value: Previous relation value used during updates.
        is_update: Whether the owning model is performing an update.

    Returns:
        dict[str, typing.Any]: Database-ready foreign-key column payload.
    """
    target = self.target
    if value is None or (isinstance(value, dict) and not value):
        value = original_value

    if isinstance(value, target):
        if (
            getattr(value, "_saffier_save_in_progress", False)
            or getattr(value, "pk", None) is not None
        ):
            return self.clean(self.name, value, for_query=False)
        await value.save()
        return self.clean(self.name, value, for_query=False)

    if hasattr(value, "__db_model__"):
        value_cls = value.__class__
        if (
            getattr(value_cls, "is_proxy_model", False)
            and getattr(value_cls, "parent", None) is target
        ):
            if (
                getattr(value, "_saffier_save_in_progress", False)
                or getattr(value, "pk", None) is not None
            ):
                return self.clean(self.name, value, for_query=False)
            await value.save()
            return self.clean(self.name, value, for_query=False)

    if isinstance(value, dict):
        return await self.pre_save_callback(
            target(**value),
            original_value=None,
            is_update=is_update,
        )

    if hasattr(value, "pk") and getattr(value, "pk", None) is not None:
        return self.clean(self.name, value, for_query=False)

    if value is None:
        return {}
    return {self.name: value}

is_cross_db

is_cross_db(owner_database=None)
Source code in saffier/core/db/fields/base.py
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
def is_cross_db(self, owner_database: typing.Any | None = None) -> bool:
    if owner_database is None:
        owner_database = getattr(self.owner, "database", None)
        if owner_database is None:
            owner_registry = getattr(getattr(self.owner, "meta", None), "registry", None)
            owner_database = getattr(owner_registry, "database", None)

    target = self.target
    target_database = getattr(target, "database", None)
    if target_database is None:
        target_registry = getattr(getattr(target, "meta", None), "registry", None)
        target_database = getattr(target_registry, "database", None)

    if owner_database is None or target_database is None:
        return False

    return str(owner_database.url) != str(target_database.url)
get_related_model_for_admin()
Source code in saffier/core/db/fields/base.py
1179
1180
1181
1182
1183
1184
1185
def get_related_model_for_admin(self) -> typing.Any | None:
    target = self.target
    registry = getattr(getattr(target, "meta", None), "registry", None)
    admin_models = getattr(registry, "admin_models", ())
    if registry and target.__name__ in admin_models:
        return target
    return None

get_column

get_column(name)
Source code in saffier/core/db/fields/base.py
1594
1595
def get_column(self, name: str) -> sqlalchemy.Column:
    return super().get_column(name)