Skip to content

Commit 8148cb9

Browse files
authored
Reduce memory usage by avoiding issubclass(Schema) (#703)
* Reduce memory usage by avoiding issubclass(Schema) issubclass() calls against Schema triggered ABCMeta.__subclasscheck__ calls which adds to an internal weaksets, _abc_cache, _abc_negative_cache * tiny optimization * Update changelog
1 parent b7da40a commit 8148cb9

File tree

2 files changed

+22
-16
lines changed

2 files changed

+22
-16
lines changed

CHANGELOG.rst

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
Changelog
22
---------
33

4-
unreleased
5-
++++++++++
4+
1.5.0 (unreleased)
5+
++++++++++++++++++
6+
7+
Bug fixes:
8+
9+
* Fix memory usage regression from 1.4.1 (1.4.2 didn't address the root cause) (:issue:`665`).
610

711
Other changes:
812

src/marshmallow_sqlalchemy/schema.py

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
from __future__ import annotations
22

3-
import inspect
43
from typing import TYPE_CHECKING, Any, cast
54

65
import sqlalchemy as sa
76
from marshmallow.fields import Field
8-
from marshmallow.schema import Schema, SchemaMeta, SchemaOpts, _get_fields
7+
from marshmallow.schema import Schema, SchemaMeta, SchemaOpts
98

109
from .convert import ModelConverter
1110
from .exceptions import IncorrectSchemaTypeError
@@ -171,18 +170,21 @@ def _maybe_filter_foreign_keys(
171170
if column.foreign_keys
172171
}
173172

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+
# XXX: Avoid issubclass(base, Schema) because it causes quadratic
175+
# ABCMeta cache growth with many schema classes (#665).
176+
declared_fields: set[str] = set()
177+
for base in klass.__mro__:
178+
if base is object:
179+
break
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())
186188

187189
return [
188190
(name, field)

0 commit comments

Comments
 (0)