Skip to content

Allow type class subclasses#104

Merged
piercefreeman merged 6 commits intomainfrom
feature/type-subclasses
Apr 6, 2026
Merged

Allow type class subclasses#104
piercefreeman merged 6 commits intomainfrom
feature/type-subclasses

Conversation

@piercefreeman
Copy link
Copy Markdown
Owner

@piercefreeman piercefreeman commented Apr 6, 2026

This PR is conceptually simple, but because of the nuances of Python's typing system and class construction, it is actually a bit challenging to implement correctly.

We want to support custom type-hinting subclasses that give static type checkers like mypy, pyright, and ty additional power to flag errors before we hit runtime. One motivating example is foreign-key correctness.

Right now, all ID fields collapse down to plain UUIDs:

from uuid import UUID

class User(TableBase):
    id: UUID

class Organization(TableBase):
    id: UUID

class Membership(TableBase):
    user_id: UUID
    organization_id: UUID

From the type checker’s perspective, these are all interchangeable. That means code like this looks valid:

def assign_membership(
    membership: Membership,
    user_id: UUID,
    organization_id: UUID,
) -> None:
    # no static error today
    membership.user_id = organization_id
    membership.organization_id = user_id

Even though both assignments are obviously wrong at the schema level, static analysis has no way to detect it because everything is just UUID.

This PR allows us to introduce semantically distinct ID types:

from uuid import UUID

class UserId(UUID): ...
class OrganizationId(UUID): ...

class User(TableBase):
    id: UserId

class Organization(TableBase):
    id: OrganizationId

class Membership(TableBase):
    user_id: UserId
    organization_id: OrganizationId

Now the same incorrect code becomes visible to the type checker:

def assign_membership(
    membership: Membership,
    user_id: UserId,
    organization_id: OrganizationId,
) -> None:
    membership.user_id = organization_id
    membership.organization_id = user_id
# Static type errors:
# `OrganizationId` is not assignable to `UserId`
# `UserId` is not assignable to `OrganizationId`

That is the core goal of this PR: preserve the existing runtime behavior while giving static tooling enough structure to understand that not all UUIDs are interchangeable.

@piercefreeman piercefreeman merged commit 45dbb42 into main Apr 6, 2026
23 checks passed
@piercefreeman piercefreeman deleted the feature/type-subclasses branch April 6, 2026 19:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant