Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adjust the classmethods to return the specific class instance, allowing subclassing #11

Merged
merged 1 commit into from
Nov 1, 2023

Conversation

johnpaulett
Copy link
Contributor

To serialize ULID in Pydantic, I've subclassed ulid.ULID

from ulid import ULID as _ULID

class MyULID(_ULID):
    """Wrap ulid.ULID to add Pydantic support."""

    # START Pydantic
    @classmethod
    def __modify_schema__(cls, field_schema: dict[str, Any]) -> None:
        field_schema.update(type="string", example="01F1Z3ZJX9NQW8J9X1ZQXZJY9A")

    @classmethod
    def __get_validators__(cls) -> Generator[Callable[[Any], "_ULID"], None, None]:
        yield cls.validate

    @classmethod
    def validate(cls, v: Any) -> "_ULID":  # TODO Consider returning muse.types.ULID
        if isinstance(v, ULID):
            return v
        elif isinstance(v, _ULID):
            # in parent class, convert into this custom subclass
            return cls.from_bytes(v.bytes)
        elif isinstance(v, UUID):
            return cls.from_uuid(v)
        elif isinstance(v, str):
            return cls.from_str(v)
        elif isinstance(v, bytes):
            return cls.from_bytes(v)
        else:
            raise TypeError("invalid ULID")

Currently, MyULID.from_str("01F1Z3ZJX9NQW8J9X1ZQXZJY9A") will return ULID instead of MyULID.

This PR marks the return of the classmethods as returning an instance of the subclass.

Happy to adjust if we want to try to reuse one of the existing TypeVars or reorganize where they are defined.

@mdomke
Copy link
Owner

mdomke commented Nov 1, 2023

@johnpaulett What do you think about adding native Pydantic support to this library instead?

@johnpaulett
Copy link
Contributor Author

@mdomke not opposed. I think this PR still could be independently valid if others created subclasses of ULID for whatever reason.

@mdomke mdomke merged commit bd9b8d4 into mdomke:main Nov 1, 2023
5 checks passed
@johnpaulett
Copy link
Contributor Author

@mdomke thanks! If you want a PR that adds these three methods into the core python-ulid, I'm happy to do so. Just debatable if you want it in here or not.

@mdomke
Copy link
Owner

mdomke commented Mar 21, 2024

@johnpaulett I ended up adding the Pydantic support directly to this library

@johnpaulett
Copy link
Contributor Author

@mdomke thanks!

@@ -71,7 +74,7 @@ def __init__(self, value: bytes | None = None) -> None:

@classmethod
@validate_type(datetime)
def from_datetime(cls, value: datetime) -> ULID:
def from_datetime(cls: type[U], value: datetime) -> U:
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is the way to do such annotations: https://typing.readthedocs.io/en/latest/spec/generics.html#self

@fletcheaston
Copy link

For those that run into this error...

pydantic_core._pydantic_core.PydanticSerializationError: Unable to serialize unknown type: <class 'ulid.ULID'>

...you'll need to set a Functional Serializer (such as PlainSerializer). You can provide additional information for your FastAPI schema using WithJsonSchema.

from ulid import ULID as _ULID

ULID = Annotated[
    _ULID,
    PlainSerializer(
        lambda x: f"{x}",
        return_type=str,
    ),
    WithJsonSchema(
        {
            "type": "string",
            "examples": ["01F1Z3ZJX9NQW8J9X1ZQXZJY9H"],
        },
        mode="serialization",
    ),
]

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.

4 participants