Skip to content

common

Common column types like Text, Integer, and Float.

NullFloat = Null[float] module-attribute

A nullable float column. Alias for Null[float].

NullInteger = Null[int] module-attribute

A nullable integer column. Alias for Null[int].

NullText = Null[str] module-attribute

A nullable text column. Alias for Null[str].

Column

Bases: ColumnBase

The main parent class for creating columns, generic over the Python type.

Source code in src/embar/column/common.py
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 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
 63
 64
 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
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
class Column[T: PyType](ColumnBase):
    """
    The main parent class for creating columns, generic over the Python type.
    """

    # This is a tuple of the two values needed to generate a foreign key:
    # - the table referred to (as a lambda as it will not be defined yet)
    # - any on_delete option
    _fk: tuple[Callable[[], Column[Any]], OnDelete | None] | None = None

    _explicit_name: str | None
    _name: str | None
    default: T | _NoDefaultType  # NO_DEFAULT means "no default"
    _primary: bool
    _not_null: bool

    # This is to support eg VARCHAR(100) and also NUMERIC(10, 2)
    _extra_args: tuple[int] | tuple[int, int] | None = None

    def __init__(
        self,
        name: str | None = None,
        default: T | _NoDefaultType = NO_DEFAULT,
        primary: bool = False,
        not_null: bool = False,
    ):
        """
        Create a new Column instance.
        """
        self._name = name
        # if no _explicit_name, one is created automatically (see __set_name__)
        self._explicit_name = name
        self.default = default
        self._primary = primary
        self._not_null = not_null

    @overload
    def __get__(self, obj: None, owner: type) -> Self: ...
    @overload
    def __get__(self, obj: object, owner: type) -> T: ...

    def __get__(self, obj: object | None, owner: type) -> Self | T:
        """
        This allows this class to be typed as itself in Table definitions
        but as `T` in object instances. The overloads ensure this works for typechecking too.

        ```python
        from embar.table import Table
        from embar.column.common import Text, text
        class MyTable(Table):
            my_col: Text = text()      # typechecked as `Text`
        my_row = MyTable(my_col="foo") # typechecked as `str`
        assert isinstance(MyTable.my_col, Text)
        assert isinstance(my_row.my_col, str)
        ```
        """
        if obj is None:
            return self  # Class access returns descriptor
        return getattr(obj, f"_{self._name}")  # Instance access returns str

    def __set__(self, obj: object, value: T) -> None:
        """
        Allows values of type T (rather than `Column[T]`) to be assigned to this class when it's a field of an object.
        """
        setattr(obj, f"_{self._name}", value)

    def __set_name__(self, owner: Any, attr_name: str) -> None:
        """
        Called after the class body has executed, when the owning `Table` is being created.

        This is needed so that each `Column` can be told what the owning table's name is.
        """
        self._name = self._explicit_name if self._explicit_name is not None else attr_name
        default_for_info = None if isinstance(self.default, _NoDefaultType) else self.default
        self.info: ColumnInfo = ColumnInfo(
            name=self._name,
            col_type=self._sql_type,
            py_type=self._py_type,
            primary=self._primary,
            not_null=self._not_null,
            default=default_for_info,
            # This is passed a function, not a value.
            # Becuase in cases where the Table doesn't have an explicit name set, its name still
            # won't be known yet.
            _table_name=owner.get_name,
        )
        if self._fk is not None:
            ref, on_delete = self._fk
            self.info.ref = ref().info
            self.info.on_delete = on_delete

        if self._sql_type in SQL_TYPES_WITH_ARGS and self._extra_args is not None:
            args = ", ".join(str(x) for x in self._extra_args)
            self.info.args = f"({args})"

    def fk(
        self,
        ref: Callable[[], Column[Any]],
        on_delete: OnDelete | None = None,
    ) -> Self:
        """
        Create a foreign key reference to another table.
        """
        self._fk = (ref, on_delete)
        return self

    def many(self) -> ManyColumn[Self]:
        """
        Used to nest many values of this column in a model.

        ```python
        from typing import Annotated
        from pydantic import BaseModel
        from embar.column.common import Text, text
        from embar.table import Table
        class MyTable(Table):
            my_col: Text = text()
        class MyModel(BaseModel):
            values: Annotated[list[str], MyTable.my_col.many()]
        ```
        """
        return ManyColumn(self)

    @property
    def has_default(self) -> bool:
        """Whether this column has a default value (including None)."""
        return not isinstance(self.default, _NoDefaultType)

has_default property

Whether this column has a default value (including None).

__get__(obj, owner)

__get__(obj: None, owner: type) -> Self
__get__(obj: object, owner: type) -> T

This allows this class to be typed as itself in Table definitions but as T in object instances. The overloads ensure this works for typechecking too.

from embar.table import Table
from embar.column.common import Text, text
class MyTable(Table):
    my_col: Text = text()      # typechecked as `Text`
my_row = MyTable(my_col="foo") # typechecked as `str`
assert isinstance(MyTable.my_col, Text)
assert isinstance(my_row.my_col, str)
Source code in src/embar/column/common.py
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
def __get__(self, obj: object | None, owner: type) -> Self | T:
    """
    This allows this class to be typed as itself in Table definitions
    but as `T` in object instances. The overloads ensure this works for typechecking too.

    ```python
    from embar.table import Table
    from embar.column.common import Text, text
    class MyTable(Table):
        my_col: Text = text()      # typechecked as `Text`
    my_row = MyTable(my_col="foo") # typechecked as `str`
    assert isinstance(MyTable.my_col, Text)
    assert isinstance(my_row.my_col, str)
    ```
    """
    if obj is None:
        return self  # Class access returns descriptor
    return getattr(obj, f"_{self._name}")  # Instance access returns str

__init__(name=None, default=NO_DEFAULT, primary=False, not_null=False)

Create a new Column instance.

Source code in src/embar/column/common.py
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
def __init__(
    self,
    name: str | None = None,
    default: T | _NoDefaultType = NO_DEFAULT,
    primary: bool = False,
    not_null: bool = False,
):
    """
    Create a new Column instance.
    """
    self._name = name
    # if no _explicit_name, one is created automatically (see __set_name__)
    self._explicit_name = name
    self.default = default
    self._primary = primary
    self._not_null = not_null

__set__(obj, value)

Allows values of type T (rather than Column[T]) to be assigned to this class when it's a field of an object.

Source code in src/embar/column/common.py
72
73
74
75
76
def __set__(self, obj: object, value: T) -> None:
    """
    Allows values of type T (rather than `Column[T]`) to be assigned to this class when it's a field of an object.
    """
    setattr(obj, f"_{self._name}", value)

__set_name__(owner, attr_name)

Called after the class body has executed, when the owning Table is being created.

This is needed so that each Column can be told what the owning table's name is.

Source code in src/embar/column/common.py
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
def __set_name__(self, owner: Any, attr_name: str) -> None:
    """
    Called after the class body has executed, when the owning `Table` is being created.

    This is needed so that each `Column` can be told what the owning table's name is.
    """
    self._name = self._explicit_name if self._explicit_name is not None else attr_name
    default_for_info = None if isinstance(self.default, _NoDefaultType) else self.default
    self.info: ColumnInfo = ColumnInfo(
        name=self._name,
        col_type=self._sql_type,
        py_type=self._py_type,
        primary=self._primary,
        not_null=self._not_null,
        default=default_for_info,
        # This is passed a function, not a value.
        # Becuase in cases where the Table doesn't have an explicit name set, its name still
        # won't be known yet.
        _table_name=owner.get_name,
    )
    if self._fk is not None:
        ref, on_delete = self._fk
        self.info.ref = ref().info
        self.info.on_delete = on_delete

    if self._sql_type in SQL_TYPES_WITH_ARGS and self._extra_args is not None:
        args = ", ".join(str(x) for x in self._extra_args)
        self.info.args = f"({args})"

fk(ref, on_delete=None)

Create a foreign key reference to another table.

Source code in src/embar/column/common.py
107
108
109
110
111
112
113
114
115
116
def fk(
    self,
    ref: Callable[[], Column[Any]],
    on_delete: OnDelete | None = None,
) -> Self:
    """
    Create a foreign key reference to another table.
    """
    self._fk = (ref, on_delete)
    return self

many()

Used to nest many values of this column in a model.

from typing import Annotated
from pydantic import BaseModel
from embar.column.common import Text, text
from embar.table import Table
class MyTable(Table):
    my_col: Text = text()
class MyModel(BaseModel):
    values: Annotated[list[str], MyTable.my_col.many()]
Source code in src/embar/column/common.py
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
def many(self) -> ManyColumn[Self]:
    """
    Used to nest many values of this column in a model.

    ```python
    from typing import Annotated
    from pydantic import BaseModel
    from embar.column.common import Text, text
    from embar.table import Table
    class MyTable(Table):
        my_col: Text = text()
    class MyModel(BaseModel):
        values: Annotated[list[str], MyTable.my_col.many()]
    ```
    """
    return ManyColumn(self)

Float

Bases: Column[float]

A floating point column type.

Source code in src/embar/column/common.py
159
160
161
162
163
164
165
class Float(Column[float]):
    """
    A floating point column type.
    """

    _sql_type: str = "REAL"
    _py_type: Type = float

Integer

Bases: Column[int]

An integer column type.

Source code in src/embar/column/common.py
150
151
152
153
154
155
156
class Integer(Column[int]):
    """
    An integer column type.
    """

    _sql_type: str = "INTEGER"
    _py_type: Type = int

Null

Bases: Column[T | None]

A nullable column type.

Use this as the annotation for columns that can be NULL in the database. The type parameter T is the underlying Python type (e.g. str, int).

At the class level, Null[str] is a :class:ColumnBase so it works in order_by, where, Annotated, etc. At the instance level, the value is typed as T | None.

Convenience aliases are provided: :data:NullText, :data:NullInteger, :data:NullFloat.

from embar.table import Table
from embar.column.common import Text, NullText, text, integer, NullInteger
class MyTable(Table):
    name: Text = text()
    email: NullText = text(default=None)      # nullable, optional
    age: NullInteger = integer(default=None)   # nullable, optional
row = MyTable(name="foo")
assert row.email is None
Source code in src/embar/column/common.py
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
class Null[T: PyType](Column[T | None]):
    """
    A nullable column type.

    Use this as the annotation for columns that can be ``NULL`` in the database.
    The type parameter ``T`` is the underlying Python type (e.g. ``str``, ``int``).

    At the class level, ``Null[str]`` is a :class:`ColumnBase` so it works in
    ``order_by``, ``where``, ``Annotated``, etc.  At the instance level, the
    value is typed as ``T | None``.

    Convenience aliases are provided: :data:`NullText`, :data:`NullInteger`,
    :data:`NullFloat`.

    ```python
    from embar.table import Table
    from embar.column.common import Text, NullText, text, integer, NullInteger
    class MyTable(Table):
        name: Text = text()
        email: NullText = text(default=None)      # nullable, optional
        age: NullInteger = integer(default=None)   # nullable, optional
    row = MyTable(name="foo")
    assert row.email is None
    ```
    """

    def __init__(
        self,
        sql_type: str,
        py_type: Type,
        name: str | None = None,
        default: T | None | _NoDefaultType = NO_DEFAULT,
        primary: bool = False,
        not_null: bool = False,
    ):
        self._sql_type = sql_type
        self._py_type = py_type
        super().__init__(name=name, default=default, primary=primary, not_null=not_null)

Text

Bases: Column[str]

A text column type.

Source code in src/embar/column/common.py
141
142
143
144
145
146
147
class Text(Column[str]):
    """
    A text column type.
    """

    _sql_type: str = "TEXT"
    _py_type: Type = str

float_col(name=None, *, default=NO_DEFAULT, primary=False, not_null=False, fk=None, on_delete=None)

float_col(
    name: str | None = ...,
    *,
    primary: bool = ...,
    not_null: bool = ...,
    fk: Callable[[], Column[float]] | None = ...,
    on_delete: OnDelete | None = ...,
) -> Float
float_col(
    name: str | None = ...,
    *,
    default: float,
    primary: bool = ...,
    not_null: bool = ...,
    fk: Callable[[], Column[float]] | None = ...,
    on_delete: OnDelete | None = ...,
) -> Float
float_col(
    name: str | None = ...,
    *,
    default: None,
    primary: bool = ...,
    not_null: bool = ...,
    fk: Callable[[], Column[float]] | None = ...,
    on_delete: OnDelete | None = ...,
) -> NullFloat

Create a :class:Float column (field specifier for @dataclass_transform).

Source code in src/embar/column/common.py
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
def float_col(
    name: str | None = None,
    *,
    default: float | None | _NoDefaultType = NO_DEFAULT,
    primary: bool = False,
    not_null: bool = False,
    fk: Callable[[], Column[float]] | None = None,
    on_delete: OnDelete | None = None,
) -> Float | NullFloat:
    """Create a :class:`Float` column (field specifier for ``@dataclass_transform``)."""
    col: Float | Null[float]
    if default is None:
        col = Null[float](
            sql_type="REAL", py_type=float, name=name, default=default, primary=primary, not_null=not_null
        )
    else:
        col = Float(name=name, default=default, primary=primary, not_null=not_null)
    if fk is not None:
        col.fk(fk, on_delete)
    return col

integer(name=None, *, default=NO_DEFAULT, primary=False, not_null=False, fk=None, on_delete=None)

integer(
    name: str | None = ...,
    *,
    primary: bool = ...,
    not_null: bool = ...,
    fk: Callable[[], Column[int]] | None = ...,
    on_delete: OnDelete | None = ...,
) -> Integer
integer(
    name: str | None = ...,
    *,
    default: int,
    primary: bool = ...,
    not_null: bool = ...,
    fk: Callable[[], Column[int]] | None = ...,
    on_delete: OnDelete | None = ...,
) -> Integer
integer(
    name: str | None = ...,
    *,
    default: None,
    primary: bool = ...,
    not_null: bool = ...,
    fk: Callable[[], Column[int]] | None = ...,
    on_delete: OnDelete | None = ...,
) -> NullInteger

Create an :class:Integer column (field specifier for @dataclass_transform).

Source code in src/embar/column/common.py
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
def integer(
    name: str | None = None,
    *,
    default: int | None | _NoDefaultType = NO_DEFAULT,
    primary: bool = False,
    not_null: bool = False,
    fk: Callable[[], Column[int]] | None = None,
    on_delete: OnDelete | None = None,
) -> Integer | NullInteger:
    """Create an :class:`Integer` column (field specifier for ``@dataclass_transform``)."""
    col: Integer | Null[int]
    if default is None:
        col = Null[int](sql_type="INTEGER", py_type=int, name=name, default=default, primary=primary, not_null=not_null)
    else:
        col = Integer(name=name, default=default, primary=primary, not_null=not_null)
    if fk is not None:
        col.fk(fk, on_delete)
    return col

text(name=None, *, default=NO_DEFAULT, primary=False, not_null=False, fk=None, on_delete=None)

text(
    name: str | None = ...,
    *,
    primary: bool = ...,
    not_null: bool = ...,
    fk: Callable[[], Column[str]] | None = ...,
    on_delete: OnDelete | None = ...,
) -> Text
text(
    name: str | None = ...,
    *,
    default: str,
    primary: bool = ...,
    not_null: bool = ...,
    fk: Callable[[], Column[str]] | None = ...,
    on_delete: OnDelete | None = ...,
) -> Text
text(
    name: str | None = ...,
    *,
    default: None,
    primary: bool = ...,
    not_null: bool = ...,
    fk: Callable[[], Column[str]] | None = ...,
    on_delete: OnDelete | None = ...,
) -> NullText

Create a :class:Text column (field specifier for @dataclass_transform).

Source code in src/embar/column/common.py
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
def text(
    name: str | None = None,
    *,
    default: str | None | _NoDefaultType = NO_DEFAULT,
    primary: bool = False,
    not_null: bool = False,
    fk: Callable[[], Column[str]] | None = None,
    on_delete: OnDelete | None = None,
) -> Text | NullText:
    """Create a :class:`Text` column (field specifier for ``@dataclass_transform``)."""
    col: Text | Null[str]
    if default is None:
        col = Null[str](sql_type="TEXT", py_type=str, name=name, default=default, primary=primary, not_null=not_null)
    else:
        col = Text(name=name, default=default, primary=primary, not_null=not_null)
    if fk is not None:
        col.fk(fk, on_delete)
    return col