123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154 |
- import re
- import sqlalchemy as sa
- from sqlalchemy import inspect
- from sqlalchemy.ext.declarative import DeclarativeMeta, declared_attr
- from sqlalchemy.schema import _get_table_key
- from ._compat import to_str
- def should_set_tablename(cls):
- """Determine whether ``__tablename__`` should be automatically generated
- for a model.
- * If no class in the MRO sets a name, one should be generated.
- * If a declared attr is found, it should be used instead.
- * If a name is found, it should be used if the class is a mixin, otherwise
- one should be generated.
- * Abstract models should not have one generated.
- Later, :meth:`._BoundDeclarativeMeta.__table_cls__` will determine if the
- model looks like single or joined-table inheritance. If no primary key is
- found, the name will be unset.
- """
- if (
- cls.__dict__.get('__abstract__', False)
- or not any(isinstance(b, DeclarativeMeta) for b in cls.__mro__[1:])
- ):
- return False
- for base in cls.__mro__:
- if '__tablename__' not in base.__dict__:
- continue
- if isinstance(base.__dict__['__tablename__'], declared_attr):
- return False
- return not (
- base is cls
- or base.__dict__.get('__abstract__', False)
- or not isinstance(base, DeclarativeMeta)
- )
- return True
- camelcase_re = re.compile(r'([A-Z]+)(?=[a-z0-9])')
- def camel_to_snake_case(name):
- def _join(match):
- word = match.group()
- if len(word) > 1:
- return ('_%s_%s' % (word[:-1], word[-1])).lower()
- return '_' + word.lower()
- return camelcase_re.sub(_join, name).lstrip('_')
- class NameMetaMixin(type):
- def __init__(cls, name, bases, d):
- if should_set_tablename(cls):
- cls.__tablename__ = camel_to_snake_case(cls.__name__)
- super(NameMetaMixin, cls).__init__(name, bases, d)
- # __table_cls__ has run at this point
- # if no table was created, use the parent table
- if (
- '__tablename__' not in cls.__dict__
- and '__table__' in cls.__dict__
- and cls.__dict__['__table__'] is None
- ):
- del cls.__table__
- def __table_cls__(cls, *args, **kwargs):
- """This is called by SQLAlchemy during mapper setup. It determines the
- final table object that the model will use.
- If no primary key is found, that indicates single-table inheritance,
- so no table will be created and ``__tablename__`` will be unset.
- """
- # check if a table with this name already exists
- # allows reflected tables to be applied to model by name
- key = _get_table_key(args[0], kwargs.get('schema'))
- if key in cls.metadata.tables:
- return sa.Table(*args, **kwargs)
- # if a primary key or constraint is found, create a table for
- # joined-table inheritance
- for arg in args:
- if (
- (isinstance(arg, sa.Column) and arg.primary_key)
- or isinstance(arg, sa.PrimaryKeyConstraint)
- ):
- return sa.Table(*args, **kwargs)
- # if no base classes define a table, return one
- # ensures the correct error shows up when missing a primary key
- for base in cls.__mro__[1:-1]:
- if '__table__' in base.__dict__:
- break
- else:
- return sa.Table(*args, **kwargs)
- # single-table inheritance, use the parent tablename
- if '__tablename__' in cls.__dict__:
- del cls.__tablename__
- class BindMetaMixin(type):
- def __init__(cls, name, bases, d):
- bind_key = (
- d.pop('__bind_key__', None)
- or getattr(cls, '__bind_key__', None)
- )
- super(BindMetaMixin, cls).__init__(name, bases, d)
- if bind_key is not None and getattr(cls, '__table__', None) is not None:
- cls.__table__.info['bind_key'] = bind_key
- class DefaultMeta(NameMetaMixin, BindMetaMixin, DeclarativeMeta):
- pass
- class Model(object):
- """Base class for SQLAlchemy declarative base model.
- To define models, subclass :attr:`db.Model <SQLAlchemy.Model>`, not this
- class. To customize ``db.Model``, subclass this and pass it as
- ``model_class`` to :class:`SQLAlchemy`.
- """
- #: Query class used by :attr:`query`. Defaults to
- # :class:`SQLAlchemy.Query`, which defaults to :class:`BaseQuery`.
- query_class = None
- #: Convenience property to query the database for instances of this model
- # using the current session. Equivalent to ``db.session.query(Model)``
- # unless :attr:`query_class` has been changed.
- query = None
- def __repr__(self):
- identity = inspect(self).identity
- if identity is None:
- pk = "(transient {0})".format(id(self))
- else:
- pk = ', '.join(to_str(value) for value in identity)
- return '<{0} {1}>'.format(type(self).__name__, pk)
|