I have a sqlalchemy class User
which inherits from Model
.
class Model:
@declared_attr
def __tablename__(self):
return self.__name__.lower()
_id = Column(Integer, primary_key=True, autoincrement=True)
in_utc = Column(BigInteger, default=time())
out_utc = Column(BigInteger, default=config['MAX_UTC'])
def to_dict(self):
return {k: v for k, v in vars(self).items() if not isinstance(v, InstanceState)}
class User(declarative_base(), Model):
email = Column(String)
password = Column(String)
name = Column(String)
The reason for the parent class is to add some columns that are common across the tables and also the to_dict() method which creates a dictionary from the columns.
However I do not want the password column to be included when calling user.to_dict().
Is there any way to annotate the password Column (like in Java reflection) so that to_dict() knows to ignore it?
For example:
class User(declarative_base(), Model):
email = Column(String)
[IgnoredInOutput()]
password = Column(String)
name = Column(String)
I've now overridden to_dict
in the User
class to remove the password column for this model.
Python 3.9 introduced Annotated
Type Hints which can be used to decorate types with context-specific metadata.
Annotated
takes at least two arguments. The first argument is a regular type (e.g. str
, int
, etc.), and the rest of the arguments is metadata. A type checker will only check the first argument, leaving the interpretation of the metadata to the application. A type hint like Annotated[str, 'ignored']
will be treated equally to str
by type checkers.
For example this will be a solutions with the new Annotated
metadata:
from sqlalchemy import Column, Integer, String, BigInteger
from sqlalchemy.ext.declarative import declarative_base, declared_attr
from sqlalchemy.orm.state import InstanceState
from typing import Annotated, _AnnotatedAlias
Base = declarative_base()
class Model:
@declared_attr
def __tablename__(self):
return self.__name__.lower()
_id = Column(Integer, primary_key=True, autoincrement=True)
in_utc = Column(BigInteger)
out_utc = Column(BigInteger)
def to_dict(self):
return {key: value for key, value in vars(self).items() if not isinstance(value, InstanceState) and key not in [var for var, th in self.__annotations__.items() if isinstance(th, _AnnotatedAlias) and 'ignored' in th.__metadata__]}
class User(Base, Model):
email: str = Column(String)
ignored: Annotated[str, 'ignored'] = Column(String)
ignored_additional: Annotated[str, 'extra', 'ignored'] = Column(String)
name = Column(String)
if __name__ == "__main__":
user = User()
user.name = "username"
user.ignored = "ignored"
user.ignored_additional = "ignored with additional metadata"
user.email = "username@email.com"
print(user.to_dict())
As you can see the type hints doesn't have to be included and normal type hints are also accepted without affecting the to_dict
function. Also other metadata besides ignored
can be added.