Skip to content

Commit

Permalink
Resolve typing errors in membership mixin
Browse files Browse the repository at this point in the history
  • Loading branch information
jace committed Jan 3, 2024
1 parent 98e037d commit 03b3c42
Show file tree
Hide file tree
Showing 4 changed files with 35 additions and 35 deletions.
50 changes: 25 additions & 25 deletions funnel/models/membership_mixin.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

from __future__ import annotations

from collections.abc import Callable, Iterable
from collections.abc import Iterable
from datetime import datetime as datetime_type
from enum import ReprEnum
from types import SimpleNamespace
from typing import TYPE_CHECKING, Any, ClassVar, Generic, Self, TypeVar
from typing import TYPE_CHECKING, Any, ClassVar, Generic, Protocol, Self, TypeVar
from uuid import UUID

from sqlalchemy import event
Expand Down Expand Up @@ -44,9 +44,18 @@
# --- Typing ---------------------------------------------------------------------------

MembershipType = TypeVar('MembershipType', bound='ImmutableMembershipMixin')
FrozenAttributionType = TypeVar(
'FrozenAttributionType', bound='FrozenAttributionProtoMixin'
)


class MembershipMixinProtocol(Protocol):
_title: declared_attr[str | None]
member: declared_attr[Account]
_local_data_only: bool

def replace(self, actor: Account, **data: Any) -> Self:
...


MembershipMixinType = TypeVar('MembershipMixinType', bound=MembershipMixinProtocol)

# --- Enum -----------------------------------------------------------------------------

Expand Down Expand Up @@ -248,14 +257,12 @@ def revoke(self, actor: Account) -> None:
self.revoked_at = sa.func.utcnow()
self.revoked_by = actor

def copy_template(self: MembershipType, **kwargs) -> MembershipType:
def copy_template(self, **kwargs) -> Self:
"""Make a copy of self for customization."""
raise NotImplementedError("Subclasses must implement copy_template")

@with_roles(call={'editor'})
def replace(
self: MembershipType, actor: Account, _accept: bool = False, **data: Any
) -> MembershipType:
def replace(self, actor: Account, _accept: bool = False, **data: Any) -> Self:
"""Replace this membership record with changes to role columns."""
if self.revoked_at is not None:
raise MembershipRevokedError(
Expand Down Expand Up @@ -315,9 +322,7 @@ def amend_by(self, actor: Account):
"""Amend a membership in a `with` context."""
return AmendMembership(self, actor)

def merge_and_replace(
self: MembershipType, actor: Account, other: MembershipType
) -> MembershipType:
def merge_and_replace(self, actor: Account, other: Self) -> Self:
"""Replace this record by merging data from an independent record."""
if self.__class__ is not other.__class__:
raise TypeError("Merger requires membership records of the same type")
Expand Down Expand Up @@ -353,7 +358,7 @@ def merge_and_replace(
return replacement

@with_roles(call={'member'})
def accept(self: MembershipType, actor: Account) -> MembershipType:
def accept(self, actor: Account) -> Self:
"""Accept a membership invitation."""
if self.record_type != MembershipRecordTypeEnum.INVITE:
raise MembershipRecordTypeError("This membership record is not an invite")
Expand All @@ -362,9 +367,7 @@ def accept(self: MembershipType, actor: Account) -> MembershipType:
return self.replace(actor, _accept=True)

@with_roles(call={'owner', 'member'})
def freeze_member_attribution(
self: MembershipType, actor: Account
) -> MembershipType:
def freeze_member_attribution(self, actor: Account) -> Self:
"""
Freeze member attribution and return a replacement record.
Expand Down Expand Up @@ -577,14 +580,9 @@ def parent_scoped_reorder_query_filter(self) -> ColumnElement:


@declarative_mixin
class FrozenAttributionProtoMixin:
class FrozenAttributionMixin:
"""Provides a `title` data column and support method to freeze it."""

if TYPE_CHECKING:
member: Mapped[Account]
replace: Callable[..., Self]
_local_data_only: bool

@declared_attr
@classmethod
def _title(cls) -> Mapped[str | None]:
Expand All @@ -596,7 +594,7 @@ def _title(cls) -> Mapped[str | None]:
)

@property
def title(self) -> str:
def title(self: MembershipMixinProtocol) -> str:
"""Attribution title for this record."""
if self._local_data_only:
# self._title may be None when returning local data
Expand All @@ -611,12 +609,14 @@ def title(self, value: str | None) -> None:
self._title = value or None # Don't set empty string

@property
def pickername(self) -> str:
def pickername(self: MembershipMixinProtocol) -> str:
"""Return member's pickername, but only if attribution isn't frozen."""
return self._title if self._title else self.member.pickername

@with_roles(call={'owner', 'member'})
def freeze_member_attribution(self, actor: Account) -> Self:
def freeze_member_attribution(
self: MembershipMixinType, actor: Account
) -> MembershipMixinType:
"""Freeze member attribution and return a replacement record."""
if self._title is None:
membership = self.replace(actor=actor, title=self.member.title)
Expand Down
2 changes: 1 addition & 1 deletion funnel/models/proposal.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ class Proposal(UuidMixin, BaseScopedIdNameMixin, VideoMixin, ReorderProtoMixin,
lazy='dynamic',
primaryjoin=lambda: sa.and_(
ProposalSponsorMembership.proposal_id == Proposal.id,
ProposalSponsorMembership.is_active,
ProposalSponsorMembership.is_active, # type: ignore[has-type] # FIXME
),
order_by=lambda: ProposalSponsorMembership.seq,
viewonly=True,
Expand Down
6 changes: 3 additions & 3 deletions funnel/models/proposal_membership.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

from . import Mapped, Model, relationship, sa, sa_orm
from .membership_mixin import (
FrozenAttributionProtoMixin,
FrozenAttributionMixin,
ImmutableUserMembershipMixin,
ReorderMembershipProtoMixin,
)
Expand All @@ -19,9 +19,9 @@
__all__ = ['ProposalMembership']


class ProposalMembership( # type: ignore[misc]
class ProposalMembership( # type: ignore[misc] # FIXME
FrozenAttributionMixin,
ImmutableUserMembershipMixin,
FrozenAttributionProtoMixin,
ReorderMembershipProtoMixin,
Model,
):
Expand Down
12 changes: 6 additions & 6 deletions funnel/models/sponsor_membership.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

from . import Mapped, Model, relationship, sa, sa_orm
from .membership_mixin import (
FrozenAttributionProtoMixin,
FrozenAttributionMixin,
ImmutableUserMembershipMixin,
ReorderMembershipProtoMixin,
)
Expand All @@ -20,9 +20,9 @@
__all__ = ['ProjectSponsorMembership', 'ProposalSponsorMembership']


class ProjectSponsorMembership( # type: ignore[misc]
class ProjectSponsorMembership( # type: ignore[misc] # FIXME
FrozenAttributionMixin,
ImmutableUserMembershipMixin,
FrozenAttributionProtoMixin,
ReorderMembershipProtoMixin,
Model,
):
Expand Down Expand Up @@ -116,10 +116,10 @@ def offered_roles(self) -> set[str]:

# FIXME: Replace this with existing proposal collaborator as they're now both related
# to "account"
class ProposalSponsorMembership( # type: ignore[misc]
FrozenAttributionProtoMixin,
ReorderMembershipProtoMixin,
class ProposalSponsorMembership( # type: ignore[misc] # FIXME
FrozenAttributionMixin,
ImmutableUserMembershipMixin,
ReorderMembershipProtoMixin,
Model,
):
"""Sponsor of a proposal."""
Expand Down

0 comments on commit 03b3c42

Please sign in to comment.