Create decorator for class to apply dataclass and JsonSchemaMixin ...not working

563 views Asked by At

I did review all the existing SO questions, googled around as best I could, tried a few different options, but can't seem to get what I want working.

Background

Simple problem - I've got a bunch of dataclasses in my code.

I'd like to have them all changed to inherit from the dataclasses-jsonschema JsonSchemaMixin so that I can programmatically generate a schema for them all.

Of course the simple approach is I could change all instances in the code everywhere to inherit from the JsonSchemaMixin mixin.

But I was thinking I could also write my own decorator that would a) apply @dataclass, and b) mixin dataclasses-schema in one go. ...my thinking being less chance of errors, and I could try writing a class decorator (I've only ever created and used my own function decorators)

# OLD

@dataclass
class X:
    ....

# Add the mixin `by hand` everywhere

@dataclass
class X(JsonSchemaMixin):
    x: int
    y: float

# What I want is to make my own decorator to do both:

@dataclass_mixedin
class X:
    x: int
    y: float

Problem

Pretty simple problem - I can't get this to work :(

What I've tried

try number 1:

from dataclasses import dataclass
from dataclasses_jsonschema import JsonSchemaMixin

def dc_schema(cls):
    @dataclass
    class _decorated(JsonSchemaMixin, cls):
        pass
    return _decorated

@dc_schema
class Data:
    x: int
    y: float
        
a = Data(x=5,y=1.1)

The above fails, with unexpected keyword args x & y.

Try number 2:

def dc_schema2(cls):
    # Try applying dataclass() directly:
    class _decorated(JsonSchemaMixin, cls):
        pass
    _decorated = dataclass(_decorated)
    return _decorated

@dc_schema2
class Data2:
    x: int
    y: float

d = Data2(x=1, y=1.1)

Again - this fails with unexpected arguments.

So I'm guessing I'm missing dataclass walking the class structure to find class variables with annotations (https://docs.python.org/3/library/dataclasses.html).

But I can't figure out how to do that

1

There are 1 answers

1
Roberto Prevato On BEST ANSWER

A possible solution is to use a metaclass. See this example:

from dataclasses import dataclass, is_dataclass
from dataclasses_jsonschema import JsonSchemaMixin


class EntityMeta(type):
    def __new__(cls, name, bases, class_dict):
        new_class = super().__new__(cls, name, bases, class_dict)
        return dataclass()(new_class)


class BaseEntity(JsonSchemaMixin, metaclass=EntityMeta):
    pass


class SomeClass(BaseEntity):
    id: str
    name: str


assert is_dataclass(SomeClass)
assert issubclass(SomeClass, JsonSchemaMixin)

x = SomeClass(1, "Hello World")

assert x.id == 1
assert x.name == "Hello World"

class SomeOtherClass(BaseEntity):
    foo: str
    name: str


x = SomeOtherClass("foo", "ufo")
assert x.foo == "foo"
assert x.name == "ufo"

print(SomeOtherClass.json_schema())

I don't recommend this, because you need anyway to inherit from a base class and there is small difference in code verbosity. You lose useful features like type hints when coding with the help of MyPy (unless of writing a dedicated plugin), or Pylance, in an IDE that provides that kind of help.

I recommend to take into consideration pydantic: it might have the features you desire.