mdomke / python-ulid

ULID implementation for Python
https://python-ulid.rtfd.io
MIT License
391 stars 18 forks source link

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

Closed johnpaulett closed 10 months ago

johnpaulett commented 11 months ago

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 commented 10 months ago

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

johnpaulett commented 10 months ago

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

johnpaulett commented 10 months ago

@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 commented 6 months ago

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

johnpaulett commented 6 months ago

@mdomke thanks!

fletcheaston commented 6 months ago

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",
    ),
]