Skip to content

Commit

Permalink
Use @classmethod with @declared_attr
Browse files Browse the repository at this point in the history
  • Loading branch information
jace committed Dec 11, 2023
1 parent eb06c07 commit 3c8d823
Show file tree
Hide file tree
Showing 3 changed files with 18 additions and 4 deletions.
16 changes: 14 additions & 2 deletions src/coaster/sqlalchemy/mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@ class MyModel(BaseMixin[int], Model): # Integer serial primary key; alt: UUID
base classes.
"""

# pylint: disable=too-few-public-methods,no-self-argument

from __future__ import annotations

import typing as t
Expand Down Expand Up @@ -197,6 +195,7 @@ def __init_subclass__(cls, *args: t.Any, **kwargs: t.Any) -> None:

@immutable
@declared_attr
@classmethod
def id(cls) -> Mapped[PkeyType]: # noqa: A003
"""Database identity for this model."""
if cls.__uuid_primary_key__:
Expand All @@ -207,6 +206,7 @@ def id(cls) -> Mapped[PkeyType]: # noqa: A003
return sa.orm.mapped_column(sa.Integer, primary_key=True, nullable=False)

@declared_attr
@classmethod
def url_id(cls) -> Mapped[str]:
"""URL-safe representation of the id value, using hex for a UUID id."""
if cls.__uuid_primary_key__:
Expand Down Expand Up @@ -320,6 +320,7 @@ def _uuid_b58_setter(self, value: str) -> None:
self.uuid = uuid_from_base58(value)

@uuid_b58.inplace.comparator
@classmethod
def _uuid_b58_comparator(cls) -> SqlUuidB58Comparator:
"""Return SQL comparator for UUID in Base58 format."""
return SqlUuidB58Comparator(cls.uuid)
Expand All @@ -338,6 +339,7 @@ class TimestampMixin:

@immutable
@declared_attr
@classmethod
def created_at(cls) -> Mapped[datetime]:
"""Timestamp for when this instance was created, in UTC."""
return sa.orm.mapped_column(
Expand All @@ -347,6 +349,7 @@ def created_at(cls) -> Mapped[datetime]:
)

@declared_attr
@classmethod
def updated_at(cls) -> Mapped[datetime]:
"""Timestamp for when this instance was last updated (via the app), in UTC."""
return sa.orm.mapped_column(
Expand Down Expand Up @@ -702,6 +705,7 @@ class BaseNameMixin(BaseMixin[PkeyType]):
__title_length__: t.ClassVar[t.Optional[int]] = 250

@declared_attr
@classmethod
def name(cls) -> Mapped[str]:
"""Column for URL name of this object, unique across all instances."""
if cls.__name_length__ is None:
Expand All @@ -715,6 +719,7 @@ def name(cls) -> Mapped[str]:
)

@declared_attr
@classmethod
def title(cls) -> Mapped[str]:
"""Column for title of this object."""
if cls.__title_length__ is None:
Expand Down Expand Up @@ -852,6 +857,7 @@ class Event(BaseScopedNameMixin, Model):
parent: t.Any

@declared_attr
@classmethod
def name(cls) -> Mapped[str]:
"""Column for URL name of this object, unique within a parent container."""
if cls.__name_length__ is None:
Expand All @@ -865,6 +871,7 @@ def name(cls) -> Mapped[str]:
)

@declared_attr
@classmethod
def title(cls) -> Mapped[str]:
"""Column for title of this object."""
if cls.__title_length__ is None:
Expand Down Expand Up @@ -1016,6 +1023,7 @@ class BaseIdNameMixin(BaseMixin[PkeyType]):
__title_length__: t.ClassVar[t.Optional[int]] = 250

@declared_attr
@classmethod
def name(cls) -> Mapped[str]:
"""Column for the URL name of this object, non-unique."""
if cls.__name_length__ is None:
Expand All @@ -1029,6 +1037,7 @@ def name(cls) -> Mapped[str]:
)

@declared_attr
@classmethod
def title(cls) -> Mapped[str]:
"""Column for the title of this object."""
if cls.__title_length__ is None:
Expand Down Expand Up @@ -1128,6 +1137,7 @@ class Issue(BaseScopedIdMixin, Model):
# FIXME: Rename this to `scoped_id` and provide a migration guide.
@with_roles(read={'all'})
@declared_attr
@classmethod
def url_id(cls) -> Mapped[int]: # type: ignore[override]
"""Column for an id number that is unique within the parent container."""
return sa.orm.mapped_column(sa.Integer, nullable=False)
Expand Down Expand Up @@ -1211,6 +1221,7 @@ class Event(BaseScopedIdNameMixin, Model):
__title_length__: t.ClassVar[t.Optional[int]] = 250

@declared_attr
@classmethod
def name(cls) -> Mapped[str]:
"""Column for the URL name of this instance, non-unique."""
if cls.__name_length__ is None:
Expand All @@ -1224,6 +1235,7 @@ def name(cls) -> Mapped[str]:
)

@declared_attr
@classmethod
def title(cls) -> Mapped[str]:
"""Column for the title of this instance."""
if cls.__title_length__ is None:
Expand Down
1 change: 1 addition & 0 deletions src/coaster/sqlalchemy/roles.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ def mixed_in1(cls) -> Mapped[str]:
return sa.orm.mapped_column(sa.Unicode(250))
@declared_attr
@classmethod
def mixed_in2(cls) -> Mapped[str]:
return with_roles(sa.orm.mapped_column(sa.Unicode(250)), rw={'owner'})
Expand Down
5 changes: 3 additions & 2 deletions tests/coaster_tests/sqlalchemy_roles_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,24 +43,25 @@
class DeclaredAttrMixin:
"""Provide `declared_attr` attrs paired with `with_roles`."""

# pylint: disable=no-self-argument

# with_roles can be used within a declared attr
@declared_attr
@classmethod
def mixed_in1(cls) -> Mapped[t.Optional[str]]:
"""Test using `with_roles` inside a `declared_attr`."""
return with_roles(sa.orm.mapped_column(sa.Unicode(250)), rw={'owner'})

# This previously used the declared_attr_roles decorator, now deprecated and removed
@with_roles(rw={'owner', 'editor'}, read={'all'})
@declared_attr
@classmethod
def mixed_in2(cls) -> Mapped[t.Optional[str]]:
"""Test (deprecated) using `with_roles` to wrap a `declared_attr`."""
return sa.orm.mapped_column(sa.Unicode(250))

# with_roles can also be used outside a declared attr
@with_roles(rw={'owner'})
@declared_attr
@classmethod
def mixed_in3(cls) -> Mapped[t.Optional[str]]:
"""Test using `with_roles` to wrap a `declared_attr`."""
return sa.orm.mapped_column(sa.Unicode(250))
Expand Down

0 comments on commit 3c8d823

Please sign in to comment.