Skip to content

Conversation

@nightsailer
Copy link

Expose and Enable Inheritance of StructMeta Metaclass

Changes

  • Exposed StructMeta metaclass to Python for direct use
  • Added Py_TPFLAGS_BASETYPE flag to enable Python inheritance of StructMeta
  • Created tests to verify functionality

Why

This allows users to:

  • Use StructMeta directly without subclassing Struct
  • Create custom metaclasses that extend StructMeta behavior
  • Implement reusable patterns across multiple struct classes

This PR resolves:

Example

from msgspec import StructMeta

# Custom metaclass with kw_only_default parameter
class KwOnlyMeta(StructMeta):
    _kw_only_default_settings = {}
    
    def __new__(mcls, name, bases, namespace, **kwargs):
        # Store and inherit kw_only_default
        kw_only_default = kwargs.pop('kw_only_default', None)
        if kw_only_default is None:
            for base in bases:
                if base.__name__ in mcls._kw_only_default_settings:
                    kw_only_default = mcls._kw_only_default_settings[base.__name__]
                    break
        else:
            mcls._kw_only_default_settings[name] = kw_only_default
            
        # Apply kw_only_default if kw_only not specified
        if 'kw_only' not in kwargs and kw_only_default is not None:
            kwargs['kw_only'] = kw_only_default
            
        return super().__new__(mcls, name, bases, namespace, **kwargs)

# Base model requiring keyword arguments
class BaseModel(metaclass=KwOnlyMeta, kw_only_default=True):
    pass

# Child class inherits kw_only=True
class User(BaseModel):
    name: str
    age: int

# Must use keyword arguments
user = User(name="John", age=30)

Technical Details

Added StructMeta to module exports in _core.c
Added Py_TPFLAGS_BASETYPE flag to StructMetaType
Updated init.py to import and expose StructMeta
Added type hints in init.pyi

Compatibility

These changes are backward compatible and only add new capabilities without modifying existing behavior.

Related Issues

@Vizonex
Copy link

Vizonex commented May 4, 2025

@nightsailer I sure hope this passes you don't know how annoying it has been to try and turn Msgspec into an Object Relational Mapping Library. If this passes it would likely remedy my recent problems #820 and possibly allow StructMeta and any SQLAlchemy Type Metaclass object to be theoretically mergeable similar to how SQLModel works.

@Vizonex
Copy link

Vizonex commented May 4, 2025

Also I ran your test on my computer and it is working correctly.

@apoorvkh
Copy link

apoorvkh commented May 5, 2025

Ah, hey @nightsailer, seems like we were working in parallel! See my PR #844. [You were first, I did not re-check for open PRs :)]

Our changes are quite similar, except that

  • you also updated the equalities (Py_TYPE(type) == &StructMetaType) to (PyType_IsSubtype(Py_TYPE(type), &StructMetaType))
  • You added metaclass=StructMeta to the Struct class in __init__.pyi
  • I added a stub for StructMeta in __init__.pyi

May I make one commit to your PR to update the stub for StructMeta to this one?

@nightsailer
Copy link
Author

Ah, hey @nightsailer, seems like we were working in parallel! See my PR #844. [You were first, I did not re-check for open PRs :)]

Our changes are quite similar, except that

  • you also updated the equalities (Py_TYPE(type) == &StructMetaType) to (PyType_IsSubtype(Py_TYPE(type), &StructMetaType))

May I make one commit to your PR to update the stub for StructMeta to this one?

Sure, absolutely! Please feel free to add that commit to update the stub. Thanks for offering!

By the way, the reason for updating the check from (Py_TYPE(type) == &StructMetaType) to (PyType_IsSubtype(Py_TYPE(type), &StructMetaType)) is that without the subtype check, functions like asdict and other encoding logic would fail to identify objects that are instances of classes derived from StructMetaType.

@apoorvkh
Copy link

apoorvkh commented May 6, 2025

Ok that makes sense, thanks for clarifying!

Please merge this PR for the change we discussed: nightsailer#1

Added `StructMeta` stub to `__init__.pyi`
@makarr
Copy link

makarr commented May 21, 2025

@nightsailer I sure hope this passes you don't know how annoying it has been to try and turn Msgspec into an Object Relational Mapping Library. If this passes it would likely remedy my recent problems #820 and possibly allow StructMeta and any SQLAlchemy Type Metaclass object to be theoretically mergeable similar to how SQLModel works.

Seconded. I am working on an ORM and want to set msgspec.UNSET to everything by default in the metaclass, so that I can have quick and dirty DTOs on the cheap. But currently it is verbose and annoying having to manually annotate every model with ... | UnsetType = UNSET. Class decorators don't work either, since __struct_defaults__ is readonly.

@apoorvkh
Copy link

@jcrist I think there's a large amount of interest in this PR! It would be really wonderful if you could take a look and consider merging. Thanks so much.

@Vizonex
Copy link

Vizonex commented May 21, 2025

@jcrist I think there's a large amount of interest in this PR! It would be really wonderful if you could take a look and consider merging. Thanks so much.

I'm interested because it means I would be able to add msgspec to a few libraries such as a few that I currently maintain such as aiocallback and a few others.

@nightsailer
Copy link
Author

I have fork a new package: msgspec-x, this pr was conflict, close now.

@apoorvkh
Copy link

Hey @nightsailer, while you've made a new package with this feature, I feel like the existing package has many pre-existing users and also deserves to have this feature. Would you consider re-opening this PR? Thanks.

@jacopoabramo
Copy link

@nightsailer given that there seems to be a transition to new mantainers, could you open a new PR with the original changes so that these can be merged into msgspec?

@ofek
Copy link
Collaborator

ofek commented Oct 20, 2025

I'd be eager to merge this if you or someone else wouldn't mind opening another PR 😄

@apoorvkh
Copy link

That would be great, thanks a lot for taking over maintenance for this project @ofek!

Since I was involved in this effort (per #837, #843), I'm happy to re-open the PR.

Please see #890 for that! This corresponds to the current PR, prior to the msgspec-x rebranding.

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.

6 participants