Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
ca4c4a2
Add support for composite unique constraint
atkei Apr 8, 2024
07f8b94
Fix code format
atkei Apr 12, 2024
e4a5209
Add tests
atkei Apr 18, 2024
dc539bf
Merge branch 'master' into composite-unique-constraint-2
atkei Oct 10, 2024
012c608
Add comments
atkei Oct 19, 2024
c7ce718
Merge branch 'master' into composite-constraints
dantownsend Jan 24, 2025
fb02cd5
`add_constraints` method
dantownsend Jan 24, 2025
074e273
change module name from `contraint` to `constraints`
dantownsend Jan 24, 2025
a953b99
refactor -> `add_unique_constraint`
dantownsend Jan 25, 2025
f62205f
update docs
dantownsend Jan 25, 2025
4e1b8e5
add check constraints
dantownsend Jan 25, 2025
e92aca2
make sure `CheckConstraint` works in migrations
dantownsend Jan 25, 2025
3f5d6ea
fix typo in check condition ddl statement
dantownsend Jan 25, 2025
acd341d
remove unused import
dantownsend Jan 25, 2025
5e7e201
improve docstring
dantownsend Jan 25, 2025
fae32e4
refactor to use `constraints` list on `Table`
dantownsend Jan 26, 2025
f369b81
update the docs
dantownsend Jan 26, 2025
0c6d0cf
improve logic for getting `Table.constraints`
dantownsend Jan 26, 2025
400046f
try lambda
dantownsend Jan 30, 2025
7bc9a2e
strings
dantownsend Jan 31, 2025
388e89f
Revert "strings"
dantownsend Jan 31, 2025
83dad23
added `querystring_for_constraint`
dantownsend Jan 31, 2025
dc56104
stop using `constraints` list
dantownsend Feb 1, 2025
b3039d5
update `_table_str`
dantownsend Feb 1, 2025
0963b58
update tests
dantownsend Feb 1, 2025
cc107b9
update tests
dantownsend Feb 1, 2025
423c40c
fix linter errors
dantownsend Feb 1, 2025
7995249
update docstrings
dantownsend Feb 1, 2025
1b9e178
Update constraints.rst
dantownsend Feb 1, 2025
b712fb1
add docs for using constraints with mixins
dantownsend Feb 1, 2025
f49edf0
make sure dates can be used in check constraints
dantownsend Feb 1, 2025
5e86cc0
let check constraints reference other columns
dantownsend Feb 12, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 28 additions & 4 deletions docs/src/piccolo/schema/advanced.rst
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,8 @@ for example with :ref:`table_finder <TableFinder>`.

-------------------------------------------------------------------------------

.. _Mixins:

Mixins
------

Expand All @@ -127,15 +129,37 @@ use mixins to reduce the amount of repetition.

.. code-block:: python

from piccolo.columns import Varchar, Boolean
from piccolo.columns import Date, Varchar
from piccolo.table import Table


class FavouriteMixin:
favourite = Boolean(default=False)
class DateOfBirthMixin:
date_of_birth = Date()


class Manager(DateOfBirthMixin, Table):
name = Varchar()

You can also add :ref:`constraints <AdvancedConstraints>` to your mixin classes.

.. code-block:: python

import datetime

from piccolo.columns import Varchar, Date
from piccolo.constraints import Check
from piccolo.table import Table


class DateOfBirthMixin:
date_of_birth = Date()

min_date_of_birth = Check(
date_of_birth >= datetime.date(year=1920, month=1, day=1)
)


class Manager(FavouriteMixin, Table):
class Manager(DateOfBirthMixin, Table):
name = Varchar()

-------------------------------------------------------------------------------
Expand Down
50 changes: 50 additions & 0 deletions docs/src/piccolo/schema/constraints.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
===========
Constraints
===========

Simple unique constraints
=========================

Unique constraints can be added to a single column using the ``unique=True``
argument of ``Column``:

.. code-block:: python

class Band(Table):
name = Varchar(unique=True)

-------------------------------------------------------------------------------

.. _AdvancedConstraints:

Advanced constraints
=====================

You can add you can implement powerful ``UNIQUE`` and ``CHECK`` constraints
on your ``Table``.

``Unique``
----------

.. currentmodule:: piccolo.constraints

.. autoclass:: Unique

``Check``
----------

.. autoclass:: Check

How are they created?
---------------------

If creating a new table using ``await MyTable.create_table()``, then the
constraints will also be created.

Also, if using auto migrations, they handle the creation and deletion of these
constraints for you.

Mixins
------

Constraints can be added to :ref:`mixin classes <Mixins>` for reusability.
1 change: 1 addition & 0 deletions docs/src/piccolo/schema/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ The schema is how you define your database tables, columns and relationships.

./defining
./column_types
./constraints
./m2m
./one_to_one
./advanced
73 changes: 70 additions & 3 deletions piccolo/apps/migrations/auto/diffable_table.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,17 @@

from piccolo.apps.migrations.auto.operations import (
AddColumn,
AddConstraint,
AlterColumn,
DropColumn,
DropConstraint,
)
from piccolo.apps.migrations.auto.serialisation import (
deserialise_params,
serialise_params,
)
from piccolo.columns.base import Column
from piccolo.constraints import Constraint
from piccolo.table import Table, create_table_class


Expand Down Expand Up @@ -62,6 +65,8 @@ class TableDelta:
add_columns: t.List[AddColumn] = field(default_factory=list)
drop_columns: t.List[DropColumn] = field(default_factory=list)
alter_columns: t.List[AlterColumn] = field(default_factory=list)
add_constraints: t.List[AddConstraint] = field(default_factory=list)
drop_constraints: t.List[DropConstraint] = field(default_factory=list)

def __eq__(self, value: TableDelta) -> bool: # type: ignore
"""
Expand Down Expand Up @@ -92,6 +97,19 @@ def __eq__(self, value) -> bool:
return False


@dataclass
class ConstraintComparison:
constraint: Constraint

def __hash__(self) -> int:
return self.constraint.__hash__()

def __eq__(self, value) -> bool:
if isinstance(value, ConstraintComparison):
return self.constraint._meta.name == value.constraint._meta.name
return False


@dataclass
class DiffableTable:
"""
Expand All @@ -103,6 +121,7 @@ class DiffableTable:
tablename: str
schema: t.Optional[str] = None
columns: t.List[Column] = field(default_factory=list)
constraints: t.List[Constraint] = field(default_factory=list)
previous_class_name: t.Optional[str] = None

def __post_init__(self) -> None:
Expand Down Expand Up @@ -196,10 +215,54 @@ def __sub__(self, value: DiffableTable) -> TableDelta:
)
)

add_constraints = [
AddConstraint(
table_class_name=self.class_name,
constraint_name=i.constraint._meta.name,
constraint_class_name=i.constraint.__class__.__name__,
constraint_class=i.constraint.__class__,
params=i.constraint._meta.params,
schema=self.schema,
)
for i in sorted(
{
ConstraintComparison(constraint=constraint)
for constraint in self.constraints
}
- {
ConstraintComparison(constraint=constraint)
for constraint in value.constraints
},
key=lambda x: x.constraint._meta.name,
)
]

drop_constraints = [
DropConstraint(
table_class_name=self.class_name,
constraint_name=i.constraint._meta.name,
tablename=value.tablename,
schema=self.schema,
)
for i in sorted(
{
ConstraintComparison(constraint=constraint)
for constraint in value.constraints
}
- {
ConstraintComparison(constraint=constraint)
for constraint in self.constraints
},
key=lambda x: x.constraint._meta.name,
)
]

return TableDelta(
add_columns=add_columns,
drop_columns=drop_columns,
alter_columns=alter_columns,
add_constraints=add_constraints,
drop_constraints=drop_constraints,
)

def __hash__(self) -> int:
Expand All @@ -225,10 +288,14 @@ def to_table_class(self) -> t.Type[Table]:
"""
Converts the DiffableTable into a Table subclass.
"""
class_members: t.Dict[str, t.Any] = {}
for column in self.columns:
class_members[column._meta.name] = column
for constraint in self.constraints:
class_members[constraint._meta.name] = constraint

return create_table_class(
class_name=self.class_name,
class_kwargs={"tablename": self.tablename, "schema": self.schema},
class_members={
column._meta.name: column for column in self.columns
},
class_members=class_members,
)
Loading
Loading