summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy
diff options
context:
space:
mode:
authorVáclav Klusák <vaclav.klusak@maptiler.com>2020-08-17 11:58:56 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2020-09-01 10:59:07 -0400
commitf806491fca4b08623d7fcffc375bd5cbe3790e5f (patch)
tree09d3eb22bc2e179ab4caf4e8e4caa59bfab46928 /lib/sqlalchemy
parented5e015ef48ab7ce0950c0eac69b7aa4ad256ca5 (diff)
downloadsqlalchemy-f806491fca4b08623d7fcffc375bd5cbe3790e5f.tar.gz
Add support for classical mapping of dataclasses
Added support for direct mapping of Python classes that are defined using the Python ``dataclasses`` decorator. See the section :ref:`mapping_dataclasses` for background. Pull request courtesy Václav Klusák. Fixes: #5027 Closes: #5516 Pull-request: https://github.com/sqlalchemy/sqlalchemy/pull/5516 Pull-request-sha: bb48c63d1561ca48c954ad9f84a3eb2646571115 Change-Id: Ie33db2aae4adeeb5d99633fe926b9c30bab0b885
Diffstat (limited to 'lib/sqlalchemy')
-rw-r--r--lib/sqlalchemy/orm/descriptor_props.py2
-rw-r--r--lib/sqlalchemy/orm/mapper.py24
-rw-r--r--lib/sqlalchemy/testing/requirements.py4
3 files changed, 25 insertions, 5 deletions
diff --git a/lib/sqlalchemy/orm/descriptor_props.py b/lib/sqlalchemy/orm/descriptor_props.py
index 39cf86e34..c2efa24a1 100644
--- a/lib/sqlalchemy/orm/descriptor_props.py
+++ b/lib/sqlalchemy/orm/descriptor_props.py
@@ -56,7 +56,7 @@ class DescriptorProperty(MapperProperty):
if self.descriptor is None:
desc = getattr(mapper.class_, self.key, None)
- if mapper._is_userland_descriptor(desc):
+ if mapper._is_userland_descriptor(self.key, desc):
self.descriptor = desc
if self.descriptor is None:
diff --git a/lib/sqlalchemy/orm/mapper.py b/lib/sqlalchemy/orm/mapper.py
index 446f6790e..755d4afc7 100644
--- a/lib/sqlalchemy/orm/mapper.py
+++ b/lib/sqlalchemy/orm/mapper.py
@@ -56,6 +56,12 @@ from ..sql import util as sql_util
from ..sql import visitors
from ..util import HasMemoized
+try:
+ import dataclasses
+except ImportError:
+ # The dataclasses module was added in Python 3.7
+ dataclasses = None
+
_mapper_registry = weakref.WeakKeyDictionary()
_already_compiling = False
@@ -2632,7 +2638,7 @@ class Mapper(
return result
- def _is_userland_descriptor(self, obj):
+ def _is_userland_descriptor(self, assigned_name, obj):
if isinstance(
obj,
(
@@ -2643,7 +2649,14 @@ class Mapper(
):
return False
else:
- return True
+ return assigned_name not in self._dataclass_fields
+
+ @HasMemoized.memoized_attribute
+ def _dataclass_fields(self):
+ if dataclasses is None or not dataclasses.is_dataclass(self.class_):
+ return frozenset()
+
+ return {field.name for field in dataclasses.fields(self.class_)}
def _should_exclude(self, name, assigned_name, local, column):
"""determine whether a particular property should be implicitly
@@ -2656,16 +2669,19 @@ class Mapper(
# check for class-bound attributes and/or descriptors,
# either local or from an inherited class
+ # ignore dataclass field default values
if local:
if self.class_.__dict__.get(
assigned_name, None
) is not None and self._is_userland_descriptor(
- self.class_.__dict__[assigned_name]
+ assigned_name, self.class_.__dict__[assigned_name]
):
return True
else:
attr = self.class_manager._get_class_attr_mro(assigned_name, None)
- if attr is not None and self._is_userland_descriptor(attr):
+ if attr is not None and self._is_userland_descriptor(
+ assigned_name, attr
+ ):
return True
if (
diff --git a/lib/sqlalchemy/testing/requirements.py b/lib/sqlalchemy/testing/requirements.py
index 9b8caac2e..4114137d4 100644
--- a/lib/sqlalchemy/testing/requirements.py
+++ b/lib/sqlalchemy/testing/requirements.py
@@ -1143,6 +1143,10 @@ class SuiteRequirements(Requirements):
)
@property
+ def dataclasses(self):
+ return self.python37
+
+ @property
def cpython(self):
return exclusions.only_if(
lambda: util.cpython, "cPython interpreter needed"