|
1 | 1 | from __future__ import annotations |
2 | 2 |
|
3 | | -import inspect |
4 | 3 | from typing import TYPE_CHECKING, Any, cast |
5 | 4 |
|
6 | 5 | import sqlalchemy as sa |
7 | 6 | from marshmallow.fields import Field |
8 | | -from marshmallow.schema import Schema, SchemaMeta, SchemaOpts, _get_fields |
| 7 | +from marshmallow.schema import Schema, SchemaMeta, SchemaOpts |
9 | 8 |
|
10 | 9 | from .convert import ModelConverter |
11 | 10 | from .exceptions import IncorrectSchemaTypeError |
@@ -171,18 +170,21 @@ def _maybe_filter_foreign_keys( |
171 | 170 | if column.foreign_keys |
172 | 171 | } |
173 | 172 |
|
174 | | - non_auto_schema_bases = [ |
175 | | - base |
176 | | - for base in inspect.getmro(klass) |
177 | | - if issubclass(base, Schema) |
178 | | - and not issubclass(base, SQLAlchemyAutoSchema) |
179 | | - ] |
180 | | - |
181 | | - # Pre-compute declared fields only once |
182 | | - declared_fields: set[Any] = set() |
183 | | - for base in non_auto_schema_bases: |
184 | | - base_fields = getattr(base, "_declared_fields", base.__dict__) |
185 | | - declared_fields.update(name for name, _ in _get_fields(base_fields)) |
| 173 | + # Collect fields explicitly declared in non-AutoSchema bases |
| 174 | + declared_fields: set[str] = set() |
| 175 | + for base in klass.__mro__: |
| 176 | + # XXX: Avoid issubclass(Schema) because it causes |
| 177 | + # quadratic ABCMeta cache growth with many schema classes. |
| 178 | + # Instead, check the type of the options class. |
| 179 | + # See https://github.com/marshmallow-code/marshmallow-sqlalchemy/issues/665 |
| 180 | + opts_cls = getattr(base, "OPTIONS_CLASS", None) |
| 181 | + if opts_cls is not None and issubclass( |
| 182 | + opts_cls, SQLAlchemyAutoSchemaOpts |
| 183 | + ): |
| 184 | + continue |
| 185 | + base_declared = base.__dict__.get("_declared_fields") |
| 186 | + if base_declared: |
| 187 | + declared_fields.update(base_declared.keys()) |
186 | 188 |
|
187 | 189 | return [ |
188 | 190 | (name, field) |
|
0 commit comments