diff --git a/docs/models/index.md b/docs/models/index.md index a2650fb01..97f39eb2a 100644 --- a/docs/models/index.md +++ b/docs/models/index.md @@ -300,6 +300,27 @@ explicitly to opt out, or pass a different value to override. issue `CREATE SCHEMA ` (PostgreSQL) or `CREATE DATABASE ` (MySQL) before tables can be created in those schemas. +### Table Comment + +You can attach a SQL `COMMENT ON TABLE` value to a model by passing `comment=` to +`OrmarConfig` (or `OrmarConfig.copy()`). The value is forwarded to the underlying +SQLAlchemy `Table` and emitted by `metadata.create_all()` on dialects that +support table comments (PostgreSQL and MySQL; SQLite ignores it). + +```python +class Book(ormar.Model): + ormar_config = base_ormar_config.copy( + tablename="books", + comment="Library catalogue entries.", + ) + + id: int = ormar.Integer(primary_key=True) + title: str = ormar.String(max_length=200) +``` + +`copy()` inherits the parent config's comment by default; pass `comment=None` +explicitly to opt out, or pass a different value to override. + ### Constraints On a model level you can also set model-wise constraints on sql columns. diff --git a/docs/releases.md b/docs/releases.md index 36db9cc53..58480a08f 100644 --- a/docs/releases.md +++ b/docs/releases.md @@ -2,6 +2,13 @@ ## Unreleased +### ✨ Features + +* Add `OrmarConfig(comment=...)` to attach a SQL `COMMENT ON TABLE` value to a + model — forwarded to the underlying SQLAlchemy `Table` and emitted by + `metadata.create_all()` on dialects that support table comments (PostgreSQL, + MySQL; SQLite ignores it). [#1240](https://github.com/collerek/ormar/issues/1240) + ### 🐛 Fixes * Fix race in `get_or_create` (and the m2m / reverse-fk variant) where diff --git a/ormar/models/helpers/sqlalchemy.py b/ormar/models/helpers/sqlalchemy.py index e2dd9d094..32df6a0cc 100644 --- a/ormar/models/helpers/sqlalchemy.py +++ b/ormar/models/helpers/sqlalchemy.py @@ -358,6 +358,7 @@ def populate_config_sqlalchemy_table_if_required(config: "OrmarConfig") -> None: *config.columns, *config.constraints, schema=config.schema, + comment=config.comment, ) config.table = table diff --git a/ormar/models/ormar_config.py b/ormar/models/ormar_config.py index d9ea814f1..6429090de 100644 --- a/ormar/models/ormar_config.py +++ b/ormar/models/ormar_config.py @@ -22,6 +22,7 @@ class OrmarConfig: engine: AsyncEngine tablename: str schema: Optional[str] + comment: Optional[str] order_by: list[str] abstract: bool proxy: bool @@ -36,6 +37,7 @@ def __init__( engine: Optional[AsyncEngine] = None, tablename: Optional[str] = None, schema: Optional[str] = None, + comment: Optional[str] = None, order_by: Optional[list[str]] = None, abstract: bool = False, proxy: bool = False, @@ -50,6 +52,7 @@ def __init__( self.engine = engine # type: ignore self.tablename = tablename # type: ignore self.schema = schema + self.comment = comment self.orders_by = order_by or [] self.columns: list[sqlalchemy.Column] = [] self.constraints = constraints or [] @@ -74,6 +77,7 @@ def copy( engine: Optional[AsyncEngine] = None, tablename: Optional[str] = None, schema: Union[str, None, PydanticUndefinedType] = PydanticUndefined, + comment: Union[str, None, PydanticUndefinedType] = PydanticUndefined, order_by: Optional[list[str]] = None, abstract: Optional[bool] = None, proxy: Optional[bool] = None, @@ -87,12 +91,16 @@ def copy( resolved_schema = ( self.schema if isinstance(schema, PydanticUndefinedType) else schema ) + resolved_comment = ( + self.comment if isinstance(comment, PydanticUndefinedType) else comment + ) return OrmarConfig( metadata=metadata or self.metadata, database=database or self.database, engine=engine or self.engine, tablename=tablename, schema=resolved_schema, + comment=resolved_comment, order_by=order_by, abstract=abstract or self.abstract, proxy=proxy if proxy is not None else self.proxy, diff --git a/tests/test_model_definition/test_table_comment.py b/tests/test_model_definition/test_table_comment.py new file mode 100644 index 000000000..e25de9906 --- /dev/null +++ b/tests/test_model_definition/test_table_comment.py @@ -0,0 +1,68 @@ +"""Tests for ``OrmarConfig.comment`` forwarding to ``sqlalchemy.Table``.""" + +import ormar +from tests.lifespan import init_tests +from tests.settings import create_config + +base_ormar_config = create_config() + + +class CommentedModel(ormar.Model): + ormar_config = base_ormar_config.copy( + tablename="commented_models", + comment="Stores commented things.", + ) + + id: int = ormar.Integer(primary_key=True) + name: str = ormar.String(max_length=100) + + +class UncommentedModel(ormar.Model): + ormar_config = base_ormar_config.copy(tablename="uncommented_models") + + id: int = ormar.Integer(primary_key=True) + + +create_test_database = init_tests(base_ormar_config) + + +def test_comment_forwarded_to_sqlalchemy_table(): + assert CommentedModel.ormar_config.table.comment == "Stores commented things." + + +def test_comment_defaults_to_none(): + assert UncommentedModel.ormar_config.comment is None + assert UncommentedModel.ormar_config.table.comment is None + + +def test_copy_inherits_parent_comment_when_omitted(): + parent = ormar.OrmarConfig( + metadata=base_ormar_config.metadata, + database=base_ormar_config.database, + engine=base_ormar_config.engine, + comment="parent comment", + ) + child = parent.copy(tablename="child") + assert child.comment == "parent comment" + + +def test_copy_can_explicitly_clear_parent_comment(): + parent = ormar.OrmarConfig( + metadata=base_ormar_config.metadata, + database=base_ormar_config.database, + engine=base_ormar_config.engine, + comment="parent comment", + ) + child = parent.copy(tablename="child", comment=None) + assert child.comment is None + + +def test_copy_can_override_parent_comment(): + parent = ormar.OrmarConfig( + metadata=base_ormar_config.metadata, + database=base_ormar_config.database, + engine=base_ormar_config.engine, + comment="parent comment", + ) + child = parent.copy(tablename="child", comment="child comment") + assert child.comment == "child comment"