lee-pai-long
3/6/2018 - 10:49 AM

Base SQLalchemy model class with auto repr and tablename

"""Base SQLalchemy model class with auto repr and tablename.

Requires:
    - inflection(https://pypi.org/project/inflection/)
Optional:
    - pytest: The output is nicer than bare doctest

Check class docsting for how to use.

To run tests:

```
python -m doctest sqla_base.py
# Or
pytest sqla_base.py --doctest-modules
```
"""

import inflection
from sqlalchemy.ext.declarative import declared_attr


class Base:
    """Class to use with SQLAlchemy declaration_base().

    >>> import sqlalchemy as sql
    >>> from sqlalchemy.ext.declarative import declarative_base
    ...
    >>> Model = declarative_base(
    ...     cls=Base, bind=sql.create_engine('sqlite:///repr.db')
    ... )
    ...
    >>> class Person(Model):
    ...    id = sql.Column(sql.Integer, primary_key=True)
    ...    firstname = sql.Column(sql.String(80))
    ...    lastname = sql.Column(sql.String(80))
    ...
    >>> assert Person.__tablename__ == 'people'
    ...
    >>> person = Person(firstname='John', lastname='Doe')
    >>> assert repr(person) == "Person(firstname='John', lastname='Doe')"
    ...
    >>> class AnonymousPerson(Model):
    ...    id = sql.Column(sql.Integer, primary_key=True)
    ...
    >>> assert AnonymousPerson.__tablename__ == 'anonymous_people'
    """

    @declared_attr
    def __tablename__(cls):
        return inflection.underscore(inflection.pluralize(cls.__name__))

    def __repr__(self):
        return "{class_name}({attributes})".format(
            class_name=self.__class__.__name__,
            attributes=', '.join([
                "{attribute}={value}".format(attribute=key, value=repr(val))
                for key, val in sorted(self.__dict__.items())
                if not key.startswith('_')
            ])
        )