Skip to content

MarshmallowPlugin force to use same 'schema class name' as 'schema name in documentation'. #605

@jitka

Description

@jitka

You got marshmallow.exceptions.RegistryError when you try to use different names.

rom sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String, create_engine
from marshmallow_sqlalchemy import SQLAlchemyAutoSchema
from apispec import APISpec
from apispec.ext.marshmallow import MarshmallowPlugin

BaseModel = declarative_base()
class Employee(BaseModel):
    __tablename__ = "employee"

    id = Column(Integer, primary_key=True, autoincrement=True)
    name = Column(String(50), nullable=False)
    age = Column(Integer)

class EmployeeSchemaCreate(SQLAlchemyAutoSchema):
    class Meta:
        model = Employee
        fields = ["name", "age"]
        load_instance = True

db_engine = create_engine("sqlite:///:memory:")
Employee.__table__.create(bind=db_engine, checkfirst=True)
        
spec = APISpec(
    title="Example api",
    version="0.1.0",
    openapi_version="3.0.0",
    plugins=[MarshmallowPlugin(),],
    #plugins=[],
)
spec.components.schema('EmployeeSchemaIn', schema=EmployeeSchemaCreate)
EmployeeSchemaCreate
spec.path(
    path='/empleyee',
    operations=dict(
        post={
            "summary": "Post employee",
            "parameters": [{"in": "body", "name": "id", "required": True, "schema": 'EmployeeSchemaIn'}],
            "responses": {
                "201": {
                    "description": "Employee accepted"
                    },
                "400": {"description": "Validation error"}
                }
            }
    ),
)
print(spec.to_yaml())
Traceback (most recent call last):
  File "/home/jitka/.env/falcon-auto-cru/lib64/python3.8/site-packages/marshmallow/class_registry.py", line 76, in get_class
    classes = _registry[classname]
KeyError: 'EmployeeSchemaIn'

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "falcon_auto_cru/pokus.py", line 34, in <module>
    spec.path(
  File "/home/jitka/.env/falcon-auto-cru/lib64/python3.8/site-packages/apispec/core.py", line 280, in path
    plugin.operation_helper(path=path, operations=operations, **kwargs)
  File "/home/jitka/.env/falcon-auto-cru/lib64/python3.8/site-packages/apispec/ext/marshmallow/__init__.py", line 194, in operation_helper
    operation["parameters"] = self.resolver.resolve_parameters(
  File "/home/jitka/.env/falcon-auto-cru/lib64/python3.8/site-packages/apispec/ext/marshmallow/schema_resolver.py", line 77, in resolve_parameters
    schema_instance = resolve_schema_instance(parameter.pop("schema"))
  File "/home/jitka/.env/falcon-auto-cru/lib64/python3.8/site-packages/apispec/ext/marshmallow/common.py", line 23, in resolve_schema_instance
    return marshmallow.class_registry.get_class(schema)()
  File "/home/jitka/.env/falcon-auto-cru/lib64/python3.8/site-packages/marshmallow/class_registry.py", line 78, in get_class
    raise RegistryError(
marshmallow.exceptions.RegistryError: Class with name 'EmployeeSchemaIn' was not found. You may need to import the class.

When you do not use Marshmallow plugin, domuntation is created with correct name but schemas are missing:

from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String, create_engine
from marshmallow_sqlalchemy import SQLAlchemyAutoSchema
from apispec import APISpec
from apispec.ext.marshmallow import MarshmallowPlugin

BaseModel = declarative_base()
class Employee(BaseModel):
    __tablename__ = "employee"

    id = Column(Integer, primary_key=True, autoincrement=True)
    name = Column(String(50), nullable=False)
    age = Column(Integer)

class EmployeeSchemaCreate(SQLAlchemyAutoSchema):
    class Meta:
        model = Employee
        fields = ["name", "age"]
        load_instance = True

db_engine = create_engine("sqlite:///:memory:")
Employee.__table__.create(bind=db_engine, checkfirst=True)
        
spec = APISpec(
    title="Example api",
    version="0.1.0",
    openapi_version="3.0.0",
    #plugins=[MarshmallowPlugin(),],
    plugins=[],
)
spec.components.schema('EmployeeSchemaIn', schema=EmployeeSchemaCreate)
EmployeeSchemaCreate
spec.path(
    path='/empleyee',
    operations=dict(
        post={
            "summary": "Post employee",
            #"parameters": [{"in": "body", "name": "id", "required": True, "schema": {}}],
            "parameters": [{"in": "body", "name": "id", "required": True, "schema": 'EmployeeSchemaIn'}],
            "responses": {
                "201": {
                    "description": "Employee accepted"
                    },
                "400": {"description": "Validation error"}
                }
            }
    ),
)
print(spec.to_yaml())
components:
  schemas:
    EmployeeSchemaIn: {}
info:
  title: Example api
  version: 0.1.0
openapi: 3.0.0
paths:
  /empleyee:
    post:
      parameters:
      - in: body
        name: id
        required: true
        schema: EmployeeSchemaIn
      responses:
        '201':
          description: Employee accepted
        '400':
          description: Validation error
      summary: Post employee

Finnaly when you use same names, documentation is generated, but you loose freedom od of names.

from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String, create_engine
from marshmallow_sqlalchemy import SQLAlchemyAutoSchema
from apispec import APISpec
from apispec.ext.marshmallow import MarshmallowPlugin

BaseModel = declarative_base()
class Employee(BaseModel):
    __tablename__ = "employee"

    id = Column(Integer, primary_key=True, autoincrement=True)
    name = Column(String(50), nullable=False)
    age = Column(Integer)

class EmployeeSchemaCreate(SQLAlchemyAutoSchema):
    class Meta:
        model = Employee
        fields = ["name", "age"]
        load_instance = True

db_engine = create_engine("sqlite:///:memory:")
Employee.__table__.create(bind=db_engine, checkfirst=True)
        
spec = APISpec(
    title="Example api",
    version="0.1.0",
    openapi_version="3.0.0",
    plugins=[MarshmallowPlugin(),],
    #plugins=[],
)
#spec.components.schema('EmployeeSchemaIn', schema=EmployeeSchemaCreate)
spec.components.schema('EmployeeSchemaCreate', schema=EmployeeSchemaCreate)
EmployeeSchemaCreate
spec.path(
    path='/empleyee',
    operations=dict(
        post={
            "summary": "Post employee",
            #"parameters": [{"in": "body", "name": "id", "required": True, "schema": 'EmployeeSchemaIn'}],
            "parameters": [{"in": "body", "name": "id", "required": True, "schema": 'EmployeeSchemaCreate'}],
            "responses": {
                "201": {
                    "description": "Employee accepted"
                    },
                "400": {"description": "Validation error"}
                }
            }
    ),
)
print(spec.to_yaml())
components:
  schemas:
    EmployeeSchemaCreate:
      properties:
        age:
          nullable: true
          type: integer
        name:
          maxLength: 50
          type: string
      required:
      - name
      type: object
info:
  title: Example api
  version: 0.1.0
openapi: 3.0.0
paths:
  /empleyee:
    post:
      parameters:
      - in: body
        name: id
        required: true
        schema:
          $ref: '#/components/schemas/EmployeeSchemaCreate'
      responses:
        '201':
          description: Employee accepted
        '400':
          description: Validation error
      summary: Post employee

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions