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

flask-marshmallow - generating Marshmallow schemas from SQLAlchemy models seemingly breaks flasgger #611

Open
snctfd opened this issue Feb 29, 2024 · 1 comment

Comments

@snctfd
Copy link

snctfd commented Feb 29, 2024

I have a SQLAlchemy model from which I derive a flask-marshmallow schema:

class Customer(db.Model):
    __tablename__ = 'customer'

    customer_id: Mapped[int] = mapped_column('pk_customer_id', primary_key=True)

    first_name: Mapped[str] = mapped_column(String(100))
    last_name: Mapped[str] = mapped_column(String(100))
    customer_number: Mapped[int]

class CustomerSchema(ma.SQLAlchemyAutoSchema):
    class Meta:
        model = Customer

I then add the Schema to my definitions with @swag_from:

@customers_bp.route('/', methods=['GET'])
@swag_from({
    'definitions': {
        'Customer': CustomerSchema
    }
})
def get_customers():
    """
    Return a list of customers. 
    ---
    responses:
    200:
        description: A list of customers
        schema:
            type: object
            properties:
                customers:
                    type: array
                    items:
                        $ref: '#/definitions/Customer'
    """
    ...

When I head to /apidocs, I am greeted with the following error:

Traceback (most recent call last):
  File "/usr/local/lib/python3.9/site-packages/flasgger/base.py", line 164, in get
    return jsonify(self.loader())
  File "/usr/local/lib/python3.9/site-packages/flask/json/__init__.py", line 170, in jsonify
    return current_app.json.response(*args, **kwargs)  # type: ignore[return-value]
  File "/usr/local/lib/python3.9/site-packages/flask/json/provider.py", line 214, in response
    f"{self.dumps(obj, **dump_args)}\n", mimetype=self.mimetype
  File "/usr/local/lib/python3.9/site-packages/flask/json/provider.py", line 179, in dumps
    return json.dumps(obj, **kwargs)
  File "/usr/local/lib/python3.9/json/__init__.py", line 234, in dumps
    return cls(
  File "/usr/local/lib/python3.9/json/encoder.py", line 201, in encode
    chunks = list(chunks)
  File "/usr/local/lib/python3.9/json/encoder.py", line 431, in _iterencode
    yield from _iterencode_dict(o, _current_indent_level)
  File "/usr/local/lib/python3.9/json/encoder.py", line 405, in _iterencode_dict
    yield from chunks
  File "/usr/local/lib/python3.9/json/encoder.py", line 405, in _iterencode_dict
    yield from chunks
  File "/usr/local/lib/python3.9/json/encoder.py", line 438, in _iterencode
    o = _default(o)
  File "/usr/local/lib/python3.9/site-packages/flask/json/provider.py", line 121, in _default
    raise TypeError(f"Object of type {type(o).__name__} is not JSON serializable")
TypeError: Object of type SQLAlchemyAutoSchemaMeta is not JSON serializable

Seemingly, the Meta object containing options for schema generation causes problems when attempting to include it in the flasgger config.

@superCoderDOM
Copy link

superCoderDOM commented Mar 7, 2024

We have a similar problem with schemas created from direct imports from flask_marshmallow. The serializer doesn't know what to do with the schema's Meta class:

ERROR:root:jsonify failure; defaulting to json.dumps
Traceback (most recent call last):
  File "/usr/local/lib/python3.9/site-packages/flasgger/base.py", line 164, in get
    return jsonify(self.loader())
  File "/usr/local/lib/python3.9/site-packages/flask/json/__init__.py", line 342, in jsonify
    return current_app.json.response(*args, **kwargs)
  File "/usr/local/lib/python3.9/site-packages/flask/json/provider.py", line 309, in response
    f"{self.dumps(obj, **dump_args)}\n", mimetype=mimetype
  File "/opt/swept/api/swept_api/__init__.py", line 55, in dumps
    return _json.dumps(obj, cls=ExtendedJSONEncoder, **kwargs)
  File "/usr/local/lib/python3.9/json/__init__.py", line 234, in dumps
    return cls(
  File "/usr/local/lib/python3.9/json/encoder.py", line 201, in encode
    chunks = list(chunks)
  File "/usr/local/lib/python3.9/json/encoder.py", line 431, in _iterencode
    yield from _iterencode_dict(o, _current_indent_level)
  File "/usr/local/lib/python3.9/json/encoder.py", line 405, in _iterencode_dict
    yield from chunks
  File "/usr/local/lib/python3.9/json/encoder.py", line 405, in _iterencode_dict
    yield from chunks
  File "/usr/local/lib/python3.9/json/encoder.py", line 438, in _iterencode
    o = _default(o)
  File "/opt/swept/api/swept_api/__init__.py", line 50, in default
    return super().default(o)
  File "/usr/local/lib/python3.9/json/encoder.py", line 179, in default
    raise TypeError(f'Object of type {o.__class__.__name__} '
TypeError: Object of type SchemaMeta is not JSON serializable

Pipfile:

apispec = "*"
flask = "==2.2.0"
flask-marshmallow = "==0.15.0"
flask-sqlalchemy = "==3.0.2"
marshmallow = "==3.9.0"
marshmallow-sqlalchemy = "==0.24.0"
sqlalchemy = "==2.0.0"

As mentioned in #590, downgrading to flasgger = "==0.9.5" resolves the SchemaMeta issue, but instead raises a different error for us:

Traceback (most recent call last):
  File "/usr/local/lib/python3.9/site-packages/flask/app.py", line 1758, in full_dispatch_request
    rv = self.dispatch_request()
  File "/usr/local/lib/python3.9/site-packages/flask/app.py", line 1734, in dispatch_request
    return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)
  File "/usr/local/lib/python3.9/site-packages/flask/views.py", line 107, in view
    return current_app.ensure_sync(self.dispatch_request)(**kwargs)
  File "/usr/local/lib/python3.9/site-packages/flask/views.py", line 188, in dispatch_request
    return current_app.ensure_sync(meth)(**kwargs)
  File "/usr/local/lib/python3.9/site-packages/flasgger/base.py", line 133, in get
    return jsonify(self.loader())
  File "/usr/local/lib/python3.9/site-packages/flasgger/base.py", line 399, in get_apispecs
    specs = get_specs(
  File "/usr/local/lib/python3.9/site-packages/flasgger/utils.py", line 134, in get_specs
    convert_schemas(apispec_swag, apispec_definitions)
  File "/usr/local/lib/python3.9/site-packages/flasgger/marshmallow_apispec.py", line 109, in convert_schemas
    v = convert_schemas(v, definitions)
  File "/usr/local/lib/python3.9/site-packages/flasgger/marshmallow_apispec.py", line 123, in convert_schemas
    definitions[v.__name__] = schema2jsonschema(v)
  File "/usr/local/lib/python3.9/site-packages/apispec/ext/marshmallow/openapi.py", line 256, in schema2jsonschema
    jsonschema = self.fields2jsonschema(fields, partial=partial)
  File "/usr/local/lib/python3.9/site-packages/apispec/ext/marshmallow/openapi.py", line 281, in fields2jsonschema
    prop = self.field2property(field_obj)
  File "/usr/local/lib/python3.9/site-packages/apispec/ext/marshmallow/field_converter.py", line 174, in field2property
    ret.update(attr_func(field, ret=ret))
  File "/usr/local/lib/python3.9/site-packages/apispec/ext/marshmallow/field_converter.py", line 224, in field2default
    default = field.load_default
AttributeError: 'String' object has no attribute 'load_default'

The only schema currently attached to a view is extremely simple:

from flasgger import Schema, fields


class InviteUserSchema(Schema):
    class Meta:
        strict = True

    message = fields.Str(required=True)
    subject = fields.Str(required=True)

In the view extended from SwaggerView, we pass the schema to definitions. If this line is commented out, the docs load without the schema definition, otherwise they raise the error above:

definitions = {"InviteUserSchema": InviteUserSchema}

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

No branches or pull requests

2 participants